14_通过寄存器控制led灯

介绍不使用库函数而使用寄存器的方式控制led灯

Posted by Jerry Chen on August 6, 2019

了解内容

linux系统中不能直接操作物理地址,但是可以操作虚拟地址从而对寄存器进行操作。

使用void *ioremap(unsigned long start, unsigned long len)输入物理地址和字节长度,可返回虚拟地址指针。

准备内容

以LED2为例,在4412芯片上引脚为GPL2_0。

在三星用户手册中找到GPL2_0有3个主要寄存器:

  • GPL2CON :配置寄存器,配置8个GPIO输入/输出模式,每一个占4bit,32位有效;

    Address = Base Address + 0x0100(Base Address: 0x1100_0000)

    其中GPL2_0由GPL2CON的低4位配置,写0输入,写1输出。

  • GPL2DAT:数据寄存器,关于8个GPIO的高低电平状态,每一个占1bit,低8位有效;

    Address = Base Address + 0x0104(Base Address: 0x1100_0000)

    其中GPL2_0状态在GPL2DAT的低1位读取或者写入,写1高电平,写0低电平。

  • GPL2PUD:上下拉控制寄存器,关于8个GPIO的高低电平状态,每一个占2bit,低16位有效;

    Address = Base Address + 0x0108(Base Address: 0x1100_0000)

    其中GPL2_0上下拉状态由GPL2PUD的低2位控制,写3为上拉。

开始

建立框架:

1
2
3
4
5
6
7
//当模块加载
gpl2_remap();	//实现IO内存的映射,得到3个可控制的寄存器
gpl2_config();	//配置GPL2[0]为输出上拉模式
gpl2_on();		//控制输出寄存器为高

//当模块卸载
gpl2_off();		//控制输出寄存器为低

实现各个函数

led_ioremap_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <linux/init.h>
#include <linux/module.h>
#include <asm/io.h>

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("jerry");

volatile static unsigned long virt_addr; //虚拟地址
volatile static unsigned long *GPL2CON, *GPL2DAT, *GPL2PUD;

static void gpl2_remap(void){
	//分析知,从0x1100_0100起映射12个字节即可
	//一个ulong在32位机器上占用4个字节,寄存器都是ulong
	unsigned long phys_addr = 0x11000100; //物理地址
	virt_addr = (unsigned long)ioremap(phys_addr,12);//将指针强转为整形
	
	//指定需要操作的寄存器地址
	GPL2CON = (unsigned long *)(virt_addr+0x00);
	GPL2DAT = (unsigned long *)(virt_addr+0x04);
	GPL2PUD	= (unsigned long *)(virt_addr+0x08);
}
static void gpl2_config(void){
	//配置为输出
	*GPL2CON &= 0xfffffff0;
	*GPL2CON |= 0x1;
	//配置为上拉
	*GPL2PUD &= 0xfffc;
	*GPL2PUD |= 0x3;
}
static void gpl2_on(void){
	*GPL2DAT &= 0xfe;
	*GPL2DAT |= 0x1;
}
static void gpl2_off(void){
	*GPL2DAT &= 0xfe;
	*GPL2DAT |= 0x0;
}

static int demo_init(void){
	gpl2_remap();	//实现IO内存的映射,得到3个可控制的寄存器
	gpl2_config();	//配置GPL2[0]为输出上拉模式
	gpl2_on();		//控制输出寄存器为高
	printk(KERN_EMERG "led is on!\n");
	return 0;
}

static void demo_exit(void){
	gpl2_off();		//控制输出寄存器为低
	printk(KERN_EMERG "led is off!\n");
}

module_init(demo_init);
module_exit(demo_exit);

其中为什么不能用指针进行加法运算,非要用整形运算?

因为指针加1偏移可能不是1个字节,比如指针指向一个结构体,加1就会偏移一个结构体size,所以这里必须强转整形进行运算。或者如果知道偏移是4字节(32位机器),使用指针+1也可以。

验证结果

编写简单Makefile:参考这里

编译生成模块后,拷贝到开发板中:

  • 加载驱动小灯亮,卸载驱动小灯灭;

关于GPIO的一些库函数

申请和释放GPIO

1
2
ret = gpio_request(EXYNOS4_GPL2(0),"TEST_GPIO");	/*申请GPIO资源*/
gpio_free(EXYNOS4_GPL2(0));							/*释放GPIO资源*/

配置GPIO

1
2
3
4
5
6
s3c_gpio_cfgpin(EXYNOS4_GPL2(0),S3C_GPIO_OUTPUT);	/*设置输出模式*/
s3c_gpio_cfgpin(EXYNOS4_GPL2(0),S3C_GPIO_INPUT);	/*设置输入模式*/
s3c_gpio_cfgpin(EXYNOS4_GPL2(0),S3C_GPIO_SFN(2));	/*设置特殊功能2模式*/
s3c_gpio_setpull(EXYNOS4_GPL2(0),S3C_GPIO_PULL_UP);	/*上拉状态*/
s3c_gpio_setpull(EXYNOS4_GPL2(0),S3C_GPIO_PULL_DOWN);	/*下拉状态*/
s3c_gpio_setpull(EXYNOS4_GPL2(0),S3C_GPIO_PULL_NONE);	/*无上拉也无下拉*/

输出模式下改变GPIO电平

1
2
gpio_set_value(EXYNOS4_GPL2(0),0);					/*置低*/
gpio_set_value(EXYNOS4_GPL2(0),1);					/*置高*/

输入模式下获取GPIO电平

1
value = gpio_set_value(EXYNOS4_GPL2(0));

配置输入输出且设置电平

1
2
3
gpio_direction_output(EXYNOS4_GPL2(0),0);	//初始化为输出,并输出低电平
gpio_direction_output(EXYNOS4_GPL2(0),1);	//初始化为输出,并输出高电平
gpio_direction_input(EXYNOS4_GPL2(0));		//初始化为输入