STM32 UART #5 || Receive Data using IDLE Line || Interrupt || DMA

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Hello and welcome to controllers tech. This is the fifth video in the STM32 UART series,   and today we will continue receiving the  data, but we will use the IDLE line to do so We covered how to receive data using the DMA in  the previous video, although the data reception   worked very well, but we needed to know the  size of the data to be received in advance. The idle line can handle the  issue of unknown data very well. Idle line is detected by the UART peripheral,  when the RX line remains idle for some time. The line can be idle when the sender has stopped  sending the data, or when the sender is sending   a large data in smaller chunks, and there is  an unusual delay between 2 successive chunks. This idle time depends on the baud  rate configuration of the device,   and will be automatically  handled by the peripheral. For high baud rates, this idle time is low  as compared to that for the lower baud rates. When the peripheral detects this idle  line, it can trigger the interrupt,   or exit the blocking mode, depending  on how we are receiving the data. In today’s tutorial, we will utilize  this idle line feature of the STM32,   to receive the data of unknown size. The advantage it has over the DMA  method we used in the previous video is,   this time we won’t need to  send the size of the data. We will extract the size from  the received data itself. I will continue with the previous project. Here I have deleted all the  code from the previous video. Now let’s open the cube MX. Here we will keep the UART DMA, as  we will use it in today’s video. Let’s change the mode to normal. Make sure the interrupt is also enabled. The basic configuration for  the UART is still the same. Click save to generate the project. We will first start with the simple  reception using the idle line. This method could be used when you want  to transfer small data between 2 devices,   but the receiver does not know  the size of the incoming data. Let’s create a buffer of 30  bytes to store the received data. The indx variable will keep track of  how many data bytes have been received. Here we have 3 different functions  to receive data using the idle line. Note that these functions start  with UARTex, and not just UART. The receive to idle is used in the blocking mode,  then we have the interrupt mode, and the DMA mode. We will not use the blocking  mode, as it is not that useful. Let’s use the interrupt mode to receive the data. The parameters are, the UART instance, the buffer  to store the data, and the size of the buffer. This size variable is the maximum  size of the data you are expecting,   so better set it the same as the buffer size. The interrupt will trigger  in 2 different scenarios. First, when the data received  is less than 30 bytes, and the   sender has stopped sending the data for some time. And second, when the sender  has sent 30 bytes data,   whether it is sending more data  or not, it does not matter here. I will demonstrate both scenarios,  so you will understand them better. When the idle line interrupt is  triggered, the RxEventCallback is called. The size variable of this callback represents   how many bytes were received  when the callback is called. Inside the callback, we will simply extract  this size, and save it in the indx variable. The interrupt is disabled  after the callback is called,   so we will again call the interrupt function,  so that the MCU can continue receiving the data. Let’s build and debug the project now. I will use the same serial monitor on the  computer, as used in the previous videos. I have added the RX data, and the  indx variable in the live expression. Let’s run the debugger now. I am going to send 5 bytes first. You can see the indx variable has the value  5, and the RX data has the data we sent. Since we are dealing with a small amount of data,  you can process this data in the callback itself. Now let’s send 8 bytes of data. Here the indx variable has been updated,  and the RX data now has the new data in it. Basically the idle line interrupt is called,  whenever the sender stops sending the data,   and the Size parameter of the callback  stores the number of bytes received. Since we are receiving a maximum of 30 bytes,   this can work well if we send data  less than or equal to 30 bytes. Let’s try sending more than 30 bytes. Here I am going to send 36 bytes of data. Note that the indx variable is now 6. This is because the first interrupt  was called, when it received 30 bytes. The data was stored in the RX data buffer,  and the receive function was called again. The remaining 6 bytes were stored in the  second call, and hence the indx variable is 6. The new data overlapped the first 6  bytes of the previously stored data. We can write a much better code to handle such  situations, but I would advise not to do that. What I am trying to say here is  that we should not receive a large   amount of data in the interrupt  mode, as we have the DMA for it. And for the small data, we can  define a buffer large enough to   store the entire data without being overlapped. So using the receive to idle in the interrupt mode   is a convenient way to receive  the data of the unknown length. After the interrupt is triggered,  we have the data, and we have the   size of the received data, so we  can handle the data very well. Now let’s see the receive to idle in the DMA mode. Right now the DMA is configured in the normal  mode, and it works similar to the interrupt mode. Here I am just replacing the interrupt part with  the DMA, the reset of the code remains the same. Let’s build and debug it now. I am sending 9 bytes of data first. Here we have received the  data, along with the size. It is working just as how it  worked using the interrupt. Even if we send 36 bytes of data, the  indx variable again has the value 6,   with the first 6 bytes of  RX data being overlapped. Let me write a separate code for the DMA. Let’s delete this part as I want to  run the receive function just once. Here I am planning to receive a large  amount of data, so defining a large buffer. Also define a count variable, which will count how   many times the callback was  called during the transfer. The size is going to be big now,  so the indx variable is 16 bit now. In the main function, we will call  the receive to idle in the DMA mode. This time we will receive a maximum of 4096 bytes. We also need to increment the  count variable in the callback,   so that we can track how many  times the callback was called. Alright let’s build and debug the project. I am going to send the same files via the  UART, that I used in the previous videos. I have also added the count  to the live expression. Alright let’s send the file with 2044 bytes data. Here you can see the number  of bytes sent by the software. All 2044 bytes have been sent. Note that the value of the indx  variable is 1018, and not 2044. The count value is one, that means  the callback was just called once. Now let’s check the RX data buffer. Since we sent 2044 bytes, let’s check if  we have received the last few data bytes. Well the buffer is just empty here. This means we did not receive the entire data. So what happened here ? The function was set to receive 4096 bytes,   so we should have been able to  receive that much data or less. But we only received 1018 bytes. This is because the software  we use to send these files,   does not send the entire file in a single attempt. Instead it sends the data in small chunks. Here it sent 1018 bytes once, then it might  have sent another set of 1018 bytes, and so on. The large data file is sent in  a smaller number of data bytes,   and hence the RX event callback  is called multiple times. But we only set the function  to be called just once,   and hence we did not receive the entire data. The number of times this loop  will be called is unpredictable. When I was testing the project, it kept varying. Even if we call the DMA function again,   the data will just start storing from  the beginning of the RX data buffer. Hence it will overlap the  previously received data. We can still use some offset  to handle such a situation,   but that would be too much work, and we  have to consider too many variables here. So to handle such scenarios, and to  receive large data of unknown size,   we better use the circular mode. Let’s open the cube MX, and  change the DMA to circular mode. Alright now I am keeping the code as it is, and  let’s see how it behaves in the circular mode. I am going to send the same file again. Here you can see the value of the indx  variable is now equal to the file size. The count is 3, that means the  callback was called 3 times. One interesting thing to note here  is that even after multiple calls,   the size variable keeps record of its previous  value, and it keeps on adding the new size to it. This is why the indx variable  is 2044, and is not equal to   the number of bytes received in the  last time the callback was called. Now let’s check the data at the 2043rd position. Here we have received the complete data. So we can receive a large amount  of data in the circular mode. We can also find out the number of bytes  received, so we don’t have to rely on the   sender to send this information, just like  we did in the DMA mode in the previous video. Note that the DMA is in the circular mode,   so the next received data will be stored  at the very next position in the RX buffer. Let’s open the Rx Data buffer to observe this. Here I am going to send 3 bytes of data. You can see the data is stored at the  very next position in the RX data buffer. Also the indx variable is now 2047. In circular mode, the DMA will always store the  new data at the very next position in the buffer. But let’s assume that we don’t  want something like this. Instead when all the data is received,   the new set of data should be stored  at the beginning of the RX buffer. It is a bit complicated to achieve this,  as we do not know the size of the data,   so we can’t figure out if the sender has sent  all the data, or is it still sending more. But there is one parameter  that we need to consider here. Even though the sender can send  the data in smaller chunks,   the delay between 2 successive  chunks is not very large. So we could implement some timeout feature  to identify if we have received all the data. Let’s define a 16 bit variable in the main file. Now define the same as an external  variable in the interrupt file. The sys tich handler is called every millisecond,  so we will increment the variable in this handler. Now whenever the RX event callback is  called, we will reset the variable to 0. The time delay between 2 successive chunks of  data can not be more than a few milliseconds. But to keep the maximum  flexibility for the sender,   I am assuming that the sender  can send data within 1 second. In the while loop, we will check if  the timer value is more than 1 second. This will imply that no data has  been received in the last 1 second,   and we could assume that the sender  has stopped sending the data. To keep things more practical, keep this  timeout small, like 100 milliseconds or so. Once the timeout is reached, you  can process the received data,   then stop the DMA and start the  receive to idle in DMA mode again. I have already mentioned it in the previous video,   the circular DMA needs to be stopped  first, before making the same call again. So we can implement this method to  receive a large amount of unknown data,   and can still get the size of the received data. But what if the data is huge, say in megabytes,   and we need to store it in  the SD card, or a flash drive. We can’t store the data in megabytes in the RAM,   as most of the MCUs do not  have enough memory for that. We will use a small buffer to receive this data,   and then send the received  data directly to the file. I am writing a separate code for this section. Let’s define RX size, as we are  going to use it in multiple places. So here we will only receive 256 bytes in the Ram  buffer at once, and using only these 256 bytes,   we will save the entire data in the final  buffer, or in an external file in the SD card. Let’s define some more variables,  which will be used in this method. Now once the 256 bytes or less are  received, the callback will be called. Here we will copy the data from  the RX buffer to the final buffer. The indx2 variable will keep track of  the data position in the final buffer,   and the indx1 variable will keep  track of the RX data buffer. Here we are copying the number of  bytes as stored in the size variable,   as this variable holds the actual number of  data bytes received when the callback is called. Now we need to update the variables,  so that there is no overlap. We will check if the size  variable is equal to the RX size. This means that all 256 bytes were  received when this callback was called. In this case, we will increment  the RX complete variable. Now update the indx2 variable, which will be  equal to the RX size times the RX complete. So when the 256 bytes are received,   the RX complete will be 1, and  hence the indx2 will be 256. When 512 bytes are received, the RX complete  will be 2, and hence the indx2 will be 512. At this moment, the new data will be received at   the beginning of the RX data buffer,  so we will reset the indx1 variable. This is to make sure that the next data will be  copied from the beginning of the RX data buffer. If the callback was called, before  we even received the 256 bytes,   we will increment the indx2  according to the size parameter. And also update the value of the indx1 variable. Here I am adding the indx2 variable with the  difference between the size and indx1 variable. This is because the size variable retains  its value between different calls. Say for example, if we received 20 bytes first,  then the indx1 and indx2, both will be 20. Now we received 30 bytes in the next chunk. The size variable will keep adding  its value, which will be 50 now. So we need to remove 20 from it to get the  actual number of bytes received during this call. Also the indx1 variable will now have the  value 50, so we can use this in the next call. If you want to implement the timeout, set the  timer to 0 each time the callback is called. In the main function, we will receive 256  bytes using the idle line in the DMA mode. Alright let’s build and debug the code now. Let me add all the necessary  variables to the live expression. Alright let’s send the 2044 file first. Here the indx2 variable is 2044, which  means we have received all the data. The RX complete is 7, which means it is 7  times that we received 256 bytes of data. The indx1 variable is 252,  which are the additional   bytes we received after last time  the RX complete was incremented. Which actually fits the calculation as shown. Let’s cross check the data once. Let’s check the beginning of the data. Here we have the values 0 8 6 7 2 6. Let’s verify it with the actual file. We have the correct data at the beginning. Here the data at the end is 1 3 5 0 0 5. Let’s check the data at the 2043rd  position in the final buffer. Here you can see that we have the  exact data at those positions. If you note here, we have received some  additional data, most probably the garbage data. But you don’t need to worry about  it, as we have the total size,   so we will only handle the  data as per the size received. You can use the timeout to reset the  buffer, stop and restart the DMA. I have not implemented it, so  I will just reset the debugger. Let’s try sending another data file. I am sending this 2176 bytes file now. Here you can see that we have  received the entire data again. So we are able to receive all  the data using a smaller buffer. Unlike the previous video, we don’t need  to know the size of the data in advance. One last thing before we end this video. The timer function can reset the  DMA in the middle of the reception,   so we will implement one more check for it. Let’s define a variable, which will be  used to enable or disable the timer. Every time the callback is  called, we will set this variable. And once the timeout occurs,  we will reset the variable. Let’s also define this as an external  variable in the interrupt file. Now in the sys tick handler, we will only  increment the timer, if the timer is enabled. This will prevent the timer from resetting  the DMA while no data is being received. So I hope you understood how to use the receive to  idle feature to receive the data of unknown size. You can download the code from  the link in the description. Leave comments in case of any doubt. Keep watching, and have a nice day ahead.
Info
Channel: ControllersTech
Views: 1,884
Rating: undefined out of 5
Keywords: stm32, stm32f4, f103, discovery, nucleo, stm32f7, stm32G, cubeIDE, sensor, module, tutorial, example, can, i2c, spi, uart, learn, tutorials, usart, tx, rx, pin, parameter, configure, configuration, send, data, serial, transmit, receive, pc, computer, connection, device, console, print, 115200, 9600, baud, rate, word, length, number, string, cubeMX, oversampling, virtual, com, port, stm32l, interrupt, dma, complete, normal, circular, callback, half, DMA, IT, hal, buffer, unknown, random, norma, mode, large, handle, idle, line, reception, using, feature, UARTex
Id: MXxopA0k3Ys
Channel Id: undefined
Length: 21min 40sec (1300 seconds)
Published: Sat Feb 03 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.