mirror of
https://gitlab.com/hyperglitch/jellyfish.git
synced 2025-12-30 07:37:10 +00:00
432 lines
15 KiB
C
432 lines
15 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2025 Igor Brkic <igor@hyperglitch.com>
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#include "jf_scpi.h"
|
|
#include "jf_common.h"
|
|
#include "main.h"
|
|
|
|
#include "cmsis_os.h"
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "aux_power.h"
|
|
#include "jf_measure.h"
|
|
#include "jf_source.h"
|
|
#include "jf_routing.h"
|
|
#include "../lib/scpiparser/libscpi/inc/scpi/scpi.h"
|
|
|
|
static char jf_scpi_input_buffer[JF_SCPI_IF_COUNT][JF_SCPI_INPUT_BUFFER_SIZE];
|
|
scpi_error_t jf_scpi_error_queue_data[JF_SCPI_IF_COUNT][JF_SCPI_ERROR_QUEUE_SIZE];
|
|
|
|
scpi_t scpi_context[JF_SCPI_IF_COUNT];
|
|
|
|
bool scpi_if_initialized[JF_SCPI_IF_COUNT] = {false};
|
|
|
|
bool (*jf_scpi_write_funcs[JF_SCPI_IF_COUNT])(const uint8_t *c, int len) = {NULL};
|
|
|
|
|
|
size_t jf_scpi_write(jf_scpi_interface_t interface, scpi_t * context, const char * data, size_t len) {
|
|
return jf_scpi_write_funcs[interface]((uint8_t*)data, len) ? len : 0;
|
|
}
|
|
size_t jf_scpi_uart_write(scpi_t * context, const char * data, size_t len) {
|
|
return jf_scpi_write(JF_SCPI_IF_UART, context, data, len);
|
|
}
|
|
size_t jf_scpi_usb_write(scpi_t * context, const char * data, size_t len) {
|
|
return jf_scpi_write(JF_SCPI_IF_USB, context, data, len);
|
|
}
|
|
|
|
scpi_result_t jf_scpi_flush(jf_scpi_interface_t interface, scpi_t * context) {
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_uart_flush(scpi_t * context) {
|
|
return jf_scpi_flush(JF_SCPI_IF_UART, context);
|
|
}
|
|
scpi_result_t jf_scpi_usb_flush(scpi_t * context) {
|
|
return jf_scpi_flush(JF_SCPI_IF_USB, context);
|
|
}
|
|
|
|
scpi_result_t jf_scpi_control(jf_scpi_interface_t interface, scpi_t * context, scpi_ctrl_name_t ctrl, scpi_reg_val_t val) {
|
|
char buf[32];
|
|
if (SCPI_CTRL_SRQ == ctrl) {
|
|
snprintf(buf, sizeof(buf), "**SRQ: 0x%X (%d)\r\n", val, val);
|
|
jf_scpi_write_funcs[interface]((uint8_t*)buf, strnlen(buf, sizeof(buf)));
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "**CTRL %02x: 0x%X (%d)\r\n", ctrl, val, val);
|
|
jf_scpi_write_funcs[interface]((uint8_t*)buf, strnlen(buf, sizeof(buf)));
|
|
}
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_uart_control(scpi_t * context, scpi_ctrl_name_t ctrl, scpi_reg_val_t val) {
|
|
return jf_scpi_control(JF_SCPI_IF_UART, context, ctrl, val);
|
|
}
|
|
scpi_result_t jf_scpi_usb_control(scpi_t * context, scpi_ctrl_name_t ctrl, scpi_reg_val_t val) {
|
|
return jf_scpi_control(JF_SCPI_IF_USB, context, ctrl, val);
|
|
}
|
|
|
|
scpi_result_t jf_scpi_reset(jf_scpi_interface_t interface, scpi_t * context) {
|
|
HAL_NVIC_SystemReset();
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_uart_reset(scpi_t * context) {
|
|
return jf_scpi_reset(JF_SCPI_IF_UART, context);
|
|
}
|
|
scpi_result_t jf_scpi_usb_reset(scpi_t * context) {
|
|
return jf_scpi_reset(JF_SCPI_IF_USB, context);
|
|
}
|
|
|
|
int jf_scpi_error(jf_scpi_interface_t interface, scpi_t * context, int_fast16_t err) {
|
|
char buf[100];
|
|
snprintf(buf, sizeof(buf), "**ERROR: %d, \"%s\"\r\n", (int16_t) err, SCPI_ErrorTranslate(err));
|
|
jf_scpi_write_funcs[interface]((uint8_t*)buf, strnlen(buf, sizeof(buf)));
|
|
return 0;
|
|
}
|
|
int jf_scpi_uart_error(scpi_t * context, int_fast16_t error) {
|
|
return jf_scpi_error(JF_SCPI_IF_UART, context, error);
|
|
}
|
|
int jf_scpi_usb_error(scpi_t * context, int_fast16_t error) {
|
|
return jf_scpi_error(JF_SCPI_IF_USB, context, error);
|
|
}
|
|
|
|
scpi_interface_t scpi_interface[] = {
|
|
{
|
|
.error = jf_scpi_uart_error,
|
|
.write = jf_scpi_uart_write,
|
|
.control = jf_scpi_uart_control,
|
|
.flush = jf_scpi_uart_flush,
|
|
.reset = jf_scpi_uart_reset,
|
|
},
|
|
{
|
|
.error = jf_scpi_usb_error,
|
|
.write = jf_scpi_usb_write,
|
|
.control = jf_scpi_usb_control,
|
|
.flush = jf_scpi_usb_flush,
|
|
.reset = jf_scpi_usb_reset,
|
|
},
|
|
};
|
|
|
|
|
|
scpi_result_t jf_scpi_source(scpi_t * context) {
|
|
bool val;
|
|
SCPI_ParamBool(context, &val, true);
|
|
jf_routing_output_en(val);
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_source_q(scpi_t * context) {
|
|
SCPI_ResultBool(context, jf_routing_output_status());
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_source_toggle(scpi_t * context) {
|
|
jf_routing_output_en(!jf_routing_output_status());
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_source_voltage_raw(scpi_t * context) {
|
|
int32_t scode;
|
|
SCPI_ParamInt32(context, &scode, true);
|
|
if (scode < 0 || scode>65535) {
|
|
return SCPI_RES_ERR;
|
|
}
|
|
const uint16_t code = (uint16_t)scode;
|
|
jf_source_set_voltage_raw(code, true);
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_source_voltage(scpi_t * context) {
|
|
int32_t voltage;
|
|
SCPI_ParamInt32(context, &voltage, true);
|
|
jf_source_set_voltage(voltage, true);
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_source_voltage_q(scpi_t * context) {
|
|
SCPI_ResultInt32(context, jf_source_get_voltage());
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_source_current(scpi_t * context) {
|
|
uint32_t current;
|
|
SCPI_ParamUInt32(context, ¤t, true);
|
|
jf_source_set_current_limit((int32_t)current, JF_CURRENT_LIMIT_BOTH, true);
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_source_current_q(scpi_t * context) {
|
|
int32_t l[2];
|
|
jf_source_get_current_limit(&l[0], &l[1]);
|
|
SCPI_ResultArrayInt32(context, l, 2, SCPI_FORMAT_NORMAL);
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_source_current_pos(scpi_t * context) {
|
|
uint32_t current;
|
|
SCPI_ParamUInt32(context, ¤t, true);
|
|
jf_source_set_current_limit((int32_t)current, JF_CURRENT_LIMIT_POSITIVE, true);
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_source_current_pos_q(scpi_t * context) {
|
|
int32_t l[2];
|
|
jf_source_get_current_limit(&l[0], &l[1]);
|
|
SCPI_ResultInt32(context, l[0]);
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_source_current_neg(scpi_t * context) {
|
|
uint32_t current;
|
|
SCPI_ParamUInt32(context, ¤t, true);
|
|
jf_source_set_current_limit((int32_t)current, JF_CURRENT_LIMIT_NEGATIVE, true);
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_source_current_neg_q(scpi_t * context) {
|
|
int32_t l[2];
|
|
jf_source_get_current_limit(&l[0], &l[1]);
|
|
SCPI_ResultInt32(context, l[1]);
|
|
return SCPI_RES_OK;
|
|
}
|
|
|
|
scpi_result_t jf_scpi_aux(scpi_t * context) {
|
|
bool val;
|
|
SCPI_ParamBool(context, &val, true);
|
|
jf_vaux_enable(val);
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_aux_q(scpi_t * context) {
|
|
SCPI_ResultBool(context, jf_vaux_is_enabled());
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_aux_toggle(scpi_t * context) {
|
|
jf_vaux_enable(!jf_vaux_is_enabled());
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_aux_voltage(scpi_t * context) {
|
|
int32_t voltage;
|
|
SCPI_ParamInt32(context, &voltage, true);
|
|
jf_vaux_set_voltage(voltage, true);
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_aux_voltage_q(scpi_t * context) {
|
|
SCPI_ResultInt32(context, jf_vaux_get_voltage());
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_aux_current(scpi_t * context) {
|
|
int32_t current;
|
|
SCPI_ParamInt32(context, ¤t, true);
|
|
if(current < 0) {
|
|
return SCPI_RES_ERR;
|
|
}
|
|
jf_vaux_set_current_limit(current);
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_aux_current_q(scpi_t * context) {
|
|
SCPI_ResultInt32(context, jf_vaux_get_current_limit());
|
|
return SCPI_RES_OK;
|
|
}
|
|
|
|
|
|
scpi_choice_def_t range_values[] = {
|
|
{"R0", JF_RANGE_3A},
|
|
{"R1", JF_RANGE_300MA},
|
|
{"R2", JF_RANGE_30MA},
|
|
{"R3", JF_RANGE_3MA},
|
|
{"R4", JF_RANGE_300UA},
|
|
{"R5", JF_RANGE_30UA},
|
|
{"AUTO", JF_RANGE_AUTO},
|
|
{"OFF", JF_RANGE_OFF},
|
|
SCPI_CHOICE_LIST_END
|
|
};
|
|
scpi_result_t jf_scpi_measure_range(scpi_t * context) {
|
|
int32_t range;
|
|
SCPI_ParamChoice(context, range_values, &range, true);
|
|
jf_measure_set_range(range, true);
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_measure_range_q(scpi_t * context) {
|
|
const jf_measure_data_t data = jf_measure_get_current_values();
|
|
const char * range_name;;
|
|
SCPI_ChoiceToName(range_values, data.range, &range_name);
|
|
SCPI_ResultText(context, range_name);
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_measure_isense1_q(scpi_t * context) {
|
|
const jf_measure_data_t data = jf_measure_get_current_values();
|
|
SCPI_ResultInt32(context, (int32_t)(data.Isense1*1000));
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_measure_isense2_q(scpi_t * context) {
|
|
const jf_measure_data_t data = jf_measure_get_current_values();
|
|
SCPI_ResultInt32(context, (int32_t)(data.Isense2*1000));
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_measure_vsense_q(scpi_t * context) {
|
|
const jf_measure_data_t data = jf_measure_get_current_values();
|
|
SCPI_ResultInt32(context, (int32_t)(data.Vsense*1000));
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_measure_ain_q(scpi_t * context) {
|
|
const jf_measure_data_t data = jf_measure_get_current_values();
|
|
SCPI_ResultInt32(context, (int32_t)(data.ain));
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_measure_gpio_q(scpi_t * context) {
|
|
const jf_measure_data_t data = jf_measure_get_current_values();
|
|
SCPI_ResultArrayUInt8(context, data.dig, 5, SCPI_FORMAT_LITTLEENDIAN);
|
|
return SCPI_RES_OK;
|
|
}
|
|
|
|
scpi_result_t jf_scpi_aux_measure_voltage(scpi_t * context) {
|
|
SCPI_ResultInt32(context, jf_vaux_measure_voltage());
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_aux_measure_current(scpi_t * context) {
|
|
SCPI_ResultInt32(context, jf_vaux_measure_current());
|
|
return SCPI_RES_OK;
|
|
}
|
|
|
|
scpi_result_t jf_scpi_route_iso_q(scpi_t * context) {
|
|
SCPI_ResultBool(context, jf_routing_iso_status());
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_route_iso(scpi_t * context) {
|
|
bool en;
|
|
SCPI_ParamBool(context, &en, true);
|
|
jf_routing_iso_en(en);
|
|
return SCPI_RES_OK;
|
|
}
|
|
|
|
scpi_result_t jf_scpi_route_extin_q(scpi_t * context) {
|
|
SCPI_ResultBool(context, jf_routing_extin_status());
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_route_extin(scpi_t * context) {
|
|
bool en;
|
|
SCPI_ParamBool(context, &en, true);
|
|
jf_routing_extin_en(en);
|
|
return SCPI_RES_OK;
|
|
}
|
|
|
|
scpi_result_t jf_scpi_route_output_q(scpi_t * context) {
|
|
SCPI_ResultBool(context, jf_routing_output_status());
|
|
return SCPI_RES_OK;
|
|
}
|
|
scpi_result_t jf_scpi_route_output(scpi_t * context) {
|
|
bool en;
|
|
SCPI_ParamBool(context, &en, true);
|
|
jf_routing_output_en(en);
|
|
return SCPI_RES_OK;
|
|
}
|
|
|
|
scpi_result_t jf_scpi_core_tst(scpi_t * context) {
|
|
// should return 0 if everything is ok
|
|
SCPI_ResultInt32(context, 0);
|
|
return SCPI_RES_OK;
|
|
}
|
|
|
|
scpi_result_t jf_scpi_get_id(scpi_t * context) {
|
|
uint32_t id[3];
|
|
jf_dev_get_uid(id);
|
|
char out[25];
|
|
snprintf(out, 25, "%08lx%08lx%08lx", id[0], id[1], id[2]);
|
|
SCPI_ResultCharacters(context, out, 24);
|
|
return SCPI_RES_OK;
|
|
}
|
|
|
|
scpi_command_t jf_scpi_commands[] = {
|
|
// IEEE Mandated Commands (SCPI std V1999.0 4.1.1)
|
|
{ .pattern = "*CLS", .callback = SCPI_CoreCls,},
|
|
{ .pattern = "*ESE", .callback = SCPI_CoreEse,},
|
|
{ .pattern = "*ESE?", .callback = SCPI_CoreEseQ,},
|
|
{ .pattern = "*ESR?", .callback = SCPI_CoreEsrQ,},
|
|
{ .pattern = "*IDN?", .callback = SCPI_CoreIdnQ,},
|
|
{ .pattern = "*OPC", .callback = SCPI_CoreOpc,},
|
|
{ .pattern = "*OPC?", .callback = SCPI_CoreOpcQ,},
|
|
{ .pattern = "*RST", .callback = SCPI_CoreRst,},
|
|
{ .pattern = "*SRE", .callback = SCPI_CoreSre,},
|
|
{ .pattern = "*SRE?", .callback = SCPI_CoreSreQ,},
|
|
{ .pattern = "*STB?", .callback = SCPI_CoreStbQ,},
|
|
{ .pattern = "*TST?", .callback = jf_scpi_core_tst,},
|
|
{ .pattern = "*WAI", .callback = SCPI_CoreWai,},
|
|
|
|
// Required SCPI commands (SCPI std V1999.0 4.2.1)
|
|
{.pattern = "SYSTem:ERRor[:NEXT]?", .callback = SCPI_SystemErrorNextQ,},
|
|
{.pattern = "SYSTem:ERRor:COUNt?", .callback = SCPI_SystemErrorCountQ,},
|
|
{.pattern = "SYSTem:VERSion?", .callback = SCPI_SystemVersionQ,},
|
|
|
|
{.pattern = "SYSTem:ID?", .callback = jf_scpi_get_id,},
|
|
|
|
{.pattern = "STATus:QUEStionable[:EVENt]?", .callback = SCPI_StatusQuestionableEventQ,},
|
|
{.pattern = "STATus:QUEStionable:ENABle", .callback = SCPI_StatusQuestionableEnable,},
|
|
{.pattern = "STATus:QUEStionable:ENABle?", .callback = SCPI_StatusQuestionableEnableQ,},
|
|
|
|
{.pattern = "STATus:PRESet", .callback = SCPI_StatusPreset,},
|
|
|
|
|
|
// source related commands
|
|
{.pattern = "SOURce", .callback = jf_scpi_source},
|
|
{.pattern = "SOURce?", .callback = jf_scpi_source_q},
|
|
{.pattern = "SOURce:TOGgle", .callback = jf_scpi_source_toggle},
|
|
{.pattern = "SOURce:VOLTage?", .callback = jf_scpi_source_voltage_q},
|
|
{.pattern = "SOURce:VOLTage", .callback = jf_scpi_source_voltage}, // set voltage in millivolts
|
|
{.pattern = "SOURce:DAC", .callback = jf_scpi_source_voltage_raw}, // set voltage as raw 16bit DAC code
|
|
{.pattern = "SOURce:CURRent", .callback = jf_scpi_source_current},
|
|
{.pattern = "SOURce:CURRent?", .callback = jf_scpi_source_current_q},
|
|
{.pattern = "SOURce:CURRent:POSitive", .callback = jf_scpi_source_current_pos},
|
|
{.pattern = "SOURce:CURRent:POSitive?", .callback = jf_scpi_source_current_pos_q},
|
|
{.pattern = "SOURce:CURRent:NEGative", .callback = jf_scpi_source_current_neg},
|
|
{.pattern = "SOURce:CURRent:NEGative?", .callback = jf_scpi_source_current_neg_q},
|
|
|
|
// aux power related commands
|
|
{.pattern = "AUX", .callback = jf_scpi_aux},
|
|
{.pattern = "AUX?", .callback = jf_scpi_aux_q},
|
|
{.pattern = "AUX:TOGgle", .callback = jf_scpi_aux_toggle},
|
|
{.pattern = "AUX:VOLTage", .callback = jf_scpi_aux_voltage},
|
|
{.pattern = "AUX:VOLTage?", .callback = jf_scpi_aux_voltage_q},
|
|
{.pattern = "AUX:CURRent", .callback = jf_scpi_aux_current},
|
|
{.pattern = "AUX:CURRent?", .callback = jf_scpi_aux_current_q},
|
|
|
|
// measurement related commands
|
|
{.pattern = "MEASure:RANGe", .callback = jf_scpi_measure_range},
|
|
{.pattern = "MEASure:RANGe?", .callback = jf_scpi_measure_range_q},
|
|
|
|
{.pattern = "MEASure:ISENSE1?", .callback = jf_scpi_measure_isense1_q},
|
|
{.pattern = "MEASure:1?", .callback = jf_scpi_measure_isense1_q},
|
|
{.pattern = "MEASure:ISENSE2?", .callback = jf_scpi_measure_isense2_q},
|
|
{.pattern = "MEASure:2?", .callback = jf_scpi_measure_isense2_q},
|
|
{.pattern = "MEASure:VSENSE?", .callback = jf_scpi_measure_vsense_q},
|
|
{.pattern = "MEASure:3?", .callback = jf_scpi_measure_vsense_q},
|
|
{.pattern = "MEASure:GPIO?", .callback = jf_scpi_measure_gpio_q},
|
|
{.pattern = "MEASure:4?", .callback = jf_scpi_measure_gpio_q},
|
|
{.pattern = "MEASure:AUX:VOLTage?", .callback = jf_scpi_aux_measure_voltage},
|
|
{.pattern = "MEASure:AUX:CURRent?", .callback = jf_scpi_aux_measure_current},
|
|
|
|
{.pattern = "ROUTE:ISO?", .callback = jf_scpi_route_iso_q},
|
|
{.pattern = "ROUTE:ISO", .callback = jf_scpi_route_iso},
|
|
|
|
{.pattern = "ROUTE:EXTin?", .callback = jf_scpi_route_extin_q},
|
|
{.pattern = "ROUTE:EXTin", .callback = jf_scpi_route_extin},
|
|
|
|
{.pattern = "ROUTE:OUTput?", .callback = jf_scpi_route_output_q},
|
|
{.pattern = "ROUTE:OUTput", .callback = jf_scpi_route_output},
|
|
|
|
SCPI_CMD_LIST_END
|
|
};
|
|
|
|
void jf_scpi_init(jf_scpi_interface_t interface, bool (*write_func)(const uint8_t *c, int len)) {
|
|
|
|
jf_scpi_write_funcs[interface] = write_func;
|
|
|
|
SCPI_Init(
|
|
&scpi_context[interface],
|
|
jf_scpi_commands,
|
|
&scpi_interface[interface],
|
|
scpi_units_def,
|
|
JF_SCPI_IDN1, JF_SCPI_IDN2, JF_SCPI_IDN3, JF_SCPI_IDN4,
|
|
jf_scpi_input_buffer[interface], JF_SCPI_INPUT_BUFFER_SIZE,
|
|
jf_scpi_error_queue_data[interface], JF_SCPI_ERROR_QUEUE_SIZE
|
|
);
|
|
|
|
scpi_if_initialized[interface] = true;
|
|
}
|
|
|
|
void jf_scpi_input(jf_scpi_interface_t interface, char *c, int len) {
|
|
if(scpi_if_initialized[interface]) {
|
|
SCPI_Input(&scpi_context[interface], c, len);
|
|
}
|
|
}
|
|
|