Project: EOGee – Programming the EOGlass microcontrollers

There are two main ways to program the STM32 microcontroller in the EOGlass prototypes. The first is by using a dedicated programmer/debugger (e.g. the STLink) to connect to the SWD pins of the chip and programming it directly. However this requires adding a pin header to the board to enable this connection. The second option is to use the bootloader.

The bootloader is a small program that is stored in the microcontroller flash memory that can receive a program via SPI/USB/UART etc. and program itself. In this case, we have a USB port on the board anyway, so we can use this to program the device without having to add any more connections to the board.

In order to enter the bootloader, however, it is necessary to hold the BOOT pin high while resetting the device. This is typically done using a pull-down resistor and a push button to pull the BOOT pin high when you want to enter bootloader.

Resistor R109 holds BOOT0 pin low until you press SW101

However because the EOGlass designs are very space constrained I did not have space for a button. My solution was to design a secondary “programmer board”. This board houses the EOGlass PCB and connects via a number of pogo pins of the reverse side. These pogo pins break out the connections to power, ground, reset, boot and SWD (for programming) to a 0.1″ header. The analog signals are also broken out via coax connections to a headphone jack.

Top side of programmer board
Bottom side of programmer board

These pogo pins and coax connections correspond with connectors and pogo pads on the EOGlass design

EOGlass2 design with pogo pads, coax connectors and threaded standoffs

This method enables the EOGlass board to be easily programmed when connected to the programmer board using either the SWD port or the USB bootloader.

Unsurprisingly I want to be able to program the device while it is in the glasses too, however once mounted on the glasses I have no access to the pogo pins which means I cannot access the connections for SWD nor the boot pin to enter the bootloader.

The pogo pads are inaccessible when the PCB is mounted on the glasses

The bootloader is simply a program in memory and therefore it is possible to jump to the location in memory and execute the code directly from my own firmware. This is what I decided to do.

Because the bootloader expects to be run immediately after booting it also expects all hardware peripherals to be configured in their default state. Therefore it is necessary to reset all peripherals and registers before jumping to the bootloader. However, as the firmware gets more complex it becomes difficult to keep track of all changes and ensure they are reset to default values. Instead I created a “magic flag”.

// This byte is maintained in SRAM (no init) between soft resets and used to flag that we should enter bootloader
uint32_t reset_bootloader_flag __attribute__((section(".noinit")));

This is a special global variable in SRAM that does not reset to it’s default value each time the microcontroller soft-resets. This means that in my firmware, when I want to enter the bootloader, I can simply set this special flag to a certain value and reset the software. When the firmware starts again, the first thing it does is check this flag to see if we want to enter the bootloader. This way, the chip is in it’s default state and we can jump straight to the bootloader.

// Start of program
int main(void)
{
  // Check if bootloader flag has been set correctly
  if(reset_bootloader_flag == reset_bootloader_magic_number)
  {
	  // Enter bootloader
	  reset_bootloader_flag = 0;
	  JumpToBootloader();
  }
  else
  {
	  // Don't enter bootloader
  }
...

We now need a way to know if the user wants to enter the bootloader and this is done via the USB-serial communication. When the device receives the letter ‘b’ over USB it sets the bootloader flag and resets the device.

// Callback for when data is recieved via CDC, called from usbd_cdc_if.c
void receive_cdc_data(uint8_t* buf, uint16_t len)
{
	if(len == 1)
	{
		if(buf[0] == 'b')
		{
			reset_bootloader_flag = reset_bootloader_magic_number;
			NVIC_SystemReset();
		}
	}
}

The NVIC_SystemReset() is a macro provided by STMicro in one of the core header files and simply resets the device as if I had pressed a reset button.

The actual function for jumping to the bootloader (JumpToBootloader) was modified from an online tutorial but effectively we are defining a function called SysMemBootJump that points to the bootloader in memory and then calling that function.

//From: https://stm32f4-discovery.net/2017/04/tutorial-jump-system-memory-software-stm32/
void JumpToBootloader(void) {

	void (*SysMemBootJump)(void);

	/**
	 * Step: Set system memory address.
	 *
	 *       For STM32L412xx, system memory is on 0x1FFF 0000
	 *       For other families, check AN2606 document table 110 with descriptions of memory addresses
	 */
	volatile uint32_t addr = 0x1FFF0000;

	/**
	 * Step: Disable RCC, set it to default (after reset) settings
	 *       Internal clock, no PLL, etc.
	 */
#if defined(USE_HAL_DRIVER)
	HAL_RCC_DeInit();
#endif /* defined(USE_HAL_DRIVER) */
#if defined(USE_STDPERIPH_DRIVER)
	RCC_DeInit();
#endif /* defined(USE_STDPERIPH_DRIVER) */

	/**
	 * Step: Disable systick timer and reset it to default values
	 */
	SysTick->CTRL = 0;
	SysTick->LOAD = 0;
	SysTick->VAL = 0;

	/**
	 * Step: Remap system memory to address 0x0000 0000 in address space
	 *       For each family registers may be different.
	 *       Check reference manual for each family.
	 *
	 *       For STM32F4xx, MEMRMP register in SYSCFG is used (bits[1:0])
	 *       For STM32F0xx, CFGR1 register in SYSCFG is used (bits[1:0])
	 *       For others, check family reference manual
	 */
	__HAL_SYSCFG_REMAPMEMORY_SYSTEMFLASH();	//Call HAL macro to do this for you

	/**
	 * Step: Set jump memory location for system memory
	 *       Use address with 4 bytes offset which specifies jump location where program starts
	 */
	SysMemBootJump = (void (*)(void)) (*((uint32_t *)(addr + 4)));

	/**
	 * Step: Set main stack pointer.
	 *       This step must be done last otherwise local variables in this function
	 *       don't have proper value since stack pointer is located on different position
	 *
	 *       Set direct address location which specifies stack pointer in SRAM location
	 */
	__set_MSP(*(uint32_t *)addr);

	/**
	 * Step: Actually call our function to jump to set location
	 *       This will start system memory execution
	 */
	SysMemBootJump();
}

This method is successful at getting me into the bootloader so that I can reprogram the microcontroller using only USB. Once this is complete all that is left to do is to start the firmware as usual. For this we have to power cycle the device as there is no way to issue a reset when in the bootloader without access to the reset pin of the microcontroller.

One thought on “Project: EOGee – Programming the EOGlass microcontrollers

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s