树莓派内核驱动编写——控制GPIO的输出

举报
dz小伟 发表于 2022/11/02 12:23:54 2022/11/02
【摘要】 一、地址总线地址,物理地址,虚拟地址 参考博文:地址概念树莓派1.BCM2835 树莓派3b CPU型号,是ARM-cotexA53架构2440 2410 CPU型号 是ARM9架构2.树莓派是32位系统,1G 内存,只能识别949M3.总线地址4G,物理地址1G,虚拟地址4G 二、芯片手册1.芯片目录Introduction 基本介绍Auxilia...

一、地址

总线地址,物理地址,虚拟地址 参考博文:地址概念
树莓派
1.BCM2835 树莓派3b CPU型号,是ARM-cotexA53架构
2440 2410 CPU型号 是ARM9架构
2.树莓派是32位系统,1G 内存,只能识别949M
3.总线地址4G,物理地址1G,虚拟地址4G
在这里插入图片描述

二、芯片手册

1.芯片目录

  • Introduction 基本介绍
  • Auxiliaries: UART1 & SPI1, SPI2 串口开发章节
  • BSC
  • DMA Controller 快速内存拷贝
  • External Mass Media Controller
  • General Purpose I/O (GPIO)
  • Interrupts
  • PCM / I2S Audio
  • Pulse Width Modulator
  • SPI
  • SPI/BSC SLAVE
  • System Timer
  • UART
  • Timer (ARM side)
  • USB

2.GPIO章节

  • 树莓派官网:树莓派(看引脚)
  • 54个通用I/O,至少2个功能选择(输入输出)
  • GPIO有41个寄存器
  • 功能选择
    GPFSEL0 GPIO Function Select 0 功能选择(输入输出)
    GPFSEL0 控制0-9
    GPFSEL1 控制11-19
    在这里插入图片描述
  • 输出控制
    GPSET0 GPIO Pin Output Set 0 输出1
    0 = No effect
    1 = Set GPIO pin n
    GPCLR0 GPIO Pin Output Clear 0 清0
    0 = No effect
    1 = Clear GPIO pin n

描述
1.特别注意树莓派是总线地址,其他芯片是物理地址.
2.除了功能和输入输出控制外,后面还有上升沿边缘触发的寄存器
在这里插入图片描述

三、编写内核代码

  1. 添加寄存器地址,编写到函数pin4_init里
    volatile:指令不会因编译器的优化而省略,直接读取
    GPIO偏移量
    在这里插入图片描述
//定义无符号指针,volatile防止优化
volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0  = NULL;
volatile unsigned int* GPCLR0  = NULL;
//指针指向虚拟地址,0x3f200000是物理地址,ioremap把物理地址映射成虚拟地址
//物理地址:3f000000加偏移量200000
//ioremap使用参考内核
GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0  = (volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0  = (volatile unsigned int *)ioremap(0x3f200028,4);
  1. 编写open函数
    配置功能寄存器
    按位操作:先清零12-14位,在赋值
*GPFSEL0 &= ~(6<<12);//配置pin4引脚为输出引脚
*GPFSEL0 |= 1<<12;
  1. 编写write函数
    读取上层write值:在内核中查找函数:copy_from_user,参考内核使用函数 userCmd和上层传的类型一样
    根据值操作io口:操作第4位
//获取上层write值
copy_from_user(&userCmd,buf,count);
//根据值操作io口
if(userCmd == 1){
	*GPSET0 |= 1<<4;
	printk("set 1\n");
}else if(userCmd == 0){
	*GPCLR0 |= 1<<4;
	printk("set 0\n");
}else{
	printk("undo\n");
}
  1. 解除映射
iounmap(GPFSEL0);//先解除虚拟地址映射,在销毁设备
iounmap(GPSET0);
iounmap(GPCLR0);

完整代码

#include <linux/fs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <asm/io.h>

volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0  = NULL;
volatile unsigned int* GPCLR0  = NULL;

static struct class *pin4_class;
static struct device *pin4_class_dev;

static dev_t devno;
static int major = 231;
static int minor = 0;
static char *module_name = "pin4";

static ssize_t pin4_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{	
	int userCmd;
	//获取上层write值
	copy_from_user(&userCmd,buf,count);
	printk("get value\n");
	//根据值操作io口
	if(userCmd == 1){
		*GPSET0 |= 1<<4;
		printk("set 1\n");
	}else if(userCmd == 0){
		*GPCLR0 |= 1<<4;
		printk("set 0\n");
	}else{
		printk("undo\n");
	}
	return 0;
}
static ssize_t pin4_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    printk("pin4_read\n");
    return 0;
}

static int pin4_open(struct inode * inode, struct file * filp)
{
	*GPFSEL0 &= ~(6<<12);//配置pin4引脚为输出引脚
	*GPFSEL0 |= 1<<12;
    printk("pin4_open\n");//内核的打印函数
    return 0;
}

//在内核源码查找struct file_operations看结构体成员,添加用到的函数
static const struct file_operations pin4_fops = {
    .owner = THIS_MODULE,
    .write = pin4_write,//函数指针
    .open  = pin4_open,
    .read  = pin4_read,
};


static int __init pin4_init(void)//驱动入口
{
	int ret;
	printk("insmod driver pin4 success\n");
	devno = MKDEV(major,minor);//创建设备号
	ret = register_chrdev(major,module_name,&pin4_fops);//注册驱动,把这个驱动加入到内核链表
	pin4_class = class_create(THIS_MODULE,"myfirstdemo");//代码自动生成设备
	pin4_class_dev = device_create(pin4_class,NULL,devno,NULL,module_name);//创建设备文件

	GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);//物理地址转换为虚拟地址,io口寄存器映射成普通内存单元进行访问
	GPSET0  = (volatile unsigned int *)ioremap(0x3f20001C,4);
	GPCLR0  = (volatile unsigned int *)ioremap(0x3f200028,4);
	return 0;
}

static void __exit  pin4_exit(void)
{
	iounmap(GPFSEL0);//先解除虚拟地址映射,在销毁设备
    iounmap(GPSET0);
	iounmap(GPCLR0);
	device_destroy(pin4_class,devno);//销毁设备
	class_destroy(pin4_class);//销毁类
	unregister_chrdev(major,module_name);//卸载设备
}

module_init(pin4_init);//入口,是个宏
module_exit(pin4_exit);
MODULE_LICENSE("GPL v2");

四、编写上层代码

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
        int fd;
        int cmd;
        int data;

        fd = open("/dev/pin4",O_RDWR);
        if(fd < 0){
                printf("open failed\n");
                perror("reson:");
                exit(-1);
        }else{
                printf("open success\n");
        }
        printf("input commnd :1/0  \n1:set pin4 high \n0:set pin4 low\n");
        scanf("%d",&cmd);

        if(cmd == 1){
                data = 1;
        }else{
                data = 0;
        }
        printf("data = %d\n",data);
        write(fd,&data,1);
        close(fd);
        return 0;
}

五、编译运行实现功能

1’编译参考博文:树莓派内核驱动编写——添加与调用
2.执行上层,查看GPIO输出

查看GPIO:gpio readall

在这里插入图片描述

师承上官可编程 —— 陈立臣

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。