玩转RT-Thread系列教程(6)–移植STemwin
一、STemwin介绍
在实际应用中我们时常需要制作 UI 界面来实现人机交互,简单的 UI 我们可以自己直接写代码,但是对于那些复杂的交互方式和界面自己写代码的话难度就会很大。为了方便开发我们可以使用第三方的 GUI 库来做 UI 界面设计,在 STM32 上最火的 GUI 库莫过于 UCGUI,而UCGUI 的高级版本就是 emWin,STemWin 是 SEGGER 授权给 ST 的 emWin 版本,ST 的芯片可以免费使用 STemWin,而且 STemWin 针对 ST 的芯片做了优化。
二、添加软件包
1.进入env配置界面
menuconfig
2.选择我们要选择的软件包
添加屏幕驱动(这里我使用的是正点原子4.3寸电容触摸屏–触摸芯片型号为GT9147)
3.使能内存分配算法
4.添加IIC驱动
经过对比引脚,我们开发板的触摸iic引脚分布在PB1、PF11上。
5.添加STemwin软件包
6.使用命令从仓库拉取软件包
7.保存配置使用命令生成MDK工程
打开工程可以看见已经生成了对应的工程
三、驱动配置
1.查看电路原理图
SRAM和LCD对应的FSMC_NE3,FSMC_NE4
2.使用Cubemx配置驱动
3.CRC配置
4.添加SRAM驱动
打开STemwin官方实例的drv_lcd.c文件,正好正点原子屏幕是使用fsmc驱动的,所以我们只需简单配置即可。
//初始化外部SRAM
void FSMC_SRAM_Init(void)
{
RCC->AHBENR |= 1 << 8; //使能FSMC时钟
RCC->APB2ENR |= 1 << 5; //使能PORTD时钟
RCC->APB2ENR |= 1 << 6; //使能PORTE时钟
RCC->APB2ENR |= 1 << 7; //使能PORTF时钟
RCC->APB2ENR |= 1 << 8; //使能PORTG时钟
//PORTD复用推挽输出
GPIOD->CRH &= 0X00000000;
GPIOD->CRH |= 0XBBBBBBBB;
GPIOD->CRL &= 0XFF00FF00;
GPIOD->CRL |= 0X00BB00BB;
//PORTE复用推挽输出
GPIOE->CRH &= 0X00000000;
GPIOE->CRH |= 0XBBBBBBBB;
GPIOE->CRL &= 0X0FFFFF00;
GPIOE->CRL |= 0XB00000BB;
//PORTF复用推挽输出
GPIOF->CRH &= 0X0000FFFF;
GPIOF->CRH |= 0XBBBB0000;
GPIOF->CRL &= 0XFF000000;
GPIOF->CRL |= 0X00BBBBBB;
//PORTG复用推挽输出 PG10->NE3
GPIOG->CRH &= 0XFFFFF0FF;
GPIOG->CRH |= 0X00000B00;
GPIOG->CRL &= 0XFF000000;
GPIOG->CRL |= 0X00BBBBBB;
//寄存器清零
//bank1有NE1~4,每一个有一个BCR+TCR,所以总共八个寄存器。
//这里我们使用NE3 ,也就对应BTCR[4],[5]。
FSMC_Bank1->BTCR[4] = 0X00000000;
FSMC_Bank1->BTCR[5] = 0X00000000;
FSMC_Bank1E->BWTR[4] = 0X00000000;
//操作BCR寄存器 使用异步模式,模式A(读写共用一个时序寄存器)
//BTCR[偶数]:BCR寄存器;BTCR[奇数]:BTR寄存器
FSMC_Bank1->BTCR[4] |= 1 << 12; //存储器写使能
FSMC_Bank1->BTCR[4] |= 1 << 4; //存储器数据宽度为16bit
//操作BTR寄存器
FSMC_Bank1->BTCR[5] |= 3 << 8; //数据保持时间(DATAST)为3个HCLK 4/72M=55ns(对EM的SRAM芯片)
FSMC_Bank1->BTCR[5] |= 0 << 4; //地址保持时间(ADDHLD)未用到
FSMC_Bank1->BTCR[5] |= 0 << 0; //地址建立时间(ADDSET)为2个HCLK 1/36M=27ns
//闪存写时序寄存器
FSMC_Bank1E->BWTR[4] = 0x0FFFFFFF; //默认值
//使能BANK1区域3
FSMC_Bank1->BTCR[4] |= 1 << 0;
}
添加到LCD初始化下面
进入gt9147.c中,添加触摸屏初始化:
//复位引脚
#define GT9147_RST_PIN 91
//中断引脚--PF10 INT
#define GT9147_IRQ_PIN 90
int rt_hw_gt9147_port(void)
{
struct rt_touch_config config;
rt_uint8_t rst;
rst = GT9147_RST_PIN;
config.dev_name = "i2c1";
config.irq_pin.pin = GT9147_IRQ_PIN;
config.irq_pin.mode = PIN_MODE_INPUT_PULLDOWN;
config.user_data = &rst;
rt_hw_gt9147_init("gt", &config);
return 0;
}
INIT_ENV_EXPORT(rt_hw_gt9147_port);
这样,我们的LCD和SRAM就配置好了。
5.编译、下载,打开串口助手
输入list_device,可以看见lcd和gt触摸屏设备已经注册好了。
输入free,查看串口返回:
串口返回1048528字节,1048528/1024=1,023.9k=1M,间接证明了我们开发板的SRAM的内存是1M。
四、STemwin配置
1.关于STemwin官方文件说明
2.系统配置
在GUIConf.c中使用动态从SRAM中申请方式为STemwin分配512k的内存。
3.触摸屏移植
配置GUI_X_Touch_Analog.c 文件:
GUI_X_Touch_Analog.c 文件中有四个函数 :GUI_TOUCH_X_ActivateX() ,GUI_TOUCH_X_ActivateY(), GUI_TOUCH_X_MeasureX()和 GUI_TOUCH_X_MeasureY()。其中前两个我们没有使用到 ,STemWin 真正调用GUI_TOUCH_X_MeasureX和GUI_TOUCH_X_MeasureY这两个函数来获取触摸屏按下时的X轴和Y轴AD值。
void GUI_TOUCH_X_ActivateX(void)
{
}
void GUI_TOUCH_X_ActivateY(void)
{
}
int GUI_TOUCH_X_MeasureX(void)
{
int32_t xvalue;
for (rt_uint8_t i = 0; i < info.point_num; i++)
{
if (read_data[i].event == RT_TOUCH_EVENT_DOWN || read_data[i].event == RT_TOUCH_EVENT_MOVE)
{
xvalue = read_data[i].x_coordinate;
}
else
{
xvalue = 0xffff;
}
return xvalue;
}
}
int GUI_TOUCH_X_MeasureY(void)
{
int32_t yvalue;
for (rt_uint8_t i = 0; i < info.point_num; i++)
{
if (read_data[i].event == RT_TOUCH_EVENT_DOWN || read_data[i].event == RT_TOUCH_EVENT_MOVE)
{
yvalue = read_data[i].y_coordinate;
}
else
{
yvalue = 0xffff;
}
return yvalue;
}
}
static void gt9147_entry(void *parameter)
{
rt_device_control(dev, RT_TOUCH_CTRL_GET_INFO, &info);
read_data = (struct rt_touch_data *)rt_malloc(sizeof(struct rt_touch_data) * info.point_num);
while (1)
{
//获取信号量
rt_sem_take(gt9147_sem, RT_WAITING_FOREVER);
//触摸屏是否按下
rt_device_read(dev, 0, read_data, info.point_num);
//开中断
rt_device_control(dev, RT_TOUCH_CTRL_ENABLE_INT, RT_NULL);
}
}
static rt_err_t rx_callback(rt_device_t dev, rt_size_t size)
{
//释放信号量
rt_sem_release(gt9147_sem);
//关中断
rt_device_control(dev, RT_TOUCH_CTRL_DISABLE_INT, RT_NULL);
return 0;
}
/* Test function */
int gt9147_sample(const char *name, rt_uint16_t x, rt_uint16_t y)
{
void *id;
dev = rt_device_find(name);
if (dev == RT_NULL)
{
rt_kprintf("can't find device:%s\n", name);
return -1;
}
if (rt_device_open(dev, RT_DEVICE_FLAG_INT_RX) != RT_EOK)
{
rt_kprintf("open device failed!");
return -1;
}
id = rt_malloc(sizeof(struct rt_touch_info));
rt_device_control(dev, RT_TOUCH_CTRL_GET_ID, id);
rt_uint8_t * read_id = (rt_uint8_t *)id;
rt_kprintf("id = %d %d %d %d \n", read_id[0] - '0', read_id[1] - '0', read_id[2] - '0', read_id[3] - '0');
rt_device_control(dev, RT_TOUCH_CTRL_SET_X_RANGE, &x); /* if possible you can set your x y coordinate*/
rt_device_control(dev, RT_TOUCH_CTRL_SET_Y_RANGE, &y);
rt_device_control(dev, RT_TOUCH_CTRL_GET_INFO, id);
rt_kprintf("range_x = %d \n", (*(struct rt_touch_info*)id).range_x);
rt_kprintf("range_y = %d \n", (*(struct rt_touch_info*)id).range_y);
rt_kprintf("point_num = %d \n", (*(struct rt_touch_info*)id).point_num);
rt_free(id);
gt9147_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_FIFO);
if (gt9147_sem == RT_NULL)
{
rt_kprintf("create dynamic semaphore failed.\n");
return -1;
}
rt_device_set_rx_indicate(dev, rx_callback);
gt9147_thread = rt_thread_create("touch_lcd",
gt9147_entry,
RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY,
THREAD_TIMESLICE);
if (gt9147_thread != RT_NULL)
rt_thread_startup(gt9147_thread);
return 0;
}
int touch_init(void)
{
gt9147_sample("gt", 800, 480);
return RT_EOK;
}
INIT_APP_EXPORT(touch_init);
最后修改 LCDConf_FlexColor_Template.c 文件中的 LCD_X_Config()函数,在最后加上触摸屏的校准(必须)
4.STemwin的“点灯仪式”
既然我们已经移植并配置好了LCD以及触摸功能,那么就来测试一下我们的emwin是否工作正常吧
我们新建一个stemwin测试文件,测试代码如下:
#include <rtthread.h>
#include "GUI.h"
#include <board.h>
#include "DIALOG.h"
//
//TOUCH任务
//设置任务优先级
#define TOUCH_TASK_PRIO 15
//任务堆栈大小
#define TOUCH_STK_SIZE 216
//EMWINDEMO任务
//设置任务优先级
#define EMWINDEMO_TASK_PRIO 18
//任务堆栈大小
#define EMWINDEMO_STK_SIZE 2048
//
#define ID_FRAMEWIN_0 (GUI_ID_USER + 0x00)
#define ID_BUTTON_0 (GUI_ID_USER + 0x01)
#define ID_BUTTON_1 (GUI_ID_USER + 0x02)
//对话框资源表
static const GUI_WIDGET_CREATE_INFO _aDialogCreate[] =
{
{ FRAMEWIN_CreateIndirect, "Framewin", ID_FRAMEWIN_0, 0, 0, 800, 480, FRAMEWIN_CF_MOVEABLE, 0x64, 0 },
{ BUTTON_CreateIndirect, "Button", ID_BUTTON_0, 300, 122, 150, 50, 0, 0x0, 0 },
{ BUTTON_CreateIndirect, "Button", ID_BUTTON_1, 300, 251, 150, 50, 0, 0x0, 0 },
};
//对话框回调函数
static void _cbDialog(WM_MESSAGE * pMsg)
{
WM_HWIN hItem;
int NCode;
int Id;
switch (pMsg->MsgId)
{
case WM_INIT_DIALOG:
//初始化对话框
hItem = pMsg->hWin;
FRAMEWIN_SetTitleHeight(hItem, 30);
FRAMEWIN_SetText(hItem, "RB_RT-Thread STemwin demo");
FRAMEWIN_SetFont(hItem, GUI_FONT_24_ASCII);
FRAMEWIN_SetTextAlign(hItem, GUI_TA_HCENTER | GUI_TA_VCENTER);
FRAMEWIN_SetTextColor(hItem, 0x0000FFFF);
//初始化BUTTON0
hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0);
BUTTON_SetFont(hItem, GUI_FONT_24_ASCII);
BUTTON_SetText(hItem, "LED1");
//初始化BUTTON1
hItem = WM_GetDialogItem(pMsg->hWin, ID_BUTTON_1);
BUTTON_SetText(hItem, "BEEP");
BUTTON_SetFont(hItem, GUI_FONT_24_ASCII);
break;
case WM_NOTIFY_PARENT:
Id = WM_GetId(pMsg->hWinSrc);
NCode = pMsg->Data.v;
switch(Id)
{
case ID_BUTTON_0: //BUTTON_0的通知代码,控制LED1
switch(NCode)
{
case WM_NOTIFICATION_CLICKED:
break;
case WM_NOTIFICATION_RELEASED: //按钮被按下并释放
rt_kprintf("led on\n");
break;
}
break;
case ID_BUTTON_1: //BUTTON_1的通知代码,控制BEEP
switch(NCode)
{
case WM_NOTIFICATION_CLICKED:
break;
case WM_NOTIFICATION_RELEASED:
rt_kprintf("beep on\n");
//LED1=~LED1;
break;
}
break;
}
break;
default:
WM_DefaultProc(pMsg);
break;
}
}
//创建一个对话框
WM_HWIN CreateFramewin(void)
{
WM_HWIN hWin;
//非阻塞式对话框
hWin = GUI_CreateDialogBox(_aDialogCreate, GUI_COUNTOF(_aDialogCreate), _cbDialog, WM_HBKWIN, 0, 0);
return hWin;
}
//BUTTON按钮上显示位图
void Buttonbmp_Demo(void)
{
WM_HWIN hWin;
hWin = CreateFramewin();
while(1)
{
GUI_Delay(10);
}
}
static void Sys_CRC_Init(void)
{
CRC_HandleTypeDef CrcHandle;
CrcHandle.State = HAL_CRC_STATE_RESET;
CrcHandle.Instance = CRC;
HAL_CRC_Init(&CrcHandle);
}
//显示任务
static void STemwin_thread(void *param)
{
Sys_CRC_Init();
WM_SetCreateFlags(WM_CF_MEMDEV); //启动所有窗口的存储设备
GUI_Init();
//
GUI_CURSOR_Show();
//更换皮肤
BUTTON_SetDefaultSkin(BUTTON_SKIN_FLEX);
CHECKBOX_SetDefaultSkin(CHECKBOX_SKIN_FLEX);
DROPDOWN_SetDefaultSkin(DROPDOWN_SKIN_FLEX);
FRAMEWIN_SetDefaultSkin(FRAMEWIN_SKIN_FLEX);
HEADER_SetDefaultSkin(HEADER_SKIN_FLEX);
MENU_SetDefaultSkin(MENU_SKIN_FLEX);
MULTIPAGE_SetDefaultSkin(MULTIPAGE_SKIN_FLEX);
PROGBAR_SetDefaultSkin(PROGBAR_SKIN_FLEX);
RADIO_SetDefaultSkin(RADIO_SKIN_FLEX);
SCROLLBAR_SetDefaultSkin(SCROLLBAR_SKIN_FLEX);
SLIDER_SetDefaultSkin(SLIDER_SKIN_FLEX);
SPINBOX_SetDefaultSkin(SPINBOX_SKIN_FLEX);
while (1)
{
Buttonbmp_Demo();
}
}
//触摸任务
static void touch_thread(void *param)
{
while(1)
{
GUI_TOUCH_Exec();
rt_thread_mdelay(5);
}
}
static int Gui_emwin_init(void)
{
rt_thread_t tid;
tid = rt_thread_create("emwin",
STemwin_thread, RT_NULL,
EMWINDEMO_STK_SIZE,
EMWINDEMO_TASK_PRIO, 10);
if (tid != RT_NULL)
rt_thread_startup(tid);
return RT_EOK;
}
INIT_APP_EXPORT(Gui_emwin_init);
static int Gui_touch_init(void)
{
rt_thread_t btn_tid;
btn_tid = rt_thread_create("touch",
touch_thread, RT_NULL,
TOUCH_STK_SIZE,
TOUCH_TASK_PRIO, 10);
if (btn_tid != RT_NULL)![image-20210530164629809](C:\Users\zbr\AppData\Roaming\Typora\typora-user-images\image-20210530164629809.png)
rt_thread_startup(btn_tid);
return RT_EOK;
}
INIT_APP_EXPORT(Gui_touch_init);
5.编译、下载,验证
让我们点击一下中间的两个按钮,观察串口输出:
至此,我们在RT-Thread上面移植STemwin已经成功的得以已验证。