mangolu 发表于 2021-2-2 03:21:46

请教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_ */

su33691 发表于 2021-2-2 08:04:16

1,在前后台中,不要运行同一个函数。
2,在51MCU使用指针,最好是使用基于存储器的指针。

mangolu 发表于 2021-2-2 08:27:24

su33691 发表于 2021-2-2 08:04
1,在前后台中,不要运行同一个函数。
2,在51MCU使用指针,最好是使用基于存储器的指针。 ...

这里把fifo写成可重入函数,难道要把函数打散,分别写对各个缓存的操作?
不懂什么叫基于存储器指针?大神能否指点下我这里指针有什么问题吗?

su33691 发表于 2021-2-2 08:34:44

本帖最后由 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);
}

su33691 发表于 2021-2-2 08:41:52

if(sFIFO->Count != FIFO_SIZE)         // FIFO不满

改为:
if(sFIFO->Count < FIFO_SIZE)         // FIFO不满

mangolu 发表于 2021-2-2 08:42:12

su33691 发表于 2021-2-2 08:34
//fifo.c:
#include "fifo.h"



谢谢,晚点我改下试试。

初音之恋 发表于 2021-2-2 08:43:17

有帧头这种协议框架的不容易丢失,直接收发对可靠性要求太高,单片机如何保证一帧数据接受完成,同时要保证在发送的时候没有数据接受进来,你这种接受完成只能基于中断超时

mangolu 发表于 2021-2-2 08:49:35

初音之恋 发表于 2021-2-2 08:43
有帧头这种协议框架的不容易丢失,直接收发对可靠性要求太高,单片机如何保证一帧数据接受完成,同时要保证 ...

这里数据持续收发,本来是想试一下串口的稳定性,我就是想试一下这种情况不丢失数据怎么处理。

Landmark 发表于 2021-2-2 08:56:37

接收中断超时就很好做呀,这样处理又简单,逻辑一看就明白。你想测试串口稳定性就应该用最简单的代码。
   

mangolu 发表于 2021-2-2 09:10:01

Landmark 发表于 2021-2-2 08:56
接收中断超时就很好做呀,这样处理又简单,逻辑一看就明白。你想测试串口稳定性就应该用最简单的代码。
    ...

假设我持续发送,并没有间隔,也没有帧头帧尾怎么处理?坛里这几天有个帖子stc8g的,就是这样的情况。我不懂那个示例代码在stc8上运行如何,我手里没有这个stc8的板子。我看示例代码就是我楼主位的处理方法,我这里也是根据自己理解写的程序。

mangolu 发表于 2021-2-2 10:33:10

su33691 发表于 2021-2-2 08:41
if(sFIFO->Count != FIFO_SIZE)         // FIFO不满

改为:


已经改为你上面的了,还是一样子会丢失一个数据。

wye11083 发表于 2021-2-2 10:36:32

mangolu 发表于 2021-2-2 08:27
这里把fifo写成可重入函数,难道要把函数打散,分别写对各个缓存的操作?
不懂什么叫基于存储器指针?大 ...

你要做两个fifo,1t1r。r在中断里填入,t当tx空闲时由程序直接写硬件,否则在tx中断里写硬件。

此外你要留点延迟,你这种丢数据可能是两侧时钟偏差较大造成的。

mangolu 发表于 2021-2-2 11:04:52

wye11083 发表于 2021-2-2 10:36
你要做两个fifo,1t1r。r在中断里填入,t当tx空闲时由程序直接写硬件,否则在tx中断里写硬件。

此外你要 ...

上面已经改为每个缓存一个写函数,读的话就读发送缓存就没改,结果是一样的

wye11083 发表于 2021-2-2 11:09:36

mangolu 发表于 2021-2-2 11:04
上面已经改为每个缓存一个写函数,读的话就读发送缓存就没改,结果是一样的 ...

干吗要用count?要想兼容中断和非中断且保证可靠性,只能使用rw ptr。如果(w+1)=r,则写满。如果r=w,则读空。

mangolu 发表于 2021-2-2 11:15:29

wye11083 发表于 2021-2-2 11:09
干吗要用count?要想兼容中断和非中断且保证可靠性,只能使用rw ptr。如果(w+1)=r,则写满。如果r=w, ...

谢谢,我想一下

security 发表于 2021-2-2 11:21:38

mangolu 发表于 2021-2-2 09:10
假设我持续发送,并没有间隔,也没有帧头帧尾怎么处理?坛里这几天有个帖子stc8g的,就是这样的情况。我 ...

仅仅测试串口的稳定性,就是用 9 楼的方法,反而你这边引入的问题,不是串口的问题了。

mangolu 发表于 2021-2-2 11:47:17

security 发表于 2021-2-2 11:21
仅仅测试串口的稳定性,就是用 9 楼的方法,反而你这边引入的问题,不是串口的问题了。 ...

本来是想测试串口,后面发现使用队列出现这个问题,现在是想怎么解决这个:)

security 发表于 2021-2-2 11:50:14

mangolu 发表于 2021-2-2 11:47
本来是想测试串口,后面发现使用队列出现这个问题,现在是想怎么解决这个:) ...

14 楼的,读、写双指针。
给个参考实现:RT-Thread 里面的 ringbuffer.c(rt-thread\components\drivers\src\ringbuffer.c)。

mangolu 发表于 2021-2-2 12:23:27

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才会出现?

或许是我理解有误或算法不对。请教能否提供一下你队列实现的方法?

mangolu 发表于 2021-2-2 12:24:16

