Programando microcontrolador ARM: 8 - Botones y estructura interna Input GPIO pull-down y pull-up (Eclipse) (stm32f103c8)

En esta entrada vamos a ver cómo podemos utilizar el botón con nuestro microcontrolador. Al igual que en otros microcontroladores interactuaremos con nuestro botón a través de los puertos del GPIO, de los cuales estudiaremos a fondo su estructura interna.

Vamos a configurar dos botones y darle diferente funcionalidad a cada uno de ellos, con el objetivo de reforzar los conocimientos.

Para visualizar la funcionalidad del vamos a incluir una variable de tipo entera que simplemente se verá afectada por los botones. En caso de pulsar uno de los botones la variable aumentará su valor en uno y de manera opuesta el otro botón restará uno al valor de la variable. Además se añaden unas condiciones que limitan a la variable a pertenecer entre el 1 y el 7.

Estructura interna input GPIO

Debemos entender primero con se configura físicamente nuestro pin GPIO. En el manual de referencia vamos a encontrar el diagrama eléctrico básico de los pines GPIO.


Como podemos ver estos pines incluyen el circuito necesario. Disponen tanto de una resistencia pull-up como pull-down. Para identificarlas se hace de manera intuitiva, la referencia es el punto medio que corresponde a la señal IO del pin, luego la resistencia que está arriba conectada a la fuente VDD es la resistencia pull-UP y la que está conectada abajo a tierra es pull-DOWN.

Estas resistencias internar se activan mediante software y solo se puede encontrar una de ella activada. Veremos cómo controlar estas resistencias mediante software en este mismo tutorial más adelante.

El fabricante nos indica que la tensión máxima a la que se debe conectar este pin IO es de máximo VDD + 0.3, en nuestro caso por lo tanto sería de 3.6V (3.3V +0.3), pero de manera típica se debe conectar a 3.3V.

Por otro lado, existen los pines GPIO FT que corresponde a pone denominados Five volts Tolerant, que pueden soportar una tensión de 5V.

La ventaja de tener esta resistencia interna, tanto pull-up como pull-down sobre todo la encontramos a la hora de diseñar hardware, estas resistencias internas nos evitan la colocación de resistencias externas, ahorrando tiempo de diseño, disminuyendo a la complejidad y ahorrando dinero, tanto en material como en proceso de ensamblado.

Configuración Pull-down

Pull-DOWN

Centrándonos en los GPIO base, los de 3.3V, la conexión mas simple es conectar el botón en serie directamente a una fuente de 3.3V, sin añadir ninguna resistencia extra ya que la resistencia interna hace todo el trabajo. De esta manera estaremos utilizando la resistencia pull-down interna. Y el microcontrolador estará leyendo en su estado estático o normal LOW, 0 binario o 0V. En el momento de pulsar el botón la corriente circulará por el pin y en ese momento leerá HIGH, 1 binario o 3.3V.

Pull-UP

Por otro lado, tenemos la opción de utilizar la resistencia interna pull-up que está conectada en serie con la alimentación interna del microcontrolador VDD. De esta manera la conexión mas normal sería conectar nuestro botón directamente a tierra. En este caso nuestro microcontrolador detecta en esa entrada normalmente HIGH, 1 en binario o 3.3V y en el momento de pulsar detectará LOW, 0 en binario o 0V.
Configuración pull-up

Floating Input

Por último, tenemos la opción de floating input, en esta opción ninguna de las resistencias internas del pin del microcontrolador estará activadas. Y debemos ser nosotros los que añadimos las resistencias pull-up o pull-down en cualquiera de los casos.

Es recomendable usar resistencia de resistencia de entre 1K a 10K con el fin de minimizar la corriente y por tanto el consumo del dispositivo.

CONEXIONES

El diagrama de conexiones es muy sencillo, simplemente necesitamos conectar uno de los lados de los botones a sus correspondientes pines, en este caso PB5 y PB6 y el otro lado a la fuente de 3.3V como se puede ver en el siguiente diagrama.
Diagrama de conexiones

PROGRAMACIÓN

