一、准备资料

开发板:ESP32-S3

开发环境:VS Code + PlatformIO

串口屏驱动 TFT-eSPI:https://github.com/Bodmer/TFT_eSPI

触摸驱动 Arduino-FT6336U:https://github.com/aselectroworks/Arduino-FT6336U

GUI LVGL:https://github.com/lvgl/lvgl

二、项目搭建

  1. 资源库下载

    这里我使用的驱动都是从 GitHub 上下载,有经验的小伙伴也可以自己写驱动程序

  2. 工程文件

    将下载的 TFT-eSPI、Arduino-FT6336U、LVGL,放在项目的lib文件下,如下图所示:

  3. 在 c_cpp_properties.json 文件中的 includePath 和 path 中添加资源路径

    "c:/Users/Administrator/Desktop/weather_clock_test/lib/TFT_eSPI",
    
    "c:/Users/Administrator/Desktop/weather_clock_test/lib/lvgl",
    "c:/Users/Administrator/Desktop/weather_clock_test/lib/lvgl/src", "c:/Users/Administrator/Desktop/weather_clock_test/lib/Arduino-FT6336U/src",

    注意:自己的项目路径,我这里只是举例。

三、屏驱动 TFT-eSPI

  1. 配置显示器驱动

    在路径TFT_eSPI/User_Setup.h,中找到User_Setup.h文件,配置显示屏的驱动,不知道怎么使用 TFT-eSPI 的小伙伴可以看我之前的笔记TFT-eSPI入门使用教程

  2. 创建对象

    TFT_eSPI tft = TFT_eSPI()
    TFT_eSPI tft = TFT_eSPI(320,240) // 在创建对象的时候设置屏幕尺寸

    注意:记得加载头文件 #include <TFT_eSPI.h>

  3. TFT-eSPI的初始化程序初始化

    /* ------------ 屏幕背光亮度 ------------*/
    /* 配置LED PWM通道属性,PWD通道为 0,频率为1KHz */
    ledcSetup(LCD_BL_PWM_CHANNEL, 1000, TFT_BL);
    /* 配置LED PWM通道属性 */
    ledcAttachPin(LCD_BL_PIN, LCD_BL_PWM_CHANNEL);
    ledcWrite(LCD_BL_PWM_CHANNEL, (int)(1 * 255)); /* 初始化显示驱动 */
    tft.init();
    /* 旋转角度 0、1、2、3 对应 0 、90度、180度、270 */
    tft.setRotation(0);
    /* 关闭颜色反转 */
    tft.invertDisplay(0);

四. 触摸驱动 Arduino-FT6336U

触摸驱动比较简单,不需要复杂的配置,只需要在初始化的时候传入引脚即可

  1. 触摸引脚的宏定义

    #define I2C_SDA 4
    #define I2C_SCL 15
    #define RST_N_PIN 5
    #define INT_N_PIN 17
  2. 创建对象

    FT6336U ft6336u(I2C_SDA, I2C_SCL, RST_N_PIN, INT_N_PIN);
    FT6336U_TouchPointType tp;
  3. 初始化

    ft6336u.begin();

五、LVGL使用

