tilibs icon indicating copy to clipboard operation
tilibs copied to clipboard

libticables: Introduce a cable for using GPIO pins on RPi SBCs

Open Vogtinator opened this issue 2 years ago • 6 comments

With this cable, a calculator can be connected to pins 16 (Tip/Red) and 18 (Ring/White) ideally through a ~1k resistor. Transmission happens by switching the lines between tristate input and 0V output states.

In case the voltage of the calculator exceeds 3.3V, diodes should be used in addition to the resistors to limit the line voltage to 3V3 to avoid damage.

With this cable I can receive files with speeds up to 4.0kB/s on a RPi2. The bottleneck is the slow mechanism to read and write GPIO values.

Depends on #62

TODO:

  • [ ] Some error conditions are not propagated upwards yet. What are the right codes for e.g. failure to read a GPIO?
  • [ ] No integration with autotools

Vogtinator avatar Feb 20 '22 17:02 Vogtinator

With this cable I can receive files with speeds up to 4.0kB/s on a RPi2. The bottleneck is the slow mechanism to read and write GPIO values.

For completeness, here's a branch which replaces use of libgpiod with direct mapped access to the GPIO controller: https://github.com/Vogtinator/tilibs/tree/gpiommap. With that I get transfer speeds of 8.4kB/s, still using the simple setup with just two 1k resistors.

Vogtinator avatar Feb 20 '22 20:02 Vogtinator

Thanks :) I don't really have access to IRC tonight.

Several notes:

  • the TI-Z80 series uses 5V, while the TI-68k series uses 3.3V but is 5V tolerant, and the two series are interoperable, if one uses software such as X-Link on the TI-Z80 side. But maybe you meant that the RPi isn't 5V tolerant, and that it's the side which needs protection from the calculators ?
  • I think that while the main target for such a GPIO cable would arguably be the RPi series, given how widespread it is, a libgpiod-based GPIO cable shouldn't be limited to the RPi. If nothing else, the device name and GPIO pin numbers should be constants defined at the beginning of the file, so that it's (slightly) easier to redefine them at compile time if needed. But see the next item and the bottom of this comment;
  • given that it's significantly faster, rivaling or beating a SilverLink (!), it would be sad not to keep the mmap()-based approach around under some form. IMO, that hints towards a second GPIO cable... this time, certainly a GPIO_MMAP_RPI platform-specific cable, since I have a feeling that it's insane to impose on you, me or anybody else the burden of trying to come up with a generic MMGPIO cable able to deal with whichever set of quirks a random GPIO controller might have :)
  • you can add a couple error codes for open and close in libticables/trunk/src/error.h; ERR_READ_ERROR and ERR_WRITE_ERROR coupled with ticables_warning() usage, as you've started doing, could arguably be reasonable ways to communicate GPIO R/W failure. The mmap()-based cable would require at least another specific error code for the inability to map memory, both the TIEmu and VTI cables already have such error codes.

For item 2 (and 3, if you want to provide the ability to use other GPIO pins ?), bonus points for implementing, say, a struct containing a version number + extra parameters to be passed by a new API similar to ticables_cable_set_device() to the cable implementation before the cable open function is called. ticables_cable_set_device() could become a deprecated wrapper for the new API, sending a struct version 1 limited to the device, while struct version 2 would be device + new parameters. This approach would kill two birds with one stone, as such advanced setup functionality is also needed for run-time handling of the several third-party GrayLink-like cables which have been springing around in the past year or so, and have their own quirks: device, speed, need to enable RTS/CTS are the ones we saw so far. No obligation to implement a UI for setting up these special parameters: something along of the line of new "cable_gpio_*" entries in the DEVICE section of the TILP config file (~/.tilp), several lines of code for parsing these and storing the config information into TILP's internal config structures, and calling the new libticables API I described before ticables_cable_open() shall do the job just fine, in my book.

How does that sound ? It shouldn't be too much of a burden in terms of development time, plus you don't have to do it entirely alone.

debrouxl avatar Feb 20 '22 21:02 debrouxl

