当前位置: 首页 > article >正文

- 串口通信

USART串口通信

目录

USART串口通信

回顾

USART串口通信

1、通信分类与作用

2、串口通信的相关参数(重点)

3、位协议层 -- RS232协议

4、STM32F103 中的串口外设

5、调试串口编程

-- (1)串口初始化:时钟、IO、外设

-- (2)串口发送

-- (3)串口接收

-- 补充

-- 应用

6、中断

usart.c完整代码


回顾

定时器的构成:
计数器:计数
时钟:为计数器提供计数频率
重装栽植:计数的最大值
-- 计数器从 0 开始计数(向上计数),一直到计数值等于重装载值,计数值会自动清零。

阻塞:程序里使用了延时函数
实现非阻塞的目的:提高 cpu 利用率 每个任务之间互不干扰

-- 基础阶段应该掌握的知识:

  • 单片机的运行流程(从上往下,顺序执行)
  • 单片机的基本知识(GPIO 中断)
  • 代码:代码的基本编写(代码框架)

  • 从这章开始真正进入单片机的开发阶段。

-- 开发阶段:

  • 首先要掌握的就是开发方法(或者是开发的流程)。

  • 代码会偏向应用函数。

  • 相同类型的模块(通信方式(接口)相同的模块),开发是一致的

通信方式(其实就是接口是一致的,学习单片机其实就是学习它的各种接口),如IO,TIM,USART,I2C,SPI,ADC,DAC,PWM等。

USART串口通信

  • 今天先学习串口类设备如何进行开发

1、通信分类与作用

有线: 485 232 can
无线:WiFi 蓝牙 zigbee 4G
-- 这些通信都用来用作设备与设备之间的通信

SPI IIC USART -- 单片机和模块之间的通信


  • 这章讲的usart 特点:串行 异步 全双工

  • 特点的含义

串行:串行数据传输时,数据是一一位地在通信线上传输的 

alt text

并行:并行通信传输中有多个数据位,同时在两个设备之间传输。 

alt text

异步:发送端和接收端没有相同的时钟线。
-- 因为双方工作不在相同的频率,传输的数据会丢失,所以在异步通信时双方要规定波特率
Tip:波特率(bps)实际就是数据传输的速度

同步:发送端和接收端有相同的时钟线。- 两个设备要工作的相同的频率下

单工:一个设备只能发送或是接收 收音机
半双工:同一时间内,只能发送或是接收 对讲机
全双工:同一时间内,能发送也能接收


2、串口通信的相关参数(重点)

-- 串口类设备
如何识别该设备是串口类设备?
-- 从物理硬件接口(物理层)来看(一定要有下面图这几根线(引脚接口)TX,RX,GND),有这些引脚就是串口类设备) 

alt text

 -- 怎么接线也要注意一下

  • 怎么确保对方收到了数据?

-- 为了保证通信,双方都会存在通信协议
保证通信(通信流程、通信协议)
双方设备通信 A 和 B,设备 A 向设备 B 传输 1 字节内容。
1、 设备 A 如何确认设备 B 是否收到了信息?
设备 B 向设备 A 发送应答信号。
2、 设备 B 如何确认收到的数据是正确的?
校验
-- 在串口通信中,没有操作1,波特率在某种程度上解决了操作1的问题,但没有很好的解决。
-- 对于串口通信,保证了操作2,-- 用RS232协议

3、位协议层 -- RS232协议

  • 协议:数据传输格式

  • 数据传输将数据分成了4部分,分别为起始位,数据位,奇偶校验位,停止位

RS232协议将数据分为1个起始位,8个数据位,1个奇偶校验位,1个停止位: 1 8 0 1

校验位:奇偶校验 CRC校验
奇校验:数据位+奇偶检验位 里面的1奇数个
偶校验:数据位+奇偶检验位 里面的1偶数个
01010101 1 如果是奇校验,在后面补个1
01010101 0 如果是偶校验,在后面补个0

  • 设备 A 向设备 B 发送 8000 位数据 双方通信波特率 9600, 问:数据传输完毕,花费多长时间?

