正点原子imx6ull使用八位数码管,Linux驱动八位静态数码管

开发步骤介绍

觉得可以的话,还请大哥们点赞,后面有完整示例代码

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;
}
  • 28
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值