Особенности программирования микроконтроллеров STM32

Программирование микроконтроллеров STM32 сводится к написанию алгоритма программы на языке программирования и записи исполняемой программы в память контроллера. Для написания программ будет использоваться язык C (Си). C (Си) – компилируемый статически типизированный язык программирования общего назначения.

Вначале код пишется на понятном для человека языке в среде программирования IDE, затем с помощью компилятора переводится в, так называемый, машинный код и записывается с помощью программатора в память микроконтроллера.

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

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

Комментарии

Комментарии необходимы для пояснения кода программы и не являются частью кода. Комментарии бывают многострочные и однострочные. Однострочные и многострочные комментарии подробнее рассмотрены в таблице виды комментариев:

image
Переменные

Все константы и операторы располагаются в FLASH – памяти (ПЗУ, RAM, память программ), содержимое данной памяти остается неизменной в процессе работы программы. Для хранения данных, которые могут быть изменены в процессе выполнения программы используется оперативная память (ОЗУ, RAM, память данных), в данной памяти так же располагаются все переменные. Переменная – поименованная, либо адресуемая иным способом область памяти, адрес которой можно использовать для осуществления доступа к данным и изменять значение в ходе выполнения программы. Для ввода переменной в языке C используются следующие ключевые слова:

  • char – создает переменную размером 1 байт (8 бит), которая может принимать 256 значений.
  • int – вещественный тип, размер создаваемой переменной будет зависеть от архитектуры используемого микроконтроллера, для STM32 размер будет равен 32 бита, и будет занимать в памяти микроконтроллера 4 байта.
  • short – вещественный тип, размер переменной 16 бит, это означает что переменная может принимать 65536 значений и занимает в памяти 2 байта.
  • long – вещественный тип, размер переменной 32 бита, это означает 32 значений и занимает в памяти 4 байта. что переменная может принимать 2
  • float – переменная используемая для операций с числами с плавающей запятой, занимает 4 байта. На действие с данным типом переменной необходимо затратить больше тактов процессора, то есть больше времени.

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

  • unsigned – указывает на то, что переменная не будет иметь знака то есть будет принимать только положительные значения, например unsigned char создаст переменную с диапазоном значений от 0 до 255.
  • signed – указывает на то, что переменная будет иметь знак, например signed char создаст переменную с диапазоном значений от -128 до +128.

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

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

  • Имена должны начинаться с букв латинского алфавита (a – z, A – Z), либо со знака нижнего подчеркивания «_».
  • В именах переменных, констант, а также функций разрешено использовать только буквы латинского алфавита, цифры (0 – 9), а также символ нижнего подчеркивания «_», другие символы использовать запрещено.
  • В языке C регистр букв имеет значение, это означает что применяемые буквы нижнего регистра (a – z) и буквы верхнего регистра (A – Z) различны. Например, имена переменных: name, Name, NaMe – различны.
  • Имена переменных, констант, функций могут иметь длину в соответствии со стандартом ANSIC не превышающую 32 символа.
  • Идентификаторы для новых объектов не должны совпадать с ключевыми словами языка, а также с названиями библиотечных функций.

Если необходимо записать значение переменной в шестнадцатеричном виде, то используется знак «0x» (например, 0xAAA), если необходимо создать переменную в двоичном виде, то используется знак «0b» (например, 0b1001). Различные виды переменных:

image

Диапазон значений для различных переменных:

image
Операторы
  • 1. «+» – оператор математического сложения, например, a+b, складывает две переменные a и b.
  • 2. «-» – оператор математического вычитания, например, a-b.
  • 3. «*» – оператор математического умножения, например, a*b, перемножает две переменные a и b.
  • 4. «/» – оператор математического деления, например, a/b.
  • 5. «%» – оператор остатка от деления, определяет остаток от деления, например, a%b, 6/4 =1.5, остаток от деления 0.5, значит при 6%4 = 0.5.
  • 6. «=» – оператор присваивания значения, например, a=b, присваивает переменной a значение переменной b.
  • 7. «<» – операция сравнение – меньше.
  • 8. «>» – операция сравнение – больше.
  • 9. «<=» – операция сравнение – меньше либо равно.
  • 10. «>=» – операция сравнение – больше либо равно.
  • 11. «==» – математическая операция сравнения – равно.
  • 12. «!=» – математическая операция сравнения – не равно.
  • 13. «++» – инкремент, например, a++, инкремент числа a, увеличение на единицу.
  • 14. «--» – декремент, например, a--, декремент числа a, уменьшение на единицу.
  • 15. «<<» – операция побитовый сдвиг влево, например, a<
  • 16. «>>» – операция побитовый сдвиг вправо, например, a>>b, сдвигает значение a вправо, на количество бит b, 23>>1, число 23 в двоичной системе счисления имеет вид 00010111, сдвигается на 1 вправо, получается 0000 1011, что соответствует числу 11 в десятичной системе счисления.
  • 17. «&» – операция поразрядовое логическое «И», например, a&b. Также данная операция называется логическим умножением.
  • 18. «|» – операция поразрядовое логическое «ИЛИ», например, a|b. Также данная операция называется логическим сложением.
  • 19. «~» – операция логическое «НЕ», например, ~a, инвертирует значение переменной a, то есть логические нули всех разрядов становятся логическими единицами, а логические единицы становятся нулями.
Логические операторы

Язык программирования не является полноценным без использования логических операторов. В языке C существует четыре основных оператора: if, while, for, switch.

Оператор if производит условный переход к выполнению различных частей программ. Оператор имеет определенную структуру:

image

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

В качестве условия очень часто указывают выражения сравнения, например, a == b или a!=b. Фигурные скобки означают начало и конец кода.

Если необходимо использовать сразу несколько условий, то такие условия объединяют операторами логических связей «&&» – логическое «И», «||» – логическое «ИЛИ».

Для создания циклов, дающих возможность многократно повторять один и тот же код до достижения какого-то условия, существую циклы while и for.

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

image

Цикл while необходимо применять с осторожность, так как возможно создать ситуацию, когда условия цикла не будет выполнено никогда, что приведет к зависанию программы. Более безопасно использовать цикл for. Данный цикл предназначен для выполнения заранее заданного количества повторений кода (рисунок 6). Синтаксис написания цикла for следующий: for(задание начального условия; условие; операция по изменению условия).

image
Функции

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

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

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

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

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

image
image

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

Заголовочные файлы

В больших программах, появляется множество различных функций, для удобства вызова этих функций их прототипы выносят в отдельные файлы, которые называются заголовочными. Заголовочный файл имеет расширение «.h» и подключается к файлу исходного кода (с расширение «.c») с помощью директивы #include<>. Для удобства читаемости кода обычно создают несколько файлов с исходным кодом (расширение «.c») и соответствующие им заголовочные файлы с расширением «.h».

Макросы (предпроцессорные директивы)

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

#define имя_макроса заменяемая строка

Команда #define используется для организации замены по всему файлу. Другими словами, #define сообщает компилятору, что необходимо заменить «имя_макроса» на «заменяемая строка».

Предпроцессорная директива #define может использовать логические условия, подобно «if…else». Форма записи логических условий:

image

Помимо #ifdef также используются и другие условия:

  • #ifndef – подобно #ifdef, но выполняется если указанный макрос не был ранее определен;
  • #if – подобно if сравнивает выполнение логического условия для макросов.

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

Для того чтобы понять особенности программирования STM32 необходимо подробно разобрать один пример. Для примера будет выбрана самая простая программа, также называемая «Hello world! для микроконтроллеров» – зажечь светодиод, расположенный на отладочной плате. Для данного примера используется отладочная плата на базе микроконтроллера STM32F103C8T6:

image

Плата имеет 32 вывода для «общения» микроконтроллера с внешним миром, которые могут быть сконфигурированы как на вход, так и на выход. Имеется также кнопка сброса, которая позволяет прекратить выполнение программы и начать всё заново. Также плата имеет интерфейсы Micro-USB и SWD для программатора. Данная отладочная плата также имеет светодиод, который подключен к порту C выводу 13.

Для загрузки рабочей программы в микроконтроллер по интерфейсу SWD будет использоваться программатор ST-LINK V2 для микроконтроллеров STM32.

image

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

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

Программа состоит из функций (подпрограмм), их может быть сколько угодно много, но всего они должны быть обязательно написаны в основной функции main(), так как данная функция является входной точкой, что находится в этой функции. Общий вид функции main():

image

Между фигурными скобками заключаются блоки программ. В функции main представлены два блока программ: первый от самой функции, в нем будут располагаться все подпрограммы разработанного кода; второй блок принадлежит бесконечному циклу while.

