基于i.MX6ULL点亮LED

描述

都说入门一款芯片的第一步是点亮LED,但是i.MX6ULL入门门槛比较高,特别是通过自学入门的,这个系列已经写了好久了,最近打算在项目不急的时候加快一下学习进度,现在就开始学习一下怎么点亮一个LED,前边学的框架就是为了点亮LED做基础,点亮LED主要是学习怎么操作实际地址,都知道内核是不能直接操作地址的,就需要通过内核提供的API去操作实际地址;

| 测试写入

在点亮LED前先测试一下APP往驱动写入数据,避免写入数据阶段就出现数据异常,再往下写就没多大意义;

chrdevbaseApp.c文件

 

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"


/*
 * @description    : main主程序
 * @param - argc   : argv数组元素个数
 * @param - argv   : 具体参数
 * @return       : 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
  int fd, retvalue;
  char *filename;
  char writebuf[100];
  unsigned char databuf[1];


  if(argc != 3){
    printf("[APP]Error Usage!
");
    return -1;
  }


  filename = argv[1];


  /* 打开驱动文件 */
  fd  = open(filename, O_RDWR);
  if(fd < 0){
    printf("[APP]Can't open file %s
", filename);
    return -1;
  }


  /* 把第三个参数赋值给databuf */
  databuf[0] = atoi(argv[2]);


  /* 向设备驱动写数据 */
  memcpy(writebuf, databuf, sizeof(databuf));
  retvalue = write(fd, writebuf, sizeof(databuf));
  if(retvalue < 0){
    printf("[APP]write file %s failed!
", filename);
  }


  /* 关闭设备 */
  retvalue = close(fd);
  if(retvalue < 0){
    printf("[APP]Can't close file %s
", filename);
    return -1;
  }


  return 0;
}

 

chrdevbase.c文件

 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define CHRDEVBASE_CNT      1    /* 设备号个数 */
#define CHRDEVBASE_NAME   "chrdevbase"  /* 名字 */


/* chrdevbase 设备结构体 */
struct newchr_dev{
  dev_t devid;       /* 设备号 */
  struct cdev cdev;     /* cdev */
  struct class *class;   /* 类 */
  struct device *device;   /* 设备 */
  int major;         /* 主设备号 */
  int minor;         /* 次设备号 */
};


struct newchr_dev chrdevbase;/* 自定义字符设备 */


/*
 * @description    : 打开设备
 * @param - inode   : 传递给驱动的inode
 * @param - filp   : 设备文件,file结构体有个叫做private_data的成员变量
 *             一般在open的时候将private_data指向设备结构体。
 * @return       : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
  printk("[BSP]chrdevbase open!
");
  filp->private_data = &chrdevbase; /* 设置私有数据 */
  return 0;
}


/*
 * @description    : 从设备读取数据 
 * @param - filp   : 要打开的设备文件(文件描述符)
 * @param - buf   : 返回给用户空间的数据缓冲区
 * @param - cnt   : 要读取的数据长度
 * @param - offt   : 相对于文件首地址的偏移
 * @return       : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
  printk("chrdevbase read!
");
  return 0;
}


/*
 * @description    : 向设备写数据 
 * @param - filp   : 设备文件,表示打开的文件描述符
 * @param - buf   : 要写给设备写入的数据
 * @param - cnt   : 要写入的数据长度
 * @param - offt   : 相对于文件首地址的偏移
 * @return       : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
  int retvalue = 0;
  char writebuf[1];


  /* 接收用户空间传递给内核的数据并且打印出来 */
  retvalue = copy_from_user(writebuf, buf, cnt);
  printk("[BSP]kernel recevdata data:%d!
",writebuf[0]);


  // printk("chrdevbase write!
");
  return 0;
}


/*
 * @description    : 关闭/释放设备
 * @param - filp   : 要关闭的设备文件(文件描述符)
 * @return       : 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
  printk("[BSP]release!
");
  return 0;
}


/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
  .owner = THIS_MODULE,  
  .open = chrdevbase_open,
  .read = chrdevbase_read,
  .write = chrdevbase_write,
  .release = chrdevbase_release,
};


/*
 * @description  : 驱动入口函数 
 * @param     : 无
 * @return     : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
  /* 注册字符设备驱动 */
  /* 1、创建设备号 */
  if (chrdevbase.major) { /* 定义了设备号 */
    chrdevbase.devid = MKDEV(chrdevbase.major, 0);
    register_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT, CHRDEVBASE_NAME);
  } else { /* 没有定义设备号 */
    alloc_chrdev_region(&chrdevbase.devid, 0, CHRDEVBASE_CNT,CHRDEVBASE_NAME); /* 申请设备号 */
    chrdevbase.major = MAJOR(chrdevbase.devid); /* 获取主设备号 */
    chrdevbase.minor = MINOR(chrdevbase.devid); /* 获取次设备号 */
  }
  printk("newcheled major=%d,minor=%d
",chrdevbase.major,chrdevbase.minor);


  /* 2、初始化 cdev */
  chrdevbase.cdev.owner = THIS_MODULE;
  cdev_init(&chrdevbase.cdev, &chrdevbase_fops);


  /* 3、添加一个 cdev */
  cdev_add(&chrdevbase.cdev, chrdevbase.devid, CHRDEVBASE_CNT);


  /* 4、创建类 */
  chrdevbase.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
  if (IS_ERR(chrdevbase.class)) {
    return PTR_ERR(chrdevbase.class);
  }


  /* 5、创建设备 */
  chrdevbase.device = device_create(chrdevbase.class, NULL,chrdevbase.devid, NULL, CHRDEVBASE_NAME);
  if (IS_ERR(chrdevbase.device)) {
    return PTR_ERR(chrdevbase.device);
  }


  return 0;
}


/*
 * @description  : 驱动出口函数
 * @param     : 无
 * @return     : 无
 */
static void __exit chrdevbase_exit(void)
{
  /* 注销字符设备 */
  cdev_del(&chrdevbase.cdev);/* 删除 cdev */
  unregister_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT);/* 注销设备号 */


  device_destroy(chrdevbase.class, chrdevbase.devid);/* 销毁设备 */
  class_destroy(chrdevbase.class);/* 销毁类 */


  printk("[BSP]chrdevbase exit!
");
}


/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);


/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

 

实测操作如下图所示,没有问题就可以继续编写!

内核

| 寄存器点亮LED

测试写入没有问题就继续补充驱动,通过配置寄存器来实现点亮LED,不同板子LED灯在不同的GPIO上,根据实际配置,下方是补充完整的驱动:

 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define CHRDEVBASE_CNT      1    /* 设备号个数 */
#define CHRDEVBASE_NAME   "chrdevbase"  /* 名字 */


#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */


/* 寄存器物理地址 */
// GPIO1时钟
#define CCM_CCGR1_BASE (0X020C406C)
// GPIO1 多路选择
#define SW_MUX_GPIO1_IO04_BASE  (0X020E006C)
// GPIO模式配置
#define SW_PAD_GPIO1_IO04_BASE  (0X020E02F8)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)


/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO04;
static void __iomem *SW_PAD_GPIO1_IO04;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;


/* chrdevbase 设备结构体 */
struct newchr_dev{
  dev_t devid;       /* 设备号 */
  struct cdev cdev;     /* cdev */
  struct class *class;   /* 类 */
  struct device *device;   /* 设备 */
  int major;         /* 主设备号 */
  int minor;         /* 次设备号 */
};


struct newchr_dev chrdevbase;/* 自定义字符设备 */


/*
* @description : LED 打开/关闭
* @param - sta : LEDON(0) 打开 LED,LEDOFF(1) 关闭 LED
* @return : 无
*/
void led_switch(u8 sta)
{
  u32 val = 0;
  if(sta == LEDON) {
    val = readl(GPIO1_DR);
    val &= ~(1 << 4); 
    writel(val, GPIO1_DR);
  }else if(sta == LEDOFF) {
    val = readl(GPIO1_DR);
    val|= (1 << 4);
    writel(val, GPIO1_DR);
  } 
}


/*
* @description : LED 硬件初始化
* @param : 无
* @return : 无
*/
void led_hal_init(void)
{
  u32 val = 0;


  /* 初始化 LED */
  /* 1、寄存器地址映射 */
  IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
  SW_MUX_GPIO1_IO04 = ioremap(SW_MUX_GPIO1_IO04_BASE, 4);
  SW_PAD_GPIO1_IO04 = ioremap(SW_PAD_GPIO1_IO04_BASE, 4);
  GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
  GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);


  /* 2、使能 GPIO1 时钟 */
  val = readl(IMX6U_CCM_CCGR1);
  val &= ~(3 << 26); /* 清楚以前的设置 */
  val |= (3 << 26); /* 设置新值 */
  writel(val, IMX6U_CCM_CCGR1);


  /* 3、设置 GPIO1_IO04 的复用功能,将其复用为
  * GPIO1_IO04,最后设置 IO 属性。
  */
  writel(5, SW_MUX_GPIO1_IO04);


  /* 寄存器 SW_PAD_GPIO1_IO04 设置 IO 属性 */
  writel(0xD0B1, SW_PAD_GPIO1_IO04);


  /* 4、设置 GPIO1_IO04 为输出功能 */
  val = readl(GPIO1_GDIR);
  val &= ~(1 << 4); /* 清除以前的设置 */
  val |= (1 << 4); /* 设置为输出 */
  writel(val, GPIO1_GDIR);


  /* 5、默认关闭 LED */
  val = readl(GPIO1_DR);
  val |= (1 << 4); 
  writel(val, GPIO1_DR);
}


/*
* @description : 取消映射
* @param : 无
* @return : 无
*/
void led_hal_exit(void)
{
  iounmap(IMX6U_CCM_CCGR1);
  iounmap(SW_MUX_GPIO1_IO04);
  iounmap(SW_PAD_GPIO1_IO04);
  iounmap(GPIO1_DR);
  iounmap(GPIO1_GDIR);
}


/*
 * @description    : 打开设备
 * @param - inode   : 传递给驱动的inode
 * @param - filp   : 设备文件,file结构体有个叫做private_data的成员变量
 *             一般在open的时候将private_data指向设备结构体。
 * @return       : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
  printk("[BSP]chrdevbase open!
");
  filp->private_data = &chrdevbase; /* 设置私有数据 */
  return 0;
}


/*
 * @description    : 从设备读取数据 
 * @param - filp   : 要打开的设备文件(文件描述符)
 * @param - buf   : 返回给用户空间的数据缓冲区
 * @param - cnt   : 要读取的数据长度
 * @param - offt   : 相对于文件首地址的偏移
 * @return       : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
  printk("chrdevbase read!
");
  return 0;
}


/*
 * @description    : 向设备写数据 
 * @param - filp   : 设备文件,表示打开的文件描述符
 * @param - buf   : 要写给设备写入的数据
 * @param - cnt   : 要写入的数据长度
 * @param - offt   : 相对于文件首地址的偏移
 * @return       : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
  int retvalue = 0;
  char writebuf[1];


  /* 接收用户空间传递给内核的数据并且打印出来 */
  retvalue = copy_from_user(writebuf, buf, cnt);
  printk("[BSP]kernel recevdata data:%d!
",writebuf[0]);


  if(writebuf[0] == LEDON) { 
    led_switch(LEDON); /* 打开 LED 灯 */
  } else if(writebuf[0] == LEDOFF) {
    led_switch(LEDOFF); /* 关闭 LED 灯 */
  }


  // printk("chrdevbase write!
");
  return 0;
}


/*
 * @description    : 关闭/释放设备
 * @param - filp   : 要关闭的设备文件(文件描述符)
 * @return       : 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
  printk("[BSP]release!
");
  return 0;
}


/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
  .owner = THIS_MODULE,  
  .open = chrdevbase_open,
  .read = chrdevbase_read,
  .write = chrdevbase_write,
  .release = chrdevbase_release,
};


/*
 * @description  : 驱动入口函数 
 * @param     : 无
 * @return     : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
  /* 初始化硬件 */
  led_hal_init();


  /* 注册字符设备驱动 */
  /* 1、创建设备号 */
  if (chrdevbase.major) { /* 定义了设备号 */
    chrdevbase.devid = MKDEV(chrdevbase.major, 0);
    register_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT, CHRDEVBASE_NAME);
  } else { /* 没有定义设备号 */
    alloc_chrdev_region(&chrdevbase.devid, 0, CHRDEVBASE_CNT,CHRDEVBASE_NAME); /* 申请设备号 */
    chrdevbase.major = MAJOR(chrdevbase.devid); /* 获取主设备号 */
    chrdevbase.minor = MINOR(chrdevbase.devid); /* 获取次设备号 */
  }
  printk("newcheled major=%d,minor=%d
",chrdevbase.major,chrdevbase.minor);


  /* 2、初始化 cdev */
  chrdevbase.cdev.owner = THIS_MODULE;
  cdev_init(&chrdevbase.cdev, &chrdevbase_fops);


  /* 3、添加一个 cdev */
  cdev_add(&chrdevbase.cdev, chrdevbase.devid, CHRDEVBASE_CNT);


  /* 4、创建类 */
  chrdevbase.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
  if (IS_ERR(chrdevbase.class)) {
    return PTR_ERR(chrdevbase.class);
  }


  /* 5、创建设备 */
  chrdevbase.device = device_create(chrdevbase.class, NULL,chrdevbase.devid, NULL, CHRDEVBASE_NAME);
  if (IS_ERR(chrdevbase.device)) {
    return PTR_ERR(chrdevbase.device);
  }


  return 0;
}


/*
 * @description  : 驱动出口函数
 * @param     : 无
 * @return     : 无
 */
static void __exit chrdevbase_exit(void)
{
  /* 取消映射 */
  led_hal_exit();


  /* 注销字符设备 */
  cdev_del(&chrdevbase.cdev);/* 删除 cdev */
  unregister_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT);/* 注销设备号 */


  device_destroy(chrdevbase.class, chrdevbase.devid);/* 销毁设备 */
  class_destroy(chrdevbase.class);/* 销毁类 */


  printk("[BSP]chrdevbase exit!
");
}


/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);


/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

 

| 细节剖析

1、先看地址定义,包括物理地址和映射后的地址指针:

内核

2、再看具体的配置,封装在led_hal_init函数中:

 

void led_hal_init(void)
{
  // LED 硬件初始化
}

 

3、再看输出电平配置,封装在led_switch函数中:

 

void led_switch(u8 sta)
{
  //电平配置
}

 

4、再看调用位置,一般在加载驱动的时候就初始化寄存器,在写函数中判断参数来进行亮灭切换;

5、最后注销驱动的时候,需要把映射地址进行取消,一般都是初始化申请了什么资源,注销的时候就需要释放什么资源;

6、多次注销和加载驱动,看看效果是否正常,注意野火的板子是多彩灯,只操作一个gpio效果不是很明显,上面的代码就是控制R灯的亮灭;

内核

又学会了一种点灯方式,在点灯的道路上越走越远,哈哈哈!

  审核编辑:汤梓红
 
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分