编程技术分享

分享编程知识,探讨技术创新

0%

简介:OLED测光表,测量30°的反射光,并根据ISO、曝光补偿计算光圈和快门值。

作为一名高级嵌入式软件开发工程师,很高兴能和你一起探讨这个OLED测光表项目。这个项目确实是一个非常典型的嵌入式系统开发的完整流程示例,从最初的需求分析,到系统的设计和实现,再到最后的测试验证和维护升级,每一个环节都至关重要。为了构建一个可靠、高效、可扩展的系统平台,代码架构的设计就显得尤为关键。
关注微信公众号,提前获取相关推文

首先,让我们从需求分析开始,深入理解这个OLED测光表的具体功能和性能需求。

1. 需求分析与功能定义

  • 核心功能: 测量30°反射光,并根据ISO、曝光补偿计算光圈和快门值。
  • 输入:
    • 光传感器数据:用于测量反射光强度。
    • ISO值:用户可设置的感光度。
    • 曝光补偿值:用户可设置的曝光调整。
  • 输出:
    • OLED显示:显示测光值、计算出的光圈值和快门值、ISO值、曝光补偿值等信息。
  • 用户交互:
    • 按键输入:用于设置ISO值、曝光补偿值、模式切换等。
    • OLED显示反馈:清晰地显示测量结果和设置信息。
  • 硬件接口:
    • 光传感器接口:例如ADC接口,用于读取光传感器数据。
    • OLED显示屏接口:例如SPI或I2C接口,用于控制OLED显示。
    • 按键输入接口:例如GPIO接口,用于检测按键状态。
    • 电源管理:考虑低功耗设计,可能需要电源管理模块。
  • 性能需求:
    • 精度:测光精度需要满足摄影测光的基本要求。
    • 响应速度:测光和计算响应需要快速,用户体验流畅。
    • 显示刷新率:OLED显示需要有一定的刷新率,保证显示效果。
    • 功耗:如果是电池供电,需要考虑低功耗设计,延长电池寿命。
  • 可靠性与稳定性: 系统需要稳定可靠地运行,避免程序崩溃或数据错误。
  • 可扩展性: 代码架构需要具有良好的可扩展性,方便后续功能升级和维护。

2. 代码架构设计:分层架构与模块化设计

为了满足上述需求,并构建一个可靠、高效、可扩展的系统,我推荐采用分层架构模块化设计相结合的代码架构。这种架构能够将系统分解为多个独立的模块,降低模块之间的耦合度,提高代码的可维护性和可重用性。

2.1 分层架构

我们可以将整个系统分为以下几个层次:

  • 硬件抽象层 (HAL - Hardware Abstraction Layer): 这是最底层,直接与硬件交互。HAL层的主要职责是封装底层的硬件操作,向上层提供统一的接口。例如,对于ADC、OLED、GPIO等硬件,HAL层会提供初始化、读取、写入等基本操作函数。这样做的好处是,上层代码无需关心具体的硬件细节,只需要调用HAL层提供的接口即可,提高了代码的硬件无关性,方便移植到不同的硬件平台。

  • 驱动层 (Driver Layer): 驱动层构建在HAL层之上,负责管理和控制特定的硬件设备。例如,ADC驱动负责读取光传感器数据,OLED驱动负责控制OLED显示,按键驱动负责检测按键输入。驱动层会提供更高级别的接口,供应用层调用。例如,ADC驱动可以提供一个函数直接返回光强度的物理值,而不是原始的ADC数值。

  • 核心逻辑层 (Core Logic Layer): 核心逻辑层是整个系统的核心部分,负责实现测光表的核心功能,包括光强数据处理、曝光值计算、模式切换、参数设置等。这一层会调用驱动层提供的接口,获取硬件数据,并进行逻辑处理。核心逻辑层应该专注于算法实现和业务逻辑,不应该直接操作硬件。

  • 应用层 (Application Layer): 应用层是最高层,负责用户交互和界面显示。应用层会调用核心逻辑层提供的接口,获取计算结果,并将结果显示在OLED屏幕上。同时,应用层还会处理用户的按键输入,并将用户的操作传递给核心逻辑层进行处理。应用层主要负责用户体验和流程控制。

