ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

树莓派字符设备驱动点灯

2022-07-16 19:31:59  阅读:205  来源: 互联网

标签:__ 字符 树莓 LED 点灯 BASE error include size


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


标签:__,字符,树莓,LED,点灯,BASE,error,include,size
来源: https://www.cnblogs.com/pypyn/p/16484997.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有