LGT8F328P: импортозамещение по-китайски (окончание)
В первой части рассказа о контроллере LGT8F328P китайской фирмы Logic Green рассказывалось об этом контроллере, как замене классического Arduino, а также об использовании в Arduino-среде некоторых его расширенных возможностей. В этой части пойдет речь о программировании LGT8F328P на самом низком уровне: на AVR-ассемблере. Это лучше позволит понять его устройство, отличия от AVR и тонкости программирования тех или иных составляющих.
Для написания и загрузки ассемблерных программ, как в AVR, так и в LGT8F328P необходимо обзавестись некоторыми специальными инструментами. Здесь не очень важно, какую именно среду использовать (любую из привычных вам, если вы справитесь с интеграцией в нее LGT8F328P), лишь бы она умела производить hex-файлы.
Программирование LGT8F328P на ассемблере
Автор не заморачивается с различными современными средствами для формирования и компилирования ассемблерных программ, а использует простейший способ с текстовым редактором и ассемблером avrasm2.
Для того, чтобы подсветка синтаксиса для 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. Компиляция текущего текста программы будет происходить по нажатию сочетания клавиш
Кроме ассемблера и редактора, вам понадобится 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 контроллера, но тогда не следует забывать запитывать плату контроллера от своего источника.
Заметим также в скобках, что интерфейс 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).
Отметим, что этот факт в таблице регистров в описании контроллера не отражен — хотя там имеется указание на «регистры прямого ввода-вывода», но для них также приведены адреса в памяти, а не номера, что способно запутать неопытного пользователя окончательно. Но ассемблер сам поможет с этим расправиться: лазать каждый раз по файлу 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, 0×80; switch to external crystal
lds temp2, PMCR
andi temp2, 0×9f
ori temp2, 0×20
sts PMCR, temp
sts PMCR, temp2
ser count;=$FF
rcall delay; waiting for crystal stable 256 clocks
ldi temp, 0×80; set to right prescale
ldi temp2, 0×00;0×20 — 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 ;рабочая переменная
Обратите внимание на число 0×04 (0b00000100), которое загружается в регистр PMCR
в начале — именно установка бита номер 2 означает включение генератора с внешним кварцем частотой 0,4–32 МГц. Если зачем-то хотите использовать низкочастотный резонатор (32–400 кГц), то здесь следует загрузить число 0×08.
В конце процедуры выполняется обнуление делителя тактовой частоты CLKPR
, что означает работу контроллера напрямую от тактового генератора. В отличие от ATmega328, здесь имеется возможность вывести заданную тактовую частоту на внешние выводы контроллера для ее контроля. Задание для регистра CLKPR
вместо 0×00 закомментированного значения 0×20 (т.е. установка бита номер 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<
ldi temp, (1<
sts WDTCSR,temp
. . . . .
Само прерывание необходимо объявить в начале программы:
rjmp RESET ;Reset Handle
.org WDTaddr
rjmp WDT_over ;Watchdog Interrupt Vector Address
Прежде чем перейти к настройке последовательного порта, разберемся с режимом энергосбережения. Здесь он включается полностью аналогично AVR:
ldi temp,((1<< SM0)|1<< SM1)|(1<
Режим DPS1 — аналог режима Power-save для AVR-контроллеров. Команду Sleep включаем в основной цикл:
Gcykle: ;главный цикл
sleep ;уходим в сон
rjmp Gcykle
Настройка UART отличается от того, что мы делали раньше для старых AVR-контроллеров только способом обращения к регистрам:
ldi temp,51 ;9600 при 8 МГЦ
sts UBRRL,temp ;скор. передачи
ldi temp,(1<
Наконец, с учетом того, что регистр 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.
Для тех, кто не дошел до конца первой части: ссылка на архив с программами и документацией здесь.