目前我們的鏡像:boot程序,設備樹文件和內核鏡像都是一起放在芯片內部的FLASH中的, boot也僅僅是初始化了SDRAM然后跳到Linux內核處運行而已。
隨著后面開發需求增加,會添加更多的驅動,內核鏡像也會越來越大,此時芯片內部的2MB FLASH就不夠用了,我們需要一個能從不同存儲介質加載Linux到SDRAM中運行的Boot,所以我們現在開始就來全新設計一個功能更完善的Boot。
為什么我們不移植UBOOT呢? 一方面UBOOT太大了,另一方面要體現我們”玩轉”的性質,我們本系列文章就是要從0開始玩轉Linux的方方面面,所以所有的東西我們都盡可能自己從0開始造,這樣有助于對整個Linux開發更加透徹的理解,以這種方式學習是最能夠深入的學習方式。且這樣才是學習”漁”之道,而不是被動接受別人的”魚”。
當你建立了一個又一個自己的輪子之后,你就像是嵌入式領域的哆啦A夢,任何需求你都可以從自己的輪子庫里快速找到素材來完成。而如果你只會使用別人的輪子,且得意于此,甚至以大論調”不要重復造輪子”來作為借口,且不深入理解使用的輪子的原理時,則永遠無法進步,只能在嵌入式門外看看算不上入門。
代碼見:https://github.com/qinyunti/stm32f429-boot.git
我們先按照現在的基本需求,來進行一個框架設計,后面再不斷實現,完善,迭代。
先簡單整理一下需求
1.準備基本的運行環境,SDRAM初始化等,這個現在已經具備。
2.要能夠實現外部存儲介質的讀寫。暫時先實現SPI FLASH后面再實現SD卡等存儲介質的讀寫。
3.要設計鏡像的存儲結構,比如HEAD信息的設計,從HEAD信息來判斷設備樹,內核鏡像從哪加載,加載到哪去等等。 具體細節我們后面實現時再詳細完善。
4.要能夠實現鏡像等文件的導入導出,實現xmodem方式傳輸文件。
5.需要有人機交互方式這里先實現簡單的SHELL和調試打印輸出等。然后實現一些有用的命令行:SPIFLASH的修改,查看,鏡像下載,導出,直接運行鏡像等等。
設計框架如下

