cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F030 UART TX via DMA only works once

CharlieMangold
Associate III
Posted on October 31, 2016 at 22:25

Hi,

I've seen similar questions to this but none seem to have helped. I'm transmitting on UART1 via DMA channel 2 and the first transmission works fine. I am unable to get anything out after receiving a DMA TC. None of the standard peripheral libraries examples show getting the DMA transmission to restart after a completion. Here are the basics(code is shown below.) 1) After pin/uart/dma initialization I wait for a send_string call. 2) Send_string() sets up DMA1 channel 2 descriptor and size, then enables DMA1_Channel2. 3) Upon DMA TC, clear DMA1_IT_TC2 flag. I have tried going to using the UART TC interrupt(as suggested in other threads) but it never seem to fire the interrupt. I'm sure it is something simple I need to clear/reset but I am unable to find it. Thanks, John C.

#ifdef SERIAL
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_0);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_0);
// Configure the serial port on PB6(TX) and PB7(RX)
GPIO_InitStructure.GPIO_Pin = TX | RX;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
#endif
#ifdef SERIAL
USART_InitTypeDef USART_InitStructure;
// USART configuration
// BaudRate = 115200 baud, Word Length = 8 Bits
// One Stop Bit, No parity,
// Hardware flow control disabled (RTS and CTS signals)
//Receive and transmit enabled
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
/* Enable USART */
USART_Cmd(USART1, ENABLE);
/* Enable the USART1 Tx DMA1 requests */
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
// USe DMA to transmit the serial data
DMA_InitTypeDef DMA_InitStructure;
/* DMA1 Channel1 Config */
DMA_DeInit(DMA1_Channel2);
DMA_InitStructure.DMA_BufferSize = SERIAL_TX_SIZE;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)infoString;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->TDR);
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_Init(DMA1_Channel2, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);
#endif
#ifdef SERIAL
/* Enable the USART Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
NVIC_SetPriority(DMA1_Channel2_3_IRQn,0);
#endif
void DMA1_Channel2_3_IRQHandler(void)
{
if ((DMA1->ISR & DMA_ISR_TCIF2) != 0) /* Test if transfer completed on DMA channel 2 */
{
txTail++;
txTail &= (MAX_SERIAL_TX_BUFFS-1);
// Clear the interrupt pending flag
DMA_ClearFlag(DMA1_FLAG_TC2);
DMA_ClearITPendingBit(DMA_ISR_TCIF2);
//DMA_ClearITPendingBit(DMA1_IT_GL2);
//USART_ClearFlag(USART1, USART_FLAG_IDLE);
//USART_ClearFlag(USART1, USART_ISR_TXE);
}
}
void uart1_send(const char *string_p)
{
size_t size;
size = strlen(string_p);
if (size)
{
uart1TX[txHead].txCount = 0;
strcpy(uart1TX[txHead].txBuffer, string_p);
DMA1_Channel2->CMAR = (uint32_t)uart1TX[txHead].txBuffer;
DMA1_Channel2->CNDTR = size;
uart1TX[txHead].txSize = size;
txHead++;
txHead &= (MAX_SERIAL_TX_BUFFS-1);
DMA_Cmd(DMA1_Channel2, ENABLE);
}
}

8 REPLIES 8
Posted on November 01, 2016 at 00:09

Well it can demonstrably work with an DeInit/Init/Enable sequence, the subset of that is probably Disable/Update/Enable. The RM no doubt covers which register can be changed while enabled, which are shadowed, etc.

Your code adding the string and starting the DMA pays no mind to if an operation is currently active, and your DMA TC doesn't initiate a new transfer.

DMA TC occurs when the last byte is transferred to the USART holding buffer, ie about two byte times before the last bit gets sent.

USART TC occurs when the last bit, of the last byte gets send, ie the USART is completely empty.

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
CharlieMangold
Associate III
Posted on November 01, 2016 at 01:05

