编程技术分享

分享编程知识,探讨技术创新

0%

简介:采用C#上位机+串口通信+CP2102USB转串口,TFT显示的桌面小型电脑性能显示

好的,作为一名高级嵌入式软件开发工程师,我将根据您提供的需求和图片,详细阐述最适合此嵌入式桌面小型电脑性能显示系统的代码设计架构,并提供具体的C代码实现。这个项目旨在构建一个可靠、高效、可扩展的嵌入式平台,用于实时显示桌面电脑的性能指标,并通过C#上位机进行数据采集和传输。
关注微信公众号,提前获取相关推文

项目目标与需求分析

  1. 系统功能:

    • 性能数据采集: 通过C#上位机从桌面电脑获取CPU温度、CPU使用率、内存(RAM)使用率、显存(GRAM)使用率等性能数据。
    • 串口通信: 上位机通过CP2102 USB转串口模块,将采集到的性能数据以特定的协议格式发送给嵌入式系统。
    • 数据解析与处理: 嵌入式系统接收串口数据,解析协议,提取性能指标数据。
    • TFT显示: 将解析出的性能指标数据,以直观的图形化界面在TFT屏幕上实时显示,包括数值、环形仪表盘、进度条等元素。
    • 系统维护与升级: 预留系统升级接口,方便后续功能扩展和固件升级。
  2. 硬件平台:

    • 微控制器 (MCU): 选择具有足够处理能力和外设资源的MCU,例如STM32系列、ESP32等。这里我们假设使用STM32F4系列,因为它在性能、资源和生态上都非常成熟,适合此类应用。
    • TFT LCD显示屏: 选择合适的TFT LCD模块,需要考虑分辨率、尺寸、接口类型(SPI、并行等)以及驱动IC。
    • CP2102 USB转串口模块: 用于上位机与嵌入式系统之间的串口通信。
    • 温度传感器 (可选): 如果需要硬件温度传感器(图片中温度值可能来自传感器或上位机软件),可以选择DS18B20、LM75等。在本例中,我们假设温度数据由上位机采集并发送。
  3. 软件平台:

    • 嵌入式端: C语言开发,使用RTOS (可选,但推荐使用,例如FreeRTOS) 或裸机编程。为了代码结构清晰和系统可维护性,我们选择使用FreeRTOS。
    • 上位机: C#语言开发,利用.NET Framework或.NET平台提供的串口通信和系统性能监控API。
  4. 关键技术:

    • 模块化设计: 采用模块化设计方法,将系统划分为HAL层、驱动层、通信层、数据处理层、GUI显示层和应用层,提高代码可读性、可维护性和可移植性。
    • 分层架构: 构建清晰的分层架构,降低层与层之间的耦合度,方便代码复用和功能扩展。
    • 异步通信: 采用串口异步通信方式,提高数据接收效率,避免阻塞主程序。
    • 数据解析与协议: 设计高效可靠的串口通信协议,确保数据传输的准确性和完整性。
    • GUI库: 选择合适的GUI库(例如LittlevGL、emWin等,这里为了简化代码,我们选择手动编写基础GUI函数),实现图形化界面显示。
    • 性能优化: 考虑嵌入式系统的资源限制,进行代码优化,提高系统运行效率,降低功耗。
    • 错误处理与容错: 在各个层面加入错误处理机制,提高系统的鲁棒性和可靠性。

代码设计架构

