TINY4412 内核编译 GPIO驱动流水灯

系统:Ubuntu 18.04.1 LTS amd64
Linux内核版本:3.5
编译工具链: arm-linux-gcc version 4.4.3

我们知道,Linux中上层应用是不能直接访问硬件的,应用访问硬件需要通过内核,因此我们的基本过程是在内核源码中添加GPIO驱动代码,该驱动代码实现上层应用可以通过调用内核中的函数对单个GPIO写,而流水灯的逻辑我们在上层的测试程序中实现。
一、 准备工作:
1、 GPIO端口选择和连线
本开发板为Tiny4412 1506,根据友善之臂提供的原理图,我们选择8个GPIO口作为LED控制端口。
TINY4412 内核编译 GPIO驱动流水灯_第1张图片
如图所示,CON17在板子上是双排针接口,作为摄像头的一个接口,我们重点关注这些引脚和CPU的什么引脚连接起来,如下图所示:
TINY4412 内核编译 GPIO驱动流水灯_第2张图片
刚好发现在CON17上,GPJ0_0-GPJ0_7都有,刚好8个端口,我们按照对应关系,将GPJ0_0-GPJ0_7从上到下依次和流水灯电路接好,如下图所示:
TINY4412 内核编译 GPIO驱动流水灯_第3张图片
将两块线路板共地,因为在上面的51单片机板上流水灯是共阳的,所以我们还需要接入5V电源。至此,线路已经连接好。
2、 内核源码和必要文件准备
新建Src文件夹用来存放源码:
mkdir Src
在这里插入图片描述
将准备好的linux内核源码(友善的资料里有)放在该文件夹下:
在这里插入图片描述
解压:tar –xzvf linux-3.5-20170221.tgz
在这里插入图片描述
将友善提供的ramdisk镜像和文件系统镜像也一并放在该文件夹下:
在这里插入图片描述
二、 内核初次编译:
该过程是为了排除源码中存在的一些错误,通过之后后续过程只关注于流水灯驱动部分。
进入目录:
在这里插入图片描述
拷贝一份工程下针对tiny4412的config文件:
在这里插入图片描述
然后执行make menuconfig进行一些配置:
TINY4412 内核编译 GPIO驱动流水灯_第4张图片
在menu里面关闭掉trustZone模式,因为我们当前的uboot不支持该模式。
TINY4412 内核编译 GPIO驱动流水灯_第5张图片
之后打开内核的debug信息输出,这样做的好处是当内核启动不成功的时候我们可以知道卡在什么地方了,方便调试。
在这里插入图片描述
然后保存退出。
打开kernel文件夹下的timeconst.pl文件,将373行的源码修改为:
在这里插入图片描述
保存退出。
然后在顶层目录下进行编译:
make –j4
TINY4412 内核编译 GPIO驱动流水灯_第6张图片
等待若干分钟后:
TINY4412 内核编译 GPIO驱动流水灯_第7张图片
编译成功。
三、 流水灯驱动代码编写:
我们在linux源码目录下,进入路径drivers:
在这里插入图片描述
创建一个文件夹lights用来保存流水灯的驱动代码:
mkdir lights
在这里插入图片描述
进入路径并创建文件lights.c
cd lights
touch lights.c
在这里插入图片描述
打开文件编辑代码:

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

struct exynos4412_lights{
unsigned int dev_major;
struct class *cls;
struct device *dev;
int value;
};

struct exynos4412_lights *lights_dev;

volatile unsigned int *gpj0_conf;
volatile unsigned int *gpj0_data;

int lights_open(struct inode *inode, struct file *filp){
printk("-------%s--------\n", func);
*gpj0_conf = 0x11111111;//Output mode
return 0;
}

ssize_t lights_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos){
printk("-------%s--------\n", func);
return count;
}

ssize_t lights_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos){
int ret = -1;
printk("-------%s--------\n", func);

ret = copy_from_user(&lights_dev->value, buf, count);
if(ret > 0){
    printk("copy from user failed!\n");
    return -EFAULT;
}

if(lights_dev->value){
    *gpj0_data = 0xff;
}
else{
    *gpj0_data = 0x00;
}

return count;

}

long lights_ioctl(struct file *filp, unsigned int value){
printk("-------%s--------\n", func);

*gpj0_data = value;

return 0;

}

