Wednesday, December 16, 2020

FTDI232H BreakOut Board--The affordable USB Swiss Army Knife for Audio DiWHY! Part I: how to get started!!!

If shelter in place has made you a listless here's a suggestion: entertainment is an AliExpress click away. This time let's check out an interesting breakout board ("BoB"), the FT232H, get into the lab and have fun. 

This BoB costs next to nothing ( < $10USD!!) and advertises the ability to take in USB data and output SPI, I2C, UART, etc.--all extremely useful to AudioDIY.  

BoB's YER UNCLE? Breakout Boards are most often useful SMD chip(s) with minimal support circuitry on a tiny PCB. Pins are usually set at 100mils; prices are usually low; English documentation goes from good (Adafruit; Sparkfun) to none  (中国克隆!!)  

This FTDI232H breakout is no exception; get it from  HiLetgo and other Shenzhenronauts. I got one branded CJMCU-FT232H; you can get this from Amazon, assuming this link still works, here.  

Upon close inspection the CJMCU BoB is a FT232H chip, with minimal support circuitry, a regulator, and a timing crystal. Do the FTDI datasheets apply? Let's find out. 

First, I downloaded the datasheet for the FT232H.  FTDI is British (didn't know that) and the (English language!!!) docs for their FTDI chips are top shelf; find everything you ever wanted to know about the FT232H here--FTDI documented the hell out of this chip.   Good news--the FTDI docs apply to our BoB!!!

 

Next up: I needed the FT232H Windows drivers since I was going to use a Windows 10 desktop computer for this project. Zadig is useful for this; this free utility finds attached USB devices and lets you, the end user, select and install a handful of generic Windows USB drivers for discovered devices. Use Zadig to match your FT232H to the USB Windows driver required by your software--for this project it worked; but beware: you can also break your Windows 10 driver coinfigurations with Zadig--In short: as long as you're careful, Zadig is a useful utility and should be in your software toolbox.

 

Zadig for Windows. Click on options > list all to  see what USB devices are attached. Then you can change drivers. The blue box on the left shows the currently driver installed; green box is a pulldown of drivers you can install over existing. "Single RS232-HS" is the BoB being discussed in this post with the drivers needed by PyFTDI shown on the left. 

Update 1-3-21: I have found that Zadig's driver installations might break if you unplug the USB device and move it to another port. You have to install the correct libusb0 driver again for the new port.  However if you move the device back to the original USB port you still have to reinstall the driver again.  Not sure why this is, I am digging into it but the workaround right now is to always plug the FT232H into the same USB port every time, and be ready to reinstall the correct driver with Zadig as needed.   

Next we need software to make this work.  I used pyftdi for these tests, get that here; you will also need to install Python v3 (I used 3.9) if you don't already have it--download that here; then import the pyFTDI module (info on importing modules in Python is here.) 

PyFTDI's authors put a lot of work into their software--pyftdi has methods and properties that touch pretty much every feature of the FT232H chip, including changing values in the FT232H's eeprom. The PyFTDI documentation (here) is exhaustive; the pinout for the 232H board is on the PYFTDI pages is here; the pinouts match the CMJCU board's silks (which sadly are on the opposite side of the board vs. the BoB's LEDs).

The rest of this week's lab work will focus on making PyFTDI USB-to-whatever work in the simplest manner. PyFTDI includes tools (here) and Python code test examples, but the examples are complex; I'd like to keep this simple for now. 

First up: Let's see if we can see the FT232H BoB at all using pyftdi. Code for doing that with PyFTDI is easy:

##################

import pyftdi.serialext

from pyftdi.ftdi import Ftdi


Ftdi.show_devices()


##Available interfaces:

##STDOUT shows this: 

#ftdi://ftdi:232h:0:1/1   (Single RS232-HS)

####################

Happily: my FT232H Breakout Board is found, which means the Windows USB drivers, BoB, USB cables, etc., are OK. The odd URL above is used to identify the board in the rest of the code examples; your URL may be different; so run the "find devices" script first and note its output.  

With that done: Let's see if we can make this BOB "talk" UART.  It's Just two wires--TX->RX and ground, for the bench work I used the 232H for transmit and my trusty and well worn Arduino UNO for Serial receive. 

Tx for the 232 is AD0.....you may also need a 1K pullup from RX line to 5V....

Here is the PYFTDI code to make this go:

#########################

import pyftdi.serialext

port = pyftdi.serialext.serial_for_url('ftdi://ftdi:232h:0:1/1', baudrate=9600)

# Send bytes

port.write(b'Hello Arduino 9I am still here')

######################

Here is the sketch for the Arduino uno for this--for simplicity, a "9" in the string above returns a carriage return in the Arduino's serial output window:

////////////////////////////////////////

//show me what is coming into RX port

int incomingByte = 0; // for incoming serial data

int p = 0;

void setup() {

  Serial.begin(9600); // opens serial port, sets data rate to 9600 bps

}


void loop() 

{

  // reply only when you receive data:

  if (Serial.available() > 0) 

        {

        // read the incoming byte:

        incomingByte = Serial.read();

          // say what you got:   

            p = (incomingByte);

            // 9 works as carriage return. 

            char c = p;

            if (c != '9')

                        {

                        Serial.print(c);

                        }

            else

                        {

                        Serial.println();

                        }

            } // end if

} //end main

////////////////////////////////////////

Easy?  Yes and no. I got this to work, the string entered into "port.write" would show up in the arduino IDE's serial output, but I spent an entire weekend day trying to figure out why, when the FTDI BoB and Uno were both online at the same time, I couldn't upload sketches to the Uno--it would get hung up on "uploading".  

This could be many things: USB conflict? Pullups? Cabling? Noteably, I had the same issue even when trying to upload from a Mac--which of course is a completely different PC! 

I finally found a link here that explained it: don't use RX at all when trying to upload sketches (which makes no sense; SPI is used to program the UNO, not serial? So there is something going on inside the UNO and Nano I still don't understand). 

From there I hit upon a workaround: unplug the UNO's USB, which kills a forever hung upload; remove the RX line, reset the Uno, and upload the sketch again. Now, with new code inside the UNO, plug back in the RX line. That worked every time. 

UPDATE: 2-8-21. to figure out why this didn't work, read the UNO schematic! Programming for  Arduinos and Arduino clones is via serial, not SPI; the USB signal is translated to serial by a CH340, FTDI, or 16u2 chip, which then sends the programming info to the Arduino's UART inputs. More detail about how this works can be found hereSo yes, shorting the MPU TX pin to ground through a 1K resistor etc. is going to break programming for an Arduino.

This also means that for any AVR loaded with Arduino Optiboot, you can use the FTDI BoB's UART outputs to program the MPU instead of USB. Go here to see how to do this from the Arduino IDE.  If your PC can't recognize a CH340 USB to UART chip due to a driver problem, this is a viable workaround.

Next up: GPIO. The pyftdi docs tell us to check the test examples (here) but I couldn't find examples for  GPIO. But figuring out how to use GPIO ("bit banging" as it's often called) with the FTDI BoB wasn't difficult:



I tried to do 2 things: flash an LED (of course!) and create a basic waveform on my scope using nothing but the BoB's GPIO pins and ground.  Success with both, here is the Python code:

For LED flash:

###########################

from pyftdi.gpio import GpioAsyncController

import time


device = 'ftdi://ftdi:232h:0:1/1'


gpioa=GpioAsyncController()


x = 0

gpioa.configure(device, direction=0b11111111)

# all pins as output; use AD0-7

#pins AC0-9 don't work with bit bang GPIO?


#flash LED 4 times



while (x != 4):

    gpioa.write(0b00000000)


    time.sleep(1) # Sleep for 1 second

    gpioa.write(0b11111111)

    time.sleep(1) # Sleep for 1 second

    #print("iter value is: " + str(x))

    x=x+1

############################################

and for generating basic waveforms on a scope:

#############################################

#FT232H as GPIO clock gen.


from pyftdi.gpio import GpioAsyncController

gpioa=GpioAsyncController()

device = 'ftdi://ftdi:232h:0:1/1'

gpioa.configure(device, direction=0b11111111)


bytes = []


#works with pins AD0-AD7

#output goes from hi-z to ground, so you may need to set up a pullup.

#you might need to buffer the output of AD0 (with transistor?)  depending on what you have downstream. For my scope I didn't need it.

#dont remember why I commented next line but

#it isn't needed.

#gpioa.open_from_url(device, direction=0b11111111)



freq=100000 #keep this reasonable. < 500K.

secs = 5 # number of seconds to run clock.

#resulting frequency of on followed by off is half of freq.

#so we mult it by 2....

gpioa.set_frequency(freq*2)


#GPIO outputs are sent as an array of bytes.

#below we create 10K bytes alternating off and on, and output the data enough

#times to create a secs second pulse.

a = range(0,10000)


for b in a:

    if b % 2 == 0:

        bytes.append(0x0)

    else:

        bytes.append(0x1)


times = int(freq/10000)

print(times)

#WRITE YER BYTES FOR "secs" SECONDS

a = range(0,times*secs)

for xx in a:


   gpioa.write(bytes)

##########################


The result is a good-enough squarewave whose frequency is determined by the freq variable in the code above. For me the frequency of the square wave generated (anything between about 10K and 500K) was accurate to a few decimel places. Nice.


Update 3-10-21 it is pretty hard to tell from the PYFTDI documentation, but for BitBang/GPIO, you can only (easily) use the AD0-7 pins. So the bus is only 8 bits wide. AC0-7 cannot be easily addressed.

Waveem off: let's not stop there; with a simple 8 bit resistor-ladder DAC we can easily get other waveforms from the FT232H BoB's GPIO pins.

Basic 8 bit DAC. Webpage on how this works is here. For this POC, 1% metal-film resistors are good enough.


I added pullups on the upper left....

Quick 8 bit DAC ladder is back from China. Get Eagle files for 8 bit DAC for this post from Github, here.




Wiring for this....


With this 8 bit DAC in hand--simple, it's just some resistors, we can make basic waveforms like triangle and ramp by hooking each data line from the FT232H to the DAC's inputs.




////////////////////////  GPIO RAMP
 

from pyftdi.gpio import GpioAsyncController
gpioa=GpioAsyncController()
device = 'ftdi://ftdi:232h:0:1/1'
gpioa.configure(device, direction=0b11111111)

bytes = []

#works with pins AD0-AD7
#output goes from hi-z to ground, so set up a pulldown.
#you might need to buffer the output of AD0 (with transistor?)  depending on what you have downstream.

#gpioa.open_from_url(device, direction=0b11111111)
#resulting frequency of on followed by off is half of freq.
#so we mult it by 2....

freq=15000 #keep this reasonable.
rampfreq = freq * 256
secs = 5 # number of seconds to run clock.

gpioa.set_frequency(rampfreq)

endtime = freq*secs
print(endtime)

#WRITE YER BYTES FOR "secs" SECONDS

a = range(0,endtime)


my_list = list(range(0, 255))



tribyte = bytearray(my_list)


for xx in a:

   gpioa.write(tribyte)

gpioa.close()

////////////////////////  GPIO TRIANGLE

from pyftdi.gpio import GpioAsyncController
gpioa=GpioAsyncController()
device = 'ftdi://ftdi:232h:0:1/1'
gpioa.configure(device, direction=0b11111111)

bytes = []
 
freq=10000 #keep this reasonable.
secs = 10 # number of seconds to run clock.

gpioa.set_frequency(freq*2)

times = int(freq/100)

print(times)

#WRITE YER BYTES FOR "secs" SECONDS

a = range(0,times*secs)

my_list2 = list(reversed(range(1,254)))
my_list = list(range(0, 255))
list3 = my_list2 + my_list


tribyte = bytearray(list3)


for xx in a:

   gpioa.write(tribyte)

gpioa.close()

////////////////////////


Gogetim: Get the python and Arduino code this post from github--here.

Shukran Breakout board for CMJCU FTDI232H Breakout board.....



Update 3-10-21 I have found that breadboarding with the CMJCU BreakOut Board is a bit tricky due to its width.  So I went the github page here and got gerbers for the "Shukran" which is a breakout board for the breakout board.  Got that PCB made, back, built. Easy to assemble; the only confusing thing for me was that the switching 5V power is tied to AC08 which can't be easily enabled through PYFTDI, and the shukran docs say "consult FTDI". OK: from FTDI docs it looks like you need to enable this by changing firmware in the FTDI chip's eprom.  If I built this again I'd probably leave the MOSFET, LEDs and resistors for this feature out, but overall the board works fine, and since AC08 can be used for things like 7.5mhz clocks, maybe consider using this feature down the road.

That's it for now....this is becoming a long post....next time we'll finish this off with simple examples for SPI and I2C. Update: posted!  go here. Until then: BoB well and live.  See ya.

No comments:

Post a Comment

Rotary Encoder Expermenter's Board: Improving the Hardware

Quick one this time....I have posted a few projects lately that incorporated a Raspberry Pi Pico, rotary encoder, and .96" OLED:  here ...