自定义串口通信协议
包头|数据 4(1+1+2) 2 地址码(1byte)| 功能码(1byte) | 数据长度(2byte)| 数据(不定长) | 校验码(2byte) 总线通信: 一条信号线上挂载了很多主机,地址码标识给谁发的 SPI、串口、I2C给下位机发送消息,可能不仅仅一个功能,标识功能256(0-255) 数据长度描述数据长度 定长的可以使用宏定义
home.h
#ifndef HOME_H
#define HOME_H
#include <QMainWindow>
#include <QSerialportinfo.h>
#include <QSerialPort>
/*
用户自定义串口通信协议:
包头|数据 4(1+1+2) 2
地址码(1byte)| 功能码(1byte) | 数据长度(2byte)| 数据(不定长) | 校验码(2byte)
总线通信:
一条信号线上挂载了很多主机,地址码标识给谁发的
SPI、串口、I2C
给下位机发送消息,可能不仅仅一个功能,标识功能256(0-255)
数据长度描述数据长度
定长的可以使用宏定义
*/
#define PACKAGE_HEAD_LEN 4
#define CHECK_LEN 2
typedef struct Package//C++无需重命名
{
uint8_t addr;
uint8_t function;
uint16_t dataLen;
uint8_t data[];//柔性数组(指针)
} package_t;
namespace Ui {
class Home;
}
class Home : public QMainWindow
{
Q_OBJECT
public:
explicit Home(QWidget *parent = 0);
~Home();
void sendPackage(package_t *pg);
void serialInitAndOpen();
public slots:
void slotRecvSerialData();
private slots:
void on_pushButtonOpenSerial_clicked();
void on_spinBox_1_valueChanged(int arg1);
private:
Ui::Home *ui;
QList<QSerialPortInfo> serialList;
QSerialPort *serialPort;
};
#endif // HOME_H
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "home.h"
#include <QMovie>
#include <QLabel>
#include <QPaintEvent>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void MovieInit();
protected:
void paintEvent(QPaintEvent *pe);
private slots:
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
Home *h;
QLabel *label;
QMovie *movie;
};
#endif // MAINWINDOW_H
home.cpp
#include "home.h"
#include "ui_home.h"
#include <QDebug>
const uint16_t CRC16Table[256] = {
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
};
/*
* 函数原型:uint16_t dataVerificationCRC16(const uint8_t *data, int len)
* 函数作用:生成本地crc16校验算法
*/
uint16_t dataVerificationCRC16(const uint8_t *data, int len)
{
int i;
uint16_t result =0;
for (i = 0; i < len; i++)
{
result = (result << 8) ^ CRC16Table[(result >> 8) ^ data[i]];
}
return result;
}
Home::Home(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Home),
serialPort(new QSerialPort(this))//分配内存空间
{
ui->setupUi(this);
this->setWindowTitle("Home");
// 信号与槽的手动连接,serialPort对象中的readyRead信号去触发以后slotRecvSerialData函数
connect(serialPort, &QSerialPort::readyRead,this,&Home::slotRecvSerialData);
serialInitAndOpen();
}
Home::~Home()
{
delete ui;
}
void Home::sendPackage(package_t *pg)
{
uint16_t localCRC = dataVerificationCRC16((uint8_t *)pg, PACKAGE_HEAD_LEN + pg->dataLen);
//
pg->data[pg->dataLen] = localCRC;
pg->data[pg->dataLen + 1] = localCRC >> 8;
serialPort->write((char *)pg, PACKAGE_HEAD_LEN + pg->dataLen + CHECK_LEN);
if(pg != NULL)
{
free(pg);
pg = NULL;
}
}
void Home::serialInitAndOpen()
{
serialList = QSerialPortInfo::availablePorts();
for(int i = 0; i < serialList.size(); i++)
{
QString portName = serialList[i].portName();
QString description = serialList[i].description();
QString manufacturer = serialList[i].manufacturer();
qDebug() << portName << description << manufacturer;
if (portName == "COM1")
{
// 设置串口的 端口、波特率、数据位、奇偶校验位、停止位、流量控制
serialPort->setPortName(portName);
serialPort->setBaudRate(QSerialPort::Baud9600);
serialPort->setDataBits(QSerialPort::Data8);
serialPort->setParity(QSerialPort::NoParity);
serialPort->setStopBits(QSerialPort::OneStop);
serialPort->setFlowControl(QSerialPort::NoFlowControl);
// 检测串口是否打开
if(serialPort->isOpen())
{
serialPort->close();
ui->textBrowser->insertPlainText("serialport close...\n");//无格式要自动换行
}
// 打开串口
if(serialPort->open(QIODevice::ReadWrite))//虚函数
{
ui->textBrowser->insertPlainText("serialport open success!\n");
}
else
{
ui->textBrowser->insertPlainText("serialport open failure!\n");
}
return ;
}
}
ui->textBrowser->insertPlainText("没有符合调试的串口!\n");
}
void Home::slotRecvSerialData()
{
qDebug() << serialPort->readAll();
}
void Home::on_pushButtonOpenSerial_clicked()
{
serialInitAndOpen();
}
void Home::on_spinBox_1_valueChanged(int arg1)
{
qDebug() << "arg1:" << arg1;
//serialPort->write((char *)&arg1, sizeof(arg1));//内存多大就读多少字节
package_t *pg = (package_t *)malloc(PACKAGE_HEAD_LEN + sizeof(arg1) + CHECK_LEN);
pg->addr = 0x00;
pg->function = 0x01;// function 0x01-0x06 表示六个舵机的参数值(比较器的值)
pg->dataLen = sizeof(arg1);
memcpy(pg->data, &arg1, sizeof(arg1));
sendPackage(pg);
//serialPort->write((char *)pg, PACKAGE_HEAD_LEN + sizeof(arg1) + CHECK_LEN);
}
舵机参数设置
初始值为什么要1500?
驱动舵机(伺服电机):需要输入一个周期为20ms的方波信号。1.0/0.02=50Hz
stm32f103c8t6:最高工作频率是72MHz(72 000000)。
PWM(片上外设)(特殊功能定时器):将72MHz降为1MHz的输入频率。(1 000 000)(分频)
STM32F103C8T6的主时钟频率可达72MHz,但它的定时器可能无法以这么高的频率运行。通过降频,可以使定时器等外设工作在它们支持的最大频率范围内。
计数器:20000 对外输出:1 000 000 / 20000 = 50Hz 周期
比较器:1500 对外输出信号翻转 高电平输出的时间比例 舵机转动角度
500:0度 1000:45度 1500:90度 2000:135度 2500:180度
发送1520 接收到的是F0 05 00 00 (发送与接收相反)
十进制1520对应的是05F0
(HEX)。
在十六进制中,每个数字代表4位,因此0xF0和0x05各自是一个字节。
- 整数1520(0x05F0)在内存中的表示,按照小端序,是
F0 05
。 - 发送端将这个16位整数序列化为两个字节,并按照小端序发送出去,即先发送
F0
,再发送05
。
和串口没关系,串口把字节流按顺序发出,组织这个要发的数据(可以理解为字节数组)。
在内存中的存放是按字节“低位在前,高位在后”的顺序存放的。
先將记录串行化,然后再发,这样就与字节高低就没关系了。
CRC相关知识
void Home::sendPackage(package_t *pg)
{
uint16_t localCRC = dataVerificationCRC16((uint8_t *)pg, PACKAGE_HEAD_LEN + pg->dataLen);
//
pg->data[pg->dataLen] = localCRC;
pg->data[pg->dataLen + 1] = localCRC >> 8;
}
CRC校验码是两个字节,一个数组中一个下标对应一个字节,右移8位,完整存储CRC校验码。
标签:void,day2,serialPort,pg,串口,Home,include,参数设置 From: https://blog.csdn.net/qq_73472828/article/details/144984992