[MM32硬件] 【灵动微电子MM32F0121测评】6、SPI主模式实验-驱动ILI9341

[复制链接]
 楼主| sujingliang 发表于 2025-6-15 14:22 | 显示全部楼层 |阅读模式
本文的目的:配置MM32F0121为SPI主模式,并成功SPI方式驱动ILI9341显示屏

相关例程可参见:
LibSamples_MM32F0120_V1.13.4\Samples\LibSamples\SPI

如果使用polling方式可参考SPI_Master_FLASH_Polling例程,如何使用DMA方式可参考SPI_Master_FLASH_DMA_Polling

5.png
因为MM32F0121只有一个SPI接口,并且已分配给板载的flash,所以需要考虑不能和flash冲突。可以将SPI1复用为其他管脚或SPI1_NSS与flash的使用管脚不同。

本例SPI1_NSS使用其他管脚。


1、SPI配置

  1. //CS:PA0  DC:PA1 RESET:PA2 BK:PA3
  2. //PA4:SPI1_NSS;PA5:SPI1_SCK;PA6:SPI1_MISO;PA7:SPI1_MOSI

  3. void SPI_Configure(void)
  4. {
  5.     GPIO_InitTypeDef GPIO_InitStruct;
  6.     SPI_InitTypeDef  SPI_InitStruct;

  7.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

  8.     SPI_StructInit(&SPI_InitStruct);
  9.     SPI_InitStruct.SPI_Mode      = SPI_Mode_Master;
  10.     SPI_InitStruct.SPI_DataSize  = SPI_DataSize_8b;
  11.     SPI_InitStruct.SPI_DataWidth = 8;
  12.     SPI_InitStruct.SPI_CPOL      = SPI_CPOL_Low;
  13.     SPI_InitStruct.SPI_CPHA      = SPI_CPHA_1Edge;
  14.     SPI_InitStruct.SPI_NSS       = SPI_NSS_Soft;
  15.     SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
  16.     SPI_InitStruct.SPI_FirstBit  = SPI_FirstBit_MSB;
  17.     SPI_Init(SPI1, &SPI_InitStruct);

  18.     SPI_BiDirectionalLineConfig(SPI1, SPI_Enable_RX);
  19.     SPI_BiDirectionalLineConfig(SPI1, SPI_Enable_TX);

  20.     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

  21.    // GPIO_PinAFConfig(GPIOA, GPIO_PinSource4,  GPIO_AF_0);
  22.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource5,  GPIO_AF_0);
  23.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource6,  GPIO_AF_0);
  24.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource7,  GPIO_AF_0);

  25.     GPIO_StructInit(&GPIO_InitStruct);
  26.     GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_5 | GPIO_Pin_7;
  27.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
  28.     GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
  29.     GPIO_Init(GPIOA, &GPIO_InitStruct);

  30.     GPIO_StructInit(&GPIO_InitStruct);
  31.     GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_6;
  32.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
  33.     GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_IPU;
  34.     GPIO_Init(GPIOA, &GPIO_InitStruct);

  35.     SPI_Cmd(SPI1, ENABLE);
  36. }
6.png

根据手册SPI最大速率可达36Mbps,没有计算,简单地取SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
CS引脚需软件控制,为了和flash的CS不同配置为PA0

2、ILI9341非SPI引脚配置
CS:PA0  DC:PA1 RESET:PA2 BK:PA3
  1. void LCD_GPIO_Configure(void)
  2. {

  3.         GPIO_InitTypeDef GPIO_InitStruct;

  4.         RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

  5.         GPIO_StructInit(&GPIO_InitStruct);
  6.         GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
  7.         GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
  8.         GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_Out_PP;
  9.         GPIO_Init(GPIOA, &GPIO_InitStruct);

  10. }


3、SPI polling方式发送数据函数
在向LCD发送少量SPI数据时,使用polling方式,如LCD配置,输出字符串
  1. void SPI_TxData_Polling(uint8_t *Buffer, uint8_t Length)
  2. {
  3.     uint8_t i = 0, Data = 0;

  4.     for (i = 0; i < Length; i++)
  5.     {
  6.         //SPI_SendData(SPI1, Buffer[i]);
  7.                                 SPI_SendByte(SPI1, Buffer[i]);

  8.         while (RESET == SPI_GetFlagStatus(SPI1, SPI_FLAG_TXEPT))
  9.         {
  10.         }

  11.         while (RESET == SPI_GetFlagStatus(SPI1, SPI_FLAG_RXAVL))
  12.         {
  13.         }

  14.         Data = SPI_ReceiveData(SPI1);
  15.     }
  16. }
4、SPI DMA方式发送数据函数
在向LCD发送大量SPI数据时,用DMA方式,如整个屏幕清屏,由于MM32F0121的RAM较小,在使用DMA时申请的内存有限,导致每次DMA传送的数据较少,影响显示速度的提高
  1. void SPI_TxData_DMA_Polling(uint8_t *Buffer, uint8_t Length)
  2. {
  3.     DMA_InitTypeDef  DMA_InitStruct;

  4.     uint8_t DataCache = 0;

  5.     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA, ENABLE);

  6.     DMA_DeInit(DMA_Channel2);
  7.     DMA_DeInit(DMA_Channel3);

  8.     DMA_StructInit(&DMA_InitStruct);
  9.     DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(SPI1->RXREG);
  10.     DMA_InitStruct.DMA_MemoryBaseAddr     = (uint32_t)&DataCache;
  11.     DMA_InitStruct.DMA_DIR                = DMA_DIR_PeripheralSRC;
  12.     DMA_InitStruct.DMA_BufferSize         = Length;
  13.     DMA_InitStruct.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
  14.     DMA_InitStruct.DMA_MemoryInc          = DMA_MemoryInc_Disable;
  15.     DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  16.     DMA_InitStruct.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;
  17.     DMA_InitStruct.DMA_Mode               = DMA_Mode_Normal;
  18.     DMA_InitStruct.DMA_Priority           = DMA_Priority_VeryHigh;
  19.     DMA_InitStruct.DMA_M2M                = DMA_M2M_Disable;
  20.     DMA_InitStruct.DMA_Auto_Reload        = DMA_Auto_Reload_Disable;
  21.     DMA_Init(DMA_Channel2, &DMA_InitStruct);

  22.     DMA_StructInit(&DMA_InitStruct);
  23.     DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(SPI1->TXREG);
  24.     DMA_InitStruct.DMA_MemoryBaseAddr     = (uint32_t)Buffer;
  25.     DMA_InitStruct.DMA_DIR                = DMA_DIR_PeripheralDST;
  26.     DMA_InitStruct.DMA_BufferSize         = Length;
  27.     DMA_InitStruct.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
  28.     DMA_InitStruct.DMA_MemoryInc          = DMA_MemoryInc_Enable;
  29.     DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  30.     DMA_InitStruct.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;
  31.     DMA_InitStruct.DMA_Mode               = DMA_Mode_Normal;
  32.     DMA_InitStruct.DMA_Priority           = DMA_Priority_VeryHigh;
  33.     DMA_InitStruct.DMA_M2M                = DMA_M2M_Disable;
  34.     DMA_InitStruct.DMA_Auto_Reload        = DMA_Auto_Reload_Disable;
  35.     DMA_Init(DMA_Channel3, &DMA_InitStruct);

  36.     DMA_Cmd(DMA_Channel2, ENABLE);
  37.     DMA_Cmd(DMA_Channel3, ENABLE);

  38.     SPI_DMACmd(SPI1, ENABLE);

  39.     while (RESET == DMA_GetFlagStatus(DMA_FLAG_TC2))
  40.     {
  41.     }

  42.     while (RESET == DMA_GetFlagStatus(DMA_FLAG_TC3))
  43.     {
  44.     }

  45.     SPI_DMACmd(SPI1, DISABLE);

  46.     DMA_Cmd(DMA_Channel2, DISABLE);
  47.     DMA_Cmd(DMA_Channel3, DISABLE);
  48. }
