嵌入式Linux应用开发-基础知识-第七章 具体单板的 LED驱动程序

嵌入式Linux应用开发-基础知识-第七章 具体单板的 LED驱动程序

  • 第七章 具体单板的 LED 驱动程序
    • 7.1 怎么写 LED 驱动程序?
    • 7.2 AM335X的 LED驱动程序
      • 7.2.1 原理图 XXXXXX_AM335X开发板结构为:
      • 7.2.2 所涉及的寄存器操作
      • 7.2.3 写程序
      • 7.2.4 配置内核去掉原有 LED驱动
      • 7.2.5 课后作业

第七章 具体单板的 LED 驱动程序

嵌入式Linux应用开发-基础知识-第七章 具体单板的 LED驱动程序_第1张图片

我们选用的内核都是 4.x 版本,操作都是类似的:

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

录制视频时,我的 source insight 里总是使用某个版本的内核。这没有关系,驱动程序中调用的内核
函数,在这些 4.x 版本的内核里都是一样的。

7.1 怎么写 LED 驱动程序?

详细步骤如下:
① 看原理图确定引脚,确定引脚输出什么电平才能点亮/熄灭 LED
② 看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是?
③ 编写驱动:先写框架,再写硬件操作的代码

注意:在芯片手册中确定的寄存器地址被称为物理地址,在 Linux 内核中无法直接使用。
需要使用内核提供的 ioremap 把物理地址映射为虚拟地址,使用虚拟地址。

ioremap 函数的使用

① 函数原型:
在这里插入图片描述
使用时,要包含头文件:

#include 

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

virt_addr  = ioremap(phys_addr, size); 

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

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

void iounmap(volatile void __iomem *cookie)

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;   // 灭灯 

7.2 AM335X的 LED驱动程序

7.2.1 原理图 XXXXXX_AM335X开发板结构为:

底板+核心板,其中一个 LED原理图如下:
嵌入式Linux应用开发-基础知识-第七章 具体单板的 LED驱动程序_第2张图片
它使用 GPIO1_16这个引脚,当它输出低电平时,LED被点亮;当它输出高电平时,LED被熄灭。

7.2.2 所涉及的寄存器操作

a. 使能 GPIO1
嵌入式Linux应用开发-基础知识-第七章 具体单板的 LED驱动程序_第3张图片

/* set PRCM to enalbe GPIO1   
* set CM_PER_GPIO1_CLKCTRL (0x44E00000 + 0xAC)  
* * val: (1<<18) | 0x2  
* */ 

b. 设置 GPIO1_16的功能
让它工作于 GPIO模式 根据原理图可以找到 GPIO1_16这个引脚接到 AM3358的 R13引脚,根据下图知道 pin name为 GPMC_A0,并且知道要设置这个引脚为 Mode 7。
嵌入式Linux应用开发-基础知识-第七章 具体单板的 LED驱动程序_第4张图片
在芯片手册中搜“conf_gpmc_a0”,可得:
嵌入式Linux应用开发-基础知识-第七章 具体单板的 LED驱动程序_第5张图片

/* set Control Module to set GPIO1_16 (R13) used as GPIO   
* conf_gpmc_a0 as mode 7  
* * addr : 0x44E10000 + 0x840  
* * val  : 7  
* */ 

c. 设置 GPIO1_16的方向,让它作为输出引脚
嵌入式Linux应用开发-基础知识-第七章 具体单板的 LED驱动程序_第6张图片

/* set GPIO1's registers , to set GPIO1_16'S dir (output)   
* GPIO_OE   
* * addr : 0x4804C000 + 0x134  
* * clear bit 16  
* */ 

d. 设置 GPIO1_16的数据,让它输出高电平 AM335X芯片支持 set-and-clear protocol,设置 GPIO_SETDATAOUT的 bit 16为 1即可让引脚输出 1:
在这里插入图片描述

/* set GPIO1_16's registers , to output 1   
* GPIO_SETDATAOUT  
* addr : 0x4804C000 + 0x194  
*/ 

e. 清除 GPIO1_16的数据,让它输出低电平 AM335X芯片支持 set-and-clear protocol,设置 GPIO_CLEARDATAOUT的 bit 16为 1即可让引脚输出0:
在这里插入图片描述

/* set GPIO1_16's registers , to output 0   
* GPIO_CLEARDATAOUT  
* addr : 0x4804C000 + 0x190  
*/
 

7.2.3 写程序

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

01_all_series_quickstart\ 
05_嵌入式 Linux驱动开发基础知识\source\02_led_drv\       
02_led_drv_for_boards\am335x_src_bin 

硬件相关的文件是 board_am335x.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 }; 

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

19 #include "led_opr.h" 
20 
21 static volatile unsigned int *CM_PER_GPIO1_CLKCTRL; 
22 static volatile unsigned int *conf_gpmc_a0; 
23 static volatile unsigned int *GPIO1_OE; 
24 static volatile unsigned int *GPIO1_CLEARDATAOUT; 
25 static volatile unsigned int *GPIO1_SETDATAOUT; 
26 
27 static int board_demo_led_init (int which) /* 初始化 LED, which-哪个 LED */ 
28 { 
29     if (which == 0) 
30     { 
31         if (!CM_PER_GPIO1_CLKCTRL) 
32         { 
33             CM_PER_GPIO1_CLKCTRL = ioremap(0x44E00000 + 0xAC, 4); 
34             conf_gpmc_a0 = ioremap(0x44E10000 + 0x840, 4); 
35             GPIO1_OE = ioremap(0x4804C000 + 0x134, 4); 
36             GPIO1_CLEARDATAOUT = ioremap(0x4804C000 + 0x190, 4); 
37             GPIO1_SETDATAOUT = ioremap(0x4804C000 + 0x194, 4); 
38         } 
39 
40         //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which); 
41         /* a. 使能 GPIO1 
42          * set PRCM to enalbe GPIO1 
43          * set CM_PER_GPIO1_CLKCTRL (0x44E00000 + 0xAC) 
44          * val: (1<<18) | 0x2 
45          */ 
46         *CM_PER_GPIO1_CLKCTRL = (1<<18) | 0x2; 
47 
48         /* b. 设置 GPIO1_16的功能,让它工作于 GPIO模式 
49          * set Control Module to set GPIO1_16 (R13) used as GPIO 
50          * conf_gpmc_ad0 as mode 7 
51          * addr : 0x44E10000 + 0x800 
52          * val  : 7 
53          */ 
54         *conf_gpmc_a0 = 7; 
55 
56         /* c. 设置 GPIO1_16的方向,让它作为输出引脚 
57          * set GPIO1's registers , to set GPIO1_16'S dir (output) 
58          * GPIO_OE 
59          * addr : 0x4804C000 + 0x134 
60          * clear bit 16 61          */ 
62 
63         *GPIO1_OE &= ~(1<<16); 
64     } 
65 
66     return 0; 
67 } 
68 

led_operations结构体中有 ctl函数指针,它指向 board_demo_led_ctl函数,在里面将会根据参数设置 LED引脚的输出电平:

69 static int board_demo_led_ctl (int which, char status) /* 控制 LED, which-哪个 LED, status:1-亮,0-灭 */ 
70 { 
71     //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off"); 
72 
73     if (which == 0) 
74     { 
75         if (status) /* on: output 0 */ 
76         { 
77             /* e. 清除 GPIO1_16的数据,让它输出低电平 
78              * AM335X芯片支持 set-and-clear protocol,设置 GPIO_CLEARDATAOUT的 bit 16为 1即可让引脚输出 0: 
79              * set GPIO1_16's registers , to output 0 
80              * GPIO_CLEARDATAOUT 
81              * addr : 0x4804C000 + 0x190 
82              */ 
83             *GPIO1_CLEARDATAOUT = (1<<16); 
84         } 
85         else 
86         { 
87             /* d. 设置 GPIO1_16的数据,让它输出高电平 
88              * AM335X芯片支持 set-and-clear protocol,设置 GPIO_SETDATAOUT的 bit 16为1即可让引脚输出 1: 
89              * set GPIO1_16's registers , to output 1 
90              * GPIO_SETDATAOUT 
91              * addr : 0x4804C000 + 0x194 
92              */ 
93             *GPIO1_SETDATAOUT = (1<<16); 
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 

7.2.4 配置内核去掉原有 LED驱动

不需要重新配置内核,只需要在开发板上执行以下 3条命令关闭内核对 LED的使用即可:

# echo none > /sys/class/leds/am335x:green:cpu0/trigger 
# echo none > /sys/class/leds/am335x:green:mmc0/trigger 
# echo none > /sys/class/leds/am335x:green:nand/trigger 

然后就可以去安装驱动程序,执行测试程序了,操作过程跟 LED框架驱动程序的测试是一样的。

7.2.5 课后作业

a. 在 board_am335x.c里有 ioremap,什么时候执行 iounmap?请完善程序
b. 视频里我们只实现了点一个 LED,请修改代码实现操作 4个 LED

你可能感兴趣的:(Linux,ARM,MCU,MCU,C51,linux,单片机,运维,c++,c语言)