De igual manera que hemos visto cómo configurar un pin como output podemos configurar un pin como tipo input. En este caso vamos a utilizar la estructura GPIO_InitTypeDef que nos permite configurar los pines de tipo GPIO.
GPIO_InitTypeDef GPIO_InitStruct_PB_B5;
GPIO_InitStruct_PB_B5.GPIO_Pin = GPIO_Pin_5;

A continuación, vamos a indicar el modo en el que queremos que actúen las resistencias internas, como hemos visto tenemos tres opciones:

- Pull-down
    GPIO_InitStruct_PB_B5.GPIO_Mode = GPIO_Mode_IPD; 

- Pull-up
    GPIO_InitStruct_PB_B5.GPIO_Mode = GPIO_Mode_IPU; 

- Floating
    GPIO_InitStruct_PB_B5.GPIO_Mode = GPIO_Mode_IN_FLOATING;

Por último, inicializamos el pin con la estructura de datos creados. 
GPIO_Init(GPIOB, &GPIO_InitStruct_PB_B5);

En este caso vamos a configurar nuestro pin GPIO como input pull-down. Para declarar ambos botones, por lo tanto:

    GPIO_InitTypeDef GPIO_InitStruct_PB_B5;
    GPIO_InitStruct_PB_B5.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStruct_PB_B5.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_Init(GPIOB, &GPIO_InitStruct_PB_B5);

    GPIO_InitTypeDef GPIO_InitStruct_PB_B6;
    GPIO_InitStruct_PB_B6.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStruct_PB_B6.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_Init(GPIOB, &GPIO_InitStruct_PB_B6);

Por último, para detectar estas pulsaciones vamos a utilizar una función que acede al registro de del estado del pin.

    GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5)

Esta función devuelve 0 y 1 dependiendo de estado del pin. De esta manera incluiremos esta función en una condición if y podremos saber el estado del pin.
    
    if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5)){
        //El botón está pulsado
    }else{
        //El botón no está pulsado
    }

Por lo tanto, el código completo

#include "stm32f10x.h"
#include <stdio.h>

void delay(long cycles);
void GPIOext_Init(void);
void led_blink(void);

int main(void)
{
       int gear = 1;
       GPIOext_Init();

       while(1){
             if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5)){
                    if(gear < 7){
                          gear++;
                    }else{
                          led_blink();
                    }
             }
             if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6)){
                    if(gear > 2){
                          gear--;
                    }else{
                          led_blink();
                    }
             }
       }
}

/***************************************************
 * Initialize GPIOC and pin13 as PP y max speed V1
 ***************************************************/
void GPIOext_Init (void)
{
       RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
       RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
       RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

    /*Configure GPIO pin : Input_Pin_C13 */
    GPIO_InitTypeDef GPIO_InitStructure_PC13;
    GPIO_InitStructure_PC13.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure_PC13.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure_PC13.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure_PC13);
    GPIOC -> BSRR |= GPIO_BSRR_BS13;

    //** PUSH BUTTONS
    /*Configure GPIO pin : PushButton_Pin_B5 */
    GPIO_InitTypeDef GPIO_InitStruct_PB_B5;
    GPIO_InitStruct_PB_B5.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStruct_PB_B5.GPIO_ModeGPIO_Mpde_IPD;
    GPIO_Init(GPIOB, &GPIO_InitStruct_PB_B5);
    /*Configure GPIO pin : PushButton_Pin_B6 */
    GPIO_InitTypeDef GPIO_InitStruct_PB_B6;
    GPIO_InitStruct_PB_B6.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStruct_PB_B6.GPIO_Mode = GPIO_Mpde_IPD;
    GPIO_Init(GPIOB, &GPIO_InitStruct_PB_B6);
}

/*******************************************
 * Blink led_blink macro
 *******************************************/
void led_blink(void){
       //clear pin set to 0
       GPIO_ResetBits(GPIOC, GPIO_Pin_13);
       //reset 0010 0000 0000 0000 |set 0000 0000 0000 0000
       delay(2000000);
    //set pin to 1
       GPIO_SetBits(GPIOC, GPIO_Pin_13);
       //reset 0000 0000 0000 0000 |set 0010 0000 0000 0000
       delay(2000000);
}

/*******************************************
 * Delay
 *******************************************/
void delay (long cycles){
       while (cycles > 0)
             cycles--;
}

Programando microcontrolador ARM: 7 - Nuevo entorno de desarrollo Open System Workbench for STM32 (Eclipse) (stm32f103c8)


Para facilitar el acceso al desarrollo y programación de microcontroladores STM32 vamos a cambiar a un entorno de desarrollo multiplataforma, de esta manera cualquier usuario, tanto Windows, Mac y Linux, podrá acceder a la información publicada en este blog.

El entorno que vamos a utilizar es System Workbench for STM32, este IDE está basado en Eclipse y contiene todo lo necesario para poder programar nuestros microcontroladores. Además de nuestro BluePill basado en STM32F103 vamos a poder programas toda la gama de microcontroladores STM32, aunque hay que tener en cuenta debemos configurar los proyectos específicamente para cada microcontrolador.

DESCARGA E INSTALACIÓN

Para poder descargar el entorno de desarrollo necesitamos registrarnos en la web de OpenSTM32, http://www.openstm32.org/HomePage.

Una vez registrados tendremos pleno acceso a la web y por tanto a las descargas. Seleccionaremos en el menú de la izquierda System Workbench for STM32.

Entonces nos aparecerá todo lo necesario para descargar el entorno. Además disponemos de una guía detallada para instalar correctamente el IDE para cada plataforma.

CREAR Y CONFIGURAR PROYECTO

Para configurar nuestro proyecto debemos empezar por crear un nuevo proyecto. Para crear un nuevo proyecto debemos ir a  File ->  New -> C Project. De esta manera nos aparecerá la primera ventana de configuración del proyecto.

En esta ventana (1) vamos a seleccionar un proyecto de tipo Executable y Ac6 STM32 MCY Project y con el toolchain Ac6 STM32 MCU GCC.

A continuación nos aparecerá la ventana (2) que nos permite seleccionar configuraciones, indicaremos que usaremos ambas configuraciones, Debug y Release.




La siguiente ventana nos va a solicitar indicar el microcontrolador que vamos a querer usar, en nuestro caso vamos a seleccionar  STM32F103C8Tx.

Primero debemos seleccionar la pestaña de Mcu, esta pestaña nos permite buscar directamente por modelos de microcontroladores STM32. Boards nos permite encontrar las diferentes placas que existen en el mercado con STM32.

Podemos usar varios filtros como escribir directamente el nombre del microcontrolador o filtrar por serie. De ambas maneras podemos lograr encontrar en microcontrolador que estamos usando.

A continuación es muy importante darle al botón de Next y no a finalizar. Si finalizamos en este paso no podremos terminar de configurar adecuadamente.


El siguiente paso nos permite configurar las bibliotecas necesarias para poder programar el microcontrolador.

Debemos seleccionar la opción de Standard Peripherial Library  (StdPeriph). Esta biblioteca es el fichero de tipo header stm32f10x.h, el cual contiene numerosos métodos definiciones de variables útiles a la hora de programar. 

Por último en caso de que no tengamos descargada la biblioteca nos aparecerá el botón de Download target firmaware en verde, una vez descargado el propio proyecto encintará los ficheros necesarios. 
Por lo general esta acción solo se realiza una vez por cada tipo de microcontrolador concretos que fuéramos a programar. Una vez realizado este paso, no necesitaríamos conexión a Internet para poder crear un proyecto nuevo.

Ahora el proyecto aparecerá en el Project Explorer, generalmente situado a la izquierda. Dentro del proyecto podemos encontrar la carpeta src y dentro el fichero main.c.

Configuración de Run y Debug

Para poder cargar nuestro código debemos realizar unas últimas configuraciones, en este caso tratan la manera en al que el código se carga en el microcontrolador.

Primero vamos a crear los ficheros necesarios para poder configurar estas opciones. Para crearlos simplemente pulsamos en el icono de Run o Debug.



Ahora si podemos acceder a la configuración de para cargar le código. Para acceder a estas configuraciones  debemos desplegar el menú de Debug o Run, cualquiera de ellos no van a llevar al mismo dialogo.