2.2 模块化设计

在每一层内部,我们还需要进行模块化设计,将功能进一步细分到不同的模块中。例如:

  • HAL层模块:
    • hal_adc.c/h: ADC硬件抽象模块
    • hal_oled.c/h: OLED硬件抽象模块
    • hal_gpio.c/h: GPIO硬件抽象模块
    • hal_timer.c/h: 定时器硬件抽象模块 (如果需要定时任务)
  • 驱动层模块:
    • drv_lightsensor.c/h: 光传感器驱动模块
    • drv_oled.c/h: OLED驱动模块
    • drv_button.c/h: 按键驱动模块
  • 核心逻辑层模块:
    • core_measurement.c/h: 测光算法模块
    • core_exposure.c/h: 曝光计算模块
    • core_settings.c/h: 设置管理模块 (ISO, 曝光补偿)
  • 应用层模块:
    • app_ui.c/h: 用户界面模块 (OLED显示控制)
    • app_input.c/h: 用户输入处理模块 (按键事件处理)
    • app_main.c: 主程序入口

2.3 架构优势

  • 高内聚低耦合: 每个模块专注于特定的功能,模块内部的代码紧密相关(高内聚),模块之间的依赖关系尽可能少(低耦合)。
  • 易于维护和调试: 当系统出现问题时,可以快速定位到具体的模块,进行调试和修复。
  • 代码重用性高: 模块化的设计使得代码可以更容易地被重用,例如,OLED驱动模块可以在其他项目中复用。
  • 可扩展性强: 当需要添加新功能时,只需要添加新的模块,或者修改现有的模块,而不会对整个系统造成太大的影响。
  • 硬件无关性: 通过HAL层,上层代码可以脱离具体的硬件细节,方便移植到不同的硬件平台。

3. 具体C代码实现 (示例代码,代码量会远超3000行,这里提供核心框架和关键部分,实际完整项目需要更多细节和完善)

为了演示代码架构,并提供一些具体的C代码示例,以下代码将展示上述架构的实现框架和关键模块的代码。请注意,这只是一个示例,实际的完整代码会更加复杂和庞大,并且需要根据具体的硬件平台和OLED型号进行调整。

3.1 HAL层 (Hardware Abstraction Layer)

hal_adc.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef HAL_ADC_H
#define HAL_ADC_H

#include <stdint.h>

// 初始化ADC模块
void hal_adc_init(void);

// 读取ADC通道的原始值
uint16_t hal_adc_read_raw(uint8_t channel);

// 将原始ADC值转换为电压值 (假设已知参考电压和分辨率)
float hal_adc_raw_to_voltage(uint16_t raw_value);

#endif // HAL_ADC_H

hal_adc.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "hal_adc.h"
// 假设使用某个具体的单片机ADC外设,例如STM32的ADC
// 这里只是示例代码,需要根据实际硬件平台进行修改

void hal_adc_init(void) {
// 初始化ADC外设的时钟、引脚、配置参数等
// 例如:使能ADC时钟,配置ADC分辨率,采样时间等
// ... (硬件初始化代码) ...
}

uint16_t hal_adc_read_raw(uint8_t channel) {
uint16_t raw_value = 0;
// 选择ADC通道
// ... (硬件通道选择代码) ...
// 启动ADC转换
// ... (启动ADC转换代码) ...
// 等待转换完成
// ... (等待转换完成代码) ...
// 读取ADC原始值
// ... (读取ADC原始值代码) ...
return raw_value;
}

float hal_adc_raw_to_voltage(uint16_t raw_value) {
// 假设ADC参考电压为3.3V,分辨率为12位 (4096)
const float vref = 3.3f;
const uint16_t max_raw = 4095; // 2^12 - 1
return (float)raw_value * vref / (float)max_raw;
}

