点灯大师——WIFI控制灯

在之前的教程中,我们学习了 ESP6266 的原理,并动手写了驱动,实现了串口的通讯和 STA、AP、STA+AP 三种模式。本次我们就来教大家如何使用 ESP8266 控制灯。这是一个简单的示例,展示了如何将 WIFI 通信与硬件控制相结合,实现远程控制的功能。你也可以扩展这个示例,添加更多的指令和功能,以满足自己的需求。

1. 源码下载及前置阅读

本文首发 良许嵌入式网 :https://www.lxlinux.net/e/ ,欢迎关注!

本文所涉及的源码及安装包如下(由于平台限制,请点击以下链接阅读原文下载):

https://www.lxlinux.net/e/stm32/esp8266-control-led.html

如果你是嵌入式开发小白,那么建议你先读读下面几篇文章。

  • 【STM32下载程序的五种方法:https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to-stm32.html】
  • 【一文教你使用MDK开发工具:https://www.lxlinux.net/e/stm32/mdk-development-tool-tutorial.html】
  • 【零基础快速上手STM32开发(手把手保姆级教程):https://www.lxlinux.net/e/stm32/stm32-quick-start-for-beginner.html 】

前期教程,没看过的小伙伴可以先看下。

  • 【手把手教你玩转WIFI模块(原理+驱动):https://www.lxlinux.net/e/stm32/esp8266-tutorial.html】
  • 【STM32串口接收不定长数据(接收中断+超时判断):https://www.lxlinux.net/e/stm32/stm32-usart-receive-data-using-rxne-time-out.html】
作者简介
大家好,我是良许,博客里所有的文章皆为我的原创。
下面是我的一些个人介绍,欢迎交个朋友:
· 211工科硕士,国家奖学金获得者;
· 深耕嵌入式11年,前世界500强外企高级嵌入式工程师;
· 书籍《速学Linux作者》,机械工业出版社专家委员会成员;
· 全网60W粉丝,博客分享大量原创成体系文章,全网阅读量累计超4000万;
· 靠自媒体连续年入百万,靠自己买房买车。

我本科及硕士都是学机械,通过自学成功进入世界500强外企。我已经将自己的学习经验写成了一本电子书,超千人通过此书学习并转行成功。现在将这本电子书免费分享给大家,希望对你们有帮助:

电子书链接:https://www.lxlinux.net/1024.html

2. 通信示意图

实现目标是我们有一个三色 LED 灯,电脑上使用网络调试助手向 WIFI 模块发送关键词「green」绿灯亮,再次发送「green」绿灯灭,黄灯和红灯的关键词是「yellow」、「red」,效果一样。

3. 编程实战

3.1 硬件接线

本教程使用的硬件如下:

  • 单片机:STM32F103C8T6
  • WIFI模块:ESP-01S
  • 小灯:三色 LED 灯模块
  • 串口:USB 转 TTL
  • 烧录器:ST-LINK V2
ESP8266LEDSTM32USB 转 TTL
3V33.3
RXA2
TXA3
GNDG
RA5
YA6
GA7
GNDG
A10TX
A9RX
GGND

烧录的时候接线如下表,如果不会烧录的话可以看我之前的文章【STM32下载程序的五种方法:https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to-stm32.html】。

ST-Link V2STM32
SWCLKSWCLK
SWDIOSWDIO
GNDGND
3.3V3V3

接好如下图:

3.2 LED逻辑代码

LED 灯的代码简简单单,只要进行一下三个灯的初始化就行。

void led_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    LED1_GPIO_CLK_ENABLE();                                 /* LED1时钟使能 */
    LED2_GPIO_CLK_ENABLE();                                 /* LED2时钟使能 */
    LED3_GPIO_CLK_ENABLE();                                 /* LED3时钟使能 */

    gpio_init_struct.Pin = LED1_GPIO_PIN;                   /* LED1引脚 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
    HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct);       /* 初始化LED1引脚 */

    gpio_init_struct.Pin = LED2_GPIO_PIN;                   /* LED2引脚 */
    HAL_GPIO_Init(LED2_GPIO_PORT, &gpio_init_struct);       /* 初始化LED2引脚 */

    gpio_init_struct.Pin = LED3_GPIO_PIN;                   /* LED3引脚 */
    HAL_GPIO_Init(LED3_GPIO_PORT, &gpio_init_struct);       /* 初始化LED3引脚 */

    LED1(0);                                                /* 关闭 LED1 */
    LED2(0);                                                /* 关闭 LED2 */
    LED3(0);                                                /* 关闭 LED3 */
}

LED 的 .h文件:

#ifndef _LED_H
#define _LED_H
#include "sys.h"


/******************************************************************************************/
/* 引脚 定义 */

#define LED1_GPIO_PORT                  GPIOA
#define LED1_GPIO_PIN                   GPIO_PIN_7
#define LED1_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PA口时钟使能 */

#define LED2_GPIO_PORT                  GPIOA
#define LED2_GPIO_PIN                   GPIO_PIN_6
#define LED2_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PA口时钟使能 */

#define LED3_GPIO_PORT                  GPIOA
#define LED3_GPIO_PIN                   GPIO_PIN_5
#define LED3_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PB口时钟使能 */

/******************************************************************************************/
/* LED端口定义 */
#define LED1(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

#define LED2(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED2_GPIO_PORT, LED2_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED2_GPIO_PORT, LED2_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

#define LED3(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED3_GPIO_PORT, LED3_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED3_GPIO_PORT, LED3_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

/* LED取反定义 */
#define LED1_TOGGLE()   do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0)        /* 翻转LED1 */
#define LED2_TOGGLE()   do{ HAL_GPIO_TogglePin(LED2_GPIO_PORT, LED2_GPIO_PIN); }while(0)        /* 翻转LED2 */
#define LED3_TOGGLE()   do{ HAL_GPIO_TogglePin(LED3_GPIO_PORT, LED3_GPIO_PIN); }while(0)        /* 翻转LED3 */

/******************************************************************************************/
/* 外部接口函数*/
void led_init(void);                                                                            /* LED初始化 */

#endif

3.3 串口通讯实现

WIFI 串口通讯我们在【手把手教你玩转WIFI模块(原理+驱动):https://www.lxlinux.net/e/stm32/esp8266-tutorial.html】有详细教程,在这里就简单带过+浅浅复习下,没看过或者忘记了的小伙伴可以点击链接看看。

实现向串口发送数据,并等待返回值。ESP8266 的 TX 和 RX 定义在串口2。

  1. 定义串口句柄 g_uart_handle ,并调用 HAL_UART_Init 进行初始化。
UART_HandleTypeDef g_uart_handle;

void esp8266_uart_init(uint32_t baudrate)
{
    g_uart_handle.Instance          = ESP8266_UART_INTERFACE;       /* ESP8266 UART */
    g_uart_handle.Init.BaudRate     = baudrate;                     /* 波特率 */
    g_uart_handle.Init.WordLength   = UART_WORDLENGTH_8B;           /* 数据位 */
    g_uart_handle.Init.StopBits     = UART_STOPBITS_1;              /* 停止位 */
    g_uart_handle.Init.Parity       = UART_PARITY_NONE;             /* 校验位 */
    g_uart_handle.Init.Mode         = UART_MODE_TX_RX;              /* 收发模式 */
    g_uart_handle.Init.HwFlowCtl    = UART_HWCONTROL_NONE;          /* 无硬件流控 */
    g_uart_handle.Init.OverSampling = UART_OVERSAMPLING_16;         /* 过采样 */
    HAL_UART_Init(&g_uart_handle);                                  /* 使能ESP8266 UART */
}

其中,ESP8266_UART_INTERFACE 是宏定义,指代的就是 USART2

传入参数 baudrate 可以定义该串口的波特率。

  1. 初始化串口底层函数,调用 HAL_UART_MspInit 函数。

注意最后一行,需要调用 __HAL_UART_ENABLE_IT 函数使能接收中断。

void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef gpio_init_struct;

    if (huart->Instance == USART_UX)                            /* 如果是串口1,进行串口1 MSP初始化 */
    {
        ....
        // 省略串口1相关代码
        ....
    }
    else if (huart->Instance == ESP8266_UART_INTERFACE)                 /* 如果是ESP8266 UART */
    {
        ESP8266_UART_TX_GPIO_CLK_ENABLE();                              /* 使能UART TX引脚时钟 */
        ESP8266_UART_RX_GPIO_CLK_ENABLE();                              /* 使能UART RX引脚时钟 */
        ESP8266_UART_CLK_ENABLE();                                      /* 使能UART时钟 */

        gpio_init_struct.Pin    = ESP8266_UART_TX_GPIO_PIN;             /* UART TX引脚 */
        gpio_init_struct.Mode   = GPIO_MODE_AF_PP;                          /* 复用推挽输出 */
        gpio_init_struct.Pull   = GPIO_NOPULL;                              /* 无上下拉 */
        gpio_init_struct.Speed  = GPIO_SPEED_FREQ_HIGH;                     /* 高速 */
        HAL_GPIO_Init(ESP8266_UART_TX_GPIO_PORT, &gpio_init_struct);    /* 初始化UART TX引脚 */

        gpio_init_struct.Pin    = ESP8266_UART_RX_GPIO_PIN;             /* UART RX引脚 */
        gpio_init_struct.Mode   = GPIO_MODE_INPUT;                          /* 输入 */
        gpio_init_struct.Pull   = GPIO_NOPULL;                              /* 无上下拉 */
        gpio_init_struct.Speed  = GPIO_SPEED_FREQ_HIGH;                     /* 高速 */
        HAL_GPIO_Init(ESP8266_UART_RX_GPIO_PORT, &gpio_init_struct);    /* 初始化UART RX引脚 */

        HAL_NVIC_SetPriority(ESP8266_UART_IRQn, 0, 0);                  /* 抢占优先级0,子优先级0 */
        HAL_NVIC_EnableIRQ(ESP8266_UART_IRQn);                          /* 使能UART中断通道 */

        __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);                          /* 使能UART接收中断 */
    }
}
  1. 使用接收中断+超时判断,完成数据接收。

由于 ESP8266 通过串口返回的数据长度不固定,所以我们可以使用接收中断+超时判断的方法完成数据接收。具体方法可以参考下文:

【STM32串口接收不定长数据(接收中断+超时判断):https://www.lxlinux.net/e/stm32/stm32-usart-receive-data-using-rxne-time-out.html】

在串口中断服务函数里,我们可以将接收到的字符保存在接收缓冲区里 g_uart_rx_buf ,并定义一个变量 esp8266_cnt 计算总共收到了多少个字符。

void ESP8266_UART_IRQHandler(void)
{
    uint8_t receive_data = 0;   
    if(__HAL_UART_GET_FLAG(&g_uart_handle, UART_FLAG_RXNE) != RESET){
        if(esp8266_cnt >= sizeof(g_uart_rx_buf))
            esp8266_cnt = 0; //防止串口被刷爆
        HAL_UART_Receive(&g_uart_handle, &receive_data, 1, 1000);//串口2接收1位数据
        g_uart_rx_buf[esp8266_cnt++] = receive_data;  
    }
    HAL_UART_IRQHandler(&g_uart_handle);
}

假如一帧的数据接收完成了,那么 esp8266_cnt 变量的值应该维持不变。

我们通过 while 死循环不停计算当前收到多少个字符,当前统计的值计算在 esp8266_cnt 变量里,定义另一个变量 esp8266_cntPre ,用于记录上一次统计接收到的数据的长度,如果本次统计数据长度跟上一次一样的话就说明数据接收完成了。代码如下:

uint8_t esp8266_wait_receive(void)
{
    if(esp8266_cnt == 0)                             //如果接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数
        return ESP8266_ERROR;

    if(esp8266_cnt == esp8266_cntPre) {                //如果上一次的值和这次相同,则说明接收完毕
        esp8266_cnt = 0;                            //清0接收计数
        return ESP8266_EOK;                            //返回接收完成标志
    }

    esp8266_cntPre = esp8266_cnt;                    //置为相同
    return ESP8266_ERROR;                            //返回接收未完成标志
}

当然,接收到的数据使用完成之后,我们就应该清空接收缓冲区,并将计数器置 0 ,方便下一次接收。

void esp8266_clear(void)
{
    memset(g_uart_rx_buf, 0, sizeof(g_uart_rx_buf));
    esp8266_cnt = 0;
}

接下来,就是最关键的一个函数了。我们使用这个函数通过串口向 ESP8266 发送一个字符串,并循环等待我们所期待得到的字符串。

在下面这个函数里,cmd 变量是我们向 ESP8266 发送的字符串,res 变量是我们期待得到的回复。

比如,我们向 ESP8266 发送 AT 这个字符串,那么 ESP8266 如果正常的话应该会回复 OK 。此时,cmd 就是 AT ,而 res 就是 OK

uint8_t esp8266_send_command(char *cmd, char *res)
{
    uint8_t timeOut = 250;

    esp8266_clear();
    HAL_UART_Transmit(&g_uart_handle, (unsigned char *)cmd, strlen((const char *)cmd), 100);

    while(timeOut--) {
        if(esp8266_wait_receive() == ESP8266_EOK) {                        //如果收到数据
            if(strstr((const char *)g_uart_rx_buf, res) != NULL)        //如果检索到关键词
                return ESP8266_EOK;
        }
        delay_ms(10);
    }

    return ESP8266_ERROR;
}

3.4 AT指令交互

接下来,我们就可以使用 esp8266_send_command 发送 AT 指令并确定 ESP8266 回复是否正确。以代码方式实现各个 AT 指令,例子如下:

uint8_t esp8266_at_test(void)
{
    return esp8266_send_command("AT\r\n", "OK");
}

uint8_t esp8266_sw_reset(void)
{
    return esp8266_send_command("AT+RST\r\n", "OK");
}

uint8_t esp8266_set_mode(uint8_t mode)
{
    switch (mode) {
        case ESP8266_STA_MODE:
            return esp8266_send_command("AT+CWMODE=1\r\n", "OK");    /* Station模式 */

        case ESP8266_AP_MODE:
            return esp8266_send_command("AT+CWMODE=2\r\n", "OK");    /* AP模式 */

        case ESP8266_STA_AP_MODE:
            return esp8266_send_command("AT+CWMODE=3\r\n", "OK");    /* AP+Station模式 */

        default:
            return ESP8266_EINVAL;
    }
}

由于 AT 指令有很多,这里只截取了其中的一部分,完整的可以参考我提供的代码。

3.5 实现STA模式

STA 模式实现思路如下:

  1. 进入 STA 模式(要重启生效);
  2. 设置单路连接;
  3. 连接 WIFI(注意 ESP8266 和服务端要在同一网络内);
  4. 浅查一下 ESP8266 的 IP 地址,不查也可以;
  5. 连接TCP服务器;
  6. 开启透传。
uint8_t esp8266_single_connection(void)
{
    return esp8266_send_command("AT+CIPMUX=0\r\n", "OK");
}

uint8_t esp8266_join_ap(char *ssid, char *pwd)
{
    char cmd[64];

    sprintf(cmd, "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, pwd);

    return esp8266_send_command(cmd, "WIFI GOT IP");
}

uint8_t esp8266_get_ip(char *buf)
{
    char *p_start;
    char *p_end;

    if (esp8266_send_command("AT+CIFSR\r\n", "STAIP") != ESP8266_EOK)
        return ESP8266_ERROR;

    p_start = strstr((const char *)g_uart_rx_buf, "\"");
    p_end = strstr(p_start + 1, "\"");
    *p_end = '\0';
    sprintf(buf, "%s", p_start + 1);

    return ESP8266_EOK;
}

uint8_t esp8266_connect_tcp_server(char *server_ip, char *server_port)
{
    char cmd[64];

    sprintf(cmd, "AT+CIPSTART=\"TCP\",\"%s\",%s\r\n", server_ip, server_port);

    return esp8266_send_command(cmd, "CONNECT");
}

uint8_t esp8266_enter_unvarnished(void)
{
    uint8_t ret;

    ret  = esp8266_send_command("AT+CIPMODE=1\r\n", "OK");
    ret += esp8266_send_command("AT+CIPSEND\r\n", ">");
    if (ret == ESP8266_EOK)
        return ESP8266_EOK;
    else
        return ESP8266_ERROR;
}

/**
 * @brief       ESP8266初始化
 * @param       baudrate: ESP8266 UART通讯波特率
 * @retval      ESP8266_EOK  : ESP8266初始化成功,函数执行成功
 *              ESP8266_ERROR: ESP8266初始化失败,函数执行失败
 */
uint8_t esp8266_init(uint32_t baudrate)
{
    char ip_buf[16];

    esp8266_uart_init(baudrate);                /* ESP8266 UART初始化 */

    /* 让WIFI退出透传模式 */
    while(esp8266_exit_unvarnished())
        delay_ms(500);

    printf("1.AT\r\n");
    while(esp8266_at_test())
        delay_ms(500);

    printf("2.RST\r\n");
    while(esp8266_sw_reset())
        delay_ms(500);
    while(esp8266_disconnect_tcp_server())
        delay_ms(500);

    printf("3.CWMODE\r\n");
    while(esp8266_set_mode(ESP8266_STA_MODE))
        delay_ms(500);

    printf("4.AT+CIPMUX\r\n");  //设置单路连接模式,透传只能使用此模式
    while(esp8266_multi_connection())
        delay_ms(500);

    printf("5.CWJAP\r\n");      //连接WIFI
    printf("%s\r\n",WIFI_SSID); 
    while(esp8266_join_ap(WIFI_SSID, WIFI_PWD))
        delay_ms(1000);

    printf("6.CIFSR\r\n");
    while(esp8266_get_ip(ip_buf))
        delay_ms(500);

    printf("ESP8266 IP: %s\r\n", ip_buf);

    printf("7.CIPSTART\r\n");
    while(esp8266_connect_tcp_server(TCP_SERVER_IP, TCP_SERVER_PORT))
        delay_ms(500);

    printf("8.CIPMODE\r\n");
    while(esp8266_enter_unvarnished())
        delay_ms(500);

    printf("ESP8266初始化完成\r\n");
    return ESP8266_EOK;
}

3.6 LED控制

检测WIFI串口是否接收到 LED 关键词,如果有就反转 LED 灯状态。

void control_led()
{
    if(strstr((const char *)bt_uart_rx_buf, "green") != NULL)           //如果接收到关键词"green"
        LED1_TOGGLE();                                                  // 翻转LED1
    if(strstr((const char *)bt_uart_rx_buf, "yellow") != NULL)          //如果接收到关键词"yellow"
        LED2_TOGGLE();                                                  // 翻转LED2
    if(strstr((const char *)bt_uart_rx_buf, "red") != NULL)             //如果接收到关键词"red"
        LED3_TOGGLE();                                                  // 翻转LED3
}

3.7 主函数

在 main 函数里,我们先调用 led_init() 函数和 esp8266_init(115200) 函数进行 LED 和ESP8266 的初始化,然后在原有通讯的基础上检测 LED 关键词。

int main(void)
{
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
    delay_init(72);                             /* 初始化延时函数 */
    usart_init(115200);                         /* 波特率设为115200 */
    led_init();
    esp8266_init(115200);

    while(1)
    {
        printf("receive: %s\r\n", g_uart_rx_buf);
        control_led();
        esp8266_clear();
        esp8266_uart_printf("from client\r\n");
        HAL_Delay(1000);
    }
}

4. 运行过程

  1. 将硬件连好,插到电脑 USB 口。
  2. 打开网络调试助手。协议选 TCP Server,本机主机地址是自动识别的,端口号选8080,点击打开。
  3. 打开电脑串口软件。设置串口助手波特率 115200(你们不一定要用我这款,随便的串口助手都可以),选择串口号,最后打开串口开始准备接收数据。

这个串口工具接收的是 MCU 串口 1 的数据,也就是 log 。WIFI接收到数据后,我们使用串口 1 打印到下面的串口助手里。

烧录代码,串口输出如下。

我们给 WIFI 模块发送数据「green」、「yellow」、「red」。

可以看到串口助手成功接收到了 「green」、「yellow」、「red」这些数据。

我们的三个小灯也打开了。(我的小绿灯不是很亮,用旧了,嘻嘻)

再次发送关键词即可关对应的灯。当然,一次发送 “green yellow red”,就可以控制三个小灯一起反转。

5. 总结

祝贺大家成功点灯!当然,除了控制灯的开关,WIFI 模块还可以应用于更广泛的场景,如个人电子设备、智能家居控制、健康医疗设备等等。随着技术的不断进步,WIFI 技术将持续演进,并在更多领域发挥作用。希望本文能够为你提供了一个初步的了解,并激发你进一步深入研究和应用 WIFI 技术的兴趣。感谢各位看官,love and peace!

另外,想进大厂的同学,一定要好好学算法,这是面试必备的。这里准备了一份 BAT 大佬总结的 LeetCode 刷题宝典,很多人靠它们进了大厂。

刷题 | LeetCode算法刷题神器,看完 BAT 随你挑!

有收获?希望老铁们来个三连击,给更多的人看到这篇文章

推荐阅读:

  • 程序员必备编程资料大全
  • 程序员必备软件资源

欢迎关注我的博客:良许嵌入式教程网,满满都是干货!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/928313.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何使用brew安装phpredis扩展?

如何使用brew安装phpredis扩展? phpredis扩展是一个用于PHP语言的Redis客户端扩展,它提供了一组PHP函数,用于与Redis服务器进行交互。 1、cd到php某一版本的bin下 /usr/local/opt/php8.1/bin 2、下载 phpredis git clone https://githu…

Android 使用OpenGLES + MediaPlayer 获取视频截图

概述 Android 获取视频缩略图的方法通常有: ContentResolver: 使用系统数据库MediaMetadataRetriever: 这个是android提供的类,用来获取本地和网络media相关文件的信息ThumbnailUtils: 是在android2.2(api8)之后新增的一个,该类为…

面向对象(二)——类和对象(上)

1 类的定义 做了关于对象的很多介绍,终于进入代码编写阶段。 本节中重点介绍类和对象的基本定义,属性和方法的基本使用方式。 【示例】类的定义方式 // 每一个源文件必须有且只有一个public class,并且类名和文件名保持一致! …

echarts的双X轴,父级居中的相关配置

前言:折腾了一个星期,在最后一天中午,都快要放弃了,后来坚持下来,才有下面结果。 这个效果就相当是复合表头,第一行是子级,第二行是父级。 子级是奇数个时,父级label居中很简单&…

顶刊算法 | 鱼鹰算法OOA-BiTCN-BiGRU-Attention多输入单输出回归预测(Maltab)

顶刊算法 | 鱼鹰算法OOA-BiTCN-BiGRU-Attention多输入单输出回归预测(Maltab) 目录 顶刊算法 | 鱼鹰算法OOA-BiTCN-BiGRU-Attention多输入单输出回归预测(Maltab)效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实…

Agile VMO分享:海尔案例

海尔集团是全球最大的家电制造商之一,拥有超过76 000名员工。它获得了2018-2019年全球智能家电品牌前10名和2018-2019年全球消费电子品牌前50名的荣誉。 海尔利用价值流结构将自己组织成一些可以自管理的微型企业。这些微型企业拥有决策,设计和交付新产品…

第七课 Unity编辑器创建的资源优化_UI篇(UGUI)

上期我们学习了简单的Scene优化,接下来我们继续编辑器创建资源的UGUI优化 UI篇(UGUI) 优化UGUI应从哪些方面入手? 可以从CPU和GPU两方面考虑,CPU方面,避免触发或减少Canvas的Rebuild和Rebatch&#xff0c…

LabVIEW MathScript工具包对运行速度的影响及优化方法

LabVIEW 的 MathScript 工具包 在运行时可能会影响程序的运行速度,主要是由于以下几个原因: 1. 解释型语言执行方式 MathScript 使用的是类似于 MATLAB 的解释型语言,这意味着它不像编译型语言(如 C、C 或 LabVIEW 本身的 VI&…

中国移动量子云平台:算力并网590量子比特!

在技术革新的浪潮中,量子计算以其独特的并行处理能力和指数级增长的计算潜力,有望成为未来技术范式变革和颠覆式创新应用的新源泉。中国移动作为通信行业的领军企业,致力于量子计算技术研究,推动量子计算产业的跨越式发展。 量子云…

pytest(二)excel数据驱动

一、excel数据驱动 excel文件内容 excel数据驱动使用方法 import openpyxl import pytestdef get_excel():excel_obj openpyxl.load_workbook("../pytest结合数据驱动-excel/data.xlsx")sheet_obj excel_obj["Sheet1"]values sheet_obj.valuescase_li…

文库 | 从嬴图的技术文档聊起

在技术的浩瀚海洋中,一份优秀的技术文档宛如精准的航海图。它是知识传承的载体,是团队协作的桥梁,更是产品成功的幕后英雄。然而,打造这样一份出色的技术文档并非易事。你是否在为如何清晰阐释复杂技术而苦恼?是否纠结…

flask的第一个应用

本文编写一个简单的实例来记录下flask的使用 文章目录 简单实例flask中的路由无参形式有参形式 参数类型本文小结 简单实例 flask的依赖包都安装好之后,我们就可以写一个最简单的web应用程序了,我们把这个应用程序命名为first.py: from flask import Fla…

【UE5 C++】判断两点连线是否穿过球体

目录 前言 方法一 原理 代码 测试 结果 方法二 原理 一、检查连线与球体的相交情况 二、检查距离与球体半径的关系 三、检查连线与球体的相交 代码 前言 通过数学原理判断空间中任意两点的连线是否穿过球体,再通过射线检测检验算法的正确性。 方法一 …

Python办公——openpyxl处理Excel每个sheet每行 修改为软雅黑9号剧中+边框线

目录 专栏导读背景1、库的介绍①:openpyxl 2、库的安装3、核心代码4、完整代码5、最快的方法(50万行44秒)——表头其余单元格都修改样式总结 专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️‍…

Figma入门-约束与对齐

Figma入门-约束与对齐 前言 在之前的工作中,大家的原型图都是使用 Axure 制作的,印象中 Figma 一直是个专业设计软件。 最近,很多产品朋友告诉我,很多原型图都开始用Figma制作了,并且很多组件都是内置的&#xff0c…

8. Debian系统中显示屏免密码自动登录

本文介绍如何在Debian系统上,启动后,自动免密登录,不卡在登录界面。 1. 修改lightDM配置文件 嵌入式Debian系统采用lightDM显示管理器,所以,一般需要修改它的配置文件/etc/lightdm/lightdm.conf,找到[Seat…

Unity类银河战士恶魔城学习总结(P156 Audio Settings音频设置)

【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili 教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/ 本章节实现了音频的大小设置与保存加载 音频管理器 UI_VolumeSlider.cs 定义了 UI_VolumeSlider 类,用于处理与音频设置相关的…

控制访问权限

Swift中的控制访问权限有5种,分别是private,fileprivate,public,open,intelnal。 如果我们没有写访问权限关键字时,默认的访问权限是intelnal 访问控制权限从高到低的顺序是:open > public…

单例模式的析构学习

1、例子 如果单例对象是类的static成员&#xff0c;那么在程序结束时不会调用类的析构函数&#xff0c;如下&#xff1a; #include <iostream> using namespace std;class A{ private:static A* m_ins;//声明&#xff0c;静态指针成员A(){} public:static A* getIns(){…

Function Arguments and Function Parameters (函数的实参和函数的形参)

Function Arguments and Function Parameters {函数的实参和函数的形参} 1. Object-Oriented Programming Using C2. Function Arguments and Function ParametersReferences 1. Object-Oriented Programming Using C https://icarus.cs.weber.edu/~dab/cs1410/textbook/index…