原理图
PS_LED连接在MIO9上,ZYNQ的GPIO中,BANK0和BANK1通过MIO连接在PS端,BANK2和BANK3通过EMIO连接在PL端。
GPIO寄存器
由上图可知,ZYNQ GPIO寄存器的基地址是 0xE000A000。
上图中为GPIO的相关寄存器,为了控制PS_LED,只需关注上图中红色框里的几个特定寄存器即可。
DATA寄存器
DATA寄存器就是控制管脚输出高低电平的,所以在输出模式下,就可以通过对寄存器的相应 bit位写 0或 1来控制某个 GPIO输出电平为高还是低。
DIRM寄存器
方向控制寄存器控制GPIO的输入和输出。
OUTEN寄存器
GPIO时钟
APER_CLK_CTRL寄存器属于系统级别的控制寄存器。
该寄存器用于控制ZYNQ AMBA外设时钟,从图中可以知道该寄存器的 bit22位 GPIO时钟控制位,向该位写入 0禁止 GPIO时钟,写入 1则使能 GPIO时钟。
驱动编写
/** ===================================================== **
*Author : ALINX Electronic Technology (Shanghai) Co., Ltd.
*Website: http://www.alinx.com
*Address: Room 202, building 18,
No.518 xinbrick Road,
Songjiang District, Shanghai
*Created: 2020-3-2
*Version: 1.0
** ===================================================== **/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/ide.h>
#include <linux/types.h>
/* 驱动名称 */
#define DEVICE_NAME "gpio_leds"
/* 驱动主设备号 */
#define GPIO_LED_MAJOR 200
/* gpio寄存器虚拟地址 */
static unsigned int gpio_add_minor;
/* gpio寄存器物理基地址 */
#define GPIO_BASE 0xE000A000
/* gpio寄存器所占空间大小 */
#define GPIO_SIZE 0x1000
/* gpio方向寄存器 */
#define GPIO_DIRM_0 (unsigned int *)(0xE000A204 - GPIO_BASE + gpio_add_minor)
/* gpio使能寄存器 */
#define GPIO_OEN_0 (unsigned int *)(0xE000A208 - GPIO_BASE + gpio_add_minor)
/* gpio控制寄存器 */
#define GPIO_DATA_0 (unsigned int *)(0xE000A040 - GPIO_BASE + gpio_add_minor)
/* 时钟使能寄存器虚拟地址 */
static unsigned int clk_add_minor;
/* 时钟使能寄存器物理基地址 */
#define CLK_BASE 0xF8000000
/* 时钟使能寄存器所占空间大小 */
#define CLK_SIZE 0x1000
/* AMBA外设时钟使能寄存器 */
#define APER_CLK_CTRL (unsigned int *)(0xF800012C - CLK_BASE + clk_add_minor)
/* open函数实现, 对应到Linux系统调用函数的open函数 */
static int gpio_leds_open(struct inode *inode_p, struct file *file_p)
{
/* 把需要修改的物理地址映射到虚拟地址 */
gpio_add_minor = (unsigned int)ioremap(GPIO_BASE, GPIO_SIZE);
clk_add_minor = (unsigned int)ioremap(CLK_BASE, CLK_SIZE);
/* MIO_0时钟使能 */
// *APER_CLK_CTRL |= 0x00400000;
*APER_CLK_CTRL |= (0x1U << 22);
/* MIO_0设置成输出 */
// *GPIO_DIRM_0 |= 0x00000001;
*GPIO_DIRM_0 |= (0x1U << 9);
/* MIO_0使能 */
// *GPIO_OEN_0 |= 0x00000001;
*GPIO_OEN_0 |= (0x1U << 9);
printk("gpio_test module open\n");
return 0;
}
/* write函数实现, 对应到Linux系统调用函数的write函数 */
static ssize_t gpio_leds_write(struct file *file_p, const char __user *buf, size_t len, loff_t *loff_t_p)
{
int rst;
char writeBuf[5] = {0};
printk("gpio_test module write\n");
rst = copy_from_user(writeBuf, buf, len);
if(0 != rst)
{
return -1;
}
if(1 != len)
{
printk("gpio_test len err\n");
return -2;
}
if(1 == writeBuf[0])
{
// *GPIO_DATA_0 &= 0xFFFFFFFE;
*GPIO_DATA_0 &= (0xFFFFFFFEU << 9 );
printk("gpio_test ON\n");
}
else if(0 == writeBuf[0])
{
// *GPIO_DATA_0 |= 0x00000001;
*GPIO_DATA_0 |= (0x1U << 9);
printk("gpio_test OFF\n");
}
else
{
printk("gpio_test para err\n");
return -3;
}
return 0;
}
/* release函数实现, 对应到Linux系统调用函数的close函数 */
static int gpio_leds_release(struct inode *inode_p, struct file *file_p)
{
printk("gpio_test module release\n");
return 0;
}
/* file_operations结构体声明, 是上面open、write实现函数与系统调用函数对应的关键 */
static struct file_operations gpio_leds_fops = {
.owner = THIS_MODULE,
.open = gpio_leds_open,
.write = gpio_leds_write,
.release = gpio_leds_release,
};
/* 模块加载时会调用的函数 */
static int __init gpio_led_init(void)
{
int ret;
/* 通过模块主设备号、名称、模块带有的功能函数(及file_operations结构体)来注册模块 */
ret = register_chrdev(GPIO_LED_MAJOR, DEVICE_NAME, &gpio_leds_fops);
if (ret < 0)
{
printk("gpio_led_dev_init_ng\n");
return ret;
}
else
{
/* 注册成功 */
printk("gpio_led_dev_init_ok\n");
}
return 0;
}
/* 卸载模块 */
static void __exit gpio_led_exit(void)
{
/* 释放对虚拟地址的占用 */
iounmap((unsigned int *)gpio_add_minor);
iounmap((unsigned int *)clk_add_minor);
/* 注销模块, 释放模块对这个设备号和名称的占用 */
unregister_chrdev(GPIO_LED_MAJOR, DEVICE_NAME);
printk("gpio_led_dev_exit_ok\n");
}
/* 标记加载、卸载函数 */
module_init(gpio_led_init);
module_exit(gpio_led_exit);
/* 驱动描述信息 */
MODULE_AUTHOR("Alinx");
MODULE_ALIAS("gpio_led");
MODULE_DESCRIPTION("GPIO LED driver");
MODULE_VERSION("v1.0");
MODULE_LICENSE("GPL");
测试应用程序编写
/** ===================================================== **
*Author : ALINX Electronic Technology (Shanghai) Co., Ltd.
*Website: http://www.alinx.com
*Address: Room 202, building 18,
No.518 xinbrick Road,
Songjiang District, Shanghai
*Created: 2020-3-2
*Version: 1.0
** ===================================================== **/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd;
char buf;
/* 验证输入参数个数 */
if(3 != argc)
{
printf("none para\n");
return -1;
}
/* 打开输入的设备文件, 获取文件句柄 */
fd = open(argv[1], O_RDWR);
if(fd < 0)
{
/* 打开文件失败 */
printf("Can't open file %s\r\n", argv[1]);
return -1;
}
/* 判断输入参数, on就点亮led, off则熄灭led */
if(!strcmp("on",argv[2]))
{
printf("ps_led1 on\n");
buf = 1;
write(fd, &buf, 1);
}
else if(!strcmp("off",argv[2]))
{
printf("ps_led1 off\n");
buf = 0;
write(fd, &buf, 1);
}
else
{
/* 输入参数错误 */
printf("wrong para\n");
return -2;
}
/* 操作结束后关闭文件 */
close(fd);
return 0;
}
驱动程序编译与应用程序编译
通过ptealinux编译驱动程序,使用交叉编译工具编译应用程序,将驱动和应用程序转移到ZYNQ开发板。
******驱动加载*********
insmod ax-led-drv.ko 加载驱动模块
cat /proc/devices 查看已经被使用的设备号
mknod /dev/alinx-led c 200 0 创建设备文件
ls /dev 查找设备文件
./a.out /dev/alinx-led on 执行程序,开灯
./a.out /dev/alinx-led off 执行程序,关灯
rmmod ax_led_drv 卸载驱动模块