这里我整理了一些 LVGL 的学习资料,需要的可以了解一下 LVGL学习资料

  1. 下载 LVGL

    从GitHub 中下载 或者克隆 LVGL 资源库

  2. 在项目中添加 LVGL 资源库

    将下载的库文件复制到项目的lib路径下,建议将资源的文件名改为 lvgl

    注意:名字不一样时,c_cpp_properties.json文件中添加的路径也会变化

  3. 重命名 lv_conf_template.h 文件

    • lvgl/lv_conf_template.h 文件重命名为 lv_conf.h
    • 将文件中的第一个 #if 0 改为 #if 1
    • 通过配置 LV_COLOR_DEPTH 宏,设置显示屏的颜色深度
  4. 配置 LVGL 的心跳时间

    在计时器或任务重每 x 毫秒调用一次 lv_tick_inc(x) 函数( x 应该在 1 ~ 10 之间)。

    当然使用 Arduino 环境的可以直接配置 lv_conf.h 文件中的宏 LV_TICK_CUSTOM ,达到目的,原理如下图所示:

  5. LVGL 库的使用

    在需要使用 LVGL 库相关函数的文件中添加 #include <lvgl.h> 头文件即可

  6. 初始化 LVGL 库

    只需要在使用 LVGL 之前 调用 lv_init() 函数即可

  7. 创建绘制缓冲区

    LVGL 将在缓冲区中渲染图像,然后通过显示驱动的函数将图像发送到显示器

    缓冲区大小可以自由设置,但是建议缓冲区最小为屏幕大小的 1/10,程序如下所示:

    /*------------ 通过静态空间创建缓冲区 ------------*/
    #define DISP_BUF_SIZE ((240*320)/10)
    static lv_disp_draw_buf_t draw_buf; // 绘制缓冲区的内部图形缓冲区
    static lv_color_t buf_1[DISP_BUF_SIZE]; // 缓冲区为屏幕大小的1/10 /* 初始化显示缓冲区 */
    lv_disp_draw_buf_init(&draw_buf, buf_1, NULL, DISP_BUF_SIZE); /*------------ 通过堆空间创建缓冲区 ------------*/
    #define DISP_BUF_SIZE ((240*320)/10)
    static lv_disp_draw_buf_t draw_buf; // 绘制缓冲区的内部图形缓冲区
    static lv_color_t *buf1; // 缓冲区1
    static lv_color_t *buf2; // 缓冲区2 buf1 = (lv_color_t*)heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
    assert(buf1 != NULL); buf2 = (lv_color_t*)heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
    assert(buf2 != NULL); lv_disp_draw_buf_init(&draw_buf, buf1, buf2, DISP_BUF_SIZE);

    注意:必须保证绘制缓冲区的声明周期,方式可以是全局变量、静态空间、堆空间。

  8. 注册显示驱动

    通过注册的回调函数,将绘制好的图形通过显示屏驱动进行绘制显示。回调函数会在刷新显示的时候调用

    /* 设置LVGL的显示驱动的结构属性 */
    static lv_disp_drv_t disp_drv; // 显示驱动程序的描述符
    lv_disp_drv_init(&disp_drv); // 初始化句柄,确保所有参数都是默认值
    disp_drv.hor_res = MY_DISP_HOR_RES; // 设置显示器的水平分辨率
    disp_drv.ver_res = MY_DISP_VER_RES; // 设置显示器的垂直分辨率
    disp_drv.flush_cb = my_disp_flush; // 显示驱动的回调函数
    disp_drv.draw_buf = &draw_buf; // 将缓冲区分配给显示器
    lv_disp_drv_register(&disp_drv); // 注册驱动 /**
    * @brief 显示回调函数,通过此回调函数将绘制空间的图形传递给显示驱动程序
    * @param disp 显示驱动程序的描述符
    * @param area 图像需要显示的区域
    * @param color_p 描绘后的图形
    */
    void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
    {
    uint32_t w = (area->x2 - area->x1 + 1);
    uint32_t h = (area->y2 - area->y1 + 1); tft.startWrite();
    tft.setAddrWindow(area->x1, area->y1, w, h);
    tft.pushColors(&color_p->full, w * h, true);
    tft.endWrite(); /* 反馈显示结果*/
    lv_disp_flush_ready(disp);
    }
  9. 输入设备驱动

    通过注册的回调函数,将触摸获取的坐标值传递给 LVGL 。此回调函数是由 LVGL 的时间管理进行定时调用的,能否通过终端的形式进行获取我目前还不知道,有了解的朋友望告知一下。

    static lv_indev_drv_t indev_drv;                     // 输入驱动程序的描述符
    lv_indev_drv_init(&indev_drv); // 初始化
    indev_drv.type = LV_INDEV_TYPE_POINTER; // 设置设备类型
    indev_drv.read_cb = touch_read; // 输入设备的回调函数
    lv_indev_drv_register(&indev_drv); // 创建输入设备 /**
    * @brief 触摸回调函数,通过此回调函数将触摸获取的坐标传递给 LVGL
    * @param indev_driver
    * @param data 输入设备的数据
    */
    void touch_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data)
    {
    tp = ft6336u.scan();
    static int16_t last_x = 0;
    static int16_t last_y = 0; /* 判断屏幕是否被按下 */
    bool touched = tp.touch_count;
    if (touched)
    {
    last_x = tp.tp[0].x;
    last_y = tp.tp[0].y;
    data->state = LV_INDEV_STATE_PRESSED;
    }
    else {
    data->state = LV_INDEV_STATE_RELEASED;
    } /* 将获取的坐标传入 LVGL */
    data->point.x = last_x;
    data->point.y = last_y;
    }
  10. 调用 lv_timer_handler()

    在主while(1) 循环或操作系统任务中每隔几毫秒定期调用lv_timer_handler()。它将重绘屏幕、处理输入设备、动画等

六、界面绘制

  1. 创建界面

    • 方式1

      创建一个空的界面

      lv_obj_t *view_test = lv_btn_create(NULL);

      注意:新的界面在显示的时候需要通过加载函数,将界面加载到显示器上

      lv_scr_load(view_test);
    • 方式2:

      在当前活动界面上创建界面,创建完成后会自动加载到显示器上

      lv_obj_t * text_t = lv_btn_create(lv_scr_act());
  2. 创建标签

    /**
    * @brief 创建一个标签
    */
    lv_obj_t *label = lv_label_create(lv_scr_act());
    if (NULL != label)
    {
    // lv_obj_set_x(label, 90); // 设置控件的X坐标
    // lv_obj_set_y(label, 100); // 设置控件的Y坐标
    // lv_obj_set_size(label, 60, 20); // 设置控件大小
    lv_label_set_text(label, "Counter"); // 初始显示 0
    // lv_obj_center(label); // 居中显示
    lv_obj_align(label, LV_ALIGN_CENTER, 0, -50); // 居中显示后,向上偏移50
    }
  3. 创建按钮

    /**
    * @brief 按钮事件回调函数
    */
    static void btn_event_callback(lv_event_t* event)
    {
    static uint32_t counter = 1; lv_obj_t* btn = lv_event_get_target(event); //获取事件对象
    if (btn != NULL)
    {
    lv_label_set_text_fmt(label, "%d", counter); //设置显示内容
    lv_obj_align(label, LV_ALIGN_CENTER, 0, -50); // 居中显示后,向上偏移50
    counter++;
    }
    } /**
    * @brief 创建按钮
    */
    void lvgl_button_test(){
    /* 在当前界面中创建一个按钮 */
    lv_obj_t* btn = lv_btn_create(lv_scr_act()); // 创建Button对象
    if (btn != NULL)
    {
    lv_obj_set_size(btn, 80, 20); // 设置对象宽度和高度
    // lv_obj_set_pos(btn, 90, 200); // 设置按钮的X和Y坐标
    lv_obj_add_event_cb(btn, btn_event_callback, LV_EVENT_CLICKED, NULL); // 给对象添加CLICK事件和事件处理回调函数
    lv_obj_align(btn, LV_ALIGN_CENTER, 0, 50); // 居中显示后,向下偏移50 lv_obj_t* btn_label = lv_label_create(btn); // 基于Button对象创建Label对象
    if (btn_label != NULL)
    {
    lv_label_set_text(btn_label, "button"); // 设置显示内容
    lv_obj_center(btn_label); // 对象居中显示
    }
    }
    }

七、测试程序

main.cpp

