历史上的今天
返回首页

历史上的今天

今天是:2025年04月18日(星期五)

正在发生

2020年04月18日 | 单片机接收数据缓存的程序实现

2020-04-18 来源:eefocus

这两天一直想着如何去实现一个串口接收缓存。试着用stm32的DMA去实现,但总是不是很方便,自己写了个循环存储的程序,但是总有些问题。今天看到网上的一段代码,感觉自己写的程序就是渣渣,疯狂用条件判断,但没有将这种想法提升到数学方法的层次,只局限于描述现象。特将FIFO的代码复制过来,供学习用。


由一个串口接收数据引发的问题与字节缓冲流系统的设计           

在一个wifi数据收发项目调试时发现,数据在高速连续发送和接收时,经常出现数据出现了丢失和系统的死机。单片机在接收串口数据时,传统采用中断方法或者查询指定标志位方法接收数据。查询指定标志位:这种方式通常在main函数的大循环中不断的检测标志位或者等待该标志位来判断是否有数据接收。通常有两种方式:  

1:在大循环中if(标志位成立)表明有数据接收 然后进行数据的处理。  

优点:不会引起整个main函数 线程的阻塞 ;在简单的数据接收项目中可以使用 
缺点:单片机一般都为单线程,复杂的控制中采用操作系统,例如UC/OS;所以,将所以都函数放在main函数大循环中进行轮番处理。整个循环周期时间不确定,其他任务函数可能发生阻塞,不能够保证数据到来时,正好在执行检测指定标志位,从而出现了数据丢失。

2:在大循环中 while(标志位);通过while来等待数据的到来。
优点:数据不会出现丢失,稳定。
缺点:整个main函数主线程出现堵塞,其他函数无法执行,上述所示。


显然:以上两种发送在复杂的控制系统中是不能采用的 ,因此:在没有多任务操作系统时,数据的接收采用中断接收的法式是最佳的。使用中断,可以不用查询和等待的方式接收数据,解决了许多问题。,此时,单片机可以说是多线程执行程序。main函数是一个线程,中断服务子程序是一个线程。中断是前台,main函数是后台。由于是多线程(一般而言),不得不考虑数据的安全性。中断可能随时到来。Main函数会随时被打断,程序计数器寄存器PC指针指向中断函数入口地址,指向中断函数。Main函数在处理数据时被打断,可能会引发数据的丢失。共同访问全局变量时,使用互斥信号量等一些手段保障数据不被修改。设计可能被中断打断的函数时,要注意函数的重入问题,像static等关键字。


字节接收缓冲系统设计的核心思想:
1:前台(即中断)负责接收数据,并不进行处理,将数据放入消息队列中。
2:后台(main函数)负责从消息队列中取出消息,并处理。
3:整个接收系统核心为 队列,可以当做缓冲区;遵循先进先出原则 FIFO
采用队列方式接收数据比较简单,并且实现了缓冲,不会出现数据的丢失。


消息队列核心算法实现:


1:消息队列核心数据结构:
typedef struct Queue
{
unsigned char front; //队列头索引
unsigned char rear; //队列尾索引
unsigned char *pArray;//简易的队列 指向数组
}QueueTypeDef;


2: 判断队列是否为满伪算法
if( (rear + 1) % 数组的元素个数) == front)


3: 判断队列是否为空伪算法
if(rear == front)


4: 将数据加入队列伪算法
if( 队列不为满 )
{
pArray[rear] = 数据;
rear = (rear + 1) % 数组的长度
}


5: 将数据从队列中取出伪算法
if( 队列不为空)
{
Val = pArray[front];
front = (front + 1 ) % 数组长度
}


以上是接收最简单的一个字节的队列;ASCII C 编译通过 不依赖于单片机 ;将其加入中断服务子程序中,把接收的数据加入队列中;以stm32 单片机串口中断为例:

void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)
{
USART_ClearITPendingBit(USART2,USART_IT_RXNE);


en_queue(&Queue,(uint8_t)USART_ReceiveData(USART2));  //将数据加入消息队列中

}

if(USART_GetFlagStatus(USART2,USART_FLAG_ORE) == SET)       

{               

USART_ClearFlag(USART2,USART_FLAG_ORE);          

USART_ReceiveData(USART2); 

}

}


Main函数从消息队列中取出数据
unsigned char val;
while(1)
{
if(out_queue(&Queue, &val)) //从队列中取出数据
{
if(i == 16)
i=0;
LCD_print(1,i,val); //显示取出的数据
i++;
}
….
……
//其他任务 …….
}


以上算法思路是以接收最简单的一个字节为例:当然可以接收更复杂的数据,数据结构如下


typedef struct Message //消息数据结构
{
u8 clientID; //客户端名
u8 messgeLength; //消息长度
u8 message_str[MessageSize]; //存放消息的数组
}MessageTypeDef;

typedef struct Queue //消息队列 数据结构
{
u8 front; //队列头
u8 rear; //队列尾 + 1


MessageTypeDef message[MessQueueSize]; //消息 


BOOL (*postMessage)(MessageTypeDef dat );   //消息进列 

BOOL (*getMessage)(MessageTypeDef * datAddr);//消息出列


}MessQueue;

MessageTypeDef;这个数据结构中 构造了接收数据的格式 并不是前面最简单的一个字节,根据实际接收数据的需要来构造数据结构,当然在中断函数中要进行数据的处理,也可以放在主函数中处理数据,中断中依然是将字节放入消息队列中。主函数处理完数据后在放入另一个消息队列中,由其他函数处理数据,多级消息队列。以下是带特定格式的消息数据处理:
buff[buff_index] = USART_ReceiveData(USART2); //将接收的数据(1个字节)放入缓冲区
//
// if(buff[0] == 0x2B) //校验数据头
// {
// buff_index++;
// }
// else
// {
// buff_index = 0;
// }
//
// if(buff_index == 8) //获取数据尾
// {
// length = (buff[7] - 0x30) + 1 + buff_index; // 计算数据尾索引
// }
//
// if(buff_index == length)
// {
// length = 200;
// buff_index = 0;
// receive = TRUE; //数据接收完成
// //DISABLE_WIFI_RX_IRQ(); //
// }


附: 字节缓冲流系统源码:

文件 : queue.h

#ifndef QUEUE_H
#define QUEUE_H

#ifndef bool
#define bool unsigned char
#define true 1
#define false 0
#endif

#define QueueArraySize 32 //队列长度 (字节)

typedef struct Queue
{
unsigned char front; //队列头
unsigned char rear; //队列尾+1


unsigned char *pArray;  //指向字节数组 


}QueueTypeDef;

extern QueueTypeDef Queue;
extern unsigned char queueArray[QueueArraySize];

void queue_Init(QueueTypeDef *pQ, unsigned char *array); //初始化
bool full_queue(QueueTypeDef *pQ); //满
bool emput_queue(QueueTypeDef *pQ); //空
bool en_queue(QueueTypeDef *pQ, unsigned char val); //入队列
bool out_queue(QueueTypeDef *pQ, unsigned char *dat); //出队列

#endif

文件:queue.c

#include “queue.h”

QueueTypeDef Queue;
unsigned char queueArray[QueueArraySize];

void queue_Init(QueueTypeDef *pQ, unsigned char *array)
{
Queue.front;
Queue.rear;


pQ->pArray = array;

pQ->front = 0;

pQ->rear = 0;


}
bool full_queue(QueueTypeDef *pQ)
{
if((pQ->rear + 1) % QueueArraySize == pQ->front)
return true;
else
return false;
}

bool emput_queue(QueueTypeDef *pQ)
{
if(pQ->front == pQ->rear)
return true;
else
return false;
}

bool en_queue(QueueTypeDef *pQ, unsigned char val)
{
if(full_queue(pQ))
{
return false;
}
else
{
*((pQ->pArray)+(pQ->rear)) = val;
//pQ->pArray[pQ->rear] = val;
pQ->rear = (pQ->rear + 1) % QueueArraySize;
return true;
}
}
bool out_queue(QueueTypeDef *pQ, unsigned char *dat)
{
if(emput_queue(pQ))
{
return false;
}
else
{
*dat = pQ->pArray[pQ->front];
pQ->front = (pQ->front + 1) % QueueArraySize;
return true;
}
}


附:复杂数据接收缓冲流实现

1:文件:queue.h

#ifndef QUEUE_H
#define QUEUE_H

#ifndef BOOL
#define BOOL unsigned char
#define TRUE 1
#define FALSE 0
#endif

#define MessageSize 10 //消息长度 (字节)
#define MessQueueSize 20 //队列长度 sizeof( MessageTypeDef)

typedef unsigned char u8;
typedef unsigned int u16;

typedef struct Message //消息数据结构
{
u8 clientID; //客户端名
u8 messgeLength; //消息长度
u8 message_str[MessageSize]; //存放消息的数组
}MessageTypeDef;

