You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							433 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
	
	
							433 lines
						
					
					
						
							12 KiB
						
					
					
				| // IRQs: USART1, USART2, USART3, UART5
 | |
| 
 | |
| // ***************************** Definitions *****************************
 | |
| #define FIFO_SIZE_INT 0x400U
 | |
| #define FIFO_SIZE_DMA 0x1000U
 | |
| 
 | |
| typedef struct uart_ring {
 | |
|   volatile uint16_t w_ptr_tx;
 | |
|   volatile uint16_t r_ptr_tx;
 | |
|   uint8_t *elems_tx;
 | |
|   uint32_t tx_fifo_size;
 | |
|   volatile uint16_t w_ptr_rx;
 | |
|   volatile uint16_t r_ptr_rx;
 | |
|   uint8_t *elems_rx;
 | |
|   uint32_t rx_fifo_size;
 | |
|   USART_TypeDef *uart;
 | |
|   void (*callback)(struct uart_ring*);
 | |
|   bool dma_rx;
 | |
| } uart_ring;
 | |
| 
 | |
| #define UART_BUFFER(x, size_rx, size_tx, uart_ptr, callback_ptr, rx_dma) \
 | |
|   uint8_t elems_rx_##x[size_rx]; \
 | |
|   uint8_t elems_tx_##x[size_tx]; \
 | |
|   uart_ring uart_ring_##x = {  \
 | |
|     .w_ptr_tx = 0, \
 | |
|     .r_ptr_tx = 0, \
 | |
