下位机开发就裸机编程而言其实就是基于MCU做固件或者说驱动开发,那目前作者接触到的主流的MCU型号国外的就是STM32,国内的就是GD32,至于其它家一般都有其特定的应用领域或者细分市场战略。就本项目的i2c bootloader开发而言,这里的下位机开发要实现的功能主要就包括两点,一是要实现串口转I2C驱动,二是要实现MCU内部Flash读写,具体来讲就是基于STM32F103来实现USB串口转I2C通信,基于GD32E230来实现能够擦写Flash的bootloader,bootloader代码主要的功能就是接收i2c master的指令信息,然后再将指令信息擦写到GD32E230的Main Flash位置区域。这里的通信链路说明一下:笔记本电脑 -> USB转I2C master设备 -> I2C slave设备,就本i2c bootloader项目开发而言,slave设备是GD32E232或者GD32E230并无区别。
传统的STM32开发方式,这里用到STM32CubeMX自动生成代码工程,需要配置的外设是USB和I2C,当然了你想直观调试查看,也可以配置USART串口打印,到时候用USB转USART设备直接连接到电脑看打印结果。需要注意这里的I2C外设要有自己的地址,默认是0,那作者这里手动设置hi2c1.Init.OwnAddress1 = 113;当然其它值也是没问题的。usbd_cdc_if.c是USB串口驱动代码,这段代码能够保证USB接收超过64字节的数据,实际上就是采用了分包处理的策略。main.c是USB转I2C驱动代码,这段代码会将USB串口接收到的上位机指令/数据转由I2C进行传输。另外需要说明的是,因为本工程采用arm-none-eabi-gcc进行编译,所以如果要实现串口重定向打印输出,就要重新实现 *int _write(int fd, char *ptr, int len) *。 V0.1版本的源码作者会放置到CSDN附件供参考使用。
/* USER CODE BEGIN PRIVATE_VARIABLES */
// uint8_t rxBuff[256+16];
#define Max_Pack_Size (64)
#define rxBuff_Len (256)
extern uint8_t rxBuff[rxBuff_Len];
uint8_t Num_Packet;
uint32_t SinglePackLength;
uint32_t Num_Rx_Data;
uint32_t Wait_RX_Dly;
uint32_t Num_Out_Pack;
/* USER CODE END PRIVATE_VARIABLES */
/**
* @brief Data received over USB OUT endpoint are sent over CDC interface
* through this function.
*
* @note
* This function will issue a NAK packet on any OUT packet received on
* USB endpoint until exiting this function. If you exit this function
* before transfer is complete on CDC interface (ie. using DMA controller)
* it will result in receiving more data while previous ones are still
* not sent.
*
* @param Buf: Buffer of data to be received
* @param Len: Number of data received (in bytes)
* @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 6 */
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
if (Num_Packet == 0)
{
memset(rxBuff, '\0', rxBuff_Len);
}
SinglePackLength = (uint32_t)*Len;
if (SinglePackLength == Max_Pack_Size) {
memcpy(((uint8_t *)&rxBuff[0]+(Max_Pack_Size*Num_Packet)), Buf, Max_Pack_Size);
Num_Rx_Data = (Max_Pack_Size * Num_Packet) + SinglePackLength;
Num_Out_Pack = ++Num_Packet;
Wait_RX_Dly = 5;
}
else
{
memcpy(((uint8_t *)&rxBuff[0]+(Max_Pack_Size*Num_Packet)), Buf, SinglePackLength);
Num_Rx_Data = (Max_Pack_Size * Num_Packet) + SinglePackLength;
Num_Out_Pack = ++Num_Packet;
Wait_RX_Dly = 0x00;
Num_Packet = 0;
memset(Buf, '\0', Max_Pack_Size);
}
return (USBD_OK);
/* USER CODE END 6 */
}
/* USER CODE BEGIN 0 */
// int ret;
uint8_t *p_rev;
uint8_t pData[command_length] = {0};
uint8_t pData1[command_length] = {0};
#ifdef __GNUC__
int _write(int fd, char *ptr, int len)
{
HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, 0xFFFF);
return len;
}
int _read(int fd, char *ptr, int len)
{
HAL_UART_Receive(&huart1, (uint8_t*)ptr, len, 0xFFFF);
return *ptr;
}
#else
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xFFFF);
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 0xFFFF);
return ch;
}
#endif
extern uint32_t Num_Rx_Data;
uint8_t txBuff[256*8+5]; //1024 -> 256
// extern uint8_t rxBuff[256];
uint8_t rxBuff[256];
int txlen=0;
enum CMDenum{
CMD_FAIL=0 , //0 F Bytes
CMD_OK , //1 O Bytes
CMD_I2C_Write, //2 W Byte I2CAddr ...
CMD_I2C_Read , //3 R 1 Byte.
CMD_WriteRead, //4 r bytes I2C address R 1 bytes.
CMD_WriteNoStop, //5 w byte I2C address... without Stop.
CMD_I2C_Read_Muti=31, //31 return Los
CMD_TEST = 250 //(0xFA)
};
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
HAL_StatusTypeDef i = HAL_OK;
uint8_t *p;
int ret=0;
int rxBuffLen;
// int miti_lens = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM3_Init();
MX_USART1_UART_Init();
MX_USB_DEVICE_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_TIM1_Init();
MX_SPI2_Init();
MX_I2C1_Init();
/* Initialize interrupts */
MX_NVIC_Init();
/* USER CODE BEGIN 2 */
rxBuffLen=0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
printf("Master!\n");
i = HAL_I2C_Slave_Receive(&hi2c1, pData1, command_length, 3000);
if (i == HAL_OK) {
p_rev = pData1 + 1;
}
ret=HAL_I2C_Master_Transmit(&hi2c1, Add, pData, sizeof(pData)/sizeof(pData[0]), 1000);
HAL_Delay(100);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
txlen=1;
rxBuffLen = Num_Rx_Data;
if(rxBuffLen)
{
p=rxBuff+1; rxBuffLen--;
while(rxBuffLen>=2 && p[1]<=rxBuffLen-2) //p[1]=len
{
switch(*p) //p[0]=rxBuff[1]
{
case CMD_I2C_Write: //0x02, W Byte I2CAddr ...
//send
for(int j = 1; j <= command_length; j++) {
pData[j-1] = p[j];
}
//receive
if (p_rev[0]) {
ret = HAL_OK;
txBuff[txlen++]= (ret==HAL_OK)?CMD_OK:CMD_FAIL;
txBuff[txlen++]=0;
}
else {
ret = HAL_ERROR;
txBuff[txlen++]= (ret==HAL_ERROR)?CMD_FAIL:CMD_OK;
txBuff[txlen++]=0;
}
break;
case CMD_I2C_Read: //0x03, R 2 I2CAddr Bytes...
for(int j = 1; j <= command_length; j++) {
pData[j-1] = p[j];
// txBuff[txlen++] = p_rev[j-1];
txBuff[txlen++] = pData1[j-1];
}
break;
default:
// txBuff[txlen++]= CMD_FAIL;
// txBuff[txlen++]=0;
rxBuffLen=2;
p[1]=0;
break;
}
rxBuffLen-=p[1]+2;
if(rxBuffLen<2) rxBuffLen=0;
p+=p[1]+2; //update the p[0]
}
// USB_Reset_RXSize();
if(txlen>1)
{
txBuff[0]=txlen-1;
while(CDC_Transmit_FS(txBuff, txlen) != USBD_OK)
Delay_us(100);
}
}
}
/* USER CODE END 3 */
}
须知bootloader源码和APP源码一样都是一个独立的固件。所以在设计bootloader时候就像写普通固件代码一样就可以了。但在写bootloader之前要查看一下自己所用的MCU的flash空间大小,有的MCU的flash过小,而待烧录的APP代码又过于复杂,导致能够被bootloader利用的空间被极端压缩,这时候就要自己权衡bootloader存在的必要性了。这里作者基于GD32提供的标准库来开发。也是定义了STM32F103C8T6的I2C设备地址。 #define MB_ADDRESS 0x71。对于V0.1版本的bootloader源码来讲目前主要实现了以下几个必要的功能。
1.FMC擦除;
2.FMC编程;
3.FMC任意指定FLASH内容读取;
4.跳转到APP;
这里需要强调一点,要想成功对FMC进行编程,FMC擦除操作一定要先执行,这是因为FLASH在编程时只允许由1到0,而不允许由0到1。 V0.1版本的源码作者会放置到CSDN附件供参考使用。
#include "cflash.h"
uint32_t *ptrd=NULL;
uint32_t address = 0x00000000;
uint32_t data0 = 0xb14bb510;
uint32_t page_num = (FMC_WRITE_END_ADDR - FMC_WRITE_START_ADDR) / FMC_PAGE_SIZE;
uint32_t word_num = ((FMC_WRITE_END_ADDR - FMC_WRITE_START_ADDR) >> 2);
void fmc_erase_pages_check(void)
{
uint32_t i;
ptrd = (uint32_t *)FMC_WRITE_START_ADDR;
for (i = 0; i < word_num; i++) {
if (0xFFFFFFFF != (*ptrd)) {
blue_led_off();
yellow_led_on();
break;
}
else {
blue_led_on();
yellow_led_off();
ptrd++;
}
}
}
/**
* @brief
*
* @param page_Numebr the data from host
* @param number_of_pages the data from host
*/
static void fmc_erase_pages_memory(uint8_t page_Numebr, uint8_t number_of_pages)
{
uint32_t erase_counter;
fmc_unlock();
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_PGERR | FMC_FLAG_WPERR);
for (erase_counter = 0; erase_counter < number_of_pages; erase_counter++)
{
fmc_page_erase(((page_Numebr * FMC_PAGE_SIZE + FMC_ERASE_ADDR) + (FMC_PAGE_SIZE * erase_counter)));
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_PGERR | FMC_FLAG_WPERR);
}
fmc_lock();
}
void fmc_program_check(void)
{
uint32_t i;
ptrd = (uint32_t *)FMC_WRITE_START_ADDR1;
for (i = 0; i < word_num; i++) {
if (*ptrd != data0) {
blue_led_on();
yellow_led_off();
break;
}
else
{
blue_led_off();
yellow_led_on();
// ptrd++;
}
}
}
/**
* @brief
*
* @param host_payload the data is from host
* @param start_addr the data is from host
* @param payload_len the data is from host
*/
static void fmc_program_memory(uint8_t *host_payload, uint32_t start_addr, uint8_t payload_len)
{
fmc_unlock();
uint8_t word_len = payload_len / sizeof(uint32_t);
uint32_t data[32] = {0};
for (uint8_t i = 0; i < word_len; i++)
{
data[i] = (host_payload[3 + 4 * i] << 24)+
(host_payload[2 + 4 * i] << 16)+
(host_payload[1 + 4 * i] << 8)+
(host_payload[0 + 4 * i]);
fmc_word_program(start_addr + 4 * i, data[i]);
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_PGERR | FMC_FLAG_WPERR);
}
fmc_lock();
}
void Bootloader_Erase_Flash(uint8_t *Host_Buffer)
{
// Perform an erase from Flash memory
fmc_erase_pages_memory(Host_Buffer[2], Host_Buffer[3]);
fmc_erase_pages_check();
}
void Bootloader_Memory_Write(uint8_t *Host_Buffer)
{
uint8_t Payload_Len = 0;
uint32_t Host_Addr = 0;
Host_Addr = *((uint32_t *)&Host_Buffer[2]);
Payload_Len = Host_Buffer[6];
fmc_program_memory((uint8_t *)&Host_Buffer[7], Host_Addr, Payload_Len);
fmc_program_check();
}
uint32_t Bootloader_Memory_Read(uint8_t *Host_Buffer)
{
uint8_t mem_val[4] = {0};
uint32_t addr;
uint32_t Host_Addr = 0;
addr = (Host_Buffer[5] << 24) +
(Host_Buffer[4] << 16) +
(Host_Buffer[3] << 8 ) +
Host_Buffer[2];
Host_Addr = *((uint32_t *)addr); //read memory
// Host_Addr = *((uint32_t *)&Host_Buffer[2]); //read memory
mem_val[0] = (Host_Addr & 0x000000FF);
mem_val[1] = (Host_Addr & 0x0000FF00) >> 8;
mem_val[2] = (Host_Addr & 0x00FF0000) >> 16;
mem_val[3] = (Host_Addr & 0xFF000000) >> 24;
blue_led_on();
return ((mem_val[3] << 24) + (mem_val[2] << 16) + (mem_val[1] << 8) + mem_val[0]);
}
typedef void (*pFunction)(void);
void Bootloader_JumpToApplication(void)
{
uint32_t JumpAddress = *(__IO uint32_t*)(APP_ADDRESS + 4);
pFunction Jump = (pFunction)JumpAddress;
if (0x20000000 == ((*(__IO uint32_t*)APP_ADDRESS) & 0x2FFE0000)) {
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
timer_disable(LED_BLINK_TIMER);
__disable_irq();
__DSB();
__ISB();
nvic_vector_table_set(NVIC_VECTTAB_FLASH, 0xDC00);
__set_MSP(*(__IO uint32_t*)APP_ADDRESS);
__DSB();
__ISB();
Jump();
}
else {
yellow_led_on();
blue_led_on();
}
}
#include "main.h"
#define command_length (16 + 128 + 4)
uint8_t i2c_receiver[command_length] = {0};
uint8_t i2c_transmitter[command_length] = {0};
uint8_t *p;
int ret;
uint32_t mem_value;
/*!
\brief main function
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
int i;
/* configure systick */
systick_config();
/* configure USART */
usart_config();
/* configure RCU */
rcu_config();
/* configure GPIO */
gpio_i2c0_config();
/* configure I2C */
i2c0_config();
while(1) {
// printf("Slave\n");
i = 0;
memset(i2c_transmitter, 0x00, sizeof(i2c_transmitter)/sizeof(i2c_transmitter[0]));
/* wait until ADDSEND bit is set */
while(!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND));
/* clear ADDSEND bit */
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
for(i = 0; i < command_length; i++) {
/* wait until the RBNE bit is set */
while(!i2c_flag_get(I2C0, I2C_FLAG_RBNE));
/* read a data byte from I2C_DATA */
i2c_receiver[i] = i2c_data_receive(I2C0);
}
/* wait until the STPDET bit is set */
while(!i2c_flag_get(I2C0, I2C_FLAG_STPDET));
/* clear the STPDET bit */
i2c_enable(I2C0);
// for(i = 0; i < 148; i++) {
// printf("0x%02X ", i2c_receiver[i]);
// }
if (i2c_receiver[0]) {
p = i2c_receiver + 1;
switch (*p) {
case CMD_I2C_Write:
ret = qsfp28lr4_i2c1_write(p + 4, *(p + 1), (p[2] << 8) + p[3]);
i2c_transmitter[1] = (ret==SET)?CMD_OK:CMD_FAIL;
break;
case CMD_I2C_Read:
qsfp28lr4_i2c1_read(p + 4, *(p + 1), (p[2] << 8) + p[3]);
for (i=0; i<((p[2] << 8) + p[3]); i++) {
i2c_transmitter[i] = p[4+i];
}
break;
case CMD_FMC_ERASE:
Bootloader_Erase_Flash(p+1);
i2c_transmitter[1] = CMD_OK;
break;
case CMD_FMC_PROGRAM:
Bootloader_Memory_Write(p+1);
i2c_transmitter[1] = CMD_OK;
break;
case CMD_FMC_READMEM:
mem_value = Bootloader_Memory_Read(p+1);
printf("%02X, %02X, %02X, %02X\n", *(p+3), *(p+4), *(p+5), *(p+6));
i2c_transmitter[0] = (mem_value & 0xFF);
i2c_transmitter[1] = (mem_value & 0xFF00) >> 8;
i2c_transmitter[2] = (mem_value & 0xFF0000) >> 16;
i2c_transmitter[3] = (mem_value & 0xFF000000) >> 24;
break;
case CMD_FMC_JUMPTOAPP:
Bootloader_JumpToApplication();
break;
case CMD_I2C_AT24C02:
ret = i2c_24c02_test();
i2c_transmitter[1] = (ret==SET)?CMD_OK:CMD_FAIL;
led_on();
break;
default:
p[0] = 0;
// led_on();
break;
}
/* time delay */
delay_ms(500);
/* wait until I2C bus is idle */
while(i2c_flag_get(I2C0, I2C_FLAG_I2CBSY));
/* send a start condition to I2C bus */
i2c_start_on_bus(I2C0);
/* wait until SBSEND bit is set */
while(!i2c_flag_get(I2C0, I2C_FLAG_SBSEND));
/* send slave address to I2C bus*/
i2c_master_addressing(I2C0, MB_ADDRESS, I2C_TRANSMITTER);
/* wait until ADDSEND bit is set*/
while(!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND));
/* clear ADDSEND bit */
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
/* wait until the transmission data register is empty */
while(!i2c_flag_get(I2C0, I2C_FLAG_TBE));
for(i = 0; i < command_length; i++) {
/* send a data byte */
i2c_data_transmit(I2C0, i2c_transmitter[i]);
/* wait until the transmission data register is empty*/
while(!i2c_flag_get(I2C0, I2C_FLAG_TBE));
}
/* send a stop condition to I2C bus*/
i2c_stop_on_bus(I2C0);
/* wait until stop condition generate */
while(I2C_CTL0(I2C0) & I2C_CTL0_STOP);
}
}
}
我们开发i2c bootloader的目的就是为了能够通过IAP的方式烧写APP。所以,准备一份能够烧写的app代码是必须的。这里的app针对待烧录设备GD32E230;所以app要是基于GD32E230开发的。需要注意的是APP在FLASH中的地址分配。本项目中我们根据bootloader代码存储空间需求(addr: 0x08000000 - 0x0800DBFF)和实际 Main FLASH(64 pages, 1page=1K)大小,在编译app固件时,要求在该app固件中的.ld链接文件中将MEMORY中的FLASH按此处样式修改FLASH (rx) : ORIGIN = 0x0800DC00, LENGTH = 9K,重新编译固件,生成.bin文件(此处为gd32e23x.bin)。在下一篇上位机开发篇中,我们需要重点处理这个.bin二进制文件,采用脚本方式将.bin文件顺利烧写到GD32E230的FLASH地址为0x0800DC00- 0x0800FFFF位置区域。 V0.1版本的源码作者会放置到CSDN附件供参考使用
/* memory map */
MEMORY
{
FLASH (rx) : ORIGIN = 0x0800DC00, LENGTH = 9K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 8K
}
ENTRY(Reset_Handler)
SECTIONS
{
__stack_size = DEFINED(__stack_size) ? __stack_size : 2K;
/* ISR vectors */
.vectors :
{
. = ALIGN(4);
KEEP(*(.vectors))
. = ALIGN(4);
__Vectors_End = .;
__Vectors_Size = __Vectors_End - __gVectors;
} >FLASH
.text :
{
. = ALIGN(4);
*(.text)
*(.text*)
*(.glue_7)
*(.glue_7t)
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
/* the symbol '_etext' will be defined at the end of code section */
_etext = .;
} >FLASH
.rodata :
{
. = ALIGN(4);
*(.rodata)
*(.rodata*)
. = ALIGN(4);
} >FLASH
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} >FLASH
.ARM : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
.ARM.attributes : { *(.ARM.attributes) } > FLASH
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(.fini_array*))
KEEP (*(SORT(.fini_array.*)))
PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH
/* provide some necessary symbols for startup file to initialize data */
_sidata = LOADADDR(.data);
.data :
{
. = ALIGN(4);
/* the symbol '_sdata' will be defined at the data section end start */
_sdata = .;
*(.data)
*(.data*)
. = ALIGN(4);
/* the symbol '_edata' will be defined at the data section end */
_edata = .;
} >RAM AT> FLASH
. = ALIGN(4);
.bss :
{
/* the symbol '_sbss' will be defined at the bss section start */
_sbss = .;
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
/* the symbol '_ebss' will be defined at the bss section end */
_ebss = .;
__bss_end__ = _ebss;
} >RAM
. = ALIGN(8);
PROVIDE ( end = _ebss );
PROVIDE ( _end = _ebss );
.stack ORIGIN(RAM) + LENGTH(RAM) - __stack_size :
{
PROVIDE( _heap_end = . );
. = __stack_size;
PROVIDE( _sp = . );
} >RAM AT>RAM
}
/* input sections */
GROUP(libgcc.a libc.a libm.a libnosys.a)