В первой части рассказа о контроллере LGT8F328P китайской фирмы Logic Green рассказывалось об этом контроллере, как замене классического Arduino, а также об использовании в Arduino-среде некоторых его расширенных возможностей. В этой части пойдет речь о программировании LGT8F328P на самом низком уровне: на AVR-ассемблере. Это лучше позволит понять его устройство, отличия от AVR и тонкости программирования тех или иных составляющих.


Для написания и загрузки ассемблерных программ, как в AVR, так и в LGT8F328P необходимо обзавестись некоторыми специальными инструментами. Здесь не очень важно, какую именно среду использовать (любую из привычных вам, если вы справитесь с интеграцией в нее LGT8F328P), лишь бы она умела производить hex-файлы.


Программирование LGT8F328P на ассемблере


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

Авторский способ программирования на AVR-ассемблере
Здесь автор излагает свой привычный способ програмирования МК на ассемблере, не уговаривает кого-то следовать именно ему, и не вступает в дискуссии по преимуществам того или иного способа. Есть множество программных инструментов для этой цели, и придерживаясь неизменной политики использования наиболее простых средств, напоминаю, что для подготовки текстов ассемблерных программ можно использовать любой редактор, умеющий создавать «чистый текст», включая стандартный Блокнот Windows. В архиве имеется папка с древним ASM Editor с подсветкой синтаксиса и возможностью запуска ассемблирования прямо из редактора. Устанавливать его не требуется: просто разверните архив ASMEDIT в удобное место на диске, причем желательно не в Program Files, а в отдельную папку.

Для того, чтобы подсветка синтаксиса для LGT8F328P заработала, необходим соответствующий конфигурационный файл, он называется AVR.shk и уже имеется в архиве с программой. Если подсветка не работает как надо (например, вы скачали ASM Editor заново из Сети), то после первого запуска программы этот файл следует подключить через меню Highlight > Add new. Файл содержит также имена большинства регистров расширенных версий AVR, а имена, специфические именно для LGT8F328P, будут подсвечиваться темно-красным.

Ассемблер avrasm2 можно извлечь из Atmel Studio или разыскать в недрах сайта MicroChip. Пример простейшего bat-файла для запуска ассемблера avrasm2 в командной строке:
c:\<наименование папки с ассемблером>\avrasm2 -fI %1.asm
Для LGT8F328P здесь ничего менять не требуется. Поместите bat-файл не слишком далеко от ASM Editor (редактор не любит длинных путей, в том числе и к компилируемым asm-файлам), и укажите путь к нему через меню Service > Properties, вкладка Project, строка Assemble ASM file. Компиляция текущего текста программы будет происходить по нажатию сочетания клавиш <Alt+A>, после чего выскакивает DOS-окно с результатами или с перечнем ошибок.

Кроме ассемблера и редактора, вам понадобится inc-файл с определениями мнемонических имен регистров и других констант. В принципе такие файлы можно генерировать автоматически с помощью Atmel Studio, но нет уверенности, что исходные xml-файлы здесь выполнены по всем правилам, потому автор провел эту работу вручную, на основе файла m328pdef.inc, дополнив его информацией из описания LGT8F328P и приведя в соответствие некоторые наименования из исходного файла. Файл под названием lgt8f328Pdef.inc также расположен в архиве к статье. Его требуется размещать в той же папке, в которой расположен asm-файл с исходным кодом.

Программатор


Для загрузки в микросхему LGT8F328P полученных в результате компиляции hex-файлов потребуется специальный программатор, способный работать через интерфейс SWD, о котором говорилось в предыдущей части. Мы уже упоминали, что в качестве такого программатора можно использовать Arduino Uno/Nano со специально загруженным скетчем. Но удобнее, конечно, приобрести отдельный USB-программатор, например LGTSWDICE, предлагаемый множеством источников на Ali Express. Его внешний вид показан на фото в начале статьи и на рис. 3 далее.

Не забудьте только проверить его комплектацию соединительным кабелем, который должен иметь двухрядный 10-контактный разъем-гнездо PBS-10 на стороне программатора и 5-контактный PLS-5 или PBS-5 на стороне устройства. Если кабеля не достанете, то его можно соорудить из старого шлейфа от COM-порта или заменить на отдельные провода из комплекта перемычек для беспаечной макетной платы. Схема подключений программатора к LGT8F328P в 32-контактном корпусе приведена на рисунке:


Рис. 1. Принципиальная схема подключения программатора LGTSWDICE к микросхеме LGT8F328P (в скобках указаны номера выводов 32-контактного корпуса).

Программатор имеет 5-вольтовое питание от USB, которое при подключении к компьютеру выдается на вывод 5V0. Согласно показанной схеме, контроллер во время программирования будет запитан от того же источника. При этом, конечно, свое питание контроллера, если оно имеется, требуется отключать. Во избежание неприятностей можно не соединять вывод программатора 5V0 с выводом Vcc контроллера, но тогда не следует забывать запитывать плату контроллера от своего источника.

О низковольтном питании
Проблемы возникнут, если вы программируете контроллер прямо в схеме, питающейся от напряжения 3,3 вольта (существует много периферии, требующей такого питания и для экономии батареек полезно). В этом случае придется изготовить переходник с преобразователем уровня на выводы SWD и SWC (лучше на основе специализированных микросхем, например, SN74LVC2T45 или ADG3301). Вывод RST работает заведомо в одну сторону (от программатора к контроллеру) потому его можно для экономии развязать просто диодом (анодом к RST контроллера, с подтяжкой этого вывода к питанию контроллера резистором 1-5 кОм). В AVR вывод Reset допускает подачу повышенного напряжения, а про LGT8F328P в этом отношении ничего неизвестно.

Заметим также в скобках, что интерфейс UART при разных уровнях питания тоже требует подобного сопряжения как минимум на линии TxD (высокое питание) –> RxD (низкое питание), а вот I2C, работающий на шине с «открытым коллектором», при напряжении низковольтной части не менее 3 В в специальном сопряжении не нуждается, для него достаточно, чтобы подтягивающие резисторы были присоединены к более низкому питанию.