有幾個大的功能需要實現
1.實現基于循環緩存區的串口驅動,方便基于此實現串口打印,xmodem文件傳輸,以及shell交互。
2.實現存儲介質SPI FLASH的驅動,提供讀寫接口,實現SHELL導入導出SPI FLASH數據。
3.實現鏡像存儲結構的設計,實現燒錄鏡像,加載鏡像運行。
后面我們就按照上述功能模塊來分多篇介紹。本篇先介紹串口驅動實現,shell,xmodem實現部分。
得益于我們公眾號之前分享的輪子,所以現在要實現這些功能就很簡單了。
基于FIFO的串口驅動,參見我們之前公眾號分享的文章《https://mp.weixin.qq.com/s/MvL9eDesyuxD60fnbl1nag 超級精簡系列之十三:超級精簡的循環FIFO,C實現》
《https://mp.weixin.qq.com/s/vzjWu2LxpVGZw-msCooh8Q 超級精簡系列之十六:基于IO模擬+FIFO的串口驅動》
這里我們只需要串口初始化,接收中斷將收到的字節寫入FIFO,發送字節接口即可。移植我們的輪子非常簡單。詳見uart.c/h。這里不再贅述。
uart.c
//#define USART_SR_TXE (1 << 7)//#define USART_CR1_RE (1 << 2)//#define USART_CR1_TE (1 << 3)//#define USART_CR1_RXNEIE (1 << 5)//#define USART_CR1_UE (1 << 13)static uint32_t reg_base[1]={0x40011000};static uint8_t s_uart_rx_buffer[1029];static fifo_st s_uart_fifo_dev[2]={{.in = 0,.len = 0,.out = 0,.buffer = s_uart_rx_buffer,.buffer_len = sizeof(s_uart_rx_buffer),},};void uart_rx_cb(int id, uint8_t* buff, uint32_t len){fifo_in(&(s_uart_fifo_dev[id-1]), buff, len);}void uart1_irqhandler(void){volatile uint32_t *USART_SR = (uint32_t*)(reg_base[1-1] + 0x00);volatile uint32_t *USART_DR = (uint32_t*)(reg_base[1-1] + 0x04);uint8_t ch;/* 讀字節 */if(*USART_SR & USART_SR_RXNE){ch = (uint8_t)(*USART_DR);/* 清中斷 讀DR自動清除標志 */uart_rx_cb(1, &ch, 1);}}void uart_init(int id, uint32_t baud){if(id == 1){volatile uint32_t *USART_BRR = (uint32_t*)(reg_base[id-1] + 0x08);volatile uint32_t *USART_CR1 = (uint32_t*)(reg_base[id-1] + 0x0C);volatile uint32_t *USART_CR2 = (uint32_t*)(reg_base[id-1] + 0x10);volatile uint32_t *USART_CR3 = (uint32_t*)(reg_base[id-1] + 0x14);uint32_t int_div, frac_div, val;*USART_CR1 = USART_CR1_TE | USART_CR1_RE;*USART_CR2 = 0;*USART_CR3 = 0;int_div = (25 * (clock_get_ahb()/2)) / (4 * baud);val = (int_div / 100) << 4;frac_div = int_div - 100 * (val >> 4);val |= ((frac_div * 16 + 50) / 100) & 0xf;*USART_BRR = val;*USART_CR1 |= USART_CR1_UE;*USART_CR1 |= USART_CR1_RXNEIE;//NVIC_SetPriority(USART1_IRQn,2,0);NVIC_EnableIRQ(USART1_IRQn);}}uint32_t uart_send(int id, uint8_t* buffer, uint32_t len){if(id == 1){for(uint32_t i=0;i<len;i++){volatile uint32_t *USART_SR = (uint32_t*)(reg_base[id-1] + 0x00);volatile uint32_t *USART_DR = (uint32_t*)(reg_base[id-1] + 0x04);while (!(*USART_SR & USART_SR_TXE));*USART_DR = buffer[i];}}return len;}uint32_t uart_read(int id, uint8_t* buffer, uint32_t len){uint32_t rlen;CriticalAlloc();EnterCritical();rlen = fifo_out(&(s_uart_fifo_dev[id-1]), buffer, len);ExitCritical();return rlen;}
extern "C" {void uart_init(int id, uint32_t baud);uint32_t uart_send(int id, uint8_t* buffer, uint32_t len);uint32_t uart_read(int id, uint8_t* buffer, uint32_t len);void uart1_irqhandler(void);}
移植xprintf前面已經介紹,這里需要換一下輸出接口
static void xprintf_out(int ch){uint8_t c=(uint8_t)ch;uart_send(1, &c, 1);}
還是參考我們之前實現的輪子https://mp.weixin.qq.com/s/XLmbJn0SKoDT1aLdxHDrbg 一個超級精簡高可移植的shell命令行C實現
添加shell.c/h; shell_func.c/h
Shell的讀寫接口
static uint32_t shell_read(uint8_t *buff, uint32_t len){return uart_read(1,buff,len);}static void shell_write(uint8_t *buff, uint32_t len){uart_send(1,buff,len);}
設置3秒內按回車進入shell
static int wait_key(uint32_t timeout){uint32_t cur;uint32_t pre = get_ticks();uint8_t c;xprintf("Press Enter Key To Go To SHELL...\r\n");while(1){if(uart_read(1,&c,1) > 0){if(c == 0x0D){return 1;}}cur = get_ticks();if((cur - pre) >= timeout*1000){return 0;}}}if(wait_key(3)){shell_set_itf(shell_read, shell_write, (shell_cmd_cfg*)g_shell_cmd_list_ast, 1);while(1){shell_exec();}}
測試如下

我們再添加一個打印任意內存地址的函數
shell_func.c中添加
static void printmemfunc(uint8_t* param);g_shell_cmd_list_ast中添加
{ (uint8_t*)"printmem", printmemfunc, (uint8_t*)"printmem mode[hex/dec] addr[hex] len datasize[8/16/32] sig[1/0]"}, 實現如下
static void printmemfunc(uint8_t* param){uint32_t addr;uint32_t len;uint8_t mode[64];int datasize;uint8_t* tmp8_u;uint16_t* tmp16_u;uint32_t* tmp32_u;int8_t* tmp8_i;int16_t* tmp16_i;int32_t* tmp32_i;int sig;if(5 == sscanf((const char*)param, "%*s %s %lx %ld %d %d", mode, &addr, &len, &datasize, &sig))char* p =(char*)param;while(1){ /* 跳過%*s部分 */if((*p > 'z') || (*p < 'a')){break;}else{p++;}}while(1){ /* Skip leading spaces */if(*p != ' '){break;}else{p++;}}uint8_t* p_mode = mode;while(1){ /* mode部分 */if((*p > 'z') || (*p < 'a')){break;*p_mode = 0;}else{*p_mode = *p;p_mode++;p++;}}long tmp;xatoi(&p, &tmp);addr = tmp;xatoi(&p, &tmp);len = tmp;xatoi(&p, &tmp);datasize = tmp;xatoi(&p, &tmp);sig = tmp;{if(strncmp((const char*)mode,"hex", 3) == 0){if(datasize == 8){tmp8_u = (uint8_t*)addr;for(uint32_t i=0; i<len ;i++){if(i%16 == 0){xprintf("\r\n[%08x]:",addr+i*1);}xprintf("%02x ",tmp8_u[i]);}xprintf("\r\n");}else if(datasize == 16){tmp16_u = (uint16_t*)addr;for(uint32_t i=0; i<len ;i++){if(i%16 == 0){xprintf("\r\n[%08x]:",addr+i*2);}xprintf("%04x ",tmp16_u[i]);}xprintf("\r\n");}else if(datasize == 32){tmp32_u = (uint32_t*)addr;for(uint32_t i=0; i<len ;i++){if(i%16 == 0){xprintf("\r\n[%08x]:",addr+i*4);}xprintf("%08x ",tmp32_u[i]);}xprintf("\r\n");}else{xprintf("datasize must be 8/16/32\r\n");}}else if(strncmp((const char*)mode,"dec", 3) == 0){if(datasize == 8){if(sig == 0){tmp8_u = (uint8_t*)addr;for(uint32_t i=0; i<len ;i++){if(i%16 == 0){xprintf("\r\n");}xprintf("%d ",tmp8_u[i]);}xprintf("\r\n");}else{tmp8_i = (int8_t*)addr;for(uint32_t i=0; i<len ;i++){if(i%16 == 0){xprintf("\r\n");}xprintf("%d ",tmp8_i[i]);}xprintf("\r\n");}}else if(datasize == 16){if(sig == 0){tmp16_u = (uint16_t*)addr;for(uint32_t i=0; i<len ;i++){if(i%16 == 0){xprintf("\r\n");}xprintf("%d ",tmp16_u[i]);}xprintf("\r\n");}else{tmp16_i = (int16_t*)addr;for(uint32_t i=0; i<len ;i++){if(i%16 == 0){xprintf("\r\n");}xprintf("%d ",tmp16_i[i]);}xprintf("\r\n");}}else if(datasize == 32){if(sig == 0){tmp32_u = (uint32_t*)addr;for(uint32_t i=0; i<len ;i++){if(i%16 == 0){xprintf("\r\n");}xprintf("%d ",tmp32_u[i]);}xprintf("\r\n");}else{tmp32_i = (int32_t*)addr;for(uint32_t i=0; i<len ;i++){if(i%16 == 0){xprintf("\r\n");}xprintf("%d ",tmp32_i[i]);}xprintf("\r\n");}}else{xprintf("datasize must be 8/16/32\r\n");}}else{xprintf("mode must be [hex/dec]\r\n");}}}
測試打印0x0800000處16個word如下

以上基于我們之前積累的輪子,完善了串口驅動提供了對應的串口讀寫接口方便其他模塊使用。實現了shell,現在就可以在boot時按回車進入shell,進行交互操作。以一個打印任意mem地址處內容的shell命令實現為例,演示了shell命令行的添加方法,后面就可以不斷擴展shell指令,添加更多的功能。