传感器模块都在交出什么:从模拟电压到数字协议

先把三种线接到同一张地图上

桌面上摆着几块常见模块:光敏、热敏和电位器。它们都能接到 STM32,但交出来的东西并不一样。先把这三种输入放到同一张地图上:

器件 模块端 参考接线 这根线交给谁
光敏电阻模块 AO PA0 / ADC1_CH0 ADC,读连续电压
光敏电阻模块 DO PB5 GPIO,读比较器后的 0/1
热敏电阻模块 AO PA1 / ADC1_CH1 ADC,读分压电压
3362P 103 中间滑动端 PA2 / ADC1_CH2 ADC,读手动电压
三个器件 GND GND 必须共地
三个器件 VCC 按产品资料供电 先确认工作电压和输出范围

光敏模块通常有 VCC、GND、AO、DO 四个引脚。AO 是传感电路产生的模拟电压,DO 是板上的比较器拿 AO 和阈值比较后的结果。模块上的小电位器,就是在改这条阈值线。

3362P 103 是 10K 电位器。把两端接到 3.3V 和 GND,中间滑动端接 PA2,它就是一只手动可调的分压器。不要把滑动端直接接到 5V 再送进 STM32 ADC。STM32F103 的 ADC 输入通常不能高于 3.3V,接线之前先用万用表量一下电压。

如果某个模块用 5V 供电,AO 可能随着光线或温度接近 5V。此时不能因为"它只是一个模拟输出"就直接插进 PA0/PA1。

ADC 没有"光照通道"

STM32F103 的 ADC 是 12 位。假设参考电压是 3.3V,它把输入电压切成 4096 个刻度:

text 复制代码
0V       -> 0
1.65V    -> 约 2048
3.3V     -> 4095

它真正做的是:采样 PA0 上的电压,转换成一个 0 到 4095 的整数。

如果光敏模块在黑暗时输出 2.4V,ADC 可能返回接近 2978;如果热敏模块在当前温度下输出 1.1V,ADC 可能返回接近 1365。数字本身没有"光"和"温度"的单位。单位来自传感器电路的关系,还需要你知道分压电阻、热敏电阻曲线、模块方向和标定结果。

c 复制代码
uint16_t light_raw = AnalogInput_ReadLight();
uint16_t thermistor_raw = AnalogInput_ReadThermistor();
uint16_t potentiometer_raw = AnalogInput_ReadPotentiometer();

这三个变量的名字已经在提醒我们:它们是 raw,是原始采样值。它们还不是 temperature 或 lux。

光敏模块:AO 和 DO 同时接出来,差别才会露出来

先只做一个动作:用手遮住光敏电阻,再拿手机灯照它。OLED 第一行显示 AO,第四行显示 DO。同时拧光敏模块上的电位器,让 DO 的状态翻转。

程序里,AO 走 ADC,DO 走普通 GPIO:

c 复制代码
static uint16_t AnalogInput_ReadChannel(uint8_t channel)
{
    ADC_RegularChannelConfig(ADC1, channel, 1,
                             ADC_SampleTime_55Cycles5);
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);

    while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET)
    {
    }

    return ADC_GetConversionValue(ADC1);
}

uint16_t AnalogInput_ReadLight(void)
{
    return AnalogInput_ReadChannel(ADC_Channel_0);
}

uint8_t AnalogInput_IsLightThresholdActive(void)
{
    uint8_t level;

    level = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == Bit_SET;

    /* 常见模块是低电平触发,换模块时只改这一层。 */
    return (uint8_t)!level;
}

这里的 !level 不是玄学。它只是把"引脚读到低电平"翻译成"阈值被触发"。如果你手上的模块恰好高电平触发,改这一层就够了,不要让上层业务到处出现 if (GPIO_Read... == Bit_RESET)。

DO 的状态由模块电位器决定。拧动电位器,改变的是比较器的阈值,不是光敏电阻本身突然变灵敏。AO 曲线仍然是传感器原始电压;DO 只是告诉你"这次有没有越过阈值"。

热敏模块:先看到电压,再谈温度

常见热敏模块把热敏电阻和固定电阻组成分压。温度改变,热敏电阻阻值改变,分压点电压跟着改变,ADC 读到的就是这个分压点。

text 复制代码
3.3V
 |
[热敏电阻]
 |
 +------> AO ------> PA1 / ADC1_CH1
 |
[固定电阻]
 |
GND

手指按住热敏电阻,数字可能变化得很慢。手指的热量先传到封装,再改变阻值,分压电压才移动;松开以后,它又慢慢回到环境温度。用 OLED 连续看几次读数,就能看到这个变化过程。至于精确的摄氏度,还需要完整的参数和标定数据。

如果要把 ADC 数值换成温度,至少还要知道:热敏电阻的标称阻值、B 值或完整阻值曲线、分压电阻值、供电电压,以及模块的实际误差。少一个条件,公式都可能只是漂亮的错数。

3362P:最诚实的模拟输入

电位器没有"传感器算法"。你拧多少,它就把滑动端电压推到哪里。它最适合用来排查 ADC:

  • 数字从接近 0 连续走到接近 4095,说明 ADC 基本链路在工作;
  • 数字卡在某个范围,先量滑动端电压,再查供电、接地和引脚;
  • 数字跳动明显,先检查面包板接触、供电和采样线;
  • 模块上的调节电位器改变 DO 翻转点,本质上也是在改变一个比较电压。
c 复制代码
uint16_t potentiometer_raw = AnalogInput_ReadPotentiometer();

OLED_ShowNum(0, 16, potentiometer_raw, 4, OLED_6X8);

把电位器从一端慢慢拧到另一端,OLED 上的数字应该跟着连续变化。它给 ADC 提供了一份能由手动控制、能重复回到原点的输入。

一次采样,代码里到底准备了什么

初始化函数把三路 ADC 配成模拟输入,把光敏 DO 配成上拉输入,再完成 ADC 时钟和校准:

c 复制代码
void AnalogInput_Init(void)
{
    GPIO_InitTypeDef gpio;
    ADC_InitTypeDef adc;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
                           RCC_APB2Periph_GPIOB |
                           RCC_APB2Periph_ADC1, ENABLE);
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);

    gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
    gpio.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &gpio);

    gpio.GPIO_Pin = GPIO_Pin_5;
    gpio.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOB, &gpio);

    adc.ADC_Mode = ADC_Mode_Independent;
    adc.ADC_ScanConvMode = DISABLE;
    adc.ADC_ContinuousConvMode = DISABLE;
    adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    adc.ADC_DataAlign = ADC_DataAlign_Right;
    adc.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &adc);

    ADC_Cmd(ADC1, ENABLE);
    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1))
    {
    }
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1))
    {
    }
}

GPIO_Mode_AIN 是在告诉 GPIO:这三根脚不再负责输出高低电平,而是把外部电压交给 ADC。ADC_RegularChannelConfig() 每次选择当前要读的通道,软件触发一次转换,EOC 置位后才取结果。

三路输入每 10ms 读一次,足够观察光敏、热敏和手动电位器。每次只选择一个通道、启动一次转换、等待完成,再读取结果,过程清清楚楚。

主循环按固定间隔采样,并把结果显示在屏幕上:

c 复制代码
if (Timer_Expired(&sample_time, 10))
{
    light_raw = AnalogInput_ReadLight();
    thermistor_raw = AnalogInput_ReadThermistor();
    potentiometer_raw = AnalogInput_ReadPotentiometer();
    light_threshold_active = AnalogInput_IsLightThresholdActive();
}

if (Timer_Expired(&oled_time, 100))
{
    OLED_ShowNum(0, 0, light_raw, 4, OLED_6X8);
    OLED_ShowNum(0, 8, thermistor_raw, 4, OLED_6X8);
    OLED_ShowNum(0, 16, potentiometer_raw, 4, OLED_6X8);
    OLED_ShowNum(0, 24, light_threshold_active, 1, OLED_6X8);
}

DO 是离散状态,直接显示 0/1 就足够;AO 则显示 ADC 的原始数值。这样看屏幕时,可以同时对照连续变化的电压和越过阈值后的状态。

看到异常,先检查输入

光敏 AO 一直不变

先用万用表测 AO 对 GND 的电压,再拧模块电位器。电位器通常只影响 DO 阈值,不能保证 AO 一定大幅变化;如果 AO 本身接近固定电压,检查光敏元件、供电和模块方向。

DO 一直是 0 或一直是 1

看模块指示灯,再慢慢拧阈值电位器。确认 PB5 的有效电平定义,必要时只改 LIGHT_DO_ACTIVE_LOW。不要把 DO 的状态异常直接归咎于 ADC,因为 DO 根本没有经过 ADC。

热敏数值变化很小

