嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十七)具体单板的LED驱动程序

以下板子选用的内核都是4.x版本,操作都是类似的:

rk3399   linux 4.4.154
rk3288   linux 4.4.154
imx6ul   linux 4.9.88
am3358  linux 4.9.168

1.1 怎么写LED驱动程序?

详细步骤如下:
① 看原理图确定引脚,确定引脚输出什么电平才能点亮/熄灭LED
②看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是?
③ 编写驱动:先写框架,再写硬件操作的代码(上下层)
注意:在芯片手册中确定的寄存器地址被称为物理地址,在Linux内核中无法直接使用。
需要使用内核提供的ioremap把物理地址映射为虚拟地址,使用虚拟地址。

ioremap函数的使用:
① 函数原型:res_cookie物理地址 ,size范围

在这里插入图片描述

使用时,要包含头文件:

在这里插入图片描述

② 它的作用:
把物理地址phys_addr开始的一段空间(大小为size),映射为虚拟地址;返回值是该段虚拟地址的首地址。

 virt_addr  = ioremap(phys_addr, size);

实际上,它是按页(4096字节)进行映射的,是整页整页地映射的。 假设phys_addr=0x10002size=4ioremap的内部实现是:
a. phys_addr按页取整,得到地址0x10000
b. size按页取整,得到4096
c. 把起始地址0x10000,大小为4096的这一块物理地址空间,映射到虚拟地址空间, 假设得到的虚拟空间起始地址为0xf0010000
d. 那么phys_addr = 0x10002对应的virt_addr =0xf0010002

③ 不再使用该段虚拟地址时,要iounmap(virt_addr)

在这里插入图片描述


假设如下程序访问内存地址,同时访问两次,会发生什么样的事情?

在这里插入图片描述
在这里插入图片描述

同一个程序,每次运行的结果应该一样。这个程序同时运行两次,每次的结果都是一样。同时运行两次,在内存中有两份代码,他们的地址不同,为何打印出的地址是相同的?
这是MMU在起到作用!MMU:Memory Manager Unit
写APP的人水平有高有低,总不能让完全没有硬件知识的人去直接访问硬件吧? 怎么禁止他们直接访问硬件? 要用MMU! MMU的两大作用:
1.地址映射:CPU发出同样的地址(虚拟地址),执行不同的APP时候,访问的是由MMU执行这个转换。
2.权限保护:CPU发出的地址,要经过MMU审核之后才可以访问具体的硬件操作。


volatile的使用:
① 编译器很聪明,会帮我们做些优化,比如:

int   a;
a = 0;   // 这句话可以优化掉,不影响a的结果
a = 1;

② 有时候编译器会自作聪明,比如:

int *p = ioremap(xxxx, 4);  // GPIO寄存器的地址
*p = 0;   // 点灯,但是这句话被优化掉了
*p = 1;   // 灭灯

③ 对于上面的情况,为了避免编译器自动优化,需要加上volatile,告诉它“这是容易出错的,别乱优化”:

volatile  int *p = ioremap(xxxx, 4);  // GPIO寄存器的地址
*p = 0;   // 点灯,这句话不会被优化掉
*p = 1;   // 灭灯

1.2 百问网IMX6ULL的LED驱动程序

1.2.1 led原理图

LED原理图如下,它使用GPIO5_IO03,引脚输出低电平时LED被点亮,输出高电平时LED被熄灭:

在这里插入图片描述

1.2.2 所涉及的寄存器操作

GPIO模块图如下:

在这里插入图片描述

代码中对硬件的操作截图如下,截图便于对比,后面有文字便于复制:

在这里插入图片描述

步骤1:使能GPIO5

在这里插入图片描述

设置b[31:30]就可以使能GPIO5,设置为什么值呢?
注意:在imx6ullrm.pdf中,CCM_CCGR1b[31:30]是保留位;我以前写程序时错用了imx6ul(不是imx6ull)的手册,导致程序中额外操作了这些保留位。不去设置b[31:30]GPIO5也是默认使能的。
看下图,设置为0b11

在这里插入图片描述

① 00:该GPIO模块全程被关闭
② 01:该GPIO模块在CPU run mode情况下是使能的;在WAIT或STOP模式下,关闭
③ 10:保留
④ 11:该GPIO模块全程使能

/* GPIO5_IO03 */
/* a. 使能GPIO5
 * set CCM to enable GPIO5
 * CCM_CCGR1[CG15] 0x20C406C
 * bit[31:30] = 0b11
 */

步骤2:设置GPIO5_IO03GPIO模式
设置如下寄存器:

在这里插入图片描述

/* b. 设置GPIO5_IO03用于GPIO
 * set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
 *		to configure GPIO5_IO03 as GPIO
 * IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3	0x2290014
 * bit[3:0] = 0b0101 alt5
 */

步骤3:设置GPIO5_IO03为输出引脚,设置其输出电平

寄存器地址为:

在这里插入图片描述

设置方向寄存器,把引脚设置为输出引脚:

在这里插入图片描述

设置数据寄存器,设置引脚的输出电平:

在这里插入图片描述

/* c. 设置GPIO5_IO03作为output引脚
 * set GPIO5_GDIR to configure GPIO5_IO03 as output
 * GPIO5_GDIR  0x020AC000 + 0x4
 * bit[3] = 0b1
 */

/* d. 设置GPIO5_DR输出低电平
 * set GPIO5_DR to configure GPIO5_IO03 output 0
 * GPIO5_DR 0x020AC000 + 0
 * bit[3] = 0b0
 */

/* e. 设置GPIO5_IO3输出高电平
 * set GPIO5_DR to configure GPIO5_IO03 output 1
 * GPIO5_DR 0x020AC000 + 0
 * bit[3] = 0b1
 */

1.2.3 写程序

使用GIT下载所有源码后,本节源码位于如下目录:

01_all_series_quickstart\
05_嵌入式Linux驱动开发基础知识\source\02_led_drv\
      02_led_drv_for_boards\
fire_imx6ull-pro_src_bin
100ask_imx6ull_src_bin

硬件相关的文件是board_fire_imx6ull-pro.c,其他文件跟LED框架驱动程序完全一样。
它首先构造了一个led_operations结构体,用来表示LED的硬件操作:

100 static struct led_operations board_demo_led_opr = {
101     .num  = 1,
102     .init = board_demo_led_init,
103     .ctl  = board_demo_led_ctl,
104 };
105

对应的头文件函数如下;

#ifndef _LED_OPR_H
#define _LED_OPR_H

struct led_operations {
	int num;
	int (*init) (int which); /* 初始化LED, which-哪个LED */       
	int (*ctl) (int which, char status); /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
};

struct led_operations *get_board_led_opr(void);


#endif


对应单板的程序:

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <asm/io.h>

#include "led_opr.h"

static volatile unsigned int *CCM_CCGR1                              ;
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
static volatile unsigned int *GPIO5_GDIR                             ;
static volatile unsigned int *GPIO5_DR                               ;

static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */       
{
    unsigned int val;

    //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
    if (which == 0)
    {
        if (!CCM_CCGR1)
        {
            CCM_CCGR1                               = ioremap(0x20C406C, 4);
            IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
            GPIO5_GDIR                              = ioremap(0x020AC000 + 0x4, 4);
            GPIO5_DR                                = ioremap(0x020AC000 + 0, 4);
        }
        
        /* GPIO5_IO03 */
        /* a. 使能GPIO5
         * set CCM to enable GPIO5
         * CCM_CCGR1[CG15] 0x20C406C
         * bit[31:30] = 0b11
         */
        *CCM_CCGR1 |= (3<<30);
        
        /* b. 设置GPIO5_IO03用于GPIO
         * set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
         *      to configure GPIO5_IO03 as GPIO
         * IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3  0x2290014
         * bit[3:0] = 0b0101 alt5
         */
        val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
        val &= ~(0xf);
        val |= (5);
        *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
        
        
        /* b. 设置GPIO5_IO03作为output引脚
         * set GPIO5_GDIR to configure GPIO5_IO03 as output
         * GPIO5_GDIR  0x020AC000 + 0x4
         * bit[3] = 0b1
         */
        *GPIO5_GDIR |= (1<<3);
    }
    
    return 0;
}

