Bootloader woes..

A project log for MiniSam-Zero

Tiny sameD09 dev board. (Zero has nothing to do with arduino, the name allows other boards in this line. namely -One, & -Two coming later.)

Jeremy g.Jeremy g. 07/02/2016 at 01:320 Comments

Lets take a look at some code. Maybe I can get some help with this as I have been beating my head against the desk trying to get this to work. I think I am almost there.

 * main.c
 *Project:		miniSam USART bootloader.
 *Author:		Weistek Engineering (jeremy G.)
 *date:			06-29-2016f
 *Summery:		Modified version of the Samd10 bootloader.
 *				If PA15 bootpin is held low, micro will enter USART bootloader mode.
 *				if PA15 is high, micro runs user program if there is one at new start
 *				memory. Look at APP_START for start location of user flash.
 *Important pins : 	UART pins [PA25 PAD3 -> TXd, PA24 PAD2 -> RXd]
 *					Boot En Pin PA15: enabled boot on reset when DTR pin LOW. Change to PA27?
 *					USART reset pin -> RTS -> RST PIN#. Used to reset the micro when
 *					Serial is plugged in, pulse RTS LOW. Almost arduino esqe.
 *Update:			fixed write_nvm function, would fall to dummy handler.
 *Todo:				need to fix Verify flash function, flash contents don't match.
 *					or they seem not to.

#include "sam.h"
#include <component/nvmctrl.h>

#define PORTA 0 //Samd09 only has one port Port0

/* Application starts from 1kB memory - Bootloader size is 1kB */
/* Change the address if higher boot size is needed */
/*good site for quick conversions.*/
#define APP_START	0x00000600 //This gives 1536 bytes of bootloader space.

/* Target application size can be 15kB */
/* APP_SIZE is the application section size in kB */
/* Change as per APP_START */
#define APP_SIZE	13	//This is how much flash memory is left for the application.

/* Flash page size is 64 bytes */
#define PAGE_SIZE	64	//used to read and write to flash.

/* Memory pointer for flash memory */
#define NVM_MEMORY        ((volatile uint16_t *)FLASH_ADDR)

/* Change the following if different SERCOM and boot pins are used */
#define BOOT_SERCOM			SERCOM1		//miniSam uses Sercom1 for USART
#define BOOT_SERCOM_BAUD	115200
#define BOOT_PIN			15 //14		//PA15 for bootloader en, toggled by the python script. or DTR from serial coms.

#define div_ceil(a,b)(((a)+(b)-1)/(b))			//extracted function from samd_math.h <- something like that.

/* SERCOM USART GCLK Frequency */
#define SERCOM_GCLK		8000000UL		//processor speed.
#define BAUD_VAL	(65536.0*(1.0-((float)(16.0*(float)BOOT_SERCOM_BAUD)/(float)SERCOM_GCLK))) //calculate baud rate from SERCOM_GCLK

uint8_t data_8 = 1;
uint32_t file_size, i, dest_addr, app_start_address;
uint8_t page_buffer[PAGE_SIZE];
uint32_t *flash_ptr;

//Version information.
uint8_t aVER[29] = {'m','i','n','i','S','a','m','d',' ','R','1','.','2',
					' ','b','o','o','t','l','o','a','d','e','r',' ','V','0','.','1'};

/*pin pad setup for SERCOM1 and USART*/
static inline void pin_set_peripheral_function(uint32_t pinmux)
    /* the variable pinmux consist of two components:
        31:16 is a pad, wich includes:
            31:21 : port information 0->PORTA, 1->PORTB
            20:16 : pin 0-31
        15:00 pin multiplex information
        there are defines for pinmux like: PINMUX_PA09D_SERCOM2_PAD1 
    uint16_t pad = pinmux >> 16;    // get pad (port+pin)
    uint8_t port = pad >> 5;        // get port
    uint8_t pin  = pad & 0x1F;      // get number of pin - no port information anymore
    PORT->Group[port].PINCFG[pin].bit.PMUXEN =1;
    /* each pinmux register is for two pins! with pin/2 you can get the index of the needed pinmux register
       the p mux resiter is 8Bit   (7:4 odd pin; 3:0 evan bit)  */
    // reset pinmux values.                             VV shift if pin is odd (if evan:  (4*(pin & 1))==0  )
    PORT->Group[port].PMUX[pin/2].reg &= ~( 0xF << (4*(pin & 1)) );
    // set new values
    PORT->Group[port].PMUX[pin/2].reg |=  ( (uint8_t)( (pinmux&0xFFFF) <<(4*(pin&1)) ) ); 

/*init USART module on SERCOM1*/
void UART_sercom_init()
	//Pmux eve = n/1, odd = (n-1)/2
	pin_set_peripheral_function(PINMUX_PA25C_SERCOM1_PAD3); // SAMD09 TX
	pin_set_peripheral_function(PINMUX_PA24C_SERCOM1_PAD2); // SAMD09 RX
	//gclk config
	//Config SERCOM1 module for UART
	SERCOM1->USART.BAUD.reg = BAUD_VAL;//65535.0f * (1.0f - (float)(16*(float)(9600)/(USART_BAUD_MODIFIER_SLOW))); //This gets the miniSam exactly at 9800 baud.
	/* for 115200 baud compiler does not like this.*/
	//SERCOM1->USART.BAUD.reg = 65535.0f * (1.0f - (float)(16*(float)(USART_BAUD_MODIFIER_FAST)/(8000000)));

/* interrupt handler for Sercom1 USART */
void SERCOM1_Handler()  // SERCOM1 ISR
	uint8_t buffer;
	buffer  = SERCOM1->USART.DATA.reg;
	while(!(SERCOM1->USART.INTFLAG.reg & 1)); // wait UART module ready to receive data
	SERCOM1->USART.DATA.reg = buffer;               // just sent that byte aback
	while(!(SERCOM1->USART.INTFLAG.reg & 2)); // wait until TX complete;

//this will be replaced with UART_sercom_simpleWrite function.
void uart_write_byte(uint8_t data)
	BOOT_SERCOM->USART.DATA.reg = (uint16_t)data;

void UART_sercom_simpleWrite(Sercom *const sercom_module, uint8_t data)
	while(!(sercom_module->USART.INTFLAG.reg & 1)); //wait UART module ready to receive data
	sercom_module->USART.DATA.reg = data;
	while(!(sercom_module->USART.INTFLAG.reg & 2)); //wait until TX complete;

//this will be replaced with UART_sercom_simpleRead function.
uint8_t uart_read_byte(void)
	return((uint8_t)(BOOT_SERCOM->USART.DATA.reg & 0x00FF));

void nvm_erase_row(const uint32_t row_address)
	/* Check if the module is busy */
	/* Clear error flags */
	/* Set address and command */
	NVMCTRL->ADDR.reg  = (uintptr_t)&NVM_MEMORY[row_address / 2];

void nvm_write_buffer(const uint32_t destination_address, const uint8_t *buffer, uint16_t length)

	/* Check if the module is busy */

	/* Erase the page buffer before buffering new data */

	/* Check if the module is busy */

	/* Clear error flags */

	uint32_t nvm_address = destination_address / 2;

	/* NVM _must_ be accessed as a series of 16-bit words, perform manual copy
	 * to ensure alignment */
	for (uint16_t k = 0; k < length; k += 2) 
		uint16_t data;
		/* Copy first byte of the 16-bit chunk to the temporary buffer */
		data = buffer[k];
		/* If we are not at the end of a write request with an odd byte count,
		 * store the next byte of data as well */
		if (k < (length - 1)) {
			data |= (buffer[k + 1] << 8);
		/* Store next 16-bit chunk to the NVM memory space */
		NVM_MEMORY[nvm_address++] = data; //<- this supposedly writes the data to the location specified.
                //This is required in order for the NVM write to be accepted. or so it seems.

int main(void)
	/* Check if boot pin is held low - Jump to application if boot pin is high */
	//PORT->Group[BOOT_PORT].OUTSET.reg = (1u << BOOT_PIN); //<- works without this definition.

	if ((PORT->Group[BOOT_PORT].IN.reg & (1u << BOOT_PIN)))
                //This seems to set app_start_address as 0xfffffff <- this is bad.
		app_start_address = *(uint32_t *)(APP_START + 4);
		/* Rebase the Stack Pointer */
		__set_MSP(*(uint32_t *) APP_START + 4);

		/* Rebase the vector table base address */
		SCB->VTOR = ((uint32_t) APP_START & SCB_VTOR_TBLOFF_Msk);

		/* Jump to application Reset Handler in the application */
		asm("bx %0"::"r"(app_start_address));
	/* Make CPU to run at 8MHz by clearing prescalar bits */ 
        SYSCTRL->OSC8M.bit.PRESC = 0;
	/* Config Usart */
	info(); //display version info on usart.
    while (1) 
        data_8 = uart_read_byte();
		if (data_8 == '#')
                        //this works fine.
		else if (data_8 == 'e')
			/*this has been fixed, it no longer fails to 
			a dummy handler*/
			for(i = APP_START; i < FLASH_SIZE; i = i + 256)
			dest_addr = APP_START;
			flash_ptr = APP_START; //0x600
		else if (data_8 == 'p')
                        //with adding the NVMCTRL cmd this seems to work.
                        //only problem is after the micro resets, the data is gone...
                        //Page_Size is 64, the python script sends a page_size of 40?
                        //this used to read for(i=0;i<(PAGE_SIZE/4) /*64/4 = 16 <- good*/;i++)
                        //the old for loop would fall to a dummy handler as well this one does not.
			for (i = 0; i < PAGE_SIZE; i++)
				page_buffer[i] = uart_read_byte();
			nvm_write_buffer(dest_addr, page_buffer, PAGE_SIZE);
			dest_addr += PAGE_SIZE;
		else if (data_8 == 'v')
			/* now we get stuck here... varifing pages fails on the first page.
			don't know why.*/
			for (i = 0; i < (PAGE_SIZE); i++)
                                //app_start_address grabs 0xffffff <- bad again. if we omit app_start_address
                                //and simply use uart_write_byte(SERCOM1,(uint8_t)(*flash_ptr>>8)); we get the proper
                                //data back... eventually for some reason we start at 0xBEC instead of 0x600+4..
                                //what am i missing? someone told me we need to do this
                                //app_start_address = &flash_ptr <- to de reference the flash_ptr. have not tried this yet.
				app_start_address = *flash_ptr;
				UART_sercom_simpleWrite(SERCOM1,(uint8_t)(app_start_address >> 8));
				//uart_write_byte((uint8_t)(app_start_address >> 8));
				UART_sercom_simpleWrite(SERCOM1,(uint8_t)(app_start_address >> 16));
				//uart_write_byte((uint8_t)(app_start_address >> 16));
				UART_sercom_simpleWrite(SERCOM1,(uint8_t)(app_start_address >> 24));
				//uart_write_byte((uint8_t)(app_start_address >> 24));

void info()
	uint8_t i;
	for(i = 0;i<=29;i++)

This code is heavily commented and works, just not in the way it should to accomplish a functional bootloader. the start of our app is at 0x600+4 this is the address that nvm_write() should write the data too, instead we write at 0xBEC ? I don't know why.

if we assign a uint32_t variable with *flash_ptr witch should be at 0x600 we actually get 0xffffff <- this is wrong or at least i see it as wrong.

I should have more time to mess with this tonight, I will try de-reference the flash pointer and also try setting the APP_START variable to 0xBEC and see if that helps. It may be that the bootloader sections are set in stone and if you go over the limit for the next lowest allowed size it automatically moves to the next largest space so 0x600 = 1536 where as the program wants to write data too 0xBEC = 3052 thats 3kb from the start of the address space. this makes no sense as the datasheet says bootloader address spacing is from 0, 256, 512, 1024, 2048...

any information. would be great. This post may seem a bit haphaserd, it was a quick one.

Stay happy, stay healthy, and keep hackin.