Bueno, un último empujon para poder hacer nuestro hola mundo.
Lo primero que quiero comentar es que al crear un proyecto con la std perip lib se nos ha agregado al proyecto 2 librerías. Una es la CMSIS, la cual tiene todos los registros de nuestro micro. Tambien hay funciones de inicializacion y configuracion de reloj.
La forma de incluir las CMSIS es haciendo
#include "stm32f4xx.h"
En el proyecto nuestro se han creado varios archivos aparte del main.c Entre ellos encontramos el startup_stm32f411xe.s el cual es el archivo con el código que se ejecuta al arrancar el micro.
Dentro de ese archivo encontramos:
/**
* @brief This is the code that gets called when the processor first
* starts execution following a reset event. Only the absolutely
* necessary set is performed, after which the application
* supplied main() routine is called.
* @param None
* @retval : None
*/
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
ldr sp, =_estack /* set stack pointer */
/* Copy the data segment initializers from flash to SRAM */
movs r1, #0
b LoopCopyDataInit
CopyDataInit:
ldr r3, =_sidata
ldr r3, [r3, r1]
str r3, [r0, r1]
adds r1, r1, #4
LoopCopyDataInit:
ldr r0, =_sdata
ldr r3, =_edata
adds r2, r0, r1
cmp r2, r3
bcc CopyDataInit
ldr r2, =_sbss
b LoopFillZerobss
/* Zero fill the bss segment. */
FillZerobss:
movs r3, #0
str r3, [r2], #4
LoopFillZerobss:
ldr r3, = _ebss
cmp r2, r3
bcc FillZerobss
/* Call the clock system intitialization function.*/
bl SystemInit
/* Call static constructors */
bl __libc_init_array
/* Call the application's entry point.*/
bl main
bx lr
.size Reset_Handler, .-Reset_Handler
Al final del código vemos que se llama a SystemInit y finalmente al main.
SystemInit es una funcion de otro archivo que se ha agregado a nuestro poryecto: system_stm32f4xx.c
En dicha funcion se configura el oscillador principal y se configura la flash y la ram.
O sea que con ese código agregado a nuestro proyecto tenemos lo mínimo indispensable para que el micro arranque y ejecute el main, pero nada mas que eso. Si queremos usar el GPIO deberemos encenderlo, lo mismo con cada modulo que este apagado por defecto.
Bien, concentrémonos en el GPIO entonces.
Si nos vamos al reference manual (p 142 en mi caso) y buscamos el apartado del GPIO veremos que registros tenemos y como se usan.
Lamentablemente este user manual no nos previene de que para usar el GPIO primero debemos habilitar el clock a este. Eso lo vemos en el capítulo 6, específicamente en "6.3.9 RCC AHB1 peripheral clock enable register". En el caso de los user manual de NXP, siempre como primer parte de un capítulo explica lo que hay que preconfigurar para que ese modulo funcione. En ST parece no ser así y es algo malísimo, obligarme a leer todo para descubrir que hay que hacer para que funcione un modulo ufffff!
Bueno, revisando ese registro, vemos que tiene un bit denominado GPIOAEN, que poniendolo en 1 le damos reloj al puerto y que en reset esta en 0.
Bien, las CMSIS deben tener esos registros en algún #define. Y efectivamente así es. En la carpeta device de las CMSIS encontramos el archivo stm32f4xx.h dentro del cual tenemos el registro RCC:
#define RCC ((RCC_TypeDef *) RCC_BASE)
la direccion RCC_BASE es:
#define RCC_BASE (AHB1PERIPH_BASE + 0x3800)
por supuesto en otra parte esta definida la direccion del AHB1
En el reference manual habla de registro RCC AHB1ENR. Esto esta codificado en el archivo stm32f4xx.h como una estructura:
/**
* @brief Reset and Clock Control
*/
typedef struct
{
__IO uint32_t CR; /*!< RCC clock control register, Address offset: 0x00 */
__IO uint32_t PLLCFGR; /*!< RCC PLL configuration register, Address offset: 0x04 */
__IO uint32_t CFGR; /*!< RCC clock configuration register, Address offset: 0x08 */
__IO uint32_t CIR; /*!< RCC clock interrupt register, Address offset: 0x0C */
__IO uint32_t AHB1RSTR; /*!< RCC AHB1 peripheral reset register, Address offset: 0x10 */
__IO uint32_t AHB2RSTR; /*!< RCC AHB2 peripheral reset register, Address offset: 0x14 */
__IO uint32_t AHB3RSTR; /*!< RCC AHB3 peripheral reset register, Address offset: 0x18 */
uint32_t RESERVED0; /*!< Reserved, 0x1C */
__IO uint32_t APB1RSTR; /*!< RCC APB1 peripheral reset register, Address offset: 0x20 */
__IO uint32_t APB2RSTR; /*!< RCC APB2 peripheral reset register, Address offset: 0x24 */
uint32_t RESERVED1[2]; /*!< Reserved, 0x28-0x2C */
__IO uint32_t AHB1ENR; /*!< RCC AHB1 peripheral clock register, Address offset: 0x30 */
__IO uint32_t AHB2ENR; /*!< RCC AHB2 peripheral clock register, Address offset: 0x34 */
__IO uint32_t AHB3ENR; /*!< RCC AHB3 peripheral clock register, Address offset: 0x38 */
uint32_t RESERVED2; /*!< Reserved, 0x3C */
__IO uint32_t APB1ENR; /*!< RCC APB1 peripheral clock enable register, Address offset: 0x40 */
__IO uint32_t APB2ENR; /*!< RCC APB2 peripheral clock enable register, Address offset: 0x44 */
uint32_t RESERVED3[2]; /*!< Reserved, 0x48-0x4C */
__IO uint32_t AHB1LPENR; /*!< RCC AHB1 peripheral clock enable in low power mode register, Address offset: 0x50 */
__IO uint32_t AHB2LPENR; /*!< RCC AHB2 peripheral clock enable in low power mode register, Address offset: 0x54 */
__IO uint32_t AHB3LPENR; /*!< RCC AHB3 peripheral clock enable in low power mode register, Address offset: 0x58 */
uint32_t RESERVED4; /*!< Reserved, 0x5C */
__IO uint32_t APB1LPENR; /*!< RCC APB1 peripheral clock enable in low power mode register, Address offset: 0x60 */
__IO uint32_t APB2LPENR; /*!< RCC APB2 peripheral clock enable in low power mode register, Address offset: 0x64 */
uint32_t RESERVED5[2]; /*!< Reserved, 0x68-0x6C */
__IO uint32_t BDCR; /*!< RCC Backup domain control register, Address offset: 0x70 */
__IO uint32_t CSR; /*!< RCC clock control & status register, Address offset: 0x74 */
uint32_t RESERVED6[2]; /*!< Reserved, 0x78-0x7C */
__IO uint32_t SSCGR; /*!< RCC spread spectrum clock generation register, Address offset: 0x80 */
__IO uint32_t PLLI2SCFGR; /*!< RCC PLLI2S configuration register, Address offset: 0x84 */
__IO uint32_t PLLSAICFGR; /*!< RCC PLLSAI configuration register, Address offset: 0x88 */
__IO uint32_t DCKCFGR; /*!< RCC Dedicated Clocks configuration register, Address offset: 0x8C */
} RCC_TypeDef;
como habran notado, este type_def es el que se uso para definir el RCC, por lo tanto podemos acceder al registro AHB1ENR como un miembro de RCC. Dentro de ese registro tenemos los ditintos bits. Los nombre de cada bit tambien estan definidos:
/******************** Bit definition for RCC_AHB1ENR register ***************/
#define RCC_AHB1ENR_GPIOAEN ((uint32_t)0x00000001)
#define RCC_AHB1ENR_GPIOBEN ((uint32_t)0x00000002)
#define RCC_AHB1ENR_GPIOCEN ((uint32_t)0x00000004)
#define RCC_AHB1ENR_GPIODEN ((uint32_t)0x00000008)
#define RCC_AHB1ENR_GPIOEEN ((uint32_t)0x00000010)
#define RCC_AHB1ENR_GPIOFEN ((uint32_t)0x00000020)
#define RCC_AHB1ENR_GPIOGEN ((uint32_t)0x00000040)
#define RCC_AHB1ENR_GPIOHEN ((uint32_t)0x00000080)
#define RCC_AHB1ENR_GPIOIEN ((uint32_t)0x00000100)
#define RCC_AHB1ENR_GPIOJEN ((uint32_t)0x00000200)
#define RCC_AHB1ENR_GPIOKEN ((uint32_t)0x00000400)
#define RCC_AHB1ENR_CRCEN ((uint32_t)0x00001000)
#define RCC_AHB1ENR_BKPSRAMEN ((uint32_t)0x00040000)
#define RCC_AHB1ENR_CCMDATARAMEN ((uint32_t)0x00100000)
#define RCC_AHB1ENR_DMA1EN ((uint32_t)0x00200000)
#define RCC_AHB1ENR_DMA2EN ((uint32_t)0x00400000)
#define RCC_AHB1ENR_DMA2DEN ((uint32_t)0x00800000)
#define RCC_AHB1ENR_ETHMACEN ((uint32_t)0x02000000)
#define RCC_AHB1ENR_ETHMACTXEN ((uint32_t)0x04000000)
#define RCC_AHB1ENR_ETHMACRXEN ((uint32_t)0x08000000)
#define RCC_AHB1ENR_ETHMACPTPEN ((uint32_t)0x10000000)
#define RCC_AHB1ENR_OTGHSEN ((uint32_t)0x20000000)
#define RCC_AHB1ENR_OTGHSULPIEN ((uint32_t)0x40000000)
Bueno, todo esto es para ponerno en tema y entender la siguiente instruccion:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
Una vez que el puerto A recibe pulsos de reloj podemos empezar a configrar cada pin. En mi caso tengo un LED conectado al Pin 5 del puerto A.
Si volvemos al apartado GPIO del user manual tenemos:
"GPIO introduction
Each general-purpose I/O port has four 32-bit configuration registers (GPIOx_MODER,
GPIOx_OTYPER, GPIOx_OSPEEDR and GPIOx_PUPDR), two 32-bit data registers
(GPIOx_IDR and GPIOx_ODR), a 32-bit set/reset register (GPIOx_BSRR), a 32-bit locking
register (GPIOx_LCKR) and two 32-bit alternate function selection register (GPIOx_AFRH
and GPIOx_AFRL)."
o sea que nos presenta los registros a usar para manejar los pines como GPIO. Mas abajo tenemos una tabla para ver como configurar cada registro para cada funcion de cada pin.