我们采用经典的分层架构,将嵌入式系统软件划分为以下几个层次:

  1. 硬件抽象层 (HAL - Hardware Abstraction Layer):

    • 目的:抽象底层硬件细节,向上层提供统一的硬件访问接口,提高代码的可移植性。
    • 模块:
      • hal_gpio.h/c: GPIO (通用输入输出) 接口
      • hal_uart.h/c: UART (通用异步收发器) 接口
      • hal_spi.h/c: SPI (串行外设接口) 接口 (如果TFT使用SPI接口)
      • hal_tft_lcd.h/c: TFT LCD 接口
      • hal_timer.h/c: 定时器接口 (用于系统Tick和延时)
      • hal_delay.h/c: 延时函数接口
  2. 驱动层 (Drivers):

    • 目的:实现HAL层定义的硬件接口的具体驱动代码,与具体的硬件设备交互。
    • 模块:
      • uart_driver.c: UART 驱动实现 (基于HAL层 UART 接口)
      • tft_lcd_driver.c: TFT LCD 驱动实现 (基于HAL层 TFT LCD 接口,需要根据具体的TFT LCD驱动IC编写)
      • timer_driver.c: 定时器驱动实现 (基于HAL层 Timer 接口)
  3. 通信层 (Communication):

    • 目的:处理串口通信,实现数据接收、发送、协议解析和数据打包。
    • 模块:
      • communication.h/c: 串口通信模块,包括初始化、数据接收、数据发送、协议解析、数据打包等功能。
      • data_protocol.h: 定义串口通信协议,例如数据帧格式、数据类型、命令字等。
  4. 数据处理层 (Data Processing):

    • 目的:对接收到的性能数据进行处理,例如数据转换、单位转换、数据校验、数据存储等。
    • 模块:
      • data_processor.h/c: 数据处理模块,负责解析性能数据,并将其转换为GUI显示所需的数据格式。
  5. GUI显示层 (GUI - Graphical User Interface):

    • 目的:实现图形用户界面,负责在TFT LCD上绘制各种UI元素,例如文本、数字、仪表盘、进度条等,并显示性能数据。
    • 模块:
      • gui.h/c: GUI 模块,包含各种GUI元素的绘制函数,例如文本绘制、圆形绘制、矩形绘制、进度条绘制、仪表盘绘制等。
      • fonts.h/c: 字体库,定义字体数据,用于文本显示。
      • images.h/c: 图片资源库 (如果需要显示图片)。
  6. 应用层 (Application):

    • 目的:系统的核心逻辑层,负责初始化各个模块,协调各个模块的工作,实现系统的主功能。
    • 模块:
      • app_performance_monitor.c: 性能监控应用模块,负责初始化系统、启动任务、接收数据、更新GUI显示等。
      • main.c: 主函数,系统入口,负责系统初始化、RTOS启动等。
  7. RTOS (Real-Time Operating System) (可选,此处选择FreeRTOS):

    • 目的:提供多任务管理、任务调度、同步与互斥机制,提高系统的实时性和并发性。
    • 模块:FreeRTOS kernel 和相关的配置文件。

C代码实现 (部分关键代码,完整代码超过3000行)

为了演示完整的系统架构和关键功能,以下代码将包含各个模块的头文件和部分关键C代码实现。请注意,由于篇幅限制,以下代码仅为示例,可能需要根据实际硬件平台和TFT LCD模块进行调整和完善。

(1) HAL层 (hal/)

hal_gpio.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

typedef enum {
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT,
GPIO_MODE_AF // Alternate Function
} GPIO_ModeTypeDef;

typedef enum {
GPIO_SPEED_LOW,
GPIO_SPEED_MEDIUM,
GPIO_SPEED_HIGH,
GPIO_SPEED_VERY_HIGH
} GPIO_SpeedTypeDef;

typedef enum {
GPIO_PULL_NONE,
GPIO_PULLUP,
GPIO_PULLDOWN
} GPIO_PullTypeDef;

typedef struct {
uint32_t Pin; // GPIO Pin number
GPIO_ModeTypeDef Mode; // GPIO Mode
GPIO_SpeedTypeDef Speed; // GPIO Speed
GPIO_PullTypeDef Pull; // GPIO Pull-up/Pull-down
} GPIO_InitTypeDef;

void HAL_GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_Init);
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint32_t GPIO_Pin, uint8_t PinState);
uint8_t HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint32_t GPIO_Pin);

// 假设 GPIO_TypeDef 和 GPIOx 的定义已经在 STM32 的头文件中定义,例如 stm32f4xx.h

#endif // HAL_GPIO_H

hal_uart.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef HAL_UART_H
#define HAL_UART_H

