4
\$\begingroup\$

I'm working on firmware for an STM32F103 which is communicating over RS232 at 115200 baud with a motor controller. The motor controller (a Copley Xenus XTL) operates on a "speak when spoken to" protocol. I'm using the ASCII programming interface in the linked documents. The STM32 always be sending the same command ("g r0x18") to poll a register, and the motor controller replies with a variable number (~4-10 bytes) of characters terminated by a carriage return ("v 12345", where the number of digits is variable). I have code to parse the response and pull a numeric value out of it. Once the response is parsed, the register poll command should be transmitted to the motor controller again. The STM32 is also reading an ADC channel over DMA in circular mode in the background.

I'd like to implement this using the DMA controller to make everything as non-blocking as possible, but I'm a bit confused as to which interrupts I should be using and when they fire. Without using the DMA controller, the parsing code currently resides in the USART RXNE interrupt. Suppose I transmit command and the motor controller starts to reply. I believe the RXNE interrupt fires for each received byte, but what about the DMA transfer complete interrupt? Is there even any functional difference in this case between using the DMA TC interrupt and the USART RXNE interrupt?

\$\endgroup\$
  • 4
    \$\begingroup\$ "the motor controller replies with a variable number (~4-10 bytes) of characters terminated by a carriage return." -- I believe that precludes any reasonable use of DMA for this. You will need to run custom code after each byte received to detect if the transmission is complete; the DMA controller won't help here. Continue to use the RXNE, but maybe reduce the ISR to just accepting a byte, putting it into your buffer, checking if it's CR, and if it is, set some signal for further processing/parsing. \$\endgroup\$ – JimmyB Oct 1 '14 at 14:36
  • 4
    \$\begingroup\$ the parsing code currently resides in the USART RXNE interrupt A better way would be to only use the RX interrupt to put the current byte in a circular buffer. In the main loop then check if the buffer contains a complete message and parse that message. \$\endgroup\$ – m.Alin Oct 1 '14 at 14:38
  • 2
    \$\begingroup\$ Besides, 115kBaud is about 11kByte/s, which means that at 66MHz CPU clock there's hardly more than one IRQ each 6000 CPU cycles; that should be about 1% CPU load, and only while actually receiving data. \$\endgroup\$ – JimmyB Oct 1 '14 at 14:58
  • \$\begingroup\$ It h=is harder to help with only part of the information. Would you please post a link to the motor controller, and its protocol. Please also say exactly which commands you are sending to the motor controller. I do agree with the two comments, a. 1% CPU overhead isn't much, and b. take the parsing code out of the interrupt service routine. The interrupt service routine needs to be as short as practical to minimise blocking. Also, it becomes more like a DMA transfer when all it does it store bytes into RAM. I'd expect to reduce the load by using DMA, but it is more complexity. \$\endgroup\$ – gbulmer Oct 1 '14 at 18:35
  • 1
    \$\begingroup\$ While DMA is probably not necessary, you might be able to use it by setting up a transfer for the maximum possible length and letting it stall when it runs out of data, then using a timer set up when you sent the command to halt the DMA and process the results. If you can change the motor controller firmware you could put the count of bytes to be sent first and have only one UART RX interrupt catch that and program the DMA. Or you could perhaps send a break condition to terminate the message and fire an interrupt on that to evaluate the result. \$\endgroup\$ – Chris Stratton Oct 1 '14 at 20:20
3
\$\begingroup\$

You can configure DMA to work in cyclic mode, and with reasonable big buffer you can pull characters as often as you want with simple poll function. Just store index of last character retrieved and use DMA register to check how many characters it has to receive until roll over (AFAIR register has NDTR in its name), and process characters received since last call, then update index of last character retrieved.

Described DMA usage makes your poll independent from context you call it from as long as you prevent preemption. Then you can use RX interrupt to trigger poll call(s), or you can do it in other context (for example with some periodic events).

This is generally efficient if frames are long enough, baudrate is big and interrupt latency is bigger than reception time of single char.

But if data is received slow enough you can get away with processing it char by char current way, and using DMA may be overkill.

As @Chris Stratton pointed you can also just set DMA for single transmission, and wait long enough, change protocol or use other signal as "end of transmission" - and then process frame in DMA buffer.

\$\endgroup\$
  • \$\begingroup\$ "Then you can use RX interrupt to trigger poll call(s)" It's good way, but have you ever try to do like than in STM32F family? I've tried and there is a problem. When I activate DMA mode by writing DMAR in USART_CR3, the RX interrupts don't come. There is a small note about it in the it's RefMan: "If DMA is used for reception, do not enable the RXNEIE bit." That means don't use the RX interrupts. Unfortunately, it seems it's impossible. \$\endgroup\$ – user92491 Nov 23 '15 at 10:28
1
\$\begingroup\$

You should use DMA interrupts. I am using STM32F02 with STM32 STL peripheral libraries, but the idea is pretty much the same for F01:

  • Initialize your UART
  • Initialize DMA (don't forget providing the CLK signal for the peripheral BEFORE calling DMA_Init!!)
  • Configure NVIC and DMA interrupts, and enable DMA as follows:

 NVIC_InitTypeDef NVIC_InitStructure;
 //Enable DMA1 channel IRQ Channel
 //Note: maybe in your implementation you don't have DMAx_Streamy, look for channel/ stream configurations in your microcontroller manual

 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream1_IRQn; // This could be different in your implementation

 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 NVIC_Init(&NVIC_InitStructure);

 // Enable DMA1 Channel Transfer Complete interrupt
 DMA_ITConfig(DMA1_Stream1, DMA_IT_TC, ENABLE);

 USART_DMACmd(ACCESSORY_UART, USART_DMAReq_Rx, ENABLE);
 DMA_Cmd(DMA1_Stream1, ENABLE);

The USART_DMACmd function binds UART with DMA, and I think that is your answer. In this way, the DMA will copy a byte to the pointer you provided in the configuration each time a RXNE event is triggered, but interrupting ONLY when a Transfer Complete (DMA_IT_TC) event is triggered, calling the corresponding DMA function according to the configured channel/ stream.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.