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

服务器 0

背景条件:

在准备电赛的时候,速成了一下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 addressconst int serverPort = 120;                    // Replace with your ESP32 server portbool 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 sgimport matplotlib.pyplot as pltimport requestsimport jsonXarray = [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]*10def 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下载器插到电脑上面才能按键按下然后发送,我不懂这是为什么。

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

也许您对下面的内容还感兴趣: