30 de junio de 2015

EXTI STM32F4 Discovery

Como vimos en GPIO STM32F4 Discovery, el empleo del polling es una técnica muy sencilla, pero por contra, tiene el inconveniente de que puede generar retardos no deseados y también, que el evento o flanco no sea detectado debido a que en ese momento se esté ejecutando otra línea de código, ademas del consumo de recursos para realizar el sondeo de los periféricos.

Para solventar estos defectos, sin introducir una gran dificultad al código, recurrimos al empleo de interrupciones. Según Wikipedia una interrupción es una señal recibida por un procesador para indicarle que debe interrumpir la ejecución normal de este y pasar a ejecutar el código especifico para tratar la situación requerida por la interrupción. Dicho código se conoce como ISR (Interrupt Service Routine, rutina de atención a la interrupción).

Nuestro dispositivo en concreto, dispone de 23 lineas de interrupción externas:
  • 16 de ellas (EXTI_Line0 a EXTI_Line15) están disponibles para direccionarlas directamente a los puertos de entrada/salida. Por ejemplo, si la interrupción se genera en el pin PA0 (botón de usuario) se usará EXTI_Line0, si la interrupción se genera en el pin PD5 se usará EXTI_Line5 y así sucesivamente.
  • Las restantes 7 están conectadas a periféricos específicos, según vemos en la siguiente tabla:
EXTI_Line
Periférico
EXTI_Line16
PVD Output
EXTI_Line17
RTC Alarm event
EXTI_Line18
USB OTG FS Wakeup from suspended event
EXTI_Line19
Ethernet Wakeup event
EXTI_Line20
USB OTG HS Wakeup event
EXTI_Line21
RTC Tramper and Time Stamp events
EXTI_Line22
RTC Wakeup event

A continuación nos centraremos en las lineas de interrupción EXTI_Line0 a EXTI_Line15, que son las que conciernen a los pines de entrada/salida (por usar el botón de usuario).
Una vez seleccionada la linea (PA0 en nuestro caso) se habrá de configurar el manejador de interrupciones (interrupt handler) mediante el NVIC (Nested Vector Interrupt Controller), que se encarga de seleccionar la interrupción de mayor prioridad y de activar y desactivar estas (soporta hasta 256 vectores de interrupción diferentes).
En la siguiente tabla podemos ver el manejador de las interrupciones de los pines de entrada/salida para configurar el NVIC:

EXTI_Line
IRQn
ISR
EXTI_Line0
EXTI0_IRQn
EXTI0_IRQHandler
EXTI_Line1
EXTI1_IRQn
EXTI1_IRQHandler
EXTI_Line2
EXTI2_IRQn
EXTI2_IRQHandler
EXTI_Line3
EXTI3_IRQn
EXTI3_IRQHandler
EXTI_Line4
EXTI4_IRQn
EXTI4_IRQHandler
EXTI_Line5 a EXTI_Line9
EXTI9_5_IRQn
EXTI9_5_IRQHandler
EXTI_Line10 a EXTI_Line15
EXTI15_10_IRQn
EXTI15_10_IRQHandler

Y en las siguientes imágenes podemos ver lo hasta aquí descrito:



Vista esta pequeña introducción a la configuración de las interrupciones, pasamos a ver el ejemplo en el que se alterna la iluminación de los leds.

