20200928会议要点总结

遇到困难,解决困难

每遇到一个问题,就放弃一个问题,就永远不会成长,遇到一个问题,解决一个问题,能力就会提升一步。

编写程序不能有警告

有些警告可能会影响程序的运行,编写程序要做到警告尽可能的少,最好是没有警告,在实验室IDE和官方IDE下都没有警告。

串口更新后接收区没有东西显示

出现问题的原因:串口接收事件或串口失效,需要重新打开一次

已采取的方法:程序烧录之后对串口显示区时间重新绑定一次

结果:未完全解决

后续采取的方法:烧录之后重新绑定一次串口

串口更新

设备连接

1.设备连接流程

​ ① 一些变量的声明:

​ (1) 返回值:用于标识寻找串口的结果(ret)

​ 0-成功找到设备

​ 1-未找到串口

​ 2-找到串口,但是未找到设备

​ (2) 串口信息:用于标识设备的串口信息(com)

​ 找到设备—返回目标设备信息

​ 未找到设备—返回计算机的所有可用com

C#–out关键字:通常一个方法只能返回一个值,但是如果在某些时候,我们想要返回多个值, 例如某个方法将一个浮点数分割成一个整数和一个小数返回去。这个时候我们就要用到out关键 字。如果用ref也可以解决,但是用ref需要在初始化的时候虚设一个值,并且还要给虚设值赋初始 值。

​ (3)

​ (4) 保存串口接收信息

​ (5) 与终端握手帧数据

2.清除一些可能的余留信息

​ (1) 右上角显示区

​ (2) 底部提示

​ (3) 左侧代码显示区

​ (4) 右侧更新提示区

​ (5) 刷新显示

​ (6) 更改显示文字

3.重新遍历串口,寻找终端

4.根据寻找串口的返回值确定执行下列分支

​ (1) 连接终端的emuart失败(不存在可用串口):

​ 禁用相关按钮

​ 修改相关显示信息

​ 关闭串口

​ 返回

​ (2) 存在串口,但不存在emuart设备:

​ 让终端设备情况数据缓存区

​ 修改相关显示信息

​ 关闭串口

​ 返回

​ (3) 找到串口与UE:

​ 向设备发送握手帧(若未收到数据,转向(4))

​ 修改相关显示信息

​ 允许相关按钮使用

​ 清空接收数据缓冲区

获得设备的信息放在recv中

解析握手帧数据(有新/旧版本握手帧)

设置设备信息

修改相关显示信息

有串口信息,则增加接收串口事件

​ (4) 未收到返回信息(与(2)类似):

​ 让终端设备情况数据缓存区

​ 修改相关显示信息

​ 关闭串口

​ 返回

基础知识

1. 伪指令

1.1 指令概念

​ 指令是指指令系统的各条指令,每一条指令在源程序汇编时都要产生可供计算机执行的指令代码(即目标代码),每一条指令语句是表示计算机具有的一个基本能力。

1.2 伪指令概念

​ 伪指令是用于对汇编过程进行控制的指令,该类指令并不是可执行指令,没有机器代码,只用于汇编过程中为汇编程序提供汇编信息。例如,提供如下信息:哪些是指令、哪些是数据及数据的字长、程序的起始地址和结束地址等。伪指令有2个特点:

(1)由于是伪“指令”,因而它只存在于汇编语言中。高级语言中不叫指令,叫语句;

(2)由于是“伪”指令,也即“假”指令,因而不是可执行指令,不会产生机器代码,不会占用ROM空间,只用于汇编过程中为汇编程序提供汇编信息。

1.3 常用指令总结

​ 在嵌入式开发中,汇编程序常常用于非常关键的地方,比如系统启动时初始化,进出中断时的环境保护,恢复等对性能有要求的地方。

​ ARM指令集可分为六大类,分别是数据处理指令、Load/Store指令、跳转指令、程序状态寄存器处理指令、协处理器指令和异常产生指令。

​ ARM的寻址方式可分为立即寻址、寄存器寻址、寄存器间接寻址、基址加偏址寻址、堆栈寻址、块拷贝寻址、相对寻址。

1.3.1 相对跳转指令:B、BL

