Советы по программированию на C в IAR AVR Embedded Workbench 4.0 |
![]() |
Добавил(а) microsin |
В этой статье решил записывать все интересное, которое удалось накопать при работе в среде программирования IAR Embedded Workbench for AVR (и для программирования с использованием AVR GCC).
2. Добавьте в начало main.c заголовочный файл, который описывает регистры вашего процессора:
Просмотрите этот файл и попытайтесь запомнить имена регистров, которые можно использовать в программе. 3. Добавьте в заголовочный в начало модуля кода:
4. После добавления файла iotiny24.h (имя заголовочного файла определяется типом выбранного процессора, здесь в качестве примера взят процессор ATTINY24) можно удобно управлять портами. Используется также флажок ENABLE_BIT_DEFINITIONS, включающий определение битов. После включения заголовочного файла iotiny24.h и флажка ENABLE_BIT_DEFINITIONS возможны такие операции, как, например:
Очень эффективны операторы типа регистр = (1 << имя_бита), например:
5. Пример программы, мигающей красным светодиодом (светодиоды _RED, _GRN и _YEL подключены к +5V питания через токоограничивающие резисторы):
5a. Добавьте анализ кнопки, подключенный на ножку PA6 и землю - для простоты без всякого анализа дребезга. Мы можем читать лог. сигнал на этой ножке, несмотря на то, что она работает как выход, и на неё нагружен светодиод! Правда, для этот нужна специальная техника (см. ниже). Если бы мы сконфигурировали PA6 как вход, то нужно было бы либо включить внутренний pull-up резистор (записать 1 в бит PORTA6), либо припаять между PA6 и плюсом питания pull-up.
Теперь по поводу управления светодиодом и одновременного чтения кнопки по одному и тому же выводу. Если бы на порте был открытый коллектор, как в архитектуре MCS51, то проблем бы не было, там нет переключения режима порта между входом и выходом, порт всегда работает и как вход, и как выход, и мы можем читать кнопку, просто записав в выходной порт 1, при этом нижний ключ закроется, светодиод погаснет и будет служить pull-up резистором для кнопки. Но в случае с AVR порт двухтактный, и имеет верхний транзистор. Это значит, что мы не сможем жать на кнопку, когда ножка порта сконфигурирована как выход, и на выходе лог. 1 - через верхний транзистор потечет ток короткого замыкания, что неправильно. Для того, чтобы можно было все-таки управлять светодиодом, и одновременно читать кнопку, нужно переключать ножку порта из режима выхода с лог. 0 (когда светодиод горит) в режим входа (при этом светодиод погашен). Начальную установку после включения питания можно не делать, поскольку после сброса наши порты и так окажутся в третьем состоянии, и светодиод на ножке будет погашен. Предположим, у нас ATtiny45, нам нужно управлять желтым светодиодом подключенным к выв. 7 PB2, и анализировать кнопку на этой же ножке (см. [10], там же есть пример схемы макетной платы). После включения питания светодиод не горит, поскольку PB2 работает как вход, и мы можем читать состояние кнопки:
Теперь светодиод горит, и читать кнопку уже нельзя (так же, как и с MCS51). Чтобы снова выключить светодиод (тогда можно будет читать кнопку):
или
6. Список ранее использовавшихся рабочих пространств (он по умолчанию открывается при старте) хранится в реестре по адресу HKEY_CURRENT_USER\Software\IAR Systems\IAR Embedded Workbench IDE\Recent Workspaces\ Почистить его можно только с помощью regedit. 7. Чтобы работали старые добрые комбинации Copy/Paste - Ctrl+Ins/Shift+Ins, нужно добавить в меню Edit алиасы в Tools\Options..., закладка Key Bindings. Целесообразно еще добавить следующие комбинации кнопок (в меню Debug): 8. Простейший способ войти в режим Power Down на ATtiny45:
После этого выйти из режима Sleep можно только по прерыванию Watchdog, прерыванию INT0 или прерыванию Pin Change, USI Start condition и по сбросу. 9. То же самое, но с разрешением выхода из Power Down по прерыванию PCI - Pin Change Interrupt. В примере используется ножка 7 ATtiny45, на которую повешена кнопка (см. схему макетной платы):
Несмотря на то, что тут будет происходить прерывание, обработчик для прерывания PCI мы не делали - IDE и компилятор Си IAR Embedded Workbench генерирует в начале дампа кода заглушки - пустые команды выхода из прерывания (на это влияет включенная по умолчанию галочка Initialize unused interrupt vectors with RETI instructions, которая находится в свойствах проекта, категория General Options, закладка System): В нашем примере будет срабатывать переход по вектору PCINT0, это адрес 000004 в байтовом выражении. 10. Как написать обработчик прерывания, например, для вектора PCINT0:
Теперь, если у нас настроено прерывание Pin Change Interrupt, как было указано в предыдущем совете, то будет вместо заглушки RETI вызываться этот код (__no_operation();). 11. Как настроить таймер Watchdog (WDT):
Все, теперь после 128 мс произойдет вызов вектора прерывания WDT_routine (байтовый адрес 0x000018. Там расположена заглушка RETI. Этот первый вызов WDT_routine аппаратно сбрасывает флаг WDTIE (Watchdog Timeout Interrupt Enable), и уже следующее переполнение таймера через 128 мс вызовет не прерывание, а сброс (переход на адрес 0). Чтобы этого не происходило, нужно добавить обработчик WDT_routine и в нем устанавливать бит WDTIE:
Теперь сброса не будет, просто процессор будет каждые 128 мс просыпаться (если была вызвана команда __sleep();). 12. Команда SLEEP выполняется очень быстро, так что иногда не успевают примениться результаты действия предыдущей команды:
Если бы не было четырех команд NOP, то порт B не успел бы переключиться на вход. У меня, например, продолжал гореть светодиод, подключенный к ножке PB2 (после SLEEP он продолжал работать как выход). Количество холостых операций перед командой SLEEP приходится подбирать экспериментально (иногда для этого лучше использовать циклы). 13. Переменная float занимает 4 байта. 14. Чтобы переменная сохранялась в EEPROM и была доступна при перезагрузке и переподключении питания, нужно указать атрибут памяти __eeprom и ключевое слово __no_init (без __no_init переменная при старте будет обнуляться), например:
Самая первая переменная размещается в EEPROM по адресу 0x01 (не 0x00!). Адрес переменной EEPROM в виде числа uchar можно получить выражением:
15. Рано радоваться и расслабляться, пользуясь прелестями программированиями на Си - память улетает в трубу совершенно незаметно, особенно если использовать типы float, строки (это сразу включает в код библиотечные функции) - за удобства надо платить. Наверное, самое большое неудобство архитектуры AVR - расход как минимум сразу 2 байт на одну команду ассемблера. При компиляции остатки памяти можно выжать "нахаляву" (128..256 байт на 4096 байтах программ), задав полную оптимизацию в свойствах проекта - категория C/C++ Compiler, закладка Optimizations, выбрать Size - High (Maximum optimization), при этом все галочки в окошке "Enabled optimizations" должны быть установлены. 16. Генерировать двоичную прошивку для программатора можно, задав в свойствах проекта, категория Linker, закладка Extra Output, галочку Generate extra output file. В области Format там же нужно выбрать Output format: intel-standard и Format variant: None (либо другой, например Output format: raw-binary и Format variant: None выбирают генерацию чистого двоичного файла). 17. Для генерирования случайного целого числа в диапазоне 0..32767 (или от 0 до константы RAND_MAX) существует имеющаяся в библиотеке CLIB или DLIB функция rand(). Чтобы её можно было использовать, надо добавить в main.c включаемый файл stdlib.h. Чтобы генерить случайное число в любом нужном диапазоне, нужно воспользоваться операцией деления по модулю (%). Например, так генерируется случайное бинарное число (true или false, т. е. случайное число от 0 до 1):
А так генерится число от 0 до 9:
На самом деле генерируемое функцией число не случайное, а псевдослучайное. Это означает, что ряд получаемых чисел при последовательных вызовах rand() всегда один и тот же, если генератор псевдослучайных чисел предварительно проинициализирован. Инициализирует этот генератор функция srand(целое_число). Этим удобно пользоваться, чтобы например, тестировать память. Последовательность действий такая: 18. Чтобы распечатать знак процента % с помощью printf (или sprintf), нужно его повторить дважды:
19. Некоторые глобальные или статические переменные можно поместить прямо в регистры процессора R4–R15, что существенно повысит скорость работы с переменными. Для этого нужно при декларировании переменной использовать расширенное ключевое слово __regvar, а также использовать для компилятора опцию --lock_regs. Пример декларирования регистровой переменной (будет использоваться регистровая пара R15:R14):
Ограничения: Максимальный размер объекта, который можно выделить в регистрах с помощью директивы __regvar, равен 4 байтам. Невозможно использовать указатели на переменную, выделенную с помощью директивы __regvar. Кроме того, нельзя при декларировании задать объекту начальную величину. Кроме того, если Вы используете __regvar, то блокируете таким образом регистры под переменную, и используемые библиотеки тоже должны быть перекомпилированы с тем же набором заблокированных регистров. Поэтому такие извращения лучше использовать в крайних случаях - наверное, лучшей альтернативой будет написать отдельный модуль на ассемблере. Все сказанное в этом совете можно найти в справочнике по компилятору IAR, файл c:\Program Files\IAR AVR Embedded Workbench 4.0\avr\doc\EWAVR_CompilerReference.pdf. См. __regvar, --lock_regs, --memory_model. 20. Проблема с Build Actions -> Post-build command line: невозможно запустить необходимые утилиты после компилирования. Обычно проблема связана с путями запуска программ. В этом случае окне View -> Messages -> Build после компиляции появляется запись "Total number of errors: 1" (хотя ошибок синтаксиса при компиляции не было), и нужные действия, указанные в командной строке запуска Post-build command line не происходят. Трудность выявления и исправления таких ошибок в том, что нет никакой информации о причине проблемы. Первое, что нужно проверить - путь до места нахождения исполняемой утилиты. Она должна быть запущена по полному прямо указанному пути, либо путь запуска должен быть прописан в переменной окружения %Path%. Если исполняемая программа находится в пределах папок проекта, то используйте встроенный в IAR макрос $PROJ_DIR$, который указывает на месторасположение файлов настроек проекта *.ewp и *.eww. Профит использования $PROJ_DIR$ в том, что она позволяет отвязаться от абсолютных путей, и можно спокойно копировать или переносить проект в любую папку на диске, не теряя его работоспособности. Второе, что нужно проверить - видит ли запускаемая программа те файлы, которые надо обработать. Если программа ожидает увидеть входные файлы в своем текущем каталоге, то используйте командный файл для запуска утилиты, и внутри командного файла используйте команду cd для перехода в каталог утилиты. Текущий путь проекта передавайте через опцию командного файла. Пример грамотно настроенной строки Post-build command line: $PROJ_DIR$\..\..\Exe\post_build_pu.bat $PROJ_DIR$ Пример файла post_build_pu.bat: rem в параметре передается путь до файла настроек проекта $PROJ_DIR 21. Как вычислить количество элементов в массиве структур или массиве элементов какого-то определенного типа? Подойдет макрос countof: #define countof(a) (sizeof(a) / sizeof(*(a)))
22. Когда программа скомпилирована с оптимизацией, есть некоторые трудности в использовании отладчика при просмотре переменных. Причина в том, что компилятор для переменных часто использует временные регистры, которые иногда не сохраняют свое значение в том месте, где нужно просмотреть переменную. Как решить проблему, не прибегая к выключению отладки (опции -O0 и т. п.)? Иногда выключить отладку не представляется возможным. ИМХО самый простой способ - присвоить переменной атрибут volatile, тогда компилятор не будет её оптимизировать, и выделит для этой переменной отдельную ячейку памяти. void placetimesectors (void) { volatile u8 ss, mm, hh; RGB_t color; ss = BCDtoBIN(rtc.reg.ss); mm = BCDtoBIN(rtc.reg.mm); hh = BCDtoBIN(rtc.reg.hh & 0x1F); Теперь переменные ss, mm и hh будут легко доступны при пошаговой отладки. Этот совет хорошо подходит для большинства компиляторов, как IAR, так и GCC. Также для AVR GCC (начиная с версии 4.4) можно отключить оптимизацию для блока кода с помощью директивы pragma: #pragma GCC push_options
#pragma GCC optimize ("O0")
//Код, где будет отключена оптимизация
...
#pragma GCC pop_options
Для функции можно отключить оптимизацию добавлением атрибута __attribute__((optimize("O0"))), например так: void __attribute__((optimize("O0"))) foo(unsigned char data) { // не модифицируемый компилятором код ... } [Ссылки] 1. AVR - как избавиться от чисел с плавающей точкой. |