0
点赞
收藏
分享

微信扫一扫

使用树莓派学习Linux驱动开发-02 面向对象/分层/分离驱动设计思想编写LED驱动程序

guanguans 2022-04-17 阅读 18

系列文章目录

此博客内容根据韦东山嵌入式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。
在这里插入图片描述

举报

相关推荐

0 条评论