stm32, uart interrupt, TureSTUDIO

Thread Starter

bug13

Joined Feb 13, 2012
2,002
Hi @MrChips

As discussed on another thread
On a side note, I am playing with an stm32f303k8 in TrueSTUDIO, I love the CubeMX which can generate the driver for me and set up the clock. But I find that the uart driver generated by CubeMX is bit weird (how it handle interrupt) and I think it's not suitable for embedded system (at least not what I use to anyway).

So I want to write my own uart/usart interrupt driver, but I don't seem to find an user guide/manual for the arm-gcc compiler use in TrueSTUDIO. I have no idea how to hook my own uart/usart interrupt code, any pointer for me?? I am totally new to ARM
Thanks for your help!
 

NV64

Joined Feb 15, 2019
38
I think it is better learn UART CubeMX than trying to write your own. But if have to, see how to implement a UART library SPL. It is lower level.
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
I think it is better learn UART CubeMX than trying to write your own. But if have to, see how to implement a UART library SPL. It is lower level.
What I want to do is just like here, and the UART by CubeMX can't do that.
https://stackoverflow.com/questions...nabled-forever-using-hal-library-on-a-stm32f1

Just google stm32 SPL, seem like what I need. I will read though the manual. Thanks for the pointer.

If by any chance you use TrueSTUDIO, do you know where I can find the arm-gcc compiler manual use in TureSTUDIO? I googled it but can't find anything.
 

MrChips

Joined Oct 2, 2009
30,824
You don't have to but learning how to use CubeMX is not a bad idea. It will provide you with the template and hooks to get you started.
It is very difficult to roll your own from scratch if you don't know how to do it.

A good starting point is to begin with a working example.
Go to
File> Import...> Example Project> Download new example project from TrueSTORE
Next>
STMicroelectronics
STM32F3_Discovery
STM32F3_Discovery_EXTI_Example

Open the file stm32f30x_it.c

This is the interrupt file.
You will see a function called
void EXTI0_IRQHandler(void)

This is your basic interrupt service routine. The UART handler will be similar. I will show you how to setup the UART and NVIC later.
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
You don't have to but learning how to use CubeMX is not a bad idea. It will provide you with the template and hooks to get you started.
It is very difficult to roll your own from scratch if you don't know how to do it.

A good starting point is to begin with a working example.
Go to
File> Import...> Example Project> Download new example project from TrueSTORE
Next>
STMicroelectronics
STM32F3_Discovery
STM32F3_Discovery_EXTI_Example

Open the file stm32f30x_it.c

This is the interrupt file.
You will see a function called
void EXTI0_IRQHandler(void)

This is your basic interrupt service routine. The UART handler will be similar. I will show you how to setup the UART and NVIC later.
Cool, that is an easy place to find. Thanks. Registers are quite different from what I use to. More reading needed.
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
bug13, That is, every time you receive data, their size is unknown?
I am just playing with this stm32f303k8 board here, not doing any real work with a stm32 yet. But look promising.

Anyway, most of the stuff I do with uart/usart, the packets are usually variable length. There is however a length byte on the packet. Unfortunately I can't rely on this length byte. As the packet is send over a long distance, and some a packet can be corrupted(validate by a CRC16 checksum).

What I usually do is, using RX interrupt, put the data in a ring buffer. Then I have a task to extract one packet from the ring buffer, validate the packet by checking the CRC checksum. And then discard the packet if it's a bad packet, or do thing as required if it's a good packet.
 

NV64

Joined Feb 15, 2019
38
If there is size information and checksum in the packet, there is no reason not to trust the length byte. That is, if the interrupt has already worked, and the package did not come completely, when further processing of the package, you simply drop the damaged package.
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
If there is size information and checksum in the packet, there is no reason not to trust the length byte. That is, if the interrupt has already worked, and the package did not come completely, when further processing of the package, you simply drop the damaged package.
There is a few situations that would worry me:
- what if I have two very long packet come in back to back? As it will take time to process the first one. And the second one will only will be incomplete. Then I will get the wrong length byte. Then the next packet will be wrong, same as the one after.
- what if the length byte is corrupted, same thing will happen as above
- what if the length byte say 10, but deal to noises, 11 bytes was interpreted at the RX. At my test, the MCU will simply just freeze. I am guessing the the last byte is received but not read, and the MCU just keep throwing interrupt.
- if I need to keep calling the HAL_uart_receive_IT() for every packet I need to receive, I may as well do poll instead.

