我和我的伙伴目前正在为 PIC16F877A 微控制器和 LCD1602 I2C 屏幕构建接口代码集
我们可以完美地加载所有内容,但屏幕上没有显示任何内容。我们仔细检查了 PIN 映射并连接了屏幕 I2C 模块上的正确端口。代码编译没有问题并运行到微控制器中,但没有显示。
/* File: config.h */
// [ PIC16F877A ] Configuration Bit Settings
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
// CONFIG
#pragma config FOSC = HS
#pragma config WDTE = OFF
#pragma config PWRTE = OFF
#pragma config BOREN = OFF
#pragma config LVP = ON
#pragma config CPD = OFF
#pragma config WRT = OFF
#pragma config CP = OFF
#include <xc.h>
/* File: I2C_LCD.h */
#define _XTAL_FREQ 16000000
#define I2C_BaudRate 100000
#define SCL_D TRISC3
#define SDA_D TRISC4
#define LCD_BACKLIGHT 0x08
#define LCD_NOBACKLIGHT 0x00
#define LCD_FIRST_ROW 0x80
#define LCD_SECOND_ROW 0xC0
#define LCD_THIRD_ROW 0x94
#define LCD_FOURTH_ROW 0xD4
#define LCD_CLEAR 0x01
#define LCD_RETURN_HOME 0x02
#define LCD_ENTRY_MODE_SET 0x06
#define LCD_CURSOR_OFF 0x0C
#define LCD_UNDERLINE_ON 0x0E
#define LCD_BLINK_CURSOR_ON 0x0F
#define LCD_MOVE_CURSOR_LEFT 0x10
#define LCD_MOVE_CURSOR_RIGHT 0x14
#define LCD_TURN_ON 0x0C
#define LCD_TURN_OFF 0x08
#define LCD_SHIFT_LEFT 0x18
#define LCD_SHIFT_RIGHT 0x1C
#define LCD_TYPE 2 // 0 -> 5x7 | 1 -> 5x10 | 2 -> 2 lines
//-----------[ Functions' Prototypes ]--------------
//---[ I2C Routines ]---
void I2C_Master_Init();
void I2C_Master_Wait();
void I2C_Master_Start();
void I2C_Master_RepeatedStart();
void I2C_Master_Stop();
void I2C_ACK();
void I2C_NACK();
unsigned char I2C_Master_Write(unsigned char data);
unsigned char I2C_Read_Byte(void);
//---[ LCD Routines ]---
void LCD_Init(unsigned char I2C_Add);
void IO_Expander_Write(unsigned char Data);
void LCD_Write_4Bit(unsigned char Nibble);
void LCD_CMD(unsigned char CMD);
void LCD_Set_Cursor(unsigned char ROW, unsigned char COL);
void LCD_Write_Char(char);
void LCD_Write_String(char*);
void Backlight();
void noBacklight();
void LCD_SR();
void LCD_SL();
void LCD_Clear();
/* File: I2C_LCD.c */
#include <xc.h>
#include "I2C_LCD.h"
unsigned char RS, i2c_add, BackLight_State = LCD_BACKLIGHT;
//---------------[ I2C Routines ]-------------------
//--------------------------------------------------
void I2C_Master_Init()
{
SSPCON = 0x28;
SSPCON2 = 0x00;
SSPSTAT = 0x00;
SSPADD = ((_XTAL_FREQ/4)/I2C_BaudRate) - 1;
SCL_D = 1;
SDA_D = 1;
}
void I2C_Master_Wait()
{
while ((SSPSTAT & 0x04) || (SSPCON2 & 0x1F));
}
void I2C_Master_Start()
{
I2C_Master_Wait();
SEN = 1;
}
void I2C_Master_RepeatedStart()
{
I2C_Master_Wait();
RSEN = 1;
}
void I2C_Master_Stop()
{
I2C_Master_Wait();
PEN = 1;
}
void I2C_ACK(void)
{
ACKDT = 0; // 0 -> ACK
I2C_Master_Wait();
ACKEN = 1; // Send ACK
}
void I2C_NACK(void)
{
ACKDT = 1; // 1 -> NACK
I2C_Master_Wait();
ACKEN = 1; // Send NACK
}
unsigned char I2C_Master_Write(unsigned char data)
{
I2C_Master_Wait();
SSPBUF = data;
while(!SSPIF); // Wait Until Completion
SSPIF = 0;
return ACKSTAT;
}
unsigned char I2C_Read_Byte(void)
{
//---[ Receive & Return A Byte ]---
I2C_Master_Wait();
RCEN = 1; // Enable & Start Reception
while(!SSPIF); // Wait Until Completion
SSPIF = 0; // Clear The Interrupt Flag Bit
I2C_Master_Wait();
return SSPBUF; // Return The Received Byte
}
//======================================================
//---------------[ LCD Routines ]----------------
//-----------------------------------------------
void LCD_Init(unsigned char I2C_Add)
{
i2c_add = I2C_Add;
IO_Expander_Write(0x00);
__delay_ms(30);
LCD_CMD(0x03);
__delay_ms(5);
LCD_CMD(0x03);
__delay_ms(5);
LCD_CMD(0x03);
__delay_ms(5);
LCD_CMD(LCD_RETURN_HOME);
__delay_ms(5);
LCD_CMD(0x20 | (LCD_TYPE << 2));
__delay_ms(50);
LCD_CMD(LCD_TURN_ON);
__delay_ms(50);
LCD_CMD(LCD_CLEAR);
__delay_ms(50);
LCD_CMD(LCD_ENTRY_MODE_SET | LCD_RETURN_HOME);
__delay_ms(50);
}
void IO_Expander_Write(unsigned char Data)
{
I2C_Master_Start();
I2C_Master_Write(i2c_add);
I2C_Master_Write(Data | BackLight_State);
I2C_Master_Stop();
}
void LCD_Write_4Bit(unsigned char Nibble)
{
// Get The RS Value To LSB OF Data
Nibble |= RS;
IO_Expander_Write(Nibble | 0x04);
IO_Expander_Write(Nibble & 0xFB);
__delay_us(50);
}
void LCD_CMD(unsigned char CMD)
{
RS = 0; // Command Register Select
LCD_Write_4Bit(CMD & 0xF0);
LCD_Write_4Bit((CMD << 4) & 0xF0);
}
void LCD_Write_Char(char Data)
{
RS = 1; // Data Register Select
LCD_Write_4Bit(Data & 0xF0);
LCD_Write_4Bit((Data << 4) & 0xF0);
}
void LCD_Write_String(char* Str)
{
for(int i=0; Str[i]!='\0'; i++)
LCD_Write_Char(Str[i]);
}
void LCD_Set_Cursor(unsigned char ROW, unsigned char COL)
{
switch(ROW)
{
case 2:
LCD_CMD(0xC0 + COL-1);
break;
case 3:
LCD_CMD(0x94 + COL-1);
break;
case 4:
LCD_CMD(0xD4 + COL-1);
break;
// Case 1
default:
LCD_CMD(0x80 + COL-1);
}
}
void Backlight()
{
BackLight_State = LCD_BACKLIGHT;
IO_Expander_Write(0);
}
void noBacklight()
{
BackLight_State = LCD_NOBACKLIGHT;
IO_Expander_Write(0);
}
void LCD_SL()
{
LCD_CMD(0x18);
__delay_us(40);
}
void LCD_SR()
{
LCD_CMD(0x1C);
__delay_us(40);
}
void LCD_Clear()
{
LCD_CMD(0x01);
__delay_us(40);
}
/* File: main.c */
#include <xc.h>
#include "config.h"
#include "I2C_LCD.h"
void main(void) {
I2C_Master_Init();
LCD_Init(0x27); // Initialize LCD module with I2C address = 0x4E
LCD_Set_Cursor(1, 1);
LCD_Write_String(" Khaled Magdy");
LCD_Set_Cursor(2, 1);
LCD_Write_String(" DeepBlue");
while(1)
{
LCD_SR();
__delay_ms(350);
LCD_SR();
__delay_ms(350);
LCD_SL();
__delay_ms(350);
LCD_SL();
__delay_ms(350);
}
return;
}
希望能从不同的角度关注此事,因为我们不知疲倦地工作,并且不确定为什么没有沟通。
与 PIC 微控制器相比,字符 LCD 模块是非常慢的设备。因此,他们处理命令和数据所需的时间可能会根据具体产品而有所不同。
这些模块应通过尊重其处理时间来正确设置,以使它们正常运行。这就是为什么
init
程序对于这些设备非常重要,应该小心处理。
作为参考,这里是取自工作项目的示例 init 函数代码片段。
// Init the LCD module for 4-bit mode
void lcd4bitInit(void)
{
__delay_ms(100); // Character LCD modules are relatively slow, the required time may vary depending on the product
// So give it sufficient time especially when setting up
lcdPort = 0x30; // Send function set command 3 times with 10ms intervals
lcdRW_low(); // Set LCD RW low for write mode
lcdRS_low(); // Set LCD RS low for command mode
lcdEN_high(); // Set EN low so that it latch the data in, should keep high at least 140ns
lcdEN_low(); // Set EN low again
__delay_ms(10); // Let it process the data
lcdEN_high(); // Latch the function set command for 2nd time (still 8-bit mode).
lcdEN_low();
__delay_ms(10); // Let it process the data
lcdEN_high(); // Latch the function set command for 3d time (still 8-bit mode).
lcdEN_low();
__delay_ms(10); // Let it process the data
lcdPort = 0x28; // Set DL 0: 4-bits; N 1: 2 lines; F 0: 5x7 dots
lcdEN_high();
lcdEN_low();
__delay_ms(10);
/* From now on, the LCD module should function in 4-bit mode. So we can write it in 4-bit mode as of now */
lcdCmd_write(0x08); // Here goes the rest of the setup which can be sent in 4-bit mode
lcd_clear();
lcdCmd_write(0x06);
lcdCmd_write(0x0C);
}
代码片段中的引脚设置函数是类似函数的宏。例如
lcdEN_high()
和 `lcdEN_low 的定义分别如下:
#define lcdEN_high() do {lcdENPort |= 1 << lcdEN; } while(0)
#define lcdEN_low() do {lcdENPort &= ~(1 << lcdEN); } while(0)
其中
lcdENPort
映射到我控制EN
引脚的引脚端口,lcdEN
映射到连接LCD的EN
的位数。
正确设置的另一个重要因素是尊重模块的处理时间。正如我之前所说,这个时间在不同的产品或制造商之间可能会略有不同。但我认为保持这些时间比所需时间多一点没有问题,因为
init
函数通常在每个系统初始化时执行一次。