Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
AGENTS.md
.tmp-*

machine.toml

software/sorteros/build/.env
software/sorteros/build/*.authkey
software/sorteros/build/out/
Expand Down
49 changes: 46 additions & 3 deletions software/firmware/sorter_interface_firmware/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,41 @@ endif()
set(INIT_DEVICE_NAME "${DEFAULT_DEVICE_NAME}" CACHE STRING "Device Name for the firmware to use on startup")
set(INIT_DEVICE_ADDRESS "0x00" CACHE STRING "Device Address in the bus")

set(FIRMWARE_VARIANT "unknown")
if (HW_SKR_PICO)
set(FIRMWARE_VARIANT "${FIRMWARE_ROLE_NORMALIZED}-skr")
elseif(HW_BASICALLY_V1_1)
set(FIRMWARE_VARIANT "${FIRMWARE_ROLE_NORMALIZED}-v1-1")
elseif(HW_BASICALLY_V1_2)
set(FIRMWARE_VARIANT "${FIRMWARE_ROLE_NORMALIZED}-v1-2")
endif()

set(FIRMWARE_GIT_VERSION "unknown")
execute_process(
COMMAND git describe --tags --match firmware/v* --always --dirty --abbrev=8
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
OUTPUT_VARIABLE FIRMWARE_GIT_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if (FIRMWARE_GIT_VERSION STREQUAL "")
set(FIRMWARE_GIT_VERSION "unknown")
endif()

set(FIRMWARE_GIT_COMMIT "unknown")
execute_process(
COMMAND git rev-parse --short=8 HEAD
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
OUTPUT_VARIABLE FIRMWARE_GIT_COMMIT
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if (FIRMWARE_GIT_COMMIT STREQUAL "")
set(FIRMWARE_GIT_COMMIT "unknown")
endif()

string(TIMESTAMP FIRMWARE_BUILD_TIME_UTC "%Y-%m-%dT%H:%M:%SZ" UTC)

# Add executable. Default name is the project name, version 0.1

add_executable(sorter_interface_firmware
Expand All @@ -67,7 +102,7 @@ add_executable(sorter_interface_firmware
)

pico_set_program_name(sorter_interface_firmware "sorter_interface_firmware")
pico_set_program_version(sorter_interface_firmware "0.1")
pico_set_program_version(sorter_interface_firmware "${FIRMWARE_GIT_VERSION}")

# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(sorter_interface_firmware 0)
Expand All @@ -83,7 +118,16 @@ target_include_directories(sorter_interface_firmware PRIVATE
)

# Pass INIT_DEVICE_NAME and INIT_DEVICE_ADDRESS as preprocessor definitions
target_compile_definitions(sorter_interface_firmware PRIVATE INIT_DEVICE_NAME="${INIT_DEVICE_NAME}" INIT_DEVICE_ADDRESS=${INIT_DEVICE_ADDRESS})
target_compile_definitions(
sorter_interface_firmware
PRIVATE
INIT_DEVICE_NAME="${INIT_DEVICE_NAME}"
INIT_DEVICE_ADDRESS=${INIT_DEVICE_ADDRESS}
FIRMWARE_GIT_VERSION="${FIRMWARE_GIT_VERSION}"
FIRMWARE_GIT_COMMIT="${FIRMWARE_GIT_COMMIT}"
FIRMWARE_BUILD_TIME_UTC="${FIRMWARE_BUILD_TIME_UTC}"
FIRMWARE_VARIANT="${FIRMWARE_VARIANT}"
)
if (HW_SKR_PICO)
target_compile_definitions(sorter_interface_firmware PRIVATE HARDWARE_SKR_PICO)
endif()
Expand All @@ -110,4 +154,3 @@ target_link_libraries(sorter_interface_firmware
)

pico_add_extra_outputs(sorter_interface_firmware)

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ const char* const STEPPER_NAMES[] = {
};
#endif

const uint8_t TMC_UART_BUS_COUNT = 1;
// Preprocessor macro, not a const — gates `#if TMC_UART_BUS_COUNT > 1` (see v1_2).
#define TMC_UART_BUS_COUNT 1
uart_inst_t* const TMC_UART_BUSES[] = {uart0};
const int TMC_UART_BUS_TX_PINS[] = {16};
const int TMC_UART_BUS_RX_PINS[] = {17};
Expand Down
16 changes: 10 additions & 6 deletions software/firmware/sorter_interface_firmware/hwcfg_basically_v1_2.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ const uint8_t STEPPER_DIR_PINS[] = {27, 22, 20, 18, 7};
// as a 4th c-channel (classification channel) instead of an actual carousel.
// Backend aliases it per machine_setup; not worth a firmware variant.
const char* const STEPPER_NAMES[] = {
"c_channel_1_rotor", // ch0 — STEP=GP28, DIR=GP27
"c_channel_2_rotor", // ch1 — STEP=GP26, DIR=GP22
"c_channel_3_rotor", // ch2 — STEP=GP21, DIR=GP20
"carousel", // ch3 — STEP=GP19, DIR=GP18
"chute_stepper" // ch4 — STEP=GP8, DIR=GP7
"chute_stepper", // ch0 — STEP=GP28, DIR=GP27 — uart0 addr0
"c_channel_1_rotor", // ch1 — STEP=GP26, DIR=GP22 — uart0 addr1
"c_channel_3_rotor", // ch2 — STEP=GP21, DIR=GP20 — uart0 addr2
"carousel", // ch3 — STEP=GP19, DIR=GP18 — uart0 addr3
"c_channel_2_rotor" // ch4 — STEP=GP8, DIR=GP7 — uart1 addr0
};
#else
const char* const STEPPER_NAMES[] = {
Expand All @@ -34,7 +34,11 @@ const char* const STEPPER_NAMES[] = {
};
#endif

const uint8_t TMC_UART_BUS_COUNT = 2;
// Must be a preprocessor macro, not a const: it gates `#if TMC_UART_BUS_COUNT > 1`
// blocks that construct/route the second UART bus. A const uint8_t is invisible to
// the preprocessor (evaluates as 0), which silently compiles out the second bus and
// makes every channel fall back to bus 0.
#define TMC_UART_BUS_COUNT 2
uart_inst_t* const TMC_UART_BUSES[] = {uart0, uart1};
const int TMC_UART_BUS_TX_PINS[] = {16, 4};
const int TMC_UART_BUS_RX_PINS[] = {17, 5};
Expand Down
3 changes: 2 additions & 1 deletion software/firmware/sorter_interface_firmware/hwcfg_skr_pico.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ const char* const STEPPER_NAMES[] = {
};
#endif

const uint8_t TMC_UART_BUS_COUNT = 1;
// Preprocessor macro, not a const — gates `#if TMC_UART_BUS_COUNT > 1` (see v1_2).
#define TMC_UART_BUS_COUNT 1
uart_inst_t* const TMC_UART_BUSES[] = {uart1};
const int TMC_UART_BUS_TX_PINS[] = {8};
const int TMC_UART_BUS_RX_PINS[] = {9};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ void CMDH_init(const BusMessage *msg, BusMessage *resp);
void CMDH_ping(const BusMessage *msg, BusMessage *resp);
void CMDH_reboot_bootloader(const BusMessage *msg, BusMessage *resp);
void CMDH_get_observability(const BusMessage *msg, BusMessage *resp);
void CMDH_get_version(const BusMessage *msg, BusMessage *resp);

const struct CommandTable baseCmdTable = { //
.prefix = NULL,
Expand All @@ -50,6 +51,7 @@ const struct CommandTable baseCmdTable = { //
{"PING", "", "", 255, NULL, CMDH_ping},
{"REBOOT_BOOTLOADER", "", "", 0, NULL, CMDH_reboot_bootloader},
{"GET_OBSERVABILITY", "", "s", 0, NULL, CMDH_get_observability},
{"GET_VERSION", "", "s", 0, NULL, CMDH_get_version},
}}};

void CMDH_stepper_move_steps(const BusMessage *msg, BusMessage *resp);
Expand Down Expand Up @@ -170,6 +172,22 @@ const MasterCommandTable command_tables = {
#define INIT_DEVICE_ADDRESS 0x00
#endif

#ifndef FIRMWARE_GIT_VERSION
#define FIRMWARE_GIT_VERSION "unknown"
#endif

#ifndef FIRMWARE_GIT_COMMIT
#define FIRMWARE_GIT_COMMIT "unknown"
#endif

#ifndef FIRMWARE_BUILD_TIME_UTC
#define FIRMWARE_BUILD_TIME_UTC "unknown"
#endif

#ifndef FIRMWARE_VARIANT
#define FIRMWARE_VARIANT "unknown"
#endif

char DEVICE_NAME[16] = INIT_DEVICE_NAME;
uint8_t DEVICE_ADDRESS = INIT_DEVICE_ADDRESS;

Expand Down Expand Up @@ -213,6 +231,17 @@ static std::array<Stepper, STEPPER_COUNT> make_stepper_array(std::index_sequence

static auto steppers = make_stepper_array(std::make_index_sequence<STEPPER_COUNT>{});

// Tracks whether each stepper's hardware nEN pin has been pulled low.
// Starts false; set on first move or explicit enable so motors don't hold at boot.
static bool stepper_hw_enabled[STEPPER_COUNT] = {};

static void ensure_stepper_hw_enabled(int i) {
if (!stepper_hw_enabled[i]) {
gpio_put(STEPPER_nEN_PINS[i], 0);
stepper_hw_enabled[i] = true;
}
}

std::atomic<uint8_t> SERVO_COUNT = 0; // Number of servos controlled by the PCA9685, should be <= 16
PCA9685 servo_controller(SERVO_I2C_ADDRESS, I2C_PORT);
std::array<Servo, 16> servos{}; // Create 16 servo objects, but only the first SERVO_COUNT will be used
Expand Down Expand Up @@ -360,6 +389,35 @@ int dump_configuration(char *buf, size_t buf_size) {
return 0;
}

int dump_version(char *buf, size_t buf_size) {
if (buf_size == 0) {
return 0;
}

int n_bytes = snprintf(
buf,
buf_size,
"{\"firmware_version\":\"%s\",\"variant\":\"%s\",\"commit\":\"%s\",\"build_time_utc\":\"%s\"}",
FIRMWARE_GIT_VERSION,
FIRMWARE_VARIANT,
FIRMWARE_GIT_COMMIT,
FIRMWARE_BUILD_TIME_UTC);

if (n_bytes >= 0 && (size_t)n_bytes < buf_size) {
return n_bytes;
}

if (buf_size >= 3) {
buf[0] = '{';
buf[1] = '}';
buf[2] = '\0';
return 2;
}

buf[0] = '\0';
return 0;
}

/** \brief Initialize all hardware components, including GPIOs, UART, stepper drivers, etc.
*
* This function is called once at startup to set up the hardware for operation. It configures the TMC2209 drivers,
Expand All @@ -383,11 +441,12 @@ void initialize_hardware() {
tmc_drivers[i].setMicrosteps(MICROSTEP_8);
tmc_drivers[i].enableStealthChop(true);
}
// Global enable for stepper drivers
// Initialize nEN pins but leave HIGH (disabled) until first move or explicit enable
for (int i = 0; i < STEPPER_COUNT; i++) {
gpio_init(STEPPER_nEN_PINS[i]);
gpio_set_dir(STEPPER_nEN_PINS[i], GPIO_OUT);
gpio_put(STEPPER_nEN_PINS[i], 0); // Enable stepper drivers
gpio_put(STEPPER_nEN_PINS[i], 1);
stepper_hw_enabled[i] = false;
}
// Initialize digital inputs
for (int i = 0; i < DIGITAL_INPUT_COUNT; i++) {
Expand Down Expand Up @@ -452,11 +511,17 @@ void CMDH_get_observability(const BusMessage *msg, BusMessage *resp) {
resp->payload_length = dump_observability((char *)resp->payload, MAX_PAYLOAD_SIZE);
}

void CMDH_get_version(const BusMessage *msg, BusMessage *resp) {
(void)msg;
resp->payload_length = dump_version((char *)resp->payload, MAX_PAYLOAD_SIZE);
}

bool VAL_stepper_channel(uint8_t channel) { return channel < STEPPER_COUNT; }

void CMDH_stepper_move_steps(const BusMessage *msg, BusMessage *resp) {
int32_t distance;
memcpy(&distance, msg->payload, sizeof(distance));
ensure_stepper_hw_enabled(msg->channel);
bool result = steppers[msg->channel].moveSteps(distance);
resp->payload[0] = result ? 1 : 0;
resp->payload_length = 1;
Expand All @@ -465,6 +530,7 @@ void CMDH_stepper_move_steps(const BusMessage *msg, BusMessage *resp) {
void CMDH_stepper_move_at_speed(const BusMessage *msg, BusMessage *resp) {
int32_t speed;
memcpy(&speed, msg->payload, sizeof(speed));
ensure_stepper_hw_enabled(msg->channel);
bool result = steppers[msg->channel].moveAtSpeed(speed);
resp->payload[0] = result ? 1 : 0;
resp->payload_length = 1;
Expand Down Expand Up @@ -533,6 +599,7 @@ void CMDH_stepper_home(const BusMessage *msg, BusMessage *resp) {
return;
}
int home_pin = digital_input_pins[home_pin_channel];
ensure_stepper_hw_enabled(msg->channel);
steppers[msg->channel].home(home_speed, home_pin, home_pin_polarity);
resp->payload_length = 0;
}
Expand All @@ -543,6 +610,7 @@ void CMDH_stepper_jitter(const BusMessage *msg, BusMessage *resp) {
memcpy(&cycles, msg->payload + 4, sizeof(cycles));
memcpy(&speed, msg->payload + 8, sizeof(speed));
memcpy(&accel, msg->payload + 12, sizeof(accel));
ensure_stepper_hw_enabled(msg->channel);
bool result = steppers[msg->channel].jitter(amplitude, cycles, speed, accel);
resp->payload[0] = result ? 1 : 0;
resp->payload_length = 1;
Expand All @@ -556,6 +624,7 @@ void CMDH_stepper_is_jittering(const BusMessage *msg, BusMessage *resp) {

void CMDH_stepper_drv_set_enabled(const BusMessage *msg, BusMessage *resp) {
bool enabled = msg->payload[0] != 0;
if (enabled) ensure_stepper_hw_enabled(msg->channel);
tmc_drivers[msg->channel].enableDriver(enabled);
resp->payload_length = 0;
}
Expand Down
6 changes: 6 additions & 0 deletions software/sorter/backend/hardware/bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class BaseCommandCode:
INIT = 0x00
PING = 0x01
GET_OBSERVABILITY = 0x03
GET_VERSION = 0x04


class MCUBusError(Exception):
Expand Down Expand Up @@ -313,6 +314,11 @@ def detect(self) -> dict:
info_str = res.payload.decode("utf-8")
return json.loads(info_str)

def get_version(self) -> dict:
res = self.send_command(BaseCommandCode.GET_VERSION, 0, b"")
info_str = res.payload.decode("utf-8")
return json.loads(info_str)



if __name__ == "__main__":
Expand Down
12 changes: 12 additions & 0 deletions software/sorter/backend/irl/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,18 @@ def mkIRLInterface(config: IRLConfig, gc: GlobalConfig) -> IRLInterface:
stepper_config: StepperConfig | None = getattr(config, attr, None)
stepper.set_hardware_name(physical_name)
stepper.set_name(attr_base)
# Write GCONF to put the TMC2209 in UART-controlled mode. The chip may have
# powered on (or reset) after the firmware's own initialize() ran, leaving it
# at hardware reset defaults (I_SCALE_ANALOG=1, MSTEP_REG_SELECT=0). Setting
# these bits here means the backend init is idempotent regardless of motor
# power sequencing.
_TMC_GCONF_UART_INIT = 0x1C0 # PD_DISABLE | MSTEP_REG_SELECT | MULTISTEP_FILT
_run_stepper_init_command_with_retry(
gc,
attr_base,
"GCONF UART init",
lambda: stepper.write_driver_register(0x00, _TMC_GCONF_UART_INIT),
)
if stepper_config is not None:
microsteps = stepper_config.microsteps
default_steps_per_second = stepper_config.default_steps_per_second
Expand Down
2 changes: 2 additions & 0 deletions software/sorter/backend/server/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def _load_saved_api_keys_into_environment() -> None:
from server.routers.runtimes import router as runtimes_router
from server.routers.chute_stress import router as chute_stress_router
from server.routers.tuning import router as tuning_router
from server.routers.telemetry import router as telemetry_router
from server.routers.tailscale import router as tailscale_router

app.include_router(hardware_router)
Expand All @@ -150,6 +151,7 @@ def _load_saved_api_keys_into_environment() -> None:
app.include_router(runtimes_router)
app.include_router(chute_stress_router)
app.include_router(tuning_router)
app.include_router(telemetry_router)
app.include_router(tailscale_router)

# ---------------------------------------------------------------------------
Expand Down
Loading