1.概述
串口通信是一种非常常用的通信方式,本文首先介绍了串口硬件和协议的相关内容,然后给出一个STM32与C#上位机通过串口通信的示例。
2.串口介绍
参考这份文档
3.通信协议
为了方便数据传输,定义了一个简单的通信协议。利用该协议可以传输不定长数据帧,另外还加入了累加和校验,可以识别出传输过程中的错误。数据帧格式如下:
数据帧由数据帧头、数据长度、数据、累加和组成,比如数据帧(0xFF 0x02 0x05 06 0x0C)解析为:
在发送方发送数据时把要发的数据和数据帧头、数据长度、累加和打包到一起,如果接收方比对发现校验和正确就提取出数据帧中的数据部分。定义了数据帧,就可以在上面定义通信协议了。在本例中,单片机中有4个参数(1-4),上位机发命令设置这些参数,单片机收到之后通过串口反馈出来。另外单片机还接了一个led,用上位机可以控制改led的亮灭。协议具体内容如下:
在上位机发送给单片机的数据中,第一字节代表了该帧数据的功能,比如Byte0=1时表示设置参数1,参数1从Byte1开始占4个字节。单片机发送的协议与此类似:
3.单片机实现
单片机用的是stm32f103rbt6(其他型号也都类似),开发环境是STM32CubeIDE 1.4.0,调试工具是Stlink V2。
3.1硬件实现
硬件借用了之前做的USBCAN加一个USB转串口模块,原理图如下:
3.2软件实现
配置为外部晶振,频率为8M,主频设为72M:
配置调试接口为stlink:
开启Timer2中断,中断周期为1ms:
配置串口为异步通信,波特率9600:
配置led引脚:
部分程序介绍如下:
void Uart1Transmit(uint8_t* data,int len)
{
uint8_t checkSum = 0;
memset(u1TxBuffer,0,len + 3);
u1TxBuffer[0] = 0xFF;
u1TxBuffer[1] = len;
checkSum+=u1TxBuffer[0];
checkSum+=u1TxBuffer[1];
for(int i=0;i<len;i++)
{
u1TxBuffer[2+i] = data[i];
checkSum+=data[i];
}
u1TxBuffer[len+2] = checkSum;
HAL_UART_Transmit_IT(&huart1, u1TxBuffer,len + 3);
}
uint32_t startByteIndex = 0;
static uint8_t buffer[500];
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
int headerIndex = 0;
uint8_t noHeader = 0;
uint8_t checkSum = 0;
uint8_t msgLength = 0;
if(huart->Instance==USART1)
{
memcpy(&buffer[startByteIndex],u1RxBuffer,Size);
result = HAL_UARTEx_ReceiveToIdle_IT(&huart1, u1RxBuffer,500);
if(result!=0)
{
__NOP();
}
do
{
noHeader = 1;
for(int i=headerIndex;i<(startByteIndex+Size);i++)
{
if(buffer[i] == 0xFF)
{
headerIndex = i;
noHeader = 0;
break;
}
}
if(noHeader == 1)
{
startByteIndex = 0;
break;
}
if((headerIndex+1) < (startByteIndex+Size))
{
msgLength = buffer[headerIndex+1];
}
else
{
memcpy(buffer,&buffer[headerIndex],startByteIndex + Size - headerIndex);
startByteIndex = startByteIndex + Size - headerIndex;
break;
}
if((headerIndex + msgLength + 3) <= (startByteIndex + Size))
{
checkSum = 0;
for(int i=0;i<msgLength + 2;i++)
{
checkSum += buffer[headerIndex+i];
}
if(checkSum == buffer[headerIndex + msgLength + 2])
{
gRxDataType.Unpack(&buffer[headerIndex+2],msgLength);
headerIndex = headerIndex + msgLength + 3;
}
else
{
startByteIndex = 0;
break;
}
}
else
{
memcpy(buffer,&buffer[headerIndex],startByteIndex + Size - headerIndex);
startByteIndex = startByteIndex + Size - headerIndex;
break;
}
}while(1);
}
}
4.上位机实现
上位机采用C#语言开发,开发环境为VS2017社区版。界面设计如下:
private void timer_uartReceive_Tick(object sender, EventArgs e)
{
byte[] data;
int dataSize = 0;
int msgLength = 0;
byte noHeaderFlag = 0;
byte checkSum = 0;
int headerIndex = 0;
if (serialPort.IsOpen)
{
result = serialPort.Receive(out data);
if(result == 0)
{
dataSize = data.Length;
if(dataSize > 0)
{
dataBuffer.AddRange(data);
while(true)
{
noHeaderFlag = 1;
for (int i = headerIndex; i < (startByteIndex + dataSize); i++)
{
if (dataBuffer[i] == 0xFF)
{
headerIndex = i;
noHeaderFlag = 0;
break;
}
}
if (noHeaderFlag == 1)
{
startByteIndex = 0;
dataBuffer.Clear();
break;
}
if((headerIndex + 1) < dataBuffer.Count)
{
msgLength = dataBuffer[headerIndex + 1];
}
else
{
dataBuffer.RemoveRange(0, headerIndex);
startByteIndex = startByteIndex + dataSize - headerIndex;
break;
}
if ((headerIndex + msgLength + 3) <= (startByteIndex + dataSize))
{
checkSum = 0;
for (int i = 0; i < msgLength + 2; i++)
{
checkSum += dataBuffer[headerIndex + i];
}
if (checkSum == dataBuffer[headerIndex + msgLength + 2])
{
byte[] recData;
result = Packet.UnPack(dataBuffer.Skip(0).Take(msgLength + 3).ToArray<byte>(), out recData);
if (result == 0)
{
result = protocol.Parse(recData);
}
else
{
}
headerIndex = headerIndex + msgLength + 3;
}
else
{
startByteIndex = 0;
dataBuffer.Clear();
break;
}
}
else
{
dataBuffer.RemoveRange(0, headerIndex);
startByteIndex = startByteIndex + dataSize - headerIndex;
break;
}
}
}
}
}
}
5.效果展示
运行单片机程序和上位机程序,修改参数后单片机能正确反馈参数,并且可以控制led,下方的记录区也能实时显示出发送的报文。
6.相关资源
完整STM32工程:https://download.csdn.net/download/m0_37782115/33372085
完整C#工程:https://download.csdn.net/download/m0_37782115/33372046
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)