前言:
我选择了使用Arduino上先完成作业。虽然我在前期付出了很多努力学习IDF,并成功搭建了IDF编程环境,在box_lite上也能够跑通官方提供的demo。我已经基本摸清了IDF项目的结构,并且可以理解基本的命令。但是由于我缺乏一些基础,还没有达到自由发挥的水平。所以我决定先专注于完成作业,这样我可以更好地掌握任务要求,并为将来学习IDF打下坚实的基础。首先点亮屏幕并完成了中文支持,得益于前期在Arduino平台的积累,使用了LovyanGFX图形库完成了该项功能需求。 其次,按键的驱动是使用esp32的实时任务系统(FreeRTos)在后台不断扫描按键是否被按下,用于控制翻页功能,而不会影响主程序的正常运行。最后通过字符串解析功能成功获取了某新闻页面内容并分页显示在屏幕上。
这是我的 技术线路图,如有雷同纯属事实。
一、Arduino环境的建立
相比idf的编程环境,在Arduino上搭建box_lite 的编程环境就相对简单很多了,在安装最新的esp32开发板支持后,就可以在“工具栏选取ESP32-S3-Box”,不用找box_lite,直接选S3_Box就行。 就这样就可以在Arduino上实现一些简单的功能,要实现音频方面的功能应是比较费劲的,要大神才可以把idf那套东西转移到Arduino上来,鉴于目前Arduino环境的条件下,用了完成电子书项目是可以达到要求的。
二、屏幕驱动
需要完成的工作不单只是点亮屏幕还要支持中文输出。可以用TFT_eSPI来点亮屏幕和实现很多作图功能,但是要支持中文就比较麻烦,需要制作字库才能实现中文显示,但是通常打开网页的时候我们是不能提现预先获得页面上的具体信息的,如果已经知道页面的内容就没有打开的必要性了,那么我们的要求就需要比较务实了,就是可以打开任意的网页都可以正常显示中文。 本项目优雅的实现以上功能,采用了LovyanGFX这个图形库来驱动屏幕,在前面的多期项目中,已经有介绍过这个库了,简单地说,它有丰富的作图函数,关键的支持全中文字库,不需要自己制作字库,完美解决中文显示的问题。
实现起来也非常简单,以为该库已原生支持了box_lite, 所以连驱动文件都不需要做。只要如下简单的设定即可点亮屏幕。
#define LGFX_ESP32_S3_BOX_LITE // Espressif ESP32-S3-BOX Lite
#define LGFX_AUTODETECT // 自动检测屏幕参数
#include <LovyanGFX.hpp> // 关键图形库
#include <LGFX_AUTODETECT.hpp> // 自动检测库
static LGFX lcd;
static LGFX_Sprite spr;
void setup() {
Serial.begin(115200);
delay(2000);
lcd.init();
lcd.setTextWrap(true, true);
lcd.setFont(&fonts::efontCN_16_b); // 设置中文字库 ,这里选择了16号字,已经比较清晰了。
lcd.setTextColor(random(0x10000), bgColor);
lcd.setTextSize(1, 2);
}
Void loop(){
Lcd.print(“我是中国人,我说中国话”);
}
三、按键的驱动
Box_lite的三个按键只通过一个GPIO来实现的,这样做法我们在上一起寒假一起练的作业中已经遇到过了,所以这次也是轻车熟路。
int adc_key_val[] = { 850, 2000, 2600, 3000 }; // 定义一个数组 存放模拟键值比较值
int NUM_KEYS = 4;
int adc_key_in;
int key = -1;
int oldkey = -1;
#define SAMPLINGFREQUENCY 44100
#define SAMPLING_TIME_US (1000000UL / SAMPLINGFREQUENCY)
#define ANALOG_SIGNAL_INPUT 1
/ 该函数判断是哪个按键被按下,返回该按键序号
int get_key(unsigned int input) {
int k;
for (k = 0; k < NUM_KEYS; k++) {
if (input < adc_key_val[k]) { // 循环对比比较值,判断是否有键按下,有返回键号
return k;
}
}
return -1;
}
void key_scan(void* parm) {
uint32_t nextTime = 0;
int analogValumeSum;
while (true) {
analogValumeSum = 0;
for (int i = 0; i < 500; i++) {
analogValumeSum += analogRead(1);
// wait for next sample
while (micros() < nextTime)
;
nextTime = micros() + SAMPLING_TIME_US;
}
key = get_key(analogValumeSum / 500); // 调用判断按键程序
// Serial.println(key);
vTaskDelay(50);
}
}
void setup() {
Serial.begin(115200);
......
xTaskCreate(key_scan, "key_scan", 4096, NULL, 2, NULL); //用多任务来完成 按键扫描的工作。
...
...}
四、联网及爬取网络页面信息
Esp32本身的网络功能就比较强,所以联网的是没有什么问题的,但是爬虫技术却不是C语言的强项,实现起来就比较卑微啦。 因为我们的要求也没有多复杂,在把要爬取的目标页面的结构看情况后,简单的关键字查询技术来完成了页面信息解析的工作。
String extractContent(String html, String startTag, String endTag) {
int startIndex = html.indexOf(startTag) + startTag.length();
int endIndex = html.indexOf(endTag, startIndex);
if (startIndex != -1 && endIndex != -1) {
return html.substring(startIndex, endIndex);
}
return "";
}
std::vector<String> extractHrefs(String content) {
std::vector<String> hrefList;
int startIndex = 0;
int endIndex = 0;
// 循环查找并提取<a>标签中的href内容
while (startIndex >= 0 && endIndex >= 0) {
startIndex = content.indexOf("href=\"", endIndex) + 6;
endIndex = content.indexOf("\"", startIndex);
if (startIndex != -1 && endIndex != -1) {
String href = content.substring(startIndex, endIndex);
hrefList.push_back(href);
}
}
return hrefList;
}
void setup() {
Serial.begin(115200);
...
...
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
// 发送GET请求,获取网页内容
http.begin(web_url);
int httpCode = http.GET();
if (httpCode > 0) {
String htmlContent = http.getString();
// 定义存储提取内容的字符串变量
// 定义用于循环提取的起始索引和结束索引
int startIndex = 0;
int endIndex = 0;
// 循环提取每个段落的内容
while (true) {
// 在当前索引之后搜索<p>标签的起始位置
startIndex = htmlContent.indexOf("<p style=\"text-indent: 2em;\">", endIndex);
// 如果找不到<p>标签,结束循环
if (startIndex == -1) {
break;
}
// 在起始位置之后搜索</p>标签的结束位置
endIndex = htmlContent.indexOf("</p>", startIndex);
// 如果找不到</p>标签,结束循环
if (endIndex == -1) {
break;
}
// 提取<p>标签内的文字内容
String pTagContent = htmlContent.substring(startIndex, endIndex);
int textStart = pTagContent.indexOf(">") + 1;
String textContent = pTagContent.substring(textStart);
// 将提取的文字内容添加到合并字符串中,同时添加分段符号
extractedContent += textContent + "\n";
}
// 打印合并后的内容
Serial.println("Merged Content:\n" + extractedContent);
http.end();
}
// 计算总页数
pageCount = extractedContent.length() / charsPerPage + 1;
lcd.fillScreen(bgColor);
int startIndex = page * charsPerPage;
int endIndex = startIndex + charsPerPage;
// 切割提取的内容,确保不超出字符串范围
if (endIndex > extractedContent.length()) {
endIndex = extractedContent.length();
}
// 提取当前页的内容
String pageContent = extractedContent.substring(startIndex, endIndex);
// 显示当前页的内容
Serial.println(pageContent);
lcd.setCursor(3, 3);
lcd.println(pageContent);
}
}
总结:
前面花了很长的时间去搭建IDF的编程环境,反正是“小马过河”,有人说有很简单,也有人说很难,反正谁用谁知道。IDF功能很强大,但是也不很容易就上手的,就是说上完小学,你会写个300字的作文了吧,但是让你写个新闻稿件你还是写不去来的,因为你只懂些皮毛,没有办法打肿脸来充胖子,需要慢慢一步一步的练习,打通了各个环节,以后才能驾驭IDF。因为IDF凭一己之力完成天下所有功能,必然是一个庞大的体系,假如就这么简单,短短几天就上手了,那还要什么计算机专业呢。
好在咱也不是奔着当“股东”来的,还留了一手,即使IDF卷不多去,退一步,我还可以用Arduino. 也没有多丢人,至少还是可以Dis一下其他类型的选手。
这一期的作业就这么多了,下次再卷IDF,回头见。