背景条件:
在准备电赛的时候,速成了一下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下载器插到电脑上面才能按键按下然后发送,我不懂这是为什么。
新手上路,请多多指教,如果本篇博客有哪里是错误的请指正。