文章目录
1. 写驱动框架
2. 硬件相关(原理图 手册 写代码)
一、board相关原理图和芯片datasheet
1. 原理图
2. datasheet
二、驱动/用户/Makefile代码
1. led驱动led_chrdev1.c
#include <linux/init.h> // module_init module_exit
#include <linux/module.h> // MODULE_LICENSE
#include <linux/fs.h> // file_operations
#include <linux/cdev.h> // cdev
#include <linux/kernel.h>
#include <linux/device.h> // class_device
#include <asm/uaccess.h> // copy_from_user copy_to_user
#include <asm/io.h> // ioremap iounmap
// GPF引脚的con和dat寄存器,地址需要映射
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
// 用于自动创建设备节点的结构class 和 class_device
static struct class *demo_class;
static struct class_device *demo_class_devs;
// 操作方法
// 配置GPF4/5/6为输出模式
static int led_open(struct inode *inode, struct file *filep)
{
*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); // 先清零 8-13 01010101
*gpfcon |= (0x1<<(4*2) | 0x1<<(5*2) | 0x1<<(6*2)); // 再对应位置1
printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);
return 0;
}
// 配置GPF4/5/6输出高或者低电平
static ssize_t led_write(struct file *filep, const char __user *buf, size_t count, loff_t *ppos)
{
// 在用户层传入的形式是 write(fd, &val, 4); val是buf,4是count
// 在内核层获取用户层传入的参数,用copy_from_user; 内核空间到用户传参数copy_to_user
int user_param = 0;
copy_from_user(&user_param, buf, count); // 从buf拷贝到user_param,长度为count
if (1 == user_param) // 开灯
{
// 4-6 000
*gpfdat &= ~((0x7<<4));
}
else if (0 == user_param) // 关灯
{
*gpfdat |= (0x7<<4);
}
printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);
return 0;
}
// 操作方法集
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
// 用于保存获取到的主设备号
unsigned int major = 0;
// 入口函数
static int demo_cdev_init(void)
{
// 自动获取主设备号资源;主设备号对应的名字在/proc/devices中,
// 内核驱动模块的名字是Makefile里指定的(一般跟驱动.c文件名字对应)
major = register_chrdev(0, "major_name_led", &fops); // 0自动分配,
// 以下为 在系统中生成设备信息的步骤
// 1. 新建一个class
demo_class = class_create(THIS_MODULE, "led_chrdev_class1");
if (IS_ERR(demo_class))
return PTR_ERR(demo_class);
// 2. 在class里边创建一个设备叫xxx,然后mdev自动创建设备节点/dev/xxx
// 在/dev目录下创建相应的设备节点
demo_class_devs = class_device_create(demo_class, NULL, MKDEV(major, 0), NULL, "led_on_off1");
if (unlikely(IS_ERR(demo_class_devs)))
return PTR_ERR(demo_class_devs);
// led寄存器的地址映射,只需要执行一次,所以在入口函数映射
gpfcon = (volatile unsigned long*)ioremap(0x56000050, 16); // start, size
gpfdat = gpfcon + 1; // unsigned long的长度
return 0;
}
// 出口函数
static void demo_cdev_exit(void)
{
// 对应卸载
unregister_chrdev(major, "major_name_led");
class_device_unregister(demo_class_devs);
class_destroy(demo_class);
// 去掉映射关系
iounmap(gpfcon);
}
// 内核模块相关
module_init(demo_cdev_init);
module_exit(demo_cdev_exit);
MODULE_LICENSE("GPL");
2. Makefile
KERN_DIR := /home/wuyexkx/Desktop/韦东山/system/linux-2.6.22.6
PWD := $(shell pwd) # 执行pwd命令并把结果赋给PWD
obj-m := led_chrdev1.o # .ko的生成依赖于.o,.o默认依赖.c
all:
make -C $(KERN_DIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm # modules为编译目标
clean:
make -C $(KERN_DIR) M=$(PWD) clean
3. 用户user_led1.c
#include <fcntl.h>
#include <stdio.h>
// main传入2个参数 user_led1 on/off
int main(int argc, char **argv)
{
int fd;
int val = 1;
// 打开 mdev根据驱动程序中的class_device_create自动创建的 设备节点
fd = open("/dev/led_on_off1", O_RDWR);
if(fd < 0)
printf("can't open '/dev/led_on_off1'\n");
// main参数个数判断
if(argc != 2)
{
printf("Usage:\n");
printf("\t%s <on|off>\n", argv[0]);
return 0;
}
// 第二参数判断
if(0 == strcmp(argv[1], "on")) // 开灯参数
{
val = 1;
}
else if(0 == strcmp(argv[1], "off"))// 关灯参数
{
val = 0;
}
// 根据第二参数write
write(fd, &val, 4);
return 0;
}
三、插入内核模块 自动创建设备节点 执行用户led
查看插入的内核驱动模块,cat proc/devices
查看自动创建的设备节点,ls dev/led_on_off1 -l
执行应用程序,./mnt/tzb/user_led1
提示使用信息;
./mnt/tzb/user_led1 on
开灯;
./mnt/tzb/user_led1 off
关灯:
四、用次设备号改进led驱动代码
1. led驱动led_chrdev2.c
#include <linux/init.h> // module_init module_exit
#include <linux/module.h> // MODULE_LICENSE
#include <linux/fs.h> // file_operations
#include <linux/cdev.h> // cdev
#include <linux/kernel.h>
#include <linux/device.h> // class_device
#include <asm/uaccess.h> // copy_from_user copy_to_user
#include <asm/io.h> // ioremap iounmap
#define DEVICE_NAME "leds" // 加载模式后,执行”cat /proc/devices”命令看到的设备名称
int major = 0; // 用于保存获取到的主设备号
// GPF引脚的con和dat寄存器,地址需要映射
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
#define GPFCON (*gpfcon)
#define GPFDAT (*gpfdat)
// 用于自动创建设备节点的结构class 和 class_device
static struct class *leds_class;
static struct class_device *leds_class_dev[4];
// 操作方法
// 根据次设备号 配置GPF4/5/6为输出模式
static int leds_open(struct inode *inode, struct file *filep)
{
// 从inode中提取次设备号
int minor = MINOR(inode->i_rdev);
switch(minor)
{
case 0: // leds
{
GPFCON &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); // 先清零 8-13 01010101
GPFCON |= (0x1<<(4*2) | 0x1<<(5*2) | 0x1<<(6*2)); // 再对应位置1
break;
}
case 1: // led1
{
GPFCON &= ~(0x3<<(4*2));
GPFCON |= (0x1<<(4*2));
break;
}
case 2: // led2
{
GPFCON &= ~(0x3<<(5*2));
GPFCON |= (0x1<<(5*2));
break;
}
case 3: // led3
{
GPFCON &= ~(0x3<<(6*2));
GPFCON |= (0x1<<(6*2));
break;
}
}
printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);
return 0;
}
// 配置GPF4/5/6输出高或者低电平
static ssize_t leds_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
// 获取次设备号
int minor = MINOR(filp->f_dentry->d_inode->i_rdev);
// 在用户层传入的形式是 write(fd, &val, 4); val是buf,4是count
// 在内核层获取用户层传入的参数,用copy_from_user; 内核空间到用户传参数copy_to_user
int user_param = 0;
copy_from_user(&user_param, buf, count); // 从buf拷贝到user_param,长度为count
switch(minor)
{
case 0:
{
if (user_param == 1)
GPFDAT |= (0x7 << 4);
else if (user_param == 0)
GPFDAT &= ~(0x7 << 4); // 4-6 000
break;
}
case 1:
{
if (user_param == 1)
GPFDAT |= (0x1 << 4);
else if (user_param == 0)
GPFDAT &= ~(0x1 << 4); // 4 0
break;
}
case 2:
{
if (user_param == 1)
GPFDAT |= (0x1 << 5);
else if (user_param == 0)
GPFDAT &= ~(0x1 << 5); // 5 0
break;
}
case 3:
{
if (user_param == 1)
GPFDAT |= (0x1 << 6);
else if (user_param == 0)
GPFDAT &= ~(0x1 << 6); // 6 0
break;
}
}
printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);
return 0;
}
// 操作方法集
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = leds_open,
.write = leds_write,
};
// 入口函数
static int leds_init(void)
{
int minor = 0; // 保存次设备号
// led寄存器的地址映射,只需要执行一次,所以在入口函数映射
gpfcon = (volatile unsigned long*)ioremap(0x56000050, 16); // start, size
gpfdat = gpfcon + 1; // unsigned long的长度
// 0. 自动获取主设备号资源;主设备号对应的名字在/proc/devices中,
// 内核驱动模块的名字是Makefile里指定的(一般跟驱动.c文件名字对应)
major = register_chrdev(0, DEVICE_NAME, &fops); // 0自动分配,
if (major < 0)
{
printk(DEVICE_NAME " can't register major number.\n");
return major;
}
// 以下为 在系统中生成设备信息的步骤
// 1. 新建一个class
leds_class = class_create(THIS_MODULE, "leds_class");
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
// 2. 在class里边创建一个设备叫xxx,然后mdev自动创建设备节点/dev/xxx
// 在/dev目录下创建相应的设备节点,0全部led设备节点
leds_class_dev[0] = class_device_create(leds_class, NULL, MKDEV(major, 0), NULL, "led0");
if (unlikely(IS_ERR(leds_class_dev[0])))
return PTR_ERR(leds_class_dev[0]);
// 1,2,3对应led1/2/3设备节点
for (minor=1; minor<4; ++minor)
{
leds_class_dev[minor] = class_device_create(leds_class, NULL, MKDEV(major, minor), NULL, "led%d", minor);
if (unlikely(IS_ERR(leds_class_dev[minor])))
return PTR_ERR(leds_class_dev[minor]);
}
printk(DEVICE_NAME " device initialized successfully...\n\n");
return 0;
}
// 出口函数
static void leds_exit(void)
{
int minor = 0; // 保存次设备号
// 对应卸载
unregister_chrdev(major, DEVICE_NAME);
for (minor=0; minor<4; ++minor)
{
class_device_unregister(leds_class_dev[minor]);
}
class_destroy(leds_class);
// 去掉映射关系
iounmap(gpfcon);
}
// 内核模块相关
module_init(leds_init);
module_exit(leds_exit);
MODULE_LICENSE("GPL");
2. Makefile
KERN_DIR := /home/wuyexkx/Desktop/韦东山/system/linux-2.6.22.6
PWD := $(shell pwd) # 执行pwd命令并把结果赋给PWD
obj-m := led_chrdev2.o # .ko的生成依赖于.o,.o默认依赖.c
all:
make -C $(KERN_DIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm # modules为编译目标
clean:
make -C $(KERN_DIR) M=$(PWD) clean
3. 用户user_led2.c
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
void print_usage(const char *argv0)
{
printf("Usage:\n");
printf("\t%s led<x> <on|off>\t[x=0~3]\n", argv0);
printf("Example:\n");
printf("\t%s led0 on\n", argv0);
printf("\t%s led0 off\n\n", argv0);
}
// main传入3个参数 user_led2 led0 on/off
int main(int argc, char **argv)
{
int fd;
int is_off = 1;
// main参数个数判断
if (argc != 3)
{
print_usage(argv[0]);
return 0;
}
// 保存设备节点名称
char device_name[10] = "/dev/";
// 根据用户输入得到完整设备节点名称 "/dev/" + "led0" = "/dev/led0"
strcat(device_name, argv[1]);
// 打开 mdev根据驱动程序中的class_device_create自动创建的 设备节点
fd = open(device_name, O_RDWR);
if(fd < 0)
{
printf("can't open %s.\n", argv[1]);
return 0;
}
// 第三参数判断
if(0 == strcmp(argv[2], "on")) // 开灯参数
{
is_off = 0;
// 根据第三参数write
write(fd, &is_off, 1);
}
else if(0 == strcmp(argv[2], "off"))// 关灯参数
{
is_off = 1;
// 根据第三参数write
write(fd, &is_off, 1);
}
else // 第三参数错误
{
printf("Unknown args '%s'.\n\n", argv[2]);
}
return 0;
}
4. 插入内核模块,运行用户代码,点亮和关闭对应led
插入内核模块,显示初始化成功,并在proc/devices
中看到为leds
设备驱动分配的主设备号252,
在dev/
中看到四个对应的设备节点,依次申请到的次设备号0/1/2/3,都是252主设备号,
运行用户代码,打印帮助信息,打开led对应的设备节点;第三参数错误也打印出来。