(三)编译设备树
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/linux-imx-imx_4.1.15_2.0.0_ga$ make dtbs
编译生成的设备树文件为imx6ull-elf1-emmc.dtb,参考《01-0 ELF1、ELF1S开发板_快速启动手册_V1》4.4节单独更新设备树。
驱动源码myled.c编写
(一)头文件引用
#include // 包含模块相关函数的头文件
#include // 包含文件系统相关函数的头文件
#include // 包含用户空间数据访问函数的头文件
#include //包含字符设备头文件
#include //包含设备节点相关的头文件
#include //包含gpio操作函数的相关头文件
(二)创建相关宏定义
#define DEVICE_NAME "mydevice" // 设备名称
#define LED_IOC_MAGIC 'k' //定义ioctl幻数
#define SET_LED_ON _IO(LED_IOC_MAGIC, 0) //定义LED开命令
#define SET_LED_OFF _IO(LED_IOC_MAGIC, 1) //定义LED关命令
#define GPIO_LED_PIN_NUM 18 //定义操作的GPIO编号
(1)ioctl幻数
ioctl幻数是一个用于区分ioctl命令的标识符。它是一个唯一的整数值,并且通常使用ASCII字符来表示。幻数的目的是确保ioctl命令的唯一性,以免不同设备的命令发生冲突。
(2)_IO宏定义
_IO宏定义用于创建无参数的ioctl命令代码。
_IO(LED_IOC_MAGIC, 0),LED_IOC_MAGIC表示定义的幻数,0或者1表示具体的命令代码。
(3)GPIO编号
在imx6ull上确定GPIO编号的公式为:GPIOn_IOx=(n-1)*32+x。
因为选择的引脚为GPIO1_IO18,所以通过(n-1)*32+x计算得到的编号为18。
(三)相关变量定义
static dev_t dev_num; //分配的设备号 struct cdev my_cdev; //字符设备指针
int major; //主设备号
int minor; //次设备号
static struct class *my_led;
static struct device *my_device;
(四)mydevice_init(void)函数的实现
static int __init mydevice_init(void)
{
int ret;
//释放之前申请的GPIO,避免申请失败
gpio_free(GPIO_LED_PIN_NUM);
// 申请GPIO,并判断是否申请成功
if (gpio_request(GPIO_LED_PIN_NUM, "led_run")) {
printk("request %s gpio faile \n", "led_run");
return -1;
}
// 注册字符设备驱动程序
ret = alloc_chrdev_region(&dev_num,0,1,DEVICE_NAME);
if (ret < 0) {
printk(KERN_ALERT "Failed to allocate device number: %d\n", ret);
return ret;
}
major=MAJOR(dev_num);
minor=MINOR(dev_num);
printk(KERN_INFO "major number: %d\n",major);
printk(KERN_INFO "minor number: %d\n",minor);
my_cdev.owner = THIS_MODULE;
cdev_init(&my_cdev,&fops);
cdev_add(&my_cdev,dev_num,1);
// 创建设备类
my_led = class_create(THIS_MODULE, "my_led");
if (IS_ERR(my_led)) {
pr_err("Failed to create class\n");
return PTR_ERR(my_led);
}
// 创建设备节点并关联到设备类
my_device = device_create(my_led, NULL, MKDEV(major, minor), NULL, "my_device");
if (IS_ERR(my_device)) {
pr_err("Failed to create device\n");
class_destroy(my_led);
return PTR_ERR(my_device);
}
printk(KERN_INFO "Device registered successfully.\n");
return 0;
}
主要添加了gpio_free()和gpio_request()函数的调用,gpio_free()函数原型如下:
void gpio_free(unsigned int gpio);
(1)参数说明
gpio:要释放的GPIO引脚的编号。
(2)返回值
无返回值。
(3)函数功能
gpio_free()函数释放先前请求的GPIO引脚,并将其解除分配。释放后的GPIO引脚可以被其他驱动程序或设备重新请求使用。
gpio_request()函数原型如下:
int gpio_request(unsigned int gpio, const char *label);
(1)参数说明
gpio:要请求的GPIO引脚的编号。
label:用于标识该GPIO引脚的描述字符串。
(2)返回值
成功时,返回0表示请求成功。
失败时,返回负数错误代码,表示请求失败。
(3)函数功能
gpio_request()函数请求一个GPIO引脚并进行相关的配置,以便将其用于后续的操作。
它会检查GPIO引脚是否已经被其他驱动程序或内核使用,如果被占用则请求失败。
请求成功后,该GPIO引脚将被锁定,使其他驱动程序无法再次请求该引脚。
(五)mydevice_exit(void)函数的实现
static void __exit mydevice_exit(void)
{
// 在这里执行驱动程序的清理操作
//释放申请的GPIO资源
gpio_free(GPIO_LED_PIN_NUM);
// 销毁设备节点
device_destroy(my_led, MKDEV(major, minor));
// 销毁设备类
class_destroy(my_led);
// 删除字符设备
cdev_del(&my_cdev);
// 注销字符设备驱动程序
unregister_chrdev(0, DEVICE_NAME);
printk(KERN_INFO "Device unregistered.\n");
}
(六)定义fops结构体
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = device_open,
.release = device_release,
.unlocked_ioctl = myled_ioctl,
};
此处用到了unlocked_ioctl函数,unlocked_ioctl函数是Linux内核中用于设备控制和通信的重要函数之一。它允许用户空间程序与设备驱动程序进行交互,发送特定的命令和参数来执行设备相关的操作。 unlocked_ioctl()函数原型如下:
long unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
(1)参数说明
filp:指向struct file结构的指针,表示要执行ioctl操作的文件。
cmd:请求代码,用于指定要执行的具体操作。请求代码通常是一个整数,可以是预定义的常量或自定义的命令。
arg:参数,根据具体的请求代码而定。
(2)返回值
unlocked_ioctl函数返回一个long类型的值,通常用于表示操作的成功与否,返回值为负数表示出现错误。
(七)device_open()函数的实现:
static int device_open(struct inode *inode, struct file *file)
{
// 在这里处理设备打开的操作
//设置GPIO引脚为输出模式,并将其初始化为高电平
gpio_direction_output(GPIO_LED_PIN_NUM, 1);
printk(KERN_INFO "This is device_open.\n");
return 0;
}
gpio_direction_output()函数原型如下:
int gpio_direction_output(unsigned int gpio, int value);
(1)参数说明
gpio:要配置的GPIO引脚的编号。
value:设置GPIO引脚的初始输出值,0表示低电平,非零表示高电平。
(2)返回值
成功时,返回0表示设置成功。
失败时,返回负数错误代码,表示设置失败。
(3)函数功能
gpio_direction_output()函数将指定的GPIO引脚配置为输出模式,并设置其初始输出值。
在配置为输出模式后,可以使用其他函数(如gpio_set_value())来更改GPIO引脚的输出值。
(八)myled_ioctl()函数的实现
static long myled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case SET_LED_ON:
// 设置GPIO引脚为低电平
gpio_set_value(GPIO_LED_PIN_NUM, 0);
break;
case SET_LED_OFF:
//设置GPIO引脚为高电平
gpio_set_value(GPIO_LED_PIN_NUM, 1);
break;
default:
return -ENOTTY;
}
return 0;
}
gpio_set_value()函数原型如下:
void gpio_set_value(unsigned int gpio, int value);
(1)参数说明
gpio:要设置的GPIO引脚的编号。
value:要设置的输出值,0表示低电平,非零表示高电平。
(2)返回值
无返回值。
(3)函数功能
gpio_set_value()函数用于设置指定GPIO引脚的输出值。该函数会将GPIO引脚配置为输出模式(如果尚未配置),并设置其输出值为指定的值。
(九)device_release()函数的实现:
static int device_release(struct inode *inode, struct file *file)
{
// 在这里处理设备关闭的操作
//设置GPIO引脚为输出模式,并将置为高电平
gpio_direction_output(GPIO_LED_PIN_NUM, 1);
printk(KERN_INFO "This is device_release.\n");
return 0;
}
完整的驱动myled.c示例源码
#include // 包含模块相关函数的头文件
#include // 包含文件系统相关函数的头文件
#include // 包含用户空间数据访问函数的头文件
#include //包含字符设备头文件
#include
#include
#define DEVICE_NAME "mydevice" // 设备名称
#define LED_IOC_MAGIC 'k'
#define SET_LED_ON _IO(LED_IOC_MAGIC, 0)
#define SET_LED_OFF _IO(LED_IOC_MAGIC, 1)
#define GPIO_LED_PIN_NUM 18
static dev_t dev_num; //分配的设备号
struct cdev my_cdev; //字符设备指针
int major; //主设备号
int minor; //次设备号
static struct class *my_led;
static struct device *my_device;
static int device_open(struct inode *inode, struct file *file)
{
// 在这里处理设备打开的操作
//设置GPIO引脚为输出模式,并将其初始化为高电平
gpio_direction_output(GPIO_LED_PIN_NUM, 1);
printk(KERN_INFO "This is device_open.\n");
return 0;
}
static int device_release(struct inode *inode, struct file *file)
{
// 在这里处理设备关闭的操作
gpio_direction_output(GPIO_LED_PIN_NUM, 1);
printk(KERN_INFO "This is device_release.\n");
return 0;
}
static long myled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case SET_LED_ON:
// 设置GPIO引脚为低电平
gpio_set_value(GPIO_LED_PIN_NUM, 0);
break;
case SET_LED_OFF:
//设置GPIO引脚为高电平
gpio_set_value(GPIO_LED_PIN_NUM, 1);
break;
default:
return -ENOTTY;
}
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = device_open,
.release = device_release,
.unlocked_ioctl = myled_ioctl,
};
static int __init mydevice_init(void)
{
int ret;
// 在这里执行驱动程序的初始化操作
//释放之前申请的GPIO,避免申请失败
gpio_free(GPIO_LED_PIN_NUM);
if (gpio_request(GPIO_LED_PIN_NUM, "led_run")) {
printk("request %s gpio faile \n", "led_run");
return -1;
}
// 注册字符设备驱动程序
ret = alloc_chrdev_region(&dev_num,0,1,DEVICE_NAME);
if (ret < 0) {
printk(KERN_ALERT "Failed to allocate device number: %d\n", ret);
return ret;
}
major=MAJOR(dev_num);
minor=MINOR(dev_num);
printk(KERN_INFO "major number: %d\n",major);
printk(KERN_INFO "minor number: %d\n",minor);
my_cdev.owner = THIS_MODULE;
cdev_init(&my_cdev,&fops);
cdev_add(&my_cdev,dev_num,1);
// 创建设备类
my_led = class_create(THIS_MODULE, "my_led");
if (IS_ERR(my_led)) {
pr_err("Failed to create class\n");
return PTR_ERR(my_led);
}
// 创建设备节点并关联到设备类
my_device = device_create(my_led, NULL, MKDEV(major, minor), NULL, "my_device");
if (IS_ERR(my_device)) {
pr_err("Failed to create device\n");
class_destroy(my_led);
return PTR_ERR(my_device);
}
printk(KERN_INFO "Device registered successfully.\n");
return 0;
}
static void __exit mydevice_exit(void)
{
// 在这里执行驱动程序的清理操作
//释放申请的GPIO资源
gpio_free(GPIO_LED_PIN_NUM);
// 销毁设备节点
device_destroy(my_led, MKDEV(major, minor));
// 销毁设备类
class_destroy(my_led);
// 删除字符设备
cdev_del(&my_cdev);
// 注销字符设备驱动程序
unregister_chrdev(0, DEVICE_NAME);
printk(KERN_INFO "Device unregistered.\n");
}
module_init(mydevice_init);
module_exit(mydevice_exit);
MODULE_LICENSE("GPL"); // 指定模块的许可证信息
MODULE_AUTHOR("Your Name"); // 指定模块的作者信息
MODULE_DESCRIPTION("A simple character device driver"); // 指定模块的描述信息
编译
复制7.4.3驱动中的Makefile文件,将其中的copy_form_user.o修改为myled.o,效果如下:
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/test/04_Pinctrl和GPIO子系统/myled$ make
编译成ko模块并拷贝到开发板中。
编写测试应用源码led_app.c
#include
#include
#include
#include
#include
#include
#define DEV_NAME "/dev/my_device"
#define LED_IOC_MAGIC 'k'
#define SET_LED_ON _IO(LED_IOC_MAGIC, 0)
#define SET_LED_OFF _IO(LED_IOC_MAGIC, 1)
int main(int argc, char *argv[])
{
int i;
int fd = 0;
fd = open (DEV_NAME, O_RDONLY);
if (fd < 0) {
perror("Open "DEV_NAME" Failed!\n");
exit(1);
}
for (i=0; i<5; i++)
{
ioctl(fd, SET_LED_ON);
sleep(1);
ioctl(fd, SET_LED_OFF);
sleep(1);
}
close(fd);
return 0;
}
编译应用
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/test/04_Pinctrl和GPIO子系统/led_app$ $CC led_app.c -o led_app
将编译生成的应用拷贝到开发板中。
测试
root@ELF1:~# insmod myled.ko
major number: 245
minor number: 0
Device registered successfully.
root@ELF1:~# ./led_app
This is device_open.
This is device_release.
root@ELF1:~# rmmod myled.ko
执行led_app后,黄色LED灯循环闪烁5次。