开发步骤介绍
觉得可以的话,还请大哥们点赞,后面有完整示例代码
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 (