Este blog está dedicado a mis experiencias, proyectos, dificultades y demás en todo lo relacionado a la electrónica y la programación en general sobre GNU/Linux

ARM Cortex-M, Nivel: Básico, STM32

Serial lib para STM32F1 con libOpenCM3

Nivel: Básico

¿Qué tengo que saber para este post?

—————————————

Voy a hacer una pausa en mi linea de tutoriales de programación de STM32F1, para comentarles de mi experiencia publicando mi primera librería para una plataforma (en este caso PlatformIO). Tras darle un par de vueltas al asunto no dudé más y decidí subir y compartir con el mundo mis funciones para la utilización del puerto serie (USART).

Seguramente, si alguno leyó este blog o lo sigue, recordarán que publiqué funciones para utilizar la USART en Atmega8 y 328p. En este caso es casi lo mismo, pero le agregué un par de giros de tuerca, porque con los años uno va adquiriendo experiencia y encuentra nuevas formas de hacer las cosas. Por eso, cuando se trata de hacer algo tan simple como un buffer circular y acceder a un periférico tan vital (y simple, si se quiere), está bueno reinventarse.

Por ello, hoy les traigo algo mucho más pulido, el funcionamiento sigue siendo el mismo, nada más que el STM32F1 tiene 3 USARTs (USART1, USART2 y USART3 respectivamente), por lo tanto las funciones ahora deben indicar qué USART vamos a estar utilizando. No voy a ahondar mucho en detalles ya que lo veremos en su momento en este blog cuando lleguemos con los tutoriales.

La funciones proporcionadas

Las funciones que cree son las siguientes y voy a puntearlas de a una (aunque están sujetas a mejoras y cambios):

void serial_begin(const uint32_t usartx, const baud_t baudrate);
uint16_t serial_available(const uint32_t usartx);
uint8_t serial_read(const uint32_t usartx);
uint16_t serial_sendable(const uint32_t usartx);
bool serial_write(const uint32_t usartx, const uint8_t data);
uint32_t serial_puts(const uint32_t usartx, const char *str);
uint32_t serial_send_data(const uint32_t usartx, const void* data, uint32_t size);

serial_begin()

Ya se imaginan de que hablamos, si observan, todas son bastante Arduino-like, pero básicamente hay que enviarle USART1/2/3 y luego la velocidad de transmisión. Y si son como yo, salvo 9600 y 115200, no recuerdan ninguna de las otras velocidades estándar, por eso definí un enum baud_t, cuyos valores se definen como BAUDXXXX, acá les dejo un ejemplo de implementación:

// Ejemplo de implementación:
serial_begin(USART1, BAUD38K4);

La habilitación de clock y pines se hacen dentro de esta función, no hace falta agregar nada más. Obviamente, al igual que la que estaba hecha para el AVR, utiliza las interrupciones de las USARTs, pero no setea prioridades, eso debe hacerse aparte si se desea, para más info, pueden ver este post anterior. Como estándar de facto se configura siempre en 8 bits de datos, sin paridad y 1 bit de stop (8,N,1).

Protocolo estándar

serial_available() y serial_sendable()

La primera de estas, para los que usan el buen Arduino, ya saben lo que hace: simplemente indica la cantidad de datos que hay por leer (en el buffer de recepción). Es decir, la cantidad de bytes que recibimos.

La otra función, es una especie de contraparte: cuantos bytes tenemos libres en el buffer de transmisión. En otras palabras, cuantos datos podemos enviar de un tirón por la USART. Para evitar que se nos desborde el buffer. Esto es un agregado que antes nos estaba.

serial_read() y serial_write()

Tampoco hay mucho que agregar acá, el comportamiento es el esperado, read() nos devuelve el primer dato disponible en el buffer de recepción y write() escribe un dato en la primera posición disponible del buffer de transmisión, devolviendo true si pudo entrar y false si no había espacio.

serial_puts() y serial_send_data()

Bueno, acá tenemos la función puts() que almacena un string en el buffer de transmisión, es decir todos los caracteres hasta el '\0' y devuelve la cantidad de caracteres que entraron en el buffer. Si no coincide con la longitud de la cadena, debemos intentar reenviar los caracteres restantes cuando se libere el espacio.

En cuanto a send_data(), está diseñada para enviar bytes en crudo. Recibe como parámetro un puntero genérico y su longitud en bytes, principalmente para enviar estructuras completas y/o aplicar protocolos personalizados. También devuelve la cantidad de bytes que entraron en el buffer para enviar el resto en otro momento.

