stm32开发之threadx+emwin+awizard使用记录

前言

  1. 图形化开发界面选择(awizard)
  2. emwin使用的版本是6.10
  3. 芯片采用的是stm32f407zgt6
  4. 这里使用的开发板是普中麒麟f4系列的

lcd驱动文件(基于提供的源码修改)

1、这里是剔除了很多兼容其他显示屏部分的代码,只保留具体信号的代码,把一些全局变量放到结构体中

头文件

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-6     shchl   first version
 */

#ifndef APP_NO_OS_BSP_TFT_LCD_HX8357D_H
#define APP_NO_OS_BSP_TFT_LCD_HX8357D_H
#include "bsp_includes.h"
//画笔颜色
#define WHITE             0xFFFF
#define BLACK             0x0000
#define BLUE             0x001F
#define BRED             0XF81F
#define GRED             0XFFE0
#define GBLUE             0X07FF
#define RED             0xF800
#define MAGENTA         0xF81F
#define GREEN             0x07E0
#define CYAN             0x7FFF
#define YELLOW             0xFFE0
#define BROWN             0XBC40 //棕色
#define BRRED             0XFC07 //棕红色
#define GRAY             0X8430 //灰色
#define DARKBLUE         0X01CF    //深蓝色
#define LIGHTBLUE         0X7D7C    //浅蓝色
#define GRAYBLUE         0X5458 //灰蓝色
#define LIGHTGREEN         0X841F //浅绿色
#define LIGHTGRAY        0XEF5B //浅灰色(PANNEL)
#define LGRAY             0XC618 //浅灰色(PANNEL),窗体背景色
#define LGRAYBLUE        0XA651 //浅灰蓝色(中间层颜色)
#define LBBLUE           0X2B12 //浅棕蓝色(选择条目的反色)

#define LCD_TFT_DIR 0

#if LCD_TFT_DIR == 1
#define LCD_TFT_WIDTH 480
#define LCD_TFT_HIGH 320
#else
#define LCD_TFT_WIDTH 320
#define LCD_TFT_HIGH 480
#endif


typedef struct tft_lcd_struct {
    volatile uint16_t reg;
    volatile uint16_t ram;
} tft_lcd;

//TFTLCD重要参数集
typedef struct tft_lcd_data_struct {
    uint16_t width;            //LCD 宽度
    uint16_t height;            //LCD 高度
    uint16_t id;                //LCD ID
    uint8_t dir;            //LCD 方向

    uint16_t front_color; /*前景色*/
    uint16_t back_color; /*背景色*/
} tft_lcd_data;

//使用NOR/SRAM的 Bank1.sector4,地址位HADDR[27,26]=11 A6作为数据命令区分线
//注意设置时STM32内部会右移一位对齐! 111 1110=0X7E
#define TFTLCD_BASE        ((uint32_t)(0x6C000000 | 0x0000007E))

extern tft_lcd_data g_tft_lcd_data;    //管理LCD重要参数
void bsp_InitTftLCD(void);

void lcd_tft_set_backlight(uint8_t pwm);

void lcd_tft_write_color(uint16_t color);

uint16_t lcd_tft_read_color(uint16_t x, uint16_t y);

void lcd_tft_draw_point(uint16_t x, uint16_t y);

void lcd_tft_draw_point_ex(uint16_t x, uint16_t y, uint16_t color);

void lcd_tft_draw_line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);

void lcd_tft_draw_line_ex(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);

void lcd_tft_draw_circle(uint16_t x0, uint16_t y0, uint8_t r);

void lcd_tft_draw_circle_ex(uint16_t x0, uint16_t y0, uint8_t r, uint16_t color);

void lcd_tft_set_dir(uint8_t dir);

void lcd_tft_write_ram_prepare(void);

void lcd_tft_fill_color(uint16_t color);

void lcd_tft_fill_area_color(uint16_t xState, uint16_t yState, uint16_t xEnd, uint16_t yEnd, uint16_t color);
void lcd_tft_setWin(uint16_t sx, uint16_t sy, uint16_t width, uint16_t height);


#if 1

void lcd_tft_show_char(uint16_t x, uint16_t y, uint8_t num, uint8_t size, uint8_t mode);

void lcd_tft_show_str(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t size, uint8_t *p);

#endif
#endif //APP_NO_OS_BSP_TFT_LCD_HX8357D_H

源文件

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-6     shchl   first version
 */

#include "bsp_tft_lcd_hx8357d.h"
#include "font.H"
#include "bsp_fsmc_lcd.h"

#ifdef HAL_SRAM_MODULE_ENABLED
#define tft    ((volatile  tft_lcd *) TFTLCD_BASE)
#define tft_delay_ms bsp_DelayDWTMS

static inline void tft_lcd_cmd_init();

tft_lcd_data g_tft_lcd_data = {
        .back_color =WHITE,
        .front_color=BLACK,
        .dir = LCD_TFT_DIR,
        .width  = LCD_TFT_WIDTH,
        .height = LCD_TFT_HIGH
};

//写寄存器函数
//cmd:寄存器值
static inline void write_cmd(uint16_t cmd) { tft->reg = cmd; }

//写数据
//data:要写入的值
static inline void write_data(uint16_t data) { tft->ram = data; }

static inline uint16_t read_data(void) { return tft->ram; }


static void back_led_gpio_init(void) {
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_GPIOB_CLK_ENABLE();            //开启GPIOB时钟
    GPIO_Initure.Pin = GPIO_PIN_15;            //PB15,背光控制
    GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP;  //推挽输出
    GPIO_Initure.Pull = GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed = GPIO_SPEED_HIGH;     //高速
    HAL_GPIO_Init(GPIOB, &GPIO_Initure);
}


void bsp_InitTftLCD(void) {

    back_led_gpio_init();
    bsp_InitExLCD();
    tft_delay_ms(50);
    // 读id,这个根据手册上描述,需要开启扩展指令,否则读取的数据是不正确的。
    // 如果你发现读取的id全部为 0xffff,就是没有开启扩展指令.这里由于影响不大,就不做处理
    write_cmd(0Xd0);
    g_tft_lcd_data.id = read_data();    //dummy read
    g_tft_lcd_data.id = read_data();
    tft_lcd_cmd_init(); /*出厂指令初始化*/

    lcd_tft_set_dir(g_tft_lcd_data.dir); /*设置方向*/
    lcd_tft_fill_color(g_tft_lcd_data.back_color);
}

//SSD1963 背光设置
//pwm:背光等级,0~100.越大越亮.
void lcd_tft_set_backlight(uint8_t pwm) {
    write_cmd(0xBE);    //配置PWM输出
    write_data(0x05);    //1设置PWM频率
    write_data(pwm * 2.55);//2设置PWM占空比
    write_data(0x01);    //3设置C
    write_data(0xFF);    //4设置D
    write_data(0x00);    //5设置E
    write_data(0x00);    //6设置F
}

void lcd_tft_write_color(uint16_t color) {
    write_data(color >> 8);
    write_data(color & 0xff);
}

//读取个某点的颜色值
//x,y:坐标
//返回值:此点的颜色
uint16_t lcd_tft_read_color(uint16_t x, uint16_t y) {
    uint16_t r = 0, g = 0, b = 0;
    if (x >= g_tft_lcd_data.width || y >= g_tft_lcd_data.height)return 0;    //超过了范围,直接返回
    lcd_tft_setWin(x, y, x, y);
    write_cmd(0X2E);
    r = tft->ram;
    r = tft->ram;
    g = tft->ram;
    b = tft->ram;
    r = r << 8 | (g & 0xff);
    return r;
}

//画点
//x,y:坐标
//FRONT_COLOR:此点的颜色
void lcd_tft_draw_point(uint16_t x, uint16_t y) {
    lcd_tft_setWin(x, y, x, y);  //设置点的位置
    lcd_tft_write_ram_prepare();
    lcd_tft_write_color(g_tft_lcd_data.front_color);
}

//画点
//x,y:坐标
//color:颜色
void lcd_tft_draw_point_ex(uint16_t x, uint16_t y, uint16_t color) {
    uint16_t old = g_tft_lcd_data.front_color;
    g_tft_lcd_data.front_color = color;
    lcd_tft_draw_point(x, y);
    g_tft_lcd_data.front_color = old;
}


//画线
//x1,y1:起点坐标
//x2,y2:终点坐标
void lcd_tft_draw_line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
    uint16_t t;
    int xerr = 0, yerr = 0, delta_x, delta_y, distance;
    int incx, incy, uRow, uCol;
    delta_x = x2 - x1; //计算坐标增量
    delta_y = y2 - y1;
    uRow = x1;
    uCol = y1;
    if (delta_x > 0)incx = 1; //设置单步方向
    else if (delta_x == 0)incx = 0;//垂直线
    else {
        incx = -1;
        delta_x = -delta_x;
    }
    if (delta_y > 0)incy = 1;
    else if (delta_y == 0)incy = 0;//水平线
    else {
        incy = -1;
        delta_y = -delta_y;
    }
    if (delta_x > delta_y)distance = delta_x; //选取基本增量坐标轴
    else distance = delta_y;
    for (t = 0; t <= distance + 1; t++)//画线输出
    {
        lcd_tft_draw_point(uRow, uCol);//画点
        xerr += delta_x;
        yerr += delta_y;
        if (xerr > distance) {
            xerr -= distance;
            uRow += incx;
        }
        if (yerr > distance) {
            yerr -= distance;
            uCol += incy;
        }
    }
}

void lcd_tft_draw_line_ex(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) {
    uint16_t old = g_tft_lcd_data.front_color;
    g_tft_lcd_data.front_color = color;
    lcd_tft_draw_line(x1, y1, x2, y2);
    g_tft_lcd_data.front_color = old;
}

//在指定位置画一个指定大小的圆
//(x,y):中心点
//r    :半径
void lcd_tft_draw_circle(uint16_t x0, uint16_t y0, uint8_t r) {
    int a, b;
    int di;
    a = 0;
    b = r;
    di = 3 - (r << 1);             //判断下个点位置的标志
    while (a <= b) {
        lcd_tft_draw_point(x0 + a, y0 - b);             //5
        lcd_tft_draw_point(x0 + b, y0 - a);             //0
        lcd_tft_draw_point(x0 + b, y0 + a);             //4
        lcd_tft_draw_point(x0 + a, y0 + b);             //6
        lcd_tft_draw_point(x0 - a, y0 + b);             //1
        lcd_tft_draw_point(x0 - b, y0 + a);
        lcd_tft_draw_point(x0 - a, y0 - b);             //2
        lcd_tft_draw_point(x0 - b, y0 - a);             //7
        a++;
        //使用Bresenham算法画圆
        if (di < 0)di += 4 * a + 6;
        else {
            di += 10 + 4 * (a - b);
            b--;
        }
    }
}

void lcd_tft_draw_circle_ex(uint16_t x0, uint16_t y0, uint8_t r, uint16_t color) {
    uint16_t old = g_tft_lcd_data.front_color;
    g_tft_lcd_data.front_color = color;
    lcd_tft_draw_circle(x0, y0, r);
    g_tft_lcd_data.front_color = old;
}

//设置LCD显示方向
//dir:0,竖屏;1,横屏
void lcd_tft_set_dir(uint8_t dir) {
    g_tft_lcd_data.dir = dir;         //横屏/竖屏
    if (dir == 0)  //默认竖屏方向
    {
        write_cmd(0x36);   //设置彩屏显示方向的寄存器
        write_data(0x4c);
        g_tft_lcd_data.height = 480;
        g_tft_lcd_data.width = 320;
    } else {
        write_cmd(0x36);
        write_data(0x2c);
        g_tft_lcd_data.height = 320;
        g_tft_lcd_data.width = 480;
    }
}


//设置窗口,并自动设置画点坐标到窗口左上角(sx,sy).
//sx,sy:窗口起始坐标(左上角)
//width,height:窗口宽度和高度,必须大于0!!
//窗体大小:width*height.
void lcd_tft_setWin(uint16_t sx, uint16_t sy, uint16_t width, uint16_t height) {
    write_cmd(0x2a);
    write_data(sx >> 8);
    write_data(sx & 0XFF);
    write_data(width >> 8);
    write_data(width & 0XFF);
    write_cmd(0x2b);
    write_data(sy >> 8);
    write_data(sy & 0XFF);
    write_data(height >> 8);
    write_data(height & 0XFF);
}

/**
 * @brief 发送开始写ram 的指令,配合@see lcd_tft_setWin 函数一起使用
 *  @note
 *      lcd_tft_setWin(0,0,120,120);
 *      lcd_tft_write_ram_prepare();
 *
 *
 *
 */
void lcd_tft_write_ram_prepare(void) {
    write_cmd(0x2c);
}


/**
 * @brief 全屏填充颜色
 * @param color 要清屏的填充色
 */
void lcd_tft_fill_color(uint16_t color) {
    uint16_t i, j;
    lcd_tft_setWin(0, 0, g_tft_lcd_data.width - 1, g_tft_lcd_data.height - 1);     //作用区域
    lcd_tft_write_ram_prepare();
    for (i = 0; i < g_tft_lcd_data.width; i++) {
        for (j = 0; j < g_tft_lcd_data.height; j++) {
            lcd_tft_write_color(color);
        }
    }
}
void lcd_tft_fill_area_color(uint16_t xState, uint16_t yState, uint16_t xEnd, uint16_t yEnd, uint16_t color){
    uint16_t temp;

    if((xState > xEnd) || (yState > yEnd))
    {
        return;
    }
    lcd_tft_setWin(xState, yState, xEnd, yEnd);
    lcd_tft_write_ram_prepare();
    xState = xEnd - xState + 1;
    yState = yEnd - yState + 1;

    while(xState--)
    {
        temp = yState;
        while (temp--)
        {
            lcd_tft_write_color(color);
        }
    }


}
#if 1

//在指定位置显示一个字符
//x,y:起始坐标
//num:要显示的字符:" "--->"~"
//size:字体大小 12/16/24
//mode:叠加方式(1)还是非叠加方式(0)
void lcd_tft_show_char(uint16_t x, uint16_t y, uint8_t num, uint8_t size, uint8_t mode) {
    uint8_t temp, t1, t;
    uint16_t y0 = y;
    uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2);        //得到字体一个字符对应点阵集所占的字节数
    num = num - ' ';//得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)
    for (t = 0; t < csize; t++) {
        if (size == 12)temp = ascii_1206[num][t];        //调用1206字体
        else if (size == 16)temp = ascii_1608[num][t];    //调用1608字体
        else if (size == 24)temp = ascii_2412[num][t];    //调用2412字体
        else return;                                //没有的字库
        for (t1 = 0; t1 < 8; t1++) {
            if (temp & 0x80)lcd_tft_draw_point_ex(x, y, g_tft_lcd_data.front_color);
            else if (mode == 0)lcd_tft_draw_point_ex(x, y, g_tft_lcd_data.back_color);
            temp <<= 1;
            y++;
            if (y >= g_tft_lcd_data.height)return;        //超区域了
            if ((y - y0) == size) {
                y = y0;
                x++;
                if (x >= g_tft_lcd_data.width)return;    //超区域了
                break;
            }
        }
    }
}

//显示字符串
//x,y:起点坐标
//width,height:区域大小
//size:字体大小
//*p:字符串起始地址
void lcd_tft_show_str(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t size, uint8_t *p) {
    uint8_t x0 = x;
    width += x;
    height += y;
    while ((*p <= '~') && (*p >= ' '))//判断是不是非法字符!
    {
        if (x >= width) {
            x = x0;
            y += size;
        }
        if (y >= height)break;//退出
        lcd_tft_show_char(x, y, *p, size, 0);
        x += size / 2;
        p++;
    }
}

#endif

static inline void tft_lcd_cmd_init() {
    write_cmd(0xE9);
    write_data(0x20);
    write_cmd(0x11); //Exit Sleep
    tft_delay_ms(120);
    write_cmd(0x3A);
    write_data(0x55);  //16Bit colors

    write_cmd(0xD1);
    write_data(0x00);
    write_data(0x65); //调试此值改善水纹
    write_data(0x1F);

    write_cmd(0xD0);
    write_data(0x07);
    write_data(0x07);
    write_data(0x80);

    write_cmd(0x36);     //Set_address_mode
    write_data(0x4c);    //4c

    write_cmd(0xC1);
    write_data(0x10);
    write_data(0x10);
    write_data(0x02);
    write_data(0x02);

    write_cmd(0xC0); //Set Default Gamma
    write_data(0x00);
    write_data(0x35);
    write_data(0x00);
    write_data(0x00);
    write_data(0x01);
    write_data(0x02);

    write_cmd(0xC4);
    write_data(0x03);

    write_cmd(0xC5); //Set frame rate
    write_data(0x01);

    write_cmd(0xD2); //power setting
    write_data(0x01);
    write_data(0x22);

    write_cmd(0xE7);
    write_data(0x38);

    write_cmd(0xF3);
    write_data(0x08);
    write_data(0x12);
    write_data(0x12);
    write_data(0x08);

    write_cmd(0xC8); //Set Gamma
    write_data(0x01);
    write_data(0x52);
    write_data(0x37);
    write_data(0x10);
    write_data(0x0d);
    write_data(0x01);
    write_data(0x04);
    write_data(0x51);
    write_data(0x77);
    write_data(0x01);
    write_data(0x01);
    write_data(0x0d);
    write_data(0x08);
    write_data(0x80);
    write_data(0x00);
    write_cmd(0x29);
}
#endif

硬件初始化

SRAM_HandleTypeDef TFTSRAM_Handler;    //SRAM句柄(用于控制LCD)
void fsmc_norsram_bank4_msp_init() {
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_FSMC_CLK_ENABLE();            //使能FSMC时钟
    __HAL_RCC_GPIOD_CLK_ENABLE();            //使能GPIOD时钟
    __HAL_RCC_GPIOE_CLK_ENABLE();            //使能GPIOE时钟
    __HAL_RCC_GPIOF_CLK_ENABLE();            //使能GPIOF时钟
    __HAL_RCC_GPIOG_CLK_ENABLE();            //使能GPIOG时钟
    //初始化PD0,1,4,5,8,9,10,14,15
    GPIO_Initure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_8 |
                       GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15;
    GPIO_Initure.Mode = GPIO_MODE_AF_PP;        //推挽复用
    GPIO_Initure.Pull = GPIO_PULLUP;            //上拉
    GPIO_Initure.Speed = GPIO_SPEED_HIGH;        //高速
    GPIO_Initure.Alternate = GPIO_AF12_FSMC;    //复用为FSMC
    HAL_GPIO_Init(GPIOD, &GPIO_Initure);     //初始化

    //初始化PE7,8,9,10,11,12,13,14,15
    GPIO_Initure.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 |
                       GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
    HAL_GPIO_Init(GPIOE, &GPIO_Initure);

    //初始化PF12
    GPIO_Initure.Pin = GPIO_PIN_12;
    HAL_GPIO_Init(GPIOF, &GPIO_Initure);

    //初始化PG12
    GPIO_Initure.Pin = GPIO_PIN_12;
    HAL_GPIO_Init(GPIOG, &GPIO_Initure);



}

void bsp_InitExLCD(void) {
    FSMC_NORSRAM_TimingTypeDef FSMC_ReadWriteTim;
    FSMC_NORSRAM_TimingTypeDef FSMC_WriteTim;
    TFTSRAM_Handler.Instance = FSMC_NORSRAM_DEVICE;
    TFTSRAM_Handler.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
    TFTSRAM_Handler.Init.NSBank = FSMC_NORSRAM_BANK4;                    //使用NE4
    TFTSRAM_Handler.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;    //地址/数据线不复用
    TFTSRAM_Handler.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;            //SRAM
    TFTSRAM_Handler.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16; //16位数据宽度
    TFTSRAM_Handler.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE; //是否使能突发访问,仅对同步突发存储器有效,此处未用到
    TFTSRAM_Handler.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;//等待信号的极性,仅在突发模式访问下有用
    TFTSRAM_Handler.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;   //存储器是在等待周期之前的一个时钟周期还是等待周期期间使能NWAIT
    TFTSRAM_Handler.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;    //存储器写使能
    TFTSRAM_Handler.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;           //等待使能位,此处未用到
    TFTSRAM_Handler.Init.ExtendedMode = FSMC_EXTENDED_MODE_ENABLE;        //读写使用不同的时序
    TFTSRAM_Handler.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;//是否使能同步传输模式下的等待信号,此处未用到
    TFTSRAM_Handler.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;           //禁止突发写
    TFTSRAM_Handler.Init.ContinuousClock = FSMC_CONTINUOUS_CLOCK_SYNC_ASYNC;
    //FMC读时序控制寄存器
    FSMC_ReadWriteTim.AddressSetupTime = 0x0F;        //地址建立时间(ADDSET)为16个HCLK 1/168M=6ns*16=96ns
    FSMC_ReadWriteTim.AddressHoldTime = 0;
    FSMC_ReadWriteTim.DataSetupTime = 60;                //数据保存时间为60个HCLK	=6*60=360ns
    FSMC_ReadWriteTim.AccessMode = FSMC_ACCESS_MODE_A;//模式A
    //FMC写时序控制寄存器
    FSMC_WriteTim.BusTurnAroundDuration = 0;            //总线周转阶段持续时间为0,此变量不赋值的话会莫名其妙的自动修改为4。导致程序运行正常
    FSMC_WriteTim.AddressSetupTime = 9;                //地址建立时间(ADDSET)为9个HCLK =54ns
    FSMC_WriteTim.AddressHoldTime = 0;
    FSMC_WriteTim.DataSetupTime = 8;                //数据保存时间为6ns*9个HCLK=54n
    FSMC_WriteTim.AccessMode = FSMC_ACCESS_MODE_A;    //模式A
    HAL_SRAM_Init(&TFTSRAM_Handler, &FSMC_ReadWriteTim, &FSMC_WriteTim);
}

触摸驱动(基于源文件修改)

头文件

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-7     shchl   first version
 */

#ifndef APP_NO_OS_BSP_TOUCH_H
#define APP_NO_OS_BSP_TOUCH_H

#include "bsp.h"
#define TP_PRES_DOWN 0x80  //触屏被按下
#define TP_CATH_PRES 0x40  //有按键按下了
#define CT_MAX_TOUCH 5    //电容屏支持的点数,固定为5点
#define TOUCH_READ_TIMES             5
#define TOUCH_READ_DISCARD           1
#define ERR_RANGE 50 //误差范围
enum touch_event_id_enum {
    TOUCH_DOWN_EVENT_ID, /*按下事件*/
    TOUCH_MOVE_EVENT_ID, /*移动事件*/
    TOUCH_RELEASE_EVENT_ID, /*释放事件*/
};


//触摸屏控制器
typedef struct lcd_touch_dev_struct {
    uint16_t x[CT_MAX_TOUCH];        //当前坐标
    uint16_t y[CT_MAX_TOUCH];        //电容屏有最多10组坐标,电阻屏则用x[0],y[0]代表:此次扫描时,触屏的坐标,用
    //x[9],y[9]存储第一次按下时的坐标.
    volatile uint16_t sta;                    //笔的状态
    //b15:按下1/松开0;
    //b14:0,没有按键按下;1,有按键按下.
    //b13~b10:保留
    //b9~b0:电容触摸屏按下的点数(0,表示未按下,1表示按下)
/触摸屏校准参数(电容屏不需要校准)//
    float xfac;
    float yfac;
    short xoff;
    short yoff;
//新增的参数,当触摸屏的左右上下完全颠倒时需要用到.
//b0:0,竖屏(适合左右为X坐标,上下为Y坐标的TP)
//   1,横屏(适合左右为Y坐标,上下为X坐标的TP)
//b1~6:保留.
//b7:0,电阻屏
//   1,电容屏
    uint8_t touchtype;

    /**
     * @brief 触摸发送事件
     * @param event_id  事件id
     * @param x x坐标
     * @param y y坐标
     */
    void (*touch_send_event)(uint8_t event_id, uint16_t x, uint16_t y);
    struct {
        uint8_t rd_x_cmd;
        uint8_t rd_y_cmd;
    } CNF;
} lcd_touch_dev;
#define PEN  		PBin(1)  	//T_PEN
#define DOUT 		PBin(2)   	//T_MISO
#define TDIN 		PFout(11)  	//T_MOSI
#define TCLK 		PBout(0)  	//T_SCK
#define TCS  		PCout(13)  	//T_CS
extern lcd_touch_dev g_lcd_touch; /*全局lcd触摸控制器设备*/

uint8_t bsp_InitLcdTouch(void);

uint8_t lcd_touch_scan(uint8_t tp);

void lcd_touch_scan2(void);

void lcd_touch_adjust(void);

uint16_t lcd_touch_read_ad(uint8_t cmd);

#endif //APP_NO_OS_BSP_TOUCH_H

源文件

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-7     shchl   first version
 */

#include "bsp_touch.h"
#include <math.h>
#include <stdlib.h>


lcd_touch_dev g_lcd_touch = {
        .CNF={0X90, 0XD0}

};
static void spi_us_delay(uint32_t us) { bsp_DelayDWTUS(us); }

//读取一个坐标值(x或者y)
//连续读取READ_TIMES次数据,对这些数据升序排列,
//然后去掉最低和最高LOST_VAL个数,取平均值
//xy:指令(CMD_RDX/CMD_RDY)
//返回值:读到的数据
uint16_t lcd_touch_read(uint8_t cmd) {
    uint16_t i, j;
    uint16_t buf[TOUCH_READ_TIMES];
    uint32_t sum = 0;
    uint16_t temp;
    for (i = 0; i < TOUCH_READ_TIMES; i++) {
        buf[i] = lcd_touch_read_ad(cmd);
    }
    for (i = 0; i < TOUCH_READ_TIMES - 1; i++)//排序
    {
        for (j = i + 1; j < TOUCH_READ_TIMES; j++) {
            if (buf[i] > buf[j])//升序排列
            {
                temp = buf[i];
                buf[i] = buf[j];
                buf[j] = temp;
            }
        }
    }
    sum = 0;
    for (i = TOUCH_READ_DISCARD; i < TOUCH_READ_TIMES - TOUCH_READ_DISCARD; i++)sum += buf[i];
    temp = sum / (TOUCH_READ_TIMES - 2 * TOUCH_READ_DISCARD);
    return temp;
}


//读取x,y坐标
//最小值不能少于100.
//x,y:读取到的坐标值
//返回值:0,失败;1,成功。
uint8_t lcd_touch_read_xy(uint16_t *x, uint16_t *y) {
    uint16_t xtemp, ytemp;
    xtemp = lcd_touch_read(g_lcd_touch.CNF.rd_x_cmd);
    ytemp = lcd_touch_read(g_lcd_touch.CNF.rd_y_cmd);
    if (xtemp < 100 || ytemp < 100)return 0;//读数失败
    *x = xtemp;
    *y = ytemp;
    return 1;//读数成功
}
//连续2次读取触摸屏IC,且这两次的偏差不能超过
//ERR_RANGE,满足条件,则认为读数正确,否则读数错误.
//该函数能大大提高准确度
//x,y:读取到的坐标值
//返回值:0,失败;1,成功。
uint8_t lcd_touch_read_xy2(uint16_t *x, uint16_t *y) {
    uint16_t x1, y1;
    uint16_t x2, y2;
    uint8_t flag;
    flag = lcd_touch_read_xy(&x1, &y1);
    if (flag == 0)return (0);
    flag = lcd_touch_read_xy(&x2, &y2);
    if (flag == 0)return (0);
    if (((x2 <= x1 && x1 < x2 + ERR_RANGE) || (x1 <= x2 && x2 < x1 + ERR_RANGE))//前后两次采样在+-50内
        && ((y2 <= y1 && y1 < y2 + ERR_RANGE) || (y1 <= y2 && y2 < y1 + ERR_RANGE))) {
        *x = (x1 + x2) / 2;
        *y = (y1 + y2) / 2;
        return 1;
    } else return 0;
}

/**
 * @brief lcd 触摸初始化
 * @return
 */
uint8_t bsp_InitLcdTouch(void) {
    GPIO_InitTypeDef GPIO_InitStructure;

    __HAL_RCC_GPIOB_CLK_ENABLE();			//开启GPIOB时钟
    __HAL_RCC_GPIOC_CLK_ENABLE();			//开启GPIOC时钟
    __HAL_RCC_GPIOF_CLK_ENABLE();			//开启GPIOF时钟
    //GPIOB1,2初始化设置
    GPIO_InitStructure.Pin=GPIO_PIN_1|GPIO_PIN_2;	//PB1/PB2 设置为上拉输入
    GPIO_InitStructure.Mode=GPIO_MODE_INPUT;  	//输入模式
    GPIO_InitStructure.Pull=GPIO_PULLUP;          //上拉
    GPIO_InitStructure.Speed=GPIO_SPEED_HIGH;     //高速
    HAL_GPIO_Init(GPIOB,&GPIO_InitStructure);     //初始化
    //PB0
    GPIO_InitStructure.Pin=GPIO_PIN_0; 			//PB0设置为推挽输出
    GPIO_InitStructure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
    HAL_GPIO_Init(GPIOB,&GPIO_InitStructure);     //初始化
    //PC13
    GPIO_InitStructure.Pin=GPIO_PIN_13;          	//PC13设置为推挽输出
    HAL_GPIO_Init(GPIOC,&GPIO_InitStructure);     //初始化
    //PF11
    GPIO_InitStructure.Pin=GPIO_PIN_11;          	//PF11设置推挽输出
    HAL_GPIO_Init(GPIOF,&GPIO_InitStructure);     //初始化
    /*初始值*/
    g_lcd_touch.xfac = -0.084720f;
    g_lcd_touch.yfac = -0.12764f;
    g_lcd_touch.xoff = 331;
    g_lcd_touch.yoff = 496;


    return lcd_touch_read_xy(&g_lcd_touch.x[0], &g_lcd_touch.y[0]) == 0 ? BSP_ERROR : BSP_EOK;
}

//触摸按键扫描
//tp:0,屏幕坐标;1,物理坐标(校准等特殊场合用)
//返回值:当前触屏状态.
//0,触屏无触摸;1,触屏有触摸
uint8_t lcd_touch_scan(uint8_t tp){
    if (PEN == 0)//有按键按下
    {
        if (tp)lcd_touch_read_xy2(&g_lcd_touch.x[0], &g_lcd_touch.y[0]);//读取物理坐标
        else if (lcd_touch_read_xy2(&g_lcd_touch.x[0], &g_lcd_touch.y[0]))//读取屏幕坐标
        {
            g_lcd_touch.x[0] = g_lcd_touch.xfac * g_lcd_touch.x[0] + g_lcd_touch.xoff;//将结果转换为屏幕坐标
            g_lcd_touch.y[0] = g_lcd_touch.yfac * g_lcd_touch.y[0] + g_lcd_touch.yoff;
        }
        if ((g_lcd_touch.sta & TP_PRES_DOWN) == 0)//之前没有被按下
        {
            g_lcd_touch.sta = TP_PRES_DOWN | TP_CATH_PRES;//按键按下
            g_lcd_touch.x[4] = g_lcd_touch.x[0];//记录第一次按下时的坐标
            g_lcd_touch.y[4] = g_lcd_touch.y[0];
        }
    } else {
        if (g_lcd_touch.sta & TP_PRES_DOWN)//之前是被按下的
        {
            g_lcd_touch.sta &= ~(1 << 7);//标记按键松开
        } else//之前就没有被按下
        {
            g_lcd_touch.x[4] = 0;
            g_lcd_touch.y[4] = 0;
            g_lcd_touch.x[0] = 0xffff;
            g_lcd_touch.y[0] = 0xffff;
        }
    }
    return g_lcd_touch.sta & TP_PRES_DOWN;//返回当前的触屏状态


}
//提示校准结果(各个参数)
void
TP_Adj_Info_Show(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3,
                 uint16_t fac) {

    BSP_Printf("(%d,%d)----(%d,%d)\r\n(%d,%d)----(%d,%d)\r\n", x0, y0, x1, y1, x2, y2, x3, y3);
    BSP_Printf("fac:%d\r\n", fac);

}
//与LCD部分有关的函数
//画一个触摸点
//用来校准用的
//x,y:坐标
//color:颜色
void TP_Drow_Touch_Point(uint16_t x, uint16_t y, uint16_t color) {
    g_tft_lcd_data.front_color = color;
    lcd_tft_draw_line(x - 12, y, x + 13, y);//横线
    lcd_tft_draw_line(x, y - 12, x, y + 13);//竖线
    lcd_tft_draw_point(x + 1, y + 1);
    lcd_tft_draw_point(x - 1, y + 1);
    lcd_tft_draw_point(x + 1, y - 1);
    lcd_tft_draw_point(x - 1, y - 1);
    lcd_tft_draw_circle(x, y, 6);//画中心圈
}

//触摸屏校准代码
//得到四个校准参数
void lcd_touch_adjust(void) {

    uint16_t pos_temp[4][2];//坐标缓存值
    uint8_t cnt = 0;
    uint16_t d1, d2;
    uint32_t tem1, tem2;
    double fac;
    uint16_t outtime = 0;
    cnt = 0;
    lcd_tft_fill_color(WHITE);//清屏
    g_tft_lcd_data.front_color = BLACK;
    TP_Drow_Touch_Point(20, 20, RED);//画点1
    g_lcd_touch.sta = 0;//消除触发信号
    g_lcd_touch.xfac = 0;//xfac用来标记是否校准过,所以校准之前必须清掉!以免错误
    while (1)//如果连续10秒钟没有按下,则自动退出
    {
        lcd_touch_scan(1);//扫描物理坐标
        if ((g_lcd_touch.sta & 0xc0) == TP_CATH_PRES)//按键按下了一次(此时按键松开了.)
        {
            outtime = 0;
            g_lcd_touch.sta &= ~(1 << 6);//标记按键已经被处理过了.

            pos_temp[cnt][0] = g_lcd_touch.x[0];
            pos_temp[cnt][1] = g_lcd_touch.y[0];
            cnt++;
            switch (cnt) {
                case 1:
                    TP_Drow_Touch_Point(20, 20, WHITE);//清除点1
                    TP_Drow_Touch_Point(g_tft_lcd_data.width - 20, 20, RED);    //画点2
                    break;
                case 2:
                    TP_Drow_Touch_Point(g_tft_lcd_data.width - 20, 20, WHITE);    //清除点2
                    TP_Drow_Touch_Point(20, g_tft_lcd_data.height - 20, RED);    //画点3
                    break;
                case 3:
                    TP_Drow_Touch_Point(20, g_tft_lcd_data.height - 20, WHITE);            //清除点3
                    TP_Drow_Touch_Point(g_tft_lcd_data.width - 20, g_tft_lcd_data.height - 20, RED);    //画点4
                    break;
                case 4:     //全部四个点已经得到
                    //对边相等
                    tem1 = abs(pos_temp[0][0] - pos_temp[1][0]);//x1-x2
                    tem2 = abs(pos_temp[0][1] - pos_temp[1][1]);//y1-y2
                    tem1 *= tem1;
                    tem2 *= tem2;
                    d1 = sqrt(tem1 + tem2);//得到1,2的距离

                    tem1 = abs(pos_temp[2][0] - pos_temp[3][0]);//x3-x4
                    tem2 = abs(pos_temp[2][1] - pos_temp[3][1]);//y3-y4
                    tem1 *= tem1;
                    tem2 *= tem2;
                    d2 = sqrt(tem1 + tem2);//得到3,4的距离
                    fac = (float) d1 / d2;
                    if (fac < 0.95 || fac > 1.05 || d1 == 0 || d2 == 0)//不合格
                    {
                        cnt = 0;
                        TP_Drow_Touch_Point(g_tft_lcd_data.width - 20, g_tft_lcd_data.height - 20, WHITE);    //清除点4
                        TP_Drow_Touch_Point(20, 20, RED);                                //画点1

                        continue;
                    }
                    tem1 = abs(pos_temp[0][0] - pos_temp[2][0]);//x1-x3
                    tem2 = abs(pos_temp[0][1] - pos_temp[2][1]);//y1-y3
                    tem1 *= tem1;
                    tem2 *= tem2;
                    d1 = sqrt(tem1 + tem2);//得到1,3的距离

                    tem1 = abs(pos_temp[1][0] - pos_temp[3][0]);//x2-x4
                    tem2 = abs(pos_temp[1][1] - pos_temp[3][1]);//y2-y4
                    tem1 *= tem1;
                    tem2 *= tem2;
                    d2 = sqrt(tem1 + tem2);//得到2,4的距离
                    fac = (float) d1 / d2;
                    if (fac < 0.95 || fac > 1.05)//不合格
                    {
                        BSP_Printf("need adjust again\r\n");
                        cnt = 0;
                        TP_Drow_Touch_Point(g_tft_lcd_data.width - 20, g_tft_lcd_data.height - 20, WHITE);    //清除点4
                        TP_Drow_Touch_Point(20, 20, RED);                                //画点1
                        TP_Adj_Info_Show(pos_temp[0][0], pos_temp[0][1], pos_temp[1][0], pos_temp[1][1], pos_temp[2][0],
                                         pos_temp[2][1], pos_temp[3][0], pos_temp[3][1], fac * 100);//显示数据
                        continue;
                    }//正确了
                    TP_Adj_Info_Show(pos_temp[0][0], pos_temp[0][1], pos_temp[1][0], pos_temp[1][1], pos_temp[2][0],
                                     pos_temp[2][1], pos_temp[3][0], pos_temp[3][1], fac * 100);//显示数据
                    //对角线相等
                    tem1 = abs(pos_temp[1][0] - pos_temp[2][0]);//x1-x3
                    tem2 = abs(pos_temp[1][1] - pos_temp[2][1]);//y1-y3
                    tem1 *= tem1;
                    tem2 *= tem2;
                    d1 = sqrt(tem1 + tem2);//得到1,4的距离

                    tem1 = abs(pos_temp[0][0] - pos_temp[3][0]);//x2-x4
                    tem2 = abs(pos_temp[0][1] - pos_temp[3][1]);//y2-y4
                    tem1 *= tem1;
                    tem2 *= tem2;
                    d2 = sqrt(tem1 + tem2);//得到2,3的距离
                    fac = (float) d1 / d2;
                    if (fac < 0.95 || fac > 1.05)//不合格
                    {
                        cnt = 0;
                        TP_Drow_Touch_Point(g_tft_lcd_data.width - 20, g_tft_lcd_data.height - 20, WHITE);    //清除点4
                        TP_Drow_Touch_Point(20, 20, RED);                                //画点1
                        TP_Adj_Info_Show(pos_temp[0][0], pos_temp[0][1], pos_temp[1][0], pos_temp[1][1], pos_temp[2][0],
                                         pos_temp[2][1], pos_temp[3][0], pos_temp[3][1], fac * 100);//显示数据
                        continue;
                    }//正确了
                    //计算结果
                    g_lcd_touch.xfac = (float) (g_tft_lcd_data.width - 40) / (pos_temp[1][0] - pos_temp[0][0]);//得到xfac
                    g_lcd_touch.xoff = (g_tft_lcd_data.width - g_lcd_touch.xfac * (pos_temp[1][0] + pos_temp[0][0])) / 2;//得到xoff

                    g_lcd_touch.yfac = (float) (g_tft_lcd_data.height - 40) / (pos_temp[2][1] - pos_temp[0][1]);//得到yfac
                    g_lcd_touch.yoff = (g_tft_lcd_data.height - g_lcd_touch.yfac * (pos_temp[2][1] + pos_temp[0][1])) / 2;//得到yoff
                    if (abs(g_lcd_touch.xfac) > 2.0 || abs(g_lcd_touch.yfac) > 2.0)//触屏和预设的相反了.
                    {
                        cnt = 0;
                        TP_Drow_Touch_Point(g_tft_lcd_data.width - 20, g_tft_lcd_data.height - 20, WHITE);    //清除点4
                        TP_Drow_Touch_Point(20, 20, RED);                                //画点1
                        BSP_Printf("TP Need readjust!TP Need readjust!\r\n");
                        g_lcd_touch.touchtype = !g_lcd_touch.touchtype;//修改触屏类型.
                        if (g_lcd_touch.touchtype)//X,Y方向与屏幕相反
                        {
                            g_lcd_touch.CNF.rd_x_cmd = 0X90;
                            g_lcd_touch.CNF.rd_y_cmd = 0XD0;
                        } else                   //X,Y方向与屏幕相同
                        {
                            g_lcd_touch.CNF.rd_x_cmd = 0XD0;
                            g_lcd_touch.CNF.rd_y_cmd = 0X90;
                        }
                        continue;
                    }
                    g_tft_lcd_data.front_color = BLUE;
                    lcd_tft_fill_color(WHITE);//清屏
                    HAL_Delay(1000);
                    return;//校正完成
            }
        }
        HAL_Delay(10);
//        outtime++;
//        if (outtime > 1000) {
//            break;
//        }
    }

}
/*--------------------------------------------------模拟通讯协议------------------------------------------*/




static inline void spi_write_byte(uint8_t dat);


//SPI读数据
//从触摸屏IC读取adc值
//cmd:指令
//返回值:读到的数据
uint16_t lcd_touch_read_ad(uint8_t cmd) {

    uint8_t count=0;
    uint16_t Num=0;
    TCLK=0;		//先拉低时钟
    TDIN=0; 	//拉低数据线
    TCS=0; 		//选中触摸屏IC
    spi_write_byte(cmd);//发送命令字
    bsp_DelayDWTUS(6);//ADS7846的转换时间最长为6us
    TCLK=0;
    bsp_DelayDWTUS(1);
    TCLK=1;		//给1个时钟,清除BUSY
    bsp_DelayDWTUS(1);
    TCLK=0;
    for(count=0;count<16;count++)//读出16位数据,只有高12位有效
    {
        Num<<=1;
        TCLK=0;	//下降沿有效
        bsp_DelayDWTUS(1);
        TCLK=1;
        if(DOUT)Num++;
    }
    Num>>=4;   	//只有高12位有效.
    TCS=1;		//释放片选
    return(Num);
}

static inline void spi_write_byte(uint8_t dat) {

    uint8_t count=0;
    for(count=0;count<8;count++)
    {
        if(dat&0x80)TDIN=1;
        else TDIN=0;
        dat<<=1;
        TCLK=0;
        bsp_DelayDWTUS(1);
        TCLK=1;		//上升沿有效
    }
}


/**
 * @brief 触摸扫描,事件驱动
 *  @note 需要指定对应的函数回调  @see g_lcd_touch.touch_send_event
 */
void lcd_touch_scan2(void) {
    static uint8_t s_tp_down = 0;
    static uint16_t x_save, y_save;
    static uint8_t s_count = 0;
    uint16_t x = 0, y = 0;
    if (PEN == 1) { // 没有按键按下
        /* 持续按下时,INT电平是脉冲信号。每隔18ms出现1个宽度4ms的高电平。 */
        if (s_tp_down == 1) {
            if (++s_count > 2) {
                s_count = 0;
                s_tp_down = 0;
                //释放
                if (g_lcd_touch.touch_send_event) {
                    BSP_Printf("TOUCH_RELEASE_EVENT_ID:%d,%d\r\n", x_save, y_save);
                    g_lcd_touch.touch_send_event(TOUCH_RELEASE_EVENT_ID, x_save, y_save);
                }
            }
        }
        return;
    }
    s_count = 0;
    if (lcd_touch_read_xy2(&x, &y))//读取屏幕坐标
    {
        x = (uint16_t) (g_lcd_touch.xfac * x) + g_lcd_touch.xoff;//将结果转换为屏幕坐标
        y = (uint16_t) (g_lcd_touch.yfac * y) + g_lcd_touch.yoff;
    } else {
        return; /*数据无效*/
    }
    x_save = x;    /* 保存坐标,用于释放事件 */
    y_save = y;
    if (s_tp_down == 0) {
        s_tp_down = 1;
        if (g_lcd_touch.touch_send_event) {
            BSP_Printf("TOUCH_DOWN_EVENT_ID:%d,%d\r\n", x_save, y_save);

            g_lcd_touch.touch_send_event(TOUCH_DOWN_EVENT_ID, x_save, y_save);

        } else {
            if (g_lcd_touch.touch_send_event) {
                BSP_Printf("TOUCH_MOVE_EVENT_ID:%d,%d\r\n", x_save, y_save);
                g_lcd_touch.touch_send_event(TOUCH_MOVE_EVENT_ID, x_save, y_save);
            }
        }

    }
}


项目添加emwin头文件和链接静态库(采用CMakeLists来管理的)

在这里插入图片描述

根节点CMakeLists文件

在这里插入图片描述

对应的EmWin6的CMakeList文件

target_link_directories(${PROJECT_NAME}.elf
        PUBLIC
        ${CMAKE_CURRENT_LIST_DIR}/Libs
)
target_link_libraries(${PROJECT_NAME}.elf
        PRIVATE
        libGUI_v7em_fpv4_sp_d16_hard_OS0.a
)
target_sources(${PROJECT_NAME}.elf
        PRIVATE
        ${CMAKE_CURRENT_LIST_DIR}/OS/GUI_X_OS.c
        ${CMAKE_CURRENT_LIST_DIR}/OS/GUI_X_Touch_Analog.c
        ${CMAKE_CURRENT_LIST_DIR}/OS/GUIConf.c
        ${CMAKE_CURRENT_LIST_DIR}/OS/GUIDRV_Template.c
        ${CMAKE_CURRENT_LIST_DIR}/OS/LCDConf_FlexColor_Template.c
)

target_include_directories(${PROJECT_NAME}.elf
        PUBLIC
       ${CMAKE_CURRENT_LIST_DIR}/Inc

)



EmWIn源码实现

GUI_X_OS.c (与os相关的)

/*********************************************************************
*                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        : GUI_X_OS.C
Purpose     : This file provides emWin Interface with FreeRTOS
---------------------------END-OF-HEADER------------------------------
*/

/**
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; 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
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/

#include "GUI.h"
#include "includes.h"

/*********************************************************************
*
* Global data
*/
static TX_MUTEX osMutex;
static TX_SEMAPHORE osSemaphore;

static TX_SEMAPHORE KeySem;
static int KeyPressed;
static char KeyIsInited = TX_FALSE;

/*********************************************************************
*
* Timing:
* GUI_X_GetTime()
* GUI_X_Delay(int)

Some timing dependent routines require a GetTime
and delay function. Default time unit (tick), normally is
1 ms.
*/

int GUI_X_GetTime(void) {
    return ((int) tx_time_get());
}

void GUI_X_Delay(int ms) {
    tx_thread_sleep(ms);
}

/*********************************************************************
*
* GUI_X_ExecIdle
*
* Note:
* Called if WM is in idle state
*/

void GUI_X_ExecIdle(void) {
    GUI_X_Delay(1);
}

/*********************************************************************
*
* Multitasking:
*
* GUI_X_InitOS()
* GUI_X_GetTaskId()
* GUI_X_Lock()
* GUI_X_Unlock()
*
* Note:
* The following routines are required only if emWin is used in a
* true multi task environment, which means you have more than one
* thread using the emWin API.
* In this case the
* #define GUI_OS 1
* needs to be in GUIConf.h
*/

/* Init OS */
void GUI_X_InitOS(void) {

    /* Create the Mutex used by the two threads */
    tx_mutex_create(&osMutex, "emWinMutex", TX_NO_INHERIT);


    /* Create the Semaphore used by the two threads */
    tx_semaphore_create(&osSemaphore, "emWinSem", 1);

}

void GUI_X_Unlock(void) {
    tx_mutex_put(&osMutex);
}

void GUI_X_Lock(void) {
    tx_mutex_get(&osMutex, TX_WAIT_FOREVER);
}

/* Get Task handle */
U32 GUI_X_GetTaskId(void) {


    return tx_thread_identify()->tx_thread_id;
}

void GUI_X_WaitEvent(void) {
    tx_semaphore_get(&osSemaphore, TX_WAIT_FOREVER);
}

void GUI_X_SignalEvent(void) {
    tx_semaphore_put(&osSemaphore);
}

/*
*********************************************************************************************************
* KEYBOARD INTERFACE FUNCTIONS
*
* Purpose: The keyboard routines are required only by some widgets.
* If widgets are not used, they may be eliminated.
*********************************************************************************************************
*/

/*
*********************************************************************************************************
* CheckInit()
*
* Description : Initialize the GUI keyboard if it is not already done.
*
* Argument(s) : none.
*
* Return(s) : none.
*
* Caller(s) : GUI_X_WaitKey().
* GUI_X_GetKey().
*
* Note(s) : none.
*********************************************************************************************************
*/
void CheckInit(void) {
    if (KeyIsInited == TX_FALSE) {
        KeyIsInited = TX_TRUE;
        GUI_X_Init();
    }
}

/*********************************************************************
*
* GUI_X_Init()
*
* Note:
* GUI_X_Init() is called from GUI_Init is a possibility to init
* some hardware which needs to be up and running before the GUI.
* If not required, leave this routine blank.
*/

void GUI_X_Init(void) {
    tx_semaphore_create(&KeySem, "keySem", 0);

}

/*
*********************************************************************************************************
* GUI_X_GetKey()
*
* Description : Get the pressed key.
*
* Argument(s) : none.
*
* Return(s) : Pressed key.
*
* Caller(s) : various.
*
* Note(s) : none.
*********************************************************************************************************
*/
int GUI_X_GetKey(void) {
    int r;
    r = KeyPressed;
    CheckInit();
    KeyPressed = 0;
    return (r);
}

/*
*********************************************************************************************************
* GUI_X_WaitKey()
*
* Description : Wait for a key to be pressed and return it.
*
* Argument(s) : none.
*
* Return(s) : Pressed key.
*
* Caller(s) : various.
*
* Note(s) : none.
*********************************************************************************************************
*/
int GUI_X_WaitKey(void) {
    int r;
    CheckInit();
    if (KeyPressed == 0) {
        tx_semaphore_get(&KeySem, TX_WAIT_FOREVER);
    }
    r = KeyPressed;
    KeyPressed = 0;
    return (r);
}

/*
*********************************************************************************************************
* GUI_X_StoreKey()
*
* Description : Store the pressed key.
*
* Argument(s) : Pressed key.
*
* Return(s) : none.
*
* Caller(s) : various.
*
* Note(s) : none.
*********************************************************************************************************
*/
void GUI_X_StoreKey(int k) //--------------( 14)
{
    KeyPressed = k;
    tx_semaphore_put(&KeySem);
}

/*********************************************************************
*
* Logging: OS dependent

Note:
Logging is used in higher debug levels only. The typical target
build does not use logging and does therefor not require any of
the logging routines below. For a release build without logging
the routines below may be eliminated to save some space.
(If the linker is not function aware and eliminates unreferenced
functions automatically)

*/

void GUI_X_Log(const char *s) {}

void GUI_X_Warn(const char *s) {}

void GUI_X_ErrorOut(const char *s) {}

/*************************** End of file ****************************/

GUIConf.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        : GUIConf.c
Purpose     : Display controller initialization
---------------------------END-OF-HEADER------------------------------
*/

/**
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; 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 "includes.h"
/*********************************************************************
*
*       Defines
*
**********************************************************************
*/
//
// Define the available number of bytes available for the GUI
//
#define GUI_NUMBYTES (PKG_STEMWIN_MEM_SIZE)

/* Define the average block size */
#define GUI_BLOCKSIZE 0x80


/*********************************************************************
*
*       Public code
*
**********************************************************************
*/
/*********************************************************************
*
*       GUI_X_Config
*
* Purpose:
*   Called during the initialization process in order to set up the
*   available memory for the GUI.
*/
void GUI_X_Config(void) {
#if 0
    //
    // 32 bit aligned memory area
    //
    static U32 aMemory[GUI_NUMBYTES / 4];
    //
    // Assign memory to emWin
    //
    GUI_ALLOC_AssignMemory(aMemory, GUI_NUMBYTES);
    GUI_ALLOC_SetAvBlockSize(GUI_BLOCKSIZE);
#else /*外部sram*/
#define EMWIN_MEM_SIZE 500*1024
    extern struct rt_memheap ex_ram_heap;
    static U32 *aMemory;
    aMemory = (U32 *) bsp_malloc(EMWIN_MEM_SIZE);
    /*  Assign memory to emWin */
    GUI_ALLOC_AssignMemory(aMemory, EMWIN_MEM_SIZE);
    GUI_ALLOC_SetAvBlockSize(GUI_BLOCKSIZE);
#endif
    //
    // Set default font
    //
    GUI_SetDefaultFont(GUI_FONT_6X8);
}

/*************************** End of file ****************************/

void APPW_X_FS_Init(void) {

}

GUIDRV_Template.c (驱动,画点,读点)

/*********************************************************************
*          Portions COPYRIGHT 2013 STMicroelectronics                *
*          Portions SEGGER Microcontroller GmbH & Co. KG             *
*        Solutions for real time microcontroller applications        *
**********************************************************************
*                                                                    *
*        (c) 1996 - 2013  SEGGER Microcontroller GmbH & Co. KG       *
*                                                                    *
*        Internet: www.segger.com    Support:  support@segger.com    *
*                                                                    *
**********************************************************************

** emWin V5.22 - 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        : GUIDRV_Template.c
Purpose     : Template driver, could be used as starting point for new
              simple display drivers supporting only one color depth.
---------------------------END-OF-HEADER------------------------------
*/

/**
  ******************************************************************************
  * @attention
  *
  * Licensed under MCD-ST Liberty SW License Agreement V2, (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/software_license_agreement_liberty_v2
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *
  ******************************************************************************
  */

#include <stddef.h>

#include "LCD_Private.h"
#include "GUI_Private.h"
#include "LCD_ConfDefaults.h"
#include "includes.h"


/*********************************************************************
*
*       Defines
*
**********************************************************************
*/
/*********************************************************************
*
*       Macros for MIRROR_, SWAP_ and LUT_
*/
#if (!defined (LCD_LUT_COM) && !defined(LCD_LUT_SEG))
#if   (!LCD_MIRROR_X && !LCD_MIRROR_Y && !LCD_SWAP_XY)
#define LOG2PHYS_X(x, y) x
#define LOG2PHYS_Y(x, y) y
#elif (!LCD_MIRROR_X && !LCD_MIRROR_Y &&  LCD_SWAP_XY)
#define LOG2PHYS_X(x, y) y
    #define LOG2PHYS_Y(x, y) x
  #elif (!LCD_MIRROR_X &&  LCD_MIRROR_Y && !LCD_SWAP_XY)
    #define LOG2PHYS_X(x, y) x
    #define LOG2PHYS_Y(x, y) LCD_YSIZE - 1 - (y)
  #elif (!LCD_MIRROR_X &&  LCD_MIRROR_Y &&  LCD_SWAP_XY)
    #define LOG2PHYS_X(x, y) y
    #define LOG2PHYS_Y(x, y) LCD_XSIZE - 1 - (x)
  #elif ( LCD_MIRROR_X && !LCD_MIRROR_Y && !LCD_SWAP_XY)
    #define LOG2PHYS_X(x, y) LCD_XSIZE - 1 - (x)
    #define LOG2PHYS_Y(x, y) y
  #elif ( LCD_MIRROR_X && !LCD_MIRROR_Y &&  LCD_SWAP_XY)
    #define LOG2PHYS_X(x, y) LCD_YSIZE - 1 - (y)
    #define LOG2PHYS_Y(x, y) x
  #elif ( LCD_MIRROR_X &&  LCD_MIRROR_Y && !LCD_SWAP_XY)
    #define LOG2PHYS_X(x, y) LCD_XSIZE - 1 - (x)
    #define LOG2PHYS_Y(x, y) LCD_YSIZE - 1 - (y)
  #elif ( LCD_MIRROR_X &&  LCD_MIRROR_Y &&  LCD_SWAP_XY)
    #define LOG2PHYS_X(x, y) LCD_YSIZE - 1 - (y)
    #define LOG2PHYS_Y(x, y) LCD_XSIZE - 1 - (x)
#endif
#else
#if   ( defined (LCD_LUT_COM) && !defined(LCD_LUT_SEG))
    #define LOG2PHYS_X(x, y) x
    #define LOG2PHYS_Y(x, y) LCD__aLine2Com0[y]
  #elif (!defined (LCD_LUT_COM) &&  defined(LCD_LUT_SEG))
    #define LOG2PHYS_X(x, y) LCD__aCol2Seg0[x]
    #define LOG2PHYS_Y(x, y) y
  #elif ( defined (LCD_LUT_COM) &&  defined(LCD_LUT_SEG))
    #define LOG2PHYS_X(x, y) LCD__aCol2Seg0[x]
    #define LOG2PHYS_Y(x, y) LCD__aLine2Com0[y]
  #endif
#endif

/*********************************************************************
*
*       Types
*
**********************************************************************
*/
typedef struct {
    U32 VRAMAddr;
    int xSize, ySize;
    int vxSize, vySize;
    int vxSizePhys;
    int BitsPerPixel;
} DRIVER_CONTEXT_TEMPLATE;

/*********************************************************************
*
*       Static functions
*
**********************************************************************
*/
/*********************************************************************
 打点函数
*/
static void _SetPixelIndex(GUI_DEVICE * pDevice, int x, int y, int PixelIndex) {
    lcd_tft_draw_point_ex(x,y,PixelIndex); //调用tftlcd.c文件中的打点函数

}


/*********************************************************************
*
	读点函数
*/
static unsigned int _GetPixelIndex(GUI_DEVICE * pDevice, int x, int y) {
    unsigned int PixelIndex;
#if (LCD_MIRROR_X == 1) || (LCD_MIRROR_Y == 1) || (LCD_SWAP_XY == 1)
    int xPhys, yPhys;

      xPhys = LOG2PHYS_X(x, y);
      yPhys = LOG2PHYS_Y(x, y);
#else
#define xPhys x
#define yPhys y
#endif
    GUI_USE_PARA(pDevice);
    GUI_USE_PARA(x);
    GUI_USE_PARA(y);
    {
        PixelIndex = lcd_tft_read_color(x,y);
    }
#if (LCD_MIRROR_X == 0) && (LCD_MIRROR_Y == 0) && (LCD_SWAP_XY == 0)
#undef xPhys
#undef yPhys
#endif
    return PixelIndex;

}
/*********************************************************************
*
*       _XorPixel
*/
static void _XorPixel(GUI_DEVICE * pDevice, int x, int y) {
    LCD_PIXELINDEX PixelIndex;
    LCD_PIXELINDEX IndexMask;

    PixelIndex = _GetPixelIndex(pDevice, x, y);
    IndexMask  = pDevice->pColorConvAPI->pfGetIndexMask();
    _SetPixelIndex(pDevice, x, y, PixelIndex ^ IndexMask);
}

/*********************************************************************
*
*       _FillRect
*/
static void _FillRect(GUI_DEVICE * pDevice, int x0, int y0, int x1, int y1) {

    lcd_tft_fill_area_color(x0,y0,x1,y1,LCD_COLORINDEX);

}


/*********************************************************************
*
*       _DrawHLine
*/
static void _DrawHLine(GUI_DEVICE * pDevice, int x0, int y, int x1) {
    _FillRect(pDevice, x0, y, x1, y);
}

/*********************************************************************
*
*       _DrawVLine, not optimized
*/
static void _DrawVLine(GUI_DEVICE * pDevice, int x, int y0, int y1) {
    _FillRect(pDevice, x, y0, x, y1);
}

/*********************************************************************
*
*       Draw Bitmap 1 BPP
*/
static void _DrawBitLine1BPP(GUI_DEVICE * pDevice, int x, int y, U8 const GUI_UNI_PTR * p, int Diff, int xsize, const LCD_PIXELINDEX * pTrans) {
    LCD_PIXELINDEX IndexMask, Index0, Index1, Pixel;

    Index0 = *(pTrans + 0);
    Index1 = *(pTrans + 1);
    x += Diff;
    switch (GUI_pContext->DrawMode & (LCD_DRAWMODE_TRANS | LCD_DRAWMODE_XOR)) {
        case 0:
            do {
                _SetPixelIndex(pDevice, x++, y, (*p & (0x80 >> Diff)) ? Index1 : Index0);
                if (++Diff == 8) {
                    Diff = 0;
                    p++;
                }
            } while (--xsize);
            break;
        case LCD_DRAWMODE_TRANS:
            do {
                if (*p & (0x80 >> Diff))
                    _SetPixelIndex(pDevice, x, y, Index1);
                x++;
                if (++Diff == 8) {
                    Diff = 0;
                    p++;
                }
            } while (--xsize);
            break;
        case LCD_DRAWMODE_XOR | LCD_DRAWMODE_TRANS:
        case LCD_DRAWMODE_XOR:
            IndexMask = pDevice->pColorConvAPI->pfGetIndexMask();
            do {
                if (*p & (0x80 >> Diff)) {
                    Pixel = _GetPixelIndex(pDevice, x, y);
                    _SetPixelIndex(pDevice, x, y, Pixel ^ IndexMask);
                }
                x++;
                if (++Diff == 8) {
                    Diff = 0;
                    p++;
                }
            } while (--xsize);
            break;
    }
}

