以下内容为对正点原子的 S T M 32 F 103 STM32F103 STM32F103精英板的 F S M C FSMC FSMC模块驱动 L C D LCD LCD屏幕例程的学习。做一个记录来加强对模块的认知。
F S M C FSMC FSMC的全称是 F l e x i b l e s t a t i c m e m o r y c o n t r o l l e r Flexible\quad static\quad memory\quad controller Flexiblestaticmemorycontroller,翻译成中文就是灵活的静态存储器控制器,这样来看的话 F S M C FSMC FSMC模块无法支持 D R A M DRAM DRAM,因为 D R A M DRAM DRAM属于动态存储器,它需要刷新电路不断的刷新来维持存储的数据,而 S R A M SRAM SRAM不需要刷新电路,只需要芯片有供电就可以维持存储的数据。但是我看 S T M 32 H 750 STM32H750 STM32H750的 F M C , F l e x i b l e m e m o r y c o n t r o l l e r FMC,Flexible\quad memory\quad controller FMC,Flexiblememorycontroller,注意这里没有了 S , S t a t i c S,Static S,Static,模块加入了对 S D R A M SDRAM SDRAM(同步 D R A M DRAM DRAM)的支持。如图1所示,同时 S T M 32 H 750 STM32H750 STM32H750的 F M C FMC FMC模块也删除了对 P C _ C A R D PC\_CARD PC_CARD的支持,可能是因为这玩意已经过时了吧, S T M 32 F 103 STM32F103 STM32F103的 F S M C FSMC FSMC模块是支持 P C _ C A R D PC\_CARD PC_CARD的。 F S M C FSMC FSMC支持的存储器类型如下所示:
N O R F L A S H NOR\quad FLASH NORFLASH和 N A N D F L A S H NAND\quad FLASH NANDFLASH在断电之后存储的内容不会丢失。 S R A M SRAM SRAM 和 P S R A M PSRAM PSRAM在断电之后存储的内容会丢失。 N O R F L A S H NOR\quad FLASH NORFLASH和 N A N D F L A S H NAND\quad FLASH NANDFLASH一般在写入之前首先要对要写入的位置所在的存储块进行擦除的操作,也就是将所有的比特存储位变为二进制值1, S R A M SRAM SRAM 和 P S R A M PSRAM PSRAM不需要。由于 N A N D F L A S H NAND\quad FLASH NANDFLASH的特性决定, N A N D F L A S H NAND\quad FLASH NANDFLASH不支持 X I P , E x c e c u t e I n P l a c e XIP,Excecute\quad In\quad Place XIP,ExcecuteInPlace,功能, N O R F L A S H NOR\quad FLASH NORFLASH支持 X I P XIP XIP功能。
我这里测试的时候用的是如图2所示的 3.5 3.5 3.5英寸的屏幕,这里我们要知道这里的屏幕不单单只是一块玻璃而已,而是一整个模组。一个基本的模组包含了液晶面板和显示驱动芯片(我这里的屏幕模组的显示驱动芯片的型号是 N T 35310 NT35310 NT35310),如图3所示。 M C U MCU MCU,比如 S T M 32 F 103 STM32F103 STM32F103,通过 F S M C FSMC FSMC模块将数据通过8080接口(当然显示驱动芯片一般支持多种接口类型,可以根据需要进行选择)发送给显示驱动芯片之后,显示驱动芯片一般再通过 R G B RGB RGB接口将接收到的数据显示到屏幕上。但是为什么 S T M 32 F 103 STM32F103 STM32F103的 F S M C FSMC FSMC模块可以用来驱动 L C D LCD LCD屏幕?如果你去看 S T M 32 F 103 STM32F103 STM32F103的 F S M C FSMC FSMC模块用户手册的话,它也没有提到它可以驱动 L C D LCD LCD屏幕,也没有8080接口相关的关键字。
图4是驱动芯片 N T 35310 NT35310 NT35310的用户手册里面的8080接口的时序图,左边是写的,右边是读的。8080接口主要的信号线如下:
图5是 S T M 32 F 103 STM32F103 STM32F103的 F S M C FSMC FSMC模块的模式 A A A的读和写的时序,图5中的地址信号线 A [ 25 : 0 ] A[25:0] A[25:0]和高低字节选择信号 N B L [ 1 : 0 ] NBL[1:0] NBL[1:0]这里我们可以不看,在驱动 L C D LCD LCD屏幕的时候用不到。图5中 N E x NEx NEx是片选信号, N O E NOE NOE是读使能信号, N W E NWE NWE是写使能信号, D [ 15 : 0 ] D[15:0] D[15:0]是数据线。对比一下图4和图5我们就可以发现 N T 35310 NT35310 NT35310的8080接口的读写时序和 S T M 32 F 103 STM32F103 STM32F103的 F S M C FSMC FSMC模块的模式 A A A的读和写的时序基本差不多,这就是我们可以用 S T M 32 F 103 STM32F103 STM32F103的 F S M C FSMC FSMC模块的模式 A A A来和 N T 35310 NT35310 NT35310的8080接口进行通信来驱动 L C D LCD LCD屏幕的原因。由于在驱动 L C D LCD LCD屏幕的时候需要有一根信号线来告诉显示驱动芯片现在发送的是命令还是数据或参数,这里在实际测试的时候采用的方法是用 F S M C FSMC FSMC模块的一根地址线来告诉显示驱动芯片现在发送的是命令还是数据或参数,这里选择的是地址信号线 A 10 A10 A10,地址信号线从0开始排序,因此在初始化 F S M C FSMC FSMC模块的地址的 G P I O GPIO GPIO引脚的时候也只初始化了一个引脚,但是因为测试的时候采用的是16 b i t bit bit数据通信,因此期望在地址信号线 A 10 A10 A10的 G P I O GPIO GPIO引脚上输出高低电平的时候需要在地址的第11位上取值1或0,而不是在第10位上,具体原因请见 S T M 32 F 103 STM32F103 STM32F103的 F S M C FSMC FSMC模块的文档说明,如图6所示。
目前也有一些专门的模块来驱动 L C D LCD LCD屏幕,比如 S T M 32 H 750 STM32H750 STM32H750的 L T D C LTDC LTDC模块,如图6所示。这种模块驱动 L C D LCD LCD屏幕的时候, L C D LCD LCD屏幕模组应该就不需要显示驱动芯片了。
说到这里我也有一个疑问显示驱动芯片 N T 35310 NT35310 NT35310是支持多种接口的,如图7所示,那我们在选择某一种接口的时候是否需要进行某种软件或硬件配置,对于这一点我还不是很明确。图8似乎是说明了有 I M 0 IM0 IM0, I M 1 IM1 IM1和 I M 2 IM2 IM2三个引脚用来选择使用哪一种接口,不知是否正确。还有就是图8里面的 s e r i a l i n t e r f a c e serial\quad interface serialinterface不知道是不是就是图7里面说的 S e r i a l P e r i p h e r a l I n t e r f a c e , S P I Serial\quad Peripheral \quad Interface,SPI SerialPeripheralInterface,SPI接口,还有就是它这里的描述的 S P I SPI SPI接口和我们常见的四个引脚( C S CS CS:片选, C L K CLK CLK:时钟, M O S I MOSI MOSI, M I S O MISO MISO)的 S P I SPI SPI接口好像又有一点差异。有知道的朋友可以帮忙明确一下。
好了下面来看代码,正点原子的代码是支持多种显示存储芯片的,为了代码的简洁,我把它改成只是基于 N T 35310 NT35310 NT35310的测试代码,其它不先关的我全部删除了,因为原理差不多。代码最开始肯定是 F S M C FSMC FSMC模块和 L C D LCD LCD屏幕模组的初始化, F S M C FSMC FSMC模块初始化了16根数据线,一根地址线来告诉显示驱动芯片发送的是命令还是数据或参数。读使能信号线,写使能信号线,片选信号线。由于液晶本身是不发光的,所以需要有一个背光灯提供光源,因此还需要一个 G P I O GPIO GPIO口来控制光源的开和关。下面来看一下 F S M C FSMC FSMC模块的参数配置:
readWriteTiming.FSMC_AddressSetupTime = 15;
readWriteTiming.FSMC_AddressHoldTime = 0;
readWriteTiming.FSMC_DataSetupTime = 11;
readWriteTiming.FSMC_BusTurnAroundDuration = 0;
readWriteTiming.FSMC_CLKDivision = 0;
readWriteTiming.FSMC_DataLatency = 0;
readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;
writeTiming.FSMC_AddressSetupTime =1;
writeTiming.FSMC_AddressHoldTime =0;
writeTiming.FSMC_DataSetupTime =1;
writeTiming.FSMC_BusTurnAroundDuration = 0;
writeTiming.FSMC_CLKDivision = 0;
writeTiming.FSMC_DataLatency =0;
writeTiming.FSMC_AccessMode = FSMC_AccessMode_A;
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_SRAM;
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable;
FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Enable;
FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming;
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &writeTiming;
至于 F S M C FSMC FSMC模块的读写时序参数的配置,我们这里只需要关注 F S M C _ A d d r e s s S e t u p T i m e FSMC\_AddressSetupTime FSMC_AddressSetupTime, F S M C _ D a t a S e t u p T i m e FSMC\_DataSetupTime FSMC_DataSetupTime这连个参数就可以了,其它的配置为0就可以了。对于这两个参数分别可以简单的认为是读写使能信号的高电平持续时间和读写使能信号的低电平持续时间,如图10和图11中红圈中标示的位置所示。这里的 H C L K HCLK HCLK为72 M H Z MHZ MHZ,周期大概为14 n s ns ns, F S M C FSMC FSMC模块配置为不对 H C L K HCLK HCLK进行分频。这里的时序要求只规定了最小值,只要配置的值超过这个最小值应该就可以,写时序的高低电平最小值是19 n s ns ns因此我们配置为2个 H C L K HCLK HCLK周期就可以了,因此 F S M C _ A d d r e s s S e t u p T i m e FSMC\_AddressSetupTime FSMC_AddressSetupTime和 F S M C _ D a t a S e t u p T i m e FSMC\_DataSetupTime FSMC_DataSetupTime都配置的为1。对于读时序,读使能的低电平最低要求时间是150 n s ns ns,因此读时序的 F S M C _ D a t a S e t u p T i m e FSMC\_DataSetupTime FSMC_DataSetupTime参数配置为11, 12 × 14 = 168 12\times14=168 12×14=168。对于读时序,读使能的高电平最低要求时间是250 n s ns ns,因此读时序的 F S M C _ A d d r e s s S e t u p T i m e FSMC\_AddressSetupTime FSMC_AddressSetupTime参数应该配置为17, 18 × 14 = 252 18\times14=252 18×14=252。但是 F S M C _ A d d r e s s S e t u p T i m e FSMC\_AddressSetupTime FSMC_AddressSetupTime最高能配置到15,因此这里配置为15。我们前面也说过了 F S M C _ A d d r e s s S e t u p T i m e FSMC\_AddressSetupTime FSMC_AddressSetupTime和 F S M C _ D a t a S e t u p T i m e FSMC\_DataSetupTime FSMC_DataSetupTime这两个参数只是大概的对应于读写使能信号的高电平持续时间和读写使能信号的低电平持续时间,因此实际的读时序的 F S M C _ A d d r e s s S e t u p T i m e FSMC\_AddressSetupTime FSMC_AddressSetupTime时间还远没有到250 n s ns ns,所以这里也不用纠结。
F S M C FSMC FSMC模块初始化完成之后就是 L C D LCD LCD模块初始化了,这里的代码是厂家提供的,但是我看 N T 35310 NT35310 NT35310的用户手册上没有,不知道出处在哪里。这里有一个地方要注意的是虽然这里的屏幕模组已经在硬件上改为16比特接口,但是这里不代表一个像素点就是用16比特表示的,至于一个像素点的格式是有命令配置的。如图10和图11所示,可以配置为16比特或18bit。初始化代码里面配置的是16比特。对于16根数据线的8080接口,如果像素比特是16比特的话,一个像素可以一次发完,但是如果像素比特是18比特的话,两个像素一共要3次发送才能完成。但是我看正点原子的程序里面对于16根数据线的8080接口,如果像素比特是16比特的话,读两个像素一共要3次读取才能完成,这我也是很迷惑。
int main(void)
{
u8 color_index=0;
u8 id1=0;
u8 id2=0;
u8 id3=0;
u8 id4=0;
delay_init();
uart_init(115200);
FEMC_LCD_Init();
LCD_WR_REG(0xD4);
id1=LCD_RD_DATA();
id2=LCD_RD_DATA();
id3=LCD_RD_DATA();
id4=LCD_RD_DATA();
printf("id1=%x,id2=%x,id3=%x,id4=%x.\r\n",id1,id2,id3,id4);
while(1)
{
switch(color_index)
{
case 0:LCD_Clear(WHITE);point_color=BLACK;break;
case 1:LCD_Clear(BLACK);point_color=BLUE;break;
case 2:LCD_Clear(BLUE);point_color=RED;break;
case 3:LCD_Clear(RED);point_color=MAGENTA;break;
case 4:LCD_Clear(MAGENTA);point_color=GREEN;break;
case 5:LCD_Clear(GREEN);point_color=CYAN;break;
case 6:LCD_Clear(CYAN);point_color=YELLOW;break;
case 7:LCD_Clear(YELLOW);point_color=BRRED;break;
case 8:LCD_Clear(BRRED);point_color=GRAY;break;
case 9:LCD_Clear(GRAY);point_color=LGRAY;break;
case 10:LCD_Clear(LGRAY);point_color=BROWN;break;
case 11:LCD_Clear(BROWN);point_color=WHITE;break;
}
LCD_ShowString(30,40,"This is FEMC test for LCD.");
LCD_ShowString(30,70,"LCD Display driver IC is NT35310.");
LCD_ShowString(30,90,"2022.11.22");
color_index++;
if(color_index==12)
color_index=0;
delay_ms(1000);
}
}
好了我们最后来看一下主函数,流程比较简单, F S M C FSMC FSMC模块和 L C D LCD LCD模块初始化完成之后就去读一下显示驱动芯片的 I D ID ID,然后循环不断地首先将屏幕刷新为一种颜色,然后用另一种颜色显示3行字符串。这里比较重要的是如何去构建字符串的字模,所谓的字模就是在一个点阵上要保证那些点为1,那些点为0,才能显示出特定的字符。这里使用的软件是 P C t o L C D 2002 PCtoLCD2002 PCtoLCD2002,这里我测试的时候采用是是 12 × 12 12\times12 12×12的点阵,但是似乎对于英文字符自动的变为 12 × 6 12\times6 12×6的点阵了。这里我们在配置的时候选择阴码(亮点为1,即对应的点阵的该点的比特位位1,暗点为0,即对应的点阵的该点的比特位位0),选择 C 51 C51 C51格式。取模方式表示最后在生成点阵的16进制数组的时候遍历点阵的顺序,点阵的一个点表示一个比特位,这里的逐列式的遍历顺序是从上到下,从左到右。顺向表示每遍历8个比特位,第一个遍历的比特位为这个构成的字节数据的最高位。
图15中字符 " A " "A" "A"的点阵字模放大之后如图16所示,按照逐列式的比特位读取顺序,从左到右,首先读取第一列的点阵数据,白色的方格表示比特位0,绿色的方格表示比特位1,。从上到下读取每一列的数据,这里需要注意的是如果每一列的点的个数不是8的倍速的话,在最后要补上0。因此这里的 12 × 12 12\times12 12×12的点阵实际是 12 × 6 12\times6 12×6的点阵的每一列的最后都要补上四个0,因此该点阵的第一列读取到的两个字节的点阵数据为 0 x 00 , 0 x 40 0x00,0x40 0x00,0x40,因此整个点阵的数据字节数为 6 × 2 = 8 6\times2=8 6×2=8。有了点阵数据之后在 L C D LCD LCD屏幕上显示对应的字符就很简单了,只是点阵中的点的显示顺序要和构成字模的16进制数据的顺序一样,比如我们之前在构建字模16进制数据的时候的顺序为从上到下,从左到右的每一列构建的。那么我们在显示的时候每读取连个字节的数据就是相当于一列的点阵数据,比特位1表示亮点,比特位0表示暗点,高位优先。
我的工程在这里。