#include <Arduino.h>
#include <lvgl.h>
#include <TFT_eSPI.h>
#include <FT6336U.h> /*------------ 触摸引脚 ------------*/
#define I2C_SDA 4
#define I2C_SCL 15
#define RST_N_PIN 5
#define INT_N_PIN 17 /*------------ 背光通道 ------------*/
#define LCD_BL_PIN 6 // PWD 的 IO 引脚
#define LCD_BL_PWM_CHANNEL 0 // Channel 通道, 0 ~ 16,高速通道(0 ~ 7)由80MHz时钟驱动,低速通道(8 ~ 15)由 1MHz 时钟驱动 /*------------ LVGL ------------*/
#define MY_DISP_HOR_RES 240 // 显示屏的宽像素
#define MY_DISP_VER_RES 320 // 显示屏的高像素
#define DISP_BUF_SIZE ((MY_DISP_HOR_RES*MY_DISP_VER_RES)/10) static lv_disp_draw_buf_t draw_buf; // 绘制缓冲区的内部图形缓冲区
static lv_color_t buf_1[DISP_BUF_SIZE]; // 缓冲区为屏幕大小的1/10
static lv_color_t *buf1; // 缓冲区为屏幕大小的1/10
static lv_color_t *buf2; // 缓冲区为屏幕大小的1/10 /*------------ 显示驱动对象 ------------*/
TFT_eSPI tft = TFT_eSPI(); /*------------ 触摸驱动对象 ------------*/
FT6336U ft6336u(I2C_SDA, I2C_SCL, RST_N_PIN, INT_N_PIN);
FT6336U_TouchPointType tp; /*------------ 测试界面对象 ------------*/
lv_obj_t *label; /**
* @brief 触摸回调函数,通过此回调函数将触摸获取的坐标传递给 LVGL
* @param indev_driver
* @param data 输入设备的数据
*/
void touch_read(lv_indev_drv_t * indev_driver, lv_indev_data_t * data)
{
tp = ft6336u.scan();
static int16_t last_x = 0;
static int16_t last_y = 0; /* 判断屏幕是否被按下 */
bool touched = tp.touch_count;
if (touched)
{
last_x = tp.tp[0].x;
last_y = tp.tp[0].y;
data->state = LV_INDEV_STATE_PRESSED;
}
else {
data->state = LV_INDEV_STATE_RELEASED;
} /* 将获取的坐标传入 LVGL */
data->point.x = last_x;
data->point.y = last_y;
} /**
* @brief 显示回调函数,通过此回调函数将绘制空间的图形传递给显示驱动程序
* @param disp 显示驱动程序的描述符
* @param area 图像需要显示的区域
* @param color_p 描绘后的图形
*/
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1); tft.startWrite();
tft.setAddrWindow(area->x1, area->y1, w, h);
tft.pushColors(&color_p->full, w * h, true);
tft.endWrite(); /* 反馈显示结果*/
lv_disp_flush_ready(disp);
} /**
* @brief 初始化显示屏驱动
*/
void disp_drv_init(){
/* ------------ 屏幕背光亮度 ------------*/
/* 配置LED PWM通道属性,PWD通道为 0,频率为1KHz */
ledcSetup(LCD_BL_PWM_CHANNEL, 1000, TFT_BL);
/* 配置LED PWM通道属性 */
ledcAttachPin(LCD_BL_PIN, LCD_BL_PWM_CHANNEL);
ledcWrite(LCD_BL_PWM_CHANNEL, (int)(1 * 255)); /* 初始化显示驱动 */
tft.init();
/* 旋转角度 0、1、2、3 对应 0 、90度、180度、270 */
tft.setRotation(0);
/* 关闭颜色反转 */
tft.invertDisplay(0);
} /**
* @brief 初始化触摸驱动
*/
void touch_drv_init(){
ft6336u.begin();
} void lvgl_init(){
/*------------- 初始化LVGL库 -------------*/
lv_init(); /* 初始化显示缓冲区 */
// lv_disp_draw_buf_init(&draw_buf, buf_1, NULL, DISP_BUF_SIZE); /*------------- 创建图形绘制缓冲区 -------------*/
buf1 = (lv_color_t*)heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf1 != NULL); buf2 = (lv_color_t*)heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf2 != NULL); lv_disp_draw_buf_init(&draw_buf, buf1, buf2, DISP_BUF_SIZE); /*------------- 设置LVGL的显示设备 -------------*/
static lv_disp_drv_t disp_drv; // 显示驱动程序的描述符
lv_disp_drv_init(&disp_drv); // 初始化句柄,确保所有参数都是默认值
disp_drv.hor_res = MY_DISP_HOR_RES; // 设置显示器的水平分辨率
disp_drv.ver_res = MY_DISP_VER_RES; // 设置显示器的垂直分辨率
disp_drv.flush_cb = my_disp_flush; // 显示驱动的回调函数
disp_drv.draw_buf = &draw_buf; // 将缓冲区分配给显示器
lv_disp_drv_register(&disp_drv); // 注册驱动 /*------------- 设置LVGL的输入设备 -------------*/
// static lv_indev_t *indev_cor;
static lv_indev_drv_t indev_drv; // 输入驱动程序的描述符
lv_indev_drv_init(&indev_drv); // 初始化
indev_drv.type = LV_INDEV_TYPE_POINTER; // 设置设备类型
indev_drv.read_cb = touch_read; // 输入设备的回调函数
lv_indev_drv_register(&indev_drv); // 创建输入设备
} /**
* @brief 创建标签
*/
void lvgl_lable_test(){
/* 创建一个标签 */
label = lv_label_create(lv_scr_act());
if (NULL != label)
{
// lv_obj_set_x(label, 90); // 设置控件的X坐标
// lv_obj_set_y(label, 100); // 设置控件的Y坐标
// lv_obj_set_size(label, 60, 20); // 设置控件大小
lv_label_set_text(label, "Counter"); // 初始显示 0
// lv_obj_center(label); // 居中显示
lv_obj_align(label, LV_ALIGN_CENTER, 0, -50); // 居中显示后,向上偏移50
}
} /**
* @brief 按钮事件回调函数
*/
static void btn_event_callback(lv_event_t* event)
{
static uint32_t counter = 1; lv_obj_t* btn = lv_event_get_target(event); //获取事件对象
if (btn != NULL)
{
lv_label_set_text_fmt(label, "%d", counter); //设置显示内容
lv_obj_align(label, LV_ALIGN_CENTER, 0, -50); // 居中显示后,向上偏移50
counter++;
}
} /**
* @brief 创建按钮
*/
void lvgl_button_test(){
/* 在当前界面中创建一个按钮 */
lv_obj_t* btn = lv_btn_create(lv_scr_act()); // 创建Button对象
if (btn != NULL)
{
lv_obj_set_size(btn, 80, 20); // 设置对象宽度和高度
// lv_obj_set_pos(btn, 90, 200); // 设置按钮的X和Y坐标
lv_obj_add_event_cb(btn, btn_event_callback, LV_EVENT_CLICKED, NULL); // 给对象添加CLICK事件和事件处理回调函数
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 50); // 居中显示后,向下偏移50 lv_obj_t* btn_label = lv_label_create(btn); // 基于Button对象创建Label对象
if (btn_label != NULL)
{
lv_label_set_text(btn_label, "button"); // 设置显示内容
lv_obj_center(btn_label); // 对象居中显示
}
}
} void setup() {
Serial.begin(115200);
Serial.println("mian.cpp-> 程序初始化......"); /* 初始化显示驱动 */
disp_drv_init(); /* 初始化触摸驱动 */
touch_drv_init(); /* lvgl 初始化 */
lvgl_init(); /* 加载标签 */
lvgl_lable_test();
/* 加载按钮 */
lvgl_button_test();
} void loop() {
lv_timer_handler();
delay(5);
}

