一、LED驱动程序的实现目标及流程图
1、打开LED
2、关闭LED
二、LED驱动程序的实现部分
1、内核层LED驱动程序
2、应用层LED测试程序
三、内核层LED驱动程序的编程步骤
1、添加头文件
2、确定主设备号,也可以让内核分配
3、定义自己的 file_operations 结构体
4、实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
4.1 led_drv_open函数
4.1.1使能时钟
4.1.2使能GPIO
4.1.3配置IO引脚
4.1.4配置IO引脚方向
4.2 led_drv_write函数
4.2.1获取应用层的数据
4.2.3GPIO输出
4.3 把 file_operations 结构体告诉内核:register_chrdev.
4.4 创建类class_create
4.5 创建设备device_create.
5、实现入口函数:安装驱动程序时,就会去调用这个入口函数,执行工作:
5.1 ioremap虚拟地址映射物理地址
5.2 把 file_operations 结构体告诉内核:register_chrdev.
5.3 创建类class_create.
5.4 创建设备device_create.
6、实现出口函数:卸载驱动程序时,就会去调用这个入口函数,执行工作:
6.1 iounmap释放虚拟地址
6.2 把 file_operations 结构体从内核注销:unregister_chrdev.
6.3 销毁类class_create.
6.4 销毁设备结点class_destroy.
7、其他完善:GPL协议、驱动作者、驱动名称
四、具体实现
1、内核层LED驱动程序
//1、添加头文件
#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>
//函数声明
static ssize_t led_drv_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos);
static ssize_t led_drv_write(struct file *file, const char __user *buffer,size_t count, loff_t *ppos);
static int led_drv_open(struct inode *inode, struct file *filp);
static int __init led_init(void);
static void __exit led_exit(void);
/*registers*/
// RCC_PLL4CR地址:0x50000000 + 0x894
static volatile unsigned int *RCC_PLL4CR;
// RCC_MP_AHB4ENSETR 地址:0x50000000 + 0xA28
static volatile unsigned int *RCC_MP_AHB4ENSETR;
// GPIOA_MODER 地址:0x50002000 + 0x00
static volatile unsigned int *GPIOA_MODER;
// GPIOA_BSRR 地址: 0x50002000 + 0x18
static volatile unsigned int *GPIOA_BSRR;
// led类
static struct class *led_class;
//2、确定主设备号,也可以让内核分配
static int major = 0;
//3、定义自己的 file_operations 结构体
static const struct file_operations my_fops = {
.read = led_drv_read,
.write = led_drv_write,
.open = led_drv_open,
.owner = THIS_MODULE,
};
//4、实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
static ssize_t led_drv_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
return 0;
}
static ssize_t led_drv_write(struct file *file, const char __user *buffer,size_t count, loff_t *ppos)
{
char val;
/*1、get data from app*/
/*copy_from_user(void * to, const void __user * from, unsigned long n):get form app*/
copy_from_user(&val,buffer,1);
/*2、set register out 1/0*/
if(val){
/*set gpio to let led on*/
*GPIOA_BSRR = (1<<26);
}else{
/*set gpio to let led off*/
*GPIOA_BSRR = (1<<10);
}
return 1;
}
static int led_drv_open(struct inode *inode, struct file *filp)
{
/*1、enalbe PLL4,it is clock source for all gpio*/
*RCC_PLL4CR|= (1<<0);
while((*RCC_PLL4CR & (1<<1)) == 0)
/*2、enable gpio*/
*RCC_MP_AHB4ENSETR |= (1<<0);
/*3、configure gpA10 as gpio*/
*GPIOA_MODER &= ~(3<<20);
/* 4、configure gpio as output*/
*GPIOA_MODER |= (1<<20);
return 0;
}
//5、实现入口函数:安装驱动程序时,就会去调用这个入口函数,执行工作:
// (1)把 file_operations 结构体告诉内核:register_chrdev.
// (2)创建类class_create.
// (3)创建设备device_create.
static int __init led_init(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0,"my_led",&my_fops);
/*虚拟地址映射物理地址*/
//ioremap(phys_addr_t phys_addr, size_t size)
// RCC_PLL4CR地址:0x50000000 + 0x894
RCC_PLL4CR = ioremap(0x50000000 + 0x894,4);
// RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28
RCC_MP_AHB4ENSETR = ioremap(0x50000000 + 0xA28,4);
// GPIOA_MODER地址:0x50000000 + 0x00
GPIOA_MODER = ioremap(0x50000000 + 0x00,4);
// GPIOA_BSRR地址:0x50000000 + 0x18
GPIOA_BSRR = ioremap(0x50000000 + 0x18,4);
led_class = class_create(THIS_MODULE, "myled");
/*创建/dev/myled*/
device_create(led_class, NULL, MKDEV(major, 0),NULL,"myled");
return 0;
}
//6、实现出口函数:卸载驱动程序时,就会去调用这个入口函数,执行工作:
// (1)把 file_operations 结构体从内核注销:unregister_chrdev.
// (2)销毁类class_create.
// (3)销毁设备结点class_destroy.
static void __exit led_exit(void)
{
// RCC_PLL4CR地址:0x50000000 + 0x894
iounmap(RCC_PLL4CR);
// RCC_MP_AHB4ENSETR地址:0x50000000 + 0xA28
iounmap(RCC_MP_AHB4ENSETR);
// GPIOA_MODER地址:0x50000000 + 0x00
iounmap(GPIOA_MODER);
// GPIOA_BSRR地址:0x50000000 + 0x18
iounmap(GPIOA_BSRR);
//销毁类
class_destroy(led_class);
//销毁设备
device_destroy(led_class, MKDEV(major, 0));
//注销驱动
unregister_chrdev(major,"my_led");
}
//修饰入口、出口函数
module_init(led_init);
module_exit(led_exit);
//7、其他完善:GPL协议、驱动作者、驱动名称
MODULE_AUTHOR("aipolo <369480046@qq.com>");
MODULE_DESCRIPTION("led Driver");
MODULE_LICENSE("GPL");
2、应用层LED测试程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
//ledtest /dev/myled on
int main(int argc, char **argv)
{
int fd;
char status = 0;
if(argc != 3){
printf("Usage: %s <dev> <on|off>\n",argv[0]);
printf("eg:%s /dev/myled on\n",argv[0]);
printf("eg:%s /dev/myled off\n",argv[0]);
return -1;
}
//open
fd = open(argv[1],O_RDWR);
if(fd < 0){
printf("open %s error\n",argv[0]);
return -1;
}
//write if是on,status = 1;if是off,status = 0;
if(strcmp(argv[2],"on") == 0){
printf("open led\n");
status = 1;
}
write(fd,&status,1);
}
3、Makefile
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_stm32mp157_pro-sdk/ToolChain/armbuildroot-linux-gnueabihf_sdk-buildroot/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
/*内核存放地址*/
KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o ledtest ledtest.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f ledtest
obj-m += led_my.o
五、实验结果
1、配置环境变量
export ARCH=arm64
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_stm32mp157_pro-sdk/ToolChain/armbuildroot-linux-gnueabihf_sdk-buildroot/bin
2、装载驱动
insmod /mnt/my_led.ko
3、查看设备
4、实现
LED开
LED关
5、卸载驱动
rmmod led_my