Initial import of firmware and host projects

This commit is contained in:
2026-04-10 16:53:41 +08:00
commit 42a36164be
124 changed files with 13943 additions and 0 deletions

6
KeyBorad/KeyBorad.slnx Normal file
View File

@@ -0,0 +1,6 @@
<Solution>
<Configurations>
<Platform Name="x64" />
</Configurations>
<Project Path="KeyBorad/KeyBorad.vcxproj" Id="175bed2a-ed45-4ddc-a67b-7c4042190634" />
</Solution>

View File

@@ -0,0 +1,121 @@
#pragma once
#include <QtCore/QByteArray>
#include <QtCore/QtGlobal>
struct Packet_len
{
static const quint8 Com_Len_UnKnow = 0;
static const quint8 Com_Len_HelloReq = 1;
static const quint8 Com_Len_HelloRsp = 9;
static const quint8 Com_Len_Bitmap = 29;
static const quint8 Com_Len_FunctionKeyEvent = 3;
static const quint8 Com_Len_LedState = 1;
static const quint8 Com_Len_TimeSync = 16;
static const quint8 Com_Len_ThemeRgb = 3;
static const quint8 Com_Len_Ack = 1;
static const quint8 Com_Len_Error = 2;
};
enum Packet_Type : quint8
{
Com_Type_UnKnow = 0x00,
Com_Type_HelloReq = 0x01,
Com_Type_HelloRsp = 0x02,
Com_Type_Bitmap = 0x10,
Com_Type_FunctionKeyEvent = 0x20,
Com_Type_LedState = 0x21,
Com_Type_TimeSync = 0x30,
Com_Type_ThemeRgb = 0x31,
Com_Type_Ack = 0x7E,
Com_Type_Error = 0x7F
};
enum Key_Action : quint8
{
Key_Action_Release = 0x00,
Key_Action_Press = 0x01
};
enum Error_Code : quint8
{
Error_Code_None = 0x00,
Error_Code_UnknownType = 0x01,
Error_Code_InvalidLength = 0x02,
Error_Code_InvalidParam = 0x03,
Error_Code_NotReady = 0x04
};
struct Packet
{
quint8 Com_Packet_Head1 = 0xAA;
quint8 Com_Packet_Head2 = 0x55;
quint8 len = Packet_len::Com_Len_UnKnow;
Packet_Type type = Com_Type_UnKnow;
QByteArray data;
};
struct Packet_HelloReq
{
quint8 ProtocolVersion = 0x01;
};
struct Packet_HelloRsp
{
quint8 ProtocolVersion = 0x01;
quint16 VendorId = 0;
quint16 ProductId = 0;
quint8 FirmwareMajor = 0;
quint8 FirmwareMinor = 0;
quint16 CapabilityFlags = 0;
};
struct Packet_Bitmap
{
QByteArray UsageBitmap = QByteArray(Packet_len::Com_Len_Bitmap, 0);
};
struct Packet_FunctionKeyEvent
{
quint16 Usage = 0;
quint8 Action = Key_Action_Release;
};
struct Packet_LedState
{
quint8 LedMask = 0;
};
struct Packet_TimeSync
{
quint8 Version = 1;
quint8 Flags = 0;
quint16 TimezoneMin = 0;
quint64 UtcMs = 0;
quint32 AccuracyMs = 0;
};
struct Packet_ThemeRgb
{
quint8 Red = 0;
quint8 Green = 0;
quint8 Blue = 0;
};
struct Packet_Ack
{
quint8 AckedType = 0;
};
struct Packet_Error
{
quint8 ErrorType = 0;
quint8 ErrorCode = Error_Code_None;
};

View File

