如何查看网站图片尺寸都兰县建设局交通局网站
2026/2/8 17:37:37 网站建设 项目流程
如何查看网站图片尺寸,都兰县建设局交通局网站,seo优化推广是什么意思,网站建设中网站需求分析报告功能来源 | 嵌入式大杂烩 在嵌入式系统开发中#xff0c;日志系统是调试和问题定位的重要工具。本文介绍一个简易的嵌入式日志系统设计思路。 类似文章#xff1a; 简易嵌入式自定义协议设计思路#xff01; 简易嵌入式优先级消息队列设计思路#xff01; 1. 简易嵌入式日志系统…来源 | 嵌入式大杂烩在嵌入式系统开发中日志系统是调试和问题定位的重要工具。本文介绍一个简易的嵌入式日志系统设计思路。类似文章简易嵌入式自定义协议设计思路简易嵌入式优先级消息队列设计思路1. 简易嵌入式日志系统1.1 日志系统测试1.1.1 同步 vs 异步输出static void log_compare_task(void *param) { (void)param; constint lines_per_burst 50; constuint32_t gap_ms 6000; constuint32_t max_flush_wait_ms 8000; while (1) { // ---------- SYNC: 直接输出包含 I/O 时间 ---------- TickType_t t0 xTaskGetTickCount(); for (int i 0; i lines_per_burst; i) { log_write(g_logger_sync, LOG_LEVEL_INFO, __FILE__, __LINE__, SYNC #%d: payloadABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789, i); } TickType_t t1 xTaskGetTickCount(); constuint32_t sync_ms (uint32_t)((t1 - t0) * portTICK_PERIOD_MS); // ---------- ASYNC: 先入队再等待后台 flush 刷空 ---------- while (log_buffer_available(g_logger) 0) { vTaskDelay(pdMS_TO_TICKS(1)); } TickType_t enq0 xTaskGetTickCount(); for (int i 0; i lines_per_burst; i) { log_write(g_logger, LOG_LEVEL_INFO, __FILE__, __LINE__, ASYNC #%d: payloadABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789, i); } TickType_t enq1 xTaskGetTickCount(); constuint32_t enq_ms (uint32_t)((enq1 - enq0) * portTICK_PERIOD_MS); TickType_t flush0 xTaskGetTickCount(); const TickType_t timeout_ticks pdMS_TO_TICKS(max_flush_wait_ms); while (log_buffer_available(g_logger) 0) { if ((xTaskGetTickCount() - flush0) timeout_ticks) { break; } vTaskDelay(pdMS_TO_TICKS(1)); } TickType_t flush1 xTaskGetTickCount(); constuint32_t flush_ms (uint32_t)((flush1 - flush0) * portTICK_PERIOD_MS); log_write(g_logger_sync, LOG_LEVEL_WARN, __FILE__, __LINE__, PERF %d lines: SYNC%lu ms | ASYNC enqueue%lu ms, flush%lu ms, lines_per_burst, (unsignedlong)sync_ms, (unsignedlong)enq_ms, (unsignedlong)flush_ms); vTaskDelay(pdMS_TO_TICKS(gap_ms)); } }性能对比模式50条日志耗时说明同步模式~472ms每条日志都立即输出到串口异步模式~17ms写入内存缓冲区刷新异步是把整行字符串写进环形缓冲当突发产生速度 后台消费速度缓冲满了就覆盖最旧字节。优化方向把 LOG_BUFFER_SIZE 调大、让输出更快。1.1.2 不同日志级别void test_basic_levels(void) { LOG_ERROR(This is an ERROR message); LOG_WARN(This is a WARN message); LOG_INFO(This is an INFO message); LOG_DEBUG(This is a DEBUG message); LOG_VERBOSE(This is a VERBOSE message); }1.1.3 格式化输出void test_formatted_output(void) { int temp 25; float voltage 3.3f; const char *status running; LOG_INFO(Temperature: %d°C, temp); LOG_INFO(Voltage: %.2fV, voltage); LOG_INFO(System status: %s, status); LOG_DEBUG(Hex data: 0x%02X 0x%04X, 0xAB, 0x1234); }1.1.4 级别过滤log_config_t config; log_get_freertos_config(config); config.level LOG_LEVEL_WARN; log_init(g_logger, config); void test_level_filter(void) { LOG_ERROR(ERROR); LOG_WARN(WARN); LOG_INFO(INFO); // 被过滤 LOG_DEBUG(DEBUG); // 被过滤 }级别过滤的性能优势被过滤的日志在格式化之前就被拒绝。1.2 本文最小实现设计思路只做最小闭环能“产生日志 → 缓存 → 输出”即可静态资源优先只使用静态/编译期分配的缓冲区与控制结构避免 malloc/free基于FreeRTOS不依赖复杂特性先不做平台抽象先基于FreeRTOS。优先用临界区/轻量锁保证一致性需要异步时再引入一个后台任务写日志尽量短、可失败记录路径以“尽快返回”为目标缓冲满时允许丢弃或覆盖策略可配置但实现保持简单异步为可选项默认直接调用平台输出当输出可能阻塞时再启用环形缓冲 刷新任务接口最小化只抽象 2 个平台钩子输出函数、时间戳函数其余参数提供合理默认值1.3 核心功能需求根据嵌入式系统的特点日志系统需要具备以下核心功能日志级别分了5个级别ERROR/WARN/INFO/DEBUG/VERBOSE级别低的会自动过滤掉。格式化输出支持类似printf的格式化用起来很方便。时间戳每条日志前面会加时间戳方便看日志时序。文件名和行号这个很有用出问题的时候一眼就能看到是哪行代码打的日志。同步/异步模式同步就是打印完才返回异步是先写缓冲区然后空闲时再输出。环形缓冲区这个是异步模式的核心。用了一个512字节的环形缓冲区可以改大小满了会自动覆盖旧数据。简单粗暴但很实用。后台刷新任务自动创建 FreeRTOS 任务后台定期刷新日志缓冲区不需要手动调用 flush 函数。平台适配通过函数指针实现接口抽象。只需要实现两个函数一个输出函数比如串口发送一个时间戳函数获取系统时钟。同步模式LOG_INFO一调用串口就开始吭哧吭哧发数据发完才返回。优点是实时性强缺点是慢所以不能在中断里这么干。异步模式LOG_INFO调用后只是把数据写到一个缓冲区然后马上返回。真正的输出是在空闲时比如主循环里或任务里统一刷新。这样中断里打日志就不会阻塞了。1.4 日志配置项// 环形缓冲区大小根据RAM大小调整 #ifndef LOG_BUFFER_SIZE #define LOG_BUFFER_SIZE 512 #endif // 单条日志最大长度 #ifndef LOG_MAX_LINE_SIZE #define LOG_MAX_LINE_SIZE 256 #endif // 刷新任务配置 #ifndef LOG_FLUSH_INTERVAL_MS #define LOG_FLUSH_INTERVAL_MS 50 // 刷新间隔 #endif #ifndef LOG_FLUSH_TASK_STACK_SIZE #define LOG_FLUSH_TASK_STACK_SIZE 512 // 刷新任务栈大小 #endif #ifndef LOG_FLUSH_TASK_PRIORITY #define LOG_FLUSH_TASK_PRIORITY 1 // 刷新任务优先级 #endif // 功能开关 #ifndef LOG_ENABLE_TIMESTAMP #define LOG_ENABLE_TIMESTAMP 1 // 启用时间戳 #endif #ifndef LOG_ENABLE_FILE_LINE #define LOG_ENABLE_FILE_LINE 1 // 启用文件名和行号 #endif #ifndef LOG_ENABLE_COLOR #define LOG_ENABLE_COLOR 1 // 启用颜色(终端) #endif #ifndef LOG_ENABLE_THREAD_SAFE #define LOG_ENABLE_THREAD_SAFE 0 // 线程安全(需要提供锁函数) #endif #ifndef LOG_ENABLE_ASYNC #define LOG_ENABLE_ASYNC 1 // 异步模式 #endif #ifndef LOG_ENABLE_FLUSH_TASK #define LOG_ENABLE_FLUSH_TASK 1 // 启用自动刷新任务 #endif1.5 数据结构设计1.5.1 日志级别定义typedef enum { LOG_LEVEL_NONE 0, // 关闭日志 LOG_LEVEL_ERROR, // 错误 LOG_LEVEL_WARN, // 警告 LOG_LEVEL_INFO, // 信息 LOG_LEVEL_DEBUG, // 调试 LOG_LEVEL_VERBOSE, // 详细 } log_level_t;5个级别一般够用了ERROR致命错误比如硬件挂了、通信失败WARN有问题但还能跑比如温度过高、缓冲区快满了INFO关键节点比如系统启动、连接成功DEBUG调试时用的比如函数调用、状态变化VERBOSE所有细节比如数据包内容、寄存器值平时不开发布版本一般设置成WARN级别只打印错误和警告节省资源。1.5.2 配置结构typedef struct { log_level_t level; // 日志级别 log_backend_t backend; // 后端类型 log_output_fn output_fn; // 输出函数 log_timestamp_fn timestamp_fn; // 时间戳函数 log_lock_fn lock_fn; // 加锁函数 log_unlock_fn unlock_fn; // 解锁函数 bool enable_color; // 是否启用颜色 bool enable_async; // 是否启用异步 #if LOG_ENABLE_FLUSH_TASK // 平台相关的任务操作用于启动后台刷新任务 log_task_create_fn task_create_fn; // 创建任务函数 log_task_delete_fn task_delete_fn; // 删除任务函数 log_delay_ms_fn delay_ms_fn; // 延时函数 #endif } log_config_t;初始化时填这个结构体。重点是output_fn和timestamp_fn这两个回调必须实现。其他的看需求不用就填NULL。1.5.3 日志对象typedef struct { log_config_t config; // 配置 log_buffer_t buffer; // 缓冲区 bool initialized; // 初始化标志 #if LOG_ENABLE_FLUSH_TASK void *flush_task_handle; // 刷新任务句柄平台相关 bool flush_task_running; // 刷新任务运行状态 #endif } logger_t;1.5.4 环形缓冲区异步模式的核心是环形缓冲区Ring Buffertypedef struct { char buffer[LOG_BUFFER_SIZE]; // 环形缓冲区 uint16_t write_pos; // 写位置 uint16_t read_pos; // 读位置 uint16_t count; // 当前数据量 } log_buffer_t; // 环形缓冲区写入 static size_t ring_buffer_write(log_buffer_t *buf, const char *data, size_t len) { if (!buf || !data || len 0) return0; size_t written 0; for (size_t i 0; i len; i) { // 缓冲区满覆盖最旧的数据 if (buf-count LOG_BUFFER_SIZE) { buf-read_pos (buf-read_pos 1) % LOG_BUFFER_SIZE; buf-count--; } buf-buffer[buf-write_pos] data[i]; buf-write_pos (buf-write_pos 1) % LOG_BUFFER_SIZE; buf-count; written; } return written; } // 环形缓冲区读取 static size_t ring_buffer_read(log_buffer_t *buf, char *data, size_t len) { if (!buf || !data || len 0) return0; size_t read 0; while (read len buf-count 0) { data[read] buf-buffer[buf-read_pos]; buf-read_pos (buf-read_pos 1) % LOG_BUFFER_SIZE; buf-count--; } return read; }为什么用环形缓冲区首先固定大小默认512字节可以改编译时就分配好了不用担心内存碎片。其次写满了会自动覆盖旧数据。有人可能觉得这样会丢日志但实际使用中如果缓冲区一直满说明你要么刷新不够频繁要么日志打太多了。与其让程序卡死不如丢掉旧的日志。最后读写操作都是O(1)很快。就是简单的指针移动不需要拷贝数据。1.6 API接口设计// 初始化日志系统 bool log_init(logger_t *logger, const log_config_t *config); // 反初始化日志系统 void log_deinit(logger_t *logger); // 设置日志级别 void log_set_level(logger_t *logger, log_level_t level); // 获取日志级别 log_level_t log_get_level(const logger_t *logger); // 日志输出核心函数 void log_write(logger_t *logger, log_level_t level, const char *file, int line, const char *fmt, ...); // 刷新缓冲区强制输出 void log_flush(logger_t *logger); // 从缓冲区读取数据 size_t log_read_buffer(logger_t *logger, char *buf, size_t size); // 获取缓冲区可用数据量 size_t log_buffer_available(const logger_t *logger); // 获取日志级别字符串 const char* log_level_str(log_level_t level); // 获取日志级别颜色 const char* log_level_color(log_level_t level); #if LOG_ENABLE_FLUSH_TASK // 启动后台刷新任务 bool log_start_flush_task(logger_t *logger); // 停止后台刷新任务 void log_stop_flush_task(logger_t *logger); #endif1.6.1 宏定义#if LOG_ENABLE_FILE_LINE #define LOG_ERROR(fmt, ...) log_write(g_logger, LOG_LEVEL_ERROR, __FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOG_WARN(fmt, ...) log_write(g_logger, LOG_LEVEL_WARN, __FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOG_INFO(fmt, ...) log_write(g_logger, LOG_LEVEL_INFO, __FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOG_DEBUG(fmt, ...) log_write(g_logger, LOG_LEVEL_DEBUG, __FILE__, __LINE__, fmt, ##__VA_ARGS__) #define LOG_VERBOSE(fmt, ...) log_write(g_logger, LOG_LEVEL_VERBOSE, __FILE__, __LINE__, fmt, ##__VA_ARGS__) #else #define LOG_ERROR(fmt, ...) log_write(g_logger, LOG_LEVEL_ERROR, NULL, 0, fmt, ##__VA_ARGS__) #define LOG_WARN(fmt, ...) log_write(g_logger, LOG_LEVEL_WARN, NULL, 0, fmt, ##__VA_ARGS__) #define LOG_INFO(fmt, ...) log_write(g_logger, LOG_LEVEL_INFO, NULL, 0, fmt, ##__VA_ARGS__) #define LOG_DEBUG(fmt, ...) log_write(g_logger, LOG_LEVEL_DEBUG, NULL, 0, fmt, ##__VA_ARGS__) #define LOG_VERBOSE(fmt, ...) log_write(g_logger, LOG_LEVEL_VERBOSE, NULL, 0, fmt, ##__VA_ARGS__) #endif1.6.2 日志写void log_write(logger_t *logger, log_level_t level, const char *file, int line, const char *fmt, ...) { if (!logger || !logger-initialized) return; // 级别过滤 if (level logger-config.level) return; // 加锁多任务环境 if (LOG_ENABLE_THREAD_SAFE logger-config.lock_fn) logger-config.lock_fn(); char log_line[LOG_MAX_LINE_SIZE] {0}; int offset 0; // 时间戳 if (LOG_ENABLE_TIMESTAMP logger-config.timestamp_fn) { uint32_t ts logger-config.timestamp_fn(); offset snprintf(log_line offset, LOG_MAX_LINE_SIZE - offset, [%u.%03u] , ts / 1000, ts % 1000); } // 日志颜色、级别 if (logger-config.enable_color) { offset snprintf(log_line offset, LOG_MAX_LINE_SIZE - offset, %s[%s]%s , log_level_color(level), log_level_str(level), LOG_COLOR_RESET); } else { offset snprintf(log_line offset, LOG_MAX_LINE_SIZE - offset, [%s] , log_level_str(level)); } // 文件名和行号 if (LOG_ENABLE_FILE_LINE file) { offset snprintf(log_line offset, LOG_MAX_LINE_SIZE - offset, [%s:%d] , get_filename(file), line); } // 用户消息 va_list args; va_start(args, fmt); offset vsnprintf(log_line offset, LOG_MAX_LINE_SIZE - offset, fmt, args); va_end(args); // 换行符 if (offset LOG_MAX_LINE_SIZE - 3) { log_line[offset] \r; log_line[offset] \n; log_line[offset] \0; } // 输出逻辑 if (logger-config.enable_async) { // 异步模式写入缓冲区 ring_buffer_write(logger-buffer, log_line, offset); } else { // 同步模式直接输出 if (logger-config.output_fn) logger-config.output_fn(log_line, offset); } // 解锁 if (LOG_ENABLE_THREAD_SAFE logger-config.unlock_fn) logger-config.unlock_fn(); }1.6.3 初始化首先要初始化填个配置结构体就行bool log_init(logger_t *logger, const log_config_t *config);1.6.4 后台刷新任务异步模式下需要定期刷新缓冲区。日志系统提供了自动刷新任务static void log_flush_task_entry(void *param) { logger_t *logger (logger_t *)param; while (logger-flush_task_running) { // 如果有数据就刷新 if (log_buffer_available(logger) 0) { log_flush(logger); } if (logger-config.delay_ms_fn) { logger-config.delay_ms_fn(LOG_FLUSH_INTERVAL_MS); } } if (logger-config.task_delete_fn) { logger-config.task_delete_fn(NULL); } } bool log_start_flush_task(logger_t *logger) { if (!logger || !logger-initialized) returnfalse; // 检查平台回调函数是否提供 if (!logger-config.task_create_fn || !logger-config.delay_ms_fn) { returnfalse; } // 检查是否已经启动 if (logger-flush_task_running) returntrue; logger-flush_task_running true; // 使用平台提供的任务创建函数 logger-flush_task_handle logger-config.task_create_fn( log_flush_task_entry, logger, LOG_FLUSH_TASK_STACK_SIZE, LOG_FLUSH_TASK_PRIORITY ); if (logger-flush_task_handle NULL) { logger-flush_task_running false; returnfalse; } returntrue; }后台任务自动检查并刷新缓冲区间隔可通过LOG_FLUSH_INTERVAL_MS宏配置。这个任务优先级较低不会影响业务逻辑。1.7 FreeRTOS 平台适配日志系统针对 FreeRTOS 进行实现需要适配两个平台相关的函数1.7.1 输出函数typedef void (*log_output_fn)(const char *data, size_t len);通常实现为串口发送可以用阻塞或 DMA 方式void log_output_uart_freertos(const char *data, size_t len) { if (data NULL || len 0) return; if (uart1_tx_done NULL) { // 未初始化时退化为阻塞发送 HAL_UART_Transmit(huart1, (uint8_t*)data, (uint16_t)len, 0xFFFF); return; } while (len 0) { size_t chunk (len sizeof(uart1_tx_buf)) ? sizeof(uart1_tx_buf) : len; // 等待上一次 DMA 完成 xSemaphoreTake(uart1_tx_done, portMAX_DELAY); // 拷贝到静态缓冲保证 DMA 期间数据稳定 memcpy(uart1_tx_buf, data, chunk); // 启动 DMA if (HAL_UART_Transmit_DMA(huart1, uart1_tx_buf, (uint16_t)chunk) ! HAL_OK) { xSemaphoreGive(uart1_tx_done); break; } data chunk; len - chunk; // 发送完成由 HAL_UART_TxCpltCallback() 释放 uart1_tx_done } } void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if (huart huart1 uart1_tx_done ! NULL) { BaseType_t xHigherPriorityTaskWoken pdFALSE; (void)xSemaphoreGiveFromISR(uart1_tx_done, xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } }1.7.2 时间戳函数typedef uint32_t (*log_timestamp_fn)(void);使用 FreeRTOS 系统时钟实现uint32_t log_timestamp_rtos(void) { return xTaskGetTickCount() * portTICK_PERIOD_MS; }时间戳精度取决于configTICK_RATE_HZ。1.7.3 线程安全多任务环境下需要提供互斥锁保护typedef void (*log_lock_fn)(void); typedef void (*log_unlock_fn)(void);使用 FreeRTOS 信号量实现static SemaphoreHandle_t log_mutex NULL; void log_lock_freertos(void) { if (log_mutex ! NULL) { xSemaphoreTake(log_mutex, portMAX_DELAY); } } void log_unlock_freertos(void) { if (log_mutex ! NULL) { xSemaphoreGive(log_mutex); } }1.7.4 日志任务管理void* log_task_create_freertos(void (*task_func)(void*), void *param, uint32_t stack_size, uint32_t priority) { TaskHandle_t task_handle NULL; BaseType_t ret xTaskCreate( task_func, log_flush, stack_size / sizeof(StackType_t), param, priority, task_handle ); return (ret pdPASS) ? task_handle : NULL; } void log_task_delete_freertos(void *task_handle) { vTaskDelete((TaskHandle_t)task_handle); } void log_delay_ms_freertos(uint32_t ms) { vTaskDelay(pdMS_TO_TICKS(ms)); }1.7.5 获取 FreeRTOS 平台的日志配置void log_get_freertos_config(log_config_t *config) { if (config NULL) return; // 填充默认配置 config-level LOG_LEVEL_INFO; config-backend LOG_BACKEND_UART; config-output_fn log_output_uart_freertos; config-timestamp_fn log_timestamp_freertos; config-lock_fn log_lock_freertos; config-unlock_fn log_unlock_freertos; config-enable_color false; config-enable_async true; #if LOG_ENABLE_FLUSH_TASK config-task_create_fn log_task_create_freertos; config-task_delete_fn log_task_delete_freertos; config-delay_ms_fn log_delay_ms_freertos; #endif }2. 局限性2.1 缓冲区容量限制固定 512B 环形缓冲写满后会覆盖旧数据高频时容易丢关键日志。常见做法内存够就直接加大缓冲用双缓冲/多缓冲降低覆盖概率覆盖策略做成可配置要么丢新日志保历史要么溢出回调做告警/计数2.2 时间戳精度时间戳精度受平台/系统 tick 影响密集日志可能出现“同一时间戳”。需要更细粒度时序时可用硬件计数器/高精度定时器如 DWT 周期计数器。2.3 Flash存储支持当前不支持 Flash 持久化掉电日志会丢3. 总结本日志系统偏“最小可用”仅作为学习使用。实际在复杂/高频场景下还是需要使用成熟的日志库。如果要用于更复杂场景通常按下面方向扩展平台抽象延时/锁/任务接口适配裸机、RT-Thread、嵌入式 Linux 等存储Flash 环形持久化、文件系统落盘、远程集中存储等传输TCP/UDP、MQTT等能力运行时动态日志级别、过滤/统计等如果觉得文章有帮助麻烦帮忙转发谢谢猜你喜欢FreeRTOS 和 RT-Thread代码风格对比分享一个嵌入式开发的交互式工具CherrySH单片机可以用 Python 开发吗

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询