eefocus_3891719 发表于 昨天 00:26

【欧时RS x Arduino】05 Led Matrix 显示风扇档位

# 背景


前面已经实现了手势控制风扇,只有在网页展示了手势识别结果以及风扇档位,这次新增板载 Led Matrix 显示风扇档位,并支持不同的亮度等级。

# 亮度定义与时序

L1~L7 与库的非零灰度 1~7 一一对应,L1 是最低亮度,L7 是最大亮度,不使用熄灭灰度 0 来绘制数字。每隔 5 秒下降一级,从 L7 到 L1 共六次下降,因此需 30 秒。

到 L1 后持续显示当前数字,不再重复刷新或打印亮度日志。重复同档指令不重新提亮;只有切换到不同风扇档位才恢复 L7。风扇切到 0 档时,电机停转,但点阵仍显示数字 0,并逐渐降至 L1。亮度变化不影响电机 PWM。

初始化时显示 0,计时从 MCU 初始化开始,可能早于 Python 启动。主循环用 millis\(\) 非阻塞计时,支持延迟 tick 追赶与计时回绕。本次不增加自动心跳超时急停。

# 实现与参数

- `sketch/matrix_display.h/.cpp`:数字、灰度、渐暗状态;使用 `setGrayscaleBits(3)` 与灰度帧 `draw()`。
- `sketch/app_config.h`:`MATRIX_BRIGHTNESS_MIN=1`、`MATRIX_BRIGHTNESS_MAX=7`、`MATRIX_DIM_INTERVAL_MS=5000`。
- `sketch/motor.cpp`:写入 PWM 后更新显示档位。
- `sketch/sketch.ino`:初始化、周期调用和版本标记。
- `python/fan_test.py`:快速切档和显示渐暗验证。

官方接口依据:(https://github.com/arduino/ArduinoCore-zephyr/blob/main/libraries/Arduino_LED_Matrix/src/Arduino_LED_Matrix.h)。采用七级非零灰度,最低等级保持常亮。

# 整体流程图

下图区分“切换档位”和“定时渐暗”两条路径。接收档位指令只修改显示状态,画面由 MCU 主循环统一刷新;即使 Linux 后续没有发送指令,亮度仍会自动下降。


!(//m.qjumbo.com/forum/data/attachment/forum/202611/02/002600wt2vvn6fahfd6f62.png)



# Linux 和 MCU 交互图

Linux 仅下发风扇档位,不传输数字字模或亮度变化指令。点阵状态由 MCU 依据其实际写入的档位维护,因此 AI 手势控制与 sweep 测试自然复用同一套显示逻辑。


!(//m.qjumbo.com/forum/data/attachment/forum/202611/02/002612ua73ohrxb73m35c5.png)



点阵反映的是 MCU 已采用并写入 PWM 的“指令档位”,不是转速传感器测量结果。电机方向与显示档位相互独立:改变方向本身不会触发数字变化或亮度重置。

# 模块接口

# 关键代码与说明

## 亮度配置

```C++
#define MATRIX_BRIGHTNESS_MIN1
#define MATRIX_BRIGHTNESS_MAX7
#define MATRIX_DIM_INTERVAL_MS 5000UL

const uint8_t GRAY_FOR_BRIGHTNESS
    = {0, 1, 2, 3, 4, 5, 6, 7};
```

数组下标就是逻辑亮度等级。运行时只使用下标1~7;第0项保留为0,但不会用来绘制数字。画面背景始终为0,数字笔画最低为1。

## 初始化以及主循环

```Plain
// setup():在电机与Bridge初始化后初始化点阵
matrix_display_begin();

// loop():与通信服务一起持续执行
bridge_service_tick();
matrix_display_tick();
```

点阵库通过 `begin()` 初始化,并调用 `setGrayscaleBits(3)`。初始化会设置0档并立即绘制,因此上电后不必等待 Linux 发出第一条指令就能看到数字。

## 电机控制与显示关联

```Plain
// motor_set_level() 中,在档位钳位完成后执行
analogWrite(MOTOR_PIN_PWMA, DUTY_FOR_LEVEL);
matrix_display_set_level(level);
```

显示使用钳位后的合法档位,与 PWM 表的索引保持一致。显示函数不改变 PWM,因此数字逐渐变暗时风扇不会随之降档。

## 切换数字以及恢复亮度

```Plain
void matrix_display_set_level(int level)
{
    if (level < 0)
    {
      level = 0;
    }
    if (level > FAN_LEVEL_MAX)
    {
      level = FAN_LEVEL_MAX;
    }
    if (level == g_level)
    {
      return;
    }
    g_level = level;
    g_brightness = MATRIX_BRIGHTNESS_MAX;
    g_last_step_ms = static_cast<uint32_t>(millis());
    g_dirty = true;
}
```

先将输入限制到合法档位。如果与现有档位相同,立即返回;否则恢复最大亮度、记录时间并设置 `g_dirty`。这里不直接绘图,由下一次主循环统一处理。

## 非阻塞渐暗

```C++
void matrix_display_tick()
{
    if (g_level < 0)
    {
      return;
    }
    const uint32_t now = static_cast<uint32_t>(millis());
    // 无符号差值允许 millis() 回绕;延迟 tick 时一次追上应有亮度。
    const uint32_t steps = (now - g_last_step_ms) / MATRIX_DIM_INTERVAL_MS;
    if (g_brightness > MATRIX_BRIGHTNESS_MIN && steps > 0)
    {
      const uint32_t remaining = g_brightness - MATRIX_BRIGHTNESS_MIN;
      g_brightness = steps >= remaining ? MATRIX_BRIGHTNESS_MIN : g_brightness - steps;
      g_last_step_ms += steps * MATRIX_DIM_INTERVAL_MS;
      g_dirty = true;
    }
    if (g_dirty)
    {
      g_dirty = false;
      render();
    }
}
```

`steps` 表示已过去多少个完整的5秒周期。若主循环延迟了12秒才执行,会一次降低两级,而不是仅下降一级。计时基准按实际处理的完整周期推进,保留余下时间,避免周期逐步漂移。

无符号时间差支持正常的 `millis()` 回绕。亮度到达 L1 后不再递减,也不因时间继续流逝而重新点亮。此设计仍要求主循环正常运行,不能用它补偿无限期停顿。

## 字模居中与灰度绘制

```C++
constexpr uint8_t WIDTH = 13;
constexpr uint8_t HEIGHT = 8;
constexpr uint8_t DIGIT_WIDTH = 5;
constexpr uint8_t DIGIT_HEIGHT = 6;
constexpr uint8_t OFFSET_X = (WIDTH - DIGIT_WIDTH) / 2;   // 4
constexpr uint8_t OFFSET_Y = (HEIGHT - DIGIT_HEIGHT) / 2; // 1

// 示例:数字0,每个二进制数表示字模的一行
const uint8_t digit_zero = {
    0b01110,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b01110
};

// render()中:先将整个g_frame清零,再按当前字模填入笔画
const uint8_t gray = GRAY_FOR_BRIGHTNESS;
for (uint8_t y = 0; y < DIGIT_HEIGHT; ++y)
{
    for (uint8_t x = 0; x < DIGIT_WIDTH; ++x)
    {
      if (DIGITS & (1U << (DIGIT_WIDTH - 1 - x)))
      {
            g_frame[(y + OFFSET_Y) * WIDTH + x + OFFSET_X] = gray;
      }
    }
}
g_matrix.draw(g_frame);
```

5×6字模占据列4~8、行1~6,上下各留一行、左右各留四列,实现矩形区域居中。所有笔画像素使用同一个灰度,形成整位数字的统一亮度。切换数字时先清空帧缓冲,避免旧数字笔画残留。

# 演示视频

<iframe src="//player.bilibili.com/player.html?isOutside=true&aid=117542047716636&bvid=BV1spxt6HE16&cid=43244915783&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>

页: [1]
查看完整版本: 【欧时RS x Arduino】05 Led Matrix 显示风扇档位

Baidu
map