Около 15-16-го года, как раз когда Atmel перешла в руки Microchip, я рылся в параметрических таблицах AVR на новом сайте, пытаясь разобраться, «что мы обрели и что потеряли». И тогда обратил внимание на продукт с крайне неудачным, на мой взгляд, названием: ATmega328PB. Почти неотличимый по названию от всем знакомого по Arduino ATmega328P, он, однако, содержит намного больше «фарша»: по паре портов UART, SPI, TWI, два 8-битных и аж три 16-битных таймера; причем все это, в отличие от монстрообразного ATmega2560, в тех же габаритах и с тем же количеством выводов.

Я тогда еще подумал, что хорошо бы кто-нибудь догадался выпустить на этом деле что-то ардуиноподобное. В 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). По рекомендациям руководства она должна соответствовать следующей:



О назначении фьюза CFD
В ATmega324pb имеется удобная система предохранения при отсутствии тактового сигнала под названием CFD (Clock Failure Detection). В обычных AVR можно установить фьюзы так, что ISR-программирование станет невозможным: например, зеркальное по отношению к правильному (показанному на рис. выше) значение CKSEL[3:0] = 0000 означает тактирование от внешнего генератора, и при его отсутствии кристалл просто перестанет откликаться. Установка фьюза CFD включает схему, которая при отсутствии тактового сигнала автоматически переключит контроллер на внутренний генератор 128 кГц, поэтому устанавливать его или нет — на ваше усмотрение.

Настройка 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 пока не обращаем внимания, мы им займемся далее):



Настройка часового модуля
Часы должны быть предварительно настроены, в том числе и вывод SQW на выдачу частоты 1 Гц. Для облегчения этой процедуры в архиве по ссылке в конце статьи помещен скетч clock1307_set.ino. Кроме того, вам понадобится установленная библиотека 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)


  1. Alexeyslav
    25.04.2023 09:24

    Возможно, загрузчик не отвечает, потому что использует другой UART?


  1. 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.. там куда больше прелестей и возможностей, примерно "за те же бабки". ;)


    1. Rusrst
      25.04.2023 09:24

      Не все хотят разбираться с 32-х битными камнями, а автор в свое время приложил немаленькую толику к популяризации именно avr. Я вот с stm32 например не работал и если что-то будет нужно то буду собирать на avr/mcs-51. Просто потому что привычно. Да и сами avr однотипные и простые.


      1. 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? Да, интересная была ветка .. но сколько их "интересных" кануло в Лету? Где тот же Барроуз?


        1. Rusrst
          25.04.2023 09:24
          +1

          Наверное потому что обычный пользователь половину ваших слов не поймет, впрочем как и я т.к. я все эти реликты истории не застал.

          И потому, что обычному пользователю драйвер не нужен, ему нужно что-то измерить, отобразить, выполнить простую работу. А avr со своей простой архитектурой с этим справляется на ура.


          1. VladimirFarshatov
            25.04.2023 09:24

            Ну так вот как раз к этой "простой архитектуре" и было .. сколько их уже кануло в Лету? Зачем поддерживать то, что уже не актуально и сегодня-завтра снимется с производства, если уже не снято как Motorolla 6502 с его архитектурой "проще некуда"? Для тех кто не в курсе, считается как-бы прародителем всех RISC архитектур, хотя, кмк, истинный прародитель .. БЭСМ-6 или уж PDP-11, но это не точно. ;)


    1. aumi13
      25.04.2023 09:24
      +1

      с одной стороны чем гибче в настройке мк, тем удобнее подобрать нужный режим работы переферии, например связать работу таймера ацп и дма без участия ядра. но и обратная сторона - слишком сложные взаимосвязи и не всегда очевидные грабли.

      в тожэ самое время запустить таймер на PIC12 чтобы светиком помограть любой пионер освоит.


      1. VladimirFarshatov
        25.04.2023 09:24

        На СТМ есть вполне вменяемые ИДЕ, которые упрощают вхождение пионеров в процесс. Ровно также освоит...