Hello and welcome to controllers tech. This is the second video in the STM32 UART series,
and today we will continue sending the data. We will use the interrupt, and the DMA to send
data to the serial console on the computer. I have already covered the basic configuration
in the previous video, so I will skip that part. In today’s video, I will only focus on
sending the data using interrupt and DMA. This is the project from the previous video, and
I will continue with it in today’s video also. Let’s open the cube MX configuration file. To demonstrate the advantage
of using interrupt and DMA, we would need to run some other process also. So I am setting the pin PA5 as the output,
which is connected to the on board LED. We will monitor this LED as some
continuous process in the code. Go to the USART 2, NVIC tab,
and enable the global interrupt. I am not making any changes to
the parameters, as we will use the same configuration as the previous video. Let’s also enable the DMA. We are sending the data, so select the USART 2 TX. There are different DMAs available in the MCU,
and DMA 1 stream 6 got selected for this purpose. The data direction is memory to peripheral as we
are sending the data from memory to the USART. The DMA can be used in the normal
mode, or the circular mode. I will demonstrate both, but let’s
leave it to the normal mode for now. The data width is set to byte as we can
transfer one byte at a time in the UART. After sending each data byte, the memory
address will be incremented automatically, so leave this option checked. That is all the configuration we need,
click save to generate the project. Alright here you can see the
USART 2 DMA handler got defined. Now first we will see why we need to use
the interrupt or DMA in the first place. Let’s define an array of 10 kilobytes,
which we will end via the UART. Now after everything is initialized,
fill the array with some data. We will send the data in the while loop, so modify the UART transkit function
to send the array we defined. The timeout is set to Haal max delay, this
means the function will never timeout. Basically we are allowing the UART to take
as much time as possible to send the data. Now toggle the LED, and give
a delay of 500 milliseconds. Basically we are sending the
data in the blocking mode, so if the data transfer takes more time, the LED
will take more than 500 milliseconds to blink. Let’s build the code now. Alright there are no errors,
so let’s flash it to the board. Here you can see the LED is definitely
taking more than 500 milliseconds to blink. This is because the UART takes a lot of
time to transfer 10 kilobytes of the data. We can see the data on the console, and it
is arriving in groups every 500 milliseconds. The LED does not blink at the required rate. Sending a large amount of data in
the blocking mode is not a good idea, as it delays the rest of the processes. This is because the CPU needs to
wait for the transfer to complete, before it can execute the next statement. To overcome such an issue, we use the interrupt. Let’s define a variable to keep track
if the data has been transferred. In the while loop, we will only transmit
the data if this variable is set to 1. Here call the function HAL_UART_Transmit_IT. The parameters of the function are the UART
instance, the data buffer, and the size. After calling the function, set the variable to 0. In the interrupt mode, the data
is transferred in the background while the CPU can process the rest of the tasks. When all the data has been transferred, an interrupt will trigger, and the
transfer complete callback will be called. Inside the callback, we can
do the rest of the processing. Here we will simply set the variable to 1, so to
inform the CPU that the data has been transmitted. Let’s also define two counters,
which will keep track of how many times the interrupt has been called,
and the while loop has run completely. Alright let’s build and debug the project. Let me add the counters to the live expression. Here you can see the interrupt counter
is half of that of the while loop. The while loop is running
independently irrespective of if the data has been transferred or not. You can see the LED is now
blinking every 500 milliseconds. Here you can see the data received in the console, and the reception is now continuous compared
to what we saw in the blocking mode. Basically it is taking around 1 second
to transfer 10 kilobytes of data, and this is why the interrupt
counter is half of the while loop. Let me change the blink delay to 1
second, and debug the project again. Now you see both the counters are
incrementing at the same rate. The LED now blinks every 1 second. The data transfer is still continuous, and it
does not take 1 second delay into consideration. This is because the data is
transferred in the background, and by the time the CPU waits for the HAAL
delay to finish, the data is transferred. Then the loop calls for the transfer again. So we were able to transfer
the data using interrupt, and the CPU still managed the
LED blinking at the defined rate. We can always use interrupt to transfer the
data, but the transfer is still done by the CPU. So when transferring a large amount of
data, it puts too much load on the CPU. This is where the DMA comes in. DMA stands for Direct Memory Access
Controller, and it is used to provide high-speed data transfers between peripherals
and memory, and between memory and memory. Data can be quickly moved by
the DMA without any CPU action, This keeps CPU resources
free for other operations. We will use the function HAL_UART_Transmit_DMA,
to send the data via the DMA. The parameters are the same
as the interrupt function. Once the data has been transferred, the UART
transmit complete callback will be called, and here we will set the variable to 1. We basically use the same setup
that we used for the interrupt. Let’s build and debug the project now. Here you can see the counters are incrementing
just as they were during the interrupt. The LED is blinking every 1 second,
so the process is running fine. We are receiving data continuously,
just as we were using the interrupt. I don’t have the means to show the CPU
load, but it is reduced while using the DMA. Now while configuring the DMA we came across the
DMA modes, the normal mode and the circular mode. Right now the DMA is configured in
the normal mode, and in this mode the DMA does not reload the address
after the transfer is complete. Basically it transfers the data once, and after
the data has been transferred, the DMA stops. Let me show this by calling the transfer
function outside the while loop. Let’s build and debug the project. Here you can see the interrupt
counter only incremented once, and the while loop continues to run. This is because the DMA transfers the data
once, so the interrupt is only triggered once. Here you can see the serial console received
10 kilobytes of data, and that’s it. Now let’s keep the same code,
go to the cube MX configuration, and change the DMA mode to circular. Let’s build and debug the project again. You can see the interrupt counter is increasing
at a faster rate than the while loop. Basically the DMA is continuously sending the
same data, and the rate is faster than 1 second. This is why the counter is
increasing at a faster rate. In the circular mode, The source and the
destination addresses, and the number of data to be transferred are automatically
reloaded after the transfer completes. So the DMA continues sending the same data to
the same address, and from the same address. It does that in the background,
and without affecting the CPU load. Now I will show how to use the circular mode
more effectively, and how to stop the DMA. Just like the transfer complete callback, we
also have a half transfer complete callback. This callback is triggered when the DMA
finishes transferring half the data. We will utilize this callback in our code. We are transferring 10 kilobytes of data, so half transfer complete callback will be
called when DMA has transferred 5 kilobytes. Basically it will be called when
the first half has been transferred. Inside this callback we can load new
data to the first half of the buffer, while the DMA is sending the second half. Similarly, when the second half is transferred,
the transfer complete callback will be called. The DMA is in the circular mode, so it
is transferring the first half again, and during that time, we will load new
data to the second half of the buffer. Basically when the DMA transfers the second
half, we update the data in the first half, and when it transfers the first half,
we update the data in the second half. This process will continue till
we issue a stop instruction. We can set some condition, and
once the condition is satisfied, call the function UART_DMA_Stop to stop the DMA. In the main function, we still
need to send all the data at once, so everything can start from there. Alright let’s build and debug the project. You can see the new data is
being received by the console. And the DMA stops in the end. The interrupt counter has been stopped
while the loop counter continues to count. The data was loaded as we programmed it to do. We can use the DMA in circular
mode to transfer large data, whereas we can still have smaller buffers
and load the new data in the runtime itself. This is it for the video. I hope you understood the use of interrupt and
DMA while transferring the data via the UART. You should use the different
modes as per the data requirement. Use blocking mode for small
data, interrupt for more data, and use DMA when you have a
lot of data to be transferred. I will continue with this series in the next
video, and we will see the receiving part. 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.