Podemos crear diferentes configuraciones para un proyecto y podremos ver todas las configuraciones de los proyectos. Podemos darle el nombre que queramos a cada configuración. Esto no es útil en caso de cargar el proyecto para diferentes microcontroladores que dispongan de configuraciones diferentes o vayamos a usar diferentes métodos de carga.


A continuación debemos acceder a la pestaña debugger. En ella debemos realizar varios cambios, primero debemos desplegar la opciones del generador, para ello presionamos en Show generator options. Al abrir esta opción debemos modificar el Reset Mode y seleccionar Software system Reset.
A continuación debemos marcar la opción de Shareable ST-Link. Como se puede ver al accionar esta opción automáticamente podemos ver si tenemos la placa o el microcontrolador conectado y comprobar si las conexiones son correctas.



Una vez que tengamos nuestra placa conectada podemos usar el botón de refres y automáticamente aparecerá nuestra placa.
Además tenemos la posibilidad de cambiarle el nombre para poder identificarla con otras y también podemos hacer parpadear el LED del programador ST-Link, de esta manera es mas fácil identificarlos microcontroladores

Ahora nos desplazamos a la pestaña Startup, en ella simplemente debemos desmarcar las opciones de Reset and Delay y Halt.


En este punto ya podemos subir y probar nuestro código. Para ello primero debemos tener conectado nuestro Blue Pill STM32F103 a través del ST-LinkV2 que disponemos. Aconsejo iniciar el programa siempre en modo debug, de esta manera podemos controlar mejor lo que pasa al cargar el código y podremos visualizar errores o advertencias en caso de que los hubiera.

Al accionar el debug entramos en la vista debug, si todo está correcto no recibiremos ningún error y el código estará ya cargado. Es posible que tengamos que accionar el play que se encuentra arriba a la izquierda en verde para que el código corra en modo debug. Debemos tener en cuenta que el código se carga desde un principio en el microcontrolador.

Podemos probar un primer código muy sencillo:
_________________________________________________________________
#include "stm32f10x.h"
void delay(long cycles);
void GPIOC_Init(void);
void led_blink(void);

int main(void)
{
GPIOC_Init();

while(1){

led_blink();
delay(500);
}
}

/***************************************************
 * Initialize GPIOC and pin13 as PP y max speed V1
 ***************************************************/
void GPIOC_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; //iniciamos el reloj para los pines del puerto C
GPIOC->CRH |= GPIO_CRH_MODE13; // en el pin 13 ponemos CNF 00 (General purpose push-pull)
   // y tambien Output mode, max speed 50MHz
}

/*******************************************
 * Blink led_blink macro
 *******************************************/
void led_blink(void){

//clear pin set to 0
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
//reset 0010 0000 0000 0000 |set 0000 0000 0000 0000
delay(2000000);

       //set pin to 1
GPIO_SetBits(GPIOC, GPIO_Pin_13);
//reset 0000 0000 0000 0000 |set 0010 0000 0000 0000
delay(2000000);

}

/*******************************************
 * Delay
 *******************************************/
void delay (long cycles){
while (cycles > 0)
cycles--;
}
_________________________________________________________________

Cuando el código está en marcha tenemos varias vistas interesantes en el modo debug, aconsejo añadir algún breakpoints en el código y así podemos observar el valor de variables y registros de memoria en tiempo real.



Para cualquier duda escribir un comentario, si hay dudas iré modificando el tutorial para fortalecerlo con la información requerida.




Programing Challenge 2 - Ordenar puntos en espacio tridimensional, Clase (Python)


En esta entrega de Programming Challenge vamos a evolucionar el código que habíamos creado para ordenar puntos en espacio tridimensional.
Para ello vamos a introducirnos a las clases y objetos en Python. De igual manera que otros lenguajes de programción podemos crear clases, que a groso modo son contenedores de información y además podemos crear funciones de la clases que nos permiten manejar esa información.

Para poder usar estos contenedores de información instanciamos, creamos, objetos. Estos objetos tienen las características específicas de la clase a la que pertenezcan.


Para este Challenge he creado una clase punto que simplemente es capaz de almacenar las tres coordenadas de un punto. Este método tiene un def que podríamos compararlo con el constructor de cualquier lenguaje orientado a objetos, el método def __init__(), se ejecutará al instanciar un objeto.
____________________________________________
class Point

    def __init__(self, xc=0, yc=0, zc=0):
        self.xc = xc
        self.yc = yc
        self.zc = zc

    def setXc(xc):
        self.xc = xc
    
    def setYc(yc):
        self.yc = yc
        
    def setZc(zc):
        self.zc = zc
        
    def getXc(self):
        return self.xc
    
    def getYc(self):
        return self.yc
        
    def getZc(self):
        return self.zc
________________________________

Además hemos añadidos los métodos get y set para poder interactuar con estas variables. El código anterior está situado en un fichero a parte de python llamado Poitn.py. De esta manera desde cualquier otro script de python. Para poder acceder a esta clase debeos importar la biblioteca.

from Point import Point

Podemos situar múltiples clases en un fichero, de esta manera podríamos tener un fichero con un conjunto de clases relacionadas, por ejemplo un fichero Geometrics e importar múltiples clases de el.

from Geometrics import Point
from Geometrics import Line
from Geometrics import Cube
from Geometrics import Cone
...

Lo mas complejo de las clases que se diferencia de otro lenguajes es el uso del self esta palabra la utilizamos para referirnos al propio objeto instanciado de la clase. Si no usáramos esta referencia de no podríamos referirnos al propio objeto de la clase.

El resto del código es muy similar a la anterior entrega del programming challenge. Podemos ver que en el momento de crear los puntos random creamos objetos de la clase Point. El resto se basa en utilizar los métodos get de la clase para calcular la distancia al origen.

Todos los ficheros .py usados en los challenges los podéis encontrar en mi GitHub (https://github.com/KarlVaello/python-programming-challenges)
___________________________________________________________
from random import randint
import math
from Point import Point

# def that sort an array of points (bubble algorithm)
def bubbleSort(alist):
    n = len(alist)
    for i in range(len(alist)-1):
        swp = False
        for j in range(len(alist)-1):
            if (alist[j][2] > alist[j+1][2]):
                tempPoint =  alist[j]
                alist[j] = alist[j+1]
                alist[j+1] = tempPoint
                swp = True

        if (swp == False):
            break

nPoint = 9 # numer of points
points = [] # points array

pointRange = 90 #aleatory range

# loop to create new random points
for x in range (nPoint):
    newPointObjet = Point(randint(-pointRange,pointRange),randint(-pointRange,pointRange),randint(-pointRange,pointRange))
    newPoint = [x+1, newPointObjet]
    points.append(newPoint)

# loop to print element on creation order
print("Element on creation order [x,y,z]")
for i in range(nPoint):
    print("(" + str(points[i][0]) + ",[" + str(points[i][1].getXc())+ ", " + str(points[i][1].getYc())+ ", " + str(points[i][1].getZc()) + "])")
    
print()
# loop to calculate distance to [0, 0, 0] of each point
for e in range (nPoint):
    d = math.sqrt(((points[e][1].getXc()-0)**2)+((points[e][1].getYc()-0)**2)+((points[e][1].getYc()-0)**2))
    points[e].append(d)
   
# loop to print element on creation order and distance to [0, 0, 0]
print("Element on creation order ( n [x,y,z] / DistanceTo0,0,0]")
for i in range(nPoint):
    print("(" + str(points[i][0]) + ",[" + str(points[i][1].getXc())+ ", " + str(points[i][1].getYc())+ ", " + str(points[i][1].getZc()) + "], " + str(points[i][2]) +  ")")
    
# bubble sort algorithm 
bubbleSort(points)

print()
# print elements in order
print("Elements ordered by distance")
for i in range(nPoint):
    print("(" + str(points[i][0]) + ",[" + str(points[i][1].getXc())+ ", " + str(points[i][1].getYc())+ ", " + str(points[i][1].getZc()) + "], " + str(points[i][2]) +  ")")
   
___________________________________________________________