- 串口通信
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 特点:串行 异步 全双工
- 特点的含义
串行:串行数据传输时,数据是一位一位地在通信线上传输的
并行:并行通信传输中有多个数据位,同时在两个设备之间传输。
异步:发送端和接收端没有相同的时钟线。
-- 因为双方工作不在相同的频率,传输的数据会丢失,所以在异步通信时双方要规定波特率
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的时钟(外设自身也有时钟))
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);
- 配置外设(配置串口)
-- 使用固件库使用手册
-- 串口初始化函数原型
-- 找到例子,复制到工程中(在例子的基础上更改)
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,表示数据发送完成,再发送数据。
//发送
void usart1_tx(uint8_t data)//参数就是要发送的数据
{
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE )== RESET){}
//上次的数据发送完成,为空表示上个数据已经发送完成
//等待的时候是还没有发送,一旦发送完成,就变成1了
USART_SendData(USART1, data);
}
-- (3)串口接收
-- 接收数据寄存器非空标志位为1,表示数据接收完成,反之标志位为0,上一次的数据还没有接收完成,所以需要等待,直到标志位为1,表示数据接收完成,再接收数据。
//接收
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
6、中断
-- 因为接收函数中写了,如果读不到数据们将会阻塞等待,会影响后面的程序
- 所以要采用中断的方式来接收数据,那么初始化函数就要加上中断的初始化
既要初始化中断,还要使能中断源
使能中断源,先从函数库中找相应的函数
复制例子,并更改
- 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、判断中断是否发生
2、处理中断 3、清理中断
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