树莓派字符设备驱动点灯
BCM2835 关于MMU的描述
BCM2835除了arm的MMU之外,还使用了第二个MMU将物理内存地址(ARM physical address) 映射成系统总线地址(VC CPU bus address) 。数据手册中罗列的寄存器地址并不是物理内存地址,而是系统总线地址。
因此,在调用 ioremap() 函数前,需要将总线地址转换成物理地址。
从数据手册中得知:总线地址上的外设寄存器起始地址是0x7000 0000 ,物理地址上的外设寄存器起始地址是0x2000 0000。
将总线地址换成物理地址。
CPU bus address | Field Name | ARM physical address | Description |
---|---|---|---|
0x7E20 0000 | GPFSEL0 | 0X2000 0000 | Select function |
0X7E20 001C | GPSET0 | 0X2000 001C | Set bit |
0X7E20 0028 | GPCLR0 | 0X2000 0028 | Clear bit |
驱动模块程序示例
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/string.h>
#define cdevname "test_cdev"
enum gpio{
GPIO0 = 0U,
GPIO1,
GPIO2 = 4U,//由于GPIO2是i2c,不希望修改该端口功能,我这里临时改为GPIO4
GPIO3,
GPIO4,
GPIO5,
};
#define GPFSEL0 0x20200000U //GPIO0~9的复用寄存器,三个bit位表示一个模式 in:000 out:001
#define GPSET0 0x2020001CU //0~31bit位分别对应GPIO0~31,将bit设置1输出高,设置0无效
#define GPCLR0 0x20200028U //0~31bit位分别对应GPIO0~31,将bit设置1输出低,设置0无效
volatile unsigned int * GPIOAF_BASE = NULL; //定义对应寄存器的虚拟内存起始地址
volatile unsigned int * GPIOSET_BASE = NULL;
volatile unsigned int * GPIOCLR_BASE = NULL;
int major = 240; //手动指定设备号
char copybuff[128] = {0,}; //交换用户数据的缓存空间
ssize_t cdev_read(struct file *file, char __user *ubuffp, size_t size, loff_t *offs) {
int error;
if(size > sizeof(copybuff))
size = sizeof(copybuff);
error = copy_to_user(ubuffp, copybuff, size); //成功返回0, 失败返回未成功拷贝字节个数
if(error) {
printk(KERN_ERR "copy to user error, failed cpy size:%d byte\n", error);
return -EINVAL; //返回错误码,注意是负数
}
printk(KERN_INFO "%s, %s, %d\n", __FILE__, __func__, __LINE__);
return size; //read()成功返回读取的字节个数
}
ssize_t cdev_write(struct file *file, const char __user *ubuffp, size_t size, loff_t *offs) {
int error;
if(size > sizeof(copybuff))
size = sizeof(copybuff);
error = copy_from_user(copybuff, ubuffp, size); //成功返回0, 失败返回未成功拷贝字节个数
if(error) {
printk(KERN_ERR "copy from user error, failed copy size:%d byte\n", error);
return -EINVAL; //返回错误码,注意是负数
}
if(!strcmp(copybuff, "LED_ON")) { //strcmp()两者相同返回 0
*GPIOSET_BASE |= (0x01U << GPIO2);
printk("LED_ON\n");
}
if(!strcmp(copybuff, "LED_OFF")) {
*GPIOCLR_BASE |= (0x01U << GPIO2);
printk("LED_OFF\n");
}
memset(copybuff, 0, size);
printk(KERN_INFO "%s, %s, %d\n", __FILE__, __func__, __LINE__);
return size;
}
int cdv_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "%s, %s, %d\n", __FILE__, __func__, __LINE__);
return 0;
}
int cdev_close(struct inode *inode, struct file *file) {
printk(KERN_INFO "%s, %s, %d\n", __FILE__, __func__, __LINE__);
return 0;
}
const struct file_operations fops = {
.open = cdv_open,
.read = cdev_read,
.write = cdev_write,
.release = cdev_close,
};
static int __init chrdev_init(void) {
int ret;
//注册字符设备驱动
ret = register_chrdev(major, cdevname, &fops);
if(ret < 0) {
printk(KERN_ERR "register char device error");
return -EAGAIN;
}
GPIOAF_BASE = ioremap(GPFSEL0, 4); //将物理地址映射成虚拟地址,成功返回地址失败返回NULL
if(GPIOAF_BASE == NULL) {
printk(KERN_EMERG "GPIOAF_BASE ioremap error\n");
return -ENOMEM;
}
GPIOSET_BASE = ioremap(GPSET0, 4);
if(GPIOSET_BASE == NULL) {
printk(KERN_EMERG "GPIOSET_BASE ioremap error\n");
return -ENOMEM;
}
GPIOCLR_BASE = ioremap(GPCLR0, 4);
if(GPIOCLR_BASE == NULL) {
printk(KERN_EMERG "GPIOCLR_BASE ioremap error\n");
return -ENOMEM;
}
//设置 GPIO2 为输出模式
*GPIOAF_BASE &= ~(0x07U << (3*GPIO2));
*GPIOAF_BASE |= 0x01U << (3*GPIO2);
printk(KERN_INFO "%s, %s, %d\n", __FILE__, __func__, __LINE__);
return 0;
}
static void __exit chrdev_exit(void) {
unregister_chrdev(major, cdevname); //注销设备号
iounmap(GPIOAF_BASE); //取消虚拟地址映射
iounmap(GPIOSET_BASE);
iounmap(GPIOCLR_BASE);
GPIOAF_BASE = NULL; //防止出现野指针
GPIOSET_BASE = NULL;
GPIOCLR_BASE = NULL;
printk(KERN_EMERG "%s, %s, %d\n",__FILE__, __func__, __LINE__);
}
module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");
测试程序示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
char LED_ON[] = "LED_ON";
char LED_OFF[] = "LED_OFF";
int main(int argc, char const *argv[])
{
int fd;
fd = open("/dev/test_cdev", O_RDWR);
if (fd == -1)
{
perror("open /dev/test_cdev error");
return -1;
}
while (1)
{
write(fd, LED_OFF, sizeof(LED_OFF));
printf("%s\n", LED_OFF);
sleep(1);
write(fd, LED_ON, sizeof(LED_ON));
printf("%s\n", LED_ON);
sleep(1);
}
close(fd);
return 0;
}
测试步骤
#编译模块和编译app #1.安装模块 sudo insmod cdev.ko #2.创建设备节点 sudo mknod /dev/test_cdev c 240 0 #3.修改设备文件权限 sudo chmod 777 /dev/test_cdev #4.运行程序 ./app #树莓派与LED硬件连接:GPIO4 -> 1K电阻 -> LED -> GND