1、驱动代码编写
框架查看博文基于框架编写驱动
1.1 声明寄存器,并赋值为NULL
volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL;
原因:寄存器地址是物理地址,需要映射为虚拟地址,故将具体地址放在init函数中去映射成虚拟地址,而不直接赋值
volatile 确保指令不会因编译器的优化而省略,且要求每次读值
unsighed 由于是内存地址,所以使用无符号形式
1.2 物理地址映射成虚拟地址
ioremap函数的作用是将物理地址映射成虚拟地址,4表示字节数
GPFSEL0 = (volatile unsigned int*)ioremap(0x3f20000,4);
GPSET0 = (volatile unsigned int*)ioremap(0x3f2001c,4);
GPCLR0 = (volatile unsigned int*)ioremap(0x3f20028,4);
如何获取寄存器物理地址?
(树莓派3B的BCM2835:IO物理地址基址为0x3F000000、)
- 首先IO在地址总线上的基址是0x7E000000,在物理地址上的基址是0x3F000000
- 其次查看芯片手册,发现GPFSEL0寄存器总线地址是0x7E200000,相对基址偏移0x00200000,故物理地址的偏移量也为0x00200000
- 最终得到GPFSEL0寄存器物理地址为0x3F200000;以此类推
得到GPSET0 寄存器物理地址为0x3F20001c
得到GPCLR0 寄存器物理地址为0x3F200028
1.3 在destroy函数中使用iounmap函数清除地址映射
iounmap(GPFSEL0);
iounmap(GPSET0);
iounmap(GPCLR0);
1.4 在open函数中设置引脚类型为输出
//引脚pin4位于GPFSEL0寄存器的bit14-12,需将其置为001
GPFSEL0 &= ~(0x6 << 12); //将bit14 13 置为0
GPFSEL0 |= (0x1 << 12); //将bit12 置为1
- 由于只改变bit14-12三位数据,使用按位与操作(通俗来说是遇到1不改变,遇到0改变为0)
想要改变bit14-12为001,就需要按位与(0x 1111…… 001 ……1111)
将其置反则变为(0x 0000……110……0000)即( 0x6 << 12 )
所以执行第一条语句 - 由于第一条语句的按位与(bit14-12为001),改变bit14 13 为0,bit12没有改变,不确定其原来是否为1,故执行按位或操作(通俗来说是遇到1改变为1,遇到0不改变)
需要将bit12置为1,故按位或(0x 0000…… 001 ……000)即(0x1 << 12 )所以执行第二条语句
1.5 在write函数中根据上层代码值进行高电平或低电平输出
copy_from_user(&cmd,buf,count);
if(cmd == 1){
printk("set 1");
GPSET0 |=(0x1 << 4);
}
else if(cmd == 0){
printk("set 0");
GPCLR0 |=(0x1 << 4);
}
- copy_from_user函数获取上层write函数的值,1表示高电平、0表示低电平
- 高电平则使用GPSET0寄存器,引脚4对应bit4,将其置为1,采用按位或操作
- 低电平则使用GPCLR0寄存器,引脚4对应bit4,将其置为1,采用按位或操作
2、测试代码编写
查看博文:内核驱动操控IO口源码(pin4引脚)
3、驱动编译、测试
查看博文:Linux驱动认知及编译加载驱动