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