security 发表于 2021-2-2 11:50
14 楼的,读、写双指针。
给个参考实现:RT-Thread 里面的 ringbuffer.c(rt-thread\components\drivers\ ...

谢谢,没用过rtt,有空再看看

kap 发表于 2021-2-2 14:05:57

Landmark 发表于 2021-2-2 08:56
接收中断超时就很好做呀,这样处理又简单,逻辑一看就明白。你想测试串口稳定性就应该用最简单的代码。
    ...

这种处理完全没问题

su33691 发表于 2021-2-2 14:23:33

mangolu 发表于 2021-2-2 10:33
已经改为你上面的了,还是一样子会丢失一个数据。

我用上述代码在STC8H1K08测试,22.1184MHZ频率,115200BSP。串口OK,不会丢数据。

su33691 发表于 2021-2-2 14:34:19

全部代码:

//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_ */

mangolu 发表于 2021-2-2 14:57:40

su33691 发表于 2021-2-2 14:34
全部代码:

//main.c


那就很奇怪。你这个是外置晶振吗?n76e003是内置RC,会不会跟这个有关?

ilikemcu 发表于 2021-2-2 15:50:41

自定义协议一般会定义帧头或者帧尾,否则无法判别是否一帧有效数据,也没法进行处理,接收的时候,只管往buf里填充,同时不断增加指针地址,每次接收字节做一个简单判断是不是帧头或帧尾就可以。
标准协议,比如MODBUS-RTU,可以用标准的3.5T超时处理。
曾经测试过一个51产品,115200bps,疯狂做MODBUS-RTU的测试,100万次通信没有发现错误,后来实在忍不住关掉了测试。

security 发表于 2021-2-2 15:58:35

本帖最后由 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,这里就出现了问题了。

mangolu 发表于 2021-2-2 16:05:48

security 发表于 2021-2-2 15:58
都会的,只不过,刚好避开了踩坑的时间点。

main 中的 FIFO_Out(&sRX_Buffer, &u8Temp);,对 count 递减 ...

谢谢,那有避免这个的解决方法吗?

security 发表于 2021-2-2 16:07:19

mangolu 发表于 2021-2-2 16:05
谢谢,那有避免这个的解决方法吗?

舍弃 count 方案,
或者在非中断环境的操作中,对 count 的操作,关开中断。

mangolu 发表于 2021-2-2 16:10:14

security 发表于 2021-2-2 16:07
舍弃 count 方案,
或者在非中断环境的操作中,对 count 的操作,关开中断。 ...

接收要用中断吧?不然还会丢失数据啊。楼上有说只记读写指针,但是如何判断缓存空还是满,好像比较麻烦吧?

security 发表于 2021-2-2 16:17:14

mangolu 发表于 2021-2-2 16:10
接收要用中断吧?不然还会丢失数据啊。楼上有说只记读写指针,但是如何判断缓存空还是满,好像比较麻烦吧 ...

非中断的调用环境,例如 main 中的 count 操作,关开中断。

这要额外的判断逻辑,至于怎么解决,去网上查吧,之前给你 RT-Thread 的参考,就是一种实现。

su33691 发表于 2021-2-2 16:28:26

security 发表于 2021-2-2 15:58
都会的,只不过,刚好避开了踩坑的时间点。

main 中的 FIFO_Out(&sRX_Buffer, &u8Temp);,对 count 递减 ...

{:handshake:}
因为count在xdata空间,操作count就不是原子操作。会有隐患。

mangolu 发表于 2021-2-2 16:37:02

security 发表于 2021-2-2 16:17
非中断的调用环境,例如 main 中的 count 操作,关开中断。

这要额外的判断逻辑,至于怎么解决,去网上 ...

好的,谢谢

su33691 发表于 2021-2-2 16:40:31

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的操作,虽然不是一条语句完成。在中断时,会将用到的寄存器 压栈,出栈。木有问题。

su33691 发表于 2021-2-2 17:08:41

在51mcu里操作u8类型的变量,在确保不发生溢出的情况下,不需要考虑临界变量问题。

su33691 发表于 2021-2-2 18:26:13

修改代码,运行频率改为11.0592,115200BPS。串口一次发送01-----0f 共15个数据,100ms自动发送。不会丢数据。
上面的代码木问题。

可能是楼主的USB-串口问题。{:titter:}
我用的是CH340,自己画的板。前段时间在xx商城用15圆优惠券买了10片。

wye11083 发表于 2021-2-2 18:41: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次方便于计算。

mangolu 发表于 2021-2-2 18:44:17

su33691 发表于 2021-2-2 18:26
修改代码,运行频率改为11.0592,115200BPS。串口一次发送01-----0f 共15个数据,100ms自动发送。不会丢数 ...

我这个是PL2303HX的,工具挺多,但是不在手上,我找另一个看看

mangolu 发表于 2021-2-2 18:47:28

wye11083 发表于 2021-2-2 18:41
因为fifo不超过256,因此读写指针是一个u8,不存在双字节操作,再加上一个pointer总是在中断里执行,因此 ...

这个还需要想一下。我上面定义有超过256的指针。

wye11083 发表于 2021-2-2 20:27:10

mangolu 发表于 2021-2-2 18:47
这个还需要想一下。我上面定义有超过256的指针。

fifo!数据缓存在你程序里面做!把串口做成服务!

security 发表于 2021-2-2 23:53:47

本帖最后由 security 于 2021-2-3 00:07 编辑

su33691 发表于 2021-2-2 16:40
看了下汇编:
    25:               (sFIFO->Count) ++;    // 存储数据字节数加1
C:0x01C6    8F82...

中断本来就不会影响被中断任务的运行环境(换句话说,就是寄存器吧)。
这里的 count,不保护的话,一定会受并发的影响。

只是说,这种测试用例下,如果都是有序的踩准节拍,也就说,避开了并发访问的点,那么表面看起来,就是没有问题,但其实埋了个地雷,没踩到而已。

mangolu 发表于 2021-2-3 02:21:15

su33691 发表于 2021-2-2 18:26
修改代码,运行频率改为11.0592,115200BPS。串口一次发送01-----0f 共15个数据,100ms自动发送。不会丢数 ...

换了个串口,还是一样,证明不是串口芯片问题:

su33691 发表于 2021-2-3 07:22:15

这是我的测试:
使用STC8H1K08,运行频率11.0592MGZ,115200BPS,内部HC震荡器。


su33691 发表于 2021-2-3 07:35:16

mangolu 发表于 2021-2-3 10:26:25

su33691 发表于 2021-2-3 07:35


估计程序逻辑问题不大,count冲突的逻辑也会有,我找其他板试试

wqsjob 发表于 2021-2-3 11:04:26

本帖最后由 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的。如果是分开的,要考虑两个中断设置为同一优先级,排除这种情况。

zqf441775525 发表于 2021-2-3 11:37:42

FIFO_IN函数既在主函数调用,又在中断里调用,这儿可能会有bug。我记得标准的51这样调用会报错的。

mangolu 发表于 2021-2-3 12:16:35

zqf441775525 发表于 2021-2-3 11:37
FIFO_IN函数既在主函数调用,又在中断里调用,这儿可能会有bug。我记得标准的51这样调用会报错的。 ...

设置为可重入就可以了

zqf441775525 发表于 2021-2-3 12:31:21

51有可重入函数保护机制吗?
我一定不会这么用。

mangolu 发表于 2021-2-5 14:44:49

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;
                }
        }
}

