织女星开发板RISC-V核通过SPI协议驱动ARDUINO LCD模块(显示)

前言

第一次写这个博客,算是新手吧,刚好有这个机会,手边有VEGA的开发板和Arduino的LCD模块,做了点小东西,想和大家分享一下。
一开始只是想着通过SPI协议初始化LCD屏幕,然后发个字符串就好了,后来又尝试着显示图片,磕磕碰碰,修修改改居然也成功了,开心!把这些过程写下来,欢迎讨论,如有不足之处也希望大家不吝指教。

准备工作

  1. 焊接织女星开发板J1,J2,J3,J4的双排母座,以便与LCD屏通信。前段时间免费申请的织女星开发板出厂是没有焊接这些模块的,所以要自己焊一下。
  2. 参照《织女星开发板快速入门指南》把“hello world”示例在Eclipse IDE中跑一遍。拿到一个新的东西总是要先把最基本的摸索尝试一下嘛。
  3. 网上查看LCD模块的相关资料, 网址 贴在这里了:http://www.waveshare.net/wiki/2.8inch_TFT_Touch_Shield。

代码分析和添加

一般情况下支持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号引脚。

Alt图1 LCD模块引脚

由物理连接可知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所示。
图2 织女星开发板J1,J2端子
图 2 织女星开发板J1,J2端子

表1总结了织女星开发板和LCD模块的部分连接关系。
表1 PIN脚和端子对应表

信号名称端子号信号值
时钟CKL12(J2)PTB4
片选CS6(J2)PTB6
数据输出SOUT8(J2)PTB5
数据输入SIN10(J2)PTB7
背光BL4(J2)PTB3
数据命令DC16(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);

以上,关于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参数设置为其他数值,将无法实现打印。
60x60
后来又打了个彩色的
在这里插入图片描述
然后又觉得不如再改改代码,打个图片看看,这个步骤摸索的时间稍微长了一点,还自己P了个图,嘿嘿~
前面提到的那个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);
    }
}

最后展示一下最终的成果:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值