前言:
博主所选用上位机为匿名上位机v7,下位机为stm32,所用MCU为f103c8t6,进行一个简单的上位机显示数据的波形和对单片机指定参数的调试。
大概的思路为匿名上位机与stm32串口通信,在windows端匿名上位机绘制实时数据波形,并实现在遵循通信协议的前提下实现对下位机的某指定参数的修改,从而达到便捷调试的目的。
接线图:
匿名上位机接收stm32发送数据的通信帧格式:
stm32接收匿名上位机发送的数据通信帧格式:
帧头1 | 帧头2 | 目标调参id | 参数改变量的绝对值 | 正/负 | 帧尾 |
0xAA | 0xAE | 00 00 00 00 | 9F 06 00 00 | 0xBF | 0xAC |
固定值 | 固定值 | 01 00 00 00 | 将十进制转换成16进制,补足8位,两个位为一组,高低位互换。 | 0xCF | 固定值 |
02 00 00 00 | 0xBF为正增量 | ||||
0,1,2分别表示第1/2/3个参数 | 0xCF为负增量 | ||||
10进制1695转成16进制为00 00 06 9F高低位互换后为9F 06 00 00 |
帧头1: 0xAA
帧头2: 0xAE
指定参数id: 00 00 00 00 / 01 00 00 00 / 02 00 00 00 分别表示指参数variable_1,variable_2,variable_3
增量的绝对值: 9F 09 01 02 将十进制转换成16进制,补足8位,两个位为一组,高低位互换。
增量的正负: 0xBF / 0xCF 0xBF表示增量为正 0xCF表示增量为负
帧尾: 0xAC
Serial.h
#ifndef __SERIAL_H
#define __SERIAL_H
extern uint8_t Serial_RxPacket[];
void USART1_Init(void);
void UsartSendByte(uint8_t Byte);
void USART1_IRQHandler(void);
void sent_data(uint16_t A,uint16_t B,uint16_t C);
uint8_t Serial_GetRxFlag(void);
extern int variable_1,variable_2,variable_3;
#endif
Serial.c
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
//cup为小端模式存储,也就是在存储的时候,低位被存在0字节,高位在1字节
#define BYTE0(dwTemp) (*(char *)(&dwTemp)) //取出int型变量的低字节
#define BYTE1(dwTemp) (*((char *)(&dwTemp) + 1)) // 取存储在此变量下一内存字节的内容,高字节
#define BYTE2(dwTemp) (*((char *)(&dwTemp) + 2))
#define BYTE3(dwTemp) (*((char *)(&dwTemp) + 3))
#define data_length 8
uint8_t receive_data[data_length+1];
uint8_t Serial_RxFlag; //定义接收数据包标志位
int variable_id; //指定需要改变的变量
int variation; //指定改变量大小
int variable_1,variable_2,variable_3;
uint8_t BUFF[30];
//windows端匿名上位机发送数据格式 AA AE 00 00 00 00 A6 09 00 00 BF/CF AC
//AA AE固定表示为两个帧头
//00 00 00 00表示指定改变variable_1
//01 00 00 00表示指定改变variable_2
//02 00 00 00表示指定改变variable_3
//A6 09 00 00表示改变量的绝对值variation 可以通过十进制转十六进制,两位一对,高低位互换获得
//exampe: 十进制2470 --> 十六进制 9a6
//补零+大写后为 00 00 09 A6
//高低位互换为 A6 09 00 00
// BF表示变化量为正 CF表示变化量为负
// AC表示帧尾
void USART1_Init(void)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = 9600;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART1, ENABLE); //使能串口1
}
/**
* 函 数:串口发送一个字节
* 参 数:Byte 要发送的一个字节
* 返 回 值:无
*/
void UsartSendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
void sent_data(uint16_t A,uint16_t B,uint16_t C)
{
int i;
uint8_t sumcheck = 0;
uint8_t addcheck = 0;
uint8_t _cnt=0;
BUFF[_cnt++]=0xAA; //帧头
BUFF[_cnt++]=0xFF; //目标地址
BUFF[_cnt++]=0XF1; //功能码
BUFF[_cnt++]=0x06; //数据长度
BUFF[_cnt++]=BYTE0(A); //数据内容,小段模式,低位在前
BUFF[_cnt++]=BYTE1(A); //需要将字节进行拆分,调用上面的宏定义即可。
BUFF[_cnt++]=BYTE0(B);
BUFF[_cnt++]=BYTE1(B);
BUFF[_cnt++]=BYTE0(C);
BUFF[_cnt++]=BYTE1(C);
//SC和AC的校验
for(i=0;i<BUFF[3]+4;i++)
{
sumcheck+=BUFF[i];
addcheck+=sumcheck;
}
BUFF[_cnt++]=sumcheck;
BUFF[_cnt++]=addcheck;
for(i=0;i<_cnt;i++)
{
UsartSendByte(BUFF[i]); //串口逐个发送数据
}
}
/**
* 函 数:获取串口接收数据包标志位
* 参 数:无
* 返 回 值:串口接收数据包标志位,范围:0~1,接收到数据包后,标志位置1,读取后标志位自动清零
*/
uint8_t Serial_GetRxFlag(void)
{
if (Serial_RxFlag == 1) //如果标志位为1
{
Serial_RxFlag = 0;
return 1; //则返回1,并自动清零标志位
}
return 0; //如果标志位为0,则返回0
}
/**
* 函 数:USART1中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0; //定义表示当前状态机状态的静态变量
static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
uint8_t RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
/*使用状态机的思路,依次处理数据包的不同部分*/
/*当前状态为0,接收数据包包头*/
if (RxState == 0)
{
if (RxData == 0xAA) //如果数据确实是包头
{
RxState = 1; //置下一个状态 //数据包的位置归零
}
}
/*当前状态为1,接收数据包数据*/
else if (RxState == 1)
{
if (RxData == 0xAE)
{
RxState = 2;
pRxPacket = 0;
}
}
/*当前状态为2,接收数据包包尾*/
else if (RxState == 2)
{
receive_data[pRxPacket] = RxData; //将数据存入数据包数组的指定位置
pRxPacket ++; //数据包的位置自增
if (pRxPacket >= 9) //如果收够9个数据
{
RxState = 3; //置下一个状态
}
}
else if(RxState == 3)
{
if (RxData == 0xAC) //如果数据确实是包尾部
{
RxState = 0; //状态归0
Serial_RxFlag = 1; //接收数据包标志位置1,成功接收一个数据包
}
}
variable_id = receive_data[3] << 24 | receive_data[2] << 16 | receive_data[1] << 8 | receive_data[0];//位移运算,将hex16进制转换成int整型
if(receive_data[data_length] == 0xBF)
{
variation = (receive_data[7] << 24 | receive_data[6] << 16 | receive_data[5] << 8 | receive_data[4]);
}
else if(receive_data[data_length] == 0xCF)
{
variation = -(receive_data[7] << 24 | receive_data[6] << 16 | receive_data[5] << 8 | receive_data[4]);
}
if(variable_id == 0)
{
variable_1 = variation;//00 00 00 00 -> variable_1
}
else if(variable_id == 1)
{
variable_2 = variation;//01 00 00 00 -> variable_2
}
else if(variable_id == 2)
{
variable_3 = variation;//02 00 00 00 -> variable_3
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除标志位
}
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
USART1_Init(); //串口初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "Var1:");
OLED_ShowString(2, 1, "Var2:");
uint16_t A=0, B=2;
uint16_t C;
while (1)
{
C = A + B;
sent_data(A,B,C);
Delay_ms(100);
A = variable_1;
B = variable_2;
OLED_ShowSignedNum(1,5,variable_1,8);
OLED_ShowSignedNum(2,5,variable_2,8);
}
}
匿名上位机:
1.连接设置
选择所用到的串口号,波特率设置为9600
2.使能F1用户帧,传输缩放设置为1,数据类型和stm32发送的数据类型保持一致uint16
3.打开数据波形显示
总结
以上就是全部内容,包括四轴匿名上位机的使用、通信协议和一些接收发送代码。
若有不当之处,欢迎指出。
stm32发送数据匿名上位机接收参考:匿名上位机V7波形显示教程-简单能用-CSDN博客
上位机发送数据stm32接收参考视频:【STM32入门教程-2023版 细致讲解 中文字幕】 https://www.bilibili.com/video/BV1th411z7sn/?p=29&share_source=copy_web&vd_source=9ec6c39a21d0abd9b95bab21aa05a11d