Мы наблюдаем общество, которое все больше зависит от машин, но при этом использует их все неэффективнее. — 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/
Добавить комментарий