Подключение стандартной библиотеки периферии к любому семейству STM32. STM32F407(STM32F4-DISCOVERY) - Нестандартный подход - Стандартная библиотека ч.1

Подключение стандартной библиотеки периферии к любому семейству STM32. STM32F407(STM32F4-DISCOVERY) - Нестандартный подход - Стандартная библиотека ч.1

Необходимое для разработки программное обеспечение. В этой статье я расскажу как его правильно настроить и связать. Все коммерческие среды такие как IAR EWARM или Keil uVision обычно сами выполняют эту интеграцию, однако в нашем случае все придется настраивать вручную, потратив на это немало времени. Преимуществом является то, что у вас есть шанс понять как это все работает изнутри, и в дальнейшем гибко настраивать все под себя. Перед началом настройки рассмотрим структуру среды в которой мы будем работать:

Eclipse будет использован для удобного редактирования файлов реализации функций (.c ), заголовочных файлов (.h ), а также файлов ассемблера (.S ). Под "удобным" я понимаю использование автодополненения кода, подсветки синтаксиса, рефакторинга, навигации по функциям и их прототипам. Файлы автоматически скармливаются нужным компиляторам, которые генерируют объектный код (в файлах .o ). Пока что этот код не содержит абсолютных адресов переменных и функций и по этому для выполнения не пригоден. Полученные объектные файлы собираются воедино сборщиком (linker-ом). Чтобы знать, какие участки адресного пространства использовать, сборщик использует специальный файл (.ld ), который называется линкер-скриптом. Он обычно содержит определение адресов секций и их размеров (секция кода, отображаемая на флеш, секция переменных, отображаемая на ОЗУ и т.д.).

В конце концов linker генерирует.elf файл (Executable and Linkable Format), который содержит в себе кроме инструкций и данных отладочную информацию (Debugging information), используемую отладчиком. Для обычной прошивки программой vsprog этот формат не подходит, поскольку для этого нужен более примитивный файл образа памяти (например Intel HEX - .hex). Для его генерации тоже есть инструмент из набора Sourcery CodeBench (arm-none-eabi-objcopy), и он отлично интегрируются в eclipse с помощью установленного ARM-плагина.

Для осуществления самой отладки используются три программы:

  1. сам eclipse, дающий возможность программисту "визуально" использовать отладку, ходить по строкам, наводить курсором мышки на переменные для просмотра их значений, и прочие удобности
  2. arm-none-eabi-gdb - GDB клиент - отладчик, который скрыто управляется eclips-ом(через stdin) в качестве реакции на действия, указанные в п.1. В свою очередь GDB подключается к Debug-серверу OpenOCD, и все поступающие на вход команды транслируются отладчиком GDB в команды, понятные для OpenOCD. Канал GDB <-> OpenOCD реализуется по протоколу TCP.
  3. OpenOCD - это debug-сервер, который может общаться непосредственно с программатором. Он запускается перед клиентом и ожидает подключения по TCP.

Данная схема может показаться вам весьма бесполезной: зачем использовать клиент и сервер в отдельности и выполнять лишний раз трансляцию команд, если все это можно было бы делать одним отладчиком? Дело в том, что такая архитектура теоретически позволяет удобно делать взаимозамену клиента и сервера. Например, если вам будет нужно вместо versaloon использовать другой программатор, который не будет поддерживать OpenOCD, а будет поддерживать другой специальный Debug-сервер (например texane/stlink для программатора stlink - который находится в отладочной плате STM32VLDiscovery), то вы просто вместо запуска OpenOCD будете запускать нужный сервер и все должно работать, без каких-либо дополнительных телодвижений. В то же время возможна обратная ситуация: допустим вы захотели использовать вместо связки Eclipse + CodeBench, среду IAR EWARM вместе с versaloon. У IAR есть свой встроенный Debug-клиент, который успешно свяжется с OpenOCD и будет им рулить, а также получать в ответ нужные данные. Однако все это иногда остается только в теории, поскольку стандарты общения клиента и сервера регламентированы не жестко, и местами могут отличатся, однако указанные мною конфигурации с st-link+eclipse и IAR+versaloon мне удавались.

Обычно клиент и сервер запускаются на одной машине и подключение к серверу происходит по адресу localhost:3333 (Для openocd), или localhost:4242 (для texane/stlink st-util). Но никто не мешает открыть порт 3333 или 4242 (и пробросить этот порт на роутере во внешнюю сеть) и ваши коллеги из другого города смогут подключится и отладить вашу железку. Данный трюк часто используется ембеддерами, работающими на удаленных объектах, доступ к которым ограничен.

Приступаем

Запускаем eclipse и выбираем File->New->C Project, выбираем тип проекта ARM Linux GCC (Sorcery G++ Lite) и имя "stm32_ld_vl" (Если у вас STV32VLDiscovery то логичнее будет назвать "stm32_md_vl"):

Нажимаем Finish, сворачиваем или закрываем окно Welcome. Итак, проект создан, и в вашем workspace должна появиться папка stm32_ld_vl. Теперь ее нужно наполнить необходимыми библиотеками.

Как вы поняли из названия проекта, я буду создавать проект для вида линейки low-density value line (LD_VL). Чтобы создать проект для других микроконтроллеров вы должны заменить все файлы и define-ы в названии которых присутствует _LD_VL (или _ld_vl ) на нужные вам, в соответствии с таблицей:

Вид линейки Обозначение Микроконтроллеры (х может менятся)
Low-density value line _LD_VL STM32F100x4 STM32F100x6
Low-density _LD STM32F101x4 STM32F101x6
STM32F102x4 STM32F102x6
STM32F103x4 STM32F103x6
Medium-density value line _MD_VL STM32F100x8 STM32F100xB
Medium-density
_MD
STM32F101x8 STM32F101xB
STM32F102x8 STM32F102xB
STM32F103x8 STM32F103xB
High density Value line _HD_VL STM32F100xC STM32F100xD STM32F100xE
High density _HD STM32F101xC STM32F101xD STM32F101xE
STM32F103xC STM32F103xD STM32F103xE
XL-density _XL STM32F101xF STM32F101xG
STM32F103xF STM32F103xG
Connectivity line _CL STM32F105xx и STM32F107xx

Чтобы понять логику таблицы, вы должны быть знакомы с маркировкой STM32 . То есть, если у вас VLDiscovery то дальше вам придется заменять все что связано с _LD_VL на _MD_VL, поскольку в дискавери распаян чип STM32F100RB, относящийся к Medium-density value line.

Добавление в проект библиотек CMSIS и STM32F10x Standard Peripherals Library

CMSIS (Cortex Microcontroller Software Interface Standard) - стандартизированная библиотека работы с микроконтроллерами Cortex, выполняющая реализацию уровня HAL (Hardware Abstraction Layer), тоесть позволяет абстрагироваться от деталей работы с регистрами, поиска адресов регистров по даташитам и т.д. Библиотека представляет собой набор из исходников на языке С и Asm. Ядерная (Core) часть библиотеки одинакова для всех Cortex-ов (Будь это ST, NXP, ATMEL, TI или еще кто другой), и разрабатывается компанией ARM. Другая же часть библиотеки отвечает за периферию, которая естественно различна у разных производителей. Поэтому в конечном итоге полная библиотека все равно распространяется производителем, хотя ядерную часть все же можно скачать отдельно на сайте ARM. Библиотека содержит определения адресов, код инициализации тактового генератора (удобно настраиваемый define-ами), и все прочее, что избавляет программиста от ручного введения в свои проекты определения адресов всяческих регистров периферии и определения битов значений этих регистров.

Но ребята из ST пошли дальше. Помимо поддержки CMSIS они предоставляют еще одну библиотеку для STM32F10x под названием Standard Peripherals Library (SPL ), которая может использоваться в дополнение к CMSIS. Библиотека обеспечивает более быстрый и удобный доступ к периферии, а также контролирует (в некоторых случаях) правильность работы с периферией. Поэтому данную библиотек часто называют набором драйверов к периферийным модулям. Она сопровождается пакетом примерчиков, разделенных по категориям для разной перифериии. Библиотека также есть не только для STM32F10x, но и под другие серии.

Скачать всю SPL+CMSIS версии 3.5 можно тут: STM32F10x_StdPeriph_Lib_V3.5.0 или на сайте ST. Разархивируйте архив. Создайте папки CMSIS и SPL в папке проекта и начнем копировать файлы к себе в проект:

Что копировать

Куда копировать (учитывая,
что папка проекта stm32_ld_vl)

Описание файла
Libraries/CMSIS/CM3/
CoreSupport/core_cm3.c
stm32_ld_vl/CMSIS/core_cm3.c Описание ядра Cortex M3
Libraries/CMSIS/CM3/
CoreSupport/core_cm3.h
stm32_ld_vl/CMSIS/ core_cm3.h Заголовки описания ядра

