前段時間在淘寶的Risym旗艦店買了一款2.8寸的TFTLCD彩屏,分辨率爲320x240。這款液晶屏的控制芯片是ILI9325或ILI9328。
賣家給的資料是一個壓縮包,以前下載下來還能打開。現在下載下來打開提示“不可預料的壓縮文件末端”,壓縮文件是壞的。打開以前下載的壓縮包,裏面有兩個例程:“STM32_FSMC_TFT_20130112-OK-MDK”和“STM32-ucosII+GUI3.9a-20121220-MDK”。運行第一個不帶操作系統的例程,發現根本運行不了,液晶屏一直是白屏。
調試時發現裏面有這樣一句話:
DeviceCode = LCD_ReadReg(0x0000);
讀取0號寄存器上的設備ID,讀出來一直是0。在這個上面卡了很久,後面找了野火的3.2寸彩屏的例程,裏面就沒有讀ID的操作,全部都是寫操作,而且那個例程不僅僅能夠驅動野火的彩屏,還能驅動這款彩屏,代碼完全一樣。
這說明,Risym賣家給的彩屏資料和例程是錯誤的。
以下給出該款彩屏正確的程序。這個程序能正常輪播顯示4張彩色圖片,程序中沒有任何讀操作,讀所謂的0號ID寄存器的結果爲0。
程序下載地址:https://pan.baidu.com/s/1Uy7z4H0sBBAAWU8U2o_hZQ(提取碼:nfmm)
【main.c】
#include <stdio.h>
#include <stm32f1xx.h>
#include "common.h"
#include "images.h"
#include "ILI9341.h"
int main(void)
{
HAL_Init();
clock_init();
usart_init(115200);
printf("STM32F103VE FSMC ILI9341\n");
printf("SystemCoreClock=%u\n", SystemCoreClock);
ILI9341_Init();
ILI9341_Clear(ILI9341_COLOR_BLUE);
ILI9341_Enable(1);
while (1)
{
HAL_Delay(1000);
ILI9341_DrawImage(image1, 0, 36, 239, 248);
HAL_Delay(1000);
ILI9341_DrawImage(image2, 0, 36, 239, 248);
HAL_Delay(1000);
ILI9341_DrawImage(image3, 0, 36, 239, 248);
HAL_Delay(1000);
ILI9341_DrawImage(image4, 0, 36, 239, 248);
}
}
【ILI9341.h】
#ifndef _ILI9341_H
#define _ILI9341_H
#define ILI9341_COLOR_BLACK 0x0000
#define ILI9341_COLOR_BLUE 0x001f
#define ILI9341_COLOR_GREEN 0x07e0
#define ILI9341_COLOR_RED 0xf800
#define ILI9341_COLOR_WHITE 0xffff
#define ILI9341_CMD (*(volatile uint16_t *)0x60000000)
#define ILI9341_DATA (*(volatile uint16_t *)0x60020000)
#define ILI9341_CMD_BEGINPAINT 0x2c
void ILI9341_Clear(uint16_t color);
void ILI9341_DrawImage(const void *image, uint16_t x, uint16_t y, uint16_t width, uint16_t height);
void ILI9341_Enable(int enabled);
void ILI9341_Init(void);
void ILI9341_SetRegion(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void ILI9341_SetScanDirection(uint8_t direction);
#endif
【ILI9341.c】
#include <stm32f1xx.h>
#include "ILI9341.h"
#define BL_OFF HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, GPIO_PIN_SET)
#define BL_ON HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, GPIO_PIN_RESET)
#define RST_OFF HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET)
#define RST_ON HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET)
NOR_HandleTypeDef hnor;
static int lcd_cx, lcd_cy;
/* 清屏 */
void ILI9341_Clear(uint16_t color)
{
int i = lcd_cx * lcd_cy;
ILI9341_SetRegion(0, 0, lcd_cx - 1, lcd_cy - 1);
ILI9341_CMD = ILI9341_CMD_BEGINPAINT;
while (i--)
ILI9341_DATA = color;
}
/* 顯示圖片 */
void ILI9341_DrawImage(const void *image, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{
const uint16_t *p = image;
int i = width * height;
ILI9341_SetRegion(x, y, x + width - 1, y + height - 1);
ILI9341_CMD = ILI9341_CMD_BEGINPAINT;
while (i--)
ILI9341_DATA = *p++;
}
/* 開/關顯示 */
void ILI9341_Enable(int enabled)
{
if (enabled)
{
HAL_Delay(20); // 延時打開背光, 避免瞬間出現白色條紋
BL_ON;
}
else
BL_OFF;
}
/* 初始化顯示屏 */
void ILI9341_Init(void)
{
FSMC_NORSRAM_TimingTypeDef timing = {0};
GPIO_InitTypeDef gpio;
__HAL_RCC_FSMC_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
// PC5: BL
BL_OFF;
gpio.Mode = GPIO_MODE_OUTPUT_PP;
gpio.Pin = GPIO_PIN_5;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOC, &gpio);
// PD12: RST
RST_ON;
gpio.Pin = GPIO_PIN_12;
HAL_GPIO_Init(GPIOD, &gpio);
// PD0~1: FSMC_D2~3, PD4: FSMC_NOE, PD5: FSMC_NWE, PD7: FSMC_NE1, PD8~11: FSMC_D13~16, PD14~15: FSMC_D0~1
// FSMC_NOE=>RD, FSMC_NWE=>WR, FSMC_D16=>RS, FSMC_NE1=>CS
gpio.Mode = GPIO_MODE_AF_PP;
gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_14 | GPIO_PIN_15;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOD, &gpio);
// PE7~15: FSMC_D4~12
gpio.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOE, &gpio);
// FSMC一共有6種模式:模式1~2和模式A~D
// 彩屏使用的是模式2(即NOR Flash模式)而非模式B,這是因爲配置FSMC時並沒有使能擴展模式
hnor.Extended = FSMC_Bank1E;
hnor.Instance = FSMC_Bank1;
hnor.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;
hnor.Init.MemoryType = FSMC_MEMORY_TYPE_NOR;
hnor.Init.NSBank = FSMC_NORSRAM_BANK1;
hnor.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
timing.AddressHoldTime = 1;
timing.AddressSetupTime = 0;
timing.CLKDivision = 2;
timing.DataLatency = 2;
timing.DataSetupTime = 1;
HAL_NOR_Init(&hnor, &timing, NULL);
HAL_Delay(1);
RST_OFF;
HAL_Delay(1);
// Power control B
ILI9341_CMD = 0xcf;
ILI9341_DATA = 0x00;
ILI9341_DATA = 0x81;
ILI9341_DATA = 0x30;
// Power on sequence control
ILI9341_CMD = 0xed;
ILI9341_DATA = 0x64;
ILI9341_DATA = 0x03;
ILI9341_DATA = 0x12;
ILI9341_DATA = 0x81;
// Driver timing control A
ILI9341_CMD = 0xe8;
ILI9341_DATA = 0x85;
ILI9341_DATA = 0x10;
ILI9341_DATA = 0x78;
// Power control A
ILI9341_CMD = 0xcb;
ILI9341_DATA = 0x39;
ILI9341_DATA = 0x2c;
ILI9341_DATA = 0x00;
ILI9341_DATA = 0x34;
ILI9341_DATA = 0x02;
// Pump ratio control
ILI9341_CMD = 0xf7;
ILI9341_DATA = 0x20;
// Driver timing control B
ILI9341_CMD = 0xea;
ILI9341_DATA = 0x00;
ILI9341_DATA = 0x00;
// Frame Rate Control (In Normal Mode/Full Colors)
ILI9341_CMD = 0xb1;
ILI9341_DATA = 0x00;
ILI9341_DATA = 0x1b;
// Display Function Control
ILI9341_CMD = 0xb6;
ILI9341_DATA = 0x0a;
ILI9341_DATA = 0xa2;
// Power Control 1
ILI9341_CMD = 0xc0;
ILI9341_DATA = 0x35;
// Power Control 2
ILI9341_CMD = 0xc1;
ILI9341_DATA = 0x11;
// VCOM Control 1
ILI9341_CMD = 0xc5;
ILI9341_DATA = 0x45;
ILI9341_DATA = 0x45;
// VCOM Control 2
ILI9341_CMD = 0xc7;
ILI9341_DATA = 0xa2;
// Enable 3G
ILI9341_CMD = 0xf2;
ILI9341_DATA = 0x00;
// Gamma Set
ILI9341_CMD = 0x26;
ILI9341_DATA = 0x01;
// Positive Gamma Correction
ILI9341_CMD = 0xe0;
ILI9341_DATA = 0x0f;
ILI9341_DATA = 0x26;
ILI9341_DATA = 0x24;
ILI9341_DATA = 0x0b;
ILI9341_DATA = 0x0e;
ILI9341_DATA = 0x09;
ILI9341_DATA = 0x54;
ILI9341_DATA = 0xa8;
ILI9341_DATA = 0x46;
ILI9341_DATA = 0x0c;
ILI9341_DATA = 0x17;
ILI9341_DATA = 0x09;
ILI9341_DATA = 0x0f;
ILI9341_DATA = 0x07;
ILI9341_DATA = 0x00;
// Negative Gamma Correction
ILI9341_CMD = 0xe1;
ILI9341_DATA = 0x00;
ILI9341_DATA = 0x19;
ILI9341_DATA = 0x1b;
ILI9341_DATA = 0x04;
ILI9341_DATA = 0x10;
ILI9341_DATA = 0x07;
ILI9341_DATA = 0x2a;
ILI9341_DATA = 0x47;
ILI9341_DATA = 0x39;
ILI9341_DATA = 0x03;
ILI9341_DATA = 0x06;
ILI9341_DATA = 0x06;
ILI9341_DATA = 0x30;
ILI9341_DATA = 0x38;
ILI9341_DATA = 0x0f;
// Pixel Format Set
ILI9341_CMD = 0x3a;
ILI9341_DATA = 0x55;
// Sleep Out
ILI9341_CMD = 0x11;
HAL_Delay(100);
ILI9341_SetScanDirection(0); // 設置掃描方向
ILI9341_CMD = 0x29; // 允許顯示顯存中的圖像 (後面還需要打開背光才能開顯示)
}
/* 設置顯示區域 */
void ILI9341_SetRegion(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
ILI9341_CMD = 0x2a;
ILI9341_DATA = x0 >> 8;
ILI9341_DATA = x0 & 0xff;
ILI9341_DATA = x1 >> 8;
ILI9341_DATA = x1 & 0xff;
ILI9341_CMD = 0x2b;
ILI9341_DATA = y0 >> 8;
ILI9341_DATA = y0 & 0xff;
ILI9341_DATA = y1 >> 8;
ILI9341_DATA = y1 & 0xff;
}
/* 設置GRAM掃描方向 */
void ILI9341_SetScanDirection(uint8_t direction)
{
assert_param(direction <= 7);
// 根據模式更新XY方向的像素寬度
if (direction % 2 == 0)
{
// 0 2 4 6模式下X方向像素寬度爲240, Y方向爲320
lcd_cx = 240;
lcd_cy = 320;
}
else
{
// 1 3 5 7模式下X方向像素寬度爲320,Y方向爲240
lcd_cx = 320;
lcd_cy = 240;
}
// 0x36命令參數的高3位可用於設置GRAM掃描方向
ILI9341_CMD = 0x36;
ILI9341_DATA = 0x08 | (direction << 5);
}
查看一下芯片的手冊,可以發現0號命令根本就不是讀取芯片ID的命令,而是一個空操作(No Operation)。
那是不是此屏不支持讀操作呢?事實並非如此,我們可以通過Memory Read命令將彩屏上顯示的內容讀回單片機。
在下面的程序中,我們先將圖片顯示到屏幕上,然後將屏幕顯示內容的前120行讀出來,和原來的圖像數據比較,看是否一樣。如果一樣,則說明讀操作沒有問題。
#include <stdio.h>
#include <stm32f1xx.h>
#include <string.h>
#include "common.h"
#include "images.h"
#include "ILI9341.h"
#define RGB888TO565(color) ((((color) >> 8) & 0xf800) | (((color) >> 5) & 0x7e0) | (((color) >> 3) & 0x1f))
static void ILI9341_GetPixels(uint16_t *pixels, int count)
{
int i = 0;
uint16_t data[3];
uint16_t rgb565[2];
uint32_t rgb888[2];
ILI9341_CMD = 0x2e;
ILI9341_DATA; // dummy read
while (i < count)
{
// 讀兩個像素, 每個像素3字節
// 每字節表示一個分量, 分量在字節中是左對齊的
data[0] = ILI9341_DATA; // 0xr1g1 (高字節爲第一個像素的紅色分量, 低字節爲第一個像素的綠色分量)
data[1] = ILI9341_DATA; // 0xb1r2 (高字節爲第一個像素的藍色分量, 低字節爲第二個像素的紅色分量)
data[2] = ILI9341_DATA; // 0xg2b2 (高字節爲第二個像素的綠色分量, 低字節爲第二個像素的藍色分量)
// 轉換成RGB888
rgb888[0] = (data[0] << 8) | (data[1] >> 8);
rgb888[1] = ((data[1] & 0xff) << 16) | data[2];
//printf("#%06X #%06X => ", rgb888[0], rgb888[1]);
// 再轉換成RGB565
rgb565[0] = RGB888TO565(rgb888[0]);
rgb565[1] = RGB888TO565(rgb888[1]);
//printf("0x%04x 0x%04x\n", rgb565[0], rgb565[1]);
// 保存顏色值
pixels[i++] = rgb565[0];
if (i < count)
pixels[i++] = rgb565[1];
}
}
static void ILI9341_GetPixelsInRect(void *pixels, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{
ILI9341_SetRegion(x, y, x + width - 1, y + height - 1);
ILI9341_GetPixels(pixels, width * height);
}
int main(void)
{
static uint16_t pixels[120][239];
HAL_Init();
clock_init();
usart_init(115200);
printf("STM32F103VE FSMC ILI9341\n");
printf("SystemCoreClock=%u\n", SystemCoreClock);
ILI9341_Init();
ILI9341_Clear(ILI9341_COLOR_BLUE);
ILI9341_Enable(1);
ILI9341_DrawImage(image1, 0, 36, 239, 248); // 在屏幕上顯示一張圖片
ILI9341_GetPixelsInRect(pixels, 0, 36, 239, 120); // 讀取屏幕上的圖像內容
if (memcmp(pixels, image1, sizeof(pixels)) == 0) // 和原圖比較
printf("same\n"); // 相同
else
printf("different\n"); // 不相同
while (1)
{
}
}
程序運行結果爲:
STM32F10STM32F103VE FSMC ILI9341
SystemCoreClock=72000000
same
所以,讀操作沒有問題。