Thanks :) I don't really have access to IRC tonight.

Several notes:

  • the TI-Z80 series uses 5V, while the TI-68k series uses 3.3V but is 5V tolerant, and the two series are interoperable, if one uses software such as X-Link on the TI-Z80 side. But maybe you meant that the RPi isn't 5V tolerant, and that it's the side which needs protection from the calculators ?

Yep.

  • I think that while the main target for such a GPIO cable would arguably be the RPi series, given how widespread it is, a libgpiod-based GPIO cable shouldn't be limited to the RPi. If nothing else, the device name and GPIO pin numbers should be constants defined at the beginning of the file, so that it's (slightly) easier to redefine them at compile time if needed. But see the next item and the bottom of this comment;

Can be done, but it's hardcoded in only a single place, so moving that elsewhere wouldn't really help much.

  • given that it's significantly faster, rivaling or beating a SilverLink (!), it would be sad not to keep the mmap()-based approach around under some form. IMO, that hints towards a second GPIO cable... this time, certainly a GPIO_MMAP_RPI platform-specific cable, since I have a feeling that it's insane to impose on you, me or anybody else the burden of trying to come up with a generic MMGPIO cable able to deal with whichever set of quirks a random GPIO controller might have :)

Yep. One issue is that the addresses are different depending on which RPi it is, so that would need some extra code and testing. It also needs root privs more than the libgpiod cable and a special kernel parameter (depending on kernel config).

  • you can add a couple error codes for open and close in libticables/trunk/src/error.h; ERR_READ_ERROR and ERR_WRITE_ERROR coupled with ticables_warning() usage, as you've started doing, could arguably be reasonable ways to communicate GPIO R/W failure. The mmap()-based cable would require at least another specific error code for the inability to map memory, both the TIEmu and VTI cables already have such error codes.

Ok.

For item 2 (and 3, if you want to provide the ability to use other GPIO pins ?), bonus points for implementing, say, a struct containing a version number + extra parameters to be passed by a new API similar to ticables_cable_set_device() to the cable implementation before the cable open function is called. ticables_cable_set_device() could become a deprecated wrapper for the new API, sending a struct version 1 limited to the device, while struct version 2 would be device + new parameters. This approach would kill two birds with one stone, as such advanced setup functionality is also needed for run-time handling of the several third-party GrayLink-like cables which have been springing around in the past year or so, and have their own quirks: device, speed, need to enable RTS/CTS are the ones we saw so far. No obligation to implement a UI for setting up these special parameters: something along of the line of new "cable_gpio_*" entries in the DEVICE section of the TILP config file (~/.tilp), several lines of code for parsing these and storing the config information into TILP's internal config structures, and calling the new libticables API I described before ticables_cable_open() shall do the job just fine, in my book.

One issue is that all of those parameters are highly cable specific and basically need hardcoded integration on the library user side as well, which means that it can't really be abstracted easily. Maybe the best way is to just not abstract it at all and have some cable specific structs...

How does that sound ? It shouldn't be too much of a burden in terms of development time, plus you don't have to do it entirely alone.

Once we have an idea of how the API could look like that's probably worth a try.

Vogtinator avatar Mar 20 '22 14:03 Vogtinator

given that it's significantly faster, rivaling or beating a SilverLink (!), it would be sad not to keep the mmap()-based approach around under some form.

FWIW, I got around ~9.2kB/s by optimizing the algorithm a bit. The bottleneck appears to be the calculator (V200 in this case) which needs ~10µS between receiving bits. So it's unfortunately not possible to go beyond 10kB/s.

The speed calculation also slows transfers down a bit, as it forces transfers to happen with a maximum block size of total/20: https://github.com/debrouxl/tilibs/blob/8ffa244e522a484146fe0d7c1130a554f8dba48e/libticalcs/trunk/src/dbus_pkt.cc#L241 This only really affects smaller files though, particularly screenshots.

IMO, that hints towards a second GPIO cable... this time, certainly a GPIO_MMAP_RPI platform-specific cable, since

Question is how to share common code between those cables. It's essentially just the open/close and set_line/get_raw_masked functions which differ. It might even be possible to deduplicate code with the serial/parallel cables that way...

Vogtinator avatar Mar 20 '22 14:03 Vogtinator

Thanks.

  • I think that while the main target for such a GPIO cable would arguably be the RPi series, given how widespread it is, a libgpiod-based GPIO cable shouldn't be limited to the RPi. If nothing else, the device name and GPIO pin numbers should be constants defined at the beginning of the file, so that it's (slightly) easier to redefine them at compile time if needed. But see the next item and the bottom of this comment;

Can be done, but it's hardcoded in only a single place, so moving that elsewhere wouldn't really help much.

And infrastructure for advanced cable parameters makes it moot anyway.

  • given that it's significantly faster, rivaling or beating a SilverLink (!), it would be sad not to keep the mmap()-based approach around under some form. IMO, that hints towards a second GPIO cable... this time, certainly a GPIO_MMAP_RPI platform-specific cable, since I have a feeling that it's insane to impose on you, me or anybody else the burden of trying to come up with a generic MMGPIO cable able to deal with whichever set of quirks a random GPIO controller might have :)

Yep. One issue is that the addresses are different depending on which RPi it is, so that would need some extra code and testing. It also needs root privs more than the libgpiod cable and a special kernel parameter (depending on kernel config).

Good points, I didn't think about that.

For item 2 (and 3, if you want to provide the ability to use other GPIO pins ?), bonus points for implementing, say, a struct containing a version number + extra parameters to be passed by a new API similar to ticables_cable_set_device() to the cable implementation before the cable open function is called. [ ... ]

One issue is that all of those parameters are highly cable specific and basically need hardcoded integration on the library user side as well, which means that it can't really be abstracted easily. Maybe the best way is to just not abstract it at all and have some cable specific structs...

In fact, libticables already has a struct CableOptions, but it doesn't use it at all. The only known consumer is TilEm, for its internal purposes, and it doesn't use the calc field (which, nowadays, should have CalcModel type anyway, since that now lives in libticonv). This gives significant freedom in expanding CableOptions; source-level and backwards compatibility doesn't have to be broken.

How does that sound ? It shouldn't be too much of a burden in terms of development time, plus you don't have to do it entirely alone.

Once we have an idea of how the API could look like that's probably worth a try.

An early proposal for the new CableOptions struct, and a setter + getter for it, at https://github.com/debrouxl/tilibs/tree/ticables_cable_options . I developed it against master, but it applies equally well to experimental2: most of the action in libti* happens in libticalcs. Thoughts ?

given that it's significantly faster, rivaling or beating a SilverLink (!), it would be sad not to keep the mmap()-based approach around under some form.

FWIW, I got around ~9.2kB/s by optimizing the algorithm a bit. The bottleneck appears to be the calculator (V200 in this case) which needs ~10µS between receiving bits. So it's unfortunately not possible to go beyond 10kB/s.

The speed calculation also slows transfers down a bit, as it forces transfers to happen with a maximum block size of total/20:

https://github.com/debrouxl/tilibs/blob/8ffa244e522a484146fe0d7c1130a554f8dba48e/libticalcs/trunk/src/dbus_pkt.cc#L241

This only really affects smaller files though, particularly screenshots.

Good point. If only this were the only issue with speed calculation, though :)

IMO, that hints towards a second GPIO cable... this time, certainly a GPIO_MMAP_RPI platform-specific cable, since

Question is how to share common code between those cables. It's essentially just the open/close and set_line/get_raw_masked functions which differ. It might even be possible to deduplicate code with the serial/parallel cables that way...

There's already some code sharing for e.g. the USB cables, so go ahead for the GPIO cables :)

debrouxl avatar Mar 20 '22 22:03 debrouxl

RFS: https://gist.github.com/rvalles/f937889712d24ac6824f1358c936b3e2 is the patch to deal with one of the "weird GrayLink" implementations, this one needs all three of speed, device and RTS / CTS enablement quirks.

debrouxl avatar Mar 27 '22 21:03 debrouxl