1 使用定时器判断

这种方式建立在两帧数据不可能连续发送的基础上,也是modbus判断帧结束的方式,在接收到第一个字节的时候打开定时器,如果继续接收到数据则更新定时器,在被设定时间内没有接收到数据则定时器超时。

enter image description here

关于定时器的设定时间有这样几个问题

其一是如果定时器超时时间大于发送两帧数据的时间间隔,则接收到的一帧数据实际上是几帧,更可能定时器无法超时,一直处于接收状态。

其二是如果定时器超时小于发生两个字节的时间间隔,则在接收到1个字节定时器就超时了。

我们于是只能设定一个尽量小但又不影响接收连续字节的时间,例如ModBus通信时规定发送完一组命令必须间隔3.5个字符时间间隔再发送下一组新命令,这里规定的便是定时器的超时时间。

关于时间的计算

首先,1个字符窗口包含起始位,数据位,校验位,停止位,其中有些位长度不一定,这里我们按1+8+1+1来计算。波特率表示的意思是在1000ms内可以传送的位数,设3.5个字节所用时间为X,波特率为9600则:

3.5*11 / X = 9600 / 1000

X = 4.010416666666667 ms

X代表的意思是两帧数据间隔时间至少为此,我们程序的超时定时器可以设定为4ms。同时也知道波特率变化是会影响该值。

示例

这里使用的是STM32F103单片机,没有使用操作系统,使用的串口1和定时器3的通道1,这样定时器还能被用来干其他事,而不是完全被串口绑定。方式和上文有一点点差距,也就是在接收到非首字节数据时,不是重置定时器,而是更新通道超时的时间。通道1使用的是输出比较,也就是定时器计数达到这个比较值就会发生中断,我在接收数据时不断修改这个比较值,达到和更新定时器同样的目的。

下列便是相关代码,其中的fifo操作见另一篇文章

标志位

static uint8_t u1_Data_Start_Recive_Flag=0;//标志数据开始接收

static uint8_t u1_Data_recive_Flag=0;//标志数据正在接收

串口1初始化

void usart_init(int buad)

{

//GPIO端口设置

GPIO_InitTypeDef GPIO_InitStructure;

USART_InitTypeDef USART_InitStructure;

NVIC_InitTypeDef NVIC_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟

USART_DeInit(USART1); //复位串口1

//USART1_TX PA.9

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出

GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9

//USART1_RX PA.10

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入

GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA10

//Usart1 NVIC 配置

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级3

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级1

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能

NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器

//USART 初始化设置

USART_InitStructure.USART_BaudRate = buad; //一般设置为9600;

USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式

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_Rx | USART_Mode_Tx; //收发模式

USART_Init(USART1, &USART_InitStructure); //初始化串口

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启中断

USART_Cmd(USART1, ENABLE); //使能串口

FifoInit( &usart1_recive_fifo, usart1_recive_buffer, 200 );

}

定时器初始化

这里设定的计数器自动填充值和预分配可以按照整体需求来配置,当然得大于准备设定的区分字节间隔的最小时间

void timer3_init()

{

TIM_OCInitTypeDef TIM_OCInitStructure;

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

NVIC_InitTypeDef NVIC_InitStructure;

// RCC_PCLK1Config(RCC_HCLK_Div1);

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

TIM_TimeBaseStructure.TIM_Period = 0xfffe; //设定计数器自动重装值

TIM_TimeBaseStructure.TIM_Prescaler =999; //预分频器 72000

TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim

TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式

TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

// TIM_PrescalerConfig(TIM1, PrescalValue,TIM_PSCReloadMode_Immediate);

//输出比较时间模式配置:通道1

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;

TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;

TIM_OCInitStructure.TIM_Pulse = 100;

TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

TIM_OC1Init(TIM3, &TIM_OCInitStructure);

//TIM_OC1PreloadConfig(TIM2,TIM_OCPreload_Disable);//禁止预装载使能

//TIM_ITConfig(TIM2,TIM_IT_CC1,ENABLE);//使能中断

//TIM_ITConfig(TIM2,TIM_IT_CC2,ENABLE);//使能中断

//TIM_Cmd(TIM2,ENABLE ); //使能定时器

TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);

TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);//清楚中断标识位

NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能

NVIC_Init(&NVIC_InitStructure);

}

串口1中断服务程序

该函数是库函数提供,事件的中断函数

void USART1_IRQHandler(void)

{

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)

{

usart_irq_free(1);

if( FifoPush( &usart1_recive_fifo, (char)(USART_ReceiveData(USART1)) ) == 0 ) //存入数据

{

}

else

{

//error

}

}

}

状态切换函数,在上面的系统中断被调用

void usart_irq_free(uint8_t usart)

{

switch(usart)

{

case 1:

if(!u1_Data_Start_Recive_Flag && !u1_Data_recive_Flag) //需要获取数据且还未开始接收

{

usart_set_recive_start(1);

}

else if(!u1_Data_Start_Recive_Flag && u1_Data_recive_Flag) //需要获取数据且正在接收

{

usart_set_recive_ing(1);

}

break;

}

当接到首字节

这里获取当前定时器计数值,然后设定匹配为2000个计数,这里我设定了一个比较大的值,在定时器初始化是进行了千分频,这里时间间隔约为27.7ms,当然是我随意设定的一个较大的值,更加需求修改即可。

void usart_set_recive_start(uint8_t usart)

{

if(usart ==1)

{

FifoFlush(&usart1_recive_fifo);

u1_Data_Start_Recive_Flag = 0;

u1_Data_recive_Flag = 1;

TIM_Cmd(TIM3, ENABLE);//开启定时器

TIM_ITConfig(TIM3,TIM_IT_CC1,ENABLE);

TIM_SetCompare1(TIM3, TIM_GetCounter(TIM3)+2000); //

}

}

数据接收中状态

更新定时器匹配值,放置在接收图中定时器中断

void usart_set_recive_ing(uint8_t usart)

{

if(usart ==1)

{

u1_Data_Start_Recive_Flag = 0;

u1_Data_recive_Flag = 1;

TIM_SetCompare1(TIM3, TIM_GetCounter(TIM3)+2000);

}

}

定时器中断

库函数提供,当发生中断时也就是一帧数据接收完成时,这里进行状态切换,和调用回调,告诉上层完成了一帧数据的接收

void TIM3_IRQHandler(void) //TIM3中断

{

if(TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)

{

TIM_ClearITPendingBit(TIM3, TIM_IT_CC1); //清除TIMx更新中断标志

usart_set_recive_end(1);

if(usart1_irq_callback !=NULL)

{

usart1_irq_callback();

}

usart_start_recive(1);

}

if(TIM_GetITStatus(TIM3, TIM_FLAG_Update) != RESET)

{

TIM_ClearITPendingBit(TIM3, TIM_FLAG_Update);

//Timer3_Update_IRQ();

}

}

一帧数据接收完成切换状态

void usart_set_recive_end(uint8_t usart)

{

if(usart ==1)

{

TIM_Cmd(TIM3, DISABLE);

TIM_ITConfig(TIM3,TIM_IT_CC1,DISABLE);

USART_Cmd(USART1, DISABLE);

u1_Data_Start_Recive_Flag = 1;

u1_Data_recive_Flag = 0;

}

}

帧完成回调函数

定义函数指针

void (*usart1_irq_callback)(void);

通过接口函数让外部对其赋值

void set_usart1_irq_callback( void (*callback)(void) )

{

usart1_irq_callback = callback;

}

串口的数据发送(和本文没啥关系)

void usart_send_buffer(uint8_t usart,uint8_t *data,int len)

{

int i=0;

switch(usart)

{

case 1:

USART_Cmd(USART1, ENABLE);

for(i=0;i

{

USART_SendData( USART1,(unsigned char) data[i]);

while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);

USART_ClearFlag(USART1, USART_FLAG_TXE);

}

//USART_Cmd(USART1, DISABLE);

break;

}

}

外部调用

每当接收到一帧数据则function_callback函数被调用

usart1_init();

timer3_init();

set_usart1_irq_callback(function_callback);

2 解析协议方式

该方式便是通过协议中的保存的长度、包头、包尾等信息来判断帧接收完成。通常是将数据保存在一个循环buffer中,从中去找对应信息,根据信息判断取出一帧数据还是继续等待。

3 空闲中断

在STM32中有个串口空闲中断,在总线由忙碌转为空闲时(RXNE被置为)参数这个中断,我们可以利用这个中断来知道一帧传输接收,需要进行处理。

下面是SMT32L151的示例代码,首先在串口初始化时,也使能空闲中断

USART_ITConfig( uart, USART_IT_IDLE, ENABLE );

然后在对于的中断函数中进行处理,其中清除中断标志位根据手册所示,对SR、DR进行读操作,最后我调用回调函数来将帧接收完成的事告诉外层。

void USART1_IRQHandler( void )

{

unsigned char temp;

···

if(USART_GetITStatus(USART1,USART_IT_IDLE)==SET)

{

temp=USART1->SR;

temp=USART1->DR; //清除标志位

//完成接收

if( UartIdel[0] != NULL )

UartIdel[0]();

}

···

}

被关联的回调函数,用来测试该功能,接收到什么就发送什么

void SerialBoardIdelCallback()

{

char tempBuffer[10];

uint8_t len=GetSerialBoardReciveDataSize();

if(len<=10)

{

GetSerialBoardReciveData(tempBuffer,len);

SerialBoardSendData((unsigned char *)tempBuffer,len);

}

}

java 串口判断报文完整_如何判断串口接收完成一帧数据相关推荐

  1. 右手螺旋判断磁感应强度方向_如何判断磁感应强度方向 方法是什么

    有很多的同学是非常想知道,如何判断磁感应强度方向,方法方法是什么,小编整理了相关信息,希望会对大家有所帮助! 怎么判断磁感应强度方向 由电流方向判断磁感应强度的方法-------安培定则 安培定则:也 ...

  2. rxtx串口事件不触发_一种串口高效收发思路及方案

    摘要:本文在探讨传统数据收发不足之后,介绍如何使用带FIFO的串口来减少接收中断次数,通过一种自定义通讯协议格式,给出帧打包方法:之后介绍一种特殊的串口数据发送方法,可在避免使用串口发送中断的情况下, ...

  3. usb检测串口是哪个角_怎样测试串口和串口线是否正常

    一步:把串口线或者USB转串口线插到计算机上. 二步:打开串口调试助手 接着选择串口,串口线和 USB 转串口的端口号查看路径: 电脑上--右键--属性--硬件--设备管理器-端口(COM 和LPT) ...

  4. Java:PC端作为客户端连接蓝牙设备并接收蓝牙发送的数据

    感谢:(1)PC端蓝牙开发  https://www.cnblogs.com/zeussbook/p/12827479.html https://blog.csdn.net/svizzera/arti ...

  5. java 线程加载类_怎么判断java当前线程是否加载了一个类的字节码

    展开全部 原生的ClassLoader是有e68a843231313335323631343130323136353331333337616631一个方法判断类是否已经加载的/** * Returns ...

  6. java判断联通手机号码_怎样判断手机号码是移动的还是联通的?

    今天遇到了一个问题,给一个手机号码,怎样判断它是移动的还是联通的.我自己查了一些资料,咨询了一些朋友.不知道是否全面,想和大家研究一下.当然我指的是业务逻辑是否正确,并不是程序本身.用java实现的: ...

  7. java 判断页面刷新_如何判断一个网页是刷新还是关闭的方法

    页面加载时只执行onload 页面关闭时只执行onunload 页面刷新时先执行onbeforeunload,然后onunload,最后onload.这样我们可以在onbeforeunload中加一个 ...

  8. java正则表达式 文件后缀名_正则表达式 判断文件名后缀是否为 csv xls xlsx

    展开全部 在input中想要获取32313133353236313431303231363533e58685e5aeb931333365646262上传的文件名,那么onclick事件是不行的,因为一 ...

  9. java 反射 判断是否存在_如何判断Javascript对象是否存在

    Javascript语言的设计不够严谨,很多地方一不小心就会出错. 举例来说,请考虑以下情况. 现在,我们要判断一个全局对象myObj是否存在,如果不存在,就对它进行声明.用自然语言描述的算法如下: ...

最新文章

  1. 一個textlist控件(左右兩個文本框)。
  2. 【初码干货】关于.NET玩爬虫这些事
  3. JSON.parse()解析单引号错误的问题
  4. (王道408考研操作系统)第四章文件管理-第一节2:文件的逻辑结构
  5. LFM算法——推荐系统
  6. 算法每日学打卡:01-21打卡(解答后面整理)
  7. 带格式粘贴至html富文本,防止复制/粘贴将网页样式复制到富文本编辑器
  8. ecshop mysql 报错_ecshop数据库操作函数
  9. 32. iostat
  10. html卡机代码,能让微信卡死的代码是什么 微信整人代码大全
  11. docker-reviewboard
  12. win10系统电脑分辨率异常解决办法
  13. 为什么下载eclipse很慢很慢
  14. 美版iPhone4卡贴解锁
  15. bp神经网络及ROC曲线绘制
  16. 电子书沦为“压泡面”神器,其实高端电子书就该从这两个里边选
  17. HashMap常见面试题汇总:建议初步了解源码后再细品
  18. 读取云服务器文件列表,读取云服务器文件列表
  19. Suzy找到实习了吗Day 17 | 二叉树进行中:110. 平衡二叉树,257 二叉树的所有路径,404. 左叶子之和
  20. 广东去年处分厅官149人 追回外逃人员237人

热门文章

  1. 【FastJson】FastJson一个Bug java.util.LinkedHashMap cannot be cast to com.alibaba.fastjson.Jsonobject
  2. 20-400-040-高可用-Flink集群的高可用搭建
  3. kafka spark Structured streaming整合后集群报错KafkaConsumer.subscribe(Ljava/util/Collection;)V
  4. 记一次极其坑爹的Maven无法下载相关Jar包的问题:IDEA问题
  5. 01-maven build项目
  6. Mybatis源码解析:sql参数处理(2)
  7. qemu debug linux内核,在QEMU环境中使用GDB调试Linux内核
  8. Java基本数据类型及其包装类
  9. 【月报】Java知音的二月汇总
  10. 12bit的图像如何向8bit转化_干货分享 | 如何鉴别Western Blot图像的真实性?