ST/STM32F10x/system_stm32f10x.c
stm32_ld_vl/CMSIS/ system_stm32f10x.c Функции инициализации и
управления тактовой частотой
Libraries/CMSIS/CM3/DeviceSupport/
ST/STM32F10x/system_stm32f10x.h
stm32_ld_vl/CMSIS/ system_stm32f10x.h Заголовки к этим функциям
Libraries/CMSIS/CM3/DeviceSupport/
ST/STM32F10x/stm32f10x.h
stm32_ld_vl/CMSIS/ stm32f10x.h Основное описание периферии
Libraries/CMSIS/CM3/DeviceSupport/
ST/STM32F10x/startup/gcc_ride7/
startup_stm32f10x_ld_vl.s
stm32_ld_vl/CMSIS/ startup_stm32f10x_ld_vl.S
(!!! Внимание расширение файла CAPITAL S)
Файл с таблицей векторов
прерываний и init-ами на asm
Project/STM32F10x_StdPeriph_Template/
stm32f10x_conf.h
stm32_ld_vl/CMSIS/stm32f10x_conf.h Шаблон для настройки
периферийных модулей

inc/*
stm32_ld_vl/SPL/inc/* Заголовочные файлы SPL
Libraries/STM32F10x_StdPeriph_Driver/
src/*
stm32_ld_vl/SPL/src/* Реализация SPL

После копирования зайдите в Eclipse и сделайте Refresh в контекстном меню проекта. В результате в Project Explorer вы должны получить такую же структуру как на картинке справа.

Возможно вы заметили, что в папке Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/ есть папки для разных IDE (в разных IDE используются разные компиляторы). Я выбрал IDE Ride7, так как в ней используется компилятор GNU Tools for ARM Embedded, совместимый с нашим Sourcery CodeBench.

Вся библиотека конфигурируется с помощью препроцессора (с помощью define-ов), это позволят решить все необходимые ветвления еще на стадии компиляции (вернее даже перед ней) и избежать нагрузки в работе самого контроллера (которая наблюдалась бы, если бы конфигурирование выполнялось в RunTime). Например все оборудование различное для разных линеек и поэтому чтобы библиотека "узнала", какую линейку вы хотите использовать, вас просят раскомментировать в файле stm32f10x.h один из define-ов (соответствующих вашей линейке):

/* #define STM32F10X_LD */ /*!< STM32F10X_LD: STM32 Low density devices */
/* #define STM32F10X_LD_VL */ /*!< STM32F10X_LD_VL: STM32 Low density Value Line devices */
/* #define STM32F10X_MD */ /*!< STM32F10X_MD: STM32 Medium density devices */

И так далее...

Но делать этого я не советую. Файлы библиотек мы трогать пока не будем, а define мы сделаем позже с помощью настроек компилятора в Eclipse. И тогда Eсlipse будет вызвать компилятор с ключем -D STM32F10X_LD_VL , что для препроцессора абсолютно эквивалентно ситуации если бы вы расскомментировали "#define STM32F10X_LD_VL" . Таким образом код мы менять не будем, в следствии, при желании, когда-нибудь вы сможете вынести библиотеку в отдельную директорию и не копировать в папку каждого нового проекта.

Linker-скрипт

В контекстном меню проекта выбираем New->File->Other->General->File, Next. Выбираем корневую папку проекта (stm32_ld_vl). Вводим имя файла "stm32f100c4.ld" (или "stm32f100rb.ld" для дискавери). Теперь копируем и вставляем в eclipse:

ENTRY(Reset_Handler) MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4K } _estack = ORIGIN(RAM) + LENGTH(RAM); MIN_HEAP_SIZE = 0; MIN_STACK_SIZE = 256; SECTIONS { /* Interrupt vector table */ .isr_vector: { . = ALIGN(4); KEEP(*(.isr_vector)) . = ALIGN(4); } >FLASH /* The program code and other data goes into FLASH */ .text: { . = ALIGN(4); /* Code */ *(.text) *(.text*) /* Constants */ *(.rodata) *(.rodata*) /* ARM->Thumb and Thumb->ARM glue code */ *(.glue_7) *(.glue_7t) KEEP (*(.init)) KEEP (*(.fini)) . = ALIGN(4); _etext = .; } >FLASH .ARM.extab: { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH .ARM: { __exidx_start = .; *(.ARM.exidx*) __exidx_end = .; } >FLASH .ARM.attributes: { *(.ARM.attributes) } > FLASH .preinit_array: { PROVIDE_HIDDEN (__preinit_array_start = .); KEEP (*(.preinit_array*)) PROVIDE_HIDDEN (__preinit_array_end = .); } >FLASH .init_array: { PROVIDE_HIDDEN (__init_array_start = .); KEEP (*(SORT(.init_array.*))) KEEP (*(.init_array*)) PROVIDE_HIDDEN (__init_array_end = .); } >FLASH .fini_array: { PROVIDE_HIDDEN (__fini_array_start = .); KEEP (*(.fini_array*)) KEEP (*(SORT(.fini_array.*))) PROVIDE_HIDDEN (__fini_array_end = .); } >FLASH _sidata = .; /* Initialized data */ .data: AT (_sidata) { . = ALIGN(4); _sdata = .; /* create a global symbol at data start */ *(.data) *(.data*) . = ALIGN(4); _edata = .; /* define a global symbol at data end */ } >RAM /* Uninitialized data */ . = ALIGN(4); .bss: { /* This is used by the startup in order to initialize the .bss secion */ _sbss = .; /* define a global symbol at bss start */ __bss_start__ = _sbss; *(.bss) *(.bss*) *(COMMON) . = ALIGN(4); _ebss = .; /* define a global symbol at bss end */ __bss_end__ = _ebss; } >RAM PROVIDE(end = _ebss); PROVIDE(_end = _ebss); PROVIDE(__HEAP_START = _ebss); /* User_heap_stack section, used to check that there is enough RAM left */ ._user_heap_stack: { . = ALIGN(4); . = . + MIN_HEAP_SIZE; . = . + MIN_STACK_SIZE; . = ALIGN(4); } >RAM /DISCARD/ : { libc.a(*) libm.a(*) libgcc.a(*) } }

Данный линкер-скрипт будет предназначен именно для контроллера STM32F100C4 (у которого 16 Кб флеша и 4 Кб ОЗУ), если у вас другой, то придется поменять параметры LENGTH у областей FLASH и RAM в начале файла (для STM32F100RB, который в Discovery: Flash 128K и ОЗУ 8К).

Сохраняем файл.

Настройка сборки (C/C++ Build)

Заходим в Project->Properties->C/C++ Build-> Settings->Tool Settings, и начинаем настраивать инструменты сборки:

1) Target Precessor

Выбираем под какое именно ядро Cortex компилятор будет работать.

  • Processor: cortex-m3

2) ARM Sourcery Linux GCC C Compiler -> Preprocessor

Добавляем два define-a путем передачи их через ключ -D компилятору.

  • STM32F10X_LD_VL - определяет линейку (о этом define-е я писал выше)
  • USE_STDPERIPH_DRIVER - указание библиотеке CMSIS, что она должна использовать драйвер SPL

3) ARM Sourcery Linux GCC C Compiler -> Directories

Добавляем пути к includ-ам библиотек.

  • "${workspace_loc:/${ProjName}/CMSIS}"
  • "${workspace_loc:/${ProjName}/SPL/inc}"

Теперь, например, если мы напишем:

#include "stm32f10x.h

То компилятор должен сначала поискать файл stm32f10x.h в директории проекта (он это делает всегда), он его там не найдет и приступит к поиску в папке CMSIS, путь к которой мы указали, ну и найдет его.

4) ARM Sourcery Linux GCC C Compiler -> Optimization

Включим оптимизацию функций и данных

  • -ffunction-sections
  • -fdata-sections

В результате все функции и элементы данных будут помещены в отдельные секции, и сборщик сможет понять какие секции не используются и просто выкинет их.

5) ARM Sourcery Linux GCC C Compiler -> General

Добавляем путь к нашему linker-скрипту: "${workspace_loc:/${ProjName}/stm32f100c4.ld}" (или как он у вас называется).

И ставим опции:

  • Do not use standard start files - не использовать стандартные файлы запуска.
  • Remove unused sections - удалить неиспользованные секции

Все, настройка закончена. OK.

С момента создания проекта мы много всего сделали, и кое-чего Eclipse мог не заметить, по этому нам нужно сказать ему чтобы он пересмотрел структуру файлов проекта. Для этого из контекстного меню проекта нужно сделать Index -> rebuild .

Hello светодиоды на STM32

Пора создать главный файл проекта: File -> New -> C/C++ -> Source File. Next. Имя файла Source file: main.c.

Копируем и вставляем в файл следующее:

