一步步学习操作系统(2)——在STM32上实现一个可动态加载kernel的"my-boot"

如果要做嵌入式Linux,我们首先要在板子上烧写的往往不是kernel,而是u-boot,这时需要烧写工具帮忙。当u-boot烧写成功后,我们就可以用u-boot附带的网络功能来烧写kernel了。每当板子上电时,u-boot一般会被加载到内存的前半段,如果我们的kernel之前就已经被烧写到开发板了,那么u-boot会加载kernel到内存的后半段并跳转到kernel的起始地址处执行(或者直接跳转到kernel的起始地址处执行,如果kernel可以直接在flash上执行的话。)

如上图所示,绿色部分为u-boot,红色部分为kernel。

把loader(指u-boot)和kernel分离究竟有什么好处呢?

举个极端的例子:没有grub的话,我们就没办法做windows和linux双系统了。这就是最大的好处。

然而对于嵌入式,我倒是说不出什么上得了台面的理由,根据个人喜好,我倒是有3点理由:

1、不用再求助烧写工具了;

2、方便使用GNU交叉编译工具;

3、摆脱Windows+linux虚拟机的工作平台。

现在,我的笔记本就可以轻松一下了,只需单开fedora/ubuntu就能工作啦!

以下是源码和工程的下载链接:

kernel源码及mdk5工程

kernel的arm-gcc源码

my-boot源码及mdk5工程

注:仅可使用在stm32f10x系列

接下来,我们将分为三部分叙述:

1、系统概述;

2、kernel;

3、“my-boot”;

4、先烧写"my-boot“,然后用"my-boot”加载kernel——操作示例;

1、系统概述

接下来我们将建立两个工程,一个是用来编译kernel,一个用来编译loader(姑且命名为“my-boot”)。首先,我们先把“my-boot”和kernel都编译好,并通过烧写工具把“my-boot”烧写进stm32的flash中。然后,我们就可以重启stm32,并使之运行“my-boot”。“my-boot”等待接收烧写kernel的起始命令,当我们通过串口向“my-boot”发送了烧写起始命令后,“my-boot”将把串口设置为DMA模式,并等待我们发送kernel的bin文件。接着,我们再通过串口传送kernel的bin文件。传送结束后,kernel也就被写入stm32的RAM中了,同时“my-boot”把串口切换回通常的窗口通信模式。此时,芯片的控制权依旧被掌控在“my-boot”手中,不过,如果我们再向串口发送一条启动kernel的指令,那么stm32将跳转到kernel代码处执行。至此,我们的目标达成。

2、kernel

