mirror of
https://github.com/andreili/klipper.git
synced 2025-08-23 11:24:06 +02:00
Support storing a reference to 'struct stepper_kinematics' in 'struct stepcompress' and support globally generating steps via the steppersync mechanism. Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
178 lines
5.6 KiB
C
178 lines
5.6 KiB
C
// Stepper step transmit synchronization
|
|
//
|
|
// Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net>
|
|
//
|
|
// 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 <stddef.h> // offsetof
|
|
#include <stdlib.h> // malloc
|
|
#include <string.h> // 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; i<ss->sc_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; i<ss->sc_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; i<ss->sc_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;
|
|
}
|