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; } }