ftdi-rs / libftd2xx

Rust safe wrappers for the libftd2xx drivers
MIT License
31 stars 15 forks source link

[FEATURE REQUEST] Asynchronous Bit Bang Mode allowing to receive D0 .. D7 #9

Open silvioprog opened 4 years ago

silvioprog commented 4 years ago

Hi.

Thanks for this awesome library!

So, it would be nice to allow to receive the data D0 .. D7 from the board in asynchronous bit bang mode. For example, supposing a circuit uses a FT232R with its pins 1 (D0), 5 (D1), 3 (D2), 11 (D3), 2 (D4), 9 (D5), 10 (D6) and 6 (D7) mapped to eight switches with pull-up resistors, it would be possible to detect if the switches are ON or OFF. Below a small example showing how to do that in C code:

#include <stdio.h>
#include <stdbool.h>
#include "ftd2xx.h"

static void die(CONST PUCHAR msg) {
    printf("%s\n", msg);
    exit(1);
}

static bool pinStatus(UCHAR data, UCHAR pin) {
    return data & (1 << pin);
}

static const char *pinStatusString(UCHAR data, UCHAR pin) {
    return pinStatus(data, pin) ? "ON" : "OFF";
}

int main(void) {
    FT_HANDLE ftHandle;
    FT_STATUS ftStatus;
    UCHAR data;
    UCHAR Mask = 0x00; // Set all ports as Input
    UCHAR Mode = FT_BITMODE_SYNC_BITBANG; // Set asynchronous bit-bang mode
    ftStatus = FT_Open(0, &ftHandle);
    if (ftStatus != FT_OK) {
        die("FT_Open failed\n");
    }
    ftStatus = FT_SetBitMode(ftHandle, Mask, Mode);
    if (ftStatus != FT_OK) {
        FT_Close(ftHandle);
        die("FT_SetBitMode failed\n");
    }
    while (1) {
        data = 0;
        ftStatus = FT_GetBitMode(ftHandle, &data);
        if (ftStatus != FT_OK) {
            FT_Close(ftHandle);
            die("FT_GetBitMode failed\n");
        }
        for (UCHAR i = 0; i < 7; i++) {
            printf("PIN %d: %s\n", i + 1, pinStatusString(data, i));
        }
        printf("\n");
        Sleep(500); // waits 500 ms
    }
    FT_Close(ftHandle);
    return 0;
}

// Prints ON/OFF according to the switches states, e.g.:
// PIN 1: ON
// PIN 2: OFF
// PIN 3: OFF
// PIN 4: ON
// PIN 5: ON
// PIN 6: OFF
// PIN 7: ON

A good reference for implementation is the official app note AN_232R-01 (topic 2 Asynchronous Bit Bang Mode) available at: https://www.ftdichip.com/Support/Documents/AppNotes/AN_232R-01_Bit_Bang_Mode_Available_For_FT232R_and_Ft245R.pdf

cheers

newAM commented 4 years ago

Oooooooh! That's what that function was for!

I thought it was for reading the BitMode set with FT_SetBitMode, but it's the state of the bus! I added this method a while ago, but it never returned the BitMode I set with FT_SetBitMode; I assumed I had a bug somewhere and left it out.

Anyway, moment of clarity aside I pushed a branch bitmode with the change, there is now a bit_mode method on FtdiCommon and an example that contains similar code to yours (but without the while(1) since rust was not too keen on the unreachable FT_Close at the bottom).

use libftd2xx::{BitMode, Ftdi, FtdiCommon, TimeoutError};

fn main() -> Result<(), TimeoutError> {
    let mut ft = Ftdi::new()?;
    let mask: u8 = 0x00;
    ft.set_bit_mode(mask, BitMode::AsyncBitbang)?;
    let mode = ft.bit_mode()?;
    for pin_index in 0..8 {
        if mode & (1 << pin_index) == 0 {
            println!("Pin {}: Off", pin_index);
        } else {
            println!("Pin {}: On", pin_index);
        }
    }
    ft.close()?;

    Ok(())
}

