STM32F103数据通过WIFI模块传输以服务器为中转并使用PC端接收

背景条件:

在准备电赛的时候,速成了一下STM32,我用的HAL库,而且之前有一些基础,入门比较快。想着找一些题目来做,来提升自己的开发的能力,考虑到最近一直在搞串口,顺便题目的一个附加功能实现一下。

题目的具体描述是这样的,把单片机的数据通过WIFI模块传给服务器,然后服务器再把数据发送给电脑并绘制成图像,X轴的数据是确定的,这里为了简单实现起见,我X轴的数据都设置的比较简单,只需要传送y轴的数据即可。

思路与硬件选择:

WIFI模块与服务器都是用的esp32,其中一个esp32作为客户端,用UART2硬串口接收STM32F103的串口数据,然后经过数据检验传给服务器的esp32,服务器的esp32把接收到的数据转成浮点数发给PC端,然后PC端直接绘制就很方便了,esp32都是使用Arduino进行开发。PC端这边我用的是python进行开发,因为python做起来比较简单。

各部分代码:

STM32F103部分:

由于我用的是HAL库进行开发,所以我一些配置直接在CubeMX勾勾勾就搞定了,我用的USART1来进行串口通信,而且我用的是HAL_UART_Transmit_IT()函数来异步串口通信,并不是阻塞传输。传输的是10个浮点数,

main.c代码片段:按键函数可以自己定义这里我用的原子哥的。

#include "uarttoesp32.h"
#include "gpio.h"
int main(void)
{
  float floatarr[10] = {2.34, 45.6, 44.1, 6.7, 23.3, 3.3, 9.8, 74.2, 11.2, 34.2};
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  while (1)
  {
     s = KEY_Scan(0);
     if (s == 1)
     {
         HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
         FloatFrameSend(floatarr);

     }
  }
}

这里的通信协议,比较简单因为我这边的通信距离比较短,信息失真率可以忽略,如果比较远的话可能要把数据封装更复杂一点

我的通信协议具体是这样的:

 uarttoesp32.c代码片段(这里仅给出一些关键的代码,初始化的可以自行改一下):

#include "uarttoesp32.h"

uint8_t TransmitCpl = 0;
uint8_t frame_send_buff[SEND_LEN] = {0};
uint8_t current_index = 1;
UART_HandleTypeDef huart1;        // USART句柄

uint8_t SumCheck(uint8_t index)                                                 // 数据和
{
    uint8_t sum_check = 0;
    for(uint8_t i=0; i<4;i++)
    {
        sum_check += frame_send_buff[index + i];
    }
    return sum_check;
}

void float_to_singleframe(float* data,uint8_t* buffer)                          // 把数据做成单帧
{   
    uint8_t *byte_arr;                                                          // 取数据的首地址,数据以字节存贮,看成一个字节数组
    byte_arr = (uint8_t*)data;
    for(uint8_t i=0; i<PRE_DATA_LEN; i++)
    {
        if (i<PRE_DATA_LEN - 1)     buffer[current_index+i] = byte_arr[i];                   // 前4位是数据,这种多字节存储是小端存储
        else                        buffer[current_index+i] = SumCheck(current_index);       // 最后一位是校验和
    }
    current_index += PRE_DATA_LEN;
}

void FloatFrameSend(float* data)                                      // float类型发送,中断发送
{
    frame_send_buff[0] = FRAME_HEAD;                                    // 先加个帧头
    for(uint8_t i=0;i<DATA_NUM;i++)                                     // 循环遍历数据,将帧加入数据域
    {
        float_to_singleframe(&data[i],frame_send_buff);       
    }
    frame_send_buff[END_POS] = FRAME_END;                               // 加个帧尾
    current_index = 1;
    HAL_UART_Transmit_IT(&huart1,frame_send_buff,SEND_LEN);             // 异步发送
}

uarttoesp32.h代码片段:

#ifndef __UARTTOESP32_H
#define __UARTTOESP32_H

#include "main.h"

#define FRAME_HEAD      0xFF        // 帧头
#define FRAME_END       0xDD        // 帧尾
#define PRE_DATA_LEN    5           // 每个数据的长度(加上校验和)
#define DATA_NUM        10          // 数据个数
#define DATA_AREA_LEN   (DATA_NUM*PRE_DATA_LEN)      // 数据域长度
#define END_POS         (DATA_AREA_LEN + 1)          // 帧尾位置
#define SEND_LEN        (DATA_AREA_LEN + 2)          // 发送帧的帧长度

void MX_USART1_UART_Init(void);

extern uint8_t TransmitCpl;
void USART1_IRQHandler(void);
void FloatFrameSend(float* data);

#endif

esp32客户端代码:

我用的是UART2串口,之前用的8266的软串口还有波特率限制,esp32就爽多了,用两根杜邦线把esp32的RX,TX跟STM32的PA9和PA10连接就好了,当客户端的esp32串口接收完数据之后就校验数据,数据是对的才会发送。用WIFI把数据传输给服务端的esp32,用的TCP连接,传输完就关闭,释放资源。

 

运行结果: 

#include <WiFi.h>
//#include <WiFiServer.h>
#include <Arduino.h>
/**************************定义***************************/
#define FRAME_LEN   52
#define DATA_NUM    10

/**********************声明变量***********************/
uint8_t frame[FRAME_LEN];
uint8_t current_index = 0;
uint8_t receive_done = 0;

const char* ssid = "ESP32";
const char* password = "12345678";
const char* serverIP = "192.168.1.1";          // Replace with your ESP32 server IP address
const int serverPort = 120;                    // Replace with your ESP32 server port
bool head_flag = false;                        // 帧头是否正确
bool end_flag = false;                         // 帧尾是否正确
bool sum_error = false;                        // 校验和是否全正确


WiFiClient espclient;

void setup() {
  Serial.begin(115200); // 初始化串口用于调试输出
  Serial2.begin(115200);  // 初始化UART2,波特率9600
  
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println();
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

}

void loop() {
  if (Serial2.available()) {                        // 检查是否有数据可读
    while (Serial2.available()) {                   // 持续读取串口信息
    if (current_index < FRAME_LEN){
      uint8_t bytes = Serial2.read();
      if (bytes == 0xFF){                           // 帧头是否正确
        head_flag = 1;
      }
      if (bytes == 0xDD){                           // 帧尾是否正确
        end_flag = 1;
      }
      if (head_flag){
        frame[current_index] = bytes;
      }
      current_index++;
      }
    }
    CheckSum();
    if (current_index == FRAME_LEN && end_flag && head_flag && !sum_error){         // 判断长度,帧头,帧尾是否正确,判断校验和是不是全对
      receive_done = 1;                                    // 可以发送标志位置1
    }
    current_index = 0;                                     // 清除标志位
    end_flag = 0;   head_flag = 0;   sum_error = 0;
  } 
  
  if (receive_done == 1){
    SendFrame();
  }
}

void CheckSum(){                                        // 校验和检验函数
  uint8_t sum = 0;
  for(uint8_t i=1; i<FRAME_LEN-2; i++){                 // 遍历数据域
     if(i%5 == 0){                                      // 到达校验和位置
       if(sum != frame[i])    sum_error = 1;            // 只要有校验和有一个出错,置1
       sum = 0;                                         // 归零
     }
     else{
      sum += frame[i];                                  // 未到达校验和位置,累加 
     }
  }
}

void printdata(){                                    // 数据打印函数
    if (receive_done==1){
    for(uint8_t i=0;i<FRAME_LEN;i++){
//    Serial.print("第"+String(i)+"位");
    Serial.print(frame[i],HEX);
    Serial.print(" ");
    }
    Serial.println(" ");
    receive_done = 0;
  }
}

esp32服务端代码:

用TCP连接接收客户端esp32传输的数据,同样地,需要检验数据是否正确,正确才会发送给PC端,而对于PC端的传输,是采用HTTP请求,当PC端来一次请求就发起一次,用的WebServer库。由于用的是HTTP请求,只能发字符串,我采用JSON格式来封装数据,提高数据的稳定性,所以也导入了ArduinoJson库。注意的是,TCP传输和Web传输两个使用不同的类,而且也要用不同的端口。

运行结果:

#include <ArduinoJson.h>
#include <WiFi.h>
#include <WebServer.h>
#include <WiFiServer.h>
#include <Arduino.h>
/************************定义****************************/
#define FRAME_LEN   52
#define DATA_NUM    10
/*******************变量声明****************************/
const char* ssid = "ESP32";
const char* password = "12345678";
//float  Xarray[] = {10,20,30,40,50,60,70,80,90,100};
int bytesRead = 0;                                    // 用于保存已读取的字节数
uint8_t recceive_frame[FRAME_LEN];                    // 接收的数据
float Yarray[DATA_NUM];
bool head_flag = false;                               // 帧头是否正确
bool end_flag = false;                                // 帧尾是否正确
bool sum_error = false;                               // 校验和是否全正确
bool data_correct = false;                            // 接收数据是否正确

WiFiServer server32(120);
WebServer server(80);                                 // 配置端口号

IPAddress local_ip(192,168,1,1);                      // 设置esp32网络参数,IP,以及子网掩码
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);



String jsondata()                                     // 数据JSON格式化
{
  String jsonStr;
  StaticJsonDocument<512> doc;
  // 将浮点型数组添加到 JSON 对象中
  JsonArray yArray = doc.createNestedArray("yData");
  for (int i = 0; i < DATA_NUM; i++) {
    yArray.add(Yarray[i]);
  }
  // 将 JSON 对象序列化为字符串
  serializeJson(doc, jsonStr);
  return jsonStr;
}

void setup() {
  Serial.begin(115200);                              // 配置串口波特率
  WiFi.softAP(ssid, password);                       // 开启AP模式,启动热点
  WiFi.softAPConfig(local_ip, gateway, subnet);      // 写入网络配置参数
  Serial.print("WiFi名称为::");
  Serial.println(ssid);
  Serial.print("IP地址为:: ");
  Serial.println(WiFi.softAPIP());
 
  //启动服务器
  server.begin();
  server32.begin();
  server.on("/data",HTTP_GET,handleRX);
  server.onNotFound(handleFound);
}

void loop() {                                                    // 主函数
  WiFiClient clientfrom32 = server32.available();                // esp32客户端请求
  if(clientfrom32){
    handleTransmit(clientfrom32);
  }
  server.handleClient();                                         // 处理URL请求
}

void bytes_to_float(){                                              // 字节数据转浮点型数据
  for (int i = 0; i < DATA_NUM; i++) {
    float floatValue;
    memcpy(&floatValue, &recceive_frame[i*5 + 1], sizeof(float));
    Yarray[i] = floatValue;
  }
}

void CheckSum(){                                                 // 校验和检验函数
  uint8_t sum = 0;
  for(uint8_t i=1; i<FRAME_LEN-2; i++){                          // 遍历数据域
     if(i%5 == 0){                                               // 到达校验和位置
       if(sum != recceive_frame[i])    sum_error = 1;            // 只要有校验和有一个出错,置1
       sum = 0;                                                  // 归零
     }
     else{
      sum += recceive_frame[i];                                  // 未到达校验和位置,累加 
     }
  }
}

void handleRX()                                                     // http请求处理函数
{
  String jst = "";
  jst = jsondata();
  Serial.println("有客户访问,");
  server.send(200,"text/plain",jst);

}

void handleTransmit(WiFiClient clientfrom32){               // 发送处理函数
  Serial.println("New client connected!");
  // Read data from the client and send it back
  while (clientfrom32.available()) {                        // 下标小于帧长
    if (bytesRead < FRAME_LEN) {
      uint8_t bytes = clientfrom32.read();
      if (bytes == 0xFF){                                   // 帧头是否正确
        head_flag = 1;
      }
      if (bytes == 0xDD){                                   // 帧尾是否正确
        end_flag = 1;
      }
      if (head_flag){
        recceive_frame[bytesRead] = bytes;
      }
      bytesRead++;
    }
  }
  
  CheckSum();
  if (bytesRead == FRAME_LEN && end_flag && head_flag && !sum_error){           // 判断长度帧头尾校验和是不是对的
      data_correct = 1;
  }
  bytesRead = 0;                                                    // 清除标志位
  end_flag = 0;     head_flag = 0;      sum_error = 0;
  
  if(data_correct){
//    printframe();
    bytes_to_float();
//    printfloat();

  }
  clientfrom32.stop();                                              // 关闭客户端连接
}

void printframe(){                                                   // 打印接收到的字节数据
    if(data_correct){
    for(uint8_t i=0;i<FRAME_LEN;i++){
      Serial.print(recceive_frame[i],HEX);
      Serial.print(" ");
    }
    Serial.println(" ");
  }
}

void printfloat(){
  if(data_correct){
    for(uint8_t i=0; i<DATA_NUM; i++){
      Serial.print(Yarray[i]);
      Serial.print(" ");
    }
    Serial.println(" ");
  }
}

void handleFound()                                                  // 请求错误
{
  server.send(404,"text/plain","404:Not Found!");
}

PC端代码:

电脑这边我用的是python来进行接收,用json库把数据解读然后用matplotlib来绘制数据图像,用PySimpleGUI来弄一个交互窗口,这个PySimpleGUI确实够simple的,具体怎么样你们可以参考网上其他的文章。这里我先把图画出来然后保存,然后把图插入GUI里面,有些文章是直接把窗口插进去然后渲染,这样太慢了。每次按下刷新按钮就向esp32请求一次,然后就把新的数据重新绘图并插入到GUI

运行结果:

 

 

import PySimpleGUI as sg
import matplotlib.pyplot as plt
import requests
import json
Xarray = [10,20,30,40,50,60,70,80,90,100]     # X轴数据
correct = False                               # 未能正确请求,服务器返回非数据

def client():
    global correct
    try:
        url = 'http://192.168.1.1/data'            # 服务器地址
        r = requests.get(url)                      # 用JSON格式进行数据传输,方便数据传输和读取
        Y = json.loads(r.text)['yData']
        correct = True
        return Y
    except:
        correct = False
        return [0]*10

def draw_graph(x, y):
    plt.clf()                                   # 清除之前的图形
    plt.plot(x, y, marker='o', linestyle='-')
    plt.xlabel('X轴')
    plt.ylabel('Y轴')
    plt.grid()
    # 用来正常显示中文标签
    plt.rcParams['font.sans-serif'] = ['SimHei']
    # 用来正常显示负号
    plt.rcParams['axes.unicode_minus'] = False
    plt.savefig('plot.png')                     # 保存生成的图片

# 初始化数据和图形
data_y = client()
draw_graph(Xarray, data_y)

layout = [                                               # 界面布局
    [sg.Image(filename='plot.png', key='-IMAGE-')],      # 图片显示
    [sg.Button('刷新数据', key='-REFRESH-'), sg.Button('退出', key='-EXIT-')]  # 按钮
]
if(correct):
    window = sg.Window('数据图形', layout)             # 窗口
else:
    window = sg.Window('错误请求', layout)  # 窗口

while True:                                               # 循环等待事件发生
    event, values = window.read()
    if event == sg.WIN_CLOSED or event == '-EXIT-':       # 退出
        break
    elif event == '-REFRESH-':                            # 刷新数据
        data_y = client()
        draw_graph(Xarray, data_y)
        window['-IMAGE-'].update(filename='plot.png')     # 刷新图片

window.close()

结尾:

以上就是我本次STM32串口数据利用WIFI传输到电脑接收的开发过程,客户端先用了esp8266,不知道是他软串口的问题还是什么,反正有时候串口会犯病,单片机没发数据就给我读一些奇怪数据,后面就换了esp32来读串口。而且有个很怪的点,我这个单片机得把DAP下载器插到电脑上面才能按键按下然后发送,我不懂这是为什么。

新手上路,请多多指教,如果本篇博客有哪里是错误的请指正。

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
近年来,随着物联网技术的飞速发展,越来越多的物联网应用出现在我们的生活和工作中。其中,stm32f103和esp8266wifi模块作为常见的物联网开发板和wifi模块,被广泛应用于物联网领域。 移动onenet平台作为国内领先的物联网云平台,提供了强大的云端存储、数据分析以及远程监控等功能,为物联网应用提供了极大的便捷性和品质保证。因此,将stm32f103通过esp8266wifi模块使用tcp协议连接至移动onenet平台,具有很高的实际应用价值。 具体实现流程如下: 1. 首先,搭建硬件环境,将stm32f103和esp8266wifi模块连接起来,保证硬件完好无损。 2. 在移动onenet平台上创建设备,并获取其设备ID、APIKey等重要参数,以便在后续代码编写中使用。 3. 在stm32f103开发板上编写相应的程序代码,通过esp8266wifi模块连接至WiFi网络,获取IP地址,并使用tcp协议建立与onenet平台的连接。 4. 将所需数据上传至onenet平台,包括发送的数据内容、数据流名称、设备ID等信息。 5. 在onenet平台上进行相应的数据处理和分析,获取实时监测数据、趋势分析、告警信息等相关内容。 总的来说,stm32f103通过esp8266wifi模块使用tcp协议连接至移动onenet平台,不仅能够实现数据的实时监测和远程控制,更能为物联网应用提供完整的云端解决方案,助力企业实现数字化转型和升级,提高竞争力和核心价值。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值