在线时间234 小时
UID3301905
注册时间2017-1-8
NXP金币112
TA的每日心情 | 开心 2018-4-20 15:04 |
---|
签到天数: 8 天 [LV.3]偶尔看看II
金牌会员
- 积分
- 2953
- 最后登录
- 2023-7-24
|
【飞凌RT1052】基于ELCDIF_RGB接口与硬件LPSPI1接口的双液晶屏异显实验
双屏异显,即两个显示屏显示不同内容,在实际项目中应用非常多,比如一些工业控制系统会用到一个0.96寸的OLED+一个大尺寸带触摸的液晶彩屏,OLED用于显示简单的参数如温湿度等,液晶彩屏显示复杂的图形曲线并加上触摸控制的功能。
本文涉及到飞凌板子两大硬件外设:ELCDIF接口与LPSPI接口,用于驱动一个4.3寸SPI总线液晶彩屏和一个4.3寸RGB接口的液晶屏。
先讲讲SPI液晶彩屏。SPI液晶屏的接口接的是飞凌1052板子LPSPI1接口,即音频接口上方的10针接口,需要用1mm间距排针转2.54mm间距的排针转换:
然后是RGB液晶彩屏,这个彩屏不能直接接到飞凌板子的RGB液晶接口,因为线序不同。好在有大佬提供了不带触摸总线的转接板,可以成功点亮RGB液晶屏:
先讲讲SPI总线的驱动,在NXP官方提供的IMXRT1050的SDK以及飞凌提供的SDK中都没有LPSPI1总线阻塞收发的例程,只有一个基于LPSPI1+LPSPI3的,带FIFO和中断的主从机通信例程:
还有个FLEXSPI的例程,这个例程是直接读写板上QSPI NOR FLASH的,我们个人开发者不建议也绝对不能使用FLEXSPI总线直接读写板上的QSPI NOR FLASH,因为有些未经确认的读写操作都有可能会导致QSPI NOR FLASH中系统BOOT信息的缺失或出错,严重点甚至会导致板子无法继续烧写裸机程序,除了烧录回官方BOOT固件到QSPI NOR FLASH以外没有别的补救措施,此处也建议飞凌厂商,给BOOT用的QSPI NOR FLASH加上写保护措施:
此处要调通LPSPI1的收发指令,RT1052的SPI分为阻塞和非阻塞两种收发方式,在日常使用中,单片机充当SPI主机,驱动SPI从机设备的方式,大多采用阻塞收发,只有在极少数追求高实时性的双MCU SPI通信或者操作系统中使用非阻塞收发,我个人的猜测是跟操作系统的时间片轮转调度有关,由于我只开发裸机程序,因此不需要了解非阻塞收发。需要参考FLEXSPI的例程中的SPI阻塞收发函数:
status_t flexspi_nor_write_enable(FLEXSPI_Type *base, uint32_t baseAddr)
{
flexspi_transfer_t flashXfer;
status_t status;
/* Write neable */
flashXfer.deviceAddress = baseAddr;
flashXfer.port = kFLEXSPI_PortA1;
flashXfer.cmdType = kFLEXSPI_Command;
flashXfer.SeqNumber = 1;
flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_WRITEENABLE;
status = FLEXSPI_TransferBlocking(base, &flashXfer);
return status;
}
将之改为LPSPI1的阻塞收发函数即可,只需要换个设备号:
unsigned char LPSPI1_ReadWriteByte(unsigned char data)
{
unsigned char spirxdata=0;
lpspi_transfer_t spi_tranxfer;
spi_tranxfer.configFlags=kLPSPI_MasterPcs0|kLPSPI_MasterPcsContinuous;
spi_tranxfer.txData=&data;
spi_tranxfer.rxData=&spirxdata;
spi_tranxfer.dataSize=1;
LPSPI_MasterTransferBlocking(LPSPI1,&spi_tranxfer);
return spirxdata;
}
configFlags设置为硬件片选设置,如果不使用LPSPI1接口自带的片选引脚,而用普通IO来片选的话,设置此行参数无任何影响。
在LPSPI1接口启用之前需初始化相关引脚和LPSPI1外设参数:
void LPSPI1_Init(int baudrate)
{
int lpspiclk=0;
CLOCK_SetMux(kCLOCK_LpspiMux,1);
CLOCK_SetDiv(kCLOCK_LpspiDiv,5);
//IOMUXC_SetPinMux(IOMUXC_GPIO_SD_B0_01_LPSPI1_PCS0,0);
IOMUXC_SetPinMux(IOMUXC_GPIO_SD_B0_00_LPSPI1_SCK,0);
IOMUXC_SetPinMux(IOMUXC_GPIO_SD_B0_02_LPSPI1_SDO,0);
//IOMUXC_SetPinMux(IOMUXC_GPIO_SD_B0_03_LPSPI1_SDI,0);
//IOMUXC_SetPinConfig(IOMUXC_GPIO_SD_B0_01_LPSPI1_PCS0,0x10B0);
IOMUXC_SetPinConfig(IOMUXC_GPIO_SD_B0_00_LPSPI1_SCK,0x10B0);
IOMUXC_SetPinConfig(IOMUXC_GPIO_SD_B0_02_LPSPI1_SDO,0x10B0);
//IOMUXC_SetPinConfig(IOMUXC_GPIO_SD_B0_03_LPSPI1_SDI,0x10B0);
lpspiclk=(CLOCK_GetFreq(kCLOCK_Usb1PllPfd0Clk)/(6));
//LPSPI1时钟
lpspi1_config.baudRate=baudrate*1000000;
//SPI速度
//lpspi1_config.whichPcs=kLPSPI_Pcs0;
//片选信号,PCS0
//lpspi1_config.pcsActiveHighOrLow=kLPSPI_PcsActiveLow;
//片选信号低电平有效
lpspi1_config.bitsPerFrame=8;
//设置SPI的数据大小:SPI发送接收8位帧结构
lpspi1_config.cpol=kLPSPI_ClockPolarityActiveHigh;
//串行同步时钟低电平有效
lpspi1_config.cpha=kLPSPI_ClockPhaseSecondEdge;
//串行同步时钟的第二个跳变沿(上升或下降)数据被采样
lpspi1_config.direction=kLPSPI_MsbFirst;
//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
lpspi1_config.pinCfg=kLPSPI_SdiInSdoOut;
//SDI输入引脚,SDO输出引脚
lpspi1_config.dataOutConfig=kLpspiDataOutRetained;
//输出数据保留
lpspi1_config.pcsToSckDelayInNanoSec=10;
//片选拉低到时钟有效之间的延时时间,单位ns
lpspi1_config.lastSckToPcsDelayInNanoSec=10;
//最后一个时钟到片选拉高之间的延时,单位ns
lpspi1_config.betweenTransferDelayInNanoSec=10;
//两次传输之间的延时,单位ns
LPSPI_MasterInit(LPSPI1,&lpspi1_config,lpspiclk);
LPSPI_Enable(LPSPI1,true);
}
然后接上逻辑分析仪,就可以判断LPSPI时序是否符合从机要求,若不符合则按从机要求修改,这里我的SPI液晶彩屏的通信时序如下:
由图可知,SPI液晶彩屏CLK时钟脚闲置时要拉高,CLK上升沿进行数据传输,数据位从高到低。
然后是RGB液晶屏的驱动,这里我参考的是SDK里面的ELCDIF-RGB例程:
看看这效果,真心不错:
你们猜猜这是用什么方法实现的?没错,一整张屏幕的缓存一起刷的,完全看不出卡顿,1052就是牛逼。
先说说硬件接口,飞凌板子的液晶接口是RGB565,也就是16位色,这是考点,大家先记着,不要管它,往下看...
在RT1052中,ELCDIF的RGB总线占用的显存空间,是从0x80200000地址开始,也就是外部SDRAM内存颗粒的其中一部分内存空间,也就是说,直接往这部分内存空间写数据,那就是直接操作RGB液晶屏的显存,于是乎,官方提供了一个初始化函数,用于搭建系统内存空间到用户内存空间的映射桥梁:
const elcdif_rgb_mode_config_t config2 =
{
.panelWidth = APP_IMG_WIDTH,
.panelHeight = APP_IMG_HEIGHT,
.hsw = APP_HSW,
.hfp = APP_HFP,
.hbp = APP_HBP,
.vsw = APP_VSW,
.vfp = APP_VFP,
.vbp = APP_VBP,
.polarityFlags = APP_POL_FLAGS,
.bufferAddr = (uint32_t)buffer,
.pixelFormat = kELCDIF_PixelFormatXRGB8888,
.dataBus = kELCDIF_DataBus24Bit,
};
ELCDIF_RgbModeInit(APP_ELCDIF, &config2);
.bufferAddr参数就是用户自己开辟的内存空间的地址,此处我们可以直接定义一个数组:
uint32_t buffer[272][480];
搭建映射关系:
...
.bufferAddr = (uint32_t)buffer,
...
完事了,直接
buffer[100][100]=0xffff0000;
那就是往屏幕的(100,100)坐标位置描一个红点,都不怎么需要加延时,多简单,是不是???NXP的东西就是这样,读起来难读,但是一旦读懂了SDK,用起来就跟手头的工具一样好用。
问题来了,刚刚不是说到,飞凌板子的接口只有16位色嘛,但是看看这:
.pixelFormat = kELCDIF_PixelFormatXRGB8888,
在官方给出的色彩数据格式中,有五种:
typedef enum _elcdif_pixel_format
{
kELCDIF_PixelFormatRAW8 = 0, /*!< RAW 8 bit, four data use 32 bits. */
kELCDIF_PixelFormatRGB565 = 1, /*!< RGB565, two pixel use 32 bits. */
kELCDIF_PixelFormatRGB666 = 2, /*!< RGB666 unpacked, one pixel uses 32 bits, high byte unused,
upper 2 bits of other bytes unused. */
kELCDIF_PixelFormatXRGB8888 = 3, /*!< XRGB8888 unpacked, one pixel uses 32 bits, high byte unused. */
kELCDIF_PixelFormatRGB888 = 4, /*!< RGB888 packed, one pixel uses 24 bits. */
} elcdif_pixel_format_t;
在官方给出的数据位数格式中,有四种:
typedef enum _elcdif_lcd_data_bus
{
kELCDIF_DataBus8Bit = LCDIF_CTRL_LCD_DATABUS_WIDTH(1), /*!< 8-bit data bus. */
kELCDIF_DataBus16Bit = LCDIF_CTRL_LCD_DATABUS_WIDTH(0), /*!< 16-bit data bus, support RGB565. */
kELCDIF_DataBus18Bit = LCDIF_CTRL_LCD_DATABUS_WIDTH(2), /*!< 18-bit data bus, support RGB666. */
kELCDIF_DataBus24Bit = LCDIF_CTRL_LCD_DATABUS_WIDTH(3), /*!< 24-bit data bus, support RGB888. */
} elcdif_lcd_data_bus_t;
既然接口只有16位长度,那么软件配置也应保持一致,将色彩格式改成16位即kELCDIF_PixelFormatRGB565,将buffer的数据类型改为short,画点函数也做了相应改变之后,可以直接使用16位数据格式传输色彩数据。而使用XRGB8888 32位格式,就会造成极大的资源浪费。
...
short buffer[272][480];
...
.bufferAddr = (uint32_t)buffer,
.pixelFormat = kELCDIF_PixelFormatRGB565,
.dataBus = kELCDIF_DataBus16Bit,
...
最后是有关RAM空间的分配问题,因为使用ELCDIF外设本身需要占用外部RAM空间,并且内部RAM的资源不足以支撑程序运行,因此scf文件需要作出相应改变,不然内存不够用,导致编译器报错:
既然两个彩屏都调通了,那不妨做个非常简单的小DEMO:
工程.zip
(898.38 KB, 下载次数: 100)
|
|