/*********************************************************************
*
*       Draw Bitmap 2 BPP
*/
static void  _DrawBitLine2BPP(GUI_DEVICE * pDevice, int x, int y, U8 const GUI_UNI_PTR * p, int Diff, int xsize, const LCD_PIXELINDEX * pTrans) {
    LCD_PIXELINDEX Pixels, PixelIndex;
    int CurrentPixel, Shift, Index;

    Pixels = *p;
    CurrentPixel = Diff;
    x += Diff;
    switch (GUI_pContext->DrawMode & (LCD_DRAWMODE_TRANS | LCD_DRAWMODE_XOR)) {
        case 0:
            if (pTrans) {
                do {
                    Shift = (3 - CurrentPixel) << 1;
                    Index = (Pixels & (0xC0 >> (6 - Shift))) >> Shift;
                    PixelIndex = *(pTrans + Index);
                    _SetPixelIndex(pDevice, x++, y, PixelIndex);
                    if (++CurrentPixel == 4) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            } else {
                do {
                    Shift = (3 - CurrentPixel) << 1;
                    Index = (Pixels & (0xC0 >> (6 - Shift))) >> Shift;
                    _SetPixelIndex(pDevice, x++, y, Index);
                    if (++CurrentPixel == 4) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            }
            break;
        case LCD_DRAWMODE_TRANS:
            if (pTrans) {
                do {
                    Shift = (3 - CurrentPixel) << 1;
                    Index = (Pixels & (0xC0 >> (6 - Shift))) >> Shift;
                    if (Index) {
                        PixelIndex = *(pTrans + Index);
                        _SetPixelIndex(pDevice, x, y, PixelIndex);
                    }
                    x++;
                    if (++CurrentPixel == 4) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            } else {
                do {
                    Shift = (3 - CurrentPixel) << 1;
                    Index = (Pixels & (0xC0 >> (6 - Shift))) >> Shift;
                    if (Index) {
                        _SetPixelIndex(pDevice, x, y, Index);
                    }
                    x++;
                    if (++CurrentPixel == 4) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            }
            break;
    }
}

/*********************************************************************
*
*       Draw Bitmap 4 BPP
*/
static void  _DrawBitLine4BPP(GUI_DEVICE * pDevice, int x, int y, U8 const GUI_UNI_PTR * p, int Diff, int xsize, const LCD_PIXELINDEX * pTrans) {
    LCD_PIXELINDEX Pixels, PixelIndex;
    int CurrentPixel, Shift, Index;

    Pixels = *p;
    CurrentPixel = Diff;
    x += Diff;
    switch (GUI_pContext->DrawMode & (LCD_DRAWMODE_TRANS | LCD_DRAWMODE_XOR)) {
        case 0:
            if (pTrans) {
                do {
                    Shift = (1 - CurrentPixel) << 2;
                    Index = (Pixels & (0xF0 >> (4 - Shift))) >> Shift;
                    PixelIndex = *(pTrans + Index);
                    _SetPixelIndex(pDevice, x++, y, PixelIndex);
                    if (++CurrentPixel == 2) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            } else {
                do {
                    Shift = (1 - CurrentPixel) << 2;
                    Index = (Pixels & (0xF0 >> (4 - Shift))) >> Shift;
                    _SetPixelIndex(pDevice, x++, y, Index);
                    if (++CurrentPixel == 2) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            }
            break;
        case LCD_DRAWMODE_TRANS:
            if (pTrans) {
                do {
                    Shift = (1 - CurrentPixel) << 2;
                    Index = (Pixels & (0xF0 >> (4 - Shift))) >> Shift;
                    if (Index) {
                        PixelIndex = *(pTrans + Index);
                        _SetPixelIndex(pDevice, x, y, PixelIndex);
                    }
                    x++;
                    if (++CurrentPixel == 2) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            } else {
                do {
                    Shift = (1 - CurrentPixel) << 2;
                    Index = (Pixels & (0xF0 >> (4 - Shift))) >> Shift;
                    if (Index) {
                        _SetPixelIndex(pDevice, x, y, Index);
                    }
                    x++;
                    if (++CurrentPixel == 2) {
                        CurrentPixel = 0;
                        Pixels = *(++p);
                    }
                } while (--xsize);
            }
            break;
    }
}

/*********************************************************************
*
*       Draw Bitmap 8 BPP
*/
static void  _DrawBitLine8BPP(GUI_DEVICE * pDevice, int x, int y, U8 const GUI_UNI_PTR * p, int xsize, const LCD_PIXELINDEX * pTrans) {
    LCD_PIXELINDEX Pixel;

    switch (GUI_pContext->DrawMode & (LCD_DRAWMODE_TRANS | LCD_DRAWMODE_XOR)) {
        case 0:
            if (pTrans) {
                for (; xsize > 0; xsize--, x++, p++) {
                    Pixel = *p;
                    _SetPixelIndex(pDevice, x, y, *(pTrans + Pixel));
                }
            } else {
                for (; xsize > 0; xsize--, x++, p++) {
                    _SetPixelIndex(pDevice, x, y, *p);
                }
            }
            break;
        case LCD_DRAWMODE_TRANS:
            if (pTrans) {
                for (; xsize > 0; xsize--, x++, p++) {
                    Pixel = *p;
                    if (Pixel) {
                        _SetPixelIndex(pDevice, x, y, *(pTrans + Pixel));
                    }
                }
            } else {
                for (; xsize > 0; xsize--, x++, p++) {
                    Pixel = *p;
                    if (Pixel) {
                        _SetPixelIndex(pDevice, x, y, Pixel);
                    }
                }
            }
            break;
    }
}

/*********************************************************************
*
*       Draw Bitmap 16 BPP, not optimized
*
* Purpose:
*   Drawing of 16bpp high color bitmaps.
*   Only required for 16bpp color depth of target. Should be removed otherwise.
*/
static void _DrawBitLine16BPP(GUI_DEVICE * pDevice, int x, int y, U16 const GUI_UNI_PTR * p, int xsize) {

    LCD_PIXELINDEX pixel;

    for (;xsize > 0; xsize--, x++, p++)
    {
        if(xsize>g_tft_lcd_data.width)y++;
        lcd_tft_setWin(x,y,g_tft_lcd_data.width-xsize,y);
        lcd_tft_write_ram_prepare();
        pixel = *p;
        lcd_tft_write_color(pixel);
        //led2=0;
        //printf("x=%d   y=%d   pixel=%x\r\n",x,y,pixel);
    }
}

/*********************************************************************
*
*       Draw Bitmap 32 BPP, not optimized
*
* Purpose:
*   Drawing of 32bpp true color bitmaps.
*   Only required for 32bpp color depth of target. Should be removed otherwise.
*/
static void _DrawBitLine32BPP(GUI_DEVICE * pDevice, int x, int y, U32 const GUI_UNI_PTR * p, int xsize) {
    for (;xsize > 0; xsize--, x++, p++) {
        _SetPixelIndex(pDevice, x, y, *p);
    }
}

/*********************************************************************
*
*       _DrawBitmap
*/
static void _DrawBitmap(GUI_DEVICE * pDevice, int x0, int y0,
                        int xSize, int ySize,
                        int BitsPerPixel,
                        int BytesPerLine,
                        const U8 GUI_UNI_PTR * pData, int Diff,
                        const LCD_PIXELINDEX * pTrans) {
    int i;

    switch (BitsPerPixel) {
        case 1:
            for (i = 0; i < ySize; i++) {
                _DrawBitLine1BPP(pDevice, x0, i + y0, pData, Diff, xSize, pTrans);
                pData += BytesPerLine;
            }
            break;
        case 2:
            for (i = 0; i < ySize; i++) {
                _DrawBitLine2BPP(pDevice, x0, i + y0, pData, Diff, xSize, pTrans);
                pData += BytesPerLine;
            }
            break;
        case 4:
            for (i = 0; i < ySize; i++) {
                _DrawBitLine4BPP(pDevice, x0, i + y0, pData, Diff, xSize, pTrans);
                pData += BytesPerLine;
            }
            break;
        case 8:
            for (i = 0; i < ySize; i++) {
                _DrawBitLine8BPP(pDevice, x0, i + y0, pData, xSize, pTrans);
                pData += BytesPerLine;
            }
            break;
            //
            // Only required for 16bpp color depth of target. Should be removed otherwise.
            //
        case 16:
            for (i = 0; i < ySize; i++) {
                _DrawBitLine16BPP(pDevice, x0, i + y0, (const U16 *)pData, xSize);
                pData += BytesPerLine;
            }
            break;
            //
            // Only required for 32bpp color depth of target. Should be removed otherwise.
            //
        case 32:
            for (i = 0; i < ySize; i++) {
                _DrawBitLine32BPP(pDevice, x0, i + y0, (const U32 *)pData, xSize);
                pData += BytesPerLine;
            }
            break;
    }
}

/*********************************************************************
*
*       _InitOnce
*
* Purpose:
*   Allocates a fixed block for the context of the driver
*
* Return value:
*   0 on success, 1 on error
*/
static int _InitOnce(GUI_DEVICE * pDevice) {
    DRIVER_CONTEXT_TEMPLATE * pContext;

    if (pDevice->u.pContext == NULL) {
        pDevice->u.pContext = GUI_ALLOC_GetFixedBlock(sizeof(DRIVER_CONTEXT_TEMPLATE));
        pContext = (DRIVER_CONTEXT_TEMPLATE *)pDevice->u.pContext;
        pContext->BitsPerPixel = LCD__GetBPP(pDevice->pColorConvAPI->pfGetIndexMask());
    }
    return pDevice->u.pContext ? 0 : 1;
}

/*********************************************************************
*
*       _GetDevProp
*/
static I32 _GetDevProp(GUI_DEVICE * pDevice, int Index) {
    DRIVER_CONTEXT_TEMPLATE * pContext;

    pContext = (DRIVER_CONTEXT_TEMPLATE *)pDevice->u.pContext;
    switch (Index) {
        case LCD_DEVCAP_XSIZE:
            return pContext->xSize;
        case LCD_DEVCAP_YSIZE:
            return pContext->ySize;
        case LCD_DEVCAP_VXSIZE:
            return pContext->vxSize;
        case LCD_DEVCAP_VYSIZE:
            return pContext->vySize;
        case LCD_DEVCAP_BITSPERPIXEL:
            return pContext->BitsPerPixel;
        case LCD_DEVCAP_NUMCOLORS:
            return 0;
        case LCD_DEVCAP_XMAG:
            return 1;
        case LCD_DEVCAP_YMAG:
            return 1;
        case LCD_DEVCAP_MIRROR_X:
            return 0;
        case LCD_DEVCAP_MIRROR_Y:
            return 0;
        case LCD_DEVCAP_SWAP_XY:
            return 0;
    }
    return -1;
}

/*********************************************************************
*
*       _GetDevData
*/
static void * _GetDevData(GUI_DEVICE * pDevice, int Index) {
    GUI_USE_PARA(pDevice);
#if GUI_SUPPORT_MEMDEV
    switch (Index) {
        case LCD_DEVDATA_MEMDEV:
            return (void *)&GUI_MEMDEV_DEVICE_16; // TBD: Has to be adapted to the right memory device depending on the used color depth!
    }
#else
    GUI_USE_PARA(Index);
#endif
    return NULL;
}

/*********************************************************************
*
*       _GetRect
*/
static void _GetRect(GUI_DEVICE * pDevice, LCD_RECT * pRect) {
    DRIVER_CONTEXT_TEMPLATE * pContext;

    pContext = (DRIVER_CONTEXT_TEMPLATE *)pDevice->u.pContext;
    pRect->x0 = 0;
    pRect->y0 = 0;
    pRect->x1 = pContext->vxSize - 1;
    pRect->y1 = pContext->vySize - 1;
}

/*********************************************************************
*
*       _SetOrg
*/
static void _SetOrg(GUI_DEVICE * pDevice, int x, int y) {
    LCD_X_SETORG_INFO Data = {0};

    Data.xPos = x;
    Data.yPos = y;
    LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_SETORG, (void *)&Data);
}

/*********************************************************************
*
*       Static code: Functions available by _GetDevFunc()
*
**********************************************************************
*/
/*********************************************************************
*
*       _SetVRAMAddr
*/
static void _SetVRAMAddr(GUI_DEVICE * pDevice, void * pVRAM) {
    DRIVER_CONTEXT_TEMPLATE * pContext;
    LCD_X_SETVRAMADDR_INFO Data = {0};

    _InitOnce(pDevice);
    if (pDevice->u.pContext) {
        pContext = (DRIVER_CONTEXT_TEMPLATE *)pDevice->u.pContext;
        pContext->VRAMAddr = (U32)pVRAM;
        Data.pVRAM = pVRAM;
        LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_SETVRAMADDR, (void *)&Data);
    }
}

/*********************************************************************
*
*       _SetVSize
*/
static void _SetVSize(GUI_DEVICE * pDevice, int xSize, int ySize) {
    DRIVER_CONTEXT_TEMPLATE * pContext;

    _InitOnce(pDevice);
    if (pDevice->u.pContext) {
        pContext = (DRIVER_CONTEXT_TEMPLATE *)pDevice->u.pContext;
        pContext->vxSize = xSize;
        pContext->vySize = ySize;
        pContext->vxSizePhys = xSize;
    }
}

/*********************************************************************
*
*       _SetSize
*/
static void _SetSize(GUI_DEVICE * pDevice, int xSize, int ySize) {
    DRIVER_CONTEXT_TEMPLATE * pContext;
    LCD_X_SETSIZE_INFO Data = {0};

    _InitOnce(pDevice);
    if (pDevice->u.pContext) {
        pContext = (DRIVER_CONTEXT_TEMPLATE *)pDevice->u.pContext;
        pContext->vxSizePhys = (pContext->vxSizePhys == 0) ? xSize : pContext->vxSizePhys;
        pContext->xSize = xSize;
        pContext->ySize = ySize;
        Data.xSize = xSize;
        Data.ySize = ySize;
        LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_SETSIZE, (void *)&Data);
    }
}
/*********************************************************************
*
*       _Init
*/
static int  _Init(GUI_DEVICE * pDevice) {
    int r;

    r = _InitOnce(pDevice);
    r |= LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_INITCONTROLLER, NULL);
    return r;
}

/*********************************************************************
*
*       _On
*/
static void _On (GUI_DEVICE * pDevice) {
    LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_ON, NULL);
}

/*********************************************************************
*
*       _Off
*/
static void _Off (GUI_DEVICE * pDevice) {
    LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_OFF, NULL);
}

/*********************************************************************
*
*       _SetLUTEntry
*/
static void _SetLUTEntry(GUI_DEVICE * pDevice, U8 Pos, LCD_COLOR Color) {
    LCD_X_SETLUTENTRY_INFO Data = {0};

    Data.Pos   = Pos;
    Data.Color = Color;
    LCD_X_DisplayDriver(pDevice->LayerIndex, LCD_X_SETLUTENTRY, (void *)&Data);
}

/*********************************************************************
*
*       _GetDevFunc
*/
static void (* _GetDevFunc(GUI_DEVICE ** ppDevice, int Index))(void) {
    GUI_USE_PARA(ppDevice);
    switch (Index) {
        case LCD_DEVFUNC_SET_VRAM_ADDR:
            return (void (*)(void))_SetVRAMAddr;
        case LCD_DEVFUNC_SET_VSIZE:
            return (void (*)(void))_SetVSize;
        case LCD_DEVFUNC_SET_SIZE:
            return (void (*)(void))_SetSize;
        case LCD_DEVFUNC_INIT:
            return (void (*)(void))_Init;
        case LCD_DEVFUNC_ON:
            return (void (*)(void))_On;
        case LCD_DEVFUNC_OFF:
            return (void (*)(void))_Off;
        case LCD_DEVFUNC_SETLUTENTRY:
            return (void (*)(void))_SetLUTEntry;
    }
    return NULL;
}

/*********************************************************************
*
*       Public data
*
**********************************************************************
*/
/*********************************************************************
*
*       GUI_DEVICE_API structure
*/
const GUI_DEVICE_API GUIDRV_Template_API = {
        //
        // Data
        //
        DEVICE_CLASS_DRIVER,
        //
        // Drawing functions
        //
        _DrawBitmap,
        _DrawHLine,
        _DrawVLine,
        _FillRect,
        (unsigned long (*)(GUI_DEVICE *, int, int)) _GetPixelIndex,
        (void (*)(GUI_DEVICE *, int, int, unsigned long)) _SetPixelIndex,
        _XorPixel,
        //
        // Set origin
        //
        _SetOrg,
        //
        // Request information
        //
        _GetDevFunc,
        _GetDevProp,
        _GetDevData,
        _GetRect,
};

/*************************** End of file ****************************/

显示屏相关配置

/*********************************************************************
*                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>&copy; 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 "includes.h"


#define TOUCH_AD_TOP		225  	//
#define TOUCH_AD_BOTTOM		3837 	//
#define TOUCH_AD_LEFT 		3896		//
#define TOUCH_AD_RIGHT		193	//

/*********************************************************************
*
*       Public functions
*
**********************************************************************
*/
#define XSIZE_PHYS  320
#define YSIZE_PHYS  480
#define VXSIZE_PHYS 480
#define VYSIZE_PHYS 320

/*********************************************************************
*
*       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_CreateAndLink(&GUIDRV_Template_API, GUICC_M565, 0, 0);
    LCD_SetSizeEx(0, g_tft_lcd_data.width, g_tft_lcd_data.height);
    LCD_SetVSizeEx(0, g_tft_lcd_data.width, g_tft_lcd_data.height);
    /*第4部分,设置触摸原点 */
    GUI_TOUCH_SetOrientation((GUI_MIRROR_X * LCD_GetMirrorXEx(0)) |
                             (GUI_MIRROR_Y * LCD_GetMirrorYEx(0)) | (GUI_SWAP_XY * LCD_GetSwapXYEx(0)));
    if (g_tft_lcd_data.dir == 0) {
        GUI_TOUCH_Calibrate(GUI_COORD_X, 0, g_tft_lcd_data.width - 1, TOUCH_AD_LEFT, TOUCH_AD_RIGHT);
        GUI_TOUCH_Calibrate(GUI_COORD_Y, 0, g_tft_lcd_data.height - 1, TOUCH_AD_BOTTOM, TOUCH_AD_TOP);
    } else {
        GUI_TOUCH_SetOrientation(GUI_SWAP_XY | GUI_MIRROR_X);
        GUI_TOUCH_Calibrate(GUI_COORD_X, 0, g_tft_lcd_data.height, 155, 3903);
        GUI_TOUCH_Calibrate(GUI_COORD_Y, 0, g_tft_lcd_data.width, 188, 3935);
    }

}

