BeagleBone Black Linux3.8内核驱动程序开发笔记——LED

最近这段时间在玩beaglebone-black,参照《LINUX设备驱动程序》想用来练习Linux下的驱动程序编写,于是把我最近这一个多月的学习做下笔记。我也是新手,本文仅贡交流之用,有写的不好的地方,还望各位高手指正,不吝赐教!

本文参照了:
《Linux下AM335X的GPIO控制》
《Linux下TI omap芯片 MUX 配置分析(以AM335X芯片为例)》
《BeagleBone Black Linux驱动程序开发入门(1): LED驱动程序》
《Linux下bb-black的GPIO驱动程序》
还有其他的一些博文,对我的帮助也很大,在这就不在这一一列举了!

本文利用embest的交叉编译工具(arm-none-linux-gnueabi-2010.09-50)、3.8内核源码以及镜像images_svn2541(BBB-eMMC-flasher-2013.09.04)进行介绍
相关下载地址:进入
强烈建议下载说明文档:下载

环境搭建:

1、下载交叉编译工具

arm-2010.09-50-arm-none-linux-gnueabi-i686-pc-linux-gnu.tar.bz2

2、安装交叉编译工具

(P.S.强烈建议使用root权限进行下列操作,$ sudo -s 切换到root权限下)

①解压源文件

先进入arm-2010.09-50-arm-none-linux-gnueabi-i686-pc-linux-gnu.tar.bz2所在的目录,默认是安装在/usr/local目录下的,所以解压源文件到/usr/local目录下。

$ tar -vxjf arm-2010.09-50-arm-none-linux-gnueabi-i686-pc-linux-gnu.tar.bz2 -C /usr/local

执行以上命令后,arm-none-linux-gnueabi-gcc安装包会被解压到/usr/local目录下的一个arm-2010.09目录中。arm-2010.09目录是在解压时自动创建的。

②设置环境变量

解压完成后,可以进入arm-2010.09目录下查看一下目录结构。其中,在/usr/local/arm-2010.09/bin/目录下存放的是交叉编译工具的各命令。为了使系统能够找到交叉编译工具的各命令,可以将/usr/local/arm-2010.09/bin/目录增加到PATH变量中。步骤如下:

打开/etc/bash.bashrc脚本

$ gedit /etc/bash.bashrc

在文件的末尾最后添加一行,来增加一个环境变量。

export PATH=$PATH:/usr/local/arm-2010.09/bin/

保存退出。

尽管增加了交叉编译工具的路径,并且保存了/etc/bash.bashrc脚本,但是,由于修改后,没有执行,因此修改后的PATH变量没有起作用,必须手动执行一次/etc/bash.bashrc脚本。

$ source /etc/bash.bashrc

当然,也可以重启系统,因为,系统启动时,会自动执行该脚本。

验证,打开一个新终端执行:

$ echo $PATH

如果有”/usr/local/arm-2010.09/bin/”则说明添加成功。

注:64位操作系统需要执行以下命令来安装所需的库文件以运行32位交叉编译工具链:
$ sudo apt-get install ia32-libs

③验证

在终端中执行:

$ arm-none-linux-gnueabi-gcc --version

如果显示出来交叉编译器的版本号出来则安装成功

3、更新Beaglebone Black的版本

(P.S.强烈建议按照《BeagleBone Black用户手册》操作进行更新)

①准备:映像文件(BBB-eMMC-flasher-2013.09.04.img.xz)、7z压缩工具SD卡烧写工具、4G以上的SD卡。
②使用7-zip压缩工具解压刚才下载的映像文件;
③用一个MicroSD卡套或者USB读卡器将一张MicroSD卡连接到PC;
④使用刚才安装的Win32 Disk ImagerDisk Imager工具将解压后映像文件写入MicroSD卡;
⑤将MicroSD卡插入BeagleBone Black的卡槽中,然后按住uSD BOOT按钮并使用USB线缆接通电源;
⑥当4个User LED同时持续亮起,表示已成功完系统更新(需耗45分钟左右)。请断开电源并取出MicroSD卡,然后再次接通电源即可。

4、编译内核

(P.S.强烈建议按照《BeagleBone Black用户手册》操作进行更新)

准备:Linux3.8内核源码bb-black-kernel-3.8.tar.bz2
先自行解压,之后执行以下命令来编译内核;

$ cd ~/kernel/kernel
$ cp ../configs/beaglebone .config
$ make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- uImage dtbs

?注意:如果编译内核过程中出现/bin/sh: lzop:command not found的错误,在Ubuntu系统下,使用命令$ sudo apt-get install lzop 安装lzop包。

驱动编译:

1、内核空间

驱动程序gpioCtl.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
/*-----------------------------------------------------*/
#define DEVICE_NAME "gpioCtl"
#define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
/*------------------------------------------------------*/

struct gpio_qset;

/*设备编号存储结构体*/
struct dev_num
{
 dev_t devNum;
 unsigned int major;
 unsigned int minor;
 unsigned int minor_first;
 unsigned int count;
};
struct dev_num gpio_dev_num;

/*设备描述结构体*/
struct gpio_dev
{
 struct cdev cdev;
 struct gpio_qset* dptr; //设备数据存储链表第一项
 unsigned long size; //链表长度(随着申请的GPIO端口数增长)
};
struct gpio_dev *gpio_devp;

/*设备数据存储结构体(采用链式存储,不理解的可以看《数据结构》)*/
struct gpio_qset
{
 unsigned long port; //端口号 #define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))
 unsigned int ddr; //方向(输入(0)或输出(1))
 char value;  //高(1)低(0)电平
 unsigned long num; //当前编号(按照申请顺序编号)
 char label[10];  //申请的GPIO使用名称
 struct gpio_qset* next; //指向链表下一项的指针
};

/**
 * 功能:初始化gpio_dev
 * *inode:
 * *filp:
 * 描述:用户空间调用open时运行
 *  int (*open) (struct inode *, struct file *);
 * 返回值:0
 */
static int gpio_open(struct inode *inode, struct file *filp)
{
 struct gpio_dev *dev;

 //由container_of获得结构体指针inode中结构体cdev的指针,
 //让结构体指针dev指向上述的指针所指的地址,
 //再让file->private指向这一地址,
 dev = container_of(inode->i_cdev, struct gpio_dev, cdev);
 filp->private_data = dev;
 dev->dptr = NULL;
 dev->size = 0;
 //printk(KERN_ERR "gpio_open success!\n");
 return 0;
}

/**
 * 功能:释放申请的GPIO端口以及释放链表(gpio_qset)
 * *inode:
 * *filp:
 * 描述:用户空间调用close时运行
 *  int (*release) (struct inode *, struct file *);
 * 返回值:0
 */
static int gpio_close(struct inode *inode, struct file *filp)
{
 struct gpio_dev *dev = filp->private_data;
 struct gpio_qset *qset, *qsetTmp;
 qsetTmp = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
 for(qset = dev->dptr; qset->next !=NULL; qset=qsetTmp)
 {
  qsetTmp = qset->next;
  gpio_free(qset->port); //释放申请的端口
  kfree(qset);  //释放gpio_qset内存
 }
 gpio_free(qsetTmp->port);
 kfree(qsetTmp);
  //printk(KERN_ERR "gpio release!\n");
 return 0;
}

/**
 * 功能:申请新的gpio_qset的内存,确定相应的GPIO端口功能
 * *inode:
 * *filp:
 * cmd:实现的功能,
 * 0:读,
 * 1:写,
 * 2:释放GPIO端口
 * arg:执行操作的GPIO端口
 * 描述:用户空间调用ioctl时运行
 *  long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
 * 返回值:成功返回操作的GPIO端口号
 *  错误返回相应的错误码
 */
static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
 int ret;
 struct gpio_dev *dev = filp->private_data;
 struct gpio_qset *qset;

 //cmd == 2 设计成为释放arg端口的功能,之后申请内存的任务便不必执行了,所以直接返回
 if(cmd == 2)
 {
  gpio_free(arg);
  return arg;
 }

 dev->size++;

 if(dev->dptr == NULL)
 {
  dev->dptr = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
  qset = dev->dptr;
 }
 else
 {
  for(qset = dev->dptr; qset->next != NULL; qset = qset->next);   //找到链表最后一项
  qset->next = (struct gpio_qset *)kzalloc(sizeof(struct gpio_qset), GFP_KERNEL);
  qset = qset->next;
 }
 /*链表数据*/
 qset->num = dev->size; //确定自己的编号
 qset->ddr = cmd; //确定方向
 qset->port = arg; //确定端口号
 qset->next = NULL; //最后一项地址清空

 //printk(KERN_ERR "qset->num=%d,qset->ddr=%d,qset->port=%d\n", qset->num, qset->ddr, qset->port);
 sprintf(qset->label, "gpio%ld", qset->port);  //确定申请的GPIO使用名称(和端口直接相关)
 ret = gpio_request(qset->port, qset->label);  //申请端口

 /*由于gpio_requset会自己判断成功与否并且退出函数,故注释掉对ret的判断
 if(ret < 0)
  printk(KERN_ERR "%s_requset failled!%d \n", qset->label, ret);
 */

 /*判断GPIO工作方向(输出或输出)*/ 
 switch(qset->ddr)
 {
  case 0: ret = gpio_direction_input(qset->port);
   if(ret < 0)
    printk(KERN_ERR "gpio_direction_input failled!\n");
   break;
  case 1: ret = gpio_direction_output(qset->port, 1);
   if(ret < 0)
    printk(KERN_ERR "gpio_direction_output failled!\n");
   break;
  default:
   return -EPERM; /* Operation not permitted */
 }
 return qset->num;
}

/**
 * 功能:获取相应端口电平,写入相应的qset_dev->value,写到用户空间的readBuf
 * *inode:
 * *filp:
 * *readBuf:读数据缓存指针,用户空间的指针
 * port:被读取的GPIO端口号
 * *offp:
 * 描述:用户空间调用read时运行
 *  ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
 * 返回值:成功返回qset->value
 *  错误返回相应的错误码
 */
static ssize_t gpio_read (struct file *filp, char __user *readBuf, size_t port, loff_t *offp)
{
 long ret;
 struct gpio_dev *dev = filp->private_data;
 struct gpio_qset *qset;

 if(dev->dptr == NULL) 
  return -ENODEV;  /* No such device */

 for(qset = dev->dptr; qset != NULL; qset = qset->next)
 {
  if(qset->port == port)
   break;
  if(qset->next == NULL) 
   return -ENODEV; /* No such device */
 }

 if(qset->ddr != 0)  //判断是否ioctl设置为读操作
  return -EPERM;  /* Operation not permitted */
 qset->value = gpio_get_value(qset->port);
 //printk(KERN_ERR "qset->port:%d, qset->value:%d\n", qset->port, qset->value);
 switch(qset->value)
 {
  case 0: ret = copy_to_user(readBuf, "0", 1);
   break;
  case 1: ret = copy_to_user(readBuf, "1", 1);
   break;
 }
 return qset->value;
}
/**
 * 功能:写入相应端口电平,写入相应的qset_dev->value,数据来自用户空间的writeBuf
 * *inode:
 * *filp:
 * *writeBuf:写数据缓存指针,用户空间的指针
 * port:被写入的GPIO端口号
 * *offp:
 * 描述:用户空间调用write时运行
 *  ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
 * 返回值:成功返回qset->value
 *  错误返回相应的错误码
 */
static ssize_t gpio_write (struct file *filp, const char __user *writeBuf, size_t port, loff_t *offp)
{
 long ret;
 struct gpio_dev *dev = filp->private_data;
 struct gpio_qset *qset;

 if(dev->dptr == NULL) 
  return -ENODEV;  /* No such device */

 for(qset = dev->dptr; qset != NULL; qset = qset->next)
 {
 // printk(KERN_ERR "qset->port=%d,port=%d\n", qset->port, port);
  if(qset->port == port)
   break;
  if(qset->next == NULL) 
   return -ENODEV; /* No such device */
 }

 if(qset->ddr != 1)  //判断是否ioctl设置为写操作
  return -EPERM;  /* Operation not permitted */ 

 ret = copy_from_user(&qset->value, writeBuf, 1);
 //printk(KERN_ERR "write:%d\n", qset->value);
 switch(qset->value)
 {
  case '0': qset->value = 0;
    gpio_set_value(qset->port, 0);
    break;
  default : qset->value = 1;
    gpio_set_value(qset->port, 1);
    break;
 }
 return qset->value;
}

/*文件操作结构体*/
static const struct file_operations gpio_fops = {
 .owner = THIS_MODULE,
 .open = gpio_open,
 .unlocked_ioctl = gpio_ioctl,
 .write = gpio_write,
 .read = gpio_read,
 .release = gpio_close,
};

/*cdev注册函数*/
static void gpio_setup_cdev(struct gpio_dev *dev, int index)
{
 int ret,devno = gpio_dev_num.devNum + index;
 cdev_init(&dev->cdev, &gpio_fops);
 dev->cdev.owner = THIS_MODULE;
 dev->cdev.ops = &gpio_fops;

 ret = cdev_add(&dev->cdev, devno, 1);

 if(ret)
  printk(KERN_ERR "error %d : adding gpioCtl%d",ret,index);
}

/*设备初始化函数*/
static int __init omap3gpio_init(void)
{
 int ret;

 gpio_dev_num.count = 1;
 gpio_dev_num.minor_first = 0;
 ret = alloc_chrdev_region(&gpio_dev_num.devNum, gpio_dev_num.minor_first, gpio_dev_num.count, DEVICE_NAME);

 if(ret < 0) 
  return ret;

 gpio_dev_num.major = MAJOR(gpio_dev_num.devNum);
 gpio_dev_num.minor = MINOR(gpio_dev_num.devNum);

 gpio_devp = kzalloc(sizeof(struct gpio_dev),GFP_KERNEL);

 gpio_setup_cdev(gpio_devp, 0);

 printk(KERN_ERR "gpio alloc_chrdev_region success, major = %d\n", gpio_dev_num.major);
 return 0;
}

/*设备释放函数*/
static void __exit omap3gpio_exit(void)
{
 /*test*/
 //struct file *filp;
 //struct inode *inode;
 //inode = container_of(&gpio_devp->cdev, struct inode, i_cdev);
 //filp = container_of(gpio_devp, struct file, private_data);
 //gpio_close(inode, filp);
 /*test*/

 cdev_del(&gpio_devp->cdev);
 kfree(gpio_devp);
 unregister_chrdev_region(gpio_dev_num.devNum, 1);
 printk(KERN_ERR "gpio unregister_chrdev_region success, major = %d\n", gpio_dev_num.major);
}

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Mlo_Lv,Tute-421E-studio");
MODULE_DESCRIPTION("This module is used to conrtol omap3 gpio");

module_init(omap3gpio_init);
module_exit(omap3gpio_exit);

?我的驱动程序文件名是gpioCtl.c,如果各位想直接使用这一驱动的话,请使用这个文件名,不要修改。还有就是其中对于GPIO操作的一些函数,例如”gpio_requset()”,”gpio_free()”,”gpio_set_value()”,如果有不理解的可以参照:http://blog.csdn.net/beyondioi/article/details/6984406,这篇博文详细讲解了omap系列产品用于操作GPIO的函数,其实看看源码应该能够明白这写函数的意思。

Makefile

#Makefile for gpioCtl.c
ARCH=arm
CROSS_COMPILE=arm-none-linux-gnueabi-
ifneq ($(KERNELRELEASE),)
	obj-m := gpioCtl.o
else 
	KERNELDIR ?= /beaglebone/bbb/kernel/kernel
	PWD := $(shell pwd)
default:
	make -C $(KERNELDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules

app: app.c
	$(CROSS_COMPILE)gcc -o app app.c
clean:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
	rm -rf modules.order
cleanr:
	rm *~
endif

?如果各位使用的交叉编译器和我的不一样的话,请修改CROSS_COMPILE(一样的话就免了),然后修改KERNELDIR成为自己内核的存储路径,至此所有有关驱动的程序就写完了,将上面的两短代码放在同一文件目录下,make(编译驱动之前,请先编译好内核),就会生成相应的驱动gpioCtl.ko

2、用户空间

应用程序app.c

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

#define GPIO_TO_PIN(bank, gpio) (32 * (bank) + (gpio))

int main(int argc, char * argv)
{
 int i, n, fd;
 char num;
 int ret;
 fd = open("/dev/gpioCtl", O_RDWR);  //打开设备
 if (fd < 0)
 {
  printf("can't open /dev/gpioCtl!\n");
  exit(1);
 }
 sleep(1);
 ioctl(fd, 1, GPIO_TO_PIN(1,22));  //设置gpio1-22为输出(user:led3)
 ioctl(fd, 0, GPIO_TO_PIN(2, 1));  //设置gpio2-1 为输入(p8-18)
 while (1) 
 {
  num = 1;
  ret = write(fd,"1",GPIO_TO_PIN(1,22));
		printf("on");
  if(ret < 0)
  {
   perror("write");
   return -1;
  }
  sleep(1);
  ret = write(fd,"0",GPIO_TO_PIN(1,22));
		printf("off");
  if(ret < 0)
  {
   perror("write");
   return -1;
		}
		sleep(1);
	}
}

?执行”$ make app”交叉编译应用程序。此程序能实现user:led3闪烁效果以及读取gpio2-1的电平!

gpioCtl.sh

#!/bin/sh
insmod gpioCtl.ko
mknod /dev/gpioCtl c 241 0

?我在上面的驱动程序编号申请采用动态分配方式,但是每次分配的主设备号都是241,所以shell中的设备号采用了241,如果各位动态分配的结果不是241,可能需要修改shell,cat /proc/devices 可以察看到分配到的设备号。

3、运行测试

现在将所有文件拷贝到板子上
可以用SSH拷贝,利用”$ scp 源文件 root@192.168.7.2:目标目录”命令进行拷贝

在beaglebone black终端上运行以下命令进行测试:

#为脚本增加执行权
$ chmod +x gpioCtl.sh
#运行脚本,挂载驱动
./gpioCtl.sh
#运行测试程序,控制LED3灯每隔1秒闪烁
./app

?如果没有问题的话,现在就能看到 led 闪烁了!


转载: 爱上极客
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值