IAP固件升级进阶(Qt上位机)

前言

时隔近一年,再次接触IAP固件升级,这次修改了以前的一些bug,同时新增一些实用性的功能。

有纰漏请指出,转载请说明。

学习交流请发邮件 1280253714@qq.com。

上位机界面

视频演示

当Up对iap固件升级的机制有了更深的理解后_哔哩哔哩_bilibili

固件升级指令

重要代码

1.通过拖拽实现文件读取

// MainWindow类的dragEnterEvent方法  
// 当鼠标拖动文件进入MainWindow的边界时,触发此方法  
void MainWindow::dragEnterEvent(QDragEnterEvent *event)  
{  
    // 检查拖动的数据是否包含URLs(即文件路径)  
    if(event->mimeData()->hasUrls())  
        // 如果包含URLs,则接受拖放操作  
        event->acceptProposedAction();  
    else  
        // 如果不包含URLs,则忽略拖放操作  
        event->ignore();  
}  
  
// MainWindow类的dropEvent方法  
// 当鼠标在MainWindow内释放拖动的文件时,触发此方法  
void MainWindow::dropEvent(QDropEvent *event)  
{  
    // 获取拖放事件中的MIME数据  
    const QMimeData *mimeData = event->mimeData();  
    // 检查MIME数据是否包含URLs  
    if(mimeData->hasUrls())  
    {  
        // 获取URLs列表  
        QList<QUrl> urlList = mimeData->urls();  
        // 获取第一个URL的本地文件路径  
        QString fileName = urlList.at(0).toLocalFile();  
  
        // 获取textEdit_fwUpdateFile控件中的文本  
        QString text = ui->textEdit_fwUpdateFile->toPlainText();  
  
        // 检查文件名是否为空,并且textEdit_fwUpdateFile中的文本为空  
        if((!fileName.isEmpty()) && (text.isEmpty()))  
        {  
            // 检查文件扩展名是否为.bin  
            if (fileName.endsWith(".bin"))  
            {  
                // 创建一个QFile对象,用于读取文件  
                QFile file(fileName);  
                // 创建一个QFileInfo对象,用于获取文件信息  
                QFileInfo fileInfo(fileName);  
  
                // 初始化固件更新相关变量  
                fwPackIndex = 0;  
                // 获取文件大小  
                fwFileLen = fileInfo.size();  
                // 计算固件包的数量(根据fwPackLength,但fwPackLength在此代码中未定义)  
                fwPackNum = fwFileLen/fwPackLength+1;  
  
                // 尝试以只读方式打开文件  
                if(!file.open(QIODevice::ReadOnly))  
                    // 如果文件打开失败,则返回不执行后续操作  
                    return;  
  
                // 读取文件全部内容  
                binRawData = file.readAll();  
  
                // 在lineEdit_fwUpdateFile控件中显示文件名  
                ui->lineEdit_fwUpdateFile->setText(fileName);  
                // 在textEdit_fwUpdateFile控件中追加文件的十六进制表示  
                ui->textEdit_fwUpdateFile->append(binRawData.toHex());  
  
                // 关闭文件  
                file.close();  
  
                // 启用开始固件更新按钮  
                ui->pushButton_startFwUpdate->setEnabled(true);  
                // 禁用停止固件更新按钮  
                ui->pushButton_stopFwUpdate->setEnabled(false);  
  
                // 初始化固件更新状态  
                fwUpdateState = fwInit;  
                // 重置固件包索引  
                fwPackIndex = 0;  
                // 重置超出标记和索引(这些变量在代码中没有明确的定义和用途)  
                fwExceedFlag = 0;  
                fwExceedIndex = 0;  
  
                // 重置进度条  
                ui->progressBar_upgrade->reset();  
            }  
            else  
            {  
                // 如果文件扩展名不是.bin,则显示警告消息  
                QMessageBox::warning(this, tr("错误"), tr("无法打开正确的文件!"));  
            }  
        }  
    }  
}

2.串口初始化自动识别并连接带CH340的串口

