一、项目需求
- 实现ESP32连接WIFI,获取网页信息;
- 实现TFT屏幕的驱动,并添加中文显示功能;
- 能够将网页上的文本信息在屏幕上显示出来;
- 实现按键交互,完成子页面选择,切换和翻页功能。
二、实现方案
1.总体方案
软件上采用PlatformIO进行开发,基于Arduino的软件框架,有很多现成的功能库可以调用,能极大的方便开发工作的进行。
在本次项目中我使用了TFT_eSPI库驱动TFT屏幕只需要根据硬件连接图对setup文件进行配置更改就可以快速点亮esp32-s3-box上的LCD屏幕。
然后关于ADC按键和WIFI等功能的开发都可以使用Arduino的接口进行快速开发。
项目设计思路流程为:
HTTP服务器搭建 -> LCD屏幕驱动、按键驱动和wifi程序设计 -> 中文显示程序设计 -> 菜单逻辑设计 -> 整合功能,完成软件设计
系统软件流程图:
2.服务器搭建
由于我对网络相关知识了解较少,所以本次项目中使用的是python中的http.server模块。
对其源文件进行了简单的修改,实现了通过.py文件直接将其同目录下的pyhttpserver文件夹作为一个局域网http服务器,可以通过本机浏览器访问localhost:8000或0.0.0.0:8000进入该目录下,局域网设备也可通过访问本机IP端口8000访问该路径
3.中文显示
中文显示是通过字库生成工具生成了一个完整的16*16单色深unioncode字库,以c源码const数组的形式存放在项目工程中,通过程序,根据UTF-8编码计算获得unioncode汉字索引
uint8_t get_utf8_font(uint8_t* utf8_text, uint8_t** font_data)
{
int outputSize = 0; //记录转换后的Unicode字符串的字节数
uint16_t font_format = 0; // 字符unicode编码
uint8_t tmp[2];
if (*utf8_text > 0x00 && *utf8_text <= 0x7F) //处理单字节UTF8字符(英文字母、数字)
{
font_format = *utf8_text;
outputSize = 1;
}
else if (((*utf8_text) & 0xE0) == 0xC0) //处理双字节UTF8字符
{
char high = *utf8_text;
utf8_text++;
char low = *utf8_text;
if ((low & 0xC0) != 0x80) //检查是否为合法的UTF8字符表示
{
return -1; //如果不是则报错
}
tmp[0] = (high << 6) + (low & 0x3F);
tmp[1] = (high >> 2) & 0x07;
font_format = tmp[0] + (tmp[1] << 8);
outputSize = 2;
}
else if (((*utf8_text) & 0xF0) == 0xE0) //处理三字节UTF8字符
{
char high = *utf8_text;
utf8_text++;
char middle = *utf8_text;
utf8_text++;
char low = *utf8_text;
if (((middle & 0xC0) != 0x80) || ((low & 0xC0) != 0x80))
{
return -1;
}
tmp[0] = (middle << 6) + (low & 0x7F);
tmp[1] = (high << 4) + ((middle >> 2) & 0x0F);
font_format = tmp[0] + (tmp[1] << 8);
outputSize = 3;
}
else //对于其他字节数的UTF8字符不进行处理
{
return -1;
}
*font_data = font_16VH[font_format];
return outputSize;
}
再按照字库取模方式,逐列式画点,编写文本显示函数,实现全unioncode的中文显示。
uint16_t show_char(uint16_t x0, uint16_t y0, uint8_t *pdata, uint16_t len, uint32_t color)
{
uint8_t *utf8_p = pdata;
uint8_t *dis_ram;
uint8_t data_size;
uint16_t x = x0, y = y0;
for (int ret = 0; ret < len && *utf8_p != 0; ret++)
{
if (*utf8_p > 0x00 && *utf8_p <= 0x7F)
data_size = 8;
else
data_size = 16;
uint8_t updata = get_utf8_font(utf8_p, &dis_ram);
if (updata)
utf8_p += updata;
else
utf8_p++;
for (int i = 0; i < data_size; i++)
{
uint8_t dis_data = dis_ram[i * 2];
for (uint8_t j = 0; j < 8; j++)
{
if (dis_data & 0x01)
tft.drawPixel(x + i, y + j, color);
dis_data >>= 1;
}
dis_data = dis_ram[i * 2 + 1];
for (uint8_t j = 0; j < 8; j++)
{
if (dis_data & 0x01)
tft.drawPixel(x + i, y + 8 + j, color);
dis_data >>= 1;
}
}
x += (data_size + 2);
}
return utf8_p - pdata;
}
4.网页子页面解析
根据http网页源码内容,对各个子路径的链接进行内容解析,获取各个文档的子路径,名字和大小,然后通过后续程序即可访问子路径并显示器中的文本内容
void http_get_list(void) // 获取首页html信息
{
HTTPClient http; // 声明HTTPClient对象
http.begin("http://192.168.31.169:8000/text/"); // 准备启用连接
int httpCode = http.GET(); // 发起GET请求
if (httpCode > 0) // 如果状态码大于0说明请求过程无异常
{
if (httpCode == HTTP_CODE_OK) // 请求被服务器正常响应,等同于httpCode == 200
{
int len = http.getSize(); // 读取响应正文数据字节数,如果返回-1是因为响应头中没有Content-Length属性
WiFiClient *stream = http.getStreamPtr(); // 获取响应正文数据流指针
while (http.connected() && (len > 0 || len == -1)) // 当前已连接并且有数据可读
{
size_t size = stream->available(); // 获取数据流中可用字节数
if (size)
{
int c = stream->readBytes(txt_buff, ((size > 10*1024) ? 10*1024 : size)); // 读取数据到buff
}
memset(html_list, 0, sizeof(html_list));
box_book_num = get_html_list(txt_buff, strlen((char *)txt_buff), html_list);
Serial.printf("html list num : %d\r\n", box_book_num);
for(uint8_t ret = 0; ret < box_book_num ; ret ++)
{
Serial.printf("%d, addr:%s, name:%s, size:%d\r\n", ret + 1, html_list[ret].name, html_list[ret].address, html_list[ret].size);
}
}
}
}
else
{
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end(); // 结束当前连接
}
再将读到的网页信息根据网页格式,解析出各个链接和文件名
uint8_t get_html_list(uint8_t* text, uint32_t text_len, html_info* list) // 解析页面中的标题和子链接路径
{
uint32_t ret = 0;
uint8_t count = 0;
for(ret = 0 ; ret < text_len ; ret ++)
{
if(text[ret] == 'a')
{
if(strncmp((char*)(&text[ret]), "a href=\"", 8) == 0)
{
ret += 8;
uint8_t ret_s;
for(ret_s = 0 ; ret_s < 100 ; ret_s ++)
{
if(text[ret] != '\"')
{
list[count].address[ret_s] = text[ret];
ret ++;
}
else
break;
}
ret += 2;
for(ret_s = 0 ; ret_s < 100 ; ret_s ++)
{
if(strncmp((char*)(&text[ret - 4]), ".txt", 4) == 0)
{
list[count].size = str_2_dec(&text[ret + 9]);
break;
}
list[count].name[ret_s] = text[ret];
ret ++;
if(text[ret] == '/')
{
list[count].size = 0;
break;
}
}
count ++;
}
}
}
return count;
}
得到这些信息就可以对各个文档的子链接进行访问
5.交互功能整合
在可以获取各个网页信息,显示文本内容以后,结合按键驱动,完善项目逻辑实现完整的文本阅读器功能。
void loop()
{
tft.fillScreen(0xFFFF);
http_get_list();
while(box_func_page == 0)
{
for(uint8_t ret = 0; ret < box_book_num ; ret ++)
{
memset(LIST_DIS_BUF, 0, 100);
sprintf((char *)LIST_DIS_BUF, "%d : %s\r\n", ret + 1, html_list[ret].name, html_list[ret].size);
show_char(20, 5 + ret * 18, LIST_DIS_BUF, sizeof(LIST_DIS_BUF), 0);
}
show_char(5, 5 + box_select_cursor * 18, (uint8_t *)">", 1, 0);
key = esp_box_adcbutton();
if(key == 1)
{
if(box_select_cursor > 0)
{
tft.fillRect(5, 5 + box_select_cursor * 18, 8, 16, 0xFFFF);
box_select_cursor --;
}
}
if(key == 2)
{
box_func_page = 1;
}
if(key == 3)
{
if(box_select_cursor < box_book_num)
{
tft.fillRect(5, 5 + box_select_cursor * 18, 8, 16, 0xFFFF);
box_select_cursor ++;
}
}
}
tft.fillScreen(0xFFFF);
show_char(120, 100, (uint8_t *)"加载中。。。", strlen("加载中。。。"), 0);
http_get_txt();
tft.fillScreen(0xFFFF);
box_page_num = 0;
box_dis_cursor = 0;
while(box_func_page == 1)
{
show_page(txt_buff + box_dis_cursor, box_all_data_len - box_dis_cursor, &n_data_len, &n_dis_len, 0);
box_page_data_len[box_page_num] = n_data_len;
key = esp_box_adcbutton();
if(key == 1)
{
if(box_page_num > 0)
{
box_page_num --;
box_dis_cursor -= box_page_data_len[box_page_num];
tft.fillScreen(0xFFFF);
}
}
if(key == 2)
{
box_func_page = 0;
}
if(key == 3)
{
if(box_dis_cursor + box_page_data_len[box_page_num] < box_all_data_len && box_page_num < 5000 - 1)
{
box_dis_cursor += box_page_data_len[box_page_num];
box_page_num ++;
tft.fillScreen(0xFFFF);
}
}
}
}
三、项目成果
项目是实现了一个可以访问HTTP服务器内txt文本的文本阅读器,可通过案件选择要阅读的文本,以及通过案件进行翻页操作。