From 8b7a171b25ac6743921d3bae882dc00d5bcd4774 Mon Sep 17 00:00:00 2001 From: Silvano Seva Date: Fri, 22 Dec 2023 19:50:13 +0100 Subject: [PATCH] USART1 driver for AT32F421 MCU --- meson.build | 1 + platform/mcu/AT32F421/drivers/USART1.cpp | 197 +++++++++++++++++++++++ platform/mcu/AT32F421/drivers/USART1.h | 81 ++++++++++ 3 files changed, 279 insertions(+) create mode 100644 platform/mcu/AT32F421/drivers/USART1.cpp create mode 100644 platform/mcu/AT32F421/drivers/USART1.h diff --git a/meson.build b/meson.build index 03e68d7b..02b0467d 100644 --- a/meson.build +++ b/meson.build @@ -261,6 +261,7 @@ at32f421_src = ['platform/mcu/AT32F421/boot/startup.cpp', 'platform/mcu/AT32F421/boot/libc_integration.cpp', 'platform/mcu/AT32F421/drivers/gpio.c', 'platform/mcu/AT32F421/drivers/delays.cpp', + 'platform/mcu/AT32F421/drivers/USART1.cpp', 'platform/mcu/CMSIS/Device/Artery/AT32F421/Source/system_at32f421.c'] at32f421_inc = ['platform/mcu/AT32F421', diff --git a/platform/mcu/AT32F421/drivers/USART1.cpp b/platform/mcu/AT32F421/drivers/USART1.cpp new file mode 100644 index 00000000..14f9dbe8 --- /dev/null +++ b/platform/mcu/AT32F421/drivers/USART1.cpp @@ -0,0 +1,197 @@ +/*************************************************************************** + * Copyright (C) 2023 by Federico Amedeo Izzo IUNUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * 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 "USART1.h" + +using namespace miosix; + +static constexpr int rxQueueMin = 16; // Minimum queue size + +static DynUnsyncQueue< char > rxQueue(128); // Queue for incoming data +static Thread *rxWaiting = 0; // Thread waiting on RX +static bool rxIdle = true; // Flag for RX idle +static FastMutex rxMutex; // Mutex locked during reception +static 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) + */ +static inline void waitSerialTxFifoEmpty() +{ + while((USART1->sts & (1 << 6)) == 0) ; +} + +/** + * \internal + * Interrupt handler function, called by USART1_IRQHandler. + */ +void __attribute__((noinline)) usart1irqImpl() +{ + unsigned int status = USART1->sts; + char c; + + // New character received + if(status & (1 << 5)) + { + //Always read data, since this clears interrupt flags + c = USART1->dt; + + //If no error put data in buffer + if((status & (1 << 1)) == 0) + { + if(rxQueue.tryPut(c) == false) {/*fifo overflow*/} + } + + rxIdle = false; + } + + // Idle line + if(status & (1 << 4)) + { + c = USART1->dt; //clears interrupt flags + rxIdle = true; + } + + // Enough data in buffer or idle line, awake thread + if((status & (1 << 4)) || rxQueue.size() >= rxQueueMin) + { + if(rxWaiting) + { + rxWaiting->IRQwakeup(); + if(rxWaiting->IRQgetPriority()> + Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + rxWaiting = 0; + } + } +} + +void __attribute__((naked)) USART1_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z13usart1irqImplv"); + restoreContext(); +} + + +void usart1_init(unsigned int baudrate) +{ + CRM->apb2en |= (1 << 14); + __DSB(); + + // Get current frequency of APB2 clock + unsigned int freq = SystemCoreClock; + if(CRM->cfg_bit.apb2div != 0) + freq /= ((CRM->cfg_bit.apb2div & 0x03) + 1); + + const unsigned int quot = 2*freq/baudrate; // 2*freq for round to nearest + USART1->baudr = quot/2 + (quot & 1); // Round to nearest + USART1->ctrl1 = (1 << 13) // Enable port + | (1 << 5) // Interrupt on data received + | (1 << 4) // Interrupt on idle line + | (1 << 3) // Transmission enbled + | (1 << 2); // Reception enabled + + NVIC_SetPriority(USART1_IRQn, 15); // Lowest priority for serial + NVIC_EnableIRQ(USART1_IRQn); +} + +void usart1_terminate() +{ + waitSerialTxFifoEmpty(); + + NVIC_DisableIRQ(USART1_IRQn); + + USART1->ctrl1 &= ~(1 << 13); + CRM->apb2en &= ~(1 << 14); + __DSB(); +} + +ssize_t usart1_readBlock(void *buffer, size_t size, off_t where) +{ + (void) 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 usart1_writeBlock(void *buffer, size_t size, off_t where) +{ + (void) where; + + miosix::Lock< miosix::FastMutex > l(txMutex); + const char *buf = reinterpret_cast< const char* >(buffer); + for(size_t i = 0; i < size; i++) + { + while((USART1->sts & (1 << 7)) == 0) ; + USART1->dt = *buf++; + } + + return size; +} + +void usart1_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((USART1->sts & (1 << 7)) == 0) ; + USART1->dt = *str++; + } + + waitSerialTxFifoEmpty(); + if(interrupts) fastEnableInterrupts(); +} diff --git a/platform/mcu/AT32F421/drivers/USART1.h b/platform/mcu/AT32F421/drivers/USART1.h new file mode 100644 index 00000000..b59f20a6 --- /dev/null +++ b/platform/mcu/AT32F421/drivers/USART1.h @@ -0,0 +1,81 @@ +/*************************************************************************** + * Copyright (C) 2023 by Federico Amedeo Izzo IUNUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * 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 USART1_H +#define USART1_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Initialise USART1 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 usart1_init(unsigned int baudrate); + +/** + * Shut down USART1 peripheral. + */ +void usart1_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 usart1_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 usart1_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 usart1_IRQwrite(const char *str); + +#ifdef __cplusplus +} +#endif + +#endif /* USART1_H */