STM32 Smart Desktop Fan

Directory

1. Function introduction

2. Hardware list

3. Detailed explanation of the module

1) Motor drive

2) Servo drive

3) Timing off

4) Temperature sensor

5) FLASH

6) Button control

7) Multi-level menu

8) Main function

4. The source code can be private

1. Function Introduction

(1) The input voltage is DC12-24V; power module

(2) Support different fan gears to adjust the wind speed, and each gear has a corresponding indication; the lowest is 3 gears, and the highest can be set to 5 gears;

(3) Support fan timing operation, and the fan timing time can be visualized;

(4) It is necessary to support the left and right swing of the fan, and the swing angle is not less than 120°;

(5) The fan can monitor the current ambient temperature and display it;

(6) The fan needs to support power-off memory. After power-off and power-on again, it can also remember the gear and mode before power-off.

(7) Support smart mode and manual mode switching selection:

a) Smart mode: After turning on the smart mode, the fan can automatically detect the temperature of the current environment to adjust different gears;

b) Manual mode: After turning on the manual mode, the speed and shaking of the fan are all transferred to the button control, leaving the intelligent temperature adjustment;

2. Hardware list

Main control chip STM32F103C8T6
Temperature sensor DS18b20
Motor drive

L9110

Power LM259
OLED Seven Needle
FLASH W25Q64
Servo 180°

Third, Module Detailed Explanation

1) Motor drive

L9110 is a two-channel push-pull power amplifier application-specific integrated circuit device designed for controlling and driving motors. It integrates discrete circuits into a single chip IC, which reduces the cost of peripheral components and improves the reliability of the whole machine. The chip has two TTL/CMOS compatible level inputs, which have good anti-interference; the two output terminals can directly drive the forward and reverse motion of the motor, and it has a large current driving capability, and each channel can pass 750~ 800mA continuous current, the peak current capability can reach 1.5~2.0A; at the same time, it has a low output saturation voltage drop; the built-in clamp diode can release the reverse impact current of the inductive load, so that it can drive relays, DC motors, The use of stepping motors or switching power tubes is safe and reliable. L9110 is widely used in circuits such as toy car motor drive, stepper motor drive and switching power tube.

Use PWM pins to control motor speed, different PWMs correspond to different wind speeds

2) Servo driver

Control principle:

Control the output of the steering gear by sending a PWM signal to the signal line of the steering gear;

Generally speaking, the period and duty cycle of PWM are controllable, so the duty cycle of PWM pulse directly determines the position of the output shaft.

The control of the steering gear generally requires a time base pulse of about 20ms. The high level part of the pulse is generally the angle control pulse part in the range of 0.5ms-2.5ms, and the total interval is 2ms. Taking the 180-degree angle as an example, the control relationship is as follows:

0.5ms————–0 degrees;

1.0ms————45 degrees;

1.5ms————90 degrees;

2.0ms————135 degrees;

2.5ms————180 degrees;

In the code, ARR=1999, PSC=719, by

Timing frequency=72M/ (PSC + 1) / (ARR + 1)

Know, T=20ms.

Set the duty cycle: TIM_SetCompare2(TIM2, Compare)

Proportional relationship

\frac{20ms}{1999 + 1}=\frac{0.5ms\sim 2.5ms}{Compare}< /strong>

If it is 0.5ms, then compare=50, which is 0°;

If it is 2.5ms, then compare=250, which is 180°

Reference: (36 messages) STM32f1 steering gear drive + rotation angle adjustment (including main code and calculation)_How to adjust the steering gear angle_Do not say anything about my blog-CSDN blog

Initialization code: (motor and steering gear share a PWM)

void PWM_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;

\t
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
\t
\t
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, & GPIO_InitStructure);
\t
TIM_InternalClockConfig(TIM2);
\t
\t
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 2000 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //PSC (20ms - corresponding to the steering gear)
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, & TIM_TimeBaseInitStructure);

TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit( & TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure. TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure. TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR1_Val; //CCR (output port 1)
TIM_OC3Init(TIM2, & TIM_OCInitStructure);
\t
\t
TIM_OCInitStructure. TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = CCR2_Val; //CCR (output port 2)
TIM_OC2Init(TIM2, & TIM_OCInitStructure);

  GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
  GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //Enable SWD and disable JTAG
\t
TIM_Cmd(TIM2, ENABLE);
}

3) Scheduled shutdown

Design ideas:

After the timing is turned on, enable the timer and generate an interrupt every 1s. In the interrupt function, count the number of seconds and make a judgment. After reaching the corresponding number of seconds, turn off the fan and disable the timer.

Initialization function:

