Краткое введение в разработку приложений для микроконтроллеров stm32

в 10:53, , рубрики: Cortex M3, IAR, STLINK-V2, stm32, stm32f103c8, Железо, микроконтроллеры, Программирование, Электроника для начинающих

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

На примере микроконтроллера stm32f103c8t6 и модуля Blue pill мы рассмотрим структуру типового проекта для среды разработки IAR и создадим работающую прошивку.

Всем, кому интересно начать работать с stm32, добро пожаловать под кат.

Что нам потребуется для дальнейшей работы

Все примеры, которые последуют далее, были сделаны в среде IAR Embedded workbench for ARM v7.30. Среда установлена на Windows XP, который установлен в виртуальной машине VirtualBOX, запускаемой из ОС Mac OS X El captain. Также используется программатор ST-LINK, который подключается к плате Blue pill, купленной на AliExpress за ~120₽.

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

  • Модуль Blue Pill или ему подобные

    Легко находятся на AliExpress по запросу «stm32f103c8t6 board» и стоят примерно 100₽.

    Краткое введение в разработку приложений для микроконтроллеров stm32 - 1

  • Программатор ST-Link

    Также легко доступен на AliExpress по запросу «stlink v2» и стоит тоже около 100₽.

    Краткое введение в разработку приложений для микроконтроллеров stm32 - 2

    Существует его более полная версия, которая рассчитана на подключение стандартного ленточного разъёма IDC20

    Краткое введение в разработку приложений для микроконтроллеров stm32 - 3

  • Среда разработки IAR 7.30 или новее

    Ограниченную версию 8.10 можно скачать с официального сайта.

  • Шаблон проекта, который содержит в себе все необходимые компоненты

    Шаблон можно скачать Здесь

  • Справочник по функциям STDPeripheralLibrary3.5.0

    В состав проекта входит библиотека функций StdPeriph3.5.0. Немного старовата для серьёзных проектов, однако для новичков она довольно проста и позволяет избежать головной боли при работе с периферией микроконтроллера. Справочник в формате WinHelp (CHM) можно скачать
    Отсюда

  • RM0008 Reference manual

    Справочное руководство по микроконтроллерам семейства stm32f103c8. Справочник содержит описание ядра и периферии микроконтролера, его архитектуру, описания регистров. Справочник желательно, со временем, прочитать вдоль и поперёк и знать, как работает каждый периферийный компонент. Если вы сторонник голого CMSIS, тогда без этой PDFки вам никак. Если же новичок, который пользуется Perlib, тогда всё равно нужно читать, как работает тот или иной модуль периферии. Скачать справочник можно с сайта STMicroelectonics

Шаблон проекта

Прилагаемый шаблон проекта не был взят целиком откуда-то из интернетов, а был создан своими руками из примеров, которые шли в PeripheralLibrary, файлов из пакета CMSIS и собственных доработок. Шаблон проекта не содержит файлов, которые относятся к какой либо среде разработки и может быть использован для любой из них (я не проверял, но полагаю, что так).

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

Структура каталогов проекта

  • config
    stm32f10x_conf.h

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

  • core

    В этом каталоге содержатся файлы CMSIS, которые относятся исключительно к процессорному ядру ARM CortexM3.

  • perlib

    Этот каталог содержит заголовочные файлы и исходный код библиотеки Perlib в каталогах inc и src.

  • startup

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

    Код в этом файле выполняется ДО того, как будет вызвана функция main() вашей программы.
    Обработчики прерываний ядра ARM, а также функции инициализации тактирования в этих файлах не хранятся, только вызываются. А хранятся они в файлах каталога system, который будет рассмотрен далее.

    Например, чтобы микроконтроллер stm32f103c8t6 работал на 72МГц, а не на дефолтные 8МГц нужно подключить в проекте файл startup_stm32f10x_md.s.

  • system

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

    stm32f10x.h

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

    stm32f10x_it

    Здесь прописаны обработчики прерываний ядра ARM. За исключением обработчика SysTick, который я использовал для вычисления интервалов времени, эти обработчики прерываний пусты. Если, вдруг, в вашем проекте потребуется обработка прерываний ядра, то этот файл как раз для вас.

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

    system_stm32f10x

    В этих двух файлах содержатся те самые функции инициализации подсистемы тактирования ядра и ФАПЧ, которые вызываются из файла первичной инициализации в каталоге startup. А именно, функции SystemInit и SystemCoreClockUpdate.

