文章目录
设计思想
面向对象
- 字符设备驱动程序抽象出一个
file_operations
结构体 - 我们写的程序针对硬件部分抽象出
led_operations
结构体
所谓面向对象就是指的根据某一类事物的属性,进行抽象。比如字符设备的操作大多都需要read,write,open,close等函数,但是具体的操作不同,因此内核将共有的属性进行抽象出来,就是file_operations结构体,这样当我们需要编写具体的字符设备驱动的时候,我们就可以继承file_operations结构体,然后根据我们的需求自己实现对应的功能
分层
上下分层,比如我们在前面所写的LED驱动程序就分为2层:
- 上层
实现硬件无关操作
,比如注册字符设备驱动 - 下层
实现硬件相关操作
,比如实现具体单板的LED操作
分离
设想一下,如果我们在board_A中突然想要在硬件上换一个引脚来控制LED怎么办呢?
如果直接修改boardA.c是不是显得非常的难受不不愿呢!
实际情况是,每一款芯片它的GPIO操作都是类似的。
既然引脚操作是有规律的,且与主芯片有关,那么我们就可以针对该芯片写出比较通用的硬件操作代码
比如board_A使用的芯片chipY,那么就可以写出:chipY_gpio.c,它实现芯片Y的GPIO操作,适用于使用芯片Y的所有单板的gpio操作;使用时,我们只需要在board_A_led.c中去指定使用那个芯片即可
如下图
实验
本实验基于STM32_MP_157开发板
手册资源
原理图 找到GREEN LED and YELLOW LED
数据手册
通过查看MP157A框图可知,GPIOA与GPIOG组挂载在AHB总线上
开发参考手册
阅读芯片手册内存和总线架构中内存映射组织环节下的外设集群章节描述可知
AHB4用于大多数GPIO的控制
因此在清除控制通路以后,我们接下来就需要的是使能和配置时钟
查看时钟部分,我们需要使能时钟给外设GPIO
,并将GPIO资源分配给MPU
,因为我们的MP157开发板是M4核与A7核共享开发板的资源。
阅读参考手册/用户手册,查看RCC部分
配置PLL4时钟源
显然这里我们只需要进行控制即可,默认配置就行。
我们需要使能PLL4,就需要设置控制寄存器的Bit0为1,同时等待Bit1为1使时钟稳定
将GPIO资源分配给MPU
我们使用的引脚使PA10和PG8,因此我们需要将GPIOA组和GPIOG组资源分配给MPU,即将设置寄存器中的Bit0和Bit6设置为1
清0寄存器
再来看看GPIO寄存器
第一个模式寄存器,设置该GPIO的工作模式
设置输出类型寄存器
输出速度寄存器
数据寄存器
但是为了方便操作,我们一般使用设置与重置寄存器
从上面我们可以知晓各个寄存器的偏移地址,下面我们需要确定其基地址
下面我们就可以进行编程了
驱动程序 led_drv.c
// this file is true driver that is hardware independent
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/stat.h>
#include "led_opr.h"
#define LED_NUM 2
int major=0;
//allow user pass parameter to major
module_param(major,int,0);
//create assist information to driver ,because it can automatically create device nodes
static struct class *led_class;
struct led_operations *p_led_opr;
static int led_open(struct inode *node, struct file *fp)
{
//get minor by node
int minor;
printk("%s %s line %d\n",__FILE__,__FUNCTION__ ,__LINE__);
minor = iminor(node);
p_led_opr->init(minor);
return 0;
}
static int led_close(struct inode *node, struct file *fp)
{
printk("%s %s line %d\n",__FILE__,__FUNCTION__ ,__LINE__);
return 0;
}
static ssize_t led_write (struct file *fp, const char __user *buffer, size_t len, loff_t *offset)
{
int minor;
char status;
int err;
struct inode *inode = file_inode(fp);
minor = iminor(inode);
err = copy_from_user(&status,buffer,1);
p_led_opr->ctl(minor,status);
return err;
}
static ssize_t led_read (struct file *fp, char __user *buffer, size_t len, loff_t * offset)
{
printk("%s %s line %d\n",__FILE__,__FUNCTION__ ,__LINE__);
return 0;
}
static struct file_operations f_opr ={
.owner = THIS_MODULE,
.open = led_open,
.release = led_close,
.write = led_write,
.read = led_read,
};
static __init int led_init(void){
int i;
printk("%s %s line %d\n",__FILE__,__FUNCTION__ ,__LINE__);
if(major){
register_chrdev(major,"jacky_led",&f_opr);
}else{
major=register_chrdev(0,"jacky_led",&f_opr);
}
led_class = class_create(THIS_MODULE,"led_class");
//check for led_class errors
if(IS_ERR(led_class)){
printk("error append in %s line %d\n",__FUNCTION__ ,__LINE__);
unregister_chrdev(major,"jacky_led");
return -1;
}
p_led_opr = get_board_led_opr();
if(p_led_opr==NULL){
printk("error append in %s line %d\n",__FUNCTION__ ,__LINE__);
unregister_chrdev(major,"jacky_led");
return -1;
}
// create device nodes
for(i=0;i<LED_NUM;i++){
device_create(led_class,NULL,MKDEV(major,i),NULL,"led_dev%d",i);
}
return 0;
}
static __exit void led_exit(void){
int i;
printk("%s %s line %d\n",__FILE__,__FUNCTION__ ,__LINE__);
for(i=0;i<LED_NUM;i++){
device_destroy(led_class,MKDEV(major,i));
}
class_destroy(led_class);
unregister_chrdev(major,"jacky_led");
return;
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
led_operations.h
#ifndef _LED_OPR_H
#define _LED_OPR_H
#include "led_resource.h"
//define struct led_operations
struct led_operations{
int (*init)(int minor);//initialize the led , which is which one gpio group
int (*ctl)(int minor,char status);//control the gpio pin, status is led status
};
struct led_operations* get_board_led_opr(void);
#endif
芯片相关chip_gpio.c
/*
* to realize led_operations for hierarchy(分层)
* */
#include "led_opr.h"
#include "led_resource.h"
#include <linux/module.h>
#include <asm/io.h>
static volatile unsigned int *RCC_PLL4CR; //PLL4 clock control register
static volatile unsigned int *RCC_MP_AHB4ENSETR;//make gpio belong to MPU enable register
static volatile unsigned int *GPIO_MODER; //GPIO mode control register
static volatile unsigned int *GPIO_BSRR;//gpio data set/reset register
//get stm32MP157 led resource from board_MP157_led.c
static struct led_resource* board_led_res;
static int board_led_init(int minor)
{
printk("%s %s line %d\n",__FILE__,__FUNCTION__ ,__LINE__);
if(!board_led_res){
board_led_res = get_led_resource();
}
printk("led resource:group = %d, pin = %d \n",
GROUP(board_led_res->pin[minor]),PIN(board_led_res->pin[minor]));
if(!RCC_PLL4CR){//init clock
RCC_PLL4CR = ioremap(ADDRESS_RCC_PLL4CR,4);
RCC_MP_AHB4ENSETR = ioremap(ADDRESS_RCC_MP_AHB4ENSETR,4);
//enable clock
*RCC_PLL4CR |= (1<<0);
//ensure clock is stabilize(稳定)
while ((*RCC_PLL4CR & (1<<1)) == 0);
}
switch (minor) {
case 0:
GPIO_MODER = ioremap(ADDRESS_GPIOA_MODER,4);
GPIO_BSRR = ioremap(ADDRESS_GPIOA_BSRR,4);
break;
case 1:
GPIO_MODER = ioremap(ADDRESS_GPIOG_MODER,4);
GPIO_BSRR = ioremap(ADDRESS_GPIOG_BSRR,4);
break;
default:
break;
}
//set gpio group work mode
// enable RCC for gpio belong to MPU
*RCC_MP_AHB4ENSETR |= (1<<GROUP(board_led_res->pin[minor]));
//clear data
*GPIO_MODER &= ~(3 << (PIN(board_led_res->pin[minor]) * 2)) ;
//set data
*GPIO_MODER |= (1 << (PIN(board_led_res->pin[minor]) * 2));
return 0;
}
static int board_led_ctl(int minor,char status)
{
printk("%s %s line %d\n",__FILE__,__FUNCTION__ ,__LINE__);
if(status){ //reset 1 is led light on
*GPIO_BSRR = (1<<(PIN(board_led_res->pin[minor])+16));
} else{ // set 1 is led light off
*GPIO_BSRR = (1<<PIN(board_led_res->pin[minor]));
}
return 0;
}
static struct led_operations chip_led_opr = {
.init = board_led_init ,
.ctl = board_led_ctl,
};
struct led_operations* get_board_led_opr(void )
{
return &chip_led_opr;
}
led_resource.h
#ifndef _LED_RESOURCE_H
#define _LED_RESOURCE_H
/*GPIOA_10 and GPIOG_8
*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))
#define led_max 100
#define ADDRESS_RCC_PLL4CR (0x50000000 + 0x894)
#define ADDRESS_RCC_MP_AHB4ENSETR (0x50000000 + 0xA28)
#define ADDRESS_GPIOA_MODER (0x50002000 + 0x00)
#define ADDRESS_GPIOA_BSRR (0x50002000 + 0x18)
#define ADDRESS_GPIOG_MODER (0x50008000 + 0x00)
#define ADDRESS_GPIOG_BSRR (0x50008000 + 0x18)
struct led_resource{
int pin_num;
int pin[led_max];
};
struct led_resource* get_led_resource(void);
#endif
单板相关 board_MP157_led.c
//set STM32MP_157 gpio resource
#include "led_resource.h"
static struct led_resource MP157_led_res = {
.pin_num = 2,
.pin[0] = GROUP_PIN(0,10),
.pin[1] = GROUP_PIN(6,8),
};
struct led_resource* get_led_resource(void)
{
return &MP157_led_res;
}
应用测试程序
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* you can designer led by yourself
#define file_1 "/dev/led_dev0"
#define file_2 "/dev/led_dev1"
*/
int main(int argc ,char **argv){
char status;
int fd;
if(argc!=3){
printf("Usage :%s <dev> <on | off>\n",argv[0]);
return -1;
}
fd=open(argv[1],O_RDWR);
if(fd<0){
perror("[open file error]");
return -1;
}
if(strncmp("on",argv[2],2)==0){
status = 1;
}else{
status = 0;
}
int res =write(fd,&status,1);
if(res<0){
perror("[write error]");
return -1;
}
close(fd);
return 0;
}
Makefile
ARCH := arm
CROSS_COMPILE := arm-buildroot-linux-gnueabihf-
CFLAGS :=-g -Wall
SOURCE := led_test.c
TARGET := build
OUTPUT := /home/jacky/nfs_rootfs/chardev/led
KERNEL_DIR := /home/jacky/100ask_stm32mp157_pro-sdk/Linux-5.4
TARGET_MODULE := jacky_led.ko
all:
make -C $(KERNEL_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc $(CFLAGS) -o $(TARGET) $(SOURCE)
cp -f $(TARGET) $(TARGET_MODULE) $(OUTPUT)
.PHONY:clean
clean:
make -C $(KERNEL_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -rf $(TARGET)
CONFIG_MODULE_SIG=n
jacky_led-y := led_drv.o chip_gpio.o board_MP157_led.o
obj-m +=jacky_led.o
实验测试
编译
编译成功并成功拷贝到nfs目录下
挂载驱动
挂载网络文件系统
关闭系统默认的绿灯设置(心跳灯)
开始挂载驱动
CONFIG_MODULE_SIG=n
当然,对我们的程序并不会造成太大的影响,你卸载了然后在挂载就不会出现污染内核的警告了
测试应用程序
检查到了我们的设备,下面进行验证我们的应用程序
可以看到我们开发板的灯是完全受到测试程序的控制的