内容参考于《抽象接口技术和组件开发规范及其思想》、《面向ametal框架和接口的C编程》
六.面向对象的嵌入式底层开发-LED
Ametal中LED的实现
资源图
1. demo_std_led.c
demo属于应用层。am_led_on和am_led_off属于通用接口,通用接口的第一个参数表示要操作的具体对象。一个系统可能有多颗LED,为每一颗LED分配一个ID号。通过ID形式隐藏了底层的对象和指针,对APP层更加友好,不被非业务的逻辑所影响。通过ID能查找到对象,但是,ID和对象并非是一对一的,一个对象可以拥有几个ID,可以理解为,主设备号(对象)下拥有这多个次设备号(ID)。
#include "ametal.h"
#include "am_led.h"
#include "am_delay.h"
void demo_std_led_entry (int led_id)
{
while (1) {
am_led_on(led_id);
am_mdelay(500);
am_led_off(led_id);
am_mdelay(500);
}
}
2. am_led.h(LED标准接口)
demo_std_led.c依赖于am_led.h
#ifndef __AM_LED_H
#define __AM_LED_H
#ifdef __cplusplus
extern "C" {
#endif
#include "am_common.h"
int am_led_set(int led_id, am_bool_t state);
int am_led_on(int led_id);
int am_led_off(int led_id);
int am_led_toggle(int led_id);
#ifdef __cplusplus
}
#endif
#endif /* __AM_LED_H */
3. am_led_dev.c(通用LED设备管理器)
通用接口的实现
- 先看看int am_led_on (int led_id)和int am_led_off (int led_id),发现其实整个的核心代码是int am_led_set (int led_id, am_bool_t state) 和 int am_led_toggle (int led_id),这两个函数屏蔽了底层的差异,无论底层如何变化(GPIO或者HC595),其使用p_dev->p_funcs->pfn_led_set 和 p_dev->p_funcs->pfn_led_toggle即可,这部分下面再进行细讲。
- am_led_dev_t * __led_dev_find_with_id(int id),其通过ID号查找到对象,将对象屏蔽在底层。
- 一般来说,在嵌入式底层,对象都是确定的,有几个LED,有几个UART,在硬件设计那一刻就确定了,代码通过宏进行声明。应用层启动前会对对象进程初始化,再进入应用。把LED对象,挂在 *static am_led_dev_t __gp_head 这个链表下面,这部分下面再进行细讲。
#include "ametal.h"
#include "am_led.h"
#include "am_led_dev.h"
#include "am_int.h"
static am_led_dev_t *__gp_head;
static am_led_dev_t * __led_dev_find_with_id (int id)
{
am_led_dev_t *p_cur = __gp_head;
int key = am_int_cpu_lock();
while (p_cur != NULL) {
if ((id >= p_cur->p_info->start_id) &&
(id <= p_cur->p_info->end_id)) {
break;
}
p_cur = p_cur->p_next;
}
am_int_cpu_unlock(key);
return p_cur;
}
static int __led_dev_add (am_led_dev_t *p_dd)
{
int key = am_int_cpu_lock();
/* add the device to device list */
p_dd->p_next = __gp_head;
__gp_head = p_dd;
am_int_cpu_unlock(key);
return AM_OK;
}
static int __led_dev_del (am_led_dev_t *p_dd)
{
int ret = -AM_ENODEV;
am_led_dev_t **pp_head = &__gp_head;
am_led_dev_t *p_prev = AM_CONTAINER_OF(pp_head, am_led_dev_t, p_next);
am_led_dev_t *p_cur = __gp_head;
int key = am_int_cpu_lock();
while (p_cur != NULL) {
if (p_cur == p_dd) {
p_prev->p_next = p_dd->p_next;
p_dd->p_next = NULL;
ret = AM_OK;
break;
}
p_prev = p_cur;
p_cur = p_cur->p_next;
}
am_int_cpu_unlock(key);
return ret;
}
int am_led_dev_lib_init (void)
{
__gp_head = NULL;
return AM_OK;
}
int am_led_dev_add (am_led_dev_t *p_dev,
const am_led_servinfo_t *p_info,
const am_led_drv_funcs_t *p_funcs,
void *p_cookie)
{
if ((p_dev == NULL) || (p_funcs == NULL) || (p_info == NULL)) {
return -AM_EINVAL;
}
if (__led_dev_find_with_id(p_info->start_id) != NULL) {
return -AM_EPERM;
}
if (__led_dev_find_with_id(p_info->end_id) != NULL) {
return -AM_EPERM;
}
p_dev->p_info = p_info;
p_dev->p_funcs = p_funcs;
p_dev->p_next = NULL;
p_dev->p_cookie = p_cookie;
return __led_dev_add(p_dev);
}
int am_led_dev_del (am_led_dev_t *p_dd)
{
if (p_dd == NULL) {
return -AM_EINVAL;
}
return __led_dev_del(p_dd);
}
int am_led_set (int led_id, am_bool_t state)
{
am_led_dev_t *p_dev = __led_dev_find_with_id(led_id);
if (p_dev == NULL) {
return -AM_ENODEV;
}
if (p_dev->p_funcs->pfn_led_set) {
return p_dev->p_funcs->pfn_led_set(p_dev->p_cookie, led_id, state);
}
return -AM_ENOTSUP;
}
int am_led_toggle (int led_id)
{
am_led_dev_t *p_dev = __led_dev_find_with_id(led_id);
if (p_dev == NULL) {
return -AM_ENODEV;
}
if (p_dev->p_funcs->pfn_led_toggle) {
return p_dev->p_funcs->pfn_led_toggle(p_dev->p_cookie, led_id);
}
return -AM_ENOTSUP;
}
int am_led_on (int led_id)
{
return am_led_set(led_id, AM_TRUE);
}
int am_led_off (int led_id)
{
return am_led_set(led_id, AM_FALSE);
}
4. am_led_dev.h(通用LED设备管理器)
- 让我们来看看led的抽象类。其实am_led_dev.h和am_led.h本该属于同一个H文件,这里分为2个文件,一个对内,一个对外,am_led.h是提供给应用层的,称为LED标准接口。am_led_dev是LED类的通用LED设备管理器,因为其不仅仅包含了抽象类,还包含了抽象类的链表。
- am_led_dev_t就是LED类,这里称为通用LED设备。
- am_led_dev_t第一个组成是抽象方法const am_led_drv_funcs_t,这里称为驱动函数,注意是const类型,驱动函数从硬件确定便已经确定了。
- am_led_dev_t第二个组成是成员void *p_cookie,驱动函数参数,通常指向对象本身,其作用下面再细讲。
- am_led_dev_t第三个组成是成员const am_led_servinfo_t *p_info,其记录着这个对象的基本消息,LED编号(一个LED设备下有几路LED)。
- am_led_dev_t第四个组成是成员struct am_led_dev *p_next,把所有的LED设备链起来,那么就能够统一管理所有的设备,具有设备管理器的功能。
#ifndef __AM_LED_DEV_H
#define __AM_LED_DEV_H
#include "ametal.h"
#include "am_led.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* \brief LED驱动函数
*/
typedef struct am_led_drv_funcs {
/* 设置LED的状态 */
int (*pfn_led_set) (void *p_cookie, int id, am_bool_t on);
/* 翻转LED的状态 */
int (*pfn_led_toggle)(void *p_cookie, int id);
} am_led_drv_funcs_t;
/**
* \brief 通用LED服务信息
*/
typedef struct am_led_servinfo {
int start_id; /**< \brief 本设备提供的LED服务的起始编号 */
int end_id; /**< \brief 本设备提供的LED服务的结束编号 */
} am_led_servinfo_t;
/**
* \brief 通用LED设备
*/
typedef struct am_led_dev {
const am_led_drv_funcs_t *p_funcs; /**< \brief 驱动函数 */
void *p_cookie; /**< \brief 驱动函数参数 */
const am_led_servinfo_t *p_info; /**< \brief LED服务信息 */
struct am_led_dev *p_next; /**< \brief 下一个LED设备 */
} am_led_dev_t;
/**
* \brief LED设备库管理初始化
* \retval AM_OK : LED设备库管理初始化库初始化成功
*/
int am_led_dev_lib_init (void);
/**
* \brief 添加一个LED设备
*
* \param[in] p_dev : LED设备实例
* \param[in] p_info :LED设备服务信息
* \param[in] p_funcs : LED设备的驱动函数
* \param[in] p_cookie : 驱动函数的参数
*
* \retval AM_OK : 添加成功
* \retval -AM_EINVAL :添加失败,参数存在错误
* \retval -AM_EPERM : 添加失败,该设备对应的LED编号已经存在
*/
int am_led_dev_add (am_led_dev_t *p_dev,
const am_led_servinfo_t *p_info,
const am_led_drv_funcs_t *p_funcs,
void *p_cookie);
/**
* \brief 删除一个LED设备
*
* \param[in] p_dev : LED设备实例
*
* \retval AM_OK : 删除成功
* \retval -AM_EINVAL :删除失败,参数存在错误
* \retval -AM_ENODEV : 删除失败,无此参数
*/
int am_led_dev_del (am_led_dev_t *p_dev);
/* @} */
#ifdef __cplusplus
}
#endif
#endif /* __AM_LED_DEV_H */
5. __gp_head
- 通过上面的代码,我们知道,最终操作的是static am_led_dev_t *__gp_head这个链表下的各个设备(对象),那么从am_led_dev_add开始入手。
- am_led_dev_add第一个参数要求输入一个设备(对象),第二个参数要求输入这个设备的信息(LED的ID范围,次设备号),第三个参数要求输入这个设备的驱动函数am_led_drv_funcs_t,第三个参数要求输入驱动函数的参数void *p_cookie。
以上描述了接口的使用、设备的抽象接口内容、设备管理器,下面描述其底层实现
通过gpio控制led的接口实现
6. am_led_gpio.h
- am_led_gpio_dev是实现接口后的类,其还包含自己的成员变量
const am_led_gpio_info_t *p_info,方法(构造函数和析构函数) am_led_gpio_init 和 am_led_gpio_deinit。 - am_led_gpio_info_t成员包含着,gpio特有的一些数据。
#ifndef __AM_LED_GPIO_H
#define __AM_LED_GPIO_H
#include "ametal.h"
#include "am_led_dev.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* \brief LED信息(GPIO驱动)
*/
typedef struct am_led_gpio_info {
/** \brief LED基础服务信息 ,包含起始编号和结束编号 */
am_led_servinfo_t serv_info;
/** \brief 使用的GPIO引脚,引脚数目应该为 (结束编号 - 起始编号 + 1) */
const int *p_pins;
/** \brief LED是否是低电平点亮 */
am_bool_t active_low;
} am_led_gpio_info_t;
/**
* \brief LED设备(GPIO驱动)
*/
typedef struct am_led_gpio_dev {
am_led_dev_t isa;
const am_led_gpio_info_t *p_info;
} am_led_gpio_dev_t;
/**
* \brief LED设备初始化(GPIO驱动)
*
* \param[in] p_dev : LED设备
* \param[in] p_info : LED设备信息
*
* \retval AM_OK : 初始化成功
* \retval -AM_EINVAL : 初始化失败
*/
int am_led_gpio_init (am_led_gpio_dev_t *p_dev,
const am_led_gpio_info_t *p_info);
/**
* \brief LED设备解初始化(GPIO驱动)
*
* \param[in] p_dev : LED设备
*
* \retval AM_OK : 解初始化成功
* \retval -AM_EINVAL : 解初始化失败
*/
int am_led_gpio_deinit (am_led_gpio_dev_t *p_dev);
#ifdef __cplusplus
}
#endif
#endif /* __AM_LED_GPIO_H */
7. am_led_gpio.c
#include "ametal.h"
#include "am_led_gpio.h"
#include "am_gpio.h"
static int __led_gpio_set (void *p_cookie, int led_id, am_bool_t state)
{
am_led_gpio_dev_t *p_dev = (am_led_gpio_dev_t *)p_cookie;
led_id = led_id - p_dev->p_info->serv_info.start_id;
am_gpio_set(p_dev->p_info->p_pins[led_id], state ^ p_dev->p_info->active_low);
return AM_OK;
}
static int __led_gpio_toggle (void *p_cookie, int led_id)
{
am_led_gpio_dev_t *p_dev = (am_led_gpio_dev_t *)p_cookie;
led_id = led_id - p_dev->p_info->serv_info.start_id;
am_gpio_toggle(p_dev->p_info->p_pins[led_id]);
return AM_OK;
}
static const am_led_drv_funcs_t __g_led_gpio_drv_funcs = {
__led_gpio_set,
__led_gpio_toggle
};
int am_led_gpio_init (am_led_gpio_dev_t *p_dev,
const am_led_gpio_info_t *p_info)
{
int i;
int num;
if ((p_dev == NULL) || (p_info == NULL)) {
return -AM_EINVAL;
}
num = p_info->serv_info.end_id - p_info->serv_info.start_id + 1;
if (num <= 0) {
return -AM_EINVAL;
}
p_dev->p_info = p_info;
for (i = 0; i < num; i++) {
if (p_info->active_low) {
am_gpio_pin_cfg(p_info->p_pins[i], AM_GPIO_OUTPUT_INIT_HIGH);
} else {
am_gpio_pin_cfg(p_info->p_pins[i], AM_GPIO_OUTPUT_INIT_LOW);
}
}
return am_led_dev_add(&p_dev->isa,
&p_info->serv_info,
&__g_led_gpio_drv_funcs,
p_dev);
}
int am_led_gpio_deinit (am_led_gpio_dev_t *p_dev)
{
int i;
int num;
if (p_dev == NULL) {
return -AM_EINVAL;
}
num = p_dev->p_info->serv_info.end_id - p_dev->p_info->serv_info.start_id + 1;
if (num <= 0) {
return -AM_EINVAL;
}
for (i = 0; i < num; i++) {
am_gpio_pin_cfg(p_dev->p_info->p_pins[i], AM_GPIO_INPUT | AM_GPIO_FLOAT);
}
return am_led_dev_del(&p_dev->isa);
}
8. am_hwconf_xx.c
应用启动前的硬件初始化,这里我们称为板级初始化,姑且这么认为(涉及到框架),这个路径就很有趣board\am217_core\project_template\user_config\am_hwconf_usrcfg。
/* GPIO形式 */
static am_led_gpio_dev_t __g_miniport_led;
static const int __g_miniport_led_pins[] = {
PIOB_7, PIOB_6, PIOB_15, PIOB_13, PIOC_10, PIOB_14, PIOB_12, PIOC_14
};
static const am_led_gpio_info_t __g_miniport_led_info = {
{
0, /* 起始编号0 */
7 /* 结束编号7,共计8个LED */
},
__g_miniport_led_pins,
AM_TRUE
};
int am_miniport_led_inst_init (void)
{
return am_led_gpio_init(&__g_miniport_led, &__g_miniport_led_info);
}
/* HC595形式 */
static am_led_hc595_dev_t __g_miniport_led_595;
static uint8_t __g_miniport_led_595_buf[1];
static const am_led_hc595_info_t __g_miniport_led_595_info = {
{
8, /* 起始编号8 */
9 /* 结束编号9,共计2个LED */
},
1,
__g_miniport_led_595_buf,
AM_TRUE
};
int am_miniport_led_595_inst_init (void)
{
return am_led_hc595_init(&__g_miniport_led_595,
&__g_miniport_led_595_info,
am_miniport_595_inst_init());
}
通过hc595控制led的接口实现
9. am_led_hc595.h
#ifndef __AM_LED_HC595_H
#define __AM_LED_HC595_H
#include "ametal.h"
#include "am_led_dev.h"
#include "am_hc595.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* \brief LED信息(HC595驱动)
*/
typedef struct am_led_hc595_info {
/** \brief LED基础服务信息 ,包含起始编号和结束编号 */
am_led_servinfo_t serv_info;
/** \brief HC595的级连个数 */
int hc595_num;
/** \brief 数据缓存,大小与HC595的级连个数相同 */
uint8_t *p_buf;
/** \brief LED是否是低电平点亮 */
am_bool_t active_low;
} am_led_hc595_info_t;
/**
* \brief LED设备(GPIO驱动)
*/
typedef struct am_led_hc595_dev {
am_led_dev_t isa;
am_hc595_handle_t handle;
const am_led_hc595_info_t *p_info;
} am_led_hc595_dev_t;
/**
* \brief LED设备初始化(HC595驱动)
*
* \param[in] p_dev : LED设备
* \param[in] p_info : LED设备信息
* \param[in] handle : HC595句柄
*
* \retval AM_OK : 初始化成功
* \retval -AM_EINVAL : 初始化失败
*/
int am_led_hc595_init (am_led_hc595_dev_t *p_dev,
const am_led_hc595_info_t *p_info,
am_hc595_handle_t handle);
/**
* \brief LED设备解初始化(HC595驱动)
*
* \param[in] p_dev : LED设备
*
* \retval AM_OK : 解初始化成功
* \retval -AM_EINVAL : 解初始化失败
*/
int am_led_hc595_deinit (am_led_hc595_dev_t *p_dev);
#ifdef __cplusplus
}
#endif
#endif /* __AM_LED_HC595_H */
10. am_led_hc595.c
#include "ametal.h"
#include "am_hc595.h"
#include "string.h"
#include "am_led_hc595.h"
static int __led_hc595_set (void *p_cookie, int led_id, am_bool_t state)
{
am_led_hc595_dev_t *p_dev = (am_led_hc595_dev_t *)p_cookie;
led_id = led_id - p_dev->p_info->serv_info.start_id;
if (state ^ p_dev->p_info->active_low) {
p_dev->p_info->p_buf[led_id >> 3] |= (1 << (led_id & 0x07));
} else {
p_dev->p_info->p_buf[led_id >> 3] &= ~(1 << (led_id & 0x07));
}
am_hc595_send(p_dev->handle,
p_dev->p_info->p_buf,
p_dev->p_info->hc595_num);
return AM_OK;
}
static int __led_hc595_toggle (void *p_cookie, int led_id)
{
am_led_hc595_dev_t *p_dev = (am_led_hc595_dev_t *)p_cookie;
led_id = led_id - p_dev->p_info->serv_info.start_id;
p_dev->p_info->p_buf[led_id >> 3] ^= (1 << (led_id & 0x07));
am_hc595_send(p_dev->handle,
p_dev->p_info->p_buf,
p_dev->p_info->hc595_num);
return AM_OK;
}
static const am_led_drv_funcs_t __g_led_hc595_drv_funcs = {
__led_hc595_set,
__led_hc595_toggle
};
int am_led_hc595_init (am_led_hc595_dev_t *p_dev,
const am_led_hc595_info_t *p_info,
am_hc595_handle_t handle)
{
if ((p_dev == NULL) || (p_info == NULL)) {
return -AM_EINVAL;
}
p_dev->p_info = p_info;
if (p_info->active_low) {
memset(p_info->p_buf, 0xFF, p_info->hc595_num);
} else {
memset(p_info->p_buf, 0x00, p_info->hc595_num);
}
am_hc595_send(handle, p_info->p_buf, p_info->hc595_num);
return am_led_dev_add(&p_dev->isa,
&p_info->serv_info,
&__g_led_hc595_drv_funcs,
p_dev);
}
int am_led_hc595_deinit (am_led_hc595_dev_t *p_dev)
{
const am_led_hc595_info_t *p_info;
if (p_dev == NULL) {
return -AM_EINVAL;
}
p_info = p_dev->p_info;
if (p_info->active_low) {
memset(p_info->p_buf, 0xFF, p_info->hc595_num);
} else {
memset(p_info->p_buf, 0x00, p_info->hc595_num);
}
am_hc595_send(p_dev->handle, p_info->p_buf, p_info->hc595_num);
return am_led_dev_del(&p_dev->isa);
}