此前的文章介绍如何利用petalinux定制ZYNQ-Linux操作系统。当ZYNQ-Linux系统搭建完毕后,需要在这个系统上开发应用程序以完成特定任务,这里面就涉及到如何在ZYNQ-Linux系统上去操作系统硬件资源的问题。目前,网上介绍的比较多的是需要改写Linux操作系统底层的设备树,并编写设备的驱动程序,这样的好处是可以提供一个统一的硬件接口,做到软硬件分离,可以更好地去保护硬件资源,但是实际操作对于初学者来说比较复杂。
本文将介绍一种简单的操作硬件方法,对于一些简单应用,可以直接操作系统的硬件资源,读写寄存器,不需要改写设备树和编写驱动程序。本文将以操作GPIO和操作VDMA两个例子做说明。
本次操作利用GPIO实现LED的点亮及熄灭。硬件如下图所示,LED和ZYNQ的MIO7引脚进行相连。
在目标板的系统中进行入/sys/class/gpio
中查看第一个IO设备的编号,从下图可以看出笔者的目标板第一个IO设备的编号为906
初始化的代码如下
FILE *fp = NULL;
char path[64];
int led_num = 913;
fp = fopen("/sys/class/gpio/export", "w");
if (fp == NULL)
perror("export open filed");
else
fprintf(fp, "%d", led_num); //创建接口
fclose(fp);
memset(path, 0, sizeof path);
snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/direction", led_num); //将可变参数格式化成字符串存入path[]
fp = fopen(path, "w");
fprintf(fp, "out"); //设置为输出
fclose(fp);
初始化过程完成了两件事,一个是创建了接口,一个是设置了接口的输入和输出特性。
操作GPIO,控制LED灯的亮灭。
FILE *fp = NULL;
int led_num = 913;
char path[50] = {"\0"};
sprintf(path, "/sys/class/gpio/gpio%d/value", led_num);
fp = fopen(path, "w");
fprintf(fp, "%d", 1); // LED亮
//fprintf(fp, "%d", 0); // LED灭
fclose(fp);
跟前面设置GPIO特性一样,对于GPIO的操作,同样也是基于一个文件操作。
本次操作利用VDMA将LMH0341接收到视频写入到DDR内存中,再从内存从读取并通过LMH0340进行输出。ZYNQ的模块连接图如下图所示。
首先需要确认VDMA地址,可以利用vivado软件查看VDMA,打开vivado软件并选择Address Editor选项卡可以看到VDMA的地址。如下图。
VDMA的设置如下
LMH0341接收的数据是10Bit的宽度,一个像素值的数据长度是20Bit,所以stream data width设置为24,内部memory map data width设置32。帧缓存的深度设置为3帧。
VDMA实现视频数据搬运的代码如下。
#include
#include
#include
#include
#include
#include
/* Register offsets */
#define OFFSET_PARK_PTR_REG 0x28
#define OFFSET_VERSION 0x2c
#define OFFSET_VDMA_MM2S_CONTROL_REGISTER 0x00
#define OFFSET_VDMA_MM2S_STATUS_REGISTER 0x04
#define OFFSET_VDMA_MM2S_VSIZE 0x50
#define OFFSET_VDMA_MM2S_HSIZE 0x54
#define OFFSET_VDMA_MM2S_FRMDLY_STRIDE 0x58
#define OFFSET_VDMA_MM2S_FRAMEBUFFER1 0x5c
#define OFFSET_VDMA_MM2S_FRAMEBUFFER2 0x60
#define OFFSET_VDMA_MM2S_FRAMEBUFFER3 0x64
#define OFFSET_VDMA_MM2S_FRAMEBUFFER4 0x68
#define OFFSET_VDMA_S2MM_CONTROL_REGISTER 0x30
#define OFFSET_VDMA_S2MM_STATUS_REGISTER 0x34
#define OFFSET_VDMA_S2MM_IRQ_MASK 0x3c
#define OFFSET_VDMA_S2MM_REG_INDEX 0x44
#define OFFSET_VDMA_S2MM_VSIZE 0xa0
#define OFFSET_VDMA_S2MM_HSIZE 0xa4
#define OFFSET_VDMA_S2MM_FRMDLY_STRIDE 0xa8
#define OFFSET_VDMA_S2MM_FRAMEBUFFER1 0xac
#define OFFSET_VDMA_S2MM_FRAMEBUFFER2 0xb0
#define OFFSET_VDMA_S2MM_FRAMEBUFFER3 0xb4
#define OFFSET_VDMA_S2MM_FRAMEBUFFER4 0xb8
/* S2MM and MM2S control register flags */
#define VDMA_CONTROL_REGISTER_START 0x00000001
#define VDMA_CONTROL_REGISTER_CIRCULAR_PARK 0x00000002
#define VDMA_CONTROL_REGISTER_RESET 0x00000004
#define VDMA_CONTROL_REGISTER_GENLOCK_ENABLE 0x00000008
#define VDMA_CONTROL_REGISTER_FrameCntEn 0x00000010
#define VDMA_CONTROL_REGISTER_INTERNAL_GENLOCK 0x00000080
#define VDMA_CONTROL_REGISTER_WrPntr 0x00000f00
#define VDMA_CONTROL_REGISTER_FrmCtn_IrqEn 0x00001000
#define VDMA_CONTROL_REGISTER_DlyCnt_IrqEn 0x00002000
#define VDMA_CONTROL_REGISTER_ERR_IrqEn 0x00004000
#define VDMA_CONTROL_REGISTER_Repeat_En 0x00008000
#define VDMA_CONTROL_REGISTER_InterruptFrameCount 0x00ff0000
#define VDMA_CONTROL_REGISTER_IRQDelayCount 0xff000000
/* S2MM status register */
#define VDMA_STATUS_REGISTER_HALTED 0x00000001 // Read-only
#define VDMA_STATUS_REGISTER_VDMAInternalError 0x00000010 // Read or write-clear
#define VDMA_STATUS_REGISTER_VDMASlaveError 0x00000020 // Read-only
#define VDMA_STATUS_REGISTER_VDMADecodeError 0x00000040 // Read-only
#define VDMA_STATUS_REGISTER_StartOfFrameEarlyError 0x00000080 // Read-only
#define VDMA_STATUS_REGISTER_EndOfLineEarlyError 0x00000100 // Read-only
#define VDMA_STATUS_REGISTER_StartOfFrameLateError 0x00000800 // Read-only
#define VDMA_STATUS_REGISTER_FrameCountInterrupt 0x00001000 // Read-only
#define VDMA_STATUS_REGISTER_DelayCountInterrupt 0x00002000 // Read-only
#define VDMA_STATUS_REGISTER_ErrorInterrupt 0x00004000 // Read-only
#define VDMA_STATUS_REGISTER_EndOfLineLateError 0x00008000 // Read-only
#define VDMA_STATUS_REGISTER_FrameCount 0x00ff0000 // Read-only
#define VDMA_STATUS_REGISTER_DelayCount 0xff000000 // Read-only
typedef struct {
unsigned int baseAddr;
int vdmaHandler;
int width;
int height;
int pixelLength;
int fbLength;
unsigned int* vdmaVirtualAddress;
unsigned char* fb1VirtualAddress;
unsigned int fb1PhysicalAddress;
unsigned char* fb2VirtualAddress;
unsigned int fb2PhysicalAddress;
unsigned char* fb3VirtualAddress;
unsigned int fb3PhysicalAddress;
pthread_mutex_t lock;
} vdma_handle;
int vdma_setup(vdma_handle *handle, unsigned int baseAddr, int width, int height, int pixelLength, unsigned int fb1Addr, unsigned int fb2Addr, unsigned int fb3Addr)
{
handle->baseAddr = baseAddr;
handle->width = width;
handle->height = height;
handle->pixelLength = pixelLength;
handle->fbLength = pixelLength * width * height;
handle->vdmaHandler = open("/dev/mem", O_RDWR | O_SYNC);
handle->vdmaVirtualAddress = (unsigned int*)mmap(NULL, 65535, PROT_READ | PROT_WRITE, MAP_SHARED, handle->vdmaHandler, (off_t)handle->baseAddr);
if(handle->vdmaVirtualAddress == MAP_FAILED) {
perror("vdmaVirtualAddress mapping for absolute memory access failed.\n");
return -1;
}
handle->fb1PhysicalAddress = fb1Addr;
handle->fb1VirtualAddress = (unsigned char*)mmap(NULL, handle->fbLength, PROT_READ | PROT_WRITE, MAP_SHARED, handle->vdmaHandler, (off_t)fb1Addr);
if(handle->fb1VirtualAddress == MAP_FAILED) {
perror("fb1VirtualAddress mapping for absolute memory access failed.\n");
return -2;
}
handle->fb2PhysicalAddress = fb2Addr;
handle->fb2VirtualAddress = (unsigned char*)mmap(NULL, handle->fbLength, PROT_READ | PROT_WRITE, MAP_SHARED, handle->vdmaHandler, (off_t)fb2Addr);
if(handle->fb2VirtualAddress == MAP_FAILED) {
perror("fb2VirtualAddress mapping for absolute memory access failed.\n");
return -3;
}
handle->fb3PhysicalAddress = fb3Addr;
handle->fb3VirtualAddress = (unsigned char*)mmap(NULL, handle->fbLength, PROT_READ | PROT_WRITE, MAP_SHARED, handle->vdmaHandler, (off_t)fb3Addr);
if(handle->fb3VirtualAddress == MAP_FAILED) {
perror("fb3VirtualAddress mapping for absolute memory access failed.\n");
return -3;
}
memset(handle->fb1VirtualAddress, 255, handle->width * handle->height * handle->pixelLength);
memset(handle->fb2VirtualAddress, 255, handle->width * handle->height * handle->pixelLength);
memset(handle->fb3VirtualAddress, 255, handle->width * handle->height * handle->pixelLength);
return 0;
}
unsigned int vdma_get(vdma_handle *handle, int num)
{
return handle->vdmaVirtualAddress[num >> 2];
}
void vdma_set(vdma_handle *handle, int num, unsigned int val)
{
handle->vdmaVirtualAddress[num >> 2] = val;
}
void vdma_halt(vdma_handle *handle)
{
vdma_set(handle, OFFSET_VDMA_S2MM_CONTROL_REGISTER, VDMA_CONTROL_REGISTER_RESET);
vdma_set(handle, OFFSET_VDMA_MM2S_CONTROL_REGISTER, VDMA_CONTROL_REGISTER_RESET);
munmap((void *)handle->vdmaVirtualAddress, 65535);
munmap((void *)handle->fb1VirtualAddress, handle->fbLength);
munmap((void *)handle->fb2VirtualAddress, handle->fbLength);
munmap((void *)handle->fb3VirtualAddress, handle->fbLength);
close(handle->vdmaHandler);
}
void vdma_start_triple_buffering(vdma_handle *handle)
{
vdma_set(handle, OFFSET_VDMA_S2MM_CONTROL_REGISTER, 0x0000108B);
vdma_set(handle, OFFSET_VDMA_S2MM_FRAMEBUFFER1, handle->fb1PhysicalAddress);
vdma_set(handle, OFFSET_VDMA_S2MM_FRAMEBUFFER2, handle->fb2PhysicalAddress);
vdma_set(handle, OFFSET_VDMA_S2MM_FRAMEBUFFER3, handle->fb3PhysicalAddress);
vdma_set(handle, OFFSET_VDMA_S2MM_FRMDLY_STRIDE, handle->width * 3); // A8
vdma_set(handle, OFFSET_VDMA_S2MM_HSIZE, handle->width * 3); // A4
vdma_set(handle, OFFSET_VDMA_S2MM_VSIZE, handle->height); // A0
vdma_set(handle, OFFSET_VDMA_MM2S_CONTROL_REGISTER, 0x0000008B);
vdma_set(handle, OFFSET_VDMA_MM2S_FRAMEBUFFER1, handle->fb1PhysicalAddress);
vdma_set(handle, OFFSET_VDMA_MM2S_FRAMEBUFFER2, handle->fb2PhysicalAddress);
vdma_set(handle, OFFSET_VDMA_MM2S_FRAMEBUFFER3, handle->fb3PhysicalAddress);
vdma_set(handle, OFFSET_VDMA_MM2S_FRMDLY_STRIDE, handle->width * 3); // 58
vdma_set(handle, OFFSET_VDMA_MM2S_HSIZE, handle->width * 3); // 54
vdma_set(handle, OFFSET_VDMA_MM2S_VSIZE, handle->height); // 50
}
int main(int argc, char *argv[])
{
//int i, j;
vdma_handle handle;
QCoreApplication a(argc, argv);
// Setup VDMA handle and memory-mapped ranges
vdma_setup(&handle, 0x43000000, 1920, 1080, 3, 0x01000000, 0x02000000, 0x03000000);
// Start triple buffering
vdma_start_triple_buffering(&handle);
sleep(60);
// Halt VDMA and unmap memory ranges
vdma_halt(&handle);
return a.exec();
}
vdma_setup
是初始化VDMA的函数 ,从函数的实现来看,在ZYNQ-Linux下实现VDMA操作的方法也是通过文件操作的方式,handle->vdmaHandler = open("/dev/mem", O_RDWR | O_SYNC);
由于帧缓存的深度为3,初始化的主要任务是建立3个缓存物理地址和VDMA虚拟地址的映射关系。
vdma_start_triple_buffering
中实现了S2MM
和MM2S
的寄存器设置,相应寄存器的位定义可以参考Xilinx官方文档pg020。
本文主要参考了以下两篇博客,并在前人的基础上做了一些修正和补充。如有错误,欢迎指正。
https://blog.csdn.net/qq_37280428/article/details/123630452
https://blog.csdn.net/luotong86/article/details/52486365