C++:带 D2XX 驱动程序的 Bit-banging(USB 到串行 UART)FTDI 模块

标签 c++ visual-c++ serial-port uart ftdi

我目前正在尝试弄清楚我如何才能简单地打开或关闭 my module 的一个可敲击的引脚之一 |它使用FT232RL芯片。

我目前正在使用以下源代码 from this tutorial (稍作修改以在 Visual C++ 中工作):

/* 8-bit PWM on 4 LEDs using FTDI cable or breakout.
   This example uses the D2XX API.
   Minimal error checking; written for brevity, not durability. */

#include "stdafx.h"
#include <windows.h>
#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <ftd2xx.h>

#define LED1 0x08  /* CTS (brown wire on FTDI cable) */
#define LED2 0x01  /* TX  (orange) */
#define LED3 0x02  /* RX  (yellow) */
#define LED4 0x14  /* RTS (green on FTDI) + DTR (on SparkFun breakout) */

int _tmain(int argc, _TCHAR* argv[])
{
    int i,n;
    unsigned char data[255 * 256];
    FT_HANDLE handle;
    DWORD bytes;

    /* Generate data for a single PWM 'throb' cycle */
    memset(data, 0, sizeof(data));
    for(i=1; i<128; i++) {
        /* Apply gamma correction to PWM brightness */
        n = (int)(pow((double)i / 127.0, 2.5) * 255.0);
        memset(&data[i * 255], LED1, n);         /* Ramp up */
        memset(&data[(256 - i) * 255], LED1, n); /* Ramp down */
    }   

    /* Copy data from first LED to others, offset as appropriate */
    n = sizeof(data) / 4;
    for(i=0; i<sizeof(data); i++)
    {
        if(data[i] & LED1) {
            data[(i + n    ) % sizeof(data)] |= LED2;
            data[(i + n * 2) % sizeof(data)] |= LED3;
            data[(i + n * 3) % sizeof(data)] |= LED4;
        }
    }   

    /* Initialize, open device, set bitbang mode w/5 outputs */
    if(FT_Open(0, &handle) != FT_OK) {
        puts("Can't open device");
        return 1;
    }
    FT_SetBitMode(handle, LED1 | LED2 | LED3 | LED4, 1);
    FT_SetBaudRate(handle, 9600);  /* Actually 9600 * 16 */

    /* Endless loop: dump precomputed PWM data to the device */
    for(;;) FT_Write(handle, &data, (DWORD)sizeof(data), &bytes);


    _getch();
    return 0;
}

但是我就是不明白。这段代码中哪里发生了魔法?有没有办法将重要的代码部分放入两个基本函数 Turn_Pin_1_On() 和 Turn_Pin_1_Off()。

最佳答案

详细解释嵌入在下面的源代码中(警告:这仅是大脑编译的,所以请注意购买者)。不过,解释的精神是成立的。代码有时可能有点不必要地臃肿,但这样做是为了详细解释 PWM 部分的操作以及位掩码的基础知识。

/* 8-bit PWM on 4 LEDs using FTDI cable or breakout.
   This example uses the D2XX API.
   Minimal error checking; written for brevity, not durability. */

#include "stdafx.h"
#include <windows.h>
#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <ftd2xx.h>

