添加补充光敏传感器驱动

This commit is contained in:
SepComet 2026-06-15 20:20:49 +08:00
parent e0946fbf36
commit 334c9ee96f
12 changed files with 245 additions and 16 deletions

View File

@ -32,7 +32,7 @@ set(CORE_SOURCES
if(USE_FRAMEBUFFER) if(USE_FRAMEBUFFER)
list(APPEND CORE_SOURCES list(APPEND CORE_SOURCES
src/Core/Platform/FBDisplay.cpp src/Core/Platform/FBDisplay.cpp
src/Core/Platform/LinuxPhotoSensor.cpp src/Core/Platform/Ap3216cPhotoSensor.cpp
) )
else() else()
list(APPEND CORE_SOURCES list(APPEND CORE_SOURCES

View File

@ -1,8 +1,12 @@
set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm) set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc) # Linaro GCC 4.9.4 2017.01 IMX6U
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++) # tar -xJf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz -C ~/toolchains
set(IMX6U_TOOLCHAIN_ROOT "$ENV{HOME}/toolchains/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf")
set(CMAKE_C_COMPILER "${IMX6U_TOOLCHAIN_ROOT}/bin/arm-linux-gnueabihf-gcc")
set(CMAKE_CXX_COMPILER "${IMX6U_TOOLCHAIN_ROOT}/bin/arm-linux-gnueabihf-g++")
set(CMAKE_SYSROOT "${IMX6U_TOOLCHAIN_ROOT}/arm-linux-gnueabihf/libc")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)

View File

@ -9,10 +9,16 @@ set(LIGHTGAME_SOURCES
src/engine/LightGameApp.cpp src/engine/LightGameApp.cpp
src/engine/RoomLayout.cpp src/engine/RoomLayout.cpp
src/systems/LightEffectSystem.cpp src/systems/LightEffectSystem.cpp
src/editor/LevelEditor.cpp
src/main.cpp src/main.cpp
) )
if(NOT USE_FRAMEBUFFER)
list(APPEND LIGHTGAME_SOURCES src/editor/LevelEditor.cpp)
endif()
add_executable(IMX6U-LightGame ${LIGHTGAME_SOURCES}) add_executable(IMX6U-LightGame ${LIGHTGAME_SOURCES})
target_include_directories(IMX6U-LightGame PRIVATE src/engine src/systems src/levels src/editor generated) target_include_directories(IMX6U-LightGame PRIVATE src/engine src/systems src/levels generated)
if(NOT USE_FRAMEBUFFER)
target_include_directories(IMX6U-LightGame PRIVATE src/editor)
endif()
imx6u_configure_app_target(IMX6U-LightGame) imx6u_configure_app_target(IMX6U-LightGame)

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include "GameObject.h" #include "GameObject.h"

View File

@ -41,6 +41,7 @@ namespace LightGame
level_total_loaded_(false), level_total_loaded_(false),
manual_light_level_(2048), manual_light_level_(2048),
light_step_(256), light_step_(256),
smoothed_light_q8_(2048 << 8),
has_manual_override_(false), has_manual_override_(false),
debug_mode_(false), debug_mode_(false),
death_processed_(false) death_processed_(false)
@ -74,6 +75,7 @@ namespace LightGame
const uint32_t next = static_cast<uint32_t>(manual_light_level_) + light_step_; const uint32_t next = static_cast<uint32_t>(manual_light_level_) + light_step_;
manual_light_level_ = static_cast<uint16_t>(next > 4095 ? 4095 : next); manual_light_level_ = static_cast<uint16_t>(next > 4095 ? 4095 : next);
has_manual_override_ = true; has_manual_override_ = true;
smoothed_light_q8_ = static_cast<int32_t>(manual_light_level_) << 8;
} }
if (keyboard_input_->is_key_down(Platform::KEY_S)) if (keyboard_input_->is_key_down(Platform::KEY_S))
{ {
@ -81,13 +83,25 @@ namespace LightGame
? manual_light_level_ - light_step_ ? manual_light_level_ - light_step_
: 0; : 0;
has_manual_override_ = true; has_manual_override_ = true;
smoothed_light_q8_ = static_cast<int32_t>(manual_light_level_) << 8;
} }
} }
if (photo_sensor_ && photo_sensor_->is_open() && !has_manual_override_) if (photo_sensor_ && photo_sensor_->is_open() && !has_manual_override_)
{ {
photo_sensor_->update(); photo_sensor_->update();
manual_light_level_ = photo_sensor_->read_level(); const uint16_t raw = photo_sensor_->read_level();
// 一阶 EMAsmoothed += (raw - smoothed) * alpha
// alpha = 1/16 → -3dB 截止频率约为 fs/10030fps 下约 0.3Hz
// 足以滤掉 ADC 量化抖动又不会让光照变化拖太久90% 收敛 ~0.7s
const int32_t target_q8 = static_cast<int32_t>(raw) << 8;
smoothed_light_q8_ += (target_q8 - smoothed_light_q8_) >> 4;
int32_t out = smoothed_light_q8_ >> 8;
if (out < 0) out = 0;
if (out > 4095) out = 4095;
manual_light_level_ = static_cast<uint16_t>(out);
} }
const GameState prev_state = state_manager_.get_state(); const GameState prev_state = state_manager_.get_state();
@ -142,7 +156,15 @@ namespace LightGame
bool LightGameApp::is_action_pressed() bool LightGameApp::is_action_pressed()
{ {
return button_input_ && button_input_->was_pressed(); if (button_input_ && button_input_->was_pressed())
{
return true;
}
if (pointer_input_ && pointer_input_->was_pressed())
{
return true;
}
return false;
} }
void LightGameApp::update_title(uint32_t dt_ms) void LightGameApp::update_title(uint32_t dt_ms)

