基于 Arduino 的淋浴间调频收音机
组件和用品
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 6 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 |
关于这个项目
几年前,我买了一个中国淋浴房,安装了带无线电的TR028控制系统。不幸的是,有一天我发现这个系统完全死了。没有人能修理它,所以我又买了一个更便宜的淋浴收音机。大约一年后它就死了。
我家以前淋浴间里有收音机,所以我开始研究这些收音机是如何制作的。在 TR028 系统内部,我发现了一个奇怪的 TEA5767 模块。一些搜索让我知道它是一个小型廉价的 FM 收音机模块。在搜索结果中,我发现了另一个有趣的 FM 收音机模块 - RDA5807。它与 TEA5767 非常相似,但具有 RDS、音量控制和低音增强功能。所以我决定在我的新项目中使用 RDA5807。
上网查了一下,发现了几个使用RDA5807模块的项目:
- 主要项目激发了我的愿景。
- 我发现了一个很好的信号强度标志和更多有用信息的变化。
- 另一种变体。
- 很棒的无线电模块库
- 有用的信息(俄语)。
- 再来一个
有可能重复这些项目之一,但没有人完全符合我的愿景。
我的愿景是:
- 带有触摸屏的设备,以确保防水结构。 (我使用了死掉的 TR0289 带有触摸面板的外壳)。
- 收音机
- 几个最喜欢的电台预设
- 音量控制
- 自动电台搜索功能
- 可以记住搜索过的电台
- 显示当前时间的时钟。
- 开/关功能
- 灯光控制
- 一些次要信息显示,例如机舱内的温度、RDS。
在速卖通上,我以不到 10 欧元的价格购买了 RDA5807、带 32kb EEPROM 的 Tiny RTC、PAM8403、NOKIA 5110 LCD、LM2596 模块并开始实验。
我最后得到了什么:
- 具有 2 行 (!) RDS 的 FM 收音机
- 最喜欢的电台的 6 个预设
- 自动或手动调整
- 可以将喜爱的广播电台存储到 6 个预设之一中
- 音量和低音增强控制
- 淋浴间灯控制
- 时钟蚂蚁日历
- RSSI(无线电信号强度指示器)
- 立体声模式指示灯
- 开/关功能
项目部分图片
对于诺基亚 5110 显示器,我找到了一个不错的库
了解 TR028 触控面板的工作原理。实际上它是 2 列 X 7 行键盘。为了操作它,我使用了这个库。
组装好的板子放在盒子里。您会注意到我已经拆焊了 USB 插座并直接焊接了电缆。用于连接 PC 和未来软件改进的可能性。
工作原理:
- 连接电源后收音机无法打开。如果电力线不稳定或断电,这可以防止广播播放。第一次打开收音机时,您必须连接电源,几秒钟后按下电源键。收音机将播放上次播放的电台,音量为 03。操作模式为 VOLUME 控制。要关闭收音机,只需按电源键。设备将关闭 LCD、LCD 背光、放大器和 LED/卤素灯。
- 要查找广播电台,您可以按“Mod”按钮选择自动或手动调谐模式。通过按“<”或“>”按钮,收音机将搜索频率降低或增加的电台。要存储找到的广播电台,请按“Mem”按钮,您将有 4 秒钟的时间选择要存储到的六个预设之一。
- 要查看当前日期,请按 I(信息)键。日期将显示 4 秒。这部分代码可以优化,因为它使用了一个 delay() 函数。
- 要调整时钟,请在听到一小时结束信号(时间信号)时按住 D 键至少 2 秒,或在某个精确时钟上查看一小时的最后几秒。松开 D 键以设置 hh.00.00。如果您的时钟从 15 分钟延迟到 1 分钟,则分秒将设置为 00,小时将增加 1,如果您的时钟从 1 分钟到 15 分钟很急,则在调整程序后只有分秒会设置为 00 .
我会改变什么:
- RTC 模块上的谐振器具有更好的精度,但时钟调整功能可以解决这个问题。
- 5110 LCD 更大更亮。可能是一些 1、8" 或 2.0" 彩色 LCD,因为有时很难读取项目中使用的 NOKIA 5110 LCD 上的信息。
- PAM8403 放大器到具有 2x15W 输出功率的 PAM8610 或具有相同特性的 TDA7297。
结论:
我很高兴我的新项目是如何运作的。工作1个月后,除时钟精度外未发现任何问题。
我不是程序员,所以可以更好地优化代码。我的C/C++编程经验一年左右,自学。这是我第一个使用 Arduino 平台的有用项目,也是我在集线器上共享的第一个项目。请理解并原谅我可能的错误和我的英语不好。
如果您有任何问题,请不要犹豫,在评论或 PM 中提问。
更新 1.:次要的硬件和软件更新。
硬件 - 在 12V 线上安装了 2A 保险丝。只是出于安全考虑。
软件 - 添加了 586 行 menu
<代码>=代码> 1;
这将在按下电源键后恢复到 VOLUME 模式。
更新 2:
不幸的是,我收音机里的液晶显示器快坏了。
所以我在寻找便宜的诺基亚 5110 LCD 替代品。我不会安装新的 5110 LCD,因为它小且难以阅读。我想我会用 1.8" TFT LCD 进行试验。好的方面 - 它更大、更亮、分辨率更高。坏方面 - 我相信 1.8" TFT 会消耗更多至关重要的资源。
欢迎您提供有关 LCD 更换的建议。
代码
- 项目草图
- 信号强度符号
项目草图Arduino
///////////////////////////////////////////// ////////////////// 基于Arduino的淋浴FM收音机项目//// Arduino NANO、RDA5807M、RTC、EEPROM、LCD5110、热敏电阻//////// ////////////////////////////////////////////////// /////////#include//http://www.rinkydinkelectronics.com/library.php?id=48#include //https://github. com/cyberp/AT24Cx#include //包含Arduino IDE#include //http://www.mathertel.de/Arduino/RadioLibrary.aspx#include // http://www.mathertel.de/Arduino/RadioLibrary.aspx#include //https://github.com/adafruit/RTClib#include //http://www. matertel.de/Arduino/RadioLibrary.aspx#include //http://playground.arduino.cc/Code/Keypad#define MAXmenu 4#define ledPin 13#define blPin 7//定义镲片上的keypadschar keys[7][2] ={ {'L', 'P'}, //LED, POWER {'I', 'D'}, //INFO, DISPLAY {'1', '2 '}, //预设 {'3', '4'}, //从 1 {'5', '6'}, //到 6 {'M', 'm'}, //M ODE, MEM {'<', '>'} //down, up};byte rowPins[7] ={11, 12, 10, 17, 9, 16, 8}; //连接到keypadbyte的行引脚 colPins[2] ={15, 14}; //连接到键盘的列引脚//Keypad kpd =Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );Keypad keypad =Keypad( makeKeymap(keys), rowPins, colPins, 7, 2 ); boolean bass =0, dspl =0, memdisplay =0, mempress =0, adj =0;boolean ledPin_state, power_state;int menu;int volume, volumeOld =5;int frequency,frequencyOld;int txtl =0, temparray =0; int samples[5];unsigned int status[6];unsigned long timeprevious =0, timeprev =0;// EEPROM objectAT24CX mem;RTC_DS1307 rtc;//(clk, din, dc, ce, rst)LCD5110 lcd(6, 5, 4, 2, 3);//创建RDA5807芯片radioRDA5807M radio的实例;///得到RDS解析器RDSParser rds;extern unsigned char SmallFont[];extern uint8_t signal5[];extern uint8_t signal4[];extern uint8_t signal3[];extern uint8_t signal2[];extern uint8_t signal1[];//----------------------------------------SETUP---- ------------------------------//void setup(){analogReference(EXTERNAL); Serial.begin(9600); Wire.begin(); // 初始化 Radio radio.init(); radio.debugEnable(); //初始化屏幕lcd.InitLCD();液晶显示器.clrScr(); //lcd.setContrast(45); //如果默认值不好调整 lcd.setFont(SmallFont); lcd.enableSleep(); //待机模式 power_state =0; //连接电源时不要“开机”(待机模式)//初始化键盘keypad.addStatedEventListener(keypadEvent); // 为这个键盘添加一个事件监听器 keypad.setHoldTime(1500); pinMode(ledPin,输出); // 将数字引脚设置为输出。 pinMode(blPin,输出); // 将数字引脚设置为输出。数字写入(ledPin,低); // 关闭 LED。数字写入(blPin,低); // 关闭 BL(待机模式) ledPin_state =digitalRead(ledPin); // 存储初始 LED 状态。 LED 点亮时为高电平。 //取消注释是否需要调整rtc /*if (!rtc.isrunning()) { Serial.println("RTC is NOT running!"); // 以下行将 RTC 设置为编译此草图的日期和时间 rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // 此行使用明确的日期和时间设置 RTC,例如设置 // 2014 年 1 月 21 日凌晨 3 点,您将调用: //rtc.adjust(DateTime(2018, 3, 13, 22, 33, 0) ); }*/ // 读取上次频率的值 =mem.readInt(201);体积 =2; //开始菜单的音量=1; //如果(音量<0)音量=0,则在开始时显示音量模式;如果(音量> 15)音量 =15;如果(频率 <0)频率 =0;如果(频率> 210)频率=210;写寄存器(0x02,0xC00d); // 将 0xC00d 写入 Reg.2(软复位,启用,RDS,)//bbz canal(frequency); // 设置RDS数据的信息链。 radio.attachReceiveRDS(RDS_process); rds.attachServicenNameCallback(DisplayServiceName); //rds.attachTimeCallback(DisplayTime); //以备将来使用。 RDS 信号弱时非常不准确。 rds.attachTextCallback(DisplayText);}//-----------------------安装结束-------------- ----------------------////------------------------ --LOOP----------------------------------------//void loop(){如果(频率!=频率老){频率老=频率; mem.writeInt(201, 频率);运河(频率); } if (volume !=volumeOld) { volumeOld =volume; WriteReg(5, 0x84D0 | 音量); } //读取键盘字符 key =keypad.getKey(); //检查RDS数据 radio.checkRDS(); // 每 0.6 秒读取一次温度探测器 5 次并计算平均浮点平均值; unsigned long timenow =millis(); if ((unsigned long)(timenow - timeprevious)> 600) { timeprevious =timenow;样本[临时数组] =模拟读取(A7);临时数组++; } if (temparray ==5) { // 计算读数平均值 average =0; for (int i =0; i <5; i++) { 平均值 +=样本 [i];打印温度(平均);临时数组 =0; } // MEM 显示超时 4 秒并输入 unsigned long dabar =millis(); if (mempress ==1) { timeprev =dabar;内存显示 =1;记忆压印 =0; } if (memdisplay ==1) { if ((unsigned long)(dabar - timeprev) <4000) { memdisplay =1; } else { memdisplay =0; } } /*时间调整说明: 1. 运行串口监视器 2. 设置9600 boud 3. 按回车激活串口读取 4. 写入hXX,其中XX 是某个时间服务器上的当前小时读数,按回车键调整RTC 小时数。串行监视器应该写“小时 XX” 5. 写 mXX,其中 XX 是在某个时间服务器上读取的当前分钟数,然后按 Enter 键来调整 RTC 分钟数。串行监视器应该写“分钟 XX” 6. 写 sXX,其中 XX 是在某个时间服务器上读取的当前秒数,然后按 Enter 键来调整 RTC 秒数。串口监视器应该写“Seconds XX” 7.您只能调整小时。 IE。当夏令时更改时。 8. 如果您只需要更正时间,您可以只调整秒。 9. 如果 RTC 必须从零调整(年、月、日等),取消注释 RTC 调整语句行并上传草图。 */ 现在日期时间 =rtc.now(); if (Serial.available()> 0) { char t =Serial.read(); switch (t) { case ('h'):{ unsigned int hours =Serial.parseInt(); rtc.adjust(DateTime(now.year(), now.month(), now.day(), hours, now.minute(), now.second())); Serial.println(F("小时")); Serial.println(小时);休息; } case ('m'):{ unsigned int mins =Serial.parseInt(); rtc.adjust(DateTime(now.year(), now.month(), now.day(), now.hour(), mins, now.second())); Serial.println(F("分钟")); Serial.println(分钟);休息; } case ('s'):{ unsigned int sec =Serial.parseInt(); rtc.adjust(DateTime(now.year(), now.month(), now.day(), now.hour(), now.minute(), sec)); Serial.println(F("秒")); Serial.println(sec);休息; } } } //在LCD上显示各种信息 printSignalStrength();打印线();打印时间();打印频率();打印立体声();打印模式();打印菜单();打印日期(); lcd.update();}//------------------------循环结束-------------- ----------------------//void printSignalStrength() //从 0000 到 1111 (0-63){ unsigned int sig;读取状态();信号 =状态 [1] / 1000; if ((sig>=0) &&(sig <=12)) { lcd.drawBitmap(1, 1, signal1, 17, 6); } if ((sig>=13) &&(sig <=24)) { lcd.drawBitmap(1, 1, signal2, 17, 6); } if ((sig>=25) &&(sig <=36)) { lcd.drawBitmap(1, 1, signal3, 17, 6); } if ((sig>=37) &&(sig <=48)) { lcd.drawBitmap(1, 1, signal4, 17, 6); } if (sig>=49) { lcd.drawBitmap(1, 1, signal5, 17, 6); }}void printLines(){ lcd.drawLine(0, 9, 84, 9); lcd.drawLine(0, 39, 84, 39);}void printTemp(float average) //可以优化:){ average /=5;平均值 =1023 / 平均值 - 1;平均值 =51700 / 平均值;漂浮斯坦哈特;斯坦哈特 =平均值 / 50000; // (R/Ro) steinhart =log(steinhart); // ln(R/Ro) 斯坦哈特 /=3950; // 1/B * ln(R/Ro) steinhart +=1.0 / (25 + 273.15); // + (1/To) steinhart =1.0 / steinhart; // 反转 steinhart -=273.15; // 摄氏度转换 int tmp =round(steinhart); lcd.printNumI(tmp, 60, 1, 2); lcd.print(F("~C"), 72, 1);}//在RDS模式下更新LCD显示屏上的ServiceName文本.void DisplayServiceName(char *name){ lcd.print(name, 18, 22);}void DisplayText(char *text){ //滚动第二行 RDS lcd.print(text, txtl, 30); txtl =txtl - 66; if (txtl ==-396) txtl =0;}void printTime(){ DateTime now =rtc.now(); lcd.printNumI(now.hour(), 24, 1, 2, '0'); lcd.print(":", 36, 1); lcd.printNumI(now.minute(), 42, 1, 2, '0');}void printDate(){ if (dspl ==1) { //检查是否按下了显示键 ClearRDS();现在日期时间 =rtc.now(); lcd.printNumI(now.year(), 12, 22, 4); lcd.print(".", 36, 22); lcd.printNumI(now.month(), 42, 22, 2, '0'); lcd.print(".", 54, 22); lcd.printNumI(now.day(), 60, 22, 2, '0'); int dw =now.dayOfTheWeek(); switch (dw) { case 0:lcd.print(F("Sekmadeinis"), CENTER, 30); //sunday F() 宏保存 sdram break;案例 1:lcd.print(F("Pirmadeinis"), CENTER, 30); //星期一等...休息;案例 2:lcd.print(F("Antradienis"), CENTER, 30);休息;案例 3:lcd.print(F("Treciadienis"), CENTER, 30);休息;案例 4:lcd.print(F("Ketvirtadienis"), CENTER, 30);休息;案例 5:lcd.print(F("Penktadienis"), CENTER, 30);休息;案例 6:lcd.print(F("Sestadienis"), CENTER, 30);休息;液晶显示器更新();延迟(4000); //不是最佳的ClearRDS(); dspl =0; }}void printMode(){ lcd.print(F("MODE "), 0, 41);}void printMenu(){ if (menu ==1) { lcd.print(F("VOLUME "), 30, 41); if (volume <0) { lcd.print(F("XX"), 72, 41); } else lcd.printNumI(volume + 1, 72, 41, 2, '0'); } if (menu ==2) { lcd.print(F("AUTO-TUNE"), 30, 41); } if (menu ==3) { lcd.print(F("MAN.-TUNE"), 30, 41); } if (menu ==4) { lcd.print(F(" BASS "), 30, 41); if (bass ==0) { lcd.print(F("OFF"), 66, 41); } else lcd.print(F("ON"), 66, 41); }}void printFreq() //显示当前频率{ int frHundr, frDec;无符号整数 fr; fr =870 + 频率; frHundr =fr / 10; frDec =fr % 10; lcd.printNumI(frHundr, 30, 12, 3); lcd.print(F("."), 48, 12); lcd.printNumI(frDec, 54, 12, 1); lcd.print(F("MHz"), 66, 12);}void printStereo(){ if (memdisplay ==1) { //如果按下了 MEM 键 lcd.print(F("MEM>"), 0 , 12); } //立体检测 else if ((status[0] &0x0400) ==0) lcd.print(F("( )"), 0, 12); //表示 MONO else lcd.print (F("(ST)"), 0, 12); //表示立体声}void search(byte direc) //自动搜索{ byte i; //向上或向下查找 if (!direc) WriteReg(0x02, 0xC30d);否则 WriteReg(0x02, 0xC10d); for (i =0; i <10; i++) { delay(200);读取状态();如果(状态 [0] &0x4000){ 频率 =状态 [0] &0x03ff;休息; } }}void canal( int canal) //直接频率{ byte numberH, numberL; numberH =运河>> 2; numberL =((canal &3) <<6 | 0x10); Wire.beginTransmission(0x11); Wire.write(0x03); Wire.write(numberH); // 将频率写入位 15:6,设置调谐位 Wire.write(numberL); Wire.endTransmission();}//RDA5807_adrs=0x10;// I2C地址RDA芯片,用于顺序访问int Readstatus(){ Wire.requestFrom(0x10, 12); for (int i =0; i <6; i++) { status[i] =256 * Wire.read() + Wire.read(); } Wire.endTransmission();}//RDA5807_adrr=0x11;// I2C-Address RDA Chip for random Accessvoid WriteReg(byte reg, unsigned int valor){ Wire.beginTransmission(0x11); Wire.write(reg); Wire.write(勇气>> 8); Wire.write(valor &0xFF); Wire.endTransmission(); //delay(50);}void RDS_process(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4) { rds.processData(block1, block2, block3, block4);}void ClearRDS(){ lcd.print(" ", 0, 22); lcd.print(" ", 0, 30);}// 处理一些特殊事件.void keypadEvent(KeypadEvent key, KeyState kpadState) { switch (kpadState) { /* 另一种调整时间的方法: 1. 按住键 D 至少 2 秒,当您听到小时结束时间信号时,或者在某个准确的时钟上看到最后一小时的秒数。 2. 松开 D 键调整 hh.00.00。 3. 时间调整,如果你的时钟从15分钟到1分钟迟到,分秒为00,小时加1。 4.如果你的时钟从1到15分钟很急,只有分秒为00 . */ case HOLD:if (key =='D' &&power_state ==1) { adj =1; } 休息;案例发布: if (key =='D' &&adj ==1) { DateTime now =rtc.now(); if (now.minute()>=45 &&now.minute() <=59) { rtc.adjust(DateTime(now.year(), now.month(), now.day(), now.hour() + 1, 0, 0)); } if (now.minute()>=1 &&now.minute() <=15) { rtc.adjust(DateTime(now.year(), now.month(), now.day(), now.hour( ), 0, 0)); } adj =0; } 休息; case PRESSED:if (key =='M' &&power_state ==1) { memdisplay =0;菜单++;如果(菜单> MAXmenu)菜单=1; } if (key =='>' &&power_state ==1) { memdisplay =0; switch (menu) { case 1:if (volume <0) { if (bass ==0) { WriteReg(0x02, 0xD00D);音量 =0; } if (bass ==1) { WriteReg(0x02, 0xC00D);音量 =0; } } else 音量++;如果(音量> 15)音量 =15;休息;情况2:搜索(0);清除RDS();休息;情况 3:频率++;如果(频率> 210)频率=210; // 上限频率 ClearRDS();休息;情况 4:如果(低音 ==0){ 低音 =1;写寄存器(0x02,0xD00D); } 休息; } } if (key =='<' &&power_state ==1) { memdisplay =0; switch (menu) { case 1:volume--; if (volume <0) { WriteReg(0x02, 0x800D); //音量=0; } 休息;案例2:搜索(1);清除RDS();休息;情况3:频率--;如果(频率 <0)频率 =0;清除RDS();休息;情况 4:如果(低音 ==1){ 低音 =0;写寄存器(0x02,0xC00D); } 休息; } } // LED 灯亮/灭 if (key =='L' &&power_state ==1) { digitalWrite(ledPin, !digitalRead(ledPin)); ledPin_state =digitalRead(ledPin); // 记住 LED 状态,点亮或熄灭。 } // 打开或关闭“电源”(待机模式) if (key =='P') { digitalWrite(blPin, !digitalRead(blPin)); power_state =digitalRead(blPin); if (power_state ==0) { lcd.enableSleep();数字写入(ledPin,低);其他 lcd.disableSleep();体积 =2;菜单 =1; } if (key =='1' &&power_state ==1) { switch (memdisplay) { case 0:frequency =mem.readInt(110);清除RDS();休息;情况 1:mem.writeInt(110, 频率);内存显示 =0;休息; } } if (key =='2' &&power_state ==1) { switch (memdisplay) { case 0:frequency =mem.readInt(120);清除RDS();休息;情况 1:mem.writeInt(120, 频率);内存显示 =0;休息; } } if (key =='3' &&power_state ==1) { switch (memdisplay) { case 0:frequency =mem.readInt(130);清除RDS();休息;情况 1:mem.writeInt(130, 频率);内存显示 =0;休息; } } if (key =='4' &&power_state ==1) { switch (memdisplay) { case 0:frequency =mem.readInt(140);清除RDS();休息;情况 1:mem.writeInt(140, 频率);内存显示 =0;休息; } } if (key =='5' &&power_state ==1) { switch (memdisplay) { case 0:frequency =mem.readInt(150);清除RDS();休息;情况 1:mem.writeInt(150, 频率);内存显示 =0;休息; } } if (key =='6' &&power_state ==1) { switch (memdisplay) { case 0:frequency =mem.readInt(160);清除RDS();休息;情况 1:mem.writeInt(160, 频率);内存显示 =0;休息; } } if (key =='m' &&power_state ==1) { mempress =1; } else { mempress =0; } if (key =='I' &&power_state ==1) { dspl =1; } 休息; }}
信号强度符号C/C++
#if defined(__AVR__) #include#define imagedatatype const uint8_t#elif defined(__PIC32MX__) #define PROGMEM #define imagedatatype const unsigned char#elif defined(__arm__) #define PROGMEM #define imagedatatype const unsigned char#endifimagedatatype signal5[] PROGMEM={0xC1, 0xC2, 0xC4, 0xFF, 0xC4, 0xC2, 0xC1, 0xC0, 0xE0, 0xC0, 0xF0, 0xC0, 0xF0, 0xF0, 0xF8, 0xC4, 0xC2, 0xF8, 0xF8, 0xC2 , };imagedatatype signal4[] PROGMEM={0xC1, 0xC2, 0xC4, 0xFF, 0xC4, 0xC2, 0xC1, 0xC0, 0xE0, 0xC0, 0xF0, 0xC0, 0xF8, 0xC4, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0 };imagedatatype signal3[] PROGMEM={0xC1, 0xC2, 0xC4, 0xFF, 0xC4, 0xC2, 0xC1, 0xC0, 0xE0, 0xC0, 0xF0, 0xC0, 0xF8, 0xC0, 0xC, 0xC0, 0xC, 0,0xC, 0xC0, 0xC0, 0xC;imagedatatype signal2[] PROGMEM={0xC1, 0xC2, 0xC4, 0xFF, 0xC4, 0xC2, 0xC1, 0xC0, 0xE0, 0xC0, 0xF0, 0xC0, 0xC0, 0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0 imagedatatype signal1[] PROGMEM={0xC1, 0xC2, 0xC4, 0xFF, 0xC4, 0xC2, 0xC1, 0xC0, 0xE0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, // 0x0010 (16) 个像素0xC0, };
示意图
制造工艺