aqui vemos como configurar el pin como salida, push-pull, sin PU ni PD y con cierta velocidad.
Si seguimos avanzando en el RM vemos el apartado 8.3.3 I/O port control registers con una explicacion adicional de los registro que ya vimos.
Si seguimos adelante vemos el apartado 8.4 GPIO registers que nos va a servir para ver bien los bits a tocar de cada registro.
Vemos que tenemos el registro MODER que se forma por 2 bit para cada pin del puerto con el siguiente significado:
Bits 2y:2y+1 MODERy[1:0]: Port x configuration bits (y = 0..15)
These bits are written by software to configure the I/O direction mode.
00: Input (reset state)
01: General purpose output mode
10: Alternate function mode
11: Analog mode
O sea que debemos cambiar a 01 los bits 10 y 11 del registro GPIOA_MODER para poder poner el pin A5 como salida.
Por supuesto que tenemos las definiciones de los bits:
/******************************************************************************/
/* */
/* General Purpose I/O */
/* */
/******************************************************************************/
/****************** Bits definition for GPIO_MODER register *****************/
#define GPIO_MODER_MODER0 ((uint32_t)0x00000003)
#define GPIO_MODER_MODER0_0 ((uint32_t)0x00000001)
#define GPIO_MODER_MODER0_1 ((uint32_t)0x00000002)
#define GPIO_MODER_MODER1 ((uint32_t)0x0000000C)
#define GPIO_MODER_MODER1_0 ((uint32_t)0x00000004)
#define GPIO_MODER_MODER1_1 ((uint32_t)0x00000008)
#define GPIO_MODER_MODER2 ((uint32_t)0x00000030)
#define GPIO_MODER_MODER2_0 ((uint32_t)0x00000010)
#define GPIO_MODER_MODER2_1 ((uint32_t)0x00000020)
#define GPIO_MODER_MODER3 ((uint32_t)0x000000C0)
#define GPIO_MODER_MODER3_0 ((uint32_t)0x00000040)
#define GPIO_MODER_MODER3_1 ((uint32_t)0x00000080)
#define GPIO_MODER_MODER4 ((uint32_t)0x00000300)
#define GPIO_MODER_MODER4_0 ((uint32_t)0x00000100)
#define GPIO_MODER_MODER4_1 ((uint32_t)0x00000200)
#define GPIO_MODER_MODER5 ((uint32_t)0x00000C00)
#define GPIO_MODER_MODER5_0 ((uint32_t)0x00000400)
#define GPIO_MODER_MODER5_1 ((uint32_t)0x00000800)
todo en el mismo stm32f4xx.h
De este modo, con las siguientes instrucciones:
GPIOA->MODER &= ~GPIO_MODER_MODER5;
GPIOA->MODER |= GPIO_MODER_MODER5_0;
habilitamos al pin A5 como salida.
luego sigue el registro GPIOA_OTYPER el cual se configura así:
Bits 15:0 OTy: Port x configuration bits (y = 0..15)
These bits are written by software to configure the output type of the I/O port.
0: Output push-pull (reset state)
1: Output open-drain
como lo quiero PP no debo tocar nada.
Luego tenemos GPIOA_OSPEEDR el cual puede tener los siguientes valores:
Bits 2y:2y+1 OSPEEDRy[1:0]: Port x configuration bits (y = 0..15)
These bits are written by software to configure the I/O output speed.
00: Low speed
01: Medium speed
10: Fast speed
11: High speed
como quiero usar el Low speed dejo ese registro sin tocar.
Finalmente tenemos GPIOA_PUPDR el cual puede tomar:
Bits 2y:2y+1 PUPDRy[1:0]: Port x configuration bits (y = 0..15)
These bits are written by software to configure the I/O pull-up or pull-down
00: No pull-up, pull-down
01: Pull-up
10: Pull-down
11: Reserved
Como es salida y no es OD no tiene sentido este registro y lo dejo en 0 sin PU ni PD
Ok, ya tenemos un pin como salida. Tratemos de poner la misma a 1.
Para eso tenemos el 8.4.7 GPIO port bit set/reset register (GPIOx_BSRR) el cual funciona de la siguiente forma:
Bits 31:16 BRy: Port x reset bit y (y = 0..15)
These bits are write-only and can be accessed in word, half-word or byte mode. A read to these bits returns the value 0x0000.
0: No action on the corresponding ODRx bit
1: Resets the corresponding ODRx bit
Note: If both BSx and BRx are set, BSx has priority.
Bits 15:0 BSy: Port x set bit y (y= 0..15)
These bits are write-only and can be accessed in word, half-word or byte mode. A read to these bits returns the value 0x0000.
0: No action on the corresponding ODRx
1: Sets the corresponding ODRx bit
O sea que tenemos los primeros 16 bits para hacer un SET en el pin y los segundos 16 bits para hacer un reset
Con la instruccion
GPIOA->BSRRL = GPIO_BSRR_BS_5;
Encendemos el led, y con
GPIOA->BSRRH = GPIO_BSRR_BS_5;
lo apagamos.
Aqui no hay que hacer uso del |= ya que los ceros que se escriben no afectan a los bits que ya estaban seteados. Solo se setean denuevo los indicados en la nueva instruccion.
Para cambiar el estado de un pin podemos usar el registro GPIOA_ODR el cual contiene los 16 bits del puerto A. El toggle se consigue con
GPIOA->ODR ^= GPIO_BSRR_BS_5;
finalmente para hacer un delay podemos usar:
for(i=0;i<5000000;i++);
Entonces el código de nuestro programa quedará:
#include "stm32f4xx.h"
int main(void)
{
int i=0;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
GPIOA->MODER &= ~GPIO_MODER_MODER5;
GPIOA->MODER |= GPIO_MODER_MODER5_0;
GPIOA->BSRRL = GPIO_BSRR_BS_5;
while (1)
{
GPIOA->ODR ^= GPIO_BSRR_BS_5;;
for(i=0;i<5000000;i++);
}
}
bueno, la prueba del funcionamiento:
Primero voy ejecutando paso a paso hasta que el LED se enciende. Luego lo dejo correr libremente.
En la próxima veremos como se simplifica, o no, usando las standar librery para finalmente pasar a HAL o lo que se conoce como STM Cube (no MX)
Saludos!