大家好,这里是程序员杰克。一名平平无奇的嵌入式软件工程师。
上篇推文对Ultrascale+ ZYNQ PS端uart controller进行了介绍和描述。对于开发者而言,最重要的是实际的工程应用。对于PS端的UART的串口回环通信使用,网上资料一搜一大把,此处不进行总结和分享。
本篇推文主要是分享实际应用场景下,PS端接收不定长数据的实例。
下面正式进入本章推送的内容。
01 实现原理和描述
STM32串口接收不定长数据有一种是通过接收中断和接收空闲中断来实现。本篇PS端实现串口接收不定长数据的思路类似(ZYNQ等皆适用)。本示例通过设置rxFIFO的缓存阈值触发中断、接收超时中断来实现串口接收不定长数据。
串口FIFO中断
在UG1085技术参考手册里面,对uart controller的FIFO中断描述如下图所示:
上图中,我们需要用到的知识点如红框所示:
-
RxFIFO的缓存阈值由Rcvr_FIFO_trigger_level[RTRIG]位设置
-
Rcvr_FIFO_trigger_level[RTRIG]为6bit,触发阈值最大可设置64-1
在UG1085技术参考手册(MPSOC)中并没有uart寄存器的详细信息,本篇推文对接收触发阈值、接收超时的寄存器配置参考UG585技术参考手册的内容(ZYNQ)进行描述,两者的寄存器内容是一致的。
接收触发阈值寄存器
设置RxFIFO接收阈值触发中断配置寄存器如下图所示:
特别说明:
从寄存器的描述中RTRIG位宽为6bit,即RxFIFO接收触发阈值最大为64-1;
接收超时寄存器
设置接收超时配置寄存器如下图所示:
特别说明:
从寄存器的描述中RTO位宽为8bit,即RxFIFO接收触发阈值最大为256-1;
02 代码实现
前面已对实现PS端串口接收不定长数据所使用的寄存器进行了描述,本章节开始对使用的代码进行描述。
实现思路
示例的串口接收不定长数据的实现思路如下:
-
设置RxFIFO触发阈值,用于接收数据到缓存buffer
-
设置接收空闲超时阈值,用于表示当前帧传输完成
串口接收不定长数据配置过程
对于Ultrascale+ ZYNQ而言,实现PS端串口接收不定长数据的配置实现步骤如下:
-
-
初始化串口设备
-
设置串口工作模式
-
设置串口通信的数据格式
-
配置RxFIFO触发中断阈值
-
配置接收超时阈值
-
使能接收FIFO阈值中断、接收超时中断中断
-
中断接收处理
-
用户逻辑处理
-
官方API描述
XLINX提供了相关的uart controller的API函数供使用。下表罗列了本示例使用的API函数(仅描述功能, 具体函数自行查看)。
-
uart相关API
API名称 | 类型 | 功能说明 |
XUartPs_LookupConfig() | 函数 | 在uart设备表中获取如基地址、设备号等外设配置参数. |
XUartPs_CfgInitialize() | 配置uart设备结构体的初始化参数内容. | |
XUartPs_SetOperMode() | 设置uart的运行模式(正常/自动回传等). | |
XUartPs_SetDataFormat() | 设置uart的数据格式. 如波特率、数据位、奇偶位、停止位等参数. | |
XUartPs_SetFifoThreshold() | 设置RxFIFO触发中断的阈值. | |
XUartPs_SetRecvTimeout() | 设置接收空闲中断的时间阈值(以波特率时钟为节拍). | |
XUartPs_SetInterruptMask() | 设置uart的中断掩码寄存器的值, 控制中断使能. | |
XUartPs_ReceiveBuffer() | 读取RxFIFO的值到设定的缓存buffer中. | |
XUartPs_Recv() | RxFIFO有数据时,从FIFO中读取数据; RxFIFO无数据时,初始化自定义接收buffer. | |
XUartPs_WriteReg() | 宏定义 | 向uart设备的寄存器写入数据 |
XUartPs_ReadReg() | 读取uart设备的寄存器数据 |
-
GIC相关API
API名称 | 类型 | 功能说明 |
XScuGic_LookupConfig() | 函数 | 在gic设备表中获取如基地址、设备号等外设配置参数. |
XScuGic_CfgInitialize() | 配置gic设备结构体的初始化参数内容. | |
Xil_ExceptionInit() | 通用API, 用于出书哈异常向量处理程序函数. 在ARM Cortex-A53/R5/A9等硬核无任何操作. | |
Xil_ExceptionRegisterHandler() | 用于gic设备的全局中断处理回调函数的注册. | |
XScuGic_Connect() | 用于向gic注册设备的中断回调函数. | |
XScuGic_Enable() | 使能设备中断. | |
Xil_ExceptionEnableMask() | 宏定义 | 使能全局中断. |
代码描述
在本示例中,杰克设置RxFIFO触发阈值为1,即每接收到一个字节便发起一次中断;设置接收空间超时阈值为8,即接收到数据后、经过8个波特率时钟无数据接收便发起空闲超时中断。具体的C语言代码如下所示:
-
串口初始化函数
s32 uart_init(XUartPs *uartPtr, u16 deviceId)
{
s32 status = XST_SUCCESS;
XUartPs_Config *config;
//初始化串口设备
config = XUartPs_LookupConfig(deviceId);
if(config == NULL)
{
return XST_FAILURE;
}
status = XUartPs_CfgInitialize(uartPtr, config, config->BaseAddress);
if(status != XST_SUCCESS)
{
return XST_FAILURE;
}
XUartPs_SetOperMode(uartPtr, XUARTPS_OPER_MODE_NORMAL); //设置串口工作模式为正常收发模式
XUartPs_SetDataFormat(uartPtr, &UartFormat0); //设置通信数据格式
XUartPs_SetFifoThreshold(uartPtr, 1); //设置RxFIFO中断阈值
XUartPs_SetRecvTimeout(uartPtr, 8); //设置接收超时阈值
XUartPs_Recv(&uart0, recvBuffer, sizeof(recvBuffer)); //初始化接收buffer相关参数
return status;
}
-
中断初始化函数
s32 uart_interrupt_init(XScuGic *intcPtr, XUartPs *uartPtr, u32 uartInteruptID)
{
s32 status = XST_SUCCESS;
//向GIC注册串口0的中断回调函数 uart_interupt_handler
status = XScuGic_Connect(intcPtr,
uartInteruptID,
(Xil_InterruptHandler) uart_interupt_handler,
(void *)uartPtr);
//设置UART0的中断触发方式: 接收FIFO触发、接收超时
XUartPs_SetInterruptMask(uartPtr, XUARTPS_IXR_RXOVR | XUARTPS_IXR_TOUT);
//使能GIC的串口中断
XScuGic_Enable(intcPtr, uartInteruptID);
return status;
}
-
中断处理函数
void uart_interupt_handler(XUartPs *uartPtr)
{
u32 isr_status;
Xil_AssertVoid(uartPtr != NULL);
Xil_AssertVoid(uartPtr->IsReady == XIL_COMPONENT_IS_READY);
isr_status = XUartPs_ReadReg(uartPtr->Config.BaseAddress, XUARTPS_IMR_OFFSET);
isr_status &= XUartPs_ReadReg(uartPtr->Config.BaseAddress, XUARTPS_ISR_OFFSET);
if(isr_status & (u32)XUARTPS_IXR_RXOVR) //RxFIFO接收阈值触发中断
{
if (uartPtr->ReceiveBuffer.RemainingBytes != (u32)0)
{
(void)XUartPs_ReceiveBuffer(uartPtr);
}
XUartPs_WriteReg(uartPtr->Config.BaseAddress, XUARTPS_ISR_OFFSET, XUARTPS_IXR_RXOVR);
}
else if(isr_status & (u32)XUARTPS_IXR_TOUT) //接收超时(空闲)
{
recvFlag = 1;
recvLen = uartPtr->ReceiveBuffer.RequestedBytes - uartPtr->ReceiveBuffer.RemainingBytes;
XUartPs_WriteReg(uartPtr->Config.BaseAddress, XUARTPS_ISR_OFFSET, XUARTPS_IXR_TOUT);
}
}
-
全局中断相关函数
int interrupt_init(XScuGic *intcPtr, u16 DeviceId)
{
s32 status;
XScuGic_Config *intc_cfg;
//初始化中断控制器GIC
intc_cfg = XScuGic_LookupConfig(DeviceId);
if(intc_cfg == NULL)
{
return XST_FAILURE;
}
status = XScuGic_CfgInitialize(intcPtr, intc_cfg, intc_cfg->CpuBaseAddress);
if(status != XST_SUCCESS)
{
return XST_FAILURE;
}
return status;
}
void Interrupt_Setup_Exception(XScuGic *intcPtr)
{
//打开中断异常处理
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
(void *)intcPtr);
Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);
}
-
主逻辑函数
int main()
{
s32 status;
//中断GIC初始化
interrupt_init(&intc, XPAR_PSU_ACPU_GIC_DEVICE_ID);
//串口0初始化
status = uart_init(&uart0, XPAR_PSU_UART_0_DEVICE_ID);
if(status == XST_FAILURE)
{
return 0;
}
uart_interrupt_init(&intc, &uart0, UART0_INT_IRQ_ID); //使能uart接收中断和接收超时中断
//全局中断使能
Interrupt_Setup_Exception(&intc);
while(1)
{
if(recvFlag)
{
recvFlag = 0;
/*
* 业务逻辑
*/
XUartPs_Recv(&uart0, recvBuffer, sizeof(recvBuffer)); //初始化接收buffer相关参数
}
}
return 0;
}
特别说明:
以上内容基于官方提供的demo,在此基础上进行二次开发。由于公众号篇幅原因,杰克仅是对官方API进行了简单的功能描述,并未对上面所使用的一些官方API函数进行深入说明,若需要深入了解可参考官方例程。
03 文章总结
XILINX的SOC芯片,无论ZYNQ、还是杰克使用的ZYNQ Ultrascale+ MPSOC皆适用于该实现方法。对于开发者而言,最重要的是实现思路以及相关API的灵活使用。由于篇幅原因,相关官方API函数杰克并未进行说明,杰克强烈建议大家深入到具体代码中了解。
参考文档:
ug585-Zynq-7000-TRM.pdf
ug1085-zynq-ultrascale-trm.pdf