DMA interrupt for SPI - c

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!

Related

Microchip PIC24FJ SPI writes correctly but only reads zeros

I have a PIC24FJ256GA702 communicating with a AD5724RBREZ quad DAC with a SPI link.
The DAC works fine, writing is no problem, but I am stuck on reading back the control register from it.
I get the correct waveform on the PIC pin that I am expecting, and the read routine runs, but it always returns zeros. The waveform is clearly not zero- the waveform on the scope is correct for the 4 channels and the internal reference being enabled.
Scope view of waveforms - blue = clock, yellow = data input from DAC at PIC pin
(The excessive ringing on the scope image is probably caused by a long distance ground connection- in practice these chips are about 25mm apart.)
I thought that the input pin was configured as an analogue, but it was correctly a digital input.
I connected it to a counter based on Timer1, and that counter does count if I try to read the DAC. This suggests that the PPS is working, the pin is not bust, and the the input signal is clean enough to use.
I think it may be a problem with the code or the decode timing of the SPI module, but as shown in the image the data is stable during the clock cycle so I cannot see what is wrong.
I have searched the forums, and it seems most with this problem trace it to analogue functions being enabled but that is not the case here.
Would anyone like to suggest something else to try, or post some working SPI read code if my code is not looking correct?
The code follows-
void AOUT_init(void)
{
//assume PPS is unlocked (it is on reset) - see note below
//setup SPI1 itself
SPI1CON1L = 0x0160; //TX work read 0
//SPI1CON1L = 0x0060; //TX not work
//SPI1CON1L = 0x0120; //TX work, read 0
//SPI1CON1L = 0x0020; //not tried
SPI1CON1H = 0x3000;
SPI1CON2L = 0x0017; //word length (17 hex = 24 bits)
//BRG
SPI1BRGL = 0x0000; //default = no divisor
//PPS - assume unlocked at this point
ANSBbits.ANSB13 = 0;
TRISBbits.TRISB13 = TRIS_INPUT;
//##########################################################################
RPINR20bits.SDI1R = 13; //set SDI1 data input to PIC to RB13
//##########################################################################
TRISBbits.TRISB15 = TRIS_OUTPUT;
RPOR7bits.RP15R = 8; //RB15 to SDI1 clock out from PIC
TRISBbits.TRISB14 = TRIS_OUTPUT;
RPOR7bits.RP14R = 7; //RB14 to SDI1 data out from PIC
//AD5724R has additional lines - not all used in practice
//setup and set initial level here
//AOUT-/LDAC
TRISBbits.TRISB6 = TRIS_OUTPUT;
LATBbits.LATB6 = 1;
//AOUT-/SYNC
TRISBbits.TRISB7 = TRIS_OUTPUT;
LATBbits.LATB7 = 1;
//AOUT-/CLR
TRISBbits.TRISB12 = TRIS_OUTPUT;
LATBbits.LATB12 = 1;
//turn SPI on
SPI1CON1Lbits.SPIEN = 1;
SPI1CON1Lbits.MSTEN = 1; //included in definition above
//now setup the AD chip
//output range set
AOUT_TX(0x0C00,0x0100); //all channels to 10V
//control
AOUT_TX(0x1900,0x0500);
//power control - enable DACs
AOUT_TX(0x1000,0x1F00);
}
The comms routine below is included for completeness- it just controls the other DAC lines. The /SYNC line is doing a chip select function.
static void AOUT_Comms(bool bSync, bool bLDAC, bool bClr)
{
//AOUT-/LDAC
LATBbits.LATB6 = bLDAC;
//AOUT-/SYNC
LATBbits.LATB7 = bSync;
//AOUT-/CLR
LATBbits.LATB12 = bClr;
}
This is write routine which works fine.
void AOUT_TX(uint16_t dataH, uint16_t dataL)
{
//AOUT uses 24 bit data
//this routine handles /SYNC line
//relies on AD chip having much faster response than chip cycle time
//AD chip limits at about 38MHz so much quicker than PIC.
AOUT_Comms(0,1,1);
SPI1BUFL = dataL;
SPI1BUFH = dataH;
while(SPI1STATLbits.SPIBUSY) //wait until sent
{
;
}
AOUT_Comms(1,1,1);
//
}
This is the read routine, which uses the routines above.
void AOUT_read_control(uint16_t ReadH, uint16_t ReadL)
{
uint16_t temp;
//to read, transmit the register to be read, then transmit a dummy command "NOP" to clock the data out.
//send register
AOUT_TX(0x9000,0x0000);
//read out- this is similar to write but reads the received buffer at the end.
//clear buffer
while (SPI1STATLbits.SPIRBF)
{
temp = SPI1BUFL;
temp = SPI1BUFH;
}
AOUT_Comms(0,1,1);
SPI1BUFL = 0;
SPI1BUFH = 0x1800; //nop operation via control register
while(SPI1STATLbits.SPIBUSY) //wait until sent
{
;
}
while (!SPI1STATLbits.SPIRBF)
{
; //hold until something received
}
ReadH = SPI1BUFH;
ReadL = SPI1BUFL; //these read zero
AOUT_Comms(1,1,1);
//
//dummy so can check counter also connected to same pin
temp = TMR1;
temp = TMR1;
}
Update-
I checked the SPI read decode by sending the received words directly to a display as suggested by the comments. I get 0000 for both words.
Also as suggested I connected a logic analyser at the PIC pins, and it decodes both read and write correctly.Logic Analyser view- Ch1/Blue/MOSI writing 0x180000, Ch2/Green/MISP reading 0x00001F, both correct

How HAL_UART_Transmit_IT manages sending data on serial on the STM32F091VB

I'm trying to understand how the STM32F091VB manages the send of data via serial protocol with the function HAL_UART_Transmit_IT()
At the moment I've a function called in the main() that creates the packet and send it via serial; it is something like this:
tx1[0] = STX;
tx1[1] = 0xFF;
tx1[2] = 0x80;
tx1[3] = 0x80;
DE_TAST_HIGH;
HAL_UART_Transmit_IT(&huart3, tx1, 8);
Now, the data I'm sending is quite small so the code run pretty fast and I'm trying to understand what's going to happen if I try to send a huge packet via serial protocol.
For istance, if my tx1[] is 100byte the HAL_UART_Transmit_IT() function block the CPU waiting while the full packet is sent to the serial port or it works more like a separate process where I tell the micro to send that packet and, while sending it it also process the remaining part of my code/main function?
I've tried to search on the micro datasheet to see if there was something about this process but I had no luck. I've read the stm32f0xx_hal_uart.c and it confirms that it is sent via interrupt in a non blocking mode but I would like to have some more in depth documentation about it
First of all you need to understand how the HAL_UART_Transmit_IT is meant to be used. We can get some help from STM FAQ.
The function is "non blocking" because when you call it it will do some configuration of the interrupts and then return. The buffer will not be transmitted during the call to your function, instead the heavy lifting is deferred to a later stage.
We can further have a look at the source code, to get a proof from what I said (note I kept only the juicy parts).
Blocking
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
uint16_t* tmp;
uint32_t tickstart = 0U;
// [ ... ]
huart->TxXferSize = Size;
huart->TxXferCount = Size;
while(huart->TxXferCount > 0U)
{
// [ ... ]
// This is were the actual HW regs are accessed, starting the transfer
huart->Instance->DR = (*pData++ & (uint8_t)0xFF);
}
}
// [ ... ]
return HAL_OK
}
Non Blocking
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
huart->pTxBuffPtr = pData;
huart->TxXferSize = Size;
huart->TxXferCount = Size;
/* Enable the UART Transmit data register empty Interrupt */
// This is the only part were HW regs are accessed. What is happening here??
SET_BIT(huart->Instance->CR1, USART_CR1_TXEIE);
return HAL_OK;
}
The _IT function only activates one interrupt, based also on the datasheet:
This means we will receive an interrupt whenever the TX buffer is free. Who is actually sending the data then?
With the help of the FAQs and reading the source code, we find that void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) does something like this:
/* UART in mode Transmitter ------------------------------------------------*/
if(((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
UART_Transmit_IT(huart);
return;
}
Which in turn calls the UART_Transmit_IT
static HAL_StatusTypeDef UART_Transmit_IT(UART_HandleTypeDef *huart)
{
uint16_t* tmp;
/* Check that a Tx process is ongoing */
if(huart->gState == HAL_UART_STATE_BUSY_TX)
{
huart->Instance->DR = (uint8_t)(*huart->pTxBuffPtr++ & (uint8_t)0x00FF);
if(--huart->TxXferCount == 0U)
{
/* Disable the UART Transmit Complete Interrupt */
CLEAR_BIT(huart->Instance->CR1, USART_CR1_TXEIE);
/* Enable the UART Transmit Complete Interrupt */
SET_BIT(huart->Instance->CR1, USART_CR1_TCIE);
}
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
This function transmits only one byte! It then decrements the counter for the transmission (remember, all the information was set into the uart handler) and if it reaches 0, finally the complete interrupt is called.
Interrupts
Note that StmCube does the peripheral initialization and interrupt linking for you, but if you program from scratch you need to remember to write and register UART_IRQ_Handler
You can find here the code navigator to review my snippets and investigate further.

Odd PIC behavior with variables set by ISR

I am observing some odd behavior in my PIC code where I copy data from a
global variable that is written by an ISR into two different buffers
consecutively, but the value only seems to successfully get written into
one of the two buffers. When I try this with a different variable that is
not written by an ISR, it correctly gets copied into both buffers.
My setup is a PIC16LF15355 using the XC8 compiler version 1.45. I am
configuring one of the PIC MSSP modules as a SPI interface to write
measurement data and switch states to an RF transceiver. I have a another
remote RF transceiver that receives and processes the data with a
PIC16LF15355 via a SPI interface. For both PICs, I am configuring the
second MSSP module as an I2C interface connected to a Raspberry Pi for
debugging purposes. Any data that is written to the SPI buffer on the
transmitting end is also copied to the I2C buffer so that it can be
periodically read to see what is being sent. Likewise, the data read
into the SPI buffer on the receiving end is copied to an I2C buffer so
that it can be read periodically to see what is being received.
There is a switch input on the transmitting end that is debounced with
a timer ISR (timer0_handler in the code below). The state gets written
into sw1State by this ISR. When it comes time to transmit data, the
switch state and other measurement data gets copied into the I2C buffer
and then the I2C buffer gets copied into the SPI buffer for transmission.
When I sample the I2C buffer contents periodically from the Pi, I can see
switch state changing in response to presses of the switch. But when I
sample the I2C buffer contents periodically on the receiving end, I only
ever see the switch state default value (1).
What is strange is that if I change the code to copy the value from a
variable that is not written by an ISR, the value shows up correctly on
the receiving end. But copy from the I2C buffer to the SPI buffer is
just a generic copy of one array to another, and all other measurement
data shows up correctly on the receiving end. The two buffers should
be identical. And there is nothing in my code that modifies the SPI
buffer between the time when it gets copied from the I2C buffer and
when the SPI buffer gets written out to the transceiver.
A stripped down copy of the code is included below. I've been looking
at this for several days and I just don't see what is causing this.
volatile uint8_t sw1State; // current switch 1 state
volatile uint8_t sw1TimeExpired; // true if switch 1 debounce time expired
uint8_t spiOutBuf[13]; // SPI output buffer
uint8_t spiInBuf[13]; // SPI input buffer
uint8_t i2cBuff[13]; // I2C input/output buffer
void interrupt main_ISR( void ) {
I2C_slave_handler();
timer0_handler();
switch_input_handler();
}
int main( void ) {
... device initialization code omitted for brevity
sw1State = 1;
sw1TimeExpired = 1;
for ( ; ; )
{
// Read the ADC values and add to the I2C buffer (for debugging).
for ( uint8_t chan = 0; chan < 9; chan++ )
{
i2cBuff[chan + 1] = ADC_read( chan );
}
// Read the switch status and add to the I2C buffer.
i2cBuff[10] = sw1State;
// Load the write transmit payload command into the SPI buffer.
spiOutBuf[0] = CMD_W_TX_PAYLOAD;
// Copy the payload data from the I2C buffer to the SPI buffer.
for ( uint8_t i = 1; i < 13; i++ )
{
spiOutBuf[i] = i2cBuff[i];
}
// Write the SPI buffer to the transceiver module payload register.
writeReadSPI( 13 );
// Clear the RB4 interrupt-on-change interrupt flag (IRQ change) and
// set CE high to initiate the transmit. Hold CE high until an ack
// is received or there is an ack timeout.
IOCBFbits.IOCBF4 = 0;
CE = 1;
__delay_us( 130 ); // ensure minimum state change transition time
... code here omitted which checks the transceiver status
__delay_ms( 80 );
}
}
void switch_input_handler( void ) {
// Check for switch 1 trigger.
if ( IOCCFbits.IOCCF6 == 1 )
{
IOCCFbits.IOCCF6 = 0; // clear IOC interrupt
// Process switch 1 trigger if debounce time has expired.
if ( sw1TimeExpired == 1 )
{
// Toggle between OFF state and ON state.
if ( sw1State == 1 )
{
sw1State = 2;
}
else
{
sw1State = 1;
}
// Load and restart Timer0 with 2-second counter value.
T0CON0bits.T0EN = 0;
TMR0H = 0xc2;
TMR0L = 0xf7;
T0CON0bits.T0EN = 1;
sw1TimeExpired = 0;
}
}
}
void timer0_handler( void ) {
if ( PIR0bits.TMR0IF == 1 )
{
PIR0bits.TMR0IF = 0; // clear the interrupt
T0CON0bits.T0EN = 0; // disable Timer0
if ( sw1TimeExpired == 0 )
{
sw1TimeExpired = 1; // Note the switch 1 timeout for switch handler.
// No state transition here. This just enables
// the next switch interrupt to change state.
}
}
}
void I2C_slave_handler( void ) {
... code omitted for brevity
}
From the code snippet it appears that the SPI channel is transmitting as a background task, while the I2C channel is transmitting in the foreground (interrupt handler). This could potentially result in a race condition and explain the behavior you are experiencing.
Recommend you try using a single transmit buffer for both channels, and set a variable to indicate when both channels have successfully sent the the data. Once both channels indicate data transmitted, then generate a fresh data packet/buffer for the next cycle.

STM8 interrupt serial receive

I am new to STM8, and trying to use a STM8S103F3, using IAR Embedded Workbench.
Using C, I like to use the registers directly.
I need serial on 14400 baud, 8N2, and getting the UART transmit is easy, as there are numerous good tutorials and examples on the net.
Then the need is to have the UART receive on interrupt, nothing else will do.
That is the problem.
According to iostm8s103f3.h (IAR) there are 5 interrupts on 0x14 vector
UART1_R_IDLE, UART1_R_LBDF, UART1_R_OR, UART1_R_PE, UART1_R_RXNE
According to Silverlight Developer: Registers on the STM8,
Vector 19 (0x13) = UART_RX
According to ST Microelectronics STM8S.h
#define UART1_BaseAddress 0x5230
#define UART1_SR_RXNE ((u8)0x20) /*!< Read Data Register Not Empty mask */
#if defined(STM8S208) ||defined(STM8S207) ||defined(STM8S103) ||defined(STM8S903)
#define UART1 ((UART1_TypeDef *) UART1_BaseAddress)
#endif /* (STM8S208) ||(STM8S207) || (STM8S103) || (STM8S903) */
According to STM8S Reference manual RM0016
The RXNE flag (Rx buffer not empty) is set on the last sampling clock edge,
when the data is transferred from the shift register to the Rx buffer.
It indicates that a data is ready to be read from the SPI_DR register.
Rx buffer not empty (RXNE)
When set, this flag indicates that there is a valid received data in the Rx buffer.
This flag is reset when SPI_DR is read.
Then I wrote:
#pragma vector = UART1_R_RXNE_vector //as iostm8s103f3 is used, that means 0x14
__interrupt void UART1_IRQHandler(void)
{ unsigned character recd;
recd = UART1_SR;
if(1 == UART1_SR_RXNE) recd = UART1_DR;
etc.
No good, I continually get interrupts, UART1_SR_RXNE is set, but UART1_DR
is empty, and no UART receive has happened. I have disabled all other interrupts
I can see that can vector to this, and still no good.
The SPI also sets this flag, presumably the the UART and SPI cannot be used
together.
I sorely need to get this serial receive interrupt going. Please help.
Thank you
The problem was one bit incorrectly set in the UART1 setup.
The complete setup for the UART1 in the STM8S103F3 is now(IAR):
void InitialiseUART()
{
unsigned char tmp = UART1_SR;
tmp = UART1_DR;
// Reset the UART registers to the reset values.
UART1_CR1 = 0;
UART1_CR2 = 0;
UART1_CR4 = 0;
UART1_CR3 = 0;
UART1_CR5 = 0;
UART1_GTR = 0;
UART1_PSCR = 0;
// Set up the port to 14400,n,8,2.
UART1_CR1_M = 0; // 8 Data bits.
UART1_CR1_PCEN = 0; // Disable parity.
UART1_CR3 = 0x20; // 2 stop bits
UART1_BRR2 = 0x07; // Set the baud rate registers to 14400
UART1_BRR1 = 0x45; // based upon a 16 MHz system clock.
// Disable the transmitter and receiver.
UART1_CR2_TEN = 0; // Disable transmit.
UART1_CR2_REN = 0; // Disable receive.
// Set the clock polarity, clock phase and last bit clock pulse.
UART1_CR3_CPOL = 0;
UART1_CR3_CPHA = 0;
UART1_CR3_LBCL = 0;
// Set the Receive Interrupt RM0016 p358,362
UART1_CR2_TIEN = 0; // Transmitter interrupt enable
UART1_CR2_TCIEN = 0; // Transmission complete interrupt enable
UART1_CR2_RIEN = 1; // Receiver interrupt enable
UART1_CR2_ILIEN = 0; // IDLE Line interrupt enable
// Turn on the UART transmit, receive and the UART clock.
UART1_CR2_TEN = 1;
UART1_CR2_REN = 1;
UART1_CR1_PIEN = 0;
UART1_CR4_LBDIEN = 0;
}
//-----------------------------
#pragma vector = UART1_R_RXNE_vector
__interrupt void UART1_IRQHandler(void)
{
byte recd;
recd = UART1_DR;
//send the byte to circular buffer;
}
You forget to add global interrupt flag
asm("rim") ; //Enable global interrupt
It happens at non isolated connections whenever you connect your board's ground with other source's ground (USB<->TTL converter connected to PC etc.), In this case microcontroller is getting noise due to high value SMPS's Y capacitor etc.
Simply connect your RX and TX line's via 1K resistor and put 1nF (can be deceased for high speed) capacitors on these lines and to ground (micro controller side) to suppress noises.

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.

Resources