0
mirror of https://gitlab.com/hyperglitch/jellyfish.git synced 2025-12-30 07:37:10 +00:00
2025-07-20 02:55:03 +02:00

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, &current, 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, &current, 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, &current, 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, &current, 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);
}
}