STM32串口接收不定长数据(空闲中断+DMA)

玩转 STM32 单片机,肯定离不开串口。串口使用一个称为串行通信协议的协议来管理数据传输,该协议在数据传输期间控制数据流,包括数据位数、波特率、校验位和停止位等。由于串口简单易用,在各种产品交互中都有广泛应用。

但在使用串口通讯的时候,我们并不知道对方会发送多少个数据,也不知道数据什么时候发送完,简单来讲就是:如何确保收到一帧完整的数据?

串口发送的数据有长有短,如果没有接收完整,肯定会影响后续业务的处理。为了接收不定长数据,常见的处理方法有:

1. 固定格式

比如双方约定,一帧的数据以 AA BB 开头,以 BB AA 结尾,这样在从机接收数据的时候,一旦收到 AA BB 字符,就知道对方要发来一个数据包了,然后就把后面发来的数据保存起来,直到接收到 BB AA 为止。

这种方法简单高效,但缺点就是需要每个字符都进行判断,浪费 CPU 资源,增加功耗。

2. 接收中断+超时判断

串口接收到一个数据时,就会触发接收中断。但如何判断数据已经发送完了呢?

通常来讲,两帧数据之间,会有个时间间隔。因此,我们可以使用一个计时器,如果在一个固定的时间点里没接收到新的字符,则认为一帧数据接收完成了。

3. 空闲中断

串口在空闲时,也就是说串口在一段时间里没有接收到新数据,则会触发空闲中断。细心的同学应该发现了,空闲中断实际上跟上面的超时判断是一样样的,只不过空闲中断是硬件自带,但超时判断需要我们自己实现。

所以,一旦接收到空闲中断,可以认为接收到一帧完整的数据。

但是,空闲中断并不是所有的 MCU 都具备,一般高端一点的 MCU 才有,低端一些的 MCU 并没有空闲中断。

1. 源码下载及前置阅读

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

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

https://www.lxlinux.net/e/stm32/stm32-usart-receive-data-using-idle-dma.html

如果你是个零基础的小白,连 STM32 都没见过,我也给你准备了一个保姆级教程,手把手教你搭建好 STM32 开发环境,并教你如何下载程序,简直业界良心!

https://www.lxlinux.net/e/stm32/stm32-quick-start-for-beginner.html

如果你连代码都不知道怎么烧录到 STM32 的,可以参考下文,提供了 5 种代码烧录方式:

https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to-stm32.html

如果你想自己搭一个属于自己的工程模板,可以参考下面这篇文章:

https://www.lxlinux.net/e/stm32/create-stm32-hal-project-template.html

在本文中,我们详细来介绍如何使用接收中断+超时判断完成不定长数据的接收,对于接收中断的接收,请查看下文

https://www.lxlinux.net/e/stm32/stm32-usart-receive-data-using-rxne-time-out.html

2. 什么是空闲中断?

前文已经提到,当接收到一字节数据时,会触发接收中断,对应串口状态寄存器第 5 位被置 1 ;如果串口在空闲时,则会触发空闲中断,第 4 位被置 1 ,如下图所示:

在中断服务函数里,记得一定要清除 IDLE 位,否则将一直触发空闲中断,影响后续的业务处理。

3. DMA

3.1 什么是DMA?

令人头秃的描述:

DMA(Direct Memory Access,直接存储器访问)提供在外设与内存存储器和存储器外设与外设之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU ,在这个时间中,CPU 对于内存的工作来说就无法使用。

简单描述:

就是一个数据搬运工!!

3.2 DMA的意义

代替 CPU 搬运数据,为 CPU 减负。

  1. 数据搬运的工作比较耗时间;

  2. 数据搬运工作时效要求高(有数据来就要搬走);

  3. 没啥技术含量(CPU 节约出来的时间可以处理更重要的事)。

3.3 搬运什么数据?

存储器、外设

这里的外设指的是 spi、usart、iic、adc 等基于APB1 、APB2 或 AHB 时钟的外设,而这里的存储器包括自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的。

三种搬运方式:

  • 存储器→存储器(例如:复制某特别大的数据 buf )
  • 存储器→外设 (例如:将某数据 buf 写入串口 TDR 寄存器)
  • 外设→存储器 (例如:将串口 RDR 寄存器写入某数据 buf )

存储器→存储器

存储器→外设

外设→存储器

3.4 DMA 控制器

STM32F103 有 2 个 DMA 控制器,DMA1 有 7 个通道,DMA2 有 5 个通道。对于 STM32F103C8T6 这颗芯片,只有 DMA1 。

一个通道每次只能搬运一个外设的数据!! 如果同时有多个外设的 DMA 请求,则按照优先级进行响应。

DMA1 有 7 个通道:

DMA2 有 5 个通道

3.5 DMA及通道的优先级

优先级管理采用软件+硬件:

  • 软件: 每个通道的优先级可以在 DMA_CCRx 寄存器中设置,有4个等级

    最高级>高级>中级>低级

  • 硬件: 如果 2 个请求,它们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先权。

    比如:如果软件优先级相同,通道 2 优先于通道 4

3.6 DMA传输方式

  • DMA_Mode_Normal(正常模式)

    一次 DMA 数据传输完后,停止 DMA 传送 ,也就是只传输一次

  • DMA_Mode_Circular(循环传输模式)

    当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式

3.7 指针递增模式

外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。

4. 硬件准备

  • STM32 核心板

本文使用 STM32F103C8T6 核心板,非常便宜,某宝上 10 元左右(关键词:STM32 核心板),一杯奶茶的钱不到。

核心板最大的优点是便宜简单,缺点就是需要根据需求自己搭一些电路,对你的动手能力要求比较高。

上面所推荐的这块核心板,主控芯片是 STM32103C8T6 ,64K flash,20K RAM,4 个定时器,3 个串口,网络上资料好几吨,非常适合初学者入门,强烈推荐。

  • USB 转 TTL

这种设备主要作用是用来调试或下载程序。价格也很便宜,普遍 5~8 元。

  • ST-Link

ST-Link 是一种用于 STM32 微控制器的调试和编程工具,它可以通过 SWD 或 JTAG 接口与开发板进行通信。一般也很便宜,七八元左右。

5. 编程实战

在本实验中,我们将串口 1 作为 log 输出端口,串口 2 作为本次实验的接收端口。

因此我们需要提前创建 uart2 模块,包含 uart2.c 及 uart2.h 两个文件,并加载进工程模板。

5.1 串口初始化

串口的初始化大家应该不陌生,主要步骤为:

  1. 定义串口句柄 uart2_handle ,并调用 HAL_UART_Init 进行初始化;
  2. 初始化串口底层函数,调用 HAL_UART_MspInit 函数。

第一步在 uart2.c 文件里进行:

UART_HandleTypeDef uart2_handle;

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

第二步在 usart.c 文件里进行,其实也可以在 uart2.c 文件里做,但我懒~

在最下面两行代码,我们使用 __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 == UART2_INTERFACE)                /* 如果是UART2 */
    {
        UART2_TX_GPIO_CLK_ENABLE();                             /* 使能UART2 TX引脚时钟 */
        UART2_RX_GPIO_CLK_ENABLE();                             /* 使能UART2 RX引脚时钟 */
        UART2_CLK_ENABLE();                                     /* 使能UART2时钟 */

        gpio_init_struct.Pin    = UART2_TX_GPIO_PIN;            /* UART2 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(UART2_TX_GPIO_PORT, &gpio_init_struct);   /* 初始化UART2 TX引脚 */

        gpio_init_struct.Pin    = UART2_RX_GPIO_PIN;            /* UART2 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(UART2_RX_GPIO_PORT, &gpio_init_struct);   /* 初始化UART2 RX引脚 */

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

        __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);              /* 使能UART2接收中断 */
        __HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);              /* 使能UART2总线空闲中断 */
    }
}

5.2 串口中断服务函数

前文已经提到,串口触发到一次接收中断,则代表接收到一个字符,我们就可以把这个字符放到接收缓冲区里。这个过程与上一篇文章一样样,可以参考下文:

【STM32串口接收不定长数据(接收中断+超时判断)】

具体代码实现如下:

void UART2_IRQHandler(void)
{
    uint8_t receive_data = 0;   
    if(__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_RXNE) != RESET){    //获取接收RXNE标志位是否被置位
        if(uart2_rx_len >= sizeof(uart2_rx_buf))                        //如果接收的字符数大于接收缓冲区大小,
            uart2_rx_len = 0;                                           //则将接收计数器清零
        HAL_UART_Receive(&uart2_handle, &receive_data, 1, 1000);        //接收一个字符
        uart2_rx_buf[uart2_rx_len++] = receive_data;                    //将接收到的字符保存在接收缓冲区
    }

    ...
    // 省略空闲中断代码
    ...
}

串口触发一次空闲中断,则代表接收到一帧数据,也就是收到了一个完整的数据包了,我们就可以将收到的数据包进行处理(比如打印出来),代码如下:

void UART2_IRQHandler(void)
{
    ...
    // 省略接收中断代码
    ...

    if (__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_IDLE) != RESET)    //获取接收空闲中断标志位是否被置位
    {
        printf("recv: %s\r\n", uart2_rx_buf);                           //将接收到的数据打印出来
        uart2_rx_clear();
        __HAL_UART_CLEAR_IDLEFLAG(&uart2_handle);                       //清除UART总线空闲中断
    }
}

在上面的代码里,一定要记得调用 __HAL_UART_CLEAR_IDLEFLAG() 函数清除 UART 总线空闲中断,否则空闲中断一直处于触发状态,影响下一次接收。

判断是否收到接收/空闲中断,需要用到的是 __HAL_UART_GET_FLAG() 函数,接收中断判断的是 UART_FLAG_RXNE 标志位,而空闲中断判断的是 UART_FLAG_IDLE 标志位。

串口中断服务函数完整代码如下(就是将上面两部分代码合二为一):

void uart2_rx_clear(void)
{
    memset(uart2_rx_buf, 0, sizeof(uart2_rx_buf));          //清空接收缓冲区
    uart2_rx_len = 0;                                       //接收计数器清零
}

void UART2_IRQHandler(void)
{
    uint8_t receive_data = 0;   
    if(__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_RXNE) != RESET){    //获取接收RXNE标志位是否被置位
        if(uart2_rx_len >= sizeof(uart2_rx_buf))                        //如果接收的字符数大于接收缓冲区大小,
            uart2_rx_len = 0;                                           //则将接收计数器清零
        HAL_UART_Receive(&uart2_handle, &receive_data, 1, 1000);        //接收一个字符
        uart2_rx_buf[uart2_rx_len++] = receive_data;                    //将接收到的字符保存在接收缓冲区
    }

    if (__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_IDLE) != RESET)    //获取接收空闲中断标志位是否被置位
    {
        printf("recv: %s\r\n", uart2_rx_buf);                           //将接收到的数据打印出来
        uart2_rx_clear();
        __HAL_UART_CLEAR_IDLEFLAG(&uart2_handle);                       //清除UART总线空闲中断
    }
}

对应的 uart2.h 文件完整代码如下:

#include <stdint.h>
#include "usart.h"

/* 引脚定义 */
#define UART2_TX_GPIO_PORT           GPIOA
#define UART2_TX_GPIO_PIN            GPIO_PIN_2
#define UART2_TX_GPIO_CLK_ENABLE()   do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)

#define UART2_RX_GPIO_PORT           GPIOA
#define UART2_RX_GPIO_PIN            GPIO_PIN_3
#define UART2_RX_GPIO_CLK_ENABLE()   do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)

#define UART2_INTERFACE              USART2
#define UART2_IRQn                   USART2_IRQn
#define UART2_IRQHandler             USART2_IRQHandler
#define UART2_CLK_ENABLE()           do{ __HAL_RCC_USART2_CLK_ENABLE(); }while(0)

/* 错误代码 */
#define UART_EOK                     0   /* 没有错误 */
#define UART_ERROR                   1   /* 通用错误 */
#define UART_ETIMEOUT                2   /* 超时错误 */
#define UART_EINVAL                  3   /* 参数错误 */

/* UART收发缓冲大小 */
#define UART2_RX_BUF_SIZE            128
#define UART2_TX_BUF_SIZE            64

void uart2_init(uint32_t baudrate);

到这里,实际上我们已经实现了使用空闲中断接收不定长数据的逻辑代码了,烧进板子后效果如下:

对于大多数应用场景下,这种串口接收不定长数据的处理方式已经足够用了。

但如果你串口每次接收的数据量过于庞大,那么就可以请出 DMA 这个数据搬运工了,一旦接收到数据则立马搬走,不占用 CPU 资源。

5.3 加入DMA

既然需要用到 DMA 外设,则在 BSP 目录下创建 dma.c 及 dma.h 两个文件,并加载进工程文件。

在 dma.c 文件里,我们要做的事情就是初始化 DMA 外设,实际上就是指定数据从哪里来、到哪里去,以及数据长度等等。

由于我们使用的是串口2 RX 通道,根据下图可知,用到的 DMA 通道为 DMA1_Channel6 (STM32F103C8T6只有 DMA1 )。

在初始化的最后,一定要记得调用 HAL_UART_Receive_DMA() 函数开启 DMA 接收,否则 DMA 这个搬运工就算请过来了,他还是依然不为你工作。

详细代码如下:

void dma_init(void)
{
    // UART2 RX DMA配置
    __HAL_RCC_DMA1_CLK_ENABLE();                                                /* DMA1时钟使能 */

    dma_handle.Instance = DMA1_Channel6;                                        /* USART2_RX使用的DMA通道为: DMA1_Channel6 */
    dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;                           /* 外设到存储器模式 */
    dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;                               /* 外设非增量模式 */
    dma_handle.Init.MemInc = DMA_MINC_ENABLE;                                   /* 存储器增量模式 */
    dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;                  /* 外设数据长度:8位 */
    dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;                     /* 存储器数据长度:8位 */
    dma_handle.Init.Mode = DMA_NORMAL;                                          /* 外设流控模式 */
    dma_handle.Init.Priority = DMA_PRIORITY_LOW;                                /* 低优先级 */

    HAL_DMA_Init(&dma_handle);

    __HAL_LINKDMA(&uart2_handle, hdmarx, dma_handle);                           /* 将DMA与USART2联系起来(发送DMA) */
    HAL_UART_Receive_DMA(&uart2_handle, uart2_rx_buf, UART2_RX_BUF_SIZE);       /* 开启DMA接收 */
}

在串口中断服务函数里,我们可以将接收中断相关代码全部去掉(因为已经有了 DMA 这个搬运工了,没必要让 CPU 一个个字符转移数据了)。

我先把代码贴上来,再详细讲解。

void UART2_IRQHandler(void)
{
    if (__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_IDLE) != RESET){           //获取接收IDLE标志位是否被置位
        __HAL_UART_CLEAR_IDLEFLAG(&uart2_handle);
        HAL_UART_DMAStop(&uart2_handle);                                        //停止DMA传输,防止干扰
        uart2_rx_len = UART2_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&dma_handle);  //获取接收到的数据长度
        printf("recv: %s, recv_len: %d\r\n", uart2_rx_buf, uart2_rx_len);
        uart2_rx_clear();
        HAL_UART_Receive_DMA(&uart2_handle, uart2_rx_buf, UART2_RX_BUF_SIZE);   //重新开启DMA传输
    }
}

上面的代码有几个要点需要解释一下:

  1. 停止 DMA 传输

当我们收到空闲中断时,实际上 DMA 已经帮我们把所有的数据搬运到了接收缓冲区了,此时我们可以先把 DMA 传输关闭掉,防止干扰到后续的操作。

  1. 获取接收到的数据长度

__HAL_DMA_GET_COUNTER() 函数表示 DMA 中待接收的数据长度。什么意思呢?假设我需要 DMA 接收 100 个字符的数据量,但现在实际上只接收到了 30 个字符,那么待接收的数据长度为 70 ,也就是 __HAL_DMA_GET_COUNTER() 函数的返回值为 70 。

所以,我们已经接收到的数据长度,等于接收缓冲区的长度,减去待接收的数据长度,翻译成代码就是:

uart2_rx_len = UART2_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&dma_handle);
  1. 重新开启 DMA 传输

一帧数据处理完成之后,我们肯定要进行下一帧数据的接收,所以需要调用 HAL_UART_Receive_DMA() 重新开启 DMA 传输,否则数据只接收一帧之后就罢工了。

到此 DMA 就加入成功了,烧进去板子后效果如下:

6. 小结

STM32 串口通讯在项目中使用的频率非常高,但由于不知道数据发送方会发送多少数据量,所以串口接收不定长数据成了一个急需解决的问题。

本文使用串口的空闲中断+DMA方法解决了此问题,并给出了详细的教程,希望对读者朋友有所帮助。


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

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

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

推荐阅读:

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

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

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

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

相关文章

佛罗里达大学利用神经网络,解密 GPCR-G 蛋白偶联选择性

内容一览&#xff1a;G 蛋白偶联受体 (GPCRs) 是一种将细胞膜外的刺激&#xff0c;传递到细胞膜内的跨膜蛋白&#xff0c;广泛参与到人体生理活动当中。近日&#xff0c;佛罗里达大学的研究者测定了 GPCRs 和 G 蛋白的结合选择性&#xff0c;并开发了预测二者选择性的算法&…

C++日常遇到的一些坑的总结

一、const 相关 C中const的不同位置的用法 const 修饰符用法总结 二、函数形参没有变量名 三、指针偏移问题 笔记&#xff1a; 包含来自C标准库的头文件&#xff0c;用#inlcude<xxx>&#xff0c;包含不来自C标准库的头文件&#xff0c;用#include"xxx"最…

【动手学深度学习】(十)PyTorch 神经网络基础

文章目录 一、层和块1.自定义块2.顺序块3.在前向传播函数中执行代码 二、参数管理1.参数访问2.参数初始化3.参数绑定 三、自定义层1.不带参数的层2.带参数的层 四、读写文件1.加载和保存张量2.加载和保存模型参数 [相关总结]state_dict() 一、层和块 为了实现复杂神经网络块&am…

论文投稿查询会议期刊及deadlines的网站

1. 这个是查近期CCF-ABC的ddl会议的网址 https://ccfddl.github.io/ https://ccfddl.top/ 2. 期刊选刊 https://ijournal.topeditsci.com/home https://journalsuggester.springer.com/ 3. IEEE出版物推荐 https://publication-recommender.ieee.org/home

java后端技术演变杂谈(未完结)

1.0版本javaWeb&#xff1a;原始servletjspjsbc 早期的jsp&#xff1a;htmljava&#xff0c;页面先在后端被解析&#xff0c;里面的java代码动态渲染完成后&#xff0c;成为纯html&#xff0c;再通过服务器发送给浏览器显示。 缺点&#xff1a; 服务器压力很大&#xff0c;因为…

【C语言】深入理解C语言中的数学运算和类型转换

文章目录 引言取负运算的奥秘源码探索分析与解读 浮点数运算的精细差异源码分析 精度损失与隐式类型转换精度和除零运算探究float类型和double类型的精度各是多少&#xff08;即十进制有效位的位数&#xff09;&#xff1f;在你的机器上&#xff0c;“负数开方”是如何处理的&a…

用友U8 Cloud TaskTreeQuery SQL注入漏洞复现

0x01 产品简介 用友U8 Cloud是用友推出的新一代云ERP&#xff0c;主要聚焦成长型、创新型企业&#xff0c;提供企业级云ERP整体解决方案。 0x02 漏洞概述 用友U8 Cloud /service/~iufo/nc.itf.iufo.mobilereport.task.TaskTreeQuery接口处存在SQL注入漏洞&#xff0c;未授权的…

类和对象——(6)友元

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 没有存储汗水&#xff0c;就无法支取成…

奥本海默-电影剧情简介

片头&#xff0c;奥本海默 脑海浮现恒星生命周期画面 1925年&#xff0c;奥本海默离开美国去欧洲学习新物理&#xff08;量子力学&#xff09; 脑海浮现量子力学相关画面&#xff08;像 德布罗意波&#xff09; 1927年从德国哥廷根大学毕业&#xff0c;获得物理学博士学位。…

MySQL笔记-第04章_运算符

视频链接&#xff1a;【MySQL数据库入门到大牛&#xff0c;mysql安装到优化&#xff0c;百科全书级&#xff0c;全网天花板】 文章目录 第04章_运算符1. 算术运算符2. 比较运算符3. 逻辑运算符4. 位运算符5. 运算符的优先级拓展&#xff1a;使用正则表达式查询 第04章_运算符 …

计算机辅助药物设计AIDD-小分子-蛋白质|分子生成|蛋白质配体相互作用预测

文章目录 计算机辅助药物设计AIDD【小分子专题】AIDD概述及药物综合数据库学习机器学习辅助药物设计图神经网络辅助药物设计自然语言处理辅助药物设计药物设计与分子生成 计算机辅助药物设计【蛋白质专题】蛋白质数据结构激酶-Kinase相似性学习基于序列的蛋白质属性预测基于结构…

【Windows】使用SeaFile搭建本地私有云盘并结合内网穿透实现远程访问

1. 前言 现在我们身边的只能设备越来越多&#xff0c;各种智能手机、平板、智能手表和数码相机充斥身边&#xff0c;需要存储的数据也越来越大&#xff0c;一张手机拍摄的照片都可能有十多M&#xff0c;电影和视频更是按G计算。而智能设备的存储空间也用的捉襟见肘。能存储大量…

使用typescript搭建express

使用typescript搭建express 开始 为这个项目创建一个新的目录&#xff0c;使用下面的命令初始化项目并创建一个包。 NPM init -y初始化后&#xff0c;让我们安装必要的包 npm i express dotenv cors helmet body-parser 在express中配置typescript npm i -D typescript typ…

filter过滤器

package com.it.filter;import javax.servlet.*; import javax.servlet.annotation.WebFilter;import java.io.IOException;WebFilter(urlPatterns"/*") public class DemoFilter implements Filter {Override // 初始化的方法 只要调用一次public void init(Filte…

【React 开发】增强你的React 技能:2024年要掌握的4种高级模式

React由于其简单和灵活&#xff0c;近年来已成为最受欢迎的前端库之一。然而&#xff0c;当应用程序的复杂性扩展时&#xff0c;管理状态、处理异步输入和维护可扩展的体系结构可能会变得困难。我们将在本文中介绍四种高级React模式&#xff0c;它们将帮助您克服这些困难以及如…

(C语言)判定一个字符串是否是另一个字符串的子串,若是则返回子串在主串中的位置。

要求&#xff1a; &#xff08;1&#xff09;在主函数中输入两个字符串&#xff0c;调用子函数cmpsubstr()判断&#xff0c;并在主函数输出结果。 &#xff08;2&#xff09;子函数的返回值为-1表示未找到&#xff0c;否则返回子串的位置&#xff08;起始下标&#xff09;。 …

人工智能-A*算法-八数码问题

一&#xff0c;A*算法设计思想 A*算法&#xff08;A-star&#xff09;是一种寻路算法&#xff0c;主要用于游戏、机器人等领域。 它的设计思想是将最短路径搜索问题转化为一个优化问题&#xff0c;通过计算每个节点的评分&#xff08;f(n) g(n) h(n)&#xff09;来寻找最优…

YOLOv8-Seg改进:简单高效的模块-现代反向残差移动模块 (iRMB) | | ICCV2023 EMO

🚀🚀🚀本文改进:设计了一种面向移动端应用的简单而高效的现代反向残差移动模块 (Inverted Residual Mobile Block, iRMB),它吸收了类似 CNN 的效率来模拟短距离依赖和类似 Transformer 的动态建模能力来学习长距离交互,引入YOLOV8 🚀🚀🚀YOLOv8-seg创新专栏:h…

【华为OD题库-064】最小传输时延I-java

题目 某通信网络中有N个网络结点&#xff0c;用1到N进行标识。网络通过一个有向无环图.表示,其中图的边的值表示结点之间的消息传递时延。 现给定相连节点之间的时延列表times[]{u&#xff0c;v&#xff0c; w)&#xff0c;其中u表示源结点&#xff0c;v表示目的结点&#xff0…

小程序长按识别二维码

小程序开发中要实现长按识别二维码的功能很简单&#xff0c;只需要在image标签里添加如下属性即可&#xff1a; 小程序版本&#xff1a; show-menu-by-longpress"{{true}}" uniapp版本&#xff1a; :show-menu-by-longpress"true" 举例&#xff1a; …