This content was presented May 23rd, 2020
Video recording of content presented here: https://www.youtube.com/watch?v=kZgci9tl8-o&feature=youtu.be
Introduction
Welcome to firmware 102! By now you should’ve completed your environment setup and run the command to build and run a project on x86.
In this lesson we’ll be covering:
Running code
Writing code
Tests + Validation
GitHub and JIRA process
Recap
First, as a quick recap of the last lesson, here’s what you should know:
Firmware is code that runs as soon as a computer is powered on
...
The non bolded lines in the above diagram are the projects we have as firmware, the bolded lines are categories
We work remotely by using x86 versions of our libraries, which don’t necessarily work the same as the real versions we use on hardware
We have a 6 step process for completing a project, half of which is testing
We collaborate using GitHub and JIRA
Running code
Let’s talk about what “running on hardware” actually means.
Microcontrollers
At the end of the day, this is what runs your code:
STM32f0 Microcontroller (AKA Just a note, some specific libraries and configuration is needed for this session. Make sure you checkout to the FW 102 Branch, and then create your own branch off of that with your name.
In this lesson you’ll learn about the fundamental of embedded systems development. We’ll be covering:
Hardware Crash course
GPIO Functionality
Interrupts
Timers
Disclaimer: The libraries used in this tutorial have been changed in our new repo to become compatible with FreeRTOS. However, the old ones are very useful for teaching some of the key embedded concepts
Hardware Crash Course
There is a lot of overlap between firmware and hardware, so it is important that we build a conception of how all of our electrical projects work. Everything that we receive as inputs to our programs, or send as outputs to the surrounding system is in the form of digital or analog electric signals which have some inherent meaning in the real world.
Embedded systems in general are comprised of a computer, an electrical interface, and a surrounding system, and a solar car is no different. We will discuss each of the building blocks specific to our system.
1. Microcontrollers
So far, we’ve only been dealing with running software on your PC, but in reality our software runs on a separate “mini computer” in the car. These are Microcontrollers (aka. MCU, controller, STM, STM32, Integrated Circuit [IC], or other)
...
Each pin has a specific purpose, but at a high level, the pins are what connect to the other electrical parts of the car. Looking back at the firmware system overview, each project is programmed onto one of these. The one that was used for the MSXIV design is an STM32f0 chip, shown below:
...
The metal bits sticking out are called “pins”, and are essentially just wires which receive/send all signals to or from the MCU. Each pin has a specific purpose, and are what connect the computation part to the rest of the electrical system. The programs that we write can be loaded onto one of these bad boys, which then runs the code upon being powered, as discussed previouslyas soon as it receives power. These are like the CPU in your laptop, but 50x cheaper and simpler.
Hardware interlude
Looking at things from a different perspective, if you have to solder (gluing parts onto the board) these onto a PCB, you probably hate your life. One small thing goes wrong and you have to redo the whole board. Also, boards often undergo revisions from human error or design errors, so every time you make a new version of a board you’d have to redo the soldering for this. This is a PCB.
...
Instead, we have a universal controller board. Pictured below is the underside of the board.
...
Outlined in red below is the controller board mount, so you can imagine the above picture being flipped over and placed on top.
...
Pictured below is a controller board placed into its mount.
...
We have lots of these. So, hardware makes all their boards compatible with this one controller board, then we can reuse them for every project. The pins on the STM are passed through the connections in the slot so it can access the board’s functionality.
Getting code onto the controller boards
This doesn’t matter so much if you’re working remotely, but it’s still good to have a conceptual idea of how your code runs on real hardware.
(this isn’t our hardware, just a picture I found online)
...
The usb looking thing pictured above is called a programmer: it connects your computer directly to pins on the microcontroller.
Ours connects to the pins outlined below:
...
If you ever work with a hardware member to test your code, they’ll be using one of these to “program” a controller board. Programming a controller board means writing your code into its memory, then rebooting it so it runs your code.
Writing code
Next, let’s talk about actually writing code! We’ll go through a simple example to show how our programs are laid out.
...
This is from the project controller_board_blinking_leds
, which you can find here: https://github.com/uw-midsun/firmware_xiv/blob/master/projects/controller_board_blinking_leds/src/main.c . Don’t worry too much about the details, I just want to point out the main building blocks. This is an example project, but there’s some patterns we always follow.
1. We use an infinite loop structure. This is so we never have to restart the controller board. Otherwise the program would end and the controller board would just do nothing until it was restarted.
...
2. If a function takes a lot of arguments, we group them all together in a struct. If you ever see a struct called “settings”, it’s for wrapping together variables that would have to be passed as arguments. The parts the struct is used for are underlined in green.
...
3. We use libraries for doing things like interacting with hardware. Here we see the GPIO (general purpose input/output) library in action. Libraries are code someone else has written that help you do something. Here, the GPIO library sends the controller instructions to toggle a pin on and off. The GPIO library calls are underlined in blue. We’ll be covering exactly what this library does in more detail in a later lesson
The fact that we use libraries is actually really important to letting us work remotely - recall how we use different versions of the libraries depending on if we’re running the program on our computer or on the STM.
Looking at a real project, we split our files into three folders:
...
Any .c files go in src (source), any .h files go in inc (include), and any test files go in test. The rules.mk
file is for compiling the project, but you shouldn’t need to worry about it for a while.
...
Pictured above: header files should include public functions, while the source .c files may include extra private functions.
...
Here, the red underline is a private helper function not in the header file, while the blue underline is the function from the header file. Private functions should be prefixed prv_
.
A full list of our coding standards are here to review at your leisure: Coding Standards
Tests + Validation
There are three parts to this. Yes, testing is that important.
Unit testing: testing small parts of our code by writing more code. Ideally, each .c file other than main should have an associated test file that tests the logic. These test files are meant to be run on x86. They’re run every time you open a pull request on GitHub.
...
Here’s an example of a unit test from the tests for our GPIO library. It sets up the pin, asserts that it was set up correctly, then ensures checking the state returns the value it was initially set to (notice that in the init settings the state is HIGH).
Info |
---|
Our testing framework is Unity (not the game engine). Every test file has three parts:
|
Hardware testing: running the project on the actual hardware. Also, creating validation projects that test specific parts of the hardware, but not necessarily implementing the logic. We call these smoke tests. More documentation can be found here: Smoke Tests .
Integration testing: Wiring a bunch of boards together and making sure they play nice. Hopefully by this stage our code is well tested and the logic is bullet proof, but there’s always something that comes up here.
When working remotely, hardware testing will occur by either asking a hardware member to flash your code and then telling you the results, or by video calling and debugging the project together.
To test over virtualbox, the command make test PROJECT=<project> PLATFORM=x86
can be used to run all the tests in a project. The command make test PROJECT=<project> TEST=<test name> PLATFORM=x86
can be used to run a specific test in a project.
GitHub and JIRA process
JIRA
...
In JIRA we use what are called tickets for tracking tasks. There are five locations a ticket can be:
...
When tickets are created, they go to the backlog. Assigned means there’s a deadline, in progress means the ticket is currently being worked on. We’ll talk about code review in the GitHub portion. After code review is complete, the ticket is moved to done.
Tickets are then organized into ‘Epics’: groups of tickets that go together. Pictured above, Front Power Distribution in yellow is an epic, with two tickets falling under that epic.
We want to keep JIRA tickets just for concrete tasks. If you need to have a meeting, don’t create a JIRA ticket for it, note it somewhere else like on confluence.
Each JIRA ticket has a number, which will be important for GitHub.
For now, only the leads will create tickets. If you find an issue, report it to a lead.
GitHub
Once you’re assigned a ticket on JIRA, the next step is to create a branch on GitHub. If this is unfamiliar to you, read up on git or watch Arshan’s video lectures on the subject.
Branches should be prefixed soft_xyz
, where xyz
is the ticket number, and should have a descriptive name following. For example, soft_148_center_console_fault_handling.
Once you complete your task, you’ll open a pull request. This lets others see your changes before merging them into our master firmware branch. Before you do, make sure to run ‘make lint’ and ‘make format’ on your box! This formats your code and checks for formatting errors.
When you open a pull request, our continuous integration tool called Travis runs. This basically runs your code, and makes sure all the unit tests pass.
...
The blue outline means that Travis successfully ran your code and that all unit tests passed. The red highlight means changes were requested, so the person who opened the pull request needs to make those changes before merging it in.
This is where the code review part comes in: we’ll look through your changes and maybe ask for edits to be made. Once that’s done, you can merge in your changes and delete the branch.
Conclusion
That was a lot to get through, hopefully you made it! In conclusion, here’s what you should now have an understanding of:
How we run code on hardware
How we structure our files and projects
How we test and validate our code
How we use JIRA and GitHub to manage tasks
In the next tutorial, we’ll be covering how to read datasheets and schematics, as well as our basic libraries for GPIO, interrupts, and ADCs.
Homework
Follow as much of the Software 101 modules as you can while working remotely. Start here: Module 2: Hello World
Notes: Don’t worry too much about GDB debugging, in reality print statements (LOG_DEBUG
) is very often used. Also, don’t worry about the “Blinking LED - Registers” section, it’s more advanced and definitely not necessary going forward.
Next, create a new project in a new branch soft_999_{firstname_lastname}_fw_102
that does the following:
Defines a struct that stores two
uint8_t
counters,counter_a
andcounter_b
.typedef
this structCounters
.Uses a single soft timer to increment
counter_a
every half a second, andcounter_b
every second.LOG_DEBUG
the value of each counter whenever they’re updated.Doesn’t use any global variables
Implements all code in
main.c
Your output should look something like this:
...
If you run into problems, let someone know! No need to bash your head against a wall.
Hints:
Refer to the blinking LED tutorial for how to use soft timers.
Use a
while (true) {}
loop to keep the program running.Don’t forget to start another soft timer within your soft timer function call.
In your
main
function, create a counters struct, and pass a pointer to that struct to thesoft_timer_start_...
call as the context.Check the supplementary material: Firmware 102 Supplementary Material for more hints on how to use logging and soft timers
Once you’re done with the project, open a pull request and request a code review from a lead. We won’t actually merge your pull request, but it’ll be a good chance to learn how code review works.
Make sure to run make format
to autoformat your code and make lint
to catch any linting errors.
[Optional]: watch Arshan’s videos on GPIO, interrupts, and ADCs.
GPIO: https://www.youtube.com/watch?v=JXQVHOYHbdk&list=PLwHCeNgf9lKdt6LN6D54__moOb4Mkj5NQ&index=9
Interrupts:
https://www.youtube.com/watch?v=zGIE8bLAFqQ&list=PLwHCeNgf9lKdt6LN6D54__moOb4Mkj5NQ&index=10
ADCs:
https://www.youtube.com/watch?v=pIitjg4jfJg&list=PLwHCeNgf9lKdt6LN6D54__moOb4Mkj5NQ&index=11The other major difference is that the microcontroller is a comprehensive computer (instead of just a CPU), with memory and peripherals included.
2. Controller boards
There’s a bit of a problem with the chips seen above, in that they are effectively useless by themselves. You have no way of regulating voltages and currents into them, so they will fry as soon as you connect them up to a solar panel or whatever else you are trying to control. If you’ve used Arduinos, STM32 or any other development board, you’ll know that they provide a circuit board with the microcontroller. For us this is our “controller board” which is a PCB (Printed Circuit Board) designed in-house by the Hardware team. You can see what it looks like below:
...
The microcontroller is in the middle. Surrounding it are various electrical components, ICs, resistors and capacitors, and a “mezzanine” or breakout wiring plug on the far right. Each pin on this mezzanine is connected to a pin on the MCU.
Luckily we don’t need to worry about many of these components. We just need to know which pin (by number) connects where. Controller boards are the same across all the projects, and we allot one to each.
3. System Boards
While the controller board is great, it doesn’t allow us to interact with the broader system. For that we need other PCBs. The example below is from an old version of our steering system. It provides plugs and connections which we would connect to a rotary encoder, for example, as well as doing any electrical adjustment or signal processing to make sure we don’t fry our controller boards.
...
These boards provide the connections needed to make the signals in the system available to the controller board, which in turn makes it available to the MCU. You can see there is a plug on the right, this is where the mezzanine seen on the controller board plugs in.
Outlined in red below is the controller board mount, so you can imagine the above picture being flipped over and placed on top.
Pictured below is a controller board placed into its mount. You can also see many other components connected to the system board, almost all of which are connected directly to the MCU.
...
4. Flashing Controller Boards
You might be saying “Hey, we’ve got some neat controller boards with little computers on them, but how am I supposed to get my code on them?”. Great question, thanks for asking.
MCU’s provide protocols for transferring programs into the flash memory. This is a variation of EEPROM (electrically erasable programmable read-only memory). This is non-volatile memory (Will stay the same even when the device loses power), and it is where we store our program (don’t worry too much about the specifics).
For us, what this means is that we need a method of connecting our computer to the MCU and transferring the data over. Luckily, ST Microelectronics provides a super sweet and easy method of doing this over USB, called “ST-Link”. For us, this requires a few components:
...
There are 3 main components here. The usb connector piece is called a programmer. There is a cable which connects the pins on the programmer to the pins on the MCU, and a jumper wire which provides power to the board. This creates a direct connection between your computer directly and the microcontroller.
If you ever work with a hardware member to test your code, they’ll be using one of these to “program” a controller board. Programming a controller board means writing your code into its memory, then rebooting it so it runs your code. We have command line arguments to do this, so it’s really not too tough to figure this out!
Embedded Concepts
Before we get to writing actual firmware, we will discuss a few concepts that are used in almost all embedded systems. These are:
GPIOs
Interrupts
Timers
GPIOs
GPIOs, or General Purpose Input Outputs are a specific type of pin on an a microcontroller which can be programmed to perform certain functionality. This GPIO Tutorial provides a great intro to the idea of what they are and how our GPIO library works. Essentially we can use them for peripheral interfaces (like I2C, SPI, CAN) or as inputs or outputs to control/read from the system.
Our GPIO Library can be found at fwxv/libraries/ms-common/inc/gpio.h
, and has the following types and functions available:
Types:
GpioAddress
- This is how we can refer to a specific pin on the MCU. The address is comprised of a port and a pin.
GpioDir
- The direction of the pin. This determines whether it is an input or an output. (There is also open-drain but don't worry about this for now)
GpioState
- These are the possible states of the GPIO when reading it or setting it to a logical value. High represents a pin state of 3.3V, Low represents a state of 0.0V.
GpioRes
- This sets the default position of the the pin to low (pulldown) or high (pullup).
Code Block | ||
---|---|---|
| ||
// Initializes GPIO globally by setting all pins to their default state. ONLY
// CALL ONCE or it will deinit all current settings. Change setting by calling
// gpio_init_pin.
StatusCode gpio_init(void);
// Initializes a GPIO pin by address.
StatusCode gpio_init_pin(const GpioAddress *address, const GpioSettings *settings);
// Set the pin state by address.
StatusCode gpio_set_state(const GpioAddress *address, GpioState state);
// Toggles the output state of the pin.
StatusCode gpio_toggle_state(const GpioAddress *address);
// Gets the value of the input register for a pin and assigns it to the state
// that is passed in.
StatusCode gpio_get_state(const GpioAddress *address, GpioState *input_state); |
gpio_init()
must be called at the start of your main function. It effectively sets up the gpio library, and initializes it for use in your programgpio_init_pin()
method configures a pin at a specific address to a specific functionality, and direction (input or output). The default is just to treat the pin state as a binary value (Logical 1 if it is above a certain voltage threshold, or 0 if below).gpio_set_state()
andgpio_toggle_state()
are used when the pin is treated as an output. gpio_set_state sets the state of the pin to high (a logical 1, or 3.3V), or low (logical 0, or 0V)/gpio_get_state()
reads the current state of the pin as an input. If the voltage is high, then it returns GPIO_STATE_HIGH, otherwise GPIO_STATE_LOW
To get a GPIO pin ready for use, we need to do 3 things.
Call gpio_init() at the start of your main program.
Call gpio_init_pin() for the specific pin you want to use. You need to pass into this function the settings you are trying to configure your pin with, and the address of the pin you are trying to configure.
Call the function to interact with the GPIO pin. This could be gpio_get_state() if the pin is configured as an input, or gpio_<set/toggle>_state() if the pin is setup as an output.
Task 1 - GPIO toggle program
For our first task we are going to modify the program from last week to toggle some LEDs! If you remember from last week, we used a delay to print to the screen every second. We will now toggle the state of an LED every second.
Setup
We need to do a few things to get setup (If you don’t have your project from last week, create a new one, and add a main.c file):
We need to include “gpio.h” at the top of our program, as this allows us to use all the GPIO types and functions discussed above
We need to call gpio_init() (with no parameters) at the start of our main function.
Next we need to configure our GPIOs. We can build a GPIO settings struct in our main function (after we called gpio_init). LEDs are just a specific pin on the board, which can have a High (light on) or Low State. The difference is that we can see when they are on/off! To use our LEDs on the board, we need to configure them with the following settings:
GPIO_DIR_OUT (the LEDs are an output)
GPIO_STATE_LOW (the lights will start off)
GPIO_ALTFN_NONE (just treat as normal GPIOs)
GPIO_RES_NONE (Don’t worry about setting a default state)
There are places for each of these parameters in the settings struct, see if you can figure out which is which! If you are not sure how to create a struct of a certain type, read through this article or ask a lead.
The next step is calling gpio_init_pin for each of our LED pins (there are 4). The following are the addresses for each.
LED | Port | Pin |
---|---|---|
1 | GPIO_PORT_B | 5 |
2 | GPIO_PORT_B | 4 |
3 | GPIO_PORT_B | 3 |
4 | GPIO_PORT_A | 15 |
You will need to create an address struct for each pin (same format as the settings struct). However, since the settings are the same for all of them, you will only need one of those structs.
This is what the gpio_init_pin function looks like.
Code Block |
---|
gpio_init_pin(const GpioAddress *address, const GpioSettings *settings); |
As you can see, it takes a pointer to an address, and a pointer to settings. We will need to call this function 4 times (for each address), passing it pointers to the correct structs in the correct position.
Operation
Now that we’ve got the setup out of the way, the actual functionality of the program is much easier. All we are going to do is, in our while loop, call gpio_toggle_state() for each pin, with a 1 second delay in between them. I will leave you to your own devices for this part but don’t be afraid to ask if you get stuck
Once this is done, make sure you can build and run your project using the scons sim command, and then send your main.c file to a lead to run it on hardware!
Interrupts
Interrupts are a somewhat complex topic, but we can discuss them solely in terms of how we use them in our system. (For a video version of this content, see here).
When a program is running, instructions will execute sequentially, starting with the entry point to the program (the main function). However, sometimes we want to change the flow of execution based on external or internal events. This is where interrupts come in. They “interrupt” the normal flow of execution, and cause a separate set of instructions to be executed.
One of the main interrupt sources in our system are caused when external signals change the state of one of our GPIO pins. This could be a signal from a sensor indicating that data is ready to be collected, or many other things, but we will look at the case of a button press.
Assuming that everyone has used a button before (I really hope), you know that a button is generally pressed, or not pressed. In electrical systems, this press will change the state of a circuit to open (no current) or closed (Current flows). This is what a button looks like in a schematic:
...
S1 is the button itself, which is connected to a 3V input, and the circuit which mediates the signal for the Microcontroller (Fun Fact for any EE friends: the capacitor prevents “bouncing”, which occurs when the button is released and oscillates between on and off). The BTN1 line that goes off to the right connects to a pin of the MCU which we can use to read the button state. When the button is pressed, the circuit will connect, and the pin will go to 3.3V which is the high state.
So let’s say we want to read the button state and use it in our program. We know that the button state is a one or a zero, so after initializing our GPIO to the correct settings we can continuously call gpio_get_state to see if the pin connected to the button has gone high, something like this:
Code Block | ||
---|---|---|
| ||
GpioState out_state;
while(true) {
// Sets the value of out_state to current state of input pin at my_address
gpio_get_state(&my_address, &out_state);
if (out_state == GPIO_STATE_HIGH) {
// Do button processing
prv_turn_on_light();
}
} |
However, this is not a good approach. We are continuously needing to check the state of the button, which means we can’t do other things in our program. Ideally, we would wait until the button is pressed, and then do the processing we need without having to do continual checking. We can do this with interrupts!
Our library for configuring and using interrupts with gpios can be found at fwxv/libraries/ms-common/inc/gpio_it.h
.
This is the main method that is used for GPIO interrupts:
Code Block |
---|
StatusCode gpio_it_register_interrupt(const GpioAddress *address, const InterruptSettings *settings,
InterruptEdge edge, GpioItCallback callback, void *context); |
This method registers an interrupt to trigger a “callback function” when a certain event occurs. As you can see, it’s got quite a few parameters! Let’s talk about each one:
GpioAddress
- This one we've already seen. This is the specific pin which we are configuring the interrupt forInterruptSettings
- Don't worry too much about this one. It will pretty much always be
type = INTERRUPT_TYPE_INTERRUPT, and priority = INTERRUPT_PRIORITY_NORMALInterruptEdge
- This is important. Interrupts need to detect a change of state to trigger. This is called an "edge". If we want the interrupt to trigger when the pin goes from high to low, we set the edge to INTERRUPT_EDGE_FALLING. If we want it from low to high, it can be set to INTERRUPT_EDGE_RISING, or INTERRUPT_EDGE_RISING_FALLING to trigger on either. Which one is it for the button?GpioItCallback
- This is the function which will be called when the interrupt is triggered*void *context
- This can be set to NULL for now. Effectively it allows you to pass a data type to the callback when it is called.
Info |
---|
*This is what is called a function pointer type. Basically, it is expecting a function with a certain return type and parameters. For the gpio_it callback, this must be a function of the following form: You can simply pass the name of this function as the GpioItCallback parameter |
Task 2 - Interrupt Driven LEDs
For this task, we are going to add to our program to use a button to change the state our LEDs.
Setup
First we need to add a couple things that we need now that we are using interrupts.
We need to include “interrupt.h”, “gpio_it.h” and “wait.h”
We need to call interrupt_init() at the start of our main function, right above gpio_init()
We will also need to do another GPIO initialization for our button pin (all of the LED initializations can stay the same). It will need a different GpioSettings struct with the following parameters:
Direction:
GPIO_DIR_IN
- This pin is acting as an inputState:
GPIO_STATE_LOW
- This could also be left blank, it only really matters for outputsResistor:
GPIO_RES_PULLDOWN
- This part is important. If we don’t use a resistor, the pin is liable to float (which means exist in a voltage somewhere between low and high, bad for us). By using a pulldown resistor, whenever the button is not pressed the pin is guaranteed to be low, preventing faulty interrupt triggeringAlt function:
GPIO_ALTFN_NONE
- Don't need to worry about this, just regular GPIO operation
Operation
We need to alter the functionality of our program as well. First we must create a callback function for our interrupt. This should have the following prototype (the return type and parameters must be the same, but the name can be whatever you like):
void prv_my_gpio_it_callback(GpioAddress *address, void *context) {...}
Next, move all of the gpio_toggle_state() function calls to inside this callback. This will cause the lights to change states whenever the button is pressed.
Next we need to register our interrupt using the gpio_register_interrupt() function. See if you can figure this part out based on the description above. It should come right after the gpio initializations. Use port A and pin 7 for your button.
Finally, remove the delay calls, and change your while true loop at the bottom of your function to the following:
Code Block |
---|
while(true) {
wait();
} |
This will prevent the program from exiting while we wait for an interrupt to happen. Make sure your code compiles and runs on your computer, and then take it to a lead to run it on hardware!
(If you are doing the training remote or cannot access hardware, you can use the function gpio_trigger_interrupt() to mock the button behaviour. By adding this function call and a delay_ms() to your while true loop, it will trigger the interrupt periodically.
Timers
Another really important embedded concept is that of time, and having functionality that is time-dependent. We’ve gone into this a little bit by using our delay library. We can call the delay function to cause the program to wait for a certain period, and this allows us to make something every fixed amount of time.
This delay function (in the way that we’ve been using it) is what’s known as a busy wait. Since we know how fast a cpu can perform one operation based on it’s clock speed (usually on the order of a few gigahertz) we can how many operations we need to perform to pass a certain amount of time. This is how the delay_ms function we have used works.
The issue with this is that it by nature blocks the cpu with useless computation until that time has passed. This is super power consumptive and bad practice. If we want to use periodic functionality in our projects, we need to use our other timer solution, interrupt-driven software timers.
Defined in fwxv/libraries/ms-common/inc/soft_timer.h
, this library allows you to create and start a software timer. Effectively, this timer will run in the background until it completes, at which time it will trigger an interrupt and call a callback function (pretty much exactly the same way that gpio_it works).
To start a timer, all you have to do is call the following function:
Code Block | ||
---|---|---|
| ||
soft_timer_start(uint32_t duration_ms, SoftTimerCallback callback, SoftTimer *timer) |
duration_ms
- the time that the soft_timer will wait for before triggering the callbackcallback
- the callback to be called. The prototype is slightly different from the GpioItCallback, but otherwise it’s the same ideatimer
- Allows you to select a specific timer, for our purposes can just be set to NULL. (One will be created for you automatically)
Task 3 - Timed LED Flashing
For our last task in this session, we are going to change our interrupt-driven LED program to use software timers to turn off and on every second.
Setup
We need to include “soft_timers.h” to be able to use the timer functionality. It does require interrupts to be initialized (interrupt_init()) but we have that already in our program.
Operation
We still want our callback function to do the same thing, but we need to change the prototype to fit the template of a SoftTimerCallback. It is shown below. I will leave it to you to try and figure out what you need to make the function look like. The syntax used is to define a type of function pointer with a return type, and specific parameter types.
Code Block | ||
---|---|---|
| ||
// Soft timer callback, called when soft timer expire
typedef void (*SoftTimerCallback)(SoftTimerId id); |
Next, you can remove the button initialization (we will still need the LED initializations), and replace it with the call to soft_timer_start(). See if you can figure out what to put in the parameters yourself 😁
Once you’ve setup your soft_timer you can try to compile and run your program. A quick hint, you will need to have a call to soft_timer start in more than one place, since the timer will only run once. Can you figure out a good place to put it?
Once you think you’re done, take your code to a lead, and they will take a look on it and try and run it on hardware.
Conclusion
Congrats! You’ve finished all the tasks for FW 102! You have created your first real firmware programs. For some context, this is what a main file from the steering project in the old car looked like:
...
In the next tutorial, we’ll be starting an introduction to FreeRTOS. Thanks for coming out, and looking forward to seeing you there!