5、ILI9341驱动部分

1)GPIO读写定义
  1. #define LCD_CS          GPIOA,GPIO_Pin_0                        //
  2. #define LCD_DC          GPIOA,GPIO_Pin_1                        //
  3. #define LCD_RST          GPIOA,GPIO_Pin_2                        //
  4. #define LCD_LED          GPIOA,GPIO_Pin_3                        //

  5. #define LCD_CS_SET()                 GPIO_WriteBit(LCD_CS, Bit_SET);
  6. #define LCD_CS_RESET()         GPIO_WriteBit(LCD_CS, Bit_RESET);

  7. #define LCD_DC_SET()                 GPIO_WriteBit(LCD_DC, Bit_SET);
  8. #define LCD_DC_RESET()         GPIO_WriteBit(LCD_DC, Bit_RESET);

  9. #define LCD_RST_SET()         GPIO_WriteBit(LCD_RST, Bit_SET);
  10. #define LCD_RST_RESET() GPIO_WriteBit(LCD_RST, Bit_RESET);

  11. #define LCD_LED_SET()         GPIO_WriteBit(LCD_LED, Bit_SET);
  12. #define LCD_LED_RESET() GPIO_WriteBit(LCD_LED, Bit_RESET);
2)写指令函数
  1. void LCD_WriteIndex(uint8_t index)
  2. {
  3.         LCD_CS_RESET();
  4.         LCD_DC_RESET();
  5.         SPI_TxData_Polling(&index,1);
  6.         LCD_CS_SET();
  7. }
3)写数据函数
  1. void LCD_WriteData(uint8_t *Buffer, uint8_t Length)
  2. {
  3.         LCD_CS_RESET();
  4.         LCD_DC_SET();
  5.         SPI_TxData_Polling(Buffer,Length);
  6.         LCD_CS_SET();
  7. }
4)还可以写发送8位数据或16位数据函数,便于其他函数调用
  1. void LCD_SendData8(uint8_t dat)
  2. {
  3.         SPI_TxData_Polling(&dat,1);
  4. }

  5. void LCD_SendData16(uint16_t dat)
  6. {
  7.         LCD_SendData8(dat>>8);
  8.         LCD_SendData8(dat);
  9. }
5)写命令然后接着写不定长数据函数
  1. void ili9341_SendData(uint8_t cmd, uint8_t *data, uint16_t length)
  2. {
  3.         LCD_WriteIndex(cmd);
  4.         LCD_WriteData(data,length);
  5. }


6)ILI9341初始化
定义一个数据结构,用来传送LCD初始化参数
  1. typedef struct {
  2.         uint8_t cmd;
  3.         uint8_t dat[16];
  4.         uint16_t datLen;
  5.         uint32_t sleep;
  6. } ili9341_ini_str_t;


