From eb474d6bee6cd68588422a539940443762f69880 Mon Sep 17 00:00:00 2001 From: Silvano Seva Date: Wed, 7 Jul 2021 22:36:36 +0200 Subject: [PATCH] Driver for STM32F4 USART3, can be used as a debug serial interface on MD3x0 devices without GPS and is enabled by defining the macro MD3x0_ENABLE_DBG --- meson.build | 1 + openrtx/src/threads.c | 4 +- platform/drivers/GPS/GPS_MDx.cpp | 6 +- platform/mcu/STM32F4xx/drivers/USART3.cpp | 193 ++++++++++++++++++++++ platform/mcu/STM32F4xx/drivers/USART3.h | 84 ++++++++++ 5 files changed, 285 insertions(+), 3 deletions(-) create mode 100644 platform/mcu/STM32F4xx/drivers/USART3.cpp create mode 100644 platform/mcu/STM32F4xx/drivers/USART3.h diff --git a/meson.build b/meson.build index c307ba67..478ac40e 100644 --- a/meson.build +++ b/meson.build @@ -140,6 +140,7 @@ stm32f405_src = ['platform/mcu/STM32F4xx/boot/startup.cpp', 'platform/mcu/STM32F4xx/drivers/delays.cpp', 'platform/mcu/STM32F4xx/drivers/rtc.c', 'platform/mcu/STM32F4xx/drivers/SPI2.c', + 'platform/mcu/STM32F4xx/drivers/USART3.cpp', 'platform/mcu/CMSIS/Device/ST/STM32F4xx/Source/system_stm32f4xx.c'] stm32f405_inc = ['platform/mcu/CMSIS/Include', diff --git a/openrtx/src/threads.c b/openrtx/src/threads.c index d8c126fc..7d97e21a 100644 --- a/openrtx/src/threads.c +++ b/openrtx/src/threads.c @@ -261,7 +261,7 @@ void *rtx_task(void *arg) } } -#ifdef HAS_GPS +#if defined(HAS_GPS) && !defined(MD3x0_ENABLE_DBG) /** * \internal Task function for parsing GPS data and updating radio state. */ @@ -345,7 +345,7 @@ void create_threads() pthread_attr_setstacksize(&kbd_attr, KBD_TASK_STKSIZE); pthread_create(&kbd_thread, &kbd_attr, kbd_task, NULL); -#ifdef HAS_GPS +#if defined(HAS_GPS) && !defined(MD3x0_ENABLE_DBG) // Create GPS thread pthread_t gps_thread; pthread_attr_t gps_attr; diff --git a/platform/drivers/GPS/GPS_MDx.cpp b/platform/drivers/GPS/GPS_MDx.cpp index a98f1b5e..2a1c9a24 100644 --- a/platform/drivers/GPS/GPS_MDx.cpp +++ b/platform/drivers/GPS/GPS_MDx.cpp @@ -94,7 +94,11 @@ void __attribute__((naked)) USART1_IRQHandler() #endif { saveContext(); + #if defined(PLATFORM_MD3x0) && defined(MD3x0_ENABLE_DBG) + asm volatile("bl _Z13usart3irqImplv"); + #else asm volatile("bl _Z12GpsUsartImplv"); + #endif restoreContext(); } @@ -117,7 +121,7 @@ void gps_init(const uint16_t baud) PORT->BRR = quot/2 + (quot & 1); PORT->CR3 |= USART_CR3_ONEBIT; PORT->CR1 = USART_CR1_RE - | USART_CR1_RXNEIE; + | USART_CR1_RXNEIE; #ifdef PLATFORM_MD3x0 NVIC_ClearPendingIRQ(USART3_IRQn); diff --git a/platform/mcu/STM32F4xx/drivers/USART3.cpp b/platform/mcu/STM32F4xx/drivers/USART3.cpp new file mode 100644 index 00000000..2545ecbe --- /dev/null +++ b/platform/mcu/STM32F4xx/drivers/USART3.cpp @@ -0,0 +1,193 @@ +/*************************************************************************** + * Copyright (C) 2021 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * Adapted from STM32 USART driver for miosix kernel written by Federico * + * Terraneo. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include +#include +#include +#include "USART3.h" + +#define U3TXD GPIOD,8 +#define U3RXD GPIOD,8 + +using namespace miosix; + +static constexpr int rxQueueMin = 16; // Minimum queue size + +DynUnsyncQueue< char > rxQueue(128); // Queue for incoming data +Thread *rxWaiting = 0; // Thread waiting on RX +bool rxIdle = true; // Flag for RX idle +FastMutex rxMutex; // Mutex locked during reception +FastMutex txMutex; // Mutex locked during transmission + +/** + * \internal + * Wait until all characters have been written to the serial port. + * Needs to be callable from interrupts disabled (it is used in IRQwrite) + */ +inline void waitSerialTxFifoEmpty() +{ + while((USART3->SR & USART_SR_TC) == 0) ; +} + +/** + * \internal + * Interrupt handler function, called by USART3_IRQHandler. + */ +void __attribute__((noinline)) usart3irqImpl() +{ + unsigned int status = USART3->SR; + char c; + + if(status & USART_SR_RXNE) + { + //Always read data, since this clears interrupt flags + c = USART3->DR; + + //If no error put data in buffer + if((status & USART_SR_FE) == 0) + { + if(rxQueue.tryPut(c) == false) {/*fifo overflow*/} + } + + rxIdle = false; + } + + if(status & USART_SR_IDLE) + { + c = USART3->DR; //clears interrupt flags + rxIdle = true; + } + + if((status & USART_SR_IDLE) || rxQueue.size() >= rxQueueMin) + { + //Enough data in buffer or idle line, awake thread + if(rxWaiting) + { + rxWaiting->IRQwakeup(); + if(rxWaiting->IRQgetPriority()> + Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + rxWaiting = 0; + } + } +} + +void usart3_init(unsigned int baudrate) +{ + gpio_setMode(U3RXD, ALTERNATE); + gpio_setMode(U3TXD, ALTERNATE); + gpio_setAlternateFunction(U3RXD, 7); + gpio_setAlternateFunction(U3TXD, 7); + + RCC->APB1ENR |= RCC_APB1ENR_USART3EN; + __DSB(); + + unsigned int freq = SystemCoreClock; + if(RCC->CFGR & RCC_CFGR_PPRE1_2) freq/= 1<<(((RCC->CFGR >> 10) & 0x3)+1); + + const unsigned int quot = 2*freq/baudrate; // 2*freq for round to nearest + USART3->BRR = quot/2 + (quot & 1); // Round to nearest + USART3->CR3 |= USART_CR3_ONEBIT; + USART3->CR1 = USART_CR1_UE // Enable port + | USART_CR1_RXNEIE // Interrupt on data received + | USART_CR1_IDLEIE // Interrupt on idle line + | USART_CR1_TE // Transmission enbled + | USART_CR1_RE; // Reception enabled + + NVIC_SetPriority(USART3_IRQn,15); // Lowest priority for serial + NVIC_EnableIRQ(USART3_IRQn); +} + +void usart3_terminate() +{ + waitSerialTxFifoEmpty(); + + NVIC_DisableIRQ(USART3_IRQn); + + USART3->CR1 &= ~USART_CR1_UE; + RCC->APB1ENR &= ~RCC_APB1ENR_USART3EN; + __DSB(); +} + +ssize_t usart3_readBlock(void *buffer, size_t size, off_t where) +{ + miosix::Lock< miosix::FastMutex > l(rxMutex); + char *buf = reinterpret_cast< char* >(buffer); + size_t result = 0; + FastInterruptDisableLock dLock; + + for(;;) + { + //Try to get data from the queue + for(; result < size; result++) + { + if(rxQueue.tryGet(buf[result])==false) break; + //This is here just not to keep IRQ disabled for the whole loop + FastInterruptEnableLock eLock(dLock); + } + if(rxIdle && result > 0) break; + if(result == size) break; + //Wait for data in the queue + do { + rxWaiting = Thread::IRQgetCurrentThread(); + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } while(rxWaiting); + } + + return result; +} + +ssize_t usart3_writeBlock(void *buffer, size_t size, off_t where) +{ + miosix::Lock< miosix::FastMutex > l(txMutex); + const char *buf = reinterpret_cast< const char* >(buffer); + for(size_t i = 0; i < size; i++) + { + while((USART3->SR & USART_SR_TXE) == 0) ; + USART3->DR = *buf++; + } + + return size; +} + +void usart3_IRQwrite(const char *str) +{ + // We can reach here also with only kernel paused, so make sure + // interrupts are disabled. This is important for the DMA case + bool interrupts = areInterruptsEnabled(); + if(interrupts) fastDisableInterrupts(); + + while(*str) + { + while((USART3->SR & USART_SR_TXE) == 0) ; + USART3->DR = *str++; + } + + waitSerialTxFifoEmpty(); + if(interrupts) fastEnableInterrupts(); +} diff --git a/platform/mcu/STM32F4xx/drivers/USART3.h b/platform/mcu/STM32F4xx/drivers/USART3.h new file mode 100644 index 00000000..df000f24 --- /dev/null +++ b/platform/mcu/STM32F4xx/drivers/USART3.h @@ -0,0 +1,84 @@ +/*************************************************************************** + * Copyright (C) 2021 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * Adapted from STM32 USART driver for miosix kernel written by Federico * + * Terraneo. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef USART3_H +#define USART3_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Initialise USART3 peripheral with a given baud rate. Serial communication is + * configured for 8 data bits, no parity, one stop bit. + * + * @param baudrate: serial port baud rate, in bits per second. + */ +void usart3_init(unsigned int baudrate); + +/** + * Shut down USART3 peripheral. + */ +void usart3_terminate(); + +/** + * Read a block of data. + * + * \param buffer buffer where read data will be stored. + * \param size buffer size. + * \param where where to read from. + * \return number of bytes read or a negative number on failure. Note that + * it is normal for this function to return less character than the amount + * asked. + */ +ssize_t usart3_readBlock(void *buffer, size_t size, off_t where); + +/** + * Write a block of data. + * + * \param buffer buffer where take data to write. + * \param size buffer size. + * \param where where to write to. + * \return number of bytes written or a negative number on failure. + */ +ssize_t usart3_writeBlock(void *buffer, size_t size, off_t where); + +/** + * Write a string. + * Can be used to write debug information before the kernel is started or in + * case of serious errors, right before rebooting. + * Can ONLY be called when the kernel is not yet started, paused or within + * an interrupt. This default implementation ignores writes. + * + * \param str the string to write. The string must be NUL terminated. + */ +void usart3_IRQwrite(const char *str); + +#ifdef __cplusplus +} +#endif + +#endif /* USART3_H */