Hallo
Endlich habe ich mal Zeit um mich mit dem PCF8574 als I2C-Porterweiterung zu beschäftigen. Nach einem guten Start mit der 4x4-Tastatur habe ich mir nun eine LCD-Ansteuerung vorgenommen. Das sieht dann so aus:
Bild hier Bild hier Bild hier
http://www.youtube.com/watch?v=tD82ZbdWSwo
http://www.youtube.com/watch?v=gx4wFKOHa-g
Die Hardware ist quasi Standard. Das LCD wird im 4-Bit-Modus nur schreibend angeschlossen. Die Datenbits 4 bis 7 gehen auf den Port 4 bis 7, Register select (RS), read/write (RW) und enable (E) auf Port 0 bis 2. Port 3 ist frei für zweites E oder Backlight-LED. (Das ist nicht die Belegung aus dem Wiki!)
Nun zur Software. Da der RP6 für seine Base keine LCD-Funktionen zur Verfügung stellt, habe ich mich bei der Library des m32 bedient. Das m32 verwendet allerdings eine parallele Schnittstelle zum LCD, deshalb habe ich die Ausgabefunktionen zum LCD durch die Funktionen der asuro-I 2 C-Erweiterung ersetzt. Das alles habe ich letztlich in eine kleine Lib eingebaut:
RP6Base_I2C_PCF8574.h
Code:
// LCD und Keys für die RP6-Base über I2C. 8.11.2010 mic
// An die Base angepasste m32-Library
#ifndef RP6Base_I2C_PCF8574_H
#define RP6Base_I2C_PCF8574_H
#include "RP6Base_I2C_PCF8574.h"
#include "RP6I2CmasterTWI.h"
#include <avr/pgmspace.h> // Program memory (=Flash ROM) access routines.
#include <avr/io.h> // I/O Port definitions
#include <stdint.h> // Standard Integer definitions
#include <stdlib.h> // C standard functions (e.g. itoa...)
#include <string.h>
#include <avr/interrupt.h> // Signal and other Interrupt macros
// Assembly and system macros:
#define nop() asm volatile("nop\n\t")
#define sysSleep() asm volatile("sleep\n\t")
////// PCF8574p Addressen und Pins (Defines aus asurowiki und asuro-Buch Band 2))
#define pcf8574_address_KEYS 0b01110000 // I2C Adresse des PCF8574 KEYS
#define pcf8574_address_LCD 0b01110010 // I2C Adresse des PCF8574 LCD
#define RS_bit 0 //Register Select Bit ( high for data, low for command)
#define RW_bit 1 //H: Read / L: Write
#define E_bit 2 //Enable Bit
#define LED_bit 3 //H: LED Off / L: LED on
#define LD4 4 // Pin to Data Bus 4
#define LD5 5 // Pin to Data Bus 5
#define LD6 6 // Pin to Data Bus 6
#define LD7 7 // Pin to Data Bus 7
/*****************************************************************************/
// LCD:
void setLCDD(uint8_t lcdd);
void write4BitLCDData(uint8_t data);
void writeLCDCommand(uint8_t cmd);
void initLCD(void);
void clearLCD(void);
void clearPosLCD(uint8_t line, uint8_t pos, uint8_t length);
void writeCharLCD(uint8_t ch);
#define writeStringLCD_P(__pstr) writeNStringLCD_P((PSTR(__pstr)))
void writeStringLengthLCD(char *string, uint8_t length, uint8_t offset);
void writeStringLCD(char *string);
void writeNStringLCD_P(const char *pstring);
void _showScreenLCD_P(const char *line1, const char *line2);
#define showScreenLCD(__line1,__line2); ({_showScreenLCD_P((PSTR(__line1)),(PSTR(__line2)));})
#ifndef HEX
#define HEX 16
#endif
#ifndef DEC
#define DEC 10
#endif
#ifndef OCT
#define OCT 8
#endif
#ifndef BIN
#define BIN 2
#endif
void writeIntegerLCD(int16_t number, uint8_t base);
void writeIntegerLengthLCD(int16_t number, uint8_t base, uint8_t length);
void setCursorPosLCD(uint8_t line, uint8_t pos);
/*****************************************************************************/
// Keys:
uint16_t PollSwitch16(void); // Liest 4x4-Tastenmatrix des RA2 per I2C
uint8_t getPressedKeyNumber(void);
uint8_t getKeyNumber(uint16_t mask); // prüft welche Taste gedrückt wurde
uint8_t checkPressedKeyEvent(void);
uint8_t checkReleasedKeyEvent(void);
#endif
RP6Base_I2C_PCF8574.c
Code:
#include "RP6RobotBaseLib.h"
#include "RP6Base_I2C_PCF8574.h"
#include "RP6I2CmasterTWI.h"
char lcd_tmp_buffer[17];
// *****************************************************************************
// Low Level Functions:
// Aus dem asuro-Buch Band 2
// *****************************************************************************
// Sendet ein Halbbyte (4 Bit) mit Enableflanke an das Display.
void setLCDD(uint8_t lcdd)
{
I2CTWI_transmitByte(pcf8574_address_LCD, lcdd); //Daten anlegen
I2CTWI_transmitByte(pcf8574_address_LCD, (lcdd|(1<<E_bit))); //Enable_bit setzen
delayCycles(500);
I2CTWI_transmitByte(pcf8574_address_LCD, lcdd); //Enable_bit löschen
delayCycles(500);
}
// Sendet Steuerbyte, getrennt in zwei Halbbytes
void writeLCDCommand(unsigned char byte)
{
setLCDD(byte&0xF0); // sende oberes Halbbyte
setLCDD(byte<<4); // sende unteres Halbbyte
}
// Sendet Datenbyte, getrennt in zwei Halbbytes
void write4BitLCDData(unsigned char byte)
{
setLCDD((byte&0xF0)|(1<<RS_bit)); // sende oberes Halbbyte, dabei RS_bit gesetzt
setLCDD((byte<<4) |(1<<RS_bit)); // sende unteres Halbbyte, dabei RS_bit gesetzt
}
// Initialize the LCD. Always call this before using the LCD!
void initLCD(void)
{
//delayCycles(34000); No need for Power ON delay as usually the
// Bootloader should have been executed before...
setLCDD(0b0011);
delayCycles(18000);
setLCDD(0b0011);
delayCycles(5500);
setLCDD(0b0011);
delayCycles(5500);
setLCDD(0b0010);
delayCycles(5500);
writeLCDCommand(0b00101000);
delayCycles(5500);
writeLCDCommand(0b00001000);
delayCycles(5500);
writeLCDCommand(0b00000001);
delayCycles(5500);
writeLCDCommand(0b00000010);
delayCycles(5500);
writeLCDCommand(0b00001100);
delayCycles(5500);
}
// *****************************************************************************
// Diese m32-Funktionen wurden durch die entsprechenden Funktionen
// aus der asuro LCD-Library ersetzt:
/*
void setLCDD(uint8_t lcdd)
{
externalPort.LCDD = lcdd;
outputExt();
PORTB |= LCD_EN;
delayCycles(50);
PORTB &= ~LCD_EN;
}
void write4BitLCDData(uint8_t data)
{
setLCDD(data >> 4);
setLCDD(data);
delayCycles(150);
}
void writeLCDCommand(uint8_t cmd)
{
PORTB &= ~LCD_RS;
write4BitLCDData(cmd);
delayCycles(150);
}
*******************************************************************************/
// Clears the whole LCD!
void clearLCD(void)
{
writeLCDCommand(0b00000001);
delayCycles(5500);
}
/**
* Write a single character to the LCD.
*
* Example:
*
* writeCharLCD('R');
* writeCharLCD('P');
* writeCharLCD('6');
* writeCharLCD(' ');
* writeCharLCD('0');
* writeCharLCD(48); // 48 is ASCII code for '0'
* writeCharLCD(49); // '1'
* writeCharLCD(50); // '2'
* writeCharLCD(51); // '3'
* //...
*
* would output:
* RP6 00123
* at the current cursor position!
* use setCursorPos function to move the cursor to a
* different location!
*/
void writeCharLCD(uint8_t ch)
{
write4BitLCDData(ch);
delayCycles(50); // ??? 8.11.2010 mic
}
/**
* Writes a null terminated string from flash program memory to the LCD.
* You can use the macro writeStringLCD_P(STRING); instead, this macro
* ensures that the String is stored in program memory only!
*
* Example:
*
* writeNStringLCD_P(PSTR("RP6 Control"));
*
* // There is also a Macro that makes life easier and
* // you can simply write:
* writeStringLCD_P("RP6 Control");
*
*/
void writeNStringLCD_P(const char *pstring)
{
uint8_t c;
for (;(c = pgm_read_byte_near(pstring++));writeCharLCD(c));
}
/**
* Writes a String from SRAM to the LCD.
*/
void writeStringLCD(char *string)
{
while(*string)
writeCharLCD(*string++);
}
/**
* Writes a string with specified length and offset from SRAM to the LCD.
* If it is a null terminated string, output will be stopped at the
* end. It does not need to be null terminated, but it is recommended
* to use only null terminated strings/buffers, otherwise the function could
* output any SRAM memory data stored after the string until it reaches a 0
* or the specified length!
*
* Example:
*
* writeStringLength("RP6 Robot Sytem",16,0);
* // would output: "RP6 Robot Sytem\n"
* writeStringLength("RP6 Robot Sytem",11,4);
* // would output: "Robot System"
* writeStringLength("RP6 Robot Sytem",40,4);
* // would output: "Robot System"
* // No matter if the specified length is 40 characters!
*
*/
void writeStringLengthLCD(char *string, uint8_t length, uint8_t offset)
{
for(string = &string[offset]; *string && length; length--)
writeCharLCD(*string++);
}
/**
* Write a number (with specified base) to the LCD.
*
* Example:
*
* // Write a hexadecimal number to the LCD:
* writeInteger(0xAACC,16);
* // Instead of 16 you can also write "HEX" as this is defined in the
* // RP6RobotBaseLib.h :
* writeInteger(0xAACC, HEX);
* // Other Formats:
* writeInteger(1024,DEC); // Decimal
* writeInteger(511,OCT); // Ocal
* writeInteger(0b11010111,BIN); // Binary
*/
void writeIntegerLCD(int16_t number, uint8_t base)
{
itoa(number, &lcd_tmp_buffer[0], base);
writeStringLCD(&lcd_tmp_buffer[0]);
}
/**
* Same as writeInteger, but with defined length.
* This means this routine will add leading zeros to the number if length is
* larger than the actual value or cut the upper digits if length is smaller
* than the actual value.
*
* Example:
*
* // Write a hexadecimal number to the LCD:
* writeIntegerLength(0xAACC, 16, 8);
* // Instead of 16 you can also write "HEX" as this is defined in the
* // RP6ControlLib.h :
* writeIntegerLength(0xAACC, HEX, 8);
* // Other Formats:
* writeIntegerLength(1024,DEC,6); // Decimal
* writeIntegerLength(511,OCT,4); // Ocal
* writeIntegerLength(0b11010111,BIN,8); // Binary
*/
void writeIntegerLengthLCD(int16_t number, uint8_t base, uint8_t length)
{
char buffer[17];
itoa(number, &buffer[0], base);
int8_t cnt = length - strlen(buffer);
if(cnt > 0) {
for(; cnt > 0; cnt--, writeCharLCD('0'));
writeStringLCD(&buffer[0]);
}
else
writeStringLengthLCD(&buffer[0],length,-cnt);
}
/**
* This function is useful for displaying text screens on the LCD.
* It clears the whole LCD and writes the two Strings to line 1 and
* line 2.
*/
void _showScreenLCD_P(const char *line1, const char *line2)
{
clearLCD();
writeNStringLCD_P(line1);
setCursorPosLCD(1, 0);
writeNStringLCD_P(line2);
}
/**
* Sets the cursor position on LCD.
*/
void setCursorPosLCD(uint8_t line, uint8_t pos)
{
pos |= 128;
if(line==1) pos += 0x40;
writeLCDCommand(pos);
}
/**
* Clears some characters after the given position.
*/
void clearPosLCD(uint8_t line, uint8_t pos, uint8_t length)
{
setCursorPosLCD(line,pos);
while(length--)
writeCharLCD(' ');
}
/*****************************************************************************/
// Keypad:
uint16_t PollSwitch16(void)
{
uint8_t i, j, x, temp;
uint16_t mask=0;
temp=I2CTWI_readByte(0b01110001); // aktuelles Bitmuster sichern
for(j=0; j<4; j++)
{
I2CTWI_transmitByte(0b01110000, ~(1<<j)); // Pin j (0 bis 3) auf Low
sleep(2); // Warten auf Pegel
x=(I2CTWI_readByte(0b01110001)>>4); // Eingang 7-4 wird zu Bit 3-0 in x
for(i=0; i<4; i++) // Bits 0-3 überprüfen
if(!(x & (1<<i))) // Wenn Eingang low ist wurde die Taste gedrückt
mask += (1 << (i*4)) * (1<<j); // entsprechendes Bit in Maske setzen
}
I2CTWI_transmitByte(0b01110000, temp); // gespeichertes Bitmuster ausgeben
return(mask);
}
/**
* Checks which key is pressed - returns the key number,
* or 0, if no key is pressed.
*
*/
uint8_t getPressedKeyNumber(void)
{
int i, mask = PollSwitch16();
for(i=0; i<16; i++) // wurde eine Taste gedrückt?
{
if(mask & (1<<i)) // Taste i gedrückt?
{
if(mask & ~(1<<i))
return(0); // mehrere Tasten gleichzeitig gedrückt!
else
return(i+1); // Tastennummer zurückgeben
}
}
return(0); // keine Taste gedrückt
}
uint8_t getKeyNumber(uint16_t mask)
{
int i;
for(i=0; i<16; i++) // wurde eine Taste gedrückt?
{
if(mask & (1<<i)) // Taste i gedrückt?
{
if(mask & ~(1<<i))
return(0); // mehrere Tasten gleichzeitig gedrückt!
else
return(i+1); // Tastennummer zurückgeben
}
}
return(0); // keine Taste gedrückt
}
/**
* This function has to be called frequently out of
* the main loop and checks if a button is pressed! It only returns
* the key number a single time, DIRECTLY when the button is pressed.
*
* This is useful for non-blocking keyboard check in the
* main loop. You don't need something like
* "while(getPressedKeyNumber());" to wait for the button
* to be released again!
*/
uint8_t checkPressedKeyEvent(void)
{
static uint8_t pressed_key = 0;
if(pressed_key) {
if(!getPressedKeyNumber())
pressed_key = 0;
}
else {
pressed_key = getPressedKeyNumber();
if(pressed_key)
return pressed_key;
}
return 0;
}
/**
* This function has to be called frequently out of
* the main loop and checks if a button is pressed AND
* released. It only returns the key number a single time,
* AFTER the button has been released.
*
* This is useful for non-blocking keyboard check in the
* main loop. You don't need something like
* "while(getPressedKeyNumber());" to wait for the button
* to be released again!
*/
uint8_t checkReleasedKeyEvent(void)
{
static uint8_t released_key = 0;
if(released_key) {
if(!getPressedKeyNumber()) {
uint8_t tmp = released_key;
released_key = 0;
return tmp;
}
}
else
released_key = getPressedKeyNumber();
return 0;
}
Diese Lib ist verträglich mit der Lib der Base und stellt zusätzlich die LCD- und Tastenfunktionen des m32 über I2C zur Verfügung. Die Videos oben wurde mit diesem Democode erzeugt:
Code:
// Base mit PCF8574 für 4x4-Tastenmatrix und LCD 8.11.2010 mic
// Aus den Libraries der Base, des m32 und der LCD-Library des asuro aus Band 2
// des asuro-Buches habe ich eine Tasten- und LCD-Library für die Base mit I2C
// und PCF8574 zusammengebastelt.
// Die auf das parallele m32-Keypad und das LCD zugreifenden Funktionen habe ich
// durch die entsprechenden PCF8574-Funktionen der asuro-Library ersetzt.
// PCF8574 allgemein:
// https://www.roboternetz.de/phpBB2/ze...ag.php?t=56693
// http://www.asurowiki.de/pmwiki/pmwik...LCDErweiterung
// PCF8574 mit 4x4-Tastenmatrix:
// https://www.roboternetz.de/phpBB2/vi...=525175#525175
// PCF8574 mit LCD:
// http://www.rn-wissen.de/index.php/LC...BCber_I.C2.B2C
// https://www.roboternetz.de/phpBB2/viewtopic.php?t=22643
// I2C-Bibliothek von Peter Fleury (die hier nicht verwendet wird!):
// http://jump.to/fleury
// Tasten und LCD mit Bascom:
// https://www.roboternetz.de/phpBB2/viewtopic.php?t=11188
// Mein RP6 mit LCD an den LEDs:
// https://www.roboternetz.de/phpBB2/ze...ag.php?t=41805
#include "RP6RobotBaseLib.h" // Library der Base
#include "RP6Config.h" // Konfiguration der Base
#include "RP6I2CmasterTWI.h" // I2C-Funktionen der Base
#include "RP6Base_I2C_PCF8574.h" // meine neue PCF8574-Library
uint8_t c=0, leds=0;
uint16_t tasten;
int main(void)
{
initRobotBase();
I2CTWI_initMaster(100);
setLEDs(63);
initLCD();
showScreenLCD("################", "################");
mSleep(1500);
showScreenLCD("<<RP6 mit I2C> >", "<<LC - DISPLAY>>");
mSleep(2500);
showScreenLCD("Hello World", "Example Program");
mSleep(2500);
clearLCD(); // Clear the whole LCD Screen
mSleep(1500);
showScreenLCD("Bitte Taste", "druecken");
startStopwatch1();
while(1)
{
if(getStopwatch1() > 50) // Tastenabfrage
{
setStopwatch1(0);
tasten=PollSwitch16();
if(tasten)
{
writeInteger(tasten, 2); // Bitmuster des Keypads an Terminal ausgeben
writeChar('\n');
setCursorPosLCD(0,0);
writeIntegerLengthLCD(tasten, 2, 16);
setCursorPosLCD(1,0);
writeStringLCD_P("Taste Nr.: ");
writeIntegerLCD(getKeyNumber(tasten), 10);
writeCharLCD(' ');
leds=0; // Spielerei mit den Leds
if(tasten & (1<<0)) leds+=8; // Taste 1
if(tasten & (1<<1)) leds+=1;
if(tasten & (1<<2)) leds+=16;
if(tasten & (1<<3)) leds+=2;
if(tasten & (1<<4)) leds+=32;
if(tasten & (1<<5)) leds+=4; // Taste 6
setLEDs(leds);
}
else setLEDs(0); // Keine Taste gedrückt
}
}
return(0);
}
Das sieht zwar schon mal ganz gut aus, hat aber auch noch ein paar Macken. Das LCD wird nur bei jedem 3. bis 5. Versuch richtig initialisiert. Dieses Problem hatte ich auch, wenn das LCD direkt parallel angeschlossen ist. Ich vermute, dieser Effekt hängt mit der 4Bit-Ansteuerung zusammen.
Nach vielen Versuchen habe ich nun erkannt, dass die PullUps auf der Base wohl zu groß sind. Seit ich zusätzliche 1k-PullUps verwende klappt die Kommunikation relativ gut.
Gruß
mic
Lesezeichen