比较诧异的是开发板上配有一个LCD显示屏,但在例程中却没有它的身影,似乎是在考验使用者的等级水平一般,等级不够似乎就没有使用的权力。
多亏知晓了该显示屏的驱动芯片是ST7789v3,以及它是一款1.47寸、分配率为172X320的屏。
才找到相近的显示屏例程以便进行参考移植,本着GPIO口横行一切的原则,决定以GPIO口来模拟SPI方式驱动这个显示屏。图1是它的接口电路,图2是其引脚占用情况。
图1 接口电路
图 使用引脚
为此,对所用引脚的配置为:
#define OLED_INIT() do{\
FL_GPIO_InitTypeDef GPIO_InitStruct;\
GPIO_InitStruct.pin = FL_GPIO_PIN_0|FL_GPIO_PIN_1|FL_GPIO_PIN_2;\
GPIO_InitStruct.mode = FL_GPIO_MODE_OUTPUT;\
GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_PUSHPULL;\
GPIO_InitStruct.pull = FL_GPIO_BOTH_DISABLE;\
GPIO_InitStruct.remapPin = FL_GPIO_PINREMAP_FUNCTON1;\
GPIO_InitStruct.analogSwitch = FL_DISABLE;\
GPIO_InitStruct.driveStrength = FL_GPIO_DRIVESTRENGTH_X3;\
(void)FL_GPIO_Init(GPIOE, &GPIO_InitStruct );\
GPIO_InitStruct.pin = FL_GPIO_PIN_9|FL_GPIO_PIN_14|FL_GPIO_PIN_15;\
GPIO_InitStruct.mode = FL_GPIO_MODE_OUTPUT;\
GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_PUSHPULL;\
GPIO_InitStruct.pull = FL_GPIO_BOTH_DISABLE;\
GPIO_InitStruct.remapPin = FL_GPIO_PINREMAP_FUNCTON1;\
GPIO_InitStruct.analogSwitch = FL_DISABLE;\
GPIO_InitStruct.driveStrength = FL_GPIO_DRIVESTRENGTH_X3;\
(void)FL_GPIO_Init(GPIOB, &GPIO_InitStruct );\
}while(0)
对输出高低电平的语句定义为:
#define OLED_SCLK_Set do{FL_GPIO_SetOutputPin(GPIOE, FL_GPIO_PIN_0);}while(0)
#define OLED_SCLK_Clr do{FL_GPIO_ResetOutputPin(GPIOE, FL_GPIO_PIN_0);}while(0)
#define OLED_SDIN_Set do{FL_GPIO_SetOutputPin(GPIOB, FL_GPIO_PIN_14);}while(0)
#define OLED_SDIN_Clr do{FL_GPIO_ResetOutputPin(GPIOB, FL_GPIO_PIN_14);}while(0)
#define OLED_RST_Set do{FL_GPIO_SetOutputPin(GPIOB, FL_GPIO_PIN_15);}while(0)
#define OLED_RST_Clr do{FL_GPIO_ResetOutputPin(GPIOB, FL_GPIO_PIN_15);}while(0)
#define OLED_DC_Set do{FL_GPIO_SetOutputPin(GPIOE, FL_GPIO_PIN_2);}while(0)
#define OLED_DC_Clr do{FL_GPIO_ResetOutputPin(GPIOE, FL_GPIO_PIN_2);}while(0)
#define OLED_CS_Set do{FL_GPIO_SetOutputPin(GPIOE, FL_GPIO_PIN_1);}while(0)
#define OLED_CS_Clr do{FL_GPIO_ResetOutputPin(GPIOE, FL_GPIO_PIN_1);}while(0)
#define OLED_BLK_Set do{FL_GPIO_SetOutputPin(GPIOB, FL_GPIO_PIN_9);}while(0)
#define OLED_BLK_Clr do{FL_GPIO_ResetOutputPin(GPIOB, FL_GPIO_PIN_9);}while(0)
模拟SPI发送字节数据的函数为:
void LCD_Writ_Bus(u8 dat)
{
u8 i;
OLED_CS_Clr;
for(i=0;i<8;i++)
{
OLED_SCLK_Clr;
if(dat&0x80)
{
OLED_SDIN_Set;
}
else
{
OLED_SDIN_Clr;
}
OLED_SCLK_Set;
dat<<=1;
}
OLED_CS_Set;
}
考虑到屏幕可能会朝不同的方向使用, LCD_Address_Set()被设计很万能,其内容如下:
void LCD_Address_Set(u16 x1,u16 y1,u16 x2,u16 y2)
{
if(USE_HORIZONTAL==0)
{
LCD_WR_REG(0x2a);
LCD_WR_DATA(x1+34);
LCD_WR_DATA(x2+34);
LCD_WR_REG(0x2b);
LCD_WR_DATA(y1);
LCD_WR_DATA(y2);
LCD_WR_REG(0x2c);
}
else if(USE_HORIZONTAL==1)
{
LCD_WR_REG(0x2a);
LCD_WR_DATA(x1+34);
LCD_WR_DATA(x2+34);
LCD_WR_REG(0x2b);
LCD_WR_DATA(y1);
LCD_WR_DATA(y2);
LCD_WR_REG(0x2c);
}
else if(USE_HORIZONTAL==2)
{
LCD_WR_REG(0x2a);
LCD_WR_DATA(x1);
LCD_WR_DATA(x2);
LCD_WR_REG(0x2b);
LCD_WR_DATA(y1+34);
LCD_WR_DATA(y2+34);
LCD_WR_REG(0x2c);
}
else
{
LCD_WR_REG(0x2a);
LCD_WR_DATA(x1);
LCD_WR_DATA(x2);
LCD_WR_REG(0x2b);
LCD_WR_DATA(y1+34);
LCD_WR_DATA(y2+34);
LCD_WR_REG(0x2c);
}
}
与之相应的屏幕初始化函数也是具有这个特点的,其内容为:
void Lcd_Init(void)
{
OLED_INIT();
OLED_RST_Clr;
FL_DelayMs(30);
OLED_RST_Set;
FL_DelayMs(100);
OLED_BLK_Set;
FL_DelayMs(100);
LCD_WR_REG(0x11);
LCD_WR_REG(0x36);
if(USE_HORIZONTAL==0) LCD_WR_DATA8(0x00);
else if(USE_HORIZONTAL==1) LCD_WR_DATA8(0xC0);
else if(USE_HORIZONTAL==2) LCD_WR_DATA8(0x70);
else LCD_WR_DATA8(0xA0);
LCD_WR_REG(0x3A);
LCD_WR_DATA8(0x05);
LCD_WR_REG(0xB2);
LCD_WR_DATA8(0x0C);
LCD_WR_DATA8(0x0C);
LCD_WR_DATA8(0x00);
LCD_WR_DATA8(0x33);
LCD_WR_DATA8(0x33);
LCD_WR_REG(0xB7);
LCD_WR_DATA8(0x35);
LCD_WR_REG(0xBB);
LCD_WR_DATA8(0x35);
LCD_WR_REG(0xC0);
LCD_WR_DATA8(0x2C);
LCD_WR_REG(0xC2);
LCD_WR_DATA8(0x01);
LCD_WR_REG(0xC3);
LCD_WR_DATA8(0x13);
LCD_WR_REG(0xC4);
LCD_WR_DATA8(0x20);
LCD_WR_REG(0xC6);
LCD_WR_DATA8(0x0F);
LCD_WR_REG(0xD0);
LCD_WR_DATA8(0xA4);
LCD_WR_DATA8(0xA1);
LCD_WR_REG(0xD6);
LCD_WR_DATA8(0xA1);
LCD_WR_REG(0xE0);
LCD_WR_DATA8(0xF0);
LCD_WR_DATA8(0x00);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x04);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x29);
LCD_WR_DATA8(0x33);
LCD_WR_DATA8(0x3E);
LCD_WR_DATA8(0x38);
LCD_WR_DATA8(0x12);
LCD_WR_DATA8(0x12);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x30);
LCD_WR_REG(0xE1);
LCD_WR_DATA8(0xF0);
LCD_WR_DATA8(0x07);
LCD_WR_DATA8(0x0A);
LCD_WR_DATA8(0x0D);
LCD_WR_DATA8(0x0B);
LCD_WR_DATA8(0x07);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x33);
LCD_WR_DATA8(0x3E);
LCD_WR_DATA8(0x36);
LCD_WR_DATA8(0x14);
LCD_WR_DATA8(0x14);
LCD_WR_DATA8(0x29);
LCD_WR_DATA8(0x32);
LCD_WR_REG(0x21);
LCD_WR_REG(0x11);
FL_DelayMs(120);
LCD_WR_REG(0x29);
}
实现指定色彩清除屏幕的函数为:
void LCD_Clear(u16 Color)
{
u16 i,j;
LCD_Address_Set(0,0,LCD_W-1,LCD_H-1);
for(i=0;i<LCD_W;i++)
{
for (j=0;j<LCD_H;j++)
{
LCD_WR_DATA(Color);
}
}
}
实现字符显示的函数为:
void LCD_ShowChar(u16 x,u16 y,u8 num,u8 mode,u16 color)
{
u8 temp;
u8 pos,t;
u16 x0=x;
if(x>LCD_W-16||y>LCD_H-16) return;
num=num-' ';
LCD_Address_Set(x,y,x+8-1,y+16-1);
if(!mode)
{
for(pos=0;pos<16;pos++)
{
temp=asc2_1608[(u16)num*16+pos];
for(t=0;t<8;t++)
{
if(temp&0x01)LCD_WR_DATA(color);
else LCD_WR_DATA(BLACK);
temp>>=1;
x++;
}
x=x0;
y++;
}
}
else
{
for(pos=0;pos<16;pos++)
{
temp=asc2_1608[(u16)num*16+pos];
for(t=0;t<8;t++)
{
if(temp&0x01)LCD_DrawPoint(x+t,y+pos,color);
temp>>=1;
}
}
}
}
相应的字符串显示函数为:
void LCD_ShowString(u16 x,u16 y,const u8 *p,u16 color)
{
while(*p!='\0')
{
LCD_ShowChar(x,y,*p,1,color);
x+=8;
p++;
}
}
实现小图标显示的函数为:
void show_tbh(unsigned int x,unsigned int y)
{
unsigned int i,j,k;
unsigned int da;
k=0;
for(i=0;i<50;i++)
{
for(j=0;j<50;j++)
{
da=gImage_8[k*2];
da<<=8;
da|=gImage_8[k*2+1];
LCD_DrawPoint(x+j,y+i,da);
k++;
}
}
}
在以上函数的支持下,测试显示功能的主程序为:
int main(void)
{
Lcd_Init();
LCD_Clear(RED);
LCD_ShowString(20,20,"FM33FT0A",YELLOW);
LCD_ShowString(20,40,"LCD Test",YELLOW);
LCD_ShowString(20,150,"jinglixixi",WHITE);
show_tbh(20,70);
while(1);
}
经程序的编译和下载,其显示效果如图3和图4所示,说明显示驱动成功。
图3 整体效果
图4 显示效果