定义LCD初始化参数
  1. static ili9341_ini_str_t lcd_ini_str[] = {
  2.     // ILI9341 初始化序列
  3.     {0xCF, {0x00, 0x83, 0x30}, 3, 0},      // Power Control B
  4.     {0xED, {0x64, 0x03, 0x12, 0x81}, 4, 0}, // Power On Sequence Control
  5.     {0xE8, {0x85, 0x01, 0x79}, 3, 0},      // Driver Timing Control A
  6.     {0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02}, 5, 0}, // Power Control A
  7.     {0xF7, {0x20}, 1, 0},                  // Pump Ratio Control
  8.     {0xEA, {0x00, 0x00}, 2, 0},            // Driver Timing Control B
  9.     {0xC0, {0x26}, 1, 0},                  // Power Control 1 (VRH[5:0])
  10.     {0xC1, {0x11}, 1, 0},                  // Power Control 2 (SAP[2:0], BT[3:0])
  11.     {0xC5, {0x35, 0x3E}, 2, 0},            // VCOM Control 1
  12.     {0xC7, {0xBE}, 1, 0},                  // VCOM Control 2
  13.     {0x36, {0xc8}, 1, 0},                  // Memory Access Control (MX, MY, RGB mode)
  14.     {0x3A, {0x55}, 1, 0},                  // Pixel Format (16-bit/pixel)
  15.     {0xB1, {0x00, 0x18}, 2, 0},            // Frame Rate Control (Normal mode, 70Hz)
  16.     {0xB6, {0x0A, 0xA2}, 2, 0},            // Display Function Control
  17.     {0xF6, {0x01, 0x30}, 2, 0},            // Interface Control
  18.     {0xF2, {0x00}, 1, 0},                  // 3Gamma Function Disable
  19.     {0x26, {0x01}, 1, 0},                  // Gamma Set (Gamma curve 1)
  20.     {0xE0, {0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00}, 15, 0}, // Positive Gamma Correction
  21.     {0xE1, {0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F}, 15, 0}, // Negative Gamma Correction
  22.     {0x11, {0x00}, 0, 120},                // Sleep Out (延迟120ms)
  23.     {0x29, {0x00}, 0, 50},                 // Display On (延迟50ms)
  24.     {0x00, {0x00}, 0, 0}                   // End Of List
  25. };
发送初始化参数:
  1. void ili9341_SendInitStr()
  2. {
  3.         uint16_t i = 0;
  4.         LCD_CS_RESET();
  5.        
  6.         while(lcd_ini_str[i].cmd != 0x00)
  7.         {
  8.                         uint8_t cmd = lcd_ini_str[i].cmd;
  9.                         uint16_t datLen = lcd_ini_str[i].datLen;
  10.                         uint8_t *dat;
  11.                         uint32_t slp = lcd_ini_str[i].sleep;

  12.                         dat = &(lcd_ini_str[i].dat[0]);
  13.                        
  14.                         LCD_DC_RESET();
  15.                         SPI_TxData_Polling(&cmd, 1);

  16.                         if(datLen > 0)
  17.                         {
  18.                                         LCD_DC_SET();
  19.                                         SPI_TxData_Polling(dat, datLen);
  20.                         }
  21.                         if(slp > 0)
  22.                         {
  23.                                         PLATFORM_DelayMS(slp);
  24.                         }
  25.                         i++;
  26.         }
  27.         LCD_CS_SET();
  28. }
7)ili9341_SetWindow
  1. void ili9341_SetWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
  2. {
  3.         /* CASET */
  4.         uint8_t cmd = 0x2A;
  5.         uint8_t buf4[4];
  6.        
  7.         buf4[0] = (x >> 8) & 0xFF;
  8.         buf4[1] = x & 0xFF;
  9.         buf4[2] = ((x + w -1) >> 8) & 0xFF;
  10.         buf4[3] = (x + w -1) & 0xFF;
  11.         ili9341_SendData(cmd, buf4, 4);

  12.         /* RASET */
  13.         cmd = 0x2B;
  14.         buf4[0] = (y >> 8) & 0xFF;
  15.         buf4[1] = y & 0xFF;
  16.         buf4[2] = ((y + h - 1) >> 8) & 0xFF;
  17.         buf4[3] = (y + h - 1) & 0xFF;
  18.         ili9341_SendData(cmd, buf4, 4);
  19.        

  20.         LCD_WriteIndex(0x2C);
  21. }
8)画点
  1. void setPixel(uint16_t Color)
  2. {
  3.         LCD_CS_RESET();
  4.         LCD_DC_SET();
  5.         LCD_SendData16(Color);
  6.         LCD_CS_SET();
  7. }
