1. 水浊度传感器工作原理与硬件选型

水浊度传感器是水质检测中常用的传感器之一,它通过光学原理测量水中悬浮颗粒的浓度。TSW-30传感器内部采用红外对管设计,当光线穿过水体时,水中的颗粒会散射和吸收光线,导致透光率发生变化。水越浑浊,透过的光量就越少,传感器输出的电流信号也就越小。

在实际项目中,我们通常需要将这种模拟信号转换为数字信号进行处理。STM32系列微控制器内置的12位ADC模块非常适合这种应用场景。ADC(模数转换器)能够将传感器输出的连续电压信号转换为离散的数字值,便于后续的数字信号处理和数据分析。

硬件选型方面,我推荐使用STM32F103系列作为主控芯片,原因有三点:首先,它具备丰富的外设资源,包括多通道ADC和USART接口;其次,开发工具链成熟,资料丰富;最后,性价比高,适合工业级应用。传感器方面,TSW-30具有响应快、稳定性好的特点,输出电压范围0-3.3V,与STM32的ADC输入范围完美匹配。

2. STM32 ADC配置详解

2.1 硬件连接与初始化

首先需要正确连接传感器与STM32开发板。TSW-30的输出引脚应连接到STM32的ADC输入通道,我通常选择PA1(ADC1通道1)作为输入引脚。在硬件设计时要注意,模拟信号路径应尽量短,并远离数字信号线以减少干扰。

ADC初始化代码需要完成以下几个关键步骤:

// 使能GPIOA和ADC1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);

// 配置ADC时钟分频(PCLK2的4分频)
RCC_ADCCLKConfig(RCC_PCLK2_Div4);

// 配置PA1为模拟输入
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStruct);

// ADC参数配置
ADC_InitTypeDef ADC_InitStruct;
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;
ADC_InitStruct.ADC_ScanConvMode = DISABLE;
ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStruct.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStruct);

// 使能ADC并校准
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));

这段代码有几个关键点需要注意:ADC时钟分频决定了转换速度,对于TSW-30这种变化较慢的信号,选择PCLK2的4分频(9MHz)完全够用;右对齐数据格式方便后续处理;单次转换模式可以降低功耗。

2.2 规则通道配置与软件触发

配置好ADC后,需要设置规则通道的采样顺序和采样时间:

ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5);

这里我选择55.5个周期的采样时间,这是考虑到TSW-30的输出阻抗较高,较长的采样时间可以确保采样电容充分充电,提高测量精度。实际项目中,这个参数需要根据传感器特性进行调整。

启动转换使用软件触发方式:

ADC_SoftwareStartConvCmd(ADC1, ENABLE);

3. 数据采集与滤波处理

3.1 原始数据采集

读取ADC值的标准流程是等待转换完成标志位,然后读取数据寄存器:

while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
uint16_t adcValue = ADC_GetConversionValue(ADC1);

但在实际应用中,单次采样往往不够可靠。我推荐采用均值滤波法,连续采样多次取平均值:

#define SAMPLE_TIMES 10

uint16_t Get_Adc_Average(uint8_t channel, uint8_t times) {
    uint32_t temp_val = 0;
    for(uint8_t i=0; i<times; i++) {
        ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_55Cycles5);
        ADC_SoftwareStartConvCmd(ADC1, ENABLE);
        while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
        temp_val += ADC_GetConversionValue(ADC1);
        Delay_ms(5);
    }
    return temp_val/times;
}

这个函数实现了10次采样取平均,每次采样间隔5ms。延时可以确保信号稳定,避免连续采样相关性过强的问题。

3.2 电压转换与浊度计算

获取ADC原始值后,需要转换为实际电压值:

float voltage = (float)adcValue * 3.3f / 4095.0f;

TSW-30的浊度计算通常采用线性公式:

#define V_CLEAR 3.0f  // 清水电压值
#define V_DIRTY 0.5f  // 高浊度电压值

float Calculate_Turbidity(float voltage) {
    float turbidity = 100.0f * (V_CLEAR - voltage) / (V_CLEAR - V_DIRTY);
    if(turbidity < 0) turbidity = 0;
    if(turbidity > 100) turbidity = 100;
    return turbidity;
}

这个公式将浊度映射到0-100%的范围,其中V_CLEAR和V_DIRTY需要根据实际校准结果调整。我在项目中发现,定期校准这两个参数可以显著提高测量精度。

4. 串口输出与数据可视化

4.1 USART初始化配置

为了实时查看浊度数据,我们需要配置USART外设:

void USART1_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
    
    // 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
    
    // 配置TX(PA9)为复用推挽输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 配置RX(PA10)为浮空输入
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // USART参数配置
    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_Init(USART1, &USART_InitStruct);
    
    USART_Cmd(USART1, ENABLE);
}

4.2 数据格式化输出

为了方便上位机解析,我建议采用固定的数据格式:

void USART_SendData(USART_TypeDef* USARTx, float turbidity) {
    char buffer[32];
    int length = sprintf(buffer, "Turbidity: %.2f NTU\r\n", turbidity);
    
    for(int i=0; i<length; i++) {
        while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
        USART_SendData(USARTx, buffer[i]);
    }
}

这个函数将浊度值格式化为"Turbidity: xx.xx NTU"的字符串发送。在串口助手中,可以设置相应的解析规则实现数据可视化。

5. 系统优化与调试技巧

5.1 电源噪声抑制

水浊度测量对电源噪声非常敏感。我在实际项目中总结出几个有效方法:

  1. 为传感器供电使用LDO而非开关电源
  2. 在传感器电源引脚就近放置0.1μF去耦电容
  3. 模拟地和数字地单点连接
  4. ADC参考电压引脚增加10μF钽电容

5.2 软件校准方法

提高测量精度的关键是定期校准。我通常采用两点校准法:

  1. 将传感器放入标准浊度液(如0NTU和100NTU)
  2. 记录对应的ADC值
  3. 使用线性插值公式修正测量结果

校准数据可以存储在STM32的Flash中:

typedef struct {
    uint16_t adc_clear;  // 清水ADC值
    uint16_t adc_dirty;  // 高浊度ADC值
    float slope;         // 斜率
    float intercept;     // 截距
} CalibrationData;

void Update_Calibration(void) {
    CalibrationData calib;
    calib.adc_clear = Get_Adc_Average(ADC_Channel_1, 20);  // 测量清水
    calib.adc_dirty = Get_Adc_Average(ADC_Channel_1, 20);  // 测量标准浊度液
    calib.slope = 100.0f / (calib.adc_clear - calib.adc_dirty);
    calib.intercept = -calib.slope * calib.adc_dirty;
    
    FLASH_Unlock();
    FLASH_ErasePage(0x0800FC00);  // 擦除最后一页
    FLASH_ProgramHalfWord(0x0800FC00, *(uint16_t*)&calib);
    FLASH_Lock();
}

5.3 异常数据处理

在实际环境中,传感器可能会受到气泡、杂质等干扰。我采用以下策略提高鲁棒性:

  1. 设置合理的数据范围(如0-100NTU)
  2. 采用滑动窗口滤波消除突变值
  3. 添加传感器状态检测(如开路/短路判断)
#define HISTORY_SIZE 5

typedef struct {
    float history[HISTORY_SIZE];
    uint8_t index;
} Filter;

float Filter_Update(Filter* f, float new_val) {
    f->history[f->index] = new_val;
    f->index = (f->index + 1) % HISTORY_SIZE;
    
    // 计算中值
    float sorted[HISTORY_SIZE];
    memcpy(sorted, f->history, sizeof(sorted));
    qsort(sorted, HISTORY_SIZE, sizeof(float), compare_float);
    
    return sorted[HISTORY_SIZE/2];
}

这个中值滤波算法能有效消除脉冲干扰,同时保持较好的实时性。

更多推荐