Как настроить смартфоны и ПК. Информационный портал

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

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

1. Устанавливаем IAR

Сборка проекта в IAR

1. Препроцессор

  1. удаляет комментарии

2. Компилятор

3. Линкер

3. Создаём новый проект в IAR

После запуска IAR появляется окно информационного центра, который нам не нужен. Нажимаем меню Project –> Create New Project. Выбираем toolchain: ARM (вряд ли у вас будет что–то ещё в том списке), Project templates: C –> main.

В левом окне («Workspace») правой кнопкой мыши вызываем меню и создаём новую группу (Add –>

Щёлкаем правой кнопкой по CMSIS

В группу Startup

С CMSIS закончили.

В группу StdPeriphLib

В группу User

5. Настраиваем проект

  1. General options –> Target –>
Выбираем ST –> STM32F100 –> ST STM32F100xB. Это наш контроллер. 2. General options –> Library Configuration –> CMSIS: ставим галочку Use CMSIS. Так мы будем использовать встроенную в компилятор библиотеку CMSIS. С версии 6.30 IAR стал поставляться со встроенной CMSIS, и это вроде как лучше - но внесло некоторую неразбериху со старыми проектами. 3. C/C++ compiler –>
$PROJ_DIR$\

* Debugger –> Setup –> Driver: выбираем ST–Link, поскольку именно такой программатор встроен в плату Discovery. Теперь настраиваем сам программатор: * Debugger –> ST–LINK –> Interface: выбираем SWD (программатор на плате подключен к контроллеру по SWD, а не по JTAG). * Debugger –>
#include "stm32f10x_conf.h"

void main()
{
while(1)
{
}
}

<1000000; i++);


for(i=0; i<1000000; i++);

#include "stm32f10x_conf.h"

void main()
{





int i;
while(1)
{

for(i=0; i<1000000; i++);

<1000000; i++); } }

архив с проектом GPIO. К счастью, этот проект можно сохранить и дальше использовать его как шаблон, чтобы не проводить всю настройку заново. Весь цикл: 1. Порты ввода–вывода (/index.php/stm32-from_zero_to_rtos-2_timers/ "STM32 - с нуля до RTOS. 2: Таймер и прерывания") (/index.php/stm32-from_zero_to_rtos-3_timer_outputs/ "STM32 - с нуля до RTOS. 3: Выходы таймера") [Ещё раз хочу написать про простой старт с STM32, только на этот раз без использования чьих–то шаблонов или примеров - с объяснением каждого шага. В статьях будет сквозная нумерация шагов.

0. Добываем плату STM32VLDiscovery

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

1. Устанавливаем IAR

Работать будем в IAR - хорошая IDE с прекрасным компилятором. Ей недостаёт удобства написания кода - но для наших целей её вполне достаточно. Я использую IAR версии 6.50.3, взять - сами знаете где.

2. Скачиваем библиотеку периферии

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

Создаём папку «STM32_Projects», складываем туда папку Libraries из скачанного архива (stsw-stm32078.zip/an3268/stm32vldiscovery_package), в ней лежит CMSIS (библиотека от ARM для всех микроконтроллеров Cortex, описание и адреса всех ресурсов) и STM32F10x_StdPeriph_Driver - библиотека периферии от ST со всеми функциями.

Также создаём там папку «1. GPIO», в которой и будет лежать наш первый проект.

Дерево папок - на рисунке. Сделайте именно так, потому что потом будут очень важны относительные пути в этом дереве.

Ну и чтобы понимать, о чём идёт речь - скачайте 1100–страничный документ на эти контроллеры.

Сборка проекта в IAR

Необходимо чётко понимать суть процесса сборки проекта. Для удобства разобьём её на стадии.

1. Препроцессор

По всем.c–файлам проекта (и по main.c, и по всем файлам в workspace) проходит препроцессор. Он делает следующее:

  1. удаляет комментарии
  2. раскрывает директивы #include, подставляя вместо них содержимое указанного файла. Этот процесс проходит рекурсивно, начиная с.c–файла и заходя в каждый встреченный #include .h, и если в.h–файле тоже встретились директивы #include - препроцессор зайдёт и в них. Получается такое дерево инклудов. Обратите внимание: он не обрабатывает ситуацию двойного включения инклудов, т.е. один и тот же.h-файл может включиться несколько раз, если он указан в #include в нескольких местах проекта. Такую ситуацию нужно обрабатывать дефайнами.
  3. совершает макроподстановки - раскрывает макросы
  4. собирает директивы компилятора.

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

На этом этапе сборщик имеет все.c–файлы в проекте, готовые для компиляции - в виде файлов.i. Никаких связей между файлами пока нет.

2. Компилятор

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

Что делает компилятор, встречая в каком-нибудь.c–файле вызов функции, которая не описана в этом файле? Он ищет её в заголовках. Если заголовки говорят, что функция лежит в другом.c–файле - он просто оставляет на этом месте указатель на этот другой файл.

На этом этапе сборщик имеет все.c–файлы проекта, собранные в.o–файлы. Они называются скомпилированными модулями. Теперь между файлами есть связи в виде указателей в местах вызова «чужих» функций - но это до сих пор несколько разных файлов.

3. Линкер

Уже почти всё готово, нужно лишь проверить все связи между файлами - пройти по main.o и подставить на место указателей на чужие функции - скомпилированные модули. Если какая-то функция из библиотек не используется - она либо не будет вообще скомпилирована на предыдущем этапе, либо не будет никуда подставлена линкером (зависит от метода работы сборщика). В любом случае в готовый бинарный код она не попадёт.

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

Первый проект - работа с портами ввода–вывода

3. Создаём новый проект в IAR

После запуска IAR появляется окно информационного центра, который нам не нужен. Нажимаем меню Project –> Create New Project. Выбираем toolchain: ARM (вряд ли у вас будет что–то ещё в том списке), Project templates: C –> main.

Теперь у вас есть новый пустой проект на C и файл main.c.

4. Подключаем к проекту библиотеки

В левом окне («Workspace») правой кнопкой мыши вызываем меню и создаём новую группу (Add –> Add Group), назовём её CMSIS. Точно так же создадим группы StdPeriphLib, Startup и User. Теперь добавляем файлы в группы (я подчеркну все файлы, чтобы было удобнее следить).

Щёлкаем правой кнопкой по CMSIS , Add, Add files - заходим в Libraries/CMSIS/CM3, из папки DeviceSupport/ST/STM32F10x (поддержка кристалла) берём system_stm32f10x.c (это описание периферии конкретного кристалла и настройка тактирования). В папке CoreSupport (поддержка ядра) лежит ещё и core_cm3.c (это описание ядра Cortex M3), но мы не станем его брать - потому что он уже есть в компиляторе. Я напишу про это дальше.

В группу Startup складываем файл startup_stm32f10x_md_vl.s из папки Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/iar. Это - действия, которые нужно выполнить при старте. Практически полностью это настройка обработчиков различных прерываний (сами обработчики будут немного дальше). Там ещё есть файлы для других кристаллов, но нас интересует именно md_vl - это значит medium density (средний объём памяти, есть ещё кристаллы с маленьким и большим объёмом), value line (оценочная линия - кристалл STM32F100 предназначен лишь для оценки возможностей, и перехода на следующие семейства).

С CMSIS закончили.

В группу StdPeriphLib добавляем из папки Libraries/STM32F10x_StdPeriph_Driver/src файлы stm32f10x_rcc.c и stm32f10x_gpio.c . Первый - это функции работы с системой тактирования, а второй - работа с ножками ввода–вывода.

В группу User перетаскиваем наш main.c . Это необязательно, но так красивее.

Теперь дерево проекта GPIO выглядит так:

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

Осталось только положить в папку с проектом ещё один файл, подключающий заголовки ко всем файлам библиотеки периферии. Можно написать его самостоятельно, но проще взять готовый. Заходим в stsw-stm32078.zip/an3268/stm32vldiscovery_package/Project/Examples/GPIOToggle - там забираем файл stm32f10x_conf.h (конфигурация проекта) и кладём его в папку «1. GPIO». Это - единственный готовый файл, который мы берём.

stm32f10x_conf.h - просто свалка инклудов нужных модулей и функций assert. Эта функция будет вызываться при ошибках работы с функциями библиотеки периферии: например, засунуть в функцию GPIO_WriteBit вместо GPIOC какую–нибудь фигню - короче, ST замечательно перестраховалась. В этой функции можно просто запустить бесконечный цикл - while(1); Нам всё равно потребуется лезть в stm32f10x_conf.h - чтобы закомментировать строки включения файлов ненужной периферии, оставив только stm32f10x_rcc.h, stm32f10x_gpio.h и misc.h - поэтому можно было написать его и самостоятельно.

5. Настраиваем проект

Нажимаем правой кнопкой мыши в окне Workspace по названию проекта:

  1. General options –> Target –> Processor variant: выбираем «Device», нажимаем кнопку правее
Выбираем ST –> STM32F100 –> ST STM32F100xB. Это наш контроллер. 2. General options –> Library Configuration –> CMSIS: ставим галочку Use CMSIS. Так мы будем использовать встроенную в компилятор библиотеку CMSIS. С версии 6.30 IAR стал поставляться со встроенной CMSIS, и это вроде как лучше - но внесло некоторую неразбериху со старыми проектами. 3. C/C++ compiler –> Preprocessor. Здесь пишем пути до папок библиотеки:
$PROJ_DIR$\
$PROJ_DIR$\..\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x
$PROJ_DIR$\..\Libraries\STM32F10x_StdPeriph_Driver\inc
Макрос $PROJ_DIR$ означает текущую папку (папку проекта), а.. - переход на один уровень выше. Мы прописали пути до папки с описанием кристалла, а также до заголовочных файлов библиотеки периферии, поскольку все.c файлы в проекте подключают свои заголовки, и компилятор должен знать где их искать. Ещё здесь нужно написать USE\_STDPERIPH\_DRIVER в Defined symbols. Это подключит нужные файлы конфигурации (например, упомянутый stm32f10x_conf.h) к проекту. Таким образом, вкладка Preprocessor будет выглядеть так: * Debugger –> Setup –> Driver: выбираем ST–Link, поскольку именно такой программатор встроен в плату Discovery. Теперь настраиваем сам программатор: * Debugger –> ST–LINK –> Interface: выбираем SWD (программатор на плате подключен к контроллеру по SWD, а не по JTAG). * Debugger –> Download: ставим галочку Use flash loader(s), «Заливать прошивку во flash–память». Логично, без неё ничего не зальётся. ## 6. Пишем код А для начала напишу, что этот код будет делать. Он будет демонстрировать простую вещь, мигание светодиодом (PC8 на плате Discovery) с паузой в бесконечном цикле. Подключаем заголовочный файл конфигурации проекта, stm32f10x\_conf.h. В нём находим строчку #include «stm32f10x\_exti.h» - это 35 строка, и закомментируем её двумя слешами. Дело в том, что в нашем проекте не понадобится модуль EXTI. В файле main.c уже есть функция int main, а в ней единственное действие - return 0. Удаляем эту строчку (мы не собираемся возвращать никаких значений), меняем тип функции на void (по той же причине), и пишем бесконечный цикл:
#include "stm32f10x_conf.h"

void main()
{
while(1)
{
}
}

### Запускаем модуль GPIO Порты ввода–вывода в STM32 называются GPIO - General Purpose Input/Output. Поэтому мы и подключали библиотеку stm32f10x_gpio.c. Однако это не всё что нам нужно, немного теории: Вся периферия на кристалле по умолчанию отключена, и от питания и от тактовой частоты. Чтобы её включить, нужно подать сигнал тактирования. Этим заведует модуль RCC, а для работы с ним существует файл stm32f10x_rcc.c. Модуль GPIO висит на шине APB2. Есть ещё AHB (аналог шины процессор–северный мост) и APB1 (так же как и APB2 - аналог шины северный мост–южный мост). Поэтому первое, что нам нужно сделать - включить тактирование модуля GPIOC. Это модуль, отвечающий за PORTC; есть ещё GPIOA, GPIOB и так далее. Делается это так: RCC\_APB2PeriphClockCmd(RCC\_APB2Periph_GPIOC, ENABLE); Всё просто - вызываем функцию подачи тактового сигнала с шины APB2 на модуль GPIOC, и тем самым включаем этот модуль. Конечно, это делаем в самом начале функции void main. Здесь - только основы, нужные для понимания. У меня также есть гораздо более [подробная статья про модуль GPIO](/index.php/stm32-%e2%86%92-%d0%bf%d0%be%d1%80%d1%82%d1%8b-gpio/ "STM32 → Порты GPIO"). ### Настраиваем модуль GPIOC Осталось совсем немного, нужно сконфигурировать модуль GPIOC. Устанавливаем ножку на выход (ещё бывает вход и альтернативные функции), настраиваем резкость фронтов (в целях ЭМ совместимости), выходной драйвер (пуш–пулл или открытый исток). Это делаем сразу после инициализации порта. GPIO\_InitTypeDef GPIO\_InitStructure; GPIO\_InitStructure.GPIO\_Speed = GPIO\_Speed\_2MHz; GPIO\_InitStructure.GPIO\_Mode = GPIO\_Mode\_Out_PP; GPIO\_InitStructure.GPIO\_Pin = GPIO\_Pin\_8; GPIO\_Init(GPIOC, &GPIO\_InitStructure); Ну всё, после этого ножка PC8 будет работать как пуш–пулл выход с относительно плавными фронтами (максимальная частота переключения 2МГц. Острые фронты - это 50МГц). Плавность фронтов глазом не заметим, а на осциллографе это видно. ### Включаем светодиод Вызываем функцию GPIO\_WriteBit(GPIOC, GPIO\_Pin\_8, Bit\_SET); Светодиод включится. ### Включаем–выключаем его в цикле В цикле while(1) пишем код включения, паузы, выключения и снова паузы:

GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET); for(i=0; i<1000000; i++);

GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_RESET);
for(i=0; i<1000000; i++);

Таким образом, полностью весь файл main.c выглядит так:

#include "stm32f10x_conf.h"

void main()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOC, &GPIO_InitStructure);

GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET);

int i;
while(1)
{
GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET);
for(i=0; i<1000000; i++);

GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_RESET); for(i=0; i<1000000; i++); } }

## 7. Запускаем! Подключаем плату STM32VLDiscovery к компьютеру через microUSB, нажимаем в IAR кнопку Download and Debug. Программа заливается в микроконтроллер (можно заметить окошко с прогрессбаром, которое быстро закрывается - настолько мал размер программы), и начинается отладка. IAR останавливается на первой инструкции кода (это довольно удобно при отладке), нужно запустить его кнопкой Go. Всё должно работать - синий светодиод PC8 на плате STM32VLDiscovery должен Как всегда, вы можете скачать архив с проектом GPIO. К счастью, этот проект можно сохранить и дальше использовать его как шаблон, чтобы не проводить всю настройку заново. Весь цикл: 1. Порты ввода–вывода (/index.php/stm32-from_zero_to_rtos-2_timers/ "STM32 - с нуля до RTOS. 2: Таймер и прерывания") (/index.php/stm32-from_zero_to_rtos-3_timer_outputs/ "STM32 - с нуля до RTOS. 3: Выходы таймера")

](/index.php/stm32-from_zero_to_rtos-4_exti_nvic/ “STM32 - с нуля до RTOS. 4: Внешние прерывания и NVIC”) 5. Ставим FreeRTOS

Итак, на ноги мы уже встали, в смысле на выводы микроконтроллера на плате 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 можно пойти несколькими путями. Первый, классический, берём точное описание контроллера на сайте www.st.com , которое фигурирует под названием «Reference Manual» и читаем описание регистров периферии. Дальше пробуем записывать их и смотрим, как работает периферия. Чтение этого документа очень полезно, но на первом этапе освоения микроконтроллера от этого можно отказаться, как это не странно. Инженеры STMicroelectronics написали библиотеку драйверов стандартной периферии. Более того, они написали множество примеров использования данных драйверов, что может свести программирование вашего приложения к нажатию клавиш Ctrl+C и Ctrl+V, с последующей небольшой правкой примера использования драйвера под свои потребности. Таким образом, подключение библиотеки драйверов периферии к вашему проекту является вторым методом построения приложения. Кроме скорости написания есть и другие достоинства этого метода: универсальность кода и применение других фирменных библиотек, таких как USB, Ethernet, управление приводом и т.д., которые предоставляются в исходниках и использующих стандартный драйвер периферии. Недостатки данного метода тоже есть: Там где можно обойтись одной строкой кода стандартный драйвер периферии STM32 напишет 10. Сама библиотека периферии также предоставляется в виде исходных файлов, так что можно проследить какой бит какого регистра меняет та или иная функция. При желании можно будет перейти от второго метода написания программы к первому, закомментировав часть кода, использующего стандартную библиотеку на свою, управляющую непосредственно регистром периферии. В результате такого действия вы выиграете в скорости управления, объёме ОЗУ и ПЗУ, а потеряете в универсальности кода. В любом случае, инженеры компании "Промэлектроника" рекомендуют использовать библиотеку стандартной периферии хотя бы на первом этапе.