八、测试

LVGL 入门使用教程的更多相关文章

  1. Angular2入门系列教程7-HTTP(一)-使用Angular2自带的http进行网络请求

    上一篇:Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数 感觉这篇不是很好写,因为涉及到网络请求,如果采用真实的网络请求,这个例子大家拿到手估计还要自己写一个web ...

  2. Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数

    上一篇:Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数 之前介绍了简单的路由以及传参,这篇文章我们将要学习复杂一些的路由以及传递其他附加参数.一个好的路由系统可以使我们 ...

  3. Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数

    上一篇:Angular2入门系列教程-服务 上一篇文章我们将Angular2的数据服务分离出来,学习了Angular2的依赖注入,这篇文章我们将要学习Angualr2的路由 为了编写样式方便,我们这篇 ...

  4. Angular2入门系列教程4-服务

    上一篇文章 Angular2入门系列教程-多个组件,主从关系 在编程中,我们通常会将数据提供单独分离出来,以免在编写程序的过程中反复复制粘贴数据请求的代码 Angular2中提供了依赖注入的概念,使得 ...

  5. ASP.NET Aries 入门开发教程7:DataGrid的行操作(主键操作区)

    前言: 抓紧勤奋,再接再励,预计共10篇来结束这个系列. 上一篇介绍:ASP.NET Aries 入门开发教程6:列表数据表格的格式化处理及行内编辑 本篇介绍主键操作区相关内容. 1:什么时候有默认的 ...

  6. ASP.NET Aries 入门开发教程6:列表数据表格的格式化处理及行内编辑

    前言: 为了赶进度,周末也写文了! 前几篇讲完查询框和工具栏,这节讲表格数据相关的操作. 先看一下列表: 接下来我们有很多事情可以做. 1:格式化 - 键值的翻译 对于“启用”列,已经配置了格式化 # ...

  7. ASP.NET Aries 入门开发教程4:查询区的下拉配置

    背景: 今天去深圳溜达了一天,刚回来,看到首页都是微软大法好,看来离.NET的春天就差3个月了~~ 回到正题,这篇的教程讲解下拉配置. 查询区的下拉配置: 1:查询框怎么配置成下拉? 在配置表头:格式 ...

  8. 2013 duilib入门简明教程 -- 第一个程序 Hello World(3)

    小伙伴们有点迫不及待了么,来看一看Hello World吧: 新建一个空的win32项目,新建一个main.cpp文件,将以下代码复制进去: #include <windows.h> #i ...

  9. 2013 duilib入门简明教程 -- 部分bug (11)

     一.WindowImplBase的bug     在第8个教程[2013 duilib入门简明教程 -- 完整的自绘标题栏(8)]中,可以发现窗口最大化之后有两个问题,     1.最大化按钮的样式 ...

