MicroBlaze最小系统+UART/CAN/GPIO(PS端 Hello World)

本文详细介绍了基于MicroBlaze的PS开发流程,从SDK的打开到HelloWorld工程的创建、BSP设置、代码分析,重点讲解了外设通过地址空间读写的原理。通过Xilinx SDK创建APP工程,设置BSP以通过MDM调试信息,然后深入代码,理解XUartLite_SendByte等函数实现串口通信。最后提到了调试配置和工程的完成。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 前言

MicroBlaze PS的开发是基于PL的,所以在设计设计师要了解底层的设计时什么样的,不然程序就是空中楼阁根本跑不起来。
本篇设计时基于我上一篇博客的设计而来的MicroBlaze最小系统+UART/CAN/GPIO

为保证质量,本系列文章通过四篇文章四个工程来讲解PS开发的过程:(想写多少写多少)

  • hello world
  • can tx rxtest
  • bram read write
  • 中断

平台是XCKU040,Vivado版本2019.1。

本人是之前参与过STM32单片机的开发,水平有限,共同进步。

2. Hello World工程搭建

2.1 打开SDK

首先要知道如何从Vivado界面打开SDK。这里需要注意的是随着Vivado版本的变化,从2019.2版本以后PS开发平台变成了Vitis。打开方式和开发方法差不多。

点击Launch SDK就可以在Vivado 界面打开SDK
在这里插入图片描述

随着电脑CPU性能越好,打开速度越快,可能要等待一阵时间。打开SDK后的界面是这样的:
在这里插入图片描述

2.2 创建APP工程

点击Flie – > new --> Application project创建工程
输入工程名hello
在这里插入图片描述
点击Next还可以选择工程类型。这是Xilinx官方准备的部分Demo,结合应用可以在这几个工程中选取何时的进行开发,我们之间保持默认选择Hello World。从Hello World开发中,我们就能掌握开发的全貌
在这里插入图片描述

生成成功后可以看到多了一个名为hello的工程,这里面是我们需要开发的C文件夹,hello_bsp是板级系统支持包的意思,里面有一些支持库
在这里插入图片描述

2.3 设置BSP

当没有准备实体串口的时候,可以点开Modify this BSP‘s Settings 设置stdin和stdout为mdm。这样有些信息就可以通过mdm在控制台上打印出来,这是为什么我强烈建议大家使用debug & UART的原因。
在这里插入图片描述

2.4 代码分析

主文件代码:

/*
 * helloworld.c: simple test application
 *
 * This application configures UART 16550 to baud rate 9600.
 * PS7 UART (Zynq) is not initialized by this application, since
 * bootrom/bsp configures it to baud rate 115200
 *
 * ------------------------------------------------
 * | UART TYPE   BAUD RATE                        |
 * ------------------------------------------------
 *   uartns550   9600
 *   uartlite    Configurable only in HW design
 *   ps7_uart    115200 (configured by bootrom/bsp)
 */

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"


int main()
{
    init_platform();

    print("Hello World\n\r");

    cleanup_platform();
    return 0;
}

可以看到代码十分简单,架构也十分的清晰,按住CTRL+鼠标左键可以查看函数定义的地方,我们进入init_platfrom();主要就是一个初始化cache和串口的过程,这里使用的是Uart Lite IP所以,STDOUT_IS_16550未定义。

init_uart()
{
#ifdef STDOUT_IS_16550
    XUartNs550_SetBaud(STDOUT_BASEADDR, XPAR_XUARTNS550_CLOCK_HZ, UART_BAUD);
    XUartNs550_SetLineControlReg(STDOUT_BASEADDR, XUN_LCR_8_DATA_BITS);
#endif
    /* Bootrom/BSP configures PS7/PSU UART to 115200 bps */
}

void
init_platform()
{
    /*
     * If you want to run this example outside of SDK,
     * uncomment one of the following two lines and also #include "ps7_init.h"
     * or #include "ps7_init.h" at the top, depending on the target.
     * Make sure that the ps7/psu_init.c and ps7/psu_init.h files are included
     * along with this example source files for compilation.
     */
    /* ps7_init();*/
    /* psu_init();*/
    enable_caches();
    init_uart();
}

这里我们关心的其实是这个文件下的头文件

#include “xparameters.h”

进入这个头文件,我们可以看到,它定义了我们所有用到的外设,并都指定了一个名和基地址。

UART

/* Definitions for peripheral AXI_UARTLITE_0 */
#define XPAR_AXI_UARTLITE_0_BASEADDR 0x40600000
#define XPAR_AXI_UARTLITE_0_HIGHADDR 0x4060FFFF
#define XPAR_AXI_UARTLITE_0_DEVICE_ID 0
#define XPAR_AXI_UARTLITE_0_BAUDRATE 115200
#define XPAR_AXI_UARTLITE_0_USE_PARITY 0
#define XPAR_AXI_UARTLITE_0_ODD_PARITY 0
#define XPAR_AXI_UARTLITE_0_DATA_BITS 8

/* Canonical definitions for peripheral AXI_UARTLITE_0 */
#define XPAR_UARTLITE_0_DEVICE_ID XPAR_AXI_UARTLITE_0_DEVICE_ID
#define XPAR_UARTLITE_0_BASEADDR 0x40600000
#define XPAR_UARTLITE_0_HIGHADDR 0x4060FFFF
#define XPAR_UARTLITE_0_BAUDRATE 115200
#define XPAR_UARTLITE_0_USE_PARITY 0
#define XPAR_UARTLITE_0_ODD_PARITY 0
#define XPAR_UARTLITE_0_DATA_BITS 8

CAN

/* Definitions for driver CAN */
#define XPAR_XCAN_NUM_INSTANCES 1

/* Definitions for peripheral CAN_0 */
#define XPAR_CAN_0_DEVICE_ID 0
#define XPAR_CAN_0_BASEADDR 0x44A00000
#define XPAR_CAN_0_HIGHADDR 0x44A0FFFF
#define XPAR_CAN_0_CAN_RX_DPTH 2
#define XPAR_CAN_0_CAN_TX_DPTH 2
#define XPAR_CAN_0_CAN_NUM_ACF 0

GPIO

/* Definitions for driver GPIO */
#define XPAR_XGPIO_NUM_INSTANCES 1

/* Definitions for peripheral AXI_GPIO_0 */
#define XPAR_AXI_GPIO_0_BASEADDR 0x40000000
#define XPAR_AXI_GPIO_0_HIGHADDR 0x4000FFFF
#define XPAR_AXI_GPIO_0_DEVICE_ID 0
#define XPAR_AXI_GPIO_0_INTERRUPT_PRESENT 1
#define XPAR_AXI_GPIO_0_IS_DUAL 1


/******************************************************************/

/* Canonical definitions for peripheral AXI_GPIO_0 */
#define XPAR_GPIO_0_BASEADDR 0x40000000
#define XPAR_GPIO_0_HIGHADDR 0x4000FFFF
#define XPAR_GPIO_0_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
#define XPAR_GPIO_0_INTERRUPT_PRESENT 1
#define XPAR_GPIO_0_IS_DUAL 1


/******************************************************************/

中断控制器

/* Definitions for peripheral MICROBLAZE_0_AXI_INTC */
#define XPAR_MICROBLAZE_0_AXI_INTC_DEVICE_ID 0
#define XPAR_MICROBLAZE_0_AXI_INTC_BASEADDR 0x41200000
#define XPAR_MICROBLAZE_0_AXI_INTC_HIGHADDR 0x4120FFFF
#define XPAR_MICROBLAZE_0_AXI_INTC_KIND_OF_INTR 0xFFFFFFFF
#define XPAR_MICROBLAZE_0_AXI_INTC_HAS_FAST 1
#define XPAR_MICROBLAZE_0_AXI_INTC_IVAR_RESET_VALUE 0x0000000000000010
#define XPAR_MICROBLAZE_0_AXI_INTC_NUM_INTR_INPUTS 3
#define XPAR_MICROBLAZE_0_AXI_INTC_ADDR_WIDTH 32


/******************************************************************/

#define XPAR_INTC_SINGLE_BASEADDR 0x41200000
#define XPAR_INTC_SINGLE_HIGHADDR 0x4120FFFF
#define XPAR_INTC_SINGLE_DEVICE_ID XPAR_MICROBLAZE_0_AXI_INTC_DEVICE_ID
#define XPAR_MICROBLAZE_0_AXI_INTC_TYPE 0U
#define XPAR_AXI_UARTLITE_0_INTERRUPT_MASK 0X000001U
#define XPAR_MICROBLAZE_0_AXI_INTC_AXI_UARTLITE_0_INTERRUPT_INTR 0U
#define XPAR_AXI_GPIO_0_IP2INTC_IRPT_MASK 0X000002U
#define XPAR_MICROBLAZE_0_AXI_INTC_AXI_GPIO_0_IP2INTC_IRPT_INTR 1U
#define XPAR_CAN_0_IP2BUS_INTREVENT_MASK 0X000004U
#define XPAR_MICROBLAZE_0_AXI_INTC_CAN_0_IP2BUS_INTREVENT_INTR 2U

/******************************************************************/

2.5 重点!

我之前一直强调:
对于这些外设的开发就是针对地址空间的读写。
我们来深究这一点。

    print("Hello World\n\r");

在helloworld文件中,有个print函数,我们可以知道就是将数据通过串口打印出去的函数。

#include "xil_printf.h"

void print(const char8 *ptr)
{
#if HYP_GUEST && EL1_NONSECURE && XEN_USE_PV_CONSOLE
	XPVXenConsole_Write(ptr);
#else
#ifdef STDOUT_BASEADDRESS
  while (*ptr != (char8)0) {
    outbyte (*ptr);
	ptr++;
  }
#else
(void)ptr;
#endif
#endif
}

