Sunday, January 25, 2009

Step by Step: interfacing a HD44780-compatible LCD display

In this "Step by Step" tutorial, we're going to (hopefully) have some fun with a LCD display. Particularly one compatible with HD44780 specifications, which seem to be most widely used.


Setting up the hardware

As usual, there are plenty resources on the web. I found this one quite nice, covering lots of thing. Basically, LCDs can be accessed with two distinct interfaces: 4-bit or 8-bit interfaces. 4-bit interface requires less pins (4 pins), but is somewhat slow, 8-bit interface requires more pins (8 pins) but is faster. jallib comes with the two flavors, it's up to you deciding which is most important, but usually, pins are more precious than speed, particularly when using a 16F88 which only has 16 I/O pins (at best). Since 4-bit interface seems to be most used, and we'll use this one here...

So, I've never used LCD, to be honest. Most guys consider it as an absolute minimum thing to have, since it can help a lot when debugging, by printing messages. I tend to use serial for this... Anyway, I've been given a LCD, so I can't resist anymore :)

The LCD I have seems to be quite a nice beast ! It has 4 lines, is 20 characters long.



Looking closer, "JHD 204A" seems to be the reference. Near the connector, only a "1" and a "16". No pin's name.


Googling the whole points to a forum, and at least a link to the datasheet. A section describes the "Pin Assignement". Now I'm sure about how to connect this LCD.


For this tutorial, we're going to keep it simple:
  • as previously said, we'll use 4-bit interface. This means we'll use DB4, DB5, DB6 and DB7 pins (respectively pin 11, 12, 13 and 14).
  • we won't read from LCD, so R/W pin must be grounded (pin 5)
  • we won't use contrast as well, V5 pin (or Vee) must be grounded (pin 3)
Including pins for power, we'll use 10 pins out of the 16 available, 6 being connected to the PIC (RS, EN and 4 data lines).

For convenience, I soldered a male connector on the LCD. This will help when building the whole on a breadboard.



So we now have everything to build the circuit. Here's the schematic. It also includes a LED, it will help us checking everything is ok while powering up the board.



Using a breadboard, it looks like this:



Writing the software

For this tutorial, we'll use one of the available samples from jallib repository. I took one for 16f88, and adapt it to my board (specifically, I wanted to use PORTA when connecting the LCD, and let PORTB's pins as is).

As usual, writing a program with jallib starts with configuring and declaring some parameters. So we first have to declare which pins will be connected:

-- LCD IO definition
var bit lcd_rs is pin_a6 -- LCD command/data select.
var bit lcd_rs_direction is pin_a6_direction
var bit lcd_en is pin_a7 -- LCD data trigger
var bit lcd_en_direction is pin_a7_direction

var byte lcd_dataport is porta_low -- LCD data port
var byte lcd_dataport_direction is porta_low_direction

-- set direction
lcd_rs_direction = output
lcd_en_direction = output
lcd_dataport_direction = output

This is, pin by pin, the translation of the schematics. Maybe except porta_low. This represents pin A0 to A3, that is pins for our 4 lines interface. porta_high represents pin A4 to A7, and porta reprensents the whole port, A0 to A7. These are just "shorcuts".

We also have to declare the LCD geometry:

const byte LCD_ROWS = 4 -- 4 lines
const byte LCD_CHARS = 20 -- 20 chars per line

Once declared, we can then include the library and initialize it:

include lcd_hd44780_4   -- LCD library with 4 data lines
lcd_init() -- initialize LCD

For this example, we'll also use the print.jal library, which provides nice helpers when printing variables.

include print

Now the main part... How to write things on the LCD.

  • You can either use a procedure call: lcd_writechar("a")
  • or you can use the pseudo-variable : lcd = "a"
  • lcd_setcursor(x,y) will set the cursor position. x is the line, y is the row, starting from 0
  • finally, lcd_clearscreen() will, well... clear the screen !
Full API documentation is available on jalapi.

So, for this example, we'll write some chars on each line, and print an increasing (and incredible) counter:

const byte str1[] = "Hello world!" -- define strings
const byte str2[] = "third line"
const byte str3[] = "fourth line"

print_string(lcd, str1) -- show hello world!
lcd_setcursor(2,0) -- to 3rd line
print_string(lcd, str2)
lcd_setcursor(3,0) -- to 4th line
print_string(lcd, str3)

var byte counter = 0

forever loop -- loop forever

counter = counter + 1 -- update counter
lcd_setcursor(1,0) -- second line
print_byte_hex(lcd, counter) -- output in hex format
delay_100ms(3) -- wait a little

if counter == 255 then -- counter wrap
lcd_setcursor(1,1) -- 2nd line, 2nd char
lcd = " " -- clear 2nd char
lcd = " " -- clear 3rd char
end if

end loop


The full and ready-to-compile code is available on jallib group's file section:

You'll need last jallib-pack, available on jallib's download section.



How does this look when running ?

Here's the video !






Sébastien Lelong

Tuesday, January 20, 2009

Step by Step: building an i2c slave with jallib (part 3)

Part 3 : implementing an i2c slave ISR


In previous parts of this tutorial, we've seen a little of theory, we've also seen how to check if the i2c bus is operational, now the time has come to finally build our i2c slave. But what will slave will do ? For this example, slave is going to do something amazing: it'll echo received chars. Oh, I'm thinking about something more exciting: it will "almost" echo chars:
  • if you send "a", it sends "b"
  • if you send "b", it sends "c",
  • if you send "z", it sends "{"
  • ...
(why "{" ? According to ASCII, "z" is the character for position 122. 123 is... "{")


Building the i2c master

Let's start with the easy part. What will master do ? Just collect characters from a serial link, and convert them to i2c commands. So you'll need a PIC to which you can send data via serial. I mean you'll need a board with serial com. capabilities. I mean we won't do this on a breadboard... There are plenty out there on the Internet, pick your choice. If you're interested, you can find one on my SirBot site: dedicated to 16f88, serial com. available, and i2c ready (pull-ups resistors).

It looks like this:



Two connectors are used for earch port, PORTA and PORTB, to plug daughter boards, or a breadboard in our case.

We'll use a 16f88 as a i2c master. 16f88 only own a SSP module, not MSSP, this means it can't handle i2c in hardware (no built-in i2c master). Not a problem, we'll use i2c software library.

The i2c initialization part is quite straight forward. SCL and SDA pins are declared, we'll use a standard speed, 400KHz,


-- I2C io definition
var volatile bit i2c_scl is pin_b4
var volatile bit i2c_scl_direction is pin_b4_direction
var volatile bit i2c_sda is pin_b1
var volatile bit i2c_sda_direction is pin_b1_direction
-- i2c setup
const word _i2c_bus_speed = 4 ; 400kHz
const bit _i2c_level = true ; i2c levels (not SMB)
include i2c_software
i2c_initialize()

We'll also use the level 1 i2c library. The principle is easy: you declare two buffers, one for receiving and one for sending bytes, and then you call procedure specifying how many bytes you want to send, and how many are expected to be returned. Joep has written a nice post about this, if you want to read more about this. We'll send one byte at a time, and receive one byte at a time, so buffers should be one byte long.
const single_byte_tx_buffer = 1 -- only needed when length is 1
var byte i2c_tx_buffer[1]
var byte i2c_rx_buffer[1]
include i2c_level1

What's next ? Well, master also has to read chars from a serial line. Again, easy:

const usart_hw_serial = true
const serial_hw_baudrate = 57_600
include serial_hardware
serial_hw_init()
-- Tell the world we're ready !
serial_hw_write("!")

So when the master is up, it should at least send the "!" char.


Then we need to specify the slave's address. This is a 8-bits long address, the 8th bits being the bit specifying if operation is a read or write one (see part 1 for more). We then need to collect those chars coming from the PC and sends them to the slave.

