编写LED驱动-传统
Posted lianglianglu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编写LED驱动-传统相关的知识,希望对你有一定的参考价值。
写LED驱动
首先,我们要建立一个file_operator结构体:
/* 定义自己的file_operations结构体 */
static struct file_operations led_drv =
.owner = THIS_MODULE, //主设备号
.open = led_drv_open, // driver function
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
;
接下来要配置GPIO的地址:
/*指定引脚的基地址 */
static unsigned int gpio_base[] =
0x56000000; /*GPIOA*/
0x56000010; /*GPIOB*/
.........;
// 这里是确定GPIOE的第n个引脚
#define IMX6ULL_GPE(n) (5<<16 | n)
//查找GPIO基地址:
编写相应的四个driver function:
static volatile unsigned int *gpio_con; // 将引脚配置为输出引脚
static volatile unsigned int *gpio_dat; // 设置高低电平
static int led_pin = IMX6ULL_GPE(5);
/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
printk("%s %s line %d\\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
int err;
usigned char status;
int pin = pin_bin * 0xffff;
printk("%s %s line %d\\n", __FILE__, __FUNCTION__, __LINE__);
err = copy_from_user(&status, buf, 1); // 将用户空间传入到值传入到kernel
if(err)
return -1;
if(status)
/*点灯*/
*gpio_data &= ~(1<<pin);
else
/*灭灯*/
*gpio_data |= (1<<pin);
return 1;
static int led_drv_open (struct inode *node, struct file *file)
int bank = led_pin >> 16;
int bass = gpio_bas[bank]; // 这里不太清楚宏定义的意思
int pin = led_pin & 0xFFFF;
gpio_con = ioremap(bass, 8); // 在bass基地址映射8(1024),size只映射空间的大小,这里是以page为单位,8<1024,也是一个page的大小
gpio_dat = gpio_con + 1;
*gpio_con |= (1<<pin*2); // 这里是相关寄存器配置,因为要配置GPIO5--[11:10]为output---01
return 0;
static int led_drv_close (struct inode *node, struct file *file)
printk("%s %s line %d\\n", __FILE__, __FUNCTION__, __LINE__);
ioumap(gpio_con)
return 0;
注册filer_operator: 告诉CPU
static int myled_init(void)
major = register_chrdev(0, "myled", &myled_oprs);
return 0;
static void myled_exit(void)
unregister_chrdev(major, "myled");
module_init(myled_init); //这里是为了告诉内核,入口函数是什么
module_exit(myled_exit);
posted @
2023-05-21 13:55 嵌入式小白— 阅读(1) 评论(0) 编辑 收藏 举报
LED驱动代码编写
为了编写LED驱动程序,需要查看开发板的原理图,查看需要配置的引脚。这里LED驱动代码的编写基于100ask-imx6ull mini开发板。
一、查看原理图
由LED硬件图可以得知配置GPIO5_3 输出低电平,LED点亮,GPIO5_3输出高电平,LED熄灭。
具体的寄存器操作应该是怎样的?
1.使能寄存器,但有些芯片的GPIO是没有使能开关的,即它总是使能的。
2.选择引脚功能,一个引脚可以用于GPIO、串口、USB或其他功能,有对应的寄存器来选择引脚的功能。
3.对于已经设置为GPIO功能的引脚,有方向寄存器用来设置它的方向:输出、输入。
4.对于已经设置为GPIO功能的引脚,有数据寄存器用来写、读引脚电平状态。
下面通过查阅手册获取我们想要的寄存器有关信息:
我们想要获取的是寄存器的地址,需要配置哪个寄存器,配置寄存器相应位上的哪个值这些信息。
通过查阅《Chapter 18: Clock Controller Module (CCM)》中的CCM clock tree,获知使能GPIO5需要配置CCGR1寄存器中的CG15
二、查看芯片手册
1.使能寄存器
① 00:该GPIO模块全程被关闭
② 01:该GPIO模块在CPU run mode情况下是使能的;在WAIT或STOP模式下,关闭
③ 10:保留
④ 11:该GPIO模块全程使能
需要配置30和31位为1使能寄存器(默认使能,可以不配置)
2.配置功能选择寄存器
根据GPIO章节提供的框图可知需要去找IOMUXC配置相应寄存器
参考《Chapter 32: IOMUX Controller (IOMUXC)》去配置引脚功能
IOMUXC中有2个寄存器用来设置它,MUX_MODE是配置我们所需要的引脚功能,pad settings是设置上拉电阻这类,暂不用管它。
需要配置IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3寄存器,配置相应位为101.
3.配置方向寄存器
GPIOx_GDIR:设置引脚方向,每位对应一个引脚,1-output,0-input
根据需要要将GPIO5_GDIR第三位配置1,即让GPIO5_3 输出
4.数据寄存器
GPIOx_DR:设置输出引脚的电平,每位对应一个引脚,1-高电平,0-低电平
根据需求配置GPIO5_DR第三位为1或0
5.读寄存器
GPIOx_PSR:读取引脚的电平,每位对应一个引脚,1-高电平,0-低电平
以上的基地址参考《Chapter 2: memory map》
三、编写代码
1.驱动代码的编写
需要注意的是对于寄存器的物理地址需要先映射成虚拟地址,才能使用
编写驱动程序的套路:
① 确定主设备号,也可以让内核分配
② 定义自己的file_operations结构体
③ 实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体
④ 把file_operations结构体告诉内核:register_chrdev
⑤ 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
⑥ 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
⑦ 其他完善:提供设备信息,自动创建设备节点:class_create, device_create
驱动怎么操作硬件?通过ioremap映射寄存器的物理地址得到虚拟地址,读写虚拟地址。
驱动怎么和APP传输数据?通过copy_to_user、copy_from_user这2个函数。
因为编写的是字符设备驱动,可在source insight 的linux源码环境下光标定位到register_chrdev按住ctrl + / 来搜索相应源码来参考。
/*
* led 驱动代码
*/
#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 int led_major;
static struct class *led_class;
// volatile 直接从变量地址中读取数据,不让编译器去优化
// 比如让GPIO5_DR先配置成0再配置成1,反映在硬件上是LED先亮后灭
// 但软件优化的结果可能是直接配置成1
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
static volatile unsigned int *GPIO5_GDIR;
static volatile unsigned int *GPIO5_DR;
static ssize_t led_write(struct file *file, const char __user *buf,size_t count, loff_t * ppos)
/* set gpio to led on */
int ret;
char val;
ret = copy_from_user(&val,buf,1);
if(val)
*GPIO5_DR &= ~(1<<3);
else
*GPIO5_DR |= (1<<3);
return 1;
static int led_open(struct inode *inode, struct file *file)
/*
* enable GPIO5 默认已经使能
* configure GPIO5_3 as gpio
* configure GPIO5_3 as OUTPUT
*/
// 需要清零 因为这里需要配置成101,既有0,也有1
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5;
*GPIO5_GDIR |= (1<<3);
return 0;
static const struct file_operations led_fops =
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
;
static int __init led_init(void)
printk("%s %s line:%d",__FILE__,__FUNCTION__,__LINE__);
led_major = register_chrdev(0, "100ask_led", &led_fops);
/* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 101 2290000h+ 14h = 2290014h
* GPIO5_GDIR 020AC000 + 4h 3
* GPIO5_DR 020AC000
*
* ioremap
* 映射寄存器物理地址 得到虚拟地址
*
*/
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14,4);
GPIO5_GDIR = ioremap(0x020AC000 + 0x04,4);
GPIO5_DR = ioremap(0x020AC000,4);
led_class = class_create(THIS_MODULE, "myled");
device_create(led_class, NULL, MKDEV(led_major, 0),NULL, "myled");
return 0;
static void __exit led_exit(void)
iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
iounmap(GPIO5_GDIR);
iounmap(GPIO5_DR);
device_destroy(led_class,MKDEV(led_major, 0));
class_destroy(led_class);
unregister_chrdev(led_major, "100ask_led");
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
2.测试代码的编写
打开设备节点 /dev/myled,写入0或1
/*
* led 驱动测试代码 上层应用
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
/*
* ./led_drv_test /dev/myled on
* ./led_drv_test /dev/myled off
*/
int main(int argc,char **argv)
char status = 0;
int fd;
if(argc != 3)
printf("Usage:%s /dev/myled 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;
fd = open(argv[1],O_RDWR);
if(fd<0)
printf("can not open\\n");
return -1;
if(strcmp(argv[2],"on")==0)
status = 1;
else if(strcmp(argv[2],"off")==0)
status = 0;
write(fd,&status,1);
return 0;
四、编译测试
Makefile(直接在工作目录下执行make,但注意相应的环境要配好)
KERN_DIR = /home/heavysea/100ask/100ask_imx6ull_mini-sdk/Linux-4.9.88
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o ledtest led_drv_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f ledtest
obj-m += led_drv.o
注意:工作目录文件名不要有多余的空格,否则执行会报错
调整文件名。执行成功
将.ko文件和测试程序拷贝到板子上运行,加载驱动进行测试
echo "7 4 1 7" > /proc/sys/kernel/printk // 打开板子的内核打印信息
其他有关内容参考博文,不再赘述。
以上是关于编写LED驱动-传统的主要内容,如果未能解决你的问题,请参考以下文章
LED驱动代码编写
LED驱动代码编写
LED驱动代码编写
LED驱动代码编写
字符设备驱动程序--LED驱动
Linux LED 驱动开发实验
退出
[Ctrl+Enter快捷键提交]
· 记一次 Visual Studio 2022 卡死分析
· 异常体系与项目实践
· .NET 通过源码深究依赖注入原理
· [趣话计算机底层技术]一个故事看懂各种锁
· 记一次 .NET 某医院门诊软件 卡死分析
· 为什么有了 HTTP 还要 RPC
· 【源码解读】asp.net core源码启动流程精细解读
· 我写了本开源书:《3D编程模式》
· 游戏前端工作流程总结
· IntelliJ IDEA一站式配置【全】(提高开发效率)