/* * debug_protocol.c * * Created on: Mar 5, 2026 * Author: janik */ #include "debug_protocol.h" #include "main.h" #include "cc_controller.h" #include "mppt.h" #include #include /* ---- External variables from main.c ---- */ extern volatile float vin, iin, vout, iout, vfly, etemp; extern volatile int16_t last_tmp; extern volatile int16_t vfly_correction; extern uint16_t VREF; extern float vfly_integral; extern volatile float vfly_avg_debug; extern float vfly_kp, vfly_ki; extern uint16_t vfly_clamp; extern uint16_t vfly_loop_counter_trigger; extern volatile uint8_t vfly_active; extern CCController cc; extern volatile float cc_target; extern volatile int cc_active; extern float cc_gain; extern float CC_MIN_STEP, CC_MAX_STEP; extern uint16_t cc_loop_counter_trigger; extern MPPTController mppt; extern volatile int mppt_active; extern uint16_t mppt_loop_counter_trigger; extern float mppt_initial_iref; extern float mppt_step; extern float mppt_iref_min, mppt_iref_max; extern float mppt_dv_threshold; extern float mppt_deadband; extern float vin_min_ctrl; extern uint8_t dt_values[]; /* Raw ADC DMA buffers */ extern uint16_t DMA1BUF1; /* adc4: vbat */ extern uint16_t DMA1BUF2[3]; /* adc1: etemp, vin, iin */ extern uint16_t DMA1BUF3[2]; /* adc2: vfly, iout */ extern uint16_t DMA1BUF4; /* adc5: itemp */ extern COMP_HandleTypeDef hcomp1, hcomp3, hcomp4; extern FMAC_HandleTypeDef hfmac; /* ---- CRC8 table (poly 0x07) ---- */ static const uint8_t crc8_table[256] = { 0x00,0x07,0x0E,0x09,0x1C,0x1B,0x12,0x15,0x38,0x3F,0x36,0x31,0x24,0x23,0x2A,0x2D, 0x70,0x77,0x7E,0x79,0x6C,0x6B,0x62,0x65,0x48,0x4F,0x46,0x41,0x54,0x53,0x5A,0x5D, 0xE0,0xE7,0xEE,0xE9,0xFC,0xFB,0xF2,0xF5,0xD8,0xDF,0xD6,0xD1,0xC4,0xC3,0xCA,0xCD, 0x90,0x97,0x9E,0x99,0x8C,0x8B,0x82,0x85,0xA8,0xAF,0xA6,0xA1,0xB4,0xB3,0xBA,0xBD, 0xC7,0xC0,0xC9,0xCE,0xDB,0xDC,0xD5,0xD2,0xFF,0xF8,0xF1,0xF6,0xE3,0xE4,0xED,0xEA, 0xB7,0xB0,0xB9,0xBE,0xAB,0xAC,0xA5,0xA2,0x8F,0x88,0x81,0x86,0x93,0x94,0x9D,0x9A, 0x27,0x20,0x29,0x2E,0x3B,0x3C,0x35,0x32,0x1F,0x18,0x11,0x16,0x03,0x04,0x0D,0x0A, 0x57,0x50,0x59,0x5E,0x4B,0x4C,0x45,0x42,0x6F,0x68,0x61,0x66,0x73,0x74,0x7D,0x7A, 0x89,0x8E,0x87,0x80,0x95,0x92,0x9B,0x9C,0xB1,0xB6,0xBF,0xB8,0xAD,0xAA,0xA3,0xA4, 0xF9,0xFE,0xF7,0xF0,0xE5,0xE2,0xEB,0xEC,0xC1,0xC6,0xCF,0xC8,0xDD,0xDA,0xD3,0xD4, 0x69,0x6E,0x67,0x60,0x75,0x72,0x7B,0x7C,0x51,0x56,0x5F,0x58,0x4D,0x4A,0x43,0x44, 0x19,0x1E,0x17,0x10,0x05,0x02,0x0B,0x0C,0x21,0x26,0x2F,0x28,0x3D,0x3A,0x33,0x34, 0x4E,0x49,0x40,0x47,0x52,0x55,0x5C,0x5B,0x76,0x71,0x78,0x7F,0x6A,0x6D,0x64,0x63, 0x3E,0x39,0x30,0x37,0x22,0x25,0x2C,0x2B,0x06,0x01,0x08,0x0F,0x1A,0x1D,0x14,0x13, 0xAE,0xA9,0xA0,0xA7,0xB2,0xB5,0xBC,0xBB,0x96,0x91,0x98,0x9F,0x8A,0x8D,0x84,0x83, 0xDE,0xD9,0xD0,0xD7,0xC2,0xC5,0xCC,0xCB,0xE6,0xE1,0xE8,0xEF,0xFA,0xFD,0xF4,0xF3, }; static uint8_t crc8(const uint8_t *data, uint16_t len) { uint8_t crc = 0x00; for (uint16_t i = 0; i < len; i++) crc = crc8_table[crc ^ data[i]]; return crc; } /* ---- Parameter table ---- */ #define PARAM_COUNT 27 static ParamEntry param_table[PARAM_COUNT]; static void param_table_init(void) { int i = 0; /* Vfly params */ param_table[i++] = (ParamEntry){PID_VFLY_KP, PTYPE_FLOAT, &vfly_kp, -10.0f, 10.0f}; param_table[i++] = (ParamEntry){PID_VFLY_KI, PTYPE_FLOAT, &vfly_ki, -10.0f, 10.0f}; param_table[i++] = (ParamEntry){PID_VFLY_CLAMP, PTYPE_UINT16, &vfly_clamp, 0, 10000}; param_table[i++] = (ParamEntry){PID_VFLY_LOOP_COUNTER_TRIGGER, PTYPE_UINT16, &vfly_loop_counter_trigger, 1, 10000}; param_table[i++] = (ParamEntry){PID_VFLY_ACTIVE, PTYPE_UINT8, (void*)&vfly_active, 0, 1}; /* CC params */ param_table[i++] = (ParamEntry){PID_CC_TARGET, PTYPE_FLOAT, (void*)&cc_target, 0, 60000}; param_table[i++] = (ParamEntry){PID_CC_GAIN, PTYPE_FLOAT, &cc_gain, -1.0f, 1.0f}; param_table[i++] = (ParamEntry){PID_CC_MIN_STEP, PTYPE_FLOAT, &CC_MIN_STEP, -1000, 0}; param_table[i++] = (ParamEntry){PID_CC_MAX_STEP, PTYPE_FLOAT, &CC_MAX_STEP, 0, 1000}; param_table[i++] = (ParamEntry){PID_CC_LOOP_COUNTER_TRIGGER, PTYPE_UINT16, &cc_loop_counter_trigger, 1, 10000}; param_table[i++] = (ParamEntry){PID_CC_ACTIVE, PTYPE_INT32, (void*)&cc_active, 0, 1}; param_table[i++] = (ParamEntry){PID_VREF, PTYPE_UINT16, &VREF, 3100, 3700}; /* MPPT params */ param_table[i++] = (ParamEntry){PID_MPPT_STEP, PTYPE_FLOAT, &mppt_step, 0, 10000}; param_table[i++] = (ParamEntry){PID_MPPT_IREF_MIN, PTYPE_FLOAT, &mppt_iref_min, 0, 60000}; param_table[i++] = (ParamEntry){PID_MPPT_IREF_MAX, PTYPE_FLOAT, &mppt_iref_max, 0, 60000}; param_table[i++] = (ParamEntry){PID_MPPT_DV_THRESHOLD, PTYPE_FLOAT, &mppt_dv_threshold, 0, 10000}; param_table[i++] = (ParamEntry){PID_MPPT_LOOP_COUNTER_TRIGGER, PTYPE_UINT16, &mppt_loop_counter_trigger, 1, 10000}; param_table[i++] = (ParamEntry){PID_MPPT_ACTIVE, PTYPE_INT32, (void*)&mppt_active, 0, 1}; param_table[i++] = (ParamEntry){PID_MPPT_INITIAL_IREF, PTYPE_FLOAT, &mppt_initial_iref, 0, 60000}; param_table[i++] = (ParamEntry){PID_MPPT_DEADBAND, PTYPE_FLOAT, &mppt_deadband, 0, 1.0f}; /* Global */ param_table[i++] = (ParamEntry){PID_VIN_MIN_CTRL, PTYPE_FLOAT, &vin_min_ctrl, 0, 90000}; /* Deadtime segments */ param_table[i++] = (ParamEntry){PID_DT_SEG0, PTYPE_UINT8, &dt_values[0], DT_HARD_MIN, 200}; param_table[i++] = (ParamEntry){PID_DT_SEG1, PTYPE_UINT8, &dt_values[1], DT_HARD_MIN, 200}; param_table[i++] = (ParamEntry){PID_DT_SEG2, PTYPE_UINT8, &dt_values[2], DT_HARD_MIN, 200}; param_table[i++] = (ParamEntry){PID_DT_SEG3, PTYPE_UINT8, &dt_values[3], DT_HARD_MIN, 200}; param_table[i++] = (ParamEntry){PID_DT_SEG4, PTYPE_UINT8, &dt_values[4], DT_HARD_MIN, 200}; param_table[i++] = (ParamEntry){PID_DT_SEG5, PTYPE_UINT8, &dt_values[5], DT_HARD_MIN, 200}; } /* ---- Protocol context ---- */ ProtoCtx proto; /* ---- Internal helpers ---- */ static void start_rx(void) { HAL_UART_Receive_IT(proto.huart, &proto.rx_byte, 1); } static void uart_recover(void) { /* Clear error flags and reset UART state so TX/RX work again */ __HAL_UART_CLEAR_FLAG(proto.huart, UART_CLEAR_OREF | UART_CLEAR_NEF | UART_CLEAR_PEF | UART_CLEAR_FEF); proto.huart->gState = HAL_UART_STATE_READY; proto.huart->RxState = HAL_UART_STATE_READY; proto.huart->ErrorCode = HAL_UART_ERROR_NONE; /* Re-arm RX */ start_rx(); } static void send_frame(uint8_t cmd, const uint8_t *payload, uint8_t len) { /* Pick the inactive buffer */ uint8_t buf_idx = proto.tx_active ^ 1; uint8_t *buf = proto.tx_buf[buf_idx]; uint16_t total = PROTO_HEADER_SIZE + len + 1; buf[0] = PROTO_SYNC_BYTE; buf[1] = cmd; buf[2] = len; if (len > 0) memcpy(&buf[3], payload, len); buf[3 + len] = crc8(buf, 3 + len); proto.tx_active = buf_idx; if (HAL_UART_Transmit(proto.huart, buf, total, 5) != HAL_OK) { proto.uart_errors++; uart_recover(); } } static ParamEntry* find_param(uint8_t id) { for (int i = 0; i < PARAM_COUNT; i++) { if (param_table[i].id == id) return ¶m_table[i]; } return NULL; } static void apply_param_write(const ParamWritePayload *pw) { ParamEntry *p = find_param(pw->param_id); if (!p) return; /* Extract value and clamp */ float fval; switch (p->type) { case PTYPE_FLOAT: fval = pw->value.f; if (fval < p->min_val) fval = p->min_val; if (fval > p->max_val) fval = p->max_val; *(float*)p->ptr = fval; break; case PTYPE_UINT16: fval = (float)pw->value.u16; if (fval < p->min_val) fval = p->min_val; if (fval > p->max_val) fval = p->max_val; *(uint16_t*)p->ptr = (uint16_t)fval; break; case PTYPE_UINT8: fval = (float)pw->value.u8; if (fval < p->min_val) fval = p->min_val; if (fval > p->max_val) fval = p->max_val; *(uint8_t*)p->ptr = (uint8_t)fval; break; case PTYPE_INT32: fval = (float)pw->value.i32; if (fval < p->min_val) fval = p->min_val; if (fval > p->max_val) fval = p->max_val; *(int32_t*)p->ptr = (int32_t)fval; break; } /* Side effects: VREF update -> ADC3 offset + CC output */ if (pw->param_id == PID_VREF) { ADC3->OFR1 = (ADC3->OFR1 & ~0xFFFU) | (VREF & 0xFFF); cc.output_f = (float)VREF; } /* Side effects for CC params: sync struct fields */ else if (pw->param_id == PID_CC_GAIN) cc.gain = cc_gain; else if (pw->param_id == PID_CC_MIN_STEP) cc.min_step = CC_MIN_STEP; else if (pw->param_id == PID_CC_MAX_STEP) cc.max_step = CC_MAX_STEP; /* Side effects for MPPT params: sync struct fields */ else if (pw->param_id == PID_MPPT_STEP) mppt.step = mppt_step; else if (pw->param_id == PID_MPPT_IREF_MIN) mppt.iref_min = mppt_iref_min; else if (pw->param_id == PID_MPPT_IREF_MAX) mppt.iref_max = mppt_iref_max; else if (pw->param_id == PID_MPPT_DV_THRESHOLD) mppt.dv_min = mppt_dv_threshold; else if (pw->param_id == PID_MPPT_DEADBAND) mppt.deadband = mppt_deadband; else if (pw->param_id == PID_MPPT_ACTIVE && mppt_active) cc_active = 1; /* Send ACK: echo back the write payload */ send_frame(CMD_PARAM_WRITE_ACK, (const uint8_t*)pw, sizeof(ParamWritePayload)); } static void send_param_value(const ParamEntry *p) { ParamWritePayload pv; pv.param_id = p->id; pv.param_type = p->type; pv._pad[0] = 0; pv._pad[1] = 0; pv.value.u32 = 0; switch (p->type) { case PTYPE_FLOAT: pv.value.f = *(float*)p->ptr; break; case PTYPE_UINT16: pv.value.u16 = *(uint16_t*)p->ptr; break; case PTYPE_UINT8: pv.value.u8 = *(uint8_t*)p->ptr; break; case PTYPE_INT32: pv.value.i32 = *(int32_t*)p->ptr; break; } send_frame(CMD_PARAM_VALUE, (const uint8_t*)&pv, sizeof(ParamWritePayload)); } static void send_all_params(void) { for (int i = 0; i < PARAM_COUNT; i++) send_param_value(¶m_table[i]); } static void process_rx_frame(uint8_t cmd, const uint8_t *payload, uint8_t len) { switch (cmd) { case CMD_PARAM_WRITE: if (len >= sizeof(ParamWritePayload)) apply_param_write((const ParamWritePayload*)payload); break; case CMD_PARAM_READ_ALL: send_all_params(); break; case CMD_PING: send_frame(CMD_PONG, NULL, 0); break; default: break; } } /* ---- Public API ---- */ void Proto_Init(UART_HandleTypeDef *huart) { memset(&proto, 0, sizeof(proto)); proto.huart = huart; proto.rx_state = RX_WAIT_SYNC; param_table_init(); start_rx(); } void Proto_SendTelemetry(void) { TelemetryPayload t; t.vin = vin; t.vout = vout; t.iin = iin; t.iout = iout; t.vfly = vfly; t.etemp = etemp; t.last_tmp = last_tmp; t.VREF = VREF; t.vfly_correction = vfly_correction; t._pad0 = 0; t.vfly_integral = vfly_integral; t.vfly_avg_debug = vfly_avg_debug; t.cc_output_f = cc.output_f; t.mppt_iref = mppt.iref; t.mppt_last_vin = mppt.last_vin; t.mppt_last_iin = mppt.last_iin; t.p_in = vin * (-iin) / 1e6f; /* mV * mA → W */ t.p_out = vout * iout / 1e6f; /* mV * mA → W */ t.seq = proto.seq++; t._pad1[0] = 0; t._pad1[1] = 0; t._pad1[2] = 0; send_frame(CMD_TELEMETRY, (const uint8_t*)&t, sizeof(TelemetryPayload)); } void Proto_SendError(const char *msg) { uint8_t len = 0; while (msg[len] && len < PROTO_MAX_PAYLOAD) len++; send_frame(CMD_ERROR_MSG, (const uint8_t*)msg, len); } void Proto_SendDiagDump(const char *reason) { char buf[128]; /* Line 1: reason */ Proto_SendError(reason); /* Line 2: converted analog values (integer-only — no float printf with nano.specs) */ snprintf(buf, sizeof(buf), "DIAG V:%lu/%lu I:%d/%lu Vfly:%lu T:%d.%u", (unsigned long)(vin > 0 ? (uint32_t)vin : 0), (unsigned long)(vout > 0 ? (uint32_t)vout : 0), (int)iin, (unsigned long)(iout > 0 ? (uint32_t)iout : 0), (unsigned long)(vfly > 0 ? (uint32_t)vfly : 0), (int)etemp, ((unsigned)(etemp > 0 ? etemp : -etemp) % 10)); Proto_SendError(buf); /* Line 3: raw ADC counts */ snprintf(buf, sizeof(buf), "RAW adc1[%u,%u,%u] adc2[%u,%u] adc4[%u] adc5[%u]", DMA1BUF2[0], DMA1BUF2[1], DMA1BUF2[2], DMA1BUF3[0], DMA1BUF3[1], DMA1BUF1, DMA1BUF4); Proto_SendError(buf); /* Line 4: controller state */ snprintf(buf, sizeof(buf), "CTRL VREF:%u last_tmp:%d vfly_corr:%d cc_out:%d mppt_iref:%d", VREF, (int)last_tmp, (int)vfly_correction, (int)cc.output_f, (int)mppt.iref); Proto_SendError(buf); /* Line 5: HRTIM fault status + comparator outputs */ uint32_t hrtim_isr = HRTIM1->sCommonRegs.ISR; uint32_t hrtim_oenr = HRTIM1->sCommonRegs.OENR; uint32_t comp_out = 0; if (HAL_COMP_GetOutputLevel(&hcomp1) == COMP_OUTPUT_LEVEL_HIGH) comp_out |= 1; if (HAL_COMP_GetOutputLevel(&hcomp3) == COMP_OUTPUT_LEVEL_HIGH) comp_out |= 2; if (HAL_COMP_GetOutputLevel(&hcomp4) == COMP_OUTPUT_LEVEL_HIGH) comp_out |= 4; snprintf(buf, sizeof(buf), "HW HRTIM_ISR:%08lX OENR:%08lX COMP:%lu FMAC_SR:%08lX", hrtim_isr, hrtim_oenr, comp_out, FMAC->SR); Proto_SendError(buf); /* Line 6: HRTIM duty + master compare */ snprintf(buf, sizeof(buf), "HRTIM TE_CMP1:%lu TF_CMP1:%lu MCMP1:%lu", HRTIM1->sTimerxRegs[HRTIM_TIMERINDEX_TIMER_E].CMP1xR, HRTIM1->sTimerxRegs[HRTIM_TIMERINDEX_TIMER_F].CMP1xR, HRTIM1->sMasterRegs.MCMP1R); Proto_SendError(buf); /* Line 7: active flags + ADC3 offset */ snprintf(buf, sizeof(buf), "FLAGS cc_act:%d mppt_act:%d vfly_act:%u ADC3_OFR1:%08lX", cc_active, mppt_active, (unsigned)vfly_active, ADC3->OFR1); Proto_SendError(buf); } /* ---- HAL Callbacks ---- */ void Proto_TxCpltCallback(UART_HandleTypeDef *huart) { if (huart == proto.huart) proto.tx_busy = 0; } void Proto_ErrorCallback(UART_HandleTypeDef *huart) { if (huart != proto.huart) return; proto.uart_errors++; uart_recover(); } void Proto_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart != proto.huart) return; uint8_t b = proto.rx_byte; switch (proto.rx_state) { case RX_WAIT_SYNC: if (b == PROTO_SYNC_BYTE) { proto.rx_buf[0] = b; proto.rx_state = RX_WAIT_CMD; } break; case RX_WAIT_CMD: proto.rx_cmd = b; proto.rx_buf[1] = b; proto.rx_state = RX_WAIT_LEN; break; case RX_WAIT_LEN: proto.rx_len = b; proto.rx_buf[2] = b; proto.rx_idx = 0; if (b == 0) proto.rx_state = RX_WAIT_CRC; else if (b > PROTO_MAX_PAYLOAD) proto.rx_state = RX_WAIT_SYNC; /* invalid, reset */ else proto.rx_state = RX_WAIT_PAYLOAD; break; case RX_WAIT_PAYLOAD: proto.rx_buf[PROTO_HEADER_SIZE + proto.rx_idx] = b; proto.rx_idx++; if (proto.rx_idx >= proto.rx_len) proto.rx_state = RX_WAIT_CRC; break; case RX_WAIT_CRC: { uint8_t expected = crc8(proto.rx_buf, PROTO_HEADER_SIZE + proto.rx_len); if (b == expected) process_rx_frame(proto.rx_cmd, &proto.rx_buf[PROTO_HEADER_SIZE], proto.rx_len); proto.rx_state = RX_WAIT_SYNC; break; } } /* Re-arm RX */ start_rx(); }