随机推荐

  1. windows下telnet的用法

    前言 这里利用Windows7下如何使用Telnet命令给大家总结如下: 第一步:在控制面板里,点击"程序"选项 第二步:在程序选项下,点击"打开或关闭Windows功能 ...

  2. sql server 跨IP库更新表字段(OPENDATASOURCE 、update)

    --跨ip库更新表字段 update uat set goodsType=dev.goodsType from OPENDATASOURCE('SQLOLEDB','Data Source=127.0 ...

  3. Day05 表格

    表格 <table width="300" border="1" cellspacing="0"> <caption> ...

  4. cmd中常用的dos命令

    在电脑中除了我们常见的图形界面之外,图形页面的操作相信都会.那么还有在cmd执行的一些dos命令,可以简单记一下,方便日后复习所用 首先打开cmd窗口,windows+R,然后在对话框输入cmd,进入 ...

  5. AI 企业多云存储架构实践 | 深势科技分享

    2020 年末,谷歌旗下 DeepMind 研发的 AI 程序 AlphaFold2 在国际蛋白质结构预测竞赛上取得惊人的准确度,使得" AI 预测蛋白质结构"这一领域受到了空前的 ...

  6. python单元测试框架笔记

    目录 单元测试概述 什么是单元测试 单元测试什么进行? 单元测试由谁负责? 单元测试需要注意 单元测试覆盖类型 python 单元测试框架 unittest pytest 测试框架 单元测试概述 什么 ...

  7. AOP-动态代理

    动态代理的原理代理设计模式的原理:使用一个代理将原本对象包装起来,然后用该代理对象"取代"原始对象.任何对原始对象的调用都要通过代理.代理对象决定是否以及何时将方法调用转到原始对象 ...

  8. SpringMVC-02

    一.SSM整合[重点] 1 SSM整合配置 问题导入 请描述"SSM整合流程"中各个配置类的作用? 1.1 SSM整合流程 创建工程 SSM整合 Spring SpringConf ...

  9. 用cmd命令进行磁盘清理(主要是系统盘)

    作用:清理磁盘(主要是系统盘)中不需要的垃圾文件操作方法: 第一步:Windows键+R键 调出cmd命令窗口(窗口图如下:) 第二步:输入框中输入cmd命令,按下Enter键,进入如下图界面: 第三 ...

  10. raspberry pi使用笔记

    1.raspberry pi系统vim问题 习惯了Linux的vim的使用,但是在raspberry pi中,用apt-get install vim后,vim变得很难用,进入编辑模式,输入字母时会莫 ...