Создаём проект

Чтобы создать наш первый проект, сначала нужно создать для него папку. Назовём её EX01.
Запускаем IAR и в меню Project выбираем пункт Create New Project.

Рисунок 1. Создание нового проекта

Краткое введение в разработку приложений для микроконтроллеров stm32 - 4

Значение toolchain следует оставить ARM, а Project template мы выбираем C → main. Далее, нажимаем кнопку OK.

Рисунок 2. Сохранение пустого проекта

Краткое введение в разработку приложений для микроконтроллеров stm32 - 5

Возникнет окно сохранения проекта. В нём следует выбрать ранее созданную папку. Назовём проект ex01. Теперь, можно нажать Сохранить.

В результате будет создан пустой файл main и пустрой проект, который не содержит пока никаких настроек. Следующим шагом нужно будет сохранить рабочую область Workspace, чтобы она не мешала нам своими вопросами в будущем. Нажимаем File → Save Workspace. Называем её ex01 и нажимаем Сохранить.

Рисунок 3. Сохранение рабочей области

Краткое введение в разработку приложений для микроконтроллеров stm32 - 6

Теперь мы готовы использовать шаблон проекта. Скопируем его из архива в нашу папку EX01.

Рисунок 4. Копирование файлов из шаблона в новый проект

Краткое введение в разработку приложений для микроконтроллеров stm32 - 7

Файлы main, которые создал шаблон проекта следует заменить файлами из проекта.

После того, как мы скопировали шаблон проекта, следует настроить сам проект.

Для этого, выбираем меню Project → Options. Откроется окно, которое будет содержать список категорий опций слева, каждое из которых будет иметь те или иные закладки.

Выбираем категорию General Options и в ней закладку Target. В группе Processor Variant следует выбрать опцию Device и нажать рядом с ней кнопку выбора конкретного устройства. В нашем случае это будет ST → STM32F103 → ST STM32F103x8.

Рисунок 5. Выбор целевого устройства для проекта

Краткое введение в разработку приложений для микроконтроллеров stm32 - 8

Следующая категория, которая требует нашего внимания, это категория C/C++ Compiler и закладка Preprocessor.

Рисунок 6. Настройки закладки Preprocessor

Краткое введение в разработку приложений для микроконтроллеров stm32 - 9

Блок Additional include directories следует заполнить ссылками на каталоги шаблона проекта.

PreInclude file нужно выбрать конфигурационный файл библиотеки PerLib, а Defined symbols следует указать STM32F10X_MD, чтобы файлы начальной инициализации установили правильное тактирование ядра и правильно настроили ФАПЧ.

Поскольку мы используем в качестве программатора ST-Link v2, следует выбрать драйвер, которым будет пользоваться среда разработчика. Выбираем категорию Debug и закладку Setup, на которых в выпадающем списке выберем Driver ST-Link.

Рисунок 7. Выбор средства отладки

Краткое введение в разработку приложений для микроконтроллеров stm32 - 10

Теперь нужно настроить заливку прошивки в контроллер. Сделать это можно в этой же категории, на закладке Download. Нас интересуют опции Verify download и Use flash loader(s).

Рисунок 8. Настройка параметров заливки прошивки в микроконтроллер

Краткое введение в разработку приложений для микроконтроллеров stm32 - 11

Так как мы выбрали ST-Link в качестве средства заливки прошивки в контроллер и отладки, следует настроить его драйвер так, чтобы он работал с нашим контроллером. У платы Blue Pill нет полноценного JTAG разъёма, который мог бы работать по полному протоколу JTAG. Вместо этого, мы будет использовать его упрощённый режим, который называется SWD. Этот режим JTAG использует только три линии. Это GND, SWDCLK и SWDIO. По-умолчанию, включен режим полноценного JTAG, поэтому нам нужно его изменить на SWD и задать частоту ядра 72МГц.
В списке категорий выбираем ST-LINK и меняем опцию в группе Interface на SWD.

Теперь можно нажимать OK, наш проект настроен.

Рисунок 9. Настройка драйвера ST-LINK

Краткое введение в разработку приложений для микроконтроллеров stm32 - 12

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

Добавление файлов в проект осуществляется в панели Workspace. Там уже есть файл main, однако нам нужно добавить PerLib, а также один из файлов начальной инициализации из каталога startup и файлы начальной инициализации из каталога system. Мы могли бы накидать просто кучей, но потом пришлось бы мучаться и если проект вырастает слишком большим, то такая свалка будет очень мешать.

Среда разработки IAR позволяет создавать группы файлов. Группы являются исключительно виртуальным понятием. Они всего лишь позволяют организовать файлы в проекте. Никакого отношения к дисковым каталогами группы не имеют.

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

Сначала, создадим группу config и положим в неё файл stm32f10x_conf.h из каталога config.
Для этого следует кликнуть правой кнопкой мышки на названии проекта в окне Workspace и в выпадающем меню выбрать Add → Add Group. Назовём группу Config.

Чтобы добавить файлы в эту группу, кликаем правой кнопкой мыши по ней и выбираем Add → Add Files. В открывшемся окне открываем папку config и выбираем файл stm32f10x_conf.h.

Краткое введение в разработку приложений для микроконтроллеров stm32 - 13

Подобным образом добавляем содержимое папок Perlib, Startup и System. Папку Core добавлять не нужно, там одни заголовочные файлы, которые итак доступны из добавленной библиотеки Perlib.

Рисунок 11. Полный вид пустого и полностью настроенного проекта

Краткое введение в разработку приложений для микроконтроллеров stm32 - 14

Вот теперь проект полностью готов к дальнейшей разработке.

Немного теории

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

В микроконтроллерах, для организации многопоточности, используются Операционные Системы Реального Времени (ОСРВ), такие, как ThreadX или FreeRTOS. Все они позволяют создавать множество таких циклов, в которых функции исполняются одна за другой, вот только циклы работают одновременно. Подобно нескольким Ардуинам, утрамбованным в один микроконтроллер.

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

ОСВР выделяет на каждый поток крошечный интервал времени. По истечении этого интервала, ОСРВ переключается на следующий поток (неважно, успел ли предыдущий завершить свои действия или нет) и так по кругу. Разные потоки могут получать разные интервалы времени, в зависимости от их приоритета. Такая многопоточность называется «вытесняющая».

За перебор потоков и передаче им управления на короткий интервал времени отвечает компонент ОСРВ, который называется «планировщик».

Начинающим программистам сложно сразу освоить огромную и сложную периферию stm32 и при этом ещё и изучить ОСРВ.

К счастью, существуют способы делать многопоточные приложения вообще без ОСРВ. Для этого, нам на помощь приходит «кооперативная многопоточность». Кооперативная многопоточность позволяет делать относительно небольшие многопоточные проекты без привлечения ОСРВ.

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

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

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

Структура кооперативно-многопоточного приложения

В основе написания кооперативно-многопоточных приложений лежит машина состояний. Я не буду подробно её описывать, поскольку здесь это уже подробно описано. Однако, вкратце объясню суть. Машина состояний — это некий абстрактный объект, количество состояний которого конечно. Объект переходит из одного состояния в другое либо под воздействием внешних факторов, либо по причине внутренних процессов. В нашем случае, поток кооперативного приложения это реализация машины состояний.

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

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

