Bit Banging I2C for communication between Raspberry Pi and PCF8591 - Page 1
Forum section: electronics / general

author: john1998
author_rank: Newbie
timestamp: July 19, 2018, 07:39:40 am
content: I am trying to interface multiple PCF8591 (around 5) to a single Raspberry Pi using I2C protocol. Since a Rpi has only one set of SDA and SCL pins, I am trying to bit-bang to make the other GPIO pins work as SDA and SCL. I am trying to use RPi.GPIO library for making the bit banging code in python.I don't understand how to communicate with PCF8591 even after referring to the manual plenty of times. I could not figure out how to receive data from a specific pin from PCF8591 since there are 4 pins available (AIN0, AIN1, AIN2, AIN3). I also want the input voltage as the differential voltage between two pins. It would be very helpful if anyone could tell me the steps to change and access different pins of PCF8591.I am attaching the code I am using. I get a reading of '255' throughout whenever I run it. It is working more or less as I could see the SCA and SDA waveforms in an oscilloscope.Code:import RPi.GPIO as GPIOimport timeimport matplotlib.pyplot as pltpin_SCL = 0pin_SDA = 0signal = []def plot_graph(time, data, graph_no, label_):fig = plt.figure(graph_no)axes = fig.add_subplot(111)axes.patch.set_facecolor('black')plt.plot(time, data, label = label_)plt.ylabel('Voltage')plt.xlabel('Time')plt.legend(loc='upper right')def set_pin(SCL, SDA):global pin_SCLglobal pin_SDApin_SCL = SCLpin_SDA = SDAGPIO.setup(pin_SCL, GPIO.OUT)def start():GPIO.setup(pin_SDA, GPIO.OUT)GPIO.output(pin_SCL, GPIO.HIGH)GPIO.output(pin_SDA, GPIO.HIGH)time.sleep(10)GPIO.output(pin_SDA, GPIO.LOW)GPIO.output(pin_SCL, GPIO.LOW)def send_byte(byte):GPIO.setup(pin_SDA,GPIO.OUT)for i in range(8):GPIO.output(pin_SDA,byte & 0b10000000)GPIO.output(pin_SCL,GPIO.HIGH)GPIO.output(pin_SCL,GPIO.LOW)byte = byte << 1def acknowledge_from_slave():GPIO.setup(pin_SDA,GPIO.IN)GPIO.output(pin_SCL,GPIO.HIGH)status = GPIO.input(pin_SDA)GPIO.output(pin_SCL,GPIO.LOW)if(status == GPIO.HIGH):print("BYTE NOT RECEIVED")def acknowledge_from_master():GPIO.setup(pin_SDA,GPIO.OUT)GPIO.output(pin_SCL,GPIO.HIGH)GPIO.output(pin_SDA,GPIO.LOW)GPIO.output(pin_SCL,GPIO.LOW)def receive_byte():global signalbyte = ''GPIO.setup(pin_SDA,GPIO.IN)for i in range(8):GPIO.output(pin_SCL,GPIO.HIGH)byte = byte + str(GPIO.input(pin_SDA))GPIO.output(pin_SCL,GPIO.LOW)byte = int(byte,2)signal.append(byte)if __name__ == "__main__":global signalGPIO.setmode(GPIO.BOARD)set_pin(38,40)start()send_byte(0b10010001)acknowledge_from_slave()send_byte(0b00110000)#control byte to tell pcf8591 work as differential inputacknowledge_from_master()try:while True:receive_byte()acknowledge_from_master()except KeyboardInterrupt:plot_graph(range(len(signal)),signal,1,'Detected Signal')plt.show()GPIO.cleanup()

author: newbrain
author_rank: Super Contributor
timestamp: July 19, 2018, 09:24:00 am
content: I did not check your code, but what is wrong with driving all the ADCs from the same I2C?The very first paragraph in PCF8591 datasheet reads:Quote

author: JPortici
author_rank: Super Contributor
timestamp: July 19, 2018, 09:26:31 am
content: do you know how I2C works?Are you aware that I2C you can have multiple slaves, each with its own address?Have you read the PCV8591 datasheet?Are you aware that you can SET the address of each PCF8591 with the three address pins (A2 A1 and A0)? This means that you can have up to eight of those in a single bus.Unless there is a reason why you can't change the address of the PCF8591 that you haven't stated

author: Siwastaja
author_rank: Super Contributor
timestamp: July 19, 2018, 03:37:55 pm
content: AFAIK, there's no way to get the timing right with the Raspberry Pi GPIO. It's just too slow and jittery to bitbang I2C for any meaningful bitrate, at least from userland through the Python library. Through a kernel module, might be possible.

author: john1998
author_rank: Newbie
timestamp: July 20, 2018, 12:01:07 am
content: I need data from all the ADC at the same time instant for my project. I came to know that the master can communicate with only one slave at any time instant and the other slaves have to sit idle at that time instant.I can only use a raspberry pi because of time and financial limitations.

