本文主要介绍三个部分的内容:一、准备SDK源码 二、如何操作GPIO 三、LED设备驱动的实现。由于firefly官方一直在对源码进行更新,所以本文只以我正在用的版本介绍。此外,官方提供的下载工具版本不同需要准备的镜像文件(.img文件)也不同,因此,这里也只介绍我正在使用的版本。
SDK版本:firefly-sdk-20200629.7z
下载工具版本:AndroidTool v2.58
U-Boot:2017.09
Linux内核:4.4.194
文件系统:buildroot
镜像文件如下,如上图所示,只会下载勾选的镜像。
boot.img ==================> kernel/zboot.img
MiniLoaderALL.bin =========> /u-boot/rk3288_loader_v1.08.258.bin
parameter.txt =============> device/rockchip/rk3288/parameter-buildroot.txt
recovery.img ==============> buildroot/output/rockchip_rk3288_recovery/images/recovery.img
rootfs.img ================> buildroot/output/rockchip_rk3288/images/rootfs.ext2
trust.img =================> u-boot/trust.img
uboot.img =================> u-boot/uboot.img
一、准备SDK源码
实际上,firefly官网已经有具体的步骤,但官网会经常更新,所以这里再简单的介绍一遍流程。
1.下载Linux-SDK源码包
2.解压Linux-SDK源码包
将下载好的Linux-SDK源码包拷贝至虚拟机,虚拟机安装好7z解压缩工具和git工具。
7z x firefly-sdk-20200629.7z -r #递归解压主和子目录的内容
git reset --hard
cd ~/proj/Firefly-RK3288
#2. 下载远程 bundle 仓库
git clone https://github.com/FireflyTeam/bundle.git -b rk3288-linux-bundle
#3. 若 clone 失败,可以前往 github 下载 bundle.zip:
#4. 更新 SDK,并且后续更新不需要再次拉取远程仓库,直接执行以下命令即可
./bundle/update rk3288-linux-bundle
#5. 按照提示已经更新内容到 FETCH_HEAD,同步 FETCH_HEAD 到 firefly 分支
git rebase FETCH_HEAD
#6. 更新共用仓库
./bundle/update common-linux-bundle
git rebase FETCH_HEAD
最后需要注意本地分支是否是firefly,如果不是,切换之。
解压后文件夹如下:
3.编译Linux内核源码
这里是直接进入Linux内核源码目录下进行编译,也可以使用官方的build.sh脚本编译,这个官网有教程,这里不再介绍。
#交叉编译器目录firefly-sdk/prebuilts/gcc/linux-x86/arm/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin
cd /RK3288/firefly-sdk/kernel #进入内核源码目录
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
make firefly_linux_defconfig #使用内核默认配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig # 使用图形化配置
make -j8 rk3288-firefly.img #编译 此过程包含设备树、uImage、zImage的编译以及image打包过程。最终生成需要的zboot.img文件
这里仅仅介绍了内核镜像文件的生成,对于开头提到的其他文件,由于不是本文的重点,这里也不再介绍,大家可以去查看官网教程。需要注意的是内核一定要编译通过,因为后面的驱动需要能够编译通过的内核源码。
二、应用层操作GPIO
RK3288的GPIO号对应的引脚可以通过如下文件查看:
cat /sys/kernel/debug/pinctrl/pinctrl/pins
[root@rk3288:/sys/kernel/debug/pinctrl/pinctrl]# cat /sys/kernel/debug/pinctrl/p
inctrl/pins
registered pins: 264
pin 0 (gpio0-0)
pin 1 (gpio0-1)
pin 2 (gpio0-2)
pin 3 (gpio0-3)
pin 4 (gpio0-4)
pin 5 (gpio0-5)
......
......
开发板上两个LED对应的引脚是:
pin 249 (gpio8-1)=====> blue
pin 250 (gpio8-2)=====> yellow
可以通过export GPIO的方式操作这两个GPIO:
[root@rk3288:/sys/class/gpio]# echo 250 > export
[root@rk3288:/sys/class/gpio]# cd gpio250
[root@rk3288:/sys/devices/platform/pinctrl/gpio/gpio250]# ls
active_low device direction edge power subsystem uevent value
[root@rk3288:/sys/devices/platform/pinctrl/gpio/gpio250]#
direction
:GPIO的方向,可以设置为in
或者out
value
:0低电平 其他值高电平
开发板上两个LED已经应用为LED子系统(gpio8-1,gpio8-2),需要取消这个应用才可以使用sys文件操作GPIO,方法如下:
Device Drivers > LED Support
< > LED Support for GPIO connected LEDs
三、LED设备驱动
前面我们已经准备好了能够编译通过的linux内核源码,现在我们可以编写Linux设备驱动了,由于我们使用的是带设备树的Linux内核,所以驱动的编写和不带设备树的内核是有一点区别的,但总体流程不变。
1.firefly-rk3288 设备树文件
firefly-rk3288 设备树文件位于/firefly-sdk/kernel/arch/arm/boot/dts目录下,对于led设备,我们需要打开rk3288-firefly.dtsi文件,找到led设备节点:
leds {
compatible = "gpio-leds";
work {
gpios = <&gpio8 1 GPIO_ACTIVE_LOW>;
label = "firefly:blue:user";
linux,default-trigger = "rc-feedback";
pinctrl-names = "default";
pinctrl-0 = <&work_led>;
};
power {
gpios = <&gpio8 2 GPIO_ACTIVE_LOW>;
label = "firefly:green:power";
linux,default-trigger = "default-on";
pinctrl-names = "default";
pinctrl-0 = <&power_led>;
};
};
rk3288开发板共有两个led,分别对应GPIO8_A1和GPIO8_A2,但是我们在驱动程序中需要通过设备树获取到这两个GPIO的值。所以下面介绍几个常用设备树操作函数(#include <linux/of.h>):
a.通过绝对路径,获取设备节点
static inline struct device_node *of_find_node_by_path(const char *path)
b.通过父节点和名称,获取设备树子节点
static inline struct device_node *of_get_child_by_name(
const struct device_node *node,
const char *name)
b.通过节点和名称,获取GPIO引脚号
static inline int of_get_named_gpio(struct device_node *np,
const char *propname, int index)
2.firefly-rk3288 LED设备驱动编写
带设备树的LED驱动与不带设备树的驱动区别在于,带设备树的LED驱动需要在程序中从设备树中获取需要的GPIO编号,然后就是字符设备驱动的那一套流程了。
驱动源码文件如下:
#include <linux/module.h>//模块加载卸载函数
#include <linux/kernel.h>//内核头文件
#include <linux/types.h>//数据类型定义
#include <linux/fs.h>//file_operations结构体
#include <linux/device.h>//class_create等函数
#include <linux/ioctl.h>
#include <linux/kernel.h>/*包含printk等操作函数*/
#include <linux/of.h>/*设备树操作相关的函数*/
#include <linux/gpio.h>/*gpio接口函数*/
#include <linux/of_gpio.h>
#include <linux/cdev.h>/*cdev_init cdev_add等函数*/
#include <asm/gpio.h>/*gpio接口函数*/
#include <asm/uaccess.h>/*__copy_from_user 接口函数*/
#define DEVICE_NAME "rk3288_led"
typedef struct
{
struct device_node *led_node;//设备树节点
int led_pin;//GPIO 引脚
struct cdev cdev;//定义一个cdev结构体
struct class *class;//创建一个LED类
struct device *device;//创建一个LED设备 该设备是需要挂在LED类下面的
int major;//主设备号
dev_t dev_id;
}led_typdef;
static led_typdef ledx;//定义一个LED设备
static int led_open(struct inode *inode, struct file *filp)
{
int ret = -1;
retry:
ret = gpio_request(ledx.led_pin, "led_yellow");
if(ret != 0)
{
printk("gpio %d req failed:%d\r\n",ledx.led_pin,ret);
gpio_free(ledx.led_pin);//引脚被占用(错误码-16)释放掉
goto retry;
}
gpio_direction_output(ledx.led_pin,0);
return 0;
}
static int led_release(struct inode* inode ,struct file *filp)
{
gpio_free(ledx.led_pin);
return 0;
}
static int led_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
int ret;
unsigned char pbuf[1];
ret = __copy_from_user(pbuf,buf, 1);
if(ret < 0)
{
printk("__copy_from_user failed\r\n");
return ret;
}
gpio_set_value(ledx.led_pin, pbuf[0]);
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
static struct file_operations led_fops={
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.read = led_read,
.release = led_release,
};
static int __init led_init(void)
{
//struct device_node *node;
/*获取设备节点*/
ledx.led_node = of_find_node_by_path("/leds/work");
printk("/leds:%p\r\n",ledx.led_node);
if(ledx.led_node == NULL)
{
printk("find node by path fialed!\r\n");
return -1;
}
/*ledx.led_node = of_get_child_by_name(node, "user");
printk("/leds/work:%p\r\n",ledx.led_node);
if(ledx.led_node == NULL)
{
printk("get child node by name fialed!\r\n");
return -1;
}*/
/*获取GPIO信息*/
ledx.led_pin = of_get_named_gpio(ledx.led_node,"gpios",0);
printk("led_pin:%d\r\n",ledx.led_pin);
if(!gpio_is_valid(ledx.led_pin))
{
printk("get_gpio failed:%d \r\n",ledx.led_pin);
return -1;
}
/*申请设备号*/
alloc_chrdev_region(&ledx.dev_id,0,1,DEVICE_NAME);
/*初始化一个cdev*/
cdev_init(&ledx.cdev,&led_fops);
/*向cdev中添加一个设备*/
cdev_add(&ledx.cdev,ledx.dev_id,1);
/*创建一个LED类*/
ledx.class = class_create(THIS_MODULE, "led_class");
if(ledx.class == NULL)
{
printk("class_create failed\r\n");
return -1;
}
/*在LED类下创建一个LED设备*/
ledx.device = device_create(ledx.class, NULL, ledx.dev_id, NULL, DEVICE_NAME);
printk("module init ok\n");
return 0;
}
static void led_exit(void)
{
/*删除LED类*/
cdev_del(&ledx.cdev);
/*释放LED设备号*/
unregister_chrdev_region(ledx.dev_id, 1);
/*注销LED设备*/
device_destroy(ledx.class, ledx.dev_id);
/*注销LED类*/
class_destroy(ledx.class);
printk("module exit ok\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xzx2020");
MakeFile文件如下:
obj-m:=led_driver.o
PWD:=$(shell pwd)
KDIR:=/RK3288/firefly-sdk/kernel
all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
rm -rf *.ko *.order *.symvers *.cmd *.o *.mod.c *.tmp_versions .*.cmd .tmp_versions
驱动编译成功后会生成一个.ko文件,将.ko文件拷贝到开发板上并加载,出现如下提示表明驱动加载成功。
接下来,我们还需要编写一个测试APP,用于测试驱动是否正常工作,测试APP的源码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <limits.h>
#include <asm/ioctls.h>
#include <time.h>
#include <pthread.h>
int main(void)
{
int fd = -1,i;
char buf[1]={0};
fd = open("/dev/rk3288_led",O_RDWR);
if(fd < 0)
{
printf("open /dev/rk3288_led fail fd=%d\n",fd);
}
for(i=0;i<50;i++)
{
buf[0] = 0;
write(fd,buf,1);
usleep(200000);
buf[0] = 1;
write(fd,buf,1);
usleep(200000);
}
fd = close(fd);
if(fd < 0)
{
printf("led test error\n");
}
return 0;
}
将上述源码用交叉编译器编译,即可生成可执行文件,将该可执行文件加上执行权限拷贝到开发上并执行,开发板的蓝色LED灯应该就开始闪烁起来了。