面向应用学习stm32(8)-串口通信-串口收发控制单片机外设

前导:本文的目的与,意在于面向应用的学习单片机,故不会涉及太多的原理知识,例如寄存器之类的。

主要目的在于面向应用的学习单片机,学会单片机的基础用法,开发板采取野火的指南者f103。

作者大二小白,写的不好的地方轻点喷,欢迎评论区交流

全部工程代码开源在Gitee仓库

1 串口通信

串口通信是一种设备间非常常用的异步串行通行方式,其简单便捷,大部分电子设备都支持。

1.1 通信简介

通信的概念很好理解,大家之间互相对话,qq,微信在网上的消息传输,短信都是通信的一部分。本质上就是为了两个实体之间的互相交流。单片机自然也有很多需要通信的情况,通信的方法也有许多种

1.2 电子设备通信的几个概念

1.2.1 同步和异步通信

同步通信就是双方按照同一个时钟节拍工作,发送的每一个消息每一个数据都是实时传送给对方,对方实时接收的,对方如果没接收就会一直等待,直到对方接收为止。例如我们的微信通话,电话,别人接了之后我们就建立了同步通信。

异步通信就是指双方通信的频率不固定,发送方发送一个消息后,不必等待对方的接收,可以继续发送消息。

1.2.2 并行和串行

  • 串行通信指的是, 一条信息的各位数据被逐位按顺序传送,最少的情况下只需要一根传输线就可以完成任务,但传输速度较慢。串行通讯又分为异步通讯和同步通讯两种方式。在单片机中,主要使用异步通讯方式。

    串行通讯中,两个设备之间通过一对信号线进行通讯,其中一根为信号线,另外一根为信号地线,信号电流通过信号线到达目标设备,再经过信号地线返回,构成一个信号回路。

  • 并行通信指的是,用了比串行通信更多的信号线路,例如8根,16根,32根,可以更快速的传输数据,但是复杂度较高,成本较高。并且抗干扰能力较低。

1.2.3 单工,半双工 ,全双工

  • 单工
    单工就是指A只能发信号,而B只能接收信号,通信是单向的。
  • 半双工
    经典的早期的对讲机就是半双工模式,双方都可以发送和接收,但是发送的时候只能发送,接收的时候只能接收
  • 全双工
    指交换机在发送数据的同时也能够接收数据,两者同步进行,这好像我们平时打电话一样,说话的同时也能够听到对方的声音。目前的交换机都支持全双工。

1.3 串口通信相关名词

1.3.1 波特率

由于异步通信中,双方没有同步的时钟信号,所以两个设备之间需要约定好波特率,波特率也指的是串口通信的速率,也就是串口通信时每秒钟可以传输多少个二进制位,因此波特率最好是大家熟知的,约定俗成的一般会用9600或者115200。

1.3.2 起始位、数据位、奇偶校验位、停止位

image-20220509092500944

1.4 总结

串口通信时因为是异步通信,所以通信双方必须事先约定好通信参数,这些通信参数包括:波特率、数据位、校验位、停止位(串口通信中起始位定义是唯一的,所以一般不用选择)。

2 STM32 USART

2.1简介

USART 通用同步异步收发器是一个串行通信设备,可以灵活的与外部设备进行全双工数据交换。有别与USART,还有一个UART,它在USART基础上裁剪掉了同步通信功能,只有异步通信。我们平时用的串口通信基本都是 UART。

2.2框图

下面是STM32F103VET6芯片的USART引脚

这里写图片描述

USART1的时钟来源于APB2总线时钟,最大频率为72MHZ,其他4个时钟来源于APB1总线时钟,最大频率36MHZ。UART只有异步传输功能,没有SCLK、nCTS和nRTS功能引脚。

3 USART配置部分

3.1 配置分析

  • 使能时钟
  • 配置GPIO
  • 配置中断
  • 配置初始化结构体并初始化
  • 开启串口接收中断,使能

3.2 代码配置部分

查询原理图,发现usb绑定的是PA10,PA9

image-20220510114223694

复制就行啦。然后注释看一看,因为串口的配置基本是死的。

注意点

  • 波特率那一片的配置以及GPIO的模式配置
  • USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接中断
