Handling UART ISR during double buffer swap - c

I am working on a double buffer for the UART on a STM32F processor. The example below adds the data to a buffer every time the UART ISR runs. This buffer is then swapped when the main process wants to pull data off of it.
I am concerned about a possible corner condition. It is possible for the ISR to fire after I have swapped the buffers but before I have reset the receive buffer count. This would mean that my buffer size and count would be out of sync.
What is the best way to handle this corner case?
uint8_t rxBufferA[MAX_RX_BUFFER_SIZE];
uint8_t rxBufferB[MAX_RX_BUFFER_SIZE];
uint8_t *receivingBuffer;
uint8_t *processingBuffer;
volatile uint32_t rxBufferSize;
uart_isr() {
receivingBuffer[rxBufferSize++] = RECEIVE_DATA_REGISTER
}
main() {
receivingBuffer = &rxBufferA;
processingBuffer = &rxBufferB;
while(1) {
if (rxBufferSize > 0) {
uint32_t i = 0;
uint8_t *ptemp = receivingBuffer;
uint8_t bufferSize = 0;
/* zero out processing buffer */
memset(processingBuffer, 0, MAX_RX_BUFFER_SIZE);
/* swap receiving and processing buffers */
receivingBuffer = processingBuffer
processingBuffer = ptemp;
/* WHAT IF THE ISR FIRES HERE??? */
/* save off buffer size and reset count */
bufferSize = rxBufferSize;
rxBufferSize = 0;
/* process rx data */
}
}
}

you need critical section. disable the user interrupt when swapping the buffer.
BTW zeroing is not needed

Related

Ringbuffer for a microcontroller

Here is a scenario I'm facing right now, I have an interrupt(thread) UART that is reading to a ringbuffer from the values that I get from the serial port, and also writing the values from the serial port to the ring buffer.
I have a main loop that access that ringbuffer for reading the values from it, while writing an AT Command, and also writing to the ring buffer those AT Commands.
Do I need the ringbuffer to be lock free or surround the shared data with a semaphore or a mutex ? I don't have an OS for getting a mutex or semaphore working.
I have read alot about the subject and it seems I need a lock free ringbuffer. On ARM I would use a compare and swap instruction. The ringbuffer is implemented as an array so I wouldn't run into ABA problem
Declaration of buffers:
#define MAX_CHANNEL_COUNT 5
#define UART_BUFSIZE 512
char buffers[2][MAX_CHANNEL_COUNT][UART_BUFSIZE];
char* writeBuffers[MAX_CHANNEL_COUNT];
char* readBuffers[MAX_CHANNEL_COUNT];
volatile int readPos[MAX_CHANNEL_COUNT] = { 0 };
volatile int writePos[MAX_CHANNEL_COUNT] = { 0 };
here is the interrupt code
void USART_IRQHandler(char Channel, USART_TypeDef *USARTx)
{
volatile unsigned int IIR;
int c = 0;
IIR = USARTx->SR;
if (IIR & USART_FLAG_RXNE)
{ // read interrupt
USARTx->SR &= ~USART_FLAG_RXNE; // clear interrupt
c = USART_ReceiveData(USARTx);
writeBuffers[Channel][writePos[Channel]] = c;
writePos[Channel]++;
if(writePos[Channel]>=UART_BUFSIZE) writePos[Channel]=0;
}
if (IIR & USART_FLAG_TXE)
{
USARTx->SR &= ~USART_FLAG_TXE; // clear interrupt
}
}
code for initializing and swapping the buffers:
void initializeBuffers(void) {
int i = 0;
for (i = 0; i < MAX_CHANNEL_COUNT; ++i)
{
writeBuffers[i] = buffers[0][i];
readBuffers[i] = buffers[1][i];
}
}
void swapBuffers(int channel) {
int i;
char * buf = writeBuffers[channel];
__disable_irq();
writeBuffers[channel] = readBuffers[channel];
readBuffers[channel] = buf;
if ( readPos[channel] == UART_BUFSIZE)
readPos[channel] = 0;
for (i =0; i < UART_BUFSIZE; i++)
{
buf[i] = 0;
}
__enable_irq();
}
here I use this function to get a char from a specific channel and from a specific UART
int GetCharUART (char Channel)
{
int c = readBuffers[Channel][readPos[Channel]++];
if (c == 0 || readPos[Channel] == UART_BUFSIZE)
{
swapBuffers(Channel); // Make this clear your read buffer.
return EMPTY;
}
return c; // Note, your code that calls this should handle the case where c == 0
}
usage of GetCharUart
PutStringUART(UART_GSM, "AT");
PutStringUART(UART_GSM, pCommand);
PutCharUART(UART_GSM, '\r');
count = 0;
timer_100ms = 0;
while (timer_100ms <= timeout)
{
zeichen = GetCharUART(UART_GSM);
}
You need synchronization between interrupt handlers and the "main-thread", so yes a sort of mutex is needed. The usual method without OS, is to just disable interrupts before "entering the critical section" and re-enable afterwards. That ensures the critical section is "atomic" (i.e. no interrupts fire in the meantime).
On ARM I would use a compare and swap instruction
What compiler are you using?
GCC and Clang has intrinsics for atomic compare-and-swap.
See e.g.
https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html
https://llvm.org/docs/Atomics.html
C11 supports it via <stdatomic.h> I believe: https://en.cppreference.com/w/c/atomic/atomic_compare_exchange
Conceptually, I would recommend splitting the ring buffer into a reading and a writing buffer (can be a ring buffer).
When you use interrupts, you have concurrency, which means that you need to protect shared data (i.e., your buffers). This is often done by mutexes or semaphores. However, they are only available on OSs or RTOSs.
So the most common thing to do is to temporarily serialise your concurrent elements. This can be done by disabling the UART interrupt. More precisely, you need to deactivate all interrupts, which can trigger code that uses the shared resource.

