Столкнувшись с необходимостью написать кое-что под имеющийся у меня в наличии чип LPC1768 (на плате LPCXpresso), я приуныл — видимо снова придется читать огроменный даташит, вручную инициализировать кучу периферии и так далее.

Но оказалось, что все мои опасения были беспочвенны, ведь существует готовая библиотека, решающая эти и многие другие проблемы, например проблему отсутствия драйверов к периферии ядра микроконтроллера (USB, I2C, SPI и все остальное). Эта библиотека называется CMSIS — Cortex Microcontroller Software Interface Standard. Она стандартизирована и, как правило, производитель сам пишет свою версию этой библиотеки для производимого им микроконтроллера. Микроконтроллер, естественно, должен принадлежать к семейству Cortex’ов.

В библиотеке CMSIS содержатся следующие хорошие вещи:

  • Определения регистров, входящих в ядро ARM, для упрощения доступа к ним.

  • Определения функций для работы с базовыми периферийными устройствами внутри микроконтроллера (например с таймерами).

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

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

Также данная библиотека сохранит время и нервы программистам — во-первых нет необходимости писать заново (в тысячный раз) писать драйвер для работы какого-нибудь SPI-интерфейса, а во вторых можно без проблем использовать код из примеров от производителя или код других разработчиков, если конечно они тоже использует CMSIS.

Перейдем теперь от теории к практике.

Вначале, в качестве среды разработки я буду использовать основанную на Eclipse среду разработки, поставляемую вместе с платами LPCXpresso. Эта среда разработки привязывается к конфигурации компьютера и требует ключи активации. Но тем не менее все бесплатно, но работает ли (и работает ли полноценно) эта IDE без активации я не проверял.

Открываем LPCXpresso IDE и приступаем

Для начала, надо импортировать библиотеку CMSIS, поставляемую вместе со средой разработки. Как это сделать — описано в руководстве по LPCXpresso для начинающих. Ссылка на него — в конце статьи.

После импорта главное окно IDE должно выглядеть примерно так:

LPCExpresso
Импорт CMSIS удался!

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

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

Во-вторых, вдобавок к опции включения CMSIS в проект, добавлена опция использования CRP:

CRP

Что же это такое?

CRP - это защита от считывания кода, дабы злые китайцы конкуренты не уперли прошивку. В даташите на LPC1768 говорится, что существуют три уровня защиты:

  • На первом уровне запрещается доступ к чипу через JTAG и допускается лишь частичное изменение Flash-памяти чипа (нельзя менять содержимое нулевого сектора) с использованием лишь определенного набора ISP команд.

  • На втором уровне защиты доступ к чипу через JTAG по прежнему запрещен, при этом допускается лишь полная запись/стирание Flash-памяти с использованием сильно ограниченного набора ISP команд.

  • На третьем уровне доступ к чипу запрещен и через JTAG и через ISP! Менять содержимое Flash-памяти можно только при помощи IAP (In Application Programming — когда программа, залитая в чип, сама меняет содержимое Flash-памяти) или же запросив восстановление работоспособности ISP у пользовательского приложения через UART0.

Короче нам такая страшная штука в данный момент не нужна — отключим ее от греха подальше!

Теперь все готово — после нажатия на кнопку "Finish" мы получим готовый и уже открытый проект.

Попробуем теперь, ради теста, скомпилировать и запустить на плате наш новый проект. В "Quickstart Panel→Start here" жмем на кнопку "Build 'test1' [Debug]" (в квадратных скобках указана текущая конфигурация — Debug или Release — настраивается в настройках проекта). Если все было сделано верно, то мы получим скомпилированный бинарник, который всего лишь инкрементирует счетчик в бесконечном цикле.

Compile successfull
Компиляция завершена успешно!

Теперь прошьем его в плату. Для этого надо лишь нажать на кнопку "Debug 'test1' [Debug]" (чуть-чуть пониже предыдущей) и немного подождать. Подключенная к компьютеру плата помигает светодиодами и микроконтроллер приступит к выполнению фнукции main(), которое тут же будет прервано брекпойнтом. Продолжим выполнение программы и начнем наслаждаться бессмысленной тратой ресурсов…​

Application running
Бессмысленная трата ресурсов в самом разгаре

Вот так — не нужно писать стартовый код и инициализировать вручную кучу устройств для запуска микроконтроллера!

К сожалению, библиотека CMSIS, поставляемая с LPCXpresso IDE, включает в себя лишь базовые модули — драйверов устройств, как в [1] там нет.

Конечно, есть возможность прикрутить к LPCXpresso вышеуказанную библиотеку через "Import Example project(s)" — архив с библиотекой в требуемом формате есть на сайте Code Red [5]. Но у меня попытки скомпилировать данную библиотеку не увенчались успехом, даже после ручной правки десятка исходных файлов…​

Ручная компиляция библиотеки с драйверами устройств тоже не вполне удалась. Несмотря на заявленную поддержку инструментальных средств GNU, под ними имеется в виду лишь тулчейн от CodeSourcery. Как выяснилось, тулчейн поставляемый с LPCXpresso и arm-linux-eabi-* тулчейн из репов убунты не особо подходят — может с их помощью и можно скомпилировать HelloWorld с данной библиотекой, но у меня это не вышло — линковщик ругался на -lcs3 not found, причем полезной информации об этой ошибке в гугле — ноль байт.

Используем CodeSourcery G++ под Linux’ом

К сожалению, из всех редакций CodeSourcery G++ полностью бесплатной является лишь Sourcery G++ Lite Edition — свободная, не поддерживаемая разработчиками версия, имеющая в своем составе только лишь утилиты командной строки (компилятор, линковщик, отладчик и т.д.). Скачать ее можно по ссылке в конце поста [6].

Установка тулчейна не сложнее установки любой виндосовской программы (да-да, вы таки не ослышались!):

Sourcery G++ Lite Edition
Next, Next, Next, Finish!

Теперь можно приступать к компиляции CMSIS с драйверами [1]. Чтобы скомпилировать ее под Linux’ом, а не под Windows’ом, придется сделать несколько телодвижений.

Во-первых, нужно поменять обратные слеши в путях файловой системы на прямые в следующих файлах:

  • LPC1700CMSIS/makefile

  • LPC1700CMSIS/makesection/makeconfig

  • LPC1700CMSIS/makesection/makerule/common/make.rules.environment

  • LPC1700CMSIS/Drivers/source/makefile

Во-вторых, необходимо поменять строчку #include "lpc17xx.h", на #include "LPC17xx.h" — юниксовые файловые системы критичны к регистру в именах файлов. Менять надо файлы:

  • LPC1700CMSIS/Drivers/include/lpc17xx_pinsel.h

  • LPC1700CMSIS/Drivers/include/lpc17xx_clkpwr.h

В третьих, надо убрать префикс $(TOOLS_PATH)/ из путей к стандартным юниксовым утилитам (всякие ls, mv, cp) в файле make.rules.environment.

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

patch -p0 -i CMSIS_LPC1700-linux.patch

в родительском каталоге каталога LPC1700CMSIS.

Перед компиляцией библиотеки рекомендую залезть в файл makeconfig и поменять там путь к каталогу с деревом исходных кодов библиотеки (PROJ_ROOT), путь к каталогу с CodeSourcery тулчейном (GNU_INSTALL_ROOT) и версию тулчейна (GNU_VERSION) на актуальные в настоящий момент. Если хочется компилировать программы с использованием библиотеки CMSIS, то можно еще поправить слеши в строчке:

include $(PROJ_ROOT)\makesection\makerule\common\make.rules.ftypes

в файле LPC1700CMSIS/makesection/makerule/example/makefile.ex.

Теперь, когда у нас есть скомпилированная версия библиотеки CMSIS, с драйверами устройств внутри, можно попробовать написать более полезную программу, чем предыдущая. Мы будем зажигать светодиод!

Согласно схеме LPCXpresso из руководства пользователя, на плате есть светодиод, подключенный к порту P0[22]:

LED on LPCExpresso board

Для того, чтобы его зажечь, достаточно использовать вывод 22 порта №0 как вывод GPIO-порта, подтянутый к нулю. Затем нужно записать единицу в соответствующий регистр порта. С применением CMSIS, все это делается так:

#include "lpc17xx_pinsel.h"
#include "lpc17xx_gpio.h"

int main() {
    PINSEL_CFG_Type led2_pin;

    led2_pin.Portnum = PINSEL_PORT_0;
    led2_pin.Pinnum = PINSEL_PIN_22;
    led2_pin.Funcnum = PINSEL_FUNC_0;
    led2_pin.Pinmode = PINSEL_PINMODE_PULLDOWN;
    led2_pin.OpenDrain = PINSEL_PINMODE_NORMAL;

    PINSEL_ConfigPin(&led2_pin);

    GPIO_SetDir(PINSEL_PORT_0, 0x00400000, 1);
    GPIO_SetValue(PINSEL_PORT_0, 0x00400000);

    while(1) {}

    return 0;
}

Все просто и код писать практически не нужно. Кстати, практически не нужно писать и Makefile! Можно воспользоваться всем тем, что нам предоставляет CMSIS:

EXDIRINC=.
EXECNAME=LED_blink_LPC1768
CMSIS_INSTALL=/home/drag0n/Загрузки/lpc17xx.cmsis.driver.library/LPC1700CMSIS

include $(CMSIS_INSTALL)/makesection/makeconfig
include $(CMSIS_INSTALL)/makesection/makerule/example/makefile.ex

clean:
    rm -f LED_blink_LPC1768* \
        main.o

.PHONY: clean

Прошивать проект можно при помощи LPCXpresso IDE — другого способа работать со встроенным JTAG-отладчиком LPCXpresso Board в Linux’е я не нашел.

Делается это так — создается/открывается простейший проект (наподобие нашей первой программы), затем он компилируется, чтобы среда разработки не пыталась перекомпилировать его перед прошивкой.

В процессе компиляции видно, что прошиваемым в плату файлом является projectname.axf:

text1.axf
test1.axf

Этот файл нужно найти в файловой системе и подменить его ELF-файлом нашего проекта. Затем как обычно — Debug и Resume (F8).

Лезем под капот CMSIS

CMSIS parts
Составляющие части библиотеки CMSIS

Основная часть Core Peripheral Access Layer (для Cortex-M3 естественно) содержится в файлах core_cm3.c и core_cm3.h. В них лежат объявления и определения различных структур данных, извлекаемых из регистров:

/** @addtogroup CMSIS_CM3_SysTick CMSIS CM3 SysTick
  memory mapped structure for SysTick
  @{
 */
/** @brief  System Tick Timer (SysTick) register structure definition */
typedef struct
{
  __IO uint32_t CTRL;                         /*!< Offset: 0x00  SysTick Control and Status Register */
  __IO uint32_t RELOAD;                       /*!< Offset: 0x04  SysTick Reload Value Register       */
  __IO uint32_t CURR;                          /*!< Offset: 0x08  SysTick Current Value Register      */
  __IO uint32_t CALIB;                        /*!< Offset: 0x0C  SysTick Calibration Register        */
} SysTick_Type;

Масок и смещений:

/* SysTick Control / Status Register Definitions */
#define SysTick_CTRL_COUNTFLAG_Pos         16                                             /*!< SysTick CTRL: COUNTFLAG Position */
#define SysTick_CTRL_COUNTFLAG_Msk         (1ul << SysTick_CTRL_COUNTFLAG_Pos)            /*!< SysTick CTRL: COUNTFLAG Mask */

#define SysTick_CTRL_CLKSOURCE_Pos          2                                             /*!< SysTick CTRL: CLKSOURCE Position */
#define SysTick_CTRL_CLKSOURCE_Msk         (1ul << SysTick_CTRL_CLKSOURCE_Pos)            /*!< SysTick CTRL: CLKSOURCE Mask */

#define SysTick_CTRL_TICKINT_Pos            1                                             /*!< SysTick CTRL: TICKINT Position */
#define SysTick_CTRL_TICKINT_Msk           (1ul << SysTick_CTRL_TICKINT_Pos)              /*!< SysTick CTRL: TICKINT Mask */

#define SysTick_CTRL_ENABLE_Pos             0                                             /*!< SysTick CTRL: ENABLE Position */
#define SysTick_CTRL_ENABLE_Msk            (1ul << SysTick_CTRL_ENABLE_Pos)               /*!< SysTick CTRL: ENABLE Mask */

/* SysTick Reload Register Definitions */
#define SysTick_LOAD_RELOAD_Pos             0                                             /*!< SysTick LOAD: RELOAD Position */
#define SysTick_LOAD_RELOAD_Msk            (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos)        /*!< SysTick LOAD: RELOAD Mask */

/* SysTick Current Register Definitions */
#define SysTick_VAL_CURRENT_Pos             0                                             /*!< SysTick VAL: CURRENT Position */
#define SysTick_VAL_CURRENT_Msk            (0xFFFFFFul << SysTick_VAL_CURRENT_Pos)        /*!< SysTick VAL: CURRENT Mask */

/* SysTick Calibration Register Definitions */
#define SysTick_CALIB_NOREF_Pos            31                                             /*!< SysTick CALIB: NOREF Position */
#define SysTick_CALIB_NOREF_Msk            (1ul << SysTick_CALIB_NOREF_Pos)               /*!< SysTick CALIB: NOREF Mask */

#define SysTick_CALIB_SKEW_Pos             30                                             /*!< SysTick CALIB: SKEW Position */
#define SysTick_CALIB_SKEW_Msk             (1ul << SysTick_CALIB_SKEW_Pos)                /*!< SysTick CALIB: SKEW Mask */

#define SysTick_CALIB_TENMS_Pos             0                                             /*!< SysTick CALIB: TENMS Position */
#define SysTick_CALIB_TENMS_Msk            (0xFFFFFFul << SysTick_VAL_CURRENT_Pos)        /*!< SysTick CALIB: TENMS Mask */
/*@}*/ /* end of group CMSIS_CM3_SysTick */

А также различных вспомогательных функций:

static __INLINE void __enable_irq()               { __ASM volatile ("cpsie i"); }
static __INLINE void __disable_irq()              { __ASM volatile ("cpsid i"); }

static __INLINE void __enable_fault_irq()         { __ASM volatile ("cpsie f"); }
static __INLINE void __disable_fault_irq()        { __ASM volatile ("cpsid f"); }

static __INLINE void __NOP()                      { __ASM volatile ("nop"); }
static __INLINE void __WFI()                      { __ASM volatile ("wfi"); }
static __INLINE void __WFE()                      { __ASM volatile ("wfe"); }
static __INLINE void __SEV()                      { __ASM volatile ("sev"); }
static __INLINE void __ISB()                      { __ASM volatile ("isb"); }
static __INLINE void __DSB()                      { __ASM volatile ("dsb"); }
static __INLINE void __DMB()                      { __ASM volatile ("dmb"); }
static __INLINE void __CLREX()                    { __ASM volatile ("clrex"); }

Также, в этот же уровень входит несколько специфичных для каждого микроконтроллера файлов, поставляемых производителем. У CMSIS от NXP это:

  • файл LPC17xx.h, который является главным заголовочным файлом для других уровней CMSIS (например для драйверов периферийных устройств). В нем определены регистры микроконтроллера, номера прерываний специфические для LPC17xx серии и т.д.

  • файл startup_LPC17xx.s — ассемблерный файл со стартовым кодом. Есть несколько версий этого файла — для GCC, для IAR и для Keil’а.

Оставшиеся два уровня (Device Peripheral Access Layer и Access Functions for Peripherals) заключены внутри содержимого директории LPC1700CMSIS/Drivers. Исходный код драйвера к какому-нибудь устройству можно найти в файлах lpc17xx_peripheralname.c [.h]. В этих файлах содержатся определения для регистров и областей памяти периферии и вспомогательные функции для работы с этой периферией. Если необходимо работать с каким-нибудь из драйверов в своей программе, то достаточно подключить соответствующий заголовочный файл и внимательно прочитать его (или Doxygen-овскую документацию, чтобы разобраться как с ним работать).

Например, если нам необходимо работать с I2C, то нужно использовать файл lpc17xx_i2c.h и файл lpc17xx_pinsel.h — чтобы использовать соответствующие выводы микроконтроллера для шин SDA и SCL.

Также, в библиотеке CMSIS от NXP содержится каталог с настройками среды сборки — ./makesection. Есть еще и весьма полезные примеры — каталог ./Examples.

Всех интересующихся отсылаю к документации, поставляемой с библиотекой — ./LPC1700CMSIS/LPC1700 Peripheral Driver Library Manual.chm.

arm C