9)输出字符串
  1. void GUI_Write16CnChar(uint16_t x, uint16_t y, const uint8_t *cn, uint16_t wordColor, uint16_t backColor)
  2. {
  3.     uint8_t i, j, wordNum, wordByte;
  4.     uint8_t color;
  5.     uint16_t currentX = x;
  6.     uint16_t currentY = y;
  7.    
  8.     while (*cn != '\0')
  9.     {
  10.         // Handle display boundaries and wrapping
  11.         if (currentX >= X_MAX_PIXEL)
  12.         {
  13.             currentX = 0;
  14.             currentY += 16;
  15.         }
  16.         if (currentY >= Y_MAX_PIXEL)
  17.         {
  18.             currentX = 0;
  19.             currentY = 0;
  20.             GUI_Clear(backColor);
  21.         }
  22.         
  23.         // Handle ASCII characters (8x16)
  24.         if (*cn < 128)
  25.         {
  26.             // Check for newline characters
  27.             if (*cn == '\r' || *cn == '\n')
  28.             {
  29.                 return;
  30.             }
  31.             
  32.             wordNum = *cn - 32;
  33.             ili9341_SetWindow(currentX, currentY, 8,  16);

  34.             for (wordByte = 0; wordByte < 16; wordByte++)
  35.             {
  36.                 color = Font_Data[wordNum].dat[wordByte];
  37.                 for (j = 0; j < 8; j++)
  38.                 {
  39.                     setPixel((color & 0x80) ? wordColor : backColor);
  40.                     color <<= 1;
  41.                 }
  42.             }
  43.             
  44.             cn++;
  45.             currentX += 8;
  46.         }
  47.         // Handle Chinese characters (16x16)
  48.         else
  49.         {
  50.             ili9341_SetWindow(currentX, currentY,  16,  16);
  51.             
  52.             // Search for the character in the font database
  53.             for (wordNum = 0; wordNum <(sizeof(CnChar16x16) / sizeof(CnChar16x16[0])); wordNum++)
  54.             {
  55.                 if (CnChar16x16[wordNum].Index[0] == cn[0] &&
  56.                     CnChar16x16[wordNum].Index[1] == cn[1])
  57.                 {
  58.                     // Draw the character
  59.                     for (i = 0; i < 32; i++)
  60.                     {
  61.                         color = CnChar16x16[wordNum].Msk[i];
  62.                         for (j = 0; j < 8; j++)
  63.                         {
  64.                             setPixel((color & 0x80) ? wordColor : backColor);
  65.                             color <<= 1;
  66.                         }
  67.                     }
  68.                     break; // Exit loop once character is found
  69.                 }
  70.             }
  71.             
  72.             cn += 2;
  73.             currentX += 16;
  74.         }
  75.     }
  76. }
10)测试一下

  1. void SPI_Master_LCD_Sample(void)
  2. {
  3.        
  4.         char buf[16];
  5.        
  6.                 SPI_Configure();
  7.        
  8.                 //配置RTC
  9.                 RTC_Configure();
  10.                 ili9341_Init();

  11.                 GUI_Clear(COLOR_Blue);
  12.                 GUI_Write16CnChar(10,16,"灵动微F0121开发板",COLOR_White,COLOR_Blue);
  13.                 GUI_WriteASCII8x16(10,40,"Hello 21ic",COLOR_White,COLOR_Blue);
  14.                 GUI_WriteASCII8x16(10,56,"Hello MM32F0121",COLOR_White,COLOR_Blue);
  15.                
  16.           while (1)
  17.     {       
  18.                                 sprintf(buf," -= %02d:%02d:%02d =- ",RTC_Calendar.hour,RTC_Calendar.minute,RTC_Calendar.second);
  19.         GUI_WriteASCII8x16(10,80,buf,COLOR_White,COLOR_Blue);
  20.     }
  21. }


tutieshi_640x357_4s (1).gif

雨果喝水 发表于 2025-6-30 22:16 | 显示全部楼层
其 SPI 接口可通过合理配置实现对 ILI9341 TFT-LCD 的高效驱动。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

84

主题

146

帖子

3

粉丝
快速回复 在线客服 返回列表 返回顶部