From bd80220cf3c54d26680f1a25b202f9d57e7b8391 Mon Sep 17 00:00:00 2001 From: tekka Date: Fri, 6 Feb 2015 14:07:40 +0100 Subject: [PATCH] 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. --- MYSBootloader/MYSBootloader.c | 307 ++++++++++++++++++++++++++++++ MYSBootloader/MYSBootloader.h | 57 ++++++ MYSBootloader/MYSBootloader.hex | 117 ++++++++++++ MYSBootloader/MYSBootloaderHW.h | 147 ++++++++++++++ MYSBootloader/MYSBootloaderRF24.h | 210 ++++++++++++++++++++ MYSBootloader/Makefile | 65 +++++++ MYSBootloader/boards.txt | 62 ++++++ 7 files changed, 965 insertions(+) create mode 100644 MYSBootloader/MYSBootloader.c create mode 100644 MYSBootloader/MYSBootloader.h create mode 100644 MYSBootloader/MYSBootloader.hex create mode 100644 MYSBootloader/MYSBootloaderHW.h create mode 100644 MYSBootloader/MYSBootloaderRF24.h create mode 100644 MYSBootloader/Makefile create mode 100644 MYSBootloader/boards.txt diff --git a/MYSBootloader/MYSBootloader.c b/MYSBootloader/MYSBootloader.c new file mode 100644 index 0000000..462d99c --- /dev/null +++ b/MYSBootloader/MYSBootloader.c @@ -0,0 +1,307 @@ +/* + MYSBootloader: OTA bootloader for Mysensor nodes (www.mysensors.org) + Code based on OTA bootloader by ToSa + Optimized and extended by tekka + Version 1.0 / 20150206 + Size: 1834 bytes + + Tested with Atmega328P, 16Mhz 3.3V and 5V and MYSController 0.1.2.266 (goo.gl/9DCWNo) + + MCU: atmega328 + clock: 16Mhz (prescaler: off/1) + bootsz: 1024W + + + fuses for ISP: + EX = 0xFE (use 0x06 for Arduino IDE, boards.txt) + HI = 0xDA + LO = 0xF7 + + nRF24L01+ connected to pins: + CE = 9 + CSN = 10 + + Successfully tested with: + + 16Mhz extXTAL, 3.3V + 1Mhz intRC (8Mhz + DIV8), 3.3V + 128kHz intRC, 3.3V + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + version 2 as published by the Free Software Foundation. + */ + +#include "MYSBootloader.h" + + +#define MYSBOOTLOADER_MAJVER 1 +#define MYSBOOTLOADER_MINVER 0 + +#define MYSBOOTLOADER_VERSION (MYSBOOTLOADER_MINVER * 256 + MYSBOOTLOADER_MAJVER) + +//#define USE_PRESCALER +#define MAX_RESEND 5 + + +// procedures and functions + +int main(void) __attribute__ ((OS_main)) __attribute__ ((section (".init9"))); +static uint16_t calcCRCrom (const void* ptr, uint16_t len); +static uint8_t IsFirmwareValid(); +static void reboot(); +static void startup(); +static void programPage(uint32_t page, uint8_t *buf); +static boolean sendWrite(MyMessage message); +static bool sendAndWait(uint8_t reqType, uint8_t resType); + + +static uint16_t calcCRCrom (const void* ptr, uint16_t len) { + // init 0xFFFF + uint16_t crc = ~0; + for (uint16_t i = 0; i < len; ++i) { + crc = _crc16_update(crc, pgm_read_byte((uint16_t) ptr + i)); + } + return crc; +} + +static uint8_t IsFirmwareValid () { + return calcCRCrom(0, fc.blocks * FIRMWARE_BLOCK_SIZE) == fc.crc; +} + +static void reboot() { + // any pending eeprom activities? + eeprom_busy_wait(); + // trigger watchdog ASAP + watchdogConfig(WATCHDOG_16MS); + // wait until WD triggers + while (1); +} + +static void startup() { + if (IsFirmwareValid()) { + // WD off + watchdogConfig(WATCHDOG_OFF); + + #ifdef USE_PRESCALER + // reset prescaler + clock_prescale_set(orgClockDiv); + #endif + + // start sketch + ((void(*)()) 0)(); + + } else { + reboot(); + } +} + +static void programPage(uint32_t page, uint8_t *buf) { + // using out commands, saves some bytes :) + __boot_page_erase_short(page); + boot_spm_busy_wait(); + for (uint16_t i = 0; i < SPM_PAGESIZE; i += 2) { + uint16_t w = *buf++; + // generate word + w += (*buf++) << 8; + __boot_page_fill_short(page + i, w); + } + __boot_page_write_short(page); + boot_spm_busy_wait(); + __boot_rww_enable_short(); +} + + +static boolean sendWrite(MyMessage message) { + return write(nc.parentNodeId, message.array, HEADER_SIZE + mGetLength(message), (message.destination == BROADCAST_ADDRESS)); +} + +static bool sendAndWait(uint8_t reqType, uint8_t resType) { + outMsg.type = reqType; + // outer loop, retries + for (uint8_t i = 0; i < MAX_RESEND; i++) { + sendWrite(outMsg); + // loop 20 times, wait time 0.1ms if no/wrong data => 2s + for (uint8_t j = 0; j < 20; j++) { + // loop 100 times, wait 1ms if no/wrong data => 0.1s + for (uint8_t k = 0; k < 100; k++) { + watchdogReset(); + // Tx FIFO data available? (we don't care about the pipe here) + if (available(NULL)) { + // read message from FIFO + readMessage(inMsg.array); + // protocol compatible? if not ignore msg + if ((mGetVersion(inMsg) != PROTOCOL_VERSION)) { + continue; + } + if (inMsg.destination == nc.nodeId) { + // msg for us + if ((mGetCommand(inMsg) == C_INTERNAL) && (inMsg.type == I_FIND_PARENT_RESPONSE)) { + if (inMsg.bValue < nc.distance - 1) { + // got new routing info, update settings + nc.distance = inMsg.bValue + 1; + nc.parentNodeId = inMsg.sender; + } + } + // did we receive expected reply? + if ((mGetCommand(inMsg) == mGetCommand(outMsg)) && (inMsg.type == resType)) { + return true; + } + + } + } else { + // wait 1ms if no data available + delaym(10); + } + } + } + } + return false; +} + + + + +// main start +int main(void) { + + asm volatile ("clr __zero_reg__"); + // reset MCU status register + MCUSR = 0; + + #ifdef USE_PRESCALER + // get current prescale + orgClockDiv = clock_prescale_get(); + //switch to 4 MHz on 16Mhz crystal + clock_prescale_set(F_CPU_DIV); + #endif + + // enable watchdog to avoid deadlock + watchdogConfig(WATCHDOG_8S); + + // initialize SPI + SPIinit(); + + // initialize RF module + RFinit(); + + // Read node config from EEPROM, i.e. nodeId, parent nodeId, distance + eeprom_read_block((void*)&nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(struct NodeConfig)); + // Read firmware config from EEPROM, i.e. type, version, CRC, blocks + eeprom_read_block((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig)); + + // bootloader should find closest node during each reboot + + // invalidate parent node settings, since we have to re-discover them for every single reboot + nc.distance = 0xFF; + nc.parentNodeId = 0xFF; + + // prepare for I_FIND_PARENTS + outMsg.sender = nc.nodeId; + outMsg.last = nc.nodeId; + outMsg.sensor = 0xFF; + outMsg.destination = BROADCAST_ADDRESS; + + mSetVersion(outMsg, PROTOCOL_VERSION); + mSetLength(outMsg, 0); + mSetCommand(outMsg, C_INTERNAL); + mSetAck(outMsg,false); + mSetPayloadType(outMsg, P_STRING); + + // set reading & writing pipe address + setAddress(nc.nodeId); + + // network up?, get neighbors, else startup + if (!sendAndWait(I_FIND_PARENT, I_FIND_PARENT_RESPONSE)) { + startup(); + } + + // all messages to gateway + outMsg.destination = GATEWAY_ADDRESS; + + // if no node id assigned, request new id + if (nc.nodeId == AUTO) { + // listen to broadcast + openReadingPipe(CURRENT_NODE_PIPE, TO_ADDR(BROADCAST_ADDRESS)); + if (sendAndWait(I_ID_REQUEST, I_ID_RESPONSE)) { + // save id to eeprom + eeprom_write_byte((uint8_t*)EEPROM_NODE_ID_ADDRESS, atoi(inMsg.data)); + } + // we could go on and set everything right here, but rebooting will take care of that - saves bytes :) + reboot(); + } + + // wuff + watchdogReset(); + // prepare for FW config request + RequestFirmwareConfig *reqFWConfig = (RequestFirmwareConfig *)outMsg.data; + mSetLength(outMsg, sizeof(RequestFirmwareConfig)); + mSetCommand(outMsg, C_STREAM); + mSetPayloadType(outMsg,P_CUSTOM); + // copy node settings to reqFWConfig + memcpy(reqFWConfig,&fc,sizeof(NodeFirmwareConfig)); + // add bootloader information + reqFWConfig->BLVersion = MYSBOOTLOADER_VERSION; + + // send node config and request FW config from controller + if (!sendAndWait(ST_FIRMWARE_CONFIG_REQUEST, ST_FIRMWARE_CONFIG_RESPONSE)) { + startup(); + } + + NodeFirmwareConfig *firmwareConfigResponse = (NodeFirmwareConfig *)inMsg.data; + + // compare with current node configuration, if equal startup + if (!memcmp(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig))) { + startup(); + } + + // *********** from here on we will fetch new FW + + // invalidate current CRC + fc.crc = 0xFFFF; + // write fetched type and version in case OTA fails (BL will reboot and re-request FW with stored settings) + eeprom_write_block(&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS,sizeof(NodeFirmwareConfig)); + + // copy new FW config + memcpy(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig)); + RequestFWBlock *firmwareRequest = (RequestFWBlock *)outMsg.data; + mSetLength(outMsg, sizeof(RequestFWBlock)); + + firmwareRequest->type = fc.type; + firmwareRequest->version = fc.version; + + // request FW from controller + for (uint16_t block = fc.blocks; block > 0; block--) { + firmwareRequest->block = (block - 1); + // request FW block + if (!sendAndWait(ST_FIRMWARE_REQUEST, ST_FIRMWARE_RESPONSE)) { + reboot(); + } + + ReplyFWBlock *firmwareResponse = (ReplyFWBlock *)inMsg.data; + // calculate page offset + uint8_t offset = ((block - 1) * FIRMWARE_BLOCK_SIZE) % SPM_PAGESIZE; + // write to buffer + memcpy(progBuf + offset, firmwareResponse->data, FIRMWARE_BLOCK_SIZE); + // program if page full + if (offset == 0) { + programPage(((block - 1) * FIRMWARE_BLOCK_SIZE), progBuf); + } + } + + // wuff + watchdogReset(); + + // all blocks transmitted, calc CRC and write to eeprom if valid + if (IsFirmwareValid()) { + // if FW is valid, write settings to eeprom + eeprom_write_block((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig)); + // we could restart directly here, but eeprom may still be busy, thus reboot (we check for idle eeprom before rebooting) and save some bytes :) + } + // final step + reboot(); +} + + + + diff --git a/MYSBootloader/MYSBootloader.h b/MYSBootloader/MYSBootloader.h new file mode 100644 index 0000000..10c48d8 --- /dev/null +++ b/MYSBootloader/MYSBootloader.h @@ -0,0 +1,57 @@ +#ifndef MyOtaBootloader_H +#define MyOtaBootloader_H + +#include +#include +#include +#include + +#include "MyMessage.h" +#include "MySensor.h" +#include "boot.h" +#include "MYSBootloaderRF24.h" + + +#define FIRMWARE_BLOCK_SIZE 16 + +// FW config structure, stored in eeprom +typedef struct { + uint16_t type; + uint16_t version; + uint16_t blocks; + uint16_t crc; +} __attribute__((packed)) NodeFirmwareConfig; + +typedef struct { + uint16_t type; + uint16_t version; + uint16_t blocks; + uint16_t crc; + uint16_t BLVersion; +} __attribute__((packed)) RequestFirmwareConfig; + +typedef struct { + uint16_t type; + uint16_t version; + uint16_t block; +} __attribute__((packed)) RequestFWBlock; + +typedef struct { + uint16_t type; + uint16_t version; + uint16_t block; + uint8_t data[FIRMWARE_BLOCK_SIZE]; +} __attribute__((packed)) ReplyFWBlock; + +static struct NodeConfig nc; +static NodeFirmwareConfig fc; +static MyMessage outMsg; +static MyMessage inMsg; + +#ifdef PRESCALE +static clock_div_t orgClockDiv = 0; +#endif + +static uint8_t progBuf[SPM_PAGESIZE]; + +#endif // MyOtaBootloader_H \ No newline at end of file diff --git a/MYSBootloader/MYSBootloader.hex b/MYSBootloader/MYSBootloader.hex new file mode 100644 index 0000000..7039c3b --- /dev/null +++ b/MYSBootloader/MYSBootloader.hex @@ -0,0 +1,117 @@ +:1078000021E0A0E0B1E001C01D92A53DB207E1F783 +:10781000CDB7DEB76897DEBFCDBF112414BE88E1B7 +:107820008093600089E2809360008FEE84B963E00A +:1078300083E201D26FE584E2FED16BE381E2FBD10A +:107840006CE485E2F8D167E286E2F5D10DD263E718 +:1078500080E5F1D166E08DE3EED16FE38CE3EBD10F +:107860006EEF80E2E8D185E053D143E050E060E084 +:1078700070E08AEC91E032D348E050E06BE171E0D7 +:1078800082EC91E02BD38FEF8093CC018093CB01DE +:107890002091CA012093A2012093A1018093A70106 +:1078A0008093A30182E08093A4018091A501887058 +:1078B00083608093A5012F3FD1F130E040E050E09C +:1078C00060E070E080E090E034504E4157456745FD +:1078D0007F4F8F4F9F4F29013A014B015C01298B4C +:1078E0005A8A4B8B7C8A6D8B9E8A8F8BB88E85E0F3 +:1078F000FE017196ADECB1E001900D928A95E1F731 +:1079000045E0BE016F5E7F4F8AE27FD149865A868D +:107910006B867C868D869E86AF86B88A45E0BE01E2 +:10792000675F7F4F8BE271D163E082E284D168E0D0 +:1079300087E09FD1811101C049D11092A3018091AC +:10794000CA018F3FF1F489838CEF8A8381EE8B83A8 +:1079500088EA8C838D831E821F82188645E0BE01D3 +:107960006F5F7F4F8BE251D164E083E082D1882347 +:1079700039F087E891E080D2682F80E090E0C7D2AC +:107980001CD1A8958091A401877080658093A40183 +:107990008091A5018871846C8093A50188E0E2EC58 +:1079A000F1E0A8EAB1E001900D928A95E1F781E05B +:1079B00090E09093B1018093B00161E080E059D1F3 +:1079C000811101C003D148E050E067E871E082EC2A +:1079D00091E06FD2892B09F4F9D08FEF9FEF90934C +:1079E000C9018093C80148E050E06BE171E082EC8E +:1079F00091E084D288E0E7E8F1E0A2ECB1E0019008 +:107A00000D928A95E1F78091A4018770806380933D +:107A1000A4018091C2019091C3019093A901809328 +:107A2000A8018091C4019091C5019093AB0180930E +:107A3000AA01A090C601B090C70185010150104F66 +:107A400064E0000F111F6A95E1F770E1972E88241A +:107A50008394E5E07E2EF1E16F2EA114B10409F4C8 +:107A60004AC081E0A81AB108B092AD01A092AC0161 +:107A700063E082E0FED0882309F482CF802F8077F4 +:107A8000A82FB0E0A050BF4FEDE8F1E0992D019094 +:107A90000D929A95E1F781112BC083E0F80187BF21 +:107AA000E89507B600FCFDCF4091000180E090E032 +:107AB000DC01A050BF4FFC01E00FF11F11962C918B +:107AC000119730E0322F2227240F311D090187BE84 +:107AD000E895112412964C9102968038910541F751 +:107AE000F80177BEE89507B600FCFDCF67BEE895C4 +:107AF00000511109B2CFA8952CD0882309F440CFAA +:107B000048E050E06BE171E082EC91E0F7D138CFD2 +:107B100090E0981751F02DE730E000002150310936 +:107B200021153105D1F79F5FF4CF089598E087FFC5 +:107B300002C02B9A01C02B9800002D9A000023B19F +:107B4000880F22952170822B2D980000915079F793 +:107B500008958091C6019091C70124E0880F991F74 +:107B60002A95E1F7E0E0F0E02FEF3FEFE817F907A3 +:107B7000D1F044912427422F42954227042E469566 +:107B800046954025042E469540254770022E232F0A +:107B9000469507944795302D242706944795302520 +:107BA00024273196E3CF81E04091C8015091C9016B +:107BB0002417350709F080E00895F999FECF88E190 +:107BC0008093600088E080936000FFCFC2DF88234D +:107BD00041F088E18093600010926000E0E0F0E006 +:107BE0000994EBDF1F93CF93DF93EB01142F2A98B7 +:107BF0009DDF1C0F1C1721F08FEF98DF8993FACFC0 +:107C00002A9ADF91CF911F9108951F93CF93DF930D +:107C1000EB01142F2A988ADF1C0F1C1719F0899189 +:107C200085DFFBCF2A9ADF91CF911F91089541E024 +:107C300060E070E0D7CFCF93DF931F92CDB7DEB770 +:107C4000698341E0BE016F5F7F4FDFDF0F90DF91FF +:107C5000CF91089540E060E070E082EED6CF40E042 +:107C600060E070E081EED1CFF5DFF9DF60E787E219 +:107C7000E2CF6F927F928F929F92AF92BF92CF92FC +:107C8000DF92EF92FF920F931F93CF93DF93CDB7C5 +:107C9000DEB7AA97DEBFCDBF762E8093A60105E0A2 +:107CA00041E2642EE1EAF1E0DE011196862D0190B9 +:107CB0000D928A95E1F7FB801C8116951695169515 +:107CC000195F2091CB0129982AA7CEDF6EEF80E2C1 +:107CD000B2DF2AA530E040E050E060E070E080E0F4 +:107CE00090E034504E41574567457F4F8F4F9F4F2F +:107CF0002AA33BA34CA35DA36EA37FA388A799A748 +:107D000045E0BE016E5D7F4F8AE27FDF45E0BE0148 +:107D10006E5D7F4F80E379DF8FEFF81202C080EB5A +:107D200001C080EA412FBE016F5F7F4F6EDF299A4D +:107D300081E0EEDE299840E060E070E08FEF52DFF6 +:107D40008073C9F360E787E276DF84FD88DF6FE048 +:107D500080E271DF89DF8090CD019090CE01A0900C +:107D6000CF01B090D001C090D101D090D201E0906D +:107D7000D301F090D4019401A501B601C701A0E0A0 +:107D80006FD029F045E06DEC71E08AE23EDF299A80 +:107D900014E134E6F32EA89587E149DF80FD4BC05E +:107DA00080E645DF813210F055DF80E0482F60E843 +:107DB00071E081E617DF60E787E23DDF80918301B4 +:107DC0008770823059F5909182018091CA01981391 +:107DD00025C040918401842F87708330B1F4809155 +:107DE0008501883091F450918701252F30E08091F2 +:107DF000CC0190E00197281739073CF45F5F50935E +:107E0000CC01809181018093CB018091A5018427D1 +:107E1000877021F480918501871559F0FA9409F053 +:107E2000BACF115009F0B5CF015009F03BCF80E037 +:107E300005C081E003C08AE06BDEF0CFAA96DEBF0A +:107E4000CDBFDF91CF911F910F91FF90EF90DF9009 +:107E5000CF90BF90AF909F908F907F906F9008953C +:107E60000024A7FD00942A17300540055005600541 +:107E70007005800590050895FC0188279927E894EE +:107E800021912032E9F3293010F02E30C8F32B3243 +:107E900039F02D3231F4689403C018D0820F911D4F +:107EA000219120532A30C8F31EF4909581959F4F5D +:107EB0000895FB01DC0104C08D910190801921F42B +:107EC00041505040C8F7881B990B08957AE0979F5E +:107ED000902D879F802D910D11240895DC01CB01F9 +:107EE000FC01F999FECF06C0F2BDE1BDF89A3196CA +:107EF00000B40D9241505040B8F70895DC01CB0119 +:107F000002C02D9105D041505040D8F70895262F3A +:107F1000F999FECF1FBA92BD81BD20BD0FB6F8946E +:0A7F2000FA9AF99A0FBE019608952F +:040000030000780081 +:00000001FF diff --git a/MYSBootloader/MYSBootloaderHW.h b/MYSBootloader/MYSBootloaderHW.h new file mode 100644 index 0000000..8769ce1 --- /dev/null +++ b/MYSBootloader/MYSBootloaderHW.h @@ -0,0 +1,147 @@ +#ifndef MYSBootloaderHW_H +#define MYSBootloaderHW_H + +#include + +// hardware + + + +#ifdef USE_PRESCALER +#define F_CPU_DIV clock_div_4 +#else +#define F_CPU_DIV clock_div_1 +#endif + +#define F_CPU_REAL F_CPU / (1 << F_CPU_DIV) +#define DELAY_M 125 * (1 << F_CPU_DIV) +#define BAUD_SETTING (( (F_CPU + BAUD_RATE * 4L) / ((BAUD_RATE * 8L))) - 1 ) +#define BAUD_ACTUAL (F_CPU/(8 * ((BAUD_SETTING)+1))) +#define BAUD_ERROR (( 100*(BAUD_RATE - BAUD_ACTUAL) ) / BAUD_RATE) +#define UART_SRA UCSR0A +#define UART_SRB UCSR0B +#define UART_SRC UCSR0C +#define UART_SRL UBRR0L +#define UART_UDR UDR0 + +// boolean definition + +typedef uint8_t bool; +typedef uint8_t boolean; +#define false 0x0 +#define true 0x1 +#define LOW 0x0 +#define HIGH 0x1 + +// Watchdog definitions and functions + +#define WATCHDOG_OFF (0) +#define WATCHDOG_16MS (_BV(WDE)) +#define WATCHDOG_32MS (_BV(WDP0) | _BV(WDE)) +#define WATCHDOG_64MS (_BV(WDP1) | _BV(WDE)) +#define WATCHDOG_125MS (_BV(WDP1) | _BV(WDP0) | _BV(WDE)) +#define WATCHDOG_250MS (_BV(WDP2) | _BV(WDE)) +#define WATCHDOG_500MS (_BV(WDP2) | _BV(WDP0) | _BV(WDE)) +#define WATCHDOG_1S (_BV(WDP2) | _BV(WDP1) | _BV(WDE)) +#define WATCHDOG_2S (_BV(WDP2) | _BV(WDP1) | _BV(WDP0) | _BV(WDE)) +#define WATCHDOG_4S (_BV(WDP3) | _BV(WDE)) +#define WATCHDOG_8S (_BV(WDP3) | _BV(WDP0) | _BV(WDE)) + +inline void watchdogReset() { + asm volatile ("wdr"); +} + +void watchdogConfig(uint8_t x) { + WDTCSR = _BV(WDCE) | _BV(WDE); + WDTCSR = x; +} + +// delay procedures + +inline static void delayu() { + asm volatile ("nop"); +} + +static void delaym(uint8_t value) { + for (uint8_t outer = 0; outer < value; outer++) { + for (uint16_t inner = 0; inner < DELAY_M; inner++) { + delayu(); + } + } +} + +// SPI communication + +#define SPI_DDR DDRB +#define SPI_PORT PORTB +#define SPI_PIN PINB +#define SPI_SCLK 5 // Arduino Pin 13 <-> Bit 5 of port B +#define SPI_MISO 4 // Arduino Pin 12 <-> Bit 4 of port B +#define SPI_MOSI 3 // Arduino Pin 11 <-> Bit 3 of port B +#define SPI_CSN 2 // Arduino Pin 10 <-> Bit 2 of port B +#define SPI_CE 1 // Arduino Pin 9 <-> Bit 1 of port B +#define csnlow() SPI_PORT &= ~_BV(SPI_CSN)//;delaym(1) // not necessary +#define csnhigh() SPI_PORT |= _BV(SPI_CSN)//;delaym(1) // not necessary +#define celow() SPI_PORT &= ~_BV(SPI_CE)//;delaym(1) // not necessary +#define cehigh() SPI_PORT |= _BV(SPI_CE)//;delaym(1) // minimum puls width 10us + +static void SPIinit() { + // set pin mode: MOSI,SCLK,CE = OUTPUT, MISO = INPUT + SPI_DDR = _BV(SPI_MOSI) | _BV(SPI_SCLK) | _BV(SPI_CE) | _BV(SPI_CSN) | ~_BV(SPI_MISO); +} + +static uint8_t SPItransfer(uint8_t value) { + for(uint8_t i = 0 ;i < 8;i++) { + if (value & 0x80) SPI_PORT |= _BV(SPI_MOSI); else SPI_PORT &= ~_BV(SPI_MOSI); + delayu(); + value <<= 1; + SPI_PORT |= _BV(SPI_SCLK); + delayu(); + value |= ((SPI_PIN >> SPI_MISO) & 0x01); + SPI_PORT &= ~_BV(SPI_SCLK); + delayu(); + } + return value; +} + + +// UART debug + +static void uart_init() { + //Double speed mode USART0 + UART_SRA = _BV(U2X0); + UART_SRB = _BV(RXEN0) | _BV(TXEN0); + UART_SRC = _BV(UCSZ00) | _BV(UCSZ01); + UART_SRL = (uint8_t)( (F_CPU + BAUD_RATE * 4L) / (BAUD_RATE * 8L) - 1 ); +} + +void putch(char ch) { + while (!(UART_SRA & _BV(UDRE0))); + UART_UDR = ch; +} + +uint8_t getch() { + uint8_t ch; + // wait until char received + while(!(UART_SRA & _BV(RXC0))); + // framing error? + if (!(UART_SRA & _BV(FE0))) { + watchdogReset(); + } + ch = UART_UDR; + return ch; +} + +static void put_string(char *s) { + while (*s) + putch(*s++); +} + +static void put_int(uint8_t i) { + char s[5]; + itoa( i, s, 10 ); + put_string( s ); +} + + +#endif // MYSBootloaderHW_H diff --git a/MYSBootloader/MYSBootloaderRF24.h b/MYSBootloader/MYSBootloaderRF24.h new file mode 100644 index 0000000..c91f902 --- /dev/null +++ b/MYSBootloader/MYSBootloaderRF24.h @@ -0,0 +1,210 @@ +#ifndef MYSBootloaderRF24_H +#define MYSBootloaderRF24_H + +#include "MYSBootloaderHW.h" +#include "nRF24L01.h" + +typedef enum { RF24_PA_MIN = 0,RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX, RF24_PA_ERROR } rf24_pa_dbm_e ; +typedef enum { RF24_1MBPS = 0, RF24_2MBPS, RF24_250KBPS } rf24_datarate_e; + +#define _write_register(reg) ( (reg) | W_REGISTER) +#define _read_register(reg) ( (reg) | R_REGISTER) + +#define addr_width 5 +uint64_t pipe0_reading_address = 0; + + +uint8_t BurstReadAddress(uint8_t addr, uint8_t* buf, uint8_t len) { + csnlow(); + uint8_t status = SPItransfer( addr ); + while ( len-- ) { + status = SPItransfer(NOP); + *buf++ = status; + } + csnhigh(); + return status; +} + +uint8_t BurstWriteAddress(uint8_t addr, uint8_t* buf, uint8_t len) { + csnlow(); + uint8_t status = SPItransfer( addr ); + while ( len-- ) { + status = SPItransfer(*buf++); + } + csnhigh(); + return status; +} + +uint8_t ReadAddress(uint8_t addr) { + return BurstReadAddress(addr,NULL,1); +} + + +uint8_t WriteAddress(uint8_t addr, uint8_t val) { + return BurstWriteAddress(addr,&val,1); +} + +#define BurstWriteRegister(reg,buf,len) BurstWriteAddress(_write_register(reg),buf,len) +#define WriteRegister(reg,val) WriteAddress(_write_register(reg),val) +#define BurstReadRegister(reg,buf,len) BurstReadAddress(_read_register(reg),buf,len) +#define ReadRegister(reg) ReadAddress(_read_register(reg)) +#define get_status() BurstReadAddress(NOP,NULL,0) + +void Flush_RX(void) { + BurstWriteAddress(FLUSH_RX,NULL,0); +} + +void Flush_TX(void) { + BurstWriteAddress(FLUSH_TX,NULL,0); +} + +void Flush_RXTX_CLI(void) { + // flush RX and TX buffer + Flush_RX(); + Flush_TX(); + // clear registers + WriteRegister(STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) ); +} + +static void startListening(void) { + // for size reasons, we do not read back the register content, set PRIM_RX + WriteRegister(CONFIG, _BV(PWR_UP) | _BV(CRCO) | _BV(EN_CRC) | _BV(PRIM_RX)) ; + Flush_RXTX_CLI(); + // write pipe0 address + if (pipe0_reading_address){ + BurstWriteRegister(RX_ADDR_P0, (uint8_t*)&pipe0_reading_address, addr_width); + } + cehigh(); +} + +static void stopListening(void) { + celow(); + Flush_RXTX_CLI(); + // for size reasons, we do not read back the register content, clear PRIM_RX + WriteRegister(CONFIG, _BV(PWR_UP) | _BV(CRCO) | _BV(EN_CRC) | ~_BV(PRIM_RX)) ; +} + + + +static bool write_buf(uint8_t* buf, uint8_t len, const bool multicast ) { + // write payload to FIFO + BurstWriteRegister( multicast ? W_TX_PAYLOAD_NO_ACK : W_TX_PAYLOAD, buf, len) ; + // CE pulse to start transmission + cehigh(); + // IMPORTANT: minimum CE pulse width 10us, see nRF24L01 specs + delaym(1); + // start transmitting + celow(); + + + // wait until sent, here we potentially have a deadlock when transmitter not connected/not working + while( ! ( get_status() & ( _BV(TX_DS) | _BV(MAX_RT) ))) { } + // read interrupts and clear + uint8_t status = WriteRegister(STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) ); + // max retries? clear FIFO + if( status & _BV(MAX_RT)){ + Flush_TX(); + return false; + } + return true; +} + +static uint8_t getDynamicPayloadSize(void) { + uint8_t result = ReadAddress(R_RX_PL_WID); + if(result > 32) { + Flush_RX(); + result = 0; + } + return result; +} + + +static bool available(uint8_t* pipe_num) { + if (!(ReadAddress(FIFO_STATUS) & _BV(RX_EMPTY) )){ + if (pipe_num) { + uint8_t status = get_status(); + *pipe_num = ( status >> RX_P_NO ) & 0b111; + } + return true; + } + return false; +} + + +static void readMessage(uint8_t* buf) { + // read payload + BurstReadRegister(R_RX_PAYLOAD,buf,getDynamicPayloadSize()); + // reset interrupts + WriteRegister(STATUS,_BV(RX_DR) | _BV(MAX_RT) | _BV(TX_DS)); +} + +static void openWritingPipe(uint64_t value) { + BurstWriteRegister(RX_ADDR_P0, (uint8_t*)&value, addr_width); + BurstWriteRegister(TX_ADDR, (uint8_t*)&value, addr_width); +} + +static void openReadingPipe(uint8_t pipe, uint64_t address) { + // If this is pipe 0, cache the address. This is needed because openWritingPipe() will overwrite the pipe 0 address, so + // startListening() will have to restore it + if (pipe == WRITE_PIPE) { + memcpy(&pipe0_reading_address,&address,addr_width); + } + // only write full address for pipe 0 and 1 + // since we do not use the BROADCASTING PIPE in this bootloader, we can remove the check + //if ( pipe < 2 ) + { + BurstWriteRegister(RX_ADDR_P0 + pipe, (uint8_t*)&address, addr_width); + } + + +} + +static boolean write(uint8_t destination, uint8_t* buf, uint8_t len, boolean multicast) { + stopListening(); + openWritingPipe(TO_ADDR(destination)); + bool result = write_buf(buf, len, multicast); + startListening(); + return result; +} + +static void setAddress(uint8_t addr) +{ + if (addr != BROADCAST_ADDRESS) { + openReadingPipe(WRITE_PIPE, TO_ADDR(addr)); + openReadingPipe(CURRENT_NODE_PIPE, TO_ADDR(addr)); + // enable pipe + WriteRegister(EN_RXADDR,_BV(ERX_P0 + WRITE_PIPE) | _BV(ERX_P0 + CURRENT_NODE_PIPE)); + } +} + +static void RFinit(void){ + // set address width + WriteRegister(SETUP_AW, (addr_width-2) % 4); + // auto retransmit delay 1500us, auto retransmit count 15 + WriteRegister(SETUP_RETR, 5 << ARD | 15 << ARC); + // enable auto ack on all pipes, except broadcasting pipe + WriteRegister(EN_AA, 0b00111111 & ~_BV(BROADCAST_PIPE)) ; + // set channel + WriteRegister(RF_CH, RF24_CHANNEL); + // set data rate + WriteRegister(RF_SETUP, ((RF24_DATARATE & 0b00000010 ) << 4) | ((RF24_DATARATE & 0b00000001 ) << 3) | ((RF24_PA_LEVEL << 1) + 1)); + // flush RX and TX FIFO, clear interrupts + Flush_RXTX_CLI(); + // activate dynamic payload feature + WriteAddress(ACTIVATE,0x73); + // enable payload with ACK and dynamic payload length + WriteRegister(FEATURE, _BV(EN_ACK_PAY) | _BV(EN_DPL) ); + // Enable dynamic payload length on all pipes + WriteRegister(DYNPD, _BV(DPL_P5) | _BV(DPL_P4) | _BV(DPL_P3) | _BV(DPL_P2) | _BV(DPL_P1) | _BV(DPL_P0)); + //powerUp, enable 16bit CRC, no RX mode + WriteRegister(CONFIG, _BV(PWR_UP) | _BV(CRCO) | _BV(EN_CRC) | ~_BV(PRIM_RX)); + // stabilize + delaym(5); + + // We are not listening to broadcast pipe since we do not reply to broadcasting messages (e.g. FIND_PARENT_RESPONSE messages) + // openReadingPipe(BROADCAST_PIPE, TO_ADDR(BROADCAST_ADDRESS)); + + +} + +#endif // MYSBootloaderRF24_H diff --git a/MYSBootloader/Makefile b/MYSBootloader/Makefile new file mode 100644 index 0000000..faabf2a --- /dev/null +++ b/MYSBootloader/Makefile @@ -0,0 +1,65 @@ +PROJECT = MYSBootloader +INCLUDES = ../libraries/MySensors + +MCU = atmega328p +CLK = 16000000L + +ISP_PORT = com5 +ISP_SPEED = 115200 +ISP_PROTOCOL = stk500v2 +ISP_MCU = m328p +ISP_HFUSE = DA +ISP_LFUSE = F7 +ISP_EFUSE = 06 +ISP_ARGS = -c$(ISP_PROTOCOL) -P$(ISP_PORT) -b$(ISP_SPEED) -p$(ISP_MCU) + +ifeq ($(OS),Windows_NT) + BINPATH = C:/Program Files (x86)/Arduino/hardware/tools/avr/bin/ + JAVAPATH = C:/Program Files/Java/jdk1.7.0_40/bin/ + JAVAC = $(JAVAPATH)javac.exe + JAVA = $(JAVAPATH)java.exe +else + UNAME_S := $(shell uname -s) + UNAME_P := $(shell uname -p) + UNAME_M := $(shell uname -m) + ifeq ($(UNAME_S),Linux) + ifeq ($(UNAME_P),x86_64) + BINPATH = ../arduino/bin-linux64/bin/ + else ifneq ($(filter %86,$(UNAME_P)),) + BINPATH = ../arduino/bin-linux32/bin/ + else ifneq ($(filter arm%,$(UNAME_M)),) + BINPATH = /usr/bin/ + endif + endif +endif + +CFLAGS = -x c -mno-interrupts -funsigned-char -funsigned-bitfields -DF_CPU=$(CLK)-Os -fno-inline-small-functions -fno-split-wide-types -ffunction-sections -fdata-sections -fpack-struct -fshort-enums -mrelax -Wall -mmcu=$(MCU) -c -std=gnu99 -MD -MP -MF "$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -MT"$(@:%.o=%.o)" +LDFLAGS = -nostartfiles -Wl,-s -Wl,-static -Wl,-Map="$(OutputFileName).map" -Wl,--start-group -Wl,--end-group -Wl,--gc-sections -mrelax -Wl,-section-start=.text=0x7800 -mmcu=$(MCU) + + + +all: clean out + +clean: + - rm *.o + - rm *.elf + - rm *.hex + +$(PROJECT).o: $(PROJECT).c + "$(BINPATH)avr-gcc" $(CFLAGS) -I$(INCLUDES) $< -o $@ + +$(PROJECT).elf: $(PROJECT).o + "$(BINPATH)avr-gcc" $(LDFLAGS) -o $@ $< -lm + +$(PROJECT).hex: $(PROJECT).elf + "$(BINPATH)avr-objcopy" -O ihex -R .eeprom $< $@ + +out: $(PROJECT).hex + "$(BINPATH)avr-size" $(PROJECT).elf + +load: clean out isp + +isp: $(PROJECT).hex + "$(BINPATH)avrdude" $(ISP_ARGS) -e -u -Ulock:w:0x3f:m -qq -Uefuse:w:0x$(ISP_EFUSE):m -Uhfuse:w:0x$(ISP_HFUSE):m -Ulfuse:w:0x$(ISP_LFUSE):m -Ulock:w:0x0f:m + "$(BINPATH)avrdude" $(ISP_ARGS) -V -q -s -Uflash:w:$(PROJECT).hex + "$(BINPATH)avrdude" $(ISP_ARGS) diff --git a/MYSBootloader/boards.txt b/MYSBootloader/boards.txt new file mode 100644 index 0000000..e84e670 --- /dev/null +++ b/MYSBootloader/boards.txt @@ -0,0 +1,62 @@ +# See: http://code.google.com/p/arduino/wiki/Platforms + +menu.cpu=Processor + +uno.name=Arduino Uno Optiboot + +uno.vid.0=0x2341 +uno.pid.0=0x0043 +uno.vid.1=0x2341 +uno.pid.1=0x0001 + +uno.upload.tool=avrdude +uno.upload.protocol=arduino +uno.upload.maximum_size=32256 +uno.upload.maximum_data_size=2048 +uno.upload.speed=115200 + +uno.bootloader.tool=avrdude +uno.bootloader.low_fuses=0xFF +uno.bootloader.high_fuses=0xDE +uno.bootloader.extended_fuses=0x05 +uno.bootloader.unlock_bits=0x3F +uno.bootloader.lock_bits=0x0F +uno.bootloader.file=optiboot/optiboot_atmega328.hex + +uno.build.mcu=atmega328p +uno.build.f_cpu=16000000L +uno.build.board=AVR_UNO +uno.build.core=arduino +uno.build.variant=standard + + +## Arduino Pro or Pro Mini (3V3 & 5V, 16 MHz) w/ ATmega328 MYSBootloader +## ------------------------------------------------- + + +proMYSBL.name=ATmega328 16Mhz MYSBootloader + +proMYSBL.upload.tool=avrdude +proMYSBL.upload.protocol=arduino +proMYSBL.upload.maximum_size=30720 +proMYSBL.upload.maximum_data_size=2048 +proMYSBL.upload.speed=115200 + +proMYSBL.bootloader.tool=avrdude +proMYSBL.bootloader.low_fuses=0xF7 +proMYSBL.bootloader.high_fuses=0xDA +proMYSBL.bootloader.extended_fuses=0x06 +proMYSBL.bootloader.unlock_bits=0x3F +proMYSBL.bootloader.lock_bits=0x0F +proMYSBL.bootloader.file=MySensors/MYSBootloader.hex + +proMYSBL.build.mcu=atmega328p +proMYSBL.build.f_cpu=16000000L +proMYSBL.build.board=AVR_UNO +proMYSBL.build.core=arduino +proMYSBL.build.variant=standard + + + + +