void MainWindow::serialPortInit()
{
    // 获取所有可用的串口信息,并存储在QList<QSerialPortInfo>中  
    QList<QSerialPortInfo> comList = QSerialPortInfo::availablePorts();  
    
    // 清空comboBox_chooseCom控件中的所有项  
    ui->comboBox_chooseCom->clear();  
    
    // 创建一个QMap,用于存储串口名(portName)到comboBox中索引的映射  
    QMap<QString, int> portNameToIndexMap;  
    
    // 遍历所有可用的串口信息  
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {  
        // 构建一个显示文本,由串口名和描述组成  
        QString displayText = info.portName() + ": " + info.description();  
    
        // 获取当前comboBox的项数,作为新项的索引  
        int index = ui->comboBox_chooseCom->count();  
    
        // 在comboBox_chooseCom控件中添加新的显示文本  
        ui->comboBox_chooseCom->addItem(displayText);  
    
        // 将当前串口名及其索引存入映射中  
        portNameToIndexMap.insert(info.portName(), index);  
    
        // 检查串口的描述或制造商信息是否包含"CH340"(不区分大小写)  
        if (info.description().contains("CH340", Qt::CaseInsensitive) ||  
            info.manufacturer().contains("CH340", Qt::CaseInsensitive)) {  
            // 使用映射来查找该串口名对应的索引  
            // 由于之前已经将串口名和索引存入映射,这里肯定能找到  
            if (portNameToIndexMap.contains(info.portName())) {  
                // 设置comboBox_chooseCom的当前项为找到的包含"CH340"的串口  
                ui->comboBox_chooseCom->setCurrentIndex(portNameToIndexMap.value(info.portName()));  
            }  
            // 找到后退出循环,因为我们只需要设置第一个匹配的串口  
            break;  
        }  
    }
}

3.IAP相关代码,跟上位机通信的逻辑部分

// IapRcvDataProc 函数用于处理接收到的IAP(In-Application Programming)数据消息  
void IapRcvDataProc(MSG_S stMsg)  
{  
    // 定义一个缓冲区用于存储接收到的消息数据  
    u8 MsgData[100];  
    // 将接收到的消息数据复制到MsgData缓冲区中  
    memcpy(MsgData, stMsg.szData, stMsg.u8Len);  
    u8 cmd = MsgData[__CmdIndex]; // 获取命令码  
  
    u8 i = 0; // 循环计数器  
  
    // 根据接收到的命令码的第3位(MsgData[2]的高4位)来构造回复命令的第1个字节  
    u8 replyCmd1 = 0xB0 | (MsgData[2] >> 4);  
  
    // 定义一个发送消息的缓冲区  
    u8 txMsg[20] = {0};  
  
    // 根据命令码执行不同的操作  
    switch(cmd)  
    {  
        case 0xF0: // 固件更新请求  
            // 读取固件更新标志位  
            SystemFlashRead(__FwUpdateFlagAddr, __FwUpdateFlagSize, &stIap.u32FwFlag);  
            // 清除IAP结构体stIap的所有数据  
            memset(&stIap, 0, sizeof(IAP_S));  
            // 复制固件版本号到IAP结构体的flashData字段  
            memcpy(&stIap.stFwVer.flashData, &MsgData[6], 3);  
            // 将固件版本号写入到固件版本地址  
            SystemFlashWrite(__FwVersionAddr, __FwUpdateFlagSize, &stIap.stFwVer.flashData);  
            // 构造回复消息  
            txMsg[0] = replyCmd1;  
            txMsg[1] = cmd;  
            txMsg[2] = 1; // 表示成功的标志位  
            // 发送回复消息  
            UartLoadTxMsg(1, txMsg, 3);  
            break;  
  
        case 0xF1: // 固件擦除 
            // 设置固件标志位为无固件  
            stIap.u32FwFlag = __NO_FW;
            // 写入固件更新标志位  
            SystemFlashWrite(__FwUpdateFlagAddr, __FwUpdateFlagSize, &stIap.u32FwFlag);  
            // 擦除应用程序空间  
            EraseFwSpace(__APP_START_ADDR, __APP_SIZE / __FLASH_PAGE_SIZE);  
            break;  
  
        case 0xF2: // 固件数据接收  
            // 更新帧索引  
            stIap.u16FwFrameIndex++;  
            // 设置接收帧的长度  
            stIap.stRcvFrame.u8Length = MsgData[6];  
            // 复制接收到的数据到IAP结构体的接收帧缓冲区  
            memcpy(&stIap.stRcvFrame.u8Data, &MsgData[7], stIap.stRcvFrame.u8Length);  
            // 逐块(每块4个字节)将数据写入到应用程序起始地址  
            for(i = 0; i < stIap.stRcvFrame.u8Length; i += 4)  
            {  
                FlashWriteWord(__APP_START_ADDR + stIap.u32WriteAddrIndex, *(u32 *)&stIap.stRcvFrame.u8Data[i]);  
                stIap.u32WriteAddrIndex += 4; // 更新写入地址  
            }  
            // 构造回复消息,包含帧编号和成功标志位  
            txMsg[0] = replyCmd1;  
            txMsg[1] = cmd;  
            txMsg[2] = MsgData[4]; // 帧编号的高字节  
            txMsg[3] = MsgData[5]; // 帧编号的低字节  
            txMsg[4] = 1; // 成功标志位  
            // 发送回复消息  
            UartLoadTxMsg(1, txMsg, 5);  
            break;  
  
        case 0xF3: // 固件更新完成  
            // 设置固件标志位为已有固件  
            stIap.u32FwFlag = __HAVE_FW_REPLY;  
            // 将固件标志位写入到固件更新标志地址  
            SystemFlashWrite(__FwUpdateFlagAddr, __FwUpdateFlagSize, &stIap.u32FwFlag);  
            // 跳转到应用程序开始执行(切换到新的固件)  
            JumpToApplication();  
            break;  
  
        case 0xF4: // 固件版本查询  
            // 从固件版本地址读取固件版本数据  
            SystemFlashRead(__FwVersionAddr, __FwUpdateFlagSize, &stIap.stFwVer.flashData);  
            // 如果读取到的固件版本数据不是0xFFFFFFFF(表示无固件或无效版本)  
            if (stIap.stFwVer.flashData != 0xFFFFFFFF)   
            {  
                // 解析固件版本数据到结构体中的major、minor、patch字段  
                stIap.stFwVer.major =  stIap.stFwVer.flashData & 0xFF; // 低8位为major版本  
                stIap.stFwVer.minor =  (stIap.stFwVer.flashData >> 8) & 0xFF; // 接下来的8位为minor版本  
                stIap.stFwVer.patch =  (stIap.stFwVer.flashData >> 16) & 0xFF; // 再接下来的8位为patch版本  
            }  
            else  
            {  
                // 如果固件版本数据为0xFFFFFFFF,则清空固件版本结构体  
                memset(&stIap.stFwVer, 0, sizeof(stIap.stFwVer));  
            }  
            // 构造回复消息,包含固件版本号  
            txMsg[0] = replyCmd1;  
            txMsg[1] = cmd;  
            txMsg[2] = stIap.stFwVer.major; // 固件major版本  
            txMsg[3] = stIap.stFwVer.minor; // 固件minor版本  
            txMsg[4] = stIap.stFwVer.patch; // 固件patch版本  
            // 发送固件版本回复消息  
            UartLoadTxMsg(1, txMsg, 5);  
            break;
		}			
}

 4.APP检测将要更新的固件版本是否高于当前固件版本

// 从系统闪存中读取固件版本信息  
SystemFlashRead(__FwVersionAddr, __FwUpdateFlagSize, &stApp.stFwVer.flashData);  
  
