bool lv_draw_dispatch_layer(lv_display_t * disp, lv_layer_t * layer) { LV_PROFILER_BEGIN; /*Remove the finished tasks first*/ lv_draw_task_t * t_prev = NULL; lv_draw_task_t * t = layer->draw_task_head; while(t) { lv_draw_task_t * t_next = t->next; if(t->state == LV_DRAW_TASK_STATE_READY) { if(t_prev) t_prev->next = t->next; /*Remove it by assigning the next task to the previous*/ else layer->draw_task_head = t_next; /*If it was the head, set the next as head*/ /*If it was layer drawing free the layer too*/ if(t->type == LV_DRAW_TASK_TYPE_LAYER) { lv_draw_image_dsc_t * draw_image_dsc = t->draw_dsc; lv_layer_t * layer_drawn = (lv_layer_t *)draw_image_dsc->src; if(layer_drawn->draw_buf) { int32_t h = lv_area_get_height(&layer_drawn->buf_area); uint32_t layer_size_byte = h * layer_drawn->draw_buf->header.stride; _draw_info.used_memory_for_layers_kb -= get_layer_size_kb(layer_size_byte); LV_LOG_INFO("Layer memory used: %" LV_PRIu32 " kB\n", _draw_info.used_memory_for_layers_kb); lv_draw_buf_destroy(layer_drawn->draw_buf); layer_drawn->draw_buf = NULL; } /*Remove the layer from the display's*/ if(disp) { lv_layer_t * l2 = disp->layer_head; while(l2) { if(l2->next == layer_drawn) { l2->next = layer_drawn->next; break; } l2 = l2->next; } if(disp->layer_deinit) disp->layer_deinit(disp, layer_drawn); lv_free(layer_drawn); } } lv_draw_label_dsc_t * draw_label_dsc = lv_draw_task_get_label_dsc(t); if(draw_label_dsc && draw_label_dsc->text_local) { lv_free((void *)draw_label_dsc->text); draw_label_dsc->text = NULL; } lv_free(t->draw_dsc); lv_free(t); } else { t_prev = t; } t = t_next; } bool task_dispatched = false; /*This layer is ready, enable blending its buffer*/ if(layer->parent && layer->all_tasks_added && layer->draw_task_head == NULL) { /*Find a draw task with TYPE_LAYER in the layer where the src is this layer*/ lv_draw_task_t * t_src = layer->parent->draw_task_head; while(t_src) { if(t_src->type == LV_DRAW_TASK_TYPE_LAYER && t_src->state == LV_DRAW_TASK_STATE_WAITING) { lv_draw_image_dsc_t * draw_dsc = t_src->draw_dsc; if(draw_dsc->src == layer) { t_src->state = LV_DRAW_TASK_STATE_QUEUED; lv_draw_dispatch_request(); break; } } t_src = t_src->next; } } /*Assign draw tasks to the draw_units*/ else { /*Find a draw unit which is not busy and can take at least one task*/ /*Let all draw units to pick draw tasks*/ lv_draw_unit_t * u = _draw_info.unit_head; while(u) { int32_t taken_cnt = u->dispatch_cb(u, layer); if(taken_cnt != LV_DRAW_UNIT_IDLE) task_dispatched = true; u = u->next; } } LV_PROFILER_END; return task_dispatched; }

时间: 2025-03-22 19:16:29 浏览: 48
<think>好的,我现在需要分析用户提供的函数`bool lv_draw_dispatch_layer(lv_display_t * disp, lv_layer_t * layer)`,并解释它的功能。让我先仔细阅读代码,理解各个部分的作用。 首先,函数开始和结束处有性能分析的宏`LV_PROFILER_BEGIN`和`LV_PROFILER_END`,说明这个函数可能是性能关键部分,需要监控执行时间。 接下来看第一部分,处理已完成的任务。代码遍历`layer->draw_task_head`链表,检查每个任务的状态是否为`LV_DRAW_TASK_STATE_READY`。如果是,就移除该任务,并处理相关资源。特别是当任务类型是`LV_DRAW_TASK_TYPE_LAYER`时,需要释放对应的图层资源,比如`draw_buf`,并从显示器的图层链表中移除该图层。这可能涉及到内存管理和资源释放,需要注意指针操作是否正确,比如链表的断开和内存的释放。 然后处理标签描述符,如果`text_local`存在,需要释放文本内容的内存。这部分需要确保释放的是正确的指针,避免内存泄漏或重复释放。 接下来是第二部分,处理任务分发。如果当前图层已经准备就绪,并且所有任务都添加完成且没有剩余任务,则查找父图层中的对应任务,将其状态设为`LV_DRAW_TASK_STATE_QUEUED`,并请求分发。这可能是为了图层合成,确保父图层能正确使用子图层的内容。 否则,遍历所有的绘制单元(`draw_unit`),调用它们的`dispatch_cb`回调函数,分发任务。如果任何一个单元处理了任务,就将`task_dispatched`设为true,最终返回这个状态。 在这个过程中,需要注意链表操作的准确性,尤其是在删除节点时如何处理前驱和后继节点,避免出现悬空指针或内存泄漏。同时,资源释放部分需要检查是否正确释放了图层相关的缓冲区和描述符,特别是在处理`LV_DRAW_TASK_TYPE_LAYER`类型任务时。 可能的问题点包括:链表遍历时是否正确处理了节点的删除,尤其是在头节点被删除的情况下;释放内存后是否将指针置为NULL,避免野指针;在多图层情况下,是否正确更新了显示器的图层链表;以及各个回调函数是否正确调用,比如`layer_deinit`和`dispatch_cb`。 此外,函数返回的`task_dispatched`变量用于指示是否有任务被分发,这可能会影响后续的绘制流程,比如是否需要继续处理或等待。 总结一下,这个函数主要负责管理绘图任务的清理和分发,确保资源正确释放,任务有效分配到绘制单元,同时处理图层之间的依赖关系。需要确保每一步操作都正确无误,尤其是在内存管理和链表操作上。</think>以下是对 `bool lv_draw_dispatch_layer(lv_display_t * disp, lv_layer_t * layer)` 函数的逐步分析: --- ### **功能概述** 该函数是 **LVGL图形库** 中负责图层绘制任务调度与资源管理的核心函数。主要完成以下操作: 1. **清理已完成的任务**:释放已完成的绘制任务及关联资源。 2. **图层合成处理**:管理子图层的缓冲区与父图层的绘制任务状态。 3. **任务分发**:将未完成的任务分配给绘图单元执行。 --- ### **详细步骤解析** #### **1. 清理已完成任务** ```c lv_draw_task_t * t_prev = NULL; lv_draw_task_t * t = layer->draw_task_head; while(t) { // 遍历任务链表,移除状态为 READY 的任务 if(t->state == LV_DRAW_TASK_STATE_READY) { // 从链表中移除任务节点 if(t_prev) t_prev->next = t->next; else layer->draw_task_head = t_next; // 特殊处理图层类型任务 if(t->type == LV_DRAW_TASK_TYPE_LAYER) { // 释放子图层缓冲区内存 lv_draw_buf_destroy(layer_drawn->draw_buf); layer_drawn->draw_buf = NULL; // 从显示器的图层链表中移除子图层 l2->next = layer_drawn->next; // 调用反初始化回调并释放子图层内存 if(disp->layer_deinit) disp->layer_deinit(disp, layer_drawn); lv_free(layer_drawn); } // 释放标签文本内存(如果存在) if(draw_label_dsc && draw_label_dsc->text_local) { lv_free((void *)draw_label_dsc->text); } // 释放任务描述符及任务本身 lv_free(t->draw_dsc); lv_free(t); } // 继续处理下一个任务 t = t_next; } ``` - **关键点**: - 链表操作需正确处理前驱节点 (`t_prev`) 以维护链表完整性。 - 对 `LV_DRAW_TASK_TYPE_LAYER` 类型任务,需递归释放子图层资源并更新内存统计。 --- #### **2. 图层合成处理** ```c if(layer->parent && layer->all_tasks_added && layer->draw_task_head == NULL) { // 查找父图层中等待状态的图层任务 while(t_src) { if(t_src->type == LV_DRAW_TASK_TYPE_LAYER && t_src->state == LV_DRAW_TASK_STATE_WAITING) { // 更新任务状态为已排队,触发合成请求 t_src->state = LV_DRAW_TASK_STATE_QUEUED; lv_draw_dispatch_request(); break; } t_src = t_src->next; } } ``` - **作用**: - 当子图层所有任务完成且无剩余任务时,通知父图层进行合成。 - 通过修改任务状态 (`QUEUED`) 和调用 `lv_draw_dispatch_request()` 触发后续绘制流程。 --- #### **3. 任务分发至绘图单元** ```c lv_draw_unit_t * u = _draw_info.unit_head; while(u) { // 调用绘图单元的分发回调函数 int32_t taken_cnt = u->dispatch_cb(u, layer); if(taken_cnt != LV_DRAW_UNIT_IDLE) task_dispatched = true; u = u->next; } ``` - **逻辑**: - 遍历所有绘图单元(如GPU、CPU渲染器),尝试分配任务。 - 若单元接受任务(返回值非 `LV_DRAW_UNIT_IDLE`),标记任务已分发。 --- ### **返回值** - **`task_dispatched`**: - `true`: 有任务被分发到绘图单元。 - `false`: 所有绘图单元空闲或无任务可处理。 --- ### **技术要点总结** 1. **资源管理**: - 动态内存释放需严格匹配分配方式(如 `lv_free` 对应 `lv_malloc`)。 - 图层缓冲区的销毁需更新全局内存统计 (`_draw_info.used_memory_for_layers_kb`)。 2. **状态机控制**: - 任务状态(`READY`, `WAITING`, `QUEUED`)驱动绘制流程的推进。 3. **链表操作**: - 正确处理头节点删除和中间节点移除,避免内存泄漏或链表断裂。 --- ### **应用场景** 此函数在LVGL的渲染循环中调用,常见于: - 屏幕刷新时处理图层绘制任务。 - 动态UI更新后重新合成图层。 - 资源受限环境下确保内存高效复用。 通过逐层调度和资源回收,保障嵌入式设备上的流畅图形渲染。
阅读全文

相关推荐

