STM32水浊度传感器实战:从ADC采集到串口输出的完整代码解析
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 电源噪声抑制
水浊度测量对电源噪声非常敏感。我在实际项目中总结出几个有效方法:
- 为传感器供电使用LDO而非开关电源
- 在传感器电源引脚就近放置0.1μF去耦电容
- 模拟地和数字地单点连接
- ADC参考电压引脚增加10μF钽电容
5.2 软件校准方法
提高测量精度的关键是定期校准。我通常采用两点校准法:
- 将传感器放入标准浊度液(如0NTU和100NTU)
- 记录对应的ADC值
- 使用线性插值公式修正测量结果
校准数据可以存储在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 异常数据处理
在实际环境中,传感器可能会受到气泡、杂质等干扰。我采用以下策略提高鲁棒性:
- 设置合理的数据范围(如0-100NTU)
- 采用滑动窗口滤波消除突变值
- 添加传感器状态检测(如开路/短路判断)
#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];
}
这个中值滤波算法能有效消除脉冲干扰,同时保持较好的实时性。
更多推荐

所有评论(0)