1.具体单板的按键驱动程序(查询方式)
1.1 GPIO操作回顾
参考章节《第四章 普适的GPIO引脚操作方法》、《第五章 具体单板的GPIO操作方法》。
1.2 百问网IMX6ULL的按键驱动程序(查询方式)
1.2.1 先看原理图确定引脚及操作方法
平时按键电平为高,按下按键后电平为低。 按键引脚为GPIO5_IO01、GPIO4_IO14。
注意:视频里使用QEMU来做实验,QEMU里的按键平时为低电平,按下后为高电平,跟实际开发板刚好相反。
1.2.2 再看芯片手册确定寄存器及操作方法
步骤1:使能GPIO5
步骤二:使能GPIO4
设置CCM_CCGR1 b[31:30]、CCM_CCGR3 b[13:12]就可以使能GPIO5、GPIO4,设置为什么值呢?
注意:在imx6ullrm.pdf中,CCM_CCGR1的b[31:30]是保留位;我以前写程序时错用了imx6ul(不是imx6ull)的手册,导致程序中额外操作了这些保留位。不去设置b[31:30],GPIO5也是默认使能的。
看下图,设置为0b11:
① 00:该GPIO模块全程被关闭
② 01:该GPIO模块在CPU run mode情况下是使能的;在WAIT或STOP模式下,关闭
③10:保留
④ 11:该GPIO模块全程使能
步骤3:设置GPIO5_IO01、GPIO4_IO18为GPIO模式
① 对于GPIO5_IO01,设置如下寄存器:
② 对于GPIO4_IO14,设置如下寄存器:
步骤4:设置GPIO5_IO01、GPIO4_IO14为输入引脚,读取引脚电平
寄存器地址为:
设置方向寄存器,把引脚设置为输出引脚:
读取引脚状态寄存器,得到引脚电平:
1.2.3 编程
1.2.3.1 程序框架
使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\ 05_嵌入式Linux驱动开发基础知识\source\ 04_button_drv\02_button_drv_for_boards\05_button_drv_for_100ask_imx6ull
1.2.3.2 硬件相关的代码
主要看board_100ask_imx6ull.c。
涉及的寄存器挺多,一个一个去执行ioremap效率太低。 先定义结构体,然后对结构体指针进行ioremap。
对于IOMUXC,可以如下定义:
struct iomux { volatile unsigned int unnames[23]; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO01; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO02; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO05; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO06; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO07; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO08; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO09; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B; };
对于GPIO,可以如下定义:
struct imx6ull_gpio { volatile unsigned int dr; volatile unsigned int gdir; volatile unsigned int psr; volatile unsigned int icr1; volatile unsigned int icr2; volatile unsigned int imr; volatile unsigned int isr; volatile unsigned int edge_sel; }; struct imx6ull_gpio gpio4 = ioremap(0x020A8000, sizeof(struct imx6ull_gpio)); struct imx6ull_gpio gpio5 = ioremap(0x20AC000, sizeof(struct imx6ull_gpio));
看一个驱动程序,先看它的入口函数,代码如下。
第127行向上层驱动注册一个button_operations结构体,该结构体在第119~123行定义。
119 static struct button_operations my_buttons_ops = { 120 .count = 2, 121 .init = board_imx6ull_button_init, 122 .read = board_imx6ull_button_read, 123 }; 124 125 int board_imx6ull_button_drv_init(void) 126 { 127 register_button_operations(&my_buttons_ops); 128 return 0; 129 }
button_operations结构体中有init函数指针,它指向board_imx6ull_button_init函数,在里面将会初始化LED引脚:使能、设置为GPIO模式、设置为输出引脚。代码如下。
值得关注的是第71~78行,对于寄存器要先使用ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器。
50 /* enable GPIO4 */ 51 static volatile unsigned int *CCM_CCGR3; 52 53 /* enable GPIO5 */ 54 static volatile unsigned int *CCM_CCGR1; 55 56 /* set GPIO5_IO03 as GPIO */ 57 static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1; 58 59 /* set GPIO4_IO14 as GPIO */ 60 static volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B; 61 62 static struct iomux *iomux; 63 64 static struct imx6ull_gpio *gpio4; 65 static struct imx6ull_gpio *gpio5; 66 67 static void board_imx6ull_button_init (int which) /* 初始化button, which-哪个button */ 68 { 69 if (!CCM_CCGR1) 70 { 71 CCM_CCGR1 = ioremap(0x20C406C, 4); 72 CCM_CCGR3 = ioremap(0x20C4074, 4); 73 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 = ioremap(0x229000C, 4); 74 IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B = ioremap(0x20E01B0, 4); 75 76 iomux = ioremap(0x20e0000, sizeof(struct iomux)); 77 gpio4 = ioremap(0x020A8000, sizeof(struct imx6ull_gpio)); 78 gpio5 = ioremap(0x20AC000, sizeof(struct imx6ull_gpio)); 79 } 80 81 if (which == 0) 82 { 83 /* 1. enable GPIO5 84 * CG15, b[31:30] = 0b11 85 */ 86 *CCM_CCGR1 |= (3<<30); 87 88 /* 2. set GPIO5_IO01 as GPIO 89 * MUX_MODE, b[3:0] = 0b101 90 */ 91 *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 = 5; 92 93 /* 3. set GPIO5_IO01 as input 94 * GPIO5 GDIR, b[1] = 0b0 95 */ 96 gpio5->gdir &= ~(1<<1); 97 } 98 else if(which == 1) 99 { 100 /* 1. enable GPIO4 101 * CG6, b[13:12] = 0b11 102 */ 103 *CCM_CCGR3 |= (3<<12); 104 105 /* 2. set GPIO4_IO14 as GPIO 106 * MUX_MODE, b[3:0] = 0b101 107 */ 108 IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B = 5; 109 110 /* 3. set GPIO4_IO14 as input 111 * GPIO4 GDIR, b[14] = 0b0 112 */ 113 gpio4->gdir &= ~(1<<14); 114 } 115 116 }
button_operations结构体中还有有read函数指针,它指向board_imx6ull_button_read函数,在里面将会读取并返回按键引脚的电平。代码如下。
118 static int board_imx6ull_button_read (int which) /* 读button, which-哪个 */ 119 { 120 //printk("%s %s line %d, button %d, 0x%x\n", __FILE__, __FUNCTION__, __LINE__, which, *GPIO1_DATAIN); 121 if (which == 0) 122 return (gpio5->psr & (1<<1)) ? 1 : 0; 123 else 124 return (gpio4->psr & (1<<14)) ? 1 : 0; 125 } 126
1.2.4 测试
先启动IMX6ULL挂载NFS文件系统。 安装驱动程序之后执行测试程序,观察它的返回值(执行测试程序的同时操作按键):
# insmod button_drv.ko # insmod board_drv.ko # insmod board_100ask_imx6ull.ko # ./button_test /dev/100ask_button0 # ./button_test /dev/100ask_button1
1.2.5 课后作业
① 修改button_test.c,使用按键来点灯