Nivel: Básico
¿Qué tengo que saber para este post?
- Conocimientos básicos de programación en C.
- Haber leído algo de los tutoriales de programación de Cortex-M con software libre (no necesariamente, pero recomendable).
—————————————
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).
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.
1 Pingback