Наибольшие трудности ожидают разработчика при подключении библиотеки к своему проекту. Если не знать, как это делать, можно потратить много времени на это мероприятие, что противоречит самой идее использования готового драйвера. Материал посвящён подключению стандартной библиотеки к любому семейству STM32.

Каждое семейство STM32 имеет свою библиотеку стандартной периферии. Это связано с тем, что сама периферия разная. Например, периферия контроллеров STM32L в качестве одной из задач имеет функцию энергосбережения, что тянет за собой добавление функций управления. Классическим примером, можно считать АЦП, который в STM32L имеет возможность аппаратного отключения, при длительном отсутствии команды преобразования – одно из следствий задачи энергосбережения. АЦП контроллеров семейств STM32F не имеют такой функции. Собственно говоря, в силу наличия аппаратной разницы периферии имеем разные библиотеки драйверов. Кроме очевидного различия функций контроллера имеет место улучшение периферии. Так, периферия контроллеров семейств, которые были выпущены позже, может быть более продуманной и удобной. Например, периферия контроллеров STM32F1 и STM32F2 имеет различия по управлению. На взгляд автора управление периферией STM32F2 более удобное. И это понятно почему: STM32F2 семейство было выпущено позже и это позволило разработчикам учесть некоторые нюансы. Соответственно, для данных семейств – индивидуальные библиотеки управления периферией. Идея вышесказанного проста: на странице микроконтроллера, который вы собираетесь использовать, находится подходящая ему библиотека периферии.

Несмотря на различие периферии в семействах драйверы скрывают 90% различий внутри себя. Например, функция настройки, упомянутого выше АЦП, для всех семейств выглядит одинаково:

void ADC_Init(ADC_Nom, ADC_Param) ,

где ADC_Nom – номер АЦП в виде ADC1, ADC2, ADC3 и т. д.

ADC_Param – указатель структуры данных, каким образом надо настроить АЦП (от чего запускаться, сколько каналов оцифровывать, выполнять ли это циклически и т.д.)

10% различий семейств, в данном примере, которые придётся поправить при переходе с одного семейства STM32 на другое, спрятаны в структуре ADC_Param. В зависимости от семейства количество полей этой структуры может быть разным. Общая же часть, имеет одинаковый синтаксис. Таким образом, перевод приложения для одного семейства STM32, написанного на базе стандартных библиотек периферии на другое, весьма прост. В части универсализации решений на микроконтроллерах STMicroelectronics неотразим!

Итак, мы скачали библиотеку для применяемого STM32. Что дальше? Далее, нам надо создать проект и подключить к нему требуемые файлы. Создание проекта рассмотрим на примере среды разработки IAR Embedded Workbench. Запускаем среду разработки и заходим на вкладку “Project”, выбираем пункт создания проекта “Create project”:

В появившемся новом проекте вводим настройки, наведя курсор на название проекта, нажав правую клавишу мыши и выбрав в выпавшем меню «Options»:

Области памяти ОЗУ и ПЗУ:

При нажатии кнопки “Save” среда предложит записать новый файл описания контроллера в папку проекта. Автор рекомендует создавать каждому проекту индивидуальный файл *.icp и хранить его в папке с проектом.

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

На вкладке выбранного отладчика указываем интерфейс подключения отладчика (в нашем случае выбран ST-Link) к контроллеру:



С этого момента наш проект без библиотек готов к компиляции и загрузке в контроллер. В других средах, таких как Keil uVision4, Resonance Ride7 и т. п. потребуется выполнить эти же действия.

Если в файле main.c написать строку:

#include "stm32f10x.h" или

#include "stm32f2xx.h" или

#include "stm32f4xx.h" или

#include "stm32l15x.h" или

#include "stm32l10x.h" или

#include "stm32f05x.h"

с указанием расположения данного файла, либо копированием данного файла в папку проекта, то произойдёт ассоциация некоторых областей памяти с регистрами периферии соответствующего семейства. Сам файл находится в папке библиотеки стандартной периферии в разделе: \CMSIS\CM3\DeviceSupport\ST\STM32F10x (или похожее по названию для других семейств). С этого момента вы заменять адрес регистра периферии в виде числа его названием. Даже если вы не собираетесь использовать функции стандартной библиотеки, рекомендуется сделать такое подключение.

Если вы собираетесь использовать в своём проекте прерывания, то рекомендуется подключить стартовый файл с расширением *.s, который находится по пути \CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\iar, или подобном, для других семейств. Важно отметить, что для каждой среды файл свой. Соответственно, если мы используем IAR EWB, то должны взять файл из папки IAR. Это обусловлено небольшим различием синтаксиса сред. Поэтому, чтобы проект запустился сразу, инженеры STMicroelectronics написали несколько вариантов стартовых файлов для нескольких наиболее популярных сред разработки. Большинство семейств STM32 имеют один файл. Семейство STM32F1 имеет несколько запускающих фалов:

  • startup_stm32f10x_cl.s – для микроконтроллеров STM32F105/107
  • startup_stm32f10x_xl.s - для микроконтроллеров STM32F101/STM32F103 768кб и более
  • startup_stm32f10x_hd.s - для микроконтроллеров STM32F101/STM32F103 c памятью Flash 256-512 кб
  • startup_stm32f10x_md.s - для микроконтроллеров STM32F101/ STM32F102/STM32F103 c памятью Flash 64-128 кб
  • startup_stm32f10x_ld.s - для микроконтроллеров STM32F101/ STM32F102/STM32F103 c памятью Flash менее 64кб
  • startup_stm32f10x_hd_vl.s для микроконтроллеров STM32F100 c памятью Flash 256-512 кб
  • startup_stm32f10x_md_vl.s для микроконтроллеров STM32F100 c памятью Flash 64-128 кб
  • startup_stm32f10x_ld_vl.s для микроконтроллеров STM32F100 c памятью Flash 32кб и менее

