catalogue
1. Create a new class mpsse_iic
IIC is implemented by GPIO simulation. In GPIO mode, any IO can be used as the pin of IIC. Here, in order to simplify the program, only the PortD port is used as the IO of IIC.
In order to maintain compatibility, xDBUS0 is selected as SCL of IIC, xDBUS1 and xDBUS2 are shorted together as SDA, xDBUS1 is used as output of SDA, and xDBUS2 is used as input of SDA. Actually, only xDBUS1 is used as the input and output of SDA port.
1. Create a new class mpsse_iic
public class mpsse_iic { public FT_Device ftDevice; mpsse_gpio gpio; public mpsse_iic(mpsse_gpio spiGPIO) { gpio = spiGPIO; } }
1.1 add a variable to indicate the corresponding IO port position
private byte SCL = 0; private byte SDAO = 1; private byte SDAI = 2;
1.2 add variable to save IO control sequence
private ArrayList ftCommand = new ArrayList();
When controlling GPIO, instead of writing the state of the IO port every time, write a group of IO states. For example, write 8bit data at a time. You can write the 8bit data into the USB Buffer at a time. In this way, these command sequences can be saved in the ftCommand variable and written out for the last time.
1.3 add variables to determine the frequency of IO
private int mCLK = 0;
2. Modify mpsse_gpio
In order not to affect the state of other IO ports when operating IIC, some methods are added.
Will variable
private byte PortDDir; private byte PortDLevel; private byte PortCDir; private byte PortCLevel;
Change to Class so that these four variables can be obtained.
public static class classPort { public byte PortDDir; public byte PortDLevel; public byte PortCDir; public byte PortCLevel; } classPort mPort = new classPort(); public classPort getPortState() { return mPort; } public void setPortState(classPort port) { mPort = port; }
3. iic initialization
In class mpsse_ Add init and clk to IIC to indicate the frequency of gpio, which is generally 1 (800KHz) or 2(400KHz). Note that this rate is approximate and not very accurate.
/* * clk: normally 1 or 2. * ioSCL/ioSDA: 0-7 low byte io, xDBUS0 - xDBUS7 */ public void init(int clk, byte ioSCL, byte ioSDA) { mCLK = clk; SCL = ioSCL; SDAO = ioSDA; gpio.setDir(SCL, (byte)0); //SCL is output gpio.setDir(SDAO, (byte)0); //SDA(AD1) is output gpio.setDir(SDAI, (byte)1); //SDA(AD2) is input gpio.output(SCL, mpsse_gpio.eLevel.High); gpio.output(SDAO, mpsse_gpio.eLevel.High); }
4. Start of IIC
Here, only the command sequence of Start is written into ftCommand, and the 0x80 command of MPSSE is used to control GPIO.
The Start timing of IIC is that SDA and SCL Start to output high level, and then SDA generates a falling edge.
private void start() { mpsse_gpio.classPort port; Log.i("IIC", "Start"); port = gpio.getPortState(); port.PortDLevel |= (byte)1 << SCL | (byte)1 << SDAO; //SCL outputs high, SDA outputs high for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } port.PortDLevel &= (byte)(~((byte)1 << SDAO)); //SDA outputs low for (int i = 0; i < mCLK * 2; i++) //Wait a moment { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } gpio.setPortState(port); }
For example, when SCL=4, SDAO=5, mCLK = 2, the contents in ftCommand are:
80 fe 3b 80 fe 3b 80 de 3b 80 de 3b 80 de 3b 80 de 3b
Every 3 bytes is an IO control, and mCLK is 2, which means to repeat it, that is, operate the same io for 2 consecutive times, and the frequency is equivalent to the highest frequency / 2.
Fe - > de, that is, SDA (bit 5) changes from high to low.
5. Stop of IIC
The Stop timing of IC is SCL output high, and then SDA generates a rising edge.
private void stop() { mpsse_gpio.classPort port; Log.i("IIC", "Stop"); port = gpio.getPortState(); port.PortDLevel &= (byte)(~((byte)1 << SDAO)); //SDA outpus low for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } port.PortDLevel |= (byte)1 << SCL; //SCL outputs high for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } port.PortDLevel |= (byte)1 << SDAO; //SDA outputs high for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } gpio.setPortState(port); }
6. Read one byte of IIC
After the SCL of IIC generates a rising edge, read the level of SDA, cycle 8 times to read 1 byte of data, and then set SDA according to whether to ack. If you want ACK, SDA outputs 0, otherwise outputs 1, and finally set SCL output low.
private void receiveByte(boolean ack) { mpsse_gpio.classPort port; Log.i("IIC", "receiveByte"); port = gpio.getPortState(); port.PortDLevel &= (byte)(~(byte)1 << SCL | (byte)1 << SDAO); //SCL outputs low, SDA outputs low port.PortDDir &= (byte)(~((byte)1 << SDAO)); //Set SDA as input ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); if(ioCtrlMode == true) { byte loop = 8; while (loop-- > 0) { port.PortDLevel &= (byte)(~((byte)1 << SCL)); //SCL output low for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } port.PortDLevel |= (byte)(1 << SCL); //SCL output high for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } ftCommand.add((byte)0x81); //Rd SDA } } else { ftCommand.add((byte)0x22); ftCommand.add((byte)7); } port.PortDLevel &= (byte)(~(byte)1 << SCL | (byte)1 << SDAO); //SCL outputs low, SDA outputs low port.PortDDir |= ((byte)1 << SDAO); //Set SDA as output ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); if (ioCtrlMode == true) { if (ack == true) port.PortDLevel &= (byte)(~((byte)1 << SDAO)); else port.PortDLevel |= ((byte)1 << SDAO); for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } port.PortDLevel |= (byte)((byte)1 << SCL); for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } } else { ftCommand.add((byte)0x13); //Clock Data Bits Out on clock raise edge MSB first ftCommand.add((byte)0x0); // 1bit if (ack == true) ftCommand.add((byte)0x00); // SDA outputs low means ack else ftCommand.add((byte)0x80); // SDA outputs high means nack, only use bit7. } ftCommand.add((byte)0x87); port.PortDLevel &= (byte)(~((byte)1 << SCL)); //SCL outputs low ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); gpio.setPortState(port); }
The SDA level status read here will be in the cache of FT4232H. The purpose of writing 0x87 command is to tell FT4232H to send the data in the cache to the host immediately.
7. Send one byte of IIC
Set the SCL to low and the SDA to set the corresponding data bit, then pull the SCL high, repeat 8 times to send a byte of data, and then read the SDA level to judge whether the slave is ACK. Finally, the SCL output is low and the SDA output is high.
private void sendByte(byte dat) { mpsse_gpio.classPort port; Log.i("IIC", "sendByte"); port = gpio.getPortState(); if (ioCtrlMode == true) { for(byte j = 0; j < 8; j++) { port.PortDLevel &= (byte)(~((byte)1 << SCL)); //SCL outputs low //for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } if ((dat & 0x80) == 0x80) port.PortDLevel |= (byte)(1 << SDAO); //SDA Output High else port.PortDLevel &= (byte)(~((byte)1 << SDAO)); //SDA Output Low dat <<= 1; port.PortDDir |= (byte)(1 << SDAO); //Set SDA as output //for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } port.PortDLevel |= (byte)(1 << SCL); //set SCL to high for (int i = 0; i < mCLK * 1; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } } } else { port.PortDLevel &= (byte)(~((byte)1 << SCL)); //SCL outputs low port.PortDLevel |= (byte)(1 << SDAO); //SDA outputs high port.PortDDir |= (byte)(1 << SDAO); //Set SDA as output for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } ftCommand.add((byte)0x13); //Clock Data Bits Out on clock raise edge MSB first ftCommand.add((byte)7); ftCommand.add(dat); } port.PortDLevel &= (byte)(~((byte)1 << SCL) | (byte)1 << SDAO); //SCL and SDA output low port.PortDDir &= (byte)(~((byte)1 << SDAO)); //Set SDA as input for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } if (ioCtrlMode == true) { port.PortDLevel &= (byte)(~((byte)1 << SCL)); //SCL output low for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } port.PortDLevel |= (byte)1 << SCL; //set SCL to high for (int i = 0; i < mCLK * 10; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } ftCommand.add((byte)0x81); //Rd SDA port.PortDLevel |= (byte)1 << SDAO; //SDA outputs high port.PortDDir |= (byte)1 << SDAO; //Set SDA as output for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } } else { ftCommand.add((byte)0x26); //Clock Data Bits In on clock falling edge MSB first ftCommand.add((byte)0); // 1bit ftCommand.add((byte)0x87); } port.PortDLevel &= (byte)(~((byte)1 << SCL)); //SCL outputs low // port.PortDLevel |= (byte)0x02; //SDA outputs high port.PortDDir |= (byte)1 << SDAO; //Set SDA as output for (int i = 0; i < mCLK; i++) { ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); } /* port.PortDLevel |= (byte)1 << SDAO; //SDA outputs high port.PortDDir |= (byte)1 << SDAO; //Set SDA as output ftCommand.add((byte)0x80); ftCommand.add(port.PortDLevel); ftCommand.add(port.PortDDir); */ gpio.setPortState(port); }
8. IIC read operation
The parameter slaveAddr indicates the IIC address of the IIC slave device; addrbit indicates the width of the internal address of the IIC device, and the valid values are 0, 8, 16; addr indicates the internal address of the device; dat is the read in data cache; len indicates how many bytes of data need to be read in. Because the maximum number of bytes for a USB communication is 64KB, and more command bytes will be used for GPIO operation, it is recommended that the data read in at one time should not exceed 1KB.
public boolean read(byte slaveAddr, byte addrbit, int addr, byte[] dat, int len) { if (len == 0) return false; int rdDatLen = len; if (ioCtrlMode == true) rdDatLen = len * 8; int rdLen = (int)(addrbit / 8 + 2 + rdDatLen); if(addrbit == 0) rdLen = (int)(addrbit / 8 + 1 + rdDatLen); int byteWritten = 0, byteRead = 0, dly = 0; //Read USB buffer first to clear the buffer. byteRead = ftDevice.getQueueStatus(); if (byteRead > 0) { byte[] tmpbuf = new byte[byteRead]; ftDevice.read(tmpbuf, byteRead); } //Create command list ftCommand.clear(); start(); if(addrbit > 0) sendByte(slaveAddr); if (addrbit == 16) sendByte((byte)(addr >> 8)); if(addrbit > 0) { sendByte((byte) (addr >> 0)); start(); } sendByte((byte)(slaveAddr | 0x01)); while (--len > 0) { receiveByte(true); } receiveByte(false); ftCommand.add((byte)0x87); stop(); byte[] cmdBuf = new byte[ftCommand.size()]; for (int i = 0; i < ftCommand.size(); i++) cmdBuf[i] = (byte)ftCommand.get(i); ftDevice.write(cmdBuf, cmdBuf.length); byte[] rdBuf = new byte[rdLen]; int offset = 0; while (true) { byteRead = ftDevice.getQueueStatus(); dly++; if (dly > 0xffff) { Log.e("iic read", "read data time out"); return false; } if(byteRead == 0) continue; byte[] tmpBuf = new byte[byteRead]; ftDevice.read(tmpBuf, byteRead); for (int i = 0; i < tmpBuf.length; i++ ) { rdBuf[offset++] = tmpBuf[i]; } if (offset >= rdLen) break; try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i = 0; i < rdBuf.length - rdDatLen; i++) { if (ioCtrlMode == true) { if ((rdBuf[i] & ((byte)(1 << SDAO))) == (byte)(1 << SDAO)) //Check ACK { Log.e("iic read", "return nack:" + Integer.toHexString((rdBuf[i] & 0x000000FF) | 0xFFFFFF00).substring(6)); return false; } } else { if ((byte)(rdBuf[i] & 0x01) == (byte)0x01) { Log.e("iic read", "return nack:" + Integer.toHexString((rdBuf[i] & 0x000000FF) | 0xFFFFFF00).substring(6)); return false; } } } for (int i = (int)(rdBuf.length - rdDatLen); i < rdBuf.length; i++) { if(ioCtrlMode == true) { int max = i + 8; byte tmp = 0; for(; i < max; i++) { tmp <<= 1; if ((rdBuf[i] & ((byte)(1 << SDAO))) == (byte)(1 << SDAO)) { tmp |= 0x01; } } dat[(i - (rdBuf.length - rdDatLen)) / 8 - 1] = tmp; i--; } else dat[i - (rdBuf.length - rdDatLen)] = rdBuf[i]; } return true; }
After sending the command data to FT4232H at one time, the read data will be in the buffer of FT4232H and read back to rdBuf. These data include IO level and ACK information.
8.1 check whether the ACK of the sending address is correct
When sending slaveAddr and addr, you will get (rdBuf.length - rdDatLen) ACK information, so you will traverse whether these acks are low level. Namely
for (int i = 0; i < rdBuf.length - rdDatLen; i++) { if (ioCtrlMode == true) { if ((rdBuf[i] & ((byte)(1 << SDAO))) == (byte)(1 << SDAO)) //Check ACK { Log.e("iic read", "return nack:" + Integer.toHexString((rdBuf[i] & 0x000000FF) | 0xFFFFFF00).substring(6)); return false; } } else { if ((byte)(rdBuf[i] & 0x01) == (byte)0x01) { Log.e("iic read", "return nack:" + Integer.toHexString((rdBuf[i] & 0x000000FF) | 0xFFFFFF00).substring(6)); return false; } } }
8.2 receiving data
The next data is the real SDA data. Note that the level of the whole PortD is read, so the corresponding bits need to be combined into bytes. Namely
for (int i = (int)(rdBuf.length - rdDatLen); i < rdBuf.length; i++) { if(ioCtrlMode == true) { int max = i + 8; byte tmp = 0; for(; i < max; i++) { tmp <<= 1; if ((rdBuf[i] & ((byte)(1 << SDAO))) == (byte)(1 << SDAO)) { tmp |= 0x01; } } dat[(i - (rdBuf.length - rdDatLen)) / 8 - 1] = tmp; i--; } else dat[i - (rdBuf.length - rdDatLen)] = rdBuf[i]; }
9. IIC write operation
IIC write operation is similar to read operation. The data read back is relatively simple and all are ACK information.
public boolean write(byte slaveAddr, byte addrbit, int addr, byte[] dat, int len) { if (len == 0) return false; int rdLen = (int)(addrbit / 8 + 1 + len); ftCommand.clear(); start(); sendByte(slaveAddr); if (addrbit == 16) sendByte((byte)(addr >> 8)); if(addrbit > 0) sendByte((byte)(addr >> 0)); int n = 0; while (len-- > 0) { sendByte(dat[n++]); } stop(); ftCommand.add((byte)0x87); byte[] cmdBuf = new byte[ftCommand.size()]; Log.i("iic write", "Command Length:" + ftCommand.size()); for (int i = 0; i < ftCommand.size(); i++) { cmdBuf[i] = (byte) ftCommand.get(i); /*if((i + 1) % 16 == 0) { Log.i("iic write", "cmd[" + (i - 15) + "] - cmd[" + i + "]=" + Integer.toHexString((cmdBuf[i - 15] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 14] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 13] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 12] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 11] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 10] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 9] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 8] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 7] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 6] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 5] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 4] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 3] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 2] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 1] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " + Integer.toHexString((cmdBuf[i - 0] & 0x000000FF) | 0xFFFFFF00).substring(6) + " " ); }*/ } int byteRead = 0, dly = 0; Log.i("iic write", "write command buffer"); ftDevice.write(cmdBuf, cmdBuf.length); Log.i("iic write", "read response data"); while (true) { byteRead = ftDevice.getQueueStatus(); dly++; if (dly > 0xffff) { Log.e("iic write", "time out"); return false; } if (byteRead >= rdLen) break; try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } byte[] rdBuf = new byte[byteRead]; ftDevice.read(rdBuf, byteRead); Log.i("iic write", "read buffer len:" + rdBuf.length); for (int i = 0; i < rdBuf.length; i++) { if (ioCtrlMode == true) { if ((rdBuf[i] & ((byte)(1 << SDAO))) == (byte)(1 << SDAO)) //Check ACK { Log.e("iic write", "return nack" + i + ":"+ Integer.toHexString((rdBuf[i] & 0x000000FF) | 0xFFFFFF00).substring(6)); //return false; } } else { if ((byte)(rdBuf[i] & 0x01) == (byte)0x01) { Log.e("iic write", "return nack" + i + ":"+ Integer.toHexString((rdBuf[i] & 0x000000FF) | 0xFFFFFF00).substring(6)); return false; } } } Log.i("iic write", "return ok"); return true; }
10. Verification
ADBUS4 as SCL and ADBUS5 as SDA are connected to an EEPROM chip with IIC interface. Verification Code:
TextView tvERomWr; TextView tvERomRd; void ERomTest() { if(mpsseDev == null) return; byte[] wrBuf = new byte[10]; byte[] rdBuf = new byte[10]; tvERomWr.setText(""); for(int i = 0; i < wrBuf.length; i++) { wrBuf[i] = (byte)(Math.random() * 256); tvERomWr.append(Integer.toHexString((wrBuf[i] & 0x000000FF) | 0xFFFFFF00).substring(6) + " "); } mpsseDev.iic.write((byte)0xA0, (byte)16, 0, wrBuf, wrBuf.length); mpsseDev.iic.read((byte)0xA0, (byte)16, 0, rdBuf, rdBuf.length); tvERomRd.setText(""); for(int i = 0; i < rdBuf.length; i++) { tvERomRd.append(Integer.toHexString((rdBuf[i] & 0x000000FF) | 0xFFFFFF00).substring(6) + " "); } }
Initialize IIC
mpsseDev.iic.init((byte)2, (byte)4, (byte)5);
Test results: