STM32 Part 3: First Project

We observe a society that is increasingly dependent on machines, but at the same time uses them all more inefficiently. - Douglas Rushkoff


This phrase should serve as a motivation for every programmer. After all, it is you who decide how the machine uses its resources. But as from the beginning of time, a person entrusts his right to decide to third parties in return for the easy way. Before asking me about the benefits of my articles when there is a โ€œCubeโ€, ask yourself why the โ€œcubeโ€ decides for me.

STM32 Part 1:
STM32 Basics Part 2: Initialization

So, let's continue our adventure. We have already written the initialization script, figured out the linker and the compiler. It is time to blink the LED. In this article, we will go over the basics of the RCC and GPIO block, and add a couple of headers, which we will use in future projects. Go.

RCC - Reset and Clock Control, a block of registers that control the clock of the processor and peripherals. Several clock sources are controlled by this unit: HSI (High speed internal), HSE (high speed external), PLL / PLLSAI / PLLI2S (Phased loop lock). All three of these clock sources can be used by the processor, of course, if the RCC block registers are configured correctly. We will talk about this in detail in the next article.

GPIO - General Purpose Input and Output. The block of registers that is created for flashing LEDs. That is, it is a block for controlling the legs of our microns, a portal to the real world. Unlike the familiar Atmega328p (Arduino), stm32 offers a wider customization of the legs. For example, the Open Drain foot function is not available on the mega. Also, the control of the speed with which the uk raises the voltage on the leg can be configured.

So let's blink already, yes. First we need two headers in which we describe the structure of these registers. Therefore, we get the reference manual and study it in search of the information we need. I work on STM32F746NG - Discovery so all the code for this board.

The first thing we need is the addresses of these blocks, basic. Let's start with the RCC block and create the rcc.h header. In it we will create macro RCC_BASE, and also describe the structure of the register block, and create a pointer to the structure.

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)


Let's do the same operation with the GPIO register block, only without a pointer and add another macro, GPIO_OFFSET. talk about it below.

gpio.h

#define GPIO_BASE   0x40020000
#define GPIO_OFFSET 0x400

typedef struct
{
   volatile unsigned int MODER;   
   volatile unsigned int OTYPER;  
   volatile unsigned int OSPEEDR; 
   volatile unsigned int PUPDR;   
   volatile unsigned int IDR;     
   volatile unsigned int ODR;     
   volatile unsigned int BSRR;    
   volatile unsigned int LCKR;    
   volatile unsigned int AFR[2];  

} GPIO_Struct;


Let's see what is the matter here and why do we need structures. The fact is that we can access the register through a pointer to a structure, we do not need to initialize it since physically it already exists. For example:

RCC->AHB1ENR = 0; 


This saves memory space, and sometimes does not require it at all. But not about saving today.

So we have two headers ready, it remains to learn how to jump with the help of these registers and create int main();. It's all simple and not quite. In STM32, to access the register block, we must first apply a clock to it, otherwise the data will reach it. I will not go deep into the structure of the tire, but just say as it is. Blocks are placed on different tires. Our unit is located on the AHB1 bus. That is, we need to enable a specific port on the AHB1 bus, in my case it is port I. For starters, of course we need the main function.

Let's update our startup file a bit and add int main (); to it. and then create the main.c file itself

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);
}


And now we create the main.c. file itself In the file, I tried to paint everything in the comments to the code, so we read and delve into it. If you have questions, write in the comments below I will answer.

#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;
}


Now we would have to collect the project and throw it on microns, for the sake of verification. I posted a repository on Github all that needs to be done is to run the Make utility.
make


Thank you all for your attention, in the next article we will talk more about the RCC unit and how to work with it.

All Articles