Итак, в зависимости семейства, подсемейства и среды разработки добавляем запускающий файл в проект:

Именно здесь оказывается микроконтроллер при старте программы. Прерывание последовательно вызывает функцию SystemInit(), а затем __iar_program_start. Вторая функция обнуляет либо записывает заранее заданные значения глобальных переменных, после чего осуществляет переход в программу пользователя main(). Функция SystemInit() настраивает тактирование микроконтроллера. Именно она даёт ответы на вопросы:

  • Надо ли переключаться на внешний кварц (HSE)?
  • Как умножить частоту от HSI/HSE?
  • Требуется ли подключение очереди загрузки команд?
  • Какая требуется задержка при загрузке команды (из-за низкой скорости работы Flash памяти)
  • Как поделить тактирование шин периферии?
  • Требуется ли разместить код во внешней ОЗУ?

Функцию SystemInit() можно прописать вручную в своём проекте. Если оформить эту функцию как пустую, то контроллер будет работать на внутреннем RC-генераторе с частотой порядка 8МГц (в зависимости от типа семейства). Вариант 2 – подключить к проекту файл system_stm32f10x.c (либо похожий по названию в зависимости от типа используемого семейства), который расположен в библиотеке по пути: Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x. В этом файле имеется функция SystemInit(). Обратите внимание на частоту внешнего кварца HSE_VALUE. Данный параметр выставляется в заголовочном файле stm32f10x.h. Стандартное значение 8 и 25МГц, в зависимости от семейства STM32. Основная задача функции SystemInit() – переключить тактирование на внешний кварц и умножить определённым образом данную частоту. Что произойдёт, если значение HSE_VALUE указано 8МГц, ядро должно тактироваться частотой 72МГц, а по факту на плате стоит кварц 16МГц? В результате таких некорректных действий ядро получит тактирование 144МГц, которые могут оказаться за пределами гарантированной работы системы на STM32. Т.е. при подключении файла system_stm32f10x.c потребуется указать значение HSE_VALUE. Всё это означает, что файлы system_stm32f10x.c, system_stm32f10x.h и stm32f10x.h (или похожие по названию для других семейств) должны быть индивидуальными для каждого проекта. И

нженеры STMicroelectronics создали инструмент Clock Configuration Tool, который позволяет правильно настроить тактирование системы. Это файл Excel, генерирующий файл system_stm32xxx.c (похожий по названию для заданного семейства семейств), после задания входных и выходных параметров системы. Рассмотрим его работу на примере STM32F4 семейства.

Варианты: внутренний RC-генератор, внутренний RC-генератор с умножением частоты, либо внешний кварц с умножением частоты. После выбора источника тактирования вводим параметры желаемой конфигурации системы, такие как входную частоту (при использовании внешнего кварца), частоту тактирования ядра, делители частоты тактирования шин периферии, работу буфера выборки команд и другие. Нажав на кнопку “Generate”, получаем окно


Подключение файла system_stm32f4xx.c и его аналогов потребует подключение ещё одного файла стандартной библиотеки периферии. Для управления тактированием имеется целый набор функций, которые вызываются из файла system_stm32xxxxxx.c. Эти функции расположены в файле stm32f10x_rcc.c и его заголовке. Соответственно, подключая к проекту файл файла system_stm32xxxxxx.c требуется подключить stm32f10x_rcc.c, иначе линкер среды сообщит об отсутствии описания функций с именем RCC_xxxxxxx. Указанный файл находится в библиотеке периферии по пути: Libraries\STM32F10x_StdPeriph_Driver\src, а его заголовок \Libraries\STM32F10x_StdPeriph_Driver\inc.

Подключение заголовочных файлов драйвера периферии происходит в файле stm32f10x_conf.h, на который ссылается stm32f10x.h. Файл stm32f10x_conf.h – это просто набор заголовочных файлов драйверов конкретных периферий контроллера, подлежащих включению в проект. Изначально все заголовки «#include» отмечены как комментарии. Подключение заголовочного файла периферии заключается в снятии комментария с соответствующего названия файла. В нашем случае – это строка #include "stm32f10x_rcc.h". Очевидно, что файл stm32f10x_conf.h индивидуален для каждого проекта, т.к. разные проекты используют разную периферию.

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



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

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

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



Таким образом, подключение заголовочного файла stm32f10x.h в приложении тянет за собой подключение других заголовочных файлов и файлов кода. Некоторые из представленных на рисунке описаны выше. Несколько слов об остальных. Файлы STM32F10x_PPP.x – это файлы-драйверы периферии. Пример подключения такого файла показан выше, это RCC – периферия управления тактированием системы. Если мы хотим подключить драйверы другой периферии, то название подключаемых файлов получается заменой «PPP» названием периферии, например АЦП - STM32F10x_ADC.с, или порты ввода-вывода STM32F10x_GPIO.с, или ЦАП - STM32F10x_DAC.с. В целом интуитивно понятно какой файл нужно подключить при подключении заданной периферии. Файлы «misc.c», «misc.h» - это по большому счёту те же STM32F10x_PPP.x, только выполняют управление ядром. Например настройку векторов прерываний, который встроен в ядро или управление таймером SysTick, который является частью ядра. Файлы xxxxxxx_it.c описывают вектора немаскируемых прерываний контроллера. Они могут быть дополнены векторами прерываний периферии. Файл core_m3.h описывает ядро CortexM3. Данное ядро стандартизовано и может встречаться в микроконтроллерах других производителей. Для кросс-платформенной универсализации компания STMicroelectronics провела работу по созданию отдельной библиотеки ядра CortexM, после чего компания ARM стандартизировала её и распространила на других производителей микроконтроллеров. Так что переход на STM32 с контроллеров других производителей с ядром CortexM будет чуть проще.

