对串口接收FIFO处理机制的解读
一、FIFOFIFO
是 First Input First Output 的缩写,先入先出队列。
使用的场景:一般是在 不同的时钟域 之间的 数据传输 (简单理解即:一个(读写)快,另外的一个(读写)慢的场景中。)
本质操作:就是将收的数据存储的一个 线性的数组 中,通过指针指向该数组的 自加1(偏移) 来遍历数据,达到 读取或者写入 的目的。
作用:起到 缓冲 环节,可防止数据的丢失。
对数据量大的进行存储, 避免频繁的总线操作 。并且可满足dma的操作。
在fifo中需要理解连个重要成员:
宽度:指 一次 写读操作的 数据位数 。
深度:存储多少个宽度的数据。(如:存储8个16位宽的数据)。 第一类、FIFO处理机制如下:
FIFO信息的定义: /* 该结构体定义成员有 缓存区, 长度, 输出, 输入的计数。 */ typedef struct fifo_t { uint8_t *buf; uint32_t size; uint32_t in; uint32_t out; } _fifo_str; #define min(x,y) ((x) < (y)?(x):(y)) 1234567891011121314 1、初始化FIFOfifo_str fifo_str; int FifoInit(uint8_t *fifo_addr, uint32_t fifo_size)//初始化fifo { _fifo_str *p = &fifo_str; if(fifo_addr == NULL || fifo_size == 0)//判断数据是否为空 return -1; memset((char *)p, 0, sizeof(_fifo_str));//初始化结构体 p->buf = fifo_addr;//对应宽度 p->in = 0;//输入计数 p->out = 0;//输出计数 p->size = fifo_size;//对应深度 return 0; } 12345678910111213141516 2、数据的长度获取//数据的实际使用数据空间长度 int FifoDataLen(void) { _fifo_str *p = &fifo_str; return (p->in - p->out);//输入计数-输出计数 } //剩余数据空间长度 int FifoSpaceLen(void) { _fifo_str *p = &fifo_str; return (p->size - (p->in - p->out));//定义长度-(实际长度) } 12345678910111213 3、FIFO的进和出处理//获取fifo数据 //数据的内容缓存区,要读的长度 int FifoRead(uint8_t *buf, uint32_t len) { uint32_t i = 0, j = 0; _fifo_str *p = &fifo_str; j = (p->out % p->size);//获取剩余空间长度未读量 len = min(len, p->in - p->out);//防止长度为超出实际拥有的数据长度,即让读取的长度在 (0<设定len<定义的缓存区长度len )这间的实际长度 i = min(len, p->size - j);//获取实际内容的长度,的数据长度 memcpy(buf, p->buf + j, i);//将数据通道里的数据拷贝给缓存区 memcpy(buf + i, p->buf, len - i);//将未有数据的区域存入,对应为写入数据的区域数据(即,有数据的填数据,没数据的地方补0) p->out += len;//已读的数据量 return len;//实际读到的数据长度 } //对fifo写入数据 int FifoWrite(uint8_t *buf, uint32_t len) { uint32_t i = 0, j = 0; _fifo_str *p = &fifo_str; j = p->in % p->size;//获取要写入的剩余空间长度 len = min(len, p->size - p->in + p->out);//得到实际写入的长度 i = min(len, p->size - j);//实际写入数据的长度 memcpy(p->buf + j, buf, i);//将写入的数据的内容拷贝值数据中。 memcpy(p->buf, buf + i, len - i);//补充多余空间的内容 p->in += len;//记录实际写入数据的数量 return len;//返回写入的长度 } 123456789101112131415161718192021222324252627282930 4、置位记录量//清空fifo 中的记录量 void FifoClear(void) { _fifo_str *p = &fifo_str; p->in = 0; p->out = 0; } 1234567 5、应用处理机制#define LEN 2048 uint8_t pdata[LEN] = {0}; FifoInit(pdata, LEN);//初始化FIFO uint8_t buf[32] = {0}; int tx_len = 0; uint8_t tx_buf[100] = {0}; HAL_UART_Receive_IT(&huart1, buf, IT_LEN);//串口的接收中断开启 while(1) { tx_len = FifoDataLen();//获取数据长度 if (tx_len > 0) { tx_len = (tx_len > 100) ? 100 : tx_len;//判读数据长度是否越界 FifoRead(tx_buf, tx_len);//读取在中断中写入FIFO缓存的数据 HAL_UART_Transmit(&huart1, tx_buf, tx_len, 1000);//将读到的数据通过串口发送出来 } } 接收回调函数中的处理 HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (FifoSpaceLen() >= 串口记录的接收数据长度)//判断写入FIFO空间的数据量是否大于接收的数据量 { FifoWrite(huart->pRxBuffPtr, huart->RxXferCount);//想FIFO中写入数据 } } 123456789101112131415161718192021222324252627
该FIFO的处理机制中用的记录是通过uint32t类型进行记录的,可能在遇到超出其数据极限的情况,导致数据通信异常。(该类型的数据极限较大,为特殊情况可能出现的情况)。 第二类、FIFO处理机制如下:/* 定义串口波特率和FIFO缓冲区大小, 分为发送缓冲区和接收缓冲区*/ #if UART1_FIFO_EN == 1 #define UART1_BAUD 115200 #define UART1_TX_BUF_SIZE 1*1024 #define UART1_RX_BUF_SIZE 1*1024 #endif /* 串口设备结构体 设置发送、接收缓存区(长度), 并设置两个变量,一个是指针,一个是计数 */ typedef struct { USART_TypeDef *uart; /* STM32内部串口设备指针 */ uint8_t *pTxBuf; /* 发送缓冲区 */ uint8_t *pRxBuf; /* 接收缓冲区 */ uint16_t usTxBufSize; /* 发送缓冲区大小 */ uint16_t usRxBufSize; /* 接收缓冲区大小 */ __IO uint16_t usTxWrite; /* 发送缓冲区写指针 */ __IO uint16_t usTxRead; /* 发送缓冲区读指针 */ __IO uint16_t usTxCount; /* 等待发送的数据个数 */ __IO uint16_t usRxWrite; /* 接收缓冲区写指针 */ __IO uint16_t usRxRead; /* 接收缓冲区读指针 */ __IO uint16_t usRxCount; /* 还未读取的新数据个数 */ void (*SendBefor)(void); /* 开始发送之前的回调函数指针(主要用于RS485切换到发送模式) */ void (*SendOver)(void); /* 发送完毕的回调函数指针(主要用于RS485将发送模式切换为接收模式) */ void (*ReciveNew)(uint8_t _byte); /* 串口收到数据的回调函数指针 */ uint8_t Sending; /* 正在发送中 */ }UART_T; /* 定义每个串口结构体变量 */ #if UART1_FIFO_EN == 1 static UART_T g_tUart1; static uint8_t g_TxBuf1[UART1_TX_BUF_SIZE]; /* 发送缓冲区 */ static uint8_t g_RxBuf1[UART1_RX_BUF_SIZE]; /* 接收缓冲区 */ #endif 12345678910111213141516171819202122232425262728293031323334353637383940414243
怎样才叫做回调函数?回调函数,是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数, 当这个指针被用为调用它所指向的函数时, 我们就说这是回调函数。
区别指针函数和函数指针: //指针函数: int *fun(int x,int y) int *x=fun(4,5); 在调用指针函数时,需要同类型的指针来接收函数返回值 是函数,返回值时指针 属于数据类型 123456 //函数指针: int (*fun)(int x,int y) int x(int x,int y); x=fun; fun=&x; x=(*fun)(1,3); 是指针,指向函数。 属于函数名称 12345678 1.初始化串口FIFO对应的相关的变量static void UartVarInit(void) { #if UART1_FIFO_EN == 1 g_tUart1.uart = USART1; /* STM32 串口设备 */ g_tUart1.pTxBuf = g_TxBuf1; /* 发送缓冲区指针 */ g_tUart1.pRxBuf = g_RxBuf1; /* 接收缓冲区指针 */ g_tUart1.usTxBufSize = UART1_TX_BUF_SIZE; /* 发送缓冲区大小 */ g_tUart1.usRxBufSize = UART1_RX_BUF_SIZE; /* 接收缓冲区大小 */ g_tUart1.usTxWrite = 0; /* 发送FIFO写索引 */ g_tUart1.usTxRead = 0; /* 发送FIFO读索引 */ g_tUart1.usRxWrite = 0; /* 接收FIFO写索引 */ g_tUart1.usRxRead = 0; /* 接收FIFO读索引 */ g_tUart1.usRxCount = 0; /* 接收到的新数据个数 */ g_tUart1.usTxCount = 0; /* 待发送的数据个数 */ g_tUart1.SendBefor = 0; /* 发送数据前的回调函数 */ g_tUart1.SendOver = 0; /* 发送完毕后的回调函数 */ g_tUart1.ReciveNew = 0; /* 接收到新数据后的回调函数 */ g_tUart1.Sending = 0; /* 正在发送中标志 */ #endif } 1234567891011121314151617181920
明确中断服务程序的顺序:中断函数处理:void USART1_IRQHandler(void)—》 UART中断请求:HAL_UART_IRQHandler(UART_HandleTypeDef *huart)—》 中断使能:UART_Receive_IT— 》 中断回调函数 HAL_UART_RxCpltCallback(huart); #if UART1_FIFO_EN == 1 void USART1_IRQHandler(void) //系统中串口的中断函数入口 { UartIRQ(&g_tUart1); } #endif 1234567
嵌入式物联网需要学的东西真的非常多,千万不要学错了路线和内容,导致工资要不上去!
无偿分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全!某鱼上买估计至少要好几十。
点击这里找小助理0元领取:嵌入式物联网学习资料(头条)
2.编辑自定义的UART中断请求static void UartIRQ(UART_T *_pUart) { uint32_t isrflags = READ_REG(_pUart->uart->ISR); uint32_t cr1its = READ_REG(_pUart->uart->CR1); uint32_t cr3its = READ_REG(_pUart->uart->CR3); if ((isrflags & USART_ISR_RXNE) != RESET) { /* 从串口接收数据寄存器读取数据存放到接收FIFO */ uint8_t ch; ch = READ_REG(pUart->uart->RDR); /* 读串口接收数据寄存器 */ _pUart->pRxBuf[_pUart->usRxWrite] = ch; /* 填入串口接收FIFO */ if (++_pUart->usRxWrite >= _pUart->usRxBufSize) /* 接收FIFO的写指针+1 */ { _pUart->usRxWrite = 0; } if (_pUart->usRxCount < _pUart->usRxBufSize) /* 统计未处理的字节个数 */ { _pUart->usRxCount++; } /* 回调函数,通知应用程序收到新数据,一般是发送1个消息或者设置一个标记 */ // if (_pUart->usRxWrite == _pUart->usRxRead) // if (_pUart->usRxCount == 1) { if (_pUart->ReciveNew) { _pUart->ReciveNew(ch); /* 比如,交给MODBUS解码程序处理字节流 */ } } } /* 处理发送缓冲区空中断 */ if (((isrflags & USART_ISR_TXE) != RESET) && (cr1its & USART_CR1_TXEIE) != RESET) { // if (_pUart->usTxRead == _pUart->usTxWrite) if (_pUart->usTxCount == 0) /* 发送缓冲区已无数据可取 */ { /* 发送缓冲区的数据已取完时, 禁止发送缓冲区空中断 (注意:此时最后1个数据还未真正发送完毕)*/ // USART_ITConfig(_pUart->uart, USART_IT_TXE, DISABLE); CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TXEIE); /* 使能数据发送完毕中断 */ // USART_ITConfig(_pUart->uart, USART_IT_TC, ENABLE); SET_BIT(_pUart->uart->CR1, USART_CR1_TCIE); } Else /* 还有数据等待发送 */ { _pUart->Sending = 1; /* 从发送FIFO取1个字节写入串口发送数据寄存器 */ // USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]); _pUart->uart->TDR = _pUart->pTxBuf[_pUart->usTxRead]; if (++_pUart->usTxRead >= _pUart->usTxBufSize) { _pUart->usTxRead = 0; } _pUart->usTxCount--; } } /* 数据bit位全部发送完毕的中断 */ if (((isrflags & USART_ISR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET)) { // if (_pUart->usTxRead == _pUart->usTxWrite) if (_pUart->usTxCount == 0) { /* 如果发送FIFO的数据全部发送完毕,禁止数据发送完毕中断 */ // USART_ITConfig(_pUart->uart, USART_IT_TC, DISABLE); CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TCIE); /* 回调函数, 一般用来处理RS485通信,将RS485芯片设置为接收模式,避免抢占总线 */ if (_pUart->SendOver) { _pUart->SendOver(); } _pUart->Sending = 0; } else { /* 正常情况下,不会进入此分支 */ /* 如果发送FIFO的数据还未完毕,则从发送FIFO取1个数据写入发送数据寄存器 */ // USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]); _pUart->uart->TDR = _pUart->pTxBuf[_pUart->usTxRead]; if (++_pUart->usTxRead >= _pUart->usTxBufSize) { _pUart->usTxRead = 0; } _pUart->usTxCount--; } } /* 清除中断标志 */ SET_BIT(_pUart->uart->ICR, UART_CLEAR_PEF); SET_BIT(_pUart->uart->ICR, UART_CLEAR_FEF); SET_BIT(_pUart->uart->ICR, UART_CLEAR_NEF); SET_BIT(_pUart->uart->ICR, UART_CLEAR_OREF); SET_BIT(_pUart->uart->ICR, UART_CLEAR_IDLEF); SET_BIT(_pUart->uart->ICR, UART_CLEAR_TCF); SET_BIT(_pUart->uart->ICR, UART_CLEAR_LBDF); SET_BIT(_pUart->uart->ICR, UART_CLEAR_CTSF); SET_BIT(_pUart->uart->ICR, UART_CLEAR_CMF); SET_BIT(_pUart->uart->ICR, UART_CLEAR_WUF); SET_BIT(_pUart->uart->ICR, UART_CLEAR_TXFECF); } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394 3.填写数据到UART发送缓冲区。
并启动发送中断,中断处理函数发送完毕后,自动关闭发送中断 . static void UartSend(UART_T *_pUart, uint8_t *_ucaBuf, uint16_t _usLen) { uint16_t i; for (i = 0; i < _usLen; i++) { /* 如果发送缓冲区已经满了,则等待缓冲区空 */ while (1) { __IO uint16_t usCount; DISABLE_INT(); usCount = _pUart->usTxCount; ENABLE_INT(); if (usCount < _pUart->usTxBufSize) { break; } else if(usCount == _pUart->usTxBufSize)/* 数据已填满缓冲区 */ { if((_pUart->uart->CR1 & USART_CR1_TXEIE) == 0) { SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE); } } } /* 将新数据填入发送缓冲区 */ _pUart->pTxBuf[_pUart->usTxWrite] = _ucaBuf[i]; DISABLE_INT(); if (++_pUart->usTxWrite >= _pUart->usTxBufSize) { _pUart->usTxWrite = 0; } _pUart->usTxCount++; ENABLE_INT(); } SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE); /* 使能发送中断(缓冲区空) */ } 12345678910111213141516171819202122232425262728293031323334 4.向串口发送一组数据。
数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送 void comSendBuf(COM_PORT_E _ucPort, uint8_t *_ucaBuf, uint16_t _usLen) { UART_T *pUart; pUart = ComToUart(_ucPort); if (pUart == 0) { return; } if (pUart-> != 0) { pUart->SendBefor(); /* 如果是RS485通信,可以在这个函数中将RS485设置为发送模式 */ } UartSend(pUart, _ucaBuf, _usLen); } 123456789101112131415
向串口发送1个字节。数据放到发送缓冲区后立即返回, 由中断服务程序在后台完成发送 void comSendChar(COM_PORT_E _ucPort, uint8_t _ucByte) { comSendBuf(_ucPort, &_ucByte, 1); } 123456
函数comSendChar是发送一个字节, 通过调用函数comSendBuf实现, 而函数comSendBuf又是通过调用函数UartSend实现, 这个函数是重点。 5.将COM端口号转换为UART指针UART_T *ComToUart(COM_PORT_E _ucPort) { if (_ucPort == COM1) { #if UART1_FIFO_EN == 1 return &g_tUart1; #else return 0; #endif } else { Error_Handler(__FILE__, __LINE__); return 0; } } 1234567891011121314151617 6.从串口接收缓冲区读取1字节数据static uint8_t UartGetChar(UART_T *_pUart, uint8_t *_pByte) { uint16_t usCount; /* usRxWrite 变量在中断函数中被改写,主程序读取该变量时,必须进行临界区保护 */ DISABLE_INT(); usCount = _pUart->usRxCount; ENABLE_INT(); /* 如果读和写索引相同,则返回0 */ //if (_pUart->usRxRead == usRxWrite) if (usCount == 0) /* 已经没有数据 */ { return 0; } else { *_pByte = _pUart->pRxBuf[_pUart->usRxRead]; /* 从串口接收 FIFO取1个数据 */ /* 改写FIFO读索引 */ DISABLE_INT(); if (++_pUart->usRxRead >= _pUart->usRxBufSize) { _pUart->usRxRead = 0; } _pUart->usRxCount--; ENABLE_INT(); return 1; } } 1234567891011121314151617181920212223242526272829
从接收缓冲区读取1字节,非阻塞。无论有无数据均立即返回。 uint8_t comGetChar(COM_PORT_E _ucPort, uint8_t *_pByte) { UART_T *pUart; pUart = ComToUart(_ucPort); if (pUart == 0) { return 0; } return UartGetChar(pUart, _pByte); } 123456789101112
接收数据的调用顺序是:comGetChar–》UartGetChar 7.判断发送缓冲区是否为空uint8_t UartTxEmpty(COM_PORT_E _ucPort) { UART_T *pUart; uint8_t Sending; pUart = ComToUart(_ucPort); if (pUart == 0) { return 0; } Sending = pUart->Sending; if (Sending != 0) { return 0; } return 1; } 1234567891011121314151617181920 8.清零串口接收缓冲区void comClearRxFifo(COM_PORT_E _ucPort) { UART_T *pUart; pUart = ComToUart(_ucPort); if (pUart == 0) { return; } pUart->usRxWrite = 0; pUart->usRxRead = 0; pUart->usRxCount = 0; } 123456789101112 9.清零串口发送缓冲区void comClearTxFifo(COM_PORT_E _ucPort) { UART_T *pUart; pUart = ComToUart(_ucPort); if (pUart == 0) { return; } pUart->usTxWrite = 0; pUart->usTxRead = 0; pUart->usTxCount = 0; } 1234567891011121314 10.输入输出的重定向int fputc(int ch, FILE *f) { #if 1 /* 将需要printf的字符通过串口中断FIFO发送出去,printf函数会立即返回 */ comSendChar(COM1, ch); return ch; #else /* 采用阻塞方式发送每个字符,等待数据发送完毕 */ /* 写一个字节到USART1 */ USART1->DR = ch; /* 等待发送结束 */ while((USART1->SR & USART_SR_TC) == 0) {} return ch; #endif } int fgetc(FILE *f) { #if 1 /* 从串口接收FIFO中取1个数据, 只有取到数据才返回 */ uint8_t ucData; while(comGetChar(COM1, &ucData) == 0); return ucData; #else /* 等待接收到数据 */ while((USART1->SR & USART_SR_RXNE) == 0) {} return (int)USART1->DR; #endif } 1234567891011121314151617181920212223242526272829 11.应用层初始化://FIFO串口初始化 UartVarInit(void); //串口参数配置 void bsp_SetUartParam(USART_TypeDef *Instance, uint32_t BaudRate, uint32_t Parity, uint32_t Mode) { UART_HandleTypeDef UartHandle; /*##-1- 配置串口硬件参数 ######################################*/ /* 异步串口模式 (UART Mode) */ /* 配置如下: - 字长 = 8 位 - 停止位 = 1 个停止位 - 校验 = 参数Parity - 波特率 = 参数BaudRate - 硬件流控制关闭 (RTS and CTS signals) */ UartHandle.Instance = Instance; UartHandle.Init.BaudRate = BaudRate; UartHandle.Init.WordLength = UART_WORDLENGTH_8B; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = Parity; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = Mode; UartHandle.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&UartHandle) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } } //对应的串口波特率配置 void comSetBaud(COM_PORT_E _ucPort, uint32_t _BaudRate) { USART_TypeDef* USARTx; USARTx = ComToUSARTx(_ucPort); if (USARTx == 0) { return; } bsp_SetUartParam(USARTx, _BaudRate, UART_PARITY_NONE, UART_MODE_TX_RX); } //硬件初始化 void InitHardUart(void) { GPIO 复用.... /* 配置NVIC the NVIC for UART */ HAL_NVIC_SetPriority(USART1_IRQn, 0, 1); HAL_NVIC_EnableIRQ(USART1_IRQn); /* 配置波特率、奇偶校验 */ bsp_SetUartParam(USART1, UART1_BAUD, UART_PARITY_NONE, UART_MODE_TX_RX); CLEAR_BIT(USART1->SR, USART_SR_TC); /* 清除TC发送完成标志 */ CLEAR_BIT(USART1->SR, USART_SR_RXNE); /* 清除RXNE接收标志 */ // USART_CR1_PEIE | USART_CR1_RXNEIE SET_BIT(USART1->CR1, USART_CR1_RXNEIE); /* 使能PE. RX接受中断 */ } 在主循环中 comGetChar(COM1, &read);//获取一个字节数据 comSendBuf(COM1, (uint8_t *)buf, strlen(buf));//发送数据 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
文章链接:https://mp.weixin.qq.com/s/h1L6sQ8OOiFf3KJq0ZjnVg
转载自:技术让梦想更伟大
文章来源:对串口接收FIFO处理机制的解读
版权声明:本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。
‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧ END ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