/* ==========================================================================
RatNET Audio Analyzer - Firmware Master UI (PIC18F4550)
Autor: Carlos Alvarez (Power by AlfinPatagonico)
Rev. 2.3 - USB - Edicion no bloqueante
========================================================================== */
#include <18F4550.h>
// ¡VREGEN es OBLIGATORIO para el USB en el 18F4550!
#FUSES HSPLL, NOWDT, NOPROTECT, NOLVP, NODEBUG, USBDIV, PLL5, CPUDIV1, VREGEN
#use delay(clock=48000000)
// USB CDC: Debe ir justo después del delay, sin #use timer
#include <usb_cdc.h>
// ============================================================================
// CONFIGURACIÓN DE PERIFÉRICOS
// ============================================================================
#use rs232(UART1, baud=9600, xmit=PIN_C6, rcv=PIN_C7, stream=DSP_UART, ERRORS)
#define LCD_BL PIN_B3
#include <lcd420.c>
// ============================================================================
// DEFINICIÓN DE PINES
// ============================================================================
#define SHIFT_DATA PIN_E0
#define SHIFT_CLK PIN_E1
#define SHIFT_RST PIN_E2
#define RELAY_MAX264 PIN_D0
#define RELAY_TS PIN_D1
#define RELAY_LOAD1 PIN_D2
#define RELAY_SCALE100V PIN_D3
#define RELAY_SCALE1V PIN_D4
#define RELAY_SCALE10V PIN_D5
#define RELAY_LOAD2 PIN_D6
#define ADC_TEMP 0
#define ADC_AUTORANGE 1
#define ADC_KEYS1 2
#define ADC_KEYS2 3
#define ENC_BTN PIN_C0
#define ENC_A PIN_C1
#define ENC_B PIN_C2
// ============================================================================
// COMANDOS UART
// ============================================================================
#define CMD_UPDATE_CONFIG 0xB0
#define CMD_EXECUTE 0xB1
#define CMD_READ_RESULT 0xB2
#define CMD_STOP 0x21
#define ACK 0x06
#define ID_THD 0x10
#define ID_SLEW 0x60
#define ID_POWER 0x30
#define ID_TS_PARAMS 0x50
#define ID_FREQ_RESP 0x80
// ============================================================================
// CONSTANTES
// ============================================================================
#define ADC_TOLERANCE 30
#define TEMP_MAX_ADC 143
#define ENC_DEBOUNCE_MS 50
#define BTN_DEBOUNCE_MS 30
#define UART_TIMEOUT_MS 500
#define MAX_QUEUE_SIZE 6
#define MAX_RESULTS 6
#define LONG_PRESS_MS 1500
#define TEMP_ALERT_HOLD_MS 2000
#define BTN_THRESHOLD_1 1002
#define BTN_THRESHOLD_2 958
#define BTN_THRESHOLD_3 880
#define BTN_THRESHOLD_4 729
#define BTN_THRESHOLD_5 445
#define STATE_IDLE 0
#define STATE_RUNNING 1
#define THD_MODE_GOERTZEL 0
#define THD_MODE_RESIDUAL 1
#define THD_MODE_IMD 2
// Máquina de estados de ejecución de la cola (no bloqueante)
#define QSTATE_IDLE 0
#define QSTATE_WAIT_ACK 1
#define QSTATE_WAIT_RESULT 2
#define BTN_THD ID_THD
#define BTN_SWEEP ID_FREQ_RESP
#define BTN_RMS ID_POWER
#define BTN_SLEW ID_SLEW
#define BTN_TS ID_TS_PARAMS
#define BTN_DDS 0x90
#define BTN_START 0x01
#define BTN_CLEAR 0x02
#define BTN_LOAD 0x03
#define BTN_SCALE 0x04
// ============================================================================
// VARIABLES GLOBALES
// ============================================================================
unsigned int32 system_ms = 0; // Reemplazo seguro para get_ticks()
int8 queue[MAX_QUEUE_SIZE];
int8 queue_size = 0;
int8 queue_index = 0;
int8 system_state = STATE_IDLE;
int8 thd_mode = THD_MODE_GOERTZEL;
float thd_freq = 1000.0;
int8 scale_mode = 0;
int8 load_mode = 0;
int8 load_rotation = 0;
int1 auto_range = 1;
#define RX_BUF_SIZE 128
char rx_buffer[RX_BUF_SIZE];
volatile int8 rx_idx = 0;
volatile int1 line_ready = 0;
volatile int1 ack_received = 0; // Set por la ISR cuando llega el byte ACK
int8 enc_last_state = 0;
int8 enc_direction = 0;
int1 enc_btn_pressed = 0;
typedef struct {
int8 test_id;
int8 num_values;
float values[10];
int1 completed;
} TestResult_t;
TestResult_t result_buffer[MAX_RESULTS];
int8 result_count = 0;
int8 current_page = 0;
int8 total_pages = 0;
unsigned int32 last_enc_read = 0;
unsigned int32 last_temp_check = 0;
unsigned int32 button_press_time = 0;
int8 last_button = 0;
// Debounce de botones no bloqueante
int8 button_candidate = 0;
unsigned int32 button_candidate_time = 0;
// Estado de ejecución de la cola (reemplaza al ProcessQueue/ExecuteTest bloqueantes)
int8 queue_run_state = QSTATE_IDLE;
unsigned int32 queue_run_timer = 0;
// Alerta de temperatura no bloqueante
int1 temp_alert_active = 0;
unsigned int32 temp_alert_timer = 0;
// ============================================================================
// PROTOTIPOS
// ============================================================================
void InitSystem(void);
void ShiftRegister_Write(int8 data);
void UpdateLEDs(void);
void SetRelay(int8 relay, int1 state);
void SetScale(int8 scale);
void SetLoad(int8 load);
int8 ReadButtonAN2(void);
int8 ReadButtonAN3(void);
void ProcessButtons(void);
void ReadEncoder(void);
void StartTest(int8 test_id);
void StartQueueExecution(void);
void UpdateQueueExecution(void);
void AdvanceQueue(void);
void SendCommand(int8 cmd, int8 *params, int8 len);
void ParseAndStoreResult(char *rx_buf);
void UpdateLCD(void);
void DisplayCurrentPage(void);
void CheckTemperature(void);
void AutoRange(void);
int8 ComputeChecksum(int8 *data, int8 len);
void ClearResults(void);
void ClearQueue(void);
void ReturnToIdle(void);
void RemoveFromQueue(int8 test_id);
// ============================================================================
// INTERRUPCIÓN UART
// ============================================================================
// Detecta el ACK de un solo byte sin esperar salto de línea (antes se perdía:
// la ISR igual consumía ese byte hacia rx_buffer y WaitForACK() nunca lo veía).
#INT_RDA
void uart_isr(void) {
char c = fgetc(DSP_UART);
if (c == ACK) {
ack_received = 1;
return;
}
if (c == '\n' || c == '\r') {
if (rx_idx > 0) {
rx_buffer[rx_idx] = '\0';
line_ready = 1;
rx_idx = 0;
}
} else {
if (rx_idx < RX_BUF_SIZE - 1) {
rx_buffer[rx_idx++] = c;
} else {
rx_idx = 0;
}
}
}
// ============================================================================
// FUNCIONES DE HARDWARE
// ============================================================================
void ShiftRegister_Write(int8 data) {
int8 i;
output_low(SHIFT_RST);
delay_us(1);
output_high(SHIFT_RST);
for (i = 0; i < 8; i++) {
if (bit_test(data, 7)) output_high(SHIFT_DATA);
else output_low(SHIFT_DATA);
output_low(SHIFT_CLK);
delay_us(1);
output_high(SHIFT_CLK);
data = data << 1;
}
}
void UpdateLEDs(void) {
int8 leds = 0;
int8 i;
for (i = 0; i < queue_size; i++) {
if (queue[i] == ID_THD) bit_set(leds, 0);
if (queue[i] == ID_FREQ_RESP) bit_set(leds, 1);
if (queue[i] == ID_POWER) bit_set(leds, 2);
if (queue[i] == ID_SLEW) bit_set(leds, 3);
if (queue[i] == ID_TS_PARAMS) bit_set(leds, 4);
if (queue[i] == 0x90) bit_set(leds, 5);
}
if (load_mode == 0) bit_set(leds, 6);
if (load_mode == 1) bit_set(leds, 7);
ShiftRegister_Write(leds);
}
void SetRelay(int8 relay, int1 state) {
if (state) output_high(relay); else output_low(relay);
}
void SetScale(int8 scale) {
SetRelay(RELAY_SCALE1V, 0); SetRelay(RELAY_SCALE10V, 0); SetRelay(RELAY_SCALE100V, 0);
if (scale == 1) SetRelay(RELAY_SCALE1V, 1);
else if (scale == 2) SetRelay(RELAY_SCALE10V, 1);
else if (scale == 3) SetRelay(RELAY_SCALE100V, 1);
}
// ============================================================================
// FUNCIÓN: SetLoad (Con alternancia de desgaste y estado OFF)
// ============================================================================
void SetLoad(int8 load) {
if (load == 0) {
// 8O: Alternar entre RELAY_LOAD1 (RD2) y RELAY_LOAD2 (RD6)
if (load_rotation == 0) {
SetRelay(RELAY_LOAD1, 1);
SetRelay(RELAY_LOAD2, 0);
} else {
SetRelay(RELAY_LOAD1, 0);
SetRelay(RELAY_LOAD2, 1);
}
// Invertir el estado para la próxima vez que se active 8O
load_rotation = 1 - load_rotation;
}
else if (load == 1) {
// 4O: Ambas en paralelo
SetRelay(RELAY_LOAD1, 1);
SetRelay(RELAY_LOAD2, 1);
}
else {
// 2: OFF (Sin carga activa)
SetRelay(RELAY_LOAD1, 0);
SetRelay(RELAY_LOAD2, 0);
}
}
int8 ReadButtonAN2(void) {
int16 adc_val;
set_adc_channel(ADC_KEYS1); delay_us(20); adc_val = read_adc();
if (adc_val > (BTN_THRESHOLD_1 - ADC_TOLERANCE) && adc_val < (BTN_THRESHOLD_1 + ADC_TOLERANCE)) return BTN_THD;
if (adc_val > (BTN_THRESHOLD_2 - ADC_TOLERANCE) && adc_val < (BTN_THRESHOLD_2 + ADC_TOLERANCE)) return BTN_SWEEP;
if (adc_val > (BTN_THRESHOLD_3 - ADC_TOLERANCE) && adc_val < (BTN_THRESHOLD_3 + ADC_TOLERANCE)) return BTN_RMS;
if (adc_val > (BTN_THRESHOLD_4 - ADC_TOLERANCE) && adc_val < (BTN_THRESHOLD_4 + ADC_TOLERANCE)) return BTN_SLEW;
if (adc_val > (BTN_THRESHOLD_5 - ADC_TOLERANCE) && adc_val < (BTN_THRESHOLD_5 + ADC_TOLERANCE)) return BTN_TS;
return 0;
}
int8 ReadButtonAN3(void) {
int16 adc_val;
set_adc_channel(ADC_KEYS2); delay_us(20); adc_val = read_adc();
if (adc_val > (BTN_THRESHOLD_1 - ADC_TOLERANCE) && adc_val < (BTN_THRESHOLD_1 + ADC_TOLERANCE)) return BTN_DDS;
if (adc_val > (BTN_THRESHOLD_2 - ADC_TOLERANCE) && adc_val < (BTN_THRESHOLD_2 + ADC_TOLERANCE)) return BTN_START;
if (adc_val > (BTN_THRESHOLD_3 - ADC_TOLERANCE) && adc_val < (BTN_THRESHOLD_3 + ADC_TOLERANCE)) return BTN_CLEAR;
if (adc_val > (BTN_THRESHOLD_4 - ADC_TOLERANCE) && adc_val < (BTN_THRESHOLD_4 + ADC_TOLERANCE)) return BTN_LOAD;
if (adc_val > (BTN_THRESHOLD_5 - ADC_TOLERANCE) && adc_val < (BTN_THRESHOLD_5 + ADC_TOLERANCE)) return BTN_SCALE;
return 0;
}
// ============================================================================
// LÓGICA DE UI
// ============================================================================
// Antes: cada acción hacía delay_ms(200), congelando usb_task()/UART durante
// 200ms por cada toque de botón. Ahora: debounce por tiempo (no bloqueante,
// dos lecturas estables separadas por BTN_DEBOUNCE_MS) y disparo único por
// flanco de pulsación (edge), sin ningún delay_ms.
void ProcessButtons(void) {
int8 btn_func = ReadButtonAN2();
int8 btn_ctrl = ReadButtonAN3();
int8 current_btn = btn_func | btn_ctrl;
if (current_btn != button_candidate) {
button_candidate = current_btn;
button_candidate_time = system_ms;
return; // todavía rebotando, se confirma en una vuelta futura del loop
}
if ((system_ms - button_candidate_time) < BTN_DEBOUNCE_MS) return;
// current_btn es ahora una lectura estable y confirmada
if (current_btn != 0 && current_btn == last_button) {
if (system_state == STATE_IDLE && current_btn > 0x04 &&
(system_ms - button_press_time) > LONG_PRESS_MS) {
RemoveFromQueue(current_btn);
last_button = 0; // evita retrigger hasta soltar el botón
}
return;
}
last_button = current_btn;
button_press_time = system_ms;
if (current_btn == 0) return; // fue una liberación, no una pulsación nueva
if (btn_ctrl == BTN_START) {
if (system_state == STATE_IDLE) {
if (queue_size > 0) {
system_state = STATE_RUNNING;
ClearResults();
lcd_gotoxy(1, 4);
printf(lcd_putc, "Ejecutando... ");
StartQueueExecution();
}
} else {
fputc(CMD_STOP, DSP_UART);
queue_run_state = QSTATE_IDLE;
ReturnToIdle();
}
return;
}
if (system_state == STATE_RUNNING) return;
if (btn_ctrl == BTN_CLEAR) {
ClearQueue(); ClearResults(); current_page = 0; total_pages = 0;
lcd_gotoxy(1, 4); printf(lcd_putc, "Cola limpiada ");
UpdateLEDs(); return;
}
// Botón Load Select (Cicla: 8O -> 4O -> OFF -> 8O)
if (btn_ctrl == BTN_LOAD) {
load_mode++;
if (load_mode > 2) load_mode = 0;
SetLoad(load_mode);
UpdateLEDs();
lcd_gotoxy(1, 4);
if (load_mode == 0) {
printf(lcd_putc, "Load: 8%c ",0xE4);
} else if (load_mode == 1) {
printf(lcd_putc, "Load: 4%c ",0xE4);
} else {
printf(lcd_putc, "Load: OFF ");
}
return;
}
if (btn_ctrl == BTN_SCALE) {
scale_mode++; if (scale_mode > 3) scale_mode = 0;
if (scale_mode == 0) { auto_range = 1; lcd_gotoxy(1, 4); printf(lcd_putc, "Scale: AUTO "); }
else {
auto_range = 0; SetScale(scale_mode); lcd_gotoxy(1, 4);
if (scale_mode == 1) printf(lcd_putc, "Scale: 1V ");
else if (scale_mode == 2) printf(lcd_putc, "Scale: 10V ");
else printf(lcd_putc, "Scale: 100V ");
}
return;
}
if (btn_func != 0) {
if (btn_func == BTN_TS || btn_func == BTN_DDS) {
ClearQueue(); queue[0] = btn_func; queue_size = 1;
} else {
int1 already_in_queue = 0; int8 i;
for (i = 0; i < queue_size; i++) { if (queue[i] == btn_func) { already_in_queue = 1; break; } }
if (!already_in_queue && queue_size < MAX_QUEUE_SIZE) queue[queue_size++] = btn_func;
}
UpdateLEDs(); lcd_gotoxy(1, 4); printf(lcd_putc, "Cola: %u pruebas ", queue_size);
}
}
void RemoveFromQueue(int8 test_id) {
int8 found = 0; int8 i, j;
for (i = 0; i < queue_size; i++) {
if (queue[i] == test_id) {
found = 1;
for (j = i; j < queue_size - 1; j++) queue[j] = queue[j + 1];
queue_size--; break;
}
}
if (found) { UpdateLEDs(); lcd_gotoxy(1, 4); printf(lcd_putc, "Quitado de cola "); }
}
void ReadEncoder(void) {
int8 state = 0;
if (!input(ENC_A)) state |= 0x01;
if (!input(ENC_B)) state |= 0x02;
if (!input(ENC_BTN)) state |= 0x04;
if (state != enc_last_state) {
if ((enc_last_state == 0x00 && state == 0x01) || (enc_last_state == 0x01 && state == 0x03) ||
(enc_last_state == 0x03 && state == 0x02) || (enc_last_state == 0x02 && state == 0x00)) enc_direction = 1;
else if ((enc_last_state == 0x00 && state == 0x02) || (enc_last_state == 0x02 && state == 0x03) ||
(enc_last_state == 0x03 && state == 0x01) || (enc_last_state == 0x01 && state == 0x00)) enc_direction = 2;
if (state & 0x04) enc_btn_pressed = 1;
enc_last_state = state;
}
}
// ============================================================================
// EJECUCIÓN DE PRUEBAS (máquina de estados no bloqueante)
// ============================================================================
// Antes: ProcessQueue() era un for{} que llamaba ExecuteTest() de forma
// sincrónica para cada prueba, y ExecuteTest() a su vez bloqueaba con
// WaitForACK() (hasta 500ms) y un while(!line_ready) (hasta 500ms más).
// Mientras tanto usb_task() no se llamaba: el enlace USB podía caerse o
// perder datos durante todo el tiempo que tomara ejecutar la cola completa.
//
// Ahora: StartTest() solo transmite y arma el estado; UpdateQueueExecution()
// se llama una vez por vuelta del loop principal y avanza un paso cuando
// corresponde (ACK recibido, resultado recibido, o timeout). El resto del
// sistema (USB, botones, encoder, temperatura) sigue respondiendo en todo
// momento, incluso con la cola en ejecución.
void StartTest(int8 test_id) {
int8 config[8]; int8 checksum;
lcd_gotoxy(1, 4); printf(lcd_putc, "Test %u/%u: ", queue_index + 1, queue_size);
switch (test_id) {
case ID_THD: printf(lcd_putc, "THD"); break;
case ID_SLEW: printf(lcd_putc, "SLEW"); break;
case ID_POWER: printf(lcd_putc, "POWER"); break;
case ID_TS_PARAMS: printf(lcd_putc, "T/S"); break;
case ID_FREQ_RESP: printf(lcd_putc, "FRESP"); break;
case 0x90: printf(lcd_putc, "DDS"); break;
}
if (test_id == ID_THD) {
int16 muestras = 4800; int16 freq = (int16)thd_freq;
config[0] = (muestras >> 8) & 0xFF; config[1] = muestras & 0xFF;
config[2] = (freq >> 8) & 0xFF; config[3] = freq & 0xFF;
config[4] = thd_mode; config[5] = 0; config[6] = 0; config[7] = 0;
checksum = ComputeChecksum(config, 8);
SendCommand(CMD_UPDATE_CONFIG, &test_id, 1); SendCommand(CMD_UPDATE_CONFIG, config, 9);
} else if (test_id == ID_SLEW || test_id == ID_POWER) {
int32 muestras = 512; float carga = 8.0;
config[0] = (muestras >> 24) & 0xFF; config[1] = (muestras >> 16) & 0xFF;
config[2] = (muestras >> 8) & 0xFF; config[3] = muestras & 0xFF;
int8 i; int8 *carga_ptr = (int8*)&carga;
for (i = 0; i < 4; i++) config[4 + i] = carga_ptr[i];
checksum = ComputeChecksum(config, 8);
SendCommand(CMD_UPDATE_CONFIG, &test_id, 1); SendCommand(CMD_UPDATE_CONFIG, config, 9);
}
ack_received = 0;
SendCommand(CMD_EXECUTE, &test_id, 1);
queue_run_state = QSTATE_WAIT_ACK;
queue_run_timer = system_ms;
}
void StartQueueExecution(void) {
queue_index = 0;
StartTest(queue[queue_index]);
}
void AdvanceQueue(void) {
queue_index++;
if (queue_index < queue_size) {
StartTest(queue[queue_index]);
} else {
queue_run_state = QSTATE_IDLE;
ReturnToIdle();
}
}
void UpdateQueueExecution(void) {
if (system_state != STATE_RUNNING) return;
switch (queue_run_state) {
case QSTATE_WAIT_ACK:
if (ack_received) {
ack_received = 0;
SendCommand(CMD_READ_RESULT, &queue[queue_index], 1);
queue_run_state = QSTATE_WAIT_RESULT;
queue_run_timer = system_ms;
} else if ((system_ms - queue_run_timer) > UART_TIMEOUT_MS) {
lcd_gotoxy(1, 4); printf(lcd_putc, "Error: Timeout ");
AdvanceQueue();
}
break;
case QSTATE_WAIT_RESULT:
if (line_ready) {
ParseAndStoreResult(rx_buffer);
line_ready = 0;
total_pages = result_count;
if (current_page >= total_pages) current_page = total_pages - 1;
DisplayCurrentPage();
AdvanceQueue();
} else if ((system_ms - queue_run_timer) > UART_TIMEOUT_MS) {
lcd_gotoxy(1, 4); printf(lcd_putc, "Error: Timeout ");
AdvanceQueue();
}
break;
default:
break;
}
}
void SendCommand(int8 cmd, int8 *params, int8 len) {
disable_interrupts(INT_RDA);
fputc(cmd, DSP_UART); int8 i;
for (i = 0; i < len; i++) fputc(params[i], DSP_UART);
enable_interrupts(INT_RDA);
}
void ParseAndStoreResult(char *rx_buf) {
int8 start = 0; int8 count = 0; int8 i = 0; int8 str_idx = 0;
char num_str[20]; float values[10]; int8 test_id = 0;
if (rx_buf[0] != 'R' || rx_buf[1] != 'E' || rx_buf[2] != 'S' || rx_buf[3] != ':') return;
start = 4;
while (rx_buf[start] != ':' && rx_buf[start] != '\0') start++;
if (rx_buf[start] == '\0') return; start++;
while (rx_buf[start] >= '0' && rx_buf[start] <= '9') { test_id = test_id * 10 + (rx_buf[start] - '0'); start++; }
while (rx_buf[start] != ':' && rx_buf[start] != '\0') start++;
if (rx_buf[start] == '\0') return; start++;
while (rx_buf[start] != '\0' && count < 10) {
str_idx = 0;
while (rx_buf[start] != ',' && rx_buf[start] != '\0' && str_idx < 19) num_str[str_idx++] = rx_buf[start++];
num_str[str_idx] = '\0';
float result = 0.0; float decimal = 0.1; int1 is_negative = 0; int8 idx = 0;
if (num_str[idx] == '-') { is_negative = 1; idx++; }
while (num_str[idx] >= '0' && num_str[idx] <= '9') { result = result * 10.0 + (num_str[idx] - '0'); idx++; }
if (num_str[idx] == '.') { idx++; while (num_str[idx] >= '0' && num_str[idx] <= '9') { result += (num_str[idx] - '0') * decimal; decimal *= 0.1; idx++; } }
if (is_negative) result = -result;
values[count] = result; count++;
if (rx_buf[start] == ',') start++;
}
if (result_count < MAX_RESULTS) {
result_buffer[result_count].test_id = test_id; result_buffer[result_count].num_values = count;
for (i = 0; i < count && i < 10; i++) result_buffer[result_count].values[i] = values[i];
result_buffer[result_count].completed = 1; result_count++;
}
}
void DisplayCurrentPage(void) {
if (total_pages == 0 || current_page >= result_count) {
lcd_gotoxy(1, 1); printf(lcd_putc, "No Results ");
lcd_gotoxy(1, 2); printf(lcd_putc, " ");
lcd_gotoxy(1, 3); printf(lcd_putc, " ");
lcd_gotoxy(1, 4); printf(lcd_putc, "Encoder: Browse "); return;
}
TestResult_t *result = &result_buffer[current_page];
lcd_gotoxy(1, 1); lcd_gotoxy(1, 2); lcd_gotoxy(1, 3); lcd_gotoxy(1, 4);
switch (result->test_id) {
case ID_THD:
if (thd_mode == THD_MODE_IMD) {
printf(lcd_putc, "IMD: %f%% ", result->values[0]);
printf(lcd_putc, "F1: %fVrms ", result->values[1]);
printf(lcd_putc, "F2: %fVrms ", result->values[2]);
printf(lcd_putc, "SMPTE 60+7kHz ");
} else {
printf(lcd_putc, "THD:%f%% N:%f%%", result->values[0], result->values[1]);
printf(lcd_putc, "SINAD:%fdB SNR:%f", result->values[2], result->values[3]);
printf(lcd_putc, "Fund:%fVrms ", result->values[4]);
printf(lcd_putc, "Freq:%fHz M:", thd_freq);
if (thd_mode == 0) printf(lcd_putc, "Goertzel"); else printf(lcd_putc, "Residual");
} break;
case ID_SLEW:
printf(lcd_putc, "Slew Rate: ");
printf(lcd_putc, " %f V/us ", result->values[0]);
printf(lcd_putc, "Vmax: %fV ", result->values[2]);
printf(lcd_putc, "Freq:1000Hz Sq "); break;
case ID_POWER:
if (current_page % 2 == 0) {
printf(lcd_putc, "Power:%fW ", result->values[0]);
printf(lcd_putc, "Peak: %fW ", result->values[1]);
printf(lcd_putc, "Eff: %f%% ", result->values[5]);
printf(lcd_putc, "Vrail:%fV ", result->values[2]);
} else {
printf(lcd_putc, "Damp: %f ", result->values[6]);
printf(lcd_putc, "Zout: %f Ohm ", result->values[7]);
printf(lcd_putc, "VA: %f VA ", result->values[8]);
printf(lcd_putc, "PF: %f ", result->values[9]);
} break;
case ID_TS_PARAMS:
if (current_page % 2 == 0) {
printf(lcd_putc, "Thiele-Small ");
printf(lcd_putc, "Fs: %f Hz ", result->values[0]);
printf(lcd_putc, "Qts: %f ", result->values[1]);
printf(lcd_putc, "Qes: %f ", result->values[2]);
} else {
printf(lcd_putc, "Thiele-Small ");
printf(lcd_putc, "Qms: %f ", result->values[3]);
printf(lcd_putc, "Re: %f Ohm ", result->values[4]);
printf(lcd_putc, "Zmax: %f Ohm ", result->values[5]);
} break;
case ID_FREQ_RESP:
if (current_page == 0) {
printf(lcd_putc, "Freq Response ");
printf(lcd_putc, "Points: 32 ");
printf(lcd_putc, "Noise:%f dB ", result->values[32]);
printf(lcd_putc, "DynSNR:%f dB ", result->values[33]);
} else {
int8 freq_idx = (current_page - 1) * 4;
if (freq_idx < 32) printf(lcd_putc, "%fHz:%f dB ", (float)freq_idx, result->values[freq_idx]);
if (freq_idx + 1 < 32) printf(lcd_putc, "%fHz:%f dB ", (float)(freq_idx+1), result->values[freq_idx+1]);
if (freq_idx + 2 < 32) printf(lcd_putc, "%fHz:%f dB ", (float)(freq_idx+2), result->values[freq_idx+2]);
if (freq_idx + 3 < 32) printf(lcd_putc, "%fHz:%f dB ", (float)(freq_idx+3), result->values[freq_idx+3]);
} break;
}
if (total_pages > 1) { lcd_gotoxy(16, 4); printf(lcd_putc, "%u/%u", current_page + 1, total_pages); }
}
void UpdateLCD(void) {
if (system_state == STATE_IDLE) {
lcd_gotoxy(1, 1); printf(lcd_putc, "RatNET Analyzer ");
lcd_gotoxy(1, 2); printf(lcd_putc, "THD Freq: %f Hz ", thd_freq);
lcd_gotoxy(1, 3);
if (auto_range) printf(lcd_putc, "Scale:AUTO Load:"); else printf(lcd_putc, "Scale: Load:");
if (load_mode == 0) printf(lcd_putc, "8"); else printf(lcd_putc, "4");
printf(lcd_putc, "%c ",0xE4);
lcd_gotoxy(1, 4); printf(lcd_putc, "Queue: %u tests ", queue_size);
}
}
// Antes: delay_ms(2000) bloqueante en cada ciclo con sobretemperatura, lo que
// congelaba USB/UART/botones 2 segundos completos. Ahora el mensaje se
// sostiene con un temporizador de system_ms y el loop principal sigue vivo.
void CheckTemperature(void) {
set_adc_channel(ADC_TEMP); delay_us(20); int16 temp_adc = read_adc();
if (temp_adc > TEMP_MAX_ADC) {
if (!temp_alert_active) {
temp_alert_active = 1;
SetRelay(RELAY_LOAD1, 0); SetRelay(RELAY_LOAD2, 0);
if (system_state == STATE_RUNNING) {
fputc(CMD_STOP, DSP_UART);
queue_run_state = QSTATE_IDLE;
ReturnToIdle();
}
}
temp_alert_timer = system_ms;
lcd_gotoxy(1, 4); printf(lcd_putc, "HI TEMP ALERT! ");
} else if (temp_alert_active && (system_ms - temp_alert_timer) > TEMP_ALERT_HOLD_MS) {
temp_alert_active = 0;
}
}
void AutoRange(void) {
if (!auto_range) return;
set_adc_channel(ADC_AUTORANGE); delay_us(20); int16 adc_val = read_adc();
if (adc_val < 20) SetScale(1); else if (adc_val < 200) SetScale(2); else SetScale(3);
}
int8 ComputeChecksum(int8 *data, int8 len) {
int8 checksum = 0; int8 i;
for (i = 0; i < len; i++) checksum ^= data[i];
return checksum;
}
void ClearResults(void) { result_count = 0; current_page = 0; total_pages = 0; }
void ClearQueue(void) { queue_size = 0; queue_index = 0; }
void ReturnToIdle(void) { system_state = STATE_IDLE; queue_index = 0; UpdateLCD(); }
// ============================================================================
// INICIALIZACIÓN
// ============================================================================
void InitSystem(void) {
setup_adc_ports(AN0_TO_AN3);
setup_adc(ADC_CLOCK_DIV_64);
output_low(RELAY_MAX264); output_low(RELAY_TS); output_low(RELAY_LOAD1);
output_low(RELAY_SCALE100V); output_low(RELAY_SCALE1V); output_low(RELAY_SCALE10V); output_low(RELAY_LOAD2);
output_high(LCD_BL);
lcd_init();
lcd_gotoxy(1, 1); printf(lcd_putc, " RatNET ");
lcd_gotoxy(1, 2); printf(lcd_putc, " Audio Analyzer ");
delay_ms(1000); // Único delay bloqueante que queda: splash de arranque, una sola vez.
ShiftRegister_Write(0x00);
SetLoad(0);
auto_range = 1;
// Inicializar USB CDC (Nativo de CCS)
usb_cdc_init();
enable_interrupts(INT_RDA);
enable_interrupts(GLOBAL);
lcd_putc('\f');
UpdateLCD();
}
// ============================================================================
// FUNCIÓN PRINCIPAL
// ============================================================================
void main(void) {
InitSystem();
while (TRUE) {
system_ms += 10; // Contador de tiempo seguro (10ms por iteración)
// 1. Mantener el stack USB vivo (CRÍTICO: debe ser lo primero)
usb_task();
// 2. Puente USB -> UART (La PC envía comandos al dsPIC)
if (usb_enumerated()) {
if (usb_cdc_kbhit()) {
char c = usb_cdc_getc();
fputc(c, DSP_UART);
}
}
// 3. UI Frontal - Botones (no bloqueante, debounce por tiempo)
ProcessButtons();
// 4. Encoder
unsigned int32 enc_elapsed = system_ms - last_enc_read;
if (enc_elapsed > ENC_DEBOUNCE_MS) {
ReadEncoder();
last_enc_read = system_ms;
if (enc_direction == 1 && current_page < total_pages - 1) {
current_page++;
DisplayCurrentPage();
} else if (enc_direction == 2 && current_page > 0) {
current_page--;
DisplayCurrentPage();
}
enc_direction = 0;
}
// 5. Puente UART -> USB (El dsPIC envía resultados a la PC)
if (line_ready) {
if (usb_cdc_connected()) {
int8 i = 0;
while (rx_buffer[i] != '\0') {
usb_cdc_putc(rx_buffer[i]);
i++;
}
usb_cdc_putc('\r');
usb_cdc_putc('\n');
}
// Si la cola está corriendo, UpdateQueueExecution() consume line_ready
// en este mismo ciclo (paso 7). Si no, lo consumimos acá para no
// dejar basura pendiente ni parsear dos veces la misma línea.
if (system_state != STATE_RUNNING) {
ParseAndStoreResult(rx_buffer);
line_ready = 0;
if (result_count > 0) {
total_pages = result_count;
DisplayCurrentPage();
}
}
}
// 6. Temperatura
unsigned int32 temp_elapsed = system_ms - last_temp_check;
if (temp_elapsed > 1000) {
CheckTemperature();
last_temp_check = system_ms;
}
// 7. Avance no bloqueante de la cola de pruebas en ejecución
UpdateQueueExecution();
// 8. Autorango
if (auto_range && system_state == STATE_IDLE) {
AutoRange();
}
delay_ms(10); // Tick fijo del scheduler cooperativo, no una espera de operación
}
}