开发步骤介绍
觉得可以的话,还请大哥们点赞,后面有完整示例代码
step1
1.采用74hc595的数码管驱动只需要由GPIO口的高低电平控制即可,也就是0、1控制。通过一定的输出规律进行控制。芯片hc595,只有三根控制信号线。数码管有动态与静态之分,动态8位数码管一次只能显示一个数字,不能同时显示多个数字,需要不断高速刷新显示,且有闪烁缺点,驱动方式类似。我这里是74hc595的8位静态共阳极数码管。数码管控制原理不做赘述。
step2
2.本人的开发板是imx6ull mini板。首先明确采用哪个GPIO口,通过GPIO口进行控制。mini的底板标注的GPIO_4其实对应的是GPIO1_IO04,这点很坑。(踩坑)关于接线如何接,后文会提到。
step3
3.接下来进行驱动编写。首先出厂的板件选用EMMC启动,自带了正点原子的Linux系统,文件树,uboot。设置IP地址后,可以用xshell与winscp连接该系统,上传下载文件等。
step4
4.想要编译驱动,使用make -j32 命令,但是编译时需要调用linux内核内容,需要在makefile中设置内核绝对路径。
例如:
KERNELDIR := /home/vincent/vincent/linux
CURRENT_PATH := $(shell pwd)
obj-m := newchrled.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
step5
5.由于使用的是正点原子的出厂系统,所以在/lib/modules/目录下存在目录名称4.1.15-gc0de9f6,其中-gc0de9f6需要设置在linux内核中的makefile中。否则无法挂载驱动。(踩坑)同时,在内核编译时,需要选择ARM7,去掉ARM6,否则也是无法挂载驱动,报错。具体参考《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》中第三十七章 Linux 内核移植。
源码使用《03、正点原子Uboot和Linux出厂源码》内核源码的makefile中修改第4行,第252行253行。
EXTRAVERSION =-gc0de9f6
ARCH ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-
内核编译正确好后,方可成功挂载驱动。最好重新烧写。驱动挂载不上再重新烧写内核到开发板。
step6 驱动编写
接下来配置GPIO_1,GPIO_2,GPIO_4,也就是GPIO1_IO01,GPIO1_IO02,GPIO1_IO04(均属于GPIO1这一组IO口)
在驱动中,linux驱动通过c语言中的ioremap函数将物理地址转换为虚拟地址,通过操作这些变量从而操作寄存器,示例:
#define SW_MUX_GPIO1_IO01_BASE (0X020E0060)
static void __iomem *SW_MUX_GPIO1_IO01;
SW_MUX_GPIO1_IO01 = ioremap(SW_MUX_GPIO1_IO01_BASE, 4);
首先在《IMX6ULL参考手册》查找用于GPIO时钟使能的CCM_CCGR1,其地址是0X020C406C。通过设置其寄存器中27-26位的值,对GPIO1这一组的io口进行时钟使能。
接下来,查找用于配置GPIO复用模式的IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO01,其物理地址在(0X020E0060),通过设置第0-4位,从而设置为通用的GPIO模式。连续查找并配置GPIO1_IO02、GPIO1_IO04。
配置完复用模式后,需要配置电器属性,通过IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO01寄存器,物理地址(0X020E02EC),进行相关配置。
接下来再进行IO的输入输出方向配置,需要全部配置为输出模式,通过寄存器GPIO1_GDIR配置,物理地址(0X0209C004)。
最后,通过寄存器GPIO1_DR控制GPIO1这一组IO口的输出高电平还是低电平。到此裸机开发的流程结束,至于Linux驱动开发中其他的格式写法在官方示例代码中有。其中值得注意的是module_init绑定了一个驱动挂载时运行的函数。file_operations函数,该函数定义了一些外部调用的函数,如需要c++程序调用的write函数就写在这里,通过write函数进行与c++的通信。其中open函数是c++程序代码打开驱动时运行的函数。示例如下
static struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
其中函数led_write示例内容如下
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
//printk("vincent kernel write start\r\n");
int retvalue;
unsigned char databuf[100];
unsigned char ledstat;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
printk("get data : %s",databuf);
return 0;
}
完整驱动74hc595八位静态数码管代码如下,改代码通过修改Linux示例程序中newchrled得来。注意其中rck的地方,是由低拉到高。某些数码管可能不一样。dio接gpio1-2,sclk接gpio1-1,rclk接gpio1-4
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define NEWCHRLED_CNT 1 /* 设备号个数 */
#define NEWCHRLED_NAME "newchrled" /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* 寄存器物理地址 */
// GPIO1时钟
#define CCM_CCGR1_BASE (0X020C406C)
// GPIO1 多路选择
#define SW_MUX_GPIO1_IO00_BASE (0X020E005C)
#define SW_MUX_GPIO1_IO01_BASE (0X020E0060)
#define SW_MUX_GPIO1_IO02_BASE (0X020E0064)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_MUX_GPIO1_IO04_BASE (0X020E006C)
// GPIO模式配置
#define SW_PAD_GPIO1_IO00_BASE (0X020E02E8)
#define SW_PAD_GPIO1_IO01_BASE (0X020E02EC)
#define SW_PAD_GPIO1_IO02_BASE (0X020E02F0)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#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_IO01;
static void __iomem *SW_MUX_GPIO1_IO02;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_MUX_GPIO1_IO04;
static void __iomem *SW_PAD_GPIO1_IO01;
static void __iomem *SW_PAD_GPIO1_IO02;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO04;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/* newchrled设备结构体 */
struct newchrled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
struct newchrled_dev newchrled; /* led设备 */
char SEGCode[18] = {0xc0,0xf9,0xa4,0xb0, //0~3
0x99,0x92,0x82,0xf8, //4~7
0x80,0x90,0x88,0x83, //8~9 A b
0xc6,0xa1,0x86,0x8e,0xff,0x7f}; // C d E F 灭 .
void led_switch(u8 leds,u8 sta);
void delay_us(uint16_t time)
{
// int i = 0;
// for(i=0;i<10000*time;i++){
// ;
// }
// ndelay(10000*time);
}
void dio_w(uint8_t state)
{
led_switch(2,state);
}
void sclk_w(uint8_t state)
{
led_switch(1,state);
}
void rclk_w(uint8_t state)
{
led_switch(4,state);
}
void led_switch(u8 gpioNum,u8 sta)
{
u32 val = 0;
if(sta == 0) {
val = readl(GPIO1_DR);
val &= ~(1 << gpioNum); / qing ling
writel(val, GPIO1_DR);
}else if(sta == 1) {
val = readl(GPIO1_DR);
val|= (1 << gpioNum);
writel(val, GPIO1_DR); / GPIO1_DR yongyu kongzhi gaodi dianping shuchu
}
}
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchrled; /* 设置私有数据 */
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
int lengthstr(char * str)
{
int i=0;
while(str[i]!='\0')
{
i++;
}
return i;
}
void vOutput(void)
{
rclk_w(1);
delay_us(500); //拉低ST时钟
// printk("rclk down -> up");
rclk_w(0);
delay_us(500);
return ; //ST时钟上升沿,更新锁存器数据
}
void pushOne(char datcode)
{
uint i;
// char datcode;
// datcode = SEGCode[dat]; //十进制数转段码
for(i = 0;i < 8;i++) //写数据
{
//拉低SH时钟
sclk_w(0);
delay_us(500);
if(datcode & 0x80) //取最高位
{
//printk("dio -> 1\r\n");
dio_w(1);
delay_us(500);
}else{
//printk("dio -> 0\r\n");
dio_w(0);
delay_us(500);
}
datcode <<= 1;
sclk_w(1);
delay_us(500); //SH时钟上升沿,数据存移位寄存器
//每次取一位,数据左移
}
dio_w(0);
sclk_w(0);
}
void Write595(char datcode)
{
pushOne( datcode);
vOutput();
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
//printk("vincent kernel write start\r\n");
int retvalue;
unsigned char databuf[4];
unsigned char ledstat;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
pushOne(SEGCode[(databuf[5]-'0')]) ;
pushOne(SEGCode[(databuf[4]-'0')]) ;
pushOne(0xBF) ;
pushOne(SEGCode[(databuf[3]-'0')]) ;
pushOne(SEGCode[(databuf[2]-'0')]) ;
pushOne(0xBF) ;
pushOne(SEGCode[(databuf[1]-'0')]) ;
pushOne(SEGCode[(databuf[0]-'0')]) ;
vOutput();
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作函数 */
static struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
static int __init led_init(void)
{
printk("led_init start \r\n");
u32 val = 0;
int xx1 = 0;
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
SW_MUX_GPIO1_IO01 = ioremap(SW_MUX_GPIO1_IO01_BASE, 4);
SW_PAD_GPIO1_IO01 = ioremap(SW_PAD_GPIO1_IO01_BASE, 4);
SW_MUX_GPIO1_IO02 = ioremap(SW_MUX_GPIO1_IO02_BASE, 4);
SW_PAD_GPIO1_IO02 = ioremap(SW_PAD_GPIO1_IO02_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_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时钟 shezhi ccgr1 zhong zhi page 700 27 26 zhi 1*/
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); /* 清楚以前的设置 */
val |= (3 << 26); /* 设置新值 11 zuoyi 26 wei */
writel(val, IMX6U_CCM_CCGR1);
writel(5, SW_MUX_GPIO1_IO01);
writel(0xD0B1, SW_PAD_GPIO1_IO01);
writel(5, SW_MUX_GPIO1_IO02);
writel(0xD0B1, SW_PAD_GPIO1_IO02);
writel(5, SW_MUX_GPIO1_IO03);
writel(0xD0B1, SW_PAD_GPIO1_IO03);
writel(5, SW_MUX_GPIO1_IO04);
writel(0xD0B1, SW_PAD_GPIO1_IO04);
/* 4、设置GPIO1_IO01为输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 1); /* 清除以前的设置 */
val |= (1 << 1); /* 设置为输出 */
writel(val, GPIO1_GDIR);
/* 4、设置GPIO1_IO02为输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 2); /* 清除以前的设置 */
val |= (1 << 2); /* 设置为输出 */
writel(val, GPIO1_GDIR);
/* 4、设置GPIO1_IO03为输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3); /* 清除以前的设置 */
val |= (1 << 3); /* 设置为输出 */
writel(val, GPIO1_GDIR);
/* 4、设置GPIO1_IO04为输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 4); /* 清除以前的设置 */
val |= (1 << 4); /* 设置为输出 */
writel(val, GPIO1_GDIR);
// val = readl(GPIO1_DR);
// val &= ~(1 << 4); / qing ling
// writel(val, GPIO1_DR);
printk("start dispaley:::::: \r\n");
// led_switch(1,1);
// led_switch(2,1);
// led_switch(4,1);
// led_switch(3,1);
uint sec =45;
pushOne(SEGCode[9]);
pushOne(SEGCode[8]);
pushOne(SEGCode[7]);
pushOne(SEGCode[6]);
pushOne((0xBF));
vOutput();
dio_w(0); // gpio1-2
sclk_w(0); //gpio1-1
rclk_w(0); //gpio1-4
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (newchrled.major) { /* 定义了设备号 */
newchrled.devid = MKDEV(newchrled.major, 0);
register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申请设备号 */
newchrled.major = MAJOR(newchrled.devid); /* 获取分配号的主设备号 */
newchrled.minor = MINOR(newchrled.devid); /* 获取分配号的次设备号 */
}
printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);
/* 2、初始化cdev */
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);
/* 3、添加一个cdev */
cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
/* 4、创建类 */
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
if (IS_ERR(newchrled.class)) {
return PTR_ERR(newchrled.class);
}
/* 5、创建设备 */
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
if (IS_ERR(newchrled.device)) {
return PTR_ERR(newchrled.device);
}
printk("led_init end \r\n");
return 0;
}
static void __exit led_exit(void)
{
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO01);
iounmap(SW_PAD_GPIO1_IO01);
iounmap(SW_MUX_GPIO1_IO02);
iounmap(SW_PAD_GPIO1_IO02);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(SW_MUX_GPIO1_IO04);
iounmap(SW_PAD_GPIO1_IO04);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 注销字符设备驱动 */
cdev_del(&newchrled.cdev);/* 删除cdev */
unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */
device_destroy(newchrled.class, newchrled.devid);
class_destroy(newchrled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zzk");
到此驱动编写完成,在虚拟机中配置好makefile后通过make clean 与make -j32编译成功,通过命令使用modinfo xxx.ko 查看.ko版本文件信息。确定是ARM7后,通过winscp软件,直接放入到板子的/lib/modules/4.1.15-gc0de9f6目录下,并且使用chmod修改权限。
使用如下命令尝试挂载命令,若挂载成功,数码管显示6789。
cd /lib/modules/4.1.15-gc0de9f6
rmmod newchrled.ko
chmod 777 newchrled.ko
depmod
modprobe newchrled
ls /dev/newchrled -l
到此驱动完成。
若不能断电重启使用的,需重新烧写板件。
step7 烧写内核、设备树、uboot、根文件系统 到开发板方法
为了脱离虚拟机运行(教程上根文件系统都在虚拟机上),将编译好的内核zImage,设备树文件,需要烧写Linux内核镜像zImage文件,设备树文件imx6ull-alientek-emmc.dtb,Uboot文件,根文件系统。烧写步骤详见《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》39章,烧写软件使用《04、正点原子MFG_TOOL出厂固件烧录工具》,通过文件夹中Mfgtool2-eMMC-ddr512-eMMC.vbs程序进行烧写,拨码置到USB,烧写前需要拷贝imx6ull-alientek-emmc.dtb与zImage到指定目录(其他的官方已经做好了有)。烧写后方能开机运行,通过串口线连接开发板,安装CH340驱动(USB串口驱动)_XP_WIN7共用,使用putty软件进行连接,选择串口 波特率115200。进入系统后,sudo passwd命令修改root密码后可以使用xshell、winscp等工具连接linux开发板子。
step8 设置开机自启动
将之前编制好的Linux驱动文件newchrled.kod放入目录/lib/modules/4.1.15-gc0de9f6。将编译好的
c++程序vledtest放入文件夹vincent中,在/etc/rc.local文件中,设置IP与自己的开机脚本v_start.sh,
//在echo 30000下添加
PATH=/sbin:/bin:/usr/sbin:/usr/bin
ifconfig eth0 192.168.3.153 netmask 255.255.255.0
route add default gw 192.168.3.1
echo "nameserver 114.114.114.114" > /etc/resolv.conf
/vincent/v_start.sh
新建开机自启动脚本文件/vincent/v_start.sh,内容如下
#!/bin/sh
rmmod newchrled.ko
depmod
modprobe newchrled
ls /dev/newchrled -l
/vincent/vledtest /dev/newchrled
不能使用/vincent/vledtest /dev/newchrled &,驱动会出现错误,笔者也不知道为啥。想要开机挂载驱动,只有两种方法,一种是动态加载还有一种是编译进内核。
step9 对于C++应用程序的编写。
测试阶段在虚拟机中可以使用g++进行测试。
g++ hello.cpp -o hello
g++ hello.cpp -o hello -lpthread (用于多线程)
g++ hello.cpp -o hello -lstdc++
需要移植到开发板上时需要使用arm-linux-gnueabihf-gcc交叉编译
arm-linux-gnueabihf-gcc main.cpp -o vledtest -lpthread
main.cpp代码如下
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[10];
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开led驱动 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
//retvalue = write(fd, argv[2], sizeof(argv[2]));
retvalue = write(fd, argv[2], 10);
if(retvalue < 0){
printf("write data to kernel\r\n");
close(fd);
return -1;
}
retvalue = close(fd); /* 关闭文件 */
if(retvalue < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}