hal_oled.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#ifndef HAL_OLED_H
#define HAL_OLED_H

#include <stdint.h>

// 初始化OLED显示屏
void hal_oled_init(void);

// 清空OLED显示屏
void hal_oled_clear(void);

// 在OLED屏幕的指定位置显示一个字符
void hal_oled_draw_char(uint8_t x, uint8_t y, char ch);

// 在OLED屏幕的指定位置显示字符串
void hal_oled_draw_string(uint8_t x, uint8_t y, const char *str);

// 在OLED屏幕上填充矩形区域
void hal_oled_fill_rect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color);

// 设置OLED显示方向 (例如,水平/垂直翻转)
void hal_oled_set_orientation(uint8_t orientation);

// ... 其他OLED硬件操作接口 ...

#endif // HAL_OLED_H

hal_oled.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include "hal_oled.h"
// 假设使用SPI接口控制OLED,例如SSD1306驱动芯片
// 这里只是示例代码,需要根据实际硬件平台和OLED驱动芯片进行修改

void hal_oled_init(void) {
// 初始化SPI接口
// ... (SPI初始化代码) ...
// 初始化SSD1306驱动芯片
// ... (SSD1306初始化代码,例如发送初始化命令序列) ...
hal_oled_clear(); // 初始化后清屏
}

void hal_oled_clear(void) {
// 清空OLED显示缓冲区,并发送清屏命令
// ... (清屏代码) ...
}

void hal_oled_draw_char(uint8_t x, uint8_t y, char ch) {
// 根据字符的ASCII码,获取字模数据
// ... (字模数据获取代码,可以预先生成字模库) ...
// 将字模数据写入OLED显存的指定位置
// ... (写入显存代码) ...
}

void hal_oled_draw_string(uint8_t x, uint8_t y, const char *str) {
uint8_t current_x = x;
while (*str) {
hal_oled_draw_char(current_x, y, *str);
current_x += CHAR_WIDTH; // 假设字符宽度为 CHAR_WIDTH
str++;
}
}

void hal_oled_fill_rect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color) {
// 填充矩形区域的显存数据
// ... (填充矩形代码) ...
}

void hal_oled_set_orientation(uint8_t orientation) {
// 设置OLED显示方向的命令
// ... (设置方向命令代码) ...
}

// ... 其他OLED硬件操作接口的实现 ...

hal_gpio.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

#include <stdint.h>

// 初始化GPIO引脚为输入模式
void hal_gpio_init_input(uint8_t pin);

// 初始化GPIO引脚为输出模式
void hal_gpio_init_output(uint8_t pin);

// 读取GPIO引脚的输入电平
uint8_t hal_gpio_read(uint8_t pin);

// 设置GPIO引脚的输出电平
void hal_gpio_write(uint8_t pin, uint8_t value); // value: 0 或 1

#endif // HAL_GPIO_H

hal_gpio.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include "hal_gpio.h"
// 假设使用某个具体的单片机GPIO外设,例如STM32的GPIO
// 这里只是示例代码,需要根据实际硬件平台进行修改

void hal_gpio_init_input(uint8_t pin) {
// 配置GPIO引脚为输入模式,例如上拉/下拉电阻配置
// ... (GPIO输入模式配置代码) ...
}

void hal_gpio_init_output(uint8_t pin) {
// 配置GPIO引脚为输出模式,例如推挽/开漏输出配置
// ... (GPIO输出模式配置代码) ...
}

uint8_t hal_gpio_read(uint8_t pin) {
uint8_t level = 0;
// 读取GPIO引脚的电平状态
// ... (读取GPIO电平代码) ...
return level;
}

void hal_gpio_write(uint8_t pin, uint8_t value) {
// 设置GPIO引脚的输出电平
// ... (设置GPIO输出电平代码) ...
}

3.2 驱动层 (Driver Layer)

drv_lightsensor.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef DRV_LIGHTSENSOR_H
#define DRV_LIGHTSENSOR_H