View File

@ -59,6 +59,11 @@ namespace LightGame
uint16_t manual_light_level_; uint16_t manual_light_level_;
uint16_t light_step_; uint16_t light_step_;
// 光敏读数缓动用的 Q8 定点内部状态real_value << 8
// 用 Q8 是为了让 alpha=1/16 时低 4 位的累积分量不会被整数除法吞掉
// 仅在 sensor 自动模式生效;手动模式直接赋值,按键响应保持瞬时
int32_t smoothed_light_q8_;
bool has_manual_override_; bool has_manual_override_;
bool debug_mode_; bool debug_mode_;
bool death_processed_; bool death_processed_;

View File

@ -12,7 +12,6 @@
#include "TimeSource.h" #include "TimeSource.h"
#include "Timer.h" #include "Timer.h"
#include "LightGameApp.h" #include "LightGameApp.h"
#include "LevelEditor.h"
#include "font_atlas.h" #include "font_atlas.h"
#include "LevelTotalData.h" #include "LevelTotalData.h"
@ -20,6 +19,7 @@
#include "FBDisplay.h" #include "FBDisplay.h"
#else #else
#include "SDLDisplay.h" #include "SDLDisplay.h"
#include "LevelEditor.h"
#include "imgui.h" #include "imgui.h"
#include "backends/imgui_impl_sdl2.h" #include "backends/imgui_impl_sdl2.h"
#include "backends/imgui_impl_sdlrenderer2.h" #include "backends/imgui_impl_sdlrenderer2.h"
@ -97,9 +97,9 @@ int main(int argc, char* argv[])
&pointerInput, &pointerInput,
&photoSensor); &photoSensor);
#ifndef USE_FRAMEBUFFER
LightGame::LevelEditor editor(ScreenWidth, ScreenHeight); LightGame::LevelEditor editor(ScreenWidth, ScreenHeight);
#ifndef USE_FRAMEBUFFER
Platform::SDLDisplay* sdl_display = static_cast<Platform::SDLDisplay*>(display); Platform::SDLDisplay* sdl_display = static_cast<Platform::SDLDisplay*>(display);
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
ImGui::CreateContext(); ImGui::CreateContext();
@ -156,6 +156,7 @@ int main(int argc, char* argv[])
buttonInput.update(); buttonInput.update();
pointerInput.update(); pointerInput.update();
#ifndef USE_FRAMEBUFFER
if (editor.is_active()) if (editor.is_active())
{ {
if (editor.get_pending_load() >= 0) if (editor.get_pending_load() >= 0)
@ -172,6 +173,10 @@ int main(int argc, char* argv[])
app.update(timer.fixed_delta_ms()); app.update(timer.fixed_delta_ms());
app.draw(ctx, font); app.draw(ctx, font);
} }
#else
app.update(timer.fixed_delta_ms());
app.draw(ctx, font);
#endif
#ifdef USE_FRAMEBUFFER #ifdef USE_FRAMEBUFFER
ctx.present(display); ctx.present(display);

View File

@ -0,0 +1,151 @@
#include "Ap3216cPhotoSensor.h"
#include <iostream>
#if defined(__linux__)
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#endif
namespace
{
// AP3216C i2c 寄存器
const uint8_t kRegSysConfig = 0x00;
const uint8_t kRegAlsConfig = 0x10;
const uint8_t kRegAlsLow = 0x0c;
const uint8_t kRegAlsHigh = 0x0d;
// 模式 / 增益
const uint8_t kSysAlsContinuous = 0x01; // ALS-only 持续测量;避免与 PS 共享 ADC 导致读数被打断
const uint8_t kAlsRange323Lux = 0x30; // bit[5:4]=11最高灵敏度对应 323 lux 满量程
const uint8_t kAp3216cAddr = 0x1e;
#if defined(__linux__)
bool I2cWriteByte(int fd, uint8_t reg, uint8_t value)
{
uint8_t buf[2] = { reg, value };
return write(fd, buf, sizeof(buf)) == static_cast<ssize_t>(sizeof(buf));
}
bool I2cReadByte(int fd, uint8_t reg, uint8_t& value)
{
if (write(fd, &reg, 1) != 1)
{
return false;
}
return read(fd, &value, 1) == 1;
}
#endif
}
namespace Platform
{
Ap3216cPhotoSensor::Ap3216cPhotoSensor()
: device_path_("/dev/i2c-0"),
opened_(false),
level_(0),
max_value_(4095)
#if defined(__linux__)
,
fd_(-1)
#endif
{
}
Ap3216cPhotoSensor::~Ap3216cPhotoSensor()
{
shutdown();
}
bool Ap3216cPhotoSensor::init(const std::string& device_path)
{
shutdown();
// 注意:这里 device_path 指 i2c 总线节点,不再是字符设备 /dev/ap3216c
// 板子上 AP3216C 挂在 i2c-0 上DT: i2c@021a0000地址 0x1e
device_path_ = device_path.empty() ? "/dev/i2c-0" : device_path;
level_ = 0;
#if defined(__linux__)
fd_ = open(device_path_.c_str(), O_RDWR);
if (fd_ < 0)
{
std::cerr << "Ap3216cPhotoSensor open failed: " << device_path_ << std::endl;
return false;
}
// 用 I2C_SLAVE_FORCE 是因为内置驱动已经把 0x1e 占住了i2cdetect 显示 UU
// 我们绕过那个驱动直接通信,所以必须 force
if (ioctl(fd_, I2C_SLAVE_FORCE, kAp3216cAddr) < 0)
{
std::cerr << "Ap3216cPhotoSensor I2C_SLAVE_FORCE failed: " << errno << std::endl;
close(fd_);
fd_ = -1;
return false;
}
// 切换到 ALS-only 持续模式 + 最高灵敏度
// 内置驱动 probe 时设的是 0x03ALS+PS+IRALS 会被 PS 中断采样而读到 0
if (!I2cWriteByte(fd_, kRegSysConfig, kSysAlsContinuous))
{
std::cerr << "Ap3216cPhotoSensor: write SysConfig failed." << std::endl;
close(fd_);
fd_ = -1;
return false;
}
if (!I2cWriteByte(fd_, kRegAlsConfig, kAlsRange323Lux))
{
std::cerr << "Ap3216cPhotoSensor: write AlsConfig failed." << std::endl;
close(fd_);
fd_ = -1;
return false;
}
opened_ = true;
return true;
#else
std::cerr << "Ap3216cPhotoSensor is only available on Linux." << std::endl;
return false;
#endif
}
void Ap3216cPhotoSensor::update()
{
if (!opened_)
{
return;
}
#if defined(__linux__)
uint8_t lo = 0;
uint8_t hi = 0;
// ALS 数据寄存器0x0c (低 8 位) + 0x0d (高 8 位)
if (!I2cReadByte(fd_, kRegAlsLow, lo) || !I2cReadByte(fd_, kRegAlsHigh, hi))
{
// 单次失败保留上次的 level_避免亮度抖到 0
return;
}
const uint16_t als = static_cast<uint16_t>(lo) |
(static_cast<uint16_t>(hi) << 8);
// 实测 323 lux 增益下强光接近 4095直接 clamp 即可
level_ = als > max_value_ ? max_value_ : als;
#endif
}
void Ap3216cPhotoSensor::shutdown()
{
#if defined(__linux__)
if (fd_ >= 0)
{
close(fd_);
fd_ = -1;
}
#endif
opened_ = false;
level_ = 0;
}
}

View File

@ -0,0 +1,35 @@
#pragma once
#include "IPhotoSensor.h"
namespace Platform
{
// 直接走 i2c 总线读 AP3216C绕过 /dev/ap3216c 字符设备驱动。
// 设备路径默认 /dev/i2c-0DT 中传感器挂在 i2c@021a0000 0x1e。
// 初始化时会强制切换到 ALS-only 持续测量 + 323 lux 满量程(最高灵敏度),
// 因为内置驱动 probe 时配的 0x03 模式会让 ALS 被 PS 共享 ADC 中断采样而归零。
class Ap3216cPhotoSensor : public IPhotoSensor
{
private:
std::string device_path_;
bool opened_;
uint16_t level_;
uint16_t max_value_;
#if defined(__linux__)
int fd_;
#endif
public:
Ap3216cPhotoSensor();
~Ap3216cPhotoSensor();
bool init(const std::string& device_path = "/dev/i2c-0") override;
void update() override;
void shutdown() override;
bool is_open() const override { return opened_; }
uint16_t read_level() const override { return level_; }
const std::string& get_device_path() const override { return device_path_; }
uint16_t get_max_value() const { return max_value_; }
};
}

View File

@ -6,7 +6,7 @@
#include "EvdevButtonInput.h" #include "EvdevButtonInput.h"
#include "EvdevKeyboardState.h" #include "EvdevKeyboardState.h"
#include "EvdevTouchInput.h" #include "EvdevTouchInput.h"
#include "LinuxPhotoSensor.h" #include "Ap3216cPhotoSensor.h"
#else #else
#include "SdlAudioInput.h" #include "SdlAudioInput.h"
#include "SdlAudioOutput.h" #include "SdlAudioOutput.h"
@ -24,7 +24,7 @@ namespace Platform
typedef EvdevButtonInput DefaultButtonInput; typedef EvdevButtonInput DefaultButtonInput;
typedef EvdevKeyboardState DefaultKeyboardState; typedef EvdevKeyboardState DefaultKeyboardState;
typedef EvdevTouchInput DefaultPointerInput; typedef EvdevTouchInput DefaultPointerInput;
typedef LinuxPhotoSensor DefaultPhotoSensor; typedef Ap3216cPhotoSensor DefaultPhotoSensor;
#else #else
typedef SdlAudioInput DefaultAudioInput; typedef SdlAudioInput DefaultAudioInput;
typedef SdlAudioOutput DefaultAudioOutput; typedef SdlAudioOutput DefaultAudioOutput;

View File

@ -11,8 +11,8 @@
namespace Platform namespace Platform
{ {
EvdevButtonInput::EvdevButtonInput() EvdevButtonInput::EvdevButtonInput()
: device_path_("/dev/input/event0"), : device_path_("/dev/input/event2"),
key_code_(28), key_code_(114),
opened_(false), opened_(false),
down_(false), down_(false),
pressed_(false), pressed_(false),
@ -33,8 +33,8 @@ namespace Platform
{ {
shutdown(); shutdown();
device_path_ = device_path.empty() ? "/dev/input/event0" : device_path; device_path_ = device_path.empty() ? "/dev/input/event2" : device_path;
key_code_ = key_code == 0 ? 28 : key_code; key_code_ = key_code == 0 ? 114 : key_code;
down_ = false; down_ = false;
pressed_ = false; pressed_ = false;
released_ = false; released_ = false;

View File

@ -21,7 +21,7 @@ namespace Platform
EvdevButtonInput(); EvdevButtonInput();
~EvdevButtonInput(); ~EvdevButtonInput();
bool init(const std::string& device_path = "/dev/input/event0", int key_code = 28) override; bool init(const std::string& device_path = "/dev/input/event2", int key_code = 114) override;
void update() override; void update() override;
void shutdown() override; void shutdown() override;