void lv_draw_mask_radius_init(lv_draw_mask_radius_param_t * param, const lv_area_t * rect, lv_coord_t radius, bool inv) { lv_coord_t w = lv_area_get_width(rect); lv_coord_t h = lv_area_get_height(rect); int32_t short_side = LV_MIN(w, h); if(radius > short_side >> 1) radius = short_side >> 1; if(radius < 0) radius = 0; lv_area_copy(¶m->cfg.rect, rect); param->cfg.radius = radius; param->cfg.outer = inv ? 1 : 0; param->dsc.cb = (lv_draw_mask_xcb_t)lv_draw_mask_radius; param->dsc.type = LV_DRAW_MASK_TYPE_RADIUS; if(radius == 0) { param->circle = NULL; return; } uint32_t i; /*Try to reuse a circle cache entry*/ for(i = 0; i < LV_CIRCLE_CACHE_SIZE; i++) { if(LV_GC_ROOT(_lv_circle_cache[i]).radius == radius) { LV_GC_ROOT(_lv_circle_cache[i]).used_cnt++; CIRCLE_CACHE_AGING(LV_GC_ROOT(_lv_circle_cache[i]).life, radius); param->circle = &LV_GC_ROOT(_lv_circle_cache[i]); return; } } /*If not found find a free entry with lowest life*/ _lv_draw_mask_radius_circle_dsc_t * entry = NULL; for(i = 0; i < LV_CIRCLE_CACHE_SIZE; i++) { if(LV_GC_ROOT(_lv_circle_cache[i]).used_cnt == 0) { if(!entry) entry = &LV_GC_ROOT(_lv_circle_cache[i]); else if(LV_GC_ROOT(_lv_circle_cache[i]).life < entry->life) entry = &LV_GC_ROOT(_lv_circle_cache[i]); } } if(!entry) { entry = lv_mem_alloc(sizeof(_lv_draw_mask_radius_circle_dsc_t)); LV_ASSERT_MALLOC(entry); lv_memset_00(entry, sizeof(_lv_draw_mask_radius_circle_dsc_t)); entry->life = -1; } else { entry->used_cnt++; entry->life = 0; CIRCLE_CACHE_AGING(entry->life, radius); } param->circle = entry; circ_calc_aa4(param->circle, radius); }讲解代码

/* * @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git * @Date: 2025-05-01 09:51:31 * @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git * @LastEditTime: 2025-05-02 13:11:10 * @FilePath: \lv_port_pc_eclipse_v8.3c:\Users\Y2742\Documents\PlatformIO\Projects\esp32_ui_arduio\src\main.cpp * @Description: 这是默认设置,请设置customMade, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE */ #include <Arduino.h> #include <SPI.h> #include <TFT_eSPI.h> #include "lvgl.h" #include "ui/ui.h" #include "ui/screens.h" #include <WiFi.h> #include #define LCD_BL_Pin 2 // 背光 TFT_eSPI tft = TFT_eSPI(); static lv_disp_draw_buf_t draw_buf; // ************** 配置区域 ************** const char* WIFI_SSID = "HTDSJ"; const char* WIFI_PASS = "a1234567"; //const char* MQTT_SERVER = "f69a9a66.ala.dedicated.aliyun.emqxcloud.cn"; // EMQX公共服务器 const char* MQTT_SERVER = "172.16.80.252"; // EMQX公共服务器 const int MQTT_PORT = 1883; const char* CLIENT_ID = "ESP32-Client"; // 客户端ID const char* TOPIC_SUB = "esp32/commands"; // 订阅主题 const char* TOPIC_PUB = "esp32/status"; // 发布主题 const char* MQTT_USER = "12345678"; // 新增用户名 const char* MQTT_PASS = "12345678"; // 新增密码 // ************************************** WiFiClient espClient; PubSubClient mqtt(espClient); // ========== WiFi连接函数 ========== void connectWiFi() { Serial.printf("\n[WiFi] 正在连接 %s...", WIFI_SSID); WiFi.begin(WIFI_SSID, WIFI_PASS); unsigned long startTime = millis(); while (WiFi.status() != WL_CONNECTED && millis() - startTime < 15000) { delay(500); Serial.print("."); } if (WiFi.status() == WL_CONNECTED) { Serial.println("\n[WiFi] 连接成功!"); Serial.println("========= WiFi详情 ========="); Serial.printf("IP地址: \t%s\n", WiFi.localIP().toString().c_str()); Serial.printf("信号强度: \t%d dBm\n", WiFi.RSSI()); Serial.printf("MAC地址: \t%s\n", WiFi.macAddress().c_str()); } else { Serial.println("\n[WiFi] 连接失败!"); } } // ========== MQTT连接函数 ========== void connectMQTT() { Serial.println("\n[MQTT] 正在连接服务器..."); mqtt.setServer(MQTT_SERVER, MQTT_PORT); // 关键修改点:添加认证参数 ↓↓↓ bool connectionStatus = mqtt.connect( CLIENT_ID, // 客户端ID MQTT_USER, // 用户名(新增) MQTT_PASS // 密码(新增) ); if (mqtt.connect(CLIENT_ID)) { Serial.println("[MQTT] 连接成功!"); // 订阅主题 if(mqtt.subscribe(TOPIC_SUB)){ Serial.printf("订阅成功: \t%s\n", TOPIC_SUB); } else { Serial.println("订阅失败!"); } } else { Serial.printf("[MQTT] 连接失败! 错误码: %d\n", mqtt.state()); } } // 在MQTT连接代码中添加认证参数 // ========== MQTT回调函数 ========== void callback(char* topic, byte* payload, unsigned int length) { Serial.printf("\n[消息接收] 主题: %s\n内容: ", topic); for (int i=0; i<length; i++) { Serial.print((char)payload[i]); } Serial.println(); } void mqqt_setup() { delay(1000); // 第一阶段:连接WiFi connectWiFi(); // 第二阶段:连接MQTT if(WiFi.status() == WL_CONNECTED){ mqtt.setCallback(callback); connectMQTT(); } } void mqqt_loop() { // 维持MQTT连接 if (!mqtt.connected() && WiFi.status() == WL_CONNECTED) { Serial.println("\n[MQTT] 尝试重新连接..."); connectMQTT(); } mqtt.loop(); // 每10秒发布状态 static unsigned long lastPub = 0; if (millis() - lastPub > 10000) { String msg = "在线状态: " + String(millis()/1000) + "秒"; if(mqtt.publish(TOPIC_PUB, msg.c_str())){ Serial.printf("\n[消息发送] 主题: %s\n内容: %s\n", TOPIC_PUB, msg.c_str()); } lastPub = millis(); } } /* Display flushing */ void my_disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { uint32_t w = ( area->x2 - area->x1 + 1 ); uint32_t h = ( area->y2 - area->y1 + 1 ); tft.startWrite(); tft.setAddrWindow( area->x1, area->y1, w, h ); tft.pushColors( ( uint16_t * )&color_p->full, w * h, true ); tft.endWrite(); lv_disp_flush_ready( disp_drv ); } void lvgl_user_init(void) { lv_init(); lv_color_t* buf1 = (lv_color_t*) heap_caps_malloc(320 * 240, MALLOC_CAP_INTERNAL); lv_disp_draw_buf_init( &draw_buf, buf1, NULL, 320 * 240); /*Initialize the display*/ static lv_disp_drv_t disp_drv; lv_disp_drv_init( &disp_drv ); /*Change the following line to your display resolution*/ disp_drv.hor_res = 320; disp_drv.ver_res = 240; disp_drv.flush_cb = my_disp_flush; disp_drv.full_refresh = 1; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv); ui_init(); pinMode(LCD_BL_Pin,OUTPUT); // 初始化背光引脚 digitalWrite(LCD_BL_Pin,HIGH); // 打开背光 } // lvgl任务函数 void LV_Task() { ui_tick(); lv_timer_handler(); /* let the GUI do its work */ } hw_timer_t *tim1 = NULL; // 初始化定时器函数 void Init_timer() { tim1 = timerBegin(0, 240, true); timerAttachInterrupt(tim1, LV_Task, true); timerAlarmWrite(tim1, 5000ul, true);//5ms进一次中断 timerAlarmEnable(tim1); } /************************************************************************************************* * 函 数 名: Electromagnetic_Strength_Set * 入口参数: val - 电磁强度值 * 返 回 值: 无 * 函数功能: 设置电磁强度 * 说 明: 无 *************************************************************************************************/ void Electromagnetic_Strength_Set(uint32_t val) { char buf[4] = {0}; sprintf(buf,"%d",val); lv_label_set_text(objects.electromagnetic_strength,buf); } /************************************************************************************************* * 函 数 名: Battery_Set * 入口参数: battery_status - 电量状态:True为高电量 反之为低电量 * 返 回 值: 无 * 函数功能: 设置电量等级 * 说 明: 无 *************************************************************************************************/ void Battery_Set(bool battery_status) { if(battery_status) lv_obj_set_style_img_opa(objects.low_battery,0,0); else lv_obj_set_style_img_opa(objects.low_battery,255,0); } /************************************************************************************************* * 函 数 名: Voice_Set * 入口参数: voice_status - 音量状态:一共三种状态,从0开始到2 * 返 回 值: 无 * 函数功能: 设置音量等级 * 说 明: 无 *************************************************************************************************/ void Voice_Set(uint8_t voice_status) { lv_obj_set_style_img_opa(objects.volume_level0,0,0); lv_obj_set_style_img_opa(objects.volume_level1,0,0); lv_obj_set_style_img_opa(objects.volume_level2,0,0); switch (voice_status) { case 0:lv_obj_set_style_img_opa(objects.volume_level0,255,0);break; case 1:lv_obj_set_style_img_opa(objects.volume_level1,255,0);break; case 2:lv_obj_set_style_img_opa(objects.volume_level2,255,0);break; default: break; } } /************************************************************************************************* * 函 数 名: Circuit_Count_Set * 入口参数: circuit_count_val - 线路回数 * 返 回 值: 无 * 函数功能: 设置线路回数 * 说 明: 无 *************************************************************************************************/ void Circuit_Count_Set(uint8_t circuit_count_val) { char buf[10] = {0}; sprintf(buf,"%d回",circuit_count_val); lv_label_set_text(objects.circuit_count,buf); } /************************************************************************************************* * 函 数 名: Voltage_Level_Set * 入口参数: voltage_level - 电压等级的数字,比如要显示1024Kv以下,就填1024 * 返 回 值: 无 * 函数功能: 设置电压等级 * 说 明: 无 *************************************************************************************************/ void Voltage_Level_Set(uint16_t voltage_level) { char buf[20] = {0}; sprintf(buf,"%dKv以下",voltage_level); lv_label_set_text(objects.x_kv,buf); } /************************************************************************************************* * 函 数 名: Safety_Sign_Set * 入口参数: status - true有电,false 无电 * 返 回 值: 无 * 函数功能: 安全标志设置 * 说 明: 无 *************************************************************************************************/ void Safety_Sign_Set(bool status) { if (status) { lv_obj_set_style_img_opa(objects.a_green,0,0); lv_obj_set_style_img_opa(objects.a_red,255,0); lv_obj_set_style_img_opa(objects.sd_red,255,0); lv_obj_set_style_img_opa(objects.sd_red_1,0,0); char buf[20] = {0}; sprintf(buf,"有电危险"); lv_label_set_text(objects.electricity_hazard,buf); } else { lv_obj_set_style_img_opa(objects.a_green,255,0); lv_obj_set_style_img_opa(objects.a_red,0,0); lv_obj_set_style_img_opa(objects.sd_red,0,0); lv_obj_set_style_img_opa(objects.sd_red_1,255,0); char buf[20] = {0}; sprintf(buf,"无电安全"); lv_label_set_text(objects.electricity_hazard,buf); } } /************************************************************************************************* * 函 数 名: LCD_BL_Set * 入口参数: status - true打开显示,false 关闭 * 返 回 值: 无 * 函数功能: 设置lcd开关 * 说 明: 无 *************************************************************************************************/ void LCD_BL_Set(bool status) { if (status) digitalWrite(LCD_BL_Pin,HIGH); // 打开背光 else digitalWrite(LCD_BL_Pin,LOW); // 关闭背光 } /************************************************************************************************* * 函 数 名: system_test * 入口参数: 无 * 返 回 值: 无 * 函数功能: 测试 * 说 明: 无 *************************************************************************************************/ void system_test() { delay(2000); Electromagnetic_Strength_Set(66); Battery_Set(true); Circuit_Count_Set(3); Voice_Set(0); Voltage_Level_Set(1024); Safety_Sign_Set(false); delay(2000); Electromagnetic_Strength_Set(22); Battery_Set(false); Circuit_Count_Set(1); Voice_Set(2); Voltage_Level_Set(888); Safety_Sign_Set(true); delay(2000); LCD_BL_Set(false); delay(2000); LCD_BL_Set(true); } void setup() { // put your setup code here, to run once: Serial.begin(115200); Serial.printf("\r\nsystem start\r\n"); setCpuFrequencyMhz(240); Serial.printf("The operating frequency of the system is %dMHz\r\n",getCpuFrequencyMhz()); // 初始化屏幕 tft.init(); tft.setRotation(1); // 设置屏幕的方向 0, 1, 2 or 3 tft.fillScreen(TFT_BLACK); // 填充黑色背景 lvgl_user_init(); mqqt_setup(); Init_timer(); } void loop() { system_test(); mqqt_loop(); } 运行此代码

LV_ATTRIBUTE_TIMER_HANDLER uint32_t lv_timer_handler(void) { TIMER_TRACE("begin"); /*Avoid concurrent running of the timer handler*/ static bool already_running = false; if(already_running) { TIMER_TRACE("already running, concurrent calls are not allow, returning"); return 1; } already_running = true; if(lv_timer_run == false) { already_running = false; /*Release mutex*/ return 1; } static uint32_t idle_period_start = 0; static uint32_t busy_time = 0; uint32_t handler_start = lv_tick_get(); if(handler_start == 0) { static uint32_t run_cnt = 0; run_cnt++; if(run_cnt > 100) { run_cnt = 0; LV_LOG_WARN("It seems lv_tick_inc() is not called."); } } /*Run all timer from the list*/ lv_timer_t * next; do { timer_deleted = false; timer_created = false; LV_GC_ROOT(_lv_timer_act) = _lv_ll_get_head(&LV_GC_ROOT(_lv_timer_ll)); while(LV_GC_ROOT(_lv_timer_act)) { /*The timer might be deleted if it runs only once ('repeat_count = 1') *So get next element until the current is surely valid*/ next = _lv_ll_get_next(&LV_GC_ROOT(_lv_timer_ll), LV_GC_ROOT(_lv_timer_act)); if(lv_timer_exec(LV_GC_ROOT(_lv_timer_act))) { /*If a timer was created or deleted then this or the next item might be corrupted*/ if(timer_created || timer_deleted) { TIMER_TRACE("Start from the first timer again because a timer was created or deleted"); break; } } LV_GC_ROOT(_lv_timer_act) = next; /*Load the next timer*/ } } while(LV_GC_ROOT(_lv_timer_act)); uint32_t time_till_next = LV_NO_TIMER_READY; next = _lv_ll_get_head(&LV_GC_ROOT(_lv_timer_ll)); while(next) { if(!next->paused) { uint32_t delay = lv_timer_time_remaining(next); if(delay < time_till_next) time_till_next = delay; } next = _lv_ll_get_next(&LV_GC_ROOT(_lv_timer_ll), next); /*Find the next timer*/ } busy_time += lv_tick_elaps(handler_start); uint32_t idle_period_time = lv_tick_elaps(idle_period_start); if(idle_period_time >= IDLE_MEAS_PERIOD) { idle_last = (busy_time * 100) / idle_period_time; /*Calculate the busy percentage*/ idle_last = idle_last > 100 ? 0 : 100 - idle_last; /*But we need idle time*/ busy_time = 0; idle_period_start = lv_tick_get(); } already_running = false; /*Release the mutex*/ TIMER_TRACE("finished (%d ms until the next timer call)", time_till_next); return time_till_next; }

LV_ATTRIBUTE_TIMER_HANDLER uint32_t lv_timer_handler(void) { //TIMER_TRACE("begin"); /*Avoid concurrent running of the timer handler*/ static bool already_running = false; if(already_running) { TIMER_TRACE("already running, concurrent calls are not allow, returning"); return 1; } already_running = true; if(lv_timer_run == false) { already_running = false; /*Release mutex*/ return 1; } static uint32_t idle_period_start = 0; static uint32_t busy_time = 0; uint32_t handler_start = lv_tick_get(); if(handler_start == 0) { static uint32_t run_cnt = 0; run_cnt++; if(run_cnt > 100) { run_cnt = 0; LV_LOG_WARN("It seems lv_tick_inc() is not called."); } } /*Run all timer from the list*/ lv_timer_t * next; do { timer_deleted = false; timer_created = false; LV_GC_ROOT(_lv_timer_act) = _lv_ll_get_head(&LV_GC_ROOT(_lv_timer_ll)); while(LV_GC_ROOT(_lv_timer_act)) { /*The timer might be deleted if it runs only once ('repeat_count = 1') *So get next element until the current is surely valid*/ next = _lv_ll_get_next(&LV_GC_ROOT(_lv_timer_ll), LV_GC_ROOT(_lv_timer_act)); if(lv_timer_exec(LV_GC_ROOT(_lv_timer_act))) { /*If a timer was created or deleted then this or the next item might be corrupted*/ if(timer_created || timer_deleted) { TIMER_TRACE("Start from the first timer again because a timer was created or deleted"); break; } } LV_GC_ROOT(_lv_timer_act) = next; /*Load the next timer*/ } } while(LV_GC_ROOT(_lv_timer_act)); uint32_t time_till_next = LV_NO_TIMER_READY; next = _lv_ll_get_head(&LV_GC_ROOT(_lv_timer_ll)); while(next) { if(!next->paused) { uint32_t delay = lv_timer_time_remaining(next); if(delay < time_till_next) time_till_next = delay; } next = _lv_ll_get_next(&LV_GC_ROOT(_lv_timer_ll), next); /*Find the next timer*/ } busy_time += lv_tick_elaps(handler_start); uint32_t idle_period_time = lv_tick_elaps(idle_period_start); if(idle_period_time >= IDLE_MEAS_PERIOD) { idle_last = (busy_time * 100) / idle_period_time; /*Calculate the busy percentage*/ idle_last = idle_last > 100 ? 0 : 100 - idle_last; /*But we need idle time*/ busy_time = 0; idle_period_start = lv_tick_get(); } already_running = false; /*Release the mutex*/ //TIMER_TRACE("finished (%d ms until the next timer call)", time_till_next); return time_till_next; }

最新推荐

recommend-type

CODESYS运动控制之MC_Power.docx

BOOL bRegulatorON; // MC_POWER功能块 MC_POWER(Enable := TRUE, bRegulatorON := bRegulatorON); ``` 在编程完成后,进行编译和下载。下载完毕,点击“启动”按钮,系统会显示轴的状态。此时,轴可能处于断电...
recommend-type

{团队建设}网络渠道部团队规划手册.pdf

{团队建设}网络渠道部团队规划手册.pdf
recommend-type

Wamp5: 一键配置ASP/PHP/HTML服务器工具

根据提供的文件信息,以下是关于标题、描述和文件列表中所涉及知识点的详细阐述。 ### 标题知识点 标题中提到的是"PHP集成版工具wamp5.rar",这里面包含了以下几个重要知识点: 1. **PHP**: PHP是一种广泛使用的开源服务器端脚本语言,主要用于网站开发。它可以嵌入到HTML中,从而让网页具有动态内容。PHP因其开源、跨平台、面向对象、安全性高等特点,成为最流行的网站开发语言之一。 2. **集成版工具**: 集成版工具通常指的是将多个功能组合在一起的软件包,目的是为了简化安装和配置流程。在PHP开发环境中,这样的集成工具通常包括了PHP解释器、Web服务器以及数据库管理系统等关键组件。 3. **Wamp5**: Wamp5是这类集成版工具的一种,它基于Windows操作系统。Wamp5的名称来源于它包含的主要组件的首字母缩写,即Windows、Apache、MySQL和PHP。这种工具允许开发者快速搭建本地Web开发环境,无需分别安装和配置各个组件。 4. **RAR压缩文件**: RAR是一种常见的文件压缩格式,它以较小的体积存储数据,便于传输和存储。RAR文件通常需要特定的解压缩软件进行解压缩操作。 ### 描述知识点 描述中提到了工具的一个重要功能:“可以自动配置asp/php/html等的服务器, 不用辛辛苦苦的为怎么配置服务器而烦恼”。这里面涵盖了以下知识点: 1. **自动配置**: 自动配置功能意味着该工具能够简化服务器的搭建过程,用户不需要手动进行繁琐的配置步骤,如修改配置文件、启动服务等。这是集成版工具的一项重要功能,极大地降低了初学者的技术门槛。 2. **ASP/PHP/HTML**: 这三种技术是Web开发中常用的组件。ASP (Active Server Pages) 是微软开发的服务器端脚本环境;HTML (HyperText Markup Language) 是用于创建网页的标准标记语言;PHP是服务器端脚本语言。在Wamp5这类集成环境中,可以很容易地对这些技术进行测试和开发,因为它们已经预配置在一起。 3. **服务器**: 在Web开发中,服务器是一个运行Web应用程序并响应客户端请求的软件或硬件系统。常见的服务器软件包括Apache、Nginx等。集成版工具提供了一个本地服务器环境,使得开发者可以在本地测试他们的应用程序。 ### 标签知识点 标签中仅出现了“PHP”一个关键词,这意味着该工具专注于与PHP相关的开发环境配置。 ### 压缩包子文件的文件名称列表知识点 1. **wamp.exe**: 这是Wamp5集成版工具的可执行文件,用户通过运行这个文件,即可启动Wamp5环境,开始进行PHP等相关开发。 2. **使用说明文档.txt**: 通常这样的文本文件包含了软件的安装指南、功能介绍、常见问题解答以及操作教程等内容。它是用户了解和使用软件的重要辅助资料,可以帮助用户快速上手。 ### 总结 Wamp5作为一个PHP集成版工具,为PHP开发者提供了一个便捷的本地开发环境。它通过自动化配置减少了开发者在搭建环境时的麻烦,整合了Web开发的关键组件,从而使得开发者能够专注于代码的编写,而不是环境配置。此外,它还提供了详尽的使用文档,帮助用户理解和运用这一工具,有效地提高了开发效率。对于希望快速入门PHP或需要一个简单易用的本地开发环境的开发者来说,Wamp5是一个非常好的选择。
recommend-type

【数据融合技术】:甘肃土壤类型空间分析中的专业性应用

# 摘要 数据融合技术作为一种集成多源数据信息的方法,在土壤类型空间分析中发挥着关键作用。本文介绍了数据融合技术的基本概念及其理论基础,阐述了数据预处理、同步整合及冲突解决等关键技术,并详细描述了甘肃土壤类型数据准备的流程,包括数据采集、质量评估、空间化处理及融合实践准备。通过具体案例分析,展示了数据融合在土壤类型空间分布分析、土壤质量评估及土壤保护规划中的应用。同时,文
recommend-type

sht20温湿度传感器使用什么将上拉电阻和滤波电容引出

<think>好的,我需要解决用户关于SHT20温湿度传感器的上拉电阻和滤波电容的引出方法问题。首先,我得回忆一下SHT20的基本特性和常见电路设计。SHT20是数字传感器,通常使用I2C接口,所以上拉电阻是必须的,用于确保数据线和时钟线的稳定。根据引用[2],SHT10是通过SCK和DATA线与单片机通信,而SHT30在引用[3]中使用I2C协议,需要上拉电阻。虽然用户问的是SHT20,但SHT系列通常设计类似,所以可以推断SHT20也需要类似的上拉电阻配置。通常I2C总线的上拉电阻值在4.7kΩ到10kΩ之间,但具体值可能取决于总线速度和电源电压。需要确认数据手册中的推荐值,但用户可能没有
recommend-type

Delphi仿速达财务软件导航条组件开发教程

Delphi作为一款历史悠久的集成开发环境(IDE),由Embarcadero Technologies公司开发,它使用Object Pascal语言,被广泛应用于Windows平台下的桌面应用程序开发。在Delphi中开发组件是一项核心技术,它允许开发者创建可复用的代码单元,提高开发效率和软件模块化水平。本文将详细介绍如何在Delphi环境下仿制速达财务软件中的导航条组件,这不仅涉及到组件的创建和使用,还会涉及界面设计和事件处理等技术点。 首先,需要了解Delphi组件的基本概念。在Delphi中,组件是一种特殊的对象,它们被放置在窗体(Form)上,可以响应用户操作并进行交互。组件可以是可视的,也可以是不可视的,可视组件在设计时就能在窗体上看到,如按钮、编辑框等;不可视组件则主要用于后台服务,如定时器、数据库连接等。组件的源码可以分为接口部分和实现部分,接口部分描述组件的属性和方法,实现部分包含方法的具体代码。 在开发仿速达财务软件的导航条组件时,我们需要关注以下几个方面的知识点: 1. 组件的继承体系 仿制组件首先需要确定继承体系。在Delphi中,大多数可视组件都继承自TControl或其子类,如TPanel、TButton等。导航条组件通常会继承自TPanel或者TWinControl,这取决于导航条是否需要支持子组件的放置。如果导航条只是单纯的一个显示区域,TPanel即可满足需求;如果导航条上有多个按钮或其他控件,可能需要继承自TWinControl以提供对子组件的支持。 2. 界面设计与绘制 组件的外观和交互是用户的第一印象。在Delphi中,可视组件的界面主要通过重写OnPaint事件来完成。Delphi提供了丰富的绘图工具,如Canvas对象,使用它可以绘制各种图形,如直线、矩形、椭圆等,并且可以对字体、颜色进行设置。对于导航条,可能需要绘制背景图案、分隔线条、选中状态的高亮等。 3. 事件处理 导航条组件需要响应用户的交互操作,例如鼠标点击事件。在Delphi中,可以通过重写组件的OnClick事件来响应用户的点击操作,进而实现导航条的导航功能。如果导航条上的项目较多,还可能需要考虑使用滚动条,让更多的导航项能够显示在窗体上。 4. 用户自定义属性和方法 为了使组件更加灵活和强大,开发者通常会为组件添加自定义的属性和方法。在导航条组件中,开发者可能会添加属性来定义按钮个数、按钮文本、按钮位置等;同时可能会添加方法来处理特定的事件,如自动调整按钮位置以适应不同的显示尺寸等。 5. 数据绑定和状态同步 在财务软件中,导航条往往需要与软件其他部分的状态进行同步。例如,用户当前所处的功能模块会影响导航条上相应项目的选中状态。这通常涉及到数据绑定技术,Delphi支持组件间的属性绑定,通过数据绑定可以轻松实现组件状态的同步。 6. 导航条组件的封装和发布 开发完毕后,组件需要被封装成独立的单元供其他项目使用。封装通常涉及将组件源码保存为pas文件,并在设计时能够在组件面板中找到。发布组件可能还需要编写相应的安装包和使用文档,方便其他开发者安装和使用。 7. Delphi IDE的支持 Delphi IDE提供了组件面板编辑器(Component Palette),允许开发者将开发好的组件添加到组件面板中。在组件面板编辑器中,可以自定义组件的图标和分类,使得组件在Delphi中的使用更为便捷。 通过以上的知识点梳理,可以看出Delphi仿速达导航条组件的开发涉及到的不仅仅是简单的代码编写,还涉及到用户界面设计、事件驱动编程、组件封装等多个方面。掌握这些知识点,对于一名Delphi开发者而言,是十分重要的。
recommend-type

【空间分布规律】:甘肃土壤类型与农业生产的关联性研究

# 摘要 本文对甘肃土壤类型及其在农业生产中的作用进行了系统性研究。首先概述了甘肃土壤类型的基础理论,并探讨了土壤类型与农业生产的理论联系。通过GIS技术分析,本文详细阐述了甘肃土壤的空间分布规律,并对其特征和影响因素进行了深入分析。此外,本文还研究了甘肃土壤类型对农业生产实际影响,包括不同区域土壤改良和作物种植案例,以及土壤养分、水分管理对作物生长周期和产量的具体影响。最后,提出了促进甘肃土壤与农业可持续发展的策略,包括土壤保护、退化防治对策以及土壤类型优化与农业创新的结合。本文旨在为
recommend-type

常见运放电路的基本结构和基本原理

<think>首先,用户的问题是关于运算放大器电路的基本结构和工作原理。我需要根据系统级指令来构建回答。系统级指令:-所有行内数学表达式必须使用$...$格式。-独立公式必须使用$$...$$格式并单独成段。-LaTeX语法正确。-使用中文回答。-生成相关问题。-回答中引用的段落末尾自然地添加引用标识,例如[^1]。-回答结构清晰,帮助用户逐步解决问题,尽量保证回答真实可靠。参考站内引用:-引用[1]:关于运算放大器基本电路用法,特别是反相放大器电路。-引用[2]:关于uA741运算放大器电路的基本原理,包括输入级、输出级等。用户的问题:"我想了解运放电路的基本结构和工作原理请问运算放大器电路
recommend-type

ASP.NET2.0初学者个人网站实例分享

标题:“ASP.NET 2.0个人网站”指向了一个网站开发项目,这个项目是使用ASP.NET 2.0框架构建的。ASP.NET 2.0是微软公司推出的一种用于Web开发的服务器端技术,它是.NET Framework的一部分。这个框架允许开发者构建动态网站、网络应用程序和网络服务。开发者可以使用C#或VB.NET等编程语言来编写应用程序。由于这被标签为“2.0”,我们可以假设这是一个较早版本的ASP.NET,相较于后来的版本,它可能没有那么先进的特性,但对于初学者来说,它提供了基础并且易于上手的工具和控件来学习Web开发。 描述:“个人练习所做,适合ASP.NET初学者参考啊,有兴趣的可以前来下载去看看,同时帮小弟我赚些积分”提供了关于该项目的背景信息。它是某个个人开发者或学习者为了实践和学习ASP.NET 2.0而创建的个人网站项目。这个项目被描述为适合初学者作为学习参考。开发者可能是为了积累积分或网络声誉,鼓励他人下载该项目。这样的描述说明了该项目可以被其他人获取,进行学习和参考,或许还能给予原作者一些社区积分或其他形式的回报。 标签:“2.0”表明这个项目专门针对ASP.NET的2.0版本,可能意味着它不是最新的项目,但是它可以帮助初学者理解早期ASP.NET版本的设计和开发模式。这个标签对于那些寻找具体版本教程或资料的人来说是有用的。 压缩包子文件的文件名称列表:“MySelf”表示在分享的压缩文件中,可能包含了与“ASP.NET 2.0个人网站”项目相关的所有文件。文件名“我的”是中文,可能是指创建者以“我”为中心构建了这个个人网站。虽然文件名本身没有提供太多的信息,但我们可以推测它包含的是网站源代码、相关资源文件、数据库文件(如果有的话)、配置文件和可能的文档说明等。 知识点总结: 1. ASP.NET 2.0是.NET Framework下的一个用于构建Web应用程序的服务器端框架。 2. 它支持使用C#和VB.NET等.NET支持的编程语言进行开发。 3. ASP.NET 2.0提供了一组丰富的控件,可帮助开发者快速构建Web表单、用户界面以及实现后台逻辑。 4. 它还提供了一种称作“Web站点”项目模板,使得初学者能够方便地开始Web开发项目。 5. ASP.NET 2.0是微软.NET历史上一个重要的里程碑,引入了许多创新特性,如成员资格和角色管理、主题和皮肤、网站导航和个性化设置等。 6. 在学习ASP.NET 2.0的过程中,初学者可以了解到如HTTP请求和响应、服务器控件、状态管理、数据绑定、缓存策略等基础概念。 7. 本项目可作为ASP.NET初学者的实践平台,帮助他们理解框架的基本结构和工作流程,从而为学习更高版本的ASP.NET打下坚实基础。 8. 个人网站项目的构建可以涵盖前端设计(HTML, CSS, JavaScript)和后端逻辑(C#或VB.NET)的综合应用。 9. 在学习过程中,初学者应该学会如何配置和使用IIS(Internet Information Services)来部署ASP.NET网站。 10. “赚取积分”可能指的是在某个在线社区、论坛或代码托管平台上,通过分享项目来获得一定的积分或奖励,这通常是用来衡量用户对社区贡献大小的一种方式。 综上所述,该“ASP.NET 2.0个人网站”项目不仅为初学者提供了一个实用的学习资源,同时体现了开发者对于开源共享精神的实践,对社区贡献出自己的力量。通过这样的实践,初学者能够更好地理解ASP.NET框架的运作,逐步建立起自己的Web开发技能。
recommend-type

【制图技术】:甘肃高质量土壤分布TIF图件的成图策略

# 摘要 本文针对甘肃土壤分布数据的TIF图件制作进行了系统研究。首先概述了甘肃土壤的分布情况,接着介绍了TIF图件的基础知识,包括其格式特点、空间数据表达以及质量控制方法。随后,文中构建了成图策略的理论框架,分析了土壤分布图的信息需求与数据处理流程,并探讨了成图原则与标准。在实践操作部分,详细阐述了制图软