按住CTRL+鼠标左键(或者 右键–> open declaration)可以查看他的定义。
可以看到这里明显是使用outbyte函数输出的。对

outbyte (*ptr);

进行溯源!

void outbyte(char c) {
	 XUartLite_SendByte(STDOUT_BASEADDRESS, c);
}

发现就是把XuartLite_SendByte()封装了一层。直接将要传输的变量给了XuartLite_SendByte。

注意:这里我们有个参数也被传了进去STDOUT_BASEADDRESS,这个是什么呢,这个就是我们当时指定调试的输出串口的基地址,因为我们在bsp的设置中改成了mdm,所以这个基地址是与mdm_1的一致。
在这里插入图片描述


/******************************************************************/

#define STDIN_BASEADDRESS 0x41400000
#define STDOUT_BASEADDRESS 0x41400000

/* Definitions for peripheral MDM_1 */
#define XPAR_MDM_1_BASEADDR 0x41400000
#define XPAR_MDM_1_HIGHADDR 0x41400FFF
#define XPAR_MDM_1_DEVICE_ID 1
#define XPAR_MDM_1_BAUDRATE 0
#define XPAR_MDM_1_USE_PARITY 0
#define XPAR_MDM_1_ODD_PARITY 0
#define XPAR_MDM_1_DATA_BITS 0

/******************************************************************/

继续对

XUartLite_SendByte(STDOUT_BASEADDRESS, c);

溯源!

******************************************************************************/
void XUartLite_SendByte(UINTPTR BaseAddress, u8 Data)
{
	while (XUartLite_IsTransmitFull(BaseAddress));

	XUartLite_WriteReg(BaseAddress, XUL_TX_FIFO_OFFSET, Data);
}


/****************************************************************************/
/**
*
* This functions receives a single byte using the UART. It is blocking in that
* it waits for the receiver to become non-empty before it reads from the
* receive register.
*
* @param	BaseAddress is the base address of the device
*
* @return	The byte of data received.
*
* @note		None.
*
******************************************************************************/
u8 XUartLite_RecvByte(UINTPTR BaseAddress)
{
	while (XUartLite_IsReceiveEmpty(BaseAddress));

	return (u8)XUartLite_ReadReg(BaseAddress, XUL_RX_FIFO_OFFSET);
}

/** @} */

发现到了XUartLite收数和发数的函数定义文件中,我们在使用uart lite时,完成初始化后,也可以直接用这两个函数。
写数据的时候不过就是写寄存器,我们对

XUartLite_WriteReg(BaseAddress, XUL_TX_FIFO_OFFSET, Data);

继续溯源!
注意:这里的传参是:基地址,偏移地址,数据

****************************************************************************/
#define XUartLite_WriteReg(BaseAddress, RegOffset, Data) \
	XUartLite_Out32((BaseAddress) + (RegOffset), (u32)(Data))

/****************************************************************************/
/**
*
* Read a value from a UartLite register. A 32 bit read is performed.
*
* @param	BaseAddress is the base address of the UartLite device.
* @param	RegOffset is the register offset from the base to read from.
*
* @return	Data read from the register.
*
* @note		C-style signature:
*		u32 XUartLite_ReadReg(u32 BaseAddress, u32 RegOffset)
*
****************************************************************************/
#define XUartLite_ReadReg(BaseAddress, RegOffset) \
	XUartLite_In32((BaseAddress) + (RegOffset))


/****************************************************************************/

到了更简单的两个函数,
注意:这里的传参变成了地址和数据。

XUartLite_Out32((BaseAddress) + (RegOffset), (u32)(Data))

进行溯源!

#define XUartLite_In32  Xil_In32
#define XUartLite_Out32 Xil_Out32

继续!

******************************************************************************/
static INLINE void Xil_Out32(UINTPTR Addr, u32 Value)
{
#ifndef ENABLE_SAFETY
	volatile u32 *LocalAddr = (volatile u32 *)Addr;
	*LocalAddr = Value;
#else
	XStl_RegUpdate(Addr, Value);
#endif
}

这里终于可以看出,对于任何外设的读写,最后都变成了对指定地址的读写。因为已经转换成了一个与外设类型无关的读写函数。

void Xil_Out32(UINTPTR Addr, u32 Value)

同理 读也差不多。有兴趣可以自己看一下,读比写更加复杂。

2.6 debug

  1. 右键选中hello文件夹
  2. 选中debug as
  3. 选中debug configurations
  4. 双击system debugger
  5. 勾选Reset entire system和Program FPGA

在这里插入图片描述
到这里Hello world的开发都完成了,但是由于本人手上现在没有FPGA开发板,暂时无法测试工程。

3. 后言

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Bigbeea

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

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

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

打赏作者

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

抵扣说明:

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

余额充值