在上一篇文章 《【瑞萨RA8D1 LVGL/LWIP评测】RA8D1 部署FreeRTOS+LVGL》 中,我们基于瑞萨 CPKCOR-RA8D1 开发板部署了 FreeRTOS + LVGL 的代码。但是该LVGL工程缺少输入设备,本文我们既不搞按键、也不添加触屏,添加一种比较特殊的输入设备:Encoder 编码器。本文似乎是全网首个瑞萨单片机使用GPT 硬件计数模式读取EC11旋转编码器的文章,我们这就开搞!
一、基础环境准备
硬件部分
CPKCOR-RA8D1 核心板
CPKEXP-EKRA8x1 扩展板
EC11 旋转编码器
软件部分
e²Studio 2025-12 (25.12.0)
Renesas FSP 6.3.0
J-Link RTT Viewer V8.92
请基于上篇文章部署好的 FreeRTOS+LVGL 的基础环境进行开发。
二、工作原理

EC11 是一种常见的增量式机械旋转编码器,其工作原理可以概括为以下三个核心点:
1. 结构与信号产生
EC11 内部有两组金属触点(通常标记为 Phase A 和 Phase B)。
旋转时:内部的金属拨片会随着转动周期性地接触和断开公共端(COM,通常接地)。
输出信号:A 相和 B 相会分别产生两个正交(相位差为 90°)的方波脉冲信号。
2. 相位差判断方向
通过 A、B 两路信号跳变的先后顺序来识别旋转方向:
顺时针 (CW):当 A 相产生下降沿时,B 相还处于高电平。
逆时针 (CCW):当 A 相产生下降沿时,B 相已经处于低电平。

现代微控制器的定时器一般支持编码器计数的场景,在“计数模式”下,底层硬件会自动监测这种超前/滞后关系,从而自动增加或减少计数器的值。
3. 机械特性
定位感(Detents):EC11 通常带有“咔哒”声的定位点。每转动一个定位点,A/B 相会完成一个或多个完整的脉冲周期。
机械抖动(Bouncing):由于触点是机械接触,信号在跳变瞬间会产生几毫秒的杂波。因此我们在处理波形时要加以过滤,比如开启 Noise Filter。
在瑞萨 RA8 系列中,EC11 编码器的检测主要有两种实现路径:GPT 硬件计数模式(最推荐,全硬件自动处理)和 ICU 外部中断模式(纯软件逻辑)。本文我们玩得“高端”一点,就用GPT!
三、FSP图形化配置
Pin引脚
开始前,我们首先翻一翻 RA8D1 芯片手册和扩展板原理图,找找合适的输入捕获引脚,用来连接编码器的A、B相。

P400 和 P401 作为 GPT6 的A、B相,在扩展板中引出,正好满足我们的需求。

我们在 Pin 配置的 GPT6 中启用相应引脚。

Stack配置
我们通过 Timers -> Timer,General PWM(r_gpt) 选项引入GPT。Stack框图:

随后修改GPT配置项:
1. Channel 选择 6,代表GPT6
2. Input 的 Count Up Source 选择 GTIOCA Rising Edge While GTIOCB Low
3. Input 的 Count Down Source 选择 GTIOCA Falling Edge While GTIOCB Low
4. Noice Filter 选择 PCLKD/64,启动滤波

四、代码绑定
1. 在board_init.c 中,加入初始化代码:
R_GPT_Open(g_encoder_timer.p_ctrl, g_encoder_timer.p_cfg); R_GPT_Start(g_encoder_timer.p_ctrl);
2. 在LVGL的移植文件 rm_lvgl_port.c 中,加入编码器回调方法
static void encoder_read_cb(lv_indev_t * indev, lv_indev_data_t * data) {
FSP_PARAMETER_NOT_USED(indev);
static uint32_t last_raw_count = 0;
timer_status_t status;
// 瑞萨 FSP 接口获取硬件计数值
R_GPT_StatusGet(g_encoder_timer.p_ctrl, &status);
uint32_t current_raw_count = status.counter;
// 计算增量
int32_t diff = (int32_t)(current_raw_count - last_raw_count);
diff = diff > 1000 ? 0 : diff;
data->enc_diff = (int16_t)diff;
/* 更新旧值 */
last_raw_count = current_raw_count;
}3. 在LVGL的移植文件 rm_lvgl_port.c 的 RM_LVGL_PORT_Open 中,加入编码器注册代码:
g_indev_encoder = lv_indev_create(); lv_indev_set_type(g_indev_encoder, LV_INDEV_TYPE_ENCODER); lv_indev_set_read_cb(g_indev_encoder, encoder_read_cb);
五、UI设计
本文我们使用GUI Guider进行UI设计,引入slider控件来测试编码器是否正常工作。我们创建一个222x480分辨率的竖屏,添加一个图片和slider,slider可以控制图片的透明度。

导出代码,复制到src下新建的ui文件夹中。
最后在LVGL线程中初始化使用的UI,将焦点设置在slider上。
代码如下:
extern lv_indev_t * g_indev_encoder;
void bind_slider(void) {
/* 1. 创建一个组 (Group) */
lv_group_t * g = lv_group_create();
lv_group_set_default(g); // 设为默认组,后续创建的控件会自动加入
/* 2. 将编码器绑定到组 */
lv_indev_set_group(g_indev_encoder, g);
/* 3. 关键:进入编辑模式 */
// 在编辑模式下,编码器旋转才会改变 Slider 的 Value
// 如果不在编辑模式,旋转通常用于在不同控件间切换焦点
lv_group_set_editing(g, true);
}
/* LVGL Thread entry function */
/* pvParameters contains TaskHandle_t */
void lvgl_thread_entry(void *pvParameters)
{
int i = 0, cnt = 0;
char label[10];
FSP_PARAMETER_NOT_USED (pvParameters);
lv_init();
board_init();
bind_slider();
setup_ui(&guider_ui);
events_init(&guider_ui);
lv_timer_handler();
while (1)
{
lv_timer_handler();
vTaskDelay (5);
}
}六、烧录运行
烧录代码,转动EC11编码器,可以看到 slider 可以随之左右滑动,图像透明度也会随之变化。

至此,我们就在 RA8D1 的 LVGL 中成功添加了EC11旋转编码器设备。
本文工程源码:
lvgl_encoder_rtos.zip
阅读全文