AMetal是广州周立功科技股份有限公司开发的一套轻量级嵌入式开发平台,它为各种外设定义了统一的抽象接口,使应用程序与芯片底层可以完全分离,轻松实现“跨平台”复用。除此之外,AMetal还致力于为用户提供大量“可裁剪、可替换、可配置”的组件,提升开发的灵活性。
目前Amteal原生支持的MCU情况如下:
理论上,Ametal支持所有的MCU,但是官方仅推出了以上表格中所列举的MCU(PS:居然都没有STM32???)的硬件层驱动,如果想移植到其他MCU上,需要自己实现硬件层驱动,实际上,同一内核的MCU硬件层驱动实现方法类似,移植起来难度不是很大,就是比较繁琐,这也是我接下来需要完成的工作。
接下来就进入正题,开始移植Ametal到STM32F103RCT6芯片上,由于STM32F103RCT6是Cortex-M3内核,所以我决定在同是M3内核的ZLG217芯片的源码基础上进行修改移植,这样就大大降低了工作量。
一、获取源码
AMetal 的源码已在 github及码云上进行开源,源码地址为:github gitee
Down下来的Ametal源码目录如下:
AMetal 根目录结构:
documents目录下有很多Ametal的说明与应用文档以及API参考手册等。我们可以将documents目录下的文档都通读一遍,简单了解下Ametal,有利于后面的移植工作。board目录下是各种开发板的模版工程,由于我们是基于ZLG217开发板进行移植,所以我们需要打开ZLG217下的模版工程,打开路径:ametal\board\am217_core\project_example\projects_keil5的keil5工程。打开main.c(user_code文件夹下)
工程目录结构说明:
文件夹 | 说明 |
---|---|
bsp_common | 通用的板级支持文件,例如C库的适配文件“am_bsp_armlib.c” 、系统堆的适配文件“am_bsp_system_heap.c”等。 |
arm | ARM 内核相关的文件,如 NVIC、SysTick 等文件。 |
drivers | 通用外设及模块的标准驱动文件,如按键、矩阵键盘、温度传感器模块、ZigBee 模块等。 |
libc | C 库适配器文件,如 armlib_adpater,主要用于适配相应的 C 库。 |
service | 通用服务组件的驱动文件,如 ADC、蜂鸣器等。 |
util | AMetal 通用辅助工具文件,如堆管理器、软件定时器以及打印输出函数等。 |
soc | 包含了与 MCU 密切相关的文件,包括硬件层和驱动层文件。 |
examples_board | 板级例程,它们调用驱动层和硬件层的例层,控制开发板的各个硬件 |
examples_components | 常用芯片的例程 如常见的 flash 芯片“EP24Cxx”和“MX25xx”等。 |
examples_soc | 硬件层例程,这些例程通过调用硬件层函数实现。 |
examples_std | 驱动层例程,主要通过调用驱动层的函数来实现 |
startup | 内核的启动代码,是一个汇编文件 |
user_code | 用户代码 如main.c |
user_config | 用户配置文件 每个外设都对应一个配置文件 |
这个工程包含很多例程(examples_开头的文件夹),以及很多外设和模块的驱动,这些都不需要修改,因为它们都是调用标准接口实现的,与具体的MCU无关。对于移植工作,我们需要重点关注的就是soc文件夹下的文件,它包括硬件层和驱动层文件,这些文件是与具体的MCU息息相关的。
刚刚我们注意到main.c这个文件中并没有main函数,而是am_main函数,实际上,在Ametal中,main函数在user_config文件夹下的am_prj_config.c文件中,这里的main函数的主要作用是完成时钟、NVIC、GPIO以及板级外设初始化,并在函数的最后调用am_main函数。这样做的好处是屏蔽了硬件,应用开发者无需关心底层硬件,真正意义上做到了应用和硬件分离。
AMetal共分为三层,硬件层、驱动层和标准接口层,这三层对应的接口均可被应用程序使用。硬件层对SOC做最原始封装;驱动层在硬件层的基础上进一步封装,简化对外设的操作;标准接口层提取出了一套标准API接口,用户使用这些标准API接口开发程序,即可做到一次编程、终生使用、跨平台。
Ametal架构如下图所示:
硬件层(HardWare):硬件层对 SOC做最原始封装,其提供的 API 基本上是直接操作寄存器的内联函数,效率最高。当需要操作外设的特殊功能,或者对效率等有需求时,可以调用硬件层 API。硬件层等价于传统SOC原厂的裸机包。硬件层接口使用amhw_/AMHW_ +芯片名作为命名空间,如 amhw_zlg116、AMHW_ZLG116。
驱动层(Driver):驱动层在硬件层的基础上做了进一步封装,进一步简化对外设的操作。驱动层分为标准驱动和非标准驱动,前者是指GPIO、UART、ADC、SPI、I2C等常见外设驱动,这类驱动通常只会提供一个初始化函数(例如,ZLG116 的 ADC 标准驱动提供的初始化函数命名形形式可能为:am_zlg116_adc_init()),初始化后即可使用标准接口操作相应的外设。
后者是指DMA等特殊外设,它未能实现标准化接口,它需要用户自定义接口供应用程序调用。
标准接口层(Standard API):标准接口层对常见外设的操作进行了抽象,提出了一套标准 API 接口,可以保证在不同的硬件上,标准 API 的行为都是一样的,接口命名空间 am_/AM_。用户使用一个 GPIO 的过程:先调用驱动初始化函数,后续在编写应用程序时仅需直接调用标准接口函数即可。可见,应用程序基于标准 API 实现的,标准 API 与硬件平台无关,使得应用程序可以轻松的在不同的硬件平台上运行,传统实现是直接实现接口。
二、Ametal思想
Ametal的核心思想是面向对象的思想,同时提出了服务(Handle)的概念,所有的设备都被抽象成一个标准服务,也就是一个对象,比如LED设备、UART设备、SPI设备等等。标准接口都是基于这些服务实现的,因为服务是抽象的,它与具体芯片没有任何关系,所以标准接口也与具体芯片没有关系,这样就实现了跨平台性。
下面是一段uart设备的标准接口代码:
int am_uart_poll_send (am_uart_handle_t handle,
const uint8_t *p_txbuf,
uint32_t nbytes)
参数 | 说明 |
---|---|
handle | UART标准服务 |
p_txbuf | 发送缓存区 |
nbytes | 发送数据长度 |
UART标准服务由底层UART设备初始化代码提供:
/** UART1实例初始化,获得uart1标准服务句柄 */
am_uart_handle_t am_zlg217_uart1_inst_init (void)
{
return am_zlg_uart_init(&__g_uart1_dev, &__g_uart1_devinfo);
}
am_uart_handle_t 的定义如下:
/**
* \brief UART服务
*/
typedef struct am_uart_serv
{
/** \brief UART驱动函数结构体指针 */
struct am_uart_drv_funcs *p_funcs;
/** \brief 用于驱动函数的第一个参数 */
void *p_drv;
} am_uart_serv_t;
/** \brief UART标准服务操作句柄类型定义 */
typedef am_uart_serv_t *am_uart_handle_t;
am_uart_drv_funcs 是一个函数指针结构体,里面包含了UART设备操作函数的集合,类似Linux设备驱动下的file_operations结构体,它的定义如下:
/**
* \brief UART驱动函数结构体
*/
struct am_uart_drv_funcs
{
/**\brief UART控制函数 */
int (*pfn_uart_ioctl)(void *p_drv,int request, void *p_arg);
/**\brief 启动UART发送函数 */
int (*pfn_uart_tx_startup)(void *p_drv);
/**\brief 设置串口回调函数 */
int (*pfn_uart_callback_set)(void *p_drv,
int callback_type,
void *pfn_callback,
void *p_arg);
/**\brief 从串口获取一个字符(查询模式) */
int (*pfn_uart_poll_getchar)(void *p_drv, char *p_inchar);
/**\brief 输出一个字符(查询模式) */
int (*pfn_uart_poll_putchar)(void *p_drv, char outchar);
};
上面提到的串口实例化初始化函数am_zlg217_uart1_inst_init实现的主要功能就是初始化串口和填充am_uart_drv_funcs 结构体。这样就相当于建立了标准接口层和底层硬件的联系。UART驱动函数结构体的实现如下:
/** \brief 标准层接口函数实现 */
static const struct am_uart_drv_funcs __g_uart_drv_funcs =
{
__uart_ioctl,
__uart_tx_startup,
__uart_callback_set,
__uart_poll_getchar,
__uart_poll_putchar,
};
//这些函数就是直接操作串口对应的寄存器实现对应的功能
Ametal架构大致的思想就是这样,上述的也仅仅针对UART、SPI等标准外设,对于DMA等特殊外设还需要特殊处理,这放在后面再详细讲解。总结来说,Ametal就是通过抽象服务的概念实现跨平台和复用的。