Комментариев на «AVR. Учебный Курс. Типовые конструкции»

  1. XDN 17 Сен 2008 3:30

Оптимизация -O0 стояла в avr-gcc? У меня самописный драйвер hd44780 с поддержкой чтения добавляет 84 байта при -0s. Тут, конечно, надо учитывать, что код всей прошивки - 3244 Кб. На первых порах, когда ты добавляешь #инклады, размер может резко ползти вверх, но потом этот рост прекратиться.

DI HALT сентября 17, 2008 at 8:15

Да на -O0. При -Os код ужимается где то до 5кб. Но при Os не зная что да как легко получить полностью нерабочий код. Т.к. в этом режиме компилер может код так заоптимизировать, что хрен потом концы найдешь. У меня помню по неопытности был прикол, когда я пол дня убил пытаясь понять чего у меня протокол не работает,а оказывается компилер загнал проверку бита куда то в память и сравнивал ее при проверке с сохраненным значением. Поставил volatile сразу же заработало, но для того чтобы вкурить в эти грабли пришлось кучу документации взрыть.

Ну так то самописный драйвер! Я бы тоже ручками уложил в крошечный обьем. Но в таких случаях предпочитаю нафигачить на асме.

XDN сентября 17, 2008 at 15:46

Использование volatile в таких случая формально указано в стандарте. Оптимизатор - тоже машина, ею управлять надо умеючи.:)

Ну да ладно, не будем холиворить.

DI HALT сентября 17, 2008 at 16:21

Так о чем и речь, что при программировании на Сях под МК вылазит куча специфичных тонкостей, завязанных именно на МК, без знания которых ничего не получится сделать. А адекватной документации на предмет того, где да как нужно делать я еще не встречал. Обрывки на форумах не в счет.

XDN сентября 17, 2008 at 16:56

volatile в C99, если не раньше, был введён как раз для таких ситуаций.
Но специфика есть, не спорю. Правда она вся хорошо описывается в документации.

Книгу по Си для МК знаю только одну: “Программирование на языке С для AVR и PIC микроконтроллеров. Ю.А.Шпак.”.

Но если взять, например, “Микроконтроллеры AVR семейства Mega. Руководство пользователя. А.В.Евстиеев”, то там идут примеры как на Асме, так и на Си. В даташитах Atmel’а тоже с недавних пор стали так делать.

DI HALT сентября 17, 2008 at 17:09

Видел я эти примеры и в Евстифееве и в даташитах. Там тот же ассемблер тока вид со стороны Си. Они слишком малы (считаные строчки), чтобы вявить такие приколы с оптимизацией.

nwanomaly сентября 17, 2008 at 22:56

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

XDN сентября 18, 2008 at 18:54

Проверил размер своего драйвера “в чистом” виде - 950 байт.

  1. Cyber_RAT 17 Сен 2008 11:37

сколько пишу на микроконтроллерах (микропроцессорах начиная от вм80, z80 и конечно же x86) больше всего люблю асм… (я не говорю о большом софте, а об девайсах на этих контроллерах и проциках)…
ассемблер - более прозрачен что-ли.. или склад ума у меня такой, что линейное программирование для меня проще:(
даже сейчас девайс на меге32 с несколькими протоколами обмена по rs232, mmc+lcd siemens s65 + rtc - все на асме…
p.s. хотя иногда хочется плюнуть и переписать на си (вроде быстрее и проще), но пару попыток это “проще” похоронили в зародыше.
p.p.s. вот такой вот сумбур получился;)

DI HALT сентября 17, 2008 at 11:43

Такая же фигня:))))

  1. SWG 17 Сен 2008 20:34

