前言
显示屏作为人机交互的一个重要窗口,在各类电子产品中被广泛地应用。在同类产品中,一个好的人机界面设计往往能更加吸引用户的目光。而显示器作为其载体,同样也是不可或缺的存在。作为一名嵌入式软件工程师,屏驱和人机界面的开发在我们的工作中是经常会遇到的,掌握此项技能也是必不可少的。本文主要基于HC32F460,简单介绍 spi屏驱的开发。
一、硬件介绍
这里我使用的屏是之前在某宝上淘来的带SPI接口通讯的TFT彩屏,下面是该屏的部分资料说明 接口定义
二、驱动代码实现
1.IO模拟SPI驱动
关于驱动部分,卖家也给了一些不同平台下的驱动例程,这些例程都是通过IO口模拟SPI的时序来驱动lcd屏的。移植到HC平台,很快就顺利点亮lcd屏。
代码如下:
#define SPI_SCK_PORT (PortD)
#define SPI_SCK_PIN (Pin00)
#define SPI_MOSI_PORT (PortD)
#define SPI_MOSI_PIN (Pin01)
#define RES_PORT (PortE)
#define RES_PIN (Pin09)
#define DC_PORT (PortE)
#define DC_PIN (Pin11)
#define BLK_PORT (PortE)
#define BLK_PIN (Pin13)
#define LCD_SCLK_Clr() M4_PORT->PORRD |= SPI_SCK_PIN
#define LCD_SCLK_Set() M4_PORT->POSRD |= SPI_SCK_PIN
#define LCD_MOSI_Clr() M4_PORT->PORRD |= SPI_MOSI_PIN
#define LCD_MOSI_Set() M4_PORT->POSRD |= SPI_MOSI_PIN
#define LCD_RES_Clr() M4_PORT->PORRE |= RES_PIN
#define LCD_RES_Set() M4_PORT->POSRE |= RES_PIN
#define LCD_DC_Clr() M4_PORT->PORRE |= DC_PIN
#define LCD_DC_Set() M4_PORT->POSRE |= DC_PIN
#define LCD_BLK_Clr() PORT_ResetBits(BLK_PORT, BLK_PIN)
#define LCD_BLK_Set() PORT_SetBits(BLK_PORT, BLK_PIN)
void LCD_GPIO_Init(void)
{
stc_port_init_t stcPortInit;
MEM_ZERO_STRUCT(stcPortInit);
stcPortInit.enPinMode = Pin_Mode_Out;
LCD_SCLK_Clr();
PORT_Init(SPI_SCK_PORT, SPI_SCK_PIN, &stcPortInit);
LCD_MOSI_Clr();
PORT_Init(SPI_MOSI_PORT, SPI_MOSI_PIN, &stcPortInit);
LCD_RES_Clr();
PORT_Init(RES_PORT, RES_PIN, &stcPortInit);
LCD_DC_Clr();
PORT_Init(DC_PORT, DC_PIN, &stcPortInit);
LCD_BLK_Clr();
PORT_Init(BLK_PORT, BLK_PIN, &stcPortInit);
}
void LCD_Writ_Bus(uint8_t dat)
{
uint8_t counter;
for(counter=0; counter<8; counter++)
{
LCD_SCLK_Clr();
if((dat&0x80)==0)
{
LCD_MOSI_Clr();
}
else
{
LCD_MOSI_Set();
}
dat=dat<<1;
LCD_SCLK_Set();
}
LCD_SCLK_Clr();
}
void LCD_WR_DATA8(uint8_t dat)
{
LCD_DC_Set();
LCD_Writ_Bus(dat);
}
void LCD_WR_DATA(uint16_t dat)
{
LCD_WR_DATA8(dat>>8);
LCD_WR_DATA8(dat);
}
void LCD_WR_REG(uint8_t dat)
{
LCD_DC_Clr();
LCD_Writ_Bus(dat);
}
void LCD_Address_Set(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2)
{
LCD_WR_REG(0x2a);
LCD_WR_DATA(x1);
LCD_WR_DATA(x2);
LCD_WR_REG(0x2b);
LCD_WR_DATA(y1);
LCD_WR_DATA(y2);
LCD_WR_REG(0x2C);
}
void LCD_Init(void)
{
LCD_GPIO_Init();
LCD_RES_Clr();
Ddl_Delay1ms(120);
LCD_RES_Set();
Ddl_Delay1ms(120);
LCD_SCLK_Set();
LCD_WR_REG(0x11);
Ddl_Delay1ms(120);
LCD_WR_REG(0x3A);
LCD_WR_DATA8(0x05);
LCD_WR_REG(0xC5);
LCD_WR_DATA8(0x1A);
LCD_WR_REG(0x36);
LCD_WR_DATA8(0x00);
LCD_WR_REG(0xb2);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x00);
LCD_WR_DATA8(0x33);
LCD_WR_DATA8(0x33);
LCD_WR_REG(0xb7);
LCD_WR_DATA8(0x05);
LCD_WR_REG(0xBB);
LCD_WR_DATA8(0x3F);
LCD_WR_REG(0xC0);
LCD_WR_DATA8(0x2c);
LCD_WR_REG(0xC2);
LCD_WR_DATA8(0x01);
LCD_WR_REG(0xC3);
LCD_WR_DATA8(0x0F);
LCD_WR_REG(0xC4);
LCD_WR_DATA8(0x20);
LCD_WR_REG(0xC6);
LCD_WR_DATA8(0X01);
LCD_WR_REG(0xd0);
LCD_WR_DATA8(0xa4);
LCD_WR_DATA8(0xa1);
LCD_WR_REG(0xE8);
LCD_WR_DATA8(0x03);
LCD_WR_REG(0xE9);
LCD_WR_DATA8(0x09);
LCD_WR_DATA8(0x09);
LCD_WR_DATA8(0x08);
LCD_WR_REG(0xE0);
LCD_WR_DATA8(0xD0);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x09);
LCD_WR_DATA8(0x09);
LCD_WR_DATA8(0x08);
LCD_WR_DATA8(0x14);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x33);
LCD_WR_DATA8(0x3F);
LCD_WR_DATA8(0x07);
LCD_WR_DATA8(0x13);
LCD_WR_DATA8(0x14);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x30);
LCD_WR_REG(0XE1);
LCD_WR_DATA8(0xD0);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x09);
LCD_WR_DATA8(0x09);
LCD_WR_DATA8(0x08);
LCD_WR_DATA8(0x03);
LCD_WR_DATA8(0x24);
LCD_WR_DATA8(0x32);
LCD_WR_DATA8(0x32);
LCD_WR_DATA8(0x3B);
LCD_WR_DATA8(0x14);
LCD_WR_DATA8(0x13);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x2F);
LCD_WR_REG(0x21);
LCD_WR_REG(0x29);
}
void LCD_Fill(uint16_t xsta,uint16_t ysta,uint16_t xend,uint16_t yend,uint16_t color)
{
uint16_t i,j;
LCD_Address_Set(xsta,ysta,xend-1,yend-1);
for(i=ysta;i<yend;i++)
{
for(j=xsta;j<xend;j++)
{
LCD_WR_DATA(color);
}
}
}
编写代码简单地刷下屏,效果如下: 虽说能成功点亮,速度也勉强能接受,但是还是很明显能看到刷屏时“拉窗帘”的现象(CSDN博客好像没办法插入小视频,只能截图了–_–|||)。
2.硬件SPI驱动
IO模拟SPI虽然能驱动LCD屏,但是刷屏速度就很明显会偏慢点,若是后续开发想利用屏幕播放一些动图(如GIF等),显示效果看起来就会很差。HC32F460本身带有4路的SPI外设,因此我们可以直接使用MCU本身的硬件SPI来驱动LCD屏,刷屏显示效果则会好很多。 代码如下:
#define RES_PORT (PortE)
#define RES_PIN (Pin09)
#define DC_PORT (PortE)
#define DC_PIN (Pin11)
#define BLK_PORT (PortE)
#define BLK_PIN (Pin13)
#define LCD_RES_Clr() M4_PORT->PORRE |= RES_PIN
#define LCD_RES_Set() M4_PORT->POSRE |= RES_PIN
#define LCD_DC_Clr() M4_PORT->PORRE |= DC_PIN
#define LCD_DC_Set() M4_PORT->POSRE |= DC_PIN
#define LCD_BLK_Clr() PORT_ResetBits(BLK_PORT, BLK_PIN)
#define LCD_BLK_Set() PORT_SetBits(BLK_PORT, BLK_PIN)
#define SPI_SCK_PORT (PortD)
#define SPI_SCK_PIN (Pin00)
#define SPI_SCK_FUNC (Func_Spi1_Sck)
#define SPI_MOSI_PORT (PortD)
#define SPI_MOSI_PIN (Pin01)
#define SPI_MOSI_FUNC (Func_Spi1_Mosi)
#define SPI_UNIT (M4_SPI1)
#define SPI_UNIT_CLOCK (PWC_FCG1_PERIPH_SPI1)
#define SPI_MASTER_MODE
void LCD_SPI_Write(uint8_t Data)
{
SPI_SendData8(SPI_UNIT, Data);
while(Reset == SPI_GetFlag(SPI_UNIT, SpiFlagSendBufferEmpty));
}
void LCD_SPI_Init(void)
{
stc_spi_init_t stcSpiInit;
MEM_ZERO_STRUCT(stcSpiInit);
PWC_Fcg1PeriphClockCmd(SPI_UNIT_CLOCK, Enable);
PORT_SetFunc(SPI_SCK_PORT, SPI_SCK_PIN, SPI_SCK_FUNC, Disable);
PORT_SetFunc(SPI_MOSI_PORT, SPI_MOSI_PIN, SPI_MOSI_FUNC, Disable);
stcSpiInit.enClkDiv = SpiClkDiv2;
stcSpiInit.enFrameNumber = SpiFrameNumber1;
stcSpiInit.enDataLength = SpiDataLengthBit8;
stcSpiInit.enFirstBitPosition = SpiFirstBitPositionMSB;
stcSpiInit.enSckPolarity = SpiSckIdleLevelHigh;
stcSpiInit.enSckPhase = SpiSckOddChangeEvenSample;
stcSpiInit.enReadBufferObject = SpiReadReceiverBuffer;
stcSpiInit.enWorkMode = SpiWorkMode3Line;
stcSpiInit.enTransMode = SpiTransFullDuplex;
stcSpiInit.enCommAutoSuspendEn = Disable;
stcSpiInit.enModeFaultErrorDetectEn = Disable;
stcSpiInit.enParitySelfDetectEn = Disable;
stcSpiInit.enParityEn = Disable;
stcSpiInit.enParity = SpiParityEven;
stcSpiInit.enMasterSlaveMode = SpiModeMaster;
stcSpiInit.stcDelayConfig.enSsSetupDelayOption = SpiSsSetupDelayCustomValue;
stcSpiInit.stcDelayConfig.enSsSetupDelayTime = SpiSsSetupDelaySck1;
stcSpiInit.stcDelayConfig.enSsHoldDelayOption = SpiSsHoldDelayCustomValue;
stcSpiInit.stcDelayConfig.enSsHoldDelayTime = SpiSsHoldDelaySck1;
stcSpiInit.stcDelayConfig.enSsIntervalTimeOption = SpiSsIntervalCustomValue;
stcSpiInit.stcDelayConfig.enSsIntervalTime = SpiSsIntervalSck6PlusPck2;
stcSpiInit.stcSsConfig.enSsValidBit = SpiSsValidChannel0;
stcSpiInit.stcSsConfig.enSs0Polarity = SpiSsLowValid;
SPI_Init(SPI_UNIT, &stcSpiInit);
SPI_Cmd(SPI_UNIT, Enable);
}
void LCD_GPIO_Init(void)
{
stc_port_init_t stcPortInit;
MEM_ZERO_STRUCT(stcPortInit);
stcPortInit.enPinMode = Pin_Mode_Out;
LCD_RES_Clr();
PORT_Init(RES_PORT, RES_PIN, &stcPortInit);
LCD_DC_Clr();
PORT_Init(DC_PORT, DC_PIN, &stcPortInit);
LCD_BLK_Clr();
PORT_Init(BLK_PORT, BLK_PIN, &stcPortInit);
}
void LCD_Writ_Bus(uint8_t dat)
{
LCD_SPI_Write(dat);
}
void LCD_Init(void)
{
LCD_GPIO_Init();
LCD_RES_Clr();
Ddl_Delay1ms(120);
LCD_RES_Set();
Ddl_Delay1ms(120);
LCD_SPI_Init();
LCD_WR_REG(0x11);
Ddl_Delay1ms(120);
LCD_WR_REG(0x3A);
LCD_WR_DATA8(0x05);
LCD_WR_REG(0xC5);
LCD_WR_DATA8(0x1A);
LCD_WR_REG(0x36);
LCD_WR_DATA8(0x00);
LCD_WR_REG(0xb2);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x00);
LCD_WR_DATA8(0x33);
LCD_WR_DATA8(0x33);
LCD_WR_REG(0xb7);
LCD_WR_DATA8(0x05);
LCD_WR_REG(0xBB);
LCD_WR_DATA8(0x3F);
LCD_WR_REG(0xC0);
LCD_WR_DATA8(0x2c);
LCD_WR_REG(0xC2);
LCD_WR_DATA8(0x01);
LCD_WR_REG(0xC3);
LCD_WR_DATA8(0x0F);
LCD_WR_REG(0xC4);
LCD_WR_DATA8(0x20);
LCD_WR_REG(0xC6);
LCD_WR_DATA8(0X01);
LCD_WR_REG(0xd0);
LCD_WR_DATA8(0xa4);
LCD_WR_DATA8(0xa1);
LCD_WR_REG(0xE8);
LCD_WR_DATA8(0x03);
LCD_WR_REG(0xE9);
LCD_WR_DATA8(0x09);
LCD_WR_DATA8(0x09);
LCD_WR_DATA8(0x08);
LCD_WR_REG(0xE0);
LCD_WR_DATA8(0xD0);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x09);
LCD_WR_DATA8(0x09);
LCD_WR_DATA8(0x08);
LCD_WR_DATA8(0x14);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x33);
LCD_WR_DATA8(0x3F);
LCD_WR_DATA8(0x07);
LCD_WR_DATA8(0x13);
LCD_WR_DATA8(0x14);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x30);
LCD_WR_REG(0XE1);
LCD_WR_DATA8(0xD0);
LCD_WR_DATA8(0x05);
LCD_WR_DATA8(0x09);
LCD_WR_DATA8(0x09);
LCD_WR_DATA8(0x08);
LCD_WR_DATA8(0x03);
LCD_WR_DATA8(0x24);
LCD_WR_DATA8(0x32);
LCD_WR_DATA8(0x32);
LCD_WR_DATA8(0x3B);
LCD_WR_DATA8(0x14);
LCD_WR_DATA8(0x13);
LCD_WR_DATA8(0x28);
LCD_WR_DATA8(0x2F);
LCD_WR_REG(0x21);
LCD_WR_REG(0x29);
}
编译重上电,可以看到刷屏效果明显改善了很多。前后波形对比图如下,可以看到,发送一个字节所耗费的时间快了10余倍,基本符合我们的预期。(好像SPI+DMA刷屏时间会更快,后续有时间可以再优化下)
总结
关于SPI屏驱动这部分的内容暂时就介绍到这里,后续有时间再详细介绍下lvgl库的移植。最后附上源代码链接:hc32f460petb_template.zip
|