🚀 驱动程序

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <linux/init.h>         // 包含初始化宏定义的头文件
#include <linux/module.h> // 包含初始化加载模块的头文件
#include <linux/miscdevice.h> // 杂项设备驱动相关的头文件
#include <linux/fs.h> // 包含文件操作集定义的头文件
#include <linux/io.h> //含有 ioremap 函数 iounmap 函数
#include <linux/uaccess.h> //含有 copy_from_user 函数和含有 copy_to_user 函数

#define GPIOE_CFG0 (0x01C20890)
#define GPIOE_CFG1 (0x01C20894)
#define GPIOE_DATA (0x01C208A0)
#define GPIOE_PUL0 (0x01C208AC)

size_t *gpioe_cfg0; //存储虚拟地址到物理地址映射
size_t *gpioe_cfg1; //存储虚拟地址到物理地址映射
size_t *gpioe_data; //存储虚拟地址到物理地址映射
size_t *gpioe_pul0; //存储虚拟地址到物理地址映射

static int led_open(struct inode *inode, struct file *file)
{
/* GPIOE 配置 */
*((volatile size_t*)gpioe_cfg0) &= ~(7<<20); //清除配置寄存器
*((volatile size_t*)gpioe_cfg0) |= (1<<20); //配置 GPIOE5 为输出模式
*((volatile size_t*)gpioe_pul0) &= ~(3<<10); //清除上/下拉寄存器
*((volatile size_t*)gpioe_pul0) |= (1<<10); //配置 GPIOE5 为上拉模式
*((volatile size_t*)gpioe_data) |= (1 << 5); //设置 GPIOE5 高电平

printk(KERN_DEBUG"open led!!!\n");
return 0;
}

static int led_close(struct inode *inode, struct file *filp)
{
printk(KERN_DEBUG"close led!!!\n");
return 0;
}

static int led_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
int ret;
size_t status = ((*((volatile size_t*)gpioe_data)) >> 5) & 0x01;//获取 GPIOE5 状态
ret = copy_to_user(buff, &status, 4); //将内核空间拷贝到用户空间 buff
if(ret < 0)
printk(KERN_DEBUG"read error!!!\n"); //输出信息
else
printk(KERN_DEBUG"read led ok!!!\n"); //输出信息
return 0;
}

static int led_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp)
{
int ret;
size_t status;
ret = copy_from_user(&status, buff, 4); //将用户空间拷贝到内核空间的 status
if(ret < 0)
printk(KERN_DEBUG"write error!!!\n"); //输出信息
else
printk(KERN_DEBUG"write led ok!!!\n"); //输出信息

*((volatile size_t*)gpioe_data) &= ~(1 << 5) ;//清除 GPIOE5 状态
if(status)
{
*((volatile size_t*)gpioe_data) |= (1 << 5);//设置 GPIOE5 状态 1
}

return 0;
}

static struct file_operations led_ops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_close,
};

struct miscdevice led_dev = {
.minor = MISC_DYNAMIC_MINOR, // 动态分配次设备号
.name = "led_misc", // 设备节点的名字
.fops = &led_ops,
};

/* 模块的入口 */
static int misc_init(void)
{
int ret;
ret = misc_register(&led_dev); // 注册杂项设备
if (ret < 0)
{
printk("led_dev register is error!");
return -1;
}
printk("led_dev register is succeed!\n");

gpioe_cfg0 = ioremap(GPIOE_CFG0, 4); //将 GPIOE_CFG0 物理地址映射为虚拟地址
gpioe_cfg1 = ioremap(GPIOE_CFG1, 4); //将 GPIOE_CFG1 物理地址映射为虚拟地址
gpioe_data = ioremap(GPIOE_DATA, 4); //将 GPIOE_DATA 物理地址映射为虚拟地址
gpioe_pul0 = ioremap(GPIOE_PUL0, 4); //将 GPIOE_PUL0 物理地址映射为虚拟地址

return 0;
}

/* 模块的出口 */
static void misc_exit(void)
{
misc_deregister(&led_dev);

iounmap(gpioe_cfg0); //取消 GPIOE_CFG0 映射
iounmap(gpioe_cfg1); //取消 GPIOE_CFG1 映射
iounmap(gpioe_data); //取消 GPIOE_DATA 映射
iounmap(gpioe_pul0); //取消 GPIOE_PUL0 映射

printk("led_dev deregister!\n");
}

/* 声明模块的入口和出口 */
module_init(misc_init);
module_exit(misc_exit);

MODULE_LICENSE("GPL"); /* 声明模块的开源许可证 */

🚀 应用程序

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
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char **argv)
{
int fd;
int val;
char *filename = NULL;

if(argc !=3)
{
printf("usage: ./led [device] [on/off]\n"); //打印用法
return -1;
}

filename = argv[1];
fd = open(filename, O_RDWR); //打开 dev/设备文件
if (fd < 0) //小于 0 说明没有成功
{
printf("error, can't open %s\n", filename);
return -1;
}

if(!strcmp(argv[2], "on")) //如果输入等于 on,则 LED 亮
val = 0;
else if(!strcmp(argv[2], "off")) //如果输入等于 off,则 LED 灭
val = 1;
else
{
printf("usage: ./led_dev.exe [device] [on/off]\n"); //打印用法
close(fd);
return -1;
}

write(fd, &val, 4); //操作 LED
close(fd);
return 0;

}


🚀 踩坑记录

1、编译应用程序应用根文件系统的交叉工具链 arm-linux-gcc, 而不是 arm-linux-gnueabi-gcc。 下面是操作过程。

编译完根文件系统后,在 buildroot 的主目录,进入 output/host/ 目录,此目录下就是 buildroot 编译根文件系统中安装的交叉工具链。我们可以将该交叉工具链安装到 usr/local 目录下 ,并,比如:

1
2
3
cd buildroot-2021.02.4/output/host/
sudo mkdir /usr/local/arm-gcc-app/
sudo cp -a ./* /usr/local/arm-gcc-app/

接下来我们添加环境变量,打开/etc/profile 文件,在末尾添加路径:

1
export PATH=$PATH:/usr/local/arm-gcc-app/bin

最后重启系统,使环境变量永久生效,就可以使用 arm-linux-gcc 编译C文件了。