typedef struct {
uint32_t BaudRate; // 波特率
uint32_t WordLength; // 数据位长度
uint32_t StopBits; // 停止位
uint32_t Parity; // 校验位
uint32_t HardwareFlowControl; // 硬件流控制
uint32_t Mode; // UART 模式 (Tx, Rx, TxRx)
} UART_InitTypeDef;

void HAL_UART_Init(UART_HandleTypeDef *huart, UART_InitTypeDef *Uart_Init);
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); // 中断接收

// 假设 UART_HandleTypeDef 和相关的宏定义已经在 STM32 的头文件中定义,例如 stm32f4xx_hal_uart.h

#endif // HAL_UART_H

(2) 驱动层 (drivers/)

uart_driver.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include "hal_uart.h"
#include "stm32f4xx_hal.h" // 包含 STM32 HAL 库头文件,根据实际MCU型号修改

UART_HandleTypeDef huart2; // 假设使用 UART2

void UART_Driver_Init(uint32_t baudrate) {
huart2.Instance = USART2; // 根据实际使用的 UART 外设修改
huart2.Init.BaudRate = baudrate;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&huart2, &huart2.Init);

// 使能 UART2 中断 (如果使用中断接收)
HAL_NVIC_SetPriority(USART2_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
}

HAL_StatusTypeDef UART_Driver_Transmit(uint8_t *pData, uint16_t Size, uint32_t Timeout) {
return HAL_UART_Transmit(&huart2, pData, Size, Timeout);
}

HAL_StatusTypeDef UART_Driver_Receive(uint8_t *pData, uint16_t Size, uint32_t Timeout) {
return HAL_UART_Receive(&huart2, pData, Size, Timeout);
}

HAL_StatusTypeDef UART_Driver_Receive_IT(uint8_t *pData, uint16_t Size) {
return HAL_UART_Receive_IT(&huart2, pData, Size);
}

// UART 中断处理函数 (如果使用中断接收)
void USART2_IRQHandler(void) {
HAL_UART_IRQHandler(&huart2);
}

// UART 中断回调函数 (在 HAL 库中配置)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
// 在这里处理接收到的数据,例如将数据放入接收缓冲区
// ...
}
}

(3) 通信层 (communication/)

communication.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#ifndef COMMUNICATION_H
#define COMMUNICATION_H

#include "stdint.h"
#include "stdbool.h"
#include "data_protocol.h"

#define COM_RX_BUFFER_SIZE 256
#define COM_TX_BUFFER_SIZE 256

typedef struct {
uint8_t rx_buffer[COM_RX_BUFFER_SIZE];
uint16_t rx_head;
uint16_t rx_tail;
uint8_t tx_buffer[COM_TX_BUFFER_SIZE];
uint16_t tx_head;
uint16_t tx_tail;
bool data_received_flag; // 数据接收完成标志
PerformanceData_t received_data; // 解析后的性能数据
} Comm_HandleTypeDef;

extern Comm_HandleTypeDef comm_handle;

void Comm_Init(uint32_t baudrate);
void Comm_ProcessData(void); // 处理接收缓冲区中的数据,解析协议
void Comm_SendData(PerformanceData_t *data); // 发送性能数据 (如果需要双向通信)
bool Comm_IsDataReceived(void); // 检查是否接收到完整的数据帧
PerformanceData_t Comm_GetReceivedData(void); // 获取接收到的性能数据

#endif // COMMUNICATION_H

communication.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include "communication.h"
#include "uart_driver.h"
#include "string.h"
#include "stdlib.h" // for atoi, atof

Comm_HandleTypeDef comm_handle;

void Comm_Init(uint32_t baudrate) {
UART_Driver_Init(baudrate);
comm_handle.rx_head = 0;
comm_handle.rx_tail = 0;
comm_handle.tx_head = 0;
comm_handle.tx_tail = 0;
comm_handle.data_received_flag = false;

// 启动 UART 接收中断
UART_Driver_Receive_IT(comm_handle.rx_buffer + comm_handle.rx_head, 1); // 每次接收一个字节
}

// UART 中断回调函数 (在 HAL 库中配置,这里示例代码直接放在 communication.c 中)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
// 接收到一个字节
comm_handle.rx_head++;
if (comm_handle.rx_head >= COM_RX_BUFFER_SIZE) {
comm_handle.rx_head = 0; // 循环缓冲区
}
UART_Driver_Receive_IT(comm_handle.rx_buffer + comm_handle.rx_head, 1); // 继续接收下一个字节

// 简单协议示例:JSON 格式字符串,以换行符 '\n' 结尾
if (comm_handle.rx_buffer[(comm_handle.rx_head - 1 + COM_RX_BUFFER_SIZE) % COM_RX_BUFFER_SIZE] == '\n') {
comm_handle.data_received_flag = true; // 接收到完整的数据帧
}
}
}

void Comm_ProcessData(void) {
if (comm_handle.data_received_flag) {
comm_handle.data_received_flag = false;
uint16_t data_len = 0;
uint8_t *data_start = NULL;

// 查找数据帧的起始和结束位置 (假设以 '\n' 结尾)
uint16_t current_pos = comm_handle.rx_tail;
while (current_pos != comm_handle.rx_head) {
if (comm_handle.rx_buffer[current_pos] == '{') { // JSON 数据以 '{' 开始
data_start = &comm_handle.rx_buffer[current_pos];
break;
}
current_pos = (current_pos + 1) % COM_RX_BUFFER_SIZE;
}

if (data_start != NULL) {
uint16_t end_pos = comm_handle.rx_head;
data_len = (end_pos - (data_start - comm_handle.rx_buffer) + COM_RX_BUFFER_SIZE) % COM_RX_BUFFER_SIZE;

// 简单 JSON 解析 (需要更健壮的 JSON 解析库,这里为了演示简化)
char json_str[COM_RX_BUFFER_SIZE];
memset(json_str, 0, sizeof(json_str));
if (data_len < sizeof(json_str)) {
if (end_pos > (data_start - comm_handle.rx_buffer)) {
memcpy(json_str, data_start, data_len);
} else { // 数据帧跨越缓冲区末尾和开头
uint16_t first_part_len = COM_RX_BUFFER_SIZE - (data_start - comm_handle.rx_buffer);
memcpy(json_str, data_start, first_part_len);
memcpy(json_str + first_part_len, comm_handle.rx_buffer, data_len - first_part_len);
}

// 解析 JSON 字符串 (非常简化的示例,实际应用需要更完善的解析)
char *temp_str = strstr(json_str, "\"cpu_temp\":");
if (temp_str) {
comm_handle.received_data.cpu_temp = atof(temp_str + strlen("\"cpu_temp\":"));
}
temp_str = strstr(json_str, "\"cpu_usage\":");
if (temp_str) {
comm_handle.received_data.cpu_usage = atoi(temp_str + strlen("\"cpu_usage\":"));
}
temp_str = strstr(json_str, "\"ram_usage\":");
if (temp_str) {
comm_handle.received_data.ram_usage = atoi(temp_str + strlen("\"ram_usage\":"));
}
temp_str = strstr(json_str, "\"gram_usage\":");
if (temp_str) {
comm_handle.received_data.gram_usage = atoi(temp_str + strlen("\"gram_usage\":"));
}
}

// 更新 rx_tail 指针,表示数据已被处理
comm_handle.rx_tail = comm_handle.rx_head; // 清空缓冲区,或者根据实际情况移动 tail 指针
}
}
}

bool Comm_IsDataReceived(void) {
return comm_handle.data_received_flag;
}

PerformanceData_t Comm_GetReceivedData(void) {
return comm_handle.received_data;
}

void Comm_SendData(PerformanceData_t *data) {
// ... (实现数据打包和发送功能,如果需要上位机发送指令到嵌入式设备)
}

data_protocol.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef DATA_PROTOCOL_H
#define DATA_PROTOCOL_H

#include "stdint.h"
#include "stdbool.h"

// 定义性能数据结构体
typedef struct {
float cpu_temp;
uint8_t cpu_usage;
uint8_t ram_usage;
uint8_t gram_usage;
} PerformanceData_t;

#endif // DATA_PROTOCOL_H

(4) 数据处理层 (data_processor/)