// 检查读取到的固件版本数据是否不是0xFFFFFFFF(通常表示无效或未设置的值)  
if (stApp.stFwVer.flashData != 0xFFFFFFFF)   
{  
	// 如果不是无效值,则解析固件版本数据  
	// 提取主要版本号(低8位)  
	stApp.stFwVer.major = stApp.stFwVer.flashData & 0xFF;  
	// 提取次要版本号(接下来的8位)  
	stApp.stFwVer.minor = (stApp.stFwVer.flashData >> 8) & 0xFF;  
	// 提取补丁版本号(再接下来的8位,但注意这里实际上只用了24位来表示版本)  
	stApp.stFwVer.patch = (stApp.stFwVer.flashData >> 16) & 0xFF;  
}  
else  
{  
	// 如果固件版本数据是无效值,则清空固件版本结构体  
	memset(&stApp.stFwVer, 0, sizeof(stApp.stFwVer));  
}  
  
// 从接收到的消息数据中复制固件版本数据(MsgData是一个包含固件版本数据的数组)  
memcpy(&stRxFwVer.flashData, &MsgData[6], 3);  
  
// 解析接收到的固件版本数据  
// 提取主要版本号  
stRxFwVer.major = stRxFwVer.flashData & 0xFF;  
// 提取次要版本号  
stRxFwVer.minor = (stRxFwVer.flashData >> 8) & 0xFF;  
// 提取补丁版本号  
stRxFwVer.patch = (stRxFwVer.flashData >> 16) & 0xFF;  
  
// 调用函数比较当前固件版本(stApp.stFwVer)和接收到的固件版本(stRxFwVer)  
// 如果接收到的版本更高或等于当前版本,则返回1  
tmp = isNewVersionHigherOrEqual(&stApp.stFwVer, &stRxFwVer);  
  
// 根据比较结果决定下一步操作  
if (tmp == 1)  
{  
	// 如果接收到的版本更高或等于当前版本  
	// 设置固件标志位为需要固件更新  
	stApp.u32FwFlag = __HAVE_FW_UPDATE;  
	// 将固件标志位写入到固件更新标志地址  
	SystemFlashWrite(__FwUpdateFlagAddr, __FwUpdateFlagSize, &stApp.u32FwFlag);  
	// 创建一个任务来跳转到IAP模式进行固件更新(假设Task2和JumpToIap是任务相关的函数和参数)  
	OS_TaskCreat(Task2, JumpToIap, 100);  
	// 注意:这里可能还需要发送一个确认消息给发送方,但代码中没有体现  
}  
else  
{  
	// 构造回复消息  
	txMsg[0] = replyCmd1; // 回复命令标识符  
	txMsg[1] = cmd;       // 原始命令  
	// 如果接收到的版本不是更高,则在回复消息中添加0表示无需更新  
	txMsg[2] = 0;  
	// 发送无需更新的确认消息  
	UartLoadTxMsg(1, txMsg, 3);  
} 

 5.上位机对收到命令进行处理

