Arduino爱好者

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 1756|回复: 4

合宙ESP32C3点亮1.54寸墨水屏

[复制链接]
发表于 2022-8-12 00:53 | 显示全部楼层 |阅读模式
本帖最后由 topdog 于 2022-8-18 07:23 编辑


这款墨水屏型号是GDEP015OC1。
e ink outdoor display,Anti UV display,(Out of Stock)1.54 inch e-paper display anti-ultraviolet (e-paper-display.com)

GxEPD2库显示GDEP015OC1不再可用,这款亚马逊已经下架。
GxEPD2_BW<GxEPD2_154, MAX_HEIGHT(GxEPD2_154)> display(GxEPD2_154(/*CS=10*/ SS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); // GDEP015OC1 no longer available

1,安装的库文件:

2.PNG
2,接线:

[pre]Eink    ESP32C3
GND     GND
VCC     3.3V
SCL     GPIO2
SDA     GPIO3
RES     GPIO10
DC      GPIO6
CS      GPIO7
BUSY    GPIO8[/pre]

3,程序如下:

(1)显示库图片和文字。
[pre]// include library, include base class, make path known
#include <GxEPD.h>

// select the display class to use, only one
#include <GxGDEP015OC1/GxGDEP015OC1.h>    // 1.54" b/w

#include <GxIO/GxIO_SPI/GxIO_SPI.h>
#include <GxIO/GxIO.h>

// FreeFonts from Adafruit_GFX
#include <Fonts/FreeMonoBold9pt7b.h>

#include GxEPD_BitmapExamples

const int Eink_SCL =  2;
const int Eink_SDA =  3;
const int Eink_RES =  10;
const int Eink_DC =  6;
const int Eink_CS =  7;
const int Eink_BUSY =  8;

SPIClass EinkSPI(SPI);
GxIO_Class io(EinkSPI, /*CS=5*/ Eink_CS, /*DC=*/Eink_DC, /*RST=*/ Eink_RES); // arbitrary selection of 17, 16
GxEPD_Class display(io, /*RST=*/ Eink_RES, /*BUSY=*/Eink_BUSY); // arbitrary selection of (16), 4


//#define DEMO_DELAY 3*60 // seconds
//#define DEMO_DELAY 1*60 // seconds
#define DEMO_DELAY 2

void setup(void)
{
  Serial.begin(115200);  
  EinkSPI.begin(Eink_SCL ,-1, Eink_SDA, Eink_CS);
  Serial.println();
  Serial.println("setup");
  display.init(115200); // enable diagnostic output on Serial
  Serial.println("setup done");
}

void loop()
{  
  display.drawExampleBitmap(BitmapExample1, sizeof(BitmapExample1));
  delay(DEMO_DELAY * 100);
  display.drawPaged(showFontCallback);
  display.powerDown();
  delay(DEMO_DELAY * 100);
}

void showFontCallback()
{
  const char* name = "FreeMonoBold9pt7b";
  const GFXfont* f = &FreeMonoBold9pt7b;
  display.fillScreen(GxEPD_WHITE);
  display.setTextColor(GxEPD_BLACK);
  display.setFont(f);
  display.setCursor(0, 0);
  display.println();
  display.println(name);
  display.println(" !\"#$%&'()*+,-./");
  display.println("0123456789:;<=>?");
  display.println("@ABCDEFGHIJKLMNO");
  display.println("PQRSTUVWXYZ[\\]^_");
  display.println("`abcdefghijklmno");
  display.println("pqrstuvwxyz{|}~ ");
}[/pre]

(2)显示下载的网络图片

[pre]// include library, include base class, make path known
#include <GxEPD.h>

// select the display class to use, only one
#include <GxGDEP015OC1/GxGDEP015OC1.h>    // 1.54" b/w

#include <GxIO/GxIO_SPI/GxIO_SPI.h>
#include <GxIO/GxIO.h>

const int Eink_SCL =  2;
const int Eink_SDA =  3;
const int Eink_RES =  10;
const int Eink_DC =  6;
const int Eink_CS =  7;
const int Eink_BUSY =  8;

SPIClass EinkSPI(SPI);
GxIO_Class io(EinkSPI, /*CS=5*/ Eink_CS, /*DC=*/Eink_DC, /*RST=*/ Eink_RES); // arbitrary selection of 17, 16
GxEPD_Class display(io, /*RST=*/ Eink_RES, /*BUSY=*/Eink_BUSY); // arbitrary selection of (16), 4

#include <WiFiClient.h>
#include <WiFiClientSecure.h>

const char* ssid     = "你的WiFi";
const char* password = "你的password";
const int httpPort  = 80;
const int httpsPort = 443;
const char* fp_api_github_com = "df b2 29 c6 a6 38 1a 59 9d c9 ad 92 2d 26 f5 3c 83 8f a5 87"; // as of 25.11.2020
const char* fp_github_com     = "5f 3f 7a c2 56 9f 50 a4 66 76 47 c6 a1 8c a0 07 aa ed bb 8e"; // as of 25.11.2020
const char* fp_rawcontent     = "8F 0E 79 24 71 C5 A7 D2 A7 46 76 30 C1 3C B7 2A 13 B0 01 B2"; // as of 29.7.2022


const char* certificate_rawcontent =
  "-----BEGIN CERTIFICATE-----\n"
  "MIIEvjCCA6agAwIBAgIQBtjZBNVYQ0b2ii+nVCJ+xDANBgkqhkiG9w0BAQsFADBh"
  "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3"
  "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD"
  "QTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaME8xCzAJBgNVBAYTAlVT"
  "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKTAnBgNVBAMTIERpZ2lDZXJ0IFRMUyBS"
  "U0EgU0hBMjU2IDIwMjAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC"
  "AQEAwUuzZUdwvN1PWNvsnO3DZuUfMRNUrUpmRh8sCuxkB+Uu3Ny5CiDt3+PE0J6a"
  "qXodgojlEVbbHp9YwlHnLDQNLtKS4VbL8Xlfs7uHyiUDe5pSQWYQYE9XE0nw6Ddn"
  "g9/n00tnTCJRpt8OmRDtV1F0JuJ9x8piLhMbfyOIJVNvwTRYAIuE//i+p1hJInuW"
  "raKImxW8oHzf6VGo1bDtN+I2tIJLYrVJmuzHZ9bjPvXj1hJeRPG/cUJ9WIQDgLGB"
  "Afr5yjK7tI4nhyfFK3TUqNaX3sNk+crOU6JWvHgXjkkDKa77SU+kFbnO8lwZV21r"
  "eacroicgE7XQPUDTITAHk+qZ9QIDAQABo4IBgjCCAX4wEgYDVR0TAQH/BAgwBgEB"
  "/wIBADAdBgNVHQ4EFgQUt2ui6qiqhIx56rTaD5iyxZV2ufQwHwYDVR0jBBgwFoAU"
  "A95QNVbRTLtm8KPiGxvDl7I90VUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG"
  "CCsGAQUFBwMBBggrBgEFBQcDAjB2BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGG"
  "GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBABggrBgEFBQcwAoY0aHR0cDovL2Nh"
  "Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNydDBCBgNV"
  "HR8EOzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRH"
  "bG9iYWxSb290Q0EuY3JsMD0GA1UdIAQ2MDQwCwYJYIZIAYb9bAIBMAcGBWeBDAEB"
  "MAgGBmeBDAECATAIBgZngQwBAgIwCAYGZ4EMAQIDMA0GCSqGSIb3DQEBCwUAA4IB"
  "AQCAMs5eC91uWg0Kr+HWhMvAjvqFcO3aXbMM9yt1QP6FCvrzMXi3cEsaiVi6gL3z"
  "ax3pfs8LulicWdSQ0/1s/dCYbbdxglvPbQtaCdB73sRD2Cqk3p5BJl+7j5nL3a7h"
  "qG+fh/50tx8bIKuxT8b1Z11dmzzp/2n3YWzW2fP9NsarA4h20ksudYbj/NhVfSbC"
  "EXffPgK2fPOre3qGNm+499iTcc+G33Mw+nur7SpZyEKEOxEXGlLzyQ4UfaJbcme6"
  "ce1XR2bFuAJKZTRei9AqPCCcUZlM51Ke92sRKw2Sfh3oius2FkOH6ipjv3U/697E"
  "A7sKPPcw7+uvTPyLNhBzPvOk\n"
  "-----END CERTIFICATE-----\n";


const char* host_rawcontent   = "raw.githubusercontent.com";
const char* path_rawcontent   = "/ZinggJM/GxEPD2/master/extras/bitmaps/";
const char* path_prenticedavid   = "/prenticedavid/MCUFRIEND_kbv/master/extras/bitmaps/";
const char* path_waveshare_c  = "/waveshare/e-Paper/master/RaspberryPi_JetsonNano/c/pic/";
const char* path_waveshare_py = "/waveshare/e-Paper/master/RaspberryPi_JetsonNano/python/pic/";

void showBitmapFrom_HTTP(const char* host, const char* path, const char* filename, int16_t x, int16_t y, bool with_color = true);
void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color = true,
                          const char* certificate = certificate_rawcontent);

void setup()
{
  Serial.begin(115200);
  EinkSPI.begin(Eink_SCL ,-1, Eink_SDA, Eink_CS);
  Serial.println();
  Serial.println("GxEPD_WiFi_Example");  
  display.init(115200);


  if (!WiFi.getAutoConnect() || ( WiFi.getMode() != WIFI_STA) || ((WiFi.SSID() != ssid) && String(ssid) != "........"))
  {
    Serial.println();
    Serial.print("WiFi.getAutoConnect()=");
    Serial.println(WiFi.getAutoConnect());
    Serial.print("WiFi.SSID()=");
    Serial.println(WiFi.SSID());
    WiFi.mode(WIFI_STA); // switch off AP
    Serial.print("Connecting to ");
    Serial.println(ssid);
    WiFi.begin(ssid, password);
  }
  int ConnectTimeout = 30; // 15 seconds
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
    Serial.print(WiFi.status());
    if (--ConnectTimeout <= 0)
    {
      Serial.println();
      Serial.println("WiFi connect timeout");
      return;
    }
  }
  Serial.println();
  Serial.println("WiFi connected");

  // Print the IP address
  Serial.println(WiFi.localIP());

  drawBitmaps_200x200();
  drawBitmaps_other();

  Serial.println("GxEPD_WiFi_Example done");
}

void loop(void)
{
}

void drawBitmaps_200x200()
{
  int16_t x = (display.width() - 200) / 2;
  int16_t y = (display.height() - 200) / 2;
  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "logo200x200.bmp", fp_rawcontent, x, y);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "first200x200.bmp", fp_rawcontent, x, y);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "second200x200.bmp", fp_rawcontent, x, y);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "third200x200.bmp", fp_rawcontent, x, y);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "fourth200x200.bmp", fp_rawcontent, x, y);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "fifth200x200.bmp", fp_rawcontent, x, y);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "sixth200x200.bmp", fp_rawcontent, x, y);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "seventh200x200.bmp", fp_rawcontent, x, y);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "eighth200x200.bmp", fp_rawcontent, x, y);
  delay(2000);
}

