LZ的硬件仿真平台的基本思想是用软件来模拟硬件外设,外围设备的行为,使应用程序运行在仿真的软件平台上和运行在实际的硬件板及真实环境中达到一样的效果,这样应用程序只需要做的平台无关,就可以在仿真硬件元器件和实际元器件不同的平台上很小代价的转移,我们开发产品的应用逻辑就可以不依赖于正式的硬件板或测试条件,因此做到将应用逻辑平台无关代码从软件中剥离出来在PC上开发,等实际硬件办出来之后再开发驱动和操作系统,最后把应用和驱动整合起来整机调试和验证。对于测试来说,开发内部业务逻辑测试都可以基于仿真平台,甚至测试组第一轮的
features测试都可以不基于实际的产品。
像大多数嵌入式软件架构一样,这里需要一个至关重要的HAL层将平台相关代码封装,向应用层提供统一的接口。所有应用层代码只运行调用HAL层接口,绝对禁止跨过HAL层直接调用操作系统和驱动的原始接口 (头文件也禁止直接引用平台相关头文件,在HAL层有统一的抽象头文件引用来隐藏底层调用细节)。
这里的HAL层主要包括两部分内容,操作系统的封装和硬件驱动的封装,因为LZ开发这个软件和框架仅仅为了我们产品的需要,因此以实际需要为主,开发的东西都是实际产品开发中用到的,因此没有做到大而全,用到什么写什么。
到目前为止,我封装了2个RTOS的操作系统, micro-c的ucos和greenhills的u-velosity,这是我们产品实际用到的两个操作系统,LZ最近在研究ecos,回头也加入到这个框架当中。
操作系统主要封装一些嵌入式OS常用的接口,重新定义所有类型定义和宏,task create, halt, sleep, run, resume, stop, event, messages, sempohore, mailbox, mutex, ISR On/Off等。下面是一个最常用的create task封装实现。
/** * @brief OS create task interface * @param t - task handle * task - task entry * arg - task parameter * name - task desc * prio - task priority * stack - stack address * stacksize - stack size * @retval : error code */ CPS_ERROR_CODE CPSCreateTask(CPS_TASK *t, CPS_TASK_RES (*task)(CPS_TASK_PARAM arg), CPS_TASK_PARAM arg, const char *name, int32_t prio, CPS_STACK_ADDR stack, int32_t stacksize) { #ifdef OS_UCOSII int8_t res = OSTaskCreateExt (task, arg, &stack[stacksize/sizeof(OS_STK) - 1], (int8_t)prio, (int16_t)prio, &stack[0], stacksize/sizeof(OS_STK), NULL, OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR); if(res == CPS_ERR_NONE) { *t = prio; OSTaskNameSet((int8_t)prio, (char *)name, &res); } return (CPS_ERROR_CODE)res; #endif /* OS_UCOSII */ #ifdef OS_GHS_UVELOSITY return gh_task_create(t, name, task, arg, stack, stacksize, prio, GH_AUTO_START); #endif /*OS_GHS*/ }
硬件驱动的封装包括产品用到的所有外设和内部元器件的驱动接口,比如ADC, nor/nand flash, FRAM, MRAM, GPIO, LED, LCD, OLED, USB, Timers, RTC, keyboard, serial port, tcp/ip port, battery等等,反正用到什么添什么,目前为止以完成上面列出的所有驱动的封装,LZ产品主要基于STM系列,目前驱动封装覆盖了对stm8, stm32L152, stm32F103, stm32F207, stm32F407系列的支持。另外最终的是为了实现用软件仿真硬件行为,所有对应用开放的接口都支持仿真平台的实现,也就是说每个接口都至少有仿真软件的实现和实际硬件板的驱动实现,来看一个flash擦除扇区在STM32F407上的实现
/** * @brief Flash erase section * @param id - flash ID number * sec - section number * @retval : 0 - success */ int hwFlashEraseSec(FlashID_t id, int32_t sec) { if(id >= FLASH_NUM) return -1; #ifdef USE_SIMU_SUPPORT return SDSEraseSec((int32_t)id,(int32_t)sec); #else if(id == HW_FRAM) { } else if(id == HW_INFLASH) { FlashSec_t SecNo = (FlashSec_t)sec; if(SecNo >= FlashSecNum) return -2; /* Clear All pending flags */ FLASH_ClearFlag( FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR); if(FLASH_EraseSector((uint32_t)SecTbls[(int)SecNo].secNo, VoltageRange_3) != FLASH_COMPLETE) return -2; } #endif return 0; }
这里USE_SIMU_SUPPORT宏代表当前运行在PC软件上,驱动接口调用仿真软件驱动SDSEraseSec(id, sec)来接管当前驱动;如果是运行在实际硬件上,首先判断flash id类型,如果是FRAM,不需要擦除操作,直接返回;如果是内部NOR FLASH,调用STM32F4 FLASH驱动实现。
这样当需要新的OS,新的MCU,新的驱动时,只需要针对现有的HAL接口实现或者添加新的实现,而不需要修改一行应用层代码。也正因为有了HAL层的封装,应用层完全不知道自己运行在什么平台上,PC或不同的MCU上,让我们可以在PC硬件仿真平台上完成所有应用业务逻辑的开发和测试。
后面的部分,LZ将介绍硬件仿真平台PC软件部分以及驱动接口实现部分。