网络知识 娱乐 对串口接收FIFO处理机制的解读

对串口接收FIFO处理机制的解读

一、FIFOFIFO

First Input First Output的缩写,先入先出队列。

使用的场景:一般是在不同的时钟域之间的数据传输(简单理解即:一个(读写)快,另外的一个(读写)慢的场景中。)

本质操作:就是将收的数据存储的一个线性的数组中,通过指针指向该数组的自加1(偏移)来遍历数据,达到读取或者写入的目的。

作用:起到缓冲环节,可防止数据的丢失。

对数据量大的进行存储,避免频繁的总线操作。并且可满足dma的操作。

在fifo中需要理解连个重要成员:

宽度:指一次写读操作的数据位数

深度:存储多少个宽度的数据。(如:存储8个16位宽的数据)。

第一类、FIFO处理机制如下:

FIFO信息的定义:

/*n该结构体定义成员有n缓存区,n长度,n输出,n输入的计数。n*/ntypedef struct fifo_t {n uint8_t *buf;n uint32_t size;n uint32_t in;n uint32_t out;n} _fifo_str;n#define min(x,y) ((x) < (y)?(x):(y)) n1234567891011121314n

1、初始化FIFO

fifo_str fifo_str;nnint FifoInit(uint8_t *fifo_addr, uint32_t fifo_size)//初始化fifon{n _fifo_str *p = &fifo_str;n n if(fifo_addr == NULL || fifo_size == 0)//判断数据是否为空n return -1;nn memset((char *)p, 0, sizeof(_fifo_str));//初始化结构体n p->buf = fifo_addr;//对应宽度n p->in = 0;//输入计数n p->out = 0;//输出计数n p->size = fifo_size;//对应深度n return 0;n}n12345678910111213141516n

2、数据的长度获取

//数据的实际使用数据空间长度nint FifoDataLen(void)n{n _fifo_str *p = &fifo_str;n return (p->in - p->out);//输入计数-输出计数n}n//剩余数据空间长度nint FifoSpaceLen(void)n{n _fifo_str *p = &fifo_str;n n return (p->size - (p->in - p->out));//定义长度-(实际长度)n}n12345678910111213n

3、FIFO的进和出处理

//获取fifo数据n//数据的内容缓存区,要读的长度nint FifoRead(uint8_t *buf, uint32_t len)n{n uint32_t i = 0, j = 0;n _fifo_str *p = &fifo_str;nn j = (p->out % p->size);//获取剩余空间长度未读量n len = min(len, p->in - p->out);//防止长度为超出实际拥有的数据长度,即让读取的长度在 (0<设定len<定义的缓存区长度len )这间的实际长度n i = min(len, p->size - j);//获取实际内容的长度,的数据长度n memcpy(buf, p->buf + j, i);//将数据通道里的数据拷贝给缓存区n memcpy(buf + i, p->buf, len - i);//将未有数据的区域存入,对应为写入数据的区域数据(即,有数据的填数据,没数据的地方补0)n p->out += len;//已读的数据量n return len;//实际读到的数据长度n}n//对fifo写入数据nint FifoWrite(uint8_t *buf, uint32_t len)n{n uint32_t i = 0, j = 0;n _fifo_str *p = &fifo_str;nn j = p->in % p->size;//获取要写入的剩余空间长度n len = min(len, p->size - p->in + p->out);//得到实际写入的长度n i = min(len, p->size - j);//实际写入数据的长度n memcpy(p->buf + j, buf, i);//将写入的数据的内容拷贝值数据中。n memcpy(p->buf, buf + i, len - i);//补充多余空间的内容n p->in += len;//记录实际写入数据的数量n return len;//返回写入的长度n}nn123456789101112131415161718192021222324252627282930n

4、置位记录量

//清空fifo 中的记录量nvoid FifoClear(void)n{n _fifo_str *p = &fifo_str;n p->in = 0;n p->out = 0;n}n1234567n

5、应用处理机制