void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
TIM_InternalClockConfig(TIM3);//internal clock
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//Count up
TIM_TimeBaseInitStructure.TIM_Period=10000-1;//period; the value of the ARR automatic reloader (1s counts 10,000 times)
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;//PSC prescaler value (arr and psc are configured count intervals)
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//The value of the repetition counter (advanced)
TIM_TimeBaseInit(TIM3, & amp;TIM_TimeBaseInitStructure);//Configure the time base unit
\t
TIM_ClearFlag(TIM3,TIM_FLAG_Update);
  TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
\t
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
\t
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
\t
NVIC_Init( &NVIC_InitStructure);


}

Interrupt function:

void TIM3_IRQHandler(void)//Time 1s to enter interrupt
{
if(TIM_GetFlagStatus(TIM3,TIM_IT_Update)==SET)//Get the interrupt flag bit
{
Num++;
if(Num==10) {timestop(1);}//Change the timing
if(Num==120){timestop(2);}
if(Num==7200) {timestop(3);}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
}
//In the timestop function, turn off the fan, that is, set the fan PWM to 2000. Timer disabled TIM_Cmd(TIM3,DISABLE)

4) Temperature sensor

Input and output pin initialization: (output is push-pull mode, input is pull-up mode)

void DS18B20_Output_Input(u8 cmd)
{
GPIO_InitTypeDef GPIO_InitStructure;
if(cmd)//out
{
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_10MHz;
}
else//input
{
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//Pull-up mode
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
}
GPIO_Init(GPIOA, & GPIO_InitStructure);
}

Work steps

The working steps of DS18B20 can be divided into three steps:

1. Initialize DS18B20
2. Execute ROM instructions
3. Execute DS18B20 function instructions

1. Initialize DS18B20

During the initialization sequence, the master device on the bus transmits (TX) a reset pulse by pulling the 1-Wire bus low for more than 480us. The master device then releases the bus and enters receive mode (RX). When the bus is released, a pull-up resistor of about 5kΩ pulls the 1-Wire bus to a high level. When DS18B20 detects the rising edge signal, it waits for 15us to 60us and then sends a presence pulse by pulling the 1-Wire bus low for 60us to 240us.

2. Execute ROM instructions

Access each DS18B20, search the 64-bit serial number, read the matching serial number value, and then match the corresponding DS18B20, if we only use a single DS18B20, we can skip the ROM instruction directly. And the byte to skip ROM instruction is 0xCC.

3. Write timing

?

  • When the MCU wants to write a 0 to DS18B20, it needs to pull down the pin of the MCU, keep the low level time between 60~120us, and then release the bus
  • When the MCU wants to write a 1 to DS18B20, it needs to pull down the pin of the MCU, the pull-down time needs to be greater than 1us, and then pull up the bus within 15us.

After the master device initializes the write period, DS18B20 will sample the bus within the time window of 15us to 60us. If the bus is high during the sampling window, a logic 1 is written to the DS18B20; if the bus is low, a logic 0 is written to the DS18B20.

void DS18B20_Write_Byte(u8 data)
{
for(u8 i=0;i<8;i ++ )
{
DS18B20_Output_Input(1);
GPIO_ResetBits(GPIOA,GPIO_Pin_0);//pull down 2us
Delay_Us(2);//greater than 1us
(data & amp;0x01) ? GPIO_SetBits(GPIOA,GPIO_Pin_0):GPIO_ResetBits(GPIOA,GPIO_Pin_0);//Start reading from the low bit
Delay_Us(45);//write time
GPIO_SetBits(GPIOA,GPIO_Pin_0);//Release the bus
\t\t
data>>=1;//shift right
}
\t\t
}

Note: At least 1us interval between 2 write cycles

4. Read timing

The host pulls down the bus level for at least 1μs in the read time slot and then releases the bus to read the 1 or 0 sent by DS18B20

After DS18B20 detects that the bus is pulled low for 1us, it starts to send data. If it wants to send 0, it pulls the bus to low level until the end of the read cycle. To send 1, release the bus to a high level.

