蕴含中医智慧的智能颈部按摩仪设计与原型开发

更新时间Invalid date

概况

现代人的生活忙忙碌碌,不少人下班之后还在地铁上用电脑做着未完成的工作,用手机回着各种消息,久而久之,颈椎就会出现疲劳、酸痛的问题,有些甚至会发生眩晕、恶心的症状。为此,我们研究设计了一款智能颈部按摩仪。

智能颈部按摩仪利用中医原理,形成一个高效复合能量场,由电极片接触皮肤,利用低电压的脉冲电流作用于神经或肌肉等。从而促进局部血液循环,改善肌肉状态,缓解急慢性疼痛。加上手机 App 控制,更加灵活方便。

功能设计

  • 智能模式,舒缓模式,活力模式,锤击拍打,刮痧模式。灵活切换模式,唤醒肌肤能量。

  • 0-15 档按摩力度,调节范围广,可以随意选择适合用户的舒适度。

  • 加热高档、低档、停止3个档,可以对颈部进行恒温热敷,随意切换。

硬件框架

功能NeckMassager.png

物料清单

硬件 (6)软件 (2)
  • BTU 云模组

    数量:1

    一款低功耗嵌入式蓝牙协议模组。查看详情

  • NTC温度传感器(MF52B)

    数量:1

    MF52B 系列产品为同向漆包线引线环氧树脂涂装型 NTC 热敏电阻,具有阻值范围宽,阻值及B值精确度高,测试精度高,体积小,反应速度快,能长时间工作。

  • 加热丝

    数量:1

    设备加热导热。

  • WTN6系列语音芯片(喇叭)

    数量:1

    ​蓝牙模组在一线串口模式下通过DATA线给WTN6系列语音芯片发送数据以达到控制的目的。可以实现控制语音播放、停止、循环等。

  • 电极片

    数量:1

    沿用了​样机中的电极片,是两个横向的椭圆形,不锈钢材质的。

  • 电池

    数量:1

    网购的一款锂电池,电池容量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充电,需要数个脉冲来提供足够的能量以加大输出电压。

第二步:产品创建

本小节简单介绍产品创建流程,详细的操作和介绍请参考 选品类创建产品

  1. 进入 涂鸦 IoT 平台

  2. 创建产品时,在运动健康品类中选择颈部按摩仪,通讯协议选择蓝牙创建产品

  3. 完成产品创建,弹出功能点选项框后已有三个必选功能选项,关闭后在自定义功能点处添加产品实现的其他功能点。

  4. 设备面板选择自由配置面板,接下来进入硬件开发流程,选择涂鸦标准模组SDK开发,模组选择BT3L,选择右下角 免费领取10个激活码 获取对应的UUID、authkey以及MAC地址填入后续的SDK中。

第三步:获取SDK及环境搭建

  • GitHub 仓库

    tuya_ble_sdk_Demo_Project_tlsr8253,克隆代码库到本地后,请仔细阅读 README.md 文件。

  • 环境搭建

    TLSR825x 蓝牙单点开发使用的是泰凌微官方 IDE,请访问 IDE for TLSR8 Chips

    下载后默认安装即可。接下来进行项目导入,可参考下载 IDE 的链接。

  • 工程开发

    1. 修改PID。

    2. 修改auth_key、device_id、mac。

    3. 开始编译。

  • 烧录工具(下载链接

    1. 芯片平台选择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

    2. 烧录完成后点击 Reset 程序即可运行。

    3. 烧录器使用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行:

      #define DEBUG_INFO_TX_PIN				GPIO_PC2
      

      修改为:

      #define DEBUG_INFO_TX_PIN				GPIO_PD3
      

第四步:软件方案设计

按摩仪主要有物理按摩和电脉冲两种,本项目为电脉冲类型,其原理是通过电极感应金属片,用脉冲技术对颈椎进行刺激,从而缓解颈椎酸痛。

市场上按摩仪的常见功能主要有敲打、锤击、揉捏、针灸、推拿、刮痧、恒温热敷等,本项目中实现了舒缓、活力、锤击拍打、刮痧、智能五个模式。

功能模式

  • 原理图

    各种模式的实现是通过 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位元为控制器。

  • 工作电压:2.4V~5.2V
  • 待机模式下,静态电流小于5uA
  • 精准的+/-1%内部震荡,有低压复位(LVR=1.8V)看门狗计时
  • 12位元PWM纯音频输出,可直接驱动8Ω/0.5W喇叭和蜂鸣器,DAC音频输出,可外接功放
  • 内置看门狗;
  • 具备串口控制模式:一线串口,两线串口(最多可以加载224段语音,后续有详解),数脉冲
  • 支持BUSY状态输出功能
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 为未使用区域,可将设备状态写入该区域内。

  1. 写状态到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;
    }
    
  2. 从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;
    }
    
  3. 恢复出厂设置。

    /***********************************************************
    *   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 平台丰富它的功能,也可以更加方便的搭建更多智能产品原型,加速智能产品的开发流程。