void MainWindow::rcvFwReply(QByteArray *protocalData)
{
    // 定义一个字节数组pRxData来存储接收到的数据
    uint8_t pRxData[100];
    // 定义一个QString对象来存储接收到的固件版本号信息(但在此段代码中未使用)
    QString rcvFwVer;

    // 将传入的QByteArray中的数据复制到pRxData数组中
    memcpy(pRxData, protocalData->data(), protocalData->size());

    // 清空并设置文本编辑框的换行模式
    ui->textEdit_fwRcvInfo->clear();
    ui->textEdit_fwRcvInfo->setWordWrapMode(QTextOption::WordWrap);

    // 根据接收到的数据的第四个字节进行不同的处理
    switch (pRxData[3])
    {
        case 0xF0:
            if(pRxData[4] == 1)
            {
                // 重置fwExceedIndex和fwUpdateState变量,并启动相关的定时器
                    fwExceedIndex = 0;
                fwUpdateState = fwTransfer;
                fwNumCmd(); // 调用fwNumCmd函数(该函数在此段代码中未给出)
                    fwUpdateTimer->start(1000); // 定时器每隔1000毫秒(1秒)触发一次
                    fwUpdateTimeOut->start(2000); // 另一个定时器每隔2000毫秒(2秒)触发一次
                    // 在文本编辑框中插入“开始升级”文本
                    ui->textEdit_fwRcvInfo->insertPlainText(QString("开始升级"));
                    // 这里fwExceedIndex被重复设置为了0,可能是冗余代码
                    fwExceedIndex = 0;
            }
            else if (pRxData[4] == 0)
            {
            // 在文本编辑框中插入“固件已是最新版/对方拒绝升级”文本
                ui->textEdit_fwRcvInfo->insertPlainText(QString("固件已是最新版/对方拒绝升级"));
            }
            break;

        case 0xF2:
            // 从接收到的数据的第五个和第六个字节中解析出一个uint16_t类型的索引值
            uint16_t u16FwPackIndex;
            *(uint16_t *)&u16FwPackIndex = *(uint16_t *)&pRxData[4];

            // 如果解析出的索引值与当前fwPackIndex相等
            if(u16FwPackIndex == fwPackIndex)
            {
                // 如果接收到的数据的第七个字节是1
                if(pRxData[6] == 1)
                {
                    // 重置fwExceedIndex变量
                    fwExceedIndex = 0;

                    // 在文本编辑框中插入“第 [索引值] 帧传输成功”的文本
                    ui->textEdit_fwRcvInfo->insertPlainText(QString("第 ["));
                    ui->textEdit_fwRcvInfo->insertPlainText(QString::number(fwPackIndex));
                    ui->textEdit_fwRcvInfo->insertPlainText(QString("]  帧传输成功"));

                    // 重置fwExceedIndex变量(这里再次被重复设置,可能是冗余代码)
                    fwExceedIndex = 0;

                    // 启动相关的定时器
                    fwUpdateTimer->start(20); // 定时器每隔20毫秒触发一次
                    fwUpdateTimeOut->start(2000); // 另一个定时器每隔2000毫秒触发一次

                    // 更新进度条的值
                    ui->progressBar_upgrade->setValue(static_cast<int>(fwPackIndex * 100.0 / fwPackNum));
                }
                else if (pRxData[6] == 0)
                {
                    // 在文本编辑框中插入“[索引值] 帧传输失败”的文本
                    ui->textEdit_fwRcvInfo->insertPlainText(QString("["));
                    ui->textEdit_fwRcvInfo->insertPlainText(QString::number(fwPackIndex));
                    ui->textEdit_fwRcvInfo->insertPlainText(QString("] 帧传输失败"));
                }
            }
            break;

        case 0xF3:
            if(pRxData[4] == 1)
            {
                // 更新状态为升级完成
                fwUpdateState = fwComplete;
                // 在文本编辑框中插入“对方回复升级成功”的文本
                ui->textEdit_fwRcvInfo->insertPlainText(QString("对方回复升级成功"));
                // 重置一些变量
                fwExceedIndex = 0;
                fwUpdateTimeOut->stop();
            }
            else if (pRxData[4] == 0)
            {
                // 在文本编辑框中插入“对方回复升级失败”的文本
                ui->textEdit_fwRcvInfo->insertPlainText(QString("对方回复升级失败"));
            }
            // 无论成功还是失败,都重置UI中的按钮状态并更新fwUpdateState和其他相关变量
            ui->pushButton_startFwUpdate->setEnabled(true);
            ui->pushButton_stopFwUpdate->setEnabled(false);
            fwUpdateState = fwInit;
            fwPackIndex = 0;
            fwExceedFlag = 0;
            fwExceedIndex = 0;
            break;

        case 0xF4:
            // 从接收到的数据的第五、六、七个字节中分别获取固件的主版本号、次版本号和补丁版本号
            quint8 data1 = static_cast<quint8>(pRxData[4]);
            quint8 data2 = static_cast<quint8>(pRxData[5]);
            quint8 data3 = static_cast<quint8>(pRxData[6]);

            // 将十六进制转换为十进制
            int versionMajor = data1;
            int versionMinor = data2;
            int versionPatch = data3;

            // 组合成版本号字符串,并确保补丁版本号至少为两位数,不足补0
            QString versionString = QString("对方固件版本号为:%1.%2.%3").arg(versionMajor).arg(versionMinor).arg(versionPatch, 2, 10, QChar('0')).toUpper();

            // 在文本编辑框中插入版本号字符串
            ui->textEdit_fwRcvInfo->insertPlainText(versionString);
            break;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/718591.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

44. 【Java教程】方法引用

通过前两个小节对Lambda表达式的学习&#xff0c;本小节我们来介绍一个更加深入的知识点 —— 方法引用。通过本小节的学习&#xff0c;你将了解到什么是方法引用&#xff0c;方法引用的基础语法&#xff0c;方法引用的使用条件和使用场景&#xff0c;方法引用的分类&#xff0…

实用软件下载:UltraEditUEStudio最新安装包及详细安装教程

​UEStudio简介&#xff1a;UEStudio建立在上文本编辑器UltraEdit的功能基础上&#xff0c;并为团队和开发人员提供了其他功能&#xff0c;例如深度Git集成&#xff0c;您可以直接在UEStudio中克隆&#xff0c;签出&#xff0c;更新&#xff0c;提交&#xff0c;推入/拉入等操作…

《汇编语言程序设计》例子之查找最大数

以下是第5章中讲到的 CMOV 的指令的例子&#xff0c;原来的源码是这样的&#xff1a; # cmovtest.s - An example of the CMOV instructions .section .data output:.asciz "The largest value is %d\n" values:.int 105, 235, 61, 315, 134, 221, 53, 145, 117, 5 …

反向海淘代购系统集成功能详解:从商品对接到物流转运的一体化解决方案

随着全球化进程的加速&#xff0c;反向海淘&#xff08;即从国外购买商品至国内&#xff09;的需求日益增长。为满足这一市场趋势&#xff0c;反向海淘代购系统正不断进化&#xff0c;集成多种功能以提供更加便捷、高效的服务。本文将深入探讨反向海淘代购系统的核心集成功能&a…

HTML基础结构入门

HTML&#xff08;超文本标记语言&#xff09;是构建网页的基础语言。它用于描述网页的结构和内容。让我们从最基本的HTML文档开始。 HTML基础结构 一个基本的HTML文档结构如下&#xff1a; <!DOCTYPE html> <html lang"zh-CN"> <head><meta …

【Linux硬盘数据读取】WIN10访问linux分区解决方案:ext2fsd

<div id"content_views" class"htmledit_views" style"user-select: auto;"><p>尝试ext2explore、Paragon ExtFS都不好用&#xff0c;强烈安利ext2fsd&#xff0c;可读写&#xff0c;很强大</p> 转自&#xff1a;https://blog…

SpringBoot三层架构

目录 一、传统方式 二、三层架构 三、代码拆分 1、dao层 2、service层 3、control层 四、运行结果 一、传统方式 上述代码存在一定的弊端&#xff0c;在进行软件设计和软件开发中提倡单一责任原则&#xff0c;使代码的可读性更强&#xff0c;复杂性更低&#xff0c;可扩展性…

PFA烧杯带把手带刻度1000ml3000mlPFA氟树脂温度范围-270~250℃

随着越来越多的痕量分析实验需要对ppb和ppt级的浓度进行测定。目前所使用的一般材料由于无特别处理&#xff0c;不可避免会与所储存的样品&#xff0c;试剂或标准液反应&#xff0c;导致痕量分析实验得到不正确的结果。但我厂的PFA产品刚好能弥补其不足。PFA金属元素空白值低&a…

AI存储解决案例分享

AI数据管道&#xff08;Data Pipeline&#xff09;是指在AI项目中&#xff0c;数据从原始状态到最终可用模型的整个处理流程&#xff0c;包括数据采集、清洗、转换、分析、训练模型、验证模型直至部署和监控等多个环节。 在AI训练和推理过程中&#xff0c;多个管道可能同时读取…

一道session文件包含题

目录 环境说明 session文件包含getshell 审计源码 session包含 base64在session中的解码分析 题目&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1Q0BN08b8gWiVE4tOnirpTA?pwdcate 提取码&#xff1a;cate 环境说明 这里我用的是linux&#xff0c;也可以用p…

【计算机毕业设计】235基于微信小程序点餐系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

从GAN到WGAN(02/2)

文章目录 一、说明二、GAN中的问题2.1 难以实现纳什均衡(Nash equilibrium)2.2 低维度支撑2.3 梯度消失2.4 模式坍缩2.5 缺乏适当的评估指标 三、改进的GAN训练四、瓦瑟斯坦&#xff08;Wasserstein&#xff09;WGAN4.1 什么是 Wasserstein 距离&#xff1f;4.2 为什么 Wassers…

大模型太贵?找找自己的原因好吧?

什么&#xff1f; 炼个大模型还嫌贵&#xff1f; 到底哪里贵了&#xff01;&#xff1f; 大模型算力贵&#xff1f;哪里贵了&#xff01;&#xff1f; 争先恐后训练大模型&#xff0c; 搞得现在“算力慌”“一卡难求”&#xff0c; 算力当然水涨船高了! “特供版”GPU又…

守护电力心脏:国网电力监控运维平台的智慧使命

国网电力监控运维平台&#xff0c;以其强大的数据分析和处理能力&#xff0c;实现了对电网运行的实时监控。无论是电压波动、电流异常&#xff0c;还是设备故障&#xff0c;平台都能迅速捕捉并发出预警&#xff0c;确保电力供应的稳定和安全。 山海鲸可视化电力监控运维平台 想…

RPC协议

3.8 既然有 HTTP 协议&#xff0c;为什么还要有 RPC 假设我们需要在 A 电脑的进程发一段数据到 B 电脑的进程&#xff0c;我们一般会在代码里使用 Socket 进行编程。 这时候&#xff0c;我们可选项一般也就 TCP 和 UDP 二选一。TCP 可靠&#xff0c;UDP 不可靠。 类似下面这…

AI办公自动化:批量根据Excel表格内容制作Word文档

工作任务&#xff1a;Excel表格中有大量文本&#xff0c;根据这些文本自动生成word文档 在chatgpt中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;写一个Python脚本&#xff0c;具体步骤如下&#xff1a; 读取Excel文件&#xff1a;"F:\AI自媒体内容\AI视…

AcWing 1273:天才的记忆 ← ST算法求解RMQ问题

【题目来源】https://www.acwing.com/problem/content/1275/【题目描述】 从前有个人名叫 WNB&#xff0c;他有着天才般的记忆力&#xff0c;他珍藏了许多许多的宝藏。 在他离世之后留给后人一个难题&#xff08;专门考验记忆力的啊&#xff01;&#xff09;&#xff0c;如果谁…

CSS文本超限后使用省略号代替

方案一&#xff1a; 只显示一行&#xff0c;超限后使用省略号代替 .detail {overflow: hidden;text-overflow: ellipsis;white-space: nowrap; }方案二&#xff1a; 显示多行&#xff0c;到最后一行还没有显示完&#xff0c;则最后一行多出来的部分使用省略号代替。 .detai…

视频监控平台:通过网络SDK对TCL网络摄像机进行PTZ控制 的源代码介绍及分享

目录 一、视频监控平台介绍 &#xff08;一&#xff09;概述 &#xff08;二&#xff09;视频接入能力介绍 &#xff08;三&#xff09;功能介绍 二、TCL网络摄像机 &#xff08;一&#xff09;360度全景自动旋转&#xff1a; &#xff08;二&#xff09;高清夜视和全彩…

探索未来工作新伙伴:机器人流程自动化(RPA)揭秘

想象一下&#xff0c;如果你的日常工作中那些繁琐、重复的任务&#xff0c;比如数据录入、文件整理、邮件发送等&#xff0c;都能自动完成&#xff0c;你将拥有更多时间专注于真正需要创造力和智慧的工作&#xff0c;是不是听起来就像拥有了一个私人助理&#xff1f;这并不是遥…