ZYNQ-Vitis(SDK)裸机开发之(一)串口收发使用:PS串口+PL串口、多个串口使用方法

14 篇文章 4 订阅
8 篇文章 1 订阅

目录

一、搭建Vivado工程

1. PS核配置如下:

2. PL设计如下:

二、新建vitis工程,关联vivado工程的硬件设计

1. 建立vitis工程,在刚才vivado工程设计的硬件基础上,进行软件的设计开发工作

2.在vitis中 编写uart收发的程序

(1)初始化GIC中断控制器

(2)编写uart初始化和操作的代码

(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◕∀◕)ノ

  • 41
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要将Zynq的EMIO(Extended Multiplexed IO)映射到PS(Processing System)端UART串口与外部PL(Programmable Logic)收发数据,需要进行以下步骤: 首先,打开Zynq的Vivado设计工具。创建一个新的工程,并选择适当的目标设备和项目名称。 然后,将Zynq的Processing System界面打开,并找到UART控制器。通过设置寄存器来配置UART的波特率、数据位、停止位等通信参数。确保UART控制器被使能和启用。 接下来,在Zynq的Block Design中,添加一个Zynq Processing System实例。在引脚规划中,将UART的引脚映射为EMIO模式,使其能够与外部PL通信。 在Block Design中,添加AXI GPIO实例,用于控制PL上的UART串口发送和接收数据的引脚。 然后,创建一个AXI UART Lite IP核,并将其连接到Processing System中的M_AXI_GP0总线。 在Block Design中,连接AXI UART Lite的接收和发送接口到AXI GPIO实例的引脚。这样就可以将数据从PL的UART接口发送到外部设备,也可以从外部设备接收数据到PL的UART接口。 完成连接后,生成Bitstream并将其下载到FPGA中。 在Petalinux系统中,通过在设备树(device tree)中配置UART串口和GPIO,来使EMIO与PS相关的外设得以识别并使用。 最后,在Linux系统中,使用UART串口的相应设备节点来进行数据的收发。 综上所述,通过对EMIO引脚和AXI UART Lite的配置,以及在设备树中的配置,就可以将Zynq的EMIO映射到PS端UART串口与外部PL收发数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

披着假发的程序唐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值