PCF8574P with keypad on I2C help

Now on my next learn process and attempting to understand the use of I2C communication but require a little help. I have a PCF8574P and a 4x4 matrix membrane keypad that i am trying to get to communicate with a picaxe 08M2 but not having much success at the moment below is the code i am using and a schematic of my circuit can somebody point out where i am going wrong.

Code:
;picaxe 08M2
;SCL connected to pin6 C.1
;SDA connected to pin5 C.2
;INT not connected
;A0-A2 connected to +5V for slave address 01001111 (read)
;LCD module connected to pin7 C.4


symbol keyint=pin3
symbol key=b1
setfreq m16


MAIN:
;debug
if keyint = 0 then PCF8574_read ;interupt from PCF8574P when key is pressed
goto main

PCF8574_read:
i2cslave %01001111, i2cslow, i2cbyte ; setup i2c to read from pcf8574
pause 1000
readi2c (key)
pause 1000
sertxd (#key)
serout c.4, n2400_8, (254,1)
	serout c.4, n2400_8, (254,128, #key)	
select case key					;checked for key pressed on 4x4 keypad	
		case is $87 goto main			;key 1
		case is $84 goto main			;key 2
		case is $82 goto main			;key 3
		case is $48 goto main			;key 4
		case is $44 goto main			;key 5
		case is $42 goto main			;key 6
		case is $28 goto main			;key 7
		case is $24 goto main			;key 8
		case is $22 goto main			;key 9
		case is $30 goto menu			;key *
		case is $12 goto main			;key #
		case is $81 goto raisevolts		;key A
		case is $41 goto lowervolts		;key B
		case is $21 goto machine		;key C
		case is $11 goto type			;key D
		else  goto main			
	end select

goto PCF8574_write

PCF8574_write:
i2cslave %01001110, i2cslow, i2cbyte ;setup i2c to write to pcf8574
; code here if required
goto main

menu: 	;display menu on lcd
;insert code here for menu
goto main

raisevolts:		;increase output volts
;insert code for increasing voltage here
goto main

lowervolts:		;lower output volts
;insert code for lowering volts here
goto main

machine:		;select pulsed or fixed
;insert code for selecting pedal type here
goto main

type:			;select line or shade
;insert code for selecting machine type here
goto main
pcf8574p cct.jpg
 

Rick100

Senior Member
Hello,

Here is a piece of code of uses an 8574 to read a keypad.



Code:
'read keypad with 8574 I2C IO expander
'Rows on 8574    P0 - P3
'Columns on 8574 P4 - P7
'
'attempt to use 8574 interrupt needs more work
'if button is pushed for short period of time the int will be jumped to but port status has changed back
'before it can be read
'if button is pushed down and held the int will be jumped to and then jumped to again on release

#picaxe 08m2
#no_data

Symbol IO_ADR = $40		'I2C address of 8574 all address line low
'Symbol IO_ADR = $4E	'I2C address of 8574 all address line hi

Symbol reserved = b0
symbol bitWeight = b2
symbol keyPressed = b3
symbol temp = b4

pullup %00001000	'pullup enabled on C.3 (leg 4) in case use of 8574 INT

HI2cSetup I2CMASTER, IO_ADR, I2CSLOW, I2CBYTE

'HI2cOut(%11110000)	'all rows low, all columns high if you want to use interrupt

main:		
keyPressed = 255	'default value
'if pinC.3 = 0 then	'uncomment these lines to try interrupt
'	sertxd("interrrupt",13,10)
	gosub getKey
'	HI2cOut(%11110000)	'all rows low, all columns high
'endif


if keyPressed < 128 then
	sertxd("key pressed = ",#keyPressed,13,10)
endif

goto main

getKey:	'returns with keyPressed < 128 if key was pressed
'puts low on each Row pin and checks column pin for change
'uses lokdown table to translate port value to 0-15 left in keyPressed variable
bitWeight = 1
keyPressed = 255	'default value
do
	temp = not bitWeight	'invert the bits
	HI2cOut(temp)
	HI2cIn (b0)
	'sertxd("Read = ",#bit7,#bit6,#bit5,#bit4,#bit3,#bit2,#bit1,#bit0,13,10)	'debug only
	if b0 <> temp then	'a key must be pressed
		lookdown b0,($EE,$DE,$BE,$7E, $ED,$DD,$BD,$7D, $EB,$DB,$BB,$7B, $E7,$D7,$B7,$77),keyPressed
		exit
	endif
	bitWeight = bitWeight * 2	'shift bit left
loop while bitWeight <= 8

return
You will need to change your wiring. On the schematic you have 220 ohm pullup resistors. They need to be removed. You may want to put 330 ohm protection resistors inline, on all the lines to the keypad. I'm not sure they're needed with the 8574 but I don't think they will hurt anything. Maybe someone else can give their opinion on that.
I tried to modify the code to use the interrupt line from the 8574 but I wasn't happy with the results. You can read the comments at the top of the program about that. I left the interrupt stuff in the code. If you want to try it, just remove the comments.
To use a 8574 pin as an input you need to set the pin to a high state. Then a switch or other signal will connect it to ground. To read the keypad you set all pins high, and pull each row line low. After the line is pulled low you look for a low on the column pins. In other words, write to the port with all the bits set except one row, and then read the port. If the values that is read is different from what was written, then a key must have been pressed. The lookdown table translates the port values into a number between zero and fifteen. The only values the lookdown will translate are when one row and one column are low. Other port values indicate more than one button was pressed.
I tested the code on a breadboard. I didn't have a keypad but I used a button between all the pin combinations. It seems to work. You may have to change the I2C address address at the top of the program.


Good luck,
Rick
 

Goeytex

Senior Member
You are using the I2Cslave command with readI2C and Write I2C. These commands have been deprecated, and while they still work, you should be using HI2Csetup and the HI2CIN/HI2OUT commands. See manual 2 P72 -P79.

The I2Cslave or Hi2Csetup command only needs to be done once at the beginning of the program, not before each read or write. The R/W bit is ignored with these commands anyway. ( See Picaxe Manual 2 P.78) . Bit0 is the R/W bit which is automatically controlled by the HI2CIN and HI2COUT commands.

According to the 8574 datasheet, INT is an open drain output. Therefore there should be a pullup on the INT line (Picaxe C.3 ). I would suggest a 10K pullup resistor. You could optionally use the Picaxe Pullup command (weak pullup). But, I prefer an stronger pullup in most cases. Your choice.


According to the 8574 Datasheet:

"To conserve power, no internal pull-upresistors are incorporated on A2, A1 or A0, so they must be externally held HIGH or LOW."​

Therefore, A0, A1 & A2 should have 10K pull down resistors (if you ever want to change the address). With no pull down resistors the inputs will float if a switch is open giving unpredictable results and an unpredictable address. Either add the resistors or eliminate the DIP switch and hard wire the pins to VDD for an address of 0 1 0 0 1 1 1 x.
 
Last edited:
Thanks for the info Rick100 and Goeytex, it now gives me something to work on. I will make the mods to my schematic and do some more testing then repost my results
Just made the changes to my schematic from your comments, is this correct now before i start breadboarding to test the code ?2014-02-03_13-01-05.jpg
 
Last edited:
Ok made the changes to the schematic and prototyped the board, everything works ok on your code sample made a couple of changes for the serial oled and the key presses are displayed ok (0-15). I briefly removed the comments from the interupt code but as yet have had no success with it, the pull up holds the int high and it does go low on a key press then returns back to high after around a second (with the interupt commented out) But with the code in then the interrupt remains high with no change when a key press occurs. I am going to read the datasheet again and see if i can come up with a solution.
 

westaust55

Moderator
An interrupt is generated when any input pin changes state.
The PCF8574 is a bit basic in its IO so your driving "outputs" could also be seen as input state changes.

The interrupt is reset by reading to or writing from the IO port.
 

Goeytex

Senior Member
By eliminating the select switch and tying A0,A1 $A2 to ground the new address is 0100000x . I trust you changed the code accordingly?
 

Rick100

Senior Member
Hello,

I worked on my code a little more. It now uses the picaxe interrupt to respond to the 8574 INT output. The program jumps to the interrupt routine when pin C.3 goes low. It then uses inline code instead of the FOR NEXT loop to read the keys. I think this a little faster. It stays in the interrupt routine until all keys are released. A flag is set to indicate the foreground program needs to process the key. If the foreground program does't record the keypress before another key is pressed, the key will be lost. The program seems well behaved but could still be improved. You can uncomment the '#define SER_DEBUG' line for a better idea of program flow.

Code:
'read keypad with 8574 I2C IO expander with interrupt
'8574 pin 13 connected to picaxe C.3 (leg 4)
'Rows on 8574    P0 - P3
'Columns on 8574 P4 - P7
'
'will wait in interrrupt routine until no key is pressed, so holding a key down will appear to hang program


#picaxe 08m2
#no_data
'#define SER_DEBUG	'uncomment this to print debugging stuff with sertxd, it will slow response
							'so hold key down longer or it will be missed 

Symbol IO_ADR = $40		'I2C address of 8574 all address line low
'Symbol IO_ADR = $4E	'I2C address of 8574 all address line hi

Symbol reserved = b0	'reserved for bit twiddling

symbol keyPressed = b3	'value of ;last key read
symbol intTemp = b4		'used in interrupt routine

Symbol bitFlags = b2	'some flag bits
Symbol keyFlag = bit8	'set if a key needs processing	'

pullup %00001000	'pullup enabled on C.3 (leg 4) for 8574 INT pin


HI2cSetup I2CMASTER, IO_ADR, I2CSLOW, I2CBYTE

sertxd("Ready",13,10)

HI2cOut(%11110000)	'all rows low, all columns high so any key down will fire interrupt pin
setint %00000000,%0001000	'interrupt on C.3 low, 8574 INT pin is active low

'----------loop for demostration
main:		

if keyFlag = 1 then	'has interrupt set flag 
	sertxd("key pressed = ",#keyPressed,13,10,13,10)
	keyFlag = 0	'clear flag
endif

goto main
'----------


interrupt:
#ifdef SER_DEBUG
sertxd("interrupt",13,10)	'debug only
#endif
'returns with keyFlag = 1 if key was pressed
'puts low on each Row pin and checks column pin for change
'uses lokdown table to translate port value to 0-15 left in keyPressed variable

keyFlag = 0	'default value = key not found

HI2cIn (intTemp)
if intTemp = %11110000 then goto exitInt	'key was not held long enough and we missed it

'inline code to clear each row line and check for key on that row
HI2cOut(%11111110)	'write the row value
HI2cIn (intTemp)		'read in port
#ifdef SER_DEBUG
b0 = intTemp	'debug only
sertxd(" int intTemp = ",#bit7,#bit6,#bit5,#bit4,#bit3,#bit2,#bit1,#bit0,13,10)	'debug only
#endif
if intTemp <> %11111110 then goto foundKey	'if read and write value are different, a key was pressed

HI2cOut(%11111101)
HI2cIn (intTemp)
#ifdef SER_DEBUG
b0 = intTemp	'debug only
sertxd(" int intTemp = ",#bit7,#bit6,#bit5,#bit4,#bit3,#bit2,#bit1,#bit0,13,10)	'debug only
#endif
if intTemp <> %11111101 then goto foundKey

HI2cOut(%11111011)
HI2cIn (intTemp)
#ifdef SER_DEBUG
b0 = intTemp	'debug only
sertxd(" int intTemp = ",#bit7,#bit6,#bit5,#bit4,#bit3,#bit2,#bit1,#bit0,13,10)	'debug only
#endif
if intTemp <> %11111011 then goto foundKey

HI2cOut(%11110111)
HI2cIn (intTemp)
#ifdef SER_DEBUG
b0 = intTemp	'debug only
sertxd(" int intTemp = ",#bit7,#bit6,#bit5,#bit4,#bit3,#bit2,#bit1,#bit0,13,10)	'debug only
#endif
if intTemp <> %11110111 then goto foundKey

goto exitInt	'if we got here we still missed it

foundKey:
keypressed = 255	'default value in case of no match in lokdown table
lookdown intTemp,($EE,$DE,$BE,$7E, $ED,$DD,$BD,$7D, $EB,$DB,$BB,$7B, $E7,$D7,$B7,$77),keyPressed
#ifdef SER_DEBUG
	sertxd(" int keyPressed = ",#keyPressed,13,10)	'debug only
#endif
if keypressed <> 255 then
	keyFlag = 1	'must have found it in lookdown table
endif

exitInt:
HI2cOut(%11110000)	'all rows low, all columns 
do
	HI2cIn (intTemp)
loop until intTemp = %11110000	'wait for no keys pressed

#ifdef SER_DEBUG
sertxd("exit int",13,10)
#endif

setint %00000000,%0001000	'interrupt on C.3 low
return				; return from interrupt
Good luck,
Rick
 
I have been playing around with the program with interrupt above kindly done by Rick100 and it works a lot better than without the interrupt. I am learning a lot through following examples kindly supplied by the experts on the forum. I have been trying to get the above code to work now on a 18M2 instead of the 08M2 and everything seems to be working i have changed the interrupt to C.0 and the oled is on C.1, however now i am trying to do something with the result when a i have the keypressed variable i am unsure where to put it as i get all sorts of weird results. I am trying to use the key presses to select different menu screens using the select case and the jumping to the menu sub. I have been placing my code main sub but cannot get it to function correctly, basically the code i am trying is like the following, although i get it to work (in a fashion) i do not seem to be able to break out of the menu sub and the oled keeps scrolling around with the text.
Code:
'read keypad with 8574 I2C IO expander with interrupt
'8574 pin 13 connected to picaxe C.3 (leg 4)
'Rows on 8574    P0 - P3
'Columns on 8574 P4 - P7
'
'will wait in interrrupt routine until no key is pressed, so holding a key down will appear to hang program


#picaxe 18m2
#no_data
;#define SER_DEBUG	'uncomment this to print debugging stuff with sertxd, it will slow response
							'so hold key down longer or it will be missed 

Symbol IO_ADR = $40		'I2C address of 8574 all address line low
'Symbol IO_ADR = $4E	'I2C address of 8574 all address line hi

Symbol reserved = b0	'reserved for bit twiddling

symbol keyPressed = b3	'value of ;last key read
symbol intTemp = b4		'used in interrupt routine

Symbol bitFlags = b2	'some flag bits
Symbol keyFlag = bit8	'set if a key needs processing	'

pullup %00000001	'pullup enabled on C.3 (leg 4) for 8574 INT pin


HI2cSetup I2CMASTER, IO_ADR, I2CSLOW, I2CBYTE
sertxd("Ready",13,10)
;goto loadmenu
serout c.1, n2400, (254,1)
serout c.1, n2400, (254,128,"Ready")
HI2cOut(%11110000)	'all rows low, all columns high so any key down will fire interrupt pin
setint %00000000,%0000001	'interrupt on C.0 low, 8574 INT pin is active low

'----------loop for demostration
main:		

if keyFlag = 1 then	'has interrupt set flag 
	sertxd("key pressed = ",#keyPressed,13,10,13,10)
	;serout c.1, n2400, (254,1)
	;if keyPressed = 1 then loadsetup
	keyFlag = 0	'clear flag
endif
select case keypressed
	case 1 gosub case1
	case 2 gosub case2
	end select
goto main
'----------


interrupt:
#ifdef SER_DEBUG
sertxd("interrupt",13,10)	'debug only
#endif
'returns with keyFlag = 1 if key was pressed
'puts low on each Row pin and checks column pin for change
'uses lokdown table to translate port value to 0-15 left in keyPressed variable

keyFlag = 0	'default value = key not found

HI2cIn (intTemp)
if intTemp = %11110000 then goto exitInt	'key was not held long enough and we missed it

'inline code to clear each row line and check for key on that row
HI2cOut(%11111110)	'write the row value
HI2cIn (intTemp)		'read in port
#ifdef SER_DEBUG
b0 = intTemp	'debug only
sertxd(" int intTemp = ",#bit7,#bit6,#bit5,#bit4,#bit3,#bit2,#bit1,#bit0,13,10)	'debug only
#endif
if intTemp <> %11111110 then goto foundKey	'if read and write value are different, a key was pressed

HI2cOut(%11111101)
HI2cIn (intTemp)
#ifdef SER_DEBUG
b0 = intTemp	'debug only
sertxd(" int intTemp = ",#bit7,#bit6,#bit5,#bit4,#bit3,#bit2,#bit1,#bit0,13,10)	'debug only
#endif
if intTemp <> %11111101 then goto foundKey

HI2cOut(%11111011)
HI2cIn (intTemp)
#ifdef SER_DEBUG
b0 = intTemp	'debug only
sertxd(" int intTemp = ",#bit7,#bit6,#bit5,#bit4,#bit3,#bit2,#bit1,#bit0,13,10)	'debug only
#endif
if intTemp <> %11111011 then goto foundKey

HI2cOut(%11110111)
HI2cIn (intTemp)
#ifdef SER_DEBUG
b0 = intTemp	'debug only
sertxd(" int intTemp = ",#bit7,#bit6,#bit5,#bit4,#bit3,#bit2,#bit1,#bit0,13,10)	'debug only
#endif
if intTemp <> %11110111 then goto foundKey

goto exitInt	'if we got here we still missed it

foundKey:
keypressed = 255	'default value in case of no match in lokdown table
lookdown intTemp,($EE,$DE,$BE,$7E, $ED,$DD,$BD,$7D, $EB,$DB,$BB,$7B, $E7,$D7,$B7,$77),keyPressed
#ifdef SER_DEBUG
	sertxd(" int keyPressed = ",#keyPressed,13,10)	'debug only
#endif
if keypressed <> 255 then
	keyFlag = 1	'must have found it in lookdown table
endif

exitInt:
HI2cOut(%11110000)	'all rows low, all columns 
do
	HI2cIn (intTemp)
loop until intTemp = %11110000	'wait for no keys pressed

#ifdef SER_DEBUG
sertxd("exit int",13,10)
#endif

setint %00000000,%0000001	'interrupt on C.0 low
return				; return from interrupt

	
case1:
	do
	serout c.1, n2400, (254,1) 
	serout c.1,n2400, ("screen 1")
	loop while keypressed = 1
case2:
	do
	serout c.1, n2400, (254,1) 
	serout c.1,n2400, ("screen 2")
	loop while keypressed = 2
 

Rick100

Senior Member
Hello,
Your code uses gosubs to enter case1 and case2 but they don't have returns. Add returns at the end of each routine, like this and try it.
Code:
[color=Black]case1:
   [/color][color=Blue]do
   serout c.1[/color][color=Black], [/color][color=Blue]n2400[/color][color=Black], [/color][color=Blue]([/color][color=Navy]254[/color][color=Black],[/color][color=Navy]1[/color][color=Blue]) 
   serout c.1[/color][color=Black],[/color][color=Blue]n2400[/color][color=Black], [/color][color=Blue]([/color][color=Red]"screen 1"[/color][color=Blue])
   loop while [/color][color=Purple]keypressed [/color][color=DarkCyan]= [/color][color=Navy]1
   [/color][color=Blue]return[/color]
[color=Black]case2:
   [/color][color=Blue]do
   serout c.1[/color][color=Black], [/color][color=Blue]n2400[/color][color=Black], [/color][color=Blue]([/color][color=Navy]254[/color][color=Black],[/color][color=Navy]1[/color][color=Blue]) 
   serout c.1[/color][color=Black],[/color][color=Blue]n2400[/color][color=Black], [/color][color=Blue]([/color][color=Red]"screen 2"[/color][color=Blue])
   loop while [/color][color=Purple]keypressed [/color][color=DarkCyan]= [/color][color=Navy]2
   [/color][color=Blue]return[/color]
Good luck,
Rick
 
Top