| You are not Logged In! |
Difference between revisions of "Public:Firmware"
(Outline and first sections) |
m (Scottjg3 moved page General:Firmware to Public:Firmware: Allow public access to firmware pages) |
||
| (8 intermediate revisions by 3 users not shown) | |||
| Line 1: | Line 1: | ||
| − | + | [[Wikipedia:Firmware|Firmware]] is the code we write for the embedded controllers on the car. Some of this code is perhaps high enough level to be considered software, but we write is as one integrated system and refer to it all as firmware. Our firmware is all in C/C++ and we run it with the ARM Mbed OS. Our firmware setup thus far has been heavily based off of that from [[CalSol]]'s [[Zephyr]]. | |
| − | [[Wikipedia:Firmware|Firmware]] is the code we write for the embedded controllers on the car. Some of this code is perhaps high enough level to be considered software, but we write is as one integrated system and refer to it all as firmware. Our firmware is all in C/C++ and we run it with the ARM Mbed OS. | ||
{{RemarksBox Info|Title=Firmware Standards|Text=Make sure to see also our current standards for writing firmware: [[Firmware Standards]]}} | {{RemarksBox Info|Title=Firmware Standards|Text=Make sure to see also our current standards for writing firmware: [[Firmware Standards]]}} | ||
| Line 17: | Line 16: | ||
=== MBed === | === MBed === | ||
| + | We use a fork of the ARM Mbed operating system. Our fork is actually a for of the [[CalSol]] fork. CalSol has used MBed for a while and several of their team members have made contributions back to the OS as they have extended its features or mostly improved support for the LPC15xx chipset they also use. | ||
| + | |||
| + | Argo runs on a modified (by Calsol) version of MBed OS version 2. Brizo's firmware is also being based on this to start, however an update to v5, or now even v6, needs to happen by the end of Brizo's life. | ||
| + | |||
| + | Changes to MBed aren't that common a thing that we do at the time being, as we are on a relatively stable and mature version of OS2, however upgrading will require more changes to our MBed fork. | ||
=== C++ Versions and Libraries === | === C++ Versions and Libraries === | ||
| + | Writing C++ for the embedded environment is different than writing c++ for running on a normal computer. Aside from the fact we write at a lower level, we also do not have access to external libraries. For example, <code><iostream></code> , depending on how exactly you use it, will alone be larger than the memory on our MCUs. Aside from some simple c standard libraries (like stdint) we do not use any standard libraries. Everything else we have available is in MBed or written ourselves. If you are used to writing C++ for a desktop environment, or even a Raspberry PI, this will probably be an adjustment. | ||
| + | |||
| + | '''C++ Versions:''' | ||
| + | |||
| + | C++ has added some nifty features in recent versions, especially 11 and 14, however it also deprecated a lot of things leaving code not compilable with the new version. Keep in mind the version we are using when writing your code, this varies by project/library: | ||
| + | * MBed OS2: C++0x (compiled with a compiler version from before it was officially 11 - originally, before delay, it was going to be 08 or 09, hence 0x) | ||
| + | * Argo and Brizo Common: C++11 | ||
| + | * Argo Projects: C++11 | ||
| + | * Brizo Projects: C++11 (although probably could update to C++14 without much work) | ||
=== Hardware === | === Hardware === | ||
| + | All of our code is written for custom hardware, meaning everything is tailored to the board, but we do keep the most important thing in common across them all - the Microcontroller. We use the LPC1549 chip for the most apart (although its sibling chips, the 1517, 1519, and 1547 have also been used in limited places). This keeps a lot of things similar. The LPC15xx series is ARM Cortex M3, have built in CAN, three Uart, two SPI, I2C, several ADC's, a DAC, 4 Clocks/Timers/PWM, and more. The LPC1549 has 256k Flash, 4k EEPROM, 36k RAM, USB, and either 30, 44, or 76 GPIO depending on the package. See the [https://www.nxp.com/docs/en/data-sheet/LPC15XX.pdf datasheet] for more. | ||
| + | |||
| + | The MCU was chosen because it has relatively good support and enough features that we generally don't have to worry about running up to the limits, while still being low power and low cost. | ||
== Communication Protocols == | == Communication Protocols == | ||
| + | There are a lot more communication protocols than just these below, but they are the most common ones we use a lot in our systems (excluding the basic digital and analog signals). | ||
=== CAN === | === CAN === | ||
| + | CAN Stands for controller area network. It is the BUS that connects the boards to each other across the car. It is by far the most complicated protocol we use, but also the most well and strictly defined. TI has a decent technical [https://www.ti.com/lit/an/sloa101b/sloa101b.pdf?ts=1606726118532&ref_url=https%253A%252F%252Fwww.google.com%252F introduction] you can read for the technical info. (For reference, we use a custom CAN implementation that is a bit of a combination of things from CANOpen and SAE J1939 CAN Protocols). | ||
| + | |||
| + | At the very least you should know the following high level information, the low level stuff for CAN is implemented by MBed so you just need to know how the system works at a high level so you can use it effectively: | ||
| + | |||
| + | CAN is a highly robust network protocol designed for high electrical noise environments, especially vehicles. We also use highly robust cabling to augment this, with shielding, isolated power, etc. Each CAN message has an ID and a payload of 1 to 8 bytes. The CAN controller (hardware on the MCU) then transforms this into a CAN frame which includes this ID and Data as well as some more start/end frames, CRC for data integrity, and some more control information. | ||
| + | |||
| + | The messages are sent on the bus with a priority system, the lower ID the higher the priority. Therefore we organize the bus carefully (see [[GitHub/CAN]] for more info). On our CAN bus we primarily use messages that are sent are regular intervals for most data and some messages that are sent only when a certain condition is met. | ||
| + | |||
| + | Every board received every message and has to handle them. The CAN controller will hold a few in the buffer, but it needs to be serviced regularly to clear that buffer. Each board needs to know how to "unpack" the data for a given message. It will get the message from the controller with the ID and the raw data, it must know how to interpret the data. | ||
| + | |||
| + | [https://os.mbed.com/handbook/CAN See the MBed OS2 CAN Documentation] | ||
=== I2C === | === I2C === | ||
| + | Inter-integrated circuit, also know as I2C (pronounced I-two-C) or I<sup>2</sup>C (pronounced I-Squared-C), is a very common on board communication protocol designed for on-board, short distance communication with lower speed peripherals. It is very common for things like I/O Expanders, Displays, ADCs or DACs, some types of memory, etc. Often these devices may support both I2C and SPI, discussed below. The [[Wikipedia:I²C|wikipedia article]] on I2C is a pretty good overview, including basic technical stuff. | ||
| + | |||
| + | In I2C the MCU is the master node and all other nodes the slave node. The slaves only respond when addressed. There is one clock line and one data line. The MCU will send a message with a device address and a message, then the peripheral device will respond to the command (if valid) with an acknowledgement, the requested data, etc. depending on the request. I2C supports speeds up to 5MBits per second. The LPC15xx can only do up to 1MBit/s, but many peripherals cannot do faster than that, and you have to operate at the speed for your slowest device. | ||
| + | |||
| + | Each device has an address that is included in the message to indicate that the message is for that device. On some devices this will be set in hardware on the chip, in others you can change some or all of it by pulling pins high or low. Address clashes are one of the biggest problems with I2C, you may want to use 4 of a device but it only has two address options, which makes that impossible. However, make sure to check as some manufacturers will sell the same device with different address options under different product numbers. | ||
| + | |||
| + | I2C is also implemented by MBed, but there is some configuration you have to do. Additionally, the interface for each device is defined by that device, so you'll have to understand those. See the [https://os.mbed.com/handbook/I2C MBed OS2 I2C Documentation] | ||
=== SPI === | === SPI === | ||
| + | Serial Peripheral interface is the other most common communication protocol to use between MCUs and peripheral devices. Unlike I2C it is rated for off-board use for a short distance, for example to devices like an encoder that might be embedded in a mechanical device a dozen cm away. For technical details the [[Wikipedia:Serial Peripheral Interface|wikipedia article on SPI]] is one of the better Wikipedia articles. Generally, you can thing of each device as just a shift register shifting data in/out. It sends the data serially, one at a time. | ||
| + | |||
| + | SPI buses have at least 4 signals. In fact they have 3 + N signals where N is the number of independent slave devices as you need a chip select signal for each of device. There will then be shared Clock, MOSI (Master Out, Slave In), and MISO (Master In, Slave Out) lines. The speed of a SPI bus is limited by the hardware, the definition itself has no max speed like I2C. You can also change speeds per device as they should ignore everything when their select signal is not on. Additionally, the separate data lines are utilized | ||
| + | |||
| + | As a protocol SPI is pretty loosely defined. Each peripheral will specify their own data frame in both size and organization. There are even 4 different clock/data arrangements to chose from. So it is not uncommon to need to reconfigure the SPI controller for each device. Additionally, this means you have to write the code to pack the data for each SPI device, unlike CAN or I2C where you just give and address/id and some data and it puts it together. Each device is different and the interface design quality can vary quite a bit - some easy to implement and some can be a pain. | ||
| + | |||
| + | The biggest challenge with SPI is that you need a slave select signal for each device, this can eat up a lot of I/O pins. To help remedy this, many devices intended to be used in groups, support daisy chaining. This would allow you to, for example, send one 32bit frame instead of two 16 bits ones. The hardware is configured to allow data shifts through the first device into the next. This saves a select pin and speeds up communication as switching devices takes some time. | ||
| + | |||
| + | The [https://os.mbed.com/handbook/SPI Basics of SPI is implemented by MBed], but since there is a more freedom within the standard there is a lot more configuration to do for each device, including creating the data frame, which is put together for you in other protocols. | ||
=== UART === | === UART === | ||
| + | UART is Universal Asynchronous Receiver/Transmitter. It is less common than the ones above in our systems, but still a good option you should know about. Unlike SPI and I2C, UART is asynchronous - there is no clock. As a perk of this that means there is just 2 wires, one for data each way - in some use cases (read only devices) there is just one data-line. However that means that all your devices need to independently know and run at the Baud rate. UART is not very fast (max baud is 115200, however 9600 is pretty standard) and it supports one device per bus. The UART Data Frame is pretty simple, there is the start bit, up to one byte of data, a parity bit, and a stop bit. You can read more on the technical side at: [https://www.circuitbasics.com/basics-uart-communication/ Circuit Basics] | ||
| + | |||
| + | On our microcontrollers there are two ways to do UART. You can bit bang it (not ideal) or you can use the USB driver which is essentially the same thing. MBed has a serial driver that does pretty much all the work from a firmware standpoint of UART. UART is a pretty strictly defined and simple protocol so you don't have much freedom anyhow. The MBed class for implementing UART is the Serial Class. [https://www.circuitbasics.com/basics-uart-communication/ MBed OS2 Serial Documentation] | ||
| + | |||
| + | == Common (and uncommon) Issues == | ||
| + | Code will always have issues until it is very rigorously tested. Even the best programmers make mistakes and mistakes can still exist after countless reviews. Of course there are almost an infinite set of posible bugs in any piece of code so it's impossible to provide a guide of all possible problems but this list will attempt to provide some guidance. | ||
| + | |||
| + | * Pin assignments | ||
| + | ** make sure all pins are correctly assigned | ||
| + | *** make sure not to mix up for exampl P0_4 and P1_4 | ||
| + | *** make sure you're looking at the correct version board schematic | ||
| + | *** make sure no pins are being used twice | ||
| + | * tickThreshold(...) != threshold(...) | ||
| + | ** for threshold you need to update the last time | ||
| + | * Check for any build warnings | ||
| + | ** there are a few expected build warnings with our current setup but make sure there are no others. | ||
== Firmware Resources == | == Firmware Resources == | ||
Latest revision as of 20:16, 8 September 2026
Firmware is the code we write for the embedded controllers on the car. Some of this code is perhaps high enough level to be considered software, but we write is as one integrated system and refer to it all as firmware. Our firmware is all in C/C++ and we run it with the ARM Mbed OS. Our firmware setup thus far has been heavily based off of that from CalSol's Zephyr.
| Firmware Standards | |
| Make sure to see also our current standards for writing firmware: Firmware Standards | |
Overview
We write firmware for each system on the car (Battery Management, Dash, etc.). Each system has its own special requirements and hardware that it has to work on, but we have standardized many things for each board and try to use a similar style for each. For one, we base code for each project on the same starter project so that people can change projects and still be familiar with what is where. Additionally, we use the same Operating System, same Micro controllers, and same tool chain across them all.
Structure
For each project that is built there are actually three different things being built - MBed, Common, and the project. MBed and Common are compiled as static libraries and then linked to the main binary for the project. MBed and Common are consumed by every project, and thus changes there must be incorporated to every project and should be made with that in mind.
Within each project we maintain a similar structure as well. See Firmware Standards for current standards for firmware.
FAQ: Should my class/driver be in Common or my specific project? It should be in your project unless the answers to each of the following is yes:
- Is this something that we could reasonably use on other boards? For example a driver for the battery voltage measurement chip probably does not have a use anywhere else in a solar car while the Analog to Digital converter used for current measurement could also be used for other things on other boards.
- Am I able to write and test this code in a sufficiently generic manor? Code in common should be relatively generic and tested for more generic usages. If the board implements this hardware in a very specific way and you don't have the ability to write or test for anything else the code is probably not ready for common. If extended in the future it is always easy to move.
MBed
We use a fork of the ARM Mbed operating system. Our fork is actually a for of the CalSol fork. CalSol has used MBed for a while and several of their team members have made contributions back to the OS as they have extended its features or mostly improved support for the LPC15xx chipset they also use.
Argo runs on a modified (by Calsol) version of MBed OS version 2. Brizo's firmware is also being based on this to start, however an update to v5, or now even v6, needs to happen by the end of Brizo's life.
Changes to MBed aren't that common a thing that we do at the time being, as we are on a relatively stable and mature version of OS2, however upgrading will require more changes to our MBed fork.
C++ Versions and Libraries
Writing C++ for the embedded environment is different than writing c++ for running on a normal computer. Aside from the fact we write at a lower level, we also do not have access to external libraries. For example, <iostream> , depending on how exactly you use it, will alone be larger than the memory on our MCUs. Aside from some simple c standard libraries (like stdint) we do not use any standard libraries. Everything else we have available is in MBed or written ourselves. If you are used to writing C++ for a desktop environment, or even a Raspberry PI, this will probably be an adjustment.
C++ Versions:
C++ has added some nifty features in recent versions, especially 11 and 14, however it also deprecated a lot of things leaving code not compilable with the new version. Keep in mind the version we are using when writing your code, this varies by project/library:
- MBed OS2: C++0x (compiled with a compiler version from before it was officially 11 - originally, before delay, it was going to be 08 or 09, hence 0x)
- Argo and Brizo Common: C++11
- Argo Projects: C++11
- Brizo Projects: C++11 (although probably could update to C++14 without much work)
Hardware
All of our code is written for custom hardware, meaning everything is tailored to the board, but we do keep the most important thing in common across them all - the Microcontroller. We use the LPC1549 chip for the most apart (although its sibling chips, the 1517, 1519, and 1547 have also been used in limited places). This keeps a lot of things similar. The LPC15xx series is ARM Cortex M3, have built in CAN, three Uart, two SPI, I2C, several ADC's, a DAC, 4 Clocks/Timers/PWM, and more. The LPC1549 has 256k Flash, 4k EEPROM, 36k RAM, USB, and either 30, 44, or 76 GPIO depending on the package. See the datasheet for more.
The MCU was chosen because it has relatively good support and enough features that we generally don't have to worry about running up to the limits, while still being low power and low cost.
Communication Protocols
There are a lot more communication protocols than just these below, but they are the most common ones we use a lot in our systems (excluding the basic digital and analog signals).
CAN
CAN Stands for controller area network. It is the BUS that connects the boards to each other across the car. It is by far the most complicated protocol we use, but also the most well and strictly defined. TI has a decent technical introduction you can read for the technical info. (For reference, we use a custom CAN implementation that is a bit of a combination of things from CANOpen and SAE J1939 CAN Protocols).
At the very least you should know the following high level information, the low level stuff for CAN is implemented by MBed so you just need to know how the system works at a high level so you can use it effectively:
CAN is a highly robust network protocol designed for high electrical noise environments, especially vehicles. We also use highly robust cabling to augment this, with shielding, isolated power, etc. Each CAN message has an ID and a payload of 1 to 8 bytes. The CAN controller (hardware on the MCU) then transforms this into a CAN frame which includes this ID and Data as well as some more start/end frames, CRC for data integrity, and some more control information.
The messages are sent on the bus with a priority system, the lower ID the higher the priority. Therefore we organize the bus carefully (see GitHub/CAN for more info). On our CAN bus we primarily use messages that are sent are regular intervals for most data and some messages that are sent only when a certain condition is met.
Every board received every message and has to handle them. The CAN controller will hold a few in the buffer, but it needs to be serviced regularly to clear that buffer. Each board needs to know how to "unpack" the data for a given message. It will get the message from the controller with the ID and the raw data, it must know how to interpret the data.
See the MBed OS2 CAN Documentation
I2C
Inter-integrated circuit, also know as I2C (pronounced I-two-C) or I2C (pronounced I-Squared-C), is a very common on board communication protocol designed for on-board, short distance communication with lower speed peripherals. It is very common for things like I/O Expanders, Displays, ADCs or DACs, some types of memory, etc. Often these devices may support both I2C and SPI, discussed below. The wikipedia article on I2C is a pretty good overview, including basic technical stuff.
In I2C the MCU is the master node and all other nodes the slave node. The slaves only respond when addressed. There is one clock line and one data line. The MCU will send a message with a device address and a message, then the peripheral device will respond to the command (if valid) with an acknowledgement, the requested data, etc. depending on the request. I2C supports speeds up to 5MBits per second. The LPC15xx can only do up to 1MBit/s, but many peripherals cannot do faster than that, and you have to operate at the speed for your slowest device.
Each device has an address that is included in the message to indicate that the message is for that device. On some devices this will be set in hardware on the chip, in others you can change some or all of it by pulling pins high or low. Address clashes are one of the biggest problems with I2C, you may want to use 4 of a device but it only has two address options, which makes that impossible. However, make sure to check as some manufacturers will sell the same device with different address options under different product numbers.
I2C is also implemented by MBed, but there is some configuration you have to do. Additionally, the interface for each device is defined by that device, so you'll have to understand those. See the MBed OS2 I2C Documentation
SPI
Serial Peripheral interface is the other most common communication protocol to use between MCUs and peripheral devices. Unlike I2C it is rated for off-board use for a short distance, for example to devices like an encoder that might be embedded in a mechanical device a dozen cm away. For technical details the wikipedia article on SPI is one of the better Wikipedia articles. Generally, you can thing of each device as just a shift register shifting data in/out. It sends the data serially, one at a time.
SPI buses have at least 4 signals. In fact they have 3 + N signals where N is the number of independent slave devices as you need a chip select signal for each of device. There will then be shared Clock, MOSI (Master Out, Slave In), and MISO (Master In, Slave Out) lines. The speed of a SPI bus is limited by the hardware, the definition itself has no max speed like I2C. You can also change speeds per device as they should ignore everything when their select signal is not on. Additionally, the separate data lines are utilized
As a protocol SPI is pretty loosely defined. Each peripheral will specify their own data frame in both size and organization. There are even 4 different clock/data arrangements to chose from. So it is not uncommon to need to reconfigure the SPI controller for each device. Additionally, this means you have to write the code to pack the data for each SPI device, unlike CAN or I2C where you just give and address/id and some data and it puts it together. Each device is different and the interface design quality can vary quite a bit - some easy to implement and some can be a pain.
The biggest challenge with SPI is that you need a slave select signal for each device, this can eat up a lot of I/O pins. To help remedy this, many devices intended to be used in groups, support daisy chaining. This would allow you to, for example, send one 32bit frame instead of two 16 bits ones. The hardware is configured to allow data shifts through the first device into the next. This saves a select pin and speeds up communication as switching devices takes some time.
The Basics of SPI is implemented by MBed, but since there is a more freedom within the standard there is a lot more configuration to do for each device, including creating the data frame, which is put together for you in other protocols.
UART
UART is Universal Asynchronous Receiver/Transmitter. It is less common than the ones above in our systems, but still a good option you should know about. Unlike SPI and I2C, UART is asynchronous - there is no clock. As a perk of this that means there is just 2 wires, one for data each way - in some use cases (read only devices) there is just one data-line. However that means that all your devices need to independently know and run at the Baud rate. UART is not very fast (max baud is 115200, however 9600 is pretty standard) and it supports one device per bus. The UART Data Frame is pretty simple, there is the start bit, up to one byte of data, a parity bit, and a stop bit. You can read more on the technical side at: Circuit Basics
On our microcontrollers there are two ways to do UART. You can bit bang it (not ideal) or you can use the USB driver which is essentially the same thing. MBed has a serial driver that does pretty much all the work from a firmware standpoint of UART. UART is a pretty strictly defined and simple protocol so you don't have much freedom anyhow. The MBed class for implementing UART is the Serial Class. MBed OS2 Serial Documentation
Common (and uncommon) Issues
Code will always have issues until it is very rigorously tested. Even the best programmers make mistakes and mistakes can still exist after countless reviews. Of course there are almost an infinite set of posible bugs in any piece of code so it's impossible to provide a guide of all possible problems but this list will attempt to provide some guidance.
- Pin assignments
- make sure all pins are correctly assigned
- make sure not to mix up for exampl P0_4 and P1_4
- make sure you're looking at the correct version board schematic
- make sure no pins are being used twice
- make sure all pins are correctly assigned
- tickThreshold(...) != threshold(...)
- for threshold you need to update the last time
- Check for any build warnings
- there are a few expected build warnings with our current setup but make sure there are no others.
Firmware Resources
Coding and C++ Basics:
Most of the code you write in solar car is likely stuff you can learn yourself from the internet or reading out other code. However, it is good to know the basics of coding taught in ECE220/CS125 and the data structures in CS225. If you have not taken these yet here are some resources for each:
ECE220 content (this is the ECE coding intro like CS125, but in C/C++ so more relevant to solar car)
- ECE220 Course page
- CProgramming.com tutorials - you should be familiar with all the parts listed under Intro and Pointers/Arrays/Strings
- Tutorials Point info - its good to know Program Structure, Basic Data Types, Variable Types, Using Constants, Control Statements, Operator Types, Pointing to Data, Using Functions, and Structured Datatypes
CS225 content
- CS225 course website
- CPlusPlus.com tutorials - stuff here that you should know (assuming you know the above from ECE220) is Data Structures, Classes (I), Classes (II), and Special Members.