赞
踩
一切程序以最后百度网盘链接的程序为准,可能在写文章的时候有些地方有改动。
主控:STM32F103C8T6
1.69 TFT-LCD(st7789驱动)
CLK:PA4
SDA:PA5
RST:PA6
D/C:PA7
BLK:PC14
CS:PA8
TFT-LCD是采用SPI通信的,这里使用stm32f103c8t6的SPI1,初始化代码如下
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能A端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_4);
//背光引脚 PC13 片选CS引脚PC14
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); //使能GPIOC时钟
//配置GPIOB的工作模式和初始化
GPIO_InitStruture.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStruture.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14; //引脚PC13 PC14
GPIO_InitStruture.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
GPIO_Init(GPIOC,&GPIO_InitStruture);
在原有的驱动基础上增加了几个宏去控制显示方向,方便后期更改
#define DISPLAY_OVERTURN 0
#define DISPLAY_BOTTOM_TO_TOP 0
#define DISPLAY_RIGHT_TO_LEFT 0
#define DISPLAY_OVERTURN 0
#define DISPLAY_BOTTOM_TO_TOP 1
#define DISPLAY_RIGHT_TO_LEFT 1
#define DISPLAY_OVERTURN 1
#define DISPLAY_BOTTOM_TO_TOP 0
#define DISPLAY_RIGHT_TO_LEFT 1
#define DISPLAY_OVERTURN 1
#define DISPLAY_BOTTOM_TO_TOP 1
#define DISPLAY_RIGHT_TO_LEFT 0
//st7789驱动 TFT_SCLK_Set(); //特别注意!! TFT_RST_Clr(); delay_ms(1000); TFT_RST_Set(); delay_ms(1000); TFT_SEND_CMD(0x11); //Sleep Out delay_ms(120); //DELAY120ms //-----------------------ST7789V Frame rate setting-----------------// //************************************************ TFT_SEND_CMD(0x3A); //颜色数据格式RGB565 65k真彩色 TFT_SEND_DATA(0x05); TFT_SEND_CMD(0xC5); //VCOM1 TFT_SEND_DATA(0x1A); /* (0,0)*********240***********-> * * * 280 240x280 * * * ↓ **/ TFT_SEND_CMD(0x36); // 屏幕显示方向设置 #if DISPLAY_BOTTOM_TO_TOP lcd_data |= (1<<7); #else lcd_data |= (0<<7); #endif #if DISPLAY_RIGHT_TO_LEFT lcd_data |= (1<<6); #else lcd_data |= (0<<6); #endif #if DISPLAY_OVERTURN//不翻转显示 lcd_data |= (1<<5);//翻转显示 #else lcd_data |= (0<<5); #endif TFT_SEND_DATA(lcd_data); /*****显示位置,注意,x和y是根据屏幕显示方向来定的,不一定统一**********/ TFT_SEND_CMD(0x2B); //设置显示区域 x轴起始及结束坐标 TFT_SEND_DATA((start_x>>8)&0xff); TFT_SEND_DATA(start_x&0xff); TFT_SEND_DATA((end_x>>8)&0xff); TFT_SEND_DATA(end_x&0xff); TFT_SEND_CMD(0x2B); //设置显示区域 Y轴起始及结束坐标 TFT_SEND_DATA((start_y>>8)&0xff); TFT_SEND_DATA(start_y&0xff); TFT_SEND_DATA((end_y>>8)&0xff); TFT_SEND_DATA(end_y&0xff); //-------------ST7789V Frame rate setting-----------// TFT_SEND_CMD(0xb2); //Porch Setting TFT_SEND_DATA(0x05); TFT_SEND_DATA(0x05); TFT_SEND_DATA(0x00); TFT_SEND_DATA(0x33); TFT_SEND_DATA(0x33); TFT_SEND_CMD(0xb7); //Gate Control TFT_SEND_DATA(0x05); //12.2v -10.43v //--------------ST7789V Power setting---------------// TFT_SEND_CMD(0xBB);//VCOM TFT_SEND_DATA(0x3F); TFT_SEND_CMD(0xC0); //Power control TFT_SEND_DATA(0x2c); TFT_SEND_CMD(0xC2); //VDV and VRH Command Enable TFT_SEND_DATA(0x01); TFT_SEND_CMD(0xC3); //VRH Set TFT_SEND_DATA(0x0F); //4.3+( vcom+vcom offset+vdv) TFT_SEND_CMD(0xC4); //VDV Set TFT_SEND_DATA(0x20); //0v TFT_SEND_CMD(0xC6); //Frame Rate Control in Normal Mode TFT_SEND_DATA(0X01); //111Hz TFT_SEND_CMD(0xd0); //Power Control 1 TFT_SEND_DATA(0xa4); TFT_SEND_DATA(0xa1); TFT_SEND_CMD(0xE8); //Power Control 1 TFT_SEND_DATA(0x03); TFT_SEND_CMD(0xE9); //Equalize time control TFT_SEND_DATA(0x09); TFT_SEND_DATA(0x09); TFT_SEND_DATA(0x08); //---------------ST7789V gamma setting-------------// TFT_SEND_CMD(0xE0); //Set Gamma TFT_SEND_DATA(0xD0); TFT_SEND_DATA(0x05); TFT_SEND_DATA(0x09); TFT_SEND_DATA(0x09); TFT_SEND_DATA(0x08); TFT_SEND_DATA(0x14); TFT_SEND_DATA(0x28); TFT_SEND_DATA(0x33); TFT_SEND_DATA(0x3F); TFT_SEND_DATA(0x07); TFT_SEND_DATA(0x13); TFT_SEND_DATA(0x14); TFT_SEND_DATA(0x28); TFT_SEND_DATA(0x30); TFT_SEND_CMD(0XE1); //Set Gamma TFT_SEND_DATA(0xD0); TFT_SEND_DATA(0x05); TFT_SEND_DATA(0x09); TFT_SEND_DATA(0x09); TFT_SEND_DATA(0x08); TFT_SEND_DATA(0x03); TFT_SEND_DATA(0x24); TFT_SEND_DATA(0x32); TFT_SEND_DATA(0x32); TFT_SEND_DATA(0x3B); TFT_SEND_DATA(0x14); TFT_SEND_DATA(0x13); TFT_SEND_DATA(0x28); TFT_SEND_DATA(0x2F); TFT_SEND_CMD(0x21); //反显 TFT_SEND_CMD(0x29); //开启显示
TFT-LCD采用RGB565格式的颜色数据,每个像素占用16bit,显示图片的方式是向TFT-LCD的GRAM写入数据,具体流程:读取图片的宽高->设置显示范围->发送写入GRAM命令(0x2C)->写入图片数据->TFT-LCD显示。
通常电脑端的BMP图片数据是RGB888的个数,需要使用工具将RGB888格式的数据转换成RGB565格式的数据。这里我用QT写了一个图片转换工具。具体细和使用方法在第四点。
注意:在图片数组的前四个字节加入图片宽高字段,方便后续读取,不需要我们手动去设置图片的宽高。
void TFT_display_image(const uint16_t *address, uint16_t startX, uint16_t startY) { uint16_t image_width;//图片宽 uint16_t image_hight;//图片高 uint16_t x,y; image_width = address[0]; image_hight = address[1]; TFT_SetWindows(startX, startY, image_width, image_hight); for(y = 0; y < image_hight; y++) { for(x = 0; x < image_width; x++) { //发送图片数据,每次发送16位,先发高八位 TFT_SEND_DATA(address[y*image_width + x + 2]>>8); TFT_SEND_DATA(address[y*image_width + x + 2]&0xff); } } }
使用PCtolLCD2002软件对要显示的字体进行取模,字体取模方式可参照下图进行设置
void TFT_display_char16_16(const uint8_t *address ,uint16_t startX,uint16_t startY,uint16_t color) { unsigned int column; unsigned char tm=0,temp; TFT_SetWindows(startX, startY, 16, 16); for(column = 0; column < 32; column++) //column loop { temp =* address; for(tm = 0; tm < 8; tm++) { if(temp&0x01) { TFT_SEND_DATA(color>>8); TFT_SEND_DATA(color); } else { TFT_SEND_DATA(0); TFT_SEND_DATA(0); } temp >>= 1; } address++; } }
1、在显示文字的时候原始写法就是如果是1就填充对应文字的颜色,没有数据就填充黑色(背景色),但是如果整个屏幕的背景色不是黑色的话看感觉文字背景设很突兀。
为了解决上一个问题在此增加了一个函数用于显示透明的文字(其它大小展示没有调试),使用这种方式也有一个缺点就是显示速度太慢,因为是一个点一个点的刷上去
void TFT_display_char16_16_noBackColor(const uint8_t *address ,uint16_t startX,uint16_t startY,uint16_t color) { unsigned int column; unsigned char tm=0,temp; unsigned int x = 0; unsigned int y = 0; for(column = 0; column < 16; column++)//显示前16行数据 { temp =* address; for(tm = 0; tm < 8; tm++) { if(temp&0x01) { TFT_display_point(startX+ tm, startY+ y ,color); printf("cloumn:%d Y:%d\r\n",column, tm); } temp >>= 1; } address++; temp =* address; for(tm = 0; tm < 8; tm++) { if(temp&0x01) { TFT_display_point(startX+ tm+8, startY+ y ,color); } temp >>= 1; } // if(column>0 && column%2 == 0)//如果开启字体的高读会压缩到之前的一半 y++; address++; } }
显示效果
可以对比下一在图片上显示效果,后者是不带底色显示问题
我这里是使用QT自己写的一个转换工具,将BMP图片转换成C语言数组和bin文件(未验证),此软件占时只做一张图片的转换,自己可以根据自己的需求做修改。
BMP图片头部14Byte+位部信息头40Byte,注意:每个信息块是从右向左计算
头部14字节具体含义如下
Byte1-2
0x42:‘B’,0x4d:‘M’
Byte3-6文件大小(bmp图片总占用空间)
注意高位是高字节,低位是低字节,实际大小应该是0x0313b6=201654
Byte11-14
位图数据部分相对于文件首的起始偏移,0x36=54
位图信息共占40位
Byte15-18
信息头部:通常为0x28或者0x38,0x0000 0028
Byte19-22
图像宽度0x0000 0118 = 280
Byte23-26
图像高度0x0000 00f0=240
Byte27-28
保留位,值永远为0x01
Byte29-30
每个像素占用的位数0x0018=24,即该图像为RGB888格式
Byte31-34
压缩方式
Byte35-38
图像尺寸(字节数,真正图片数据不包括图片信息的头部)0x0003 1380=201600(Byte3-6 减去 Byte11-14)
Byte39-42
水平分辨率0x0ec4 = 3780
Byte43-46
垂直分辨率0x0ec4 = 3780
Byte47-50
引用色彩数
Byte51-54
关键色彩数
#include "mainwindow.h" #include "./ui_mainwindow.h" #include "protocol.h" #include <QDataStream> #include <QFile> #include <QFileDialog> #include <QImage> #include <QMessageBox> #include <QPixmap> #include <QSettings> MainWindow::MainWindow(QWidget *parent) : QWidget(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_openImage_clicked() { QString fileName; QSettings setting("./lastopenFile.ini", QSettings::IniFormat); QString lastPath = setting.value("LastFilePath").toString(); fileName = QFileDialog::getOpenFileName(this, QString::fromLocal8Bit("选择图片"), lastPath, tr("Images(*.bmp)")); ui->imageInfoEdit->append("/*imag name:"+fileName+"*/"); qInfo()<<"*imag name:"<< fileName<<"*"; if(!fileName.isEmpty()) { setting.setValue("LastFilePath", fileName); read_BMP(fileName); } } void MainWindow::read_BMP(QString fileName) { QFile file(fileName); QFile saveFile("./image.bin");//图片数据保存的bin文件 saveFile.open(QIODevice::WriteOnly);//以只写的方式打开文件 QDataStream saveFileStream(&saveFile);//定义一个数据流 QString showStr; QString bmpToCarray; BmpHeader header; Pixel bmpPixel; unsigned short rgb565Data = 0x0000; int times = 0; int lines = 0; char bmpData = 0x00; // unsigned char rgb888[WIDTH*3]; if(!file.exists()) { return; } file.open(QFileDevice::ReadOnly); file.read((char*)&header, sizeof(BmpHeader));//读取图片文件头信息 showStr.append("/*图片宽:"+(QString("%1 ").arg(header.bmpWidth, 1, 10, QLatin1Char('0')).toUpper())+"\n"); showStr.append("图片高:"+(QString("%1 ").arg(header.bmpHeight, 1, 10, QLatin1Char('0')).toUpper())+"\n"); showStr.append("每像素占用位数(bit):"+(QString("%1 ").arg(header.bmpBitCount, 1, 10, QLatin1Char('0')).toUpper())+"*/\n"); bmpToCarray.append("#ifndef __IMAGE_H\n"); bmpToCarray.append("#define __IMAGE_H\n#include <stdint.h>\n"); bmpToCarray.append("const uint16_t bmp_array[] ={ "); /*图像的宽高*/ bmpToCarray.append("\n\t0x"+(QString("%1 ").arg((header.bmpWidth)&0xffff, 4, 16, QLatin1Char('0')).toUpper())+",/*width*/ "); // bmpToCarray.append("0x"+(QString("%1 ").arg(header.bmpWidth&0xff, 2, 10, QLatin1Char('0')).toUpper())+", /*width*/ "); bmpToCarray.append("\n\t0x"+(QString("%1 ").arg((header.bmpHeight)&0xffff, 4, 16, QLatin1Char('0')).toUpper())+",/*height*/\n\t"); // bmpToCarray.append("0x"+(QString("%1 ").arg(header.bmpHeight&0xff, 2, 10, QLatin1Char('0')).toUpper())+", /*height*/\n\t"); for(int i = 0; i < header.bmpHeight; i++) { file.seek((header.bmpHeight - i -1)*header.bmpWidth*3 + header.bfOffBits+((header.bmpHeight - i -1))*(4-header.bmpWidth%4)); for(int j = 0; j < header.bmpWidth; j++) { /*注意:BMP图片数据存储是从左到右,从下到上存储的!!!*/ // file.seek(i*header.bmpWidth*3+j*sizeof(bmpPixel)+54); file.read((char*)&bmpPixel, sizeof(Pixel)); rgb565Data = RGB888toRGB565(bmpPixel.red, bmpPixel.green, bmpPixel.blue); if(i == 0 && j == 0) { qInfo()<<"RED:0x"<<(QString("%1").arg(bmpPixel.red&0xff, 2, 16, QLatin1Char('0')).toUpper()); qInfo()<<"GREEN:0x"<<(QString("%1").arg(bmpPixel.green&0xff, 2, 16, QLatin1Char('0')).toUpper()); qInfo()<<"BLUE:0x"<<(QString("%1").arg(bmpPixel.blue&0xff, 2, 16, QLatin1Char('0')).toUpper()); } bmpToCarray.append("0x"+(QString("%1").arg(rgb565Data&0xfFFf, 4, 16, QLatin1Char('0')).toUpper())+", "); lines++; if(lines == 8) { bmpToCarray.append("\n\t"); lines = 0; } saveFileStream<<(unsigned short)rgb565Data;//保存到bin文件中 ("%1 ").arg(bmpData & 0xff, 1, 16, QLatin1Char('0')).toUpper())); } } bmpToCarray.append("};\n"); bmpToCarray.append("#endif\n"); // } ui->imageInfoEdit->append(showStr); ui->imageInfoEdit->append(bmpToCarray); file.close(); saveFile.close(); } /************************************ * * RGB888格式转换成RGB565规则 * 取出R的高5位、G高6位、B高5位 * * *********************************/ unsigned short MainWindow::RGB888toRGB565(unsigned char red, unsigned char green, unsigned char blue) { //RGB888转RGB565,都是保留高几位 unsigned short B = (blue >> 3) & 0x001F; unsigned short G = ((green >> 2) << 5) & 0x07E0; unsigned short R = ((red >> 3) << 11) & 0xF800; return (unsigned short) (R | G | B); }
在下载的资源QT文件夹下有个tool文件夹目录点进去
点击BMP_Transform_BIN.exe打开软件
点击打开图片按钮找到你要转换的图片(只能是BMP格式的)
然后将转换后的图片数据复制到keil中image.h文件夹中
最后运行代码烧录到stm32中就可以了
图片数据,因为图片格式为RGB888格式,即三个字节表示一个像素点,注意:实际保存的数据不是Red、Green、Blue、而是Blue、Green、Red(小端模式,即低地址存放低位数据)
如图所示red:0x21,green:0x2a,blue:0x31
注意:BMP图片数数据时从左到右,从下到上存储的
1、BMP图片数据存储是从左到右,从下到上存储的!!!
如下图所示,c6 c6 d3…是bmp图片最后一行像素信息
2、windows是4字节对齐,如果图片宽度不是4字节对齐的话windows会自动补齐(填0x00),这样会导致显示的图片出现倾斜。如下图,图片的宽度是50,每个像素占用3Byte,3*50=150,所以系统自动填充2Byte补齐。所以在程序中要注意这种情况,在读数据的时候做特殊处理。
所以在QT中要加以下特殊处理
既然都看到这了,如果对你有帮助就点点赞吧
传送门
提取码:5656
如有侵权,请联系删除!!!!!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。