请教51单片机串口收发缓存FIFO的写法
本帖最后由 mangolu 于 2021-2-2 03:24 编辑这里的单片机用N76E003,我想要完成的功能就是从串口以115200波特率以100mS间隔发送一串数据到单片机,单片机原样返回。
看了很多资料就是开设两个缓存分别存储收发数据,在接收中断写接收缓存,然后在main函数循环里查询发送缓存,有数据则发送出去。并以一定时间间隔取接收缓存的数据写入发送缓存。
我以下的代码实现了这个功能,但是每隔一段时间会丢失一个数据。看似数据也没有丢失,后继会错会发回,就是少发回一个数,运行一段时间后,收发的数据相差越来越大:
丢失的数据,可以看到后续数据是会发回,但是整体少了个数据并错位:
想请教的是,这种收发方式应该怎么做?或者是不是我这里的缓存写得有问题?有人可能会说操作缓存要上锁,原子操作,我试过发现上锁,数据丢失更严重。
下面是代码:
#include "stdint.h"
#include "common.h"
#include "Function_Define.h"
#include "N76E003.h"
#include "SFR_Macro.h"
#include "fifo.h"
FIFO xdata sRX_Buffer; // 接收队列
FIFO xdata sTX_Buffer; // 发送队列
uint8_t u8Busy_Flag = 0; // 忙标志位
uint8_t u8Temp;
uint8_t u8Count = 0;
void Uart_Send_Buffer(FIFO *sTX_Buffer);
/** 需要自行定义部分结束<<< */
void main(void) {
FSYS_Initial(); // 初始化时钟
Uart_Initial(); // 初始化串口
FIFO_Initial(&sRX_Buffer); // 初如化接收队列
FIFO_Initial(&sTX_Buffer); // 初如化发送队列
while(1) {
u8Count ++;
Uart_Send_Buffer(&sTX_Buffer); // 串口发送发送队列
if(u8Count > 10) { // 16.6MHz时钟,大概10uS
u8Count = 0;
// if(FIFO_Out(&sRX_Buffer, &u8Temp) == FIFO_SUCESS) { // 接收队列不为空
if(FIFO_Get_Count(&sRX_Buffer) != 0) { // 接收队列不为空
FIFO_Out(&sRX_Buffer, &u8Temp);
FIFO_In(&sTX_Buffer, u8Temp); // 把接收队列的值写入发送队列中
}
}
}
}
/**
* @brief把队列数据从串口发送出去
*
* @param sTX_Buffer 要发送的队列缓存
*
* @par 函数说明:
* 把队列数据从串口发送出去
*/
void Uart_Send_Buffer(FIFO *sTX_Buffer) {
uint8_t u8Temp_Send;
if((u8Busy_Flag == 0) && (FIFO_Get_Count(sTX_Buffer) != 0)) { // 忙标志位为不忙和发送队列不为空
FIFO_Out(sTX_Buffer, &u8Temp_Send);
SBUF_1 = u8Temp_Send;
u8Busy_Flag = 1; // 设置忙标志位为忙
}
}
/**
* @brief串口1中断处理
*
*
* @par 函数说明:
* 串口1中断处理,中断函数不需要声明。在其他应用中实现,要注释掉本函数。
*/
void Uart1_ISR(void) interrupt 15 { // 串口1中断向量为15
// uint8_t u8Temp1;
if (RI_1 == 1) { // 接收中断
clr_RI_1; // 清接收中断,需手动清除
/** 接收中断表示数据接收到,取串口缓存寄存器的值 */
/** 存入FIFO中 */
FIFO_In(&sRX_Buffer, SBUF_1);
}
if(TI_1 == 1) { // 发送中断
clr_TI_1; // 清发送中断,需手动清除
/** 发送中断表示数据发送完成,清忙标志 */
u8Busy_Flag = 0;
}
}
fifo.c:
#include "fifo.h"
/**
* @brief初始化FIFO
*
* @param sFIFO 要初始化的FIFO
* @return eFIFO_State 成功返回FIFO_SUCESS
*
* @par 函数说明:
* 初始化FIFO:
* 1. 数据头指向缓存0位置
* 2. 数据尾指向缓存0位置
* 3. 缓存区清零
*/
eFIFO_State FIFO_Initial(FIFO *sFIFO) {
#if(FIFO_SIZE < 256) // 缓存小于256使用8位地址
uint8_t u8Count;
/** 初始化FIFO缓存区 */
for(u8Count = 0; u8Count < FIFO_SIZE; u8Count ++) {
*(sFIFO->Buffer + u8Count) = 0;
}
#elif(FIFO_SIZE >= 256) // 缓存大于等于256使用16位地址
uint16_t u16Count;
/** 初始化FIFO缓存区 */
for(u16Count = 0; u16Count < FIFO_SIZE; u16Count ++) {
*(sFIFO->Buffer + u16Count) = 0;
}
#endif/** FIFO_SIZE */
sFIFO->Count = 0; // 存储数据字节数初始化
sFIFO->Front = 0; // 数据头位置初始化
sFIFO->Rear = 0; // 数据尾位置初始化
return FIFO_SUCESS;
}
/**
* @brief存储一个数据到FIFO中
*
* @param sFIFO 要操作的FIFO
* @param u8Data 要存储的数据
* @return eFIFO_State :
* 1. 成功返回 FIFO_SUCESS FIFO操作成功
* 2. FIFO已满,无法写入返回 FIFO_FULL FIFO已满
*
* @par 函数说明:
* 存储一个数据到FIFO中。
*/
eFIFO_State FIFO_In(FIFO *sFIFO, uint8_t u8Data) reentrant {
eFIFO_State eState = FIFO_SUCESS;// 初始状态值为FIFO_SUCESS
if(sFIFO->Count == FIFO_SIZE) {// FIFO已满
eState = FIFO_FULL; // 设置状态值为FIFO_FULL
} else {
/** 循环写入,写满返回位置0写 */
*(sFIFO->Buffer + sFIFO->Rear) = u8Data; // 数据存入缓存尾部
sFIFO->Rear = (sFIFO->Rear + 1) % FIFO_SIZE;// 数据尾与缓存长度取模,使数据尾永远在缓存长度范围内
(sFIFO->Count) ++; // 存储数据字节数加1
}
return eState;
}
/**
* @brief从FIFO中读出数据,并存入指定指针中
*
* @param sFIFO 要操作的FIFO
* @param u8pData 存储读出数据的指针
* @return eFIFO_State :
* 1. 成功返回 FIFO_SUCESS FIFO操作成功
* 2. FIFO已空,无法读出返回 FIFO_EMPTY FIFO已空
*
* @par 函数说明:
* 从FIFO中读出数据,并存入指定指针中。
*/
eFIFO_State FIFO_Out(FIFO *sFIFO, uint8_t *u8pData) reentrant {
eFIFO_State eState = FIFO_SUCESS;// 初始状态值为FIFO_SUCESS
if(sFIFO->Count == 0) {// FIFO已空
eState = FIFO_EMPTY; // 设置状态值为FIFO_EMPTY
} else {
/** 循环读取,写满返回位置0读 */
*u8pData = *(sFIFO->Buffer + sFIFO->Front); // 从缓存中读取数据
sFIFO->Front = (sFIFO->Front + 1) % FIFO_SIZE;// 数据头与缓存长度取模,使数据头永远在缓存长度范围内
(sFIFO->Count) --; // 存储数据字节数减1
}
return eState;
}
/**
* @brief获取FIFO中存储数据字节数
*
* @param sFIFO 要操作的FIFO
* @return uint16_t FIFO中存储数据字节数
*
* @par 函数说明:
* 存储数据字节数
*/
uint16_t FIFO_Get_Count(FIFO *sFIFO) {
return (sFIFO->Count);
}
fifo.h:
#ifndef _FIFO_H_
#define _FIFO_H_
#include "stdint.h"
#include "common.h"
#define FIFO_SIZE 32/** 定义FIFO缓存长度 */
/**
* @briefFIFO状态定义枚举
*
* @par 函数说明:
* FIFO状态定义枚举
*/
typedef enum {
FIFO_SUCESS, // FIFO操作成功
FIFO_FULL, // FIFO已满
FIFO_EMPTY, // FIFO已空
FIFO_LOCK, // FIFO已上锁
} eFIFO_State;
/**
* @briefFIFO结构定义
*
* @par 函数说明:
* FIFO结构定义。从数据头读出,从数据尾写入。
*/
#if(FIFO_SIZE < 256) // 缓存小于256使用8位地址
typedef struct {
uint8_t Count; // 存储数据字节数
uint8_t Front; // 数据头位置,指向数据读出的位置
uint8_t Rear; // 数据尾位置,指向数据写入的位置
uint8_t Buffer;// 数据存储缓存
} FIFO;
#elif(FIFO_SIZE >= 256) // 缓存大于等于256使用16位地址
typedef struct {
uint16_t Count; // 存储数据字节数
uint16_t Front; // 数据头位置,指向数据读出的位置
uint16_t Rear;// 数据尾位置,指向数据写入的位置
uint8_t Buffer;// 数据存储缓存
} FIFO;
#endif/** FIFO_SIZE */
/**
* @brief初始化FIFO
*
* @param sFIFO 要初始化的FIFO
* @return eFIFO_State 成功返回FIFO_SUCESS
*
* @par 函数说明:
* 初始化FIFO:
* 1. 数据头指向缓存0位置
* 2. 数据尾指向缓存0位置
* 3. 缓存区清零
*/
eFIFO_State FIFO_Initial(FIFO *sFIFO);
/**
* @brief存储一个数据到FIFO中
*
* @param sFIFO 要操作的FIFO
* @param u8Data 要存储的数据
* @return eFIFO_State :
* 1. 成功返回 FIFO_SUCESS FIFO操作成功
* 2. FIFO已满,无法写入返回 FIFO_FULL FIFO已满
*
* @par 函数说明:
* 存储一个数据到FIFO中。
*/
eFIFO_State FIFO_In(FIFO *sFIFO, uint8_t u8Data) reentrant;
/**
* @brief从FIFO中读出数据,并存入指定指针中
*
* @param sFIFO 要操作的FIFO
* @param u8pData 存储读出数据的指针
* @return eFIFO_State :
* 1. 成功返回 FIFO_SUCESS FIFO操作成功
* 2. FIFO已空,无法读出返回 FIFO_EMPTY FIFO已空
*
* @par 函数说明:
* 从FIFO中读出数据,并存入指定指针中。
*/
eFIFO_State FIFO_Out(FIFO *sFIFO, uint8_t *u8pData) reentrant;
/**
* @brief获取FIFO中存储数据字节数
*
* @param sFIFO 要操作的FIFO
* @return uint16_t FIFO中存储数据字节数
*
* @par 函数说明:
* 存储数据字节数
*/
uint16_t FIFO_Get_Count(FIFO *sFIFO);
#endif /** _FIFO_H_ */
1,在前后台中,不要运行同一个函数。
2,在51MCU使用指针,最好是使用基于存储器的指针。 su33691 发表于 2021-2-2 08:04
1,在前后台中,不要运行同一个函数。
2,在51MCU使用指针,最好是使用基于存储器的指针。 ...
这里把fifo写成可重入函数,难道要把函数打散,分别写对各个缓存的操作?
不懂什么叫基于存储器指针?大神能否指点下我这里指针有什么问题吗? 本帖最后由 su33691 于 2021-2-2 08:36 编辑
//fifo.c:
#include "fifo.h"
/**
* @brief初始化FIFO
*
* @param sFIFO 要初始化的FIFO
* @return eFIFO_State 成功返回FIFO_SUCESS
*
* @par 函数说明:
* 初始化FIFO:
* 1. 数据头指向缓存0位置
* 2. 数据尾指向缓存0位置
* 3. 缓存区清零
*/
eFIFO_State FIFO_Initial(FIFO xdata *sFIFO) {
#if(FIFO_SIZE < 256) // 缓存小于256使用8位地址
uint8_t u8Count;
/** 初始化FIFO缓存区 */
for(u8Count = 0; u8Count < FIFO_SIZE; u8Count ++) {
*(sFIFO->Buffer + u8Count) = 0;
}
#elif(FIFO_SIZE >= 256) // 缓存大于等于256使用16位地址
uint16_t u16Count;
/** 初始化FIFO缓存区 */
for(u16Count = 0; u16Count < FIFO_SIZE; u16Count ++) {
*(sFIFO->Buffer + u16Count) = 0;
}
#endif/** FIFO_SIZE */
sFIFO->Count = 0; // 存储数据字节数初始化
sFIFO->Front = 0; // 数据头位置初始化
sFIFO->Rear = 0; // 数据尾位置初始化
return FIFO_SUCESS;
}
/**
* @brief存储一个数据到FIFO中
*
* @param sFIFO 要操作的FIFO
* @param u8Data 要存储的数据
* @return eFIFO_State :
* 1. 成功返回 FIFO_SUCESS FIFO操作成功
* 2. FIFO已满,无法写入返回 FIFO_FULL FIFO已满
*
* @par 函数说明:
* 存储一个数据到FIFO中。
eFIFO_State FIFO_In(FIFO xdata *sFIFO, uint8_t u8Data) reentrant {
eFIFO_State eState = FIFO_SUCESS;// 初始状态值为FIFO_SUCESS
if(sFIFO->Count == FIFO_SIZE) {// FIFO已满
eState = FIFO_FULL; // 设置状态值为FIFO_FULL
} else {
// 循环写入,写满返回位置0写
*(sFIFO->Buffer + sFIFO->Rear) = u8Data; // 数据存入缓存尾部
sFIFO->Rear = (sFIFO->Rear + 1) % FIFO_SIZE;// 数据尾与缓存长度取模,使数据尾永远在缓存长度范围内
(sFIFO->Count) ++; // 存储数据字节数加1
}
return eState;
}
*/
void FIFO_In_RXBuff(FIFO xdata *sFIFO, uint8_t u8Data)
{
if(sFIFO->Count != FIFO_SIZE) // FIFO不满
{
*(sFIFO->Buffer + sFIFO->Rear) = u8Data; // 数据存入缓存尾部
sFIFO->Rear = (sFIFO->Rear + 1) % FIFO_SIZE;// 数据尾与缓存长度取模,使数据尾永远在缓存长度范围内
(sFIFO->Count) ++; // 存储数据字节数加1
}
}
void FIFO_In_TXBuff(FIFO xdata *sFIFO, uint8_t u8Data)
{
if(sFIFO->Count != FIFO_SIZE) // FIFO不满
{
*(sFIFO->Buffer + sFIFO->Rear) = u8Data; // 数据存入缓存尾部
sFIFO->Rear = (sFIFO->Rear + 1) % FIFO_SIZE;// 数据尾与缓存长度取模,使数据尾永远在缓存长度范围内
(sFIFO->Count) ++; // 存储数据字节数加1
}
}
/**
* @brief从FIFO中读出数据,并存入指定指针中
*
* @param sFIFO 要操作的FIFO
* @param u8pData 存储读出数据的指针
* @return eFIFO_State :
* 1. 成功返回 FIFO_SUCESS FIFO操作成功
* 2. FIFO已空,无法读出返回 FIFO_EMPTY FIFO已空
*
* @par 函数说明:
* 从FIFO中读出数据,并存入指定指针中。
*/
eFIFO_State FIFO_Out(FIFO xdata *sFIFO, uint8_t data *u8pData) //reentrant
{
eFIFO_State eState = FIFO_SUCESS;// 初始状态值为FIFO_SUCESS
if(sFIFO->Count == 0) {// FIFO已空
eState = FIFO_EMPTY; // 设置状态值为FIFO_EMPTY
} else {
/** 循环读取,写满返回位置0读 */
*u8pData = *(sFIFO->Buffer + sFIFO->Front); // 从缓存中读取数据
sFIFO->Front = (sFIFO->Front + 1) % FIFO_SIZE;// 数据头与缓存长度取模,使数据头永远在缓存长度范围内
(sFIFO->Count) --; // 存储数据字节数减1
}
return eState;
}
/**
* @brief获取FIFO中存储数据字节数
*
* @param sFIFO 要操作的FIFO
* @return uint16_t FIFO中存储数据字节数
*
* @par 函数说明:
* 存储数据字节数
*/
uint16_t FIFO_Get_Count(FIFO xdata *sFIFO) {
return (sFIFO->Count);
} if(sFIFO->Count != FIFO_SIZE) // FIFO不满
改为:
if(sFIFO->Count < FIFO_SIZE) // FIFO不满 su33691 发表于 2021-2-2 08:34
//fifo.c:
#include "fifo.h"
谢谢,晚点我改下试试。 有帧头这种协议框架的不容易丢失,直接收发对可靠性要求太高,单片机如何保证一帧数据接受完成,同时要保证在发送的时候没有数据接受进来,你这种接受完成只能基于中断超时 初音之恋 发表于 2021-2-2 08:43
有帧头这种协议框架的不容易丢失,直接收发对可靠性要求太高,单片机如何保证一帧数据接受完成,同时要保证 ...
这里数据持续收发,本来是想试一下串口的稳定性,我就是想试一下这种情况不丢失数据怎么处理。 接收中断超时就很好做呀,这样处理又简单,逻辑一看就明白。你想测试串口稳定性就应该用最简单的代码。
Landmark 发表于 2021-2-2 08:56
接收中断超时就很好做呀,这样处理又简单,逻辑一看就明白。你想测试串口稳定性就应该用最简单的代码。
...
假设我持续发送,并没有间隔,也没有帧头帧尾怎么处理?坛里这几天有个帖子stc8g的,就是这样的情况。我不懂那个示例代码在stc8上运行如何,我手里没有这个stc8的板子。我看示例代码就是我楼主位的处理方法,我这里也是根据自己理解写的程序。 su33691 发表于 2021-2-2 08:41
if(sFIFO->Count != FIFO_SIZE) // FIFO不满
改为:
已经改为你上面的了,还是一样子会丢失一个数据。 mangolu 发表于 2021-2-2 08:27
这里把fifo写成可重入函数,难道要把函数打散,分别写对各个缓存的操作?
不懂什么叫基于存储器指针?大 ...
你要做两个fifo,1t1r。r在中断里填入,t当tx空闲时由程序直接写硬件,否则在tx中断里写硬件。
此外你要留点延迟,你这种丢数据可能是两侧时钟偏差较大造成的。 wye11083 发表于 2021-2-2 10:36
你要做两个fifo,1t1r。r在中断里填入,t当tx空闲时由程序直接写硬件,否则在tx中断里写硬件。
此外你要 ...
上面已经改为每个缓存一个写函数,读的话就读发送缓存就没改,结果是一样的 mangolu 发表于 2021-2-2 11:04
上面已经改为每个缓存一个写函数,读的话就读发送缓存就没改,结果是一样的 ...
干吗要用count?要想兼容中断和非中断且保证可靠性,只能使用rw ptr。如果(w+1)=r,则写满。如果r=w,则读空。 wye11083 发表于 2021-2-2 11:09
干吗要用count?要想兼容中断和非中断且保证可靠性,只能使用rw ptr。如果(w+1)=r,则写满。如果r=w, ...
谢谢,我想一下 mangolu 发表于 2021-2-2 09:10
假设我持续发送,并没有间隔,也没有帧头帧尾怎么处理?坛里这几天有个帖子stc8g的,就是这样的情况。我 ...
仅仅测试串口的稳定性,就是用 9 楼的方法,反而你这边引入的问题,不是串口的问题了。 security 发表于 2021-2-2 11:21
仅仅测试串口的稳定性,就是用 9 楼的方法,反而你这边引入的问题,不是串口的问题了。 ...
本来是想测试串口,后面发现使用队列出现这个问题,现在是想怎么解决这个:) mangolu 发表于 2021-2-2 11:47
本来是想测试串口,后面发现使用队列出现这个问题,现在是想怎么解决这个:) ...
14 楼的,读、写双指针。
给个参考实现:RT-Thread 里面的 ringbuffer.c(rt-thread\components\drivers\src\ringbuffer.c)。 wye11083 发表于 2021-2-2 11:09
干吗要用count?要想兼容中断和非中断且保证可靠性,只能使用rw ptr。如果(w+1)=r,则写满。如果r=w, ...
初始:r = w = 0
读写各一个:r=1 w=1,此时不能说明为空吧?
w + 1 = r,也只有r =0才会出现?
或许是我理解有误或算法不对。请教能否提供一下你队列实现的方法? security 发表于 2021-2-2 11:50
14 楼的,读、写双指针。
给个参考实现:RT-Thread 里面的 ringbuffer.c(rt-thread\components\drivers\ ...
谢谢,没用过rtt,有空再看看 Landmark 发表于 2021-2-2 08:56
接收中断超时就很好做呀,这样处理又简单,逻辑一看就明白。你想测试串口稳定性就应该用最简单的代码。
...
这种处理完全没问题 mangolu 发表于 2021-2-2 10:33
已经改为你上面的了,还是一样子会丢失一个数据。
我用上述代码在STC8H1K08测试,22.1184MHZ频率,115200BSP。串口OK,不会丢数据。 全部代码:
//main.c
//115200bps@22.1184MHz
#include "fifo.h"
FIFO xdata sRX_Buffer = {0,0,0,{0}}; // 接收队列
FIFO xdata sTX_Buffer = {0,0,0,{0}}; // 发送队列
uint8_t u8Busy_Flag = 0; // 忙标志位
uint8_t u8Temp;
uint8_t u8Count = 0;
void Uart_Send_Buffer(FIFO xdata *sTX_Buffer);
void Uart_Initial(void) //115200bps@22.1184MHz
{
P_SW1 = 0x40; //P36,P37
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x40; //定时器1时钟为Fosc,即1T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设定定时器1为16位自动重装方式
TL1 = 0xD0; //设定定时初值
TH1 = 0xFF; //设定定时初值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
ES = 1; //使能串口中断
}
void InitGpio(void) //IO初始化
{
// P1 = 0xfe;
P1M0 = 0x00; //P1.0配置为推挽输出
P1M1 = 0x00;
P3M0 = 0x80; //P37配置为推挽输出
P3M1 = 0x00;
P5M0 = 0x00; //
P5M1 = 0x00;
}
/** 需要自行定义部分结束<<< */
void main(void)
{
InitGpio(); // 初始化IO
Uart_Initial(); // 初始化串口
// FIFO_Initial(&sRX_Buffer); // 初如化接收队列
// FIFO_Initial(&sTX_Buffer); // 初如化发送队列
EA = 1;
while(1)
{
u8Count ++;
Uart_Send_Buffer(&sTX_Buffer); // 串口发送发送队列
if(u8Count > 10)
{ // 16.6MHz时钟,大概10uS
u8Count = 0;
if(FIFO_Get_Count(&sRX_Buffer) != 0)
{ // 接收队列不为空
FIFO_Out(&sRX_Buffer, &u8Temp);
FIFO_In_TXBuff(&sTX_Buffer, u8Temp); // 把接收队列的值写入发送队列中
}
}
}
}
/**
* @brief把队列数据从串口发送出去
*
* @param sTX_Buffer 要发送的队列缓存
*
* @par 函数说明:
* 把队列数据从串口发送出去
*/
void Uart_Send_Buffer(FIFO xdata *sTX_Buffer)
{
uint8_t u8Temp_Send;
if((u8Busy_Flag == 0) && (FIFO_Get_Count(sTX_Buffer) != 0))
{ // 忙标志位为不忙和发送队列不为空
FIFO_Out(sTX_Buffer, &u8Temp_Send);
SBUF = u8Temp_Send;
u8Busy_Flag = 1; // 设置忙标志位为忙
}
}
/**
* @brief串口1中断处理
*
*
* @par 函数说明:
* 串口1中断处理,中断函数不需要声明。在其他应用中实现,要注释掉本函数。
*/
void Uart1_ISR(void) interrupt 4
{ // 串口1中断向量为15
// uint8_t u8Temp1;
if (RI)
{ // 接收中断
RI = 0; // 清接收中断,需手动清除
/** 接收中断表示数据接收到,取串口缓存寄存器的值 */
/** 存入FIFO中 */
FIFO_In_RXBuff(&sRX_Buffer, SBUF);
}
if(TI)
{ // 发送中断
TI = 0; // 清发送中断,需手动清除
/** 发送中断表示数据发送完成,清忙标志 */
u8Busy_Flag = 0;
}
}
//fifo.c:
#include "fifo.h"
/**
* @brief存储一个数据到FIFO中
*
* @param sFIFO 要操作的FIFO
* @param u8Data 要存储的数据
* @return eFIFO_State :
* 1. 成功返回 FIFO_SUCESS FIFO操作成功
* 2. FIFO已满,无法写入返回 FIFO_FULL FIFO已满
*
* @par 函数说明:
* 存储一个数据到FIFO中。
*/
void FIFO_In_RXBuff(FIFO xdata *sFIFO, uint8_t u8Data)
{
if(sFIFO->Count < FIFO_SIZE) // FIFO不满
{
*(sFIFO->Buffer + sFIFO->Rear) = u8Data; // 数据存入缓存尾部
sFIFO->Rear = (sFIFO->Rear + 1) % FIFO_SIZE;// 数据尾与缓存长度取模,使数据尾永远在缓存长度范围内
(sFIFO->Count) ++; // 存储数据字节数加1
}
}
void FIFO_In_TXBuff(FIFO xdata *sFIFO, uint8_t u8Data)
{
if(sFIFO->Count < FIFO_SIZE) // FIFO不满
{
*(sFIFO->Buffer + sFIFO->Rear) = u8Data; // 数据存入缓存尾部
sFIFO->Rear = (sFIFO->Rear + 1) % FIFO_SIZE;// 数据尾与缓存长度取模,使数据尾永远在缓存长度范围内
(sFIFO->Count) ++; // 存储数据字节数加1
}
}
/**
* @brief从FIFO中读出数据,并存入指定指针中
*
* @param sFIFO 要操作的FIFO
* @param u8pData 存储读出数据的指针
* @return eFIFO_State :
* 1. 成功返回 FIFO_SUCESS FIFO操作成功
* 2. FIFO已空,无法读出返回 FIFO_EMPTY FIFO已空
*
* @par 函数说明:
* 从FIFO中读出数据,并存入指定指针中。
*/
void FIFO_Out(FIFO xdata *sFIFO, uint8_t data *u8pData) //reentrant
{
// eFIFO_State eState = FIFO_SUCESS;// 初始状态值为FIFO_SUCESS
if(sFIFO->Count != 0)
{
/** 循环读取,写满返回位置0读 */
*u8pData = *(sFIFO->Buffer + sFIFO->Front); // 从缓存中读取数据
sFIFO->Front = (sFIFO->Front + 1) % FIFO_SIZE;// 数据头与缓存长度取模,使数据头永远在缓存长度范围内
// (sFIFO->Count) --; // 存储数据字节数减1
(sFIFO->Count) --;
}
// return eState;
}
/**
* @brief获取FIFO中存储数据字节数
*
* @param sFIFO 要操作的FIFO
* @return uint16_t FIFO中存储数据字节数
*
* @par 函数说明:
* 存储数据字节数
*/
uint8_t FIFO_Get_Count(FIFO xdata *sFIFO)
{
return (sFIFO->Count);
}
//fifo.h:
#ifndef _FIFO_H_
#define _FIFO_H_
#include "STC8H.h"
#define FIFO_SIZE 32/** 定义FIFO缓存长度 */
/**
* @briefFIFO状态定义枚举
*
* @par 函数说明:
* FIFO状态定义枚举
*/
typedef enum {
FIFO_SUCESS, // FIFO操作成功
FIFO_FULL, // FIFO已满
FIFO_EMPTY, // FIFO已空
FIFO_LOCK, // FIFO已上锁
} eFIFO_State;
/**
* @briefFIFO结构定义
*
* @par 函数说明:
* FIFO结构定义。从数据头读出,从数据尾写入。
*/
typedef struct
{
uint8_t Count; // 存储数据字节数
uint8_t Front; // 数据头位置,指向数据读出的位置
uint8_t Rear; // 数据尾位置,指向数据写入的位置
uint8_t Buffer;// 数据存储缓存
} FIFO;
/**
* @brief存储一个数据到FIFO中
*
* @param sFIFO 要操作的FIFO
* @param u8Data 要存储的数据
* @return eFIFO_State :
* 1. 成功返回 FIFO_SUCESS FIFO操作成功
* 2. FIFO已满,无法写入返回 FIFO_FULL FIFO已满
*
* @par 函数说明:
* 存储一个数据到FIFO中。
*/
//eFIFO_State FIFO_In(FIFO xdata *sFIFO, uint8_t u8Data) reentrant;
void FIFO_In_RXBuff(FIFO xdata *sFIFO, uint8_t u8Data);
void FIFO_In_TXBuff(FIFO xdata *sFIFO, uint8_t u8Data);
/**
* @brief从FIFO中读出数据,并存入指定指针中
*
* @param sFIFO 要操作的FIFO
* @param u8pData 存储读出数据的指针
* @return eFIFO_State :
* 1. 成功返回 FIFO_SUCESS FIFO操作成功
* 2. FIFO已空,无法读出返回 FIFO_EMPTY FIFO已空
*
* @par 函数说明:
* 从FIFO中读出数据,并存入指定指针中。
*/
void FIFO_Out(FIFO xdata *sFIFO, uint8_t data *u8pData);
/**
* @brief获取FIFO中存储数据字节数
*
* @param sFIFO 要操作的FIFO
* @return uint16_t FIFO中存储数据字节数
*
* @par 函数说明:
* 存储数据字节数
*/
uint8_t FIFO_Get_Count(FIFO xdata *sFIFO);
#endif /** _FIFO_H_ */
su33691 发表于 2021-2-2 14:34
全部代码:
//main.c
那就很奇怪。你这个是外置晶振吗?n76e003是内置RC,会不会跟这个有关? 自定义协议一般会定义帧头或者帧尾,否则无法判别是否一帧有效数据,也没法进行处理,接收的时候,只管往buf里填充,同时不断增加指针地址,每次接收字节做一个简单判断是不是帧头或帧尾就可以。
标准协议,比如MODBUS-RTU,可以用标准的3.5T超时处理。
曾经测试过一个51产品,115200bps,疯狂做MODBUS-RTU的测试,100万次通信没有发现错误,后来实在忍不住关掉了测试。 本帖最后由 security 于 2021-2-2 15:59 编辑
mangolu 发表于 2021-2-2 14:57
那就很奇怪。你这个是外置晶振吗?n76e003是内置RC,会不会跟这个有关?
都会的,只不过,刚好避开了踩坑的时间点。
main 中的 FIFO_Out(&sRX_Buffer, &u8Temp);,对 count 递减
ISR 中 FIFO_In_RXBuff(&sRX_Buffer, SBUF);,对 count 递增,
对 count 的操作,并不是原子操作,时间久了,踩到点了,就会使得 count 错乱掉。
例如,main 中的对 count 的递减操作:MOVX A,@DPTR
DEC A
MOVX @DPTR,A
如果在 DEC A 之前(假设此时 count = 8,那么 A = 8),发生了接收中断,进入到 ISR,那么 count 数,将自增为 9,
然后继续返回到 main 中,继续执行 :DEC A
MOVX @DPTR,A。此时 A = 8,那么 count 的数值,将被修改为 7,这里就出现了问题了。 security 发表于 2021-2-2 15:58
都会的,只不过,刚好避开了踩坑的时间点。
main 中的 FIFO_Out(&sRX_Buffer, &u8Temp);,对 count 递减 ...
谢谢,那有避免这个的解决方法吗? mangolu 发表于 2021-2-2 16:05
谢谢,那有避免这个的解决方法吗?
舍弃 count 方案,
或者在非中断环境的操作中,对 count 的操作,关开中断。 security 发表于 2021-2-2 16:07
舍弃 count 方案,
或者在非中断环境的操作中,对 count 的操作,关开中断。 ...
接收要用中断吧?不然还会丢失数据啊。楼上有说只记读写指针,但是如何判断缓存空还是满,好像比较麻烦吧? mangolu 发表于 2021-2-2 16:10
接收要用中断吧?不然还会丢失数据啊。楼上有说只记读写指针,但是如何判断缓存空还是满,好像比较麻烦吧 ...
非中断的调用环境,例如 main 中的 count 操作,关开中断。
这要额外的判断逻辑,至于怎么解决,去网上查吧,之前给你 RT-Thread 的参考,就是一种实现。 security 发表于 2021-2-2 15:58
都会的,只不过,刚好避开了踩坑的时间点。
main 中的 FIFO_Out(&sRX_Buffer, &u8Temp);,对 count 递减 ...
{:handshake:}
因为count在xdata空间,操作count就不是原子操作。会有隐患。 security 发表于 2021-2-2 16:17
非中断的调用环境,例如 main 中的 count 操作,关开中断。
这要额外的判断逻辑,至于怎么解决,去网上 ...
好的,谢谢 security 发表于 2021-2-2 16:07
舍弃 count 方案,
或者在非中断环境的操作中,对 count 的操作,关开中断。 ...
看了下汇编:
25: (sFIFO->Count) ++; // 存储数据字节数加1
C:0x01C6 8F82 MOV DPL(0x82),R7
C:0x01C8 8E83 MOV DPH(0x83),R6
C:0x01CA E0 MOVX A,@DPTR
C:0x01CB 04 INC A
C:0x01CC F0 MOVX @DPTR,A
这里对count的操作,虽然不是一条语句完成。在中断时,会将用到的寄存器 压栈,出栈。木有问题。 在51mcu里操作u8类型的变量,在确保不发生溢出的情况下,不需要考虑临界变量问题。 修改代码,运行频率改为11.0592,115200BPS。串口一次发送01-----0f 共15个数据,100ms自动发送。不会丢数据。
上面的代码木问题。
可能是楼主的USB-串口问题。{:titter:}
我用的是CH340,自己画的板。前段时间在xx商城用15圆优惠券买了10片。 mangolu 发表于 2021-2-2 12:23
初始:r = w = 0
读写各一个:r=1 w=1,此时不能说明为空吧?
因为fifo不超过256,因此读写指针是一个u8,不存在双字节操作,再加上一个pointer总是在中断里执行,因此不需要考虑冲突问题。很简单,a=(a+1)&(n-1)就行了。n一般是常数,取2的n次方便于计算。 su33691 发表于 2021-2-2 18:26
修改代码,运行频率改为11.0592,115200BPS。串口一次发送01-----0f 共15个数据,100ms自动发送。不会丢数 ...
我这个是PL2303HX的,工具挺多,但是不在手上,我找另一个看看 wye11083 发表于 2021-2-2 18:41
因为fifo不超过256,因此读写指针是一个u8,不存在双字节操作,再加上一个pointer总是在中断里执行,因此 ...
这个还需要想一下。我上面定义有超过256的指针。 mangolu 发表于 2021-2-2 18:47
这个还需要想一下。我上面定义有超过256的指针。
fifo!数据缓存在你程序里面做!把串口做成服务! 本帖最后由 security 于 2021-2-3 00:07 编辑
su33691 发表于 2021-2-2 16:40
看了下汇编:
25: (sFIFO->Count) ++; // 存储数据字节数加1
C:0x01C6 8F82...
中断本来就不会影响被中断任务的运行环境(换句话说,就是寄存器吧)。
这里的 count,不保护的话,一定会受并发的影响。
只是说,这种测试用例下,如果都是有序的踩准节拍,也就说,避开了并发访问的点,那么表面看起来,就是没有问题,但其实埋了个地雷,没踩到而已。 su33691 发表于 2021-2-2 18:26
修改代码,运行频率改为11.0592,115200BPS。串口一次发送01-----0f 共15个数据,100ms自动发送。不会丢数 ...
换了个串口,还是一样,证明不是串口芯片问题:
这是我的测试:
使用STC8H1K08,运行频率11.0592MGZ,115200BPS,内部HC震荡器。
su33691 发表于 2021-2-3 07:35
估计程序逻辑问题不大,count冲突的逻辑也会有,我找其他板试试 本帖最后由 wqsjob 于 2021-2-3 11:08 编辑
缓存我一般用循环队列。不是FIFO。大小为64,128,256等。
比如51,我一般使用64字节缓存,比如转发功能如下
rx_interrupt()
{
buf=SBUF;
rx_piont++;
buf_num++;
rx_point&0x3f;//0-63
}
//------------------
tx_interrupt()
{
if(buf_num)
{
buf_num--;
tx_piont++;
tx_point&0x3f;//0-63
SBUF=buf;
}
else
{
tx_state=0;
}
}
//---------------------------------
tx_statrt()
{
if(buf_num>63)
{
//出错处理,一般为调用间隔太长导致
}
else
{
if((buf_num>0)&&(0==tx_state)
{
SBUF=buf;
tx_state=1;
}
}
}
//刚才写错了,RX和TX应该是一个函数里的。这个是不会同时操作buf_num的。如果是分开的,要考虑两个中断设置为同一优先级,排除这种情况。 FIFO_IN函数既在主函数调用,又在中断里调用,这儿可能会有bug。我记得标准的51这样调用会报错的。 zqf441775525 发表于 2021-2-3 11:37
FIFO_IN函数既在主函数调用,又在中断里调用,这儿可能会有bug。我记得标准的51这样调用会报错的。 ...
设置为可重入就可以了 51有可重入函数保护机制吗?
我一定不会这么用。 wqsjob 发表于 2021-2-3 11:04
缓存我一般用循环队列。不是FIFO。大小为64,128,256等。
比如51,我一般使用64字节缓存,比如转发功能如 ...
今天有空把你的代码拿过来试了下,不懂得发送回来的为什么后面多一个数,并且是00 01这样变换:
还没仔细分析原因。代码如下:
#define BUFFER_SIZE 64 // 缓存大小
uint8_t u8Buffer; // 定义缓存
uint8_t u8Front = 0; // 读指针
uint8_t u8Rear = 0; // 写指针
uint8_t u8Count = 0; // 缓存存储字节计数
uint8_t TX_State;
void Uart_Send_Start(void);
void main(void) {
Initial_System(); // 初始化系统
Uart_Send_String(UART1, "Uart1 Initial OK!");
Uart_Send_String(UART1, "\r\n");
while(1) {
Uart_Send_Start();
}
}
/**
* @brief串口1中断处理
*
*
* @par 函数说明:
* 串口1中断处理,中断函数不需要声明。在其他应用中实现,要注释掉本函数。
*/
void Uart1_ISR(void) interrupt 15 { // 串口1中断向量为15
// uint8_t u8Temp1;
if (RI_1 == 1) { // 接收中断
clr_RI_1; // 清接收中断,需手动清除
/** 接收中断表示数据接收到,取串口缓存寄存器的值 */
u8Buffer = SBUF_1;
u8Rear ++;
u8Count ++;
u8Rear &= 0x3F; // 0-63
}
if(TI_1 == 1) { // 发送中断
clr_TI_1; // 清发送中断,需手动清除
/** 发送中断表示数据发送完成,清忙标志 */
if(u8Count > 0) {
u8Count --;
u8Front ++;
SBUF_1 = u8Buffer;
u8Front &= 0x3F; // 0-63
} else {
TX_State = 0;
}
}
}
void Uart_Send_Start(void) {
if(u8Count > 63) {
// 出错处理,一般为调用间隔太长导致
} else {
if((u8Count > 0) && (TX_State == 0)) {
SBUF_1 = u8Buffer;
// u8Count --;
// u8Front --;
TX_State = 1;
}
}
} 哦,我改成下面这样好了:
void Uart_Send_Start(void) {
if(u8Count > 63) {
// 出错处理,一般为调用间隔太长导致
} else {
if((u8Count > 0) && (TX_State == 0)) {
SBUF_1 = u8Buffer;
u8Count --;
u8Front ++;
TX_State = 1;
}
}
}
其实这个跟我的差不多,只是Count是在中断里更改,不会造成同时有Count变化的情况出现,所以不会出错。 mangolu 发表于 2021-2-5 14:56
哦,我改成下面这样好了:
谈一下我的看法:
1. N76E003这颗芯片很烂{:huffy:} ,还不如STC12,
STC的51, 在经过STC15和STC8两代优化,指令效率很高。
2. 在51MCU使用指针,最好是使用基于存储器的指针。 su33691 发表于 2021-2-5 16:27
谈一下我的看法:
正在跑看,目前感觉没出问题。不过发现同时使用串口0和串口1出问题,网上说了几个方法,我现在还没有测试,目前只用到一个串口。至于你说的基于存储器的指针我不大明白,我上面有定义的xdata FIFO buffer这样的是基于存储器的指针吗? 基于存储器的指针:
void FIFO_Out(FIFO xdata *sFIFO, uint8_t data *u8pData); su33691 发表于 2021-2-5 16:47
基于存储器的指针:
void FIFO_Out(FIFO xdata *sFIFO, uint8_t data *u8pData);
哦,谢谢!我看一下 115200波特率发送一个字节需80US左右
fifo变量位于xdata空间,运行效率很低。
收发一个字节要运行2次fifo_in和2次 fifo_out。
如果使用一般指针,在80US,大循环跑不了一遍,count变量在前后台均在改变的隐患就会成为真正的BUG.{:lol:} su33691 发表于 2021-2-5 17:10
115200波特率发送一个字节需80US左右
fifo变量位于xdata空间,运行效率很低。
这个单片机是1T的,运行在16.6MHz下,一个机器周期是1/16.6 = 0.06US,这个关系应该不大吧? mangolu 发表于 2021-2-5 17:45
这个单片机是1T的,运行在16.6MHz下,一个机器周期是1/16.6 = 0.06US,这个关系应该不大吧? ...
STC12也号称1T的。STC8的指令速度比STC12快2.5x
呵呵。 mangolu 发表于 2021-2-5 14:56
哦,我改成下面这样好了:
嗯,问题的确出在这里。相当于中断中多判断了一次。
中断程序把这两行提到if语句之前应该就可以了。
页:
[1]