本教程将详细介绍如何在零知增强板上使用3.5寸ILI9486显示屏实现电子书阅读器功能。我们将使用LVGL库构建用户界面,并实现翻页、进度显示等核心功能。
目录
一、硬件连接
1.1 硬件组件清单
组件名称 | 规格参数 | 备注 |
---|---|---|
零知开发板 | STM32F407VGT6 | 主控制器 |
ILI9486显示屏 | 3.5英寸TFT LCD (480×320) | 电阻触摸屏 |
SD卡模块 | SPI接口,支持FAT32 | 存储电子书文件 |
XPT2046触摸控制器 | SPI接口 | 集成在显示屏模块上 |
W25Q128 Flash芯片 | 128M-bit (16MB) | 存储字体和系统文件 |
USB数据线 | 直流供电 | 系统电源 |
1.2 连接方式
零知增强板设计有专门的显示屏接口,3.5寸ILI9486显示屏可直接插入增强板,无需额外连线
电子书阅读器界面效果图:
二、软件UI组件实现
2.1 核心数据结构
static const char* ebook_content[] = {
"Embedded Systems Fundamentals\n\n"
"Embedded systems are specialized computing systems that perform dedicated functions.",
"Microcontroller Architecture\n\n"
"Microcontrollers (MCUs) contain a processor core, memory, and programmable I/O peripherals.",
"Real-Time Operating Systems\n\n"
"RTOS provides deterministic timing behavior for embedded applications.",
"Communication Protocols\n\n"
"Wired: UART, SPI, I2C, CAN\nWireless: BLE, Wi-Fi, LoRaWAN",
"Development Lifecycle\n\n"
"Stages: Requirements, Design, Implementation, Testing, Deployment"
};
static const uint8_t ebook_page_count = sizeof(ebook_content) / sizeof(ebook_content[0]);
// 电子书翻页事件回调
static void ebook_prev_event_cb(lv_obj_t* btn, lv_event_t event);
static void ebook_next_event_cb(lv_obj_t* btn, lv_event_t event);
typedef struct {
uint8_t current_page;
uint8_t total_pages;
bool is_english;
lv_point_t touch_start; // 触摸起始点
lv_obj_t* page_label;
lv_obj_t* progress_label;
lv_obj_t* btn_translate;
} EBookState;
2.2 触摸事件处理
// 声明全局ebook_state变量
static EBookState ebook_state;
// 3. 完全兼容的触摸事件处理(替代手势事件)
static void ebook_touch_event_cb(lv_obj_t* obj, lv_event_t event) {
static lv_point_t touch_start;
static uint32_t touch_time;
switch(event) {
case LV_EVENT_PRESSED: {
lv_indev_t* indev = lv_indev_get_act();
if(indev) {
lv_indev_get_point(indev, &touch_start);
touch_time = lv_tick_get();
}
break;
}
case LV_EVENT_RELEASED: {
lv_indev_t* indev = lv_indev_get_act();
lv_point_t touch_end;
if(indev) {
lv_indev_get_point(indev, &touch_end);
// 计算滑动距离和时间
lv_coord_t dx = touch_end.x - touch_start.x;
uint32_t duration = lv_tick_elaps(touch_time);
// 判断有效滑动 (水平移动>30像素且时间<300ms)
if(abs(dx) > 30 && duration < 300) {
if(dx > 0) {
// 向右滑动:上一页
if(ebook_state.current_page > 0) {
ebook_prev_event_cb(NULL, LV_EVENT_SHORT_CLICKED);
}
} else {
// 向左滑动:下一页
if(ebook_state.current_page < ebook_state.total_pages - 1) {
ebook_next_event_cb(NULL, LV_EVENT_SHORT_CLICKED);
}
}
}
}
break;
}
}
}
// 更新电子书显示
static void update_ebook_display() {
// 重置位置避免动画残留
lv_obj_set_x(ebook_state.page_label, 0);
lv_label_set_text(ebook_state.page_label, ebook_content[ebook_state.current_page]);
char progress[16];
snprintf(progress, sizeof(progress), "%d/%d", ebook_state.current_page + 1, ebook_state.total_pages);
lv_label_set_text(ebook_state.progress_label, progress);
}
2.3 初始化界面
void show_app_book() {
lv_obj_t* win = create_app_win("EBook Reader");
lv_coord_t hres = lv_disp_get_hor_res(NULL);
lv_coord_t vres = lv_disp_get_ver_res(NULL);
// 初始化电子书状态
ebook_state.current_page = 0;
ebook_state.total_pages = ebook_page_count;
ebook_state.is_english = true; // 默认英文
ebook_state.touch_start.x = 0;
ebook_state.touch_start.y = 0;
// 创建内容容器(支持触摸检测)
lv_obj_t* content_cont = lv_cont_create(win, NULL);
lv_obj_set_size(content_cont, hres - 40, vres - 100);
lv_obj_align(content_cont, NULL, LV_ALIGN_IN_TOP_MID, 0, 20);
// lv_obj_set_gesture_parent(content_cont, true);
lv_obj_set_event_cb(content_cont, ebook_touch_event_cb);
// 创建页面标签
ebook_state.page_label = lv_label_create(content_cont, NULL);
lv_obj_set_width(ebook_state.page_label, lv_obj_get_width(content_cont) - 20);
lv_label_set_long_mode(ebook_state.page_label, LV_LABEL_LONG_EXPAND);
lv_label_set_align(ebook_state.page_label, LV_LABEL_ALIGN_LEFT);
lv_obj_set_width(ebook_state.page_label, lv_obj_get_width(content_cont) - 40); // 增加一些边距
lv_label_set_text(ebook_state.page_label, "");
lv_obj_align(ebook_state.page_label, NULL, LV_ALIGN_CENTER, 0, 0);
// 创建进度标签
ebook_state.progress_label = lv_label_create(win, NULL);
lv_obj_align(ebook_state.progress_label, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, -30);
lv_label_set_text(ebook_state.progress_label, "0/0");
// 初始显示
update_ebook_display();
}
2.4 翻页功能实现
// 翻页动画函数 (兼容旧版LVGL)
static void ebook_page_anim(lv_obj_t* label, lv_coord_t start, lv_coord_t end) {
lv_anim_t a;
lv_anim_init(&a);
// lv_anim_set_var(&a, label);
lv_anim_set_values(&a, start, end);
lv_anim_set_time(&a, 300, 0); // 兼容旧版API:设置持续时间和延迟
lv_anim_set_exec_cb(&a, label, (lv_anim_exec_xcb_t)lv_obj_set_x); // 兼容旧版API
lv_anim_create(&a);
}
// 上一页事件
static void ebook_prev_event_cb(lv_obj_t* btn, lv_event_t event) {
if(event == LV_EVENT_SHORT_CLICKED) {
if(ebook_state.current_page > 0) {
// 先设置新内容再动画
ebook_state.current_page--;
update_ebook_display();
// 从左侧滑入动画
lv_obj_set_x(ebook_state.page_label, -lv_obj_get_width(lv_obj_get_parent(ebook_state.page_label)));
ebook_page_anim(ebook_state.page_label,
-lv_obj_get_width(lv_obj_get_parent(ebook_state.page_label)),
0);
}
}
}
// 下一页事件
static void ebook_next_event_cb(lv_obj_t* btn, lv_event_t event) {
if(event == LV_EVENT_SHORT_CLICKED) {
if(ebook_state.current_page < ebook_state.total_pages - 1) {
ebook_state.current_page++;
update_ebook_display();
// 从右侧滑入动画
lv_obj_set_x(ebook_state.page_label, lv_obj_get_width(lv_obj_get_parent(ebook_state.page_label)));
ebook_page_anim(ebook_state.page_label,
lv_obj_get_width(lv_obj_get_parent(ebook_state.page_label)),
0);
}
}
}
2.5 功能说明
支持左右滑动翻页,滑动距离大于30像素且时间小于300ms时触发
屏幕两侧的箭头按钮提供物理翻页功能
使用LVGL动画实现平滑的翻页效果
底部显示当前页码和总页数
使用英文字体内容显示(中文字体需要进一步优化取模)
三、零知IDE配置
3.1 项目设置
打开零知IDE,创建新项目
选择正确的开发板型号(零知增强板)
添加以下库依赖:
LVGL
ILI9486驱动
XPT2046触摸驱动
3.2 LCD屏幕驱动和初始化
/* 与LCD驱动关联 */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
u16 height,width;
u16 i,j;
width=area->x2 - area->x1+1; //得到填充的宽度
height=area->y2 - area->y1+1; //高度
for(i=0;i<height;i++)
{
LCD_SetCursor(area->x1,area->y1+i); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
for(j=0;j<width;j++)
{
LCD_TYPE->LCD_RAM=color_p->full;//写入数据
color_p++;
}
}
lv_disp_flush_ready(disp);
}
/* 中断 ms */
static void lv_tick_handler(HardwareTimer*)
{
lv_tick_inc(LVGL_TICK_PERIOD);
}
void lvgl_setup()
{
lv_init();
lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * 10);
lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = LV_HOR_RES_MAX;
disp_drv.ver_res = LV_VER_RES_MAX;
disp_drv.flush_cb = my_disp_flush;
disp_drv.buffer = &disp_buf;
lv_disp_drv_register(&disp_drv);
lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touchpad_read;
lv_indev_drv_register(&indev_drv);
MyTim = new HardwareTimer(TIM2);
MyTim->setMode(2, TIMER_OUTPUT_COMPARE);
MyTim->setOverflow(1000/LVGL_TICK_PERIOD, HERTZ_FORMAT);
MyTim->attachInterrupt(lv_tick_handler);
MyTim->resume();
}
四、演示效果
4.1 功能演示
打开电子书应用,显示第一页内容
向右滑动:切换到上一页内容
向左滑动:切换到下一页内容
进度更新:底部页码随翻页自动更新
4.2 视频演示
STM32驱动ILI9486显示屏实现电子书阅读器
4.3 性能指标
项目 | 数值 | 说明 |
---|---|---|
翻页响应时间 | < 100ms | 从触摸到页面开始动画的时间 |
动画帧率 | 30 FPS | 翻页动画流畅度 |
内存占用 | 42KB | 包括LVGL和电子书数据 |
刷新率 | 30Hz | 显示屏刷新频率 |
五、常见问题解决
5.1 触摸不灵敏
解决方案
- 检查触摸屏校准数据
- 增加触摸检测阈值
bool my_touchpad_read(lv_indev_drv_t * indev, lv_indev_data_t * data)
{
static lv_coord_t last_x = 0;
static lv_coord_t last_y = 0;
data->state = ts.touched() ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
if(data->state == LV_INDEV_STATE_PR){
TS_Point p = ts.getPoint();
last_x = LV_HOR_RES-(p.y *LV_HOR_RES)/4095;
last_y = (p.x *LV_VER_RES)/4095;
Serial.print("touched:");
Serial.print(last_x);Serial.print(",");Serial.println(last_y);
}
data->point.x = last_x;
data->point.y = last_y;
return false;
}
5.2 翻页卡顿
优化建议
- 减少页面内容长度
- 使用LVGL的局部刷新功能
- 优化动画参数
lv_anim_set_time(&a, 300, 0); // 兼容旧版API:设置持续时间和延迟
六、总结与扩展
6.1 实现总结
本教程实现了电子书阅读器的核心功能:中文内容显示、触摸翻页、翻页动画效果、阅读进度显示
6.2 扩展建议
添加书签保存和跳转功能
实现字体大小切换
添加暗色主题保护视力
从SD卡加载电子书文件
6.3 下一步
在下一个系列教程中,我们将实现日历显示及切换