自己做了一个项目,需要利用电脑发送一个精确的角度数据到单片机,单片机接收这一串数据并将拆分的角度重新合并,控制电机运转这个信号指定的角度。由于串口是8位的,因此在串口中传输的数据只能为8位即C++中的char类型(取值只能为整数0~255),以下我写了一个类来完成这项工作,该类可以发送单字节数据,也可以发送一串数据,程序利用Visual Studio 2019编写。
由于串口是8位的,若想要串口传递更多的信息,可以采取数据包的形式发送。对于单片机控制来说,串口数据包可以为固定长度的一串字符(如:'@' 'a' 'b' 'c' '&'),这些字符依次发送出来,为了让这一串字符传递更多的信息,每一位可以有不同的含义,以下是我自己编写时使用的一种方法,对于我制造的单片机控制系统很适用。
1.数据包的数据头、数据尾
数据包的头、尾可以作为校验字符,以校验数据是否正确,还可以告诉单片机是否开始接收数据。例如定义数据头为字符:'@';数据尾为字符:'&',这样单片机通过检测数据头和数据尾即可判断该数据包是否是正常的,否则将该组数据作废。数据头和数据尾是哪几个字符可以自己任意选取,例如我编写的系统中,发送的角度数据被拆分为了度、分、秒、小秒(1/60秒),因此除了“度”之外,数据包其余所有位中的数据均不大于60,因此我的数据头和数据尾可以选用ASCII码表中对应的十进制数据大于60的字符,这里我选择了数据头为字符:'x'(对应十进制:120),数据尾为字符:'y'(对应十进制:121)。
2.数据包中的数据
数据包中的数据可自己定义,我定义的方法是:第1位为电机运转方向和速度位;第2~6为运行角度。单片机接收到正确的数据包后,将角度位重新合并为一个单位,输出指定个数的脉冲。
3.单片机串口收发机制
单片机接收数据包可以使用状态机方法,具体为:
STEP1:等待数据头 | 持续判断串口接收的数据,若为定义的数据头,则进入第二步。 |
STEP2:接收数据 | 单片机开始计数,每收到一个数据则计数加一,直到收到定义的数据长度个数据。 |
STEP3:等待数据尾以及校验 | 判断串口接收的最后一个数据是否为定义的数据尾,如果接收到数据尾则处理数据,否则销毁数据。 |
头文件:serial_writer.h
#pragma once
#include"windows.h"
#include <TCHAR.H>
#include <string.h>
#include <iostream>
#include "atlstr.h"
#include <vector>
class serial_writer
{
public:
/** @brief 设置串口参数。
@param port_name 串口名称,请打开<设备管理器>查找电脑连接单片机的串口,如令port_name = "COM5";
@param baud_rate 波特率。
*/
void set_serial(CString port_name, int baud_rate = 9600);
/** @brief 发送单字节数据,调用该函数立即发送
@param input_data_1_byte 字符型(8位)数据。
*/
void send_data(char input_data_1_byte);
/** @brief 发送一个数据包,调用该函数立即发送,从input_data[0]开始发送。
@param input_data 待发送的字符容器。
*/
void send_data(std::vector<char> input_data);
private:
LPCWSTR COM_port = _T("COM3");
HANDLE hCom;
int baud_rate = 9600;
int serial_open(LPCWSTR COMx, int BaudRate);
int serial_write(char lpOutBuffer[]);
void Serial_close(void);
};
源文件:serial_writer.cpp
#include "serial_writer.h"
void serial_writer::set_serial(CString port_name, int baud_rate) {
this->COM_port = port_name.AllocSysString();
this->baud_rate = baud_rate;
}
void serial_writer::send_data(char input_data_1_byte) {
bool serial_open = FALSE;
serial_open = this->serial_open(this->COM_port, this->baud_rate);
if (serial_open) {
this->serial_write(&input_data_1_byte);
this->Serial_close();
}
}
void serial_writer::send_data(std::vector<char> input_data) {
bool serial_open = FALSE;
int i;
serial_open = this->serial_open(this->COM_port, this->baud_rate);
if (serial_open) {
for (i = 0; i < input_data.size(); i++) {
this->serial_write(&input_data[i]);
}
this->Serial_close();
}
}
int serial_writer::serial_open(LPCWSTR COMx, int BaudRate) {
this->hCom = CreateFile(COMx, //COM1口
GENERIC_READ | GENERIC_WRITE, //允许读和写
0, //独占方式
NULL,
OPEN_EXISTING, //打开而不是创建
0, //重叠方式FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED (同步方式设置为0)
NULL);
if (this->hCom == INVALID_HANDLE_VALUE)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED);
std::cout << "------------------------- 无法打开串口! -------------------------" << std::endl;
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY |
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
std::cout << std::endl;
return FALSE;
}
SetupComm(this->hCom, 1024, 1024); //输入缓冲区和输出缓冲区的大小都是1024
//设定读写超时
/*COMMTIMEOUTS TimeOuts;
TimeOuts.ReadIntervalTimeout=1000;
TimeOuts.ReadTotalTimeoutMultiplier=500;
TimeOuts.ReadTotalTimeoutConstant=5000; //设定写超时
TimeOuts.WriteTotalTimeoutMultiplier=500;
TimeOuts.WriteTotalTimeoutConstant = 2000;
SetCommTimeouts(hCom, &TimeOuts); //设置超时
*/
DCB dcb;
GetCommState(this->hCom, &dcb);
dcb.BaudRate = BaudRate; //设置波特率为BaudRate
dcb.ByteSize = 8; //每个字节有8位
dcb.Parity = NOPARITY; //无奇偶校验位
dcb.StopBits = ONESTOPBIT; //一个停止位
SetCommState(this->hCom, &dcb); //设置参数到hCom
PurgeComm(this->hCom, PURGE_TXCLEAR | PURGE_RXCLEAR);//清空缓存区 //PURGE_TXABORT 中断所有写操作并立即返回,即使写操作还没有完成。
//PURGE_RXABORT 中断所有读操作并立即返回,即使读操作还没有完成。
//PURGE_TXCLEAR 清除输出缓冲区
//PURGE_RXCLEAR 清除输入缓冲区
return TRUE;
}
int serial_writer::serial_write(char lpOutBuffer[]) {//同步写串口
DWORD dwBytesWrite = sizeof(lpOutBuffer);
COMSTAT ComStat;
DWORD dwErrorFlags;
BOOL bWriteStat;
ClearCommError(this->hCom, &dwErrorFlags, &ComStat);
bWriteStat = WriteFile(this->hCom, lpOutBuffer, dwBytesWrite, &dwBytesWrite, NULL);
if (!bWriteStat)
{
//printf("写串口失败!\n");
return FALSE;
}
PurgeComm(this->hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
return TRUE;
}
void serial_writer::Serial_close(void){//关闭串口
CloseHandle(this->hCom);
}
示例代码:
void main()
{
serial_writer Serial_writer;
std::vector<char> input_array;
/*发送一个数据------------------------------------------------------------------------*/
Serial_writer.set_serial("COM3", 9600);//设置发送端口名称为:COM3;设置发送波特率为:9600
Serial_writer.send_data('a');//发送1字节数据:'a'
/*发送数据包------------------------------------------------------------------------*/
for (int i = 0; i < 5; i++) {
input_array.push_back('b');
}
Serial_writer.send_data(input_array);//发送5字节数据,从低到高依次为:'b'、'b'、'b'、'b'、'b'
}