前言
由于使用的ZYNQ PS部分只有两个串口,其中一个还当成了控制台用,串口不够用,于是使用PL的逻辑部分并利用IP核:AXI UARTLITE 为PS增加串口数量,并添加了AXI TIMER。
Vivado和Vitis为2020,PS为裸机使用。
包含以下内容:
1、Vivado的配置
2、axi uartlite代码
3、axi timer代码
4、利用IP核:axi timer,实现类似串口空闲中断的功能,这种思路我在FPGA、单片机和一些软件开发时经常使用,比较方便。
5、使用效果
一、Vivado配置
1.1. axi uartlite添加
我这里使用的波特率是460800,PL的时钟频率为120M,在配置的时候发现PL频率需要大于100M才能使用460800的波特率,比较费解。
1.2. axi timer添加
使能了两个通道
1.3. PL时钟修改
1.4. ZYNQ系统
最终的ZYNQ系统如图,注意axi uartlite 和axi timer都使能了中断。
二、axi uartlite代码
2.1. 头文件包含:
#include "xuartlite.h"
#include "xuartlite_l.h"
#include "stdio.h"
#include "xparameters.h"
#include "xil_printf.h"
#include "xscugic.h"
#define AXI_UARTLITE_DEVICE_ID XPAR_AXI_UARTLITE_0_DEVICE_ID //AXI_Timer设备ID
#define AXI_UARTLITE_IRPT_INTR XPAR_FABRIC_AXI_UARTLITE_0_INTERRUPT_INTR //AXI TIMER 中断号
XUartLite xUartLite1;
uint16_t xUartLite1_RecvCount=0;
uint8_t xUartLite1_RecvBuffer[1024];
2.2. 串口接收
初始化代码,注意串口中断回调是xUartLite1_Int_Handler函数,Sys_ScuGic顾名思义 是系统中断控制器的实例:
int xUartLite1_Init(XUartLite *xUartLite_Ptr)
{
int Status;
//初始化AXI UART
Status = XUartLite_Initialize(xUartLite_Ptr,AXI_UARTLITE_DEVICE_ID);
//自检
Status = XUartLite_SelfTest(xUartLite_Ptr);
if (Status != XST_SUCCESS)\
{
return XST_FAILURE;
}
//中断设置
XScuGic_SetPriorityTriggerType(&Sys_ScuGic, AXI_UARTLITE_IRPT_INTR,0xA0, 0x3);
//中断设置
XScuGic_Connect(&Sys_ScuGic, AXI_UARTLITE_IRPT_INTR,(Xil_ExceptionHandler)xUartLite1_Int_Handler, (void *)xUartLite_Ptr);
//使能 GIC中的中断
XScuGic_Enable(&Sys_ScuGic, AXI_UARTLITE_IRPT_INTR);
//中断使能
XUartLite_EnableInterrupt(xUartLite_Ptr);
return XST_SUCCESS;
}
中断回调函数:
串口相关的中断都会进入此函数,例如发送完成、接收到数据等,在此函数中,仅判断是否接收到数据,然后进入xUartLite1_RecvInt_Handler函数进行数据读取。
void xUartLite1_Int_Handler()
{
int IsrStatus;
IsrStatus = XUartLite_ReadReg(xUartLite1.RegBaseAddress,XUL_STATUS_REG_OFFSET);
if((IsrStatus & (XUL_SR_RX_FIFO_FULL | XUL_SR_RX_FIFO_VALID_DATA)) != 0)
{
xUartLite1_RecvInt_Handler();
}
}
//串口接收回调
void xUartLite1_RecvInt_Handler()
{
//获取接收字节
xUartLite1_RecvBuffer[xUartLite1_RecvCount++] = XUartLite_RecvByte(xUartLite1.RegBaseAddress);
xTmrCh2_Count = 0;
}
2.3. 串口发送
直接调用库函数:
void xUartLite1_Send(uint8_t *Data,uint16_t Lenth)
{
XUartLite_Send(&xUartLite1,Data,Lenth);
}
三、axi timer代码
比较简单,不做解释了,就这些代码,注意timer的ch1定时周期是1秒,ch2是1us,ch2是供串口使用的
#include "timer.h"
#define AXI_TIMER_DEVICE_ID XPAR_AXI_TIMER_0_DEVICE_ID //AXI_Timer设备ID
#define AXI_TIMER_IRPT_INTR XPAR_FABRIC_AXI_TIMER_0_INTERRUPT_INTR //AXI TIMER 中断号
uint8_t xTmr1Ch1_IntFlg=0;
uint32_t xTmrCh2_Count=0;
extern uint16_t xUartLite1_RecvCount;
extern uint8_t xUartLite1_RecvBuffer[1024];
extern void xUartLit1_ReceiveData_Process(uint8_t *Data,uint16_t Lenth);
XTmrCtr xTmr1_Inst; //AXI Timer
#define AXI_TIMER_CH1_PERIOD_US 1000000 //AXI TIMER CH1 的定时器周期为1S
#define AXI_TIMER_CH2_PERIOD_US 1 //AXI TIMER CH2 的定时器周期为1US
//定时器中断回调
void xTmrCtr_Int_Handler(void *CallBackRef, u8 TmrCtrNumber)
{
//AXI TIMER 的定时器1
if(TmrCtrNumber == AXI_TIMER_CHANNEL_1)
{
xTmr1Ch1_IntFlg = 1;
}
//AXI TIMER 的定时器2
if(TmrCtrNumber == AXI_TIMER_CHANNEL_2)
{
xTmrCh2_Count++;
if(xUartLite1_RecvCount != 0 && xTmrCh2_Count > 100)
{
xUartLit1_ReceiveData_Process(xUartLite1_RecvBuffer,xUartLite1_RecvCount);
//重新开始接收
xUartLite1_RecvCount = 0;
}
}
}
//定时的微秒数转寄存器值
//AXI TIMER是倒计时,如果想定时1MS,那么寄存器中要写入:最大值-1MS
//AXI TIMER的时钟为120M,1US为120个Tick
u32 xTmr_US_To_RegValue(u32 US)
{
u32 Value;
Value = 120*US;
return 0xFFFFFFFF - Value;
}
//定时的微秒数转纳秒
u32 xTmr_US_To_NS(u32 US)
{
return US*1000;
}
//AXITIMER初始化
int xTmrCtr_Init(XTmrCtr *xTmrCtr_Ptr)
{
int Status;
//初始化AXI_TIMER 0
Status = XTmrCtr_Initialize(xTmrCtr_Ptr, AXI_TIMER_DEVICE_ID);
if(Status != XST_SUCCESS)
{
return XST_FAILURE;
}
//自检:两个定时器
Status = XTmrCtr_SelfTest(xTmrCtr_Ptr, 2-1);
if(Status != XST_SUCCESS)
{
return XST_FAILURE;
}
//设置中断回调函数
XTmrCtr_SetHandler(xTmrCtr_Ptr,xTmrCtr_Int_Handler,xTmrCtr_Ptr);
//定时器1中断计数
XTmrCtr_SetResetValue(xTmrCtr_Ptr,AXI_TIMER_CHANNEL_1,xTmr_US_To_RegValue(AXI_TIMER_CH1_PERIOD_US));
//定时器2中断计数
XTmrCtr_SetResetValue(xTmrCtr_Ptr,AXI_TIMER_CHANNEL_2,xTmr_US_To_RegValue(AXI_TIMER_CH2_PERIOD_US));
//定时器中断打开
XTmrCtr_SetOptions(xTmrCtr_Ptr, AXI_TIMER_CHANNEL_1, XTC_INT_MODE_OPTION | XTC_AUTO_RELOAD_OPTION);
XTmrCtr_SetOptions(xTmrCtr_Ptr, AXI_TIMER_CHANNEL_2, XTC_INT_MODE_OPTION | XTC_AUTO_RELOAD_OPTION);
//中断设置
XScuGic_Connect(&Sys_ScuGic, AXI_TIMER_IRPT_INTR,(Xil_ExceptionHandler)XTmrCtr_InterruptHandler, (void *)xTmrCtr_Ptr);
//使能 GIC中的定时器中断
XScuGic_Enable(&Sys_ScuGic, AXI_TIMER_IRPT_INTR);
return XST_SUCCESS;
}
void xTmr_Enable(XTmrCtr *xTmrCtr_Ptr,uint8_t Channel)
{
XTmrCtr_Start(xTmrCtr_Ptr,Channel);
}
void xTmr_Disable(XTmrCtr *xTmrCtr_Ptr,uint8_t Channel)
{
XTmrCtr_Stop(xTmrCtr_Ptr,Channel);
}
四、利用定时器实现串口空闲中断
串口波特率是460800,一个字节大约耗时21us,每当收到串口数据时,定时器清0,在定时器1us的回调中,检测到串口接收长度不为0,且定时器时间>100us,认为一帧数据接收完成,100us的时间可以调整。
//串口接收
void xUartLite1_RecvInt_Handler()
{
//获取接收字节
xUartLite1_RecvBuffer[xUartLite1_RecvCount++] = XUartLite_RecvByte(xUartLite1.RegBaseAddress);
xTmrCh2_Count = 0;
}
//定时器判断串口接收
//定时器中断回调
void xTmrCtr_Int_Handler(void *CallBackRef, u8 TmrCtrNumber)
{
//AXI TIMER 的定时器1
if(TmrCtrNumber == AXI_TIMER_CHANNEL_1)
{
xTmr1Ch1_IntFlg = 1;
}
//AXI TIMER 的定时器2
if(TmrCtrNumber == AXI_TIMER_CHANNEL_2)
{
xTmrCh2_Count++;
//认为一帧数据接收完成,可以处理
if(xUartLite1_RecvCount != 0 && xTmrCh2_Count > 100)
{
xUartLit1_ReceiveData_Process(xUartLite1_RecvBuffer,xUartLite1_RecvCount);
//重新开始接收
xUartLite1_RecvCount = 0;
}
}
}
五、使用效果
每次收到一帧数据后,打印长度、第一个数据和最后一个数据文章来源:https://www.toymoban.com/news/detail-848993.html
void xUartLit1_ReceiveData_Process(uint8_t *Data,uint16_t Lenth)
{
xil_printf("AXI UartLite Receive Process,Lenth: %d,Data:%02X,%02X\n",Lenth,Data[0],Data[Lenth-1]);
}
完整工程下载连接,包括vivado和vitis:
https://download.csdn.net/download/qq_36365231/88677067文章来源地址https://www.toymoban.com/news/detail-848993.html
到了这里,关于ZYNQ PS使用axi uartlite进行串口收发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!