|
了解如何使用EFM8的USB功能将图像从PC传输到LCD。
所需的硬件/软件 ● SLSTK2000A EFM8评估板 ● Simplicity Studio集成开发环境 ● SCILAB
项目概况 在之前的文章中,我们探索了EFM8的SPI功能实现,与LCD模块的通信方式,格式化和打印10×8像素字符,以及使用VCPXpress库在EFM8和SciLab之间建立USB连接。本篇文章将这些功能结合在一起,实现高效方便地在LCD上显示一张128×128像素的图像。首先从任何标准灰度的.bmp图像文件开始,然后使用Scilab处理它,通过USB将其传输到EFM8微控制器,以便我们可以在128×128像素的LCD上显示它。该项目仅可以处理单张图像,但此处介绍的技术可以很容易地适用于显示由一系列相似图像组成的简单动画。
首先使用Paint.NET或其他图像编辑应用程序创建一张图像。该图像被加载到Scilab中,处理成与LCD兼容的格式,转换成像素数据矩阵,并通过64字节USB数据包传输到EFM8控制器。然后使用更新的SPI状态机将该像素数据(一次四行)传输到LCD模块。
端口I / O 端口I / O配置与我们在上一篇文章中使用的相同。
SPI信号映射到相应的端口引脚,芯片选择信号除外,我们通过P0.1手动驱动。我们不需要直接配置USB数据线的端口引脚;所有的USB外设初始化都是通过VCPXpress库完成的。
外设和中断 外设和中断设置与我们在上一篇文章中使用的相同:SPI配置为与LCD模块通信,Timer4用于短延迟。我们不在这个项目中使用Timer2,因为我们不需要设置帧速率。不同的是,当从PC接收像素数据包时,LCD被依次更新。
固件 此项目的VCP配置与我们在上一篇文章中使用的配置相同。但是还有一些新增的USB功能:上一篇文章中EFM8只接收来自Scilab的数据,而现在EFM8也传输数据。 - void USBTxByte(unsigned char BytetoSend)
- {
- Block_Write(&BytetoSend, 1, &USBBytesTransmitted);
- }
复制代码
正如上面的Block_Write()函数的名称所暗示的那样,VCPXpress库能够通过一个函数调用来传输字节数组。然而,在这个项目中,来自EFM8的USB传输仅用于数据流控制:EFM8发送一个字节通知Scilab可以发送更多数据。所以USBTxByte()函数只是使用Block_Write()传输单个字节的简便方法。
接收到的USB数据包用以下代码处理: - if (API_InterruptCode & RX_COMPLETE) // USB read complete
- {
- if(USBBytesReceived == 1 && USBRxPacket[0] == NEW_IMAGE_FLAG)
- {
- CLEAR_LCD = TRUE;
- NextLinetoWrite = 0;
- //return the new image flag byte to the PC for flow control
- USBTxByte(NEW_IMAGE_FLAG);
- //continue with the next USB read procedure
- Block_Read(USBRxPacket, USB_PACKET_SIZE, &USBBytesReceived);
- }
- else if(USBBytesReceived == USB_PACKET_SIZE)
- {
- /*this flag tells the while loop in ImagetoLCD_main.c
- to process a received USB pixel data packet*/
- USB_PACKET_RECEIVED = TRUE;
- //continue with the next USB read procedure
- Block_Read(USBRxPacket, USB_PACKET_SIZE, &USBBytesReceived);
- }
- }
复制代码
当Scilab脚本完成将图像文件转换为LCD像素数据后,它会发送一个单字节数据包,其值在ImagetoLCD_Defs.h中定义为NEW_IMAGE_FLAG。因此,如果接收到的数据包长度为1,并且单个接收字节的值为NEW_IMAGE_FLAG,则微控制器知道新图像正在处理中。它清除LCD,将NEW_IMAGE_FLAG发送到PC,并将零加载到NextLinetoWrite中,NextLinetoWrite是一个变量,用于保存微控制器接收下一个像素数据包时要更新的第一个行地址。如果接收的数据包长度是64字节而不是一个字节,则数据包将带来实际的像素数据。在这种情况下,我们只需将USB_PACKET_RECEIVED标志设置为true;流量控制字节将在LCD更新完成后发送。 当ImagetoLCD_main.c中的无限循环意识到USB_PACKET_RECEIVED已设置为true时,它会调用ProcessUSBRxPacket(): - void ProcessUSBRxPacket()
- {
- unsigned char n = 0, row, column;
- //copy the received pixel data to the LCD display data array
- for(row = 0; row < LINES_PER_PACKET; row++)
- {
- for(column = 0; column < NUM_LINE_DATA_BYTES; column++)
- {
- LCDDisplayData[row][column] = USBRxPacket[n];
- n++;
- }
- }
- //wait until the SPI state variable indicates that the bus is available for a new transfer
- while(LCDTxState != IDLE);
- UpdateLCDLines();
- }
复制代码
在这里,我们将像素数据传输到适当的二维数组中。在这个项目中,LCDDisplayData [] []是4行乘16列:我们仍然需要16列字节来存储128位水平数据,但我们只需要4行,因为像素数据是以64字节的数据包从PC传输的,和64字节除以每行16字节等于4行。更新数组后,程序将等待LCD通信接口处于空闲状态,然后调用UpdateLCDLines()。
该项目需要对支配SPI传输至LCD的状态机进行一些更改。以前我们有UpdateAllLCDLines()函数,它可以从一个SPI传输中启动一个更新所有LCD线的过程。但是现在我们在一次SPI传输过程中只更新了四条线,并且在该过程结束时执行了两个额外的任务:
SCILAB Scilab脚本开始时是图像处理部分:
输入图像必须是一个灰度128×128像素.bmp文件。 SegmentByThreshold()函数将图像从灰度转换为黑白,因为使用我们的LCD,像素可以打开或关闭 - 不允许灰色。通过使用一系列的bitset()操作将此图像数据转换为可发送到EFM8并直接传送到LCD的像素数据。请注意,像Scilab这样复杂的计算应用程序并不完全针对我们在此使用的按位运算进行优化。换句话说,上述代码中的双循环块需要很长时间才能执行完成(例如,使用运行Windows 8.1的2.5 GHz处理器大约需要23秒)。因此,如果要调整此代码以显示动画序列,则需要在开始向EFM8发送数据之前将所有图像转换为LCD像素格式。
Scilab脚本中的另一个主要部分for循环,通过VCP连接将像素数据发送到EFM8:
每四行像素数据被转换为一维数组,并使用slSendArray()函数以单个64字节数据包进行传输。然后脚本在发送接下来的四行像素数据之前从EFM8中读取单个确认字节。重要说明:此脚本中调用slReadByte()函数时,第二个参数选择1(即response = slReadByte(EFM8Port,1))。这个“1”表示函数将被阻塞,也就是说Scilab在至少有一个字节到达之前什么也不做。这里的优点是脚本尽可能快地运行,因为只要EFM8发送确认字节后,执行就会立即继续。但问题是,如果出现问题并且确认字节永远不会出现,Scilab将处于昏迷状态,直到您关闭程序并重新打开它。因此,在调试阶段执行此操作的更好方法是使用sleep()函数为EFM8提供时间进行响应,然后读取没有阻塞的字节,即slReadByte(EFM8Port,0)。
Scilab脚本还会调用tic()和toc()来测量并显示传输和显示一幅图像所需的时间长度。使用上面提到的相同的2.5 GHz Windows机器,该过程仅需要大约50毫秒,这意味着该操作系统应该能够舒适地保持每秒10个图像的动画帧速率。 |