1.原理
32位机对应2^32(4G)位虚拟内存空间,我们的开发板含有512M物理内存,linux内核启动会初始化MMU(内存管理单元),MMU会将这512M的物理空间映射到这4G的空间上。我们通过向虚拟内存写数据,操作物理内存。
我们想要知道物理内存对应的虚拟内存地址,涉及两个API函数ioremap和iounmap;
ioremap:用于获取物理地址空间对应对应的虚拟地址空间。
函数原型:
__arm_ioremap为真实函数对应原型:
参数1:cookiie物理空间起始地址
参数2:size:映射空间的大小
参数3:mtype:ioremap 的类型,可以选择 MT_DEVICE、MT_DEVICE_NONSHARED、
MT_DEVICE_CACHED 和 MT_DEVICE_WC,ioremap 函数选择 MT_DEVICE。
返回值:void __iomen*:它表示返回值为虚拟空间的viod*类型
用法举例:想找到 I.MX6ULL 的 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器对应
的虚拟地址。IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的实际起始地址:0X020E0068;i.MX寄存器的大小位4字节
iounmap:用于取消内存映射,再卸载驱动时使用。
函数原型:
参数:要取消映射虚拟空间的首地址。
I/O内存:由外部寄存器或内存映射到虚拟内存空间的内存
得到虚拟内存的地址后,可以直接操作该地址,但linux内核建议使用一组操作函数来使用
1.读:
该组函数用于读取8,16,32位数据,addr是要读取数据的地址,返回值是读取的数据
2.写:
实际操作led过程
控制GPIO1_IO03的寄存器地址如下:
定义虚拟内存指针并获取映射地址
有虚拟地址后,我们就可以像操作裸机一样通过配置寄存器控制该引脚了,先是使能时钟,将功能复用为GPIO功能,再设置IO属性,最后将该引脚设置为输出功能。
接下来,我们就是搭建驱动模块,和完成file_operetions结构体,在驱动的读函数中将这些功能设置完成,然后构建字符设备驱动。
最后编写测试APP,通过向我们的设备文件/dev/led写0或1来控制led灯亮或熄灭。
驱动源码
测试APP比较简单,就是向我们创建的设备文件/dev/led写入0或1,将它通过file_operations的.write写入到驱动,从而控制led
源码如下
结果:向/dev/led写0,led熄灭
向/dev/led写1点亮led
总结:
系统对内存的地址进行重定义,让重定义后的地址组合成一块连续的内存,它的最大空间就是cpu的最大寻址空间,对于32位机来说,这个空间的范围是4GB,在每个进程创建时,内核会为这个进程分配4G的内存,而我们嵌入式开发需要的是具体某个外设寄存的地址,由单片机的物理内存到虚拟内存的映射是由内核的MMU完成的,我们只需要获取到真实寄存器地址对应的虚拟内存地址,然后往虚拟地址对应的内存写入值,从而也是向寄存器写值,控制外设。