【Arduino TFT】基于 ESP8266 以及 SSD1306实现的天气时钟

忘记过去,超越自己

  • ❤️ 博客主页 单片机菜鸟哥,一个野生非专业硬件IOT爱好者 ❤️
  • ❤️ 本篇创建记录 2023-10-21 ❤️
  • ❤️ 本篇更新记录 2023-10-21 ❤️
  • 🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝
  • 🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留言轰炸哦!及时修正!感谢支持!
  • 🔥 Arduino ESP8266教程累计帮助过超过1W+同学入门学习硬件网络编程,入选过选修课程,刊登过无线电杂志 🔥零基础从入门到熟悉Arduino平台下开发ESP8266,同时会涉及网络编程知识。专栏文章累计超过60篇,分为基础篇、网络篇、应用篇、高级篇,涵盖ESP8266大部分开发技巧。

快速导航
单片机菜鸟的博客快速索引(快速找到你要的)

如果觉得有用,麻烦点赞收藏,您的支持是博主创作的动力。

1. 前言

在这里插入图片描述
使用esp8266驱动 oled 1306 实现:

  • 日期时间
  • 城市温度信息
  • 未来三天预报信息

2. 软件代码

在这里插入图片描述

2.1 主工程代码

Esp8266_Clock_Weather.ino

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>

#include <time.h>
#include <sys/time.h>                  
#include <coredecls.h>      

#include "SSD1306Wire.h"    //0.96寸用这个
#include "OLEDDisplayUi.h"
#include "HeFeng.h"
#include "WeatherStationFonts.h"
#include "WeatherStationImages.h"

/***************************
   Begin Settings
 **************************/
const char* WIFI_SSID = "CMCC-pm3h";  //填写你的WIFI名称及密码
const char* WIFI_PWD = "hw2htwv4";

//由于太多人使用我的秘钥,导致获取次数超额,所以不提供秘钥了,大家可以到 https://dev.heweather.com/ 获取免费的
const char* HEFENG_KEY = "f78a1c10fe5c490b8f3dc3946fe00975";//填写你的和风天气秘钥
const char* HEFENG_LOCATION =   "101280107";//填写你的城市ID,可到 https://where.heweather.com/index.html 查询

#define TZ              8      // 中国时区为8
#define DST_MN          0      // 默认为0

const int UPDATE_INTERVAL_SECS = 30 * 60; // 30分钟更新一次天气

const int I2C_DISPLAY_ADDRESS = 0x3c;  //I2c地址默认

const String WDAY_NAMES[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};  //星期
const String MONTH_NAMES[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};  //月份

/***************************
   End Settings
 **************************/
 
SSD1306Wire     display(I2C_DISPLAY_ADDRESS, SDA, SCL);   // 0.96寸用这个
OLEDDisplayUi   ui( &display );

HeFengCurrentData currentWeather; //实例化对象
HeFengForeData foreWeather[3];
HeFeng HeFengClient;

#define TZ_MN           ((TZ)*60)   //时间换算
#define TZ_SEC          ((TZ)*3600)
#define DST_SEC         ((DST_MN)*60)

time_t now; //实例化时间

bool readyForWeatherUpdate = false; // 天气更新标志
bool first = true;  //首次更新标志
long timeSinceLastWUpdate = 0;    //上次更新后的时间
long timeSinceLastCurrUpdate = 0;   //上次天气更新后的时间

void drawProgress(OLEDDisplay *display, int percentage, String label);   //提前声明函数
void updateData(OLEDDisplay *display);
void updateDatas(OLEDDisplay *display);
void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex);
void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state);
void setReadyForWeatherUpdate();

//添加框架
//此数组保留指向所有帧的函数指针
//框架是从右向左滑动的单个视图
FrameCallback frames[] = { drawDateTime, drawCurrentWeather, drawForecast };
//页面数量
int numberOfFrames = 3;

OverlayCallback overlays[] = { drawHeaderOverlay }; //覆盖回调函数
int numberOfOverlays = 1;  //覆盖数

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println();

  // 屏幕初始化
  display.init();
  display.clear();
  display.display();

  display.flipScreenVertically(); //屏幕翻转
  display.setContrast(120); //屏幕亮度

  wifiConnect();

  ui.setTargetFPS(30);  //刷新频率

  ui.setActiveSymbol(activeSymbole); //设置活动符号
  ui.setInactiveSymbol(inactiveSymbole); //设置非活动符号
  // 符号位置
  // 你可以把这个改成TOP, LEFT, BOTTOM, RIGHT
  ui.setIndicatorPosition(BOTTOM);
  // 定义第一帧在栏中的位置
  ui.setIndicatorDirection(LEFT_RIGHT);
  // 屏幕切换方向
  // 您可以更改使用的屏幕切换方向 SLIDE_LEFT, SLIDE_RIGHT, SLIDE_TOP, SLIDE_DOWN
  ui.setFrameAnimation(SLIDE_LEFT);
  ui.setFrames(frames, numberOfFrames); // 设置框架
  ui.setTimePerFrame(5000); //设置切换时间
  ui.setOverlays(overlays, numberOfOverlays); //设置覆盖
  // UI负责初始化显示
  ui.init();
  display.flipScreenVertically(); //屏幕反转

  configTime(TZ_SEC, DST_SEC, "ntp.ntsc.ac.cn", "ntp1.aliyun.com"); //ntp获取时间,你也可用其他"pool.ntp.org","0.cn.pool.ntp.org","1.cn.pool.ntp.org","ntp1.aliyun.com"
  delay(200);
}

void loop() {
  if (first) {  //首次加载
    updateDatas(&display);
    first = false;
  }
  if (millis() - timeSinceLastWUpdate > (1000L * UPDATE_INTERVAL_SECS)) { //屏幕刷新
    setReadyForWeatherUpdate();
    timeSinceLastWUpdate = millis();
  }

  if (readyForWeatherUpdate && ui.getUiState()->frameState == FIXED) { //天气更新
    updateData();
  }

  int remainingTimeBudget = ui.update(); //剩余时间预算

  if (remainingTimeBudget > 0) {
    //你可以在这里工作如果你低于你的时间预算。
    delay(remainingTimeBudget);
  }
  
}

void wifiConnect() {  //WIFI密码连接,Web配网请注释
  WiFi.begin(WIFI_SSID, WIFI_PWD);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(80);
    display.clear();
    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_5);
    display.display();
    delay(80);
    display.clear();
    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_6);
    display.display();
    delay(80);
    display.clear();
    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_7);
    display.display();
    delay(80);
    display.clear();
    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_8);
    display.display();
    delay(80);
    display.clear();
    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_1);
    display.display();
    delay(80);
    display.clear();
    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_2);
    display.display();
    delay(80);
    display.clear();
    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_3);
    display.display();
    delay(80);
    display.clear();
    display.drawXbm(34, 0, bili_Logo_width, bili_Logo_height, bili_Logo_4);
    display.display();
  }
  Serial.println("");
  delay(500);
}

void drawProgress(OLEDDisplay *display, int percentage, String label) {    //绘制进度
  display->clear();
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->setFont(ArialMT_Plain_10);
  display->drawString(64, 10, label);
  display->drawProgressBar(2, 28, 124, 10, percentage);
  display->display();
}

void updateData(void) {  //天气更新
  HeFengClient.doUpdateCurr(&currentWeather, HEFENG_KEY, HEFENG_LOCATION);
  HeFengClient.doUpdateFore(foreWeather, HEFENG_KEY, HEFENG_LOCATION);
  readyForWeatherUpdate = false;
}

void updateDatas(OLEDDisplay *display) {  //首次天气更新
  drawProgress(display, 33, "Updating weather...");
  HeFengClient.doUpdateCurr(&currentWeather, HEFENG_KEY, HEFENG_LOCATION);
  
  drawProgress(display, 66, "Updating forecasts...");
  HeFengClient.doUpdateFore(foreWeather, HEFENG_KEY, HEFENG_LOCATION);
  
  readyForWeatherUpdate = false;
  drawProgress(display, 100, "Done...");
  delay(200);
  
}

void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {  //显示时间
  now = time(nullptr);
  struct tm* timeInfo;
  timeInfo = localtime(&now);
  char buff[16];

  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->setFont(ArialMT_Plain_16);
  String date = WDAY_NAMES[timeInfo->tm_wday];

  sprintf_P(buff, PSTR("%04d-%02d-%02d  %s"), timeInfo->tm_year + 1900, timeInfo->tm_mon + 1, timeInfo->tm_mday, WDAY_NAMES[timeInfo->tm_wday].c_str());
  display->drawString(64 + x, 5 + y, String(buff));
  display->setFont(ArialMT_Plain_24);

  sprintf_P(buff, PSTR("%02d:%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
  display->drawString(64 + x, 22 + y, String(buff));
  display->setTextAlignment(TEXT_ALIGN_LEFT);
}

void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {  //显示天气
  display->setFont(ArialMT_Plain_10);
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->drawString(64 + x, 38 + y, currentWeather.cond_txt + "    |   Wind: " + currentWeather.wind_sc + "  ");

  display->setFont(ArialMT_Plain_24);
  display->setTextAlignment(TEXT_ALIGN_LEFT);
  String temp = currentWeather.tmp + "°C" ;
  display->drawString(60 + x, 3 + y, temp);
  display->setFont(ArialMT_Plain_10);
  display->drawString(62 + x, 26 + y, currentWeather.fl + "°C | " + currentWeather.hum + "%");
  display->setFont(Meteocons_Plain_36);
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->drawString(32 + x, 0 + y, currentWeather.iconMeteCon);
}

void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {  //天气预报
  drawForecastDetails(display, x, y, 0);
  drawForecastDetails(display, x + 44, y, 1);
  drawForecastDetails(display, x + 88, y, 2);
}

void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex) {  //天气预报
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->setFont(ArialMT_Plain_10);
  display->drawString(x + 20, y, foreWeather[dayIndex].dateStr);
  display->setFont(Meteocons_Plain_21);
  display->drawString(x + 20, y + 12, foreWeather[dayIndex].iconMeteCon);

  String temp = foreWeather[dayIndex].tmp_min + " | " + foreWeather[dayIndex].tmp_max;
  display->setFont(ArialMT_Plain_10);
  display->drawString(x + 20, y + 34, temp);
  display->setTextAlignment(TEXT_ALIGN_LEFT);
}

void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {   //绘图页眉覆盖
  now = time(nullptr);
  struct tm* timeInfo;
  timeInfo = localtime(&now);
  char buff[14];
  sprintf_P(buff, PSTR("%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min);

  display->setColor(WHITE);
  display->setFont(ArialMT_Plain_10);
  display->setTextAlignment(TEXT_ALIGN_LEFT);
  display->drawString(6, 54, String(buff));
  display->setTextAlignment(TEXT_ALIGN_RIGHT);
  display->drawHorizontalLine(0, 52, 128);
}

void setReadyForWeatherUpdate() {  //为天气更新做好准备
  Serial.println("Setting readyForUpdate to true");
  readyForWeatherUpdate = true;
}

2.2 和风天气

  • HeFeng.h

#pragma once
#include <ArduinoJson.h>

typedef struct HeFengCurrentData {

  String cond_txt;
  String fl;
  String tmp;
  String hum;
  String wind_sc;
  String iconMeteCon;
}
HeFengCurrentData;
typedef struct HeFengForeData {
  String dateStr;
  String tmp_min;
  String tmp_max;
  String iconMeteCon;

}
HeFengForeData;
class HeFeng {
  private:
    String getMeteConIcon(String cond_code);
    bool fetchBuffer(const char* url);
    static uint8_t _buffer[1024 * 3]; //gzip流最大缓冲区
    static size_t _bufferSize;
  public:
    HeFeng();
    void doUpdateCurr(HeFengCurrentData *data, String key, String location);
    void doUpdateFore(HeFengForeData *data, String key, String location);
};

  • HeFeng.cpp

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h>
#include "ArduinoUZlib.h" // gzip库
#include "HeFeng.h"

uint8_t HeFeng::_buffer[1024 * 3];
size_t HeFeng::_bufferSize = 0;

HeFeng::HeFeng() {
}

bool HeFeng::fetchBuffer(const char *url)
{
    _bufferSize = 0;
    std::unique_ptr<WiFiClientSecure> client(new WiFiClientSecure);
    client->setInsecure();
    HTTPClient https;
    Serial.print("[HTTPS] begin...now\n");
    if (https.begin(*client, url))
    {
        https.addHeader("Accept-Encoding", "gzip");
        https.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0");
        int httpCode = https.GET();
        if (httpCode > 0)
        {
            if (httpCode == HTTP_CODE_OK)
            {
                int len = https.getSize(); // get length of document (is -1 when Server sends no Content-Length header)
                static uint8_t buff[128 * 1] = {0}; // create buffer for read
                int offset = 0;                 // read all data from server
                while (https.connected() && (len > 0 || len == -1))
                {
                    size_t size = client->available(); // get available data size
                    if (size)
                    {
                        int c = client->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
                        memcpy(_buffer + offset, buff, sizeof(uint8_t) * c);
                        offset += c;
                        if (len > 0)
                        {
                            len -= c;
                        }
                    }
                    delay(1);
                }
                _bufferSize = offset;
            }
        }
        else
        {
            Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
        }
        https.end();
    }else{
        Serial.printf("Unable to connect\n");
    }
    Serial.print("[HTTPS] end...now\n");
    return _bufferSize > 0;
}

void HeFeng::doUpdateCurr(HeFengCurrentData *data, String key, String location) {  //获取天气

  String url = "https://devapi.qweather.com/v7/weather/now?lang=en&gzip=n&location=" + location + "&key=" + key;
  Serial.print("[HTTPS] begin...now\n");
  fetchBuffer(url.c_str()); // HTTPS获取数据流
  if (_bufferSize){
     Serial.print("bufferSize:");
     Serial.println(_bufferSize, DEC);
     uint8_t *outBuf=NULL;
     size_t outLen = 0;
     ArduinoUZlib::decompress(_buffer, _bufferSize, outBuf, outLen); // GZIP解压
     // 输出解密后的数据到控制台。
     Serial.write(outBuf,outLen);
     if(outBuf && outLen){
         DynamicJsonDocument  jsonBuffer(2048);
         deserializeJson(jsonBuffer, (char*)outBuf,outLen);
         JsonObject root = jsonBuffer.as<JsonObject>();

         String tmp = root["now"]["temp"];//温度
         data->tmp = tmp;
         String fl = root["now"]["feelsLike"];//体感温度
         data->fl = fl;
         String hum = root["now"]["humidity"];//湿度
         data->hum = hum;
         String wind_sc = root["now"]["windScale"];//风力
         data->wind_sc = wind_sc;
         String cond_code = root["now"]["icon"];//天气图标
         String meteConIcon = getMeteConIcon(cond_code);
         String cond_txt = root["now"]["text"];//天气
         data->cond_txt = cond_txt;
         data->iconMeteCon = meteConIcon;

         jsonBuffer.clear();
      } else {
         Serial.println("doUpdateCurr failed");
         data->tmp = "-1";
         data->fl = "-1";
         data->hum = "-1";
         data->wind_sc = "-1";
         data->cond_txt = "no network";
         data->iconMeteCon = ")";
      }
      //一定要记得释放内存
      if(outBuf != NULL) {
         free(outBuf);
         outBuf=NULL;
      }
     _bufferSize = 0;
  }
}

void HeFeng::doUpdateFore(HeFengForeData *data, String key, String location) {  //获取预报

  String url = "https://devapi.qweather.com/v7/weather/3d?lang=en&gzip=n&location=" + location + "&key=" + key;
  Serial.print("[HTTPS] begin...forecast\n");
  fetchBuffer(url.c_str()); // HTTPS获取数据流
  if (_bufferSize){
     Serial.print("bufferSize:");
     Serial.println(_bufferSize, DEC);
     uint8_t *outBuf=NULL;
     size_t outLen = 0;
     ArduinoUZlib::decompress(_buffer, _bufferSize, outBuf, outLen); // GZIP解压
     // 输出解密后的数据到控制台。
     Serial.write(outBuf,outLen);
     if(outBuf && outLen){
         DynamicJsonDocument  jsonBuffer(2048);
         deserializeJson(jsonBuffer, (char*)outBuf,outLen);
         JsonObject root = jsonBuffer.as<JsonObject>();

         int i;
         for (i = 0; i < 3; i++) {
           String dateStr = root["daily"][i]["fxDate"];
           data[i].dateStr = dateStr.substring(5, dateStr.length());
           String tmp_min = root["daily"][i]["tempMin"];
           data[i].tmp_min = tmp_min;
           String tmp_max = root["daily"][i]["tempMax"];
           data[i].tmp_max = tmp_max;
           String cond_code = root["daily"][i]["iconDay"];
           String meteConIcon = getMeteConIcon(cond_code);
           data[i].iconMeteCon = meteConIcon;
         }

         jsonBuffer.clear();
      } else {
         int i;
         for (i = 0; i < 3; i++) {
           data[i].tmp_min = "-1";
           data[i].tmp_max = "-1";
           data[i].dateStr = "N/A";
           data[i].iconMeteCon = ")";
         }
      }
      //一定要记得释放内存
      if(outBuf != NULL) {
         free(outBuf);
         outBuf=NULL;
      }
     _bufferSize = 0;
  }
}

String HeFeng::getMeteConIcon(String cond_code) {  //获取天气图标  见 https://dev.qweather.com/docs/start/icons/
  if (cond_code == "100" || cond_code == "150" || cond_code == "9006") {//晴 Sunny/Clear
    return "B";
  }
  if (cond_code == "101") {//多云 Cloudy
    return "Y";
  }
  if (cond_code == "102") {//少云 Few Clouds
    return "N";
  }
  if (cond_code == "103" || cond_code == "153") {//晴间多云 Partly Cloudy/
    return "H";
  }
  if (cond_code == "104" || cond_code == "154") {//阴 Overcast
    return "D";
  }
  if (cond_code == "300" || cond_code == "301") {//阵雨 Shower Rain 301-强阵雨 Heavy Shower Rain
    return "T";
  }
  if (cond_code == "302" || cond_code == "303") {//302-雷阵雨  Thundershower / 303-强雷阵雨
    return "P";
  }
  if (cond_code == "304" || cond_code == "313" || cond_code == "404" || cond_code == "405" || cond_code == "406") {
    //304-雷阵雨伴有冰雹 Freezing Rain
    //313-冻雨 Freezing Rain
    //404-雨夹雪 Sleet
    //405-雨雪天气 Rain And Snow
    //406-阵雨夹雪  Shower Snow
    return "X";
  }
  if (cond_code == "305" || cond_code == "308" || cond_code == "309" || cond_code == "314" || cond_code == "399") {
    //305-小雨 Light Rain
    //308-极端降雨 Extreme Rain
    //309-毛毛雨/细雨 Drizzle Rain
    //314-小到中雨 Light to moderate rain
    //399-雨 Light to moderate rain
    return "Q";
  }
  if (cond_code == "306" || cond_code == "307" || cond_code == "310" || cond_code == "311" || cond_code == "312" || cond_code == "315" || cond_code == "316" || cond_code == "317" || cond_code == "318") {
    //306-中雨 Moderate Rain
    //307-大雨 Heavy Rain
    //310-暴雨  Storm
    //311-大暴雨 Heavy Storm
    //312-特大暴雨 Severe Storm
    //315-中到大雨 Moderate to heavy rain
    //316-大到暴雨 Heavy rain to storm
    //317-暴雨到大暴雨 Storm to heavy storm
    //318-大暴雨到特大暴雨 Heavy to severe storm
    return "R";
  }
  if (cond_code == "400" || cond_code == "408") {
    //400-小雪 Light Snow
    //408-小到中雪 Light to moderate snow
    return "U";
  }
  if (cond_code == "401" || cond_code == "402" || cond_code == "403" || cond_code == "409" || cond_code == "410") {
    //401-中雪 Moderate Snow
    //402-大雪 Heavy Snow
    //403-暴雪 Snowstorm
    //409-中到大雪 Moderate to heavy snow
    //410-大到暴雪 Heavy snow to snowstorm
    return "W";
  }
  if (cond_code == "407") {
    //407-阵雪 Snow Flurry
    return "V";
  }
  if (cond_code == "499" || cond_code == "901") {
    //499-雪 Snow
    //901-冷 Cold
    return "G";
  }
  if (cond_code == "500") {
    //500-薄雾 Mist
    return "E";
  }
  if (cond_code == "501" || cond_code == "509" || cond_code == "510" || cond_code == "514" || cond_code == "515") {
    //501-雾 Foggy
    return "M";
  }
  if (cond_code == "502" || cond_code == "511" || cond_code == "512" || cond_code == "513") {
    //502-霾 Haze
    return "L";
  }
  if (cond_code == "503" || cond_code == "504" || cond_code == "507" || cond_code == "508") {
    //503-扬沙 Sand
    return "F";
  }
  
  if (cond_code == "999") {//未知
    return ")";
  }
  if (cond_code == "213") {
    return "O";
  }
  if (cond_code == "200" || cond_code == "201" || cond_code == "202" || cond_code == "203" || cond_code == "204" || cond_code == "205" || cond_code == "206" || cond_code == "207" || cond_code == "208" || cond_code == "209" || cond_code == "210" || cond_code == "211" || cond_code == "212") {
    return "S";
  }
  return ")";
}

2.3 字体图片(过大)

  • WeatherStationFonts.h
  • WeatherStationImages.h
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

单片机菜鸟哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值