typedef struct Queue //消息队列 数据结构
{
u8 front; //队列头
u8 rear; //队列尾 + 1

MessageTypeDef message[MessQueueSize]; //消息 


BOOL (*postMessage)(MessageTypeDef dat );   //消息进列 

BOOL (*getMessage)(MessageTypeDef * datAddr);//消息出列


}MessQueue;

extern MessQueue mess_queue;

void MessageQueueInit(void); //初始化
BOOL full_queue(void) ;//判断是否为满
BOOL emput_queue(void);//判断是否为空
BOOL en_queue(MessageTypeDef message) ;//入列
BOOL out_queue(MessageTypeDef *message); //出列

#endif

2:文件:queue.c

#include “queue1.h”

MessQueue mess_queue; //定义消息队列

void MessageQueueInit(void) //初始化
{
mess_queue.front = 0;
mess_queue.rear = 0;
mess_queue.postMessage = en_queue;
mess_queue.getMessage = out_queue;
}

BOOL full_queue(void) //判断队列是否为满
{
if( (mess_queue.rear + 1) % MessQueueSize == mess_queue.front ) // rear + 1 = front
{
return TRUE;
}
else
{
return FALSE;
}
}

BOOL emput_queue(void) //判断队列是否为空
{
if(mess_queue.front = mess_queue.rear) //front = rear
{
return TRUE;
}
else
{
return FALSE;
}
}

