我买了一个CH32V103R_NUCLEO,这个板子看起来真漂亮,黑金色。
此外,我拥有我制作的屏幕模块。 我设计了一个 Arduino 标准引脚。 事实证明,它工作良好,可以准确地安装在CH32V103R_NUCLEO板上。
此屏由ST7789驱动,具有三线9位SPI接口。 也就是说,这个屏幕没有一个叫做D/C的引脚来区分SPI数据是reg还是command。 为此,我们需要在 SPI 发送 8 位数据之前使用单个位。 告诉屏幕接下来的 8 位是 reg 还是指令。
首先,我根据现有数据(在STM32平台上,使用IO模拟SPI驱动)移植到CH32V103。 我必须感谢制造商在底层驱动程序上所做的工作。 我的移植很简单,屏幕显示成功。 通过一个小型的逻辑分析仪,我们可以看到一个成功的9位SPI时序。在每一个上升沿采样MOSI引脚的电平。
这一段是程序中用来对屏幕进行初始化所发送的数据,
...... /* Memory Data Access Control */ LCD_WR_REG(0x36); LCD_WR_DATA(0x00); /* RGB 5-6-5-bit */ LCD_WR_REG(0x3A); LCD_WR_DATA(0x55); ......
到目前为止,一切都还很顺利。
由于Arduino引脚中是包含SPI接口的,我在设计屏幕背板的时候特意将相应的引脚放在特定的位置,通过观察CH32V103R_NUCLEO的原理图,我们可以看到制造商也将SPI2的引脚放在相应的位置,这也就意味着我能够使用硬件SPI来驱动我的屏幕,这也许意味着我能够拥有更快的刷屏速度(实话说,模拟SPI在屏幕切换时的效果太缓慢了)。
于是我做了一些工作来完成我的想法,我从开发板资料包中EVT>EXAM>SPI>1Lines_half-duplex工程中复制了一些代码作为移植的基础。
首先对SPI初始化代码进行修改,示例程序中没有任何和SPI2的初始化相关,我根据头文件中的一些宏定义,修改了一些项目
void SPI2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15|GPIO_Pin_13|GPIO_Pin_14; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //SPI2相关的引脚复用 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init( GPIOB, &GPIO_InitStructure ); LCD_CTRL->BSHR=12; RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE ); //时钟使能 SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //时钟上升沿锁定电平? SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //设置速度慢一些用于调试 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init( SPI2, &SPI_InitStructure ); //SPI2的初始化 SPI_Cmd( SPI2, ENABLE ); //SPI2使能 }
(这些修改应该是没有问题的,因为在后续的验证过程中,当我去掉所添加的D/C发送过程后,SPI便能够顺利的发送所有的reg与command)
SPI2的发送函数如下,这段代码从网络上相关的讨论中获得,判断了一个标志位以及添加超时策略。
void SPIv_WriteData(u8 Data) { unsigned int retry=0; while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE)==RESET) { retry++; if(retry>1000) { printf("timeout!\r\n"); break; } } SPI_I2S_SendData(SPI2,Data); retry=0; }
接下来我开始尝试在SPI发送前添加一个bit来当作D/C位,发送程序如下
void LCD_WR_REG(u8 data) { LCD_CS_CLR; IO_SW(0); //用于切换SCLK和MOSI引脚的工作模式 SPI_MOSI_CLR; //发送REG时D/C为0,发送COMMAND时D/C为1 SPI_SCLK_CLR; Delay_Us(2); SPI_SCLK_SET; IO_SW(1); //用于切换SCLK和MOSI引脚的工作模式 SPIv_WriteData(data); LCD_CS_SET; }
上述的工作模式切换部分是我通过在一些论坛搜索时得到的提示,在直接操作SCLK和MOSI引脚之前将其初始化为out_PP,在操作完成之后将其恢复AF_PP。这个函数如下所示
void IO_SW(uint8_t flag) { GPIO_InitTypeDef iosw; iosw.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_15; (flag==0)?(iosw.GPIO_Mode = GPIO_Mode_Out_PP): (iosw.GPIO_Mode = GPIO_Mode_AF_PP); iosw.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &iosw); }
至此,我感觉移植工作应该已经完成了,从我个人有限的知识来看,我的工作完成的很不错,很周到,但是事实却很糟糕,我的屏幕没有显示任何东西,通过逻辑分析仪我能够看到,我好像离成功已经非常近了(下方红字表示正确的),存在一些问题,我单独操作的MOSI电平似乎延后到了硬件SPI的发送动作中,此外还有操作MOSI不成功的情况,但是现在我却不知道如何进行下一步了,我需要开始寻求大家的帮助。
我也曾试着在发送函数的不同位置添加延时,但那看起来似乎没有起作用。
希望有一些朋友有相关经验的能够一起讨论