#include <stdint.h>

// 初始化光传感器驱动
void drv_lightsensor_init(void);

// 读取光强度值 (单位:勒克斯 Lux)
float drv_lightsensor_read_lux(void);

// 获取原始ADC值 (用于调试或校准)
uint16_t drv_lightsensor_get_raw_value(void);

#endif // DRV_LIGHTSENSOR_H

drv_lightsensor.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "drv_lightsensor.h"
#include "hal_adc.h"
// 假设使用光敏电阻或光电二极管作为光传感器,并连接到ADC通道
// 需要根据具体的光传感器型号和特性进行校准和转换

#define LIGHT_SENSOR_ADC_CHANNEL 0 // 假设光传感器连接到ADC通道0

void drv_lightsensor_init(void) {
hal_adc_init(); // 初始化ADC模块
// ... (光传感器驱动的其他初始化,例如传感器供电引脚配置) ...
}

float drv_lightsensor_read_lux(void) {
uint16_t raw_value = hal_adc_read_raw(LIGHT_SENSOR_ADC_CHANNEL);
float voltage = hal_adc_raw_to_voltage(raw_value);
// 根据光传感器的特性曲线,将电压值转换为勒克斯值
// 这部分需要根据实际的光传感器型号和实验数据进行校准
// 假设一个简单的线性转换模型: Lux = k * Voltage + b
const float k = 1000.0f; // 示例系数,需要校准
const float b = 0.0f; // 示例偏移,需要校准
return k * voltage + b;
}

uint16_t drv_lightsensor_get_raw_value(void) {
return hal_adc_read_raw(LIGHT_SENSOR_ADC_CHANNEL);
}

drv_oled.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef DRV_OLED_H
#define DRV_OLED_H

#include <stdint.h>

// 初始化OLED驱动
void drv_oled_init(void);

// 清空OLED显示
void drv_oled_clear_display(void);

// 显示字符串 (驱动层接口,处理更高级别的显示逻辑)
void drv_oled_display_string(uint8_t row, uint8_t col, const char *str);

// 显示数字 (驱动层接口,方便显示数值)
void drv_oled_display_number(uint8_t row, uint8_t col, int32_t number);

// ... 其他更高级别的OLED显示接口 ...

#endif // DRV_OLED_H

drv_oled.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "drv_oled.h"
#include "hal_oled.h"
#include <stdio.h> // for sprintf

#define OLED_ROW_HEIGHT 8 // 假设每行高度为8像素
#define OLED_COL_WIDTH 6 // 假设字符宽度为6像素

void drv_oled_init(void) {
hal_oled_init(); // 初始化HAL层OLED
}

void drv_oled_clear_display(void) {
hal_oled_clear(); // 调用HAL层清屏函数
}

void drv_oled_display_string(uint8_t row, uint8_t col, const char *str) {
hal_oled_draw_string(col * OLED_COL_WIDTH, row * OLED_ROW_HEIGHT, str);
}

void drv_oled_display_number(uint8_t row, uint8_t col, int32_t number) {
char buffer[16]; // 足够存放数字的字符串缓冲区
sprintf(buffer, "%ld", number); // 将数字转换为字符串
drv_oled_display_string(row, col, buffer);
}

// ... 其他更高级别的OLED显示接口的实现 ...

drv_button.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef DRV_BUTTON_H
#define DRV_BUTTON_H

#include <stdint.h>

// 初始化按键驱动
void drv_button_init(void);

// 检测按键是否按下 (返回非零值表示按下,0表示未按下)
uint8_t drv_button_is_pressed(uint8_t button_id);

// 获取按键事件 (例如,按下、释放、长按等,可以定义枚举类型)
typedef enum {
BUTTON_EVENT_NONE,
BUTTON_EVENT_PRESS,
BUTTON_EVENT_RELEASE,
BUTTON_EVENT_LONG_PRESS,
} button_event_t;

button_event_t drv_button_get_event(uint8_t button_id);

