Mastering Stm32fx Bootloaders - Furuta Kimiko
Mastering Stm32fx Bootloaders - Furuta Kimiko
BOOTLOADERS
Building Robust Bootloaders for Custom Development for
STM32Fx Microcontrollers
By
Furuta Kimiko
TABLE OF CONTENTS
WHAT IS BOOT LOADER AND WHY IT IS NEEDED
MCU EMBEDDED MEMORY ORGANIZATION
UNDERSTANDING RESET SEQUENCE AND MEMORY ALIASING OF THE MCU
BOOT CONFIGURATIONS OF THE STM32 MCU
ABOUT MCU DEVELOPMENT BOARD
STM32F4 DISCOVERY AND NUCLEO BOARD DETAILS
STLINK DRIVER INSTALLATION
ST LINK FIRMWARE UPGRADE
KEILMDK5 INSTALLATION
KEILMDK5 INSTALLATION CONTD
LOCATING PACK INSTALLATION FILES
CREATING A KEIL PROJECT
CREATING A LED TOGGLING APP USING BOARD BSP APIS
CREATING A LED TOGGLING APP USING BOARD BSP APISNUCLEO
DOWNLOADING AND INSTALLING OPENSTM32 SYSTEM WORK BENCH
INSTALLING OPENSTM32 SYSTEM WORK BENCH
STM32CUBEMX INSTALLATION
ACTIVATING STS BOOTLOADER PART1
ACTIVATING STS BOOTLOADER PART2
ACTIVATING STS BOOTLOADER PART3
ACTIVATING STS BOOTLOADER PART4
BOOTLOADER TRANSPORT
BOOTLOADER CODE PLACEMENT
BOOTLOADER SUPPORTED COMMANDS
HOST BOOTLOADER COMMUNICATION
BOOTLOADER PROJECT CREATION
BOOTLOADER PROJECT EXPLORATION PART1
COMMAND UART TESTING
DEBUG UART TESTING
BOOTLOADER JUMPING TO USER APPLICATION PART1
BOOTLOADER JUMPING TO USER APPLICATION PART2
BOOTLOADER JUMPING TO USER APPLICATION PART3
FLASH CODE PLACEMENT USING OPENSTM32 SYSTEM WORKBENCH
VECTOR TABLE OFFSET REGISTERVTOR USE CASE
BOOTLOADER JUMPING TO USER APPLICATION PART4
BOOTLOADER COMMAND FORMAT
BOOTLOADER READ COMMANDS IMPLEMENTATION
COMMAND HANDLE FUNCTIONS IMPLEMENTATION
BOOTLOADER COMMAND HANDLING FLOWCHART
BLGETVER HANDLE FUNCTION IMPLEMENTATION
BOOTLOADER ACKNACK IMPLEMENTATION
BOOTLOADER VERIFY CRC
SENDING BLGETVERCMD REPLY
BLGETVERCMD TESTING PART1
PYTHON INSTALLATION ON HOST
PYTHON PYSERIAL MODULE INSTALLATION
BLGETVERCMD TESTING PART2
BLGETVERCMD DEBUGGING
BLGETHELPCMD IMPLEMENTATION AND TESTING
BLGETCIDCMD IMPLEMENTATION AND TESTING
UNDERSTANDING FLASH READ PROTECTION LEVELS
BLGETRDPLEVEL COMMAND TESTING
BLGOTOADDR COMMAND IMPLEMENTATION
BLGOTOADDR COMMAND TESTING
BLFLASHERASE COMMAND IMPLEMENTATION
BLFLASHERASE COMMAND IMPLEMENTATION CONTD
TESTING FLASH SECTOR ERASE
TESTING FLASH MASS ERASE
BLMEMWRITE COMMADN IMPLEMENTATION
BLMEMWRITE COMMAND TESTING
OPTIONS BYTES PROGRAMMING
IMPLEMENTING FLASH SECTOR PROTECTION COMMANDS
SUMMARY OF THE COMMANDS
HOST APPLICATION SOURCE FILES AND DETAILS
PROCEDURE TO ADD YOUR OWN COMMAND
WHAT IS BOOT LOADER
AND WHY IT IS NEEDED
So, in this project let's understand, what exactly is a bootloader and why it is
needed? So, Bootloader is nothing but a small piece of code stored in the
MCU flash or read only memory to act as an application loader as well as
mechanism to update the applications whenever required. So, now let's
understand this with couple of examples. Consider this very famous board in
the market Arduino UNO which is based on at mega 328p micro-controller.
So, Does this MCU come with on chip bootloader? Yes it is! So, this micro-
controller indeed comes with on chip bootloader which is programmed in the
flash memory of the micro-controller. And does it run whenever MCU
undergoes reset? So, yes. Upon reset, the Arduino bootloader runs first. And
what's the main use of that bootloader? So, this bootloader is mainly used in
this board OK, to download you are Arduino sketches to the board. So, what
you do in the Arduino programmer? You connect your Arduino board to the
PC, you open the Arduino programmer. So, you write some code and then
you hit upload button in the Arduino programmer. Isn't it? So, the when you
do that the board momentarily goes for reset and the bootloader immediately
runs, and it waits for the command from the programmer. And then it receives
the code in the format of binary and it programs the flash of the micro-
controller.
And then it again goes for reset and then it gives control to the application
you just programmed. So, here this is actually a clear example of in
application programming. The way you program Arduino board using your
programmer is an example of in application programming here you are taking
the help of bootloader in order to program your application into the Flash
memory of the micro-controller. Now, let's look at another example. So,
consider this board. This is STM32F446RE Nucleo 64 board for produced
by ST. So, Does this MCU come with on chip bootloader? Yes. This MCU
indeed comes with on chip bootloader not only this micro-controller or ST
micro-controllers, they come with on chip bootloader. Does that bootloader
run whenever MCU undergoes reset? No.
By default in this board the micro-controllers bootloader won't run whenever
it undergoes reset. You should activate it by changing the status of some boot
pins, that will see later. So, but it won't run by default. So, what's the main
use of the boot loader? Again to download and upload binary. Now look at
this board. This board has something called In-circuit Debugger and
programmer. So, whenever we program this board we don't take the help of
boot loader. We take the help for this In-circuit debugging and programming
circuitry, what we call as ICD In-circuit debugger. So, its name is ST link
and we take its help in order to program our micro-controller, also we use
this circuitry in order to debug the code which is running on the micro-
controller. So, this circuitry is absent in this board. So, that's why you have
to do in application programming that is take the help of bootloader in order
to flash your binary. But in this case, we don't take the help of bootloader.
Instead of that we directly do it by In-circuit debugger called ST link. So,
that's why it is called as ISP that is in-system programming. Now, consider
this Tiva based micro-controller launchpad board from Texas Instruments
again So, does this MCU come with on chip bootloader? Yes. So, it is. It has
some bootloader called TivaWare bootloader inside the read only memory of
the micro-controller. You should activated by changing the status of some
boot pins. So, in this case how do you program this board? Again this board
has got the in circuit debugger and programmer and by using that you program
the micro-controller or debug the he micro-controller directly without taking
the help of bootloader.
So, that means whenever your board has in circuit debugger and programmer.
So, in order to program your board you need not to use the bootloader which
is sitting inside the micro-controller. But, in this case since you don't have the
in circuit debugger and programmer you should take the help of the
bootloader in order to download your program into the board. All right. So,
that was a quick introduction to the bootloader and why exactly it is needed.
So, in summary if you are board or product doesn't support in circuit
debugging and programming circuitry. Then you have to take the help for the
bootloader in order to update or write a new application binary into the
board.
MCU EMBEDDED MEMORY
ORGANIZATION
So, in this project let's explore about the memory organization of a micro-
controller. So, in this case I'm considering the micro-controller from ST. So,
that is STM32F446XX micro-controller. And this is a generic discussion.
You can apply this to any micro-controller you have at your hand and not
strictly bound to STs micro-controller. Understanding a memory organization
of a micro-controller is really important for our course. Because, in later
project I will be mentioning various sections of the memory and you will be
using base addresses of different memory areas. So,in that case for the
smooth understanding OK, it's always good to explore about the different
memories present in the micro-controller. Grate. So, this micro-controller
has an internal flash OK also called as embedded Flash and it size is 512
kilobytes in this micro-controller. And this is also called as you know on
chip flash. So, this is write there on your micro-controller itself. This micro-
controller has got 2 SARMs. One is 112 kilobytes and another one is 16
kilobytes. So, this is a random access memory of this micro-controller. And
some micro-controller may have 3 SRAMs or some micro-controller may
have only one SRAM. So, it is all you know depending upon what type of
micro-controller it is and which application domain it targets. For example,
F4 series are actually high performance micro-controllers from ST. So, there
price is high and they target high end applications. So, that's why they
introduce one SRAM, two SRAM, three SRAM so like that. And it has also
got a ROM memory of 30 kilobytes. And it is also mentioned as system
memory in STs documentation. Can only read this memory and you cannot
write into this memory, and this is read only and this memory is actually used
by the ST to store STs native bootloader. So, bootloader will be stored at
this memory. And it has got something called a one time programmable
memory. And its size is only 528 bytes, and you can program this only once,
and this is usually used to store the product number or serial number of a
product OK by the user. And there are something called Option bytes memory
of 16 bytes. This is not for general use case. So, these bytes will have some
flags, which control the access to the flash memory. So, that it will explore
later and it has got 4 kilobytes of backup RAM. That means you can back up
the RAM by using battery.
So, whenever you remove the power from the micro-controller so you can
still hold the contents of the RAM by backing up it with a battery. So, and
let's move forward and let's explore about the internal flash memory. So, as I
said its size is 512 kilobytes in this micro-controller and it begins at this
address. So, this is the base address of the internal flash memory on the
memory map and it's and addresses this one. So, if you calculate this it comes
around 512 kilobytes. And it is nonvolatile of course. It is used to store your
application code and read only data of the program. Also it is used to store
the vector table.
So, I have mentioned that, also it is used to store the vector table. Now, let's
move forward and internal SRAM 1 its size is this much, and it begins at this
address in the memory map of the processor, and it ends at this address, and
it is used it to store your application global data and static variables, and it is
also used for stack and heap purpose,and its contents are indeed volatile, and
you can also execute code from this memory. That means processor can be
jump to SRAM if you have stored any instruction there so it can execute that.
OK, no problem. An internal SRAM 2 is of 16 kilobytes and it starts at this
address in the memory map and it is exactly same as SRAM 1. So, only in
size is smaller and it follows immediately after the SRAM 1 in the memory
map. Right? If you add +1 to this, that is nothing but the beginning of the
SRAM 2. This is the beginning of the SRAM 2. And it is also used to store
application global data, static variables and you can even set up stack and
heap in the SRAM 2, no problem. And you can also execute code from this
memory.
Now, the next one is system memory. This is read only memory and its size is
30 kilobytes in this micro-controller, and it begins at this address in the
memory map and all the ST micro-controllers store Bootloader in this
memory. So, when the chip is stepped out, the bootloader will be
programmed in the read only memory of the micro-controller. So, this comes
by default. By default micro-controller will not execute any code from this
memory but you can configure the micro-controller to boot or execute
bootloader from this memory. So, what it means is that when you reset your
micro-controller or when you give power to your micro-controller. The
micro-controller don't care about this bootloader. It won't execute that.
Unless you make some changes in the boot configuration of the micro-
controller which we will explore later. So, by default it is just sitting inside
your micro-controller and it won't execute at all OK, unless you trigger it.
Now, let's explore about the flash organization in detail. So, as I said the
main flash memory that is user flash. OK, is of 512 kilobyte . So, this is
called as on chip flash or user flash. And that flash is divided into different
segments. or sectors. So, there are 8 such sectors. And you can see that the
first 4 sectors are of 16 kilobytes each, and the next one that is fifth one is 16
kilobytes in size, and last 3 are 128 kilobytes. And here you store your code,
in this area you store your code. So, when you hit download code option in
your IDE, IDE by default, So, downloads it into the sector 0 and it occupies
from there. So, you can even instruct the IDE to download the code into
sector 1, or sector 2, or whatever sector in this flash area.
And as I said, this area is used for the bootloader which is which comes from
ST actually. And this is OTP area where you can save some information, and
these are option bytes. Great. So, this is a flash module organization of this
micro-controller. And if I take a look into let's say TIs micro-controller. So,
the things will be almost same here we can see it has got 1MB of flash. This
series of microcontroller may come with 1MB of flash, 256 kilobytes of
SRAM, it has got inbuilt EEPROM. So, that feature is not available in STs
microcontroller. So, this has got 6 kilobytes of EEPROM and there is also
ROMs. So, TI will store it's bootloader at this location that is in the ROM
location. Great. So, memories are almost same if you go across multiple
microcontrollers from different manufacturers And take a look into
embedded memories of your microcontroller and you must see something
similar to this.
UNDERSTANDING RESET
SEQUENCE AND MEMORY
ALIASING OF THE MCU
let's explore about the reset sequences of a ARM Cortex M processor based
microcontroller and we will also explore about the memory aliasing
technique of a microcontroller. So, what happens when you reset your
microcontroller or your microcontroller development board. So, The first
thing what happens is OK it undergoes the power on reset, when you apply
power to it. And the PC of the processor is loaded with the value
0x0000_0000. Then the processor reads the value. @ memory location 0
into the MSP. SO, here MSP is the main stack pointer ARM Cortex micro-
controller. And that means, the processor first actually initializes the stack
pointer register So, That's a very important step and the processor that does
that first. So, you may be knowing this OK because, we have extensively
discussed all those things in our ARM Cortex as well as master in micro-
controller course and I'm not going to cover the internal details of ARM
Cortex processor again in this course
So, if you want to learn about the internals of ARM Cortex M Processor, then
you have to read some of the documentation related to ARM Cortex M
processor, or you may wish to take a look into these courses. So, after that,
processor reads the value of @ memory location 0x0000_0004 into PC. So,
that value is actually the address of the reset handler. Right? So, now then PC
jumps to the reset handler. So, now the reset handler is just a C or assembly
function written by you to carry out some of the initialization required for
your application. And from the reset handler you call your main() function of
the application. So, control comes to the main() function of your application
from the reset handler. So, this is what happens when the ARM Cortex M
processor based microcontroller undergoes reset. So, from this discussion it
is clear that the first address which is produced on the bus is this one, that is
0. And at this location the value of the value of the MSP has to be present,
that is that's your responsibility put a value and that value will be copied into
the MSP. Grate, so that we understood. But the thing is in STM32F446
microcontroller, so the user flash actually starts at this location. So, not only
in this microcontroller if you take any microcontroller produced by ST.
The user flash actually starts at this location. But this may not be true, if you
change the vendor of the microcontroller. For example, if you take TI then the
user flash may start at some other address So, that will see later. So, but for
the time being let's consider STM32F446xx microcontroller and the user
flash halts at this location. That means, all the code of an application, the
vector table, the MSP value, everything we stored on the user flash which
starts at this location. Right? So, that I can verify. So, let me just compile
and download this code into the microcontroller. So, we actually
downloaded the code into the Flash memory of the microcontroller. Right?
So,that is a user flash. So, now if I want to observe the contents then we can
do that in KEIL. Isn't it? So, I just opened the memory window here and you
know when I give the base address of the user flash, it is indeed showing the
contents of MSP, the vector tables, and all these are actually instructions,
which are generated for my code. Right? So, the very first address of the
flash will contain the MSP value. Correct.
So, that's why you can see that in the register window. The MSP is indeed
loaded with this value. Correct. And this is the address of the reset handler.
Suppose, if I reset this processor from the IDE, you can see that processor
comes and stops at the reset handler. Take a look into the address of the reset
handler. It is indeed matches with this address, isn't it? So, this recent
handler is written in assembly code and from there we call the main()
function OK, of our application code. Right? From there it comes to main
program of the application. So, now the mystery is now the processor
produces this address. In order to fetch the value of the MSP, but the value of
MSP is actually present at this address. Right? So, then how do you think the
processor successfully fetch the value stored at this address. OK and it puts
that value into the MSP. So, because we programmed our code in this
location not in the this location. Isn't it? So, the answer for this is memory
aliasing. Memory aliasing is a technique used by the micro-controller
manufacturer to map different addresses onto different other addresses. For
example, in this case by default the base address of the user flash is mapped
onto the base address of the memory map, that is 0x0000_0000. That means,
if you read 0th address. So, that address somehow converted as this
address. And you will end up reading the value which is stored at this
address. If you read this address than you will end up reading this address.
That's because this memory region is actually mapped onto this memory
regions, so that we call as Memory aliasing.
And by default the user flash is actually aliased to the very first address of
the memory map that is 0. Got it. So, that's the reason. Even if you type 0
here. So, you will still able to see the same contents What you just saw in
the user flash area. So, here it is so 0x0000_0000 OK, the same value you
will see. That means, when the debugger tried to read this address the
microcontroller somehow converted this address into the base address of the
flash and it returned the contents of that address which happens to be this.
And same is applied for all the other addresses.
BOOT CONFIGURATIONS
OF THE STM32 MCU
let's understand the Boot Configuration of STM32F446 micro-controller.
And I will also take an example of TIs micro-controller. So, once I finish
this. So, now in the previous project you know that when the processor
produces the address 0. OK, that is somehow mapped into user flash. So, this
is a technique done by your microcontroller manufacturer inside the
microcontroller. So, all the manufacturer need not forward that 0th address to
the base address of user flash it's manufacturer dependent. So, look at what
ST is doing. So, ST says that there are 2 pins called Boot1 and Boot0 on
your microcontroller. OK later you will explore this. If Boot0 pin is
grounded during reset, then Boot1 pin is don't care. And the Boot mode will
be from the user flash memory so, that we also call as main flash memory.
That is the main flash memory is selected as the Boot area. So, don't give
much importance to the word boot. So, boot simply means that the
microcontroller executes instruction from that memory area.
So, That's it. So, that also means that the 0th memory location aliased to the
base address of the main flash memory. Great. So, now what happens if
Boot1 pin needs 0, and Boot 0 pin is 1. OK during reset. In that case OK the
boot mode they call it as the system memory. So, that means the system
memory area will be aliased to the 0th address of the memory map. So, that
means, if the processor produces the address 0 then that addresses forwarded
to the system memory area which starts from here. Isn't it? So, that means the
system memory is selected in order to execute the instruction after the
processor undergoes reset. And during reset, If Boot1 pin is 1 and if Boot0
pin is 1, than the boot mode is embedded SRAM. You know that embedded
SRAM starts from here. Isn't it? So, processor will start executing core from
the embedded SRAM area. So, that means the embedded SRAM region is
aliased to the 0th memory location of the processor memory map. So, that
means you have these 2 pins in STs microcontrollers, Boot 1 pin and Boot 0
pin and by using those pins OK, during reset you can control from where
exactly the processor has to fetch instructions. Let's say, you place some code
in the embedded SRAM and after reset if you want your processor has to
execute instructions from embedded SRAM, then during reset OK the Boot 1
pin and the Boot 0 pin has to be 1. So, if you make that then processors 0th
address will be forwarded to the embedded SRAM region, and from there
the processor will start fetching the instructions. So, I hope you understood
this Boot Configuration of this microcontroller, and if you have any doubts
please feel free to ask. So, now what I do is I'll go to this TIs datasheet to
explore what exactly it TI does. So, here I am in the datasheet of TIs Tiva
series of microcontrollers. Now, let's discuss what exactly happens in this
microcontroller. So, here if I go to the memory map section let me search for
memory map here. OK, memory map. OK, here it is. Let's go to this table and
it says that the on chip flash starts at this location. So, this is different from
TIs. Sorry, from STs design, Isn't it? In ST, our on chip flash or we can also
call it as main flash, or user flash starts at this location So, but in TIs, it starts
at this location. So, do you think memory aliasing is required here in order to
execute your application code? No. Right? Because, the flash is already
mapped onto the 0th memory address. So, your processor also produces 0th
address in the beginning and flash is also at this address, so, no memory
aliasing is required. Isn't it? At least to execute code from the main flash or
on chip flash. isn't it? So, these kind of differences maybe exist in different
microcontrollers
So, that means when you connect your Nucleo board to the PC, it will
enumerate as the virtual port. Suppose, if I open my device manager here.
Here, we can see my Nucleo board is actually enumerated as COM3 Port.
So, that means I can talk to the board over the UART, by just connecting my
Nucleo board to the PC. But that feature is not available in the discovery
board, and for this you have to make some settings. So, that is actually
mentioned in the User Manual of the discovery board. So here, if you go to
the discovery board, user manual, if you go to the section ST-LINK/V2-A or
V21 VCP configuration that is virtual COM configuration. The ST-LINK/V2-
A that is a newer board. Supports virtual COM port on U2 pin 12 and U2
pin 13 but these pins are not connected to the USART of the STM32F407
microcontroller for mbed support. So, this is problem. In order to enumerate
the virtual COM port support. So, that's why they give 2 solutions here. One
is you use UART to USB dongle from the market That means you buy it from
the market So, those are actually very low cost devices. So, you can buy one
dongle USART to USB dongle. So, if you can just search and in the these are
very low cost dongles you can used to get the virtual COM port support, Or
you have to short these wires here. So, that is actually a TDS process and I
suggest to you not to do that, because you may damage the board. So, that
means the virtual COM port support is not directly available, but on the
Nucleo it's already available. So, that connection this connection is already
exist on the Nucleo. That means, I can communicate with this board from my
PC over UART without using any external USB to UART converted dongle.
STLINK DRIVER
INSTALLATION
So, once you have your ST board either discovery or Nucleo or any other
board from ST, which is based on ST-Link in circuit debugger and
programmer, then in order to talk to the board successfully you have to install
the ST-Link drivers. The ST-Link drivers that means the driver for this
interface. This is the ST-Link in circuit debugger and programmer, isn't it?
Now, the driver for Windows 32/ 64 bit these versions. It is available in STs
Web site so you can directly go there. So, just go to this link and so this is a
USB driver for those boards which has this ST-Link circuitry, and at the end
you will find the drivers so just download this. So, once you download this
go to that a folder. So, here I have downloaded and here you will see 2
applications. So, this is for 32 bit, and this is for 64 bit. So, what you do is
connect your board, It could be either discovery or Nucleo. And just run this
software, now let's click next, and it will install here the WinUSB driver that
is required in order to talk to this ST-Link and other drivers it will install,
here it is. So, once you do that click Finish and just see your device manager.
Now currently my Nucleo board is connected.
And here, you will see the COM port and after that in USB devices you will
find ST microelectronics ST-Link dongle. So, you will see this option. So, in
order to communicate with the board over UART, we use this interface the
same interface and your host application or host IDE uses this interface. This
is the interface used for debugging. Great. Remember that, the ST-Link driver
installation is very very important. Once you have your board we have to do
this. And for Ubuntu you just have to run these commands on your command
prompt to install these packages, and ST doesn't give any drivers specifically
for Ubuntu or Mac. So, Ubuntu guys just install these, and for Mac no driver
installation is required. Great. Now, in the next project, we will see how we
can do ST-Link firmware upgrade. So, ones you install the ST-Link drivers,
you have to upgrade the firmware which is running in the ST-Link circuitry.
That will see you in the next project.
ST LINK FIRMWARE
UPGRADE
Web page from where you can download the ST-Link firmware upgrade
software. So, this is a software given to all the platforms such as Windows,
Mac, and Ubuntu, in order to do the firmware upgrade of the ST-Link. And
here you can see, it can do the firmware upgrade for ST-Link. ST-LINK/V2
and ST-LINK/V2-1. So, here is a confusion. So, here they say that, it is ST-
LINK/V2-A and here they say it as ST-LINK/V2-1. Here is a naming
mismatch but please note that, that V2-A is nothing but V2-1. So, now you go
at the end and download the software. So, let's click on get software and you
have to accept the agreement, and you have to login in order to download the
software. If you don't have any account then you have to create one. All right.
So, here I have downloaded the archive and if your windows go and execute
this ST-Link upgrade software. So, if your using any other platforms then you
can simply run this. You can simply run this exec utable jar file Since, it is a
jar file it can run on Linux as well as on Macs. So, simply double click on
this. All right. And when you do that, So, now I'm on Windows and I have not
connected my board to the PC, and I'll just run this and here it is. It is not
showing anything, and now I connect it.
I connected my board and I click on refresh device list and now it has
detected that my board is actually old board, actually I connected my
discovery board. That is actually a old board which contains ST-LINK/V2
version. And then click on Open in update mode. And here you can see that,
the firmware version which is running on my board is this one and the latest
available is also that one only because, I updated it. You may see mismatch
here. So, in that case you have to update to the latest firmware version. So,
just click on upgrade and it will upgrade the firmware which is running on
the ST-Link micro-controller. And if you are using newer board than it may
show you V2-1 instead of V2. Great. So, now the upgrade is successful,
close it and restart your microcontroller. Great. So, that's for discovery and if
you have Nucleo board, then also the procedure is same, the software is
same, and this Nucleo board is actually based on ST-LINK/V2 debugger and
programming. So, because Nucleo came after discovery so that's why, it
comes with V2-1. And for that also now what I do is, I will just run that ST-
LINK once again, and now I connect my board, let's see what happens. So,
now I click on refresh device list and here it is, it is showing V2-1. And OK,
it is showing that firmware version unknown, So, just click on Open in
update mode and just wait until it install some drivers. So, now it is saying
that the current firmware version on the board is 28, available is 29. So, now
just click on upgrade to the newer firma ware version. Now, the upgrade is
successful. Just close this and restart your microcontroller. So, great.
KEILMDK5 INSTALLATION
So, in the previous project what we did was, we actually set up a eclipse
based a development a set up for our microcontroller programming. Right?
So, we created a small project we compiled it and we learnt lots of eclipse
related ecosystem and then we finally tested it on the hardware. So, eclipse
is great. Because it supports all the platforms like Windows, Mac,and Ubuntu
or Linux. And it is a little hard to setup so, because we removed lots of files,
so we added lots of files, but it was a great learning. Isn't it? So, if you know
that ecosystem you can extend that to any kind of microcontroller. So, now in
this section we will concentrate on how we can a set up you know Kiel
based development environment for our microcontroller programming. So,
the bad thing about Kiel is it supports only Windows platform. So, if you are
using Mac or Linux then, I would request you to skip this project or skip this
entire section on Keil. And you can jump to the next section no problem. All
right. So,now first step in setting up a keil based development environment
for your micro-controller is downloading a KEIL MDK software. So, just
type Keil mdk in Google, just follow this link MDK Version5. Just open this
and you'll be presented with this page All right. So, the MDK starts for
micro-controller development kit. So, as Keil says it is the most
comprehensive software development solution for ARM based for
microcontrollers, and it includes all the components that you need to create,
build, and debug embedded applications.
So, if you look at this product components diagram here. It is a full package
of various tools, middle ware, the Device HAL, libraries and the CMSIS
drivers as well as the CMSIS APIs. The very important thing we notice here
is the IDE. So, it gives us the Keil microVision IDE where we you know
write our code,we edit our code etc. . Right? So, and then it gives a Keil
microVision debugger by which we debug our code on the microcontroller
And it also provides ARMs native C/C++ compiler remember. So, here we
don't use the GCC compiler. So, I think you can configure kid use GCC based
compiler, but by default ARM provides its own proprietary compiler.
Remember, in the previous section we actually integrated an external tool
chain. right? That is GCC based GNU GCC ARM toolchain externally we
downloaded, and then we integrated that with our eclipse IDE. But,in this
case you need to do that because ARM gives it's native compiler. And as
expected all the CMSIS-Core APIs, DSP APIs, RTOS APIs etc. . So, as you
know we don't use CMSIS- DSP or RTOS in this course. So, but we need
CMSIS-Core APIs which are actually used to talk to your microcontroller to
be specifically to the core aspects of your processor. This MDK supports
various devices. So, devices in this case means various microcontroller
produced by different vendors like TI, NXP, ST etc. . So, all those devices
which are based on ARM based processor It gives use the startup code, it
gives you the Device HAL libraries, CMSIS drivers etc. . And it also gives
you lots of middle wares. Let's say, you want to design an application in
which you want to convert your microcontroller into USB device or USB
host etc. . So, you need a USB stack. Isn't it? So, you need not to hunt for
USB stack outside. MDK itself gives you a stack, a USB stack by which you
can convert your you know micro-controller, or a board into USB device, or
you can add USB host functionality like that. So, suppose if you want to you
know interface your micro-controller to Ethernet PI, and if you want to run an
IP stack, then it gives you IPv4 networking stack, IPv6 networking stack, you
can even run a file system on your microcontroller. So, it gives you all those
drivers in the middle ware component. So, it's a complete package of tools,
compilers, you know Device HALs, middlewares etc. . So, in this course we
will majorly using MDK microcontroller development kit for our course. So,
I'll be using Keil MDK file software and unfortunately as I said it doesn't
support other platforms than Windows. And so, that's why I have already
explained how to use eclipse. So, if you are using Linux or Mac based
systems then you can parallely use eclipse based development setup, that I
have explained previously. So, I'll be using Keil, and if you are using
Windows, then you can definitely use Keil. No problem. But, if you are using
Mac or Linux then you can continue using eclipse. All right. Great. So, now
let's download this software. So, it comes with a different editions. So, all
these are actually paid editions like Essential, Plus and Professional those
are paid additions, but MDK Lite is a code limited. But, it's a free version.
So, but the restriction is you should not exceed code size, that is your binary
size should not exceed 32 kilobytes. Sorry. So, during compilation if it
detects that if your code exceeds 32 kilobytes, then it will start throwing
errors saying it cannot compile. Because, the code size should be limited to
32 kilo bytes. So, but for most of our work I'm sure we don't exceed 32
kilobytes. So, that's why, we will download Keil MDK-Lite software. Great.
So, now let's go and download this software. Just click on download and
install That will take you to this web page here, and here you can click on
Download MDK-core, In order to download the software and here you have
to fill this form in order download the software. So, I'll just quickly fill this
form. So, I just filled this form and then click on submit. All right. So, now
what will do is, just click on this EXE file in order to download that
software.
KEILMDK5 INSTALLATION
CONTD
The application is downloaded. So, now let's install that. So, just double
click on this and just go ahead and install this. All right. Click next, Agree
next OK . So, here you can see that, it is saying it is going to install the Core
at this location and the Pack at this location. So, let's not worry about that.
So, you can issue next here, and here you can see that it is installing in the C
directory. So, that's fine. We can go ahead here, click next OK, click next. So,
it is started installing. So, please wait until it completes. All right. So, the
setup is completed. So, just click Finish here. After that, you will see that the
pack installer is launched automatically. So, what you do is just click OK
here, and just close this. Don't worry we will explore pack installer later,
just exit from here.
So, after the installation what have to do is, we have to launch the Keil
microvision IDE. For that, So, you can even go to the installation directory.
So, we installed Keil version 5, that is MDK version and 5 in C drive.
Right? So, in your C drive you should see a folder Keil_v5. So, go under this
and here you will see these files and folders. So, UV4 stands for
microvision4. So, just go under that and here you must locate a application
called UV4. So, this is your Kiel microvision4 application. So, this is a
IDE where we develop code, compile code, and finally download code into
our hardware. So, just launch this application and you'll see something like
this. That means our Keil MDK 5 software is installed successfully.
LOCATING PACK
INSTALLATION FILES
We just completed installing software packs for our microcontroller. So, now
in order to locate where exactly it is stored in your PC. So, just go to your C
drive. And so, this is a folder gets created when we installed Keil. Right?
So, go under that, and now go under ARM, and here you will see a folder
called Pack. Go under that, here you will see another folder called Keil, just
go under that, and here we can see that these are the software packs we have
installed for different microcontrollers. This for ST, this is for this a Nucleo
BSP, and this is the device firmware package for TIs Tiva series of
microcontrollers. So, whenever you install software packs for different
microcontrollers. So, it will the keil will put all those software packs, which
is downloaded into this path. All right. So, now let's go under this F4xx DFP
and. This is the latest version 2. 11. And if I go under that, here you will see
one important folder called drivers
So, just go under that and you will locate your HAL layer for this
microcontroller. And if you go under that, you will see 2 important folders.
One is source and another one is include. So, when you open source you can
locate the entire HAL layer or you can also say that, So, all these are
peripheral drivers for different peripherals present on your microcontroller.
So, like CAN,CRC, Crypto engine, Dac,DMA, Flash,I2C,UART etc. . So,
this layer OK, will have drivers for all the on chip peripherals of your
microcontroller, that is STM32F4x family of microcontrollers. And this
folder that is include will have the supportive header files for these source
files. And the same layer you also encountered in your eclipse section. Isn't
it? So, there we added all these files from you know generating a CubeMX
Project and from there we imported into our eclipse project, isn't it? So, the
HAL layer used there and what your seeing here, if the source codes are
exactly same. Why? Because, these source codes are actually given by the
ST. Right? So, this is given by the vendor. And when you go back to drivers
here and here you can locate all the CMSIS header files for different layers
like CMSIS, Core header files, DSP header files,RTOS header files etc.
CREATING A KEIL
PROJECT
The previous project, we installed the software pack for our microcontroller
and we also explore where exactly it is stored in our PC, and we also
located the HAL driver for the microcontroller. So, now in this project what
will do is. We will create a project a keil project for our microcontroller.
So, I'll be using STM32F407 discovery board for this experiment. And if you
have Nucleo board or any other board from different vendors, then please
continue using the same. The procedure will be almost same. So, let's see
how we can do that. Okay. So, now to create a new project on Keil
Microvision IDE, just go to project and then click on new micro vision
project. So, now once you do that, then Keil will ask you to store your
project in some location. So, will use the same location which we used in
our eclipse section. So,what I do is, I will just create a new folder. I will
just name it as Keil. I'll just name it as keil workspace and under that. So,
we will give a project name for our first project. So,I will just give it as
Blinky, a STM32F4 discovery board. So, now let's save this project, and
now Keil is asking you to select your microcontroller. And here it is showing
you 3 options And you'll see these options that is that ST microelectronics or
Texas Instruments only if you have installed the software pack remember. So,
if you don't install software pack and you don't see these options. Since, we
have installed software packs for ST as well as TI, it is showing us 2
options. Great. So, now let me select my microcontroller that is actually
407VGTx. All right. And here you can see a small description about this
microcontroller. Great. Now, click on So, once you do that a manage run time
environment will be shown by the keli automatically. Here, Just select your
board STM32F4 discovery, and after that what we do is, we will add a start
up a file for our microcontroller. So, remember in the eclipse section you
have added a startup file for your project. Right? So, the startup file actually
who took it from the CubeMX generated project. Isn't it?
So, here go to Device, and click on Startup. So, this will add the Startup
code which is specific to our microcontroller to our project. And when you
select that you can see here there are some warnings. The startup is actually
depending upon the CMSIS-core. All right. So, CMSIS core APIs. So, what
have to do now is, you have to add this two. So, this can be added from the
CMSIS option here. So, here there is a Core option that's you have to select
this. Otherwise, you can just click Resolve here. So, what Keil does is, it
will automatically resolve this warning by adding those required software
components. So, when I click Resolve, you can see that the Core is
automatically selected. Otherwise, you can explicitly select that OK, no
problem. Great. So, now we have added startup code for our microcontroller
as well as CMSIS core support for our project. Great. So, for a time being
only this much is sufficient and if we need more then we can again launch this
window and we can add more software components. No problem. So, now
click And here you can see that, a project is created and under the project it
has added all these files. So,here it's a source code group Where you can add
all the source files like main. c or any other source files. So, which will see
later and this is the CMSIS core header files. And here you can also see that,
it has added a device component here and under that you will find 2 Startup
files one is . S that is a assembly file. So, this startup actually contains all the
vector table information of your microcontroller. And here you can see that,
one more system startup file. OK, which is a . c file is also added to our
project automatically, which has some functions which are actually they will
get called before reaching the function main of your application. So, these are
these are actually called as startup routines which will get called prior to
reaching main function of your application. It does some of the
microcontroller specific initialization related to clocking a setting of vector
table, addresses, and some other required setups which will allow you to
successfully execute your application on the microcontroller. So, whenever
we create a new project for microcontroller the keil will automatically add
at least to these two files for your project. Great.
So, now let's quickly see how can we create a Keil project for TIs
microcontroller. Now, let me try one more project creation. In this case, let's
try it for TI based microcontroller. So, let's go to project and click on new
Microvision project and here just create one folder called TI, and just go
under that and give a name for your project. OK . let's say, blinky_TIVA and
click save. And now, let me select a Tiva series of microcontroller. So, now
let me select this microcontroller just to expand TIVA123x series here. And
this development board is actually based on this microcontroller.
123GH6PM. So, just try to locate that option here. If you browse so you'll
find at the end Here it is TM4C123GH6PM and just select that. So, again it
will give you the manager runtime environment and here you have to select
the startup. And you have to also add the CMSIS Core, and then click And
now, when you see here it has added TIs specific Startup files. So, one is . s
and another one is . c. The . s contains all the vector table information for this
micro-controller and . c ontains OK all the routines which will get called
before reaching your main. So, these are required in order to initialize this
micro-controller. All right. Great. So, like this you have to create a project
for your micro-controller and make sure that these two Startup files are
present in the project. Great. So, now let me go back to my project earlier
project, that is for STM. So, this one, When I tried to compile this, So, you
can just select on this project and just hit compile here or build. So, when I
tried to build that, you can see that it's started building and here you will see
a error undefined symbol main. So, why that error came. Because, So, this
error happened because, remember in your project the code execution always
starts from the reset handler of the MCU. The reset handler is the very first
function which will get called by the processor when it undergoes power on
reset. So, that means when you give power to your board or when your
microcontroller goes power on reset. The very first function which will get
executed is reset handler, which is present in the startup file that is . s. So,
here you can see that this is a reset handler. So, just search where exactly that
is defined. So, just copy and search that function here. Here, we can see the
reset handler is actually coded in the assembly language. And when the reset
handler executes it first tries to call the SystemInit. So, which is defined in
this file system_stm32F4xx. c and then it calls main. And since we have not
defined main, the linker actually thrown us the error undefined symbol main.
So, that means we have to create now one source file and after that we have
to add our main function there. So, let's do that. All right. So, now let's add a
source file into our project. So, for that just click on this Source Group 1, a
just right click on this and then click on Add new item to group OK, it to this
group. Just select that. So, then Keil will ask what you want to add? Whether
you want to add . c file or dot cpp, assembly, headers etc. . So, let me add a .
c file. So, just give a name for it. So, let's say main. So, I want to add main. c
or any other name. No problem. So, now let's hit Add. Now, you can see that
the main. c is added. So, now I would just insert a main function, int_main I
will just type void, and I'll just return 0. Grate. So, that's it. So, only a small
function. So, now when I compile this code, just select here and just click on
built and here you can see that, that error actually vanished. Isn't it? So, there
is still one warning The last line of file ends without newline. So, just give a
new line here and save it, and now again build it. And here you can see that,
the code actually compiled without any errors So, this is a so So, that means
you have successfully created a project and compiled it successfully for your
microcontroller. So, but this file is not doing anything useful. So, what will
do now? So, will try to you know toggle the LED of our board.
CREATING A LED
TOGGLING APP USING
BOARD BSP APIS
So, in the last project you just created a very trivial main. c. So, which is not
doing anything useful, but it compiles successfully. Right? So, you should
achieve this on your machine. Great. So, now what will do is, now will just
toggle the on board LED of the development board. So, every development
board usually have at least 1user LED. So, in my Nucleo board for example,
say I have this board with me. It has actually one user LED on the board. And
my discovery board actually comes with 4 user LEDs. Great. And even TIs
board, it will also come with one or two user LEDs on board. So, now our
goal is to toggle that user LED. So, for that we first we need a schematic of
the board. Right? So, because we don't know where exactly the LED is
connected. So, first will see the schematics. So, just open the schematic of
your board. So, in my case since I'm using discovery board for this
experiment I just opened the user manual of my discovery kit. So, the because
the user manual itself has the schematic of the board I just attach to this user
manual to this project and you can download it, if you are using the same
board. Otherwise, if you are using any other board then you should have the
schematic for that. So, I have also attached schematic of the Nucleo board
which I have. Great. So, if you have any other boards so be ready with the
schematic of your board. Great. So, in the User Manual of the discovery kit at
the end it has the schematic So, here it is. I think you should go to the page
number 31. So, this is a schematic of the board and just scroll down until you
locate the the LED. So, the best way to locate your user LEDs OK, is to
search by using the component number which is mentioned on the board. All
right. So, if you look at your discovery board. You should find the component
numbers for the user LEDs like LD3, LD4, LD5 and LD6. Right? So, there
are 4 user LEDs. So, what you do is just search in this document for LD3
let's say. LD3 OK just search, and here it is LD3,LD4,LD5 and LD6. So, LD3
is a orange color LED, LD4 is green, LD5 is red, and LD6 is blue.
That's great. And the schematic is also saying that LD4 is connected to PD12.
That means, GPIO port D pin number 12. LD6 which is a blue LED
connected to PD15. That means, GPIO port D pin number 15. So, don't worry
about the GPIO ports and it's pin details. I have a separate section, which you
will see as you make a progress in the course where I have a extensively
discussed about GPIO ports, GPIO pin numbers, how to configure GPIO
registers, how to write it, how to read it. So, everything is covered in the
subsequent projects This is just a practice project in order to work with Keil,
eclipse and with your hardware So, just to make comfortable with Keil
environment, eclipse environment, and just trying out some projects on the
board to get comfortable with the board. So, don't expect everything needs to
be covered right now. So, as to make progress if you have any doubts it will
get clear. So, we got some information now where our LEDs are connect.
Right? Great. So, now let's go back to our Keil project. So, this is a project
we created and here just go and click the Manage Run-Time Environment.
And now what will do is, take the help of board support package in order to
get the APIs to control our LEDs. So, we will be using readymade APIs,
because the board support package is already available for our board.
Because, in the previous project we have installed it by using pack installer.
Isn't it? So, here you can see the board support option, and here there's a
variant, so select your board. In my case it is STM32F4 discovery. And if
you have Nucleo board and please hold down I will cover that in the next
project. Great. So, now what we do is, just expand this and it says that there
are 2 sections available for board support, one is Buttons and LEDs. And
under board support will see APIs for buttons, that is controlling or reading
buttons and APIs to work with LEDs. So, what will do is, will first add
LED support to our application. So, just expand this and select this. When
you select this, So, the Keil is complaining a with some warnings that
additional software components required. So, that means, it is saying if you
add LED there are some additional things I need to add to the project. So,
let's not worry about what are those components needs to be added Just click
Resolve. And Keil try to resolve something but still, there are some
dependencies. So, it is saying that additional software components required.
Select these components from the list. Which list this list STM32 Cube
framework under device. So,will go under device here, and STM32Cube
framework that means this one and here, OK, so just expand this. Here, it is
saying either I should add STM32CubeMX framework or STM32Cube
Framework as a classic version. All right. So, what you do now is, just
select this classic version. So, because if you select this it will launch the
CubeMX software and all. So, that we don't want to launch. So, this way
actually we did in the eclipse project, right? We launched the CubeMX and
we tried to build one project etc. . So, all those things we need not to do now
just select classic. And after that, what you do is, that's it.
So, now there are no errors, just click Great. So, now the board support is
added to the project. So, if you expand that you can see that there is only one
file, that is actually to control our on board LED. So, if you just check this
file it's a very easy file, but let's not try to understand it Now, let's see what
are the APIs it provides to the user application developer. So, because the
job of the board support package to give APIs to the application developer.
Right? Great. So, now this LED_F4Discovery. c gives LED initialize
function. That means, a function used to initialize the LED, that's it. And it
gives a function to uninitialize the LED. And it gives the function to turn on
the LED, and turn off the LED, and some other Get count and Set count.
Great. So, it is very straightforward. Right? So, there are some APIs which
are very straightforward. So. So, I think we should start using it. So, what I
do is just look at this API. This API doesn't take any parameter, that's great.
Then I can just use this on my in my application. I'll just use it. Right? So,
because it doesn't take any parameter. So, a function which is doesn't take any
parameter is actually a very simplest API to use. Right? You need not to use
your brain much to think what and all parameter you have to send. So,
because you need not to send anything as a parameter here. Right? Great. So,
now just try to compile it. All right. The compilation is actually successful,
but here we can see that we got one warning. The warning is the LED
initialize function declared implicitly. So, that means its prototype is missing
in this file. Actually that is required because, when the compiler try to
compile main. c it encountered this function and since there are no prototype
found for this function it is throwing this error, it is declared implicitly. So,
the compiler is basically warning you that the prototype of this function is not
expressed explicitly, So, it is a assuming implicitly. So, we have to resolve
this warning because that's not good. So, if you go to the LED_F4Discovery.
c and here you can see, they have added this header file board underscore
LED. h So, just select that and right click and just click on open document.
So, it will open here in the editor. And here you can see that all these are
function prototypes. So, that means our job is now So, either you add this
function here to resolve this error. So, if I compile this now the error goes
away. All right. So, there are some other error which is related to something
else, don't worry about that. But, the function implicitly declared is
resolved. Right? So, the best practice it is not to add like this. Rather, what
you have to do is, you have to add this include file in the main. c. That's it.
Now, just compile your application and now the error went away.
So, that means the compiler is happy that before coming to main it found out
the function prototype by expanding this header file. Right? Great. So, now
we initialized. Now, what is next. Now, let's turn on the LED. So, again go to
your board support package. And so, what is the function to turn on the LED.
It's LED_On, right? So, but you need to give some parameter for the LED.
That means, you have to pass the LED number. Great. So, let's see how to use
this. So, now let's say LED_On. But, you cannot use like this because you
have to send a parameter. So, parameter is nothing but LED number. So, what
are the valid LED numbers? All right. So, by the schematic we know that our
board has 4 LEDs and we will number like this. we will number this LED as
0, will number this LED as 1, 2 and 3. When I pass here the argument 0, So,
then that means, I'm going to turn on the LED 0. So, similarly I can do it like
this. So, 0,1, 2 and 3. So, now this code must turn on all these 4 LEDs. So,
here the LEDs are actually identified through numbers. Great. So, now what
will do is we just compile this code. So, great. The compilation is
successful. So, now we have to download this code into our hardware. So,
before that we have to do some settings, that is called as Target settings.
Click on your Target and right click and select Options for Target. great. So,
now here go to debug option and here select you are debugger. For ST, You
have to select ST-Link debugger. If you have any other debug interface like J-
LINK, or ULINK, or TIs XDS debugger than you have to select OK
according to that. A like ICDI, for Stellaris is TI, Stellaris related boards. In
my case, it is as ST-Link debugger. So, now click here on settings and check
this option, Verify code download and download code Download to Flash.
Great. That's the thing you have to do, and click on And after that, just click
on Great. So, now you are ready to download this code onto your hardware.
Now, click on this load button to download code into your hardware. Great.
The flashing is successful. Now, go and reset your board by pressing the
reset button. Great. So, here we can see all LEDs are On. So, we just played
with LEDs in no time. Isn't it? So, now what will do is let's toggle that. So.
So, to toggle you have to turn on and turn off, right? So, what will do is, will
just give a small delay will implement a small delay function. So,I will just
write a small function which implements a small software delay OK, equal to
0 and for i equal to 0, i less than let say i use the counter 50000. I don't know
how much is the delay, but less use 50000 Just to introduce small delay i++ .
Let's halt in the for loop until it counts up to 50000. Great. So, now let's just
compile this code OK, the compilation is successful, and I suggest you not to
use this you know this notation in describing variables Instead of this I can
just use uint32 underscore t. So, if I use this the compiler will issue error.
When you want to use this shorthand notation, so always better to use the #
include always better to include stdint. h All right. Great. So, now let's
compile again. Yes, it is compiling. So, now what will do is, let's turn on
everything and will give a small delay. Just call our delay function. Great.
So, now after that what will do is LED_Off function. Right? So, where is
LED_Off. So, this is the LED_Off function. So,let's use that. Let's use for all
the LEDs. Grate. So, Now let me just format my code. Okay, great. And it's
LED 1, 2, 3. Great. So, now after that will call the delay function once again.
Great. So, our toggling application is ready. Now, let's just compile this.
Grate, it's compile successfully and just download it and just press the reset
button and it will execute only one time. Isn't it? So, what will do is will just
put a while loop here. while (1) So, will just put a while 1. So, this code
will be you know, so this will toggle our LED is continuously OK, it's a
infinite loop. So, let's compile once again and let's download. So, there is
one warning, the statement is unreachable that's OK, because we are trapped
in the while loop. Right? Great. So, let's download and just hit you are reset
button and you can see that we are not observing the toggle. Isn't it? So, that's
because the delay is very small. So, just to increase the delay. So, just put
one more 0 here, and just compile this once again and download the code and
just reset your board, and here you can see that, the indeed you are able to
observe the toggling of all your LEDs.
CREATING A LED
TOGGLING APP USING
BOARD BSP APISNUCLEO
So, in the last project we did LED toggling application for our STM32F407
discovery board. Isn't it? So, now I will repeat the same experiment if for the
Nucleo board. So, what I do is, I just create another project OK, project-
New microvision project. So, and I'll just go to Keil workspace, and I just
create another folder here called Nucleo- F446RE and I will just go inside
that and I will just create another folder, which is blinky. Great. And in that
folder I will save my project, blinky_Nucleo_F446RE board. So, now let's
save, and now, I'll just select my microcontrollers so everything is simular
What we did in the last project so mine is 446RE, right? And now, what I do
is, if the board support I just select my Nucleo board here. So, now what I do
is, I go to Device and I select the startup and I just click on Resolve here.
Great. That's it. I just then click And as we saw in the last project it created
to startup files. Right? So, now our job is to add a source file, isn't it? So,
what I do is, I right click and click on Add new items. So, similarly what we
did in the last project, Add a C file let's say main. c. I will just write main.
So, . c will be attached later by this window. Great. Now, let's click Add.
So, the main. c is added. So, now I will just copy paste my earlier code.
Great. All right. So, now what have to do, we have to add board support
packages for our LED. So, what have to do is, we have to go to manage
runtime environment, and here just select the Nucleo board. So, this is my
board. So, select according to your board and just select LED here. So, now
the kid is not showing any warning that's fine. Great. So, now let's click
Awesome. So, we just added the LED_Nucleo- F446RE. c and as expected
this file also will give you some APIs OK, which we can use to drive our
LEDs. So, the Nucleo board actually as I explained in my previous project
has only one LED. Right? So, I have also attach to the schematic of that and if
you open that schematic you will find that this is a user LED, LD2. And this
user LED is green in color and it is connected all the way to the PA5. That is
GPIO Port A pin number 5. Right? So, that's the details we can obtain from
this schematic. Right? So, now there is only one LED. Right? So, that's why I
can call it as LED 0.
So, the number for this LED is LED 0. Great. So, now will just modify our
code. So, the code is exactly same. So, but we have only one LED so we
have to remove all these, I will just remove this. Because we have only one
LED, that we call it as LED0. So, just initialize the LED and call LED_On
with 0 as the argument and here 0 as the argument for LED_Off. With a small
delay which we implemented in the last project. Great. So, that's it. Let's
save it and let's build it, let's see whether it builds successfully or not. Yes, it
build successfully. Great. So, now go to your target settings. Just right click
here, and select options for Target and make sure that in the Debug tab. So,
you have selected ST-Link debugger, or your debugger which is there on your
board. And in the settings just click on verify code download and download
to flash, click All right. That's it. Now click OK here. So, now what you do
is connect to your board that is Nucleo board. So, I just connect my Nucleo
board to my PC and I will just download the code now. Yes, it download
successfully and I just press the reset button on my hardware and indeed the
green LED is blinking. That's great. So, we also executed on Nucleo board.
So, try until here and let me know if you face any issues. So, now I will see
you in the next project and in the next project what we'll do is, we will add
button support for this application.
DOWNLOADING AND
INSTALLING OPENSTM32
SYSTEM WORK BENCH
So, in the previous project you just install the Keil MDK5 software, and we
also saw how to install the Device firmware package from the pack installer
for different microcontrollers. Isn't it? And if you are not using Windows as
your host then, you cannot use Keil MDK5 software. So, in this project let's
see how we can download the open STM32 software. This software is
actually supported on all the major platforms like Windows, Linux, and Mac.
So, go to the Homepage of the openstm32. org, and here you can find a
download area, just click over there And it will ask you to log in. So, you
have to create account, Or you have to sign up here if you don't have an
account. So, let me just login. Yes, here it is. Downloading the System
Workbench for STM32 installer. So, this is a Windows section, this is a Mac
section, and this a Linux section. So, if you are using Windows you can both
32 and 64 bit versions are available.
So, if you are using Windows 32 bit machine, then you have to download
from here. This is Win32 exe. And if you had using 64 bit version, than you
had to download here this one. And for Mac So, you have to read this section
here, The Mac OS version is available for 64 bit systems. You have to
download this and you have to run this executable on your machine. So, just
try that if you are using Mac OS. And for Linux, here also a Linux 32 bit
installer as well as 64 bit installer supported. But, here there is a warning
that this is an old release OK, and this is released in March and this is no
more maintained. So, 32 bit Linux machines are so will not be supported
anymore from this community. So, a people who are using 32 bit machine
with Linux may face issues with this IDE. So, that's the Warning. But, Linux
64 bit installer is updated, again it is released very recently. So, you can just
download this and you can run. And remember that here there are some
warnings for Linux users. So, first after downloading you have to change the
mode OK, that means you have to give executable permission to the binary.
So, just you have to run chmod a+x. So, +x means giving executable
permission to this binary. Which is present at this directory OK, you have to
just run that command. To launch the installer in the GUI mode, JavaRE must
be installed 7 or later and this pseudo command must be present.
So, you can just run app to get install in order to install that. So, you can just
search for Oracle Java download in Google and it will take you to the
official Java JRE download page and here just click on download here, or
you can click on JRE download here. And then it will take you to the Java
runtime environment version 9, and here you can find Java JRE for Linux,
Mac, and Windows. If you don't have Java JRE in your machine then you
have to install it. For Linux 64 bit download this and install Mac guys
download this and Windows you can install from here. Grate. So, just accept
the license agreement and you can download it and you can install. So, now
after that, It doesn't matter whether you are using 32 bit machine or 64 bit
machine. I mean Linux machine, just run this command on your Ubuntu
machine So, this will help you to execute all the 32 bit executables on the 64
bit machine. This warning is for Ubuntu users. But, remember that the Java
JRE what I just shown OK, should be installed for all the platforms. Doesn't
matter whether you are using Windows, Linux, or Mac. Because this IDE is
based on Eclipse. So, in order to run eclipse successfully on your machine
Java must be present. Grate, carefully read this warnings before installing.
Great. So, I hope you do that and let me know if you face any issues OK, for
Windows and Mac it is very easy to install, just you had to run your
executable and it will get installed. Great. So, now since I'm using Windows
and let me download this 64 bit version.
INSTALLING OPENSTM32
SYSTEM WORK BENCH
So, I just downloaded that into my Software & Toollchain folder. So, here it
is. And now let's install that. So, now it is trying to install. So, it is throwing
some error but it is trying to execute that exe. Please be patient and here it is.
You will get this window and here just click next, accept it and then it will
ask you where exactly you want to store these installation files. So, you have
to select your drive here. So, in my C-Drive I don't how much memory. So, I
will save it in D drive, then click next. So, it will create the folder AC6
System workbench. Click next, and it is saying that directory already exists
because I have already installed that before. And override the existing files.
Ok for me it is For you So, that warning may not come. Here, it will install
the STM32 system workbench. That's And it will install the driver for our in
circuit debugger and programmer that is ST-Link version 2, and it is going to
install the driver for that. That's great. Let it install and ST-Link server. So,
all 3 are going to be installed. Now let's click next, next, and this is the
chosen path.
That's correct. And now let's click next and it is installing. So, Mac and
Ubuntu guys for you also this window will pop up when you run your
binaries, and then you have to install this software So, you should see that
folder in the D drive now, here it is. So, it has created this folder and it is
copying all the files. So, let's wait until it finishes the installation. All right.
So, now it is throwing some error here. So, now you can click yes here
continue installation. Now, it is trying to install some ST-Link related
drivers. So, now let's Now it is finish, Now let's click next. Now, it is asking
permission to install the device driver. Now, let's click Next here and here it
is. It is saying that the drivers were successfully installed on this computer.
So,ST-Link drivers are installed, Now let's click Finish, Next and the
installation has completed successfully and you can click Done here. Great.
So, in this folder we successfully installed the system workbench for STM32
micro-controller. Now, how to launch the application? So, once installed.
So, you will see this eclipse icon here.
So, you have to click that to open. Now, the workbench is getting launched.
All right. Now, it is asking you to give the workspace path and by default it
creates a workspace in this path. So, if you don't want eclipse or this
software to create a workspace at this path you have to give your path. So,
now let me consider So, here I will create one folder called STM32
workspace. All right. And I will give this path Great, click Great. The
software is now launched, and we also created a workspace. So, a
workspace is nothing but a collection of projects. We haven't created any
projects, So, we just created a workspace and here you can create multiple
projects for your target. And it is now installing automatically the ARM
toolchain, So, let it install don't interrupt it. Fine. So, now here our
workspace is ready and you can enter the workspace by clicking here. And
here is the workspace, and it doesn't contain any project. Right? So, we just
finished installing open STM32 System workbench.
STM32CUBEMX
INSTALLATION
So, now let's install the STM32 CubeMX Software. So, just search STM32
CubeMX in google and just follow the first link. STM32 CubeMX So, this is
actually application given by ST, which generates code automatically by
taking your configuration. So, it's a very powerful tool. Let's explore this.
Come down here and here there is a Get software section is there, right? So,
just download this CubeMX software. You have to login I think. All right.
So, now let's download that. So, I will be right back after the download. So,
the download is completed, Now, let's go to that folder and let's extract this
So, it is extracted and let's go under this CubeMX and you will find one
application here. So, let's install this, click next. I accept the agreement, next.
So, this will install this application in C directory. Let it be. Let's install in
this path no problem. So, it says that the directory is already exist for me
because I have already experimented with some older versions. For you You
may not see this error if you are installing it for the first time. Click next OK
and it is installing CubeMX related files. So, now click next, Installation has
completed successfully. So, under this ST micro electronics folder. That's
fine. Click on done. Great. So, this is CubeMX version 4. 22 OK, just
remember that. Now, let's go to all programs and here I have to open a
STMicroelectronics. right? Here, and cube here we can see STM32Cube and
just open this application CubeMX. Great. Now, go to help and click on this
help here, which will open up the whole documentation of this software.
So, if you are interested you can go through this documentation but for a time
being not required. I'll just close this. If you check about here you can see
that this is a latest version 4. 22. So, now what have to do is , again go to
help and click for check for updates. And what it is saying is, there is the
firmware package for STM32F4xx series is available. And if your
microcontroller is based on F4, then you may have to install this. So,what
have to do is just click here and you can see what are all the updates
available. So, like Seems like they have updated Fat file system,FreeRTOS
to 9. 0 etc. . So, they have made couple of changes and if your
microcontroller is F7, then it may have to install this. So, you can just click
on install now to install that. But, my microcontroller is of F4 I don't want to
install that. But, after some time if you check again for updates, then you may
see some of the updates available for your microcontroller. So, frequently
you have to check this and install if any updates are available for your
microcontroller family. So, that you have to do. To be in sync with the
developers of ST. So, they will fix new bux. They will introduce new
drivers or they will add new features. So, if you want to be sync with them,
then you have to frequently update, check for updates, and install it.
ACTIVATING STS
BOOTLOADER PART1
So, while exploring about the system memory of the microcontroller that is
ST's STM32F446 microcontroller, we explored this point . Isn't it? All the
ST MCUs store bootloader in this memory. And by default MCU will not
execute any code from this memory that's because of the aliasing, the 0th
location is not aliasing to this memory region by default. So, that's why you
cannot execute any code from this. But you can configure the MCU to boot or
execute bootloader from this memory by using the boot pins which we just
saw in this table. Now, what will do is, we will take up the Nucleo board
and we will make the boot1 pin as grounded OK, and boot0 pin as VCC, and
then will reset the board. So, if you do that you should activate bootloader
which is present on the board. So, let's do that. So, before that we have to
check some details in the schematic in order to see, where exactly the boot0
and boot1 pins are present. So, open up the schematic of your board.
Whether it is your discovery board or Nucleo board. For Nucleo board This
is actually the common schematic for these boards. So, if you have discovery
board then you have to open up your schematic. So, just check your user
manual of the board so you will find the schematic. And here what we see is
boot0 pin, OK on the Morpho connector. Right? That is CN7.
And let's search for boot1, boot1. yes, here it is boot1 it is on the
microcontroller. That's fine. But, how about locating this on the connector.
No, it is not available. So, that is. So, that means boot1 pin is multiplexed
with the port B second pin, that is PB2. Isn't it? So, PB2 maybe present on
the expansion header or Morpho connectors. So, just search for that and yes
it indeed present so you can use this pin as boot1. So, if you want to touch
boot1 pin. So, in our case we have to make boot1 as 0 So, that is already
grounded, no problem and boot0 as 1. So,the boot0 is here and this you have
to connect it to VCC. Right? In order to make it 1. So. So, how do you make
this pin as VCC, so just connect that to this VDD here. So, Just take one pin
and short this pin number 7 and pin number 5. Or you can even take this 3. 3
volte supply here. So, you can even connected to pin number 16 which is 3. 3
volte. Great. So, now let's do that and then will reset the board and will see
what happens. All right. So, I just shorted the pin 5 and 7 here. If you need
more information about what exactly is boot pin in your Nucleo board or
discovery board you have to refer to the user manual of the board. For
example, here I have a user manual for STM32 Nucleo boards OK, which is
applicable to all these boards. So, here if you search for let's say boot0 pin.
So, just search for boot0,
So,here it is on Nucleo-F030R8 board OK, boot0 is actually the 7 pin. That
means, this schematic is actually a generic to all the Nucleo boards. But on
the Discovery board it may be different. All right. Fine. So, let's search
further. And here we can see, in all the boards it is you know pin number 7
on the morpho connector CN7. So, here it is clearly mentioned that, default
state of the boot0 is 0. It can be set to 1 when a jumper is on the pin 5 and 7.
of CN7. So, that means you have to connect one wire or jumper in order to
short 5 and 7. And after that please reset your board. So, when you reset
your board so the microcontroller will detect that the boot0 pin is actually
high, and it will go to the system memory boot mode. So, that means, that's
how you activate the bootloader which is sitting in the system memory.
ACTIVATING STS
BOOTLOADER PART2
So, in the last project you booted the board through the system memory. Isn't
it? That means, the bootloader must be running by now in your hardware. So,
before communicating with the bootloader OK let's learn some of the features
of the ST's native bootloader which is there in the system memory. For this
there is a nice application note OK here it is. So, This is a application note
you have to refer in order to fully understand the STM32 microcontroller
system memory boot mode. So, this is design by ST and it has huge features
like it. It supports communicating over USART peripheral, CAN, USB, I2C,
SPI, etc. . And they use a communication protocol which is actually not a
standardized protocol in order to communicate with external world and the
bootloader. So, the packet format what they used to exchange between host
and the target is all ST specific and how that is designed. What is the
flowchart of the bootloader? Everything is discussed in this document. So, it
is really worth to invest time in reading this document. And this document
has some general discussion about the bootloader, and it has separate section
for different families of microcontrollers from ST. For example, let's go to
one family, let's say I go to STM32F446xx device bootloader.
So, when I go here. So, it actually describes through which peripheral a the
bootloader communicates with the host. So, in this table you are seeing here
it has the capability to talk to the host over the USART peripheral, USART1,
USART3. Or it can even talk to CAN peripheral, I2C1, I2C2 etc. . Even
through the USB and CAN2 peripheral. So, even it can talk to the host over
USB. And this is a nice flowchart about the initialization of the bootloader
and how it receives packets, how it poles for the different peripherals etc. .
And in this course, we are going to write our own custom bootloader OK, so
that's the objective of this course. And in our bootloader, we will support the
bootloader and host communication over the USART peripheral. And of
course, you can always extend this your custom bootloader in order to
support more and more communication protocols like I2C, SPI, USB etc. .
So, that you can always extend.
ACTIVATING STS
BOOTLOADER PART3
So, in the previous project you saw that bootloader can able to communicate
with the host over USART peripheral, or CAN, SPI, I2C, USB etc. . So, now
I will demonstrate you how you can communicate with the bootloader over
the USART peripheral. So, just connect your board to the PC and then check
in the Device Manager section here, and you see that the board is indeed
detected and it is saying that it's a virtual COM port (COM3). But,
unfortunately we cannot communicate with the bootloader over this virtual
Com Port. That's because, So, if I open my Nucleo's User Manual and if I go
to the USART section, here the USART communication. And here, what they
have mentioned is the USART2 interface available on PA2 and PA3 on the
STM32 microcontroller can be connected to ST-LINK MCU. So, that means
what you are seeing here, that is Virtual COM Port. This actually belongs to
the USART2 peripheral. But unfortunately, what we see in the system
memory boot mode guide. Here, It clearly says that the bootloader which is
sitting in the STM32F446 microcontroller, doesn't support OK,
communicating over the USART2 peripheral.
So, that means you have to check your boards user manual. Which USART
peripheral is utilized for this Virtual COM connection. If it is USART1 or
USART3, then you can communicate to the bootloader over this interface
from the host, no problem. So, but on the Nucleo board since USART2 is
utilized for this Virtual COM port. You cannot use this interface in order to
talk to the bootloader of the board. So, you really have to check that in your
User manual. So, that means you have to use one USB to UART converter
hardware in between. In order to talk to the board over UART from the PC.
So, these are very cheap hardware's, USB to UART converter so you can buy
it or you can it reuse it if you have one. All right. So, now let's go back to
system memory document. Now, what will do is, we will use a USART3
peripheral in order to talk to the bootloader. So, here the USART3_RX pin
will come over PC11 and USART3_TX pin will come over PC10 of the
microcontroller. So, you have to search for PC11 and PC10 in your
schematic. So, let's go to a schematic and let me search PC10. So, here it is
PC10 and PC11 they are available at 1 and 2 pins of the CN7 converter. So,
you can even perform this action on let's say USART1 peripheral no
problem. And the USART3 peripheral you can also bring in on PB10 and
PB11 that's also possible. So, but let me do it on PC10 and PC11. So, PC 10
is actually the TX. So, let's say here one pin is PC10 and another one is let's
say PC11. So, PC10 is actually TX, that is TX from the board end. Isn't it?
So, PC10 should go to the RX pin of your USB to UART converter. And
PC11 is actually RX. So, that's why that PC11 pin you have to connect to the
TX pin of the USB to UART converter. And this ground pin and so this is a
ground pin you have to connect to one ground point here. That's it. And this
you will the connect to the PCs USB port. This much connection you have to
do. Then what will do is we will run some application here and then we can
talk to the bootloader which is running on this MCU over USART interface.
And after doing this you have to download one application from ST's official
website, STM32 Flash loader demonstrator and I have given you the link,
just go through that link and download this software here, and you have to
login or register in order to download that. And once downloaded we will
have this Zip folder here. And you will see one Windows application so
unfortunately doesn't support Mac or Linux. So, if you are running Mac or
Linux then you may have to download some open source flash loaders.
Because, officially ST has not released this application for Windows and
Mac users. And it's If you don't get a chance to run this demo application no
problem So, you can just watch the video and skip doing it. No problem. And
Install this software and I'll see you in the next project.
ACTIVATING STS
BOOTLOADER PART4
So, hope you have downloaded this software and installed it. So, now let me
open that here it is, Demonstrated GUI. And hope you have connected the
USB to UART hardware to your PC and did all the connections to the board
and it is not currently detecting my port. I'll just close it. And I will open my
device manager and here it is. It is showing 2 devices now. This is my USB
to UART converter from Prolific. Now, I open that Demonstrator GUI and
here you can see that it is already selected the COM2. Otherwise, you have
to select the appropriate device. Now, what I do is. I will just click next.
Here it is. The application is unable to talk to the bootloader. It's like
hanged. See it is saying it's not responding. So, no response from the target,
the bootloader cannot be started. Please, verify the boot mode configuration
and the flash protection status Reset your device then try again. All right. So,
now what I do is a reset my board. I reseted my board and let me hit Next
again, and now it detected. So, that means I think my board was not in the
system memory boot mode. So, now it is saying the target is readable.
Please, click Next to proceed. Next. Grate, so now it actually detected my
board. It is saying that it is from STM32F4 family and the flash is 512K. So,
everything it detected fine and it also saying that, I have got these many
sectors and these are the base addresses and end addresses of those sectors.
So, that's all correct. Right? So, now let's hit next. All right. So, now here it
is giving us lots of options to perform lots of actions like erasing flash,
uploading a binary file to the board, that means flashing a binary file to the
board, reading from the flash memory into the host. So, all these are very
interesting actions you can perform.
So, now in this course we are going to design a custom bootloader OK,
which also going to support all these actions, which you are going to perform
from the host. Now, let's try downloading a binary file or a hex file into the
board over the USART interface For that, just click download to device. You
have to select a binary file or hex file of your application. So, for that we
will use our Blinky application. So, this is a blinky application, which we
did in the earlier part of the course. Here, Just click on this options for
Target, OK, and here the controller is F446RE. That's correct. And in the
Output, click on Create hex file and then OK, and then just rebuild your code.
Great. So, the hex file for this application is created. So, now let's see where
it is located. So, you have to go to the project directory, and in the Objects.
So, you'll see some Hex file here. Here it is. So, now we will upload this
Hex file into the board over the USART interface, OK, by taking the help of
the bootloader. So, the bootloader which is running on the board now it will
receive this file in the form of a streams of bytes, and then it will flash this
hex file into the flash memory of the board. So, this is what we call as in
application programming. Here, we are not using any debugger like ST-LINK
in order to flash our code directly into the flash. Isn't it? We are taking the
help of UART and bootloader and we are passing the file over the UART as
a stream of bytes. And those stream of bytes will be received by the another
program, which is nothing but the bootloader running on the microcontroller,
and once the bootloader receives those streams of bytes OK, it will flash it
into the flash. Grate. So, now let's select this path, let's go here, let me paste
this path. So, here just select hex, here it is this file select it. Great. So, now
it is asking us where to program this. And here there are a couple of options,
Erase necessary pages, or No erase, Global erase. So, let it be Erase
necessary pages. That means, it will erase the required sectors of the flash in
order to program this hex file. So, now here is a option Jump to the user
program. So, this option indicates that you know after flashing, If you check
this field what happens is. Once the bootloader finishes flashing this hex file
onto the flash, then it jumps to this application. So, let's select that. Great.
Now, let's hit next. So, now here we can see bootloader successfully flashed
code onto the flash and it is already started executing our application. If you
now try to communicate to the board OK, it won't communicate to the board.
Why? Because, the bootloader has already given control to the user
application which we just flashed. So, That's why, you will get no response
from the bootloader. Because, the bootloader is not running on the board, it is
already given control to the user application. You have to again make the
boot0 pin as 1 and you have to reset the board, then you have to click next
again. Now, this time it will communicate with the bootloader because the
bootloader is now running on the boat. Don't worry, all these things we will
implement as a part of our custom bootloader implementation. So, this was
just a demonstration of usage of ST's native bootloader.
BOOTLOADER
TRANSPORT
So, let's understand the transport protocol we use in our custom bootloader
design. So, this is the high level diagram of how we communicate to the host
from the Nucleo board. So, we will use in this case 2 USART peripherals of
the STM microcontroller, One is USART2 and another one is USART3. So,
we will use USART2 peripheral of the microcontroller in order to receive
the commands which is sent by the host, as well as it sends the bootloader
replies to the PC. And for this we can make use of the virtual COM port.
Right? Because, when you connect the board to your PC you can see that the
virtual COM port is already enumerated. So, that's because, the virtual COM
port support is there on the board itself. And I already explained that the
USART2 peripheral is actually used for virtual COM port support, right? on
the board.
So, that's why for this path we need not to use any USB to UART converter
hardware at all. So, just you need to connect your Nucleo board to the PC,
that's it. So, this path we can use it for sending bootloader commands and
receiving bootloader replies. We can also use USART3 peripheral as a
debug port. To get debug prints from the bootloader. Because, in the early
development you can put some printf statements OK, in your code to see what
exactly going on OK, when you call different functions. So, all those debug
prints we can divert on this path to the PC. So, that's why it is just a
unidirectional. So, bootloader to PC it will dump all the debug messages if
you want. And for this path of course, you need USB to UART converter
hardware in order to push data to the PC from the Nucleo board. And during
our custom bootloader development you will come to know that OK, I almost
don't use this path. So, I'll make use of only this path. So, that means using of
USB to UART hardware is optional for this course. So, if you don't have it
and if you think you don't need it in future then, you need not to buy it. Great.
So, that's about our bootloader loaders transport protocol. That means, a
protocol used to transport data from a PC to bootloader as well as from
bootloader to PC. And in ST's case, it's bootloader support various other
transport protocols like USB, SPI, I2C etc. . But, for our case we just support
this. And of course, you can extend this custom bootloader to support other
transport protocols.
BOOTLOADER CODE
PLACEMENT
So, in this bootloader project. So, you know that we already have our ROM
memory of 30 kilobytes. So, I'm considering ST's STM32F446RE
microcontroller and the ST guys have already stored the ST bootloader in
that memory area. We cannot remove that. So, now the custom bootloader
what we are going to write. We will place that bootloader into the main flash
memory, that is user flash in the sector 0 and 1. So, we will use first 32
kilobytes of the main flash memory, that is 2 sectors.
Sector 0 and sector 1. In order to store our bootloader. And then Sector-2 to
Sector-7 we can utilize for storing our user application. So, this is a table
which I showed you in the beginning, right? Here, is ST's bootloader and we
will use first two sectors in order to store the bootloader. OK, ours custom
bootloader. So, that means the base address of our bootloader will be this
address. Isn't it? And the base address of our user application placement will
be this one. So, just take a note of these addresses because we will be using
this While flashing boot loader onto the microcontroller as well as while
downloading user application into the main flash memory.
BOOTLOADER
SUPPORTED COMMANDS
So, in this project let's explore about some of the supported commands of our
bootloader. So, the bootloader supports all this commands, which are sent by
the host. So, let's go one by one. So, the host sends all this commands and the
bootloader replies according to that command, and here I have given some
notes. So, I have also attached this document and you can download and
read it. For example, let's say host sends this command that is bootloader get
to version command (BL_GET_VER) . So, this is a command code which is
sent by the host for this command and the bootloader replies the version
number of the bootloader which is of 1 byte. So, this command is used to
read the bootloader version from the microcontroller. Suppose, if the host
sends let's say bootloader GET_HELP command, then the bootloader returns
all the supported command codes. So, all the command codes or commands
need not be supported by the bootloader. So, it just replies. What are the
commands really implemented? And let say, if hosts sends CID then it returns
chip identification number of the microcontroller. The GET_RDP_STATUS.
So, that means this command is actually used by the host to read the Flash
Read Protection level.
Suppose, if host wants a bootloader to jump to some specified address, then
it will use the command go to address and the host will send this command
code along with the address. And bootloader jumps to that address. And
FLASH_ERASE command. The FLASH_ERASE command will be used to
erase the flash memory either by sector wise or mass erase. So, this
command is used to mass erase or sector erase of the user flash. And the host
can use the MEM_WRITE command in order to program the flash. Or it
wants to write some memories of the microcontroller whether it could be
SRAM or user flash. For example, if we want to program the microcontroller
by sending the binary file from the host OK then we can use this command,
either we can program the binaries on the SRAM or on the user flash. So, the
host to will use this command for that purpose in order to program the
microcontroller. ENDIS_RW_PROTECT. So, this command is actually used
to enable or disable read/write protection on different sectors of the user of
flash. And also the memory read command and the OTP read command. So,
all these are not implemented and I have left it has a exercise for the student.
So, once you understand all these commands and its implementation, it will
be very easy for you to implement this. Don't worry about that. Great.
So,these are the supported commands of the bootloader, and in the next
project, so explore about the packet format to send these commands over
UART interface.
HOST BOOTLOADER
COMMUNICATION
let's talk about the Host and bootloader communication in our custom
bootloader design. So, Host in our case is PC or it can be another micro-
controller, no problem. So, but we will use PC as our host and the target is
of course the microcontroller which is running the bootloader. And this is
how the host and bootloader communicate with each other. So, as soon as
you reset OK your micro-controller, let's say you reset the microcontroller
here. So, the microcontroller will now do lots of initializations like clock
initialization, UART initialization and it hangs at one point waiting for the
data. OK from the host, or waiting for the command packet from the host on
UART. So, it will just hang on UART reading function. So, than OK, when
the host sends the command packet. So, we will see what is the format of the
command packet later. So, once host sends the command packet the
bootloader receives that packet OK, and decodes it. And it first checks for
the byte integrity. That means, the CRC of the received packet is verified. We
actually use 32bit CRC engine here. In order to calculate the CRC and verify
the CRC of the transmitted bytes here So, that will see during our
implementation.
So, if the CRC is good it will send ACK plus one byte length field, and if the
CRC fails then it sends NACK. So, here 2 bytes are actually sent. The first
byte is as I said it is NACK or ACK. And the second byte is, So, if the CRC
verification is success, then the second byte will be how many bytes the
bootloader is going to send in its next reply That is replied reply to the
command. So, the second field actually contains how many bytes of data, the
bootloader is going to send next. Then it executes the command and then it
sends the reply back to the host. For example, let's say host sends the
command Get chip ID. That is get chip identification number. So, if the CRC
is verified then it sends the ACK And then, in the second byte it will include
how many bytes it is going to send next. So, then it execute some instructions
to read the chip ID from the microcontroller, and then it you know sends the
reply. So, let's say in this case chip ID will be of 2 bytes. It sends 2 bytes
here. So, how many bytes it is going to send here is already in form to the
host. OK in this field. So, that's how the host and bootloader communicates
with each other, and this is common for all the commands which we have
used in this course. Fine, and this is the design followed in this course and if
you think you have any better design of course, you can try that
implementation by changing the code. No problem with that. So, this is the
host bootloader communication method, I followed for this bootloader
implementation. All right. So, now in the next project we have to understand
what exactly is a command packet, which is going to come from the host.
BOOTLOADER PROJECT
CREATION
So, from this project on words let's get started with our development step by
step and you can use either Keil or eclipse IDE for this code development.
And we have to first create a project for our bootloader and we have to
include all the peripheral drivers, OK which we may need for this task. Let
me click on new project and here go to the board selector and here select the
type of the board OK, microcontroller family and in my case it is this
microcontroller, I'm going to double click on that. So, now let's select the
project location. I will do it in the keil workspace which we created and
here just create another folder with your board name or micro-controller
name And just select that path, Just click on Open. So, I just selected this path
and here let's create a project called bootloader OK STM32F446. So, now
select the Toolchain as MDK v5 and that's it. And here, as you know you
have to select only copy only the necessary library file. Great. So, now click
OK, and here don't change any of these pins. So, don't change anything. We
will add first the UART peripheral, because that is our transport protocol.
Isn't it? All right. So, now let's add UART peripherals to our project. So, we
are using 2 USART peripherals. Right? So, one is a USART2 and another
one is USART3. So, USART2 is actually used to communicate with a boot
loader by sending commands and receiving replies from the host. Right? And
the USART3 is for debug print message purpose. Great. Now, let's first add
USART2. So, click over here and select the Asynchronous mode. So, that
means we are actually using UART2 OK, not USART2. So, that is in the
Asynchronous mode. So, now the hardware flow control. So, let it be
disabled and the when you add that OK, you will see here 2 pins PA2 and
PA3. Isn't it? So, those actually go and connect to your ST-LINK hardware.
And that will actually enumerate as a virtual COM in your PC. Right? So,
your PC actually communicates over PA2 and PA3. So, now what will do is
will add USART3 OK, again select the asynchronous mode and let it be
disabled this one the hardware flow control. And when I select that you can
see here 2 more pins got added. Isn't it? So, here it is USART3_RX and
USART3_TX. So, now what you do is. You just hold your control key OK,
left control key press and hold it and then click on this one and you can see it
shows an alternate pin for this pin. Right? And for this also So, what will do
is, we will move these pin to here PC10 and PC11. This pin PB10, I can
move it to PC10.
This means, that OK both are having the same functionality. I can bring out
USART3_TX functionality on PB10 or PC10. And I can bring out the
USART3_RX functionality on PC5 or PC11. So, let's move that. So, now
what I will do is, I will just press and hold my left control button click over
here drag and drop here. And for this also and drag and drop here. Grate. So,
now we added our USART peripheral successfully and now let's add the
CRC engine. So, let's select this activated box. So, what else do we need.
Let's check in the RCC. So, the high speed external clock is disabled. That's
We don't need external clock in this project. So, don't change anything here.
And yeah, that's it. And anyway the GPIOs and other things are already
selected by default. over these pin positions. Okay. Great. So, now what
will do is, will go to the clock configuration and here the SYSCLK is 84
MHz and we get the HCLK as 84 MHz, that is more than sufficient for this
project and you are USART engine may be hanging on APB1 or maybe on
APB2 bus and that gets 42 and 84 MHz. So, that's more than sufficient. So,
don't change this configuration. So, let it be. And here it is generate code.
Yes, click on open project. Great. So, your project is ready now. So, with all
the peripheral drivers. So, now you should use this to add all your
bootloader specific code which I'm going to discuss in this course. So, now
here there is a system file, here all the drivers This is the application layer
where you write your bootloader code and this is a startup file. Fine. So, let's
do some debug related settings here. So, let's choose the debugger, It is ST-
LINK debugger it's already select it OK, select just verify code download,
download to flash options here and click OK will see whether it builds or
not. Great.
So, it compiled all the files of the project. Right? So, drivers system files,
startup files, the application theirs main. c everything compiled successfully.
And let's download. Yes, the download is successful and let's see whether
we are able to debug or not. Fine, the code is reaching main. So, great. So,
everything looks fine. Great. So, for the Keil also you created the bootloader
project successfully and we have to use this project in order to develop all
our bootloader related code, then we will test it. All right. So, now let me
show you the procedure to create the same project for STM32 system
workbench. All right. So, this will be helpful for those who are using STM32
system workbench, and if you are not using the software then you can skip
this. So, the thing is after all you are CubeMX settings. Go to project and in
the settings, in the project location you have to give the path of the workspace
you just created in the earlier videos. Right? So, this is our workspace. Isn't
it? So, I will just give that in the project location and give the project name,
the same name we can give the bootloader_STM32F446xx or you can give
your micro-controller name here and here Toolchain select SW4STM32. And
in the code generator of course you have to select the copy only necessary
library files, and that's it. You are done. Let's click OK and after that, Just go
to project and generate code. I hope you have done all the peripheral
selection as we did in the Keil and then generated the code. And now let's
click on Open Project and this will launch the system workbench software.
Now, it is asking you which Worldspace it should launch. So,as I said this is
our workspace, isn't it? And this is our workspace, and here it is the
bootloader project is already created. All right. So, just give this path here or
your workspace path, and now click Now this software is launching that
workspace and it will import the project which is created. So, here we can
see that, this is a project we created and there are some old projects I
created. So, I will just delete them so let me just delete all these things.
These are my old projects. So, now this is a project we created by using the
CubeMX software and this software will automatically import that project
into this workspace. So, now if you go to this path you can see here this is
our workspace files, and this is a project we created. Since, we launch this
workspace. So, this project will automatically get imported into the
workspace. So, that's what happened here. Okay. Great. So, now here we can
see all the required files, this is a main. c and now in order to compile this
project just select your project and click on this hammer icon. So,it will just
build your project. Great. So, the project is build successfully, and here we
can see that this build process will generate the elf as well as bin files of the
project. So, now if you go to the project this is a project and if you go to
debug, and here we can see it has created . bin as well as . elf. Unlike Keil
which generates only the elf format. So, in Keil it doesn't generate bin file by
default. So, now in this course I'll be using Kiel as my primary IDE to
develop all the codes and I'll be using main. c of this bootloader project in
order to put all the bootloader related codes, and if you are using this IDE
and you can also do the same along with main so you can just use this IDE
and this main. c in order to put all those bootloader codes and to test on the
hardware. So, you should be able to do that. And as we make a progress in
this course I will also show you how you can download code into the target
as well. OK, by using this software. So, for you have just installed this
software, you and you have created a bootloader project and you should be
able to build it successfully. And in the later videos I'll show you how you
can test a small piece of code on the hardware as well.
BOOTLOADER PROJECT
EXPLORATION PART1
So, now let's spend some time to understand this code what the CubeMX
software has generated for us. As I said this is your application layer, Just
open the main. c and here you will see the main function of the project, and
lots of initialization functions are already called from this function In this
function you can see here Init functions they are actually used to configure the
peripherals. So, how many peripherals were using in this project? So,
GPIOs, 2 UARTs That is UART2 and UART3 and we are using CRC engine.
So, that's why the CubeMX has inserted 4 Init functions for us. So, if you just
right click over it and if you just click on go to definition, it will take you to
the implementation of that. So, that is also implemented in the main. c file and
here you'll see that they actually use the Init function, the HAL_Init function in
order to initialize the peripheral. In this case the peripheral GPIO. So, that's
why they have used HAL_GPIO_Init. And here we are enabling the clocks
for the various GPIO ports we are going to use. For example, So, this is a
initialization for the button and this is the GPIO initialization for the LED like
that.
And again let's go back to our main function and here next is UART2
initialization. So, if you go to UART2 initialization. So, this is a small
function which initializes the UART2 peripheral, that's it. So, it just use the
Init function of the peripheral like HAL_UART_Init and it uses all this
configurations like the BaudRate will be this much, and this is a Word length
of the UART. And other configuration detail. And similarly, the CubeMX
software has also added MX_USART3 UART_Init. This is for the USART3.
Grate, and you can also see here that there is one configuration function
System Clock configuration. So, this configuration function is used to setup
the microcontroller clock. As you know OK in the STM microcontroller are
you can take any other microcontroller from TI, or NXP, or Atmel. By default
MCU will be using the internal high speed RC oscillator. OK, as it system
clock. And if you want to change that you can do it by using your code. So, in
this project as I shown you already the CubeMX is not using the HSI clock. It
is using the clock from the main PLL block of the microcontroller. Because, it
wants to setup the system clock as 84 MHz. So, this software by default
configures it for 84 MHz. But, for our work do we really need 84 MHz of
SYSCLK. May not be because, here we are using let's say UART for the
BaudRate of 11 52 00 for such a low BaudRate 84 MHz of SYSCLK may not
be required. So, you can even change this to HSI If you are really concerned
about the clock. So, for our work let it be at 84 megahertz. No big problem.
So, that's why, it has generated a code here to configure the PLL engine in
order to generate 84 MHz of clock. So, even you can comment this line no
problem. So, in that case the MCU will be using the HSI clock as the default
clock for the system clock. And here you can see a main HAL_Init. So, this is
to do some early initializations. For example, in this case they have written
comments here, The reset of all peripherals, Initializes the Flash interface
and the systick. And this function is used to initialize the HAl library; it must
be the first instruction to be executed in the main program. So , whenever you
are using you know ST's hardware abstraction layer, then in the main function
it's always better to include this as the first function.
And another important functionality of this function is actually to setup the
systick timer. OK . So, remember that ARM Cortex M processor core will
have a peripheral called systick timer and the ST's hardware abstraction
layer. That means, some of the peripheral drivers will use the timings of this
systick timer For example, here they are initialising the systick timer of the
ARM Cortex M processor. For example, if I take you into this function here
you can see that configure the systic timer to have interrupt for every one
milliseconds. So, that means in this project OK, there will be one
background interrupt will be generated that is from the systic timer for every
one millisecond. So,why it is used, It can be used for any purpose. So, even
you can use it in your application for any delay generation or for any other
purpose but by default that will be used by the peripheral drivers like UART
driver, or I2C drivers, SPI driver, in order to implement some of the blocking
calls. So, that's why the peripheral driver layer needs this timings that's why,
CubeMX software has added the systick timer initialization code in order to
generate interrupt for every one milliseconds.
COMMAND UART TESTING
So, I have just connected my Nucleo board and I will just write a code to
generate characters over the USART2 interface. So, that is actually
connected to the virtual COM port of the Nucleo board. So, now what I do is,
I will just write some code here and what I do is I just use the API. See we
are working with the UART peripheral, right? And to know the APIs that is
the driver exposed APIs, for the UART peripheral and you have to take a
look into the you are driver provided by ST For that, what I suggest do is, go
to this functions tab here and here expand STM32F4xx hal_uart. c For
example here it is And once you do that OK, So, you will see all the
functions which are supported by the UART driver or all the API exposed by
the UART driver. So, in this select appropriate function which matches with
your purpose. For example, here I want to send a string of data to my PC
over the UART peripheral. Right? So, for that I will select
HAL_UART_Transmit and you will also find another similar function
HAL_UART_Transmit_IT. IT means in a interrupt mode. So, I will just
select this one HAL_UART_Transmit. And if you just double click that OK,
it will take you to that function which is implemented in the driver file. So,
for example this is a hal. uart. c. So, don't change anything in this file. So,
this is written by the vendor. So, your not supposed to change this. And here
you can see that, you just have to pass a handle a peripheral handle and data,
and how many bytes you want to send, and the timeout information. So, great.
So, now lets use this function in our main. c it's is very simple. Right? All
right. So, now let's use this function to send some characters. So, I'll just
copy this function name into my code. And here in the main. c, let's call in the
while , and for this as you know you have to send the address of the handle
and let's select this handle UART2, handle UART2. We have to send the
address of that and the data also some strings, we have to send. So,let's say I
Now, let's declare some strings. Bootloader
So,I will just give \r and . Great. So, now let's use this pointer here OK, so
uint8_t *. So, we have typecast that it unsigned character pointer, and then the
size of that array and here you just give HAL_MAX_DELAY, the delay field.
Grate. So, this will print this string continuously onto our serial terminal. So,
if you want to give some delay so you can give that, and we know that
already the systick actually generates interrupts for every one milliseconds.
Right? So, you can use that to create delay. For example, let's say so let's
declare one variable. So, let's say current_tick is equal to there is a function
called HAL_GetTick. So, this is actually implemented in STM32F4xx_hal. c
and it returns a tick value, cuuren_tick value. This variable is actually a
global variable, which is incremented whenever the systick timer generates
interrupt. That means, this variable is incremented for every one
millisecond. So, if you want to know where exactly it is incremented. You
have to go and check the interrupt handler of the systick timer OK, which is
written in STM32F4xx_it. c, all the interrupt handler will be written here.
And if you browse through this file you will find one function systick handler
and this increments the tick So, if you just go inside that you will see ++ here.
Great. So, now let's see what this does. So, this actually gives a call back to
your application if you want. So, that's not required for us. All right. So, this
returns the current_tick value. So, now I can easily implement a small delay.
So, let's say while this current time is less than or equal to.
So,let's say current_tick + 500. So, this while loop will hang until the
current_tick value exceeds 500 more ticks OK, than the current_tick. Great.
So, this while loop will hang until whatever the value returned by this
GetTick increases by 500 over this value. Great, Now let's compile this.
Let's see whether it's compiles or not. Grate, it compiles no problem. And
let's download, So, let's reset the board and let's open a TeraTerm or Putty
whatever. So, here is a virtual COM port detected. When I connected my
board let me select that and So, it is sending some garbage value. Let's go to
set up and click on serial port and here select a Baud rate that is 115200,
click and here it is. We are indeed getting some prints over the virtual COM
port. So, which happens to be our USART2. Isn't it? All right. So, now let's
do the same UART testing using STM32 system workbench. And we will
also understand how we can flash the code onto the target using this
software. Great.
So, I have used the same code in the main. c what we just wrote in Keil, In
the while loop I have just returned these 3 lines of code and it is just printing
this some data string, which happens to be this string. Right? So, the same
code. Now, first let's build the project. So, I just click on this hammer icon
here and it has build the code, and now hope your board is connected to the
PC and right click on the project and select Debug As,here we are
debugging. So, Debug As select Ac6 STM32 C/C++ Application, Just click
on that and now it will flash and it will try to go to the debug mode. So, that's
why it is asking whether you want to change the perspective. Take a look into
these icons here. So, this is nothing but the C and C++ code editing
perspective, this is a debug perspective. So, it is asking your permission to
go into this debug perspective. So, click Yes and here you can see it went
into the debug perspective. And now here the code is not running, So, now
the courser is pointing to the first instruction of the main function. Now, let's
hit this resume button in order to execute the code. So, just click a resume
button and the code must be running. So, let's check in the TeraTerm whether
we are getting any output or not. All right. So, yes in the TeraTerm we are
indeed getting the strings. Right? Now, If you want to terminate, you can just
click over here that will terminate the debug process. And here you can see
the code is still running So, and you can come back to the C and C++
perspective by clicking over hit and again here again edit the code and you
can again test it. Now, sometime you just want to flash the code you don't
want to debug the code. So, in that case what you do is, you can just right
click on your project and click Run As instead of Debug As. So, Run As
Ac6 application and it will just you know flash that code on to your
hardware So, it won't it won't go into the debug mode. All right. It is just
flashing. Grate. So, that's how you can use this software in order to
download and debug code onto the target hardware. I hope you will do that
and all the remaining code editions What I do, Using Keil you can also do it
on this software and you can test it.
DEBUG UART TESTING
In the previous project you tested the working with the virtual COM port
using both Keil as well as using STM32 system workbench. Now, in this
video let's quickly test our debug UART also. So, now let's send some
characters over USART3 as well. For that, again we will extend this
code,here what I do is, I will just copy this and again I will paste here and
instead of UART2 I have to use UART3. Right? So, because we are using
two handles this handle for UART2 and this handle for UART3, and let me
just replace that with UART3. All right. And this field let it be some data.
That's fine. That is this one. Let it be OK, and everything is fine. So, now
what will do is. Now, we will use USB to UART converter hardware, and
we have to connect the USB to UART converter hardware to these pins. Isn't
it? What you have to do. OK, if you want to test this, If you have any
hardware than the USB, UARTs TX pin and you have to connect to PC11 or
the RX pin of the microcontroller and RX pin of the USB, UART converter
should be connected to TX pin So, that is PC10. So, let's do that. I have a
USB to UART converter hardware, and let's test this. So, I'll just compile
this first and I will connect my hardware. So, I just connected my hardware,
and again let's open the TeraTerm. All right. So, let's open the TeraTerm once
again. So, this time I will select the COM2 , which happens to be the prolific
USB to serial converter which I have connected
And let's set up the Baud rate as 115200 and just reset the board and you will
see the same output, but this time it is coming over the UART3 that's our
debug UART. Grate. So, that means we indeed successfully tested the
working of UART2 as well as UART3 peripherals. Replicate this exercise at
your desk. So, because you'll be using HAL_UART_Transmit API as well as
receive API in order to get the command from the host and to send a reply to
the host. OK, during our bootloader development. And if you have any other
board of a ST like a discovery or any other flavor of nucleo, than your UART
numbers may be different that you can easily find out in the CubeMX
software, when you actually select your board. And I hope you can reproduce
this at your board and let me know if you face any issues. And please make a
note that here, and while printing some strings we have to first declare the
character array or character pointer, then we have to store that array. Right?
Then we have to you know send that address through the UART_Transmit
API. That means, in order to print something with the format specifier, by
sending lots of arguments and to that format specifier just like you do using
printf of standard library. You can create your own a printf function. That I
will show you here I have implemented that. So, here is a code for that. So,
this is actually a function used to print formatted string to console, over the
UART interface So, this just behaves like your standard library printf where
it takes formatted input, as an argument and it uses C's va-list APIs, in order
to extract the argument from this pointer. It actually then extracts those
arguments format specifiers, details everything and it converts that into a
character array here, and then we use that character array to send it to over
the UART peripheral. You can just use this function like your printf function.
So, lets see how that can be done. So, here I have a enable this code only if
this macro is defined. So, let's define this macro. So, here I have defined that
macro enable this line to get debug messages over debug UART. So, let's say
this is defined, and in order to give support for these C's va-list APIs. So,
you have to include stdarg. h. So, let's add that here. So, now so I will just
remove these two lines of code And we will also add the function prototype
of this function here. In the private function prototype section. So, I'll just
add here. So, let me call it as static. Okay great. So, now what will do is
So,here I have used string length. So. for that let's add string. h. Fine. So,
now we are transmitting over the debug UART. So, for identification
purposes I have just used a macro D_UART. So, let's define this. let's say
let's define here, D_UART. # define D_UART means this handle. And of this
handle, And also let's define the virtual COM UART. So, that I call it as
C_UART That means because, we send bootloader commands over it. Isn't
it? So, that's why I will call it as C_UART. And I will define this as
UART2. So, in the UART calls you can use these macros instead of this
name. OK, great. So, now what I do is so here in the code. So, let's use this
printf message function. So, now let's use that print message here, print
message function current_tick value. Let's print the current_tick value is
equal to %d . And let's print this value. Great. So, now let's test this
application. Yes, it compiled successfully let's download and So, let's hit the
reset button and here it is. So, the TeraTerm is not showing properly that's
because of this carriage return is missing. So, let's say this is \r . So, now
let's test it OK, so let's download and let's hit reset and here it is. So, instead
of using HAL_UART_ Transmit data because that doesn't take the formatted
string. Isn't it? So, that's why will use our own printf function, that is nothing
but print message. So, instead of using HAL_UART_Transmit, you just use
this because this function will eventually use HAL_UART_Transmit inside
the function. And now, we will move to our next task I will see you in the
next project.
BOOTLOADER JUMPING
TO USER APPLICATION
PART1
So, in the previous project we created CubeMX project for our bootloader
work, and we actually included all these initialization functions. Actually, the
CubeMX software has automatically inserted lots of initialization functions
like HAL_Init GPIOs, and initialization code for UART as well as CRC and
also for the clock. Isn't it? So, we also tested the UART a working and our
Task2 is to test the section. In this task whenever we reset the micro-
controller the bootloader code which is stored in the sector 0 and 1 will run
first. And then the bootloader code will check the status of the user button.
So,if the user button is pressed that is yes, then that means if the user button is
pressed during reset of the microcontroller or board, then bootloader will
execute this function bootloader uart_read_data(). And if the user button is
not pressed during resetting of the board, then this path will be executed, that
is the bootloader will execute bootloader_jump_to_user_app(). So, that
means this is the place where bootloader will hand of control to the user
application,which is stored in the sector 3 onwards. OK of the flash memory.
This is the design in this custom bootloader that is whenever you reset the
microcontroller the bootloader code always runs first, and based on the user
button status. It will take a decision whether to continue in the bootloader
mode or give control to the user application. Great. Now, let's implement
this.
So, let's implement our Task 2 the moment we reset the board, the bootloader
actually runs first. And if we press the button then the bootloader should
continue in the bootloader mode and if the user doesn't press on board button,
than it should jump to the user application. Here, is a code to take that small
decision. So, here just take a lo So,here I am first reading the status of the
button and if the button is pressed, then in Nucleo board pressing means
making that GPIO to ground. In STM32F446 Nucleo OK, pressing a user
button means making that GPIO to ground. So, this may not be true on all the
Nucleos. Suppose, if you consider discovery board from ST, pressing a
button means making it high. High voltage. So, there may be differences in the
board so you have to check how your button is connected between the VCC
and the ground. And you should know, what exactly happens when you press
the button whether it drives it to 0, or whether it drives it to high voltage OK,
Nucleo it drives it to ground.
So, that's why if that button reads 0 then the button is pressed. Then, I call the
function bootloader uart_read_data(). That means, bootloader continues in
the bootloader mode and it tries to read the command in the boot loader
uart_read_data function OK, that implementation will see later. And if you
don't press it, So, then what happens is bootloader should jump to the user
application. So, where user application is stored? As we discussed user
application is stored in the sector number 2. So, this is the base address of
the sector 2 and you have to jump over there and you have to execute the
application. So, that means that now to test this we need a user application.
Isn't it? So, this is our bootloader code, right? So, the bootloader has to jump
to another application when the button is not pressed. That means, we have to
write a small another application lets say which blinks some on board
LED's, and the bootloader should give control to that application, if the
button is not pressed. Now, let's include this code segment into our
bootloader project. All right. So, I'm coming back to my bootloader project.
So, in the previous project we tested this print message function. Isn't it?
Now, let's remove that OK, so because we don't need that anyway we tested
it and So, now let me jump to some bigger IDEs. Now, let me use code block
for that to describe the source code. This is the same code OK, but I just
opened in the code block. So, now I am in the main now, main of the
bootloader and here. So, let me remove all this code So, this is still main.
So, I just remove those code and let me put that decision making code here,
OK the main. Grate. All right. So, now we have to implement these two
functions. Isn't it? So, now let's implement that here, Let's copy this and let
me just write here, void, let's keep it empty. No problem. OK, for a time
being and let's copy this also. So, now we have to add the function
prototypes of these two functions. So, for that let me create a main. h first.
Now, let me go to the Keil IDE and here we actually have a main. h.
The CubeMX software has already added a main dot. h So, just open that OK
so here it is. So, now let me see where exactly it is present. So. here in the
Inc folder. So, now let me just drag that and add it to my Core block IDE.
So, now let's add the function prototype let me add it in the header. So, now
let me add it here prototypes. Here it is. So, now let's add this is also. Great.
So, now let's go back to the Keil and let's see everything is fine or not. Yes,
we indeed the code is present here, and let's compile. All right. The
compilation is proper and let's download this code OK and we will keep 2
break points here. Debug this code and let me hit run, and Yes, it comes to
button is not pressed, that's true. Because the button is not pressed. So, now
let me keep a break point here and reset the board and this time I'll press and
hold the user button of the board and let me hit run and let me remove this.
And yes, it is now showing the button is pressed. OK, great. So, it's working.
Fine. So, now in the next project we will add these two function contents. In
the next project, first we will implement how to jump to user application
which is present in another sector of the flash.
BOOTLOADER JUMPING
TO USER APPLICATION
PART2
So, in the previous project you implemented a button check. Right? So, and
we tested it successfully. So, in the Keil actually this B1_GPIO_Port is a
macro which is defined in main. h So, this is automatically generated by the
CubeMX and it is pointing to GPIO Port C in my case. For your case it may
be different So, you have to verify where exactly the button is connected and
the GPIO pin is the button pin is actually pin number 13. That is GPIO Port C
pin number 13 is a button in my case. Please, verify these details. Otherwise,
you may not be able to execute this successfully. Great. Now, let's implement
the user application. Because, we want to jump to user application, and for
that we should have one user application. Isn't it? So, let's create that. So, for
that again I'm using the CubeMX software for that. So, let me just create a
new project. Great. So, you know the project creation step I will quickly
finish this and I'll come back.
All right. So, I just created a project. So, now let me go to settings first. And
let me give a project name so let me save here, no problem. I will call it as a
User_app. I'll just say User_app Stm32 F 446x. And it is created under Keil
workspace Nucleo-F446RE. That's fine. So, let's select MDK5 and click All
right. So, now we will use this application to generate some button
interrupts. And whenever I press a button you know the interrupt must be
generated and it should you know toggle some LED. So, you will made this
interrupt driven because, we want to really know whenever our bootloader
jumps to the user application all the interrupt things a really works or not.
Great, and we will also include UART peripheral in this application. Which
prints to the console saying that the user application is indeed running.
So, this ensures that a bootloaders hand of to the user application is proper.
So, first let's add the GPIO button interrupt functionality for that you have to
go to configuration. Remember that, by default CubeMX software will
configure all the board related peripherals like button LEDs. For example,
here you are saying you know the LED if you just put a mouse over it, it
already shows that it is in the output mode. And here is a button where is
button. Here is a button, and if you just keep a pointer over it, it just shows
that PC13 is already in the GPIO EXTI13 mode. That is it is actually in a
interrupt mode. So, you can see the status of all the configured GPIOs in the
configuration tab. And here, you will see and just click on GPIO and it will
show you 2 options here. So, PA5 is port number a GPIO Port number A pin
number 5, and it is connected to LED. And it is already in the output push
pull mode. You can see here you can see it's level, it's mode, it's pull-up pull-
down resister status. and the user label etc. .
And if you just click on PC13 it is our button and the button is in the mode
External interrupt. That means, button is configured to generate External
interrupt whenever the falling edge is triggered. So, in my board as I said
whenever I press button the pin is actually pulled to ground. So, that means
it's the falling edge. Great. So, now let's click apply. So, now let's click on
this NVIC OK, to configure the interrupt handler as well as in order to enable
the interrupt, for the button, for the GPIO. And here just first click on NVIC
and here you can see that the EXTI line interrupts. So, under the EXTI line
interrupts here just look at the option, EXTI line interrupts and under enable
it is actually unchecked. So, our GPIO interrupt actually comes over the
EXTI line of the EXTI block of the ST's microcontroller. So, that means we
have to enable the interrupt for this in order to receive the intercept from the
GPIO pins. So, now lets enable it and need not to touch these priority settings
let it be, because we don't have much interrupts in this case and we have only
one interrupt. Isn't it? So, and in the code generation it is asking you to
whether to generate the IRQ handler or not.
So, let's let's check this option generate IRQ handler. All right. So, now click
apply. Great. So, now let's go to clock configuration and it is at 84 MHz the
system clock. That's fine. OK, we need not to change it. And let's add some
peripherals, let's say let's add again the USART2 peripheral. So, that we
write something on to the console. Over the virtual COM because USART2
comes over virtual COM you already know this, and that's it. So, now let's
generate code it is generating the source code. All right. So, now click Open
Project So,the project is opened now. So, now let's go to application user
click main, and here it is the code is generated the main function has lots of
initialization function. This time only two initialization function according to
our peripheral selection and in the IT. c, you will find a systick handler OK,
as well as the IRQ händler for the EXTI line. So, whenever I press button the
control actually comes over here.
BOOTLOADER JUMPING
TO USER APPLICATION
PART3
So, let's just download the code, and let's see whether pressing of button
triggers the interrupt or not. So, let's go to main. c sorry. Let's go to it. c and
let's keep a break point here. And just run first and let's hit a button and here
it is indeed it comes, right? Great. So, now let's toggle one LED here. So, in
the IRQ handler let's just add one LED toggling application. So, I just added
these two lines. So here, whenever the ISR executes it toggles the LED,
which is actually connected to GPIO a port number 5. So, this is just a small
toggling code. And after that, we will also print something in the main. So, in
the main function, So, I'm just added these lines to print some data fine. So,
now let's compile this. All right. So, now let's download and just reset the
board and let's check whether the data is coming or not. All right. So, Hello
From user application so I just initialized this character array with this string.
Great.
So, this is working. So, now this code has to be downloaded into Sector 2.
Isn't it? That is this is the base address. Right? So, let's do that. So, for that
go to your Keil IDE and click here, right click on this and select options for
target. And here in the target mention the desired based address. So, here I
have already mentioned 0x0800800. So, that's the base address of the section
2 of the Flash. Okay. And then come here in the linker, So, just uncheck this
and change the address to your desired value and then check this. And then
click OK and then rebuild the code. So, let's rebuild the code. All right. The
code is rebuilt. Great. So, now if we download the code then we should find
our user application here. Right? in the base of sector 2. So, what I do is, I
first I actually completely erased the chip. So, you need not to do this, just
watch this. That is actually not required but for the demonstration purpose I
will just show that Yes, this is a ST-LINK utility provided by ST. So, this is
available for Windows online. You can download it and you can play with
the board. I go to target and I just connect to the board. And I will just select
let's say the base address of the flash that is this one. So, some data is there.
Right? So, this is actually our bootloader code which we flashed earlier. So,
now what I do is, I will just go and erase the chip. So, this will do a full chip
erase. That means, all my flash content will be gone.
I mean the flash contents from sector 0 to sector 7. So, let's click yes. Now, it
is erasing the full flash. So, now you can see the flash actually doesn't have
any data. So, all are FF's. Right? You can check any sector. For example, let's
check this sector 4 0 0. So, that is also FF and C00. So, like that 800. So,
this sector is also erased. So, now let's disconnect and let's flash our code
that is the user application. So, now it is flashed. Now, go here and connect
it back, and here you see in the 8000 address you are seeing the contents.
Right? So, that means our user application indeed stored at this section. That
is section number 2 which starts from here. So, you can still see the very first
section that is section 0 still having FF's. So, because here we are going to
store the bootloader. So, that means we successfully stored our user
application into the flash, and our next job is store a bootloader then make it
to jump to the user application, which we have just stored that i will take up
in the next project.
FLASH CODE PLACEMENT
USING OPENSTM32
SYSTEM WORKBENCH
So, in this project let's see how we can place the code in to different sectors
of the user flash using STM32 system workbench. Now, it's very very simple.
So, first we have to search for the linker script that we call as some time it is
mentioned Mem. ld. So,we have to search for ld file in the project. So, here
we can see in this project there is this . ld file. So, this file will be used by
the linker in order to understand the code placement onto the user flash
memory. So, through this file the linker will come to know about what are the
start and end addresses of various memories of the microcontroller and other
details it will fetch from this file. Now, we have to edit this file. So, in the
project directory, So, you can find this file over here. So, now let's just open
that file, let's open that in notepad. So, or you can also open in the IDE, here
it is. And here change this address. So, this is actually pointing to the 0 th
sector of the User class. So, now let's make it as 8000. So, now it points to
the 0 1 2, sector 2 of the user flash memory. So, code placement will start
from there on words. sector 2 on words. Great. So, now let's save this, and
so once you change the linker script remember that you had to clean the
project and then you had to build again. Great. Now, let's wait until it
finishes building. Fine.
So, now it has successfully built the code, and now what will do is, we will
first erase the chip. So, now this is just for your demonstration. So, I will go
to target here and I will click on erase chip. So, it is erasing the chip. So, if
you can't use ST-Link utility then you can use this option of this IDE itself. It
is very helpful. Great. So, now here we can see it has erased the sectors 0
through 7. Right? Now, what you do is, go to right click on the project and go
to target and then click on program chip. Now, click the elf format of this
application and then click on And now it is programming the chip, and it has
programmed. Now, how to verify this. I mean, how to verify whether it
indeed programmed this sector of the user flash. Now, let's debug the code.
So, now let's debug the code, let's right click, click on Debug As you can
select here. And now it is going into the debug perspective. And now let's go
to window, let's go to show view, and then go to memory browser, and here
type the address you wanted and click enter. Here it is. It indeed programmed
this sector of the flash memory. Isn't it? So, now let's go back to our C and
C++ perspective, and let's terminate this. So, that's how you can do the code
placement into different sectors of the user flash using this software.
VECTOR TABLE OFFSET
REGISTERVTOR USE
CASE
And in this project, let's discuss about the vector table relocation feature of
the ARM Cortex M processor. And this is very important to understand in our
bootloader development. What we have done so for is so we have a flash
memory. Isn't it? So, let's say this is a flash memory. So, this is a user Flash.
Let's say this is the start address. OK, let's say this is 0x0800-0000. Right?
And here, we have stored in the 2 sectors, we have stored the bootloader
code. So, the bootloader code is leaving here. And the starting from this
address onwards there will be vector table. So, vector table of bootloader.
Right? So, vector table of bootloader. And we also have stored user
application somewhere here, 0 x0800-8000. So, this happens to be the base
address of the sector 2. Isn't it? And here our user application is stored
user_app, right? And from this address on words, there will be a vector table
which happens to be the vector table of user application. Isn't it? user
application. So, there are 2 vector tables. One is starting from this address
and another one is starting from this address. Right? And once the bootloader
runs, if the user button is not pressed then what happens. So, whenever you
reset the microcontroller the bootloader will run first. Isn't it? And then if the
user or button is not pressed during the powering up of the board, the
bootloader will give control to the user application. Right? So, how it does?
It actually calls the reset handler of the user application.
That's how the user application will get triggered. Isn't it? This is actually
nothing but calling the reset handler address of the user application. Now,
once the user application starts running, if user application wants to handle
any interrupt, then what happens. Remember that, by default the ARM Cortex
MX processor assumes that the vector table is present at the location
0x0000-0000 which is aliased to this address by default. Isn't it? This you
already know. So, that's why the ARM Cortex MX processor will always
thinks that the vector table is told at this address. Right? But, this is not the
vector table of user application. This is a vector table of bootloader. So,
that's why once the user application starts running you have to tell the ARM
controller that. So, use this vector table instead of this one. Right? So, that
we can tell to the ARM controller by using one of the register of the ARM
controller that is VTOR (vector table relocation register). So, this value is by
default 0. So, that's why ARM Cortex M processor always try to use the
vector table addresses present at this location. Right? So, now once you jump
to the user application in the reset handler of the user application, you have
to change the content of this register. So, you have to put the base address of
this sector, that is sector 2 that is this address. So, you have to put 0x0800-
8000 over here. So, that actually ensures that whenever you use whenever
you run the user application in any interrupt triggers, then the vector address
is present in this vector table will be used instead of this one. Great. So,
that's about the VTOR. That is vector table reallocation register and I will
show you how to modify and where to modify this VTOR register. Once you
come to the reset handler of the user application. I will see you in the next
project.
BOOTLOADER JUMPING
TO USER APPLICATION
PART4
So, in this project as I said we are going to implement
bootloader_jump_to_user_app function. Right? So, here I have written a
small function OK, which I will explain you line by line. First, this is a code
to jump to user application, Here we are assuming
FLASH_SECTOR2_BASE_ADDRESS is where the user application is
stored. So, now before doing this OK, so let me open my ST-LINK utility and
observe the 8000 address. Here it is. So, this function I just added to the
main. c and we will just add this prototype also in the main. h All right. So,
it's already added no problem. So, this is the first line is just a function
pointer to hold the address of the reset handler of the user application.
Because, the user application also has its own reset handler and lots of IRQ
handlers. Isn't it? So, this is a pointer to hold that reset handler. After that, I'm
printing something. That's fine. And here, after that, configure the MSP by
reading the value from the base address of the sector 2. Because, as you may
be knowing the very first address will be holding the value of the main stack
pointer. Right? So,this we also discussed when we were discussing about the
resets sequence of the processor. Right? So, in that case the base address
was the 0x0800-0000. But, this time this is a base address of the user
application.
So, now this value is nothing but the MSP value. So, that's why we have to
read this value from this location, so that's what happening here. This is a
base address so which is this one and we are reading it's content that is a 32
bit value. Here, this is a dereferencing of this pointer which is actually of
unsigned 32 data type. And we are storing that into msp_value, than we are
using the CMSIS API set_MSP to set the msp_value. And after that, So, this
address is holding the MSP value and the next address that is 800 plus 4 will
be holding the value of the reset handler. Isn't it? of the user application. That
address we are fetching into this variable. Again, I'm just doing +4 here and
dereferencing that pointer and storing the address of the reset handler here.
And then I'm initializing a function pointer with the reset handler. Then I am
all set to jump to that address, which is initialized here. That is I'm jumping
to reset handler of the user application. This is a step where the bootloader is
handling of control to the user application. So, user application will never
return from there to here. So, what happens is this address will be now put
into the PC and the processor will start executing reset handler of the user
application. So, what is the reset handler of the user application? So, let's
have a lo So, this is our user application and the reset handler will be found
in in the startup file. So, here it is. This is a reset handler. Right? So, the
control will come to here and from here it will call SystemInit. And it
initializes various things let it do no problem. As we discussed in our theory
part OK, we have to shift the vector table to the base address of the user
application, where exactly it is stored. And so, that's why, we have to change
this Flash_Base here because that is initialized to this address, which is not
true. So, we have to move our vector table. Right? So,that's why this vector
table offset is given So, you have to make use of that.
So, let's see what is the value of that. It's value is 0. So, this value must be
multiple of 200. So, now what is the offset, Offset is how much. So, this is
the offset. Right? This is a offset 8000. So, let's do that 8000. Great. So, now
we actually change the code, So, that's why we have again load the project
into the target. So, let's build once again So, let's download. So, because the
ST-LINK is open. So, we have to disconnect that. All right. Fine. Now ,the
application is stored we can just verify that, here it is. And what about 8000
it is still FF . Because, we have not stored any bootloader code. Right? Now,
let's disconnect this. All right. So, now let's go back to our bootloader
project So, this is our bootloader project and something got changed. Yes.
And in the main. c OK, here we have our code ready and this flash Sector
base address is not defined So, let's define that in the main. h. So, I'll just go
to code block and I would do that. So. So, let's define some where here. 0
x0800-8000. Right? # define since it is unsigned address, you can new here.
Fine. Grate.
So, now let's see whether we are able to compile our bootloader code. So,
let's compile. So, it's compiling. All right. So, project is built successfully
So,there is still 1 warning that's So, now let's download the code. All right.
So, now let's go back to our ST-LINK to see whether indeed code is there or
not. Yes, it's there. Now, you can see the bootloader code over here. So,
because this is the base address and if you check the 80000 still our user
application code is also there So, no corruption. That's fine. So, now let's
disconnect this. So, now what have to do is, we have to reset our board.
Right? and before that So, now let me open the TeraTerm and let's select the
COM3 here, that is my virtual COM. Let's Click OK and let's hit a reset of
the board. Let's change this Baud rate and let's click OK and let's hit the
reset button. Yes, here we can see indeed our user application is running. So,
I will just to reset once again and my interrupt is also working. And if you
press the user button, during powering up of the board then you don't see this
output. So, let me show that. I'll just clear the screen, and I'll hold the user
button, and then I will reset my board, and here you see the output is top.
That means, the bootloader didn't jump to the user application. So, now I
simply reset my board and the log continues. So, we can even debug this
application, what I do is, I go to the the bootloader course. So, this
bootloader code and I will simply debug this and let me keep a break point
here. OK, while we jump to the reset handler of the application and I will
just hit run. Yes, you can see here. It is now about to jump and once I and you
can see all the parameters here like reset handler address is D1 D9. Right? It
is showing here. Right? That you can verify in the memory window if you
want. let's say 0X08008000. Let me go for unsigned long and here it is 81D9
So, that's the address actually stored in the reset handler, and once you single
step here from this point the PC will be loaded with that address. Which the
debugger cannot able to trace. So, but the handing of is happened and here
you can see that the log is indeed coming. Here it is. Great. So, let me
summarize what we did in this project. So, what we did was we just
implemented a bootloader_jump_ to_user_application code,where we
extracted the msp_value from the FLASH_SECTOR2_BASE_ADDRESS
and then we initialized the stack pointer and then we extracted the reset
handler address from the FLASH_SECTOR2_BASE_ADDRESS +4, and
then you know initialized that to a function pointer here and we then
dereferenced the function pointer, in order to jump to the user application
which we have already stored in the flash sector2 base address. So, try this
at your desk and try to reproduce it and let me know if you face any issues.
And now in the next project, we will see how we can implement the
bootloader uart_read_data. Actually, it reads the command packet which
comes from the host application.
BOOTLOADER COMMAND
FORMAT
We will discuss more on this in the implementation part also, don't worry
about that. And in this project, Let's see what are the formats of the
commands that we send from the host to the bootloader. Here, is the
command packet format for the command bootloader get version. So, the host
sents this command in order to get the bootloader version. So, remember that
in our implementation in every command packet. The first byte will be the
length to follow field. And the second field, that is the second byte will be
the command code of that respective command, that is sent by the host to the
bootloader. And our project will also use CRC code of 4 bytes in order to
help bootloader to check the integrity of the bytes received from the host. So,
in this particular command that is PL get version, the total bytes of the packet
will be 6 bytes. So, 4+1+1+4 . Right? And the length to follow field will
contain the value 5, and the command code will be 0x51 for this command.
And the reply sent by the bootloader for this command will be 1 byte, that
will be the bootloader version number. Now, let's see another example. This
is the bootloader get help command. The goal of this command to extract all
the supported command codes from the bootloader.
I suggest you to refer to this document in order to understand, what each
command does. All right. And in this particular command the total bytes of
the packet will be again 6, and the length to follow field will contain the
value 5, and the command code will be 0x52 and bootloader reply will be
all the supported command codes. We will see more on this when we
implement this code in our implementation projects. And here is another
command get the chip identification number of the microcontroller. Again the
length to follow field will contain the value 5, and total bytes of the packet
will be 6 bytes, and bootloader reply will be 2 bytes for this particular
command. So, that is actually 2 bytes of microcontroller chip identification
number. Now, let's move forward and here is another command. So, this
command format is little different.
This command is bootloader go to address. All right. Now, let's explore that
command here, bootloader go to address. This command is used to jump
bootloader to specified address. So, host uses this command in order to
instruct the bootloader to jump to specified memory address, So, that memory
address will be sent by the host in this field memory address. So, the format
is the first 2 bytes will be reserved for length to follow and command code.
And the third field will be memory address of 4 bytes mentioned by the host,
and again the last 4 bytes will be the CRC. So, once bootloader receives this
command it will jump to the memory specified by the host. So, here the third
field will be base memory address that is 4 byte base address to jump. And
the total byte now will be 4+ 4+2 that is 10, and the length to follow will
contain the value 9. And the bootloader reply will be status whether it is
success or failure. The failure happens, if bootloader detects that the given
address is completely invalid. Because, the bootloader jumps to only those
memory locations for which it is allowed. So, if host mentions any invalid
address then the bootloader will simply reply saying, it's an invalid address
in the status field. So, we will see more on this as we make a progress, don't
worry about that. And now this is the command packet format to execute the
bootloader flash erase command. And the host uses this command in order to
instruct the bootloader to erase a particular sector or range of a specified
sectors. And the command format is very simple. The first 2 fields are as
before and the third field will be sector number. All right. So, in my
microcontroller there are 7 sectors, sorry there are 8 sectors from 0 to 7. So,
host will mention which sector needs to be erased in this field. So, the only
allowed values are this much 0 to 7. And how many sectors host wants to
erase? Starting from the sector number over here. So, that you can that host
can mention here. So, range is from 0 to 7. So, for example here, if I mention
something like 3 and 2 then host wants to erase sector 3 and 4. So, because
number of sectors to erase is 2. Isn't it? So, that's why, starting from 3
bootloader erases 2 sectors, 3 and 4. All right. And the status will be what
exactly is the status of that flash erase command. Whether it is a success or
failure. All right. So, that status code will be returned to the host. And the
next command is Memory write. Now, this is really important command
because, we use this command in order to program the binary into the
microcontroller flash memory. And there are 3 extra fields here. One is the
base memory address. That means, the host tells the bootloader start writing
from this base memory address and the payload length. That means, how
many bytes the host wants to write. So, that is mentioned in the payload
length, and then this field is the payload that is streams of bytes, which the
host wants to write on to the flash or RAM memory of the microcontroller.
So, you can use this command in order to write to any allowed memory
address. All right. So, it need not be only flash. It can be RAM, SRAM1 or
SRAM2 or any other allowed memories. So, here the payload length field is
1 byte. Isn't it? So, that means the maximum value of this field will be 255.
Isn't it? So, that's why the maximum bytes you can send in a payload field is
255 bytes. Isn't it? All right. So, here is a description. Base memory address
that is it's a 4 byte base address, from which bootloader should start writing
into the memory and payload length is number of bytes to write and payload
of course it's a stream of bytes to write. Fine. So, that's the format of the
BL_MEM_WRITE command. Now, let's move forward. All right. So, the
next command is bootloader Mem Read. That means, a memory read
command and this command is actually used to read the contents of the
memory back into the host. So, you can instruct the bootloader to read some
bytes from the memory address given in this field, and you have to mention
how many bytes you want to read in this field. So, that the bootloader can
read those amounts of memory contents and it can send it back to the host and
then host can write that into some text file or something to recreate the bin
file or something like that. So, this is not a much useful command, but you
know you can implement this command as a part of your bootloader
development and this command I have not implemented in this project. So, I
have left this as an exercise for the students and if you are interested you can
implement this. So, here the base memory address means 4 byte base address
from which data has to be read. And length is number of bytes to read and the
status will be I mean the reply from the bootloader will be status plus length
number of bytes. So, if status is 0 then it's a success, if status field is 1 than is
a failure. So, if status field is 1 then this is ignored and if the status field is 0
that means it's a success and this will contain length number of bytes. Great.
Now, let's move to the next command that is bootloader and enable read
write protect. So, this is very important command. This command is a very
simple command. So,which is used to put read and write protection on
different sectors of the flash memory. So, here in this field it's a one byte
field. Here, you have to mention the sector details or you have to encode the
sector details. So, sector numbers are encoded in 8 bits. For example, 0th bit
is sector 0. So, I like that. So, 7th bit is sector 7. So, if that bit is 1, then it
means protection is needed.
All right. All right. So, I just implemented all those handle functions over
here all are empty functions, and I also wrote a small comments to describe
those functions. Great. So, for a time being all those are empty functions and
as we make a progress in this course we will implement each and every
function. There are some functions which are left for the students to
implement. So, now let's see whether we are able to compile it successfully
or not. Now, let me go to my Keil and in the main . c we have implemented,
isn't it? So, here it is all those functions at the end. Fine. So, now let's
compile and this time all the errors should resolve. Yes, here it is 0 errors.
Great. So, now that means we have successfully added all the bootloader
handle functions and now we will implement each handle one by one.
BOOTLOADER COMMAND
HANDLING FLOWCHART
Now let's implement our very first helper function that is bootloader handle
get version command. So, before implementing this function let's go through
the some flowcharts to understand, how our bootloader code really behaves.
So, first when we reset the microcontroller the bootloader code actually runs
and it does all this peripheral and clock initializations like it initializes the
hardware abstraction library, it Initializes the GPIOs used, UARTs used, the
CRC engine used, and the clock. So, all these initializations will be done and
then it checks the status of the user button. So, if the user button is not pressed
it goes to the function bootloader_jump_ to_user_app() and it continues with
the user application.
And if the button is pressed during the reset, it goes to the bootloader
uart_read_data. Or you can also call it as read host command whatever. And
when it comes to bootloader_uart_ read_data() there we actually poll for the
UART data, isn't it? So, if you look at this function uart_read_data, it is
actually trapped in the while 1 loop. So, that's an in finite loop. Isn't it? And
here we first poll for the data, isn't it? To see 1 byte that is nothing but length
of the command packet. So, if that length byte is not received, then it
continues to poll the UART to get that first byte. If length byte is received,
then it reads length number of bytes. All right. That is other bytes of the
command packet and then that command will be decoded in the switch case.
Isn't it? And then handle function will be called. And when we go to the
handle function, what we do. We first check the CRC of the received
command packet. Because, that is really important if the CRC is bad, then we
send 'NACK" to the host. And then we finished the handler function, that
means we return from the handle function and we come back to the
bootloader uart_ read_data function again. And again we will start polling
for the bytes from the host. So, finish Handle_X_command means, returning
from the individual handle function. So, if the CRC is good, so we first send
the "ACK" and then we obtain the reply.
For example, if it is a get version command then we obtain the version. If it
is get chip identification number then we obtain the chip identification
number and then we send the reply back to the host. And after that, we return
from the Handle_X_command and then we again come back to the bootloader
uart_read_data function. That means, we poll for the uart data. So, this is the
flowchart used in this project implementation. So, if you look at the code,
when the command is decoded we go to the respective handle function. So,
let's say we go to this handle function and here we have to implement this
one. In the handle function this is the handle function, and here we have to
first check for the CRC and if it is good you have to send "ACK", then obtain
the reply and then send the reply. If it is if the CRC is bad then send "NACK"
and return from this function and you go back to the bootloader
uart_read_data function.
BLGETVER HANDLE
FUNCTION
IMPLEMENTATION
First, when we come to the handle function we also have the rx_buffer.
Right? And the first thing what we do here is, we check the CRC in this
function. And if this function returns 0, that means the CRC is good. If this
function returns non-zero value, then that means the CRC is bad. So, if this
function returns non zero then we come here. That means, the CRC
verification has failed. That's why we send NACK. So, this is a function
which sends NACK to the host. But, if the CRC is good, then the first thing
what we do is we send the ack and then we obtain the reply here.
So, get the bootloader version. So, all this function implementations we will
see later don't worry about that. So, here we obtain the reply, and then here
we send the reply back to the host. So, sending reply so is done by this
function bootloader uart_write_data. So, this actually writes data from
bootloader to the host. This one sends Ack, and this one sends nack, and this
one ratifies the CRC. Now, in the next project we will see the
implementation of verification of CRC, sending Ack, sending bootloader data
to the host, and also we will see how bootloader send NACK is
implemented. So, we'll be reusing all this functions in all our bootloader
handle command functions.
BOOTLOADER ACKNACK
IMPLEMENTATION
Now first let's explore how sending NACK and sending ACK is
implemented. So, let's go to our code block project and here is a function
which implements bootloader_send_ack and bootloader_send_nack. So,
sending nack is very very simple. We just use HAL_UART_Transmit API in
order to send nack bytes. So, this is a byte used to indicate it's a nack. All
right. So, this is defined in main. h and it's value is 0x7F. So, when the host
receives 0x7F it understands that it's a nack. And for the ack we use the byte
0xA5. So, these two are #defined in the main. h. In the main. c as I said these
2 functions are placed at the very end and sending ack is always of 2 bytes.
So, this we have already discussed when we were exploring about the host
and target communication or host and bootloader communication. Isn't it? For
example, So, that is here.
Right? So,when the bootloader sends ack it also sends another field or
another byte which consists of a value, which is nothing but length to follow
in the next reply. So, that's why sending of ack is always 2 bytes. So, the first
byte is the code of the ack. All right. And the second byte is length to follow.
So, length to follow will be mentioned in this argument follow length. And
that's why, we send 2 bytes by using HAL_UART_Transmit function. So, but
for nack there is only 1 byte will be sent. Because, there is no question of
sending reply if CRC fails. Great. So, that's about sending ack and sending
nack, and that is used here. And for this function we send 2 arguments, one is
the command code. The command code is nothing but the first byte of the
buffer. Then the second argument is length to follow.
So, the length to follow will be decided by this function itself. All right. So,
this is function specific or command specific. So, for this command the
bootloader is going to send one to one byte as a reply. So this we have
already documented in our document, isn't it? Supported command document.
Isn't it? For get version the bootloader reply will be 1 byte. So, that's why I
have written 1 here. So, that means we have added 2 more functions to our
project. That is bootloader_send_ack and bootloader send_nack we have to
add the prototype of these 2 functions. Isn't it? Otherwise, you will face some
errors. So, I will just add the prototypes of these 2 functions. All right. So, I
will add it in the main. h. Fine. So, let's go back to main. c and now let's
explore about the bootloader verify CRC and the bootloader uart_write_data
functions in the next project.
BOOTLOADER VERIFY
CRC
And in the previous project you understood about implementing the sending
Nack as well as Ack to the host from the bootloader. Isn't it? Now, in this
project let's explore about the implementation of bootloader verify CRC.
And here is a function, which takes care of verifying the CRC. So, now let's
understand this function with an example. So, this function verifies the CRC
of the given buffer in pData. So, it takes 3 arguments as you can see here. The
first one is pointer to the data, and second one is the length over which you
have to calculate the CRC, and third parameter is CRC value a 32 bit CRC
value received by the host. So, our job is now to calculate the CRC of this
data over this length and compare that CRC value, against this value which is
sent by the host. If both matches, then return verify CRC success to the calling
function. Otherwise, return fail. So, now here is a code block where the CRC
of these streams of bytes is calculated over this length. Now, for this we use
an API called HAL_CRC_Accumulate from the ST CRC driver code. So,
remember that in this course I don't explain the working principles of the
CRC engine or the CRC peripheral of the microcontroller. So, that's not the
objective of this course and I don't want to divert the discussion to something
else. It's a very simple peripheral and there are lots of application notes from
the ST. So, you can easily explore this on your own. All right. Now, our job
is here to take the help of CRC engine using some of its APIs in order to get
our work done. So, here the HAL_CRC_Accumulate calculates the CRC of
the given data over the given length. So, that's why I'm running a for loop of
length bytes and I'm using this API and it takes 3 arguments.
So, let's understand this. So, now let's see the implementation of this function.
Now, let's go to the driver code. So,this is an API. So, let's go to that
function. So, as I said it is implemented in hal_crc. c. And the first argument
is a pointer to the handle variable, that is CRC peripheral handle variable.
So, that handle variable is added by the CubeMX software when we selected
the CRC peripheral in the global space you can see here. All right. So, this is
a handle variable for the CRC peripheral and we send the pointer to that
here. All right. And the second argument is of data type uint32. So, you have
to send a value or an array. And the third argument is the length. Handle
variable, data and length. So, that's what we are giving here. Now, let's go
back to CRC verification function. All right. So, here it is. I'm converting a
byte value into to a 32 bit data here. Okay. And then, I'm sending the address
of this variable here. I'm just doing CRC calculation and accumulation one by
one. All right. So, this does CRC calculation as well as accumulation. All
right. So, that's what written here. Computes the 32 bit CRC of 32-bit data
buffer using combination of the previous CRC value. So, that's what
accumulation means. And the new one. All right. Great. So, now when this
code block completes you'll be having CRC value of all these bytes pointed
by this pointer over this length. Let's discuss this with an example here. So,
let's say I'm sending this command packet BL_GET_VER. So, here as you
know the total length of this command packet is 6 bytes. Right? Total bytes is
6, and this field contains the value 5. Right? So, length to follow. Now, first
what we do here is, we start from here and we go up to here. This is length.
All right. So, this is length and this is our pData. So, this is our pData So, this
is the pointer pData and this is length.
So, we calculate the CRC for these many bites. All right. And we get when
one value and that value we compare with this CRC sent by the host, if both
matches then we send verify success. I hope this is clear. All right. Now, let's
see how to use this bootloader_verify_crc function, I mean how to call this
function. Now, will go back to our handle function and handle get version
command, and here is a place where we call that. So, as I said it takes 3
arguments. So, first one is a starting address of this command packet, that
means this one. All right. So, that means the address of the first byte of the
command packet. All right. And length. So, as I said we want to compute the
CRC only for these many bytes. Isn't it? So, how to calculate this length. So,
it is calculated like this command_packet_len, that is total length -4. So, this
you have to subtract. Right? In the total bytes you have to subtract 4. Isn't it?
That is the length of the CRC. So, command packet length is calculated like
this the rx buffers first byte +1. Isn't it? So, that means here the first bite is 5.
Right? 5+1 gives the total bytes. So, that's the total length. Now, the total
length -4. All right. So, that is this one, this length and the host CRC. We have
to send to the bootloader verify CRC functions. So,now host CRC is
calculated or extracted like this. So, extract the CRC 32 sent by the host. So,
that means you have to move your pointer here and then extract the 4 byte
CRC. So, that's what done here. So, here we are moving the starting pointer
to this location. To this location and I am type casting that as a 32 bit pointer.
And then, I'm dereferencing in order to extract the 4 bytes of CRC. After that,
I'm calling this function in order to calculate the verify CRC and the verify
CRC returns verify CRC success macro, if both the value matches. All right.
So, that means this is a calculated value and this is given by the host. So, now
these 2 macros are defined in main. h. So, fail means 1 and success means 0.
So, that's how you calculate or verify the CRC, and in all our handle
functions. Remember one thing that these 2 code blocks will be same. So,
that means for every command we have to first test the verify CRC.
SENDING BLGETVERCMD
REPLY
We have already explored the bootloader verify CRC, the sending of Nack as
well as sending of Ack. Isn't it? So, now in this code block we have to
explore get_bootloader_version and bootloader_uart_write. So, here is a
function where it obtains the reply. All right. So, what is the reply in this
command. So, the bootloader just sends the version of this bootloader, that's
it. And this function I have implemented at the end here. All right. So,
get_bootloader_version. So,it just returns the macro value that is bootloader
version. So, this is actually decided by you. So, this is a simple macro
defined in main. h. So, let's #defined to 0x10. That means the version is 1. 0.
So, you can mention anything you want. Great. And the next function is this
one bootloader uart_write_data. So, this is a function which sents reply back
to the host over the command UART, that is our UART2. All right. So, again
this is very very simple function, which just uses the HAL UART_Transmit
function of the UART driver of the ST. So, it just sends the data over the
UART2. And the argument for this function is very very simple, the buffer
that is the data buffer and the length, that's it. And the same thing we give it to
the HAL_UART_Transmit function. That's it. So, whenever you want to send
any data to the host over the command UART just call this function. That's it.
It is just a wrapper function for HAL_UART_Transmit. Great. Now, let's go
back to our handling of get version command. So, let me just repeat once
again very quickly. So, whenever bootloader calls this function first we
verifiy the CRC and if the CRC is good, then we first send the Ack + length
to follow information and then we get the reply. In this case, it is just you
know sending the value of the bootloader version that's it. And then we call
bootloader uart_write_data function in order to send that reply. In this case, it
is just a bootloader version. So, that's why I'm sending the address of that
variable and 1 byte we have to send. So, That's why, I have mentioned it as
1. And if this is CRC fails, then we just sent Nack. All right. Great. So, now
in the next project what we'll do is, we will compile this code to see whether
everything is fine or not, and we will test only this command by using our
host application. I will see you in the next project.
BLGETVERCMD TESTING
PART1
So, now let's go to the Keil and let's compile these two files, so I have just
saved everything. Let's go to the Keil. Now, let's just hit the compile button.
All right. So, we met with 3 errors. All right. So, pBuffer is undefined and
then some functions are declared implicitly. So, we'll go back to our code
block. So, first of all I have ohh I'm sorry. So,here I have to use rx_buffer this
one. All right. So, this must be the rx_buffer and there are some problems
here. These function prototypes I have not yet given. So, we have to do that,
and let's do it here. And now, let's go back to main. c and also for this
function uart_write_data. Fine. All right. So, now let's give a semicolon here.
Fine. Let's go back to Keil click yes and let's click compile, Yes. There are
no errors now.
All right. So, now the code has been compiled successfully and we have
given support for one bootloader command, that is to get the bootloader
version. So, now before implementing all other commands let's test this from
our host. So, to test our code what we need is a host application, isn't it?
So,that host application is used to simulate all the command packets and it
should send that across the UART from the PC to the target and it should
collect back all the replies it receives from the bootloader. All right, So, now
if you go to the course repository so you will have this folder. Right? So, and
here under host you will find 2 sub folders. One is C and another one is
Python. So, when I was resigning this course I implemented the host
application which communicates to the bootloader of the board in C. Then
later, I came to know that you know it will be very TDS in order to change
the code for Mac and Ubuntu. That you have to include all the operating
system native serial port reading APIs and all. Also that becomes very
tedious for the students to change. So, what I did was, I implemented a
python application which you need not to change anything and you can
directly run on your host.
Doesn't matter whether it is Windows, Macro, Ubuntu. You can run Python, of
course you have to install Python as well as some libraries that I will show
you in later part of the course. So,under C you will find this folder this
project STM32 Programmer_V1 and if you go inside that. So, this is the host
application which is written in C and this is a code block IDE project. So,
I'll show you later how we can import this project into the code block IDE
and here you'll find sources and headers. In the sources you'll find all the
source codes used and in the headers you'll find all the header files which
are used to create the host application in C. Now, we have also another
folder as I said Python which is same application, but written in Python and
Windows, Mac, Ubuntu all users can use this application no problem. And in
the later project, I'll show you how to run this python program which
communicates to the bootloader. So, as I said when I was designing this
course a used the host application which is written in C. So, that's why you
will see I'm using the C application through out the course. But, you can also
use Python application parallely at your desk. No problem. Okay great. So,
I'll see you in the next project. In the next project I will show you how you
can run this host application on your host machine to talk to the bootloader.
PYTHON INSTALLATION
ON HOST
So, now first let's install the python on our machine and just go to
www.python.org, and here if you click on downloads. Here we can see that,
it is automatically detecting my operating system that is Windows. So, now it
is asking me 2 versions here to download. One is a legacy version that is
download Python 2. 7 that we don't want. And at the time of making this
video this was the latest version 3. 6. 5 and download the latest version.
According to your operating system and when I click that, I will get this, and
let me just download this. All right. So, the application is downloaded for
me that is for Windows and I'm going to install this now if you're using Mac
and go ahead and download the same application, you'll get . pkg version.
That you need to install similar to . exe. So, for Mac and Windows it is super
simple to install and for Ubuntu I will show you in the next project. Great.
Now, so if you're using Ubuntu then you can skip this video.
Let's install so let me launch this. And So, here we can see that, I have
already installed some older versions of Python so that's why it is asking to
upgrade the previous installation, so that I'm going to do. Now, if you are
installing it for the first time then you just need to click Continue continue,
and then it will install the python on your machine. Now, I'm going to click
update now. All right. So, now it is installing. All right. So, now it is
completed, now let's close, now let's open the common prompt. So, you can
also open the command prompt if you are using Mac OS and just type python
and it should show the latest version, you just installed. So, it is showing 3.
6. 5. Great. So, now you will entered into the Python command prompt and if
you want to get out of that, just do Ctrl+G and enter. Great. So, now that's
how you install the python. All right. So, ensure that the latest python is
installed on your machine and I will see you in the next project.
PYTHON PYSERIAL
MODULE INSTALLATION
Hope you have installed the python on your machine. And in this project, let's
understand how we can install a pySerial Python library, which we have to
use in order to communicate to with the target over the serial port. So, the
pySerial python module will provide all the opening, reading, and writing a
python functions for the serial port. So, for Windows it is very very simple.
Just run this command that's it. Python -m pip, pip is a command in order to
fetch those modules and install pySerial. So, now let's run this let's see what
happens. I'm going to my command prompt and I'm just pasting that command,
and hit enter, And here we can see it is saying that a requirement already
satisfied. So, that means I have already installed it. So, that's why it is not
going to install at again. So, for you it should install this module. That is
pySerial And if you are using Ubuntu just go ahead and type this command
sudo apt-get install python3-serial.
So, that should install the pySerial module. So, for Mac what have to do is,
you have to first install the pip command. OK on to your machine. For that,
Just run this command curl and give this path, in order to fetch that script that
is get-pip. py and save it in your machine as get-pip. py. So, you can do it
anywhere you want just you execute this from your user directory And after
getting this script what have to do is, just run pseudo python get-pip. py. So,
that means you just execute this script. Now, when this script executes it
actually knows how to install the pip command on your machine and then just
run sudo pip install pyserial. That's it. This should install the pyserial python
module onto your machine. So, without this module you cannot run the host
application so that's why you should install this.
BLGETVERCMD TESTING
PART2
So, in the previous 2 projects you have installed Python as well as pyserial
module. So, I hope you have done that, and now let's execute the host
application. All right. So, the host application is actually in this path source
code host Python. Isn't it? So, let's get into this directory. So, I will just open
my command prompt, so you can also open your command prompt and here
let me just get into first this directory. So, I'll just copy this and I will first go
to E and CD. Let me paste that path. Great. And now, what have to do is. You
have to type python, that's a python script, and give the name that is
STM32_Programmer_V1. py. All right. So, this is a same command across
all the platforms. So, just press enter and now it ask you to enter the port
name of your device. That means, you have to enter the virtual COM port
name of your target, that is Nucleo board or discovery board whatever So, if
you don't know which port it is, then just press enter and it will list all the
available ports. So, now since I have not connected my board it is not
showing anything. So, now what I do is, let me connect my board. So, I just
connected my board and let me again rerun the script, and let me just press
the enter once again, and here we can see that it is saying that these are the
available ports on my PC.
So, now let me just run once again and this time I'll give you COM3. And
we can also get this information from Device Manager in Windows. And if
you're running this on Mac then it will show you the device file associated
with that board. So, you have to enter that. And if you are running this on
Ubuntu, then it will show you dev, tty,acm0 or acm1 etc. . So, you have to
enter that. So, just enter what exactly it is showing between these single
quotes. Now, let me enter COM3 here and let me press enter. And this is the
menu launched by the script. So, it is saying that it's a STM32F4 bootloader
version1 and it is asking you which bootloader command do you want to
send. Remember that, before executing any command make sure that the
board is in the bootloader mode. So, that means your bootloader code must
be running on the target hardware. So, and it is showing all the available
commands and its associated number so for bootloader get version command,
I have to enter command code1 in order to execute that. So, now let me just
type one here and let's see what happens. And the host has sent that command
to the bootloader but it couldn't able to read any reply. That's because, the
board is not in the bootloader mode. Now, let me flash the bootloader code
onto the target. So, now let me rebuild the project once again. All right. So,
now let me download the code onto the target and now let me reset the board,
but remember that while resetting the board make sure that you press the user
button. So, that you allow the board to go into the bootloader mode. Now,
I'm pressing the user button and then I am resetting the board. Now, the
bootloader code must be running on my target. And now let me go to this
menu and press any key to continue, let me press any key. And now, let me
execute again the BL_GET_VER command that we just implemented. Let me
press 1, and let's hit enter and here we can see that the bootloader has
returned 0x10. Right? Great. Now, let me change something in the code now.
Let me go to main. h and here is a bootloader version. Isn't it? So, let me
make it 20. And let me just test, let me compile. All right. So, now let's
download the code. Let me reset the board. And let's press any key to
continue, and let's again execute the BL_GET_VER command type 1 and
press enter and here we can see the bootloader version received is 0x20.
Great. So, try this at your desk and let me know if you face any issues And in
the next project, let's see how we can debug the bootloader project.
BLGETVERCMD
DEBUGGING
The previous project you used a python application in order to run the very
first command. That is BL_GET_VER command and you also got the reply,
isn't it? So, now in this project let's debug the bootloader project to see all
the execution steps, which is involved in executing the BL_GET_VER
command and getting back the reply. So, now what will do is, we will enter
into the debug mode and then we will debug step by step. Keep the board
into debug mode to see whether we really receive the command sent by the
host or not. And now what I do is, I just go to the bootloader_uart_read_data
and I press the user button first of the board, and then I reset my board, and
then I hit run. So, now here you we can see that the control came to
bootloader_uart_read_data. So, if you don't press the user button then it will
go to user bootloader_jump_to_user_app. So, you can check that. So, you can
once again reset and run. So, here it is. It came to
bootloader_jump_to_user_app. So, we don't want to do that, we want to
come over here, isn't it? So, I once again press and hold the user button, then
I reset the board, then hit run.
So, now the control came to uart_read_data. Now, let's go to this function
and this is the code we have implemented, right? So, what I do is, I just keep
a break point here. And let's hit run. And now the code is waiting for the uart
data. isn't it? So, once the data is received this break point will be hit. The
data will only be received when I send something from the host. So, now I
press 1, that is the command code to send the BL_GET_VER command. And
now I hit enter and this window says that, this is the BL_GET_VER
command and it actually sent the command. All right. So, these are the
contents of the command packet 0x05 is the length, that is the first byte which
is sent from the host. And these are the remaining bytes of the command
packet. For example,0x51 is the command code for this command. And these
last 4 bytes are the CRC, 32 bit CRC. Now, this is saying that it's a timeout.
Why? Because, we have kept the break point here. Isn't it? So, here we can
see the breakpoint is hit. So, now the code is halted, right? So, that's why the
host is saying timeout. I mean, I have given some timeout information there.
So, no response from the bootloader reset the board and try again. That's
fine. Because we are actually halted here. All right.
So, now the code is here. Right? So,now what is the contents of the
bl_rx_buffer you can see here. The contents of the bl_rx_buffer of 0 is 0x05.
That's true. That's what we sent here. Isn't it? 0x05. Great. So, now what will
do is, now let's keep the break point here. So, that we execute both uart
receive functions. So, now what I do is, I just reset the board and I press and
hold the user button and then hit run. Fine. Now, the board is now the code is
actually waiting for the data in the uart. So, now let's go to our host and here,
Do you want to continue? Let's press yes. And it gives the menus once again
and here let's again press 1 and here it is. This break point is hit now. Isn't it?
So, that means we have received full data bytes that is command packet bytes
in the bl_rx buffer. Now, we can verify that. Now, let me open my watch
window. So, here is a watch window. All right. Now, let me just drag and
drop this here. Here it is. The first byte is 0x05. So, that's what we sent 0x05.
Second one is 51, E7, E9, 51E7,E9 ,AP and 7C. Right? So , that means we
indeed received the whole packet. Right? here. So, now next thing is it
should come here because, it's get version command. Now, let's see whether
it happens or not. Yes. It indeed came to bootloader handle get version
command. All right. So, now let's remove this break point and let's do the
single step and we entered the bootloader handle version command. Now,
let's see whether the CRC verifies or not.
So, now let me just keep a break point here and let me just hit run, and now it
is about to enter into the verify CRC function that is this one. Right? And let
me keep a break point here as well as here. Let's see, whether it is the
success or failure. Now, let me hit run and here it is. It is actually success.
Now, let me hit single step and we actually verified the CRC. Right? And
then, it sends the ack and then it executes the get_bootloader_version, in
order to get the bootloader version number and then it writes that into the uart
in order to send it as a reply to the host.
BLGETHELPCMD
IMPLEMENTATION AND
TESTING
The previous project we have successfully implemented our first command.
Right? That is BL_GET_VER command. Now, in this project let's implement
the handle function for bootloader handle get help command. All right. So,
now let me go to this function. All right. So, this is a function and here
bootloader sends out all supported command codes to the host. So, this
command is used to know what are the commands supported by the
bootloader. So, bootloader reply will be all supported command codes. All
right. So, now let's implement this handle function, I'll just show you the code
for that. So, here is the code for that. All right.
So, all these things you already know that it's extracting the whole CRC and
calculating the comand packet length. Then we call the bootloader verify
CRC. So, this line has no change. It will be same across all the handle
functions. And here, we are sending the reply to the host with the supported
command list. So, this is a global variable where I have maintained the
supported command codes of the bootloader. So, this is a global array. Let's
go there, here it is. In the global space I have just declared this array variable
and I have just initialized that 2 different command codes. That's it. It's a very
simple code and now let me test this. All right. Now, let me go back to my
Keil, now let's compile this code. All right. So, now let's download.
All right. So, now let me just open my host application and now let me just
type the command code 1. Yes, it's a version the bootloader version and now
type yes. And this time I will give the command code 2, which is get help.
Let's see what happens, here it is. Bootloader replied with all the supported
commands list the 0x51 to 5a. So, you can have more command codes to this
list no problem. All right. Here, when it was sending the ack, it sends the ack
along with how many bytes it is going to send next in the reply. Right? So,
that's why I have used the sizeof supported commands. So, then it replies by
calling bootloader uart_write data function. All right. So, we have also
tested the BL_GET_HELP command. Now, let's see what is bootloader
GET_CID. That is chip identification number that is of 2 bytes.
BLGETCIDCMD
IMPLEMENTATION AND
TESTING
So, now we have to implement our third command, that is get chip
identification number. So, this command is used to read the MCU chip
identification number or device identifier. So, now let's see how to
implement this functionality in our bootloader. So, here I have written a small
function to read the chip identification number or device identifier. You can
also call like that. So, the STM32F446xx MCUs integrate an MCU ID code.
This ID identifies the ST MCU part number and the die revision. It is the part
of the DBG_ MCU component. So, now let's understand this. So, what
basically they are saying is, let's go to the reference manual of the
microcontroller. And here in the section MCU device ID code in this section.
So, what basically they are saying is the MCUs from ST integrate an MCU ID
code. That means, there is one register which holds the MCU identification
number. Now, this ID identifies the ST MCU part number and the dire
revision. So, this is the name of that register which is at this address. So, this
register is of 32 bits and here are the details. So, 0 to 11 bits they describe
the device identifier. That means, the device identifier number is stored at
these bit locations. So, now the Device ID for this particular microcontroller
is 421. For some other microcontroller it may be different. All right. So, for
STM32F446xx family based MCUs it is 421, and bit 16 to 31 they represent
the revision identifier and if it is 0x1000 then this is revision 8. All right.
Great. So, now our job is to read this register in order to obtain this device
identifier. So, output must be 0x421. Now, let's see how we can do this.
Now, as I said this register is part of the debug component of the
microcontroller and lets see, how we can implement the code for that. So,
here is a code. All right. So, let me just open this in Keil, here it is.
I am dereferencing the debug MCU structure of the microcontroller. So, this
is actually #defined to this address. All right. So, this is actually defined in
STM32F446xx. h, and this is defined to this address, and if you check what
exactly this address is, here it is. This address is this one. So,that exactly
matches with this number. Isn't it? Now, that address is type casted to this
structure type structure pointer type. Right? So, this structure lets see what is
this structure. So, this structure has some member elements all are 32 bit
registers actually. The first register is IDCODE that is this one IDCODE, and
that actually holds MCU device ID code. So, that means we have to read this
register now. All right. So, that's what done here. So, we are dereferencing
that address and we are reading the IDCODE register. And then I am just
masking other bits, because we are only interested in 0 to 11 So, because that
is the device identifier. And then this function, this is a small helper function
which returns that ID.
Now, lets see how the handle function is implemented. So, here is a handle
function bootloader_handle_getcid. Let me open this in my code block and
after sending the ack, here we are reading that chip identification number and
then bootloader sends that as a reply back to the host. All right. So, it sends 2
bytes. Now, let's go to Keil, let's compile and let's test this. Let me launch my
host application, host application is ready and let me reset the board. All
right. So, now let me just type the code 3. 3 is for GET_CID and here it is the
CRC is success, the chip identification number is 421. Right? So, the
bootloader successfully read that register and it replied back to the host. You
can even modify this command to read the revision number also. Here, you
can do that. So, that I have not implemented, you can change this code or you
can add a new command in order to read the revision identifier. Now ,our
next job is BL_GET_RDP_STATUS. That is read protection status or read
protection level. So, this command is actually used to read the Flash read
protection level. Now, we will discuss more on this in the next project. First
we'll explore what exactly is RDP level or RDP status, and then we'll try to
implement that.
UNDERSTANDING FLASH
READ PROTECTION
LEVELS
We will implement a command which is used to read the Flash Read
Protection Level. All right. So, now our all the remaining commands, OK are
actually used to deal with flash memory like erasing the flash memory, a
jump into different sectors of the flash memory, write into the flash memory.
All right. So, even though these commands you can used to write data to even
RAM memory. But, most of our discussion will be now you know revolve
around the flash management. All right. So, that's why it's better to explore
some of the features of the flash memory interface. All right. So, this is the
flash module organization of this microcontroller, that is STM32F4466
family of microcontrollers. There is a main memory where we store our read
only data, code, vector table everything, and it has got 8 sectors from sector
0 to sector 7. And in the first two sectors we store the bootloader and from
sector 2 we actually store the user application. And remember that, by
default all these sectors are writable, erasable and readable. So,3 important
operations you can do. One is erase, write and read. And the microcontroller
also gives a provision in order to secure the contents of the flash memory.
That means, by default remember that all these sectors are not protected. But,
you can make it non erasable, or non writable, or non readable. All right. So,
these kinds of restriction or configuration or settings you can do in some of
the given registers of the ST, in order to make them protected. Now, to do
that we use something called Option bytes. So, here it is.
So, option bytes are used to manage security level on these flash memories or
sectors. Option byte size is 16 bytes and the option bytes you can configure at
this address. So, now let's explore more about the option bytes. So, now, let
me take you to the option byte section here. All right. The option bytes are
configured by the end user depending on the application requirements. So,
this table shows the organization of these bytes inside the user configuration
sector. Now, remember that this microcontroller gives you 2 addresses this
one and this one. In which you can configure the option bytes in order to
manage your different sectors of the flash memory. At this address only 0 to
15 bits are usable rest are reserved. All right. And this address you can use
to impose some write protection bits for sector 0 to 7. So, that will explore
later. No problem. All right. So, now let's first explore this RDP. So,now for
that we have to explore this address. All right. So, now let's go down here it
is. So, option bytes at this address and as I said Only 18 to 15 bits are
usable. And here this field is called as RDP( Read protection option byte).
The read protection is used to protect the software code stored in the flash
memory. So, that protection can be in 3 levels. One is level1, level2, and
level0. If the content of these bits in this address is AA, then it's a level0
protection. That means no protection. So, level 0 production means no
protection on the software code you have stored in the flash memory. Now, if
it is CC, then it's a level 2 protection. And if it is any other value than AA or
CC then it's a level1 protection. And when the read protection level is set to
level0 by writing AA into the read protection option byte, all read/write
operations from/to the flash memory are possible in all boot configurations
such as Flash user boot, debug or boot from RAM. So, that means there are
no restrictions if it is level 0. So, now if it is level 1 read production is
enabled. So, now the read protection level 1 is activated by writing any
value( except for AA and CC) OK into the RDP option. byte.
So, now what happens when we enable the level1, read protection here it is.
No access read, erase and program to flash memory can be performed. So,
you cannot perform a read, erase, and program operation. While the debug
feature is connected or while booting from RAM or system memory
bootloader. So, the bus error is generated in case of read request. So, simply
if I want to say it is very simple when in level 1, you cannot use your
debugger to read, or erase,or program your flash memory. So, suppose a
think of a situation that you have given a software code to your customer. A
customer can insert a debugger into that product and he can read the code,
What you have stored that. Right? If you don't want your customer to do that
then you can put the RDP level to level 1. So, your customer can never able
to access those software code from the debugger. All right, So, now when
booting from flash memory, accesses that is read ,erase, program to flash
memory from user code are allowed. That's fine. Because, whenever you
boot from the flash memory that happens internally, right? I mean you can
read from the flash, you can write, and you can erase. No problem.
But, you cannot connect the debugger and read some data from the flash
memory, or erase it, or program it with something else. And also there is one
important point here it is. The Mass erase is performed when level 1 is
active and level 0 is requested. So,whenever your customer wants to get into
level 0 the whole code will be erased OK, when you want to go from level 1
to level 0. So, that's the protection level design is given by the ST. So, this
design may not be the same if you go for some other vendor like TI. There it
may be different so you have to explore that. Now, level 2. In level 2, The
read protection level 2 is activated by writing CC to the option byte RDP
option byte. So, when the level 2 is set, all protections provided by level 1
are active and some more protections are given. That is booting from RAM
or system memory that is booting from the ST's bootloader. Right? Invoking
that ST's bootloader is no more allowed and you cannot use JTAG, or single-
wire viewer, or ETM, or boundary scan feature. So, complete protections no
tweaking is allowed. User option bytes can no longer be changed. So, once
you are in level2 user option bytes can never be changed. When booting from
flash memory that's the default booting, isn't it? Accesses that is read, erase,
and program to flash memory from user code are of course allowed. And
remember one thing, It is very very important. Memory read protection level
2 is an irreversible operation. When level 2 is activated, the level of
protection cannot be decreased to level 0 or level 1. So, this is an
irreversible operation so that's why I don't want you to change the RDP
option by to level 2. So ,if you do that you can never able to do all these
activities. All right. And it is irreversible. So, this can only be done during
protection of your product. All right. So, where you are giving out some
product your customer, and if you want to impose all these restrictions then
only you can make it as level 2 and you can give it to the customer. So, that
customer has no chance of tweaking inside the code or changing or erasing
that code. All right. In your development phase don't change the RDP status
to level 2. Otherwise, you will trap into problems. Okay. Great. So now,
what's our task? Our task is now to read the flash read protection level. That
is RDP level. So, in order to read that as I said we have to read this address.
In this address we have to read 8 to 15 bits. So, that's a read protection
option byte. So, if it is AA then no protection, if it is CC then it is level 2 like
that. Okay. And by default it will be AA. Let's write a small code to do this.
Now, let me go back to my project and All right. So, this is my bootloader
project and at the end I have added the code for that. Here it is. All right. So,
let me just maximize this get_flash_rdp_ level. So, I just added this function
to main. c and I have used to 2 implementations here. So, let me first explain
this implementation. So,it's very simple. First, I am here declaring and
initializing a 32 bit unsigned, 32 bit pointer variable and I initialized that to
this address, that is this address. All right. And after that, I'm just reading it's
contents and I'm interested in the bits 8 to 15. isn't it? Right? That means the
second byte. So, that's why I just shifted by 8 times. Now, then I'm returning
that rdp_status. Now, the same thing also can be done using the APIs
provided by the flash driver. Now, that I will explain you later. So, now let's
test this. So, now here is a handle function for that. This is a handle function
for the get_rdp and here, I'm calling this function get_flash_rdp_level.
Then,I'm sending that as a reply back to the host.
BLGETRDPLEVEL
COMMAND TESTING
So, now let me just compile this code first. Download this code and let me
reset my board. So, now let me send the command code 4. All right. So, that
is for get_rdp_status and here it is. I'm getting the RDP status as AA. So, that
means we are in level 0. Now, I can change it to level 1, no problem. But, I
will never change it to level 2. Now, if I change it to level 1 and if I want to
go back to level 0, then the whole flash memory will be erased. That we can
try, I mean you can add a command to do that. I mean, to switch the RDP
levels like from going from level 0 to level 1 or from level 1 to level 0. I
have not added that command. So, it's only read RDP not change RDP. Now,
what I do is, I open my ST-Link utility and I read that address. First, I connect
and then I read this address.
So, when I read this address here I can see AA. Right? the second byte. This
option bytes window you can use to change the readout protection. All right.
So, you can switch between different levels. So, as I said don't go to level 2.
And if you make it as level 1 and if you go back to level 0, then it will be
problem. So, the action of going back to level 0 from level 1 will mass erase
entire chip and you can do other sector settings here. All right. For a time
being I don't want to use it, and here it is saying that the second byte is indeed
A, and that's what we read with the help of the bootloader. Right? So, that
means we have successfully implemented our get_rdp_status command. Now,
let me show you one more implementation of that, flash RDP level. So, you
can also use the APIs provided by the flash driver of the ST hardware
abstraction layer. Here, we can see that flash_extension. c and flash. c. So,
these 2 are flash drivers. That means, they deal with the flash memory
internal flash memory as well as external flash memory, if it is interfaced.
And these 2 driver files they give lots of APIs in order to handle the flash
memory. So, in this implementation I have used function called
HAL_FLASHEx _OBGetConfig. OB means option bytes. And for this you
have to send the address of the flash handle variable. This API is
implemented in flash_extension. c. And here we can read that, get the option
byte configuration and it returns all these information. Not only the RDP
level but also the user configuration details. The other details like BOR level
and write protection status of different sectors. And we are interested in Get
RDP level it calls this function and here it is. This function then actually goes
and read the our address we just read in our application. All right. So, you
can also use these flash APIs of the ST's library or driver file in order to get
your job done. And this is a structure which will be populated by that API.
So, at one go you will get all these information. Great. So, now that's about
get RDP level command. And in the next project, we will explore the next
one that is the BL_GO_TO_ADDR command. So, the host uses this command
to instruct the bootloader to jump to specified address. That address can be
RAM address, it can be Flash address or whatever. So, the bootloader has to
jump to that address. We will test this command by jumping to sector 2. So,
you know that we have already stored a user application in sector 2. Isn't it?
So, we will use go to command in order to instruct the bootloader to jump to
this location. So, when that command is executed that is go to address. We
should see our application running on the microcontroller.
BLGOTOADDR COMMAND
IMPLEMENTATION
We are going to implement bootloader handle go command or go to address
command. All right. So, this is the handle function and this is the code
implementation. Now, let me explain this code to you. The host sends the
memory address to which a bootloader has to jump. So, that is given in the
third field, that is 4 bytes of address of the code or whatever to jump. And in
the code, after the CRC verification first we send Ack that you know already.
All right. So, send the Ack. And after that extract the address. So, here we
extract the 32 bit or 4 bytes of address and after that, that address has to be
verified. Because, the bootloader cannot jump to all the addresses. Right?
That is restricted. Because we can only jump to RAM location, we can only
jump to the user flash locations or you can only jump to the external memory
locations in order to execute some code. Isn't it? So. So, you cannot jump to
let's say peripheral memory region in order to execute instructions. That's not
possible. That's not allowed by the Cortex M processor.
And if you try to do that, then you will end up having some exceptions. So,
that's why we have to verify that address. So, that verify address, I have
implemented here it's a very simple code and here it is. So, can we jump to
system memory? Yes. Can we jump to SRAM 1 memory? Yes. We can jump
to SRAM 2 memory, we can even jump to backup SRAM, and we can even
jump to external memory, but we cannot jump to peripheral memory. That's
why, this code verify address. Just checks this go to address. All right.
Against some allowed memory addresses like SRAM1_BASE,
SRAM2_BASE, FLASH_BASE, BKPSRAM_BASE, etc. . And if that
address is within the allowed range,then this verify address returns address
valid Otherwise, it returns address invalid. All right.
So, all these macros I have included in main. h here it is. These are some
start and end addresses of different memories of the STM32F446
microcontroller and this maybe different. So, you have to change according
to your microcontroller. All right. So, now let's go back to our handle
function. So,in the handle function if the address is valid we tell the host that
address is fine. Then we jump to the "go" address. So, we don't care what is
being done there, host must to ensure that valid code is present over there.
That means where it is instructing the bootloader to jump. So, it's not the duty
of bootloader. So, we just trust and jump. All right. Now, the go to address
has to be incremented by 1 in order to make T bit as 1 in ARM Cortex M
based processors. So, you can watch this video in order to understand that.
Why? All right. So, T bit has to be 1, whenever you are executing thum
instructions on ARM Cortex M processor. And the last bit that is 0th bit of
the program counter is treated as 30 bit. Now, after that we just initialize this
function pointer to this address. What we received from the host, and then we
just jump. That's it. And if the address is invalid, then we come to the else
part here and we will tell the host that the address is invalid. By sending this
error code that is address invalid error code. Great. So, now let's test this.
So, in the first case what will do is. We will try to jump to some invalid
address and let's see how our bootloader behaves.
BLGOTOADDR COMMAND
TESTING
The compilation is successful. And now let's download the code and let me
just keep the board into bootloader mode. And do you want to continue? Yes.
All right. So, the go to address is command code is 5. So, let me just type 5
and you have to enter the address now. So, host is asking to which address
you want to jump and let me give some peripheral address, let's say
0x40000000. Now, let's see what happens. So, now here the address status is
1. So, address status 1 means it's a invalid address. All right. So, now let me
type yes here, and let me try once again, now let me jump to 0x08000000. So,
this is a base address of the flash. Isn't it? So, now you can see here the
address status is 0. All right.
That's a valid address. Great. So, now let's jump to some application in
order to execute that. All right. Now, as you know that we have our user
application, which we created in our previous project. So, this is our user
application. This application toggles the LED whenever I press the button as
well as it print some data, Hello from user application on to the virtual COM
port. Isn't it? So, now we also stored this code in the sector 2. So, sector 2
starts from this address 0x8008000. So, now this is already stored in our
flash. Isn't it? So, now let's jump to the reset handler of this application in
order to trigger the execution of this application. For that,we have to know
the address of the reset handler. Isn't it? So, now let me just download this
code and let me just go into the debug mode. All right. So, now if you just go
to the memory window. So, this is the base address of the sector 2, and so
this is the MSP value and this is the address of the reset handler. Isn't it? So,
if we jump here then we are able to execute this application. Isn't it? Great.
So, now let's copy this address. All right. So, now let me just hey
somewhere, sorry 0x080081D9. Right? So, or you can say D8.
All right. So, now we go back to our host application and let's type yes here.
Now, command code 5 and 0x080081D8. This is the address of the reset
handler of the user application. So, now let's hit enter and now it is saying
time out. All right. So, that's because my bootloader is not running. Let me
reset the board. All right. Now, let's try once again 81D8. Now, let's hit enter
and the address status is 0. That means, it's a valid address. Now, our
application has to be running. Isn't it? Now, will verify that. So, our
application must be printing now some data on to the virtual COM port. So,
for a time being I will just close this host application and let me open the
TeraTerm to see whether user application is indeed running or not. So, let me
just set up the baud rate and here it is. The user application is indeed running
and my interrupt is also working. Even ever I press the user button, the LED
is toggling. That means we indeed able to jump to the user application which
is stored in the sector 2. That's the use case of the go to address command.
You store some code on to different memories like you may be having code
in the RAM, SRAM1, SRAM2, in the backup SRAM, in the external
memory,or in the Flash on different sectors. Than host can ask the bootloader
to jump to those codes in order to execute that. All right. So, that's the
demonstration of this command. Great. So, try this at your desk and let me
know if you face any issues. And our next command is bootloader flash
erase. So, this command is used to erase a given sector of the flash memory
or you can even use this command in order to mass erase the flash memory.
That means, erase all the sectors of the flash in one go. So, let's see how we
can implement this and for this we use driver APIs from the flash driver of
the ST's hardware abstraction layer. Will see how to implement this in the
next project.
BLFLASHERASE
COMMAND
IMPLEMENTATION
We have to give support for another command that erases the flash. It's a flash
erase command. The use case of this command is to erase a given sector or
mass erase the entire main memory, main flash memory of the micro-
controller. So, erasing a flash memory or flash contents is nothing but making
its content as FF. All right.
So, erase means making everything as FF. So, in flash always remember that
if you want to write something it has to be erased first. For example, So, let's
say the current flash content lets say 0xAB. So, you cannot make this 0xCD.
So, you have to make first 0xFF, then only you can make 0xCD. Now, lets see
how to erase a flash. For this you have to understand the flash writing
sequences of the microcontroller. So, now if I go to the reference manual to
understand erase and program operations here. Here, you can see that these
are the steps you have to follow in order to erase a particular sector of the
flash memory. To erase a sector, follow the procedure below. So, check that
no memory operation is ongoing by checking the BSY flag in the flash status
register, that is FLASH_SR. Now, then set the SER bit and select the sector
out of the 7 sectors in the main memory block you wish to erase in the
FLASH_CR register. That means, you have to configure something in the
flash configure register, you have to tell the flash module that which sector
you want to erase. Now, then said the start bit in the flash control register and
then wait until the BSY flag to be cleared. So, these are the procedures you
have to follow if you want to write your own code. And for the mass erase
you have to follow these steps. All right. And for programming you have to
follow these steps. All right. Great. So, all these are documented well in the
reference manual and I hope if you want to write your own routine in order to
erase a flash, or in order to perform mass erase, or program. So, you can
follow these steps in your code. But, in our project we will be using direct
APIs from the flash driver.
BLFLASHERASE
COMMAND
IMPLEMENTATION CONTD
So, this is the command format of the bootloader flash erase command and
host uses this command in order to instruct the bootloader to erase a
specified sector number. And here we can see there are 2 extra field in this
command packet and the sector number has to be between 0 and 7, and
number of sectors must be between 0 to 7. All right. And now, we can
introduce one special case for this sector number. Now, the special case is if
the sector number is 0xFF, then it's an indication for the bootloader to
perform mass erase. All right. So, that's a special case I want to introduce
into this command format. If the sector number is 0xFF, then it will be a
special case where bootloader should do mass erase. That means, it should
erase all the sectors from 0 to 7. Great. Now, let's do this.
And I would like to take you to the flash driver first, hal_flash. c and here
you will find the API in order to erase the flash memory. I'm sorry, not in
flash. c but flash_ex. c here. Here is a function for erase. So, this API takes 2
arguments here. So, one is FLASH_EraseInit structure and you have to fill
this structure and you have to then pass it to the flash erase API. We have to
send the pointer to that structure. All right. And another argument is address
to the uint32 data type. So, now you can read here. A pointer the first
argument is a pointer to this structure type that contains the configuration
information for the erasing. All right. And the second argument is pointer to
the variable that contains the configuration information on faulty sector in
case of error. All right. So, this address will be used by the driver API in
order to store some information. If it stores this number, then it means that all
the sectors have been corrupting erased. So, it is for you to examine how
many sectors are really erased or is there any error etc. . So, you have to take
a look into this variable in order to decode that information. All right. So,
let's not worry about this argument. Now, let's take a look into this structure.
So, let's right click and let's go to definition and here it is.
So, this structure you have to fill and you have to send it to the driver. So,
first you have to mention what type of erase, whether it's a mass erase or
sector erase. So, you can check this reference here in order to see what are
the possible options. For example, So, let's go here, let's search for this
macro here it is. So, either you can make that variable as
FLASH_TYPEERASE_SECTORS, this is for sector erase or you can make
that type of erase member element as mass erase. Now, let's go back. So,
either one of those macros you have to use in this field. All right. And next is
Bank, flash bank. So, select banks to erase when mass erase is enabled. So,
this is not actually applicable to our microcontroller so you can leave this a
member element. All right. Now, next is sector number. All right. So, initial
flash sector to erase you have to mention this. So, the number of sectors as
well as you have to mention the voltage range. So, voltage range means, so
whenever you are dealing with flash there are some voltage range has to be
maintained. So, that you had to mention in this field. So, you have to select
one option which is given in this macro. All right. This parameter must be a
value of this reference. All right. So, now let's explore this reference here it
is. So, you have to mention one among these macros. So, if your micro-
controller operating in this voltage, you have to mention this macro.
And if you are micro-controller is operating in this voltage which is true for
our case, OK Our microcontroller will be operating in this voltage range,
that is 2. 7 to 3. 6. All right. So, in that case you have to use this macro for
this member element. And after that, this erase function will take care of
erasing your flash. So, this code is written in the driver file and it is written
by keeping these procedures in mind. Great. So, now our job is to just use
this in order to perform mass erase or sector erase. Now, let's see how it is
used in our case, and now let me go back to our bootloader project. So, here
is a function which takes care or handles the bootloader flash erase
command. So, this is a handle function. Now, let's explore this. So, after the
CRC verification we send the Ack and after that I call this function execute
flash erase. All right. And I will pass the third byte as well as the 4th byte of
the command packet. So, that means I pass sector number as well as number
of sectors. So, that is the third and fourth byte, isn't it? That actually takes us
to execute flash erase. Now, let's see what is implemented here and this
execute flash erase returns. What is the error status, whether it is success or
failure or it's a invalid sector etc. . And I just send that status back to the host
here. So, that's a bootloader reply here. All right. And before starting the
erase I just make the LED high, and after the erase I just make the LED low
just to indicate that the flash erase is going on. All right. Great.
Now, let's explore what exactly this execute flash erase does? Now, let's go
to the implementation and here is a implementation. So, we have totally 8
sectors in this micro-controller 0 to 7. Now, number of sector has to be in the
range of 0 to 7. Right? So,now if the sector number is FF, then that means that
it's a request for mass erase. Now, code needs to be modified if your MCU
supports more flash sectors. So, you can always edit this code. Now, here I
extract lotsof details. Here, is a sector number is passed by our handle
function, and this is a number of sector and it undergoes various checks here.
If sector number is FF, then it's a type of erase is mass erase. All right. And if
it is not equal to FF, then it's a sector erase. So. that's why I make type of type
erase as sector erase. All right. Now, after that sector will be programmed
with this sector number. So, and the number of sectors will be programmed
with number of sector field,which we received here. And at the end what we
do is we execute flash erase. And remember that the voltage range as I told
you it must be FLASH_VOLTAGE_RANGE_3. Now, before calling this API
you have to unlock the flash. So, that is required for this microcontroller
because in order to access any flash registers you have to unlock the flash
first. Again this is an API from the flash driver to unlock the flash. So,
unlocking about unlocking you can read more here. It is right here.
All right. So, unlocking the flash control register. So, that's what done in this
function unlocking. Actually you have to set some passwords are keys. All
right. So, you have to write this key into the flash key register this one. And
again you have to put another key into the flash key register OK, that actually
unlocks the access to the flash registers. All right. And after the flash erase
you again lock back. That's what done here. So, all these informations are
actually stored in flashErase_handle, that's a variable of this structure type.
And the address of this will be passed in the erase API. And the status which
is returned by this API will be captured in the status variable and that will be
sent back to the handle application. All right. So, the possible status could be
I will show you the possible status. Let's go to Keil. So, this is a possible
status so let's explore this. So, these are the possible status. HAL_OK if
everything is OK, if there is any error it will return that the API will return a
HAL_ERROR. If the flash is busy then it will return busy. That means if any
other operation is going on then it may return busy and if there is any timeout
that it may return timeout. All right. So, this will be passed back to the host.
All right. So, this is our flash erase handle function. And now let's test this.
TESTING FLASH SECTOR
ERASE
So, now let's download this code. Now, let's test this FLASH_ERASE. Now,
you already know that you know in the flash module organization, we
actually stored the bootloader in these 2 sectors and the user application is
stored sector 2 on wards. Right? Now, let's go and check the contents For
that, I will use I will use the ST-LINK utility. So, if you are not using
windows then you may not be able to use ST-LINK utility, then you just
watch it. So, it's not required to run this software. But, I'm just showing for
the demonstration purpose. Now, if I go to the sector 0 address you can see
here there are lots of contents. Right? So, these are the bootloader contents,
and if I go to the sector 2 base address that is this one. Then this is actually
the content of the user application. Isn't it? So, now let's first erase this
sector. Sector 2 means from here to here.
Right? So, that much memory locations has to be erased. All right. So, now
let's try FLASH_ERASE_CMD is 7. All right. So, now let me just type 7
here and now it is asking enter the sector number. The sector number is 2,
and it is asking how many sectors you want to erase. Now, let me just give 1.
So, now let's hit run and here it is. Erase status is success and code is HAL_
So, that means we just erased the sector 2. Now, let's verify that. Now, let's
disconnect this and let's connect back and here it is. Now, all are FFs. Isn't
it? So, that means our sector 2 indeed got erased.
All right. So, now let's open our user application once again. All right. So,
user application this one and this is our user application and let me program
once again. All right. So, it cannot program because it is connected. Let's
disconnect. All right. Let's program and let's connect back the sector 2
contents reappeared because we just programmed. All right. So, now let's go
to the sector 3 and it is all already erased. So, let's put some contents here as
well. So, I will download the same application into the sector 3. All right.
So, let me go to my user application, and let me change this address here to
C. Right? So, that's our sector 3 is C00. All right. Great. Now, let's rebuild
the code. All right. So, now let's download, before that let me just disconnect
this. All right. So, now let's connect back and C00 also has the same content
now. Great. Now, let's run our host application to erase sector 2 as well as
sector 3. Let's do that. Now, let me press Y. The command code is 7 and
enter the initial sector that is enter the sector number. Now,2 and it is asking
how many sectors to erase, let's enter 2. Because, I want to erase sector 2
and sector 3. Now, let's hit enter and it is successful and now let's check.
Now, let's disconnect and let's connect back and here it is. This is the sector
3 which is erase, and this is the sector 2 that is also got erased. So, we
erased sector 2 and sector 3. Great. So, now let's test the Mass erase.
TESTING FLASH MASS
ERASE
Now, in this project let's test the Mass erase So, now let me just open my ST-
LINK utility and let me just connect first. And in the sector 0, we have
bootloader code and I have kept some code in the sector 0, 1, 2, 3 also OK,
there are some contents. And now, let's see when we execute Mass erase
what happens. All sectors. That is write from 0 to sector 7 must be erased.
Let's try this. Now, let me just disconnect this and let me just go back to my
host application, let me launch it. All right. So, it is already opened. Let's
type the command code 7 and enter the sector number. So, I have to enter FF,
0xFF for mass erase. Now, let's do that. Now, let's hit enter and now the
operation is completed. And now let's go back and check ST_LINK utility,
but before that now let me just try to execute some more command, because
our bootloader code is now erased.
Isn't it? So, that's why I should not get any reply. Now, let's try. Here, we can
see I'm getting timeout error. Now, let's go and check in the ST_LINK utility
let's connect and let's see what is there in the sector 0, It is FF sector1FF,
sector2FF, sector3FF. So, all sectors are erased. So, that means it's a Mass
erase Now,we have to program the bootloader once again. All right. So, now
let's do that. So, this is our bootloader code, now before that let's disconnect,
I connect back. All right. So, now I should see my bootloader code. All right.
It's there. And the driver API that is FLASH_ERASE finally calls
FLASH_MassErase. Which is there in the flash_ex. c and I suggest you to go
through this in order to see what and all register it configures in order to
trigger the Mass erase. This function is called by the FLASH_ERASE API,
that is this one. So, if I go to FLASH_ERASE is here. Here, if it is Mass
erase If the type erase is equal to Mass erase is, then it'll call the MassErase
function which is there here.
So, I have not included you know programming the RAM and executing code
from the RAM. You can do that no problem instead of sending to flash. You
can write a small function which copies bytes to the RAM. All right. So, you
can do that. Now, This function does not check whether "mem_address" is
valid address of the flash range. So, I just assume here that the mem_address
falls in the correct flash range. All right. You can see here, this is a very
simple function. Isn't it? Where I use the API HAL_FLASH_Program from
the flash driver of the ST's hardware abstraction layer. Now, here I just run
the loop for length value and I program the content of this address. All right.
Byte by byte. That's it starting from here. All right. So, this is a mem_address
that is the base address and this is the content. And here I'm instructing this
API in order to carry out byte by byte. That's it. Then I returned the status of
that operation. So, now how to program the flash again you have to take a
look into the reference manual. All right. So, here it is. This API programs
the flash according to this procedure flash memory programming sequence.
So, you have to go through this document in order to understand how exactly
that API programs the flash memory. And remember that, So, we have to
unlock the flash module to get control of the registers. All right. So, that's
why I have used HAL_FLASH_Unlock and HAL_FLASH_Lock after that.
So, it's very simple. Now, let's test this handle function. Now, how to test
this? What will do is we have our user application. Isn't it? Now, we will
create a binary file out of that. So, then we will ask our host application to
read that binary file, in order to send all the contents of that binary file to the
target by using this command. So, this command can transport 250 bytes in
one go. Isn't it? So, our binary file may be more than let's say 5 kilobytes or 2
kilobytes. Than a host application will repeatedly use this command to
transport bytes to the bootloader. So, then so whenever bootloader receives
this command, it will program the contents of this payload OK into the
memory indicated in the base memory address field. So, now let's test this so
for this we should have a binary file, isn't it? bin file. Now, let's create that.
Now, let me go to Keil now and this is our user application. Right? So, now
let me open the user application. So, this is our user application. Isn't it? So,
which print something over the virtual COM port, that is hello from user
application. Now, let's rebuild this code in order to generate a binary file.
Now, if I just click a download here. All right. So, what ID does? It will just
download this code onto the flash in the base starting from the address which
we have mentioned here. Right? This one. So, this is a base address we have
mentioned, isn't it? This is the address of the sector 2. Now, instead of
downloading from here. So, what will do is, we will use the binary of this
application in order to program through our host application. So, before that
we have to create a binary of this project. Isn't it? All right. So, in Keil you
can use this command in order to generate the binary file. So, here what we
do is we use the command from elf. So, we use this executable in order to
generate the binary file out of the elf file. All right. So, the elf file which is
generated by the keil will be in the format dot axf. All right. For example,
when I compile my code here we can see that it has generated axf file in the
folder this one. So, this axf file contains code as well as lots of debug
information. So, that's how you are able to debug the code on the target. All
right. Now, you can also use Keil to generate the Intel hex format. For
example, if you go to output and if you check create HEX, then it will also
create Intel hex format. But, there are no such options to create a binary file.
So, I searched every where, but I couldn't be able to find any options. All
right. So, if you know you can tell me one way to generate binary file is by
using the fromelf executable of the Keil toolchain. Now, for this you have to
mention the input file name that is what we generated here, that is the axf.
And then you have to mention this argument to the fromelf binary, that I want
to create the bin file and you have to mention the output file name with the
extension bin. So, if you do this it will create the binary file. Now, let's try
now the input file name is this one. Right? So, that's the elf format what we
generated here. And after that this is the output, So, here we can do any name.
But, I have just given user_app. bin this is my output and this is the command.
All right. Great. Now, let's test this. So, copy this command and go to Keil
and go to options for target, then click on user. So, now here you see this
option after build. So, that means after build you are instructing the Keil to
run these commands Run 1, Run 2. All right. So, check Run 1 and paste that
command. So, now click OK and then try to build again. And here we can see
it has executed this command fromelf. Oh there is an error. Could not open
file this one. All right. So, that means it is not able to locate this file. Why?
Will let's check, where exactly this file is present. Now, let me just open the
open containing folder and this is my project. It must be in MDK-ARM. So, it
is there inside this folder. You can see here . axf, here it is. It is there, but I
think we have to give the full path. I'm not sure I will just copy this folder
name. Let's see that works or not. I'll just paste this folder name. Now, let's
see whether it's a valid format or not. So, now let me just remove this, paste
that Now, let's try to compile. Yes, it has generated the binary file. Now, let's
check. Now, let's go to project directory and here it is the bin file is
generated. So, you cannot open this with the notepad. So, because it's a
binary file. That means it contains only the fewer of code for your
application. All right. Now, you can open this with the hex editor. I have a
hex editor here. So, I can open that and all right. So,it's my trial version. All
right. So, let's continue.
Now, here it is. So, this is the binary file. So, this is pure of code of your
code. This is the MSP value, right? So, this is 08 00 18 81 d9. So, this is a
address of the reset handler, and this is the address of the next exception
handler. So, basically these are the vector table. Isn't it? That means it's a
pure hex code for your application. So, this is not in the intel hex format
remember. Grate. So, this is the pure binary now. So, what's the size? Size is
4792 bytes. So, that means I have to execute my mem_write command. How
many times? 4792 divided by 255. We have to execute that command 19
times. Because each time our bootloader memory write command carries 255
bytes. Isn't it? So, this application totally has 4792 bytes. All right. So, now
in the next project we will test this command.
BLMEMWRITE COMMAND
TESTING
And in this project, let's test the bootloader memory write command. So,we
have to now flash the binary we just generated using our command. Isn't it?
Remember that, the binary what you generated after compiling this project.
So, you generate a binary Isn't it? So, that binary you have to place in the
same directory, where your python script, or your C application executable is
stored. So, for example, here I have the python host application here. So,
here only you have to keep that binary and you have to give the name
user_app. So, just copy the binary you generated in this project to the place
where host application is stored. All right. So, now let's execute this script,
let me enter the COM port. So, now first of all as you know we are going to
program into the sector 2. Isn't it? So, let's first erase that. I mean this is not
required you need not to erase it, but to make sure that we indeed program
that sector. First, let's erase that. Let's make sure that that is erased. So, I'm
going to enter the command 7 here. So, that is the FLASH_ERASE and the
sector I'm going to erase is 0, 1, 2. And enter the number of sectors let's
enter 2. And here you can see that Erase status is success. Great. So, now
let's check. Let me connect the ST-LINK and let's check 0x800, So, that is FF
and C00. So, that is also FF. That is actually sector 2 and sector 3. Grate.
Now, let's disconnect and let's go back and press any key to continue, and
now let's run the BL_MEM_WRITE command, that is 8. And enter the
memory right address here.
So, the base address of the sector 2 you have to mention 0x08008000 and So,
there is a problem. There's a problem. The problem is the bootloader is not
responding. So, that's because I placed this issue that, when I connect and
disconnect the ST_LINK the bootloader stops responding. I need to debug
this but whenever you connect and disconnectthe ST_LIMK you have to reset
the board. This problem arises only when you use ST_LINK. So, let me just
reset the board once again. Fine. That's fine. Let me again run and let me
enter COM3 and 000. Great. Hit enter. And here you can see that, the host
application has sent so many packets to the bootloader and each packet
contains 255 bytes as a payload which is nothing but the content of the binary.
So,here you can see that, it also prints the bytes so for sent as well as bytes
remaining. And at the end you can see that, it actually sent in total 4792
bytes. And in each packet it transfers 255 bytes from that user_app. bin. All
right. So. now we have just programmed the binary. Now, let's trigger that
binary. So, how to trigger? Simple, let's execute the go to command. Now,
let's press any key to continue and now, let me type the command code 5, and
what is the reset handler address? That is 0x080081d8. isn't it? So, that's the
address of the reset handler. Isn't it? So, let's confirm that. So, let's go back to
user_app and let's open this in the hex editor. All right. So, zero. So, this is
actually these 4 bytes actually the MSP value and this is the reset handler 08
00 81 d8. So, d9 you have mention it as d8. So, now 81. So, 81 d8. Isn't it?
right? Correct. So, now let's hit enter, and address status is 0, that means
success. Now, our code must be running. Now, let's check. Now, let me close
this and let me open the Tera Term. Let's set the baud rate, and here it is the
application indeed running. So, we just programmed using
BL_MEM_WRITE command into the sector 2 of the user flash. And then we
used the bootloader go to command in order to trigger the execution of the
reset handler of that application. Great. And you can also use this command
to store binary on different sectors of the user flash and use the go to
command in order to execute that. All right. That's about the implementation
of BL_MEM_WRITE command and try with the different binaries So, I just
showed you a simple binary which has the button interrupt as well as printing
over UART. So, you can try with different binaries also try with different
memories. You put the code into RAM, SRAM 1,SRAM 2 and try to execute
that. So, try to reproduce this and let me see you in the next project.
OPTIONS BYTES
PROGRAMMING
Now, before going to our next command let's understand some theory In this
project, we are going to understand the write protection and read protection
on these sectors of the main flash memory. As you know we have 8 sectors
and all sectors are by default are not protected. In the sector 0 as well as in
the sector 1 you actually store the bootloader code. And you don't want to
erase that bootloader accidentally or you may think that OK, someone should
not even read it. All right. So, in those cases you can put restriction on
different sectors and you can configure those protections. In the previous
project, in the option bytes we explored this, isn't it? The read protection
option byte. And if this level is 0, then there is no protection on the entire
software code stored in the flash memory. So, this level applicable to the
whole user flash memory. So, you can apply protection to the individual
sectors. So, that's what we are going to learn in this project. Again all this
protection related configurations you have to do it in option bytes. Now, let's
explore this table of a option bytes and just go down, and here you will see
the configuration of protections for individual sectors here it is. So, this is in
the address this one. All right. So, in this address so the first 8 bits are to put
protection configuration on individual sectors. There are 8 sectors, that's why
8 bits are reserved for this purpose. In some other microcontroller, if it has
more sectors than you may find more configuration bits.
Now, just read the description over here. If SPRMOD is reset, SPRMODE is
nothing but this bit 15th bit. So, if this bit is reset that's the default state, then
making any of these bits 0 will put write protection on that sector. And if you
make any of these bit as 1 the relative sector will not be protected, will not
be write protected. So,0 means write protection and 1 means it's not write
protected. It's like that. All right. And if this SPRMOD bit is set, so then 0
means PCROP protection. So, PCROP protection means read and write
protection. So, you can read here. So, SPRMOD means selection of
protection mode of nWPRi bits. So, nWPRi bits are here. All right. So, if this
bit is 0, then nWPRi bits used for sector i write protection, whereas if this bit
is 1 then nWPRi bits used for sector i PCROP protection. So, what is
PCROP protection? So, let's explore. So, that is also mentioned here. So, just
browse down and you will see the definition of that. All right. So, let's go
down and here it is proprietary code readout protection. So, that means you
are actually putting read restriction on that particular sector. So, for example,
if you are using debugger to read that sector contents, then it will not be
possible if you put PCROP restriction. That is proprietary code read out
protection. All right. Now, let's go to that table. So, that means this
SPRMOD bit decides whether you're sectors are write protected or PCROP
protected, that is read protected. All right. So, if this bit is 0, then these
conditions will apply and if that bit is 1, then these conditions will apply. So,
now how to program these bits. Now, for that again you have to refer
guidelines described in your reference manual. All right. So, you have to
read this. So, here is a procedure. So, to modify the user option value, follow
the sequence below. Now, check that no flash operation is ongoing by
checking the BSY bit in the flash status register. Write the desired option
value in the FLASH_OPTCR register. So, this is very very important because
you should not modify these addresses remember. So, you can read it. So, in
the previous project what we did, we read this address. Isn't it? To get this
level value. You can do that no problem. But, if you want to change the level
or if you want to change the user option bytes remember you should not
modify this address, that's not possible. All right. So, these are not writable
addresses.
So, that's why here they are clearly saying that, write the desired option value
in the FLASH_OPTCR register. Now, will take a look into that registry in a
moment. Don't worry. Now, set the option start bit in the FLASH_OPTCR
register, and then again wait for the BSY bit to be cleared. Now, we have to
explore this flash OPTCR register. Now, for that just go to your flash section
and here is a register FLASH_OPTCR, that is flash option control register
and you have to read here that the reset value is this one. The reset value is
not 0, and the option bits are loaded with the values from the Flash memory
at reset release. And here it is. Here is the SPRMOD bit and these are the
individual sector configurable bits. That is nWRP. All right. So, you have to
use these bits in order to put read or write protection on the individual
sectors. And this will decide whether it's a read protection or the write
protection. Great. So, now we have to use this register in our work. All right.
Great. So, now with this background you should be able to implement our
next command that is enable/disable a read write protect. Before that let me
give you a demonstration of this by using ST_LINK utility. So, let's say I
have a board now connected to my PC. All right. So, now let me just open
my ST-LINK utility and let me just connect to my board. All right. So, it is
connected. All right. So, this is the sector 0 and you can see the codes here.
And if I go to sector 1 and yes, there are so some code. And if I go to sector
3 there are no codes, no problem. Fine. So, now what I do is, I open the
option bytes option here. Here. All right. So, this will read the status the
current status of the option bytes. Now, here we can see the readout
protection is level 0. That's fine. That is by default and here we can see that
all sectors are not protected, no protection by default. Now, let's put write
protection. All right. So, now let me just check these two. Now, later you
have to do this by using your own command bootloader command. So, I'm
just demonstrating you by using ST's software. Here, I have checked these
two options. Now, it is saying that it is write protected. And then you have to
click Apply button. So, apply button I'm not able to show this is actually
hidden here. So, there is a apply button. But, since for recording purpose I
have kept Udemy is recommended resolution or at screen resolution, which
actually prevents this software to show that option. For you, you should see
apply button here. So, you have to press that apply button. So, now let me do
that. I just use the tab key. All right. So, now I'm going to apply. I'm going to
press enter and here you can see that, now these two sectors are now write
protected. So, now let's go and read the option bytes once again. And here
we can see that write protection is currently active. Great. Now, let me just
disconnect and now let me try to download, the Keil is throwing error. Why?
Because, it couldn't able to touch that sectors because it is write protected.
So, you cannot be able to download the code now. So,now let's again connect
back and now let's read the option byte once again and now let's remove this
protection. All right. Let's apply. All right. So, now the write protection is
removed, disconnect and try to download. Yes, it works. I again connect back
and again read the option bytes, and now this time I put read/write
protection.
So, what we just saw here proprietary code readout protection. That is this
one PCROP. Now, PCROP is enabled. So, read as well as write. All right.
So, now let's apply. So, here you can see now you cannot even read this
code. It is everywhere it is showing these numbers. So, these numbers. I'm
not able to read what exactly is the contents of those sectors. Here also you
can see the same thing. All right. So, everywhere it is these numbers. So,
now let's disconnect and let's try to download, now it don't work. Because,
the read as well as write protection is active. Great. And let's connect. Let's
read the option bytes and here is one important thing you must understand. If I
want to disable this read and write protection that is PCROP. That is PCROP
protection It is not that simple. Now, if I want to change this protection level
than what the flash controller does is, it will mass erase the whole user flash.
So, that means if you want to go back from read/write protection to some
other protection level like write protection or no protection, then the entire
flash memory will be mass erased. Now, I will hit apply and here it is. It is
throwing this warning disabling of SPRMOD and read protection of user
flash sectors will generate a full chip erase. So, that means the whole chip
will be erased now. So, now we lost all the contents of the user flash now.
Here you can see every where it is erased. Now, it is entirely blank. All
right. So, now you have to reprogram it. Now,let's read the option bytes and
it is now went into the default state. So, now in the next project what we're
going to do is. We are going to use these 3 command. So, the first command
is bootloader enable read/write protection. The second command is
bootloader disable read/write protection. And the third command is
bootloader read sector protection status. I will show you the implementation
of enable read/write protection and the disable read/write protection. And
the read sector protection status you have to implement on your own and you
have to read the current protection status of the sectors. For example, it
should looks something like this. If you open the ST's software, how do you
read the protection status of the sectors you just go and open the option bytes.
Right? So, this operation is actually reading the protection status of the
different sectors. Isn't it? So, something similar you have to implement,
something like this. Now, I will show you how that looks like. All right. So, I
will just take you to the host application. Now, I'll just reset my board and
my bootloader code must be running. So, now here is the command read
sectors protection status. All right. So, the command code is 11. Let me type
11 and here it is. This is the output that my implementation is reading the
status of all the sectors. All right. So, something similar to this one.
Something similar to this one. All right. Now, what I do is, I just change
some of the option bytes. Let me just connect and go to option bytes. And
Now, let me put write protection on these two and these two. All right. Now,
let me click apply. So, now it is write protected. You can see here 0, 1, 6, 7.
All right. Great. So, now let's check with our command 11. That is to read the
sector protection status. All right. So, it is not responding because after
programming the option bytes through ST's software you have to again restart
it. All right. So, now let's press any key to continue, and let me just put the
command code 11 and here it is. So, now my command is also properly
reading the current status of the sectors. So, these 4 sectors are write
protected. All right. So, let's check that once again. Now, let me clear those
protection, unselect all. All right. And put and check sector 3, sector 4 and
put read/write protection. Now, let's check by reading the status 11 and here
it is read/write protection. So, what's happening here.
So, I have implemented read sector status command in the bootloader. That is
a handle function. When I send that command to the bootloader from host
bootloader just goes and it reads status of this register. And it actually gives
the contents of these bits as well as these bits. Back to the host and then host
decodes these information and it just prints in a nice readable format. That's
it. All right. So, this you have to implement. And in the next project we will
see how we can implement enable read/write protection on the sector as
well as disable read/write protection on the sectors.
IMPLEMENTING FLASH
SECTOR PROTECTION
COMMANDS
So, this is a command bootloader enable read/write protect on the sectors.
And this is a very simple command and you will see only 2 important fields
here. So, one is sector details and the protection mode. All right. So,sector
details means here it is. Sector numbers encoded in 8 bits, because there are
8 sectors. Each bit is for one sector. For example, 0th bit is sector 0. So, if
you make any bit as 1, that means you want protection and if you leave that 0,
then you don't want any protection. All right. Great. So, and this is protection
mode. So, what kind of protection you want on those sectors. If you mention
1 here it's a write protection and if you mention 2 it's a read/write protection,
that is PCROP. This is a command code 0x58, and this is a status whether it
was successful or failure it indicates that error code.
Let's see, how we can implement this command handle function in the
bootloader code. Now, let me directly take you into the bootloader project.
So, let me just open this in my code block the handle function is here, here it
is handle enable read write protect. So, the most important thing is you have
to understand the theory part that we explored from the datasheet. And you
have to put some effort in order to understand the option byte modification
procedure explained in the user in the reference manual. So, you have to read
that. All right. So, then that one you have to convert into code. Now, here this
handle function of course it does all these CRC checks and all. And after that,
it comes to the configure flash sector read/write protection So, this function.
This is a important function. All right. So, now let's go to this function. Now,
again this procedure you have to remember, we have to read couple of times
from the reference manual that is modifying user option bytes. And
everywhere I have written comments, so you can read that and you can able
to understand that. So, now first we are going to touch this address. So, this
is the flash OPTCR register. All right. So, what we just saw here in the
reference manual this register which is at this address. Now, here you have to
as I said our business is just to touch these bits from 16 to 23 and 31. That's
it. All right. First, we have to unlock the option byte programming. Because,
by default you cannot touch those registers. So, you have to unlock first.
So, this is the driver API I have used to unlock. And now this function is a
helper function which takes 3 arguments here we can see sector details. What
we discussed here. Right? So, sector details this one. So, this is actually
encoded Sector information. Isn't it? So, now the second argument is
protection mode, that is this field. And the third one is disable. So, this field
will be used in our next command that is disable read/write protect. So, need
not to worry about this. In this command that is enable read/write protect. All
right. Let's go down. So, don't worry about this segment. Now, let's go here.
So, if protection mode is equal to 1 so than it's a write protection. Right? If it
is 2, then it's a read/write protection. So, that means here in this function we
are doing write protection. So, now these codes are exactly like the
procedure what they have mentioned in the reference manual. First unlock
the option byte access that unlocking feature is implemented in this driver
API. So, I have just used that. So, if you want to clearly understand how
exactly that is implemented, it's very simple. Just go and check this. Now,
let's go to flash. c and let's search for this function. And here it is. So, what
they are doing? They are just putting these option keys into these registers.
That's it. So, that's a procedure in order to unlock the option byte
modification. So, what are these keys? So, these keys are these numbers. All
right. So, exactly as mentioned in the reference manual. So, I can even show
that keys to you. Let's go to option bytes. All right. So, just browse here, and
here it is. So, these 2 keys you have to program into the registers, key
registers into the key register. So, one is this number and another one is this
number. All right. So, just they are programming that. So, that's how
unlocking happens. Now,let's go back to the code. After unlocking wait till
no active operation on the flash. So, we actually read the busy flag from the
status register and we have to wait until it is reset. All right. So, that's why
this code is added. Now,after that remember we are actually putting write
protection in this code block. Let me write that.
We are putting write protection on the sectors encoded in sector_details field
or argument. Right? All right. So, here setting just write protection for the
sectors so clear the 31st bit. All right. So, if you clear this bit, which bit.
Let's go to this bit. So, then it's a write protection. Right? So, that's why I'm
clearing that and after that put write protection on sectors. All right. So,
sectors start strong 16. Isn't it? So, that's why I'm just left shifting those sector
information which is encoded here. To 16th position. So, then we are
clearing those bits. So, that actually puts the write protection. So, just
carefully observe this code statement and you'll able to understand. All right.
Great. So, this actually puts a write protection on the sectors. This is again
according to the procedure. All right. Set the option start bit in the
FLASH_OPTCR register. So, this step actually this third step. So, that's why
I have written this and than again wait till no active operation on the flash
and then again lock back So, simple. Isn't it? So, if you do this much then
write protection will be imposed on those sectors, which are encoded in the
argument sector details. Great. So, what if protection mode is equal 2.
Protection mode is equal to 2 means read/write protection. So, we come here
else if. All right. And we carry out the same procedure here also.
Instead of clearing that 31st bit we actually set it. And we do this, put read
and write protection on the sectors. So, we actually set each bit in the nWRP
bit field in order to activate the read and write protection. All right. And rest
all will be same. So, my suggestion is before trying this try to understand this
and you better not to try the protection mode 2. Don't try to put read and
write protection because that will again a lead to mass erase and all, if you
later want to change. So,but you can play with protection mode1, no problem.
That is just a write protection. But, you should also know how to clear it. So,
for clearing we use this disable code block. All right. So, this is used to
clear all the protection and coming back to the default state. Now, for that we
use this command. The next command that is bootloader disable read/write
protection and this disables active protections on all the sector. So, very
simple command. All right. So, this doesn't have any special field in the
command.
So, when the bootloader receives this command it just clears everything and
returns back to the default state. And the command code is 5C and let's look
at the handle function for this command and it is write here in the function
bootloader_handle_dis_rw_protect. All right. So, this also calls the same
function configure flash sector read/write protection which we just explored.
But,it doesn't use these 2 arguments. So, it just passes 1 to that disable field.
So, that's it. So, that means when we go here this disable will be 1. So, we
then come to this code block which actually resumes everything into the
default state. All right. Then it just returns 0. All right. So, that actually
disables all the active protection on the sectors. So, explore this before
trying and if you are unsure about anything then don't try it on your hardware
Otherwise, you will simply end up blocking some of your sectors and for
Windows users you are lucky because, you have this flash utility. You know
if something goes wrong then you can open the option bytes and you can
resume to the normal operation or you can resume to the default state. And as
I repeatedly saying don't ever try to go to the level 2 read protection. All
right. So, that is irreversible. Now, we are going to test this. So, now let's
test these 2 commands. Now, let me just save this and let me just compile this
code. All right. So, now let me just download the code so let me just close
this. All right. Now, I reseted my board and this is my host application it is
still working. All right. So, now first enable read/write protection. The
command is this one enable read/write protection. So, the command code
here is 9. So, let me press 9 and now it is asking me, how many sectors do
you want to protect? Now, let's say I want to protect 4. Now, you have to
enter the sector numbers let's say 0, 1, 2, 3. Now, it is asking enter sector
protection mode. Whether you want to set read protection or you want to put
read and write protection.
So, I want to put the write protection and here it is, the command is executed
on the board and now how to test it? We can use our read sectors status
command. Let's continue and sector status command is 11. All right. This I
have already implemented but this I have also given you as an exercise to
implement. All right. So, now let's hit enter and here is 0, 1, 2, 3. are write
protected. Great. Now, let's clear this. Now, let's press any key to continue,
and for clearing disable read/write protect, that is 13. So, let's give the
command 13, and press enter and it is success. And now let's again check the
status. That is 11. All right. And here it is everything got cleared. Great guys.
So, we have completed most of our commands and now there are some
commands are still pending like memory read, OTP read, sector protection
status read. So, all these commands you have to implement, you can always
add new command to this project. So, if you think this bootloader lacks
some features, then you can always you can add that feature to the bootloader
and you can test it.
SUMMARY OF THE
COMMANDS
And we have so for completed almost all the commands and which I taught I
will cover in this course. And of course, the bootloader project needs some
more improvements and more commands can be added. There are lots of
other features can be added like security, encryption etc. . And now, we have
come to an end and we have almost reach to the end of this project, and let
me quickly test whether all the commands are indeed working or not, or we
messed up something. So, now let me reset my board. The bootloader code is
running. Let me just check one by one. The first Get version command, it's
working. And Get help command, It's working. All right. So, we got the reply
and 3rd one is CID. Let's see. Yes, it's working. And the fourth one is Get
RDP status. Let's see it's working. Now, 5th one is Go To address. All right.
So, we have to jump to some address. Right? So, let's give the reset handler
address 80081D8. . All right. So, let's hit enter and you can check. So, let's
close this application let's open TeraTerm. All right. So, let's check in the
TeraTerm. Yes, It's working. All right. So, It's executing. Now, let's
disconnect this. Grate. So, the next command is MASS_ERASE 6, 1. So, that
is actually not supported. And now FLASH_ERASE is 7. Let's see, what is
that 7 is Flash erase. Enter sector number.
So, sector number. Let's say 2, enter number of sectors 1 and it is success.
Now, let's check in the Flash utility. Not this one, this one. Yes, it is FF.
Right? It's a working. Now, then press any key to continue the next command
is MEM_WRITE. Isn't it? 8 Enter the address. So, we will give the bas e
address affected to 0x08008000 and let's check connect. Yes. It is written.
Grate. Now let's disconnect, now press any key to continue and the next
command is read/write protect. So, which I have already shown you in the
previous video. Let's check the sector status 11. All right. So, after writing
you have to actually restart the bootloader. All right. So, I don't know
whether it's a bug or I have to fix this issue. So, now press any key to
continue. OK 11. Let's press 11 and these are the protection status on the
sectors. Great. So, all the commands are working as expected and try this at
your desk and in the next project I will explain about the host applications.
And what each file does in the project
HOST APPLICATION
SOURCE FILES AND
DETAILS
So, now let me quickly explain you the host application which I have written.
Now, the project is this one. The project name is let me go to the default
state. Yes. Now, let me just close everything. All right. So, this is the code
block . If you don't have code block then please install the code block and
this is the code block project or you can use any other IDEs. No problem.
Only thing is you have to use these source files as well as header files. All
right. Now, I am using Windows and this application runs currently on
Windows. So, of course, you need some modification to this project in order
to run it on a Linux or a Mac operating system. Now, remember that this
project has 2 parts. One is the operating system independent codes and
operating system dependent codes. All right. I think you should be able to see
these files WindowsSerialPort. c, OSxSerialPort. c and LinuxSerialPort. c.
So, these three files they are operating system dependent files and all the rest
are operating system independent files. So, in WindowsSerialPort. c I have
added codes in order to configure the serial port by using windows the serial
port APIs. So, this is WindowsSerialPort. c. There is an API called serial
port configuration, which uses Windows API to configure the serial port and
there is one API called read serial port, which uses this API in order to read
from the serial port, close the serial port, purge the serial port contents, and
write to serial ports. So, these APIs you cannot directly use on Linux. Thats
why, I have given you this file. So, you have to implement the same function.
All right. So, function name should not be changed. So, this name you have to
use in LinuxSerialPort. c also, but you have to implement by using the system
calls supported by your operating system.
So, this is a wrapper function or wrapper API. So, you have to replace this
with your operating system specific and you have to implement this here. So,
that's about the OS dependent codes. All right. And now let's go back to our
project. So, and after that all these other files are independent of the
operating system. So, you can reuse them. And BlCommands means
bootloader commands. So, all the command packet creation is implemented
in this file. So, I suggest you to go through this and I have written comments
where ever required and it is self explanatory. So, this function or this file
has one function which makes the command packet and then it sends over the
serial port. You can see here, it's very simple. For example, how the
GET_CID is implemented.
So, I have taken data buffer and fill the required details like command length,
command code, CRC information. Then just to write to the serial port. That's
it. I first write the length byte and then the remaining bytes and then read the
bootloader reply. All right. So, that's how the commands are implemented.
And the reply processing is implemented in a separate file or bootloader
reply processing. So, this takes care of processing the reply from the
bootloader. All right. So,read_bootloader_reply is a major function in this
file, which actually reads from the serial port. So, just go through this
function in order to understand more and than the file operation . c is to
handle the file related functionalities like file opening, file reading, closing,
seeking, etc. . So, this is required because we are going to open the file that
is user_app. bin. Isn't it? So, this is our binary file we have to read that file in
our memory write command and then we have to send it etc. . So, for this
fileops are implemented and this is not depending on any operating system.
And we have main. c, So, we actually prints the menu and then it calls this
function decode_menu_command, which is implemented in the bootloader
commands. c.
PROCEDURE TO ADD
YOUR OWN COMMAND
So, what you have to do? So, first you have to define that command, and you
have to select one unused command code for that. All right. And then go to
main. h. Here is a bootloader commands and you add that command at the
end. All right. So, for example, let's say #define
COMMAND_BL_MY_NEW COMMAND. Now, let me give one command
code for that, let's say 0x5D. Then you have to mention the command length.
Sorry. All right. So, then you have to mention the command length here.
Length details of the command. So, #define the length information you have to
give. All right. Let's say 8. All right. So, I don't know what exactly is a length
but let's say 8. So, you added the length. So, that's a second thing, that the step
number 2. And then you have to you know go to the commands. So, in the
decode_menu_command_code function you have to add one special case for
that command. Will go at the end, let's say case. So, this is a last command of
our project and you are new command has to come after this. So, what I
suggest to you is, simply copy this existing one and paste it, and increment
this case number to 14, and after that you have to use this code block in order
to populate your command details.
For example, for a time being let me remove this printfs. Here, first field will
always be you know length information. So, you have to paste that length
information by using your new command length. Isn't it? So, let's go to
command. So, this is a length. So, in the length you replace your command
length. All right. And this is a command code, second field in the command
packet is always the command code. Isn't it? So, this is the command code.
And if you have any other information in the command packet, then you have
to use this data_buf. In order to fill that information OK, and for the CRC
again replaced this length with your length. All right. So, this will be
common. All right. So, this actually calls this actually converts 4 bytes CRC
into bytes in little endian format. Great. Then you write that to serial port. So,
while writing remember that, the first length field will be sent first. That is 1
byte this is a length field. This is the length field. All right. So, one byte will
be sent first and then the remaining. So, we start from the second byte or
second field of the common packet and then we send the rest. All right. So,
here this must be my new command length. Then you read the bootloader
reply. Now, let's go to the bootloader reply.
Here is a implementation of that in the BlReplyProcessing. c. Now, let's
explore this. So here, you have to add one case for your command. All right.
So, let's add a new case. So, this is the new command and then you have to
add one process function. All right. So, now let me copy this first and let me
paste here. So, this is a function or helper function where that processing will
be taken care. Now, let's change is process helper function. All right. So, I
just copy this command name and let me paste here. All right. Great. So,
what it means, when it comes to the read_bootloader_reply. So, first ack will
be read and if ack is good. I mean if it is ask then we will read the reply of
the bootloader. Right? So, that's why we go to the appropriate process handle
function or helper function, in order to read the reply from the bootloader.
All right. So, you have to implement this in this file. All right. So,
somewhere here in the down. Here you can see, the lots of process
bootloader reply helper functions for each command. Now, let's say I
implement this process function here, somewhere here. All right. So, this
must be let's say uint32 len. All right. So, here you have to read the reply
from the bootloader. So, take an example of this. All right.
So, here I am reading by using read_serial_port. So, here I'm reading length
bytes. Similar thing you have to do according to your command and it's to
reply behavior. All right. And don't forget to add the prototype of this
function in main. h. All right. So, in the main. h you can see here bootloader
reply process prototypes. So, just add here. So, now let's try to build this
project let's see whether it builds or not. Let's first build. All right. So, there
are some problem. Let's build this. So, there are some problem here. So.
this is uint32_t. Sorry. All right. So, now it build successfully. And now let's
launch, run and here we can see that this menu is not showing your new
command. Isn't it? So, we have to add that command into the menu also.
So, for that go to main. c and here you have to add that entry. So,let's copy
this last command and paste it. All right. So, what is a our command? Name
this one. Right? COMMAND_BL_MY_NEW_COMMAND. So, let's go to
main. c, and let's add in the menu. So, let's say this is 14. All right. Now,
when you enter 14, that will be decoded by decode menu command function.
All right. So, it will come to 14 here. Where is 14? It will come here and you
can put some printf statement here. So,let's say this is this command my new
command and you prepare your data buffer, that is you prepare the command
bytes,command packet bytes, then you write. And then you immediately go to
read_bootloader_reply, and then you first read the ack here, 2 bytes of ack.
So,that's why, I call read_serial_port with 2 bytes and if it is really the ack
then we go and read the length here. So, here we can read the CRC of last
command was good. . received ACK and "len to follow". So, the second
field what we receive with the ACK is nothing but length to follow. So, that's
why, length to follow must be copied and should be passed to the individual
bootloader process handle functions. All right. So, we actually go here. Then
we go here actually. All right. So, and here you have to read these many bytes
and you have to print according to the reply. Let now let's build this, run and
here we can see new command is appeared here. And if I type 14, and there
is actually no response from the bootloader because, the handle function the
bootloader handle function in your firmware is not yet implemented. Now,
you have to implement handle function for this in the bootloader project also.
All right. So, for example, if I go to my bootloader project. So, this is a
bootloader project. First you have to go to the main. h. All right. So, first you
have to go to the main. h and in the main. h you have to add that new
command here. And after that go to main. c, and in the main. c here you have
to add a handle function for your command. All right. So, you can add at the
end, you can add after this command. And then you implement that handle
function. So, what that command needs to be done. You have to implement
that logic and then you reply the result back to the host. So, you should take
the help of these already implemented handle function. To see, how to
implement your own handle function for your new command. All right. Great.
So, that's about adding new command to the project. If you ever want to do
that so you can take these tips, and I think you should be able to add new
command and let me know if you face any issues.