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
- 右键选中hello文件夹
- 选中debug as
- 选中debug configurations
- 双击system debugger
- 勾选Reset entire system和Program FPGA
到这里Hello world的开发都完成了,但是由于本人手上现在没有FPGA开发板,暂时无法测试工程。
3. 后言
无