Известно, что софт можно дописывать вечно, а всякого рода недочёты на плате полностью исправляются ревизии так к третьей. И если с железом уже ничего не поделаешь, то для обновления микропрограмм придумали неплохой способ обхода ограничений пространства и времени — Bootloader.

Загрузчик — это удобно и полезно, не правда ли? А если загрузчик собственной реализации, то это еще более удобно, полезно и гибко и не стабильно. Ну и конечно же, очень круто!

Так же, это прекрасная возможность углубиться и изучить особенности используемой вычислительной машины — в нашем случае микроконтроллера STM32 с ядром ARM Cortex-M3.

На самом деле, загрузчик — это проще, чем кажется на первый взгляд. В доказательство, под cut'ом соберём свой собственный USB Mass Storage Bootloader!

imageimage

Работать мы будем с самодельной платой на микроконтроллере (дальше — МК) STM32F103RET. Чтобы не переполнять публикацию лишними картинками, приведу усеченную схему этой железки:

image

При написании bootloader'а я руководствовался следующими принципами:

  1. Свой bootloader очень нужен и хватит откладывать это в TODO-лист, пора уже сесть и сделать;
  2. Bootloader должен иметь удобный для пользователя интерфейс загрузки программы. Никаких драйверов, сторонних программ, плат-переходников и жгутов МГТФ провода до целевого устройства. Что может быть проще автоматически определяемого USB флеш накопителя?
  3. Для работы в режиме bootloader'а микроконтроллеру необходима минимальная аппаратная обвязка (фактически, только USB, кварц и кнопка);
  4. Размер boot'а — не главное. Важен, конечно же, но не будем преследовать цель ужать его в пару килобайт. Без мук совести мы поднимем USB стек, поработаем с файловой системой, навтыкаем printf() через строку и вообще не будем особо ни в чем себе отказывать (hello, Standard Peripheral Libraries!);

Погнали

Немного о FLASH


Так как с собственной FLASH-памятью STM32 мы будем работать постоянно и часто, стоит сразу пояснить некоторые ключевые моменты, связанные с этим фактом.

В используемом МК содержится 512 Kbyte FLASH памяти. Она разбита на страницы по 2048 байт:

image

Для нас это означает, что записать несколько байт в произвольный адрес просто так не выйдет. При записи во FLASH возможно только обнулять нужные ячейки, а вот установка единиц выполняется с помощью операции стирания, минимально возможный объем для которой — одна страница. Для этого служит регистр FLASH_AR, в который достаточно записать любой адрес в пределах нужной нам страницы — и она будет заполнена байтами 0xFF. А еще нужно не забыть разблокировать FLASH перед операциями стирания/записи.

Виртуально разобьем FLASH на несколько областей, у каждой из которых будет свое, особое назначение:

image

  • BOOT_MEM — область памяти, выделенная под bootloader;
  • USER_MEM — тут мы будем хранить (и исполнять отсюда же) пользовательскую прошивку. Очевидно, что теперь она имеет ограничение в 200 Kbyte;
  • MSD_MEM — а тут будет MASS STORAGE диск, куда можно закинуть прошивку средствами компьютера и вашей любимой ОС;
  • OTHER_MEM — ну и оставим еще немного места на всякий случай;

USER_MEM будет соответствовать MSD_MEM по размеру. Это логично, т.к. два противоположных случая будут давать либо нехватку памяти в USER_MEM, либо избыток.

