news 2026/7/31 15:12:59

STM32驱动雨滴传感器:从ADC采集到RTOS任务的全流程解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32驱动雨滴传感器:从ADC采集到RTOS任务的全流程解析

1. 从“下雨了吗?”到“雨有多大?”:雨滴传感器的核心价值

最近在整理工作室的旧项目,翻出来一个落灰的雨滴传感器模块和一块STM32F103的开发板。这让我想起几年前第一次接触这个传感器时,脑子里冒出的第一个问题:这不就是个“下雨了吗”的开关吗?后来在实际项目中,特别是涉及到智能农业灌溉、汽车自动雨刮、智能家居窗户控制这些场景时,才发现,一个简单的“下雨了吗”的判断,背后藏着从定性到定量的巨大需求鸿沟。

雨滴传感器,或者说雨水传感器,它的核心任务远不止是告诉你“天在下雨”。一个成熟的系统,需要回答的是:“雨有多大?”、“雨滴的密度如何?”、“这场雨会持续多久?”。这些问题的答案,直接决定了后续的控制逻辑是“立即关窗”还是“延时10秒再关”,是“启动低速雨刮”还是“启动高速雨刮并打开大灯”。STM32作为一款功能强大、资源丰富的微控制器,正是实现这种从简单检测到智能感知跃迁的理想平台。它内置的ADC(模数转换器)和定时器,让我们可以轻松地将传感器输出的模拟信号或频率信号,转化为精确的、可量化的雨量信息。

所以,这次我们不只停留在“点亮一个LED”的入门实验,而是深入探讨如何用STM32驱动雨滴传感器,构建一个稳定、可靠的雨量监测子系统。我会结合常见的模块、实际的项目经验,以及那些容易踩坑的细节,把整个过程掰开揉碎了讲清楚。

2. 雨滴传感器模块的“体检报告”:原理、选型与电路

市面上常见的雨滴传感器模块,虽然外观大同小异,但内核和工作方式却有区别,选错了或者用错了,后续的代码怎么写都别扭。

2.1 两种主流传感器类型与工作原理

最常见的有两种:电阻式电容式。它们都利用了雨水会改变传感器表面电气特性的原理,但具体实现和输出信号截然不同。

电阻式雨滴传感器:这是最普遍、成本最低的一种。它的感应区域通常是一系列交错排列的铜箔,表面涂有防氧化涂层。当没有雨水时,铜箔之间是绝缘的,电阻接近无穷大。当雨滴落下,水桥接了铜箔之间的间隙,电阻值会急剧下降。模块板上通常集成了一个比较器电路(如LM393),将电阻变化转化为一个数字开关量(DO)输出,同时也会引出一个模拟量(AO)引脚,直接反映电阻值(电压值)。

注意:电阻式传感器表面的涂层会随着时间老化、积灰,导致其基准电阻发生变化,影响长期稳定性。它的测量更直接反映的是“水的覆盖面积”,但容易受到水质(纯净水导电性差)的影响。

电容式雨滴传感器:它的感应区域是一个电容的极板。没有水时,介质是空气,电容量固定。当雨水附着,水的介电常数远大于空气,会导致整体电容量增加。模块电路通过测量这个电容量的变化(通常转换为频率或占空比信号)来输出。这种传感器表面通常是一整块覆铜,没有裸露的电极,因此更耐腐蚀,寿命更长,且对水质的导电性不敏感,只对水的存在和厚度敏感。

对于STM32开发者来说,选择哪种传感器,决定了你使用哪个外设:

  • 电阻式(AO输出):连接至STM32的ADC引脚,读取电压值(例如0-3.3V)。电压越高,通常表示越干燥;电压越低,表示雨水越多。
  • 电阻式(DO输出):连接至STM32的GPIO输入引脚(可配置中断),用于简单的阈值报警。
  • 电容式(频率输出):连接至STM32的定时器输入捕获引脚,测量输出方波的频率。频率变化反映雨量大小。

2.2 模块电路与STM32接口设计

以最常见的电阻式模块为例,我们看看如何与STM32正确连接。

模块通常有4个引脚:

  • VCC: 供电,接STM32开发板的3.3V或5V(注意模块逻辑电平,通常3.3V/5V兼容)。
  • GND: 接地。
  • DO: 数字输出。模块上有一个电位器,可以调节比较器的阈值。当传感器模拟值超过阈值时,DO输出低电平(或高电平,取决于模块设计),否则相反。接STM32的任一GPIO,如PA0。
  • AO: 模拟输出。直接输出传感器分压后的电压信号。接STM32的ADC通道引脚,如PA1(对应ADC1的通道1)。

一个关键细节是上拉电阻。很多模块的DO口是开漏输出,需要STM32内部或外部上拉电阻才能读到稳定的高电平。稳妥起见,可以在代码中初始化该GPIO时,设置为上拉输入模式。

// 以STM32标准库为例,初始化DO引脚为带上拉的数字输入 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入模式 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);

对于AO引脚,我们需要配置ADC。这里有一个容易忽略的点:ADC的采样时间。雨滴传感器的阻抗变化可能较快,如果采样时间太短,可能导致采样值不稳定。通常,将采样周期设置得稍长一些(例如239.5个ADC时钟周期),有助于获得更稳定的读数。

// ADC1 通道1 (PA1) 初始化片段 ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道 ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); // 配置通道的采样时间 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);

3. 软件架构:从裸机轮询到RTOS任务

拿到稳定的硬件读数只是第一步,如何设计软件来优雅地处理这些数据,并做出决策,是更见功力的地方。这里我分享三种由浅入深的实现方式。

3.1 方式一:裸机轮询——简单直接

这是最基础的方法,在主循环里不断读取ADC值或检查DO引脚状态。

while (1) { // 1. 读取ADC值 ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 等待转换结束 uint16_t adc_value = ADC_GetConversionValue(ADC1); // 2. 简单的阈值判断 if (adc_value < RAIN_THRESHOLD_LIGHT) { // 小雨逻辑 printf("Light rain detected.\r\n"); } else if (adc_value < RAIN_THRESHOLD_HEAVY) { // 中雨逻辑 printf("Moderate rain detected.\r\n"); } else { // 大雨或传感器故障(短路)逻辑 printf("Heavy rain or sensor error!\r\n"); } // 3. 加入简单滤波(例如取平均) // 4. 延时,控制检测频率 Delay_ms(200); }

这种方式的问题很明显:Delay_ms(200)会阻塞整个程序。如果系统还有其他任务(如按键扫描、显示刷新),体验会非常卡顿。它只适用于功能极其简单的系统。

3.2 方式二:定时器中断驱动——更高效

利用STM32的定时器产生一个固定间隔(如100ms)的中断,在中断服务函数中启动ADC转换,或者设置一个标志位,在主循环中查询该标志位再进行ADC读取和逻辑处理。这样就把“何时采样”和“如何处理数据”解耦了。

// 定时器中断服务函数 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 设置一个采样标志位 adc_sample_flag = 1; } } // 主循环 while (1) { if (adc_sample_flag) { adc_sample_flag = 0; // 执行ADC读取和雨量判断逻辑 ProcessRainSensor(); } // 其他任务可以在这里无阻塞地运行 ProcessKey(); UpdateDisplay(); }

这种方式大大提高了系统的响应性。但逻辑处理ProcessRainSensor()如果比较耗时,仍然可能影响其他任务。这时,一个更清晰的划分是:在中断里只做最少的操作(如放置数据到队列),把复杂的算法和逻辑放到主循环或更低优先级的任务中。

3.3 方式三:基于RTOS的任务设计——工业级可靠

当系统复杂程度上升,比如你需要同时处理雨量数据、通过网络发送状态、驱动电机(雨刮)、管理液晶屏时,一个实时操作系统(RTThread、FreeRTOS等)会让你的代码结构清晰百倍。你可以为雨滴传感器专门创建一个任务。

// 雨滴传感器任务函数 void rain_sensor_task(void *parameter) { // 初始化ADC、定时器等 rain_sensor_init(); while (1) { // 等待信号量或消息队列,由定时器中断释放 if (xSemaphoreTake(adc_sample_semaphore, portMAX_DELAY) == pdTRUE) { uint16_t raw_value = read_adc_average(10); // 读取10次取平均 // 进行数据滤波和状态判断 rain_intensity_t intensity = classify_rain_intensity(raw_value); // 将结果发送到其他任务(如控制任务、通信任务) xQueueSend(rain_data_queue, &intensity, 0); // 本地也可以根据状态执行紧急操作 if (intensity == RAIN_HEAVY) { emergency_close_window(); // 立即关窗 } } // 任务可以主动延时,控制自身节奏 vTaskDelay(pdMS_TO_TICKS(50)); } }

在这种架构下,雨量检测成为一个独立的、可管理的模块。它通过消息队列与“决策控制任务”通信,决策任务再综合其他信息(如风速、温度)来做出最终动作,实现了完美的解耦和模块化。

4. 核心算法:滤波、标定与状态机

有了稳定的数据流,接下来就是如何从原始数据中提取出可靠的“雨量信息”。这离不开信号处理和状态管理。

4.1 软件滤波:对抗噪声的必备手段

传感器的原始ADC读数一定是跳动的,尤其是在临界状态。直接使用单次采样值做判断,会导致输出频繁抖动。必须滤波。

移动平均滤波:最简单有效。维护一个数组,存储最近N次采样值,输出其平均值。

#define FILTER_SIZE 8 uint16_t adc_buffer[FILTER_SIZE] = {0}; uint8_t buffer_index = 0; uint16_t moving_average_filter(uint16_t new_sample) { adc_buffer[buffer_index] = new_sample; buffer_index = (buffer_index + 1) % FILTER_SIZE; uint32_t sum = 0; for (int i = 0; i < FILTER_SIZE; i++) { sum += adc_buffer[i]; } return (uint16_t)(sum / FILTER_SIZE); }

一阶滞后滤波(低通滤波):计算量小,适合资源紧张的场景。Y(n) = α * X(n) + (1-α) * Y(n-1),其中α是滤波系数(0<α<1),α越小,滤波越强,响应越慢。

float alpha = 0.2; // 系数,可调 float filtered_value = 0; uint16_t low_pass_filter(uint16_t new_sample) { filtered_value = alpha * new_sample + (1 - alpha) * filtered_value; return (uint16_t)filtered_value; }

在实际项目中,我常采用“移动平均+一阶滞后”的组合拳:先用移动平均平滑突发毛刺,再用一阶滞后让曲线更柔和。

4.2 传感器标定:从ADC值到雨量等级

“ADC值=1234,这算大雨还是小雨?”这需要标定。标定不是高深理论,就是做实验。

  1. 准备:将传感器水平固定。
  2. 干态基准:记录完全干燥时的ADC值ADC_dry(通常接近3.3V对应值,如4095)。
  3. 湿态模拟
    • 用滴管或喷雾瓶,在传感器表面均匀地滴上少量水,模拟小雨。记录此时的ADC值ADC_light
    • 增加水量,让表面形成连续水膜,模拟中雨。记录ADC_medium
    • 倾倒更多水,模拟大雨/积水。记录ADC_heavy
  4. 划分区间:根据记录的值,划分阈值区间。注意,由于是电阻式,ADC值越小,表示雨水越多
    #define THRESHOLD_HEAVY 500 // ADC < 500, 大雨 #define THRESHOLD_MEDIUM 1500 // 500 <= ADC < 1500, 中雨 #define THRESHOLD_LIGHT 2500 // 1500 <= ADC < 2500, 小雨 // ADC >= 2500, 无雨或微量水汽
    这个阈值需要根据你的具体模块、供电电压和安装角度反复测试调整。不要迷信别人的参数

4.3 状态机设计:让逻辑更清晰,防抖更自然

判断是否下雨,不是简单的“当前值超过阈值”。你需要一个状态机来区分“开始下雨”、“持续下雨”、“雨停”等状态,并加入迟滞和去抖。

一个简单的四状态机可以这样设计:

  • 状态0:干燥:ADC值持续高于“小雨退出阈值”(一个比进入阈值更高的值,用于迟滞)。
  • 状态1:疑似下雨:ADC值首次低于“小雨进入阈值”。进入此状态后启动一个计时器(如2秒)。
  • 状态2:确认下雨:在“疑似下雨”状态下,计时器超时前,ADC值持续低于“进入阈值”。则确认下雨,进入状态2,并记录雨量等级。
  • 状态3:雨停判断:在状态2下,ADC值回升到“退出阈值”以上,进入此状态并启动另一个计时器(如5秒,防止雨滴间隙误判)。
  • 返回状态0:在“雨停判断”状态下,计时器超时前ADC值未再低于“进入阈值”,则判定雨停,回到状态0。

这个状态机有效地解决了单点判断的抖动问题,并且“进入阈值”和“退出阈值”的不同,形成了迟滞比较,防止在临界点反复横跳。

5. 高级应用与系统集成

当基础功能稳定后,我们可以考虑更复杂的应用,这往往需要和其他模块或协议联动。

5.1 与执行机构联动:智能雨刮模拟

假设我们要用STM32控制一个舵机模拟雨刮。逻辑如下:

  • 小雨/中雨:舵机低速周期性摆动。
  • 大雨:舵机高速摆动。
  • 雨停:舵机归位并停止。

这里的关键是平滑控制。不要直接在状态判断后突然改变舵机速度,可以设计一个速度渐变的过程。同时,舵机控制应放在一个独立的定时器PWM输出任务中,雨量判断任务通过共享变量或消息队列来更新目标速度值。

// 在雨量判断任务中 rain_intensity_t current_intensity = get_current_intensity(); int target_servo_speed = 0; switch (current_intensity) { case RAIN_LIGHT: case RAIN_MEDIUM: target_servo_speed = SERVO_SPEED_LOW; break; case RAIN_HEAVY: target_servo_speed = SERVO_SPEED_HIGH; break; default: target_servo_speed = 0; // 停止 break; } // 通过队列或共享变量(注意互斥)将 target_servo_speed 传递给舵机控制任务

5.2 数据通信与远程监控

通过STM32的串口(UART)可以将雨量数据发送给上位机(如PC、树莓派)或者无线模块(如ESP8266 Wi-Fi模块)。

协议设计:建议定义简单的ASCII协议,方便调试。例如:[RAIN],INTENSITY:2,ADC:1234\r\n上位机解析后,可以在图形界面显示雨量等级曲线,或者通过MQTT协议上报到云平台。

使用ESP8266:这是非常常见的组合。STM32作为主控,负责采集传感器数据和处理逻辑;ESP8266作为通信从机,通过AT指令或透传模式,将STM32发送过来的数据包转发到指定的服务器。接线通常是STM32的串口2(USART2)的TX、RX连接ESP8266的RX、TX。务必注意电平匹配,两者都是3.3V电平则可以直接连接。

5.3 低功耗设计考量

对于电池供电的户外雨量监测站,功耗至关重要。

  1. 传感器供电控制:雨滴传感器模块本身有一定功耗。可以通过STM32的一个GPIO控制一个MOS管,来给传感器模块间歇供电。例如,每10分钟唤醒一次,供电,采样30秒,然后断电进入休眠。
  2. STM32休眠模式:在采样间隔,让STM32进入停止模式(Stop Mode)待机模式(Standby Mode)。使用RTC(实时时钟)或外部中断(比如连接传感器的DO口,当检测到雨水时唤醒)来唤醒MCU。
  3. 外设时钟管理:不用的外设(ADC、定时器、串口)时钟一定要关闭。

6. 实战调试与避坑指南

理论说再多,不如实际调一次。下面是我在多个项目中总结的“血泪教训”。

6.1 硬件层面的常见坑

坑1:电源噪声导致ADC值跳动

  • 现象:即使传感器干燥,ADC值也在几十甚至上百个LSB范围内无规律跳动。
  • 排查:首先用万用表测量给传感器供电的3.3V/5V是否稳定。如果开发板使用USB供电,且连接了其他大电流设备(如电机),电源纹波会很大。
  • 解决
    1. 为传感器模块单独增加一个LC滤波电路(一个电感加一个电容)。
    2. 在STM32的VDDA(模拟供电)引脚靠近MCU处,并联一个10uF钽电容和一个0.1uF陶瓷电容到地,这是STM32数据手册明确要求的。
    3. 如果条件允许,使用线性稳压器(LDO)为模拟部分单独供电。

坑2:传感器安装位置与角度

  • 问题:水平安装时,积水无法流走,可能导致ADC值达到饱和(接近0),且雨后干燥极慢。
  • 解决:传感器应倾斜一定角度安装(例如15-30度),让雨水能自然流走。这能显著改善测量动态范围和响应速度。

坑3:DO数字输出不触发或一直触发

  • 现象:调节模块上的蓝色电位器,DO指示灯状态不变,或者变化点非常模糊。
  • 排查
    1. 检查DO引脚是否已正确配置为上拉/下拉输入。
    2. 用万用表测量AO引脚电压,同时调节电位器,观察DO指示灯。如果电压变化正常但DO不变,可能是比较器芯片(LM393)损坏或电位器损坏。
    3. 关键:很多模块的DO输出是开集电极(OC)结构,需要外部上拉电阻。如果STM32内部上拉不够强(通常为40kΩ左右),可以在DO脚和VCC之间加一个4.7kΩ-10kΩ的外部上拉电阻。

6.2 软件调试技巧

技巧1:利用串口打印原始数据绘图不要只打印处理后的状态(如“小雨”)。将原始的、滤波后的ADC值通过串口以固定格式(如只发送数字和换行)发送到电脑,用串口绘图工具(如Serial Plotter in Arduino IDE, 或者Python的Matplotlib)实时绘制曲线。这能让你直观地看到噪声水平、滤波效果和阈值设置是否合理。

技巧2:模拟输入进行单元测试在代码中,可以创建一个“模拟传感器输入”的调试模式。例如,通过一个按键或串口命令,来手动设置一个虚拟的ADC值,从而测试你的状态机和控制逻辑是否按预期运行,而不需要真的去洒水。

#ifdef DEBUG_MODE // 如果定义了调试模式,从串口读取模拟值 if (uart_received_simulated_value) { raw_adc = get_simulated_value_from_uart(); } else { raw_adc = read_real_adc(); } #else raw_adc = read_real_adc(); #endif

技巧3:合理利用STM32的DMA+定时器触发ADC对于追求高采样率或低CPU占用的应用,可以配置一个定时器(如TIM2)以固定频率触发ADC转换,并启用DMA将转换结果自动搬运到内存中的数组。这样,CPU只需要在DMA搬运完成一半或全部时(通过DMA中断)去处理一批数据即可,效率极高,特别适合与RTOS配合。

6.3 环境适应性与长期稳定性

问题:传感器表面污染长期户外使用,灰尘、油污会附着在传感器表面,改变其干态基准电阻和湿态响应曲线。

  • 软件应对:实现“动态基准校准”。在系统初始化时,记录一个干态基准值。可以定期(如每天凌晨)或在检测到长时间无雨且读数稳定时,轻微更新这个基准值。但要注意,如果更新时表面恰好有露水,就会校准错误。所以需要加入一些合理性判断(如温度、湿度辅助判断)。
  • 硬件维护:定期清洁传感器表面。对于要求高的场合,可以考虑带有自清洁功能(如微型雨刮或震动器)的传感器。

问题:温度与湿度影响环境温湿度会影响水的电阻率和蒸发速度,从而间接影响读数。

  • 应对:如果系统中有温湿度传感器(如DHT11、SHT30),可以将温湿度作为补偿因子引入雨量判断算法。例如,在高湿度环境下,即使有小水滴凝结,也可能不判定为下雨。

最后,我想说的是,玩转一个雨滴传感器,远不止是调用一个HAL_ADC_GetValue()函数那么简单。它涉及模拟电路的理解、数字滤波的应用、状态机的设计、低功耗的考量以及多任务系统的整合。把这个项目吃透,你对STM32的GPIO、ADC、定时器、中断乃至RTOS的理解会上一个大台阶。我自己的经验是,把所有参数(滤波系数、阈值、延时时间)都做成可配置的,通过串口命令能实时修改和保存,这样在调试和适配不同应用场景时会无比方便。下次当你看到车窗上的雨刮自动摆动时,或许就能会心一笑,想起自己曾经用STM32和一个小模块,也构建过这样一个感知世界微小变化的系统。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/31 15:12:23

Unity编辑器UI开发:从IMGUI迁移到UI Toolkit的完整指南

1. 项目概述&#xff1a;为什么是时候告别IMGUI了&#xff1f;如果你是一个Unity开发者&#xff0c;尤其是经常需要为团队或自己制作编辑器工具的开发者&#xff0c;那么对IMGUI&#xff08;Immediate Mode GUI&#xff09;这套系统一定又爱又恨。爱的是它的直接和灵活&#xf…

作者头像 李华
网站建设 2026/7/31 15:11:51

斐讯N1盒子刷Armbian部署Docker版CUPS,打造家庭网络打印服务器

1. 项目概述与核心价值 手头有个吃灰的斐讯N1盒子&#xff0c;总想着让它发挥点余热。最近家里的老款惠普打印机&#xff08;比如经典的M126a&#xff09;在连接新电脑时总是遇到驱动问题&#xff0c;每次打印都得折腾半天。一个偶然的想法冒出来&#xff1a;能不能用N1刷个轻…

作者头像 李华
网站建设 2026/7/31 15:10:45

2026年小程序卖货平台搭建哪家好?交易能力、费用与运营条件对比

商家搜索“小程序卖货平台搭建哪家好”&#xff0c;通常不只是想做一个商品页面&#xff0c;而是希望完成商品上架、在线支付、会员沉淀和营销转化。平台能否长期使用&#xff0c;取决于交易闭环和运营门槛&#xff0c;而不是模板看起来是否丰富。商品数量较少、交易规则标准的…

作者头像 李华
网站建设 2026/7/31 15:08:45

3大革新突破:SysML v2如何重塑复杂系统工程建模

3大革新突破&#xff1a;SysML v2如何重塑复杂系统工程建模 【免费下载链接】SysML-v2-Release The latest incremental release of SysML v2. Start here. 项目地址: https://gitcode.com/gh_mirrors/sy/SysML-v2-Release SysML v2作为新一代系统建模语言标准&#xff…

作者头像 李华
网站建设 2026/7/31 15:07:55

Zotero-OCR:让扫描PDF文献变得可搜索的终极解决方案

Zotero-OCR&#xff1a;让扫描PDF文献变得可搜索的终极解决方案 【免费下载链接】zotero-ocr Zotero Plugin for OCR 项目地址: https://gitcode.com/gh_mirrors/zo/zotero-ocr 如果你是一位学术研究者、学生或需要处理大量PDF文档的专业人士&#xff0c;那么你一定会遇…

作者头像 李华
网站建设 2026/7/31 15:05:02

C++实现跨平台云备份工具:架构设计与工程实践

1. 项目概述&#xff1a;一个跨平台的C云备份工具最近在整理几个跨平台的项目&#xff0c;数据分散在Ubuntu服务器和Windows开发机上&#xff0c;手动备份既繁琐又容易遗漏。市面上成熟的云备份方案不少&#xff0c;但要么是闭源的黑盒&#xff0c;要么年费不菲&#xff0c;要么…

作者头像 李华