STM32F411E-DISCO Uart circular buffer on interrupts

I would like to receive and transmit data that will be put into a circular buffer. I have functions for inserting characters and reading characters.
How can I use these functions and where should I put them? Currently I transmit and receive what I send to the terminal and it works fine.
I am just learning, please give me some advice
#define UART_RX_BUF_SIZE 7
#define UART_TX_BUF_SIZE 7
int8_t uart_put_char(char data);
int8_t uart_get_char(char *data);
volatile char uart_rxBuff[UART_RX_BUF_SIZE];
volatile char uart_txBuff[UART_TX_BUF_SIZE];
void uart_put_string(char *s);
typedef struct {
volatile char *const buffer;
uint8_t head;
uint8_t tail;
} circ_buffer_t;
volatile circ_buffer_t uart_rx_circBuff = {uart_rxBuff, 0, 0};
volatile circ_buffer_t uart_tx_circBuff = {uart_txBuff, 0, 0};
uint8_t received_char;
int8_t uart_put_char(char data) {
uint8_t head_temp = uart_tx_circBuff.head + 1;
if (head_temp == UART_TX_BUF_SIZE)
head_temp = 0;
if (head_temp == uart_tx_circBuff.tail)
return 0;
uart_tx_circBuff.buffer[head_temp] = data;
uart_tx_circBuff.head = head_temp;
__HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);
return 1;
}
int8_t uart_get_char(char *data) {
if (uart_rx_circBuff.head == uart_rx_circBuff.tail)
return 0;
uart_rx_circBuff.tail++;
if (uart_rx_circBuff.tail == UART_RX_BUF_SIZE)
uart_rx_circBuff.tail = 0;
*data = uart_rx_circBuff.buffer[uart_rx_circBuff.tail];
return 1;
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// uart_put_char(&received_char);
HAL_UART_Transmit(&huart1, &received_char, 1, 100);
// uart_get_char(received_char);
HAL_UART_Receive_IT(&huart1, &received_char, 1);
}
}
I want to put a side-note first, so it doesn't get missed:
I would suggest incrementing your head and tail indices after you've put the byte in the buffer. So, upon initialisation, head is 0. When you call put_tx, the byte will be stored at index 0 and then be incremented to 1. When the interrupt calls get_tx, the tail is still 0 so you'll get that first character, then it'll be incremented to 1. Doesn't really matter, but I think it's easier to write clean code if you think in this way.
Also, it's totally a micro-optimisation, but think about setting your buffer size to a power of 2. That way, instead of going if (head==BUF_SIZE) head=0; you can go head &= BUF_SIZE-1; and you'll save a few clock cycles, no need for a test. ;-)
It definitely is a pain to set up, but very much worth it if you can get around the multiple steps. The HAL will probably handle much of this for you, but I don't know enough about that.
You need an interrupt handler for UART events.
You need to tell the UART to raise interrupts for RXNE (receive not empty), and TXE (transmit empty) when you've sent a character.
You need to tell the NVIC to enable the UART interrupt.
I definitely recommend having push and pop (or put and get) functions for both buffers. The interrupts will call put_rx and get_tx, while user code will call put_tx and get_rx. Or better yet, write some wrapper functions, like Uart_Send(char), Uart_SendBuffer(const char*, size_t), Uart_TryReceive(char*, size_t) that will call put_tx and get_rx.
If the HAL is as smart as I think it is, you can can probably combine steps 1-3 into a single step, and just implement HAL_UART_RxCpltCallback (as you've done) and HAL_UART_TxCpltCallback.
I don't know how the HAL works enough to give you an exact solution (all my STM32 work is built on headers I made myself before I realised even CMSIS existed - whoops!) so here's how I would do it in low-level code.
void USART1_IRQHandler() __attribute__((interrupt))
{
// This would probably be handled in HAL_UART_RxCpltCallback
if (USART1->SR.RXNE) // We've received a byte!
uart_push_rx(USART1->DR); // Push the received byte onto the back
// of the RX buffer.
// This would probably be handled in HAL_UART_TxCpltCallback
if (USART1->SR.TXE) // Transmit buffer is empty - send next byte
{
char nextByte;
if (uart_pop_tx(&nextByte)) // Get the next byte in the buffer and
USART1->DR = nextByte; // shove it in the UART data register.
else // No more data in the circular buffer,
USART1->CR1.TXEIE = 0; // so disable the TXE interrupt or we'll
// end up stuck in a loop.
}
if (USART1->SR.TC) // There's also a flag for 'transmit complete'. This
{ } // is different from TXE in that TXE means "Okay, I've started this
// one, tell me what'll come next," whereas TC says "Okay, I've
// finished sending everything now. Anything else, boss?"
// We won't use it, but be aware of the terminology. The HAL might
// try and confuse you with its words.
}
void InitialiseUart()
{
HAL_UART_Configure(&huart1, baud, stopBits, andStuff, probably, etc);
HAL_UART_Enable(&huart1);
// You probably don't need to worry about anything below here,
// if the HAL is smart. But I've included it for completeness,
// so you can understand more of what the MCU is doing.
// Enable RXNE (receive not empty) interrupt
USART1->CR1.RXNEIE = 1;
// Don't enable TXE (transmit empty) interrupt yet. Only when you send
// a character, or the interrupt will fire immediately.
// Enable UART interrupts at the system level
NVIC_EnableIRQ(USART1_IRQn);
}
If you need me to, I'll look into the HAL code and try and give you some more direct guidance, but I think it's valuable to take this, understand it, and translate it to your implementation.
I've had a closer look at the HAL source code, and it looks like by using the HAL_xxxx_IT() functions, all the interrupt code is already handled for you. That said, there are a lot of similarities to doing it in bare metal in your application, since you're only sending and receiving one character at a time.
When you call __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE) you're telling the microcontroller to fire an interrupt when the data register is empty. But the HAL interrupt routine doesn't know what to transmit, so it'll send out garbage until it thinks it's finished.
I think another problem might be caused by directly accessing your circular buffer in multiple places, causing clashes. You'd be better off calling your HAL functions with a temporary buffer (or pointer to a single char) that either is taken from, or stores into, your circular buffer.
Main function
// Entry point
int main(void)
{
// Initialise system and peripherals
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM10_Init();
// Enable interrupts
HAL_NVIC_EnableIRQ(USART1_IRQn);
// Prepare reception of the first character
HAL_UART_Receive_IT(&huart1, &received_char, 1);
while (1)
{
char downloaded;
if (UartReceiveChar(&downloaded) && downloaded == 'x')
UartSendChar(downloaded);
}
}
UART wrapper
// Function declarations
int8_t UartSendChar (char data);
void UartSendString (char* str);
int8_t UartReceiveChar (char* data);
// Variables
int8_t isTransmitting = 0;
uint8_t sendingChar;
uint8_t receivedChar;
// Function definitions
// Callback function for when a character has finished sending by the HAL
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
// The HAL has just sent the tail byte from the TX buffer
// If there is still data in the buffer, we want to send the
// next byte in the buffer.
if (buffer_pop_tx(&sendingChar))
HAL_UART_Transmit_IT(huart, &sendingChar, 1);
else
isTransmitting = 0;
}
// Callback function for when a character is received by the HAL
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// The HAL has received a character into 'receivedChar'
// All we need to do is push it onto our circular buffer
buffer_push_rx(receviedChar);
// and prepare to receive the next character
HAL_UART_Receive_IT(huart, &receivedChar, 1);
}
// Send a character
int8_t UartSendChar(char data)
{
// Push the character onto the buffer
int8_t returnValue = buffer_push_tx(data);
// Start sending the buffer, if we're not already transmitting
if (!isTransmitting)
{
sendingChar = data;
isTransmitting = 1;
HAL_UART_Transmit_IT(&huart1, &sendingChar, 1);
}
return returnValue;
}
// Send a null-terminated string
int8_t UartSendString(char* str)
{
// Iterate through all the non-null characters
while (*str)
{
// Send the character; Wait if the buffer is full
while (!UartSendChar(*str)) ;
++str;
}
return 1;
}
// Receive a character
int8_t UartReceiveChar(char* data)
{
// Just a wrapper for buffer_pop_rx
return buffer_pop_rx(data);
}
Buffer implementation
// I've changed your circular buffer size for optimisation purposes
#define UART_RX_BUF_SIZE 8
#define UART_TX_BUF_SIZE 8
// Buffer type definition
typedef struct
{
volatile char *const buffer;
uint8_t head;
uint8_t tail;
uint8_t isFull : 1;
uint8_t isEmpty : 1;
uint8_t hasOverflowed : 1; // Overflow and underflow are only relevant if we choose to
uint8_t hasUnderflowed : 1; // allow pushing and popping with an empty/full buffer
} circ_buffer_t;
// Function declarations
int8_t buffer_push_tx (char data);
int8_t buffer_push_rx (char data);
int8_t buffer_pop_tx (char* data);
int8_t buffer_pop_rx (char* data);
// Variables
volatile char uart_rxBuff[UART_RX_BUF_SIZE];
volatile char uart_txBuff[UART_TX_BUF_SIZE];
volatile circ_buffer_t uart_rx_circBuff = {uart_rxBuff, 0, 0};
volatile circ_buffer_t uart_tx_circBuff = {uart_txBuff, 0, 0};
// Function definitions
// Push a character onto the transmit buffer
int8_t buffer_push_tx(char data)
{
if (uart_tx_circBuff.isFull) // buffer is full
{
// uart_tx_circBuff.hasOverflowed = 1; // Nasty things can happen if we allow overflows. But in some special cases, it may be necessary.
return 0;
}
// Put the character at the head position and increment the head index
uart_tx_circBuff.buffer[uart_tx_circBuff.head++] = data;
uart_tx.circBuff.head &= (UART_TX_BUF_SIZE - 1); // don't use &= if the buffer size isn't a power of 2
// mark the buffer as full if the head and tail indices are the same
uart_tx_circBuff.isFull = (uart_tx_circBuff.head == uart_tx_circBuff.tail);
uart_tx_circBuff.isEmpty = 0;
// OK
return 1;
}
// Push a character onto the receive buffer
int8_t buffer_push_rx(char data)
{
if (uart_rx_circBuff.isFull) // buffer is full
{
// uart_rx_circBuff.hasOverflowed = 1;
return 0;
}
// Put the character at the head position and increment the head index
uart_rx_circBuff.buffer[uart_rx_circBuff.head++] = data;
uart_rx.circBuff.head &= (UART_RX_BUF_SIZE - 1); // don't use &= if the buffer size isn't a power of 2
// mark the buffer as full if the head and tail indices are the same
uart_rx_circBuff.isFull = (uart_rx_circBuff.head == uart_rx_circBuff.tail);
uart_rx_circBuff.isEmpty = 0;
// OK
return 1;
}
// Try to get a character from the receive buffer.
int8_t uart_pop_rx(char *data)
{
if (uart_rx_circBuff.isEmpty) // buffer is empty
{
// uart_rx_circBuff.hasUnderflowed = 1;
return 0;
}
// Put the character from the tail position of the buffer into 'data' and increment the tail index
*data = uart_rx_circBuff.buffer[uart_rx_circBuff.tail++];
uart_rx_circBuff.tail &= (UART_RX_BUF_SIZE - 1); // // don't use &= if the buffer size isn't a power of 2
// mark the buffer as full if the head and tail indices are the same
uart_rx_circBuff.isEmpty = (uart_rx_circBuff.head == uart_rx_circBuff.tail);
uart_rx_circBuff.isFull = 0;
// OK
return 1;
}
// Try to get a character from the transmit buffer.
int8_t uart_pop_rx(char *data)
{
if (uart_tx_circBuff.head == uart_tx_circBuff.tail) // buffer is empty
{
// uart_tx_circBuff.hasUnderflowed = 1;
return 0;
}
// Put the character from the tail position of the buffer into 'data' and increment the tail index
*data = uart_tx_circBuff.buffer[uart_tx_circBuff.tail++];
uart_tx_circBuff.tail &= (UART_TX_BUF_SIZE - 1); // don't use &= if the buffer size isn't a power of 2
// mark the buffer as full if the head and tail indices are the same
uart_tx_circBuff.isEmpty = (uart_tx_circBuff.head == uart_tx_circBuff.tail);
uart_tx_circBuff.isFull = 0;
// OK
return 1;
}

