/* * This file is part of the usbcangpio project. * Copyright 2026 Edward V. Emelianov . * * 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 "ringbuffer.h" #include "usart.h" // We share DMAch4/5 for both USARTs, so only one can work in a time!!! #define USARTSNO 2 // buffers for DMA or interrupt-driven data management // here we use INDEX (usart number minus 1) static uint8_t inbuffer[DMARXBUFSZ]; // DMA in buffer static uint8_t rbbuffer[RXRBSZ]; // for in ringbuffer static uint8_t outbuffer[DMATXBUFSZ]; // DMA out buffer static uint8_t TXrdy = 1; // TX DMA ready static uint8_t RXrdy = 0; // == 1 when got IDLE or '\n' (only when `monitoring` is on static uint8_t textformat = 0; // out by '\n'-terminated lines static uint8_t monitor = 0; // monitor USART rx static int dma_read_idx = 0; // start of data in DMA inbuffers static int curUSARTidx = -1; // working USART index (0/1), -1 if unconfigured static ringbuffer RBin = {.data = rbbuffer, .length = RXRBSZ}; static volatile USART_TypeDef* const Usarts[USARTSNO] = {USART1, USART2}; static uint8_t const UIRQs[USARTSNO] = {USART1_IRQn, USART2_IRQn}; /** * @brief usart_config - configure US[A]RT based on usb_LineCoding * @param usartconf (io) - (modified to real speeds) * @return TRUE if all OK */ int usart_config(usartconf_t *usartconf){ SYSCFG->CFGR1 |= SYSCFG_CFGR1_USART1RX_DMA_RMP | SYSCFG_CFGR1_USART1TX_DMA_RMP; // both USARTs on DMA1ch4(tx)/5(rx) // all clocking and GPIO config should be done in hardware_setup() & gpio_reinit()! if(!usartconf) return FALSE; if(curUSARTidx != -1) usart_stop(); // disable previous USART if enabled uint8_t No = usartconf->No; if(No >= USARTSNO || No < 1 || usartconf->speed < USART_MIN_SPEED) return FALSE; --No; // now this is an INDEX! volatile USART_TypeDef *U = Usarts[No]; uint32_t peripheral_clock = 48000000; // Disable USART while configuring U->CR1 = 0; U->ICR = 0xFFFFFFFF; // Clear all interrupt flags // Assuming oversampling by 16 (default after reset). For higher baud rates you might use by 8. U->BRR = peripheral_clock / (usartconf->speed); usartconf->speed= peripheral_clock / U->BRR; // fix for real speed uint32_t cr1 = 0; // format: 8N1, so CR2 used only for character match (if need) if(usartconf->monitor){ if(usartconf->textproto){ U->CR2 = USART_CR2_ADD_VAL('\n'); cr1 |= USART_CR1_CMIE; }else cr1 |= USART_CR1_IDLEIE; // monitor binary data by IDLE flag } textformat = usartconf->textproto; monitor = usartconf->monitor; // Enable transmitter, receiver, and interrupts (optional) if(usartconf->Rxen) cr1 |= USART_CR1_RE; if(usartconf->Txen){ cr1 |= USART_CR1_TE; // DMA Tx volatile DMA_Channel_TypeDef *T = DMA1_Channel4; T->CCR = 0; T->CPAR = (uint32_t) &U->TDR; T->CCR = DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TCIE; } // Main config U->CR1 = cr1; curUSARTidx = No; return TRUE; } // start NUMBER int usart_start(){ if(curUSARTidx == -1) return FALSE; volatile USART_TypeDef *U = Usarts[curUSARTidx]; if(monitor) NVIC_EnableIRQ(UIRQs[curUSARTidx]); NVIC_EnableIRQ(DMA1_Channel4_5_IRQn); // reset Rx DMA if(U->CR1 & USART_CR1_RE){ volatile DMA_Channel_TypeDef *R = DMA1_Channel5; dma_read_idx = 0; R->CCR = 0; R->CPAR = (uint32_t) U->RDR; R->CMAR = (uint32_t) inbuffer; R->CNDTR = DMARXBUFSZ; R->CCR = DMA_CCR_MINC | DMA_CCR_CIRC | DMA_CCR_EN; RB_clearbuf(&RBin); } U->CR1 |= USART_CR1_UE; // enable USARTx U->ICR = 0xFFFFFFFF; // Clear flags // Wait for the idle frame to complete (optional) uint32_t tmout = 16000000; while(!(U->ISR & USART_ISR_TC)){ if (--tmout == 0) break; } TXrdy = 1; return TRUE; } /** * @brief usart_stop - turn off U[S]ART for given interface * @param ifNo - interface number */ void usart_stop(){ if(curUSARTidx == -1) return; Usarts[curUSARTidx]->CR1 &= ~USART_CR1_UE; NVIC_DisableIRQ(DMA1_Channel4_5_IRQn); NVIC_DisableIRQ(UIRQs[curUSARTidx]); curUSARTidx = -1; } /** * @brief usart_receive - receive data from USART by user request (return none if monitor == 1) * @param buf (io) - user buffer * @param len - `buf` length * @return -1 if USART not configured or amount of data from ringbuffer */ int usart_receive(uint8_t *buf, int len){ if(curUSARTidx == -1) return -1; if(textformat){ int toN = RB_datalento(&RBin, '\n'); if(toN == 0) return 0; if(toN < len) len = toN; // read only until '\n' in text format } int got = RB_read(&RBin, buf, len); if(got < 0) got = 0; return got; } /** * @brief usart_process - send/receive processing * Try to send data from output ringbuffer, check input DMA buffer and full input ringbuffer */ /** * @brief usart_process - USART data processing * @param buf - buffer for "async" messages (when monitor==1) * @param len - length of `buf` * @return amount of bytes read or -1 if USART isn't active */ int usart_process(uint8_t *buf, int len){ if(curUSARTidx == -1 || !(Usarts[curUSARTidx]->CR1 & USART_CR1_UE)) return -1; // none activated or started int ret = 0; // returned value // Input data int write_idx = DMARXBUFSZ - DMA1_Channel5->CNDTR; // next symbol to be written int available = (write_idx - dma_read_idx); // length of data available if(available < 0) available += DMARXBUFSZ; // write to the left of read if(available){ if(RXrdy){ if(buf && len > 0){ if(len < available) available = len; }else RXrdy = 0; } // TODO: force copying data to "async" buffer in case of overflow danger if(available >= (DMARXBUFSZ/2) || RXrdy){ // enough data or lonely couple of bytes but need to show // copy data in one or two chunks (wrap handling) int wrOK = FALSE; if(dma_read_idx + available <= DMARXBUFSZ){ // head before tail if(RXrdy){ memcpy(buf, &inbuffer[dma_read_idx], available); ret = available; wrOK = TRUE; }else{ if(available == RB_write(&RBin, &inbuffer[dma_read_idx], available)) wrOK = TRUE; } }else{ // head after tail - two chunks int first = DMARXBUFSZ - dma_read_idx; if(RXrdy){ memcpy(buf, &inbuffer[dma_read_idx], first); memcpy(buf, inbuffer, available - first); ret = available; wrOK = TRUE; }else{ if((first == RB_write(&RBin, &inbuffer[dma_read_idx], first)) && (available - first) == RB_write(&RBin, inbuffer, available - first)) wrOK = TRUE; } } if(wrOK){ RXrdy = 0; dma_read_idx = write_idx; // update read pointer } } } #if 0 // Output data if(TXrdy){ // ready to send new data - here we can process RBout, if have int got = RB_read... if(got > 0){ // send next data portion volatile DMA_Channel_TypeDef *T = cfg->dma_tx_channel; T->CCR &= ~DMA_CCR_EN; T->CMAR = (uint32_t) outbuffers[i]; T->CNDTR = got; TXrdy = 0; T->CCR |= DMA_CCR_EN; // start new transmission } } #endif return ret; } // send data buffer int usart_send(const uint8_t *data, int len){ if(curUSARTidx == -1 || !data || len < 1) return 0; if(TXrdy == 0) return -1; if(len > DMATXBUFSZ) len = DMATXBUFSZ; memcpy(outbuffer, data, len); volatile DMA_Channel_TypeDef *T = DMA1_Channel4; T->CCR &= ~DMA_CCR_EN; T->CMAR = (uint32_t) outbuffer; T->CNDTR = len; TXrdy = 0; T->CCR |= DMA_CCR_EN; // start new transmission return len; } /** * @brief usart_isr - U[S]ART interrupt: IDLE (for DMA-driven) or * @param ifno - interface index */ static void usart_isr(){ if(curUSARTidx == -1) return; // WTF??? volatile USART_TypeDef *U = Usarts[curUSARTidx]; // IDLE active when we monitor binary data if((U->ISR & USART_ISR_IDLE) && (U->CR1 & USART_CR1_IDLEIE)){ // try to send collected data (DMA-driven) RXrdy = 1; // seems like data portion is over - try to send it } if((U->ISR & USART_ISR_CMF) && (U->CR1 & USART_CR1_CMIE)){ // character match -> the same for text data RXrdy = 1; } U->ICR = 0xffffffff; // clear all flags } void dma1_channel4_5_isr(){ // TX ready, channel5 if(DMA1->ISR & DMA_ISR_TCIF5){ TXrdy = 1; DMA1->IFCR = DMA_IFCR_CTCIF5; DMA1_Channel4->CCR &= ~DMA_CCR_EN; // disable DMA channel until next send } } // U[S]ART interrupts void usart1_isr() {usart_isr();} void usart2_isr() {usart_isr();}