20201012会议要点总结

遇到问题,要珍惜问题

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

论文一页纸

每周负责的人需要写出一页论文相关的内容

每件事情都要做到底

每件事情都要做到底,做10件未完成的事情不如做一件完成的事情

研究生要会做也要会写

研究生要有做事的能力,也要学会总结,否则知识就会散掉。

第5周-学习备忘录

日期 星期 时间记录 学习内容 日合计
10.5 1
10.6 2
10.7 3
10.8 4 9:00-21:30 比赛 12.5
10.9 5 9:00-21:30 比赛 12.5
10.10 6 9:00-21:30 比赛 12.5
10.11 7
周合计时间
周平均时间

全局变量赋值后未被改变

出现问题的原因:全局变量只被声明而未被定义

已采取的方法:在使用的地方对全局变量进行定义

结果:已解决

1
2
3
4
5
6
7
8
9
10
11
12
//include.h文件

//(2)【不动】全局变量一处声明多处使用处理方法代码段
// 为下面声明全局变量,使用前缀G_VAR_PREFIX宏定义做准备
// GLOBLE_VAR在main.c中有宏定义
#ifdef GLOBLE_VAR //main.c文件中包含本头文件时,全局变量不加“extern”前缀
#define G_VAR_PREFIX
#else //其他文件中包含本头文件时,全局变量自动加“extern”前缀
#define G_VAR_PREFIX extern
#endif

G_VAR_PREFIX vuint_8 gState;
1
2
//photo.h文件--使用全局变量的文件
vuint_8 gState; //定义后使用即可解决问题

Gcc 编译优化简介

​ gcc 提供了为了满足用户不同程度的的优化需要,提供了近百种优化选项。用来对编译时间,目标文件长度,执行效率这个三维模型进行不同的取舍和平衡。优化的方法不一而足,总体上将有以下几类:

​ 1)精简操作指令;

​ 2)尽量满足cpu的流水操作;

​ 3)通过对程序行为地猜测,重新调整代码的执行顺序;

​ 4)充分使用寄存器;

​ 5)对简单的调用进行展开等等。

Gcc 优化等级详解

​ gcc提供了从O0-O3以及Os这几种不同的优化级别供大家选择,在这些选项中,包含了大部分有效的编译优化选项,并且可以在这个基础上,对某些选项进行屏蔽或添加,从而大大降低了使用的难度,毕竟,在一定基础上进行取舍,比万事从头开始要好得多。下面着重围绕这几个不同的级别进行简单介绍。

-O0: 不做任何优化,这是默认的编译选项。

-O1 : 对程序做部分编译优化,

​ 对于大函数,优化编译占用稍微多的时间和相当大的内存。使用本项优化,编译器会尝试减小生成代码的尺寸,以及缩短执行时间,但并不执行需要占用大量编译时间的优化。

​ 打开的优化选项:

​ 1.l -fdefer-pop:延迟栈的弹出时间。当完成一个函数调用,参数并不马上从栈中弹出,而是在多个函数被调用后,一次性弹出。

​ 这一优化选项主要是减少因函数调用后弹出参数所使用的时间,函数调用结束之后,不会立即将参数弹出,后续函数使用栈不多的时候,这个优化并不会影响函数的执行,只是相当于减少了栈的空间,若后续函数所需栈空间不够的时候,系统会把栈内堆积的参数一次性全部释放,这样就大大减少了栈弹出的时间。—提高了函数的执行效率

​ 2.l -fmerge-constants:尝试横跨编译单元合并同样的常量(string constants and floating point constants)

1
2
3
4
5
6
e.g: 
int main()
{
char str1[] = "Hello";
char str2[] = "Hello";
}

​ 编译器发现str1和str2所用的常量都是”Hello”,那么编译器不会给向内存申请两个地址,只会申请一个地址存放”Hello”,然后两个指针指向的字符串是同一个”Hello”的地址。—减少了目标文件长度。

​ 3.l -fthread-jumps:如果某个跳转分支的目的地存在另一个条件比较,而且该条件比较包含在前一个比较语句之内,那么执行本项优化.根据条件是true或者false,前面那条分支重定向到第二条分支的目的地或者紧跟在第二条分支后面. —提高执行效率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
e.g:
int main()
{
int i = 5;
if(i > 0)
{
//i > 0的跳转分支的目的地的条件为i < 10,这个条件包含在i > 0,
//那么会执行-fthread-jumps优化
if(i < 10)
{
printf("%d",i);//如果0 < i < 10,则会直接跳转到这句话
}
//如果i > 10 ,则会直接跳转到这
}
}
==>//经过-fthread-jumps优化后,函数的功能会类似于如下形式
int main()
{
int i = 5;
if(i > 0 && i < 10)
{
printf("%d",i);
}
}

​ 4.l -floop-optimize:执行循环优化,将常量表达式从循环中移除,简化判断循环的条件,并且optionally do strength-reduction,或者将循环打开等。在大型复杂的循环中,这种优化比较显著。

—提高执行效率

*(常量表达式就是表达式里面只有常量的式子,比如1+2是常量表达式,如果定义a为常量1,那么a+2也是常量表达式)*

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
e.g: 
//1.移除常量表达式
int main()
{
//将常量表达式从循环中移除
int i;
int a;
for(i = 0; i < 100; i++)
{
a = 5; //编译器会将这个常量表达式直接移除
}
}

//2.循环打开/循环展开
int main()
{
int i;
for(i = 0; i < 100; i++)
{
delete(i);
}
}
==>//循环打开后
int main()
{
int i;
for(i = 0; i < 100 ; i += 5)
{
delete(i);
delete(i+1);
delete(i+2);
delete(i+3);
delete(i+4);
}
}

​ 循环打开后,新程序只需要进行20次迭代,而不是100次。之后,只需要采用20%的跳转和条件分支,并且在多次迭代中表示可能显着减少。循环管理开销。为了产生最佳效益,不应在需要指针运算的展开代码中指定变量。这通常需要“基础加偏移”寻址,而不是索引引用。

​ 5.l -fif-conversion:尝试将条件跳转转换为等价的无分支型式。优化实现方式包括条件移动,min,max,设置标志,以及abs指令,以及一些算术技巧等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
e.g: 
int main()
{
int i = 3, j = 5;
if(i < j)
{
cout << i << endl;
}
else
{
cout << j << endl;
}
}
==>//优化后
int main()
{
int i = 3, j = 5;
cout << min(i,j) << endl;
}

​ 6.l -fif-conversion2基本意义相同。—提高执行效率

​ 7.l -fdelayed-branch:这种技术试图根据指令周期时间重新安排指令。 它还试图把尽可能多的指令移动到条件分支前, 以便最充分的利用处理器的治理缓存。—提高执行效率

​ 在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分以下3种类型:

​ (1)编译器优化的重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

​ (2)指令级并行的重排序:现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

​ (3)内存系统的重排序:由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

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
e.g:
int main()
{
int a = 1;
int b = 2;
a++;
b++;
b++;
b++;
a++;
b++;
}
==>//编译器会在不改变功能的情况下重新安排执行顺序
int main()
{
int a = 1;
int b = 2;
a++;
a++;
b++;
b++;
b++;
b++;
}
//这样子让a不需要进缓存等待b执行,加快程序速度

e.g: 指令移动到条件分支前未想到合适例子

​ 8.l -fguess-branch-probability:当没有可用的profiling feedback或__builtin_expect时,编译器采用随机模式猜测分支被执行的可能性,并移动对应汇编代码的位置,这有可能导致不同的编译器会编译出迥然不同的目标代码。

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
e.g:
int main()
{
if(__builtin_expect(x,0)) //if(__builtin_expect(x,0))==if(x==0),期望x!=0这个条件不成立,不希望程序走这条分支,所以执行func的可能性小
{
func();
}
else
{
//do something
}
}

e.g:
int main()
{
if(__builtin_expect(ptr != NULL,1)) //期望 ptr !=NULL这个条件成立(1), 更希望走这条分支,所以执行func()的可能性小
{
//do something
}
else
{
func();
}
}

​ 9.l -fcprop-registers:因为在函数中把寄存器分配给变量, 所以编译器执行第二次检查以便减少调度依赖性(两个段要求使用相同的寄存器)并且删除不必要的寄存器复制操作。

WiFi主要使用函数

1.连接WiFi

​ 通过发送AT指令让EMW3072连接WiFi,WiFi连接成功后,EMW3072芯片会回发”STATION_UP”,收到这个回发信息后,芯片就设定已经连接上WiFi。

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
//======================================================================
//函数名称:wifi_linktossid
//功能概要:接入指定wifi接入点
//参数说明:ssid:接入点名称,password:密码
//函数返回:接入是否成功=0成功,=1错误,=2无返回数据
//======================================================================
uint8_t wifi_linktossid(uint8_t* ssid,uint8_t* password)
{
//定义局部变量
uint8_t cmd[40],flag;
//2.设置帧尾
strcpy((char*)WIFI_OK_STOP,"STATION_UP");
strcpy((char*)WIFI_ERR_STOP,"ERROR");
strcpy((char*)cmd,"AT+WJAP=");
strcat((char*)cmd,(char*)ssid);
strcat((char*)cmd,",");
strcat((char*)cmd,(char*)password);
strcat((char*)cmd,"\r");
//3.发送指令
flag=wifi_send_cmd(cmd);
if(flag==WIFI_NORESPONSE)
wifi_send_cmd((uint8_t*)"AT+WJAP=?\r");
if(strstr((char*)WIFI_CMD,"STATION_UP")!=NULL)
flag=WIFI_OK;
if(flag==WIFI_OK)
WIFI_AP_STATE=1;
return (flag);
}

2.连接服务器

​ 连接WiFi成功后,芯片通过发送AT指令让EMW3072连接服务器,连接服务器成功后,EMW3072芯片会回发”SERVER,CONNECTED”

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
//======================================================================
//函数名称:wifi_con_srv
//功能概要:与服务器建立连接
//参数说明:ip:服务器IP地址,port:端口号(均为字符串格式),
// 格式"xxx.xxx.xxx.xxx",xxx取值范围0-255
//函数返回:=0连接成功,=1连接失败,=2处于发送模式
//======================================================================
uint8_t wifi_con_srv(uint8_t* ip,uint8_t* port)
{
//定义局部变量
uint8_t flag,cmd[100];
//1.检测wifi模块当前状态是否处于发送模式
if(WIFI_SEND_STATE==1)
return 2;
//2.检测输入的ip地址是否合法
if(!wifi_check_ip(ip))
return WIFI_ERROR;
//3.检测输入的port值是否合法
if(!wifi_check_port(port))
return WIFI_ERROR;
//4.设置帧尾
strcpy((char*)WIFI_OK_STOP,"SERVER,CONNECTED");
strcpy((char*)WIFI_ERR_STOP,"\r\nERROR");
//5.断开已有连接
wifi_disconnect();
//6.拼接指令

port[5]=0;
//AT+CIPSTART=1,tcp_client,192.168.31.244,8888\r
strcpy((char*)cmd,(char*)WIFI_START);
strcat((char*)cmd,"1,tcp_client,");
strcat((char*)cmd,(char*)(ip));
strcat((char*)cmd,",");
strcat((char*)cmd,(char*)(port));
strcat(((char*)cmd),"\r");

//7.发送指令
flag=wifi_send_cmd(cmd);
return flag;
}

3.接收返回信息

​ 接收返回信息函数是每个AT指令发送后都需要用到的函数,根据发出的命令存储不同的返回信息供芯片判断发送指令成功与否。

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
//======================================================================
//函数名称:wifi_int_re
//功能概要:中断接收字符
//参数说明:ch:要接受的字符,*dataLen:存储接收到的数据长度,
// *data:将接收到的数据存放到该数组中;
//函数返回:无
//======================================================================
void wifi_int_re(uint8_t ch,uint16_t *dataLen, uint8_t *data)
{
//定义局部变量
//1.WIFI_ACKSTOP大于零时接收指令回信
switch(WIFI_RECVSTOP)
{
//数据接收模式
case 0:uecom_recv(ch,dataLen,data);
break;
//命令返回接收模式
case 1:if(WIFI_CMDLENGTH<400)
{
wifi_returncheck(ch);
WIFI_CMD[WIFI_CMDLENGTH+1]=0;
}
break;
//重启模式
case 2:WIFI_BOOTFLAG = wifi_rebootcheck(ch);
if(WIFI_BOOTFLAG)
WIFI_RECVSTOP = 0;
break; //重启等待
case 3:
break;
}
}

4. 发送消息

​ 芯片通过这个函数发送消息,函数会将你想要发送的消息进行二次组帧以确保信息的稳定性和安全性。

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
//======================================================================
//函数名称:wifi_sendN
//功能概要:连接建立后,向服务器发送N个字符,如果是第一次发送,会开启发送模式,开启
//发送模式后部分函数会不可用,具体请看各函数说明
//参数说明:data:需要发送的字符,length:数据长度,IMSI:本机的IMSI号
//函数返回:发送是否成功=0成功,=1开启发送模式失败
//======================================================================
uint8_t wifi_sendN(uint8_t* data,uint16_t length,uint8_t* IMSI)
{
uint8_t buf[400], cmd[20], strlength[5];
if(WIFI_SEND_STATE==0)
{
//3.拼接发送请求帧尾
strcpy((char*)WIFI_OK_STOP,"OK");
strcpy((char*)WIFI_ERR_STOP,"ERROR");
WIFI_SEND_STATE=1;
}

wifi_frameEncode(IMSI,data,length,buf,&length);
//5.发送内容
itoa(length,(char*)strlength,10);
strcpy((char*)cmd,"AT+CIPSEND=1,");
strcat((char*)cmd,(char*)strlength);
strcat((char*)cmd,"\r");
uart_send_string(UART_WIFI,(uint8_t*)cmd);
uart_sendN(UART_WIFI,length,buf);
return WIFI_OK;
}

常用AT指令总结

进入AT指令模式: +++
退出AT指令模式,进WIFI的透传模式: AT+CIPSENDRAW\r
重启模块: AT+REBOOT\r
设置串口回显打开: AT+UARTE=ON\r
设置串口回显关闭: AT+UARTE=OFF\r
设置 Station 名称 和 密码: AT+WJAP=Seventeen,jjljjl123789\r
查询当前Station模式的IP地址,子网掩码和网关: AT+WJAPIP?\r
查询当前Station连接状态: AT+WJAPS\r
断开当前Station连接: AT+WJAPQ\r
STATION模式下,启动一个tcp client: AT+CIPSTART=1,tcp_client,192.168.31.244,8888\r
断开id=0的连接: AT+CIPSTOP=0\r
查询id=0的连接状态: AT+CIPSTATUS=0\r
id=0连接,tcp client发来的数据下发至串口: AT+CIPRECV=0,53588\r

代码用例

利用封装好的函数连接WiFi

1
2
3
4
5
6
7
8
9
10
11
12
13
void comm_init()
{
wifi_reset();
for(;;)
{
if(wifi_linktossid((uint8_t*)gFlashData.UE_SSID,(uint8_t*)gFlashData.UE_PSSWD)==WIFI_OK)
{
break;
}
LCD_ShowString(6,300,BLUE,GRAY, "WiFi Initing... ");
printf("\r\nWiFi Initing...");
}
}

利用封装好的函数连接服务器

1
2
3
4
5
6
7
8
9
10
11
for(;;)
{
mflag = wifi_con_srv((uint8_t*)gFlashData.serverIP,(uint8_t*)gFlashData.serverPort);
if(mflag!=WIFI_OK)
{
LCD_ShowString(6,300,BLUE,GRAY, "WiFi Connecting... ");
printf("\r\nWiFi Connecting...\r\n");
continue;
}
break;
}

发送流程

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
Send_Procedure_Branch:
for(int i = 0; i < 4133394; i++) continue;
mSendFreq++;
LCD_ShowString(49,209,BLUE,GRAY,(char *)gTimeString);
gpio_reverse(LIGHT_RED);
if (mSendFreq-mSendFreqOld>=30)
{
mSendFreqOld=mSendFreq;
mSendFlag = 1; //发送标志置1;
printf("\r\n%d 秒时间到,发送一帧数据!\r\n",30);
}
mj=0;
for (mi=0;mi<3000;mi++)
mj=mj+gpio_get(GPIO_TSI);
if (mj<2000)
{
mTsi++;
printf("\r\n触摸次数 = %d\r\n", mTsi);
LCD_ShowString(40,189,BLUE,GRAY,(char *)IntConvertToStr(mTsi,mString));
if ((mTsi-gUserData.touchNum)>=3)
{
mSendFlag = 1;
printf("\r\n触摸三次,发送一帧数据!\r\n");
gUserData.touchNum=mTsi;
printf("\r\n触摸次数 ============= %d\n", mTsi);
}
}
if(mSendFlag == 1)
{
userData_get(&gUserData);
//(2.3.2)根据命令,获得需要发送的数据
if(gFlashData.frameCmd[0]=='U'&&gFlashData.frameCmd[1]=='0')
{
mSendLen = mUserLen;
ArrayCpy(gUserData.cmd,gFlashData.frameCmd,2);
ArrayCpy(mSendData,(uint8_t *)&gUserData,mSendLen);
}
else if(gFlashData.frameCmd[0]=='U'&&gFlashData.frameCmd[1]=='1')
{
ArrayCpy(mSendData,gFlashData.frameCmd,2);
ArrayCpy(mSendData+2,gUserData.IMSI,15);
//【20200816】
main_TimeStamp(); //给gUserData.currentTime赋值

ArrayCpy(mSendData+17,(uint8_t *)&gUserData.currentTime,8);

ArrayCpy(mSendData+25,(uint8_t *)&gUserData.mcuTemp,4);
ArrayCpy(mSendData+29,(uint8_t *)&gUserData.signalPower,1);
ArrayCpy(mSendData+30,(uint8_t *)&gUserData.bright,2);
ArrayCpy(mSendData+32,(uint8_t *)&gUserData.touchNum,2);
ArrayCpy(mSendData+34,gUserData.lbs_location,25);
}
mflag = wifi_sendN(mSendData,mSendLen,gUserData.IMSI);
if(!mflag)
{
LCD_ShowString(6,300,BLUE,GRAY,(char *)"WiFi Send Successfully ");
printf("\r\nWiFi Send Successfully\r\n");
}
}
goto Send_ClearMark_Procedure_Branch;