void drawBitmaps_other()
{
  int16_t w2 = display.width() / 2;
  int16_t h2 = display.height() / 2;
  showBitmapFrom_HTTP("www.squix.org", "/blog/wunderground/", "chanceflurries.bmp", w2 - 50, h2 - 50, false);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "betty_1.bmp", fp_rawcontent, w2 - 100, h2 - 160);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "betty_4.bmp", fp_rawcontent, w2 - 102, h2 - 126);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "marilyn_240x240x8.bmp", fp_rawcontent, w2 - 120, h2 - 120);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "miniwoof.bmp", fp_rawcontent, w2 - 60, h2 - 80);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "test.bmp", fp_rawcontent, w2 - 100, h2 - 100);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "tiger.bmp", fp_rawcontent, w2 - 160, h2 - 120);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "tiger_178x160x4.bmp", fp_rawcontent, w2 - 89, h2 - 80);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "tiger_240x317x4.bmp", fp_rawcontent, w2 - 120, h2 - 160);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "tiger_320x200x24.bmp", fp_rawcontent, w2 - 160, h2 - 100);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "tiger16T.bmp", fp_rawcontent, w2 - 160, h2 - 100);
  delay(2000);
  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "woof.bmp", fp_rawcontent, w2 - 100, h2 - 100);
  delay(2000);
}

void drawBitmaps_test()
{
  int16_t w2 = display.width() / 2;
  int16_t h2 = display.height() / 2;
  showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "betty_4.bmp", fp_rawcontent, w2 - 102, h2 - 126);
  delay(2000);
}

static const uint16_t input_buffer_pixels = 800; // may affect performance

static const uint16_t max_row_width = 1448; // for up to 6" display 1448x1072
static const uint16_t max_palette_pixels = 256; // for depth <= 8

uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24
uint8_t output_row_mono_buffer[max_row_width / 8]; // buffer for at least one row of b/w bits
uint8_t output_row_color_buffer[max_row_width / 8]; // buffer for at least one row of color bits
uint8_t mono_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 b/w
uint8_t color_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 c/w

void drawBitmapFrom_HTTP_ToBuffer(const char* host, const char* path, const char* filename, int16_t x, int16_t y, bool with_color)
{
  WiFiClient client;
  bool connection_ok = false;
  bool valid = false; // valid format to be handled
  bool flip = true; // bitmap is stored bottom-to-top
  uint32_t startTime = millis();
  if ((x >= display.width()) || (y >= display.height())) return;
  display.fillScreen(GxEPD_WHITE);
  Serial.print("connecting to "); Serial.println(host);
  if (!client.connect(host, httpPort))
  {
    Serial.println("connection failed");
    return;
  }
  Serial.print("requesting URL: ");
  Serial.println(String("http://") + host + path + filename);
  client.print(String("GET ") + path + filename + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "User-Agent: GxEPD_WiFi_Example\r\n" +
               "Connection: close\r\n\r\n");
  Serial.println("request sent");
  while (client.connected())
  {
    String line = client.readStringUntil('\n');
    if (!connection_ok)
    {
      connection_ok = line.startsWith("HTTP/1.1 200 OK");
      if (connection_ok) Serial.println(line);
      //if (!connection_ok) Serial.println(line);
    }
    if (!connection_ok) Serial.println(line);
    //Serial.println(line);
    if (line == "\r")
    {
      Serial.println("headers received");
      break;
    }
  }
  if (!connection_ok) return;
  // Parse BMP header
  if (read16(client) == 0x4D42) // BMP signature
  {
    int32_t fileSize = read32(client);
    int32_t creatorBytes = read32(client);
    int32_t imageOffset = read32(client); // Start of image data
    int32_t headerSize = read32(client);
    int32_t width  = read32(client);
    int32_t height = read32(client);
    uint16_t planes = read16(client);
    uint16_t depth = read16(client); // bits per pixel
    int32_t format = read32(client);
    int32_t bytes_read = 7 * 4 + 3 * 2; // read so far
    (void) creatorBytes;
    if ((planes == 1) && ((format == 0) || (format == 3))) // uncompressed is handled, 565 also
    {
      Serial.print("File size: "); Serial.println(fileSize);
      Serial.print("Image Offset: "); Serial.println(imageOffset);
      Serial.print("Header size: "); Serial.println(headerSize);
      Serial.print("Bit Depth: "); Serial.println(depth);
      Serial.print("Image size: ");
      Serial.print(width);
      Serial.print('x');
      Serial.println(height);
      // BMP rows are padded (if needed) to 4-byte boundary
      uint32_t rowSize = (width * depth / 8 + 3) & ~3;
      if (depth < 8) rowSize = ((width * depth + 8 - depth) / 8 + 3) & ~3;
      if (height < 0)
      {
        height = -height;
        flip = false;
      }
      uint16_t w = width;
      uint16_t h = height;
      if ((x + w - 1) >= display.width())  w = display.width()  - x;
      if ((y + h - 1) >= display.height()) h = display.height() - y;
      valid = true;
      uint8_t bitmask = 0xFF;
      uint8_t bitshift = 8 - depth;
      uint16_t red, green, blue;
      bool whitish = false;
      bool colored = false;
      if (depth == 1) with_color = false;
      if (depth <= 8)
      {
        if (depth < 8) bitmask >>= depth;
        //bytes_read += skip(client, 54 - bytes_read); //palette is always @ 54
        bytes_read += skip(client, imageOffset - (4 << depth) - bytes_read); // 54 for regular, diff for colorsimportant
        for (uint16_t pn = 0; pn < (1 << depth); pn++)
        {
          blue  = client.read();
          green = client.read();
          red   = client.read();
          client.read();
          bytes_read += 4;
          whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
          colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
          if (0 == pn % 8) mono_palette_buffer[pn / 8] = 0;
          mono_palette_buffer[pn / 8] |= whitish << pn % 8;
          if (0 == pn % 8) color_palette_buffer[pn / 8] = 0;
          color_palette_buffer[pn / 8] |= colored << pn % 8;
          //Serial.print("0x00"); Serial.print(red, HEX); Serial.print(green, HEX); Serial.print(blue, HEX);
          //Serial.print(" : "); Serial.print(whitish); Serial.print(", "); Serial.println(colored);
        }
      }
      display.fillScreen(GxEPD_WHITE);
      uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset;
      //Serial.print("skip "); Serial.println(rowPosition - bytes_read);
      bytes_read += skip(client, rowPosition - bytes_read);
      for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line
      {
        if (!connection_ok || !(client.connected() || client.available())) break;
        delay(1); // yield() to avoid WDT
        uint32_t in_remain = rowSize;
        uint32_t in_idx = 0;
        uint32_t in_bytes = 0;
        uint8_t in_byte = 0; // for depth <= 8
        uint8_t in_bits = 0; // for depth <= 8
        uint16_t color = GxEPD_WHITE;
        for (uint16_t col = 0; col < w; col++) // for each pixel
        {
          yield();
          if (!connection_ok || !(client.connected() || client.available())) break;
          // Time to read more pixel data?
          if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3)
          {
            uint32_t get = in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain;
            uint32_t got = read8n(client, input_buffer, get);
            while ((got < get) && connection_ok)
            {
              //Serial.print("got "); Serial.print(got); Serial.print(" < "); Serial.print(get); Serial.print(" @ "); Serial.println(bytes_read);
              uint32_t gotmore = read8n(client, input_buffer + got, get - got);
              got += gotmore;
              connection_ok = gotmore > 0;
            }
            in_bytes = got;
            in_remain -= got;
            bytes_read += got;
          }
          if (!connection_ok)
          {
            Serial.print("Error: got no more after "); Serial.print(bytes_read); Serial.println(" bytes read!");
            break;
          }
          switch (depth)
          {
            case 24:
              blue = input_buffer[in_idx++];
              green = input_buffer[in_idx++];
              red = input_buffer[in_idx++];
              whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
              colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
              break;
            case 16:
              {
                uint8_t lsb = input_buffer[in_idx++];
                uint8_t msb = input_buffer[in_idx++];
                if (format == 0) // 555
                {
                  blue  = (lsb & 0x1F) << 3;
                  green = ((msb & 0x03) << 6) | ((lsb & 0xE0) >> 2);
                  red   = (msb & 0x7C) << 1;
                }
                else // 565
                {
                  blue  = (lsb & 0x1F) << 3;
                  green = ((msb & 0x07) << 5) | ((lsb & 0xE0) >> 3);
                  red   = (msb & 0xF8);
                }
                whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
                colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
              }
              break;
            case 1:
            case 4:
            case 8:
              {
                if (0 == in_bits)
                {
                  in_byte = input_buffer[in_idx++];
                  in_bits = 8;
                }
                uint16_t pn = (in_byte >> bitshift) & bitmask;
                whitish = mono_palette_buffer[pn / 8] & (0x1 << pn % 8);
                colored = color_palette_buffer[pn / 8] & (0x1 << pn % 8);
                in_byte <<= depth;
                in_bits -= depth;
              }
              break;
          }
          if (whitish)
          {
            color = GxEPD_WHITE;
          }
          else if (colored && with_color)
          {
            color = GxEPD_RED;
          }
          else
          {
            color = GxEPD_BLACK;
          }
          uint16_t yrow = y + (flip ? h - row - 1 : row);
          display.drawPixel(x + col, yrow, color);
        } // end pixel
      } // end line
    }
    Serial.print("bytes read "); Serial.println(bytes_read);
  }
  Serial.print("loaded in "); Serial.print(millis() - startTime); Serial.println(" ms");
  if (!valid)
  {
    Serial.println("bitmap format not handled.");
  }
}

void showBitmapFrom_HTTP(const char* host, const char* path, const char* filename, int16_t x, int16_t y, bool with_color)
{
  Serial.println(); Serial.print("downloading file \""); Serial.print(filename);  Serial.println("\"");
  drawBitmapFrom_HTTP_ToBuffer(host, path, filename, x, y, with_color);
  display.update();
}

void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color, const char* certificate)
{
  // Use WiFiClientSecure class to create TLS connection
#if USE_BearSSL
  BearSSL::WiFiClientSecure client;
#else
  WiFiClientSecure client;
#endif
  bool connection_ok = false;
  bool valid = false; // valid format to be handled
  bool flip = true; // bitmap is stored bottom-to-top
  uint32_t startTime = millis();
  if ((x >= display.width()) || (y >= display.height())) return;
  display.fillScreen(GxEPD_WHITE);
  Serial.print("connecting to "); Serial.println(host);
#if defined (ESP8266)
  client.setBufferSizes(4096, 4096); // doesn't help
  if (fingerprint) client.setFingerprint(fingerprint);
#elif defined (ESP32)
  if (certificate) client.setCACert(certificate);
#endif
  if (!client.connect(host, httpsPort))
  {
    Serial.println("connection failed");
    return;
  }
  Serial.print("requesting URL: ");
  Serial.println(String("https://") + host + path + filename);
  client.print(String("GET ") + path + filename + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "User-Agent: GxEPD_WiFi_Example\r\n" +
               "Connection: close\r\n\r\n");
  Serial.println("request sent");
  while (client.connected())
  {
    String line = client.readStringUntil('\n');
    if (!connection_ok)
    {
      connection_ok = line.startsWith("HTTP/1.1 200 OK");
      if (connection_ok) Serial.println(line);
      //if (!connection_ok) Serial.println(line);
    }
    if (!connection_ok) Serial.println(line);
    //Serial.println(line);
    if (line == "\r")
    {
      Serial.println("headers received");
      break;
    }
  }
  if (!connection_ok) return;
  // Parse BMP header
  if (read16(client) == 0x4D42) // BMP signature
  {
    int32_t fileSize = read32(client);
    int32_t creatorBytes = read32(client);
    int32_t imageOffset = read32(client); // Start of image data
    int32_t headerSize = read32(client);
    int32_t width  = read32(client);
    int32_t height = read32(client);
    uint16_t planes = read16(client);
    uint16_t depth = read16(client); // bits per pixel
    int32_t format = read32(client);
    int32_t bytes_read = 7 * 4 + 3 * 2; // read so far
    (void) creatorBytes;
    if ((planes == 1) && ((format == 0) || (format == 3))) // uncompressed is handled, 565 also
    {
      Serial.print("File size: "); Serial.println(fileSize);
      Serial.print("Image Offset: "); Serial.println(imageOffset);
      Serial.print("Header size: "); Serial.println(headerSize);
      Serial.print("Bit Depth: "); Serial.println(depth);
      Serial.print("Image size: ");
      Serial.print(width);
      Serial.print('x');
      Serial.println(height);
      // BMP rows are padded (if needed) to 4-byte boundary
      uint32_t rowSize = (width * depth / 8 + 3) & ~3;
      if (depth < 8) rowSize = ((width * depth + 8 - depth) / 8 + 3) & ~3;
      if (height < 0)
      {
        height = -height;
        flip = false;
      }
      uint16_t w = width;
      uint16_t h = height;
      if ((x + w - 1) >= display.width())  w = display.width()  - x;
      if ((y + h - 1) >= display.height()) h = display.height() - y;
      valid = true;
      uint8_t bitmask = 0xFF;
      uint8_t bitshift = 8 - depth;
      uint16_t red, green, blue;
      bool whitish = false;
      bool colored = false;
      if (depth == 1) with_color = false;
      if (depth <= 8)
      {
        if (depth < 8) bitmask >>= depth;
        //bytes_read += skip(client, 54 - bytes_read); //palette is always @ 54
        bytes_read += skip(client, imageOffset - (4 << depth) - bytes_read); // 54 for regular, diff for colorsimportant
        for (uint16_t pn = 0; pn < (1 << depth); pn++)
        {
          blue  = client.read();
          green = client.read();
          red   = client.read();
          client.read();
          bytes_read += 4;
          whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
          colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
          if (0 == pn % 8) mono_palette_buffer[pn / 8] = 0;
          mono_palette_buffer[pn / 8] |= whitish << pn % 8;
          if (0 == pn % 8) color_palette_buffer[pn / 8] = 0;
          color_palette_buffer[pn / 8] |= colored << pn % 8;
          //Serial.print("0x00"); Serial.print(red, HEX); Serial.print(green, HEX); Serial.print(blue, HEX);
          //Serial.print(" : "); Serial.print(whitish); Serial.print(", "); Serial.println(colored);
        }
      }
      display.fillScreen(GxEPD_WHITE);
      uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset;
      //Serial.print("skip "); Serial.println(rowPosition - bytes_read);
      bytes_read += skip(client, rowPosition - bytes_read);
      for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line
      {
        if (!connection_ok || !(client.connected() || client.available())) break;
        delay(1); // yield() to avoid WDT
        uint32_t in_remain = rowSize;
        uint32_t in_idx = 0;
        uint32_t in_bytes = 0;
        uint8_t in_byte = 0; // for depth <= 8
        uint8_t in_bits = 0; // for depth <= 8
        uint16_t color = GxEPD_WHITE;
        for (uint16_t col = 0; col < w; col++) // for each pixel
        {
          yield();
          if (!connection_ok || !(client.connected() || client.available())) break;
          // Time to read more pixel data?
          if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3)
          {
            uint32_t get = in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain;
            uint32_t got = read8n(client, input_buffer, get);
            while ((got < get) && connection_ok)
            {
              //Serial.print("got "); Serial.print(got); Serial.print(" < "); Serial.print(get); Serial.print(" @ "); Serial.println(bytes_read);
              uint32_t gotmore = read8n(client, input_buffer + got, get - got);
              got += gotmore;
              connection_ok = gotmore > 0;
            }
            in_bytes = got;
            in_remain -= got;
            bytes_read += got;
          }
          if (!connection_ok)
          {
            Serial.print("Error: got no more after "); Serial.print(bytes_read); Serial.println(" bytes read!");
            break;
          }
          switch (depth)
          {
            case 24:
              blue = input_buffer[in_idx++];
              green = input_buffer[in_idx++];
              red = input_buffer[in_idx++];
              whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
              colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
              break;
            case 16:
              {
                uint8_t lsb = input_buffer[in_idx++];
                uint8_t msb = input_buffer[in_idx++];
                if (format == 0) // 555
                {
                  blue  = (lsb & 0x1F) << 3;
                  green = ((msb & 0x03) << 6) | ((lsb & 0xE0) >> 2);
                  red   = (msb & 0x7C) << 1;
                }
                else // 565
                {
                  blue  = (lsb & 0x1F) << 3;
                  green = ((msb & 0x07) << 5) | ((lsb & 0xE0) >> 3);
                  red   = (msb & 0xF8);
                }
                whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish
                colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
              }
              break;
            case 1:
            case 4:
            case 8:
              {
                if (0 == in_bits)
                {
                  in_byte = input_buffer[in_idx++];
                  in_bits = 8;
                }
                uint16_t pn = (in_byte >> bitshift) & bitmask;
                whitish = mono_palette_buffer[pn / 8] & (0x1 << pn % 8);
                colored = color_palette_buffer[pn / 8] & (0x1 << pn % 8);
                in_byte <<= depth;
                in_bits -= depth;
              }
              break;
          }
          if (whitish)
          {
            color = GxEPD_WHITE;
          }
          else if (colored && with_color)
          {
            color = GxEPD_RED;
          }
          else
          {
            color = GxEPD_BLACK;
          }
          uint16_t yrow = y + (flip ? h - row - 1 : row);
          display.drawPixel(x + col, yrow, color);
        } // end pixel
      } // end line
    }
    Serial.print("bytes read "); Serial.println(bytes_read);
  }
  Serial.print("loaded in "); Serial.print(millis() - startTime); Serial.println(" ms");
  if (!valid)
  {
    Serial.println("bitmap format not handled.");
  }
  client.stop();
}

void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color, const char* certificate)
{
  Serial.println(); Serial.print("downloading file \""); Serial.print(filename);  Serial.println("\"");
  drawBitmapFrom_HTTPS_ToBuffer(host, path, filename, fingerprint, x, y, with_color, certificate);
  display.update();
}

uint16_t read16(WiFiClient& client)
{
  // BMP data is stored little-endian, same as Arduino.
  uint16_t result;
  ((uint8_t *)&result)[0] = client.read(); // LSB
  ((uint8_t *)&result)[1] = client.read(); // MSB
  return result;
}

int32_t read32(WiFiClient& client)
{
  // BMP data is stored little-endian, same as Arduino.
  int32_t result;
  ((uint8_t *)&result)[0] = client.read(); // LSB
  ((uint8_t *)&result)[1] = client.read();
  ((uint8_t *)&result)[2] = client.read();
  ((uint8_t *)&result)[3] = client.read(); // MSB
  return result;
}

#if USE_BearSSL

uint32_t skip(BearSSL::WiFiClientSecure& client, int32_t bytes)
{
  int32_t remain = bytes;
  uint32_t start = millis();
  while (client.connected() && (remain > 0))
  {
    if (client.available())
    {
      int16_t v = client.read();
      remain--;
    }
    else delay(1);
    if (millis() - start > 2000) break; // don't hang forever
  }
  return bytes - remain;
}

uint32_t read(BearSSL::WiFiClientSecure& client, uint8_t* buffer, int32_t bytes)
{
  int32_t remain = bytes;
  uint32_t start = millis();
  while (client.connected() && (remain > 0))
  {
    if (client.available())
    {
      int16_t v = client.read();
      *buffer++ = uint8_t(v);
      remain--;
    }
    else delay(1);
    if (millis() - start > 2000) break; // don't hang forever
  }
  return bytes - remain;
}

#endif

uint32_t skip(WiFiClient& client, int32_t bytes)
{
  int32_t remain = bytes;
  uint32_t start = millis();
  while ((client.connected() || client.available()) && (remain > 0))
  {
    if (client.available())
    {
      int16_t v = client.read();
      (void) v;
      remain--;
    }
    else delay(1);
    if (millis() - start > 2000) break; // don't hang forever
  }
  return bytes - remain;
}

uint32_t read8n(WiFiClient& client, uint8_t* buffer, int32_t bytes)
{
  int32_t remain = bytes;
  uint32_t start = millis();
  while ((client.connected() || client.available()) && (remain > 0))
  {
    if (client.available())
    {
      int16_t v = client.read();
      *buffer++ = uint8_t(v);
      remain--;
    }
    else delay(1);
    if (millis() - start > 2000) break; // don't hang forever
  }
  return bytes - remain;
}[/pre]

发表于 2022-8-13 17:03 | 显示全部楼层
你是我的神
 楼主| 发表于 2022-8-13 17:38 | 显示全部楼层

一起学习,一起进步。
发表于 2022-8-17 11:31 | 显示全部楼层
请问,楼主可以使用GXEPD2点亮吗 我折腾了一下点亮不了
 楼主| 发表于 2022-8-17 21:44 | 显示全部楼层
本帖最后由 topdog 于 2022-8-17 22:27 编辑
liangfengxiao 发表于 2022-8-17 11:31
请问,楼主可以使用GXEPD2点亮吗 我折腾了一下点亮不了

这款墨水屏型号是GDEP015OC1。
e ink outdoor display,Anti UV display,(Out of Stock)1.54 inch e-paper display anti-ultraviolet (e-paper-display.com)

GxEPD2库显示GDEP015OC1不再可用,这款亚马逊已经下架。
GxEPD2_BW<GxEPD2_154, MAX_HEIGHT(GxEPD2_154)> display(GxEPD2_154(/*CS=10*/ SS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); // GDEP015OC1 no longer available


驱动:
[pre]/*
* Copyright (c) 2019, Mahyar Koshkouei
* Released under ISC License.
*
* Library to operate the GDEP015OC1 display.
*/

#ifndef MK_GDEP015OC1_H
#define        MK_GDEP015OC1_H

/* User required definitions. */
#ifndef MK_GDEP_READ_BUSY_PIN
#error "MK_GDEP_READ_BUSY_PIN was not defined."
#endif

#ifndef MK_GDEP_RESET_DISPLAY
#error "MK_GDEP_SET_RESET_PIN was not defined."
#endif

#ifndef MK_GDEP_SET_DC_PIN
#error "MK_GDEP_SET_DC_PIN was not defined."
#endif

#ifndef MK_GDEP_SET_CS_PIN
#error "MK_GDEP_SET_CS_PIN was not defined."
#endif

#ifndef MK_GDEP_SET_SCLK_PIN
#error "MK_GDEP_SET_SCLK_PIN was not defined."
#endif

#ifndef MK_GDEP_SET_SDI_PIN
#error "MK_GDEP_SET_SDI_PIN was not defined."
#endif

#ifndef MK_GDEP_SEND_SPI_BYTE
#error "MK_GDEP_SEND_SPI_BYTE was not defined."
#endif

/* Display resolution */
#define GDEP015OC1_DISPLAY_WIDTH                200
#define GDEP015OC1_DISPLAY_HEIGHT                200

/* GDEP015OC1 commands */
#define DRIVER_OUTPUT_CONTROL                        0x01
#define BOOSTER_SOFT_START_CONTROL                0x0C
#define GATE_SCAN_START_POSITION                0x0F
#define DEEP_SLEEP_MODE                                0x10
#define DATA_ENTRY_MODE_SETTING                        0x11
#define SW_RESET                                0x12
#define TEMPERATURE_SENSOR_CONTROL                0x1A
#define MASTER_ACTIVATION                        0x20
#define DISPLAY_UPDATE_CONTROL_1                0x21
#define DISPLAY_UPDATE_CONTROL_2                0x22
#define WRITE_RAM                                0x24
#define WRITE_VCOM_REGISTER                        0x2C
#define WRITE_LUT_REGISTER                        0x32
#define SET_DUMMY_LINE_PERIOD                        0x3A
#define SET_GATE_TIME                                0x3B
#define BORDER_WAVEFORM_CONTROL                        0x3C
#define SET_RAM_X_ADDRESS_START_END_POSITION        0x44
#define SET_RAM_Y_ADDRESS_START_END_POSITION        0x45
#define SET_RAM_X_ADDRESS_COUNTER                0x4E
#define SET_RAM_Y_ADDRESS_COUNTER                0x4F
#define TERMINATE_FRAME_READ_WRITE                0xFF

enum lut_selection_e {
        LUT_NONE,        /* Uses previously used LUT? */
        LUT_FULL,
        LUT_PARTIAL
};

const uint8_t lut_full_update[] =
{
    0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22,
    0x66, 0x69, 0x69, 0x59, 0x58, 0x99, 0x99, 0x88,
    0x00, 0x00, 0x00, 0x00, 0xF8, 0xB4, 0x13, 0x51,
    0x35, 0x51, 0x51, 0x19, 0x01, 0x00
};

const uint8_t lut_partial_update[] =
{
    0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

/**
* Transmits a given buffer over SPI.
*
* @param buf buffer to transmit
* @param len length of buffer
*/
void send_spi_buffer(const uint8_t *buf, const uint_fast8_t len)
{
        // Select data transmission
        MK_GDEP_SET_DC_PIN(1);

        // Select display for SPI communication
        MK_GDEP_SET_CS_PIN(0);

        for(uint_fast8_t i = 0; i < len; i++)
                MK_GDEP_SEND_SPI_BYTE(buf);

        MK_GDEP_SET_CS_PIN(1);
}

/**
* Send command over SPI to the display.
*
* @param cmd Command to send.
*/
void send_disp_command(const uint8_t cmd)
{
        // Select command transmission
        MK_GDEP_SET_DC_PIN(0);

        // Select display for SPI communication
        MK_GDEP_SET_CS_PIN(0);

        MK_GDEP_SEND_SPI_BYTE(cmd);

        MK_GDEP_SET_CS_PIN(1);
}

/**
* Waits until the display is idle.
*/
inline void disp_wait_until_idle(void)
{
        while(MK_GDEP_READ_BUSY_PIN);
}

/**
* Initialise the connected display.
*/
inline void init_display(const enum lut_selection_e lut)
{
        MK_GDEP_RESET_DISPLAY();

        send_disp_command(DRIVER_OUTPUT_CONTROL);
        const uint8_t doc_data[] = {
                0xC7, // Low 8 bits for display height
                0x00, // High 8 bits
                0x00
                //        0b00000000
                //          UUUUU
                //               0   GD: Selects gate layout of panel
                //                0  SM: Disable interleaved mode
                //                 0 TB: Scanning direction of gate driver
        };
        send_spi_buffer(doc_data, sizeof (doc_data));

        send_disp_command(BOOSTER_SOFT_START_CONTROL);
        const uint8_t bssc_data[] = {0xD7, 0xD6, 0x9D};
        send_spi_buffer(bssc_data, sizeof (bssc_data));

        send_disp_command(WRITE_VCOM_REGISTER);
        const uint8_t wvr_data[] = {0xA8};
        send_spi_buffer(wvr_data, sizeof (wvr_data));

        send_disp_command(SET_DUMMY_LINE_PERIOD);
        const uint8_t sdlp_data[] = {0x1A};
        send_spi_buffer(sdlp_data, sizeof (sdlp_data));

        send_disp_command(SET_GATE_TIME);
        const uint8_t sgt_data[] = {0x08};
        send_spi_buffer(sgt_data, sizeof (sgt_data));

        send_disp_command(DATA_ENTRY_MODE_SETTING);
        const uint8_t dems_data[] = {0x03};
        send_spi_buffer(dems_data, sizeof (dems_data));

        switch(lut)
        {
        case LUT_FULL:
                send_disp_command(WRITE_LUT_REGISTER);
                send_spi_buffer(lut_full_update, sizeof (lut_full_update));
                break;
               
        case LUT_PARTIAL:
                send_disp_command(WRITE_LUT_REGISTER);
                send_spi_buffer(lut_partial_update, sizeof(lut_partial_update));
                break;
               
        default:
                break;
        };
}

void disp_set_memory_area(const uint8_t x_start, const uint8_t y_start,
                          const uint8_t x_end, const uint8_t y_end)
{
        send_disp_command(SET_RAM_X_ADDRESS_START_END_POSITION);
        /* x point must be the multiple of 8 or the last 3 bits will be ignored */
        uint8_t set_ramx_addr[] = {(x_start >> 3), (x_end >> 3)};
        send_spi_buffer(set_ramx_addr, sizeof (set_ramx_addr));

        send_disp_command(SET_RAM_Y_ADDRESS_START_END_POSITION);
        uint8_t set_ramy_addr[] = {y_start, 0x00, y_end, 0x00};
        send_spi_buffer(set_ramy_addr, sizeof (set_ramy_addr));
}

void set_memory_pointer(uint8_t x, uint8_t y)
{
        send_disp_command(SET_RAM_X_ADDRESS_COUNTER);
        /* x point must be the multiple of 8 or the last 3 bits will be ignored */
        uint8_t set_ramx_addr_count[] = {x >> 3};
        send_spi_buffer(set_ramx_addr_count, sizeof (set_ramx_addr_count));

        send_disp_command(SET_RAM_Y_ADDRESS_COUNTER);
        uint8_t set_ramy_addr_count[] = {y, 0x00};
        send_spi_buffer(set_ramy_addr_count, sizeof (set_ramy_addr_count));

        disp_wait_until_idle();
}

void disp_clear_frame_memory(const uint8_t color)
{
        disp_set_memory_area(0, 0,
                        GDEP015OC1_DISPLAY_WIDTH - 1, GDEP015OC1_DISPLAY_HEIGHT - 1);
        set_memory_pointer(0, 0);
        send_disp_command(WRITE_RAM);

        // Select data transmission
        MK_GDEP_SET_DC_PIN(1);

        // Select display for SPI communication
        MK_GDEP_SET_CS_PIN(0);

        /* send the colour data */
        uint_fast16_t i = (GDEP015OC1_DISPLAY_WIDTH / 8) * GDEP015OC1_DISPLAY_HEIGHT;
        do
        {
                MK_GDEP_SEND_SPI_BYTE(color);
        }
        while(--i);

        MK_GDEP_SET_CS_PIN(1);
}

void disp_display_frame(void)
{
        send_disp_command(DISPLAY_UPDATE_CONTROL_2);
        const uint8_t duc2_data[] = {0xC4};
        send_spi_buffer(duc2_data, sizeof (duc2_data));

        send_disp_command(MASTER_ACTIVATION);
        send_disp_command(TERMINATE_FRAME_READ_WRITE);

        disp_wait_until_idle();
}

void disp_sleep(void)
{
        send_disp_command(DEEP_SLEEP_MODE);

        const uint8_t sleep_data[] = {0x01};
        send_spi_buffer(sleep_data, sizeof (sleep_data));
}

void disp_set_block(const uint8_t x, const uint8_t y,
                    const uint8_t x_end, const uint8_t y_end,
                    const uint8_t pattern)
{

        uint16_t pixels = (((x_end + 1) - x) * ((y_end + 1) - y));

        disp_set_memory_area(x, y, x_end, y_end);
        set_memory_pointer(x, y);
        send_disp_command(WRITE_RAM);

        // Select data transmission
        MK_GDEP_SET_DC_PIN(1);

        // Select display for SPI communication
        MK_GDEP_SET_CS_PIN(0);

        /* send the pattern data */
        do
        {
                MK_GDEP_SEND_SPI_BYTE(pattern);
        }
        while(--pixels);

        MK_GDEP_SET_CS_PIN(1);
}

void disp_write_image(const uint8_t x, const uint8_t y,
                      const uint8_t x_end, const uint8_t y_end,
                      const uint8_t *img, const uint_fast16_t len)
{
        disp_set_memory_area(x, y, x_end, y_end);
        set_memory_pointer(x, y);
        send_disp_command(WRITE_RAM);

        // Select data transmission
        MK_GDEP_SET_DC_PIN(1);

        // Select display for SPI communication
        MK_GDEP_SET_CS_PIN(0);

        /* send the pattern data */
        for(uint_fast16_t i = 0; i < len; i++)
                MK_GDEP_SEND_SPI_BYTE(img);

        MK_GDEP_SET_CS_PIN(1);
}

#endif        /* MK_GDEP015OC1_H */[/pre]
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino爱好者

GMT+8, 2022-12-9 14:08 , Processed in 0.072950 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表