B001-V3 works with it now
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
# for more information about component CMakeLists.txt files.
|
||||
|
||||
idf_component_register(
|
||||
SRCS main.c i2c.c rtc.c storage.c uart_comms.c # list the source files of this component
|
||||
SRCS main.c i2c.c rtc.c storage.c uart_comms.c control_fsm.c power_mgmt.c rf.c rtc.c sensors.c solar.c # list the source files of this component
|
||||
INCLUDE_DIRS # optional, add here public include directories
|
||||
PRIV_INCLUDE_DIRS # optional, add here private include directories
|
||||
REQUIRES # optional, list the public requirements (component names)
|
||||
|
||||
@@ -14,22 +14,27 @@
|
||||
#include "storage.h"
|
||||
#include "rtc.h"
|
||||
#include "sensors.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#define TRANSITION_DELAY_US 1000000
|
||||
|
||||
#define TAG "FSM"
|
||||
|
||||
static QueueHandle_t fsm_cmd_queue = NULL;
|
||||
|
||||
// map from relay number to bridge
|
||||
bridge_t bridge_map[] = {
|
||||
BRIDGE_AUX,
|
||||
BRIDGE_AUX,
|
||||
BRIDGE_AUX,
|
||||
BRIDGE_AUX,
|
||||
BRIDGE_JACK,
|
||||
BRIDGE_JACK,
|
||||
BRIDGE_DRIVE,
|
||||
BRIDGE_DRIVE };
|
||||
|
||||
uint8_t relay_pins[N_RELAYS] = {
|
||||
GPIO_NUM_13, // A1
|
||||
GPIO_NUM_14, // B1
|
||||
GPIO_NUM_15, // A2
|
||||
GPIO_NUM_16, // B2
|
||||
GPIO_NUM_17, // A3
|
||||
GPIO_NUM_18 // B3
|
||||
};
|
||||
|
||||
bool relay_states[N_RELAYS] = {false};
|
||||
int64_t override_times[N_RELAYS] = {-1};
|
||||
bool relay_states[8] = {false};
|
||||
int64_t override_times[8] = {-1};
|
||||
bool enabled = false;
|
||||
|
||||
void setRelay(int8_t relay, bool state) {
|
||||
@@ -37,36 +42,24 @@ void setRelay(int8_t relay, bool state) {
|
||||
}
|
||||
|
||||
void driveRelays() {
|
||||
for (uint8_t i=0; i<N_RELAYS; i++) {
|
||||
if (efuse_is_tripped(i/2))
|
||||
gpio_set_level(relay_pins[i], 0);
|
||||
else
|
||||
gpio_set_level(relay_pins[i], relay_states[i]);
|
||||
uint8_t state = 0x00;
|
||||
for (uint8_t i=0; i<8; i++) {
|
||||
// if we command and efuse permits it set the relay
|
||||
if (relay_states[i] && !efuse_is_tripped(bridge_map[i])) {
|
||||
state |= 0x01<<i;
|
||||
set_autozero(bridge_map[i]);
|
||||
}
|
||||
}
|
||||
i2c_set_relays(state);
|
||||
}
|
||||
|
||||
int8_t bridge_polarities[3] = {
|
||||
+1,
|
||||
-1,
|
||||
-1
|
||||
};
|
||||
void setBridge(bridge_t bridge, int8_t dir) {
|
||||
dir *= bridge_polarities[bridge];
|
||||
setRelay(bridge*2+0, dir<0);
|
||||
setRelay(bridge*2+1, dir>0);
|
||||
}
|
||||
|
||||
|
||||
int8_t get_bridge_state(bridge_t bridge) {
|
||||
if (relay_states[bridge*2 + 0]) return +1;
|
||||
if (relay_states[bridge*2 + 1]) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
volatile fsm_state_t current_state = STATE_IDLE;
|
||||
volatile int64_t current_time = 0;
|
||||
volatile bool start_running_request = false;
|
||||
|
||||
|
||||
|
||||
fsm_state_t fsm_get_state() {
|
||||
return current_state;
|
||||
}
|
||||
@@ -79,18 +72,16 @@ static inline void set_timer(uint64_t us) {
|
||||
}
|
||||
static inline bool timer_done() { return current_time >= timer_end; }
|
||||
|
||||
void pulseOverride(bridge_t bridge, int8_t dir, int64_t pulse) {
|
||||
if (dir < 0)
|
||||
override_times[bridge*2 + (bridge_polarities[bridge]>0?0:1)] = esp_timer_get_time() + pulse;
|
||||
if (dir > 0)
|
||||
override_times[bridge*2 + (bridge_polarities[bridge]>0?1:0)] = esp_timer_get_time() + pulse;
|
||||
void pulseOverride(relay_t relay) {
|
||||
if (current_state == STATE_IDLE)
|
||||
override_times[relay] = current_time + get_param(PARAM_RF_PULSE_LENGTH).u64;
|
||||
}
|
||||
|
||||
void fsm_begin_auto_move() {
|
||||
/*void fsm_begin_auto_move() {
|
||||
if (current_state == STATE_IDLE)
|
||||
current_state = STATE_MOVE_START_DELAY;
|
||||
set_timer(TRANSITION_DELAY_US);
|
||||
}
|
||||
}*/
|
||||
|
||||
void fsm_request(fsm_cmd_t cmd)
|
||||
{
|
||||
@@ -122,9 +113,10 @@ int8_t fsm_get_current_progress(int8_t denominator) {
|
||||
return x;
|
||||
}
|
||||
|
||||
#define JACK_TIME (uint64_t)get_param_i8("jack_dist")*get_param_u32("jack_mspi")*1000
|
||||
#define DRIVE_TIME (uint64_t)get_param_i8("drive_dist")*get_param_u32("drive_mspf")*1000
|
||||
#define DRIVE_DIST ((int32_t)(get_param_i8("drive_dist")))*((int32_t)get_param_u32("drive_tpdf"))/10
|
||||
|
||||
#define JACK_TIME get_param(PARAM_JACK_MSPI ).u32 * 1000 * get_param(PARAM_JACK_DIST ).u8
|
||||
#define DRIVE_TIME get_param(PARAM_DRIVE_MSPF).u32 * 1000 * get_param(PARAM_DRIVE_DIST).u8
|
||||
#define DRIVE_DIST get_param(PARAM_DRIVE_TPDF).u32 / 10 * get_param(PARAM_DRIVE_DIST).u8
|
||||
|
||||
void control_task(void *param) {
|
||||
esp_task_wdt_add(NULL);
|
||||
@@ -133,14 +125,6 @@ void control_task(void *param) {
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(50);
|
||||
enabled = true;
|
||||
|
||||
|
||||
|
||||
for (size_t i = 0; i < N_RELAYS; ++i) {
|
||||
gpio_reset_pin( relay_pins[i]);
|
||||
gpio_set_direction(relay_pins[i], GPIO_MODE_OUTPUT);
|
||||
gpio_set_level( relay_pins[i], 0); // Force low
|
||||
gpio_hold_dis( relay_pins[i]); // CRITICAL: Allow control after wake
|
||||
}
|
||||
|
||||
while (enabled) {
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
@@ -149,13 +133,15 @@ void control_task(void *param) {
|
||||
fsm_cmd_t cmd;
|
||||
while (xQueueReceive(fsm_cmd_queue, &cmd, 0) == pdTRUE) {
|
||||
switch (cmd) {
|
||||
case FSM_CMD_STOP:
|
||||
if (current_state != STATE_IDLE &&
|
||||
current_state != STATE_UNDO_JACK_START &&
|
||||
current_state != STATE_UNDO_JACK) {
|
||||
current_state = STATE_IDLE;
|
||||
case FSM_CMD_START:
|
||||
if (current_state == STATE_IDLE) {
|
||||
current_state = STATE_MOVE_START_DELAY;
|
||||
set_timer(TRANSITION_DELAY_US);
|
||||
}
|
||||
break;
|
||||
case FSM_CMD_STOP:
|
||||
current_state = STATE_IDLE;
|
||||
break;
|
||||
case FSM_CMD_UNDO:
|
||||
if (current_state != STATE_IDLE &&
|
||||
current_state != STATE_UNDO_JACK_START &&
|
||||
@@ -245,12 +231,12 @@ void control_task(void *param) {
|
||||
default: break;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
int64_t elapsed_t = (current_time-timer_start);
|
||||
int64_t total_t = (timer_end-timer_start);
|
||||
int32_t ticks = get_sensor_counter(SENSOR_DRIVE);
|
||||
ESP_LOGI("FSM", "[%d] %lld / %lld ms, %ld ticks", current_state, (long long) elapsed_t, (long long) total_t, (long) ticks);
|
||||
*/
|
||||
//ESP_LOGI("FSM", "[%d] %lld / %lld ms, %ld ticks", current_state, (long long) elapsed_t, (long long) total_t, (long) ticks);
|
||||
|
||||
// Output control
|
||||
switch (current_state) {
|
||||
case STATE_IDLE:
|
||||
@@ -261,42 +247,69 @@ void control_task(void *param) {
|
||||
if (active) reset_shutdown_timer();
|
||||
|
||||
// prohibit movement past jack limit switch
|
||||
if (i == BRIDGE_JACK*2+(bridge_polarities[BRIDGE_JACK]>0?0:1) && get_sensor(SENSOR_JACK))
|
||||
setRelay(i, false);
|
||||
else
|
||||
//if (i == BRIDGE_JACK*2+(bridge_polarities[BRIDGE_JACK]>0?0:1) && get_sensor(SENSOR_JACK))
|
||||
// setRelay(i, false);
|
||||
//else
|
||||
setRelay(i, active);
|
||||
//if (active) ESP_LOGI("FSM", "RUN CHANNEL %d (%lld %c %lld)", i, (long long) override_times[i], active ? '>':'<', (long long) current_time);
|
||||
|
||||
|
||||
}
|
||||
break;
|
||||
case STATE_JACK_UP:
|
||||
setBridge(BRIDGE_DRIVE, 0);
|
||||
setBridge(BRIDGE_JACK, +1);
|
||||
setBridge(BRIDGE_AUX, +1);
|
||||
// jack up and fluff
|
||||
setRelay(RELAY_A1, false);
|
||||
setRelay(RELAY_B1, false);
|
||||
|
||||
setRelay(RELAY_A2, true);
|
||||
setRelay(RELAY_B2, false);
|
||||
|
||||
setRelay(RELAY_A3, true);
|
||||
reset_shutdown_timer();
|
||||
break;
|
||||
case STATE_DRIVE:
|
||||
setBridge(BRIDGE_DRIVE, +1);
|
||||
setBridge(BRIDGE_JACK, 0);
|
||||
setBridge(BRIDGE_AUX, +1);
|
||||
// drive and fluff
|
||||
setRelay(RELAY_A1, true);
|
||||
setRelay(RELAY_B1, false);
|
||||
|
||||
setRelay(RELAY_A2, false);
|
||||
setRelay(RELAY_B2, false);
|
||||
|
||||
setRelay(RELAY_A3, true);
|
||||
reset_shutdown_timer();
|
||||
break;
|
||||
case STATE_UNDO_JACK:
|
||||
case STATE_JACK_DOWN:
|
||||
setBridge(BRIDGE_DRIVE, 0);
|
||||
setBridge(BRIDGE_JACK, -1);
|
||||
setBridge(BRIDGE_AUX, +1);
|
||||
// jack down and fluffer
|
||||
setRelay(RELAY_A1, false);
|
||||
setRelay(RELAY_B1, false);
|
||||
|
||||
setRelay(RELAY_A2, false);
|
||||
setRelay(RELAY_B2, true);
|
||||
|
||||
setRelay(RELAY_A3, true);
|
||||
reset_shutdown_timer();
|
||||
break;
|
||||
case STATE_UNDO_JACK_START:
|
||||
case STATE_DRIVE_START_DELAY:
|
||||
case STATE_DRIVE_END_DELAY:
|
||||
setBridge(BRIDGE_DRIVE, 0);
|
||||
setBridge(BRIDGE_JACK, 0);
|
||||
setBridge(BRIDGE_AUX, +1);
|
||||
// only fluffer
|
||||
setRelay(RELAY_A1, false);
|
||||
setRelay(RELAY_B1, false);
|
||||
|
||||
setRelay(RELAY_A2, false);
|
||||
setRelay(RELAY_B2, false);
|
||||
|
||||
setRelay(RELAY_A3, true);
|
||||
reset_shutdown_timer();
|
||||
break;
|
||||
default: break;
|
||||
default:
|
||||
// invalid state; turn all relays off
|
||||
setRelay(RELAY_A1, false);
|
||||
setRelay(RELAY_B1, false);
|
||||
setRelay(RELAY_A2, false);
|
||||
setRelay(RELAY_B2, false);
|
||||
setRelay(RELAY_A3, false);
|
||||
break;
|
||||
}
|
||||
|
||||
driveRelays();
|
||||
@@ -304,20 +317,20 @@ void control_task(void *param) {
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < N_RELAYS; ++i) {
|
||||
gpio_set_level(relay_pins[i], 0);
|
||||
gpio_hold_en(relay_pins[i]);
|
||||
}
|
||||
|
||||
if (fsm_cmd_queue != NULL) {
|
||||
vQueueDelete(fsm_cmd_queue);
|
||||
fsm_cmd_queue = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void start_fsm() {
|
||||
esp_err_t fsm_init() {
|
||||
if (fsm_cmd_queue == NULL) {
|
||||
fsm_cmd_queue = xQueueCreate(8, sizeof(fsm_cmd_t));
|
||||
}
|
||||
xTaskCreate(control_task, "FSM", 4096, NULL, 5, NULL);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t fsm_stop() { return ESP_OK; }
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "freertos/queue.h"
|
||||
|
||||
|
||||
typedef enum { FSM_CMD_STOP, FSM_CMD_UNDO, FSM_CMD_SHUTDOWN} fsm_cmd_t;
|
||||
typedef enum { FSM_CMD_START, FSM_CMD_STOP, FSM_CMD_UNDO, FSM_CMD_SHUTDOWN} fsm_cmd_t;
|
||||
|
||||
typedef enum {
|
||||
STATE_IDLE = 0,
|
||||
@@ -22,21 +22,33 @@ typedef enum {
|
||||
} fsm_state_t;
|
||||
|
||||
typedef enum {
|
||||
BRIDGE_AUX = 0,
|
||||
RELAY_NONE = 0,
|
||||
RELAY_C3,
|
||||
RELAY_B3,
|
||||
RELAY_A3,
|
||||
RELAY_B2,
|
||||
RELAY_A2,
|
||||
RELAY_B1,
|
||||
RELAY_A1
|
||||
} relay_t;
|
||||
|
||||
typedef enum {
|
||||
BRIDGE_AUX = 2,
|
||||
BRIDGE_JACK = 1,
|
||||
BRIDGE_DRIVE = 2,
|
||||
BRIDGE_DRIVE = 0,
|
||||
} bridge_t;
|
||||
|
||||
#define N_RELAYS 6
|
||||
#define N_RELAYS 8
|
||||
#define N_BRIDGES 3
|
||||
|
||||
void pulseOverride(bridge_t bridge, int8_t dir, int64_t pulse);
|
||||
void pulseOverride(relay_t relay/*, int64_t pulse*/);
|
||||
|
||||
void start_fsm();
|
||||
esp_err_t fsm_init();
|
||||
esp_err_t fsm_stop();
|
||||
|
||||
void fsm_request(fsm_cmd_t cmd);
|
||||
|
||||
void fsm_begin_auto_move();
|
||||
//void fsm_begin_auto_move();
|
||||
|
||||
int8_t fsm_get_current_progress(int8_t remainder);
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
|
||||
|
||||
|
||||
// Debounce & Repeat Settings
|
||||
#define DEBOUNCE_MS 50
|
||||
#define REPEAT_MS 200
|
||||
#define REPEAT_START_MS 700
|
||||
|
||||
static uint8_t lcd_col = 0;
|
||||
static uint8_t lcd_row = 0;
|
||||
|
||||
|
||||
11
main/i2c.c
11
main/i2c.c
@@ -24,7 +24,7 @@
|
||||
#define TCA_REG_CONFIG1 0x07
|
||||
|
||||
// Debounce & Repeat Settings
|
||||
#define DEBOUNCE_MS 50
|
||||
#define DEBOUNCE_MS 50
|
||||
#define REPEAT_MS 200
|
||||
#define REPEAT_START_MS 700
|
||||
|
||||
@@ -45,7 +45,7 @@ static esp_err_t tca_read_word(uint8_t reg, uint16_t *value) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t i2cdev_init(void) {
|
||||
esp_err_t i2c_init(void) {
|
||||
if (i2c_initted) return ESP_OK;
|
||||
|
||||
i2c_config_t conf = {
|
||||
@@ -75,6 +75,13 @@ esp_err_t i2c_set_led1(uint8_t state) {
|
||||
return tca_write_word_8(TCA_REG_OUTPUT0, state<<5);
|
||||
}
|
||||
|
||||
esp_err_t i2c_stop() {
|
||||
if (!i2c_initted) return ESP_OK;
|
||||
i2c_set_relays(0);
|
||||
i2c_set_led1(0);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#define N_BTNS 2
|
||||
static bool debounced_state[N_BTNS] = {false};
|
||||
static bool last_known_state[N_BTNS] = {false};
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
#include "esp_err.h"
|
||||
|
||||
// Public Functions
|
||||
esp_err_t i2cdev_init(void);
|
||||
esp_err_t i2c_init(void);
|
||||
esp_err_t i2c_stop(void);
|
||||
|
||||
esp_err_t i2c_set_relays(uint8_t states);
|
||||
esp_err_t i2c_set_led1(uint8_t state);
|
||||
|
||||
199
main/main.c
199
main/main.c
@@ -1,3 +1,5 @@
|
||||
#include "esp_task_wdt.h"
|
||||
#include "i2c.h"
|
||||
#include "storage.h"
|
||||
#include "uart_comms.h"
|
||||
#include "esp_err.h"
|
||||
@@ -7,9 +9,13 @@
|
||||
#include "power_mgmt.h"
|
||||
#include "rtc.h"
|
||||
#include "sensors.h"
|
||||
#include "solar.h"
|
||||
#include "rf.h"
|
||||
|
||||
#define TAG "MAIN"
|
||||
|
||||
|
||||
int64_t last_log_time = 0;
|
||||
esp_err_t send_log() {
|
||||
|
||||
char entry[LOG_ENTRY_SIZE] = {0};
|
||||
@@ -20,13 +26,13 @@ esp_err_t send_log() {
|
||||
memcpy(&entry[1], &be_timestamp, 8);
|
||||
|
||||
// Pack 32-bit voltages/currents into bytes 9-24
|
||||
/*int32_t be_voltage = htobe32(get_battery_mV());
|
||||
float be_voltage = htobe32(get_battery_V());
|
||||
memcpy(&entry[9], &be_voltage, 4);
|
||||
int32_t be_current1 = htobe32(get_bridge_mA(BRIDGE_DRIVE));
|
||||
float be_current1 = htobe32(get_bridge_A(BRIDGE_DRIVE));
|
||||
memcpy(&entry[13], &be_current1, 4);
|
||||
int32_t be_current2 = htobe32(get_bridge_mA(BRIDGE_JACK));
|
||||
float be_current2 = htobe32(get_bridge_A(BRIDGE_JACK));
|
||||
memcpy(&entry[17], &be_current2, 4);
|
||||
int32_t be_current3 = htobe32(get_bridge_mA(BRIDGE_AUX));
|
||||
float be_current3 = htobe32(get_bridge_A(BRIDGE_AUX));
|
||||
memcpy(&entry[21], &be_current3, 4);
|
||||
|
||||
int32_t be_counter = htobe32(get_sensor_counter(SENSOR_DRIVE));
|
||||
@@ -34,35 +40,188 @@ esp_err_t send_log() {
|
||||
|
||||
entry[29] = get_sensor(SENSOR_DRIVE);
|
||||
entry[30] = get_sensor(SENSOR_JACK);
|
||||
entry[31] = fsm_get_state();*/
|
||||
entry[31] = fsm_get_state();
|
||||
|
||||
last_log_time = esp_timer_get_time();
|
||||
|
||||
return write_log(entry);
|
||||
}
|
||||
|
||||
|
||||
typedef enum {
|
||||
LED_STATE_DRIVING,
|
||||
LED_STATE_ERROR,
|
||||
LED_STATE_AWAKE,
|
||||
LED_STATE_CANCELLING,
|
||||
LED_STATE_START1,
|
||||
LED_STATE_START2,
|
||||
LED_STATE_START3,
|
||||
LED_STATE_START4,
|
||||
LED_STATE_BOOTING
|
||||
} led_state_t;
|
||||
|
||||
void driveLEDs(led_state_t state) {
|
||||
uint8_t patterns[4][12] = {
|
||||
{1,3,7,6,4,0},
|
||||
{7,0},
|
||||
{1,1,1,1,1,1, 1,1,1,3},
|
||||
{4,2}
|
||||
};
|
||||
switch(state) {
|
||||
case LED_STATE_DRIVING:
|
||||
i2c_set_led1(patterns[state][(esp_timer_get_time()/100000) % 6]);
|
||||
break;
|
||||
case LED_STATE_ERROR:
|
||||
i2c_set_led1(patterns[state][(esp_timer_get_time()/1000000) % 2]);
|
||||
break;
|
||||
case LED_STATE_AWAKE:
|
||||
i2c_set_led1(patterns[state][(esp_timer_get_time()/200000) % 10]);
|
||||
break;
|
||||
case LED_STATE_CANCELLING:
|
||||
i2c_set_led1(patterns[state][(esp_timer_get_time()/200000) % 2]);
|
||||
break;
|
||||
|
||||
case LED_STATE_BOOTING:
|
||||
i2c_set_led1(1);
|
||||
break;
|
||||
|
||||
case LED_STATE_START1:
|
||||
i2c_set_led1(0);
|
||||
break;
|
||||
case LED_STATE_START2:
|
||||
i2c_set_led1(1);
|
||||
break;
|
||||
case LED_STATE_START3:
|
||||
i2c_set_led1(3);
|
||||
break;
|
||||
case LED_STATE_START4:
|
||||
i2c_set_led1(7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void) {
|
||||
// Initialize storage and load parameters
|
||||
ESP_LOGI(TAG, "Initializing storage...");
|
||||
esp_err_t ret = storage_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Storage init failed, using defaults");
|
||||
esp_task_wdt_add(NULL);
|
||||
|
||||
// Say hello; turn on the lights
|
||||
esp_sleep_wakeup_cause_t cause = rtc_wakeup_cause();
|
||||
if (i2c_init() != ESP_OK) ESP_LOGE(TAG, "I2C FAILED");
|
||||
i2c_set_relays(0);
|
||||
driveLEDs(LED_STATE_BOOTING);
|
||||
|
||||
// Every boot we load parameters and monitor solar, no matter what
|
||||
if (adc_init() != ESP_OK) ESP_LOGE(TAG, "ADC FAILED");
|
||||
if (rtc_xtal_init() != ESP_OK) ESP_LOGE(TAG, "RTC FAILED");
|
||||
if (storage_init() != ESP_OK) ESP_LOGE(TAG, "STORAGE FAILED");
|
||||
if (log_init() != ESP_OK) ESP_LOGE(TAG, "LOG FAILED");
|
||||
if (run_solar_fsm() != ESP_OK) ESP_LOGE(TAG, "SOLAR FAILED");
|
||||
// TODO: Do a 12V check and enter deep sleep if there's a problem
|
||||
|
||||
|
||||
send_log();
|
||||
|
||||
// Check wake reasons
|
||||
// If button held, we stay #woke
|
||||
// If not it must've been the RTC - check alarms
|
||||
// If there's an alarm or the button was held, do a full boot
|
||||
// Full boot will handle things from there
|
||||
|
||||
// only truly wake up if we saw it on EXT0, or from alarm
|
||||
/*if (!rtc_is_set()) {
|
||||
//ESP_LOGI("MAIN", "RTC is not set. Can't sleep til then.");
|
||||
} else */if (cause == ESP_SLEEP_WAKEUP_EXT0) {
|
||||
ESP_LOGI("MAIN", "Woke from button press");
|
||||
} else {
|
||||
if (!alarm_tripped()) {
|
||||
//enter_deep_sleep();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize logging
|
||||
ESP_LOGI(TAG, "Initializing logging...");
|
||||
ret = log_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Log init failed");
|
||||
}
|
||||
/*** FULL BOOT ***/
|
||||
if (uart_init() != ESP_OK) ESP_LOGE(TAG, "UART FAILED");
|
||||
if (power_init() != ESP_OK) ESP_LOGE(TAG, "POWER FAILED");
|
||||
if (rf_init() != ESP_OK) ESP_LOGE(TAG, "RF FAILED");
|
||||
if (fsm_init() != ESP_OK) ESP_LOGE(TAG, "FSM FAILED");
|
||||
if (sensors_init() != ESP_OK) ESP_LOGE(TAG, "SENSORS FAILED");
|
||||
|
||||
/*** MAIN LOOP ***/
|
||||
|
||||
uart_start();
|
||||
|
||||
|
||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(100);
|
||||
|
||||
while(true) {
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
|
||||
send_log();
|
||||
i2c_poll_buttons();
|
||||
|
||||
if (i2c_get_button_state(0))
|
||||
reset_shutdown_timer();
|
||||
|
||||
switch (fsm_get_state()) {
|
||||
case STATE_IDLE:
|
||||
// LED cue for user
|
||||
if (i2c_get_button_ms(0) > 1600){
|
||||
driveLEDs(LED_STATE_START4);
|
||||
} else if (i2c_get_button_ms(0) > 1100){
|
||||
driveLEDs(LED_STATE_START3);
|
||||
} else if (i2c_get_button_ms(0) > 600){
|
||||
driveLEDs(LED_STATE_START2);
|
||||
} else if (i2c_get_button_ms(0) > 100){
|
||||
driveLEDs(LED_STATE_START1);
|
||||
} else{
|
||||
driveLEDs(LED_STATE_AWAKE);
|
||||
}
|
||||
|
||||
// when not actively moving we log at a low frequency
|
||||
if (esp_timer_get_time() > last_log_time + DEEP_SLEEP_US)
|
||||
send_log();
|
||||
|
||||
if(i2c_get_button_ms(0) > 2100)
|
||||
fsm_request(FSM_CMD_START);
|
||||
break;
|
||||
case STATE_UNDO_JACK:
|
||||
case STATE_UNDO_JACK_START:
|
||||
// assume it's running
|
||||
send_log();
|
||||
driveLEDs(LED_STATE_CANCELLING);
|
||||
if (i2c_get_button_tripped(0)) {
|
||||
ESP_LOGI(TAG, "AAAAH STOP!!!");
|
||||
fsm_request(FSM_CMD_STOP);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// assume it's running in every other case
|
||||
send_log();
|
||||
driveLEDs(LED_STATE_DRIVING);
|
||||
if (i2c_get_button_tripped(0)) {
|
||||
ESP_LOGI(TAG, "CTRL + Z PLZ!");
|
||||
fsm_request(FSM_CMD_UNDO);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (alarm_tripped()) {
|
||||
fsm_request(FSM_CMD_START);
|
||||
set_next_alarm();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*ESP_LOGI(TAG, "VOLTAGE: %2.3f | CURRENTS: %+2.8f %+2.8f %+2.8f",
|
||||
get_battery_V(),
|
||||
get_bridge_A(BRIDGE_DRIVE),
|
||||
get_bridge_A(BRIDGE_JACK),
|
||||
get_bridge_A(BRIDGE_AUX));*/
|
||||
|
||||
|
||||
|
||||
run_solar_fsm();
|
||||
|
||||
//check_shutdown_timer();
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
}
|
||||
@@ -34,194 +34,47 @@
|
||||
#include "storage.h"
|
||||
#include "rtc.h"
|
||||
|
||||
#define TAG "POWER"
|
||||
|
||||
// === GPIO Pin Definitions ===
|
||||
#define PIN_V_ISENS1 ADC_CHANNEL_6 // GPIO34
|
||||
#define PIN_V_ISENS2 ADC_CHANNEL_3 // GPIO39 / VN
|
||||
#define PIN_V_ISENS1 ADC_CHANNEL_0 // GPIO36 / VP
|
||||
#define PIN_V_ISENS2 ADC_CHANNEL_6 // GPIO34
|
||||
#define PIN_V_ISENS3 ADC_CHANNEL_7 // GPIO35
|
||||
#define PIN_V_BATTERY ADC_CHANNEL_0 // GPIO36 / VP
|
||||
#define PIN_V_BATTERY ADC_CHANNEL_3 // GPIO39 / VN
|
||||
#define PIN_V_SENS_BAT PIN_V_BATTERY
|
||||
|
||||
#define PIN_CHG_DISABLE GPIO_NUM_26
|
||||
#define PIN_CHG_BULK GPIO_NUM_27
|
||||
|
||||
#define AUTOZERO_THRESH 2000.0f // mA
|
||||
// update time
|
||||
#define UPDATE_MS 20
|
||||
#define UPDATE_S 0.02f
|
||||
|
||||
|
||||
typedef enum {
|
||||
CHG_T_LOWBAT = 0,
|
||||
CHG_T_BULK = 1,
|
||||
CHG_T_STEADY = 2,
|
||||
} charge_timer_t;
|
||||
|
||||
#define N_CHG_TIMERS 3
|
||||
RTC_DATA_ATTR charge_state_t current_charge_state = CHG_STATE_BULK;
|
||||
RTC_DATA_ATTR int64_t charge_timers[N_CHG_TIMERS] = {-1};
|
||||
|
||||
int64_t now;
|
||||
|
||||
charge_state_t get_charging_state() { return current_charge_state; }
|
||||
|
||||
void setTimerN(charge_timer_t i, int64_t sec) {
|
||||
// set the timer for <sec> in the future if it's currently less than now
|
||||
if (charge_timers[i] < now) {
|
||||
charge_timers[i] = now + sec;
|
||||
ESP_LOGI("BAT", "Set timer[%d] +%lld", i, (long long)sec);
|
||||
}
|
||||
}
|
||||
|
||||
void resetTimerN(charge_timer_t i) {
|
||||
charge_timers[i] = -1;
|
||||
}
|
||||
void resetBatTimers() {
|
||||
for (uint8_t i=0; i<N_CHG_TIMERS; i++)
|
||||
resetTimerN(i);
|
||||
}
|
||||
|
||||
bool getTimerN(charge_timer_t i) {
|
||||
if (charge_timers[i] < 0) return false;
|
||||
return system_rtc_get_raw_time() > charge_timers[i];
|
||||
}
|
||||
|
||||
#define BULK_CHARGE_S 20 //2*60*60
|
||||
#define FLOAT_STEADY_S 10 //30*60
|
||||
#define LOW_DETECT_S 10 //5*60
|
||||
|
||||
#define STEADY_MV 13000
|
||||
#define LOW_MV 12800
|
||||
|
||||
void run_charge_fsm() {
|
||||
now = system_rtc_get_raw_time();
|
||||
|
||||
//ESP_LOGI("BAT", "FSM STATE %d", current_charge_state);
|
||||
|
||||
if (rtc_is_set()) {
|
||||
switch(current_charge_state) {
|
||||
case CHG_STATE_BULK:
|
||||
// turn off bulk charging and go to float when time is up
|
||||
if (getTimerN(CHG_T_BULK)) {
|
||||
ESP_LOGI("BAT", "BULK -> FLOAT");
|
||||
current_charge_state = CHG_STATE_FLOAT;
|
||||
}
|
||||
break;
|
||||
|
||||
case CHG_STATE_FLOAT:
|
||||
if (getTimerN(CHG_T_STEADY)) {
|
||||
ESP_LOGI("BAT", "FLOAT -> OFF");
|
||||
current_charge_state = CHG_STATE_OFF;
|
||||
}
|
||||
if (get_battery_mV() > STEADY_MV) {
|
||||
setTimerN(CHG_T_STEADY, FLOAT_STEADY_S);
|
||||
} else {
|
||||
resetTimerN(CHG_T_STEADY);
|
||||
}
|
||||
// NO break; !! float should also kick into bulk with same triggers
|
||||
case CHG_STATE_OFF:
|
||||
|
||||
// after 5 minutes of low-ish battery go into bulk charge
|
||||
if (getTimerN(CHG_T_LOWBAT)) {
|
||||
ESP_LOGI("BAT", " -> BULK");
|
||||
current_charge_state = CHG_STATE_BULK;
|
||||
setTimerN(CHG_T_BULK, BULK_CHARGE_S);
|
||||
}
|
||||
if (get_battery_mV() < LOW_MV) {
|
||||
setTimerN(CHG_T_LOWBAT, LOW_DETECT_S);
|
||||
} else {
|
||||
resetTimerN(CHG_T_LOWBAT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
//ESP_LOGI("BAT", " -> BULK");
|
||||
current_charge_state = CHG_STATE_BULK;
|
||||
}
|
||||
|
||||
//rtc_gpio_hold_dis(PIN_CHG_BULK);
|
||||
//rtc_gpio_hold_dis(PIN_CHG_DISABLE);
|
||||
switch(current_charge_state) {
|
||||
case CHG_STATE_BULK:
|
||||
gpio_set_level(PIN_CHG_BULK, 1);
|
||||
gpio_set_level(PIN_CHG_DISABLE, 0);
|
||||
//ESP_LOGI("BAT", "BULK");
|
||||
break;
|
||||
case CHG_STATE_FLOAT:
|
||||
gpio_set_level(PIN_CHG_BULK, 0);
|
||||
gpio_set_level(PIN_CHG_DISABLE, 0);
|
||||
//ESP_LOGI("BAT", "FLOAT");
|
||||
break;
|
||||
case CHG_STATE_OFF:
|
||||
gpio_set_level(PIN_CHG_BULK, 0);
|
||||
gpio_set_level(PIN_CHG_DISABLE, 1);
|
||||
//ESP_LOGI("BAT", "OFF");
|
||||
break;
|
||||
}
|
||||
//rtc_gpio_hold_en(PIN_CHG_BULK);
|
||||
//rtc_gpio_hold_en(PIN_CHG_DISABLE);
|
||||
}
|
||||
int64_t now; // us
|
||||
|
||||
typedef struct {
|
||||
bool enabled; // Auto-zero active for this channel
|
||||
float threshold_ma; // Max current to consider "zero" (mA)
|
||||
float learned_offset_mv; // Accumulated zero offset (mV)
|
||||
bool initialized; // First valid zero established
|
||||
} autozero_t;
|
||||
static autozero_t autozero[N_BRIDGES] = {0};
|
||||
int64_t az_enable_time; // Timestamp to enable autozeroing at (negative to disable)
|
||||
float az_offset; // Accumulated zero offset
|
||||
bool az_initialized; // First valid zero established
|
||||
|
||||
// === E-Fuse (Software Breaker) Configuration ===
|
||||
static const char* currentLimits_A[N_BRIDGES] = {
|
||||
[BRIDGE_DRIVE] = "efuse_drive_A", //40000,
|
||||
[BRIDGE_AUX] = "efuse_aux_A", // 5000,
|
||||
[BRIDGE_JACK] = "efuse_jack_A" // 10000
|
||||
};
|
||||
static const float i2t_thresholds[N_BRIDGES] = { // A^2*s (tunable per bridge if needed)
|
||||
[BRIDGE_DRIVE] = 6.0f,
|
||||
[BRIDGE_AUX] = 6.0f,
|
||||
[BRIDGE_JACK] = 6.0f
|
||||
};
|
||||
static const float i_instant[N_BRIDGES] = { // Instant trip multiplier of I_rated
|
||||
[BRIDGE_DRIVE] = 15.0f,
|
||||
[BRIDGE_AUX] = 15.0f,
|
||||
[BRIDGE_JACK] = 15.0f
|
||||
};
|
||||
static const float cool_rate[N_BRIDGES] = { // Cooling constant (1/s)
|
||||
[BRIDGE_DRIVE] = 0.008f,
|
||||
[BRIDGE_AUX] = 0.008f,
|
||||
[BRIDGE_JACK] = 0.008f
|
||||
};
|
||||
static const int32_t cooldown_ms[N_BRIDGES] = { // Auto-reset delay after trip
|
||||
[BRIDGE_DRIVE] = 5000,
|
||||
[BRIDGE_AUX] = 5000,
|
||||
[BRIDGE_JACK] = 5000
|
||||
};
|
||||
|
||||
static float efuse_heat[N_BRIDGES] = {0};
|
||||
static uint64_t efuse_trip_time[N_BRIDGES] = {0}; // Timestamp when tripped
|
||||
static bool efuse_tripped[N_BRIDGES] = {false};
|
||||
bool ema_init;
|
||||
float ema_current;
|
||||
|
||||
float current; // with all the corrections applied
|
||||
|
||||
float heat;
|
||||
bool tripped;
|
||||
int64_t trip_time;
|
||||
} isens_channel_t;
|
||||
static isens_channel_t isens[N_BRIDGES] = {0};
|
||||
|
||||
// === ADC Handles ===
|
||||
static adc_oneshot_unit_handle_t adc1_handle = NULL;
|
||||
static adc_cali_handle_t adc_cali_handle = NULL;
|
||||
|
||||
// === EMA Filter State ===
|
||||
#define EMA_ALPHA_CURRENT 0.5f
|
||||
#define EMA_ALPHA_BATTERY 0.05f
|
||||
|
||||
static float ema_current[N_BRIDGES] = {0};
|
||||
static bool ema_init[N_BRIDGES] = {false};
|
||||
|
||||
static float ema_battery = 0.0f;
|
||||
static bool ema_battery_init = false;
|
||||
|
||||
// === Shared Volatile Outputs ===
|
||||
volatile int32_t bridgeCurrents_mA[N_BRIDGES] = {0};
|
||||
volatile int32_t batteryVoltage_mV = 0;
|
||||
|
||||
// === ADC Initialization ===
|
||||
static esp_err_t adc_init(void) {
|
||||
if (adc1_handle != NULL) {
|
||||
return ESP_OK; // Already initialized
|
||||
}
|
||||
|
||||
// ADC1 oneshot mode
|
||||
esp_err_t adc_init() {
|
||||
// ADC1 oneshot mode
|
||||
adc_oneshot_unit_init_cfg_t init_cfg = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
};
|
||||
@@ -246,51 +99,62 @@ static esp_err_t adc_init(void) {
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&cali_cfg, &adc_cali_handle));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
float get_raw_battery_voltage(void) {
|
||||
int adc_raw = 0;
|
||||
int voltage_mv = 0;
|
||||
|
||||
if (adc_oneshot_read(adc1_handle, PIN_V_SENS_BAT, &adc_raw)
|
||||
!= ESP_OK) { return NAN; }
|
||||
if (adc_cali_raw_to_voltage(adc_cali_handle, adc_raw, &voltage_mv)
|
||||
!= ESP_OK) { return NAN; }
|
||||
|
||||
// Voltage divider: 150kohm to 1Mohm -> gain = 1.15 -> scale = 1150/150
|
||||
return voltage_mv * 0.00766666666; // same as / 1000.0 * 1150.0 / 150.0;
|
||||
}
|
||||
|
||||
esp_err_t process_battery_voltage(void)
|
||||
{
|
||||
float raw = get_raw_battery_voltage();
|
||||
|
||||
if (!ema_battery_init) {
|
||||
ema_battery = (float)raw;
|
||||
ema_battery_init = true;
|
||||
} else {
|
||||
float alpha = get_param(PARAM_ADC_ALPHA_BATTERY).f32;
|
||||
if (isnan(raw)) {
|
||||
ESP_LOGI(TAG, "RAW BATTERY IS NAN");
|
||||
} else {
|
||||
if (isnan(ema_battery) || isnan(alpha))
|
||||
ema_battery = raw;
|
||||
else
|
||||
ema_battery = alpha * (float)raw + (1.0f - alpha) * ema_battery;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void autozero_enable(bridge_t bridge, bool enable) {
|
||||
if (bridge >= N_BRIDGES) return;
|
||||
autozero[bridge].enabled = enable;
|
||||
if (!enable) {
|
||||
autozero[bridge].learned_offset_mv = 0.0f;
|
||||
autozero[bridge].initialized = false;
|
||||
}
|
||||
void set_autozero(bridge_t bridge) {
|
||||
// enable autozeroing for this bridge 1 second from now
|
||||
isens[bridge].az_enable_time = now+1000000;
|
||||
//ESP_LOGI(TAG, "KILLING BRIDGE %d; %lld -> %lld", bridge, (long long int) now, (long long int) isens[bridge].az_enable_time);
|
||||
}
|
||||
|
||||
void autozero_set_threshold(bridge_t bridge, float threshold_ma) {
|
||||
if (bridge >= N_BRIDGES) return;
|
||||
autozero[bridge].threshold_ma = fmaxf(0.0f, threshold_ma);
|
||||
}
|
||||
|
||||
float autozero_get_offset_mv(bridge_t bridge) {
|
||||
if (bridge >= N_BRIDGES) return 0.0f;
|
||||
return autozero[bridge].learned_offset_mv;
|
||||
}
|
||||
|
||||
void autozero_reset(bridge_t bridge) {
|
||||
if (bridge >= N_BRIDGES) return;
|
||||
autozero[bridge].learned_offset_mv = 0.0f;
|
||||
autozero[bridge].initialized = false;
|
||||
}
|
||||
|
||||
void autozero_reset_all(void) {
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
autozero_reset((bridge_t)i);
|
||||
}
|
||||
}
|
||||
|
||||
// === Raw Current Reading (mA) ===
|
||||
static int32_t read_bridge_current_raw(bridge_t bridge) {
|
||||
int adc_raw = 0;
|
||||
esp_err_t process_bridge_current(bridge_t bridge) {
|
||||
int adc_raw = 0;
|
||||
int voltage_mv = 0;
|
||||
|
||||
isens_channel_t *channel = &isens[bridge];
|
||||
|
||||
adc_channel_t pin;
|
||||
switch(bridge) {
|
||||
case BRIDGE_DRIVE: pin = PIN_V_ISENS3; break;
|
||||
case BRIDGE_AUX: pin = PIN_V_ISENS1; break;
|
||||
case BRIDGE_DRIVE: pin = PIN_V_ISENS1; break;
|
||||
case BRIDGE_JACK: pin = PIN_V_ISENS2; break;
|
||||
default: return -42069;
|
||||
case BRIDGE_AUX: pin = PIN_V_ISENS3; break;
|
||||
default: return -42069; // lol
|
||||
}
|
||||
|
||||
if (adc_oneshot_read(adc1_handle, pin, &adc_raw) != ESP_OK) {
|
||||
@@ -299,205 +163,171 @@ static int32_t read_bridge_current_raw(bridge_t bridge) {
|
||||
if (adc_cali_raw_to_voltage(adc_cali_handle, adc_raw, &voltage_mv) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
float current_sense_mv = (float)voltage_mv;
|
||||
autozero_t *az = &autozero[bridge];
|
||||
|
||||
// === AUTO-ZERO LEARNING PHASE ===
|
||||
if (az->enabled && get_bridge_state(bridge)==0) {
|
||||
float raw_current_ma = 0.0f;
|
||||
switch (bridge) {
|
||||
case BRIDGE_JACK:
|
||||
case BRIDGE_AUX:
|
||||
// ACS37042KLHBLT-030B3 is 30A capable and 44 mV/A
|
||||
raw_current_ma = (current_sense_mv - 1650.0f) * 1000.0f / 44.0f;
|
||||
break;
|
||||
case BRIDGE_DRIVE:
|
||||
// ACS37220LEZATR-100B3 is 100A capable and 13.2 mV/A
|
||||
raw_current_ma = (current_sense_mv - 1650.0f) * 1000.0f / 13.20f;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fabsf(raw_current_ma) <= az->threshold_ma) {
|
||||
// Valid zero sample
|
||||
if (!az->initialized) {
|
||||
az->learned_offset_mv = current_sense_mv - 1650.0f;
|
||||
az->initialized = true;
|
||||
} else {
|
||||
// EMA on offset (slow adaptation)
|
||||
float alpha = 0.1f;
|
||||
az->learned_offset_mv = alpha * (current_sense_mv - 1650.0f) +
|
||||
(1.0f - alpha) * az->learned_offset_mv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === APPLY AUTO-ZERO OFFSET ===
|
||||
float corrected_mv = current_sense_mv - az->learned_offset_mv;
|
||||
|
||||
int32_t offset_mv = (int32_t)(corrected_mv - 1650.0f);
|
||||
int32_t current_ma = 0;
|
||||
|
||||
|
||||
float raw_a = NAN;
|
||||
|
||||
switch (bridge) {
|
||||
case BRIDGE_JACK:
|
||||
case BRIDGE_AUX:
|
||||
current_ma = offset_mv * 1000 / 44; // 44 mV/A
|
||||
// ACS37042KLHBLT-030B3 is 30A capable and 44 mV/A
|
||||
raw_a = (voltage_mv - 1650.0f) / 44.0f;
|
||||
break;
|
||||
case BRIDGE_DRIVE:
|
||||
current_ma = offset_mv * 10000 / 132; // 13.2 mV/A
|
||||
// ACS37220LEZATR-100B3 is 100A capable and 13.2 mV/A
|
||||
raw_a = (voltage_mv - 1650.0f) / 13.2f;
|
||||
break;
|
||||
}
|
||||
|
||||
return current_ma;
|
||||
}
|
||||
|
||||
// === Raw Battery Voltage Reading (mV) ===
|
||||
static int32_t read_battery_voltage_raw(void)
|
||||
{
|
||||
int adc_raw = 0;
|
||||
int voltage_mv = 0;
|
||||
|
||||
if (adc_oneshot_read(adc1_handle, PIN_V_SENS_BAT, &adc_raw) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
if (adc_cali_raw_to_voltage(adc_cali_handle, adc_raw, &voltage_mv) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Voltage divider: 150kΩ to 1MΩ → gain = 1.15 → scale = 1150/150
|
||||
return (int32_t)voltage_mv * 1150 / 150;
|
||||
}
|
||||
|
||||
// === EMA Filter Update ===
|
||||
static void apply_ema(float *state, bool *init, float alpha, int32_t raw, volatile int32_t *out)
|
||||
{
|
||||
if (!*init) {
|
||||
*state = (float)raw;
|
||||
*init = true;
|
||||
|
||||
if (!channel->ema_init) {
|
||||
channel->ema_current = (float)raw_a;
|
||||
channel->ema_init = true;
|
||||
} else {
|
||||
*state = alpha * (float)raw + (1.0f - alpha) * *state;
|
||||
}
|
||||
*out = (int32_t)(*state + 0.5f);
|
||||
}
|
||||
|
||||
// === Public Accessors ===
|
||||
int32_t get_bridge_mA(uint8_t bridge)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return -1;
|
||||
return (int32_t)bridgeCurrents_mA[bridge];
|
||||
}
|
||||
|
||||
int32_t get_battery_mV(void)
|
||||
{
|
||||
return (int32_t)batteryVoltage_mV;
|
||||
}
|
||||
|
||||
// === E-Fuse: Trip Logic (called every cycle) ===
|
||||
static void efuse_update(uint8_t bridge, float I, float dt, uint64_t now)
|
||||
{
|
||||
float I_rated = (float)get_param_i8(currentLimits_A[bridge]);
|
||||
float I_norm = I / I_rated;
|
||||
|
||||
// Instant trip on extreme overcurrent
|
||||
if (I_norm >= i_instant[bridge]) {
|
||||
efuse_tripped[bridge] = true;
|
||||
efuse_trip_time[bridge] = now;
|
||||
return;
|
||||
}
|
||||
|
||||
// Cooling when below threshold
|
||||
if (I_norm < 1.1f) {
|
||||
efuse_heat[bridge] -= efuse_heat[bridge] * cool_rate[bridge] * dt;
|
||||
efuse_heat[bridge] = fmaxf(0.0f, efuse_heat[bridge]);
|
||||
efuse_tripped[bridge] = false; // Auto-clear if cooled
|
||||
return;
|
||||
}
|
||||
|
||||
// Accumulate heat (I²t)
|
||||
efuse_heat[bridge] += (I_norm * I_norm) * dt;
|
||||
|
||||
if (efuse_heat[bridge] >= i2t_thresholds[bridge]) {
|
||||
efuse_tripped[bridge] = true;
|
||||
efuse_trip_time[bridge] = now;
|
||||
}
|
||||
}
|
||||
|
||||
// === E-Fuse: Auto-Reset After Cooldown ===
|
||||
static void efuse_cooldown_check(uint64_t now)
|
||||
{
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
if (efuse_tripped[i] &&
|
||||
(now - efuse_trip_time[i]) >= (cooldown_ms[i] * 1000ULL)) {
|
||||
efuse_heat[i] = 0.0f;
|
||||
efuse_tripped[i] = false;
|
||||
float alpha = get_param(PARAM_ADC_ALPHA_ISENS).f32;
|
||||
if (isnan(raw_a)) {
|
||||
ESP_LOGI(TAG, "RAW BATTERY IS NAN");
|
||||
channel->ema_current = NAN;
|
||||
} else {
|
||||
if (isnan(ema_battery) || isnan(alpha))
|
||||
channel->ema_current = raw_a;
|
||||
else
|
||||
channel->ema_current = alpha * raw_a + (1.0f - alpha) * channel->ema_current;
|
||||
}
|
||||
}
|
||||
|
||||
// === AUTO-ZERO LEARNING PHASE ===
|
||||
if (now > channel->az_enable_time) {
|
||||
//ESP_LOGI(TAG, "AZING %d", bridge);
|
||||
float db = get_param(PARAM_ADC_DB_IAZ).f32;
|
||||
if (isnan(db) || fabsf(channel->ema_current) <= db) {
|
||||
// Valid zero sample
|
||||
if (!channel->az_initialized) {
|
||||
channel->az_offset = channel->ema_current;
|
||||
channel->az_initialized = true;
|
||||
} else {
|
||||
float alpha = get_param(PARAM_ADC_ALPHA_IAZ).f32;
|
||||
if (isnan(raw_a)) {
|
||||
ESP_LOGI(TAG, "RAW BATTERY IS NAN");
|
||||
} else {
|
||||
if (isnan(ema_battery) || isnan(alpha))
|
||||
channel->az_offset = channel->ema_current;
|
||||
else
|
||||
channel->az_offset = alpha * channel->ema_current +
|
||||
(1.0f - alpha) * channel->az_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the offset
|
||||
channel->current = channel->ema_current - channel->az_offset;
|
||||
|
||||
|
||||
// PARAMETERS FOR E-FUSING ALGORITHM
|
||||
// PARAM_EFUSE_KINST : ratio of nominal current that should cause an immediate shutdown
|
||||
// PARAM_EFUSE_TCOOL : cooldown timer from trip (in microseconds)
|
||||
// PARAM_EFUSE_TAUCOOL : speed of cooldown for heating (units are 1/s; bigger = faster cooldown)
|
||||
|
||||
// Monitor E-fusing
|
||||
float I_nominal = NAN;
|
||||
switch(bridge) {
|
||||
case BRIDGE_DRIVE:
|
||||
I_nominal = get_param(PARAM_EFUSE_INOM_1).f32;
|
||||
break;
|
||||
case BRIDGE_JACK:
|
||||
I_nominal = get_param(PARAM_EFUSE_INOM_2).f32;
|
||||
break;
|
||||
case BRIDGE_AUX:
|
||||
I_nominal = get_param(PARAM_EFUSE_INOM_3).f32;
|
||||
break;
|
||||
}
|
||||
|
||||
// Normalize the current as a fraction of rated current
|
||||
float I_norm = fabsf(channel->current / I_nominal);
|
||||
|
||||
// Instant trip on extreme overcurrent
|
||||
if (I_norm >= get_param(PARAM_EFUSE_KINST).f32) {
|
||||
channel->tripped = true;
|
||||
channel->trip_time = now;
|
||||
ESP_LOGI(TAG, "FUSE TRIP: Inom: %+.5f HEAT:%+2.5f", I_norm, channel->heat);
|
||||
return ESP_OK; // no more processing, if we're over, we're over
|
||||
}
|
||||
|
||||
// Accumulate heat
|
||||
channel->heat += (I_norm * I_norm) * UPDATE_S;
|
||||
|
||||
// Only do cooling when below threshold
|
||||
if (I_norm < 1.0f) {
|
||||
// if we are hot we radiate more heat
|
||||
// (I^2/I^2*t) * (1/t) * t = I^2/I^2*t
|
||||
channel->heat -= channel->heat * get_param(PARAM_EFUSE_TAUCOOL).f32 * UPDATE_S;
|
||||
channel->heat = fmaxf(0.0f, channel->heat); // keep it from going negative
|
||||
// channel.tripped = false; // Auto-clear if cooled (WTF why this is insane)
|
||||
}
|
||||
|
||||
// If built-up heat exceeds the time limit, trip
|
||||
// Recall units of heat are (current_actual^2/current_nominal^2)*time
|
||||
// Ergo, heat is measured in seconds
|
||||
if (channel->heat > get_param(PARAM_EFUSE_HEAT_THRESH).f32) {
|
||||
channel->tripped = true;
|
||||
channel->trip_time = now;
|
||||
|
||||
// If we're not overheated
|
||||
// And enough time has passed
|
||||
// Go ahead and reset the e-fuse
|
||||
} else if (channel->tripped &&
|
||||
(now - channel->trip_time) > get_param(PARAM_EFUSE_TCOOL).i64) {
|
||||
channel->tripped = false;
|
||||
// channel.heat = 0.0f // I think we should wait for the e-fuse to catch up
|
||||
}
|
||||
|
||||
if (bridge == BRIDGE_DRIVE)
|
||||
ESP_LOGI(TAG, "FUSE: Inom: %+.5f HEAT:%+2.5f", I_norm, channel->heat);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
// === Public Accessors ===
|
||||
float get_bridge_A(bridge_t bridge)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return NAN;
|
||||
return isens[bridge].current;
|
||||
}
|
||||
|
||||
float get_battery_V(void)
|
||||
{
|
||||
if (ema_battery_init)
|
||||
return ema_battery;
|
||||
return get_raw_battery_voltage();
|
||||
}
|
||||
|
||||
// === Public E-Fuse Controls ===
|
||||
void efuse_reset_all(void)
|
||||
/*void efuse_reset_all(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
efuse_heat[i] = 0.0f;
|
||||
efuse_tripped[i] = false;
|
||||
isens[i].heat = 0.0f;
|
||||
isens[i].tripped = false;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
bool efuse_is_tripped(uint8_t bridge)
|
||||
bool efuse_is_tripped(bridge_t bridge)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return false;
|
||||
return efuse_tripped[bridge];
|
||||
return isens[bridge].tripped;
|
||||
}
|
||||
|
||||
|
||||
// === Power Management Task ===
|
||||
void power_mgmt_task(void *param) {
|
||||
esp_task_wdt_add(NULL);
|
||||
|
||||
/*gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << PIN_CHG_DISABLE) | (1ULL << PIN_CHG_BULK),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io_conf);*/
|
||||
|
||||
/*// Enable RTC GPIO domain (required for hold)
|
||||
rtc_gpio_init(PIN_CHG_DISABLE);
|
||||
rtc_gpio_init(PIN_CHG_BULK);
|
||||
|
||||
// Set as output
|
||||
rtc_gpio_set_direction(PIN_CHG_DISABLE, RTC_GPIO_MODE_OUTPUT_ONLY);
|
||||
rtc_gpio_set_direction(PIN_CHG_BULK, RTC_GPIO_MODE_OUTPUT_ONLY);
|
||||
|
||||
// Optional: set initial level (will be held)
|
||||
//rtc_gpio_set_level(PIN_CHG_DISABLE, 1); // e.g., start disabled
|
||||
//rtc_gpio_set_level(PIN_CHG_BULK, 0);
|
||||
|
||||
// **Critical: Enable hold function**
|
||||
rtc_gpio_hold_en(PIN_CHG_DISABLE);
|
||||
rtc_gpio_hold_en(PIN_CHG_BULK);*/
|
||||
|
||||
ESP_ERROR_CHECK(adc_init());
|
||||
|
||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(20);
|
||||
|
||||
// Optional: Enable auto-zero with default threshold
|
||||
autozero_enable(BRIDGE_DRIVE, true);
|
||||
autozero_enable(BRIDGE_AUX, true);
|
||||
autozero_enable(BRIDGE_JACK, true);
|
||||
|
||||
autozero_set_threshold(BRIDGE_DRIVE, AUTOZERO_THRESH);
|
||||
autozero_set_threshold(BRIDGE_AUX, AUTOZERO_THRESH);
|
||||
autozero_set_threshold(BRIDGE_JACK, AUTOZERO_THRESH);
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(UPDATE_MS);
|
||||
|
||||
//uint64_t last_wake_time = esp_timer_get_time();
|
||||
//const uint64_t period = 5000; // 100 us => 10kHz
|
||||
while (1) {
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
uint64_t now_us = esp_timer_get_time();
|
||||
now = esp_timer_get_time(); // us
|
||||
|
||||
/*if (now - last_wake_time < period) {
|
||||
uint32_t delay_us = (period - (now - last_wake_time)) / 1000;
|
||||
@@ -508,43 +338,20 @@ void power_mgmt_task(void *param) {
|
||||
|
||||
// Sample currents
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
int32_t raw_ma = read_bridge_current_raw((bridge_t)i);
|
||||
apply_ema(&ema_current[i], &ema_init[i], EMA_ALPHA_CURRENT,
|
||||
raw_ma, &bridgeCurrents_mA[i]);
|
||||
|
||||
// Reset spike timer if under limit
|
||||
/*if (bridgeCurrents_mA[i] < currentLimits_mA[i]) {
|
||||
currentSpikeSafeTimes[i] = now + CURRENT_SPIKE_TIME_US;
|
||||
}*/
|
||||
|
||||
// === E-FUSE UPDATE ===
|
||||
float I = (float)bridgeCurrents_mA[i] / 1000.0f;
|
||||
float dt = 0.020f; // 20 ms task period
|
||||
efuse_update(i, I, dt, now_us);
|
||||
process_bridge_current(i);
|
||||
}
|
||||
|
||||
/*ESP_LOGI("PWR", "[ %6ld | %6ld | %6ld mA ] { %6ld mV }",
|
||||
(long)bridgeCurrents_mA[BRIDGE_DRIVE],
|
||||
(long)bridgeCurrents_mA[BRIDGE_JACK],
|
||||
(long)bridgeCurrents_mA[BRIDGE_AUX],
|
||||
(long)batteryVoltage_mV);*/
|
||||
|
||||
// Sample battery
|
||||
int32_t raw_bat = read_battery_voltage_raw();
|
||||
apply_ema(&ema_battery, &ema_battery_init, EMA_ALPHA_BATTERY,
|
||||
raw_bat, &batteryVoltage_mV);
|
||||
|
||||
|
||||
//run_charge_fsm();
|
||||
efuse_cooldown_check(now_us);
|
||||
process_battery_voltage();
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
}
|
||||
|
||||
void start_power() {
|
||||
esp_err_t power_init() {
|
||||
xTaskCreate(power_mgmt_task, "PWR", 4096, NULL, 5, NULL);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void shutdown_power() {
|
||||
|
||||
esp_err_t power_stop() {
|
||||
return ESP_OK;
|
||||
}
|
||||
546
main/power_mgmt.c.old
Normal file
546
main/power_mgmt.c.old
Normal file
@@ -0,0 +1,546 @@
|
||||
/*
|
||||
* power_mgmt.c
|
||||
*
|
||||
* 1 kHz power-management task:
|
||||
* • Samples all three H-bridge current sensors (DRIVE, AUX, JACK)
|
||||
* • Samples battery voltage (BAT)
|
||||
* • Applies EMA filtering on every channel
|
||||
* • Updates shared volatile globals for the control FSM
|
||||
* • Handles over-current spike protection
|
||||
*
|
||||
* Updated to modern ESP-IDF ADC API (line fitting)
|
||||
* All variables now defined locally
|
||||
*
|
||||
* Created on: Nov 10, 2025
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "driver/rtc_io.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_adc/adc_cali.h"
|
||||
#include "esp_adc/adc_cali_scheme.h"
|
||||
#include "esp_timer.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "control_fsm.h"
|
||||
#include "soc/rtc_io_reg.h"
|
||||
#include "power_mgmt.h"
|
||||
|
||||
#include "storage.h"
|
||||
#include "rtc.h"
|
||||
|
||||
// === GPIO Pin Definitions ===
|
||||
#define PIN_V_ISENS1 ADC_CHANNEL_0 // GPIO36 / VP
|
||||
#define PIN_V_ISENS2 ADC_CHANNEL_6 // GPIO34
|
||||
#define PIN_V_ISENS3 ADC_CHANNEL_7 // GPIO35
|
||||
#define PIN_V_BATTERY ADC_CHANNEL_3 // GPIO39 / VN
|
||||
#define PIN_V_SENS_BAT PIN_V_BATTERY
|
||||
|
||||
#define PIN_CHG_BULK GPIO_NUM_26
|
||||
|
||||
#define AUTOZERO_THRESH 2000.0f // mA
|
||||
|
||||
|
||||
typedef enum {
|
||||
CHG_T_LOWBAT = 0,
|
||||
CHG_T_BULK = 1,
|
||||
CHG_T_STEADY = 2,
|
||||
} charge_timer_t;
|
||||
|
||||
#define N_CHG_TIMERS 3
|
||||
RTC_DATA_ATTR charge_state_t current_charge_state = CHG_STATE_BULK;
|
||||
RTC_DATA_ATTR int64_t charge_timers[N_CHG_TIMERS] = {-1};
|
||||
|
||||
int64_t now;
|
||||
|
||||
charge_state_t get_charging_state() { return current_charge_state; }
|
||||
|
||||
void setTimerN(charge_timer_t i, int64_t sec) {
|
||||
// set the timer for <sec> in the future if it's currently less than now
|
||||
if (charge_timers[i] < now) {
|
||||
charge_timers[i] = now + sec;
|
||||
ESP_LOGI("BAT", "Set timer[%d] +%lld", i, (long long)sec);
|
||||
}
|
||||
}
|
||||
|
||||
void resetTimerN(charge_timer_t i) {
|
||||
charge_timers[i] = -1;
|
||||
}
|
||||
void resetBatTimers() {
|
||||
for (uint8_t i=0; i<N_CHG_TIMERS; i++)
|
||||
resetTimerN(i);
|
||||
}
|
||||
|
||||
bool getTimerN(charge_timer_t i) {
|
||||
if (charge_timers[i] < 0) return false;
|
||||
return system_rtc_get_raw_time() > charge_timers[i];
|
||||
}
|
||||
|
||||
#define BULK_CHARGE_S 20 //2*60*60
|
||||
#define FLOAT_STEADY_S 10 //30*60
|
||||
#define LOW_DETECT_S 10 //5*60
|
||||
|
||||
#define STEADY_MV 13000
|
||||
#define LOW_MV 12800
|
||||
|
||||
void run_charge_fsm() {
|
||||
now = system_rtc_get_raw_time();
|
||||
|
||||
//ESP_LOGI("BAT", "FSM STATE %d", current_charge_state);
|
||||
|
||||
if (rtc_is_set()) {
|
||||
switch(current_charge_state) {
|
||||
case CHG_STATE_BULK:
|
||||
// turn off bulk charging and go to float when time is up
|
||||
if (getTimerN(CHG_T_BULK)) {
|
||||
ESP_LOGI("BAT", "BULK -> FLOAT");
|
||||
current_charge_state = CHG_STATE_FLOAT;
|
||||
}
|
||||
break;
|
||||
|
||||
case CHG_STATE_FLOAT:
|
||||
if (getTimerN(CHG_T_STEADY)) {
|
||||
ESP_LOGI("BAT", "FLOAT -> OFF");
|
||||
current_charge_state = CHG_STATE_OFF;
|
||||
}
|
||||
if (get_battery_mV() > STEADY_MV) {
|
||||
setTimerN(CHG_T_STEADY, FLOAT_STEADY_S);
|
||||
} else {
|
||||
resetTimerN(CHG_T_STEADY);
|
||||
}
|
||||
// NO break; !! float should also kick into bulk with same triggers
|
||||
case CHG_STATE_OFF:
|
||||
|
||||
// after 5 minutes of low-ish battery go into bulk charge
|
||||
if (getTimerN(CHG_T_LOWBAT)) {
|
||||
ESP_LOGI("BAT", " -> BULK");
|
||||
current_charge_state = CHG_STATE_BULK;
|
||||
setTimerN(CHG_T_BULK, BULK_CHARGE_S);
|
||||
}
|
||||
if (get_battery_mV() < LOW_MV) {
|
||||
setTimerN(CHG_T_LOWBAT, LOW_DETECT_S);
|
||||
} else {
|
||||
resetTimerN(CHG_T_LOWBAT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
//ESP_LOGI("BAT", " -> BULK");
|
||||
current_charge_state = CHG_STATE_BULK;
|
||||
}
|
||||
|
||||
//rtc_gpio_hold_dis(PIN_CHG_BULK);
|
||||
//rtc_gpio_hold_dis(PIN_CHG_DISABLE);
|
||||
switch(current_charge_state) {
|
||||
case CHG_STATE_BULK:
|
||||
gpio_set_level(PIN_CHG_BULK, 1);
|
||||
//ESP_LOGI("BAT", "BULK");
|
||||
break;
|
||||
case CHG_STATE_FLOAT:
|
||||
gpio_set_level(PIN_CHG_BULK, 0);
|
||||
//ESP_LOGI("BAT", "FLOAT");
|
||||
break;
|
||||
case CHG_STATE_OFF:
|
||||
gpio_set_level(PIN_CHG_BULK, 0);
|
||||
//ESP_LOGI("BAT", "OFF");
|
||||
break;
|
||||
}
|
||||
//rtc_gpio_hold_en(PIN_CHG_BULK);
|
||||
//rtc_gpio_hold_en(PIN_CHG_DISABLE);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
bool enabled; // Auto-zero active for this channel
|
||||
float threshold_ma; // Max current to consider "zero" (mA)
|
||||
float learned_offset_mv; // Accumulated zero offset (mV)
|
||||
bool initialized; // First valid zero established
|
||||
} autozero_t;
|
||||
static autozero_t autozero[N_BRIDGES] = {0};
|
||||
|
||||
// === E-Fuse (Software Breaker) Configuration ===
|
||||
static const char* currentLimits_A[N_BRIDGES] = {
|
||||
[BRIDGE_DRIVE] = "efuse_drive_A", //40000,
|
||||
[BRIDGE_AUX] = "efuse_aux_A", // 5000,
|
||||
[BRIDGE_JACK] = "efuse_jack_A" // 10000
|
||||
};
|
||||
static const float i2t_thresholds[N_BRIDGES] = { // A^2*s (tunable per bridge if needed)
|
||||
[BRIDGE_DRIVE] = 6.0f,
|
||||
[BRIDGE_AUX] = 6.0f,
|
||||
[BRIDGE_JACK] = 6.0f
|
||||
};
|
||||
static const float i_instant[N_BRIDGES] = { // Instant trip multiplier of I_rated
|
||||
[BRIDGE_DRIVE] = 15.0f,
|
||||
[BRIDGE_AUX] = 15.0f,
|
||||
[BRIDGE_JACK] = 15.0f
|
||||
};
|
||||
static const float cool_rate[N_BRIDGES] = { // Cooling constant (1/s)
|
||||
[BRIDGE_DRIVE] = 0.008f,
|
||||
[BRIDGE_AUX] = 0.008f,
|
||||
[BRIDGE_JACK] = 0.008f
|
||||
};
|
||||
static const int32_t cooldown_ms[N_BRIDGES] = { // Auto-reset delay after trip
|
||||
[BRIDGE_DRIVE] = 5000,
|
||||
[BRIDGE_AUX] = 5000,
|
||||
[BRIDGE_JACK] = 5000
|
||||
};
|
||||
|
||||
static float efuse_heat[N_BRIDGES] = {0};
|
||||
static uint64_t efuse_trip_time[N_BRIDGES] = {0}; // Timestamp when tripped
|
||||
static bool efuse_tripped[N_BRIDGES] = {false};
|
||||
|
||||
// === ADC Handles ===
|
||||
static adc_oneshot_unit_handle_t adc1_handle = NULL;
|
||||
static adc_cali_handle_t adc_cali_handle = NULL;
|
||||
|
||||
// === EMA Filter State ===
|
||||
#define EMA_ALPHA_CURRENT 0.5f
|
||||
#define EMA_ALPHA_BATTERY 0.05f
|
||||
|
||||
static float ema_current[N_BRIDGES] = {0};
|
||||
static bool ema_init[N_BRIDGES] = {false};
|
||||
|
||||
static float ema_battery = 0.0f;
|
||||
static bool ema_battery_init = false;
|
||||
|
||||
// === Shared Volatile Outputs ===
|
||||
volatile int32_t bridgeCurrents_mA[N_BRIDGES] = {0};
|
||||
volatile int32_t batteryVoltage_mV = 0;
|
||||
|
||||
// === ADC Initialization ===
|
||||
static esp_err_t adc_init(void) {
|
||||
if (adc1_handle != NULL) {
|
||||
return ESP_OK; // Already initialized
|
||||
}
|
||||
|
||||
// ADC1 oneshot mode
|
||||
adc_oneshot_unit_init_cfg_t init_cfg = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_cfg, &adc1_handle));
|
||||
|
||||
// Configure all channels
|
||||
adc_oneshot_chan_cfg_t chan_cfg = {
|
||||
.atten = ADC_ATTEN_DB_11,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_ISENS1, &chan_cfg));
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_ISENS2, &chan_cfg));
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_ISENS3, &chan_cfg));
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, PIN_V_SENS_BAT, &chan_cfg));
|
||||
|
||||
// Line fitting calibration (modern scheme)
|
||||
adc_cali_line_fitting_config_t cali_cfg = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.atten = ADC_ATTEN_DB_11,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&cali_cfg, &adc_cali_handle));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void autozero_enable(bridge_t bridge, bool enable) {
|
||||
if (bridge >= N_BRIDGES) return;
|
||||
autozero[bridge].enabled = enable;
|
||||
if (!enable) {
|
||||
autozero[bridge].learned_offset_mv = 0.0f;
|
||||
autozero[bridge].initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
void autozero_set_threshold(bridge_t bridge, float threshold_ma) {
|
||||
if (bridge >= N_BRIDGES) return;
|
||||
autozero[bridge].threshold_ma = fmaxf(0.0f, threshold_ma);
|
||||
}
|
||||
|
||||
float autozero_get_offset_mv(bridge_t bridge) {
|
||||
if (bridge >= N_BRIDGES) return 0.0f;
|
||||
return autozero[bridge].learned_offset_mv;
|
||||
}
|
||||
|
||||
void autozero_reset(bridge_t bridge) {
|
||||
if (bridge >= N_BRIDGES) return;
|
||||
autozero[bridge].learned_offset_mv = 0.0f;
|
||||
autozero[bridge].initialized = false;
|
||||
}
|
||||
|
||||
void autozero_reset_all(void) {
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
autozero_reset((bridge_t)i);
|
||||
}
|
||||
}
|
||||
|
||||
// === Raw Current Reading (mA) ===
|
||||
static int32_t read_bridge_current_raw(bridge_t bridge) {
|
||||
int adc_raw = 0;
|
||||
int voltage_mv = 0;
|
||||
|
||||
adc_channel_t pin;
|
||||
switch(bridge) {
|
||||
case BRIDGE_DRIVE: pin = PIN_V_ISENS1; break;
|
||||
case BRIDGE_AUX: pin = PIN_V_ISENS3; break;
|
||||
case BRIDGE_JACK: pin = PIN_V_ISENS2; break;
|
||||
default: return -42069; // lol
|
||||
}
|
||||
|
||||
if (adc_oneshot_read(adc1_handle, pin, &adc_raw) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
if (adc_cali_raw_to_voltage(adc_cali_handle, adc_raw, &voltage_mv) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
float current_sense_mv = (float)voltage_mv;
|
||||
autozero_t *az = &autozero[bridge];
|
||||
|
||||
// === AUTO-ZERO LEARNING PHASE ===
|
||||
if (az->enabled && get_bridge_state(bridge)==0) {
|
||||
float raw_current_ma = 0.0f;
|
||||
switch (bridge) {
|
||||
case BRIDGE_JACK:
|
||||
case BRIDGE_AUX:
|
||||
// ACS37042KLHBLT-030B3 is 30A capable and 44 mV/A
|
||||
raw_current_ma = (current_sense_mv - 1650.0f) * 1000.0f / 44.0f;
|
||||
break;
|
||||
case BRIDGE_DRIVE:
|
||||
// ACS37220LEZATR-100B3 is 100A capable and 13.2 mV/A
|
||||
raw_current_ma = (current_sense_mv - 1650.0f) * 1000.0f / 13.20f;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fabsf(raw_current_ma) <= az->threshold_ma) {
|
||||
// Valid zero sample
|
||||
if (!az->initialized) {
|
||||
az->learned_offset_mv = current_sense_mv - 1650.0f;
|
||||
az->initialized = true;
|
||||
} else {
|
||||
// EMA on offset (slow adaptation)
|
||||
float alpha = 0.1f;
|
||||
az->learned_offset_mv = alpha * (current_sense_mv - 1650.0f) +
|
||||
(1.0f - alpha) * az->learned_offset_mv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === APPLY AUTO-ZERO OFFSET ===
|
||||
float corrected_mv = current_sense_mv - az->learned_offset_mv;
|
||||
|
||||
int32_t offset_mv = (int32_t)(corrected_mv - 1650.0f);
|
||||
int32_t current_ma = 0;
|
||||
|
||||
switch (bridge) {
|
||||
case BRIDGE_JACK:
|
||||
case BRIDGE_AUX:
|
||||
current_ma = offset_mv * 1000 / 44; // 44 mV/A
|
||||
break;
|
||||
case BRIDGE_DRIVE:
|
||||
current_ma = offset_mv * 10000 / 132; // 13.2 mV/A
|
||||
break;
|
||||
}
|
||||
|
||||
return current_ma;
|
||||
}
|
||||
|
||||
// === Raw Battery Voltage Reading (mV) ===
|
||||
static int32_t read_battery_voltage_raw(void)
|
||||
{
|
||||
int adc_raw = 0;
|
||||
int voltage_mv = 0;
|
||||
|
||||
if (adc_oneshot_read(adc1_handle, PIN_V_SENS_BAT, &adc_raw) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
if (adc_cali_raw_to_voltage(adc_cali_handle, adc_raw, &voltage_mv) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Voltage divider: 150kΩ to 1MΩ → gain = 1.15 → scale = 1150/150
|
||||
return (int32_t)voltage_mv * 1150 / 150;
|
||||
}
|
||||
|
||||
// === EMA Filter Update ===
|
||||
static void apply_ema(float *state, bool *init, float alpha, int32_t raw, volatile int32_t *out)
|
||||
{
|
||||
if (!*init) {
|
||||
*state = (float)raw;
|
||||
*init = true;
|
||||
} else {
|
||||
*state = alpha * (float)raw + (1.0f - alpha) * *state;
|
||||
}
|
||||
*out = (int32_t)(*state + 0.5f);
|
||||
}
|
||||
|
||||
// === Public Accessors ===
|
||||
int32_t get_bridge_mA(uint8_t bridge)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return -1;
|
||||
return (int32_t)bridgeCurrents_mA[bridge];
|
||||
}
|
||||
|
||||
int32_t get_battery_mV(void)
|
||||
{
|
||||
return (int32_t)batteryVoltage_mV;
|
||||
}
|
||||
|
||||
// === E-Fuse: Trip Logic (called every cycle) ===
|
||||
static void efuse_update(uint8_t bridge, float I, float dt, uint64_t now)
|
||||
{
|
||||
float I_rated = (float)get_param_i8(currentLimits_A[bridge]);
|
||||
float I_norm = I / I_rated;
|
||||
|
||||
// Instant trip on extreme overcurrent
|
||||
if (I_norm >= i_instant[bridge]) {
|
||||
efuse_tripped[bridge] = true;
|
||||
efuse_trip_time[bridge] = now;
|
||||
return;
|
||||
}
|
||||
|
||||
// Cooling when below threshold
|
||||
if (I_norm < 1.1f) {
|
||||
efuse_heat[bridge] -= efuse_heat[bridge] * cool_rate[bridge] * dt;
|
||||
efuse_heat[bridge] = fmaxf(0.0f, efuse_heat[bridge]);
|
||||
efuse_tripped[bridge] = false; // Auto-clear if cooled
|
||||
return;
|
||||
}
|
||||
|
||||
// Accumulate heat (I²t)
|
||||
efuse_heat[bridge] += (I_norm * I_norm) * dt;
|
||||
|
||||
if (efuse_heat[bridge] >= i2t_thresholds[bridge]) {
|
||||
efuse_tripped[bridge] = true;
|
||||
efuse_trip_time[bridge] = now;
|
||||
}
|
||||
}
|
||||
|
||||
// === E-Fuse: Auto-Reset After Cooldown ===
|
||||
static void efuse_cooldown_check(uint64_t now)
|
||||
{
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
if (efuse_tripped[i] &&
|
||||
(now - efuse_trip_time[i]) >= (cooldown_ms[i] * 1000ULL)) {
|
||||
efuse_heat[i] = 0.0f;
|
||||
efuse_tripped[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Public E-Fuse Controls ===
|
||||
void efuse_reset_all(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
efuse_heat[i] = 0.0f;
|
||||
efuse_tripped[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool efuse_is_tripped(uint8_t bridge)
|
||||
{
|
||||
if (bridge >= N_BRIDGES) return false;
|
||||
return efuse_tripped[bridge];
|
||||
}
|
||||
|
||||
|
||||
// === Power Management Task ===
|
||||
void power_mgmt_task(void *param) {
|
||||
esp_task_wdt_add(NULL);
|
||||
|
||||
/*gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << PIN_CHG_DISABLE) | (1ULL << PIN_CHG_BULK),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io_conf);*/
|
||||
|
||||
/*// Enable RTC GPIO domain (required for hold)
|
||||
rtc_gpio_init(PIN_CHG_DISABLE);
|
||||
rtc_gpio_init(PIN_CHG_BULK);
|
||||
|
||||
// Set as output
|
||||
rtc_gpio_set_direction(PIN_CHG_DISABLE, RTC_GPIO_MODE_OUTPUT_ONLY);
|
||||
rtc_gpio_set_direction(PIN_CHG_BULK, RTC_GPIO_MODE_OUTPUT_ONLY);
|
||||
|
||||
// Optional: set initial level (will be held)
|
||||
//rtc_gpio_set_level(PIN_CHG_DISABLE, 1); // e.g., start disabled
|
||||
//rtc_gpio_set_level(PIN_CHG_BULK, 0);
|
||||
|
||||
// **Critical: Enable hold function**
|
||||
rtc_gpio_hold_en(PIN_CHG_DISABLE);
|
||||
rtc_gpio_hold_en(PIN_CHG_BULK);*/
|
||||
|
||||
ESP_ERROR_CHECK(adc_init());
|
||||
|
||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(20);
|
||||
|
||||
// Optional: Enable auto-zero with default threshold
|
||||
autozero_enable(BRIDGE_DRIVE, true);
|
||||
autozero_enable(BRIDGE_AUX, true);
|
||||
autozero_enable(BRIDGE_JACK, true);
|
||||
|
||||
autozero_set_threshold(BRIDGE_DRIVE, AUTOZERO_THRESH);
|
||||
autozero_set_threshold(BRIDGE_AUX, AUTOZERO_THRESH);
|
||||
autozero_set_threshold(BRIDGE_JACK, AUTOZERO_THRESH);
|
||||
|
||||
//uint64_t last_wake_time = esp_timer_get_time();
|
||||
//const uint64_t period = 5000; // 100 us => 10kHz
|
||||
while (1) {
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
uint64_t now_us = esp_timer_get_time();
|
||||
|
||||
/*if (now - last_wake_time < period) {
|
||||
uint32_t delay_us = (period - (now - last_wake_time)) / 1000;
|
||||
if (delay_us > 0) vTaskDelay(pdMS_TO_TICKS(delay_us));
|
||||
continue;
|
||||
}
|
||||
last_wake_time = now;*/
|
||||
|
||||
// Sample currents
|
||||
for (uint8_t i = 0; i < N_BRIDGES; i++) {
|
||||
int32_t raw_ma = read_bridge_current_raw((bridge_t)i);
|
||||
apply_ema(&ema_current[i], &ema_init[i], EMA_ALPHA_CURRENT,
|
||||
raw_ma, &bridgeCurrents_mA[i]);
|
||||
|
||||
// Reset spike timer if under limit
|
||||
/*if (bridgeCurrents_mA[i] < currentLimits_mA[i]) {
|
||||
currentSpikeSafeTimes[i] = now + CURRENT_SPIKE_TIME_US;
|
||||
}*/
|
||||
|
||||
// === E-FUSE UPDATE ===
|
||||
float I = (float)bridgeCurrents_mA[i] / 1000.0f;
|
||||
float dt = 0.020f; // 20 ms task period
|
||||
efuse_update(i, I, dt, now_us);
|
||||
}
|
||||
|
||||
/*ESP_LOGI("PWR", "[ %6ld | %6ld | %6ld mA ] { %6ld mV }",
|
||||
(long)bridgeCurrents_mA[BRIDGE_DRIVE],
|
||||
(long)bridgeCurrents_mA[BRIDGE_JACK],
|
||||
(long)bridgeCurrents_mA[BRIDGE_AUX],
|
||||
(long)batteryVoltage_mV);*/
|
||||
|
||||
// Sample battery
|
||||
int32_t raw_bat = read_battery_voltage_raw();
|
||||
apply_ema(&ema_battery, &ema_battery_init, EMA_ALPHA_BATTERY,
|
||||
raw_bat, &batteryVoltage_mV);
|
||||
|
||||
|
||||
//run_charge_fsm();
|
||||
efuse_cooldown_check(now_us);
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
}
|
||||
|
||||
void start_power() {
|
||||
xTaskCreate(power_mgmt_task, "PWR", 4096, NULL, 5, NULL);
|
||||
}
|
||||
|
||||
void shutdown_power() {
|
||||
|
||||
}
|
||||
@@ -8,27 +8,22 @@
|
||||
#ifndef MAIN_POWER_MGMT_H_
|
||||
#define MAIN_POWER_MGMT_H_
|
||||
|
||||
#include "control_fsm.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
typedef enum {
|
||||
CHG_STATE_OFF = 0,
|
||||
CHG_STATE_FLOAT = 1,
|
||||
CHG_STATE_BULK = 2
|
||||
} charge_state_t;
|
||||
#define N_CHARGE_STATES 3
|
||||
|
||||
charge_state_t get_charging_state();
|
||||
//void efuse_reset_all(void); // Clear all trip states (manual/programmatic reset)
|
||||
bool efuse_is_tripped(bridge_t bridge); // Query if bridge is currently faulted
|
||||
|
||||
void resetBatTimers();
|
||||
float get_bridge_A(bridge_t bridge);
|
||||
float get_battery_V();
|
||||
|
||||
void efuse_reset_all(void); // Clear all trip states (manual/programmatic reset)
|
||||
bool efuse_is_tripped(uint8_t bridge); // Query if bridge is currently faulted
|
||||
void set_autozero(bridge_t bridge);
|
||||
|
||||
int32_t get_bridge_mA(uint8_t bridge);
|
||||
int32_t get_battery_mV();
|
||||
|
||||
void start_power();
|
||||
void shutdown_power();
|
||||
esp_err_t adc_init();
|
||||
esp_err_t power_init();
|
||||
esp_err_t power_stop();
|
||||
|
||||
#endif /* MAIN_POWER_MGMT_H_ */
|
||||
89
main/rf.c
89
main/rf.c
@@ -1,6 +1,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "control_fsm.h"
|
||||
#include "driver/rmt_rx.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
@@ -14,16 +15,18 @@
|
||||
#include "driver/rmt_rx.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "rf.h"
|
||||
|
||||
#include "flash.h"
|
||||
#include "storage.h"
|
||||
|
||||
#define RF_PIN GPIO_NUM_23
|
||||
#define TAG "RF"
|
||||
|
||||
#define RF_PIN GPIO_NUM_25
|
||||
#define P_HIGH 1040
|
||||
#define P_LOW 340
|
||||
#define P_MARGIN 70
|
||||
#define P_SKIPMIN 250
|
||||
#define RF_DEBUG 0
|
||||
#define NUM_RF_BUTTONS 4
|
||||
|
||||
// Struct to hold decoded RF data
|
||||
typedef struct {
|
||||
@@ -37,6 +40,8 @@ typedef struct {
|
||||
// Global queue for passing decoded codes between tasks
|
||||
static QueueHandle_t g_code_queue = NULL;
|
||||
|
||||
int learn_flag = -1;
|
||||
|
||||
// For rmt_rx_register_event_callbacks
|
||||
static bool rfrx_done(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *udata) {
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
@@ -48,6 +53,7 @@ static bool rfrx_done(rmt_channel_handle_t channel, const rmt_rx_done_event_data
|
||||
// Task that receives and decodes RF signals
|
||||
static void rf_receiver_task(void* param) {
|
||||
esp_task_wdt_add(NULL);
|
||||
esp_log_level_set("rmt", ESP_LOG_NONE); // disable rmt messages about hw buffer too small
|
||||
const uint16_t tlow = (P_HIGH - P_LOW - (2 * P_MARGIN));
|
||||
const uint16_t thigh = (P_HIGH - P_LOW + (2 * P_MARGIN));
|
||||
|
||||
@@ -87,7 +93,7 @@ static void rf_receiver_task(void* param) {
|
||||
ESP_LOGI("RF", "RF receiver task started on core %d", xPortGetCoreID());
|
||||
|
||||
for(;;) {
|
||||
if (xQueueReceive(rx_queue, &rx_data, pdMS_TO_TICKS(1000)) == pdPASS) {
|
||||
if (xQueueReceive(rx_queue, &rx_data, pdMS_TO_TICKS(500)) == pdPASS) {
|
||||
|
||||
size_t len = rx_data.num_symbols;
|
||||
rmt_symbol_word_t *cur = rx_data.received_symbols;
|
||||
@@ -126,16 +132,44 @@ static void rf_receiver_task(void* param) {
|
||||
|
||||
// If we got a valid code, send it to processing task
|
||||
if (code) {
|
||||
rf_code_t rf_msg = {
|
||||
.code = code,
|
||||
.high_avg = high / 24,
|
||||
.low_avg = low / 24,
|
||||
.errors = err,
|
||||
.num_symbols = len
|
||||
};
|
||||
|
||||
// Non-blocking send - if queue is full, just drop it
|
||||
xQueueSend(g_code_queue, &rf_msg, 0);
|
||||
int64_t encoded = ((int64_t)len << 56) | code;
|
||||
|
||||
ESP_LOGI(TAG, "GOT KEYCODE 0x%lx [%d]", (long) code, len);
|
||||
|
||||
if (learn_flag >= 0) {
|
||||
set_param(PARAM_KEYCODE_0 + learn_flag,
|
||||
(param_value_t){.i64 = encoded});
|
||||
ESP_LOGI(TAG, "LEARNED KEYCODE");
|
||||
learn_flag = -1;
|
||||
} else {
|
||||
rf_code_t rf_msg = {
|
||||
.code = code,
|
||||
.high_avg = high / 24,
|
||||
.low_avg = low / 24,
|
||||
.errors = err,
|
||||
.num_symbols = len
|
||||
};
|
||||
|
||||
// Don't do this anymore. No need to pass data between threads. Just act on it.
|
||||
// Non-blocking send - if queue is full, just drop it
|
||||
//xQueueSend(g_code_queue, &rf_msg, 0);
|
||||
|
||||
|
||||
for (uint8_t i = 0; i < NUM_RF_BUTTONS; i++) {
|
||||
int64_t match = get_param(PARAM_KEYCODE_0+i).i64;
|
||||
if (encoded == match) {
|
||||
switch (i) {
|
||||
case 0: pulseOverride(RELAY_A1); pulseOverride(RELAY_A3); break;
|
||||
case 1: pulseOverride(RELAY_B1); pulseOverride(RELAY_A3); break;
|
||||
case 2: pulseOverride(RELAY_A2); break;
|
||||
case 3: pulseOverride(RELAY_B2); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Debug output - print raw symbols
|
||||
@@ -182,33 +216,36 @@ static void rf_receiver_task(void* param) {
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void start_rf() {
|
||||
esp_err_t rf_init() {
|
||||
g_code_queue = xQueueCreate(5, sizeof(rf_code_t));
|
||||
assert(g_code_queue);
|
||||
|
||||
xTaskCreate(rf_receiver_task, "RF", 4096, NULL, 10, NULL);
|
||||
xTaskCreate(rf_receiver_task, TAG, 4096, NULL, 10, NULL);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
esp_err_t rf_stop() { return ESP_OK; }
|
||||
|
||||
void rf_set_keycode(uint8_t index, int64_t code) {
|
||||
char key[] = "keycode0";
|
||||
key[7] = 48+index; // ASCII
|
||||
|
||||
ESP_LOGI("RF", "SET KEYCODE[%d] = 0x%16llx", index, code);
|
||||
|
||||
set_param_i64(key, code);
|
||||
set_param(PARAM_KEYCODE_0+index, (param_value_t){.i64=code});
|
||||
}
|
||||
|
||||
void rf_learn_keycode(uint8_t index) {
|
||||
if (index >= 8) return;
|
||||
learn_flag = index;
|
||||
}
|
||||
void rf_cancel_learn_keycode() {
|
||||
learn_flag = -1;
|
||||
}
|
||||
|
||||
int8_t rf_get_keycode() {
|
||||
rf_code_t received_code;
|
||||
|
||||
char key[] = "keycode0";
|
||||
|
||||
if (xQueueReceive(g_code_queue, &received_code, 0) == pdPASS) {
|
||||
int64_t newcode = ((int64_t)received_code.num_symbols << 56) | received_code.code;
|
||||
|
||||
for (uint8_t i = 0; i < NUM_RF_BUTTONS; i++) {
|
||||
key[7] = 48+i; // ASCII
|
||||
if (newcode == get_param_i64(key))
|
||||
if (newcode == get_param(PARAM_KEYCODE_0+i).i64)
|
||||
return i;
|
||||
}
|
||||
ESP_LOGI("RF", "Received unknown code 0x%08lx (%d) [0x%16llx]", (unsigned long)received_code.code, received_code.num_symbols, (unsigned long long) newcode);
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#define NUM_RF_BUTTONS 4
|
||||
#define NUM_RF_BUTTONS 8
|
||||
|
||||
int64_t recieveKeycode();
|
||||
|
||||
void start_rf();
|
||||
esp_err_t rf_init();
|
||||
esp_err_t rf_stop();
|
||||
|
||||
void rf_set_keycode(uint8_t index, int64_t code);
|
||||
|
||||
@@ -23,4 +24,7 @@ int64_t rf_get_raw_keycode();
|
||||
|
||||
void rf_clear_queue();
|
||||
|
||||
void rf_learn_keycode(uint8_t index);
|
||||
void rf_cancel_learn_keycode();
|
||||
|
||||
#endif
|
||||
17
main/rtc.c
17
main/rtc.c
@@ -28,16 +28,13 @@
|
||||
|
||||
//#include "esp32/rtc_clk.h" // For RTC_SLOW_FREQ_32K_XTAL enum and rtc_clk_slow_freq_set()
|
||||
#include "driver/rtc_io.h" // For RTC I/O handling (optional but recommended for pin configuration)
|
||||
#include "solar.h"
|
||||
#include "storage.h"
|
||||
|
||||
#define PIN_BTN_INTERRUPT GPIO_NUM_13
|
||||
|
||||
#define POWER_INACTIVITY_TIMEOUT_MS 20000
|
||||
#define BATTERY_CHECK_INTERVAL_SEC 30
|
||||
uint64_t last_activity_tick = 0;
|
||||
|
||||
#define DEEP_SLEEP_US 30000000ULL /* 30 seconds in deep sleep */
|
||||
|
||||
// RTC_DATA_ATTR keeps this var in RTC memory; persists across sleeps (but not across boots)
|
||||
RTC_DATA_ATTR int64_t next_alarm_time_s = -1;
|
||||
RTC_DATA_ATTR bool rtc_set = false;
|
||||
@@ -45,7 +42,7 @@ bool rtc_is_set() {
|
||||
return rtc_set;
|
||||
}
|
||||
|
||||
esp_err_t start_rtc(void) {
|
||||
esp_err_t rtc_xtal_init(void) {
|
||||
/* ---- Wake sources ---- */
|
||||
esp_sleep_enable_ext0_wakeup(PIN_BTN_INTERRUPT, 0);
|
||||
gpio_set_direction(PIN_BTN_INTERRUPT, GPIO_MODE_INPUT);
|
||||
@@ -62,7 +59,7 @@ esp_err_t start_rtc(void) {
|
||||
// Select 32 kHz XTAL as slow clock source (wait for stabilization)
|
||||
//rtc_clk_slow_freq_set(RTC_SLOW_FREQ_32K_XTAL);
|
||||
// Optional: Brief delay for crystal stabilization (typically <1 ms)
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
//vTaskDelay(pdMS_TO_TICKS(1));
|
||||
|
||||
//ESP_LOGI("RTC", "Configured with external 32 kHz oscillator (freq: %d Hz)", rtc_clk_slow_freq_get_hz());
|
||||
|
||||
@@ -80,9 +77,9 @@ void reset_shutdown_timer(void)
|
||||
void enter_deep_sleep(void)
|
||||
{
|
||||
//close_current_log();
|
||||
//fsm_request(FSM_CMD_STOP);
|
||||
i2c_set_relays(0);
|
||||
//esp_sleep_enable_timer_wakeup(DEEP_SLEEP_US);
|
||||
fsm_request(FSM_CMD_STOP);
|
||||
i2c_stop();
|
||||
esp_sleep_enable_timer_wakeup(DEEP_SLEEP_US);
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
@@ -91,7 +88,7 @@ void rtc_set_time(struct tm *tm) {
|
||||
start_new_log_file();
|
||||
struct timeval tv = { .tv_sec = mktime(tm), .tv_usec = 0 };
|
||||
settimeofday(&tv, NULL);
|
||||
resetBatTimers();
|
||||
reset_solar_fsm();
|
||||
}
|
||||
|
||||
void rtc_get_time(struct tm *tm)
|
||||
|
||||
@@ -18,16 +18,14 @@
|
||||
#include "esp_err.h"
|
||||
|
||||
|
||||
#define TRANSITION_DELAY_US 1000000
|
||||
#define OVERRIDE_PULSE_RF 160000
|
||||
#define OVERRIDE_PULSE_UX 80000
|
||||
|
||||
#define POWER_INACTIVITY_TIMEOUT_MS 180000
|
||||
#define DEEP_SLEEP_US 120000000ULL /* 120 seconds in deep sleep */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Public API */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
esp_err_t start_rtc();
|
||||
esp_err_t rtc_xtal_init();
|
||||
|
||||
bool rtc_is_set();
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
static const char* TAG = "SENS";
|
||||
|
||||
uint8_t sensor_pins[N_SENSORS] = {GPIO_NUM_19, GPIO_NUM_25};
|
||||
uint8_t sensor_pins[N_SENSORS] = {GPIO_NUM_27, GPIO_NUM_14};
|
||||
|
||||
volatile int32_t sensor_count[N_SENSORS] = {0};
|
||||
static volatile uint64_t sensor_last_isr_time[N_SENSORS] = {0};
|
||||
@@ -43,6 +43,7 @@ static void IRAM_ATTR sensor_isr_handler(void* arg) {
|
||||
|
||||
// Debounce task: Processes queue, updates state & count
|
||||
static void sensor_debounce_task(void* param) {
|
||||
esp_task_wdt_add(NULL);
|
||||
sensor_event_t evt;
|
||||
static uint64_t last_processed_time[N_SENSORS] = {0};
|
||||
static bool last_raw_state[N_SENSORS] = {false};
|
||||
@@ -54,8 +55,6 @@ static void sensor_debounce_task(void* param) {
|
||||
last_raw_state[i] = level;
|
||||
last_processed_time[i] = esp_timer_get_time();
|
||||
}
|
||||
|
||||
esp_task_wdt_add(NULL);
|
||||
|
||||
|
||||
uint8_t i = 0;
|
||||
@@ -117,7 +116,7 @@ static void sensor_debounce_task(void* param) {
|
||||
}
|
||||
}
|
||||
|
||||
void start_sensors() {
|
||||
esp_err_t sensors_init() {
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << sensor_pins[0]) | (1ULL << sensor_pins[1]),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
@@ -130,7 +129,7 @@ void start_sensors() {
|
||||
sensor_event_queue = xQueueCreate(16, sizeof(sensor_event_t));
|
||||
if (!sensor_event_queue) {
|
||||
ESP_LOGE(TAG, "Failed to create sensor queue");
|
||||
return;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// Install ISR service
|
||||
@@ -141,15 +140,19 @@ void start_sensors() {
|
||||
sensor_stable_state[i] = !gpio_get_level(sensor_pins[i]);
|
||||
}
|
||||
|
||||
xTaskCreate(sensor_debounce_task, "SENS_DEBOUNCE", 3072, NULL, 6, NULL);
|
||||
xTaskCreate(sensor_debounce_task, "SENSORS", 3072, NULL, 6, NULL);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void shutdown_sensors() {
|
||||
esp_err_t sensors_stop() {
|
||||
for (uint8_t i = 0; i < N_SENSORS; i++) {
|
||||
gpio_isr_handler_remove(sensor_pins[i]);
|
||||
}
|
||||
gpio_uninstall_isr_service();
|
||||
vQueueDelete(sensor_event_queue);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Public API
|
||||
|
||||
@@ -27,7 +27,7 @@ int32_t get_sensor_counter(sensor_t i);
|
||||
|
||||
bool get_sensor(sensor_t i);
|
||||
|
||||
void start_sensors();
|
||||
void shutdown_sensors();
|
||||
esp_err_t sensors_init();
|
||||
esp_err_t sensors_stop();
|
||||
|
||||
#endif /* MAIN_SENSORS_H_ */
|
||||
|
||||
91
main/solar.c
Normal file
91
main/solar.c
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "solar.h"
|
||||
#include "driver/rtc_io.h"
|
||||
#include "rtc.h"
|
||||
#include "power_mgmt.h"
|
||||
#include "esp_log.h"
|
||||
#include "storage.h"
|
||||
|
||||
#define TAG "SOLAR"
|
||||
|
||||
#define PIN_CHG_BULK GPIO_NUM_26
|
||||
|
||||
typedef enum {
|
||||
CHG_STATE_FLOAT = 0,
|
||||
CHG_STATE_BULK = 1
|
||||
} charge_state_t;
|
||||
RTC_DATA_ATTR charge_state_t current_charge_state = CHG_STATE_FLOAT;
|
||||
RTC_DATA_ATTR int64_t timer;
|
||||
|
||||
esp_err_t reset_solar_fsm() {
|
||||
timer = -1;
|
||||
current_charge_state = CHG_STATE_FLOAT;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
RTC_DATA_ATTR bool solar_needs_init = true;
|
||||
esp_err_t init_solar_gpio() {
|
||||
if (solar_needs_init) {
|
||||
rtc_gpio_init(PIN_CHG_BULK);
|
||||
rtc_gpio_set_direction(PIN_CHG_BULK, RTC_GPIO_MODE_OUTPUT_ONLY);
|
||||
solar_needs_init = false;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t run_solar_fsm() {
|
||||
init_solar_gpio();
|
||||
|
||||
int64_t now = system_rtc_get_raw_time();
|
||||
|
||||
//ESP_LOGI("BAT", "FSM STATE %d", current_charge_state);
|
||||
|
||||
float vbat = get_battery_V();
|
||||
|
||||
/*
|
||||
The state machine is simple.
|
||||
- After a period of time when battery is low, switch to bulk
|
||||
- After a period of time in bulk, switch to float
|
||||
*/
|
||||
|
||||
//if (rtc_is_set()) {
|
||||
switch(current_charge_state) {
|
||||
case CHG_STATE_BULK:
|
||||
if (now > timer+get_param(PARAM_CHG_BULK_S).i64) {
|
||||
current_charge_state = CHG_STATE_FLOAT;
|
||||
}
|
||||
|
||||
break;
|
||||
case CHG_STATE_FLOAT:
|
||||
// if we have sufficient voltage, reset the timer
|
||||
if (vbat > get_param(PARAM_CHG_LOW_V).f32) {
|
||||
timer = now;
|
||||
}
|
||||
|
||||
if (now > timer+get_param(PARAM_CHG_LOW_S).i64) {
|
||||
timer = now;
|
||||
current_charge_state = CHG_STATE_BULK;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
/*} else {
|
||||
reset_solar_fsm();
|
||||
ESP_LOGI(TAG, "RESET SOLAR FSM");
|
||||
}*/
|
||||
|
||||
rtc_gpio_hold_dis(PIN_CHG_BULK);
|
||||
switch(current_charge_state) {
|
||||
case CHG_STATE_BULK:
|
||||
rtc_gpio_set_level(PIN_CHG_BULK, 1);
|
||||
//ESP_LOGI(TAG, "BULK");
|
||||
break;
|
||||
case CHG_STATE_FLOAT:
|
||||
rtc_gpio_set_level(PIN_CHG_BULK, 0);
|
||||
//ESP_LOGI(TAG, "FLOAT");
|
||||
break;
|
||||
}
|
||||
rtc_gpio_hold_en(PIN_CHG_BULK);
|
||||
//rtc_gpio_hold_en(PIN_CHG_DISABLE);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
16
main/solar.h
Normal file
16
main/solar.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* solar.h
|
||||
*
|
||||
* Created on: Dec 13, 2025
|
||||
* Author: Thad
|
||||
*/
|
||||
|
||||
#ifndef MAIN_SOLAR_H_
|
||||
#define MAIN_SOLAR_H_
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
esp_err_t run_solar_fsm();
|
||||
esp_err_t reset_solar_fsm();
|
||||
|
||||
#endif /* MAIN_SOLAR_H_ */
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "esp_crc.h"
|
||||
#include "storage.h"
|
||||
|
||||
#define TAG "PARAMS"
|
||||
#define TAG "STORAGE"
|
||||
|
||||
// ============================================================================
|
||||
// PARAMETER TABLE GENERATION
|
||||
@@ -301,7 +301,7 @@ esp_err_t write_log(char* entry) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Log @ sector %lu / index %lu / offset %lu", (unsigned long) current_sector, (unsigned long) log_head_index, (unsigned long)current_offset);
|
||||
//ESP_LOGI(TAG, "Log @ sector %lu / index %lu / offset %lu", (unsigned long) current_sector, (unsigned long) log_head_index, (unsigned long)current_offset);
|
||||
|
||||
|
||||
log_head_index++;
|
||||
|
||||
@@ -67,23 +67,39 @@ typedef enum {
|
||||
PARAM_DEF(NUM_MOVES, u32, 0) \
|
||||
PARAM_DEF(MOVE_START, u32, 0) \
|
||||
PARAM_DEF(MOVE_END, u32, 0) \
|
||||
PARAM_DEF(EFUSE_1_A, u32, 40) \
|
||||
PARAM_DEF(EFUSE_2_A, u32, 10) \
|
||||
PARAM_DEF(EFUSE_3_A, u32, 10) \
|
||||
PARAM_DEF(EFUSE_4_A, u32, 10) \
|
||||
PARAM_DEF(EFUSE_1_AS, u16, 2400) \
|
||||
PARAM_DEF(EFUSE_2_AS, u16, 2400) \
|
||||
PARAM_DEF(EFUSE_3_AS, u16, 600) \
|
||||
PARAM_DEF(EFUSE_4_AS, u16, 600) \
|
||||
PARAM_DEF(DRIVE_DIST, u16, 10) \
|
||||
PARAM_DEF(DRIVE_DIST, u16, 10) /*3*/\
|
||||
PARAM_DEF(JACK_DIST, u8, 5) \
|
||||
PARAM_DEF(DRIVE_TPDF, u16, 4000) \
|
||||
PARAM_DEF(DRIVE_MSPF, u16, 600) \
|
||||
PARAM_DEF(JACK_MSPI, u16, 600) \
|
||||
PARAM_DEF(KEYCODE_0, i64, -1) \
|
||||
PARAM_DEF(KEYCODE_1, i64, -1) \
|
||||
PARAM_DEF(KEYCODE_2, i64, -1) \
|
||||
PARAM_DEF(KEYCODE_3, i64, -1)
|
||||
PARAM_DEF(JACK_MSPI, u16, 600) /*7*/\
|
||||
PARAM_DEF(KEYCODE_0, i64, 0x19000000005D0C61) \
|
||||
PARAM_DEF(KEYCODE_1, i64, 0x19000000005D0C62) \
|
||||
PARAM_DEF(KEYCODE_2, i64, 0x19000000005D0C64) \
|
||||
PARAM_DEF(KEYCODE_3, i64, 0x19000000005D0C68) /*11*/\
|
||||
PARAM_DEF(KEYCODE_4, i64, -1) \
|
||||
PARAM_DEF(KEYCODE_5, i64, -1) \
|
||||
PARAM_DEF(KEYCODE_6, i64, -1) \
|
||||
PARAM_DEF(KEYCODE_7, i64, -1) /*15*/\
|
||||
PARAM_DEF(ADC_ALPHA_BATTERY, f32, 0.02) \
|
||||
PARAM_DEF(ADC_ALPHA_ISENS, f32, 0.02) \
|
||||
PARAM_DEF(ADC_ALPHA_IAZ, f32, 0.005) \
|
||||
PARAM_DEF(ADC_DB_IAZ, f32, 5.0) /*19*/\
|
||||
PARAM_DEF(EFUSE_INOM_1, f32, 40.0) \
|
||||
PARAM_DEF(EFUSE_INOM_2, f32, 6.0) \
|
||||
PARAM_DEF(EFUSE_INOM_3, f32, 2.0) \
|
||||
PARAM_DEF(EFUSE_HEAT_THRESH, f32, 60.0) /*23*/\
|
||||
PARAM_DEF(EFUSE_KINST, f32, 4.0) \
|
||||
PARAM_DEF(EFUSE_TAUCOOL, f32, 0.2) \
|
||||
PARAM_DEF(EFUSE_TCOOL, i64, 5000000) \
|
||||
PARAM_DEF(LOW_PROTECTION_V, f32, 10.0) /*27*/\
|
||||
PARAM_DEF(LOW_PROTECTION_S, i64, 10) \
|
||||
PARAM_DEF(CHG_LOW_V, f32, 5.0) \
|
||||
PARAM_DEF(CHG_LOW_S, i64, 5.0) \
|
||||
PARAM_DEF(CHG_BULK_S, i64, 20) /*31*/\
|
||||
PARAM_DEF(RF_PULSE_LENGTH, u64, 350000) \
|
||||
|
||||
|
||||
|
||||
|
||||
// Generate enum for parameter indices
|
||||
#define PARAM_DEF(name, type, default_val) PARAM_##name,
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "esp_task_wdt.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "storage.h"
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include "rf.h"
|
||||
|
||||
#define TAG "UART"
|
||||
|
||||
@@ -19,6 +23,11 @@ static char cmd_buffer[CMD_MAX_LEN];
|
||||
static int cmd_pos = 0;
|
||||
static TaskHandle_t uart_task_handle = NULL;
|
||||
|
||||
// TODO: Set Time
|
||||
// TODO: Pair Remote
|
||||
// TODO: Command Move
|
||||
// TODO: Show current sensor values
|
||||
|
||||
// Parse value as either decimal or hex (0x prefix)
|
||||
static bool parse_uint64(const char *str, uint64_t *result) {
|
||||
char *endptr;
|
||||
@@ -80,7 +89,7 @@ static void print_param_value(param_idx_t id, param_value_t val) {
|
||||
|
||||
case PARAM_TYPE_i64:
|
||||
printf("%lld (0x%016llX)\n",
|
||||
(long long)val.i64, (unsigned long long)val.u64);
|
||||
(long long)val.i64, (unsigned long long)val.i64);
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_f32:
|
||||
@@ -99,6 +108,54 @@ static void print_param_value(param_idx_t id, param_value_t val) {
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t parse_param_value(const char *orig_str, param_type_e type, param_value_t *val) {
|
||||
const char *str = orig_str;
|
||||
// Skip leading whitespace
|
||||
while (isspace((unsigned char)*str)) str++;
|
||||
|
||||
// Check for negative sign on unsigned integer types
|
||||
bool is_unsigned_int = (type == PARAM_TYPE_u8 || type == PARAM_TYPE_u16 || type == PARAM_TYPE_u32 || type == PARAM_TYPE_u64);
|
||||
if (is_unsigned_int && *str == '-') {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
char *endptr;
|
||||
errno = 0;
|
||||
|
||||
switch (type) {
|
||||
case PARAM_TYPE_u8:
|
||||
case PARAM_TYPE_u16:
|
||||
case PARAM_TYPE_u32:
|
||||
case PARAM_TYPE_u64:
|
||||
val->u64 = strtoull(str, &endptr, 0);
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_i8:
|
||||
case PARAM_TYPE_i16:
|
||||
case PARAM_TYPE_i32:
|
||||
case PARAM_TYPE_i64:
|
||||
val->i64 = strtoll(str, &endptr, 0);
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_f32:
|
||||
val->f32 = strtof(str, &endptr);
|
||||
break;
|
||||
|
||||
case PARAM_TYPE_f64:
|
||||
val->f64 = strtod(str, &endptr);
|
||||
break;
|
||||
|
||||
default:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (errno == ERANGE || endptr == str || *endptr != '\0') {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Process set parameter command: sp <id> <value>
|
||||
static void cmd_set_param(char *args) {
|
||||
char *id_str = strtok(args, " \t");
|
||||
@@ -123,17 +180,14 @@ static void cmd_set_param(char *args) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse value
|
||||
uint64_t value;
|
||||
if (!parse_uint64(val_str, &value)) {
|
||||
param_value_t param_val = {0};
|
||||
param_type_e type = get_param_type(id);
|
||||
esp_err_t parse_err = parse_param_value(val_str, type, ¶m_val);
|
||||
if (parse_err != ESP_OK) {
|
||||
printf("ERROR: Invalid value\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set parameter (store as u64, will be cast appropriately when used)
|
||||
param_value_t param_val;
|
||||
param_val.u64 = value;
|
||||
|
||||
esp_err_t err = set_param(id, param_val);
|
||||
if (err == ESP_OK) {
|
||||
printf("OK: Parameter %u (%s) set to ",
|
||||
@@ -259,6 +313,30 @@ static void cmd_help(char *args) {
|
||||
printf("- Parameter IDs range from 0 to %d\n\n", NUM_PARAMS - 1);
|
||||
}
|
||||
|
||||
static void cmd_rf_learn(char *args) {
|
||||
char *id_str = strtok(args, " \t");
|
||||
|
||||
if (id_str == NULL) {
|
||||
rf_cancel_learn_keycode();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse parameter ID
|
||||
uint64_t id_u64;
|
||||
if (!parse_uint64(id_str, &id_u64)) {
|
||||
printf("ERROR: Invalid parameter ID\n");
|
||||
return;
|
||||
}
|
||||
|
||||
param_idx_t id = (param_idx_t)id_u64;
|
||||
if (id < 8) {
|
||||
printf("Listening for keycode for slot %d\n", id);
|
||||
rf_learn_keycode(id);
|
||||
return;
|
||||
}
|
||||
printf("ERROR: Keycode slot index out of bounds.\n");
|
||||
}
|
||||
|
||||
// Parse and execute command
|
||||
static void process_command(char *cmd) {
|
||||
// Trim leading whitespace
|
||||
@@ -310,6 +388,8 @@ static void process_command(char *cmd) {
|
||||
cmd_list_params(cmd);
|
||||
} else if (strcmp(command, "help") == 0) {
|
||||
cmd_help(cmd);
|
||||
} else if (strcmp(command, "rfl") == 0) {
|
||||
cmd_rf_learn(cmd);
|
||||
} else {
|
||||
printf("ERROR: Unknown command '%s' (type 'help' for commands)\n", command);
|
||||
}
|
||||
@@ -317,9 +397,11 @@ static void process_command(char *cmd) {
|
||||
|
||||
// UART event task
|
||||
void uart_event_task(void *pvParameters) {
|
||||
esp_task_wdt_add(NULL);
|
||||
uint8_t data[BUF_SIZE];
|
||||
|
||||
while (1) {
|
||||
esp_task_wdt_reset();
|
||||
int len = uart_read_bytes(UART_NUM, data, BUF_SIZE - 1, 20 / portTICK_PERIOD_MS);
|
||||
|
||||
if (len > 0) {
|
||||
@@ -364,14 +446,14 @@ void uart_event_task(void *pvParameters) {
|
||||
}
|
||||
}
|
||||
|
||||
void uart_start() {
|
||||
esp_err_t uart_init() {
|
||||
// Configure UART
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = 115200,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.baud_rate = 115200,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
|
||||
@@ -379,24 +461,25 @@ void uart_start() {
|
||||
ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config));
|
||||
|
||||
// Print startup message
|
||||
printf("\n\n");
|
||||
/*printf("\n\n");
|
||||
printf("=================================\n");
|
||||
printf(" ESP32 Parameter Manager\n");
|
||||
printf("=================================\n");
|
||||
printf("Type 'help' for available commands\n\n");
|
||||
printf("> ");
|
||||
fflush(stdout);
|
||||
fflush(stdout);*/
|
||||
|
||||
// Create UART task
|
||||
xTaskCreate(uart_event_task, "uart_event_task", 4096, NULL, 12, &uart_task_handle);
|
||||
|
||||
ESP_LOGI(TAG, "UART interface started");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void uart_stop() {
|
||||
esp_err_t uart_stop() {
|
||||
if (uart_task_handle == NULL) {
|
||||
ESP_LOGW(TAG, "UART task not running");
|
||||
return;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Shutting down UART...");
|
||||
@@ -415,4 +498,6 @@ void uart_stop() {
|
||||
uart_driver_delete(UART_NUM);
|
||||
|
||||
ESP_LOGI(TAG, "UART shutdown complete");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -8,7 +8,9 @@
|
||||
#ifndef MAIN_UART_COMMS_H_
|
||||
#define MAIN_UART_COMMS_H_
|
||||
|
||||
void uart_start();
|
||||
void uart_stop();
|
||||
#include "esp_err.h"
|
||||
|
||||
esp_err_t uart_init();
|
||||
esp_err_t uart_stop();
|
||||
|
||||
#endif /* MAIN_UART_COMMS_H_ */
|
||||
|
||||
Reference in New Issue
Block a user