Salvando espacio

Como yo no sé si quieren usar una o las 3 USARTs, y considerando que las 3 tienen sus propios buffers de transmisión y recepción independientes, esta lib ocupa bastante espacio. Por lo tanto agregué un define en serial.h para que puedan especificar cual o cuales USARTs van a utilizar:

// default:
#define USING (USE_ALL_USARTS)
// Alternativas:
// #define USING (USE_USART1)
// #define USING (USE_USART1|USE_USART3)

Espero que sea lo suficientemente obvio como para que esto suene redundante. Lo que pongan en USING habilitara (o no) parte del código y los buffers de entrada y salida. Por ello es importante declararlos, aunque estos MCUs tiene mucha RAM y FLASH. Los buffers están definidos en 128 bytes por default y DEBEN, siempre, siempre, siempre, ser potencia de 2. Es decir: 2, 4, 8, 16, 32, etc. De todas formas, si ponen un valor que no sea potencia de 2 les dará un error de compilación. Los defines son MAX_BUFFER_TX1/2/3 y MAX_BUFFER_RX1/2/3:

// Tamaños de los buffers de la USART1:
#define MAX_BUFFER_TX1 128UL
#define MAX_BUFFER_RX1 128UL

Ejemplo e instalación en PlatformIO

Como les comenté (aunque tuve varios errores de novato) registré esta lib en PlatformIO para que puedan utilizarla y agregarla fácilmente. Lo que deben hacer es crear un proyecto nuevo con la BluePill (u otro STMF10x) y elegir libOpenCM3 como framework. Una vez creado el proyecto en el archivo platformio.ini deben agregar:

lib_deps = Serial-libOpenCM3

Esto creará una copia local en su proyecto de la lib, y es lo recomendable, porque si lo instalaran con el gestor de librerías tendrían una copia global para todos sus proyectos y si editaran el serial.h, ¡lo editarían para todos los proyectos!

Y luego sólo necesitan agregar a sus includes:

#include <serial.h>

Y ahora a disfrutar usar la lib en un ejemplo simple, por eso tenemos el clásico ejemplo de enviar mayúsculas cuando recibimos minúsculas y viceversa. Agregué un par de validaciones para que vean como se usarían:

#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <string.h>
#include <serial.h>

int main()
{
    const char *saludo = "Hola, desde la Bluepill!\r\n";
    uint32_t saludo_len = strlen(saludo);
    const char *otromsj = "Este es otro mensaje desde la Bluepill...\r\n";
    uint32_t otromsj_len = strlen(otromsj);

    rcc_clock_setup_in_hse_8mhz_out_72mhz();

    // Se configura el led de la bluepill, indica si hubo un error al mandar el string
    rcc_periph_clock_enable(RCC_GPIOC);
    gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
    gpio_set(GPIOC, GPIO13);

    // Se inicializa la USART
    serial_begin(USART1, BAUD38K4);

    // Si la cantidad de caracteres enviados no coincide con la longitud de la cadena:
    if (saludo_len != serial_puts(USART1, saludo))
        gpio_clear(GPIOC, GPIO13); // ups! ERROR no había suficiente espacio en el buffer

    // Si hay suficiente espacio en el buffer se manda el próximo mensaje
    if (otromsj_len < serial_sendable(USART1))
        serial_puts(USART1, otromsj);

    while (1)
    {
        if (serial_available(USART1) > 0)
        {                                              // Si hay datos en el buffer de entrada:
            uint8_t c = serial_read(USART1);           // Se lee el dato del buffer
            if (c >= 'A' && c <= 'Z')                  // Si llegó una mayúscula:
                serial_write(USART1, c + ('a' - 'A')); //      se envía su minúscula
            else if (c >= 'a' && c <= 'z')             // Si llegó una minúscula:
                serial_write(USART1, c - ('a' - 'A')); //      se envía su mayúscula
            else                                       // cualquier otro carácter:
                serial_write(USART1, c);               //      se envía eco
        }
    }
}

Si quieren chusmear o aportar a este proyecto les dejo el repo de gitlab, donde puede descargarlo y usarlo sin PlatformIO, con makefiles al estilo libOpenCM3. Eso sí, deben tener las herramientas de compilación y grabado de firmware instaladas y configuradas.

https://gitlab.com/tute-avalos/serial-api-libopencm3

Dejar una respuesta