本程序采用的是STM32F103VE单片机,外部晶振的大小为8MHz,使用HAL库编写程序。
程序下载地址:https://pan.baidu.com/s/1-Q4LX3DkMjDcLod1m3r1oQ(提取码:mcji)
(程序里面注释FSMC_D16=>RS写错了,应该是A16才对,D没有16)
去ST官网下载STM32F1的Cube包,文件名称为en.stm32cubef1.zip,STemWin图形库就位于STM32Cube_FW_F1_V1.8.0/Middlewares/ST/STemWin文件夹中。
将整个inc文件夹复制到工程中,然后复制Config文件夹下的GUIConf.c、GUIConf.h、LCDConf_FlexColor.c和LCDConf_FlexColor.h文件(去掉了文件名中的_Template)。再复制GUI_X.c和STemWin_CM3_wc16.a就可以了。
STemWin库本身是支持ILI9341的,因此移植过程非常简单。
在GUIConf.c中,我们只需要将GUI_NUMBYTES宏的值调小就行了。因为默认的值太大了,单片机根本没有这么多内存可分配,可以改成32768,也就是只分配32KB的内存给STemWin:
//
// Define the available number of bytes available for the GUI
//
#define GUI_NUMBYTES 32768
对于LCDConf_FlexColor.c,我们只需要实现读写液晶屏命令和数据的函数就行了。先包含STM32头文件:
#include <stm32f1xx.h>
#include "../ILI9341.h"
实现命令和数据读写函数:
/********************************************************************
*
* LcdWriteReg
*
* Function description:
* Sets display register
*/
static void LcdWriteReg(U16 Data) {
// ... TBD by user
ILI9341_CMD = Data; // 写命令
}
/********************************************************************
*
* LcdWriteData
*
* Function description:
* Writes a value to a display register
*/
static void LcdWriteData(U16 Data) {
// ... TBD by user
ILI9341_DATA = Data; // 写数据
}
/********************************************************************
*
* LcdWriteDataMultiple
*
* Function description:
* Writes multiple values to a display register.
*/
static void LcdWriteDataMultiple(U16 * pData, int NumItems) {
while (NumItems--) {
// ... TBD by user
ILI9341_DATA = *pData++; // 写多个数据
}
}
/********************************************************************
*
* LcdReadDataMultiple
*
* Function description:
* Reads multiple values from a display register.
*/
static void LcdReadDataMultiple(U16 * pData, int NumItems) {
while (NumItems--) {
// ... TBD by user
*pData++ = ILI9341_DATA; // 读多个数据
}
}
其中ILI9341_CMD和ILI9341_DATA是两个FSMC内存地址,是定义在我们的ILI9341.h中的。
#define ILI9341_CMD (*(volatile uint16_t *)0x60000000)
#define ILI9341_DATA (*(volatile uint16_t *)0x60020000)
接下来在LCD_X_Config中,我们需要调用STemWin中内置的ILI9341的驱动程序,把第三个参数改成F66709:
GUIDRV_FlexColor_SetFunc(pDevice, &PortAPI, GUIDRV_FLEXCOLOR_F66709, GUIDRV_FLEXCOLOR_M16C0B16);
// 根据StemWin手册, 驱动Ilitek ILI9335的屏幕, 第三个参数应该选择66708
// Ilitek ILI9338, ILI9340, ILI9341, ILI9342应该选择66709
// Samsung S6E63D6应该选择66719
可以在STemWin的手册里面看到ILI9341对应的是F66709:
在本程序中,我们不需要颠倒X和Y坐标轴,也不需要翻转Y轴,于是注释掉:
//Config.Orientation = GUI_SWAP_XY | GUI_MIRROR_Y; // 是否交换XY方向
这样的话,X轴就是短边,Y轴就是长边了。
接下来,将STemWin的头文件目录添加到工程属性里面去:
STemWin_CM3_wc16.a的文件类型要修改为Library file:
OK,就这么简单,移植完成了,现在可以编写主程序运行一下试试看了!
注意:调用GUI_Init函数初始化STemWin图形库前必须要在RCC中打开STM32的CRC外设的时钟!!!
很奇怪今天晚上液晶屏触摸突然好了,X坐标和Y坐标都能读了。触控程序没有改过,之前测的时候按下去X坐标(0xd0寄存器)一直是0,Y坐标(0x90寄存器)的范围是2950~3050(从屏幕上方滑到下方)。松开时X和Y都是4095。也就是说只能读Y坐标不能读X坐标。而且,触摸中断一直为高电平,按下屏幕没有任何反应。
今天晚上弄着弄着就X和Y都能读了,触摸中断(PC4低电平)也好使了。感觉很可能是屏幕本身的问题,同样程序在其他屏幕上运行就没有问题,之前偏偏在这块屏幕上有问题。
#include <stdio.h>
#include <stm32f1xx.h>
#include <GUI.h>
#include "common.h"
#include "images.h"
#include "Keyboard.h"
#include "ILI9341.h"
#include "XPT2046.h"
// 矩阵键盘按键处理
static void key_handler(int key, void *arg)
{
char str[30];
GUI_RECT rect;
GUI_SetBkColor(GUI_LIGHTBLUE);
rect.x0 = 0;
rect.y0 = 0;
rect.x1 = 239;
rect.y1 = 99;
GUI_ClearRectEx(&rect);
if (key != -1)
{
snprintf(str, sizeof(str), "You pressed KEY%d!", key);
GUI_SetColor(GUI_RED);
}
else
{
snprintf(str, sizeof(str), "You released the key!");
GUI_SetColor(GUI_GREEN);
}
GUI_DispStringInRectWrap(str, &rect, GUI_TA_HCENTER | GUI_TA_VCENTER, GUI_WRAPMODE_WORD);
//GUI_InvertRect(rect.x0, rect.y0, rect.x1, rect.y1); // 反色
}
// 截取一部分位图并显示
static void copy_part_of_bitmap(int x, int y, const GUI_BITMAP *bmp, int x0, int y0, int width, int height)
{
GUI_RECT rect;
rect.x0 = x;
rect.y0 = y;
rect.x1 = x + width - 1;
rect.y1 = y + height - 1;
GUI_SetClipRect(&rect);
GUI_DrawBitmap(bmp, x - x0, y - y0);
GUI_SetClipRect(NULL);
}
static void display_images(void)
{
GUI_BITMAP bmp;
bmp.BitsPerPixel = 16;
bmp.BytesPerLine = sizeof(image1[0]);
bmp.pData = (const uint8_t *)image1;
bmp.pMethods = GUI_DRAW_BMPM565;
bmp.pPal = NULL;
bmp.XSize = GUI_COUNTOF(image1[0]);
bmp.YSize = GUI_COUNTOF(image1);
//GUI_DrawBitmap(&bmp, 0, 36); // 显示完整的图片
// 截取图片的一部分并显示
copy_part_of_bitmap(20, 120, &bmp, 60, 125, 131, 123);
//GUI_Delay(1000);
copy_part_of_bitmap(180, 120, &bmp, 127, 32, 16, 64);
}
// 显示触控坐标
void display_touch(int x, int y)
{
char str[30];
GUI_RECT rect;
GUI_SetBkColor(GUI_LIGHTGREEN);
rect.x0 = 0;
rect.y0 = 263;
rect.x1 = 239;
rect.y1 = 319;
GUI_ClearRectEx(&rect);
snprintf(str, sizeof(str), "(%d,%d)\n", x, y);
GUI_SetColor(GUI_ORANGE);
GUI_DispStringInRectWrap(str, &rect, GUI_TA_HCENTER | GUI_TA_VCENTER, GUI_WRAPMODE_WORD);
}
int main(void)
{
int x, y;
HAL_Init();
clock_init();
usart_init(115200);
printf("STM32F103VE FSMC ILI9341\n");
printf("SystemCoreClock=%u\n", SystemCoreClock);
Keyboard_Init();
ILI9341_Init();
XPT2046_Init();
__HAL_RCC_CRC_CLK_ENABLE();
GUI_Init();
GUI_SetBkColor(GUI_LIGHTRED);
GUI_Clear(); // 清屏
ILI9341_Enable(1); // 开显示
GUI_SetFont(GUI_FONT_32B_1);
GUI_SetTextMode(GUI_TM_TRANS); // 绘制文字时无背景颜色
display_images();
while (1)
{
if (XPT2046_GetITStatus())
{
XPT2046_ReadPosition(&x, &y);
printf("(%d,%d)\n", x, y);
display_touch(x, y);
}
Keyboard_Process(key_handler, NULL);
}
}
程序运行效果:
可以发现已经可以正常绘制图片,矩形,以及显示文字了。
但是现在GUI_Delay无法延时,一调用就会陷入死循环。而且GUI_InvertRect函数也无法正常工作,原因是因为我们还没有正确配置好STemWin读取屏幕像素点的功能,凡是需要读取像素点的(例如GUI_DM_XOR方式绘制)函数都无法正常工作。
首先解决GUI_Delay的问题,只需要将GUI_X.c里面GUI_X_GetTime和GUI_X_Delay分别与HAL库中的HAL_GetTick和HAL_Delay函数绑定就可以了:
GUI_TIMER_TIME GUI_X_GetTime(void) {
return HAL_GetTick();
}
void GUI_X_Delay(int ms) {
HAL_Delay(ms);
}
实现读取屏幕像素点需要修改LCD_X_Config函数。查阅手册可知,STemWin自带了三种读取屏幕像素点的方式,一一测试:
GUIDRV_FlexColor_SetReadFunc66709_B16(pDevice, GUIDRV_FLEXCOLOR_READ_FUNC_I);
GUIDRV_FlexColor_SetReadFunc66709_B16(pDevice, GUIDRV_FLEXCOLOR_READ_FUNC_II);
GUIDRV_FlexColor_SetReadFunc66709_B16(pDevice, GUIDRV_FLEXCOLOR_READ_FUNC_III);
发现前两个都不行,第三个会导致HardFault错误,所以用StemWin自带的没有希望了。我们可以自己编写函数来实现读取像素点,然后和STemWin绑定:
LCD_SetDevFunc(0, LCD_DEVFUNC_READPIXEL, (void (*)(void))LcdReadPixel);
LCD_SetDevFunc(0, LCD_DEVFUNC_READMPIXELS, (void (*)(void))LcdReadMPixels);
第一个函数是读单个点,第二个函数是读多个点,函数的实现如下:
static U16 LcdReadPixel(int LayerIndex)
{
U16 color;
ILI9341_GetPixels(&color, 1);
return color;
}
static void LcdReadMPixels(int LayerIndex, U16 *pBuffer, U32 NumPixels)
{
ILI9341_GetPixels(pBuffer, NumPixels);
}
注意这两个函数都只传入了LayerIndex(图层号)参数,没有传入X坐标和Y坐标,这是因为STemWin已经帮我们设置好了坐标了,我们只需要去读取颜色值就行了:
/* 读取屏幕显示内容 */
void ILI9341_GetPixels(uint16_t *pixels, int count)
{
int i = 0;
uint16_t data[3];
uint16_t rgb565[2];
uint32_t rgb888[2];
ILI9341_CMD = 0x2e;
ILI9341_DATA; // dummy read
while (i < count)
{
// 读两个像素, 每个像素3字节
// 每字节表示一个分量, 分量在字节中是左对齐的
data[0] = ILI9341_DATA; // 0xr1g1 (高字节为第一个像素的红色分量, 低字节为第一个像素的绿色分量)
data[1] = ILI9341_DATA; // 0xb1r2 (高字节为第一个像素的蓝色分量, 低字节为第二个像素的红色分量)
data[2] = ILI9341_DATA; // 0xg2b2 (高字节为第二个像素的绿色分量, 低字节为第二个像素的蓝色分量)
// 转换成RGB888
rgb888[0] = (data[0] << 8) | (data[1] >> 8);
rgb888[1] = ((data[1] & 0xff) << 16) | data[2];
//printf("#%06X #%06X => ", rgb888[0], rgb888[1]);
// 再转换成RGB565
rgb565[0] = ILI9341_RGB888TO565(rgb888[0]);
rgb565[1] = ILI9341_RGB888TO565(rgb888[1]);
//printf("0x%04x 0x%04x\n", rgb565[0], rgb565[1]);
// 保存颜色值
pixels[i++] = rgb565[0];
if (i < count)
pixels[i++] = rgb565[1];
}
}
下面附上完整的LCDConf_FlexColor.c的文件内容:
/*********************************************************************
* SEGGER Microcontroller GmbH & Co. KG *
* Solutions for real time microcontroller applications *
**********************************************************************
* *
* (c) 1996 - 2017 SEGGER Microcontroller GmbH & Co. KG *
* *
* Internet: www.segger.com Support: support@segger.com *
* *
**********************************************************************
** emWin V5.44 - Graphical user interface for embedded applications **
All Intellectual Property rights in the Software belongs to SEGGER.
emWin is protected by international copyright laws. Knowledge of the
source code may not be used to write a similar product. This file may
only be used in accordance with the following terms:
The software has been licensed to STMicroelectronics International
N.V. a Dutch company with a Swiss branch and its headquarters in Plan-
les-Ouates, Geneva, 39 Chemin du Champ des Filles, Switzerland for the
purposes of creating libraries for ARM Cortex-M-based 32-bit microcon_
troller products commercialized by Licensee only, sublicensed and dis_
tributed under the terms and conditions of the End User License Agree_
ment supplied by STMicroelectronics International N.V.
Full source code is available at: www.segger.com
We appreciate your understanding and fairness.
----------------------------------------------------------------------
File : LCDConf_FlexColor_Template.c
Purpose : Display controller configuration (single layer)
---------------------------END-OF-HEADER------------------------------
*/
/**
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2018 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under Ultimate Liberty license SLA0044,
* the "License"; You may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
* http://www.st.com/SLA0044
*
******************************************************************************
*/
#include "GUI.h"
#include "GUIDRV_FlexColor.h"
#include <stm32f1xx.h>
#include "../ILI9341.h"
/*********************************************************************
*
* Layer configuration (to be modified)
*
**********************************************************************
*/
//
// Physical display size
//
#define XSIZE_PHYS 240 // To be adapted to x-screen size
#define YSIZE_PHYS 320 // To be adapted to y-screen size
/*********************************************************************
*
* Configuration checking
*
**********************************************************************
*/
#ifndef VXSIZE_PHYS
#define VXSIZE_PHYS XSIZE_PHYS
#endif
#ifndef VYSIZE_PHYS
#define VYSIZE_PHYS YSIZE_PHYS
#endif
#ifndef XSIZE_PHYS
#error Physical X size of display is not defined!
#endif
#ifndef YSIZE_PHYS
#error Physical Y size of display is not defined!
#endif
#ifndef GUICC_565
#error Color conversion not defined!
#endif
#ifndef GUIDRV_FLEXCOLOR
#error No display driver defined!
#endif
/*********************************************************************
*
* Local functions
*
**********************************************************************
*/
/********************************************************************
*
* LcdWriteReg
*
* Function description:
* Sets display register
*/
static void LcdWriteReg(U16 Data) {
// ... TBD by user
ILI9341_CMD = Data; // 写命令
}
/********************************************************************
*
* LcdWriteData
*
* Function description:
* Writes a value to a display register
*/
static void LcdWriteData(U16 Data) {
// ... TBD by user
ILI9341_DATA = Data; // 写数据
}
/********************************************************************
*
* LcdWriteDataMultiple
*
* Function description:
* Writes multiple values to a display register.
*/
static void LcdWriteDataMultiple(U16 * pData, int NumItems) {
while (NumItems--) {
// ... TBD by user
ILI9341_DATA = *pData++; // 写多个数据
}
}
/********************************************************************
*
* LcdReadDataMultiple
*
* Function description:
* Reads multiple values from a display register.
*/
static void LcdReadDataMultiple(U16 * pData, int NumItems) {
while (NumItems--) {
// ... TBD by user
*pData++ = ILI9341_DATA; // 读多个数据
}
}
static U16 LcdReadPixel(int LayerIndex)
{
U16 color;
ILI9341_GetPixels(&color, 1);
return color;
}
static void LcdReadMPixels(int LayerIndex, U16 *pBuffer, U32 NumPixels)
{
ILI9341_GetPixels(pBuffer, NumPixels);
}
/*********************************************************************
*
* Public functions
*
**********************************************************************
*/
/*********************************************************************
*
* LCD_X_Config
*
* Function description:
* Called during the initialization process in order to set up the
* display driver configuration.
*
*/
void LCD_X_Config(void) {
GUI_DEVICE * pDevice;
CONFIG_FLEXCOLOR Config = {0};
GUI_PORT_API PortAPI = {0};
//
// Set display driver and color conversion
//
pDevice = GUI_DEVICE_CreateAndLink(GUIDRV_FLEXCOLOR, GUICC_565, 0, 0);
//
// Display driver configuration, required for Lin-driver
//
LCD_SetSizeEx (0, XSIZE_PHYS , YSIZE_PHYS);
LCD_SetVSizeEx(0, VXSIZE_PHYS, VYSIZE_PHYS);
//
// Orientation
//
//Config.Orientation = GUI_SWAP_XY | GUI_MIRROR_Y; // 是否交换XY方向
GUIDRV_FlexColor_Config(pDevice, &Config);
//
// Set controller and operation mode
//
PortAPI.pfWrite16_A0 = LcdWriteReg;
PortAPI.pfWrite16_A1 = LcdWriteData;
PortAPI.pfWriteM16_A1 = LcdWriteDataMultiple;
PortAPI.pfReadM16_A1 = LcdReadDataMultiple;
GUIDRV_FlexColor_SetFunc(pDevice, &PortAPI, GUIDRV_FLEXCOLOR_F66709, GUIDRV_FLEXCOLOR_M16C0B16);
// 根据StemWin手册, 驱动Ilitek ILI9335的屏幕, 第三个参数应该选择66708
// Ilitek ILI9338, ILI9340, ILI9341, ILI9342应该选择66709
// Samsung S6E63D6应该选择66719
LCD_SetDevFunc(0, LCD_DEVFUNC_READPIXEL, (void (*)(void))LcdReadPixel);
LCD_SetDevFunc(0, LCD_DEVFUNC_READMPIXELS, (void (*)(void))LcdReadMPixels);
//GUIDRV_FlexColor_SetReadFunc66709_B16(pDevice, GUIDRV_FLEXCOLOR_READ_FUNC_III);
}
/*********************************************************************
*
* LCD_X_DisplayDriver
*
* Function description:
* This function is called by the display driver for several purposes.
* To support the according task the routine needs to be adapted to
* the display controller. Please note that the commands marked with
* 'optional' are not cogently required and should only be adapted if
* the display controller supports these features.
*
* Parameter:
* LayerIndex - Index of layer to be configured
* Cmd - Please refer to the details in the switch statement below
* pData - Pointer to a LCD_X_DATA structure
*
* Return Value:
* < -1 - Error
* -1 - Command not handled
* 0 - Ok
*/
int LCD_X_DisplayDriver(unsigned LayerIndex, unsigned Cmd, void * pData) {
int r;
(void) LayerIndex;
(void) pData;
switch (Cmd) {
case LCD_X_INITCONTROLLER: {
//
// Called during the initialization process in order to set up the
// display controller and put it into operation. If the display
// controller is not initialized by any external routine this needs
// to be adapted by the customer...
//
// ...
return 0;
}
default:
r = -1;
}
return r;
}
/*************************** End of file ****************************/
以及4x4矩阵键盘和彩屏排座的电路图:
连续运行了一天触控一直都好使。晚上关机,第二天中午再开,屏幕触控就没有任何反应了,触控中断无信号。通电大概二十分钟过后,触控又好使了(屏幕透明膜之前已经揭下来了),PC4触控中断有反应。看来这个屏幕真的有问题。。。