Когда в начале 90х я, поработав уже почти десяток лет на Ассемблере (для 8080, 8086, 8048, Z80) и нескольких BASICах, а также DBASE III+ и CLIPPER, немножко FORT, решил попробовать что - нибудь более серьезное, естественно, начал с C++ 3.0. Про него ходила молва, что он шибко крутой, ужасно мощный и гибкий, но скрытный, как закопанный в землю шланг. В общем, “Настоящие программисты пишут только на C”!!!
Первое, на что обратил внимание, - бедность встроенного набора функций. Даже для элементарного консольного ввода - вывода уже нужна библиотека (stdio.h, если не ошибаюсь). Не было даже такого элементарного понятия, как СТРОКА (string). Вместо нее - используй одномерный символьный массив, не забывая добавлять в конце символы ВК и ПС. Программку намахал быстро, откомпилировалась без проблем, но чтобы нормально заработала, пришлось попотеть. То вешалась без обьяснения причин, то вообще делала непонятно что.
Для сравнения попробовал написать то же самое на Борланд Паскале 7.0. Написал, компилятор ткнул меня несколько раз во всякие точки и запятые, ругнулся на несоответствие типов данных, после исправления запускаю, и - О, ЧУДО!!! Все работает! и именно так, как надо!
С тех пор я нафиг забросил C, писал на Паскале, затем Дельфи (1, 2, 3, 4, 5, 6…). Работа у меня в последние лет 12 в основном связана с обработкой текстовой информации (биллинговые записи, базы данных, всевозможная статистика, автоматизация отчетности).
Конечно, за эти годы С тоже не стоял на месте, многое позаимствовав у других языков, но что - то меня к нему не тянет. А тем более использовать его в микроконтроллерах для пересылки регистр - регистр, чтобы он перед этим на всякий случай сохранил кучу информации в стеке, потом долго возвращал ее обратно…
Правда, языки высокого уровня с микроконтроллерами упрощают такие вещи, как инициализация UART, ADC, I2C, LCD и прочего. Удобно, например, дать одну команду “PWM1_Init(500);” - и ШИМ1 уже настроен на работу на частоте 500гц, вместо того чтобы распихивать битики по десятку служебных регистров.
Или, например:
USART_Init(9600);//-инициализирует USART (9600 baud rate, 1 stop bit, no parity).
А сколько пришлось бы распихать вручную? Или: USART_Write(’#'); - и символ ушел в линию. А как вам такое:

program ADC_USART;
var temp_res: word;
begin
USART_Init(19200); // initalize USART (19200 baud rate, 1 stop bit, no parity…)
ANSEL:= $04; // configure AN2 pin as analog input
TRISA:= $FF;
ANSELH:= 0; // configure other AN pins as digital I/O
while TRUE do
begin
temp_res:= ADC_Read(2) shr 2; //read 10-bit ADC from AN2 and discard 2 LS bits
USART_Write(temp_res); // send ADC reading as byte
Delay_ms(100);
end;
end.

Две команды - и напряжение батареи измерено и ушло в RS232!
Ради этого стоит написать такие вещи на МикроПаскале или МикроБэйсике, откомпилировать, отладить, а потом взять ассемблерный листинг и дописать на ассемблере все остальное, попутно проанализировав и оптимизировав код, выданный компилятором до этого. Часто это намного ускоряет работу.

Инициализация памяти
Мало кто подозревает о том, что при включении в оперативке далеко не всегда все байты равны 0xFF. Они могут, но не обязаны. Равно как и регистры РОН не всегда равны нулю при запуске. Обычно да, все обнулено, но я несколько раз сталкивался со случаями когда после перезапуска и/или включения-выключения питания, микроконтроллер начинал творить не пойми что. Особнно часто возникает когда питание выключаешь, а потом, спустя некоторое время, пара минут, не больше, включаешь. А всему виной остаточные значения в регистрах.

Итак, возьмите себе за правило после каждого включения, в разделе инициализации, еще даже до инициализации стека, делать зануление памяти и очистку всех регистров. Разумеется делается это все в цикле. Вот примерный вариант кода:

123456789101112 RAM_Flush: LDI ZL,Low(SRAM_START); Адрес начала ОЗУ в индекс LDI ZH,High(SRAM_START) CLR R16; Очищаем R16 Flush: ST Z+,R16; Сохраняем 0 в ячейку памяти CPI ZH,High(RAMEND+1); Достигли конца оперативки? BRNE Flush; Нет? Крутимся дальше! CPI ZL,Low(RAMEND+1); А младший байт достиг конца? BRNE Flush CLR ZL; Очищаем индекс CLR ZH

Поскольку адрес оперативки у нас двубайтный, то мы вначале смотрим, чтобы старший байт совпал с концом, а потом добиваем оставшиеся 255 байт в младшем байте адреса.
Далее убиваем все регистры от первого до последнего. Все, контроллер готов к работе.

12345 LDI ZL, 30; Адрес самого старшего регистра CLR ZH; А тут у нас будет ноль DEC ZL; Уменьшая адрес ST Z, ZH; Записываем в регистр 0 BRNE PC-2; Пока не перебрали все не успокоились

За процедурку зануления регистров спасибо Testicq

Либо значения сразу же инициализируются нужными величинами. Но, обычно, я от нуля всегда пляшу. Поэтому зануляю все.

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

Вообще, архитектура программ это отдельная тема и ближе к концу курса, в его Сишной части я подробней рассказываю о разных типах организации прошивки. Можешь забежать вперед и поглядеть, что да как.

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

Суперцикл
Все программы на микроконтроллерах обычно зацикленные. Т.е. у нас есть какой то главный цикл, который вращается непрерывно.

Структура же программы при этом следующая:

  • Макросы и макроопредения
  • Сегмент ОЗУ
  • Точка входа - ORG 0000
  • Таблица векторов - и вектора, ведущие в секцию обработчиков прерываний
  • Обработчики прерываний - тела обработчиков, возврат отсюда только по RETI
  • Инициализация памяти - а вот уже отсюда начинается активная часть программы
  • Инициализация стека
  • Инициализация внутренней периферии - программирование и запуск в работу всяких таймеров, интерфейсов, выставление портов ввода-вывода в нужные уровни. Разрешение прерываний.
  • Инициализация внешней периферии - инициализация дисплеев, внешней памяти, разных аппаратных примочек, что подключены к микроконтроллеру извне.
  • Запуск фоновых процессов - процессы работающие непрерывно, вне зависимости от условий. Такие как сканирование клавиатуры, обновление экрана и так далее.
  • Главный цикл - тут уже идет вся управляющая логика программы.
  • Сегмент ЕЕПРОМ


Начинается все с макросов, их пока не много, если что по ходу добавим.

1234567891011121314151617181920 .include "m16def.inc"; Используем ATMega16;= Start macro.inc ========================================. macro OUTI LDI R16,@1.if @0 < 0x40 OUT @0,R16.else STS @0,R16.endif.endm.macro UOUT.if @0 < 0x40 OUT @0,@1.else STS @0,@1.endif.endm ;= End macro.inc =======================================

В оперативке пока ничего не размечаем. Нечего.

123 ; RAM ===================================================. DSEG; END RAM ===============================================

С точкой входа и таблицей векторов все понятно, следуя нашему давнему шаблону, берем его оттуда:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546 ; FLASH ======================================================. CSEG.ORG $000; (RESET) RJMP Reset.ORG $002 RETI; (INT0) External Interrupt Request 0. ORG $004 RETI; (INT1) External Interrupt Request 1. ORG $006 RETI; (TIMER2 COMP) Timer/Counter2 Compare Match. ORG $008 RETI; (TIMER2 OVF) Timer/Counter2 Overflow. ORG $00A RETI; (TIMER1 CAPT) Timer/Counter1 Capture Event. ORG $00C RETI; (TIMER1 COMPA) Timer/Counter1 Compare Match A. ORG $00E RETI; (TIMER1 COMPB) Timer/Counter1 Compare Match B. ORG $010 RETI; (TIMER1 OVF) Timer/Counter1 Overflow. ORG $012 RETI; (TIMER0 OVF) Timer/Counter0 Overflow. ORG $014 RETI; (SPI,STC) Serial Transfer Complete. ORG $016 RETI; (USART,RXC) USART, Rx Complete. ORG $018 RETI; (USART,UDRE) USART Data Register Empty. ORG $01A RETI; (USART,TXC) USART, Tx Complete. ORG $01C RETI; (ADC) ADC Conversion Complete. ORG $01E RETI; (EE_RDY) EEPROM Ready. ORG $020 RETI; (ANA_COMP) Analog Comparator. ORG $022 RETI; (TWI) 2-wire Serial Interface. ORG $024 RETI; (INT2) External Interrupt Request 2. ORG $026 RETI; (TIMER0 COMP) Timer/Counter0 Compare Match. ORG $028 RETI; (SPM_RDY) Store Program Memory Ready. ORG INT_VECTORS_SIZE; Конец таблицы прерываний

Обработчики пока тоже пусты, но потом добавим

12 ; Interrupts ==============================================; End Interrupts ==========================================

Инициализация ядра. Память, стек, регистры:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950 Reset: LDI R16,Low(RAMEND); Инициализация стека OUT SPL,R16; Обязательно!!! LDI R16,High(RAMEND) OUT SPH,R16; Start coreinit.inc RAM_Flush: LDI ZL,Low(SRAM_START); Адрес начала ОЗУ в индекс LDI ZH,High(SRAM_START) CLR R16; Очищаем R16 Flush: ST Z+,R16; Сохраняем 0 в ячейку памяти CPI ZH,High(RAMEND); Достигли конца оперативки? BRNE Flush; Нет? Крутимся дальше! CPI ZL,Low(RAMEND); А младший байт достиг конца? BRNE Flush CLR ZL; Очищаем индекс CLR ZH CLR R0 CLR R1 CLR R2 CLR R3 CLR R4 CLR R5 CLR R6 CLR R7 CLR R8 CLR R9 CLR R10 CLR R11 CLR R12 CLR R13 CLR R14 CLR R15 CLR R16 CLR R17 CLR R18 CLR R19 CLR R20 CLR R21 CLR R22 CLR R23 CLR R24 CLR R25 CLR R26 CLR R27 CLR R28 CLR R29; End coreinit.inc

Всю эту портянку можно и нужно спрятать в inc файл и больше не трогать.

Секции внешней и внутренней инициализации переферии пока пусты, но ненадолго. Равно как и запуск фоновых программ. Потом я просто буду говорить, что мол добавьте эту ботву в секцию Internal Hardware Init и все:)