void USART_Config(int bound)
{  
  GPIO_InitTypeDef GPIO_InitStructure;
  USART_InitTypeDef USART_InitStructure;  
  NVIC_InitTypeDef NVIC_InitStructure;
    
  //使能USART1,GPIOA时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
    
  //USART1_TX   GPIOA.9
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  
  //USART1_RX	GPIOA.10
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);
    
  //Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3  
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;      //子优先级3
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;		  //IRQ通道使能
  NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
    
  //USART 初始化设置
  USART_InitStructure.USART_BaudRate = bound;//串口波特率
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
  USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
  USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
  //无硬件数据流控制
  USART_InitStructure.USART_HardwareFlowControl =  USART_HardwareFlowControl_None;
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式
  USART_Init(USART1, &USART_InitStructure); //初始化串口1
    
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接中断
  USART_Cmd(USART1, ENABLE);                    //使能串口1   
}

4 代码收发部分

4.1 串口发送

从stm32f10x_usart.h里我们可以找到函数USART_SendData,查阅手册可得这个函数的作用是按字节发送数据。那如果我们要发现字符串就要循环一个个发。

然后每次发的时候肯定要判断是不是到了结尾。有这样的一行函数

FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)

看这个英语可以得到,我们需要的第二个参数是USART_FLAG_TXE

image-20220509164600326

写出这样的函数,这样我们就可以实现向电脑数据的发送了。

void USART_SendString(int8_t *str)
{
    uint8_t index = 0;
    do
    {
        USART_SendData(USART1,str[index]);
        while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
        index++;  
    }
    while(str[index] != 0);    
}

然后我们在while(1)前面写下这个测试

		//...省略其他配置
		USART_Config(9600);
		USART_SendString("Hello world\n");
		while(1)
        {
            //...省略
        }

电脑成功接收到了数据(注意这里的串口配置要和你初始化的串口配置一样)
这个串口助手也已经上传到了Gitee仓库里,里面那个firetool的压缩包就是了
image-20220509165530902

4.2串口接收

首先复制中断服务函数,老地方startup.s里复制,然后直接复制这些代码

我们收到数据后,肯定要先清除中断标志位,基本所有的中断程序都有这个套路。然后调用USART_ReceiveData获取接收的字符,根据该字符判断接收是否结束。

肯定要规定数据的结束规则,我这里的规则就是 以感叹号结尾或者大于20个字符。否则数据永远无法接收完整。

接收完后,标志位置1,这样就可以在主函数那边写个if判断,当RXOVER==1的时候,代表接收完成,可以进行对应操作。

char USART_RXBUF[28];

uint8_t RXOVER = 0;
uint8_t RXCUNT = 0;

void USART1_IRQHandler(void)
{
	uint8_t temp;
	//判断是否接收
	if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET)
	{
        //清除中断标志位
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);		
        //获取接收的字符
		temp = USART_ReceiveData(USART1);
        //判断是不是停止字符
		if(temp == '!' || RXCUNT==20)
		{
            //字符数清零
			RXCUNT = 0;
            //结束标志置1
			RXOVER = 1; 
            //关闭中断
			USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);
		}
		else
		{
            //往接收数值中拼接数据
			USART_RXBUF[RXCUNT] = temp;
			RXCUNT++;			
		}
	}
}

例子,记得收到后要RXOVER改回0,然后重新开启接收中断

		while(1)
		{ 
			if(RXOVER==1)
			{
				//不能打印 \n 换行符
				ILI9341_DispStringLine_EN(LINE(1),USART_RXBUF);	
				USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启串口接中断
				RXOVER=0;
			}
		}

发送数据,以感叹号结尾,这时屏幕上就能显示我们发送的字符了。

image-20220509170245677

5 小实验

5.1 串口发送数据控制灯泡

5.1.1 需求

串口发送数据 LED:x(换行结尾)的数据给单片机。默认亮黄灯
或者SPEED:xxx(换行结尾)(三位数) 控制闪烁的间隔速度。默认500ms

规定x=1的时候亮红灯, x=2的时候亮绿灯, x=3的时候交替闪烁当前颜色的灯,再次接受到x=3的时候则切换模式。x=其他数值的时候灯泡熄灭

发送格式错误的时候,屏幕输出 ”ERROR“

5.1.2 思路分析

  • 利用一个变量color记录当前的颜色

  • 解析串口的前三位是否是LED:或者SPEED:,如果不是的话,屏幕打印ERROR

  • 如果是LED的话取出后面的数字,改变color变量进而改变灯的颜色

  • 如果是SPEED的话取出后面的数字,改变闪烁的speed变量

  • 将数字和标志位关联到一起

  • x=3的时候,以规定的speed闪烁当前color变量

5.1.3代码部分

解析数据

void Usart_Control()
{
			if(RXOVER==1)
			{
                //调用数据解析函数
				Data_Analysis();
				USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启串口接中断
				RXOVER=0;
			}
}
void Data_Analysis()
{
    	//如果是LED的控制数据
		if(USART_RXBUF[0]=='L' && USART_RXBUF[1]=='E' && USART_RXBUF[2]=='D'
           && USART_RXBUF[3]==':')
		{
			error_flag = 0;
			if(USART_RXBUF[4]='1')
			{	
				color = LED_RED;
			}
			else if(USART_RXBUF[4]='2')
			{	
				color = LED_GREEN;
			}
			else if(USART_RXBUF[4]='3')
			{	
				mode = !mode;
			}
			else
			{
				color = LED_OFF;
			}
		}
   		//如果是闪烁速度的控制数据
		else if(USART_RXBUF[0]=='S'&&USART_RXBUF[1]=='P'&&USART_RXBUF[2]=='E'
            &&USART_RXBUF[3]=='E'&&USART_RXBUF[4]=='D'&&USART_RXBUF[5]==':')
		{
			error_flag = 0;
			speed = (USART_RXBUF[6]-'0')*100 +
                	(USART_RXBUF[7]-'0')*10 +
                	(USART_RXBUF[8]-'0');
		}
		else
		{
            //格式错误 标志位置1
			error_flag = 1;
		}
}

LED控制部分

void LED_Show()
{
	if(mode)
	{
		LED_Color(color);
	}
	else
	{
		LED_Flashing(color,speed);
	}
}

显示部分

void LCD_Show()
{
	if(error_flag)
	{
		ILI9341_DispStringLine_EN(LINE(1),"ERROR");		
	}	
	else
	{
		ILI9341_DispStringLine_EN(LINE(1),"       ");	
	}
}

5.2 串口实现倒计时

5.2.1需求

串口发送数据格式为 xx:xx:xx (换行结尾)的消息数据。

比如 01:11:15,单片机接收到后,显示出来,然后开始倒计时1小时11分钟15s,时间到的时候,蜂鸣器响,红灯常亮。按下KEY1按键后,灯泡变绿,蜂鸣器停止。

设备默认倒计时为五分钟,按下KEY1后恢复倒计时五分钟。

5.2.2思路分析

  • 利用一个模式int mode标志位:用来控制倒计时到了,和未到的时候。
  • 时分秒全为0的时候标志位置0,亮红灯,响蜂鸣器
  • 按键按下时,亮绿灯,关蜂鸣器,时分秒置为默认,标志位置1
  • systick实现秒的控制,只有标志位为1的时候才可以计时
  • 串口接收到数据的时候,亮绿灯,关蜂鸣器,时分秒设置为串口发送的,标志位置1

5.2.3代码

倒计时部分

void Time_Control()
{
    //时间到
	if(minute==0 && second==0 && hour==0)
	{
		LED_Color(LED_RED);//红灯
		Beep_Control(BEEP_ON);//蜂鸣器响
		mode=0;
	}
	if(second==0 && minute>0)
	{
		second = 59;
		minute--;
	}
    //注意这个hour>0 细节问题
	if(minute==0 && hour>0)
	{
		second = 59;
		hour--;
	}
}

按键控制部分

void KEY_Control()
{
	char key = KEY_Scan();
	if(key=='1')
	{
		hour = 0;
		second = 0;
		minute = 5;
		mode = 1;
		Beep_Control(BEEP_OFF);
		LED_Color(LED_GREEN);
	}
}

Usart控制部分

这里涉及到字符转int类型的数字,ascii码值相关的。详情可以查阅ascii码表。

大家只要记住就像我代码部分这样转换就行。然后我们十位数部分*10,个位数直接加上去。

取值就是从接收数组上取值

void Usart_Control()
{
			if(RXOVER==1)
			{
				hour = (USART_RXBUF[0] - '0')*10+(USART_RXBUF[1] - '0');
				minute = (USART_RXBUF[3] - '0')*10+(USART_RXBUF[4] - '0');
				second = (USART_RXBUF[6] - '0')*10+(USART_RXBUF[7] - '0');
				mode=1;
				Beep_Control(BEEP_OFF);
				LED_Color(LED_GREEN);
				USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启串口接中断
				RXOVER=0;
			}
}

LCD显示

void LCD_Show()
{
	sprintf(dispBuff,"%2d:%2d:%2d", hour,minute,second);				
	ILI9341_DispStringLine_EN(LINE(1),dispBuff);			
}

SysTick中断

extern int second;
extern int mode;
int count=0;
void SysTick_Handler(void)
{
	if(++count==1000 && mode==1)
	{
		second--;
		count=0;
	}	
	TimeDelay--;
}

主函数

		//...省略配置
		while(1)
		{ 
			Usart_Control();
			LCD_Show();
			Time_Control();
			KEY_Control();
		}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这里煤球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值