diff --git a/inc/gpib_defs.h b/inc/gpib_defs.h index 6a1e79e..d669a8d 100644 --- a/inc/gpib_defs.h +++ b/inc/gpib_defs.h @@ -3,7 +3,7 @@ #include "ch32fun.h" -// #define GPIB_DEBUG 1 +#define GPIB_DEBUG 1 // Control Lines (Active LOW) #define PIN_EOI PB3 @@ -153,14 +153,42 @@ #define GPIB_READ(pin) funDigitalRead(pin) // Data Lines (DIO1-DIO8) -#define PIN_DIO1 PB9 -#define PIN_DIO2 PB8 -#define PIN_DIO3 PB5 -#define PIN_DIO4 PB4 -#define PIN_DIO5 PB15 -#define PIN_DIO6 PB14 -#define PIN_DIO7 PB13 -#define PIN_DIO8 PB12 +// #define PIN_DIO1 PB9 +// #define PIN_DIO2 PB8 +// #define PIN_DIO3 PB5 +// #define PIN_DIO4 PB4 +// #define PIN_DIO5 PB15 +// #define PIN_DIO6 PB14 +// #define PIN_DIO7 PB13 +// #define PIN_DIO8 PB12 + +// Physical Pin mappings on PORT B +#define PIN_DIO1 9 // PB9 +#define PIN_DIO2 8 // PB8 +#define PIN_DIO3 5 // PB5 +#define PIN_DIO4 4 // PB4 +#define PIN_DIO5 15 // PB15 +#define PIN_DIO6 14 // PB14 +#define PIN_DIO7 13 // PB13 +#define PIN_DIO8 12 // PB12 + +#define SHIFT_DIO1 (PIN_DIO1 - 0) // 9 +#define SHIFT_DIO2 (PIN_DIO2 - 1) // 7 +#define SHIFT_DIO3 (PIN_DIO3 - 2) // 3 +#define SHIFT_DIO4 (PIN_DIO4 - 3) // 1 +#define SHIFT_DIO5 (PIN_DIO5 - 4) // 11 +#define SHIFT_DIO6 (PIN_DIO6 - 5) // 9 +#define SHIFT_DIO7 (PIN_DIO7 - 6) // 7 +#define SHIFT_DIO8 (PIN_DIO8 - 7) // 5 + +// pins that share the same shift amount +// Group A: shift right 9 (PB9->Bit0, PB14->Bit5) +#define MASK_GROUP_A ((1 << 0) | (1 << 5)) +// Group B: shift right 7 (PB8->Bit1, PB13->Bit6) +#define MASK_GROUP_B ((1 << 1) | (1 << 6)) + +#define CALC_PIN_BSHR(val, bit_idx, pin_num) \ + ((val & (1 << bit_idx)) ? (1U << (pin_num + 16)) : (1U << pin_num)) #define MASK_DIO1 (1U << 9) #define MASK_DIO2 (1U << 8) @@ -171,7 +199,7 @@ #define MASK_DIO7 (1U << 13) #define MASK_DIO8 (1U << 12) -static const int DIO_PINS[] = {PIN_DIO1, PIN_DIO2, PIN_DIO3, PIN_DIO4, - PIN_DIO5, PIN_DIO6, PIN_DIO7, PIN_DIO8}; +// static const int DIO_PINS[] = {PIN_DIO1, PIN_DIO2, PIN_DIO3, PIN_DIO4, +// PIN_DIO5, PIN_DIO6, PIN_DIO7, PIN_DIO8}; #endif diff --git a/main.c b/main.c index 2bb6c2b..3743556 100644 --- a/main.c +++ b/main.c @@ -42,7 +42,7 @@ #define USB_SEND_TIMEOUT_MS 50 #define ENV_SENSOR_READ_INTERVAL_MS 1000 -#define DEFAULT_GPIB_TIMEOUT_MS 3000 +#define DEFAULT_GPIB_TIMEOUT_MS 1200 // Menu Animation Timings #define MENU_DOT_INTERVAL_MS 500 // Speed of "..." addition @@ -92,36 +92,55 @@ typedef enum { CMD_UNKNOWN = 0, - // Prologix / Standard GPIB - CMD_ADDR, - CMD_AUTO, - CMD_READ, - CMD_WRITE, - CMD_TRG, - CMD_CLR, - CMD_DCL, - CMD_SPOLL, - CMD_LOC, - CMD_GTL, - CMD_LLO, - CMD_REN, - CMD_IFC, - CMD_STAT, - CMD_RST, - CMD_VER, - CMD_HELP, - // HP3478A Features - CMD_CONT, - CMD_TEMP, - CMD_REL, - CMD_XOHM, - CMD_DBM, - CMD_DIODE, - CMD_MATH, - CMD_NORM, - CMD_DISP, - CMD_ENV, - CMD_TIMEOUT + + // Controller config + CMD_VER, // Get Firmware Version + CMD_HELP, // List commands + CMD_SAVECFG, // Save configuration to EEPROM (we just ACK to not break + // compat, cba w/ flash) + CMD_RST, // Reset the controller + CMD_ADDR, // Set GPIB primary address + CMD_MODE, // Set Controller vs Device mode (we're always the controller) + CMD_TIMEOUT, // Set read timeouts + + // Data transport/formatting + CMD_READ, // Read data from bus + CMD_WRITE, // Write data to bus + CMD_AUTO, // Auto-read mode (Prologix header) + CMD_EOI, // Enables or disables the assertion of the EOI signal + CMD_EOS, // GPIB termination character + CMD_EOR, // End of receive + CMD_EOT_ENABLE, // the character to be appended to the USB output from the + // interface to the host + CMD_EOT_CHAR, // specific EOT char (e.g. \n or \r) + + // GPIB Bus Management + CMD_TRG, // Group Execute Trigger (GET) + CMD_CLR, // Selected Device Clear (SDC) + CMD_DCL, // Device Clear (Universal - clears all) + CMD_SPOLL, // Serial Poll + CMD_SRQ, // Service Request check + CMD_STAT, // Status check + + // Lower Level Bus Control + CMD_LOC, // Go To Local (Release remote control) + CMD_GTL, // Go To Local (GPIB command) + CMD_LLO, // Local Lockout (Disable front panel) + CMD_REN, // Remote Enable line + CMD_IFC, // Interface Clear (Bus Reset) + + // HP3478A features + CMD_DISP, // Write on display + CMD_MATH, // MIN/MAX/AVG + CMD_ENV, // Temp/Hum sensor + CMD_CONT, // Continuity mode + CMD_TEMP, // Temperature mode + CMD_REL, // Relative mode + CMD_XOHM, // Extended Ohm range + CMD_DBM, // dBm + CMD_DIODE, // Diode test + CMD_NORM // Normal functionality + } cmd_id_t; typedef struct { @@ -130,41 +149,63 @@ typedef struct { } cmd_entry_t; static const cmd_entry_t COMMAND_TABLE[] = { - // common - {"read", CMD_READ}, - {"write", CMD_WRITE}, - {"addr", CMD_ADDR}, - {"trg", CMD_TRG}, - {"auto", CMD_AUTO}, - {"stat", CMD_STAT}, - // feats - {"cont", CMD_CONT}, - {"temp", CMD_TEMP}, - {"rel", CMD_REL}, - {"xohm", CMD_XOHM}, - {"dbm", CMD_DBM}, - {"diode", CMD_DIODE}, - {"math", CMD_MATH}, - {"norm", CMD_NORM}, - {"disp", CMD_DISP}, - {"env", CMD_ENV}, - // GPIB mgmt - {"read_tmo_ms", CMD_TIMEOUT}, - {"tmo", CMD_TIMEOUT}, - {"clr", CMD_CLR}, - {"dcl", CMD_DCL}, - {"spoll", CMD_SPOLL}, - {"loc", CMD_LOC}, - {"gtl", CMD_GTL}, - {"llo", CMD_LLO}, - {"ren", CMD_REN}, - {"ifc", CMD_IFC}, - {"rst", CMD_RST}, + // Controller config {"ver", CMD_VER}, {"help", CMD_HELP}, {"?", CMD_HELP}, + {"savecfg", CMD_SAVECFG}, + {"rst", CMD_RST}, + {"addr", CMD_ADDR}, // Set target GPIB address + {"mode", CMD_MODE}, // 0=Device, 1=Controller + {"tmo", CMD_TIMEOUT}, // Set read timeout + {"read_tmo_ms", CMD_TIMEOUT}, + + // Protocol/formatting + {"auto", CMD_AUTO}, // Auto-read data after write + {"eoi", CMD_EOI}, // Enable/Disable EOI assert + {"eos", CMD_EOS}, + {"eor", CMD_EOR}, + {"eot_enable", CMD_EOT_ENABLE}, + {"eot_char", CMD_EOT_CHAR}, + + // Common + {"read", CMD_READ}, + {"write", CMD_WRITE}, + {"trg", CMD_TRG}, // Trigger instrument (GET) + {"clr", CMD_CLR}, // Clear device buffer (SDC) + {"stat", CMD_STAT}, // Controller Status + + // GPIB Bus Lines/States + {"dcl", CMD_DCL}, // "Device Clear" (Resets state of ALL devices) + {"spoll", CMD_SPOLL}, // "Serial Poll" (Read status byte) + {"srq", CMD_SRQ}, // Check "Service Request" line status + {"loc", CMD_LOC}, // Return to local (front panel) control + {"gtl", CMD_GTL}, // "Go To Local" message + {"llo", CMD_LLO}, // "Local Lockout" (Disables front panel buttons) + {"ren", CMD_REN}, // "Remote Enable" + {"ifc", CMD_IFC}, // "Interface Clear" (Hard bus reset) + + // HP3478A + {"cont", CMD_CONT}, // Continuity Test + {"diode", CMD_DIODE}, // Diode Test + {"xohm", CMD_XOHM}, // Extended ohm + {"dbm", CMD_DBM}, // dBm + {"temp", CMD_TEMP}, // Temperature + {"rel", CMD_REL}, // Relative (Delta) measurement + {"math", CMD_MATH}, // MIN/MAX/AVG + {"disp", CMD_DISP}, // Set Display Text + {"env", CMD_ENV}, // Temp/Hum sensor + {"norm", CMD_NORM}, // Reset to Normal/DC Volts + {NULL, CMD_UNKNOWN}}; +typedef enum { + AUTO_OFF = 0, + AUTO_ON = 1, // read after every write + AUTO_QUERY = 2, // read only if command ends in ? + // AUTO_CONT = 3 // continuous read (triggered by ++read) +} auto_mode_t; + typedef enum { MODE_PASSTHROUGH = 0, // Standard USB-GPIB bridge MODE_MENU, // User is cycling options on DMM display @@ -320,16 +361,24 @@ typedef struct { uint8_t usb_online : 1; uint8_t usb_raw_prev : 1; uint8_t env_sensor_present : 1; - uint8_t auto_read : 1; uint8_t dmm_online : 1; uint8_t has_saved_state : 1; uint8_t beep_active : 1; - uint8_t reserved : 1; + uint8_t auto_read : 2; + + // Protocol Config + uint8_t eoi_assert : 1; // ++eoi 0|1 + uint8_t eos_mode : 2; // ++eos 0-3 + uint8_t eot_enable : 1; // ++eot_enable 0|1 + uint8_t eor_mode : 3; // ++eor 0-7 + uint8_t reserved : 1; // padding + + uint32_t gpib_timeout_ms; // Addresses uint8_t target_addr; uint8_t dmm_addr; - uint32_t gpib_timeout_ms; + uint8_t eot_char; // Timers uint32_t usb_ts; @@ -362,11 +411,6 @@ typedef struct { mode_data_t data; } app_state_t; -static app_state_t app = {.dmm_addr = DEFAULT_DMM_ADDR, - .target_addr = DEFAULT_DMM_ADDR, - .poll_interval = POLL_INTERVAL_MS, - .gpib_timeout_ms = DEFAULT_GPIB_TIMEOUT_MS}; - typedef union { // command processing struct { @@ -388,13 +432,45 @@ typedef union { uint8_t raw[256]; } app_scratchpad_t; +static app_state_t app = { + // config defaults + .target_addr = DEFAULT_DMM_ADDR, + .dmm_addr = DEFAULT_DMM_ADDR, + .gpib_timeout_ms = DEFAULT_GPIB_TIMEOUT_MS, + .poll_interval = POLL_INTERVAL_MS, + + // prologix std defaults + .auto_read = 0, // ++auto 0 (off) + .eoi_assert = 1, // ++eoi 1 (assert EOI on write end) + .eos_mode = 0, // ++eos 0 (CR+LF appended to writes) + .eor_mode = 0, // ++eor 0 (terminates read on CR+LF) + .eot_enable = 0, // ++eot_enable 0 (off) + .eot_char = 0, // ++eot_char 0 + + .current_mode = MODE_PASSTHROUGH, + + .usb_online = 0, + .dmm_online = 0, + .has_saved_state = 0, + .beep_active = 0}; + static app_scratchpad_t scratch; -// USB Ring Buffer +// GPIB session +typedef enum { SESSION_WRITE, SESSION_READ } session_mode_t; + +// LUT for GPIB writes +static uint32_t gpib_write_lut[256]; + +// USB Ring Buffers #define USB_RX_BUF_SIZE 512 +#define USB_TX_BUF_SIZE 1024 volatile uint8_t usb_rx_buffer[USB_RX_BUF_SIZE]; -volatile uint16_t usb_rx_head = 0; -volatile uint16_t usb_rx_tail = 0; +// volatile uint8_t usb_tx_buffer[USB_TX_BUF_SIZE]; +volatile int usb_rx_head = 0; +// volatile int usb_tx_head = 0; +volatile int usb_rx_tail = 0; +// volatile int usb_tx_tail = 0; static uint8_t cdc_line_coding[7] = {0x00, 0xC2, 0x01, 0x00, 0x00, 0x00, 0x08}; @@ -758,6 +834,16 @@ void build_restoration_string(char* buffer, const dmm_decoded_state_t* state) { strcat(buffer, state->cmd_az); } +static int is_query(const char* cmd) { + if (!cmd || !*cmd) return 0; + size_t len = strlen(cmd); + while (len > 0 && isspace((unsigned char)cmd[len - 1])) { + len--; + } + if (len > 0 && cmd[len - 1] == '?') return 1; + return 0; +} + #ifdef GPIB_DEBUG static void gpib_dump_state(const char* context) { uint8_t d = 0; @@ -783,6 +869,48 @@ static void gpib_dump_state(const char* context) { #endif // low level +inline static void gpib_calc_lut(void) { + for (int i = 0; i < 256; i++) { + uint32_t bshr = 0; + // bits 0-7 mappings + if (i & 0x01) + bshr |= (1U << (16 + PIN_DIO1)); + else + bshr |= (1U << PIN_DIO1); + if (i & 0x02) + bshr |= (1U << (16 + PIN_DIO2)); + else + bshr |= (1U << PIN_DIO2); + if (i & 0x04) + bshr |= (1U << (16 + PIN_DIO3)); + else + bshr |= (1U << PIN_DIO3); + if (i & 0x08) + bshr |= (1U << (16 + PIN_DIO4)); + else + bshr |= (1U << PIN_DIO4); + if (i & 0x10) + bshr |= (1U << (16 + PIN_DIO5)); + else + bshr |= (1U << PIN_DIO5); + if (i & 0x20) + bshr |= (1U << (16 + PIN_DIO6)); + else + bshr |= (1U << PIN_DIO6); + if (i & 0x40) + bshr |= (1U << (16 + PIN_DIO7)); + else + bshr |= (1U << PIN_DIO7); + if (i & 0x80) + bshr |= (1U << (16 + PIN_DIO8)); + else + bshr |= (1U << PIN_DIO8); + + gpib_write_lut[i] = bshr; + } +} + +// void gpib_write_data(uint8_t b) { GPIOB->BSHR = gpib_write_lut[b]; } void gpib_write_data(uint8_t b) { uint32_t bshr = 0; @@ -823,6 +951,21 @@ void gpib_write_data(uint8_t b) { GPIOB->BSHR = bshr; } +// uint8_t gpib_read_data(void) { +// // read all 16 pins, invert (gpib is active low) +// uint32_t r = ~(GPIOB->INDR); +// uint8_t b = 0; + +// // parallel extraction +// b |= (r >> SHIFT_DIO1) & MASK_GROUP_A; // handles D1 & D6 +// b |= (r >> SHIFT_DIO2) & MASK_GROUP_B; // handles D2 & D7 +// b |= (r >> SHIFT_DIO3) & (1 << 2); +// b |= (r >> SHIFT_DIO4) & (1 << 3); +// b |= (r >> SHIFT_DIO5) & (1 << 4); +// b |= (r >> SHIFT_DIO8) & (1 << 7); + +// return b; +// } uint8_t gpib_read_data(void) { uint32_t r = ~(GPIOB->INDR); // active low uint8_t b = 0; @@ -838,7 +981,8 @@ uint8_t gpib_read_data(void) { return b; } -static int gpib_wait_pin(int pin, int expected_state) { + +inline static int gpib_wait_pin(int pin, int expected_state) { uint32_t start = millis(); while (GPIB_READ(pin) != expected_state) { @@ -911,7 +1055,7 @@ int gpib_read_byte(uint8_t* data, int* eoi_asserted) { // float data lines gpib_write_data(0x00); - // Delay_Us(2); + Delay_Us(10); // signal ready for data GPIB_RELEASE(PIN_NRFD); @@ -946,10 +1090,9 @@ int gpib_read_byte(uint8_t* data, int* eoi_asserted) { 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) { + printf("gpib_start_session\n"); GPIB_ASSERT(PIN_ATN); Delay_Us(20); @@ -967,6 +1110,12 @@ int gpib_start_session(uint8_t target_addr, session_mode_t mode) { if (gpib_write_byte(GPIB_CMD_TAD | talker, 0) < 0) goto err; if (gpib_write_byte(GPIB_CMD_LAD | listener, 0) < 0) goto err; + if (mode == SESSION_READ) { + gpib_write_data(0x00); // Float Data + GPIB_ASSERT(PIN_NDAC); // Drive LOW (Not Accepted) + GPIB_ASSERT(PIN_NRFD); // Drive LOW (Not Ready) + } + Delay_Us(10); GPIB_RELEASE(PIN_ATN); // Switch to Data Mode Delay_Us(10); @@ -997,7 +1146,7 @@ void gpib_remote_enable(int enable) { } // Check SRQ Line (Active Low) -int gpib_check_srq(void) { return !GPIB_READ(PIN_SRQ); } +static inline int gpib_check_srq(void) { return !GPIB_READ(PIN_SRQ); } // Universal Commands (Affects All Devices) @@ -1005,7 +1154,7 @@ int gpib_check_srq(void) { return !GPIB_READ(PIN_SRQ); } // Resets logic of ALL devices on the bus int gpib_universal_clear(void) { GPIB_ASSERT(PIN_ATN); - Delay_Us(20); + Delay_Us(1); if (gpib_write_byte(GPIB_CMD_DCL, 0) < 0) { GPIB_RELEASE(PIN_ATN); @@ -1021,7 +1170,7 @@ int gpib_universal_clear(void) { // Disables front panel "Local" buttons on all devices int gpib_local_lockout(void) { GPIB_ASSERT(PIN_ATN); - Delay_Us(20); + Delay_Us(1); // LLO is universal, no addressing needed if (gpib_write_byte(GPIB_CMD_LLO, 0) < 0) { @@ -1029,7 +1178,6 @@ int gpib_local_lockout(void) { return -1; } - Delay_Us(10); GPIB_RELEASE(PIN_ATN); return 0; } @@ -1040,7 +1188,7 @@ int gpib_local_lockout(void) { // Resets logic of ONLY the targeted device int gpib_device_clear(uint8_t addr) { GPIB_ASSERT(PIN_ATN); - Delay_Us(20); + Delay_Us(1); if (gpib_write_byte(GPIB_CMD_UNL, 0) < 0) goto err; if (gpib_write_byte(GPIB_CMD_LAD | addr, 0) < 0) goto err; @@ -1057,7 +1205,7 @@ err: // Triggers the device to take a measurement int gpib_trigger(uint8_t addr) { GPIB_ASSERT(PIN_ATN); - Delay_Us(20); + Delay_Us(1); if (gpib_write_byte(GPIB_CMD_UNL, 0) < 0) goto err; if (gpib_write_byte(GPIB_CMD_LAD | addr, 0) < 0) goto err; @@ -1075,7 +1223,7 @@ err: // (Keeps REN asserted for other devices on the bus) int gpib_go_to_local(uint8_t addr) { GPIB_ASSERT(PIN_ATN); - Delay_Us(20); + Delay_Us(1); if (gpib_write_byte(GPIB_CMD_UNL, 0) < 0) goto err; if (gpib_write_byte(GPIB_CMD_LAD | addr, 0) < 0) goto err; @@ -1091,6 +1239,7 @@ err: // Serial Poll // Reads the Status Byte (STB) from the device int gpib_serial_poll(uint8_t addr, uint8_t* status) { + printf("gpib_serial_poll :: entry\n"); GPIB_ASSERT(PIN_ATN); Delay_Us(20); @@ -1100,6 +1249,13 @@ int gpib_serial_poll(uint8_t addr, uint8_t* status) { 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; + printf("gpib_serial_poll :: gpib_write_data(0x00); \n"); + + gpib_write_data(0x00); // Float data lines + GPIB_ASSERT(PIN_NDAC); // Busy / Not Accepted + GPIB_ASSERT(PIN_NRFD); // Busy / Not Ready + Delay_Us(5); + // drop ATN to read data GPIB_RELEASE(PIN_ATN); Delay_Us(5); @@ -1107,6 +1263,8 @@ int gpib_serial_poll(uint8_t addr, uint8_t* status) { int eoi; int res = gpib_read_byte(status, &eoi); + printf("gpib_serial_poll :: after handshake (res: %d)\n", res); + // handshake complete, clean up lines GPIB_RELEASE(PIN_NRFD); GPIB_RELEASE(PIN_NDAC); @@ -1132,33 +1290,34 @@ 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; - } - } + // EOS mode determines what we ADD to the string + const char* suffix = ""; + if (app.eos_mode == 0) + suffix = "\r\n"; // CRLF + else if (app.eos_mode == 1) + suffix = "\r"; // CR + else if (app.eos_mode == 2) + suffix = "\n"; // LF + // EOS 3 = none - // tag the last byte with EOI - int is_last = (i == len - 1) || (skip && i == len - 2); + int suffix_len = strlen(suffix); + int total_len = len + suffix_len; - if (gpib_write_byte(b, is_last) < 0) { - // error during write, try to clean up bus + for (int i = 0; i < total_len; i++) { + uint8_t b = (i < len) ? str[i] : suffix[i - len]; + + // assert EOI on last byte ONLY if ++eoi 1 (eoi_assert) is set + int is_last = (i == total_len - 1); + int trigger_eoi = (app.eoi_assert && is_last); + + if (gpib_write_byte(b, trigger_eoi) < 0) { + // error cleanup GPIB_ASSERT(PIN_ATN); gpib_write_byte(GPIB_CMD_UNL, 0); GPIB_RELEASE(PIN_ATN); return -1; } - - if (skip) i++; } // normal cleanup @@ -1175,14 +1334,57 @@ int gpib_receive(uint8_t addr, char* buf, int max_len) { int count = 0; int eoi = 0; uint8_t byte; + int effective_max = max_len - 2; - while (count < max_len - 1) { + while (count < effective_max) { if (gpib_read_byte(&byte, &eoi) < 0) break; buf[count++] = (char)byte; - // stop on EOI or LF - if (eoi || byte == '\n') break; + // hw EOI always stops the read immediately + if (eoi) break; + + int match = 0; + char c = (char)byte; + + // buf[count-1] is 'c'. buf[count-2] is prev. + char prev = (count >= 2) ? buf[count - 2] : 0; + char prev2 = (count >= 3) ? buf[count - 3] : 0; + + switch (app.eor_mode) { + case 0: // CR + LF + if (prev == '\r' && c == '\n') match = 1; + break; + case 1: // CR + if (c == '\r') match = 1; + break; + case 2: // LF + if (c == '\n') match = 1; + break; + case 3: // none? + match = 0; + break; + case 4: // LF + CR + if (prev == '\n' && c == '\r') match = 1; + break; + case 5: // ETX (0x03) + if (c == 0x03) match = 1; + break; + case 6: // CR + LF + ETX + if (prev2 == '\r' && prev == '\n' && c == 0x03) match = 1; + break; + case 7: // EOI signal only + match = 0; + break; + } + + if (match) break; } + + // append USB EOT char if enabled + if (app.eot_enable) { + buf[count++] = (char)app.eot_char; + } + buf[count] = 0; // null terminate // ensure listeners are open before asserting ATN @@ -1227,13 +1429,6 @@ int gpib_receive_binary(uint8_t addr, char* buf, int expected_len) { 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); @@ -1267,6 +1462,16 @@ void gpib_init(void) { funPinMode(PIN_DIO7, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD); funPinMode(PIN_DIO8, GPIO_Speed_50MHz | GPIO_CNF_OUT_OD); + // calculate BSHR for the DIO lines + gpib_calc_lut(); + // for (int i = 0; i < 256; i++) { + // gpib_write_lut[i] = + // CALC_PIN_BSHR(i, 0, PIN_DIO1) | CALC_PIN_BSHR(i, 1, PIN_DIO2) | + // CALC_PIN_BSHR(i, 2, PIN_DIO3) | CALC_PIN_BSHR(i, 3, PIN_DIO4) | + // CALC_PIN_BSHR(i, 4, PIN_DIO5) | CALC_PIN_BSHR(i, 5, PIN_DIO6) | + // CALC_PIN_BSHR(i, 6, PIN_DIO7) | CALC_PIN_BSHR(i, 7, PIN_DIO8); + // } + // float data lines (release to HIGH) gpib_write_data(0x00); @@ -1309,7 +1514,7 @@ void buzzer_set(uint32_t freq_hz) { return; } - uint16_t reload_val = (uint16_t)(1000000UL / (2 * freq_hz)); + int reload_val = (int)(1000000UL / (2 * freq_hz)); TIM2->ATRLR = reload_val; TIM2->CNT = 0; // reset phase only on CHANGE buzzer_active = 1; @@ -1319,7 +1524,7 @@ void TIM2_IRQHandler(void) __attribute__((interrupt)); void TIM2_IRQHandler(void) { if (TIM2->INTFR & TIM_UIF) { // clr the flag - TIM2->INTFR = (uint16_t)~TIM_UIF; + TIM2->INTFR = (int)~TIM_UIF; if (buzzer_active) { // Toggle PC13 @@ -1348,7 +1553,7 @@ void tone(unsigned int freq_hz, unsigned int duration_ms) { buzzer_set(0); } -void tone_nb(uint16_t freq, uint32_t duration_ms) { +void tone_nb(int freq, uint32_t duration_ms) { buzzer_set(freq); app.beep_start_ts = millis(); app.beep_duration = duration_ms; @@ -1369,10 +1574,8 @@ void play_startup_tune() { 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 0x21: // CDC_GET_LINE_CODING ctx->pCtrlPayloadPtr = cdc_line_coding; return 7; case 0x22: // CDC_SET_CONTROL_LINE_STATE @@ -1395,7 +1598,7 @@ void HandleDataOut(struct _USBState* ctx, int endp, uint8_t* data, int len) { } 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; + int 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; @@ -1404,6 +1607,49 @@ void HandleDataOut(struct _USBState* ctx, int endp, uint8_t* data, int len) { } } +// void usb_process_tx(void) { +// if (!app.usb_online) return; + +// // check HW Busy (endpoint 3) +// if (USBFSCTX.USBFS_Endp_Busy[3]) return; +// // check buffer empty +// if (usb_tx_head == usb_tx_tail) return; + +// // calc contiguous chunk size +// int len; +// if (usb_tx_head > usb_tx_tail) { +// len = usb_tx_head - usb_tx_tail; +// } else { +// len = USB_TX_BUF_SIZE - usb_tx_tail; +// } + +// // cap at USB pkt size +// if (len > 64) len = 64; + +// // send to hw, with memcpy +// USBFS_SendEndpointNEW(3, (uint8_t*)&usb_tx_buffer[usb_tx_tail], len, 1); +// // advance tail +// usb_tx_tail = (usb_tx_tail + len) % USB_TX_BUF_SIZE; +// } + +// void usb_send_text(const char* str) { +// if (!app.usb_online) { +// // if offline, just reset buffer +// usb_tx_head = usb_tx_tail = 0; +// return; +// } + +// while (*str) { +// int next = (usb_tx_head + 1) % USB_TX_BUF_SIZE; +// while (next == usb_tx_tail) { +// return; // we just drop it +// } + +// usb_tx_buffer[usb_tx_head] = *str++; +// usb_tx_head = next; +// } +// } + static void usb_send_text(const char* str) { if (!app.usb_online) return; @@ -1438,7 +1684,7 @@ static void usb_send_text(const char* str) { 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 temp_tail = usb_rx_tail; int len = 0; int found_newline = 0; @@ -2351,53 +2597,61 @@ void app_loop(void) { static void cmd_help(void) { static const char* help_text = - "\r\n=== HP3478A Internal USB-GPIB v" FW_VERSION - " ===\r\n" + "\r\nHP3478A Internal USB-GPIB " FW_VERSION "\r\n" - "Prologix-style Commands:\r\n" - " ++addr 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 Write data 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" + "[GPIB Setup]\r\n" + " ++addr <0-30> Target Address\r\n" + " ++auto <0-2> 0:Off, 1:Read-After-Write, 2:Query-Only\r\n" + " ++read_tmo_ms Timeout in ms\r\n" + " ++eoi <0|1> Assert hardware EOI on write end\r\n" + " ++eos <0-3> Write Term: 0:CRLF, 1:CR, 2:LF, 3:None\r\n" + " ++eor <0-7> Read Stop: 0:CRLF ... 7:EOI-Only\r\n" + " ++eot_enable Append extra char to read output\r\n" + " ++eot_char The char to append\r\n" + " ++savecfg Save current config\r\n" + " ++ver Firmware Version\r\n" + " ++rst System Reset\r\n" + "\r\n" + "[GPIB Bus Operations]\r\n" + " ++read Read from target\r\n" + " ++write Write to target\r\n" + " ++trg Device Trigger (GET)\r\n" + " ++clr Device Clear (SDC)\r\n" + " ++dcl Universal Device Clear (DCL)\r\n" + " ++ifc Interface Clear (Bus Reset)\r\n" + " ++spoll [addr] Serial Poll\r\n" + " ++srq Query SRQ Line (0=High/Idle, 1=Low/Active)\r\n" + " ++loc Local (Drop REN)\r\n" + " ++llo Local Lockout\r\n" + "\r\n" + "[Internal HP3478A Features]\r\n" " ++cont, ++temp, ++rel, ++xohm, ++dbm\r\n" - " ++diode, ++math (Min/Max/Avg)\r\n" - " ++norm Exit Special Mode\r\n" - " ++disp 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 '? ' for this help.\r\n"; + " ++diode, ++math, ++norm\r\n" + " ++env [temp|hum] Internal AHT20 Sensor\r\n" + " ++disp Write text to LCD\r\n" + "\r\n"; usb_send_text(help_text); } static void cmd_status(void) { + int srq_is_active = gpib_check_srq(); + snprintf(scratch.cmd.fmt_buf, sizeof(scratch.cmd.fmt_buf), - "Stat:\r\n" - " Target Addr: %d\r\n" - " Internal DMM: %d\r\n" - " Timeout: %lu ms\r\n" - " Auto Read: %s\r\n" - " Current Mode: %d\r\n" - " FW: " FW_VERSION "\r\n", - app.target_addr, app.dmm_addr, app.gpib_timeout_ms, - app.auto_read ? "ON" : "OFF", app.current_mode); + "Config:\r\n" + " Addr: %d\r\n" + " Timeout: %lu ms\r\n" + " Auto Read: %d\r\n" + " EOS Mode: %d (0:CRLF 1:CR 2:LF 3:N)\r\n" + " EOR Mode: %d\r\n" + " EOI Assert: %d\r\n" + " EOT: %s (Char %d)\r\n" + " SRQ Line: %d %s\r\n", + app.target_addr, app.gpib_timeout_ms, app.auto_read, app.eos_mode, + app.eor_mode, app.eoi_assert, app.eot_enable ? "ON" : "OFF", + app.eot_char, srq_is_active, srq_is_active ? "(Active)" : "(Idle)"); + usb_send_text(scratch.cmd.fmt_buf); } @@ -2408,16 +2662,19 @@ static void process_command(void) { char* p_cmd = skip_spaces(scratch.cmd.line_buf); int is_cpp_cmd = (strncmp(p_cmd, "++", 2) == 0); - int is_query = (strchr(p_cmd, '?') != NULL); if (is_cpp_cmd) { - p_cmd += 2; // skip "++" - - // 'p_args' will point to the first non-space char after the command word + p_cmd += 2; 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 + while (*p_args && !isspace((unsigned char)*p_args)) { + p_args++; + } + // if we found a space/separator, split + if (*p_args) { + *p_args = 0; + p_args++; + p_args = skip_spaces(p_args); + } cmd_id_t cmd_id = parse_command_id(p_cmd); @@ -2428,7 +2685,6 @@ static void process_command(void) { 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 { @@ -2441,10 +2697,17 @@ static void process_command(void) { case 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"); + int val = atoi(p_args); + // Allow 0, 1, 2. Ignore 3 since we don't support it + // it's possible but.. effort + if (val >= 0 && val <= 2) { + app.auto_read = val; + } + } else { + snprintf(scratch.cmd.fmt_buf, sizeof(scratch.cmd.fmt_buf), "%d\r\n", + app.auto_read); + usb_send_text(scratch.cmd.fmt_buf); + } break; case CMD_VER: @@ -2460,13 +2723,86 @@ static void process_command(void) { NVIC_SystemReset(); break; + case CMD_EOI: + if (*p_args) { + app.eoi_assert = (atoi(p_args) ? 1 : 0); + } else { + usb_send_text(app.eoi_assert ? "1\r\n" : "0\r\n"); + } + break; + + case CMD_EOS: + if (*p_args) { + int val = atoi(p_args); + if (val >= 0 && val <= 3) app.eos_mode = val; + } else { + snprintf(scratch.cmd.fmt_buf, sizeof(scratch.cmd.fmt_buf), "%d\r\n", + app.eos_mode); + usb_send_text(scratch.cmd.fmt_buf); + } + break; + + case CMD_EOT_ENABLE: + if (*p_args) { + app.eot_enable = (atoi(p_args) ? 1 : 0); + } else { + usb_send_text(app.eot_enable ? "1\r\n" : "0\r\n"); + } + break; + + case CMD_EOT_CHAR: + if (*p_args) { + app.eot_char = (uint8_t)atoi(p_args); + } else { + snprintf(scratch.cmd.fmt_buf, sizeof(scratch.cmd.fmt_buf), "%d\r\n", + app.eot_char); + usb_send_text(scratch.cmd.fmt_buf); + } + break; + + case CMD_EOR: + if (*p_args) { + int val = atoi(p_args); + if (val >= 0 && val <= 7) { + app.eor_mode = val; + } + } else { + snprintf(scratch.cmd.fmt_buf, sizeof(scratch.cmd.fmt_buf), "%d\r\n", + app.eor_mode); + usb_send_text(scratch.cmd.fmt_buf); + } + break; + + case CMD_MODE: + if (*p_args) { + // int val = atoi(p_args); + // if (val == 1) { + // // we are always controller + // } else { + // usb_send_text("ERR: dev mode unsupp\r\n"); + // } + } else { + // current mode -> 1 + usb_send_text("1\r\n"); + } + break; + + case CMD_SAVECFG: + // usb_send_text("OK\r\n"); + break; + + case CMD_SRQ: + usb_send_text(gpib_check_srq() ? "1\r\n" : "0\r\n"); + break; + // data case CMD_READ: goto do_read_operation; case CMD_WRITE: if (*p_args) { gpib_send(app.target_addr, p_args); - if (app.auto_read) goto do_read_operation; + // if auto is 1 (Read-after-write), read now + if (app.auto_read == 1) goto do_read_operation; } break; @@ -2477,7 +2813,6 @@ static void process_command(void) { // min 1ms, max 60s if (val > 0 && val < 60000) { app.gpib_timeout_ms = val; - usb_send_text("OK\r\n"); } else { usb_send_text("ERR: Range 1-60000\r\n"); } @@ -2489,37 +2824,29 @@ static void process_command(void) { break; case CMD_TRG: gpib_trigger(app.target_addr); - usb_send_text("OK\r\n"); break; case CMD_CLR: gpib_device_clear(app.target_addr); - usb_send_text("OK\r\n"); break; case CMD_DCL: gpib_universal_clear(); - usb_send_text("OK\r\n"); break; case CMD_IFC: gpib_interface_clear(); - usb_send_text("OK\r\n"); break; case CMD_LLO: gpib_local_lockout(); - usb_send_text("OK\r\n"); break; case CMD_GTL: gpib_go_to_local(app.target_addr); - usb_send_text("OK\r\n"); break; case CMD_LOC: gpib_remote_enable(0); - usb_send_text("OK\r\n"); break; case CMD_REN: if (*p_args) { gpib_remote_enable(atoi(p_args)); - usb_send_text("OK\r\n"); } else usb_send_text("ERR: Usage ++ren 1|0\r\n"); break; @@ -2532,7 +2859,7 @@ static void process_command(void) { stb); usb_send_text(scratch.cmd.fmt_buf); } else - usb_send_text("ERR: Bus\r\n"); + usb_send_text("ERR: Poll TMO\r\n"); break; } @@ -2636,7 +2963,14 @@ static void process_command(void) { usb_send_text("ERR: Send Fail\r\n"); return; } - if (is_query || app.auto_read) goto do_read_operation; + + // 2. Auto-Read Logic + // If Auto == 1: Always Read + // If Auto == 2: Read only if command ends with '?' + if (app.auto_read == 1 || (app.auto_read == 2 && is_query(p_cmd))) { + goto do_read_operation; + } + return; do_read_operation: { @@ -2644,8 +2978,9 @@ do_read_operation: { sizeof(scratch.io.raw_data)); if (len > 0) { usb_send_text(scratch.io.raw_data); - } else if (is_cpp_cmd || is_query) { - usb_send_text("ERR: Read Timeout\r\n"); + } else { + // 'no-data' state or EOI + // maybe send nothing or a newline? } } } @@ -2670,19 +3005,20 @@ int main() { USBFSSetup(); // usb_debug = 1; - play_startup_tune(); + // play_startup_tune(); // app state - app.current_mode = MODE_PASSTHROUGH; - app.usb_online = 0; app.usb_raw_prev = USB_HW_IS_ACTIVE(); - app.usb_ts = millis(); + // init timers + uint32_t now = millis(); + app.usb_ts = now; + app.ignore_input_start_ts = now - 2000; /* Allow input immediately */ app.last_poll_time = 0; - app.ignore_input_start_ts = millis() - 2000; - app.dmm_online = 0; + app.env_last_read = 0; while (1) { handle_usb_state(); + // usb_process_tx(); app_loop(); handle_env_sensor();