#include "stm32f10x.h" uint8_t i=0; int main(void) { RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // Enable PORTB Periph clock RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Enable TIM2 Periph clock // Disable JTAG for release LED PIN RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; AFIO->MAPR |= AFIO_MAPR_SWJ_CFG_JTAGDISABLE; // Clear PB4 and PB5 control register bits GPIOB->CRL &= ~(GPIO_CRL_MODE4 | GPIO_CRL_CNF4 | GPIO_CRL_MODE5 | GPIO_CRL_CNF5); // Configure PB.4 and PB.5 as Push Pull output at max 10Mhz GPIOB->CRL |= GPIO_CRL_MODE4_0 | GPIO_CRL_MODE5_0; TIM2->PSC = SystemCoreClock / 1000 - 1; // 1000 tick/sec TIM2->ARR = 1000; // 1 Interrupt/1 sec TIM2->DIER |= TIM_DIER_UIE; // Enable tim2 interrupt TIM2->CR1 |= TIM_CR1_CEN; // Start count NVIC_EnableIRQ(TIM2_IRQn); // Enable IRQ while(1); // Infinity loop } void TIM2_IRQHandler(void) { TIM2->SR &= ~TIM_SR_UIF; //Clean UIF Flag if (1 == (i++ & 0x1)) { GPIOB->BSRR = GPIO_BSRR_BS4; // Set PB4 bit GPIOB->BSRR = GPIO_BSRR_BR5; // Reset PB5 bit } else { GPIOB->BSRR = GPIO_BSRR_BS5; // Set PB5 bit GPIOB->BSRR = GPIO_BSRR_BR4; // Reset PB4 bit } }

Хоть мы подключали библиотек SPL, тут она использована не была. Все обращения к полям вроде RCC->APB2ENR полностью описаны в CMSIS.

Можно выполнять Project -> Build All. Если все получилось, то в папке Debug проекта должен появится файл stm32_ld_vl.hex. Он был автоматически сгенерирован из elf встроенными инструментами. Прошиваем файл и видим как мигают светодиоды с частотой раз в секунду:

Vsprog -sstm32f1 -ms -oe -owf -I /home/user/workspace/stm32_ld_vl/Debug/stm32_ld_vl.hex -V "tvcc.set 3300"

Естественно вместо /home/user/workspace/ вы должны вписать свой путь к workspace.

Для STM32VLDiscovery

Код немного отличается от того, который я дал выше для своей отладочной платки. Отличие заключается в пинах, на которых "висят" светодиоды. Если у меня в плате это были PB4 и PB5, то в Discovery это PC8 и PC9.

#include "stm32f10x.h" uint8_t i=0; int main(void) { RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // Enable PORTC Periph clock RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Enable TIM2 Periph clock // Clear PC8 and PC9 control register bits GPIOC->CRH &= ~(GPIO_CRH_MODE8 | GPIO_CRH_CNF8 | GPIO_CRH_MODE9 | GPIO_CRH_CNF9); // Configure PC8 and PC9 as Push Pull output at max 10Mhz GPIOC->CRH |= GPIO_CRH_MODE8_0 | GPIO_CRH_MODE9_0; TIM2->PSC = SystemCoreClock / 1000 - 1; // 1000 tick/sec TIM2->ARR = 1000; // 1 Interrupt/sec (1000/100) TIM2->DIER |= TIM_DIER_UIE; // Enable tim2 interrupt TIM2->CR1 |= TIM_CR1_CEN; // Start count NVIC_EnableIRQ(TIM2_IRQn); // Enable IRQ while(1); // Infinity loop } void TIM2_IRQHandler(void) { TIM2->SR &= ~TIM_SR_UIF; //Clean UIF Flag if (1 == (i++ & 0x1)) { GPIOC->BSRR = GPIO_BSRR_BS8; // Set PC8 bit GPIOC->BSRR = GPIO_BSRR_BR9; // Reset PC9 bit } else { GPIOC->BSRR = GPIO_BSRR_BS9; // Set PC9 bit GPIOC->BSRR = GPIO_BSRR_BR8; // Reset PC8 bit } }

Под Windows, прошить полученный hex(/workspace/stm32_md_vl/Debug/stm32_md_vl.hex) можно утилитой от ST.

Ну а под linux утилитой st-flash. НО!!! Утилита не хавает hex формата Intel HEX (который генерируется по дефолту), поэтому крайне важно в настройках создания Flash-образа, выбрать формат binary:

Расширение файла при этом не поменяется (останется hex как и было), но формат файла изменится. И только после этого можно выполнять:

St-flash write v1 /home/user/workspace/stm32_md_vl/Debug/stm32_md_vl.hex 0x08000000

Кстати, на счет расширения и формата: обычно бинарные файлы помечают расширением.bin, в то время как файлы формата Intel HEX именуют расширением.hex. Отличие в этих двух форматах скорей техническое, чем функциональное: бинарный формат содержит просто байты инструкций и данных, которые будут просто записываться в контроллер программатором "как есть". IntelHEX же имеет не бинарный формат, а текстовый: точно те же байты разбиты по 4 бита и представлены посимвольно в формате ASCII, причем использованы только символы 0-9, A-F (bin и hex - системы счисления с кратными основаниями, то есть 4 бита в bin можно представить одной цифрой в hex). Так что формат ihex более чем в 2 раза превышает размер обычного бинарного файла (каждые 4 бита заменяются байтом + переносы строк для удобного чтения), но его можно читать в обычном текстовом редакторе. Поэтому, если вы собираетесь отправить этот файл кому-то, или использовать его в других программах-программаторах, то желательно переименовать его в stm32_md_vl.bin, дабы не вводить в заблуждение тех, кто будет смотреть на его имя.

Итак мы настроили сборку прошивки для stm32. В следующий раз я расскажу как

Итак, на ноги мы уже встали, в смысле на выводы микроконтроллера на плате STM32VL Discovery у нас подключено все что надо, говорить мы научились, на языке программирования Си, пора бы и в первый класс проект создать.

Написание программы

Закончив с созданием и настройкой проекта, можно приступить к написанию реальной программы. Как повелось у всех программистов, первой программой написанной для работы на компьютере, является программа, выводящая на экран надпись «HelloWorld», так и у всех микроконтроллерщиков первая программа для микроконтроллера производит мигание светодиода. Мы не будем исключением из этой традиции и напишем программу, которая будет управлять светодиодом LD3 на плате STM32VL Discovery.

После создания пустого проекта в IAR, он создает минимальный код программы:

Теперь наша программа будет всегда «крутиться» в цикле while .

Для того, чтобы мы могли управлять светодиодом, нам необходимо разрешить тактирование порта к которому он подключен и настроить соответствующий вывод порта микроконтроллера на выход. Как мы уже рассматривали ранее в первой части, за разрешение тактирования порта С отвечает битIOPCEN регистра RCC_APB2ENR . Согласно документу «RM0041 Reference manual .pdf » для разрешения тактирования шины порта С необходимо в регистре RCC_APB2ENR установить бит IOPCEN в единицу. Чтобы при установке данного бита, мы не сбросили другие, установленные в данном регистре, нам необходимо к текущему состоянию регистра применить операцию логического сложения (логического «ИЛИ») и после этого записать полученное значение в содержимое регистра. В соответствии со структурой библиотеки ST, обращение к значению регистра для его чтения и записи производится через указатель на структуру RCC -> APB 2 ENR . Таким образом, вспоминая материал со второй части, можно записать следующий код, выполняющий установку бита IOPCEN в регистре RCC_APB2ENR :

Как можно убедиться, из файла «stm32f10x.h», значение бита IOPCEN определено как 0x00000010, что соответствует четвертому биту (IOPCEN ) регистра APB2ENR и совпадает со значением указанным в даташите.

Теперь аналогичным образом настроим вывод 9 порта С . Для этого нам необходимо настроить данный вывод порта на выход в режиме push-pull. За настройку режима порта на вход/выход отвечает регистр GPIOC_CRH , мы его уже рассматривали в , его описание также находится в разделе «7.2.2 Port configuration register high» даташита. Для настройки вывода в режим выхода с максимальным быстродействием 2МГц, необходимо в регистре GPIOC_CRH установить MODE9 в единицу и сбросить бит MODE9 в нуль. За настройку режима работы вывода в качестве основной функции с выходом push-pull отвечают биты CNF 9 иCNF 9 , для настройки требуемого нам режима работы, оба эти бита должны быть сброшены в ноль.

Теперь вывод порта, к которому подключен светодиод, настроен на выход, для управления светодиодом нам необходимо изменить состояние вывода порта, установив на выходе логическую единицу. Для изменения состояния вывода порта существует два способа, первый это запись непосредственно в регистр состояния порта измененного содержимого регистра порта, так же как мы производили настройку порта. Данный способ не рекомендуется использовать в виду возможности возникновения ситуации, при которой в регистр порта может записаться не верное значение. Данная ситуация может возникнуть если во время изменения состояния регистра, с момента времени когда уже было произведено чтение состояния регистра и до момента когда произведется запись измененного состояния в регистр, какое либо периферийное устройство или прерывание произведет изменение состояния данного порта. По завершению операции по изменению состояния регистра произойдет запись значения в регистр без учета произошедших изменений. Хотя вероятность возникновения данной ситуации является очень низкой, все же стоит пользоваться другим способом, при котором описанная ситуация исключена. Для этого в микроконтроллере существует два регистра GPIOx_BSRR и GPIOx_BRR . При записи логической единицы в требуемый бит регистра GPIOx_BRR произойдет сброс соответствующего вывода порта в логический ноль. Регистр GPIOx_BSRR может производить как установку, так и сброс состояния выводов порта, для установки вывода порта в логическую единицу необходимо произвести установку битов BSn , соответствующих номеру необходимого бита, данные биты располагаются в младших регистрах байта. Для сброса состояния вывода порта в логический нуль, необходимо произвести запись битов BRn соответствующих выводов, эти биты располагаются в старших разрядах регистра порта.

Светодиод LD3 подключен к выводу 9 порта С . Для включения данного светодиода, нам необходимо подать на соответствующем выводе порта логическую единицу, чтобы «зажечь» светодиод.

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

//Не забываем подключить заголовочный файл с описанием регистров микроконтроллера

#include "stm32f10x.h"

void Delay (void );

void Delay (void )
{
unsigned long i;
for (i=0; i<2000000; i++);
}

//Наша главная функция

void main(void )
{


RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;

//очистим разряды MODE9 (сбросить биты MODE9_1 и MODE9_0 в нуль)
GPIOC->CRH &= ~GPIO_CRH_MODE9;

//Выставим бит MODE9_1, для настройки вывода на выход с быстродействием 2MHz
GPIOC->CRH |= GPIO_CRH_MODE9_1;

//очистим разряды CNF (настроить как выход общего назначения, симметричный (push-pull))
GPIOC->CRH &= ~GPIO_CRH_CNF9;

while (1)
{

//Установка вывода 9 порта С в логическую единицу («зажгли» светодиод)
GPIOC->BSRR = GPIO_BSRR_BS9;


Delay();


GPIOC->BSRR = GPIO_BSRR_BR9;


Delay();

}
}

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

Наша первая работоспособная программа написана, при её написании, для работы и настройки периферии, мы пользовались данными из официального даташита «RM0041 Reference manual .pdf », данный источник информации о регистрах микроконтроллера является самым точным, но для того чтобы им пользоваться приходится перечитывать много информации, что усложняет написание программ. Для облегчения процесса настройки периферии микроконтроллера, существуют различные генераторы кода, официальной утилитой от компании ST представлена программа Microxplorer , но она пока еще малофункциональна и по этой причине сторонними разработчиками была создана альтернативная программа «STM32 Генератор программного кода » . Данная программа позволяет легко получить код настройки периферии, используя удобный, наглядный графический интерфейс (см. рис. 2).


Рис. 2 Скриншот программы STM32 генератор кода

Как видно из рисунка 2, сгенерированный программой код настройки вывода светодиода совпадает с кодом написанным нами ранее.

Для запуска написанной программы, после выполнения компиляции исходного кода, необходимо загрузить нашу программу в микроконтроллер и посмотреть, как она выполняется.

Видео режима отладки программы мигания светодиодом

Видео работы программы мигания светодиодом на плате STM32VL Discovery

Библиотечные функции работы с периферией

Для упрощения работы с настройкой регистров периферии микроконтроллера, компания ST разработала библиотеки, благодаря использованию которых, не требуется так досконально читать даташит, поскольку при использовании данных библиотек, работа по написанию программы станет более приближена к написанию программ высокого уровня, в виду того, что все низкоуровневые функции реализуются на уровне функций библиотеки. Однако не следует полностью отказываться от использования непосредственной работы с регистрами микроконтроллера, в виду того, что библиотечные функции требуют больше процессорного времени на свое исполнение, как следствие их использование в критичных по времени выполнения участках программы не оправдано. Но все же, в большинстве случаев, такие вещи как инициализация периферии, не критичны ко времени выполнения, и удобство использования библиотечных функций оказывается более предпочтительным.

Теперь напишем нашу программу с использованием библиотеки ST. В программе требуется произвести настройку портов ввода/вывода, для использования библиотечных функций настройки портов, необходимо произвести подключение заголовочного файла «stm32f10x_gpio.h » (см. табл. 1). Подключение данного файла можно произвести расскоментированием соответствующей строки в подключенном заголовочном конфигурационном файле «stm32f10x_conf.h ». В конце файла «stm32f10x_gpio.h » имеется список объявлений функций для работы с портами. Подробное описание всех имеющихся функций можно прочитать в файле «stm32f10x_stdperiph_lib_um.chm », краткое описание наиболее часто применяемых приведено в таблице 2.

Таблица 2.Описание основных функций настройки портов

Функция

Описание функции, передаваемых и возвращаемых параметров

GPIO_DeInit (
GPIO_TypeDef* GPIOx)

Производит установку значений регистров настройки порта GPIOx на значения по умолчанию