Gave it a go with my FT232H, and it seems to work as expected when I tie ADBUS6 to GND.

$ cargo run --example async_bitbang
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/examples/async_bitbang`
Pin 0: On
Pin 1: On
Pin 2: On
Pin 3: On
Pin 4: On
Pin 5: On
Pin 6: Off
Pin 7: On

Can you give this a go and let me know if it solves the issue?

newAM commented 4 years ago

I did a 0.13.0 release with the content of the bitmode branch (now deleted), if it doesn't solve the issue I can always do another release, not too concerned about releases right now because this crate is not yet 1.x.

silvioprog commented 4 years ago

Wow, this is great news! I'm going to test it in my board and back with some feedback.

Indeed, the FT_GetBitMode() sounds like game cheats hehe. :-)

Another awesome feature to use with FT_GetBitMode() is the FT_SetEventNotification() notifier. It allows to get the pins state asynchronous using events (WaitForSingleObject() / pthread_cond_wait()) instead of locking like while(1).

newAM commented 4 years ago

That's a neat function that I have not gotten to yet!

Do you know what WaitForSingleObject()/pthread_cond_wait() would look like in rust?

It looks like I can just call into libc on the linux side of things, and there is a crate here for Windows things; but I am not sure if there are any higher level abstractions I should be using.

silvioprog commented 4 years ago

The example async_bitbang.rs worked like a charm with my board. Thank you very much for implementing it! :-)

Regarding FT_SetEventNotification(), I totally agree with you by using libc/winapi-rs. The topic 3.28 FT_SetEventNotification of D2XX_Programmer's_Guide.pdf provides two examples that can be a good start point to understand how to use the function on Linux and Windows.

newAM commented 4 years ago

I implemented the dependency methods (FT_GetStatus and FT_GetModemStatus) on the master branch, and there is a topic branch issue/9 with dirty work.

I did hit one snag on the Windows side. The Windows example in the D2XX Programmer's Guide uses CreateEvent, which appears to be deprecated? At any rate there are only functions for CreateEventA, and CreateEventW in the winapi crate.

There is a disconnect that needs to be resolved here - but I am not yet sure where.

Thinking out loud:

Do you have any guidance here? I usually use Linux at work and at home so these Windows things are still new to me :smile:

silvioprog commented 4 years ago

Thanks for starting this great feature!

The wide strings are commonly used in Rust world, so the CreateEventW() is a good choice. Passing its last parameter lpName as something like libftd2xx-event (in UTF-16) would be welcome too. :-)

I have a naive tip. Maybe adding files like linux.rs and windows.rs would make it easy to implement the event logic for each OS. This small library provides some idea how to separate the OS-specific logic in separated files: lock_keys/src.

You have written clean and easy-to-use interfaces. I'm going to check the new branch and back with some feedback ... (and try to help you in the implementation for Windows)

newAM commented 4 years ago

The wide strings are commonly used in Rust world, so the CreateEventW() is a good choice. Passing its last parameter lpName as something like libftd2xx-event (in UTF-16) would be welcome too. :-)

Oh, whoops, I missed the giant purple box at the bottom of the Microsoft docs that explains this, thanks for pointing that out, makes much more sense now!

The synchapi.h header defines CreateEvent as an alias which automatically selects the ANSI or Unicode version of this function based on the definition of the UNICODE preprocessor constant. Mixing usage of the encoding-neutral alias with code that not encoding-neutral can lead to mismatches that result in compilation or runtime errors. For more information, see Conventions for Function Prototypes.

I have a naive tip. Maybe adding files like linux.rs and windows.rs would make it easy to implement the event logic for each OS. This small library provides some idea how to separate the OS-specific logic in separated files: lock_keys/src.

That's definitely a better more ergonomic way to lay this out than what I was thinking of :+1: