首先树莓派3B的物理地址已经变了。能找到的手册只有2835/2708型号的手册,其实其他的几乎都没什么改变。怎么知道他的io地址改变了呢?通过3B更新的boot文件补丁里面得到的。
我们在编写驱动程序的时候,IO空间的起始地址是0x3f000000,加上GPIO的偏移量0x2000000,所以GPIO的物理地址应该是从0x3f200000开始的,然后在这个基础上进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上。
特别注意,BCM2708 和BCM2709 IO起始地址不同,BCM2708是0x20000000,BCM2709是0x3f000000,这是造成大部分人驱动出现“段错误”的原因。树莓派3B的CPU为BCM2709。
根据手册原来的io地址是0x20200000,而3B的io地址变成了0x3f200000.其他的寄存器地址根据这个基地址算就可以了。
设置步骤为
1.读取该寄存器的值
根据数据手册记载,每个gpio口占用输出寄存器的1个位,也就是说每个寄存器管理32/1一共32个gpio口,所以确定是哪个寄存器(每个寄存器可以配置32个gpio 每个gpio需要1位)
2.修改寄存器方向的值(输入/输出)
根据数据手册记载,每个gpio口占用方向寄存器的3个位,也就是说每个寄存器管理32/3一共10个gpio口,所以确定是哪个寄存器(每个寄存器可以配置10个gpio 每个gpio需要3位)
3.写入寄存器
根据数据手册记载,每个gpio口占用输出寄存器的1个位,也就是说每个寄存器管理32/1一共32个gpio口,所以确定是哪个寄存器(每个寄存器可以配置32个gpio 每个gpio需要1位)
新建一个文本,名字为pi_leds.c,实际可根据你的爱好来
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PIN 18
#define BCM2835_GPIO_BASE 0x3f200000
//32 32 4Bit function register
#define BCM2835_GPIOReg_GPFSEL1 0x00000004
//gpio set 1 output register
#define BCM2835_GPIOReg_GPSET0 0x0000001c
//gpio set 0 output register
#define BCM2835_GPIOReg_GPCLR0 0x00000028
#define DEVICE_NAME "led"
#define LED_MAJOR 231
#define IOCTL_LED_ON 0
#define IOCTL_LED_OFF 1
static volatile unsigned long *gpcon;
static volatile unsigned long *gpset;
static volatile unsigned long *gpclr;
static int pi_leds_open(struct inode *inode, struct file *file)
{
//config your led pin function mode
gpcon = (volatile unsigned long *)ioremap(0x3f200004,4);
gpset = (volatile unsigned long *)ioremap(0x3f20001C,4);
gpclr = (volatile unsigned long *)ioremap(0x3f200028,4);
return 0;
}
static int pi_leds_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
if (arg > 3) {
return -EINVAL;
}
switch(cmd) {
case IOCTL_LED_ON:
// set your led 0
*gpcon = *gpcon | (1<<24); //set pin18 to output function mode
*gpset = *gpset | (1<<18); //set pin18 output high
return 0;
case IOCTL_LED_OFF:
// set your led 1
*gpcon = *gpcon | (1<<24); //set pin18 to output function mode
*gpclr = *gpclr | (1<<18); //set pin18 output low
return 0;
default:
return -EINVAL;
}
}
static struct file_operations pi_leds_fops = {
.owner = THIS_MODULE,
.open = pi_leds_open,
.unlocked_ioctl = pi_leds_ioctl,
};
static int __init pi_leds_init(void)
{
int ret;
ret = register_chrdev(LED_MAJOR, "led", &pi_leds_fops);
if (ret < 0) {
printk("led can't register major number\n");
return ret;
}
printk("led is initialized \n");
printk("led device number is : %d \n",ret);
return 0;
}
static void __exit pi_leds_exit(void)
{
unregister_chrdev(LED_MAJOR, DEVICE_NAME);
printk("led is goodbye\n");
}
module_init(pi_leds_init);
module_exit(pi_leds_exit);
MODULE_LICENSE("GPL");
Makefile文件
修改KDIR为你的linux源码根目录,修改NAME为你的驱动文件名字
这里第一次用的话,要去到你的源码目录里面的drivers/char/Makefile里面随便找一个新的行,输入###。这样我们自己写的Makefile才好定位插入文本。
ifneq ($(KERNELRELEASE),)
obj-m := pi_leds.o
else
KDIR := /home/ubuntu1604/Desktop/work/linux-rpi-4.9.y
SPWD := $(shell pwd)
NAME := pi_leds
default:
cp $(SPWD)/*.c $(KDIR)/drivers/char
sed -i "s/###/###\nobj-m += "$(NAME)".o/g" $(KDIR)/drivers/char/Makefile
make -C $(KDIR) modules
cp $(KDIR)/drivers/char/$(NAME).ko $(SPWD)
rm -f $(KDIR)/drivers/char/$(NAME).*
sed -i "N;s/\nobj-m += $(NAME).o//g" $(KDIR)/drivers/char/Makefile
sed -i "N;s/obj-m += $(NAME).o\n//g" $(KDIR)/drivers/char/Makefile
#zai di er ju shi
#zal di yi ju shi
clean:
sed -i "N;s/\nobj-m += $(NAME).o//g" $(KDIR)/drivers/char/Makefile
sed -i "N;s/obj-m += $(NAME).o\n//g" $(KDIR)/drivers/char/Makefile
endif
然后make就可以了,编译后会直接把.ko复制到你的目录下面。
应用程序代码
新建文本,名字为app.c
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int dev_fd;
int on;
if (argc != 2 || sscanf(argv[1],"%d", &on) != 1 ||on < 0 || on > 1 ) {
fprintf(stderr, "Usage:%s 0|1\n",argv[0]);
exit(1);
}
dev_fd = open("/dev/led",O_RDWR);
if( dev_fd == -1){
printf("Cann't open file \n");
return 1;
}
/*通过ioctl来控制灯的亮、灭*/
if(on){
printf("turn on leds!\n");
ioctl(dev_fd, 1);
}
else {
printf("turn off leds!\n");
ioctl(dev_fd, 0);
}
close(dev_fd);
return 0;
}
编译命令
可以去树莓派上编译
gcc app.c -o app.o
在主机上编译就要用交叉编译器,实际编译器名字根据你的环境而定
arm-linux-gcc app.c -o app.o
插入模块 ,创建节点,更改权限
sudo insmod pi_leds.ko
sudo mknod /dev/led c 231 0
sudo chmod 777 /dev/led
测试应用程序
分别控制亮灭
./app.o 1
./app.o 0
#include
#include
#include
#include
#include
int main()
{
int dev_fd;
int on=0;
dev_fd = open("/dev/led",O_RDWR | O_NONBLOCK);
if( dev_fd == -1){
printf("Cann't open file /dev/led\n");
return 1;
}
printf("Start led\n");
while(1){
sleep(1);
fflush(stdout);
if(on==1)on=0;
else on=1;
if(on==0)
{
ioctl(dev_fd,1);
printf("on");
}
else
{
ioctl(dev_fd,0);
printf("off");
}
}
printf("Stop led\n");
close(dev_fd);
return 0;
}
这里要说一点,如果用循环和sleep去延时的话,会出现程序卡死。io控制函数不起作用。可能是lock机制的问题或者print缓冲的问题。用强制刷新缓冲区 fflush(stdout); 就可以解决了,如果还是卡的话,可以在每个延时和循环后面加fflush(stdout);。