回发流程

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
PostBack_Procedure_Branch:
mRecvCount++;
mflag = 0xff;
mSendLen = 0;
mWriteFlashFlag = 0;
mLCD_RefreshFlag=1;
LCD_ShowString(6,300,BLUE,GRAY,"AHL Recv one frame ");
printf("\r\n收到一帧\n");
ArrayCpy(mCmd,gcRecvBuf,2);
ArrayCpy(mSendData,gcRecvBuf,2);
if(mCmd[0]=='A') //读取flash中的信息
{
if(mCmd[1]=='0') //读取flash中的所有信息
{
mSendLen = mFlashLen+2;
ArrayCpy(mSendData+2,(uint8_t*)(&gFlashData),mFlashLen);
}
else if(mCmd[1]=='1') //读取flash中的产品信息
{
mSendLen = 145;
ArrayCpy(mSendData+2,gFlashData.equipName,mSendLen-2);
}
else if(mCmd[1]=='2') //读取flash中的服务器信息
{
mSendLen = 22;
ArrayCpy(mSendData+2,gFlashData.serverIP,mSendLen-2);
}
else if(mCmd[1]=='3') //读取用户存入flash的信息
{
mSendLen = 10;
ArrayCpy(mSendData+2,(uint8_t*)(&gFlashData.sendFrequencySec),mSendLen-2);
}
}
else if(mCmd[0]=='B') //更改flash中的信息
{
if(mCmd[1]=='0') //更改flash中的所有信息
{
ArrayCpy((uint8_t *)(gFlashData.equipName),(uint8_t*)&(gcRecvBuf[2]),mFlashLen);
mWriteFlashFlag = 1;
mSendLen = 9;
ArrayCpy((uint8_t *)mSendData+2,(uint8_t *)"success",mSendLen-2);
}
else if(mCmd[1]=='1') //更改flash中的产品信息
{
ArrayCpy((uint8_t *)(gFlashData.equipName),(uint8_t*)&(gcRecvBuf[2]),124);
mWriteFlashFlag = 1;
mSendLen = 9;
ArrayCpy((uint8_t *)mSendData+2,(uint8_t *)"success",mSendLen-2);
}
else if(mCmd[1]=='2') //更改flash中的服务器信息
{
ArrayCpy((uint8_t *)(gFlashData.serverIP),(uint8_t*)&(gcRecvBuf[2]),30);
mWriteFlashFlag = 1;
mSendLen = 9;
ArrayCpy((uint8_t *)mSendData+2,(uint8_t *)"success",mSendLen-2);
}
else if(mCmd[1]=='3') //更改用户存入flash的信息
{
ArrayCpy((uint8_t *)(&gFlashData.sendFrequencySec),(uint8_t*)&(gcRecvBuf[2]),8);
mWriteFlashFlag = 1;
mSendLen = 9;
ArrayCpy((uint8_t *)mSendData+2,(uint8_t *)"success",mSendLen-2);
}
}
else if(mCmd[0]=='U')
{
if(mCmd[1]=='0') //获取“U0”命令要发送的数据
{
ArrayCpy(gFlashData.frameCmd,mCmd,2);
if(gcRecvLen == mUserLen) //若为整帧数据
{
mSendLen = gcRecvLen;
mWriteFlashFlag =1;
ArrayCpy((uint8_t*)(&gUserData),gcRecvBuf,mUserLen);
ArrayCpy(gFlashData.equipName,gUserData.equipName,30);
ArrayCpy(gFlashData.equipID,gUserData.equipID,20);
ArrayCpy(gFlashData.equipType,gUserData.equipType,20);
ArrayCpy(gFlashData.vendor,gUserData.vendor,30);
ArrayCpy(gFlashData.userName,gUserData.userName,20);
ArrayCpy(gFlashData.phone,gUserData.phone,11);
ArrayCpy(gFlashData.serverIP,gUserData.serverIP,15);
ArrayCpy(gFlashData.serverPort,gUserData.serverPort,5);
gFlashData.sendFrequencySec = gUserData.sendFrequencySec;
gFlashData.resetCount = gUserData.resetCount;
ArrayCpy(gFlashData.frameCmd,gUserData.cmd,2);
//【画瓢处2】-执行操作
if(strcmp((char*)gUserData.surplusInfo,(char*)"LIGHT_ON") == 0)
{
gpio_set(LIGHT_BLUE,LIGHT_ON);
printf("LIGHT_ON");
}
else if(strcmp((char*)gUserData.surplusInfo,(char*)"LIGHT_OFF") == 0)
{
gpio_set(LIGHT_BLUE,LIGHT_OFF);
printf("LIGHT_OFF");
}
else
{

}

}
}
if(mCmd[1]=='1') //获取“U1”命令要发送的数据
{
if(gcRecvLen == 59)
{
ArrayCpy(gUserData.cmd,gcRecvBuf,2);
ArrayCpy(gUserData.IMSI,gcRecvBuf+2,15);
ArrayCpy((uint8_t *)&gUserData.currentTime,gcRecvBuf+17,8);
ArrayCpy((uint8_t *)&gUserData.mcuTemp,gcRecvBuf+25,4);
ArrayCpy((uint8_t *)&gUserData.signalPower,gcRecvBuf+29,1);
ArrayCpy((uint8_t *)&gUserData.bright,gcRecvBuf+30,2);
ArrayCpy((uint8_t *)&gUserData.touchNum,gcRecvBuf+32,2);
ArrayCpy(gUserData.lbs_location,gcRecvBuf+34,25);
}
}
}

if (mSendLen>0) //若有需要发送的数据
{
mflag = wifi_sendN(mSendData,mSendLen,gUserData.IMSI);
}
gcRecvLen = 0; //接收数据长度清零,表明已经读取
if(mflag==0)
LCD_ShowString(6,300,BLUE,GRAY,"AHL Reply Successfully ");
else if(mflag == 0xff)
LCD_ShowString(6,300,BLUE,GRAY,"AHL Recv Successfully ");
else
LCD_ShowString(6,300,BLUE,GRAY,"Send Error:Send Data Not OK ");
//判断是否需要写flash
if(mWriteFlashFlag == 1)
{
flash_read_logic((uint8_t*)gcRecvBuf,(MCU_SECTOR_NUM-1),0,MCU_SECTORSIZE);
flash_erase(MCU_SECTOR_NUM-1);
ArrayCpy(((uint8_t*)gcRecvBuf+28),(uint8_t*)(&gFlashData),mFlashLen);
flash_write((MCU_SECTOR_NUM-1),0,MCU_SECTORSIZE,(uint8_t*)gcRecvBuf);

mWriteFlashFlag = 0;
}
//
if (mLCD_RefreshFlag==1)
{
LCD_Showfirst(); //更新LCD上的显示
mLCD_RefreshFlag=0;
//补充显示显示设备的IMSI号、基站位置信息、接收次数
LCD_ShowString(60,85,BLUE,GRAY,(char *)mRetdata+20);
LCD_ShowString(60,131,BLUE,GRAY,(char *)mLBS); //基站位置
LCD_ShowString(90,251,BLUE,GRAY,(char *)IntConvertToStr(mRecvCount,mString));
}

  1. 一个

开发C语言程序需要经过四个步骤:预编译、编译、链接、运行

预编译

预编译对源码中所有的预处理语句进行处理,例如: #include语句所包含的文件内容替换掉语句本身,所有已定义的宏被展开。根据#ifdef,#if等语句的条件是否成立取舍相应的部分,预处理之后源码中不再包含预处理语句。GCC预处理可以生成.i的文件。

编译

这一阶段,编译器对源码进行词法分析、语法分析、优化等操作,最后生成汇编代码。

汇编

这一阶段使用汇编器对汇编代码进行处理,生成机器语言代码,保存在后缀为.o的目标文件中。当程序有多个代码文件构成时,每个文件都要先完成汇编工作,生成.o目标文件后,才能进入下一步的链接工作。

链接

经过汇编以后的机器代码还不能直接运行。为了使操作系统能够正确加载可执行文件,文件中必须包含固定格式的信息头,还必须与系统提供的启动代码链接起来才能正常运行,这些都是又链接器来完成的。

在STM32L431RC芯片中,链接的工作由链接文件.ld来完成,链接文件的主要功能就是将多个目标文件(.o)和库文件(.a)链接成一个可执行的文件。

链接文件中主要有以下三个部分:链接配置、内存布局定义、段链接定义

链接配置中设定一些符号变量的定义、入口地址、输出格式等

第一部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = 0x20010000; /* end of "RAM" Ram type memory【1】-0x2000ffff */

_Min_Heap_Size = 0x200 ; /* required amount of heap */
_Min_Stack_Size = 0x400 ; /* required amount of stack */


MCU_SECTORSIZE =2048;

MCU_FLASH_ADDR_START = 0x8000000; /*STM32L432RC的FLASH起始地址*/
GEC_USER_SECTOR_START =0; /* 26=USER程序开始扇区号,0=自启动【2】-26 */
GEC_USER_SECTOR_END =127; /*USER程序结束扇区号 */
GEC_USER_RAM_START =0x20000000; /*USER RAM区域起始地址 【3】-0x20002000*/
GEC_USER_RAM_END =0x20010000; /*USER RAM区域结束地址 */