А теперь все то же самое, только для машины (и удобства программиста):
#define FLASH_PAGE_SIZE		2048 						//2 Kbyte per page
#define FLASH_START_ADDR	0x08000000					//Origin
#define FLASH_MAX_SIZE		0x00080000					//Max FLASH size - 512 Kbyte
#define FLASH_END_ADDR		(FLASH_START_ADDR + FLASH_MAX_SIZE)		//FLASH end address
#define FLASH_BOOT_START_ADDR	(FLASH_START_ADDR)				//Bootloader start address
#define FLASH_BOOT_SIZE		0x00010000					//64 Kbyte for bootloader
#define FLASH_USER_START_ADDR	(FLASH_BOOT_START_ADDR + FLASH_BOOT_SIZE)	//User application start address
#define FLASH_USER_SIZE		0x00032000					//200 Kbyte for user application
#define FLASH_MSD_START_ADDR	(FLASH_USER_START_ADDR + FLASH_USER_SIZE)	//USB MSD start address
#define FLASH_MSD_SIZE		0x00032000					//200 Kbyte for USB MASS Storage
#define FLASH_OTHER_START_ADDR	(FLASH_MSD_START_ADDR + FLASH_MSD_SIZE)		//Other free memory start address
#define FLASH_OTHER_SIZE	(FLASH_END_ADDR - FLASH_OTHER_START_ADDR)	//Free memory size

Договорившись о разбиении памяти на области, самое время прикинуть, как это все будет взаимодействовать. Нарисуем блок-схему:

image

Согласно такому алгоритму, bootloader имеет два основных режима, работающих независимо друг от друга, но имеющих общий ресурс — кусок памяти MSD_MEM. Однако, даже его использование происходит в разные моменты времени, что положительно влияет на стабильность работы bootloader'а и упрощает процесс программирования и отладки.

  1. Первый режим отвечает за получение и хранение пользовательского ПО в области MSD_MEM, которая доступна, как внешний накопитель.

  2. Второй режим проверяет MSD_MEM на наличие файла с именем «APP.BIN», проверяет его целостность, подлинность, а так же перемещает в USER_MEM, если там пусто или если прошивка «APP.BIN» более свежая.

Рассмотрим каждый из режимов подробнее:

USB Mass Storage Device


Запускается сразу же после входа в main() в случае, если выполнено соответствующее условие запуска — зажата кнопка. На моей плате это верхний ползунок двухпозиционного переключателя (который, кстати, заведен на ножки МК BOOT0 и BOOT1(PB2) — это позволяет задействовать аппаратный UART загрузчик МК в случае такой необходимости).

int main(void)
int main(void)
{
	Button_Config();
	if(GPIO_ReadInputDataBit(BUTTON_PORT, BUTTON_PIN) == SET) //Bootloader or Mass Storage?
	{
		LED_RGB_Config();
		USB_Config();
		Interrupts_Config();
		USB_Init();
		while(TRUE);
	}
//Bootloader mode
}

Работа в режиме Mass Storage взята из примеров от STMicroelectronics (STM32_USB-FS-Device_Lib_V4.0.0), которые можно скачать с их сайта. Там нам показывают, как нужно (или наоборот, не нужно — отношение к библиотекам от ST у народа не всегда положительное) работать с микроконтроллером и картой памяти, подключенной по интерфейсу SDIO в режиме USB MSD. В примере реализованы два Bulk In/Out Endpoint'а с длиной пакета 64 байта, а так же набор необходимых для работы SCSI команд. Выкидываем оттуда функции, связанные с SD картами или NAND памятью (mass_mal.c/.h) и заменяем их на работу с внутренней FLASH:

u16 MAL_Init(u8 lun)
u16 MAL_Init(u8 lun) 
{
	switch (lun)
	{
		case 0:
			FLASH_Unlock();
			break;

		default:
			return MAL_FAIL;
	}

	return MAL_OK;
}

u16 MAL_Read(u8 lun, u32 memOffset, u32 *readBuff)
u16 MAL_Read(u8 lun, u32 memOffset, u32 *readBuff)
{
	u32 i;

	switch (lun)
	{
		case 0:
			LED_RGB_EnableOne(LED_GREEN);

			for(i = 0; i < MassBlockSize[0]; i += SIZE_OF_U32)
			{
				readBuff[i / SIZE_OF_U32] = *((volatile u32*)(FLASH_MSD_START_ADDR + memOffset + i));
			}

			LED_RGB_DisableOne(LED_GREEN);
			break;

		default:
			return MAL_FAIL;
	}

	return MAL_OK;
}

u16 MAL_Write(u8 lun, u32 memOffset, u32 *writeBuff)
u16 MAL_Write(u8 lun, u32 memOffset, u32 *writeBuff)
{
	u32 i;

	switch (lun)
	{
		case 0:
			LED_RGB_EnableOne(LED_RED);

			while(FLASH_GetStatus() != FLASH_COMPLETE);
			FLASH_ErasePage(FLASH_MSD_START_ADDR + memOffset);

			for(i = 0; i < MassBlockSize[0]; i += SIZE_OF_U32)
			{
				while(FLASH_GetStatus() != FLASH_COMPLETE);
				FLASH_ProgramWord(FLASH_MSD_START_ADDR + memOffset + i, writeBuff[i / SIZE_OF_U32]);
			}

			LED_RGB_DisableOne(LED_RED);
			break;

		default:
			return MAL_FAIL;
	}

	return MAL_OK;
}

Если все сделано правильно, при подключении компьютер определит наше изделие, как USB Mass Storage Device и предложит его отформатировать, т.к. в области MSD_MEM лежит мусор. Стоит отметить, что в данном режиме работы МК является просто посредником между хостом и FLASH памятью, а операционная система самостоятельно решает, какие данные и по каким адресам будут лежать на нашем накопителе.

Отформатируем диск и посмотрим, как это отразилось на области MSD_MEM:

image

Объем совпадает, размер сектора Windows определила верный, нулевой сектор — загрузочный, расположение в памяти соответствует задуманному. Файлы пишутся, читаются, не исчезают после отключения питания — полноценная флешка на 200 Kbyte!

Bootloader


Запускается, если обновление прошивки не требуется. То есть, нормальный режим работы устройства. В нём нам предстоит совершить несколько базовых действий, необходимых для успешного запуска пользовательского ПО. Базовых — потому что при необходимости можно дополнять работу bootloader'а всякими фичами, такими как шифрование, проверка целостности, выводом отладочных сообщений и т.д.

Пусть мы уже создали средствами Windows файловую систему на USB накопителе и загрузили необходимое ПО. Теперь неплохо бы увидеть содержимое носителя «глазами» МК, а значит идем в гости к товарищу ChaN'у за FatFS (модуль простой файловой системы FAT, предназначенный для маленьких встраиваемых систем на микроконтроллерах). Скачиваем, закидываем в проект, прописываем функцию чтения с диска нужных данных:

DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
DRESULT disk_read (
	BYTE pdrv,	/* Physical drive nmuber to identify the drive */
	BYTE *buff,	/* Data buffer to store read data */
	DWORD sector,	/* Sector address in LBA */
	UINT count	/* Number of sectors to read */
)
{
	u32 i;

	for(i = 0; i < count * SECTOR_SIZE; i++)
	{
		buff[i] = *((volatile u8*)(FLASH_MSD_START_ADDR + sector * SECTOR_SIZE + i));
	}

	return RES_OK;
}

disk_write() не понадобится и оставлена заглушкой, ибо смонтированная файловая система — Read Only. Это так же можно задать в конфигурационном файле ffconf.h, дополнительно отключив все ненужные и неиспользуемые функции.

Дальше все более-менее очевидно: монтируем файловую систему, открываем файл прошивки, начинаем читать. Изначально было реализовано так, что основное место хранения прошивки — MSD_MEM, а микроконтроллер каждый раз при включении перезаписывает свою FLASH память. Нет прошивки — отладочное сообщение об отсутствии и while(TRUE). Есть прошивка — закидываем её в USER_MEM. Однако очевидный минус такого решения — ресурс стирания/записи FLASH памяти имеет лимит и было бы глупо постепенно и осознанно убивать изделие.

Поэтому сравним «APP.BIN» и USER_MEM, тупо, байт за байтом. Возможно, сравнение хеш-сумм двух массивов выглядело бы более изящным решением, но уж точно не самым быстрым. Заглянем снова в main():

