带有FIFO硬件缓存的串口
51单片机的串口并没有配置FIFO硬件,故收发数据时只能一字节一字节地收发。最近接触了FIFO(先进先出)的硬件缓存,这样串口的配置就更多了一个维度。
以CH58x系列单片机为例,在CH583的手册中(笔者手头的是CH582m的板子,与CH583用法一致,只有部分硬件不同),有关于串口和FIFO配置的说明。具体应用可以查看笔者关于串口透传的一篇随笔。
9.3.1 波特率计算
1)计算串口内部基准时钟 Fuart,设置 R8_UARTx_DIV 寄存器,最大值 127,通常写入 1。
2)计算波特率,设置 R16_UARTx_DL 寄存器。 波特率公式 =Fsys * 2 / R8_UARTx_DIV / 16 / R16_UARTx_DL。 //通过GetSysClock()这个函数获得主频
9.3.2 串口发送
串口发送的“THR 寄存器空”中断 UART_II_THR_EMPTY 是指当前发送 FIFO 空。
当读取 IIR 寄存器后,该中断被清除,或者当向 THR 写入下一个数据后,该中断也能被清除。 //IIR:中断识别寄存器 THR:发送保存寄存器
如果仅仅是向 THR写入 一个字节,那么由于该字节很快被转移到发送移位寄存器 TSR 中开始发送,所以很快会再次产生发送 THR 寄存器空中断的请求,此时可以写入下一个准备发送的数据。
当 TSR 寄存器中的数据被全部移出 后,串口发送才真正完成,此时 LSR 寄存器的 RB_LSR_TX_ALL_EMP 位变为 1 有效。 //TSR:发送移位寄存器
在中断触发方式下,当收到串口发送保持寄存器 THR 空的中断后,如果已使能 FIFO,那么可以向 THR 寄存器及 FIFO 一次写入最多 8 字节,然后控制器会按顺序自动发送;
如果禁止 FIFO,那么一 次只能写入一个字节; //使能FIFO后,向THR写入的数据先存放于FIFO中,自动以此发送,一次可以写入多个字节
如果没有数据需要发送,那么可以直接退出(之前读取 IIR 时已经自动清除中断)。
在查询方式下,可以根据 LSR 寄存器的 RB_LSR_TX_FIFO_EMP 位判断发送 FIFO 是否为空,当此位 为 1 则可以向 THR 寄存器及 FIFO 写入数据,如果使能 FIFO,那么一次可以写入最多 8 个字节。
也可读取 R8_UARTx_TFC 寄存器判断当前 FIFO 中待发送的剩余数据个数,如果不等于8,则可继 续向 FIFO 中写入待发送数据,这种方式可以节约填充时间。
9.3.3 串口接收 //假设设置FIFO设置为7个字节的缓存
串口接收数据可用中断 UART_II_RECV_RDY 是指接收 FIFO 中的已有数据字节数已经到或超过由FCR 寄存器的 RB_FCR_FIFO_TRIG 设置选择的 FIFO 触发点。 //FIFO中的数据达到了设定的7个字节,会触发_RECV_RDY ,若取出这7个字节,FIFO中会空
当从 RBR 读取数据使 FIFO 字数低于 FIFO 触发点时,该中断被清除。 //RBR:接收缓存寄存器
串口接收数据超时中断 UART_II_RECV_TOUT 是指接收 FIFO 中至少有一个字节的数据,
并且从上一次串口接收到数据和从上一次被系统取走数据开始,已经等待了相当于接收 4 个数据的时间。 //这里的“4”个数据的时间与FIFO触发设置的“4”字节无关,注意:FIFO中要留有一个数据进行比较,该标志才能有效
当再次接收到一个新的数据后,该中断被清除,或者当单片读取一次 RBR 寄存器后,该中断也能被清除。 //接收到新数据或者读取RBR后,超时标志位清除
当接收FIFO全空时, LSR寄存器的RB_LSR_DATA_RDY位为0,当接收FIFO中有数据时, RB_LSR_DATA_RDY 位为 1 有效。 //LSR:线路状态寄存器 _RDY标志位为1时有效,表示FIFO中有数据
在中断触发方式下,当收到串口接收数据超时的中断后,可以读取 R8_UARTx_RFC 寄存器查询当前 FIFO 中剩余数据计数,直接读取全部数据, //超时中断后,判断为没有数据了,一次全部读取FIFO中的数据或者不断查询LSR的标志位不断读取
或者不断查询 LSR 寄存器的 RB_LSR_DATA_RDY,如果 此位有效则读数据,直到此位无效。
当收到串口接收数据可用的中断后,可以先从 RBR 寄存器一次性读取 RB_FCR_FIFO_TRIG 设定字节个数的数据, //收到可用中断后,可以从_TRIG寄存器读取设定字节的个数,亦可以结合_RDY和_RFC读取所有数
或者也可以根据 RB_LSR_DATA_RDY 位和 R8_UARTx_RFC 寄存器读取当前 FIFO 中所有数据。
在查询方式下,可以根据 LSR 寄存器的 RB_LSR_DATA_RDY 位判断接收 FIFO 是否为空,或读取 R8_UARTx_RFC 寄存器获取当前 FIFO 中数据计数,来获取串口接收的所有数据。
//以下为串口1初始化函数 void u1_init() { uint32_t x; /* 配置串口1:先配置IO口模式,再配置串口 */ GPIOA_SetBits(GPIO_Pin_9); //先将PA9置位为高 GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU); // RXD-配置上拉输入 GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA); // TXD-配置推挽输出,注意要先让IO口输出高电平 x = 10 * GetSysClock() / 8 / 115200; //串口1定为115200波特率 x = (x + 5) / 10; //整型运算中四舍五入 R16_UART1_DL = (uint16_t)x; //给波特率寄存器赋值 R8_UART1_FCR = (2 << 6) | RB_FCR_TX_FIFO_CLR | RB_FCR_RX_FIFO_CLR | RB_FCR_FIFO_EN; //FIFO控制寄存器配置,10左移6位:触发点4字节;收发FIFO数据都清空;FIFO使能 R8_UART1_LCR = RB_LCR_WORD_SZ; //线路控制寄存器配置,选择串口数据长度 R8_UART1_IER = RB_IER_TXD_EN | RB_IER_RECV_RDY | RB_IER_LINE_STAT; //中断使能控制器配置,TX引脚输出使能;接收中断使能;接收线路状态中断使能 R8_UART1_MCR |= RB_MCR_INT_OE; //调制解调器控制寄存器配置,允许串口的中断请求输出 R8_UART1_DIV = 1; //用于波特率配置,参考手册中的公式 PFIC_EnableIRQ(UART1_IRQn); //中断注册 } //下为中断服务函数 __INTERRUPT __HIGH_CODE void UART1_IRQHandler(void) { volatile uint8_t i; switch(UART1_GetITFlag()) { //略掉一个case分支 case UART_II_RECV_RDY: // 数据达到设置触发点 for(i = 0; i != trigB-1; i++)//若要能够走到_TOUT的case分支,这里需要留一个字节数据,故这里为trigB-1(FIFO触发字节-1) { RxBuff[i] = UART1_RecvByte(); UART1_SendByte(RxBuff[i]); } break; case UART_II_RECV_TOUT: // 接收超时,暂时一帧数据接收完成 i = UART1_RecvString(RxBuff); UART1_SendString(RxBuff, i); break; //略掉几个case分支 default: break; } }