Skip to content

Commit b25df52

Browse files
committed
Merge pull request arduino#52 from tekka007/MYSBootloader
MYSBootloader 1.0
2 parents 4590257 + bd80220 commit b25df52

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)