现代人的生活忙忙碌碌,不少人下班之后还在地铁上用电脑做着未完成的工作,用手机回着各种消息,久而久之,颈椎就会出现疲劳、酸痛的问题,有些甚至会发生眩晕、恶心的症状。为此,我们研究设计了一款智能颈部按摩仪。
智能颈部按摩仪利用中医原理,形成一个高效复合能量场,由电极片接触皮肤,利用低电压的脉冲电流作用于神经或肌肉等。从而促进局部血液循环,改善肌肉状态,缓解急慢性疼痛。加上手机 App 控制,更加灵活方便。
智能模式,舒缓模式,活力模式,锤击拍打,刮痧模式。灵活切换模式,唤醒肌肤能量。
0-15 档按摩力度,调节范围广,可以随意选择适合用户的舒适度。
加热高档、低档、停止3个档,可以对颈部进行恒温热敷,随意切换。
一款低功耗嵌入式蓝牙协议模组。查看详情
MF52B 系列产品为同向漆包线引线环氧树脂涂装型 NTC 热敏电阻,具有阻值范围宽,阻值及B值精确度高,测试精度高,体积小,反应速度快,能长时间工作。
设备加热导热。
蓝牙模组在一线串口模式下通过DATA线给WTN6系列语音芯片发送数据以达到控制的目的。可以实现控制语音播放、停止、循环等。
沿用了样机中的电极片,是两个横向的椭圆形,不锈钢材质的。
网购的一款锂电池,电池容量700mAH,带电池保护板(防止过充/过放/短路/过流等保护),标称电压3.7V,充满电电压为4.2V的一款聚合物锂电池。
为实现 涂鸦智能 或 智能生活 App 远程控制,这次选用涂鸦 BTU 云模组 作为主控。
电路分为 BTU 主控、充电管理电路、语音播放部分、按键检测部分、低频脉冲电流输出部分、加热部分、温度检测等。
NTC温度传感器(MF52B)
MF52B系列产品为同向漆包线引线环氧树脂涂装型NTC热敏电阻,具有阻值范围宽,阻值及B值精确度高,测试精度高,体积小,反应速度快,能长时间工作。( B=3950, R=10K)
热敏电阻实物图
温度与阻值对应关系
温度检测电路
通过模组的ADC口进行电压的采集,换算出此时的温度值。从而对加热丝的温度进行调控。
加热丝
白色的线条为加热丝。
加热丝实物图
加热控制电路
通过单片机P7输出PWM波调节加热丝的温度。输出低电平,加热关。输出一定频率的方波,加热弱。输出高电平,加热强。
喇叭
喇叭 实物图
语音控制电路
蓝牙模组在一线串口模式下通过DATA线给WTN6系列语音芯片发送数据以达到控制的目的。可以实现控制语音播放、停止、循环等。
电极片
样机中的电极片是两个横向的椭圆形,不锈钢材质的。
电极片实物图
低频脉冲电流输出
模组的IO口P24和P9输出的脉冲对称,且不能同时为高电平。
当P24为高电平时,P9为低电平时,Q2导通,Q5截止,则Q4截止,Q6截止,Q8截止,由于人体存在阻抗K欧姆级别,所以Q3饱和导通。
P17为高电平。低频输出口,是两个完全对称的脉冲波。
当P24为低电平,P9为高电平,Q2截止,Q5导通,则Q4导通,Q6导通,Q8导通,Q3截止。
P17为低电平。低频输出口,是两个完全对称的脉冲波。
模组的IO口P24和P9的脉冲不对称,存在PB5和PCO都为高电平。
此时Q5导通,Q2导通,Q6导通,则Q4导通,Q3导通,容易导致烧坏管子。
S8050的开关状态跟二极管D4正端电压有关系,D4正端电压跟流过的电流有关系,可以做一个过流保护。一旦流过D4的电流超过10mA,D4的压降就会超过0.7V,三极管Q2会导通。
通过单片机IO口P24和P9输出不同频率的脉冲信号(P24和P9输出脉冲对称),组合成不同的波形输出,从而形成不同的模式。
D4参数:
电池
样机所用电池是网上买的一款锂电池,电池容量700mAH,带电池保护板(防止过充/过放/短路/过流等保护),标称电压3.7V,充满电电压为4.2V的一款聚合物锂电池。
电池尺寸:10*23*30mm 厚*宽*长
充电管理电路以及升压电路:
充电管理电路:XT2051是一款用于单芯锂离子电池的恒流/恒压充电电路。该器件包括一个内部功率晶体管,在应用中不需要外部电流检测电阻和阻塞二极管。XT2051需要最少的外部组件,并满足USB总线规范,非常适合便携式应用领域。
升压电路原理:当晶体管MMBT5551接通时,肖特基二极管D1反向偏置,电源经过电感L1至晶体管Q1形成回路,输入电压加在升压电感中转化为磁能贮存;当晶体管Q1关断时,肖特基二极管D1正向偏置,电感中的磁能因不能突变而转化为电能,此电压与输入电源一起为负载提供能量,并给输出电容C6充电,需要数个脉冲来提供足够的能量以加大输出电压。
本小节简单介绍产品创建流程,详细的操作和介绍请参考 选品类创建产品。
进入 涂鸦 IoT 平台。
创建产品时,在运动健康品类中选择颈部按摩仪,通讯协议选择蓝牙创建产品
完成产品创建,弹出功能点选项框后已有三个必选功能选项,关闭后在自定义功能点处添加产品实现的其他功能点。
设备面板选择自由配置面板,接下来进入硬件开发流程,选择涂鸦标准模组SDK开发,模组选择BT3L,选择右下角 免费领取10个激活码 获取对应的UUID、authkey以及MAC地址填入后续的SDK中。
GitHub 仓库
tuya_ble_sdk_Demo_Project_tlsr8253,克隆代码库到本地后,请仔细阅读 README.md
文件。
环境搭建
TLSR825x 蓝牙单点开发使用的是泰凌微官方 IDE,请访问 IDE for TLSR8 Chips。
下载后默认安装即可。接下来进行项目导入,可参考下载 IDE 的链接。
工程开发
修改PID。
修改auth_key、device_id、mac。
开始编译。
烧录工具(下载链接)
芯片平台选择8258 EVK,编译完成后单击 File 找到对应的bin文件,路径位于 tuya_ble_sdk_Demo_Project_tlsr8253\telink_kite_ble_sdk_v3.4.0_20190816\ble_sdk_multimode\8258_module\8258_module.bin
烧录完成后点击 Reset 程序即可运行。
烧录器使用Telink烧录器,SWM口接芯片的SWS口即可烧录。
注意:
- GPIO读取到高电平返回的是一个大于1的数,1、2、128都有可能。
- SDK默认串口日志打印为TL_C2,波特率为230400,由于IO口资源有限,该项目中将日志打印口改为TL_D3,修改完成后编译即可。修改路径为
tuya_ble_sdk_Demo_Project_tlsr8253\telink_kite_ble_sdk_v3.4.0_20190816\ble_sdk_multimode\vendor\8258_module\app_config.h
。
将第47行:
修改为:
按摩仪主要有物理按摩和电脉冲两种,本项目为电脉冲类型,其原理是通过电极感应金属片,用脉冲技术对颈椎进行刺激,从而缓解颈椎酸痛。
市场上按摩仪的常见功能主要有敲打、锤击、揉捏、针灸、推拿、刮痧、恒温热敷等,本项目中实现了舒缓、活力、锤击拍打、刮痧、智能五个模式。
原理图
各种模式的实现是通过 P9 和 P24 两个 I/O 口控制三极管的通断从而产生电脉冲来实现。
波形图
P9 和 P24 分别产生正占空比为 26.5%,周期为 1ms 的 PWM 波,但不能同时为高电平,否则会烧毁三级管,需特别注意。
一次产生四束PWM波后等待30ms再次产生四束波,循环往复即可实现舒缓模式的功能效果。
图中一个脉冲中有四束PWM波,其波形如上图所示。脉冲与脉冲之间的时间为30ms。
各模式实现原理
舒缓、活力、锤击拍打、刮痧、智能五个模式是通过脉冲之间的时间间隔来实现的,如下表:
模式 | 脉冲时间间隔 |
---|---|
舒缓模式 | 30ms |
活力模式 | 20ms |
锤击拍打模式 | 40ms |
刮痧模式 | 50ms |
智能模式 | 随机产生20 30 40 50ms |
相关引脚初始化
void pattern_pin_init(void)
{
gpio_set_func(PATTERN_PIN_A, AS_PWM1_N);
gpio_set_func(PATTERN_PIN_B, AS_PWM5);
gpio_set_func(HEAT_PIN, AS_GPIO);
gpio_set_output_en(PATTERN_PIN_A, 1);
gpio_set_output_en(PATTERN_PIN_B, 1);
gpio_set_output_en(HEAT_PIN, 1);
gpio_write(PATTERN_PIN_A, 0);
gpio_write(PATTERN_PIN_B, 0);
// gpio_write(HEAT_PIN, 1);
//PWM0 1ms cycle 26.5% duty 1000Hz
pwm_set_mode(PWM1_ID, PWM_NORMAL_MODE);
pwm_set_clk(CLOCK_SYS_CLOCK_HZ, CLOCK_SYS_CLOCK_HZ);
pwm_set_phase(PWM1_ID, 0); //no phase at pwm beginning
pwm_set_cycle_and_duty(PWM1_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (0 * CLOCK_SYS_CLOCK_1US) );
pwm_polo_enable(PWM1_ID, 1); //to enable the pwm polarity
pwm_start(PWM1_ID);
//PWM5 1ms cycle 26.5% duty 1000Hz
pwm_set_mode(PWM5_ID, PWM_NORMAL_MODE);
pwm_set_clk(CLOCK_SYS_CLOCK_HZ, CLOCK_SYS_CLOCK_HZ);
pwm_set_phase(PWM5_ID, 0); //no phase at pwm beginning
pwm_set_cycle_and_duty(PWM5_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (265 * CLOCK_SYS_CLOCK_1US) );
}
由于GPIO_PC1(PATTERN_PIN_A)
与档位调节GPIO_PC2(BOOST_PIN)
都用到了PWM功能且GPIO_PC1(PATTERN_PIN_A)
引脚仅支持PWM_0通道,所以GPIO_PC2(BOOST_PIN)
使用PWM_0通道,GPIO_PC1(PATTERN_PIN_A)
使用AS_PWM1_N通道,这里您只需要pwm_polo_enable(PWM1_ID, 1)
将PWM极性改变一下就可以了。
控制PWM个数以及脉冲时间间隔都是通过延时来实现的难免会有误差。由于SDK没有涉及到RTOS所以整个程序是跑在裸机上的,因此该函数执行时需放在while(1)
大循环里面。
void switching_pattern(unsigned char pat)
{
if (pat > 4) {
TUYA_APP_LOG_ERROR("*********No such model!!!**********");
}
switch (pat) {
case relieve:
pwm_start(PWM5_ID);
sleep_us(450); // 延时480us,防止同时置高烧毁三极管
pwm_set_cycle_and_duty(PWM1_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (265 * CLOCK_SYS_CLOCK_1US) );
sleep_us(5 * TIME_MS);
pwm_stop(PWM5_ID);
pwm_set_cycle_and_duty(PWM1_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (0 * CLOCK_SYS_CLOCK_1US) );
sleep_us(30 * TIME_MS);
break;
case vitality:
pwm_start(PWM5_ID);
sleep_us(450);
pwm_set_cycle_and_duty(PWM1_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (265 * CLOCK_SYS_CLOCK_1US) );
sleep_us(5 * TIME_MS);
pwm_stop(PWM5_ID);
pwm_set_cycle_and_duty(PWM1_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (0 * CLOCK_SYS_CLOCK_1US) );
sleep_us(20 * TIME_MS);
break;
case hammering:
pwm_start(PWM5_ID);
sleep_us(450);
pwm_set_cycle_and_duty(PWM1_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (265 * CLOCK_SYS_CLOCK_1US) );
sleep_us(5 * TIME_MS);
pwm_stop(PWM5_ID);
pwm_set_cycle_and_duty(PWM1_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (0 * CLOCK_SYS_CLOCK_1US) );
sleep_us(40 * TIME_MS);
break;
case scraping_therapy:
pwm_start(PWM5_ID);
sleep_us(450);
pwm_set_cycle_and_duty(PWM1_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (265 * CLOCK_SYS_CLOCK_1US) );
sleep_us(5 * TIME_MS);
pwm_stop(PWM5_ID);
pwm_set_cycle_and_duty(PWM1_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (0 * CLOCK_SYS_CLOCK_1US) );
sleep_us(50 * TIME_MS);
break;
case intelligent:
pwm_start(PWM5_ID);
sleep_us(450);
pwm_set_cycle_and_duty(PWM1_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (265 * CLOCK_SYS_CLOCK_1US) );
sleep_us(5 * TIME_MS);
pwm_stop(PWM5_ID);
pwm_set_cycle_and_duty(PWM1_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (0 * CLOCK_SYS_CLOCK_1US) );
sleep_us(((rand() % 4 + 2) * 10) * TIME_MS); // 随机延时20/30/40/50ms
break;
default:
break;
}
return;
}
在产品创建和五大功能模式都实现的基础上接着实现语音播报、加热、档位切换的功能。
本次用到了WTN6系列的离线语音模块,WTN6系列产品为多功能单芯片语音合成4位元为控制器。
Pad 名称 | Pad 编号 | 属性 | 描述 |
---|---|---|---|
PA2 | 1 | I/O | 忙信号输出 |
PA1 | 2 | I/O | 两线串口时钟信号输入端/一线串口数据信号输入端/数脉冲数据信号输入端 |
PA0 | 3 | I/O | 两线串口数据信号输入端/复位脚 |
PA3 | 4 | I/O | 暂未使用(NC) |
PWM- | 5 | out | PWM 输出脚 |
VDD | 6 | Power | 电源正极 |
PWM+/DAC | 7 | I/O | PWM、DAC 输出脚 |
GND | 8 | Power | 电源负极 |
一线串口通讯
本次仅用到了一线串口通讯,一线串口模式可以利用 MCU 通过 DATA 线给 WTN6 系列语音芯片发送数据以达到控制的目的。可以实现控制语音播放、停止、循环等。
管脚分配:
管脚 | 作用 |
---|---|
PA1 | DATA |
PA2 | BUSY |
一线语音地址对应关系:
数据(十六进制) | 功能 |
---|---|
00H | 播放第 0 段语音 |
01H | 播放第 1 段语音 |
02H | 播放第 2 段语音 |
…… | …… |
DFH | 播放第 222 段语音 |
一线串口时序图
先把数据线拉低 5ms 后,发送 8 位数据,先发送低位,再发送高位,使用高电平和低电平比例来表示每个数据位的值。
功能代码
功能引脚初始化
void voice_prompt_init(void)
{
gpio_set_func(WTN6_DATA_PIN | WTN6_BUSY_PIN, AS_GPIO);
gpio_set_input_en(WTN6_BUSY_PIN, 1);
gpio_set_output_en(WTN6_DATA_PIN, 1);
gpio_write(WTN6_BUSY_PIN, 0);
}
声音播放
void voice_playing(uint8_t sb_data)
{
uint8_t s_data, j;
bool b_data;
s_data = sb_data;
gpio_write(WTN6_DATA_PIN, 0);
sleep_us(5000); //延时 5ms
b_data = s_data & 0X01;
for (j=0; j<8; j++) {
if (b_data == 1) {
gpio_write(WTN6_DATA_PIN, 1);
sleep_us(600); //延时 600us
gpio_write(WTN6_DATA_PIN, 0);
sleep_us(200); //延时 200us
} else {
gpio_write(WTN6_DATA_PIN, 1);
sleep_us(200); //延时 200us
gpio_write(WTN6_DATA_PIN, 0);
sleep_us(600); //延时 600us
}
s_data = s_data >> 1;
b_data = s_data & 0X01;
}
gpio_write(WTN6_DATA_PIN, 1);
}
输入参数填入对应地址voice_playing(0x01)
即可播放离线语音。
加热功能是通过控制P7引脚的拉高拉低来实现加热开、加热关的,内置温度检测模块,加热时当温度超过40℃会自动关闭加热功能以免过热引起不适或被烫伤。
int switching_heat(unsigned char warm)
{
if (warm > 1) {
TUYA_APP_LOG_ERROR("*********No such model!!!**********");
}
// printf("wram%d massage_state.heat%d\r\n", warm, massage_state.heat);
switch (warm) {
case strong_heat:
TUYA_APP_LOG_INFO("**********strong_heat************");
gpio_write(HEAT_PIN, 1);
temperature_detection();
break;
case off_heat:
TUYA_APP_LOG_INFO("**********off_heat************");
gpio_write(HEAT_PIN, 0);
break;
default:
break;
}
return 0;
}
/*温度检测,弱热强热功能开启时调用*/
int temperature_detection(void)
{
int Rntc = 0, Vcc = 0;
adc_channel_checkout(channel_x1);
Vcc = adc_sample_and_get_result(); //uint:mv
Rntc = Vcc*R25 / (3300-Vcc);
TUYA_APP_LOG_INFO("Rntc_val=%dΩ", Rntc);
if (Rntc >= 5311) { // NTC在40℃时电阻值为5311Ω
TUYA_APP_LOG_WARNING("********High Temperature Warning!!!********");
gpio_write(HEAT_PIN, 0); //超过40℃关闭加热功能
}
return 0;
}
NTC温度与阻值对照表见上文功能模块处 温度与阻值对应关系图。
按摩仪共设置15个档位,可以控制按摩的力度,通过BOOST升压电路实现。
通过P8口产生PWM波,调节PWM输出的正占空比来实现升压,详细的硬件原理可参考硬件部分关于升压原理的介绍,具体数值如下:
档位 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 最大档 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
升压值(V) | 12 | 15 | 19 | 22 | 26 | 29 | 33 | 36 | 40 | 43 | 47 | 50 | 54 | 57 | 60 | 60 |
占空比(%) | 1 | 2 | 4 | 6 | 7 | 9 | 12 | 16 | 18 | 22 | 24 | 26 | 30 | 34 | 36 | 36 |
档位调节是通过按键的单击和双击来实现的,单击档位加,双击档位减。由于 SDK 配置原因,在程序烧录后复位模组时 P8 口已经被拉高,所以在此处对其做拉低处理。
void boost_init(void)
{
gpio_set_func(BOOST_PIN, AS_PWM0);
gpio_set_output_en(BOOST_PIN, 1);
//PWM0 1ms cycle
pwm_set_mode(PWM0_ID, PWM_NORMAL_MODE);
pwm_set_clk(CLOCK_SYS_CLOCK_HZ, BOOST_PWM_CLOCK_HZ); //设定BOOST升压时PWM频率为 16M / (968-1) ≈ 16.55KHz
pwm_set_phase(PWM0_ID, 0); //no phase at pwm beginning
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (0 * CLOCK_SYS_CLOCK_1US) );
pwm_start(PWM0_ID);
}
初始化完成后通过输入参数的具体值改变占空比升压到对应值。
void switching_gear(unsigned char gears)
{
if (gears > 15) {
TUYA_APP_LOG_ERROR("*********There is no such gear!!!**********");
}
switch (gears) {
case first_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (10 * CLOCK_SYS_CLOCK_1US) );
break;
case second_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (20 * CLOCK_SYS_CLOCK_1US) );
break;
case third_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (40 * CLOCK_SYS_CLOCK_1US) );
break;
case fourth_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (60 * CLOCK_SYS_CLOCK_1US) );
break;
case fifth_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (70 * CLOCK_SYS_CLOCK_1US) );
break;
case sixth_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (90 * CLOCK_SYS_CLOCK_1US) );
break;
case seventh_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (120 * CLOCK_SYS_CLOCK_1US) );
break;
case eighth_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (160 * CLOCK_SYS_CLOCK_1US) );
break;
case ninth_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (180 * CLOCK_SYS_CLOCK_1US) );
break;
case tenth_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (220 * CLOCK_SYS_CLOCK_1US) );
break;
case eleventh_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (240 * CLOCK_SYS_CLOCK_1US) );
break;
case twelfth_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (260 * CLOCK_SYS_CLOCK_1US) );
break;
case thirteenth_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (300 * CLOCK_SYS_CLOCK_1US) );
break;
case fourteenth_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (340 * CLOCK_SYS_CLOCK_1US) );
break;
case fifteenth_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (360 * CLOCK_SYS_CLOCK_1US) );
break;
case max_gear:
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US), (u16) (360 * CLOCK_SYS_CLOCK_1US) );
break;
default:
break;
}
}
断电记忆的原理是将设备在断电前的各个工作状态写入到空闲的 Flash 闪存中,在下次上电时从 Flash 中读取数据,让设备工作在断电前的工作状态。
TLSR8253 芯片 Flash 布局如下:
0x040000
- 0x060000
为未使用区域,可将设备状态写入该区域内。
写状态到Flash。
/***********************************************************
* Function: write_massage_status_to_flash
* Input: none
* Output: none
* Return: none
* Notice: 将按摩仪状态写到 Flash 中
***********************************************************/
void write_massage_status_to_flash(void)
{
Flash_Write_Buff[0] = massage_state.on_off;
Flash_Write_Buff[1] = massage_state.pattern;
Flash_Write_Buff[2] = massage_state.gear;
Flash_Write_Buff[3] = massage_state.heat;
flash_write_page(FLASH_ADDR, FLASH_BUFF_LEN, (unsigned char *)Flash_Write_Buff);
return;
}
从Flash读状态。
/**********************************************************************
* Function: read_massage_status_to_flash
* Input: none
* Output: none
* Return: none
* Notice: 从 Flash 中读取按摩仪断电前状态,存到按摩仪状态结构体内
**********************************************************************/
void read_massage_status_to_flash(void)
{
flash_read_page(FLASH_ADDR, FLASH_BUFF_LEN, (unsigned char *)Flash_Read_Buff);
//将从 Flash 读取到的数据,存放到结构体中
massage_state.on_off = Flash_Read_Buff[0];
massage_state.pattern = Flash_Read_Buff[1];
massage_state.gear = Flash_Read_Buff[2];
massage_state.heat = Flash_Read_Buff[3];
return;
}
恢复出厂设置。
/***********************************************************
* Function: erase_massage_flash
* Input: none
* Output: none
* Return: none
* Notice: 恢复出厂设置
***********************************************************/
void erase_massage_flash(void)
{
massage_state.on_off = OFF;
massage_state.pattern = relieve;
massage_state.gear = first_gear;
massage_state.heat = off_heat;
Flash_Write_Buff[0] = OFF;
Flash_Write_Buff[1] = relieve;
Flash_Write_Buff[2] = first_gear;
Flash_Write_Buff[3] = off_heat;
flash_write_page(FLASH_ADDR, FLASH_BUFF_LEN, (unsigned char *)Flash_Write_Buff);
return;
}
单点蓝牙一个DP的所有信息存放在一个数组中。以功能模式为例,其DPID为104,对应数组为:
unsigned char mode_buf[] = {0x68, 0x04, 0x01, 0x00}; //{DP_ID, DP_type, DP_len, DP_data}
tuya_ble_dp_data_report(mode_buf, 4); //数据上报函数
上报单个DP数据时调用 tuya_ble_dp_data_report(uint8_t *p_data,uint32_t len)
函数即将数据上报到云端。
App下发控制命令也是以数组的形式存放在 dp_data_array[255+3]
数组中,编写DP数据下发处理函数,将 dp_data_array[255+3]
作为输入参数放在 tuya_ble_demo.c
中的 tuya_cb_handler(tuya_ble_cb_evt_param_t* event)
函数中即可实现App指令下发。
void app_dp_handle(uint8_t *dp_data)
{
printf("dp_data:%d %d %d %d\r\n", dp_data[0], dp_data[1], dp_data[2], dp_data[3]);
switch (dp_data[0]) {
case 0x66:
if (dp_data[3] == strong_heat) {
massage_state.heat = strong_heat;
} else {
massage_state.heat = off_heat;
}
printf("dp_data[3]:%d massage_state.heat:%d\r\n", dp_data[3], massage_state.heat);
if (!app_flag) {
switching_heat(massage_state.heat);
}
break;
case 0x67:
printf("dp_data[3]:%d \r\n", dp_data[3]);
switch (dp_data[3]) {
case first_gear:
massage_state.gear = first_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case second_gear:
massage_state.gear = second_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case third_gear:
massage_state.gear = third_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case fourth_gear:
massage_state.gear = fourth_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case fifth_gear:
massage_state.gear = fifth_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case sixth_gear:
massage_state.gear = sixth_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case seventh_gear:
massage_state.gear = seventh_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case eighth_gear:
massage_state.gear = eighth_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case ninth_gear:
massage_state.gear = ninth_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case tenth_gear:
massage_state.gear = tenth_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case eleventh_gear:
massage_state.gear = eleventh_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case twelfth_gear:
massage_state.gear = twelfth_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case thirteenth_gear:
massage_state.gear = thirteenth_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case fourteenth_gear:
massage_state.gear = fourteenth_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case fifteenth_gear:
massage_state.gear = fifteenth_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
case max_gear:
massage_state.gear = max_gear;
if (!app_flag) {
switching_gear(massage_state.gear);
}
break;
default:
break;
}
break;
case 0x68:
if (dp_data[3] == relieve) {
massage_state.pattern = relieve;
} else if (dp_data[3] == vitality) {
massage_state.pattern = vitality;
} else if (dp_data[3] == hammering) {
massage_state.pattern = hammering;
} else if (dp_data[3] == scraping_therapy) {
massage_state.pattern = scraping_therapy;
} else {
massage_state.pattern = intelligent;
}
break;
case 0x69:
if (dp_data[3] == ON) {
massage_state.on_off = ON;
rs2255_init();
voice_prompt_init();
pattern_pin_init();
app_flag = 0;
} else {
massage_state.on_off = OFF;
power_off_init();
app_flag = 1;
}
break;
default:
break;
}
}
设备控制的第一步是添加设备并配网,您可以在移动应用商店中(例如 App Store)下载涂鸦智能或智能生活 App。然后打开 App 进行设备添加和控制。
至此智能颈部按摩仪样机就完成了。
它可以 App 控制,按键控制。具有模式切换,档位调节,温热热灸,自动定时等功能。它采用全新升级 TENS 技术和输出脉冲控制可进行 NTC 恒温控制,按摩的同时也可以对颈椎进行热敷,可加速颈椎的血液流通,深度缓解肌肉酸胀。并且结合了语音播报,是非常棒的智能硬件产品。在这款智能颈部按摩仪的基础上还有很多工作模式可以深入开发,对其功能进行完善。
同时,您可以基于涂鸦 IoT 平台丰富它的功能,也可以更加方便的搭建更多智能产品原型,加速智能产品的开发流程。