这可能是热量传递慢,也可能是模块分压范围窄。把手指按住几十秒,再松开观察曲线;如果只看一瞬间的 OLED 数字,很容易把"响应慢"误判成"代码没工作"。

电位器不能走满 0 到 4095

先量两端是否真的接在 3.3V 和 GND,滑动端是否接对。电位器阻值、ADC 输入范围、供电和接触电阻都会影响端点;不要为了追求满量程把 ADC 输入接到超过 3.3V 的电压。

这几种输入为什么不能共用一个"传感器函数"

它们最后都可以得到数字,但数字之前发生的事情不同:

text 复制代码
光敏 AO       -> 电压 -> ADC 原始值
光敏 DO       -> 比较器阈值 -> GPIO 状态
热敏电阻 AO   -> 分压电压 -> ADC 原始值 -> 需要曲线才能换温度
3362P         -> 手动分压电压 -> ADC 原始值

如果接口只写成 Sensor_Read(),调用者很快就会忘记返回的是电平、ADC 原始值,还是已经换算过的物理量。把名字写具体一些:ReadLight()、ReadThermistor()、ReadPotentiometer(),并注明它们当前返回的仍然是原始 ADC 数字。

先把这几种输入的来路分清楚:传感器给出电压,ADC 把电压变成数字,模块上的比较器则把连续变化压成一个阈值状态。未经标定的热敏模块,不能只凭一个 ADC 数字得出可靠的摄氏度。

ADC 不是"光照值"和"温度值"的翻译器。它只是电压的记账员;至于这笔电压账最后代表什么,要由传感器电路、标定数据和你的工程目标共同决定。

先让 DATA 线在空闲时站稳

裸 DHT11 有四个针脚,实际接三根。NC 保持悬空。不同批次和封装的脚位顺序可能不同,接线前先对照自己手上的资料和元件正面标记。

DHT11 引脚 参考接法 观察什么
VCC 3.3V;也可按资料使用 5V 先确认 DATA 高电平会不会超过 MCU 可承受范围
DATA PB2 主机发起读取,DHT11 回传数据
NC 不接 没有功能
GND STM32 GND DHT11 和开发板共地

DATA 和 VCC 之间需要一颗上拉电阻 R1,常用 4.7K 到 10K。很多 DHT11 模块已经把它焊在板上,裸传感器则需要外接。接线前先看清模块原理图或板上丝印:已经有 R1 的模块,不再重复加一颗。

text 复制代码
VCC
 |
[R1]
 |
 +------ DATA ------ PB2
 |
DHT11
 |
GND

DHT11 需要发低电平时,把 DATA 拉到地;它松手时,R1 把线带回高电平。STM32 也遵守这个习惯:发起读取时把 PB2 拉低,随后把 PB2 改成输入,把这根线还给 DHT11。

没有 R1,DATA 在双方都松手时就没有明确的高低电平,程序读到的结果也不可靠。R1 的工作不是制造一串数据,而是给这根共享的数据线准备稳定的空闲状态。

DATA 上跑的是 40 个 bit

DHT11 每次回传 5 个字节:

text 复制代码
湿度整数 | 湿度小数 | 温度整数 | 温度小数 | 校验和

DHT11 常见整数型号的小数字节多为 0。第五个字节由前四个相加得到:

c 复制代码
checksum = humidity_integer + humidity_decimal +
           temperature_integer + temperature_decimal;

这里没有一个"2.1V 等于 26 度"的换算公式。DATA 线上先后经过的是一段段低电平和高电平。每一位先低约 50us;后面的高电平很短时记为 0,持续约 70us 时记为 1。

text 复制代码
低约 50us | 高约 26~28us  -> 0
低约 50us | 高约 70us     -> 1

主机还要先敲门:

text 复制代码
STM32 拉低 DATA 至少 18ms
STM32 释放 DATA
DHT11 拉低约 80us,再拉高约 80us
DHT11 连续发送 40 位

观察 PB2 的程序状态和 DHT11 返回结果时,最该先找的不是一整帧数据。先分清主机拉低的起始动作,再分清代表 0 和 1 的两种高电平时长。

PB2 在一次读取中换了两次身份

一次读取里,PB2 要先做输出,再做输入:

c 复制代码
static void DHT11_SetOutput(void)
{
    GPIO_InitTypeDef gpio;

    gpio.GPIO_Pin = GPIO_Pin_2;
    gpio.GPIO_Mode = GPIO_Mode_Out_OD;
    gpio.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOB, &gpio);
}

static void DHT11_SetInput(void)
{
    GPIO_InitTypeDef gpio;

    gpio.GPIO_Pin = GPIO_Pin_2;
    gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB, &gpio);
}

开始读取时,STM32 把 PB2 配成开漏输出,拉低 DATA。协议要求这段低电平至少 18ms,而这里用的是空循环延时,不是精确的毫秒计时------它一个"单位"大约 2µs 上下,随主频和编译器变,所以 19000 落地时比 19ms 要长,接板子前先按自己的时钟校准一次:

c 复制代码
DHT11_SetOutput();
GPIO_ResetBits(DHT11_PORT, DHT11_PIN);
DHT11_DelayUs(19000);
GPIO_SetBits(DHT11_PORT, DHT11_PIN);
DHT11_DelayUs(30);
DHT11_SetInput();

最后一行一执行,STM32 就不再控制 DATA。随后等 DHT11 先拉低、再拉高。等不到预期电平,DHT11_WaitLevel() 返回,DHT11_Read() 给出 DHT11_TIMEOUT。没有超时保护的版本会卡在 while 里,线路没有响应时程序就再也回不到主循环。

每一位高电平开始后,程序等一小段再看一次 PB2。这里的 40 同样受空循环延时的影响,不是精确的 40µs(按上面那个单位估算落在 80~90µs 这一档)------它的作用是在高电平结束前采一次样,所以这个数要按自己的板子校准,别照抄:

c 复制代码
DHT11_DelayUs(40);
data <<= 1;

if (DHT11_ReadLevel())
{
    data |= 1;
}

短高电平已经结束,读到 0;长高电平还在,读到 1。这就是为什么 DATA 接在普通 GPIO 上,驱动里却反复谈时间。ADC 擅长把稳定电压转换成数字;DHT11 需要程序分辨脉冲什么时候落下。

两秒读一次就够了

DHT11 自己完成一次测量需要时间,常见资料给出的两次读取间隔至少约 1 秒。每两秒请求一次,能留出明显余量。屏幕可以继续显示上一次成功读到的温度、湿度和状态码;到了下一次读取时间,再取一组新数据。

一次读取本身会占用几十毫秒,因为程序必须等完那 40 位高低电平。间隔的两秒并不是程序停住两秒,而是等到下一次该读取时再发起请求。

温湿度数字先要过三道关

屏幕上显示温度和湿度之前,驱动已经做了三次判断:DHT11 有没有回应,40 个 bit 有没有按时读完,最后的校验和能不能对上。结果可以归成三种状态:

text 复制代码
0:收到完整数据,校验通过
1:等响应或等位超时
2:收满五个字节,校验失败

0 表示五个字节都收到了,而且第五个字节与前四个字节相加的结果一致,这时温湿度才可以更新到屏幕上。

1 表示程序在等某个该出现的电平,却一直没有等到。DATA 没接好、上拉电阻缺失、供电异常、读取间隔太短,都可能走到这个结果。它的意义是"这一帧没有读完",不是"当前温湿度为 1"。

2 表示 40 个 bit 已经全部收到,但最后一项核对没有通过。这些 bit 有可能在传输中受干扰,也有可能其中某一位的时长被误判。驱动不需要猜是哪一位出了问题,只需要拒绝用这帧数据覆盖上一次正确的温湿度。

这三种状态比"屏幕上有个 26 度"更早告诉你程序走到了哪里。上拉电阻保证空闲电平,超时判断防止程序永远等待,校验和负责拦住看似完整却不可信的数据。

DHT11 给 STM32 的是一帧有时序的数据。PA0 上的光敏 AO 可以交给 ADC;PB2 上的 DHT11 DATA 需要按脉冲宽度读取。线只有一根,里面跑的东西完全不同。

先把"看哪里"摆在桌面上

模块 观察对象 触发动作 典型用途
单个 TCRT5000 / 单路灰度 一处近距离的反射 黑白或距离变化让通道翻转 到位检测、近物检测
八路循迹 / 八路灰度 车底八处位置的反射差异 黑线让若干通道翻转 判断线在左还是右
对射红外 发射端和接收端之间的光路 中间出现遮挡物时翻转 计数、门边检测、掉落检测
光敏模块 DO 当前位置的环境光 光线越过阈值时翻转 光暗开关