static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
{
    //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
    if (which == 0)
    {
        if (status) /* on: output 0*/
        {
            /* d. 设置GPIO5_DR输出低电平
             * set GPIO5_DR to configure GPIO5_IO03 output 0
             * GPIO5_DR 0x020AC000 + 0
             * bit[3] = 0b0
             */
            *GPIO5_DR &= ~(1<<3);
        }
        else  /* off: output 1*/
        {
            /* e. 设置GPIO5_IO3输出高电平
             * set GPIO5_DR to configure GPIO5_IO03 output 1
             * GPIO5_DR 0x020AC000 + 0
             * bit[3] = 0b1
             */ 
            *GPIO5_DR |= (1<<3);
        }
    
    }
    return 0;
}

static struct led_operations board_demo_led_opr = {
    .num  = 1,
    .init = board_demo_led_init,
    .ctl  = board_demo_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
    return &board_demo_led_opr;
}


led_operations结构体中有init函数指针,它指向board_demo_led_init函数,在里面将会初始化LED引脚:使能、设置为GPIO模式、设置为输出引脚。
值得关注的是第35~38行,对于寄存器要先使用ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器:

21 static volatile unsigned int *CCM_CCGR1                              ;
22 static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
23 static volatile unsigned int *GPIO5_GDIR                             ;
24 static volatile unsigned int *GPIO5_DR                               ;
25
26 static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */    
27 {
28     unsigned int val;
29
30     //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
31     if (which == 0)
32     {
33         if (!CCM_CCGR1)
34         {
35             CCM_CCGR1  = ioremap(0x20C406C, 4);
36         IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
37             GPIO5_GDIR  = ioremap(0x020AC000 + 0x4, 4);
38             GPIO5_DR    = ioremap(0x020AC000 + 0, 4);
39         }
40
41         /* GPIO5_IO03 */
42         /* a. 使能GPIO5
43          * set CCM to enable GPIO5
44          * CCM_CCGR1[CG15] 0x20C406C
45          * bit[31:30] = 0b11
46          */
47         *CCM_CCGR1 |= (3<<30);
48
49         /* b. 设置GPIO5_IO03用于GPIO
50          * set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
51          *      to configure GPIO5_IO03 as GPIO
52          * IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3  0x2290014
53          * bit[3:0] = 0b0101 alt5
54          */
55         val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
56         val &= ~(0xf);
57         val |= (5);
58         *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
59
60
61         /* b. 设置GPIO5_IO03作为output引脚
62          * set GPIO5_GDIR to configure GPIO5_IO03 as output
63          * GPIO5_GDIR  0x020AC000 + 0x4
64          * bit[3] = 0b1
65          */
66         *GPIO5_GDIR |= (1<<3);
67     }
68
69     return 0;
70 }
71
led_operations结构体中有ctl函数指针,它指向board_demo_led_ctl函数,在里面将会根据参数设置LED引脚的输出电平:
72 static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
73 {
74     //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
75     if (which == 0)
76     {
77         if (status) /* on: output 0*/
78         {
79             /* d. 设置GPIO5_DR输出低电平
80              * set GPIO5_DR to configure GPIO5_IO03 output 0
81              * GPIO5_DR 0x020AC000 + 0
82              * bit[3] = 0b0
83              */
84             *GPIO5_DR &= ~(1<<3);
85         }
86         else  /* off: output 1*/
87         {
88             /* e. 设置GPIO5_IO3输出高电平
89              * set GPIO5_DR to configure GPIO5_IO03 output 1
90              * GPIO5_DR 0x020AC000 + 0
91              * bit[3] = 0b1
92              */
93             *GPIO5_DR |= (1<<3);
94         }
95
96     }
97     return 0;
98 }
99

下面的get_board_led_opr函数供上层调用,给上层提供led_operations结构体:

106 struct led_operations *get_board_led_opr(void)
107 {
108     return &board_demo_led_opr;
109 }
110

1.2.4 上机实验

首先设置工具链,然后修改驱动程序Makefile指定内核源码路径,就可以编译驱动程序和测试程序了。
启动开发板,挂载NFS文件系统,这样就可以访问到Ubuntu中的文件。 最后,就可以在开发板上进行下列测试。