我们的kernel很简单,只有一个源文件,其功能就是不停的闪led。程序参考了博客http://www.cnblogs.com/sky1991/archive/2012/10/13/2722640.html的“例子一”,并加以修改与简化,代码给出如下:

  1 ;RCC寄存器地址映像
  2 RCC_BASE                EQU             0x40021000
  3 RCC_CR                  EQU             (RCC_BASE + 0x00)
  4 RCC_CFGR                EQU             (RCC_BASE + 0x04)
  5 RCC_CIR                 EQU             (RCC_BASE + 0x08)
  6 RCC_APB2RSTR            EQU             (RCC_BASE + 0x0C)
  7 RCC_APB1RSTR            EQU             (RCC_BASE + 0x10)
  8 RCC_AHBENR              EQU             (RCC_BASE + 0x14)
  9 RCC_APB2ENR             EQU             (RCC_BASE + 0x18)
 10 RCC_APB1ENR             EQU             (RCC_BASE + 0x1C)
 11 RCC_BDCR                EQU             (RCC_BASE + 0x20)
 12 RCC_CSR                 EQU             (RCC_BASE + 0x24)
 13 ;GPIO寄存器地址映像
 14 GPIOA_BASE              EQU             0x40010800
 15 GPIOA_CRL               EQU             (GPIOA_BASE + 0x00)
 16 GPIOA_CRH               EQU             (GPIOA_BASE + 0x04)
 17 GPIOA_IDR               EQU             (GPIOA_BASE + 0x08)
 18 GPIOA_ODR               EQU             (GPIOA_BASE + 0x0C)
 19 GPIOA_BSRR              EQU             (GPIOA_BASE + 0x10)
 20 GPIOA_BRR               EQU             (GPIOA_BASE + 0x14)
 21 GPIOA_LCKR              EQU             (GPIOA_BASE + 0x18)
 22 
 23 SETENA0                 EQU             0xE000E100
 24 SETENA1                 EQU             0xE000E104
 25 
 26 ;;FLASH缓冲寄存器地址映像
 27 FLASH_ACR               EQU             0x40022000
 28 
 29 ;-----------------
 30 MSP_TOP                 EQU             0x20005000              ;主堆栈起始值
 31 PSP_TOP                 EQU             0x20004E00              ;进程堆栈起始值
 32 
 33 DelayTime                EQU             13000000        ; to choose a better number to fit your cpu
 34 CLRPEND0                 EQU             0xE000E280    
 35 
 36 ;常数定义---------
 37 Bit0                    EQU             0x00000001
 38 Bit1                    EQU             0x00000002
 39 Bit2                    EQU             0x00000004
 40 Bit3                    EQU             0x00000008
 41 Bit4                    EQU             0x00000010
 42 Bit5                    EQU             0x00000020
 43 Bit6                    EQU             0x00000040
 44 Bit7                    EQU             0x00000080
 45 Bit8                    EQU             0x00000100
 46 Bit9                    EQU             0x00000200
 47 Bit10                   EQU             0x00000400
 48 Bit11                   EQU             0x00000800
 49 Bit12                   EQU             0x00001000
 50 Bit13                   EQU             0x00002000
 51 Bit14                   EQU             0x00004000
 52 Bit15                   EQU             0x00008000
 53 Bit16                   EQU             0x00010000
 54 Bit17                   EQU             0x00020000
 55 Bit18                   EQU             0x00040000
 56 Bit19                   EQU             0x00080000
 57 Bit20                   EQU             0x00100000
 58 Bit21                   EQU             0x00200000
 59 Bit22                   EQU             0x00400000
 60 Bit23                   EQU             0x00800000
 61 Bit24                   EQU             0x01000000
 62 Bit25                   EQU             0x02000000
 63 Bit26                   EQU             0x04000000
 64 Bit27                   EQU             0x08000000
 65 Bit28                   EQU             0x10000000
 66 Bit29                   EQU             0x20000000
 67 Bit30                   EQU             0x40000000
 68 Bit31                   EQU             0x80000000
 69     
 70 
 71 ;向量表*********************************************************************************
 72                 AREA            RESET, DATA, READONLY
 73 
 74                 DCD             MSP_TOP                   ;初始化主堆栈
 75                 DCD             Start                     ;复位向量
 76                 DCD             NMI_Handler               ;NMI Handler
 77                 DCD             HardFault_Handler         ;Hard Fault Handler
 78 ;***************************************************************************************
 79                 AREA            |.text|, CODE, READONLY
 80 ;主程序开始
 81                 ENTRY                           ;指示程序从这里开始执行
 82 Start
 83                 CPSID    I              ;关中断
 84                 ldr     r0, =MSP_TOP        
 85                 msr        msp,    r0        ;重设MSP
 86                 mov     r0, #0            
 87                 msr     control, r0         ;切换MSP,并进入特权级
 88                 
 89                 mov     r0, #0
 90                 mov     r1, #0
 91                 mov     r2, #0
 92                 mov     r3, #0
 93                 mov     lr, #0
 94                 
 95                 ldr     r0, =CLRPEND0
 96                 ldr     r1, [r0]
 97                 orr     r1, #0xFFFFFFFF
 98                 str     r1, [r0]
 99                 