GPIO_Init (
GPIO_TypeDef* GPIOx,

Производит установку регистров настройки порта GPIOx в соответствии с указанными параметрами в структуре GPIO_InitStruct

GPIO_StructInit (
GPIO_InitTypeDef* GPIO_InitStruct)

Заполняет все поля структуры GPIO_InitStruct, значениями по умолчания

uint8_t GPIO_ReadInputDataBit(
GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin);

Чтение входного значения вывода GPIO_Pin порта GPIOx

uint16_t GPIO_ReadInputData (
GPIO_TypeDef* GPIOx)

Чтение входных значений всех выводов порта GPIOx

GPIO_SetBits(
GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin)

Установка выходного значения вывода GPIO_Pin порта GPIOx в логическую единицу

GPIO_ResetBits(
GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin)

Сброс выходного значения вывода GPIO_Pin порта GPIOx в логический ноль

GPIO_WriteBit(
GPIO_TypeDef* GPIOx,
uint16_t GPIO_Pin,
BitAction BitVal)

Запись значения BitVal в вывод GPIO_Pin порта GPIOx

GPIO_Write(
GPIO_TypeDef* GPIOx,
uint16_t PortVal)

Запись значения PortVal в порт GPIOx

Как видно из описания функций, в качестве параметров настроек порта и т.п., в функцию передают не множество различных отдельных параметров, а одну структуру. Структуры - это объединенные данные, у которых есть некоторая логическая взаимосвязь. В отличие от массивов, структуры могут содержать данные разных типов. Другими словами, структура представляет набор различных переменных с различными типами, объединенными в одну своеобразную переменную. Переменные, находящиеся в данной структуре называются полями структуры, а обращение к ним производится следующим образом, сперва пишется имя структуры, затем пишется точка и имя поля структуры (имя переменной в этой структуре).

Список переменных, включенных в структуры для функций работы с портами, описаны в том же файле несколько выше описания функций. Так, например, структура «GPIO_InitTypeDef » имеет следующую структуру:

typedef struct
{

uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */

GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */

GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */

}GPIO_InitTypeDef;

Первое поле данной структуры содержит переменную «GPIO _ Pin » типа unsigned short , в данную переменную необходимо записывать флаги номеров соответствующих выводов, для которых предполагается произвести необходимую настройку. Можно произвести настройку сразу несколько выводов, задав в качестве параметра несколько констант через оператор побитовое ИЛИ (см. ). Побитовое ИЛИ «соберёт» все единички из перечисленных констант, а сами константы являются маской, как раз предназначенной для такого использования. Макроопределения констант указаны в этом же файле ниже.

Второе поле структуры «GPIO_InitTypeDef » задает максимально возможную скорость работы выхода порта. Список возможных значений данного поля перечислен выше:

Описание возможных значений:

  • GPIO_Mode_AIN - аналоговый вход (англ. Analog INput);
  • GPIO_Mode_IN_FLOATING - вход без подтяжки, болтающийся (англ. Input float) в воздухе
  • GPIO_Mode_IPD - вход с подтяжкой к земле (англ. Input Pull-down)
  • GPIO_Mode_IPU - вход с подтяжкой к питанию (англ. Input Pull-up)
  • GPIO_Mode_Out_OD - выход с открытым стоком (англ. Output Open Drain)
  • GPIO_Mode_Out_PP - выход двумя состояниями (англ. Output Push-Pull - туда-сюда)
  • GPIO_Mode_AF_OD - выход с открытым стоком для альтернативных функций (англ. Alternate Function). Используется в случаях, когда выводом должна управлять периферия, прикрепленная к данному выводу порта (например, вывод Tx USART1 и т.п.)
  • GPIO_Mode_AF_PP - то же самое, но с двумя состояниями

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

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

//Объявляем структуру

/*
Прежде чем начать заполнение полей структуры, рекомендуется проинициализировать содержимое структуры данными по умолчанию, это делается в целях предотвращения записи неверных данных, если по какой либо причине не все поля структуры были заполнены.

Для передачи значений структуры в функцию необходимо перед именем структуры поставить символ &. Данный символ говорит компилятору, что необходимо передавать функции не сами значения, содержащиеся в структуре, а адрес в памяти, по которому располагаются данные значения. Это делается для того, чтобы уменьшить количество необходимых действий процессора по копированию содержимого структуры, а также позволяет экономить оперативную память. Таким образом, вместо передачи в функцию множества содержащихся в структуре байт, будет передан только один, содержащий адрес структуры.
*/

/* Запишем в поле GPIO_Pin структуры GPIO_Init_struct номер вывода порта, который мы будем настраивать далее */

GPIO_Init_struct.GPIO_Pin=GPIO_Pin_9;

/* Подобным образом заполним поле GPIO_Speed */

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

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

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

//Не забываем подключить заголовочный файл с описание регистров микроконтроллера

#include "stm32f10x.h"
#include "stm32f10x_conf.h"

//объявляем функцию программной задержки

void Delay (void );

//сама функция программной задержки

void Delay (void )
{
unsigned long i;
for (i=0; i<2000000; i++);
}

//Наша главная функция

void main(void )
{

//Разрешаем тактирование шины порта С
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

//Объявляем структуру для настройки порта
GPIO_InitTypeDef GPIO_Init_struct;

//Заполняем структуру начальными значениями
GPIO_StructInit(&GPIO_Init_struct);

/* Запишем в поле GPIO_Pin структуры GPIO_Init_struct номер вывода порта, который мы будем настраивать далее */
GPIO_Init_struct.GPIO_Pin = GPIO_Pin_9;

// Подобным образом заполним поля GPIO_Speed и GPIO_Mode
GPIO_Init_struct.GPIO_Speed= GPIO_Speed_2MHz;
GPIO_Init_struct.GPIO_Mode = GPIO_Mode_Out_PP;

//Передаем заполненную структуру, для выполнения действий по настройке регистров
GPIO_Init(GPIOC, &GPIO_Init_struct);

//Наш основной бесконечный цикл
while (1)
{
//Установка вывода 9 порта С в логическую единицу ("зажгли" светодиод)
GPIO_SetBits(GPIOC, GPIO_Pin_9);

//Добавляем программную задержку, чтобы светодиод светился некоторое время
Delay();

//Сброс состояния вывода 9 порта С в логический ноль
GPIO_ResetBits(GPIOC, GPIO_Pin_9);

//Добавляем снова программную задержку
Delay();
}
}

ссылке .

Из приведенного выше примера видно, что использование библиотечных функций работы с периферией позволяет приблизить написание программ для микроконтроллера к объектно-ориентированному программированию, а также снижает необходимость в частом обращению к даташиту для чтения описания регистров микроконтроллера, но использование библиотечных функций требует более высоких знаний языка программирования. В виду этого, для людей не особо близко знакомых с программированием, более простым вариантом написания программ будет являться способ написания программ без использования библиотечных функций, с прямым обращением к регистрам микроконтроллера. Для тех же, кто хорошо знает язык программирования, но плохо разбирается в микроконтроллерах, в частности STM32, использование библиотечных функций существенно упрощает процесс написания программ.

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

Обработчик прерывания

Микроконтроллеры имеют одну замечательную способность – останавливать выполнение основной программы по какому-то определенному событию, и переходить к выполнению специальной подпрограммы – обработчику прерывания . В качестве источников прерывания могут выступать как внешние события – прерывания по приему/передаче данных через какой либо интерфейс передачи данных, или изменение состояния вывода, так и внутренние – переполнение таймера и т.п.. Список возможных источников прерывания для микроконтроллеров серии STM32 приведен в даташите «RM0041 Reference manual » в разделе «8 Interrupts and events ».

Поскольку обработчик прерывания также является функцией, то и записываться она будет как обычная функция, но чтобы компилятор знал, что данная функция является обработчиком определенного прерывания, в качестве имени функции следует выбрать заранее определенные имена, на которые указаны перенаправления векторов прерывания. Список имен этих функций с кратким описанием находится в ассемблерном файле «startup_stm32f10x_md_vl.s ». На один обработчик прерывания может приходиться несколько источников вызывающих прерывания, например функция обработчик прерывания «USART1_IRQHandler » может быть вызвана в случае окончания приема и окончания передачи байта и т.д..

Для начала работы с прерываниями следует настроить и проинициализировать контроллер прерываний NVIC. В архитектуре Cortex M3 каждому прерыванию можно выставить свою группу приоритета для случаев, когда возникает несколько прерываний одновременно. Затем следует произвести настройку источника прерывания.

В поле NVIC_IRQChannel указывается, какое именно прерывание мы хотим настроить. Константа USART1_IRQn обозначает канал, отвечающий за прерывания, связанные с USART1. Она определена в файле «stm32f10x.h », там же определены другие подобные константы.

В следующих двух полях указывается приоритет прерываний (максимальные значения этих двух параметров определяются выбранной приоритетной группой). Последнее поле, собственно, включает использование прерывания.

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

Теперь в настройках модуля необходимо установить параметры, по которым данный модуль будет генерировать прерывание. Для начала следует произвести включение прерывания, это делается вызовом функции name _ITConfig() , которая находится заголовочном файле периферийного устройства.

//Разрешаем прерывания по окончанию передачи байта по USART1
USART_ITConfig(USART1, USART_IT_TXE, ENABLE);

//Разрешаем прерывания по окончанию приема байта по USART1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

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

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

Для выполнения различных, небольших, повторяющихся с точным периодом действий, в микроконтроллерах с ядром Cortex-M3 имеется специально предназначенный для этого системный таймер. В функции данного таймера входит только вызов прерывания через строго заданные интервалы времени. Как правило, в вызываемом этим таймером прерывании, размещают код для измерения продолжительности различных процессов. Объявление функции настройки таймера размещено в файле «core _ cm 3. h ». В качестве передаваемого функции аргумента указывается число тактов системной шины между интервалами вызова обработчика прерывания системного таймера.

SysTick_Config(clk);

Теперь разобравшись с прерываниями, перепишем нашу программу, используя в качестве времязадающего элемента системный таймер. Поскольку таймер «SysTick » является системным и им могут пользоваться различные функциональные блоки нашей программы, то разумным будет вынести функцию обработки прерывания от системного таймера в отдельный файл, из этой функции вызывать функции для каждого функционального блока по отдельности.

Пример файла «main.с» программы мигания светодиода с использованием прерывания:

//Подключаем заголовочный файл с описанием регистров микроконтроллера

#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "main.h"

unsigned int LED_timer;

//Функция, вызываемая из функции-обработчика прерываний системного таймера

void SysTick_Timer_main(void )
{
//Если переменная LED_timer еще не дошла до 0,
if (LED_timer)
{
//Проверяем ее значение, если оно больше 1500 включим светодиод
if (LED_timer>1500) GPIOC->BSRR= GPIO_BSRR_BS9;

//иначе если меньше или равно 1500 то выключим
else GPIOC->BSRR= GPIO_BSRR_BR9;

//Произведем декремент переменной LED_timer
LED_timer--;
}

//Ели же значение переменной дошло до нуля, зададим новое значение 2000
else LED_timer=2000;
}

//Наша главная функция

void main(void )
{

//Разрешаем тактирование шины порта С
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

//Объявляем структуру для настройки порта
GPIO_InitTypeDef GPIO_Init_struct;

//Заполняем структуру начальными значениями
GPIO_StructInit(&GPIO_Init_struct);

/* Запишем в поле GPIO_Pin структуры GPIO_Init_struct номер вывода порта, который мы будем настраивать далее */
GPIO_Init_struct.GPIO_Pin = GPIO_Pin_9;

// Подобным образом заполним поля GPIO_Speed и GPIO_Mode
GPIO_Init_struct.GPIO_Speed= GPIO_Speed_2MHz;
GPIO_Init_struct.GPIO_Mode = GPIO_Mode_Out_PP;

//Передаем заполненную структуру, для выполнения действий по настройке регистров
GPIO_Init(GPIOC, &GPIO_Init_struct);

//выбираем приоритетную группу для прерываний
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

//Настраиваем работу системного таймера с интервалом 1мс
SysTick_Config(24000000/1000);

//Наш основной бесконечный цикл
while (1)
{
//В этот раз тут пусто, все управление светодиодом происходит в прерываниях
}
}

Часть исходного кода в файле «stm32f10x_it.c»:


#include "main.h"

/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/

void SysTick_Handler(void )
{
SysTick_Timer_main();
}

Пример рабочего проекта программы мигания светодиода с использованием прерывания можно скачать по ссылке .

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

Надеюсь, я смог заинтересовать Вас заняться изучением микроконтроллеров и разработкой устройств на них, и мои труды будут Вам полезны и интересны.

Взаимодействие пользовательского кода с регистрами ядра и периферии микроконтроллеров STM32 может быть осуществлено двумя способами: с помощью стандартных библиотек или с помощью наборов сниппетов (программных подсказок). Выбор между ними зависит от объема собственной памяти контроллера, требуемого быстродействия, срока выполнения разработки. В статье анализируются особенности структуры, достоинства и недостатки наборов сниппетов для микроконтроллеров семейств STM32F1 и STM32L0 производства компании STMicroelectronics.

Одно из преимуществ использования микроконтроллеров STMicroelectronics – широкий спектр средств разработки: документации, отладочных плат, программного обеспечения.

Программное обеспечение для STM32 включает в себя собственное ПО производства компании STMicroelectronics, источники Open Source, коммерческое ПО.

ПО от STMicroelectronics обладает важными достоинствами. Во-первых, оно доступно для бесплатного скачивания. Во-вторых, программные библиотеки представлены в виде исходных кодов – пользователь сам может модифицировать код, учитывая незначительные ограничения, описанные в лицензионном соглашении.

Библиотеки STMicroelectronics соответствуют ANSI-C и могут быть разделены по уровню абстракции (рисунок 1):

  • CMSIS (Core Peripheral Access Layer) – уровень регистров ядра и периферии, ARM библиотека;
  • Hardware Abstraction Layer – низкоуровневые библиотеки: стандартные библиотеки периферии (standard peripheral library), наборы сниппетов (snippets);
  • Middleware – библиотеки среднего уровня: операционные системы реального времени (RTOS), файловые системы, USB, TCP/IP, Bluetooth, Display, ZigBee, Touch Sensing и другие;
  • Application Field – библиотеки прикладного уровня: аудио, управление двигателями, автомобильные и промышленные решения.

На рисунке 1 видно, что для взаимодействия с уровнем CMSIS компания STMicroelectronics предлагает использовать два основных инструмента – стандартные библиотеки и сниппеты.

Стандартная библиотека – это набор драйверов. Каждый драйвер предоставляет пользователю функции и определения для работы с конкретным периферийным блоком (SPI, USART, ADC и так далее). Напрямую пользователь с регистрами уровня CMSIS не взаимодействует.

Наборы сниппетов – это высокоэффективные программные примеры, использующие прямой доступ к регистрам CMSIS. Разработчики ПО могут использовать реализации функций из этих примеров в собственном коде.

Каждый из способов имеет достоинства и недостатки. Выбор между ними делается с учетом доступного объема FLASH и ОЗУ, требуемого быстродействия, срока выполнения разработки, опытности программистов и других обстоятельств.

Уровень CMSIS

Микроконтроллер – это сложная цифро-аналоговая микросхема, состоящая из процессорного ядра, памяти, периферийных блоков, цифровых шин и так далее. Взаимодействие с каждым блоком происходит с помощью регистров.

С точки зрения программистов, микроконтроллер представляет собой пространство памяти. В нем размещены не только ОЗУ, FLASH и EEPROM, но и программные регистры. Каждому аппаратному регистру соответствует ячейка памяти. Таким образом, чтобы записать данные в регистр или вычитать его значение, программисту необходимо обратиться к соответствующей ячейке адресного пространства.

Человек имеет некоторые особенности восприятия. Например, символьные названия воспринимаются им гораздо лучше, чем адреса ячеек памяти. Это особенно заметно, когда используется большое число ячеек. В микроконтроллерах ARM число регистров, а значит, и используемых ячеек, превышает тысячу. Чтобы упростить работу, необходимо произвести определение символьных указателей. Это определение выполнено на уровне CMSIS.

Например, чтобы установить состояние выводов порта А, нужно записать данные в регистр GPIOA_ODR. Это можно сделать двумя способами – воспользоваться указателем с адресом ячейки 0xEBFF FCFF со смещением 0x14 или применить указатель с символьным названием GPIOA и готовую структуру, определяющую смещение. Очевидно, что второй вариант гораздо проще для восприятия.

CMSIS выполняет и другие функции. Он реализован в виде следующей группы файлов:

  • startup_stm32l0xx.s содержит ассемблерный стартовый код Cortex-M0+ и таблицу векторов прерываний. После выполнения стартовой инициализации происходит передача управления сначала функции SystemInit() (ниже будут приведены пояснения), а затем – основной функции int main(void);
  • stm32l0xx.h содержит определения, необходимые для выполнения основных операций с битами и определение типа используемого микропроцессора;
  • system_stm32l0xx.c/.h. После начальной инициализации выполняется функция SystemInit(). Она производит первичную настройку системной периферии, таймингов блока RCC;
  • stm32l0yyxx.h – файлы реализации конкретных микроконтроллеров (например, stm32l051xx.h). Именно в них определяются символьные указатели, структуры данных, битовые константы и смещения.

Взаимодействие со CMSIS. Стандартные библиотеки и сниппеты

Число регистров для микроконтроллеров STM32 в большинстве моделей превышает тысячу. Если использовать прямое обращение к регистрам, пользовательский код станет нечитаемым и абсолютно непригодным для поддержки и модернизации. Эта проблема может быть решена при использовании стандартной библиотеки периферии (standard peripheral library).

Стандартная библиотека периферии – это набор низкоуровневых драйверов. Каждый драйвер предоставляет пользователю набор функций для работы с периферийным блоком. Таким образом пользователь использует функции, а не обращается напрямую к регистрам. При этом уровень CMSIS оказывается скрытым от программиста (рисунок 2а).

Рис. 2. Взаимодействие с CMSIS с помощью стандартной библиотеки (а) и сниппетов (б)

Например, взаимодействие с портами ввода/вывода в STM32L0 реализовано с помощью драйвера, выполненного в виде двух файлов: stm32l0xx_hal_gpio.h и stm32l0xx_hal_gpio.c. В stm32l0xx_hal_gpio.h даны основные определения типов и функций, а в stm32l0xx_hal_gpio.c представлена их реализация.

Такой подход имеет вполне очевидные достоинства (таблица 1):

  • Быстрота создания кода. Программисту не требуется изучать перечень регистров. Он сразу начинает работать на более высоком уровне. Например, для прямого взаимодействия с портом ввода/вывода в STM32L0 необходимо знать и уметь работать с одиннадцатью регистрами управления/состояния, большинство из которых имеют до 32 настраиваемых битов. При использовании библиотечного драйвера достаточно освоить восемь функций.
  • Простота и наглядность кода. Пользовательский код не забит названиями регистров, может быть прозрачным и легко читаемым, что важно при работе команды разработчиков.
  • Высокий уровень абстракции. При использовании стандартной библиотеки код оказывается достаточно платформо-независимым. Например, если сменить микроконтроллер STM32L0 на микроконтроллер STM32F0 , часть кода, работающего с портами ввода/вывода, вообще не придется менять.

Таблица 1. Сравнение способов реализации пользовательского кода

Параметр сравнения При использовании стандартной
библиотеки периферии
При использовании наборов сниппетов
Размер кода средний минимальный
Затраты ОЗУ средние минимальные
Быстродействие среднее максимальное
Читаемость кода отличная низкая
Уровень независимости от платформы средний низкий
Скорость создания программ высокая низкая

Наличие дополнительной оболочки в виде драйверов имеет и очевидные недостатки (таблица 1):

  • Увеличение объема кода программы. Реализованные в библиотечном коде функции требуют дополнительного места в памяти.
  • Повышенные затраты ОЗУ за счет увеличения числа локальных переменных и использования громоздких структур данных.
  • Снижение быстродействия за счет увеличения накладных расходов при вызове библиотечных функций.

Именно наличие этих недостатков приводило к тому, что пользователь зачастую был вынужден оптимизировать код – самостоятельно реализовывать функции взаимодействия с CMSIS, оптимизировать библиотечные функции, убирая все лишнее, копировать реализации библиотечных функций непосредственно в свой код, использовать __INLINE-директивы для увеличения скорости выполнения. В результате, тратилось дополнительное время на доработку кода.

Компания STMicroelectronics, идя навстречу разработчикам, выпустила сборники сниппетов STM32SnippetsF0 и STM32SnippetsL0 .

Сниппеты входят в пользовательский код (рисунок 2б).

Использование сниппетов предоставляет очевидные преимущества:

  • повышение эффективности и быстродействия кода;
  • уменьшение объема программы;
  • снижение объемов используемой ОЗУ и нагрузки на стек.

Впрочем, стоит отметить и недостатки:

  • уменьшение простоты и наглядности кода за счет «загрязнения» его названиями регистров и самостоятельной реализацией низкоуровневых функций;
  • исчезновение платформо-независимости.

Таким образом, выбор между стандартной библиотекой и сниппетами не является очевидным. В большинстве случаев стоит говорить не о конкуренции, а о взаимном их использовании. На начальных этапах для быстрого построения «красивого» кода, логично использовать стандартные драйвера. При необходимости оптимизации можно обратиться к готовым сниппетам, чтобы не тратить время на разработку собственных оптимальных функций.

Стандартные библиотеки драйверов и сниппетов STM32F0 и STM32L0 (таблица 2) доступны для свободного скачивания на сайте www.st.com.

Таблица 2. Низкоуровневые библиотеки для STM32F10 и STM32L0

Более тесное знакомство со сниппетами, как и с любым ПО, следует начинать с рассмотрения особенностей лицензионного соглашения.

Лицензионное соглашение

Любой ответственный программист перед использованием сторонних программных продуктов внимательно изучает лицензионное соглашение. Несмотря на то, что сборники сниппетов производства ST Microelectronics не требуют лицензирования и доступны для свободного скачивания, это не значит, что на их использование не накладываются ограничения.

Лицензионное соглашение входит в комплект всех свободно скачиваемых продуктов производства компании STMicroelectronics. После загрузки STM32SnippetsF0 и STM32SnippetsL0 в корневом каталоге легко обнаружить документ MCD-ST Liberty SW License Agreement V2.pdf, который знакомит пользователя с правилами использования данного ПО.

В папке Project содержатся подкаталоги с примерами для конкретных периферийных блоков, готовые проекты для ARM Keil и EWARM, а также файлы main.c.

Запуск и особенности использования наборов сниппетов STM32SnippetsF0 и STM32SnippetsL0

Особенностью данных наборов снипетов является их платформозависимость. Они предназначены для работы с конкретными платами. STM32SnippetsL0 использует платформу STM32L053 Discovery board, а STM32SnippetsF0 – плату STM32F072 Discovery board.

При использовании плат собственной разработки код и проекты должны быть изменены, об этом будет более подробно рассказано в последнем разделе.

Для запуска примера необходимо выполнить ряд шагов:

  • запустить готовый проект из директории с требуемым примером. Для простоты можно воспользоваться готовыми проектами для сред ARM Keil или EWARM, расположенными в папке MDK-ARM\ и EWARM\ соответственно;
  • включить питание отладочной платы STM32L053 Discovery/STM32F072 Discovery;
  • подключить питание отладочной платы к ПК с помощью USB-кабеля. Благодаря встроенному отладчику ST-Link/V2 дополнительного программатора не потребуется;
  • открыть, настроить и запустить проект;
    • Для ARM Keil:
      • открыть проект;
      • скомпилировать проект – Project → Rebuild all target files;
      • загрузить его в контроллер – Debug → Start/Stop Debug Session;
      • запустить программу в окне Debug → Run (F5).
    • Для EWARM:
      • открыть проект;
      • скомпилировать проект – Project → Rebuild all;
      • загрузить его в контроллер – Project → Debug;
      • запустить программу в окне Debug → Go(F5).
  • провести тестирование в соответствии с алгоритмом, описанном в main.c.

Для анализа программного кода рассмотрим конкретный пример из STM32SnippetsL0: Projects\LPUART\01_WakeUpFromLPM\.

Запуск примера для LPUART

Отличительной особенностью новых микроконтроллеров семейства STM32L0 на ядре Cortex-M0+ является возможность динамического изменения потребления за счет большого числа нововведений. Одним из таких новшеств стало появление Low Power-периферии: 16-битного таймера LPTIM и приемопередатчика LPUART. Эти блоки обладают способностью тактирования, не зависящего от тактирования основной периферийной шины APB. При необходимости снижения потребляемой мощности рабочая частота шины APB (PCLK) может быть уменьшена, а сам контроллер переведен в режим пониженного потребления. При этом Low Power-периферия продолжает работу с максимальной производительностью.

Рассмотрим пример из директории Projects\LPUART\01_WakeUpFromLPM\, в котором рассматривается возможность независимой работы LPUART в режиме пониженного потребления.

При открытии проекта в среде ARM Keil отображаются всего три файла: startup_stm32l053xx.s, system_stm32l0xx.c и main.c (рисунок 4). В случае применения стандартной библиотеки в проект было бы необходимо добавить файлы драйверов.

Функционирование и анализ структуры файла Main.c

Программа из выбранного примера выполняется в несколько этапов.

После старта запускается функция SystemInit(), реализованная в system_stm32l0xx.c. Она проводит настройку параметров блока тактирования RCC (тайминги и рабочие частоты). Далее осуществляется передача управления в основную функцию int main(void). В ней инициализируется пользовательская периферия – порты вводы/вывода, LPUART – после чего контроллер переводится в режим пониженного потребления STOP. В нем обычная периферия и ядро остановлены, работает только LPUART. Он ждет начала передачи данных от внешнего устройства. При приходе стартового бита LPUART пробуждает систему и принимает сообщение. Прием сопровождается мерцанием светодиода отладочной платы. После этого контроллер вновь переводится в состояние STOP и ждет следующей передачи данных, если не было обнаружено ошибок.

Передача данных происходит при помощи виртуального COM-порта и дополнительного ПО.

Рассмотрим main.c из нашего проекта. Этот файл представляет собой стандартный С-файл. Главной его особенностью является самодокументация – наличие подробных комментариев, пояснений и рекомендаций. Пояснительная часть содержит несколько разделов:

  • заголовок с указанием названия файла, версии, даты, автора, краткого пояснения назначения;
  • описание последовательности настройки системной периферии (RCC specific features): FLASH, ОЗУ, системы питания и тактирования, периферийных шин и так далее;
  • перечень используемых ресурсов микроконтроллера (MCU Resources);
  • краткое пояснение по использованию данного примера (How to use this example);
  • краткое пояснение по тестированию примера и алгоритм его проведения (How to test this example).

Функция int main(void) имеет компактную форму и снабжена комментариями, которые в листинге 1, для большей наглядности, переведены на русский.

Листинг 1. Пример реализация функции main

int main(void)
{
/* К началу выполнения этой части когда уже произведена конфигурация системных блоков в функции SystemInit(), реализованной в system_stm32l0xx.c. */
/* конфигурация периферийных блоков*/
Configure_GPIO_LED();
Configure_GPIO_LPUART();
Configure_LPUART();
Configure_LPM_Stop();
/* проверка наличия ошибок при приеме */
while (!error) /* бесконечный цикл */
{
/* ожидание готовности LPUART и переход в режим STOP */
if((LPUART1->ISR & USART_ISR_REACK) == USART_ISR_REACK)
{
__WFI();
}
}
/* при возникновении ошибки */
SysTick_Config(2000); /* установка периода прерываний системного таймера 1 мс */
while(1);
}

В файле main.c объявлены и определены функции конфигурации периферии и две функции обработки прерываний. Рассмотрим их особенности.

В приведенном примере используются четыре функции конфигурации (листинг 2). Все они не имеют аргументов и не возвращают значений. Их главное предназначение – быстро и с наименьшими затратами занимаемого кода произвести инициализацию периферии. Это реализуется за счет двух особенностей: применения прямого обращения к регистрам и использования директивы __INLINE (листинг 3).

Листинг 2. Объявление функций конфигурации периферии

void Configure_GPIO_LED(void);
void Configure_GPIO_LPUART(void);
void Configure_LPUART(void);
void Configure_LPM_Stop(void);

Листинг 3. Пример реализации __INLINE-функции с прямым доступом к регистрам LPUART

INLINE void Configure_LPUART(void)
{
/* (1) Enable power interface clock */
/* (2) Disable back up protection register to allow the access to the RTC clock domain */
/* (3) LSE on */
/* (4) Wait LSE ready */
/* (5) Enable back up protection register to allow the access to the RTC clock domain */
/* (6) LSE mapped on LPUART */
/* (7) Enable the peripheral clock LPUART */
/* Configure LPUART */
/* (8) oversampling by 16, 9600 baud */
/* (9) 8 data bit, 1 start bit, 1 stop bit, no parity, reception mode, stop mode */
/* (10) Set priority for LPUART1_IRQn */
/* (11) Enable LPUART1_IRQn */
RCC->APB1ENR |= (RCC_APB1ENR_PWREN); /* (1) */
PWR->CR |= PWR_CR_DBP; /* (2) */
RCC->CSR |= RCC_CSR_LSEON; /* (3) */
while ((RCC->CSR & (RCC_CSR_LSERDY)) != (RCC_CSR_LSERDY)) /*(4)*/
{
/* add time out here for a robust application */
}
PWR->CR &=~ PWR_CR_DBP; /* (5) */
RCC->CCIPR |= RCC_CCIPR_LPUART1SEL; /* (6) */
RCC->APB1ENR |= RCC_APB1ENR_LPUART1EN; /*(7) */
LPUART1->BRR = 0x369; /* (8) */
LPUART1->CR1 = USART_CR1_UESM | USART_CR1_RXNEIE | USART_CR1_RE | USART_CR1_UE; /* (9) */
NVIC_SetPriority(LPUART1_IRQn, 0); /* (10) */
NVIC_EnableIRQ(LPUART1_IRQn); /* (11) */
}

Обработчики прерываний от системного таймера и от LPUART также используют прямое обращение к регистрам.

Таим образом, общение с CMSIS производится без стандартной библиотеки. Код оказывается компактным и высокоэффективным. Однако его читаемость значительно ухудшатся из-за обилия обращений к регистрам.

Использование сниппетов в собственных разработках

Предложенные наборы сниппетов имеют ограничения: необходимо использовать отладочную плату STM32L053 Discovery board для STM32SnippetsL0 , а плату STM32F072 Discovery board – для STM32SnippetsF0 .

Для применения сниппетов в своих разработках потребуется произвести ряд изменений. Во-первых, необходимо переконфигурировать проект под нужный процессор. Для этого в нем нужно сменить стартовый файл startup_stm32l053xx.s на файл другого контроллера и определить нужную константу: STM32L051xx, STM32L052xx, STM32L053xx, STM32L062xx, STM32L063xx, STM32L061xx, STM32F030, STM32F031, STM32F051 и другие. После этого при компиляции stm32l0xx.h, будет автоматически подключен нужный файл с определением периферии контроллера stm32l0yyxx.h (stm32l051xx.h/stm32l052xx.h/stm32l053xx.h/stm32l061xx.h/stm32l062xx.h/stm32l063). Во-вторых, нужно выбрать соответствующий программатор в настройках свойств проекта. Во-третьих – изменить код функций из примеров, если они не отвечают требованиям пользовательского приложения.

Заключение

Наборы сниппетов и стандартные библиотеки периферии производства компании ST Microelectronics не являются взаимоисключающими. Они дополняют друг друга, добавляя гибкость при создании приложений.

Стандартная библиотека дает возможность быстрого создания ясного кода с высоким уровнем абстракции.

Сниппеты позволяют повысить эффективность кода – увеличить производительность и сократить объем занимаемой памяти FLASH и ОЗУ.

Литература

  1. Data brief. STM32SnippetsF0. STM32F0xx Snippets firmware package. Rev. 1. – ST Microelectronics, 2014.
  2. Data brief. STM32SnippetsL0. STM32F0xx Snippets firmware package. Rev. 1. – ST Microelectronics, 2014.
  3. MCD-ST Liberty SW License Agreement V2.pdfElectromechanical Relays. Technical Information. – ST Microelectronics, 2011.
  4. Data brief. 32L0538DISCOVERY Discovery kit for STM32L053 microcontrollers. Rev. 1. – ST Microelectronics, 2014.
  5. http://www.st.com/.
О компании ST Microelectronics

Я указывал, что к системе подключается стандартная библиотека. На самом деле, подключается CMSIS - система обобщенного структурного представления МК, а также SPL - стандартная библиотека периферии. Рассмотрим каждую из них:

CMSIS
Представляет собой набор заголовочных файлов и небольшого набора кода для унификации и структурировании работы с ядром и периферией МК. По сути, без этих файлов невозможно нормально работать с МК. Получить библиотеку можно на странице к МК.
Эта библиотека если верить описанию создавалась для унификации интерфейсов пр работе с любым МК семейства Cortex. Однако, на деле выходит, что это справедливо только для одного производителя, т.е. перейдя на МК другой фирмы вы вынуждены изучать его периферию почти с нуля.
Хотя те файлы которые касаются процессорного ядра МК у всех производителей идентичны (хотя бы потому, что модель процессорного ядра у них одна - предоставленная в виде ip-блоков компанией ARM).
Поэтому работа с такими частями ядра как регистры, инструкции, прерывания и сопроцессорные блоки стандартна для всех.
Что касается периферии то у STM32 и STM8 (внезапно) она почти похожа, также частично это справедливо и для других МК выпущенных компанией ST. В практической части, я покажу насколько просто использовать CMSIS. Однако трудности в его использовании связаны с нежеланием людей читать документацию и разбираться в устройстве МК.

SPL
Standard Peripheral Library - стандартная библиотека периферии. Как следует из названия, назначение этой библиотеки - создание абстракции для периферии МК. Библиотека состоит из заголовочных файлов где объявлены человеко-понятные константы для конфигурирования и работы с периферией МК, а также файлы исходного кода собираемые собственно в саму библиотеку для операций с периферией.
SPL является абстракцией над CMSIS представляя пользователю общий интерфейс для всех МК не только одного производителя, но и вообще всех МК с процессорным ядром Cortex-Mxx.
Считается, что она более удобна новичкам, т.к. позволяет не думать как работает периферия, однако качество кода, универсальность подхода и скованность интерфейсов накладывают на разработчика определенные ограничения.
Также функционал библиотеки не всегда позволяет точно реализовать настройку некоторых компонентов таких как USART (универсальный синхронный-асинхронный последовательный порт) в определённых условиях. В практической части, я также опишу работу с этой частью библиотеки.

До этого момента мы использовали стандартную библиотеку ядра - CMSIS. Для настройки какого-либо порта на нужный режим работы нам приходилось обращаться к , чтобы найти отвечающий за определенную функцию регистр, а также искать по большому документу другую связанную с этим процессом информацию. Дело примет еще большие мучительные и рутинные обороты, когда мы приступим к работе с таймером или АЦП. Количество регистров там значительно больше, чем у портов ввода-вывода. Ручная настройка отнимает немало времени и повышает шанс допустить ошибку. Поэтому многие предпочитают работать со стандартной библиотекой периферии - StdPeriph. Что же она дает? Всё просто - повышается уровень абстракции, вам не нужно лезть в документацию и думать о регистрах в большинстве своеём. В этой библиотеке все режимы работы и параметры периферии МК описаны в виде структур. Теперь для настройки периферийного устройства необходимо лишь вызвать функцию инициализации устройства с заполненной структурой.

Ниже приведена картинка со схематичным изображением уровней абстракции.

Мы работали с CMSIS (которая находится «ближе» всего к ядру), чтобы показать, как устроен микроконтроллер. Следующим шагом является стандартная библиотека, пользоваться которой мы научимся сейчас. Дальше идут драйвера устройств. Под ними понимаются *.c \ *.h -файлы, которые обеспечивают удобный программный интерфейс для управления каким-либо устройством. Так, например, в этом курсе мы предоставим вам драйверы для микросхемы max7219 и WiFi-модуля esp8266.

Стандартный проект будет включать в себя следующие файлы:


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

Файлы библиотеки можно найти на странице, посвященной целевому МК (для нас это stm32f10x4), в разделе Design Resources (в среде CooCox IDE эти файлы скачиваются из репозитория среды разработки). Каждой периферии соответствуют два файла - заголовочный (*.h) и исходного кода (*.c). Детальное описание можно найти в файле поддержки, который лежит в архиве с библиотекой на сайте.

  • stm32f10x_conf.h - файл конфигурации библиотеки. Пользователь может подключить или отключить модули.
  • stm32f10x_ppp.h - заголовочный файл периферии. Вместо ppp может быть gpio или adc.
  • stm32f10x_ppp.c - драйвер периферийного устройства, написанный на языке Си.
  • stm32f10x_it.h - заголовочный файл, включающий в себя все возможные обработчики прерываний (их прототипы).
  • stm32f10x_it.c - шаблонный файл исходного кода, содержащий сервисные рутинные прерывания (англ. interrupt service routine , ISR) для исключительных ситуаций в Cortex M3. Пользователь может добавить свои ISR для используемой периферии.

В стандартной библиотекии периферии есть соглашение в наименовании функций и обозначений.

  • PPP - акроним для периферии, например, ADC.
  • Системные, заголовочные файлы и файлы исходного кода - начинаются с stm32f10x_ .
  • Константы, используемые в одном файле, определены в этом файле. Константы, используемые в более чем одном файле, определены в заголовочных файлах. Все константы в библиотеке периферии чаще всего написаны в ВЕРХНЕМ регистре.
  • Регистры рассматриваются как константы и именуются также БОЛЬШИМИ буквами.
  • Имена функций, относящихся к периферии, содержат акроним, например, USART_SendData() .
  • Для настройки каждого периферийного устройства используется структура PPP_InitTypeDef , которая передается в функцию PPP_Init() .
  • Для деинициализации (установки значения по умолчанию) можно использовать функцию PPP_DeInit() .
  • Функция, позволяющая включить или отключить периферию, именуется PPP_Cmd() .
  • Функция включения/отключения прерывания именуется PPP_ITConfig .

С полным списком вы опять же можете ознакомиться в файле поддержки библиотеки. А теперь давайте перепишем мигание светодиода с использованием стандартной библиотеки периферии!

Перед началом работы заглянем в файл stm32f10x.h и найдем строчку:

#define USE_STDPERIPH_DRIVER

Если вы будете настраивать проект с нуля, используя файлы библиотеки из скачанного архива, то вам будет необходимо раскомментировать данную строчку. Она позволит использовать стандартную библиотеку. Данное определение (макрос) скомандует препроцессору подключить файл stm32f10x_conf.h:

#ifdef USE_STDPERIPH_DRIVER #include "stm32f10x_conf.h" #endif

В этом файле подключаются модули. Если вам нужны только конкретные - отключите остальные, это сэкономит время при компиляции. Нам, как вы уже могли догадаться, нужны модули RTC и GPIO (однако в будущем потребуются также _bkp.h , _flash , _pwr.h , _rtc.h , _spi.h , _tim.h , _usart.h):

#include "stm32f10x_flash.h" // for init_pll() #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h"

Как и в прошлый раз, для начала нужно включить тактирование порта B. Делается это функцией, объявленной в stm32f10x_rcc.h:

Void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);