(1)B与BL指令的作用是什么?

​ B与BL指令的作用: 实现程序跳转,也就是调用子程序。

(2)B与BL指令的区别是什么?

​ B指令: 简单的程序跳转,跳转到目标号处执行。

​ BL指令:带链接程序跳转,也就是要带返回地址。在发生跳转前,将当前PC-4保存到R14中。

​ 也就是将返回地址存在R14中,所以可以在子程序返回时只要MOV PC,LR即可

(3)B{条件} <地址>

​ B—Branch

1
2
3
4
5
6
7
b funa
....
funa:
b funb
....
funb:
....

(4)BL{条件}<地址>

​ BL—Branch withLink

(5)BX{条件}<地址>

​ BX—Branch and eXchange_Instructions(计算机中一般不用E,例如执行权限EXE,用X代表)

​ BX用法:

​ BX Rn

​ BX是跳转指令,Rn是寄存器,如果Rn的位0为1,则进入Thumb状态;如果Rn的位为0,这进入ARM状态。(原因:ARM指令的后两位始终为0,而Thumb指令的后一位始终为0,因此采用位0来表示ARM指令与Thumb指令的切换标志位。)

​ 1、ARM状态:32位,ARM状态执行字对齐的32位ARM指令。

​ 2、Thumb状态,16位,执行半字对齐的16位指令。

​ 3、用BX Rn指令来进行两种状态的切换:

​ 注:1、ARM和Thumb两种状态之间的切换不影响处理器的工作模式和寄存器的内容。

​ 2、ARM处理器在处理异常时,不管处理器处于什么状态,则都将切换到ARM状态。

1.3.2 数据传送指令mov,地址读取伪指令ldr

​ mov指令可以把一个寄存器的值赋给另外一个寄存器,或者把一个常数赋给寄存器。

​ mov r1, r2

​ mov r1, #1024

​ mov传送的常数必须能用立即数表示。当不能用立即数表示时,可以用ldr命令来赋值。Tips:ldr时伪命令,不是真实存在的指令,编译器会把他扩展成真正的指令;如果该常数能用“立即数”来表示,则使用mov指令,否则编译时将该常数保存在某个位置,使用内存读取指令把它读出来。

1.3.3 内存访问指令ldr、str、ldm、stm

1.3.4 加减指令

​ add r1, r2, #1

​ sub r1, r2, #1

1.3.5 程序状态寄存器的访问指令msr,mrs

1.3.6 异常中断指令

1.3.7 其他伪指令

​ (1).extern main

​ ‘.extern’定义一个外部符号(可以是变量也可以是函数),上面的代码表示文本文件中引用的main是一个外部函数。

​ (2).text

​ ‘.text’表示下面的语句都属于代码段

​ (3).global _start

​ _start:

​ ‘.global’将本文件中的某个程序标号定义为全局的,如’_start’就是个全局函数

链接文件分析

一、链接文件基础知识

什么是链接文件?有何作用?

​ 当我们编写了多个C文件时,我们需要将它们编译链接成一个可执行的文件xxx.exe,此时就需要用到链接文件(.ld)。链接文件的主要功能就是:将多个目标文件(.o)和库文件(.a)链接成一个可执行文件。

链接脚本文件主要有什么内容?

  1. 链接配置(可不配置)

    如一些符号变量的定义、入口地址、输出格式等

1
2
3
4
STACK_SIZE = 0X200
OUTPUT_FORMAT(elf32-littlearm)
OUTPUT_ARCH(arm)
ENTRY(_start)
  1. 内存布局定义

    链接文件中以MEMORY命令定义了存储空间,其中以ORIGIN定义地址空间的起始地址,LENGTH定义地址空间的长度

1
2
3
4
MEMORY
{
FLASH (rx) : ORIGIN = 0, LENGTH = 64K
}
  1. 段链接定义

    链接文件中以SECTIONS命令定义一些段(text、data、bss段等)链接分布

1
2
3
4
5
6
7
SECTIONS
{
.text:
{
*(.text*)
} > FLASH
}

.text段即代码段, *(.text*)指示将工程中所有目标文件的.text段链接到FLASH中

二、常用关键字、命令

  1. MEMORY命令

    1.1 使用MEMORY来定义内存如下:

1
2
3
4
5
6
MEMORY
{
NAME [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN1
NAME [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2
...
}

​ 1.2 参数介绍

NAME : 存储区域的名字 。(命名无要求)

ATTR :定义改存储区域的属性(权限)。ATTR属性内可以出现以下7个字符(可出现多个):

· R 只读section

· W 读/写section

· X 可执行section

· A 可分配的section

· I 初始化了的section

· L 与I相同

· ! 除该字符以外的任何一个属性的section

ORIGIN(关键字) : 区域的开始地址,可简写成org或o

LENGTH(关键字) : 区域的大小,可简写成len或l

​ 1.3 KL36——BIOS示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 定义内存区域的起始地址和长度 */
MEMORY
{
/*中断向量区*/
m_interrupts (RX) : ORIGIN = MCU_SECTORSIZE * GEC_BIOS_SECTOR_START +0x00000000, LENGTH = 0x00000100
/*配置区*/
m_flash_config (RX) : ORIGIN = MCU_SECTORSIZE * GEC_BIOS_SECTOR_START +0x00000400, LENGTH = 0x00000010
/*Flash代码区*/
m_text (RX) : ORIGIN = MCU_SECTORSIZE * GEC_BIOS_SECTOR_START +0x00000410, LENGTH = MCU_SECTORSIZE *(GEC_BIOS_SECTOR_END +0x01) - MCU_SECTORSIZE * GEC_BIOS_SECTOR_START - 0x0410
/*统命令代码放置处。*/
DY_CMD (RX) : ORIGIN = MCU_SECTORSIZE * GEC_DYNAMIC_START, LENGTH =MCU_SECTORSIZE * ( GEC_DYNAMIC_END - GEC_DYNAMIC_START + 1 )
/*函数列表放置处。*/
CPT_LST (RX) : ORIGIN = MCU_SECTORSIZE * GEC_COMPONENT_LST_START, LENGTH =MCU_SECTORSIZE * ( GEC_COMPONENT_LST_END - GEC_COMPONENT_LST_START + 1 )
/*函数代码放置处。*/
CPT_FUN (RX) : ORIGIN = MCU_SECTORSIZE * GEC_COMPONENT_FUN_START, LENGTH =MCU_SECTORSIZE * ( GEC_COMPONENT_FUN_END - GEC_COMPONENT_FUN_START + 1 )
/*RAM区*/
m_data (RW) : ORIGIN = GEC_BIOS_RAM_START, LENGTH = GEC_BIOS_RAM_END - GEC_BIOS_RAM_START
}
  1. SECTIONS命令

    2.1 SECTIONS基本的命令语法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SECTIONS
{
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{
contents
} >region :phdr =fill
...
}

/* 这么多参数,secname和contents是必须的,即可简写成: */

SECTIONS
{
...
secname :
{
contents
}
...
}

​ 2.2 参数解析

链接脚本本质就是描述输入和输出。

secname表示输出文件的段,即输出文件中有那些段。

contents描述输出文件的这个段从哪些文件里抽取出来,即输入文件,一般就是目标文件。

​ 2.3 示例

如下,将目标文件的数据段链接到输出文件的数据段(段名可以自己定义,段名前后必须要有空格)

1
2
3
4
5
6
7
8
9
10
SECTIONS
{
...
.data : /*输出文件中的data*/
{
main.o(.data) /*输入文件中的data*/
*(.data)
}
...
}

其中 *(.data) 表示将所有的目标的.data段链接到输出文件.data段中, 特别注意的是,之前链接的就不会再链接,这样做的目的是可以将某些特殊的目标文件链接到地址前面。

​ 2.4 其他参数的介绍

· start: 表示将某个段强制链接到的地址

· AT(addr) : 实现存放地址和加载地址不一致的功能,AT表示在文件中存放的位置,而在内存里呢,按照普通方式存储。

· region:这个region就是前面说的MEMORY命令定义的位置信息。

​ 2.5 KL36——BIOS示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/* Define output sections */
SECTIONS
{
/* The startup code goes first into internal flash */
.interrupts :
{
__VECTOR_TABLE = .;
. = ALIGN(4);

/*
isr_vector在start.S中定义:.section .isr_vector, "a",按照MEMORY命令说明,
isr_vector由于没有指定输出section,因此会创建与输入section同名的输出section,且会按照
isr_vector的属性放到合适的内存区域,此处KEEP是保证isr_vector的输出section不会被删除
*/
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} > m_interrupts

.flash_config :
{
. = ALIGN(4);
KEEP(*(.FlashConfig)) /* Flash Configuration Field (FCF) */
. = ALIGN(4);
} > m_flash_config

/* The program code and other data goes into internal flash */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
} > m_text

.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > m_text

.ARM :
{
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} > m_text

.ctors :
{
__CTOR_LIST__ = .;
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*crtbegin?.o(.ctors))
/* We don't want to include the .ctor section from
from the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
__CTOR_END__ = .;
} > m_text

.dtors :
{
__DTOR_LIST__ = .;
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
__DTOR_END__ = .;
} > m_text

.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} > m_text

.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} > m_text

.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
} > m_text

.dynamic_command :
{
. = ALIGN(1);
KEEP(*(.dynamic_command))
. = ALIGN(1);
} > DY_CMD

.component_list :
{
. = ALIGN(1);
KEEP(*(.component_list))
. = ALIGN(1);
} > CPT_LST

/* Flash中的构件函数区 */
.component_fun :
{
. = ALIGN(1);
KEEP(*(.component_fun))
. = ALIGN(1);
} > CPT_FUN
__etext = .; /* define a global symbol at end of code */
__DATA_ROM = .; /* Symbol is used by startup for data initialization */





/* RAM中的变量区 */
.data : AT(__DATA_ROM) /*存储在FLASH中,上电后拷贝至RAM区*/
{
. = ALIGN(4);
__DATA_RAM = .;
__data_start__ = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
KEEP(*(.jcr*))
. = ALIGN(4);
__data_end__ = .; /* define a global symbol at data end */
} > m_data


__DATA_END = __DATA_ROM + (__data_end__ - __data_start__);
.bss :
{
/* This is used by the startup in order to initialize the .bss section */
. = ALIGN(4);
__START_BSS = .;
__bss_start__ = .;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
__bss_end__ = .;
__END_BSS = .;
} > m_data


.heap :
{
. = ALIGN(8);
__end__ = .;
PROVIDE(end = .);
__HeapBase = .;
__HeapLimit = .;
} > m_data

.stack :
{
. = ALIGN(8);
} > m_data

/* Initializes stack on the end of block */
__StackTop = ORIGIN(m_data) + LENGTH(m_data);

PROVIDE(__stack = __StackTop);

.ARM.attributes 0 : { *(.ARM.attributes) }


}
  1. 各关键字

    3.1 定位符号’.’的使用

    ‘.’表示当前地址,它可以被赋值也可以赋值给某个变量。

​ 如下为将当前地址赋值给某个变量(链接器链接是按照SECTIONS里的段顺序排列的,前面的排列完之后就能计算出当前地址)—我们可以看到上述SECTIONS中频繁出现 . = ALIGN(x)或者 xxx = .;那么这个.的地址是会不断更新的。比如上一段.的地址 为 0x80,上一段大小有20,那么下一段的时候.的地址则为0x100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*将.赋值给RAM_START*/
RAM_START = .;


SECTIONS
{
. = 0×10000;/*将0x10000赋值给.*/
.text :
{
*(.text)
}

. = 0×8000000;/*将0x8000000赋值给.*/
.data :
{
*(.data)
}
}

​ “. = 0×10000;”该语句表示将当前地址设置为0x10000。如上代码中,意思是将所有目标文件的text段从0x10000地址开始存放。

​ 3.2 PROVIDE关键字

​ 该关键字定义一个(目标文件内被引用但没定义)符号。相当于定义一个全局变量的符号表,其他C文件可以通过该符号来操作对应的存储内存。

1
2
3
4
5
6
7
8
9
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}

​ 如上,在链接脚本中声明了_etext 符号。特别注意的是_etext 只是一个符号,没有存储内存,并不是一个变量,该符对应(映射)的是一个地址,,其地址为.text section之后的第一个字节的地址。C文件中引用用法如下。

1
2
3
4
5
6
7
int main()
{
//引用该变量
extern char _etext;
char *p = &_etext;
//...
}

若在链接脚本中 “ _etext = 0x100; “,即表示符号_etext对应的地址为0X100, 此时 & _etext的值为 0x100, char a= *p;表示为 从0X100地址取值存储的值赋值给变量a。

​ 3.3 KEEP关键字

​ 在连接命令行内使用了选项–gc-sections后,连接器可能将某些它认为没用的section过滤掉,此时就有必要强制连接器保留一些特定的 section,可用KEEP()关键字达此目的。如KEEP(* (.text))或KEEP(SORT(*)(.text))。说的通俗易懂就是:防止被优化。

​ 3.4 ALIGN关键字

​ 表示字节对齐, 如 “ . = ALIGN(4);”表示从该地址开始后面的存储进行4字节对齐。

后续解析具体实例

产品简介

1.使用场景

​ EMW307x 系列模组主要应用于物联网数据通讯。通过丰富的外设接口实现数据采集和控制,并可以通过 Wi-Fi 网络连接,将数据传输到物联网云服务平台上,实现万物互联。本系列模组通过各种不同的外形尺寸,接口形式,天线接口和温度范围,应用于广泛的物联网应用中。

2.使用范围

​ EMW3072-WiFi芯片的工作温度在-40°C到+105°C之间,它的典型应用有:智能电工、照明,特别适合球灯泡。

3.使用方法

​ EMW3072-WiFi芯片使用了AT2.0指令,通过串口发送AT指令与EMW3072-WiFi芯片通信来达到控制WiFi的功能。

4.工作模式

​ EMW3072-WiFi芯片不止提供了局域网内的信息通信,同时提供了高效的开发环境、各大物联网云服务的接入协议栈、丰富的示例程序和各种典型应用。

硬件及软件准备

1.硬件准备

开发EMW3072 模块主要需要用到以下三种芯片:EMW3072应用板、MXKit-Core板、MXKit-Base板。

​ EMW3072应用板主要是用于嵌入到其他芯片上用于增加芯片的远程通信功能,本身不提供引脚,采用的是与ESP8266相同的邮票孔设计。嵌入到芯片上后,芯片通过AT指令与EMW3072进行交互即可使得芯片具有WiFi功能。

​ MXKit-Core板是包含MXCHIP无线通讯模组的IOT接入核心板,通过MXPort接口与MXKit-Base板或MXKit-Arduino板连接,主要包括:

​ ⚫MXPort 接口,通过板对板连接器与 MXKit-Base 板或 MXKit-Arduino 板连接。

​ ⚫ MXCHIP 无线通讯模块,包括 Wi-Fi 模块,BLE 模块,Wi-Fi + BT 模块,LoRa 模块,GPRS 模块以及 SigFox 模块等。

​ MXKit-Core板上可放置不同种类的可拆卸的无线模组芯片,用户可以仅使用一个底板使用不同的MXKit-Core板

芯片启动后转向用户程序、操作系统的过程

上一章介绍了芯片启动后的过程,芯片上电后开始启动执行到无操作系统主函数main(07_NosPrg\main.cpp)前。那么针对于BIOS最小系统和mbedOS的跳转过程如下:

1. BIOS最小系统的跳转

BIOS的main函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
int main(void)
{
//(1)======启动部分(开头)============================================
//(1.1)声明main函数使用的局部变量(声明时不准赋值)
uint_8 muFlag; //更新状态标志
uint_32 mCount; //延时计数变量

//(1.2)【不变】关总中断
DISABLE_INTERRUPTS;

//(1.3)给全局变量及主函数使用的局部变量赋初值
muFlag=0; //更新状态标志=0,默认进入User
mCount=0; //延时计数变量
if (IS_POWERON_RESET) //若是冷复位,给有关全局变量赋初值
{
gFunFlag = 0; //更新操作提示初始化
gCount=0; //500ms计数单元
gTime=0; //全局时间戳,复位计时初始化
Rst_Count = 0; //按“复位键”的次数
memcpy((void*)gcUpdateFlag,"POWSTART\0",9); //初始化字符串,默认进入User
}

//(1.4)初始化外设模块
gpio_init(LIGHT_GREEN,GPIO_OUTPUT,LIGHT_OFF); //初始化BIOS运行指示灯
emuart_init(UART_UPDATE,115200); //初始化BIOS程序更新串口
timer_init(TIMER_BIOS,20); //初始化BIOS定时器
flash_init(); //初始化Flash构件
shakeDataInit(); //初始化握手数据

//(1.5)使能模块中断
uart_enable_re_int(UART_UPDATE); //使能UART_UPDATE模块中断
timer_enable_int(TIMER_BIOS); //使能BIOS定时器中断

//(1.6)【不变】开总中断
ENABLE_INTERRUPTS; //开总中断
//(1.7)按键复位判断
pin_reset_judge(); //如果短时间发生了多次按键复位,则不进入用户程序

//(1.8)判断是否需要进行更新操作
muFlag = updateJudge();
if(muFlag != 0) gFunFlag = 1; //需进行更新提示操作
else gcRecvLen = 0;

//(1)======启动部分(结尾)============================================

//(2)======主循环部分(开头)==========================================
for(;;)
{
switch(gFunFlag) //根据gFunFlag跳转执行
{
case 0: //无需更新数据,跳转User程序执行
gpio_set(LIGHT_GREEN,LIGHT_OFF); //关闭绿灯
uart_send_string (UART_UPDATE," 【BIOS提示信息】:USER程序开始运行...\r\n");
user_reset(GEC_USERBASE); //跳转User程序
break;
case 1: //需进行更新提示操作
gFunFlag = 10;
break;
default: //其他情况,绿灯闪烁,提示处于BIOS程序
mCount++;
if(mCount > 0x5fffff)
{
mCount = 0;
gpio_reverse(LIGHT_GREEN);
}
}
}
//(2)======主循环部分(结尾)==========================================
return 0;
}

那么,在上述代码中,有两处关键代码 pin_reset_judge();user_reset(GEC_USERBASE);

pin_reset_judge();

代码进行的是复位引脚被触碰了几次,本例子中以6次为限,触碰6次则程序返回BIOS中。

那么上一章所提的BSS段被注释掉就是希望芯片启动后不清楚某些全局变量的状态,如果芯片复位标志被置位,说明已触碰6次,那么就不会跳转到GEC_USERBASE用户区。反之,芯片启动后会直接跳转到用户区。具体流程见后续章节。

user_reset(GEC_USERBASE);

这段代码会让芯片直接跳转到用户区,GEC_USERBASE是用户区的首地址。

2. mbedOS的跳转

​ main 函数开始 mbedOS 的启动,我们把 mbedOS 启动过程梳理到一个函数中运行,由 main 函数来调用,这个函数原型为:

1
2
3
4
5
mbedOS_start(osThreadId_t &thd, void (*func)(void))。
//该函数有两个参数
//thd 为将要运行的主线程控制块的引用
//func为主线程执行的实际函数
//实际调用时为 mbedOS_start(thd_main,*app_init)。

​ mbedOS 进入 main 函数之后,调用 mbedOS 的启动函数,mbedOS 就开始启动,并无休无止地开始运行,进行任务调度。

mbedOS_start 函数是 mbedOS 启动过程函数

其主要任务是:

1.设置 mbedOS 堆栈区

2.重定向中断向量表

3.完成内核初始化

4.建立互斥信号

5.创建主线程和启动内核

类似于BIOS,在main函数中会直接调用mbedOS_start这个函数跳转到mbedOS的首地址处。

当内核启动成功后,函数 svcRtxKernelStart 执行完成返回到 SVC 中断中,进行上下文切换,返回到定时器线程中执行。定时器线程 osRtxInfo.timer.thread 运行后会进入阻塞状态,此时 mbedOS 会选择就绪队列中优先级最高的线程,即主线程 main_thread,使其进入激活态。

主线程被调度运行时,其执行函数 app_init主要负责完成初始化外设模块、初始化全局变量、使能中断模块、创建并启动其他用户线程、阻塞主线程的功能,之后的线程运行和切换都由 mbedOS 调度完成