系列文章目录
此博客内容根据韦东山嵌入式Linux驱动开发课程书写而来,将课程中用到的代码移植到树莓派4B板子。
文章目录
前言
在上一篇内容中,书写了一个设备驱动程序来完成LED的点亮与熄灭,在里面抽象出了一个file_operations结构体,通过register_chrdev函数将file_operations结构体将结构体告诉内核来注册驱动程序;
针对硬件操作部分抽象出了led_operations结构体,在led_operations定义了LED的属性和动作。
抽象结构体就是利用了面向对象的思想,并且在程序中还利用了上下分层的思想,在程序中分为了两侧,上层实现了与硬件无关的操作,比如注册字符设备驱动(lecdrv.c),下层则是与树莓派硬件操作相关的,里面定义了树莓派LED的属性和动作。
一、驱动设计思想–分离
分离的思想则是在前面面向对象和分册的思想的基础上进一步升级,加入分离思想。假设我们有两块树莓派的板子,两块板子使用的是同一款芯片bcm2711,但是两块板中使用不同的LED资源,假设A主板使用GPIO0、1,B主板使用了GPIO2、3,如果我们还是使用前面的设计思想,在驱动程序中我们需要写两个不同的单板程序来操作GPIO相关的寄存器,重新书写单板程序,这样就会针对同一芯片书写多份GPIO寄存器相关操作的程序。我们希望通过分离的思想,抽象出一个led_resoruce结构体,之后单板程序只要初始化led_resource结构体,告诉芯片的实现函数单板需要使用到哪些引脚资源即可,而在芯片的GPIO实现函数中则实现了所有GPIO相关的操作,不同的单板通过初始化通过的led_resource结构体即可,不用再繁琐的去操作GPIO相关的寄存器了。
led_resource.h文件
#ifndef _LED_RESOURCE_H
#define _LED_RESOURCE_H
/* GPIO3_0 */
/* bit[31:16] = group */
/* bit[15:0] = which pin */
#define GROUP(x) (x>>16)
#define PIN(x) (x&0xFFFF)
#define GROUP_PIN(g,p) ((g<<16) | (p))
struct led_resource {
int pin;
};
struct led_resource *get_led_resouce(void);
#endif
二、示例代码
假设我们自己使用了BCM2711做了一块自己的开发板,我们使用了GPIO26,如下图所示,在GPIO接口左排的倒数第二个引脚,因为最后一个引脚就是GND,所以我们将LED灯管直接接在最后两个引脚来演示效果。
代码的led_opr.h、leddrv.c和ledtest.c文件和使用树莓派学习Linux驱动开发-01 LED驱动程序里面是一致的,在分离驱动中,增加了chip_bcm2711_gpio.c文件和board_pi4b_led.c文件,在chip_bcm2711_gpio.c定义了bcm2711芯片的GPIO寄存器的初始化,置位和位清零的操作。即下图的10个寄存器。
GPFSELx是用来定义GPIO引脚的功能模式,每个GPFSEL寄存器可以初始化10个GPIO,用6个寄存器来初始化58个GPIO引脚,也即bcm2711芯片的所有引脚。GPSETx寄存器可以设置对应的GPIO引脚输出高电平,每个寄存器可以设置32个GPIO,GPCLRx寄存器可以设置对应的GPIO引脚输出低电平,每个寄存器可以设置32个GPIO。
chip_bcm2711_gpio.c代码如下
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <asm/io.h>
#include "led_opr.h"
#include "led_resource.h"
static volatile unsigned int *GPFSEL0;
static volatile unsigned int *GPFSEL1;
static volatile unsigned int *GPFSEL2;
static volatile unsigned int *GPFSEL3;
static volatile unsigned int *GPFSEL4;
static volatile unsigned int *GPFSEL5;
static volatile unsigned int *GPSET0;
static volatile unsigned int *GPSET1;
static volatile unsigned int *GPCLR0;
static volatile unsigned int *GPCLR1;
#define GPIO_BASE_Physical_Address 0xfe200000
#define GPFSEL0_Offs 0x00 //GPIO0--GPIO9
#define GPFSEL1_Offs 0x04 //GPIO10--GPIO19
#define GPFSEL2_Offs 0x08 //GPIO20--GPIO29
#define GPFSEL3_Offs 0x0C //GPIO30--GPIO39
#define GPFSEL4_Offs 0x10 //GPIO40--GPIO49
#define GPFSEL5_Offs 0x14 //GPIO50--GPIO57
#define GPSET0_Offs 0x1C //GPIO0--GPIO31
#define GPSET1_Offs 0x20 //GPIO32--GPIO57
#define GPCLR0_Offs 0x28 //GPIO0--GPIO31
#define GPCLR1_Offs 0x2C //GPIO32--GPIO57
static struct led_resource *led_rsc;
static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */
{
if (!led_rsc)
{
led_rsc = get_led_resouce();
}
GPFSEL0 = (volatile unsigned int*)ioremap(GPIO_BASE_Physical_Address + GPFSEL0_Offs, 4);
GPFSEL1 = (volatile unsigned int*)ioremap(GPIO_BASE_Physical_Address + GPFSEL1_Offs, 4);
GPFSEL2 = (volatile unsigned int*)ioremap(GPIO_BASE_Physical_Address + GPFSEL2_Offs, 4);
GPFSEL3 = (volatile unsigned int*)ioremap(GPIO_BASE_Physical_Address + GPFSEL3_Offs, 4);
GPFSEL4 = (volatile unsigned int*)ioremap(GPIO_BASE_Physical_Address + GPFSEL4_Offs, 4);
GPFSEL5 = (volatile unsigned int*)ioremap(GPIO_BASE_Physical_Address + GPFSEL5_Offs, 4);
GPSET0 = (volatile unsigned int*)ioremap(GPIO_BASE_Physical_Address + GPSET0_Offs, 4);
GPSET1 = (volatile unsigned int*)ioremap(GPIO_BASE_Physical_Address + GPSET1_Offs, 4);
GPCLR0 = (volatile unsigned int*)ioremap(GPIO_BASE_Physical_Address + GPCLR0_Offs, 4);
GPCLR1 = (volatile unsigned int*)ioremap(GPIO_BASE_Physical_Address + GPCLR1_Offs, 4);
return 0;
}
static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
{
//将需要初始化的引脚设置为output模式,即3'b001
int pinmode_group = (GROUP(led_rsc->pin) * 10 + PIN(led_rsc->pin) ) / 10;
int pinmode_bits = ((GROUP(led_rsc->pin) * 10 + PIN(led_rsc->pin) ) % 10) * 3;
printk("board_demo_led_ctl pinmode_group %d, pinmode_bits %d\n", pinmode_group, pinmode_bits);
switch(pinmode_group)
{
case 0:
{
*GPFSEL0 &= ~(7 << pinmode_bits); //7 = 3'b111
*GPFSEL0 |= 1 << pinmode_bits; //1 = 3'b001
break;
}
case 1:
{
*GPFSEL1 &= ~(7 << pinmode_bits); //7 = 3'b111
*GPFSEL1 |= 1 << pinmode_bits; //1 = 3'b001
break;
}
case 2:
{
*GPFSEL2 &= ~(7 << pinmode_bits); //7 = 3'b111
*GPFSEL2 |= 1 << pinmode_bits; //1 = 3'b001
break;
}
case 3:
{
*GPFSEL3 &= ~(7 << pinmode_bits); //7 = 3'b111
*GPFSEL3 |= 1 << pinmode_bits; //1 = 3'b001
break;
}
case 4:
{
*GPFSEL4 &= ~(7 << pinmode_bits); //7 = 3'b111
*GPFSEL4 |= 1 << pinmode_bits; //1 = 3'b001
break;
}
case 5:
{
*GPFSEL5 &= ~(7 << pinmode_bits); //7 = 3'b111
*GPFSEL5 |= 1 << pinmode_bits; //1 = 3'b001
break;
}
}
//将需要点亮的LED对应的GPIO SET or CLEAR
int set_clr_group = (GROUP(led_rsc->pin) * 10 + PIN(led_rsc->pin) ) / 32;
int set_clr_bit = (GROUP(led_rsc->pin) * 10 + PIN(led_rsc->pin) ) % 32;
printk("set_clr_group %d, set_clr_bit %d\n", set_clr_group, set_clr_bit);
if (status)
{
switch (set_clr_group)
{
case 0:
{
*GPSET0 &= ~(1 << set_clr_bit);
*GPSET0 |= 1 << set_clr_bit;
break;
}
case 1:
{
*GPSET1 &= ~(1 << set_clr_bit);
*GPSET1 |= 1 << set_clr_bit;
break;
}
}
}
else
{
switch (set_clr_group)
{
case 0:
{
*GPCLR0 &= ~(1 << set_clr_bit);
*GPCLR0 |= 1 << set_clr_bit;
break;
}
case 1:
{
*GPCLR0 &= ~(1 << set_clr_bit);
*GPCLR0 |= 1 << set_clr_bit;
break;
}
}
}
return 0;
}
static struct led_operations board_demo_led_opr = {
.init = board_demo_led_init,
.ctl = board_demo_led_ctl,
};
struct led_operations *get_board_led_opr(void)
{
return &board_demo_led_opr;
}
而board_pi4b_led.c文件则是定义了board_pi4b_led结构体,在结构体中初始化了这块开发板使用到的LED号。
board_pi4b_led.c代码:
#include "led_resource.h"
static struct led_resource board_pi4b_led = {
.pin = GROUP_PIN(2,6),
};
struct led_resource *get_led_resouce(void)
{
return &board_pi4b_led ;
}
代码中GROUP_PIN(2,6)代表使用第二组第六个GPIO引脚,每组10个GPIO,所以GPIO引脚对应GPIO26。
假设我们有另外一块使用bcm2711开发板使用到的GPIO25来点亮GPIO引脚,因为使用的是同一款主控芯片,GPIO相关的寄存器设置是一致的,此时我们不再需要去修改leddrv.c即chip_bcm2711_gpio.c文件,只需要修改board_pi4b_led.c文件中的board_pi4b_led 结构体即可,内容修改为GROUP_PIN(2,5)。
三、操作步骤如下
拷贝代码到树莓派中,设置好相关的环境变量。
代码在https://gitee.com/zhousong918/pi_linux_driver.git。