@@ -0,0 +1,198 @@
#include "COM/Com_CdcDecode.h"
#include "COM/Com_CdcEncode.h"
namespace
{
constexpr quint8 COM_CDC_CONST_PACKET_HEAD1 = 0xAA;
constexpr quint8 COM_CDC_CONST_PACKET_HEAD2 = 0x55;
constexpr int COM_CDC_CONST_FRAME_OVERHEAD = 5;
constexpr int COM_CDC_CONST_MAX_PAYLOAD_LENGTH = 64;
bool Com_Cdc_Func_IsKnownLengthValid(Packet_Type Type, quint8 DataLength)
{
switch (Type)
{
case Com_Type_UnKnow:
return DataLength == Packet_len::Com_Len_UnKnow;
case Com_Type_HelloReq:
return DataLength == Packet_len::Com_Len_HelloReq;
case Com_Type_HelloRsp:
return DataLength == Packet_len::Com_Len_HelloRsp;
case Com_Type_Bitmap:
return DataLength == Packet_len::Com_Len_Bitmap;
case Com_Type_FunctionKeyEvent:
return DataLength == Packet_len::Com_Len_FunctionKeyEvent;
case Com_Type_LedState:
return DataLength == Packet_len::Com_Len_LedState;
case Com_Type_TimeSync:
return DataLength == Packet_len::Com_Len_TimeSync;
case Com_Type_ThemeRgb:
return DataLength == Packet_len::Com_Len_ThemeRgb;
case Com_Type_Ack:
return DataLength == Packet_len::Com_Len_Ack;
case Com_Type_Error:
return DataLength == Packet_len::Com_Len_Error;
default:
return true;
}
}
bool Com_Cdc_Func_ParseFrameAtStart(const QByteArray& ByteArray, Packet* p_Packet)
{
if ((p_Packet == nullptr) || (ByteArray.size() < COM_CDC_CONST_FRAME_OVERHEAD))
{
return false;
}
if (static_cast<quint8>(ByteArray.at(0)) != COM_CDC_CONST_PACKET_HEAD1)
{
return false;
}
if (static_cast<quint8>(ByteArray.at(1)) != COM_CDC_CONST_PACKET_HEAD2)
{
return false;
}
const quint8 DataLength = static_cast<quint8>(ByteArray.at(2));
if (DataLength > COM_CDC_CONST_MAX_PAYLOAD_LENGTH)
{
return false;
}
const Packet_Type Type =
static_cast<Packet_Type>(static_cast<quint8>(ByteArray.at(3)));
if (!Com_Cdc_Func_IsKnownLengthValid(Type, DataLength))
{
return false;
}
const int FrameLength = COM_CDC_CONST_FRAME_OVERHEAD + static_cast<int>(DataLength);
if (ByteArray.size() != FrameLength)
{
return false;
}
const QByteArray Body = ByteArray.left(FrameLength - 1);
const quint8 Checksum = Com_Cdc_Func_CalcChecksum(Body);
const quint8 ChecksumRx = static_cast<quint8>(ByteArray.at(FrameLength - 1));
if (Checksum != ChecksumRx)
{
return false;
}
p_Packet->Com_Packet_Head1 = COM_CDC_CONST_PACKET_HEAD1;
p_Packet->Com_Packet_Head2 = COM_CDC_CONST_PACKET_HEAD2;
p_Packet->len = DataLength;
p_Packet->type = Type;
p_Packet->data = ByteArray.mid(4, DataLength);
return true;
}
int Com_Cdc_Func_FindHead(const QByteArray& ByteArray)
{
for (int Index = 0; Index + 1 < ByteArray.size(); ++Index)
{
if ((static_cast<quint8>(ByteArray.at(Index)) == COM_CDC_CONST_PACKET_HEAD1) &&
(static_cast<quint8>(ByteArray.at(Index + 1)) == COM_CDC_CONST_PACKET_HEAD2))
{
return Index;
}
}
return -1;
}
void Com_Cdc_Func_KeepTailForNextScan(QByteArray* p_Buffer)
{
if (!p_Buffer->isEmpty() &&
(static_cast<quint8>(p_Buffer->at(p_Buffer->size() - 1)) == COM_CDC_CONST_PACKET_HEAD1))
{
*p_Buffer = QByteArray(1, static_cast<char>(COM_CDC_CONST_PACKET_HEAD1));
return;
}
p_Buffer->clear();
}
} // namespace
bool Com_Cdc_Func_ParseFrame(const QByteArray& ByteArray, Packet* p_Packet)
{
return Com_Cdc_Func_ParseFrameAtStart(ByteArray, p_Packet);
}
bool Com_Cdc_Func_TryTakeFrame(QByteArray* p_Buffer, Packet* p_Packet)
{
if ((p_Buffer == nullptr) || (p_Packet == nullptr))
{
return false;
}
while (true)
{
if (p_Buffer->size() < 2)
{
return false;
}
const int HeadIndex = Com_Cdc_Func_FindHead(*p_Buffer);
if (HeadIndex < 0)
{
Com_Cdc_Func_KeepTailForNextScan(p_Buffer);
return false;
}
if (HeadIndex > 0)
{
p_Buffer->remove(0, HeadIndex);
}
if (p_Buffer->size() < 4)
{
return false;
}
const quint8 DataLength = static_cast<quint8>(p_Buffer->at(2));
if (DataLength > COM_CDC_CONST_MAX_PAYLOAD_LENGTH)
{
p_Buffer->remove(0, 1);
continue;
}
const Packet_Type Type =
static_cast<Packet_Type>(static_cast<quint8>(p_Buffer->at(3)));
if (!Com_Cdc_Func_IsKnownLengthValid(Type, DataLength))
{
p_Buffer->remove(0, 1);
continue;
}
const int FrameLength = COM_CDC_CONST_FRAME_OVERHEAD + static_cast<int>(DataLength);
if (p_Buffer->size() < FrameLength)
{
return false;
}
const QByteArray FrameBytes = p_Buffer->left(FrameLength);
if (!Com_Cdc_Func_ParseFrameAtStart(FrameBytes, p_Packet))
{
p_Buffer->remove(0, 1);
continue;
}
p_Buffer->remove(0, FrameLength);
return true;
}
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include <QtCore/QByteArray>
#include "COM/Com_Cdc.h"
bool Com_Cdc_Func_ParseFrame(const QByteArray& ByteArray, Packet* p_Packet);
bool Com_Cdc_Func_TryTakeFrame(QByteArray* p_Buffer, Packet* p_Packet);

View File

@@ -0,0 +1,159 @@
#include "COM/Com_CdcEncode.h"
namespace
{
Packet Com_Cdc_Func_MakePacket(Packet_Type Type, const QByteArray& Data)
{
Packet PacketData;
PacketData.type = Type;
PacketData.len = static_cast<quint8>(Data.size());
PacketData.data = Data;
return PacketData;
}
void Com_Cdc_Func_WriteLe16(QByteArray* p_Data, int Offset, quint16 Value)
{
(*p_Data)[Offset] = static_cast<char>(Value & 0x00FFU);
(*p_Data)[Offset + 1] = static_cast<char>((Value >> 8) & 0x00FFU);
}
void Com_Cdc_Func_WriteLe32(QByteArray* p_Data, int Offset, quint32 Value)
{
for (int Index = 0; Index < 4; ++Index)
{
(*p_Data)[Offset + Index] = static_cast<char>((Value >> (Index * 8)) & 0xFFU);
}
}
void Com_Cdc_Func_WriteLe64(QByteArray* p_Data, int Offset, quint64 Value)
{
for (int Index = 0; Index < 8; ++Index)
{
(*p_Data)[Offset + Index] = static_cast<char>((Value >> (Index * 8)) & 0xFFULL);
}
}
QByteArray Com_Cdc_Func_FitPayload(const QByteArray& Data, int ExpectedLength)
{
QByteArray Payload = Data.left(ExpectedLength);
if (Payload.size() < ExpectedLength)
{
Payload.append(ExpectedLength - Payload.size(), 0);
}
return Payload;
}
} // namespace
quint8 Com_Cdc_Func_CalcChecksum(const QByteArray& ByteArray)
{
quint8 Checksum = 0;
for (int Index = 0; Index < ByteArray.size(); ++Index)
{
const quint8 ByteValue = static_cast<quint8>(ByteArray.at(Index));
Checksum = static_cast<quint8>(Checksum ^ ByteValue);
}
return Checksum;
}
QByteArray Com_Cdc_Func_BuildFrame(const Packet& PacketData)
{
if (PacketData.data.size() > 0xFF)
{
return QByteArray();
}
QByteArray ByteArray;
const quint8 DataLength = static_cast<quint8>(PacketData.data.size());
ByteArray.append(static_cast<char>(PacketData.Com_Packet_Head1));
ByteArray.append(static_cast<char>(PacketData.Com_Packet_Head2));
ByteArray.append(static_cast<char>(DataLength));
ByteArray.append(static_cast<char>(PacketData.type));
ByteArray.append(PacketData.data);
const quint8 Checksum = Com_Cdc_Func_CalcChecksum(ByteArray);
ByteArray.append(static_cast<char>(Checksum));
return ByteArray;
}
QByteArray Com_Cdc_Func_BuildHelloReq(const Packet_HelloReq& PacketData)
{
QByteArray Payload(Packet_len::Com_Len_HelloReq, 0);
Payload[0] = static_cast<char>(PacketData.ProtocolVersion);
return Com_Cdc_Func_BuildFrame(Com_Cdc_Func_MakePacket(Com_Type_HelloReq, Payload));
}
QByteArray Com_Cdc_Func_BuildHelloRsp(const Packet_HelloRsp& PacketData)
{
QByteArray Payload(Packet_len::Com_Len_HelloRsp, 0);
Payload[0] = static_cast<char>(PacketData.ProtocolVersion);
Com_Cdc_Func_WriteLe16(&Payload, 1, PacketData.VendorId);
Com_Cdc_Func_WriteLe16(&Payload, 3, PacketData.ProductId);
Payload[5] = static_cast<char>(PacketData.FirmwareMajor);
Payload[6] = static_cast<char>(PacketData.FirmwareMinor);
Com_Cdc_Func_WriteLe16(&Payload, 7, PacketData.CapabilityFlags);
return Com_Cdc_Func_BuildFrame(Com_Cdc_Func_MakePacket(Com_Type_HelloRsp, Payload));
}
QByteArray Com_Cdc_Func_BuildBitmap(const Packet_Bitmap& PacketData)
{
return Com_Cdc_Func_BuildFrame(
Com_Cdc_Func_MakePacket(
Com_Type_Bitmap,
Com_Cdc_Func_FitPayload(PacketData.UsageBitmap, Packet_len::Com_Len_Bitmap)));
}
QByteArray Com_Cdc_Func_BuildFunctionKeyEvent(const Packet_FunctionKeyEvent& PacketData)
{
QByteArray Payload(Packet_len::Com_Len_FunctionKeyEvent, 0);
Com_Cdc_Func_WriteLe16(&Payload, 0, PacketData.Usage);
Payload[2] = static_cast<char>(PacketData.Action);
return Com_Cdc_Func_BuildFrame(
Com_Cdc_Func_MakePacket(Com_Type_FunctionKeyEvent, Payload));
}
QByteArray Com_Cdc_Func_BuildLedState(const Packet_LedState& PacketData)
{
QByteArray Payload(Packet_len::Com_Len_LedState, 0);
Payload[0] = static_cast<char>(PacketData.LedMask);
return Com_Cdc_Func_BuildFrame(Com_Cdc_Func_MakePacket(Com_Type_LedState, Payload));
}
QByteArray Com_Cdc_Func_BuildTimeSync(const Packet_TimeSync& PacketData)
{
QByteArray Payload(Packet_len::Com_Len_TimeSync, 0);
Payload[0] = static_cast<char>(PacketData.Version);
Payload[1] = static_cast<char>(PacketData.Flags);
Com_Cdc_Func_WriteLe16(&Payload, 2, PacketData.TimezoneMin);
Com_Cdc_Func_WriteLe64(&Payload, 4, PacketData.UtcMs);
Com_Cdc_Func_WriteLe32(&Payload, 12, PacketData.AccuracyMs);
return Com_Cdc_Func_BuildFrame(Com_Cdc_Func_MakePacket(Com_Type_TimeSync, Payload));
}
QByteArray Com_Cdc_Func_BuildThemeRgb(const Packet_ThemeRgb& PacketData)
{
QByteArray Payload(Packet_len::Com_Len_ThemeRgb, 0);
Payload[0] = static_cast<char>(PacketData.Red);
Payload[1] = static_cast<char>(PacketData.Green);
Payload[2] = static_cast<char>(PacketData.Blue);
return Com_Cdc_Func_BuildFrame(Com_Cdc_Func_MakePacket(Com_Type_ThemeRgb, Payload));
}
QByteArray Com_Cdc_Func_BuildAck(const Packet_Ack& PacketData)
{
QByteArray Payload(Packet_len::Com_Len_Ack, 0);
Payload[0] = static_cast<char>(PacketData.AckedType);
return Com_Cdc_Func_BuildFrame(Com_Cdc_Func_MakePacket(Com_Type_Ack, Payload));
}
QByteArray Com_Cdc_Func_BuildError(const Packet_Error& PacketData)
{
QByteArray Payload(Packet_len::Com_Len_Error, 0);
Payload[0] = static_cast<char>(PacketData.ErrorType);
Payload[1] = static_cast<char>(PacketData.ErrorCode);
return Com_Cdc_Func_BuildFrame(Com_Cdc_Func_MakePacket(Com_Type_Error, Payload));
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include <QtCore/QByteArray>
#include "COM/Com_Cdc.h"
quint8 Com_Cdc_Func_CalcChecksum(const QByteArray& ByteArray);
QByteArray Com_Cdc_Func_BuildFrame(const Packet& PacketData);
QByteArray Com_Cdc_Func_BuildHelloReq(const Packet_HelloReq& PacketData);
QByteArray Com_Cdc_Func_BuildHelloRsp(const Packet_HelloRsp& PacketData);
QByteArray Com_Cdc_Func_BuildBitmap(const Packet_Bitmap& PacketData);
QByteArray Com_Cdc_Func_BuildFunctionKeyEvent(const Packet_FunctionKeyEvent& PacketData);
QByteArray Com_Cdc_Func_BuildLedState(const Packet_LedState& PacketData);
QByteArray Com_Cdc_Func_BuildTimeSync(const Packet_TimeSync& PacketData);
QByteArray Com_Cdc_Func_BuildThemeRgb(const Packet_ThemeRgb& PacketData);
QByteArray Com_Cdc_Func_BuildAck(const Packet_Ack& PacketData);
QByteArray Com_Cdc_Func_BuildError(const Packet_Error& PacketData);

View File

@@ -0,0 +1,227 @@
#include "DRI/Dri_Cdc.h"
#include "COM/Com_Cdc.h"
#include "COM/Com_CdcDecode.h"
#include "COM/Com_CdcEncode.h"
#include <QtCore/QElapsedTimer>
#include <QtCore/QtGlobal>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
namespace
{
bool Dri_Cdc_Func_TryReadFrame(
QSerialPort* p_Serial,
QByteArray* p_ReadBuffer,
int TimeoutMs,
Packet* p_Packet)
{
if ((p_Serial == nullptr) || (p_ReadBuffer == nullptr) || (p_Packet == nullptr))
{
return false;
}
if (Com_Cdc_Func_TryTakeFrame(p_ReadBuffer, p_Packet))
{
return true;
}
QElapsedTimer Timer;
Timer.start();
while (Timer.elapsed() < TimeoutMs)
{
const int RemainingMs = TimeoutMs - static_cast<int>(Timer.elapsed());
const int WaitMs = qMin(20, RemainingMs);
if (WaitMs <= 0)
{
break;
}
if (!p_Serial->waitForReadyRead(WaitMs))
{
continue;
}
const QByteArray ReadBytes = p_Serial->readAll();
if (!ReadBytes.isEmpty())
{
p_ReadBuffer->append(ReadBytes);
}
if (Com_Cdc_Func_TryTakeFrame(p_ReadBuffer, p_Packet))
{
return true;
}
}
return false;
}
} // namespace
QVector<Dri_Cdc_Struct_PortInfo> Dri_Cdc_Enum()
{
QVector<Dri_Cdc_Struct_PortInfo> PortList;
const QList<QSerialPortInfo> InfoList = QSerialPortInfo::availablePorts();
for (int Index = 0; Index < InfoList.size(); ++Index)
{
const QSerialPortInfo& Info = InfoList.at(Index);
Dri_Cdc_Struct_PortInfo PortInfo;
PortInfo.PortName = Info.portName();
PortInfo.Description = Info.description();
PortInfo.Manufacturer = Info.manufacturer();
PortList.append(PortInfo);
}
return PortList;
}
void Dri_Cdc_Deinit(Dri_Cdc_Struct_Port* p_Port)
{
if (p_Port->p_Port != nullptr)
{
if (p_Port->p_Port->isOpen())
{
p_Port->p_Port->close();
}
delete p_Port->p_Port;
}
*p_Port = Dri_Cdc_Struct_Port();
}
bool Dri_Cdc_Init(
Dri_Cdc_Struct_Port* p_Port,
const Dri_Cdc_Struct_InitConfig& Config)
{
Dri_Cdc_Deinit(p_Port);
const QVector<Dri_Cdc_Struct_PortInfo> PortList = Dri_Cdc_Enum();
if (PortList.isEmpty())
{
return false;
}
Packet_HelloReq HelloReq;
const QByteArray HelloReqFrame = Com_Cdc_Func_BuildHelloReq(HelloReq);
for (int Index = 0; Index < PortList.size(); ++Index)
{
const Dri_Cdc_Struct_PortInfo& PortInfo = PortList.at(Index);
QSerialPort* p_Serial = new QSerialPort();
p_Serial->setPortName(PortInfo.PortName);
p_Serial->setBaudRate(Config.BaudRate);
p_Serial->setDataBits(QSerialPort::Data8);
p_Serial->setParity(QSerialPort::NoParity);
p_Serial->setStopBits(QSerialPort::OneStop);
p_Serial->setFlowControl(QSerialPort::NoFlowControl);
if (!p_Serial->open(QIODevice::ReadWrite))
{
delete p_Serial;
continue;
}
p_Serial->readAll();
if (p_Serial->write(HelloReqFrame) != HelloReqFrame.size())
{
p_Serial->close();
delete p_Serial;
continue;
}
if (!p_Serial->waitForBytesWritten(Config.HandshakeTimeoutMs))
{
p_Serial->close();
delete p_Serial;
continue;
}
QByteArray HandshakeBuffer;
QElapsedTimer Timer;
Timer.start();
while (Timer.elapsed() < Config.HandshakeTimeoutMs)
{
Packet PacketData;
const int RemainingMs =
Config.HandshakeTimeoutMs - static_cast<int>(Timer.elapsed());
if (!Dri_Cdc_Func_TryReadFrame(
p_Serial,
&HandshakeBuffer,
qMin(20, RemainingMs),
&PacketData))
{
continue;
}
if (PacketData.type == Com_Type_HelloRsp)
{
p_Port->p_Port = p_Serial;
p_Port->IsOpened = true;
p_Port->PortName = PortInfo.PortName;
p_Port->ReadBuffer = HandshakeBuffer;
return true;
}
}
p_Serial->close();
delete p_Serial;
}
return false;
}
bool Dri_Cdc_Read(
Dri_Cdc_Struct_Port* p_Port,
QByteArray& ByteArray)
{
ByteArray.clear();
if (!p_Port->IsOpened || (p_Port->p_Port == nullptr))
{
return false;
}
Packet PacketData;
if (Com_Cdc_Func_TryTakeFrame(&p_Port->ReadBuffer, &PacketData) ||
Dri_Cdc_Func_TryReadFrame(p_Port->p_Port, &p_Port->ReadBuffer, 50, &PacketData))
{
ByteArray = Com_Cdc_Func_BuildFrame(PacketData);
return !ByteArray.isEmpty();
}
return false;
}
bool Dri_Cdc_Write(
Dri_Cdc_Struct_Port* p_Port,
const QByteArray& ByteArray)
{
if (!p_Port->IsOpened || (p_Port->p_Port == nullptr))
{
return false;
}
if (ByteArray.isEmpty())
{
return false;
}
if (p_Port->p_Port->write(ByteArray) != ByteArray.size())
{
return false;
}
if (!p_Port->p_Port->waitForBytesWritten(200))
{
return false;
}
return true;
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include <QtCore/QByteArray>
#include <QtCore/QString>
#include <QtCore/QVector>
class QSerialPort;
struct Dri_Cdc_Struct_PortInfo
{
QString PortName;
QString Description;
QString Manufacturer;
};
struct Dri_Cdc_Struct_InitConfig
{
qint32 BaudRate = 115200;
int HandshakeTimeoutMs = 200;
};
struct Dri_Cdc_Struct_Port
{
QSerialPort* p_Port = nullptr;
bool IsOpened = false;
QString PortName;
QByteArray ReadBuffer;
};
QVector<Dri_Cdc_Struct_PortInfo> Dri_Cdc_Enum();
void Dri_Cdc_Deinit(Dri_Cdc_Struct_Port* p_Port);
bool Dri_Cdc_Init(
Dri_Cdc_Struct_Port* p_Port,
const Dri_Cdc_Struct_InitConfig& Config);
bool Dri_Cdc_Read(
Dri_Cdc_Struct_Port* p_Port,
QByteArray& ByteArray);
bool Dri_Cdc_Write(
Dri_Cdc_Struct_Port* p_Port,
const QByteArray& ByteArray);

View File

@@ -0,0 +1,4 @@
<RCC>
<qresource prefix="KeyBorad">
</qresource>
</RCC>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<UI version="4.0" >
<class>KeyBoradClass</class>
<widget class="QWidget" name="KeyBoradClass" >
<property name="objectName" >
<string notr="true">KeyBoradClass</string>
</property>
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>400</height>
</rect>
</property>
<property name="windowTitle" >
<string>KeyBorad</string>
</property>$centralwidget$
</widget>
<layoutDefault spacing="6" margin="11" />
<pixmapfunction></pixmapfunction>
<resources>
<include location="KeyBorad.qrc"/>
</resources>
<connections/>
</UI>

View File

@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="18.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{175BED2A-ED45-4DDC-A67B-7C4042190634}</ProjectGuid>
<Keyword>QtVS_v304</Keyword>
<WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">10.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">10.0</WindowsTargetPlatformVersion>
<QtMsBuild Condition="'$(QtMsBuild)'=='' OR !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v145</PlatformToolset>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v145</PlatformToolset>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
<Import Project="$(QtMsBuild)\qt_defaults.props" />
</ImportGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="QtSettings">
<QtInstall>D:\App\Qt\5.13.1\msvc2015_64</QtInstall>
<QtModules>core;serialport</QtModules>
<QtBuildConfig>debug</QtBuildConfig>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="QtSettings">
<QtInstall>D:\App\Qt\5.13.1\msvc2015_64</QtInstall>
<QtModules>core;serialport</QtModules>
<QtBuildConfig>release</QtBuildConfig>
</PropertyGroup>
<Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
<Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
</Target>
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(QtMsBuild)\Qt.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
<ClCompile>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'" Label="Configuration">
<ClCompile>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>false</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="COM\Com_Cdc.h" />
<ClCompile Include="COM\Com_CdcEncode.cpp" />
<ClInclude Include="COM\Com_CdcEncode.h" />
<ClCompile Include="COM\Com_CdcDecode.cpp" />
<ClInclude Include="COM\Com_CdcDecode.h" />
<ClCompile Include="DRI\Dri_Cdc.cpp" />
<ClCompile Include="main.cpp" />
<ClInclude Include="DRI\Dri_Cdc.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
<Import Project="$(QtMsBuild)\qt.targets" />
</ImportGroup>
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Form Files">
<UniqueIdentifier>{99349809-55BA-4b9d-BF79-8FDBB0286EB3}</UniqueIdentifier>
<Extensions>ui</Extensions>
</Filter>
<Filter Include="Translation Files">
<UniqueIdentifier>{639EADAA-A684-42e4-A9AD-28FC9BCB8F7C}</UniqueIdentifier>
<Extensions>ts</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="COM\Com_Cdc.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClCompile Include="COM\Com_CdcEncode.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClInclude Include="COM\Com_CdcEncode.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClCompile Include="COM\Com_CdcDecode.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClInclude Include="COM\Com_CdcDecode.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClCompile Include="DRI\Dri_Cdc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClInclude Include="DRI\Dri_Cdc.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,19 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include "DRI/Dri_Cdc.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Dri_Cdc_Struct_Port Port;
Dri_Cdc_Struct_InitConfig Config;
Config.BaudRate = 115200;
const bool IsInitOk = Dri_Cdc_Init(&Port, Config);
qDebug() << "Dri_Cdc_Init =" << IsInitOk;
Dri_Cdc_Deinit(&Port);
return 0;
}

View File

@@ -0,0 +1,305 @@
# KeyBorad Protobuf 对齐步骤
这份步骤是按当前工程目录写的,目标是:
1. 保留当前 CDC 传输帧: `AA 55 + len + type + data + checksum`
2.`data` 改成 protobuf 二进制载荷
3. 上位机使用 protobuf C++
4. 下位机使用 nanopb
## 1. 先确定边界
当前工程里三层职责不要打乱:
- `KeyBorad\KeyBorad\DRI\Dri_Cdc.*`
作用: 串口枚举、握手、收发字节流
- `KeyBorad\KeyBorad\COM\Com_CdcEncode.*`
作用: 把一帧 CDC 包组出来
- `KeyBorad\KeyBorad\COM\Com_CdcDecode.*`
作用: 从 CDC 字节流缓存里拆出一帧
引入 protobuf 以后,建议职责变成:
- `DRI`
只管串口
- `COM`
只管外层帧
- `proto`
只管 `data` 的结构定义
也就是说:
- `type` 继续保留在外层帧里
- `data` 改为 protobuf 序列化结果
- `len` 改为 protobuf 载荷实际长度
## 2. 当前已经准备好的文件
本次已经加了两份协议源文件:
- `KeyBorad\proto\keyboard.proto`
- `KeyBorad\proto\keyboard.options`
其中:
- `keyboard.proto`
定义了 `HelloReqPayload / HelloRspPayload / BitmapPayload / FunctionKeyEventPayload / TimeSyncPayload` 等消息
- `keyboard.options`
`nanopb``bytes usage_bitmap` 指定了 `max_size:29`
## 3. 下载工具
### 上位机 protobuf
按 protobuf 官方文档,`protoc``--cpp_out` 生成 C++ 代码。
你需要准备:
- `protoc.exe`
- protobuf C++ runtime 头文件和库
官方参考:
- protobuf C++ generated code guide
- protobuf releases / protoc binary
### 下位机 nanopb
你需要准备:
- `nanopb` runtime
`pb.h`, `pb_common.c/h`, `pb_encode.c/h`, `pb_decode.c/h`
- `nanopb_generator.py`
官方参考:
- nanopb overview
- nanopb generator reference
## 4. 生成代码
### 4.1 生成上位机 C++ 代码
在工程根目录 `C:\Users\lst\Desktop\动态链接库版本\20260320_new_keyboard` 下执行:
```powershell
New-Item -ItemType Directory -Force -Path KeyBorad\generated\cpp | Out-Null
protoc --proto_path=KeyBorad\proto --cpp_out=KeyBorad\generated\cpp KeyBorad\proto\keyboard.proto
```
预期生成:
- `KeyBorad\generated\cpp\keyboard.pb.h`
- `KeyBorad\generated\cpp\keyboard.pb.cc`
### 4.2 生成下位机 nanopb 代码
如果你是 nanopb 源码目录:
```powershell
New-Item -ItemType Directory -Force -Path KeyBorad\generated\nanopb | Out-Null
python third_party\nanopb\generator\nanopb_generator.py --output-dir=KeyBorad\generated\nanopb --strip-path KeyBorad\proto\keyboard.proto
```
如果你用的是 nanopb 二进制包,自带 generator也可以直接换成对应可执行文件。
预期生成:
- `KeyBorad\generated\nanopb\keyboard.pb.h`
- `KeyBorad\generated\nanopb\keyboard.pb.c`
## 5. 先做一处关键改造
### 必改: 不再把 payload 长度当成固定值
一旦 `data` 改成 protobuf`HelloRsp / TimeSync / Ack``data` 长度就不一定再等于今天的固定字节数。
所以迁移时要先把 `COM` 层规则改成:
- `len` 表示 protobuf 载荷实际字节数
- `Com_CdcDecode` 不再用 `type -> 固定 len` 做强校验
- 改成:
- 校验帧头
- 校验 `len`
- 校验 checksum
- 可选校验某个 `type` 的最大长度,不再校验固定长度
建议做法:
- 先保留 `Packet_Type`
- 删除或弱化 `Packet_len` 在解码期的“硬匹配”
- `Packet_len` 只保留给旧协议兼容或做上限参考
## 6. 上位机接入方式
### 6.1 在 VCXPROJ 里加入生成文件
把下面文件加入 `KeyBorad\KeyBorad\KeyBorad.vcxproj`:
- `..\generated\cpp\keyboard.pb.cc`
- `..\generated\cpp\keyboard.pb.h`
并加 include 目录:
- `KeyBorad\generated\cpp`
- protobuf runtime include 目录
再链接 protobuf C++ runtime 库。
### 6.2 改造原则
不要再在 `Com_Cdc.h` 里维护两份字段和 `data`
建议改成两层:
- 原始帧:
`Packet`
- protobuf payload:
`keyboard::cdc::HelloReqPayload` 等生成类
### 6.3 发送流程
`HelloReq` 为例:
1. 创建 `keyboard::cdc::HelloReqPayload`
2. 填字段
3. `SerializeToString()` / `SerializeToArray()`
4. 把序列化结果塞进 `Packet.data`
5. `Packet.type = Com_Type_HelloReq`
6.`Com_Cdc_Func_BuildFrame()` 打外层包
伪代码:
```cpp
keyboard::cdc::HelloReqPayload payload;
payload.set_protocol_version(1);
std::string bytes;
payload.SerializeToString(&bytes);
Packet frame;
frame.type = Com_Type_HelloReq;
frame.data = QByteArray(bytes.data(), static_cast<int>(bytes.size()));
QByteArray tx = Com_Cdc_Func_BuildFrame(frame);
```
### 6.4 接收流程
1. `Dri_Cdc_Read()` 读到完整 CDC 帧
2. `Com_Cdc_Func_ParseFrame()` 拿到 `Packet`
3. 根据 `Packet.type` 选择 protobuf 消息类型
4.`Packet.data``ParseFromArray()`
伪代码:
```cpp
Packet frame;
if (!Com_Cdc_Func_ParseFrame(frameBytes, &frame))
{
return false;
}
if (frame.type == Com_Type_HelloRsp)
{
keyboard::cdc::HelloRspPayload payload;
if (!payload.ParseFromArray(frame.data.constData(), frame.data.size()))
{
return false;
}
}
```
## 7. 下位机接入方式
下位机不要碰外层 CDC 帧格式,直接沿用:
- 帧头
- 长度
- type
- checksum
只把 `type` 对应的 `data` 改成 nanopb 编解码。
建议流程:
1. 串口 ISR / DMA 收满一帧
2. 校验 `AA 55 / len / checksum`
3. 根据 `type``keyboard.pb.h` 里的消息描述符
4. `pb_decode()` 解 protobuf 载荷
5. 业务处理
6. 回复时 `pb_encode()` 出 payload再挂回外层 CDC 帧
## 8. 推荐迁移顺序
建议按下面顺序,不要一次全换:
1. 只提交 `.proto``.options`
2. 上位机先接 `HelloReq / HelloRsp`
3. 下位机把 `HelloReq / HelloRsp` 改成 nanopb
4. 确认握手通了,再迁移 `Ack / Error`
5. 然后迁移 `TimeSync / ThemeRgb`
6. 最后迁移 `Bitmap / FunctionKeyEvent`
原因:
- `HelloReq / HelloRsp` 字段简单,最适合先打通生成链路
- `Bitmap``bytes[29]`,更适合在 `nanopb` 选项验证完后再切
## 9. 你当前 COM 文件还能起什么作用
即使引入 protobuf当前 `COM` 层仍然非常有价值:
- `Com_CdcEncode`
继续负责外层帧打包
- `Com_CdcDecode`
继续负责流式拆包
- `Packet_Type`
继续作为外层快速分发 ID
真正被 protobuf 替代的是:
- 旧的 `data` 手写字段布局
- 手工小端拼 payload 的代码
一句话:
- `COM` 继续保留
-`COM` 不再定义业务 payload 字段
- payload 交给 `.proto`
## 10. 一套最小可复现命令
假设你已经装好 `protoc``nanopb`,最小步骤就是:
```powershell
Set-Location C:\Users\lst\Desktop\动态链接库版本\20260320_new_keyboard
New-Item -ItemType Directory -Force -Path KeyBorad\generated\cpp | Out-Null
protoc --proto_path=KeyBorad\proto --cpp_out=KeyBorad\generated\cpp KeyBorad\proto\keyboard.proto
New-Item -ItemType Directory -Force -Path KeyBorad\generated\nanopb | Out-Null
python third_party\nanopb\generator\nanopb_generator.py --output-dir=KeyBorad\generated\nanopb --strip-path KeyBorad\proto\keyboard.proto
```
然后做两边接入:
1. 上位机 `vcxproj` 加入 `keyboard.pb.cc`
2. 下位机工程加入 `keyboard.pb.c`
3.`HelloReq / HelloRsp``data` 改为 protobuf payload
4.`Com_CdcDecode` 的固定长度校验去掉
## 11. 最后一个关键建议
如果你只是想“共享结构定义”,那就:
- 保留外层 `type`
- 每个 `type` 对应一个 protobuf message
不要一开始就把外层 `type` 也塞进 protobuf `oneof` 里。
原因很简单:
- 外层 `type` 对调试串口抓包很直观
- 下位机分发也更省
- 迁移成本最小

View File

@@ -0,0 +1 @@
BitmapPayload.usage_bitmap max_size:29

View File

@@ -0,0 +1,83 @@
syntax = "proto3";
package keyboard.cdc;
// The AA55 CDC frame still lives outside protobuf:
// [head1][head2][len][type][data][checksum]
// `data` is the serialized protobuf payload for the matching `type`.
// Keep the outer `type` field for fast dispatch and debugging.
enum CdcPacketType {
CDC_PACKET_TYPE_UNKNOWN = 0;
CDC_PACKET_TYPE_HELLO_REQ = 1;
CDC_PACKET_TYPE_HELLO_RSP = 2;
CDC_PACKET_TYPE_BITMAP = 16;
CDC_PACKET_TYPE_FUNCTION_KEY_EVENT = 32;
CDC_PACKET_TYPE_LED_STATE = 33;
CDC_PACKET_TYPE_TIME_SYNC = 48;
CDC_PACKET_TYPE_THEME_RGB = 49;
CDC_PACKET_TYPE_ACK = 126;
CDC_PACKET_TYPE_ERROR = 127;
}
enum KeyAction {
KEY_ACTION_RELEASE = 0;
KEY_ACTION_PRESS = 1;
}
enum ErrorCode {
ERROR_CODE_NONE = 0;
ERROR_CODE_UNKNOWN_TYPE = 1;
ERROR_CODE_INVALID_LENGTH = 2;
ERROR_CODE_INVALID_PARAM = 3;
ERROR_CODE_NOT_READY = 4;
}
message HelloReqPayload {
uint32 protocol_version = 1;
}
message HelloRspPayload {
uint32 protocol_version = 1;
uint32 vendor_id = 2;
uint32 product_id = 3;
uint32 firmware_major = 4;
uint32 firmware_minor = 5;
uint32 capability_flags = 6;
}
message BitmapPayload {
bytes usage_bitmap = 1;
}
message FunctionKeyEventPayload {
uint32 usage = 1;
KeyAction action = 2;
}
message LedStatePayload {
uint32 led_mask = 1;
}
message TimeSyncPayload {
uint32 version = 1;
uint32 flags = 2;
sint32 timezone_min = 3;
fixed64 utc_ms = 4;
fixed32 accuracy_ms = 5;
}
message ThemeRgbPayload {
uint32 red = 1;
uint32 green = 2;
uint32 blue = 3;
}
message AckPayload {
uint32 acked_type = 1;
}
message ErrorPayload {
uint32 error_type = 1;
ErrorCode error_code = 2;
}