驱动开发(四)——点灯大师


前言

经过一晚上的心理挣扎,我选择克服畏难情绪,作为社会主义接班人,怎么能因为这么一点点小小的困难就选择放弃呢?(开个玩笑

我决定,花费一个下午,理清思路,来写一个完完整整的LED驱动程序,就从看芯片原理图数据手册开始。

实现从0到1

项目需求

在以往我们写LED驱动程序的时候,往往是针对某一个LED进行编写驱动,可是为什么我们不能用同一套驱动程序来适配开发板上的所有LED灯呢?

答案是:当然没有问题,我们根据主设备号驱动驱动程序的实现,可以根据此设备号来区分不同的LED,进而初始化相应LED对应的GPIO引脚对应的寄存器。

问题

因此,这里我们就能简单的再向之前一样去写驱动程序了。我们需要先像写单片机程序一样去芯片手册中,寻找该硬件相关的信息,这样才能使得我们的驱动程序能够控制硬件设备

如何根据实际需要查看手册

注意,我这里是基于STM32MP157开发板,其他开发板方法类似

查看原理图

查看原理图的作用就是为了,确定该芯片拥有那些资源!确定设备

搜索LED
在这里插入图片描述在这里插入图片描述

还有一个BLUE LED,这个灯是用于4G模块的,咱们不好直接操控,万一弄坏了就寄了,所以选绿灯和黄灯来作为实现对象吧

查看参考手册

参考手册的作用就是,找该硬件相关的介绍,其作用是什么,为谁服务!

Q:为什么查看数据手册呢?
A:因为我们需要知道控制LED的控制寄存器和数据寄存器地址,以及初始化LED的GPIO时钟,才能达到实现控制LED的目的

在这里插入图片描述
通过查看157开发板的框架图,查看到GPIOA和CPIOG挂在AHB总线上,
但是这对我们好像没啥作用

时钟信息

在参考手册中找到RCC章节
OK,找到了
在这里插入图片描述这不就是找到亲人了

GPIO信息

在这里插入图片描述

查看数据手册

数据手册的作用就是知道了该硬件的作用,我们需要知道该如何对该硬件进行相应功能的配置

搜索PLL4

在这里插入图片描述在这里插入图片描述继续阅读,看看如何控制
在这里插入图片描述查看到使能PLL4,是bit0 and bit1
在这里插入图片描述好,现在时钟已经设置好了,我们需要找到对应的GPIO寄存器来设置

但是对于157开发板而言又很特殊,因为他既有MPU,还有MCU。因此存在一个资源分配的问题
在这里插入图片描述
因此我们需要设置GPIO模块给MPU使用而不是MCU,因此需要使能MPU的GPIO模块寄存器对应的时钟
搜索RCC_MP_AHB

在这里插入图片描述
因此我们需要去使能GPIOA和GPIOG

下面我们来具体看看如何设置某个引脚
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
这里我们就不用去设置上拉还是下拉,因为我们只需要对其进行写,不读引脚
在这里插入图片描述
在这里插入图片描述
不用上面的寄存器的原因是,你再对其操作的时候,不能影响其他位,因此,不需要先读出全部数据,然后再进行修改,再读回去

驱动各个寄存器的基地址
在这里插入图片描述
显然就是这个AHB4了,再详细的确定以下各个分区
在这里插入图片描述
没问题了
确定RCC寄存器的基地址是:0x50000000
确定GPIO寄存器的基地址是:0x50002000

终于可以开始Coding了

头文件

led_option.h

/*在头文件中进行申明,在board.c文件中进行实现,目的就是为了适用于所有的板子*/
#ifndef _LED_OPTION_H
#define _LED_OPTION_H
//申明设置LED操作结构体
struct led_operations {
    int num ;//表示LED的数量
    int (*init)(int which) ;  //初始化LED,which表示那个LED
    int (*ctl)(int which, char status); //控制LED,which表示那个LED,status:1:亮,0:灭
};

//申明一个函数,用于获取led操作结构体地址
struct led_operations *get_board_led_opr(void);
#endif

单板相关代码

/*适配STM32MP157开发板*/
#include "led_option.h"
#include <asm/io.h>
#include <linux/module.h>
#include <linux/init.h>
//set register
//RCC_PLL4 address : 0x50000000 + 0x894
static volatile unsigned int *RCC_PLL4CR;

//RCC_MP_AHB4ENSETR address:0x50000000 + 0xA28
static volatile unsigned int *RCC_MP_AHB4ENSETR;

//GPIOA_MODE address: 0x50002000 + 0x00
static volatile unsigned int *GPIOA_MODER;

//GPIOA_BSRR address: 0x50002000 + 0x18
static volatile unsigned int *GPIOA_BSRR;

//初始化157开发板LED,使能时钟和分配GPIO给MPU
static int MP157_board_led_init(int which)
{

    if(!RCC_PLL4CR){//如果该时钟寄存器未被初始化,这里就进行初始化
        //RCC_PLL4 address : 0x50000000 + 0x894
        RCC_PLL4CR=ioremap(0x50000000+0x894, 4);

        //RCC_MP_AHB4ENSETR address:0x50000000 + 0xA28
        RCC_MP_AHB4ENSETR=ioremap(0x50000000+0xA28,4);

        //GPIOA_MODER address: 0x50002000 + 0x00
        GPIOA_MODER=ioremap(0x50002000 + 0x00,4);

        //GPIOA_BSRR address: 0x50002000 + 0x18
        GPIOA_BSRR=ioremap(0x50002000 + 0x18,4);
    }

    if(which==0){
        /*enable PLL4, it is clock source for all gpio*/
        *RCC_PLL4CR |=(1<<0);
        //确保时钟稳定
        while((*RCC_PLL4CR & (1<<1) )== 0);

        //enable gpioA
        *RCC_MP_AHB4ENSETR |= (1<<0);

        /*
         * configure gpioa10 as gpio
         * configure gpioa10 as output
         * */
        *GPIOA_MODER &=~(3<<20);//先清0
        *GPIOA_MODER |=(1<<20);
    }
    printk("init OK:%s %s line: %d \n",__FILE__,__FUNCTION__ ,__LINE__);
    return 0;
}

static int MP157_board_led_ctl(int which ,char status)
{
    if(which==0){
        printk("%s %s line: %d \n",__FILE__,__FUNCTION__ ,__LINE__);
        if(status){
            *GPIOA_BSRR = (1<<26);
        }else{
            *GPIOA_BSRR = (1<<10);
        }
    }
    return 0;
}

static struct led_operations MP157_board_led_opr = {
        .num  = 1,
        .init = MP157_board_led_init,
        .ctl  = MP157_board_led_ctl,
};

struct led_operations *get_board_led_opr(void)
{
    return &MP157_board_led_opr;
}

驱动源码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/stat.h>
#include <linux/uaccess.h>
#include "led_option.h"

static int major=0;
//allow user to define major for themselves
module_param(major,int ,0);
static struct class *led_class;
struct led_operations *p_led_opr;

//open function
static int led_drv_open(struct inode *node, struct file *fp)
{
    //iminor is to get minor number and is a inline
    int minor = iminor(node);
    printk("%s %s line %d\n",__FILE__,__FUNCTION__ ,__LINE__);
    p_led_opr->init(minor); //init led by minor number
    return 0;
}

//close function
static int led_drv_close(struct inode *node, struct file *fp)
{
    printk("%s %s line %d\n",__FILE__,__FUNCTION__ ,__LINE__);
    return 0;
}
//read function
static ssize_t led_drv_read(struct file *fp, char __user *buffer, size_t len, loff_t *pos)
{
    printk("%s %s line %d\n",__FILE__,__FUNCTION__ ,__LINE__);
    return 0;
}
//write function
static ssize_t led_drv_write(struct file *fp, const char __user *buffer, size_t len, loff_t *pos)
{
    int err;
    char status;
    //get minor number
    struct inode *inode=file_inode(fp);
    int minor = iminor(inode);

    printk("%s %s line %d\n",__FILE__,__FUNCTION__ ,__LINE__);
    err = copy_from_user(&status,buffer,1);

    //control the led by minor number
    p_led_opr->ctl(minor,status);
    return err;
}

//define struct file_operations
struct file_operations led_drv = {
        .owner = THIS_MODULE,
        .open  = led_drv_open,
        .read  = led_drv_read,
        .write = led_drv_write,
        .release = led_drv_close,
};
//let struct file_operations tell to kernel, register driver programmer
//this is a entrance function,install driver will invoke this function
static  int __init led_init(void)
{
    long err;
    int i;

    printk("%s %s line: %d \n",__FILE__,__FUNCTION__ ,__LINE__);
    if(!major){
        major=register_chrdev(0,"my_led",&led_drv);
    }else{
        register_chrdev(major,"my_led",&led_drv);
    }
    //add driver support information
    led_class = class_create(THIS_MODULE,"class_led");
    err=PTR_ERR(led_class);
    if(IS_ERR(led_class)){
        printk("%s %s line %d\n",__FILE__,__FUNCTION__ ,__LINE__);
        unregister_chrdev(major,"my_led");
        return -1;
    }
    p_led_opr =get_board_led_opr();
    for(i=0;i<p_led_opr->num;i++){
        device_create(led_class,NULL,MKDEV(major,i),NULL,"led_dev%d",i);
    }
    return 0;
}
//export function
static  void __exit led_exit(void)
{
    int i;
    printk("%s %s line %d\n",__FILE__,__FUNCTION__ ,__LINE__);
    for(i=0;i<p_led_opr->num;i++){
        device_destroy(led_class,MKDEV(major,i));
    }
    class_destroy(led_class);
    unregister_chrdev(major,"my_led");
    return;
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jacky");


测试代码

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int main(int argc ,char **argv){
    if(argc!=3){
        printf("Usage: %s <dev> <on | off>\n",argv[0]);
        return -1;
    }
    //open device file
    int fd=open(argv[1],O_RDWR);
    if(fd==-1){
        perror("[open file  error]");
        return -1;
    }

    //write to file
    char status;
    if(strcmp(argv[2],"on")==0){
        status=1;
    }else{
        status=0;
    }
    printf("fd = %d\n",fd);
    write(fd,&status,1);
    //close the file
    close(fd);
    return 0;
}


Makedile

ARCH :=arm
CROSS_COMPILE :=arm-buildroot-linux-gnueabihf-
SOURCE := led_app_test.c
LDFLAGS := -g -Wall
TARGET := build
MODULE := jacky_led.ko
OUTPUT := /home/jacky/nfs_rootfs/chardev/led

KERNEL_DIR :=/home/jacky/100ask_stm32mp157_pro-sdk/Linux-5.4

all:
	make -C $(KERNEL_DIR) M=`pwd` modules
	$(CROSS_COMPILE)gcc $(LDFLAGS) -o $(TARGET) $(SOURCE)
	cp -f $(MODULE) $(TARGET) $(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 STM32MP157_board.o
obj-m += jacky_led.o

测试

关闭开发板的心跳灯echo none > /sys/class/leds/heartbeat/trigger
发现开发板的绿灯停止闪烁

挂载驱动并查看是否有对应的设备
在这里插入图片描述测试应用程序
在这里插入图片描述

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jacky~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值