Перечисление FunctionalState определено в stm32f10x.h:

Typedef enum {DISABLE = 0, ENABLE = !DISABLE} FunctionalState;

Объявим структуру для настройки нашей ножки (найти её можно в файле stm32f10x_gpio.h):

GPIO_InitTypeDef LED;

Теперь нам предстоит её заполнить. Давайте посмотрим на содержание этой структуры:

Typedef struct { uint16_t GPIO_Pin; GPIOSpeed_TypeDef GPIO_Speed; GPIOMode_TypeDef GPIO_Mode; } GPIO_InitTypeDef;

Все необходимые перечисления и константы можно найти в этом же файле. Тогда переписанная функция init_leds() примет следующий вид:

Void led_init() { // Включаем тактирование RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // Объявляем структуру и заполняем её GPIO_InitTypeDef LED; LED.GPIO_Pin = GPIO_Pin_0; LED.GPIO_Speed = GPIO_Speed_2MHz; LED.GPIO_Mode = GPIO_Mode_Out_PP; // Инициализируем порт GPIO_Init(GPIOB, &LED); }

Перепишем функцию main() :

Int main(void) { led_init(); while (1) { GPIO_SetBits(GPIOB, GPIO_Pin_0); delay(10000000); GPIO_ResetBits(GPIOB, GPIO_Pin_0); delay(10000000); } }

Главное - прочувствовать порядок инициализации: включаем тактирование периферии, объявляем структуру, заполняем структуру, вызываем метод инициализации. Другие периферические устройства обычно настраиваются по подобной схеме.



© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows