最简单的驱动程序¶
点亮LED¶
GPIO的操作方法¶
1.GPIO 模块一般结构(Registers)¶
- 使能(Enable):GPIO电源/时钟控制
- 模式(Mode):配置GPIO复用
- 方向:选择GPIO是输出还是输入
- 数值:设置或读取引脚的电平状态
(1)设置CCM向GPIO模块提供时钟¶
Clock Controller Mpdule (CCM) 这里有7个寄存器CCM_CCGR0–CCM_CCGR6,这7个寄存器控制着I.MX6U的所有外设时钟开关,以 CCM_CCGR0 为例来看
(Address: 20C_4000h base + 68h offset = 20C_4068h)
该寄存器每两位控制着一个外设时钟,取值含义如下:
- 00:该GPIO模块全程被关闭
- 01:该GPIO模块在CPU run mode情况下是使能的;在WAIT或STOP模式下,关闭
- 10:保留
- 11:该GPIO模块全程使能
如果要打开GPIO2的时钟:
CCM_CCGR0=3<<30
(2)设置IOMUXC选择引脚的模式¶
选择GPIO功能(IOMUXC_SW_MUX_CTL)
(Address: 20E_0000h base + 60h offset = 20E_0060h GPIOx的base address看参考手册chapter2 : Memonry Maps 如:)
设置上下拉电阻等参数(IOMUXC_SW_PAD_CTL)
(3)设置引脚方向(GPIOx_GDIR)¶
(Address: Base address + 4h GPIOx的base address看参考手册chapter2 : Memonry Maps)
该寄存器某一位的值代表某一个引脚
(4)设置输出引脚的电平(GPIOx_DR)¶
(Address: Base address + 0h offset GPIOx的base address看参考手册chapter2 : Memonry Maps)
该寄存器某一位的值代表某一个引脚
(5)读取引脚的电平(GPIOx_PSR)¶
(Address: Base address + 8h offset GPIOx的base address看参考手册chapter2 : Memonry Maps)
该寄存器某一位的值代表某一个引脚
2.GPIO 寄存器操作¶
(1)直接读写:读出、修改对应位、写入¶
/*要设置某一位 bit n:*/
val = data_reg;
val = val | (1<<n);
data_reg = val;
/*要清除某一位 bit n:*/
val = data_reg;
val = val & ~(1<<n);
data_reg = val;
(2)set-and-clear protocol:¶
/*set_reg, clr_reg, data_reg 三个寄存器对应的是同一个物理寄存器*/
/*要设置某一位*/
bit n:set_reg = (1<<n);
/*要清除某一位*/
bit n:clr_reg = (1<<n);
驱动程序¶
应用函数与驱动函数一 一对应,应用需要什么,驱动就提供什么
驱动编程要点¶
- 确定主设备号或让内核分配
- 定义file_operations结构体
- 实现对应的open/read/write等函数,填入file_operations结构体
- 把file_operations结构体告诉内核:注册驱动程序
- 入口函数:注册驱动程序 , 安装驱动程序时,就会去调用这个入口函数
- 出口函数:卸载驱动程序时,就会去调用这个出口函数
- 提供设备信息,自动创建设备节点
标准的驱动程序模板¶
/* Regsiters 地址*/
/*
LInux中驱动程序无法直接访问寄存器的物理地址
需要经过ioremap的虚拟映射才能访问
映射过程在入口函数中进行
*/
/*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址: 配置GPIO*/
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
/*GPIO5_GDIR地址: 配置GPIO方向*/
static volatile unsigned int *GPIO5_GDIR
/*GPIO5_DR地址: 配置GPIO输出电平*/
static volatile unsigned int *GPIO5_DR
/* 1. 确定主设备号 */
static int major = 0; //主设备号为零时将由系统自动分配
static struct class *led_class;
/* 2. 定义自己的file_operations结构体 */
static struct file_operations led_drv = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
/*APP 调用 read 函数时,把驱动中保存的数据返回给 APP*/
}
/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
/*APP 调用 write 函数时,传入的数据保存在驱动中*/
char val
/* copy_from_user : get date from app */
copy_from_user(&val,buf,1) //把数据从用户空间拷贝到内核空间
/* to set gpio register : out 1/0 */
if(val == 1)
{
/* set led on */
*GPIO5_DR &= ~(1<<3);
}
else
{
/* set led off */
*GPIO5_DR |= (1<<3);
}
}
static int led_drv_open (struct inode *node, struct file *file)
{
/* 当设备文件打开时调用
* enable gpio(默认已经开启)
* configure pin as gpio
* configure gpio as output */
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf; //清除低四位,防止之前被别的函数修改
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x05; //赋值后四位0101
*GPIO5_GDIR |= (1<<3);//把GPIO5的第三位置为1,即将GPIO5_3设为输出引脚
}
static int led_drv_close (struct inode *node, struct file *file)
{
}
/* 4. 把file_operations结构体告诉内核:注册驱动程序 */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); //确保入口函数被执行
/* register_chrdev :注册字符设备驱动程序
* ioremap : 无法直接访问物理寄存器需要对其虚拟映射
* class_create :创建设备节点的辅助信息
* device_creat : 创建设备驱动节点
* */
major = register_chrdev(0, "myled", &led_drv); /* /dev/led */
/*ioremap*/
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14)
GPIO5_GDIR = ioremap(0x020AC004 + 0x14)
GPIO5_DR = ioremap(0x020AC000 + 0x14)
led_class = class_create(THIS_MODULE, "myled");
device_creat(led_class,NULL,MKDEV(major,0),NULL,"myled")
return 0;
}
/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit led_exit(void)
{
/* 入口函数加载的全都卸载 */
iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
iounmap(GPIO5_GDIR);
iounmap(GPIO5_DR);
device_destroy(led_class, MKDEV(major, i)); /* /dev/100ask_led0,1,... */
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
unregister_chrdev(major, "myled");
}
/* 7. 其他完善:提供设备信息,自动创建设备节点 */
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
应用测试¶
/* 应用函数的用法:
=> argv[0]/argv[1]/argv[2]
ledtest/dev/myled on
ledtest/dev/myled off
*/
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, status =0;
char *filename;
unsigned char databuf[1];
if(argc != 3){ //如果参数个数不为3的话,则输入错误
printf("Usage: %s <dev> <on|off>\n",argv[0]);
printf( "eg: %s /dev/myled on \n" ,argv[0]);
printf( "eg: %s /dev/myled off\n" ,argv[0]);
return -1;
}
filename = argv[1];
/* OPEN */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
/* write */
if(strcmp(argv[2], "on") == 0)
{
status = 1;
}
write(fd, &status ,1);
return 0;
}
Makefile¶
KERNELDIR := /home/yangwentao/IMX6ULL/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek #内核存放目录
CURRENT_PATH := $(shell pwd)
obj-m := led.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean