STM32 DMA and FreeRTOS Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so we're going to continue on programming the firmware for one of these little brain boards which i had made by glc pcb last time we just started out with the firmware getting familiar with stm32 cube ide and programming them in general we looked at the rgb led how we can do that with timers and pwm we looked at how to do the bmi 08 spi and polling mode and we also looked at how to do the usb port and the connection via for example virtual com port we're going to do a slightly more advanced topic this time that's dma and we can use dma to offload the main mcu and kind of acquire and transmit data in the background without using too much of the cpu cycles we're also going to be looking at how to then after dma incorporate that with free rtos so that's a real-time operating system hopefully a bit more advanced than last time when we'll explore the peripherals and then in future videos we can use that to for example implement kalman filters or just look at some digital filters in general so without further ado let's get started so i just want to quickly show you how you can order these boards for yourself via glc pcb so maybe if you want to play along at home and just program it with me you can do that so first thing you have to do is navigate to github.com pms67 and find this repository little brain sensor board i've put all the data for these videos in there so all the firmware all the libraries from keycad but also these urban assembly folders so you can either just download the zip file or you can clone this repository using just git once you have that let's move it to jlc pcb they also have this one a cu 2020 online exhibition which starts on november 10th and you can win different prizes and get coupons and stuff like that so just sign up for that if you want from november 10th four layer boards from certain dimensions have gone down to two dollars for full air psb so that's pretty pretty amazing okay but let's get on with ordering these little brain boards let's click on quote now and then we want to upload our gerber files and i've made in the directory then this raw file here that contains all the gerbers we'll upload this and this little jlc pcb will process these files and extract things like the layer count and dimensions so here we go it's uploaded four layers in these dimensions you can choose your pcb quantity but let's just stick with five for now then we want impedance control i typically go with hot air service leveling or enig and we want to remove the order number by clicking on specify location of course we want smt assembly we want the top side assembled five pieces and i've added the tooling holes already click on add by customer click confirm now we need to upload the assembly information there's a billion materials file we can find that in the git repo under assembly builder materials and of course the footprint position files then we click on next now i've already done this for you but you can verify left and right columns just to see that the parts match now you see some of the parts are out of stock at the moment but i'm sure soon they'll be back in stock and you can order these boards okay but once you've checked that just click on next now there's parts placement and rotations and i've already done that for you so the correct rotations um okay yeah and that's all you pretty much have to do then you just save to cart and choose your shipping method and you can get these boards delivered to you and it's about 88 plus of course the parts that aren't in stock so let's say about a hundred dollars i guess maybe a bit more for these boards and that's five of them that's pretty good all right but let's get moving on to the firmware so here we are again in stm32 cube ide and i'm gonna assume you watched the previous video where we set up all the other peripherals for example the rgb led we set up the zero wire debug pins usb in the virtual com port we set the external crystal oscillator and we also set up spi-1 which is connected to the inertial measurement unit and we set that in polling mode we're going to add a few bits to this before we move on to code but first of all let me just show you the data sheet for the bmi 088 which is the inertial measurement unit this is the block diagram for it and you can kind of see it's split into two parts one is the accelerometer which has its own like abcs and spi interface and so on and one is the gyroscope which is very similar but what we're interested in are actually these interrupt pins so in one and then two and in three and e and four now every time new data is available for the accelerometer or for the gyroscope we can actually set up these interrupt pins to go high or low and this is really useful because we can use these interrupts connected to our stm32 microcontroller to trigger dma reads and writes that's what we're going to be doing so going back to stm32 cube ide i've actually set up the interrupt pins or connected them to the mcu on pc2 and pc3 so if we click on pc2 you can see down here we have the option of gpio underscore xdi too and that's an external interrupt so we're actually using this essentially the trigger interrupt and we read that from the bmi oh date remember right click we can enter user label and let's just call that int ack so this is the interrupt for the accelerometer and pc3 same thing again left click and gprox i3 external interrupt that is the interrupt for the gyroscope okay so we've added these two interrupt pins and that will be using those interrupts then to trigger the dma transfers now what we need to do to enable the amaze streams spi-1 is go down here into connectivity click on spi-1 and you can see there's several different tabs here and one of them is the dma settings tab so click on that and now we need to add the transmit and receive dma streams so it's really simple click on add then we want the type for example let's start with receive now it automatically chooses which is the correct dma number and which is the correct dma stream so you don't have to worry anything about that the direction is peripheral to memory and we want the priority it doesn't really matter in this case but let's just set it to high okay date to width that's important we want to be bytes so we're going to be using byte transfers so that's fine and another important thing is the mode so we either have normal or circular mode normal is you have to every time you want to read or you want to write you have to start the dma transfer or request yourself circular means it's kind of just repeats indefinitely so once it's done transferring once it's done receiving it'll just repeat by itself in this case because we are triggering via an external interrupt we want normal so the dma reads and writes are going to be triggered by these interrupts okay so we do the same thing for the transmit string so sp1 tx it chooses all the correct strings for us that's direction memory to peripheral and let's just change that to high as well the priority the data width is still byte and the mode is still normal awesome so that all looks alright now up here in the interrupt controller tab we also need to enable the interrupts for the accelerometer and gyroscope pins and all you have to do is click on these two checkboxes here to enable line two and align three that's pretty much it regarding the interrupts all the dma interrupts already set automatically for you now we click on save and it will generate code so click on yes and it'll generate all the code for us including all the low-level drivers for the dma and it'll root the stuff together and you'll see here some of this stuff really saw in the previous video right we had the usb virtual com port include the the polling driver for the bmi 088 which is the inertia measurement unit you see here it's added these two uh dma handles uh both spy 1 rx and tx and that's what we always set in this file over here and all our old stuff is still in here so all the led rgb code is still there and this is all from last time if you need a refresher feel free to go back to my initial programming tutorial video and everything is explained in detail there but yeah basically here we have just turned the led off initialize the od and initialize the bmi 08 and now we're going to add the code to actually not do polling with spi but actually do dma so let's get started with that okay so first of all i've moved all of the drive code we had from last time into their own little header and dot c file so bmi 088.h and dot c and this is just so everything's nicely contained effectively all i've done is taken the data from the data sheet for example all the register addresses and defines and put them into a neat little file this includes the initialization function this includes functions just to read a single register or write to us to a register as well as for example the polling functions we looked at last time and today we're actually going to look at how to fill in the blanks essentially for these dma functions so the way this is going to work is that we actually have two functions relating to the read process and two dma functions so we have read accelerometer with the dma and we also have a function that handles everything once that read has been completed the way dma works is effectively we start the read we let it do it in the background and at some point we'll get an interrupt from the dma dma handler telling us okay this transaction has finished so first we have to start the dma transaction at some point we'll get an interrupt and once we've got that interrupt once the read has been completed we'll end essentially the transaction here all right so let's start off with this read accelerometer dma function now this function returns an unsigned int so either one if it's excluded or zero if the transaction didn't succeed and it takes this argument here which is a pointer to a struct a custom structure made essentially contains all the imu data so if i look at that in the header file it contains all the spi handles so spi-1 in our case it contains the locations of the chip select pins for the accelerometer and gyroscope some conversion constants and the actual measured accelerometer and gyroscope values so that's what we're passing then to this function now remember we're using full duplex spi which means we have to transmit and receive essentially the same time so we need a transmit and receive buffer now remember from the datasheet from the external datasheet to we want to read these values so from ox12 to ox17 that's six bytes in total and it's structured as a most significant bit and at least significant bit and that forms the xyz components of the accelerometer data and the way we do that if we think back to last video is we need to send the register address and we need to flip the most significant bit to a one so let's create our transmit buffer and it's going to be eight bytes eight bytes because one the first byte is the register address ordered with ox 80 to flip the most leaving bit then it's one byte of dummy data and then six extra bytes because that's what we on the read okay so then we change the first byte to the accelerometer uh data register and we want to all that with ox 80 because we want to flip the most significant bit to a one so ox 80 in binary is a one followed by seven zeros now the rest of the tx buffer doesn't matter what that contains as soon as the accelerometer reads this and says ah it's only a read it doesn't care about uh what date it receives we just have to send data because we're doing full duplex and then to start the spi transaction we need to pull the chip select pin low now remember in the struct over here i declared it in the imu struct so we just need to access essentially this pin bank and this pin and then the hell function is actually gpio pin reset that pulls this gpi low and with that we started the spi transaction pretty much and now we actually want to start the dma stream to transmit and receive the data kind of in the background and the hell functions how spi transmit receive so this is for duplex uh underscore dma okay first we need to pass is the sva handle then we need to pass the transmit buffer and now we actually need to put in a receive buffer now i could just create a function a variable in here like rx buff with eight bytes but then of course this is running in the background so this transmit received dma will finish sometime in the background so we can't actually put it in this function we need to make it more global so i'm going to put that in destruct essentially a un-a-t buffer so on-site in buffer and let's just call that x accelerometer rx surf right and that's going to be 8 bytes all right and now we can access that and pass that to this function so it's gonna be imu rx buff and we want to transmit and receive eight bytes all right and that's pretty much all there is to the hell um dma function so we pass the handle we say we wanna transfer these bytes and we want to put the result in these bytes and this byte buffer and this is the number of bytes we'll know transmit now i said before that we are actually returning either one or zero if this succeeded or not the way we do that is so if we check if this function returns l okay then we say okay everything's good let's just return a one otherwise the transaction didn't succeed and we should pull the chip select pin low again before continuing right because it did it didn't work out so well we pull it high rather and then return a zero to indicate yes uh well this doesn't didn't succeed but that's pretty much all there is so it's just a quirk of the chip itself that we need to um send a dummy byte and that we need to or the register address with a one more significant one to indicate a read then we pull the the chip select line low then we try and start the transaction if everything's okay return to one if everything isn't okay we pull the chip select line high again and return zero and that's pretty much all there is to it now we're going to worry about when we actually trigger this read accelerated dma function in just a bit but let's just implement this function down here which is actually when okay we've received an interrupt from the dma stream which i'll look at in just a second what do we do afterwards the first thing we have to do is actually pull the gpio line or the chip select line high again because we're done with the transaction right assuming everything worked out once we're done with that we actually need to convert all these raw values according to the data sheet so now we will have received six actual data bytes so we need to put them together and the way we do that right we want to form 16-bit integers so we receive the most significant bit we shift it left by eight places and we all that with the least significant bit and then just cast it to a sine 16-bit integer and we do that for the remaining x y and z components as well and then finally there is also a conversion from these raw values to actual units so in the accelerometer case i would like this to be in meters per second squared and the gyroscope i might want it to be in radians per second so there's going to be some sort of conversion factor and i'm actually storing the conversion factor in the struct as well and i'm calculating this conversion factor in the initialization function which i called right at the beginning and this is all stuff you can get from the data sheet but effectively all i need to do now is convert to meters per second squared and i'm storing that in the struct as well so i'm doing the acceleration accelerator conversion constant times our raw value so really simple okay so let's see actually how we trigger these functions and when they need to be triggered so now that we've added the dma and accelerometer part to our bmio date driver let's go back to main.c and see how we can actually start these dma transactions and the way we're going to do that is actually via the gpio interrupt pin we set at the beginning of this video remember pc2 was accelerometer interrupt which is connected to the inertial measurement unit and this will always go high when there's new data ready from the accelerometer so essentially you want to see is there an interrupt on pc2 if there is let's start the dma transfer and read out some data from the accelerometer so back in main.c you see i included our bmi 088.h header file which contains our driver and i've also created a struct which will then be passing to these various functions and we want to now write the callback function for the gpio interrupt and luckily hal provides that for us so it's actually called how gpio external interrupt underscore callback and as an argument it actually takes which gpio pin caused the interrupt or which on which you power open there wasn't interrupt okay so because we named our gpio pins quite nicely so into underscore x we can actually type that in here so you want to check if the gpio pin that registered the interrupt is equal to the accelerometer pin interrupt we want to start our dma read so now we need to copy this read accelerometer data function over to our interrupt handler and then of course you want to ask our struct by reference and that's pretty much all we need to do so every time there's an interrupt we check which pin it was on and then we call the function we just wrote to start the dma transfer so now that we've started the transfer effectively all we have to do is wait and then we will get an interrupt from the dma saying your data has been read out and then we need to process that data so now we need to implement the second callback the first callback was just the interrupt from the gpio pin telling us okay let's start and read the data the second interrupt will be a dma essentially callback dma spi callback which will tell us that the transfer has been completed and in hell that is simply void now spi tx ix because it's full duplex complete callback and there's an argument it's the the handle spi handle that actually is complete so the first thing i typically do is check is this even the correct spi handle that called it because i also have spi-1 i've got spi 3 on this i just want to make sure yeah this is this was actually the spi handle that is sitting with the inertial management unit so i just say okay is that equal to one then all we have to do is copy our second function which is our complete function and pop that in here and again we want to first pass us direct by reference and that's all there is to it so right we once we get an interrupt on one of the gpio pins the accelerometer interrupt we start the transaction once the transaction is complete the dma transaction essentially the market controller will call this we check if the instance is correct and then we do the cleanup work which we wrote in this function down here where we just convert all the raw values to meters per second squared in this case now that's pretty much all there is the next step would then be to write the similar functions also dma and interrupt for the gyroscope and then you're pretty much done with the dma driver or this inertial measurement unit now let's actually test this on the real world board and because we're using dma in interrupts and all of that the while loop actually becomes a lot simpler and i only have kind of accessory tasks i'm doing here one of them is transmitting the accelerometer and gyroscope data via usb and that's you can see that in the previous video on how to do that and i'm also just toggling the red led and then i'm always waiting 250 milliseconds before i did it again but all the dma and interrupt stuff is happening in the background so we're not loading the mcu only by these functions over here which is really nice so let's switch the camera and see if it's doing it so now i've got the whole system up and running i've got my st link connected via serial wire debug cable to the little brown board and i'm powering it via usb and also relaying data by usb you can see i've already flashed the firmware the red led is blinking away and i look here it's enumerated as a virtual comport com3 i've got h term open and that's showing me the accelerometer and gyroscope data from the imu which has been connected collected via dma so if i rotate the board the accelerometer values changes the gyroscope value changes and this is essentially all done via dma so the mcu is being offloaded you see i'm rotating the board here and that's giving me different changes here so now that we've looked at a brief introduction to dma on stm32 microcontrollers i just briefly also like to give you an overview of free rtos it's a real-time operating system that can run on these stm32 microcontrollers and do task scheduling for you so you don't have to write your own timer functions you can essentially just schedule several different tasks and then let them run and let the scheduler decide which priority tasks run when but at fixed times okay so the way we do that is go back to stm32 cube id on this configuration file and friatas is actually included if you go on the middleware click on friatas and then choose your interface i typically go with cmsis version one okay there's a lot of a lot of things you can set up with friatos including all these kernel settings over here what tick rates you're having what if you're using floating point units and so on so i'm only going to go over the really really basics now just so we can see how we can get a really simple free outdoor system running and let our drivers run in that system so typically i will enable the floating point unit because it's an stm32f405 so i want the floating point you've enabled to take care of that we want preemption enabled preemption is pretty much so we can have different tasks running at different sampling rates and they will have different priorities so if one low priority task takes too long uh we can let the scheduler say okay let a higher priority task that is due take over now and that's using this preemption that's pretty much there's stuff like mutexes and semaphores and stuff like that but essentially now we're just going to only look at tasks and how we can schedule them so click on the tasks tab over here and you can see there's this default task over here and every task has a name it has a priority and has a stack size so effectively how much memory it can use and what its entry function is called and how its memory is allocated and stuff like that now you can if you double click on it you can change it so let's let's create different tasks so you might need a task for the led to flash you know you want to task for the usb and so on all right okay so let's just start one let's call this led task we can change the priority to let's say it's a really low priority stack size 128 let's just leave that and let's call the entry function start led task all that is fine click ok so we've got one task a flash led you might want another task to for example do the usb transfer okay so let's call that a usb task and this priority might be slightly higher maybe normal than the led task so we draw i'd prefer if both run at the same time the led task and usb tasks i'd prefer the usb tasks to run is essentially what i'm saying with this priority a stack will probably need a larger stack because we're using a lot of buffers in the usb task so i know uh two five two five six should be fine and of course give it a name start usb task and everything else is fine and for now these two tasks are pretty much all we need because the dma is going to be running in the background anyway and we don't really need a task for that okay and that's pretty much it now you all have to do is click save and all the code will be generated before the code is generated rtos will tell you you'll you might want to switch your time source so let's click on no go back here and then go to system call sys and then change this time course to typo one now click save again and um the system shouldn't complain all right so now all the code has been generated as you can see we still have our original code so we still got the driver includes and the usb includes and so on and all the callbacks and so forth led initialization functions but something new has appeared over here in the in the main function so we have all these definitions here for mutexes semaphores timers and so on but what's interesting for us right now uh are these threads and tasks over here and then os kernel start so secondly we this is here to start the scheduler so this will actually start saying okay when do i run which task and it'll handle all that for us but before that we need to create the tasks and luckily friatos through cube id has done all that for us so we create the first task which is the led and we create the second task which is the usb task so i have i've had to done do nothing which is really good now you can really see in the comments left by the friatas pre-made code that actually will never arrive at the while loop as soon as the os kernel start is called the scheduler takes over and and then calls task at specific intervals depending on what we write in the code so now we're not going to put anything in the while loop anymore but rather go to the threads and functions that are defined for example by start led task and start usbs task both of these functions are fairly low down here let's have a look there we go all right so we've got one function for starter led task and that's going to be called by the scheduler and one start usb task also going to be called by scheduler the part before the for loop in each of these tasks is only been going to be called once when the mcu starts or when the task is first called but everything inside this for loop will be called depending on what value you set in the os delay and that's in milliseconds so if i write os delay 1000 everything that's in this for loop in this task will be called once every second or once every one thousand milliseconds okay so everything before that only get called once everything in the for loop called essentially depending on the os delay now if two tasks happen to want to run together at the same time the scheduler will say okay the one with the higher priority will run first that's really cool with this rtos we don't have to do any of this management ourselves all i have to do is write some initialization function and then whatever we want in these for loops okay so the led task essentially we just want to store for example the intensity of led so let's call it led intensity and we start that at zero then we might want to toggle the led so we might do led rgb set intensity led intensity so we only want to toggle the red part and then we change the intensity from full on to full off and that's all i'm doing here right so this is only going to be called once to initialize the variable then we set the intensity of the red led with this variable if the intensity is zero we change to 100 if not we change it to zero and then we wait essentially one second for it to flash again so really really simple so that's the led task let's move on to the usb task and remember the usb task was used for us to transmit all the accelerometer gyroscope data which we which we processed from the inertial measurement unit our usb and the virtual comport so again we want this to only be called once and we want to create a little buffer here so let's make a buffer of size 64. that should be enough to store all our data that we want to transmit and then again we just use this sprintf function and then the cdc transmit function so if i just paste that in here and now of course we want to set the os delay so essentially at what refresh or sample time do you want to transmit whatever is in this buffer and let's say we want to do that every half a second so every 500 milliseconds that's pretty much all you do so log buff is created once and then everything in the for loop is executed hopefully every 5 milliseconds and that pretty much concludes the only two tasks we made so we're flashing led at every one second and we're sending data via usb every half a second and because in our main function up here we started the we started the initialization function of the accelerometer and gyroscope all the dma stuff and all the interrupts will be hand will be handed in the background so we started the vma before that and we set up all the interrupts before that or rather hal and stm32qid did that for us so let's see if this works by uploading the code to the board so i've connected the little brain board via serial wire debug and the st link as well as powering it via usb as i did before and we're going to just going to test this by using the debugging functions so i'm going to set some break points the one i might be setting over here in the led function or an led task and one over here in the usb task then i can just click on this little bug icon up here to flash the firmware to the board and actually start the debugging cycle so you can see down here everything is being uploaded download verified successfully and we have our first breakpoint of course in the main so i'm gonna click on this little arrow on the top left click resume and then okay so the usb tasks triggered first click resume again then it details triggered all right so you can see the red led is flashing away every one second and that's the free rtos led task every once a second and of course you have the usb relaying data at double the rate so every half a second we're relaying all the accelerometer gyroscope data from the board so i'm moving the board you can see different values change as well that's free rtos and all the dma stuff doing its thing so i hope this video was helpful to show you how we can use dma to offload the mcu and take care of like data acquisition tasks and also how we can use free rtos really basically to schedule tasks and make sure tasks run at certain and fixed times with different priorities if you enjoyed the video please leave a like leave a comment if you have any questions or any suggestions for future videos otherwise thank you for watching if you haven't subscribed already please subscribe that really helps me out and i hope to see you in a future video thanks again
Info
Channel: Phil’s Lab
Views: 52,194
Rating: undefined out of 5
Keywords:
Id: OyVemnshlQQ
Channel Id: undefined
Length: 29min 9sec (1749 seconds)
Published: Sat Nov 07 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.