Skip to content

Commit cf42ea7

Browse files
author
Robert Schöftner
committed
add hal_extend_int function
helper function to deal with wrap around and extension of lower-width counters to 64bit ints.
1 parent fcd8ed1 commit cf42ea7

1 file changed

Lines changed: 96 additions & 0 deletions

File tree

src/hal/hal.h

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ extern int hal_get_signal_value_by_name(
692692
extern int hal_get_param_value_by_name(
693693
const char *name, hal_type_t *type, hal_data_u **data);
694694

695+
695696
/***********************************************************************
696697
* EXECUTION RELATED FUNCTIONS *
697698
************************************************************************/
@@ -983,6 +984,101 @@ extern bool hal_stream_writable(hal_stream_t *stream);
983984
extern void hal_stream_wait_writable(hal_stream_t *stream, sig_atomic_t *stop);
984985
#endif
985986

987+
988+
/***********************************************************************
989+
* MISC HELPER FUNCTIONS *
990+
************************************************************************/
991+
992+
993+
/** HAL_STATIC_ASSERT wrapper for compile time asserts
994+
*/
995+
#if __STDC_VERSION__ >= 202311L || __cplusplus
996+
#define HAL_STATIC_ASSERT(expression, message) static_assert((expression), message)
997+
#else
998+
/* _Static_assert: GCC extension, in C standard since C11, deprecated in favour of
999+
static_assert (like C++) from C23 onwards*/
1000+
#define HAL_STATIC_ASSERT(expression, message) _Static_assert((expression), message)
1001+
#endif
1002+
1003+
1004+
/** hal_extend_counter() extends a counter value with nbits to 64 bits.
1005+
1006+
For some hardware counters or encoders it may be desireable to
1007+
extend their range beyond their native width.
1008+
1009+
This function maintains a 64bit counter value and counts wrap
1010+
arounds. It may be useful to e.g. keep track of full rotations on
1011+
a gray disc absolute encoder.
1012+
1013+
Changes of hardware encoder between calls to this function need to
1014+
be less than 2**(nbits-1).
1015+
1016+
Usage:
1017+
1018+
Call with current 64bit counter value to be updated as @param old,
1019+
new low-width counter value read from hardware as @param newlow
1020+
and width of counter as @param nbits.
1021+
@returns new 64bit counter value which should be stored and
1022+
supplied as old in the next call.
1023+
*/
1024+
static inline rtapi_s64 hal_extend_counter(rtapi_s64 old, rtapi_s64 newlow, int nbits)
1025+
{
1026+
/* Extend low-bit-width counter value to 64bit, counting wrap arounds resp.
1027+
"rotations" in additional bits.
1028+
1029+
see https://github.com/LinuxCNC/linuxcnc/pull/3932#issuecomment-4239206615
1030+
This code avoids branches and undefined behaviour due to signed overflow.
1031+
Idea from MicroPython.
1032+
1033+
The tricky part is how to efficiently calculate the difference between
1034+
two counter values that cross from minimum to maximum or vice versa.
1035+
(underflow/overflow).
1036+
1037+
To calculate this difference and avoid costly branches and explicit
1038+
verbose code, the difference is calculated from values shifted to the
1039+
right, the MSB of the counter values are shifted to the MSB of the
1040+
processor registers (64 bit). This difference is automatically
1041+
truncated to 64bit and then shifted back right to its original position
1042+
while preserving the sign (arithmetic right shift). This difference
1043+
is added to the large counter value.
1044+
1045+
The limit when using N-bit bit-width limited counters is that the maximum
1046+
count difference between subsequent invocation may not be larger than
1047+
2**(nshift-1)-1. Otherwise it is impossible to determine the direction of the
1048+
counter.
1049+
1050+
Heuristically, if your encoder can do more than half a rotation between
1051+
calls to this function, it is impossible to deduce which direction it
1052+
went.
1053+
1054+
Code contributed by Jeff Epler.
1055+
1056+
Example with 3bit absolute hardware encoder and 8bit extended counter
1057+
(nshift would be 5):
1058+
1059+
counter at 7, transition from 111 (7) to 000 (0) should increment the
1060+
extended counter to 8.
1061+
1062+
call hal_extend_counter(7, 0, 3)
1063+
oldlow_shifted = 7<<5 = 224 (1110 0000)
1064+
newlow_shifted = 0<<5 = 0
1065+
delta_shifted = 0 - 224 = -224 (1 0010 0000) = 32 (0010 0000) truncated to 8 bits
1066+
old + (32 >> 5) = 7 + 1 = 8
1067+
*/
1068+
1069+
/* tripwire if somebody tries to use this code on a Cray with wrong
1070+
compiler flags */
1071+
HAL_STATIC_ASSERT((-2 >> 1) == -1,
1072+
"hal_extend_counter impl only works with arithmetic right shift");
1073+
1074+
int nshift = 64 - nbits;
1075+
rtapi_u64 oldlow_shifted = ((rtapi_u64)old << nshift);
1076+
rtapi_u64 newlow_shifted = ((rtapi_u64)newlow << nshift);
1077+
rtapi_s64 diff_shifted = newlow_shifted - oldlow_shifted;
1078+
return (rtapi_u64)old + (diff_shifted >> nshift); // unsigned to avoid signed overflow
1079+
}
1080+
1081+
9861082
RTAPI_END_DECLS
9871083

9881084
#endif /* HAL_H */

0 commit comments

Comments
 (0)