使用“新字符设备的一般模板”编写LED驱动,使用寄存器直接开关灯。
1、创建LED目录
输入“cd /home/zgq/linux/Linux_Drivers/回车”
切换到“/home/zgq/linux/Linux_Drivers/”
输入“ls回车”,查看“/home/zgq/linux/Linux_Drivers/”
输入“mkdir MyNewLED回车”,创建“MyNewLED”目录
输入“ls回车”,查看“/home/zgq/linux/Linux_Drivers/”
2、LED.c文件如下:
#include "LED.h"
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/gpio.h>
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
/*RCC_MP_AHB4ENSETR寄存器*/
static void __iomem *GPIOI_MODER_PI; /*GPIOx_MODER寄存器,x=A to K, Z*/
static void __iomem *GPIOI_OTYPER_PI;/*GPIOx_OTYPER,x=A to K,Z*/
static void __iomem *GPIOI_OSPEEDR_PI;/*GPIOx_OSPEEDR,x=A to K, Z*/
static void __iomem *GPIOI_PUPDR_PI; /*GPIOx_PUPDR,x=A to K, Z*/
static void __iomem *GPIOI_BSRR_PI;/*GPIOx_BSRR,x=A to K, Z*/
void led_ioremap(void);
void led_iounmap(void);
void led_Pin_Init(void);
void led_switch(u8 sta);
/* 寄存器地址映射 */
void led_ioremap(void)
{
MPU_AHB4_PERIPH_RCC_PI = ioremap(RCC_MP_AHB4ENSETR, 4);
GPIOI_MODER_PI = ioremap(GPIOI_MODER, 4);
GPIOI_OTYPER_PI = ioremap(GPIOI_OTYPER, 4);
GPIOI_OSPEEDR_PI = ioremap(GPIOI_OSPEEDR, 4);
GPIOI_PUPDR_PI = ioremap(GPIOI_PUPDR, 4);
GPIOI_BSRR_PI = ioremap(GPIOI_BSRR, 4);
}
/*取消“寄存器地址映射”*/
void led_iounmap(void)
{
iounmap(MPU_AHB4_PERIPH_RCC_PI);
iounmap(GPIOI_MODER_PI);
iounmap(GPIOI_OTYPER_PI);
iounmap(GPIOI_OSPEEDR_PI);
iounmap(GPIOI_PUPDR_PI);
iounmap(GPIOI_BSRR_PI);
}
void led_Pin_Init(void)
{
u32 val = 0;
/* 2、使能RCC时钟 */
val = readl(MPU_AHB4_PERIPH_RCC_PI);/* 读RCC_MP_AHB4ENSETR寄存器 */
val &= ~(0X1 << 8);/* 清除以前的bit8设置 */
val |= (0X1 << 8); /* 设置新的bit8值 */
writel(val, MPU_AHB4_PERIPH_RCC_PI);
/* 将val的值写入RCC_MP_AHB4ENSETR寄存器 */
/* 3、将PI0输出引脚。*/
val = readl(GPIOI_MODER_PI);/*读GPIOI_MODER寄存器*/
val &= ~(0X3 << 0); /* bit0:1清零 */
val |= (0X1 << 0); /* bit0:1设置01,配置为输出模式 */
writel(val, GPIOI_MODER_PI);
/* 将val的值写入GPIOI_MODER寄存器 */
/* 4、设置PI0为推挽模式 */
val = readl(GPIOI_OTYPER_PI);/*读GPIOI_OTYPER寄存器*/
val &= ~(0X1 << 0); /* bit0清零,设置为上拉*/
writel(val, GPIOI_OTYPER_PI);
/* 将val的值写入GPIOI_OTYPER寄存器 */
/* 5、设置PI0为极高速 */
val = readl(GPIOI_OSPEEDR_PI);/*读GPIOI_OSPEEDR寄存器*/
val &= ~(0X3 << 0); /* bit0:1 清零 */
val |= (0x3 << 0); /* bit0:1 设置为11,极高速*/
writel(val, GPIOI_OSPEEDR_PI);
/* 将val的值写入GPIOI_OSPEEDR寄存器 */
/* 6、设置PI0为上拉。*/
val = readl(GPIOI_PUPDR_PI);/*读GPIOI_PUPDR寄存器*/
val &= ~(0X3 << 0); /* bit0:1 清零*/
val |= (0x1 << 0); /*bit0:1 设置为01,配置为上拉*/
writel(val,GPIOI_PUPDR_PI);
/* 将val的值写入GPIOI_PUPDR寄存器 */
/* 6、默认打开LED,PI0=0 */
val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/
val &= ~(0X1 << 16); /* bit16 清零*/
val |= (0x1 << 16); /*bit16 设置为1,令PI0输出低电平*/
writel(val, GPIOI_BSRR_PI);
/* 将val的值写入GPIOI_BSRR寄存器 */
/* 6、默认关闭LED,PI0=1 */
val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/
val &= ~(0X1 << 0); /* bit0 清零*/
val |= (0x1 << 0);/*bit0 设置为1,令PI0输出高电平*/
writel(val, GPIOI_BSRR_PI);
/* 将val的值写入GPIOI_BSRR寄存器 */
}
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/
val &= ~(0X1 << 16); /* bit16 清零*/
val |= (0x1 << 16); /*bit16 设置为1,令PI0输出低电平*/
writel(val, GPIOI_BSRR_PI);
/* 将val的值写入GPIOI_BSRR寄存器 */
}
else if(sta == LEDOFF) {
val = readl(GPIOI_BSRR_PI);/*读GPIOI_BSRR寄存器*/
val &= ~(0X1 << 0); /* bit0 清零*/
val |= (0x1 << 0);/*bit0 设置为1,令PI0输出高电平*/
writel(val, GPIOI_BSRR_PI);
/* 将val的值写入GPIOI_BSRR寄存器 */
}
}
3、LED.h文件如下:
#ifndef __LED_H
#define __LED_H
#include <linux/types.h>
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* 寄存器物理地址 */
#define PERIPH_BASE (0x40000000)
#define MPU_AHB4_PERIPH_BASE (PERIPH_BASE + 0x10000000)
#define RCC_BASE (MPU_AHB4_PERIPH_BASE + 0x0000)
#define RCC_MP_AHB4ENSETR (RCC_BASE + 0XA28)
#define GPIOI_BASE (MPU_AHB4_PERIPH_BASE + 0xA000)
#define GPIOI_MODER (GPIOI_BASE + 0x0000)
#define GPIOI_OTYPER (GPIOI_BASE + 0x0004)
#define GPIOI_OSPEEDR (GPIOI_BASE + 0x0008)
#define GPIOI_PUPDR (GPIOI_BASE + 0x000C)
#define GPIOI_BSRR (GPIOI_BASE + 0x0018)
extern void led_ioremap(void);
extern void led_iounmap(void);
extern void led_Pin_Init(void);
extern void led_switch(u8 sta);
#endif
4、LEDInterface.c文件如下:
#include "LED.h"
#include <linux/types.h>
//数据类型重命名
//使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
//使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/cdev.h> //使用字符设备结构
#include <linux/mdev.h>
#define MyNewLED_CNT 1 //定义设备数量为1
#define MyNewLED_NAME "MyNewLEDName"//定义设备的名字
struct MyNewLED_dev{
dev_t devid; /*声明32位变量devid用来给保存设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct cdev cdev; /*字符设备结构变量cdev */
struct class *class; /* 类 */
struct device *device;/*设备*/
};
struct MyNewLED_dev strMyNewLED;
/* 打开设备 */
static int MyNewLED_open(struct inode *inode, struct file *filp)
{
/* 用户实现具体功能 */
printk("MyNewLED_open!\r\n");
return 0;
}
/* 从设备读取数据,保存到首地址为buf的数据块中,长度为cnt个字节 */
//file结构指针变量flip表示要打开的设备文件
//buf表示用户数据块的首地址
//cnt表示用户数据的长度,单位为字节
//loff_t结构指针变量offt表示“相对于文件首地址的偏移”
static ssize_t MyNewLED_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
/* 向设备写数据,将数据块首地址为buf的数据,长度为cnt个字节,发送给用户 */
//file结构指针变量flip表示要打开的设备文件
//buf表示用户数据块的首地址
//cnt表示用户数据的长度,单位为字节
//loff_t结构指针变量offt表示“相对于文件首地址的偏移”
static ssize_t MyNewLED_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char databuf[1];
unsigned char ledstat;
ret = copy_from_user(databuf, buf, cnt);
if(ret <0){
printk("kernel write failed!\r\n");
ret = -EFAULT;
}
ledstat = databuf[0];/*获取到应用传递进来的开关灯状态*/
led_switch(ledstat);/*执行开灯或执行关灯*/
return ret;
}
/* 关闭/释放设备 */
static int MyNewLED_release(struct inode *inode, struct file *filp)
{
/* 用户实现具体功能 */
printk("MyNewLED_release!\r\n");
return 0;
}
/*声明file_operations结构变量MyCharDevice_fops*/
/*它是指向设备的操作函数集合变量*/
const struct file_operations MyNewLED_fops = {
.owner = THIS_MODULE,
.open = MyNewLED_open,
.read = MyNewLED_read,
.write = MyNewLED_write,
.release = MyNewLED_release,
};
/*驱动入口函数 */
static int __init MyNewLED_init(void)
{
int ret;
/* 1、寄存器地址映射 */
led_ioremap();//寄存器地址映射
led_Pin_Init();//LED灯引脚初始化
/*2、申请设备号*/
strMyNewLED.major=0;
if(strMyNewLED.major)/*如果指定了主设备号*/
{
strMyNewLED.devid = MKDEV(strMyNewLED.major, 0);
//输入参数strMyNewLED.major为“主设备号”
//输入参数0为“次设备号”,大部分驱动次设备号都选择0
//将strMyNewLED.major左移20位,再与0相或,就得到“Linux设备号”
ret=register_chrdev_region(strMyNewLED.devid, MyNewLED_CNT, MyNewLED_NAME);
//strMyNewLED.devid表示起始设备号
//MyNewLED_CNT表示次设备号的数量
//MyNewLED_NAME表示设备名
if(ret < 0)
goto fail_map;
}
else
{ /* 没有定义设备号 */
ret=alloc_chrdev_region(&strMyNewLED.devid, 0, MyNewLED_CNT,MyNewLED_NAME);
/* 申请设备号 */
//strMyNewLED.devid:保存申请到的设备号
//0:次设备号的起始地址
//MyNewLED_CNT:要申请的次设备号数量;
//MyNewLED_NAME:表示“设备名字”
if(ret < 0)
goto fail_map;
strMyNewLED.major = MAJOR(strMyNewLED.devid);
/* 获取分配号的主设备号 */
//输入参数strMyNewLED.devid为“Linux设备号”
//将strMyNewLED.devid右移20位得到“主设备号”
strMyNewLED.minor = MINOR(strMyNewLED.devid);
/* 获取分配号的次设备号 */
//输入参数strMyNewLED.devid为“Linux设备号”
//将strMyNewLED.devid与0xFFFFF相与后得到“次设备号”
}
/*3、注册字符设备*/
strMyNewLED.cdev.owner = THIS_MODULE;
//使用THIS_MODULE将owner指针指向当前这个模块
cdev_init(&strMyNewLED.cdev,&MyNewLED_fops);
//注册字符设备,初始化“字符设备结构变量strMyNewLED.cdev”
//strMyNewLED.cdev是等待初始化的结构体变量
//MyNewLED_fops就是字符设备文件操作函数集合
/*4、添加字符设备*/ ret=cdev_add(&strMyNewLED.cdev,strMyNewLED.devid,MyNewLED_CNT);
//添加字符设备
/*&strMyNewLED.cdev表示指向要添加的字符设备,即字符设备结构strMyNewLED.cdev变量*/
//strMyNewLED.devid表示设备号
//MyNewLED_CNT表示需要添加的设备数量
if(ret < 0 ) //添加字符设备失败
goto del_register;
printk("dev id major = %d,minor = %d\r\n", strMyNewLED.major, strMyNewLED.minor);
printk("MyNewLED_init is ok!!!\r\n");
/*5、自动创建设备节点 */
strMyNewLED.class =class_create(THIS_MODULE, MyNewLED_NAME);
if (IS_ERR(strMyNewLED.class)){
goto del_cdev;
}
/*6、创建设备 */
strMyNewLED.device = device_create(strMyNewLED.class, NULL, strMyNewLED.devid, NULL, MyNewLED_NAME);
//创建设备
//设备要创建在strMyNewLED.class类下面
//NULL表示没有父设备
//strMyNewLED.devid是设备号;
//参数drvdata=NULL,设备没有使用数据
//MyNewLED_NAME是设备名字
//如果设置fmt=MyNewLED_NAME 的话,就会生成/dev/MyNewLED_NAME设备文件。
//返回值就是创建好的设备。
if (IS_ERR(strMyNewLED.device)){
goto destroy_class;
}
return 0;
destroy_class:
class_destroy(strMyNewLED.class);
//删除类
//strMyNewLED.class就是要删除的类
del_cdev:
cdev_del(&strMyNewLED.cdev);
//删除字符设备
//&strMyNewLED.cdev表示指向需要删除的字符设备,即字符设备结构strMyNewLED.cdev变量
del_register:
unregister_chrdev_region(strMyNewLED.devid, MyNewLED_CNT);
/* 释放设备号 */
//strMyNewLED.devid:需要释放的起始设备号
//MyNewLED_CNT:需要释放的次设备号数量;
fail_map://申请设备号失败
/*若有释放的内存,则释放内存*/
led_iounmap();
return -EIO;
}
/*驱动出口函数 */
static void __exit MyNewLED_exit(void)
{
/*1、释放内存*/
led_iounmap();
/*2、 释放设备号 */
unregister_chrdev_region(strMyNewLED.devid, MyNewLED_CNT);
/*释放设备号 */
//strMyNewLED.devid:需要释放的起始设备号
//MyNewLED_CNT:需要释放的次设备号数量;
/*3、删除字符设备*/
cdev_del(&strMyNewLED.cdev);
/*删除字符设备*/
/*&strMyNewLED.cdev表示指向需要删除的字符设备,即字符设备结构strMyNewLED.cdev变量*/
/*4、 删除设备 */
device_destroy(strMyNewLED.class, strMyNewLED.devid);
//删除创建的设备
//newchrled.class是要删除的设备所处的类
//newchrled.devid是要删除的设备号
/*5、删除类*/
class_destroy(strMyNewLED.class);
//删除类
//strMyNewLED.class就是要删除的类
}
module_init(MyNewLED_init);
//指定MyNewLED_init()为驱动入口函数
module_exit(MyNewLED_exit);
//指定MyNewLED_exit()为驱动出口函数
MODULE_AUTHOR("Zhanggong");//添加作者名字
MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”
MODULE_INFO(intree,"Y");
//去除显示“loading out-of-tree module taints kernel.”
5、LED_APP.c文件如下:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
//APP运行命令:./LED_APP filename <1>|<0>如果是1表示打开LED,如果是0表示关闭LED
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/*
参数argc: argv[]数组元素个数
参数argv[]:是一个指针数组
返回值: 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3)
{
printf("Error Usage!\r\n");
return -1;
}
//argv[]是指向输入参数“./LED_App” “/dev/LMyNewLEDName” “1”
filename = argv[1];
//argv[1]指向字符串“/dev/MyNewLEDName”
fd = open(filename, O_RDWR);
//如果打开“/dev/MyNewLEDName”文件成功,则fd为“文件描述符”
//fd=0表示关灯; fd=1表示开灯;
if(fd < 0)
{
printf("Can't open file %s\r\n", filename);
return -1;
}
databuf[0]= atoi(argv[2]); /* 写入的数据,是数字的,表示打开或关闭 */
retvalue = write(fd, databuf, 1);
//将databuf[]中前1个字节发送给用户
//返回值大于0表示写入的字节数;
//返回值等于0表示没有写入任何数据;
//返回值小于0表示写入失败
if(retvalue < 0)
{
printf("write file %s failed!\r\n", filename);
close(fd);
//fd表示要关闭的“文件描述符”
//返回值等于0表示关闭成功
//返回值小于0表示关闭失败
return -1;
}
/* 关闭设备 */
retvalue = close(fd);
//fd表示要关闭的“文件描述符”
//返回值等于0表示关闭成功
//返回值小于0表示关闭失败
if(retvalue < 0)
{
printf("Can't close file %s\r\n", filename);
return -1;
}
return 0;
}
6、Makefile文件如下:
KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31
#使用“:=”将其后面的字符串赋值给KERNELDIR
CURRENT_PATH := $(shell pwd)
#采用“shell pwd”获取当前打开的路径
#使用“$(变量名)”引用“变量的值”
MyAPP := LED_APP
MyNewLED_Module-objs = LEDInterface.o LED.o
obj-m := MyNewLED_Module.o
CC := arm-none-linux-gnueabihf-gcc
drv:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
app:
$(CC) $(MyAPP).c -o $(MyAPP)
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
rm $(MyAPP)
install:
sudo cp *.ko $(MyAPP) /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -f
7、添加“c_cpp_properties.json”
按下“Ctrl+Shift+P”,打开VSCode控制台,然后输入“C/C++:Edit Configurations(JSON)”,打开以后会自动在“.vscode ”目录下生成一个名为“c_cpp_properties.json” 的文件。
修改c_cpp_properties.json内容如下所示:
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31",
"/home/zgq/linux/Linux_Drivers/MyNewLED",
"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include",
"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include",
"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include/generated"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "gnu11",
"cppStandard": "gnu++14",
"intelliSenseMode": "gcc-x64"
}
],
"version": 4
}
8、编译
输入“make clean回车”
输入“make drv回车”
输入“make app回车”
输入“make install回车”
输入“ls /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -l回车”产看是存在“LED_APP和MyNewLED_Module.ko”
10、测试
启动开发板,从网络下载程序
输入“root”
输入“cd /lib/modules/5.4.31/回车”
切换到“/lib/modules/5.4.31/”目录
注意:“lib/modules/5.4.31/”在虚拟机中是位于“/home/zgq/linux/nfs/rootfs/”目录下,但在开发板中,却是位于根目录中。
输入“ls -l”查看“MyNewLED_Module.ko和LED_APP”是否存在
输入“depmod”,驱动在第一次执行时,需要运行“depmod”
输入“modprobe MyNewLED_Module.ko”,加载“MyNewLED_Module.ko”模块
输入“lsmod”查看有哪些驱动在工作
输入“ls /dev/MyNewLEDName -l回车”,发现节点文件“/dev/MyNewLEDName”
输入“./LED_APP /dev/MyNewLEDName 1回车”执行开灯
输入“./LED_APP /dev/MyNewLEDName 0回车”执行关灯
输入“rmmod MyNewLED_Module.ko”,卸载“MyNewLED_Module.ko”模块
注意:输入“rmmod MyNewLED_Module”也可以卸载“MyNewLED_Module.ko”模块
输入“lsmod”查看有哪些驱动在工作。
输入“ls /dev/MyNewLEDName -l回车”,查询节点文件“/dev/MyNewLEDName”是否存在