You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
455 lines
12 KiB
455 lines
12 KiB
/*************************************************************************** |
|
* Copyright (C) 11/2014 by Olaf Rempel * |
|
* razzor@kopf-tisch.de * |
|
* * |
|
* This program is free software; you can redistribute it and/or modify * |
|
* it under the terms of the GNU General Public License as published by * |
|
* the Free Software Foundation; version 2 of the License, * |
|
* * |
|
* This program is distributed in the hope that it will be useful, * |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of * |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
|
* GNU General Public License for more details. * |
|
* * |
|
* You should have received a copy of the GNU General Public License * |
|
* along with this program; if not, write to the * |
|
* Free Software Foundation, Inc., * |
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * |
|
***************************************************************************/ |
|
#include <avr/io.h> |
|
#include <avr/interrupt.h> |
|
#include <avr/boot.h> |
|
#include <avr/pgmspace.h> |
|
|
|
#include <string.h> |
|
|
|
#include "target.h" |
|
#include "rfm12.h" |
|
|
|
/* *********************************************************************** */ |
|
|
|
#define BOOTWAIT_EXPIRED 0x0000 |
|
#define BOOTWAIT_INTERRUPTED 0xFFFF |
|
|
|
#define MSG_TYPE_REQUEST 0x00 /* master -> slave req */ |
|
#define MSG_TYPE_CONFIRMATION 0x40 /* master -> slave rsp */ |
|
#define MSG_TYPE_INDICATION 0x80 /* slave -> master req */ |
|
#define MSG_TYPE_RESPONSE 0xC0 /* slave -> master rsp */ |
|
#define MSG_TYPE_MASK 0xC0 |
|
#define MSG_CMD_MASK 0x3F |
|
|
|
#define MSG_CMD_SWITCHAPP_REQUEST (MSG_TYPE_REQUEST | 0x20) |
|
#define MSG_CMD_SWITCHAPP_RESPONSE (MSG_TYPE_RESPONSE | 0x20) |
|
|
|
#define MSG_CMD_VERSION_REQUEST (MSG_TYPE_REQUEST | 0x21) |
|
#define MSG_CMD_VERSION_RESPONSE (MSG_TYPE_RESPONSE | 0x21) |
|
|
|
#define MSG_CMD_CHIPINFO_REQUEST (MSG_TYPE_REQUEST | 0x22) |
|
#define MSG_CMD_CHIPINFO_RESPONSE (MSG_TYPE_RESPONSE | 0x22) |
|
|
|
#define MSG_CMD_READ_REQUEST (MSG_TYPE_REQUEST | 0x23) |
|
#define MSG_CMD_READ_RESPONSE (MSG_TYPE_RESPONSE | 0x23) |
|
|
|
#define MSG_CMD_WRITE_REQUEST (MSG_TYPE_REQUEST | 0x24) |
|
#define MSG_CMD_WRITE_RESPONSE (MSG_TYPE_RESPONSE | 0x24) |
|
|
|
#define CAUSE_SUCCESS 0x00 |
|
#define CAUSE_NOT_SUPPORTED 0xF0 |
|
#define CAUSE_INVALID_PARAMETER 0xF1 |
|
#define CAUSE_UNSPECIFIED_ERROR 0xFF |
|
|
|
#define BOOTMODE_BOOTLOADER 0x00 |
|
#define BOOTMODE_APPLICATION 0x80 |
|
|
|
#define MEMTYPE_FLASH 0x01 |
|
#define MEMTYPE_EEPROM 0x02 |
|
|
|
/* *********************************************************************** */ |
|
|
|
struct bootloader_msg |
|
{ |
|
uint8_t command; |
|
uint8_t seqnum; |
|
uint8_t cause; |
|
|
|
union { |
|
struct { |
|
uint8_t app; |
|
} switchapp; |
|
|
|
struct { |
|
uint8_t data[16]; |
|
} version; |
|
|
|
struct { |
|
uint8_t data[8]; |
|
} chipinfo; |
|
|
|
struct { |
|
uint16_t address; |
|
uint8_t mem_type; |
|
uint8_t size; |
|
} read_req; |
|
|
|
struct { |
|
uint8_t data[32]; |
|
} read_rsp; |
|
|
|
struct { |
|
uint16_t address; |
|
uint8_t mem_type; |
|
uint8_t size; |
|
uint8_t data[32]; |
|
} write_req; |
|
} p; |
|
}; |
|
|
|
/* *********************************************************************** */ |
|
|
|
#define LED_RX 0 |
|
#define LED_TX 1 |
|
|
|
static uint8_t led_timer[2]; |
|
volatile static uint8_t clock_tick; |
|
|
|
const static uint8_t version_info[16] = VERSION_STRING; |
|
const static uint8_t chip_info[8] = { |
|
SIGNATURE_BYTES, |
|
|
|
SPM_PAGESIZE, |
|
|
|
((BOOTLOADER_START) >> 8) & 0xFF, |
|
(BOOTLOADER_START) & 0xFF, |
|
((E2END +1) >> 8 & 0xFF), |
|
(E2END +1) & 0xFF |
|
}; |
|
|
|
|
|
/* *********************************************************************** */ |
|
|
|
|
|
static void read_flash(uint8_t *data, uint16_t address, uint8_t size) |
|
{ |
|
while (size--) |
|
{ |
|
*data++ = pgm_read_byte_near(address++); |
|
} |
|
} /* read_flash_byte */ |
|
|
|
|
|
static void write_flash(uint8_t *data, uint16_t address, uint8_t size) |
|
{ |
|
static uint16_t pagestart; |
|
static uint8_t pagesize; |
|
|
|
if ((address & (SPM_PAGESIZE -1)) == 0x00) |
|
{ |
|
pagestart = address; |
|
pagesize = SPM_PAGESIZE; |
|
|
|
boot_page_erase(pagestart); |
|
boot_spm_busy_wait(); |
|
} |
|
|
|
while (size && pagesize) |
|
{ |
|
uint16_t dataword; |
|
|
|
dataword = (*data++); |
|
dataword |= (*data++) << 8; |
|
boot_page_fill(address, dataword); |
|
|
|
address += 2; |
|
pagesize -= 2; |
|
size -= 2; |
|
} |
|
|
|
if (pagesize == 0) |
|
{ |
|
boot_page_write(pagestart); |
|
boot_spm_busy_wait(); |
|
boot_rww_enable(); |
|
} |
|
} /* write_flash */ |
|
|
|
|
|
static void read_eeprom(uint8_t *data, uint16_t address, uint8_t size) |
|
{ |
|
while (size--) |
|
{ |
|
EEARL = address; |
|
EEARH = (address >> 8); |
|
EECR |= (1<<EERE); |
|
|
|
address++; |
|
*data++ = EEDR; |
|
} |
|
} /* read_eeprom */ |
|
|
|
|
|
static void write_eeprom(uint8_t *data, uint16_t address, uint8_t size) |
|
{ |
|
while (size--) |
|
{ |
|
EEARL = address; |
|
EEARH = (address >> 8); |
|
EEDR = *data++; |
|
|
|
address++; |
|
|
|
cli(); |
|
#if defined (__AVR_ATmega168__) |
|
EECR |= (1<<EEMPE); |
|
EECR |= (1<<EEPE); |
|
#endif |
|
sei(); |
|
|
|
eeprom_busy_wait(); |
|
} |
|
} /* write_eeprom */ |
|
|
|
|
|
ISR(TIMER0_OVF_vect) |
|
{ |
|
TCNT0 = TIMER_RELOAD; |
|
|
|
clock_tick = 1; |
|
} /* TIMER0_OVF_vect */ |
|
|
|
|
|
static void (*jump_to_app)(void) __attribute__ ((noreturn)) = 0x0000; |
|
|
|
|
|
#if defined(__AVR_ATmega168__) |
|
/* |
|
* For newer devices the watchdog timer remains active even after a |
|
* system reset. So disable it as soon as possible. |
|
* automagically called on startup |
|
*/ |
|
void disable_wdt_timer(void) __attribute__((naked, section(".init3"))); |
|
void disable_wdt_timer(void) |
|
{ |
|
MCUSR = 0; |
|
WDTCSR = (1<<WDCE) | (1<<WDE); |
|
WDTCSR = (0<<WDE); |
|
} /* disable_wdt_timer */ |
|
#endif |
|
|
|
|
|
int main(void) __attribute__ ((noreturn)); |
|
int main(void) |
|
{ |
|
/* init LEDs */ |
|
LED_INIT(); |
|
|
|
#if defined (__AVR_ATmega168__) |
|
/* move interrupt-vectors to bootloader */ |
|
MCUCR = (1<<IVCE); |
|
MCUCR = (1<<IVSEL); |
|
|
|
/* timer0, FCPU/64, overflow interrupt */ |
|
TCCR0B = (1<<CS01) | (1<<CS00); |
|
TIMSK0 = (1<<TOIE0); |
|
#endif |
|
|
|
rfm12_init(RFM12_ADDRESS); |
|
|
|
sei(); |
|
|
|
uint16_t boot_timeout = TIMEOUT; |
|
while (1) |
|
{ |
|
if (clock_tick == 0) |
|
continue; |
|
|
|
clock_tick = 0; |
|
|
|
if (led_timer[LED_RX] > 0) |
|
{ |
|
led_timer[LED_RX]--; |
|
LED_GN_ON(); |
|
} |
|
else |
|
{ |
|
LED_GN_OFF(); |
|
} |
|
|
|
if (led_timer[LED_TX] > 0) |
|
{ |
|
led_timer[LED_TX]--; |
|
LED_RT_ON(); |
|
} |
|
else |
|
{ |
|
LED_RT_OFF(); |
|
} |
|
|
|
/* do periodic work (wait for 5 ticks silence before start TX) */ |
|
rfm12_tick(5); |
|
|
|
/* get TX buffer */ |
|
struct rfm12_packet *rsp_pkt = rfm12_get_txpkt(); |
|
|
|
if (boot_timeout == BOOTWAIT_EXPIRED) |
|
{ |
|
/* timeout elapsed and TX buffer available (tx completed) -> start application */ |
|
if (rsp_pkt != NULL) |
|
{ |
|
break; |
|
} |
|
} |
|
else if (boot_timeout != BOOTWAIT_INTERRUPTED) |
|
{ |
|
boot_timeout--; |
|
} |
|
|
|
if ((boot_timeout & 0x3F) == 0) |
|
{ |
|
led_timer[LED_TX] = 5; |
|
} |
|
|
|
/* get RX data */ |
|
struct rfm12_packet *req_pkt = rfm12_get_rxpkt(); |
|
if (req_pkt == NULL) |
|
{ |
|
/* no data available */ |
|
continue; |
|
} |
|
else |
|
{ |
|
led_timer[LED_RX] = 5; |
|
|
|
/* no tx buffer available, ignore request */ |
|
if (rsp_pkt == NULL) |
|
{ |
|
rfm12_clear_rx(); |
|
continue; |
|
} |
|
} |
|
|
|
/* stay in bootloader */ |
|
boot_timeout = BOOTWAIT_INTERRUPTED; |
|
|
|
struct bootloader_msg *req_msg = (struct bootloader_msg *)req_pkt->data; |
|
struct bootloader_msg *rsp_msg = (struct bootloader_msg *)rsp_pkt->data; |
|
|
|
/* retransmitted request -> retransmit response */ |
|
if ((req_pkt->source_address == rsp_pkt->dest_address) && |
|
((req_msg->command & MSG_CMD_MASK) == (rsp_msg->command & MSG_CMD_MASK)) && |
|
(req_msg->seqnum == rsp_msg->seqnum) |
|
) |
|
{ |
|
/* RX packet no longer needed */ |
|
rfm12_clear_rx(); |
|
|
|
/* transmit response */ |
|
if (rfm12_start_tx()) |
|
{ |
|
led_timer[LED_TX] = 5; |
|
} |
|
|
|
continue; |
|
} |
|
|
|
rsp_pkt->dest_address = req_pkt->source_address; |
|
rsp_pkt->data_length = 3; |
|
rsp_msg->command = req_msg->command | MSG_TYPE_RESPONSE; |
|
rsp_msg->seqnum = req_msg->seqnum; |
|
rsp_msg->cause = CAUSE_SUCCESS; |
|
|
|
switch (req_msg->command) |
|
{ |
|
case MSG_CMD_SWITCHAPP_REQUEST: |
|
if (req_msg->p.switchapp.app == BOOTMODE_APPLICATION) |
|
{ |
|
boot_timeout = BOOTWAIT_EXPIRED; |
|
} |
|
else if (req_msg->p.switchapp.app != BOOTMODE_BOOTLOADER) |
|
{ |
|
rsp_msg->cause = CAUSE_INVALID_PARAMETER; |
|
} |
|
break; |
|
|
|
case MSG_CMD_VERSION_REQUEST: |
|
memcpy(rsp_msg->p.version.data, version_info, sizeof(rsp_msg->p.version.data)); |
|
rsp_pkt->data_length += sizeof(rsp_msg->p.version.data); |
|
break; |
|
|
|
case MSG_CMD_CHIPINFO_REQUEST: |
|
memcpy(rsp_msg->p.chipinfo.data, chip_info, sizeof(rsp_msg->p.chipinfo.data)); |
|
rsp_pkt->data_length += sizeof(rsp_msg->p.chipinfo.data); |
|
break; |
|
|
|
case MSG_CMD_READ_REQUEST: |
|
if (req_msg->p.read_req.mem_type == MEMTYPE_FLASH) |
|
{ |
|
read_flash(rsp_msg->p.read_rsp.data, |
|
req_msg->p.read_req.address, |
|
req_msg->p.read_req.size); |
|
|
|
rsp_pkt->data_length += req_msg->p.read_req.size; |
|
} |
|
else if (req_msg->p.read_req.mem_type == MEMTYPE_EEPROM) |
|
{ |
|
read_eeprom(rsp_msg->p.read_rsp.data, |
|
req_msg->p.read_req.address, |
|
req_msg->p.read_req.size); |
|
|
|
rsp_pkt->data_length += req_msg->p.read_req.size; |
|
} |
|
else |
|
{ |
|
rsp_msg->cause = CAUSE_INVALID_PARAMETER; |
|
} |
|
break; |
|
|
|
case MSG_CMD_WRITE_REQUEST: |
|
if (req_msg->p.write_req.mem_type == MEMTYPE_FLASH) |
|
{ |
|
write_flash(req_msg->p.write_req.data, |
|
req_msg->p.write_req.address, |
|
req_msg->p.write_req.size); |
|
} |
|
else if (req_msg->p.write_req.mem_type == MEMTYPE_EEPROM) |
|
{ |
|
write_eeprom(req_msg->p.write_req.data, |
|
req_msg->p.write_req.address, |
|
req_msg->p.write_req.size); |
|
} |
|
else |
|
{ |
|
rsp_msg->cause = CAUSE_INVALID_PARAMETER; |
|
} |
|
break; |
|
|
|
default: |
|
rsp_msg->cause = CAUSE_NOT_SUPPORTED; |
|
break; |
|
} |
|
|
|
/* RX packet no longer needed */ |
|
rfm12_clear_rx(); |
|
|
|
/* transmit response */ |
|
if (rfm12_start_tx()) |
|
{ |
|
led_timer[LED_TX] = 5; |
|
} |
|
} |
|
|
|
cli(); |
|
|
|
#if defined (__AVR_ATmega168__) |
|
/* disable timer0 */ |
|
TIMSK0 = 0x00; |
|
TCCR0B = 0x00; |
|
|
|
/* move interrupt vectors back to application */ |
|
MCUCR = (1<<IVCE); |
|
MCUCR = (0<<IVSEL); |
|
#endif |
|
|
|
LED_OFF(); |
|
|
|
jump_to_app(); |
|
} /* main */
|
|
|