树莓派字符设备驱动点灯

BCM2835 关于MMU的描述


BCM2835除了arm的MMU之外,还使用了第二个MMU将物理内存地址(ARM physical address) 映射成系统总线地址(VC CPU bus address) 。数据手册中罗列的寄存器地址并不是物理内存地址,而是系统总线地址。

因此,在调用 ioremap() 函数前,需要将总线地址转换成物理地址。

从数据手册中得知:总线地址上的外设寄存器起始地址是0x7000 0000 ,物理地址上的外设寄存器起始地址是0x2000 0000。

将总线地址换成物理地址。

CPU bus addressField NameARM physical addressDescription
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


posted @ 2022-07-16 19:22  PYPYN  阅读(85)  评论(0编辑  收藏  举报