/*********************************************************************
*
*       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;
    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;
        }
        case LCD_X_SETVRAMADDR:
        {
            //
            // Required for setting the address of the video RAM for drivers
            // with memory mapped video RAM which is passed in the 'pVRAM' element of p
            //
            LCD_X_SETVRAMADDR_INFO * p;
            (void)p;
            p = (LCD_X_SETVRAMADDR_INFO *)pData;
            //...
            return 0;
        }
        case LCD_X_SETORG:
        {
            //
            // Required for setting the display origin which is passed in the 'xPos' and 'yPos' element of p
            //
            LCD_X_SETORG_INFO * p;
            (void)p;
            p = (LCD_X_SETORG_INFO *)pData;

            //...
            return 0;
        }
        case LCD_X_SHOWBUFFER:
        {
            //
            // Required if multiple buffers are used. The 'Index' element of p contains the buffer index.
            //
            LCD_X_SHOWBUFFER_INFO * p;
            (void)p;
            p = (LCD_X_SHOWBUFFER_INFO *)pData;
            //...
            return 0;
        }
        case LCD_X_SETLUTENTRY:
        {
            //
            // Required for setting a lookup table entry which is passed in the 'Pos' and 'Color' element of p
            //
            LCD_X_SETLUTENTRY_INFO * p;
            (void)p;
            p = (LCD_X_SETLUTENTRY_INFO *)pData;
            //...
            return 0;
        }
        case LCD_X_ON:
        {
            //
            // Required if the display controller should support switching on and off
            //
            return 0;
        }
        case LCD_X_OFF:
        {
            //
            // Required if the display controller should support switching on and off
            //
            // ...
            return 0;
        }
        default:
            r = -1;
    }
    return r;
}


/*************************** End of file ****************************/

触摸配置

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024/3/23     shchl   first version
 */
#include "GUI.h"
#include "bsp_touch.h"


void GUI_TOUCH_X_ActivateX(void)
{
    // XPT2046_WriteCMD(0x90);
}


void GUI_TOUCH_X_ActivateY(void)
{
    //XPT2046_WriteCMD(0xd0);
}

int  GUI_TOUCH_X_MeasureX(void)
{
    return  lcd_touch_read_ad(g_lcd_touch.CNF.rd_x_cmd) ;  //CMD_RDX=0XD0
}

int  GUI_TOUCH_X_MeasureY(void)
{

    return  lcd_touch_read_ad(g_lcd_touch.CNF.rd_y_cmd) ;  //CMD_RDY=0X90
}

与threadx 项目组合

GUI组件(app_gui_emwin_component.c)

  1. 初始化GUI_Init()之前,要先调用crc初始化
/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-20     shchl         em win demo
 */
#include "includes.h"


#if  1

#include "GUI.h"

/*
*******************************************************************************************************
*                               外部引入变量
*******************************************************************************************************
*/

/*
*******************************************************************************************************
*                               变量
*******************************************************************************************************
*/

/*
*********************************************************************************************************
*                                       静态全局变量
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                      函数声明
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                      外部函数
*********************************************************************************************************
*/
int gui_emwin_application_define(void) {
    CRC_HandleTypeDef CrcHandle;
    CrcHandle.Instance = CRC;
    HAL_CRC_Init(&CrcHandle);
    GUI_Init();

    GUI_SetBkColor(BLACK);
    GUI_Clear();
    return FX_SUCCESS;
}

TX_APP_DEFINE_LV1(gui_emwin_application_define); /*首先创建模块应用*/
/*
*********************************************************************************************************
*                                      内部函数
*********************************************************************************************************
*/

#endif

创建GUI任务线程(包含显示,和触摸)

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-20     shchl   first version
 */
#include "includes.h"


#if 1
#include "GUI.h"
#define APP_TASK_GUI_EMWIN_PRIO 15
#define APP_TASK_GUI_EMWIN_STK_SIZE 2048

#define APP_TASK_TOUCH_EMWIN_PRIO 10
#define APP_TASK_TOUCH_EMWIN_STK_SIZE 2048
/*
*******************************************************************************************************
*                               外部引入变量
*******************************************************************************************************
*/

/*
*******************************************************************************************************
*                               变量
*******************************************************************************************************
*/
TX_THREAD gui_thread;
TX_THREAD touch_thread;
VOID *gui_thread_stack_area;
VOID *touch_thread_stack_area;
/*
*********************************************************************************************************
*                                       静态全局变量
*********************************************************************************************************
*/



/*
*********************************************************************************************************
*                                      函数声明
*********************************************************************************************************
*/
static VOID gui_thread_entry(ULONG input);

static VOID touch_thread_entry(ULONG input);
/*
*********************************************************************************************************
*                                      外部函数
*********************************************************************************************************
*/
/**
 * @brief cpu 状态任务
 * @param first_thread 第一个启动的任务线程首地址
 */
int tx_task_gui_emwin_create() {
    gui_thread_stack_area = app_malloc(APP_TASK_GUI_EMWIN_STK_SIZE);
    touch_thread_stack_area = app_malloc(APP_TASK_TOUCH_EMWIN_STK_SIZE);
    tx_thread_create(&gui_thread,              /* 任务控制块地址 */
                     "gui thread",               /* 任务名 */
                     gui_thread_entry,                  /* 启动任务函数地址 */
                     0,                             /* 传递给任务的参数 */
                     gui_thread_stack_area,            /* 堆栈基地址 */
                     APP_TASK_GUI_EMWIN_STK_SIZE,    /* 堆栈空间大小 */
                     APP_TASK_GUI_EMWIN_PRIO,        /* 任务优先级*/
                     APP_TASK_GUI_EMWIN_PRIO,        /* 任务抢占阀值 */
                     TX_NO_TIME_SLICE,               /* 不开启时间片 */
                     TX_AUTO_START);                 /* 创建后立即启动 */


    tx_thread_create(&touch_thread,              /* 任务控制块地址 */
                     "touch thread",               /* 任务名 */
                     touch_thread_entry,                  /* 启动任务函数地址 */
                     0,                             /* 传递给任务的参数 */
                     touch_thread_stack_area,            /* 堆栈基地址 */
                     APP_TASK_TOUCH_EMWIN_STK_SIZE,    /* 堆栈空间大小 */
                     APP_TASK_TOUCH_EMWIN_PRIO,        /* 任务优先级*/
                     APP_TASK_TOUCH_EMWIN_PRIO,        /* 任务抢占阀值 */
                     TX_NO_TIME_SLICE,               /* 不开启时间片 */
                     TX_AUTO_START);                 /* 创建后立即启动 */
    return TX_SUCCESS;
}

TX_THREAD_EXPORT(tx_task_gui_emwin_create);

/*
*********************************************************************************************************
*                                      内部函数
*********************************************************************************************************
*/
static VOID gui_thread_entry(ULONG input) {
    MainTask();
    while (1) {
    }

}

static VOID touch_thread_entry(ULONG input) {

    while (1) {

        GUI_TOUCH_Exec();
        GUI_X_Delay(50);
    }

}
#endif

使用Awizard开发图形界面(使用方式,网上都有)

在这里插入图片描述

添加对应的源文件(这里工程是保存到项目下的,所以用CMakelists管理)

在这里插入图片描述

在这里插入图片描述

下载程序

在这里插入图片描述

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

詩不诉卿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值