电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
【野火i.MX6ULL ARM Linux开发板连载】GPIO开发之点灯
分 享
扫描二维码分享
【野火i.MX6ULL ARM Linux开发板连载】GPIO开发之点灯
野火
瑟寒凌风
关注
发布时间: 2021-01-28
丨
阅读: 545
本文是在字符驱动的基础上,再进行的GPIO控制,所以本篇文章不再详细叙述申请及释放设备号、 添加以及注销设备,初始化、添加与删除cdev结构体等操作。本篇的主要目的是控制GPIO,源码全部贴出来。 # 裸机开发与linux驱动开发区别 在裸机操作下,裸机驱动一般针对没有操作系统支持的层面,不用考虑操作系统对它的调用。 Linux驱动是在裸机驱动基础上,按照一定的规范来实现, 虽然实现的都是同一个东西,不过你发现在 Linux驱动搀杂了许多维护信息。Linux设备驱动就是比裸机驱动多了一些框架。 在裸机方式下,ARM的软件集成开发环境就显得极为重要,因为在这种方式下可以把所有代码都放在这个环境里面编写、编译和调试。在这种方式下测试驱动程序,首先要完成CPU的初始化,然后把需要测试的程序装载到系统的RAM区/或者SDRAM中。当然,如果需要处理一些复杂的中断处理的话,最好也把CPU的复位向量表放到RAM区中。把所有程序都调试好之后,再把最后的程序烧写到Flash里面去执行。 有linux操作系统的存在大大降低了应用软件与硬件平台的耦合度,它充当了我们硬件与应用软件之间的纽带, 使得应用软件只需要调用驱动程序接口API就可以让硬件去完成要求的开发,而应用软件则不需要关心硬件到底是如何工作的。 这将大大提高我们应用程序的可移植性和开发效率。 操作系统能够带来多任务并发机制。 # 传说中的MMU 说到MMU,大家都不陌生,相对于计算机来说,这个东西就是我们说的内存,在linux环境直接访问物理内存是很危险的,如果用户不小心修改了内存中的数据,很有可能造成错误甚至系统崩溃。 MMU是 MemoryManagementUnit 的缩写即,内存管理单元。针对各种CPU, MMU是个可选的配件。MMU负责的是虚拟地址与物理地址的转换。提供硬件机制的内存访问授权。 MMU 的作用: 1. 将虚拟地址翻译成为物理地址,然后访问实际的物理地址; 2. 访问权限控制。 当没有启用MMU的时候,CPU在读取指令或者访问内存时便会将地址直接输出到芯片的引脚上,此地址直接被内存接收,这段地址称为物理地址。当CPU开启了MMU时,CPU发出的地址将被送入到MMU,被送入到MMU的这段地址称为虚拟地址, 之后MMU会根据去访问页表地址寄存器然后去内存中找到页表(假设只有一级页表)的条目,从而翻译出实际的物理地址。 # 地址转换 设备通常会提供一组寄存器来用于控制设备、读写设备和获取设备状态,即控制寄存器、数据寄存器和状态寄存器。这些寄存器可能位于 I/O 空间,也可能位于内存空间。当位于 I/O 空间时,通常被称为 I/O 端口,位于内存空间时,对应的内存空间被称为 I/O 内存。 内存管理单元(MMU)通常以页为单位进行处理,而不是字节,ioremap函数也同样属于页映射。 ## ioremap()函数 在内核中访问 I/O 内存之前,需首先使用 ioremap()函数将设备所处的物理地址映射到虚拟地址。ioremap()的原型如下: ```c void *ioremap(unsigned long offset, unsigned long size); ``` 参数: 1.物理地址 2.要映射的空间的大小 返回值:页映射,返回虚拟地址。 同一物理地址可以多次ioremap映射,分配的虚拟空间地址各部相同,iounmap互不影响。 访问I/O内存的正确方式是通过一系列专用于此目的的函数(在
中定义的): ```c unsigned int ioread8(void*addr); unsigned int ioread16(void*addr); unsigned int ioread32(void*addr); ``` ```c /@@*addr是从ioremap获得的地址(可能包含一个整型偏移量),返回值是从给定I/O内存读取的值*/ /@@*对应的I/O内存写函数*/ void iowrite8(u8value,void*addr); void iowrite16(u16value,void*addr); void iowrite32(u32value,void*addr); ``` ```c /@@*读和写一系列值到一个给定的I/O内存地址,从给定的buf读或写count个值到给定的addr*/ void ioread8_rep(void *addr, void *buf, unsigned long count); void ioread16_rep(void *addr, void *buf, unsigned long count); void ioread32_rep(void *addr, void *buf, unsigned long count); void iowrite8_rep(void *addr, const void *buf, unsigned long count); void iowrite16_rep(void *addr, const void *buf, unsigned long count); void iowrite32_rep(void *addr, const void *buf,unsigned long count); ``` 注意,在linux下有以下旧的接口,虽然仍然能够使用,但是不建议用,比如如下api: ```c unsigned readb(address); unsigned readw(address); unsigned readl(address); void writeb(unsigned value,address); void writew(unsigned value,address); void writel(unsigned value,address); ``` ## iounmap函数 iounmap函数用于取消ioremap()所做的映射,原型如下: void iounmap(void *addr) 函数参数和返回值如下: ```c void iounmap(void *addr) ``` 参数: addr: 需要取消ioremap映射之后的起始地址(虚拟地址)。 返回值: 无 # 技术手册资料 本次使用开发板上的三色灯进行控制。 ## 硬件电路 查阅硬件电路图,电路图如下 ![](https://cf04.ickimg.com/bbsimages/202101/7d347d2b7dfd1210ef6b0da18033a99e.jpg) ![](https://cf04.ickimg.com/bbsimages/202101/8356572a372f7328882bca02845fa85f.jpg) ![](https://cf04.ickimg.com/bbsimages/202101/df6c095d31adeafab031447348a95422.jpg) 对GPIO操作来说,主要分为如下几个步骤: 1. 使能GPIO时钟 2. 设置引脚功能复用为GPIO 3. 设置引脚的上下拉,速率,驱动能力等 4. 控制GPIO引脚的输出电平,高或者低 ## 寄存器地址 这里就不详细叙述如何查阅资料寻找寄存器地址了,下面直接给出寄存器地址: 使能GPIO时钟,寄存器地址0x20C406C,该寄存器的27-26位设置如下可获取不同属性: 00:所有模式下都关闭外设时钟 01:只有在运行模式下打开外设时钟 10:保留 11:除了停止模式以外,该外设时钟全程使能 代码中该部分实现如下图 ![](https://cf04.ickimg.com/bbsimages/202101/cc6594cf3be466c06549916bf33e3d68.jpg) 设置引脚功能复用为GPIO,寄存器地址0x20E006C,关于该复用的功能如下图 ![](https://cf04.ickimg.com/bbsimages/202101/f0262ad611839ff12e34d0d3849f59f5.jpg) 由此可见,需要配置成GPIO,需要设置模式ALT5.该部分代码实现如下 ![](https://cf04.ickimg.com/bbsimages/202101/843cbfac141ad09a5d905d99be5ec548.jpg) 设置引脚的上下拉,速率,驱动能力等,寄存器地址0x20E02F8,该部分功能较多 HYS(bit16):用来使能迟滞比较器 。 PUS(bit15-bit14):用来设置上下拉电阻大小。 PUE(bit13):当 IO 作为输入的时候,这个位用来设置 IO 使用上下拉还是状态保持器。 PKE(bit12):用来使能或者禁止上下拉/状态保持器功能。 ODE(bit11):IO 作为输出的时候,此位用来禁止或者使能开漏输出。 SPEED(bit7-bit6):当 IO 用作输出的时候,此位用来设置 IO 速度。 DSE(bit5-bit3):当 IO 用作输出的时候用来设置 IO 的驱动能力。 SRE(bit0):设置压摆率 我们对该寄存器写入0x1F838。 代码如下 ![](https://cf04.ickimg.com/bbsimages/202101/23ab26c60cfe21051341473eef2b2e7f.jpg) 控制GPIO引脚的输出电平的寄存器地址为baseaddr+0x04,直接写入0表示输入,写入1表示输出。 ![](https://cf04.ickimg.com/bbsimages/202101/6f6cc842c3917a0b00f6f8cbf415a3e6.jpg) 实际操作下,在/dev/下出现多个led设备节点,如下图 ![](https://cf04.ickimg.com/bbsimages/202101/6aa3ab519d855169c68522b236611b3c.jpg) 执行后的效果如下图 ![](https://cf04.ickimg.com/bbsimages/202101/51d62d3626925681985d9ceed77a79b8.jpg) # 代码 如下是led驱动代码 ```c #include
#include
#include
#include
#include
#include "linux/cdev.h" #include
#include
#include
#include
/@@* 使能GPIO时钟 设置引脚复用为GPIO 设置引脚属性(上下拉、速率、驱动能力) 控制GPIO引脚输出高低电平 GPIO1_IO04:0x020C406C,0x20E006C,0x020E02F8,0x0209C004 GPIO4_IO20: GPIO4_IO19: */ #define BUFFSIZE 64 #define DEV_CNT 3 int major = 0; int minor = 0; dev_t devno = 0; char buf[BUFFSIZE] = {0}; static struct class *led_class; struct led_chardev{ struct cdev dev; unsigned int __iomem *va_dr; unsigned int __iomem *va_gdir; unsigned int __iomem *va_iomuxc_mux; unsigned int __iomem *va_ccm_ccgrx; unsigned int __iomem *va_iomux_pad; unsigned long pa_dr; unsigned long pa_gdir; unsigned long pa_iomuxc_mux; unsigned long pa_ccm_ccgrx; unsigned long pa_iomux_pad; unsigned int led_pin; unsigned int clock_offset; }; static struct led_chardev led_cdev[DEV_CNT] = { { .pa_dr = 0x0209C000, .pa_gdir = 0x0209C004,//输出电平,-0:输入 -1:输出 .pa_iomuxc_mux =0x20E006C,//引脚复用GPIO .pa_ccm_ccgrx = 0x20C406C,//gpio1时钟 .pa_iomux_pad =0x20E02F8,//引脚属性 .led_pin = 4, .clock_offset = 26 }, { .pa_dr = 0x20A8000, .pa_gdir = 0x20A8004, .pa_iomuxc_mux =0x20E01E0, .pa_ccm_ccgrx = 0x20C4074, .pa_iomux_pad =0x20E046C, .led_pin = 20, .clock_offset = 12 }, { .pa_dr = 0x20A8000, .pa_gdir = 0x20A8004, .pa_iomuxc_mux =0x20E01DC, .pa_ccm_ccgrx = 0x20C4074, .pa_iomux_pad =0x20E0468, .led_pin = 19, .clock_offset = 12 }, }; ssize_t led_read(struct file *file, char __user *data, size_t length, loff_t *loff) { int ret = 0; printk(KERN_EMERG "[ KERN_EMERG ] led Module led_read\n"); for(ret=0;ret
private_data; val = ioread32(led_cdev->va_dr); if (ret == 0) val &= ~(0x01 << led_cdev->led_pin); else val |= (0x01 << led_cdev->led_pin); iowrite32(val, led_cdev->va_dr); *loff += tmp; return tmp; } int led_open(struct inode *inode, struct file *file) { unsigned int val = 0; printk(KERN_EMERG "[ KERN_EMERG ] led Module led_open\n"); struct led_chardev *led_cdv = (struct led_chardev*)container_of(inode->i_cdev,struct led_chardev,dev); file->private_data = led_cdev; /@@* 实现地址映射 */ led_cdev->va_dr = ioremap(led_cdev->pa_dr, 4);//数据寄存器映射,将led_cdev->va_dr指针指向映射后的虚拟地址起始处,这段地址大小为4个字节 led_cdev->va_gdir = ioremap(led_cdev->pa_gdir, 4);//方向寄存器映射 led_cdev->va_iomuxc_mux = ioremap(led_cdev->pa_iomuxc_mux, 4);//端口复用功能寄存器映射 led_cdev->va_ccm_ccgrx = ioremap(led_cdev->pa_ccm_ccgrx, 4);//时钟控制寄存器映射 led_cdev->va_iomux_pad = ioremap(led_cdev->pa_iomux_pad, 4);//电气属性配置寄存器映射 val = ioread32(led_cdev->va_ccm_ccgrx); val = val | (3 << led_cdev->clock_offset);//置位对应的时钟位 iowrite32(val,led_cdev->va_ccm_ccgrx); iowrite32(5,led_cdev->va_iomuxc_mux);//复用为GPIO val = ioread32(led_cdev->va_gdir); val &= ~(1 << led_cdev->led_pin); val |= (1 << led_cdev->led_pin); iowrite32(val, led_cdev->va_gdir); //配置位输出模式 val = ioread32(led_cdev->va_dr); val |= (0x01 << led_cdev->led_pin); iowrite32(val, led_cdev->va_dr); //输出高电平 return 0; } int led_release(struct inode *inode, struct file *file) { printk(KERN_EMERG "[ KERN_EMERG ] led Module led_release\n"); struct led_chardev *led_cdev = (struct led_chardev *)container_of(inode->i_cdev, struct led_chardev, dev); /@@* 释放ioremap后的虚拟地址空间 */ iounmap(led_cdev->va_dr); //释放数据寄存器虚拟地址 iounmap(led_cdev->va_gdir); //释放输入输出方向寄存器虚拟地址 iounmap(led_cdev->va_iomuxc_mux); //释放I/O复用寄存器虚拟地址 iounmap(led_cdev->va_ccm_ccgrx); //释放时钟控制寄存器虚拟地址 iounmap(led_cdev->va_iomux_pad); //释放端口电气属性寄存器虚拟地址 return 0; } struct file_operations fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .write = led_write, .read = led_read, }; static int __init led_init(void) { int ret = 0; int i = 0; int j = 0; printk(KERN_EMERG "[ KERN_EMERG ] led Module Init\n"); ret = alloc_chrdev_region(&devno, 0,DEV_CNT, "led"); if(ret == 0) printk(KERN_EMERG "[ KERN_EMERG ] baseminor ok\n"); else goto out; major = MAJOR(devno); minor = MINOR(devno); led_class = class_create(THIS_MODULE,"led"); if(led_class < 0) goto out; for(i=0;i
#include
#include
int main() { char buf[6] = {0}; int val = 0; char *hello = "/dev/led_chrdev1"; int fd = open(hello,O_RDWR|O_NDELAY); if(fd < 0) { printf("open error\n"); return 0; } while(1) { write(fd,&val,5); sl
eep(1); val = !val; } close(fd); return 0; } ```
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
0
)
瑟寒凌风
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交