首先感谢B站up主小智-学长提供的优质开源项目并且还有详细的视频、文档教学。视频教学包括硬件设计、软件设计和外壳结构的设计,视频的链接如下:
【有手就行系列】嵌入式单片机教程-桌面小屏幕实战教学 从设计、硬件、焊接到代码编写、调试 ESP32 持续更新2022【有手就行系列】嵌入式单片机教程-桌面小屏幕实战教学 从设计、硬件、焊接到代码编写、调试 ESP32 持续更新2022_哔哩哔哩_bilibili
DesktopScreenDemoV4.0.0的源码可以在小智学长的线上文档中下载到Docs (feishu.cn)。
此文主要用于个人学习成果的检验,如果能帮助到像我一样的初学者那就更好了。如果有理解错的或者写得不够好的地方,希望各位大佬不吝赐教,小弟一定虚心学习!
DesktopScreenDemoV4.0.0 app_main.c文件中对桌面小屏幕的功能实现用到了三个函数,所以我计划把项目代码分成三篇文章来进行分享,主要分成三个部分,sleep_mode_init()函数、background_task(void* arg)函数和app_main函数。这一节与大家一起学习的是sleep_mode_init()函数的内容。
sleep_mode_init()函数是DesktopScreen进入低功耗模式的初始化和一些功能的处理函数。
static void sleep_mode_init(){
while (true) {
set_tp_wackup_timeleft(600); 1.
//ap&sta关闭、且当前在主页、且超过10min未触摸时才进入低功耗
do {
esp_task_wdt_reset(); 2.
vTaskDelay(pdMS_TO_TICKS(1000)); 3.
count_tp_wackup_timeleft(); 4.
printf("wait enter sleep mode run... %d\n",get_tp_wackup_timeleft()); 5.
if(get_is_ap_sta_open() == false && ds_ui_get_now_show_page() == PAGE_TYPE_MEMU && get_tp_wackup_timeleft() == 0){
break;
} 6.
} while (1);
ds_touch_gpio_isr_remove(); 7.
gpio_wakeup_enable(BUTTON_GPIO_NUM_DEFAULT,GPIO_INTR_LOW_LEVEL); 8.
/* Wake up in 60 seconds, or when button is pressed */
// esp_sleep_enable_timer_wakeup(60000000);
esp_sleep_enable_gpio_wakeup(); 9.
printf("Entering light sleep\n");
/* Get timestamp before entering sleep */
int64_t t_before_us = esp_timer_get_time(); 10.
/* Enter sleep mode */
esp_light_sleep_start(); 11.
/* Execution continues here after wakeup */
/* Get timestamp after waking up from sleep */
int64_t t_after_us = esp_timer_get_time(); 12.
/* Determine wake up reason */
const char* wakeup_reason; 13.
uint32_t wackup_timeleft = 600; 14.
switch (esp_sleep_get_wakeup_cause()) { 15.
case ESP_SLEEP_WAKEUP_TIMER:
wakeup_reason = "timer";
wackup_timeleft = 5;
break;
case ESP_SLEEP_WAKEUP_GPIO:
wakeup_reason = "pin";
break;
default:
wakeup_reason = "other";
break;
}
printf("Returned from light sleep, reason: %s, t=%lld ms, slept for %lld ms\n",
wakeup_reason, t_after_us / 1000, (t_after_us - t_before_us) / 1000); 16.
set_tp_wackup_timeleft(wackup_timeleft); 17.
gpio_wakeup_disable(BUTTON_GPIO_NUM_DEFAULT); 18.
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); 19.
ds_touch_gpio_init(); 20.
ds_touch_gpio_isr_add(); 21.
reset_tp_action_manage(); 22.
}
}
1.set_tp_wackup_timeleft(600);设置屏幕唤醒时进入低功耗模式的倒计时。在ds_system_data.c中g_system_data被初始化为SYSTRM_DATA_T结构体的全局变量,tp_wackup_timeleft是SYSTRM_DATA_T结构体中一个无符号32位变量,用于配置指定的倒计时时间单位为秒。
函数原型如下:
void set_tp_wackup_timeleft(uint32_t timeleft){
//600/1s 10min
g_system_data.tp_wackup_timeleft = timeleft;
}
2.esp_task_wdt_reset();是esp32官方提供的看门狗相关的API函数,此函数可在包含esp_task_wdt.h后进行使用。调用此函数为当前运行的任务重置Task Watchdog Timer (TWDT) 任务监视计时器。每个订阅TWDT的任务都必须周期性地调用此函数来预防TWDT计时超时。如果单个或多个订阅了TWDT的任务没有在任务函数中重置TWDT,那TWDT将会发生超时。如果空闲任务订阅了TWDT,它将会在空闲钩子任务中自动地重置TWDT(空闲任务一般在使用Freertos时会涉及)。在从未订阅TWDT或者TWDT没有初始化的任务中调用此函数将会返回错误信息。
3.vTaskDelay()函数需要包含freertos文件夹下task.h文件并且配置宏定义INCLUDE_vTaskDelay为1后才能使用。调用此函数进行任务阻塞延时,参数xTicksToDelay用于配置延时多少时钟节拍(Ticks)。实际的任务阻塞时间取决于时钟节拍率。常量portTICK_PERIOD_MS可用于根据时钟节拍率计算实时时间——分辨率为一个时钟周期。宏定义pdMS_TO_TICKS()可用于计算在分辨率为一个时钟周期的情况下,单位为毫秒的时钟节拍数。这行代码调用此函数做1s的任务阻塞延时。
4.count_tp_wackup_timeleft()开启从运行模式进入低功耗模式的倒计时,set_tp_wackup_timeleft函数配置了计时时间,只有调用此函数才真正开始进行倒计时。函数原型如下:
void count_tp_wackup_timeleft(){
if(g_system_data.tp_wackup_timeleft > 0){
g_system_data.tp_wackup_timeleft --;
}
}
5.调用get_tp_wackup_timeleft()函数获取当前倒计时剩余时间,然后通过printf函数打印出来。函数原型如下:
uint32_t get_tp_wackup_timeleft(){
return g_system_data.tp_wackup_timeleft;
}
6.假如Wifi的ap&sta模式关闭与屏幕显示在主页并且屏幕未被触摸超过10min进入低功耗模式。
7. ds_touch_gpio_isr_remove()函数调用了gpio_isr_handler_remove(GPIO_INPUT_IO_0);函数移除GPIO0输入模式的中断处理器。
void ds_touch_gpio_isr_remove(){
gpio_isr_handler_remove(GPIO_INPUT_IO_0);
}
8.gpio_wakeup_enable(BUTTON_GPIO_NUM_DEFAULT,GPIO_INTR_LOW_LEVEL);是esp32官方自带的API函数,要使用它需要在文件中包含ds_gpio.h文件。第一个参数是要唤醒的GPIO管脚号,宏定义BUTTON_GPIO_NUM_DEFAULT为4;第二个参数是高电平或低电平触发,这里是低电平触发。调用此函数开启触屏TP的中断引脚(GPIO4)作为低电平唤醒功能,即只要外界触摸屏幕就进入唤醒模式。
9.esp_sleep_enable_gpio_wakeup();是esp32官方自带的API函数,要使用它需要在文件中包含esp_sleep.h文件。开启唤醒功能需要先调用gpio_wakeup_enable函数指定GPIO号和开启唤醒模式的电平触发方式,然后调用esp_sleep_enable_gpio_wakeup()函数开启唤醒功能。每个GPIO管脚都支持唤醒功能并且可以通过选择电平触发的方式来开启唤醒功能。与EXT0和EXT1唤醒源不同的是,此方法适用于所有IO口:包括RTC IO口和普通的数字IO口。不过它们只能用来唤醒轻睡眠模式。
10.获取进入睡眠之前的时间戳。调用esp_timer_get_time()函数获取从调用esp_timer_init()函数以来的微秒数。在本项目中esp_timer_init()函数会在app_main()函数中调用sleep_mode_init()函数之前进行调用。
11.使用配置好的唤醒源进入轻睡眠模式。
12.获取从睡眠中醒来后的时间戳。esp_timer_get_time()函数的作用见10.
13.用于保存唤醒的原因。
14.设置TP唤醒剩余时间,用于进入休眠的倒计时。
15.通过调用esp_sleep_get_wakeup_cause()函数获取唤醒源从睡眠中唤醒的原因。宏定义ESP_SLEEP_WAKEUP_TIMER表示唤醒原因为定时器唤醒,ESP_SLEEP_WAKEUP_GPIO表示唤醒原因为GPIO引脚触发。wakeup_reason存储唤醒原因,wackup_timeleft设置睡眠倒计时(单位为秒)。
16.打印内容:返回从轻睡眠模式中唤醒的原因,t_after_us / 1000从睡眠中醒来后的时间戳(除以1000转换成毫秒显示时间),(t_after_us - t_before_us) / 1000) 睡眠的时间 = 睡眠中醒来后的时间戳 - 进入睡眠之前的时间戳(除以1000转换成毫秒显示时间)。
17.配置TP唤醒无操作状态下进入低功耗状态的倒计时时间。
18.关闭TP中断引脚的唤醒功能。
19.关闭所有唤醒源。此函数用于禁用所有作为参数传递给函数的触发源。但不会修改RTC中的唤醒配置。 它将在esp_sleep_start函数中执行。
20.ds_touch_gpio_init() 触摸屏TP的GPIO初始化,函数原型如下:
void ds_touch_gpio_init(){
static bool has_init_isr = false; 1.
gpio_config_t io_conf; 2.
//disable interrupt
io_conf.intr_type = GPIO_PIN_INTR_DISABLE; 3.
//set as output mode
io_conf.mode = GPIO_MODE_OUTPUT; 4.
//bit mask of the pins that you want to set,e.g.GPIO18/19
io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL; 5.
//disable pull-down mode
io_conf.pull_down_en = 0; 6.
//disable pull-up mode
io_conf.pull_up_en = 0; 7.
//configure GPIO with the given settings
gpio_config(&io_conf); 8.
//GPIO interrupt type : both rising and falling edge
io_conf.intr_type = GPIO_INTR_ANYEDGE; 9.
//bit mask of the pins, use GPIO4/5 here
io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL; 10.
//set as input mode
io_conf.mode = GPIO_MODE_INPUT; 11.
//enable pull-up mode
io_conf.pull_up_en = 1; 12.
gpio_config(&io_conf); 13.
//change gpio intrrupt type for one pin
// gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_NEGEDGE);
if(has_init_isr == false){
//create a queue to handle gpio event from isr
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t)); 14.
//start gpio task
xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL); 15.
//install gpio isr service
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT); 16.
//hook isr handler for specific gpio pin
gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0); 17.
}
has_init_isr = true; 18.
}
20.1初始化中断标志位。
20.2 GPIO配置结构体。共有5个结构体成员,分别配置GPIO的位掩码、输入/输出模式、是否上拉GPIO口、是否下拉GPIO口、GPIO的中断类型。
typedef struct {
uint64_t pin_bit_mask; /*!< GPIO pin: set with bit mask, each bit maps to a GPIO */
gpio_mode_t mode; /*!< GPIO mode: set input/output mode */
gpio_pullup_t pull_up_en; /*!< GPIO pull-up */
gpio_pulldown_t pull_down_en; /*!< GPIO pull-down */
gpio_int_type_t intr_type; /*!< GPIO interrupt type */
} gpio_config_t;
20.3关闭GPIO口中断模式。
20.4开启GPIO输出模式。
20.5设置管脚的位掩码。
20.6关闭下拉模式。
20.7关闭上拉模式。
20.8调用gpio_config(&io_conf);函数,根据结构体成员的参数设置进行GPIO初始化。
小结:从进入函数开始到此处的代码是对TP的IO5口,也就是T_RST引脚进行配置,下面开始进行IO4口TP的T_INT引脚进行配置。
20.9配置中断类型为GPIO_INTR_ANYEDGE,上升沿和下降沿皆可触发中断。
20.10设置管脚的位掩码。
20.11开启GPIO输入模式。
20.12开启上拉模式。
20.13调用此函数根据结构体成员的参数设置,进行GPIO初始化。
20.14创建新队列,参数说明:QueueHandle_t为队列句柄。uxQueueLength为队列项数目。uxItemSize为每个队列项的大小,单位为字节。队列项通过拷贝的方式入队而不是通过引用入队,因此需要队列项的大小。每个队列项的大小必须相同。函数原型如下:
QueueHandle_t xQueueCreate (UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
20.15
创建gpio_task_example任务函数获取队列信息,并做处理。
static void gpio_task_example(void* arg)
{
uint32_t io_num;
for(;;) {
if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) { 1.
printf("GPIO[%d] intr, val: %d\n", io_num, gpio_get_level(io_num)); 2.
if(io_num == GPIO_INPUT_IO_0){ 3.
set_tp_wackup_timeleft(600);
if(gpio_get_level(io_num) == 0){
//TODO START COUNT
reset_tp_action_manage();
}else{ 4.
//TODO STOP COUNT
check_tp_action();
}
}
}
}
}
20.15.1此函数实际上是一个宏定义,真正被调用的函数是xQueueGenericReceive(),函数从队列中读取一个队列项并在队列中删除这个队列项,读取队列项是以拷贝的形式完成,因此需要有足够大的缓冲区以便容纳队列项,参数pvBuffer指向这个缓冲区。函数原型如下:
BaseType_t xQueueReceive(QueueHandle_t xQueue,
void *pvBuffer,TickType_t xTicksToWait);
20.15.2打印接收到的GPIO号同时获取GPIO号的电平触发状态。
20.15.3假如读取到缓冲区的数据,即GPIO口为T_INT(IO4),设置睡眠倒计时10分钟,函数详细讲解见1.;假如GPIO为低电平触发,则调用reset_tp_action_manage()函数初始化触摸屏管理相关结构体中的唤醒标志位。函数原型如下:
void reset_tp_action_manage(){
memset(&g_system_data.tp_action_manage,0,sizeof(TP_ACTION_MANAGE_T));
//set check on
g_system_data.tp_action_manage.status = 1;
}
20.15.4 check_tp_action()如果为高电平则检查当前屏幕被触摸的方式。
void check_tp_action(){
g_system_data.tp_action_manage.status = 0;
TP_ACTION_MANAGE_T *m_manage = &g_system_data.tp_action_manage; 1.
// if(abs(m_action_manage->tp_start_x - m_action_manage->tp_stop_x) > ){
// }
// ESP_LOGI(TAG,"timecount %d",m_manage->tp_interval_timecount);
ESP_LOGI(TAG, "start %d %d stop %d %d",m_manage->tp_start_x,m_manage->tp_start_y,m_manage->tp_stop_x,m_manage->tp_stop_y); 2.
if(m_manage->tp_stop_x == 0 && m_manage->tp_stop_y == 0 && m_manage->tp_start_x !=0 && m_manage->tp_start_y !=0){ 3.
ESP_LOGI(TAG,"action TP_ACTION_SHORT");
g_system_data.tp_action_manage.tp_action = TP_ACTION_SHORT;
ds_ui_page_manage_send_event(g_system_data.tp_action_manage.tp_action,g_system_data.tp_action_manage.tp_start_x,g_system_data.tp_action_manage.tp_start_y); 4.
return;
}
if(abs(m_manage->tp_start_x - m_manage->tp_stop_x) > abs(m_manage->tp_start_y - m_manage->tp_stop_y)){ 5.
//左右滑动
if(abs(m_manage->tp_start_x - m_manage->tp_stop_x) > 10){
if(m_manage->tp_start_x > m_manage->tp_stop_x){
ESP_LOGI(TAG,"action TP_ACTION_MOVE_LEFT");
g_system_data.tp_action_manage.tp_action = TP_ACTION_MOVE_LEFT;
}else{
ESP_LOGI(TAG,"action TP_ACTION_MOVE_RIGHT");
g_system_data.tp_action_manage.tp_action = TP_ACTION_MOVE_RIGHT;
}
ds_ui_page_manage_send_event(g_system_data.tp_action_manage.tp_action,g_system_data.tp_action_manage.tp_start_x,g_system_data.tp_action_manage.tp_start_y);
return;
}
}else{ 6.
//上下滑动
if(abs(m_manage->tp_start_y - m_manage->tp_stop_y) > 10){
if(m_manage->tp_start_y > m_manage->tp_stop_y){
ESP_LOGI(TAG,"action TP_ACTION_MOVE_UP");
g_system_data.tp_action_manage.tp_action = TP_ACTION_MOVE_UP;
}else{
ESP_LOGI(TAG,"action TP_ACTION_MOVE_DOWN");
g_system_data.tp_action_manage.tp_action = TP_ACTION_MOVE_DOWN;
}
ds_ui_page_manage_send_event(g_system_data.tp_action_manage.tp_action,g_system_data.tp_action_manage.tp_start_x,g_system_data.tp_action_manage.tp_start_y);
return;
}
}
//300ms
if(m_manage->tp_interval_timecount < 30){ 7.
if((m_manage->tp_stop_x + m_manage->tp_stop_y + m_manage->tp_start_x + m_manage->tp_start_y) == 0)
return;
ESP_LOGI(TAG,"action TP_ACTION_SHORT");
g_system_data.tp_action_manage.tp_action = TP_ACTION_SHORT;
ds_ui_page_manage_send_event(g_system_data.tp_action_manage.tp_action,g_system_data.tp_action_manage.tp_start_x,g_system_data.tp_action_manage.tp_start_y);
return;
}
//1.5s
if(m_manage->tp_interval_timecount > 150){ 8.
if(m_manage->tp_start_x != 0 && m_manage->tp_start_y != 0 &&m_manage->tp_stop_x != 0 &&m_manage->tp_stop_y != 0){
return;
}
ESP_LOGI(TAG,"action TP_ACTION_LONG");
g_system_data.tp_action_manage.tp_action = TP_ACTION_LONG;
ds_ui_page_manage_send_event(g_system_data.tp_action_manage.tp_action,g_system_data.tp_action_manage.tp_start_x,g_system_data.tp_action_manage.tp_start_y);
return;
}
}
20.15.4.1创建一个指向TP_ACTION_MANAGE_T触摸动作管理结构体的结构体指针用来存储当前已有的与触摸动作相关的数据。
20.15.4.2打印当前文件信息、屏幕被触摸的x轴和y轴起始坐标和结束坐标的日志信息到屏幕上,方便调试。
20.15.4.3如果只有x轴和y轴的起始坐标但没有结束坐标,那么屏幕被触摸的方式为短按。然后就是打印日志信息,设置屏幕触摸结构体中的触摸方式为短按。
//TP点击事件
void ds_ui_page_manage_send_event(TP_ACTION_E key,uint8_t touch_x,uint8_t touch_y){
UI_EVENT_T evt;
evt.key = key;
evt.touch_x = touch_x;
evt.touch_y = touch_y;
if(evt.key == TP_ACTION_SHORT){ 1.
if(evt.touch_x < 75){
if(evt.touch_y < 75){
//时钟
evt.action = PAGE_TYPE_TIME;
}else{
//单词
evt.action = PAGE_TYPE_WORD;
}
}else{
if(evt.touch_y < 75){
//天气
evt.action = PAGE_TYPE_WEATHER;
}else{
//番茄时钟
evt.action = PAGE_TYPE_TOMATO;
}
}
}
send_beep_event_from_isr(BEEP_SHORT_100MS); 2.
xQueueSendFromISR(ui_event_queue, &evt, NULL); 3.
}
20.15.4.3.1处理TP短按事件,创建UI_EVENT_T结构体变量evt存储屏幕按下方式、按下位置的x轴和y轴。屏幕触摸方式为短按,获取屏幕触摸的位置并处理对应的功能。屏幕的触摸位置如下表:
功能 | x轴 | y轴 |
时钟 | <75 | <75 |
单词 | <75 | >75 |
天气 | >75 | <75 |
番茄时钟 | >75 | >75 |
![](https://i-blog.csdnimg.cn/blog_migrate/bbddbc2fd593ced88a4a97e8b88c1e4a.png)
20.15.4.3.2触发蜂鸣器100毫秒 。xQueueSendFromISR函数实际上是一个宏,真正调用的是xQueueGenericSendFromISR函数,此函数发送一个项到队列的尾部并且适合在中断中使用,参数说明:xQueue为要发送到的目标队列的句柄; pvItemToQueue为指向要放置在队列中项目的指针,要存储在队列中项目的大小在队列创建时被定义,存储的字节大小从pvItemToQueue拷贝到队列的存储区域中;pxHigherPriorityTaskWoken,当发送到队列的任务为非阻塞状态,并且当前任务的优先级高于先前任务的优先级时,函数内部会把pxHigherPriorityTaskWoken设置为真,当pxHigherPriorityTaskWoken为真时,则在中断退出之前请求上下文切换。函数原型如下:
void send_beep_event_from_isr(BEEP_TYPE_E type){
uint32_t evt = type;
xQueueSendFromISR(beep_queue, &evt, 0);
}
#define xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) \
xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
20.15.4.3.3函数说明见20.15.4.3.2
20.15.5当屏幕x轴的起始坐标减去x轴结束坐标的绝对值大于屏幕y轴的起始坐标减去y轴结束坐标的绝对值时,判断为左右滑动。当x轴起始坐标>x轴结束坐标时判断为左滑,x轴起始坐标<x轴结束坐标时判断为右滑,并打印相关日志消息,记录标志位。最后调用ds_ui_page_manage_send_event函数处理屏幕点击事件。
20.15.6当屏幕y轴的起始坐标减去y轴结束坐标的绝对值大于屏幕x轴的起始坐标减去x轴结束坐标的绝对值时,判断为上下滑动。当y轴起始坐标>y轴结束坐标时判断为上滑,y轴起始坐标<y轴结束坐标时判断为下滑,并打印相关日志消息,记录标志位。最后调用ds_ui_page_manage_send_event函数处理屏幕点击事件。
20.15.7判断短按,假如屏幕被按下的时间小于300ms为短按。
20.15.8判断长按,假如屏幕被按下的时间大于1.5s为长按。
20.16配置GPIO中断。
20.17配置GPIO中断后需要调用此函数添加对应的GPIO中断服务。
20.18记录中断初始化标志。
21.此函数在函数体内部调用gpio_isr_handler_add函数,应该是为了保障中断能有效地被添加再次调用add函数,同20.17。
22.同20.15.3。
小结:sleep_mode_init()函数的个人学习分享到这里就结束了,感谢耐心观看的小伙伴,希望对大家能有一点帮助!