13 янв. 2011 г.

CMSIS - code less, create more!

Столкнувшись с необходимостью написать кое-что под имеющийся у меня в наличии чип LPC1768 (на плате LPCXpresso), я приуныл - видимо снова придется читать огроменный даташит, вручную инициализировать кучу периферии и так далее.
Но оказалось, что все мои опасения были беспочвенны, ведь существует готовая библиотека, решающая эти и многие другие проблемы, например проблему отсутствия драйверов к периферии ядра микроконтроллера (USB, I2C, SPI и все остальное). Эта библиотека называется CMSIS - Cortex Microcontroller Software Interface Standard. Она стандартизирована и, как правило, производитель сам пишет свою версию этой библиотеки для производимого им микроконтроллера. Микроконтроллер, естественно, должен принадлежать к семейству Cortex'ов.
В библиотеке CMSIS содержатся следующие хорошие вещи:
  • Определения регистров, входящих в ядро ARM, для упрощения доступа к ним.
  • Определения функций для работы с базовыми периферийными устройствами внутри микроконтроллера (например с таймерами).
  • Также в нее могут входить функции для работы с остальными периферийными устройствами, которые производитель включил в микроконтроллер.
Использование данной библиотеки в программах для встраиваемой вычислительной техники позволяет достаточно просто комбинировать программы от различных производителей внутри одного кристалла - обращения к железу проходят через единый, стандартизированный интерфейс.
Также данная библиотека сохранит время и нервы программистам - во-первых нет необходимости писать заново (в тысячный раз) писать драйвер для работы какого-нибудь SPI-интерфейса, а во вторых можно без проблем использовать код из примеров от производителя или код других разработчиков, если конечно они тоже использует CMSIS.

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

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

Для начала, надо импортировать библиотеку CMSIS, поставляемую вместе со средой разработки. Как это сделать - описано в руководстве по LPCXpresso для начинающих. Ссылка на него - в конце статьи.
После импорта главное окно IDE должно выглядеть примерно так:
Импорт CMSIS удался!
Процесс создания нового проекта описан в уже упомянутом руководстве для начинающих. Увы, но руководство видимо устарело и охватывает не все опции мастера создания проектов в IDE. Некоторые, наиболее важные вещи я опишу здесь.

Во-первых, если вы пошли по пути, предложенному авторами руководства, и создали новый Workspace, то  для начала нужно заново импортировать в него библиотеку CMSIS.
Во-вторых, вдобавок к опции включения CMSIS в проект, добавлена опция использования 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 - настраивается в настройках проекта). Если все было сделано верно, то мы получим скомпилированный бинарник, который всего лишь инкрементирует счетчик в бесконечном цикле.
Компиляция завершена успешно!
Теперь прошьем его в плату. Для этого надо лишь нажать на кнопку "Debug 'test1' [Debug]" (чуть-чуть пониже предыдущей) и немного подождать. Подключенная к компьютеру плата помигает светодиодами и микроконтроллер приступит к выполнению фнукции main(), которое тут же будет прервано брекпойнтом. Продолжим выполнение программы и начнем наслаждаться бессмысленной тратой ресурсов...
Бессмысленная трата ресурсов в самом разгаре
Вот так - не нужно писать стартовый код и инициализировать вручную кучу устройств для запуска микроконтроллера!

К сожалению, библиотека 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].
Установка тулчейна не сложнее установки любой виндосовской программы (да-да, вы таки не ослышались!):
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]:
Для того, чтобы его зажечь, достаточно использовать вывод 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:
test1.axf
Этот файл нужно найти в файловой системе и подменить его ELF-файлом нашего проекта. Затем как обычно - Debug и Resume (F8).

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

Составляющие части библиотеки 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.


Ссылки
  1. CMSIS-Compliant библиотека для LPC17xx: GNU, Keil, IAR (lpc17xx.cmsis.driver.library.zip, 20.7 Мб)
  2. LPCXpresso IDE (Linux, Windows, для скачивания нужна регистрация)
  3. Руководство для начинающих по LPCXpresso IDE (lpcxpresso.getting.started.pdf, 3.0 Мб)
  4. Руководство пользователя по чипам LPC17xx (user.manual.lpc17xx.pdf, 4.9 Мб)
  5. Библиотека CMSIS для LPC17xx с драйверами периферийных устройств, пригодная для импорта в LPCXpresso IDE (lpc.cmsis.driver.library.zip, 10 Мб)
  6. Code Sourcery G++ Lite Edition 2010.09-51 for ARM EABI (~100 Мб)
  7. Code Sourcery G++ Lite Edition for ARM
  8. Патч для CMSIS-Compliant библиотеки для LPC17xx. С ним она компилируется в Linux'е. (CMSIS_LPC1700-linux.patch, 7669 байт)
  9. Программа, зажигающая светодиод на плате (LED_blink_LPC1768.tar.gz, 638 байт)