Реализация программного кода для модуля индикации на ILI9341 + STM32. Часть 4.2
Часть 1
Часть 2
Часть 3
Часть 4.1
Пролог
Мнения были разные по поводу разбора кода и его необходимости вообще. Я постарался в данной статье реализовать метод «золотого сечения», поэтому:
а) в конце статьи будет приложен исходник экспертам дальше не читать
б) приведу алгоритм работы и разберу его
в) объясню как пользоваться библиотеками SPL
г) в объеме статьи расскажу как пользоваться определенной периферией, покажу реализацию работы с ней в коде
д) отдельным пунктом опишу работу с ILI9341, т.к. тема довольно разжевана, то просто расскажу о главном — как обдумано реализовать функцию инициализации (в интернете видел лишь код с фразой: «вот рабочая инициализация, копируйте и не вдумывайтесь что это») и запустить его через аппаратный SPI.
Слишком подробный разбор кода вы тут не увидите, все будет в меру, иначе мне придется написать книгу страниц так в 200–250. Поэтому изучайте даташиты и прочую документацию (ссылки будут) перед тем, как приступать к написанию программы. Те, кто первый раз сядет за МК — не бойтесь, если возникнут вопросы я вам подскажу и помогу, так что данный код вы осилите.
Какую среду разработки и можно ли облегчить жизнь библиотеками?
Скажу сразу — код в данной статье не «родной», но опробован и работает! Т.к. IAR не самый удобный софт для новичков, да и как-то его многие обходят стороной из-за слишком большого функционала или еще каких-то причин, то было принято решение для учебной статьи перенести проект в «кокос» (CooCoxIDE 1.7.7). Так же я решил отойти от регистров и написать код на SPL.
SPL — это стандартные библиотеки от ST для работы с периферией МК STM32. Сейчас на замену вышли новые библиотеки HAL, но пока они не вытеснили первые во многих проектах. Плюсы и достоинства можете вычитать в гугле.
Я выбрал SPL — т.к. мне больше нравится конечный код после «жесткой оптимизации», они интуитивно понятнее, HAL более приспособлены для работы с различными RTOS. Это сугубо мое мнение, я не прошу никого принимать это на веру и рассказывать в комментариях о том, что лучше или хуже, не стоит.
Как я выше писал — был переход с регистров на библиотеки, в чем разница:
Плюсы:
а) читаемость кода написанного с использованием SPL просто отличная, а в статьях с уклоном под обучение это самое главное;
б) скорость написания кода намного выше, мне потребовалось 1,5 часа чтобы под имеющийся алгоритм написать код с нуля и отладить его;
в) более легкий переезд с камня на камень, ну вдруг у вас нету STM32F103RBT6, а есть STM32F105VCT6 и вы на нем будете собирать.Минусы:
а) функции SPL более громоздкие и занимают больше памяти, а так же дольше выполняются. Но это относится лишь к функциям инициализации, все остальное имеет такое же быстродействие;
б) в даташитах весь разбор идет именно на регистрах и это главный минус.Мой итог:
Можно смело работать в CoIDE и писать программы с использованием SPL ровно до уровня пока вы считаете себя любителем. Как только у вас появляется цель проектировать серьезные устройства или начать зарабатывать деньги электроникой — тут же пересаживайтесь на IAR и курите даташиты с их регистрами.
Составляем алгоритм нашей программы
Для начала прикинем хрен к носу общую функциональную структуру устройства:
Рисунок 1 — Блок-схема для программы управления
Теперь мы видимо что должна измерить наша часть устройства и как поступить с полученными данными. Все измеренные напряжения и ток вывести на TFT панель, определить попадает ли напряжение в нормальный диапазон (согласно ГОСТу), а в конце перемножить выходной ток с выходным напряжением и получить мощность потребления нагрузкой. Вроде бы все просто и понятно.
Так же наша плата должна измерить температуру, вывести ее на экран, в случае превышения аварийных 85 оС должна отключить устройство путем подачи лог. 1 на ноги SD драйвера IR2110 в силовых платах. А так же по результатам измеренной температуры должно регулироваться значение скважности ШИМа, управляющего куллерами.
Все аварийные состояния должны так же отображаться на светодиодной индикации, которая служит для отображения «серьезных вещей» неполадок в системе или выход из строя устройства.
Функционал данного блока не сложный, а значит большого труда в реализации его не возникнет.
Что необходимо сделать перед тем, как продолжить изучать статью?
1) Скачать софт с которым мы будем работать. Это будет CooCoxIDE 1.7.7, ниже я приложил ссылочку на свой ЯД, где вы можете скачать папку с данной версией программы, уже с установленными в ней SPL, а так же с уже скаченным компилятором чтобы не мучились и не искали. Все для старта работы там есть:
CooCoxIDE 1.7.7
2) Скачать священный манускрип под названием даташит на STM32F103:
Datasheet STM32F103
3) Скачать не менее священное писание под названием референс мануал, который гуглится как RM0008 и имеет эпичный объем в 1128 страниц. Это самое объемное научное писание, которое я осилил, правда были еще все тома Ландау-Лившица, но там несколько книг все таки… Я к чему — советую обращаться к данному файлу при любых возникающих непонятнках и курьезах.
RM0008
4) Еще очень настоятельно советую блог одного паренька с уровнем знаний по ST — Бог, у него там разжевана переферия для новичков и достаточно сложные и интересные задачи:
Николай Горбунов
5) И последнее… бежим скачивать очень полезную программку STM32CubeMX — Официальный сайт ST
Надеюсь вы скачали все, что я вам посоветовал и теперь можно приступить к написанию программы.
Собираем проект
1) Выбираем переферию с которой нам предстоит работать
Рисунок 2 — Репозиторий для выбора переферии, которую будем использовать
Теперь вкратце кто и за что отвечает:
а) CMSIS core — первым делом ставим галочку именно на эту библиотеку, сразу подключится еще несколько библиотек. Это минимум с которым можно работать. Данная библиотека как следует из ее названия подключает «ядро», базовые функции через которые будет работать остальная периферия
б) RCC — библиотека отвечающая за тактирование микроконтроллера, это отдельный хитрый момент, который сегодня подобно рассмотрю. Данная библиотека позволяет выбрать источником генерации, а так же выставить коэффициент деления частоты для каждой периферии.
в) GPIO — библиотека работы с портами ввода/вывода. Так же она позволяет провести все первичные настройки для остальной периферии.
г) DMA — эта библиотека позволяет работать с прямым доступом к памяти. Очень подробно это можно прочитать в интернете — тема разжевана, для новичков пока достаточно понять, что данный принцип позволяет повысить скорость работы всего устройства.
д) SPI — библиотека для работы с интерфейсом SPI, позволяющий обмениваться данными с кучей устройств. В нашем случае через него мы общаемся с TFT экраном на ILI9341.
е) TIM — библиотека для работы с таймера. Тут думаю все понятно, она позволит нам запустить ШИМ для управления куллерами и конечно же для реализации задержек и генерации прерываний.
ж) ADC — библиотека для работы с нашей главной периферией аналогово-цифровым преобразователем (АЦП), которая измеряет все наши напряжения и ток.
Переходим к написанию программы
Я не собирался давать уроки по С, но так как я ориентируюсь на аудиторию людей, которые боялись взяться за изучение МК и мне хочется их подтолкнуть, то основные моменты буду описывать. Первые 2 команды:
1) define — это команда, которая служит для подстановки одного значения вместо другого. Половина тут не поняли, как и я, когда изучал)
Пример 1:
У нас программа, которая зажигает светодиод командой GPIO_SetBits (GPIOA, GPIO_Pin_1); и выключает его командой GPIO_ResetBits (GPIOA, GPIO_Pin_1); . Сейчас вам не надо задумываться над этими командами. Как видите команда достаточно сложная и в процессе написания кода вам не хочется ее 100 раз переписывать, и тем более не хочется вспоминать каждый раз как включать/выключать светодиод. Поэтому мы поступаем следующим образом, пишем:
#define LED_ON GPIO_SetBits(GPIOA, GPIO_Pin_1);
#define LED_OFF GPIO_ResetBits(GPIOA, GPIO_Pin_1);
Что мы сделали? А упростили себе жизнь. Теперь каждый раз, когда мы будем писать в тексте программы LED_ON; (точка с запятой обязательна), то будет происходить замена данной фразы на команду GPIO_SetBits (GPIOA, GPIO_Pin_1); Тоже самое с выключением.
Пример 2:
Вы пишите программу, которая считает дни в году. Чтобы каждый раз в программе не писать число 365 (а на самом деле может быть любое сложное число, хоть Пи) мы поступаем аналогично предыдущему примеру и пишем:
#define Year 365
Стоит заметить, что так как 365 просто константа, а не команда, то после нее не надо ставить точку с запятой. Иначе будет вставлять не 365, а 365; и при использование в той же формуле будет воспринято как ошибка.
Команда #define X1 Y2 просто выполняет в коде замену всех X1 на Y2.
Надеюсь тут вопросов не осталось и переходим к более простой, но пожалуй самой важной команде:
2) include — она позволяет нам прикрепить к нашему коду другие файлы и библиотеки.
Пример 1:
Любой код, в том числе и наш будет начинаться именно с этой команды! Мы выбрали в репозитории галочками библиотеки и Кокос взял и скопировал файлы с библиотеками в папку с нашим проектом. Этого мало, нам необходимо их прицепить в нашем файле main.
Я пока не стал присоединять все библиотеки, АЦП, таймеры и прочее что мы упоминали выше и не дописали тут. Прикрепил основные библиотеки, чтобы на данном этапе не забить голову. Получаем такой код:
#include "stm32f10x_conf.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_spi.h"
#include "TFT ILI9341.h"
Чтобы понять откуда мы взяли это непонятные файл надо посмотреть в дерево проекта:
Рисунок 3 — Дерево проекта с необходимыми библиотеками
Как видим все, что мы прикрепили есть в этом «дереве». Это библиотека CMSIS и прочие. Тут стоит обратить внимание на четыре момента:
а) думаю момент с подключение понятен, но уточню — #include «имя файла» Именно так надо включать файл, указывая его имя в скобках и не надо ставить точку с запятой в конце строки.
б) если заметили, то подключаю я исключительно файлы с расширением .h, надо всегда делать именно так. Чем отличается файл с расширением .h и файла с расширением .c
в) есть 2 файла при использование библиотек SPL, которые подключаются всегда в main.
#include "stm32f10x_conf.h"
#include "stm32f10x.h"
г) как видно файл font.h я не подключил в основной файл main, т.к. он у меня подключен в файле библиотеки TFT ILI9341.h Почему так? FONT — библиотека с шрифтами и используется только в функциях работы с TFT панелью. Чтобы не загромождать основной файл main я прикрепил шрифты с помощью #include уже внутри файла TFT ILI9341.h.
3) Отличия файлов с разришениями .h и .c
Любая приличная библиотека состоит двух файлов, один из них имеет разрешение .c В этом файле собраны все функции и их реализации. Файл с данным расширением является основной для файла .h и поэтому прикрепляется внутри последнего.
Вторая часть библиотеки файл .h содержит как раз все необходимые инклуды, как например FONT для TFT ILI9341. Так же в ней описаны все define, объявлены константы. Для работы с данной библиотекой, как вы видели выше — мы подключаем именно этот файл, а файл .c идет «хвостом» внутри .h
Фух… все, если у вас не взорвался мозг, то тогда просто отдохните, попейте чая и пойдем дальше за самым интересным.
Тактирование микроконтроллеров STM32
Приступаем к самому ответственному и хитрому пункту. Тактирование STMок происходит не как у AVR и пиков. Там просто берется частота кварца и гонится в камень.
В STM32 делается так:
а) МК всегда запускается от HSI — это внутренний генератор на 8 МГц
б) Потом МК переходит на HSE — это внешний кварцевый резонатор
в) Далее сигнал идет на PLL, где частота умножается и идет на периферию.
Именно из-за последнего пункта, когда я переходил на STM-ки у меня возник ступор: кварц я подключил на 8 МГц, а работает все на 72 МГц. Поэтому типичный кварцы это 8, 16, 24 МГц. Дальше частота умножается внутри микроконтроллера.
Все это можно увидеть на следующей схеме из даташита, находится она на странице 14. Именно поэтому я включил данный манускрип в обязательные)
Рисунок 4 — Схема тактирования периферии микроконтроллера STM32
Еще прошу обратить внимание, что когда тактовая частота берется с PLL (множителя частоты) и потом распределяется по периферии и устанавливается с помощью делителя. Есть две шины: APB2 и APB1, на каждой висит определенная периферия. У каждой шины есть ограничение по частоте: 72 МГц у APB2 и 36 МГц у APB1, то есть на 1-й шине частота равна ½ от тактовой с PLL.
Приведу пример: у SPI1 питание идет с шины APB2 с максимальной частотой 72 МГц, а SPI2 висит на шине APB1 с частотой 36 МГц и из этого следует, что скорость SPI2 будет ниже. Это стоит учитывать!
Теперь обратимся к функции, которая выполняет все настройки шин. За тактирование отвечает библиотека RCC, поэтому функцию нужно искать в файле stm32f10x_rcc.h, который мы подключили к нашему проекту в самом начале.
void RCC_Configuration(void)
{
RCC_DeInit(); // Выполняем сброс reset
RCC_HSEConfig(RCC_HSE_ON); // Включаем тактирование HSE (от кварца)
HSEStartUpStatus = RCC_WaitForHSEStartUp(); // Ждем пока частота кварца стабилизируется
if (HSEStartUpStatus == SUCCESS) // Если все отлично, то переходим на кварц
{
RCC_HCLKConfig(RCC_SYSCLK_Div1); // Выставляет делитель в 1 (Div1), системная частота становится 72 МГц
RCC_PCLK2Config(RCC_HCLK_Div1); // Запитываем APB2 от тактовой частоты PLL в 72 МГц
RCC_PCLK1Config(RCC_HCLK_Div1); // Запитываем APB1 от тактовой частоты PLL в 72 МГц
RCC_ADCCLKConfig(RCC_PCLK2_Div2); // Ставим делитель на 2 (Div2) и получаем частоту 36 МГц вместо 72 МГц
RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_9); // Устанавливаем множитель 9, получаем 8 МГц * 9 = 72 МГц на PLL
RCC_PLLCmd(ENABLE); // Включаем PLL
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {} // Ждем пока PLL устаканится
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // Выбираем PLL как источник тактового сигнала
while (RCC_GetSYSCLKSource() != 0x08) {} // Ждем пока PLL установится как источник тактирования
}
}
А теперь вспомним, что перед тем как использовать функцию, необходимо ее объявить вначале программы, поэтому в итоге получим вот такой кусок кода, который будет настраивать ваше тактирование. Работать он будет на любом МК серии F10x, так что можете сохранить как библиотеку:
/*************************************** Тактирование процессора **********************************/
void RCC_Configuration(void);
ErrorStatus HSEStartUpStatus;
RCC_ClocksTypeDef RCC_Clocks;
void RCC_Configuration(void)
{
RCC_DeInit(); // Выполняем сброс reset
RCC_HSEConfig(RCC_HSE_ON); // Включаем тактирование HSE (от кварца)
HSEStartUpStatus = RCC_WaitForHSEStartUp(); // Ждем пока частота кварца стабилизируется
if (HSEStartUpStatus == SUCCESS) // Если все отлично, то переходим на кварц
{
RCC_HCLKConfig(RCC_SYSCLK_Div1); // Выставляет делитель в 1 (Div1) и системная частота становится 72 МГц
RCC_PCLK2Config(RCC_HCLK_Div1); // Запитываем APB2 от тактовой частоты PLL в 72 МГц
RCC_PCLK1Config(RCC_HCLK_Div1); // Запитываем APB1 от тактовой частоты PLL в 72 МГц
RCC_ADCCLKConfig(RCC_PCLK2_Div2); // Ставим делитель на 2 (Div2) и получаем частоту 36 МГц вместо входящей 72 МГц
RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_9); // Устанавливаем множитель частоты 9, получаем 8 МГц * 9 = 72 МГц на PLL
RCC_PLLCmd(ENABLE); // Включаем PLL
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {} // Ждем пока PLL устаканится
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // Выбираем PLL как источник тактового сигнала
while (RCC_GetSYSCLKSource() != 0x08) {} // Ждем пока PLL установится как источник тактирования
}
}
Я думаю тактирования я более менее описал подробно, теперь необходимо разбирать периферию.
Работа с портами ввода/вывода
GPIO — это и есть наши порты ввода-вывода. Это основа основ, т.к. перед использованием любой другой периферии придется настраивать именно порты на которые она выведена.
Рассказ о данной периферии я буду вести на примере работы светодиодной индикации в нашей схеме. Из предыдущей статьи мы возьмем подключение:
а) Светодиод №1 (LED1) подключен к PB12
б) Светодиод №2 (LED2) подключен к PB13
в) Светодиод №3 (LED3) подключен к PB14
г) Светодиод №4 (LED4) подключен к PB15
д) Светодиод №5 (LED5) подключен к PС6
Что это значит… У STM-ок есть порты, они имеют 16 выводов от 0 до 15. Правда есть исключения, некоторые порты не всегда имеют 16 ногу, а могут, например, лишь 4 или 5. Обозначение PB12 означает что это порт B и 12-й вывод. Теперь открываем скаченный ранее STM32CubeMX и смотрим где эти ноги находятся и удобно ли нам их будет разводить.
Рисунок 5 — Выбор расположения переферии в программе STM32CubeMX
Огромная прелесть STM в том, что их «гибкость» позволяет использовать любые ноги для ввода-вывода, а вся периферия может быть переведена на альтернативные ноги (запасной вариант), так называемый Remap. Все это позволяет очень качественно, быстро и удобно разводить плату, поэтому новичкам учиться пользоваться всем тем, что дают нам разработчики ST.
Теперь нам необходимо работать с индикацией, то есть зажигать светодиоды в определенных ситуациях. Теперь мы идем и смотрим как это делать, нам надо устанавливать наш вывод в лог.1, т.к. аноды подключены к МК, а катоды с «минусу» схемы. Для этого открываем файл stm32f10x_gpio.h и листаем вниз файла, там список всех доступных функций:
Рисунок 6 — Функции доступные при работе с GPIO
Там мы видим функции установки и сброса состояния вывода:
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
Как работать с такими функциями: копируем ее, видимо что первая запись в скобках GPIO_TypeDef* GPIOx, как понятно вместо «х» необходимо указать порт, у нас это порты В и С, получим GPIOB. Тут вспоминаем функции из начала статьи, которые я просил особо не вкуривать, тут уже пора). Смотрим на вторую часть в скобках uint16_t GPIO_Pin, тут видим переменную GPIO_Pin, так же видим, что тип этой переменной беззнаковая величина размером 16 бит, то есть 216 или же 65536. Но нам столько не нужно, я думаю понятно, что тут нам необходимо указать номер вывода (пина) от 0 до 15. В итоге получим GPIO_Pin_12. Учитывая все это пишем код:
GPIO_SetBits(GPIOB, GPIO_Pin_12); // Зажигаем светодиод № 1
GPIO_SetBits(GPIOB, GPIO_Pin_13); // Зажигаем светодиод № 2
GPIO_SetBits(GPIOB, GPIO_Pin_14); // Зажигаем светодиод № 3
GPIO_SetBits(GPIOB, GPIO_Pin_15); // Зажигаем светодиод № 4
GPIO_SetBits(GPIOC, GPIO_Pin_6); // Зажигаем светодиод № 5
Как видите каждый раз вспоминать к какой ноге мы подключили светодиод, писать лишние буквы, когда код хотя бы в 5 000 строк это уже ой как существенно)) Поэтому вспоминаем замечательное свойство команды #define и модифицируем наш код:
#define LED1_ON GPIO_SetBits(GPIOB, GPIO_Pin_12); // Зажигаем светодиод № 1
#define LED2_ON GPIO_SetBits(GPIOB, GPIO_Pin_13); // Зажигаем светодиод № 2
#define LED3_ON GPIO_SetBits(GPIOB, GPIO_Pin_14); // Зажигаем светодиод № 3
#define LED4_ON GPIO_SetBits(GPIOB, GPIO_Pin_15); // Зажигаем светодиод № 4
#define LED5_ON GPIO_SetBits(GPIOC, GPIO_Pin_6); // Зажигаем светодиод № 5
Теперь незабываем сделать функцию, которая будет еще и выключать наши «лампочки». Для этого в нынешней функции SetBits меняем на ResetBits — думаю тут все предельно ясно. Получаем в итоге такой конечный код:
#define LED1_ON GPIO_SetBits(GPIOB, GPIO_Pin_12); // Зажигаем светодиод № 1
#define LED2_ON GPIO_SetBits(GPIOB, GPIO_Pin_13); // Зажигаем светодиод № 2
#define LED3_ON GPIO_SetBits(GPIOB, GPIO_Pin_14); // Зажигаем светодиод № 3
#define LED4_ON GPIO_SetBits(GPIOB, GPIO_Pin_15); // Зажигаем светодиод № 4
#define LED5_ON GPIO_SetBits(GPIOC, GPIO_Pin_6); // Зажигаем светодиод № 5
#define LED1_OFF GPIO_ResetBits(GPIOB, GPIO_Pin_12); // Гасим светодиод № 1
#define LED2_OFF GPIO_ResetBits(GPIOB, GPIO_Pin_13); // Гасим светодиод № 2
#define LED3_OFF GPIO_ResetBits(GPIOB, GPIO_Pin_14); // Гасим светодиод № 3
#define LED4_OFF GPIO_ResetBits(GPIOB, GPIO_Pin_15); // Гасим светодиод № 4
#define LED5_OFF GPIO_ResetBits(GPIOC, GPIO_Pin_6); // Гасим светодиод № 5
Теперь для простого зажигания светодиода достаточно написать LED1_ON; Чтобы его выключить пишем так же LED1_OFF;
Вроде все просто? А нет! Остался последний момент, которые надо было показать в начале, но он бы половину людей возможно отпугнул бы. Хотя он и простой, но работоспособность зависит именно от него — это инициализация периферии GPIO. Это необходимо, чтобы указать порту откуда брать ему тактовую частоту, на какой работать и в каком режиме. Все это делается в той же библиотеке stm32f10x_gpio.h, но теперь к ней необходимо еще и библиотеку тактирования stm32f10x_rcc.h подключить.
Перед тем как вообще что-то делать с любой периферией необходимо включить ее тактирование, делается это в stm32f10x_rcc.h, идем туда и смотрим какой функцией это сделать, их список так же в конце файла:
Рисунок 7 — Функции тактирования
Тут мы видим знакомые нам APB2 и APB1, это шины к которым подсоединены наши порты. В данном случае и С и В сидят на APB2, эту функция я выделил на скрине. Она простейшая: в первой части необходимо написать название периферии, во второй указать статус. Статуса может быть два: ENABLE (включено) и DISABLE. Хз почему, но принципиально писать большими буквами статус, иначе Кокос не подсветит текст.
Теперь необходимо откуда-то взять правильное название периферии. Поэтому идем в файл библиотеки stm32f10x_rcc.h и жмет Ctrl + F — это поиск по файлу, пишем в него RCC_APB2Periph и жмем Find. И несколько раз тыкаем Find пока не дойдем до такого списка, где указаны все состояния, которые может принимать данная надпись. Выбираем нужную периферию:
Рисунок 8 — Поиск значения функции по библиотеки
И так… как включить тактирование разобрались, получили мы такую строку, вернее 2 строки, т.к. используем два порта В и С:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
Все, с библиотекой тактирования пока закончим и переходим к файлу stm32f10x_gpio.h Идем в конец и ищем функцию инициализации, обычно они называются Init, тут сплошное капитанство если знаешь английский хоть немного. Дальше копируем имя функции GPIO_Init и дальше по стандарту Ctrl + F:
Рисунок 9 — Вот так выглядит функция в списке
Дальше пару раз тыкаем Find пока не дойдем до момента будет описание функции и как она выглядит:
Рисунок 10 — Функция инициализации
так она выглядит и состоит из 3-х составляющих:
а) GPIO_Pin — тут указываем как вывод мы настраиваем
б) GPIO_Speed — тут указываем скорость/частоту максимальную на которой может работать нога контроллера
в) GPIO_Mode — устанавливаем режим работы ноги
Если выделить каждую составляющую, нажать Ctrl + F и вставить и нажать Find, то будет список того, что можно написать каждой составляющей. Теперь пример инициализации для нашего случая:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // Включаем тактирование порта
GPIO_InitTypeDef led; // Даем имя нашей инициализации led
led.GPIO_Pin = (GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15) ; // Указываем выводы которые подчинятся этой настройке
led.GPIO_Speed = GPIO_Speed_2MHz; // Указываем максимальную частоту работы
led.GPIO_Mode = GPIO_Mode_Out_PP; // Указываем тип настройки ног, тут выход push-pull
GPIO_Init(GPIOB, &led); // Запускаем инициализацию
Подробно о настройках можно почитать в даташите или погуглить, часть остальных режимов работы будет встречаться и дальше, поэтому выбирайте сами как будете изучать.
Максимальную частоту я тут поставил 2 МГц, т.к. в таком режиме периферия потребляет чуточку меньше тока. Если эта была бы настройка для SPI, то надо указывать максимальную, чтобы не было ограничения. Думаю тут все понятно, если нет готов ответить в личку.
В итоге после сегодняшней части мы должны получить такой вот код. Он для обучения, но дополняя именно его в дальнейшем мы получим конечный исходник: разобранный и понятный каждому!
#include "stm32f10x_conf.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_spi.h"
#include "TFT ILI9341.h"
#define LED1_ON GPIO_SetBits(GPIOB, GPIO_Pin_12); // Зажигаем светодиод № 1
#define LED2_ON GPIO_SetBits(GPIOB, GPIO_Pin_13); // Зажигаем светодиод № 2
#define LED3_ON GPIO_SetBits(GPIOB, GPIO_Pin_14); // Зажигаем светодиод № 3
#define LED4_ON GPIO_SetBits(GPIOB, GPIO_Pin_15); // Зажигаем светодиод № 4
#define LED5_ON GPIO_SetBits(GPIOC, GPIO_Pin_6); // Зажигаем светодиод № 5
#define LED1_OFF GPIO_ResetBits(GPIOB, GPIO_Pin_12); // Гасим светодиод № 1
#define LED2_OFF GPIO_ResetBits(GPIOB, GPIO_Pin_13); // Гасим светодиод № 2
#define LED3_OFF GPIO_ResetBits(GPIOB, GPIO_Pin_14); // Гасим светодиод № 3
#define LED4_OFF GPIO_ResetBits(GPIOB, GPIO_Pin_15); // Гасим светодиод № 4
#define LED5_OFF GPIO_ResetBits(GPIOC, GPIO_Pin_6); // Гасим светодиод № 5
/****************************************************************************************************************/
/********************** Функции используемы для работы **********************************************************/
/****************************************************************************************************************/
/*************************************** Тактирование процессора **********************************/
void RCC_Configuration(void);
ErrorStatus HSEStartUpStatus;
RCC_ClocksTypeDef RCC_Clocks;
void RCC_Configuration(void)
{
RCC_DeInit(); // Выполняем сброс reset
RCC_HSEConfig(RCC_HSE_ON); // Включаем тактирование HSE (от кварца)
HSEStartUpStatus = RCC_WaitForHSEStartUp(); // Ждем пока частота кварца стабилизируется
if (HSEStartUpStatus == SUCCESS) // Если все отлично, то переходим на кварц
{
RCC_HCLKConfig(RCC_SYSCLK_Div1); // Выставляет делитель в 1 (Div1) и системная частота становится 72 МГц
RCC_PCLK2Config(RCC_HCLK_Div1); // Запитываем APB2 от тактовой частоты PLL в 72 МГц
RCC_PCLK1Config(RCC_HCLK_Div1); // Запитываем APB1 от тактовой частоты PLL в 72 МГц
RCC_ADCCLKConfig(RCC_PCLK2_Div2); // Ставим делитель на 2 (Div2) и получаем частоту 36 МГц вместо входящей 72 МГц
RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_9); // Устанавливаем множитель частоты 9, получаем 8 МГц * 9 = 72 МГц на PLL
RCC_PLLCmd(ENABLE); // Включаем PLL
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {} // Ждем пока PLL устаканится
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // Выбираем PLL как источник тактового сигнала
while (RCC_GetSYSCLKSource() != 0x08) {} // Ждем пока PLL установится как источник тактирования
}
}
/************************** Основное тело программы ***********************************************/
/**************************************************************************************************/
/**************************************************************************************************/
int main(void)
{
RCC_GetClocksFreq (&RCC_Clocks);
RCC_Configuration();
RCC_GetClocksFreq (&RCC_Clocks);
/************* Настройка портов *****************************************/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // Включаем тактирование порта
GPIO_InitTypeDef led; // Даем имя нашей инициализации led
led.GPIO_Pin = (GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15) ; // Указываем выводы которые подчинятся этой настройке
led.GPIO_Speed = GPIO_Speed_2MHz; // Указываем максимальную частоту работы
led.GPIO_Mode = GPIO_Mode_Out_PP; // Указываем тип настройки ног, тут выход push-pull
GPIO_Init(GPIOB, &led); // Запускаем инициализацию
while(1)
{
LED1_ON;
LED2_OFF;
LED3_ON;
LED4_ON;
LED5_OFF;
}
}
В итоге мы научились настраивать тактирование от кварца для высокой точности, разобрали как пользоваться библиотеками SPL и каким образом настраивать GPIO и пользоваться ими. Так же упомянули основные функции аля дефайн и инклуд, с разбором их применения в реальном коде.
Эпилог
Да уж, статейка действительно напрягла мне голову, т.к. сенсей из меня плохой, а рассказать подробно о прошивке просили. Надеюсь у меня получилось и те, кто только планируют начать изучение программирования микроконтроллеров смогу понять меня, мои описания и совету.
Так получилось, что это часть не последняя, будет еще 4.3, где мы разберем оставшуюся периферию и допишем программу. Не хотел я делать кучу подразделов, но получается так, что нельзя рассказать даже о достаточно простом коде для одной платки в объеме одно статьи.
Статья 4.3 с окончанием программного кода выйдет до Нового года, части с силовой схемотехникой будут уже в следующем году. Правда небольшая плюшка все таки будет — до НГ я планирую опубликовать еще одну статью не посвященную данному циклу про ИБП, но все таки имеющая некоторое отношение в плане схемотехники.
Статья будет называться — «Изготавливаем импульсный лабораторный блок питания 0–60В и 20А». Прототип у меня готов, обкатан и готов показаться в свет.
Пожалуй у меня все и просьба к классным специалистам оставлять комментарии с учетом, что это обучающая статья начального уровня и все мои разборки кода будут именно такими. Заранее благодарю!