OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 2772|回复: 2

微雪的墨水屏数据收发、黑白红三色图片抖动算法浅析

[复制链接]

9

主题

19

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
217
金钱
217
注册时间
2017-3-24
在线时间
20 小时
发表于 2022-6-2 19:56:37 | 显示全部楼层 |阅读模式
本帖最后由 abcrazy 于 2022-6-5 13:17 编辑

某鱼买了个黑白红三色4.2寸墨水屏,连邮费27元,网上资料微雪的最好用,微雪的 ESP32 例程是 Arduino 的,安装乐鑫环境 github 还是一如既往的无法下载,几经艰辛,终于编译出固件。微雪的例程还是挺好的,各型号各版本的墨水屏只要一个固件支持完。
本人习惯用乐鑫的 ESP-IDF 环境编译,参考了微雪的色阶、抖动算法,编译了固件(只支持4.2B黑白红墨水屏),添加了 airkiss 智能配网。

============ 以下是购买的电子价签 ===============
IMG_20220506_135612.jpg
IMG_20220506_135603.jpg

IMG_20220506_150850.jpg

IMG20220601162209.jpg

特别说明一下,那个屏幕背面的 NFC 是贴上去的,跟屏幕没有任何电路连接,CC2510芯片靠 2.4G 收发数据。

================= 以下是电路图 =====================
CC2510管脚.png

SES电子价签电路板_各引脚测试点.jpg

SES电子价签电路板背面_各引脚测试点.jpg


=============== 以下是资料附件 ===============



4.2inch-e-paper-b-specification.pdf (2.51 MB, 下载次数: 0)
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

9

主题

19

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
217
金钱
217
注册时间
2017-3-24
在线时间
20 小时
 楼主| 发表于 2022-6-4 11:16:44 | 显示全部楼层
本帖最后由 abcrazy 于 2022-6-5 11:22 编辑

第二部分:微雪 ESP8266 / ESP32 开发板 WIFI 数据传送分析
对应的微雪源码在 1 楼附件 墨水屏资料.zip
微雪可以适配多个版本型号的墨水屏 ESP32 开发板精髓:在网页中处理图片,再把处理后的数据发送到墨水屏。
优点是 MCU 的内存可以很小,小到无法装下一帧内容,只负责接收一小部分数据,然后马上发送到墨水屏中。把需要消耗大量内存解码 png 、 mpeg、 bmp 图片的任务交给电脑浏览器或手机浏览器。
网页.png

以下是第一段接受到的数据内容:
  1. POST /EPDo_ HTTP/1.1
  2. Host: 192.168.11.80
  3. Connection: keep-alive
  4. Content-Length: 0
  5. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36
  6. Content-Type: text/plain;charset=UTF-8
  7. Accept: */*
  8. Origin: null
  9. Accept-Encoding: gzip, deflate
  10. Accept-Language: zh-CN,zh;q=0.9

  11. POST /aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaiodaLOAD_ HTTP/1.1
  12. Host: 192.168.11.80
  13. Connection: keep-alive
  14. Content-Length: 0
  15. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36
  16. Content-Type: text/plain;charset=UTF-8
  17. Accept: */*
  18. Origin: null
  19. Accept-Encoding: gzip, deflate
  20. Accept-Language: zh-CN,zh;q=0.9

复制代码
一、确定墨水屏型号:
分析第1行:POST /EPDo_ HTTP/1.1
1.EPD:墨水屏的特征码
2.o:墨水屏的型号
参考文件:E-Paper_ESP32_Driver_Board_Code\Loader_esp32wf\epd.h
  1. /* The set of pointers on 'init', 'load' and 'show' functions, title and code */
  2. struct EPD_dispInfo
  3. {
  4.     int(*init)(); // Initialization
  5.     void(*chBk)();// Black channel loading
  6.     int next;     // Change channel code
  7.     void(*chRd)();// Red channel loading
  8.     void(*show)();// Show and sleep
  9.     char*title;   // Title of an e-Paper
  10. };

  11. /* Array of sets describing the usage of e-Papers ----------------------------*/
  12. EPD_dispInfo EPD_dispMass[] =
  13. {
  14.     { EPD_Init_1in54,                EPD_loadA,                -1  ,        0,                                EPD_1IN54_Show,                        "1.54 inch"                },// a 0
  15.     { EPD_Init_1in54b,                EPD_loadB,                0x13,        EPD_loadA,                EPD_showB,                        "1.54 inch b"        },// b 1
  16.     { EPD_Init_1in54c,                EPD_loadA,                0x13,        EPD_loadA,                EPD_showB,                        "1.54 inch c"        },// c 2
  17.     { EPD_Init_2in13,                EPD_loadC,                -1  ,        0,                                EPD_showA,                        "2.13 inch"                },// d 3
  18.     { EPD_Init_2in13b,                EPD_loadA,                0x13,        EPD_loadA,                EPD_showB,                        "2.13 inch b"        },// e 4
  19.     { EPD_Init_2in13b,                EPD_loadA,                0x13,        EPD_loadA,                EPD_showB,                        "2.13 inch c"        },// f 5
  20.     { EPD_Init_2in13d,                EPD_loadA,                -1  ,        0,                                EPD_showD,                        "2.13 inch d"        },// g 6
  21.     { EPD_Init_2in7,                EPD_loadA,                 1  ,        0,                                EPD_showB,                        "2.7 inch"                },// h 7
  22.     { EPD_Init_2in7b,                EPD_loadA,                0x13,        EPD_loadA,                EPD_showB,                        "2.7 inch b"        },// i 8
  23.     { EPD_Init_2in9,                EPD_loadA,                -1  ,        0,                                EPD_showA,                        "2.9 inch"                },// j 9
  24.     { EPD_Init_2in9b,                EPD_loadA,                0x13,        EPD_loadA,                EPD_showB,                        "2.9 inch b"        },// k 10
  25.     { EPD_Init_2in9b,                EPD_loadA,                0x13,        EPD_loadA,                EPD_showB,                        "2.9 inch c"        },// l 11
  26.     { EPD_Init_2in9d,                EPD_loadA,                -1  ,        0,                                EPD_2IN9D_Show,                "2.9 inch d"        },// M 12
  27.     { EPD_Init_4in2,                EPD_loadA,                -1  ,        0,                                EPD_showB,                        "4.2 inch"                },// N 13
  28.     { EPD_Init_4in2b,                EPD_loadA,                0x13,        EPD_loadA,                EPD_showB,                        "4.2 inch b"        },// O 14
  29.     { EPD_Init_4in2b,                EPD_loadA,                0x13,        EPD_loadA,                EPD_showB,                        "4.2 inch c"        },// P 15
  30.     { EPD_5in83__init,                EPD_loadD,                -1  ,        0,                                EPD_showC,                        "5.83 inch"                },// Q 16
  31.     { EPD_5in83b__init,                EPD_loadE,                -1  ,        0,                                EPD_showC,                        "5.83 inch b"        },// R 17
  32.     { EPD_5in83b__init,                EPD_loadE,                -1  ,        0,                                EPD_showC,                        "5.83 inch c"        },// S 18
  33.     { EPD_7in5__init,                EPD_loadD,                -1  ,        0,                                EPD_showC,                        "7.5 inch"                },// T 19   
  34.     { EPD_7in5__init,                EPD_loadE,                -1  ,        0,                                EPD_showC,                        "7.5 inch b"        },// u 20
  35.     { EPD_7in5__init,                EPD_loadE,                -1  ,        0,                                EPD_showC,                        "7.5 inch c"        },// v 21
  36.     { EPD_7in5_V2_init,                EPD_loadAFilp,        -1  ,        0,                                EPD_7IN5_V2_Show,        "7.5 inch V2"        },// w 22
  37.     { EPD_7in5B_V2_Init,        EPD_loadA,                0x13,        EPD_loadAFilp,        EPD_7IN5_V2_Show,        "7.5 inch B V2"        },// x 23
  38.     { EPD_7IN5B_HD_init,        EPD_loadA,                0x26,        EPD_loadAFilp,        EPD_7IN5B_HD_Show,        "7.5 inch B HD"        },// y 24
  39.         { EPD_5IN65F_init,                EPD_loadG,                -1  ,        0,                                EPD_5IN65F_Show,        "5.65 inch F "        },// z 25
  40.         { EPD_7IN5_HD_init,                EPD_loadA,                -1        ,        0,                                EPD_7IN5_HD_Show,        "7.5 inch HD"        },// A 26
  41.         { EPD_3IN7_1Gray_Init,        EPD_loadA,                -1        ,        0,                                EPD_3IN7_1Gray_Show,"3.7 inch"                },// 27
  42.         { EPD_2IN66_Init,                EPD_loadA,                -1        ,        0,                                EPD_2IN66_Show,                "2.66 inch"                },// 28
  43.         { EPD_5in83b_V2_init,        EPD_loadA,                0x13,        EPD_loadAFilp,        EPD_showC,                        "5.83 inch B V2"},// 29
  44.         { EPD_Init_2in9b_V3,        EPD_loadA,                0x13,        EPD_loadA,                EPD_showC,                        "2.9 inch B V3"        },// 30
  45.         { EPD_1IN54B_V2_Init,        EPD_loadA,                0x26,        EPD_loadAFilp,        EPD_1IN54B_V2_Show,        "1.54 inch B V2"},// 31
  46.         { EPD_2IN13B_V3_Init,        EPD_loadA,                0x13,        EPD_loadA,                EPD_2IN13B_V3_Show,        "2.13 inch B V3"},// 32
  47.         { EPD_Init_2in9_V2,         EPD_loadA,                -1,                0,                                EPD_2IN9_V2_Show,        "2.9 inch V2"        },// 33
  48.         { EPD_Init_4in2b_V2,        EPD_loadA,                0x13,        EPD_loadA,                EPD_4IN2B_V2_Show,        "4.2 inch b V2"        },// 34
  49.         { EPD_2IN66B_Init,                EPD_loadA,                0x26,        EPD_loadAFilp,        EPD_2IN66_Show,                "2.66 inch b"        },// 35
  50.         { EPD_Init_5in83_V2,        EPD_loadAFilp,        -1,                0,                                EPD_showC,                        "5.83 inch V2"        },// 36
  51.         { EPD_4IN01F_init,                EPD_loadG,                -1,                0,                                EPD_4IN01F_Show,        "4.01 inch f"        },// 37
  52.         { EPD_Init_2in7b_V2,        EPD_loadA,                0x26,        EPD_loadAFilp,        EPD_Show_2in7b_V2,        "2.7 inch B V2"        },// 38
  53.         { EPD_Init_2in13_V3,        EPD_loadC,                -1,         0,                                 EPD_2IN13_V3_Show,         "2.13 inch V3"        },// 39
  54.         { EPD_2IN13B_V4_Init,        EPD_loadA,                0x26,        EPD_loadA,                EPD_2IN13B_V4_Show, "2.13 inch B V4"},// 40
  55. };