GPS UART data written multiple times to Buffer

I am receiving/reading data from a GPS module sent via USART3 to the STM32F091.
The data gets there just fine which I confirm by sending it to my PC COM3 port and feeding it to 'u-center' (GPS evaulation software).
My problem is that I want to evaluate the data myself in my C program, and for that purpose I feed it into a Ring Buffer, however, every character of the GPS signal is written multiple times to the buffer, instead of one by one.
For example
GGGGGGGPPPPPPPPSSSSSSSS instead of GPS
I am unsure what I'm doing wrong, maybe it's something really obvious I'm overlooking after staring at this code so long.
Here's the relevant code.
stm32f0xx_it.c
#include <main.h>
void USART3_8_IRQHandler(void)
{
if (USART_FLAG_RXNE != RESET)
{
uint16_t byte = 0;
/* Data reception */
/* Clear Overrun Error Flag, necessary when RXNE is used */
USART_GetITStatus(USART3, USART_IT_ORE);
/* Read from Receive Data Register and put into byte */
byte = USART_ReceiveData(USART3);
(*pRXD3).wr = ((*pRXD3).wr + 1) % (*pRXD3).max;
(*pRXD3).Buffer[(*pRXD3).wr] = byte;
/* Send Data to PC, and reset Transmission Complete Flag */
USART_GetITStatus(USART1, USART_IT_TC);
USART_SendData(USART1, byte);
return;
}
return;
}
uartGPS.h
....
struct GPSuart
{
BYTE Buffer[255];
WORD max;
WORD re;
WORD wr;
};
....
main.h
....
extern volatile BYTE B_ser_txd_3[255];
extern volatile BYTE B_ser_rxd_3[255];
extern volatile struct GPSuart TXD_uart_3;
extern volatile struct GPSuart RXD_uart_3;
extern volatile struct GPSuart *pRXD3;
extern volatile struct GPSuart *pTXD3;
....
Let me know if I should provide additional information.
This:
if (USART_FLAG_RXNE != RESET)
does not test a flag, that code is inspecting the flag constant itself, which is not what you meant.
You need more code, to access the UART's status register and check the flag:
if (USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) != RESET)

UART DMA for varying sized arrays

Using MPLAB X 1.70 with a dsPIC33FJ128GP802 microcontroller.
I've got an application which is collecting data from two sensors at different sampling rates (one at 50Hz, the other at 1000Hz), both sensor packets are also different sizes (one is 5 bytes, the other is 21 bytes). Up until now I've used manual UART transmision as seen below:
void UART_send(char *txbuf, char size) {
// Loop variable.
char i;
// Loop through the size of the buffer until all data is sent. The while
// loop inside checks for the buffer to be clear.
for (i = 0; i < size; i++) {
while (U1STAbits.UTXBF);
U1TXREG = *txbuf++;
}
}
The varying sized arrays (5 or 21 bytes) were sent to this function, with their size, and a simple for loop looped through each byte and outputted it through the UART tx register U1TXREG.
Now, I want to implement DMA to relieve some pressure on the system when transmitting the large amount of data. I've used DMA for my UART receive and ADC, but having trouble with transmit. I've tried both ping pong mode on and off, and one-shot and continuous mode, but whenever it comes to sending the 21 byte packet it messes up with strange values and zero value padding.
I'm initialising the DMA as seen below.
void UART_TX_DMA_init() {
DMA2CONbits.SIZE = 0; // 0: word; 1: byte
DMA2CONbits.DIR = 1; // 0: uart to device; 1: device to uart
DMA2CONbits.AMODE = 0b00;
DMA2CONbits.MODE = 1; // 0: contin, no ping pong; 1: oneshot, no ping pong; 2: contin, ping pong; 3: oneshot, ping pong.
DMA2PAD = (volatile unsigned int) &U1TXREG;
DMA2REQ = 12; // Select UART1 Transmitter
IFS1bits.DMA2IF = 0; // Clear DMA Interrupt Flag
IEC1bits.DMA2IE = 1; // Enable DMA interrupt
}
The DMA interrupt I'm just clearing the flag. To build the DMA arrays I've got the following function:
char TXBufferADC[5] __attribute__((space(dma)));
char TXBufferIMU[21] __attribute__((space(dma)));
void UART_send(char *txbuf, char size) {
// Loop variable.
int i;
DMA2CNT = size - 1; // x DMA requests
if (size == ADCPACKETSIZE) {
DMA2STA = __builtin_dmaoffset(TXBufferADC);
for (i = 0; i < size; i++) {
TXBufferADC[i] = *txbuf++;
}
} else if (size == IMUPACKETSIZE) {
DMA2STA = __builtin_dmaoffset(TXBufferIMU);
for (i = 0; i < size; i++) {
TXBufferIMU[i] = *txbuf++;
}
} else {
NOTIFICATIONLED ^= 1;
}
DMA2CONbits.CHEN = 1; // Re-enable DMA2 Channel
DMA2REQbits.FORCE = 1; // Manual mode: Kick-start the first transfer
}
This example is with ping pong turned off. I'm using the same DMA2STA register but changing the array depending on which packet type I have. I'm determining the packet type from the data to be sent, changing the DMA bytes to be sent (DMA2CNT), building the array same as before with a for loop, then forcing the first transfer along with re-enabling the channel.
It takes much longer to process the data for the large data packet and I'm starting to think the DMA is missing these packets and sending null/weird packets in its place. It seems to be polling before I build the buffer and force the first transfer. Perhaps the force isn't necessary for every poll; I don't know...
Any help would be great.
After a few days of working on this I think I've got it.
The main issue I experienced was that the DMA interrupt was being polled faster than previous transmission, therefore I was only getting segments of packages before the next package overwrote the previous. This was solved with simply waiting for the end of UART transmission with:
while (!U1STAbits.TRMT);
I managed to avoid the redundancy of recreating a new DMA with the package data by simply making the original data array the one recognised by the DMA.
In the end the process was pretty minimal, the function called every time a package was created is:
void sendData() {
// Check that last transmission has completed.
while (!U1STAbits.TRMT);
DMA2CNT = bufferSize - 1;
DMA2STA = __builtin_dmaoffset(data);
DMA2CONbits.CHEN = 1; // Re-enable DMA0 Channel
DMA2REQbits.FORCE = 1; // Manual mode: Kick-start the first transfer
}
Regardless of what the size of the package, the DMA changes the amount it sends using the DMA2CNT register, then it's simply re-enabling the DMA and forcing the first bit.
Setting up the DMA was:
DMA2CONbits.SIZE = 1;
DMA2CONbits.DIR = 1;
DMA2CONbits.AMODE = 0b00;
DMA2CONbits.MODE = 1;
DMA2PAD = (volatile unsigned int) &U1TXREG;
DMA2REQ = 12; // Select UART1 Transmitter
IFS1bits.DMA2IF = 0; // Clear DMA Interrupt Flag
IEC1bits.DMA2IE = 1; // Enable DMA interrupt
Which is one-shot, no ping-pong, byte transfer, and all the correct parameters for UART1 TX.
Hope this helps someone in the future, the general principle can be applied to most PIC microcontrollers.

