led控制除了在裸机开发中使用寄存器编程以外,当板子运行linux操作系统时,需要在内核进行驱动,应用程序通过设备文件的IO接口,操作内核驱动中的相关函数,通过地址映射,进而控制寄存器的地址的值。
步骤:
1.实现模块加载和卸载入口函数
2.在模块加载函数中实现 a.申请设备号(register_chrdev()) b.常见设备文件(class_create()和device_create()) c.将寄存器物理地址映射为内核虚拟地址(ioremap())、申请中断以及配置寄存器(readl()和writel())
3.实现file_operations结构体中的相关open、read、 write等函数。
驱动代码
#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/slab.h> #include <asm/uaccess.h> #include <asm/io.h> //设计一个类型,描述一个设备的信息 struct led_desc{ unsigned int dev_major; //设备号 struct class *cls; struct device *dev; //创建设备文件 void *reg_virt_base; //表示是寄存器地址到基准值 }; //物理地址 #define GPX2_CON 0x11000C40 #define GPX2_SIZE 8 struct led_desc *led_dev;//表示一个全局的设备对象 static int kernel_val = 555; // read(fd, buf, size); ssize_t led_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos) { printk("-------%s-------\n", __FUNCTION__); int ret; ret = copy_to_user(buf, &kernel_val, count); if(ret > 0) { printk("copy_to_user error\n"); return -EFAULT; } return 0; } ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos) { //printk("-------%s-------\n", __FUNCTION__); int ret; int value; ret = copy_from_user(&value, buf, count); if(ret > 0) { printk("copy_to_user error\n"); return -EFAULT; } if(value){ writel( readl(led_dev->reg_virt_base + 4) | (1<<7), led_dev->reg_virt_base + 4 ); }else{ writel( readl(led_dev->reg_virt_base + 4) & ~(1<<7), led_dev->reg_virt_base + 4 ); } return 0; } int led_drv_open(struct inode *inode, struct file *filp) { printk("-------%s-------\n", __FUNCTION__); return 0; } int led_drv_close(struct inode *inode, struct file *filp) { printk("-------%s-------\n", __FUNCTION__); return 0; } const struct file_operations my_fops = { .open = led_drv_open, .read = led_drv_read, .write = led_drv_write, .release = led_drv_close, }; static int __init led_dev_init(void) { int ret; // 0, 实例化全局的设备对象--分配空间 led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL); if(led_dev == NULL) { printk(KERN_ERR "malloc error\n"); return -ENOMEM; } // 1, 一般都是申请设备号资源 // 申请设备号 led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops); if(led_dev->dev_major < 0) { printk(KERN_ERR "register_chrdev error\n"); ret = -ENODEV; goto err_0; } // 2,创建设备文件 led_dev->cls = class_create(THIS_MODULE, "led_cls"); if(IS_ERR(led_dev->cls)) { printk(KERN_ERR "class_create error\n"); ret = PTR_ERR(led_dev->cls); //将指针出错的具体原因转换成一个出错码 goto err_1; } // /dev/led0 led_dev->dev = device_create(led_dev->cls, NULL, MKDEV(led_dev->dev_major, 0), NULL, "led%d", 0); if(IS_ERR(led_dev->dev)) { printk(KERN_ERR "device_create error\n"); ret = PTR_ERR(led_dev->dev); //将指针出错的具体原因转换成一个出错码 goto err_2; } // 3,硬件初始化 // 对地址进行映射 led_dev->reg_virt_base = ioremap(GPX2_CON, GPX2_SIZE); if(led_dev->reg_virt_base == NULL) { printk(KERN_ERR "ioremap error\n"); ret = -ENOMEM; goto err_3; } // gpio的输出功能的配置 u32 value = readl(led_dev->reg_virt_base); value &= ~(0xf<<28); value |= (0x1<<28); writel(value, led_dev->reg_virt_base); return 0; err_3: device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0)); err_2: class_destroy(led_dev->cls); err_1: unregister_chrdev(led_dev->dev_major, "led_dev_test"); err_0: kfree(led_dev); return ret; } static void __exit led_dev_exit(void) { //一般都是释放资源 iounmap(led_dev->reg_virt_base); device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0)); class_destroy(led_dev->cls); unregister_chrdev(led_dev->dev_major, "led_dev_test"); kfree(led_dev); } //驱动模块加载和卸载函数申明 module_init(led_dev_init); module_exit(led_dev_exit); MODULE_LICENSE("GPL");
编写Makefile将驱动程序编译成ko文件
Makefile
ROOTFS_DIR = /opt/4412/rootfs MODULE_NAME = led_drv APP_NAME = led_test CROSS_COMPILE = /home/george/Linux_4412/toolchain/gcc-4.6.4/bin/arm-none-linux-gnueabi- CC = $(CROSS_COMPILE)gcc ifeq ($(KERNELRELEASE), ) KERNEL_DIR = /home/george/Linux_4412/kernel/linux-3.14 CUR_DIR = $(shell pwd) all : make -C $(KERNEL_DIR) M=$(CUR_DIR) modules $(CC) $(APP_NAME).c -o $(APP_NAME) clean : make -C $(KERNEL_DIR) M=$(CUR_DIR) clean rm -rf $(APP_NAME) install: cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module else obj-m += $(MODULE_NAME).o endif
在板子上的linux系统上输入
insmod led_drv.ko 并且 cat /proc/devices查看设备号
应用程序
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char *argv[]) { //调用驱动 int fd; int value = 0; fd = open("/dev/led0", O_RDWR); if(fd < 0) { perror("open"); exit(1); } read(fd, &value, 4); printf("___USER___: value = %d\n", value); //应用程序去控制灯到亮和灭 while(1) { value = 0; write(fd, &value, 4); sleep(1); value = 1; write(fd, &value, 4); sleep(1); } close(fd); return 0; }
文章来源: armA9单片机liunxOS下led驱动开发