【程序】STM32F103VE單片機通過FSMC驅動Risym 2.8寸TFTLCD 320x240分辨率 ILI9325/9328彩屏(ID寄存器讀出來爲0是正常現象)

前段時間在淘寶的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

所以,讀操作沒有問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章