#define LED1 0x08  /* CTS (brown wire on FTDI cable) */
#define LED2 0x01  /* TX  (orange) */
#define LED3 0x02  /* RX  (yellow) */
#define LED4 0x14  /* RTS (green on FTDI) + DTR (on SparkFun breakout) */
/*
WARNING:  don't have a FTDI device for proper
testing, so everything here is BRAIN COMPILED.
CAVEAT EMPTOR.

It is very simple really. Every time you send a byte out 
either using FT_Write (for large buffers of precomputed bytes),
or using ftdi_write_data(...), the content of your byte directly maps
to the state of those pins. Say I display the content of a byte as follows
with 1 marking a bit that is set, and empty marking a bit that is clear (0)

                THESE ARE THE BITS IN YOUR BYTE
 bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
+------+------+------+------+------+------+------+------+
|      |      |      |      |      |      |      |      |
+------+------+------+------+------+------+------+------+

If FTDI module receives a byte, where:
      - bit 0 is set:   then orange (TX)  wire LED will turn ON
      - bit 0 is clear: then orange (TX)  wire LED will turn OFF
      - bit 1 is set:   then yellow (RX)  wire LED will turn ON
      - bit 1 is clear: then yellow (RX)  wire LED will turn OFF
      - bit 2 is set:   then green (RTS)  wire will turn ON
      - bit 2 is clear: then green( RTS)  wire will turn OFF
      - bit 3 is set:   then brown (CTS)  wire LED will turn ON
      - bit 3 is clear: then brown (CTS)  wire LED will turn OFF
      - bit 4 is set:   then DTR(on sparkfun) wire will turn ON
      - bit 4 is clear: then DTR(on sparkfun) wire will turn OFF

Now, your defines above for LED1, LED2, LED3, LED4 define the
bit states needed to turn on individual LEDS.
For example, look at your LED1 constant:

                LED1 constant (0x08 -> 0b00001000)
 bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
+------+------+------+------+------+------+------+------+
|      |      |      |      |  X   |      |      |      |
+------+------+------+------+------+------+------+------+
   ...thus sending this directly will turn brown LED ON
      AND turn off all other LEDs.

The LED4 actually turns 2 wires on at a time. Because the FTDI only
implements RTS, and the Sparkfun only implements DTR, in practice 
one of those wires turn out to be unused by either module, but
for the sake of simplicity the coders decided to control both
bits at the same time

                LED4 constant (0x14 -> 0b00010100)
 bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
+------+------+------+------+------+------+------+------+
|      |      |      |  X   |      |  X   |      |      |
+------+------+------+------+------+------+------+------+
   thus sending this directly will turn green & DTR LEDs ON
      AND turn off all other LEDs.

Now, what if, say I want to turn LED 1 and LED4 ON? Then I must
combine both constants together

               Example: turn LED1 & LED 4 ON, turn off LED2 & LED 3
               Command to be sent:  0x08 + 0x14 = 0x1C
 bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
+------+------+------+------+------+------+------+------+
|      |      |      |  X   |  X   |  X   |      |      |
+------+------+------+------+------+------+------+------+



a simple function that gets the proper formatted byte for a desired LED 
state would be:
*/
unsigned char GetByteForLEDState(BOOL isLED1On, BOOL isLED2On, BOOL isLED3On, BOOL isLED4On){
    unsigned char ret=0x00;
    if(isLED1On) ret += LED1;
    if(isLED2On) ret += LED2;
    if(isLED3On) ret += LED3;
    if(isLED4On) ret += LED4;
    return ret;
}
/*
Now, you see that a problem with this method is that you cannot set/clear a 
LED off without changing the others at the same time: they all need to be 
set/cleared simultaneously. So how do you make it so that you can leave 
certain LEDs unchanged while setting/clearing some others, in an easy way? 
C offers the bitmasking strategy in order to set/clear bits individually in 
a byte without touching others. 

Example: I have BYTE STATE = 0x1C, 
(LED 1 & LED4 ON), and I need to turn off LED1 but leave LED4 untouched

How do I make that happen? By bitwise ANDing the STATE byte with 
the LED1 constant inverted

             OLD STATE (bits) original state:
 bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
+------+------+------+------+------+------+------+------+
|      |      |      |  X   |  X   |  X   |      |      |
+------+------+------+------+------+------+------+------+

                        BITWISE AND WITH...

     Inverse of LED1 constant
 bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
+------+------+------+------+------+------+------+------+
|  X   |  X   |  X   |  X   |      |  X   |  X   |  X   |
+------+------+------+------+------+------+------+------+

                          YIELDS...

                         NEW STATE
 bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
+------+------+------+------+------+------+------+------+
|      |      |      |  X   |      |  X   |      |      |
+------+------+------+------+------+------+------+------+
    STATE = STATE & (0x08)

As you can see, LED1 was turned off, and LED4 is still On.
in practice, the C style operation I just did was this:

STATE = STATE & (~LED1);

To turn an individual LED ON, say LED1 back on,
without touching the others in a given LED state byte, 
I do it via bitwise ORing the STATE byte with constant LED1 

           OLD STATE (bits) original state:
 bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
+------+------+------+------+------+------+------+------+
|      |      |      |  X   |      |  X   |      |      |
+------+------+------+------+------+------+------+------+

                   BITWISE OR WITH...

                LED1 constant (0x08 -> 0b00001000)
 bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
+------+------+------+------+------+------+------+------+
|      |      |      |      |  X   |      |      |      |
+------+------+------+------+------+------+------+------+

                     YIELDS...

                    NEW STATE
 bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
+------+------+------+------+------+------+------+------+
|      |      |      |  X   |  X   |  X   |      |      |
+------+------+------+------+------+------+------+------+

In practice, the C style operation I just did was this:

STATE = STATE | LED1;

so what would be the stateless functions that implement those? well:
say we use the specific format for our stateless functions:
*/
unsigned char STATELESS_MODIFIER_FUNCTION(unsigned char originalState){ 
    unsigned char newState = originalState;
    /* Do some operations here... */
    return newState;
}

