Эта статья не учебник, не пособие по выбору языка программирования, микроконтроллера, или компилятора. И даже не введение в программирование для микроконтроллеров. Это такой своеобразный "день открытых дверей" для новичков и интересующихся.
Современная автоматизация практически не мыслима без микроконтроллеров. Они пришли на смену схемам с жесткой логикой, что дало новые возможности, но породило и новые вопросы и сложности. Если раньше, для аналоговых, импульсных, и цифровых схем было достаточно знаний электроники (и знаний предметной области, разумеется), то теперь нужно знать и программирование. Особенно это касается любительской, бытовой, автоматизации, так как на производстве не часто приходится все делать одному человеку.
Сегодня я немного коснусь темы разработки программ для микроконтроллеров. Если точнее, то выбора инструментария и некоторым особенностям, которые отличают разработку программ для микроконтроллеров от разработки для ПК. Большая часть статьи будет проиллюстрирована микроконтроллерами Microchip PIC, но это не означает, что сам подход будет не применим для других микроконтроллеров.
Отличительной, и общей, особенностью применяемых любителями микроконтроллеров является ограниченность доступных ресурсов. Это касается и аппаратной части, например, количество выводов и их использование для нескольких функций одновременно, и программной части, например, доступной памяти для программ и данных. Безусловно, существуют и старшие модели микроконтроллеров, у которых ресурсов гораздо больше, но в любительских конструкциях их применяют не часто, по причине высокой цены. Да и паять микросхему с большим количеством маленьких выводов, расположенных близко, решится не каждый. Кроме того, микроконтроллеры разных производителей, да и разные модели одного производителя, могут очень сильно различаться. Да и сама архитектура бывает весьма специфической.
Другими словами, разработка устройств на микроконтроллерах не столь тривиальна и требует хороших знаний и особого подхода. Оставлю в стороне вопросы электроники, это будет в отдельных статьях, поговорим о программировании.
Немного об архитектуре вычислительных систем
Подробное рассмотрение вопросов построения архитектуры микроконтроллеров, в частности, и вычислительных систем, в общем, выходит далеко за рамки данной статьи. Однако несколько слов сказать требуется, иначе многое из сказанного далее будет непонятным.
Существует несколько подходов к построению архитектуры процессоров. Не буду перечислять все, назову лишь две, встречающиеся чаще всего.
Архитектура фон-Неймана
Пожалуй, самая известная, благодаря IBM PC архитектура. Да и большие ЭВМ (IBM, DEC, ЕС, СМ) в большинстве своем строились по этой архитектуре.
Из всех характеристик машин фон-Неймана для важно то, что память программ и память данных представляет собой единое целое
Здесь в оперативной памяти размещается и сама программа (машинные команды), и данные, и специальные области, такие как, например, стек, и операционная система (если она есть), и системные области данных. Это позволяет заполнить область памяти данными, а потом передать на нее управление. Или рассматривать область памяти, занятую программой, как данные.
В современных процессорах, как правило, существует возможность разбиения общей памяти на различные области и организация схем управления доступом к этим областям. Но на сам принцип построения машин фон-Неймана это не влияет.
Однако, эта архитектура практически не применяется в микроконтроллерах. В чем же причина? В том, что в микроконтроллерах программа, чаще всего, размещается в ПЗУ. А использовать ПЗУ для хранения постоянно изменяющихся данных нельзя. Поэтому микроконтроллеры, чаще всего, строятся по Гарвардской архитектуре.
Гарвардская архитектура
Как я уже сказал, самая важная характеристика Гарвардской архитектуры, для нас, раздельные области для программ и данных
Эта, логичная для микроконтроллеров, архитектура накладывает некоторые особенности на разработку программ. Они не столь принципиальны, но могут немного смущать новичков. Например, у нас появляется два разных типа указателей, которые нельзя смешивать. Невозможно указатель на память программ присвоить указателю на данные.
Но я еще ни слова не сказал про ввод-вывод. А ведь это важная часть архитектуры.
Архитектура ввода-вывода
Существует несколько подходов к построению ввода-вывода, то есть, принципов доступа к внешним, и внутренним, устройствам. Но прежде, чем переходить к их описанию, нужно напомнить, что устройства, как внутренние, так и внешние, обычно представлены набором управляющих регистров и, возможно, своей памятью.
С точки зрения программиста, управляющие регистры оборудования могут быть обычными переменными, расположенными в памяти данных. Или могут располагаться в отдельной области памяти, доступ к которой осуществляется специальными командами. Это немного напоминает разницу между Гарвардской и фон-Неймановской архитектурами.
Если управляющие регистры располагаются в отдельных областях памяти, для доступа к ним, обычно, используются специальные команды IN и OUT. Эти команды можно найти, например, в микроконтроллерах MCS-51. Есть они и в микропроцессорах Intel 80x86, и AMD, но используются довольно редко.
Нужно отметить, что не обязательно все регистры размещаются одинаково. Так часть регистров может располагаться в области памяти данных, например, регистр состояния процессора, а часть в специальной области, например, регистр управления питанием.
Так же, необходимо отметить, что внешние устройства могут подключаться к специальным шинам, например, I2C или RS-485. Но, с точки зрения программы, эти шины представлены управляющими регистрами контроллеров соответствующих шин.
На этом, пожалуй, можно закончить краткий обзор архитектур.
Что нужно знать для разработки программ для микроконтроллеров
Несколько прециозное название... Но тем не менее, я попробую обозначить основные знания, без которых трудно обойтись. Не ставлю перед собой цель перечислять абсолютно все. Итак, по порядку:
1. Предметную область
Без знания предметной области невозможно разработать ни программу, ни устройство. Причем, это касается любых программ, в том числе, для больших ЭВМ. Немного темы предметных областей я коснулся в статьях Влажность почвы. Почему все так не просто?, Что может быть проще? Водокачка автомат. и Автоматика. Усложняем задачу. Термостат.
2. Постановку задачи
Нужно четко представлять, что именно требуется сделать. Одно дело, когда, например, нужно поддерживать влажность почвы в заданных пределах. И совсем другое, когда регулировкой влажности почвы нужно достичь максимальной урожайности. В последнем случае нужно учитывать изменение потребности растений в воде в разные периоды развития (вегетация, плодоношение) и многое другое.
Другим примером является требуемая точность, зависящая от задачи. 1 мм это много или мало? Это очень много, если вы разрабатываете блок управления самодельным станком для сверления печатных плат. И ничтожно мало, если вам нужно контролировать длину кабеля в бухте, где его 500 метров.
3. Общие знания по методам измерения и управления
Вот тут немного сложнее. Немного об этом я писал в статье Автоматизация. Начало. Что и почему. Дело в том, что предметная область не всегда дает подсказку, как именно измерять и управлять. Влажность почвы можно измерять разными методами. Как и температуру. Управление может быть простым, на основе релейного автомата, аналоговым, импульсным. Или сложным, таким как ПИД регулирование.
То есть, исходя из предметной области, с учетом постановки задачи, выбираются методы измерений и управления. И уже на основе всего этого строят математическую модель и алгоритмы, лежащие в основе программы.
Сюда же относится и понимание, например, принципов работы различных АЦП и ЦАП, синтезаторов частот, фазового управления, и многого другого, что потребуется вам для решения поставленной задачи.
4. Знать используемые электронные компоненты
На первый взгляд элементарное требование. Но все ли, и всегда ли, внимательно читают документацию на используемые микросхемы и другие детали? А перечитывают? Невнимательность, или недостаточное знание и понимание, могут приводить к странным артефактам в поведении готового устройства. Причем, не редко трудно воспроизводимыми.
Это касается не только разработки электронной части, где нужно учитывать, например, необходимость подтягивающих резисторов на выводах порта, но и программиста, которому нужно знать, например, что одной из микросхем требуется время для инициализации после включения питания.
Обычно программисту нужно знать:
- систему команд микроконтроллера
- состав включенных в состав микроконтроллера периферийных устройств и особенности их работы
- управляющие регистры, их назначение и формат
- режимы работы микроконтроллера
- правила назначения выводам микроконтроллера специальных функций
- схему подключения внешних, по отношению к микроконтроллеру, функциональных устройств (например, контроллеров внешних шин, или модулей памяти) и правила работы с ними
Это далеко не полный список, но он хорошо показывает общий объем требуемых знаний.
Как пример требуемых знаний, приведу свою статью по архитектуре памяти 8-битных семейств микроконтроллеров Microchip PIC (PIC10, PIC12, PIC16, PIC18). Подобное сводное описание архитектуры памяти этих семейств можно найти не часто. Да и для понимания написанного далее эта статья пригодится.
5. Выбранный язык программирования
Да, как бы это ни казалось странным. Ассемблер пугает многих, но его знание чрезвычайно полезно. Для каждого семейства микроконтроллеров язык ассемблера свой. Причем определяется это не только набором команд и их форматом, но и правилами записи, служебным инструкциями, форматом данных, и многим другим. О пользе знания ассемблера я расскажу чуть позже.
Казалось бы, уж с зыками высокого уровня проблем быть не должно. Но это не совсем так. Начну с того, что, обычно, выбор языков высокого уровня не столь богат, как для ПК. Кроме ассемблера наиболее распространен С. Изредка встречаются компиляторы с языка Pascal. Еще реже Basic.
Но давайте вспомним, что мы пишем программу для довольно специфического устройства - микроконтроллера. А значит, даже язык высокого уровня будет учитывать эту специфику. Будут определенные ограничения и специфические расширения. Да и от знания микроконтроллера это не освобождает. Приведу несколько простейших примеров расширений языка С
bit led_K_TRIS @ TRISIO.5;
uns16 TMR1 @ 0x0E;
bank1 char tx[3];
const page1 char tx[] = "Hello!";
T1CON=0b0.0.11.0.0.0.0;
В качестве ограничений, например, может быть невозможность использовать в одном выражении двух обращений к массивам. Например, абсолютно корректное с точки зрения С выражение
led_buf[i]=tbl[bin_val];
может быть объявлено верным, но недопустимым для данного семейства микроконтроллеров. Придется разбивать на два выражения с временной переменной. И это не взятый с потолка пример. Это для реального компилятора. И объясняется это тем, что для индексного доступа используется специальный регистр. Для PIC это FSR, который во многих микроконтроллерах всего один. Подробнее про FSR можно почитать в уже упоминавшейся мной статье Организация памяти 8 битных микроконтроллеров Microchip PIC. Я предупреждал, что она пригодится.
Другой пример, следующее выражение недопустимо для, например, PIC12F510 (допустимо только присваивание), но допустимо для PIC12F629
TRISGPIO |= 0x0E;
А все дело в том, что TRISGPIO это аппаратный регистр, который для PIC12F510 доступен только для записи специальной командой TRIS, а в PIC12F629 он является обычным управляющим регистром в памяти данных и доступен, в том числе, для чтения.
Кстати, пример с TRISGPIO и работой с массивами относится не только к знанию языка программирования, точнее, его диалекта, а и к предыдущему пункту, знанию своих комплектующих.
Следующий фрагмент иллюстрирует работу с управляющими регистрами микроконтроллера
i=10;
FSR=lcd_buf;
do {
i2c_Write(INDF);
FSR++;
} while(--i);
Если вы прочитали статью про организацию памяти, то без труда поймете, что делает этот фрагмент. Но без знания, что за регистры FSR и INDF, ничего не понятно.
К тонкостям программирования мы еще вернемся. Тогда же расскажу, чем полезен ассемблер. Но пока переходим к следующему пункту.
6. Знать используемый компилятор и среду разработки
Это пункт перекликается с предыдущим, так как многие расширения и ограничения определяются используемым компилятором. И для разработки эффективной программы, в условиях ограниченности ресурсов микроконтроллера, нужно досконально знать предоставляемые компилятором возможности и тонкости его работы. И имеющиеся в нем ошибки.
Да, в компиляторах тоже бывают ошибки. Мне приходилось пользоваться одним таким, который не простейшей паре операторов, не вызывающих сообщения об ошибке, начинал генерировать неверные адреса используемых в программе переменных. Производитель ошибку признал. Но до ее исправления приходилось учитывать эту особенность и обходить ее. Кстати, это один из примеров, когда без знания ассемблера и набора команд невозможно найти причины неверной работы программы.
Нужно знать и используемую среду разработки, если она используется. О полезности отладчика много говорить не нужно, но Mplab X от Microchip, позволяет эмулировать и работу периферийных, что бывает очень полезно. Но без внимательного изучения документации этой возможностью не получится воспользоваться.
Пожалуй, на этом остановлюсь со списком требуемых знаний.
Выбор компилятора и среды разработки
Очень не простой вопрос, и комплексный. Встает вместе с выбором языка программирования. Дело в том, что не все производители микроконтроллеров предоставляют средства разработки программ. А если предоставляют, что они могут быть платными, с ограниченными по функционалу бесплатными вариантами. Согласитесь, для любителей вопрос цены далеко не праздный.
Кроме того, могут ограничения по используемым операционным системам. Под Windows, обычно, вопросов не возникает. А вот с Linux уже сложнее. А я как раз использую Linux (точнее, Unix-like системы).
Полный список компиляторов и сред разработки для всех микроконтроллеров привести не возможно. Я ограничусь лишь несколькими компиляторами С для 8-битных Microchip PIC.
Mplab X IDE, mpasm, XC8
Эти средства предоставляет производитель микроконтроллеров, что очень приятно. Среда разработки Mplab X IDE бесплатна и довольно функциональна. mpasm это компилятор с языка Assembler, поставляемый в комплекте с Mplab X IDE. Он так же бесплатен. Поддерживает все модели микроконтроллеров PIC. Несколько ограниченная поддержка макросов, но для любителей будет достаточно.
Скачать Mplab X IDE и mpasm можно с сайта Microchip. И среда разработки, и mpasm работают как под Windows, так и под Linux. Что встречается не часто.
XC8 это компилятор с языка С. Причем весьма продвинутый! Если быть честным, то это не разработка Microchip, а приобретенной ими компании. Возможности компилятора сильно менялись с ростом номера версии. Считается эталонным для PIC. Но на мой взгляд, компилятор сильно перегружен и избыточен, так как рассчитан, большей частью, на разработку программ для старших моделей микроконтроллеров. А с приобретением Microchip компании Atmel он еще и умеет генерировать код для ее микроконтроллеров. Поддерживает С90. Мне этот компилятор кажется неудобным, но на вкус на цвет, как известно...
Компилятор имеет бесплатную версию с ограниченной оптимизацией. С некоторого времени лицензия Standart для него не продается, поэтому для бесплатной версии разрешили больший уровень оптимизации, но это не сильно спасает ситуацию. Генерируемый бесплатной версией код очень избыточен и засорен, его размер раза в полтора-два больше, чем генерируемый бесплатной версией компилятора CC5X (о нем позже).
Скачать этот компилятор можно с сайта Microchip. Компилятор подключается к Mplab X IDE, что предоставляет отличные условия для отладки.
Лицензия PRO, в настоящий момент со скидкой, стоит $995. Есть возможность приобрести подписку за $29.95, но для России это не доступно (я пробовал).
В настоящий момент, компиляторы Microchip и среда разработки поддерживают разработку программ для микроконтроллеров Atmel. Подробности вы можете найти на официальном сайте, и там же скачать необходимое.
CCS С
Неплохой, но довольно не стандартный компилятор. Именно в этом компилятора была та ошибка, о которой я говорил. Есть бесплатная версия для любителей, но с ограничениями. Есть ограниченная по времени полнофункциональная пробная версия. Можно приобрести только компилятор (командная строка) и подключить его к Mplab X IDE, а можно полную среду разработки. Особенностью лицензирования является то, что для компилятор нужно приобретать отдельно для Base Line, Mid Range, PIC18. Есть еще версия для PIC24 и dsPIC. Так что, если нужно разрабатывать программы для разных семейств, покупка может получиться дорогой. Лицензия по времени не ограничена, а вот поддержка и возможность обновления ограничены.
Этот компилятор является наглядным примером того, что нужно досконально знать свой компилятор. Хотя, многие говорят, что он слишком простой и только для начинающих. Кто то даже сказал "попсовый", но я с этим категорически не согласен. Есть версии и под Windows, и под Linux. Приобретаются отдельно, будьте внимательны!
MikroC PRO, MikroPascal PRO, MikroBasic PRO
Компиляторы не только для PIC, но и для других семейств (приобретаются отдельно). Своя IDE. Есть бесплатные ограниченные версии. С этими компиляторами сам не сталкивался, поэтому никаких подробностей сказать не могу. Но новички хвалят. Работает только под Windows. Под wine пробовал запускать, ради интереса, работает плохо и неустойчиво (IDE).
CC5X, CC8E
На эти компиляторы наткнулся случайно, но мне они понравились больше всего. Примеры расширений и ограничений, приведенные выше, показаны как раз на основе этого компилятора. Компилятор бесплатен, с небольшим ограничением на оптимизацию. Можно приобрести полнофункциональную версию. Генерирует самый компактный код. Самые оптимальные (для меня) расширения. Но при этом, он меньше всего автоматизирован. То есть, нужно явно указывать банки памяти данных и страницы памяти программ. Привыкшим свободно писать на С он может показаться ограниченным и неудобным. Но тем, кто много пишет на ассемблере, как я, он прост и естественен. Работает под Windows. Хорошо себя чувствует под wine, но в Linux его не получится подключить к MplabX IDE. Для привыкших к командной строке это не проблема.
О важности знания ассемблера и учета особенностей микроконтроллера
Давайте посмотрим на несколько примеров, которые покажут, насколько разным может быть сгенерированный компилятором С код. Буду использовать CC5X. Примеры будут очень простыми.
Сначала посмотрим, какой код генерируется для приведенного выше примера
i=10;
FSR=lcd_buf;
do {
i2c_Write(INDF);
FSR++;
} while(--i);
А код получается такой, всего 9 команд, которые занимают 9 слов в памяти программ.
MOVLW 0x0A
MOVWF i_3
MOVLW 0x2A
MOVWF FSR
m013:
MOVF INDF,W
CALL i2c_Write
INCF FSR,1
DECFSZ i_3,1
GOTO m013
Теперь о том, что же делает этот фрагмент кода на С. Это передача по шине i2C содержимого буфера lcd_buf, 10 байт. Если написать это на чистом С, то будет примерно так
int i;
for(i=0; i<10; i++) {
i2c_Write(lcd_buf[i]);
}
Теперь будет сгенерирован такой код уже 11 команд (слов). То есть, на 2 команды больше.
CLRF i_3
m013:
MOVLW 0x0A
SUBWF i_3,W
BTFSC 0x03,Carry
GOTO i2c_Stop
MOVLW 0x2A
ADDWF i_3,W
MOVWF FSR
MOVF INDF,W
CALL i2c_Write
INCF i_3,1
GOTO m013
Не обращайте внимания на GOTO i2c_Stop, эта команда стоит после цикла в полной версии программы, я не учитывал ее в подсчете. Велика ли разница в две команды? Вероятно, не очень. Но нужно учесть, насколько небольшой фрагмент программы выбран в качестве примера. В большой программе разница в размере кода может быть существенной, что уже важно.
Немного изменим код, вернемся к первому варианту, но не будем использовать возможности микроконтроллера вручную, только чистый С
i=0;
do {
i2c_Write(lcd_buf[i++]);
} while(i<10);
Получаем такой сгенерированный код, те же 11 команд.
CLRF i_3
m013:
MOVLW 0x2A
ADDWF i_3,W
MOVWF FSR
MOVF INDF,W
CALL i2c_Write
INCF i_3,1
MOVLW 0x0A
SUBWF i_3,W
BTFSS 0x03,Carry
GOTO m013
Что вполне естественно, поскольку это практически идентично варианту с циклом for.
Еще немного изменим код, тоже чистый С
i=0;
while(i<10) {
i2c_Write(lcd_buf[i++]);
};
В данном случае будет сгенерирован код полностью идентичный коду для цикла for, поскольку это его полный аналог.
Получается, что наш первый вариант самый оптимальный? Да. Из чистого С генерируется код использующий те же самые регистры FSR и INDF, но немного не столь красивый. Просто компилятор рассчитан на некоторый усредненный вариант, а мы смогли оптимизировать лучше. И это не ограничения бесплатной версии, этот код сгенерирован коммерческой версией.
Еще большей разницы можно достичь если требуется бежать по массиву в сторону уменьшения индексов. Там уже и разница между for, while и do while будет заметной. А если и с левой стороны оператора присваивания будет стоять массив...
Поскольку эта статья не про особенности генерирования кода конкретным компилятором, пожалуй, закончу с примерами. Важность изучения сгенерированного кода, надеюсь, понятна. А это невозможно без знания системы команд, как минимум, и ассемблера, в оптимальном случае.
А почему не Arduino?
Да, этот вопрос мне задают часто. Почему ничего не пишу для Arduin, ведь это такая популярная штука. Собственно, вся эта статья является своеобразным ответом на этот вопрос. Да и все содержание канала (и сайта, который невелик). Я сторонник глубоких знаний и понимания самой сути вопросов. А Arduino это такой конструктор современности, вроде наборов для сборки радиоприемников, времен СССР. Он хорош для того, что бы заинтересовать новичка. Или для простых самоделок, когда надо быстро и дешево. Но большинство не вдумываются, что это просто распаянный на плате процессор с обвеской, по своей сути. И его сила в большом сообществе и удобной, адаптированной для новичков любителей, среде разработки. Но большинство используют Arduino не вдумываясь и не вдаваясь в подробности. Просто собирая программу из готовых скетчей. Поэтому я признаю, что Arduino может быть хороша в качестве первого шага. Но в дальнейшем нужно переходить к собственно микроконтроллерам и... Все перечисленные выше пункты по необходимым знаниям.
Заключение
На этом все, заканчиваю. Эта статья не учебник, не пособие по выбору языка программирования, микроконтроллера, или компилятора. И даже не введение в программирование для микроконтроллеров. Это такой своеобразный "день открытых дверей" для новичков и интересующихся.