- 串口通信

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
相关推荐
电子科技圈1 小时前
IAR与鸿轩科技共同推进汽车未来
科技·嵌入式硬件·mcu·汽车
电子科技圈7 小时前
XMOS携手合作伙伴晓龙国际联合推出集成了ASRC等功能的多通道音频板
科技·嵌入式硬件·mcu·物联网·音视频·iot
7yewh9 小时前
嵌入式硬件杂谈(四)-高速板PCB设计 高速信号全面讲解 蛇形线 等长线 差分对 阻抗对
驱动开发·嵌入式硬件·mcu·物联网·硬件工程·pcb工艺·精益工程
linux_carlos1 天前
#lwIP 的 Raw API 使用指南
stm32·单片机·mcu·物联网·rtdbs
liu_endong1 天前
杰发科技AC7840——EEP中RAM的配置
mcu·杰发科技·autochips·车规芯片
King~30+1 天前
STM32--中断使用(超详细!)
stm32·单片机·嵌入式硬件·mcu
网易独家音乐人Mike Zhou1 天前
【Linux驱动开发】irq中断配置API及中断应用 阻塞休眠和非阻塞的驱动操作
linux·c语言·驱动开发·stm32·单片机·mcu·iot
7yewh1 天前
嵌入式硬件电子电路设计(七)稳压二极管-齐纳二极管-齐纳击穿全面详解
stm32·嵌入式硬件·mcu·物联网·硬件架构·硬件工程·pcb工艺
7yewh2 天前
嵌入式硬件实战基础篇(二)-稳定输出3.3V的太阳能电池-无限充放电
stm32·嵌入式硬件·mcu·物联网·硬件架构·硬件工程·pcb工艺
7yewh2 天前
嵌入式硬件杂谈(三)-高速PCB入门,什么是阻抗匹配?
嵌入式硬件·mcu·物联网·硬件架构·硬件工程·pcb工艺·精益工程