Hey Clive,

           The code was a beginning test base to see if pumping out a string every second or so would work. Once it is working I can adjust the head/tail logic to correct for the fact that there are two bytes left to transmit out(even though I find no documentation of this other than here on the forum.) When you say ''The RM no doubt covers which register can be changed'', what are you referring to? (This DMA controller is not like one's I have worked with in the past where DMA descriptors are used.) In this current operation the second send_string() call does not come about for a full second, so I would have thought that the issue was something between the UART and DMA that needs to be cleared/reset.

Thanks,

     John C.

CharlieMangold
Associate III
Posted on November 01, 2016 at 01:16

Clive,

         I'm a little slow as I see you mean reference manual. I did find in there that non circular mode DMA needs to have the DMA channel disabled before a reload. I'll try this in the morning.

John C.

''If the channel is configured in non-circular mode, no DMA request is served after the last transfer (that is once the number of data items to be transferred has reached zero). In order to reload a new number of data items to be transferred into the DMA_CNDTRx register, the DMA channel must be disabled.''

CharlieMangold
Associate III
Posted on November 01, 2016 at 14:05

I modified the DMA interrupt to disable DMA1 channel2 after clearing the pending bits as suggested in the reference manual but it did not work. Any other suggestions on how to get this to DMA to fire again?

Thanks, John C.

void DMA1_Channel2_3_IRQHandler(void)
{
if ((DMA1->ISR & DMA_ISR_TCIF2) != 0) /* Test if transfer completed on DMA channel 2 */
{
txTail++;
txTail &= (MAX_SERIAL_TX_BUFFS-1);
// Clear the interrupt pending flag
DMA_ClearFlag(DMA1_FLAG_TC2);
DMA_ClearITPendingBit(DMA_ISR_TCIF2);
DMA_Cmd(DMA1_Channel1, DISABLE);
}
}

CharlieMangold
Associate III
Posted on November 01, 2016 at 14:12

Ok, It works better when you actually disable the correct channel. Here is the code that actually makes it work. Given that this solution still leaves two bytes that need to be transmitted out the USART before the actual transmission is complete, is there a way to use the USART interrupt so that it only fires when the transmission is complete?

Thanks, John C.

void DMA1_Channel2_3_IRQHandler(void)
{
if ((DMA1->ISR & DMA_ISR_TCIF2) != 0) /* Test if transfer completed on DMA channel 2 */
{
txTail++;
txTail &= (MAX_SERIAL_TX_BUFFS-1);
// Clear the interrupt pending flag
DMA_ClearFlag(DMA1_FLAG_TC2);
DMA_ClearITPendingBit(DMA_ISR_TCIF2);
DMA_Cmd(DMA1_Channel2, DISABLE);
}
}

Posted on November 01, 2016 at 16:33

The DMA flags TC even when you don't enable the interrupt against it.

Enable the USART TC interrupt, and use that to confirm the DMA TC state.

Not that it makes that much difference. If you fire the next DMA in the USART TC, the inter-symbol delay is going to be fractional.

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
CharlieMangold
Associate III
Posted on November 01, 2016 at 17:23

Thanks Clive,

     I have the Serial TC working but I don't like the way my head/tail works in this configuration(needing to reload a different address into the DMA register.) I guess I'll go with a single transmit buffer and use the head/tail for string queuing by the main loop when there is already a transmission going on and leave the DMA complete interrupt there as I need to disable the DMA before I can being another DMA.

John C.

CharlieMangold
Associate III
Posted on November 03, 2016 at 16:59

Hey Clive,

Just thought I'd post my final revision for anyone else needing a way to DMA to the USART from the main loop. I ended up leaving the DMA TC interrupt in but I'll probably remove that code and put it in the USART TC interrupt when I get a chance. The main loop call to uart1_send() toggles a transmit start flag to indicate that a serial transmission is underway. The USART TC interrupt toggles a transmit end flag when the transmission is done. When the flags are not equal there is a serial transmission in progress and the string needs to be queued. The main loop also needs to call uart1_check_tx_buffers() to see if any strings have ended up being queued. This code also needs a bunch of error checking but as a basic test it works well. Thanks for all the help. John C.

#ifdef SERIAL
// Serial debug code
#define MAX_SERIAL_TX_BUFFS 8
#define MAX_TX_BUFF_SIZE 128
typedef struct
{
uint32_t txSize;
uint32_t txCount;
uint8_t txBuffer[MAX_TX_BUFF_SIZE];
} SERIAL_TX_STRUCT;
volatile uint8_t txStart, txEnd;
volatile uint32_t txHead, txTail; // Head = Tail = Empty
volatile SERIAL_TX_STRUCT uart1TX[MAX_SERIAL_TX_BUFFS];
volatile uint8_t txBuffer[MAX_TX_BUFF_SIZE];
volatile uint8_t RxBuffer[64];
#endif
#ifdef SERIAL
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_0);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_0);
// Configure the serial port on PB6(TX) and PB7(RX)
GPIO_InitStructure.GPIO_Pin = TX | RX;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
#endif
#ifdef SERIAL
USART_InitTypeDef USART_InitStructure;
// USART configuration
// BaudRate = 115200 baud, Word Length = 8 Bits
// One Stop Bit, No parity,
// Hardware flow control disabled (RTS and CTS signals)
//Receive and transmit enabled
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
/* Enable USART */
USART_Cmd(USART1, ENABLE);
/* Enable the USART1 Tx DMA1 requests */
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
// USe DMA to transmit the serial data
DMA_InitTypeDef DMA_InitStructure;
/* DMA1 Channel1 Config */
DMA_DeInit(DMA1_Channel2);
DMA_InitStructure.DMA_BufferSize = SERIAL_TX_SIZE;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)txBuffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->TDR);
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_Init(DMA1_Channel2, &DMA_InitStructure);
#endif
#ifdef SERIAL
/* Enable the USART Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_EnableIRQ(USART1_IRQn);
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
NVIC_SetPriority(DMA1_Channel2_3_IRQn,0);
#endif
#ifdef SERIAL
txHead = 0;
txTail = 0; // Head = Tail = Empty
txStart = 0;
txEnd = 0; // txStart = txEnd = No active serial transmission
memset(uart1TX, 0, sizeof(uart1TX));
#endif
#ifdef SERIAL
// Turn on the serial receiver. Clear the pending bit otherwise we see a TC interrupt when the
// USART is turned on.
USART_ClearITPendingBit(USART1, USART_IT_TC);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);
#endif
#ifdef SERIAL
void DMA1_Channel2_3_IRQHandler(void)
{
if ((DMA1->ISR & DMA_ISR_TCIF2) != 0) /* Test if transfer completed on DMA channel 2 */
{
// Clear the interrupt pending flag and disable the DMA channel
// as the DMA channel needs to be disabled so that on the next
// enable the DMA will work.
DMA_ClearITPendingBit(DMA1_IT_TC2);
DMA_Cmd(DMA1_Channel2, DISABLE);
}
}
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
/* Read one byte from the receive data register */
RxBuffer[RxCount++] = (USART_ReceiveData(USART1) & 0x7F);
if(RxCount == NbrOfDataToRead)
{
/* Disable the EVAL_COM1 Receive interrupt */
USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
}
}
if(USART_GetITStatus(USART1, USART_IT_TC) != RESET)
{
// The entire serial string has been sent, toggle the
// transmit end to indicate this transmission is
// complete.
txEnd ^= 1;
USART_ClearITPendingBit(USART1, USART_IT_TC);
}
}
uint32_t uart1_get_num_tx_buffers(void)
{
uint32_t size;
if (txHead == txTail)
size = MAX_SERIAL_TX_BUFFS;
else if (txHead < 
txTail
)
{
size = (MAX_SERIAL_TX_BUFFS-txTail) + txHead - 1;
}
else
{
size
= 
txHead
- txTail;
}
return(size);
}
void uart1_check_tx_buffers(void)
{
if (txTail != txHead)
{
// There is stuff to transmit, see if we have completed
// the previous transmission.
if (txStart == txEnd)
{
// The serial transmission is complete, queue up the
// next one.
strcpy(txBuffer, uart1TX[txTail].txBuffer);
DMA1_Channel2->CNDTR = uart1TX[txTail].txSize;
txTail++;
txTail &= (MAX_SERIAL_TX_BUFFS-1);
// Toggle the start index to indicate a serial transmission has
// started and begin serial transmission
txStart ^= 1;
DMA_Cmd(DMA1_Channel2, ENABLE);
}
}
}
void uart1_send(const char *string_p)
{
size_t size;
size = strlen(string_p);
if (size)
{
if (txStart == txEnd)
{
// Nothing is pending, send the string out now.
strcpy(txBuffer, string_p);
DMA1_Channel2->CNDTR = size;
// Toggle the start index to indicate a serial transmission has
// started and begin serial transmission
txStart ^= 1;
DMA_Cmd(DMA1_Channel2, ENABLE);
}
else
{
// There is a serial transmission in progress, queue the
// string.
//Check if there are any free Tx buffers available
if (uart1_get_num_tx_buffers())
{
// Put the string into the Tx buffer queue.
uart1TX[txHead].txSize = size;
strcpy(uart1TX[txHead].txBuffer, string_p);
// Move the head index to show there is a buffer to be
// transmitted
txHead++;
txHead &= (MAX_SERIAL_TX_BUFFS-1);
}
}
}
}
#endif