Getting Started With STM32 & Nucleo Part 4: Working with ADC and DMA - Maker.io

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Applause] [Music] let's see them have a sensor that outputs an analog voltage that can be a light sensor or a temperature sensor and let's say you've got a microcontroller and you want it to read that analog voltage one of the easiest ways to do this is to use an analog to digital converter or ADC most modern microcontrollers have a DC's built-in and the stm32 is no exception to test the ADC with her nucleo board we're going to connect a simple 10k potentiometer to 3.3 volts and ground will connect the wiper pin to a zero so we can read the voltage take a look at the new Clio L 4 7 6 RG pin out and we see that a 0 on the Arduino headers is connected to PA 0 we'll want to take a peek at the timing of the ADC so we'll connect a scope probe to D 2 which is connected to PA 10 start a new STM 32 C project in stm32 cube ide and give it some memorable name zoom in on pin PA 0 and assign it the ADC one-in-five function if you go into the analog category you can see that our L 4 7 6 RG has three separate ADC peripherals and each peripheral can read from various pins also known as channels go to ADC one and set channel v 2 in v single-ended for a basic one-shot single conversion we can leave these settings alone note that if you want to perform consecutive conversions from one pin scan through different channels fire interrupts or set up DMA you will have to mess with these settings let's also set pin PA 10 to a GPIO output so that we can measure our one-shot ADC timing Save and generate code and then open main dot C in main we see that we have a handle to our ADC peripheral as well as a handle to you our to which we'll use to transmit our ADC readings the cube editor automatically configured the UART for us as we told it we're using a nucleo board in the main function declare two variables raw to store our 12 bit ADC reading and a 10 character buffer that we'll fill out to transmit over you are the first thing we'll want to do in our main loop is to set p8n high so we can begin timing next we'll start an ADC conversion by calling Hal ADC start and passing in the address of our ADC handle we then call pull for conversion which causes the processor to hang while it waits for an ADC conversion to complete once the conversion is done we get the value from the ADC channel register and store the raw value since it's a 12 bit value and we're using 3.3 volts as our reference voltage the raw value can be from 0 for 0 volts up to 4095 for 3.3 volts we then set our GPO pin low to stop our timing we'll use s print F to create a string out of our raw value and we'll then spit that message out over the UART two-port which is connected to the st link on our nucleo board finally we'll add a brief 1 millisecond delay here since we're using string functions we'll need to include the string and stdio libraries let's compile and start debugging run the code and open a serial monitor to get a printout of the raw ADC values try twisting the potentiometer back and forth and you should see the numbers rise and fall to reflect the changing voltage now let's attach an oscilloscope probe to ground and pin d2 when we measure it we see that it takes our code about 9 microseconds to start the ADC make a conversion and then copy it to a memory location that might not seem like a lot but it can add up if you need to make a constant stream of conversions very quickly or measure several channels at a time what I just showed you will work in many cases where you need to make infrequent ADC readings this could be a temperature sensor or you need to measure battery voltage but let's say you want to capture audio streams or you're making your own oscilloscope you'll need to be able to capture and store a lot more data than that in a short amount of time we don't want to bog down our CPU so we need to rely on another peripheral in this case we need to use direct memory access or DMA to move data directly from the ADC into a memory buffer here is a very oversimplified view of a microcontroller with a central processing unit over seeing a number of peripherals these peripherals might be internal like memory or they can interact with the outside world like the ADC or I squared C bus normally whenever we interact with a peripheral all data to and from these peripherals must pass through the CPU in our previous demo for example we read a value from the ADC store it to memory and then read the value from that memory address to send out over the UART peripheral this is easy to implement but it can quickly bog down our CPU if we start moving too much data some microcontrollers like our stm32 have access to an on-board direct memory access controller which functions like any other peripheral we spend a few cycles up front to configure the DMA controller along with the desired peripherals for example let's say we want our ADC to continually take samples and store them to a large buffer in memory with that the DMA acts like a large pipe that funnels data from one peripheral to the other while the CPU goes off and does other things the DMA controller will continue to pump data from the ADC to memory until we tell it to stop or we set some stop condition like a particular buffer is full to show a more simple example let's start by piping data from a buffer in memory to the UART note that some dmas will let you do peripheral to peripheral or memory to memory connections but you will most often see them being used to pass data between a peripheral and memory if you take a look at the reference manual for the l4 7/6 you can see that there's a whole chapter dedicated to the DMA note that the l4 7 6rg has two DMA peripherals you can see how the first one is connected here where each channel is like one of our pipes while you can set up multiple channels on each DMA only one channel can be communicating at a time and note that you can assign different priorities to the channels to determine who gets to talk first the second DMA controller works similarly but it is connected to different peripherals this controller can also be used to do memory - memory transfers but neither can do peripheral to peripheral in the stm32 architecture - pipe data from memory - you are to take a look at the DMA one table we see that Channel seven is connected to you start to TX so that's what we'll need to use let's see how to use DMA with UI so start a new project and give it some name like DMA UART test in the cube viewer we see that use our TX and rx for usart to is already set up for us we also have PA five connected to the LD two LED which we can use to make sure our interrupts callback is working go into system core and DMA add a new DMA request for DMA 1 and select use our to TX as we want to send data from memory out over the uart port note that it automatically assigns us to channel 7 that's all we need for this example Save and generate code in main C you can see that the DMA instance has already been configured for us it's important to note that DMA 1 channel 7 is initialized with interrupts we'll need an interrupt to occur to let us know that we're done with the memory transfer in the Hal MSP file you can see how the DMA was actually set up in code these should reflect the parameters you set in the cube MX interface finally in the interrupt file you should see a place for your interrupt handler for DMA feel free to put your interrupt service routine code in this function or I'll show you how to register your own call back in a minute back in main dot C create some long and arbitrary message that you want to send out in the while loop we want to enable the DMA transmitter bit which tells the you start peripheral that we plan to send out data via DMA while we don't explicitly need to do this as it's handled in the Hal code we're going to disable it in the interrupt callback which should ideally let you use usart for other purposes next we're going to tell the DMA to start in interrupt mode with a handle to our DMA instance and our message in memory as the source we set the destination address to the transmit data register or TDR of the usart we also tell it how long our messages when that's done it should call a callback function we could use the interrupt routine in the IT file but I want to show you how to register your own callback anyway both should get called after the transfer completes we use the Hal DMA register callback function pass it a handle to our DMA instance deep in the Hal DMA driver files we can find this enum which details the types of interrupts that can occur we want to use the Hal DMA transfer complete callback ID so we write that one here finally we give it the address of our callback function which we're about to write let's declare our callback function which I'll call DMA transfer complete we just need to pass it a pointer to our DMA instance at the bottom of main dot C we can write that function and all we'll do is disable the DMA transmit bit in usart - and toggle the onboard led since we use the string length function let's include string dot H build the project and fix any errors such as the one where I forgot to call the DMA instance by its full name which has a TX at the end I also forgot to add a delay in here so I'll make the processor wait for one second before trying to transmit again start the debug process and you should see your long string being printed out over serial every second you should also see the LED toggle every second this tells us that the DMA interrupt and callback are working let's combine these concepts by using the DMA to read the ADC and store the results to a large buffer go back into our first ADC example and open the dot IOC file which should load the cube M X perspective we already have PA 0 set as an ADC so go into the ADC settings notice that the pre scalar is set to 1 so the ADC is running as fast as possible asynchronous mode refers to the fact that the ADC samples can be taken independently of the main CPU clock and the ADC clock is set to 64 megahertz on this nucleo board by default it's also important to note that with our 12 bit resolution it takes a minimum of 2.5 cycles for the chip to take an ADC sample and another 12.5 cycles to make the conversion if we were to set the ADC clock to 80 megahertz that would give us a max of 5.33 mega samples per second since we're only using one channel we can leave scan conversion alone but we'll want to enable continuous conversion mode this will let the ADC continually take samples from one channel and store them to memory by default it will just throw an error or overwrite the value in its register if we don't read it fast enough however with DMA we can keep all those samples in memory notice that we can't enable DMA mode yet as we need to go to system core DMA and add a DMA request that's connected to our ADC peripheral in normal mode the DMA transfer will stop whenever it fills up the buffer we can set it to circular mode so that once the buffer is filled it will just start again from the beginning of the buffer just make sure you read the data fast enough or you'll lose it we do want to increment the memory address after each read but we leave the peripheral increment address unchecked as there's only one register that we get ADC values from notice that the data width is changed to half word we're dealing with 12 bit values and a half word is 16 bits so that's a good size for our data back in the ADC settings we can now enable DMA requests the other settings should be good in their default States save and generate code in main C we want to define a pretty good size for our buffer how about for K samples we also want to create a buffer of unsigned 16-bit integers to store these samples we can get rid of our old example code as we're using DMA now lucky for us the stm32 hell library has a function that will start the DMA attached to the ADC for us we just need to give it a handle to the ADC instance our buffer and the size of the buffer I'll compile to make sure I wrote these correctly however we do need to add some callbacks as our code won't know when the buffer is full hal gives us two callbacks for the ADC that are super helpful the first is the hal a DC conversion half complete callback since the ADC is tied to the DMA controller the ADC now considers a full conversion to be complete only when the entire buffer is filled so this function is called whenever the buffer is half filled we'll toggle the LED hi here the other is the Hal ADC conversion complete callback which is called whenever the buffer is completely filled we'll set the LED pin to low when that happens this is a great way to set up a double or ping-pong buffer you can notify your main code to do something with half the buffer while the other half is being filled by the DMA let's start debugging I'll set a breakpoint in my first half callback once the program stops here I'll take a peek in the buffer you can see that it was very quickly filled by the ADC and DMA note that the second half was filled to the ADC and DMA will continue to run even though we stopped the processor with our debugger remember this is all happening even though there is nothing in our main while loop you can continue to run code while the DMA is happily filling up that buffer over and over again let's crank the knob up to 11 if we peak in the buffer again we can see that new values were filled in this time denoting the max voltage from the potentiometer I'll add a scope probe to D 13 which is connected to the LED let's remove the breakpoint and let the code continually run if we bring up the scope you can see the LED toggling very quickly half the buffer which is over two thousand samples is filled in just under half a millisecond I know these weren't necessarily practical examples but I hope they help you get started using the analog to digital converters as well as direct memory access in the stm32 line please check the description for a link to the code and please subscribe if you'd like to see more videos like this good luck and happy hacking [Music] [Music]
Info
Channel: Digi-Key
Views: 86,162
Rating: undefined out of 5
Keywords: DigiKey, Shawn Hymel, ADC, DMA, STM32, STM32CubeIDE
Id: EsZLgqhqfO0
Channel Id: undefined
Length: 15min 5sec (905 seconds)
Published: Sun Sep 29 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.