28 de junio de 2015

GPIO STM32F4 Discovery

Como dije anteriormente, me dispongo a compartir el primer ejemplo de uso de la placa STM32F4 Discovery, que como era de esperar, se trata del popular "Hola mundo!" introductorio a las clases de programación en C, pero en esta ocasión quienes "dirán hola" serán los leds de los que dispone la placa, junto con el botón también disponible en esta, para dar a conocer así la configuración de los puertos de entrada y salida. ¡Al lío!

En esta ocasión, se trata de un código corto y muy sencillo, por lo que lo pondré todo por aquí. Para cuando el código sea mas largo o tenga muchas librerías, haré hincapié en la entrada en los aspectos mas interesantes, y finalmente dejaré disponible en Dropbox o algún servidor tipo GitHub el proyecto al completo, para que simplemente haya que importarlo al IDE SW4STM32.

El main de nuestro programa se encargará, mediante polling sobre el botón de usuario, de cambiar la secuencia de iluminación de los leds.
Lo primero que haremos será configurar la interrupción SysTick_Handler que nos servirá para realizar delays que nos permitan modificar el estado de los leds y de tiempo a verlo. Aquí podemos ver la función de inicialización:

1
2
3
4
5
6
7
void SysTick_Configure (void)
{
    RCC_ClocksTypeDef RCC_Clocks;

    RCC_GetClocksFreq (&RCC_Clocks);
    SysTick_Config (RCC_Clocks.HCLK_Frequency / 1000);
}

Dicha interrupción llama a una función que se encarga de decrementar el valor que se le pasa por la función Delay.

1
2
3
4
void SysTick_Handler(void)
{
    TimingDelay_Decrement ();
}

Esta es la opción "por defecto" para generar delays al tener implementada esta interrupción. Otra mas sencilla seria mediante bucles while o for, pero la primera opción permite tener un control mas exacto del tiempo. En este caso la interrupción SysTick_Handler se ejecutará cada ms (esto se puede variar cambiando el valor que se le pasa a SysTic_Config, por ejemplo, dividiendo por 1000000, se ejecutaría cada us). Las funciones aquí mencionadas son:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void Delay (uint32_t nTime)
{
    TimingDelay = nTime;

    while(TimingDelay != 0);
}

void TimingDelay_Decrement (void)
{
    if (TimingDelay != 0x00)
    {
        TimingDelay--;
    }
}

Tras esto realizaremos la configuración de los puertos de entrada/salida mediante la función GPIO_Configure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void GPIO_Configure (void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    /* User button */
    RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
    GPIO_Init (GPIOA, &GPIO_InitStructure);

    /* Leds */
    RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOD, ENABLE);

                  /* LED4 Green | LED3 Orange |  LED5 Red   | LED6  Blue */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init (GPIOD, &GPIO_InitStructure);
}

Para ello activamos el reloj de los periféricos GPIOA (para el botón de usuario) y GPIOD (para los leds) mediante la función RCC_xxxxPeriphClockCmd, donde "xxxx" corresponde al bus (AHB1, AHB2, AHB3,  APB1, APB2). Vuelvo a colocar la imagen del núcleo del microcontrolador donde se pueden apreciar estos buses:


A continuación rellenamos las estructuras de configuración de los puertos. Los distintos modos de configuración se pueden ver en el archivo de librería stm32f4xx_gpio.h, siendo los nuestros:

Botón de usuario:
  • Se encuentra conectado al pin 0 del puerto A (GPIO_Pin_0).
  • Se ha configurado en modo entrada (GPIO_Mode_IN).
  • Se ha usado la velocidad mas alta (GPIO_Speed_100MHz).
  • La configuración de la salida no es critica, puesto que es una entrada, estando configurado en push pull (GPIO_OType_PP).
  • Se le ha conectado una resistencia de pull down (GPIO_PuPd_DOWN), debido a la conexión eléctrica dispuesta en la placa, como podemos ver en la siguiente imagen:


Leds:
  • Se encuentran conectados a los pines 12 a 15 del puerto D (GPIO_Pin_12 a GPIO_Pin_15).
  • Se ha configurado en modo salida (GPIO_Mode_OUT).
  • Se ha usado también la velocidad mas alta.
  • La configuración de la salida esta en modo push pull (GPIO_OType_PP).
  • No se le ha conectado resistencia de pull up ni pull down (GPIO_PuPd_NOPULL).


Una vez rellenadas las estructuras de configuración, se realiza la inicialización de los periféricos mediante la función GPIO_Init.

Finalmente vemos el funcionamiento del main. Tras ejecutar las funciones descritas anteriormente encenderemos uno tras otro los cuatro ledas para comprobar su funcionamiento. A continuación entraremos en un while infinito donde se comprobará en cada ejecución de este el valor del botón de usuario. Si este está activo se incrementará el contador i y cambiará la iluminación de los leds.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include "stm32f4xx.h"
#include "stm32f4_discovery.h"
#include "functions.h"

int main (void)
{
    int i = 0;

    SysTick_Configure ();
    GPIO_Configure ();

    GPIO_SetBits (GPIOD, GPIO_Pin_12);
    Delay (500);
    GPIO_SetBits (GPIOD, GPIO_Pin_13);
    Delay (500);
    GPIO_SetBits (GPIOD, GPIO_Pin_14);
    Delay (500);
    GPIO_SetBits (GPIOD, GPIO_Pin_15);
    Delay (500);

    while (1)
    {
        if (GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_0) == 1)
        {
            while (GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_0) == 1);
            i++;
            if (i == 3)
                i = 0;
        }
        if (i == 0)
        {
            GPIO_ToggleBits (GPIOD, GPIO_Pin_12);
            Delay (500);
            GPIO_ToggleBits (GPIOD, GPIO_Pin_13);
            Delay (500);
            GPIO_ToggleBits (GPIOD, GPIO_Pin_14);
            Delay (500);
            GPIO_ToggleBits (GPIOD, GPIO_Pin_15);
            Delay (500);
        }
        else if (i == 1)
        {
            GPIO_SetBits (GPIOD, GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
            Delay (300);
            GPIO_ResetBits (GPIOD, GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15);
            Delay (300);
        }
        else if (i == 2)
        {
            GPIO_SetBits (GPIOD, GPIO_Pin_12);
            Delay (500);
            GPIO_ResetBits (GPIOD, GPIO_Pin_12);
            Delay (200);
            GPIO_SetBits (GPIOD, GPIO_Pin_13);
            Delay (500);
            GPIO_ResetBits (GPIOD, GPIO_Pin_13);
            Delay (200);
            GPIO_SetBits (GPIOD, GPIO_Pin_14);
            Delay (500);
            GPIO_ResetBits (GPIOD, GPIO_Pin_14);
            Delay (200);
            GPIO_SetBits (GPIOD, GPIO_Pin_15);
            Delay (500);
            GPIO_ResetBits (GPIOD, GPIO_Pin_15);
            Delay (200);
        }
    }
}

Este modo de comprobar el estado del botón (o de cualquier otro periférico), denominado polling, introduce un inconveniente, pues para comprobar su estado es necesario esperar a que finalice la ejecución del bucle while y también genera una espera para comprobar que el botón ha vuelto al estado inactivo para evitar una ejecución continua no deseada. Esto se evita mediante el empleo de interrupciones, que será el tema a tratar en la siguiente entrada.

Aquí podéis ver un vídeo en el que se muestran los efectos de los leds y también como influye la técnica del polling en la ejecución del código (para asegurarme que pasa de un estado a otro he de dejar el botón pulsado hasta que finaliza el ciclo de iluminación actual).


También dejo disponible el proyecto en Dropbox para importar a SW4STM32 en el siguiente enlace: GPIO STM32F4 Discovery Dropbox