Каждый поток представляет собой отдельный модуль (заголовочный файл и файл кода).

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

void XXX_Init();
void XXX_Control();

Функция XXX_Init() вызывается перед главным циклом в функции main(), а функция XXX_Control() вызывается в главном цикле функции main.

void main()
{
   // Инициализация модуля XXX
    XXX_Init();
   // Инициализация модуля YYY
    YYY_Init();
   // Инициализация модуля ZZZ
    ZZZ_Init();
    while(true){
        XXX_Control();
        YYY_Control();
        ZZZ_Control();
    }
}

Файл модуля XXX мог бы выглядеть следующим образом:

xxx.c

#include "xxx.h"
#define XXX_WATER_MAX_THRESHOLD  100500
#define XXX_WATER_MIN_THRESHOLD 9000
typedef enum{
      idle,
      state1,
      state2,
        :
      stateX,
}XXX_States;
static XXX_States xxxCurrentState = idle;
static int xxxToiletWaterLevel=0;
//--------- Приватные функции модуля --------
void private_init1()
{
}
void private_init2()
{
}
void private_measureLevel()
{
}
void private_flush()
{
}
void private_superFlush()
{
}
//-------- Публичные функции модуля ---------
void XXX_Init()
{
    xxxCurrentState=idle;
}
void XXX_Reset()
{
    private_superFlush();
    xxxCurrentState=idle;
}
void XXX_Control()
{
     switch(xxxCurrentState)
     {
          case idle:
                private_measureLevel();
                if(xxxToiletWaterLevel>XXX_WATER_MIN_THRESHOLD)
                     xxxCurrentState=state1;
                break;
          case state1:
                 if(xxxToiletWaterLevel<XXX_WATER_MAX_THRESHOLD)
                 {
                     private_flush();
                     xxxCurrentState=idle;
                 }
                else
                    xxxCurrentState=state2;
                break;
          case state2:
               // Ждём сброса
               break;
     }
}

Пример кооперативно-многопоточного приложения

Чтобы не быть слишком уж абстрактным, давайте представим себе реальную задачу. Допустим, у нас есть поток, который мигает светодиодом (два раза в секунду), который подключен катодом к порту PC13. Также у нас есть поток, который получает команды через последовательный порт. Если приходит символ '0' (0x30), тогда мигание прекращается и клиенту посылается знак '-'. Если же приходит символ '1' (0x31), мигание включается и клиенту возвращается символ '*'. При нажатии любой другой клавиши возвращается символ 'E'.

Поток управления миганием светодиода мы расположим в файлах modLed.h и modLed.c. Этот поток изначально находится в состоянии idle и ничего не делает. Однако, его публичная функция MODLED_command, при получении аргумента modled_on переключает состояние потока в
modled_st_on. В этом состоянии поток зажигает светодиод, запоминает начальное значение счётчика global_count и переходит в состояние ожидания modled_st_wait1. В этом состоянии он постоянно проверяет текущее значение счётчика global_count и когда разность текущего счёта и начального счёта составляет MODLED_BLINK_DELAY_ON, поток переходит в состояние modled_st_off. В этом состоянии, поток выключает светодиод, запоминает текущее значение счёта и переходит в состояние modled_st_wait2. В этом состоянии поток также сравнивает текущее значение счётчика global_count с начальным и когда разница составляет MODLED_BLINK_DELAY_OFF переходит в состояние modled_st_on. И так будет продолжаться до тех пор, пока кто-то не вызовет функцию MODLED_command с аргументом modled_off. Тогда, функция переключит состояние потока в modled_st_clamp. Поток выключит светодиод и перейдёт в состояние modled_st_idle.

Инициализация потока modled начинается в функции main вызовом функции MODLED_init(). В этой функции происходит инициализация порта GPIOC и установка начального состояния потока. После чего, в цикле производится постоянный вызов функции MODLED_control(), которая за одну итерацию выполняет проверку текущего состояния и выполняет небольшие действия для него.

Поток управления последовательным портом сделан по идентичной схеме.

У него имеются приватные функции инициализации порта GPIO и модуля USART1. Также, внутри него спрятан обработчик прерывания от периферийного модуля USART1, в котором производится запоминание текущего принятого байта и установка статуса потока в moduart_st_command.

Изначально поток moduart находится в состоянии moduart_st_idle, в котором он ожидает приёма байта. Как только байт принят и запомнен в переменной, обработчик прерывания изменяет состояние потока на moduart_st_command и поток проверяет принятый байт. Если принятый байт является командой '0', тогда вызывается функция MODLED_command с аргументом modled_off и возвращается символ '-'. Если принятый байт является командой '1', тогда вызывается функция MODLED_command с аргументом modled_on и возвращается символ '*'. В остальных случаях просто возвращается символ 'E'.

Инициализация потока MODUART также происходит в файле main, вызовом функции MODUART_init(). Эта функция инициализирует порт и периферийный модуль USART1 и переводит поток в режим ожидания. В главном цикле вызывается контрольная функция потока MODUART_control(), которая проверяет текущее состояние и выполняет небольшой фрагмент кода, связанный с его обработкой.

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

Переменная global_count

Наверное, стоит отдельно рассказать про эту переменную global_count.

В файле инициализации startupstartup_stm32f10x_md.s содержится таблица прерываний микроконтроллера. В ней прописаны адреса обработчиков для всех прерываний периферии
и ядра. Однако, прерывания периферии приходят только тогда, когда периферия инициализирована. Поэтому, изначально, обработчики указывают на временные заглушки. А вот обработчики прерываний ядра Cortex M3 реально существуют и содержаться в файле systemstm32f10x_it. Одно из таких прерываний, это прерывание системного таймера SysTick. Этот таймер используется ОСРВ для вызова планировщика задач. Но, я использую его для вызова функции TimingDelay_Decrement, которая реально определена в файле main.

//-------------------------------------------------------------------
// Глобальный счётчик
unsigned long global_count=0;
// Эта функция вызывается из обработчика прерываний SysTick, который
// находтся в файле stm32f10x_it.c
void TimingDelay_Decrement(void)
{
    // Увеличиваем глобальный счётчик
    global_count++;
    if (TimingDelay != 0x00)
    { 
        TimingDelay--;
    }       
}

В начале функции main стоит установка частоты SysTick таймера на 1 мс. Следовательно,
каждую тысячную долю секунды в обработчике прерывания SysTick будет увеличиваться счётчик.

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

Эпилог

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

По сравнению с другими подобными «мигалками светодиодов на stm32», которые встречаются на просторах интернета, моя получилась довольно громоздкой. Однако, на таком шаблоне я начинаю новые и сложные проекты и потратить 2-5 минут на его создание не кажется мне такой уж страшной потерей.

Исходный код файла modLed.h

Файл modLed.h

#ifndef __MODLED_H
#define __MODLED_H
#include "stm32f10x.h"
typedef enum{
    modled_off,
    modled_on,
}MODLED_Commands;
void MODLED_init();
void MODLED_command(MODLED_Commands aCmd);
void MODLED_control();
#endif

Исходный код файла modLed.c

Файл modLed.c

