- Hardware & Machines
- Driver Boards
- Solving the USB Latency Dogma for HMI/MPG: Technical Feedback Request
Solving the USB Latency Dogma for HMI/MPG: Technical Feedback Request
- COFHAL
- Away
- Platinum Member
-
- Posts: 420
- Thank you received: 54
ls: cannot access '/dev/io_decoder': No such file or directory
Please Log in or Create an account to join the conversation.
- bobwolf
- Offline
- Senior Member
-
- Posts: 64
- Thank you received: 24
Go to the "Create a udev Rule" section described in bobwolfrst.github.io/io_decoder-linuxCNC/demo_mode.enI try this bot with the command ls -l /dev/io_decoder show this error
ls: cannot access '/dev/io_decoder': No such file or directory
or insert the following in the line that calls the component in the .hal file describing your machine: loadrt io_decoder firmware=255 usb_port_name="[the port address obtained with lsusb]"
Please Log in or Create an account to join the conversation.
- bobwolf
- Offline
- Senior Member
-
- Posts: 64
- Thank you received: 24
1. Why Arduino UNO & Serial Converter? You’re right, native USB (like 32u4) is faster, but the UNO is the most ubiquitous board on the planet. I chose it for the Evaluation to make it accessible to everyone. My dedicated hardware is optimized for the task. Regarding CDC, the driver handles the communication stream to ensure packet integrity without the overhead of complex generic protocols.
2. Closed Firmware & 'Over-engineering' I respect the Open Source philosophy, but io_decoder is a professional project that includes dedicated hardware. The 'over-engineering' you see in the HAL driver is actually optimization. While a user-space component can work, a dedicated C component ensures deterministic polling and much lower jitter. If you want a 'professional feel' on the handwheel, those milliseconds matter.
3. USB Stability & Real-Time Thread I’m well aware that USB isn't for the Servo Thread. In fact, io_decoder doesn't try to be a motion controller. It’s an HMI driver. The reason it’s written as a component is to ensure that the communication window is synchronized and doesn't suffer from the unpredictable latencies of user-space scheduling.Regarding errors: the driver includes a specific state-machine to handle reconnections much faster than a standard generic serial bridge.
4. The Goal Most 'standard' serial-to-HAL projects are hacks. io_decoder is a finished product. It’s for the user who wants to plug in a board, load a component, and have a 100% reliable, low-latency control panel without writing a single line of Python or C.I invite you to actually test the Eval version.
You’ll see that the 'over-engineering' results in a responsiveness that user-space scripts simply can't match.
Please Log in or Create an account to join the conversation.
- COFHAL
- Away
- Platinum Member
-
- Posts: 420
- Thank you received: 54
Please Log in or Create an account to join the conversation.
- COFHAL
- Away
- Platinum Member
-
- Posts: 420
- Thank you received: 54
Attachments:
Please Log in or Create an account to join the conversation.
- unknown
- Offline
- Platinum Member
-
- Posts: 887
- Thank you received: 325
What's in it for the testers ?
What thread will it run in ? And you suggesting another thread to add to a config ? What period would this be run at ?USB Stability & Real-Time Thread I’m well aware that USB isn't for the Servo Thread.
How would that affect a config that has a base thread (for software step gen) and a servo thread for other functions ?
Please Log in or Create an account to join the conversation.
- meister
- Away
- Platinum Member
-
- Posts: 695
- Thank you received: 424
2. Closed Firmware & 'Over-engineering' I respect the Open Source philosophy, but io_decoder is a professional project that includes dedicated hardware. The 'over-engineering' you see in the HAL driver is actually optimization. While a user-space component can work, a dedicated C component ensures deterministic polling and much lower jitter. If you want a 'professional feel' on the handwheel, those milliseconds matter.
ahh, ok, This is what professional code looks like when compiling.
halcompile --compile io_decoder.c
Compiling realtime io_decoder.c
io_decoder.c: In function ‘usb_thread_function’:
io_decoder.c:1119:60: warning: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Wsign-compare]
1119 | if (tx_buffer_size > sizeof(io->tx_packet_buffer)) {
| ^
io_decoder.c:1214:80: warning: comparison of integer expressions of different signedness: ‘int’ and ‘uint32_t’ {aka ‘unsigned int’} [-Wsign-compare]
1214 | if (parsed_len == io->usb_rx_payload_len) { //
| ^~
io_decoder.c:1318:31: warning: variable ‘rx_success_delta’ set but not used [-Wunused-but-set-variable]
1318 | float rx_success_delta = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:1316:31: warning: variable ‘tx_success_delta’ set but not used [-Wunused-but-set-variable]
1316 | float tx_success_delta = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:1313:31: warning: variable ‘rx_success’ set but not used [-Wunused-but-set-variable]
1313 | float rx_success = 0;
| ^~~~~~~~~~
io_decoder.c:1311:31: warning: variable ‘tx_success’ set but not used [-Wunused-but-set-variable]
1311 | float tx_success = 0;
| ^~~~~~~~~~
io_decoder.c:805:13: warning: unused variable ‘serial_verify’ [-Wunused-variable]
805 | int serial_verify;
| ^~~~~~~~~~~~~
io_decoder.c:783:21: warning: unused variable ‘old_usb_loop’ [-Wunused-variable]
783 | static long old_usb_loop;
| ^~~~~~~~~~~~
io_decoder.c:776:20: warning: unused variable ‘old_pin_debug2’ [-Wunused-variable]
776 | static int old_pin_debug2 = 0;
| ^~~~~~~~~~~~~~
io_decoder.c:775:20: warning: unused variable ‘old_hand_invalid_received’ [-Wunused-variable]
775 | static int old_hand_invalid_received = 0;
| ^~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:773:20: warning: unused variable ‘old_hand_invalid_send’ [-Wunused-variable]
773 | static int old_hand_invalid_send = 0;
| ^~~~~~~~~~~~~~~~~~~~~
io_decoder.c:771:20: warning: unused variable ‘old_hand_valid_received’ [-Wunused-variable]
771 | static int old_hand_valid_received = 0;
| ^~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:769:20: warning: unused variable ‘old_hand_valid_send’ [-Wunused-variable]
769 | static int old_hand_valid_send = 0;
| ^~~~~~~~~~~~~~~~~~~
io_decoder.c:755:16: warning: variable ‘comm_packet_received_and_validated’ set but not used [-Wunused-but-set-variable]
755 | static int comm_packet_received_and_validated = 0; // Per controllare il loop all'interno di COMMUNICATING
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:754:16: warning: unused variable ‘comm_packet_sent’ [-Wunused-variable]
754 | static int comm_packet_sent = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:751:16: warning: variable ‘error_state_comm_timeout’ set but not used [-Wunused-but-set-variable]
751 | static int error_state_comm_timeout = 0;
| ^~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:750:16: warning: variable ‘error_state_hand_serial_read’ set but not used [-Wunused-but-set-variable]
750 | static int error_state_hand_serial_read = 0;; // Per seriale in handshake errata
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:749:16: warning: variable ‘error_state_payload’ set but not used [-Wunused-but-set-variable]
749 | static int error_state_payload = 0; // Per payload in handshake errato
| ^~~~~~~~~~~~~~~~~~~
io_decoder.c:748:16: warning: variable ‘error_state_firmware_2’ set but not used [-Wunused-but-set-variable]
748 | static int error_state_firmware_2 = 0; // Per valori funzioni dentro il firmware errate
| ^~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:747:16: warning: variable ‘error_state_firmware’ set but not used [-Wunused-but-set-variable]
747 | static int error_state_firmware = 0; // Per firmware errato
| ^~~~~~~~~~~~~~~~~~~~
io_decoder.c:746:16: warning: variable ‘error_state_hand_packet’ set but not used [-Wunused-but-set-variable]
746 | static int error_state_hand_packet = 0; // Per pacchetto di handshake di lunghezza non corrispondente
| ^~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:745:16: warning: variable ‘error_state_serial_configure’ set but not used [-Wunused-but-set-variable]
745 | static int error_state_serial_configure = 0; // Per seriale non configurata
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:743:16: warning: unused variable ‘error_state_dbg_printed’ [-Wunused-variable]
743 | static int error_state_dbg_printed = 0; // Per HAL_STATE_ERROR (primo ingresso)
| ^~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:740:16: warning: unused variable ‘handshake_response_dbg_printed’ [-Wunused-variable]
740 | static int handshake_response_dbg_printed = 0; // Per HAL_STATE_HANDSHAKE_WAIT_RESPONSE
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:739:16: warning: unused variable ‘handshake_init_dbg_printed’ [-Wunused-variable]
739 | static int handshake_init_dbg_printed = 0; // Per HAL_STATE_HANDSHAKE_INIT
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:737:16: warning: unused variable ‘timer_overflow_5’ [-Wunused-variable]
737 | static int timer_overflow_5 = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:735:16: warning: unused variable ‘timer_overflow_3’ [-Wunused-variable]
735 | static int timer_overflow_3 = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:734:16: warning: unused variable ‘timer_overflow_2’ [-Wunused-variable]
734 | static int timer_overflow_2 = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:733:16: warning: unused variable ‘timer_overflow_1’ [-Wunused-variable]
733 | static int timer_overflow_1 = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:732:16: warning: unused variable ‘timer_overflow_0’ [-Wunused-variable]
732 | static int timer_overflow_0 = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:724:17: warning: variable ‘last_read_time_ns’ set but not used [-Wunused-but-set-variable]
724 | static long last_read_time_ns; // Inizializza al tempo corrente
| ^~~~~~~~~~~~~~~~~
io_decoder.c:720:10: warning: unused variable ‘next_handshake_attempt_time_ns’ [-Wunused-variable]
720 | long next_handshake_attempt_time_ns = 0; // Tempo per il prossimo tentativo di handshake
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c: In function ‘npic_in_routine’:
io_decoder.c:1443:72: warning: unused parameter ‘length’ [-Wunused-parameter]
1443 | uint8_t npic_in_routine(hal_io_decoder_t *io, uint8_t* buffer, uint8_t length) {
| ~~~~~~~~^~~~~~
io_decoder.c: In function ‘npic_out_routine’:
io_decoder.c:1494:68: warning: unused parameter ‘length’ [-Wunused-parameter]
1494 | void npic_out_routine(hal_io_decoder_t *io, uint8_t* dato, uint8_t length) {
| ~~~~~~~~^~~~~~
io_decoder.c: In function ‘encoder_routine’:
io_decoder.c:1577:9: warning: unused variable ‘encoder_delta_from_arduino’ [-Wunused-variable]
1577 | int encoder_delta_from_arduino; // Questo sarà il valore decodificato (il "delta")
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c: In function ‘adc_routine’:
io_decoder.c:1770:57: warning: comparison of integer expressions of different signedness: ‘uint32_t’ {aka ‘unsigned int’} and ‘int32_t’ {aka ‘int’} [-Wsign-compare]
1770 | if ((io->adc_raw_joy[i] <= joy_center_plus) && (io->adc_raw_joy[i] >= joy_center_minus)) {
| ^~
io_decoder.c:1770:100: warning: comparison of integer expressions of different signedness: ‘uint32_t’ {aka ‘unsigned int’} and ‘int32_t’ {aka ‘int’} [-Wsign-compare]
1770 | if ((io->adc_raw_joy[i] <= joy_center_plus) && (io->adc_raw_joy[i] >= joy_center_minus)) {
| ^~
io_decoder.c:1778:48: warning: comparison of integer expressions of different signedness: ‘uint32_t’ {aka ‘unsigned int’} and ‘int32_t’ {aka ‘int’} [-Wsign-compare]
1778 | if (io->adc_raw_joy[i] > (joy_center_plus)) {
| ^
io_decoder.c:1784:55: warning: comparison of integer expressions of different signedness: ‘uint32_t’ {aka ‘unsigned int’} and ‘int32_t’ {aka ‘int’} [-Wsign-compare]
1784 | } else if (io->adc_raw_joy[i] < (joy_center_minus)) {
| ^
io_decoder.c:1680:17: warning: unused variable ‘joy_offset’ [-Wunused-variable]
1680 | int32_t joy_offset;
| ^~~~~~~~~~
io_decoder.c:1678:10: warning: unused variable ‘overflow_period_ns’ [-Wunused-variable]
1678 | long overflow_period_ns = 20 * 1000 * 1000; // Periodo di 20ms per resettare l'overflow
| ^~~~~~~~~~~~~~~~~~
io_decoder.c: In function ‘DAC_out_routine’:
io_decoder.c:1874:13: warning: unused variable ‘i’ [-Wunused-variable]
1874 | int i;
| ^
io_decoder.c: In function ‘in_expansion_routine’:
io_decoder.c:1918:13: warning: unused variable ‘current_value’ [-Wunused-variable]
1918 | float_t current_value = 0; // Useremo un int32_t per gestire il valore
| ^~~~~~~~~~~~~
io_decoder.c:1917:13: warning: unused variable ‘bytesWritten’ [-Wunused-variable]
1917 | uint8_t bytesWritten = 0; // Contatore dei byte letti dal buffer
| ^~~~~~~~~~~~
io_decoder.c:1916:13: warning: unused variable ‘i’ [-Wunused-variable]
1916 | int i;
| ^
io_decoder.c:1914:61: warning: unused parameter ‘buffer’ [-Wunused-parameter]
1914 | uint8_t in_expansion_routine(hal_io_decoder_t *io, uint8_t* buffer, uint8_t length) {
| ~~~~~~~~~^~~~~~
io_decoder.c:1914:77: warning: unused parameter ‘length’ [-Wunused-parameter]
1914 | uint8_t in_expansion_routine(hal_io_decoder_t *io, uint8_t* buffer, uint8_t length) {
| ~~~~~~~~^~~~~~
io_decoder.c: In function ‘out_expansion_routine’:
io_decoder.c:1951:13: warning: unused variable ‘current_value’ [-Wunused-variable]
1951 | float_t current_value = 0; // Useremo un int32_t per gestire il valore
| ^~~~~~~~~~~~~
io_decoder.c:1950:13: warning: unused variable ‘bytesWritten’ [-Wunused-variable]
1950 | uint8_t bytesWritten = 0; // Contatore dei byte letti dal buffer
| ^~~~~~~~~~~~
io_decoder.c:1949:13: warning: unused variable ‘i’ [-Wunused-variable]
1949 | int i;
| ^
io_decoder.c:1947:59: warning: unused parameter ‘dato’ [-Wunused-parameter]
1947 | void out_expansion_routine(hal_io_decoder_t *io, uint8_t* dato, uint8_t length) {
| ~~~~~~~~~^~~~
io_decoder.c:1947:73: warning: unused parameter ‘length’ [-Wunused-parameter]
1947 | void out_expansion_routine(hal_io_decoder_t *io, uint8_t* dato, uint8_t length) {
| ~~~~~~~~^~~~~~
io_decoder.c: In function ‘key_simulation_loop’:
io_decoder.c:2032:53: warning: variable ‘suppress_base_key’ set but not used [-Wunused-but-set-variable]
2032 | int suppress_base_key = 0; // 0 = false, 1 = true
| ^~~~~~~~~~~~~~~~~
io_decoder.c:2004:9: warning: unused variable ‘send_status’ [-Wunused-variable]
2004 | int send_status;
| ^~~~~~~~~~~
io_decoder.c:2001:13: warning: unused variable ‘bitValue’ [-Wunused-variable]
2001 | uint8_t bitValue;
| ^~~~~~~~
io_decoder.c: In function ‘calc_io_decoder’:
io_decoder.c:2421:45: warning: unused parameter ‘period’ [-Wunused-parameter]
2421 | static void calc_io_decoder(void *arg, long period) {
| ~~~~~^~~~~~
io_decoder.c: In function ‘rtapi_app_main’:
io_decoder.c:2484:28: warning: unused variable ‘uidev’ [-Wunused-variable]
2484 | struct uinput_user_dev uidev;
| ^~~~~
io_decoder.c:2471:20: warning: unused variable ‘i’ [-Wunused-variable]
2471 | int n, retval, i;
| ^
io_decoder.c: In function ‘npic_in_routine’:
io_decoder.c:1491:1: warning: control reaches end of non-void function [-Wreturn-type]
1491 | }
| ^
io_decoder.c: In function ‘adc_routine’:
io_decoder.c:1870:1: warning: control reaches end of non-void function [-Wreturn-type]
1870 | }
| ^
io_decoder.c: In function ‘in_expansion_routine’:
io_decoder.c:1945:1: warning: control reaches end of non-void function [-Wreturn-type]
1945 | }
| ^
io_decoder.c: At top level:
io_decoder.c:732:16: warning: ‘timer_overflow_0’ defined but not used [-Wunused-variable]
732 | static int timer_overflow_0 = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:733:16: warning: ‘timer_overflow_1’ defined but not used [-Wunused-variable]
733 | static int timer_overflow_1 = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:734:16: warning: ‘timer_overflow_2’ defined but not used [-Wunused-variable]
734 | static int timer_overflow_2 = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:735:16: warning: ‘timer_overflow_3’ defined but not used [-Wunused-variable]
735 | static int timer_overflow_3 = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:737:16: warning: ‘timer_overflow_5’ defined but not used [-Wunused-variable]
737 | static int timer_overflow_5 = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:739:16: warning: ‘handshake_init_dbg_printed’ defined but not used [-Wunused-variable]
739 | static int handshake_init_dbg_printed = 0; // Per HAL_STATE_HANDSHAKE_INIT
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:740:16: warning: ‘handshake_response_dbg_printed’ defined but not used [-Wunused-variable]
740 | static int handshake_response_dbg_printed = 0; // Per HAL_STATE_HANDSHAKE_WAIT_RESPONSE
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:743:16: warning: ‘error_state_dbg_printed’ defined but not used [-Wunused-variable]
743 | static int error_state_dbg_printed = 0; // Per HAL_STATE_ERROR (primo ingresso)
| ^~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:754:16: warning: ‘comm_packet_sent’ defined but not used [-Wunused-variable]
754 | static int comm_packet_sent = 0;
| ^~~~~~~~~~~~~~~~
io_decoder.c:769:20: warning: ‘old_hand_valid_send’ defined but not used [-Wunused-variable]
769 | static int old_hand_valid_send = 0;
| ^~~~~~~~~~~~~~~~~~~
io_decoder.c:771:20: warning: ‘old_hand_valid_received’ defined but not used [-Wunused-variable]
771 | static int old_hand_valid_received = 0;
| ^~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:773:20: warning: ‘old_hand_invalid_send’ defined but not used [-Wunused-variable]
773 | static int old_hand_invalid_send = 0;
| ^~~~~~~~~~~~~~~~~~~~~
io_decoder.c:775:20: warning: ‘old_hand_invalid_received’ defined but not used [-Wunused-variable]
775 | static int old_hand_invalid_received = 0;
| ^~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:776:20: warning: ‘old_pin_debug2’ defined but not used [-Wunused-variable]
776 | static int old_pin_debug2 = 0;
| ^~~~~~~~~~~~~~
io_decoder.c:783:21: warning: ‘old_usb_loop’ defined but not used [-Wunused-variable]
783 | static long old_usb_loop;
| ^~~~~~~~~~~~
io_decoder.c:643:14: warning: ‘format_buffer_for_debug’ defined but not used [-Wunused-function]
643 | static char* format_buffer_for_debug(const unsigned char *buffer, int len) {
| ^~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:148:12: warning: ‘printed_debug’ defined but not used [-Wunused-variable]
148 | static int printed_debug[100] = {0}; //serve a creare uno stop alle scritte di debug
| ^~~~~~~~~~~~~
io_decoder.c: In function ‘usb_thread_function’:
io_decoder.c:1297:28: warning: ‘usb2_current_time_ns’ may be used uninitialized [-Wmaybe-uninitialized]
1297 | usb_jitter_time_ns = usb_current_time_ns - usb2_current_time_ns;
| ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:729:10: note: ‘usb2_current_time_ns’ was declared here
729 | long usb2_current_time_ns;
| ^~~~~~~~~~~~~~~~~~~~
io_decoder.c:1297:28: warning: ‘usb_current_time_ns’ may be used uninitialized [-Wmaybe-uninitialized]
1297 | usb_jitter_time_ns = usb_current_time_ns - usb2_current_time_ns;
| ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:728:10: note: ‘usb_current_time_ns’ was declared here
728 | long usb_current_time_ns;
| ^~~~~~~~~~~~~~~~~~~
io_decoder.c:1308:38: warning: ‘next_parse_error_time_ns’ may be used uninitialized [-Wmaybe-uninitialized]
1308 | if ((current_time_ns - next_parse_error_time_ns) > parse_error_time_ns) {
| ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c:726:10: note: ‘next_parse_error_time_ns’ was declared here
726 | long next_parse_error_time_ns;
| ^~~~~~~~~~~~~~~~~~~~~~~~
io_decoder.c: In function ‘npic_out_routine’:
io_decoder.c:1550:107: warning: ‘freq_conversion’ may be used uninitialized [-Wmaybe-uninitialized]
1550 | if (current_time_ns > (io->out_blink_time_ns[current_bit] + freq_conversion)) {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
io_decoder.c:1503:10: note: ‘freq_conversion’ was declared here
1503 | long freq_conversion;
| ^~~~~~~~~~~~~~~
Linking io_decoder.so“Bare-Metal” Firmware (No Bootloader)
We have removed the Arduino bootloader for two critical reasons:
Safety: No random pin state changes during startup (typical of bootloaders).
Speed: The board is operational within a few milliseconds.
what about updates ?
Please Log in or Create an account to join the conversation.
- Hardware & Machines
- Driver Boards
- Solving the USB Latency Dogma for HMI/MPG: Technical Feedback Request