/* The specific implementations for PIN1 would go like this: */
unsigned char Turn_Pin_1_On(unsigned char originalState){   
    return originalState | LED1;     /* we use bitwise OR */
} 
unsigned char Turn_Pin_1_Off(unsigned char originalState){
    return originalState & (~LED1);  /*we use bitwise AND with INVERSE */
} 
unsigned char Toggle_Pin_1(unsigned char originalState){ 
    return originalState ^ LED1;     /*we use bitwise XOR */
} 

unsigned char Write_Pin_1_State(unsigned char originalState, BOOL pinState){
    if(pinState) return Turn_Pin_1_On();
    else return Turn_Pin_1_Off();
}

/*Same for PIN 2: */
unsigned char Turn_Pin_2_On(unsigned char originalState){   return originalState | LED2; } /*we use bitwise OR */
unsigned char Turn_Pin_2_Off(unsigned char originalState){ return originalState & (~LED2); } /*we use bitwise AND with INVERSE */
unsigned char Toggle_Pin_2(unsigned char originalState){ return originalState ^ LED2; } /*we use bitwise XOR */

unsigned char Write_Pin_2_State(unsigned char originalState, BOOL pinState){
    if(pinState) return Turn_Pin_2_On();
    else return Turn_Pin_2_Off();
}

/*Same for PIN 3: */
unsigned char Turn_Pin_3_On(unsigned char originalState){   return originalState | LED3; } /*we use bitwise OR */
unsigned char Turn_Pin_3_Off(unsigned char originalState){ return originalState & (~LED3); } /*we use bitwise AND with INVERSE */
unsigned char Toggle_Pin_3(unsigned char originalState){ return originalState ^ LED3; } /*we use bitwise XOR */

unsigned char Write_Pin_3_State(unsigned char originalState, BOOL pinState){
    if(pinState) return Turn_Pin_3_On();
    else return Turn_Pin_3_Off();
}

/*Same for PIN 4: */
unsigned char Turn_Pin_4_On(unsigned char originalState){   return originalState | LED4; } /*we use bitwise OR */
unsigned char Turn_Pin_4_Off(unsigned char originalState){ return originalState & (~LED4); } /*we use bitwise AND with INVERSE */
unsigned char Toggle_Pin_4(unsigned char originalState){ return originalState ^ LED4; } /*we use bitwise XOR */

unsigned char Write_Pin_4_State(unsigned char originalState, BOOL pinState){
    if(pinState) return Turn_Pin_4_On();
    else return Turn_Pin_4_Off();
}

/*
... same for the other pins, of course.

now, you can also include equivalent stateful functions that
store the last known state of the serial port chip in some
global variable, for simplicity purposes.
*/
unsigned char lastKnownState; /* our global state variable here */

unsigned char GetState(){return lastKnownState;} /* some basic accessor for state */
/* PIN : */
void ChangeState_TurnPin1On(){ lastKnownState = Turn_Pin_4_On(lastKnownState); }
void ChangeState_TurnPin1Off(){ lastKnownState = Turn_Pin_4_Off(lastKnownState); }
void ChangeState_TogglePin1(){ lastKnownState = Toggle_Pin_4(lastKnownState); }
void ChangeState_WritePin1(BOOL pinState){ lastKnownState = Write_Pin_4_State(lastKnownState, pinState); }
/* PIN 2: */
void ChangeState_TurnPin2On(){ lastKnownState = Turn_Pin_4_On(lastKnownState); }
void ChangeState_TurnPin2Off(){ lastKnownState = Turn_Pin_4_Off(lastKnownState); }
void ChangeState_TogglePin2(){ lastKnownState = Toggle_Pin_4(lastKnownState); }
void ChangeState_WritePin2(BOOL pinState){ lastKnownState = Write_Pin_4_State(lastKnownState, pinState); }
/* PIN 3: */
void ChangeState_TurnPin3On(){ lastKnownState = Turn_Pin_4_On(lastKnownState); }
void ChangeState_TurnPin3Off(){ lastKnownState = Turn_Pin_4_Off(lastKnownState); }
void ChangeState_TogglePin3(){ lastKnownState = Toggle_Pin_4(lastKnownState); }
void ChangeState_WritePin3(BOOL pinState){ lastKnownState = Write_Pin_4_State(lastKnownState, pinState); }
/* PIN 4: */
void ChangeState_TurnPin4On(){ lastKnownState = Turn_Pin_4_On(lastKnownState); }
void ChangeState_TurnPin4Off(){ lastKnownState = Turn_Pin_4_Off(lastKnownState); }
void ChangeState_TogglePin4(){ lastKnownState = Toggle_Pin_4(lastKnownState); }
void ChangeState_WritePin4(BOOL pinState){ lastKnownState = Write_Pin_4_State(lastKnownState, pinState); }

