Рассмотрим синтаксические особенности ассемблера AVR на примере небольшой тестовой программы для микроконтроллера ATmega8, приведенной ниже. Программа формирует на выводе PB2 импульсы с частотой следования ≈ 2.5 Гц при частоте внутреннего RC-генератора 1 МГц.
1. ; Тестовая программа для ATmega8. 2. // Светодиод подключен к выводу PB0 микроконтроллера. 3. /* Биты конфигурации: Low Fuse High Fuse BODLEVEL = 1 RSTDISBL = 1 BODEN = 1 WDTON = 1 SUT1 = 1 SPIEN = 1 SUT0 = 0 CKOPT = 1 CKSEL3 = 0 | EESAVE = 1 CKSEL2 = 0 |_ RC-генератор BOOTSZ1 = 1 CKSEL1 = 0 | 1 МГц BOOTSZ0 = 1 CKSEL0 = 1 | BOOTRST = 1 */ 4. .nolist ;подключение стандартного заголовочного файла 5. .include "m8def.inc" 6. .list 7. .equ PAUSE = 50000 ;задержка времени 8. .equ LED = PB2 ;вывод для подключения светодиода 9. .def temp = R16 ;регистр для промежуточных операций 10. .def buffer = R17 ;регистр для чтения порта 11. .cseg 12. .org 0 13. rjmp initial ;0xC01F 14. rjmp 0 ;rjmp service_INT0 ;внешнее прерывание 0 0xCFFE 15. rjmp 0 ;rjmp service_INT1 ;внешнее прерывание 1 0xCFFD 16. rjmp 0 ;rjmp service_OC2 ;совпадение TCNT2 и OCR2 0xCFFC 17. rjmp 0 ;rjmp service_OVF2 ;переполнение TCNT2 0xCFFB 18. rjmp 0 ;rjmp service_ICP1 ;захват в ICP1 0xCFFA 19. rjmp 0 ;rjmp service_OC1A ;совпадение TCNT1 и OCR1A 0xCFF9 20. rjmp 0 ;rjmp service_OC1B ;совпадение TCNT1 и OCR1B 0xCFF8 21. rjmp 0 ;rjmp service_OVF1 ;переполнение TCNT1 0xCFF7 22. rjmp 0 ;rjmp service_OVF0 ;переполнение TCNT0 0xCFF6 23. rjmp 0 ;rjmp service_SPI ;прерывание от модуля SPI 0xCFF5 24. rjmp 0 ;rjmp service_URXC ;получение байта по USART 0xCFF4 25. rjmp 0 ;rjmp service_UDRE ;опустошение UDR в USART 0xCFF3 26. rjmp 0 ;rjmp service_UTXC ;передача байта по USART 0xCFF2 27. rjmp 0 ;rjmp service_ADCC ;прерывание от АЦП 0xCFF1 28. rjmp 0 ;rjmp service_ERDY ;завершение записи в EEPROM 0xCFF0 29. rjmp 0 ;rjmp service_ACI ;прерывание от компаратора 0xCFEF 30. rjmp 0 ;rjmp service_TWI ;прерывание от модуля TWI 0xCFEE 31. rjmp 0 ;rjmp service_SPMR ;завершение выполнения spm 0xCFED 32. .org 0x20 33. initial: ldi temp,low(RAMEND) ;0xE50F 34. out SPL,temp ;0xBF0D 35. ldi temp,high(RAMEND) ;0xE004 36. out SPH,temp ;0xBF0E 37. cbi PORTB,LED ;0x98C2 38. sbi DDRB,LED ;0x9ABA 39. ldi temp,1«LED ;0xE004 40. main: in buffer,PORTB ;0xB318 41. eor buffer,temp ;0x2710 42. out PORTB,buffer ;0xBB18 43. rcall delay ;0xD001 44. rjmp main ;0xCFFB 45. delay: ldi XH,high(PAUSE) ;0xECB3 46. ldi XL,low(PAUSE) ;0xE5A0 47. sbiw XH:XL,1 ;0x9711 48. brne PC-1 ;0xF7F1 49. ret
В строках 1…3 приведено 3 возможных варианта оформления комментариев. Комментарий в строке 1, начинающийся со знака “;”, распознается любым ассемблером и поэтому является наиболее предпочтительным. Комментарии в строках 2 и 3 подобны тем, которые используются в нотациях языков высокого уровня. Последний из них дает возможность выделить сразу несколько строк (маркерами начала и конца фрагмента текста является “/*” и “*/” соответственно).
В строке 5 директивой .include к программе подключается стандартный заголовочный файл "m8def.inc". Необязательные директивы .nolist и .list (строки 4 и 6 соответственно) запрещают вывод содержимого подключаемого файла в файл листинга.
Все директивы допускается размещать в одной строке. Так, например, можно было бы записать
.nolist .include "m8def.inc" .list
Несмотря на это, желательно придерживаться правильного стиля программирования, в соответствии с которым в одной строке должна быть расположена только одна директива ассемблера.
Объявление констант находится в строках 7 и 8. Константе LED присваивается номер линии ввода-вывода PB2 = 2, описание которой находится в заголовочном файле. В строках 9 и 10 двум рабочим РОНам назначаются пользовательские имена.
Секция рабочего кода открывается директивой .cseg в строке 11. Директива .org 0 (строка 12) устанавливает начальный адрес в памяти программ. В строке 13 должна находиться инструкция перехода на метку начала основной программы initial.
Метка представляет собой адрес в пределах секции кода или данных. В ассемблере AVR она должна быть записана в начале строки и завершаться в конце двоеточием.
Конечно, адрес в команде может быть указан и явно. В данном примере его можно задать безошибочно (rjmp 0x20 вместо rjmp initial). Но, это возможно только потому, что директива .org 0x20 (строка 32) заставляет компилятор поместить команду в строке 33 по адресу 32-го слова FLASH-памяти программ. Однако, в большинстве случаев, адрес размещения той или иной команды заранее неизвестен и, кроме того, он может изменяться по мере того, как в программу будут вноситься изменения. Именно поэтому предпочтительней использовать метки. Назначение их адресов производится автоматически на этапе компиляции. Да и символьные имена меток, предоставляют об объектах намного больше информации, чем просо какие-то числа.
На месте каждого неиспользуемого в программе прерывания желательно поставить “заглушки” в виде команды возврата rjmp 0 или reti, как это сделано в строках 14…31.
В строках 33…39 производится инициализация ресурсов микроконтроллера. Любая программа обязательно должна начинаться с установки начального значения указателя стека (если он имеется). В строках 33…36 в SPH:SPL заносится значение RAMEND = 0x045F (вершина стека перемещается в самый верх SRAM). Для выделения младшего и старшего полубайтов константы RAMEND используются встроенные функции low(RAMEND)=0x5F и high(RAMEND)=0x04 соответственно.
Далее разряд LED в регистре данных порта B сбрасывается на 0 (стока 37), а сама линия управления светодиодом настраивается на вывод (стока 38). В строке 39 в регистр temp заносится константа 1<
ldi temp,(1«PB0)|(1«PB5) или ldi temp,(1«PB0)+(1«PB5) ;temp <- 0b00100001 out DDRB,temp ;DDRB <- temp
Код в строках 40…44 представляет собой тело основной программы. В регистр buffer считывается текущее состояние порта данных PORTB (строка 40). Далее между содержимым buffer и temp производится операция “Исключающее ИЛИ” (строка 41), после чего модифицированное содержимое buffer снова выводится в порт (строках 42). В результате такого действия логический уровень на линии PB2 изменится на противоположный. В строке 43 происходит вызов подпрограммы задержки времени delay (≈200 мс), а в строке 44 расположена инструкция перехода на метку main. Так образуется основной цикл программы, в котором происходит постоянное повторение операторов в строках 40…44: инвертирование уровня на выводе LED, задержка, затем снова инвертирование и т.д.
Внутри подпрограммы delay в регистровую пару XH:XL заносится число PAUSE = 50000, определяющее длительность задержки времени (строки 45, 46). В строке 47 из содержимого XH:XL вычитается 1. Команда условного перехода в строке 48 проверяет, значение флага Z из SREG. Если Z = 1 (результат предыдущей операции не равен нулю), то управление передается на строку 47. Так будет повторяться до тех пор, пока не выполнится условие XH:XL = 0 (т.е. пока не пройдет 50000 циклов вычитания). Команда выхода ret в строке 49 возвращает управление в то место основной программы, с которого произошел вызов delay.
Отдельно стоит обратить внимание на встроенную в ассемблер переменную PC в строке 48. Она представляет собой текущее содержимое программного счетчика. Адрес PC-1 будет указывать на предыдущее слово в памяти программ микроконтроллера. В данном случае в этом слове находится команда sbiw XH:XL,1. Использовать PC очень удобно для программных переходов в небольших пределах. Однако здесь всегда необходимо помнить, что у микроконтроллеров AVR имеются команды, которые имеют размер в 2 16-разрядных слова программ (lds Rd,k, jmp k и др.), из-за чего при вычислении смещения необходимо будет добавлять\отнимать к PC значение 2 вместо 1 на каждую такую инструкцию.
Справа, в листинге приведен машинный код, который будет сгенерирован после компиляции программы.
Перейти к следующей части: Макросы
Комментарии (0) | Я собрал (0) | Подписаться
Для добавления Вашей сборки необходима регистрация