更多信息请访问:与华为官方共建的鸿蒙技术社区https://ost.51cto.com1.openHarmony是如何控制外设的?1.1裸机程序控制硬件实例——GPIO灯编程寄存器设置从BearPiMicro的原理图可以看出,它的LED灯连接到芯片的PA13引脚,低电平点亮.为了达到控制LED灯的目的,首先使能相应的GPIO时钟,通过MODER寄存器配置相应端口为输出模式,通过OTYPER设置输出类型,然后通过ODR寄存器。程序编写voidmydelay_ms(intms){volatileinti=0,j=0;while(ms--){for(i=0;i<1000;i++)for(j=0;j<1000;j++);}}intmain(){RCC_MP_AHB5ENSETR=(RCC_MP_AHB4ENSETR&=~(0xff))|0x01;GPIOA.MODER=(GPIOA.MODER&=~(0x3<<10))|0x1<<10;//设置为输出模式GPIOA.OTYPER&=~(0x1<<13);//推送输出GPIOA.OSPEEDR&=~(0x3<<10);//低速while(1)//flash{GPIOA.ODR=(GPIOA.ODR&=~(0x1<<13))|0x1<<13;我的延迟毫秒(30);GPIOZ.ODR&=~(0x1<<13);我的延迟毫秒(30);}return0;}1.2OpenHarmony控制硬件实例-GPIO点灯编程【参考:applications/BearPi/BearPi-HM_Micro/docs/device-dev/写点亮LED灯的程序.md·BearPiOpenSourceCommunity/BearPi-HM_Micro_small-Gitee.com]。[补充]:OpenHarmonyLiteOS-A内核架构图。从上图可以看出,OpenHarmonyLiteOS-A内核区分了用户空间和内核空间。后面我们会讲到用户空间和内核空间之间的数据传输。我们先看看内核空间是如何控制LED灯的。以下是大佬们对HDF驱动框架的解析,请移步观看:OpenHarmonyHDF驱动框架介绍及驱动加载过程分析-OpenHarmony技术社区-.COM。看完之后是不是对HDF驱动框架有了一定的了解呢?接下来,让我们仔细看看这个LED是如何点亮的。首先我们看一下LED驱动的注册方法://定义驱动入口的对象必须是HdfDriverEntry类型的全局变量(在hdf_device_desc.h中定义)structHdfDriverEntryg_ledDriverEntry={.moduleVersion=1,.moduleName="HDF_LED“,Bind=HdfLedDriverBind,.Init=HdfLedDriverInit,.Release=HdfLedDriverRelease,};//调用HDF_INIT在HDF框架中注册驱动入口HDF_INIT(g_ledDriverEntry);加载驱动程序时,将自动调用HdfLedDriverBind和HdfLedDriverInit函数。让我们看一下HdfLedDriverInit函数。//驱动自身业务的初始接口int32_tHdfLedDriverInit(structHdfDeviceObject*device){structStm32Mp1ILed*led=&g_Stm32Mp1ILed;int32_t退役;if(device==NULL||device->property==NULL){HDF_LOGE("%s:deviceorpropertyNULL!",__func__);返回HDF_ERR_INVALID_OBJECT;/*************************************************************************补充hcs文件内容root{LedDriverConfig{led_gpio_num=13;match_attr="st_stm32mp157_led";//该字段的值必须和device_info.hcs中deviceMatchAttr的值一致}}我们重点关注属性led_gpio_num,具体计算方法参考上一篇************************************************************************************/*读取hcs私有属性值*/ret=Stm32LedReadDrs(led,device->property);/***********************************************这是Stm32LedReadDrs函数中最重要的实现,获取LED灯的控制管脚号,读取led.hcs中led_gpio_num的值ret=drsOps->GetUint32(node,"led_gpio_num",&led->gpioNum,0);*/如果(返回!=HDF_SUCCESS){HDF_LOGE("%s:获取led设备资源失败:%d",__func__,ret);返还;}/*将GPIO引脚配置为输出*/ret=GpioSetDir(led->gpioNum,GPIO_DIR_OUT);if(ret!=0){HDF_LOGE("GpioSerDir:failed,ret%d\n",ret);返还;}HDF_LOGD("Led驱动初始化成功");returnHDF_SUCCESS;}以上是HdfLedDriverInit方法,具体操作硬件的方法请参考之前的文章1.3裸机驱动LED和OpenHarmony操作系统驱动LED有什么区别?可见裸机驱动LED是直接控制GPIO寄存器,而操作系统驱动LED。经过层层封装来控制硬件,直接操作寄存器点亮LED和通过驱动点亮LED最本质的区别就是是否使用操作系统。操作系统的存在大大降低了应用软件与硬件平台之间的耦合度。它作为我们的硬件和应用软件之间的纽带,使得应用软件只需要调用驱动接口API就可以让硬件完成需要的开发,而应用软件不需要关心硬件是如何工作的。这将大大提高我们应用程序的可移植性和开发效率。1.4操作系统如何控制具体的硬件外设?在openHarmony环境下直接访问物理内存是非常危险的。如果用户不小心修改了内存中的数据,很可能会导致错误甚至系统崩溃。为了解决这些问题,内核引入了MMU,它为编程提供了方便、统一的内存空间抽象。其实我们程序中写的变量地址就是虚拟内存中的地址。如果处理器要访问这个地址,MMU会将这个虚拟地址(VirtualAddress)翻译成实际的物理地址(PhysicalAddress),然后处理器再对实际的物理地址进行操作。MMU是一个实际的硬件,而不是软件程序。它的主要功能是将虚拟地址翻译成真实的物理地址,同时管理和保护内存。不同的进程有自己的虚拟地址空间。一个进程中的程序不能修改另一个进程使用的物理地址,这样进程之间就不会相互干扰,相互隔离。什么是虚拟地址,什么是物理地址?当MMU不使能时,CPU在读取指令或访问内存时,会直接将地址输出到芯片的引脚上。这个地址直接被内存接收。这个地址叫做物理地址,如下图所示。物理地址与硬件平台是统一的,不同的硬件平台对应的地址几乎不一样。因此,为了在操作系统中实现通用性,必须对物理地址进行统一管理,于是诞生了虚拟地址的概念。简单的说,当CPU打开MMU时,CPU发送的地址就会发送给MMU。发送给MMU的地址称为虚拟地址。之后,MMU会访问页表地址寄存器,然后去内存中寻找页表(假设只有一级页表)条目,翻译出实际的物理地址。最后总结一下,MMU是物理地址和虚拟地址之间的桥梁。Let'stakealookatthemappingrelationshipbetweenthevirtualaddressandthephysicaladdressofthemp157developmentboardfortheBearPi:??//256MB#ifdefLOSCFG_TEE_ENABLE#defineDDR_MEM_ADDR0xC1000000#defineDDR_MEM_SIZE0xf000000#else#defineDDR_MEM_ADDR0xBF00MEM_ADDR0xBF00MEM_ADDR0xBF000M0000#define_SIZend_0#defineSYS_MEM_BASEDDR_MEM_ADDR#defineSYS_MEM_SIZE_DEFAULT0x07f00000#defineSYS_MEM_END(SYS_MEM_BASE+SYS_MEM_SIZE_DEFAULT)/*Peripheralregisteraddressbaseandsize*/#definePERIPH_PMM_BASE0x40000000#definePERIPH_PMM_SIZE0x20000000/*ddrramfs*/#defineDDR_RAMFS_VBASE(PERIPH_UNCACHED_BASE+PERIPH_UNCACHED_SIZE)#defineDDR_RAMFS_ADDR(DDR_MEM_ADDR+DDR_MEM_SIZE)#defineDDR_RAMFS_SIZE(0x4000000)/*64Mramfs*/#defineGIC_VIRT_SIZEU32_C(GIC_PHY_SIZE)#defineDDR_RAMFS_REAL_SIZE(0xa00000)/*ramfsAD_ER_ELAD内核加载地址*/#/*(0xC0100000)LosArchMmuInitMappingg_archMmuInitMapping[]={{.phys=SYS_MEM_BASE,.virt=KERNEL_VMM_BASE,.size=KERNEL_VMM_SIZE,.flags=MMU_DESCRIPTOR_KERNEL_L1_PTE_FLAGS,.name="KernelCached",},{.phys=SYS_MEM_BASE,.virt=SYS_MEM_BASE,.virt=UNCACHEDSize,.virt=UNCACHEDSize_,.flags=MMU_INITIAL_MAP_NORMAL_NOCACHE,.name="KernelUncached",},{.phys=PERIPH_PMM_BASE,.virt=PERIPH_DEVICE_BASE,.size=PERIPH_DEVICE_SIZE,.flags=MMU_INITIAL_MAP_DEVICE,.name="PeriphDevice",}=,{PERIPH_MMBRI.phys,佩里phStronglyOrdered",},{.phys=GIC_PHY_BASE,.virt=GIC_VIRT_BASE,.size=GIC_VIRT_SIZE,.flags=MMU_INITIAL_MAP_DEVICE,.name="GIC",},{.phys=DDR_RAMFS_ADDR,.virt=DDR_RAMFS_VBASE,.size=DZSIDZSI,.flags=MMU_INITIAL_MAP_DEVICE,.name=“Sbull”,},{.phys=FB_PHY_BASE,.virt=FB_VIRT_BASE,.size=FB_SIZE,.flags=MMU_INITIAL_MAP_DEVICE,.name=“FB”,},{0}};挑一段代码来分析,详细分析我单独开篇文章研究MMU,我不管它{.phys=PERIPH_PMM_BASE,.virt=PERIPH_DEVICE_BASE,.size=PERIPH_DEVICE_SIZE,.flags=MMU_INITIAL_MAP_DEVICE,.name="PeriphDevice",},其中phys表示具体的物理地址,0x40000000。virt表示映射的虚拟地址空间,供内核引用。size表示地址段的最大连续长度,0x20000000。flags代表映射方式,name代表地址段的名称。当我们的内核驱动需要访问外设时,必须通过虚拟地址来访问,而mmu会根据虚拟地址来操作实际的物理地址。所以,openHarmony控制硬件的具体过程是:根据数据手册找到具体的物理地址,地址是固定地址,与数据手册密切相关。当开发板移植到openHarmony时,必须向内核提供相应的地址映射表。内核根据物理地址调用OsalIoRemap方法将物理地址转换为虚拟地址。内核只能对物理地址进行操作。2.OsalIoRemap功能分析话不多说,直接看源码:/***@briefRemaps一个I/O物理地址到它的虚拟地址。**@paramphys_addr表示I/O物理地址。*@paramsize表示要重映射的物理地址的大小。*@return返回虚拟地址。**@since1.0*@version1.0*/staticinlinevoid*OsalIoRemap(unsignedlongphys_addr,unsignedlongsize){returnioremap(phys_addr,size);}可以看到,这个函数可以将实际地址转换成虚拟地址内核可以使用的地址。@paramphys_addr:物理地址@paramsize:length@return为空指针,即虚拟地址使用示例://寄存器地址映射stm32gpio->regBase=OsalIoRemap(stm32gpio->gpioPhyBase,stm32gpio->groupNum*stm32gpio->gpioRegStep);if(stm32gpio->regBase==NULL){HDF_LOGE("%s:errremapphy:0x%x",__func__,stm32gpio->gpioPhyBase);返回HDF_ERR_IO;}/*OsalIoRemap:重映射寄存器*/stm32gpio->exitBase=OsalIoRemap(stm32gpio->irqPhyBase,stm32gpio->iqrRegStep);if(stm32gpio->exitBase==NULL){dprintf("%s:OsalIoRemap失败!",__func__);返回-1;}使用OsalIoRemap方法将物理地址转换为虚拟地址后,内核就可以随心所欲地控制硬件了。更多信息请访问:与华为官方共建的鸿蒙技术社区https://ost.51cto.com
