制作一款可以记录运动历史数据的智能呼啦圈——嵌入式功能实现
Posted 三明治开发社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了制作一款可以记录运动历史数据的智能呼啦圈——嵌入式功能实现相关的知识,希望对你有一定的参考价值。
简介:本文将从智能呼啦圈软件整体方案,外设驱动以及功能实现几个维度来带大家一起了解如何实现呼啦圈智能计算、切换运动模式以及运动历史曲线。
开发环境搭建
智能呼啦圈方案是基于涂鸦 BLE SDK 和 Telink 芯片平台 TLSR825x 进行开发。BLE模组开发环境的搭建方案我们在前期的Demo有介绍过,大家可以参考BLE模组开发环境搭操作步骤
软件方案介绍
完整Demo可在 tuya-iotos-embeded-demo-ble-smart-hula-hoop 中获取。
一.总体设计
1.功能需求
智能呼啦圈demo的功能需求定义如下:
功能 | 需求描述 |
---|---|
模式选择 | 支持运动模式选择(普通模式[默认]、目标模式)。 【本地】模式键:短按,选择模式;长按≥2s,确认模式。 段码液晶屏:普通模式显示“010”,目标模式显示“020”。 【APP】下发“模式”数据。 |
目标设定 | 支持运动目标时长设定。 - 当日运动目标设置最长时间不超过45min; - 当月运动目标设置最长时间不超过1350min (45min*30天)。 【APP】下发“目标”数据。 |
目标完成提醒 | 支持目标模式下运动目标完成提醒。 【本地】段码液晶屏显示“—”并闪烁3次。 |
智能计数 | 支持运动时间(min)、圈数(圈)、卡路里(kcal)累计。 【本地】转动情况下计数,实时数据>999时重新从0开始计数。 |
运动数据显示 | 支持运动数据显示。 【本地】段码液晶屏:高位为0时不显示; 1)普通模式:时长、圈数、卡路里数据轮流显示,每间隔5分钟显示一轮; 2)目标模式:时长显示6s,每间隔5分钟显示一次。 【APP】接收本地数据并显示。 |
运动数据记录 | 支持运动数据记录。 【本地】记录30天内累计运动数据(时长、圈数、卡路里)。 |
数据指示 | 支持屏显数据指示: 【本地】时长、圈数、卡路里指示灯根据当前屏显数据依次点亮。 |
屏幕状态更新 | 支持屏幕状态更新。 【本地】按键、开始转动时屏幕点亮;停止转动30s后屏幕熄灭。 |
设备配网 | 支持设备配网。 【本地】上电时:允许设备配网,1分钟后若未被用户绑定,禁止配网; 模式键:长按≥5秒,允许设备配网,1分钟后若未被用户绑定,禁止配网; 配网提醒:配网(时长)指示灯快闪; |
数据上报 | 支持本地运动数据上报。 【本地】上报模式数据、运动数据至APP显示。 |
设备复位 | 支持设备复位。 【本地】复位键:长按≥2s,清空所有运动数据(恢复原始状态); 段码液晶屏显示“000”并闪烁3次。 |
2.模块划分
对以上功能需求进行分析梳理后,确定智能呼啦圈demo程序可划分为以下七大模块:
No. | 模块 | 处理内容 |
---|---|---|
1 | 外设驱动组件 | 按键、霍尔传感器、指示灯、段码液晶屏的驱动程序 |
2 | 设备基础服务 | 设备状态迁移处理、模式切换、本地定时等 |
3 | 显示处理服务 | 段码液晶屏显示内容更新和状态控制、LED状态控制 |
4 | 数据处理服务 | 运动数据(计时、圈数、卡路里)的更新和存储 |
5 | 用户事件处理 | 按键事件检测和处理、霍尔传感器事件检测和处理 |
6 | 定时事件处理 | 各定时事件的判断和处理 |
7 | 联网相关处理 | 配网相关处理、数据上报与接收处理、云端时间获取 |
3. 代码结构
根据模块层级关系,设定代码文件结构如下,后续将分别介绍外设驱动模块和应用层六大功能模块:
├── src /* 源文件目录 */
| ├── common
| | └── tuya_local_time.c /* 本地定时 */
| ├── sdk
| | └── tuya_uart_common_handler.c /* UART通用对接实现代码 */
| ├── driver
| | ├── tuya_hall_sw.c /* 霍尔开关驱动 */
| | ├── tuya_key.c /* 按键驱动 */
| | ├── tuya_led.c /* 指示灯驱动 */
| | └── tuya_seg_lcd.c /* 段码液晶屏驱动 */
| ├── platform
| | ├── tuya_gpio.c /* GPIO驱动 */
| | └── tuya_timer.c /* Timer驱动 */
| ├── tuya_ble_app_demo.c /* 应用层入口文件 */
| ├── tuya_hula_hoop_ble_proc.c /* 呼啦圈联网相关处理 */
| ├── tuya_hula_hoop_evt_user.c /* 呼啦圈用户事件处理 */
| ├── tuya_hula_hoop_evt_timer.c /* 呼啦圈定时事件处理 */
| ├── tuya_hula_hoop_svc_basic.c /* 呼啦圈基础服务 */
| ├── tuya_hula_hoop_svc_data.c /* 呼啦圈数据服务 */
| ├── tuya_hula_hoop_svc_disp.c /* 呼啦圈显示服务 *
| └── tuya_hula_hoop.c /* 呼啦圈demo入口 */
|
└── include /* 头文件目录 */
├── common
| ├── tuya_common.h /* 通用类型和宏定义 */
| └── tuya_local_time.h /* 本地定时 */
├── sdk
| ├── custom_app_uart_common_handler.h /* UART通用对接实现代码 */
| ├── custom_app_product_test.h /* 自定义产测项目相关实现 */
| └── custom_tuya_ble_config.h /* 应用配置文件 */
├── driver
| ├── tuya_hall_sw.h /* 霍尔开关驱动 */
| ├── tuya_key.h /* 按键驱动 */
| ├── tuya_led.h /* 指示灯驱动 */
| └── tuya_seg_lcd.h /* 段码液晶屏驱动 */
├── platform
| ├── tuya_gpio.h /* GPIO驱动 */
| └── tuya_timer.h /* Timer驱动 */
├── tuya_ble_app_demo.h /* 应用层入口文件 */
├── tuya_hula_hoop_ble_proc.h /* 呼啦圈联网相关处理 */
├── tuya_hula_hoop_evt_user.h /* 呼啦圈用户事件处理 */
├── tuya_hula_hoop_evt_timer.h /* 呼啦圈定时事件处理 */
├── tuya_hula_hoop_svc_basic.h /* 呼啦圈基础服务 */
├── tuya_hula_hoop_svc_data.h /* 呼啦圈数据服务 */
├── tuya_hula_hoop_svc_disp.h /* 呼啦圈显示服务 */
└── tuya_hula_hoop.h /* 呼啦圈demo入口 */
4.软件框图
二.外设驱动
为方便后续程序扩展,可以先将各外设驱动部分的代码分别编写成组件。
本demo所使用外设的基本情况如下:
外设 | 数量&规格 | 驱动方式 |
---|---|---|
按键 | 2个 | 开关量检测 |
霍尔传感器 | 1个;开关型 | 开关量检测 |
发光二极管 | 3个;红色 | 电平或PWM驱动 |
段码液晶屏 | 1个;3位数字“8”,4个COM口,6个SEG口 | COM口和SEG口两端施加一定压差的交流电,压差大于阈值时对应笔段点亮 |
1.按键&霍尔驱动
- 按键
由于按键是嵌入式开发中的常用器件,这里不做过多介绍,结合呼啦圈demo的功能需求,组件功能设置如下:
<1> 可在初期注册按键信息,包括引脚、有效电平、长按时间(2种可选)、按键触发时响应的回调函数;
<2> 可检测按键触发事件,包括短按、长按(2种可选),并在按键确认触发时,执行用户设置的回调函数;
<3> 可实现多个按键同时检测;
首先我们先来实现按键的初始化:
/* 第一步:定义供用户注册按键信息使用的结构体类型 (tuya_key.h) */
/* 按键事件类型 */
typedef BYTE_T KEY_PRESS_TYPE_E;
#define SHORT_PRESS 0x00 /* 短按 */
#define LONG_PRESS_FOR_TIME1 0x01 /* 长按超过时间1 */
#define LONG_PRESS_FOR_TIME2 0x02 /* 长按超过时间2 */
/* 按键回调函数类型 */
typedef VOID_T (*KEY_CALLBACK)(KEY_PRESS_TYPE_E type);
/* 按键注册信息类型 */
typedef struct {
TY_GPIO_PORT_E port; /* 端口 */
BOOL_T active_low; /* 有效电平 (1-低电平有效,0-高电平有效) */
UINT_T long_press_time1; /* 长按时间1 (ms) */
UINT_T long_press_time2; /* 长按时间2 (ms) */
KEY_CALLBACK key_cb; /* 触发按键回调函数 */
} KEY_DEF_T;
/* 第二步:定义用来存储按键状态的结构体类型 (tuya_key.c) */
typedef struct {
BOOL_T cur_stat; /* 今回状态 */
BOOL_T prv_stat; /* 前回状态 */
UINT_T cur_time; /* 今回计时 */
UINT_T prv_time; /* 前回计时 */
} KEY_STATUS_T;
/* 第三步:定义用于按键信息管理的结构体类型和该类型的指针 (tuya_key.c) */
typedef struct key_manage_s {
struct key_manage_s *next; /* 下一个按键信息存储的地址,实现多按键检测 */
KEY_DEF_T *key_def_s;
KEY_STATUS_T key_status_s;
} KEY_MANAGE_T;
STATIC KEY_MANAGE_T *sg_key_mag_list = NULL;
/* 第四步:定义按键错误信息代码 (tuya_key.h) */
typedef BYTE_T KEY_RET;
#define KEY_OK 0x00
#define KEY_ERR_MALLOC_FAILED 0x01
#define KEY_ERR_CB_UNDEFINED 0x02
/* 第五步:编写按键注册函数,包括对按键的初始化工作 (tuya_key.c) */
STATIC VOID_T __key_gpio_init(IN CONST TY_GPIO_PORT_E port, IN CONST BOOL_T active_low)
{
tuya_gpio_init(port, TRUE, active_low);
}
KEY_RET tuya_reg_key(IN KEY_DEF_T *key_def)
{
/* 检查是否定义了回调函数,未定义则返回错误信息 */
if (key_def->key_cb == NULL) {
return KEY_ERR_CB_UNDEFINED;
}
/* 为key_mag分配空间并初始化,分配失败则返回错误信息 */
KEY_MANAGE_T *key_mag = (KEY_MANAGE_T *)tuya_ble_malloc(SIZEOF(KEY_MANAGE_T));
if (NULL == key_mag) {
return KEY_ERR_MALLOC_FAILED;
}
/* 记录用户设置的按键信息,并存放至按键管理列表 */
key_mag->key_def_s = key_def;
if (sg_key_mag_list) {
key_mag->next = sg_key_mag_list;
}
sg_key_mag_list = key_mag;
/* 根据用户设置的有效电平对引脚进行初始化 */
__key_gpio_init(key_def->port, key_def->active_low);
/* 返回成功信息 */
return KEY_OK;
}
/* 第六步:在头文件中定义按键注册接口 (tuya_key.h) */
KEY_RET tuya_reg_key(IN KEY_DEF_T *key_def);
完成了按键初始化工作之后,我们来实现按键事件的检测和处理,基本思路是每10ms检测一次每个按键的状态,并判断是否满足了按键触发事件的条件,标记事件的类型,然后执行对应的回调函数:
/* 第一步:定义相关参数值 (tuya_key.c) */
#define KEY_SCAN_CYCLE_MS 10 /* 扫描周期 */
#define KEY_PRESS_SHORT_TIME 50 /* 短按确认时间 */
/* 第二步:编写用于更新单个按键状态的相关函数 (tuya_key.c) */
/* 获取按键实时状态,1-按压,0-释放 */
STATIC BOOL_T __get_key_stat(IN CONST TY_GPIO_PORT_E port, IN CONST UCHAR_T active_low)
{
BOOL_T key_stat;
if (active_low) {
key_stat = tuya_gpio_read(port) == 0 ? TRUE : FALSE;
} else {
key_stat = tuya_gpio_read(port) == 0 ? FALSE : TRUE;
}
return key_stat;
}
/* 更新按键状态 */
STATIC VOID_T __update_key_status(INOUT KEY_MANAGE_T *key_mag)
{
BOOL_T key_stat;
/* 保存前回状态 */
key_mag->key_status_s.prv_stat = key_mag->key_status_s.cur_stat;
key_mag->key_status_s.prv_time = key_mag->key_status_s.cur_time;
/* 获取实时状态 */
key_stat = __get_key_stat(key_mag->key_def_s->pin, key_mag->key_def_s->active_low);
/* 更新今回状态 */
if (key_stat != key_mag->key_status_s.cur_stat) {
key_mag->key_status_s.cur_stat = key_stat;
key_mag->key_status_s.cur_time = 0;
} else {
key_mag->key_status_s.cur_time += KEY_SCAN_CYCLE_MS;
}
}
/* 第三步:编写用于判断单个按键事件的相关函数 (tuya_key.c) */
/* 判断按键保持按压状态的时间是否达到了over_time */
STATIC BOOL_T __is_key_press_over_time(IN CONST KEY_STATUS_T key_status_s, IN CONST UINT_T over_time)
{
if (key_status_s.cur_stat == TRUE) {
if ((key_status_s.cur_time >= over_time) &&
(key_status_s.prv_time < over_time)) {
return TRUE;
}
}
return FALSE;
}
/* 判断按键从开始被按压到被释放经过的时间是否达到了over_time并且少于less_time */
STATIC BOOL_T __is_key_release_to_release_over_time_and_less_time(IN CONST KEY_STATUS_T key_status_s, IN CONST UINT_T over_time, IN CONST UINT_T less_time)
{
if ((key_status_s.prv_stat == TRUE) &&
(key_status_s.cur_stat == FALSE)) {
if ((key_status_s.prv_time >= over_time) &&
(key_status_s.prv_time < less_time)) {
return TRUE;
}
}
return FALSE;
}
/* 判断与处理按键事件 */
STATIC VOID_T __detect_and_handle_key_event(INOUT KEY_MANAGE_T *key_mag)
{
KEY_PRESS_TYPE_E type;
BOOL_T time_exchange;
UINT_T long_time1, long_time2;
/* 比较用户设置的长按时间1和长按时间2的大小,并标记是否交换 */
if (key_mag->key_def_s->long_press_time2 >= key_mag->key_def_s->long_press_time1) {
long_time1 = key_mag->key_def_s->long_press_time1;
long_time2 = key_mag->key_def_s->long_press_time2;
time_exchange = FALSE;
} else {
long_time1 = key_mag->key_def_s->long_press_time2;
long_time2 = key_mag->key_def_s->long_press_time1;
time_exchange = TRUE;
}
/* 判断按键状态,标记事件类型并跳转到KEY_EVENT (根据长按时间设置情况使用对应的判断方式) */
if ((long_time2 != 0) && (long_time1 != 0)) {
if (__is_key_press_over_time(key_mag->key_status_s, long_time2)) {
type = LONG_PRESS_FOR_TIME2;
goto KEY_EVENT;
}
if (__is_key_release_to_release_over_time_and_less_time(key_mag->key_status_s, long_time1, long_time2)) {
type = LONG_PRESS_FOR_TIME1;
goto KEY_EVENT;
}
if (__is_key_release_to_release_over_time_and_less_time(key_mag->key_status_s, KEY_PRESS_SHORT_TIME, long_time1)){
type = SHORT_PRESS;
goto KEY_EVENT;
}
} else if ((long_time2 != 0) && (long_time1 == 0)) {
if (__is_key_press_over_time(key_mag->key_status_s, long_time2)) {
type = LONG_PRESS_FOR_TIME2;
goto KEY_EVENT;
}
if (__is_key_release_to_release_over_time_and_less_time(key_mag->key_status_s, KEY_PRESS_SHORT_TIME, long_time2)){
type = SHORT_PRESS;
goto KEY_EVENT;
}
} else if ((long_time2 == 0) && (long_time1 != 0)) {
if (__is_key_press_over_time(key_mag->key_status_s, long_time1)) {
type = LONG_PRESS_FOR_TIME1;
goto KEY_EVENT;
}
if (__is_key_release_to_release_over_time_and_less_time(key_mag->key_status_s, KEY_PRESS_SHORT_TIME, long_time1)){
type = SHORT_PRESS;
goto KEY_EVENT;
}
} else {
if (__is_key_press_over_time(key_mag->key_status_s, KEY_PRESS_SHORT_TIME)) {
type = SHORT_PRESS;
goto KEY_EVENT;
}
}
return;
/* 处理按键事件 */
KEY_EVENT:
/* 如果在判断前进行了时间参数的交换,则将标记的事件类型进行交换 */
if (time_exchange) {
if (type == LONG_PRESS_FOR_TIME2) {
type = LONG_PRESS_FOR_TIME1;
} else if (type == LONG_PRESS_FOR_TIME1) {
type = LONG_PRESS_FOR_TIME2;
} else {
;
}
}
/* 执行用户设置的回调函数 */
key_mag->key_def_s->key_cb(type);
}
/* 第四步:编写10ms处理函数 (tuya_key.c) */
STATIC INT_T __key_timeout_handler(VOID_T)
{
/* 获取按键信息管理列表,无按键注册则返回 */
KEY_MANAGE_T *key_mag_tmp = sg_key_mag_list;
if (NULL == key_mag_tmp) {
return 0;
}
/* 循环处理每个按键 */
while (key_mag_tmp) {
__update_key_status(key_mag_tmp); /* 更新按键状态 */
__detect_and_handle_key_event(key_mag_tmp); /* 判断并处理按键事件 */
key_mag_tmp = key_mag_tmp->next; /* 加载下一个按键信息 */
}
return 0;
}
/* 第五步:在按键注册函数中创建定时器 (tuya_key.c) */
KEY_RET tuya_reg_key(IN KEY_DEF_T *key_def)
{
...
if (sg_key_mag_list) {
key_mag->next = sg_key_mag_list;
} else { /* 注册第一个按键时创建10ms定时器,注册回调函数 */
tuya_software_timer_create(KEY_SCAN_CYCLE_MS*1000, __key_timeout_handler);
}
...
}
- 霍尔传感器
这次使用的霍尔传感器是开关型的,一般情况下也可视为按键处理。考虑到呼啦圈快速转动时霍尔传感器与磁铁的接触时间较短,并且上述按键驱动组件使用了10ms软件定时器进行处理,易被外部程序执行时间影响,可能会导致呼啦圈计数漏检的情况,所以这里我们采取外部中断的方式处理:
/* 【tuya_hall_sw.h】 */
/* 错误信息代码 */
typedef BYTE_T HSW_RET;
#define HSW_OK 0x00
#define HSW_ERR_MALLOC_FAILED 0x01
#define HSW_ERR_CB_UNDEFINED 0x02
/* 霍尔开关数据结构 */
typedef VOID_T (*HALL_SW_CALLBACK)();
typedef struct {
TY_GPIO_PORT_E port; /* 端口 */
BOOL_T active_low; /* 有效电平 */
HALL_SW_CALLBACK hall_sw_cb; /* 触发时回调函数 */
UINT_T invalid_intv; /* 两次触发间隔如果小于该时间则无效 */
} HALL_SW_DEF_T;
/* 【tuya_hall_sw.c】 */
/* 霍尔开关信息管理 */
typedef struct hall_sw_manage_s {
struct hall_sw_manage_s *next;
HALL_SW_DEF_T *def;
UINT_T wk_tm;
} HALL_SW_MANAGE_T;
STATIC HALL_SW_MANAGE_T *sg_hsw_mag_list = NULL;
/* 霍尔开关引脚初始化 */
STATIC VOID_T __hall_sw_gpio_init(IN CONST TY_GPIO_PORT_E port, IN CONST BOOL_T active_low)
{
tuya_gpio_init(port, TRUE, active_low);
if (active_low) {
tuya_gpio_irq_init(port, TY_GPIO_IRQ_FALLING, __hall_sw_irq_handler);
} else {
tuya_gpio_irq_init(port, TY_GPIO_IRQ_RISING, __hall_sw_irq_handler);
}
}
/* 霍尔开关注册 */
HSW_RET tuya_reg_hall_sw(IN HALL_SW_DEF_T *hsw_def)
{
/* 检查是否定义了回调函数,未定义则返回错误信息 */
if (hsw_def->hall_sw_cb == NULL) {
return HSW_ERR_CB_UNDEFINED;
}
/* 为hall_sw_mag分配空间并初始化,分配失败则返回错误信息 */
HALL_SW_MANAGE_T *hall_sw_mag = (HALL_SW_MANAGE_T *)tuya_ble_malloc(SIZEOF(HALL_SW_MANAGE_T));
if (NULL == hall_sw_mag) {
return HSW_ERR_MALLOC_FAILED;
}
hall_sw_mag->def = hsw_def;
/* 记录用户设置的霍尔开关信息,并存放至霍尔开关管理列表 */
if (sg_hsw_mag_list) {
hall_sw_mag->next = sg_hsw_mag_list;
}
sg_hsw_mag_list = hall_sw_mag;
/* 引脚初始化 */
__hall_sw_gpio_init(hsw_def->port, hsw_def->active_low);
return HSW_OK;
}
/* 霍尔开关触发时处理 */
STATIC VOID_T __hall_sw_trigger_handler(IN HALL_SW_MANAGE_T *hsw_mag)
{
/* 两次触发间隔检查 */
if (!tuya_is_clock_time_exceed(hsw_mag->wk_tm, hsw_mag->def->invalid_intv)) {
return;
}
hsw_mag->wk_tm = tuya_get_clock_time();
/* 执行用户设置的回调函数 */
hsw_mag->def->hall_sw_cb();
}
/* 霍尔开关外部中断回调 */
STATIC VOID_T __hall_sw_irq_handler(TY_GPIO_PORT_E port)
{
HALL_SW_MANAGE_T *hsw_mag_tmp = sg_hsw_mag_list;
while (hsw_mag_tmp) {
if (hsw_mag_tmp->def->port == port) {
__hall_sw_trigger_handler(hsw_mag_tmp);
break;
}
hsw_mag_tmp = hsw_mag_tmp->next;
}
}
2.发光二极管驱动
发光二极管同样是常用器件,结合呼啦圈demo的功能需求,组件功能设置如下:
<1> 可在初期注册LED信息,包括引脚、有效电平;
<2> 可控制LED点亮或熄灭或闪烁;
<3> 可设置LED闪烁模式,包括闪烁方式(指定时长/指定次数/永远闪烁)、闪烁开始时和闪烁结束后的状态、点亮阶段的时间、熄灭阶段的时间、闪烁结束时的回调函数;
<4> 可实现多个LED同时控制;
这次没有设置亮度调节、呼吸灯控制等功能,因此只需要使用电平驱动方式。
首先还是先来实现初始化部分:
/* 第一步:定义LED句柄,用户通过该句柄来控制单个LED (tuya_led.h) */
typedef VOID_T *LED_HANDLE;
/* 第二步:定义初期需注册的LED驱动相关信息 (tuya_led.c) */
typedef struct {
TY_GPIO_PORT_E pin; /* LED引脚 */
BOOL_T active_low; /* LED有效电平 (1-低电平点亮,0-高电平点亮) */
} LED_DRV_T;
/* 第三步:定义LED信息管理列表 (tuya_led.c) */
typedef struct led_manage_s {
struct led_manage_s *next;
LED_DRV_T drv_s;
} LED_MANAGE_T;
STATIC LED_MANAGE_T *sg_led_mag_list = NULL;/* 下一个LED信息存储的地址,实现多LED控制 */
/* 第四步:编写LED引脚初始化函数和LED注册函数 (tuya_led.c) */
STATIC VOID_T __led_gpio_init(IN CONST TY_GPIO_PORT_E port, IN CONST BOOL_T active_low)
{
tuya_gpio_init(port, FALSE, active_low);
}
LED_RET tuya_create_led_handle(IN CONST GPIO_PinTypeDef pin, IN CONST UCHAR_T active_low, OUT LED_HANDLE *handle)
{
/* 检查句柄,未指定则返回错误参数 */
if (NULL == handle) {
return LED_ERR_INVALID_PARM;
}
/* 为led_mag分配空间并初始化,分配失败则返回错误信息 */
LED_MANAGE_T *led_mag = (LED_MANAGE_T *)tuya_ble_malloc(SIZEOF(LED_MANAGE_T));
if (NULL == led_mag) {
return LED_ERR_MALLOC_FAILED;
}
/* 记录用户设置的LED信息,并存放至LED管理列表,同时将存储地址 */
led_mag->drv_s.pin = pin;
led_mag->drv_s.active_low = active_low;
*handle = (LED_HANDLE)led_mag;
if (sg_led_mag_list) {
led_mag->next = sg_led_mag_list;
}
sg_led_mag_list = led_mag;
/* 根据用户设置的有效电平对引脚进行初始化 (注册时默认不点亮) */
__led_gpio_init(pin, active_以上是关于制作一款可以记录运动历史数据的智能呼啦圈——嵌入式功能实现的主要内容,如果未能解决你的问题,请参考以下文章