Итак, мы можем подключить библиотеку стандартной периферии к любому семейству STM32. Того, кто научился это делать ждёт приз: очень простое программирование микроконтроллеров. Библиотека кроме драйверов в виде исходных файлов содержит множество примеров применения периферии. Для примера, рассмотрим создание проекта с участием выходов сравнения таймера. При традиционном подходе мы будем внимательно изучать описание регистров данной периферии. Но сейчас мы можем изучить текст работающей программы. Заходим в папку примеров стандартной периферии, которая находится по пути ProjectSTM32F10x_StdPeriph_Examples. Здесь находятся папки примеров с названием применяемой периферии. Заходим в папку «TIM». Таймеры в STM32 имеют множество функций и настроек, поэтому одним примером возможности контроллера продемонстрировать невозможно. Поэтому внутри указанного каталога имеется множество примеров применения таймеров. Нас интересует генерация ШИМ сигнала таймером. Заходим в папку «7PWM_Output». Внутри имеется описание работы программы на английском языке и набор файлов:

main.c stm32f10x_conf.h stm32f10x_it.h stm32f10x_it.c system_stm32f10x.c

Если проект не имеет прерываний, то содержательная часть полностью расположена в файле main.c. Копируем эти файлы в каталог проекта. Скомпилировав проект, мы получим программу для STM32, которая настроит таймер и порты ввода-вывода на генерацию 7-ми ШИМ сигналов от таймера 1. Далее, мы можем приспособить уже написанный код под свою задачу. Например, уменьшить число ШИМ сигналов, изменить скважность, направление счёта и т.п. Функции и их параметры хорошо описаны в файле stm32f10x_stdperiph_lib_um.chm. Названия же функций и их параметров легко ассоциируются с их назначением для тех, кто немного знает английский язык. Для наглядности приводим часть кода взятого примера:

/* Time Base configuration */ TIM_TimeBaseStructure.TIM_Prescaler = 0; // предделение счётных импульсов отсутствует (16-битный регистр) TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // направление счёта вверх TIM_TimeBaseStructure.TIM_Period = TimerPeriod; // счёт выполнять до значения TimerPeriod (константа в программе) TIM_TimeBaseStructure.TIM_ClockDivision = 0; // предделение счётных отсутствует TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; // счётчик переполнений для генерации событий (не используется в программе) TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); // ввод значений TimeBaseStructure в регистры таймера 1 (ввод данных в эту // переменную - выше) /* Channel 1, 2,3 and 4 Configuration in PWM mode */ // настройка ШИМ выходов TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; // режим работы ШИМ2 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // разрешитьвыход ШИМ сигналов таймера TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; // разрешить комплиментарный выходШИМтаймера TIM_OCInitStructure.TIM_Pulse = Channel1Pulse; // ширина импульс Channel1Pulse – константа в программе TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; // настройка полярности выхода TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; // настройка полярности комплиментарного выхода TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; // установка безопасного состояния выхода ШИМ TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset; // установка безопасного состояния комплиментарного выхода ШИМ TIM_OC1Init(TIM1, &TIM_OCInitStructure); // ввод значений переменной TIM_OCInitStructure в регистры ШИМ канала 1 // таймера1 TIM_OCInitStructure.TIM_Pulse = Channel2Pulse; // меняем ширину импульса в переменной OCInitStructure и вводим её в TIM_OC2Init(TIM1, &TIM_OCInitStructure); // регистры ШИМ канала 2 таймера1 TIM_OCInitStructure.TIM_Pulse = Channel3Pulse; // меняем ширину импульса в переменной OCInitStructure и вводим её в TIM_OC3Init(TIM1, &TIM_OCInitStructure); // регистры ШИМ канала 3 таймера1 TIM_OCInitStructure.TIM_Pulse = Channel4Pulse; // меняем ширину импульса в переменной OCInitStructure и вводим её в TIM_OC4Init(TIM1, &TIM_OCInitStructure); // регистры ШИМ канала 4 таймера1 /* TIM1 counter enable */ TIM_Cmd(TIM1, ENABLE); // запускаем таймер1 /* TIM1 Main Output Enable */ TIM_CtrlPWMOutputs(TIM1, ENABLE); // разрешаем работу выходов сравнения таймера 1

В правой части автор оставил комментарий на русском языке к каждой строке программы. Если открыть этот же пример в описании функций библиотек stm32f10x_stdperiph_lib_um.chm, то мы увидим, что все используемые параметры функций имеют ссылку на собственное описание, где будут указаны их возможные значения. Сами функции тоже имеют ссылку на собственное описание и исходный код. Это очень полезно, т.к. зная, что функция делает, мы можем проследить, каким образом она это делает, на какие биты регистров периферии и как она воздействует. Это, во-первых, ещё один источник информации для освоения контроллера, основанный на практическом использовании контроллера. Т.е. вы сначала решите техническую задачу, а потом изучите само решение. Во-вторых, это поле для оптимизации программы тому, кого библиотека не устроит по скорости работы и объёму кода.



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

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

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

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

Ниже представлен рисунок платы STM32F3 Discovery , где: 1 — MEMS датчик. 3-осевой цифровой гироскоп L3GD20. 2 — МЕМС система-в-корпусе, содержащая 3-осевой цифровой линейный акселерометр и 3-осевой цифровой геомагнитный сенсор LSM303DLHC. 4 – LD1 (PWR) – питание 3.3V. 5 – LD2 – красный/зеленый светодиод. По умолчанию красный. Зеленый означает связь между ST-LINK/v2 (or V2-B) и ПК. У меня ST-LINK/v2-B, а также индикации пользовательского порта USB. 6. -LD3/10 (red), LD4/9 (blue), LD5/8 (orange) and LD6/7 (green). В прошлой записи мы с Вами мигали светодиодом LD4. 7. – Две кнопки: пользовательская USER и сброса RESET. 8. — USB USER with Mini-B connector.

9 — USB отладчик/программатор ST-LINK/V2. 10. — Микроконтроллер STM32F303VCT6. 11. — Внешний высокочастотный генератор 8 Мгц. 12. – Здесь должен быть низкочастотный генератор, к сожелению не запаян. 13. – SWD – интерфейс. 14. – Джемперы для выбора программирования внешних контроллеров или внутреннего, в первом случае должны быть удалены. 15 – Джемпер JP3 – перемычка, предназначена для подключения амперметра, что б измерить потребление контроллера. Понятно если она удалена, то наш камушек не запустится. 16. – STM32F103C8T6 на нем находится отладочная плата. 17. — LD3985M33R Регулятор с низким падением напряжения и уровнем шума, 150мА, 3.3В.

Теперь познакомимся поближе с архитектурой микроконтроллера STM32F303VCT6. Его техническая характеристика: корпус LQFP-100, ядро ARM Cortex-M4, максимальная частота ядра 72МГц, объём памяти программ 256 Кбайт, тип памяти программ FLASH, объём оперативной памяти SRAM 40 кбайт, RAM 8 кбайт, количество входов/выходов 87, интерфейсы (CAN, I²C, IrDA, LIN, SPI, UART/USART, USB), периферия (DMA, I2S, POR, PWM, WDT), АЦП/ЦАП 4*12 bit/2*12bit, напряжение питания 2 …3.6 В, рабочая температура –40 …...+85 С. На рисунке ниже распиновка, где видим 87 портов ввода/вывода, 45 из них Normal I/Os (TC, TTa), 42 5-volt tolerant I/Os (FT, FTf) – совместимые с 5 В. (на плате справа 5В выводы, слева 3,3В). Каждая цифровая линия ввода-вывода может выполнять функцию линии ввода-вывода общего
назначения или альтернативную функцию. По ходу продвижения проектов мы с Вами будем последовательно знакомится с периферией.

Рассмотрим блок диаграмму ниже. Сердцем является 32-битное ядро ARM Cortex-M4, работающее до 72 МГц. Имеет встроенный блок с плавающей запятой FPU и блок защиты памяти MPU, встроенные макро-ячейки трассировки — Embedded Trace Macrocell (ETM), которые могут быть использованы для наблюдения за процессом выполнения основной программы внутри микроконтроллера. Они способны непрерывно выводить данные этих наблюдений через контакты ETM до тех пор, пока устройство работает. NVIC (Nested vectored interrupt controller) – модуль контроля прерываний. TPIU (Trace Port Interface Unit). Содержит память FLASH –256 Кбайт, SRAM 40 кбайт, RAM 8 кбайт. Между ядром и памятью расположена Bus matrix (Шинная матрица), которая позволяет соединить устройства напрямую. Также здесь видим два типа шинной матрицы AHB и APB, где первая более производительная и используется для связи высокоскоростных внутренних компонентов, а последняя для периферии (устройств ввода/вывода). Контроллер имеет 4 –ри 12-разрядных ADC (АЦП) (5Мбит/с) и датчик температуры, 7 компараторов (GP Comparator1 …7), 4-ри программируеммых операционных усилителя (OpAmp1…4) (PGA (Programmable Gain Array)), 2 12 разрядных канала DAC (ЦАП), RTC (часы реального времени), два сторожевых таймера — независимый и оконный (WinWatchdog and Ind. WDG32K), 17 таймеров общего назначения и многофункциональные.

В общих чертах мы рассмотрели архитектуру контроллера. Теперь рассмотори доступные программные библиотеки. Сделав обзор можна выделить следующие: CMSIS, SPL и HAL. Рассмотрим каждую применив в простом примере мигая светодиодом.

1). CMSIS (Cortex Microcontroller Software Interface Standard) - стандартная библиотека для Cortex®-M. Обеспечивает поддержку устройств и упрощает программные интерфейсы. CMSIS предоставляет последовательные и простые интерфейсы для ядра, его периферии и операционных систем реального времени. Ее использования является профессиональным способом написания программ, т.к. предполагает прямую запись в регистры и соответственно необходимо постоянное чтение и изучение даташитов. Независим от производителя аппаратного уровня.
CMSIS включает в себя следующие компоненты:
— CMSIS-CORE: Consistent system startup and peripheral access (Постоянный запуск системы и периферийный доступ);
— CMSIS-RTOS : Deterministic Real-Time Software Execution (Детерминированное исполнение программного обеспечения реального времени);
— CMSIS-DSP: Fast implementation of digital signal processing (Быстрая реализация цифровой обработки сигналов);
— CMSIS-Driver: Generic peripheral interfaces for middleware and application code (Общие периферийные интерфейсы для промежуточного программного обеспечения и кода приложения);
— CMSIS-Pack : Easy access to reusable software components (Легкий доступ к многократно используемым программным компонентам);
— CMSIS-SVD: Consistent view to device and peripherals (Согласованное представление устройства и периферийных устройств);
— CMSIS-DAP: Connectivity to low-cost evaluation hardware (Возможность подключения к недорогому оборудованию для оценки). ПО для отладки.

Для примера напишем программу – мигаем светодиодом. Для этого нам понадабится документация описывающая регистры. В моем случае RM0316 Reference manual STM32F303xB/C/D/E, STM32F303x6/8, STM32F328x8, STM32F358xC, STM32F398xE advanced ARM ® -based MCUs , а также описние конткретной ножки за что отвечает DS9118 : ARM®-based Cortex®-M4 32b MCU+FPU, up to 256KB Flash+ 48KB SRAM, 4 ADCs, 2 DAC ch., 7 comp, 4 PGA, timers, 2.0-3.6 V. Для начала в программе затактируем порт, т.к. по умолчанию все отключено чем достигается уменьшенное эрергопотребление. Открываем Reference manual и смотрим раздел Reset and clock control далее RCC register map и смотрим что за включение IOPEEN отвечает регистр

