Hello World Blinky! Bare Metal Programming Series 1

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello and welcome to this bare metal programming Series where we're building and exploring firmware for a cortex M4 sdm32 microcontroller now this is episode one in the series and there has been an episode zero which is just introducing the projects the goals the non-goals and the hardware that we're using if you kind of want to get an overview of where we're going you can check that one out you'll find a link in the description today we're actually going to be building the first firmware code for the the boards that we're using here and in order to do that we first have to actually kind of set up the environment and install the lib lib opencm3 Library so let's uh switch over to the code view here and just take a look at the repository as it stands now so this is an basically an uninitialized repository which just contains a few files here one of them is just the little presentation that I've showed the other is just where we're going to download this lib opencm3 library from and we have an app directory here which contains uh just an empty C program which is going to constitute our firmware it contains basically a very small header file just in order to actually have two directories we have a link a script this Linker script is taken almost verbatim from the lib opencm3 project for this chip and we're going to get really deep into the Linker script at some moment talking about how kind of how it works what its purpose is and it's going to become really important when we deal with the bootloader but for now we can more or less ignore this and just know that it is a file required for the compilation process which kind of tells uh you know kind of tells the compiler like where the essential stuff of this chip is like where is Ram where is ROM because if you want to generate code and you you want to make jumps from this this line to this line you kind of want need to know like where you start so that's kind of what the Linker script does um we also have a make file here again this is taken almost verbatim from the lib opencm3 examples um I've actually condensed this down and collected up the various bits of make file that they have in that repository just to focus on this one processor and just to kind of keep everything as simple as possible so the make file you can think of it as a very simple description of all of the files that we have where are libraries and everything are located what kind of chip we're compiling for so we have to Define some special options for that we have to describe our Linker script describe the different tools that get run here and basically the rest is just you know telling telling it how to compile these files right so we're not going to go into this for now and probably the only thing we'll really touch in this is where we when we add new source files we'll come and place them in here but aside from that we don't really need to know the details of the make file we may look more in detail at the compilation process at some point and kind of the flags that get past the GCC in order to make it generate the right code for this chip but we're not going to do too much of that for now the last kind of important thing um to know is I've got this vs code directory up here this is kind of the visual studio code settings for this project and it just contains a couple of things so it contains um some C plus plus uh C and C plus plus extension properties that uh just say like you know this is what kind of compiler we're using and this is kind of how you should generate the intellisense um we also say like what is involved in or include path so so we can actually find definitions for functions and things like that and kind of like any defines that should be given to the preprocessor and be assumed and this basically means that uh you know sometimes you pass a definition of a compile time and that will either include a piece of code or it will not and you kind of want to tell your editor like no this piece of code will be included so kind of kind of know that so that's what these two defines do they just basically um tell the editor to recognize that like certain bits of code will be included so you can just use this in your project it's fine the other part is that I have this tasks file and this task file um exposes a couple of like commands just to build uh like the bootloader we don't have a bootloader yet but this will be the command that does this part will build our main application and you can see it just calls make bin and uh there are a couple more little scripts up here which just call into the j-link tools which allow us to run some j-link scripts to and in this case all it does is uh tell the j-link to power the board or to unpower the board so you don't always have to do that with j-link this is just the way I have my j-link set up and if you have an st link you won't have to have any kind of power on power down because as soon as you plug in the USB cable the board will have power finally there is a launch uh Json this is what allows us to configure the debugger in Visual Studio code and again it just calls into the j-link tools right so there are St tools for this as well so if you're using the St link you can use the st-link tools in order to generate uh um you know set up your debugger but in this case we have one configuration that allows us to build the firmware flash it to the device and then debug and we have one configuration that allows us to connect to a running system so we don't build and Flash we simply connect to a system that already is running and has firmware and as long as that firmware matches the code that we have we should be able to pause the program and use the debugger as normal so these are two very useful configurations to have while you're debugging all right um that's kind of it and if not all of that made sense then don't worry right I'm I'm gonna try and approach this gently and although I'm not going to be like explicitly teaching C in this in this uh series I do um understand that some people are not going to be that familiar with C so I'm going to try and also explain some of the idios idiosyncrasies in that respect and kind of what I'm doing and you know some some of the basic elements okay uh I'll just take a little sip of water here because I'll be talking for a while um the first thing we're going to want to do here is to set up kind of an infinite Loop in the main function and again if you're not really from like kind of what if you're not familiar with working with microcontrollers or the embedded world or like really really low level programming you might be more used to writing programs that don't have any infinite Loops in them right normally you don't want to put an infinite Loop in your program because that would be bad right you want your programs to end you you they start they do something interesting and then they return the result and finish that's a little bit different from microcontrollers right because you're running a program and it's the only program on the system so like it cannot end like it doesn't make even make sense for a program to end on a microcontroller because what would the CPU run afterwards the CPU is on there's no kind of turning the CPU off once it's on and that CPU just wants to run instructions all the time so the way that we do that is we just structure code as a big loop and actually under the hood a lot of low level code you know on your operating system in event loops and all kinds of things is actually just a big loop where you're checking things um again and again and again and kind of seeing how state has changed since the last time that the loop span around so that's kind of what we're going to need right and because of that we're never actually going to return from here okay we will never return we just have to put the return zero here because that's the signature of this function okay so now we have that the goal of this first initial hello world firmware is just going to be to Blink an onboard LED so what is involved in that is we're going to have to tell the processor to tell the gpio the general purpose input output peripheral to actually turn a pin on and off and this is going to be the specific pin which is connected to the LED on this board I happen to know where that is so I'm just going to explicitly write it out but you can find that out by looking at the schematics of your development board or if you happen to have put your own LED in which you absolutely can of course you just have to make sure that you configure the pin which is you know connected to this led so we're going to have to actually configure the general purpose input output peripheral to actually set up our pin so it's going to be an output and we can write to it and we're going to have to do a little bit of configuration as well to the overall system clock to get that up and running so I think I'm going to start out with the system clock because it's the most kind of lowest level most important thing to do when you bring the system up is to get the clock kind of configuration set up for that we need the RCC which is the the kind of clock configuration I don't know if you would call it a peripheral but it's kind of the clock configuration of the the scm32 so we're going to include our first thing from Liber and actually this is not going to work because we don't even have lib opencm3 installed yet so let's before we get into that let's actually install the get sub module for this um now if you claim this repository the sub module will already be kind of registered so you won't need to add it but you will need to run git sub module in it and get sub-module update just to pull the code in when you have that we can go inside the lib opencm3 directory itself which is now going to be full of uh the you know the core Library files and we're just going to run make and make is just going to build all of the the the library files and objects so that we have those available when we actually compile right we don't have to compile the lib opencm3 Library every time we uh build our own code we just do that the one time now so that's what's happening down here it's building a way it takes about a minute or so um and once we have that we can actually go and include the RCC a header file so I already know that that's going to end up being a lib opencm3 stm32 RCC because I happen to have this installed elsewhere and I know kind of how that is going to lay out so we are going to write a function which is going to be our RCC setup so it's going to be a void function because it doesn't return anything and we'll just call it RCC setup doesn't take any arguments so a right void there too and we will make the whole function static which just means that it belongs to this file right it's not a global function we're not exposing this function we mark it as static to say like this is available only within this translation unit and a translation unit you can think of it as just being sort of the C file plus all the header files that it includes um okay so what we actually want to do here is we want to kind of set the CPU clock and frequency and the the hardware that actually kind of sets up that clock frequency so to do that we call this function RCC and we have this function available because we included it from this header so it's the RCC clock setup PLL PLL stands for phased locked Loop and it's actually uh well a phased lock loop is a dedicated piece of Hardware that's built around a feedback loop and it allows you to take one clock frequency signal in and produce a different clock frequency signal out or maybe even a phase shifted signal as well or maybe even multiple clock frequencies often multiple chord frequencies um I'm not going to get into the details I don't think I could even explain them if I wanted to but you can always check out the the signal path Channel and see he's explained a lot of different plls over there so if you want to get a good idea about how they work you can check that out here it's a little bit out of scope for now so to this function which is going to set up clock up we need to pass a const struct RCC clock scale pointer to this thing and if you're not like already a c programmer I think this is just about one of the like most off-putting types that you can imagine having to take in a function it's like it's kind of just like the first thing you have to do but it's not as complex as it seems and you can break it down so um this struct RCC clock scale that is just a a type of an object it's an object type right so there's an object out there called RCC clock scale and it happens to have a certain set of properties and members and pieces of data and that's just like a fixed thing so that's what the struct um the struct RCC clock scale is is a fit it's just an object type const means we're passing it in and we don't expect this function to change it so it's just a marker to indicate that this function will not modify the thing that we pass in and we're actually passing in a pointer so it's it's an assurance that we will continue sort of the ownership of this object we we don't expect this function to kind of do anything to this object and we're still in control of it so that's kind of what all of that means now thankfully um there is actually a kind of preset uh there are a few preset kind of clock configurations for this chip out there included within lib opencm3 so we don't have to configure this from scratch although we could and we will look at what it looks like so if we just um start typing in and this is kind of how I work with this library is I use the intellisense heavily I type in the the name of the thing I'm working with so all of the this library's functions uh kind of like constants all of the enums all of those kind of things they all just begin with the thing that they refer to so RCC underscore so if you just do that you're going to get like access to all of the things which are relevant for the RCC there happen to be quite a lot of them um but I happen to know that we need the RCC HSI configs so they're a set of configs and you can see that this actually matches exactly almost the signature that we looked at the construct RCC scale configs this happens to be an array of four of them and we need a specific one so we need the RCC um I don't remember exactly what goes in this part so it's the clock 3.3 84 megahertz configuration and you can see that there's a red squiggly line here because this isn't actually quite what the function needs the function needs a pointer to one of these things and we've just given it one of these things right a const struct so we need to uh just basically give the address of this thing so that the function can deal with that and the reason is that you do something like this is because if you were to pass the whole object you're actually passing a copy of the whole object right you're you're sort of pushing it somewhere else and kind of copying it around and we don't need to do that we can just say hey use this one over here that's why it's pointer okay so what is this thing actually that's actually uh just like follow this through this link through and this opens up a file rcc.c and you can see that um this is the the array definition and this is the high speed 58 Where is the us HSI configs this is actually opening up a different um it's not opening up the one I wanted to open up because the lib opencm3 Library actually supports many different uh arm cortex chips right it supports stm32 chips and within the SDM 32 chips it supports on cortex zero on cortex three four and so on so when you go into the C files you often have to just make sure like you're looking at the right um you're targeting the right one for you so in this case this is the configuration that we are passing you can see it's just an object that has a bunch of parameters these parameters get passed to the PLL itself um and they just kind of configure everything so um we could have written this from scratch uh it kind of states the clock frequencies that various things will run at the buses the clock speeds of the bus controllers um and the Flash and kind of our voltage scaling and all of that kind of stuff is very low level configuration stuff um and we're just going to take the default here so that's that's what that's all about we don't need to go too much in detail but now we know that our clock is set up and that our clock frequency is 84 megahertz so at this point we're ready to actually set up our gpio open so I'm just going to write another function and I'm going to call it gpio setup it's again going to take no arguments and again we should make it static same time I'll also include the gpio header which will include all the GPO related functions so if we now do GPI on the score you'll see that we have a bunch of GPO related things um so we want to do one thing here which is essentially we want to configure this one pin that we're trying to talk to right the pin that the LED is connected to so we're gonna for that we're going to call GPO mode setup and to this we have to pass a couple of pieces of configuration we need to pass the port we need to pass the mode the pull up pull down and the gpos and there is some description of kind of what it means here but actually the I'll explain each of these elements because it's not necessarily obvious if you don't know how this kind of works already so the gpos split up into various ports and a port is just a collection of pins together right so you may have uh I think the stm32s have 16 pins to a port so if you have something like um a pin that's called pa5 for example that refers to Port A so this is the the a collection of 16 pins maybe you have a b and a c and a D if you have port a and then within that Port it's the fifth pin so pa5 refers to Port a pin 5. so in this case we actually do want to um use the a port right the the GPI the GPO that's connected to the LED is import a so we're going to pass this here now the next thing is the mode um and again I'm just going to use the intellisense to kind of work out what I should put here so if I just put gpio underscore mode well here you can see that there are a bunch of constants related to mode and one of them is output one of them is mask one is input analog and this AF here refers to Alternative function so we just want to have an output so this is the mode that I'm going to pass to this thing uh next pull up pull down um pull up and pull down just refer to the fact that in the gpio peripheral inside the chip each pin can have a pull-up or a pull down resistor and this is a concept that actually it confused me a lot when I first learned about this in like several years ago about the idea of like pull up and pull down resistors kind of thought there was more to it than there actually is but it's essentially you can think of it as kind of providing a default value for what this thing should be like what the value of this thing should be to the outside world if we don't directly drive a high or a low to this pin so if the pin is in a so-called floating State like it will still have a recognizable value and the way that that's done electronically is that a resistor is connected between the pin and either ground or the the voltage level that represents a high signal and that resistance is kind of um that the way that the the resistor is set up is that you can easily override it so to speak by driving a higher signal through it so this is kind of um this is kind of how that works I just wanted to cover that because I remember that being a thing I was confused about myself again we'll just use the same strategy I'll begin to kind of write pull up gpio pull up and we'll see that again there are a bunch of uh options here so we can have pull up none pull down or pull up so we're just going to have none right because we're gonna drive the LED on or off right we we're not going to leave it floating and finally we have the pins themselves GPO notice that this is gpios you can actually specify multiple pins at once here and this is quite um it's quite clever how this works um so the pin that the LED is connected to is gpo5 on Port A so it's pa5 so if we search GPO 5 and I'm not typing here gpio five you'll see that there's just a definition for that and if we look at that it's a one shifted up into the fifth position right so we didn't put the number five here we take a one and we shift it up in binary five places so that actually gives us the number 32 yep I think it gives us the number 32 and if we wanted to include another pin in this we would actually just use a bit wise or to bring in a different pin so maybe gpo06 and GPO 6 notice is just one shifted up by six the sixth position so you can imagine that you have a 16-bit number and for any of the the bits that are in here there they all represent one pin so if we set this one on and this one on well those are the two pins that we're talking about so that's kind of how that works and that's used a lot in uh in this kind of embedded uh embedded world and we'll see this Fair bit it's the idea of actually kind of configuring a port is a is a really smart one right if you want to control a bunch of pins related pins at once you can actually set or read all of those values in one kind of atomic operation you don't have to um you know do that over a series of many instructions you can literally set or read the values of those pins in one one go one instruction so that's pretty cool um so we've configured the gpio here and we can just jump down here to the main Loop and before we actually go into the loop we can run our kind of setup functions so let's just run the RCC setup first and then we can run the gpio setup just afterwards now you could argue that we probably shouldn't share the same prefix with the um with the library itself and I think that's a that's a pretty there's a pretty good reason for not doing that to not get too confused I'll just leave it like this for now because it's it's kind of clear um now inside this while loop um we want to turn the LED on and then sometime later we want to turn it off and then we want to turn it back on and we kind of want to do that a fixed frequency like I think let's choose for simplicity's sake let's choose that we want this light to be on for one second and we want it to be off for one second well the way that which can do that is just by using the gpio toggle function which is actually a really handy little function that just means you don't have to keep track of the state right if you only want to flip something from one position to another and you don't care which position you came from you can use toggle that's quite a common thing that you might not need to care about the the orientation of this thing so to speak so again this is just going to be gpioa and gpo5 that we passed to this we passed the port and the pins again pins we could do this to multiple um in this case I think it's kind of useful to break out the definition of an LED port and the LED pin and this just allows us to have you know a semantic name for this thing instead of using GPO A5 because if we if we forget somewhere down the line that that is the PIN assignment or we want to change the pin assignment we probably don't want to have to go everywhere that we specified these and change them all at once so let's actually replace these in line here and then we can use the LED port and the LED pin so this code there there is currently two problems with this code the first problem is that um and this is a confusing one that I think a lot of people have when they first come to a program an arm cortex chip from the ground up is that by default um everything that can be used every peripheral on the chip that can be used is off by default so all of the gpio ports all of the timers all of the adcs everything you can imagine is off by default and that's like a power saving thing right inside this chip you can imagine you want to deploy this with on on a bet something that's on a battery or has a very unreliable power supply or is like literally is in like a car and is connected to the car's battery you don't want it to drain energy for no good reason so everything is off by default and you need to switch on the things that you're using so in this case what we want to do here is just add one more line to the GPO setup which is the art calling the RCC and the way that things are turned off is obviously there's not like a power switch everything has to receive a clock signal right this is why we have you know a clock frequency for the Central Chip and you get various clock frequencies that go out to different parts of the Chip And every peripheral receives its own clock the clock drives you know digital electronic signal a digital electron kind of constructions forward and so we have to turn on the clock for a particular peripheral so we're going to call the peripheral clock enable function and we are going to enable a certain peripheral so the peripheral we are going to enable is the this is like with all capitals RCC gpio and every pull of the gpio is configured individually as well so we're going to turn on gpio A's clock so now uh the gpos will actually be on so when we run this toggle function something will actually happen but of course we're not going to see an LED blinking for one second here right because this thing is just going to run as quickly as possible in a while loop forever so what we'll actually see is an LED that appears to be on but is kind of half as bright as it would normally be and that is because it's it's kind of in a fixed uh time frame is on you know for half the time and then it goes off and then it goes back on and it's off and it's on to our eyes that will average out to a 50 brightness level so that's what we would see it's not very interesting we want to slow that right down all the ways that it's on for a second there's off for a second so for that we actually need to build um some kind of function and we'll have something like delay Cycles And Delay Cycles we haven't written it yet we have to write this function ourselves this function is just going to take in a number of clock Cycles we're calling it clock Cycles but it's not really clock Cycles um we're going to take in a number of kind of Cycles to delay for and we're just going to do nothing for that time so it's really um it's very inefficient in terms of the chip right the chip cannot do anything else while we're just counting down waiting for this time to be over um but for now this will get us somewhere and we will write a much better timing mechanism in the future so let's actually write this delay cycles and let's just you know imagine we're going to put a number in there somewhere so um it's not going to return anything or make it static for now And Delay cycles and we are going to take in um a number of a number of Cycles to delay for so we'll make it a un32 a 32-bit unsigned integer which can carry numbers up until a very large number I don't remember the exact number right now I think 4 billion um something like that and then we can delay for a certain number of cycles and we want that to be a relatively large number because of course um you know we're running at 84 megahertz and that is 84 million cycles per second and so if you want to wait one second and this this does equate to you know clock Cycles which it doesn't but let's say it did you'd have to wait 84 million clock Cycles so you want to be able to receive a large number here so these are going to be the number of cycles that we wait for and we're just going to have a for Loop inside um where we start at zero and while it's less than number of Cycles we are going to increment you might imagine that we could just leave it like this and this would be enough but the compiler even in kind of a debug configuration is kind of smart enough to see that this is just nothing is happening here and it can just not do this Loop right this it can easily see that literally nothing happens but counting this number up and up and up and so it will eliminate this code and we don't want it to do that so let's just put in a piece of inline assembly and run a no-op instruction so this is going to be enough to tell the compiler like you know there's actually something happening in here um a decent compiler at a certain level of uh optimization may also optimize this out and you might have to put a little bit more information to actually make it keep it but in this case it would definitely keep this and this should work okay so how many cycles do we delay for well like I mentioned earlier right we could delay for 84 million Cycles um and that would uh that would be if we were running one clock cycle per iteration of this Loop that would be one second um but it's actually not just one instruction right although we are running a no-op instruction and a no op instruction does take one cycle to execute we're also incrementing a number here every cycle and we're also comparing a number to another number every cycle so because of that um there are going to be at least three or four instructions or at least three or four Cycles um going on for this Loop so what we might do a good place to start to assume would be that if we divide this by four this will be roughly the number of clock cycles per second um and we can break this out as well right I'm not going to do it I'm going to leave it there but we could break this out into kind of a definition or a constant up above and and call it like one set one second and then we can divide that further down if we wanted to use different um a different number of seconds but for now this this should be enough so what I'm going to do is I'm going to come out of the lib opencm3 directory and I'm going to go into the app directory and I'm going to run make and you can see that there were no errors so that's actually always a good sign and we've got some artifacts that got built here so we've got an output file an object file rather which is basically you know just contains all the information of all of our code we have an elf file which is kind of the final like most rich representation of all the information that we have we can use that file to debug this application we can use that file to extract the actual code that needs to be loaded onto this chip and that's what this bin file is we also have a map file and the map file is a really useful file that tells you where everything in your program ended up and how much space it ends up taking and where it comes from so it can be a really good way of seeing kind of what gets included like what what ends up going into your uh into your program where does it land uh we'll look at this if we need to do some debugging or if we need to kind of figure out like if things are really being included or not this is kind of a great place to to figure that stuff out but for now I think actually um this probably worked it compiled so let's actually load this onto the board and see if we can get an LED flashing so the first thing I'm going to do is I'm going to power the board on because that's something I have to do in order for the the j-link to work correctly and I'll just lift this up to the camera now you should be able to see hopefully in that corner there that there's a red light on and that just means the board's on and ready and communication must be established between the two because we were able to turn the board on or at least we can talk to the power right which is a good sign okay so the easiest way to load codon is just to use the debugger so I'm I'm going to just press play which will uh do all the things necessary to load the code on it's already been loaded and now we're in this like basically we it's as if we have a breakpoint here and it just put it put us in the first place inside this function so I'm just going to press play I'm going to let it uh let it continue and we'll see if there is a blinking light and I can see that there is in fact a blinking light at around one Hertz let me just switch the camera to the larger View and I believe you should be able to see the LED just here blinking roughly once per second or blinking for a second and then being off for a second Okay so that's actually a pretty good uh result for that we've got kind of the the the library working we've got the debugging working we've got the code loading all of that kind of stuff and it appears that our first program was able to do something it's not very interesting but this is the hello world and we have done it in you know just over 30 lines of code of course there are many more lines of code backing this and we are going to explore kind of what each of these different functions is actually doing like what happens when you run gpio toggle like what are these various values um we're going to explore those in the next episode where we will extend this simple example a little bit and kind of set up at least a couple more peripherals and kind of bump bump the level of complexity a little bit and we'll also in the next few episodes we're going to sort of dive into some of the documentation of where you can find information about what this chip can do like kind of it's Central documentation location that isn't the library but is just for this chip and as we continue down the series we're going to get more and more complex and build up a bigger and bigger Foundation until we reach uh you know the level of having a whole bootloader application firmware updates and firmware signing so I hope you've enjoyed that if you have any comments please leave them in the description in the comments rather and I'll see you next time
Info
Channel: Low Byte Productions
Views: 25,767
Rating: undefined out of 5
Keywords: c programming, bare metal, stm32, cortex-m, cortex-m4, arm, bootloader, signed firmware, firmware, usb, vscode, open source, libopencm3
Id: 06ICtJjPKlA
Channel Id: undefined
Length: 38min 33sec (2313 seconds)
Published: Sun Mar 12 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.