100                 
101 ;时钟系统设置
102                 ;启动外部8M晶振
103         
104                 ldr             r0,=RCC_CR
105                 ldr             r1,[r0]
106                 orr             r1,#Bit16
107                 str             r1,[r0]
108 ClkOk
109                 ldr             r1,[r0]
110                 ands            r1,#Bit17
111                 beq             ClkOk
112                 ldr             r1,[r0]
113                 orr             r1,#Bit17
114                 str             r1,[r0]                                                                                                                                                                                                                                                                              
115                 ;FLASH缓冲器
116                 ldr             r0,=FLASH_ACR
117                 mov             r1,#0x00000032
118                 str             r1,[r0]
119                 ;设置PLL锁相环倍率为7,HSE输入不分频
120                 ldr             r0,=RCC_CFGR
121                 ldr             r1,[r0]
122                 orr             r1,#Bit18 | Bit19 | Bit20 | Bit16 | Bit14
123                 orr             r1,#Bit10
124                 str             r1,[r0]
125                 ;启动PLL锁相环
126                 ldr             r0,=RCC_CR
127                 ldr             r1,[r0]
128                 orr             r1,#Bit24
129                 str             r1,[r0]
130 PllOk
131                 ldr             r1,[r0]
132                 ands            r1,#Bit25
133                 beq             PllOk
134                 ;选择PLL时钟作为系统时钟
135                 ldr             r0,=RCC_CFGR
136                 ldr             r1,[r0]
137                 orr             r1,#Bit18 | Bit19 | Bit20 | Bit16 | Bit14
138                 orr             r1,#Bit10
139                 orr             r1,#Bit1
140                 str             r1,[r0]
141                 ;其它RCC相关设置
142                 ldr             r0,=RCC_APB2ENR
143                 mov             r1,#Bit2
144                 str             r1,[r0]
145 ;IO端口设置
146                 ldr             r0,=GPIOA_CRH
147                 ldr             r1,[r0]
148                 orr             r1,#Bit0 | Bit1         ;PA.8输出模式,最大速度50MHz 
149                 and             r1,#~Bit2 & ~Bit3       ;PA.8通用推挽输出模式
150                 str             r1,[r0]
151 
152                 mov     r5, #0 ; led flag
153                 
154                 ;CPSIE    I
155 ;主循环=================================================================================
156 main
157                 bl                 Delay
158                 bl              LedFlas
159                 b               main
160 ;子程序**********************************************************************************
161 LedFlas
162                 push            {r0-r3}
163                 cmp             r5,#1
164                 beq             ONLED
165                 
166                 mov             r5, #1
167                 ;PA.8输出1
168                 ldr             r0,=GPIOA_BRR
169                 ldr             r1,[r0]
170                 orr             r1,#Bit8
171                 str             r1,[r0]
172                 b               LedEx
173 ONLED
174                 mov             r5, #0
175                 ;PA.8输出0
176                 ldr             r0,=GPIOA_BSRR
177                 ldr             r1,[r0]
178                 orr             r1,#Bit8
179                 str             r1,[r0]
180 LedEx
181                 pop            {r0-r3}
182                 bx              lr
183                 
184 Delay
185                 push  {r0-r3}
186                 
187                 ldr    r0, =DelayTime
188 Loop            CBZ    r0, LoopExit
189                 sub    r0, #1
190                 b        Loop
191 LoopExit        
192                 pop            {r0-r3}
193                 bx              lr
194 ;异常程序*******************************************************************************
195 NMI_Handler
196                 ;xxxxxxxxxxxxxxxxxx
197                 bx              lr
198 ;-----------------------------
199 HardFault_Handler
200                 ;xxxxxxxxxxxxxxxxxx
201                 bx              lr
202 ;***************************************************************************************
203                 ALIGN           ;通过用零或空指令NOP填充,来使当前位置与一个指定的边界对齐
204 ;-----------------------------
205                 END

(1)主循环程序:

main

  bl Delay     // 延时
  bl LedFlas  // 翻转led
  b main      // 跳转会main开头(即“延时”)

(2)延时程序:

Delay
  push {r0-r3}

  ldr r0, =DelayTime // r0 = DelayTime;
Loop

   CBZ r0, LoopExit // if(r0 != 0) {
  sub r0, #1    //   r0 -= 1;
  b Loop      //   goto Loop; }
LoopExit
  pop {r0-r3}
  bx lr

该延时程序是“C51式”的延时,就是纯粹的让CPU空跑n个周期,这里是“DelayTime=13000000“。“13000000”是随便设的一个数,只是为了让眼睛和耐性都能接受,时钟频率变化之后,这个数字可以自行的、随性的去进行调整。

(3)LED翻转程序:

LedFlas
  push {r0-r3}
  cmp r5,#1     // if(r5 == 1)
  beq ONLED     //   goto ONLED;

  mov r5, #1       //  r5 = 1;
  ;PA.8输出1
  ldr r0,=GPIOA_BRR 
  ldr r1,[r0]       
  orr r1,#Bit8
  str r1,[r0]
  b LedEx
ONLED
  mov r5, #0       // r5 = 0;
  ;PA.8输出0
  ldr r0,=GPIOA_BSRR
  ldr r1,[r0]
  orr r1,#Bit8
  str r1,[r0]
LedEx
  pop {r0-r3}
  bx lr

该LED翻转程序以“r5”寄存器为标志,“r5”为0或1时,分别使PA.8输出不同的电平(此处PA.8对应开发板上一个红色LED)。


注:

一般MDK会生成hex文件,但不生成bin文件,所以我们还要给MDK加一些设置:

先找到fromelf.exe文件(一般在你的MDK安装目录里的bin目录里),然后如下图输入,

如:

C:Keil_v5ARMARMCCinfromelf.exe --bin --output kernel.bin kernel.axf

重新编译之后,于是我们就得到kernel的bin文件了,即kernel.bin,留着备用。

此处的kernel是可以独立运行的,所以不妨将该程序通过烧写工具烧写进开发板验证一下。

Note:

如果要用arm-gcc的kernel,首先,你的Linux必须得有arm-gcc编译工具。可使用目录中提供的脚本build.sh直接编译。此处我用的是“arm-none-eabi-as”等,如果是arm-linux-eabi-as等,需要简单修改脚本中的“PREFIX”变量。

3、“my-boot”

我们知道,在kernel和loader之间,真正的主角是kernel,loader只是一个辅助工具罢了。然而,作为loader的"my-boot"在这里却比kernel复杂许多。

“my-boot”以一步步学习操作系统(1)中的代码为基础,并将之整理了一下,把各个源文件分类到了不同的目录。

如图,除了obj目录是存放编译时所用的中间文件和hex文件外,其余4个目录都存放源码。

(1)arch目录:其中的源码均是和CPU架构相关,如中断代码、串口初始化、启动代码等;

(2)include目录:所有的头文件都在这里;

(3)kernel目录:包含主函数、任务调度、延时相关的源码;

(4)lib目录:stm32f10x库函数源码及“printf”重定向至串口的辅助代码(printf_to_serial)。

 

主程序一共建立3个任务:Task1, TaskBH, TaskDMA_Print。

 1 int main(void)
 2 {
 3     memset(SRAM_Buffer, 0, PAGE_SIZE);
 4     OSInit();
 5 
 6     OSTaskCreate(Task1, (void*)0, (OS_STK*)&Task1Stk[TASK_STACK_SIZE-1]);
 7     OSTaskCreate(TaskBH, (void*)0, (OS_STK*)&TaskBHStk[TASK_STACK_SIZE-1]);
 8     OSTaskCreate(TaskDMA_Print, (void*)0, (OS_STK*)&TaskDMA_PrintStk[TASK_STACK_SIZE-1]);
 9 
10     OSStart();
11 }

Task1:和kernel的功能一样,也是不断的闪led(最好是不同于kernel所使用的led),用来指示程序依旧正常运行,其功能很单纯;

TaskBH:接受串口发送过来的相关命令,并向串口打印信息以提示命令发送成功。特别是当收到“startos”指令后,会置位变量“GotoKernelFlag”,以致后续代码将跳转到kernel运行,该任务是三个任务中最复杂的一个;

TaskDMA_Print:打印RAM中的kernel代码。

其实以上三个任务的负担并不重,身上担子最重的时串口中断程序:

串口通信遵循一个自定义的协议,协议内容如下:

将以下串口中断程序与TaskBH结合着看,串口接受三种命令:

第一种:BURN命令:

如:

"BURN 0x08004000"

协议信息16进制表示为

57 41 4e 15 00 75 42 55 52 4e 20 30 78 30 38 30 30 34 30 30 30

该命令就是在通知开发板:“我要发送kernel了呦,赶紧准备接驾。”

这时,串口中断程序会启动串口的DMA模式,并开启DMA中断。

关于命令中的地址“0x08004000”,该值是设计为以后烧写flash做准备的,但现在我们只将kernel写入SRAM,所以现在还没有特别的作用,任意值都可以。

这个命令发送之后就要小心了,紧跟着必须向串口发送kernel的bin文件。发送结束后,DMA中断会被触发,并且会调用“LED1TURN()”去翻转另一个LED(不同于Task1的LED),用以指示kernel已经被写入RAM。

 Note:看了以下代码后,其实对于"BURN”这个命令来说,校验和是形同虚设的,为了图方便就偷了个懒……

 1 volatile void IRQ_Usart1(void)
 2 {
 3     
 4     RecvBuffer[Index] = serial_1;
 5 
 6     // Magic handling
 7     // Byte order: 0 1 2
 8     if(!MagicGotten) {
 9         if(0 == Index && 'W' == RecvBuffer[Index]) {
10             Index++;
11         }else if(1 == Index && 'A' == RecvBuffer[Index]) {
12             Index++;
13         }else if(2 == Index && 'N' == RecvBuffer[Index]) {
14             Index++;
15             MagicGotten = TRUE;
16         }else {
17             Index = 0;
18         }
19         return;
20     } 
21     
22     // Size handling
23     // byte order: 3 4
24     if(!SizeGotten) {
25         Index++;
26         if(5 == Index) {
27             SizeGotten = TRUE;
28             MsgSize = RecvBuffer[3] + (RecvBuffer[4] << 8);
29         }
30         if(SizeGotten && MsgSize > BUFSIZ) {
31             MagicGotten = FALSE;
32             SizeGotten = FALSE;
33             Index = 0;
34         }
35         return;
36     }
37 
38     // Checksum handling
39     // byte order: 5
40     if(!ChecksumGotten) {
41         Index++;
42         if(6 == Index) {
43             ChecksumGotten = TRUE;
44         }else {
45             MagicGotten = FALSE;
46             SizeGotten = FALSE;
47             Index = 0;
48         }
49         return;
50     }
51 
52     // Data handling:
53     // byte order: 6...
54     Index++;
55     if(Index >= MsgSize) {
56         MagicGotten = FALSE;
57         SizeGotten = FALSE;
58         ChecksumGotten = FALSE;
59         Index = 0;
60         MsgGotten = TRUE;
61         if(0 == strncmp((char *)RecvBuffer + 6, "BURN", 4)) {
62             USART_Cmd(USART1, DISABLE);
63             USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
64             USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); 
65             DMA1_Channel5->CNDTR = PAGE_SIZE;//re-load
66             DMA_Cmd(DMA1_Channel5, ENABLE);//re-open DMA
67             USART_Cmd(USART1, ENABLE);
68             LED1TURN();
69         }
70     }
71 }

 第二种:“startos”

协议信息16进制表示为

57 41 4e 0d 00 f8 73 74 61 72 74 6f 73

开发板接收到该命令后,TaskBH会将变量“GotoKernelFlag”设为1。之后,当SysTick中断程序(如下)再次执行时,将会调用“ModifyPC()”(这里的“PC”不是指“Personal Computer”,而是指PC指令寄存器哦)。这个函数很难懂。如果能理解这个函数,那么loader加载kernel的原理也就等于理解了80%了。我们不妨来试着啃一啃这块硬骨头!

 1 volatile void IRQ_SysTick(void)
 2 {
 3      OS_ENTER_CRITICAL();
 4      if(GotoKernelFlag) ModifyPC();
 5      if((--TaskTimeSlice) == 0){
 6         TaskTimeSlice = TASK_TIME_SLICE;
 7         OSTaskSchedule();
 8     }
 9     TimeMS++;
10     
11     OS_EXIT_CRITICAL();
12 }

 “ModifyPC()”是嵌入C语言式的汇编代码。其作用就是:

修改 PSP中存储的“当前被SysTick中断的任务”的 PC指针,使之等于kernel代码的起始地址。当该任务再一次被调度时,由于PC被换成了kernel代码的起始地址,所以就进入了kernel。

于是,两个问题出现了:

(1)kernel的起始地址是什么?

(2)被SysTick中断的任务的PC又在哪?

或许有人会认为:“kernel在DMA传送时,被放进‘SRAM_Buffer’这个缓冲区了,那么kernel的起始地址不就是‘SRAM_Buffer’吗?”(一开始我也是这么想的……)

可惜,真正的“起始地址”要比SRAM_Buffer在靠后一点点。

不妨在MDK5下,在kernel工程里打开Debug,接着再用二进制编辑器打开kernel.bin,这样就能看出蹊跷了。

stm32烧写程序时,是将代码烧至起始地址为0x08000000的flash中,并在开机运行时也是直接从flash启动。

看到没有,我们开机时的第一条命令是“CPSID I”,对应的指令地址为0x08000010,机器码为“B672”。

再用二进制文件打开kernel.bin后,发现果然是“B672”(二进制文件为“小端法”表示,所以是“72 B6”)。

所以,我们的kernel代码的起始地址,确切来说是第一条命令“CPSID I”的地址为“SRAM_Buffer + 0x10”。

Note:

“既然代码烧写进地址为0x08000000起始的地方,那么第一条指令为什么确实0x08000010呢?”

意味0x08000010 - 0x08000000 = 0x10 = 16 = 4*4,也即代码开头的“4个DCD”,每个DCD4字节。

第二个问题,“被SysTick中断的任务”的PC到底在哪儿呢?

首先我们要知道,任务使用的是PSP(可参考“PendSV_Handler”的汇编代码)。确认了这点之后,我们就可以继续往下讲了。

根据《Cortex-M3权威指南》--“chap09中断的具体行为”--“入栈”,当SysTick中断发生时,PSP会将发生如下图的变化。

也就是说,当SysTick中断发生时,CPU会自动将被中断任务的R0-R3,R12,LR,PC,xPSR这8个寄存器装载进PSP的后续存储空间,并且PSP最后将指向被中断任务R0寄存器的存储地址。

那么被中断任务的PC寄存器的存储地址就找到啦:PSP+24!如果该任务再次被调度执行,其第一条指令就是地址“PSP+24”存储的内容,如果我“偷偷的”把这个存储内容换成kernel代码的起始地址(确切来说,是第一条指令所在的地址),那么当该任务再次被调度时,原来的任务摇身一变,就成了kernel。

那么,ModifyPC()函数的代码就比较容易理解了。

 

PCModifyPC伪代码可写为:

ModifyPC()

  PSP.PC = SRAM_Buffer+0x10 

1 __asm void ModifyPC(void) {
2     IMPORT SRAM_Buffer
3     MRS R0, PSP
4     LDR R1, =SRAM_Buffer
5     ADD R1, #0x10
6     STR R1, [R0, #24] 
7     BX LR
8     align 4
9 }

第三种:任意字符串

如:“ls”

协议信息16进制表示为

57 41 4e 08 00 31 6c 73

该命令将对TaskDMA_Print的行为产生影响(代码如下)。

不难看出,只有当“ReadDMAFlag不为0时,该任务才会打印缓冲区SRAM_Buffer的内容。而在TaskBH中,上述命令会使变量“ReadDMAFlag”在0,1之间翻转,所以该命令也就起到控制打印“SRAM_Buffer”内容的作用。

 1 void TaskDMA_Print(void *p_arg)
 2 {
 3     int i = 0;
 4     while(1) {
 5             delayMs(2000);
 6             if(!ReadDMAFlag) continue;
 7             printf("########DMA##########START
");
 8             for(i = 0; i < PAGE_SIZE; i++) {
 9                 printf("%x ", SRAM_Buffer[i]);
10             }
11             printf("########DMA##########END
");
12       
13     }
14 }

 “my-boot"中几点注意事项:

(1)宏定义PAGE_SIZE

该宏在hardware.h中定义如下:

#define PAGE_SIZE 284

“284”?这个数字怎么这么莫名其妙?其实它表示的是kernel的大小(如下图),同时它也决定了缓冲区SRAM_Buffer的大小。

如果我编译了一个新的kernel,大小不再是284字节了怎么办?

实在对不住!“my-boot”中的这个宏也要改成相应的数字。当然,这确实是个不合理的地方,但现在为使代码尽可能简洁,所以就未做完善这方面的工作了,暂且辛苦一下。

(2)预设宏定义:USE_STDPERIPH_DRIVER

为了使用stm32的函数库,且避免编译出错,故定义该宏。具体内容可查询“stm32f10x.h”第8296行附近的代码。

 (3)库函数文件:

如果stm32的型号不是stm32f10x系列的,需要自备相应的函数库。

 4、先烧写"my-boot“,然后用"my-boot”加载kernel——操作示例

(1)将“my-boot”烧进stm32开发板

(2)向stm32开发板发送烧写命令:

BURN 0x08004000

16进制表示为
57 41 4e 15 00 75 42 55 52 4e 20 30 78 30 38 30 30 34 30 30 30

命令发送之后,串口工具会打印信息“Addr: 8004000”。而且还有一个变化,那就是另一个LED灯亮了/灭了(如果存在第二个led的话)。

注意,是16进制发送。

(3)发送kernel.bin

这时我们会发现,刚刚亮了/灭了的LED现在又灭了/亮了(如果存在第二个led的话)。

(4)打印刚刚烧进SRAM中的kernel命令(可选):

ls

16进制表示为

57 41 4e 08 00 31 6c 73

该命令发送一次,就会打印一次“###ls###”,并且跟后会打印SRAM中的内容。如果该命令只发送一次,那么SRAM中的打印将每隔2秒打印一次,直到再一次发送该命令为止。

所以图中有2个“###ls###”,第二个就是终止打印的。

 

(5)启动kernel:

startos

16进制表示为

57 41 4e 0d 00 f8 73 74 61 72 74 6f 73

这时你将会看到开发板在运行kernel的程序啦!

原文地址:https://www.cnblogs.com/ansersion/p/4672101.html