STM32 Часть 3: Первый Проект

от автора

Мы наблюдаем общество, которое все больше зависит от машин, но при этом использует их все неэффективнее. — Douglas Rushkoff

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

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

RCC — Reset and Clock Control, блок регистров которые управляют тактированием процессора и периферии. Этим блоком управляются несколько источников тактирования: HSI (High speed internal), HSE (high speed external), PLL/PLLSAI/PLLI2S (Phased loop lock). Все эти три источника тактовой частоты могут быть использованы процессором, конечно же при правильной настройки регистров блока RCC. Об этом мы поговорим подробно в следующей статье.

GPIO — General Purpose Input and Output. Блок регистров который создан для мигания светодиодами.То есть это блок для управления ногами нашего мк, портал в реальный мир. В отличии от всем знакомого Atmega328p (Arduino), stm32 предлагает более широкую настройку ножек. К примеру функция ноги Open Drain не доступна на меге. Также контроль скорости, с которой мк поднимает вольтаж на ноге, может быть настроен.

Итак, давайте уже мигнем, да. Для начала нам понадобиться два хедера в которым мы опишем структуру этих регистров. Поэтому достаем референс мануалы и штудируем его в поисках нужной нам информации. Я работаю на STM32F746NG — Discovery поэтому весь код под эту плату.

Первое что нам понадобиться это адреса этих блоков, базовые. Начнем с блока RCC, и создадим хедер rcc.h. В нем мы создадим macro RCC_BASE, а также опишем структуру блока регистров, и создадим указатель на структуру.

rcc.h

#define RCC_BASE   0x40023800  typedef struct {   volatile unsigned int CR;              volatile unsigned int PLLCFGR;         volatile unsigned int CFGR;            volatile unsigned int CIR;             volatile unsigned int AHB1RSTR;        volatile unsigned int AHB2RSTR;        volatile unsigned int AHB3RSTR;        unsigned int          RESERVED0;       volatile unsigned int APB1RSTR;        volatile unsigned int APB2RSTR;        unsigned int          RESERVED1[2];    volatile unsigned int AHB1ENR;         volatile unsigned int AHB2ENR;         volatile unsigned int AHB3ENR;         unsigned int          RESERVED2;       volatile unsigned int APB1ENR;         volatile unsigned int APB2ENR;         unsigned int          RESERVED3[2];    volatile unsigned int AHB1LPENR;       volatile unsigned int AHB2LPENR;       volatile unsigned int AHB3LPENR;       unsigned int          RESERVED4;       volatile unsigned int APB1LPENR;       volatile unsigned int APB2LPENR;       unsigned int          RESERVED5[2];    volatile unsigned int BDCR;            volatile unsigned int CSR;             unsigned int          RESERVED6[2];    volatile unsigned int SSCGR;           volatile unsigned int PLLI2SCFGR;      volatile unsigned int PLLSAICFGR;      volatile unsigned int DCKCFGR1;        volatile unsigned int DCKCFGR2;       } RCC_Struct;  #define RCC ((RCC_Struct *) RCC_BASE) 

Давайте проведем такую же операцию с блоком регистров GPIO, тролько без указателя и добавим еще одно макро GPIO_OFFSET. о нем поговорим ниже.

gpio.h

#define GPIO_BASE   0x40020000 #define GPIO_OFFSET 0x400  typedef struct {   volatile uint32_t MODER;      volatile uint32_t OTYPER;     volatile uint32_t OSPEEDR;    volatile uint32_t PUPDR;      volatile uint32_t IDR;        volatile uint32_t ODR;        volatile uint32_t BSRR;       volatile uint32_t LCKR;       volatile uint32_t AFR[2];    } GPIO_Struct;

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

RCC->AHB1ENR = 0; 

Это экономит место в памяти, а иногда и вообще его не требует. Но об экономии не сегодня.

Так два хедера у нас готовы, осталось узнать как дрыгнуть ногой при помощи этих регистров и создать int main();. Тут все просто и не совсем. В STM32 для доступа к блоку регистров мы должны сначала подать на него тактирование, а иначе как данные до него доедут. Я не буду сейчас углубляться в строение шины, а просто скажу как есть. Блоки размещены на разных шинах. Наш блок находиться на шине AHB1. То есть нам надо включить определеный порт на шине AHB1, в моем случае это порт I. Для начала нам конечно понадобиться функция main.

Давайте немного обновим наш стартап файл и добавим в него int main();. а после создадим и сам файл main.c

extern void *_estack;  void Reset_Handler(); void Default_Handler();  // Форвард декларация тут int  main();  void NMI_Handler()                    __attribute__ ((weak, alias ("Default_Handler"))); void HardFault_Handler()              __attribute__ ((weak, alias ("Default_Handler")));  void *vectors[] __attribute__((section(".isr_vector"), used)) = {     &_estack, 	&Reset_Handler, 	&NMI_Handler, 	&HardFault_Handler };  extern void *_sidata, *_sdata, *_edata, *_sbss, *_ebss;    void __attribute__((naked, noreturn)) Reset_Handler() {   	void **pSource, **pDest; 	for (pSource = &_sidata, pDest = &_sdata; pDest != &_edata; pSource++, pDest++) 		*pDest = *pSource;  	for (pDest = &_sbss; pDest != &_ebss; pDest++) 		*pDest = 0;      //Мейн фнкция добавлена тут     main();  	while(1); }  void __attribute__((naked, noreturn)) Default_Handler(){     while(1); } 

А теперь создаем и сам файл main.c. В файле я постарался расписать все в комментариях к коду, поэтому читаем и вникаем. если есть вопросы пишите в коменты ниже я отвечу.

#include "rcc.h" #include "gpio.h"   int main() {     //Включаем тактирование GPIO I порта, порт номер 8 (очень важно)     RCC->AHB1ENR |= (1<<8);      //Структура для обращения к регистрам порта     //Используем офсет умноженый на номер порта что дает нам адресс порта     //В дальнейшем нам такой подход очень пригодится     volatile GPIO_Struct *GPIOI = (GPIO_Struct *)(GPIO_BASE + (GPIO_OFFSET*8));        //Далее настраиваем режим ножки на "Выход"     GPIOI->MODER |= (1<<2);      //Теперь указываем тип выхода, в данном примере, push-pull, необходимости указывать нет.     //По умолчанию стоит push pull     GPIOI->OTYPER &= ~(1<<1);      //Скорость с которой будет происходить переключение ножки, очень важный парметр при работе с переферией     GPIOI->OSPEEDR |= (2<<2);      //И теперь наш цикл     while(1){         for(volatile int i = 0; i < 1000000; i++){             // Задержка         }          //Перпеключаем светодиод, если был 1 то будет 0 и наоброт.         GPIOI->ODR ^= (1<<1);     }      return 0; }

Теперь бы нам собрать проект и закинуть его на мк, проверки ради. Я выложил репозиторий на Github все что нужно сделать так это запустить Make утилиту.

make

Всем спасибо за внимание, в следующей статье подробнее поговорим о блоке RCC и как с ним работать.

ссылка на оригинал статьи https://habr.com/ru/post/491406/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *