io物理地址
首先樹莓派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.其他的寄存器地址根據這個基地址算就可以了。
樹莓派gpio操作
設置步驟爲
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位)
led驅動代碼
新建一個文本,名字爲pi_leds.c,實際可根據你的愛好來
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#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<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<linux/ioctl.h>
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
另一款app(在應用程序上實現flash燈)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<linux/ioctl.h>
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);。