操控树莓派IO口的驱动代码编写
程序员文章站
2022-07-03 17:15:01
操控树莓派IO口的驱动代码编写树莓派(bcm2835芯片手册)驱动代码要对I/O口操作,首先得把其对应的物理地址在代码中用变量表示出来,但内核和上层代码访问的都是虚拟地址,所以在驱动代码里不能直接写物理地址,需要把物理地址转化为虚拟地址。先定义变量volatile unsigned int* GPFSEL0 = NULL;volatile unsigned int* GPSET0 = NULL;volatile unsigned int* GPCLR0 = NULL;volatile /...
操控树莓派IO口的驱动代码编写
要对I/O口操作,首先得把其对应的物理地址在代码中用变量表示出来,但内核和上层代码访问的都是虚拟地址,所以在驱动代码里不能直接写物理地址,需要把物理地址转化为虚拟地址。
先定义变量
volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL;
volatile //特征修饰符 作为指令关键字
作用:
1.确保指令不会因编译器的优化而省略。(编译器自认为人为给的数据不行,可能会
被编译器给优化掉)
2.要求每次直接从寄存器读值。(寄存器随着硬件的执行可能会改变寄存器里面的数
据,如果没有volatile修饰,读取的数据是原先数据的一个备份,是个老数据,数据
时效性就很差。)
unsigned
作用:
整数分为有符号与无符号,如果要把类型声明为无符号数就需要使用unsigned来修饰
(除char以外的数据类型中,默认情况下声明的整型变量都是有符号的类型),两者
区别在于,有符号的数最高位的数作为符号位,无符号最高位作为值。如两个字节的
short,有符号表示范围是-32768~32767,无符号范围是0~65535.
查看树莓派型号
cat /proc/cupinfo
此处指令看到的型号不是树莓派cpu真的型号,其真正型号应该是BCM2837,也就是IO在物理地址上的基址应该是0x3F000000。
通过查看芯片手册发GPFSEL0寄存器VC CPU总线地址是0x7E200000,
相对基址(0x7E000000)偏移0x00200000,那么ARM物理地址也是偏移这
么多,所以GPIO的物理地址应该是从0x3f200000 开始 。
物理地址转化为虚拟地址(由芯片手册总线地址GPFSEL0、GPSET0、GPCLR0这三个地址的偏移量推出其物理地址)
GPFSEL0 = volatile(unsigned int *)ioremap(0x3F200000,4);//ioremap函数将物理地址转换成虚拟地址,io口寄存器映射成普通内存单元进行访问
GPSET0 = volatile(unsigned int *)ioremap(0x3F20001C,4);
GPCLR0 = volatile(unsigned int *)ioremap(0x3F200028,4);
把上面三条语句写入函数:int __init pin4_drv_init(void) //真实驱动入口
如此就实现了物理地址转化成虚拟地址。(注:退出驱动时,用iounmap(*GP)函数解除地址映射);
此处以pin4引脚为例,要把pin4引脚设置为输出引脚,根据芯片手册内容需要配置GPFSEL0的14-12位(位置由0开始)为001。
但要注意的是在改变14-12位置上的值时,其它位置上的内容不能变,不然将会影响其它的引脚。
设置pin4引脚为输出引脚
//把pin4变为输出引脚,配置14-12位置(由位置0开始)的内容为001
*GPFSEL0 &= ~(0x6<<12);//6的二进制是110左移12位后,110对应的位置是14-12,取反后110变为001其它位为1,和GPFSEL0进行与运算后就实现只有14、13位改变为0
*GPFSEL0 |= (0x1<<12);//把位置12变为1
获取上层write函数写入的值
copy_from_user(&userCmd,buf,count);//获取上层write函数写入的值,放入userCmd变量;copy_to_user()函数用来读取引脚
//根据值来操作io口变成高电平或者低电平
if(userCmd == 1){//置1,使引脚输出高电平
printk("set 1\n");
*GPFSET0 |= (0x1<<4);//通常pin4用1左移4位进行或运算
}else if(userCmd == 0){//置0,使引脚输出低电平
printk("set 0");
*GPCLR0 |= (0x1<<4);
}else{
printk("undo\n");
}
驱动代码pin4driver.c
#include<linux/fs.h> // file_operations声明
#include<linux/module.h> // module_init module_exit声明
#include<linux/init.h> // __init __exit 宏定义声明
#include<linux/device.h> // class device声明
#include<linux/uaccess.h> // copy_from_user的头文件
#include<linux/types.h> // 设备号 dev_t 类型声明
#include <asm/io.h> // ioremap iounmap 的头文件
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"; //模块名
volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL;
static int pin4_read (struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
printk("pin4_read\n"); // 内核的打印函数
//可以用copy_to_user()函数读取引脚
return 0;
}
//pin4_open函数
static int pin4_open(struct inode *inode, struct file *file)
{
printk("pin4_open\n"); // 内核的打印函数,和printf类似
//把pin4变为输出引脚,配置14-12位置(由位置0开始)的内容为001
*GPFSEL0 &= ~(0x6<<12);//6的二进制是110左移12位后,110对应的位置是14-12,取反后110变为001其它位为1,和GPFSEL0进行与运算后就实现只有14、13位改变为0
*GPFSEL0 |= ~(0x6<<12);//把位置12变为1
return 0;
}
//open_write函数
static ssize_t pin4_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
printk("pin4_write\n");
copy_from_user(&userCmd,buf,count);//获取上层write函数写入的值
//根据值来操作io口变成高电平或者低电平
if(userCmd == 1){
printk("set 1\n");
*GPFSET0 |= (0x1<<4);//通常pin4用1左移4位进行或运算
}else if(userCmd == 0){
printk("set 0\n");
*GPCLR0 |= (0x1<<4);
}else{
printk("undo\n");
}
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
.read = pin4_read,
};
int __init pin4_drv_init(void) //真实驱动入口
{
int ret;
devno = MKDEV(major,minor); //2. 创建设备号
ret = register_chrdev(major , module_name, &pin4_fops); //3.注册驱动,告诉内核,把这个驱动加入到内核的链表中
pin4_class = class_create( THIS_MODULE, "myfirstdemo" ); // 让代码在dev自动生成设备
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;
}
void __exit pin4_drv_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_drv_init);
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
上层测试代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd;
int cmd;
fd = open("/dev/pin4",O_RDWR);
if(fd < 0){
printf("open failed\n");
perror("error");
}
else{
printf("open success\n");
}
printf("input commnd 0/1:\n0:set pin4 low\n1:set pin4 high\n");
scanf("%d",&cmd);
write(fd,&cmd,4);
close(fd);
return 0;
}
然后编译后传到树莓派参考这篇文章
运行
dmesg查看
gpio readall查看
自此操控树莓派pin4 IO口的驱动代码编写完成。如有错误之处,请帮忙指出改正。
本文地址:https://blog.csdn.net/weixin_49817112/article/details/108849996
上一篇: 软件工程三