mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-03 17:59:27 +02:00
Merge branch 'main' into tesla-bugfixes
This commit is contained in:
commit
30de0d8bde
12 changed files with 129 additions and 1472 deletions
|
@ -472,7 +472,7 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
if (isMinCellVoltageStale && isMaxCellVoltageStale) {
|
||||
datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop
|
||||
datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
set_event(EVENT_STALE_VALUE, 0);
|
||||
} else {
|
||||
datalayer.battery.status.cell_min_voltage_mV = min_cell_voltage; //Value is alive
|
||||
datalayer.battery.status.cell_max_voltage_mV = max_cell_voltage; //Value is alive
|
||||
|
|
|
@ -614,7 +614,7 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
battery_current != 0) { //Ignore stale values if there is no current flowing
|
||||
datalayer.battery.status.cell_min_voltage_mV = 9999; //Stale values force stop
|
||||
datalayer.battery.status.cell_max_voltage_mV = 9999; //Stale values force stop
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
set_event(EVENT_STALE_VALUE, 0);
|
||||
#ifdef DEBUG_LOG
|
||||
logging.println("Stale Min/Max voltage values detected during charge/discharge sending - 9999mV...");
|
||||
#endif // DEBUG_LOG
|
||||
|
|
|
@ -672,14 +672,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
set_voltage_minmax_limits(); // Count cells, and set voltage limits accordingly
|
||||
}
|
||||
|
||||
/* Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error*/
|
||||
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
||||
set_event(EVENT_CANFD_RX_FAILURE, 0);
|
||||
} else {
|
||||
datalayer.battery.status.CAN_battery_still_alive--;
|
||||
clear_event(EVENT_CANFD_RX_FAILURE);
|
||||
}
|
||||
|
||||
if (waterleakageSensor == 0) {
|
||||
set_event(EVENT_WATER_INGRESS, 0);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -52,6 +52,7 @@ void map_can_frame_to_variable_charger(CAN_frame rx_frame) {
|
|||
switch (rx_frame.ID) {
|
||||
//ID 0x212 conveys instantaneous DC charger stats
|
||||
case 0x212:
|
||||
datalayer.charger.CAN_charger_still_alive = CAN_STILL_ALIVE; // Let system know charger is sending CAN
|
||||
charger_stat_HVcur_temp = (uint16_t)(rx_frame.data.u8[0] << 8 | rx_frame.data.u8[1]);
|
||||
datalayer.charger.charger_stat_HVcur = (float)(charger_stat_HVcur_temp >> 3) * 0.05;
|
||||
|
||||
|
@ -68,6 +69,7 @@ void map_can_frame_to_variable_charger(CAN_frame rx_frame) {
|
|||
|
||||
//ID 0x30A conveys instantaneous AC charger stats
|
||||
case 0x30A:
|
||||
datalayer.charger.CAN_charger_still_alive = CAN_STILL_ALIVE; // Let system know charger is sending CAN
|
||||
charger_stat_ACcur_temp = (uint16_t)((rx_frame.data.u8[0] << 8 | rx_frame.data.u8[1]) >> 4);
|
||||
datalayer.charger.charger_stat_ACcur = (float)(charger_stat_ACcur_temp) * 0.2;
|
||||
|
||||
|
@ -80,10 +82,13 @@ void map_can_frame_to_variable_charger(CAN_frame rx_frame) {
|
|||
// 0x266 and 0x308 are len 5
|
||||
// 0x268 may be temperature data (len 8). Could resemble the Lear charger equivalent TODO
|
||||
case 0x266:
|
||||
datalayer.charger.CAN_charger_still_alive = CAN_STILL_ALIVE; // Let system know charger is sending CAN
|
||||
break;
|
||||
case 0x268:
|
||||
datalayer.charger.CAN_charger_still_alive = CAN_STILL_ALIVE; // Let system know charger is sending CAN
|
||||
break;
|
||||
case 0x308:
|
||||
datalayer.charger.CAN_charger_still_alive = CAN_STILL_ALIVE; // Let system know charger is sending CAN
|
||||
break;
|
||||
default:
|
||||
#ifdef DEBUG_LOG
|
||||
|
|
|
@ -118,6 +118,7 @@ void map_can_frame_to_variable_charger(CAN_frame rx_frame) {
|
|||
|
||||
switch (rx_frame.ID) {
|
||||
case 0x679: // This message fires once when charging cable is plugged in
|
||||
datalayer.charger.CAN_charger_still_alive = CAN_STILL_ALIVE; // Let system know charger is sending CAN
|
||||
OBCwakeup = true;
|
||||
datalayer.charger.charger_aux12V_enabled = true; //Not possible to turn off 12V charging on LEAF PDM
|
||||
// Startout with default values, so that charging can begin right when user plugs in cable
|
||||
|
@ -126,6 +127,7 @@ void map_can_frame_to_variable_charger(CAN_frame rx_frame) {
|
|||
datalayer.charger.charger_setpoint_HV_VDC = 400; // Target voltage
|
||||
break;
|
||||
case 0x390:
|
||||
datalayer.charger.CAN_charger_still_alive = CAN_STILL_ALIVE; // Let system know charger is sending CAN
|
||||
OBC_Charge_Status = ((rx_frame.data.u8[5] & 0x7E) >> 1);
|
||||
if (OBC_Charge_Status == PLUGGED_IN_WAITING_ON_TIMER || CHARGING_OR_INTERRUPTED) {
|
||||
PPStatus = true; //plug inserted
|
||||
|
@ -146,6 +148,9 @@ void map_can_frame_to_variable_charger(CAN_frame rx_frame) {
|
|||
OBC_Charge_Power = ((rx_frame.data.u8[0] & 0x01) << 8) | (rx_frame.data.u8[1]);
|
||||
datalayer.charger.charger_stat_HVcur = OBC_Charge_Power;
|
||||
break;
|
||||
case 0x393:
|
||||
datalayer.charger.CAN_charger_still_alive = CAN_STILL_ALIVE; // Let system know charger is sending CAN
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
CAN_device_t CAN_cfg; // CAN Config
|
||||
const int rx_queue_size = 10; // Receive Queue size
|
||||
volatile bool send_ok = 0;
|
||||
volatile bool send_ok_2515 = 0;
|
||||
volatile bool send_ok_2518 = 0;
|
||||
|
||||
#ifdef CAN_ADDON
|
||||
static const uint32_t QUARTZ_FREQUENCY = CRYSTAL_FREQUENCY_MHZ * 1000000UL; //MHZ configured in USER_SETTINGS.h
|
||||
|
@ -156,7 +157,13 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
|
|||
for (uint8_t i = 0; i < MCP2515Frame.len; i++) {
|
||||
MCP2515Frame.data[i] = tx_frame->data.u8[i];
|
||||
}
|
||||
can.tryToSend(MCP2515Frame);
|
||||
|
||||
send_ok_2515 = can.tryToSend(MCP2515Frame);
|
||||
if (!send_ok_2515) {
|
||||
set_event(EVENT_CAN_BUFFER_FULL, interface);
|
||||
} else {
|
||||
clear_event(EVENT_CAN_BUFFER_FULL);
|
||||
}
|
||||
#else // Interface not compiled, and settings try to use it
|
||||
set_event(EVENT_INTERFACE_MISSING, interface);
|
||||
#endif //CAN_ADDON
|
||||
|
@ -176,8 +183,8 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
|
|||
for (uint8_t i = 0; i < MCP2518Frame.len; i++) {
|
||||
MCP2518Frame.data[i] = tx_frame->data.u8[i];
|
||||
}
|
||||
send_ok = canfd.tryToSend(MCP2518Frame);
|
||||
if (!send_ok) {
|
||||
send_ok_2518 = canfd.tryToSend(MCP2518Frame);
|
||||
if (!send_ok_2518) {
|
||||
set_event(EVENT_CANFD_BUFFER_FULL, interface);
|
||||
} else {
|
||||
clear_event(EVENT_CANFD_BUFFER_FULL);
|
||||
|
|
|
@ -86,7 +86,7 @@ typedef struct {
|
|||
uint16_t CAN_error_counter;
|
||||
/** uint8_t */
|
||||
/** A counter set each time a new message comes from battery.
|
||||
* This value then gets decremented each 5 seconds. Incase we reach 0
|
||||
* This value then gets decremented every second. Incase we reach 0
|
||||
* we report the battery as missing entirely on the CAN bus.
|
||||
*/
|
||||
uint8_t CAN_battery_still_alive = CAN_STILL_ALIVE;
|
||||
|
@ -171,6 +171,12 @@ typedef struct {
|
|||
bool charger_HV_enabled = false;
|
||||
/** True if the 12V DC/DC output is enabled */
|
||||
bool charger_aux12V_enabled = false;
|
||||
/** uint8_t */
|
||||
/** A counter set each time a new message comes from charger.
|
||||
* This value then gets decremented every second. Incase we reach 0
|
||||
* we report the battery as missing entirely on the CAN bus.
|
||||
*/
|
||||
uint8_t CAN_charger_still_alive = CAN_STILL_ALIVE;
|
||||
} DATALAYER_CHARGER_TYPE;
|
||||
|
||||
typedef struct {
|
||||
|
@ -256,7 +262,7 @@ typedef struct {
|
|||
#endif
|
||||
/** uint8_t */
|
||||
/** A counter set each time a new message comes from inverter.
|
||||
* This value then gets decremented each 5 seconds. Incase we reach 0
|
||||
* This value then gets decremented every second. Incase we reach 0
|
||||
* we report the inverter as missing entirely on the CAN bus.
|
||||
*/
|
||||
uint8_t CAN_inverter_still_alive = CAN_STILL_ALIVE;
|
||||
|
|
|
@ -163,31 +163,41 @@ void update_machineryprotection() {
|
|||
|
||||
// Check if the BMS is still sending CAN messages. If we go 60s without messages we raise an error
|
||||
if (!datalayer.battery.status.CAN_battery_still_alive) {
|
||||
set_event(EVENT_CAN_RX_FAILURE, 0);
|
||||
set_event(EVENT_CAN_BATTERY_MISSING, can_config.battery);
|
||||
} else {
|
||||
datalayer.battery.status.CAN_battery_still_alive--;
|
||||
clear_event(EVENT_CAN_RX_FAILURE);
|
||||
clear_event(EVENT_CAN_BATTERY_MISSING);
|
||||
}
|
||||
|
||||
// Too many malformed CAN messages recieved!
|
||||
if (datalayer.battery.status.CAN_error_counter > MAX_CAN_FAILURES) {
|
||||
set_event(EVENT_CAN_RX_WARNING, 1);
|
||||
set_event(EVENT_CAN_CORRUPTED_WARNING, can_config.battery);
|
||||
} else {
|
||||
clear_event(EVENT_CAN_RX_WARNING);
|
||||
clear_event(EVENT_CAN_CORRUPTED_WARNING);
|
||||
}
|
||||
|
||||
#ifdef CAN_INVERTER_SELECTED
|
||||
// Check if the inverter is still sending CAN messages. If we go 60s without messages we raise an error
|
||||
// Check if the inverter is still sending CAN messages. If we go 60s without messages we raise a warning
|
||||
if (!datalayer.system.status.CAN_inverter_still_alive) {
|
||||
set_event(EVENT_CAN_INVERTER_MISSING, 0);
|
||||
set_event(EVENT_CAN_INVERTER_MISSING, can_config.inverter);
|
||||
} else {
|
||||
datalayer.system.status.CAN_inverter_still_alive--;
|
||||
clear_event(EVENT_CAN_INVERTER_MISSING);
|
||||
}
|
||||
#endif //CAN_INVERTER_SELECTED
|
||||
|
||||
#ifdef CHARGER_SELECTED
|
||||
// Check if the charger is still sending CAN messages. If we go 60s without messages we raise a warning
|
||||
if (!datalayer.charger.CAN_charger_still_alive) {
|
||||
set_event(EVENT_CAN_CHARGER_MISSING, can_config.charger);
|
||||
} else {
|
||||
datalayer.charger.CAN_charger_still_alive--;
|
||||
clear_event(EVENT_CAN_CHARGER_MISSING);
|
||||
}
|
||||
#endif //CHARGER_SELECTED
|
||||
|
||||
#ifdef DOUBLE_BATTERY // Additional Double-Battery safeties are checked here
|
||||
// Check if the Battery 2 BMS is still sending CAN messages. If we go 60s without messages we raise an error
|
||||
// Check if the Battery 2 BMS is still sending CAN messages. If we go 60s without messages we raise a warning
|
||||
|
||||
// Pause function is on
|
||||
if (emulator_pause_request_ON) {
|
||||
|
@ -196,17 +206,17 @@ void update_machineryprotection() {
|
|||
}
|
||||
|
||||
if (!datalayer.battery2.status.CAN_battery_still_alive) {
|
||||
set_event(EVENT_CAN2_RX_FAILURE, 0);
|
||||
set_event(EVENT_CAN_BATTERY2_MISSING, can_config.battery_double);
|
||||
} else {
|
||||
datalayer.battery2.status.CAN_battery_still_alive--;
|
||||
clear_event(EVENT_CAN2_RX_FAILURE);
|
||||
clear_event(EVENT_CAN_BATTERY2_MISSING);
|
||||
}
|
||||
|
||||
// Too many malformed CAN messages recieved!
|
||||
if (datalayer.battery2.status.CAN_error_counter > MAX_CAN_FAILURES) {
|
||||
set_event(EVENT_CAN_RX_WARNING, 2);
|
||||
set_event(EVENT_CAN_CORRUPTED_WARNING, can_config.battery_double);
|
||||
} else {
|
||||
clear_event(EVENT_CAN_RX_WARNING);
|
||||
clear_event(EVENT_CAN_CORRUPTED_WARNING);
|
||||
}
|
||||
|
||||
// Cell overvoltage, critical latching error without automatic reset. Requires user action.
|
||||
|
|
|
@ -133,13 +133,13 @@ void init_events(void) {
|
|||
events.entries[EVENT_CANMCP2517FD_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CANMCP2515_INIT_FAILURE].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CANFD_BUFFER_FULL].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_BUFFER_FULL].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_OVERRUN].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_CANFD_RX_OVERRUN].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_RX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CAN2_RX_FAILURE].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CANFD_RX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CAN_RX_WARNING].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CAN_CORRUPTED_WARNING].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_NATIVE_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CAN_BATTERY_MISSING].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CAN_BATTERY2_MISSING].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_CHARGER_MISSING].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_CAN_INVERTER_MISSING].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CONTACTOR_WELDED].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_WATER_INGRESS].level = EVENT_LEVEL_ERROR;
|
||||
|
@ -148,6 +148,7 @@ void init_events(void) {
|
|||
events.entries[EVENT_12V_LOW].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_SOC_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_SOC_UNAVAILABLE].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_STALE_VALUE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_KWH_PLAUSIBILITY_ERROR].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BALANCING_START].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_BALANCING_END].level = EVENT_LEVEL_INFO;
|
||||
|
@ -265,23 +266,23 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
case EVENT_CANMCP2515_INIT_FAILURE:
|
||||
return "CAN-MCP addon initialization failed. Check hardware";
|
||||
case EVENT_CANFD_BUFFER_FULL:
|
||||
return "CAN-FD buffer overflowed. Some CAN messages were not sent. Contact developers.";
|
||||
return "MCP2518FD buffer overflowed. Some CAN messages were not sent. Contact developers.";
|
||||
case EVENT_CAN_BUFFER_FULL:
|
||||
return "MCP2515 buffer overflowed. Some CAN messages were not sent. Contact developers.";
|
||||
case EVENT_CAN_OVERRUN:
|
||||
return "CAN message failed to send within defined time. Contact developers, CPU load might be too high.";
|
||||
case EVENT_CANFD_RX_OVERRUN:
|
||||
return "CAN-FD failed to receive all messages from CAN bus. Contact developers, CPU load might be too high.";
|
||||
case EVENT_CAN_RX_FAILURE:
|
||||
return "No CAN communication detected for 60s. Shutting down battery control.";
|
||||
case EVENT_CAN2_RX_FAILURE:
|
||||
return "No CAN communication detected for 60s on CAN2. Shutting down the secondary battery control.";
|
||||
case EVENT_CANFD_RX_FAILURE:
|
||||
return "No CANFD communication detected for 60s. Shutting down battery control.";
|
||||
case EVENT_CAN_RX_WARNING:
|
||||
case EVENT_CAN_CORRUPTED_WARNING:
|
||||
return "High amount of corrupted CAN messages detected. Check CAN wire shielding!";
|
||||
case EVENT_CAN_TX_FAILURE:
|
||||
return "CAN messages failed to transmit, or no one on the bus to ACK the message!";
|
||||
case EVENT_CAN_NATIVE_TX_FAILURE:
|
||||
return "CAN_NATIVE failed to transmit, or no one on the bus to ACK the message!";
|
||||
case EVENT_CAN_BATTERY_MISSING:
|
||||
return "Battery not sending messages via CAN for the last 60 seconds. Check wiring!";
|
||||
case EVENT_CAN_BATTERY2_MISSING:
|
||||
return "Secondary battery not sending messages via CAN for the last 60 seconds. Check wiring!";
|
||||
case EVENT_CAN_CHARGER_MISSING:
|
||||
return "Charger not sending messages via CAN for the last 60 seconds. Check wiring!";
|
||||
case EVENT_CAN_INVERTER_MISSING:
|
||||
return "Inverter not sending messages on CAN bus. Check wiring!";
|
||||
return "Inverter not sending messages via CAN for the last 60 seconds. Check wiring!";
|
||||
case EVENT_CONTACTOR_WELDED:
|
||||
return "Contactors sticking/welded. Inspect battery with caution!";
|
||||
case EVENT_CHARGE_LIMIT_EXCEEDED:
|
||||
|
@ -296,6 +297,8 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
return "SOC reported by battery not plausible. Restart battery!";
|
||||
case EVENT_SOC_UNAVAILABLE:
|
||||
return "SOC not sent by BMS. Calibrate BMS via app.";
|
||||
case EVENT_STALE_VALUE:
|
||||
return "Important values detected as stale. System might have locked up!";
|
||||
case EVENT_KWH_PLAUSIBILITY_ERROR:
|
||||
return "kWh remaining reported by battery not plausible. Battery needs cycling.";
|
||||
case EVENT_BALANCING_START:
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
// #define INCLUDE_EVENTS_TEST // Enable to run an event test loop, see events_test_on_target.cpp
|
||||
|
||||
#define EE_MAGIC_HEADER_VALUE 0x0021 // 0x0000 to 0xFFFF
|
||||
#define EE_MAGIC_HEADER_VALUE 0x0022 // 0x0000 to 0xFFFF
|
||||
|
||||
#define GENERATE_ENUM(ENUM) ENUM,
|
||||
#define GENERATE_STRING(STRING) #STRING,
|
||||
|
@ -29,14 +29,14 @@
|
|||
XX(EVENT_CANMCP2517FD_INIT_FAILURE) \
|
||||
XX(EVENT_CANMCP2515_INIT_FAILURE) \
|
||||
XX(EVENT_CANFD_BUFFER_FULL) \
|
||||
XX(EVENT_CAN_BUFFER_FULL) \
|
||||
XX(EVENT_CAN_OVERRUN) \
|
||||
XX(EVENT_CANFD_RX_OVERRUN) \
|
||||
XX(EVENT_CAN_RX_FAILURE) \
|
||||
XX(EVENT_CAN2_RX_FAILURE) \
|
||||
XX(EVENT_CANFD_RX_FAILURE) \
|
||||
XX(EVENT_CAN_RX_WARNING) \
|
||||
XX(EVENT_CAN_TX_FAILURE) \
|
||||
XX(EVENT_CAN_CORRUPTED_WARNING) \
|
||||
XX(EVENT_CAN_BATTERY_MISSING) \
|
||||
XX(EVENT_CAN_BATTERY2_MISSING) \
|
||||
XX(EVENT_CAN_CHARGER_MISSING) \
|
||||
XX(EVENT_CAN_INVERTER_MISSING) \
|
||||
XX(EVENT_CAN_NATIVE_TX_FAILURE) \
|
||||
XX(EVENT_CHARGE_LIMIT_EXCEEDED) \
|
||||
XX(EVENT_CONTACTOR_WELDED) \
|
||||
XX(EVENT_DISCHARGE_LIMIT_EXCEEDED) \
|
||||
|
@ -44,6 +44,7 @@
|
|||
XX(EVENT_12V_LOW) \
|
||||
XX(EVENT_SOC_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_SOC_UNAVAILABLE) \
|
||||
XX(EVENT_STALE_VALUE) \
|
||||
XX(EVENT_KWH_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_BALANCING_START) \
|
||||
XX(EVENT_BALANCING_END) \
|
||||
|
|
|
@ -15,10 +15,10 @@ int ESP32CAN::CANWriteFrame(const CAN_frame_t* p_frame) {
|
|||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("CAN failure! Check wires");
|
||||
#endif
|
||||
set_event(EVENT_CAN_TX_FAILURE, 0);
|
||||
set_event(EVENT_CAN_NATIVE_TX_FAILURE, 0);
|
||||
start_time = millis();
|
||||
} else {
|
||||
clear_event(EVENT_CAN_TX_FAILURE);
|
||||
clear_event(EVENT_CAN_NATIVE_TX_FAILURE);
|
||||
}
|
||||
} else {
|
||||
if ((millis() - start_time) >= 20) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue