Skip to content

Commit bd80220

Browse files
committed
MYSBootloader 1.0
Bootloader for (direct) OTA FW updates. Occupies 1834 bytes .hex is compiled for 16Mhz. Bootloader was tested in this frequency range; 128kHz (intRC) to 16Mhz(XTAL) at 3.3 and 5V. For OTA bootloading, MYSController was used, NodeJS Controller not tested.
1 parent 08221e6 commit bd80220

File tree

7 files changed

+965
-0
lines changed

7 files changed

+965
-0
lines changed

MYSBootloader/MYSBootloader.c

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
/*
2+
MYSBootloader: OTA bootloader for Mysensor nodes (www.mysensors.org)
3+
Code based on OTA bootloader by ToSa
4+
Optimized and extended by tekka
5+
Version 1.0 / 20150206
6+
Size: 1834 bytes
7+
8+
Tested with Atmega328P, 16Mhz 3.3V and 5V and MYSController 0.1.2.266 (goo.gl/9DCWNo)
9+
10+
MCU: atmega328
11+
clock: 16Mhz (prescaler: off/1)
12+
bootsz: 1024W
13+
14+
15+
fuses for ISP:
16+
EX = 0xFE (use 0x06 for Arduino IDE, boards.txt)
17+
HI = 0xDA
18+
LO = 0xF7
19+
20+
nRF24L01+ connected to pins:
21+
CE = 9
22+
CSN = 10
23+
24+
Successfully tested with:
25+
26+
16Mhz extXTAL, 3.3V
27+
1Mhz intRC (8Mhz + DIV8), 3.3V
28+
128kHz intRC, 3.3V
29+
30+
This program is free software; you can redistribute it and/or
31+
modify it under the terms of the GNU General Public License
32+
version 2 as published by the Free Software Foundation.
33+
*/
34+
35+
#include "MYSBootloader.h"
36+
37+
38+
#define MYSBOOTLOADER_MAJVER 1
39+
#define MYSBOOTLOADER_MINVER 0
40+
41+
#define MYSBOOTLOADER_VERSION (MYSBOOTLOADER_MINVER * 256 + MYSBOOTLOADER_MAJVER)
42+
43+
//#define USE_PRESCALER
44+
#define MAX_RESEND 5
45+
46+
47+
// procedures and functions
48+
49+
int main(void) __attribute__ ((OS_main)) __attribute__ ((section (".init9")));
50+
static uint16_t calcCRCrom (const void* ptr, uint16_t len);
51+
static uint8_t IsFirmwareValid();
52+
static void reboot();
53+
static void startup();
54+
static void programPage(uint32_t page, uint8_t *buf);
55+
static boolean sendWrite(MyMessage message);
56+
static bool sendAndWait(uint8_t reqType, uint8_t resType);
57+
58+
59+
static uint16_t calcCRCrom (const void* ptr, uint16_t len) {
60+
// init 0xFFFF
61+
uint16_t crc = ~0;
62+
for (uint16_t i = 0; i < len; ++i) {
63+
crc = _crc16_update(crc, pgm_read_byte((uint16_t) ptr + i));
64+
}
65+
return crc;
66+
}
67+
68+
static uint8_t IsFirmwareValid () {
69+
return calcCRCrom(0, fc.blocks * FIRMWARE_BLOCK_SIZE) == fc.crc;
70+
}
71+
72+
static void reboot() {
73+
// any pending eeprom activities?
74+
eeprom_busy_wait();
75+
// trigger watchdog ASAP
76+
watchdogConfig(WATCHDOG_16MS);
77+
// wait until WD triggers
78+
while (1);
79+
}
80+
81+
static void startup() {
82+
if (IsFirmwareValid()) {
83+
// WD off
84+
watchdogConfig(WATCHDOG_OFF);
85+
86+
#ifdef USE_PRESCALER
87+
// reset prescaler
88+
clock_prescale_set(orgClockDiv);
89+
#endif
90+
91+
// start sketch
92+
((void(*)()) 0)();
93+
94+
} else {
95+
reboot();
96+
}
97+
}
98+
99+
static void programPage(uint32_t page, uint8_t *buf) {
100+
// using out commands, saves some bytes :)
101+
__boot_page_erase_short(page);
102+
boot_spm_busy_wait();
103+
for (uint16_t i = 0; i < SPM_PAGESIZE; i += 2) {
104+
uint16_t w = *buf++;
105+
// generate word
106+
w += (*buf++) << 8;
107+
__boot_page_fill_short(page + i, w);
108+
}
109+
__boot_page_write_short(page);
110+
boot_spm_busy_wait();
111+
__boot_rww_enable_short();
112+
}
113+
114+
115+
static boolean sendWrite(MyMessage message) {
116+
return write(nc.parentNodeId, message.array, HEADER_SIZE + mGetLength(message), (message.destination == BROADCAST_ADDRESS));
117+
}
118+
119+
static bool sendAndWait(uint8_t reqType, uint8_t resType) {
120+
outMsg.type = reqType;
121+
// outer loop, retries
122+
for (uint8_t i = 0; i < MAX_RESEND; i++) {
123+
sendWrite(outMsg);
124+
// loop 20 times, wait time 0.1ms if no/wrong data => 2s
125+
for (uint8_t j = 0; j < 20; j++) {
126+
// loop 100 times, wait 1ms if no/wrong data => 0.1s
127+
for (uint8_t k = 0; k < 100; k++) {
128+
watchdogReset();
129+
// Tx FIFO data available? (we don't care about the pipe here)
130+
if (available(NULL)) {
131+
// read message from FIFO
132+
readMessage(inMsg.array);
133+
// protocol compatible? if not ignore msg
134+
if ((mGetVersion(inMsg) != PROTOCOL_VERSION)) {
135+
continue;
136+
}
137+
if (inMsg.destination == nc.nodeId) {
138+
// msg for us
139+
if ((mGetCommand(inMsg) == C_INTERNAL) && (inMsg.type == I_FIND_PARENT_RESPONSE)) {
140+
if (inMsg.bValue < nc.distance - 1) {
141+
// got new routing info, update settings
142+
nc.distance = inMsg.bValue + 1;
143+
nc.parentNodeId = inMsg.sender;
144+
}
145+
}
146+
// did we receive expected reply?
147+
if ((mGetCommand(inMsg) == mGetCommand(outMsg)) && (inMsg.type == resType)) {
148+
return true;
149+
}
150+
151+
}
152+
} else {
153+
// wait 1ms if no data available
154+
delaym(10);
155+
}
156+
}
157+
}
158+
}
159+
return false;
160+
}
161+
162+
163+
164+
165+
// main start
166+
int main(void) {
167+
168+
asm volatile ("clr __zero_reg__");
169+
// reset MCU status register
170+
MCUSR = 0;
171+
172+
#ifdef USE_PRESCALER
173+
// get current prescale
174+
orgClockDiv = clock_prescale_get();
175+
//switch to 4 MHz on 16Mhz crystal
176+
clock_prescale_set(F_CPU_DIV);
177+
#endif
178+
179+
// enable watchdog to avoid deadlock
180+
watchdogConfig(WATCHDOG_8S);
181+
182+
// initialize SPI
183+
SPIinit();
184+
185+
// initialize RF module
186+
RFinit();
187+
188+
// Read node config from EEPROM, i.e. nodeId, parent nodeId, distance
189+
eeprom_read_block((void*)&nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(struct NodeConfig));
190+
// Read firmware config from EEPROM, i.e. type, version, CRC, blocks
191+
eeprom_read_block((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig));
192+
193+
// bootloader should find closest node during each reboot
194+
195+
// invalidate parent node settings, since we have to re-discover them for every single reboot
196+
nc.distance = 0xFF;
197+
nc.parentNodeId = 0xFF;
198+
199+
// prepare for I_FIND_PARENTS
200+
outMsg.sender = nc.nodeId;
201+
outMsg.last = nc.nodeId;
202+
outMsg.sensor = 0xFF;
203+
outMsg.destination = BROADCAST_ADDRESS;
204+
205+
mSetVersion(outMsg, PROTOCOL_VERSION);
206+
mSetLength(outMsg, 0);
207+
mSetCommand(outMsg, C_INTERNAL);
208+
mSetAck(outMsg,false);
209+
mSetPayloadType(outMsg, P_STRING);
210+
211+
// set reading & writing pipe address
212+
setAddress(nc.nodeId);
213+
214+
// network up?, get neighbors, else startup
215+
if (!sendAndWait(I_FIND_PARENT, I_FIND_PARENT_RESPONSE)) {
216+
startup();
217+
}
218+
219+
// all messages to gateway
220+
outMsg.destination = GATEWAY_ADDRESS;
221+
222+
// if no node id assigned, request new id
223+
if (nc.nodeId == AUTO) {
224+
// listen to broadcast
225+
openReadingPipe(CURRENT_NODE_PIPE, TO_ADDR(BROADCAST_ADDRESS));
226+
if (sendAndWait(I_ID_REQUEST, I_ID_RESPONSE)) {
227+
// save id to eeprom
228+
eeprom_write_byte((uint8_t*)EEPROM_NODE_ID_ADDRESS, atoi(inMsg.data));
229+
}
230+
// we could go on and set everything right here, but rebooting will take care of that - saves bytes :)
231+
reboot();
232+
}
233+
234+
// wuff
235+
watchdogReset();
236+
// prepare for FW config request
237+
RequestFirmwareConfig *reqFWConfig = (RequestFirmwareConfig *)outMsg.data;
238+
mSetLength(outMsg, sizeof(RequestFirmwareConfig));
239+
mSetCommand(outMsg, C_STREAM);
240+
mSetPayloadType(outMsg,P_CUSTOM);
241+
// copy node settings to reqFWConfig
242+
memcpy(reqFWConfig,&fc,sizeof(NodeFirmwareConfig));
243+
// add bootloader information
244+
reqFWConfig->BLVersion = MYSBOOTLOADER_VERSION;
245+
246+
// send node config and request FW config from controller
247+
if (!sendAndWait(ST_FIRMWARE_CONFIG_REQUEST, ST_FIRMWARE_CONFIG_RESPONSE)) {
248+
startup();
249+
}
250+
251+
NodeFirmwareConfig *firmwareConfigResponse = (NodeFirmwareConfig *)inMsg.data;
252+
253+
// compare with current node configuration, if equal startup
254+
if (!memcmp(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig))) {
255+
startup();
256+
}
257+
258+
// *********** from here on we will fetch new FW
259+
260+
// invalidate current CRC
261+
fc.crc = 0xFFFF;
262+
// write fetched type and version in case OTA fails (BL will reboot and re-request FW with stored settings)
263+
eeprom_write_block(&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS,sizeof(NodeFirmwareConfig));
264+
265+
// copy new FW config
266+
memcpy(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig));
267+
RequestFWBlock *firmwareRequest = (RequestFWBlock *)outMsg.data;
268+
mSetLength(outMsg, sizeof(RequestFWBlock));
269+
270+
firmwareRequest->type = fc.type;
271+
firmwareRequest->version = fc.version;
272+
273+
// request FW from controller
274+
for (uint16_t block = fc.blocks; block > 0; block--) {
275+
firmwareRequest->block = (block - 1);
276+
// request FW block
277+
if (!sendAndWait(ST_FIRMWARE_REQUEST, ST_FIRMWARE_RESPONSE)) {
278+
reboot();
279+
}
280+
281+
ReplyFWBlock *firmwareResponse = (ReplyFWBlock *)inMsg.data;
282+
// calculate page offset
283+
uint8_t offset = ((block - 1) * FIRMWARE_BLOCK_SIZE) % SPM_PAGESIZE;
284+
// write to buffer
285+
memcpy(progBuf + offset, firmwareResponse->data, FIRMWARE_BLOCK_SIZE);
286+
// program if page full
287+
if (offset == 0) {
288+
programPage(((block - 1) * FIRMWARE_BLOCK_SIZE), progBuf);
289+
}
290+
}
291+
292+
// wuff
293+
watchdogReset();
294+
295+
// all blocks transmitted, calc CRC and write to eeprom if valid
296+
if (IsFirmwareValid()) {
297+
// if FW is valid, write settings to eeprom
298+
eeprom_write_block((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig));
299+
// we could restart directly here, but eeprom may still be busy, thus reboot (we check for idle eeprom before rebooting) and save some bytes :)
300+
}
301+
// final step
302+
reboot();
303+
}
304+
305+
306+
307+

MYSBootloader/MYSBootloader.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#ifndef MyOtaBootloader_H
2+
#define MyOtaBootloader_H
3+
4+
#include <avr/power.h>
5+
#include <stdint.h>
6+
#include <string.h>
7+
#include <util/crc16.h>
8+
9+
#include "MyMessage.h"
10+
#include "MySensor.h"
11+
#include "boot.h"
12+
#include "MYSBootloaderRF24.h"
13+
14+
15+
#define FIRMWARE_BLOCK_SIZE 16
16+
17+
// FW config structure, stored in eeprom
18+
typedef struct {
19+
uint16_t type;
20+
uint16_t version;
21+
uint16_t blocks;
22+
uint16_t crc;
23+
} __attribute__((packed)) NodeFirmwareConfig;
24+
25+
typedef struct {
26+
uint16_t type;
27+
uint16_t version;
28+
uint16_t blocks;
29+
uint16_t crc;
30+
uint16_t BLVersion;
31+
} __attribute__((packed)) RequestFirmwareConfig;
32+
33+
typedef struct {
34+
uint16_t type;
35+
uint16_t version;
36+
uint16_t block;
37+
} __attribute__((packed)) RequestFWBlock;
38+
39+
typedef struct {
40+
uint16_t type;
41+
uint16_t version;
42+
uint16_t block;
43+
uint8_t data[FIRMWARE_BLOCK_SIZE];
44+
} __attribute__((packed)) ReplyFWBlock;
45+
46+
static struct NodeConfig nc;
47+
static NodeFirmwareConfig fc;
48+
static MyMessage outMsg;
49+
static MyMessage inMsg;
50+
51+
#ifdef PRESCALE
52+
static clock_div_t orgClockDiv = 0;
53+
#endif
54+
55+
static uint8_t progBuf[SPM_PAGESIZE];
56+
57+
#endif // MyOtaBootloader_H

0 commit comments

Comments
 (0)