@@ -692,6 +692,7 @@ extern int hal_get_signal_value_by_name(
692692extern 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);
983984extern 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+
9861082RTAPI_END_DECLS
9871083
9881084#endif /* HAL_H */
0 commit comments