For the next model I surely need to spice up specs too. What is the funniest thing one can make with three LEDs? Traffic lights probably, which gives away the biggest shortcoming of my console. But what would be the best choice for graphics? 3D's popular but I believe it is nothing more but a passing fad. Like who ever talks about how they remember particulary nice polygons or shaders from their favorite games? No one.
No, the best games are all about good gameplay, fascinating storylines or memorable characters like Super Mario, Link and Sonic. And what style of graphics has more characters than ASCII graphics? That's why I ordered this 16 x 2 character display LCD 1602 from DealExtreme for less than 5 euros.
Front side of the LCD with power and data wires attached. |
Back side of the circuit. The I2C expander is the black circuit. |
To ease out my task, I'll begin programming with Raspberry Pi and Python instead of AVR and C. Compared to C language, Python is much more flexible and faster to write, making it ideal for prototyping. It is an interpreted language, meaning that it doesn't require compiling but on the downside isn't generally as fast to execute. However, there are powerful and efficient libraries for pretty much everything that makes Python useful for computationally demanding applications as well. At work I use Python for measurement control, data analysis and simulations, for example. And the best part is that it is free and also very easy to learn (you can find Python tutorials for example at Codecademy).
I decided to use RPi.GPIO module to control GPIO pins. On the display, there are four pins to be connected to the Pi. I connected VCC to pin 1, GND to pin 6 and the I2C lines SCL and SDA to pins 3 and 5 respectively. There are two different numbering schemes for the Pi's GPIO pins that RPi.GPIO supports. I decided to use the physical numbering i.e. how the pins are arranged on the board, as I find it easier to use.
Physical numbering of Raspberry Pi GPIO pins [source] |
The display lights up immediately when connected to the Pi. |
In the idle state both SDA (serial data) and SCL (serial clock) lines are in HIGH state. The communication starts when the master device (Raspberry Pi) sets the SDA line down while SCL is kept HIGH. This tells the slave (I2C chip of the display) to get ready to receive data. Normally SDA shouldn't change when SCL is HIGH.
As I2C supports multiple devices on the same bus, we need a way to tell which device we want to communicate with. Therefore the first byte send after the start condition contains a 7-bit address that matches the correct device. The 8th bit is a read/write bit indicating whether the master wants to send to or receive data from the slave (HIGH for read, LOW for write). The master sets the state of the SDA line according to each bit and sends the clock pulse on SCL when the bit is ready to be read.
After the 8th bit, the master gives the control of the SDA line to the slave. If there is a slave device with the correct address, it sets the SDA line LOW, indicating that it is ready to receive or send data. If not, then SDA should stay HIGH and the master decides how to continue. This as called as the acknowledge bit (ACK). The master reads the ACK bit and sends the 9th clock pulse on SCL.
If the ACK was LOW then it is time to read or write some data. The procedure for writing is pretty much the same as with the previous step; the master sets the SDA line and informs the slave with SCL pulses. After 8 bits the control of SDA is given to the slave and the ACK bit is read. Reading the data is the same, except now the slave controls the SDA line and the master sets the ACK bit. In both cases SCL is controlled by the master, however.
This can be repeated for arbitrary number of bytes. When the last byte is written/read, the master sets the SDA line LOW. Then it sets the SCL to the HIGH state and sets the SDA line HIGH as well. Change of SDA from LOW to HIGH when SCL is HIGH tells the slave that the communication is over.
And here is how I implemented it in Python:
import RPi.GPIO as GPIO
#Pin numbering mode
GPIO.setmode(GPIO.BOARD)
#pins
SCL = 3
SDA = 5
#set pins to output
GPIO.setup(SCL,GPIO.OUT)
GPIO.setup(SDA,GPIO.OUT)
#set the bus to ready state
GPIO.output(SDA,True)
GPIO.output(SCL,True)
#chip address
ADDRESS = [0,1,0,0,1,1,1]
def write_bytes(array_of_bytes):
#add read/write bit to address and add the result to array
array_of_bytes = [ADDRESS+[0]] + array_of_bytes
#start condition (bus should be ready)
GPIO.output(SDA,False)
#write bytes
GPIO.output(SCL,False) #Clock LOW
for byte in array_of_bytes:
for bit in byte:
GPIO.output(SDA,bit) #Set data line
GPIO.output(SCL,True) #Clock HIGH
GPIO.output(SCL,False) #Clock LOW
if not byte is array_of_bytes[-1]:
#if the byte is not the last one -> acknowledge
GPIO.setup(SDA,GPIO.IN) #Give SDA contol to slave
GPIO.output(SCL,True) #Clock HIGH
print 'ACK: ', GPIO.input(SDA) #read the ACK bit
GPIO.output(SCL,False) #Clock LOW
GPIO.setup(SDA,GPIO.OUT) #Regain the control of SDA
GPIO.output(SDA,True)
else:
#stop condition
GPIO.output(SDA,False)
GPIO.output(SCL,True)
GPIO.output(SDA,True)
ADDRESS = 0100111 comes partly from the datasheet of PCF8574 which converts the serial data sent through I2C to parallel pin states and vice versa. The beginning 0100 is fixed but the last three bits are set through A2...0 pins of the chip. Luckily the manufacturer was kind enough to provide the circuit diagram of the display and the expander chip. Here's its essential part:
We see all address pins are connected to VCC, so the ending of the address is 111. Now we have everything to test the code out.
Before getting into the display itself, I first wanted to try something simple. From the datasheets and the diagram above, we can see that the pin P3 of the I2C chip controls the backlight LED of the display through a transistor. So writing something like 00000000 to the chip would turn it off and 00001000 back on. Using my I2C code it looks like this:
write_bytes([[0,0,0,0,0,0,0,0]]) #turn backlight OFF time.sleep(1) #1 sec delay, remember to import time write_bytes([[0,0,0,0,1,0,0,0]])
#turn backlight ON
GPIO.cleanup() #Clears the pin setup on exit
And the light turns off and on again! I guess I would qualify as an IT support person. I didn't took video this time but I'm sure you can imagine how it looks like.
So the I2C communication is up and running. In the next post I'll take this step further and actually write something on the screen.
---
Ps. Now, when I'm writing these things down, they seem and feel straightforward but it's very misleading. There are some tedious stuff that are necessary to go through, like gathering information from datasheets and internet, but typically most of the time goes to hunting down errors and mistakes. Usually stupid ones.
Like here I first thought that I don't have to read the ACK bit, because I'll notice it anyway whether the screen works or not. I'll save a bit of effort just neglecting it. As a result I spend all night (until 4 am) trying to fix my code in vain. The next morning I started to think that maybe giving the control to the slave is actually important and I got it working in less than 10 minutes.
Lesson learned: Respect the protocol.
References:
LCD 1602 datasheet
PCF8574 datasheet
I2C on Wikipedia: https://en.wikipedia.org/wiki/I²C
Python logo https://commons.wikimedia.org/wiki/File:Python_logo_and_wordmark.svg
No comments:
Post a Comment