WordPress 后台反应好慢如何做seo优化
一、项目概述
本文介绍如何通过Linux系统的串口通信,驱动工业级LED显示屏实现动态数据展示。项目采用C语言开发,包含气象数据显示和实时时钟两大核心功能,涉及以下关键技术点:
-
串口通信协议配置
-
自定义数据帧封装
-
CRC16校验算法
-
UTF-8到GB2312编码转换
-
多线程数据刷新
二、环境准备
硬件配置
-
树莓派4B(或其他Linux开发板)
-
BX-Y08A LED控制卡(支持RS232/RS485)
-
USB转串口模块(CH340/CP2102等)
软件依赖
sudo apt install gcc build-essential # 编译工具
sudo chmod 666 /dev/ttyUSB0 # 串口权限设置
三、核心代码解析
3.1 主程序逻辑
int main(void) {// 气象参数初始化int fengxiang=20;float fengsu=20.5, wendu=20.5, yuliang=20.5;// 格式化气象信息char buffer[100];sprintf(buffer, "\\F0040\\C1%5d°\n%4.1lfm/s\n%5.1lf℃\n%5.1lfmm", fengxiang, fengsu, wendu, yuliang);// 发送到显示屏区域0Chinese_Show_String(buffer, 0);// 实时时钟循环while (1) {time_t t = time(NULL);struct tm *tm_info = localtime(&t);sprintf(buffer, "\\F0040\\C1%d/%02d/%02d %02d:%02d:%02d",tm_info->tm_year+1900, tm_info->tm_mon+1, tm_info->tm_mday,tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec);Chinese_Show_String(buffer, 1);sleep(1);}
}
3.2 串口配置函数
int send_data_over_serial(uint8_t *data, size_t length, const char *port) {// 打开串口设备int fd = open(port, O_RDWR | O_NOCTTY);// 配置串口参数struct termios tty;cfsetospeed(&tty, B9600); // 波特率tty.c_cflag &= ~PARENB; // 无校验tty.c_cflag &= ~CSTOPB; // 1位停止位tty.c_cflag |= CS8; // 8位数据位// 发送数据write(fd, data, length);close(fd);return 0;
}
四、关键技术实现
4.1 数据帧结构设计
帧结构组成:
| 帧头(8字节) | 包头(14字节) | 数据区 | CRC校验(2字节) | 结束符(0x5A) |
协议示例:
uint8_t n2s_show[] = {// 帧头0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,// 包头0xFE,0xFF,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x01,0xFE,0x02, [长度字段],// 数据区0xA3,0x06, [坐标参数], [显示内容],// CRC16校验crc_low, crc_high,// 结束符0x5A
};
4.2 编码转换实现
void replace_utf8_to_gb2312(char *input) {// °符号转换:C2 B0 → A1 E3char *pos = strstr(input, "\xC2\xB0");if(pos) { pos[0] = 0xA1; pos[1] = 0xE3;}// ℃符号转换:E2 84 83 → A1 E6pos = strstr(input, "\xE2\x84\x83");if(pos) {memmove(pos+2, pos+3, strlen(pos+3)+1);pos[0] = 0xA1;pos[1] = 0xE6;}
}
五、常见问题排查
5.1 串口通信失败
检查设备权限:
ls -l /dev/ttyUSB*
验证波特率匹配:
stty -F /dev/ttyUSB0 speed
使用调试工具:
minicom -D /dev/ttyUSB0
5.2 显示乱码处理
-
确认控制卡字体编码
-
检查替换函数是否生效
-
使用十六进制查看器分析数据:
hexdump -C send_buffer.bin
六、性能优化建议
-
双缓冲机制:预先构建下一帧数据
-
CRC预计算:对固定帧头部分提前计算
-
异步发送:使用线程池处理串口通信
-
数据压缩:对重复内容进行行程编码
七、完整代码获取
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h> // 添加此行
#include <time.h> // 用于获取本地时间typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;uint8_t Bx_header[8] = {0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5,0xA5};//帧头
size_t array_len = 0;/******************************************************************************* @函数名: send_data_over_serial(uint8_t *data, size_t length, const char *port)* @功 能: 发送数据到指定的串口* @输 入: data--要发送的数据数组 length-数据长度 port-串口设备路径* @返 回: 成功返回0,失败返回-1
******************************************************************************/
int send_data_over_serial(uint8_t *data, size_t length, const char *port) {int fd;struct termios tty;// 打开串口设备fd = open(port, O_RDWR | O_NOCTTY);if (fd < 0) {perror("Error opening serial port");return -1;}// 获取当前串口设置if (tcgetattr(fd, &tty) != 0) {perror("Error getting serial port attributes");close(fd);return -1;}// 设置波特率cfsetospeed(&tty, B9600); // 设置输出波特率为115200cfsetispeed(&tty, B9600); // 设置输入波特率为115200// 设置其他串口参数tty.c_cflag &= ~PARENB; // 无奇偶校验位tty.c_cflag &= ~CSTOPB; // 1个停止位tty.c_cflag &= ~CSIZE; // 清除数据位掩码tty.c_cflag |= CS8; // 8个数据位
#ifdef CRTSCTStty.c_cflag &= ~CRTSCTS; // 禁用硬件流控
#endiftty.c_cflag |= CREAD | CLOCAL; // 启用接收器并忽略控制线状态变化tty.c_lflag &= ~ICANON; // 非规范模式tty.c_lflag &= ~ECHO; // 关闭回显tty.c_lflag &= ~ECHOE; // 关闭擦除字符时的回显tty.c_lflag &= ~ECHONL; // 关闭换行符回显tty.c_lflag &= ~ISIG; // 禁用信号tty.c_iflag &= ~(IXON | IXOFF | IXANY); // 禁用软件流控tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // 输入处理tty.c_oflag &= ~OPOST; // 原始输出模式tty.c_cc[VTIME] = 10; // 读超时时间(单位为100ms)tty.c_cc[VMIN] = 0; // 最小字节数// 应用新的串口设置if (tcsetattr(fd, TCSANOW, &tty) != 0) {perror("Error setting serial port attributes");close(fd);return -1;}// 发送数据ssize_t bytes_written = write(fd, data, length);if (bytes_written < 0) {perror("Error writing to serial port");close(fd);return -1;}printf("Successfully sent %zd bytes over serial.\n", bytes_written);// 关闭串口设备close(fd);return 0;
}/******************************************************************************* @函数名: crc16_modbus(uint8_t *data, size_t length)* @功 能: 生成crc16校验位 * @输 入: data--要校验的数组 length-长度* @返 回: 校验结果* @备 注:
******************************************************************************/
uint16_t crc16_ibm(const uint8_t* data, uint16_t length) {uint16_t crc = 0x0000;uint16_t polynomial = 0xA001;uint16_t i, j;for (i = 0; i < length; i++) {crc ^= data[i];for (j = 0; j < 8; j++) {if (crc & 0x0001) {crc = (crc >> 1) ^ polynomial;} else {crc = crc >> 1;}}}return crc;
}/******************************************************************************* @函数名: replace_utf8_to_gb2312(char *input)* @功 能: 替换特定的UTF-8字符为GB2312编码 * @输 入: input--要替换的字符串* @返 回: NULL
******************************************************************************/
void replace_utf8_to_gb2312(char *input) {char *pos = input;while ((pos = strstr(pos, "\xC2\xB0")) != NULL) { // 查找°符号pos[0] = '\xA1';pos[1] = '\xE3';// pos += 2; // 跳过已替换的字符break;}// pos = input;while ((pos = strstr(pos, "\xE2\x84\x83")) != NULL) { // 查找℃符号// 将后部内容向前移动1字节(覆盖原第三字节\x83)memmove(pos + 2, pos + 3, strlen(pos + 3) + 1);pos[0] = '\xA1';pos[1] = '\xE6';// pos += 2; // 跳过已替换的字符break;}
}/******************************************************************************* @函数名: Chinese_Show_String(char *string_show, uint16_t Dstaddr)* @功 能: 显示指定信息 -- 要显示指定颜色的时候记得添加转义字符 * @输 入: string_show--要显示的字符串 Dstaddr-屏地址* @返 回: NULL
******************************************************************************/
uint8_t Chinese_Show_String(char *String_show, uint16_t Dstaddr) {if(Dstaddr == 0) replace_utf8_to_gb2312(String_show); // 替换特定的UTF-8字符为GB2312编码uint8_t *data = (uint8_t *)String_show; // 直接使用输入字符串int crc16_L, crc16_H = 0;int len = 0;uint8_t n2s_show[200] = {0};array_len = strlen((char *)data); // 获取字符串长度memcpy(n2s_show, Bx_header, 8); // 帧头len += 8; // 偏移的长度// 包头数据n2s_show[len++] = 0xfe;n2s_show[len++] = 0xff;n2s_show[len++] = 0x00;n2s_show[len++] = 0x80;n2s_show[len++] = 0x00;n2s_show[len++] = 0x00;n2s_show[len++] = 0x00;n2s_show[len++] = 0x00;n2s_show[len++] = 0x00;n2s_show[len++] = 0x01;n2s_show[len++] = 0xFE; // 设备型号n2s_show[len++] = 0x02;n2s_show[len++] = (array_len + 36) & 0xff; // 数据区长度n2s_show[len++] = (array_len + 36) >> 8; // 数据区长度// 数据区n2s_show[len++] = 0xa3; // 命令分组编号n2s_show[len++] = 0x06; // 命令编号n2s_show[len++] = 0x00; // 是否要求控制器回复n2s_show[len++] = 0x00; // 不清区域n2s_show[len++] = 0x00; // 保留n2s_show[len++] = 0x00; // 删除区域个数n2s_show[len++] = 0x01; // 更新区域个数n2s_show[len++] = (array_len + 27) & 0xff; // 区域0数据长度n2s_show[len++] = (array_len + 27) >> 8; // 区域0数据长度// 区域0数据n2s_show[len++] = 0x00; // 区域类型if(Dstaddr == 0){// 四要素n2s_show[len++] = 0x20; // 区域X坐标,默认以字节(8个像素点)为单位n2s_show[len++] = 0x00;n2s_show[len++] = 40; // 区域Y坐标,以像素点为单位n2s_show[len++] = 0x00;n2s_show[len++] = 0x14; // 区域宽度,默认以字节(8个像素点)为单位n2s_show[len++] = 0x00;n2s_show[len++] = 160; // 区域高度,以像素点为单位n2s_show[len++] = 0x00;}else if(Dstaddr == 1){// 时钟n2s_show[len++] = 0x00; // 区域X坐标,默认以字节(8个像素点)为单位n2s_show[len++] = 0x00;n2s_show[len++] = 0x00; // 区域Y坐标,以像素点为单位n2s_show[len++] = 0x00;n2s_show[len++] = 0x32; // 区域宽度,默认以字节(8个像素点)为单位n2s_show[len++] = 0x00;n2s_show[len++] = 40; // 区域高度,以像素点为单位n2s_show[len++] = 0x00;}n2s_show[len++] = Dstaddr; // 动态区域编号n2s_show[len++] = 0X00; // 行间距n2s_show[len++] = 0X00; // 动态区数据循环显示n2s_show[len++] = 0X02; // 超时时间n2s_show[len++] = 0X00; // 超时时间n2s_show[len++] = 0x00; // 不使能语音模块n2s_show[len++] = 0x00; // 拓展位数n2s_show[len++] = 0x00; // 字体对齐方式 --上下居中 左右右对齐n2s_show[len++] = 0x02; // 是否多行显示n2s_show[len++] = 0x01; // 不自动换行n2s_show[len++] = 0x01; // 静止显示01n2s_show[len++] = 0x00; // 退出方式n2s_show[len++] = 0x00; // 显示速度n2s_show[len++] = 0x0a; // 特技显示时间n2s_show[len++] = array_len & 0xff; // 显示内容长度n2s_show[len++] = array_len >> 8;n2s_show[len++] = array_len >> 16;n2s_show[len++] = array_len >> 24;int i, j;for (i = 0; i < array_len; i++) {n2s_show[len++] = data[i]; // 显示内容}crc16_L = crc16_ibm(&n2s_show[8], len - 8) & 0xff;crc16_H = crc16_ibm(&n2s_show[8], len - 8) >> 8;n2s_show[len++] = crc16_L;n2s_show[len++] = crc16_H;n2s_show[len++] = 0x5a;// printf("%d:", len);// for (j = 0; j < len; j++) {// printf("%02x ", n2s_show[j]);// }// printf("\n");// 发送数据if (send_data_over_serial(n2s_show, len, "/dev/ttyUSB0") != 0) {printf("Failed to send data over serial.\n");return -1;}return 0;
}int main(void){int fengxiang=20;float fengsu=20.5;float wendu=20.5;float yuliang=20.5;char iniid[100];// // 数字左对齐// sprintf(iniid, "\\F0040\\C1%d°\n%lfm/s\n%lf℃\n%lfmm", fengxiang, fengsu, wendu, yuliang);// 单位右对齐sprintf(iniid, "\\F0040\\C1%5d°\n%4.1lfm/s\n%5.1lf℃\n%5.1lfmm", fengxiang, fengsu, wendu, yuliang);Chinese_Show_String(iniid, 0);while (1) {time_t t = time(NULL);struct tm *tm_info = localtime(&t);int year = tm_info->tm_year + 1900;int month = tm_info->tm_mon + 1;int day = tm_info->tm_mday;int hour = tm_info->tm_hour;int minute = tm_info->tm_min;int second = tm_info->tm_sec;sprintf(iniid, "\\F0040\\C1%d/%02d/%02d %02d:%02d:%02d", year, month, day, hour, minute, second);Chinese_Show_String(iniid, 1);sleep(1); // 每秒更新一次}
}