资讯动态

STM32手环实战:ADXL345计步、心率体温采集与LCD显示完整方案

发布时间:2026/10/18 19:27:01 来源:尧图企业网站定制
简介基于STM32F103C8T6单片机的智能手环功能实现方案面向嵌入式初学者、高校毕业设计或课程设计学生以及可穿戴设备开发爱好者覆盖运动计步与健康监测场景。硬件集成ADXL345三轴加速度传感器、心率检测模块和数字温度传感器软件实现步数统计、步行距离换算、平均速度计算并同步完成心率与体表温度连续监测所有测量结果由LCD1602字符液晶屏分类动态显示。压缩包共92个文件整体仅454KB包含38个头文件与37个C源码文件形成Keil MDK标准分层工程CORE/SYSTEM/HARDWARE另有完整原理图SchDoc、PCB版图PDF、设计说明文档、功能清单TXT、可烧录HEX、demo.py辅助脚本及keilkilll.bat一键清理脚本组织结构清晰、轻量易读。已有64人学习下载。代码基于标准外设库传感器驱动与显示逻辑分离便于逐模块调试与二次开发资料配套完整既可直接烧录验证也可作为STM32外设驱动、低功耗手环逻辑和项目文档撰写的参考底稿。1. STM32F103C8T6手环主控工程ADXL345计步、心率、体温加上LCD1602一条传感链路到底值不值得做在可穿戴设备的开发检索里STM32F103C8T6手环主控工程被搜到的频率一直很高原因很直接它把嵌入式里最值得练的几件事——传感器驱动、信号滤波、数据融合、屏幕显示——收进了一个能戴在手上的小系统。ADXL345负责计步光电式心率传感器测脉搏NTC热敏电阻测体温LCD1602把这三组数据落屏。对刚学完定时器和串口的开发者来说这是从流水灯迈向完整产品的第一道门槛对做方案预研的工程师来说它又是一套验证器件选型和算法可行性的低成本底座。这篇文章按我实际做可穿戴原型机的路子把原理图、驱动、计步算法和显示调度一起讲透连最容易翻车的细节也标出来。2. 原理图设计三处关键选型供电、I2C总线和NTC分压2.1 器件选型为什么是F103C8T6、ADXL345、光电心率模块和LCD1602STM32F103C8T6是Cortex-M3核72MHz64KB Flash20KB RAM没有FPU。这个配置对手环来说不算宽裕但恰好卡在“够用又需要动脑子”的位置计步和心率滤波要尽量用定点运算或快速近似这能帮你提前建立对嵌入式算力边界的感知。外设上它有2个I2C、3个SPI、1个ADC挂三个传感器加一块屏幕不需要刻意省资源。ADXL345是学习级三轴加速度计里资料最全的一颗I2C和SPI双接口量程±2g到±16g可编程输出数据率可设。选它而不是MPU6050关键原因是它没有陀螺仪功耗更低而计步只需要加速度不需要做姿态融合MPU6050也不是不行但代码复杂度会明显上一个台阶。心率模块我建议用光电式PPG方案常见模块是红光加红外双LED加光电二极管通过I2C输出数字信号。它的好处是和ADXL345共用一条I2C总线地址不冲突就能挂上去。体温部分直接用NTC热敏电阻不要用DS18B20这类数字温度传感器NTC可以贴着皮肤用PCB铜箔感温响应更快成本也更低而且通过电阻分压接到STM32的ADC引脚即可不占用I2C地址。LCD1602看似老实则在调试阶段比OLED更实用因为它不依赖全屏刷新显示信息密度高把步数、心率和体温一屏放下成本只有OLED的一半。搭配PCF8574 I2C转接板后只占SCL、SDA两根线整机走线很清爽。下表是我常用的引脚分配信号器件引脚说明SCLADXL345、心率模块、LCD转接板PB6I2C1时钟线SDAADXL345、心率模块、LCD转接板PB7I2C1数据线ADC输入NTC分压节点PA0ADC1通道0电源全部器件3.3V轨每颗IC就近0.1uF去耦2.2 供电与去耦3.3V电源轨、BOOT0和I2C上拉电阻的规范接法整个系统统一用3.3V。USB供电时先降压常用AMS1117-3.3或RT9193后者压差更小、静态功耗更低。原理图里最容易埋雷的是三个地方。第一I2C总线上挂了三个从机上拉电阻放一组就好不要每个模块板都自带一组上拉否则等效并联阻抗可能低于2kΩ低电平拉不下去通信会随机失败。我一般取4.7kΩ总线较长时降到2.2kΩ。第二BOOT0引脚必须用10kΩ电阻下拉到GND。很多复刻工程把这个引脚悬空上电后程序不跑还以为是晶振问题。F103的BOOT0悬空时电平不确定这是玄学问题里最常见的一个。第三48pin封装的F103把VREF和VDDA内部连在一起所以VDDA要单独滤波用10Ω电阻或磁珠从3.3V引过来再并一个1uF和一个0.1uF电容。这个细节直接影响ADC采样稳定性和心率模拟信号质量。每颗芯片的电源引脚都放0.1uF去耦电容并尽可能靠近引脚这不是可选项是可靠性的底线。2.3 NTC体温采样电路分压比、阻值范围与ADC分辨率核算NTC我用最通用的10kΩ、B值3950。分压结构取“固定电阻在上、NTC在下”3.3V先经过10k固定电阻中间节点接PA0再经过NTC到地。温度升高时NTC阻值下降节点电压也随之下降换算关系更直观。人体体温范围35℃到42℃内这只10k NTC的阻值大约在6.5k到4.9k之间分压节点电压约1.30V到1.08V。12位ADC在3.3V参考下每LSB约0.8mV算下来这个区间约有275个LSB温度分辨率可以做到0.1℃以内完全够用手环显示。需要注意的是ADC引脚的采样稳定时间。分压节点阻抗约在3k到4kADC内部采样电容充电需要一点时间我习惯在每次切换通道后延时50us再启动转换同时在PA0与地之间放一个0.1uF电容构成简单低通滤波抑制手机射频和电源高频干扰。3. ADXL345计步落地I2C驱动、加速度合成与峰值检测算法3.1 初始化与三轴读取寄存器配置与I2C连续读ADXL345的I2C地址由SDO引脚决定SDO接GND时是0x53接VCC时是0x1D。初始化第一步读DEVID寄存器固定值0xE5读不到就说明链路不通先去查地址和上拉电阻。#include stm32f1xx_hal.h #include math.h #define ADXL345_ADDR 0x53 // SDO接地时7bit地址 #define ADXL345_DEVID 0x00 #define ADXL345_BW_RATE 0x2C #define ADXL345_POWER_CTL 0x2D #define ADXL345_DATAX0 0x32 static uint8_t adxl_read_reg(uint8_t reg) { uint8_t val 0; HAL_I2C_Mem_Read(hi2c1, ADXL345_ADDR 1, reg, I2C_MEMADD_SIZE_8BIT, val, 1, 100); return val; } static void adxl_write_reg(uint8_t reg, uint8_t val) { HAL_I2C_Mem_Write(hi2c1, ADXL345_ADDR 1, reg, I2C_MEMADD_SIZE_8BIT, val, 1, 100); } int adxl345_init(void) { if (adxl_read_reg(ADXL345_DEVID) ! 0xE5) { return -1; // I2C通信失败检查接线或地址 } // DATA_FORMAT0x01±4g量程10位分辨率 adxl_write_reg(0x31, 0x01); // 输出率100Hz带宽50Hz adxl_write_reg(ADXL345_BW_RATE, 0x0A); // 退出待机进入测量模式 adxl_write_reg(ADXL345_POWER_CTL, 0x08); return 0; }逻辑说明DEVID是只读辨识寄存器能读回0xE5说明I2C物理层通了。DATA_FORMAT写0x01选择±4g量程相比默认±2g给手腕摆动留出更大动态余量避免削顶。BW_RATE写0x0A对应100Hz输出率这个采样率对1.5Hz到2.2Hz的步频有足够余量芯片内部还能做抗混叠滤波。POWER_CTL写0x08是测量模式如果写0则上电后停留在待机模式读出来的三个轴全是0。读取三轴数据时用连续读6字节从DATAX0开始一次I2C事务拿回全部三轴避免反复访问typedef struct { int16_t x; int16_t y; int16_t z; } acc_t; acc_t adxl345_read(void) { uint8_t buf[6]; acc_t a; HAL_I2C_Mem_Read(hi2c1, ADXL345_ADDR 1, ADXL345_DATAX0, I2C_MEMADD_SIZE_8BIT, buf, 6, 100); a.x (buf[1] 8) | buf[0]; a.y (buf[3] 8) | buf[2]; a.z (buf[5] 8) | buf[4]; return a; }逻辑说明0x32是DATAX0寄存器连续读会自动地址自增。10位模式下数据以补码存进16位高6位是符号扩展。±4g量程下每LSB对应7.8mg后面计算合成加速度时要按这个比例换算成g值。3.2 计步算法合成加速度、高通滤波与上下沿确认计步不能只看某一个轴因为手环在手腕上的姿态会变单轴信号随朝向不同会失效。标准做法是取三轴合成加速度a_mag sqrt(x^2 y^2 z^2)合成值包含约1g的重力分量和叠加的摆动分量。先做高通滤波去掉重力再对滤波后的信号做峰值检测#define ALPHA_HP 0.85f float hp_filter(float input, float *prev) { *prev ALPHA_HP * (*prev input - *prev); return input - *prev; }逻辑说明这个一阶高通是先做低通再与原信号相减工程上直观且稳定。ALPHA_HP取值0.85对应100Hz采样率下的截止频率约2Hz能把静止时的低频漂移滤掉同时保留1.5Hz到2.2Hz的步频。如果采样率改成50Hz这个系数要调到0.75附近否则截止频率偏移低幅摆手会被放大成伪步。峰值检测我用“波峰—波谷成对确认”而不是单点过阈值这样能滤掉大部分毛刺#define PEAK_THRESHOLD 0.9f // 合成加速度峰值门限单位g #define STEP_MIN_MS 250 // 步间隔下限对应240步/分钟 #define STEP_MAX_MS 1200 // 步间隔上限对应50步/分钟 static float acc_magnitude(acc_t a) { float xg a.x / 128.0f; // ±4g量程每g对应128 LSB float yg a.y / 128.0f; float zg a.z / 128.0f; return sqrtf(xg * xg yg * yg zg * zg); } uint32_t step_count 0; static uint8_t detect_state 0; void step_detect(float mag, uint32_t now_ms) { static float last_peak 0, last_valley 0; static uint32_t last_step_ms 0; if (detect_state 0) { if (mag PEAK_THRESHOLD mag last_peak) { last_peak mag; } else if (mag last_peak - 0.15f) { detect_state 1; last_valley mag; } } else { if (mag last_peak - 0.30f mag last_valley) { last_valley mag; } else if (mag last_valley 0.15f) { uint32_t gap now_ms - last_step_ms; if (gap STEP_MIN_MS gap STEP_MAX_MS) { step_count; last_step_ms now_ms; } detect_state 0; last_peak 0; } } }逻辑说明进入找峰状态后持续更新最高点直到信号从峰值回落0.15g认为峰已经过去转入找谷。找谷同样要求信号比峰值低0.3g再从谷底回升0.15g这才确认一个完整摆动周期。两个0.15g的回跳阈值过滤掉了幅度小于0.3g的抖动。步距窗口用毫秒而不是样本数避免采样率改变后参数失效。参数说明PEAK_THRESHOLD取0.9g对应正常走路慢走时摆动小可以降到0.7g跑步时摆动大可以升到1.2g。回落阈值0.15g偏灵敏如果出现多记步把它改成0.25g让判定更钝。3.3 三个必调参数阈值、回落宽度和步距时间窗计步不准的原因多数不在算法复杂而在参数和佩戴方式不匹配。第一个必调参数是合成加速度峰值阈值。它要和量程联动。±4g量程下0.9g合理如果改回±2g量程同样的摆动换算成g值会偏大阈值要相应提高否则每次摆手都被算成一步。第二个是回落宽度。走路时波形平滑0.15g的回落宽度没有问题但如果手环戴得松传感器会出现高频抖动回落宽度要加大到0.25g甚至0.3g。判断依据是看串口打出的原始mag波形如果波形在峰值附近有连续毛刺就加宽。第三个是步距时间窗。我将STEP_MIN_MS设为250STEP_MAX_MS设为1200对应每分钟50到240步。跑步时步频可能到180到200仍在窗口内。如果做的是老人步态检测建议把下限放宽到300把上限提高到1500否则慢步会被过滤掉。静止漂移也是隐蔽问题。手环戴在手腕上静止时肌肉微颤会产生约0.3g的小波动双沿判定已经能挡掉大半。如果在地铁或公交车上车身振动频率和步频接近会出现虚步。这种场景原型阶段不用强纠产品上一般会加入连续两步周期一致性判断。4. 心率与体温采集PPG信号链和ADC信号链的完整实现4.1 心率传感器初始化与PPG读取心率模块内部原理是红光和红外LED交替照射皮肤光电二极管接收反射光血液搏动引起反射光强变化形成光电容积脉搏波PPG。模块通过I2C输出原始PPG采样值STM32负责从波形中提取心率。市面上常见模块寄存器地址基本相近但不同批次可能有差异初始化前先确认模块手册。常见的初始化流程是复位芯片、配置采样率和LED脉宽#define HR_SENSOR_ADDR 0x57 int hr_init(void) { uint8_t val; // 复位模块bit6置1触发软复位 val 0x40; HAL_I2C_Mem_Write(hi2c1, HR_SENSOR_ADDR 1, 0x09, I2C_MEMADD_SIZE_8BIT, val, 1, 100); HAL_Delay(100); // 配置采样率约50HzLED脉宽约411us val 0x03; HAL_I2C_Mem_Write(hi2c1, HR_SENSOR_ADDR 1, 0x08, I2C_MEMADD_SIZE_8BIT, val, 1, 100); return 0; }逻辑说明复位寄存器地址0x09写0x40是软复位等待100ms让内部上电时序稳定。工作配置寄存器地址0x08写0x03意思是50Hz采样率和中等LED电流。50Hz对心率信号足够心率基频在1Hz到2Hz左右。读取PPG时要关注FIFO溢出。模块内部有FIFO缓存多组数据如果读取不及时新数据会覆盖旧数据波形出现断点。我的经验是每20ms读一次并检查溢出标志溢出时先清FIFO再继续。4.2 峰值间隔法计算心率与滑动窗口滤波完整的心率算法用FFT做频谱估计在F103上能做但对原型项目来说不划算一是M3没有FPU二是窗口数据要反复搬运。工程上更常见的是峰值间隔法检测PPG波形的波峰记录相邻两个波峰的时间间隔再用60000除以间隔得到瞬时心率做滑动平均。#define HR_WINDOW_SIZE 10 static uint16_t hr_buf[HR_WINDOW_SIZE] {0}; static uint8_t hr_buf_idx 0; static uint32_t last_peak_ms 0; static uint8_t rising_edge 0; uint16_t hr_value 0; void ppg_process(uint32_t ppg_value, uint32_t now_ms) { // 典型模块原始值范围在20000到80000之间 // 实际阈值需要根据静态基线调整不能写死 if (ppg_value 55000 rising_edge 0) { rising_edge 1; if (last_peak_ms ! 0 (now_ms - last_peak_ms) 300) { uint32_t interval now_ms - last_peak_ms; uint16_t bpm 60000 / interval; if (bpm 40 bpm 220) { hr_buf[hr_buf_idx] bpm; hr_buf_idx (hr_buf_idx 1) % HR_WINDOW_SIZE; uint32_t sum 0, cnt 0; for (uint8_t i 0; i HR_WINDOW_SIZE; i) { if (hr_buf[i] 0) { sum hr_buf[i]; cnt; } } if (cnt 5) { hr_value sum / cnt; } } } last_peak_ms now_ms; } else if (ppg_value 45000) { rising_edge 0; } }逻辑说明这里用了高阈值和低阈值构成滞回比较避免信号在阈值附近反复震荡。55000和45000是某类模块的常见初值实际佩戴时基线因人种、肤色、松紧和光照差异很大必须先用串口打印一段原始PPG数据观察波形基线后再设定。滑动窗口长度10对50Hz采样率来说覆盖了2秒的数据。心率更新滞后约1秒正好抵消呼吸性心率波动的影响显示值不会来回跳。4.3 NTC体温换算B值公式、滑动平均与电压极性反转体温采样用ADC1通道0NTC分压节点接PA0。分压结构是固定电阻在上、NTC在下温度升高时NTC阻值下降节点电压下降这是反极性信号换算时要注意方向。#define NTC_SERIES_RES 10000.0f // 分压固定电阻 #define NTC_NOMINAL 10000.0f // 25℃时NTC阻值 #define NTC_B_VALUE 3950.0f float read_ntc_temperature(void) { uint16_t adc_val 0; HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 100) HAL_OK) { adc_val HAL_ADC_GetValue(hadc1); } float vout adc_val / 4095.0f * 3.3f; // 固定电阻在上NTC在下 // Rntc Rfixed * Vout / (3.3 - Vout) float rntc NTC_SERIES_RES * vout / (3.3f - vout); // B值公式1/T 1/T0 (1/B) * ln(R/R0) float ln_r logf(rntc / NTC_NOMINAL); float t_kelvin 1.0f / (1.0f / 298.15f 1.0f / NTC_B_VALUE * ln_r); return t_kelvin - 273.15f; }逻辑说明B值公式是厂商给出的标准方程比Steinhart-Hart三系数方程更适合原型阶段因为不需要额外标定。注意分子位置的vout不能为0所以NTC分压节点不能直接短路到地。参数说明如果买到的是100k NTCNTC_NOMINAL要改成100000NTC_SERIES_RES也要配套换成100k。100k方案在人体温区节点阻抗更高对走线噪声更敏感原型阶段我还是首推10k。体温数据做一个简单的滑动平均连续采5次去掉最大最小再平均能滤掉接触不良造成的尖峰。如果体温显示持续漂移优先检查是不是手指按压导致局部温度累积而不是怀疑算法。5. LCD1602显示整合与排查避坑5个高发问题和根因5.1 LCD1602 I2C驱动PCF8574转接板的底层时序LCD1602原生并行接口需要6个IO加上传感器占用的I2C和ADCIO会紧张。带PCF8574转接板的LCD1602模块把8位并行转成I2C只占SCL和SDA两条线而且能和ADXL345、心率模块共用同一条I2C总线。PCF8574的输出映射是P7到P4接LCD的D7到D4P3接背光P2接ENP1接RWP0接RS。写数据时要先发高4位脉冲再发低4位脉冲每个脉冲要有足够高电平时间#define LCD_ADDR 0x27 // 常见默认地址A0A1A2全接地时 static void lcd_byte(uint8_t data, uint8_t rs) { uint8_t buf[4]; uint8_t hi data 0xF0; uint8_t lo (data 4) 0xF0; uint8_t ctrl rs ? 0x01 : 0x00; // RS位 // 依次输出高4位EN1高4位EN0低4位EN1低4位EN0 buf[0] hi | 0x04 | ctrl | 0x08; buf[1] hi | 0x00 | ctrl | 0x08; buf[2] lo | 0x04 | ctrl | 0x08; buf[3] lo | 0x00 | ctrl | 0x08; HAL_I2C_Master_Transmit(hi2c1, LCD_ADDR 1, buf, 4, 100); HAL_Delay(2); } void lcd_init(void) { HAL_Delay(200); // 等待LCD自身上电稳定 lcd_byte(0x30, 0); // 8位模式指令 lcd_byte(0x20, 0); // 切到4位模式 lcd_byte(0x28, 0); // 4位模式双行 lcd_byte(0x0C, 0); // 显示开、光标关 lcd_byte(0x01, 0); // 清屏 HAL_Delay(20); }逻辑说明PCF8574转接板虽然只用了I2C但LCD内部还是HD44780控制器初始化时序必须完整走一遍4位模式流程。lcd_byte里的0x04对应EN引脚0x08对应背光使能发送时先拉高EN再拉低产生一个下降沿LCD在下降沿锁存数据。每个字节之间的2ms延时是为了等LCD内部处理完如果延时太短会丢字符。注意这个地址和引脚映射是针对最常见的转接板不同批次可能有差异拿到模块先扫描I2C地址确认是0x27还是0x3F。5.2 多传感器调度时间片轮询与屏幕刷新策略三个传感器加一块屏幕调度不用上RTOS一个while循环加时间戳差值就够了。ADXL345按100Hz轮询PPG按50Hz读体温和屏幕每秒刷新一次。uint32_t t_now, t_last_ppg, t_last_lcd; t_last_ppg 0; t_last_lcd 0; while (1) { t_now HAL_GetTick(); acc_t acc adxl345_read(); float mag acc_magnitude(acc); step_detect(mag, t_now); if (t_now - t_last_ppg 20) { // 50Hz t_last_ppg t_now; uint32_t ppg hr_read_ppg(); ppg_process(ppg, t_now); } if (t_now - t_last_lcd 1000) { // 1Hz t_last_lcd t_now; float temp read_ntc_temperature(); refresh_lcd(step_count, hr_value, temp); } HAL_Delay(10); }逻辑说明用时间戳差值而不是HAL_GetTick取模避免毫秒翻转时发生瞬时重复执行。LCD刷新只有1Hz因为LCD1602本身响应慢刷快了人眼也感知不到增益。I2C总线上三个从机地址不同HAL库的Mem_Read和Master_Transmit每次都会发起完整START到STOP事务天然是互斥的不需要加锁。显示布局上第一行放“STEP”和“HR”第二行放“TEMP”和状态。LCD1602一行只有16个字符数据之间要留分隔符避免数字位数变化时黏在一起。5.3 5类典型故障的现象、原因与解决办法现象一I2C扫描不到ADXL345。原因通常是SDO引脚悬空导致地址不确定或者上拉电阻没生效。解决先确认SDO是接地还是接VCC再分别扫描0x53和0x1D两个地址量一下SCL和SDA对地电压3.3V下空闲电平应该在3.0V以上。现象二计步数明显偏多。原因一般是PEAK_THRESHOLD太低或回落宽度太小手部小动作也被当成一步。解决把阈值提到1.1g及以上把回落宽度从0.15g改到0.25g然后按正常步速走100步统计误差。现象三心率读数忽高忽低。原因通常是佩戴不紧、环境光漏进传感器或者LED电流太大导致信号饱和。解决用遮光胶带或黑色软胶包裹模块边缘调低LED电流再把滑动窗口从10加大到20可以让显示值稳定下来。现象四NTC体温读数漂移。原因多半是电源纹波直接进了ADC或者分压节点阻抗过高。解决在PA0和地之间加0.1uF电容软件里做5次采样去极值平均还可以加一阶滞后滤波让温度变化速率更符合物理规律。现象五LCD1602复位后乱码。原因是初始化时序不对或LCD上电比MCU晚STM32已经跑完初始化序列LCD还没准备好。解决在lcd_init开头加200ms延时确保LCD内部上电复位完成再按HD44780手册的时序逐条发送初始化命令。若只在部分字符随机异常重点检查PCF8574转接板的EN脉冲宽度把lcd_byte里的2ms延时加长到5ms。6. 进阶验证用串口日志把计步阈值从拍脑袋变成工程标定手环工程最容易被忽略的是最后一步代码跑起来了但没人验证数据对不对。我习惯把串口当成示波器把合成加速度、PPG原始值、时间戳按CSV格式打出来存到电脑里画曲线。格式类似time_ms,acc_mag_g,ppg_raw,step_flag 0,1.03,53210,0 10,1.05,53289,0 20,1.47,53312,1step_flag置1表示算法认为这一步成立。把这份数据和实际迈步脚数对齐后才能判断阈值是否合理而不是看LCD数字“好像差不多”。具体做法是走100步步速刻意分成慢、中、快三段每段走完按一下板载按键打一个标记行离线统计每段误差。标定流程我固定分三步第一步记录三种场景的原始mag峰值静止时看基线噪声幅度慢走看峰值快走看峰值上限第二步把阈值定在慢走峰值的70%到80%确保慢走能触发、静止抖动不能触发第三步验证步距窗口是否覆盖三段步频必要时动态统计相邻峰间隔的中位数来做自适应。我见过不少开发者在这类工程上反复下载数十次固件就为了调一个阈值本质上是把算法当成了黑匣子在猜。把原始数据打出来看一次曲线比盲改代码高效得多。这个习惯也是我从这个手环工程里收获最大的部分传感器数据必须先被看见才能被信任。做完这个工程之后计步、心率、体温三路数据已经形成了完整的闭环。换一块OLED替换LCD1602或者接上蓝牙模块把数据传到手机都是在现有驱动上的增量改造工程骨架不需要推翻。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