串口通信详解(项目级接收、发送机制,基于STM32F103ZET6)
前言
作为从事物联网(五大IT热门行业之一)行业的工作者,串口通信无疑是key点之一,能很好的掌握串口通信,是必不可少的!为了让读者更好的理解和学习串口,我将项目的应用串口移植到了大多数学者学过的STM32F103ZET6,并且通过了验证!
目的 【串口通信详解(项目级接收、发送机制,基于STM32F103ZET6)】实现与外部mcu通信,完成对数据的接收处理,包括多条数据缓存功能,提高串口的性能!成功解析数据后完成对外部mcu的数据发送,完成通信流程
概念 在项目中,我们必须了解的是线程与进程的关系:(简单描述一下)
- 进程是分配的基本单位,它是执行程序的一个实例,在运行程序时建立。
- 线程是执行程序的最小单位,是进程的一个执行流,,一个进程可以是多个线程组成。
可以简单理解为我们STM32开发板的main函数中创建一个死循环while(1),也就是一个线程,可以简单理解为一个线程对应一个while循环,可以不退出循环,主要看项目内容;
程序讲解 头文件调用:
看头文件注释,string.h很重要,后续程序中用到的拷贝函数memcpy(),值位函数memset()都会用到,具体参数,可以看库函数
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "stdio.h"
#include "string.h"//字符串操作头文件
宏定义
解释看注视
#define len 10//根据自己情况调节每次接收数据的最大长度
#define ARRAY_NUM 4//缓存从外部mcu收到的数据条数
#define ture 1
#define fals 0
typedef struct _usart//每个数据的参数,数据,标志位信息
{
uint16_t DATA_LEN;
uint8_t DATA[250];
uint8_t FLAG;
}usart_;
变量定义
uint8_t bufff[len];
//保存长度为len的字节
uint16_t USART_RX_STA=0;
usart_ g_uart[ARRAY_NUM];
//保存数据为ARRAY_NUM的数据
重点 小编以前也是进行了很多的方法尝试,最终选择了这套处理方式:
串口的舒适化及接收数据
流程梳理:
每接收到一个字节,会触发(停止位来触发中断)一次中断,此时将收到的数据直接保存在我们定义的全局bufff中,此时我们应该注意到接收数据的长度,一旦超过长度选择抛弃掉其他的数据
void 中断函数入口(void)
{
if(判断是否为接收中断触发)
{
uint8_t 临时变量;
临时变量=(uint8_t)接收函数(串口);
if(接收的数据长度>=预定的长度)
{
接收的数据长度=预定的长度;
返回;
}
串口数据赋值给定义的数组;
}
}
void usart_init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate=bound;
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
USART_InitStructure.USART_Parity=USART_Parity_No;
USART_InitStructure.USART_StopBits=USART_StopBits_1;
USART_InitStructure.USART_WordLength=USART_WordLength_8b;
USART_Init(USART1,&USART_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;
NVIC_Init(&NVIC_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);
}void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
uint8_t res;
res=(uint8_t)USART_ReceiveData(USART1);
if(USART_RX_STA>=len)
{
USART_RX_STA=len;
return;
}
bufff[USART_RX_STA++]=res;
}
else if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
{
;
}
}
定时器中断初始化及数据处理
初始化中的重点就是溢出的时间,为10ms即可,如果两个数据传输的时间间隔小于10ms,则判断为一帧数据
计算公式:
Tout=(重装载值+1)*(分频值+1)/72MHz
流程概述
一直开启定时器,循环计数,串口数据缓存数量与定时器中保存数据数量的初始值都为零,当定时器时间到,用全局变量num先比较串口中接收到的数据的数量USART_RX_STA,如果串口在此溢出时间段收到了新的数据,接着进入定时器中断,此时为初始值为num=0,但是USART_RX_STA=1,在定时器中断中判断,如果USART_RX_STA>num,则表明一帧数据未接受完全,如果相等且不为零,则表明在上一个溢出时间周期(10ms)中没有收到新的数据,判断为一帧数据接收结束,再循环扫描保存此数据的结构体的数组是否为空,如果为空,复制数据到此数组,如果开辟的数组空间都被占用,则忽略此数据
一句话概括:
循环判断结构体中的数组内存是否为空,一旦检测到为空,则将其收到的数据复制进此结构体
算法代码解析
void 定时器中断函数入口(void) {
if(判断是否为定时器中断)
{static uint16_t num定义静态变量;
static uint8_t index定义静态变量;
unsigned char buff_busy定义局部变量;
if(串口中断接收数据数量>静态变量)
{
静态变量=串口中断接收数据数量;
}
else if(静态变量==串口中断接收数据数量&&静态变量!=0)
{
while(g_uart[静态变量%定义的缓存数组数量].FLAG==ture)
{
静态变量=(静态变量+1)%定义的缓存数组数量;
if(局部变量++>定义的缓存数组数量)
{
将收到的数据清空;
USART_RX_STA=0;
//清零
num=0;
break;
}}
一旦检测到空数组,就开始赋值;
g_uart[idex].DATA_LEN=num;
//数组长度
memset(g_uart[idex].DATA,0,g_uart[idex].DATA_LEN);
//将要赋值的结构体数组清零memcpy(g_uart[idex].DATA,bufff,g_uart[idex].DATA_LEN);
//将要赋值的数据存到清零的结构体
g_uart[idex].FLAG=ture;
//将此结构体的标志位标志位ture
memset(bufff,0,g_uart[idex].DATA_LEN);
//将串口接收的数据bufff成功赋值给结构体后立即清零,方便下一次的接收
USART_RX_STA=0;
//方便下一次数据接收计数
num=0;
//清零
}
else
{
;
}
LED1=!LED1;
TIM_ClearITPendingBit(TIM3, TIM_IT_Update );
//清除中断标志位
}
}
void timer_init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
LED_Init();
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode= TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period= 100;
TIM_TimeBaseInitStructure.TIM_Prescaler=7199;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
TIM_Cmd(TIM3, ENABLE);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;
NVIC_Init(&NVIC_InitStructure);
}
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
static uint16_t num=0;
static uint8_t idex=0;
unsigned char buff_busy;
if(USART_RX_STA>num)
{
num=USART_RX_STA;
}
else if(USART_RX_STA==num&&num!=0)
{
while(g_uart[idex%ARRAY_NUM].FLAG==ture)
{
idex=(idex+1)%ARRAY_NUM;
if(buff_busy++>ARRAY_NUM)
{
memset(bufff,0,g_uart[0].DATA_LEN);
USART_RX_STA=0;
num=0;
break;
}}
g_uart[idex].DATA_LEN=num;
memset(g_uart[idex].DATA,0,g_uart[idex].DATA_LEN);
memcpy(g_uart[idex].DATA,bufff,g_uart[idex].DATA_LEN);
g_uart[idex].FLAG=ture;
memset(bufff,0,g_uart[idex].DATA_LEN);
USART_RX_STA=0;
num=0;
}
else
{
;
}LED1=!LED1;
TIM_ClearITPendingBit(TIM3, TIM_IT_Update );
}
}
总结: 串口只负责接收数据(在一定数目内),定时器监听数据收发情况,同时负责复制数据保存到数组内,while循环负责一直检测和解析出数组中的数据,并做相应回应!串口发送函数我们可以直接调用,不需要自己进行数据处理
希望读者能够互相交流,多多交流,谢谢!
推荐阅读
- Docker应用:容器间通信与Mariadb数据库主从复制
- Java|Java OpenCV图像处理之SIFT角点检测详解
- C语言浮点函数中的modf和fmod详解
- Unity和Android通信系列文章2——扩展UnityPlayerActivity
- Android|Android BLE蓝牙连接异常处理
- 虚拟DOM-Diff算法详解
- LSTM网络层详解及其应用实例
- OC:|OC: WKWebView详解
- vue中的条件判断详解v-if|vue中的条件判断详解v-if v-else v-else-if v-show
- KubeDL HostNetwork(加速分布式训练通信效率)