Перейдем в описание тактирования перефирии данного регистра AHB peripheral clock enable register (RCC_AHBENR) , где видим что данный порт находится под 21-м битом. Включаем его RCC->AHBENR|=(1<<21) . Далее сконфигурируем регистры GPIO. Нас интересует три: GPIOE_MODER и GPIOx_ODR . C помощью них повторим программу с предыдущей статьи, затактируем PE8. Первый отвечает за конфигурацию входа выхода, выбираем 01: General purpose output mode. GPIOE->MODER|=0×10000 . Второй за включение низкого/высокого уровня на ножке. Ниже программа:

#include "stm32f3xx.h" //Заголовочный файл микроконтроллера
unsigned int i;
void delay () {
for (i=0;i<500000;i++);
}
int main (void) {
RCC->AHBENR|=(1<<21);
GPIOE->MODER|=0×10000;
while (1){
delay ();
GPIOE->ODR|=0×100;
delay ();
GPIOE->ODR&=~(0×100);
} }

2). SPL (Standard Peripherals Library) - данная библиотека предназначена для объединения всех процессоров фирмы ST Electronics. Разработана для урощения преносимости кода и в первую очередь расчитана на начинающег разаработчика. ST работала над заменой SPL под названием «low layer», которая совместима с HAL. Драйверы Low Layer (LL) разработаны для обеспечения почти легкого экспертно-ориентированного уровня, который ближе к оборудованию, чем HAL. В дополнение к HAL также доступны LL API . Пример тойже программы на SPL.

#include
#include
#include
#define LED GPIO_Pin_8
int main() {
long i;
GPIO_InitTypeDef gpio;
// Blue LED is connected to port E, pin 8 (AHB bus)
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOE, ENABLE);
// Configure port E (LED)
GPIO_StructInit(&gpio); //объявляем и инициализируем переменную структуры данных
gpio.GPIO_Mode = GPIO_Mode_OUT;
gpio.GPIO_Pin = LED;
GPIO_Init(GPIOE, &gpio);
// Blinking LEDS
while (1) {
// On
GPIO_SetBits(GPIOE, LED);
for (i = 0; i < 500000; i++);
// All off
GPIO_ResetBits(GPIOE, LED);
for (i = 0; i < 500000; i++);
} }

Каждая функция описывается в технической документации UM1581 User manual Description of STM32F30xx/31xx Standard Peripheral Library . Здесь подключаем три заголовочных файла которые содержат необходимоые данные, структуры, функции управления сбросом и синхронизацией, а также для конфигурации портов ввода/вывода.

3). HAL - (Hardware Acess Level , Hardware Abstraction Layer) - Еще одна общая библиотека для разработки. С которой вышла и программа CubeMX для конфигурации, которую мы с Вами использовали в прошлой статье. Там же мы написали программу мигания светодиодом используя данную библиотеку. Как видим на рисунке, ниже, куб генерирует драйвера HAL and CMSIS. Что ж опишем основные используемые файлы:
— system_stm32f3x.c и system_stm32f3x.h - предоставляют минимальные наборы функций для конфигурации системы тактирования;
— core_cm4.h – предоставляет доступ к регистрам ядра и его периферии;
— stm32f3x.h - заголовочный файл микроконтроллера;
— startup_system32f3x.s — стартовый код, содержит таблица векторов прерываний и др.

#include «main.h»
#include «stm32f3xx_hal.h»
void SystemClock_Config (void); /*Объявляем функции конфигурации тактирования*/
static void MX_GPIO_Init (void); /*Инициализация ввода/вывода*/
int main (void) {
/*Reset of all peripherals, Initializes the Flash interface and the Systick.*/
HAL_Init ();
/* Configure the system clock */
SystemClock_Config ();
/* Initialize all configured peripherals */
MX_GPIO_Init ();
while (1) {
HAL_GPIO_TogglePin (GPIOE, GPIO_PIN_8);//Переключаем состояние ножки
HAL_Delay (100); }
}
void SystemClock_Config (void) {
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;

RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = 16;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig (&RCC_OscInitStruct) != HAL_OK) {

}
/**Initializes the CPU, AHB and APB busses clocks */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig (&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) {
_Error_Handler (__FILE__, __LINE__);
}
/**Configure the Systick interrupt time*/
HAL_SYSTICK_Config (HAL_RCC_GetHCLKFreq ()/1000);
/**Configure the Systick */
HAL_SYSTICK_CLKSourceConfig (SYSTICK_CLKSOURCE_HCLK);
/* SysTick_IRQn interrupt configuration */
HAL_NVIC_SetPriority (SysTick_IRQn, 0, 0);
}
/** Configure pins as Analog Input Output EVENT_OUT EXTI */
static void MX_GPIO_Init (void) {
GPIO_InitTypeDef GPIO_InitStruct;
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOE_CLK_ENABLE ();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin (GPIOE, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11
|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);
/*Configure GPIO pins: PE8 PE9 PE10 PE11 PE12 PE13 PE14 PE15 */
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11
|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init (GPIOE, &GPIO_InitStruct);
}
void _Error_Handler (char * file, int line) {
while (1) {
} }
#ifdef USE_FULL_ASSERT

Void assert_failed (uint8_t* file, uint32_t line) {
}
#endif
Здесь также как и в предыдущем примере можем просмотреть описание каждой функции в документации, например UM1786 User Manual Description of STM32F3 HAL and low-layer drivers.

Можем подвести итог, что первый выриант, используя CMSIS, является менее громоздким. Для каждой библиотеки есть документация. В последующих проектах, мы будем использовать HAL и CMSIS, используя программу для конфигурации STCube и по возможности использовать регистры напрямую, без программных оберток. На этом сегодя и остановимся. В следующей статье мы с Вами рассмотрим основные принципы построения умного дома. Всем пока.

Лучшие статьи по теме