复制代码
注意看后面注释,o 字母对应的是 "4.2 inch b" 型号墨水屏。
里面的参数分别对应:初始化屏幕函数指针、发送黑白色到屏幕的函数指针、切换红白色命令码、发送红白色到屏幕的函数指针、显示内容的函数指针、字符串说明。


二、接收数据--传送数据大小
iodaLOAD
当识别到“LOAD”字母时,那么前面四个字节,就是本次传送数据的大小。
特别注意:微雪的十六进制数跟网络传送字符转换:以“a”或“A”为偏移数,两个字节的 hex 组成实际对应一个字节数。
如:ioda 转为 ascii 码后分别减去 'a' 得到的 hex 为:'i'-'a'=105-97=8,'o'-'a'=111-97=14='e','d'-'a'=100-97=3,'a'-'a'=97-97=0
最后为:8e 30
我们可以数一下 POST 后面,正好有 1000 个 'a',转为 16 进制为 0x03e8
那么可以看出,微雪的十六进制规则是:第一字节减去 'a' 后得到的数是实际数一个字节低四位,第二字节减去 'a' 后得到的数是实际数一个字节高四位。小端,低地址在前,高地址在后。

三、接收数据--传送数据内容
以三色条示例,400x300 分辨率,黑白红各占 400x100 个像素点,黑色和红色实际都为 bit 0,白色为 bit 1,只是黑白和红白分别记录在两个不同的内存中。
把微雪的网页文件,放在 C:/http/ 内,安装 python 3 以上的版本,建立一个简单的服务器,输入命令行:
python -m http.server 80 --directory c:/http/

浏览器打开网页:127.0.0.1 可以得到微雪转换图片的网页,修改好网页内本机的 IP 、选择好墨水屏型号、选择图片、转换图片为想要的效果
点击上载数据。