#define LEN 2048nuint8_t pdata[LEN] = {0};nFifoInit(pdata, LEN);//初始化FIFOnuint8_t buf[32] = {0}; nint tx_len = 0;nuint8_t tx_buf[100] = {0};nnHAL_UART_Receive_IT(&huart1, buf, IT_LEN);//串口的接收中断开启nwhile(1)n { n tx_len = FifoDataLen();//获取数据长度n if (tx_len > 0)n {n tx_len = (tx_len > 100) ? 100 : tx_len;//判读数据长度是否越界n FifoRead(tx_buf, tx_len);//读取在中断中写入FIFO缓存的数据n HAL_UART_Transmit(&huart1, tx_buf, tx_len, 1000);//将读到的数据通过串口发送出来n }n }nn接收回调函数中的处理nHAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)n{n if (FifoSpaceLen() >= 串口记录的接收数据长度)//判断写入FIFO空间的数据量是否大于接收的数据量n {n FifoWrite(huart->pRxBuffPtr, huart->RxXferCount);//想FIFO中写入数据n }n} n123456789101112131415161718192021222324252627n

该FIFO的处理机制中用的记录是通过uint32t类型进行记录的,可能在遇到超出其数据极限的情况,导致数据通信异常。(该类型的数据极限较大,为特殊情况可能出现的情况)。

第二类、FIFO处理机制如下:

/* 定义串口波特率和FIFO缓冲区大小,n分为发送缓冲区和接收缓冲区*/ n#if UART1_FIFO_EN == 1 n#define UART1_BAUD 115200 n#define UART1_TX_BUF_SIZE 1*1024 n#define UART1_RX_BUF_SIZE 1*1024n#endif nn/* 串口设备结构体n 设置发送、接收缓存区(长度),n 并设置两个变量,一个是指针,一个是计数n */ ntypedef struct n{ n USART_TypeDef *uart; /* STM32内部串口设备指针 */ nn uint8_t *pTxBuf; /* 发送缓冲区 */ n uint8_t *pRxBuf; /* 接收缓冲区 */ n uint16_t usTxBufSize; /* 发送缓冲区大小 */ n uint16_t usRxBufSize; /* 接收缓冲区大小 */nn __IO uint16_t usTxWrite; /* 发送缓冲区写指针 */ n __IO uint16_t usTxRead; /* 发送缓冲区读指针 */ n __IO uint16_t usTxCount; /* 等待发送的数据个数 */ n n __IO uint16_t usRxWrite; /* 接收缓冲区写指针 */ n __IO uint16_t usRxRead; /* 接收缓冲区读指针 */n __IO uint16_t usRxCount; /* 还未读取的新数据个数 */ n n void (*SendBefor)(void); /* 开始发送之前的回调函数指针(主要用于RS485切换到发送模式) */ n void (*SendOver)(void); /* 发送完毕的回调函数指针(主要用于RS485将发送模式切换为接收模式) */ n void (*ReciveNew)(uint8_t _byte); /* 串口收到数据的回调函数指针 */ n uint8_t Sending; /* 正在发送中 */nn }UART_T;nn/* 定义每个串口结构体变量 */ n#if UART1_FIFO_EN == 1nstatic UART_T g_tUart1; nstatic uint8_t g_TxBuf1[UART1_TX_BUF_SIZE]; /* 发送缓冲区 */ nstatic uint8_t g_RxBuf1[UART1_RX_BUF_SIZE]; /* 接收缓冲区 */ n#endifnn12345678910111213141516171819202122232425262728293031323334353637383940414243n

怎样才叫做回调函数?回调函数,是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数, 当这个指针被用为调用它所指向的函数时, 我们就说这是回调函数。

区别指针函数和函数指针:

//指针函数:nint *fun(int x,int y)nint *x=fun(4,5);n在调用指针函数时,需要同类型的指针来接收函数返回值n是函数,返回值时指针n属于数据类型n123456n//函数指针:nint (*fun)(int x,int y)nint x(int x,int y);nx=fun;nfun=&x;nx=(*fun)(1,3);n是指针,指向函数。n属于函数名称n12345678n