int lights_close(struct inode *inode, struct file *filp){
printk("-------%s--------\n", func);
*gpj0_data = 0x00;
return 0;
}

struct file_operations lights_fops = {
//.owner = THIS_MODULE,
.open = lights_open,
.read = lights_read,
.write = lights_write,
.unlocked_ioctl = lights_ioctl,
.release = lights_close,
};

static __exit void lights_exit(void)
{
printk("--------%s---------\n", func);
device_destroy(lights_dev->cls, MKDEV(lights_dev->dev_major, 0));
class_destroy(lights_dev->cls);
unregister_chrdev(lights_dev->dev_major, “lights_drv”);
kfree(lights_dev);
}

static __init int lights_init(void)
{
int ret = -1;
printk("--------%s---------\n", func);

lights_dev = kzalloc(sizeof(struct exynos4412_lights), GFP_KERNEL);
if(NULL == lights_dev){
    printk("kzalloc failed!\n");
    return -ENOMEM;
}

lights_dev->dev_major = register_chrdev(0, "lights_drv", &lights_fops);
if(lights_dev->dev_major < 0){
    printk("register failed\n");
    return -EINVAL;
}
lights_dev->cls = class_create(THIS_MODULE, "lights_cls");
if(IS_ERR(lights_dev->cls)){
    printk("class_register failed!\n");
    ret = PTR_ERR(lights_dev);
    goto err1;
}


lights_dev->dev = device_create(lights_dev->cls, NULL, MKDEV(lights_dev->dev_major, 0), NULL, "lights_yjp");
if(IS_ERR(lights_dev->dev)){
    printk("device create failed!\n");
    ret = PTR_ERR(lights_dev->dev);
    goto err2;
}

gpj0_conf = ioremap(0x11400240, 8);
gpj0_data = gpj0_conf + 1;

return 0;

err2:
class_destroy(lights_dev->cls);
err1:
unregister_chrdev(lights_dev->dev_major, “lights_drv”);
return ret;
}

module_init(lights_init);
module_exit(lights_exit);
MODULE_LICENSE(“GPL”);

在lights_init函数中,动态建立逻辑设备并给进行相应的初始化,然后将此逻辑设备加到linux内核系统的设备驱动程序模型中。在内核运行之后我们就会在dev文件夹下看到文件lights_yjp,是我们在这里创建的。之后在初始化的时候需要对硬件设备进行虚拟地址映射,使用ioremap函数,参数是GPIO口的物理地址,我们查芯片的手册找到其物理地址:
TINY4412 内核编译 GPIO驱动流水灯_第8张图片
GPJ0DAT的地址是GPJ0CON地址+1,也就是0x11400244。
Lights_fops函数是一个接口函数或者映射函数,linux下任何设备都是以文件的形式存在,所以我们对该硬件的读写就是对文件的读写,这里映射一些函数代表执行文件操作后对该硬件应该执行什么样的操作。
当我们在测试程序中打开lights_yjp设备时,对GPIO口进行配置,就是指明该GPIO口的功能,输入输出或者复用功能,如下图所示:
TINY4412 内核编译 GPIO驱动流水灯_第9张图片
在这里我们8个口都需要配置成输出模式,所以在lights_open函数中有行代码:
*gpj0_conf = 0x11111111;
规定接口函数lights_ioctls有两个参数,一个是文件参数,另一个是我们从上层程序中传入的值,该值的低8位代表8个IO口的状态。
至此,驱动程序编写完毕。具体代码在附件中有。
四、将驱动编入内核
在完成驱动代码之后,需要将其编入内核,需要我们来配置makefile文件。根据其他一些已经存在的驱动,我们仿照他的格式,在lights文件夹下创建文件Makefile:
touch Makefile
在这里插入图片描述
gedit Makefile
输入内容为:
在这里插入图片描述
-y的意思是该部分为必须,告诉编译器要将该文件编入内核。
在上层文件夹的makefile添加lights文件夹:
在这里插入图片描述
配置完毕,在顶层目录执行
make –j4
在这里插入图片描述
报错:
我发现lights文件夹的makefile里面什么也没有,可能是刚才写了没保存,返回去重写写入
obj-y += lights.o
再make
TINY4412 内核编译 GPIO驱动流水灯_第10张图片
成功。至此我们已经将驱动编入内核了。
五、下载内核、文件系统到开发板
基于上篇博客的uboot,将开发板的串口线和micro USB先与电脑连接起来,打开minicom
sudo minicom
TINY4412 内核编译 GPIO驱动流水灯_第11张图片
给开发板上电:
TINY4412 内核编译 GPIO驱动流水灯_第12张图片
在终端输入fastboot进入fastboot模式:
TINY4412 内核编译 GPIO驱动流水灯_第13张图片
我们将刚才编译好的zImage下载,另开一个终端:
cd arch/arm/boot
在这里插入图片描述
sudo fastboot flash kernel zImage
TINY4412 内核编译 GPIO驱动流水灯_第14张图片
然后下载ramdisk和file system
sudo fastboot flash ramdisk ramdisk-u.img
sudo fastboot flash fat rootfs_qtopia_qt4.img
TINY4412 内核编译 GPIO驱动流水灯_第15张图片
TINY4412 内核编译 GPIO驱动流水灯_第16张图片
在minicom终端输入printenv查看一下环境参数对不对:
TINY4412 内核编译 GPIO驱动流水灯_第17张图片
若不是,需要改成这样。
reset重新启动:
TINY4412 内核编译 GPIO驱动流水灯_第18张图片
我们发现内核启动成功。
查看dev下有没有lights_yjp文件:
cd dev
ls
TINY4412 内核编译 GPIO驱动流水灯_第19张图片
存在,说明一切正常。
六、编写测试程序:
在Src文件夹下新建test文件夹:
mkdir test
cd test
在这里插入图片描述
新建LightsTest_yjp.c:
touch LightsTest_yjp.c
gedit LightsTest_yjp.c
输入内容如下:

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

void left();
void right();
void recycle();
void flash();

int fd = -1;

int main(void)
{
int val = -1;

fd = open("/dev/lights_yjp", O_RDWR);
if(fd < 0){
perror("open");
exit(1);
}
    int mode=0;
while(1)
{
printf("**************choose mode****************\r\n");
printf("*             left------0               *\r\n");
printf("*             right-----1               *\r\n");
printf("*             recycle---2               *\r\n");
printf("*             flash-----3               *\r\n");
printf("*****************************************\r\n");

scanf("%d",&mode);
switch(mode){
case 0 :{left();break;}
case 1 :{right();break;}
case 2 :{recycle();break;}
case 3 :{flash();break;}
default:{
printf("Invalid parameter!\r\n");
printf("please choose again\r\n");
break;}
}
}
return 0;

}

void left(){
short value = 0x01;
while(1){
ioctl(fd,~value);
usleep(200*1000);
if(value==0x80) break;
else value<<=1;
}
}

void right(){
short value = 0x80;
while(1){
ioctl(fd,~value);
usleep(200*1000);
if(value==0x01) break;
else value>>=1;
}
}

void recycle(){
short value = 0x01;
while(1){
ioctl(fd,~value);
usleep(200*1000);
if(value==0x80) value=0x01;
else value<<=1;
}
}

void flash(){
short value = 0xff;
while(1){
ioctl(fd,~value);
usleep(2001000);
ioctl(fd,value);
usleep(200
1000);
}
}

因为我们在驱动中预留的接口就是一个int型的参数,该参数的后8位代表了GPJ0_7-GPJ0_0的每个状态,所以我们写端口的时候只需要写该8位即可。左移,右移,循环移,闪烁的逻辑实现较简单,在这里不加赘述,源代码在附件中有。
touch Makefile
gedit Makefile
输入内容如下:
LightsTest_yjp : LightsTest_yjp.o arm-linux-gcc -o LightsTest_yjp LightsTest_yjp.o
LightsTest_yjp.o : LightsTest_yjp.c arm-linux-gcc -c LightsTest_yjp.c
clean:
rm -rf LightsTest_yjp.o LightsTest_yjp

在test目录下执行make
在这里插入图片描述
可以看到编译成功。
利用minicom将可执行文件传入开发板,在minicom中按下ctrl+A 再按S,选择zmodel:
选择可执行文件并传输,在开发板的目录下:
TINY4412 内核编译 GPIO驱动流水灯_第20张图片
然后执行./LightsTest_yjp就可以测试。

你可能感兴趣的:(嵌入式Linux学习,TINY4412,GPIO驱动流水灯,minicom使用,内核编译)