目录
1. 建立vitis工程,在刚才vivado工程设计的硬件基础上,进行软件的设计开发工作
(3)main.c函数调用,对GIC控制器、串口进行初始化配置
本文档主要对ZYNQ SOC芯片裸机开发中串口的使用进行讲解,包含了PS串口和PL串口的使用,PS、PL串口都集成在同一个工程中,目前网上通过一个工程同时讲解PS和PL串口的教程还是比较少的,也是因为这个原因写的该文档,大家觉得有用的话可以帮忙点个赞收藏下~
一、搭建Vivado工程
具体搭建vivado工程的一般方法,可以详见以下文章
ZYNQ-Linux开发之(二)Vivado工程搭建、Block Design设计搭建、PS、PL的IP核的使用配置
1. PS核配置如下:
ps端串口波特率配置为230400,如果这里不配置,后续在vitis中也可以通过代码配置修改
QSPI做如下配置,主要是为了方便固化程序,其中QSPI对应引脚的约束以及Bank0、Bank1的电平,需要根据自己项目的原理图去确定。不要直接抄图
PS串口这里只用了1个,用的UART0,同样IO约束根据自己项目选取,其余SD卡配置等可以不选,根据自己项目需求
时钟按照如下配置,选取一个PS向外部输出的时钟配置,配置为输出200MHZ时钟
DDR配置,型号一定要选对,根据自己项目原理图的选型选择,选错可能程序起不来
增加PL到PS的中断,该中断最多支持16路,设计blockdesign时,一定要注意这点,否则可能会导致中断资源不够用
PS配置完成后,是这样子的
2. PL设计如下:
添加PL端的IP核,也就是咱们这次需要使用的PL端的uart ip核,即AXI Uartlite核,整个BlockDesign搭建完毕后如下图所示,添加了两个AXI Uartlite IP核,为了将PS与PL端IP核通过AXI总线相连,添加了AXI Interconnect IP核,相当于集线器功能,同时添加Concat IP核,用来将PL端两个AXI Uartlite核的中断信号,集合起来引到PS端,详细连接方式见图:
右击工程文件,选择输出产品,选择Global,点击Generate
输出产品后,再生成顶层文件
新建约束文件,在约束文件中,增加PL端两个串口收发引脚的约束,对应约束引脚,根据自己项目硬件原理图修改
然后依次点击左边菜单栏的:综合、实现、导出bit文件
bit文件生成成功后,将生成的xsa文件导出来,用于后续的vitis使用,具体步骤如下:
这里选择第二个选项,include bit,因为包含PL端的设计,如果只有PS端,则选择第一个
点击next,选择文件输出的路径,这个路径记清楚,待会建立vitis工程时需要使用这个文件
直接点击finish即可,到这步,vivado的相关工作就完成了,后续就是vitis工程的内容了
二、新建vitis工程,关联vivado工程的硬件设计
1. 建立vitis工程,在刚才vivado工程设计的硬件基础上,进行软件的设计开发工作
点击tools,选择Launch Vitis IDE
选择vitis工程所在的目录,我是新建了一个目录用来存放vitis工程,然后将vivado生成的xsa文件放在了vitis工程的目录下,点击launch
弹出vitis软件中,点击新建应用工程,点击next
选择通过xsa方式建立工程,并点击Browse选取xsa工程文件
xsa文件导入后如下图所示,直接点击next
输入vitis工程的名字,选取ARM处理器,我用的ZYNQ 7020,ARM是双核,所以有两个选择,点击next
这里配置一些信息以及确认一些信息,比如是裸机开发,还是使用简单的系统freertos开发,我这里选择裸机
这里选择新建工程模板,可以选择hello word,也可以选择空白工程,建议选择hello word,因为hello word工程模板包含一些系统编译运行的必须文件,而空白模板工程什么也没有,容易出现编写错误,然后点击finish,完成工程的创建,这样一个vitis工程就创建完毕了。
2.在vitis中 编写uart收发的程序
(1)初始化GIC中断控制器
使用串口收发数据时,首先需要初始化zynq的GIC中断控制器,串口收发数据都是通过中断完成的,在Vitis中,新建intr_hdl.c和intr_hdl.h文件,用来初始化GIC中断控制器
头文件:intr_hdl.h
通过宏定义INTC_DEVICE_ID重新定义GIC设备ID,也是为了后续方便修改和移植,直接使用XPAR_SCUGIC_SINGLE_DEVICE_ID也行。
/*!
\file intr_hdl.h
\brief firmware functions to manage intr
\version 2024-03-12, V1.0.0
\author tbj
*/
#ifndef INTR_HDL_H
#define INTR_HDL_H
#include "xscugic.h"
#ifdef __cplusplus
extern "C" {
#endif
//GIC中断控制器ID号
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
//GIC中断控制器初始化
int Gic_Intr_Init(XScuGic *GicDevicePtr);
#ifdef __cplusplus
}
#endif
#endif /* INTR_HDL_H */
源文件:intr_hdl.c
/*!
\file intr_hdl.c
\brief firmware functions to manage intr
\version 2024-03-12, V1.0.0
\author tbj
*/
#include "intr_hdl.h"
/* 功能:初始化GIC中断控制器
* 入参1:GIC中断控制器实例化对象指针
* 入参2:GIC中断控制器设备ID号
*/
int Gic_Intr_Init(XScuGic *GicDevicePtr){
int Status = 0;
XScuGic_Config *IntcConfig;
//初始化中断控制器
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
if (NULL == IntcConfig) {
return XST_FAILURE;
}
Status = XScuGic_CfgInitialize(GicDevicePtr, IntcConfig,
IntcConfig->CpuBaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
return XST_SUCCESS;
}
(2)编写uart初始化和操作的代码
头文件:uart_hdl.h
通过宏定义对PS、PL串口的设备ID号、设备GIC中断号进行了重定义
声明了PS、PL串口初始化函数
声明了PS、PL串口中断初始化函数
声明了PS、PL串口接收中断处理函数
上面的头文件,根据自己需求进行删减,或放在其余文件中引用
/*!
\file uart_hdl.h
\brief firmware functions to manage COM ports
\version 2024-03-12, V1.0.0
\author tbj
*/
#ifndef UART_HDL_H
#define UART_HDL_H
#include <stdio.h>
#include <string.h>
#include "xparameters.h"
#include "xuartlite.h"
#include "xil_exception.h"
#include "xil_printf.h"
#include "xscugic.h"
#include "xuartlite_l.h"
#include "xgpiops.h"
#include "sleep.h"
#include "platform.h"
#include "xscuwdt.h"
#include "xscutimer.h"
#include "xuartps.h"
#ifdef __cplusplus
extern "C" {
#endif
//PS串口设备ID号
#define UART0_PS_DEVICE_ID XPAR_PS7_UART_0_DEVICE_ID
//PL串口设备ID号
#define UART0_PL_DEVICE_ID XPAR_UARTLITE_0_DEVICE_ID
#define UART1_PL_DEVICE_ID XPAR_UARTLITE_1_DEVICE_ID
//PS串口GIC中断号
#define UART0_PS_INTR_ID XPAR_XUARTPS_0_INTR
//PL串口GIC中断号
#define UART0_PL_INTR_ID XPAR_FABRIC_UARTLITE_0_VEC_ID
#define UART1_PL_INTR_ID XPAR_FABRIC_UARTLITE_1_VEC_ID
//初始化PL端的串口
int Pl_Uart_Init(u8 PlUartId);
//初始化PS端的串口
int Ps_Uart_Init(u8 PsUartId);
//PL串口中断初始化
int Pl_Uart_Intr_Init(u8 PsUartId, XScuGic *IntcInstancePtr);
//PS串口中断初始化
int Ps_Uart_Intr_Init(u8 PlUartId, XScuGic *IntcInstancePtr);
//PL串口接收中断函数
void Pl_Uart0_Intr_Hander(void *CallBackRef);
void Pl_Uart1_Intr_Hander(void *CallBackRef);
//PS串口接收中断函数
void Ps_Uart0_Intr_Hander(void *CallBackRef);
#ifdef __cplusplus
}
#endif
#endif /* UART_HDL_H */
源文件:uart_hdl.c
实例化PS、PL串口结构体对象,并新建数组,存放实例化后的对象指针,方便后续使用操作,这样后面的串口初始化配置等操作,只需要传入对应串口号即可操作响应的串口结构体;
将PS、PL串口的设备ID号和GIC中断ID号列到数组中,同样是为了方便后续操作;
定义一个串口接收的buffer:recv_buf[100],100个字节;
然后分别对PS串口初始化函数、PS串口中断初始化函数、PL串口初始化函数、PL串口中断初始化函数、PS串口中断接收函数、PL串口中断接收函数进行实现,具体可见下列代码;
其中PS和PL串口中断接收函数中,收到数据后直接就发送出去了,实际项目中需根据自己的需求对数据进行处理;
一些注意事项:PS和PL串口同时使用时,PL串口配置的中断优先级不能小于等于0xA0(即数值不能大于等于0xA0),目前代码中配置的PL串口中断优先级为0x98,如果数值大于等于0xA0时,单独使用PL串口收发没有问题,一旦使用了一次PS串口的收发,PL串口收发功能就失效了,具体原因我也不太清楚,还没深究,大家知道的话可以评论区说下~
#include "uart_hdl.h"
//PS串口初始化实例
XUartPs PsUart0;
//PS串口初始化实例指针数组
XUartPs *Ps_Uart_Arry[1] = {&PsUart0};
//PS设备ID号以及中断ID号
u32 Ps_Uart_Id[1] = {UART0_PS_DEVICE_ID};
u32 Ps_Uart_Intr_Id[1] = {UART0_PS_INTR_ID};
//PL串口初始化实例
XUartLite PlUart0, PlUart1;
//PL串口初始化实例指针数组
XUartLite *Pl_Uart_Arry[2] = {&PlUart0, &PlUart1};
//PL设备ID号以及中断ID号
u32 Pl_Uart_Id[2] = {UART0_PL_DEVICE_ID, UART1_PL_DEVICE_ID};
u32 Pl_Uart_Intr_Id[2] = {UART0_PL_INTR_ID, UART1_PL_INTR_ID};
//串口接收数据缓存区
u8 recv_buf[100] = {0};
/* 功能:初始化PS端串口
* 入参1:PS端串口号
*/
int Ps_Uart_Init(u8 PsUartId){
int Status = 0;
XUartPs_Config *Config;
//提取PS串口结构体指针
Config = XUartPs_LookupConfig(Ps_Uart_Id[PsUartId]);
if (NULL == Config) {
return XST_FAILURE;
}
//初始化对应PS串口
Status = XUartPs_CfgInitialize(Ps_Uart_Arry[PsUartId], Config, Config->BaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
//串口自检
Status = XUartPs_SelfTest(Ps_Uart_Arry[PsUartId]);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/*
*PS串口可以在代码中设置模式、波特率等属性
*PL串口不需要在代码中设置模式、波特率等属性,是在block design中的IP核配置的
*/
//设置工作模式:正常模式
XUartPs_SetOperMode(Ps_Uart_Arry[PsUartId], XUARTPS_OPER_MODE_NORMAL);
//设置波特率
XUartPs_SetBaudRate(Ps_Uart_Arry[PsUartId],230400);
//设置 RxFIFO 的中断触发等级,FIFO中只要收到一个字节就触发中断,类似于非空中断
XUartPs_SetFifoThreshold(Ps_Uart_Arry[PsUartId], 1);
return XST_SUCCESS;
}
/* 功能:初始化PS端串口中断
* 入参1:PS端串口号
* 入参2:GIC中断实例化对象指针
*/
int Ps_Uart_Intr_Init(u8 PsUartId, XScuGic *IntcInstancePtr){
int Status = 0;
//绑定中断处理函数-接收串口数据到buffer
switch(Ps_Uart_Intr_Id[PsUartId]){
case UART0_PS_INTR_ID:
//绑定中断处理函数
Status = XScuGic_Connect(IntcInstancePtr, Ps_Uart_Intr_Id[PsUartId],
(Xil_ExceptionHandler)Ps_Uart0_Intr_Hander,
Ps_Uart_Arry[PsUartId]);
break;
default:
break;
}
if (Status != XST_SUCCESS) {
return Status;
}
//打开中断异常处理
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
IntcInstancePtr);
Xil_ExceptionEnable();
//设置 UART 的中断触发方式
XUartPs_SetInterruptMask(Ps_Uart_Arry[PsUartId], XUARTPS_IXR_RXOVR);
//使能 GIC 中的串口中断
XScuGic_Enable(IntcInstancePtr, Ps_Uart_Intr_Id[PsUartId]);
return XST_SUCCESS;
}
/* 功能:初始化PL端的串口
* 入参1:PL端串口号
*/
int Pl_Uart_Init(u8 PlUartId){
int Status = 0;
//初始化串口
Status = XUartLite_Initialize(Pl_Uart_Arry[PlUartId], Pl_Uart_Id[PlUartId]);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
//串口自检
Status = XUartLite_SelfTest(Pl_Uart_Arry[PlUartId]);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
/*
*PS串口可以在代码中设置模式、波特率等属性
*PL串口不需要在代码中设置模式、波特率等属性,是在block design中的IP核配置的
*/
return XST_SUCCESS;
}
/* 功能:PL串口中断初始化
* 入参1:PL端串口号
* 入参2:GIC中断实例化对象指针
*/
int Pl_Uart_Intr_Init(u8 PlUartId, XScuGic *IntcInstancePtr){
int Status = 0;
//打开中断异常处理
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
IntcInstancePtr);
Xil_ExceptionEnable();
/*初始化配置中断优先级和中断触发类型 0x98:优先级160,0最高
*优先级,0xF8(248)是最低的。有32个优先级
*由8的台阶支撑。因此,支持的优先事项是0、8、16、32、40。。。,248
*0x3:B11,触发类型上升沿
*PL串口中断优先级不能使用0xA0,否则和PS串口同时使用时,PL先收发好用,一旦PS串口收发,PL串口收发就会失灵
*/
switch(Pl_Uart_Intr_Id[PlUartId]){
case UART0_PL_INTR_ID:
XScuGic_SetPriorityTriggerType(IntcInstancePtr, Pl_Uart_Intr_Id[PlUartId],
0x98, 0x3);
//绑定中断处理函数
Status = XScuGic_Connect(IntcInstancePtr, Pl_Uart_Intr_Id[PlUartId],
(Xil_ExceptionHandler)Pl_Uart0_Intr_Hander,
(void*)Pl_Uart_Arry[PlUartId]);
break;
case UART1_PL_INTR_ID:
XScuGic_SetPriorityTriggerType(IntcInstancePtr, Pl_Uart_Intr_Id[PlUartId],
0x98, 0x3);
//绑定中断处理函数
Status = XScuGic_Connect(IntcInstancePtr, Pl_Uart_Intr_Id[PlUartId],
(Xil_ExceptionHandler)Pl_Uart1_Intr_Hander,
(void*)Pl_Uart_Arry[PlUartId]);
break;
default:
break;
}
if (Status != XST_SUCCESS) {
return Status;
}
//使能串口中断
XScuGic_Enable(IntcInstancePtr, Pl_Uart_Intr_Id[PlUartId]);
XUartLite_EnableInterrupt(Pl_Uart_Arry[PlUartId]);
return XST_SUCCESS;
}
/*
* 功能:PS串口uart0中断接收处理函数
*/
void Ps_Uart0_Intr_Hander(void *CallBackRef){
u32 recv_num = 0 ; //接收数据个数
u32 isr_status ; //中断状态标志
//读取中断 ID 寄存器,判断触发的是哪种中断
isr_status = XUartPs_ReadReg(PsUart0.Config.BaseAddress,
XUARTPS_IMR_OFFSET);
isr_status &= XUartPs_ReadReg(PsUart0.Config.BaseAddress,
XUARTPS_ISR_OFFSET);
//判断中断标志位 RxFIFO 是否触发
if (isr_status & (XUARTPS_IXR_RXOVR | XUARTPS_IXR_RXFULL)){
//读取接收的数据到缓存区
recv_num = XUartPs_Recv(&PsUart0, recv_buf, 100);
//将缓存区中接收到的数据再发送出去
XUartPs_Send(&PsUart0, recv_buf, recv_num);
//清空缓存区
memset(recv_buf, 0, 100);
}
}
/*
* 功能:PL串口uart0中断接收处理函数
*/
void Pl_Uart0_Intr_Hander(void *CallBackRef){
uint16_t recv_num = 0 ;
u32 IsrStatus ; //中断状态标志
//读取中断 ID 寄存器,判断触发的是哪种中断
IsrStatus = XUartLite_ReadReg(PlUart0.RegBaseAddress,
XUL_STATUS_REG_OFFSET);
if ((IsrStatus & (XUL_SR_RX_FIFO_FULL | XUL_SR_RX_FIFO_VALID_DATA)) != 0) {
//读取接收的数据到缓存区
recv_num = XUartLite_Recv(&PlUart0, recv_buf, 100);
//发送出去-测试使用
XUartLite_Send(&PlUart0, recv_buf, recv_num);
//清空缓存区
memset(recv_buf, 0, 100);
}
}
/*
* 功能:PL串口uart1中断接收处理函数
*/
void Pl_Uart1_Intr_Hander(void *CallBackRef){
//同uart0
}
(3)main.c函数调用,对GIC控制器、串口进行初始化配置
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
int main()
{
//初始化GIC中断控制器
Gic_Intr_Init(&GicIntrDevice);
//初始化PS串口
Status |= Ps_Uart_Init(0);
Status |= Ps_Uart_Intr_Init(0, &GicIntrDevice);
Status |= Pl_Uart_Init(0);
Status |= Pl_Uart_Intr_Init(0, &GicIntrDevice);
//初始化PL串口
while(1){
}
return 0;
}
创作不易,希望大家点赞、收藏、关注哦!!!ヾ(o◕∀◕)ノ