一、串口的介绍
1.什么是串口通信
串口通信是一种简单的通信协议,通信协议就是双方在通信过程中需要共同遵守的约定,数据的发送方按照某种协议把数据调制成某种可以传输的信号,接收方就可以使用相同的协议从信号中解析出数据,比如说人的语言就可以看作是一种通信协议,说话的人会把想要说的话调制在声波中进行传输,收听的一方就会从声波上解析出这一句话,这个过程就使用了“语言”这个协议,只有当双方都掌握这种语言的时候,交谈才能成功,不然就是鸡对鸭讲。
串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信
单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力
2、串口传输一个字节过程
串口一般由三根线组成,因为我们是通过高低电平来传输信号的,而两根线才能够形成电势差,我们定义高电平表示逻辑1,低电平表示逻辑0,还有一根线接单片机的GND相连
注意单片机与电脑的TX RX线要交替连接,一般使用USB转TTL模块来将单片机与电脑连接
(1)、首先在空闲状态下,也就是没有数据传输的时候,传输线上的电平是高电平,表示逻辑1,当发送端需要发送数据的时候,就会将电平拉低,作为起始信号,这是,接收端检测到了电平的高低变化,就会做好接收准备,起始信号会占用一个位的时间,称为起始位
(2)、之后,进入数据传输阶段,发送端会将字节每一位依次发出,发送方式就是让每一位对应的电平持续一段时间,接收端就会不断的对传输线上的电平信号进行采样,判断发哦四年方发出的是0还是1
(3)、当完成了数据的传输之后,进入检验阶段,接下来这一个位叫做校验位,可以对传输的正确与否进行校验,检验位分为奇校验和偶检验,若配置位奇检验,那么发送出来的数据位和检验位里面,逻辑1的个数是奇数,偶检验则为偶数,如果接收端发现不是奇数(偶数)的话,说明数据传输过程中发生了错误。
(4)、最后是停止位,发送方将电平拉高,表示传输完成,可配置成1位或2位,停止位长的话可以让传输更加稳定,但传输数据的速度也就慢了一点。
以上就是串口通信时序介绍,下面是时序图
此外还需配置一个很重要的参数,波特率,单位是bit/s,表示每秒可以传输多少个这样的位,典型的波特率配置是9600以及他的整数倍频和分频,很明显,波特率高的话传输速率就会变快,但同时出现误码的错误也会变高,鱼和熊掌不可兼得。
需要注意的是,发送端和接收端必须配置成一样的,否则是没有办法进行通信的
3、一些传输例子
由上两图可知,串口是低位先行,先发送低位,在发送高位,从最低位开始依次发出
二、cubemx配置
串口也是STM32的外设,我们并不需要去手动操控IO的高低电平,而是在配置好协议参数后,在程序中调用函数
1、配置基本工程
(1)先进行工程的基础配置
首先用cubemx新建一个工程,点击下方ACCESS TO MCU SELECTOR
然后按下面顺序操作,添加自己芯片型号,然后进行第三步Start Project 生成工程
然后进行每次创建工程都需要做的三板斧
第一板斧:如下操作,否则不能烧录
第二板斧:选择高速时钟为晶振
第三板斧:系统时钟SCLK配置为最大,有显示你的板子是多大的,照写就行,然后按回车键
为了生成keil文件,还需进行以下两步配置
最后点击生成代码(先写基本工程配置,下面进行串口配置)
点击第一个蓝色的打开文件,点击第二个打开工程(keil文件)
2、串口配置
3、串口发送
接线:
单片机 电脑
RX------------------ TX
TX ------------------ RX
GND---------------- GND
(1)阻塞式串口收发(当我们要传输的数据都传输完了,这个函数才会执行结束)
while (1)
{
HAL_UART_Transmit(&huart2,(uint8_t*)"ojbk ",5,0xff);
HAL_Delay(500);
}
}
这段代码,简单来说,就是 哪个串口发送,发送什么,长度多少,0xff表示延时时间,单位是毫秒,如果超出这个时间还没发送完毕就结束发送,剩余的不发了
使用printf函数来进行串口输出:
首先添加头文件#include "stdio.h"
然后重写fputc函数,因为printf内部就是调用这个函数来进行单个字符输出的
int fputc(int c,FILE *stream)
{
uint8_t ch[1]={c};
HAL_UART_Transmit(&huart2,ch,1,0xff);
return c;
}
结果展示,这里没有结果的话 把魔术棒里面target---use microlib钩选上
(2)阻塞式接收
while (1)
{
uint8_t buf[5];//缓存数组
HAL_UART_Receive(&huart2,buf,5,0xffff);//电脑发送数据给单片机,用buf储存
HAL_UART_Transmit(&huart2,buf,5,0xffff);//单片机像电脑发送数据验证接收正确与否
/*
上面两个函数的数据长度,超时时间都给多点,不然会有奇奇怪怪的事情发生,别问我是怎么知道的
*/
HAL_Delay(500);
}
使用scanf函数来进行串口接收:
重写fgect函数
/*
scanf内部就是通过这个函数来进行单个字节的读入的
我们需要在这个函数中通过串口读入一个字节
然后把他作为返回值返回回去
*/
int fgetc(FILE *stream)
{
uint8_t ch[1];
HAL_UART_Receive(&huart2,ch,1,0xff);
return ch[0];
}
while (1)
{
int val=0;
scanf("%d",&val);
printf("%d\r\n",val);
HAL_Delay(500);
}
(2)使用中断进行串口收发
由于使用阻塞的方式收发太浪费CPU资源,而且会影响整个程序的运行,数据实际上都是由硬件自动进行传输的,软件只需要指定好发送的数据就行,所以使用阻塞式时,大部分的时间都被浪费在了等待硬件完成传输的过程中,为解决这个问题,故使用中断方式进行收发
步骤:
首先在cubemx里面把串口的中断给打开,在NVIC中使能USART2中断,并调一下优先级,然后点击生成代码
串口发送
/*
跟上面的阻塞式差不多一样,只是少了个超时时间
每发完一个字节进入一个中断,然后在中断中HAL库会再触发下一个字节的发送,直到发完全部数据
所以说此函数不需等待数据发送完毕才会退出
*/,他只是触发了中断发送的流程
while (1)
{
HAL_UART_Transmit_IT(&huart2,(uint8_t *)"ojbk",5);
HAL_Delay(500);
}
回调函数,在完成了所有数据的发送之后,会进入回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
}
串口接收:
uint8_t buffer[5];//接收数组,这是全局变量
//HAL_UART_Receive_IT(&huart2,buffer,3);//接收函数,每接收到一个字节进入一次中断,并保存到接收区里面,所以buffer不能为局部变量,不然有可能接收到数据存入时局部变量已经被销毁了,没有办法进行保存
//当接收完指定数量的字节后,就会调用以下回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit_IT(&huart2,buffer,3);//把接收到的数据发送回去
HAL_UART_Receive_IT(&huart2,buffer,3);//再开启下一次接收
}
HAL_UART_Receive_IT(&huart2,buffer,3);//开启接收
while()
{
}
这个代码有bug,还未解决,只有第一次发送的是正确的,后续发送结果如下所示求大佬指正
以上bug解决方法,把串口发送长度加2,因为串口接收有两个字节长度不可见
uint8_t buffer[5];//接收数组,这是全局变量
//HAL_UART_Receive_IT(&huart2,buffer,3);//接收函数,每接收到一个字节进入一次中断,并保存到接收区里面,所以buffer不能为局部变量,不然有可能接收到数据存入时局部变量已经被销毁了,没有办法进行保存
//当接收完指定数量的字节后,就会调用以下回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit_IT(&huart2,buffer,3);//把接收到的数据发送回去
HAL_UART_Receive_IT(&huart2,buffer,5);//再开启下一次接收
}
HAL_UART_Receive_IT(&huart2,buffer,5);//开启接收
while()
{
}
总结:使用中断方式收发的流程
1.调用函数开启一次中断收发的流程
2.每发送或接收到一个字节会进入中断由HAL进行处理,用户程序无需参与
3.传输完指定数量的字节后,HAL调用发送或接收完成回调函数
(3)、用DMA进行串口收发
cpu只需要为DMA分配好内存空间就行,不用每个字节都进中断进行处理
DMA配置
点击Add分别添加发送和接收的DMA,然后点击生成代码
用DMA进行串口发送函数
HAL_UART_Transmit_DMA(&huart2,buffer,3);
用DMA进行串口接收函数
HAL_UART_Receive_DMA(&huart2,buffer,5);
这两个函数跟阻塞式的用法一样
//发送
while (1)
{
HAL_UART_Transmit_DMA(&huart2,(uint8_t *)"ojbk\n",5);
HAL_Delay(500);
}
//接收
while (1)
{
uint8_t buffer[5];
HAL_UART_Receive_DMA(&huart2,buffer,5);//½ÓÊÕ
HAL_UART_Transmit_DMA(&huart2,buffer,5);
HAL_Delay(500);
}