串口通信详解(项目级接收、发送机制,基于STM32F103ZET6)

前言
作为从事物联网(五大IT热门行业之一)行业的工作者,串口通信无疑是key点之一,能很好的掌握串口通信,是必不可少的!为了让读者更好的理解和学习串口,我将项目的应用串口移植到了大多数学者学过的STM32F103ZET6,并且通过了验证!
目的 【串口通信详解(项目级接收、发送机制,基于STM32F103ZET6)】实现与外部mcu通信,完成对数据的接收处理,包括多条数据缓存功能,提高串口的性能!成功解析数据后完成对外部mcu的数据发送,完成通信流程
概念 在项目中,我们必须了解的是线程与进程的关系:(简单描述一下)

  1. 进程是分配的基本单位,它是执行程序的一个实例,在运行程序时建立。
  2. 线程是执行程序的最小单位,是进程的一个执行流,,一个进程可以是多个线程组成。
    可以简单理解为我们STM32开发板的main函数中创建一个死循环while(1),也就是一个线程,可以简单理解为一个线程对应一个while循环,可以不退出循环,主要看项目内容;
初始化 初始化定时器,溢出时间确定在10ms,串口初始化,选择自己想要的串口进行通信
程序讲解 头文件调用:
看头文件注释,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循环负责一直检测和解析出数组中的数据,并做相应回应!串口发送函数我们可以直接调用,不需要自己进行数据处理
希望读者能够互相交流,多多交流,谢谢!

    推荐阅读