单个 TCRT5000 和八路循迹模块本质上是同一种反射式传感器,八路循迹模块也常被叫作灰度传感器。前者把一组发射管和接收管对着一小块区域,后者把多组相同的器件排成一行。把八路模块拆开看,其中任意一路和单个 TCRT5000 都是在交出一个反射状态;确认供电和输出电平后,两者可以按同一种 GPIO 输入方式接入。真正不同的是数量和安装位置:一个点只能回答"眼前这一小块区域是什么样",八个点还能比较出黑线偏左还是偏右。

对射不看目标反射回来的光。发射端一直对着接收端,接收端只等那一束光有没有到达。手指伸进中间,状态就变;手指拿走,状态又恢复。

把状态接到看得见的地方

下面这组引脚用于把这些状态接到同一块开发板上:

信号 参考引脚 有效状态
对射 DO PB0 低电平表示光路被挡
TCRT5000 DO PB2 低电平表示近处反射触发
光敏 DO PB5 低电平表示越过光照阈值
有源蜂鸣器 IN PB12 低电平响
外接 LED PC13 TCRT 有效时点亮

模块的 VCC、GND 按自己买到的产品资料接,三个 DO 和 STM32 必须共地。对射模块的发射端和接收端也要分别供电,先让两端指示灯稳定,再把 DO 接 PB0。

底层接口只保留"这个状态是否有效":

c 复制代码
static uint8_t DigitalSensor_ReadPin(uint16_t pin,
                                     uint8_t active_low)
{
    uint8_t level;

    level = GPIO_ReadInputDataBit(GPIOB, pin) == Bit_SET;

    if (active_low)
    {
        return (uint8_t)!level;
    }

    return level;
}

换一块输出逻辑相反的模块,只改 BEAM_ACTIVE_LOW 或 REFLECT_ACTIVE_LOW,屏幕、蜂鸣器和 LED 的逻辑不用跟着散开。

第一次运行:保持初始状态

上电后让对射两端正对,TCRT5000 前方放一块距离合适的白纸,光敏模块放在桌面上。OLED 三行显示:

text 复制代码
beam    reflect    light

三行的具体 0/1 取决于模块的有效电平定义。先看模块指示灯和代码里的有效电平宏,把实际状态记录下来。

程序每 10ms 读取一次 GPIO:

c 复制代码
if (Timer_Expired(&sample_time, 10))
{
    beam_blocked = DigitalSensor_ReadBeam();
    reflect_active = DigitalSensor_ReadReflect();
    light_active = DigitalSensor_ReadLight();
}

10ms 对手动遮挡和近物检测足够。需要快速计数时,采样频率还要按目标运动速度重新安排;三种"1"背后,对应的是三种不同的空间关系。

让两个动作把区别说出来

c 复制代码
Buzzer_Set(beam_blocked);

if (reflect_active)
{
    LED_On();
}
else
{
    LED_Off();
}

PB12 接常见三针有源蜂鸣器模块,触发端低电平时发声。这里把它当成"低电平进去就响"的黑盒;有源和无源的发声原理另开文章。

把手指插进对射两端之间。蜂鸣器应该随着 beam_blocked 的有效状态动作;拿开手指,声音停止。把白纸移到 TCRT5000 前面,PC13 外接 LED 随 reflect_active 翻转;纸离开,LED 恢复。

对射的手指不需要靠近接收头,只要穿过中间那条线;TCRT5000 的手指必须进入近距离反射范围;光敏没有遮挡动作也可以触发,只要环境亮暗改变。

让状态变化看得见

如果每 10ms 都刷新一次屏幕,重复的 0 和 1 没有太大意义。可以只在状态发生变化时更新显示:

c 复制代码
if (beam_blocked != old_beam)
{
    old_beam = beam_blocked;
    OLED_ShowNum(0, 0, beam_blocked, 1, OLED_6X8);
}

if (reflect_active != old_reflect)
{
    old_reflect = reflect_active;
    OLED_ShowNum(0, 8, reflect_active, 1, OLED_6X8);
}

用纸片连续遮挡对射三次,屏幕上的 beam 应该跟着变化;把纸片在 TCRT5000 前后移动,reflect 的变化对应的是距离和表面反射,不是某一条固定光路被截断。

光敏 DO 的变化和手机灯、手掌阴影、环境灯有关;光敏 AO 和 DO 的阈值关系,可以对照前文的实验观察。

三个实验,把"红外"拆开

实验一:对射只认光路

先让发射端和接收端正对,记录 beam 的初始值。用硬纸片从两端之间穿过,保持纸片不碰模块;再把纸片移走。

记录三件事:纸片没有进入光路时的状态、挡住光路时的状态和蜂鸣器反应、移走后多久恢复。把纸片放在发射端旁边、接收端旁边,但不挡中间直线,状态不应该因为"附近有一块纸"就改变。

实验二:TCRT5000 需要一个反射面

先把白纸放在模块前方,慢慢拉远,记录 LED 什么时候熄灭。换成黑纸重复,再换一个有光泽的物体。

不要求不同模块给出同一个距离。留下自己的三条记录:白纸触发范围、黑纸触发范围、亮面物体的反应。TCRT5000 看的不是抽象的"有没有东西",它看的是返回到接收管的反射光够不够越过阈值。

把手指放在模块侧面,既不挡正前方,也不让反射面进入感应区。LED 如果仍翻转,调整安装角度和遮光条件,检查环境光是不是从侧面进来了。

实验三:光敏 DO 不等于 TCRT5000 DO

把手机灯照向光敏模块,再用手掌遮住;同时保持 TCRT5000 前面的白纸位置不变。记录三行 OLED 的变化。

然后把手机灯移走,拿白纸在 TCRT5000 前后移动,观察哪一行变化。两种动作可以分别触发两个状态,说明 DO 只描述输出形式,没有说明传感器正在观察什么。

故障时先看空间关系

对射一直显示"被挡"时,让两端靠近、正对,观察接收端指示灯;如果光路这样仍不恢复,检查 VCC、GND 和发射/接收端。再把两端一点点拉开,找出稳定距离。

TCRT5000 一直触发时,把目标拿远,再拧模块电位器;一直不触发时,把白纸贴近,确认发射灯在工作。黑纸、白纸和亮面材料的结果不同,属于反射条件变化。

光敏 DO 一直翻转时,重新观察 AO 电压和阈值电位器;两者的变化能帮助定位问题。

只有当指示灯、供电和实际动作都正常,OLED 上的 0/1 方向却反了,才改 ACTIVE_LOW。

八路循迹和 TCRT5000 都是在比较反射光,只是观察点数量不同;对射红外则把发射端和接收端分开,判断的是中间光路是否被挡住。把八路模块中的一路单独使用时,接线和读取方式可以与单个 TCRT5000 按同一思路处理,再根据实际输出电平调整有效状态。

传感器只交出状态。对射状态让蜂鸣器响,TCRT5000 状态让 LED 亮;上层动作改变时,底层读取函数仍然可以不动。

最后橘猫说

传感器模块到底在交出什么,不外乎三种:模拟电压、比较器切好的 0/1、以及按节奏送出来的数字协议。AO 的电压要交给 ADC,DO 的电平交给 GPIO,DHT11 的 40 个 bit 要在一根数据线上慢慢收。先把一根线到底交出什么问清楚,接错接口、读错数值的弯路能少走一大半。十分推荐大家去看看 ADC 读数的原理,十分的天才。

最后蜘蛛说

传感器天天把风吹草动整理成小报告递给我,我对着屏幕看了半天,最后发现是杜邦线在和我玩捉迷藏。

相关推荐
Brilliantwxx1 小时前
【STM32】 工程实战 WS2812B 全彩 LED 灯控制(DMA 驱动)
stm32·单片机·嵌入式硬件·mcu
youshi66683 小时前
平衡车——PID算法优化
stm32·单片机·嵌入式硬件·算法·pid·平衡车
ye150127774554 小时前
220V降压24V700mA交转直芯片WT5110
stm32·单片机·嵌入式硬件·其他·硬件工程
创界工坊工作室7 小时前
21-基于单片机的智能窗帘与光照联动控制系统设计
stm32·单片机·嵌入式硬件·proteus
zhaoshuzhaoshu7 小时前
嵌入式系统看门狗详解——以 STM32 为例
stm32·嵌入式硬件
石马马户8 小时前
传感器的中断 Latch、中断状态寄存器、中断读取与保持时间
stm32·单片机·嵌入式硬件
fly_sunnn18 小时前
STM32-GPIO
stm32·单片机·嵌入式硬件
宵时待雨1 天前
STM32基础1:嵌入式历史与生态
stm32·单片机·嵌入式硬件
zhangzhangkeji1 天前
stm32 14-1 WDG 看门狗:理论部分
stm32