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的全局变量

参考博客

c语言中局部变量,全局变量.
stm32串口通信相关实验
C语言中程序内存的各种变量存储区的理解

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
串口是计算机上一种非常通用设备通信的协议(不要与通用串行总线Universal Serial Bus或者USB混淆)。大多数计算机包含两个基于RS232的串口串口同时也是仪器仪表设备通用的通信协议;很多GPIB兼容的设备也带有RS- 232口。同时,串口通信协议也可以用于获取远程采集设备的数据。 串口通信的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总常不得超过20米,并且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。 典型地,串口用于ASCII码字符的传输。通信使用3根线完成:(1)地线,(2)发送,(3)接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但是不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通行的端口,这些参数必须匹配: a,波特率:这是一个衡量通信速度的参数。它表示每秒钟传送的bit的个数。例如300波特表示每秒钟发送300个bit。当我们提到时钟周期时,我们就是指波特率例如如果协议需要4800波特率,那么时钟是4800Hz。这意味着串口通信在数据线上的采样率为4800Hz。通常电话线的波特率为 14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是 GPIB设备的通信。 b,数据位:这是衡量通信实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语 “包”指任何通信的情况。 c,停止位:用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。 d,奇偶校验位:在串口通信一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位位1,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值