MCV876 Controller Update: Bootloader

Aus Synthesizer Wiki
Zur Navigation springen Zur Suche springen

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