linux驱动编写--1--点亮led

目标: 编写一个最基本的驱动,加载时点亮led,卸载时熄灭led。

硬件:microchip推出的SAMA5D3 X PLAINED开发板

知识点:

        1. 驱动的框架

        2. 使用内存映射

        3. 编译驱动

        4. 加载驱动

驱动框架

        这里就不赘述什么是驱动,驱动和应用的关系了。

        驱动需要在一台安装了linux的电脑内编写(你用来编译linux镜像的那台电脑),必备arm-gcc交叉编译器,以及linux源码项目(就是那个make之后会编译出一个linux镜像的项目)。

        首先,我们先创建一个.c文件,这里就起名为led_drv.c。这里我们先瞄一眼最后的完整代码

#include<linux/module.h>     //驱动模块初始化相关
#include<asm/io.h>                 //进行底层io操作相关

#define PE_OER  ((uint32_t *)0xFFFFFA10)
#define PE_ODR  ((uint32_t *)0xFFFFFA14)

static void __iomem *ADDR_ONECE; //给函数临时存储虚拟地址使用
static void write_addr( uint32_t *p , uint32_t  value )
{
         ADDR_ONECE = ioremap( (resource_size_t) p, 4);
         writel(value,ADDR_ONECE);
         iounmap( ADDR_ONECE );
}

static int __init led_init(void)
{
         write_addr(PE_ODR,1<<24);
         printk("加载驱动\r\n");
         return 0;
}
static void __exit led_exit(void)
{
         write_addr(PE_OER,1<<24);
         printk("驱动卸载\r\n");
}

module_init(led_init); //登记模块加载时要执行的函数
module_exit(led_exit); //登记模块卸载时要执行的函数
MODULE_LICENSE("GPL");

1.头文件

        驱动的默认头文件路径,是linux源码项目内的include文件夹下。后面你会经常打开这个路径,查看头文件具体内容的。

        

        本篇教程实现的功能较为简单,只需要使用两个头文件,一个是驱动必备的基础头文件,一个是操作寄存器所需要使用到的。

#include<linux/module.h>     //驱动模块初始化相关
#include<asm/io.h>                 //进行底层io操作相关

2. init及exit函数

         驱动里没有main函数,取而代之的是一条会在驱动被加载时被调出来运行的入口函数,一条在卸载时运行的出口函数

          这两条函数不限定函数名,只要自己定义后使用module_init和module_exit函数登记,就会在加载和卸载驱动时调出他俩来执行,如下:

        接下来我们会把点亮和熄灭led的程序写进这俩函数内

static int __init led_init(void)
{  
}
static void __exit led_exit(void)
{  
}
module_init(led_init); //登记模块加载时要执行的函数
module_exit(led_exit); //登记模块卸载时要执行的函数

        用__init修饰函数,则本函数所占用的内存会在初始化结束后被释放掉。

 3. 添加开源协议声明

         在文件最后添加以下函数,声明使用GPL开源许可。

MODULE_LICENSE("GPL");

内存映射

       控制led,只需要通过给寄存器赋值,控制引脚输出高电平即可。

        由于mmu的存在(Memory Manage Unit 内存管理单元,可以实现虚拟内存,内存访问权限管理等功能,必须带有mmu才能装linux系统),如果像写单片机时那样直接对寄存器赋值,会失败,需要让mmu做内存映射。

1. 寻找控制io电平的寄存器

        阅读芯片手册,学习gpio的控制流程及相关寄存器功能。这里的目的是为了展示怎么使用内存映射,实际应用中linux为很多常见操作都提供了一套标准操作流程,比如配置引脚功能,控制led,管理输入等,后续会再学习。

        最终查询到我所使用的ATSAMA5D36芯片有如下两个寄存器,给OER寄存器赋1,则会让对应引脚输出高电平,给ODR寄存器赋1,则会让引脚输出低电平。

#define PE_OER  ((uint32_t *)0xFFFFFA10)
#define PE_ODR  ((uint32_t *)0xFFFFFA14)

 2. 内存映射的API

定义于arch\arm\include\asm\io.h

        地址映射使用ioremap函数,需要传入需要传入两个参数,1是要转换的物理地址起始,2要转换从该地址起始的多少字节。会返回转换后的虚拟地址起始位置

void __iomem *ioremap(resource_size_t res_cookie, size_t size);

         释放虚拟地址使用iounmap函数,只要传入要释放的虚拟地址即可。

void iounmap(volatile void __iomem *iomem_cookie);

         读取内存总共有3条api,只要传入虚拟地址起始,就会返回其存储的值。bwl分别对应将该地址视为8 16 32位变量进行读取。