Разработка любого кода начинается с выбора и подключения библиотек. Для того чтобы зажечь светодиод необходимы всего две библиотеки RCC и GPIO.

Библиотека RCC используется для управления тактовым генератором микроконтроллера. Тактовый генератор микроконтроллера позволяет подавать напряжение на порты ввода – вывода и другую периферию, так как одной из особенностей микроконтроллеров STM32 является выключенная периферия для экономии энергии.

Библиотека GPIO используется для управления GPIO (General Ports Input – Output) портами ввода – вывода. Позволяет настраивать выводы микроконтроллера на вход или на выход, считывать состояние выводов, получать входные данные и т.д.

Заголовочные файл – файл, содержимое которого автоматически добавляется препроцессором в исходный текст. По сложившейся традиции в заголовочных файлах размещают библиотечные функции. Подключаются с помощью директивы #include <>, где между «<>» должно быть записано название заголовочного файла. Пример подключения заголовочных файлов:

image

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

Тактирование портов в данном микроконтроллере включается функцией RCC_APB2PeriphClockCmd(), APB2 – это шина к которой подключены все порты ввода – вывода данного микроконтроллера. Функция RCC_APB2PeriphClockCmd() имеет два аргумента, первый аргумент — это периферия, которую собираемся включить (например, RCC_APB2Periph_GPIOC, так включается тактирование порта C), второй аргумент — это включение или отключение тактирования и может принимать значения либо ENABLE, либо DISABLE соответственно.

После включения тактирования порта необходимо настроить вывод 13, как выход. Для этого нужно создать и заполнить структуру, понятие структура было введено ранее. Для создания структуры портов ввода – вывода используется ключевое слово GPIO_InitTypeDef. Затем структура должна быть заполнена полями, для данного микроконтроллера чтобы определить вывод необходимо заполнить всего три поля:

1) GPIO_Mode – определяет в качестве чего будет настроен вывод, может принимать значения:

  • GPIO_Mode_AIN — аналоговый вход;
  • GPIO_Mode_IN_FLOATING — дифференциальный вход;
  • GPIO_Mode_IPD — вход с подтягивающим резистором к земле;
  • GPIO_Mode_IPU — вход с подтягивающим резистором к питанию;
  • GPIO_Mode_Out_OD — выход с открытым стоком;
  • GPIO_Mode_Out_PP — выход двумя состояниями;
  • GPIO_Mode_AF_OD — выход с открытым стоком для альтернативных функций. Используется в случаях, когда выводом должна управлять периферия, прикрепленная к данному выводу порта (например, вывод Tx USART1 и т.п.);
  • GPIO_Mode_AF_PP — выход для альтернативных функций с двумя состояниями;

2) GPIO_Pin – определяет номер вывода;

3) GPIO_Speed – определяет скорость тактирования вывода.

Для включения светодиода нужно настроить вывод как выход, для этого необходимо в поле GPIO_Mode указать значение GPIO_Mode_Out_PP. В поле GPIO_Pin необходимо указать значение GPIO_Pin_13, так как светодиод находится на 13 выводе. В поле GPIO_Speed необходимо указать значение GPIO_Speed_2MHz такой частоты достаточно для того чтобы зажечь светодиод.

После заполнения всех полей необходимо инициализировать структуру с помощью функции GPIO_Init(), эта функция передают значения полей структуры в порт GPIO. Функция GPIO_Init() имеет два аргумента, первый аргумент – наименование порта в который будет отсылаться структура, в данном случае это GPIOC, второй аргумент — это наименование структуры перед которой обязательно ставится знак «&». Знак «&» называется указателем на адрес памяти.

После инициализации структуры необходимо записать бит в вывод 13 порта B, для включения светодиода. Делается это функцией GPIO_SetBits(): функция имеет два аргумента, первый аргумент — это наименование порта в данном случае это GPIOC, второй аргумент это номер вывода, в данном случае это GPIO_Pin_13. После этого необходимо сбросить бит функцией GPIO_ResetBits(): функция имеет два аргумента, таких же как их предыдущая. Обе этих функции записываются в бесконечном цикле while(1).

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

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

image
Микроконтроллер STM32

Одна из линеек микроконтроллеров, которые производит компания STMicroelectronics.

Применяемость

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