The following should do the trick (believe me, it does :))


var byte icaddress = 0x5C -- slave address

forever loop
if serial_hw_read(pc_char)
then
serial_hw_write(pc_char) -- echo
-- transmit to slave
-- we want to send 1 byte, and receive 1 from the slave
i2c_tx_buffer[0] = pc_char
var bit _trash = i2c_send_receive(icaddress, 1, 1)
-- receive buffer should contain our result
ic_char = i2c_rx_buffer[0]
serial_hw_write(ic_char)
end if
end loop


The whole program is available on jallib SVN repository here (subject to change, since not released yet).


Building the i2c slave

So this is the main part ! As exposed on first post, we're going to implement a finite state machine. jallib comes with a library where all the logic is already coded, in a ISR. You just have to define what to do for each state encountered during the program execution. To do this, we'll have to define several callbacks, that is procedures that will be called on appropriate state.

Before this, we need to setup and initialize our slave. i2c address should exactly be the same as the one defined in the master section. This time, we won't use interrrupts on Start/Stop signals; we'll just let the SSP module triggers an interrupts when the i2c address is recognized (no interrupts means address issue, or hardware problems, or...). Finally, since slave is expected to receive a char, and send char + 1, we need a global variable to store the results. This gives:

include i2c_hw_slave

const byte SLAVE_ADDRESS = 0x5C
i2c_hw_slave_init(SLAVE_ADDRESS)

-- will store what to send back to master
-- so if we get "a", we need to store "a" + 1
var byte data

Before this, let's try to understand how master will talk to the slave (red) and what the slave should do (green), according to each state (with code following):
  • state 1: master initiates a write operation (but does not send data yet). Since no data is sent, slave should just do... nothing (slave just knows someone wants to send data).
procedure i2c_hw_slave_on_state_1(byte in _trash) is
pragma inline
-- _trash is read from master, but it's a dummy data
-- usually (always ?) ignored
end procedure

  • state 2: master actually sends data, that is one character. Slave should get this char, and process it (char + 1) for further sending.
procedure i2c_hw_slave_on_state_2(byte in rcv) is
pragma inline
-- ultimate data processing... :)
data = rcv + 1
end procedure

  • state 3: master initiates a read operation, it wants to get the echo back. Slave should send its processed char.
procedure i2c_hw_slave_on_state_3() is
pragma inline
i2c_hw_slave_write_i2c(data)
end procedure

  • state 4: master still wants to read some information. This should never occur, since one char is sent and read at a time. Slave should thus produce an error.
procedure i2c_hw_slave_on_state_4() is
pragma inline
-- This shouldn't occur in our i2c echo example
i2c_hw_slave_on_error()
end procedure

  • state 5: master hangs up the connection. Slave should reset its state.
procedure i2c_hw_slave_on_state_5() is
pragma inline
data = 0
end procedure

Finally, we need to define a callback in case of error. You could do anything, like resetting the PIC, and sending log/debug data, etc... In our example, we'll blink forever:

procedure i2c_hw_slave_on_error() is
pragma inline
-- Just tell user user something's got wrong
forever loop
led = on
_usec_delay(200000)
led = off
_usec_delay(200000)
end loop
end procedure

Once callbacks are defined, we can include the famous ISR library.
include i2c_hw_slave_isr

So the sequence is:
  1. include i2c_hw_slave, and setup your slave
  2. define your callbacks,
  3. include the ISR


The full code is available from jallib's SVN repository (caution, not released yet, could be modified):


You'll also need jallib-pack from here. Copy the two libraries to the "lib" directory, compile the two samples and program two 16f88.

(Edit on 01/30: libraries and samples are now include in jallib's version > 0.1, no extra download needed !)

Connecting and testing the whole thing...

As previously said, the board I use is ready to be used with a serial link. It's also i2c ready, I've put the two pull-ups resistors. If your board doesn't have those resistors, you'll have to add them on the breadboard, or it won't work (read part 2 to know and see why...).

I use a connector adapted with a PCB to connect my main board with my breadboard. Connector's wires provide power supply, 5V-regulated, so no other powered wires it required.

Connector, with power wires


Everything is ready...


Crime scene: main board, breadboard
and battery pack


Once connected, power the whole and use a terminal to test it. When pressing "a", you'll get a "a" as an echo from the master, then "b" as result from the slave.



What now ?

We've seen how to implement a simple i2c hardware slave. The ISR library provides all the logic about the finite state machine. You just have to define callbacks, according to your need.

i2c is a widely used protocol. Most of the time, you access i2c devices, acting as a master. We've seen how to be on the other side, on the slave side. Being on the slave side means you can build modular boards, accessible with a standard protocol. For instance, I've build a DC motor controller daughter board using this. It's a module, a unit on its own, just plug, and send/receive data, with just two wires.

And I also plan to build a LCD controller board, but that's for another "Step by Step" post :)


Reading:




Sébastien Lelong

Saturday, January 17, 2009

Step by Step: building an i2c slave with jallib (part 2)

Part 2 : checking the hardware and the i2c bus...


Last time, we saw a basic overview of how to implement an i2c slave, using a finite state machine implementation. Today, we're going to get our hands a little dirty, and starts connecting our master/slave together.

First of all, i2c is quite hard to debug, especially if you don't own an oscilloscope (like me). So you have to be accurate and rigorous. That's why, in this second part of this tutorial, we're going to setup the hardware, and just make sure the i2c bus is properly operational.

Connecting two PIC together through i2c is quite easy from a hardware point of view. Just connect SDA and SCL together, and don't forget pull-ups resistors. There are many differents values for these resistors, depending on how long the bus is, or the speed you want to reach. Most people use 2.2K resistors, so let's do the same ! The following schematics is here to help:


In this circuit, both PIC have a LED connected, which will help us understand what's going on. On a breadboard, this looks like that:


The master is on the right side, the slave on the left. I've put the two pull-ups resistors near the master:


Green and orange wires connect the two PICs together:




The goal of this test is simple: check if the i2c bus is properly built and operational. How ? PIC 16F88 and its SSP peripheral is able to be configured so it triggers an interrupts when a Start or Stop signal is detected. Read this page (part of an nice article on i2c, from last post's recommandations).

How are we gonna test this ? The idea of this test is simple:

  1. On power, master will blink a LED a little, just to inform you it's alive
  2. On the same time, slave is doing the same
  3. Once master has done blinking, it sends a i2c frame through the bus
  4. If the bus is properly built and configured, slave will infinitely blink its LED, at high speed

Note master will send its i2c frame to a specific address, which don't necessarily need to be the same as the slave one (and I recommand to use different addresses, just to make sure you understand what's going on).

What about the sources ? Download last jallib pack, then go get some other libraries and samples (those files aren't release yet, API may change):


(Edit on 01/30: those libraries and samples are now included in jallib's version > 0.1. No extra download needed)


The main part of the slave code is the way the initialization is done. A constant is declared, telling the library to enable Start/Stop interrupts [edit on 01/30, API has changed, previously init was taking a true/false parameter]:


const SLAVE_ADDRESS = 0x23 -- whatever, it's not important, and can be
-- different from the address the master wants
-- to talk to
-- with Start/Stop interrupts
const bit i2c_enable_start_stop_interrupts = true
-- this init automatically sets global/peripherals interrupts
i2c_hw_slave_init(SLAVE_ADDRESS)


And, of course, the Interrupt Service Routine (ISR):

procedure i2c_isr() is
pragma interrupt
if ! PIR1_SSPIF then
return
end if
-- reset flag
PIR1_SSPIF = false
-- tmp store SSPSTAT
var byte tmpstat
tmpstat = SSPSTAT
-- check start signals
if (tmpstat == 0b_1000) then
-- If we get there, this means this is an SSP/I2C interrupts
-- and this means i2c bus is properly operational !!!
while true loop
led = on
_usec_delay(100000)
led = off
_usec_delay(100000)
end loop
end if
end procedure



The important thing is to:
  • check if interrupt is currently a SSP interrupts (I2C)
  • reset the interrupt flag,
  • analyze SSPSTAT to see if Start bit is detected
  • if so, blinks 'til the end of time (or your battery)


Now, go compile both samples, and program two PICs with them. With a correct i2c bus setting, you should see the following:



On this next video, I've removed the pull-ups resistors, and it doesn't work anymore (slave doesn't high speed blink its LED).





Next time (and last time on this topic), we'll see how to implement the state machine using jallib, defining callback for each states.

Reading:



Sébastien Lelong

Wednesday, January 14, 2009

Step by Step: building an i2c slave with jallib (part 1)

Part 1 : a few words before getting our hands dirty...

i2c is a nice protocol: it is quite fast, reliable, and most importantly, it's addressable. This means that on a single 2-wire bus, you'll be able to plug up to 128 devices using 7bits addresses, and even 1024 using 10bits address. Far enough for most usage... I won't cover i2c in depth, as there are plenty resources on the Web (and I personally like this page).

i2c is found in many chips and many modules. Most of the time, you create a master, like when accessing an EEPROM. This time, in this tutorial, we're going to build a slave, which will thus respond to master's requests.

The slave side is somewhat more difficult because, as it does not initiate the talk, it has to listen to "events", and be as responsive as possible. You've guessed, we'll use interrupts. I'll only cover i2c hardware slave, that is using SSP peripheral (note: some PICs have MSSP, this means they can also be used as i2c hardware Master). Implementing an i2c software slave may be very difficult (and I even wonder if it's reasonable...).

There are different way implementing an i2c slave, but one seems to be quite common: defining a finite state machine. This implementation is well described in Microchip AppNote AN734 . It is highly recommended that you read this appnote, and the i2c sections of your favorite PIC datasheet as well (I swear it's quite easy to read, and well explained).

Basically, during an i2c communication, there can be 5 distinct states:
  1. Master writes, and last byte was an address : to sum up, master wants to talk to a specific slave, identified by the address, it wants to send data (write)
  2. Master writes, and last byte was data : this time, master sends data to the slave
  3. Master read, and last byte was an address : almost the same as 1., but this time, master wants to read something from the salve
  4. Master read, and last byte was data : just the continuation of state 3., master has started to read data, and still wants to read more data
  5. Master sends a NACK : basically, master doesn't want to talk to the slave anymore, it hangs up...

Note: in i2c protocol, one slave has actually two distinct addresses. One is for read operations, and it ends with bit 1. Another is for write operations, and it ends with bit 0.

Ex: consider the following address (8-bits long, last bit is for operation type)

0x5C => 0b_0101_1100 => write operation

The same address for read operation will be:

0x93 => 0b_0101_1101 = read operation


[EDIT: jallib currently supports up to 128 devices on a i2c bus, using 7-bits long addresses (without the 8th R/W bits). There's currently no support for 10-bits addresses, which would give 1024 devices on the same bus. If you need it, please let us know, we'll modify libraries as needed !]

OK, enough for today. Next time, we'll see how two PICs must be connected for i2c communication, and we'll check the i2c bus is fully working, before diving into the implementation.


Reading:



Sébastien Lelong

Tuesday, January 13, 2009

Common pitfall: digital pins aren't digital unless explicitely set as digital

If you've read some jallib samples, you may have often seen this line:

enable_digital_io() -- disable analog I/O (if any)

What does this mean and why ? This simply means you want to set all pins as digital pins. Because, by default, pins are configured as analog pins.

Where does this come from ? From the holy datasheet, of course ! Consider the 16f88 chip. PORTA provides the following functions:



Amongst them, some provide analog function, such Analog-to-Digital-Conversion (ADC). And it's explicitly said the PORTA pins are configured as analog inputs on Power-on Reset:


Datasheet is less explicit for PORTB. Two pins provide analog functions, for the last two ADC channels (pins RB6 and RB7):


Those pins are configured with ANSEL register, which is read as 1 on Power-on Reset, which means by default, pins are configured as analog:


Phew... Now why don't we, at jallib, set all pins as digital ? After all, most of the time, you need digital pins, and even if not, you could just set them as analog pins as needed. Yes, you could. But we decided to follow the holy datasheet. And this means if you read and know the datasheet, when you type this:

include 16f88

you expect to get analog pins, because the datasheet says so. To switch them to digital pins, just call
enable_digital_io(). This procedure is the same for every PIC: whatever the device file you're using, calling this procedure will configure and setup all registers so you have digital pins (and there are many differencies and inconsistencies between PICs, fortunately thanks to Rob's hard work, this is made easy for users).

So, one important point using jallib is: "be explicit". While this may need more typing from you, you'll get a better understanding of what's going on, and you'll finally get a cleaner and more maintainable code.

Sébastien Lelong

Sunday, January 4, 2009

Common pitfall: setting up registers while using bootloaders

I've recently been in trouble understanding how the 16F88's internal clock is working. Starting from a supposed i2c speed problem, I dive into internal clock's internals, with Joep, my guide... Here's what I learned.

First of all, I like using internal clock, because it requires less parts, provides still nice performances and consume less energy, which is good when working on embedded systems. Maybe the major drawback is you can't reach a decent 115 200 bds for your serial communication.

Anyway, I used to setup my 16f88 to run at 8 MHz. Yeah, just say:

pragma target CLOCK 8_000_000
pragma target OSC INTOSC_NOCLKOUT

and that's it, you'll run at 8 MHz... Will you ? Well, all my 16f88 always run @ 8MHz using this configuration. But Joep, the Grand Master of the Hidden Clock, said: "no you don't, because the holy datasheet says, by default (on power), this configuration will setup a clock running @ 31.25 KHz". Errr... 31.25 KHz, how can this be possible ? I'm sure it runs @ 8MHz. And why 31.25 KHz ? "Because IRCF bits are read as 0 when powered, see OSCCON register", said Joep the Grand Master.

So I looked at OSCCON register:



Yes, now I can see OSCON_IRCF bits selects the appropriate internal clock speed. IRCF = 0b_000 by default, and not 0b_111, which is the correct setting for an 8MHz clock.

Still, I'm sure it's running @ 8MHz. It's like my 16f88 decided to set IRCF to 0b_111, despite what datasheet can say... How can this be possible ?

Bootloaders... Yes, bootloaders can be sometime quite surprising. Because when using them, what you specify in your code about registers might not be what's programmed ! I use Tiny bootloader, a very nice and small one. And I use the internal clock version for 16f88, running @ 8MHz. And Tiny bootloader code correctly setup the internal clock. So the "default" value becomes what Tiny has set:
IRCF = 0b_111 that is, it runs @ 8MHz. Of course, you can still select the speed you want, by playing with IRCF, but the apparent default value is not what the datasheet says... Confusing :)

So, to be accurate, the appropriate way to specify an internal clock running @ 8MHz for a 16f88 is:

pragma target CLOCK 8_000_000
pragma target OSC INTOSC_NOCLKOUT
OSCCON_IRCF = 0b_111


Another pitfall I usually fall into is CCP1MUX. With this register, you can specify which pin, RB0 or RB3, will produce PWM for instance. Every time I set it to RB3, it just doesn't work. Why ? Because Tiny bootloader has set it to RB0. And this time, it cannot be changed, because Tiny bootloader (as any bootloader for PIC ?) cannot re-program such as register !

If using a bootloader, remember, the actual program being originally uploaded to your PIC is not your program, it's the bootloader itself. So remember its proper configuration might conflict with your own, or at least confuse you...

Solution ? Don't use bootloader, or be careful !