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

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

© Habrahabr.ru