1.初始化串口FIFO对应的相关的变量

static void UartVarInit(void) n{ n #if UART1_FIFO_EN == 1 n g_tUart1.uart = USART1; /* STM32 串口设备 */ n g_tUart1.pTxBuf = g_TxBuf1; /* 发送缓冲区指针 */ n g_tUart1.pRxBuf = g_RxBuf1; /* 接收缓冲区指针 */ n g_tUart1.usTxBufSize = UART1_TX_BUF_SIZE; /* 发送缓冲区大小 */ n g_tUart1.usRxBufSize = UART1_RX_BUF_SIZE; /* 接收缓冲区大小 */ n g_tUart1.usTxWrite = 0; /* 发送FIFO写索引 */ n g_tUart1.usTxRead = 0; /* 发送FIFO读索引 */ n g_tUart1.usRxWrite = 0; /* 接收FIFO写索引 */ n g_tUart1.usRxRead = 0; /* 接收FIFO读索引 */ n g_tUart1.usRxCount = 0; /* 接收到的新数据个数 */ n g_tUart1.usTxCount = 0; /* 待发送的数据个数 */ n g_tUart1.SendBefor = 0; /* 发送数据前的回调函数 */ n g_tUart1.SendOver = 0; /* 发送完毕后的回调函数 */ n g_tUart1.ReciveNew = 0; /* 接收到新数据后的回调函数 */ n g_tUart1.Sending = 0; /* 正在发送中标志 */ n #endif n} n1234567891011121314151617181920n

明确中断服务程序的顺序:中断函数处理:void USART1_IRQHandler(void)—》 UART中断请求:HAL_UART_IRQHandler(UART_HandleTypeDef *huart)—》 中断使能:UART_Receive_IT— 》 中断回调函数 HAL_UART_RxCpltCallback(huart);

#if UART1_FIFO_EN == 1nvoid USART1_IRQHandler(void) //系统中串口的中断函数入口n{n UartIRQ(&g_tUart1);n}n#endifnn1234567n

嵌入式物联网需要学的东西真的非常多,千万不要学错了路线和内容,导致工资要不上去!

无偿分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全!某鱼上买估计至少要好几十。

点击这里找小助理0元领取:嵌入式物联网学习资料(头条)




2.编辑自定义的UART中断请求

static void UartIRQ(UART_T *_pUart)n{n uint32_t isrflags = READ_REG(_pUart->uart->ISR);n uint32_t cr1its = READ_REG(_pUart->uart->CR1);n uint32_t cr3its = READ_REG(_pUart->uart->CR3);nn if ((isrflags & USART_ISR_RXNE) != RESET)n {n /* 从串口接收数据寄存器读取数据存放到接收FIFO */n uint8_t ch;n ch = READ_REG(pUart->uart->RDR);n /* 读串口接收数据寄存器 */n _pUart->pRxBuf[_pUart->usRxWrite] = ch; /* 填入串口接收FIFO */n if (++_pUart->usRxWrite >= _pUart->usRxBufSize) /* 接收FIFO的写指针+1 */n {n _pUart->usRxWrite = 0;n }n if (_pUart->usRxCount < _pUart->usRxBufSize) /* 统计未处理的字节个数 */n {n _pUart->usRxCount++;n }n /* 回调函数,通知应用程序收到新数据,一般是发送1个消息或者设置一个标记 */n // if (_pUart->usRxWrite == _pUart->usRxRead)n // if (_pUart->usRxCount == 1)n {n if (_pUart->ReciveNew)n {n _pUart->ReciveNew(ch); /* 比如,交给MODBUS解码程序处理字节流 */n }n }n }n /* 处理发送缓冲区空中断 */n if (((isrflags & USART_ISR_TXE) != RESET) && (cr1its & USART_CR1_TXEIE) != RESET)n {n // if (_pUart->usTxRead == _pUart->usTxWrite)n if (_pUart->usTxCount == 0) /* 发送缓冲区已无数据可取 */n {n /* 发送缓冲区的数据已取完时, 禁止发送缓冲区空中断 (注意:此时最后1个数据还未真正发送完毕)*/n // USART_ITConfig(_pUart->uart, USART_IT_TXE, DISABLE);n CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TXEIE); /* 使能数据发送完毕中断 */n // USART_ITConfig(_pUart->uart, USART_IT_TC, ENABLE);n SET_BIT(_pUart->uart->CR1, USART_CR1_TCIE);n }n Else /* 还有数据等待发送 */n {n _pUart->Sending = 1; /* 从发送FIFO取1个字节写入串口发送数据寄存器 */n // USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]);n _pUart->uart->TDR = _pUart->pTxBuf[_pUart->usTxRead];n if (++_pUart->usTxRead >= _pUart->usTxBufSize)n {n _pUart->usTxRead = 0;n }n _pUart->usTxCount--;n }n }n /* 数据bit位全部发送完毕的中断 */n if (((isrflags & USART_ISR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))n {n // if (_pUart->usTxRead == _pUart->usTxWrite)n if (_pUart->usTxCount == 0)n { /* 如果发送FIFO的数据全部发送完毕,禁止数据发送完毕中断 */n // USART_ITConfig(_pUart->uart, USART_IT_TC, DISABLE);n CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TCIE); /* 回调函数, 一般用来处理RS485通信,将RS485芯片设置为接收模式,避免抢占总线 */n if (_pUart->SendOver)n {n _pUart->SendOver();n }n _pUart->Sending = 0;n }nn elsen { /* 正常情况下,不会进入此分支 */n /* 如果发送FIFO的数据还未完毕,则从发送FIFO取1个数据写入发送数据寄存器 */n // USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]);n _pUart->uart->TDR = _pUart->pTxBuf[_pUart->usTxRead];n if (++_pUart->usTxRead >= _pUart->usTxBufSize)n {n _pUart->usTxRead = 0;n }n _pUart->usTxCount--;n }n } /* 清除中断标志 */n SET_BIT(_pUart->uart->ICR, UART_CLEAR_PEF);n SET_BIT(_pUart->uart->ICR, UART_CLEAR_FEF);n SET_BIT(_pUart->uart->ICR, UART_CLEAR_NEF);n SET_BIT(_pUart->uart->ICR, UART_CLEAR_OREF);n SET_BIT(_pUart->uart->ICR, UART_CLEAR_IDLEF);n SET_BIT(_pUart->uart->ICR, UART_CLEAR_TCF);n SET_BIT(_pUart->uart->ICR, UART_CLEAR_LBDF);n SET_BIT(_pUart->uart->ICR, UART_CLEAR_CTSF);n SET_BIT(_pUart->uart->ICR, UART_CLEAR_CMF);n SET_BIT(_pUart->uart->ICR, UART_CLEAR_WUF);n SET_BIT(_pUart->uart->ICR, UART_CLEAR_TXFECF);n}n12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394n

3.填写数据到UART发送缓冲区。

并启动发送中断,中断处理函数发送完毕后,自动关闭发送中断 .

static void UartSend(UART_T *_pUart, uint8_t *_ucaBuf, uint16_t _usLen) n{ n uint16_t i; n for (i = 0; i < _usLen; i++) n { /* 如果发送缓冲区已经满了,则等待缓冲区空 */ n while (1) n { n __IO uint16_t usCount; DISABLE_INT(); n usCount = _pUart->usTxCount; n ENABLE_INT(); n if (usCount < _pUart->usTxBufSize) n { n break; n } n else if(usCount == _pUart->usTxBufSize)/* 数据已填满缓冲区 */ n { n if((_pUart->uart->CR1 & USART_CR1_TXEIE) == 0) n { n SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE); n } n } n } /* 将新数据填入发送缓冲区 */ n _pUart->pTxBuf[_pUart->usTxWrite] = _ucaBuf[i]; n DISABLE_INT(); n if (++_pUart->usTxWrite >= _pUart->usTxBufSize) n { n _pUart->usTxWrite = 0; n } n _pUart->usTxCount++; n ENABLE_INT(); n } n SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE); /* 使能发送中断(缓冲区空) */ n} nn12345678910111213141516171819202122232425262728293031323334n

4.向串口发送一组数据。

数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送

void comSendBuf(COM_PORT_E _ucPort, uint8_t *_ucaBuf, uint16_t _usLen) n{ n UART_T *pUart; n pUart = ComToUart(_ucPort); n if (pUart == 0) n {n return; n } n if (pUart-> != 0) n {n pUart->SendBefor(); /* 如果是RS485通信,可以在这个函数中将RS485设置为发送模式 */ n } n UartSend(pUart, _ucaBuf, _usLen); n}nn123456789101112131415n

向串口发送1个字节。数据放到发送缓冲区后立即返回, 由中断服务程序在后台完成发送

void comSendChar(COM_PORT_E _ucPort, uint8_t _ucByte)n { n comSendBuf(_ucPort, &_ucByte, 1); n} nn123456n

函数comSendChar是发送一个字节, 通过调用函数comSendBuf实现, 而函数comSendBuf又是通过调用函数UartSend实现, 这个函数是重点。

5.将COM端口号转换为UART指针

UART_T *ComToUart(COM_PORT_E _ucPort) n{ n if (_ucPort == COM1) n { n #if UART1_FIFO_EN == 1 n return &g_tUart1; n #else n return 0; n #endif n } n else n { n Error_Handler(__FILE__, __LINE__); n return 0; n } n}nn1234567891011121314151617n

6.从串口接收缓冲区读取1字节数据

static uint8_t UartGetChar(UART_T *_pUart, uint8_t *_pByte) n{ nn uint16_t usCount; /* usRxWrite 变量在中断函数中被改写,主程序读取该变量时,必须进行临界区保护 */ nn DISABLE_INT(); usCount = _pUart->usRxCount; ENABLE_INT(); /* 如果读和写索引相同,则返回0 */ n n //if (_pUart->usRxRead == usRxWrite) n if (usCount == 0) /* 已经没有数据 */ n { n return 0; n } n else n { n *_pByte = _pUart->pRxBuf[_pUart->usRxRead]; /* 从串口接收 FIFO取1个数据 */ n /* 改写FIFO读索引 */ nn DISABLE_INT();n n if (++_pUart->usRxRead >= _pUart->usRxBufSize) n { n _pUart->usRxRead = 0; n } n _pUart->usRxCount--; n ENABLE_INT(); n return 1; n }n n} n1234567891011121314151617181920212223242526272829n

从接收缓冲区读取1字节,非阻塞。无论有无数据均立即返回。

uint8_t comGetChar(COM_PORT_E _ucPort, uint8_t *_pByte) n{nn UART_T *pUart; n pUart = ComToUart(_ucPort); n if (pUart == 0)n { n return 0; n } n return UartGetChar(pUart, _pByte); n}n123456789101112n

接收数据的调用顺序是:comGetChar–》UartGetChar

7.判断发送缓冲区是否为空

uint8_t UartTxEmpty(COM_PORT_E _ucPort)n{n UART_T *pUart;n uint8_t Sending;n n pUart = ComToUart(_ucPort);n if (pUart == 0)n {n return 0;n }nn Sending = pUart->Sending;nn if (Sending != 0)n {n return 0;n }n return 1;n}nn1234567891011121314151617181920n

8.清零串口接收缓冲区

void comClearRxFifo(COM_PORT_E _ucPort)n{n UART_T *pUart;n pUart = ComToUart(_ucPort);n if (pUart == 0)n {n return;n }n pUart->usRxWrite = 0;n pUart->usRxRead = 0;n pUart->usRxCount = 0;n}n123456789101112n

9.清零串口发送缓冲区

void comClearTxFifo(COM_PORT_E _ucPort)n{n UART_T *pUart;nn pUart = ComToUart(_ucPort);n if (pUart == 0)n {n return;n }nn pUart->usTxWrite = 0;n pUart->usTxRead = 0;n pUart->usTxCount = 0;n}n1234567891011121314n

10.输入输出的重定向

int fputc(int ch, FILE *f)n{n#if 1 /* 将需要printf的字符通过串口中断FIFO发送出去,printf函数会立即返回 */n comSendChar(COM1, ch);n return ch;n#else /* 采用阻塞方式发送每个字符,等待数据发送完毕 */n /* 写一个字节到USART1 */n USART1->DR = ch;n /* 等待发送结束 */n while((USART1->SR & USART_SR_TC) == 0)n {}n return ch;n#endifn}nnnint fgetc(FILE *f)n{n#if 1 /* 从串口接收FIFO中取1个数据, 只有取到数据才返回 */n uint8_t ucData;n while(comGetChar(COM1, &ucData) == 0);n return ucData;n#elsen /* 等待接收到数据 */n while((USART1->SR & USART_SR_RXNE) == 0)n {}n return (int)USART1->DR;n#endifn}n1234567891011121314151617181920212223242526272829n

11.应用层初始化:

//FIFO串口初始化nUartVarInit(void);n//串口参数配置nvoid bsp_SetUartParam(USART_TypeDef *Instance, uint32_t BaudRate, uint32_t Parity, uint32_t Mode)n{n UART_HandleTypeDef UartHandle; n /*##-1- 配置串口硬件参数 ######################################*/n /* 异步串口模式 (UART Mode) */n /* 配置如下:n - 字长 = 8 位n - 停止位 = 1 个停止位n - 校验 = 参数Parityn - 波特率 = 参数BaudRaten - 硬件流控制关闭 (RTS and CTS signals) */n UartHandle.Instance = Instance;n UartHandle.Init.BaudRate = BaudRate;n UartHandle.Init.WordLength = UART_WORDLENGTH_8B;n UartHandle.Init.StopBits = UART_STOPBITS_1;n UartHandle.Init.Parity = Parity;n UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;n UartHandle.Init.Mode = Mode;n UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;n n if (HAL_UART_Init(&UartHandle) != HAL_OK)n {n Error_Handler(__FILE__, __LINE__);n }n}n//对应的串口波特率配置nvoid comSetBaud(COM_PORT_E _ucPort, uint32_t _BaudRate)n{n USART_TypeDef* USARTx;n USARTx = ComToUSARTx(_ucPort);n if (USARTx == 0)n {n return;n }n bsp_SetUartParam(USARTx, _BaudRate, UART_PARITY_NONE, UART_MODE_TX_RX);n}n//硬件初始化nvoid InitHardUart(void)n{nGPIO 复用....n /* 配置NVIC the NVIC for UART */ n HAL_NVIC_SetPriority(USART1_IRQn, 0, 1);n HAL_NVIC_EnableIRQ(USART1_IRQn);n n /* 配置波特率、奇偶校验 */n bsp_SetUartParam(USART1, UART1_BAUD, UART_PARITY_NONE, UART_MODE_TX_RX);nn CLEAR_BIT(USART1->SR, USART_SR_TC); /* 清除TC发送完成标志 */n CLEAR_BIT(USART1->SR, USART_SR_RXNE); /* 清除RXNE接收标志 */n // USART_CR1_PEIE | USART_CR1_RXNEIEn SET_BIT(USART1->CR1, USART_CR1_RXNEIE); /* 使能PE. RX接受中断 */n}n在主循环中nncomGetChar(COM1, &read);//获取一个字节数据ncomSendBuf(COM1, (uint8_t *)buf, strlen(buf));//发送数据n12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061n


文章链接:https://mp.weixin.qq.com/s/h1L6sQ8OOiFf3KJq0ZjnVg

转载自:技术让梦想更伟大

文章来源:对串口接收FIFO处理机制的解读

版权声明:本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。

‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧ END ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