Las funciones de configuración (GPIO_Configure y SysTick_Configure) son las mismas, ademas de las utilizadas para generar los delays (Delay y TimingDelay_Decrement). En cambio, se ha introducido una nueva función de configuración para la interrupción del botón de usuario, como podemos ver a continuación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
void EXTI_Configure (void)
{
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd (RCC_APB2Periph_SYSCFG, ENABLE);

    SYSCFG_EXTILineConfig (EXTI_PortSourceGPIOA, EXTI_PinSource0);

    EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init (&EXTI_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init (&NVIC_InitStructure);
}

Lo primero es configurar SYSCFG para que sea consciente de que se usarán las interrupciones y a continuación especificamos la procedencia de estas. La estructura de inicialización de la interrupción externa se comenta a continuación:
  • Dado que usamos el pin cero, usaremos EXTI_Line0, que como se comentó, tiene correspondencia directa con el numero del pin que se escoja.
  • Se configura en modo interrupción (EXTI_Mode_Interrupt).
  • Se escoge como disparo de la interrupción el flanco de subida (EXTI_Trigger_Rising).
  • Se activa la linea de interrupción (ENABLE).
Seguidamente vemos la estructura del vector de interrupciones:
  • Se escoge el canal 0 de las interrupciones externas, que como vimos antes, corresponde en cierta medida con el pin escogido (EXTI0_IRQn).
  • Se escoge el nivel de prioridad (valor de 0 a 15), siendo 0 al no existir mas interrupciones.
  • Se escoge el nivel de subprioridad (valor de 0 a 15), que igualmente será 0 (0x0 en hexadecimal) al no existir mas interrupciones con el mismo nivel de prioridad.
  • Se activa el canal de interrupción (ENABLE).
Tras la configuración e inicialización de la interrupción, pasamos a ver la rutina de atención a la interrupción (ISR).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void EXTI0_IRQHandler (void)
{
    if (EXTI_GetITStatus (EXTI_Line0) != RESET)
    {
        i++;
        if (i == 3)
            i = 0;
        EXTI_ClearITPendingBit (EXTI_Line0);
    }
}

Cada vez que se pulse el botón, se accederá a la rutina anterior, en la cual se comprobará que la interrupción ha sido generada en la linea que esperamos y a continuación se incrementará el valor de i, en este caso declarada como variable global en el main a la que haremos referencia de la siguiente forma:

1
extern int i;

Las lineas descritas son las que finalmente sustituyen a la funcionalidad de las siguientes lineas que aparecían en el main del programa de la entrada anterior.

1
2
3
4
5
6
7
if (GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_0) == 1)
{
    while (GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_0) == 1);
    i++;
    if (i == 3)
        i = 0;
}

Finalmente vuelvo a colocar un pequeño vídeo en el que esta funcionando en nuestro dispositivo el programa descrito. En este caso, se puede pulsar el botón en cualquier momento, que la rutina detectará esto e incrementará el contador i, pero si se pulsa varias veces antes de esperar a que finalice la secuencia actual de iluminación, saltará a la secuencia que indique i y no a la que iría justo después de la actual.


En la siguiente entrada hablaremos de los Timers, presentando primero su uso mas sencillo, la generación de una interrupción cada x tiempo, seguido de otros empleos habituales de estos.

Y aquí está, como será siempre, el proyecto listo para importar: EXTI STM32F4 Discovery Dropbox

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

24 de junio de 2015

STM32F4 Discovery

Hace ya varios años que tengo una placa programable de STMicroelectronics que conseguí gratis gracias a un amigo, la STM32 Value Line Discovery,



la cual ofrecían para participar en un concurso. Por entonces no tenía ni idea de como se usaba, así que tras adquirir experiencia durante la carrera, comencé a programar sobre esta, hasta que de ultimo decidí pasarme, a la que he denominado la hermana mayor, la STM32F4 Discovery, y dado que es una placa con mucho que contar, me he propuesto realizar algunos ejemplos de uso de esta.

En esta ocasión me dedicaré simplemente a ver los detalles mas importantes de esta placa, y algunos de los programas que usaré para interactuar con dicho dispositivo (STM32F4 Discovery MB997C).



Las características principales de esta placa son (sacadas del manual de usuario):
  • Microcontrolador STM32F407VGT6.
  • Programador/depurador ST-LINK V2 (conector USB mini A) y conector SWD.
  • Acelerómetro LIS3DSH de tres ejes.
  • Micrófono digital omnidireccional MP45DT02.
  • Decodificador de audio (DAC) de 24 bits CS43L22.
  • Cuatro leds y un botón de usuario.
  • USB OTG (conector USB micro AB).
  • Extensión de entradas y salidas para conexión rápida.
Las características principales del microcontrolador STM32F407VGT6 son (observar imagen, aquí se nombran las que se usarán principalmente):


  • Núcleo ARM Cortex M4 de 32 bits a 168 MHz (210 DMIPS) con 1 MB Flash y 192 KB RAM.
  • USB OTG FS.
  • Entradas/salidas: 8 puertos de 16 bits + 1 puerto de 12 bits con función alternativa.
  • 14 timers, 6 USART, 3 SPI, 3 I2C, 3 ADC, 2 DAC, 2 CAN ...
Para el desarrollo del código usé Atollic True Studio (lo tuve que dejar debido a la limitación de los 32 KB de programa) y Eclipse (las librerías por defecto eran las HAL y estoy acostumbrado a las Standard, ademas de que es mas engorrosa la generación de nuevos proyectos), pero finalmente me decanté por una opción completamente libre y que permite escoger que librerías quieres, se trata del IDE SW4STM32. Para conseguirlo simplemente hay que registrarse en el enlace anterior.

También he recurrido en ocasiones al software STM32CubeMX, para obtener unas configuraciones rápidas de los periféricos, pero dado que también hace uso de las librerías HAL, no me centrare apenas en este.

Finalmente, y dado que es un aspecto muy interesante, también hago amplio uso del software LabView para el desarrollo de aplicaciones que permitan monitorizar y controlar en tiempo real los periféricos programados en la placa. En ocasiones se usara también un monitor de interfaz serie para estos aspectos.

La instalación de estos programas es muy sencilla, por lo que simplemente expondré unas capturas de la creación de proyectos y algunos aspectos relevantes de estos.

SW4STM32

File -> New -> C Project.


Damos nombre al proyecto y seleccionamos "Ac6 STM32 MCU Project".


Click en Next.


Seleccionamos la placa dentro de la serie SM32F4 (deseleccionar las mismas opciones para búsqueda mas rápida) y hacemos click en Next.


Seleccionamos "Standard Peripheral Library (StdPeriph)" y "As sources" (si no generará un segundo proyecto donde estarán dichas librerías) y en caso de que haga falta se añadirán también de "Additional drivers" o "Additional utilities and third-party utilities" y hacemos click en Finish.


El contenido final del proyecto se encuentra distribuido de la siguiente forma:


Así pues, tenemos la librería "StdPeriph_Driver" con los drivers de los periféricos; la carpeta "Utilities" para el manejo rápido de funciones del DAC de audio, el acelerómetro y el botón y leds de usuario; la carpeta "inc" para incluir nuestros propios header files; la carpeta "src" para nuestros source files; la carpeta "startup" que contiene la rutina de inicio de la placa (no os asoméis a ella, esta en ensamblador ... ;-)) y la carpeta "CMSIS" con las funciones propias del nucleo de nuestro microcontrolador.
Una vez escrito y compilado (icono del martillo) el código, nos vamos a la venta de depuración (icono de la cucaracha), momento en el que posiblemente aparezca esta ventana, donde seleccionaremos la primera opción.


Tras esto se nos presentará la siguiente ventana, y con ayuda de los botones play, pause y stop, ademas del uso de breakpoints, realizaremos la depuración del código.


Si en vez de realizar la depuración queremos directamente cargar el código en la placa, usamos el icono del play justo al lado de la cucaracha.

STM32CubeMX

New Project.


En la ventana que se nos abre seleccionamos nuestra placa y hacemos click en OK.


A continuación configuramos los periféricos que vayamos a necesitar (el stick en verde significa que esta programado, el warning significa que solo parte se puede programar debido a que otro periférico este usando alguna de las entradas/salidas del microcontrolador y la cruz en rojo significa que no se podrá usar a menos que se desconfigure el periférico que este usando la función alternativa que capa a este último).


Una vez realizada la configuración hacemos click en Project -> Generate Code, damos nombre al proyecto, seleccionamos el IDE, en mi caso "SW4STM32" y finalmente hacemos click en Ok.


LabView

Create Project.


Seleccionamos "Blank VI" y click en Finish.


Cuando generamos el proyecto nos aparecen dos ventanas, la de la izquierda, "Front Panel" será la interfaz de usuario final y la de la derecha, "Block Diagram" relaciona "eléctricamente" los elementos que aparecen en la primera.


Terminal v1.93b

Por último, muestro una captura del programa para monitorizar el puerto serial (para realizar una comprobación rápida de los datos que circulan por dicha interfaz), que sera la forma de comunicar la placa con el PC.


En unos días comenzaré a publicar algunos ejemplos de uso de los periféricos mas interesantes mientras disponga de herramientas o dispositivos para comprobar el correcto funcionamiento.