CAN bus bootloader for AVR microcontrollers attached to an MCP2515 CAN controller.
- Flash the main application into a MCU (microcontroller unit)
- Verify after flashing
- Read the whole flash (excluding the bootloader area)
- Erase the whole flash (excluding the bootloader area)
- Unique 16bit IDs to identify the MCU to flash
- Correctly handled disabling of the watchdog at startup to prevent bootloader loops when using the watchdog in the main application
- Use Extended Frame Format (EFF, default) or Standard Frame Format (SFF) CAN-IDs
- Very low impact on active CAN systems which enables to flash MCUs in active networks
- Optional automatic detection of the used bitrate on the CAN bus
MCP-CAN-Boot is made as a PlatformIO project.
It uses parts of the Arduino framework.
For controlling the MCP2515 a modified version of the Arduino MCP2515 CAN interface library is used.
This bootloader will fit into a 2048 words (4096 bytes) bootloader section.
The fuse bits of the MCU have to be set correctly to a boot flash section size of 2048 words and the boot reset vector must be enabled (BOOTRST=0).
To flash the MCP-CAN-Boot bootloader you need to use an ISP programmer.
To upload the bootloader run pio run --target upload --environment ATmega1284P
for example.
Make sure to set the fuse bits correctly for a 2048 words bootloader and boot resest vector enabled. Otherwise the bootloader will not work.
Examples for each supported controller are in platformio.ini
(commented out by default).
To set the fuse bits, enable the lines starting with board_fuses.*
for your controller. Always check/edit the fuses values to fit your needs!
If you set the correct values, you may run pio run --target fuses --environment ATmega1284P
to program the fuses on your controller.
The whole communication via the CAN bus uses only two CAN-IDs.
Defaults:
0x1FFFFF01
for messages from MCU to remote0x1FFFFF02
for messages from remote to MCU
Using this two IDs nearly at the end of CAN-ID range with the lowest priority there will be almost none interference flashing an MCU in a active CAN system.
Each CAN message consists of eight data bytes.
The first four bytes are used for MCU identification, commands, data lengths and data identification. The other four bytes contain the data to read or write.
The official remote application for flashing the MCU using the CAN bus is written in Node.js and located in the mcp-can-boot-flash-app repository.
The flash-app is also available on npm as mcp-can-boot-flash-app
.
No need to install: Just run the flash-app using npx
(this will take a moment):
npx mcp-can-boot-flash-app [...]
Or install it globally and run it if you need it more often:
npm install -g mcp-can-boot-flash-app
mcp-can-boot-flash-app [...]
--file, -f Hex file to flash [string] [required]
--iface, -i CAN interface to use [string] [default: "can0"]
--partno, -p Specific AVR device like in avrdude [string] [required]
--mcuid, -m ID of the MCU bootloader [string] [required]
-e Erase whole flash before flashing new data [boolean]
-V Do not verify [boolean]
-r Read flash and save to given file (no flashing!), optional
with maximum address to read until [string]
-F Force flashing, even if the bootloader version missmatched
[boolean]
--reset, -R CAN message to send on startup to reset the MCU
(<can_id>#{hex_data}) [string]
--can-id-mcu CAN-ID for messages from MCU to remote
[string] [default: 0x1FFFFF01]
--can-id-remote CAN-ID for messages from remote to MCU
[string] [default: 0x1FFFFF02]
--sff Use Standad Frame Format (SFF) instead of the default
Extended Frame Format (EFF) for the CAN-IDs [boolean]
--ping Send a ping in the given interval (ms) to keep the bus active
(should be used if the bootloader uses bitrate detection)
[number]
--help, -h Show help [boolean]
Example:
npx mcp-can-boot-flash-app -f firmware.hex -p m1284p -m 0x0042
- AVR CAN flasher - ESP32 port of the Flash-App
You can use the watchdog to reset the MCU by your main application.
To do so, you have to disable all interrupts, setup the watchdog for a very short time and enter an infinite loop like this:
#include <avr/wdt.h> // include watchdog functions
cli(); // disable interrupts
wdt_enable(WDTO_15MS); // watchdog timeout 15ms
while(1); // wait for watchdog to reset mcu
This will reset your mcu and enter the bootloader.
In the flash-app you may use the -R or --reset argument to send a CAN message which triggers the code above to do reset.
MCP-CAN-Boot clears the MCUSR/MCUCSR register on startup. This is needed for propper watchdog handling.
The original MCUSR/MCUCSR value is stored in the R2 register by the bootloader which allows you to get it in your main application.
To get the original value of the MCUSR/MCUCSR register by you main application, you have to read from the R2 register.
To add a globale variable mcusr
containing the original value of the MCUSR/MCUCSR register just add the following
code on top of your main application:
uint8_t mcusr __attribute__ ((section (".noinit")));
void getMCUSR(void) __attribute__((naked)) __attribute__((section(".init0")));
void getMCUSR(void) {
__asm__ __volatile__ ( "mov %0, r2 \n" : "=r" (mcusr) : );
}
After this, the global variable mcusr
is available and contains the value of the original MCUSR/MCUCSR register.
You don't have to call getMCUSR
manually! This done automatically by the init0
section.
To read the value into a local variable (e.g. inside your main
function) just add the following code in your function:
uint8_t mcusr;
__asm__ __volatile__ ( "mov %0, r2 \n" : "=r" (mcusr) : );
After this, the local variable mcusr
is available and contains the value of the original MCUSR/MCUCSR register.
Each CAN message has a fixed length of 8 byte. Unneeded bytes will be set to 0x00
and simply ignored.
The lower four bytes (0 to 3) are used as control bytes.
These two bytes always contain the ID of the MCU (MCU_ID
in config.h
) to make sure the correct MCU will be flashed.
The ID is written in big-endian format, so byte 0 is the MSB and byte 1 the LSB.
The command which is used to identify the type of message. The commands are described below.
Used to store the amount of the data bytes of the message and a part of the current flash address for verification.
Therefor the three bits 5 to 7 of this byte represent the data length (zero to four bytes) and the bits 0 to 4 are the lower five bits of the current flash address.
This byte will only be used in some commands and otherwise it will be set to 0x00
.
The four data bytes 4 to 7 are set depending on the command. If they contain flash data the byte 3 of the CAN message will be set accordingly.
Command | Binary | Direction |
---|---|---|
Bootloader start | 0b00000010 |
MCU to Remote |
Flash init | 0b00000110 |
Remote to MCU |
Flash ready | 0b00000100 |
MCU to Remote |
Flash set address | 0b00001010 |
Remote to MCU |
Flash address error | 0b00001011 |
MCU to Remote |
Flash data | 0b00001000 |
Remote to MCU |
Flash data error | 0b00001101 |
MCU to Remote |
Flash done | 0b00010000 |
Remote to MCU |
Flash done verify | 0b01010000 |
Remote to MCU and MCU to Remote |
Flash erase | 0b00100000 |
Remote to MCU |
Flash read | 0b01000000 |
Remote to MCU |
Flash read data | 0b01001000 |
MCU to Remote |
Flash read address error | 0b01001011 |
MCU to Remote |
Start app | 0b10000000 |
Remote to MCU and MCU to Remote |
Ping | 0b00000000 |
Remote to MCU |
Hint: All flash addresses will always be the uint32_t byte address with the bytes ordered in big-endian format.
The bootloader start command will be send by the MCU when the bootloader is started up to notify a possibly waiting flash application.
The data bytes 4, 5 and 6 are set to the device signature 0, 1 and 2 of the AVR. This enables the remote flash application to identify the type of the MCU.
Data byte 7 is set to the command set version of the bootloader to make sure bootloader and flash application are speaking the same language.
After this message is send by the MCU the bootloader waits a limited amount of time (default 250ms, configurable via TIMEOUT
in config.h
) for the flash init command.
It no flash init is received the bootloader will start main application.
Flash init have to be send by the flash application to the MCU after bootloader start and before the bootloader starts the main application. This will enter the flashing mode of the bootloader and is the one and only command which must be timed correctly.
The data bytes 4, 5 and 6 must be set to the correct device signature like in bootloader start.
After the bootloader entered the flash mode the next flash address will be set to 0x0000
and a flash ready command will be send.
The flash ready command is send by the MCU when ever it is ready to receive the next flash data.
The four data bytes will be set to the flash address where the next received flash data will be stored. If this is not the address where the data should be stored, the flash application may send a flash set address command.
Flash set address may be used by the flash application to tell the bootloader where the next flash data should be stored.
If the address is set the MUC responds with a flash ready command. If the requested address is out range of the flash area the MCU responds with a flash address error command.
The bootloader responds with flash address error if some command from the flash application requested a flash address which is out of range of the flash area of the MCU.
The triggering command will be discarded by the MCU.
The four data bytes will be set to the flash end address which is maximum supported flash address by this MCU (without the bootloader section).
It's up to the flash application to handle this error and let the bootloader know what to do next.
Flash data is send from the flash application to the bootloader to transmit the content to flash.
It can contain one to four data bytes. The number of data bytes must be set in bit 5 to 7 of the byte 3 (length and address part) of the CAN message.
To verify the correct flash address between flash application and bootloader the bits 0 to 4 of the byte 3 must be set to the LSB five bits of the flash address where the data belongs to.
If the data is accepted by the bootloader the flash address will be increased by the received data length and a flash ready command will be send. The flash application may directly send the next flash data after receiving the flash ready command.
If the address part from byte 3 mismatches the expected flash address by the bootloader a flash data error command will be send with the four data bytes set to the expected flash address.
The bootloader will respond with a flash address error if the data length will exceed the flash end address.
If the address part in byte 3 of a flash data command mismatches the expected flash address by the bootloader it will respond with a flash data error. The four data bytes will be set to the expected flash address.
It's up to the flash application to handle this error and let the bootloader know what to do next.
A flash done can be send by the flash application if all flash data is transmitted. The bootloader will write the remaining internal buffer to the flash and then start the main application.
A flash done verify has to be send by the flash application if all flash data is transmit and the flash application wants to verify (read) the flash. The bootloader will write the remaining internal buffer to the flash, send a flash done verify back to the flash application and wait for flash read commands.
Using the flash read command the flash application may read the current flash data from the MCU.
In each flash read command the four data bytes must be set to the flash address to read from. If the flash address is behind the flash end address (without bootloader area) a flash read address error will be send back to flash application.
There will always be four bytes read from the flash. In case of this exceeds the flash end address, the remaining bytes will be set to 0x00
.
The bootloader will respond with a flash read data message.
The bootloader sends a flash read data command in response to a flash read.
The data bytes will be set to flash data read from the MCU flash at the requested address.
In byte 3 of the CAN message there will be set the length of the read flash data (bit 5 to 7) and the LSB address part of the read flash address (bit 0 to 5).
Directly after receiving the flash read data the flash application may send the next flash read commend.
If all needed flash data is read by the flash application it have to send a start app command to let the bootloader exit the flashing mode and start the main application.
Additionally the flash application may continue to flash data using flash set address and flash data commands.
A flash address read error will be send by the bootloader if a flash read tried to read data behind the flash end address (without bootloader section).
It's up to the flash application to handle this error and let the bootloader know what to do next.
The flash erase command my be send by the flash application to let the bootloader erase the whole flash (without the bootloader section).
When the erase is done the bootloader will set the next flash address to 0x0000
and respond with a flash ready command.
The flash application may use flash erase before the first flash data command to ensure a clean MCU flash before flashing a new application.
The start app command will be send by the bootloader to the flash application if the bootloader is starting the main application from flashing mode.
If the bootloader is starting the main application without entering flashing mode (normal MCU startup) no start app will be send.
Additionally a start app command may be send at any time by the flash application to the bootloader while the bootloader is in flashing mode. This will stop the flashing process without writing anything else to the flash and start the main application.
- Added support for ATmega328PB
- Added support for ATmega32U4
- Optimized bootloader size (724 to 770 bytes smaller dending on used MCU)
Thanks to Dan Hankewycz (hankedan000)! - Added bitrate detect feature (merged from it's own branch since it now fits into 2048 words bootloader section)
- Fixed variable overflow while flashing addresses bigger than 0xFFFF (Tanks to Cosmin-Andrei Popovici for reporting this issue!)
- Updated readme and fixed some spelling issues
- Added config option to use SFF CAN-IDs instead of the default EFF CAN-IDs
- Added some basic checks for the defined CAN-IDs at compile time
- Added storing MCUSR into R2 on bootup
This makes it possible to find the MCU reset caused in run code.
CC BY-NC-SA 4.0
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
Copyright (C) 2020-2024 Peter Müller peter@crycode.de https://crycode.de