int main(void)
int main(void)
{
	Button_Config();
	if(GPIO_ReadInputDataBit(BUTTON_PORT, BUTTON_PIN) == SET) //Bootloader or Mass Storage?
	{
		//USB MSD mode
	}
	
	FATFS_Status = f_mount(&FATFS_Obj, "0", 1);
	if(FATFS_Status == FR_OK)
	{
		FILE_Status = f_open(&appFile, "/APP.BIN", FA_READ);
		if(FILE_Status == FR_OK)
		{
			appSize = f_size(&appFile);

			for(i = 0; i < appSize; i++) //Byte-to-byte compare files in MSD_MEM and USER_MEM
			{
				f_read(&appFile, &appBuffer, 1, &readBytes);

				if(*((volatile u8*)(FLASH_USER_START_ADDR + i)) != appBuffer[0]) 
				{
					//if byte of USER_MEM != byte of MSD_MEM
					break;
				}
			}

			if(i != appSize)//=> was done "break" instruction in for(;;) cycle => new firmware in MSD_FLASH
			{
				CopyAppToUserMemory();
			}

			FILE_Status = f_close(&appFile);
			FATFS_Status = f_mount(NULL, "0", 1);

			PeriphDeInit();
			GoToUserApp();
		}
		else //if FILE_Status != FR_OK
		{
			if(FILE_Status == FR_NO_FILE)
			{
				//No file error
			}
			else //if FILE_Status != FR_NO_FILE
			{
				//Other error
			}
			FATFS_Status = f_mount(NULL, "0", 1);
			while(TRUE);
		}
	}
	else //FATFS_Status != FR_OK
	{
		//FatFS mount error
		while(TRUE);
	}
}

Если в процессе сравнения мы не дошли до конца цикла, значит прошивки различны и самое время обновить USER_MEM с помощью CopyAppToUserMemory(). Ну а потом неплохо бы уничтожить следы работы bootloader'а вызовом PeriphDeInit() и затем GoToUserApp(). Но это чуть позже, а пока — процесс копирования:

void CopyAppToUserMemory(void)
void CopyAppToUserMemory(void)
{
	f_lseek(&appFile, 0); //Go to the fist position of file

	appTailSize = appSize % APP_BLOCK_TRANSFER_SIZE;
	appBodySize = appSize - appTailSize;
	appAddrPointer = 0;

	for(i = 0; i < ((appSize / FLASH_PAGE_SIZE) + 1); i++) //Erase n + 1 pages for new application
	{
		while(FLASH_GetStatus() != FLASH_COMPLETE);
		FLASH_ErasePage(FLASH_USER_START_ADDR + i * FLASH_PAGE_SIZE);
	}

	for(i = 0; i < appBodySize; i += APP_BLOCK_TRANSFER_SIZE)
	{
		/*
		 * For example, size of File1 = 1030 bytes
		 * File1 = 2 * 512 bytes + 6 bytes
		 * "body" = 2 * 512, "tail" = 6
		 * Let's write "body" and "tail" to MCU FLASH byte after byte with 512-byte blocks
		 */
		f_read(&appFile, appBuffer, APP_BLOCK_TRANSFER_SIZE, &readBytes); //Read 512 byte from file
		for(j = 0; j < APP_BLOCK_TRANSFER_SIZE; j += SIZE_OF_U32) //write 512 byte to FLASH
		{
			while(FLASH_GetStatus() != FLASH_COMPLETE);
			FLASH_ProgramWord(FLASH_USER_START_ADDR + i + j, *((volatile u32*)(appBuffer + j)));
		}
		appAddrPointer += APP_BLOCK_TRANSFER_SIZE; //pointer to current position in FLASH for write
	}

	f_read(&appFile, appBuffer, appTailSize, &readBytes); //Read "tail" that < 512 bytes from file

	while((appTailSize % SIZE_OF_U32) != 0)		//if appTailSize MOD 4 != 0 (seems not possible, but still...)
	{
		appTailSize++;				//increase the tail to a multiple of 4
		appBuffer[appTailSize - 1] = 0xFF;	//and put 0xFF in this tail place
	}

	for(i = 0; i < appTailSize; i += SIZE_OF_U32) //write "tail" to FLASH
	{
		while(FLASH_GetStatus() != FLASH_COMPLETE);
		FLASH_ProgramWord(FLASH_USER_START_ADDR + appAddrPointer + i, *((volatile u32*)(appBuffer + i))); 
	}
}

Копировать будем блоками по 512 байт. 512 — потому что я где-то видел, что при размере буфера больше этого значения f_read() может косячить. Я проверял этот момент — у меня все работало и с буфером большего размера. Но на всякий случай оставил 512 — почему бы и нет? Экономим RAM, да и на скорость не влияет, к тому же выполняется один раз — в момент включения устройства и лишь при условии, что прошивку пора обновлять.

Предварительно стираем во FLASH памяти местечко под файл. Размер стираемой области равен количеству страниц в памяти, которые полностью займет «APP.BIN» + еще одна (которую не полностью). А еще, виртуально бьем файл прошивки на «body» и «tail», где «body» — максимально возможный кусок файла, в который входит целое количество блоков по 512 байт, а «tail» — все остальное.

Кажется, что все бинарные файлы прошивок кратны 4-м байтам. Я не был в этом уверен точно (и до сих пор), так что на всякий случай — если прошивка не кратна sizeof(u32) — дополняем её байтами 0xFF. Повторюсь: кажется, что этого не нужно делать — но операция безобидна для кратных sizeof(u32) бинарников, так что оставим.

Hello, User Application!


Уже близко. Деинициализируем всю использованную периферию функцией PeriphDeInit() (а ее вообще почти ничего — GPIO для кнопки выбора режима и при желании UART для вывода отладочных сообщений; никаких прерываний не используется).

Заключительный жизненный этап загрузчика — начало исполнения пользовательской прошивки:

void GoToUserApp(void)
void GoToUserApp(void)
{
	u32 appJumpAddress;
	void (*GoToApp)(void);

	appJumpAddress = *((volatile u32*)(FLASH_USER_START_ADDR + 4));
	GoToApp = (void (*)(void))appJumpAddress;
	SCB->VTOR = FLASH_USER_START_ADDR;
	__set_MSP(*((volatile u32*) FLASH_USER_START_ADDR)); //stack pointer (to RAM) for USER app in this address
	GoToApp();
}

Всего-то 5 строк, но сколько всего происходит!

В ядре ARM Cortex M3, когда возникает какое-либо исключение, для него вызывается соответствующий обработчик. Что бы определить начальный адрес обработчика исключений, используется механизм векторной таблицы. Таблица векторов представляет собой массив слов данных внутри системной памяти, каждое из которых является начальным адресом одного типа исключений. Таблица перемещаема и перемещение управляется специальным регистром VTOR в SCB (System Control Block)(В мануале звучит круче, но я сломался: The vector table is relocatable, and the relocation is controlled by a relocation register in the NVIC). После RESET'а значение этого регистра равно 0, то есть таблица векторов лежит по адресу 0x0 (для STM32F103 в стартап файле мы уже самостоятельно двигаем её на 0x08000000). И что очень важно для нас, порядок следования там следующий:

image

  • Значение, лежащее по адресу 0x04 — это то место в программе, куда мы попадаем после Reset-исключения
  • Значение, лежащее по адресу 0x00 — это начальное значение Main Stack Pointer для пользовательского приложения

Все это вместе взятое, плюс немного магии с указателем на функцию, и Алиса прыгает вслед за кроликом.

Теперь проверим, работает ли оно вообще. Напишем простую программу мигания светодиодов, с циклами в main() и парочкой прерываний (SysTick и TIM4):

Test programm for MSD bootloader
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_tim.h"
#include "misc.h"

#define SYSCLK_FREQ	72000000
#define TICK_1_KHz	((SYSCLK_FREQ / 1000) - 1)
#define TICK_1_MHz	((SYSCLK_FREQ / 1000000) - 1)

volatile u32 i, j;

int main(void)
{
	GPIO_InitTypeDef GPIO_Options;
	NVIC_InitTypeDef NVIC_Options;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

	GPIO_Options.GPIO_Pin = GPIO_Pin_7;
	GPIO_Options.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Options.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOA, &GPIO_Options);

	GPIO_Options.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
	GPIO_Options.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Options.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOB, &GPIO_Options);

	GPIOB->BSRR = GPIO_Pin_0 | GPIO_Pin_1; //LEDs off
	GPIOA->BSRR = GPIO_Pin_7

	TIM4->PSC = 720 - 1;		//clock prescaller
	TIM4->ARR = 60000 - 1;		//auto-reload value
	TIM4->CR1 |= TIM_CounterMode_Up;//upcounter
	TIM4->DIER |= TIM_IT_Update;	//update interrupt enable
	TIM4->CR1 |= TIM_CR1_CEN;	//timer start

	NVIC_Options.NVIC_IRQChannel = TIM4_IRQn;
	NVIC_Options.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_Options.NVIC_IRQChannelSubPriority = 0;
	NVIC_Options.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_Options);

	SysTick_Config(TICK_1_KHz);

	while(1)
	{
		__disable_irq();

		GPIOB->BSRR = GPIO_Pin_0 | GPIO_Pin_1; //Off
		for(i = 0; i < 10; i++)
		{
			for(j = 0; j < 500000; j++); //Pause
			GPIOA->ODR ^= GPIO_Pin_7; //Reverse
		}
		GPIOA->BSRR = GPIO_Pin_7; //Off

		__enable_irq();

		for(i = 0; i < 5000000; i++); //Pause
	}
}

void SysTick_Handler(void)
{
	volatile static u32 LED_Counter = 0;

	if(LED_Counter >= 40)
	{
		GPIOB->ODR ^= GPIO_Pin_1; //Reverse
		LED_Counter = 0;
	}

	LED_Counter++;
}

void TIM4_IRQHandler()
{
	TIM4->SR = ~TIM_SR_UIF;
	GPIOB->ODR ^= GPIO_Pin_0; //Reverse
}

