Признайтесь, как часто вы думали о том, чтоб освоить азы программирования микроконтроллеров? Наверняка у вас есть в голове несколько идей потенциальных проектов, но воплощать их в жизнь вы так и не взялись. Так вот: лучше времени для старта, чем сейчас просто не найти.
Почему я решил, что этот вопрос интересен аудитории Хабра? Достаточно проанализировать количество добавления в избранное некоторых постов и выводы напрашиваются сами собой.
С другой стороны, в моём текущем окружении очень много программистов, но почти нет имбедеров. Когда я разговариваю с ними на тему микроконтроллеров, у меня создаётся впечатление, что мнение о них у многих осталось на уровне 10-летней давности.
Не смотря на то, что писать на asm’е для микроконтроллеров проще, чем под x86 архитектуру, многие его боятся и это служит для них преградой на пути к встраиваемым системам. Друзья, для того, чтоб сейчас запустить микроконтроллер, не обязательно, даже, досконально читать даташиты, не говоря уже о том, чтоб знать его инструкции. Конечно, если это – ваша профессия, то тут уровень погружения гораздо выше, но, скажите мне, как часто вы, вне зависимости от того, профессионалом в каком деле вы являетесь, при создании продуктов стараетесь не нарушать принципа инкапсуляции до последнего и нет нет да и заглядываете в исходники используемых библиотек? Сейчас вы поймёте, что я имею в виду.
Я смутно помню те времена, когда я не программировал микроконтроллеры. Я начинал писать на asm’е, не только потому, что это делали все, но и потому, что нормальных инструментов практически не было. Популярность 8-битных контроллеров от AVR я объясняю тем, что для них создавались очень простые в использовании библиотеки, позволяющие создать новый проект, написать десяток строчек кода и получить вполне себе рабочую программу (адреса регистров периферийных устройств и векторы прерываний любезно заполнены создателями библиотек). Я не проводил исследования, это из моих личных воспоминаний — я уверен, что более толковые библиотеки для других контроллеров существовали ещё раньше, но тогда мы об этом не знали.
Первый действительно массовый 32-битный микроконтроллер создала французская контора STM. Именно в тот момент многие любители познакомились с архитектурой Cortex-M3 и начали её широко использовать. Помню, мне одновременно в руки попало 2 контроллера – STM32F103 и LPC1768. Мне надо было как можно быстрее сделать прототип устройства на одном из них. Естественно, мой выбор пал на первый: французы выпустили библиотеку для периферии под названием Standard Peripherals Library и всё, что мне оставалось сделать – это запустить FreeRTOS, подключить необходимую периферию и на основе созданного скелета собирать проект уже на следующем уровне абстракции, не отвлекаясь больше на работу с регистрами. Скелет я использовал и в дальнейшем, часто перегибая и засовывая 32-х битный Cortex туда, где хватило бы и самой маленькой ATtiny, но чаще всего цена позволяла (а там, где не позволяла, либо нужно было пониженное энергопотребление, можно было использовать дешёвые MSP430 и STM8, но случалось это редко).
Конечно, я слукавлю, если скажу, что мне так и не пришлось выучить полностью архитектуру Cortex-M3 и скурить даташит F103 – конечно же, пришлось и тут моё увлечение библиотекой CMSIS и StdPeriph_Lib мне скорее помешало, чем помогло, но скорость вхождения в новое для меня семейство поразила и уже тогда я понял, что мир контроллеров меняется и становится одновременно и проще и сложнее.
И вот мы плавно подобрались к тому, о чём я и хотел вам рассказать. Дело в том, что популярность всяких Arduino сборок долго не давала покоя ребятам из Texas Instruments. Они выпускали лаунчпады на основе MSP430 и продавали их дешевле себестоимости и бесплатной доставкой, они запускали сообщество, в котором можно было выкладывать свои проекты, они создавали Energia – форк Arduino, они выпускали лаунчпады Stellaris, а затем переименовывали их в Tiva C (хотя тут речь идёт о более глобальном ребрендинге и добавлении некоторых новых функций, суть не поменялась). О последнем я и хочу поговорить.
Купить лаунчпад EK-TM4C123GXL можно за 12.99$ вместе с доставкой FedEx (то есть получите вы его достаточно скоро). Плата не изобилует различной электроникой, как, например, Discovery F4 (на борту которой находятся акселерометр, звуковой сенсор, ЦАП, куча светодиодов) – всё, что вы найдёте на Tiva C Launchpad – это RGB диод и 2 кнопки, но его сила заключается не в дополнительных устройствах на плате.
Сила EK-TM4C123GXL в библиотеках, доступных для скачивания с сайта TI под названием TivaWare. Дело в том, что библиотеки для своих контроллеров сейчас пишут все, но многие из них, к сожалению, качеством не особо отличаются и являются скорее традиционными примерами, чем полноценными библиотеками, которые не стыдно использовать в своих проектах (для упомянутого чуть выше LPC1768, NXP написали свою библиотеку почти одновременно с STM, но качеством она тогда не особо отличалась). Библиотека для Tiva C удивляет своей стандартизированностью, документированностью и многообразием.
Чуть позже я предложу вам скачать TivaWare и, если вам не будет лень, то после установки вы сможете наблюдать следующие каталоги:
- Driverlib – тут находятся непосредственно драйверы для периферийных устройств, таких как adc, gpio и так далее (в том числе и заголовочный файл с макросами для вызова функций из rom – об этом далее)
- Examples – ну тут, понятно, примеры. Делятся на boards, peripherals и project. В первой, естественно, проекты для конкретных отладочных плат, во второй примеры использования периферийных устройств и в третьей пример чистого проекта для различных сред разработки (IAR, Keil, CCS) – файлы доступны под лицензией BSD.
- Inc – различные заголовочные файлы с макросами, в том числе и файл tm4c123gh6pm, который пригодится нам для создания простейшего проекта
- Docs – вот это отличительная черта TI — простая и понятная документация. Внутри находятся несколько User Guide, относящиеся к описанным тут компонентам – DriverLib, Examples, Bootloader, IQmath и так далее, а так-же юзергайд непосредственно к используемому нами лаунчпаду.
- Sensorlib – вот это то, что меня действительно удивило: тут находятся драйвера для различных сенсоров сторонних производителей, таких как sht21, lsm303d, MPU6050 и так далее. Я люблю использовать последний (это такая MEMS сборка из акселерометров и гироскопов на одном кристале) и раньше я всегда подключал выдранную из примеров Invensense либу, так что весьма аккуратно написанные исходники от TI меня прям порадовали (кроме того, тут же я нашёл сырцы для работы с квантерионами).
- IQmath – библиотека алгоритмов, оптимизированных для работы с числами с плавающей запятой на Stellaris (Tiva C) устройствах.
- Utils – часто используемые утилиты, для работы с командной строкой, последовательным портом, планировщик и многое другое.
- Оставшиеся каталоги содержат bootloader, сырцы сторонних производителей (FreeRTOS, например), библиотеку для работы с USB, драйвера для Windows и так далее.
Для того, чтоб запустить любой пример на вашем лаунчпаде, достаточно открыть проект из папки examples/boards/ ek-tm4c123gxl в вашей любимой IDE – там всё уже готово (я использую IAR, по этому я открыл ek-tm4c123gxl.eww и он мне загрузил уже настроенный workspace ). Но, знаете, такие настроенные примеры есть сейчас почти у каждого микроконтроллера, а настоящие трудности начинаются, когда мы пытаемся создать что-то своё.
Ок, давайте откажемся не только от примеров но и от настроенного скелета проекта – запустим всё с нуля (естественно с использованием файл из библиотек). Сразу дисклеймер: работать с лаунчпадом я начал только сегодня, так что я пока не знаю почти ничего ни о нём, ни об исходниках с которыми я буду работать. Это и есть основной лейтмотив всей статьи – человек видит Tiva C первый раз и сразу пытается работать с ней.
Я хотел написать сразу какое-нибудь сложное и интересное приложение, но потом понял, что это займёт некоторое время, а публиковать статью надо срочно и, поэтому я решил сделать простенький пример, а потом, если будут желающие, мы сделаем что-нибудь поинтереснее.
Итак, первое, что нам нужно — это скачать и установить библиотеку (если вы не сделали этого до сих пор). Кроме этого, вам необходимо установить IDE и флэшер.
IDE и Flash Programmer находятся в папке Tools.
Если нет — можете скачать отдельно TivaWare for Tiva C Series, LM Flash Programmer и IDE.
В качестве IDE я буду использовать IAR (следовательно и примеры будут с ним), однако вы можете использовать любую из поддерживаемых.
Кто не знает, у IAR есть 2 варианта бесплатного использования — с ограничением по времени и с ограничением по размеру кода. И, естественно, есть другие варианты, если вам не подходят эти 2 (сами знаете какие). Описывать процедуру установки и регистрации IAR я не буду — там всё просто, но, если у вас возникнут трудности — не стесняйтесь задавать вопросы в комментариях.
Итак, первым делом создадим новый проект. В свойствах проекта изменим некоторые настройки.
В C/C++ Compiler, на вкладке Preprocessor укажем путь к папке TivaWare. На этой же вкладке чуть позже мы добавим некоторые константы.
В Debugger выберем TI Stellaris.
Более детальные настройки (если нужно), можно посомотреть в примерах (например там используется другой файл конфигурации линковщика, не тот, что предложен IAR'ом).
Теперь нам необходимо настроить структуру проекта. Я предлагаю придерживаться структуры, предложенной TI (как в примерах), но с некоторыми отличиями.
В папке (на самом деле «в группе», но папки — както привычнее) Src у нас располагаются наши исходники. В примерах туда добавляют и исходники библиотечных файлов, но я считаю, что это только запутает проект.
В папке Library хранятся файлы из DriverLib. TI добавляют туда уже скомпилированный файл (в случае с IAR это файл driverlib/ewarm/Exe/driverlib.a), вы можете сделать так же, но я бы предложил добавлять вместо этого исходники, причём только в случае необходимости — так проще запомнить где что находится, да и смотреть исходный код полезно. Если вы планируете добавлять в эту же папку файлы из других библиотек (Utils, например), то лучше создать ещё один уровень иерархии.
В примерах упущено, и я тоже этого не делал, но, если вы будете писать свои заголовочные файлы, то создайте папку Inc.
Главное не забывать, что вот эти папки — это только для удобства пользователя, они ничего общего с размещением файлов на диске не имеют и никак не влияют на компиляцию.
Итак, в папке Src создадим файл main.c
В папку Library добавим startup_ewarm.c — он нужен для правильной инициализации вектора прерываний. Взять можете его из project_0 (это в examples) проекта, например.
Начнём нашу программу с функции main. Так как к порту F нашего лаунчпада подключен RGB светодиод, то им мы и поморгаем.
void main(void)
{
volatile uint32_t ui32Counter;
//Инициализация периферии
while(1)
{
//Тут код для переключения пинов
for(ui32Counter = 0; ui32Counter < 1000000; ui32Counter++)
{
}
//Тут код для переключения пинов
for(ui32Counter = 0; ui32Counter < 1000000; ui32Counter++)
{
}
}
}
Думаю всё понятно: использовать типы вроде uint32_t — хорошая привычка, это помогает решить неоднозначность с размерами переменных на различных контроллерах (кроме того, это соответствует стандартам MISRA-C); volatile — сообщает компилятору, что эту переменную не стоит оптимизировать (потому-что мы её будем использовать для, в общем-то, бесполезной операции). Дальше — беконечный цикл (как учат студентов — программа на микроконтроллерах не должна никогда кончаться) и 2 счётчика для задержек.
Чтоб этот код компилировался, добавим в начало файла.
#include <stdint.h>
Теперь мы приступим непосредственно к миганию светодиодами.
Скачиваем со стрницы лаунчпада файл Tiva C Series TM4C123G LaunchPad Evaluation Kit User's Manual и читаем в разделе User Switches and RGB User LED о том, что диод подключен к пинам PF1 (red), PF2 (blue) и PF3 (green).
Теперь открываем SW-TM4C-DRL-UG (в папке docs TivaWare) и просматриваем раздел GPIO. Из введения мы понимаем, что сперва пины должны быть сконфигурированны (конфигураций, естественно, множество) на выход. Тут же читаем, что одни из самых полезный функций — GPIOPinRead() и GPIOPinWrite(). Что они делают — понятно, осталось глянуть в их описание для уточнения списка параметров. Тут же находим, что для регистрации пинов на вывод используется функция GPIOPinTypeGPIOOutput(). Итак, меняем комментарии, в нашем коде на:
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2); //для инициализации
//и
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0xFF);
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 0);
//и
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0);
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 0xFF);
Для включения и выключения диодов.
Естественно, не забываем добавить файл driverlib/gpio.c в папку Library, а так же
#include "driverlib/gpio.h"
в заголовки.
Кроме того, уже в процессе компиляци замечаем, что необходимо добавить ещё 2 заголовочных файла:
#include <stdbool.h> // Для поддержки типа bool, который используется в gpio
#include "inc/hw_memmap.h" //Для поддержки деклараций пинов.
Теперь наша программа выглядит вот так:
#include <stdint.h>
#include <stdbool.h>
#include "driverlib/gpio.h"
#include "inc/hw_memmap.h"
void main(void)
{
volatile uint32_t ui32Counter;
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1);
while(1)
{
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0xFF);
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 0);
for(ui32Counter = 0; ui32Counter < 2000000; ui32Counter++)
{
}
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0);
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 0xFF);
for(ui32Counter = 0; ui32Counter < 2000000; ui32Counter++)
{
}
}
}
И, если вы её скомпилируете и запустите — сможете наблюдать поочерёдную смену цвета диода.
Но моргание светодиодом — слишком просто для нас. Давайте пойдём чуть дальше и добавим поддержку порта ввода-вывода.
Действия — все те же. Находим к каким портам подключен UART, читаем про конфигурацию модуля в UserGuide, конфигурируем, используем функции для записи в и чтения из uart.
Моя функция инициализации uart получилась похожа на функцию из примера, но с одним интересным отличием. Вот так выглядит инициализация из примера:
void ConfigureUART(void)
{
//
// Enable the GPIO Peripheral used by the UART.
//
ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
//
// Enable UART0
//
ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
//
// Configure GPIO Pins for UART mode.
//
ROM_GPIOPinConfigure(GPIO_PA0_U0RX);
ROM_GPIOPinConfigure(GPIO_PA1_U0TX);
ROM_GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
//
// Use the internal 16MHz oscillator as the UART clock source.
//
UARTClockSourceSet(UART0_BASE, UART_CLOCK_PIOSC);
//
// Initialize the UART for console I/O.
//
UARTStdioConfig(0, 115200, 16000000);
}
Как видите, тут используются странные функции с префиксом ROM_ — это специальные функции, которые уже сохранены в ROM'е микроконтроллера лаунчпада. Прочитать о них можно в всё том же UserGuide для DRL. Созданны они для того, чтоб уменьшить размер кода в Flash памяти. Нужно оно вам или нет — решать вам, мне идея понравилась (раз уж я всё-равно использую Peripheral Driver Library). Кстати, если вы не знаете, будет код использвоаться на устройстве с кусками библиотеки в ROM или нет — вы можете использовать Mapped ROM Calls. Тогда будет использоваться код из ROM, если он есть и компилироваться, если его нет.
Для работы с ROM необходимо настроить несколько констант: в опциях проекта, в C/C++ Compiler, на вкладке Preprocessor добавить константу TARGET_IS_BLIZZARD_RB1 в поле Defined Symbols. Туда же сразу добавьте PART_TM4C123GH6PM и ewarm — они нужны для успешной компиляции фалов библиотеки.
Кроме того, необходимо добавить в дерево проекта недостающие файлы:
Итак, всё, что нам осталось, это вывести что-то в порт (можете использовать для чтения любой эмулатор терминала. Например, для Windows я использовал Real Term). Затем, я предлагаю считать букву из порта, проверить, принадлежит ли она к одному из цветов (r, g, b) и поменять состояние соответствующего пина.
Функция для инициализации UART у вас уже есть. Изменяем инициализацию портов, чтоб добавить третий пин (раньше мы настроили только 2 на выход). Вывести строку в терминал можно с помощью функции UARTprintf(); из библиотеки utils/uartstdio.c (естественно, этот необходимо добавить к проекту и подключить заголовчный файл).
Считываем символ функцией UARTCharGet(). Она входит в цикл до тех пор, пока в уарт не придёт символ. После этого совершаем действия над пинами и возвращаемся в начало цикла.
#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/gpio.h"
#include "driverlib/rom.h"
#include "driverlib/sysctl.h"
#include "driverlib/uart.h"
#include "driverlib/pin_map.h"
#include "utils/uartstdio.h"
void ConfigureUART(void)
{
ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
ROM_GPIOPinConfigure(GPIO_PA0_U0RX);
ROM_GPIOPinConfigure(GPIO_PA1_U0TX);
ROM_GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
UARTClockSourceSet(UART0_BASE, UART_CLOCK_PIOSC);
UARTStdioConfig(0, 115200, 16000000);
}
void main(void)
{
volatile uint32_t ui32Loop;
uint32_t ui32Color;
uint8_t ui8Red = 0xFF;
uint8_t ui8Green = 0xFF;
uint8_t ui8Blue = 0xFF;
GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3);
ConfigureUART();
UARTprintf("Hello HabraHabr!n");
while (1)
{
UARTprintf("Please, enter color(r,g,b) n");
ui32Color = 'g';
switch (ui32Color)
{
case 'r':
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, ui8Red);
ui8Red = ~ui8Red;
break;
case 'b':
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, ui8Blue);
ui8Blue = ~ui8Blue;
break;
case 'g':
GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3, ui8Green);
ui8Green = ~ui8Green;
break;
default:
UARTprintf("Incorrect color! n");
break;
}
}
}
В TivaWare я нашёл интересную либу – utils/cmdline. Она позволяет создавать и обрабатывать команды введённые из командной строки. Я хотел сделать интересный проект с использованием её и ещё нескольких интересных библиотек, но время поджимает, так что я могу написать об этом позже, если будет интерес (а так же о прерываниях, которые тут даже не упомянуты и о FreeRTOS).
Ну, а теперь о том, почему я несколько раз написал о том, что время поджимает и почему из всех благоприятных времён для начала изучений контроллеров сейчас самое благоприятное: 22 января на edX стартуют курсы Embedded Systems — Shape The World.
Регистрация бесплатная, но, если вы хотите получить сретификат, то необходимо заплатить взнос (минимум 50$). Лично я заплатил 50$ — не из-за сертификата, а просто из любви к подобным курсам и в их поддержку.
Для участия вам надо купить Tiva C лаунчпад и различную рассыпуху. Последнюю вы можете купить на любом радиорынке, а вот с лаунчпадом придётся поторопиться: TI обычно отправляют через FedEx, но это может занять до 10 дней, в то время, как до начала курса осталась неделя.
Но переживать не стоит: не думаю, что на первом уроке вам сразу понадобится работать с железом, возможно вы сможете просимулировать вашу программу.
Итак, немного выводов. Использование подобных библиотек – палка о двух концах. С одной стороны она упрощает разработку, понижает порог входа, с другой стороны создаёт уровень абстракции, который усложняет понимание основ (во всей статье нет ни одной отсылки к даташиту, и это – неправильно: даташит надо смотреть всегда и это является обязательным условием певращения из любителя в профессионала). Но у подобных библиотек в отличии от ардуино (кстати, Energia поддерживает описанный лаунчпад), есть одно преимущество: они не создают у вас ложного понимания реальности. Если вы используете библиотеку, вы понимаете, что за абстракцией кроется вполне реальное устройство и параллели между функциями и реальными регистрами проследить совсем не сложно.
Я надесюь, что данный материал подтолкнёт вас к покупке и изучению (пусть и столь поверхностному) этого замечательного устройства. Если у вас есть идеи проектов, которые вы могли бы реализовать на TM4C123G, но возникли сложности в реализации – не стесняйтесь писать об этом в комментариях: будем разбираться вместе.
Автор: aronsky