DMA interrupt for SPI

I'm trying to recreate a project of writing to an SD card (using FatFS) for a dsPIC33FJ128GP802 microcontroller.
Currently to collect the date from the SPI I have a do/while that loops 512 times and writes a dummy value to the SPI buffer, wait for the SPI flag, then read the SPI value, like so:
int c = 512;
do {
SPI1BUF = 0xFF;
while (!_SPIRBF);
*p++ = SPI1BUF;
} while (--c);
I'm trying to recreate this using the DMA intterupts but it's not working like I had hoped. I'm using one DMA channel, SPI is in 8 bit mode for the time being, so DMA is in byte mode, it's also in 'null write' mode, and continuous without ping pong. My buffers are only one member arrays and the DMA is matched.
DMA2CONbits.CHEN = 0; //Disable DMA
DMA2CONbits.SIZE = 1; //Receive bytes (8 bits)
DMA2CONbits.DIR = 0; //Receive from SPI to DMA
DMA2CONbits.HALF = 0; //Receive full blocks
DMA2CONbits.NULLW = 1; //null write mode on
DMA2CONbits.AMODE = 0; //Register indirect with post-increment
DMA2CONbits.MODE = 0; //continuous mode without ping-pong
DMA2REQbits.IRQSEL = 10; //Transfer done (SPI)
DMA2STA = __builtin_dmaoffset(SPIBuffA); //receive buffer
DMA2PAD = (volatile unsigned int) &SPI1BUF;
DMA2CNT = 0; //transfer count = 1
IFS1bits.DMA2IF = 0; //Clear DMA interrupt
IEC1bits.DMA2IE = 1; //Enable DMA interrupt
From what I understand from the null write mode, the DMA will write a null value every time a read is performed. However, the DMA wont start until an initial write is performed by the CPU, so I've used the manual/force method to start the DMA.
DMA1CONbits.CHEN = 1; //Enable DMA
DMA1REQbits.FORCE = 1; //Manual write
The interrupt will now start, and runs without error. However the code later shows that the collection was incorrect.
My interrupt is simple in that all I'm doing is placing the collected data (which I assume is placed in my DMAs buffer as allocated above) into a pointer which is used throughout my program.
void __attribute__((interrupt, no_auto_psv)) _DMA2Interrupt(void) {
if (RxDmaBuffer == 513) {
DMA2CONbits.CHEN = 0;
rxFlag = 1;
} else {
buffer[RxDmaBuffer] = SPI1BUF;
RxDmaBuffer++;
}
IFS1bits.DMA2IF = 0; // Clear the DMA0 Interrupt Flag
}
When the interrupt has run 512 times, I stop the DMA and throw a flag.
What am I missing? How is this not the same as the non-DMA method? Is it perhaps the lack of the while loop which waits for the completion of the SPI transmission (while (!_SPIRBF);). Unfortunately with the null write mode automatically sending and receiving the SPI data I can't manually put any sort of wait in.
I've also tried using two DMA channels, one to write and one to read, but this also didn't work (plus I need that channel later for when I come to proper writing to the SD card).
Any help would be great!

Resources