编程技术分享

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

0%

简介:基于ESP32的MPPT,将原有的ADC改为2颗INA226

好的,作为一名高级嵌入式软件开发工程师,我将针对您提供的基于ESP32的MPPT太阳能充电控制器的项目,详细阐述最适合的代码设计架构,并提供具体的C代码实现。本项目将INA226电流/电压传感器替代了原有的ADC,这在精度和功能上都有了显著提升。
关注微信公众号,提前获取相关推文

项目概述:基于ESP32和INA226的MPPT太阳能充电控制器

本项目旨在设计并实现一个高效、可靠、可扩展的太阳能充电控制器,核心技术是最大功率点跟踪 (MPPT) 算法。该控制器将使用ESP32作为主控芯片,并采用两颗INA226高精度电流/电压传感器来精确测量太阳能电池板和电池的电压、电流。通过MPPT算法,控制器能够持续追踪太阳能电池板的最大功率点,从而最大化能量转换效率,为电池高效充电。

嵌入式系统开发流程

一个完整的嵌入式系统开发流程通常包括以下几个阶段:

  1. 需求分析阶段:

    • 明确产品功能和性能指标: 例如,MPPT效率目标、充电电流范围、电池类型支持、通信需求(如Wi-Fi监控)、保护功能(过压、过流、过温等)。
    • 确定硬件平台: ESP32作为主控,INA226作为传感器,以及功率器件(MOSFET、二极管、电感等)的选择。
    • 软件需求分析: 确定需要实现的软件模块,例如MPPT算法模块、传感器驱动模块、控制模块、保护模块、通信模块等。
    • 环境和约束条件: 工作温度范围、功耗限制、成本预算、开发周期等。
  2. 系统设计阶段:

    • 硬件系统设计: 包括电路原理图设计、PCB布局设计、元器件选型等。关键是保证硬件电路的稳定性和可靠性,以及满足系统性能需求。
    • 软件系统架构设计: 确定软件的模块划分、模块间的接口、数据流向、控制流程、任务调度策略、异常处理机制等。选择合适的代码架构至关重要,直接影响系统的可维护性、可扩展性和可靠性。
  3. 编码实现阶段:

    • 编写驱动程序: 实现对硬件的控制,例如INA226传感器的I2C驱动、PWM输出控制DC-DC转换器、GPIO控制等。
    • 实现核心算法: 编写MPPT算法的核心代码,以及其他功能模块的代码,例如保护算法、通信协议等。
    • 代码优化: 注重代码的效率和资源占用,特别是在嵌入式系统中,资源通常有限。
  4. 测试验证阶段:

    • 单元测试: 对每个软件模块进行独立测试,确保模块功能的正确性。
    • 集成测试: 将各个模块组合起来进行测试,验证模块间的接口和协作是否正常。
    • 系统测试: 对整个系统进行全面测试,包括功能测试、性能测试、稳定性测试、可靠性测试、环境测试等。
    • 硬件在环测试 (HIL): 使用仿真环境模拟真实的工作场景,对硬件和软件进行联合测试。
  5. 维护升级阶段:

    • 缺陷修复: 及时修复测试阶段和用户反馈的缺陷。
    • 功能升级: 根据需求增加新功能,例如远程监控、数据分析、更高级的MPPT算法等。
    • 性能优化: 持续优化系统性能,例如提高MPPT效率、降低功耗、提升响应速度等。
    • 固件升级: 提供方便的固件升级机制,例如OTA (Over-The-Air) 在线升级,方便用户更新系统。

最适合的代码设计架构:分层模块化架构

对于嵌入式系统,特别是像MPPT太阳能充电控制器这样功能较为复杂的系统,分层模块化架构是最适合的选择。这种架构具有以下优点:

  • 高内聚低耦合: 每个模块负责特定的功能,模块内部的代码高度相关,模块之间的依赖性低,易于维护和修改。
  • 可重用性: 模块化的设计使得代码更容易重用,例如传感器驱动模块、通信模块等可以在不同的项目中复用。
  • 可扩展性: 当需要增加新功能时,只需添加新的模块,而不需要修改已有的模块,系统扩展性好。
  • 易于测试: 模块化的设计使得单元测试更加容易,可以独立测试每个模块的功能。
  • 易于理解和维护: 清晰的模块划分和层次结构使得代码更容易理解和维护,降低了开发和维护成本。

分层模块化架构的具体划分

在本MPPT项目中,我们可以将软件系统划分为以下几个层次和模块:

  1. 硬件抽象层 (HAL - Hardware Abstraction Layer):

    • INA226驱动模块 (HAL_INA226): 封装对INA226传感器的I2C通信操作,提供读取电压、电流、功率等数据的API。
    • PWM驱动模块 (HAL_PWM): 封装ESP32的PWM功能,提供控制DC-DC转换器开关频率和占空比的API。
    • GPIO驱动模块 (HAL_GPIO): 封装ESP32的GPIO功能,用于控制指示灯、风扇、继电器等外围设备。
    • I2C驱动模块 (HAL_I2C): 底层I2C通信驱动,供INA226驱动模块调用。
    • 定时器驱动模块 (HAL_TIMER): 提供定时器功能,用于周期性任务调度、MPPT算法采样等。
  2. 设备驱动层 (Device Driver Layer):

    • 传感器管理模块 (DRV_SENSOR): 管理INA226传感器,包括初始化、数据读取、数据校验等,向上层提供统一的传感器数据接口。
    • DC-DC控制模块 (DRV_DCDC): 控制DC-DC转换器的开关,根据MPPT算法的输出调整PWM占空比,实现电压和电流的调节。
    • 保护模块 (DRV_PROTECT): 实现过压保护、过流保护、过温保护等功能,监控系统状态,并在异常情况下采取保护措施。
  3. 应用层 (Application Layer):

    • MPPT算法模块 (APP_MPPT): 实现MPPT核心算法,例如扰动观察法 (P&O)、增量电导法 (IC) 等。根据传感器数据计算最佳功率点,并输出PWM占空比控制信号。
    • 充电控制模块 (APP_CHARGE): 管理充电过程,包括充电状态机、充电模式选择、充电参数设置、电池类型管理等。
    • 系统管理模块 (APP_SYSTEM): 负责系统初始化、任务调度、错误处理、日志记录、状态监控等系统级功能。
    • 通信模块 (APP_COMM): 实现与上位机或云平台的通信,例如Wi-Fi通信、MQTT协议等,用于远程监控和数据上报。
  4. 接口层 (Interface Layer):

    • 配置接口 (CONFIG): 提供系统配置参数的接口,例如传感器参数、MPPT参数、充电参数、通信参数等,方便用户配置和修改。
    • 数据接口 (DATA_INTERFACE): 定义模块间数据传递的接口,例如传感器数据结构、MPPT算法输入输出数据结构等。
    • API接口 (API): 向上层应用提供API接口,例如获取系统状态、设置充电模式、发送控制指令等。

C代码实现 (示例,代码量不足3000行,需要根据实际项目进行扩展和完善)

为了演示分层模块化架构,并提供一些具体的C代码,以下是一个简化的代码示例,涵盖了关键模块的框架和部分实现。请注意,这只是一个框架,实际项目中需要根据具体需求进行详细设计和编码,并完善错误处理、异常情况处理、性能优化等方面。

1. config.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
27
#ifndef CONFIG_H
#define CONFIG_H

// --- 硬件配置 ---
#define INA226_I2C_ADDR_1 0x40 // INA226 #1 I2C地址 (太阳能板侧)
#define INA226_I2C_ADDR_2 0x41 // INA226 #2 I2C地址 (电池侧)
#define PWM_PIN 27 // PWM输出引脚
#define PWM_FREQ 20000 // PWM频率 (Hz)
#define LED_PIN 2 // LED指示灯引脚

// --- MPPT 配置 ---
#define MPPT_SAMPLE_PERIOD_MS 100 // MPPT采样周期 (ms)
#define MPPT_STEP_SIZE 0.01 // PWM占空比调整步长
#define MPPT_VOLTAGE_THRESHOLD 12.0 // 最小工作电压阈值

// --- 电池配置 ---
#define BATTERY_TYPE_LITHIUM 0
#define BATTERY_TYPE_LEAD_ACID 1
#define BATTERY_TYPE BATTERY_TYPE_LITHIUM // 电池类型
#define BATTERY_MAX_VOLTAGE 14.6 // 最大充电电压
#define BATTERY_MIN_VOLTAGE 10.5 // 最低放电电压
#define BATTERY_CHARGE_CURRENT_MAX 5.0 // 最大充电电流 (A)

// --- 系统配置 ---
#define SYSTEM_LOOP_PERIOD_MS 10 // 系统主循环周期 (ms)

#endif // CONFIG_H

2. hal_i2c.hhal_i2c.c (HAL I2C驱动)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// hal_i2c.h
#ifndef HAL_I2C_H
#define HAL_I2C_H

#include <stdint.h>
#include <stdbool.h>

typedef enum {
I2C_OK = 0,
I2C_ERROR,
I2C_TIMEOUT
} i2c_status_t;

i2c_status_t hal_i2c_init(i2c_port_t i2c_num, int scl_gpio, int sda_gpio, uint32_t clock_speed_hz);
i2c_status_t hal_i2c_write_byte(i2c_port_t i2c_num, uint8_t dev_addr, uint8_t reg_addr, uint8_t data);
i2c_status_t hal_i2c_read_byte(i2c_port_t i2c_num, uint8_t dev_addr, uint8_t reg_addr, uint8_t *data);
i2c_status_t hal_i2c_read_bytes(i2c_port_t i2c_num, uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, size_t data_len);

#endif // HAL_I2C_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
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// hal_i2c.c
#include "hal_i2c.h"
#include "driver/i2c.h"
#include "esp_log.h"

static const char *TAG_I2C = "HAL_I2C";

i2c_status_t hal_i2c_init(i2c_port_t i2c_num, int scl_gpio, int sda_gpio, uint32_t clock_speed_hz) {
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = sda_gpio,
.scl_io_num = scl_gpio,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = clock_speed_hz,
};
esp_err_t ret = i2c_param_config(i2c_num, &conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG_I2C, "I2C param config failed, error=%d", ret);
return I2C_ERROR;
}
ret = i2c_driver_install(i2c_num, conf.mode, 0, 0, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG_I2C, "I2C driver install failed, error=%d", ret);
return I2C_ERROR;
}
return I2C_OK;
}

i2c_status_t hal_i2c_write_byte(i2c_port_t i2c_num, uint8_t dev_addr, uint8_t reg_addr, uint8_t data) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg_addr, true);
i2c_master_write_byte(cmd, data, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, pdMS_TO_TICKS(1000));
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
ESP_LOGE(TAG_I2C, "I2C write byte failed, error=%d", ret);
return I2C_ERROR;
}
return I2C_OK;
}

i2c_status_t hal_i2c_read_byte(i2c_port_t i2c_num, uint8_t dev_addr, uint8_t reg_addr, uint8_t *data) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg_addr, true);
i2c_master_start(cmd); // Repeated start for read
i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_READ, true);
i2c_master_read_byte(cmd, data, I2C_MASTER_NACK); // NACK for single byte read
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, pdMS_TO_TICKS(1000));
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
ESP_LOGE(TAG_I2C, "I2C read byte failed, error=%d", ret);
return I2C_ERROR;
}
return I2C_OK;
}

i2c_status_t hal_i2c_read_bytes(i2c_port_t i2c_num, uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, size_t data_len) {
if (data_len == 0) return I2C_OK;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg_addr, true);
i2c_master_start(cmd); // Repeated start for read
i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_READ, true);
if (data_len > 1) {
i2c_master_read(cmd, data, data_len - 1, I2C_MASTER_ACK); // ACK for all but last byte
}
i2c_master_read_byte(cmd, data + data_len - 1, I2C_MASTER_NACK); // NACK for last byte
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_num, cmd, pdMS_TO_TICKS(1000));
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
ESP_LOGE(TAG_I2C, "I2C read bytes failed, error=%d", ret);
return I2C_ERROR;
}
return I2C_OK;
}

3. hal_ina226.hhal_ina226.c (HAL INA226驱动)

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
// hal_ina226.h
#ifndef HAL_INA226_H
#define HAL_INA226_H

#include <stdint.h>
#include <stdbool.h>
#include "hal_i2c.h"

typedef struct {
float shunt_voltage_mV;
float bus_voltage_V;
float current_mA;
float power_mW;
} ina226_data_t;

typedef enum {
INA226_OK = 0,
INA226_ERROR,
INA226_TIMEOUT
} ina226_status_t;

ina226_status_t hal_ina226_init(i2c_port_t i2c_num, uint8_t dev_addr);
ina226_status_t hal_ina226_read_data(i2c_port_t i2c_num, uint8_t dev_addr, ina226_data_t *data);

#endif // HAL_INA226_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
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
56
57
58
59
60
61
62
63
64
65
66
67
68
// hal_ina226.c
#include "hal_ina226.h"
#include "hal_i2c.h"
#include "esp_log.h"
#include <math.h>

static const char *TAG_INA226 = "HAL_INA226";

#define INA226_REG_CONFIG 0x00
#define INA226_REG_SHUNT_VOLTAGE 0x01
#define INA226_REG_BUS_VOLTAGE 0x02
#define INA226_REG_POWER 0x03
#define INA226_REG_CURRENT 0x04
#define INA226_REG_CALIBRATION 0x05

#define INA226_CONFIG_RESET_MASK (1 << 15)
#define INA226_CONFIG_AVG_1 (0 << 9) // 1 sample
#define INA226_CONFIG_AVG_4 (1 << 9) // 4 samples
#define INA226_CONFIG_AVG_16 (2 << 9) // 16 samples
#define INA226_CONFIG_AVG_64 (3 << 9) // 64 samples
#define INA226_CONFIG_AVG_128 (4 << 9) // 128 samples
#define INA226_CONFIG_AVG_256 (5 << 9) // 256 samples
#define INA226_CONFIG_AVG_512 (6 << 9) // 512 samples
#define INA226_CONFIG_AVG_1024 (7 << 9) // 1024 samples
#define INA226_CONFIG_VBUSCT_CONT (3 << 6) // Continuous bus voltage conversion
#define INA226_CONFIG_VSHCT_CONT (3 << 3) // Continuous shunt voltage conversion
#define INA226_CONFIG_MODE_CONT_SH_BUS (7 << 0) // Continuous shunt and bus voltage

#define INA226_CALIBRATION_VALUE 8192 // Example calibration value (adjust based on shunt resistor)

ina226_status_t hal_ina226_init(i2c_port_t i2c_num, uint8_t dev_addr) {
uint16_t config_reg = INA226_CONFIG_RESET_MASK | INA226_CONFIG_AVG_128 | INA226_CONFIG_VBUSCT_CONT | INA226_CONFIG_VSHCT_CONT | INA226_CONFIG_MODE_CONT_SH_BUS;
uint8_t config_data[2] = {(config_reg >> 8) & 0xFF, config_reg & 0xFF};
if (hal_i2c_write_byte(i2c_num, dev_addr, INA226_REG_CONFIG, config_data[0]) != I2C_OK ||
hal_i2c_write_byte(i2c_num, dev_addr, INA226_REG_CONFIG + 1, config_data[1]) != I2C_OK) {
ESP_LOGE(TAG_INA226, "Failed to write config register");
return INA226_ERROR;
}

uint8_t calib_data[2] = {(INA226_CALIBRATION_VALUE >> 8) & 0xFF, INA226_CALIBRATION_VALUE & 0xFF};
if (hal_i2c_write_byte(i2c_num, dev_addr, INA226_REG_CALIBRATION, calib_data[0]) != I2C_OK ||
hal_i2c_write_byte(i2c_num, dev_addr, INA226_REG_CALIBRATION + 1, calib_data[1]) != I2C_OK) {
ESP_LOGE(TAG_INA226, "Failed to write calibration register");
return INA226_ERROR;
}

return INA226_OK;
}

ina226_status_t hal_ina226_read_data(i2c_port_t i2c_num, uint8_t dev_addr, ina226_data_t *data) {
uint8_t raw_data[6]; // Shunt Voltage (2 bytes), Bus Voltage (2 bytes), Current (2 bytes)

if (hal_i2c_read_bytes(i2c_num, dev_addr, INA226_REG_SHUNT_VOLTAGE, raw_data, 6) != I2C_OK) {
ESP_LOGE(TAG_INA226, "Failed to read sensor data");
return INA226_ERROR;
}

int16_t shunt_voltage_raw = (raw_data[0] << 8) | raw_data[1];
uint16_t bus_voltage_raw = (raw_data[2] << 8) | raw_data[3];
int16_t current_raw = (raw_data[4] << 8) | raw_data[5];

data->shunt_voltage_mV = shunt_voltage_raw * 2.5f / 100.0f; // LSB = 2.5uV, convert to mV
data->bus_voltage_V = bus_voltage_raw * 1.25f / 1000.0f * 16.0f; // LSB = 1.25mV, convert to V, *16 for actual voltage
data->current_mA = current_raw * 0.1f; // LSB = 0.1mA, convert to mA (adjust based on calibration)
data->power_mW = data->bus_voltage_V * data->current_mA; // Power calculation (mW)

return INA226_OK;
}

4. hal_pwm.hhal_pwm.c (HAL PWM驱动)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// hal_pwm.h
#ifndef HAL_PWM_H
#define HAL_PWM_H

#include <stdint.h>
#include <stdbool.h>

typedef enum {
PWM_OK = 0,
PWM_ERROR,
PWM_TIMEOUT
} pwm_status_t;

pwm_status_t hal_pwm_init(int pwm_pin, uint32_t freq_hz);
pwm_status_t hal_pwm_set_duty_cycle(int pwm_pin, float duty_cycle); // Duty cycle 0.0 - 1.0

#endif // HAL_PWM_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
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
// hal_pwm.c
#include "hal_pwm.h"
#include "driver/ledc.h"
#include "esp_log.h"

static const char *TAG_PWM = "HAL_PWM";

pwm_status_t hal_pwm_init(int pwm_pin, uint32_t freq_hz) {
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_HIGH_SPEED_MODE,
.timer_num = LEDC_TIMER_0,
.duty_resolution = LEDC_TIMER_10_BIT, // 10-bit resolution (0-1023)
.freq_hz = freq_hz,
.clk_cfg = LEDC_AUTO_CLK,
};
esp_err_t ret = ledc_timer_config(&ledc_timer);
if (ret != ESP_OK) {
ESP_LOGE(TAG_PWM, "LEDC timer config failed, error=%d", ret);
return PWM_ERROR;
}

ledc_channel_config_t ledc_channel = {
.speed_mode = LEDC_HIGH_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.timer_sel = LEDC_TIMER_0,
.intr_type = LEDC_INTR_DISABLE,
.gpio_num = pwm_pin,
.duty = 0, // Initial duty cycle 0%
.hpoint = 0,
};
ret = ledc_channel_config(&ledc_channel);
if (ret != ESP_OK) {
ESP_LOGE(TAG_PWM, "LEDC channel config failed, error=%d", ret);
return PWM_ERROR;
}
return PWM_OK;
}

pwm_status_t hal_pwm_set_duty_cycle(int pwm_pin, float duty_cycle) {
if (duty_cycle < 0.0f || duty_cycle > 1.0f) {
ESP_LOGE(TAG_PWM, "Invalid duty cycle value: %f", duty_cycle);
return PWM_ERROR;
}
uint32_t duty = (uint32_t)(duty_cycle * 1023); // 10-bit resolution
esp_err_t ret = ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, duty);
if (ret != ESP_OK) {
ESP_LOGE(TAG_PWM, "LEDC set duty failed, error=%d", ret);
return PWM_ERROR;
}
ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0);
return PWM_OK;
}

5. drv_sensor.hdrv_sensor.c (设备驱动 - 传感器管理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// drv_sensor.h
#ifndef DRV_SENSOR_H
#define DRV_SENSOR_H

#include <stdint.h>
#include <stdbool.h>
#include "hal_ina226.h"

typedef struct {
ina226_data_t solar_panel_data;
ina226_data_t battery_data;
} sensor_data_t;

typedef enum {
SENSOR_OK = 0,
SENSOR_ERROR,
SENSOR_TIMEOUT
} sensor_status_t;

sensor_status_t drv_sensor_init(void);
sensor_status_t drv_sensor_read_data(sensor_data_t *data);

#endif // DRV_SENSOR_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
27
28
29
30
31
// drv_sensor.c
#include "drv_sensor.h"
#include "hal_ina226.h"
#include "config.h"
#include "esp_log.h"

static const char *TAG_SENSOR = "DRV_SENSOR";

sensor_status_t drv_sensor_init(void) {
if (hal_ina226_init(I2C_NUM_0, INA226_I2C_ADDR_1) != INA226_OK) {
ESP_LOGE(TAG_SENSOR, "INA226 #1 init failed");
return SENSOR_ERROR;
}
if (hal_ina226_init(I2C_NUM_0, INA226_I2C_ADDR_2) != INA226_OK) {
ESP_LOGE(TAG_SENSOR, "INA226 #2 init failed");
return SENSOR_ERROR;
}
return SENSOR_OK;
}

sensor_status_t drv_sensor_read_data(sensor_data_t *data) {
if (hal_ina226_read_data(I2C_NUM_0, INA226_I2C_ADDR_1, &data->solar_panel_data) != INA226_OK) {
ESP_LOGE(TAG_SENSOR, "Failed to read data from INA226 #1");
return SENSOR_ERROR;
}
if (hal_ina226_read_data(I2C_NUM_0, INA226_I2C_ADDR_2, &data->battery_data) != INA226_OK) {
ESP_LOGE(TAG_SENSOR, "Failed to read data from INA226 #2");
return SENSOR_ERROR;
}
return SENSOR_OK;
}

6. drv_dcdc.hdrv_dcdc.c (设备驱动 - DC-DC控制)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// drv_dcdc.h
#ifndef DRV_DCDC_H
#define DRV_DCDC_H

#include <stdint.h>
#include <stdbool.h>

typedef enum {
DCDC_OK = 0,
DCDC_ERROR,
DCDC_TIMEOUT
} dcdc_status_t;

dcdc_status_t drv_dcdc_init(void);
dcdc_status_t drv_dcdc_set_duty_cycle(float duty_cycle);

#endif // DRV_DCDC_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// drv_dcdc.c
#include "drv_dcdc.h"
#include "hal_pwm.h"
#include "config.h"
#include "esp_log.h"

static const char *TAG_DCDC = "DRV_DCDC";

dcdc_status_t drv_dcdc_init(void) {
if (hal_pwm_init(PWM_PIN, PWM_FREQ) != PWM_OK) {
ESP_LOGE(TAG_DCDC, "PWM init failed");
return DCDC_ERROR;
}
return DCDC_OK;
}

dcdc_status_t drv_dcdc_set_duty_cycle(float duty_cycle) {
if (hal_pwm_set_duty_cycle(PWM_PIN, duty_cycle) != PWM_OK) {
ESP_LOGE(TAG_DCDC, "PWM set duty cycle failed");
return DCDC_ERROR;
}
return DCDC_OK;
}

7. app_mppt.happ_mppt.c (应用层 - MPPT算法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// app_mppt.h
#ifndef APP_MPPT_H
#define APP_MPPT_H

#include <stdint.h>
#include <stdbool.h>
#include "drv_sensor.h"

typedef enum {
MPPT_OK = 0,
MPPT_ERROR,
MPPT_TIMEOUT
} mppt_status_t;

mppt_status_t app_mppt_init(void);
mppt_status_t app_mppt_process(sensor_data_t *sensor_data, float *duty_cycle);

#endif // APP_MPPT_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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// app_mppt.c
#include "app_mppt.h"
#include "drv_sensor.h"
#include "drv_dcdc.h"
#include "config.h"
#include "esp_log.h"

static const char *TAG_MPPT = "APP_MPPT";

static float current_duty_cycle = 0.5f; // 初始占空比
static float previous_power = 0.0f;

mppt_status_t app_mppt_init(void) {
current_duty_cycle = 0.5f;
previous_power = 0.0f;
return MPPT_OK;
}

mppt_status_t app_mppt_process(sensor_data_t *sensor_data, float *duty_cycle) {
float current_power = sensor_data->solar_panel_data.bus_voltage_V * sensor_data->solar_panel_data.current_mA;

if (sensor_data->solar_panel_data.bus_voltage_V < MPPT_VOLTAGE_THRESHOLD) {
ESP_LOGW(TAG_MPPT, "Solar panel voltage too low, MPPT disabled");
*duty_cycle = 0.0f; // Stop charging
return MPPT_OK;
}

if (current_power > previous_power) {
// Power increased, continue in the same direction
} else {
// Power decreased, reverse direction
current_duty_cycle -= (current_duty_cycle > 0.5f) ? MPPT_STEP_SIZE : -MPPT_STEP_SIZE; // Perturb and Observe
if (current_duty_cycle < 0.0f) current_duty_cycle = 0.0f;
if (current_duty_cycle > 1.0f) current_duty_cycle = 1.0f;
}

previous_power = current_power;
*duty_cycle = current_duty_cycle;

return MPPT_OK;
}

8. app_charge.happ_charge.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
// app_charge.h
#ifndef APP_CHARGE_H
#define APP_CHARGE_H

#include <stdint.h>
#include <stdbool.h>
#include "drv_sensor.h"

typedef enum {
CHARGE_OK = 0,
CHARGE_ERROR,
CHARGE_TIMEOUT
} charge_status_t;

typedef enum {
CHARGE_STATE_IDLE,
CHARGE_STATE_MPPT,
CHARGE_STATE_CV, // Constant Voltage
CHARGE_STATE_CC, // Constant Current
CHARGE_STATE_FULL
} charge_state_t;

charge_status_t app_charge_init(void);
charge_status_t app_charge_process(sensor_data_t *sensor_data, float mppt_duty_cycle);
charge_state_t app_charge_get_state(void);

#endif // APP_CHARGE_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
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// app_charge.c
#include "app_charge.h"
#include "drv_sensor.h"
#include "drv_dcdc.h"
#include "config.h"
#include "esp_log.h"

static const char *TAG_CHARGE = "APP_CHARGE";

static charge_state_t current_charge_state = CHARGE_STATE_IDLE;

charge_status_t app_charge_init(void) {
current_charge_state = CHARGE_STATE_IDLE;
return CHARGE_OK;
}

charge_status_t app_charge_process(sensor_data_t *sensor_data, float mppt_duty_cycle) {
float battery_voltage = sensor_data->battery_data.bus_voltage_V;
float battery_current = sensor_data->battery_data.current_mA / 1000.0f; // Convert mA to A

switch (current_charge_state) {
case CHARGE_STATE_IDLE:
if (sensor_data->solar_panel_data.bus_voltage_V > MPPT_VOLTAGE_THRESHOLD) {
current_charge_state = CHARGE_STATE_MPPT;
ESP_LOGI(TAG_CHARGE, "Charge state: MPPT");
}
drv_dcdc_set_duty_cycle(0.0f); // Stop charging in IDLE
break;

case CHARGE_STATE_MPPT:
if (battery_voltage >= BATTERY_MAX_VOLTAGE) {
current_charge_state = CHARGE_STATE_FULL;
ESP_LOGI(TAG_CHARGE, "Charge state: FULL");
drv_dcdc_set_duty_cycle(0.0f); // Stop charging when full
} else if (battery_current >= BATTERY_CHARGE_CURRENT_MAX) {
current_charge_state = CHARGE_STATE_CC;
ESP_LOGI(TAG_CHARGE, "Charge state: CC");
drv_dcdc_set_duty_cycle(mppt_duty_cycle); // Continue MPPT, limit current later if needed in CC state
} else {
drv_dcdc_set_duty_cycle(mppt_duty_cycle); // MPPT tracking
}
break;

case CHARGE_STATE_CC: // Constant Current (Example - needs more sophisticated CC/CV logic)
if (battery_voltage >= BATTERY_MAX_VOLTAGE) {
current_charge_state = CHARGE_STATE_CV; // Switch to Constant Voltage when voltage reaches max
ESP_LOGI(TAG_CHARGE, "Charge state: CV");
} else if (battery_current > BATTERY_CHARGE_CURRENT_MAX * 1.1f) { // Simple current limit
mppt_duty_cycle *= 0.95f; // Reduce duty cycle to limit current
if (mppt_duty_cycle < 0.0f) mppt_duty_cycle = 0.0f;
drv_dcdc_set_duty_cycle(mppt_duty_cycle);
ESP_LOGW(TAG_CHARGE, "Current limit active, reducing duty cycle");
} else {
drv_dcdc_set_duty_cycle(mppt_duty_cycle); // Continue MPPT, try to maintain CC within limit
}
break;

case CHARGE_STATE_CV: // Constant Voltage (Example - basic CV, needs proper termination logic)
if (battery_current <= 0.1f) { // Low current termination for CV - very basic example
current_charge_state = CHARGE_STATE_FULL;
ESP_LOGI(TAG_CHARGE, "Charge state: FULL (CV Termination)");
drv_dcdc_set_duty_cycle(0.0f); // Stop charging
} else {
drv_dcdc_set_duty_cycle(mppt_duty_cycle); // Maintain voltage, adjust duty as needed
if (battery_voltage > BATTERY_MAX_VOLTAGE * 1.01f) { // If voltage exceeds slightly, reduce duty
mppt_duty_cycle *= 0.98f;
if (mppt_duty_cycle < 0.0f) mppt_duty_cycle = 0.0f;
drv_dcdc_set_duty_cycle(mppt_duty_cycle);
ESP_LOGW(TAG_CHARGE, "Voltage limit active, reducing duty cycle in CV");
}
}
break;

case CHARGE_STATE_FULL:
drv_dcdc_set_duty_cycle(0.0f); // Stop charging in FULL state
break;

default:
current_charge_state = CHARGE_STATE_IDLE;
ESP_LOGE(TAG_CHARGE, "Invalid charge state, resetting to IDLE");
drv_dcdc_set_duty_cycle(0.0f);
break;
}

return CHARGE_OK;
}

charge_state_t app_charge_get_state(void) {
return current_charge_state;
}

9. app_system.happ_system.c (应用层 - 系统管理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app_system.h
#ifndef APP_SYSTEM_H
#define APP_SYSTEM_H

#include <stdint.h>
#include <stdbool.h>

typedef enum {
SYSTEM_OK = 0,
SYSTEM_ERROR,
SYSTEM_TIMEOUT
} system_status_t;

system_status_t app_system_init(void);
system_status_t app_system_loop(void);

#endif // APP_SYSTEM_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
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
56
57
58
59
60
61
62
63
64
65
66
67
// app_system.c
#include "app_system.h"
#include "drv_sensor.h"
#include "app_mppt.h"
#include "app_charge.h"
#include "config.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

static const char *TAG_SYSTEM = "APP_SYSTEM";

system_status_t app_system_init(void) {
ESP_LOGI(TAG_SYSTEM, "System initializing...");

if (hal_i2c_init(I2C_NUM_0, 22, 21, 100000) != I2C_OK) { // Example I2C pins
ESP_LOGE(TAG_SYSTEM, "I2C init failed");
return SYSTEM_ERROR;
}
if (drv_sensor_init() != SENSOR_OK) {
ESP_LOGE(TAG_SYSTEM, "Sensor init failed");
return SYSTEM_ERROR;
}
if (drv_dcdc_init() != DCDC_OK) {
ESP_LOGE(TAG_SYSTEM, "DCDC init failed");
return SYSTEM_ERROR;
}
if (app_mppt_init() != MPPT_OK) {
ESP_LOGE(TAG_SYSTEM, "MPPT init failed");
return SYSTEM_ERROR;
}
if (app_charge_init() != CHARGE_OK) {
ESP_LOGE(TAG_SYSTEM, "Charge init failed");
return SYSTEM_ERROR;
}

ESP_LOGI(TAG_SYSTEM, "System initialization complete.");
return SYSTEM_OK;
}

system_status_t app_system_loop(void) {
sensor_data_t sensor_data;
float mppt_duty_cycle = 0.0f;

if (drv_sensor_read_data(&sensor_data) != SENSOR_OK) {
ESP_LOGE(TAG_SYSTEM, "Sensor data read error");
return SYSTEM_ERROR;
}

if (app_mppt_process(&sensor_data, &mppt_duty_cycle) != MPPT_OK) {
ESP_LOGE(TAG_SYSTEM, "MPPT process error");
return SYSTEM_ERROR;
}

if (app_charge_process(&sensor_data, mppt_duty_cycle) != CHARGE_OK) {
ESP_LOGE(TAG_SYSTEM, "Charge process error");
return SYSTEM_ERROR;
}

// --- 状态信息打印 (Debug) ---
ESP_LOGI(TAG_SYSTEM, "Solar V: %.2fV, I: %.2fA, P: %.2fW, Bat V: %.2fV, I: %.2fA, Charge State: %d, Duty: %.2f%%",
sensor_data.solar_panel_data.bus_voltage_V, sensor_data.solar_panel_data.current_mA / 1000.0f, sensor_data.solar_panel_data.power_mW / 1000.0f,
sensor_data.battery_data.bus_voltage_V, sensor_data.battery_data.current_mA / 1000.0f, app_charge_get_state(), mppt_duty_cycle * 100.0f);

vTaskDelay(pdMS_TO_TICKS(SYSTEM_LOOP_PERIOD_MS)); // 周期性循环
return SYSTEM_OK;
}

10. main.c (主程序入口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "app_system.h"

static const char *TAG_MAIN = "MAIN";

void app_main(void) {
esp_log_level_set("*", ESP_LOG_INFO); // 设置全局日志级别

if (app_system_init() != SYSTEM_OK) {
ESP_LOGE(TAG_MAIN, "System init failed, halting.");
while(1) { vTaskDelay(pdMS_TO_TICKS(1000)); } // 错误处理,可以加入重启逻辑
}

while (1) {
if (app_system_loop() != SYSTEM_OK) {
ESP_LOGE(TAG_MAIN, "System loop error."); // 可以在这里加入错误处理或重启逻辑
}
}
}

项目中采用的各种技术和方法 (实践验证)

  • ESP32 微控制器: 选择ESP32是因为其强大的处理能力、丰富的外设接口 (I2C, PWM, GPIO, Wi-Fi, Bluetooth)、以及成熟的开发生态 (ESP-IDF)。ESP32的双核处理器可以轻松应对MPPT算法和系统控制任务。
  • INA226 电流/电压传感器: 使用INA226替代ADC的优势在于:
    • 高精度和高分辨率: INA226是专用电流/电压传感器,精度远高于ESP32的内置ADC,能够更准确地测量太阳能板和电池的电压电流,提高MPPT效率和充电控制精度。
    • 集成电流检测: INA226内部集成了电流检测放大器和高精度ADC,无需外部精密电阻和运放电路,简化了硬件设计,降低了成本。
    • 数字接口 (I2C): I2C数字接口抗干扰能力强,易于连接和通信。
  • 扰动观察法 (P&O) MPPT算法: P&O算法是最常用的MPPT算法之一,其优点是简单易实现,计算量小,适用于低成本的嵌入式系统。虽然更高级的算法 (例如增量电导法) 在某些情况下性能更优,但P&O算法在大多数应用场景下能够达到较好的效率,并且实践证明其可靠性和稳定性良好。
  • PWM 控制 DC-DC 转换器: 使用PWM控制Buck或Boost型DC-DC转换器是实现MPPT的关键环节。通过调整PWM的占空比,可以改变DC-DC转换器的输出电压和电流,从而追踪太阳能电池板的最大功率点。PWM控制技术成熟,效率高,易于实现。
  • 分层模块化代码架构: 如上所述,分层模块化架构提高了代码的可维护性、可扩展性、可重用性和可测试性,这在嵌入式系统开发中至关重要。这种架构在实践中被广泛验证是高效且可靠的。
  • FreeRTOS 实时操作系统 (可选): 虽然上述代码示例没有显式使用FreeRTOS,但在实际项目中,如果系统功能更加复杂,或者需要处理更多的并发任务 (例如通信、数据记录、显示等),可以考虑引入FreeRTOS或类似的实时操作系统,实现任务调度和资源管理,提高系统的实时性和可靠性。
  • C 语言编程: C语言是嵌入式系统开发中最常用的编程语言,因为它具有效率高、可移植性好、硬件控制能力强等优点。C语言在嵌入式领域拥有广泛的应用和实践验证。
  • 软件调试和测试方法: 嵌入式软件开发离不开调试和测试。常用的方法包括:
    • 串口日志输出: 通过串口输出调试信息,例如传感器数据、MPPT算法输出、系统状态等,方便开发人员监控系统运行状态和排查问题。
    • 在线调试器 (JTAG/SWD): 使用JTAG或SWD接口连接调试器,进行代码单步调试、内存查看、断点设置等,深入分析程序运行过程。
    • 单元测试和集成测试: 针对模块和系统进行全面的测试,确保功能和性能满足需求。
    • 硬件在环测试 (HIL): 使用仿真器或半实物仿真平台,模拟真实的工作环境,对硬件和软件进行联合测试,验证系统的可靠性和稳定性。

代码扩展和完善方向 (超过3000行代码的思路)

为了将代码量扩展到3000行以上,并使其更完善和更接近实际项目,可以从以下几个方面进行扩展:

  1. 完善错误处理和异常情况处理: 在各个模块中加入更详细的错误检测和处理代码,例如I2C通信错误重试、传感器数据校验、PWM控制错误处理、MPPT算法异常情况处理、充电状态机错误处理等。并加入相应的错误日志记录和告警机制。
  2. 实现更高级的MPPT算法: 例如增量电导法 (IC)、分数开路电压法 (FOCV)、分数短路电流法 (FSC) 等。对比不同算法的性能,并选择最适合本项目需求的算法。可以实现多种MPPT算法,并提供配置选项让用户选择。
  3. 实现更完善的充电控制逻辑: 上述示例中的充电控制逻辑较为简单,需要根据具体的电池类型和充电需求,实现更精细的充电控制策略,例如:
    • 多阶段充电 (恒流、恒压、涓流充电): 根据电池类型和充电状态,实现更智能的多阶段充电过程。
    • 温度补偿: 根据电池温度调整充电参数,优化充电性能和电池寿命。
    • 电池均衡 (对于串联电池组): 如果使用串联电池组,需要加入电池均衡功能,保证电池组的整体性能和寿命。
    • 充电效率优化: 通过算法优化和参数调整,提高充电效率,减少能量损耗。
  4. 加入保护功能模块: 完善保护功能,例如:
    • 过压保护 (OVP): 防止电池电压过高损坏电池。
    • 过流保护 (OCP): 防止充电电流过大损坏电池或功率器件。
    • 过温保护 (OTP): 监控系统温度,防止器件过热损坏。
    • 反接保护: 防止太阳能板或电池反接损坏电路。
    • 短路保护: 防止输出短路损坏电路。
  5. 实现通信功能: 加入Wi-Fi或蓝牙通信功能,实现:
    • 远程监控: 通过手机App或Web平台远程监控太阳能充电控制器的运行状态,例如太阳能板电压、电流、功率、电池电压、充电电流、充电状态、系统温度等。
    • 数据记录和分析: 记录历史数据,进行数据分析,评估系统性能,优化系统参数。
    • 远程控制和配置: 远程设置充电参数、MPPT参数、系统配置等,实现远程管理。
    • OTA 在线固件升级: 实现OTA功能,方便远程升级固件,修复bug或增加新功能。
  6. 加入用户界面 (可选): 如果需要本地用户界面,可以考虑:
    • LED 指示灯: 使用LED指示灯显示系统状态,例如充电状态、错误状态等。
    • LCD 显示屏: 使用LCD显示屏显示更详细的系统信息,例如电压、电流、功率、充电状态、设置参数等。
    • 按键操作: 使用按键进行本地参数设置和操作。
  7. 代码注释和文档编写: 为了提高代码的可读性和可维护性,需要加入详细的代码注释,并编写相关的开发文档,包括需求文档、设计文档、用户手册等。

通过以上扩展和完善,代码量可以轻松超过3000行,并且系统功能将更加完善、可靠和实用。实际项目开发中,需要根据具体的需求和资源限制,选择合适的功能和技术,并进行充分的测试和验证,才能最终开发出高质量的嵌入式产品。

希望以上详细的解答和代码示例能够帮助您理解基于ESP32和INA226的MPPT太阳能充电控制器的开发过程和代码架构设计。如果您有任何其他问题,欢迎继续提问。

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