基于App控制的STM32温湿度控制系统(WIFI模块)

  • ESP8266WIFI模块
    • 模块使用说明
    • 常用AT指令
  • DHT11温湿度传感器
    • 简介
    • 模块数据的发送流程
    • 代码实现
    • μs级的延时配置
      • HAL库配置
      • 代码实现
  • 项目总框架
    • 总框架图
    • 单片机控制流程图
    • APP控制流程图
  • 项目代码实现
    • WIFI模块
    • STM32单片机(主机)
      • 初始化WIFI模块
      • 控制任务
      • DHT11数据发送给WIFI模块
    • APP的编写
    • 布局文件
    • 逻辑代码
  • BUG记录
    • WIFI模块的发送
    • 开任务之后main.c里的while不跑了

ESP8266WIFI模块

在这个项目中我选用的正点原子的WIFI模块(ATK-ESP8266),这个模块采用串口(LVTTL)与MCU(或其他串口设备)通信,内置了TCP/IP的协议栈,可以实现串口与WIFI直接的转换,通过ATK-SEP8266模块,传统的串口设备只是需要简单的串口配置,即可通过网络(WIFI)传输自己的数据。

模块使用说明

其实挺简单的,拿出你的模块看,VCC就接3.3V或者5V,GND就接GND啦,然后WIFI模块与串口设备TX接RX,RX接TX,RST的复位是低电平有效。IO-0适用于固件烧写的,低电平就是烧写模式,高电平时运行模式(默认态)

RST和IO-0一般是不用接的(一般使用只用接前4个就好了)!

常用AT指令

指令 描述
AT+CWMODE 选择 WIFI 应用模式
AT+CWJAP 加入 AP
AT+CWLAP 列出当前可用 AP
AT+CWQAP 退出与 AP 的连接
AT+CWSAP 设置 AP 模式下的参数
AT+CWLIF 查看已接入设备的 IP
AT+CWDHCP 设置 DHCP 开关
AT+CWAUTOCONN 设置 STA 开机自动连接到 wifi
AT+CIPSTAMAC 设置 STA 的 MAC 地址
AT+CIPAPMAC 设置 AP 的 MAC 地址
AT+CIPSTA 设置 STA 的 IP 地址
AT+CIPAP 设置 AP 的 IP 地址
AT+CWSTARTSMART 启动智能连接
AT+CWSTOPSMART 停止智能连接
AT+WPS 设置 WPS 功能
AT+MDNS 设置 MDNS 功能
AT+CWHOSTNAME 设置 ATK-ESP-01 Station 的主机名字

具体AT指令的使用还是得去看下模块的用户手册,这里就不做过多的赘述了。

DHT11温湿度传感器

简介

参数 Value
工作电压 3.3V~5V
湿度测量范围 20%~95%
温度测量范围 0°~50°

传感器数据输出的是未编码的二进制数据。数据(湿度、温度、整数、小数)之间应该分开处理。(如下图所示)

模块数据的发送流程


首先主机发送开始信号,即:拉低数据线,保持t1(至少18ms)时间,然后拉高数据线t2(20-40us)时间,然后读取DHT11的响应,正常的话,DHT11会拉低数据线,保持t3(40-50us)时间,作为响应信号,然后DHT11拉高数据线,保持t4(40-50us)时间后,开始输出数据。

代码实现

/*更改IO模式的封装函数*/
void DHT11_PIN_MODE(uint8_t mode)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if(mode==Intput){GPIO_InitStruct.Pin = DHT11_DATA_Pin;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(DHT11_DATA_GPIO_Port, &GPIO_InitStruct);}else{GPIO_InitStruct.Pin = DHT11_DATA_Pin;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(DHT11_DATA_GPIO_Port, &GPIO_InitStruct);}
}static uint8_t DHT11_ReadByte ( void )
{DHT11_PIN_MODE(Intput);uint8_t i, temp=0;for(i=0;i<8;i++)   {         /*每bit以50us低电平标置开始,轮询直到从机发出的50us低电平结束*/  while(Read_DHT11_DATA()==GPIO_PIN_RESET);/*DHT11 以26~28us的高电平表示“0”,以70us高电平表示“1”,*通过检测 x us后的电平即可区别这两个状 ,x 即下面的延时                 */HAL_Delay_us(40); //延时x us 这个延时需要大于数据0持续的时间即可                     if(Read_DHT11_DATA()==GPIO_PIN_SET)/* x us后仍为高电平表示数据“1” */{/* 等待数据1的高电平结束 */while(Read_DHT11_DATA()==GPIO_PIN_SET){}temp|=(uint8_t)(0x01<<(7-i));  //把第7-i位置1,MSB先行}else         // x us后为低电平表示数据“0”{                           temp&=(uint8_t)~(0x01<<(7-i)); //把第7-i位置0,MSB先行}}return temp;
}uint8_t DHT11_Read_TempAndHumidity(DHT11_Data_TypeDef *DHT11_Data)
{if(Frame_Flag.dht11flag++ >= 1000 )Frame_Flag.dht11flag = 0;    uint8_t temp;uint16_t humi_temp;/*输出模式*/DHT11_PIN_MODE(Output);/*主机拉低*/DHT11_DATA_RESET();/*延时18ms*/Delay_ms(18);/*总线拉高 主机延时30us*/DHT11_DATA_SET();HAL_Delay_us(30);   //延时30us/*主机设为输入 判断从机响应信号*/DHT11_PIN_MODE(Intput);/*判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行*/   if(Read_DHT11_DATA()==GPIO_PIN_RESET)     {/*轮询直到从机发出 的80us 低电平 响应信号结束*/  while(Read_DHT11_DATA()==GPIO_PIN_RESET);/*轮询直到从机发出的 80us 高电平 标置信号结束*/while(Read_DHT11_DATA()==GPIO_PIN_SET);/*开始接收数据*/   DHT11_Data->humi_high8Bit= DHT11_ReadByte();DHT11_Data->humi_low8bit = DHT11_ReadByte();DHT11_Data->temp_high8bit= DHT11_ReadByte();DHT11_Data->temp_low8bit = DHT11_ReadByte();DHT11_Data->check_sum    = DHT11_ReadByte();/*读取结束,引脚改为输出模式*/DHT11_PIN_MODE(Output);/*主机拉高*/DHT11_DATA_SET();/* 对数据进行处理 */                humi_temp=DHT11_Data->humi_high8Bit*100+DHT11_Data->humi_low8bit;DHT11_Data->humidity =(float)humi_temp/100;humi_temp=DHT11_Data->temp_high8bit*100+DHT11_Data->temp_low8bit;DHT11_Data->temperature=(float)humi_temp/100;   /*检查读取的数据是否正确*/temp = DHT11_Data->humi_high8Bit + DHT11_Data->humi_low8bit +DHT11_Data->temp_high8bit+ DHT11_Data->temp_low8bit;if(DHT11_Data->check_sum==temp){return SUCCESS;}else{return ERROR;}}else{return ERROR;}
}

这里需要说明的是,如果用的是FreeRTOS的话就需要开多一个定时器用来进行μs级别的计时,同时不能放在任务中跑,这样任务很容易阻塞导致系统的暂停运行。但是实际上测量温度可以通过开关中断的方式进行读取,将读取速率放在一个任务里面也可以,主要是检测的时候用的us级的上拉下拉要注意下。

μs级的延时配置

这里选用的TIM6用来做计时器

HAL库配置

由于TIM6是挂载在APB1上的,所以要APB1的值

因为我APB1Timer clocks是90MHz
所以89+1/90=1MHz,也就是1μs计数一次。

代码实现

void HAL_Delay_us(uint16_t us)
{//  uint16_t startCount = __HAL_TIM_GET_COUNTER(&htim6);
//
//  while((__HAL_TIM_GET_COUNTER(&htim6) - startCount) <= us);uint16_t differ = 0xffff-us-5;HAL_TIM_Base_Start(&htim6);__HAL_TIM_SetCounter(&htim6,differ);while(differ < 0xffff-5){differ = __HAL_TIM_GetCounter(&htim6);}HAL_TIM_Base_Stop(&htim6);}

项目总框架

讲完这个项目所用的两个外设了,现在来说一下整个项目的总体设计思路。

总框架图


如上图所示,首先我们利用AT指令初始化WIFI模块,然后WIFI模块等待APP建立TCP连接。建立完连接之后,便利用TCP间客户机与服务机之间的通信进行数据的传输。DHT11模块接收到温湿度信号后发送脉冲给单片机的IO口,IO口读取电平后将其转化成数字,再由单片机的串口发送到WIFI模块回显在APP上面。

单片机控制流程图


单片机首先初始化板上的资源(IO口、串口、WIFI模块),然后开启了串口中断,DMA传输,以及创建控制任务。开启了串口中断后就开始接收数据。如果没有接收到数据,则返回重新接收,如果有,就把数据存进缓冲区,存进缓冲区之后,程序会对缓冲区的数据进行解包处理后存进结构体对应的成员变量内,然后通过读取温度与湿度的成员变量,通过串口发送给WIFI模块,APP对数据进行回显。

APP控制流程图


在打开APP的时候,在界面中输入服务器IP地址和端口号,点击CONNECT按钮就会跟服务器建立TCP连接。然后在控制界面有开灯、关灯、播放音乐的按钮,点击可以触发对应的事件然后向WIFI模块发送信息。WIFI模块收到信息之后通过AT指令会发送数据给APP,APP处理之后可以回显到界面上。

项目代码实现

WIFI模块

首先需要利用单片机给WIFI模块通过串口传输发送以下AT指令进行初始化:

char rst[] = "AT+RESTORE\r\n";//WIFI模块重置
char cipmode[] = "AT+CIPMODE=0\r\n";//退出透传模式(因为WIFI模块在AP模式下是不需要透传的)
char mux[] = "AT+CIPMUX=1\r\n";//设置多连接
char server[] = "AT+CIPSERVER=1\r\n";//设置服务器模式

当APP(从机)与WIFI模块(主机建立连接后)需要发送如下指令:

char cipsend[] = "AT+CIPSEND=0,30\r\n";//其中的0代表连接号,30代表需要发送的数据 根据自己需求定

STM32单片机(主机)

初始化WIFI模块

 /*给大延时是为了等待模块反应*/HAL_UART_Transmit(&huart7,(uint8_t *)rst,strlen(rst),0xFFFF);HAL_Delay(5000);memset(ReceiveBuff,'\0',PackSize);HAL_Delay(1000);HAL_UART_Transmit(&huart7,(uint8_t *)cipmode,strlen(cipmode),0xFFFF);HAL_Delay(2000);memset(ReceiveBuff,'\0',PackSize);HAL_Delay(1000);HAL_UART_Transmit(&huart7,(uint8_t *)mux,strlen(mux),0xFFFF);HAL_Delay(2000);memset(ReceiveBuff,'\0',PackSize);HAL_Delay(1000);HAL_UART_Transmit(&huart7,(uint8_t *)server,strlen(server),0xFFFF);HAL_Delay(2000);memset(ReceiveBuff,'\0',PackSize);

控制任务

void Control_Task(void *argument)
{portTickType xLastWakeTime;xLastWakeTime = xTaskGetTickCount();switch(data_wifi.control_light){case 'W':LIGHT_CLOSE();Send_PackCage.light_status = 'n';break;case 'Q':LIGHT_ON();Send_PackCage.light_status = 'y';break;default:break;}switch(data_wifi.control_music){case 'A':LittleStar_InitMusic();Send_PackCage.music_status = 'l';break;case 'S':HappySong_InitMusic();Send_PackCage.music_status = 'h';break;case 'D':SuperMario_InitMusic();Send_PackCage.music_status = 's';break;case '#':musicflag = 1;break;default:HAL_TIM_PWM_Stop(&htim12, TIM_CHANNEL_1);Send_PackCage.music_status = 'n';break;}if(Frame_Flag.controlflag++ >= 1000 )Frame_Flag.controlflag = 0;   vTaskDelayUntil(&xLastWakeTime, 50 / portTICK_RATE_MS);}
}

DHT11数据发送给WIFI模块

     if(Count_Data.dht11_count >= TASK_DHT11){DHT11_Read_TempAndHumidity(&DHT11_Data);sprintf(str,"%.2f",DHT11_Data.temperature);strcat(str,fuhao);//这里利用一个符号可以在App的逻辑代码中对字符串进行分割sprintf(humi,"%f",DHT11_Data.humidity);strcat(str,humi);if(Init_FLAG == 1){HAL_UART_Transmit(&huart7,(uint8_t *)cipsend,strlen(cipsend),0xFFFF);HAL_Delay(200);HAL_UART_Transmit(&huart7,(uint8_t *)str,strlen(str),0xFFFF);}Count_Data.dht11_count = 0;}

APP的编写

布局文件

内容偏长,需要的可以复制,不需要的直接跳过就好了

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/black"android:orientation="vertical"tools:context=".MainActivity"><ImageViewandroid:layout_width="70dp"android:layout_height="70dp"android:layout_gravity="center"android:layout_marginTop="40dp"android:background="@drawable/wolf"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="Smart Home Center"android:textStyle="bold"android:textColor="@color/white"android:textSize="20sp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"/><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="25dp"android:paddingLeft="50dp"><TextViewandroid:id="@+id/textip"android:layout_width="70dp"android:layout_height="40dp"android:text="设备IP:"android:gravity="center"android:textColor="@color/white" /><EditTextandroid:id="@+id/ip"android:layout_width="250dp"android:layout_height="40dp"android:layout_toRightOf="@+id/textip"android:gravity="center"android:maxLines="1"android:textColor="@color/white" /></RelativeLayout><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:paddingLeft="50dp"><TextViewandroid:layout_width="70dp"android:layout_height="40dp"android:text="Port:"android:id="@+id/textport"android:gravity="center"android:textColor="@color/white"android:inputType="number"/><EditTextandroid:id="@+id/port"android:layout_width="250dp"android:layout_height="40dp"android:layout_toRightOf="@+id/textport"android:maxLines="1"android:textColor="@color/white" /></RelativeLayout><Buttonandroid:layout_width="150dp"android:layout_height="40dp"android:background="#00B8D4"android:layout_gravity="center"android:textColor="@color/white"android:textStyle="bold"android:text="Connect"android:onClick="Connect"/><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="25dp"android:paddingLeft="50dp"><TextViewandroid:id="@+id/textTem"android:layout_width="180dp"android:layout_height="20dp"android:text="Home Of Temperature:"android:textColor="@color/white"android:textSize="15sp"/><TextViewandroid:id="@+id/temperature"android:layout_width="60dp"android:layout_height="20dp"android:layout_toRightOf="@+id/textTem"android:textColor="@color/white"android:textSize="15sp" /></RelativeLayout><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="25dp"android:paddingLeft="50dp"><TextViewandroid:id="@+id/texthumi"android:layout_width="180dp"android:layout_height="20dp"android:text="Home Of Humidity:"android:textColor="@color/white"android:textSize="15sp" /><TextViewandroid:id="@+id/humidity"android:layout_width="35dp"android:layout_height="20dp"android:layout_toRightOf="@+id/texthumi"android:textColor="@color/white"android:textSize="15sp" /><TextViewandroid:layout_width="20dp"android:layout_height="20dp"android:id="@+id/fuhao"android:layout_toRightOf="@id/humidity"android:textColor="@color/white"android:textSize="15sp"/></RelativeLayout><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Device_Control"android:textColor="@color/white"android:layout_marginTop="10dp"android:textSize="20sp"android:layout_gravity="center"android:textStyle="bold"/><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="                   —————————————————————————"android:textColor="@color/white"android:layout_marginTop="5dp"/><RelativeLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:layout_gravity="center"><Buttonandroid:id="@+id/online"android:layout_width="120dp"android:layout_height="50dp"android:background="#0AD9F6"android:text="JudgeOnline"android:textColor="@color/white"android:textSize="12dp"android:textStyle="italic" /><TextViewandroid:id="@+id/status"android:layout_width="200dp"android:layout_height="50dp"android:layout_toRightOf="@+id/online"android:gravity="center"android:text="Wait"android:textColor="@color/white"android:textSize="20dp" /></RelativeLayout><RelativeLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:layout_gravity="center"><Buttonandroid:id="@+id/lighton"android:layout_width="150dp"android:layout_height="50dp"android:background="#0AD9F6"android:text="LIGHTON"android:textColor="@color/white"android:textStyle="italic" /><Buttonandroid:id="@+id/lightclose"android:layout_width="150dp"android:layout_height="50dp"android:layout_toRightOf="@+id/lighton"android:layout_marginLeft="20dp"android:background="#0AD9F6"android:gravity="center"android:text="LIGHTCLOSE"android:textColor="@color/white" /></RelativeLayout><RelativeLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="15dp"android:layout_gravity="center"><Buttonandroid:id="@+id/music1"android:layout_width="120dp"android:layout_height="50dp"android:background="#0AD9F6"android:text="MUSIC1"android:textColor="@color/white"android:textStyle="italic" /><Buttonandroid:id="@+id/music2"android:layout_width="120dp"android:layout_height="50dp"android:layout_toRightOf="@id/music1"android:layout_marginLeft="10dp"android:background="#0AD9F6"android:text="MUSIC2"android:textColor="@color/white"android:textStyle="italic" /><Buttonandroid:id="@+id/music3"android:layout_width="120dp"android:layout_height="50dp"android:layout_toRightOf="@id/music2"android:layout_marginLeft="10dp"android:background="#0AD9F6"android:text="MUSIC3"android:textColor="@color/white"android:textStyle="italic" /></RelativeLayout><RelativeLayoutandroid:layout_width="wrap_content"android:layout_height="match_parent"android:layout_marginTop="15dp"android:layout_gravity="center"><TextViewandroid:layout_width="120dp"android:layout_height="50dp"android:gravity="center"android:text="No Music"android:textColor="@color/white"android:textSize="20sp"android:id="@+id/musicname"/><Buttonandroid:layout_width="100dp"android:layout_height="20dp"android:layout_alignBottom="@+id/musicname"android:layout_marginLeft="10dp"android:layout_marginTop="10dp"android:layout_marginBottom="-12dp"android:id="@+id/shutdown"/></RelativeLayout></LinearLayout>

效果图

小黄鸭除外

智能家居—基于STM32的温湿度控制系统(WIFI模块)相关推荐

  1. 基于stm32和ESP8266实现wifi模块smartlink功能以及TCP连接

    一直想把wifi模块和stm32连接起来,但是网上找了很久,并没有C语言相关的库,只有基于arduino的ESP8266 C++库,这个库是没办法直接给stm32使用的,所以自己动手丰衣足食,自己手动 ...

  2. 很酷的智能家居/工业路由器两用的物联网WiFi模块 MQTT/TCP协议 MIPS+LINUX+Openwrt技术覆盖

    MT7688 支持两种运作模式:IoT gateway 模式与 IoT device 模式. 在 IoT gateway 模式中,可透过 PCIe 界面连接至 802.11ac 芯片组,并作为双频 8 ...

  3. 毕业设计 基于stm32的灯光控制系统 物联网

    基于stm32的灯光控制系统 本次设计的是基于STM32F103C8T6单片机设计的一款教室灯光控制系统 系统内有光敏电阻模块采集教室内的光照强度,光电管检测教室内是否有人 通过按键可以直接控制开灯和 ...

  4. 基于STM32的温度控制系统

    提示:记录毕设 文章目录 前言 一.任务书 1.1设计(研究)目标: 1.2设计(研究)内容: 二.代码思路 三.硬件 四.联系我们 五.设计 六.框图代码等资料 喜欢请点赞哦! 前言 基于STM32 ...

  5. 单片机课程设计:基于STM32的温湿度检监测报警系统的设计

    基于STM32的温湿度检监测报警系统 文章目录 基于STM32的温湿度检监测报警系统 前言 一.设计任务 二.系统硬件设计 1.元器件选用 2.系统模型设计 3.硬件连接 二.系统程序设计 1.程序流 ...

  6. 基于单片机的温湿度控制系统

    设计简介: 本设计是基于单片机的温湿度控制系统,主要实现以下功能: 可通过LCD1602显示温湿度和阈值: 可通过按键设置温湿度阈值: 可通过蜂鸣器进行报警. 标签:51单片机.LCD1602.DHT ...

  7. 3、★☛基于STM32的手机通过wifi控LED灯√♠★

    3.★★☞基于STM32的手机通过wifi控LED灯

  8. 基于stm32的温湿度检测案例串口通信屏显示(二)

    文章目录 前言 一.串口通信屏幕 二.DHT11测试效果与说明 三.读入数据 四.串口及中断配置 五.屏幕上显示数据 5.1.屏幕内嵌指令 5.2.静态数据 5.3.动态数据 六.效果 前言 因为st ...

  9. 15、基于STM32的温湿度超限报警器

    15.基于STM32的温湿度超限报警器 引言 1系统概述 1.1设计任务 1.2设计要求 2 方案设计与论证 2.1芯片选择方案 2.2 系统概述 2.3设计要求: 2.4系统总体设计思路 2.5各功 ...

最新文章

  1. 袁绍困局与张朝阳的雄心——类微博的狐友能让搜狐重回主战场吗
  2. javaweb-服务器输出字符数据到浏览器
  3. 【论文阅读】A Gentle Introduction to Graph Neural Networks [图神经网络入门](4)
  4. 初识Android应用程序的五大基本组件
  5. 一个IT青年北漂四年的感悟
  6. 你可能没有听说过 js中的 DOM操作还有这个: HTMLCollection 和 NodeList
  7. 机器学习速成课程 | 练习 | Google Development——编程练习:TensorFlow 编程概念
  8. 泛型数组 List c# 1613647847
  9. c++cout不使用科学计数法打印数字
  10. 数字信号处理matlab——FIR浅析1
  11. 2022跨年烟花代码(五)HTML5全屏烟花特效
  12. 达梦数据库DCA培训总结
  13. PyTorch手写字体识别
  14. 制造型企业呼叫中心搭建-SDCC呼叫中心
  15. 深夜看了张一鸣的微博,我不得不惊
  16. C++面向对象程序设计:银行储蓄管理系统
  17. xilinx FPGA IOB约束使用以及注意事项
  18. 华为鸿蒙生态班怎么上,华为联合西北工业大学开设“鸿蒙生态菁英班”: 50 人左右...
  19. cocos2d-x 菜鸟学习笔记一(跨平台编译)
  20. python字符串中占位符详解

热门文章

  1. 【数据库06】web应用程序开发的任督二脉
  2. pdf/word转化+水印+加解密(完全免费)
  3. 实战分析,薪酬缩水,(1)
  4. 如何区别有追索权与无追索权应收账款保理业务?
  5. 计算机xp bios密码设置方法,电脑开机密码怎么设置_教您各系统设置方法
  6. html a标签相关属性设置
  7. 小米air新电脑嵌入式开发环境搭建
  8. skynet服务端_skynet总体架构
  9. ue4 android 地图瓦片,Bitmap2material 3贴图快速生成软件
  10. cf1041F. Ray in the tube