0xAA55 发表于 2018-11-6 07:20:17

【单片机】用stm32f103驱动ili9341

上次写的代码用了DMA,做了个异步架构。但其实对于传输的数据很小,等待的数量却很多的情况下,这么做犹如大炮打蚊子——设置DMA通道那会儿花费的时间足够SPI传输完所需要的数据了。ili9341屏幕对于传输“命令”的时候DC线必须为低电平,而传输“数据”的时候DC必须为高电平,命令通常只有1字节,传输完了后就要立即传输数据作为“参数”。这个时候如果你要使用那个异步DMA方式来完成命令和数据的自动传输的话,你不能一次性就把命令和数据都打包好让DMA自动传递数据给SPI控制器,必须在发送完命令后等SPI控制器真的完成了数据传输,再设置GPIO的状态(DC线的电平),然后再进行下一轮的DMA传输。在绘制大量“随机坐标”的像素点,比如使用“中点画圆算法”画圆的时候,或者绘制斜线的时候,由于一条由像素块组成的“斜线”通常由无数条不同长度的由像素块组成的“直线段”组成,这些“直线段”很多时候都很短,使用异步DMA的话,你会把大量的时间浪费在读取异步操作队列、设置DMA通道等各种初始化的操作上。

事实上,stm32f103 它也并不适合这种方式。它既不能产生足够多的、值得去使用异步方式进行输出的数据,速度也不够快——ili9341 支持很高的SPI波特率,至少你可以每秒传输 60 帧以上的每像素 18bit 的全屏画面(320x240x18,共 1382400 bit ,也就是 172800 字节。每秒钟 60 帧意味着波特率约为 82 MHz)而 stm32f103 的 SPI 波特率一般只能到 36 MHz(SPI1 运行于 APB2,假设主频 72 MHz,APB2 也是 72 MHz)。直接使用写外设寄存器来通讯的方式已经足够。

### 使用 StdPeriph 库实现的 STM32F103 驱动 ILI9341。个人建议还是去用 STM32CubeMX 吧,HAL 库挺好用的。
```
#include<inttypes.h>
#include<stm32f10x.h>
#include<system_stm32f10x.h>
#include<stddef.h>

#include"delay.h"
#include"ili9341.h"

#define ILI9341_RCC_APB2Periph_SPIx                        RCC_APB2Periph_SPI1
#define ILI9341_RCC_APB2Periph_GPIOx                RCC_APB2Periph_GPIOA
#define ILI9341_GPIOx                                                GPIOA
#define ILI9341_SPIx                                                SPI1
#define ILI9341_RESET_PIN                                        GPIO_Pin_2
#define ILI9341_DC_PIN                                                GPIO_Pin_3
#define ILI9341_CS_PIN                                                GPIO_Pin_4
#define ILI9341_SCK_PIN                                                GPIO_Pin_5
#define ILI9341_MISO_PIN                                        GPIO_Pin_6
#define ILI9341_MOSI_PIN                                        GPIO_Pin_7
#define ILI9341_LED_PIN                                                GPIO_Pin_8

int ILI9341_XRes;
int ILI9341_YRes;
int ILI9341_bpp;

// 配置外设时钟
static void ILI9341_RCC_Configuration()
{
        RCC_PCLK2Config(RCC_HCLK_Div1);
        RCC_APB2PeriphClockCmd(ILI9341_RCC_APB2Periph_SPIx | ILI9341_RCC_APB2Periph_GPIOx | RCC_APB2Periph_AFIO, ENABLE);
}

// 配置GPIO
static void ILI9341_GPIO_Configuration()
{
        GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
       
        GPIO_InitStructure.GPIO_Pin = ILI9341_SCK_PIN | ILI9341_MOSI_PIN;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_Init(ILI9341_GPIOx, &GPIO_InitStructure);
       
        GPIO_InitStructure.GPIO_Pin = ILI9341_MISO_PIN;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(ILI9341_GPIOx, &GPIO_InitStructure);
       
        GPIO_InitStructure.GPIO_Pin = ILI9341_RESET_PIN | ILI9341_DC_PIN | ILI9341_CS_PIN | ILI9341_LED_PIN;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_Init(ILI9341_GPIOx, &GPIO_InitStructure);
}

// 配置SPI
static void ILI9341_SPI_Configuration()
{
        SPI_InitTypeDef SPI_InitStructure;
       
        SPI_StructInit(&SPI_InitStructure);
        SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
        SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
        SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
        SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
        SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
        SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
        SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
        SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
        SPI_InitStructure.SPI_CRCPolynomial = 7;
        SPI_Init(ILI9341_SPIx, &SPI_InitStructure);
        SPI_Cmd(ILI9341_SPIx, ENABLE);
}

// 使用SPI进行传输
static uint32_t ILI9341_SPIx_Transfer(uint32_t data)
{
        while (SPI_I2S_GetFlagStatus(ILI9341_SPIx, SPI_I2S_FLAG_TXE) == RESET);
        SPI_I2S_SendData(ILI9341_SPIx, data);
        while (SPI_I2S_GetFlagStatus(ILI9341_SPIx, SPI_I2S_FLAG_RXNE) == RESET);
        return SPI_I2S_ReceiveData(ILI9341_SPIx);
}

// 等待SPI忙状态
static void ILI9341_SPIx_Flush()
{
        while(SPI_I2S_GetFlagStatus(ILI9341_SPIx, SPI_I2S_FLAG_BSY) == SET);
}

// SPI控制器切到8bit模式
static void ILI9341_SPIx_8bit()
{
        ILI9341_SPIx_Flush();
        SPI_Cmd(ILI9341_SPIx, DISABLE);
        SPI_DataSizeConfig(ILI9341_SPIx, SPI_DataSize_8b);
        SPI_Cmd(ILI9341_SPIx, ENABLE);
}

// SPI控制器切到16bit模式
static void ILI9341_SPIx_16bit()
{
        ILI9341_SPIx_Flush();
        SPI_Cmd(ILI9341_SPIx, DISABLE);
        SPI_DataSizeConfig(ILI9341_SPIx, SPI_DataSize_16b);
        SPI_Cmd(ILI9341_SPIx, ENABLE);
}

//设置屏幕方向
void ILI9341_SetOrientation(int orient)
{
        uint8_t o;
        switch(orient)
        {
        case 0: // 水平
                o = 0x28;
                ILI9341_XRes = 320;
                ILI9341_YRes = 240;
                break;
        case 1: // 垂直(默认)
                o = 0x48;
                ILI9341_XRes = 240;
                ILI9341_YRes = 320;
                break;
        case 2: // 垂直反向
                o = 0x88;
                ILI9341_XRes = 240;
                ILI9341_YRes = 320;
                break;
        case 3: // 水平反向
                o = 0xE8;
                ILI9341_XRes = 320;
                ILI9341_YRes = 240;
                break;
        default:
                return;
        }
        ILI9341_GPIOx->BRR = ILI9341_CS_PIN | ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x36);
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(o);
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_CS_PIN;
}

// 设置传输的像素格式为RGB565
void ILI9341_Set16bpp()
{
        ILI9341_bpp = 16;
        ILI9341_GPIOx->BRR = ILI9341_CS_PIN | ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x3A);
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x55);
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_CS_PIN;
}

// 设置传输的像素格式为RGB666(实际传输是RGB888,每通道低位会被ILI9341丢弃)
void ILI9341_Set18bpp()
{
        ILI9341_bpp = 18;
        ILI9341_GPIOx->BRR = ILI9341_CS_PIN | ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x3A);
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x66);
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_CS_PIN;
}

// 初始化ILI9341
void ILI9341_Init()
{
        ILI9341_RCC_Configuration();
        ILI9341_GPIO_Configuration();
        ILI9341_SPI_Configuration();
       
        ILI9341_GPIOx->BRR = ILI9341_LED_PIN | ILI9341_RESET_PIN; // 熄灯,重置
        delay_ms(10);
        ILI9341_GPIOx->BSRR = ILI9341_RESET_PIN; // 结束重置,等待初始化
        delay_ms(50);
        ILI9341_GPIOx->BRR = ILI9341_CS_PIN | ILI9341_DC_PIN; // 传输命令
        ILI9341_SPIx_Transfer(0x11); // 退出睡眠
        delay_ms(150);
        ILI9341_SPIx_Transfer(0x29); // 开启显示
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_CS_PIN;
       
        ILI9341_SetOrientation(1); // 设置朝向
        ILI9341_Set16bpp(); // 设置像素格式(默认18bpp)
       
        ILI9341_GPIOx->BSRR = ILI9341_LED_PIN; // 屏幕亮灯
}

// 垂直滚动
void ILI9341_VScroll(int top, int lines, int dest_y)
{
        ILI9341_GPIOx->BRR = ILI9341_CS_PIN | ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x33);
        ILI9341_SPIx_16bit();
        ILI9341_GPIOx->BSRR = ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(top);
        ILI9341_SPIx_Transfer(lines);
        ILI9341_SPIx_Transfer(ILI9341_YRes - top - lines);
        ILI9341_SPIx_8bit();
        ILI9341_GPIOx->BRR = ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x37);
        ILI9341_SPIx_16bit();
        ILI9341_GPIOx->BSRR = ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(dest_y);
        ILI9341_SPIx_8bit();
        ILI9341_GPIOx->BSRR = ILI9341_CS_PIN;
}

// 设置地址窗口
void ILI9341_SetAddress(int x1, int y1, int x2, int y2)
{
        ILI9341_GPIOx->BRR = ILI9341_CS_PIN | ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x2A);
        ILI9341_SPIx_16bit();
        ILI9341_GPIOx->BSRR = ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(x1);
        ILI9341_SPIx_Transfer(x2);
        ILI9341_SPIx_8bit();
        ILI9341_GPIOx->BRR = ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x2B);
        ILI9341_SPIx_16bit();
        ILI9341_GPIOx->BSRR = ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(y1);
        ILI9341_SPIx_Transfer(y2);
        ILI9341_SPIx_8bit();
        ILI9341_GPIOx->BSRR = ILI9341_CS_PIN;
}

// 设置亮度
void ILI9341_SetBrightness(uint8_t brightness)
{
        ILI9341_GPIOx->BRR = ILI9341_CS_PIN | ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x51);
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(brightness);
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_CS_PIN;
}

// 取得亮度
uint8_t ILI9341_GetBrightness()
{
        uint8_t brightness;
        ILI9341_GPIOx->BRR = ILI9341_CS_PIN | ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x52);
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0);
        brightness = ILI9341_SPIx_Transfer(0);
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_CS_PIN;
        return brightness;
}

// 取像素
uint32_t ILI9341_GetPixel(int x, int y)
{
        uint32_t ret = 0;
        if(x < 0 || y < 0 || x >= ILI9341_XRes || y >= ILI9341_YRes) return 0;
       
        ILI9341_SetAddress(x, y, ILI9341_XRes - 1, ILI9341_YRes - 1); // 把x,y当作“起点”
        ILI9341_ReadGRAM(&ret, 1);
        return ret;
}

// 绘制单个像素。慢。
void ILI9341_SetPixel(int x, int y, int r, int g, int b)
{
        if(x < 0 || y < 0 || x >= ILI9341_XRes || y >= ILI9341_YRes) return;
       
        ILI9341_SetAddress(x, y, ILI9341_XRes - 1, ILI9341_YRes - 1); // 把x,y当作“起点”
        ILI9341_WriteGRAMColor(r, g, b, 1);
}

// 读显存
void ILI9341_ReadGRAM(void *ptr, size_t count)
{
        ILI9341_GPIOx->BRR = ILI9341_CS_PIN | ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x2E);
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0); // dummy read
        if(ILI9341_bpp == 16)
        {
                uint16_t *ptr_16 = ptr;
                ILI9341_SPIx_16bit();
                while(count --) *ptr_16 ++ = ILI9341_SPIx_Transfer(0);
                ILI9341_SPIx_8bit();
        }
        else
        {
                uint8_t *ptr_8 = ptr;
                while(count --)
                {
                        *ptr_8 ++ = ILI9341_SPIx_Transfer(0);
                        *ptr_8 ++ = ILI9341_SPIx_Transfer(0);
                        *ptr_8 ++ = ILI9341_SPIx_Transfer(0);
                }
                ILI9341_SPIx_Flush();
        }
        ILI9341_GPIOx->BSRR = ILI9341_CS_PIN;
}

// 写显存
void ILI9341_WriteGRAM(void *ptr, size_t count)
{
        ILI9341_GPIOx->BRR = ILI9341_CS_PIN | ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x2C);
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_DC_PIN;
        if(ILI9341_bpp == 16)
        {
                uint16_t *ptr_16 = ptr;
                ILI9341_SPIx_16bit();
                while(count --) ILI9341_SPIx_Transfer(*ptr_16 ++);
                ILI9341_SPIx_8bit();
        }
        else
        {
                uint16_t *ptr_8 = ptr;
                while(count --)
                {
                        ILI9341_SPIx_Transfer(*ptr_8 ++);
                        ILI9341_SPIx_Transfer(*ptr_8 ++);
                        ILI9341_SPIx_Transfer(*ptr_8 ++);
                }
                ILI9341_SPIx_Flush();
        }
        ILI9341_GPIOx->BSRR = ILI9341_CS_PIN;
}

// 向显存里重复写入纯色
void ILI9341_WriteGRAMColor(int r, int g, int b, size_t count)
{
        ILI9341_GPIOx->BRR = ILI9341_CS_PIN | ILI9341_DC_PIN;
        ILI9341_SPIx_Transfer(0x2C);
        ILI9341_SPIx_Flush();
        ILI9341_GPIOx->BSRR = ILI9341_DC_PIN;
        if(ILI9341_bpp == 16)
        {
                uint16_t color_value =
                        ((r << 8) & 0xF800) |
                        ((g << 3) & 0x07E0) |
                        ((b >> 3) & 0x001F) |
                        0;
                ILI9341_SPIx_16bit();
                while(count --) ILI9341_SPIx_Transfer(color_value);
                ILI9341_SPIx_8bit();
        }
        else
        {
                while(count --)
                {
                        ILI9341_SPIx_Transfer(r);
                        ILI9341_SPIx_Transfer(g);
                        ILI9341_SPIx_Transfer(b);
                }
                ILI9341_SPIx_Flush();
        }
        ILI9341_GPIOx->BSRR = ILI9341_CS_PIN;
}

// 读取像素,注意SPI频率必须不能高于24 MHz
size_t ILI9341_ReadPixels(int x, int y, int w, int h, void *ptr)
{
        size_t count;
        size_t cb;
        if(x < 0)
        {
                w += x;
                if(w < 1) return 0;
                x = 0;
        }
        else if(x >= ILI9341_XRes) return 0;
        if(y < 0)
        {
                h += y;
                if(h < 1) return 0;
                y = 0;
        }
        else if(y >= ILI9341_YRes) return 0;
        if(x + w > ILI9341_XRes) w = ILI9341_XRes - x;
        if(y + h > ILI9341_YRes) h = ILI9341_YRes - y;
       
        count = w * h;
        cb = count * (ILI9341_bpp == 16 ? 2 : 3);
        if(!ptr) return cb;
       
        ILI9341_SetAddress(x, y, x + w - 1, y + h - 1);
        ILI9341_ReadGRAM(ptr, count);
        return cb;
}

// 写像素,SPI频率可以很高
size_t ILI9341_WritePixels(int x, int y, int w, int h, void *ptr)
{
        size_t count;
        size_t cb;
        if(x < 0)
        {
                w += x;
                if(w < 1) return 0;
                x = 0;
        }
        else if(x >= ILI9341_XRes) return 0;
        if(y < 0)
        {
                h += y;
                if(h < 1) return 0;
                y = 0;
        }
        else if(y >= ILI9341_YRes) return 0;
        if(x + w > ILI9341_XRes) w = ILI9341_XRes - x;
        if(y + h > ILI9341_YRes) h = ILI9341_YRes - y;
       
        count = w * h;
        cb = count * (ILI9341_bpp == 16 ? 2 : 3);
        if(!ptr) return cb;
       
        ILI9341_SetAddress(x, y, x + w - 1, y + h - 1);
        ILI9341_WriteGRAM(ptr, count);
        return cb;
}

// 填充矩形区域,给两个对角坐标
void ILI9341_FillRect(int x1, int y1, int x2, int y2, int r, int g, int b)
{
        if(x2 < x1) {int t = x1; x1 = x2; x2 = t;}
        if(y2 < y1) {int t = y1; y1 = y2; y2 = t;}
        if(x1 < 0) x1 = 0; else if(x1 > ILI9341_XRes - 1) x1 = ILI9341_XRes - 1;
        if(x2 < 0) x2 = 0; else if(x2 > ILI9341_XRes - 1) x2 = ILI9341_XRes - 1;
        if(y1 < 0) y1 = 0; else if(y1 > ILI9341_YRes - 1) y1 = ILI9341_YRes - 1;
        if(y2 < 0) y2 = 0; else if(y2 > ILI9341_YRes - 1) y2 = ILI9341_YRes - 1;
       
        ILI9341_SetAddress(x1, y1, x2, y2);
        ILI9341_WriteGRAMColor(r, g, b, (x2 + 1 - x1) * (y2 + 1 - y1));
}

// 填充矩形区域,给左上角坐标和矩形宽高
void ILI9341_FillRect2(int x, int y, int w, int h, int r, int g, int b)
{
        if(x < 0)
        {
                w += x;
                if(w < 1) return;
                x = 0;
        }
        else if(x >= ILI9341_XRes) return;
        if(y < 0)
        {
                h += y;
                if(h < 1) return;
                y = 0;
        }
        else if(y >= ILI9341_YRes) return;
        if(x + w > ILI9341_XRes) w = ILI9341_XRes - x;
        if(y + h > ILI9341_YRes) h = ILI9341_YRes - y;
       
        ILI9341_SetAddress(x, y, x + w - 1, y + h - 1);
        ILI9341_WriteGRAMColor(r, g, b, w * h);
}
```
事实上,`ILI9341_GetPixel()` 和 `ILI9341_ReadPixels()` 经过实际测试并不能正确运行。不知道是因为什么的原因。我试着将 SPI 的波特率降低到 0.5 MHz都不行。它的表现是:当你把手指放在MISO线的金属部分的时候,读到的就全是0xFF;而当你不去触摸 MISO 线的时候,它的表现甚至犹如 CSPRNG 一样“优秀”。估计和我买的这款屏幕本身的组装设计有关,因为 ILI9341 本身并不是只能用SPI进行传输的,它其实支持不止一种协议。

### 编辑于 2025 年 6 月 14 日

我现在知道为什么 `ILI9341_GetPixel()` 和 `ILI9341_ReadPixels()` 在当时不能正确运行了。杜邦线太长,有 15 CM,信号一个来回要走 30 CM,太长了。SPI 的传输是时钟走一下的同时,这边 MOSI 出数据,时钟信号走到那边后,那边 MISO 出数据,然后数据再回到这边,这个过程是要耗时的,此时读回来的数据信号还在导线里面,还没进到芯片里。

按照 36 MHz 的波特率,一个时钟周期里,那边的数据来不及传输过来。电流在导线内的传播速度差不多就是电磁波在导线作为媒介内的传播速度,如果我当时用的杜邦线是铜线的话,电阻小,电流传播速度慢,只有 _大概三分之二的真空光速的速度_ 。

Si515 发表于 2018-11-20 18:16:17

我发现,帖子怎么好像都是你发的?

0xAA55 发表于 2018-11-21 02:58:37

Si515 发表于 2018-11-20 18:16
我发现,帖子怎么好像都是你发的?

别人发的也有
页: [1]
查看完整版本: 【单片机】用stm32f103驱动ili9341