【程序】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

所以,读操作没有问题。

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