En un post anterior (Empezando con el STM32F4 y Ada) os contaba cómo instalar y empezar a usar la placa de desarrollo STM32F4 Discovery, y comenzar un nuevo proyecto en el lenguaje Ada. Lo siguiente que necesitamos para nuestro proyecto es poder leer los valores del ADC y mostrarlos por pantalla. Vamos a usar el módulo USART para enviar datos y el ADC para leer una señal analógica y convertirla a un valor digital. En esta primera entrada vamos a ver como configurar una consola a través de UART.

USART

El UART/USART es un protocolo de comunicaciones síncrono/asíncrono en serie a través de 2 líneas (Tx y Rx). Por su simplicidad en la programación y la electrónica, es un protocolo muy usado en comunicaciones punto a punto entre 2 módulos. Solo son necesarias 3 conexiones: TX a RX, RX a TX y la última conexión para unir las tierras de ambos dispositivos. Además, el protocolo es altamente configurable en velocidad, bits de detección de errores… El principal inconveniente de este protocolo es que solo se pueden comunicar 2 sistemas en un mismo bus, es decir hacen falta tantos módulos UART en el chip, como dispositivos quieras comunicar. Mientras que I2C o SPI son capaces de tener varios modulos hablando en el mismo bus (con un TX y un RX en el chip, puedes hablar con varios dispositivos I2C/SPI).

Además, para poder ver que el UART está funcionando correctamente existen dispositivos que se conectan al puerto UART del microcontrolador y te permiten leer la salida en el PC (e incluso hacer el camino contrario y enviar datos desde el PC hacia el micro). Por ejemplo, en mi caso tengo la versión más barata (0.57€, pero funcional) que he encontrado (Aliexpress).

Adaptador USB-UART

Adaptador USB-UART

Una consola a través del USART

Lo primero que vamos a hacer es crear nuestra “Consola” a través de nuestro puerto USART de modo que podamos mandar datos al PC, principalmente para depurar a través de mensajes. Empezaremos con nuestro proyecto base que creamos en el post anterior sobre como empezar con Ada y el STM32. A partir del código fuente vamos a intentar obtener un “Hola mundo” en la consola del PC a través del adaptador CH340. Para ellos vamos a ir paso a paso creando nuestro package Console.

1
2
3
package Console is

end Console;

De momento fácil, no? :P. Lo primero en cualquier package es definir que variables y funciones vamos a tener disponibles, en nuestro caso como necesitamos: encender el módulo USART, configurar los GPIOs para que funcionen como USARTs y además sabemos que de primeras necesitamos enviar datos; parece claro que tendremos que definir un par de funciones en el Console.ads.

La librería de Ada Library nos provee con muchos tipos de variable predefinidos acordes con las especificaciones del STM32. Por si no estáis muy familiarizados con Ada, una de sus principales características es el alto tipado que tienen todas las variables. Se generan tipos de datos prácticamente por cada variable diferente del sistema. Para ahorrarnos todo el lío de estar definiendo todos los tipos, vamos a usar los que nos trae la librería. Para poder usarlos necesitamos incluir la parte de la librería que los define (como los #include "lib.h" de C++), con la palabra reservada with Libreria;.

La librería de Ada para la STM32 separa las librerías en función de si son definiciones propias del ARM, si son del STM32, de un módulo concreto(STM32.USART por ejemplo) o si son de la propia placa. En nuestro caso necesitamos hacer operaciones con GPIOs, USARTs y además tenemos que acceder a los propios de nuestro chip así que necesitamos incluir:

  • STM32.GPIO; Opciones de configuración de cada GPIO
  • STM32.USARTs; Opciones de configuración de los módulos de USART
  • STM32.Device; Accesos a los GPIO y USART concretos de STM32

Para que además, la librería sea un poco más genérica vamos a hacer que se puedan configurar los pines de Tx/Rx y el módulo de USART a utilizar. Creamos las variables para guardar los pines y el USART a usar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
with STM32.USARTs;
with STM32.Device;
with STM32.GPIO;

package Console is

    -- Configuración de pines
    tx_Pin : STM32.GPIO.GPIO_Point := STM32.device.PA2;
    rx_Pin : STM32.GPIO.GPIO_Point := STM32.device.PA3;

    dev : STM32.USARTs.USART renames STM32.device.USART_2;
    gpio_af: STM32.GPIO_Alternate_Function := STM32.Device.GPIO_AF_USART2_7;  

    -- Configura el USART y los GPIO
    procedure Init(baudRate : STM32.USARTs.Baud_Rates);

    -- Envía un char por el USART
    procedure putChar(c: out Character);
end Console;

Escribiendo código de verdad

Con el interfaz ya definido, ahora viene la parte del salseo. Una cosa muy importante del STM32 y creo que de ARM en general, es que cada sistema tiene su entrada de reloj, y si no se activa el reloj del sistema, nada de ese sistema va a funcionar por lo que tenemos que tener presente activar los diferentes relojes según los vayamos necesitando.

Vayamos por partes: GPIOs

La configuración de los GPIOs de la librería de Ada tiene un record con los parámetros a rellenar. Este tipo de estructura son estructuras con discriminantes, es decir que se pueden generar diferentes estructuras en función del discriminante, en esta ocasión el Modo del GPIO. Vemos que en cualquier caso hay que configurar la resistencia interna del GPIO y después en función del modo en el que queremos el GPIO, habrá que rellenar un campo u otro.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   type GPIO_Port_Configuration (Mode : Pin_IO_Modes := Mode_In) is record
      Resistors : Internal_Pin_Resistors;
      case Mode is
         when Mode_In | Mode_Analog =>
            null;
         when Mode_Out =>
            Output_Type : Pin_Output_Types;
            Speed       : Pin_Output_Speeds;
         when Mode_AF =>
            AF_Output_Type : Pin_Output_Types;
            AF_Speed       : Pin_Output_Speeds;
            AF             : GPIO_Alternate_Function;
      end case;
   end record;

Como en nuestro caso queremos usar la función alternativa de los GPIO PA2 y PA3 tendremos que generar una configuración con el Modo: Mode_AF. Y junto con esto, rellenar el resto de campos con lo que queremos usar. Así que preparamos la estructura (record) con todos los campos rellenos, activamos el reloj del módulo de GPIO y configuramos los pines.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
procedure Init( baudRate: STM32.USARTs.Baud_Rates) is
      config: STM32.GPIO.GPIO_Port_Configuration(STM32.GPIO.Mode_AF);
   begin
     
      -- Configuramos GPIOs
      config.Resistors := STM32.GPIO.Pull_Up;
      config.AF_Output_Type := STM32.GPIO.Open_Drain;
      config.AF_Speed := STM32.GPIO.Speed_50MHz;
      config.AF := gpio_af;
     
      STM32.device.Enable_Clock(tx_pin & rx_Pin);
      STM32.GPIO.Configure_IO(tx_pin & rx_pin,
                              config);
       
end Init;

Vayamos por partes II: UART

Ya tenemos los pines configurados para ser usados por el USART2, así que como en nuestra variable dev del package ya tenemos la dirección de USART2 para poder usarla, disponemos de todo lo necesario para arrancar el módulo de USART. Como en los casos anteriores encendemos el reloj del módulo, activamos el dispositivo y configuramos todo lo necesario, que en este caso sería: el modo y la velocidad.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
procedure Init( baudRate: STM32.USARTs.Baud_Rates) is
      config: STM32.GPIO.GPIO_Port_Configuration(STM32.GPIO.Mode_AF);
   begin

   -- Configuración del GPIO
   -- ...

   -- Configuración del USART
   STM32.device.Enable_Clock(dev);
   STM32.USARTS.Enable(dev);
   
   STM32.USARTS.Set_Mode(This => dev,
                         To   => STM32.USARTs.Tx_Rx_Mode);
     
   STM32.USARTS.Set_Baud_Rate(dev, baudRate);
end Init;

Con esto terminaríamos la parte de inicio del USART y tendríamos el módulo USART2 listo para enviar y recibir a través de los pines PA2 y PA3. Así que nos quedaría nuestro primer objetivo que es enviar datos. Para ello tenemos definida la función putChar para enviar un carácter. Esta parte es muy sencilla y solo tenemos que esperar a que el USART2 esté disponible para enviar y cuando lo esté, enviar los datos.

1
2
3
4
5
6
7
8
9
procedure putChar(c : Character) is
begin
   -- Esperamos a que el transmisor esté disponible
   while not STM32.USARTs.Tx_Ready(dev) loop
      null;
   end loop;
   STM32.USARTs.Transmit(This => dev,
                         Data => Character'Pos(c));
end putChar;

Y ya que estamos no cuesta nada hacer una función para mandar una cadena de texto completa. Solo tendríamos que enviar todos los caracteres de la cadena.

1
2
3
4
5
6
procedure put(s: String) is
begin
  for i in s'Range loop
     putChar(s(i));
  end loop;
end put;

Y nuestro programa principal?

Con todo lo que llevamos hasta ahora ya podemos modificar nuestro programa principal para enviar una “Hola Mundo” a través de nuestro USART y recibirlo en nuestro PC con nuestro terminal favorito. Si estáis en Windows podeis usar putty o mobaXterm, desde Linux un simple tail -f /dev/ttyUSBx os debería valer. Hemos añadido el with Console; para poder usar la consola. La hemos inicializado con 9600 baudios de velocidad y cada 2 segundos mostramos un “Hola Mundo”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
with STM32.Board;   use STM32.Board;
with Ada.Real_Time; use Ada.Real_Time;
with Console;


procedure Main is
   Next_execution: Ada.Real_Time.Time;
   Period: constant Ada.Real_Time.Time_Span:= Ada.Real_Time.To_Time_Span(2.0); -- 2secs
begin
   Initialize_LEDs;
   Console.init(baudRate => 9600);
   Next_execution:= Ada.Real_Time.Clock + Period;
   All_LEDs_Off;
   loop
      Toggle_LEDs(All_LEDs);
      Console.putLine("Hola mundo!");
      delay until Next_execution;
      Next_execution:= clock + Period;      
   end loop;
   
end Main;
Categorías: ECG

Gluón

Teleco con ganas de aprender más y compartirlo. Viajero empedernido y amante de la fotografía y la tecnología. Espero dejar mi granito de arena y que este pueda servir de ayuda.