author: nuno
author_rank: Frequent Contributor
timestamp: July 20, 2018, 12:35:00 am
content: At least the A version of the Pi had a hardware bug that makes it ignore slave's clock stretching, so be ware.

author: tsman
author_rank: Frequent Contributor
timestamp: July 20, 2018, 01:27:14 am
content: Quote from: nuno on July 20, 2018, 12:35:00 amThe bug with incomplete support for clock stretching is still there even with the latest 3B+. They've not changed the SoC peripherals. The Raspberry Pi Foundation approved workaround is still to bitbang I2C.

author: nuno
author_rank: Frequent Contributor
timestamp: July 20, 2018, 01:48:41 pm
content: I2c is, by far, the most problematic bus I've ever seen. I don't think I've ever worked professionally in a project that had some I2C peripherals where it didn't had problems, either hardware or software related. I've lost countless hours hunting down hardware and software I2C bugs, including that one on the Pi.

author: Sal Ammoniac
author_rank: Super Contributor
timestamp: July 20, 2018, 02:45:23 pm
content: Quote from: nuno on July 20, 2018, 01:48:41 pmCompared to SPI, I2C can be problematical. Microcontroller vendors are putting better I2C peripherals in some of their later chips, which is improving the situation. Anyone who suffered through getting I2C to work on an STM32F1xx series micro can attest to that. The I2C peripheral on the STM32F7 series is actually quite nice.

author: nuno
author_rank: Frequent Contributor
timestamp: July 20, 2018, 08:36:29 pm
content: One of the more difficult problems with I2C (apart from sporadic issues) is knowing who's freezing the bus. An I2C isolator is a huge help in these cases as they have different low voltage levels for the master and slave, so you can tell right away who's freezing the bus. Unfortunately it's difficult to add one to an existing board.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 23, 2018, 10:41:41 am
content: Quote from: nuno on July 20, 2018, 08:36:29 pmMore and more (and cheaper) microcontrollers are starting to implement a numerous bunch of I2C interfaces instead of just one or two, making dedicated one bus per device possible. While this is against the original "idea" in I2C, that "idea" seems to be problematic enough to warrant this simplification in everything else but the number of PCB traces and pullup resistors. (Routing often gets easier, as well!)This might be easier than a separate isolator component, if the controller has enough interfaces available. 3-5 seems to be fairly typical even in mid-range MCUs nowadays.(This reply is relevant for the OP, too: consider using an MCU in-between to collect the data and buffer it. Many bigger MCUs have 5 I2C interfaces, and then you can use SPI to communicate with the Raspberry, for example.)

author: NiHaoMike
author_rank: Super Contributor
timestamp: July 23, 2018, 04:30:28 pm
content: Quote from: nuno on July 20, 2018, 08:36:29 pmCut the traces, then add 100 ohm or so series resistors. That will cause a small offset that can be seen on a scope.

author: orin
author_rank: Frequent Contributor
timestamp: July 23, 2018, 07:58:10 pm
content: Quote from: john1998 on July 19, 2018, 07:39:40 amThat code almost certainly won't work. You need to control SCL and SDA more precisely.Code:def set_pin(SCL, SDA):global pin_SCLglobal pin_SDApin_SCL = SCLpin_SDA = SDAGPIO.setup(pin_SCL, GPIO.OUT)This sets SCL to be an output, but doesn't define its value.It could well go low at this point. It would appear GPIO.setup(channel, GPIO.OUT, initial=GPIO.HIGH) would be a better idea.Not. If SCL is low and you didn't set it low, you are doomed.Code:def start():GPIO.setup(pin_SDA, GPIO.OUT)<snip>Again, what value will SDA take? If SCL and SDA were high and this sets SDA low, you just generated a start prematurely...Now you also need to take into account that your device might be holding SDA low due to a previous failed transaction. To fix that, you have to toggle SCL until SDA goes high.So the first thing that you must do (at the end of your set_pin) is set both SDA and SCL to inputs and if SDA is low, toggle SCL until SDA is high.I2C doesn't have a minimum speed, so as long as you don't glitch SCL or SDA, you should be fine driving it from python.You just have to be absolutely sure what value is going to go on the line when you set it as an output!I don't know if the python/pi libraries allow it, but the typical way of driving I2C lines is to set them as inputs and set the data output register for them to zero. Set them as inputs if you want them to be 1, allowing the pullup resistors to do their job. Set them as outputs if you want them to be 0.What you never want to do is to set a line to be an output and drive it to 1.That is quite possible with the existing code and would be very bad if the device was driving the line to 0.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 24, 2018, 08:51:49 am
content: Quote from: orin on July 23, 2018, 07:58:10 pmMany I2C devices do specify a minimum speed. Surely some of them might work below that, but the operation is completely unspecified.

author: orin
author_rank: Frequent Contributor
timestamp: July 24, 2018, 08:50:58 pm
content: Quote from: Siwastaja on July 24, 2018, 08:51:49 amThat would seem to violate the I2C spec... but would be necessary if they are claiming SMBus compatibility which requires 10kHz minimum. See section 4.2.1 ofhttps://www.nxp.com/docs/en/user-guide/UM10204.pdfAs for the PCF8591, it doesn't seem to have a minimum, but I'd wonder about meeting the tVD;DAT spec of 3.4 uS max from python.

author: MrAureliusR
author_rank: Unknown
timestamp: July 25, 2018, 04:50:37 pm
content: Quote from: john1998 on 19-07-2018, 18:01:07Haven't read the datasheet for that part, but almost ALL ADC chips with serial interfaces will support sending a single byte to each device telling them to take a sample (so you'd end up with maybe 100uS to do all five, so roughly 20uS between samples -- is it THAT time critical??). Then after each has taken a sample, you can burst read all the sample data out. Separate the sampling from the reading.Also, if you are using a combination of hardware I2C and bitbanging, that will take even LONGER than doing all the chips on one or the other. Stick with the hardware I2C, hardware is always faster than software.Being able to only use a Pi because of financial reasons is ludicrous. I understand time limitations, but there are MANY much cheaper development platforms that would be WAY more appropriate for your situation. Any chip that is running Linux is inherently not realtime. I know you can compile the Linux kernel with realtime options, but it still doesn't come anywhere near close to a dedicated micro. You could pick up an enormous variety of different micro dev boards for half the cost of a Pi and have more control and less bloat.Unless you need a GUI, but even then, you can always pass the results up to a PC or phone via USB... anyway.I'm blathering onTo everyone here complaining about I2C, I suggest you read Vincent Himpe's book Mastering the I2C Bus. It's an amazing book and shows how I2C really works and how to use it well. I never have any issues with I2C anymore.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 26, 2018, 09:37:31 am
content: Quote from: MrAureliusR on July 25, 2018, 04:50:37 pmI understand very well how I2C works, thank you.I'm 100% sure that this book would not explain how to circumvent broken I2C implementations in all I2C devices in existence by case-by-case basis, when even the erratas of the said chips won't.Older generation STM32s were especially painful and outright broken. For example, F205 requires an interesting sequence of forcing peripheral reset in initialization - otherwise the I2C fails on about 10% of units! And this procedure is described in the errata of a completely another STM32 family - they "forgot" to copy it to other erratas using the same silicon-faulty devices - and they never fixed the actual silicon in later revs. Things like this seem much more usual with I2C than with other things with similar end-use complexity.The problem with I2C is that it tries to do too much and offer too much genericity so that very few devices actuallyA) follow the I2C specificationB) are usable in a sane way (this goes especially with master devices such as MCUs).Just now we are starting to see MCU I2C HW peripherals which actually do something sane in HW (and work with DMA, for example). Just a few years ago, it was almost always bitbanging the control registers in a time-critical way almost like you bitbanged the I2C itself!This being said, it's still the de facto thing and always somehow usable, and in the end, things tend to work out. Still, all the critique is valid; it could be so much simpler, and it could be much more robust, for such a trivial task it performs.No one uses the more "advanced" features of I2C such as multi-master or 10-bit addressing, but trying to support them still breaks the implementations.The problem is that I2C is normally used for very simple and trivial communications between a few chips on a PCB, one being a master and others being slaves, typically just polling a few bytes of data; yet it's complex enough that you needbooksabout it.And, if anyone needs all those fancy features, they would never use I2C anyway for that purpose, because it wasn't designed to be fancy: look at the electrical physical level specification which screams simplicity and small surface area.

author: MrAureliusR
author_rank: Unknown
timestamp: July 26, 2018, 12:33:50 pm
content: Wow. I am speechless. The broken forum software just nuked a post I took about 10 minutes to type out. If you modify a post that has a quote in it, everything goes haywire. That's the last time I take the time to make a post on this forum until it's fixed. Just unbelievable...

author: nuno
author_rank: Frequent Contributor
timestamp: July 27, 2018, 02:05:23 pm
content: Quote from: Siwastaja on July 26, 2018, 09:37:31 amThis. Period

author: tsman
author_rank: Frequent Contributor
timestamp: July 27, 2018, 02:56:46 pm
content: You don't need to reinvent the wheel and do all the bitbanging yourself. You can configure multiple kernel bitbanged I2C interfaces using thei2c-gpio overlay.This doesn't solve your problem about needing 5 samples at precisely the same time though.

author: orin
author_rank: Frequent Contributor
timestamp: July 27, 2018, 06:45:54 pm
content: Quote from: MrAureliusR on July 26, 2018, 12:33:50 pmI've been bitten by this kind of bug before.I find it's best to edit forum posts in an external text editor, then copy/paste the text into the forum. Just use the forum's compose window to create any tag/emoticon you want to use.

author: jancumps
author_rank: Unknown
timestamp: July 27, 2018, 08:11:58 pm
content: For the sampling at the same time, an ADC famiky with sync input maybe?

author: boB
author_rank: Frequent Contributor
timestamp: July 27, 2018, 08:32:37 pm
content: Quote from: Siwastaja on July 26, 2018, 09:37:31 amThis is kinda what I heard. Has anyone heard about the newer STM32F4 parts regarding I2C brokenness ?boBK7IQ