// Stepper step transmit synchronization // // Copyright (C) 2016-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. // The steppersync object is used to synchronize the output of mcu // step commands. The mcu can only queue a limited number of step // commands - this code tracks when items on the mcu step queue become // free so that new commands can be transmitted. It also ensures the // mcu step queue is ordered between steppers so that no stepper // starves the other steppers of space in the mcu step queue. #include // offsetof #include // malloc #include // memset #include "compiler.h" // __visible #include "serialqueue.h" // struct queue_message #include "stepcompress.h" // stepcompress_flush #include "steppersync.h" // steppersync_alloc struct steppersync { // Serial port struct serialqueue *sq; struct command_queue *cq; // Storage for associated stepcompress objects struct stepcompress **sc_list; int sc_num; // Storage for list of pending move clocks uint64_t *move_clocks; int num_move_clocks; }; // Allocate a new 'steppersync' object struct steppersync * __visible steppersync_alloc(struct serialqueue *sq, struct stepcompress **sc_list , int sc_num, int move_num) { struct steppersync *ss = malloc(sizeof(*ss)); memset(ss, 0, sizeof(*ss)); ss->sq = sq; ss->cq = serialqueue_alloc_commandqueue(); ss->sc_list = malloc(sizeof(*sc_list)*sc_num); memcpy(ss->sc_list, sc_list, sizeof(*sc_list)*sc_num); ss->sc_num = sc_num; ss->move_clocks = malloc(sizeof(*ss->move_clocks)*move_num); memset(ss->move_clocks, 0, sizeof(*ss->move_clocks)*move_num); ss->num_move_clocks = move_num; return ss; } // Free memory associated with a 'steppersync' object void __visible steppersync_free(struct steppersync *ss) { if (!ss) return; free(ss->sc_list); free(ss->move_clocks); serialqueue_free_commandqueue(ss->cq); free(ss); } // Set the conversion rate of 'print_time' to mcu clock void __visible steppersync_set_time(struct steppersync *ss, double time_offset , double mcu_freq) { int i; for (i=0; isc_num; i++) { struct stepcompress *sc = ss->sc_list[i]; stepcompress_set_time(sc, time_offset, mcu_freq); } } // Generate steps and flush stepcompress objects int32_t __visible steppersync_generate_steps(struct steppersync *ss, double gen_steps_time , uint64_t flush_clock) { int i; for (i=0; isc_num; i++) { struct stepcompress *sc = ss->sc_list[i]; int32_t ret = stepcompress_generate_steps(sc, gen_steps_time , flush_clock); if (ret) return ret; } return 0; } // Expire the stepcompress history before the given clock time void __visible steppersync_history_expire(struct steppersync *ss, uint64_t end_clock) { int i; for (i = 0; i < ss->sc_num; i++) { struct stepcompress *sc = ss->sc_list[i]; stepcompress_history_expire(sc, end_clock); } } // Implement a binary heap algorithm to track when the next available // 'struct move' in the mcu will be available static void heap_replace(struct steppersync *ss, uint64_t req_clock) { uint64_t *mc = ss->move_clocks; int nmc = ss->num_move_clocks, pos = 0; for (;;) { int child1_pos = 2*pos+1, child2_pos = 2*pos+2; uint64_t child2_clock = child2_pos < nmc ? mc[child2_pos] : UINT64_MAX; uint64_t child1_clock = child1_pos < nmc ? mc[child1_pos] : UINT64_MAX; if (req_clock <= child1_clock && req_clock <= child2_clock) { mc[pos] = req_clock; break; } if (child1_clock < child2_clock) { mc[pos] = child1_clock; pos = child1_pos; } else { mc[pos] = child2_clock; pos = child2_pos; } } } // Find and transmit any scheduled steps prior to the given 'move_clock' int __visible steppersync_flush(struct steppersync *ss, uint64_t move_clock) { // Order commands by the reqclock of each pending command struct list_head msgs; list_init(&msgs); for (;;) { // Find message with lowest reqclock uint64_t req_clock = MAX_CLOCK; struct queue_message *qm = NULL; int i; for (i=0; isc_num; i++) { struct stepcompress *sc = ss->sc_list[i]; struct list_head *sc_mq = stepcompress_get_msg_queue(sc); if (!list_empty(sc_mq)) { struct queue_message *m = list_first_entry( sc_mq, struct queue_message, node); if (m->req_clock < req_clock) { qm = m; req_clock = m->req_clock; } } } if (!qm || (qm->min_clock && req_clock > move_clock)) break; uint64_t next_avail = ss->move_clocks[0]; if (qm->min_clock) // The qm->min_clock field is overloaded to indicate that // the command uses the 'move queue' and to store the time // that move queue item becomes available. heap_replace(ss, qm->min_clock); // Reset the min_clock to its normal meaning (minimum transmit time) qm->min_clock = next_avail; // Batch this command list_del(&qm->node); list_add_tail(&qm->node, &msgs); } // Transmit commands if (!list_empty(&msgs)) serialqueue_send_batch(ss->sq, ss->cq, &msgs); return 0; }