u8 DS18B20_Read_Byte(void)
{
u8 data=0;
for(u8 i=0;i<8;i ++ )
{
data>>=1;//and write followed by (the read operation starts from the right bit, and the last read or write ends at the lowest bit, shift right one bit back to the highest bit)
DS18B20_Output_Input(1);
GPIO_ResetBits(GPIOA,GPIO_Pin_0);//pull down 2us
Delay_Us(2);
GPIO_SetBits(GPIOA,GPIO_Pin_0);//Release the bus
DS18B20_Output_Input(0);
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==SET)//1
{
data |=0x80;//Start from the high bit or, the low bit becomes the high bit and the high bit becomes the low bit
}//0 will automatically fill in 0
Delay_Us(45);
}
return data;

}

  • The temperature conversion time reaches a maximum of 750ms when the conversion accuracy is 12-Bits.
    DS18B20_Startup();
    DS18B20_Write_Byte(0XCC);//Skip ROM (only one temperature sensor)
    DS18B20_Write_Byte(0X44);//temperature conversion
    Delay_Ms(750);//12-bit resolution 750 milliseconds
    DS18B20_Startup();//Read register
    DS18B20_Write_Byte(0XCC);
    DS18B20_Write_Byte(0Xbe);//read ram
  • DS18B20 uses 16-bit complement code to store temperature data, The temperature is in degrees Celsius. When the temperature conversion command is issued, the converted temperature value is stored in the 0th and 1st bytes of the high-speed temporary storage memory in the form of two-byte complement.

    The five S in the high byte are sign bits, S=1 when the temperature is positive, and S=0 when the temperature is negative

    The remaining 11 bits are temperature data bits. For 12-bit resolution, all bits are valid. For 11-bit resolution, bit 0 (bit0) is undefined. For 10-bit resolution, bit 0 and bit 1 are undefined. For 9-bit resolution, bit 0, bit 1, and bit 2 are undefined

    ?

  • When the five sign bits S=0, the temperature is a positive value, directly convert the following 11-bit binary to decimal, and then multiply by 0.0625 (12-bit resolution), you can get the temperature value;
  • When the five sign bits S=1, the temperature is a negative value, first change the following 11-bit binary complement to the original code (the sign bit remains unchanged, and the value bit is reversed Add 1), and then calculate the decimal value. Then multiply by 0.0625 (12-bit resolution), you can get the temperature value;
LSB=DS18B20_Read_Byte();//Read the low bit first
MSB=DS18B20_Read_Byte();
temp=(MSB<<8)|LSB;//merge bytes
\t
if((temp & 0xF800)== 0xF800)
{
*data=((~temp + 0x01)*-0.625) + 0.5;//round to one decimal place
}
else
{
*data=(temp*0.0625) + 0.5;
}

Call: DS18B20_Read_Temp( & amp;tempe); //tempe is the returned temperature value

Reference: (36 messages) DS18B20 main command_ds18b20 command_Coal’s wonderful drifting blog-CSDN blog

(36 messages) [commonly used sensors] DS18B20 temperature sensor principle detailed explanation and routine code_Z Xiaoxuan’s Blog-CSDN Blog

5) FLASH

  1. First build an SPI module to realize pin packaging, initialization, and SPI communication start, end and exchange a character

The output pin is configured as a push-pull output, and the input pin is configured as a floating or pull-up input (for the host)

void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
\t
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, & GPIO_InitStructure);
\t
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, & GPIO_InitStructure);
\t
MySPI_W_SS(1);//Slave is not selected by default
MySPI_W_SCK(0);//use mode 0
}
  • Start condition: SS switches from high level to low level
  • Termination condition: SS switches from low to high
void MySPI_Start(void)//start signal
{
MySPI_W_SS(0);
}

void MySPI_Stop(void)//terminate signal
{
MySPI_W_SS(1);
}

Send data (in mode 0)

SS is set to 0→data shift out→SCK rising edge→data read in→SCK falling edge

2. Build the W25Q64 module and call the SPI puzzle to assemble the complete timing of various instructions and functions, such as write enable, erase, page programming, read data, etc.

Write code according to the instruction list format

void W25Q64_WaitBusy(void)
{
uint32_t Timeout;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);//command
Timeout = 100000;
while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
{
Timeout --;//Avoid writing dead loop directly to make the program stuck
if (Timeout == 0)
{
break;
}
}
MySPI_Stop();
}

void W25Q64_PageProgram(uint32_t Address, uint16_t *DataArray, uint16_t Count)//page programming
{
uint16_t i;
\t
W25Q64_WriteEnable();
\t
MySPI_Start();
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);//page programming instruction
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for (i = 0; i < Count; i ++ )
{
MySPI_SwapByte(DataArray[i]);//Write multiple bytes
}
MySPI_Stop();
\t
W25Q64_WaitBusy();//Wait afterwards
}

void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable();
\t
MySPI_Start();
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
MySPI_Stop();
\t
W25Q64_WaitBusy();
}

void W25Q64_ReadData(uint32_t Address, uint16_t *DataArray, uint32_t Count)
{
uint32_t i;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_DATA);//Send command
MySPI_SwapByte(Address >> 16);//write high address
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for (i = 0; i < Count; i ++ )
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
}
MySPI_Stop();
}

Refer to the video related to spi communication of Jiangxie Technology at station b

6) Button control

There are 5 buttons in total, the functions are to scroll up, scroll down, return, confirm, and turn off the fan.

Initialization function:

void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
\t
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, & GPIO_InitStructure);
}

Get the key value (while debounce):

