I am using ATxMega 32E5 and my current task is to rewrite existing RS485 driver and use DMA to save in my case
valuable cycles. The interrupt based code is communicating at a speed of 230.400k bauds, but this new driver will
have to be able to work at 2MHz, so this is why I want to use DMA, since I will be able to do only 160 cycles per
character and the RXC ISR uses at best 110 cycles (110/160 = 70% duty cycle, which is too much).
So naturaly I have been experimenting with single design that I iteratively reduced to
minimal example shown below.
Let me explain what I am trying to accomplish. We have defined proprietary
protocol (slighty similar to modbus) and I have already implemented it using interrupts.
This simplified version however is trying to do only the following:
1) Wait for a character in RXC ISR and read it
2) Disable interrupts for RXC by setting the interrupt priority to *0b00*
3) Set-up DMA to receive one character (I want to use DMA because in real life at this stage I will read between 40-120 characters, but for test I read here just one byte).
4) Wait until the DMA finishes
5) Set interrupt priority for RXC to *0b10*, disable and re-enable reception
6) repeat from 1.
The code is below (I am testing it with 230.400k baudrate at 32MHz mcu, you can skip the initialization - it should be OK since it worked well with previous interrupt-based design).
#define RS_PRIORITY_RXC USART_RXCINTLVL_HI_gc #define RS_PRIORITY_TXC USART_TXCINTLVL_HI_gc // baud rate 230400 #define RS_BAUDCTRL 131 #define RS_BAUDSCALE -3 void rs_init() { // Enable Transmit Complete Interrupt USARTD0.CTRLA = RS_PRIORITY_TXC; PORTD.REMAP = 1<<4; // Remap usart from 2,3 to 6,7 PORTD.DIRSET = 1<<7; PORTD.OUTSET = 1<<7; //set TX high PORTD.OUTCLR = 1<<5; // DIR low = input USARTD0.CTRLB = 0b11100; //enable TX and RX, 2X speed USARTD0.CTRLC = 0b011; //no parity, async, 8bit, 1 stop bit // Sets-up 230.400k baud rate USARTD0.BAUDCTRLA = RS_BAUDCTRL & 0xff; // BSEL lo USARTD0.BAUDCTRLB = ((RS_BAUDSCALE & 0xf) << 4) | ((RS_BAUDCTRL >> 8) & 0xf); //BSCALE:7-4, BSEL:3-0 // Enable Receive Complete interrupt USARTD0.CTRLA |= RS_PRIORITY_RXC; // Initialize EDMA - this is needed for SPI and RS485 EDMA.CTRL = (1<<6); // reset EDMA.CTRL = (1<<7) | 0b0000; // enable, no double buffering, four perip chan // CH0 is for RS485 RX, CH2 for TX // CH1 is for SPI TX, CH3 for RX EDMA.CH1.CTRLA = EDMA.CH0.CTRLA = 1<<6; // reset EDMA.CH3.CTRLA = EDMA.CH2.CTRLA = 1<<6; // reset } struct { char buffer[128]; } state; static void setup_rx_dma(uint8_t len) { // USARTD0.CTRLA &= ~RS_PRIORITY_RXC; // volatile uint32_t reg __attribute__((__unused__)); // reg = USARTD0.DATA; EDMA.CH0.CTRLB = 0b1010; // set medium interrupt levels EDMA.CH0.ADDRCTRL = 0b001; // none reload, incremental transfer peripheral to memory EDMA.CH0.TRIGSRC = EDMA_CH_TRIGSRC_USARTD0_RXC_gc; EDMA.CH0.TRFCNT = len; EDMA.CH0.ADDR = (register16_t) state.buffer; EDMA.CH0.CTRLA = (1<<7) /* enable */ | (1<<2) /* single shot mode */; } static inline void dontExpect(uint8_t *expr, uint8_t exp_value, uint16_t delay) { if(*expr == exp_value) { while(1) { led_toggle(); rtc_delay_ms(delay); } } *expr = exp_value; } static uint8_t _state = 0; ISR(EDMA_CH0_vect) { led_disable(); // Check dontExpect(&_state, 0, 100); // Clear timeout interrupt working = 0; // Disable reception of frames USARTD0.CTRLB &= ~USART_RXEN_bm; // Set RXC interrupt from 0b00 to 0b10 USARTD0.CTRLA |= RS_PRIORITY_RXC; // Enable reception of frames USARTD0.CTRLB |= USART_RXEN_bm; // Reset DMA EDMA.CH0.CTRLA = (1<<6) /* reset */; } ISR(USARTD0_RXC_vect) { led_enable(); // Check dontExpect(&_state, 1, 500); // Read the received character volatile uint32_t reg __attribute__((__unused__)); reg = USARTD0.DATA; // Disable RXC interrupts by setting interrupt priority from 0b10 to 0b00 USARTD0.CTRLA &= ~RS_PRIORITY_RXC; // Start DMA transfer for 1 byte // note: in real application there will be more data to be read (40-120 bytes), this is just for a test setup_rx_dma(1); }
What happens however is that it blinks only once (very fast blink) and then it remains
off and the mcu does nothing - I re-enable the interrupts in DMA_CH0 ISR, but it has no
effect since the LED remains off which means that RXC is called only once first and
last time when a byte is received.
I have tested the DMA code on its own and it successfully received however many bytes I wanted.
But together the Interrupts + DMA do not work. Does someone know why is this not working?
Basically I enable the interrupts for RXC in DMA_CH0_vect and even restart the transmission, but the USARTD0_RXC_vect is not getting called again.
I am sorry, but I have spent way too much time debugging this driver and
I can't even write working minimal example code.