MCV876 Controller Update: Bootloader
Worum geht's?
Midi-Bootlader für den ATMega 88.
Empfängt einen als Midi-Sys-Ex Datenstrom eingepacktes Programm-Update und schreibt das in den Programm-Speicher des Controllers.
Compilieren
Der Code wird mit dem AVR-Studio 4 erzeugt (neues Projekt erzeugen, Quelltext in das Default-File kopieren, fertig)
Einzige Besonderheit ist, dass der Code für die Boolader-Adresse gelinkt werden muss. Dazu unter Projekt-> Configuration Options -> Custom Options -> Linker Options eine neue Option "-Ttext=0x1800" zufügen (add)
(Im Prinzip ist das hier nicht viel anders als das, was hier beschrieben wird: http://www.mikrocontroller.net/articles/AVR_Bootloader_in_C_-_eine_einfache_Anleitung )
Controller programmieren
Der Bootlader muss mit einem Programmiergerät in den Controller "geflasht" werden. Das sollte mit jedem Programmieradapter gehen, der für den ATmega 88 gedacht ist.
Für ISP-Adapter braucht man irgendeine Platine mit ISP-Sockel, da der Adapter keine Programmierpins hat. Ich habe einfach eine Platine eines anderen Projektes genommen...
Damit der Bootlader so arbeitet wie gewünscht, muss man die "Fuses" des Controllers richtig setzen:
Hier als Screen-Shot aus dem AVR-Studio 4, so sieht das mit dem Dragon als Programmieradapter aus. Sollte sich aber problemlos auf andere Programmiergeräte übertragen lassen.
- Der Bootbereich darf sich nicht selber überschreiben (Look SPM in Boot Section)
- 2k-Byte Boot-Bereich (kleiner hat keinen Zweck, da die ganzen 2k immer nur unter CPU-Halt beschrieben werden können, was unsere Update-Infrastruktur aber nicht zulässt)
- Reset springt in den Bootlader
- Der Reset-Pin ist I/O Pin. (Vorsicht, das sperrt den ISP-Prgrammer final aus - erst ganz zum Schluß machen!)
- Unterspannungsdetektor bei 4.3V (eher theoretisch...)
- Oszillator für externen Quarz.
Ein "neuer" Atmega steht übrigens auf intern 8MHz-RC/8, da muss man, zumindest beim Dragon, den Takt erst mal kleiner stellen, sonst spricht der Dragon gar nicht mit dem Controller.
Quelltext
/*
MCV876 with ATMega 88
Bootloader
Preliminary
Version 0.98 2011-06-26
*/
/*
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, either version 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
*/
#include <avr/boot.h>
#include <avr/pgmspace.h>
#include <avr/io.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <util/crc16.h>
#define LEDGATE0 0 // Led an Gate 0, keine LED Ansteuerung
#define LEDGATE4 4 // LED an Gate 5,
#define LEDLED 6 // LED an Reset-Pin (Serie, kein Debuging)
#define LEDOUT LEDLED
#define BUTTON (!(PIND & (1<<4)))
void (*applicationmain)(void) = (void *)0x0000;
void (*bootloader)(void) = (void *)0x1800;
volatile unsigned char program_it;
volatile unsigned char prog_active;
volatile unsigned char flash_buf[64];
volatile unsigned char blockAdress;
#define EESTATUS_OK 1 // last flash was ok
#define EESTATUS_START 2 // last flash was started (if read on boot-up: ... but never finished)
#define BOOTEEPROMADR ((uint8_t *) 500) // permanent Flag for last update status ('started' or 'success')
// more or less obvisious the permanent state musst be success, else it failed to complete
// and that can't be ok...
#define PROGRAM_IDLE 0
#define PROGRAM_DO 1
#define PROGRAM_FINISH 2
#define PROGRAM_ERROR 3
#define PROGRAM_START 4
#define PROGRAM_ABORT 5
/* ***********************************************************************************
LED
*/
void setLed(unsigned char on)
{
#if LEDOUT == LEDLED
if (on) // PC6
PORTC |= (1<< 6);
else
PORTC &=~(1<< 6);
#endif
#if LEDOUT == LEDGATE4
if (on) // PB1
PORTB |= (1<< 1);
else
PORTB &=~(1<< 1);
#endif
}
/* ***********************************************************************************
Main
*/
int main(void)
{
unsigned char i,j;
unsigned short int flashAdress;
volatile uint16_t tmp;
volatile uint16_t adr;
/*
HARDWARE INIT
*/
cli(); // just to be sure... its not allways a cold boot which runs here..
// Input/Output Ports initialization
// DDRx = Define I/O direction for each pins
// PORTx = If pin is Output then `0' = lo, `1' = hi
// If pin is input then `0' = no pullup, `1' = pullup enabled
// `0' = Input `1' = Output
// Port B initialization
DDRB = 0x3f; // PB0=G4; PB1=G5 (Opt. LED); PB2=G6 ; PB3=WR1; PB4=D0; PB5=D1 (all output)
PORTB = 0x04; // WR1 High, Rest Low
// Port C initialization
DDRC = 0x7F; // PC0=D2; PC1=D3; PC2=D4; PC3=D5; PC4=D6; PC5=D7; PC6=LED (opt.) (all output)
PORTC = 0x00; // all Low
// Port D initialization
DDRD = 0xee; // [PD0=RX]; PD1=WR2; PD2=WR3; PD3=WR4; PD4=Button; PD5=G1; PD6=G2; PD7=G3
PORTD = 0x1F; // Pullup on RX & Button, all WR High, Gates Low
if(GPIOR1==0xAB)
{
// app intended Boot Load
GPIOR1 = 0;
}
else
{ // no main application intended Boot-Load
_delay_ms(10); // Wait for Button-Pin to get High if not pressed
GPIOR1=0;
while(BUTTON && GPIOR1 < 8)
{
_delay_ms(1800);
setLed(1);
_delay_ms(200);
setLed(0);
GPIOR1++;
}
_delay_ms(10);
if( ( !BUTTON && GPIOR1==0 ) || GPIOR1 >= 8)
{
unsigned char x;
_delay_ms(100);
x=eeprom_read_byte(BOOTEEPROMADR);
if(x != EESTATUS_START && pgm_read_word(0)!=0xffff ) // if no failed update atemps and not erased
{
_delay_ms(100);
applicationmain();
}
}
}
/* Enable change of Interrupt Vectors */
MCUCR = (1 << IVCE );
/* Move Interrupts to boot flash section */
MCUCR = ( 1 << IVSEL );
// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 20Mhz /64
// Mode: CTC
// OC0 output: Disconnected
OCR1A= 625; // 2 ms
TCCR1A =0;
TCCR1B =8+3; // CTC Mode, TOP from OCR1A + Clock/64
TIMSK1 =2; // OCF1A Interupt
// USART initialization
// Communication Parameters: 8 Data, 1 Stop, No Parity
// USART Receiver: On
// USART Transmitter: OFF
// USART Mode: Asynchronous
UCSR0A = 0x00;
/* Set frame format: 8data, 1stop bit */
UCSR0C = 6;
// USART Baud rate: 31250; 20E6/16/31250 - 1
UBRR0H = 0;
UBRR0L = 39;
/*Enable receiver , Enable Rx-int */
UCSR0B = ((1<<RXEN0)|(1<<RXCIE0) ) ;
/*
HARDWARE READY
*/
sei(); // Interupts on
while(1) // Main Loop
{
if(program_it== PROGRAM_DO || program_it== PROGRAM_FINISH)
{
flashAdress=blockAdress ; // Block number
flashAdress<<=6; // * block size of 64 bytes
cli(); // some register writes must be done within 4 clock cycles max, Interupts can kill that
boot_page_erase(flashAdress); // issue the block erease command
sei();
do
{
cli();
j=(__SPM_REG & (uint8_t)_BV(__SPM_ENABLE)); // j=it's done?
sei();
}while(j); // wait until j says, that it is done
// this takes some millisekonds, during which we'll receive about a quarter of the next block
// so interupts can't be off the whole time.
;
for (i = 0; i < 64; i+=2) // fill page buffer with 32 words (à two bytes each)
{
tmp = (flash_buf[i]) | (flash_buf[i + 1]<<8);
adr=flashAdress+i;
cli(); // again, one of the "max 4 cycle" macros
boot_page_fill(adr, tmp); // write a word
sei();
}
cli();
boot_page_write(flashAdress); // write to flash
sei();
do // wait until write is done
{
cli(); // prepare uninteruptable max. 4 Cylce sequence
j=(__SPM_REG & (uint8_t)_BV(__SPM_ENABLE)); // read ready flag
sei(); // done
}while(j); // while not ready. Again this takes 3.5 to 4.5ms, during which about another quarter of the
// next block comes in. As we don't want to lose any chars, the interupts cant be turned off the whole time
cli();
boot_rww_enable(); // enable read access to programm flash.
sei();
if(program_it==PROGRAM_FINISH) // if last block
{
eeprom_write_byte(BOOTEEPROMADR,EESTATUS_OK); // store flag for successfull update
prog_active=PROGRAM_IDLE; // should be senseless, but inhibits permanent eeprom writes if something goes wrong...
// Update done, go to new application programm
cli(); // no more intupts
/* Enable change of Interrupt Vectors */ // set interupt vectors to program
MCUCR = (1 << IVCE );
/* Move Interrupts to regular flash section */
MCUCR = 0; // ( 0 << IVSEL );
applicationmain(); // call application (never comes back, stack is lost...)
}
if(program_it!=PROGRAM_ERROR) // if not error // which might happen asynchronous - and should be kept
program_it=PROGRAM_IDLE; // programming of current page done.
}
if(program_it==PROGRAM_START)
{
prog_active=PROGRAM_START;
eeprom_write_byte(BOOTEEPROMADR,EESTATUS_START);
if(program_it!=PROGRAM_ERROR) // might happen asynchronous
program_it=PROGRAM_IDLE;
}
if(program_it==PROGRAM_ABORT)
{
cli();
/* Enable change of Interrupt Vectors */
MCUCR = (1 << IVCE );
/* Move Interrupts to regular flash section */
MCUCR = 0; // ( 0 << IVSEL );
bootloader(); // checks, if there is a valid program to start
}
}
}
/* ***********************************************************************************
2ms Timer Interupt
*/
volatile unsigned char dataRx;
#define DATARX 4
ISR(TIMER1_COMPA_vect)
{
static unsigned char bit;
static unsigned char blinker;
static unsigned char blinks[]={ 40 , 10, 200, 10, 15,15,15,15 };
static unsigned char btn;
if (blinker-- == 0)
{
bit++;
bit&=3;
blinker=blinks[bit | dataRx];
setLed(bit&1);
dataRx=0;
}
if(BUTTON)
{
btn=10;
}
else
{
if(btn>0)
{
btn--;
if(btn == 0)
{
if(prog_active==PROGRAM_IDLE && program_it == PROGRAM_IDLE)
program_it=PROGRAM_ABORT;
}
}
}
}
/* ***********************************************************************************
SERIAL MIDI INTERFACE
*/
#define SYSEX 0xF0
#define SYSEND 0xF7
#define FRAMING_ERROR (1<<FE0)
#define PARITY_ERROR (1<<UPE0)
#define DATA_OVERRUN (1<<DOR0)
#define STAT_IDLE 0
#define STAT_INSYSEX1 1
#define STAT_INSYSEX2 2
#define STAT_INSYSEX3 3
#define STAT_INSYSEX4 4
#define STAT_INSYSEX5 5
#define STAT_INSYSEX6 6
#define STAT_PROGRAM_BYTES 7
#define STAT_WAITEND 8
#define STAT_ERROR 9
unsigned char rx_buf[68]; // 1Byte Block-Adress, 64 Data-Bytes, 2Byte CRC
// USART Receiver interrupt service routine
ISR(USART_RX_vect)
{
static unsigned char status, error, current_block,num_blocks;
// static unsigned char nibble_cnt, data;
static unsigned short int runningByte; // no joke, this accumulates our 8-bit-bytes from 7bit midi, so it must hold up to 14bit in between...
static unsigned char byte_cnt,bits;
static unsigned short int crc;
unsigned char rxStat,byte;
rxStat = UCSR0A;
byte = UDR0;
if ((rxStat & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0 )
{
if(status > STAT_IDLE)
dataRx=DATARX;
if (byte > 0x7F ) // Command byte
{
if( byte < 0xF8 ) // no Real-Time Message
{
if(byte == SYSEX ) // Start
{
status=STAT_INSYSEX1;
}
else if(byte==SYSEND) // SysEx-End (might not be ours)
{
if( status==STAT_PROGRAM_BYTES) // properly framed SysEx received
{
status=STAT_IDLE;
if(crc==0 )
{
unsigned char i;
if(rx_buf[0]==0 && byte_cnt==6 ) // first block = info block
{
error=0;
current_block=1;
num_blocks=rx_buf[1];
for(i=0;i<2;i++)
flash_buf[i]=rx_buf[i+2];
program_it=PROGRAM_START;
}
else if(byte_cnt==67 && current_block==rx_buf[0] )
{ // Programm Data Block
for(i=0;i<64;i++)
flash_buf[i]=rx_buf[i+1];
blockAdress=rx_buf[0]-1;
if(!error)
{
if(num_blocks==current_block)
{
program_it=PROGRAM_FINISH;
}
else
{
program_it=PROGRAM_DO;
}
}
current_block++;
}
else
{
status=STAT_ERROR;
}
}
else
status=STAT_ERROR;
}
else // not my frame format
status=STAT_IDLE;
}
else // some other Status byte
{
if(status!=STAT_IDLE)
{
if(status<STAT_PROGRAM_BYTES) // might be unrelated to us
status=STAT_IDLE;
else
status=STAT_ERROR; // our Data gets messed up
}
}
} // no real time message
}
else // Data Byte
{
switch(status)
{
default: // should never happen
status=STAT_ERROR;
case STAT_IDLE: // any Data which is not in our SysExFrame
break;
case STAT_INSYSEX1: // Manufacturer
if(byte==0x70)
{
status=STAT_INSYSEX2;
}
else
status=STAT_IDLE;
break;
case STAT_INSYSEX2: // Unit ID
if(byte==0x7D)
{
status=STAT_INSYSEX4;
}
else
status=STAT_IDLE;
break;
case STAT_INSYSEX4: // Unit Adress (ignored)
if(byte< 0x10)
{ status=STAT_INSYSEX5;
}
else
status=STAT_IDLE;
break;
case STAT_INSYSEX5: // "Data Adress" (magic byte 1)
if(byte == 0x15)
{
status=STAT_INSYSEX6;
}
else
status=STAT_IDLE;
break;
case STAT_INSYSEX6: // magic byte 2 (this would stop the regular SysEx Decoder from taking this as data)
if(byte == 0x55)
{
status=STAT_PROGRAM_BYTES;
crc=0xffff;
byte_cnt=0;
bits=0;
}
else
status=STAT_IDLE;
break;
case STAT_PROGRAM_BYTES: // we've made it: now the new flash data rolls in
runningByte <<=7;
runningByte|=byte;
bits+=7;
if(bits>=8) // if enough for one Byte
{
unsigned char b;
bits-=8;
b=runningByte>>bits;
rx_buf[byte_cnt]=b;
crc=_crc16_update(crc,b);
if(++byte_cnt>67)
status=STAT_ERROR;
}
break;
} // switch status
} // data byte
}
else // any Rx-Erros => abort
status=STAT_ERROR;
if(status==STAT_ERROR)
{
status=STAT_IDLE;
error=1;
}
}