这是我第一次写帖子,由于毕设对于显示的需求,我选择使用2.8寸TFT来做触控显示,但是全网并没有2.8寸st735TFT的详细资料,导致我在完成毕设的时候特别艰难,在我完成的第一时间,我第一时间想到要写下我遇到的问题及解决方案。
注:本代码为本人毕设作品使用,他人用若用作文章发表还请注明出处。不想看问题分析只想看代码的可以直接跳过分析部分。
环境:keil MDK,2.8寸240*320像素ST7735+XTP2046触摸屏,STM32F103C8T6最小系统板。
问题分析
起初我在CSDN查找到商家优信电子发布的初始化官方代码,自己将软件及硬件SPI写好之后将代码烧录进STM32,发现其显示极其不稳定,时常屏闪。起初我一直以为是我程序问题我又将商家给的测试代码烧录测试,发现仍然如此,而且.官方测试代码为128*128分辨率,显示还X镜像了。
屏闪图片
ST7735 TFT屏闪视频
TFT正常显示
在多次更改代码,网上查资料没有结果之后,我详细的观看了一遍数据手册。之前我一度以为是我的屏幕质量有问题,在详细查询数据手册之后这个问题终于解决。
导致显示错误的问题有两个点,这里将结合优信电子给定的官方代码讲解。
MCU_write_TFT_Byte(0xC0,TFT_COMMAND);
MCU_write_TFT_Byte(0xA2,TFT_DATA);
MCU_write_TFT_Byte(0x02,TFT_DATA);
MCU_write_TFT_Byte(0x84,TFT_DATA);
MCU_write_TFT_Byte(0xC1,TFT_COMMAND);
MCU_write_TFT_Byte(0xC5,TFT_DATA);
————————————————
版权声明:本文为CSDN博主「优信电子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42250136/article/details/119909394
在上述代码0XC0,0XC1为配置电压命令,这里我不知道为什么官方没有配置为默认值。这里插入一段知识,首先要明确如果没有人为的配置命令,系统会使用默认配置,这里将默认配置更改之后就会出现屏闪问题。
商家官方原文给出的代码并没有地址配置,这也将导致问的出现。
我使用的为240*320分辨率的屏幕,行和列地址对应0xFO-1,0x140-1,代码配置为
LCD_WR_REG(0X2A);
LCD_WR_DATA(0x00); // 列地址起始
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x00); // 列地址结束
LCD_WR_DATA(0xef);
LCD_WR_REG(0X2B);
LCD_WR_DATA(0x00); // 行地址起始
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x01); // 行地址结束
LCD_WR_DATA(0x3f);
代码部分
其实大部分配置都维持默认即可,全部初始化代码如下
void LCD_Init(void)
{
SPI_Config();//spi初始化
RES_LOW;
delay_ms(500);
RES_HEG;
delay_ms(500); //LCD 复位
LCD_WR_REG(0x01);//软复位
delay_ms (150);
LCD_WR_REG(0x11);//退出睡眠
delay_ms (600);
LCD_WR_REG(0xB4); //反色命令
LCD_WR_DATA(0x07);//默认反色处理
LCD_WR_REG(0x36); //MX, MY, RGB 模式
LCD_WR_DATA(0x98); //"0x#0"BGR色 "0x#8"RGB色
//此处我使用为竖屏旋转180,从左向右刷新。
LCD_WR_REG(0X2A);
LCD_WR_DATA(0x00); // 列地址起始
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x00); // 列地址结束
LCD_WR_DATA(0xef);
LCD_WR_REG(0X2B);
LCD_WR_DATA(0x00); // 行地址起始
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x01); // 行地址结束
LCD_WR_DATA(0x3f);
LCD_WR_REG(0x3A); //65k mode
LCD_WR_DATA(0x05);
// 打开显示
LCD_WR_REG(0X29);
delay_ms (100);
}
硬件SPI配置,此配置为硬件SPI的极限速度,追求跟高速度可尝试使用DMA。
const u16 DelayTim=1000;
/*--------硬件SPI配置-------*/
void SPI_Config(void)
{//初始化结构体
GPIO_InitTypeDef GPIO_InitStu;
SPI_InitTypeDef SPI_InitStu;
//开SPI对应GPIO时钟
RCC_APB2PeriphClockCmd(SPI_GPIO_RCC, ENABLE);
//初始化SCL/MISO/MOSI/CS
GPIO_InitStu.GPIO_Mode = GPIO_Mode_AF_PP ;
GPIO_InitStu.GPIO_Pin = CLK_PIN|MOSI_PIN;
GPIO_InitStu.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( SPI_GPIOX, & GPIO_InitStu);
GPIO_InitStu.GPIO_Mode = GPIO_Mode_IN_FLOATING ;
GPIO_InitStu.GPIO_Pin = MISO_PIN;
GPIO_Init( SPI_GPIOX, & GPIO_InitStu);
GPIO_InitStu.GPIO_Mode = GPIO_Mode_Out_PP ;
GPIO_InitStu.GPIO_Pin = NSS_PIN;
GPIO_Init( SPI_GPIOX, & GPIO_InitStu);
GPIO_InitStu.GPIO_Pin = TFT_RES_PIN|TFT_DC_PIN;
GPIO_Init( SPI_GPIOX, & GPIO_InitStu);
NSS_HEG;
RCC_APB2PeriphClockCmd(SPIX_RCC_CLK ,ENABLE);
SPI_InitStu.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStu.SPI_Mode = SPI_Mode_Master;
SPI_InitStu.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStu.SPI_CPOL = SPI_CPOL_High;
SPI_InitStu.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStu.SPI_NSS = SPI_NSS_Soft;
SPI_InitStu.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
SPI_InitStu.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStu.SPI_CRCPolynomial = 7;
SPI_Init(SPIX, &SPI_InitStu);
//使能SPI1
SPI_Cmd(SPIX, ENABLE);
}
uint8_t SPI_WriteReadByet (uint8_t data)
{
u16 Tim;
Tim=DelayTim;
while(SPI_I2S_GetFlagStatus ( SPIX, SPI_I2S_FLAG_TXE)==RESET )
{
if ((Tim--)==0) return 0;
}
SPI_I2S_SendData ( SPIX,data);
return 1;
}
发送数据时序图
软件SPI代码,经测试软件SPI明显速度低于硬件,对于引脚被限制或移植性有要求的建议使用
/*----------软件SPI配置----------*/
void SPI_Config(void)
{
GPIO_InitTypeDef GPIO_InitStu;
//开SPI对应GPIO时钟
RCC_APB2PeriphClockCmd(SPI_GPIO_RCC, ENABLE);
//初始化SCL/MISO/MOSI/CS
GPIO_InitStu.GPIO_Mode = GPIO_Mode_IN_FLOATING ;
GPIO_InitStu.GPIO_Pin = MISO_PIN;
GPIO_Init( SPI_GPIOX, & GPIO_InitStu);
GPIO_InitStu.GPIO_Mode = GPIO_Mode_Out_PP ;
GPIO_InitStu.GPIO_Pin = CLK_PIN|MOSI_PIN|NSS_PIN;
GPIO_InitStu.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( SPI_GPIOX, & GPIO_InitStu);
GPIO_InitStu.GPIO_Pin = TFT_RES_PIN|TFT_DC_PIN;
GPIO_Init( SPI_GPIOX, & GPIO_InitStu);
NSS_HEG;
CLK_HEG;
}
//SPI读写函数
uint8_t SPI_WriteReadByet (uint8_t data)
{
for (int i=0;i<8;i++)
{
CLK_LOW;
if( 0x80&(data<<i) )DAT_HEG;
else DAT_LOW;
CLK_HEG;
}
return 0;
}
触摸IC XTP-2046的驱动代码如下:
触摸驱动代码最开始是从CSDN其他帖子复制的,但是我在直接使用中发现其读取触摸数据完全失败,我下方的代码是自己使用逻辑分析仪采用不断改进时序改进而来,我希望通过我的努力让后来的兴趣爱好者可以降低开发难度,可以不用走前人的坑,以至于慢慢失去兴趣。
void TFT_TP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStu;
EXTI_InitTypeDef EXTI_InitStu;
RCC_APB2PeriphClockCmd( TFT_TP_GPIO_CLK, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//打开AFIO重映射PB4,PB3,使其成为普通IO口
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//关闭JTAG功能(PB3/4),只使用SWD(PA13/14)调试
GPIO_InitStu.GPIO_Pin = TFT_TP_MOSI|TFT_TP_CLK|TFT_TP_CS;
GPIO_InitStu.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStu.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(TFT_TP_GPIO, &GPIO_InitStu);
GPIO_InitStu.GPIO_Pin = TFT_TP_IRQ|TFT_TP_MISO;
GPIO_InitStu.GPIO_Mode = GPIO_Mode_IPD; ; //上拉输入
GPIO_Init(TFT_TP_GPIO, &GPIO_InitStu);
RCC_APB2PeriphClockCmd( RCC_APB2Periph_AFIO, ENABLE);
GPIO_EXTILineConfig( GPIO_PortSourceGPIOB, GPIO_PinSource8);
//使用中断来及时读取触摸数据,接口为PB8
EXTI_InitStu.EXTI_Line = EXTI_Line8;
EXTI_InitStu.EXTI_LineCmd = ENABLE;
EXTI_InitStu.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStu.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStu);
TFT_TP_CS_HIG; //释放片选
TFT_TP_CLK_LOW;
TFT_TP_MOSI_LOW;
TFT_TP_ITconfig( ENABLE);
}
void TFT_TP_ITconfig(FunctionalState ED)
{
NVIC_InitTypeDef NVIC_InitStu;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断分组
NVIC_InitStu.NVIC_IRQChannel = EXTI9_5_IRQn;//设置EXTI8中断
NVIC_InitStu.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级为2
NVIC_InitStu.NVIC_IRQChannelSubPriority = 2;//响应优先级为1
NVIC_InitStu.NVIC_IRQChannelCmd = ED;//使能中断源
NVIC_Init(&NVIC_InitStu);
}
static void delay_us(u32 us)
{//对于stm32f1系列 72mhz大致是1us
u16 i = 0;
while(us--)
{
i = 2;
while(i--);
}
}
void TFT_TP_SPI_WriteByet(u8 CMD)
{
u8 count=0;
for(count=0;count<8;count++)
{
if(CMD&0x80)TFT_TP_MOSI_HIG;
else TFT_TP_MOSI_LOW;
CMD<<=1;
delay_us(1);
TFT_TP_CLK_LOW;
TFT_TP_CLK_HIG; //上升沿有效
}
}
u16 TFT_TP_SPI_ReadXY(u8 CMD)
{
u8 count=0;
u16 Num=0;
TFT_TP_CLK_LOW; //先拉低时钟
TFT_TP_MOSI_LOW; //拉低数据线
TFT_TP_CS_LOW; //选中触摸屏IC
TFT_TP_SPI_WriteByet(CMD);//发送命令字
TFT_TP_CLK_LOW;
delay_us(6);//ADS7846的转换时间最少为6us
for(count=0;count<16;count++)//读出16位数据,只有高12位有效
{
Num<<=1;
TFT_TP_CLK_HIG;
TFT_TP_CLK_LOW; //下降沿有效
delay_us(1);
if( GPIO_ReadInputDataBit(TFT_TP_GPIO,TFT_TP_MISO) )Num+=1;
}
Num>>=4; //只有高12位有效.
TFT_TP_CS_HIG; //释放片选
return Num;
}
u16 TFT_TP_Read_XorY(u8 CMD)
{
u16 i, j;
u16 buf[5];//读取5次
u16 sum=0;
u16 temp;
for(i=0;i<5;i++)buf[i]=TFT_TP_SPI_ReadXY(CMD);
for(i=0;i<4; i++)//排序
{
for(j=i+1;j<5;j++)
{
if(buf[i]>buf[j])//升序排列
{
temp=buf[i];
buf[i]=buf[j];
buf[j]=temp;
}
}
}
sum=0;
for(i=1;i<4;i++)sum+=buf[i];//去掉一个最大值一个最小值
temp=sum/3;
return temp;
}
void TFT_TP_ReadXY(XYcoordType *XY)
{
XY->x = TFT_TP_Read_XorY(0XD0);
XY->y = TFT_TP_Read_XorY(0X90);
XY->x = 240 -( (XY->x) * 240)/4096;//这里用240减是为了将数据取反,
//因为我将对角屏幕倒置使用
XY->y = ( (XY->y) * 320)/4096; //除4096因为触摸数据为12位需要对应于像素。
}