一、FIFO队列
FIFO是First Input First Output(先入先出)的缩写,是一种常见的队列数据结构。
FIFO队列主要用于不同时钟域之间的数据传输,特别适用于一个时钟域读写速度快,另一个时钟域读写速度慢的场景。
FIFO的本质操作是将接收到的数据存储在一个线性数组中,在读取或写入时通过指针的自增来遍历数据。
它的作用是作为一个缓冲区,在数据传输过程中防止数据丢失,并且可以减少频繁的总线操作。同时,FIFO也适合用于支持DMA(直接内存访问)操作。
在FIFO中,需要理解两个重要的成员:
-
宽度:指一次读写操作的数据位数。
-
深度:表示FIFO队列可以存储多少个宽度的数据。例如,一个深度为8的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)
1234567891011121314
1、初始化FIFO
fifo_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);//防止长度为超出实际拥有的数据长度,即让读取的长度在 (0size - 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
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 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 while (1)
{
__IO uint16_t usCount; DISABLE_INT();
usCount = _pUart->usTxCount;
ENABLE_INT();
if (usCount 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
以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !