700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十七)具体单板的LED驱动程序

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

时间:2023-08-13 14:13:56

相关推荐

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

文章目录

1.1 怎么写LED驱动程序?1.2 百问网IMX6ULL的LED驱动程序1.2.1 led原理图1.2.2 所涉及的寄存器操作1.2.3 写程序1.2.4 上机实验1.2.5 课后作业

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

rk3399 linux 4.4.154rk3288 linux 4.4.154imx6ul linux 4.9.88am3358 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_TAMPER30x2290014* 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_bin100ask_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_Hstruct 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 ;2526 static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */ 27 {28unsigned int val;2930//printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);31if (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 }4041 /* GPIO5_IO03 */42 /* a. 使能GPIO543* set CCM to enable GPIO544* CCM_CCGR1[CG15] 0x20C406C45* bit[31:30] = 0b1146*/47 *CCM_CCGR1 |= (3<<30);4849 /* b. 设置GPIO5_IO03用于GPIO50* set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER351*to configure GPIO5_IO03 as GPIO52* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 0x229001453* bit[3:0] = 0b0101 alt554*/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;596061 /* b. 设置GPIO5_IO03作为output引脚62* set GPIO5_GDIR to configure GPIO5_IO03 as output63* GPIO5_GDIR 0x020AC000 + 0x464* bit[3] = 0b165*/66 *GPIO5_GDIR |= (1<<3);67}6869return 0;70 }71led_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");75if (which == 0)76{77 if (status) /* on: output 0*/78 {79 /* d. 设置GPIO5_DR输出低电平80 * set GPIO5_DR to configure GPIO5_IO03 output 081 * GPIO5_DR 0x020AC000 + 082 * bit[3] = 0b083 */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 190 * GPIO5_DR 0x020AC000 + 091 * bit[3] = 0b192 */93 *GPIO5_DR |= (1<<3);94 }9596}97return 0;98 }99

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

106 struct led_operations *get_board_led_opr(void)107 {108return &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-.05-x86_64_aarch64-linux-gnu/bin # 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,# 请参考各开发板的高级用户使用手册KERN_DIR = /home/book/100ask_fire-imx6ull/linux-4.9.88all:make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o ledtest ledtest.c clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -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.ko100ask_led-y := leddrv.o board_fire_imx6ull-pro.oobj-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。

代码留置区

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。