But if I can simply have a interrupt on every single byte received, and put it in a ring buffer. I can easily deal with the situation mentioned above. And I don’t need to keep calling the HAL_uart_receive_IT(). And it’s almost impossible to miss any byte is the buffer is size approximately.
 

NV64

Joined Feb 15, 2019
38
- what if I have two very long packet come in back to back? As it will take time to process the first one. And the second one will only will be incomplete. Then I will get the wrong length byte. Then the next packet will be wrong, same as the one after.
This problem can be solved as follows: Get the packet -> the interrupt is triggered -> immediately in the interrupt UART disable the same interrupt -> set the flag "data ready" -> exit the interrupt uart -> check the flag "data ready" in the main program -> process the received packet as long as necessary -> enable the interrupt UART -> wait for a new package.

- what if the length byte is corrupted, same thing will happen as above
If the length is damaged (i.e. the package is "broken") you will not come together CRC16. When analyzing a package, simply ignore it.

- what if the length byte say 10, but deal to noises, 11 bytes was interpreted at the RX. At my test, the MCU will simply just freeze. I am guessing the the last byte is received but not read, and the MCU just keep throwing interrupt.
Again, you will not come together CRC16. When analyzing a package, simply ignore it.
 

MrChips

Joined Oct 2, 2009
30,824
Are we not getting ahead of ourselves here?
Ultimately, the MCU has to process packets faster than the rate it is receiving them. You also have to implement a handshake mechanism with the sender to acknowledge both good and bad reception.

The way I would approach this is to get UART single character send/receive working first using polling. After that we can implement interrupts.

What is your baud?
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
This problem can be solved as follows: Get the packet -> the interrupt is triggered -> immediately in the interrupt UART disable the same interrupt -> set the flag "data ready" -> exit the interrupt uart -> check the flag "data ready" in the main program -> process the received packet as long as necessary -> enable the interrupt UART -> wait for a new package.
I think we maybe looking at different driver here. This is the one I am looking at (STM32F3 HAL and low-layer drivers, UM1786), and I am assuming in a supper loop.
Code:
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef * huart, uint8_t * pData, uint16_t Size)
If I can process the received packet as long as necessary without buffering the new incoming packet, will the new incoming packet lost?? But If I put the data in a ring buffer in an interrup, I can process the received packet as long as necessary and also capture all the new incoming packet.


If the length is damaged (i.e. the package is "broken") you will not come together CRC16. When analyzing a package, simply ignore it.
Again, referring to the driver above. If the packet is 10 bytes, but the length byte is corrupted, it say 15 instead, I will end up getting one packet and part of the new packet. I can handle this with extra code (and check CRC etc...), but it's just a lot simpler if I can just buffer this in a ring buffer instead. Same go if the packet is 10, but the length byte say 5.

Again, you will not come together CRC16. When analyzing a package, simply ignore it.
I understand I can check CRC16 and discard bad packet, but the problem here is not with the CRC16. The problem is if 10 bytes were expected but for whatever reason 11 bytes were send, if using the driver above, the 11th byte will not be served, and it will "freeze" the MCU.

This is the code I used to test the above situation, you can free the MCU by simply typing in terminal program very fast:
Code:
volatile uint8_t buffer[5]; // buffer

while(1){
   /* setup an uart interrupt and put data into a buffer */
   HAL_UART_Transmit_IT(&huart2, buffer, sizeof(buffer)/(sizeof(buffer[0]));
    /* a simple delay to simulate all other tasks, including processing the buffer  */
    delay_ms(10);
}
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
Are we not getting ahead of ourselves here?
In my OP, I was asking for a user guide/manaul for the compiler use in TrueSTUDIO

Ultimately, the MCU has to process packets faster than the rate it is receiving them. You also have to implement a handshake mechanism with the sender to acknowledge both good and bad reception.
On average, the MCU can process all the packets faster than the rate it is reeving them. But I have no control on when a packet is send, nor the size of a packet. I am at the receiving end (master). All the nodes (slaves) can send whatever and whenever. They do re-send if the master didn't receive their packets, but this is not desirable.

The way I would approach this is to get UART single character send/receive working first using polling. After that we can implement interrupts.
I don't have problem using the drivers. I just find the RX interrupt driver is not suitable what I need.

What is your baud?
Usually 19200, not fast I know. I do however have a lots of other tasks (about 10ms to 20ms, lots of bytes can be missed if I don't have a continue RX interrupt setup with a ring buffer)
 
Last edited:

MrChips

Joined Oct 2, 2009
30,824
No problem there. You can write the UART ISR to capture every character that is sent its way.
You just need to be able to process the packet fast enough in order to prevent buffer overrun.
 

NV64

Joined Feb 15, 2019
38
I think we maybe looking at different driver here.
No, we're talking about the same thing. I can try to create a project, but I don't have STM32F3 right now.

If I can process the received packet as long as necessary without buffering the new incoming packet, will the new incoming packet lost??
As far as I understand, no packages will be lost when using DMA.

I can handle this with extra code (and check CRC etc...)
In either case, you will need to handle CRC16, the start and end labels of the package, and so on.

The problem is if 10 bytes were expected but for whatever reason 11 bytes were send, if using the driver above, the 11th byte will not be served, and it will "freeze" the MCU.
Just now I realized what the problem is. If you use DMA and buffer the received data, the MCU will not hang. Approximately this order of operation(for example, if the packet is 10 bytes, the buffer is 50 bytes): data comes from DMA -> in the main program, we begin to process the received data. During the processing of 1 package, we may recive a few more packages. They will be accepted and buffered -> once the processing of the first batch is complete, proceed to the processing of the second batch. That is, we process each packet as long as it takes. At the same time we accept new packages. Nothing will be lost -> if it came 11 bytes instead of 10, the MCU will just write them to the buffer and continue. When processing the package in the main program "extra" byte easy to ignore.
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
No problem there. You can write the UART ISR to capture every character that is sent its way.
You just need to be able to process the packet fast enough in order to prevent buffer overrun.
If it's fast enough in a 8-bit, It should be fast enough in a 32-bit and more MHz. ;) I am just playing with a arm (stm32) here.

I still need to hunt down the manual so I can understand how the compiler handle interrupts somehow, still not comfortable without the manual.
 
Last edited:

MrChips

Joined Oct 2, 2009
30,824
I don't know what information you seek in the GNU gcc compiler that will assist you in creating your own ISR. You can look here:

https://www.gnu.org/software/gnu-c-manual/gnu-c-manual.pdf

The code template for any UART ISR is relatively straight forward.
You respond to the interrupt request by clearing the interrupt flag and storing the received character.

C:
/******************************************************************************
    USART3 IRQ HANDLER
******************************************************************************/
// globals
// RX globals
// ----------
// UART3_RX_Data
// UART3_RX_Char_Count
// UART3_RX_Data_Available
// SCI0_RX_buffer[]
// SCI0_RX_buffer_count
// SCI0_RX_data_available

// TX globals
// ----------
// UART3_TX_Data
// SCI0_TX_buffer[]
// SCI0_TX_buffer_count
// SCI0_TX_buffer_empty

void USART3_IRQHandler(void)
{
  if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
  { // RX interrupt
   if ((USART3->SR & USART_FLAG_RXNE) == 0)
    {
      UART3_RX_Data = 0;
    }
   else
    {
      UART3_RX_Data = USART3->DR & 0x7F;
      if ( (SCI0_RX_buffer_count >= SCI0_RX_BUFFER_MAX) ||  ( UART3_RX_Data == ASCII_CR) )
      { // end of buffer
        SCI0_RX_buffer[SCI0_RX_buffer_count++] = 0x00;
        SCI0_RX_Data_Available = TRUE;
      } // end of buffer
      else
      { // put into buffer
        SCI0_RX_buffer[SCI0_RX_buffer_count++] = UART3_RX_Data;
      } // put into buffer
    }
    USART_ClearITPendingBit(USART3, USART_IT_RXNE);
  } // RX interrupt

  else if(USART_GetITStatus(USART3, USART_IT_TXE) != RESET)
  { // TX interrupt
    if (USART3->SR & USART_FLAG_TXE)
    { // TXE interrupt
      //USART_ClearITPendingBit(USART3, USART_IT_TXE);
      UART3_TX_Data = SCI0_TX_buffer[SCI0_TX_buffer_count++];
      if (UART3_TX_Data)
      { // send TX data
        // TXE is only cleared by writing to DR
        USART3->DR = UART3_TX_Data;
      } // send TX data
      else
      { // end of buffer
        SCI0_TX_buffer_count = 0;
        SCI0_TX_buffer_empty = TRUE;
        USART_ITConfig(USART3, USART_IT_TXE, DISABLE);
      } // end of buffer
    } // TXE interrupt
  } // TX interrupt

}
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
I don't know what information you seek in the GNU gcc compiler that will assist you in creating your own ISR. You can look here:

https://www.gnu.org/software/gnu-c-manual/gnu-c-manual.pdf
That's quite different from what I use to (XC8, XC16 and TI compiler), there is usually an section all about interrupt. It usually mention what should do and what not to do specially apply to the target architect, contents saving and switching etc...

I am hoping to find something similar, but there is not even an section about interrupt on this gnu c manual. Could that be the arm compiler for stm32 handle all these stuff already.
 

nsaspook

Joined Aug 27, 2009
13,315
That's quite different from what I use to (XC8, XC16 and TI compiler), there is usually an section all about interrupt. It usually mention what should do and what not to do specially apply to the target architect, contents saving and switching etc...

I am hoping to find something similar, but there is not even an section about interrupt on this gnu c manual. Could that be the arm compiler for stm32 handle all these stuff already.
There shouldn't be much about interrupts in a generic C manual. There is no native C interrupt abstraction like in some languages as C is designed for low level operations on non specific hardware where interrupts are totally implementation dependent. The place to look for interrupt information is in the specific compiler documentation for a specific hardware platform.
 

MrChips

Joined Oct 2, 2009
30,824
As far as I know, there is nothing special that the programmer needs to do in the ISR except read the interrupt status and clear the interrupt flag.
C:
void EXTI0_IRQHandler(void)
{
  if ((EXTI_GetITStatus(USER_BUTTON_EXTI_LINE) == SET)&&(STM_EVAL_PBGetState(BUTTON_USER) != RESET))
  {
   ... your code
    /* Clear the EXTI line 0 pending bit */
    EXTI_ClearITPendingBit(USER_BUTTON_EXTI_LINE);
  }  
}
ARM specific code needs to initialize the NVIC for that specific interrupt, for example, EXTI0:
C:
void EXTI0_Config(void)
{
  EXTI_InitTypeDef   EXTI_InitStructure;
  GPIO_InitTypeDef   GPIO_InitStructure;
  NVIC_InitTypeDef   NVIC_InitStructure;
  /* Enable GPIOA clock */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
  /* Configure PA0 pin as input floating */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  /* Enable SYSCFG clock */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
  /* Connect EXTI0 Line to PA0 pin */
  SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);

  /* Configure EXTI0 line */
  EXTI_InitStructure.EXTI_Line = EXTI_Line0;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);

  /* Enable and set EXTI0 Interrupt to the lowest priority */
  NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}
 
Top