 Hello and welcome on our STM32Cube IDE by Zeke's training session. In this section we will create ADC example using the DMA transfers of the conversion results. ADC will be triggered by the timer events. For all of those actions we will use HAL library. We will start with launching STM32Cube IDE. We can use existing workspace or we can create a new one. And we will create a new project for STM32G071 microcontroller. I will create a new project within existing workspace. So I go to file new STM32 project. It will start with the target selection. So our microcontroller is STM32G071RB, RQFP64 package which is present on Nucleobard. Our project name could be for example G0, ADC, DMA. And I would use C language, executable binary file and project type STM32Cube. I would keep the rest as a default settings. Let's wait a minute till the device configuration will be started. Okay, we've got the device configuration window in front of us. There is our microcontroller and it's been out. So let's start from selecting the debug interface. So I go to system, core, assist and serial wire. I can see PA13 and 14 selected as a debug interfaces. Then let's select analog channel. So I go to analog section, ADC1. I scroll down to select the internal temperature sensor. We cannot see any change on the pinout because it's internal. And I need to put some configuration for the ADC. So let's go to the parameter settings first and let's select some basic settings. We will keep the clock prescalar to synchronous clock mode divided by 2. It means that our ADC would be clocked by the system clock divided by 2. Our system clock is HSI 16 MHz, which we can verify in a clock configuration. So this is our system clock, 16 MHz. And ADC clock mode, it is this one. This selector, it is used for the ADC clock. At the moment we can see that it's clocked by the system clock. And additionally, we are dividing this clock by 2. It means that ADC would be clocked by 8 MHz, which is important to select the proper sampling time parameters. Let's go further. We need to set the sampling time for selected channel. We've got two sampling times. In fact, we can select two different sampling times for two different groups of channels, which would be sampled. In our case, there would be only one channel, which would use, for example, sampling time common 1. By default, there is the smallest possible value there. It's 1.5 clock cycles, which is far too fast for our temperature sensor. But to have the exact value, we need to go to the documentation of our microcontroller. How to reach documentation to our microcontroller? In the meantime, I have opened the datasheet for our STM32G071 microcontroller. Let's try to find the proper value of the sampling time of our temperature sensor. For this purpose, we'll have a look into the electrical characteristics and operating conditions. Within this list, there should be one chapter concerning temperature sensor characteristics, like here. In this chapter, we can see some basic characteristics of our temperature sensor. On the next page, we can find the sampling time for this temperature sensor, which is set as a minimum of 5 microseconds, which is the 5 microseconds. Having these 5 microseconds as a minimum sampling time requested for internet temperature sensor and knowing that our ADC would be blocked by 8 MHz, we can calculate easily that we need at least 40 clock cycles of this 8 MHz clock to properly sample our ADC channel temperature sensor. Let's have a look at which value would be the most proper one. We do not have 40 clock cycles, we've got 39.5, which is below this minimum value. This is why we will select the next one, so 79.5 clock cycles as a sampling time. We are leaving the sampling time common to not touched, as we will not use it. We will have only one conversion, which is the temperature sensor, and we need to specify as well the trigger conversion source. By default it is software, we will change it to Timer 2 trigger out, and we select one of the edges, we can keep it like this. Let's scroll down the rank 1, which is the information about our channel. So we can see it is one channel, it is its name, it's a temperature sensor, and its sampling time is 79.5 clock cycles, which is the value we would like to have. We keep the rest of the parameters not touched, because we will not use them. The next point would be to configure the DMA, which would be used to transfer the data from ADC to internal buffer within SRAM. So let's go to the DMA settings, there is nothing at the moment, click add, select the channel, there is only one DMA request, ADC1. We have more than one channel available, because please remember that SDM32G0 contains DMA MOOCs, which allows us to select different DMA channels for different peripherals. We've got a lot of possibilities here. Select the default configuration. That direction would be from peripheral to memory, because we will take the data from ADC and transfer them into the buffer within SRAM. There would be only one DMA transfer, so let's keep the priority on the low level. We will use only a single set of the conversions, so let's keep them out normal. That means that after the last conversion, the DMA transfer would be stopped and we will stop on the last component within the buffer. Data with, let's keep as a half-word, because we will transfer 12-bit data, and we would increment the address on memory side. The rest of the parameters we will keep not touched. So that's it for ADC configuration. Next step would be the configuration of the timer, which would trigger our ADC conversions. So let's go to the Timers, Timer tool, and let's select the clock source for this timer as an internal clock. We will not use any channels, because we will use only timebase. So the overflows of the timer to trigger the ADC and these ADC conversions. So let's switch to the configuration, and we need to set somehow the configuration of the timer to trigger our ADC with the frequency of 1 Hz. How to configure our timer to work with the frequency of 1 Hz on the output and trigger our ADC with this frequency? Let's have a look on the clock configuration once again. We see that our system clock is configured on 16 MHz, which is transferred to all of the peripherals. As we can see, our timer clock is as well 16 MHz. So we need to divide somehow these 16 MHz in such a way to have 1 Hz at the end. Let's come back to the pinout configuration, and we've got here a few possibilities. I would propose the following technique. Let's use a pre-scaler, which is 16-bit value, to divide the clock as the first step. So I would propose to divide it by 16 000. It is important to put here the value, which is the desired value, minus 1, due to the fact that in the final calculation there is one edit to the value stored in this PSC part of the register. After this operation, we will have as input clock to the timer, to its counter, 1 kHz. So we need to divide these 1 kHz somehow to have 1 Hz. To do this, we are using this counter period, which would be set to 1000. I put the value 999, due to the fact that we are calculating from 0. So after these 2 operations, our timer would overflow with the frequency of 1 Hz, which is the desired value. This is the first part of the configuration of the timer. So we will have a timer which would overflow in the frequency of 1 Hz. The second step is to configure the trigger output, the RGO pin parameters. So the next point is to select properly the trigger output parameters. So first we need to enable this master slave mode to enable triggering another peripherals like ADC by this timer and then select the source of this trigger output signal. So we will change this reset, which is the default value, to update event. And on each overflow of the timer, we will have a trigger pulse to start a new conversion by ADC. That's all operations within the device configuration. We can generate the code. So I can simply save the project, so Ctrl S. Yes, after a while a project is generated. So we can go to our main .c file, which is the main file of our code. Okay, so we can see it is initially pre-configured. All of the peripherals within the device configuration are initialized. What is missing is a calibration of ADC, then its start and the final configuration of its connection to DMI. And then as a final step, start of a timer tool, which would trigger the ADC conversions. So let's do it step by step. Let's start from definition of the buffer we will use to store the ADC data. So the first thing would be the definition of its maximum size. So I would call it as ADC buff size and I would set it to 8. Please have a look that I'm putting the code within the user code sections just to be sure that after the code regeneration from the device configuration my code would be not deleted. So the next step would be the buffer itself. The buffer will be used to store the 12-bit data. So we will use a 16-bit size of its basic component. Then let's name it ADC buffer and its size will set to this define, which we have defined in bit before. Okay, so this is the first step, definition of the data where we will store the temperature values. Okay, so next step would be to calibrate the ADC. Calibration is needed to remove the offset error and it should be run on each power on of the application. So just after the reset in our case. For this we've got a dedicated function within the hull. So if I would use hull ADC EX, EX means extended. It is special marking for the functions within the hull, which means that this particular function may differ from other similar functions on different STM32 lines. If you do not see an EX as a suffix the function name, it means that this function can be copy-pasted across the families without any change. In case of ADC, these calibration functions may differ from family to family. This is why we've got this EX as a suffix at the end. Okay, so we need to select the proper function. Calibration start. Then the first argument, the only argument is ADC one handler. And that's it. To be sure that this calibration has been done properly, I would check it. So I would check whether this function has been executed correctly. If it's executed correctly, it should return the value hull okay. So I would use simple if and then if... Okay, so if it's not equal, it should execute the error handler function. This error handler function is automatically generated by Cubemix as well. You can find it at the bottom of this file. This function is defined as an empty function. So we can put here any action which would be triggered in case of any problems of hull functions execution. Let's back to our coding. So our ADC is calibrated. Then the next step would be to start ADC. So let's have a look for the options we've got. ADC start. And we've got three options. We've got start, startDMA, startIT. Start is a following mode. StartIT is with usage of interrupts. And startDMA is with usage of cooperation with DMA. So we are selecting this option. And we need to specify three arguments. The first one is a handler to ADC we are using. So this is ADC HADC1. Then there is a pointer to the buffer where the data should be stored. And length of this buffer. So the first argument is pointer. Then there should be the buffer name. So ADC buffer. And it's length. And again, it would be good to check whether this function is executed correctly as well. This is why I'm adding this part of the code. And the last operation to start the process is to start the timer2. To start the timer2 we need to execute the hull function from a timer module which would start the timebase. Timebase means that we are using only the counter and its overflows without any action connected to input or output channels. So in our case we've got again startDMA, startIT. We don't care about the IT or DMA usage for the timer. So we are selecting the first function, the simplest one. The only argument we need to put here is a handler to this timer. So there is only one timer2. And again, let's be sure that everything is correct with this function execution. This is why I'm adding this checkup. Okay, and we are done. So this is the basic piece of the code to start our ADC conversion with usage of DMA and triggering by the timer2. The source of the trigger has been done in a device configuration. In the code we need to first do the calibration of ADC and then specify the buffer and its size and start both peripherals. Okay, so the missing point is to process the interrupt which is related to our DMA transfer complete. The interrupt call is managed within the stm32g0xx underscore IT.c file where we will find the DMA1 channel 1 IRQ handler. This function is automatically generated by the device configuration by stm32cube IDE. And it is calling the function HAL DMA IRQ handler from our device. The HAL library is built in such a way that if we are using DMA usually it is assigned to one of the peripherals. There is a link created between DMA and the peripheral with which DMA is working. So in such a case instead of calling the callbacks from the peripheral in our case ADC can be used instead. So what we need to do, it is defined within the HAL MSP.c file. If we go to ADC MSP init function you can see that there is a configuration of DMA channel and there is a macro called HAL underscore link DMA which is connecting our ADC with a DMA handler and it is connecting in fact the callbacks from ADC which should be used normally by its interouts to callbacks with callbacks from DMA. As a result we can reuse for example ADC conversion complete callback from ADC as a final result of DMA transfer complete. Knowing this we can add the function within our main.c file user code for a section is very good for this and we are adding the new code. So HAL ADC conversion complete callback and within this callback we can either stop ADC in DMA mode or timer to do not trigger ADC anymore. So I will stop ADC so I'm selecting the function HAL ADC stop and again we've got a DMA option and I'm checking whether this function is properly executed. If not I will call error handler. Okay so this will give us only the option to really stop the ADC operations once the buffer is full. In the next step we will try to post-process those data. So let's finish this project at the moment. Let's try to compile it. In the next step we will connect our board and try to render the debug session and let's observe what would be the result which we can gain in the ADC buffer. Okay I have my board connected. The code is compiled. Let's try after connection of the board to run the debug session. So let's go to the debug session. I run it. The application will switch to the debug perspective. Now I can have a look on the buffer. As you can see now I can have a look on its value current value, the base address, the type of it. So at the moment it contains only zeros. Please remember that our conversion will last 8 seconds. We've got a buffer which sizes 8 elements and the frequency of the trigger is 1 hertz. It gave us 8 seconds. To be sure that we are just at the end of the conversion let's put our breakpoint in a callback which would be triggered in case of DMA transfer complete. Okay so I'm starting the execution of the code. Let's write 8 seconds more or less. Okay execution is finished at the moment. So now we can have a look on the buffer. So we can for example highlight it in this area. And you see the values are not the temperature ones. We need to convert it to the temperature. Right now it's pure reading from ADC, it's a real value. It needs to be converted. For this we need a reference voltage value which is in our case the power supply of the board which is 3.3 volts. And we've got a special macro which can be used here to convert this value into the Celsius decrease. So now we will do this process. Okay so the next step would be to specify the flag which would be used to highlight when there is a time to do the conversion of the data. I would go into user code section for the private variable and I would create one variable which is 8-bit long and I would call it flag with initial value set to 0. Then I would set this flag within our DMA transfer complete flag set to 1. And based on this value I would trigger the post-processing of ADC data within while one loop. So I would create here the if loop. So if a flag is equal to 1 we will do some post-processing and at the end of course we need to clear the flag to not do the same operations all the time. Okay let's try to find the function which would convert our data but before this we will specify the index which would be used to convert the data from the buffer one by one. I would use the simplest possible variable 8-bit because we've got only 8-bit data. IDX I would set the default value as 0 and then we would create here the simple for loop for IDX equal to 0 then IDX less than ADC buff size IDX plus plus. So it would create a simple for loop. Let's find the function for macro which would convert our ADC row data into the temperature in Celsius decrease. A lot of such functions are started with low-layer modules for given peripheral. So let's start from this underscore low-layer which is the common name of the macros very useful macros to do simple operations then the peripheral name like ADC underscore and control space. We can see here interesting macro ADC calc temperature it looks that it is something we are looking for as a first argument it needs reference analog voltage which has been used for the ADC then the row data from ADC and resolution we have used during our measurements. Let's have a closer look on these macros so I would click right button on mouse and open declaration. So the first argument is analog reference voltage in millivolts so I would define this name within my code in a section private defines over here I have measured this value it's 3.3 volts in millivolts it would be 3300 and I would use this as a first argument of our macro. Let's go to the second. Second argument is conversion data measured by ADC so this is our buffer this is the name of our buffer and of course we need a single element so idx and the third argument was the resolution we have used 12 bits so I will select the resolution 12 bits like this and of course we need to store the value in the buffer again so we will just replace the old values with the new ones in the Celsius decrease let's try to compile the code and start the demarc session let's remove the break point from our callback I already did it so we don't need it in this place we would need to insert the callback in our wild one loop on the place when the flag would be cleared which means that it should be triggered once the conversion to the Celsius decrease is done I double click on this line and I run the conversion so first it would convert data from the temperature sensor and then it will land in this part of the code and convert it to the Celsius decrease we are already there so I would just highlight our ADC buffer and please have a look right now instead of 900 something we've got a value in the Celsius decrease please remember that this value is the temperature of the silicon of the structure within the microcontroller it is not the ambient temperature it is the structure temperature this is why it can be a bit higher what we feel, what we've got in our environment ok, we can terminate the session and that's it for this module thank you for watching this video