前言
经过一晚上的心理挣扎,我选择克服畏难情绪
,作为社会主义接班人,怎么能因为这么一点点小小的困难就选择放弃
呢?(开个玩笑
)
我决定,花费一个下午,理清思路,来写一个完完整整的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
发现开发板的绿灯停止闪烁
挂载驱动并查看是否有对应的设备
测试应用程序