Files
hp3478a_ext/main.c
2025-11-30 04:47:14 +06:00

1702 lines
42 KiB
C

#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "aht20.h"
#include "ch32fun.h"
#include "fsusb.h"
#include "gpib_defs.h"
#include "i2c_bitbang.h"
#include "systick.h"
#define FW_VERSION "1.0.0"
#define MY_ADDR 0
#define DEFAULT_DMM_ADDR 18 // the HP3478A addr
#define PIN_VBUS PB10
#define PIN_BUZZ PC13
#define USB_HW_IS_ACTIVE() (!((USBFSCTX.USBFS_DevSleepStatus) & 0x02))
// Timing Config
#define USB_DEBOUNCE_CONNECT_MS 50
#define USB_DEBOUNCE_DISCONNECT_MS 200
#define ENV_SENSOR_READ_INTERVAL_MS 1000
#define GPIB_TIMEOUT_MS 100
#define MENU_HOVER_COMMIT_MS 2400
// time between Serial Polls in Passthrough mode
#define POLL_INTERVAL_MS 100
// dead time after SRQ mask to the DMM
// polling it too soon after recovery causes a timeout
#define DMM_RECOVERY_DELAY_MS 1000
// PT1000 Constants
#define RTD_A 3.9083e-3
#define RTD_B -5.775e-7
#define RTD_R0 1000.0
// HP3478A Specific Commands
#define HP_CMD_RESET_DISPLAY "D1"
#define HP_CMD_TEXT_DISPLAY "D3"
#define HP_CMD_MASK_BTN_ONLY "M20" // SRQ on Front Panel Button
#define HP_CMD_MASK_BTN_DATA "M21" // SRQ on Data Ready + Button
#define CONT_THRESHOLD_OHMS 10.0f
#define HP_OVERLOAD_VAL 9.0e9f // Threshold for +9.99990E+9
#define REL_STABLE_SAMPLES 3 // samples to wait before locking NULL
typedef enum {
MODE_PASSTHROUGH = 0, // Standard USB-GPIB bridge
MODE_MENU, // User is cycling options on DMM display
MODE_FEAT_REL, // Relative Mode active
MODE_FEAT_TEMP, // PT1000 Temp Mode active
MODE_FEAT_CONT, // Continuity Mode active
MODE_FEAT_XOHM // Extended Ohms active
} work_mode_t;
static const char* MENU_NAMES[] = {"REL", "CONT", "TEMP", "XOHM", "EXIT"};
typedef enum {
MENU_REL = 0,
MENU_CONT,
MENU_TEMP,
MENU_XOHM,
MENU_EXIT,
MENU_MAX_ITEMS
} menu_item_t;
typedef enum { CONT_SHORT = 0, CONT_OPEN } cont_state_t;
typedef struct {
// USB conn state
int usb_online; // debounced connection state
int usb_raw_prev; // previous raw state
uint32_t usb_ts; // ts for debounce logic
// env sensor
int env_sensor_present;
aht20_data current_env;
uint32_t env_last_read;
// GPIB
uint8_t target_addr; // active target
uint8_t dmm_addr; // specifically the HP3478A
int auto_read;
// local firmware
work_mode_t current_mode;
menu_item_t menu_pos;
uint32_t menu_timer;
uint32_t next_poll_time;
int dmm_online; // 0 = offline, 1 = online
// feature stuff
float rel_offset;
uint8_t rel_stable_count; // counter for relative mode stabilization
// XOHM variables
float xohm_r1; // Internal 10M reference value
uint8_t xohm_calibrated; // Flag: 0 = Measuring R1, 1 = Measuring Rx
// continuity
int cont_last_state; // To dedup display updates
uint32_t cont_disp_timer; // To refresh display occasionally
} app_state_t;
static app_state_t app = {.dmm_addr = DEFAULT_DMM_ADDR,
.target_addr = DEFAULT_DMM_ADDR};
// Buffers
static char cmd_buffer[128];
static char resp_buffer[256];
static char tmp_buffer[128];
static char disp_buffer[13];
// USB Ring Buffer
#define USB_RX_BUF_SIZE 512
volatile uint8_t usb_rx_buffer[USB_RX_BUF_SIZE];
volatile uint16_t usb_rx_head = 0;
volatile uint16_t usb_rx_tail = 0;
static uint8_t cdc_line_coding[7] = {0x00, 0xC2, 0x01, 0x00, 0x00, 0x00, 0x08};
volatile uint8_t buzzer_active = 0;
static uint32_t current_buzz_freq = 0;
extern volatile uint8_t usb_debug;
// helpers
static int starts_with_nocase(const char* str, const char* prefix) {
while (*prefix) {
if (tolower((unsigned char)*str) != tolower((unsigned char)*prefix)) {
return 0;
}
str++;
prefix++;
}
return 1;
}
static char* skip_spaces(char* str) {
while (*str && isspace((unsigned char)*str)) {
str++;
}
return str;
}
void fmt_float(char* buf, size_t size, float val, int precision) {
if (val != val) {
snprintf(buf, size, "NaN");
return;
}
if (val > 3.4e38f) {
snprintf(buf, size, "Inf");
return;
}
if (val < 0.0f) {
*buf++ = '-';
val = -val;
size--;
}
float rounder = 0.5f;
for (int i = 0; i < precision; i++) rounder *= 0.1f;
val += rounder;
uint32_t int_part = (uint32_t)val;
float remainder = val - (float)int_part;
int len = snprintf(buf, size, "%lu", int_part);
if (len < 0 || (size_t)len >= size) return;
buf += len;
size -= len;
if (precision > 0 && size > 1) {
*buf++ = '.';
size--;
while (precision-- > 0 && size > 1) {
remainder *= 10.0f;
int digit = (int)remainder;
if (digit > 9) digit = 9;
*buf++ = '0' + digit;
remainder -= digit;
size--;
}
*buf = 0;
}
}
void format_resistance(char* buffer, size_t buf_len, float val) {
memset(buffer, 0, buf_len);
if (val >= 1e9f) {
fmt_float(buffer, buf_len, val / 1e9f, 3);
strcat(buffer, " G");
} else if (val >= 1e6f) {
fmt_float(buffer, buf_len, val / 1e6f, 4);
strcat(buffer, " M");
} else if (val >= 1e3f) {
fmt_float(buffer, buf_len, val / 1e3f, 3);
strcat(buffer, " K");
} else {
fmt_float(buffer, buf_len, val, 2);
strcat(buffer, " OHM");
}
}
float parse_float(const char* s) {
float res = 0.0f;
float fact = 1.0f;
int sign = 1;
int point_seen = 0;
while (*s == ' ') s++;
if (*s == '+')
s++;
else if (*s == '-') {
sign = -1;
s++;
}
// parse mantissa
while (*s) {
if (*s == '.') {
point_seen = 1;
} else if (*s >= '0' && *s <= '9') {
if (point_seen) {
fact /= 10.0f;
res += (*s - '0') * fact;
} else {
res = res * 10.0f + (*s - '0');
}
} else if (*s == 'E' || *s == 'e') {
s++; // skip 'E'
int exp = atoi(s);
// apply exponent
float power = 1.0f;
int exp_abs = abs(exp);
while (exp_abs--) power *= 10.0f;
if (exp > 0)
res *= power;
else
res /= power;
break;
} else {
break;
}
s++;
}
return res * sign;
}
#ifdef GPIB_DEBUG
static void gpib_dump_state(const char* context) {
uint8_t d = 0;
if (!GPIB_READ(PIN_DIO1)) d |= 0x01;
if (!GPIB_READ(PIN_DIO2)) d |= 0x02;
if (!GPIB_READ(PIN_DIO3)) d |= 0x04;
if (!GPIB_READ(PIN_DIO4)) d |= 0x08;
if (!GPIB_READ(PIN_DIO5)) d |= 0x10;
if (!GPIB_READ(PIN_DIO6)) d |= 0x20;
if (!GPIB_READ(PIN_DIO7)) d |= 0x40;
if (!GPIB_READ(PIN_DIO8)) d |= 0x80;
printf("\n[GPIB DUMP] %s\n", context);
printf(" M: ATN=%d IFC=%d REN=%d EOI=%d | S: SRQ=%d\n", GPIB_READ(PIN_ATN),
GPIB_READ(PIN_IFC), GPIB_READ(PIN_REN), GPIB_READ(PIN_EOI),
GPIB_READ(PIN_SRQ));
printf(" H: DAV=%d NRFD=%d NDAC=%d\n", GPIB_READ(PIN_DAV),
GPIB_READ(PIN_NRFD), GPIB_READ(PIN_NDAC));
printf(" D: 0x%02X\n", d);
}
#else
#define gpib_dump_state(x) ((void)0)
#endif
// low level
void gpib_write_data(uint8_t b) {
uint32_t bshr = 0;
if (b & 0x01)
bshr |= (MASK_DIO1 << 16);
else
bshr |= MASK_DIO1;
if (b & 0x02)
bshr |= (MASK_DIO2 << 16);
else
bshr |= MASK_DIO2;
if (b & 0x04)
bshr |= (MASK_DIO3 << 16);
else
bshr |= MASK_DIO3;
if (b & 0x08)
bshr |= (MASK_DIO4 << 16);
else
bshr |= MASK_DIO4;
if (b & 0x10)
bshr |= (MASK_DIO5 << 16);
else
bshr |= MASK_DIO5;
if (b & 0x20)
bshr |= (MASK_DIO6 << 16);
else
bshr |= MASK_DIO6;
if (b & 0x40)
bshr |= (MASK_DIO7 << 16);
else
bshr |= MASK_DIO7;
if (b & 0x80)
bshr |= (MASK_DIO8 << 16);
else
bshr |= MASK_DIO8;
GPIOB->BSHR = bshr;
}
uint8_t gpib_read_data(void) {
uint32_t r = ~(GPIOB->INDR); // active low
uint8_t b = 0;
if (r & MASK_DIO1) b |= 0x01;
if (r & MASK_DIO2) b |= 0x02;
if (r & MASK_DIO3) b |= 0x04;
if (r & MASK_DIO4) b |= 0x08;
if (r & MASK_DIO5) b |= 0x10;
if (r & MASK_DIO6) b |= 0x20;
if (r & MASK_DIO7) b |= 0x40;
if (r & MASK_DIO8) b |= 0x80;
return b;
}
static int gpib_wait_pin(int pin, int expected_state) {
uint32_t start = millis();
while (GPIB_READ(pin) != expected_state) {
if ((millis() - start) > GPIB_TIMEOUT_MS) {
#ifdef GPIB_DEBUG
// Print which specific pin failed
char* pin_name = "UNKNOWN";
if (pin == PIN_NRFD)
pin_name = "NRFD";
else if (pin == PIN_NDAC)
pin_name = "NDAC";
else if (pin == PIN_DAV)
pin_name = "DAV";
printf("[GPIB ERR] Timeout waiting for %s to be %d\n", pin_name,
expected_state);
gpib_dump_state("TIMEOUT STATE");
#endif
return -1;
}
}
return 0;
}
int gpib_write_byte(uint8_t data, int assert_eoi) {
#ifdef GPIB_DEBUG
printf("[TX] 0x%02X (EOI=%d)... ", data, assert_eoi);
#endif
// wait for listeners to be ready
if (gpib_wait_pin(PIN_NRFD, 1) < 0) {
return -1;
}
gpib_write_data(data);
// assert EOI if this is the last byte
if (assert_eoi) {
GPIB_ASSERT(PIN_EOI);
}
Delay_Us(1); // T1
GPIB_ASSERT(PIN_DAV);
// wait for listeners ack
if (gpib_wait_pin(PIN_NDAC, 1) < 0) {
GPIB_RELEASE(PIN_DAV);
GPIB_RELEASE(PIN_EOI);
#ifdef GPIB_DEBUG
printf("NDAC stuck LOW, (device didn't accept)\n");
#endif
return -2;
}
GPIB_RELEASE(PIN_DAV);
GPIB_RELEASE(PIN_EOI);
// float bus
gpib_write_data(0x00);
return 0;
}
int gpib_read_byte(uint8_t* data, int* eoi_asserted) {
// sssert busy state
GPIB_ASSERT(PIN_NDAC); // not accepted yet
GPIB_ASSERT(PIN_NRFD); // not ready yet
// float data lines
gpib_write_data(0x00);
// Delay_Us(2);
// signal ready for data
GPIB_RELEASE(PIN_NRFD);
// wait for talker to assert DAV
if (gpib_wait_pin(PIN_DAV, 0) < 0) {
GPIB_RELEASE(PIN_NDAC);
GPIB_RELEASE(PIN_NRFD);
return -1; // timeout
}
Delay_Us(1); // T2
// read data and EOI status
*data = gpib_read_data();
*eoi_asserted = (GPIB_READ(PIN_EOI) == 0); // active LOW
// signal not ready (processing data)
GPIB_ASSERT(PIN_NRFD);
// signal data accepted
GPIB_RELEASE(PIN_NDAC);
// wait for talker to release DAV
if (gpib_wait_pin(PIN_DAV, 1) < 0) {
GPIB_RELEASE(PIN_NRFD);
return -2; // timeout
}
// prepare for next byte
GPIB_ASSERT(PIN_NDAC);
return 0;
}
typedef enum { SESSION_WRITE, SESSION_READ } session_mode_t;
// Sets up Talker/Listener for data transfer
int gpib_start_session(uint8_t target_addr, session_mode_t mode) {
GPIB_ASSERT(PIN_ATN);
Delay_Us(20);
// Unlisten everyone first to clear bus state
if (gpib_write_byte(GPIB_CMD_UNL, 0) < 0) {
GPIB_RELEASE(PIN_ATN);
return -1;
}
uint8_t talker = (mode == SESSION_WRITE) ? MY_ADDR : target_addr;
uint8_t listener = (mode == SESSION_WRITE) ? target_addr : MY_ADDR;
// Untalk, Set Talker, Set Listener
if (gpib_write_byte(GPIB_CMD_UNT, 0) < 0) goto err;
if (gpib_write_byte(GPIB_CMD_TAD | talker, 0) < 0) goto err;
if (gpib_write_byte(GPIB_CMD_LAD | listener, 0) < 0) goto err;
Delay_Us(10);
GPIB_RELEASE(PIN_ATN); // Switch to Data Mode
Delay_Us(10);
return 0;
err:
GPIB_RELEASE(PIN_ATN);
return -1;
}
// Bus management
// Assert Interface Clear (IFC) - The "Big Reset Button"
void gpib_interface_clear(void) {
GPIB_ASSERT(PIN_IFC);
Delay_Ms(1); // IEEE-488 requires >100us
GPIB_RELEASE(PIN_IFC);
Delay_Ms(1);
}
// Control Remote Enable (REN)
void gpib_remote_enable(int enable) {
if (enable) {
GPIB_ASSERT(PIN_REN);
} else {
GPIB_RELEASE(PIN_REN);
}
}
// Check SRQ Line (Active Low)
int gpib_check_srq(void) { return !GPIB_READ(PIN_SRQ); }
// Universal Commands (Affects All Devices)
// Universal Device Clear (DCL)
// Resets logic of ALL devices on the bus
int gpib_universal_clear(void) {
GPIB_ASSERT(PIN_ATN);
Delay_Us(20);
if (gpib_write_byte(GPIB_CMD_DCL, 0) < 0) {
GPIB_RELEASE(PIN_ATN);
return -1;
}
Delay_Us(10);
GPIB_RELEASE(PIN_ATN);
return 0;
}
// Local Lockout (LLO)
// Disables front panel "Local" buttons on all devices
int gpib_local_lockout(void) {
GPIB_ASSERT(PIN_ATN);
Delay_Us(20);
// LLO is universal, no addressing needed
if (gpib_write_byte(GPIB_CMD_LLO, 0) < 0) {
GPIB_RELEASE(PIN_ATN);
return -1;
}
Delay_Us(10);
GPIB_RELEASE(PIN_ATN);
return 0;
}
// Addressed cmds
// Selected Device Clear (SDC)
// Resets logic of ONLY the targeted device
int gpib_device_clear(uint8_t addr) {
GPIB_ASSERT(PIN_ATN);
Delay_Us(20);
if (gpib_write_byte(GPIB_CMD_UNL, 0) < 0) goto err;
if (gpib_write_byte(GPIB_CMD_LAD | addr, 0) < 0) goto err;
if (gpib_write_byte(GPIB_CMD_SDC, 0) < 0) goto err;
GPIB_RELEASE(PIN_ATN);
return 0;
err:
GPIB_RELEASE(PIN_ATN);
return -1;
}
// Group Execute Trigger (GET)
// Triggers the device to take a measurement
int gpib_trigger(uint8_t addr) {
GPIB_ASSERT(PIN_ATN);
Delay_Us(20);
if (gpib_write_byte(GPIB_CMD_UNL, 0) < 0) goto err;
if (gpib_write_byte(GPIB_CMD_LAD | addr, 0) < 0) goto err;
if (gpib_write_byte(GPIB_CMD_GET, 0) < 0) goto err;
GPIB_RELEASE(PIN_ATN);
return 0;
err:
GPIB_RELEASE(PIN_ATN);
return -1;
}
// Go To Local (GTL)
// Addresses a specific device and restores Front Panel control
// (Keeps REN asserted for other devices on the bus)
int gpib_go_to_local(uint8_t addr) {
GPIB_ASSERT(PIN_ATN);
Delay_Us(20);
if (gpib_write_byte(GPIB_CMD_UNL, 0) < 0) goto err;
if (gpib_write_byte(GPIB_CMD_LAD | addr, 0) < 0) goto err;
if (gpib_write_byte(GPIB_CMD_GTL, 0) < 0) goto err;
GPIB_RELEASE(PIN_ATN);
return 0;
err:
GPIB_RELEASE(PIN_ATN);
return -1;
}
// Serial Poll
// Reads the Status Byte (STB) from the device
int gpib_serial_poll(uint8_t addr, uint8_t* status) {
GPIB_ASSERT(PIN_ATN);
Delay_Us(20);
// setupo seq: UNL -> SPE -> LAD(Me) -> TAD(Target)
if (gpib_write_byte(GPIB_CMD_UNL, 0) < 0) goto err;
if (gpib_write_byte(GPIB_CMD_SPE, 0) < 0) goto err;
if (gpib_write_byte(GPIB_CMD_LAD | MY_ADDR, 0) < 0) goto err;
if (gpib_write_byte(GPIB_CMD_TAD | addr, 0) < 0) goto err;
// drop ATN to read data
GPIB_RELEASE(PIN_ATN);
Delay_Us(5);
int eoi;
int res = gpib_read_byte(status, &eoi);
// handshake complete, clean up lines
GPIB_RELEASE(PIN_NRFD);
GPIB_RELEASE(PIN_NDAC);
// end seq: ATN -> SPD -> UNT
GPIB_ASSERT(PIN_ATN);
Delay_Us(5);
gpib_write_byte(GPIB_CMD_SPD, 0); // disable spoll
gpib_write_byte(GPIB_CMD_UNT, 0); // untalk
GPIB_RELEASE(PIN_ATN);
return res;
err:
GPIB_RELEASE(PIN_ATN);
return -1;
}
// Data transfer
// Send string to device (auto-handles CRLF escape sequences)
int gpib_send(uint8_t addr, const char* str) {
if (gpib_start_session(addr, SESSION_WRITE) < 0) return -1;
int len = strlen(str);
for (int i = 0; i < len; i++) {
uint8_t b = str[i];
int skip = 0;
// escape sequence handling (\n, \r)
if (b == '\\' && i < len - 1) {
if (str[i + 1] == 'n') {
b = 0x0A;
skip = 1;
} else if (str[i + 1] == 'r') {
b = 0x0D;
skip = 1;
}
}
// tag the last byte with EOI
int is_last = (i == len - 1) || (skip && i == len - 2);
if (gpib_write_byte(b, is_last) < 0) {
// error during write, try to clean up bus
GPIB_ASSERT(PIN_ATN);
gpib_write_byte(GPIB_CMD_UNL, 0);
GPIB_RELEASE(PIN_ATN);
return -1;
}
if (skip) i++;
}
// normal cleanup
GPIB_ASSERT(PIN_ATN);
gpib_write_byte(GPIB_CMD_UNL, 0);
GPIB_RELEASE(PIN_ATN);
return 0;
}
// Receive string from device
int gpib_receive(uint8_t addr, char* buf, int max_len) {
if (gpib_start_session(addr, SESSION_READ) < 0) return -1;
int count = 0;
int eoi = 0;
uint8_t byte;
while (count < max_len - 1) {
if (gpib_read_byte(&byte, &eoi) < 0) break;
buf[count++] = (char)byte;
// stop on EOI or LF
if (eoi || byte == '\n') break;
}
buf[count] = 0; // null terminate
// ensure listeners are open before asserting ATN
GPIB_RELEASE(PIN_NDAC);
GPIB_RELEASE(PIN_NRFD);
// cleanup: ATN -> UNT
GPIB_ASSERT(PIN_ATN);
gpib_write_byte(GPIB_CMD_UNT, 0);
GPIB_RELEASE(PIN_ATN);
return count;
}
// write then read (for "?" commands)
int gpib_query(uint8_t addr, const char* cmd, char* buf, int max_len) {
if (gpib_send(addr, cmd) != 0) return -1;
Delay_Ms(2); // give device time to process
return gpib_receive(addr, buf, max_len);
}
void gpib_init(void) {
// configure control lines as open-drain outputs
funPinMode(PIN_EOI, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
funPinMode(PIN_REN, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
funPinMode(PIN_ATN, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
funPinMode(PIN_IFC, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
funPinMode(PIN_DAV, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
funPinMode(PIN_NDAC, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
funPinMode(PIN_NRFD, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
// SRQ is input with pull-up
funPinMode(PIN_SRQ, GPIO_CNF_IN_PUPD);
funDigitalWrite(PIN_SRQ, 1);
// release all control lines to idle (HIGH)
GPIB_RELEASE(PIN_EOI);
GPIB_RELEASE(PIN_REN);
GPIB_RELEASE(PIN_ATN);
GPIB_RELEASE(PIN_IFC);
GPIB_RELEASE(PIN_DAV);
GPIB_RELEASE(PIN_NDAC);
GPIB_RELEASE(PIN_NRFD);
// data lines
funPinMode(PIN_DIO1, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
funPinMode(PIN_DIO2, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
funPinMode(PIN_DIO3, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
funPinMode(PIN_DIO4, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
funPinMode(PIN_DIO5, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
funPinMode(PIN_DIO6, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
funPinMode(PIN_DIO7, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
funPinMode(PIN_DIO8, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD);
// float data lines (release to HIGH)
gpib_write_data(0x00);
#ifdef GPIB_DEBUG
printf("[GPIB] Asserting IFC...\n");
#endif
gpib_interface_clear();
#ifdef GPIB_DEBUG
gpib_dump_state("INIT DONE");
// if no device is connected: NRFD/NDAC/DAV should all be 1
// if device is connected: NRFD/NDAC might be 0
#endif
}
// ------------------------------------
void buzzer_init(void) {
funPinMode(PIN_BUZZ, GPIO_Speed_50MHz | GPIO_CNF_OUT_PP);
funDigitalWrite(PIN_BUZZ, 0);
RCC->APB1PCENR |= RCC_TIM2EN;
TIM2->PSC = (FUNCONF_SYSTEM_CORE_CLOCK / 1000000) - 1;
TIM2->ATRLR = 250;
TIM2->DMAINTENR |= TIM_UIE;
NVIC_EnableIRQ(TIM2_IRQn);
TIM2->CTLR1 |= TIM_CEN;
}
void buzzer_set(uint32_t freq_hz) {
if (current_buzz_freq == freq_hz) return;
current_buzz_freq = freq_hz;
if (freq_hz == 0) {
buzzer_active = 0;
return;
}
uint16_t reload_val = (uint16_t)(1000000UL / (2 * freq_hz));
TIM2->ATRLR = reload_val;
TIM2->CNT = 0; // reset phase only on CHANGE
buzzer_active = 1;
}
void TIM2_IRQHandler(void) __attribute__((interrupt));
void TIM2_IRQHandler(void) {
if (TIM2->INTFR & TIM_UIF) {
// clr the flag
TIM2->INTFR = (uint16_t)~TIM_UIF;
if (buzzer_active) {
// Toggle PC13
if (GPIOC->OUTDR & (1 << 13)) {
GPIOC->BSHR = (1 << (16 + 13)); // Reset (Low)
} else {
GPIOC->BSHR = (1 << 13); // Set (High)
}
} else {
// ensure low when inactive
if (GPIOC->OUTDR & (1 << 13)) {
GPIOC->BSHR = (1 << (16 + 13));
}
}
}
}
// TODO: maybne don't sleep inside it
void tone(unsigned int freq_hz, unsigned int duration_ms) {
if (freq_hz == 0) {
Delay_Ms(duration_ms);
return;
}
buzzer_set(freq_hz);
Delay_Ms(duration_ms);
buzzer_set(0);
}
void play_startup_tune() {
// "Boot Up"
tone(1500, 100);
Delay_Ms(20);
tone(2500, 100);
Delay_Ms(20);
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) {
if (ctx->USBFS_SetupReqType & USB_REQ_TYP_CLASS) {
switch (setup_code) {
case 0x21: // CDC_GET_LINE_CODING
ctx->pCtrlPayloadPtr = cdc_line_coding;
return 7;
case 0x20: // CDC_SET_LINE_CODING
case 0x22: // CDC_SET_CONTROL_LINE_STATE
return 0;
}
}
return -1;
}
int HandleInRequest(struct _USBState* ctx __attribute__((unused)),
int endp __attribute__((unused)),
uint8_t* data __attribute__((unused)),
int len __attribute__((unused))) {
return 0;
}
void HandleDataOut(struct _USBState* ctx, int endp, uint8_t* data, int len) {
if (endp == 0) {
ctx->USBFS_SetupReqLen = 0;
} else if (endp == 2) {
// Copy to Ring Buffer
for (int i = 0; i < len; i++) {
uint16_t next_head = (usb_rx_head + 1) % USB_RX_BUF_SIZE;
if (next_head != usb_rx_tail) {
usb_rx_buffer[usb_rx_head] = data[i];
usb_rx_head = next_head;
}
}
}
}
static void usb_send_text(const char* str) {
int len = strlen(str);
int pos = 0;
while (pos < len) {
int chunk = len - pos;
if (chunk > 64) chunk = 64;
USBFS_SendEndpointNEW(3, (uint8_t*)(str + pos), chunk, 1);
Delay_Us(250); // yikes
pos += chunk;
}
}
// pull a line from ring buffer
int get_start_command(char* dest_buf, int max_len) {
if (usb_rx_head == usb_rx_tail) return 0;
uint16_t temp_tail = usb_rx_tail;
int len = 0;
int found_newline = 0;
// Peek for newline
while (temp_tail != usb_rx_head) {
char c = usb_rx_buffer[temp_tail];
if (c == '\n' || c == '\r') {
found_newline = 1;
break;
}
temp_tail = (temp_tail + 1) % USB_RX_BUF_SIZE;
len++;
if (len >= max_len - 1) break;
}
if (found_newline) {
// copy out
for (int i = 0; i < len; i++) {
dest_buf[i] = usb_rx_buffer[usb_rx_tail];
usb_rx_tail = (usb_rx_tail + 1) % USB_RX_BUF_SIZE;
}
dest_buf[len] = 0;
// eat newline chars
while (usb_rx_tail != usb_rx_head) {
char c = usb_rx_buffer[usb_rx_tail];
if (c == '\r' || c == '\n') {
usb_rx_tail = (usb_rx_tail + 1) % USB_RX_BUF_SIZE;
} else {
break;
}
}
return len;
}
return 0;
}
// ----------------------------------------
static void handle_usb_state(void) {
int raw_status = USB_HW_IS_ACTIVE();
uint32_t now = millis();
// edge detection
if (raw_status != app.usb_raw_prev) {
app.usb_ts = now;
app.usb_raw_prev = raw_status;
}
// debounce with different thresholds for connect/disconnect
uint32_t threshold =
raw_status ? USB_DEBOUNCE_CONNECT_MS : USB_DEBOUNCE_DISCONNECT_MS;
if ((now - app.usb_ts) > threshold) {
// state has been stable long enough
if (app.usb_online != raw_status) {
app.usb_online = raw_status;
if (app.usb_online) {
usb_rx_tail = usb_rx_head = 0;
play_connected_tune();
} else {
play_disconnected_tune();
}
}
}
}
static void handle_env_sensor(void) {
if (!app.env_sensor_present) {
return;
}
uint32_t now = millis();
if ((now - app.env_last_read) >= ENV_SENSOR_READ_INTERVAL_MS) {
if (aht20_read(&app.current_env) == AHT20_OK) {
app.env_last_read = now;
}
}
}
// Helper to write text to HP3478A Display
// CMD: D2 = Full alphanumeric message
void dmm_display(const char* text) {
snprintf(resp_buffer, 64, "%s%s\n", HP_CMD_TEXT_DISPLAY, text);
gpib_send(app.dmm_addr, resp_buffer);
}
static inline void dmm_display_normal(void) {
gpib_send(app.dmm_addr, HP_CMD_RESET_DISPLAY);
}
void exit_to_passthrough(void) {
dmm_display_normal(); // "D1"
buzzer_set(0); // mute
gpib_send(app.dmm_addr, "H1 T1 N5" HP_CMD_MASK_BTN_ONLY);
Delay_Ms(50);
gpib_go_to_local(app.dmm_addr);
app.current_mode = MODE_PASSTHROUGH;
}
void enter_feature_mode(menu_item_t item) {
gpib_remote_enable(1); // assert REN
Delay_Ms(20);
switch (item) {
case MENU_REL:
// T1: Internal Trigger, M21: Data Ready + SRQ Button
gpib_send(app.dmm_addr, "F3 N5 T1" HP_CMD_MASK_BTN_DATA);
app.current_mode = MODE_FEAT_REL;
app.rel_offset = 0.0f;
dmm_display("REL MODE");
break;
case MENU_TEMP:
// F3: 2W Ohm, M21: Data + Button Mask
gpib_send(app.dmm_addr, "F3 R3 N4 Z1 T1 " HP_CMD_MASK_BTN_DATA);
app.current_mode = MODE_FEAT_TEMP;
dmm_display("TEMP PT1000");
break;
case MENU_CONT:
// F3: 2W Ohm, R0: 30 Ohm Range, N3: 3.5 Digits (fastest ADC), M21
gpib_send(app.dmm_addr, "F3 R1 N3 Z0 T1 " HP_CMD_MASK_BTN_DATA);
app.current_mode = MODE_FEAT_CONT;
app.cont_last_state = -1;
app.cont_disp_timer = millis();
dmm_display("CONT MODE");
break;
case MENU_XOHM:
// H7: High Impedance / Extended Ohm Mode
// The DMM puts internal 10M in parallel with input
gpib_send(app.dmm_addr, "H7 N5 T1 " HP_CMD_MASK_BTN_DATA);
app.xohm_r1 = 0.0f;
app.xohm_calibrated = 0;
app.current_mode = MODE_FEAT_XOHM;
dmm_display("XOHM 10M REF");
break;
case MENU_EXIT:
default:
exit_to_passthrough();
break;
}
}
void enter_menu_mode(void) {
uint32_t now = millis();
app.current_mode = MODE_MENU;
app.menu_pos = MENU_REL;
app.menu_timer = now;
dmm_display("M: REL");
gpib_send(app.dmm_addr, HP_CMD_MASK_BTN_ONLY);
Delay_Ms(200);
}
void handle_feature_logic(void) {
uint8_t stb = 0;
gpib_serial_poll(app.dmm_addr, &stb);
// exit button (SRQ)
if (stb & 0x10) {
exit_to_passthrough();
return;
}
// data ready (Bit 0)
if (!(stb & 0x01)) return;
int len = gpib_receive(app.dmm_addr, resp_buffer, sizeof(resp_buffer));
if (len < 0) {
// timeout or error
// printf("Read Timeout in Feature\n");
app.current_mode = MODE_PASSTHROUGH;
app.dmm_online = 0;
gpib_interface_clear();
return;
}
float val = parse_float(resp_buffer);
// overload (HP 3478A sends +9.99990E+9 for OL)
int is_overload = (val > HP_OVERLOAD_VAL);
// RELATIVE MODE
if (app.current_mode == MODE_FEAT_REL) {
if (app.rel_offset == 0.0f) {
// waiting to capture the NULL value
if (is_overload) {
app.rel_stable_count = 0; // reset counter if probes are open
dmm_display("O.VLD");
} else {
// valid reading
app.rel_stable_count++;
if (app.rel_stable_count >= REL_STABLE_SAMPLES) {
app.rel_offset = val;
dmm_display("NULL SET");
tone(3000, 50);
app.rel_stable_count = 0;
} else {
dmm_display("LOCKING...");
}
}
} else {
// offset is already set
if (is_overload) {
dmm_display("O.VLD");
} else {
float diff = val - app.rel_offset;
fmt_float(disp_buffer, sizeof(disp_buffer), diff, 4);
if (strlen(disp_buffer) < 11) strcat(disp_buffer, " D");
dmm_display(disp_buffer);
}
}
}
// TEMP MODE
else if (app.current_mode == MODE_FEAT_TEMP) {
if (val > 4000.0f || val < 10.0f) {
dmm_display("OPEN / ERR");
} else {
float c = RTD_R0 - val;
float b = RTD_R0 * RTD_A;
float a = RTD_R0 * RTD_B;
float disc = (b * b) - (4 * a * c);
if (disc >= 0) {
float temp = (-b + sqrtf(disc)) / (2 * a);
fmt_float(disp_buffer, sizeof(disp_buffer), temp, 1);
strcat(disp_buffer, " C");
dmm_display(disp_buffer);
}
}
}
// CONT MODE
else if (app.current_mode == MODE_FEAT_CONT) {
int is_short = (!is_overload && val < CONT_THRESHOLD_OHMS);
// beep
if (is_short) {
buzzer_set(2000); // 2kHz tone
} else {
buzzer_set(0);
}
// display logic
uint32_t now = millis();
// force update if state changed or timeout
if ((is_short != app.cont_last_state) ||
(now - app.cont_disp_timer > 200)) {
if (is_overload) {
dmm_display("OPEN");
} else {
if (val < 1000.0f) {
fmt_float(disp_buffer, sizeof(disp_buffer), val, 1);
strcat(disp_buffer, " OHM");
} else {
// shouldn't happen in 300 range :)
format_resistance(disp_buffer, sizeof(disp_buffer), val);
}
dmm_display(disp_buffer);
}
app.cont_last_state = is_short;
app.cont_disp_timer = now;
}
}
// XOHM MODE
else if (app.current_mode == MODE_FEAT_XOHM) {
// cal phase, measure the internal 10M resistor
if (app.xohm_calibrated == 0) {
// need the probes to be open. Internal R is ~10M
if (val > 8.0e6f && val < 12.0e6f) {
app.xohm_r1 = val; // Store R1
app.xohm_calibrated = 1;
tone(3000, 100);
dmm_display("READY");
Delay_Ms(500);
} else {
dmm_display("OPEN PROBES");
}
}
// Rx = (R1 * R2) / (R1 - R2)
// R1 = xohm_ref (Internal)
// R2 = val (Measured Parallel)
else {
if (is_overload || val >= (app.xohm_r1 - 1000.0f)) {
dmm_display("OPEN");
} else {
float r1 = app.xohm_r1;
float r2 = val;
float rx = (r1 * r2) / (r1 - r2);
format_resistance(disp_buffer, sizeof(disp_buffer), rx);
dmm_display(disp_buffer);
}
}
}
}
void handle_menu_navigation(void) {
uint32_t now = millis();
uint32_t elapsed = now - app.menu_timer;
// nav: check SRQ (next item)
if (gpib_check_srq()) {
uint8_t stb = 0;
gpib_serial_poll(app.dmm_addr, &stb);
// only 4b (front panel button SRQ)
if (stb & 0x10) {
app.menu_timer = now; // reset the "hover" timer
app.menu_pos++;
if (app.menu_pos >= MENU_MAX_ITEMS) app.menu_pos = 0;
char* s = "M: ???";
switch (app.menu_pos) {
case MENU_REL:
s = "M: REL";
break;
case MENU_CONT:
s = "M: CONT";
break;
case MENU_TEMP:
s = "M: TEMP";
break;
case MENU_XOHM:
s = "M: XOHM";
break;
case MENU_EXIT:
s = "M: EXIT";
break;
default:
break;
}
dmm_display(s);
// re-arm SRQ
gpib_send(app.dmm_addr, HP_CMD_MASK_BTN_ONLY);
Delay_Ms(200);
return;
}
}
// visual cd
static int last_dot_count = -1;
int dot_count = elapsed / 800;
if (dot_count > 3) dot_count = 3;
// only update display if the dots changed
if (dot_count != last_dot_count) {
const char* dots = "";
if (dot_count == 1)
dots = ".";
else if (dot_count == 2)
dots = "..";
else if (dot_count == 3)
dots = "...";
snprintf(disp_buffer, sizeof(disp_buffer), "M: %s%s",
MENU_NAMES[app.menu_pos], dots);
dmm_display(disp_buffer);
last_dot_count = dot_count;
}
// reset dot tracker if cycled
if (elapsed < 100) last_dot_count = 0;
// commit selection
if (elapsed > 2400) {
if (app.menu_pos == MENU_EXIT) {
// printf("[MENU] Hover EXIT\n");
exit_to_passthrough();
} else {
// printf("[MENU] Hover Select Item %d\n", app.menu_pos);
enter_feature_mode(app.menu_pos);
}
return;
}
}
void app_loop(void) {
uint32_t now = millis();
// Passthrough
if (app.current_mode == MODE_PASSTHROUGH) {
if (now <= app.next_poll_time) {
return;
}
uint8_t stb = 0;
int poll_result = gpib_serial_poll(app.dmm_addr, &stb);
// DMM offline - detect recovery
if (poll_result != 0) {
if (app.dmm_online) {
// printf("DMM Lost\n");
gpib_interface_clear();
app.dmm_online = 0;
}
app.next_poll_time = now + POLL_INTERVAL_MS;
return;
}
// DMM online - detect recovery transition
if (!app.dmm_online) {
// printf("DMM Recovered. Re-initializing...\n");
gpib_send(app.dmm_addr, HP_CMD_MASK_BTN_ONLY "K");
gpib_go_to_local(app.dmm_addr);
app.dmm_online = 1;
tone(4000, 50);
// do it in the next clean poll
app.next_poll_time = now + DMM_RECOVERY_DELAY_MS;
return;
}
// check for button press
if (stb & 0x10) {
enter_menu_mode();
}
app.next_poll_time = now + POLL_INTERVAL_MS;
return;
}
// Nav
if (app.current_mode == MODE_MENU) {
handle_menu_navigation();
return;
}
// Features
// early exit if no SRQ
if (!gpib_check_srq()) {
return;
}
uint8_t stb;
if (gpib_serial_poll(app.dmm_addr, &stb) != 0) {
// DMM crashed during feature mode
// printf("Feature crash: DMM Lost\n");
app.current_mode = MODE_PASSTHROUGH;
app.dmm_online = 0;
gpib_interface_clear();
return;
}
// check exit button first (priority)
if (stb & 0x10) {
exit_to_passthrough();
return;
}
// handle measurement data ready
if (stb & 0x01) {
handle_feature_logic();
}
}
static void cmd_help(void) {
static const char* help_text =
"\r\n=== HP3478A Internal USB-GPIB v" FW_VERSION
" ===\r\n"
"\r\n"
"Prologix-style Commands:\r\n"
" ++addr <N> Set Target GPIB Address (0-30)\r\n"
" ++auto <0|1> 0=Off, 1=Read-After-Write\r\n"
" ++read Read data from current target\r\n"
" ++write <D> Write data <D> to target\r\n"
" ++trg Trigger (GET) - Target\r\n"
" ++clr Device Clear (SDC) - Target\r\n"
" ++dcl Device Clear (DCL) - All Devices\r\n"
" ++spoll [A] Serial Poll (Target or Addr A)\r\n"
" ++loc Local Mode (Drop REN Line)\r\n"
" ++gtl Go To Local (GTL) - Target Only\r\n"
" ++llo Local Lockout (Disable front panels)\r\n"
" ++ren <0|1> Remote Enable Line control\r\n"
" ++ifc Interface Clear (Bus Reset)\r\n"
" ++ver Firmware Version\r\n"
" ++stat Show configuration\r\n"
" ++rst System Reboot\r\n"
"\r\n"
"HP3478A Internal Commands:\r\n"
" ++cont, ++temp, ++rel, ++xohm, ++norm\r\n"
" ++disp <msg> Text message on LCD (Max 12)\r\n"
" ++env [temp|hum] Internal Sensor (Default: csv)\r\n"
"\r\n"
"Usage:\r\n"
" Commands starting with ++ are executed locally.\r\n"
" All other data is sent to the target GPIB device.\r\n"
" Input '?' or '? <cmd>' for this help.\r\n";
usb_send_text(help_text);
}
static void cmd_status(void) {
snprintf(tmp_buffer, sizeof(tmp_buffer),
"Stat:\r\n"
" Target Addr: %d\r\n"
" Internal DMM: %d\r\n"
" Auto Read: %s\r\n"
" Current Mode: %d\r\n"
" FW: " FW_VERSION "\r\n",
app.target_addr, app.dmm_addr, app.auto_read ? "ON" : "OFF",
app.current_mode);
usb_send_text(tmp_buffer);
}
static void process_command(void) {
if (!get_start_command(cmd_buffer, sizeof(cmd_buffer))) {
return;
}
int is_query = 0;
char* p_cmd = skip_spaces(cmd_buffer);
int is_cpp_cmd = (strncmp(p_cmd, "++", 2) == 0);
if (app.current_mode != MODE_PASSTHROUGH) {
buzzer_set(0);
app.current_mode = MODE_PASSTHROUGH;
dmm_display_normal();
gpib_remote_enable(1); // ensure REN matches state
}
if (is_cpp_cmd) {
// move past "++"
p_cmd += 2;
// 'p_args' will point to the first non-space char after the command word
char* p_args = p_cmd;
while (*p_args && !isspace((unsigned char)*p_args))
p_args++; // find end of word
p_args = skip_spaces(p_args); // find start of args
// CMD: ADDR
if (starts_with_nocase(p_cmd, "addr")) {
if (*p_args) {
int addr = atoi(p_args);
if (addr >= 0 && addr <= 30) {
app.target_addr = addr;
usb_send_text("OK\r\n");
} else
usb_send_text("ERR: Invalid Addr\r\n");
} else {
// if no arg provided, show current
snprintf(tmp_buffer, sizeof(tmp_buffer), "%d\r\n", app.target_addr);
usb_send_text(tmp_buffer);
}
}
// CMD: WRITE
else if (starts_with_nocase(p_cmd, "write")) {
// sends the rest of the string (p_args) to GPIB
if (*p_args) {
gpib_send(app.target_addr, p_args);
if (app.auto_read) goto do_read_operation; // jmp to read block
}
}
// CMD: READ
else if (starts_with_nocase(p_cmd, "read")) {
goto do_read_operation;
}
// CMD: AUTO
else if (starts_with_nocase(p_cmd, "auto")) {
if (*p_args) {
app.auto_read = atoi(p_args) ? 1 : 0;
usb_send_text("OK\r\n");
} else {
usb_send_text(app.auto_read ? "1\r\n" : "0\r\n");
}
}
// CMD: TRG
else if (starts_with_nocase(p_cmd, "trg")) {
gpib_trigger(app.target_addr);
usb_send_text("OK\r\n");
}
// CMD: STATUS / STAT
else if (starts_with_nocase(p_cmd, "stat")) {
cmd_status();
}
// CMD: CLR
else if (starts_with_nocase(p_cmd, "clr")) {
gpib_device_clear(app.target_addr);
usb_send_text("OK\r\n");
}
// CMD: REN (Remote Enable)
else if (starts_with_nocase(p_cmd, "ren")) {
if (*p_args) {
int state = atoi(p_args);
gpib_remote_enable(state);
usb_send_text("OK\r\n");
} else {
usb_send_text("usage: ++ren 1|0\r\n");
}
}
// CMD: IFC (Interface Clear)
else if (starts_with_nocase(p_cmd, "ifc")) {
gpib_interface_clear();
usb_send_text("OK\r\n");
}
// CMD: SPOLL (Serial Poll)
else if (starts_with_nocase(p_cmd, "spoll")) {
uint8_t poll_addr = app.target_addr;
if (*p_args) {
int arg = atoi(p_args);
if (arg >= 0 && arg <= 30) poll_addr = arg;
}
uint8_t stb;
if (gpib_serial_poll(poll_addr, &stb) == 0) {
// print status byte as dec
snprintf(tmp_buffer, sizeof(tmp_buffer), "%d\r\n", stb);
usb_send_text(tmp_buffer);
} else {
usb_send_text("ERR: Bus\r\n");
}
}
// CMD: LLO (Local Lockout)
else if (starts_with_nocase(p_cmd, "llo")) {
gpib_local_lockout();
usb_send_text("OK\r\n");
}
// CMD: DCL (Universal Clear)
else if (starts_with_nocase(p_cmd, "dcl")) {
gpib_universal_clear();
usb_send_text("OK\r\n");
}
// CMD: GTL (Go To Local)
else if (starts_with_nocase(p_cmd, "gtl")) {
gpib_go_to_local(app.target_addr);
usb_send_text("OK\r\n");
}
// HP3478A
else if (starts_with_nocase(p_cmd, "cont"))
enter_feature_mode(MENU_CONT);
else if (starts_with_nocase(p_cmd, "temp"))
enter_feature_mode(MENU_TEMP);
else if (starts_with_nocase(p_cmd, "rel"))
enter_feature_mode(MENU_REL);
else if (starts_with_nocase(p_cmd, "xohm"))
enter_feature_mode(MENU_XOHM);
else if (starts_with_nocase(p_cmd, "norm"))
exit_to_passthrough();
else if (starts_with_nocase(p_cmd, "disp")) {
int i = 0;
while (p_args[i] != 0 && i < 12) {
char c = p_args[i];
if (c >= 'a' && c <= 'z') {
c -= 32;
}
disp_buffer[i] = c;
i++;
}
disp_buffer[i] = 0;
dmm_display(disp_buffer);
usb_send_text("OK\r\n");
}
// SYSTEM
else if (starts_with_nocase(p_cmd, "loc")) {
gpib_remote_enable(0);
usb_send_text("OK\r\n");
} else if (starts_with_nocase(p_cmd, "rst")) {
usb_send_text("Rebooting...\r\n");
Delay_Ms(100);
NVIC_SystemReset();
} else if (starts_with_nocase(p_cmd, "ver")) {
usb_send_text("HP3478A Internal GPIB " FW_VERSION "\r\n");
} else if (starts_with_nocase(p_cmd, "help") ||
starts_with_nocase(p_cmd, "?")) {
cmd_help();
} else if (starts_with_nocase(p_cmd, "env")) {
if (!app.env_sensor_present) {
usb_send_text("ERR: No Sensor\r\n");
return;
}
float t = (float)app.current_env.temp_c_x100 / 100.0f;
float h = (float)app.current_env.hum_p_x100 / 100.0f;
char* arg = skip_spaces(p_args);
char out_buf[32];
// temp only
if (starts_with_nocase(arg, "temp")) {
fmt_float(out_buf, sizeof(out_buf), t, 2);
usb_send_text(out_buf);
usb_send_text("\r\n");
}
// hum only
else if (starts_with_nocase(arg, "hum")) {
fmt_float(out_buf, sizeof(out_buf), h, 2);
usb_send_text(out_buf);
usb_send_text("\r\n");
}
// CSV format (temp,hum)
else {
fmt_float(out_buf, 16, t, 2);
strcat(out_buf, ",");
// fmt humidity into a temp buffer and append
char h_buf[16];
fmt_float(h_buf, sizeof(h_buf), h, 2);
strcat(out_buf, h_buf);
strcat(out_buf, "\r\n");
usb_send_text(out_buf);
}
} else {
usb_send_text("ERR: Unknown Command\r\n");
}
return; // end of ++
}
// PASSTHROUGH MODE
// check for query '?' to trigger implicit read
is_query = (strchr(p_cmd, '?') != NULL);
if (gpib_send(app.target_addr, p_cmd) < 0) {
usb_send_text("ERR: Send Fail\r\n");
return;
}
// check if we should read back
if (is_query || app.auto_read) {
goto do_read_operation;
}
return;
do_read_operation: {
int len = gpib_receive(app.target_addr, resp_buffer, sizeof(resp_buffer));
if (len > 0) {
usb_send_text(resp_buffer);
} else {
if (is_cpp_cmd || is_query) {
usb_send_text("ERR: Read Timeout\r\n");
}
}
}
}
int main() {
SystemInit();
systick_init();
funGpioInitAll();
// Buzzer setup
buzzer_init();
// I2C sensor
i2c_init();
app.env_sensor_present = aht20_init() == AHT20_OK ? 1 : 0;
// GPIB controller
gpib_init();
gpib_remote_enable(1);
// USB interface
USBFSSetup();
// usb_debug = 1;
play_startup_tune();
// app state
app.current_mode = MODE_PASSTHROUGH;
app.target_addr = 18;
app.dmm_addr = app.target_addr;
app.xohm_r1 = 0.0f;
app.usb_online = 0;
app.usb_raw_prev = USB_HW_IS_ACTIVE();
app.usb_ts = millis();
while (1) {
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, HP_CMD_MASK_BTN_ONLY "K");
gpib_go_to_local(app.dmm_addr);
break;
}
Delay_Ms(100);
}
app.dmm_online = 1;
while (1) {
handle_usb_state();
app_loop();
handle_env_sensor();
if (app.usb_online) {
process_command();
}
}
}