ENTRY(Reset_Handler)

—–指定入口地址

_estack = 0x20010000

—–指定RAM的结束地址(起始地址为0x20000000,说明RAM区大小为64K)

_Min_Heap_Size = 0x200

—–指定堆的大小为512B

_Min_Stack_Size = 0x400

—–指定栈的大小为1KB

MCU_SECTORSIZE =2048

—–指定扇区大小

MCU_FLASH_ADDR_START = 0x8000000

—–指定Flash起始地址

GEC_USER_SECTOR_START =0

—–指定用户区起始扇区号

GEC_USER_SECTOR_END =127

—–指定用户区结束扇区号

GEC_USER_RAM_START =0x20000000

—–指定用户RAM开始地址

GEC_USER_RAM_END =0x20010000

—–指定用户RAM结束地址

第二部分

1
2
3
4
5
6
7
8
9
10
11
12
MEMORY
{
/*USER程序放置处。*/
USER_INTVEC(rx) : ORIGIN =MCU_FLASH_ADDR_START + MCU_SECTORSIZE * GEC_USER_SECTOR_START , LENGTH = MCU_SECTORSIZE

FLASH (rx) : ORIGIN = MCU_FLASH_ADDR_START + MCU_SECTORSIZE * (GEC_USER_SECTOR_START + 1) , LENGTH = MCU_SECTORSIZE * (GEC_USER_SECTOR_END - GEC_USER_SECTOR_START )


/*USER的RAM空间。*/
/* RAM (rwx) : ORIGIN = GEC_USER_RAM_START , LENGTH = GEC_USER_RAM_END - GEC_USER_RAM_START */
RAM (rwx) : ORIGIN = GEC_USER_RAM_START + 0x188, LENGTH = GEC_USER_RAM_END - GEC_USER_RAM_START - 0x188 /*【4】*/
}

MEMORY给出了地址的划分

这里的名字USER_INTVEC,FLASH,USER指的是一段地址,ORIGIN是起始地址,LENGTH是该段地址的长度。仔细观察可以发现,在下一部分SECTIONS中每段结束后会有 >USER_INTVEC,>FLASH,>USER,这个意思是将程序的各个内容放在USER_INTVEC,FLASH,USER所指的地址中。

第三部分

1
2
3
4
5
6
7
8
9
10
SECTIONS
{
/* The startup code into "FLASH" Rom type memory */
.isr_vector :
{
. = ALIGN(8);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(8);
} >USER_INTVEC
}

SECTIONS实际上指定了程序的各个内容该如何放置在FLASH或者RAM中。

. = ALIGN(8);

—– .表示当前地址,. = ALIGN(8)表示从当前地址开始后面的存储进行8字节对齐

KEEP(*(.isr_vector))

—– KEEP防止所有.isr_vector段被过滤掉,防止被优化

>USER_INTVEC

—– 将.isr_vector段的内容放置在USER_INTVEC中

一般的程序中包含常见的几个段:

text(存放程序)

rodata(存放被初始化的数据)

data(表示初始化不为0的变量)

bss(表示初始化值为默认的全局变量)

text,rodata放在flash中,data中的初始化值作为rodata放在flash中

变量在ram中占有空间,bss占ram空间

1
2
3
4
5
6
7
8
9
10
11
.malloc :
{
. = ALIGN(4);
__MALLOC_SYMBOLS = .; /* create a global symbol at ccmram start */
*(.malloc)
*(.malloc*)

. = ALIGN(4);
__EMALLOC_SYMBOLS = .; /* create a global symbol at ccmram end */
} >MALLOC AT> FLASH
/*结尾跳过*/

段可以自定义,如上面写的malloc段,由于编译obj过程中不会生成用户自定义的段,因此在源码中需要指定需要特殊处理的段

结尾的>MALLOC指上面花括号内的内容都放在第二部分中定义的MALLOC空间中。如果没有AT> FLASH,那么编译bin文件时地址是连续的