嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十五)最简单的LED驱动程序

简介: 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十五)最简单的LED驱动程序

1.写LED驱动程序


1.1第一步 先看原理图

1670916733815.jpg


1.2第二步 再看芯片手册:使能GPIO

1670916743334.jpg


1.3 第三步 设置引脚是GPIO功能

1670916755533.jpg


1.4 第四步 设置引脚是输出

1670916765570.jpg


1.5 第五步 设置输出电平

1670916775437.jpg


2.最简单的LED驱动程序


后面的LED驱动程序为了容易扩展,引入了很多数据结构。对C语言的要求有点高,所以我们基于Hello驱动程序先写出最简单的LED驱动程序。


2A.1 LED操作方法_基于IMX6ULL


视频中的文档,放在GIT仓库中:

01_all_series_quickstart\
05_嵌入式Linux驱动开发基础知识\doc_pic\pic\6A.最简单的LED驱动程序\
    03_IMX6ULL的LED操作方法.pptx


2A.2 最简单的LED驱动程序编程_基于IMX6ULL


视频中的源码文档,放在GIT仓库中:

01_all_series_quickstart\
05_嵌入式Linux驱动开发基础知识\source\
    02_led_drv\
00_led_simple\imx6ull


2A.2.1 字符设备驱动程序框架


字符设备驱动程序的框架

1670916828822.jpg

编写驱动程序的套路:

① 确定主设备号,也可以让内核分配

② 定义自己的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个函数。


下面就是写驱动程序的模板!!

********************************************************
//这个是驱动程序的框架,以后可以直接套用,在上面添加和修改
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <asm/io.h>
/*定义主设备号*/
static struct major; 
/*定义led class类*/
static struct class *led_class;
/*结构体中led_open函数*/
static int led_open(struct inode *inode, struct file *file) 
{
  /*
  *enable gpio
  *configure pin as gpio
  *configure gpio as output
  */
  return 0;
}
/*结构体中led_write函数*/ 
static ssize_t led_write(struct file *file ,const char __user * buf, size_t count, loff_t *ppos)
{
  char val;//val来自内核定义
  /*copy_from_user:get data from app,会涉及到寄存器,那么涉及到物理层,需要用到虚拟地址*/
  copy_from_user(&val, buf, 1);//把app(buf)中的数据拷贝至内核空间(val),拷贝一个字节。
  /*to set gpio register :out 1/0,会涉及到寄存器,那么涉及到物理层,需要用到虚拟地址*/
  if(val)
  {
  /*set gpio to let led on */ 
  }
  else
  {
  /*set gpio to let led off */  
  }
  return 0;
}
/*定义结构体*/
static struct file_operations led_fops ={
  .owner = THIS_MODULE,
    .open = led_open,
  .write = led_write,
};
/*入口函数*/
static int __init  led_init(void)
{
  printk("%s %s %d \n",_File_,_FUNCTION_,_LINE_);
  major=register_chrdev(0, "100ask_led", &led_fops);//unsigned int major;设置为0.const char * name设置为“100ask_led”
  /*ioremap*/
  led_class=class_create(THIS_MODULE, "myled");//这个变量的结构体名字被称为class,给这个模块创造一个名字为class类的结构。
  device_create(led_class, NULL, MKDEV(major, 0),NULL, "myled");/**设备就会给我们创建一个/dev/myled设备结点。*/
  return 0; 
}
/*出口函数*/
static void  led_exit(void)
{
  device_destroy(led_class, NULL);
  class_destroy(led_class);
  unregister_chrdev(major,"100ask_led" )
}
/*完善入口函数*/
module_init(led_init);
/*完善出口函数*/
module_exit(led_init);
/*设置为GPL协议*/
MODULE_LICENSE("GPL");//指定GPL协议


2A.2.2 实现什么功能


先编写驱动程序:

实现led_open函数,在里面初始化LED引脚。 实现led_write函数,在里面根据APP传来的值控制LED。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <asm/io.h>
/*定义主设备号*/
static struct major; 
/*定义led class类*/
static struct class *led_class;
/*registers*/
// IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14
static volatile unsigned int*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
// GPIO5_GDIR地址:0x020AC004
static volatile unsigned int*GPIO5_GDIR;
//GPIO5_DR地址:0x020AC000
static volatile unsigned int*GPIO5_DR;
/*结构体中led_open函数*/
static int led_open(struct inode *inode, struct file *file) 
{
  /*
  *enable gpio
  *configure gpio5_3 as gpio
  *configure gpio5_3 as output
  */
  //把引脚配置成gpio
  *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
  *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= ~0x05;
  //把引脚配置成输出
  *GPIO5_GDIR |=(1<<3);
  return 0;
}
/*结构体中led_write函数*/ 
static ssize_t led_write(struct file *file ,const char __user * buf, size_t count, loff_t *ppos)
{
  char val;//val来自内核定义
  /*copy_from_user:get data from app,会涉及到寄存器,那么涉及到物理层,需要用到虚拟地址*/
  copy_from_user(&val, buf, 1);//把app(buf)中的数据拷贝至内核空间(val),拷贝一个字节。
  /*to set gpio register :out 1/0,会涉及到寄存器,那么涉及到物理层,需要用到虚拟地址*/
  if(val)
  {
  /*set gpio to let led on */ 
  *GPIO5_DR &=~(1<<3);
  }
  else
  {
  /*set gpio to let led off */
  *GPIO5_DR &=(1<<3);
  }
  return 0;
}
/*定义结构体*/
static struct file_operations led_fops ={
  .owner = THIS_MODULE,
    .open = led_open,
  .write = led_write,
};
/*入口函数*/
static int __init  led_init(void)
{
  printk("%s %s %d \n",_File_,_FUNCTION_,_LINE_);
  major=register_chrdev(0, "100ask_led", &led_fops);//unsigned int major;设置为0.const char * name设置为“100ask_led”
  /*ioremap*/
  // IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14
  IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3=ioremap (0x02290000 + 0x14, 4);
  // GPIO5_GDIR地址:0x020AC004
  GPIO5_GDIR=ioremap (0x020AC004 + 0x14, 4);
  //GPIO5_DR地址:0x020AC000
  GPIO5_DR=ioremap (0x020AC000 + 0x14, 4);
  static inline void __iomem * ioremap (unsigned long offset, unsigned long size)
  {
  return __ioremap(offset, size, 0);
  }
  led_class=class_create(THIS_MODULE, "myled");//这个变量的结构体名字被称为class,给这个模块创造一个名字为class类的结构。
  device_create(led_class, NULL, MKDEV(major, 0),NULL, "myled");/**设备就会给我们创建一个/dev/myled设备结点。*/
  return 0; 
}
/*出口函数*/
static void  led_exit(void)
{
  device_destroy(led_class, NULL)
  class_destroy(led_class);
  unregister_chrdev(major,"100ask_led" )
}
/*完善入口函数*/
module_init(led_init);
/*完善出口函数*/
module_exit(led_init);
/*设置为GPL协议*/
MODULE_LICENSE("GPL");//指定GPL协议


再编写测试程序。

//ledtest /dev/myled on
//ledtest /dev/myled off
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(int argc,char **argc)
{
  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);//open函数怎么使用呢,打开虚拟机远程登录。man 2 open, 看参数和头文件 
  if(fd<0)
  {
  printf("can not open %s\n ",argv[0]);
  return -1;
  }
  // write
  if(strcmp(argv[2],"on")==0)
  {
    status =1;
  }
  write(fd,&status,1);//把status写入fd,写一个字节
  return 0;
  //write
}


最后编写makefile

KERN_DIR=/home/book/100ask_imx6ull-sdk/linux-4.9.88
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 hello_drv_test
obj -m +=led_drv.o


2A.2.3 上机实验


编译过程实践看韦东山老师的开发手册


先设置工具链,参考第2篇第八章的内容。 再编译程序,把代码上传代服务器后执行make命令。没有问题后。

接着在开发板上挂载NFS,参考第2篇第六章的内容。 最后在开发板上加载驱动程序,执行测试程序,如下: echo "7 4 1 7" > /proc/sys/kernel/printk // 打开内核的打印信息,有些板子默认打开了

insmod /mnt/led_drv.ko
/mnt/ledtest /dev/myled on                      // 点灯
/mnt/ledtest /dev/myled off                     // 关灯
相关文章
|
4天前
|
人工智能 自然语言处理 开发者
HarmonyOS NEXT~鸿蒙开发利器:CodeGenie AI辅助编程工具全面解析
鸿蒙开发迎来新利器!DevEco CodeGenie 是华为推出的 AI 辅助编程工具,专为 HarmonyOS NEXT 开发者设计。它具备智能代码生成(支持 ArkTS 和 C++)、精准知识问答以及万能卡片生成三大核心功能,大幅提升编码效率。通过与 DeepSeek 深度整合,CodeGenie 实现流畅的问答体验,帮助开发者解决技术难题。无论是新手还是资深开发者,都能从中受益,享受更智能高效的开发过程。快来体验吧!
33 5
|
13天前
|
安全 索引
鸿蒙开发:如何更新对象数组
关于对象数组中的数据更新,目前例举了三种方式,一种是传统的装饰器方式,另外两种是针对数据源进行操作,数据源直接赋值的方式,适合简单、高频的单元素修改,性能最优且类型安全,而splice方法适合复杂操作或需保持引用稳定的场景,但需注意性能损耗,在实际的开发中可以根据需求,选择自己适合的方式。
81 34
|
10天前
|
JavaScript 安全 开发者
鸿蒙开发:如何解决软键盘弹出后的间距
三种方式,比较推荐方式一,简单便捷,一行代码便可以搞定,当然,另外两种也是实现的办法,在实际的开发中,选择适合的即可。
41 14
鸿蒙开发:如何解决软键盘弹出后的间距
|
4天前
|
搜索推荐 调度
鸿蒙开发中对want的深入理解,want和uiability的关系-深度理解want的意思有利开发-优雅草卓伊凡
鸿蒙开发中对want的深入理解,want和uiability的关系-深度理解want的意思有利开发-优雅草卓伊凡
24 2
鸿蒙开发中对want的深入理解,want和uiability的关系-深度理解want的意思有利开发-优雅草卓伊凡
|
19天前
|
安全 前端开发 Android开发
拥抱国产化:转转APP的鸿蒙NEXT端开发尝鲜之旅
本文将要分享的是转转APP在开发全新鸿蒙NEXT端所遇到的一些问题,对比了鸿蒙开发和 Android、iOS 的不同,总结了这次开发过程中的一些经验等等。希望能带给你启发。
36 0
|
2月前
|
前端开发
鸿蒙开发:使用Rect绘制矩形
几何矩形,在实际的开发中,有多种的实现方式,并非一定需要Rect组件,但是,如果有需要用到矩形的场景,建议还是使用Rect组件,因为Rect组件自身携带了很多样式属性,可以满足我们日常的不同的需求。
鸿蒙开发:使用Rect绘制矩形
|
1月前
|
人工智能 物联网 Android开发
【04】优雅草星云物联网AI智控系统从0开发鸿蒙端适配-deveco studio-自定义一个设置输入小部件组件-完成所有设置setting相关的页面-优雅草卓伊凡
【04】优雅草星云物联网AI智控系统从0开发鸿蒙端适配-deveco studio-自定义一个设置输入小部件组件-完成所有设置setting相关的页面-优雅草卓伊凡
167 92
|
1月前
|
人工智能 物联网 Android开发
【03】优雅草星云物联网AI智控系统从0开发鸿蒙端适配-deveco studio-在lib目录新建自定义库UtilsLibrary,ComponentLibrary,CommonConstLibrary完成设置SettingsView.ets初始公共类书写-优雅草卓伊凡
【03】优雅草星云物联网AI智控系统从0开发鸿蒙端适配-deveco studio-在lib目录新建自定义库UtilsLibrary,ComponentLibrary,CommonConstLibrary完成设置SettingsView.ets初始公共类书写-优雅草卓伊凡
81 23
【03】优雅草星云物联网AI智控系统从0开发鸿蒙端适配-deveco studio-在lib目录新建自定义库UtilsLibrary,ComponentLibrary,CommonConstLibrary完成设置SettingsView.ets初始公共类书写-优雅草卓伊凡
|
2月前
|
存储 调度 开发者
HarmonyOS Next 实战卡片开发 03
本文详细介绍了基于 HarmonyOS Next 的卡片开发实战,涵盖从项目创建到功能实现的全流程。首先通过新建项目和服务卡片搭建基础框架,并设置沉浸式体验优化界面。接着实现了首页轮播图功能,包括申请网络权限、初始化数据和构建轮播组件。随后深入讲解了卡片 id 的处理,涉及获取、返回、持久化存储及移除操作,确保卡片与应用间的高效通信。此外,封装了下载图片工具类,支持卡片发起通知获取网络图片,增强功能扩展性。最后实现了卡片同步轮播功能,使首页与卡片轮播状态保持一致。整个流程注重细节,结合实际案例,为开发者提供了全面的参考。
101 20
HarmonyOS Next 实战卡片开发 03
|
1月前
|
存储 人工智能 物联网
【02】优雅草星云物联网AI智控系统从0开发鸿蒙端适配-deveco studio-登录页面LoginView.ets完成-并且详细解释关于arkui关于 CommonConst, commonColor, InputDataModel-优雅草卓伊凡
【02】优雅草星云物联网AI智控系统从0开发鸿蒙端适配-deveco studio-登录页面LoginView.ets完成-并且详细解释关于arkui关于 CommonConst, commonColor, InputDataModel-优雅草卓伊凡
70 14
【02】优雅草星云物联网AI智控系统从0开发鸿蒙端适配-deveco studio-登录页面LoginView.ets完成-并且详细解释关于arkui关于 CommonConst, commonColor, InputDataModel-优雅草卓伊凡