Для загрузки потребуется программа-загрузчик, в качестве которой рекомендуется LGTMix_isp (http://www.lgtic.com/upload/tools/lgtmix_isp, в момент написания этих строк последняя версия называлась LGTMix_ISP v3.7.4). В Windows 7 потребуется драйвер SWDISP_mkII (http://www.lgtic.com/upload/tools/swdice_mkii/), в Windows 10 драйвер не нужен. Разверните архив с программой в любую папку и запустите exe-файл. Если предварительно контроллер подключен к программатору, вставленному в USB, то программа его обнаружит автоматически и покажет следующее окно:


Рис 2. Программа LGTMix_isp

Щелкнув по надписи Program слева, укажите необходимый hex-файл. После этого загрузите программу нажатием на Write внизу справа. Никаких установок в программе делать не нужно.

К сожалению, Logic Green не позаботилась о том, чтобы выпустить LGT8F328P в удобном для макетирования DIP-корпусе. Для отладки программ и прототипирования устройств на низком уровне можно использовать аналоги Arduino Mini (например, две крайние справа платы в верхнем ряду на рис. 1 в первой части статьи), однако на них много лишнего, и потому стоит собрать свою макетную плату, к которой можно быстро и просто подключать различную периферию. Вариант такой платы на основе имеющейся в продаже универсальной макетки для QFR-корпусов вместе с подключенным программатором LGTSWDICE показан на фото:


Рис 3. Макетная плата с контроллером LGT8F328P в 32-контактном корпусе и с подключенным программатором LGTSWDICE

На этой плате все 32 контакта микросхемы с помощью подпаянных с нижней стороны платы проводочков выведены на два двухрядных разъема типа PBS-16, снабженных подписями. Отдельно выведены также контакты питания и GND, установлен программирующий разъем (по схеме рис. 1) и к выводам XTAL0/XTAL1 подключен кварцевый резонатор по стандартной схеме с двумя конденсаторами по 22 пф. В примерах далее будем ориентироваться на резонатор 8 МГц, но можно установить любой другой по вашему выбору. Из дополнительных устройств установлен только стандартный светодиод на выводе PB5 (соответствующем выводу D13 Arduino). По питанию светодиод не устанавливался (чтобы минимизировать потребление платы), но его стоит подключить через разъем к контактам Vcc и GND (контакты корпуса 4 и 5).

Особенности программирования LGT8F328P на низком уровне


Напоминаем что в AVR-ассемблере действительны две формы представления шестнадцатеричных чисел: из языка Pascal ($FF) и языка C (0xFF). Первый короче, второй – общепринятый, и здесь мы будем пользоваться этими формами вперемешку.

Надо помнить, что большинство регистров ввода-вывода в LGT8F328P относятся к расширенным (memory mapped), тем более, что здесь их заметно больше, чем в ATmega328. Какие именно — можно узнать, если просмотреть с помощью Блокнота приложенный файл lgt8f328Pdef.inc. Для чтения/записи обычных регистров (номера от $00 до $3F) используются команды in/out, для расширенных — команды lds/sts (при этом используются не номера, а истинные адреса в памяти данных, от $40+$20 = $60 до $FF).

Об адресах расширенных регистров в LGT8F328P
Отметим, что этот факт в таблице регистров в описании контроллера не отражен – хотя там имеется указание на «регистры прямого ввода-вывода», но для них также приведены адреса в памяти, а не номера, что способно запутать неопытного пользователя окончательно. Но ассемблер сам поможет с этим расправиться: лазать каждый раз по файлу lgt8f328Pdef.inc, чтобы узнать какой регистр к какому пространству относится, необязательно, при неправильном указании ассемблер выдаст номер строки с ошибкой и комментарием «operand out of range».

Установка источника тактирования


Источник тактового сигнала для LGT8F328P в среде Arduino вы выбираете заранее из меню Инструменты (см. рис. 2 в первой части статьи). Здесь это придется делать самостоятельно, в первых строках секции установок программы. Так как fuse-биты здесь отсутствуют, делается это через два регистра — PMCR отвечает за включение соответствующего генератора и подключение к нему, а CLKPR – за коэффициент деления системной тактовой частоты. Заметьте, что CLKPR присутствует и в ATmega328, однако там он инициализируется в зависимости от предварительной установки fuse-бита CKDIV8 — при установленном бите тактовая частота от выбранного источника поступает напрямую (CLKPR =$00), при сброшенном в 8 раз меньше (CLKPR =$03).

В LGT8F328P регистр CLKPR всегда инициализируется значением $03 (деление на 8), а источник по умолчанию — встроенный RC-генератор 32 МГц. Поэтому при запуске контроллер начинает работу на частоте 4 МГц. Этот факт делает применение LGT8F328P куда более безопасным, чем AVR: даже если вы умудритесь его подвесить плохо подключенным кварцем или неправильным программированием, ситуацию тут же можно исправить, загрузив откорректированную программу.

Частоту тактирования по умолчанию несложно проверить, загрузив пример из архива под названием migalka_tim0.asm — эта программа, использующая прерывание переполнения таймера 0, на любом котроллере с тактовой частотой 4 МГц выдаст на выводе PD6 частоту ровно 1 кГц. Для того, чтобы она заработала в LGT8F328P, необходимо заменить в программе, рассчитанной на ATmega8, команду out TIMSK,temp на команду sts TIMSK0,temp и строку .include "m8def.inc" на .include "lgt8f328Pdef.inc". Полученную частоту можно проверить с помощью осциллографа или мультиметра, снабженного возможностью измерения частоты.

Переключить контроллер на внешний кварц можно с помощью следующей последовательности команд:

;====== Set external quartz 0.4 -32 MHz======
clr temp
clr temp2
ldi temp, 0x80 ;enable external crystal
lds temp2, PMCR
ori temp2, 0x04
sts PMCR, temp
sts PMCR, temp2
ser count ;=$FF
rcall delay ;waiting for crystal stable 256 clocks

clr temp
clr temp2
ldi temp, 0x80 ;switch to external crystal
lds temp2, PMCR
andi temp2, 0x9f
ori temp2, 0x20
sts PMCR, temp
sts PMCR, temp2
ser count ;=$FF
rcall delay ;waiting for crystal stable 256 clocks

ldi temp, 0x80 ;set to right prescale
ldi temp2, 0x00 ;0x20 - out system clock to PB0
sts CLKPR, temp
sts CLKPR, temp2
nop
nop
nop
;=====end set quartz ===

Задержка delay на 256 тактов выполняется с помощью следующей процедуры:

delay:
   dec count
   brne delay
ret

Все используемые регистры из второй половины регистрового файла:

def count = r17 ;счетчик
.def temp = r18 ;рабочая переменная
.def temp2 = r19 ;рабочая переменная

Обратите внимание на число 0x04 (0b00000100), которое загружается в регистр PMCR в начале — именно установка бита номер 2 означает включение генератора с внешним кварцем частотой 0,4-32 МГц. Если зачем-то хотите использовать низкочастотный резонатор (32-400 кГц), то здесь следует загрузить число 0x08.

В конце процедуры выполняется обнуление делителя тактовой частоты CLKPR, что означает работу контроллера напрямую от тактового генератора. В отличие от ATmega328, здесь имеется возможность вывести заданную тактовую частоту на внешние выводы контроллера для ее контроля. Задание для регистра CLKPR вместо 0x00 закомментированного значения 0x20 (т.е. установка бита номер 5) выведет частоту на вывод PB0.

Последовательность команд установки частоты от кварца, приведенную выше, можно оформить в виде отдельной процедуры или макроса, но поскольку она употребляется только один раз, можно просто включить ее в секцию начальных установок (по метке Reset) — обязательно после установки указателя стека. Это проделано в примере migalka_tim0_8MHz.asm, который в точности соответствует предыдущему варианту, за исключением того, что добавлены указанные команды переключения на кварц. Если больше ничего не делать, то частота на выводе PD6 окажется равной 2 кГц, так как теперь тактовая частота таймера при кварцевом резонаторе 8 МГц оказывается вдвое выше. Для возвращения к частоте на выходе 1 кГц следует с помощью счетчика count отсчитывать не каждое второе, а каждое четвертое прерывание переполнения, что делается контролем не первого бита счетчика (sbrs count,0 / sbrc count,0), а второго (sbrs count,1 / sbrc count,1).

Загрузите пример migalka_tim0_8MHz.asm, и проверьте корректность его работы. Попробуйте также вывести тактовую частоту на вывод PB0, изменив загружаемое в CLKPR значение, как описано выше — при проверке осциллографом получите весьма поучительное зрелище.

Мигалка с помощью Timer3


Для проверки третьего таймера возьмем за образец пример мигалки, основанной на прерывании переполнения 16-разрядного Timer1, в котором с частотой раз в секунду попеременно переключаются два вывода PD6 и PD7.

В начале такой программы необходимо установить адрес прерывания, но с его обработчиком для Timer3 возникнут трудности. Константу, обозначающую адрес прерывания третьего таймера, вы найдете в конце файла lgt8f328Pdef.inc, она называется TC3INTaddr (адрес $3A = 58) – и обнаружите, что это единственное прерывание, связанное с Timer3. То есть задать типов прерываний через TIMSK3 можно даже больше, чем для Timer1, но выход у них у всех только через единственный вектор. Иными словами, когда возникает прерывание TC3INT, оно не может определить, какой из пяти вариантов его вызвал, и, соответственно, не сбрасывает аппаратно нужного флага. Флаг прерывания здесь следует сбрасывать «вручную», первой командой после входа в обработчик (сброс производится записью единицы в нужную позицию регистра TIFR3):
TIM3_INT: ;процедура обработки прерывания таймера3
    sbi TIFR3,TOV3 ;сбрасываем флаг прерывания переполнения
. . . . . <обработка прерывания переполнения>
reti ;конец прерывания

Если у вас прерывание Timer3 одно-единственное, можно сбрасывать регистр флагов целиком:

TIM3_INT: ;процедура обработки прерывания таймера3
    ser temp ;temp = $FF
    out TIFR3,temp
. . . . . <обработка прерывания>
reti ;конец прерывания

Более универсальное решение для случая, когда для Timer3 было инициировано несколько прерываний — проверка, какой именно флаг установлен. В примере установлены прерывания переполнения, сравнения A и сравнения B:
TIM3_INT: ;процедура обработки прерывания таймера3
   sbis TIFR3,TOV3 ;если флаг переполнения установлен
   rjmp OCF3A_int ;иначе переходим к проверке флага сравнения A
   sbi TIFR3,TOV3 ;сбрасываем флаг прерывания переполнения
. . . . . <обработка прерывания переполнения>
reti ;выход из обработчика
OCF3A_int:
   sbis TIFR3,OCF3A ;если флаг сравнения A установлен
   rjmp OCF3B_int ;иначе переходим к проверке флага сравнения B
   sbi TIFR3, OCF3A ;сбрасываем флаг сравнения A
. . . . . <обработка прерывания сравнения A>
reti ;выход из обработчика
OCF3B_int:
   sbis TIFR3,OCF3B ;если флаг сравнения B установлен
   reti ;иначе выходим из обработчика
   sbi TIFR3, OCF3B ;сбрасываем флаг сравнения B
. . . . . <обработка прерывания сравнения B>
reti ;выход из обработчика

Загрузите пример migalka_tim3_8MHz.asm, подключите к портам PD6 и PD7 светодиоды и проверьте их попеременное мигание. Число, записываемое в счетные регистры, в расчете на тактовую частоту 8 МГц рассчитывается, исходя из следующих соображений. При такой тактовой частоте с делителем 1:64 длительность между импульсами равна 8 мкс, из чего следует, что до 500 мс необходимо просчитать 500000/8 = 62500 тактов, то есть предзаписывать в счетные регистры необходимо 65536-62500 = 3036, что и указано в тексте примера.

Watchdog, энергосбережение и UART


В одном примере мы сразу проверим, как настраивать режим Sleep, сторожевой таймер Watchdog и последовательный порт UART.
Начнем со сторожевого таймера. Включается здесь он точно так же, как в обычных Mega и Tiny c т.н. расширенным сторожевым таймером (через регистр SMCR, а не MCUCR, как в старых моделях). Только для LGT8F328P необходимо проделать еще одну операцию, аналогичную включению fuse-бита WDTON в AVR-контроллерах: подключить источник тактирования, в качестве которого целесообразно выбрать встроенный генератор 32 кГц. В этом случае длительность задержки срабатывания может достигать 32 секунд.

Следующая последовательность команд инициализирует Watchdog в режиме прерывания каждые 4 секунды:
wdr ;сбрасываем WDT
;задаем источник тактов wdt 32 кГц
lds temp,PMCR
ori temp,0x10 ;set WCLKS bit
ldi Razr0,0x80
sts PMCR, Razr0
sts PMCR, temp
;включаем WDT:
ldi temp,(1<<WDCE)|(1<<WDE) ; разрешение изменений + запуск
sts WDTCSR,temp
ldi temp, (1<<WDIE)|(1<< WDP2)|(1<<WDP1)
;прерывание, время (WDP3:0 = 0110 =4 сек) + запуск
sts WDTCSR,temp
. . . . .

Само прерывание необходимо объявить в начале программы:

rjmp RESET ;Reset Handle
.org WDTaddr
rjmp WDT_over ;Watchdog Interrupt Vector Address


Прежде чем перейти к настройке последовательного порта, разберемся с режимом энергосбережения. Здесь он включается полностью аналогично AVR:

ldi temp,((1<< SM0)|1<< SM1)|(1<<SE) ;разрешение Sleep, режим DPS1
out SMCR,temp

Режим DPS1 — аналог режима Power-save для AVR-контроллеров. Команду Sleep включаем в основной цикл:

Gcykle: ;главный цикл
   sleep ;уходим в сон
rjmp Gcykle

Настройка UART отличается от того, что мы делали раньше для старых AVR-контроллеров только способом обращения к регистрам:

ldi temp,51 ;9600 при 8 МГЦ
sts UBRRL,temp ;скор. передачи
ldi temp,(1<<RXEN|1<<TXEN)
sts UCSRB,temp ;разреш. приема/передачи 8бит

Наконец, с учетом того, что регистр UCSRA переехал в memory mapped область, процедура передачи байта через UART будет следующей:

out_com: ;посылка байта из count с ожид. готовности
lds temp,UCSRA
sbrs temp,UDRE ;ждем готовности буфера передатчика
rjmp out_com
sts UDR,count ;count!!! а не temp
ret

Обработчик прерывания WDT будет включать на долю секунды светодиод (на порту PB5) и отсылать через UART значение некоего счетчика, увеличивающегося с каждым прерыванием, что позволит нам убедиться в сохранности регистров во время «сна». С учетом всего рассмотренного, обработчик прерывания WDT будет выглядеть следующим образом:

WDT_over: ;пробуждение по Watchdog
   sbi PortB,5 ;зажигаем светодиод
    inc count ;увеличиваем счетчик к следующему разу
    rcall out_com ;посылаем во внешний мир счетчик
   Delay80 $03,$0D,40 ;задержка 0,125 с при 8 МГц
   cbi PortB,5 ;гасим светодиод
reti ;конец прерывания WDT

Здесь Delay80 — обращение к универсальному макросу для формирования задержек:

;Число N для задержки T (с) при такт. частоте F (Гц) равно
;N = TF/5; 8 МГц = 0,125 c = 200 000, $03 0D 40
.macro Delay80 ;процедура задержки до 80 c при 1 Мгц
ldi Razr2,@0 ;старший байт задержки
ldi Razr1,@1 ;средний байт задержки
ldi Razr0,@2 ;младший байт задержки
R_sub:
   subi Razr0,1
   sbci Razr1,0
   sbci Razr2,0
brcc R_sub
.endm

Загрузите пример WDT_int_sleep_test_LGT.asm в ASM Editor, скомпилируйте его и загрузите в контроллер. После загрузки каждые 4 секунды должен кратко мигать светодиод, подключенный к выводу PB5 (вывод 17 32-контактного корпуса). Отключите программатор и подключите к выводам RxD (вывод 30) и TxD (вывод 31) контроллера любой USB-UART адаптер. Запущенная на компьютере программа-монитор последовательного порта покажет посылаемые числа счетчика, последовательно меняющиеся от 0 до 255.

О Мониторе порта
К сожалению, Монитор порта Arduino IDE не настраиваемый, и по умолчанию принимаемые числа интерпретирует, как текстовые символы ASCII. Поэтому с его помощью вы можете только проверить работоспособность программы. Можно настроить в качестве приемника любую из плат Arduino с программным Serial, а уже через нее с помощью функции Print посылать в Монитор порта принятые числа. Другой вариант — использовать более продвинутый Монитор порта — например, программу автора COM2000 (http://revich.lib.ru/comcom.zip).

Для тех, кто не дошел до конца первой части: ссылка на архив с программами и документацией здесь.

Комментарии (9)


  1. Rusrst
    05.02.2023 17:56

    Спасибо, давно не читал ассемблер, хоть и до сих пор его люблю, все же с си иногда надо повозиться с тем, что в ассемблере делается стандартно.

    С программированием при низком напряжении и у avr были проблемы - у меня atmega16a зашивалась один раз из 3-х, если была подключена по 3.3 вольтам.

    Плюшка с безопасностью тактирования достаточно приятный бонус, да. Правда я так и не понял, у клонов вообще fuse bits есть?


    1. YRevich Автор
      05.02.2023 19:24

      Правда я так и не понял, у клонов вообще fuse bits есть?

      Начало прочтите. И по ссылкам в начале сходите. Там все объясняется.


      1. Rusrst
        05.02.2023 19:54

        Профукал, спасибо.


  1. kkuznetzov
    05.02.2023 17:56

    Код для ATMega328 заработает?
    С учётом другой частоты встроенного генератора конечно.


    1. YRevich Автор
      05.02.2023 19:25

      То же самое - сходите в начало, там все рассказано.


    1. YRevich Автор
      06.02.2023 07:59

      Код для ATMega328 заработает?

       kkuznetzov, я хочу уточнить. Меня тут поправили, что команды lgt8f328p выполняются быстрее, и это может сказаться на выполнении разных операций, критичных ко времени. Но по умолчанию, напомним, вся эта бодяга работает на частоте 4МГц. Так что все должно заработать так же, как и для ATMega328, если не устанавливать тактовую частоту на экстремальные значения. Не исключено, конечно, что тщательным исследованием где-то выявятся нюансы.


  1. safari2012
    05.02.2023 22:06

    а что насчет аппаратного wdt?


    1. YRevich Автор
      06.02.2023 08:03

      Там про аппаратный wdt рассказывается, а какой еще бывает?


      1. safari2012
        06.02.2023 11:12

        Бывают ещё программные. Зная китайцев, вполне могут заместить (как с эмуляцией EEPROM)