注意:如果要使用板子自带的系统,关闭原有LED驱动的方法是类似的,也是进入开发板/sys/class/leds/目录,对于每一个LED在该目录下都有一个子目录,假设某个子目录名为XXX,则执行如下命令:


# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_fire-imx6ull/linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o ledtest ledtest.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f ledtest

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o

# leddrv.c board_demo.c 编译成 100ask.ko
100ask_led-y := leddrv.o board_fire_imx6ull-pro.o
obj-m	+= 100ask_led.o

# echo none  >  /sys/class/leds/XXX/trigger

使用我们的系统时,按如下操作。 要先禁止内核中原来的LED驱动,把“heatbeat”功能关闭,执行以下命令即可:

# echo none > /sys/class/leds/cpu/trigger

这样就可以使用我们的驱动程序做实验了:

# insmod  100ask_led.ko
#./ledtest  /dev/100ask_led0  on
#./ledtest  /dev/100ask_led0  off

如果想恢复原来的心跳功能,可以执行:

# echo heartbeat > /sys/class/leds/cpu/trigger 

1.2.5 课后作业

a. 在驱动里有ioremap,什么时候执行iounmap?请完善程序
b. 视频里我们只实现了点一个LED,修改代码支持两个LED。

代码留置区
HD2010操作说明(HD2010 operating instructions) ⅰ.软件安装(Installed software) 双击光盘HD2010 V2.0目录下的安装文件图标 ,将控制系统安装到个人电脑.如下图2-1 (Open Profile HD2010 V2.0 in the compact disk,Dblclick Installed the HD2010 V2.0 in your personal computer.As shown in Figure 2-1) 图(Figure)2-1 点击确定后进入下一步如图2-2 (Click确定intro- next step.As shown in Figure 2-2 ) 图(Figure)2-2 点击下一步后进入下一步(英文环境)在中文环境直接点击下一步完成安装)如图2-3 2-4 2-5 2-6 2-7 (Click next intro- next step.As shown in Figure 2-3 2-4 2-5 2-6 2-7) 图(Figure)2-3 注意:发送的文件保存在安装目录下的ProjFile文件夹里,点击下一步完成安装 (Announcements:The file what you send saved in the ProjFile in path of Install.And click next to install and finish) 图(Figure)2-4 图(Figure)2-5 图(Figure)2-6 安装最后一步如图2-7 (The final step of install. As shown in Figure 2-7) 图(Figure)2-7 点击完成打开软件主界面如图2-8 (Click finish open main interface of the software. As shown in Figure 2-8) ⅱ.软件设置(Setting software) ⅱ.ⅰ主界面如图2-8(Main interface.As shown in Figure 2-8) 图(Figure)2-8 ⅱ.ⅱ软件属性(Software properties) 1.文件菜单(File menu) a.新建--新建一个新的显示屏(New—Create a new screen) b.打开--打开一个显示屏(Open—Open a exist screen) c.保存--保存建立的文件(Save—Save file) d.另存为--保存副本(Save as—save a copy) e.导出.hds--导出.hds文件,用于u盘读取文件 (Export.hds—Export.hds file,for usb reading) f.退出--退出软件(Exit—Exit the software) 2.设置菜单(Settings menu) a.屏参设置--设置显示屏属性 (Screen settings—Display Screen configuration attributes) b.通信设置--通信端口/方式设置 (Communication settings—Set Communication prot and fashion) c.系统设置--配置系统默认项 (System settings—Set the acquice properties of software) 3.操作菜单(Operate menu) a.发送项目--发送节目(Send project—send the programmes) b.导出到U盘--把节目导入到U盘 (Export to U disk—send the programme to your U disk) c.时间设置--时间校对 (Time setting—get the right time im your computer) d.亮度设置--选择亮度模式 (Luminance setting—Choice the module of Luminance) e.固件更新--固件升级(Update Firmware—Update your controller Firmware) 4.语言菜单(Language menu) a.简体中文(Simplified Chinese) b.繁体中文(Chinese Traditional) c.英语(English) ⅱ.ⅲ节目编辑(Edit
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值