mangolu 发表于 2021-2-5 14:56:59

哦,我改成下面这样好了:

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变化的情况出现,所以不会出错。

su33691 发表于 2021-2-5 16:27:33

mangolu 发表于 2021-2-5 14:56
哦,我改成下面这样好了:




谈一下我的看法:


1. N76E003这颗芯片很烂{:huffy:} ,还不如STC12,
        STC的51, 在经过STC15和STC8两代优化,指令效率很高。
2. 在51MCU使用指针,最好是使用基于存储器的指针。

mangolu 发表于 2021-2-5 16:44:25

su33691 发表于 2021-2-5 16:27
谈一下我的看法:




正在跑看,目前感觉没出问题。不过发现同时使用串口0和串口1出问题,网上说了几个方法,我现在还没有测试,目前只用到一个串口。至于你说的基于存储器的指针我不大明白,我上面有定义的xdata FIFO buffer这样的是基于存储器的指针吗?

su33691 发表于 2021-2-5 16:47:59

基于存储器的指针:

void FIFO_Out(FIFO xdata *sFIFO, uint8_t data *u8pData);

mangolu 发表于 2021-2-5 17:01:16

su33691 发表于 2021-2-5 16:47
基于存储器的指针:

void FIFO_Out(FIFO xdata *sFIFO, uint8_t data *u8pData);

哦,谢谢!我看一下

su33691 发表于 2021-2-5 17:10:45

115200波特率发送一个字节需80US左右

fifo变量位于xdata空间,运行效率很低。
收发一个字节要运行2次fifo_in和2次 fifo_out。
如果使用一般指针,在80US,大循环跑不了一遍,count变量在前后台均在改变的隐患就会成为真正的BUG.{:lol:}

mangolu 发表于 2021-2-5 17:45:37

su33691 发表于 2021-2-5 17:10
115200波特率发送一个字节需80US左右

fifo变量位于xdata空间,运行效率很低。


这个单片机是1T的,运行在16.6MHz下,一个机器周期是1/16.6 = 0.06US,这个关系应该不大吧?

su33691 发表于 2021-2-5 17:50:31

mangolu 发表于 2021-2-5 17:45
这个单片机是1T的,运行在16.6MHz下,一个机器周期是1/16.6 = 0.06US,这个关系应该不大吧? ...

STC12也号称1T的。STC8的指令速度比STC12快2.5x
呵呵。

wqsjob 发表于 2021-2-6 15:45:22

mangolu 发表于 2021-2-5 14:56
哦,我改成下面这样好了:




嗯,问题的确出在这里。相当于中断中多判断了一次。
中断程序把这两行提到if语句之前应该就可以了。
页: [1]
查看完整版本: 请教51单片机串口收发缓存FIFO的写法