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

11 comentarios:

  1. Saludos estoy muy interesado en la programación de las STM, por favor tengo una duda, las primeras configuraciones del SysTick_Handler y para el resto en que sección las configuraste porque no las encuentro en el main.c de tu programa espero tu ayuda por favor.

    ResponderEliminar
    Respuestas
    1. Hola, todas las funciones de configuración se encuentran en el archivo functions.c. También puedes ver la parte en concreto por la que preguntas en el código que aparece al inicio de la entrada.
      Dicha configuración es muy sencilla, pues solo necesitas obtener la frecuencia del reloj principal y multiplicarlo por el periodo de la interrupción que deseas obtener.
      Saludos.

      Eliminar
    2. Saludos y muchas gracias por tu pronta respuesta y ayuda, efectivamente he podido cargar el programa en la flash con las instrucciones de la sección anterior. En cuanto a la functions.c que incluye la functions.h me imagino que hay que agregar y crear la librería, o sea es una librería personal porque no está predefinida. De ser así e intentado dando click derecho en la carpeta src del proyecto, y le dí en NEW pero mi duda es cual de las opciones escoger para crear esa librería.

      Eliminar
    3. Hola, el archivo de cabecera functions.h se encuentra en la carpeta inc (de no ser así házmelo saber, gracias).
      En cuanto a añadir un archivo de cabecera (o header file), efectivamente, haciendo clic derecho sobre la carpeta escogida, vas a "New" y después clic en "Header File". Cuando des nombre al archivo recuerda ponerle la extensión ".h".
      Un consejo, si hubiera intentando incluir el archivo de cabecera en la carpeta "src" quizás no lo hubiera reconocido el compilador (dando fallo o warning) al no encontrarse en las carpetas seleccionadas para ello (para ver/seleccionar estas: clic derecho sobre el proyecto, "Properties", "C/C++ Build", "Settings" y añadir/ver en "MCU GCC Compiler-Includes" y "MCU GCC Assembler-General").
      Saludos y suerte con los proyectos.

      Eliminar
  2. Hola, ántes que nada gracias por tus post !
    Te escribo porque no logro hacer funcionar tu proyecto de los leds (GPIO_STM32F4_Discovery).
    El problema, lo tengo después de la compilación (aprox. 25 s compilando) ya que tu código no presenta errores ni warnings.
    En la consola del debugger me aparece la siguiente información (no es un error):

    Consola del debugger:
    Open On-Chip Debugger 0.10.0-dev-00270-g7ec9836 (2016-05-18-14:26)
    Licensed under GNU GPL v2
    For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
    Info : auto-selecting first available session transport "hla_swd". To override use 'transport select '.
    Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
    adapter speed: 2000 kHz
    adapter_nsrst_delay: 100
    srst_only separate srst_nogate srst_open_drain connect_assert_srst
    srst_only separate srst_nogate srst_open_drain connect_assert_srst
    Info : Unable to match requested speed 2000 kHz, using 1800 kHz
    Info : Unable to match requested speed 2000 kHz, using 1800 kHz
    Info : clock speed 1800 kHz
    Error: libusb_open() failed with LIBUSB_ERROR_ACCESS
    Error: open failed
    in procedure 'program'
    in procedure 'init' called at file "embedded:startup.tcl", line 473
    in procedure 'ocd_bouncer'
    ** OpenOCD init failed **
    shutdown command invoked



    Parece haber algún problema con el clock pero no he visto dónde lo puedo cambiar.

    Por otro lado, he intentado usar el ST-Link Utility para grabar el archivo compilado directamente en el micro y probar el funcionamiento (como en tu video), pero lamentablemente no funcionó.
    Después de la programación del chip, dos leds permanecen encendidos en color rojo: LD1=COM (communication) y LD2=PWR (power). Ninguno de los botones parece responder, y lo mismo sucede al desconectar el kit del USB y volviéndolo a conectar :-(
    En éste programa para grabar el binario, encontré en "Target->Settings" una opción para cambiar la frecuencia del clock del programador (modo SWD) pero las opciónes son 4MHz; 1,8MHz; 0,9MHz; etc... no presenta la opción de 2MHz (=200KHz) que aparecía en la consola del debugger.

    ¿Me podrías orientar con éste problema?
    He estado probando mil recomendaciones de otros foros con éste problema pero no doy en la solución.

    Desde ya muchas gracias.

    ResponderEliminar
    Respuestas
    1. Hola Gus, gracias por tu comentario.

      Lamentablemente no dispongo actualmente de la placa (no estoy en España), por lo que no puedo comprobar la diferencia entre tu error y una correcta comunicación con la placa.

      Lo cierto es que no te sabría decir ahora mismo porque puede ser, no llegué a tener ese problema, pero me da la impresión de que puede ser fallo de los drivers por lo que me dices de que no pudiste cargar el programa con ST-Link Utility (esto te lo digo apoyado en este link: http://www.openstm32.org/forumthread790, es el mismo problema que tu tienes, no se si lo habrás mirado ya).

      En cuanto a los leds, mientras se esta programando la placa deberías ver el LD1 parpadeando entre amarillo y rojo. El LD2 estará encendido siempre que tengas alimentada la placa.

      Una cosa si te pregunto, cuando la conectaste por primera vez, ¿pudiste usar la demo con la que viene programada? Simplemente para asegurar un correcto funcionamiento de la placa.

      Siento no serte de mucha ayuda, pero intenta buscar con la frase clave "Unable to match requested speed 2000 kHz, using 1800 kHz" en Google, pues hay mucha gente con el mismo error. Y la otra idea es que intentes ver el texto que se imprime en pantalla durante una comunicación correcta con la placa y buscar en internet las diferencias que encuentres.

      Espero que encuentres solución pronto y puedas continuar con tus proyectos. Y si no te importara, en ese caso, dejar un comentario con la solución, por si le valiera a alguien mas.

      Saludos.

      Eliminar
    2. Hola. Yo en GNU/Linux con openocd tuve el mismo problema con mi placa STM32F407G-disc1. La solución que encontré fue modificar o crear un nuevo archivo .cfg. Por ejemplo: stm32.cfg

      source [find interface/stlink-v2-1.cfg]

      transport select hla_swd

      source [find target/stm32f4x.cfg]

      reset_config srst_only

      El problema es que el archivo original busca interfaz stlink-v2.cfg
      Al ejecutar openocd referenciar la dirección del archivo.
      Espero te sea de ayuda. Saludos

      Eliminar
    3. Gracias por el comentario Carlos, espero que le sea de ayuda a Gus u otras personas con el mismo problema.

      PD: Carlos, llegaron dos comentarios tuyos, publiqué el último de ellos

      Eliminar
  3. Gracias muchachos!
    Lamentablemente estoy con Win y no parece ser lo mismo, y todavía no pude resolverlo. Por otro lado con Keil puedo programarla sin problemas.

    La info de carlos está explicada con mas detalle aquí:
    http://vedder.se/2012/12/debugging-the-stm32f4-using-openocd-gdb-and-eclipse/

    ResponderEliminar
  4. #include "stm32f4_discovery.h"
    Como añado esta libreria

    ResponderEliminar
    Respuestas
    1. No estoy seguro de a que te refieres.
      De todas maneras, esa librería ya debería aparecer añadida en el main.c, en caso contrario, deberías de copiar esa linea al inicio del main.c y poner el archivo stm32f4_discovery.h en la carpeta inc.

      Saludos.

      Eliminar