嵌入式驱动开发案例实例过程

嵌入式驱动开发案例实例过程

编写LED字符设备驱动实现Linux下控制LED灯的亮灭。

总结编写字符设备驱动的详细步骤

  • 先搭建驱动框架:

    • 头文件
    • 入口函数
    • 出口函数
    • 此时先不要写入口和出口
  • 各种该:

    • 该声明的声明
    • 该定义的定义
    • 该初始化的初始化
    • 先搞硬件后搞软件【变量】
  • 填充入口和出口

    • 先写注释
    • 后塞代码【体力活】
  • 最后编写各个接口函数

编写驱动程序 led_drv.c‘’

/*************************************************************************
	> File Name: led_drv.c
	> Author: 
	> Mail: 
	> Created Time: 2019年10月09日 星期三 20时03分33秒
 ************************************************************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/cdev.h> //struct cdev
#include <linux/fs.h> //struct file_operations
#include <mach/platform.h>

//声明描述LED硬件信息的数据结构
struct led_resource{
    char *name;//名称
    int gpio;//gpio 编号
};

//定义初始化硬件信息对象
static struct led_resource led_info[] = {
    {
        .name = "LED1",
        .gpio = PAD_GPIO_C + 12
    },
    {
        .name = "LED2",
        .gpio = PAD_GPIO_C + 17
    },
    {
        .name = "LED3",
        .gpio = PAD_GPIO_C + 11
    },
    {
        .name = "LED4",
        .gpio = PAD_GPIO_B + 26
    }
};

//open device func
//return -0 success -1-fail
static int led_open(struct inode *inode, struct file *file)
{
    int i = 0;
    for(i = 0; i < ARRAY_SIZE(led_info); i++)
    {
        gpio_set_value(led_info[i].gpio, 0);
        printk("%s:打开第%d个灯。\n", __func__, i+1);
    }
    return 0;
}

//close device func
static int led_close(struct inode *inode, struct file *file)
{
    int i = 0;
    for(i = 0; i < ARRAY_SIZE(led_info); i++)
    {
        gpio_set_value(led_info[i].gpio, 1);
        printk("%s:关闭第%d个灯。\n", __func__, i+1);
    }
    return 0;
}

//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
    .open = led_open,//open device
    .release = led_close //close device 
};

//定义字符设备对象
static struct cdev led_cdev;
//定义设备号对象
static dev_t dev;

//init device func
static int led_init(void)
{
    //申请GPIO资源,配置为输出:1
    int i;
    for(i = 0; i < ARRAY_SIZE(led_info); i++)
    {
        gpio_request(led_info[i].gpio, led_info[i].name);
        gpio_direction_output(led_info[i].gpio, 1);
    }
    //申请设备号,
    alloc_chrdev_region(&dev, 0, 1, "myled");
    printk("major:%d, minor:%d\n", MAJOR(dev), MINOR(dev));
    //初始化字符设备对象,添加硬件操作接口
    cdev_init(&led_cdev, &led_fops);
    //想内核中注册字符设备对象
    cdev_add(&led_cdev, dev, 1);
    return 0;
}

//exit device func
static void led_exit(void)
{
    int i = 0;
    //从内核中卸载字符设备对象
    cdev_del(&led_cdev);
    //释放设备号
    unregister_chrdev_region(dev, 1);
    //输出1,释放GPIO资源,GPIO输出1为置空
    for(i = 0; i < ARRAY_SIZE(led_info); i++)
    {
        gpio_set_value(led_info[i].gpio, 1);
        gpio_free(led_info[i].gpio);
    }
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");


led_test.c

/*************************************************************************
	> File Name: led_test.c
	> Author: 
	> Mail: 
	> Created Time: 2019年10月09日 星期三 21时15分21秒
 ************************************************************************/

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    int fd;
    while(1){
        fd = open("/dev/myled", O_RDWR);
        if(fd < 0)
        {
            printf("open led device failed.\n");
            return -1;
        }
        sleep(3);
        close(fd);
    }
    return 0;

}


Makefile

obj-m += led_drv.o

all:
	make -C /home/ww/ww/ARM/kernel SUBDIRS=$(PWD) modules

clean:
	make -C /home/ww/ww/ARM/kernel SUBDIRS=$(PWD) clean

遇到的问题

共享库libgcc_s.so.1找不到的问题

  • 解决方法:添加相关共享库
    • 在交叉编译工具下找到相应的共享库添加到文件系统下的/lib下。
//查看执行文件需要的依赖共享库
arm-cortex_a9-linux-gnueabi-readelf -d led_test