/*
Now you see that those functions change the state byte global variable at 
any time so in order to use this, say to make an immediate change to the 
serial port chip pins in practice, you obviously need to write the state 
byte out to the FT device like so (returns TRUE if success):
*/
BOOL TurnPin1On(FT_HANDLE handle){
    ChangeState_TurnPin1On();
    DWORD bytes;
    FT_Write(handle, &lastKnownState, 1, &bytes); 
    /*
    note that our buffer here is a single byte variable!
    so in this case we take its memory address using & and we pass it into 
    the FT_Write function. That function will treat it as any char buffer, 
    and get its value accordingly.
    */
    if(bytes==1) return TRUE;
    else return FALSE:
}
/*
... and so forth for your other functions. Now the problem with this approach 
is that it is SLOW because you bit-bang once per function call, so you don't 
even simultaneously write the new state for all your pins before sending the 
command out. Thus this is definitely not an optimal approach. In the original 
code you provided below, the coders decided to write every desired byte state 
in a periodic sequence, store it in an array of bytes of known size, and then 
just send this array over and over again. Thus, this is way faster code, 
because you leverage the buffering of the FT chip.

I've modified the code below in order to fit the new functions above, and to
make the whole process clearer. Note that the resulting code is less optimal, 
speed-wise, of course. But I guess it is more newbie-friendly so that's a 
start.
*/
int _tmain(int argc, _TCHAR* argv[])
{
    int i,j,itr,n;
    unsigned char triangleWaveTemplate1[256]; /* template for pin 1 */
    unsigned char triangleWaveTemplate2[256]; /* template for pin 2 */
    unsigned char triangleWaveTemplate3[256]; /* template for pin 3 */
    unsigned char triangleWaveTemplate4[256]; /* template for pin 4 */
    unsigned char data[256 * 255];
    FT_HANDLE handle;
    DWORD bytes;

    /* Generate triangle wave templates. First we clear all buffers to zero */
    memset(data, 0, sizeof(data));
    memset(triangleWaveTemplate1, 0, sizeof(triangleWaveTemplate1));
    memset(triangleWaveTemplate2, 0, sizeof(triangleWaveTemplate2));
    memset(triangleWaveTemplate3, 0, sizeof(triangleWaveTemplate3));
    memset(triangleWaveTemplate4, 0, sizeof(triangleWaveTemplate4));

    /* write to template for pin 1 */
    for(i=1; i<128; i++) {
        n = (int)(pow((double)i / 127.0, 2.5) * 255.0);
        triangleWaveTemplate1[i] = n; /* triangle wave ramp up */
        triangleWaveTemplate1[256-i] = n; /* triangle wave ramp down */
    }
    /* now generate the other templates for the other pins */
    n = sizeof(triangleWaveTemplate1) / 4;
    for(i=0; i<sizeof(triangleWaveTemplate2); i++){
        /* Pin 2 is an offsetted waveform by 3/4 wavelength to the left 
        (i.e. 1/4 to the right) versus Pin 1 waveform */
        triangleWaveTemplate2[i] = triangleWaveTemplate1[(i + 3*n) % sizeof(triangleWaveTemplate1)];
    }
    for(i=0; i<sizeof(triangleWaveTemplate3); i++){
        /* Pin 3 is an offsetted waveform by 2/4 wavelength versus Pin 1
        waveform */
        triangleWaveTemplate3[i] = triangleWaveTemplate1[(i + 2*n) % sizeof(triangleWaveTemplate1)];
    }
    for(i=0; i<sizeof(triangleWaveTemplate4); i++){
        /* Pin 4 is an offsetted waveform by 1/4 wavelength to the left
        (i.e. 3/4 to the right) versus Pin 1 waveform */
        triangleWaveTemplate4[i] = triangleWaveTemplate1[(i + 1*n) % sizeof(triangleWaveTemplate1)];
    }
    /* now, triangleWaveTemplateX buffers contain the desired triangle waves 
    for 1 oscillation period for all 4 pins. We can now implement the PWM 
    thing (basically the inner for loop acts like a sawtooth wave and what 
    we do is compare its iterator (j) to see if it is below or above a 
    certain threshold defined by the template buffers. If it is below, 
    we know the corresponding pin must be on. Else it should be off.*/
    itr=0;
    for(i=0; i<256;i++){
        for(j=0;j<255;j++){
            if(j<triangleWaveTemplate1[i]) ChangeState_TurnPin1On();
            else ChangeState_TurnPin1Off();

            if(j<triangleWaveTemplate2[i]) ChangeState_TurnPin2On();
            else ChangeState_TurnPin2Off();

            if(j<triangleWaveTemplate3[i]) ChangeState_TurnPin3On();
            else ChangeState_TurnPin3Off();

            if(j<triangleWaveTemplate4[i]) ChangeState_TurnPin4On();
            else ChangeState_TurnPin4Off();

            data[itr] = GetState(); /* get resulting state byte for all pins, write in the output data buffer */
        }
    }
    /*             var j waveform: sawtooth
       v  |        /|        /|        /|        /|--------/
       a  |       / |       / |       / |-------/-|       /
       r  |      /  |      /  |------/--|      /  |      /
          |     /   |-----/---|     /   |     /   |     /
       i<-|----/----|    /    |    /    |    /    |    /
          |   /     |   /     |   /     |   /     |   /
       t  |  / ^    |  /  ^   |  /   ^  |  /    ^ |  /
       h  | /  |    | /   |   | /    |  | /     | | /
       r  |/   |    |/    |   |/     |  |/      | |/
       e  +----------------------------------------------------->
       s      time        |          |          |
       h       |    ^     |   ^      |  ^       | ^
       o       |    |     |   |      |  |       | |
       l       |    |     |   |      |  |       | |
       d       |    |     |   |      |  |       | |
               |    |     |   |      |  |       | |
               |    |     |   |      |  |       | |
       L  |    |    |     |   |      |  |       | |
       E  |    
       D  |----+    +-----+   +------+  +-------+ +--------
          |    |    |     |   |      |  |       | |
       s  |    |    |     |   |      |  |       | |
       t  |    |    |     |   |      |  |       | |
       a  |    |    |     |   |      |  |       | |
       t  |    +----+     +---+      +--+       +-+
       e  +----------------------------------------------------->
             time
    */



    /* Initialize, open device, set bitbang mode w/5 outputs */
    if(FT_Open(0, &handle) != FT_OK) {
        puts("Can't open device");
        return 1;
    }
    FT_SetBitMode(handle, LED1 | LED2 | LED3 | LED4, 1);
    FT_SetBaudRate(handle, 9600);  /* Actually 9600 * 16 */

    /* Endless loop: dump precomputed PWM data to the device */
    for(;;) FT_Write(handle, data, (DWORD)sizeof(data), &bytes); 
    /*
    The original article gets the ADDRESS of the POINTER to the data buffer 
    using &data ? I think this was a mistake because "data" refers to the 
    pointer to the buffer already! I corrected here. See API for FTDI
    function here:
    http://www.ftdichip.com/Support/Knowledgebase/index.html?ft_write.htm
    */

    _getch();
    return 0;
}

关于C++:带 D2XX 驱动程序的 Bit-banging(USB 到串行 UART)FTDI 模块,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24703492/

相关文章:

c++ - 在裸机 Controller 上的不同上下文中设置 int 变量中的标志

c++ - 包括 windows.storage.streams.h

c++ - 如何创建一个 unordered_set 的 shared_ptr 对象?

linux - 未知原因读取串行端口 block

python - 使用 Python 读取串行端口。缓冲器如何工作?

c++ - 为什么我不能在 C++ 中将引用存储在 `std::map` 中?

C++ 多态性,关于宣布对派生类的基类引用

c++ - 静态成员声明为 const 但初始化为 constexpr

c++ - 是否可以用调试中断或错误抛出替换所有标准错误对话框?

c# - 使用 C++ 或 C# 与端口通信?