- This topic has 5 个回复, 4 个参与人, and was last updated 5 years, 4 months 前 by solo2pm.
-
作者帖子
-
七月 17, 2019 - 2:36 下午 #1831123参与者
前言
难得的机会,手边刚好有VEGA的开发板和Arduino的LCD模块,尝试了一下点亮LCD屏幕,后来又接着实现了一些功能,想和大家分享一下。
一开始只是想着通过SPI协议初始化LCD屏幕,然后发个字符串就好了,后来又尝试着显示图片,优化了一下读取速度,修修改改的也都成功了,开心!把这些过程写下来,欢迎讨论,如有不足之处也希望大家不吝指教。准备工作
- 焊接织女星开发板J1,J2,J3,J4的双排母座,以便与LCD屏通信。前段时间免费申请的织女星开发板出厂是没有焊接这些模块的,所以要自己焊一下。
- 参照《织女星开发板快速入门指南》把“hello world”示例在Eclipse IDE中跑一遍。拿到一个新的东西总是要先把最基本的摸索尝试一下嘛。
- 网上查看LCD模块的相关资料, 网址贴在这里了:http://www.waveshare.net/wiki/2.8inch_TFT_Touch_Shield .
代码分析和修改添加
SPI基本概念
一般情况下支持SPI的设备都会有四根通信线,分别是SIN(数据输入),SOUT(数据输出),SCK(时 钟),CS(片选)。其中CS是控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),对此芯片的操作才有效。
通讯是通过数据交换完成的, SPI是串行通讯协议,数据是一位一位的传输的。由SCK提供时钟脉冲,SIN,SOUT则基于此脉冲完成数据传输。数据输出通过SOUT线,数据在时钟上沿或下沿时改变,在紧接着的下沿或上沿被读取,完成一位数据传输,输入也使用同样原理。这样,在至少8次时钟信号的改变(上沿和下沿为一次),就可以完成8位数据的传输。
查看LCD模块的原理图得知CN2的3,4,5,6引脚分别对应着CS(片选信号),MOSI(主设备输出,从设备输入),MISO(主设备输入,从设备输出),CKL(时钟),如图1 所示。在本示例中,主设备是织女星开发板,从设备是LCD屏,所以MOSI对应着SOUT,MISO对应着SIN。暂时不考虑MISO的情形,因为目前要实现的功能只是用织女星开发板驱动LCD屏并打印,并不需要MISO的功能。除此之外,还有BL(backlight)和DC(command)信号线分别对应着CN2的2号和CN3的8号引脚。
由物理连接可知LCD的CN2的2到6号引脚对应着织女星开发板J2的4,6,8,10,12号引脚,LCD的CN3的8号引脚对应着织女星开发板的J1的16号引脚。查看织女星开发板原理图《VEGA_Lite_SCH》文件,可知J2的4,6,8,10,12号引脚分别连接到PTB3, PTB6, PTB5, PTB7, PTB4,J1的16号引脚连接到PTB1。如图2所示。
表1总结了织女星开发板和LCD模块的部分连接关系。
表1 PIN脚和端子对应表
信号名称 端子号 信号值 时钟CKL 12(J2) PTB4 片选CS 6(J2) PTB6 数据输出SOUT 8(J2) PTB5 数据输入SIN 10(J2) PTB7 背光BL 4(J2) PTB3 数据命令DC 16(J1) PTB1 初始化相关代码
在BOARD_InitPins()函数中根据以上分析,初始化相应的pin脚,具体实现如下:
void BOARD_InitPins(void) {
/* Clock Gate Control: 0x01u */
CLOCK_EnableClock(kCLOCK_PortB);
/* PORTB4 is configured as LPSPI0_SCK */
PORT_SetPinMux(PORTB, PIN4_IDX, kPORT_MuxAlt2);
/* PORTB5 is configured as LPSPI0_SOUT */
PORT_SetPinMux(PORTB, PIN5_IDX, kPORT_MuxAlt2);
/* PORTB6 is configured as LPSPI0_PCS2 */
PORT_SetPinMux(PORTB, PIN6_IDX, kPORT_MuxAlt2);
/* PORTB7 is configured as LPSPI0_SIN */
// PORT_SetPinMux(PORTB, PIN7_IDX, kPORT_MuxAlt2);
/* PORTB1 is configured as GPIO */
PORT_SetPinMux(PORTB, PIN1_IDX, kPORT_MuxAsGpio);
/* PORTB3 is configured as GPIO */
PORT_SetPinMux(PORTB, PIN3_IDX, kPORT_MuxAsGpio);
}由于只需要输出,暂时不考虑输入的问题,所以代码中把设置输入信号的一行注释掉了。Pin1和pin3设置为GPIO,需要初始化设置一下,在main函数中的语句如下:
gpio_pin_config_t spi_config = {
kGPIO_DigitalOutput, 0,
};
GPIO_PinInit(GPIOB, 1u, &spi_config);
GPIO_PinInit(GPIOB, 3u, &spi_config);配置LCD模块
以上,关于PIN脚的配置就完成了,接下来是关于LCD和SPI 的设置。直接调用SDK中的CLOCK_SetIpSrc函数为SPI设置时钟源和获取主时钟源。
/*Set clock source for LPSPI and get master clock source*/
CLOCK_SetIpSrc(kCLOCK_Lpspi0, kCLOCK_IpSrcFircAsync);在LCD初始化函数中主要做了两件事,分别是硬件初始化和写寄存器。
硬件初始化就是配置SPI相关参数,具体实现如下:static uint8_t lcd_hardware_init(void)
{
lpspi_master_config_t masterConfig;
uint32_t srcClock_Hz;
/* SPI init */
LPSPI_MasterGetDefaultConfig(&masterConfig);
/*********Master config*********/
masterConfig.baudRate = 24000000;
/* 这里LPSPI_TRANSFER_SIZE的值在宏定义中设置为1 */
masterConfig.bitsPerFrame = 8*LPSPI_TRANSFER_SIZE;
masterConfig.cpol = kLPSPI_ClockPolarityActiveHigh;
masterConfig.cpha = kLPSPI_ClockPhaseFirstEdge;
masterConfig.direction = kLPSPI_MsbFirst;
masterConfig.pcsToSckDelayInNanoSec = 1000000000 / masterConfig.baudRate;
masterConfig.lastSckToPcsDelayInNanoSec = 1000000000 / masterConfig.baudRate;
masterConfig.betweenTransferDelayInNanoSec = 1000000000 / masterConfig.baudRate;
/* 设置pcs2为片选信号 */
masterConfig.whichPcs = kLPSPI_Pcs2;
masterConfig.pcsActiveHighOrLow = kLPSPI_PcsActiveLow;
masterConfig.pinCfg = kLPSPI_SINInSOUTOut;
masterConfig.dataOutConfig = kLpspiDataOutRetained;
srcClock_Hz = CLOCK_GetIpFreq(kCLOCK_Lpspi0);
LPSPI_MasterInit(LCD_SPI, &masterConfig, srcClock_Hz);
return true;
}通过写寄存器来实现LCD模块中的各类电信号,代码如下:
void lcd_write_reg(uint8_t reg, uint8_t val)
{
lcd_write_byte(reg, LCD_CMD);
lcd_write_byte(val, LCD_DATA);
}传入两个参数分别是lcd的目标寄存器reg和要写入的数据内容val,一次写入一个字长的数据,分别由两个字节写入函数来实现。在宏定义中将LCD_CMD定义为0,将LCD_DATA定义为1,分别代表了命令(command)和数据(data)。当传输数据的时候将DC信号置1,当确定寄存器的时候将DC信号清零。
通过SPI协议将8个比特的数据写入LCD的代码如下:static void LCD_BYTE_WRITE(uint8_t data){
lpspi_transfer_t masterXfer;
masterXfer.txData = &data;
masterXfer.rxData = NULL;
masterXfer.dataSize = 1;
masterXfer.configFlags = BOARD_LPSPI_PCS_FOR_TRANSFER | kLPSPI_MasterPcsContinuous ;
LPSPI_MasterTransferBlocking(LCD_SPI, &masterXfer);
while (LPSPI_GetStatusFlags(LCD_SPI) & kLPSPI_ModuleBusyFlag) {}
}当发送数据的内容,大小等信息配置完成后,直接调用了SDK中的LPSPI_MasterTransferBlocking(LPSPI_Type *base, lpspi_transfer_t *transfer)函数来实现数据块的输出。这个函数大概150行的样子,就不贴在这里了,在SDK中的fsl_lpspi.c文件中可以查看。此函数使用Polling的方法传输数据,所以会一直等待所有的数据传送完毕。传入的两个参数分别为基本SPI外设地址和传输数据指针,返回值为传输状态。
初始化完成之后就可以在LCD屏幕上显示想要的东西了。lcd_clear_screen(uint16_t color)函数将整个屏幕刷新为指定的颜色,lcd_display_string(uint16_t xpos, uint16_t ypos, const uint8_t *string, uint8_t size, uint16_t color)函数实现在屏幕上的制定位置显示字符串。其中参数color 表示颜色,xpos和ypos分别为字符长起始位置的横坐标和纵坐标,string为字符串指针,size为字体格式,在这个例子中只能选择12或者16这两种,如果把size参数设置为其他数值,将无法实现打印。
七月 17, 2019 - 2:56 下午 #1832123参与者添加显示图片代码
前面提到的那个Arduino LCD模块的网页里有图片提取软件,先利用这个工具把图片转换成16位的彩色数据,生成一个大数组的C文件,把这个数组复制到 lcd_fonts文件中,再在主函数中调用就可以了。
根据屏幕刷新的部分改写了个显示图片的代码:
void lcd_showimage(const unsigned char *p)
{
uint32_t i, cnt = 0;
uint16_t data;
uint8_t d1,d2;
cnt = LCD_WIDTH * LCD_HEIGHT ;
lcd_set_cursor(0, 0);
lcd_write_byte(0x22, LCD_CMD);
LCD_DC_SET();
for (i = 0; i < cnt; i ++)
{
d1 = *p++;
d2 = *p++;
data = (uint16_t)d1+((uint16_t)d2<<8);
LCD_WORD_WRITE(data);
}
}
展示一下最终的成果:七月 17, 2019 - 3:15 下午 #1833123参与者代码的补充和优化
优化
以单个像素点来读取数据可能会有点慢,一次读取多个像素点能大大提高读取速度,但也有一些情况就是需要一次只读取一个像素点,所以我定义了一个宏HIGH_LEVEL_OPTIMIZE,来控制选择是否一次读取多个像素点:
#if HIGH_LEVEL_OPTIMIZE
static uint16_t LCD_MULTI_WORD_WRITE(uint16_t* data, uint16_t len)
{
uint8_t *transfer = (uint8_t*)malloc(len);
lpspi_transfer_t masterXfer;
if(transfer){
uint8_t exist = len % 8; //copy 8 Byte one time
uint32_t i = 0;
for(;i<(len - exist);){ transfer[i++] = (*(data)) & 0xff; transfer[i++] = ((*(data++)) & 0xff00) >> 8;
transfer[i++] = (*(data)) & 0xff;
transfer[i++] = ((*(data++)) & 0xff00) >> 8;
transfer[i++] = (*(data)) & 0xff;
transfer[i++] = ((*(data++)) & 0xff00) >> 8;
transfer[i++] = (*(data)) & 0xff;
transfer[i++] = ((*(data++)) & 0xff00) >> 8;
}
for(int j=0;j<exist/2;j++){ transfer[i++] = (*(data)) & 0xff; transfer[i++] = ((*(data++)) & 0xff00) >> 8;
}
masterXfer.txData = transfer;
masterXfer.rxData = NULL;
masterXfer.dataSize = len;
masterXfer.configFlags = BOARD_LPSPI_PCS_FOR_TRANSFER | kLPSPI_MasterPcsContinuous;
LPSPI_MasterTransferBlocking(LCD_SPI, &masterXfer);
free(transfer);
transfer = NULL;
}
else{
free(transfer);
transfer = NULL;
return -1;
}
return *data;
}
#endif修改lcd_clear_screen(uint16_t color)函数,代码如下:
void lcd_clear_screen(uint16_t color)
{
uint32_t i, cnt = 0;
cnt = LCD_WIDTH * LCD_HEIGHT;
lcd_set_cursor(0, 0);
lcd_write_byte(0x22, LCD_CMD);
LCD_DC_SET();
#if HIGH_LEVEL_OPTIMIZE
const uint32_t nPixelSet = 32; //send n pixels one time
uint16_t temp[nPixelSet];
uint16_t color2send = INVERSE_MSB(color); //MSB first, need to convert the data
for(int i=0;i<nPixelSet;){
temp[i++] = color2send;
temp[i++] = color2send;
temp[i++] = color2send;
temp[i++] = color2send;
}
/*** Send 64 Bytes one time, but the lcd will accept one 16-bit as a pixel,
so the len will be the len of the (uint16), need to sizeof(temp)/sizeof(uint16_t);***/
for (i = 0; i < cnt; i += nPixelSet)
{
LCD_MULTI_WORD_WRITE(temp, sizeof(temp));
}
#else
/**** send 2 bytes one time***/
for (i = 0; i < cnt; i ++)
{
LCD_WORD_WRITE(color);
}
#endif
}显示图片部分的代码也是通过类似的方法来优化。
补充
一楼显示的配置SPI的代码看起来有点乱,我想修改的时候已经不能编辑了,在这里重新写一遍:
static uint8_t lcd_hardware_init(void)
{
lpspi_master_config_t masterConfig;
uint32_t srcClock_Hz;
/* SPI init */
LPSPI_MasterGetDefaultConfig(&masterConfig);
/*********Master config*********/
masterConfig.baudRate = 24000000;
/* 这里LPSPI_TRANSFER_SIZE的值在宏定义中设置为1 */
masterConfig.bitsPerFrame = 8*LPSPI_TRANSFER_SIZE;
masterConfig.cpol = kLPSPI_ClockPolarityActiveHigh;
masterConfig.cpha = kLPSPI_ClockPhaseFirstEdge;
masterConfig.direction = kLPSPI_MsbFirst;
masterConfig.pcsToSckDelayInNanoSec = 1000000000 / masterConfig.baudRate;
masterConfig.lastSckToPcsDelayInNanoSec = 1000000000 / masterConfig.baudRate;
masterConfig.betweenTransferDelayInNanoSec = 1000000000 / masterConfig.baudRate;
/* 设置pcs2为片选信号 */
masterConfig.whichPcs = kLPSPI_Pcs2;
masterConfig.pcsActiveHighOrLow = kLPSPI_PcsActiveLow;
masterConfig.pinCfg = kLPSPI_SdiInSdoOut;
masterConfig.dataOutConfig = kLpspiDataOutRetained;
srcClock_Hz = CLOCK_GetIpFreq(kCLOCK_Lpspi0);
LPSPI_MasterInit(LCD_SPI, &masterConfig, srcClock_Hz);
return true;
}七月 17, 2019 - 4:17 下午 #1836七月 19, 2019 - 9:37 上午 #1841west_wind参与者学习了。
-
作者帖子
- 抱歉,回复话题必需登录。