data_processor.h:

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef DATA_PROCESSOR_H
#define DATA_PROCESSOR_H

#include "stdint.h"
#include "stdbool.h"
#include "data_protocol.h"

void DataProcessor_Init(void);
void DataProcessor_UpdatePerformanceData(PerformanceData_t *data);
PerformanceData_t DataProcessor_GetProcessedData(void);

#endif // DATA_PROCESSOR_H

data_processor.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "data_processor.h"

PerformanceData_t processed_data;

void DataProcessor_Init(void) {
// 初始化数据处理模块
memset(&processed_data, 0, sizeof(processed_data));
}

void DataProcessor_UpdatePerformanceData(PerformanceData_t *data) {
// 数据校验和处理 (例如数据平滑滤波)
processed_data = *data; // 简单赋值,实际应用中可以添加更复杂的数据处理逻辑
}

PerformanceData_t DataProcessor_GetProcessedData(void) {
return processed_data;
}

(5) GUI显示层 (gui/)

gui.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#ifndef GUI_H
#define GUI_H

#include "stdint.h"
#include "stdbool.h"
#include "data_protocol.h"

#define TFT_WIDTH 240 // 假设 TFT 宽度
#define TFT_HEIGHT 320 // 假设 TFT 高度

// 颜色定义 (RGB565 格式)
#define COLOR_BLACK 0x0000
#define COLOR_WHITE 0xFFFF
#define COLOR_RED 0xF800
#define COLOR_GREEN 0x07E0
#define COLOR_BLUE 0x001F
#define COLOR_GRAY 0x8420

void GUI_Init(void);
void GUI_ClearScreen(uint16_t color);
void GUI_DrawPixel(uint16_t x, uint16_t y, uint16_t color);
void GUI_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
void GUI_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
void GUI_FillRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
void GUI_DrawCircle(uint16_t x, uint16_t y, uint16_t r, uint16_t color);
void GUI_FillCircle(uint16_t x, uint16_t y, uint16_t r, uint16_t color);
void GUI_DrawText(uint16_t x, uint16_t y, const char *text, uint16_t color, uint16_t bgcolor);
void GUI_DrawGauge(uint16_t x, uint16_t y, uint16_t radius, uint8_t percentage, uint16_t color, uint16_t bgcolor);
void GUI_DrawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t percentage, uint16_t color, uint16_t bgcolor);
void GUI_UpdatePerformanceDisplay(PerformanceData_t *data);

#endif // GUI_H

gui.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include "gui.h"
#include "tft_lcd_driver.h" // 包含 TFT LCD 驱动头文件
#include "fonts.h" // 包含字体库

void GUI_Init(void) {
TFT_LCD_Init(); // 初始化 TFT LCD 驱动
GUI_ClearScreen(COLOR_BLACK); // 清屏
}

void GUI_ClearScreen(uint16_t color) {
TFT_LCD_FillScreen(color);
}

void GUI_DrawPixel(uint16_t x, uint16_t y, uint16_t color) {
if (x < TFT_WIDTH && y < TFT_HEIGHT) {
TFT_LCD_DrawPixel(x, y, color);
}
}

// ... (实现 GUI_DrawLine, GUI_DrawRectangle, GUI_FillRectangle, GUI_DrawCircle, GUI_FillCircle 函数,使用 TFT_LCD_Driver 中的底层绘图函数)

void GUI_DrawText(uint16_t x, uint16_t y, const char *text, uint16_t color, uint16_t bgcolor) {
// 使用字体库和底层绘图函数实现文本绘制
// (简化的文本绘制示例,实际应用需要更完善的字体处理)
uint16_t current_x = x;
uint16_t current_y = y;
for (int i = 0; text[i] != '\0'; i++) {
char c = text[i];
if (c == '\n') {
current_x = x;
current_y += FONT_HEIGHT; // 假设字体高度固定
continue;
}
if (current_x + FONT_WIDTH > TFT_WIDTH) { // 换行
current_x = x;
current_y += FONT_HEIGHT;
}
if (current_y + FONT_HEIGHT > TFT_HEIGHT) { // 超出屏幕
break;
}
// 假设字体数据存储在 fonts.h 中,例如 Font8x16
// 循环遍历字体数据,绘制像素点
for (int row = 0; row < FONT_HEIGHT; row++) {
for (int col = 0; col < FONT_WIDTH; col++) {
if (Font8x16[c - ' '][row * FONT_WIDTH + col]) { // 假设字体数据格式
GUI_DrawPixel(current_x + col, current_y + row, color);
} else if (bgcolor != COLOR_TRANSPARENT) { // 背景色
GUI_DrawPixel(current_x + col, current_y + row, bgcolor);
}
}
}
current_x += FONT_WIDTH;
}
}