#define readb(c)              ({ u8  __v = readb_relaxed(c); __iormb(); __v; })
#define readw(c)             ({ u16 __v = readw_relaxed(c); __iormb(); __v; })
#define readl(c)               ({ u32 __v = readl_relaxed(c); __iormb(); __v; })

        写内存也有3条api,传入两个参数,v是要修改的值,c是虚拟地址。bwl分别对应将该地址视为8 16 32位变量进行修改

#define writeb(v,c)          ({ __iowmb(); writeb_relaxed(v,c); })
#define writew(v,c)          ({ __iowmb(); writew_relaxed(v,c); })
#define writel(v,c)           ({ __iowmb(); writel_relaxed(v,c); })

3. 添加led控制代码

        先定义一个用于修改内存的函数,传入参数是地址和要赋的值。第一行是获取映射后的虚拟地址,第二行进行赋值,第三行释放该虚拟地址。

static void __iomem *ADDR_ONECE; //给函数临时存储虚拟地址使用
static void write_addr( uint32_t *p , uint32_t  value )
{
    ADDR_ONECE = ioremap( (resource_size_t) p, 4);
    writel(value,ADDR_ONECE);
    iounmap( ADDR_ONECE );
}

         然后往led_init内添加以下代码,调用上面的函数,将odr寄存器赋值为1<<24,即可点亮led

 write_addr(PE_ODR,1<<24);

         往led_exit内添加以下代码,将oer寄存器赋值为1<<24,就会熄灭led

write_addr(PE_OER,1<<24);

完整驱动代码

         完整代码如下,额外加了两句printk用于输出提示信息。运行于内核中的驱动不能使用printf函数,需要使用printk。

#include<linux/module.h>     //驱动模块初始化相关
#include<asm/io.h>                 //进行底层io操作相关

#define PE_OER  ((uint32_t *)0xFFFFFA10)
#define PE_ODR  ((uint32_t *)0xFFFFFA14)

static void __iomem *ADDR_ONECE; //给函数临时存储虚拟地址使用
static void write_addr( uint32_t *p , uint32_t  value )
{
         ADDR_ONECE = ioremap( (resource_size_t) p, 4);
         writel(value,ADDR_ONECE);
         iounmap( ADDR_ONECE );
}

static int __init led_init(void)
{
         write_addr(PE_ODR,1<<24);
         printk("加载驱动\r\n");
         return 0;
}

static void __exit led_exit(void)
{
         write_addr(PE_OER,1<<24);
         printk("驱动卸载\r\n");
}


module_init(led_init); //登记模块加载时要执行的函数
module_exit(led_exit); //登记模块卸载时要执行的函数

MODULE_LICENSE("GPL");

编译

1. 编写makefile

        首先创建一个文件,文件命名为Makefile,在里面添加修改以下内容:

        第一行的含义是将led_drv.c编译后的.o文件添加进行生成,需要修改为你的驱动程序的名称

        第二行是定义了linux源码项目在电脑中的路径,需要按实际情况修改          

obj-m := led_drv.o
KERNEL_PATH := /home/cb/work/linux-at91/
PWD := $(shell pwd)

all: 
    make -C $(KERNEL_PATH) M=$(PWD) modules 

clean:
    make -C $(KERNEL_PATH) M=$(PWD) clean     

2. 编译

        首先命令行切换到我们刚刚编写的drv.c所在的目录,里面就如下两个文件

        

我们是在电脑上编译的,需要运行以下命令,声明使用的交叉编译器

export CROSS_COMPILE=arm-linux-gnueabi-

然后运行以下命令,开始编译

make ARCH=arm

        

编译结束,会得到一个和驱动程序同名,但后缀为.ko的文件,这就是我们需要的目标文件。如这里是led_drv.ko

测试

0. 将驱动存进linux开发板

        我采用的是在电脑端搭建一个nfs服务器,然后在linux开发板上使用命令,将nfs服务器挂载到开发板的指定文件夹内,实现共享文件夹。

1. 加载驱动

        在板子上使用insmod 命令来加载驱动

insmod led_drv.ko

        板子上的红灯即是我们点亮的,屏幕上也出现了我们用printk输出的语句

        

2.  查看已加载的驱动

         使用以下命令

lsmod

        

3. 卸载驱动

        使用rmmod 命令来加载驱动

rmmod led_drv

        板子上的红灯d3熄灭了,屏幕上也出现了我们用printk输出的语句

        

本篇教程到此结束,下一篇会介绍驱动接口函数的编写方法,并编写一个运行于linux下的程序,透过驱动来控制led闪烁

请点击下一篇教程继续学习:    linux驱动编写--2--应用程序控制led闪烁

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值