ok, let's stop
This commit is contained in:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -111,7 +111,8 @@
|
|||||||
"string.h": "c",
|
"string.h": "c",
|
||||||
"math.h": "c",
|
"math.h": "c",
|
||||||
"cctype": "c",
|
"cctype": "c",
|
||||||
"stdlib.h": "c"
|
"stdlib.h": "c",
|
||||||
|
"float.h": "c"
|
||||||
},
|
},
|
||||||
"cmake.sourceDirectory": "/home/mira/src/embedded/ch32v208_sens/lwip"
|
"cmake.sourceDirectory": "/home/mira/src/embedded/ch32v208_sens/lwip"
|
||||||
}
|
}
|
||||||
463
main.c
463
main.c
@@ -1,3 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* HP3478A internal USB-GPIB adapter & extension
|
||||||
|
*
|
||||||
|
* DESCRIPTION:
|
||||||
|
* This firmware acts as a bridge between USB-CDC and GPIB while also working
|
||||||
|
* in standalone mode inside HP3478A and MITM-ing GPIB and adding features.
|
||||||
|
*
|
||||||
|
* TODO:
|
||||||
|
* - Implement a VTable for features, to replace the massive switch
|
||||||
|
* to make consistnet entry and exits
|
||||||
|
* - Break main() up into app_process_usb() and app_process_gpib()?
|
||||||
|
* - SCPI-compliant command set for the passthrough mode
|
||||||
|
* - Data logging?
|
||||||
|
*/
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
@@ -35,6 +49,7 @@
|
|||||||
#define MENU_COMMIT_DELAY_MS 2400 // Time to hold before entering mode
|
#define MENU_COMMIT_DELAY_MS 2400 // Time to hold before entering mode
|
||||||
#define MENU_SUBLAYER_DELAY_MS 500 // Time to "hover" before dots start
|
#define MENU_SUBLAYER_DELAY_MS 500 // Time to "hover" before dots start
|
||||||
#define MENU_DEBOUNCE_MS 200 // Button press dead-time
|
#define MENU_DEBOUNCE_MS 200 // Button press dead-time
|
||||||
|
#define MENU_LOCKOUT_MS 1000 // How long to ignore SRQ for after exiting menu
|
||||||
|
|
||||||
// Polling
|
// Polling
|
||||||
#define POLL_INTERVAL_MS 100 // 10Hz polling when in Passthrough
|
#define POLL_INTERVAL_MS 100 // 10Hz polling when in Passthrough
|
||||||
@@ -51,13 +66,6 @@
|
|||||||
#define RTD_B -5.775e-7
|
#define RTD_B -5.775e-7
|
||||||
#define RTD_R0 1000.0
|
#define RTD_R0 1000.0
|
||||||
|
|
||||||
// Thermistor
|
|
||||||
// TODO: different ranges? 5k, 10k etc.?
|
|
||||||
// This is a 5k NTC! kinda useless here
|
|
||||||
#define THERM_A 0.001286
|
|
||||||
#define THERM_B 0.00023595
|
|
||||||
#define THERM_C 0.0000000941
|
|
||||||
|
|
||||||
// Thermocouple
|
// Thermocouple
|
||||||
#define CJC_FALLBACK_TEMP 22.0 // used if !app.env_sensor_present
|
#define CJC_FALLBACK_TEMP 22.0 // used if !app.env_sensor_present
|
||||||
// this is just cursed but.. yeah, the PCB is near a transformer
|
// this is just cursed but.. yeah, the PCB is near a transformer
|
||||||
@@ -181,6 +189,32 @@ typedef enum {
|
|||||||
MENU_MAX_ITEMS
|
MENU_MAX_ITEMS
|
||||||
} menu_item_t;
|
} menu_item_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
// Generic
|
||||||
|
NTC_10K_3950 = 0, // Generic china 3950
|
||||||
|
NTC_10K_3435, // alt 10k
|
||||||
|
NTC_50K_3950, // alt 50k
|
||||||
|
NTC_100K_3950, // alt 100k
|
||||||
|
|
||||||
|
// YSI 44000
|
||||||
|
NTC_YSI_2252, // YSI 44004 (Mix B)
|
||||||
|
NTC_YSI_3K, // YSI 44005 (Mix B)
|
||||||
|
NTC_YSI_5K, // YSI 44007 (Mix B)
|
||||||
|
NTC_YSI_10K_A, // YSI 44006 (Mix H)
|
||||||
|
NTC_YSI_10K_B, // YSI 44016 (Mix B)
|
||||||
|
NTC_YSI_30K, // YSI 44008 (Mix H)
|
||||||
|
NTC_YSI_100K, // YSI 44011 (Mix H)
|
||||||
|
NTC_YSI_1MEG, // YSI 44015 (Mix H)
|
||||||
|
|
||||||
|
NTC_MAX_ITEMS
|
||||||
|
} ntc_preset_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
double r0; // R @ 25C
|
||||||
|
double beta; // beta coefficient (typ. 3000-4500)
|
||||||
|
const char* name; // display name
|
||||||
|
} ntc_def_t;
|
||||||
|
|
||||||
// UI Strings
|
// UI Strings
|
||||||
static const char* MENU_NAMES[] = {"REL", "TEMP", "DBM", "CONT",
|
static const char* MENU_NAMES[] = {"REL", "TEMP", "DBM", "CONT",
|
||||||
"DIODE", "XOHM", "STATS", "EXIT"};
|
"DIODE", "XOHM", "STATS", "EXIT"};
|
||||||
@@ -189,11 +223,30 @@ static const char* SENSOR_NAMES[] = {"PT1000", "THERM", "TYPE K"};
|
|||||||
|
|
||||||
static const char* WIRE_NAMES[] = {"2-WIRE", "4-WIRE"};
|
static const char* WIRE_NAMES[] = {"2-WIRE", "4-WIRE"};
|
||||||
|
|
||||||
|
// some common NTC defs
|
||||||
|
static const ntc_def_t NTC_DEFS[] = {
|
||||||
|
// Generic/China
|
||||||
|
{10000.0, 3950.0, "10K 3950"}, // black bead)
|
||||||
|
{10000.0, 3435.0, "10K 3435"}, // euro?
|
||||||
|
{50000.0, 3950.0, "50K 3950"}, // generic
|
||||||
|
{100000.0, 3950.0, "100K 3950"}, // generic
|
||||||
|
// From YSI datasheet
|
||||||
|
{2252.0, 3891.0, "2.252K YSI"}, // 44004
|
||||||
|
{3000.0, 3891.0, "3K YSI"}, // 44005
|
||||||
|
{5000.0, 3891.0, "5K YSI"}, // 44007
|
||||||
|
{10000.0, 3574.0, "10K YSI A"}, // Mix H (YSI 10k)
|
||||||
|
{10000.0, 3891.0, "10K YSI B"}, // Mix B (matches 2.2K curve)
|
||||||
|
{30000.0, 3810.0, "30K YSI"}, // 44008
|
||||||
|
{100000.0, 3988.0, "100K YSI"}, // 44011
|
||||||
|
{1000000.0, 4582.0, "1MEG YSI"} // 44015
|
||||||
|
};
|
||||||
|
|
||||||
// Sub-menu states
|
// Sub-menu states
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SUBMENU_NONE = 0,
|
SUBMENU_NONE = 0,
|
||||||
SUBMENU_TEMP_SENS, // step 1: sensor Type
|
SUBMENU_TEMP_SENS, // step 1: sensor Type
|
||||||
SUBMENU_TEMP_WIRE // step 2: wire mode (skipped for Type K)
|
SUBMENU_TEMP_WIRE, // step 2a: wire mode (skipped for Type K)
|
||||||
|
SUBMENU_TEMP_NTC // step 2b: NTC Value (Thermistor only)
|
||||||
} submenu_state_t;
|
} submenu_state_t;
|
||||||
|
|
||||||
// Sensor Types
|
// Sensor Types
|
||||||
@@ -209,7 +262,6 @@ typedef enum {
|
|||||||
DIODE_STATE_OPEN = 0, // probes open
|
DIODE_STATE_OPEN = 0, // probes open
|
||||||
DIODE_STATE_CHECKING, // voltage @ diode range, checking stability
|
DIODE_STATE_CHECKING, // voltage @ diode range, checking stability
|
||||||
DIODE_STATE_SHORT, // voltage too low, silent
|
DIODE_STATE_SHORT, // voltage too low, silent
|
||||||
DIODE_STATE_PLAYING, // is a diode, chirping
|
|
||||||
DIODE_STATE_DONE // chirped, latched silent
|
DIODE_STATE_DONE // chirped, latched silent
|
||||||
} diode_state_t;
|
} diode_state_t;
|
||||||
|
|
||||||
@@ -271,7 +323,8 @@ typedef struct {
|
|||||||
uint8_t auto_read : 1;
|
uint8_t auto_read : 1;
|
||||||
uint8_t dmm_online : 1;
|
uint8_t dmm_online : 1;
|
||||||
uint8_t has_saved_state : 1;
|
uint8_t has_saved_state : 1;
|
||||||
uint8_t reserved : 2;
|
uint8_t beep_active : 1;
|
||||||
|
uint8_t reserved : 1;
|
||||||
|
|
||||||
// Addresses
|
// Addresses
|
||||||
uint8_t target_addr;
|
uint8_t target_addr;
|
||||||
@@ -283,6 +336,9 @@ typedef struct {
|
|||||||
uint32_t env_last_read;
|
uint32_t env_last_read;
|
||||||
uint32_t last_poll_time;
|
uint32_t last_poll_time;
|
||||||
uint32_t poll_interval;
|
uint32_t poll_interval;
|
||||||
|
uint32_t ignore_input_start_ts;
|
||||||
|
uint32_t beep_start_ts; // buzzer
|
||||||
|
uint32_t beep_duration;
|
||||||
|
|
||||||
// Logic
|
// Logic
|
||||||
work_mode_t current_mode;
|
work_mode_t current_mode;
|
||||||
@@ -291,6 +347,7 @@ typedef struct {
|
|||||||
// Config Selection
|
// Config Selection
|
||||||
temp_sensor_t temp_sensor;
|
temp_sensor_t temp_sensor;
|
||||||
wire_mode_t temp_wire_mode;
|
wire_mode_t temp_wire_mode;
|
||||||
|
ntc_preset_t temp_ntc_preset;
|
||||||
|
|
||||||
// Environmental Data
|
// Environmental Data
|
||||||
aht20_data current_env;
|
aht20_data current_env;
|
||||||
@@ -298,7 +355,7 @@ typedef struct {
|
|||||||
// DMM Restore
|
// DMM Restore
|
||||||
uint8_t saved_state_bytes[5];
|
uint8_t saved_state_bytes[5];
|
||||||
|
|
||||||
// Display shadow buffer
|
// Display shadow buffer (cache)
|
||||||
char last_disp_sent[HP_DISP_BUF_SIZE];
|
char last_disp_sent[HP_DISP_BUF_SIZE];
|
||||||
|
|
||||||
// Unionized Mode Data
|
// Unionized Mode Data
|
||||||
@@ -310,19 +367,35 @@ static app_state_t app = {.dmm_addr = DEFAULT_DMM_ADDR,
|
|||||||
.poll_interval = POLL_INTERVAL_MS,
|
.poll_interval = POLL_INTERVAL_MS,
|
||||||
.gpib_timeout_ms = DEFAULT_GPIB_TIMEOUT_MS};
|
.gpib_timeout_ms = DEFAULT_GPIB_TIMEOUT_MS};
|
||||||
|
|
||||||
// Buffers
|
typedef union {
|
||||||
// TODO: buffer usage is very inconsistent
|
// MODE A: Command Processing Context
|
||||||
// cmd and resp can probably be a union, and what do we even use tmp_buffer
|
// Used when we are inside process_command()
|
||||||
// for?..
|
struct {
|
||||||
static char cmd_buffer[128];
|
char line_buf[128]; // Replaces: scratch.cmd.line_buf
|
||||||
static char resp_buffer[256];
|
char fmt_buf[128]; // Replaces: scratch.cmd.fmt_buf (For "Stat:", "OK",
|
||||||
static char tmp_buffer[128];
|
// etc.)
|
||||||
// Max theoretic bytes for 12 visual chars is 24
|
} cmd;
|
||||||
static char disp_buffer[HP_DISP_BUF_SIZE];
|
|
||||||
// Display Command Buffer
|
// MODE B: General IO Context
|
||||||
// Size: "D3"(2) + Text(HP_DISP_BUF_SIZE) + "\n"(1) + Null(1)
|
// Used when reading from GPIB or calculating Features
|
||||||
// "D3" + "1.2....2." + "\n" + \0
|
struct {
|
||||||
static char disp_cmd_buffer[2 + HP_DISP_BUF_SIZE + 2];
|
// Replaces: scratch.io.raw_data
|
||||||
|
// Used for reading GPIB data ("+1.234E-3") before parsing
|
||||||
|
char raw_data[256];
|
||||||
|
} io;
|
||||||
|
|
||||||
|
// MODE C: Display Context
|
||||||
|
// Used when formatting text for the HP3478A
|
||||||
|
struct {
|
||||||
|
// Replaces: scratch.disp.full_cmd AND scratch.disp.full_cmd
|
||||||
|
// We will generate the final command directly here
|
||||||
|
char full_cmd[64];
|
||||||
|
} disp;
|
||||||
|
|
||||||
|
uint8_t raw[256];
|
||||||
|
} app_scratchpad_t;
|
||||||
|
|
||||||
|
static app_scratchpad_t scratch;
|
||||||
|
|
||||||
// USB Ring Buffer
|
// USB Ring Buffer
|
||||||
#define USB_RX_BUF_SIZE 512
|
#define USB_RX_BUF_SIZE 512
|
||||||
@@ -385,9 +458,9 @@ void double_to_str(char* buf, size_t buf_size, double val, int prec) {
|
|||||||
val = -val;
|
val = -val;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Multiplier (Integer Powers of 10)
|
// multiplier
|
||||||
if (prec < 0) prec = 0;
|
if (prec < 0) prec = 0;
|
||||||
if (prec > 9) prec = 9; // Limit precision to prevent overflow
|
if (prec > 9) prec = 9; // limit precision
|
||||||
|
|
||||||
unsigned long long multiplier = 1;
|
unsigned long long multiplier = 1;
|
||||||
for (int i = 0; i < prec; i++) multiplier *= 10;
|
for (int i = 0; i < prec; i++) multiplier *= 10;
|
||||||
@@ -395,7 +468,7 @@ void double_to_str(char* buf, size_t buf_size, double val, int prec) {
|
|||||||
// scale and round
|
// scale and round
|
||||||
val = (val * (double)multiplier) + 0.5;
|
val = (val * (double)multiplier) + 0.5;
|
||||||
|
|
||||||
// 5. split integer and fractional parts
|
// split integer and fractional parts
|
||||||
unsigned long long full_scaled = (unsigned long long)val;
|
unsigned long long full_scaled = (unsigned long long)val;
|
||||||
unsigned long int_part = (unsigned long)(full_scaled / multiplier);
|
unsigned long int_part = (unsigned long)(full_scaled / multiplier);
|
||||||
unsigned long long frac_part = full_scaled % multiplier;
|
unsigned long long frac_part = full_scaled % multiplier;
|
||||||
@@ -1271,7 +1344,6 @@ void TIM2_IRQHandler(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: maybne don't sleep inside it
|
|
||||||
void tone(unsigned int freq_hz, unsigned int duration_ms) {
|
void tone(unsigned int freq_hz, unsigned int duration_ms) {
|
||||||
if (freq_hz == 0) {
|
if (freq_hz == 0) {
|
||||||
Delay_Ms(duration_ms);
|
Delay_Ms(duration_ms);
|
||||||
@@ -1283,29 +1355,22 @@ void tone(unsigned int freq_hz, unsigned int duration_ms) {
|
|||||||
buzzer_set(0);
|
buzzer_set(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void play_startup_tune() {
|
void tone_nb(uint16_t freq, uint32_t duration_ms) {
|
||||||
|
buzzer_set(freq);
|
||||||
|
app.beep_start_ts = millis();
|
||||||
|
app.beep_duration = duration_ms;
|
||||||
|
app.beep_active = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// "Boot Up"
|
// "Boot Up"
|
||||||
|
void play_startup_tune() {
|
||||||
tone(1500, 100);
|
tone(1500, 100);
|
||||||
Delay_Ms(20);
|
Delay_Ms(25);
|
||||||
tone(2500, 100);
|
tone(2500, 100);
|
||||||
Delay_Ms(20);
|
Delay_Ms(25);
|
||||||
tone(4000, 100);
|
tone(4000, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
void play_connected_tune() {
|
|
||||||
// "Device Attached"
|
|
||||||
tone(3000, 100);
|
|
||||||
tone(4000, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
void play_disconnected_tune() {
|
|
||||||
// "Device Removed"
|
|
||||||
tone(4000, 100);
|
|
||||||
tone(3000, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
void beep(int ms) { tone(2500, ms); }
|
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
||||||
int HandleSetupCustom(struct _USBState* ctx, int setup_code) {
|
int HandleSetupCustom(struct _USBState* ctx, int setup_code) {
|
||||||
@@ -1437,12 +1502,7 @@ static void handle_usb_state(void) {
|
|||||||
if (app.usb_online != raw_status) {
|
if (app.usb_online != raw_status) {
|
||||||
app.usb_online = raw_status;
|
app.usb_online = raw_status;
|
||||||
|
|
||||||
if (app.usb_online) {
|
if (app.usb_online) usb_rx_tail = usb_rx_head = 0;
|
||||||
usb_rx_tail = usb_rx_head = 0;
|
|
||||||
play_connected_tune();
|
|
||||||
} else {
|
|
||||||
play_disconnected_tune();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1472,12 +1532,12 @@ void dmm_display(const char* text, const char* mode) {
|
|||||||
|
|
||||||
// update shadow buf
|
// update shadow buf
|
||||||
strncpy(app.last_disp_sent, text, HP_DISP_BUF_SIZE - 1);
|
strncpy(app.last_disp_sent, text, HP_DISP_BUF_SIZE - 1);
|
||||||
app.last_disp_sent[HP_DISP_BUF_SIZE - 1] = '\0';
|
|
||||||
// "D[23]" + Text + "\n"
|
// "D[23]" + Text + "\n"
|
||||||
snprintf(disp_cmd_buffer, sizeof(disp_cmd_buffer), "%s%s\n", mode,
|
snprintf(scratch.disp.full_cmd, sizeof(scratch.disp.full_cmd), "%s%s\n", mode,
|
||||||
app.last_disp_sent);
|
app.last_disp_sent);
|
||||||
|
|
||||||
// send it
|
// send it
|
||||||
gpib_send(app.dmm_addr, disp_cmd_buffer);
|
gpib_send(app.dmm_addr, scratch.disp.full_cmd);
|
||||||
}
|
}
|
||||||
static inline void dmm_display_normal(void) {
|
static inline void dmm_display_normal(void) {
|
||||||
gpib_send(app.dmm_addr, HP3478A_DISP_NORMAL);
|
gpib_send(app.dmm_addr, HP3478A_DISP_NORMAL);
|
||||||
@@ -1507,33 +1567,34 @@ void restore_dmm_state(void) {
|
|||||||
decode_dmm_state_bytes(app.saved_state_bytes, &state);
|
decode_dmm_state_bytes(app.saved_state_bytes, &state);
|
||||||
|
|
||||||
// "D1 Fx Rx Nx Zx" + "T1 M20"
|
// "D1 Fx Rx Nx Zx" + "T1 M20"
|
||||||
build_restoration_string(cmd_buffer, &state);
|
build_restoration_string(scratch.cmd.line_buf, &state);
|
||||||
strcat(cmd_buffer, HP3478A_TRIG_INTERNAL HP3478A_CMD_MASK_BTN_ONLY);
|
strcat(scratch.cmd.line_buf, HP3478A_TRIG_INTERNAL HP3478A_CMD_MASK_BTN_ONLY);
|
||||||
|
|
||||||
gpib_send(app.dmm_addr, cmd_buffer);
|
gpib_send(app.dmm_addr, scratch.cmd.line_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
void exit_to_passthrough(void) {
|
void exit_to_passthrough(void) {
|
||||||
buzzer_set(0); // mute
|
buzzer_set(0);
|
||||||
restore_dmm_state();
|
restore_dmm_state();
|
||||||
gpib_go_to_local(app.dmm_addr);
|
gpib_go_to_local(app.dmm_addr);
|
||||||
|
|
||||||
app.current_mode = MODE_PASSTHROUGH;
|
app.current_mode = MODE_PASSTHROUGH;
|
||||||
app.data.menu.layer = SUBMENU_NONE;
|
app.data.menu.layer = SUBMENU_NONE;
|
||||||
app.menu_pos = 0;
|
app.menu_pos = 0;
|
||||||
app.last_poll_time = millis();
|
app.beep_active = 0;
|
||||||
|
|
||||||
Delay_Ms(MENU_DEBOUNCE_MS);
|
uint32_t now = millis();
|
||||||
|
app.last_poll_time = now;
|
||||||
|
app.ignore_input_start_ts = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
void enter_feature_mode(menu_item_t item) {
|
void enter_feature_mode(menu_item_t item) {
|
||||||
// force display refresh
|
// force display refresh
|
||||||
app.last_disp_sent[0] = '\0';
|
app.last_disp_sent[0] = '\0';
|
||||||
// clean buffer for cmds
|
// clean buffer for cmds
|
||||||
cmd_buffer[0] = '\0';
|
scratch.cmd.line_buf[0] = '\0';
|
||||||
|
|
||||||
gpib_remote_enable(1); // assert REN
|
gpib_remote_enable(1); // make sure REN is asserted
|
||||||
Delay_Ms(20);
|
|
||||||
|
|
||||||
// we will hold the decoded state here for REL/STATS
|
// we will hold the decoded state here for REL/STATS
|
||||||
dmm_decoded_state_t saved_cfg;
|
dmm_decoded_state_t saved_cfg;
|
||||||
@@ -1542,8 +1603,9 @@ void enter_feature_mode(menu_item_t item) {
|
|||||||
case MENU_REL:
|
case MENU_REL:
|
||||||
if (app.has_saved_state) {
|
if (app.has_saved_state) {
|
||||||
decode_dmm_state_bytes(app.saved_state_bytes, &saved_cfg);
|
decode_dmm_state_bytes(app.saved_state_bytes, &saved_cfg);
|
||||||
build_restoration_string(cmd_buffer, &saved_cfg);
|
build_restoration_string(scratch.cmd.line_buf, &saved_cfg);
|
||||||
strcat(cmd_buffer, HP3478A_TRIG_INTERNAL HP3478A_CMD_MASK_BTN_DATA
|
strcat(scratch.cmd.line_buf,
|
||||||
|
HP3478A_TRIG_INTERNAL HP3478A_CMD_MASK_BTN_DATA
|
||||||
HP3478A_CMD_SRQ_CLEAR);
|
HP3478A_CMD_SRQ_CLEAR);
|
||||||
strcpy(app.data.rel.unit, saved_cfg.unit_str);
|
strcpy(app.data.rel.unit, saved_cfg.unit_str);
|
||||||
} else {
|
} else {
|
||||||
@@ -1551,7 +1613,7 @@ void enter_feature_mode(menu_item_t item) {
|
|||||||
dmm_display("ERR NO STATE", HP3478A_DISP_TEXT_FAST);
|
dmm_display("ERR NO STATE", HP3478A_DISP_TEXT_FAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
gpib_send(app.dmm_addr, cmd_buffer);
|
gpib_send(app.dmm_addr, scratch.cmd.line_buf);
|
||||||
|
|
||||||
app.current_mode = MODE_FEAT_REL;
|
app.current_mode = MODE_FEAT_REL;
|
||||||
app.data.rel.offset = 0.0;
|
app.data.rel.offset = 0.0;
|
||||||
@@ -1579,8 +1641,20 @@ void enter_feature_mode(menu_item_t item) {
|
|||||||
HP3478A_AUTOZERO_ON HP3478A_TRIG_INTERNAL
|
HP3478A_AUTOZERO_ON HP3478A_TRIG_INTERNAL
|
||||||
HP3478A_CMD_MASK_BTN_DATA HP3478A_CMD_SRQ_CLEAR);
|
HP3478A_CMD_MASK_BTN_DATA HP3478A_CMD_SRQ_CLEAR);
|
||||||
dmm_display("TEMP TYPE-K", HP3478A_DISP_TEXT_FAST);
|
dmm_display("TEMP TYPE-K", HP3478A_DISP_TEXT_FAST);
|
||||||
|
} else if (app.temp_sensor == SENS_THERMISTOR) {
|
||||||
|
// Thermistor: 2-Wire Ohms (Usually standard for NTC)
|
||||||
|
// Range Auto (NTC varies wildy)
|
||||||
|
gpib_send(app.dmm_addr,
|
||||||
|
HP3478A_FUNC_OHMS_2WIRE HP3478A_RANGE_AUTO HP3478A_DIGITS_5_5
|
||||||
|
HP3478A_AUTOZERO_ON HP3478A_TRIG_INTERNAL
|
||||||
|
HP3478A_CMD_MASK_BTN_DATA HP3478A_CMD_SRQ_CLEAR);
|
||||||
|
|
||||||
|
char buf[13];
|
||||||
|
snprintf(buf, sizeof(buf), "NTC %s",
|
||||||
|
NTC_DEFS[app.temp_ntc_preset].name);
|
||||||
|
dmm_display(buf, HP3478A_DISP_TEXT_FAST);
|
||||||
} else {
|
} else {
|
||||||
// Resistor Setup (PT1000/Therm):
|
// Resistor Setup (PT1000):
|
||||||
// F3=2W / F4=4W
|
// F3=2W / F4=4W
|
||||||
// R4=30kOhm Range
|
// R4=30kOhm Range
|
||||||
if (app.temp_wire_mode == WIRE_2W) {
|
if (app.temp_wire_mode == WIRE_2W) {
|
||||||
@@ -1597,8 +1671,8 @@ void enter_feature_mode(menu_item_t item) {
|
|||||||
|
|
||||||
if (app.temp_sensor == SENS_PT1000)
|
if (app.temp_sensor == SENS_PT1000)
|
||||||
dmm_display("TEMP PT1000", HP3478A_DISP_TEXT_FAST);
|
dmm_display("TEMP PT1000", HP3478A_DISP_TEXT_FAST);
|
||||||
else
|
// else
|
||||||
dmm_display("TEMP THERM", HP3478A_DISP_TEXT_FAST);
|
// dmm_display("TEMP THERM", HP3478A_DISP_TEXT_FAST);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -1645,20 +1719,21 @@ void enter_feature_mode(menu_item_t item) {
|
|||||||
case MENU_STATS:
|
case MENU_STATS:
|
||||||
if (app.has_saved_state) {
|
if (app.has_saved_state) {
|
||||||
decode_dmm_state_bytes(app.saved_state_bytes, &saved_cfg);
|
decode_dmm_state_bytes(app.saved_state_bytes, &saved_cfg);
|
||||||
build_restoration_string(cmd_buffer, &saved_cfg);
|
build_restoration_string(scratch.cmd.line_buf, &saved_cfg);
|
||||||
strcat(cmd_buffer, HP3478A_TRIG_INTERNAL HP3478A_CMD_MASK_BTN_DATA
|
strcat(scratch.cmd.line_buf,
|
||||||
|
HP3478A_TRIG_INTERNAL HP3478A_CMD_MASK_BTN_DATA
|
||||||
HP3478A_CMD_SRQ_CLEAR);
|
HP3478A_CMD_SRQ_CLEAR);
|
||||||
strcpy(app.data.stats.unit, saved_cfg.unit_str);
|
strcpy(app.data.stats.unit, saved_cfg.unit_str);
|
||||||
} else {
|
} else {
|
||||||
// fallback
|
// fallback
|
||||||
strcpy(cmd_buffer,
|
strcpy(scratch.cmd.line_buf,
|
||||||
HP3478A_FUNC_DC_VOLTS HP3478A_RANGE_AUTO HP3478A_DIGITS_5_5
|
HP3478A_FUNC_DC_VOLTS HP3478A_RANGE_AUTO HP3478A_DIGITS_5_5
|
||||||
HP3478A_AUTOZERO_ON HP3478A_TRIG_INTERNAL
|
HP3478A_AUTOZERO_ON HP3478A_TRIG_INTERNAL
|
||||||
HP3478A_CMD_MASK_BTN_DATA HP3478A_CMD_SRQ_CLEAR);
|
HP3478A_CMD_MASK_BTN_DATA HP3478A_CMD_SRQ_CLEAR);
|
||||||
strcpy(app.data.stats.unit, "VDC");
|
strcpy(app.data.stats.unit, "VDC");
|
||||||
}
|
}
|
||||||
|
|
||||||
gpib_send(app.dmm_addr, cmd_buffer);
|
gpib_send(app.dmm_addr, scratch.cmd.line_buf);
|
||||||
|
|
||||||
app.current_mode = MODE_FEAT_STATS;
|
app.current_mode = MODE_FEAT_STATS;
|
||||||
|
|
||||||
@@ -1692,7 +1767,6 @@ void enter_menu_mode(void) {
|
|||||||
|
|
||||||
dmm_display("M: REL", HP3478A_DISP_TEXT_FAST);
|
dmm_display("M: REL", HP3478A_DISP_TEXT_FAST);
|
||||||
gpib_send(app.dmm_addr, HP3478A_CMD_MASK_BTN_ONLY HP3478A_CMD_SRQ_CLEAR);
|
gpib_send(app.dmm_addr, HP3478A_CMD_MASK_BTN_ONLY HP3478A_CMD_SRQ_CLEAR);
|
||||||
Delay_Ms(200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_feature_logic(void) {
|
void handle_feature_logic(void) {
|
||||||
@@ -1709,7 +1783,8 @@ void handle_feature_logic(void) {
|
|||||||
if (!(stb & HP3478A_MASK_DATA_READY)) return;
|
if (!(stb & HP3478A_MASK_DATA_READY)) return;
|
||||||
|
|
||||||
// read measurement
|
// read measurement
|
||||||
int len = gpib_receive(app.dmm_addr, resp_buffer, sizeof(resp_buffer));
|
int len = gpib_receive(app.dmm_addr, scratch.io.raw_data,
|
||||||
|
sizeof(scratch.io.raw_data));
|
||||||
if (len < 0) {
|
if (len < 0) {
|
||||||
// timeout or error
|
// timeout or error
|
||||||
// printf("Read Timeout in Feature\n");
|
// printf("Read Timeout in Feature\n");
|
||||||
@@ -1719,7 +1794,7 @@ void handle_feature_logic(void) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
double val = parse_double(resp_buffer);
|
double val = parse_double(scratch.io.raw_data);
|
||||||
// overload (HP 3478A sends +9.99990E+9 for OL)
|
// overload (HP 3478A sends +9.99990E+9 for OL)
|
||||||
int is_overload = 0;
|
int is_overload = 0;
|
||||||
if (val < DMM_OL_NEG_THRESHOLD || val > DMM_OL_THRESHOLD) is_overload = 1;
|
if (val < DMM_OL_NEG_THRESHOLD || val > DMM_OL_THRESHOLD) is_overload = 1;
|
||||||
@@ -1738,7 +1813,7 @@ void handle_feature_logic(void) {
|
|||||||
if (app.data.rel.stable_count >= REL_STABLE_SAMPLES) {
|
if (app.data.rel.stable_count >= REL_STABLE_SAMPLES) {
|
||||||
app.data.rel.offset = val;
|
app.data.rel.offset = val;
|
||||||
dmm_display("NULL SET", HP3478A_DISP_TEXT_FAST);
|
dmm_display("NULL SET", HP3478A_DISP_TEXT_FAST);
|
||||||
tone(3000, 50);
|
tone_nb(3000, 50);
|
||||||
app.data.rel.stable_count = 0;
|
app.data.rel.stable_count = 0;
|
||||||
} else {
|
} else {
|
||||||
dmm_display("LOCKING...", HP3478A_DISP_TEXT_FAST);
|
dmm_display("LOCKING...", HP3478A_DISP_TEXT_FAST);
|
||||||
@@ -1754,9 +1829,9 @@ void handle_feature_logic(void) {
|
|||||||
// prepend 'D'
|
// prepend 'D'
|
||||||
snprintf(eff_unit, sizeof(eff_unit), "D%s", app.data.rel.unit);
|
snprintf(eff_unit, sizeof(eff_unit), "D%s", app.data.rel.unit);
|
||||||
|
|
||||||
format_metric_value(disp_buffer, sizeof(disp_buffer), diff, eff_unit,
|
format_metric_value(scratch.disp.full_cmd,
|
||||||
1);
|
sizeof(scratch.disp.full_cmd), diff, eff_unit, 1);
|
||||||
dmm_display(disp_buffer, HP3478A_DISP_TEXT);
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1766,20 +1841,21 @@ void handle_feature_logic(void) {
|
|||||||
dmm_display("O.VLD", HP3478A_DISP_TEXT_FAST);
|
dmm_display("O.VLD", HP3478A_DISP_TEXT_FAST);
|
||||||
} else {
|
} else {
|
||||||
// P(mW) = (V^2 / 50) * 1000 = V^2 * 20
|
// P(mW) = (V^2 / 50) * 1000 = V^2 * 20
|
||||||
double p_mw = (val * val * 20.0f);
|
double p_mw = (val * val * 20.0);
|
||||||
|
|
||||||
if (p_mw < 1e-9f) {
|
if (p_mw < 1e-9) {
|
||||||
// Align -INF to look consistent
|
// Align -INF to look consistent
|
||||||
// Display: "-INF DBM"
|
// Display: "-INF DBM"
|
||||||
memset(disp_buffer, ' ', HP_DISP_LEN);
|
memset(scratch.disp.full_cmd, ' ', HP_DISP_LEN);
|
||||||
disp_buffer[HP_DISP_LEN] = '\0';
|
scratch.disp.full_cmd[HP_DISP_LEN] = '\0';
|
||||||
memcpy(disp_buffer, "-INF", 4);
|
memcpy(scratch.disp.full_cmd, "-INF", 4);
|
||||||
memcpy(&disp_buffer[8], " DBM", 4);
|
memcpy(&scratch.disp.full_cmd[8], " DBM", 4);
|
||||||
dmm_display(disp_buffer, HP3478A_DISP_TEXT_FAST);
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT_FAST);
|
||||||
} else {
|
} else {
|
||||||
double dbm = 10.0 * log10(p_mw);
|
double dbm = 10.0 * log10(p_mw);
|
||||||
format_metric_value(disp_buffer, sizeof(disp_buffer), dbm, "DBM", 00);
|
format_metric_value(scratch.disp.full_cmd,
|
||||||
dmm_display(disp_buffer, HP3478A_DISP_TEXT);
|
sizeof(scratch.disp.full_cmd), dbm, "DBM", 00);
|
||||||
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1791,7 +1867,7 @@ void handle_feature_logic(void) {
|
|||||||
double temp_c = 0.0;
|
double temp_c = 0.0;
|
||||||
const char* unit_str = " C";
|
const char* unit_str = " C";
|
||||||
|
|
||||||
// 1. TYPE K THERMOCOUPLE
|
// TYPE K THERMOCOUPLE
|
||||||
if (app.temp_sensor == SENS_TYPE_K) {
|
if (app.temp_sensor == SENS_TYPE_K) {
|
||||||
// 30mV range safety check (Floating input > 50mV)
|
// 30mV range safety check (Floating input > 50mV)
|
||||||
if (fabs(val) > 0.050) {
|
if (fabs(val) > 0.050) {
|
||||||
@@ -1817,9 +1893,9 @@ void handle_feature_logic(void) {
|
|||||||
temp_c = t_amb + (val * TYPE_K_SCALE);
|
temp_c = t_amb + (val * TYPE_K_SCALE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 2. PT1000 RTD (Callendar-Van Dusen)
|
// PT1000 RTD (Callendar-Van Dusen)
|
||||||
else if (app.temp_sensor == SENS_PT1000) {
|
else if (app.temp_sensor == SENS_PT1000) {
|
||||||
if (val < 10.0f) {
|
if (val < 10.0) {
|
||||||
dmm_display("SHORT", HP3478A_DISP_TEXT_FAST);
|
dmm_display("SHORT", HP3478A_DISP_TEXT_FAST);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -1829,32 +1905,44 @@ void handle_feature_logic(void) {
|
|||||||
double disc = (b * b) - (4 * a * c);
|
double disc = (b * b) - (4 * a * c);
|
||||||
|
|
||||||
if (disc >= 0)
|
if (disc >= 0)
|
||||||
temp_c = (-b + sqrtf(disc)) / (2 * a);
|
temp_c = (-b + sqrt(disc)) / (2 * a);
|
||||||
else {
|
else {
|
||||||
dmm_display("RANGE ERR", HP3478A_DISP_TEXT_FAST);
|
dmm_display("RANGE ERR", HP3478A_DISP_TEXT_FAST);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 3. THERMISTOR (Steinhart-Hart)
|
// THERMISTOR (simplified Steinhart-Hart)
|
||||||
else {
|
else {
|
||||||
if (val < 10.0f) {
|
if (val < 10.0) {
|
||||||
dmm_display("SHORT", HP3478A_DISP_TEXT_FAST);
|
dmm_display("SHORT", HP3478A_DISP_TEXT_FAST);
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
// log(0) ? :)
|
if (val > 4000000.0) {
|
||||||
double r = (val < 1.0f) ? 1.0f : val;
|
dmm_display("OPEN", HP3478A_DISP_TEXT_FAST);
|
||||||
double lr = log(r);
|
return;
|
||||||
double lr3 = lr * lr * lr;
|
}
|
||||||
double inv_t = THERM_A + (THERM_B * lr) + (THERM_C * lr3);
|
|
||||||
|
|
||||||
temp_c = (1.0f / inv_t) - 273.15f;
|
double r_meas = val;
|
||||||
}
|
// constants from our preset
|
||||||
|
double r0 = NTC_DEFS[app.temp_ntc_preset].r0;
|
||||||
|
double beta = NTC_DEFS[app.temp_ntc_preset].beta;
|
||||||
|
const double t0_k = 298.15; // 25.0C in Kelvin
|
||||||
|
|
||||||
|
// 1/T = 1/T0 + (1/B * ln(R/R0))
|
||||||
|
double ln_ratio = log(r_meas / r0);
|
||||||
|
double inv_t = (1.0 / t0_k) + ((1.0 / beta) * ln_ratio);
|
||||||
|
|
||||||
|
// convert Kelvin to Celsius
|
||||||
|
temp_c = (1.0 / inv_t) - 273.15;
|
||||||
|
|
||||||
|
unit_str = "C NTC";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display: "24.5 C" (or "24.5 C (K)")
|
// Display: "24.5 C" (or "24.5 C (K)")
|
||||||
format_metric_value(disp_buffer, sizeof(disp_buffer), temp_c, unit_str,
|
format_metric_value(scratch.disp.full_cmd, sizeof(scratch.disp.full_cmd),
|
||||||
0);
|
temp_c, unit_str, 0);
|
||||||
dmm_display(disp_buffer, HP3478A_DISP_TEXT);
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// CONT MODE
|
// CONT MODE
|
||||||
@@ -1871,27 +1959,28 @@ void handle_feature_logic(void) {
|
|||||||
if (is_overload) {
|
if (is_overload) {
|
||||||
dmm_display("OPEN", HP3478A_DISP_TEXT_FAST);
|
dmm_display("OPEN", HP3478A_DISP_TEXT_FAST);
|
||||||
} else {
|
} else {
|
||||||
format_metric_value(disp_buffer, sizeof(disp_buffer), val, "OHM", 1);
|
format_metric_value(scratch.disp.full_cmd,
|
||||||
dmm_display(disp_buffer, HP3478A_DISP_TEXT);
|
sizeof(scratch.disp.full_cmd), val, "OHM", 1);
|
||||||
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (app.current_mode == MODE_FEAT_DIODE) {
|
else if (app.current_mode == MODE_FEAT_DIODE) {
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
float voltage = is_overload ? 9.9f : (val / 1000.0f);
|
double voltage = is_overload ? 9.9 : (val / 1000.0);
|
||||||
uint8_t is_valid_signal =
|
uint8_t is_valid_signal =
|
||||||
(voltage > DIODE_TH_SHORT && voltage < DIODE_TH_OPEN);
|
(voltage > DIODE_TH_SHORT && voltage < DIODE_TH_OPEN);
|
||||||
|
|
||||||
if (voltage < DIODE_TH_OPEN) {
|
if (voltage < DIODE_TH_OPEN) {
|
||||||
format_metric_value(disp_buffer, sizeof(disp_buffer), voltage, "VDC", 1);
|
format_metric_value(scratch.disp.full_cmd, sizeof(scratch.disp.full_cmd),
|
||||||
dmm_display(disp_buffer, HP3478A_DISP_TEXT);
|
voltage, "VDC", 1);
|
||||||
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT);
|
||||||
} else {
|
} else {
|
||||||
dmm_display("OPEN", HP3478A_DISP_TEXT);
|
dmm_display("OPEN", HP3478A_DISP_TEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
diode_state_t next_state = app.data.diode.connected;
|
diode_state_t next_state = app.data.diode.connected;
|
||||||
uint8_t request_buzzer = 0;
|
|
||||||
|
|
||||||
switch (app.data.diode.connected) {
|
switch (app.data.diode.connected) {
|
||||||
case DIODE_STATE_OPEN:
|
case DIODE_STATE_OPEN:
|
||||||
@@ -1910,15 +1999,8 @@ void handle_feature_logic(void) {
|
|||||||
if (!is_valid_signal) {
|
if (!is_valid_signal) {
|
||||||
next_state = DIODE_STATE_SHORT; // signal lost/glitch
|
next_state = DIODE_STATE_SHORT; // signal lost/glitch
|
||||||
} else if ((now - app.data.diode.chirp_start) >= DIODE_STABLE_MS) {
|
} else if ((now - app.data.diode.chirp_start) >= DIODE_STABLE_MS) {
|
||||||
next_state = DIODE_STATE_PLAYING;
|
// has been stable long enough
|
||||||
app.data.diode.chirp_start = now;
|
tone_nb(2500, DIODE_CHIRP_MS);
|
||||||
request_buzzer = 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DIODE_STATE_PLAYING:
|
|
||||||
request_buzzer = 1;
|
|
||||||
if ((now - app.data.diode.chirp_start) >= DIODE_CHIRP_MS) {
|
|
||||||
next_state = DIODE_STATE_DONE;
|
next_state = DIODE_STATE_DONE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1936,7 +2018,6 @@ void handle_feature_logic(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.data.diode.connected = next_state;
|
app.data.diode.connected = next_state;
|
||||||
buzzer_set(request_buzzer ? 2500 : 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// XOHM MODE
|
// XOHM MODE
|
||||||
@@ -1947,9 +2028,7 @@ void handle_feature_logic(void) {
|
|||||||
if (val > 8.0e6 && val < 12.0e6) {
|
if (val > 8.0e6 && val < 12.0e6) {
|
||||||
app.data.xohm.r1 = val; // Store R1
|
app.data.xohm.r1 = val; // Store R1
|
||||||
app.data.xohm.calibrated = 1;
|
app.data.xohm.calibrated = 1;
|
||||||
tone(3000, 100);
|
tone_nb(3000, 100);
|
||||||
dmm_display("READY", HP3478A_DISP_TEXT_FAST);
|
|
||||||
Delay_Ms(500);
|
|
||||||
} else {
|
} else {
|
||||||
dmm_display("OPEN PROBES", HP3478A_DISP_TEXT_FAST);
|
dmm_display("OPEN PROBES", HP3478A_DISP_TEXT_FAST);
|
||||||
}
|
}
|
||||||
@@ -1958,15 +2037,16 @@ void handle_feature_logic(void) {
|
|||||||
// R1 = xohm_ref (Internal)
|
// R1 = xohm_ref (Internal)
|
||||||
// R2 = val (Measured Parallel)
|
// R2 = val (Measured Parallel)
|
||||||
else {
|
else {
|
||||||
if (is_overload || val >= (app.data.xohm.r1 - 1000.0f)) {
|
if (is_overload || val >= (app.data.xohm.r1 - 1000.0)) {
|
||||||
dmm_display("OPEN", HP3478A_DISP_TEXT_FAST);
|
dmm_display("OPEN", HP3478A_DISP_TEXT_FAST);
|
||||||
} else {
|
} else {
|
||||||
double r1 = app.data.xohm.r1;
|
double r1 = app.data.xohm.r1;
|
||||||
double r2 = val;
|
double r2 = val;
|
||||||
double rx = (r1 * r2) / (r1 - r2);
|
double rx = (r1 * r2) / (r1 - r2);
|
||||||
|
|
||||||
format_metric_value(disp_buffer, sizeof(disp_buffer), rx, "OHM", 1);
|
format_metric_value(scratch.disp.full_cmd,
|
||||||
dmm_display(disp_buffer, HP3478A_DISP_TEXT);
|
sizeof(scratch.disp.full_cmd), rx, "OHM", 1);
|
||||||
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (app.current_mode == MODE_FEAT_STATS) {
|
} else if (app.current_mode == MODE_FEAT_STATS) {
|
||||||
@@ -2015,7 +2095,8 @@ void handle_feature_logic(void) {
|
|||||||
// render
|
// render
|
||||||
if (app.data.stats.view_mode == 0) {
|
if (app.data.stats.view_mode == 0) {
|
||||||
// live mode
|
// live mode
|
||||||
format_metric_value(disp_buffer, sizeof(disp_buffer), val_to_show,
|
format_metric_value(scratch.disp.full_cmd,
|
||||||
|
sizeof(scratch.disp.full_cmd), val_to_show,
|
||||||
app.data.stats.unit, 1);
|
app.data.stats.unit, 1);
|
||||||
} else {
|
} else {
|
||||||
// stats mode: prefix (4) + number (8)
|
// stats mode: prefix (4) + number (8)
|
||||||
@@ -2038,17 +2119,18 @@ void handle_feature_logic(void) {
|
|||||||
double_to_str(num_buf, sizeof(num_buf), scaled, 4);
|
double_to_str(num_buf, sizeof(num_buf), scaled, 4);
|
||||||
|
|
||||||
// combine: prefix + number + suffix
|
// combine: prefix + number + suffix
|
||||||
snprintf(disp_buffer, sizeof(disp_buffer), "%s%s%c ", prefix_str,
|
snprintf(scratch.disp.full_cmd, sizeof(scratch.disp.full_cmd),
|
||||||
num_buf, suffix ? suffix : ' ');
|
"%s%s%c ", prefix_str, num_buf, suffix ? suffix : ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
// send it
|
// send it
|
||||||
dmm_display(disp_buffer, HP3478A_DISP_TEXT);
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// gens the base string (e.g., "M: TEMP", "S: TYPE K") into disp_buffer
|
// gens the base string (e.g., "M: TEMP", "S: TYPE K") into
|
||||||
|
// scratch.disp.full_cmd
|
||||||
void prepare_menu_base_string(void) {
|
void prepare_menu_base_string(void) {
|
||||||
const char* prefix = "";
|
const char* prefix = "";
|
||||||
const char* name = "???";
|
const char* name = "???";
|
||||||
@@ -2062,28 +2144,29 @@ void prepare_menu_base_string(void) {
|
|||||||
} else if (app.data.menu.layer == SUBMENU_TEMP_WIRE) {
|
} else if (app.data.menu.layer == SUBMENU_TEMP_WIRE) {
|
||||||
prefix = "T: ";
|
prefix = "T: ";
|
||||||
if (app.menu_pos < WIRE_MAX_ITEMS) name = WIRE_NAMES[app.menu_pos];
|
if (app.menu_pos < WIRE_MAX_ITEMS) name = WIRE_NAMES[app.menu_pos];
|
||||||
|
} else if (app.data.menu.layer == SUBMENU_TEMP_NTC) {
|
||||||
|
prefix = "N: ";
|
||||||
|
if (app.menu_pos < NTC_MAX_ITEMS) name = NTC_DEFS[app.menu_pos].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(disp_buffer, sizeof(disp_buffer), "%s%s", prefix, name);
|
snprintf(scratch.disp.full_cmd, sizeof(scratch.disp.full_cmd), "%s%s", prefix,
|
||||||
|
name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_menu_navigation(void) {
|
void handle_menu_navigation(void) {
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
uint32_t elapsed = now - app.data.menu.timer;
|
uint32_t elapsed = now - app.data.menu.timer;
|
||||||
|
|
||||||
// nav: check SRQ (next item)
|
if (elapsed > MENU_DEBOUNCE_MS) {
|
||||||
|
// only poll GPIB if physical line is asserted
|
||||||
if (gpib_check_srq()) {
|
if (gpib_check_srq()) {
|
||||||
uint8_t stb = 0;
|
uint8_t stb = 0;
|
||||||
gpib_serial_poll(app.dmm_addr, &stb);
|
gpib_serial_poll(app.dmm_addr, &stb);
|
||||||
|
|
||||||
// only 4b (front panel button SRQ)
|
// check if it was the front panel btn
|
||||||
if (stb & HP3478A_MASK_KEYBOARD_SRQ) {
|
if (stb & HP3478A_MASK_KEYBOARD_SRQ) {
|
||||||
if (elapsed < MENU_DEBOUNCE_MS) {
|
// reset timer
|
||||||
// we polled, so STB is clear on DMM. Just return.
|
app.data.menu.timer = now;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
app.data.menu.timer = now; // reset the "hover" timer
|
|
||||||
app.menu_pos++;
|
app.menu_pos++;
|
||||||
|
|
||||||
int max_items = 0;
|
int max_items = 0;
|
||||||
@@ -2093,19 +2176,23 @@ void handle_menu_navigation(void) {
|
|||||||
max_items = SENS_MAX_ITEMS;
|
max_items = SENS_MAX_ITEMS;
|
||||||
else if (app.data.menu.layer == SUBMENU_TEMP_WIRE)
|
else if (app.data.menu.layer == SUBMENU_TEMP_WIRE)
|
||||||
max_items = WIRE_MAX_ITEMS;
|
max_items = WIRE_MAX_ITEMS;
|
||||||
|
else if (app.data.menu.layer == SUBMENU_TEMP_NTC)
|
||||||
|
max_items = NTC_MAX_ITEMS;
|
||||||
|
|
||||||
if (app.menu_pos >= max_items) app.menu_pos = 0;
|
if (app.menu_pos >= max_items) app.menu_pos = 0;
|
||||||
|
|
||||||
// update display immediately
|
// update display
|
||||||
prepare_menu_base_string();
|
prepare_menu_base_string();
|
||||||
dmm_display(disp_buffer, HP3478A_DISP_TEXT_FAST);
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT_FAST);
|
||||||
|
|
||||||
// re-arm SRQ
|
// re-arm SRQ
|
||||||
gpib_send(app.dmm_addr, HP3478A_CMD_MASK_BTN_ONLY HP3478A_CMD_SRQ_CLEAR);
|
gpib_send(app.dmm_addr,
|
||||||
Delay_Ms(MENU_DEBOUNCE_MS);
|
HP3478A_CMD_MASK_BTN_ONLY HP3478A_CMD_SRQ_CLEAR);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prepare_menu_base_string();
|
prepare_menu_base_string();
|
||||||
|
|
||||||
@@ -2115,9 +2202,9 @@ void handle_menu_navigation(void) {
|
|||||||
int dots = dot_time / MENU_DOT_INTERVAL_MS;
|
int dots = dot_time / MENU_DOT_INTERVAL_MS;
|
||||||
if (dots > 3) dots = 3;
|
if (dots > 3) dots = 3;
|
||||||
|
|
||||||
for (int i = 0; i < dots; i++) strcat(disp_buffer, ".");
|
for (int i = 0; i < dots; i++) strcat(scratch.disp.full_cmd, ".");
|
||||||
}
|
}
|
||||||
dmm_display(disp_buffer, HP3478A_DISP_TEXT_FAST);
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT_FAST);
|
||||||
|
|
||||||
if (elapsed > MENU_COMMIT_DELAY_MS) {
|
if (elapsed > MENU_COMMIT_DELAY_MS) {
|
||||||
// L0: main menu
|
// L0: main menu
|
||||||
@@ -2129,7 +2216,7 @@ void handle_menu_navigation(void) {
|
|||||||
app.data.menu.timer = now;
|
app.data.menu.timer = now;
|
||||||
|
|
||||||
prepare_menu_base_string();
|
prepare_menu_base_string();
|
||||||
dmm_display(disp_buffer, HP3478A_DISP_TEXT_FAST);
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT_FAST);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// enter standard modes
|
// enter standard modes
|
||||||
@@ -2144,6 +2231,12 @@ void handle_menu_navigation(void) {
|
|||||||
if (app.temp_sensor == SENS_TYPE_K) {
|
if (app.temp_sensor == SENS_TYPE_K) {
|
||||||
// Type K is voltage based so skip wire select
|
// Type K is voltage based so skip wire select
|
||||||
enter_feature_mode(MENU_TEMP);
|
enter_feature_mode(MENU_TEMP);
|
||||||
|
} else if (app.temp_sensor == SENS_THERMISTOR) {
|
||||||
|
app.data.menu.layer = SUBMENU_TEMP_NTC;
|
||||||
|
app.menu_pos = 0; // default to 10K
|
||||||
|
app.data.menu.timer = now;
|
||||||
|
prepare_menu_base_string();
|
||||||
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT_FAST);
|
||||||
} else {
|
} else {
|
||||||
// wire select (for resistive)
|
// wire select (for resistive)
|
||||||
app.data.menu.layer = SUBMENU_TEMP_WIRE;
|
app.data.menu.layer = SUBMENU_TEMP_WIRE;
|
||||||
@@ -2151,16 +2244,20 @@ void handle_menu_navigation(void) {
|
|||||||
app.data.menu.timer = now;
|
app.data.menu.timer = now;
|
||||||
|
|
||||||
prepare_menu_base_string();
|
prepare_menu_base_string();
|
||||||
dmm_display(disp_buffer, HP3478A_DISP_TEXT_FAST);
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT_FAST);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// L2a: wire select
|
||||||
// L2: wire select
|
|
||||||
else if (app.data.menu.layer == SUBMENU_TEMP_WIRE) {
|
else if (app.data.menu.layer == SUBMENU_TEMP_WIRE) {
|
||||||
app.temp_wire_mode = (wire_mode_t)app.menu_pos;
|
app.temp_wire_mode = (wire_mode_t)app.menu_pos;
|
||||||
enter_feature_mode(MENU_TEMP);
|
enter_feature_mode(MENU_TEMP);
|
||||||
return;
|
return;
|
||||||
|
} // L2b: NTC select
|
||||||
|
else if (app.data.menu.layer == SUBMENU_TEMP_NTC) {
|
||||||
|
app.temp_ntc_preset = (ntc_preset_t)app.menu_pos;
|
||||||
|
enter_feature_mode(MENU_TEMP);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2168,6 +2265,13 @@ void handle_menu_navigation(void) {
|
|||||||
void app_loop(void) {
|
void app_loop(void) {
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
if (app.beep_active) {
|
||||||
|
if ((now - app.beep_start_ts) >= app.beep_duration) {
|
||||||
|
buzzer_set(0);
|
||||||
|
app.beep_active = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Passthrough
|
// Passthrough
|
||||||
if (app.current_mode == MODE_PASSTHROUGH) {
|
if (app.current_mode == MODE_PASSTHROUGH) {
|
||||||
int srq_asserted = gpib_check_srq();
|
int srq_asserted = gpib_check_srq();
|
||||||
@@ -2202,13 +2306,14 @@ void app_loop(void) {
|
|||||||
app.dmm_online = 1;
|
app.dmm_online = 1;
|
||||||
gpib_send(app.dmm_addr, HP3478A_CMD_MASK_BTN_ONLY HP3478A_CMD_SRQ_CLEAR);
|
gpib_send(app.dmm_addr, HP3478A_CMD_MASK_BTN_ONLY HP3478A_CMD_SRQ_CLEAR);
|
||||||
gpib_go_to_local(app.dmm_addr);
|
gpib_go_to_local(app.dmm_addr);
|
||||||
tone(4000, 50);
|
tone_nb(4000, 50);
|
||||||
app.poll_interval = POLL_INTERVAL_MS; // restore fast polling
|
app.poll_interval = POLL_INTERVAL_MS; // restore fast polling
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// valid and online, check buttons
|
// valid and online, check buttons
|
||||||
if (stb & HP3478A_MASK_KEYBOARD_SRQ) {
|
if (stb & HP3478A_MASK_KEYBOARD_SRQ &&
|
||||||
|
(now - app.ignore_input_start_ts) > MENU_LOCKOUT_MS) {
|
||||||
enter_menu_mode();
|
enter_menu_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2288,7 +2393,7 @@ static void cmd_help(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void cmd_status(void) {
|
static void cmd_status(void) {
|
||||||
snprintf(tmp_buffer, sizeof(tmp_buffer),
|
snprintf(scratch.cmd.fmt_buf, sizeof(scratch.cmd.fmt_buf),
|
||||||
"Stat:\r\n"
|
"Stat:\r\n"
|
||||||
" Target Addr: %d\r\n"
|
" Target Addr: %d\r\n"
|
||||||
" Internal DMM: %d\r\n"
|
" Internal DMM: %d\r\n"
|
||||||
@@ -2298,15 +2403,15 @@ static void cmd_status(void) {
|
|||||||
" FW: " FW_VERSION "\r\n",
|
" FW: " FW_VERSION "\r\n",
|
||||||
app.target_addr, app.dmm_addr, app.gpib_timeout_ms,
|
app.target_addr, app.dmm_addr, app.gpib_timeout_ms,
|
||||||
app.auto_read ? "ON" : "OFF", app.current_mode);
|
app.auto_read ? "ON" : "OFF", app.current_mode);
|
||||||
usb_send_text(tmp_buffer);
|
usb_send_text(scratch.cmd.fmt_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void process_command(void) {
|
static void process_command(void) {
|
||||||
if (!get_start_command(cmd_buffer, sizeof(cmd_buffer))) {
|
if (!get_start_command(scratch.cmd.line_buf, sizeof(scratch.cmd.line_buf))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* p_cmd = skip_spaces(cmd_buffer);
|
char* p_cmd = skip_spaces(scratch.cmd.line_buf);
|
||||||
int is_cpp_cmd = (strncmp(p_cmd, "++", 2) == 0);
|
int is_cpp_cmd = (strncmp(p_cmd, "++", 2) == 0);
|
||||||
int is_query = (strchr(p_cmd, '?') != NULL);
|
int is_query = (strchr(p_cmd, '?') != NULL);
|
||||||
|
|
||||||
@@ -2333,8 +2438,9 @@ static void process_command(void) {
|
|||||||
usb_send_text("ERR: Invalid Addr\r\n");
|
usb_send_text("ERR: Invalid Addr\r\n");
|
||||||
} else {
|
} else {
|
||||||
// if no arg provided, show current
|
// if no arg provided, show current
|
||||||
snprintf(tmp_buffer, sizeof(tmp_buffer), "%d\r\n", app.target_addr);
|
snprintf(scratch.cmd.fmt_buf, sizeof(scratch.cmd.fmt_buf), "%d\r\n",
|
||||||
usb_send_text(tmp_buffer);
|
app.target_addr);
|
||||||
|
usb_send_text(scratch.cmd.fmt_buf);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -2381,9 +2487,9 @@ static void process_command(void) {
|
|||||||
usb_send_text("ERR: Range 1-60000\r\n");
|
usb_send_text("ERR: Range 1-60000\r\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
snprintf(tmp_buffer, sizeof(tmp_buffer), "%lu\r\n",
|
snprintf(scratch.cmd.fmt_buf, sizeof(scratch.cmd.fmt_buf), "%lu\r\n",
|
||||||
app.gpib_timeout_ms);
|
app.gpib_timeout_ms);
|
||||||
usb_send_text(tmp_buffer);
|
usb_send_text(scratch.cmd.fmt_buf);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CMD_TRG:
|
case CMD_TRG:
|
||||||
@@ -2427,8 +2533,9 @@ static void process_command(void) {
|
|||||||
uint8_t poll_addr = (*p_args) ? atoi(p_args) : app.target_addr;
|
uint8_t poll_addr = (*p_args) ? atoi(p_args) : app.target_addr;
|
||||||
uint8_t stb;
|
uint8_t stb;
|
||||||
if (gpib_serial_poll(poll_addr, &stb) == 0) {
|
if (gpib_serial_poll(poll_addr, &stb) == 0) {
|
||||||
snprintf(tmp_buffer, sizeof(tmp_buffer), "%d\r\n", stb);
|
snprintf(scratch.cmd.fmt_buf, sizeof(scratch.cmd.fmt_buf), "%d\r\n",
|
||||||
usb_send_text(tmp_buffer);
|
stb);
|
||||||
|
usb_send_text(scratch.cmd.fmt_buf);
|
||||||
} else
|
} else
|
||||||
usb_send_text("ERR: Bus\r\n");
|
usb_send_text("ERR: Bus\r\n");
|
||||||
break;
|
break;
|
||||||
@@ -2447,10 +2554,10 @@ static void process_command(void) {
|
|||||||
while (p_args[i] != 0 && i < HP_DISP_LEN) {
|
while (p_args[i] != 0 && i < HP_DISP_LEN) {
|
||||||
char c = p_args[i];
|
char c = p_args[i];
|
||||||
if (c >= 'a' && c <= 'z') c -= 32;
|
if (c >= 'a' && c <= 'z') c -= 32;
|
||||||
disp_buffer[i++] = c;
|
scratch.disp.full_cmd[i++] = c;
|
||||||
}
|
}
|
||||||
disp_buffer[i] = 0;
|
scratch.disp.full_cmd[i] = 0;
|
||||||
dmm_display(disp_buffer, HP3478A_DISP_TEXT_FAST);
|
dmm_display(scratch.disp.full_cmd, HP3478A_DISP_TEXT_FAST);
|
||||||
usb_send_text("OK\r\n");
|
usb_send_text("OK\r\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2538,9 +2645,10 @@ static void process_command(void) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
do_read_operation: {
|
do_read_operation: {
|
||||||
int len = gpib_receive(app.target_addr, resp_buffer, sizeof(resp_buffer));
|
int len = gpib_receive(app.target_addr, scratch.io.raw_data,
|
||||||
|
sizeof(scratch.io.raw_data));
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
usb_send_text(resp_buffer);
|
usb_send_text(scratch.io.raw_data);
|
||||||
} else if (is_cpp_cmd || is_query) {
|
} else if (is_cpp_cmd || is_query) {
|
||||||
usb_send_text("ERR: Read Timeout\r\n");
|
usb_send_text("ERR: Read Timeout\r\n");
|
||||||
}
|
}
|
||||||
@@ -2574,20 +2682,9 @@ int main() {
|
|||||||
app.usb_online = 0;
|
app.usb_online = 0;
|
||||||
app.usb_raw_prev = USB_HW_IS_ACTIVE();
|
app.usb_raw_prev = USB_HW_IS_ACTIVE();
|
||||||
app.usb_ts = millis();
|
app.usb_ts = millis();
|
||||||
app.last_poll_time = millis();
|
app.last_poll_time = 0;
|
||||||
|
app.ignore_input_start_ts = millis() - 2000;
|
||||||
while (1) {
|
app.dmm_online = 0;
|
||||||
uint8_t status_byte;
|
|
||||||
if (gpib_serial_poll(app.dmm_addr, &status_byte) == 0) {
|
|
||||||
// printf("Device Found (Stb: 0x%02X)\n", status_byte);
|
|
||||||
gpib_send(app.dmm_addr, HP3478A_CMD_MASK_BTN_ONLY HP3478A_CMD_SRQ_CLEAR);
|
|
||||||
gpib_go_to_local(app.dmm_addr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Delay_Ms(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.dmm_online = 1;
|
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
handle_usb_state();
|
handle_usb_state();
|
||||||
|
|||||||
Reference in New Issue
Block a user