看回命令行返回的数据,每种颜色正好发 10 次,每次 1000 字节:
分析2.png 黑色是 400x100 = 40000 像素,而正常来说 1bit = 1 像素,那么黑色实际需要 40000/8 = 5000 字节。
而接收的数据有 1000x10= 10000 字节,这么算起来,两个字节对应实际的一个字节。
这里又是以 'a' 为基础的 hex 数据流,变为正常的 hex,那么有 10000 个 0,对应实际 5000 个字节的 0x00,代表黑白屏的全屏 1/3 黑色。
10次 1000 字节  'a' 后,接着是 20 次 1000 字节的 'p',转为 hex 是 20000 个 'f',对应实际 10000 个字节的 0xff,代表黑白屏的全屏 2/3 白色。

分析3.png
发送完黑白屏数据后,接着发红白屏数据。
检查到有 'NEXT'  标志后,就应该发送转屏码到墨水屏了,黑白屏命令码为 0x10,转为红白屏命令码为 0x13。
转为红白屏后,对应的写入墨水屏的对应函数也许会一样,也许会不同,具体看屏型号了。
再看看发送的数据,首先是 20 次 1000 字节的 'p',再接着 10 次 1000 字节的 'a'。
跟前面差不多,红白屏前 2/3 数据是 0xFF 代表白色,后 1/3 是 0x00 代表红色。

分析4.png
最后,检测到 ’SHOW' 则显示数据。
微雪的js网页图片转换.zip (7.1 KB, 下载次数: 2)
回复 支持 反对

使用道具 举报

9

主题

19

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
217
金钱
217
注册时间
2017-3-24
在线时间
20 小时
 楼主| 发表于 2022-6-5 10:00:19 | 显示全部楼层
本帖最后由 abcrazy 于 2022-6-5 11:10 编辑

第三部分:微雪 ESP8266 / ESP32 开发板 JS 网页算法分析
微雪图片处理只有 2 种算法:色阶法、抖动法。
色阶法非常简单容易理解,稍微看一下代码,并结合百度介绍原理,很快明白如何操作。
这里只说说微雪的抖动法如何实现。抖动原理也不是很难理解,关键是在 MCU 里面实现,需要面对 2 个问题:
第一个问题是内存,抖动算法至少需要 2 行 RGB 屏宽空间,如:400x300 的墨水屏,则至少需要 400 x 2 x 3 = 2400 字节内存。这里是抛开解码 JPG 、PNG、BMP 图片需要的最少内存。
第二个问题是精度,抖动算法是先算出当前像素原色与目标颜色的误差,再将此误差分成 16 份按 3 : 5 : 1 : 7 的比例累加到附近的像素点。我们需要显示的点是 unsigned char 类型,算法需要 float 类型,最后需要 float 类型转换为 unsigned char 类型,负数和大于 255 的数怎么处理?如果如果不注意这点,显示的结果会不尽人意。
算法1.png

一、准备工具:谷歌浏览器、微雪 JS 网页(2楼附件)
操作步骤:
1.双击【index.html】文件。
2.谷歌浏览器右上角【...】 --> 【更多工具】 --> 【开发者工具】 --> 【Sources】 --> 【ScriptC.js】 --> 点击行号 55 放置断点。
3.选择图片、墨水屏类型、抖动算法,即可在断点处停止。
算法2.png

  1. var aInd=0;
  2. var bInd=1;
  3. var errArr=new Array(2);
  4. errArr[0]=new Array(dW);
  5. errArr[1]=new Array(dW);
  6. for (var i=0;i<dW;i++)
  7.         errArr[bInd][i]=[0,0,0];
  8. for (var j=0;j<dH;j++){
  9.         var y=dY+j;
  10.         if ((y<0)||(y>=sH)){
  11.                 for (var i=0;i<dW;i++,index+=4)setVal(pDst,index,(i+j)%2==0?1:0);
  12.                 continue;
  13.         }
  14.         aInd=((bInd=aInd)+1)&1;
  15.         for (var i=0;i<dW;i++)errArr[bInd][i]=[0,0,0];
  16.         for (var i=0;i<dW;i++){
  17.                 var x=dX+i;
  18.                 if ((x<0)||(x>=sW)){
  19.                         setVal(pDst,index,(i+j)%2==0?1:0);
  20.                         index+=4;
  21.                         continue;
  22.                 }
  23.                 var pos=(y*sW+x)*4;
  24.                 var old=errArr[aInd][i];
  25.                 var r=pSrc.data[pos  ]+old[0];
  26.                 var g=pSrc.data[pos+1]+old[1];
  27.                 var b=pSrc.data[pos+2]+old[2];
  28.                 var colVal = curPal[getNear(r,g,b)];
  29.                 pDst.data[index++]=colVal[0];
  30.                 pDst.data[index++]=colVal[1];
  31.                 pDst.data[index++]=colVal[2];
  32.                 pDst.data[index++]=255;
  33.                 r=(r-colVal[0]);
  34.                 g=(g-colVal[1]);
  35.                 b=(b-colVal[2]);
  36.                 if (i==0){
  37.                         errArr[bInd][i  ]=addVal(errArr[bInd][i  ],r,g,b,7.0);
  38.                         errArr[bInd][i+1]=addVal(errArr[bInd][i+1],r,g,b,2.0);
  39.                         errArr[aInd][i+1]=addVal(errArr[aInd][i+1],r,g,b,7.0);
  40.                 }else if (i==dW-1){
  41.                         errArr[bInd][i-1]=addVal(errArr[bInd][i-1],r,g,b,7.0);
  42.                         errArr[bInd][i  ]=addVal(errArr[bInd][i  ],r,g,b,9.0);
  43.                 }else{
  44.                         errArr[bInd][i-1]=addVal(errArr[bInd][i-1],r,g,b,3.0);
  45.                         errArr[bInd][i  ]=addVal(errArr[bInd][i  ],r,g,b,5.0);
  46.                         errArr[bInd][i+1]=addVal(errArr[bInd][i+1],r,g,b,1.0);
  47.                         errArr[aInd][i+1]=addVal(errArr[aInd][i+1],r,g,b,7.0);
  48.                 }
  49.         }
  50. }
复制代码
二、要点:1. 申请 2 行宽度的 RGBA 空间 errArr[2][dW][4],并全部置 0。dW 是宽度 400 像素,每个像素点是 4 字节,红绿蓝+透明度通道。
2. 在 for 循环中 aInd 永远代表当前的像素点所在行,bInd 则代表当前像素点所在行的下一行。
3. errArr 保存着当前行和下一行需要累加的值(全部都没有加上原色,循环到当前像素再加原色。跟网上其它资料计算步骤不一样)

三、基本流程:
原色:
    var pos=(y*sW+x)*4;
    pSrc.data[pos  ]

当前像素点对应累加值:
    var old=errArr[aInd];

显示前的颜色值:
    var r=pSrc.data[pos  ]+old[0];
    var g=pSrc.data[pos+1]+old[1];
    var b=pSrc.data[pos+2]+old[2];

上一步的颜色值,用色阶法,得到最接近的 黑、白、红 值,即目标值:
    var colVal = curPal[getNear(r,g,b)];

目标值填充到最终输出的数据当中,注意输出是 RGBA 带透明度通道:
    pDst.data[index++]=colVal[0];
    pDst.data[index++]=colVal[1];
    pDst.data[index++]=colVal[2];
    pDst.data[index++]=255;

误差值=原色-目标色
    r=(r-colVal[0]);
    g=(g-colVal[1]);
    b=(b-colVal[2]);


误差分成 16 份按 3 : 5 : 1 : 7 的比例累加到附近的像素点:
aInd 代表当前像素点所在行;
bInd 代表当前像素点所在行的下一行;
    errArr[bInd][i-1]=addVal(errArr[bInd][i-1],r,g,b,3.0);
    errArr[bInd][i  ]=addVal(errArr[bInd][i  ],r,g,b,5.0);
    errArr[bInd][i+1]=addVal(errArr[bInd][i+1],r,g,b,1.0);
    errArr[aInd][i+1]=addVal(errArr[aInd][i+1],r,g,b,7.0);



当前像素点为每行的第一个时,附近只有三个点,比例改为 7 : 2 : 7
    errArr[bInd][i  ]=addVal(errArr[bInd][i  ],r,g,b,7.0);
    errArr[bInd][i+1]=addVal(errArr[bInd][i+1],r,g,b,2.0);
    errArr[aInd][i+1]=addVal(errArr[aInd][i+1],r,g,b,7.0);


当前像素点为每行的最后一个时,附近只有两个点,比例改为 7 : 9
    errArr[bInd][i-1]=addVal(errArr[bInd][i-1],r,g,b,7.0);
    errArr[bInd][i  ]=addVal(errArr[bInd][i  ],r,g,b,9.0);


最后注意一点,微雪官方修改了抖动算法分 16 份的比例为 32 份,不明白其中的玄机。
算法3.png

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-22 10:37

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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