Dynamic section at offset 0x754 contains 25 entries:
  标记        类型                         名称/值
 0x00000001 (NEEDED)                     共享库:[libgcc_s.so.1]
 0x00000001 (NEEDED)                     共享库:[libc.so.6]
 0x0000000c (INIT)                       0x8420
 0x0000000d (FINI)                       0x8690
 0x00000019 (INIT_ARRAY)                 0x10748
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x0000001a (FINI_ARRAY)                 0x1074c
 0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 0x00000004 (HASH)                       0x818c
 0x00000005 (STRTAB)                     0x82a4
 0x00000006 (SYMTAB)                     0x81d4
 0x0000000a (STRSZ)                      224 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000015 (DEBUG)                      0x0
 0x00000003 (PLTGOT)                     0x10844
 0x00000002 (PLTRELSZ)                   56 (bytes)
 0x00000014 (PLTREL)                     REL
 0x00000017 (JMPREL)                     0x83e8
 0x00000011 (REL)                        0x83e0
 0x00000012 (RELSZ)                      8 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffe (VERNEED)                    0x83a0
 0x6fffffff (VERNEEDNUM)                 2
 0x6ffffff0 (VERSYM)                     0x8384
 0x00000000 (NULL)                       0x0

lsmod: /proc/modules: No such file or directory

  • 解决方法:修改启动脚本
    /etc/init.d/rcS内容,解决/proc下的设备挂载问题。
/bin/mount -n -t ramfs /var
/bin/mount -n -t ramfs ramfs /tmp
/bin/mount -n -t sysfs none /sys
/bin/mount -n -t ramfs none /dev

/bin/mkdir /var/tmp
/bin/mkdir /var/modules
/bin/mkdir /var/run
/bin/mkdir /var/log

/bin/mkdir -p /dev/pts
/bin/mkdir -p /dev/shm

/sbin/mdev -s
/bin/mount -a
echo /sbin/mdev > /proc/sys/kernel/hotplug

echo "" > /proc/sys/kernel/hotplug
mount -n -o mode=0755 -t tmpfs tmpfs /dev
mknod /dev/console c 5 1
mknod /dev/null c 1 3
echo "starting the hotplug events dispatcher udevd"
udevd --daemon
echo "synthesizing initial hotplug events"
udevtrigger
udevsettle --timeout=300

mkdir /dev/pts
mount -t devpts devpts /dev/pts
mkdir /dev/shm

echo "hello world"

printk打印不显示问题

  • 内核显示级别设置问题,修改启动参数部分的内核显示等级为console = … debug即可。
    有时调试内核模块,打印信息太多了,可以通过修改/proc/sys/kernel/printk文件内容来控制。
    默认设置是7   4   1   7
    cat /proc/sys/kernel/printk
    7       4       1      7
    该文件有四个数字值,它们根据日志记录消息的重要性,定义将其发送到何处。关于不同日志级别的更多信息,请查阅syslog(2)联机帮助。上面显示的4个数据分别对应:控制台日志级别:优先级高于该值的消息将被打印至控制台默认的消息日志级别:将用该优先级来打印没有优先级的消息最低的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级)默认的控制台日志级别:控制台日志级别的缺省值 数值越小,优先级越高

查看申请到的设备号及创建相关设备文件

cd /home/drivers
insmod led_drv.ko //调用入口函数
cat /proc/devices //查看申请到的主设备号
mknod /dev/myled c 主设备号 0 //创建设备文件,代表LED0
./led_test
//open device fail 测试失败
cd /home/drivers
insmod led_drv.ko //调用入口函数
cat /proc/devices //查看申请到的主设备号
    character devices://当前系统支持的字符设备,主设备号和设备名称
    1   mem
    5   /dev/tty
    5   /dev/console
    5   /dev/ptmx
    ...
    244 myled  //LED驱动申请到的主设备号就是244,设备名称为myled
    ...
mknod /dev/myled c 主设备号 0 //创建设备文件,它代表LED设备。
./led_test //再次执行
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《ARM嵌入系统软件开发实例(一) 周立功 北京航天航空大学出版社》(PDF) 目录第1章 ZLG/FS原理与应用 1.1 概述 1.1.1 ZLG/FS简介 1.1.2 ZLG/FS的特点 1.1.3 已实现的特性 1.1.4 暂时未实现的特性 1.2 使用 1.2.1 使用示例 1.2.2 Config.h和fat.h 1.2.3 与编译器无关的数据类型 1.2.4 初始化ZLG/FS 1.2.5 目录相关操作 1.2.6 文件相关操作 1.2.7 关闭ZLG/FS 1.2.8 在多任务环境下使用ZLG/FS 1.3 ZLG/FS的结构视图 1.3.1 概述 1.3.2 应用程序 1.3.3 文件管理与目录管理 1.3.4 文件分配表管理与文件目录表管理 1.3.5 逻辑盘管理模块 1.3.6 高速缓存管理模块 1.3.7 底层驱动程序 1.3.8 实用程序 1.3.9 源代码文件说明 1.4 驱动程序设计指南 1.4.1 一个驱动程序的例子 1.4.2 参数 1.4.3 逻辑盘初始化 1.4.4 卸载逻辑盘 1.4.5 读/写扇区 1.5 FAT文件系统基础知识 1.5.1 简介 1.5.2 本节的约定 1.5.3 概述(适用于各类型的FAT文件系统) 1.5.4 引导扇区和BPB 1.5.5 FAT数据结构 1.5.6 FAT类型的确定 1.5.7 FAT卷的初始化 1.5.8 FAT32 FSInfo扇区结构和备份引导扇区 1.5.9 FAT的目录结构(FDT表) 1.5.10 FAT的长目录项 1.5.11 命名限制和字符集 1.5.12 短文件名和长文件名的名字映射 1.5.13 命名惯例和长文件名 1.5.14 长目录项对旧版FAT的影响 1.5.15 验证目录的内容 1.5.16 与FAT目录项相关的其他注意事项 1.6 逻辑盘信息管理 1.6.1 用户接口函数 1.6.2 内部使用函数 1.6.3 逻辑盘和卷的区别 1.6.4 逻辑盘信息登录项 1.6.5 初始化 1.6.6 加载底层驱动程序 1.6.7 卸载底层驱动程序 1.6.8 获取逻辑盘信息 1.6.9 获取空闲登录项 1.7 Cache管理 1.7.1 用户接口函数 1.7.2 内部使用函数 1.7.3 原理 1.7.4 初始化 1.7.5 通过Cache读/写逻辑扇区 1.7.6 把Cache数据写回逻辑盘 1.8 文件分配表管理 1.8.1 FAT简介 1.8.2 接口函数 1.8.3 获取簇的下一个簇号 1.8.4 设置下一个簇号 1.8.5 为簇链增加一个簇 1.8.6 删除一个簇链 1.9 文件目录表管理 1.9.1 FDT简介 1.9.2 用户接口函数 1.9.3 内部接口函数 1.9.4 数据结构 1.9.5 读取FDT信息 1.9.6 保存FDT信息 1.9.7 获取指定目录指定FDT信息 1.9.8 设置指定目录指定FDT信息 1.9.9 在指定目录查找指定FDT 1.9.10 指定目录查增加FDT 1.9.11 在指定目录删除指定FDT 1.9.12 改变指定目录指定FDT属性 1.9.13 察看指定目录是否为空 1.9.14 在指定目录查看指定FDT是否存在 1.10 目录操作 1.10.1 用户接口函数 1.10.2 内部接口函数 1.10.3 获取指定目录的逻辑盘号 1.10.4 改变当前逻辑盘 1.10.5 建立目录 1.10.6 删除目录 1.10.7 改变当前目录 1.10.8 用户文件/目录名转换为系统名 1.10.9 获取指定文件/目录所在的目录的开始簇号及系统内名称 1.10.10 获取指定目录开始簇号 1.11 文件操作 1.11.1 用户接口函数 1.11.2 数据结构 1.11.3 初始化 1.11.4 删除文件 1.11.5 打开文件 1.11.6 查看指定的文件是否处于打开状态 1.11.7 关闭文件 1.11.8 从文件中读数据 1.11.9 把数据写入文件 1.11.10 判断文件是否读/写到文件尾 1.11.11 移动文件读/写位置 1.12 实用程序 第2章 USB驱动程序开发 2.1 USB1.1协议简介 2.1.1 USB系统构成 2.1.2 USB设备的枚举过程 2.1.3 USB的分组标识 2.1.4 USB标准设备请求 2.1.5 USB设备描述符 2.2 PDIUSBD12器件简介 2.3 硬件电路设计 2.4 软件设计总体思想 2
嵌入Linux驱动程序实战开发是指通过实际开发嵌入Linux驱动程序来掌握嵌入Linux驱动程序开发的技能和实践经验。奚海蛟是一位资深的嵌入Linux驱动程序开发专家,他的著作以《嵌入Linux内核源代码情景分析》为代表,通过实际案例来讲解嵌入Linux驱动程序的开发方法和技巧。 在嵌入系统中,驱动程序起到了桥梁的作用,它连接硬件设备和操作系统。嵌入Linux驱动程序开发需要熟悉Linux内核、C语言以及硬件设备的相关知识。奚海蛟的著作通过详细的分析和实例演示,帮助读者理解嵌入Linux驱动程序开发的关键点和难点,让开发者能够深入了解Linux内核的原理,从而掌握驱动开发技巧。 通过实战开发,读者可以学到如何编写自己的驱动程序、通过配置文件进行硬件设备驱动的初始化、使用相应的接口和函数与硬件设备进行交互等技能。在这个过程中,读者将会面临各种挑战和问题,但通过不断的实践和学习,将能掌握解决问题的方法和技巧。 嵌入Linux驱动程序实战开发的过程中还要注意与硬件设备的兼容性问题,了解硬件设备的特点和规格,以确保驱动程序的正确性和稳定性。奚海蛟的著作中对这些问题也进行了详细的讲解。 总之,嵌入Linux驱动程序实战开发是一项具有挑战性和实用性的工作,需要开发者具备扎实的专业知识和实践经验。奚海蛟通过他的著作为读者提供了宝贵的经验和指导,使读者能够更加有效地掌握嵌入Linux驱动程序开发的技巧和方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值