1234567891011 ; Internal Hardware Init ======================================; End Internal Hardware Init ===================================; External Hardware Init ======================================; End Internal Hardware Init ===================================; Run ==========================================================; End Run ======================================================

А теперь, собственно, сам главный цикл.

12345 ; Main ========================================================= Main: JMP Main; End Main =====================================================

Все процедуры располагаются в отдельной секции, не смешиваясь с главным циклом. Так удобней, потом можно их по частям вынести в библиотечные файлы и разделить исходник на несколько файлов. Но мы пока это делать не будем. Разделим их просто логически.

123 ; Procedure ====================================================; End Procedure ================================================

Рано или поздно наступает момент когда сложность алгоритма становится такой, что дальнейшее развитие и усложнение программы превращается в нетривиальную задачу. Очень легко запутаться и тяжело отлаживать эту портянку. Многие бегут от этих сложностей в языки высокого уровня, впрочем это не особо спасает — разница минимальна на самом деле и проще не становится.
Самое верное решение в данном случае — внедрение в проект операционной системы. Которая бы предоставляла API для решения задач, а также обеспечивала порядок работы всей системы.

В результате было написано микроядро. Камрад Serg2×2 подглядел концепцию в прошивке сотового телефона Motorola и портировал на микроконтроллер АТ89С2051, после ее перенесли на AVR, а я привел все в библиотечный и структурированный вид, обвязал все удобными макросами, а также подробно описал и задокументировал. Так что теперь интеграция ядра операционки в проект под микроконтроллер AVR занимает буквально пару минут работы Copy-Paste.

Ядро обеспечивает очередь задач (пока без приоритетов, но это в планах) и службу таймеров. Многозадачность кооперативная, что накладывает соответствующие ограничения на стиль написания. Фактически, каждую процедуру мы пишем как прерывание, максимально коротко и быстро, все задержки вешая на службу таймеров.

Параметры и системные требования микроядра:

  • Занимаемый обьем в Flash - 500 байт, при желании можно ужать до 400 байт, выкинув ненужные функции.
  • Рекомендуемый объем RAM - не менее 20 байт +стек, впрочем, можно еще ужать если нагрузка небольшая.
  • Крайне желательная поддержка команд STS и LDS, можно и без них, но неудобно. Впрочем, макросы решают.


Суть всех заморочек
Представь что ты пишешь простейшую программу — мигнуть светодиодом. Как ты это будешь реализовывать?
На ум сразу же приходит следующий алгоритм:

  • Зажечь диод.
  • Потупить в цикле
  • Погасить диод.

Просто, логично, понятно.

А если усложнить? Мигать будем не одним диодом, а тремя, да еще чтобы первый мигал с частотой в 1кГц, второй в 500Гц, а третий 200Гц. Представил сразу же в голове алгоритм? Наверняка тут же получил переклин мозга и немудрено. Линейно развернуть такую структуру весьма сложно, даже если применить таймеры. Да, на таймерах выкрутишься, но это пока диодов три, а если десять?

Как то же самое делается на микроядре:


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: