Compare commits
9 Commits
f9803a2803
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
3bf60570d1
|
|||
|
fb6c3cbb7b
|
|||
|
329a648f9c
|
|||
|
9fed882f50
|
|||
|
054bc38683
|
|||
|
ad833867d1
|
|||
|
fd177c0b4a
|
|||
|
4c6bbc58fd
|
|||
|
7e6195f967
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -112,7 +112,9 @@
|
||||
"math.h": "c",
|
||||
"cctype": "c",
|
||||
"stdlib.h": "c",
|
||||
"float.h": "c"
|
||||
"float.h": "c",
|
||||
"stdbool.h": "c",
|
||||
"initializer_list": "c"
|
||||
},
|
||||
"cmake.sourceDirectory": "/home/mira/src/embedded/ch32v208_sens/lwip"
|
||||
}
|
||||
137
README.md
137
README.md
@@ -0,0 +1,137 @@
|
||||
# HP3478A Internal USB-GPIB Extension
|
||||
|
||||
An internal extension board for the HP3478A Multimeter. USB-CDC-GPIB bridge (Prologix-style) and an internal feature controller.
|
||||
|
||||
Uses WCH CH32V203 (RISC-V) MCU.
|
||||
|
||||
It enumerates as a standard serial device (e.g., /dev/ttyACM0 or COMx).
|
||||
|
||||
PCB [here](https://git.ayau.me/mira/mhardware/src/branch/master/hp3478a-ch32-ext)
|
||||
|
||||
Photos in [img/](./img/) and the [demo video](./img/feats.mp4)
|
||||
|
||||
## Menu System
|
||||
|
||||
Extended features (Continuity, Temp, etc.) can be accessed using the SRQ button of HP3478A without a PC
|
||||
|
||||
1. **Enter Menu:** Press the Front Panel **SRQ**.
|
||||
2. **Nav:** Press the button again to cycle through modes (`M: REL` -> `M: TEMP` -> `M: DBM`...).
|
||||
3. **Selection (Hover):** Stop pressing the button.
|
||||
- The display will animate dots (`...`) after 500ms.
|
||||
- After ~2.5 seconds of inactivity, the selected mode is activated.
|
||||
4. **Sub-Menus:** Some modes (like Temp) have sub-menus for sensor type and 2W/4W mode. Same cycle logic to select these.
|
||||
5. **Exit:** Press the SRQ button to exit back to initial mode (one we entered menu from).
|
||||
|
||||
## Features
|
||||
|
||||
These can be triggered via the Menu or the serial commands below.
|
||||
|
||||
Relative and Statistics depend on the mode you enter the menu from, i.e. if you enter relative from DCV it saves your state and you get relative voltage, from 2W relative 2W etc.
|
||||
|
||||
```
|
||||
++help
|
||||
|
||||
HP3478A Internal USB-GPIB 1.2.0
|
||||
|
||||
[GPIB Setup]
|
||||
++addr <0-30> Target Address
|
||||
++auto <0-2> 0:Off, 1:Read-After-Write, 2:Query-Only
|
||||
++read_tmo_ms <t> Timeout in ms
|
||||
++eoi <0|1> Assert hardware EOI on write end
|
||||
++eos <0-3> Write Term: 0:CRLF, 1:CR, 2:LF, 3:None
|
||||
++eor <0-7> Read Stop: 0:CRLF ... 7:EOI-Only
|
||||
++eot_enable <B> Append extra char to read output
|
||||
++eot_char <dec> The char to append
|
||||
++dmm_loop <0|1> Toggle HP3478A loop, if it conflicts w/ GPIB bus
|
||||
|
||||
[System Configuration]
|
||||
++config List all configurable parameters
|
||||
++get <name> Get parameter value
|
||||
++set <name> <v> Set parameter value
|
||||
++savecfg Save config to flash
|
||||
++ver Firmware Version
|
||||
++rst System Reset
|
||||
|
||||
[GPIB Bus Operations]
|
||||
++read Read from target
|
||||
++write <data> Write to target
|
||||
++trg Device Trigger (GET)
|
||||
++clr Device Clear (SDC)
|
||||
++dcl Universal Device Clear (DCL)
|
||||
++ifc Interface Clear (Bus Reset)
|
||||
++spoll [addr] Serial Poll
|
||||
++srq Query SRQ Line (0=High/Idle, 1=Low/Active)
|
||||
++loc Local (Drop REN)
|
||||
++llo Local Lockout
|
||||
|
||||
[Internal HP3478A Features]
|
||||
++cont, ++hold, ++rel, ++xohm
|
||||
++dbm, ++diode, ++math, ++norm
|
||||
++temp <l1> <l2> Temperature sensor mode
|
||||
++env [temp|hum] Internal AHT20 Sensor
|
||||
++disp <text> Write text to LCD
|
||||
```
|
||||
|
||||
There's also a configuration which can be saved using `++savecfg` if you want to make it persist (it's saved to v203's "undocumented" flash).
|
||||
Also see [inc/config.h](./inc/config.h).
|
||||
|
||||
```
|
||||
++config
|
||||
my_addr: 0
|
||||
dmm_addr: 18
|
||||
target_addr: 18
|
||||
eot_char: 0
|
||||
eot_enable: 0
|
||||
eoi_assert: 1
|
||||
eos_mode: 0
|
||||
eor_mode: 0
|
||||
auto_read: 0
|
||||
gpib_timeout_ms: 1200
|
||||
poll_interval_ms: 100
|
||||
env_sensor_read_interval_ms: 1000
|
||||
dmm_recovery_delay_ms: 1000
|
||||
usb_debounce_connect_ms: 50
|
||||
usb_debounce_disconnect_ms: 200
|
||||
usb_timeout_target_ms: 5
|
||||
menu_dot_interval_ms: 500
|
||||
menu_commit_delay_ms: 2400
|
||||
menu_sublayer_delay_ms: 500
|
||||
menu_debounce_ms: 100
|
||||
menu_lockout_ms: 1000
|
||||
stats_cycle_time_ms: 3000
|
||||
cont_disp_update_ms: 150
|
||||
diode_stable_ms: 20
|
||||
buzzer_chirp_hz: 3000
|
||||
buzzer_chirp_ms: 75
|
||||
buzzer_cont_hz: 2500
|
||||
rtd_a: 0.003908
|
||||
rtd_b: -0.000001
|
||||
rtd_r0: 1000.000000
|
||||
cjc_fallback_temp: 22.000000
|
||||
cjc_self_heating_offset: 4.000000
|
||||
type_k_scale: 24390.240000
|
||||
dbm_ref_z: 50.000000
|
||||
diode_th_short: 0.050000
|
||||
diode_th_open: 2.500000
|
||||
autohold_threshold: 1.000000
|
||||
autohold_change_req: 2.000000
|
||||
autohold_min_val: 0.050000
|
||||
cont_threshold_ohms: 10.000000
|
||||
autohold_stable_count: 3
|
||||
rel_stable_count: 3
|
||||
```
|
||||
|
||||
i.e.
|
||||
|
||||
```
|
||||
++set dbm_ref_z 60
|
||||
++savecfg
|
||||
```
|
||||
|
||||
Now your dBm feature will use 600 ohm ref instead of 50.
|
||||
|
||||
## Known Issues
|
||||
|
||||
1. I did NOT use the brain when routing the USBLC6. Either have to DNP the IC and jumper the data lines, or rely on the software hack to check USB `SUSPEND` to guess if the host is connected. I do the latter, works but meh. VBUS detection would be nicer.
|
||||
2. The buzzer is **not on a hardware TIM pin**. It's on a generic GPIO... This means it can't be hardware/timer PWMed. Instead, the firmware is firing a TIM IRQ thousands of times a second just to manually toggle the pin in the ISR..
|
||||
3. There is a microSD card footprint on the board. The fw does absolutely nothing with it. Data logging?
|
||||
|
||||
@@ -5,5 +5,10 @@
|
||||
// #define FUNCONF_SYSTEM_CORE_CLOCK 120000000
|
||||
// #define FUNCONF_PLL_MULTIPLIER 15
|
||||
#define FUNCONF_SYSTICK_USE_HCLK 1
|
||||
#define FUNCONF_USE_DEBUGPRINTF 0
|
||||
#define FUNCONF_USE_UARTPRINTF 0
|
||||
#define FUNCONF_USE_USBPRINTF 0
|
||||
#define FUNCONF_NULL_PRINTF 1
|
||||
// #define GPIB_DEBUG 1
|
||||
|
||||
#endif
|
||||
|
||||
BIN
img/ch32v203.jpg
Normal file
BIN
img/ch32v203.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 545 KiB |
BIN
img/connection_gpib.jpg
Normal file
BIN
img/connection_gpib.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 833 KiB |
BIN
img/connection_pcb.jpg
Normal file
BIN
img/connection_pcb.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 790 KiB |
BIN
img/feats.mp4
Normal file
BIN
img/feats.mp4
Normal file
Binary file not shown.
BIN
img/usb_b.jpg
Normal file
BIN
img/usb_b.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 667 KiB |
155
inc/config.h
Normal file
155
inc/config.h
Normal file
@@ -0,0 +1,155 @@
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define ERASE_PAGE_SIZE 64
|
||||
#define CONFIG_MAGIC 0x1234ABCD
|
||||
#define CONFIG_VERSION 1
|
||||
|
||||
#define MY_ADDR 0
|
||||
#define DEFAULT_DMM_ADDR 18 // the HP3478A addr
|
||||
|
||||
// Timing Config
|
||||
#define USB_DEBOUNCE_CONNECT_MS 50
|
||||
#define USB_DEBOUNCE_DISCONNECT_MS 200
|
||||
|
||||
#define ENV_SENSOR_READ_INTERVAL_MS 1000
|
||||
#define DEFAULT_GPIB_TIMEOUT_MS 1200
|
||||
|
||||
// This kind of ass but.. yeah, I don't want to access systick there
|
||||
// assume if the PC hasn't read after ~5ms it's stalled
|
||||
#define USB_TIMEOUT_TARGET_MS 5
|
||||
#define CYCLES_PER_LOOP 50
|
||||
#define USB_TIMEOUT_LIMIT \
|
||||
((FUNCONF_SYSTEM_CORE_CLOCK / 1000 * USB_TIMEOUT_TARGET_MS) / CYCLES_PER_LOOP)
|
||||
|
||||
// Menu Animation Timings
|
||||
#define MENU_DOT_INTERVAL_MS 500 // Speed of "..." addition
|
||||
#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_DEBOUNCE_MS 100 // Button press dead-time
|
||||
#define MENU_LOCKOUT_MS 200 // How long to ignore SRQ for after exiting menu
|
||||
|
||||
// Polling
|
||||
#define POLL_INTERVAL_MS 100 // 10Hz polling when in Passthrough
|
||||
#define DMM_RECOVERY_DELAY_MS 1000 // Backoff if DMM vanishes
|
||||
|
||||
// Diode sound
|
||||
#define DIODE_TH_SHORT 0.050 // Volts (below this = SHORT)
|
||||
#define DIODE_TH_OPEN 2.500 // Volts (above this = OPEN/OL)
|
||||
#define DIODE_STABLE_MS 20 // wait X ms for voltage to settle
|
||||
#define DIODE_CHIRP_MS 50
|
||||
|
||||
// PT1000 (DIN 43760 / IEC 751 Standard)
|
||||
#define RTD_A 3.9083e-3
|
||||
#define RTD_B -5.775e-7
|
||||
#define RTD_R0 1000.0
|
||||
|
||||
// Thermocouple
|
||||
#define CJC_FALLBACK_TEMP 22.0 // used if !app.env_sensor_present
|
||||
// this is just cursed but.. yeah, the PCB is near a transformer
|
||||
// ideally, the temp should be measured right at the binding posts..
|
||||
#define CJC_SELF_HEATING_OFFSET 4.0
|
||||
#define TYPE_K_SCALE 24390.24 // 1 / 41uV
|
||||
// dBm
|
||||
#define DBM_REF_Z 50.0
|
||||
// Stats
|
||||
#define STATS_CYCLE_TIME_MS 3000 // time per screen (Live -> Avg -> Min...)
|
||||
#define STATS_INIT_MIN_VAL 1.0e9
|
||||
#define STATS_INIT_MAX_VAL -1.0e9
|
||||
|
||||
// Autohold
|
||||
#define AUTOHOLD_THRESHOLD 1.0 // deviation % allowed for stability check
|
||||
#define AUTOHOLD_CHANGE_REQ 2.0 // % new value must differ by to trigger upd
|
||||
// XXX: ideally, we should have different sample requirements for diff ranges
|
||||
// we don't need as many counts at N5 range as at N3
|
||||
#define AUTOHOLD_STABLE_COUNT 3 // num samples required
|
||||
#define AUTOHOLD_MIN_VAL 0.05 // noise floor
|
||||
|
||||
#define CONT_DISP_UPDATE_MS 150 // display throttling
|
||||
#define CONT_THRESHOLD_OHMS 10.0 // continuity beep threshold
|
||||
|
||||
#define REL_STABLE_COUNT 3 // filter depth for Relative NULL
|
||||
|
||||
#define BUZZER_CHIRP_HZ 3000
|
||||
#define BUZZER_CHIRP_MS 75
|
||||
#define BUZZER_CONT_HZ 2500
|
||||
#define BUZZER_ONLINE_TUNE 0 // todo: add to conf
|
||||
|
||||
// #define LOCAL_COMMIT 1
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
|
||||
// addressing
|
||||
uint8_t my_addr; // controller
|
||||
uint8_t dmm_addr; // hp3478a
|
||||
uint8_t target_addr; // target
|
||||
|
||||
uint8_t eot_char;
|
||||
uint8_t eot_enable;
|
||||
uint8_t eoi_assert;
|
||||
uint8_t eos_mode;
|
||||
uint8_t eor_mode;
|
||||
uint8_t auto_read;
|
||||
uint8_t padding; // align
|
||||
|
||||
// timings (ms)
|
||||
uint32_t gpib_timeout_ms;
|
||||
uint32_t poll_interval_ms;
|
||||
uint32_t env_sensor_read_interval_ms;
|
||||
uint32_t dmm_recovery_delay_ms;
|
||||
uint32_t usb_debounce_connect_ms;
|
||||
uint32_t usb_debounce_disconnect_ms;
|
||||
uint32_t usb_timeout_target_ms;
|
||||
|
||||
// timings
|
||||
uint32_t menu_dot_interval_ms;
|
||||
uint32_t menu_commit_delay_ms;
|
||||
uint32_t menu_sublayer_delay_ms;
|
||||
uint32_t menu_debounce_ms;
|
||||
uint32_t menu_lockout_ms;
|
||||
uint32_t stats_cycle_time_ms;
|
||||
uint32_t cont_disp_update_ms;
|
||||
uint32_t diode_stable_ms;
|
||||
|
||||
// buzzer
|
||||
uint32_t buzzer_chirp_hz;
|
||||
uint32_t buzzer_chirp_ms;
|
||||
uint32_t buzzer_cont_hz;
|
||||
|
||||
// math & cal
|
||||
double rtd_a;
|
||||
double rtd_b;
|
||||
double rtd_r0;
|
||||
double cjc_fallback_temp;
|
||||
double cjc_self_heating_offset;
|
||||
double type_k_scale;
|
||||
double dbm_ref_z;
|
||||
double diode_th_short;
|
||||
double diode_th_open;
|
||||
|
||||
// thresholds
|
||||
double autohold_threshold;
|
||||
double autohold_change_req;
|
||||
double autohold_min_val;
|
||||
double cont_threshold_ohms;
|
||||
|
||||
// logic counts
|
||||
uint32_t autohold_stable_count;
|
||||
uint32_t rel_stable_count;
|
||||
|
||||
} fw_config_t;
|
||||
|
||||
typedef enum { CFG_TYPE_UINT8, CFG_TYPE_UINT32, CFG_TYPE_DOUBLE } cfg_type_t;
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
size_t offset;
|
||||
cfg_type_t type;
|
||||
} cfg_field_t;
|
||||
|
||||
#endif // CONFIG_H
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
#include "ch32fun.h"
|
||||
|
||||
// #define GPIB_DEBUG 1
|
||||
|
||||
// Control Lines (Active LOW)
|
||||
#define PIN_EOI PB3
|
||||
#define PIN_REN PB11
|
||||
@@ -147,6 +145,7 @@
|
||||
#define HP3478A_CMD_MASK_BTN_DATA "M21"
|
||||
// "M00" -> Clear Mask (Disable SRQ)
|
||||
#define HP3478A_CMD_MASK_CLEAR "M00"
|
||||
#define HP3478A_CMD_MASK_BTN_SYNERR "M24"
|
||||
|
||||
#define GPIB_ASSERT(pin) funDigitalWrite(pin, 0)
|
||||
#define GPIB_RELEASE(pin) funDigitalWrite(pin, 1)
|
||||
@@ -162,6 +161,32 @@
|
||||
#define PIN_DIO7 PB13
|
||||
#define PIN_DIO8 PB12
|
||||
|
||||
// Physical Pin mappings on PORT B
|
||||
#define PIN_POS_D1 9 // PB9
|
||||
#define PIN_POS_D2 8 // PB8
|
||||
#define PIN_POS_D3 5 // PB5
|
||||
#define PIN_POS_D4 4 // PB4
|
||||
#define PIN_POS_D5 15 // PB15
|
||||
#define PIN_POS_D6 14 // PB14
|
||||
#define PIN_POS_D7 13 // PB13
|
||||
#define PIN_POS_D8 12 // PB12
|
||||
|
||||
#define SHIFT_GRP_9 9 // D1 (PB9->0) & D6 (PB14->5)
|
||||
#define SHIFT_GRP_7 7 // D2 (PB8->1) & D7 (PB13->6)
|
||||
#define SHIFT_D3 3 // D3 (PB5->2)
|
||||
#define SHIFT_D4 1 // D4 (PB4->3)
|
||||
#define SHIFT_D5 11 // D5 (PB15->4)
|
||||
#define SHIFT_D8 5 // D8 (PB12->7)
|
||||
|
||||
// pins that share the same shift amount
|
||||
// Group A: shift right 9 (PB9->Bit0, PB14->Bit5)
|
||||
#define MASK_GRP_9 ((1 << 0) | (1 << 5))
|
||||
// Group B: shift right 7 (PB8->Bit1, PB13->Bit6)
|
||||
#define MASK_GRP_7 ((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)
|
||||
#define MASK_DIO3 (1U << 5)
|
||||
@@ -171,7 +196,4 @@
|
||||
#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};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,11 +9,7 @@
|
||||
#define SYSTICK_ONE_MILLISECOND ((uint32_t)FUNCONF_SYSTEM_CORE_CLOCK / 1000)
|
||||
#define SYSTICK_ONE_MICROSECOND ((uint32_t)FUNCONF_SYSTEM_CORE_CLOCK / 1000000)
|
||||
|
||||
extern volatile uint32_t systick_millis;
|
||||
|
||||
#define millis() (systick_millis)
|
||||
#define micros() (SysTick->CNT / SYSTICK_ONE_MICROSECOND)
|
||||
|
||||
void systick_init(void);
|
||||
|
||||
#endif // SYSTICK_H
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
|
||||
#define FUSB_USB_VID 0x1209
|
||||
#define FUSB_USB_PID 0x3478
|
||||
#define FUSB_USB_REV 0x0110
|
||||
#define FUSB_USB_REV 0x0120
|
||||
#define FUSB_STR_MANUFACTURER u"Open Source GPIB"
|
||||
#define FUSB_STR_PRODUCT u"HP3478A Internal Adapter"
|
||||
#define FUSB_STR_SERIAL u"3478A-USB-110"
|
||||
#define FUSB_STR_SERIAL u"3478A-USB-120"
|
||||
|
||||
//Taken from http://www.usbmadesimple.co.uk/ums_ms_desc_dev.htm
|
||||
static const uint8_t device_descriptor[] = {
|
||||
|
||||
21
systick.c
21
systick.c
@@ -1,21 +0,0 @@
|
||||
#include "systick.h"
|
||||
|
||||
volatile uint32_t systick_millis;
|
||||
|
||||
void systick_init(void) {
|
||||
SysTick->CTLR = 0x0000;
|
||||
SysTick->CMP = SysTick->CNT + SYSTICK_ONE_MILLISECOND;
|
||||
systick_millis = 0;
|
||||
SysTick->CTLR = SYSTICK_CTLR_STE | // Enable Counter
|
||||
SYSTICK_CTLR_STIE | // Enable Interrupts
|
||||
SYSTICK_CTLR_STCLK; // Set Clock Source to HCLK/1
|
||||
|
||||
NVIC_EnableIRQ(SysTick_IRQn);
|
||||
}
|
||||
|
||||
void SysTick_Handler(void) __attribute__((interrupt));
|
||||
void SysTick_Handler(void) {
|
||||
SysTick->CMP = SysTick->CNT + SYSTICK_ONE_MILLISECOND;
|
||||
SysTick->SR = 0;
|
||||
systick_millis++;
|
||||
}
|
||||
Reference in New Issue
Block a user