Кстати, надо не забыть исправить в проекте пару вещей, без которых ничего работать не будет:

  1. Убрать из SystemInit() операцию перемещения таблицы векторов на какое либо значение (//SCB->VTOR = FLASH_BASE). Bootloader перемещает ее самостоятельно перед переходом в пользовательскую программу!

  2. В Linker script поменять начало нашей программы с адреса 0x08000000 на адрес начала USER_MEM (FLASH (rx): ORIGIN = 0x08010000, LENGTH = 200K);

И вот так этот код исполняется (ну, может не все видели, как моргают светодиоды...):



А вот так выглядит лог загрузки этой прошивки в МК через бутлоадер:

UART log message
---------------START LOG---------------

BOOT_MEM start addr: 0x08000000
BOOT_MEM size: 64K
USER_MEM start addr: 0x08010000
USER_MEM size: 200K
MSD_MEM start addr: 0x08042000
MSD_MEM size: 200K
OTHER_MEM start addr: 0x08074000
OTHER_MEM size: 48K

Total memory size: 512K

BOOTLOADER Mode…
FAT FS mount status = 0
Application file open status = 0
Difference between MSD_MEM and USER_MEM: 4 byte from 2212 byte
Start copy MSD_MEM to USER_MEM:

File size = 2212 byte
Body size = 2048 byte
Tail size = 164 byte

Sector 0 (0x08010000 — 0x08010800) erased
Sector 1 (0x08010800 — 0x08011000) erased

0 cycle, read status = 0, 512 byte read
512 byte programmed: 0x08010000 — 0x08010200
1 cycle, read status = 0, 512 byte read
512 byte programmed: 0x08010200 — 0x08010400
2 cycle, read status = 0, 512 byte read
512 byte programmed: 0x08010400 — 0x08010600
3 cycle, read status = 0, 512 byte read
512 byte programmed: 0x08010600 — 0x08010800
Tail read: read status = 0, 164 byte read, size of tail = 164
New size of tail = 164
164 byte programmed: 0x08010800 — 0x080108A4

File close status = 0
FAT FS unmount status = 0
DeInit peripheral and jump to 0x08010561…

Подведём итоги. Бутлоадер получился! И даже работает. С выводом отладочных сообщений в UART он занимает 31684 байт FLASH памяти, без — 25608 байт. Не так уж и мало, если еще и учесть, сколько памяти нужно отдать под Mass Storage диск. Исходники и рабочий проект (Atollic TrueSTUDIO) можно посмотреть на Bitbucket.

Спасибо за внимание!
Поделиться с друзьями
-->

Комментарии (29)


  1. telhin
    05.09.2016 14:09

    Блок схема у вас очень красивая, можно поинтересоваться в чем приготовлена?


    1. Katbert
      05.09.2016 14:10

      Спасибо.
      https://www.lucidchart.com/


  1. igor_suhorukov
    05.09.2016 14:19

    Спасибо за статью. Как вариант — DFU bootloader из проекта Maple для stm32.
    Но usb mass storage конечно удобнее!


    1. Katbert
      05.09.2016 18:44

      И вам спасибо!
      Кажется, DFU bootloader требует некоторую внешнюю программу на стороне ПК (я не использовал никогда DFU)?
      Специально хотелось уйти от этого.


      1. igor_suhorukov
        05.09.2016 21:54

        Все верно. Отдельный протокол, отдельная утилита.


  1. x893
    05.09.2016 14:31

    Мне удобнее было не отводить память для MSD, а сделать её виртуальной. Со стороны хоста диск отформатирован и содержит файл APP.BIN. Если его считать — это содержимое флэша. Я просто копирую файл APP.bin (до 496K) и образ сразу записывается во флэш.
    То есть размер APP.bin = MCU Flash Size — BOOTLOADER Size (16K у меня)


    1. kekekeks
      05.09.2016 15:53

      Вот тут не факт, что ОС будет писать в те же самые сектора, а не попытается пересоздать файл. Тогда уж надо реализацию MTP делать.


      1. x893
        05.09.2016 16:05

        Мне за MS сложно отвечать, но работает.
        Примерно так же работает во всех дебаггерах с поддержкой MSD с одной стороны и SWD/SWC с другой стороны (NUCLEO и ещё 100500).


      1. Konachan700
        05.09.2016 19:10

        Файл точно размером с флеш (без загрузчика), на нее и отображается. Больше места на диске нет ни байта. Если кинуть меньший файл, он запишется, но при рефреше опять станет стандартного размера. Видел такое.
        То, что описано в статье, с первого взгляда пахнет избыточностью, но только с первого. На самом деле для обновления прошивки пользователем это идеальный вариант, поскольку можно проверить подпись\заголовок прошивки и точно узнать, что вместо прошивки туда не положили по ошибке картинку или документ. В случае виртуальной ФС с прямым отображением файла на флеш такую проверку сделать затруднительно, если возможно вообще.


        1. Katbert
          05.09.2016 19:21

          Описанное в статье и правда избыточно, я так изначально и задумывал — не вдаваться в оптимизацию алгоритма работы прежде, чем все не будет работать. Лично я не планирую использование бута в том виде, в котором он описан — это скорее учебный вариант. В дальнейшем я просто буду его модифицировать отдельно под очередной проект. Где флешка будет внешняя, где — принимающий интерфейс не ЮСБ, и т.д.


    1. Katbert
      05.09.2016 19:04

      Вы имеете ввиду, что нужно отдать USB_MSD всю флешку, кроме той, где хранится бутлоадер? А потом при перезагрузке прыгать по тому адресу, где лежит файл APP.BIN (ОС его туда положила — и этот адрес не сложно вычислить без монтирования файловой системы, а вручную, по таблице FAT)?
      Тут проблема в том, что я не могу предугадать действия ОС. Вдруг она разобьет файл на куски, и разбросает их по диску. Это вполне возможно, если на MSD диске лежат какие-то другие файлы (например, несколько разных прошивок, или файлы настройки чего-то внутри МК). Поэтому я и пользуюсь FatFS — что бы легко получить доступ к файлу невзирая на то, что ОС с ним творила.


  1. saw_tooth
    05.09.2016 18:29

    а если предполагается что устройство уже является win-совместимым и сразу определяется системой, как тогда можно использовать Ваш бут лоадер?


    1. Katbert
      05.09.2016 18:54

      Его, как минимум, можно использовать для обучения :-)
      Что значит " устройство уже является win-совместимым"? То есть у него уже есть какой-то USB интерфейс (и он настоящий, не usb-uart конвертор)? Тогда можно прошить этот бут на ваше устройство и потом через него обновлять вашу прошивку. Нужна только кнопка, которая при включении устройства выбирает — грузится в режиме бута или же в режиме исполнения вашей программы.

      Я пробовал через этот бутлоадер заливать приложения, использующие USB (CDC или MSD). Удачно все.


      1. saw_tooth
        05.09.2016 19:02

        В моем случае есть MIDI-USB устройтсво, на STM32, так же задумывался о бутлоадере, в виде MS, но руки не дошли. И вот интересует как использовать бутлоадер в данном случае. Копки «Update» не придвидится… вы не думали реализовать бутлоадер в виде композит устройтва, в возможностью выбора режима при подключении устроятва к USB?
        PS. Насколько я понимаю, такое устройство должно поддерживать две конфигурации, одна из которых — как раз и будет ваш кастомный бутлоадер для перепрошивки.


  1. Katbert
    05.09.2016 19:07

    Ну хоть какая-нибудь кнопка будет в вашем устройстве? Этот бут можно завести на вообще любую кнопку.
    Нет, в виде композита не думал, т.к. у всего, для чего я буду использовать этот бут — есть хотя бы одна кнопка.


  1. softmart
    05.09.2016 20:50

    Где-то на electronix.ru рекомендуют переход на основную прошивку выполнять после полного ресета, иначе возможны фокусы. Ну а определить что грузить — по флагу во флеш или в BkpSram.


    1. Katbert
      05.09.2016 20:52

      Да, я тоже так изначально думал делать. Но не стал — забыл, почему. Вспомню — напишу.


  1. Dark_Purple
    05.09.2016 21:19

    Дороги под часовым кварцем — плохая идея.


  1. Vooon
    05.09.2016 21:28
    +1

    Я не так давно написал бут для STM32F373, когда оказалось, что вызвать встроенный DFU-бут на F3 невозможно (баг в буте).
    На F4 семействе кстати работает без проблем.

    У меня бут прошивает с SD-карты, но на будущее — это не удобно.
    Бут занял 27 КиБ, это Nil + HAL + FatFS + tinf (zlib inflate) + RIOT CBOR + SHA1.


  1. Katbert
    05.09.2016 21:30

    Почему неудобно?


  1. softmart
    05.09.2016 22:05

    Красивая плата. А что за TVS'ы между USB разъемами? Можно где то увидеть всю схему/плату?


    1. Katbert
      05.09.2016 22:54

      Это шоттки по питанию, они есть на схеме в публикации.
      Большая часть платы приведена тут, кстати. Остальное — карта памяти uSD, радиомодуль nRF24L01+, аналоговый датчик света, EEPROM память и сокеты для дочерних плат


      1. softmart
        06.09.2016 18:52

        А что за L4, L5 и для чего они?



  1. x893
    06.09.2016 07:41

    Судя по схеме нельзя запаять ничего кроме F10x.
    Жаль. Если бы было расчитано на F20x/F40x, я бы даже спросил где купить.


  1. Strannii
    06.09.2016 10:08

    Очень аккуратная плата у Вас получилась! Правда, что-то мне подсказывает, что можно было обойтись и двумя слоями)

    Кстати, форм-фактор — это кредитная карта?
    И что это за интересные крепежные винты такие?


    1. Katbert
      06.09.2016 10:13

      Спасибо.
      Обойтись было можно, но не было смысла — я просто подкидываю платы для хобби к другим заказам с более сложными платами.
      Форм фактор свой, не кредитный, просто так вышло.
      Это очень клевые стойки от Wurth Electronics


      1. Strannii
        06.09.2016 10:57

        Спасибо, стойки возьму на вооружение!

        Еще вопросик — вскрытие маски на антенне хоть как-то влияет на ее характеристики?


        1. Katbert
          06.09.2016 23:10

          Не знаю. Думаю, что не очень сильно, но не готов это грамотно обосновать.