mirror of
https://github.com/MarlinFirmware/Marlin.git
synced 2024-11-30 07:17:59 +00:00
[1.1.x] Assorted fixes and improvements (#10914)
Co-Authored-By: ejtagle
This commit is contained in:
parent
67d9d1870c
commit
3b06a8e917
@ -52,6 +52,10 @@
|
|||||||
#define CRITICAL_SECTION_END SREG = _sreg;
|
#define CRITICAL_SECTION_END SREG = _sreg;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define ISRS_ENABLED() TEST(SREG, SREG_I)
|
||||||
|
#define ENABLE_ISRS() sei()
|
||||||
|
#define DISABLE_ISRS() cli()
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
// Types
|
// Types
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
@ -148,7 +152,6 @@ void TIMER1_COMPA_vect (void) { \
|
|||||||
A("lds r16, %[timsk1]") /* 2 Load into R0 the stepper timer Interrupt mask register [TIMSK1] */ \
|
A("lds r16, %[timsk1]") /* 2 Load into R0 the stepper timer Interrupt mask register [TIMSK1] */ \
|
||||||
A("andi r16,~%[msk1]") /* 1 Disable the stepper ISR */ \
|
A("andi r16,~%[msk1]") /* 1 Disable the stepper ISR */ \
|
||||||
A("sts %[timsk1], r16") /* 2 And set the new value */ \
|
A("sts %[timsk1], r16") /* 2 And set the new value */ \
|
||||||
A("sei") /* 1 Enable global interrupts - stepper and temperature ISRs are disabled, so no risk of reentry or being preempted by the temperature ISR */ \
|
|
||||||
A("push r16") /* 2 Save TIMSK1 into stack */ \
|
A("push r16") /* 2 Save TIMSK1 into stack */ \
|
||||||
A("in r16, 0x3B") /* 1 Get RAMPZ register */ \
|
A("in r16, 0x3B") /* 1 Get RAMPZ register */ \
|
||||||
A("push r16") /* 2 Save RAMPZ into stack */ \
|
A("push r16") /* 2 Save RAMPZ into stack */ \
|
||||||
@ -258,7 +261,7 @@ void TIMER0_COMPB_vect (void) { \
|
|||||||
A("out 0x3B, r16") /* 1 Restore RAMPZ register to its original value */ \
|
A("out 0x3B, r16") /* 1 Restore RAMPZ register to its original value */ \
|
||||||
A("pop r16") /* 2 Get the original TIMSK0 value but with temperature ISR disabled */ \
|
A("pop r16") /* 2 Get the original TIMSK0 value but with temperature ISR disabled */ \
|
||||||
A("ori r16,%[msk0]") /* 1 Enable temperature ISR */ \
|
A("ori r16,%[msk0]") /* 1 Enable temperature ISR */ \
|
||||||
A("cli") /* 1 Disable global interrupts - We must do this, as we will reenable the temperature ISR, and we don´t want to reenter this handler until the current one is done */ \
|
A("cli") /* 1 Disable global interrupts - We must do this, as we will reenable the temperature ISR, and we don't want to reenter this handler until the current one is done */ \
|
||||||
A("sts %[timsk0], r16") /* 2 And restore the old value */ \
|
A("sts %[timsk0], r16") /* 2 And restore the old value */ \
|
||||||
A("pop r16") /* 2 Get the old SREG */ \
|
A("pop r16") /* 2 Get the old SREG */ \
|
||||||
A("out __SREG__, r16") /* 1 And restore the SREG value */ \
|
A("out __SREG__, r16") /* 1 And restore the SREG value */ \
|
||||||
|
@ -68,8 +68,6 @@
|
|||||||
uint8_t xon_xoff_state = XON_XOFF_CHAR_SENT | XON_CHAR;
|
uint8_t xon_xoff_state = XON_XOFF_CHAR_SENT | XON_CHAR;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void clear_command_queue();
|
|
||||||
|
|
||||||
#if ENABLED(SERIAL_STATS_DROPPED_RX)
|
#if ENABLED(SERIAL_STATS_DROPPED_RX)
|
||||||
uint8_t rx_dropped_bytes = 0;
|
uint8_t rx_dropped_bytes = 0;
|
||||||
#endif
|
#endif
|
||||||
@ -78,10 +76,14 @@
|
|||||||
ring_buffer_pos_t rx_max_enqueued = 0;
|
ring_buffer_pos_t rx_max_enqueued = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// A SW memory barrier, to ensure GCC does not overoptimize loops
|
||||||
|
#define sw_barrier() asm volatile("": : :"memory");
|
||||||
|
|
||||||
#if ENABLED(EMERGENCY_PARSER)
|
#if ENABLED(EMERGENCY_PARSER)
|
||||||
#include "emergency_parser.h"
|
#include "emergency_parser.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// (called with RX interrupts disabled)
|
||||||
FORCE_INLINE void store_rxd_char() {
|
FORCE_INLINE void store_rxd_char() {
|
||||||
const ring_buffer_pos_t h = rx_buffer.head,
|
const ring_buffer_pos_t h = rx_buffer.head,
|
||||||
i = (ring_buffer_pos_t)(h + 1) & (ring_buffer_pos_t)(RX_BUFFER_SIZE - 1);
|
i = (ring_buffer_pos_t)(h + 1) & (ring_buffer_pos_t)(RX_BUFFER_SIZE - 1);
|
||||||
@ -121,18 +123,22 @@
|
|||||||
// let the host react and stop sending bytes. This translates to 13mS
|
// let the host react and stop sending bytes. This translates to 13mS
|
||||||
// propagation time.
|
// propagation time.
|
||||||
if (rx_count >= (RX_BUFFER_SIZE) / 8) {
|
if (rx_count >= (RX_BUFFER_SIZE) / 8) {
|
||||||
|
|
||||||
// If TX interrupts are disabled and data register is empty,
|
// If TX interrupts are disabled and data register is empty,
|
||||||
// just write the byte to the data register and be done. This
|
// just write the byte to the data register and be done. This
|
||||||
// shortcut helps significantly improve the effective datarate
|
// shortcut helps significantly improve the effective datarate
|
||||||
// at high (>500kbit/s) bitrates, where interrupt overhead
|
// at high (>500kbit/s) bitrates, where interrupt overhead
|
||||||
// becomes a slowdown.
|
// becomes a slowdown.
|
||||||
if (!TEST(M_UCSRxB, M_UDRIEx) && TEST(M_UCSRxA, M_UDREx)) {
|
if (!TEST(M_UCSRxB, M_UDRIEx) && TEST(M_UCSRxA, M_UDREx)) {
|
||||||
|
|
||||||
// Send an XOFF character
|
// Send an XOFF character
|
||||||
M_UDRx = XOFF_CHAR;
|
M_UDRx = XOFF_CHAR;
|
||||||
|
|
||||||
// clear the TXC bit -- "can be cleared by writing a one to its bit
|
// clear the TXC bit -- "can be cleared by writing a one to its bit
|
||||||
// location". This makes sure flush() won't return until the bytes
|
// location". This makes sure flush() won't return until the bytes
|
||||||
// actually got written
|
// actually got written
|
||||||
SBI(M_UCSRxA, M_TXCx);
|
SBI(M_UCSRxA, M_TXCx);
|
||||||
|
|
||||||
// And remember it was sent
|
// And remember it was sent
|
||||||
xon_xoff_state = XOFF_CHAR | XON_XOFF_CHAR_SENT;
|
xon_xoff_state = XOFF_CHAR | XON_XOFF_CHAR_SENT;
|
||||||
}
|
}
|
||||||
@ -145,8 +151,14 @@
|
|||||||
xon_xoff_state = XOFF_CHAR;
|
xon_xoff_state = XOFF_CHAR;
|
||||||
#else
|
#else
|
||||||
// We are not using TX interrupts, we will have to send this manually
|
// We are not using TX interrupts, we will have to send this manually
|
||||||
while (!TEST(M_UCSRxA, M_UDREx)) {/* nada */}
|
while (!TEST(M_UCSRxA, M_UDREx)) sw_barrier();
|
||||||
M_UDRx = XOFF_CHAR;
|
M_UDRx = XOFF_CHAR;
|
||||||
|
|
||||||
|
// clear the TXC bit -- "can be cleared by writing a one to its bit
|
||||||
|
// location". This makes sure flush() won't return until the bytes
|
||||||
|
// actually got written
|
||||||
|
SBI(M_UCSRxA, M_TXCx);
|
||||||
|
|
||||||
// And remember we already sent it
|
// And remember we already sent it
|
||||||
xon_xoff_state = XOFF_CHAR | XON_XOFF_CHAR_SENT;
|
xon_xoff_state = XOFF_CHAR | XON_XOFF_CHAR_SENT;
|
||||||
#endif
|
#endif
|
||||||
@ -162,6 +174,7 @@
|
|||||||
|
|
||||||
#if TX_BUFFER_SIZE > 0
|
#if TX_BUFFER_SIZE > 0
|
||||||
|
|
||||||
|
// (called with TX irqs disabled)
|
||||||
FORCE_INLINE void _tx_udr_empty_irq(void) {
|
FORCE_INLINE void _tx_udr_empty_irq(void) {
|
||||||
// If interrupts are enabled, there must be more data in the output
|
// If interrupts are enabled, there must be more data in the output
|
||||||
// buffer.
|
// buffer.
|
||||||
@ -243,117 +256,139 @@
|
|||||||
CBI(M_UCSRxB, M_UDRIEx);
|
CBI(M_UCSRxB, M_UDRIEx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MarlinSerial::checkRx(void) {
|
|
||||||
if (TEST(M_UCSRxA, M_RXCx)) {
|
|
||||||
CRITICAL_SECTION_START;
|
|
||||||
store_rxd_char();
|
|
||||||
CRITICAL_SECTION_END;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int MarlinSerial::peek(void) {
|
int MarlinSerial::peek(void) {
|
||||||
CRITICAL_SECTION_START;
|
#if RX_BUFFER_SIZE > 256
|
||||||
|
// Disable RX interrupts, but only if non atomic reads
|
||||||
|
const bool isr_enabled = TEST(M_UCSRxB, M_RXCIEx);
|
||||||
|
CBI(M_UCSRxB, M_RXCIEx);
|
||||||
|
#endif
|
||||||
const int v = rx_buffer.head == rx_buffer.tail ? -1 : rx_buffer.buffer[rx_buffer.tail];
|
const int v = rx_buffer.head == rx_buffer.tail ? -1 : rx_buffer.buffer[rx_buffer.tail];
|
||||||
CRITICAL_SECTION_END;
|
#if RX_BUFFER_SIZE > 256
|
||||||
|
// Reenable RX interrupts if they were enabled
|
||||||
|
if (isr_enabled) SBI(M_UCSRxB, M_RXCIEx);
|
||||||
|
#endif
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
int MarlinSerial::read(void) {
|
int MarlinSerial::read(void) {
|
||||||
int v;
|
int v;
|
||||||
CRITICAL_SECTION_START;
|
|
||||||
const ring_buffer_pos_t t = rx_buffer.tail;
|
#if RX_BUFFER_SIZE > 256
|
||||||
if (rx_buffer.head == t)
|
// Disable RX interrupts to ensure atomic reads
|
||||||
|
const bool isr_enabled = TEST(M_UCSRxB, M_RXCIEx);
|
||||||
|
CBI(M_UCSRxB, M_RXCIEx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const ring_buffer_pos_t h = rx_buffer.head;
|
||||||
|
|
||||||
|
#if RX_BUFFER_SIZE > 256
|
||||||
|
// End critical section
|
||||||
|
if (isr_enabled) SBI(M_UCSRxB, M_RXCIEx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ring_buffer_pos_t t = rx_buffer.tail;
|
||||||
|
|
||||||
|
if (h == t)
|
||||||
v = -1;
|
v = -1;
|
||||||
else {
|
else {
|
||||||
v = rx_buffer.buffer[t];
|
v = rx_buffer.buffer[t];
|
||||||
rx_buffer.tail = (ring_buffer_pos_t)(t + 1) & (RX_BUFFER_SIZE - 1);
|
t = (ring_buffer_pos_t)(t + 1) & (RX_BUFFER_SIZE - 1);
|
||||||
|
|
||||||
|
#if RX_BUFFER_SIZE > 256
|
||||||
|
// Disable RX interrupts to ensure atomic write to tail, so
|
||||||
|
// the RX isr can't read partially updated values
|
||||||
|
const bool isr_enabled = TEST(M_UCSRxB, M_RXCIEx);
|
||||||
|
CBI(M_UCSRxB, M_RXCIEx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Advance tail
|
||||||
|
rx_buffer.tail = t;
|
||||||
|
|
||||||
|
#if RX_BUFFER_SIZE > 256
|
||||||
|
// End critical section
|
||||||
|
if (isr_enabled) SBI(M_UCSRxB, M_RXCIEx);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if ENABLED(SERIAL_XON_XOFF)
|
#if ENABLED(SERIAL_XON_XOFF)
|
||||||
if ((xon_xoff_state & XON_XOFF_CHAR_MASK) == XOFF_CHAR) {
|
if ((xon_xoff_state & XON_XOFF_CHAR_MASK) == XOFF_CHAR) {
|
||||||
|
|
||||||
// Get count of bytes in the RX buffer
|
// Get count of bytes in the RX buffer
|
||||||
ring_buffer_pos_t rx_count = (ring_buffer_pos_t)(rx_buffer.head - rx_buffer.tail) & (ring_buffer_pos_t)(RX_BUFFER_SIZE - 1);
|
ring_buffer_pos_t rx_count = (ring_buffer_pos_t)(h - t) & (ring_buffer_pos_t)(RX_BUFFER_SIZE - 1);
|
||||||
|
|
||||||
// When below 10% of RX buffer capacity, send XON before
|
// When below 10% of RX buffer capacity, send XON before
|
||||||
// running out of RX buffer bytes
|
// running out of RX buffer bytes
|
||||||
if (rx_count < (RX_BUFFER_SIZE) / 10) {
|
if (rx_count < (RX_BUFFER_SIZE) / 10) {
|
||||||
xon_xoff_state = XON_CHAR | XON_XOFF_CHAR_SENT;
|
xon_xoff_state = XON_CHAR | XON_XOFF_CHAR_SENT;
|
||||||
CRITICAL_SECTION_END; // End critical section before returning!
|
write(XON_CHAR);
|
||||||
writeNoHandshake(XON_CHAR);
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
CRITICAL_SECTION_END;
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
ring_buffer_pos_t MarlinSerial::available(void) {
|
ring_buffer_pos_t MarlinSerial::available(void) {
|
||||||
CRITICAL_SECTION_START;
|
#if RX_BUFFER_SIZE > 256
|
||||||
|
const bool isr_enabled = TEST(M_UCSRxB, M_RXCIEx);
|
||||||
|
CBI(M_UCSRxB, M_RXCIEx);
|
||||||
|
#endif
|
||||||
|
|
||||||
const ring_buffer_pos_t h = rx_buffer.head, t = rx_buffer.tail;
|
const ring_buffer_pos_t h = rx_buffer.head, t = rx_buffer.tail;
|
||||||
CRITICAL_SECTION_END;
|
|
||||||
|
#if RX_BUFFER_SIZE > 256
|
||||||
|
if (isr_enabled) SBI(M_UCSRxB, M_RXCIEx);
|
||||||
|
#endif
|
||||||
|
|
||||||
return (ring_buffer_pos_t)(RX_BUFFER_SIZE + h - t) & (RX_BUFFER_SIZE - 1);
|
return (ring_buffer_pos_t)(RX_BUFFER_SIZE + h - t) & (RX_BUFFER_SIZE - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MarlinSerial::flush(void) {
|
void MarlinSerial::flush(void) {
|
||||||
// Don't change this order of operations. If the RX interrupt occurs between
|
#if RX_BUFFER_SIZE > 256
|
||||||
// reading rx_buffer_head and updating rx_buffer_tail, the previous rx_buffer_head
|
const bool isr_enabled = TEST(M_UCSRxB, M_RXCIEx);
|
||||||
// may be written to rx_buffer_tail, making the buffer appear full rather than empty.
|
CBI(M_UCSRxB, M_RXCIEx);
|
||||||
CRITICAL_SECTION_START;
|
#endif
|
||||||
rx_buffer.head = rx_buffer.tail = 0;
|
|
||||||
clear_command_queue();
|
rx_buffer.tail = rx_buffer.head;
|
||||||
CRITICAL_SECTION_END;
|
|
||||||
|
#if RX_BUFFER_SIZE > 256
|
||||||
|
if (isr_enabled) SBI(M_UCSRxB, M_RXCIEx);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if ENABLED(SERIAL_XON_XOFF)
|
#if ENABLED(SERIAL_XON_XOFF)
|
||||||
if ((xon_xoff_state & XON_XOFF_CHAR_MASK) == XOFF_CHAR) {
|
if ((xon_xoff_state & XON_XOFF_CHAR_MASK) == XOFF_CHAR) {
|
||||||
xon_xoff_state = XON_CHAR | XON_XOFF_CHAR_SENT;
|
xon_xoff_state = XON_CHAR | XON_XOFF_CHAR_SENT;
|
||||||
writeNoHandshake(XON_CHAR);
|
write(XON_CHAR);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if TX_BUFFER_SIZE > 0
|
#if TX_BUFFER_SIZE > 0
|
||||||
uint8_t MarlinSerial::availableForWrite(void) {
|
|
||||||
CRITICAL_SECTION_START;
|
|
||||||
const uint8_t h = tx_buffer.head, t = tx_buffer.tail;
|
|
||||||
CRITICAL_SECTION_END;
|
|
||||||
return (uint8_t)(TX_BUFFER_SIZE + h - t) & (TX_BUFFER_SIZE - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MarlinSerial::write(const uint8_t c) {
|
void MarlinSerial::write(const uint8_t c) {
|
||||||
#if ENABLED(SERIAL_XON_XOFF)
|
|
||||||
const uint8_t state = xon_xoff_state;
|
|
||||||
if (!(state & XON_XOFF_CHAR_SENT)) {
|
|
||||||
// Send 2 chars: XON/XOFF, then a user-specified char
|
|
||||||
writeNoHandshake(state & XON_XOFF_CHAR_MASK);
|
|
||||||
xon_xoff_state = state | XON_XOFF_CHAR_SENT;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
writeNoHandshake(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MarlinSerial::writeNoHandshake(const uint8_t c) {
|
|
||||||
_written = true;
|
_written = true;
|
||||||
CRITICAL_SECTION_START;
|
|
||||||
bool emty = (tx_buffer.head == tx_buffer.tail);
|
|
||||||
CRITICAL_SECTION_END;
|
|
||||||
|
|
||||||
// If the buffer and the data register is empty, just write the byte
|
// If the TX interrupts are disabled and the data register
|
||||||
// to the data register and be done. This shortcut helps
|
// is empty, just write the byte to the data register and
|
||||||
// significantly improve the effective datarate at high (>
|
// be done. This shortcut helps significantly improve the
|
||||||
// 500kbit/s) bitrates, where interrupt overhead becomes a slowdown.
|
// effective datarate at high (>500kbit/s) bitrates, where
|
||||||
if (emty && TEST(M_UCSRxA, M_UDREx)) {
|
// interrupt overhead becomes a slowdown.
|
||||||
CRITICAL_SECTION_START;
|
if (!TEST(M_UCSRxB, M_UDRIEx) && TEST(M_UCSRxA, M_UDREx)) {
|
||||||
M_UDRx = c;
|
M_UDRx = c;
|
||||||
|
|
||||||
|
// clear the TXC bit -- "can be cleared by writing a one to its bit
|
||||||
|
// location". This makes sure flush() won't return until the bytes
|
||||||
|
// actually got written
|
||||||
SBI(M_UCSRxA, M_TXCx);
|
SBI(M_UCSRxA, M_TXCx);
|
||||||
CRITICAL_SECTION_END;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t i = (tx_buffer.head + 1) & (TX_BUFFER_SIZE - 1);
|
const uint8_t i = (tx_buffer.head + 1) & (TX_BUFFER_SIZE - 1);
|
||||||
|
|
||||||
// If the output buffer is full, there's nothing for it other than to
|
// If the output buffer is full, there's nothing for it other than to
|
||||||
// wait for the interrupt handler to empty it a bit
|
// wait for the interrupt handler to empty it a bit
|
||||||
while (i == tx_buffer.tail) {
|
while (i == tx_buffer.tail) {
|
||||||
if (!TEST(SREG, SREG_I)) {
|
if (!ISRS_ENABLED()) {
|
||||||
// Interrupts are disabled, so we'll have to poll the data
|
// Interrupts are disabled, so we'll have to poll the data
|
||||||
// register empty flag ourselves. If it is set, pretend an
|
// register empty flag ourselves. If it is set, pretend an
|
||||||
// interrupt has happened and call the handler to free up
|
// interrupt has happened and call the handler to free up
|
||||||
@ -361,17 +396,18 @@
|
|||||||
if (TEST(M_UCSRxA, M_UDREx))
|
if (TEST(M_UCSRxA, M_UDREx))
|
||||||
_tx_udr_empty_irq();
|
_tx_udr_empty_irq();
|
||||||
}
|
}
|
||||||
else {
|
// (else , the interrupt handler will free up space for us)
|
||||||
// nop, the interrupt handler will free up space for us
|
|
||||||
}
|
// Make sure compiler rereads tx_buffer.tail
|
||||||
|
sw_barrier();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store new char. head is always safe to move
|
||||||
tx_buffer.buffer[tx_buffer.head] = c;
|
tx_buffer.buffer[tx_buffer.head] = c;
|
||||||
{ CRITICAL_SECTION_START;
|
|
||||||
tx_buffer.head = i;
|
tx_buffer.head = i;
|
||||||
|
|
||||||
|
// Enable TX isr
|
||||||
SBI(M_UCSRxB, M_UDRIEx);
|
SBI(M_UCSRxB, M_UDRIEx);
|
||||||
CRITICAL_SECTION_END;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -384,33 +420,23 @@
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
while (TEST(M_UCSRxB, M_UDRIEx) || !TEST(M_UCSRxA, M_TXCx)) {
|
while (TEST(M_UCSRxB, M_UDRIEx) || !TEST(M_UCSRxA, M_TXCx)) {
|
||||||
if (!TEST(SREG, SREG_I) && TEST(M_UCSRxB, M_UDRIEx))
|
if (!ISRS_ENABLED()) {
|
||||||
// Interrupts are globally disabled, but the DR empty
|
// Interrupts are globally disabled, but the DR empty
|
||||||
// interrupt should be enabled, so poll the DR empty flag to
|
// interrupt should be enabled, so poll the DR empty flag to
|
||||||
// prevent deadlock
|
// prevent deadlock
|
||||||
if (TEST(M_UCSRxA, M_UDREx))
|
if (TEST(M_UCSRxA, M_UDREx))
|
||||||
_tx_udr_empty_irq();
|
_tx_udr_empty_irq();
|
||||||
}
|
}
|
||||||
|
sw_barrier();
|
||||||
|
}
|
||||||
// If we get here, nothing is queued anymore (DRIE is disabled) and
|
// If we get here, nothing is queued anymore (DRIE is disabled) and
|
||||||
// the hardware finished tranmission (TXC is set).
|
// the hardware finished transmission (TXC is set).
|
||||||
}
|
}
|
||||||
|
|
||||||
#else // TX_BUFFER_SIZE == 0
|
#else // TX_BUFFER_SIZE == 0
|
||||||
|
|
||||||
void MarlinSerial::write(const uint8_t c) {
|
void MarlinSerial::write(const uint8_t c) {
|
||||||
#if ENABLED(SERIAL_XON_XOFF)
|
while (!TEST(M_UCSRxA, M_UDREx)) sw_barrier();
|
||||||
// Do a priority insertion of an XON/XOFF char, if needed.
|
|
||||||
const uint8_t state = xon_xoff_state;
|
|
||||||
if (!(state & XON_XOFF_CHAR_SENT)) {
|
|
||||||
writeNoHandshake(state & XON_XOFF_CHAR_MASK);
|
|
||||||
xon_xoff_state = state | XON_XOFF_CHAR_SENT;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
writeNoHandshake(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MarlinSerial::writeNoHandshake(uint8_t c) {
|
|
||||||
while (!TEST(M_UCSRxA, M_UDREx)) {/* nada */}
|
|
||||||
M_UDRx = c;
|
M_UDRx = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@
|
|||||||
extern ring_buffer_pos_t rx_max_enqueued;
|
extern ring_buffer_pos_t rx_max_enqueued;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class MarlinSerial { //: public Stream
|
class MarlinSerial {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MarlinSerial() {};
|
MarlinSerial() {};
|
||||||
@ -111,13 +111,10 @@
|
|||||||
static int read(void);
|
static int read(void);
|
||||||
static void flush(void);
|
static void flush(void);
|
||||||
static ring_buffer_pos_t available(void);
|
static ring_buffer_pos_t available(void);
|
||||||
static void checkRx(void);
|
|
||||||
static void write(const uint8_t c);
|
static void write(const uint8_t c);
|
||||||
#if TX_BUFFER_SIZE > 0
|
#if TX_BUFFER_SIZE > 0
|
||||||
static uint8_t availableForWrite(void);
|
|
||||||
static void flushTX(void);
|
static void flushTX(void);
|
||||||
#endif
|
#endif
|
||||||
static void writeNoHandshake(const uint8_t c);
|
|
||||||
|
|
||||||
#if ENABLED(SERIAL_STATS_DROPPED_RX)
|
#if ENABLED(SERIAL_STATS_DROPPED_RX)
|
||||||
FORCE_INLINE static uint32_t dropped() { return rx_dropped_bytes; }
|
FORCE_INLINE static uint32_t dropped() { return rx_dropped_bytes; }
|
||||||
|
@ -556,10 +556,13 @@ void Endstops::update() {
|
|||||||
} \
|
} \
|
||||||
}while(0)
|
}while(0)
|
||||||
|
|
||||||
// Call the endstop triggered routine for single endstops
|
// Call the endstop triggered routine for dual endstops
|
||||||
#define PROCESS_DUAL_ENDSTOP(AXIS1, AXIS2, MINMAX) do { \
|
#define PROCESS_DUAL_ENDSTOP(AXIS1, AXIS2, MINMAX) do { \
|
||||||
if (TEST_ENDSTOP(_ENDSTOP(AXIS1, MINMAX)) || TEST_ENDSTOP(_ENDSTOP(AXIS2, MINMAX))) { \
|
const byte dual_hit = TEST_ENDSTOP(_ENDSTOP(AXIS1, MINMAX)) | (TEST_ENDSTOP(_ENDSTOP(AXIS2, MINMAX)) << 1); \
|
||||||
|
if (dual_hit) { \
|
||||||
_ENDSTOP_HIT(AXIS1, MINMAX); \
|
_ENDSTOP_HIT(AXIS1, MINMAX); \
|
||||||
|
/* if not performing home or if both endstops were trigged during homing... */ \
|
||||||
|
if (!stepper.performing_homing || dual_hit == 0x3) \
|
||||||
planner.endstop_triggered(_AXIS(AXIS1)); \
|
planner.endstop_triggered(_AXIS(AXIS1)); \
|
||||||
} \
|
} \
|
||||||
}while(0)
|
}while(0)
|
||||||
|
@ -107,7 +107,15 @@ class Endstops {
|
|||||||
/**
|
/**
|
||||||
* Get current endstops state
|
* Get current endstops state
|
||||||
*/
|
*/
|
||||||
FORCE_INLINE static esbits_t state() { return live_state; }
|
FORCE_INLINE static esbits_t state() {
|
||||||
|
return
|
||||||
|
#if ENABLED(ENDSTOP_NOISE_FILTER)
|
||||||
|
validated_live_state
|
||||||
|
#else
|
||||||
|
live_state
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Report endstop hits to serial. Called from loop().
|
* Report endstop hits to serial. Called from loop().
|
||||||
|
@ -2449,9 +2449,13 @@ void Planner::_set_position_mm(const float &a, const float &b, const float &c, c
|
|||||||
position_float[C_AXIS] = c;
|
position_float[C_AXIS] = c;
|
||||||
position_float[E_AXIS] = e;
|
position_float[E_AXIS] = e;
|
||||||
#endif
|
#endif
|
||||||
previous_nominal_speed_sqr = 0.0; // Resets planner junction speeds. Assumes start from rest.
|
if (has_blocks_queued()) {
|
||||||
ZERO(previous_speed);
|
//previous_nominal_speed_sqr = 0.0; // Reset planner junction speeds. Assume start from rest.
|
||||||
|
//ZERO(previous_speed);
|
||||||
buffer_sync_block();
|
buffer_sync_block();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
stepper.set_position(position[A_AXIS], position[B_AXIS], position[C_AXIS], position[E_AXIS]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Planner::set_position_mm_kinematic(const float (&cart)[XYZE]) {
|
void Planner::set_position_mm_kinematic(const float (&cart)[XYZE]) {
|
||||||
@ -2483,8 +2487,12 @@ void Planner::set_position_mm(const AxisEnum axis, const float &v) {
|
|||||||
#if HAS_POSITION_FLOAT
|
#if HAS_POSITION_FLOAT
|
||||||
position_float[axis] = v;
|
position_float[axis] = v;
|
||||||
#endif
|
#endif
|
||||||
previous_speed[axis] = 0.0;
|
if (has_blocks_queued()) {
|
||||||
|
//previous_speed[axis] = 0.0;
|
||||||
buffer_sync_block();
|
buffer_sync_block();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
stepper.set_position(axis, position[axis]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recalculate the steps/s^2 acceleration rates, based on the mm/s^2
|
// Recalculate the steps/s^2 acceleration rates, based on the mm/s^2
|
||||||
|
@ -91,13 +91,13 @@ uint8_t Stepper::last_direction_bits = 0,
|
|||||||
bool Stepper::abort_current_block;
|
bool Stepper::abort_current_block;
|
||||||
|
|
||||||
#if ENABLED(X_DUAL_ENDSTOPS)
|
#if ENABLED(X_DUAL_ENDSTOPS)
|
||||||
bool Stepper::locked_x_motor = false, Stepper::locked_x2_motor = false;
|
bool Stepper::locked_X_motor = false, Stepper::locked_X2_motor = false;
|
||||||
#endif
|
#endif
|
||||||
#if ENABLED(Y_DUAL_ENDSTOPS)
|
#if ENABLED(Y_DUAL_ENDSTOPS)
|
||||||
bool Stepper::locked_y_motor = false, Stepper::locked_y2_motor = false;
|
bool Stepper::locked_Y_motor = false, Stepper::locked_Y2_motor = false;
|
||||||
#endif
|
#endif
|
||||||
#if ENABLED(Z_DUAL_ENDSTOPS)
|
#if ENABLED(Z_DUAL_ENDSTOPS)
|
||||||
bool Stepper::locked_z_motor = false, Stepper::locked_z2_motor = false;
|
bool Stepper::locked_Z_motor = false, Stepper::locked_Z2_motor = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -169,21 +169,15 @@ uint8_t Stepper::step_loops, Stepper::step_loops_nominal;
|
|||||||
volatile int32_t Stepper::endstops_trigsteps[XYZ];
|
volatile int32_t Stepper::endstops_trigsteps[XYZ];
|
||||||
|
|
||||||
#if ENABLED(X_DUAL_ENDSTOPS) || ENABLED(Y_DUAL_ENDSTOPS) || ENABLED(Z_DUAL_ENDSTOPS)
|
#if ENABLED(X_DUAL_ENDSTOPS) || ENABLED(Y_DUAL_ENDSTOPS) || ENABLED(Z_DUAL_ENDSTOPS)
|
||||||
#define LOCKED_X_MOTOR locked_x_motor
|
|
||||||
#define LOCKED_Y_MOTOR locked_y_motor
|
|
||||||
#define LOCKED_Z_MOTOR locked_z_motor
|
|
||||||
#define LOCKED_X2_MOTOR locked_x2_motor
|
|
||||||
#define LOCKED_Y2_MOTOR locked_y2_motor
|
|
||||||
#define LOCKED_Z2_MOTOR locked_z2_motor
|
|
||||||
#define DUAL_ENDSTOP_APPLY_STEP(A,V) \
|
#define DUAL_ENDSTOP_APPLY_STEP(A,V) \
|
||||||
if (performing_homing) { \
|
if (performing_homing) { \
|
||||||
if (A##_HOME_DIR < 0) { \
|
if (A##_HOME_DIR < 0) { \
|
||||||
if (!(TEST(endstops.state(), A##_MIN) && count_direction[_AXIS(A)] < 0) && !LOCKED_##A##_MOTOR) A##_STEP_WRITE(V); \
|
if (!(TEST(endstops.state(), A##_MIN) && count_direction[_AXIS(A)] < 0) && !locked_##A##_motor) A##_STEP_WRITE(V); \
|
||||||
if (!(TEST(endstops.state(), A##2_MIN) && count_direction[_AXIS(A)] < 0) && !LOCKED_##A##2_MOTOR) A##2_STEP_WRITE(V); \
|
if (!(TEST(endstops.state(), A##2_MIN) && count_direction[_AXIS(A)] < 0) && !locked_##A##2_motor) A##2_STEP_WRITE(V); \
|
||||||
} \
|
} \
|
||||||
else { \
|
else { \
|
||||||
if (!(TEST(endstops.state(), A##_MAX) && count_direction[_AXIS(A)] > 0) && !LOCKED_##A##_MOTOR) A##_STEP_WRITE(V); \
|
if (!(TEST(endstops.state(), A##_MAX) && count_direction[_AXIS(A)] > 0) && !locked_##A##_motor) A##_STEP_WRITE(V); \
|
||||||
if (!(TEST(endstops.state(), A##2_MAX) && count_direction[_AXIS(A)] > 0) && !LOCKED_##A##2_MOTOR) A##2_STEP_WRITE(V); \
|
if (!(TEST(endstops.state(), A##2_MAX) && count_direction[_AXIS(A)] > 0) && !locked_##A##2_motor) A##2_STEP_WRITE(V); \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
else { \
|
else { \
|
||||||
@ -1117,27 +1111,22 @@ void Stepper::set_directions() {
|
|||||||
HAL_STEP_TIMER_ISR {
|
HAL_STEP_TIMER_ISR {
|
||||||
HAL_timer_isr_prologue(STEP_TIMER_NUM);
|
HAL_timer_isr_prologue(STEP_TIMER_NUM);
|
||||||
|
|
||||||
// Program timer compare for the maximum period, so it does NOT
|
Stepper::isr();
|
||||||
// flag an interrupt while this ISR is running - So changes from small
|
|
||||||
// periods to big periods are respected and the timer does not reset to 0
|
|
||||||
HAL_timer_set_compare(STEP_TIMER_NUM, HAL_TIMER_TYPE_MAX);
|
|
||||||
|
|
||||||
// Call the ISR scheduler
|
|
||||||
hal_timer_t ticks = Stepper::isr_scheduler();
|
|
||||||
|
|
||||||
// Now 'ticks' contains the period to the next Stepper ISR - And we are
|
|
||||||
// sure that the time has not arrived yet - Warrantied by the scheduler
|
|
||||||
|
|
||||||
// Set the next ISR to fire at the proper time
|
|
||||||
HAL_timer_set_compare(STEP_TIMER_NUM, ticks);
|
|
||||||
|
|
||||||
HAL_timer_isr_epilogue(STEP_TIMER_NUM);
|
HAL_timer_isr_epilogue(STEP_TIMER_NUM);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define STEP_MULTIPLY(A,B) MultiU24X32toH16(A, B)
|
#define STEP_MULTIPLY(A,B) MultiU24X32toH16(A, B)
|
||||||
|
|
||||||
hal_timer_t Stepper::isr_scheduler() {
|
void Stepper::isr() {
|
||||||
uint32_t interval;
|
|
||||||
|
// Disable interrupts, to avoid ISR preemption while we reprogram the period
|
||||||
|
DISABLE_ISRS();
|
||||||
|
|
||||||
|
// Program timer compare for the maximum period, so it does NOT
|
||||||
|
// flag an interrupt while this ISR is running - So changes from small
|
||||||
|
// periods to big periods are respected and the timer does not reset to 0
|
||||||
|
HAL_timer_set_compare(STEP_TIMER_NUM, HAL_TIMER_TYPE_MAX);
|
||||||
|
|
||||||
// Count of ticks for the next ISR
|
// Count of ticks for the next ISR
|
||||||
hal_timer_t next_isr_ticks = 0;
|
hal_timer_t next_isr_ticks = 0;
|
||||||
@ -1148,6 +1137,9 @@ hal_timer_t Stepper::isr_scheduler() {
|
|||||||
// We need this variable here to be able to use it in the following loop
|
// We need this variable here to be able to use it in the following loop
|
||||||
hal_timer_t min_ticks;
|
hal_timer_t min_ticks;
|
||||||
do {
|
do {
|
||||||
|
// Enable ISRs to reduce USART processing latency
|
||||||
|
ENABLE_ISRS();
|
||||||
|
|
||||||
// Run main stepping pulse phase ISR if we have to
|
// Run main stepping pulse phase ISR if we have to
|
||||||
if (!nextMainISR) Stepper::stepper_pulse_phase_isr();
|
if (!nextMainISR) Stepper::stepper_pulse_phase_isr();
|
||||||
|
|
||||||
@ -1161,13 +1153,13 @@ hal_timer_t Stepper::isr_scheduler() {
|
|||||||
// Run main stepping block processing ISR if we have to
|
// Run main stepping block processing ISR if we have to
|
||||||
if (!nextMainISR) nextMainISR = Stepper::stepper_block_phase_isr();
|
if (!nextMainISR) nextMainISR = Stepper::stepper_block_phase_isr();
|
||||||
|
|
||||||
|
uint32_t interval =
|
||||||
#if ENABLED(LIN_ADVANCE)
|
#if ENABLED(LIN_ADVANCE)
|
||||||
// Select the closest interval in time
|
MIN(nextAdvanceISR, nextMainISR) // Nearest time interval
|
||||||
interval = (nextAdvanceISR <= nextMainISR) ? nextAdvanceISR : nextMainISR;
|
|
||||||
#else
|
#else
|
||||||
// The interval is just the remaining time to the stepper ISR
|
nextMainISR // Remaining stepper ISR time
|
||||||
interval = nextMainISR;
|
|
||||||
#endif
|
#endif
|
||||||
|
;
|
||||||
|
|
||||||
// Limit the value to the maximum possible value of the timer
|
// Limit the value to the maximum possible value of the timer
|
||||||
NOMORE(interval, HAL_TIMER_TYPE_MAX);
|
NOMORE(interval, HAL_TIMER_TYPE_MAX);
|
||||||
@ -1206,6 +1198,16 @@ hal_timer_t Stepper::isr_scheduler() {
|
|||||||
// Compute the tick count for the next ISR
|
// Compute the tick count for the next ISR
|
||||||
next_isr_ticks += interval;
|
next_isr_ticks += interval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The following section must be done with global interrupts disabled.
|
||||||
|
* We want nothing to interrupt it, as that could mess the calculations
|
||||||
|
* we do for the next value to program in the period register of the
|
||||||
|
* stepper timer and lead to skipped ISRs (if the value we happen to program
|
||||||
|
* is less than the current count due to something preempting between the
|
||||||
|
* read and the write of the new period value).
|
||||||
|
*/
|
||||||
|
DISABLE_ISRS();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current tick value + margin
|
* Get the current tick value + margin
|
||||||
* Assuming at least 6µs between calls to this ISR...
|
* Assuming at least 6µs between calls to this ISR...
|
||||||
@ -1227,8 +1229,14 @@ hal_timer_t Stepper::isr_scheduler() {
|
|||||||
// Advance pulses if not enough time to wait for the next ISR
|
// Advance pulses if not enough time to wait for the next ISR
|
||||||
} while (next_isr_ticks < min_ticks);
|
} while (next_isr_ticks < min_ticks);
|
||||||
|
|
||||||
// Return the count of ticks for the next ISR
|
// Now 'next_isr_ticks' contains the period to the next Stepper ISR - And we are
|
||||||
return (hal_timer_t)next_isr_ticks;
|
// sure that the time has not arrived yet - Warrantied by the scheduler
|
||||||
|
|
||||||
|
// Set the next ISR to fire at the proper time
|
||||||
|
HAL_timer_set_compare(STEP_TIMER_NUM, hal_timer_t(next_isr_ticks));
|
||||||
|
|
||||||
|
// Don't forget to finally reenable interrupts
|
||||||
|
ENABLE_ISRS();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,13 +104,13 @@ class Stepper {
|
|||||||
static bool abort_current_block; // Signals to the stepper that current block should be aborted
|
static bool abort_current_block; // Signals to the stepper that current block should be aborted
|
||||||
|
|
||||||
#if ENABLED(X_DUAL_ENDSTOPS)
|
#if ENABLED(X_DUAL_ENDSTOPS)
|
||||||
static bool locked_x_motor, locked_x2_motor;
|
static bool locked_X_motor, locked_X2_motor;
|
||||||
#endif
|
#endif
|
||||||
#if ENABLED(Y_DUAL_ENDSTOPS)
|
#if ENABLED(Y_DUAL_ENDSTOPS)
|
||||||
static bool locked_y_motor, locked_y2_motor;
|
static bool locked_Y_motor, locked_Y2_motor;
|
||||||
#endif
|
#endif
|
||||||
#if ENABLED(Z_DUAL_ENDSTOPS)
|
#if ENABLED(Z_DUAL_ENDSTOPS)
|
||||||
static bool locked_z_motor, locked_z2_motor;
|
static bool locked_Z_motor, locked_Z2_motor;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Counter variables for the Bresenham line tracer
|
// Counter variables for the Bresenham line tracer
|
||||||
@ -189,7 +189,7 @@ class Stepper {
|
|||||||
// Interrupt Service Routines
|
// Interrupt Service Routines
|
||||||
|
|
||||||
// The ISR scheduler
|
// The ISR scheduler
|
||||||
static hal_timer_t isr_scheduler();
|
static void isr();
|
||||||
|
|
||||||
// The stepper pulse phase ISR
|
// The stepper pulse phase ISR
|
||||||
static void stepper_pulse_phase_isr();
|
static void stepper_pulse_phase_isr();
|
||||||
@ -243,18 +243,18 @@ class Stepper {
|
|||||||
|
|
||||||
#if ENABLED(X_DUAL_ENDSTOPS)
|
#if ENABLED(X_DUAL_ENDSTOPS)
|
||||||
FORCE_INLINE static void set_homing_flag_x(const bool state) { performing_homing = state; }
|
FORCE_INLINE static void set_homing_flag_x(const bool state) { performing_homing = state; }
|
||||||
FORCE_INLINE static void set_x_lock(const bool state) { locked_x_motor = state; }
|
FORCE_INLINE static void set_x_lock(const bool state) { locked_X_motor = state; }
|
||||||
FORCE_INLINE static void set_x2_lock(const bool state) { locked_x2_motor = state; }
|
FORCE_INLINE static void set_x2_lock(const bool state) { locked_X2_motor = state; }
|
||||||
#endif
|
#endif
|
||||||
#if ENABLED(Y_DUAL_ENDSTOPS)
|
#if ENABLED(Y_DUAL_ENDSTOPS)
|
||||||
FORCE_INLINE static void set_homing_flag_y(const bool state) { performing_homing = state; }
|
FORCE_INLINE static void set_homing_flag_y(const bool state) { performing_homing = state; }
|
||||||
FORCE_INLINE static void set_y_lock(const bool state) { locked_y_motor = state; }
|
FORCE_INLINE static void set_y_lock(const bool state) { locked_Y_motor = state; }
|
||||||
FORCE_INLINE static void set_y2_lock(const bool state) { locked_y2_motor = state; }
|
FORCE_INLINE static void set_y2_lock(const bool state) { locked_Y2_motor = state; }
|
||||||
#endif
|
#endif
|
||||||
#if ENABLED(Z_DUAL_ENDSTOPS)
|
#if ENABLED(Z_DUAL_ENDSTOPS)
|
||||||
FORCE_INLINE static void set_homing_flag_z(const bool state) { performing_homing = state; }
|
FORCE_INLINE static void set_homing_flag_z(const bool state) { performing_homing = state; }
|
||||||
FORCE_INLINE static void set_z_lock(const bool state) { locked_z_motor = state; }
|
FORCE_INLINE static void set_z_lock(const bool state) { locked_Z_motor = state; }
|
||||||
FORCE_INLINE static void set_z2_lock(const bool state) { locked_z2_motor = state; }
|
FORCE_INLINE static void set_z2_lock(const bool state) { locked_Z2_motor = state; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ENABLED(BABYSTEPPING)
|
#if ENABLED(BABYSTEPPING)
|
||||||
@ -268,16 +268,18 @@ class Stepper {
|
|||||||
// Set the current position in steps
|
// Set the current position in steps
|
||||||
inline static void set_position(const int32_t &a, const int32_t &b, const int32_t &c, const int32_t &e) {
|
inline static void set_position(const int32_t &a, const int32_t &b, const int32_t &c, const int32_t &e) {
|
||||||
planner.synchronize();
|
planner.synchronize();
|
||||||
CRITICAL_SECTION_START;
|
const bool was_enabled = STEPPER_ISR_ENABLED();
|
||||||
|
if (was_enabled) DISABLE_STEPPER_DRIVER_INTERRUPT();
|
||||||
_set_position(a, b, c, e);
|
_set_position(a, b, c, e);
|
||||||
CRITICAL_SECTION_END;
|
if (was_enabled) ENABLE_STEPPER_DRIVER_INTERRUPT();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline static void set_position(const AxisEnum a, const int32_t &v) {
|
inline static void set_position(const AxisEnum a, const int32_t &v) {
|
||||||
planner.synchronize();
|
planner.synchronize();
|
||||||
CRITICAL_SECTION_START;
|
const bool was_enabled = STEPPER_ISR_ENABLED();
|
||||||
|
if (was_enabled) DISABLE_STEPPER_DRIVER_INTERRUPT();
|
||||||
count_position[a] = v;
|
count_position[a] = v;
|
||||||
CRITICAL_SECTION_END;
|
if (was_enabled) ENABLE_STEPPER_DRIVER_INTERRUPT();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Loading…
Reference in New Issue
Block a user