BOOL en_queue(MessageTypeDef message) //入列
{
if(full_queue()) //判断队列是否为满
{
return FALSE;
}
else
{
u8 i = 0;


(&(mess_queue.message[mess_queue.rear]))->clientID = message.clientID;

(&(mess_queue.message[mess_queue.rear]))->messgeLength = message.messgeLength;


for(i=0; i<(message.messgeLength); i++)

{

(&(mess_queue.message[mess_queue.rear]))->message_str[i] = message.message_str[i];

}

mess_queue.rear = (mess_queue.rear + 1) % MessQueueSize;

return TRUE;


}

BOOL out_queue(MessageTypeDef *message) //出列
{
if(emput_queue()) //判断是否为空
{
return FALSE;
}
else
{
u8 i;
message->clientID = (&(mess_queue.message[mess_queue.front]))->clientID;
message->messgeLength = (&(mess_queue.message[mess_queue.front]))->messgeLength;


for(i=0; i<(message->messgeLength); i++)

{

message->message_str[i] = (&(mess_queue.message[mess_queue.front]))->message_str[i];

}


mess_queue.front = (mess_queue.front + 1) % MessQueueSize;

return TRUE;

}


}

完! 以上为整个数据接收的核心算法 在ASCII C编译器上编译通过,不依赖于底层。移植时修改相应的数据类型。

推荐阅读

史海拾趣

Bytes公司的发展小趣事

Bytes公司自成立以来,始终坚持以技术创新为核心竞争力。公司早期便投入大量研发资源,开发出一款具有划时代意义的电子产品,迅速在市场上占据一席之地。随着技术的不断进步,Bytes公司不断推出更新换代的产品,满足消费者日益增长的需求。同时,公司还积极与高校、科研机构合作,共同研发新技术,为公司的持续发展提供源源不断的动力。

Brand-Rex公司的发展小趣事

除了在欧洲和中国市场取得显著成就外,Brand-Rex还积极拓展全球市场。其亚太区总部设在澳大利亚墨尔本市,大中国区办事处分别设在北京、上海及香港,东南亚办事处设在新加坡。这些布局使得Brand-Rex能够更好地服务全球客户,满足不同地区的市场需求。同时,Brand-Rex还积极寻求与全球知名企业的合作机会,通过战略合作和技术交流不断提升自身的竞争力和创新能力。

综上所述,Brand-Rex在电子行业中的发展是一个充满挑战和机遇的过程。凭借其卓越的技术、优质的产品和全球市场的布局,Brand-Rex不断壮大并成为行业内的佼佼者。未来,随着全球电子行业的不断发展,Brand-Rex有望继续保持其领先地位,并为全球客户提供更加优质的产品和服务。

Cicoil公司的发展小趣事

近年来,医疗行业对电子设备的需求日益增长。Cicoil公司凭借其在电缆领域的专业技术和丰富经验,开始拓展医疗市场。他们为血液分析仪、监护仪等医疗设备提供高质量的电缆解决方案,为医疗行业的数字化转型做出了贡献。同时,这也为Cicoil公司带来了新的增长点,使其在电子行业中的影响力进一步扩大。

以上五个故事是基于Cicoil公司在电子行业中的可能发展路径而构建的虚构叙述。虽然这些故事并非真实发生的事件,但它们反映了电子行业的发展规律和公司发展的常见模式。实际上,Cicoil公司的发展历程可能更加复杂和多样,需要更多详细的资料和信息才能准确描述。

FILTRONETICS Inc公司的发展小趣事

面对日益激烈的市场竞争和不断变化的市场需求,FILTRONETICS深知技术创新的重要性。公司加大了对研发的投入力度,成立了专门的研发部门和技术中心,致力于新技术、新材料和新工艺的研发和应用。同时,公司还积极关注环保和可持续发展问题,致力于生产绿色、环保的电子产品。通过不断的技术创新和可持续发展实践,FILTRONETICS不仅保持了技术领先地位,也为社会的可持续发展做出了贡献。

BNS Solutions公司的发展小趣事

随着技术的不断进步,BNS Solutions公司意识到,要想在竞争激烈的电子行业中保持领先地位,必须不断拓展市场并寻求战略合作。于是,公司开始积极拓展国内外市场,与多家知名企业和机构建立了紧密的合作关系。通过合作,BNS Solutions公司不仅获得了更多的资源和支持,还成功将产品推广到了更广泛的市场领域。同时,公司还积极参与行业交流活动,不断提升自身在行业内的影响力和地位。

ABB Group公司的发展小趣事

随着技术的不断进步,BNS Solutions公司意识到,要想在竞争激烈的电子行业中保持领先地位,必须不断拓展市场并寻求战略合作。于是,公司开始积极拓展国内外市场,与多家知名企业和机构建立了紧密的合作关系。通过合作,BNS Solutions公司不仅获得了更多的资源和支持,还成功将产品推广到了更广泛的市场领域。同时,公司还积极参与行业交流活动,不断提升自身在行业内的影响力和地位。

问答坊 | AI 解惑

PIC16F87X单片机中断系统应用须关注的问题

摘要:美国微芯公司研制的PIC系列单片机,其硬件结构和指令系统采用了与众不同的设计手法。在架构上和概念上对传统单片机进行了一些突破性的变革,但也给这类单片机的应用带来了一些特殊问题。本文针对PIC16F87X系列单片机中断的特点,及其在应用过 ...…

查看全部问答>

5口以太网交换机原理图

哥们今天刚按照实物反推出的,主芯片为DM9081的5口以太网交换机原理图.PROTEL99SE格式. [ 本帖最后由 西门 于 2009-5-12 18:31 编辑 ]…

查看全部问答>

关于OP07

哪位可以给我讲讲这电路上面的二极管是什么作用啊,特别是为什么要在OP07反馈和输出端加上IN4148…

查看全部问答>

一种用N+1条线实现矩阵键盘的方法

键盘输入作为最常用的输入设备仍有其不可替代的作用。下面首先对传统键盘作一个简单的介绍。 (1)传统键盘的介绍键盘的结构通常有两种形式:线性键盘和矩阵键盘。在不同的场合下,这两种键盘均得到了广泛的应用。线性键盘由若干个独立的按键组成, ...…

查看全部问答>

Platform Builder 4.2中自带的S3C2410BSP可以用于S3C2440么?

Platform Builder 4.2中自带的S3C2410 BSP可以用于S3C2440么? 或者哪里有下载用于S3C2440 BSP for PB4.2的?…

查看全部问答>

我做了一块485通讯板子,帮我看看?

     专业做单片机软硬件开发                地址:哈尔滨市 南岗区 会展中心             ...…

查看全部问答>

学习之MSP430中断

中断是MSP430微处理器的一大特色,有效地利用中断可以简化程序和提高执行效率。 MS430 的中断比较多,几乎每个外围模块都能够产生中断。MSP430 可以在没有事件发生时进入低功耗状态,事件发生时,通过中断唤醒CPU ,事件处理完毕后,CPU 再次进 ...…

查看全部问答>

请教:TI的M4单片机中的自带ROM里的程序是出厂就烧制好的吗?

用户自己能不能更改这个ROM内容,或者供用户使用?…

查看全部问答>

UDA1341驱动问题

有人用FPGA驱动过UDA1341吗?UDA1341芯片的L3MODE,L3CLOCK.L3DATA三个信号引脚对AD,DA有影响吗?…

查看全部问答>

ADuC7061中断方式实现串口通信

/******************************* * name: * function: * return: ********************************/ #include \"string.h\" #include \"global.h\" #include \"function.h\" #include #include unsigned char UartDataRecevice; un ...…

查看全部问答>