#include "modLed.h"
#include "main.h"
// Интервал ожидания перед переключением состояния
#define MODLED_BLINK_DELAY_ON  250
#define MODLED_BLINK_DELAY_OFF  250
// Состояния потока
typedef enum{
    modled_st_idle,
    modled_st_on,
    modled_st_wait1,
    modled_st_off,
    modled_st_wait2,
    modled_st_clamp,
}MODLED_States;
// Внешний счётчик, который увеличивается на 1 каждую милисекунду.
extern unsigned long global_count;
static MODLED_States modledState=modled_st_idle;
static uint32_t modledStart, modledEnd;
/*
    PC13 - led (Open drain)
*/
void modled_init_gpio()
{
    // Разрешаем тактирование порта GPIOC
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    GPIO_DeInit(GPIOC);
    
    GPIO_InitTypeDef gpio;
	GPIO_StructInit(&gpio);
    /*
        Модули Blue Pill имеют на борту светодиод, подключенный к PC13.
        ВНИМАНИЕ: Порт PC13 имеет очень слабый источник тока (3мА макс) и ни в коем
        случае не должен использоваться как драйвер светодиода. Только как открытый коллектор.
    */
    // Режим порта ВЫВОД, открытый коллектор.
	gpio.GPIO_Mode=GPIO_Mode_Out_OD;
	gpio.GPIO_Speed=GPIO_Speed_2MHz;
	gpio.GPIO_Pin=GPIO_Pin_13;
	GPIO_Init(GPIOC, &gpio);
    // Выключаем светодиод
    GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);
}
void MODLED_init()
{
    modled_init_gpio();
    modledState=modled_st_idle;
    modledStart=global_count;
}
// Функция переключения состояния.
void MODLED_command(MODLED_Commands aCmd)
{
    
    switch(aCmd)
    {
        case modled_on:
            modledState=modled_st_on;
            break;
        case modled_off:
            modledState=modled_st_clamp;
            break;
    }
}
void MODLED_control()
{
    switch(modledState)
    {
        case modled_st_idle:
            break;
        case modled_st_on:
            // Записываем бит включения светодиода
            GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET);
            // Засекаем текущий счётчик милисекунд
            modledStart=global_count;
            // Переходим в состояние ожидания интервала включения
            modledState=modled_st_wait1;
            break;
        case modled_st_wait1:
            // Читаем текущий счётчик
            modledEnd=global_count;
            // Разность между начальным и конечным значениями счёта есть количество миллисекунд
            if((modledEnd-modledStart)>=MODLED_BLINK_DELAY_ON)
            {
                // Переходим в состояние гашения и засекаем начальный отсчёт
                modledState=modled_st_off;
            }
            break;
        case modled_st_off:
            // Выключаем светодиод
            GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);
            // Засекаем начальный интервал времени
            modledStart=global_count;
            // Переходим в состояние ожидания интервала выключения
            modledState=modled_st_wait2;
            break;
        case modled_st_wait2:
            // Читаем текущий счётчик
            modledEnd=global_count;
            // Разность между начальным и конечным значениями счёта есть количество миллисекунд
            if((modledEnd-modledStart)>=MODLED_BLINK_DELAY_OFF)
            {
                // Переходим в состояние включения и засекаем начальный отсчёт
                modledState=modled_st_on;
            }
            break;
        case modled_st_clamp:
            // Состояние гашения с уходом в бездействие
            GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);
            modledState=modled_st_idle;
            break;
        default:
            modledState=modled_st_idle;
            
    }
}

Исходный код файла modUart.h

Файл modUart.h

#ifndef __MOD_UART_H
#define __MOD_UART_H
#include "stm32f10x.h"
void MODUART_init();
void MODUART_control();
#endif

Исходный код файла modUart.c

Файл modUart.c

#include "modUart.h"
#include "modLed.h"
#define MODUART_BAUDRATE    115200
typedef enum{
    moduart_st_idle,
    moduart_st_command,
}MODUART_STATES;