void GUI_DrawGauge(uint16_t x, uint16_t y, uint16_t radius, uint8_t percentage, uint16_t color, uint16_t bgcolor) {
// 绘制环形仪表盘
// ... (使用 GUI_DrawCircle, GUI_FillCircle, GUI_DrawLine 等函数实现)
// 需要计算角度和坐标,根据 percentage 绘制指针或填充扇形
}

void GUI_DrawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t percentage, uint16_t color, uint16_t bgcolor) {
// 绘制进度条
// ... (使用 GUI_FillRectangle, GUI_DrawRectangle 等函数实现)
uint16_t filled_width = (width * percentage) / 100;
GUI_FillRectangle(x, y, x + filled_width, y + height, color);
GUI_DrawRectangle(x, y, x + width, y + height, COLOR_GRAY); // 边框
}

void GUI_UpdatePerformanceDisplay(PerformanceData_t *data) {
GUI_ClearScreen(COLOR_BLACK); // 清屏

// 显示 CPU 温度
char temp_str[20];
sprintf(temp_str, "TEMP:%.1fC", data->cpu_temp);
GUI_DrawText(20, 20, temp_str, COLOR_WHITE, COLOR_BLACK);
GUI_DrawGauge(120, 80, 50, (uint8_t)data->cpu_usage, COLOR_GREEN, COLOR_BLACK); // CPU 使用率仪表盘
char usage_str[20];
sprintf(usage_str, "%d%%", data->cpu_usage);
GUI_DrawText(100, 140, usage_str, COLOR_WHITE, COLOR_BLACK);

// 显示 RAM 使用率
char ram_str[20];
sprintf(ram_str, "RAM:%d%%", data->ram_usage);
GUI_DrawText(20, 180, ram_str, COLOR_WHITE, COLOR_BLACK);
GUI_DrawProgressBar(20, 200, 200, 10, data->ram_usage, COLOR_BLUE, COLOR_BLACK);

// 显示 GRAM 使用率
char gram_str[20];
sprintf(gram_str, "GRAM:%d%%", data->gram_usage);
GUI_DrawText(20, 240, gram_str, COLOR_WHITE, COLOR_BLACK);
GUI_DrawProgressBar(20, 260, 200, 10, data->gram_usage, COLOR_RED, COLOR_BLACK);

// ... (可以添加更多 UI 元素和信息)
}

(6) 应用层 (app/)

app_performance_monitor.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include "app_performance_monitor.h"
#include "communication.h"
#include "data_processor.h"
#include "gui.h"
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

#define DISPLAY_UPDATE_PERIOD_MS 500 // 显示更新周期 (毫秒)

void PerformanceMonitorTask(void *pvParameters);
void DisplayUpdateTimerCallback(TimerHandle_t xTimer);

void App_PerformanceMonitor_Init(void) {
Comm_Init(115200); // 初始化串口通信,波特率 115200
DataProcessor_Init(); // 初始化数据处理模块
GUI_Init(); // 初始化 GUI 显示

// 创建性能监控任务
BaseType_t task_status = xTaskCreate(PerformanceMonitorTask, "PerfMonTask", 256, NULL, 2, NULL);
if (task_status != pdPASS) {
// 任务创建失败处理
while(1); // 错误处理
}

// 创建显示更新定时器
TimerHandle_t display_timer = xTimerCreate("DisplayTimer", pdMS_TO_TICKS(DISPLAY_UPDATE_PERIOD_MS),
pdTRUE, NULL, DisplayUpdateTimerCallback);
if (display_timer == NULL) {
// 定时器创建失败处理
while(1); // 错误处理
}
xTimerStart(display_timer, 0); // 启动定时器
}

void PerformanceMonitorTask(void *pvParameters) {
PerformanceData_t received_data;
PerformanceData_t processed_data;

while (1) {
if (Comm_IsDataReceived()) {
received_data = Comm_GetReceivedData();
DataProcessor_UpdatePerformanceData(&received_data);
processed_data = DataProcessor_GetProcessedData();

// 触发显示更新 (通过定时器回调函数更新显示,避免在任务中直接更新,提高响应性)
// 这里可以设置一个标志位,在定时器回调函数中检查并更新显示
// ... (使用事件标志组或消息队列等机制通知显示更新)
// 为了简化示例,这里直接在任务中更新显示,实际应用中建议使用异步更新机制
GUI_UpdatePerformanceDisplay(&processed_data);
}
vTaskDelay(pdMS_TO_TICKS(10)); // 适当延时,降低 CPU 占用率
}
}

void DisplayUpdateTimerCallback(TimerHandle_t xTimer) {
// 在定时器回调函数中更新显示 (如果使用异步更新机制)
// ... (检查标志位,获取 processed_data,调用 GUI_UpdatePerformanceDisplay)
// 在简化示例中,显示更新直接在 PerformanceMonitorTask 中完成
}

(7) 主函数 (main.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include "main.h"
#include "app_performance_monitor.h"
#include "FreeRTOS.h"
#include "task.h"

int main(void) {
// 系统初始化 (时钟、外设等) - 根据实际硬件平台进行初始化
HAL_Init();
SystemClock_Config(); // 配置系统时钟 (根据 STM32 HAL 库示例)
GPIO_Initialize(); // 初始化 GPIO (根据 STM32 HAL 库示例)

App_PerformanceMonitor_Init(); // 初始化性能监控应用

// 启动 FreeRTOS 任务调度器
vTaskStartScheduler();

// 正常情况下不会运行到这里
while (1) {
}
}

// 示例系统时钟配置函数 (根据 STM32 HAL 库示例修改)
void SystemClock_Config(void) {
// ... (配置系统时钟的代码,例如使用 HSE 外部高速时钟,配置 PLL 等)
}

// 示例 GPIO 初始化函数 (根据 STM32 HAL 库示例修改)
void GPIO_Initialize(void) {
// ... (初始化 GPIO 的代码,例如使能 GPIO 时钟,配置 GPIO 引脚模式等)
}

// 错误处理函数 (例如 HardFault_Handler, MemManage_Handler, BusFault_Handler, UsageFault_Handler)
void Error_Handler(void) {
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1) {
}
/* USER CODE END Error_Handler_Debug */
}

#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

(8) C# 上位机代码 (简要示例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using System;
using System.IO.Ports;
using System.Diagnostics;
using System.Text.Json;

namespace PerformanceMonitor上位机
{
class Program
{
static SerialPort serialPort;
static PerformanceCounter cpuCounter;
static PerformanceCounter ramCounter;
static PerformanceCounter gpuRamCounter; // 假设获取 GRAM 使用率

static void Main(string[] args)
{
// 配置串口
serialPort = new SerialPort("COM3", 115200); // 根据实际串口号修改
serialPort.Open();

// 初始化性能计数器
cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
ramCounter = new PerformanceCounter("Memory", "% Committed Bytes In Use");
gpuRamCounter = new PerformanceCounter("GPU Adapter Memory", "Dedicated Usage", "_Total"); // 示例,可能需要根据实际 GPU 计数器调整

while (true)
{
// 获取性能数据
float cpuTemp = GetCpuTemperature(); // 需要实现获取 CPU 温度的函数 (可能需要硬件传感器或第三方库)
int cpuUsage = (int)cpuCounter.NextValue();
int ramUsage = (int)ramCounter.NextValue();
int gramUsage = (int)gpuRamCounter.NextValue(); // 获取 GRAM 使用率

// 构建 JSON 数据
var performanceData = new
{
cpu_temp = cpuTemp,
cpu_usage = cpuUsage,
ram_usage = ramUsage,
gram_usage = gramUsage
};
string jsonString = JsonSerializer.Serialize(performanceData) + "\n"; // 添加换行符作为帧结束符

// 发送数据到串口
serialPort.Write(jsonString);

Console.WriteLine("Sent: " + jsonString);
System.Threading.Thread.Sleep(500); // 间隔 500ms 发送一次数据
}
}

static float GetCpuTemperature()
{
// 需要根据实际情况实现获取 CPU 温度的方法
// 可以使用 WMI, OpenHardwareMonitorLib 等库
// 这里返回一个模拟值作为示例
Random rnd = new Random();
return 45.0f + (float)rnd.NextDouble() * 10.0f; // 模拟 CPU 温度在 45-55 度之间
}
}
}

代码说明:

  • 模块化和分层: 代码按照之前描述的模块化和分层架构进行组织,方便理解和维护。
  • HAL抽象: HAL层提供了硬件抽象接口,例如 hal_gpio.h, hal_uart.h,使得驱动层代码可以独立于具体的硬件平台。
  • 驱动实现: uart_driver.ctft_lcd_driver.c 是驱动层的示例实现,需要根据实际使用的 UART 外设和 TFT LCD 驱动IC 进行编写。
  • 通信协议: communication.cdata_protocol.h 定义了串口通信协议,这里使用了简单的 JSON 格式,并以换行符 \n 作为数据帧结束符。实际应用中可以根据需求设计更完善的协议,例如加入校验和、帧头帧尾等。
  • GUI显示: gui.cgui.h 实现了 GUI 显示功能,包括基本的绘图函数和性能数据更新函数。为了简化示例,文本绘制功能比较基础,实际应用中可以使用更专业的 GUI 库,例如 LittlevGL 或 emWin。
  • FreeRTOS: 代码示例使用了 FreeRTOS 实时操作系统,创建了性能监控任务 PerformanceMonitorTask 和显示更新定时器,提高了系统的实时性和响应性。
  • C# 上位机: C# 上位机代码示例演示了如何使用 SerialPort 类进行串口通信,使用 PerformanceCounter 类获取系统性能数据,并将数据序列化为 JSON 格式发送到嵌入式系统。

项目实践与验证:

  1. 硬件连接: 按照电路原理图连接 STM32 开发板、TFT LCD 模块和 CP2102 USB转串口模块。
  2. 软件编译与烧录: 使用 Keil MDK 或其他合适的 IDE 编译嵌入式端 C 代码,并烧录到 STM32 开发板。
  3. 上位机软件运行: 运行 C# 上位机程序,确保串口端口号与 CP2102 模块连接的端口号一致。
  4. 功能测试: 观察 TFT LCD 屏幕,应该能够实时显示桌面电脑的 CPU 温度、CPU 使用率、内存使用率和显存使用率等性能指标。
  5. 性能测试: 测试系统的实时性、稳定性、数据传输速率和显示刷新率等性能指标。
  6. 代码优化与改进: 根据测试结果,对代码进行优化,例如优化 GUI 绘制效率、提高数据解析速度、降低 CPU 占用率等。同时,可以根据实际需求扩展系统功能,例如添加报警功能、历史数据记录、远程监控等。

总结:

这个嵌入式桌面小型电脑性能显示系统项目涵盖了嵌入式系统开发的完整流程,从需求分析、架构设计到代码实现、测试验证。采用模块化分层架构和关键技术,构建了一个可靠、高效、可扩展的系统平台。提供的C代码示例虽然只是部分关键代码,但已经能够清晰地展示系统的整体架构和核心功能实现思路。在实际项目开发中,需要根据具体的硬件平台、TFT LCD 模块和功能需求,对代码进行详细的编写、调试和优化。 这个项目展示了嵌入式软件工程师在构建复杂系统时所需的技能和方法,包括系统设计、架构选择、编程实现、硬件驱动、通信协议、GUI 开发、性能优化和问题解决能力。

欢迎关注我的其它发布渠道