|     .elems_tx = ((uint8_t *)&elems_tx_##x), \
 | |
|     .tx_fifo_size = size_tx, \
 | |
|     .w_ptr_rx = 0, \
 | |
|     .r_ptr_rx = 0, \
 | |
|     .elems_rx = ((uint8_t *)&elems_rx_##x), \
 | |
|     .rx_fifo_size = size_rx, \
 | |
|     .uart = uart_ptr, \
 | |
|     .callback = callback_ptr, \
 | |
|     .dma_rx = rx_dma \
 | |
|   };
 | |
| 
 | |
| 
 | |
| // ***************************** Function prototypes *****************************
 | |
| void uart_init(uart_ring *q, int baud);
 | |
| 
 | |
| bool getc(uart_ring *q, char *elem);
 | |
| bool putc(uart_ring *q, char elem);
 | |
| 
 | |
| void puts(const char *a);
 | |
| void puth(unsigned int i);
 | |
| void hexdump(const void *a, int l);
 | |
| 
 | |
| void debug_ring_callback(uart_ring *ring);
 | |
| 
 | |
| // ******************************** UART buffers ********************************
 | |
| 
 | |
| // esp_gps = USART1
 | |
| UART_BUFFER(esp_gps, FIFO_SIZE_DMA, FIFO_SIZE_INT, USART1, NULL, true)
 | |
| 
 | |
| // lin1, K-LINE = UART5
 | |
| // lin2, L-LINE = USART3
 | |
| UART_BUFFER(lin1, FIFO_SIZE_INT, FIFO_SIZE_INT, UART5, NULL, false)
 | |
| UART_BUFFER(lin2, FIFO_SIZE_INT, FIFO_SIZE_INT, USART3, NULL, false)
 | |
| 
 | |
| // debug = USART2
 | |
| UART_BUFFER(debug, FIFO_SIZE_INT, FIFO_SIZE_INT, USART2, debug_ring_callback, false)
 | |
| 
 | |
| uart_ring *get_ring_by_number(int a) {
 | |
|   uart_ring *ring = NULL;
 | |
|   switch(a) {
 | |
|     case 0:
 | |
|       ring = &uart_ring_debug;
 | |
|       break;
 | |
|     case 1:
 | |
|       ring = &uart_ring_esp_gps;
 | |
|       break;
 | |
|     case 2:
 | |
|       ring = &uart_ring_lin1;
 | |
|       break;
 | |
|     case 3:
 | |
|       ring = &uart_ring_lin2;
 | |
|       break;
 | |
|     default:
 | |
|       ring = NULL;
 | |
|       break;
 | |
|   }
 | |
|   return ring;
 | |
| }
 | |
| 
 | |
| // ***************************** Interrupt handlers *****************************
 | |
| 
 | |
| void uart_tx_ring(uart_ring *q){
 | |
|   ENTER_CRITICAL();
 | |
|   // Send out next byte of TX buffer
 | |
|   if (q->w_ptr_tx != q->r_ptr_tx) {
 | |
|     // Only send if transmit register is empty (aka last byte has been sent)
 | |
|     if ((q->uart->SR & USART_SR_TXE) != 0) {
 | |
|       q->uart->DR = q->elems_tx[q->r_ptr_tx];   // This clears TXE
 | |
|       q->r_ptr_tx = (q->r_ptr_tx + 1U) % q->tx_fifo_size;
 | |
|     }
 | |
| 
 | |
|     // Enable TXE interrupt if there is still data to be sent
 | |
|     if(q->r_ptr_tx != q->w_ptr_tx){
 | |
|       q->uart->CR1 |= USART_CR1_TXEIE;
 | |
|     } else {
 | |
|       q->uart->CR1 &= ~USART_CR1_TXEIE;
 | |
|     }
 | |
|   }
 | |
|   EXIT_CRITICAL();
 | |
| }
 | |
| 
 | |
| void uart_rx_ring(uart_ring *q){
 | |
|   // Do not read out directly if DMA enabled
 | |
|   if (q->dma_rx == false) {
 | |
|     ENTER_CRITICAL();
 | |
| 
 | |
|     // Read out RX buffer
 | |
|     uint8_t c = q->uart->DR;  // This read after reading SR clears a bunch of interrupts
 | |
| 
 | |
|     uint16_t next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size;
 | |
|     // Do not overwrite buffer data
 | |
|     if (next_w_ptr != q->r_ptr_rx) {
 | |
|       q->elems_rx[q->w_ptr_rx] = c;
 | |
|       q->w_ptr_rx = next_w_ptr;
 | |
|       if (q->callback != NULL) {
 | |
|         q->callback(q);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     EXIT_CRITICAL();
 | |
|   }
 | |
| }
 | |
| 
 | |
| // This function should be called on:
 | |
| // * Half-transfer DMA interrupt
 | |
| // * Full-transfer DMA interrupt
 | |
| // * UART IDLE detection
 | |
| uint32_t prev_w_index = 0;
 | |
| void dma_pointer_handler(uart_ring *q, uint32_t dma_ndtr) {
 | |
|   ENTER_CRITICAL();
 | |
|   uint32_t w_index = (q->rx_fifo_size - dma_ndtr);
 | |
|   
 | |
|   // Check for new data
 | |
|   if (w_index != prev_w_index){
 | |
|     // Check for overflow
 | |
|     if (
 | |
|       ((prev_w_index < q->r_ptr_rx) && (q->r_ptr_rx <= w_index)) ||                               // No rollover
 | |
|       ((w_index < prev_w_index) && ((q->r_ptr_rx <= w_index) || (prev_w_index < q->r_ptr_rx)))    // Rollover
 | |
|     ){   
 | |
|       // We lost data. Set the new read pointer to the oldest byte still available
 | |
|       q->r_ptr_rx = (w_index + 1U) % q->rx_fifo_size;
 | |
|     }
 | |
| 
 | |
|     // Set write pointer
 | |
|     q->w_ptr_rx = w_index;
 | |
|   }
 | |
| 
 | |
|   prev_w_index = w_index;
 | |
|   EXIT_CRITICAL();
 | |
| }
 | |
| 
 | |
| // This read after reading SR clears all error interrupts. We don't want compiler warnings, nor optimizations
 | |
| #define UART_READ_DR(uart) volatile uint8_t t = (uart)->DR; UNUSED(t);
 | |
| 
 | |
| void uart_interrupt_handler(uart_ring *q) {
 | |
|   ENTER_CRITICAL();
 | |
| 
 | |
|   // Read UART status. This is also the first step necessary in clearing most interrupts
 | |
|   uint32_t status = q->uart->SR;
 | |
| 
 | |
|   // If RXNE is set, perform a read. This clears RXNE, ORE, IDLE, NF and FE
 | |
|   if((status & USART_SR_RXNE) != 0U){
 | |
|     uart_rx_ring(q);
 | |
|   }
 | |
| 
 | |
|   // Detect errors and clear them
 | |
|   uint32_t err = (status & USART_SR_ORE) | (status & USART_SR_NE) | (status & USART_SR_FE) | (status & USART_SR_PE);
 | |
|   if(err != 0U){
 | |
|     #ifdef DEBUG_UART
 | |
|       puts("Encountered UART error: "); puth(err); puts("\n");
 | |
|     #endif
 | |
|     UART_READ_DR(q->uart)
 | |
|   }
 | |
|   // Send if necessary
 | |
|   uart_tx_ring(q);
 | |
| 
 | |
|   // Run DMA pointer handler if the line is idle
 | |
|   if(q->dma_rx && (status & USART_SR_IDLE)){
 | |
|     // Reset IDLE flag
 | |
|     UART_READ_DR(q->uart)
 | |
| 
 | |
|     if(q == &uart_ring_esp_gps){
 | |
|       dma_pointer_handler(&uart_ring_esp_gps, DMA2_Stream5->NDTR);
 | |
|     } else {
 | |
|       #ifdef DEBUG_UART
 | |
|         puts("No IDLE dma_pointer_handler implemented for this UART.");
 | |
|       #endif
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   EXIT_CRITICAL();
 | |
| }
 | |
| 
 | |
| void USART1_IRQHandler(void) { uart_interrupt_handler(&uart_ring_esp_gps); }
 | |
| void USART2_IRQHandler(void) { uart_interrupt_handler(&uart_ring_debug); }
 | |
| void USART3_IRQHandler(void) { uart_interrupt_handler(&uart_ring_lin2); }
 | |
| void UART5_IRQHandler(void) { uart_interrupt_handler(&uart_ring_lin1); }
 | |
| 
 | |
| void DMA2_Stream5_IRQHandler(void) {
 | |
|   ENTER_CRITICAL();
 | |
| 
 | |
|   // Handle errors
 | |
|   if((DMA2->HISR & DMA_HISR_TEIF5) || (DMA2->HISR & DMA_HISR_DMEIF5) || (DMA2->HISR & DMA_HISR_FEIF5)){
 | |
|     #ifdef DEBUG_UART
 | |
|       puts("Encountered UART DMA error. Clearing and restarting DMA...\n");
 | |
|     #endif
 | |
| 
 | |
|     // Clear flags
 | |
|     DMA2->HIFCR = DMA_HIFCR_CTEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CFEIF5;
 | |
| 
 | |
|     // Re-enable the DMA if necessary
 | |
|     DMA2_Stream5->CR |= DMA_SxCR_EN;
 | |
|   }
 | |
| 
 | |
|   // Re-calculate write pointer and reset flags
 | |
|   dma_pointer_handler(&uart_ring_esp_gps, DMA2_Stream5->NDTR);
 | |
|   DMA2->HIFCR = DMA_HIFCR_CTCIF5 | DMA_HIFCR_CHTIF5;
 | |
| 
 | |
|   EXIT_CRITICAL();
 | |
| }
 | |
| 
 | |
| // ***************************** Hardware setup *****************************
 | |
| 
 | |
| void dma_rx_init(uart_ring *q) {
 | |
|   // Initialization is UART-dependent
 | |
|   if(q == &uart_ring_esp_gps){
 | |
|     // DMA2, stream 5, channel 4
 | |
| 
 | |
|     // Disable FIFO mode (enable direct)
 | |
|     DMA2_Stream5->FCR &= ~DMA_SxFCR_DMDIS;
 | |
| 
 | |
|     // Setup addresses
 | |
|     DMA2_Stream5->PAR = (uint32_t)&(USART1->DR);    // Source
 | |
|     DMA2_Stream5->M0AR = (uint32_t)q->elems_rx;     // Destination
 | |
|     DMA2_Stream5->NDTR = q->rx_fifo_size;           // Number of bytes to copy
 | |
| 
 | |
|     // Circular, Increment memory, byte size, periph -> memory, enable
 | |
|     // Transfer complete, half transfer, transfer error and direct mode error interrupt enable
 | |
|     DMA2_Stream5->CR = DMA_SxCR_CHSEL_2 | DMA_SxCR_MINC | DMA_SxCR_CIRC | DMA_SxCR_HTIE | DMA_SxCR_TCIE | DMA_SxCR_TEIE | DMA_SxCR_DMEIE | DMA_SxCR_EN;
 | |
|     
 | |
|     // Enable DMA receiver in UART
 | |
|     q->uart->CR3 |= USART_CR3_DMAR;
 | |
| 
 | |
|     // Enable UART IDLE interrupt
 | |
|     q->uart->CR1 |= USART_CR1_IDLEIE;
 | |
| 
 | |
|     // Enable interrupt
 | |
|     NVIC_EnableIRQ(DMA2_Stream5_IRQn);
 | |
|   } else {
 | |
|     puts("Tried to initialize RX DMA for an unsupported UART\n");
 | |
|   }
 | |
| }
 | |
| 
 | |
| #define __DIV(_PCLK_, _BAUD_)                    (((_PCLK_) * 25U) / (4U * (_BAUD_)))
 | |
| #define __DIVMANT(_PCLK_, _BAUD_)                (__DIV((_PCLK_), (_BAUD_)) / 100U)
 | |
| #define __DIVFRAQ(_PCLK_, _BAUD_)                ((((__DIV((_PCLK_), (_BAUD_)) - (__DIVMANT((_PCLK_), (_BAUD_)) * 100U)) * 16U) + 50U) / 100U)
 | |
| #define __USART_BRR(_PCLK_, _BAUD_)              ((__DIVMANT((_PCLK_), (_BAUD_)) << 4) | (__DIVFRAQ((_PCLK_), (_BAUD_)) & 0x0FU))
 | |
| 
 | |
| void uart_set_baud(USART_TypeDef *u, unsigned int baud) {
 | |
|   if (u == USART1) {
 | |
|     // USART1 is on APB2
 | |
|     u->BRR = __USART_BRR(48000000U, baud);
 | |
|   } else {
 | |
|     u->BRR = __USART_BRR(24000000U, baud);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void uart_init(uart_ring *q, int baud) {
 | |
|   // Set baud and enable peripheral with TX and RX mode
 | |
|   uart_set_baud(q->uart, baud);
 | |
|   q->uart->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
 | |
| 
 | |
|   // Enable UART interrupts
 | |
|   if(q->uart == USART1){
 | |
|     NVIC_EnableIRQ(USART1_IRQn);
 | |
|   } else if (q->uart == USART2){
 | |
|     NVIC_EnableIRQ(USART2_IRQn);
 | |
|   } else if (q->uart == USART3){
 | |
|     NVIC_EnableIRQ(USART3_IRQn);
 | |
|   } else if (q->uart == UART5){
 | |
|     NVIC_EnableIRQ(UART5_IRQn);
 | |
|   } else {
 | |
|     // UART not used. Skip enabling interrupts
 | |
|   }
 | |
| 
 | |
|   // Initialise RX DMA if used
 | |
|   if(q->dma_rx){
 | |
|     dma_rx_init(q);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // ************************* Low-level buffer functions *************************
 | |
| 
 | |
| bool getc(uart_ring *q, char *elem) {
 | |
|   bool ret = false;
 | |
| 
 | |
|   ENTER_CRITICAL();
 | |
|   if (q->w_ptr_rx != q->r_ptr_rx) {
 | |
|     if (elem != NULL) *elem = q->elems_rx[q->r_ptr_rx];
 | |
|     q->r_ptr_rx = (q->r_ptr_rx + 1U) % q->rx_fifo_size;
 | |
|     ret = true;
 | |
|   }
 | |
|   EXIT_CRITICAL();
 | |
| 
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| bool injectc(uart_ring *q, char elem) {
 | |
|   int ret = false;
 | |
|   uint16_t next_w_ptr;
 | |
| 
 | |
|   ENTER_CRITICAL();
 | |
|   next_w_ptr = (q->w_ptr_rx + 1U) % q->tx_fifo_size;
 | |
|   if (next_w_ptr != q->r_ptr_rx) {
 | |
|     q->elems_rx[q->w_ptr_rx] = elem;
 | |
|     q->w_ptr_rx = next_w_ptr;
 | |
|     ret = true;
 | |
|   }
 | |
|   EXIT_CRITICAL();
 | |
| 
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| bool putc(uart_ring *q, char elem) {
 | |
|   bool ret = false;
 | |
|   uint16_t next_w_ptr;
 | |
| 
 | |
|   ENTER_CRITICAL();
 | |
|   next_w_ptr = (q->w_ptr_tx + 1U) % q->tx_fifo_size;
 | |
|   if (next_w_ptr != q->r_ptr_tx) {
 | |
|     q->elems_tx[q->w_ptr_tx] = elem;
 | |
|     q->w_ptr_tx = next_w_ptr;
 | |
|     ret = true;
 | |
|   }
 | |
|   EXIT_CRITICAL();
 | |
| 
 | |
|   uart_tx_ring(q);
 | |
| 
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| // Seems dangerous to use (might lock CPU if called with interrupts disabled f.e.)
 | |
| // TODO: Remove? Not used anyways
 | |
| void uart_flush(uart_ring *q) {
 | |
|   while (q->w_ptr_tx != q->r_ptr_tx) {
 | |
|     __WFI();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void uart_flush_sync(uart_ring *q) {
 | |
|   // empty the TX buffer
 | |
|   while (q->w_ptr_tx != q->r_ptr_tx) {
 | |
|     uart_tx_ring(q);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void uart_send_break(uart_ring *u) {
 | |
|   while ((u->uart->CR1 & USART_CR1_SBK) != 0);
 | |
|   u->uart->CR1 |= USART_CR1_SBK;
 | |
| }
 | |
| 
 | |
| void clear_uart_buff(uart_ring *q) {
 | |
|   ENTER_CRITICAL();
 | |
|   q->w_ptr_tx = 0;
 | |
|   q->r_ptr_tx = 0;
 | |
|   q->w_ptr_rx = 0;
 | |
|   q->r_ptr_rx = 0;
 | |
|   EXIT_CRITICAL();
 | |
| }
 | |
| 
 | |
| // ************************ High-level debug functions **********************
 | |
| void putch(const char a) {
 | |
|   if (has_external_debug_serial) {
 | |
|     // assuming debugging is important if there's external serial connected
 | |
|     while (!putc(&uart_ring_debug, a));
 | |
| 
 | |
|   } else {
 | |
|     // misra-c2012-17.7: serial debug function, ok to ignore output
 | |
|     (void)injectc(&uart_ring_debug, a);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void puts(const char *a) {
 | |
|   for (const char *in = a; *in; in++) {
 | |
|     if (*in == '\n') putch('\r');
 | |
|     putch(*in);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void putui(uint32_t i) {
 | |
|   uint32_t i_copy = i;
 | |
|   char str[11];
 | |
|   uint8_t idx = 10;
 | |
|   str[idx] = '\0';
 | |
|   idx--;
 | |
|   do {
 | |
|     str[idx] = (i_copy % 10U) + 0x30U;
 | |
|     idx--;
 | |
|     i_copy /= 10;
 | |
|   } while (i_copy != 0U);
 | |
|   puts(&str[idx + 1U]);
 | |
| }
 | |
| 
 | |
| void puth(unsigned int i) {
 | |
|   char c[] = "0123456789abcdef";
 | |
|   for (int pos = 28; pos != -4; pos -= 4) {
 | |
|     putch(c[(i >> (unsigned int)(pos)) & 0xFU]);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void puth2(unsigned int i) {
 | |
|   char c[] = "0123456789abcdef";
 | |
|   for (int pos = 4; pos != -4; pos -= 4) {
 | |
|     putch(c[(i >> (unsigned int)(pos)) & 0xFU]);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void hexdump(const void *a, int l) {
 | |
|   if (a != NULL) {
 | |
|     for (int i=0; i < l; i++) {
 | |
|       if ((i != 0) && ((i & 0xf) == 0)) puts("\n");
 | |
|       puth2(((const unsigned char*)a)[i]);
 | |
|       puts(" ");
 | |
|     }
 | |
|   }
 | |
|   puts("\n");
 | |
| }
 | |
| 
 |