static MODUART_STATES moduartState=moduart_st_idle;
static uint16_t moduartCmd=0;
/*
PA9     UART1_TX
PA10    UART1_RX
*/
void moduart_init_gpio()
{
    //RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    // Разрешаем тактирование порта GPIOA
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitTypeDef gpio;
	GPIO_StructInit(&gpio);
	gpio.GPIO_Mode=GPIO_Mode_AF_PP;
	gpio.GPIO_Speed=GPIO_Speed_2MHz;
	gpio.GPIO_Pin=GPIO_Pin_9;
	GPIO_Init(GPIOA, &gpio);
	gpio.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	gpio.GPIO_Pin=GPIO_Pin_10;
	GPIO_Init(GPIOA, &gpio);
}
void moduart_init_uart1()
{
    // Включаем тактирование модуля периферии UART1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    USART_InitTypeDef uart;
	USART_StructInit(&uart);
	uart.USART_BaudRate=MODUART_BAUDRATE;
	uart.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	uart.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
	uart.USART_Parity=USART_Parity_No;
	uart.USART_StopBits=USART_StopBits_1;
	uart.USART_WordLength=USART_WordLength_8b;
	USART_Init(USART1, &uart);
    // Настраиваем режим срабатывания прерываний
    // -- По непустому регистру приёмника USART1
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    // Указываем контроллеру прерываний пропускать прерывания от последовательного порта
    NVIC_EnableIRQ(USART1_IRQn);
    // Включаем периферию USART1.
	USART_Cmd(USART1, ENABLE);
}
// Обработчик прерываний от USART1
void USART1_IRQHandler()
{
    // Выясняем причину прерывания
    if(USART_GetITStatus(USART1, USART_IT_RXNE)!=RESET)
	{
        // Подтверждаем выполнение прерывания
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
        // Считываем принятый байт
		moduartCmd = USART_ReceiveData(USART1);
        moduartState = moduart_st_command;
    }
}
void moduart_processCmd()
{
    // Символ ошибки
    uint16_t r = 'E';
    switch(moduartCmd)
    {
        // Команда выключения мигания
        case '0':
            {
                MODLED_command(modled_off);
                r = '-';
            }
            break;
    
        // Команда включения мигания
        case '1':
            {
                MODLED_command(modled_on);
                r = '*';
            }
            break;
        
    }
    // Посылаем символ клиенту
    USART_SendData(USART1, r);
    moduartCmd=0;
}
void MODUART_init()
{
    moduartState=moduart_st_idle;
    moduart_init_gpio();
    moduart_init_uart1();
}
void MODUART_control()
{
    switch(moduartState)
    {
        case moduart_st_idle:
            break;
        case moduart_st_command:
            moduart_processCmd();
            moduartState=moduart_st_idle;
            break;
        default:
            moduartState=moduart_st_idle;
    }
}

Исходный код файла main.c

Файл main.c

#include "main.h"
#include "modUart.h"
#include "modLed.h"
// Imported value
static __IO uint32_t TimingDelay;
RCC_ClocksTypeDef RCC_Clocks;
int main()
{
	RCC_GetClocksFreq(&RCC_Clocks);
    // Инициализируем таймер SysTick чтобы он срабатывал каждую миллисекунду
	SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);
    // Инициализируем поток светодиода
    MODLED_init();
    // Инициализируем поток последовательного порта
    MODUART_init();
    do{
        // Вызываем итерацию потока моргателя светодиода
        MODLED_control();
        // Вызываем итерацию потока последовательного порта
        MODUART_control();
    }while(1);
#pragma diag_suppress=Pe111
    return 0;
}
//-------------------------------------------------------------------
void Delay(__IO uint32_t nCount)
{
        TimingDelay = nCount;
        while(TimingDelay != 0);
}
//-------------------------------------------------------------------
// Глобальный счётчик
unsigned long global_count=0;
// Эта функция вызывается из обработчика прерываний SysTick, который
// находтся в файле stm32f10x_it.c
void TimingDelay_Decrement(void)
{
    // Увеличиваем глобальный счётчик
    global_count++;
    if (TimingDelay != 0x00)
    { 
        TimingDelay--;
    }       
}

//-------------------------------------------------------------------
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
        while(1){}
}
#endif

Полный проект можно загрузить отсюда.

Автор: Станислав Дятлов

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js