• 方案介绍
  • 附件下载
  • 相关推荐
申请入驻 产业图谱

【ESP32/ESP8266】GPS定位数据解析与超声测距(ATGM336H+HC-SR04)

02/28 08:39
2074
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

联系方式.docx

共1个文件

前言

做拓展项目需要要用GPS定位数据以及超声设计,之前没有用过ESP-12F这个开发板去做过,都是用到STM32板子,今天尝试用Arduino环境进行开发,并实现GPS经纬度数据获取以及超声测距

一、芯片介绍

这款芯片的可用io口还是比较少的,只适合做小型的设备控制,不用看图上有很多io口,其实他很多都不能用,否则就会导致启动错误,我已经踩过坑了

ESP-12F可用输出引脚列表

GPIO编号 物理引脚 特殊限制 最大输出电流 PWM支持 推荐用途
GPIO4 Pin 10 12mA ?? LED控制、传感器驱动
GPIO5 Pin 11 启动时需高电平 12mA ?? 继电器、数字输出
GPIO12 Pin 14 启动时需低电平 12mA ?? 按键输入、低功耗设备
GPIO13 Pin 15 12mA ?? SPI MOSI、数据总线
GPIO14 Pin 16 启动时需高电平 12mA ?? SPI CLK、高速信号
GPIO15 Pin 17 必须启动时低电平 12mA ?? 接地设备控制

禁止使用的输出引脚

GPIO编号 限制原因
GPIO0 启动模式选择(需外部上拉)
GPIO2 内部上拉,启动时需高电平
GPIO16 仅支持开漏输出

二、材料准备

准备这是四个材料就行了

ESP8266开发板 ESP8266-Node
显示屏-0.96寸 显示屏-0.96寸

GPS模块 ATGM336H

GPS是每一秒自动进行发送数据,当GPS状态的闪烁,就说明获取到了卫星定位

超声波模块 HC-SR04

三、代码编写

这个的话,我主要调一个重要部分,最后会附上源码,因为我代码里面带了联网部分,我这边就先暂时把联网部分屏蔽掉的

1 超声测距部分

一个io设置为输入,一个设置为输出

// 检测距离
void Distance_text(void) {

  // 发送触发信号
  digitalWrite(trigPin, LOW);   // 确保先拉低Trig引脚
  delayMicroseconds(2);         // 维持2微秒
  digitalWrite(trigPin, HIGH);  // 触发脉冲
  delayMicroseconds(10);        // 维持10微秒
  digitalWrite(trigPin, LOW);   // 结束触发
  // 测量回波脉冲持续时间
  distance = float(pulseIn(echoPin, HIGH) * 17) / 1000;
  if ((int)distance < 50) {

  } else {

  }
  // 显示结果
  // Serial.print("disc:");
  // if (distance > 0) {
  //   Serial.print(distance);
  //   Serial.println(" cm");
  // } else {
  //   Serial.println("out");
  // }
}

2 GPS部分

使用模拟串口,进行数据接收和解析。并且这里利用了显示了时区,我们的时区需要+8H,就是当前时间,程序已经完成这部分

void gpsParser() {
  while (gpsSerial.available()) {
    char Res = gpsSerial.read();

    if (Res == '$') {
      point1 = 0;
      memset(USART_RX2_BUF, 0, sizeof(USART_RX2_BUF));
    }

    USART_RX2_BUF[point1++] = Res;

    // 检测GPRMC/GNRMC语句
    if (point1 >= 6 && USART_RX2_BUF[3] == 'R' && USART_RX2_BUF[4] == 'M' && USART_RX2_BUF[5] == 'C') {

      if (Res == 'n') {
        memset(Save_Data.GPS_Buffer, 0, sizeof(Save_Data.GPS_Buffer));
        memcpy(Save_Data.GPS_Buffer, USART_RX2_BUF, point1);
        Save_Data.isGetData = true;
        point1 = 0;
      }
    }

    if (point1 >= sizeof(USART_RX2_BUF) - 1) {
      point1 = 0;
    }
  }
}

void parseGpsBuffer() {
  char* subString;
  char* subStringNext;
  char usefullBuffer[2] = { 0 };
  int commaCount = 0;

  subString = strtok(Save_Data.GPS_Buffer, ",");
  while (subString != NULL) {
    switch (commaCount) {
      case 1:  // UTC时间
        strncpy(Save_Data.UTCTime, subString, sizeof(Save_Data.UTCTime));
        break;
      case 2:  // 数据有效性
        strncpy(usefullBuffer, subString, sizeof(usefullBuffer));
        break;
      case 3:  // 纬度
        strncpy(Save_Data.latitude, subString, sizeof(Save_Data.latitude));
        break;
      case 4:  // N/S
        strncpy(Save_Data.N_S, subString, sizeof(Save_Data.N_S));
        break;
      case 5:  // 经度
        strncpy(Save_Data.longitude, subString, sizeof(Save_Data.longitude));
        break;
      case 6:  // E/W
        strncpy(Save_Data.E_W, subString, sizeof(Save_Data.E_W));
        break;
    }
    subString = strtok(NULL, ",");
    commaCount++;
  }

  Save_Data.isUsefull = (usefullBuffer[0] == 'A');
  Save_Data.isParseData = true;
}

void printGpsBuffer() {
  if (Save_Data.isUsefull) {
    // 转换经度
    longitude_sum = atof(Save_Data.longitude);
    int longitude_int = (int)(longitude_sum / 100);
    float longitude = longitude_int + (longitude_sum - longitude_int * 100) / 60;

    // 转换纬度
    latitude_sum = atof(Save_Data.latitude);
    int latitude_int = (int)(latitude_sum / 100);
    float latitude = latitude_int + (latitude_sum - latitude_int * 100) / 60;
    // 转化为经纬度
    longitude_sum = longitude_int + ((longitude_sum / 100 - longitude_int) * 100) / 60;
    latitude_sum = latitude_int + ((latitude_sum / 100 - latitude_int) * 100) / 60;


    // 时间解析修正
    String utcTime = Save_Data.UTCTime;
    if (utcTime.length() >= 6) {
      // 安全解析
      hour = utcTime.substring(0, 2).toInt();    // 小时
      minute = utcTime.substring(2, 4).toInt();  // 分钟
      second = utcTime.substring(4, 6).toInt();  // 秒
      // 时区调整(UTC+8)
      hour += 8;
      // 处理溢出
      if (hour >= 24) {
        hour -= 24;
        // 日期+1(需补充日期处理逻辑)
      } else if (hour < 0) {
        hour += 24;
        // 日期-1
      }

      // 时间有效性验证
      if (hour < 0 || hour >= 24 || minute < 0 || minute >= 60 || second < 0 || second >= 60) {
        Serial.println("Invalid Time Data");
        return;
      }

      格式化输出
      Serial.printf("Local Time: %02d:%02d:%02dn", hour, minute, second);
    } else {
      Serial.println("UTC Time Format Error");
    }

    Serial.print("Latitude: ");
    Serial.print(latitude, 6);
    Serial.print(" ");
    Serial.println(Save_Data.N_S);

    Serial.print("Longitude: ");
    Serial.print(longitude, 6);
    Serial.print(" ");
    Serial.println(Save_Data.E_W);
    Serial.println("---------------------");
  } else {
    Serial.println("GPS Data Invalid");
  }
}

3 完整代码

编译芯片的选择:ESPino(ESP-12)Module

#include <SoftwareSerial.h>
#include <PubSubClient.h>
#include <Ticker.h>
#include <ESP8266WiFi.h>
//OLED
#include <U8g2lib.h>
#include <Wire.h>
//JSON
#include <ArduinoJson.h>

// 设置wifi接入信息(请根据您的WiFi信息进行修改)
const char* ssid = "NET";
const char* password = "12345678";
const char* mqttServer = "iot-06z00axdhgfk24n.mqtt.iothub.aliyuncs.com";
// 如以上MQTT服务器无法正常连接,请前往以下页面寻找解决方案
// http://www.taichi-maker.com/public-mqtt-broker/

Ticker ticker;
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
int count;  // Ticker计数用变量
int count_2;
// ****************************************************
// 注意!以下需要用户根据然也物联平台信息进行修改!否则无法工作!
// ****************************************************
const char* mqttUserName = "smartdevice&h9sjy2rtcTI";                                                          // 服务端连接用户名(需要修改)
const char* mqttPassword = "5ba9463de09043190a8a743647518c46b1db6e1bfc2e0021198efe3384580772";                 // 服务端连接密码(需要修改)
const char* clientId = "h9sjy2rtcTI.smartdevice|securemode=2,signmethod=hmacsha256,timestamp=1739158837609|";  // 客户端id (需要修改)
const char* subTopic = "/broadcast/h9sjy2rtcTI/test1";                                                         // 订阅主题(需要修改)
const char* pubTopic = "/broadcast/h9sjy2rtcTI/test2";                                                         // 订阅主题(需要修改)
const char* willTopic = "/broadcast/h9sjy2rtcTI/test2";                                                        // 遗嘱主题名称(需要修改)
// ****************************************************

//遗嘱相关信息
const char* willMsg = "esp8266 offline";  // 遗嘱主题信息
const int willQos = 0;                    // 遗嘱QoS
const int willRetain = false;             // 遗嘱保留

const int subQoS = 1;            // 客户端订阅主题时使用的QoS级别(截止2020-10-07,仅支持QoS = 1,不支持QoS = 2)
const bool cleanSession = true;  // 清除会话(如QoS>0必须要设为false)


// LED 配置
#define LED_BUILTIN 15
#define LED 2
bool ledStatus = HIGH;
int Waning = 0;
int Ledstate = 0;
// OLED
#define SCL 14
#define SDA 12
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, SCL, SDA, /*reset=*/U8X8_PIN_NONE);
char str[50];  //拼接字符使用
// 定义超声波传感器引脚
#define trigPin 5
#define echoPin 4
float distance;

// 配置软串口(根据实际接线修改引脚)
SoftwareSerial gpsSerial(15, 13);  // RX: D8, TX: D7
// GPS数据结构
struct {
  char GPS_Buffer[128];
  char UTCTime[12];
  char latitude[16];
  char N_S[2];
  char longitude[16];
  char E_W[2];
  bool isGetData = false;
  bool isParseData = false;
  bool isUsefull = false;
} Save_Data;
// 全局变量
char USART_RX2_BUF[256];
unsigned char point1 = 0;
float longitude_sum, latitude_sum;
int hour, minute, second;

void setup() {

  Serial.begin(9600);     // 启动串口通讯
  gpsSerial.begin(9600);  // GPS模块常用波特率
  gpsSerial.println("GPS Parser Started");
  //OELD
  u8g2.begin();
  u8g2.setFont(u8g2_font_t0_16_tf);  //设定字体  u8g2_font_ncenB08_tr

  // //设置ESP8266工作模式为无线终端模式
  // WiFi.mode(WIFI_STA);
  // // 连接WiFi
  // connectWifi();
  // // 设置MQTT服务器和端口号
  // mqttClient.setServer(mqttServer, 1883);
  // mqttClient.setCallback(receiveCallback);
  // // 连接MQTT服务器
  // connectMQTTserver();

  // Ticker定时对象
  ticker.attach(1, tickerCount);
  // LED和蜂鸣器
  pinMode(LED_BUILTIN, OUTPUT);          // 设置板上LED引脚为输出模式
  pinMode(LED, OUTPUT);                 // 设置板上LED引脚为输出模式
  digitalWrite(LED, ledStatus);  // 启动后关闭板上LED
  digitalWrite(LED_BUILTIN, ledStatus);  // 启动后关闭板上LED

  pinMode(trigPin, OUTPUT);  // 设置Trig引脚为输出模式
  pinMode(echoPin, INPUT);   // 设置Echo引脚为输入模式

}
void loop() {
  // // 如果开发板未能成功连接服务器,则尝试连接服务器
  // if (!mqttClient.connected()) {
  //   connectMQTTserver();
  // } else {
    //1秒更新一次
    if (count_2 >= 1) {

      Distance_text();  // 测量距离
      u8g2.clearBuffer();
      // 解析 GPS
      if (Save_Data.isGetData) {
        parseGpsBuffer();
        Save_Data.isGetData = false;
      }
      if (Save_Data.isParseData) {
        printGpsBuffer();
        Save_Data.isParseData = false;
      }
      // LED
      if (Ledstate) {
        digitalWrite(LED_BUILTIN, HIGH);  // 启动
      } else {
        digitalWrite(LED_BUILTIN, LOW);
        gpsSerial.println("欢迎使用");
        Serial.print("欢迎使用");  
      }
      if (ledStatus == LOW) {
        ledStatus = HIGH;
        digitalWrite(LED, ledStatus);

      } else {
        ledStatus = LOW;
        digitalWrite(LED, ledStatus);
      }
      if ((int)longitude_sum > 1) {
        sprintf(str, "Log: %.6f", longitude_sum);
        u8g2.drawStr(0, 16, str);
        sprintf(str, "Lat: %.6f", latitude_sum);
        u8g2.drawStr(0, 32, str);
        sprintf(str, "Time: %02d:%02d:%02d", hour, minute, second);
        u8g2.drawStr(0, 48, str);
      } else {
        sprintf(str, "Log: error", longitude_sum);
        u8g2.drawStr(0, 16, str);
        sprintf(str, "Lat: error", latitude_sum);
        u8g2.drawStr(0, 32, str);
        sprintf(str, "Time:error", hour, minute, second);
        u8g2.drawStr(0, 48, str);
      }

      sprintf(str, "Disc: %.2f CM", distance);
      u8g2.drawStr(0, 64, str);
      u8g2.sendBuffer();  //显示

      count_2 = 0;
    }
    //4秒发送一次
    if (count >= 4) {
      // 每隔4秒钟发布一次信息
      // pubMQTTmsg();
      count = 0;
    }
  }

  gpsParser();
  // 处理信息以及心跳
  mqttClient.loop();
}
// 连接WiFi
void connectWiFi() {
  WiFi.begin(ssid, password);
  //Serial.print("Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    
  }
 // Serial.println("nConnected! IP: " + WiFi.localIP().toString());
}
//计时器
void tickerCount() {
  count++;
  count_2++;
}
// 连接MQTT服务器并订阅信息
void connectMQTTserver() {
  // 根据ESP8266的MAC地址生成客户端ID(避免与其它ESP8266的客户端ID重名)
  /* 连接MQTT服务器
  boolean connect(const char* id, const char* user, 
                  const char* pass, const char* willTopic, 
                  uint8_t willQos, boolean willRetain, 
                  const char* willMessage, boolean cleanSession); 
  若让设备在离线时仍然能够让qos1工作,则connect时的cleanSession需要设置为false                
                  */
  if (mqttClient.connect(clientId, mqttUserName,
                         mqttPassword, willTopic,
                         willQos, willRetain, willMsg, cleanSession)) {
    // Serial.print("MQTT Server Connected. ClientId: ");
    // Serial.println(clientId);
    // Serial.print("MQTT Server: ");
    // Serial.println(mqttServer);

    subscribeTopic();  // 订阅指定主题

  } else {
    // Serial.print("MQTT Server Connect Failed. Client State:");
    // Serial.println(mqttClient.state());
    delay(5000);
  }
}

// 收到信息后的回调函数
void receiveCallback(char* topic, byte* payload, unsigned int length) {
  // Serial.print("Message Received [");
  // Serial.print(topic);
  // Serial.print("] ");
  // for (int i = 0; i < length; i++) {
  //   Serial.print((char)payload[i]);
  // }
  //解析数据
  massage_parse_json((char*)payload);
  //回传数据
  // pubMQTTmsg();
}

// 订阅指定主题
void subscribeTopic() {
  // 通过串口监视器输出是否成功订阅主题以及订阅的主题名称
  // 请注意subscribe函数第二个参数数字为QoS级别。这里为QoS = 1
  if (mqttClient.subscribe(subTopic, subQoS)) {
    //Serial.println(subTopic);
  } else {
    //Serial.print("Subscribe Fail...");
    connectMQTTserver();  // 则尝试连接服务器
  }
}

// 发布信息
void pubMQTTmsg() {
  char pubMessage[254];
  // 数据流
  MQTT_FillBuf(pubMessage);

  // 实现ESP8266向主题发布信息
  if (mqttClient.publish(pubTopic, pubMessage)) {
    //Serial.println("Publish Topic:");Serial.println(pubTopic);
    //Serial.println("Publish message:");
    //Serial.println(pubMessage);
  } else {
    subscribeTopic();  // 则尝试连接服务器
    //Serial.println("Message Publish Failed.");
  }
}

// ESP8266连接wifi
void connectWifi() {

  u8g2.clearDisplay();  // 清屏
  sprintf(str, "Wait ...  ");
  u8g2.drawStr(0, 32, str);
  u8g2.sendBuffer();  //显示
  WiFi.begin(ssid, password);

  //等待WiFi连接,成功连接后输出成功信息
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    // Serial.print(".");
  }

  //Serial.println("WiFi Connected!");
}

// 数据封装
unsigned char MQTT_FillBuf(char* buf) {

  char text[256];
  memset(text, 0, sizeof(text));

  strcpy(buf, "{");
  if ((int)latitude_sum > 1) {
    memset(text, 0, sizeof(text));
    sprintf(text, ""lat":"%.6f",", latitude_sum);  // Temp是数据流的一个名称,temper是温度值
    strcat(buf, text);
    memset(text, 0, sizeof(text));
    sprintf(text, ""log":"%.6f",", longitude_sum);  // Temp是数据流的一个名称,temper是温度值
    strcat(buf, text);
  }


  memset(text, 0, sizeof(text));
  sprintf(text, ""disc":"%d",", (int)distance);  // Temp是数据流的一个名称,temper是温度值
  strcat(buf, text);

  memset(text, 0, sizeof(text));
  sprintf(text, ""waring":"%d",", Waning);  // Temp是数据流的一个名称,temper是温度值
  strcat(buf, text);

  memset(text, 0, sizeof(text));
  sprintf(text, ""led":"%d"", digitalRead(LED_BUILTIN));  // Temp是数据流的一个名称,temper是温度值
  strcat(buf, text);

  memset(text, 0, sizeof(text));
  sprintf(text, "}");
  strcat(buf, text);

  return strlen(buf);
}

// 解析json数据
void massage_parse_json(char* message) {
  // 声明一个 JSON 文档对象
  StaticJsonDocument<200> doc;
  // 解析 JSON 数据
  DeserializationError error = deserializeJson(doc, (const char*)message);
  // 检查解析是否成功
  if (error) {
    // Serial.print("Failed to parse JSON: ");
    // Serial.println(error.c_str());
    return;
  }
  // 从解析后的 JSON 文档中获取键值对
  int cmd = doc["cmd"];
  JsonObject data = doc["data"];
  switch (cmd) {
    case 1:
      Ledstate = data["led"];
      count = 1;
      break;
    case 2:
      if((int)latitude_sum > 1){
        sprintf(str, "当前时间:%02d:%02d:%02d", hour, minute, second);
        gpsSerial.println(str);
      }else{
        sprintf(str, "请先到开阔地带进行定位定位", hour, minute, second);
        gpsSerial.println(str);
      }
      count = 1;
      break;
    case 3:

      count = 1;
      break;
  }
}
// 检测距离
void Distance_text(void) {

  // 发送触发信号
  digitalWrite(trigPin, LOW);   // 确保先拉低Trig引脚
  delayMicroseconds(2);         // 维持2微秒
  digitalWrite(trigPin, HIGH);  // 触发脉冲
  delayMicroseconds(10);        // 维持10微秒
  digitalWrite(trigPin, LOW);   // 结束触发
  // 测量回波脉冲持续时间
  distance = float(pulseIn(echoPin, HIGH) * 17) / 1000;
  if ((int)distance < 50) {

  } else {

  }
  // 显示结果
  // Serial.print("disc:");
  // if (distance > 0) {
  //   Serial.print(distance);
  //   Serial.println(" cm");
  // } else {
  //   Serial.println("out");
  // }
}

void gpsParser() {
  while (gpsSerial.available()) {
    char Res = gpsSerial.read();

    if (Res == '$') {
      point1 = 0;
      memset(USART_RX2_BUF, 0, sizeof(USART_RX2_BUF));
    }

    USART_RX2_BUF[point1++] = Res;

    // 检测GPRMC/GNRMC语句
    if (point1 >= 6 && USART_RX2_BUF[3] == 'R' && USART_RX2_BUF[4] == 'M' && USART_RX2_BUF[5] == 'C') {

      if (Res == 'n') {
        memset(Save_Data.GPS_Buffer, 0, sizeof(Save_Data.GPS_Buffer));
        memcpy(Save_Data.GPS_Buffer, USART_RX2_BUF, point1);
        Save_Data.isGetData = true;
        point1 = 0;
      }
    }

    if (point1 >= sizeof(USART_RX2_BUF) - 1) {
      point1 = 0;
    }
  }
}

void parseGpsBuffer() {
  char* subString;
  char* subStringNext;
  char usefullBuffer[2] = { 0 };
  int commaCount = 0;

  subString = strtok(Save_Data.GPS_Buffer, ",");
  while (subString != NULL) {
    switch (commaCount) {
      case 1:  // UTC时间
        strncpy(Save_Data.UTCTime, subString, sizeof(Save_Data.UTCTime));
        break;
      case 2:  // 数据有效性
        strncpy(usefullBuffer, subString, sizeof(usefullBuffer));
        break;
      case 3:  // 纬度
        strncpy(Save_Data.latitude, subString, sizeof(Save_Data.latitude));
        break;
      case 4:  // N/S
        strncpy(Save_Data.N_S, subString, sizeof(Save_Data.N_S));
        break;
      case 5:  // 经度
        strncpy(Save_Data.longitude, subString, sizeof(Save_Data.longitude));
        break;
      case 6:  // E/W
        strncpy(Save_Data.E_W, subString, sizeof(Save_Data.E_W));
        break;
    }
    subString = strtok(NULL, ",");
    commaCount++;
  }

  Save_Data.isUsefull = (usefullBuffer[0] == 'A');
  Save_Data.isParseData = true;
}

void printGpsBuffer() {
  if (Save_Data.isUsefull) {
    // 转换经度
    longitude_sum = atof(Save_Data.longitude);
    int longitude_int = (int)(longitude_sum / 100);
    float longitude = longitude_int + (longitude_sum - longitude_int * 100) / 60;

    // 转换纬度
    latitude_sum = atof(Save_Data.latitude);
    int latitude_int = (int)(latitude_sum / 100);
    float latitude = latitude_int + (latitude_sum - latitude_int * 100) / 60;
    // 转化为经纬度
    longitude_sum = longitude_int + ((longitude_sum / 100 - longitude_int) * 100) / 60;
    latitude_sum = latitude_int + ((latitude_sum / 100 - latitude_int) * 100) / 60;


    // 时间解析修正
    String utcTime = Save_Data.UTCTime;
    if (utcTime.length() >= 6) {
      // 安全解析
      hour = utcTime.substring(0, 2).toInt();    // 小时
      minute = utcTime.substring(2, 4).toInt();  // 分钟
      second = utcTime.substring(4, 6).toInt();  // 秒
      // 时区调整(UTC+8)
      hour += 8;
      // 处理溢出
      if (hour >= 24) {
        hour -= 24;
        // 日期+1(需补充日期处理逻辑)
      } else if (hour < 0) {
        hour += 24;
        // 日期-1
      }

      // 时间有效性验证
      if (hour < 0 || hour >= 24 || minute < 0 || minute >= 60 || second < 0 || second >= 60) {
        Serial.println("Invalid Time Data");
        return;
      }

      格式化输出
      Serial.printf("Local Time: %02d:%02d:%02dn", hour, minute, second);
    } else {
      Serial.println("UTC Time Format Error");
    }

    Serial.print("Latitude: ");
    Serial.print(latitude, 6);
    Serial.print(" ");
    Serial.println(Save_Data.N_S);

    Serial.print("Longitude: ");
    Serial.print(longitude, 6);
    Serial.print(" ");
    Serial.println(Save_Data.E_W);
    Serial.println("---------------------");
  } else {
    Serial.println("GPS Data Invalid");
  }
}

编译完成后图片

四、实现效果

开启串口会输出

五、 参考

Arduino IDE 使用安装以及ESP32库的导入(离线)https://blog.csdn.net/herui_2/article/details/135296814?spm=1001.2014.3001.5501

ESP32/ESP8622 -- 使用MQTT协议连接云平台(带图文说明)https://herui.blog.csdn.net/article/details/135317019?spm=1001.2014.3001.5502


联系方式 微信号:13648103287

  • 联系方式.docx
    下载

相关推荐