uint8_t Get_KEY_Value(void)
{
uint8_t KeyNum = 0;
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0)//press the button
{
Delay_Ms(20);//Debounce
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0);
Delay_Ms(20);
KeyNum = 2;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)
{
Delay_Ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);
Delay_Ms(20);
KeyNum = 3;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == 0)
{
Delay_Ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == 0);
Delay_Ms(20);
KeyNum = 4;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 0)
{
Delay_Ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) == 0);
Delay_Ms(20);
KeyNum = 5;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 0)
{
Delay_Ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 0);
Delay_Ms(20);
KeyNum = 6;
}
\t
return KeyNum;
}

Use an array lookup table to implement a multi-level menu.

Design framework:

Define the structure:

typedef struct
{
u8 Cur_Index;//Current index item
u8 previous;//Previous page
u8 next;//next page
u8 enter;//confirm
u8 back;//return
void (*current_operation)(u8,u8);//The function (interface) executed by the current index
}Main_Menu;

Define the index table:

Main_Menu table[20]=
{
//Cur_Index, previous (previous), next (next), enter, back, (*current_operation) (u8, u8)
\t//Main interface
{_Main_UI,_Main_UI,_speed_set,_speed_set,_Main_UI,Main_UI},
\t//main menu
{_speed_set,_mode_set,_time_set,_speed_child,_Main_UI,Main_Menu_Func}, // wind speed adjustment
{_time_set,_speed_set,_tou_set,_time_child,_Main_UI,Main_Menu_Func},//timing operation
{_tou_set, _time_set, _mode_set, _tou_child, _Main_UI, Main_Menu_Func}, // swing head setting
{_mode_set,_tou_set,_speed_set,_mode_child,_Main_UI,Main_Menu_Func},//mode setting
\t
\t//Submenu
{_speed_child,_speed_child,_speed_child,_speed_child,_speed_set,speed_child}, // wind speed adjustment submenu
{_time_child, _time_child, _time_child, _time_child, _time_set, time_child}, //Timing operation submenu
{_tou_child, _tou_child, _tou_child, _tou_child, _tou_set, tou_child},//Swing head setting submenu
{_mode_child,_mode_child,_mode_child,_mode_child,_mode_set,mode_child}, //mode setting submenu

};

For example, my current interface (cur_Index) is _speed_set, then I press the up button, I will jump to the interface corresponding to the previous index value (_mode_set), press the up button, I will jump to the next corresponding interface (_time_set). The implemented code is as follows (constantly obtain the key value, when the key value changes, update the index value, and jump to the interface)

void GUI_Refresh(void)
{
u8 key_val=Get_KEY_Value();
if(key_val!=0)//Only press the key to refresh the screen
{
last_index=func_index;//Update the last interface index value
switch(key_val)
{
case KEY_PREVIOUS: func_index=table[func_index].previous;//Update index value
break;
case KEY_ENTER: func_index=table[func_index].enter;//Update index value
break;
case KEY_NEXT:func_index=table[func_index].next;//Update index value
break;
case KEY_BACK:func_index=table[func_index].back;//Update index value
break;
case KEY_STOP:fsspeed =2000;Motor_SetSpeed();;//Turn off the fan
break;
\t\t\t
default: break;
}
\t\t
OLED_Clear();//clear screen
}
current_operation_func=table[func_index].current_operation;
(*current_operation_func)(func_index,key_val);//Execute the function corresponding to the current index
\t\t
}

At the same time, in gui.c, different interface buttons are selected to return different control parameters, so that peripherals including motors, steering gears, temperature sensors, etc. can switch working states.

Reference: (36 messages) STM32 simple multi-level menu (array lookup table method)_stm32 multi-level menu_Programmer’s blog-CSDN blog

8) Main function

Call a series of initialization functions, read out the content in the flash, and keep the wind speed and mode before the last power failure. In the while (1) loop, only the acquisition of the key value and the update of the oled screen are performed. If other programs with a delay are written, the acquisition of the key value will be insensitive and the screen update will be slow.

int main(void)
{
OLED_Init();

Key_Init();
PWM_Init();
Motor_Init();
W25Q64_Init();
W25Q64_ReadData(0x000000, ArrayRead, 4);//Read
switch(ArrayRead[0])
{
case 0:fsspeed=2000;break;
case 1:fsspeed=1100;break;
case 2:fsspeed=500;break;
case 3:fsspeed=0;break;
default: break;
}

\t
mode=ArrayRead[1];
Motor_SetSpeed();//Read the flash first and then initialize
DS18B20_Init();
Timer_Init();
modeTimer_Init();
OLED_Clear();//clear screen
\t
if(mode==1)
{
TIM_Cmd(TIM4,ENABLE);
}
\t

  while (1)
 {
GUI_Refresh();

 }
\t
}

Code file structure reference: [2-2] New project_哔哩哔哩_bilibili

syntaxbug.com © 2021 All Rights Reserved.