Я тогда еще подумал, что хорошо бы кто-нибудь догадался выпустить на этом деле что-то ардуиноподобное. В Arduino всегда остро не хватало лишнего «железного» UART: родной занят общением с компом и загрузкой программ, а SoftwareSerial, как я убедился на собственном опыте, выручает далеко не всегда.
Но прошло почти десять лет, за это время развитие и профессионального и DIY-сектора свернуло в более производительную и удобную сторону 32-разрядных платформ, а на ATmega328PB так ничего и не появилось — казалось, он вообще остался незамеченным. И вот на этом фоне мне вдруг совершенно неожиданно предложили попробовать отладочную плату с контроллером, о котором я ничего ранее не знал: ATmega324PB. По названию можно догадаться, что это какой-то аналог упомянутого ATmega328PB — старая модель с увеличенным количеством «фарша». И все оказалось даже лучше, чем можно было ожидать.
Плата ATmega324
Сведения о плате можно разыскать на сайте https://www.as-kit.ru, в разделе платы ATmega324. Мне досталась версия 2.0, причем укомплектованная ЖК-дисплеем конфигурации 1602 (16 символов в 2 строки). Схема платы доступна в файле AS-mega324_v2_sch.pdf, руководство пользователя — в файле AS-mega324_v2_ug.pdf.
ATmega324PB имеет больше выводов, чем ATmega328PB (44-выводной TQFR), и дополнен третьим UART (один которых можно использовать в качестве второго SPI). Третий порт UART2 на плате подключен к USB-UART преобразователю CP2104. USB-порт выведен на гнездо miniUSB. Питаться плата может через USB или через внешний адаптер +7-12 вольт (стабилизатор LM1117IDT), в точности так же, как в Arduino Uno. 5-вольтовое питание развязано с помощью диодов Шоттки, потому имеет реальное значение около 4,7 вольта. Питание 3,3 вольта формируется, как в Arduino Nano — с помощью микросхемы преобразователя CP2104, потому при питании от внешнего адаптера не работает (этот недостаток исправлен в версии 3 платы).
Внешний вид лицевой стороны платы вы можете посмотреть на рисунке, заимствованном из руководства пользователя. На плате имеются места для подпайки дополнительной периферии: так, на лицевой стороне DD1 (см. рисунок) — это микросхема ADM3202 преобразования UART/RS-232, а DA2 — 8/10/12-разрядного ЦАП. На обратной стороне можно подпаять преобразователь UART/RS-485 ADM3485 (вместо ADM3202), кроме того, имеются посадочные места для памяти с последовательным интерфейсом I2C (AT24) или SPI (AT25). Разъемы J1-J4 совпадают по месту установки и, по возможности, функционально с аналогичными разъемами Arduino Uno, что позволяет приспособить некоторые «шилды».
Приятное дополнение — разъем (P2) для подключения стандартных HD44780-совместимых дисплеев по параллельному интерфейсу. В него уже распаяна гребенка PLS-16 и добавлены компоненты для подсветки и регулирования яркости-контраста ЖК-вариантов. При обычной ответной гнездовой части (PBS-16) дисплей загородит arduino-разъемы J1-J2, поэтому при желании подключить, например, I2C-датчики, дисплей придется устанавливать на разъем с длинными ножками (вот такими) или снабжать гибким шлейфом.
Для изучения некоторых возможностей контроллера ATmega324PB мы с помощью этой платы попробуем сделать промежуточную станцию по приему данных от внешних погодных датчиков. Сразу предупреждаю, что по полной программе здесь это не получится: функциональность такой станции предполагает накопление данных и выдачу их по запросу во внешнее устройство или в Интернет. Для этого следует снабдить плату EEPROM-памятью серий AT24 или AT25, отработать внешние программы сбора данных, спроектировать модули взаимодействия с Интернетом и так далее — такой проект выходит далеко за рамки не такой уж большой статьи. Потому здесь мы только ограничимся настройкой UART, подключением I2C-часов, приемом данных с одного внешнего датчика по радиоканалу и выводом всего этого на LCD-дисплей — в целях просто посмотреть, к чему пригоден ATmega324PB и как с ним можно обращаться.
Программирование
На плате ATmega324 установлен кварц 14,7456 МГц, что хорошо для организации стандартных скоростей UART с высокой точностью. Для программирования через Atmel Studio и другие среды, имеющие на выходе hex-файлы или приспособленные к интерфейсу JTAG, частота кварца не имеет значения. Но я первым делом попытался приспособить плату к программированию через привычное Arduino, что удалось мне только частично.
Для осведомления Ardino IDE о существовании такой сущности, как ATmega324PB, я остановился на пакете с претенциозным названием MightyCore. По ссылке вам расскажут, как установить его наиболее коротким путем (требуется Arduino IDE версии не ниже 1.6.5). После установки пакета вы обнаружите в перечне плат ряд Mega-контроллеров, среди которых будет и ATmega324:
После щелчка по этому пункту следует обратиться к меню «Инструменты», где следует установить пункты, подчеркнутые красным на рисунке:
Bootloader мне заставить работать не удалось (поэтому на рисунке соответствующий пункт красным не подчеркнут) — ни при загрузке через Arduino (точнее, через avrdude), ни вручную. Конфигурацию fuse-бит (файл boards.txt) и соответствующий hex-файл можно разыскать в недрах папки установленного пакета Arduino\packages\MightyCore. Записанный загрузчик, однако, не откликается. Я более чем уверен, что дело в MightyCore, запутавшемся в нестандартной частоте тактирования, но тратить время на утомительное изучение настроек avrdude не стал, как и искать другие пакеты. В конце концов, мы всего лишь изучаем возможности платы, потому обойдемся пунктом Скетч | Экспорт бинарного файла в совокупности с внешним ISP-программатором (использовать, разумеется, следует hex-файл с более коротким именем, без добавки «with_bootloader» в названии). Я традиционно использую программатор под названием AS-4E, из того же источника, что и описываемая плата (именно в прилагаемой к нему программе ASISP сделаны скриншоты состояния fuse-бит, показанные далее).
Более огорчительно, что из-за подключения USB-адаптера к порту UART2 стандартный
Serial
через miniUSB-кабель не работает. Можно подключить к порту USB0 (разъем J3) внешний адаптер и все будет в порядке, но при этом два UART из трех окажутся занятыми. Не работает также Reset при запуске Монитора порта. Все остальные системные функции вполне работоспособны: все эти millis()
и delay()
, а также библиотеки Wire
, EEPROM
, SPI
и даже SoftwareSerial
. Перед загрузкой hex-файла через ISP-программатор, проверьте конфигурацию fuse-бит (особенно, если вы перед этим пытались добиться работы bootloader). По рекомендациям руководства она должна соответствовать следующей:
Настройка UART2 для общения с компьютером
Коли стандартная
Serial
у нас не работает, попробуем первым делом отладить работу через UART2 для общения с компьютером. Код, приведенный далее, частично заимствован из примера, размещенного в архиве на сайте разработчика. Для настройки сначала явно объявим частоту тактирования (заодно при нужде будут правильно работать системные временные функции типа _delay_ms()
) и на ее основе рассчитаем число для настройки делителя частоты UART с заданной скоростью обмена. Для совместимости со стандартными настройками Монитора порта Arduino скорость зададим равной 9600:#define F_CPU 14745600UL
#define BUAD 9600
#define BRC ((F_CPU/16/BUAD) - 1)
#define LED0 (1<<5)
Кроме настроек порта, здесь определяется пользовательский светодиод LED0, который у нас будет сигнализировать о работе программы (он подключен к порту PD5). В функции
setup()
сначала задается режим LED0, а затем настраивается UART2:void setup() {
DDRD |= LED0;
PORTD |= LED0;
UBRR2H = (unsigned char) (BRC >> 8); // порт UART2, скорость = BUAD
UBRR2L = (unsigned char) BRC;
UCSR2B |= (1 << TXEN); //разрешение передачи
UCSR2C |= (1 << UCSZ1) | (1 << UCSZ0); //8 бит
}
Дальше необходимо создать процедуру корректной передачи через UART с отслеживанием освобождения буфера:
void out_uart2(byte bb) //посылка UART2 с ожиданием готовности
{
while (!bitRead(UCSR2A,UDRE));//ожидаем очистки бита UDRE
UDR2=bb; //посылаем байт
}
Для пересылки одного-двух байт (как в примере с сайта) это не имеет значения, а при большем количестве без отслеживания готовности буфера посылаемые байты будут теряться.
На основе этой процедуры создадим последовательность действий при отсылке произвольного числа в ASCII-виде. Число (целое со знаком любого типа) назовем
t_time
и ограничимся тремя десятичными разрядами:byte t_10=0;
//выводим число t_time, преобразованное в ASCII
//ведущие нули не выводим
if (t_time<0) char_lcd('-');
t_time=abs(t_time); //отбрасываем знак
t_10=t_time/100; //старший
if (t_10!=0) out_uart2(t_10+0x30);
t_10 = t_time/10;
if (t_10!=0) out_uart2(t_10%10+0x30); //средний
t_10=t_time%10;
out_uart2(t_10+0x30); //младший
Если число заведомо положительное (типа
byte
(uint8_t
) или word
(uint16_t
), например), то строки с выводом знака и его отбрасыванием можно опустить. Процедура вывода положительного числа с двумя десятичными разрядами и с выводом ведущего нуля короче (она нам понадобится в дальнейшем при выводе значений даты и времени):byte t_10;
//выводим с ведущими нулями:
t_10=date/10; //старший data
out_uart2(t_10+0x30);
t_10=date%10; //младший data
out_uart2(t_10+0x30);
В этих процедурах прибавление числа 0x30 означает перевод значения цифры в ее ASCII-код. Символы можно указывать непосредственно в прямых кавычках:
out_uart2(';'); // точка с запятой
Для перевода строки (аналог
println()
) выполним вывод двух символов «перевод строки-возврат каретки»:out_uart2(0x0A); //перевод строки 0x0A
out_uart2(0x0D); //возврат каретки 0x0D
Программа Proba_UART.ino, которую вы найдете в архиве по адресу, указанному в конце статьи, каждую секунду выводит в компьютер значение секунд, прошедших с момента запуска (рассчитанное на основе
millis()
), а также традиционную строку «Hello World». При каждом выводе светодиод LED0 переключается в противоположное состояние.Восстановив таким образом простейшую функциональность последовательного порта для связи с
компьютером, попробуем подключиться к интерфейсу I2C.
Подключение часов DS1307
Выводы SDA и SDL первого (нулевого) I2C расположены на разъеме J4 (крайние контакты 9 и 10, так же, как у Arduino Uno, именно на них работает библиотека Wire). Кроме того, на разъеме J3 имеются две пары свободных выводов (7,8 и 3,4), на которые можно вывести питание. Наиболее удобным способом такой доработки будут перемычки с обратной стороны платы к имеющимся поблизости контактам питания, показанные на рисунке ниже. В результате мы имеем питание Vcc на контактах 4 и 8, и «землю» на контактах 3 и 7.
Нам понадобится обычный arduino-модуль RTC DS1307 или DS3231, но обязательно с выведенным контактом SQW, на которым формируется заданная частота (у DS1307 по умолчанию это 1 Гц, у DS3231 частоту надо специально настраивать). Через два контакта от выводов SCL/SDA, на контакте 6 разъема J4, имеется вывод PB2, соответствующий внешнему прерыванию INT2. Подключив к нему SQW, настроенный на 1 Гц, мы сможем настроить внешнее прерывание для отсчета секунд внутри контроллера — так удобнее, чем каждый раз выполнять относительно долгую процедуру чтения значений непосредственно из часов.
Подключение RTC-модуля к плате ATmega324 с учетом доработки контактов питания показано на рисунке (на приемопередатчик HC-12 пока не обращаем внимания, мы им займемся далее):
RTClib.h
. В тексте функции setup()
вы увидите закомментированные строки настройки. Инструкции по тому, какие именно две строки следует расскомментировать в зависимости от имеющегося модуля, помещены в начале скетча. Вы подключаете модуль часов к любому стандартному Arduino, загружаете скетч с расскомментироваными настройками, потом, не выключая Arduino и не перезапуская Монитор порта, закомментируете строки с настройками опять и тут же перезаписываете скетч заново. Смысл этих действий в том, что в момент компиляции в программу переносятся значения часов из компьютера, которые при выполнении setup()
запишутся в часы. Чтобы одни и те же значения не записывались в часы каждый раз при перезапуске Arduino, следует установочные процедуры немедленно удалить, что мы и выполняем при повторной загрузке скетча с заново закомментированными настройками.Продублируем содержимое, касающееся инициализации UART2, в новый скетч под именем Atm324pb_Clock_Int.ino. Прежде чем читать значения времени, подготовим для этого сцену: в начале программы добавим
#include <Wire.h>
, а в функцию setup()
строку инициализации Wire.begin()
. Заведем глобальные переменные (спецификатор volatile
обязателен тем, что будут меняться в прерываниях): byte second, minute, hour, date, month, year;
volatile byte sek=0; //внутренний отсчет секунд
volatile byte Flag=0; //флаг выполняемого действия
Затем настроим прерывание INT2 по нисходящему фронту, записав в
setup()
следующие строки: EICRA |= (1<<ISC21); //включим прерывания INT2 по спаду
EIMSK |= (1<<INT2); //разрешим внешние прерывания INT2
Обработчик прерывания INT2:
ISR(INT2_vect) //прерывание INT2 (вывод PB2)
{
PORTD ^= LED0; // мигание светодиодом LED1
sek++; //внутренний отсчет секунд
if (sek==60) sek=0;
if (sek%4==0) Flag=2; //каждые 4 сек будем выводить
if (sek==0) Flag=1; //в начале минуты прочтем часы и синхронизируемся
}
Из этого кода следует, что при значении
Flag
, равного 1 (т.е. в начале каждой минуты, когда sek
равно 0), мы будем читать значения часов из модуля, а при Flag
, равном 2 (т.е. при значении секунд, кратном 4) — выводить их в порт. Это мы будем делать в основном цикле, чтобы не затягивать прерывания (их у нас еще добавится в дальнейшем).Чтение часов с помощью библиотеки Wire не представляет каких-то особенностей, но прежде нужно вспомнить, что все данные о времени в микросхемах RTC записаны в упакованном BCD-коде. Функцию преобразования в обычное число я банально заимствовал из библиотеки
RTClib
, где это сделано максимально лаконично:uint8_t bcd2bin (uint8_t val) {return val - 6 * (val >> 4); }
Окончательно процедура чтения будет выглядеть таким образом:
if (Flag==1) { //чтение часов каждую минуту
Flag=0; //только один раз
Wire.beginTransmission(0x68); // Start I2C protocol with DS1307 address
Wire.write(0); // Send register address
Wire.endTransmission(false); // I2C restart
Wire.requestFrom(0x68, 7); // Request 7 bytes from DS1307
second = bcd2bin(Wire.read()); // Read seconds from register 0
minute = bcd2bin(Wire.read()); // Read minuts from register 1
hour = bcd2bin(Wire.read()); // Read hour from register 2
Wire.read(); // Read day-of-week from register 3 (not used)
date = bcd2bin(Wire.read()); // Read date from register 4
month = bcd2bin(Wire.read()); // Read month from register 5
year = bcd2bin(Wire.read()); // Read year from register 6
sek=second; //синхронизация секунд
}
Как видим, при выполнении чтения переменная
Flag
сразу обнуляется. Если мы вставим этот код в функцию loop()
, то он при нулевом значении секунд выполнится только один раз. Последним оператором мы синхронизируем внутреннее значение секунд sek
со значением second
, прочитанным из часов.Следующим блоком внутри
loop()
мы каждые 4 секунды выводим полученные значения через ранее настроенный UART2:if (Flag==2) { //каждые 4 сек выводим
Flag=0; //только один раз
byte t_10;
//выводим с ведущими нулями:
t_10=date/10; //старший data
out_uart2(t_10+0x30);
t_10=date%10; //младший data
out_uart2(t_10+0x30);
out_uart2('.'); //точка
. . . . . < аналогично месяц, год, а также после пробела часы и минуты через двоеточие и заканчиваем секундами: >
out_uart2(':');
t_10=sek/10; //старший sec
out_uart2(t_10+0x30);
t_10=sek%10; //младший sec
out_uart2(t_10+0x30);
out_uart2(0x0A); //перевод строки 0x0A
out_uart2(0x0D); //возврат каретки 0x0D
}//end sek%4=0
Полностью программу Atm324pb_Clock_Int.ino вы можете найти в архиве по адресу, указанному в конце статьи. После закачки программы в компьютер через USB начнут выдаваться строки, сначала содержащие только нули. Но не беспокойтесь: не позднее, чем через минуту, часы синхронизируются и начнет выдаваться полноценное дата-время в формате по образцу «21.04.23 07:31:24».
Теперь научим плату наряду с чтением часов принимать данные с приемопередатчика HC-12.
Подключение приемопередатчика HC-12
Как я уже писал ранее, приемопередатчик HC-12 хорош двумя особенностями: во-первых, он работает на частоте 433 МГц, обладающей несравненно более высокой проникающей способностью через препятствия, чем Wi-Fi со своими 2,4 ГГц. Во-вторых, он не требует никакой настройки и никаких библиотек, и в качестве удлинителя UART работает прямо из коробки на стандартной скорости 9600.
Внешний датчик с радиоинтерфейсом
Для дальнешего нам будет нужен отдельный передатчик, считывающий погодные данные и посылающий их через такой же HC-12. В файле datchik_WDT_SHT20_8x4s.ino вы найдете скетч такого передатчика, который можно соорудить на основе любого стандартного Arduino (Uno, Nano, Mini). В данном случае он рассчитан на подключение датчика SHT20/21, но заменив ссылку на библиотеку, вы можете использовать любой другой. Передатчик HC-12 подключается через
SoftwareSerial
на контактах Arduino D3 (RxD контроллера, подключается к TxD HC-12) и D4 (TxD, подключается к RxD HC-12). Программа тактируется прерываниями WatchDog, настроенного на пробуждение каждые 8 секунд. Каждое четвертое такое прерывание (т.е. примерно каждые 32 с) считываются данные с I2C-датчика, преобразуются в удобный целый положительный формат (к температуре, умноженной на 10, прибавляется произвольно выбранное число 2731; влажность без бесполезных десятых занимает один байт и посылается напрямую), и побайтно посылаются через HC-12. При этом коротко подмигивает светодиод L. Посылка предваряется индивидуальной сигнатурой датчика, в данном случае символами 'G', 'H' и 'T', и заканчивается точкой с запятой для надежного определения конца посылки. Arduino с загруженным скетчем, подключенным датчиком и модулем HC-12, запитывается от отдельного источника и откладывается в сторону во включенном состоянии.
Приемник на плате ATmega324
В нашей конструкции на плате ATmega324 модуль HC-12 мы подключим к UART0 (контакты 1 и 2 разъема J3 — RXD0 и TXD0 соответственно). Напоминаем, что выводы RxD и TxD контроллера и модуля соединяются перекрестно (RxD модуля к TxD контроллера и наоборот, см. схему выше). Можно вспомнить, что в Arduino для UART0 действует стандартная библиотека
Serial
, но мы в целях единообразия будем также настраивать UART0 напрямую, что несложно сделать, так как все три порта абсолютно одинаковы, и различаются лишь номерами в наименованиях регистров.Сохраним созданный в предыдущем примере скетч под новым названием Atm324pb_Clock_Int_priemnik_HC-12.ino. Заведем дополнительно следующие переменные:
volatile byte count_rx=0; //счетчик принятых байт (должно быть 7)
volatile byte arr[7]; //приемный массив
volatile int value=0; //целая температура в десятых градуса
volatile byte hum=0; //влажность
В функцию
setup()
добавим следующие строки для инициализации UART0://заводим порт UART0 для для приема данных через HC-12
UBRR0H = (unsigned char) (BRC >> 8); // порт UART2, скорость = BUAD
UBRR0L = (unsigned char) BRC;
//передатчик+приемник+прерывание прием окончен:
UCSR0B = (1 << TXEN)|(1 << RXEN)|(1 << RXCIE);
UCSR0C = (1 << USBS) | (1 << UCSZ1) | (1 << UCSZ0);
Передача через UART0 нам здесь не потребуется, но она может понадобиться при организации обмена с датчиками, кода их больше одного. Прерывание «прием окончен» позволит нам воспроизвести функциональность
Serial.available()
— немедленное обнаружение чего-то пришедшего через порт. Обработчик прерывания каждые ~30 секунд должен принять 7 байт данных: три байта сигнатуры («GHT»), два байта целочисленной температуры, один байт влажности и точку с запятой для окончания передачи. Обработчик выглядит следующим образом:
ISR(USART0_RX_vect) //прерывание прием окончен
{
arr[count_rx]=UDR0;
if (arr[count_rx]==';') {
Flag=3; count_rx=0; //конец приема будем обрабатывать
if ((arr[0] == 'G') && (arr[1] == 'H') && (arr[2] == 'T'))
//если первые три символа = "GHT"
{
value = arr[4] * 256 + arr[3]; // формируем двухбайтовое число
value = value-2731; //целое число десятых градуса со знаком
hum=arr[5];
}
} else count_rx++;
}
На выходе мы имеем
value
— целое десятичное число со знаком, представляющее температуру в десятых градуса (абсолютная величина 3 десятичных разряда) и величину влажности hum
. Вывод этих данных по значению Flag
= 3 будем производить в главном цикле loop()
:if (Flag==3) {
byte t_10;
//выводим температуру:
if (value<0) out_uart2('-');
else out_uart2('+');
value=abs(value); //отбрасываем знак
//3-разрядное число value отображаем с фиксированной точкой
//без ведущего нуля в старшем разряде
t_10=value/100; //старший
if (t_10!=0) out_uart2(t_10+0x30);
t_10 = value/10;
out_uart2(t_10%10+0x30); //средний
out_uart2('.'); //отделяем десятые
t_10=value%10;
out_uart2(t_10+0x30); //младший
out_uart2(0xB0); //стандартный ASCII-градус
out_uart2(0x20); //пробел
t_10=hum/10; //старший влажн
out_uart2(t_10+0x30);
t_10=hum%10; //младший влажн
out_uart2(t_10+0x30);
out_uart2('%');
out_uart2(0x0A); //перевод строки 0x0A
out_uart2(0x0D); //возврат каретки 0x0D
Flag=0;
} //end Flag=3
Закачайте в плату программу Atm324pb_Clock_Int_priemnik_HC-12.ino из архива. Убедитесь, что внешний датчик включен и каждые ~30 секунд подмигивает светодиодом, посылая данные. Монитор порта по истечении периода синхронизации часов и приема первого значения от датчика, каждые 4 с будет выдавать время и по мере приема данных от датчика выдавать температуру-влажность по образцу «+28.8° 21%».
Обратим внимание на код значка градуса (0xB0) — в принципе любой нормальный Windows-редактор должен отобразить его адекватно (°), однако окно Монитора порта Arduino к нормальным не относится. Чтобы увидеть значок, вы должны либо воспользоваться альтернативным монитором порта, либо скопировать строки из окна и вставить их, например, в Блокнот.
Подключение ЖK-дисплея
Как мы говорили, разработчик платы ATmega324 уже все приготовил для подключения HD44780-совместимых дисплеев. Снабдив стандартный ЖК-дисплей гнездовым разъемом на 16 контактов, вы подключаете его в разъему P2. И что делать дальше, коли привычная
LiquidCrystal
, скорее всего, компилироваться откажется, а даже если не откажется, то непонятно, какие выводы указывать при ее инициализации?Поэтому мы просто распишем все необходимые для обращения с HD44780-совместимыми дисплеями функции с нуля. Большую их часть я заимствовал из прилагаемого к плате примера и вынес в отдельный файл под названием lcd_proc.c. Этот текст присоединяется к проекту Arduino в отдельной вкладке, чтобы не загромождать основной текст скетча. Для этого сначала создайте новый скетч (назовем его proba_LCD), затем нажмите на треугольную стрелку справа под значком Монитора порта и введите название файла. Файл должен размещаться в той же папке, что и текст скетча в ino-файле, то есть в данном случае в папке proba_LCD. Его следует подключить к скетчу обычным способом:
#include "lcd_proc.c"
.Расписывать все процедуры нет смысла — это стандартный алгоритм, тот же, что реализован в библиотеке
LiquidCrystal
. Для вывода данных на дисплей имеются две функции — вывода одного символа char_lcd()
и целой строки str_lcd()
. Функция clearlcd()
очищает экран, функция setpos(<позиция>,<строка>)
установит невидимый курсор на позицию, в которой будет отображен первый символ в следующей операции вывода. Проверочный скетч вы найдете в примере proba_LCD.ino из архива, он выдаст на экран десять цифр, значок градуса и традиционное приветствие:Обратим внимание, что значок градуса здесь имеет код 0xDF, он взят из стандартной таблицы знакогенератора контроллера HD44780.
Вывод данных с датчика на ЖК-дисплей
Пример с приемом данных и отсылкой их через последовательный порт в компьютер, который мы составляли в разделе «Приемник на ATmega324» легко переделать для отображения на дисплее. Поскольку и там и там передача осуществляется побайтно, то в принципе достаточно просто заменить название функции
out_uart2
на char_lcd
и будет выдаваться все то же самое, только на дисплей, а не в Монитор порта.Но нам, конечно, «все то же самое» на дисплее не нужно: нет никакой необходимости демонстрировать секунды и вообще затеивать долгую процедуру смены показаний до изменения основной информации. Поэтому мы перепишем обработчик прерывания INT2 следующим образом:
ISR(INT2_vect) //прерывание INT2 (вывод PB2)
{
// PORTD ^= LED0; // мигание светодиодом LED1
sek++; //внутренний отсчет секунд
if (sek%2==0) //мигалка двоеточием
{setpos(11,0); //11 позиция, верхняя строка
char_lcd(':');} //четная секунда - двоеточие
else
{setpos(11,0); //11 позиция, верхняя строка
char_lcd(' ');} //нечетная - пробел
if (sek==60) sek=0;
// if (sek%4==0) Flag=2; //каждые 4 сек будем выводить
if (sek==0) Flag=1; //в начале минуты прочтем часы и синхронизируемся
}
Здесь мы закомментировали установку
Flag=2
каждую четвертую секунду, но добавили процедуру, которая каждую секунду будет мигать двоеточием в позиции между часами и минутами. В принципе можно мигалку выполнить и с помощью встроенной в HD44780 функциональности, но, во-первых, не все дисплеи выполняют эту функцию адекватно, во-вторых, мы будем лишены возможности регулировать частоту мигания, которая обычно оказывается великовата.По значению
Flag
=1 в начале каждой минуты, как и прежде, значения даты-времени обновляются и синхронизируется значение секунд. По приему данных от датчика в прерывании UART0 «прием окончен» устанавливается Flag=3
, по которому выводится сразу и дата-время и принятые данные о температуре-влажности. Если данные о погоде придут раньше синхронизации часов, то первый раз на месте даты-времени отобразятся нули, через минуту все придет в порядок. Обратите внимание на закомментированную мигалку LED0 — выводы обоих светодиодов совпадают с управляющими выводами ЖК-экрана и на всякий случай во избежание конфликтов мигание было остановлено — тем более, что при наличии дисплея оно не требуется.
Пример Atm324pb_LCD_Clock_Int_priemnik_HC-12.ino вы найдете в архиве по адресу, указанному ниже. В тексте оставлена инициализация UART2 и закомментированы места, относящиеся к выводу через него в компьютер (для возможного контроля). После запуска на дисплее отобразится надпись «Wait…», которая через некоторое время сменится данными:
Архив с программами доступен по этому адресу. Автор приносит благодарность Николаю Королеву с сайта www.as-kit.ru за предоставленную отладочную плату ATmega324, консультации и правку документации по моим скромным замечаниям.
Комментарии (12)
VladimirFarshatov
25.04.2023 09:24-2Не понял в чем цимес этой отладочной платы, да и эти чипы популярными так и не стали, несмотря на допонения железом. Кмк, для типовых и простых применений, традиционной Нано хватает вполне. Для расширенных Mega2560 не настолько дорого, насколько избыточно. Ну и опять же, между ними просто тьма контроллеров из линеек STM и не только.
Если очень надо, то вот ещё вам для разнообразия: https://vk.com/id484853030 размер 56х88мм, с доп. платой имеем 520кб практически полноценного ОЗУ (512кб - страничная адресация по 64,32,16кб в имеющемся окне 64кб + 8кб родных) в дополнение к 256кб flash меги.. На доп. плате также собран SD-ридер для карточек.
Надо? Не думаю, ибо проще взять что-то по современнее на STM32.. там куда больше прелестей и возможностей, примерно "за те же бабки". ;)
Rusrst
25.04.2023 09:24Не все хотят разбираться с 32-х битными камнями, а автор в свое время приложил немаленькую толику к популяризации именно avr. Я вот с stm32 например не работал и если что-то будет нужно то буду собирать на avr/mcs-51. Просто потому что привычно. Да и сами avr однотипные и простые.
VladimirFarshatov
25.04.2023 09:24Так мы все когда-то приложили ручки к чему-то, чего сейчас нет и в помине. Что теперь, упираться и писать на Си под МСДОС (CP/M)? Ну было у меня к нему "диспетчер параллелизации исполнения" вкупе с "библиотекой взаимодействия процессов по Хоару" (современные каналы в Go, а раньше задачи в Ада, с нее и писалось) .. ну было у меня ко всему этому щастью ещё и стек OSI от драйвера до сеансового уровня, а в прикупе драйвер видеокарты EGA на 12-16 кадров в секунду .. и что теперь?
И МСДОС канул в Лету и писюки 286-е для которых это все писалось умерли раньше чем дописал до полноценной ОС.. а ведь тема с сегментными регистрами 286-х была крайне интересной в свое время!
Кстати, а почему на AVR, а не на Zilog? Спектрум - разве не наше всё? ;) Или уж тогда 8080 (КР580ИК01) тут хоть отечественный вариант есть, можно обьяснить "импортозамещением".. Кстати о последнем: есть российские и вполне годные аналоги ранних STM и даже с "развитием"..
Я же не для рекламы дал ссыль, а как пример того, что можно сваять на Mega2560 - очень крутая железка, только .. никому не нужная, ибо пока ваял в школах и кружках уже успели перейти на иные варианты решений, где всё это уже есть "из каропки". Начинал делать подобное решение для Mega-128A для которой тоже есть "российский аналог", но забросил, т.к. уже старо и никому не надо.. под все тот же MigthyCore, который доводил до ума с ИДЕ 1.6.7
В чем смысл упираться в древний AVR? Да, интересная была ветка .. но сколько их "интересных" кануло в Лету? Где тот же Барроуз?
Rusrst
25.04.2023 09:24+1Наверное потому что обычный пользователь половину ваших слов не поймет, впрочем как и я т.к. я все эти реликты истории не застал.
И потому, что обычному пользователю драйвер не нужен, ему нужно что-то измерить, отобразить, выполнить простую работу. А avr со своей простой архитектурой с этим справляется на ура.
VladimirFarshatov
25.04.2023 09:24Ну так вот как раз к этой "простой архитектуре" и было .. сколько их уже кануло в Лету? Зачем поддерживать то, что уже не актуально и сегодня-завтра снимется с производства, если уже не снято как Motorolla 6502 с его архитектурой "проще некуда"? Для тех кто не в курсе, считается как-бы прародителем всех RISC архитектур, хотя, кмк, истинный прародитель .. БЭСМ-6 или уж PDP-11, но это не точно. ;)
aumi13
25.04.2023 09:24+1с одной стороны чем гибче в настройке мк, тем удобнее подобрать нужный режим работы переферии, например связать работу таймера ацп и дма без участия ядра. но и обратная сторона - слишком сложные взаимосвязи и не всегда очевидные грабли.
в тожэ самое время запустить таймер на PIC12 чтобы светиком помограть любой пионер освоит.
VladimirFarshatov
25.04.2023 09:24На СТМ есть вполне вменяемые ИДЕ, которые упрощают вхождение пионеров в процесс. Ровно также освоит...
Alexeyslav
Возможно, загрузчик не отвечает, потому что использует другой UART?