串口一次发送 8 位数据,需要发送 1000 次, 串口发送一次数据花费 10/9600s
(10/9600)*1000 = 100/96 S = 25/24s

4、STM32F103 中的串口外设

  • STM32F103ZET6:一共有 5 个 USART1 USART2 USART3 UART4 UART5
  • 单片机它可以同时驱动 5 个串口设备

alt text

5、调试串口编程

  • 通信双方 单片机和ch340

alt text

  • 串口通信编程:串口初始化,串口发送,串口接收
-- (1)串口初始化:时钟、IO、外设
  • 先看数据手册中该外设在哪条线上 

    alt text

  • 看原理图知道串口连接的引脚 

    alt text

  • 然后配置相应的时钟(A端口的时钟和USART1的时钟(外设自身也有时钟))

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);			//看数据手册在哪条线上 - APB2
  • 配置IO
//IO PA9/PA10
	GPIO_InitTypeDef GPIO_InitStructure = {0};						//定义结构体变量,并且将结构体变量赋初值
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; 						//引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;			//速度
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//PA9 复用推挽
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; 							//引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;		//浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);
  • 配置外设(配置串口)

-- 使用固件库使用手册 

alt text

-- 串口初始化函数原型 

alt text

-- 找到例子,复制到工程中(在例子的基础上更改) 

alt text

    USART_InitTypeDef USART_InitStructure = {0};
	USART_InitStructure.USART_BaudRate = 9600;   					 			//波特率  常用的是4800 9600  115200
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;	//数据位长度
	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_Tx | USART_Mode_Rx;//模式
	USART_Init(USART1, &USART_InitStructure);
	
	USART_Cmd(USART1,ENABLE);//开启串口    //使能或者失能 USART 外设(一般外设都要写这个)
-- (2)串口发送

-- 在固件库使用手册中找到相应函数 

alt text

-- 如果只写发送数据的语句,那么怎么知道上次的数据是否已经发送完了,如果上次的数据没有发送完的情况下,再次发送数据的话,就会覆盖上次的数据,所以需要判断上次的数据是否发送完成。

-- 判断数据是否发送完成 -- 采用状态寄存器 

alt text

-- 发送数据空标志位为1,表示数据发送完成,反之标志位为0,上一次的数据还没有发送完成,所以需要等待,直到标志位为1,表示数据发送完成,再发送数据。 

alt text

//发送
void usart1_tx(uint8_t data)//参数就是要发送的数据
{
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE )== RESET){}
		//上次的数据发送完成,为空表示上个数据已经发送完成
		//等待的时候是还没有发送,一旦发送完成,就变成1了
	USART_SendData(USART1, data);
}
-- (3)串口接收

alt text

-- 接收数据寄存器非空标志位为1,表示数据接收完成,反之标志位为0,上一次的数据还没有接收完成,所以需要等待,直到标志位为1,表示数据接收完成,再接收数据。

alt text

//接收

uint8_t usart1_rx(void)
{
	while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE )== RESET){} 			//接收数据寄存器非空标志位
	uint8_t RxData = USART_ReceiveData(USART1);
	return RxData;
}
-- 补充
  • 将printf输出的数据直接输出到串口上,将它写在usart.c中,然后在usart.h中声明。这样就可以直接使用printf函数了。
int fputc(int a,FILE *p)//重定向函数
{
	usart1_tx(a);
	return a;
}
-- 应用
  • main.c
int main()
{
	usart_init();
	
	uint8_t str[] = "123456789";
	
	uint8_t len = strlen((char *)str);
	
	for(uint8_t i=0;i<len;i++)
		usart1_tx(str[i]);
	//usart1_tx(0x1);
	
	uint8_t data = usart1_rx();//可以协助我们调试代码,接收到了,m
	printf("data:%d\r\n",data);//这里的换行是\r\n					//这里是发送到串口。可以根据是否发送到串口来判断程序是否正常执行
    }

-- 这里的printf可以用来调试程序,前面已经将printf重定向到串口了,所以可以直接使用printf函数,将数据发送到串口上。在想知道一个模块是否执行时,就可以写一个printf,看是否发送数据来看是否执行这个模块。


  • 使用串口助手向单片机发送数据,控制led状态。例如:发送“1111”,4个led灯全亮;发送“1010”,13灯亮,24灯灭;发送“0000”,4个led灯全灭。(90%)
int main()
{
	led_init();
	
	uint8_t str[] = "123456789";
	
	uint8_t len = strlen((char *)str);
	
	for(uint8_t i=0;i<len;i++)
		usart1_tx(str[i]);

	
	while(1)
	{
		uint8_t data[20]={0};
		for(uint8_t i=0;i<4;i++)
		data[i] = usart1_rx();
		if(!strcmp(data,"1111"))
		{
			LED1(1);LED2(1);LED3(1);LED4(1);
		}else if(!strcmp(data,"1010")){
			LED1(1);LED2(0);LED3(1);LED4(0);
		}else if(!strcmp(data,"0000")){
			LED1(0);LED2(0);LED3(0);LED4(0);
		}
		printf("data:%s\r\n",data);//这里的换行是\r\n					//这里是发送到串口。可以根据是否发送到串口来判断程序是否正常执行
    }
}
	
  • 注意这里的换行符必须是\r\n

alt text

alt text


6、中断

-- 因为接收函数中写了,如果读不到数据们将会阻塞等待,会影响后面的程序 

alt text

alt text

  • 所以要采用中断的方式来接收数据,那么初始化函数就要加上中断的初始化

既要初始化中断,还要使能中断源

使能中断源,先从函数库中找相应的函数

alt text

 

alt text

复制例子,并更改

  • usart.c
	//使能中断源			//串口有10个,用哪一个就要开哪一个中断源
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	//中断
	NVIC_InitTypeDef NVIC_InitStructure = {0}; 
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能(使能哪个中断通道,就要写哪个(必须写)中断服务函数)
	NVIC_Init(&NVIC_InitStructure);

  • 中断服务函数的内容

1、判断中断是否发生 

alt text

 2、处理中断 3、清理中断 

alt text

uint8_t usart1_buff[10] = {0};//这些参数还要再usart.h中声明
uint8_t usart1_len = 0;

void USART1_IRQHandler(void)//串口收到1字节数据,中断就会被触发一次
{
	//判断接收中断是否发生
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		//处理中断:保存数据
		usart1_buff[usart1_len++] = USART_ReceiveData(USART1);
		usart1_len %= 10;//对10求余,他就一直会小于10
		//清理终端
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}
  • main.c
if(usart1_len == 4)//如果接收到的数据为4位时
	{
		if(usart1_buff[0] == '0')
			LED1(0);
		else if(usart1_buff[0] == '1')
			LED1(1);
		
		if(usart1_buff[1] == '0')
			LED2(0);
		else if(usart1_buff[1] == '1')
			LED2(1);
		
		if(usart1_buff[2] == '0')
			LED3(0);
		else if(usart1_buff[2] == '1')
			LED3(1);
		
		if(usart1_buff[3] == '0')
			LED4(0);
		else if(usart1_buff[3] == '1')
			LED4(1);
	
		memset(usart1_buff,0,10);//数据处理完毕之后,将这次数据全部清0
		usart1_len = 0;
	}
  • 注:数据处理完毕后,记得清理。

usart.c完整代码

#include "usart.h"

void usart_init(void)
{
	//时钟 A端口  USART1(外设自身也有时钟)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);			//看数据手册在哪条线上 - APB2
	
	//IO PA9/PA10
	GPIO_InitTypeDef GPIO_InitStructure = {0};						//定义结构体变量,并且将结构体变量赋初值
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; 						//引脚
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;			//速度
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//PA9 复用推挽
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; 							//引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;		//浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure = {0};
	USART_InitStructure.USART_BaudRate = 9600;   					 			//波特率  常用的是4800 9600  115200
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;	//数据位长度
	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_Tx | USART_Mode_Rx;//模式
	USART_Init(USART1, &USART_InitStructure);
	
	USART_Cmd(USART1,ENABLE);//开启串口    //使能或者失能 USART 外设(一般外设都要写这个)
	
	
	//使能中断源			//串口有10个,用哪一个就要开哪一个中断源
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	
	//中断
	NVIC_InitTypeDef NVIC_InitStructure = {0}; 
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能(使能哪个中断通道,就要写哪个(必须写)中断服务函数)
	NVIC_Init(&NVIC_InitStructure);
	
}



//应用函数

//发送
void usart1_tx(uint8_t data)//参数就是要发送的数据
{
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE )== RESET){}
		//上次的数据发送完成,为空表示上个数据已经发送完成
		//等待的时候是还没有发送,一旦发送完成,就变成1了
	USART_SendData(USART1, data);
}

//接收

uint8_t usart1_rx(void)
{
	while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET){} 			//接收数据寄存器非空标志位
	uint8_t RxData = USART_ReceiveData(USART1);
	return RxData;
}

int fputc(int a,FILE *p)//重定向函数
{
	usart1_tx(a);
	return a;
}

uint8_t usart1_buff[10] = {0};
uint8_t usart1_len = 0;

void USART1_IRQHandler(void)//串口收到1字节数据,中断就会被触发一次
{
	//判断接收中断是否发生
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		//处理中断:保存数据
		usart1_buff[usart1_len++] = USART_ReceiveData(USART1);
		usart1_len %= 10;//对10求余,他就一直会小于10
		//清理终端
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}

  • usart.h
#ifndef _USART_H_
#define _USART_H_

#include "stdio.h"
#include "STM32f10x.h"

void usart_init(void);
void usart1_tx(uint8_t data);
uint8_t usart1_rx(void);

int fputc(int a,FILE *p);

extern uint8_t usart1_buff[10];
extern uint8_t usart1_len;


#endif
s

```s

http://www.kler.cn/news/324606.html

相关文章:

  • 多线程篇八
  • SpringBoot集成阿里easyexcel(二)Excel监听以及常用工具类
  • 阴影的基本原理
  • 梳理一下C语言中的格式说明符
  • uniapp js判断key是否在json中?
  • ArcgisEngine开发中,Ifeatureclass.Addfield 报错0x80040655处理方法
  • 0基础学习CSS(六)字体
  • python-list-comprehension-three-way-partitioning-array-around-given-range
  • iText 5 通过创建 Document 对象,并使用 PdfWriter 将内容写入 PDF 文件
  • ubuntu重新安装clickhouse
  • 前端面试题(九)
  • 【Mybatis】常见面试题汇总 共56题
  • 复试经验分享《一、问答题自测》(408、相关前沿技术)
  • 在Kali Linux VNC服务器上安装RDP服务
  • Android使用RecyclerView仿美团分类界面
  • 【JavaEE】——阻塞队列,生产消费者模型(较难)
  • BACnet协议-(基于ISO 8802-3 UDP)(2)
  • 【系统方案】智慧城市大数据平台建设方案(Word)
  • 【小程序websocket最佳实践,有心跳和断线重连】
  • JD面试题
  • huggingface实现中文文本分类
  • Gitee基本指令操作
  • 若依生成主子表
  • 前端框架:选择的艺术
  • IP地址不足
  • Python电能质量扰动信号分类(五)基于CNN-Transformer的一维信号分类模型
  • 版本发布 | IvorySQL 3.4 发版
  • 鸿蒙开发(NEXT/API 12)【硬件(获取出行业务事件信息)】车载系统
  • Java解析Excel文件
  • 校企合作 | 宝兰德与西安航空职业技术学院共筑智慧教育新高地