首页 > 其他分享 >- 串口通信

- 串口通信

时间:2024-09-29 15:47:53浏览次数:8  
标签:USART -- 通信 InitStructure 串口 GPIO usart1

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

标签:USART,--,通信,InitStructure,串口,GPIO,usart1
From: https://blog.csdn.net/m0_71813740/article/details/142528190

相关文章

  • 使用MessagePipe实现进程间通信
    使用MessagePipe实现进程间通信 1、MessagePipe介绍可以用于.NET和Unity上面的高性能的内存/分布式消息传递管道。适用于发布/订阅模式、CQRS的中介模式、Prism中的EventAggregator、IPC(进程间通信)-RPC等。支持:依赖注入过滤器管道更好的事件同步/异步带键值的/无键值......
  • CPU与UFS设备的通信
    CPU与UFS(UniversalFlashStorage)设备通信的过程涉及多个层次,硬件、固件以及软件协议相互配合。1.硬件层面CPU与UFS设备通过硬件总线进行通信,具体的总线通常是MIPI(MobileIndustryProcessorInterface)*联盟定义的*MIPIM-PHY。UFS协议在硬件上使用MIPIM-PHY作为物理层,提供高......
  • 【射频通信电子线路第六讲】射频信号与调制包括调幅和部分调频的内容
    一、调制(Modulation)与解调(Demodulation)1、相关概念调制是指使一个信号(如光信号、高频电磁振荡等)的某些参数(振幅、频率和相位)按照另一个欲传输的信号的特点变化的过程。解调是指调制的逆过程,作用是从已调信号中取出原来的调制信号。载波:未受调制的周期性振荡信号基带信号:要......
  • 物联网系统中基于IIC通信的数字温度传感器测温方案
    01物联网系统中为什么要使用数字式温度传感器芯片物联网系统中使用数字式温度传感器芯片的原因主要有以下几点:高精度与稳定性高精度测量:数字式温度传感器芯片,如DS18B20,采用芯片集成技术,能够有效抑制外界不同程度的干扰,从而提供高精度的温度测量。这对于物联网系统来说至关......
  • 嵌入式学习——进程间通信方式(2)—— 信号
    一、基本概念什么是信号:由进程或系统发出的,用来通知发生了某个事件,希望接收方进行响应。    信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。正如我们所了解的中断服务函数一样,在中断发......
  • 嵌入式常用硬件电路设计及分析 — 通信篇
    目录一、简介二、RS232通信1、硬件电路设计2、硬件电路设计分析(1)常规电路(2)EMC设计使用注意事项3、DB9接头定义三、RS485通信1、硬件电路设计2、硬件电路设计分析(1)自动收发电路(2)上下拉电阻注意事项(3)TVS管作用(4)末端匹配电阻作用四、USB转串口(1)常规电路设计(2)ISP......
  • 三大硬核方式揭秘:Java如何与底层硬件和工业设备轻松通信!
    大家好,我是V哥,程序员聊天真是三句不到离不开技术啊,这不前两天跟一个哥们吃饭,他是我好多年前的学员了,一直保持着联系,现在都李总了,在做工业互联网相关的项目,真是只要Java学得好,能干一辈子,卷死的是那些半吊子。感谢李总给我分享了工业互联网项目的事情,收获很多,今天的内容来聊一聊......
  • 大模型驱动,开启智能通信时代
    从2021年,我们介绍了5G消息如何赋能各行各业;2022年,我们探讨了云原生驱动的融合通信;去年,我们提出了融合通信的未来可期,并介绍了云通信在各种场景下的解决方案以及与AI的结合。今年,我们继续探讨基于大模型驱动的融合通信,分享我们在这一领域的思考、探索和实践。规模化使用云通......
  • STM32-使用串口空闲中断,实现串口不定长接收
    示例代码是直接操作寄存器的核心思路很简单:接收到消息后,打开串口总线空闲中断总线进入空闲中断后,关闭空闲中断代码如下:``点击查看代码#include"myusart2.h"#include"string.h"#include"tool.h"charusart2_rec_buff[usart2_rec_len];u8usart2_rec_flag=0;......
  • python接口串口数据
    importtimeimportserialdeftest_receive():#配置串口参数port='/dev/ttyUSB0'#根据你的设备更改端口号baud_rate=9600#波特率bytesize=serial.EIGHTBITS#数据位parity=serial.PARITY_NONE#校验位stop_bits=serial.......