工业现场的温度采集说起来简单做起来全是细节。我做过好几个 HVAC 控制板和工业变送器的项目最常被问到的问题就是热电偶和 RTD 到底怎么选24 位 ADC 是不是必须的MCU 和 ADC 之间用 SPI 还是 I2C这些问题背后其实都指向同一个核心矛盾——在工业环境的电磁干扰、宽温漂移和长线传输条件下怎么把微伏级的温度信号稳定地采回来。ADS122U04IPWR 这颗 24 位 ΔΣ ADC 配合 PIC18F87J11 这颗经典 8 位 MCU是我在多个项目中反复验证过的一套组合成本可控、精度够用、抗干扰能力也经得起现场考验。下面我把这套方案从选型逻辑到寄存器配置、从硬件布局到软件状态机完整拆一遍。1. 为什么是 ADS122U04 加 PIC18F87J11 这个组合1.1 温度测量的核心矛盾不在 ADC 位数很多人一上来就盯着多少位 ADC觉得 24 位一定比 16 位好。实际在工业温度测量里决定精度的往往不是 ADC 的分辨率而是前端信号调理和参考源的稳定性。一个 K 型热电偶在 25°C 环境下输出大约 1mV温度每变化 1°C 只产生约 41μV 的电压变化。如果参考电压本身漂了 50ppm那换算到温度上就是好几度的误差再高的位数也救不回来。ADS122U04 的价值在于它把可编程增益放大器PGA、低漂移参考源、激励电流源和 24 位 ΔΣ 调制器集成在一颗芯片里。它的内置参考漂移典型值是 5ppm/°CPGA 增益可以设到 128 倍这意味着热电偶那 41μV/°C 的微弱信号可以被放大到 ADC 能轻松分辨的量级。同时它自带的两路匹配激励电流源IDAC可以直接给 RTD 或热敏电阻供电省掉了外部恒流源电路。1.2 PIC18F87J11 在这个方案里的角色定位PIC18F87J11 是一颗 64 引脚、128KB Flash 的 8 位 MCU主频最高 40MHz自带 SPI、I2C、UART 和 12 位 ADC。在这个方案里它的 12 位 ADC 基本不用因为精度不够它的核心任务是通过 SPI 读取 ADS122U04 的转换结果运行温度换算算法热电偶查表、RTD 的 Callendar-Van Dusen 方程处理冷端补偿CJC驱动 LCD 或通过 UART/RS-485 上传数据管理多通道切换和采样时序选它的理由很实际工业级温度范围-40°C 到 85°C 甚至 125°C、丰富的外设、成熟的开发工具链以及大量存量代码可以直接复用。HVAC 控制板通常还需要驱动继电器、读取按键、控制风机PIC18F87J11 的 I/O 数量和定时器资源刚好够用不需要再挂一颗 MCU。1.3 这套组合的典型应用场景应用场景传感器类型通道数精度要求采样率HVAC 风管温度RTD PT10002-4±0.5°C10-20 SPS工业炉温监测K 型热电偶1-2±1°C20-50 SPS冷库温度记录NTC 热敏电阻4-8±0.3°C5-10 SPS锅炉水温控制PT100 三线制1±0.2°C20 SPS这张表是我从实际项目里总结的可以看到不同场景对采样率和精度的要求差异很大而 ADS122U04 的灵活配置刚好能覆盖这些需求。它的数据速率可以从 5 SPS 到 2000 SPS 调节PGA 增益从 1 到 128 可编程激励电流从 10μA 到 1.5mA 分档可调。2. 硬件设计里最容易翻车的几个点2.1 模拟输入端的滤波和共模电压ADS122U04 的模拟输入是差分输入但共模电压必须落在规定范围内否则内部 PGA 会饱和。很多人在做热电偶输入时直接把热电偶两端接到 AIN0 和 AIN1结果发现读数乱跳。原因是热电偶的共模电压是浮空的没有直流通路输入级会漂到轨附近。正确的做法是给每个输入端加一个偏置电阻到中间电平比如 AVDD/2。典型电路是在 AIN0 和 AIN1 各接一个 1MΩ 电阻到 REFOUT/2 或者用一个电阻分压网络产生 2.5V 的偏置。同时差分输入端并一颗 100nF 的 C0G 电容做共模滤波再串两颗 1kΩ 电阻做限流。这个 RC 网络的截止频率大约在 1.6kHz对 50Hz/60Hz 工频干扰有很好的抑制。注意偏置电阻不能用太高阻值否则会引入热噪声。1MΩ 是经验值再高的话噪声会明显增大。如果传感器是 RTD 三线制偏置网络要重新设计因为 RTD 本身有低阻直流通路。2.2 参考电压的选择和布局ADS122U04 支持外部参考和内部参考。内部参考的温漂是 5ppm/°C对于大多数工业温度测量已经够用但如果你的应用要求全温区 ±0.1°C 的精度建议用外部精密参考比如 REF5025 这类低漂移基准。用内部参考时REFP 和 REFN 之间必须接一颗 1μF 到 10μF 的陶瓷电容而且这颗电容要尽量靠近芯片引脚走线要短而粗。我见过一个案例电容放在离芯片 2cm 远的地方结果参考电压上叠加了明显的开关噪声温度读数每隔几秒就跳一下。后来把电容挪到引脚旁边问题立刻消失。2.3 电源去耦和地平面处理ADS122U04 有 AVDD 和 DVDD 两组电源。模拟电源和数字电源必须分开走线在芯片下方用单点连接或者磁珠隔离。每个电源引脚旁边都要放 100nF 加 1μF 的去耦电容100nF 要最近。地平面处理上模拟地和数字地在芯片下方分开铺铜最后在一点汇合。这个一点通常选在芯片的 AGND 引脚附近。如果整个板子只有一个地平面那至少要把模拟部分的走线和数字部分的走线物理隔开不要让数字信号线从模拟区域穿过。2.4 SPI 接口的时序和隔离ADS122U04 的 SPI 接口最高支持 10MHz但在工业环境里不建议跑太高我一般用 1MHz 到 4MHz。原因是长线传输时高速时钟的边沿会变缓容易产生时序问题。如果 ADC 和 MCU 不在同一块板上中间要用隔离器或者缓冲器。SPI 的四根线CS、SCLK、DIN、DOUT中CS 和 SCLK 是最容易受干扰的。建议在 CS 和 SCLK 上串 22Ω 到 33Ω 的电阻做阻抗匹配DOUT 上可以加一颗小电容做滤波。如果现场干扰特别严重考虑用数字隔离器把 SPI 隔离掉但要注意隔离器的传播延迟会影响最高时钟频率。3. ADS122U04 的寄存器配置与初始化流程3.1 寄存器映射和关键配置位ADS122U04 有四个配置寄存器Config0 到 Config3通过 SPI 命令读写。下面这张表是我在实际项目里常用的配置组合寄存器位域配置值说明Config0MUX[3:0]0000AIN0/AIN1 差分输入Config0GAIN[2:0]100PGA 增益 16Config0PGA_BYPASS0使能 PGAConfig1DR[2:0]01120 SPSConfig1MODE[1:0]00连续转换模式Config1CM0单次转换Config2VREF[1:0]00内部参考 2.048VConfig2IDAC[2:0]101激励电流 500μAConfig3DRDY_MODE0DRDY 专用引脚配置的逻辑是这样的先确定传感器类型和信号幅度再反推 PGA 增益和数据速率。比如 K 型热电偶在 0-500°C 范围内输出 0-20mV用内部 2.048V 参考时如果 PGA 增益设 128满量程输入是 2.048V/128 16mV刚好覆盖。但如果你的测量范围到 1000°C热电偶输出能到 40mV这时候增益就要降到 64 或 32。3.2 初始化代码示例下面这段初始化代码是基于 PIC18F87J11 的 SPI 外设写的用 MCC 生成的框架可以直接嵌入// ADS122U04 初始化序列 void ADS122U04_Init(void) { // 1. 复位芯片 ADS122U04_SendCommand(CMD_RESET); __delay_ms(1); // 等待复位完成 // 2. 配置 Config0: AIN0/AIN1, 增益16, PGA使能 uint8_t config0 0x00; // MUX0000, GAIN100, PGA_BYPASS0 ADS122U04_WriteRegister(REG_CONFIG0, config0); // 3. 配置 Config1: 20SPS, 连续转换, 单次模式 uint8_t config1 0x18; // DR011, MODE00, CM0 ADS122U04_WriteRegister(REG_CONFIG1, config1); // 4. 配置 Config2: 内部参考, 激励电流500uA uint8_t config2 0x05; // VREF00, IDAC101 ADS122U04_WriteRegister(REG_CONFIG2, config2); // 5. 配置 Config3: DRDY专用引脚 uint8_t config3 0x00; ADS122U04_WriteRegister(REG_CONFIG3, config3); // 6. 启动连续转换 ADS122U04_SendCommand(CMD_START); }这段代码里有个细节复位之后必须等至少 1ms 再写寄存器因为芯片内部的上电复位和振荡器稳定需要时间。我早期有个项目为了省时间复位后直接写寄存器结果偶尔出现配置不生效的情况后来加了延时就没再出过问题。3.3 读取转换结果的时序ADS122U04 的 DRDY 引脚在转换完成时会拉低。读取数据时要先等 DRDY 变低再发读命令然后连续读 3 个字节。注意数据是 24 位补码格式最高字节先出。int32_t ADS122U04_ReadData(void) { uint8_t data[3]; int32_t result 0; // 等待 DRDY 变低 while (DRDY_PIN 1); // 拉低 CS CS_PIN 0; // 发送读数据命令 SPI_Write(CMD_RDATA); // 读 3 个字节 data[0] SPI_Read(); data[1] SPI_Read(); data[2] SPI_Read(); // 拉高 CS CS_PIN 1; // 组合成 24 位有符号数 result ((int32_t)data[0] 16) | ((int32_t)data[1] 8) | data[2]; // 符号扩展 if (result 0x800000) { result | 0xFF000000; } return result; }提示读数据时 CS 必须在整个 3 字节传输期间保持低电平中间不能拉高。有些 SPI 驱动会在每个字节之间自动拉高 CS这会导致数据错位。用 MCC 配置 SPI 时要确认 CS 是手动控制的。4. 温度换算算法的实现细节4.1 热电偶的查表和冷端补偿热电偶的电压-温度关系是非线性的不能用简单的线性公式换算。标准做法是用 NIST 提供的分度表做查表加线性插值。以 K 型热电偶为例分度表覆盖 -200°C 到 1372°C每 1°C 一个点总共 1573 个条目。在 PIC18F87J11 的 128KB Flash 里存这张表完全没问题。冷端补偿是热电偶测量的关键。热电偶测量的是热端和冷端之间的温差所以必须知道冷端的绝对温度。常用方案是在 ADC 的另一个通道接一颗 PT1000 或者高精度 NTC测量接线端子处的温度。ADS122U04 的 IDAC 可以直接给 PT1000 供电省掉外部电路。float Thermocouple_Convert(int32_t adc_code, float cold_junction_temp) { // 1. 将 ADC 码转换为电压 float v_measured (float)adc_code * VREF / (GAIN * 8388608.0f); // 2. 加上冷端补偿电压 float v_cjc Thermocouple_LookupVoltage(cold_junction_temp); float v_total v_measured v_cjc; // 3. 查表得到热端温度 float hot_temp Thermocouple_LookupTemp(v_total); return hot_temp; }这里的VREF是 2.048VGAIN是 168388608是 2 的 23 次方24 位 ADC 的满量程一半。注意 ADC 码是补码格式满量程对应 ±8388608。4.2 RTD 的 Callendar-Van Dusen 方程PT100 和 PT1000 的换算用 Callendar-Van Dusen 方程在 0°C 以上可以简化为R(T) R0 × (1 A×T B×T²)其中 R0 是 0°C 时的阻值PT100 是 100ΩPT1000 是 1000ΩA 3.9083×10⁻³B -5.775×10⁻⁷。反解 T 的时候可以用二次方程求根公式或者用迭代法。实际项目里我更喜欢用分段线性插值因为 PIC18F87J11 做浮点运算比较慢查表加插值的效率更高。把 -200°C 到 850°C 分成若干段每段存两个端点值和斜率计算量小很多。4.3 数字滤波和异常值处理工业现场的干扰是常态ADC 读数偶尔跳变是正常的关键是怎么滤掉。我在项目里通常用两级滤波第一级是中值滤波连续采 5 个点去掉最大和最小剩下 3 个取平均。这能有效抑制脉冲干扰。第二级是一阶低通滤波公式是y[n] α × x[n] (1-α) × y[n-1]α 的取值取决于你的响应速度要求。对于 HVAC 温度测量温度变化很慢α 可以取 0.1 到 0.2截止频率大约在 0.1Hz 到 0.5Hz对工频干扰和随机噪声都有很好的抑制。#define ALPHA 0.15f float LowPass_Filter(float new_sample) { static float filtered 0.0f; static uint8_t first_run 1; if (first_run) { filtered new_sample; first_run 0; } else { filtered ALPHA * new_sample (1.0f - ALPHA) * filtered; } return filtered; }注意α 不能取太小否则系统响应会非常迟钝。我见过有人取 0.01结果温度从 20°C 升到 80°C 花了十几分钟才稳定这在需要快速响应的控制回路里是不可接受的。5. 从实验室到现场实测中暴露的问题和解决过程5.1 问题一读数每隔几秒跳一次现象在实验室里读数很稳拿到现场后每隔 3 到 5 秒温度读数会跳 2 到 3°C然后又恢复。排查过程先用示波器看 ADS122U04 的电源引脚发现 AVDD 上有周期性的尖峰频率大约 0.3Hz。顺着电源线查发现是现场的一台变频器在附近工作它的开关频率和我的采样率产生了拍频。再检查我的电源去耦发现 AVDD 的 100nF 电容离芯片有 1.5cm 远而且走线很细。解决把去耦电容挪到芯片引脚旁边走线加粗到 20mil同时在 AVDD 入口处加了一颗 10μF 的钽电容和一颗 100nF 的陶瓷电容组成 π 型滤波。问题解决。经验去耦电容的位置比容值更重要。100nF 的电容如果离芯片超过 5mm高频去耦效果会大打折扣。另外工业现场的变频器、继电器、接触器都是干扰源电源入口的滤波要做好。5.2 问题二低温环境下精度变差现象在 -20°C 环境下测试时PT1000 的读数比标准值偏高约 1.5°C。排查过程先怀疑是 PT1000 本身的问题换了一颗高精度传感器问题依旧。然后检查参考电压用高精度万用表测量 REFP 引脚发现实际值是 2.043V 而不是标称的 2.048V。查数据手册内部参考的初始精度是 ±0.1%温漂是 5ppm/°C。在 -20°C 时相对于 25°C 有 45°C 的温差漂移量是 45 × 5ppm 225ppm也就是 2.048V × 225×10⁻⁶ ≈ 0.46mV。这个漂移量换算到温度上大约是 0.1°C不足以解释 1.5°C 的偏差。继续查发现激励电流也有温漂。IDAC 的温漂典型值是 50ppm/°C在 -20°C 时电流比 25°C 时小了约 0.2%。PT1000 是 1000Ω电流变化导致电压变化最终反映到温度上就是 0.5°C 左右的误差。再加上参考电压的漂移和 PT1000 本身的容差累积起来就接近 1.5°C 了。解决改用比例式测量法用同一个参考电压同时给 IDAC 和 ADC 参考供电这样参考电压的漂移会被抵消掉。具体做法是把 REFP 连接到 IDAC 的供电轨或者用外部参考同时驱动两者。改完之后-20°C 下的误差降到了 0.3°C 以内。经验比例式测量是消除参考漂移的最有效手段。只要激励源和参考源来自同一个源头它们的漂移就会相互抵消。这个技巧在 RTD 和热敏电阻测量中特别有用。5.3 问题三SPI 通信偶尔失败现象系统运行几个小时后偶尔出现 SPI 读不到数据的情况重启后恢复正常。排查过程用逻辑分析仪抓 SPI 波形发现失败时 SCLK 上有一个额外的毛刺导致 ADC 误判了时钟边沿。检查 PCB 布局发现 SCLK 走线和一条 PWM 输出线平行走了很长一段距离PWM 的快速边沿通过容性耦合到了 SCLK 上。解决把 SCLK 走线改道远离 PWM 线同时在 SCLK 上串了一颗 33Ω 的电阻。另外在软件里加了超时重试机制如果连续 3 次读不到有效数据就复位 ADC 重新初始化。经验SPI 时钟线是最容易受干扰的布局时要远离高频开关信号。串阻可以抑制反射和毛刺但阻值不能太大否则会影响上升沿。33Ω 是个比较安全的取值。6. 软件架构状态机设计和任务调度6.1 为什么需要状态机在 HVAC 和工业控制里温度采集不是孤立的它要和控制逻辑、通信协议、故障检测协同工作。如果用一个简单的 while 循环顺序执行很容易出现某个任务阻塞导致其他任务超时的问题。我习惯用时间片轮询加状态机的架构把温度采集拆成几个状态IDLE等待采样周期到达START_CONV启动 ADC 转换WAIT_DRDY等待转换完成READ_DATA读取转换结果PROCESS温度换算和滤波ERROR故障处理每个状态执行时间很短不会阻塞其他任务。主循环里用定时器产生 10ms 的时间片温度采集任务每 100ms 执行一次通信任务每 50ms 执行一次控制任务每 20ms 执行一次。6.2 状态机代码框架typedef enum { TEMP_STATE_IDLE, TEMP_STATE_START, TEMP_STATE_WAIT, TEMP_STATE_READ, TEMP_STATE_PROCESS, TEMP_STATE_ERROR } TempState_t; TempState_t temp_state TEMP_STATE_IDLE; uint32_t temp_tick 0; void Temp_Task(void) { switch (temp_state) { case TEMP_STATE_IDLE: if (temp_tick SAMPLE_PERIOD) { temp_tick 0; temp_state TEMP_STATE_START; } break; case TEMP_STATE_START: ADS122U04_SendCommand(CMD_START); temp_state TEMP_STATE_WAIT; break; case TEMP_STATE_WAIT: if (DRDY_PIN 0) { temp_state TEMP_STATE_READ; } else if (timeout TIMEOUT_MAX) { temp_state TEMP_STATE_ERROR; } break; case TEMP_STATE_READ: raw_data ADS122U04_ReadData(); temp_state TEMP_STATE_PROCESS; break; case TEMP_STATE_PROCESS: temperature Thermocouple_Convert(raw_data, cjc_temp); temperature LowPass_Filter(temperature); temp_state TEMP_STATE_IDLE; break; case TEMP_STATE_ERROR: ADS122U04_Init(); // 重新初始化 temp_state TEMP_STATE_IDLE; break; } }这个框架的好处是每个状态都有明确的入口和出口不会卡死。即使 ADC 不响应超时机制也会把状态机拉回 IDLE 重新开始。6.3 多通道切换的处理如果需要测量多个温度点可以用 ADS122U04 的内部多路复用器切换通道。切换通道后要等至少 4 个转换周期再读数据因为 PGA 和参考需要时间稳定。我通常的做法是切换通道后丢弃前 3 个转换结果从第 4 个开始读。如果通道数超过 4 个可以用外部多路复用器比如 CD4051 或 ADG708。但要注意外部多路复用器的导通电阻会影响精度特别是用 IDAC 激励时。导通电阻会串联在激励回路里导致实际电流偏小。解决办法是用低导通电阻的模拟开关或者在软件里做校准补偿。7. 校准和标定出厂前必须做的事7.1 两点校准法每块板子在出厂前都要做校准最实用的是两点校准在低温点和高温点各测一次计算增益误差和偏移误差。具体操作是把传感器放到已知温度的恒温槽里等温度稳定后读取 ADC 值记录两个点T1, ADC1和T2, ADC2然后计算增益 (T2 - T1) / (ADC2 - ADC1) 偏移 T1 - 增益 × ADC1校准系数存在 MCU 的 EEPROM 里每次上电时读取。校准点的选择要覆盖你的工作范围比如 HVAC 应用可以选 0°C 和 50°C工业炉温可以选 100°C 和 500°C。7.2 冷端补偿的校准热电偶的冷端补偿也需要校准。方法是用一个已知温度的参考点比如把接线端子放在 25°C 的恒温环境里读取冷端传感器的值和标准温度计对比计算补偿系数。如果冷端传感器是 PT1000它的校准可以和主通道一起做。如果是 NTC因为 NTC 的离散性比较大建议每块板单独校准。7.3 长期漂移的监测工业设备通常要求几年甚至十几年的使用寿命长期漂移是必须考虑的因素。ADS122U04 的参考漂移是 5ppm/°C年漂移典型值在 20ppm 以内换算到温度上大约是 0.01°C/年可以忽略。但传感器的漂移可能更大PT100 的年漂移大约是 0.05°C热电偶在高温下漂移更快。我的做法是在系统里加一个自检通道用一个高精度电阻模拟传感器定期测量它的值。如果读数偏离预期超过阈值就发出维护提醒。这个功能在远程无人值守的场景里特别有用。8. 几个容易被忽略的实操细节8.1 上电顺序和复位时序ADS122U04 和 PIC18F87J11 的上电顺序有讲究。如果 ADC 先上电而 MCU 后上电ADC 可能会在 MCU 还没准备好时就开始转换导致 DRDY 引脚状态不确定。建议在 MCU 的初始化代码里先拉低 ADC 的 CS 引脚等 MCU 外设初始化完成后再释放。复位时序上ADS122U04 的复位命令需要至少 1ms 的延时前面已经提过。另外上电后建议先读一次所有寄存器的值确认芯片通信正常再开始配置。这个自检步骤能提前发现焊接不良或芯片损坏的问题。8.2 温度传感器的接线方式RTD 有两线制、三线制和四线制。两线制最简单但精度最差因为导线电阻会直接叠加到测量结果里。三线制可以补偿导线电阻是工业上最常用的。四线制精度最高但接线复杂一般只在实验室用。热电偶的接线要注意极性不能接反而且补偿导线要和热电偶类型匹配。K 型热电偶要用 K 型补偿导线不能用铜线直接延长否则会在接线处产生额外的热电势。8.3 低功耗设计的考虑如果设备是电池供电的低功耗就很重要。ADS122U04 支持单次转换模式转换完成后自动进入低功耗状态电流从几百微安降到几微安。PIC18F87J11 也有多种休眠模式可以在两次采样之间休眠。我的做法是每 10 秒唤醒一次启动 ADC 转换读取数据处理完后立即休眠。这样平均电流可以做到 50μA 以下用一节 2000mAh 的电池能撑好几年。8.4 EMC 测试中的常见问题工业产品要做 EMC 测试辐射发射和传导发射是最容易超标的两项。SPI 时钟线和 ADC 的模拟输入线是主要的辐射源。对策是SPI 时钟线尽量短必要时加屏蔽或串阻模拟输入线用地线包围或者用屏蔽线电源入口加共模扼流圈和 X/Y 电容PCB 边缘留足够的禁布区减少边缘辐射传导发射方面开关电源的输入滤波要做好共模和差模都要滤。如果用的是线性电源传导发射通常不是问题。9. 常见问题速查现象可能原因排查方向读数全为 0SPI 通信失败检查 CS、SCLK 波形确认寄存器读写正常读数满量程输入饱和检查 PGA 增益设置确认共模电压在范围内读数跳动大参考或电源噪声检查去耦电容位置用示波器看电源纹波温度偏高/偏低冷端补偿错误检查冷端传感器读数和补偿算法低温下精度差参考或激励漂移改用比例式测量或换低温漂参考通信偶尔失败干扰导致时序错误检查 SPI 走线加串阻和超时重试多通道切换后读数不准建立时间不足丢弃前 3 个转换结果等 PGA 稳定这张表是我从多个项目的调试记录里整理出来的大部分问题都能归到电源、参考、时序这三类。遇到问题时按这个顺序排查效率会高很多。10. 写在最后的一点个人体会这套 ADS122U04 加 PIC18F87J11 的方案我从第一版到现在已经迭代了四五次。最大的感受是温度测量的难点从来不在 ADC 本身而在整个信号链的每一个环节。从传感器的选型、接线方式到前端滤波、参考源、电源去耦再到 PCB 布局、软件滤波、校准标定任何一个环节出问题都会反映到最终的读数上。我踩过的最大的坑是早期太相信数据手册的标称精度忽略了实际工作环境的温度变化和电磁干扰。后来养成了一个习惯每块板子打样回来先不急着写代码而是用高精度源表给模拟输入端加标准信号测一遍全温区的精度。这个步骤能提前发现硬件设计的问题比在软件里修修补补有效得多。另外校准数据一定要存在非易失性存储器里而且要有备份。我遇到过 EEPROM 写坏的情况校准系数丢了整批板子都要返工。后来改成双备份加校验和就没再出过问题。如果你也在做类似的温度测量项目希望这些经验能帮你少走点弯路。有什么问题欢迎一起交流。
阅读完成 · 觉得有帮助?