mirror of
https://github.com/sipeed/MaixCDK.git
synced 2026-11-09 17:04:38 -06:00
feat(peripheral): add fill light class with PWM dimming on MaixCAM2
- add maix::peripheral::light::Light with on/off/is_on/set_duty/duty/pin/freq, exposed to MaixPy as maix.light.Light - MaixCAM2: drive the light by PWM and read maix_light_freq/maix_light_duty from /boot/configs (default 10000Hz/50%), warn when duty > 70% because long time high power running may cause the LED to age quickly - MaixCAM: keep GPIO on/off control - app_camera: use the new Light class instead of raw GPIO
This commit is contained in:
102
components/peripheral/include/maix_light.hpp
Normal file
102
components/peripheral/include/maix_light.hpp
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* @author Sipeed
|
||||
* @copyright Sipeed Ltd 2026-
|
||||
* @license Apache 2.0
|
||||
* @update 2026.11.5: create this file.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "maix_basic.hpp"
|
||||
|
||||
namespace maix::peripheral::light
|
||||
{
|
||||
/**
|
||||
* @brief Fill light(补光灯) control class.
|
||||
*
|
||||
* The light pin is read from the board config file(/boot/board or /boot/boards/board.<id>)
|
||||
* item "cam_light_io", default is "B25" on MaixCAM2 and "B3" on MaixCAM.
|
||||
*
|
||||
* On MaixCAM2 the light is driven by PWM so its brightness can be adjusted,
|
||||
* the PWM frequency and duty are read from the system config file(/boot/configs):
|
||||
* maix_light_freq: PWM frequency, unit Hz, default 10000
|
||||
* maix_light_duty: PWM duty, unit %, range [0, 100], default 50
|
||||
* On MaixCAM the light is driven by GPIO, only on/off is supported.
|
||||
*
|
||||
* @maixpy maix.peripheral.light.Light
|
||||
*/
|
||||
class Light
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Fill light constructor, will open the light hardware and turn it off by default.
|
||||
* @throw err::Exception if open the light hardware failed.
|
||||
* @maixpy maix.peripheral.light.Light.__init__
|
||||
*/
|
||||
Light();
|
||||
~Light();
|
||||
|
||||
/**
|
||||
* @brief Turn on the fill light.
|
||||
* On MaixCAM2 the PWM duty will be set to the configured duty(maix_light_duty),
|
||||
* on MaixCAM the GPIO will be set to high level.
|
||||
* @return error code, err::ERR_NONE means success.
|
||||
* @maixpy maix.peripheral.light.Light.on
|
||||
*/
|
||||
err::Err on();
|
||||
|
||||
/**
|
||||
* @brief Turn off the fill light.
|
||||
* @return error code, err::ERR_NONE means success.
|
||||
* @maixpy maix.peripheral.light.Light.off
|
||||
*/
|
||||
err::Err off();
|
||||
|
||||
/**
|
||||
* @brief Get the fill light state.
|
||||
* @return true if the light is on, else false.
|
||||
* @maixpy maix.peripheral.light.Light.is_on
|
||||
*/
|
||||
bool is_on();
|
||||
|
||||
/**
|
||||
* @brief Set the fill light brightness(PWM duty), only take effect at runtime,
|
||||
* will not modify the config file.
|
||||
*
|
||||
* On MaixCAM only 0(off) and non-zero(on) are supported.
|
||||
*
|
||||
* @note If duty > 70, a warning will be printed, because long time high power
|
||||
* running may cause the LED to age quickly.
|
||||
*
|
||||
* @param[in] duty PWM duty, unit %, range [0, 100]. 0 means turn off the light.
|
||||
* @return error code, err::ERR_NONE means success.
|
||||
* @maixpy maix.peripheral.light.Light.set_duty
|
||||
*/
|
||||
err::Err set_duty(double duty);
|
||||
|
||||
/**
|
||||
* @brief Get the current fill light brightness(PWM duty).
|
||||
* @return PWM duty, unit %, range [0, 100].
|
||||
* @maixpy maix.peripheral.light.Light.duty
|
||||
*/
|
||||
double duty();
|
||||
|
||||
/**
|
||||
* @brief Get the fill light pin name.
|
||||
* @return pin name, e.g. "B25".
|
||||
* @maixpy maix.peripheral.light.Light.pin
|
||||
*/
|
||||
std::string pin();
|
||||
|
||||
/**
|
||||
* @brief Get the fill light PWM frequency.
|
||||
* @return PWM frequency, unit Hz. Return 0 if the light is driven by GPIO.
|
||||
* @maixpy maix.peripheral.light.Light.freq
|
||||
*/
|
||||
int freq();
|
||||
|
||||
private:
|
||||
void *_priv;
|
||||
};
|
||||
} // namespace maix::peripheral::light
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "maix_adc.hpp"
|
||||
#include "maix_gpio.hpp"
|
||||
#include "maix_light.hpp"
|
||||
#include "maix_i2c.hpp"
|
||||
#include "maix_pwm.hpp"
|
||||
#include "maix_spi.hpp"
|
||||
|
||||
317
components/peripheral/port/linux_common/maix_light.cpp
Normal file
317
components/peripheral/port/linux_common/maix_light.cpp
Normal file
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* @author Sipeed
|
||||
* @copyright Sipeed Ltd 2026-
|
||||
* @license Apache 2.0
|
||||
* @update 2026.11.5: create this file.
|
||||
*/
|
||||
|
||||
#include "maix_light.hpp"
|
||||
#include "maix_log.hpp"
|
||||
#include "maix_sys.hpp"
|
||||
#include "maix_app.hpp"
|
||||
#include "maix_pinmap.hpp"
|
||||
#include "maix_gpio.hpp"
|
||||
#include "maix_pwm.hpp"
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined(PLATFORM_MAIXCAM2)
|
||||
#define LIGHT_DEFAULT_PIN "B25"
|
||||
#else
|
||||
#define LIGHT_DEFAULT_PIN "B3"
|
||||
#endif
|
||||
|
||||
namespace maix::peripheral::light
|
||||
{
|
||||
static std::string _str_trim(const std::string &s)
|
||||
{
|
||||
size_t start = s.find_first_not_of(" \t\r\n");
|
||||
if (start == std::string::npos)
|
||||
return "";
|
||||
size_t end = s.find_last_not_of(" \t\r\n");
|
||||
return s.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
static std::string _get_light_pin()
|
||||
{
|
||||
std::string pin = LIGHT_DEFAULT_PIN;
|
||||
try
|
||||
{
|
||||
auto configs = sys::device_configs();
|
||||
auto it = configs.find("cam_light_io");
|
||||
if (it != configs.end())
|
||||
{
|
||||
std::string value = _str_trim(it->second);
|
||||
if (!value.empty())
|
||||
pin = value;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
log::warn("read device configs failed: %s", e.what());
|
||||
}
|
||||
return pin;
|
||||
}
|
||||
|
||||
#if defined(PLATFORM_MAIXCAM2)
|
||||
#define LIGHT_DUTY_WARN_THRESHOLD 70.0
|
||||
|
||||
static void _warn_high_duty(double duty)
|
||||
{
|
||||
log::warn("light duty %.0f%% is greater than %.0f%%, long time high power running may cause the LED to age quickly",
|
||||
duty, LIGHT_DUTY_WARN_THRESHOLD);
|
||||
}
|
||||
#endif
|
||||
|
||||
struct LightPriv
|
||||
{
|
||||
bool is_on;
|
||||
std::string pin;
|
||||
int freq; // PWM frequency, 0 if driven by GPIO
|
||||
double duty; // configured duty from /boot/configs, used when on() called
|
||||
double cur_duty;
|
||||
#if defined(PLATFORM_MAIXCAM2)
|
||||
pwm::PWM *pwm;
|
||||
#endif
|
||||
gpio::GPIO *gpio;
|
||||
|
||||
LightPriv()
|
||||
: is_on(false)
|
||||
, freq(0)
|
||||
, duty(0)
|
||||
, cur_duty(0)
|
||||
#if defined(PLATFORM_MAIXCAM2)
|
||||
, pwm(nullptr)
|
||||
#endif
|
||||
, gpio(nullptr)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(PLATFORM_MAIXCAM2)
|
||||
static int _get_pin_pwm_id(const std::string &pin, int &pwm_id)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::vector<std::string> funcs = pinmap::get_pin_functions(pin);
|
||||
for (auto &func : funcs)
|
||||
{
|
||||
if (func.rfind("PWM", 0) == 0)
|
||||
{
|
||||
pwm_id = atoi(func.c_str() + 3);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
log::warn("get pin %s functions failed: %s", pin.c_str(), e.what());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
Light::Light()
|
||||
{
|
||||
LightPriv *priv = new LightPriv();
|
||||
_priv = priv;
|
||||
|
||||
priv->pin = _get_light_pin();
|
||||
bool use_pwm = false;
|
||||
|
||||
#if defined(PLATFORM_MAIXCAM2)
|
||||
{
|
||||
std::string freq_str = app::get_sys_config_kv("light", "freq", "10000");
|
||||
std::string duty_str = app::get_sys_config_kv("light", "duty", "50");
|
||||
priv->freq = atoi(freq_str.c_str());
|
||||
if (priv->freq <= 0)
|
||||
priv->freq = 10000;
|
||||
priv->duty = atof(duty_str.c_str());
|
||||
if (priv->duty < 0)
|
||||
priv->duty = 0;
|
||||
if (priv->duty > 100)
|
||||
priv->duty = 100;
|
||||
if (priv->duty > LIGHT_DUTY_WARN_THRESHOLD)
|
||||
_warn_high_duty(priv->duty);
|
||||
|
||||
int pwm_id = -1;
|
||||
if (_get_pin_pwm_id(priv->pin, pwm_id) == 0)
|
||||
{
|
||||
err::check_raise(pinmap::set_pin_function(priv->pin, "PWM" + std::to_string(pwm_id)), "set light pin function failed");
|
||||
try
|
||||
{
|
||||
priv->pwm = new pwm::PWM(pwm_id, priv->freq, 0);
|
||||
use_pwm = true;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
log::warn("open light pwm failed: %s, fallback to gpio", e.what());
|
||||
priv->pwm = nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log::warn("pin %s has no PWM function, fallback to gpio", priv->pin.c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!use_pwm)
|
||||
{
|
||||
priv->freq = 0;
|
||||
err::check_raise(pinmap::set_pin_function(priv->pin, "GPIO" + priv->pin), "set light pin function failed");
|
||||
priv->gpio = new gpio::GPIO(priv->pin, gpio::Mode::OUT);
|
||||
err::check_null_raise(priv->gpio, "open light gpio failed");
|
||||
priv->gpio->low();
|
||||
}
|
||||
priv->is_on = false;
|
||||
priv->cur_duty = 0;
|
||||
log::info("light pin: %s, freq: %d, duty: %.1f", priv->pin.c_str(), priv->freq, priv->duty);
|
||||
}
|
||||
|
||||
Light::~Light()
|
||||
{
|
||||
LightPriv *priv = (LightPriv *)_priv;
|
||||
if (!priv)
|
||||
return;
|
||||
#if defined(PLATFORM_MAIXCAM2)
|
||||
if (priv->pwm)
|
||||
{
|
||||
priv->pwm->duty(0);
|
||||
delete priv->pwm;
|
||||
priv->pwm = nullptr;
|
||||
}
|
||||
#endif
|
||||
if (priv->gpio)
|
||||
{
|
||||
priv->gpio->low();
|
||||
delete priv->gpio;
|
||||
priv->gpio = nullptr;
|
||||
}
|
||||
delete priv;
|
||||
_priv = nullptr;
|
||||
}
|
||||
|
||||
err::Err Light::on()
|
||||
{
|
||||
LightPriv *priv = (LightPriv *)_priv;
|
||||
if (!priv)
|
||||
return err::ERR_RUNTIME;
|
||||
#if defined(PLATFORM_MAIXCAM2)
|
||||
if (priv->pwm)
|
||||
{
|
||||
if (priv->pwm->duty(priv->duty) < 0)
|
||||
return err::ERR_IO;
|
||||
priv->cur_duty = priv->duty;
|
||||
priv->is_on = true;
|
||||
return err::ERR_NONE;
|
||||
}
|
||||
#endif
|
||||
if (priv->gpio)
|
||||
{
|
||||
priv->gpio->high();
|
||||
priv->cur_duty = 100;
|
||||
priv->is_on = true;
|
||||
return err::ERR_NONE;
|
||||
}
|
||||
return err::ERR_RUNTIME;
|
||||
}
|
||||
|
||||
err::Err Light::off()
|
||||
{
|
||||
LightPriv *priv = (LightPriv *)_priv;
|
||||
if (!priv)
|
||||
return err::ERR_RUNTIME;
|
||||
#if defined(PLATFORM_MAIXCAM2)
|
||||
if (priv->pwm)
|
||||
{
|
||||
if (priv->pwm->duty(0) < 0)
|
||||
return err::ERR_IO;
|
||||
priv->cur_duty = 0;
|
||||
priv->is_on = false;
|
||||
return err::ERR_NONE;
|
||||
}
|
||||
#endif
|
||||
if (priv->gpio)
|
||||
{
|
||||
priv->gpio->low();
|
||||
priv->cur_duty = 0;
|
||||
priv->is_on = false;
|
||||
return err::ERR_NONE;
|
||||
}
|
||||
return err::ERR_RUNTIME;
|
||||
}
|
||||
|
||||
bool Light::is_on()
|
||||
{
|
||||
LightPriv *priv = (LightPriv *)_priv;
|
||||
if (!priv)
|
||||
return false;
|
||||
return priv->is_on;
|
||||
}
|
||||
|
||||
err::Err Light::set_duty(double duty)
|
||||
{
|
||||
LightPriv *priv = (LightPriv *)_priv;
|
||||
if (!priv)
|
||||
return err::ERR_RUNTIME;
|
||||
if (duty < 0)
|
||||
duty = 0;
|
||||
if (duty > 100)
|
||||
duty = 100;
|
||||
#if defined(PLATFORM_MAIXCAM2)
|
||||
if (priv->pwm)
|
||||
{
|
||||
if (duty > LIGHT_DUTY_WARN_THRESHOLD && priv->cur_duty <= LIGHT_DUTY_WARN_THRESHOLD)
|
||||
_warn_high_duty(duty);
|
||||
if (priv->pwm->duty(duty) < 0)
|
||||
return err::ERR_IO;
|
||||
priv->cur_duty = duty;
|
||||
priv->is_on = duty > 0;
|
||||
return err::ERR_NONE;
|
||||
}
|
||||
#endif
|
||||
if (priv->gpio)
|
||||
{
|
||||
if (duty > 0)
|
||||
{
|
||||
priv->gpio->high();
|
||||
priv->cur_duty = 100;
|
||||
priv->is_on = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
priv->gpio->low();
|
||||
priv->cur_duty = 0;
|
||||
priv->is_on = false;
|
||||
}
|
||||
return err::ERR_NONE;
|
||||
}
|
||||
return err::ERR_RUNTIME;
|
||||
}
|
||||
|
||||
double Light::duty()
|
||||
{
|
||||
LightPriv *priv = (LightPriv *)_priv;
|
||||
if (!priv)
|
||||
return 0;
|
||||
return priv->cur_duty;
|
||||
}
|
||||
|
||||
std::string Light::pin()
|
||||
{
|
||||
LightPriv *priv = (LightPriv *)_priv;
|
||||
if (!priv)
|
||||
return "";
|
||||
return priv->pin;
|
||||
}
|
||||
|
||||
int Light::freq()
|
||||
{
|
||||
LightPriv *priv = (LightPriv *)_priv;
|
||||
if (!priv)
|
||||
return 0;
|
||||
return priv->freq;
|
||||
}
|
||||
} // namespace maix::peripheral::light
|
||||
@@ -46,14 +46,14 @@
|
||||
2. 码率修改:仅在非录制状态下可修改码率,录制过程中修改码率会提示「视频忙」且不生效;
|
||||
3. 延时摄影:开启延时摄影后,音频会自动关闭,关闭延时摄影后需手动重新开启音频;
|
||||
4. RAW格式:开启RAW格式后,照片保存体积会增大,且需要专用软件解析;
|
||||
5. 补光灯:补光灯的硬件引脚默认配置为GPIOB3,可通过设备配置文件修改引脚映射;
|
||||
5. 补光灯:补光灯由 `maix::peripheral::light::Light` 控制,引脚可通过设备配置文件中的「cam_light_io」修改(MaixCam 默认 B3,MaixCam2 默认 B25);MaixCam2 使用 PWM 调光,频率和占空比由 `/boot/configs` 中的 `maix_light_freq`(默认 10000)和 `maix_light_duty`(默认 50)决定,MaixCam 使用 GPIO 仅支持开关;
|
||||
6. 存储路径:照片/视频默认按日期分目录存储,需确保存储设备有足够空间,否则会保存失败;
|
||||
7. 音视频同步:录制视频时,若出现音频卡顿,可检查音频采样率配置(默认48000Hz)。
|
||||
|
||||
## 5. 更多介绍
|
||||
### 5.1 硬件适配
|
||||
- 支持MaixCam、MaixCam2两款硬件平台,MaixCam2支持更高码率(最高100Mbps)和AI-ISP功能;
|
||||
- 补光灯引脚可通过设备配置文件中的「cam_light_io」参数自定义,默认B3引脚;
|
||||
- 补光灯由 `maix::peripheral::light::Light` 控制,引脚可通过设备配置文件中的「cam_light_io」参数自定义(MaixCam 默认 B3,MaixCam2 默认 B25);MaixCam2 通过 PWM 调光,频率/占空比由 `/boot/configs` 的 `maix_light_freq`/`maix_light_duty` 配置,MaixCam 通过 GPIO 控制;
|
||||
- 摄像头传感器最大分辨率决定可选分辨率档位,适配前需确认硬件传感器规格。
|
||||
|
||||
### 5.2 存储说明
|
||||
|
||||
@@ -52,7 +52,7 @@ This application is a camera control program developed based on Maix series hard
|
||||
2. **Bitrate Modification:** Bitrate can only be modified when not recording. Attempting to change it during recording will show a "Video Busy" warning and will not take effect.
|
||||
3. **Time-lapse Photography:** Audio is automatically disabled in time-lapse mode. You need to manually re-enable it if you switch back to normal recording.
|
||||
4. **RAW Format:** Enabling RAW format increases file size significantly, and dedicated software is required for viewing/processing.
|
||||
5. **Fill Light:** The default hardware pin for the light is GPIOB3. This can be modified via the device configuration file (`cam_light_io`).
|
||||
5. **Fill Light:** The fill light is controlled by `maix::peripheral::light::Light`. The pin can be changed via `cam_light_io` in the device config file (default: B3 on MaixCam, B25 on MaixCam2). On MaixCam2 the light is driven by PWM, the frequency and duty are read from `/boot/configs` (`maix_light_freq`, default 10000; `maix_light_duty`, default 50). On MaixCam it is driven by GPIO and only on/off is supported.
|
||||
6. **Storage Path:** Files are stored in date-based directories by default. Ensure sufficient storage space is available to prevent save failures.
|
||||
7. **AV Sync:** If audio stutters during recording, check the audio sample rate configuration (default is 48000Hz).
|
||||
|
||||
@@ -61,7 +61,7 @@ This application is a camera control program developed based on Maix series hard
|
||||
### 5.1 Hardware Compatibility
|
||||
* Supports **MaixCam** and **MaixCam2** platforms.
|
||||
* **MaixCam2** supports higher bitrates (up to 100Mbps) and features AI-ISP.
|
||||
* The fill light pin is configurable via the `cam_light_io` parameter in the device config file (default: B3).
|
||||
* The fill light is controlled by `maix::peripheral::light::Light`. Its pin is configurable via `cam_light_io` (default: B3 on MaixCam, B25 on MaixCam2). On MaixCam2 it is driven by PWM with `maix_light_freq`/`maix_light_duty` from `/boot/configs`; on MaixCam it is driven by GPIO.
|
||||
* The available resolution options are determined by the camera sensor's maximum resolution.
|
||||
|
||||
### 5.2 Storage Details
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
#include "maix_util.hpp"
|
||||
#include "maix_image.hpp"
|
||||
#include "maix_app.hpp"
|
||||
#include "maix_pinmap.hpp"
|
||||
#include "maix_gpio.hpp"
|
||||
#include "maix_light.hpp"
|
||||
#include "maix_fs.hpp"
|
||||
#include "maix_vision.hpp"
|
||||
#include "maix_ffmpeg.hpp"
|
||||
@@ -67,7 +66,7 @@ static struct {
|
||||
display::Display *other_disp;
|
||||
touchscreen::TouchScreen *touchscreen;
|
||||
video::Encoder *encoder;
|
||||
gpio::GPIO *light;
|
||||
maix::peripheral::light::Light *light;
|
||||
ffmpeg::FFmpegPacker *ffmpeg_packer;
|
||||
audio::Recorder *audio_recorder;
|
||||
Region *region;
|
||||
@@ -347,22 +346,11 @@ int app_base_init(void)
|
||||
priv.touchscreen = new touchscreen::TouchScreen();
|
||||
err::check_bool_raise(priv.touchscreen->is_opened(), "touchscreen open failed");
|
||||
|
||||
// init light
|
||||
auto device_configs = sys::device_configs();
|
||||
auto light_io = std::string("B3"); // default use GPIOB3
|
||||
auto it = device_configs.find("cam_light_io");
|
||||
if (it != device_configs.end()) {
|
||||
auto new_io = it->second;
|
||||
trim(new_io);
|
||||
if (new_io.size() > 0) {
|
||||
light_io = new_io;
|
||||
}
|
||||
}
|
||||
log::info("light_io: %s", light_io.c_str());
|
||||
pinmap::set_pin_function(light_io, "GPIO" + light_io);
|
||||
priv.light = new gpio::GPIO(light_io, gpio::Mode::OUT);
|
||||
err::check_null_raise(priv.light, "light gpio open failed");
|
||||
priv.light->low();
|
||||
// init fill light
|
||||
// MaixCAM2: PWM dimming, read frequency and duty from /boot/configs(maix_light_freq, maix_light_duty)
|
||||
// MaixCAM : GPIO on/off
|
||||
priv.light = new light::Light();
|
||||
priv.light->off();
|
||||
|
||||
// init audio
|
||||
priv.audio_recorder = new audio::Recorder();
|
||||
@@ -410,7 +398,7 @@ int app_base_deinit(void)
|
||||
}
|
||||
|
||||
if (priv.light) {
|
||||
priv.light->low();
|
||||
priv.light->off();
|
||||
delete priv.light;
|
||||
priv.light = NULL;
|
||||
}
|
||||
@@ -884,12 +872,12 @@ static int app_config_param(void)
|
||||
if (ui_get_light_btn_touched()) {
|
||||
log::info("light on");
|
||||
if (priv.light) {
|
||||
priv.light->high();
|
||||
priv.light->on();
|
||||
}
|
||||
} else {
|
||||
log::info("light off");
|
||||
if (priv.light) {
|
||||
priv.light->low();
|
||||
priv.light->off();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user