STM32 Parte 3: Primer proyecto

Observamos una sociedad que depende cada vez más de las máquinas, pero al mismo tiempo las usa de manera más ineficiente. - Douglas Rushkoff


Esta frase debería servir como motivación para cada programador. Después de todo, es usted quien decide cómo la máquina usa sus recursos. Pero desde el principio de los tiempos, una persona confía su derecho a decidir a terceros a cambio de la manera fácil. Antes de preguntarme sobre los beneficios de mis artículos cuando hay un "Cubo", pregúntate por qué el "cubo" decide por mí.

STM32 Parte 1: Conceptos básicos de
STM32 Parte 2: Inicialización

Entonces, continuemos nuestra aventura. Ya hemos escrito el script de inicialización, descubrimos el enlazador y el compilador. Es hora de parpadear el LED. En este artículo, repasaremos los conceptos básicos del bloque RCC y GPIO, y agregaremos un par de encabezados, que utilizaremos en futuros proyectos. Vamos.

RCC - Reset and Clock Control, un bloque de registros que controlan el reloj del procesador y los periféricos. Esta unidad controla varias fuentes de reloj: HSI (alta velocidad interna), HSE (alta velocidad externa), PLL / PLLSAI / PLLI2S (bloqueo de bucle por fases). El procesador puede utilizar estas tres fuentes de reloj, por supuesto, si los registros de bloque RCC están configurados correctamente. Hablaremos de esto en detalle en el próximo artículo.

GPIO - Entrada y salida de uso general. El bloque de registros que se crea para parpadear los LED, es decir, es un bloque para controlar las patas de nuestras micras, un portal al mundo real. A diferencia del familiar Atmega328p (Arduino), stm32 ofrece una personalización más amplia de las patas. Por ejemplo, la función Open Drain Foot no está disponible en el mega. Además, se puede configurar el control de la velocidad con la que el Reino Unido eleva el voltaje en la pata.

Entonces, parpadeemos ya, sí. Primero necesitamos dos encabezados en los que describimos la estructura de estos registros. Por lo tanto, obtenemos el manual de referencia y lo estudiamos en busca de la información que necesitamos. Trabajo en STM32F746NG - Discovery, así que todo el código para esta placa.

Lo primero que necesitamos son las direcciones de estos bloques, básicas. Comencemos con el bloque RCC y creemos el encabezado rcc.h. En él crearemos la macro RCC_BASE, y también describiremos la estructura del bloque de registro, y crearemos un puntero a la estructura.

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)


Hagamos la misma operación con el bloque de registro GPIO, solo sin un puntero y agreguemos otra macro, GPIO_OFFSET. Hablemos de eso a continuación.

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;


Veamos cuál es el problema aquí y por qué necesitamos estructuras. El hecho es que podemos acceder al registro a través de un puntero a una estructura, no necesitamos inicializarlo ya que físicamente ya existe. Por ejemplo:

RCC->AHB1ENR = 0; 


Esto ahorra espacio en la memoria y, a veces, no lo requiere en absoluto. Pero no se trata de ahorrar hoy.

Así que tenemos dos encabezados listos, queda por aprender cómo saltar con la ayuda de estos registros y crear int main();. Todo es simple y no del todo. En STM32, para acceder al bloque de registro, primero debemos aplicarle un reloj, de lo contrario los datos lo alcanzarán. No profundizaré en la estructura de la llanta, solo diré como es. Los bloques se colocan en diferentes neumáticos. Nuestra unidad está ubicada en el autobús AHB1. Es decir, necesitamos habilitar un puerto específico en el bus AHB1, en mi caso es el puerto I. Para empezar, por supuesto, necesitamos la función principal.

Actualicemos un poco nuestro archivo de inicio y agreguemos int main (); y luego crea el archivo 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);
}


Y ahora creamos el archivo main.c. En el archivo, traté de pintar todo en los comentarios del código, así que lo leímos y profundizamos en él. Si tiene preguntas, escriba en los comentarios a continuación, responderé.

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


Ahora tendríamos que recopilar el proyecto y lanzarlo en micras, en aras de la verificación. Publiqué un repositorio en Github, todo lo que hay que hacer es ejecutar la utilidad Make.
make


Gracias a todos por su atención, en el próximo artículo hablaremos más sobre la unidad RCC y cómo trabajar con ella.

All Articles