- 串口通信

USART串口通信

目录

USART串口通信

回顾

USART串口通信

1、通信分类与作用

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

[3、位协议层 -- RS232协议](#3、位协议层 -- RS232协议)

[4、STM32F103 中的串口外设](#4、STM32F103 中的串口外设)

5、调试串口编程

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

[-- (2)串口发送](#-- (2)串口发送)

[-- (3)串口接收](#-- (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 特点:串行 异步 全双工

  • 特点的含义

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


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


异步:发送端和接收端没有相同的时钟线。

-- 因为双方工作不在相同的频率,传输的数据会丢失,所以在异步通信时双方要规定波特率

Tip:波特率(bps)实际就是数据传输的速度
同步:发送端和接收端有相同的时钟线。- 两个设备要工作的相同的频率下
单工:一个设备只能发送或是接收 收音机

半双工:同一时间内,只能发送或是接收 对讲机

全双工:同一时间内,能发送也能接收


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

-- 串口类设备

如何识别该设备是串口类设备?

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

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

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

-- 为了保证通信,双方都会存在通信协议

保证通信(通信流程、通信协议)

双方设备通信 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 个串口设备

5、调试串口编程

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

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

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

cs 复制代码
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);			//看数据手册在哪条线上 - APB2
  • 配置IO
cs 复制代码
//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);
  • 配置外设(配置串口)

-- 使用固件库使用手册

-- 串口初始化函数原型

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

cs 复制代码
    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)串口发送

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

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

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

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

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

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

cs 复制代码
//接收

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函数了。
cs 复制代码
int fputc(int a,FILE *p)//重定向函数
{
	usart1_tx(a);
	return a;
}
-- 应用
  • main.c
cs 复制代码
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%)
cs 复制代码
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

6、中断

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

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

既要初始化中断,还要使能中断源
使能中断源,先从函数库中找相应的函数

复制例子,并更改

  • usart.c
cs 复制代码
	//使能中断源			//串口有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、判断中断是否发生

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

cs 复制代码
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
cs 复制代码
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完整代码

cs 复制代码
#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
cs 复制代码
#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
相关推荐
硅基手札18 小时前
【I2C 技术系列 05】I2C 多主仲裁与时钟同步
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
硅基手札19 小时前
【I2C 技术系列 01】I2C 电气特性与物理层
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
硅基手札1 天前
【串口技术系列文档 10】低功耗与唤醒机制
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
蓝天居士2 天前
PY32F系列MCU在OTA时App区概率性跑不起来的根因分析(5)
mcu·嵌入式
小何好运暴富开心幸福2 天前
嵌入式历史与生态
stm32·单片机·mcu
你的强来了9982 天前
WCHToolKit场景实战
网络·串口·调试·usb·ble·分析
Brilliantwxx2 天前
【STM32】 工程实战 WS2812B 全彩 LED 灯控制(DMA 驱动)
stm32·单片机·嵌入式硬件·mcu
蓝天居士2 天前
PY32F系列MCU在OTA时App区概率性跑不起来的根因分析(6)
mcu·嵌入式
圆周率智能 iMLite AI2 天前
嵌入式高尔夫地图如何压到单洞 100KB?离线图源的数据组织与压缩思路
mcu·边缘计算·嵌入式开发·rtos·离线地图·高尔夫·高尔夫地图
硅基手札3 天前
【串口技术系列文档 07】UART控制器寄存器详解
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设