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.