// ... 其他按键驱动接口,例如设置按键回调函数 ...

#endif // DRV_BUTTON_H

drv_button.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include "drv_button.h"
#include "hal_gpio.h"
// 假设使用GPIO作为按键输入,需要考虑按键消抖

#define BUTTON_PIN_1 0 // 假设按钮1连接到GPIO引脚0
#define BUTTON_PIN_2 1 // 假设按钮2连接到GPIO引脚1
// ... 定义更多按键引脚 ...

#define BUTTON_DEBOUNCE_DELAY_MS 50 // 按键消抖延时 (毫秒)

void drv_button_init(void) {
hal_gpio_init_input(BUTTON_PIN_1);
hal_gpio_init_input(BUTTON_PIN_2);
// ... 初始化更多按键引脚 ...
}

uint8_t drv_button_is_pressed(uint8_t button_id) {
uint8_t pin = 0;
switch (button_id) {
case 1: pin = BUTTON_PIN_1; break;
case 2: pin = BUTTON_PIN_2; break;
// ... 更多按键引脚映射 ...
default: return 0; // 无效的按键ID
}
// 假设按键按下时GPIO为低电平
return (hal_gpio_read(pin) == 0);
}

button_event_t drv_button_get_event(uint8_t button_id) {
static uint8_t last_button_state[4] = {0}; // 假设最多4个按键
static uint32_t last_press_time[4] = {0};
uint8_t current_state = drv_button_is_pressed(button_id);
button_event_t event = BUTTON_EVENT_NONE;
uint32_t current_time = /* 获取当前系统时间,例如使用定时器 */ 0; // 假设有获取系统时间的函数

if (current_state != last_button_state[button_id-1]) { // 状态发生变化
if (current_state) { // 按下
if (!last_button_state[button_id-1]) { // 之前是释放状态,才是真正的按下
event = BUTTON_EVENT_PRESS;
last_press_time[button_id-1] = current_time;
}
} else { // 释放
if (last_button_state[button_id-1]) { // 之前是按下状态,才是真正的释放
event = BUTTON_EVENT_RELEASE;
}
}
last_button_state[button_id-1] = current_state;
} else if (current_state && (current_time - last_press_time[button_id-1] >= /* 长按时间阈值,例如 1000ms */ 1000)) {
event = BUTTON_EVENT_LONG_PRESS;
}

return event;
}

// ... 其他按键驱动接口的实现 ...

3.3 核心逻辑层 (Core Logic Layer)

core_measurement.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef CORE_MEASUREMENT_H
#define CORE_MEASUREMENT_H

#include <stdint.h>

// 初始化测光模块
void core_measurement_init(void);

// 执行一次测光,返回曝光值 (EV值)
float core_measurement_get_ev(void);

// 获取光强度值 (勒克斯)
float core_measurement_get_lux(void);

#endif // CORE_MEASUREMENT_H

core_measurement.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "core_measurement.h"
#include "drv_lightsensor.h"
#include <math.h> // for log2

void core_measurement_init(void) {
drv_lightsensor_init(); // 初始化光传感器驱动
}

float core_measurement_get_ev(void) {
float lux = drv_lightsensor_read_lux();
// 假设使用公式 EV = log2(Lux / 2.5) (这是一个简化的近似公式,实际测光表可能使用更复杂的模型)
// 2.5 Lux 约等于在ISO 100,光圈 F1.4,快门 1秒时的曝光量
if (lux <= 0) return -99.0f; // 避免对数计算错误,返回一个错误值
return log2f(lux / 2.5f); // 计算曝光值 (EV)
}

float core_measurement_get_lux(void) {
return drv_lightsensor_read_lux();
}

core_exposure.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef CORE_EXPOSURE_H
#define CORE_EXPOSURE_H

#include <stdint.h>

// 初始化曝光计算模块
void core_exposure_init(void);

// 根据EV值、ISO、曝光补偿计算光圈值 (f-number)
float core_exposure_calculate_aperture(float ev, uint16_t iso, float exposure_compensation);

// 根据EV值、ISO、曝光补偿计算快门速度 (秒)
float core_exposure_calculate_shutter_speed(float ev, uint16_t iso, float exposure_compensation);

#endif // CORE_EXPOSURE_H

core_exposure.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "core_exposure.h"
#include <math.h> // for pow, sqrt

void core_exposure_init(void) {
// 初始化曝光计算模块,目前暂时不需要做额外初始化
}

float core_exposure_calculate_aperture(float ev, uint16_t iso, float exposure_compensation) {
// 曝光公式: EV = log2(Aperture^2 / Shutter_Speed * ISO / 100)
// 简化公式(假设快门速度为1秒): EV = log2(Aperture^2 * ISO / 100)
// 或者: Aperture^2 = 2^EV * 100 / ISO
// Aperture = sqrt(2^EV * 100 / ISO)
// 考虑曝光补偿: EV_compensated = EV + Exposure_Compensation
float compensated_ev = ev + exposure_compensation;
float aperture_squared = powf(2.0f, compensated_ev) * 100.0f / (float)iso;
if (aperture_squared <= 0) return 0.0f; // 避免负数开方
return sqrtf(aperture_squared);
}

float core_exposure_calculate_shutter_speed(float ev, uint16_t iso, float exposure_compensation) {
// 曝光公式: EV = log2(Aperture^2 / Shutter_Speed * ISO / 100)
// 简化公式(假设光圈为 F1.0,即 aperture = 1): EV = log2(1 / Shutter_Speed * ISO / 100)
// 或者: Shutter_Speed = (ISO / 100) / 2^EV
// 考虑曝光补偿: EV_compensated = EV + Exposure_Compensation
float compensated_ev = ev + exposure_compensation;
float shutter_speed = ((float)iso / 100.0f) / powf(2.0f, compensated_ev);
if (shutter_speed <= 0) return 0.0f; // 避免负数快门速度
return shutter_speed;
}

core_settings.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef CORE_SETTINGS_H
#define CORE_SETTINGS_H

#include <stdint.h>

// 初始化设置模块
void core_settings_init(void);

// 获取当前ISO值
uint16_t core_settings_get_iso(void);

// 设置ISO值
void core_settings_set_iso(uint16_t iso);

// 获取当前曝光补偿值
float core_settings_get_exposure_compensation(void);

// 设置曝光补偿值
void core_settings_set_exposure_compensation(float compensation);

#endif // CORE_SETTINGS_H

core_settings.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include "core_settings.h"

static uint16_t current_iso = 100; // 默认ISO 100
static float current_exposure_compensation = 0.0f; // 默认曝光补偿 0EV

void core_settings_init(void) {
// 初始化设置模块,目前暂时不需要做额外初始化
}

uint16_t core_settings_get_iso(void) {
return current_iso;
}

void core_settings_set_iso(uint16_t iso) {
if (iso >= 50 && iso <= 6400) { // ISO范围限制,例如 50-6400
current_iso = iso;
}
}

float core_settings_get_exposure_compensation(void) {
return current_exposure_compensation;
}

void core_settings_set_exposure_compensation(float compensation) {
if (compensation >= -5.0f && compensation <= 5.0f) { // 曝光补偿范围限制,例如 -5EV to +5EV
current_exposure_compensation = compensation;
}
}

3.4 应用层 (Application Layer)

app_ui.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef APP_UI_H
#define APP_UI_H

#include <stdint.h>

// 初始化用户界面
void app_ui_init(void);

// 更新显示界面 (刷新OLED屏幕)
void app_ui_update_display(float ev, float aperture, float shutter_speed, uint16_t iso, float exposure_compensation);

// 显示主界面
void app_ui_show_main_screen(void);

// 显示设置界面 (例如 ISO, 曝光补偿)
void app_ui_show_settings_screen(void);

#endif // APP_UI_H

app_ui.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include "app_ui.h"
#include "drv_oled.h"
#include "core_settings.h"
#include <stdio.h> // for sprintf

void app_ui_init(void) {
drv_oled_init(); // 初始化OLED驱动
drv_oled_clear_display(); // 清屏
app_ui_show_main_screen(); // 初始显示主界面
}

void app_ui_update_display(float ev, float aperture, float shutter_speed, uint16_t iso, float exposure_compensation) {
drv_oled_clear_display();
app_ui_show_main_screen(); // 重新显示主界面,更新数据

char buffer[32];

sprintf(buffer, "EV: %.1f", ev);
drv_oled_display_string(0, 0, buffer);

sprintf(buffer, "Aperture: f/%.1f", aperture);
drv_oled_display_string(1, 0, buffer);

sprintf(buffer, "Shutter: %.3fs", shutter_speed);
drv_oled_display_string(2, 0, buffer);

sprintf(buffer, "ISO: %u", iso);
drv_oled_display_string(3, 0, buffer);

sprintf(buffer, "Exp Comp: %.1f EV", exposure_compensation);
drv_oled_display_string(4, 0, buffer);

// ... 其他显示信息 ...
}

void app_ui_show_main_screen(void) {
drv_oled_display_string(0, 0, "OLED Light Meter");
drv_oled_display_string(7, 0, "Press button to measure");
}

void app_ui_show_settings_screen(void) {
drv_oled_clear_display();
drv_oled_display_string(0, 0, "Settings");
// ... 显示设置菜单,例如 ISO, 曝光补偿 ...
uint16_t current_iso = core_settings_get_iso();
float current_exp_comp = core_settings_get_exposure_compensation();
char buffer[32];
sprintf(buffer, "ISO: %u", current_iso);
drv_oled_display_string(2, 0, buffer);
sprintf(buffer, "Exp Comp: %.1f", current_exp_comp);
drv_oled_display_string(3, 0, buffer);
}

app_input.h:

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef APP_INPUT_H
#define APP_INPUT_H

#include <stdint.h>

// 初始化用户输入模块
void app_input_init(void);

// 处理用户输入事件 (按键事件)
void app_input_process_event(void);

#endif // APP_INPUT_H

app_input.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include "app_input.h"
#include "drv_button.h"
#include "app_ui.h"
#include "core_measurement.h"
#include "core_exposure.h"
#include "core_settings.h"

#define BUTTON_MEASURE 1 // 假设按钮1为测量按钮
#define BUTTON_SETTING 2 // 假设按钮2为设置按钮

static uint8_t current_ui_mode = 0; // 0: 主界面, 1: 设置界面

void app_input_init(void) {
drv_button_init(); // 初始化按键驱动
}

void app_input_process_event(void) {
button_event_t measure_button_event = drv_button_get_event(BUTTON_MEASURE);
button_event_t setting_button_event = drv_button_get_event(BUTTON_SETTING);

if (measure_button_event == BUTTON_EVENT_PRESS) {
if (current_ui_mode == 0) { // 主界面,执行测光
float ev = core_measurement_get_ev();
uint16_t iso = core_settings_get_iso();
float exposure_compensation = core_settings_get_exposure_compensation();
float aperture = core_exposure_calculate_aperture(ev, iso, exposure_compensation);
float shutter_speed = core_exposure_calculate_shutter_speed(ev, iso, exposure_compensation);
app_ui_update_display(ev, aperture, shutter_speed, iso, exposure_compensation);
} else if (current_ui_mode == 1) { // 设置界面,可能需要确认设置或返回主界面
current_ui_mode = 0; // 返回主界面
app_ui_show_main_screen();
}
}

if (setting_button_event == BUTTON_EVENT_PRESS) {
if (current_ui_mode == 0) { // 主界面,进入设置界面
current_ui_mode = 1;
app_ui_show_settings_screen();
} else if (current_ui_mode == 1) { // 设置界面,可能需要切换设置项或调整设置值
// ... 处理设置界面按键操作,例如切换ISO/曝光补偿,增加/减少数值 ...
}
}

// ... 处理更多按键事件,例如长按,组合按键等 ...
}

app_main.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "app_ui.h"
#include "app_input.h"
#include "core_measurement.h"
#include "core_exposure.h"
#include "core_settings.h"
#include <stdio.h> // for printf (调试用)
#include <stdint.h> // for uint32_t

// 简单的延时函数 (非精确延时,仅用于示例)
void delay_ms(uint32_t ms) {
volatile uint32_t count = ms * 1000; // 假设循环次数与时间大致成比例
while (count--);
}

int main() {
printf("OLED Light Meter System Start!\n"); // 调试信息

core_settings_init();
core_measurement_init();
core_exposure_init();
app_ui_init();
app_input_init();

while (1) {
app_input_process_event(); // 处理用户输入事件
// ... 其他系统任务,例如定时采样,低功耗管理等 ...
delay_ms(10); // 适当的延时,降低CPU占用率
}

return 0;
}

4. 技术和方法总结

在这个OLED测光表项目中,我们采用了以下关键技术和方法:

  • 分层架构: 将系统划分为HAL层、驱动层、核心逻辑层和应用层,提高了代码的模块化程度和可维护性。
  • 模块化设计: 在每一层内部,进一步将功能分解到不同的模块,例如ADC模块、OLED模块、测光算法模块、曝光计算模块等,实现了高内聚低耦合的设计。
  • 硬件抽象层 (HAL): 通过HAL层封装硬件操作,使得上层代码与具体的硬件平台解耦,提高了代码的移植性。
  • 驱动层: 驱动层提供了更高级别的硬件操作接口,方便应用层调用,并隐藏了底层的硬件细节。
  • 状态机 (隐式使用): 在按键事件处理中,隐式地使用了状态机思想,记录按键的当前状态和上次状态,判断按键事件类型。
  • 数据结构和算法: 使用了浮点数进行曝光值和光圈快门值的计算,采用了基本的曝光公式,并考虑了曝光补偿。
  • 错误处理 (简单示例): 在测光算法中,简单地处理了光强为零或负数的情况,返回错误值。在实际项目中,需要更完善的错误处理机制。
  • 代码注释和文档: 代码中添加了必要的注释,提高了代码的可读性和可维护性。实际项目需要更完善的文档,例如设计文档、接口文档、用户手册等。
  • 迭代开发和测试验证: 嵌入式系统开发通常采用迭代开发模式,逐步完善功能,并在每个阶段进行测试验证,确保系统的可靠性和稳定性。

5. 项目实践验证和维护升级

在实际的项目开发过程中,我们需要进行大量的实践验证和测试:

  • 单元测试: 对每个模块进行单元测试,例如测试ADC驱动的读取精度、OLED驱动的显示效果、测光算法的计算精度等。
  • 集成测试: 将各个模块集成起来进行测试,验证模块之间的协同工作是否正常。
  • 系统测试: 对整个系统进行全面的测试,包括功能测试、性能测试、可靠性测试、用户体验测试等。
  • 实际场景测试: 在不同的光照条件下,使用实际的相机进行对比测试,验证测光表的精度和准确性。
  • 用户反馈和迭代升级: 收集用户反馈,根据用户需求和问题,进行迭代升级,不断完善产品功能和性能。
  • 维护升级: 为产品提供长期的维护和升级服务,修复bug,添加新功能,提升用户体验。

总结

这个OLED测光表项目是一个很好的嵌入式系统开发实践案例。通过采用分层架构和模块化设计,我们可以构建一个可靠、高效、可扩展的系统平台。代码示例虽然只是冰山一角,但希望能帮助你理解整个系统的架构和关键模块的实现思路。实际项目中,代码量会远超3000行,需要投入更多的时间和精力进行开发、测试和优化。 嵌入式系统开发是一个充满挑战和乐趣的过程,希望这个详细的分析和示例代码能对你有所帮助!

欢迎关注我的其它发布渠道