STM32串口通信以及C语言程序在Keil中针对stm32系统进行编程
基于寄存器与基于固件库的stm32 LED流水灯例子的编程方式有什么差异。
1.从两个使用过的角度来讲:
使用固件库,目前比较多的例程是使用固件库编写的。官方的例子也都采用固件库方式。特点就是简单,易于理解,资料多。如果你没有CortexM系列内核的开发基础,建议从固件库开始玩起。等有一定基础,或是特别需要时再用寄存器。
使用寄存器,想要深入理解CortexM3内核或是需要为了获得更好的可移植性,学习寄存器编程会比较有帮助。但是从专业的角度上看,寄存器更贴近底层,对外设的工作原理和运行机理会有更深的理解。
2.从直观角度来讲:
(1)寄存器–比较直观的感觉就是,寄存器版式直接对内部寄存器进行操作,需要我们对寄存器非常熟悉
(2)库函数–是用ST提供的库函数开发,有函数的集合,不需要与寄存器直接打教导,提供用户函数调用的API
3.从实际操作来讲:
(1)寄存器
下列操作语句为寄存器的特点:
GPIOB->CRL&=0XFFOFEFFE;
GPIOB->CRL1=0X00300000;
(2)库函数:
两者对照来看,库函数比寄存器多了FWLIB里面的函数,这就是上面提到过的,ST提供的库函数,给用户提供函数的了API接口。例如对函数库的调用如下:
GPIO SetBits(GPIOB, GPIO_Pin_5);
GPIO ResetBits(GPLOB, GPIO_Pin_5);
STM32的USART窗口通讯程序
1.配置库函数
(这些都是厂家配置好了的,不需要我们在进行配置操作)
2.部分函数介绍
本次实验主要运用的主要是SYSTEM下的串口部分:usart_init函数:
然后比较主要的函数就是中断函数USART1_IRQHandler,这里不做介绍,可查询资料。
3.相关硬件
这里使用的是:
(1)正点原子stm32开发板
(2)USB转TTL
(3)J-Link下载器
4.实操
做一些初始化操作:设置NVIC中断 分组2:2抢占优先级,2位响应优先级。之后初始化串口,根据要求串口初始化位115200,然后初始化按键,然后初始化LED,作用就是显示程序在板子上是否正常运行。
之后进入函数的主体:先是给上位机发送Hello Windows,由于一直发送太快了,所以就做了一个延时才发送;为了方便控制发送与停止,我设置了标记变量flag,如果发送停止标记,那么flag置1。我把Hello Windows放置在一个数组中,而结束标志stop stm32放在另一个数组中。
发送由下代码:
USART_SendData(USART1,p[t]);//向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
由接收的话可以由USART_RX_STA来得知,测得得出数据的长度,先在上位机上显示,然后比较是否为结束指令,如果是,就改变标记。
这样,就能成功实现,1、发送给上位机Hello Windows 2、上位机发送stop stm32就停止。
5.效果演示
6.程序代码:
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "beep.h"
int main(void)
{
u16 t;
u16 len;
u16 times=0;
char *p=" Hello Windows";
u8 *q="stop stm32";
u8 flag=0; //标志如果是1的话,停止发送数据
BEEP_Init(); //初始化蜂鸣器不响
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
KEY_Init(); //初始化与按键连接的硬件接口
while(1)
{
//给上位机连续发送Hello Windows
if(!flag)
{
times++;
if(times%100==0)
{
len=14;
for(t=0;t<len;t++)
{
USART_SendData(USART1,p[t]);//向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
}
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}
if(times%30==0)LED0=!LED0;//闪烁LED,提示系统正在运行.
delay_ms(10);
}
//接收上位机的发送,并判断是否停止
if(USART_RX_STA&0x8000)
{
//显示消息
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n收到消息为:\n\r");
for(t=0;t<len;t++)
{
printf("%c",USART_RX_BUF[t]);//接收得到的数据
}
printf("\r\n");//插入换行
//判断是否停止发送
for(t=0;t<10;t++)
if(q[t]!=USART_RX_BUF[t])//如果出现不等,那么就继续发送
{
flag=0;
break;
}
if(t==10)//如果都相等,那么停止发送
{
flag=1;
printf("已收到停止,现在停止发送!");
break;
}
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}
}
}
7.总结
通过对STM32串口通信的实验,我学习到很多stm32串口通信的操作以及知识。对于实体班子的操作是一件很有趣的事情,这个过程熟悉了函数库,直接通过调函数来实现十分方便。
C语言程序里全局变量、局部变量、堆、栈等概念
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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;
}
该代码定义了全局变量并输出它们的地址
再输入:
#include <stdio.h>
#include <stdlib.h>
//定义全局变量
int init_global_a = 1;
int uninit_global_a;
static int inits_global_b = 2;
static int uninits_global_b;
void output(int a)
{
printf("hello");
printf("%d",a);
printf("\n");
}
int main( )
{
//定义局部变量
int a=2;
static int inits_local_c=2, uninits_local_c;
int init_local_d = 1;
output(a);
char *p;
char str[10] = "lyy";
//定义常量字符串
char *var1 = "1234567890";
char *var2 = "qwertyuiop";
//动态分配
int *p1=malloc(4);
int *p2=malloc(4);
//释放
free(p1);
free(p2);
printf("栈区-变量地址\n");
printf(" a:%p\n", &a);
printf(" init_local_d:%p\n", &init_local_d);
printf(" p:%p\n", &p);
printf(" str:%p\n", str);
printf("\n堆区-动态申请地址\n");
printf(" %p\n", p1);
printf(" %p\n", p2);
printf("\n全局区-全局变量和静态变量\n");
printf("\n.bss段\n");
printf("全局外部无初值 uninit_global_a:%p\n", &uninit_global_a);
printf("静态外部无初值 uninits_global_b:%p\n", &uninits_global_b);
printf("静态内部无初值 uninits_local_c:%p\n", &uninits_local_c);
printf("\n.data段\n");
printf("全局外部有初值 init_global_a:%p\n", &init_global_a);
printf("静态外部有初值 inits_global_b:%p\n", &inits_global_b);
printf("静态内部有初值 inits_local_c:%p\n", &inits_local_c);
printf("\n文字常量区\n");
printf("文字常量地址 :%p\n",var1);
printf("文字常量地址 :%p\n",var2);
printf("\n代码区\n");
printf("程序区地址 :%p\n",&main);
printf("函数地址 :%p\n",&output);
return 0;
}
通过运行结果可以发现,Ubuntu在栈区和堆区的地址值都是从上到下依次增大的
stm32的堆、栈、全局变量的分配地址
之前串口通信模板,把main.c改为如下:
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "beep.h"
static unsigned int val1 = 1; //data段
unsigned int val2 = 1; //初始化的全局变量data段
unsigned int val3 ; //未初始的在bsss段
const unsigned int val4 = 1; //常量在rodata段,只读
unsigned char Demo(unsigned int num)
{
char var; //栈区,123456,存放在常量区
unsigned int num1=1; //栈区
static unsigned int num2=0; //,data段
const unsigned int num3 =7; //栈区
printf("val1: 0x%x\r\n",&val1);
printf("val2: 0x%x\r\n",&val2);
printf("val3: 0x%x\r\n",&val3);
printf("val4: 0x%x\r\n",&val4);
printf("var: 0x%x\r\n",&var);
printf("num1: 0x%x\r\n",&num1);
printf("num2 0x%x\r\n",&num2);
printf("num3: 0x%x\r\n",&num3);
return 1;
}
int main(void)
{
unsigned int num=0;
BEEP_Init(); //初始化蜂鸣器不响
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
num = Demo(num); //返回值存放在栈区
}
经过keil编译
存放了Code、RO-data、RW-data、ZI-data四个代码段的大小。其中Code就是代码占用大小,RO-data是只读常量、RW-data是已初始化的可读可写变量,ZI-data是未初始化的可读可写变量。
生成hex文件,录入板子,经过串口发送后得到变量地址,打开上位机显示后如下:
查看STM32地址分配:
ROM的地址分配是从0x8000000开始,整个大小为0x80000,这个部分用于存放代码区和文字常量区。RAM的地址分配是从0x20000000开始,其大小是0x10000,这个区域用来存放栈、堆、全局区(.bss段、.data段)。与代码结果显示进行对比,也可以看出对应得部分得地址与设置的是相对应的。
结合来看,可以大概看出栈在顶层(地址最大),然后依次是堆,静态区。对比以下地址分配图,大致符合。
Keil下Code、RO-data、RW-data、ZI-data 这几个段
1.Code是存储程序代码的;
2.RO-data是存储const常量和指令
3.RW-data是存储初始化值不为0的全局变量
4.ZI-data是存储未初始化的全局变量或初始化值为0的全局变量