两者之间的差异主要体现在如下方面:
1.使用固件库:目前比较多的例程是使用固件库编写的。官方的例子也都采用固件库方式。特点就是简单,易于理解,资料多。如果你没有CortexM系列内核的开发基础,建议从固件库开始玩起。等有一定基础,或是特别需要时再用寄存器。
2.使用寄存器:想要深入理解CortexM3内核或是需要为了获得更好的可移植性,学习寄存器编程会比较有帮助。但是从专业的角度上看,寄存器更贴近底层,对外设的工作原理和运行机理会有更深的理解。
1.本次实验使用的是stm32f103指南者,从电脑中下载程序到stm32我采用的是使用串口下载程序
2.先下载以下几个压缩包:
下载链接
提取码:yong
3.用usb线把stm32开发板和电脑相连接,stm32端连接 ‘usb转串口’,打开stm32开关,小红灯亮起即可, 其中要求开发版上BOOT0和BOOT1接地,RXD接A9,TXD接A10,开发板买来默认就是这样,不需要改动:
4.打开我们下载的安装包,点击CH341SER.EXE安装,显示安装成功:
5.然后打开串口下载助手mcuisp:
6.在该页面进行如下设置(在后续的步骤中要进行更换其中的.hex文件,现在暂时不管):
7.该处串口通信实现以下功能:
*设置波特率为115200,1位停止位,无校验位。
*STM32系统给上位机(win10)连续发送“hello windows!”,
*当上位机给stm32发送“Stop stm32!”后,stm32停止发送,并返回信息“收到”。
然后我们打开如下图中所示的之前下载好的的文件:
8.打开之后进入如下界面,之后我们对部分程序进行一些改动:
9.我们开始修改其中的部分函数,如下:
*将stm32f10x_it.c文件的串口中断服务函数部分改为:
int i=0;
uint8_t ucTemp[50];
void DEBUG_USART_IRQHandler(void)
{
if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)
{
ucTemp[i] = USART_ReceiveData(USART1);
}
if(ucTemp[i] == '!')
{
if(ucTemp[i-1] == '2'&&ucTemp[i-2] == '3'&&ucTemp[i-3] == 'm'&&ucTemp[i-4] == 't'&&ucTemp[i-5] == 's'&&ucTemp[i-6] == ' ')
if(ucTemp[i-7] == 'p'&&ucTemp[i-8] == 'o'&&ucTemp[i-9] == 't'&&ucTemp[i-10] == 's')
{
printf("收到!");
while(1);
}
}
i++;
}
#include "stm32f10x.h"
#include "bsp_usart.h"
void delay(uint32_t count)
{
while(count--);
}
int main(void)
{
USART_Config();
while(1)
{
printf("hello windows 10!\n");
delay(5000000);
}
}
如图:
10.编译生成hex文件了,按照上面的方式(第6步)把hex文件烧录到stm32中,然后打开最开始下载的串口调试助手,点击打开串口,便可看到stm32发给电脑的信息,我们输入stop stem32!即可停止:
最终我们打开串口之后运行结果如下:
一个由C/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等:
其操作方式类似于数据结构中的栈。
2、堆区(heap) — 在内存开辟另一块存储区域:
一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。
注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(静态区)(static)—编译器编译时即分配内存:
全局变量和静态变量的存储是放在一块的,
初始化的全局变量和静态变量在一块区域,
未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
程序结束后由系统释放
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放。
5、程序代码区—存放函数体的二进制代码。
1、首先我们先用gedit命令创建一个ll.c的文件并进入编写如下程序:
#include
#include
#include
void before()
{
}
char g_buf[16];
char g_buf2[16];
char g_buf3[16];
char g_buf4[16];
char g_i_buf[]="123";
char g_i_buf2[]="123";
char g_i_buf3[]="123";
void after()
{
}
int main(int argc, char **argv)
{
char l_buf[16];
char l_buf2[16];
char l_buf3[16];
static char s_buf[16];
static char s_buf2[16];
static char s_buf3[16];
char *p_buf;
char *p_buf2;
char *p_buf3;
p_buf = (char *)malloc(sizeof(char) * 16);
p_buf2 = (char *)malloc(sizeof(char) * 16);
p_buf3 = (char *)malloc(sizeof(char) * 16);
printf("g_buf: 0x%x\n", g_buf);
printf("g_buf2: 0x%x\n", g_buf2);
printf("g_buf3: 0x%x\n", g_buf3);
printf("g_buf4: 0x%x\n", g_buf4);
printf("g_i_buf: 0x%x\n", g_i_buf);
printf("g_i_buf2: 0x%x\n", g_i_buf2);
printf("g_i_buf3: 0x%x\n", g_i_buf3);
printf("l_buf: 0x%x\n", l_buf);
printf("l_buf2: 0x%x\n", l_buf2);
printf("l_buf3: 0x%x\n", l_buf3);
printf("s_buf: 0x%x\n", s_buf);
printf("s_buf2: 0x%x\n", s_buf2);
printf("s_buf3: 0x%x\n", s_buf3);
printf("p_buf: 0x%x\n", p_buf);
printf("p_buf2: 0x%x\n", p_buf2);
printf("p_buf3: 0x%x\n", p_buf3);
printf("before: 0x%x\n", before);
printf("after: 0x%x\n", after);
printf("main: 0x%x\n", main);
if (argc > 1)
{
strcpy(l_buf, argv[1]);
}
return 0;
}
l_buf/l_buf2/l_buf3 ,直接定义,是由编译器自动分配的,存储在栈中
s_buf/s_buf2/s_buf3,定义static,编译器编译时分配内存。为全局变量,在全局初始化区
p_buf/p_buf2/p_buf3,他们指向的空间,通过malloc申请空间,存放在堆中
g_buf/g_buf2/g_buf3/g_buf4/,定义的为全局变量,在全局初始化区
2、然后我们用gcc命令运行编译程序生成可执行文件ll:
3、输入命令./ll执行该可执行程序得到以下结果:
4、通过对结果进行分析,我们可以知道:
栈存放区域是由高地址到低地址向下增长
堆存放区是由低地址到高地址像上增长
静态变量地址从高地址到低地址向下增长
函数地址是从低地址到高地址向上增长
在一个STM32程序代码中,从内存高地址到内存低地址,依次分布着栈区、堆区、全局区(静态区)、常量区、代码区,其中全局区中高地址分布着.bss段,低地址分布着.data段。
临时创建的局部变量存放在栈区。
函数调用时,其入口参数存放在栈区。
函数返回时,其返回值存放在栈区。
const定义的局部变量存放在栈区。
2、堆区(heap)
堆区用于存放程序运行中被动态分布的内存段,可增可减。
可以有malloc等函数实现动态分布内存。
有malloc函数分布的内存,必须用free进行内存释放,否则会造成内存泄漏。
3、全局区(静态区)
全局区有.bss段和.data段组成,可读可写。
4、.bss段
未初始化的全局变量存放在.bss段。
初始化为0的全局变量和初始化为0的静态变量存放在.bss段。
.bss段不占用可执行文件空间,其内容有操作系统初始化。
5、.data段
已经初始化的全局变量存放在.data段。
静态变量存放在.data段。
.data段占用可执行文件空间,其内容有程序初始化。
const定义的全局变量存放在.rodata段。
6、常量区
字符串存放在常量区。
常量区的内容不可以被修改。
7、代码区
程序执行代码存放在代码区。
字符串常量也有可能存放在代码区。
使用之前野火的串口通信模板,把main.c改为如下,详细代码:
#include "stm32f10x.h"
#include "bsp_usart.h"
char global1[16];
char global2[16];
char global3[16];
int main(void)
{
char part1[16];
char part2[16];
char part3[16];
USART_Config();
printf("part1: 0x%p\n", part1);
printf("part2: 0x%p\n", part2);
printf("part3: 0x%p\n", part3);
printf("global1: 0x%p\n", global1);
printf("global2: 0x%p\n", global2);
printf("global3: 0x%p\n", global3);
while(1)
{
}
}
8、我们在keil软件工程之中修改main.c之后运行如图:
其中图中箭头所示表示:
Code:表示程序所占用 FLASH 的大小(FLASH)。
RO-data:即 Read Only-data,表示程序定义的常量,如 const 类型(FLASH)。
RW-data:即 Read Write-data,表示已被初始化的全局变量(SRAM)
ZI-data:即 Zero Init-data,表示未被初始化的全局变量(SRAM)
*FLASH占用大小计算公式:Code+RO-data
*SRAM占用大小计算公式:RW-data+ZI-data
/
9、然后将生成hex文件,按之前的步骤烧制到stm32中,再打开串口调试助手,点击打开串口,然后点击stm32上的reset按钮就可以得到以下结果:
可以发现:
前3行为局部变量,它们储存到了栈中,地址依次减小;
后三行全局变量,它们储存到了静态区,地址依次增加。
10、再将main.c改为以下函数(定义了静态变量和指针):
#include "stm32f10x.h"
#include "bsp_usart.h"
#include
int main(void)
{
static char st1[16];
static char st2[16];
static char st3[16];
char *p1;
char *p2;
char *p3;
USART_Config();
printf("st1: 0x%p\n", st1);
printf("st2: 0x%p\n", st2);
printf("st3: 0x%p\n", st3);
p1 = (char *)malloc(sizeof(char) * 16);
p2 = (char *)malloc(sizeof(char) * 16);
p3 = (char *)malloc(sizeof(char) * 16);
printf("p1: 0x%p\n", p1);
printf("p2: 0x%p\n", p2);
printf("p3: 0x%p\n", p3);
while(1)
{
}
}
将生成的hex文件烧制到stm32中,打开串口调试助手,点击打开串口,然后点击stm32上的reset按钮就可以得到如下:
可以发现:
前三个静态变量储存到了静态区,地址依次增加;
后三个指针储存到了堆中,地址依次增加。
11、综合以上两次修改后的实验结果可以得出:
栈存放区域是由高地址到低地址向下增长;
堆存放区是由低地址到高地址像上增长;
静态变量地址从高地址到低地址向下增长;
函数地址是从低地址到高地址向上增长。
(验证了之前在Ubuntu下得出的结论)
通过本次STM32串口通信试验,让我了解了全局变量、局部变量、堆、栈等概念,以及如何烧制程序等等,学到了很多新的东西!