STM32串口DMA双缓冲,数据接收与发送,HAL库实现

news2024/11/15 5:48:01

STM32串口DMA双缓冲

1.简介

STM32F429系列DMA支持双缓冲模式进行数据传输,相当于数字电路设计领域的乒乓操作,但是HAL库并没有实现像单缓冲区一样可以简单使用的函数,有的方法是使用单缓冲的方式,但是通过接收半满的中断控制CPU进行数据处理,相当于是把一个缓冲区拆成两半使用。双缓冲的优势是显而易见的,在将串口数据写入到一个缓冲区时,CPU同时可以进行另一个缓冲区的读取,可以加快数据的处理

HAL库没有实现硬件双缓冲的代码,需要自己实现一下。

2.代码分析

要想自己实现一个双缓冲的接收函数,需要首先看一下HAL库是如何实现单缓冲模式的串口接收的。首先使用STM32CubeMX生成一个正常的开启DMA,串口的工程,先确保单缓冲的串口可以正常收发数据。

开启双缓冲,MDA会强制性切换为循环模式,无法调整。

HAL库启动串口DMA使用函数​HAL_UART_Receive_DMA

HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  /* Check that a Rx process is not already ongoing */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return HAL_ERROR;
    }

    /* Process Locked */
    __HAL_LOCK(huart);

    /* Set Reception type to Standard reception */
    huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;

    return (UART_Start_Receive_DMA(huart, pData, Size));
  }
  else
  {
    return HAL_BUSY;
  }
}

其中主要功能是UART_Start_Receive_DMA​函数

HAL_StatusTypeDef UART_Start_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  uint32_t *tmp;

  huart->pRxBuffPtr = pData;
  huart->RxXferSize = Size;

  huart->ErrorCode = HAL_UART_ERROR_NONE;
  huart->RxState = HAL_UART_STATE_BUSY_RX;

  /* Set the UART DMA transfer complete callback */
  huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt;

  /* Set the UART DMA Half transfer complete callback */
  huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt;

  /* Set the DMA error callback */
  huart->hdmarx->XferErrorCallback = UART_DMAError;

  /* Set the DMA abort callback */
  huart->hdmarx->XferAbortCallback = NULL;

  /* Enable the DMA stream */
  tmp = (uint32_t *)&pData;
  HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t *)tmp, Size);

  /* Clear the Overrun flag just before enabling the DMA Rx request: can be mandatory for the second transfer */
  __HAL_UART_CLEAR_OREFLAG(huart);

  /* Process Unlocked */
  __HAL_UNLOCK(huart);

  if (huart->Init.Parity != UART_PARITY_NONE)
  {
    /* Enable the UART Parity Error Interrupt */
    ATOMIC_SET_BIT(huart->Instance->CR1, USART_CR1_PEIE);
  }

  /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
  ATOMIC_SET_BIT(huart->Instance->CR3, USART_CR3_EIE);

  /* Enable the DMA transfer for the receiver request by setting the DMAR bit
  in the UART CR3 register */
  ATOMIC_SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);

  return HAL_OK;
}

UART_Start_Receive_DMA​函数的主要过程是

  • 给DMA传输完成,传输一半,和串口错误设置回调函数
  • 启动中断模式的DMA传输
  • 使能串口的DMA触发

然后再来看一下DMA传输完成的回调函数​UART_DMAReceiveCplt


/**
  * @brief  DMA UART receive process complete callback.
  * @param  hdma  Pointer to a DMA_HandleTypeDef structure that contains
  *               the configuration information for the specified DMA module.
  * @retval None
  */
static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma)
{
  UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
  /* DMA Normal mode*/
  if ((hdma->Instance->CR & DMA_SxCR_CIRC) == 0U)
  {
    huart->RxXferCount = 0U;

    /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
    ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE);
    ATOMIC_CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

    /* Disable the DMA transfer for the receiver request by setting the DMAR bit
       in the UART CR3 register */
    ATOMIC_CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

    /* At end of Rx process, restore huart->RxState to Ready */
    huart->RxState = HAL_UART_STATE_READY;

    /* If Reception till IDLE event has been selected, Disable IDLE Interrupt */
    if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
    {
      ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
    }
  }

  /* Check current reception Mode :
     If Reception till IDLE event has been selected : use Rx Event callback */
  if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
  {
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
    /*Call registered Rx Event callback*/
    huart->RxEventCallback(huart, huart->RxXferSize);
#else
    /*Call legacy weak Rx Event callback*/
    HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
  }
  else
  {
    /* In other cases : use Rx Complete callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
    /*Call registered Rx complete callback*/
    huart->RxCpltCallback(huart);
#else
    /*Call legacy weak Rx complete callback*/
    HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
  }
}

UART_DMAReceiveCplt​函数的主要内容是

  • 进行一些状态的更新、对一些中断标志位进行清除
  • 调用HAL开放出来让用户编写的中断服务函数​HAL_UART_RxCpltCallback(huart);

整体的流程是,HAL_UART_Receive_DMA​->UART_Start_Receive_DMA​->( 回调函数+HAL_DMA_Start_IT​)

要想实现双缓冲,可以仿造HAL库的方式,自己实现一个启动的流程,大体上直接复制HAL库的即可,关键部分进行自己的修改,最好不要直接在HAL库改,在其他的地方重新写。

1.实现HAL_UART_Receive_DMA_double​,主要复制HAL_UART_Receive_DMA​,最后调用的函数改为UART_Start_Receive_DMA_double


HAL_StatusTypeDef HAL_UART_Receive_DMA_double(UART_HandleTypeDef *huart, uint8_t *pData,uint8_t *pData1 ,uint16_t Size)
{
  /* Check that a Rx process is not already ongoing */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return HAL_ERROR;
    }

    /* Process Locked */
    __HAL_LOCK(huart);

    /* Set Reception type to Standard reception */
    huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;

    return (UART_Start_Receive_DMA_double(huart, pData,pData1, Size));
  }
  else
  {
    return HAL_BUSY;
  }
}

2.实现​UART_Start_Receive_DMA_double

HAL_StatusTypeDef UART_Start_Receive_DMA_double(UART_HandleTypeDef *huart, uint8_t *pData,uint8_t *pData1, uint16_t Size)
{
  uint32_t *tmp;
	uint32_t *tmp1;

  huart->pRxBuffPtr = pData;
  huart->RxXferSize = Size;

  huart->ErrorCode = HAL_UART_ERROR_NONE;
  huart->RxState = HAL_UART_STATE_BUSY_RX;

  /* Set the UART DMA transfer complete callback */
  huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt_M0;
huart->hdmarx->XferM1CpltCallback = UART_DMAReceiveCplt_M1;
  /* Set the UART DMA Half transfer complete callback */
  huart->hdmarx->XferHalfCpltCallback = NULL;

  /* Set the DMA error callback */
  huart->hdmarx->XferErrorCallback = UART_DMAError;

  /* Set the DMA abort callback */
  huart->hdmarx->XferAbortCallback = NULL;

  /* Enable the DMA stream */
  tmp = (uint32_t *)&pData;
	tmp1 = (uint32_t *)&pData1;
  //HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t *)tmp, Size);
	HAL_DMAEx_MultiBufferStart_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t *)tmp,*(uint32_t *)tmp1 ,Size);
  /* Clear the Overrun flag just before enabling the DMA Rx request: can be mandatory for the second transfer */
  __HAL_UART_CLEAR_OREFLAG(huart);

  /* Process Unlocked */
  __HAL_UNLOCK(huart);

  if (huart->Init.Parity != UART_PARITY_NONE)
  {
    /* Enable the UART Parity Error Interrupt */
    ATOMIC_SET_BIT(huart->Instance->CR1, USART_CR1_PEIE);
  }

  /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
  ATOMIC_SET_BIT(huart->Instance->CR3, USART_CR3_EIE);

  /* Enable the DMA transfer for the receiver request by setting the DMAR bit
  in the UART CR3 register */
  ATOMIC_SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);

  return HAL_OK;
}

后面启动DMA传输的函数更改为HAL_DMAEx_MultiBufferStart_IT​,此函数要求必须要有两个缓冲区满的回调函数以及传输错误的回调函数,因此必须实现这三个回调函数。

3.实现回调函数,直接复制HAL库原本的回调函数

/**
  * @brief  DMA UART receive process complete callback.
  * @param  hdma  Pointer to a DMA_HandleTypeDef structure that contains
  *               the configuration information for the specified DMA module.
  * @retval None
  */
static void UART_DMAReceiveCplt_M0(DMA_HandleTypeDef *hdma)
{
  UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
  /* DMA Normal mode*/
  if ((hdma->Instance->CR & DMA_SxCR_CIRC) == 0U)
  {
    huart->RxXferCount = 0U;

    /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
    ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE);
    ATOMIC_CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

    /* Disable the DMA transfer for the receiver request by setting the DMAR bit
       in the UART CR3 register */
    ATOMIC_CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

    /* At end of Rx process, restore huart->RxState to Ready */
    huart->RxState = HAL_UART_STATE_READY;

    /* If Reception till IDLE event has been selected, Disable IDLE Interrupt */
    if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
    {
      ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
    }
  }

  /* Check current reception Mode :
     If Reception till IDLE event has been selected : use Rx Event callback */
  if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
  {
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
    /*Call registered Rx Event callback*/
    huart->RxEventCallback(huart, huart->RxXferSize);
#else
    /*Call legacy weak Rx Event callback*/
    HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
  }
  else
  {
    /* In other cases : use Rx Complete callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
    /*Call registered Rx complete callback*/
    huart->RxCpltCallback(huart);
#else
    /*Call legacy weak Rx complete callback*/
    //HAL_UART_RxCpltCallback(huart);
    printf("000\r\n");
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
  }
}


/**
  * @brief  DMA UART receive process complete callback.
  * @param  hdma  Pointer to a DMA_HandleTypeDef structure that contains
  *               the configuration information for the specified DMA module.
  * @retval None
  */
static void UART_DMAReceiveCplt_M1(DMA_HandleTypeDef *hdma)
{
  UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
  /* DMA Normal mode*/
  if ((hdma->Instance->CR & DMA_SxCR_CIRC) == 0U)
  {
    huart->RxXferCount = 0U;

    /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
    ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE);
    ATOMIC_CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

    /* Disable the DMA transfer for the receiver request by setting the DMAR bit
       in the UART CR3 register */
    ATOMIC_CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

    /* At end of Rx process, restore huart->RxState to Ready */
    huart->RxState = HAL_UART_STATE_READY;

    /* If Reception till IDLE event has been selected, Disable IDLE Interrupt */
    if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
    {
      ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
    }
  }

  /* Check current reception Mode :
     If Reception till IDLE event has been selected : use Rx Event callback */
  if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
  {
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
    /*Call registered Rx Event callback*/
    huart->RxEventCallback(huart, huart->RxXferSize);
#else
    /*Call legacy weak Rx Event callback*/
    HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
  }
  else
  {
    /* In other cases : use Rx Complete callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
    /*Call registered Rx complete callback*/
    huart->RxCpltCallback(huart);
#else
    /*Call legacy weak Rx complete callback*/
    //HAL_UART_RxCpltCallback(huart);
    printf("11111\r\n");
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
  }
}

static void UART_DMAError(DMA_HandleTypeDef *hdma)
{
  uint32_t dmarequest = 0x00U;
  UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;

  /* Stop UART DMA Tx request if ongoing */
  dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAT);
  if ((huart->gState == HAL_UART_STATE_BUSY_TX) && dmarequest)
  {
    huart->TxXferCount = 0x00U;
    UART_EndTxTransfer(huart);
  }

  /* Stop UART DMA Rx request if ongoing */
  dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
  if ((huart->RxState == HAL_UART_STATE_BUSY_RX) && dmarequest)
  {
    huart->RxXferCount = 0x00U;
    UART_EndRxTransfer(huart);
  }

  huart->ErrorCode |= HAL_UART_ERROR_DMA;
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
  /*Call registered error callback*/
  huart->ErrorCallback(huart);
#else
  /*Call legacy weak error callback*/
  HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}

在这里的回调函数中简单测试,如果第一个缓冲区满就输出0,第二个缓冲区满就输出1,

定义两个缓冲区

uint8_t rx_buf[10];
uint8_t rx_buf1[10];

在主函数中调用

HAL_UART_Receive_DMA_double(&huart1,rx_buf,rx_buf1,10);

​[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-70TGixxb-1686544064828)(https://assets.b3logfile.com/siyuan/1675829124924/assets/串口发送-20230610194158-8imrwc7.gif)]​

双缓冲基本实现

3.数据转发实现

在上述简单的接收双缓冲实现的情况下,尝试实现将接收到的数据转发出去,首先将双缓冲区的大小设置为10字节,因为在双缓冲发挥作用的场景下,应该是接收大量的数据,只有将缓冲设置的小一点,才能测试在相对于缓冲区大量的数据接收,如果缓冲区就设置1k字节,那我得发多少才算得上是大量呢?

数据转发的原理如下:

  • 首先设置两个发送缓冲区
  • 接收缓冲区1满就将数据复制到发送缓冲区1,接收缓冲区2满就将数据复制到发送缓冲区2
  • 数据复制完毕后,主函数中根据状态控制将准备好的发送缓冲区1或者2通过串口DMA再发送出去
  while (1)
  {
    if (rx_state == 1)//接收缓冲区1完成
    {
      if (tx_state == 2)//发送缓冲区2发送完成
      {
        tx_state_strat = 1;
        tx_state = 0;
        HAL_UART_Transmit_DMA(&huart1,tx_buf,buf_size);//开始发送 发送缓冲区1的数据
      }
    }
    if (rx_state == 2)//接收缓冲区2完成
    {
      if (tx_state == 1)//发送缓冲区1发送完成
      {
        tx_state_strat = 2;
        tx_state = 0;
        HAL_UART_Transmit_DMA(&huart1,tx_buf1,buf_size);//开始发送 发送缓冲区2的数据
      }
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

上述原理不难理解,看起来也没什么问题,简单测试也没问题,但是在继续增大数据量的时候数据的转发出现了问题。

image

数据出现了丢失!!!!!数据量少一点的时候一切正常,数据量超过某个值之后就会稳定的丢20个字节的数据。

接逻辑分析仪进行观察。

image

发现在数据传输中途缺失数据。

为了分析执行过程,在双缓冲切换回调函数以及串口DMA发送完成回调函数进行管脚电平翻转,

image

在1的位置接收缓冲区1满,2的位置是发送缓冲区1发送完毕,3是接受缓冲区2满,4是发送缓冲区2发送完成

image

数据变多之后,二者的相位差越来越大,原因是,接收来的数据每个字节的间隔比较短,而转发之后,由于每个字节之间需要执行程序,就会导致两个发送字节之间的间隔比接收的间隔长,这样一直累计起来就会导致发送和接收的相位差越来越大。

image

最终发送和接收的时间差累计到一个缓冲区接收的时间后,就会导致数据丢失。

image

现在这个情况相当于是对接收到的速度进行处理的速度比接收的速度慢,因为波特率相同,而发送数据还需要一段处理的时间,就导致发送速度慢于接收的速度,这样就会导致数据丢失,如果不从根本上提升数据发送的速度,永远会有数据丢失,所以应该换一个串口以更快的波特率发送数据。

增加一个串口,波特率设置为921600,缓冲区仍然为10字节,一次性发送5760字节,转发也没有问题

image

image

4.总结

接收使用双缓冲,好处是可以使CPU在处理接收到的数据的同时,新的数据接收还可以正常进行。但是也需要保证对数据的处理速度是要比接收的速度快的,否则一定会导致数据丢失。

如果需要接收的数据长度不可知,还需要结合空闲中断实现数据不定长的接收,可以参考http://t.csdn.cn/vFnt8

工程代码已经上传

https://download.csdn.net/download/Master_0_/87897707


参考链接

https://blog.csdn.net/zhang1079528541/article/details/121049656

https://www.sohu.com/a/260229041_807475

https://blog.csdn.net/qq_51368339/article/details/124439407

https://www.cnblogs.com/puyu9499/archive/2022/02/19/15914090.html

https://blog.csdn.net/qq_20553613/article/details/108367512

https://blog.csdn.net/qq_19999465/article/details/81054680

http://t.csdn.cn/XCan6


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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

头文件header file和源文件 source file

头文件 header file,后缀是.h,头文件负责类的定义,函数声明,常量的定义 源文件source file,后缀是.cpp, 函数的实现 最主要的作用就是将函数的声明和实现分开,如果想将类和函数交给别人使用&a…

python基本语法知识(五)——面向对象

类和对象 例子1 class Student:name Nonegender Nonenationality Nonenative_place None # 籍贯age None# 类内的成员方法,第一个参数必须为 self,# self相当于是当前对象def say_hi(self):print(f"大家好,我是{self.name}")def say_hi2(self, msg):print(f&q…

自学网络安全怎么规划路线?

01 什么是网络安全 网络安全可以基于攻击和防御视角来分类,我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域,都有攻与防两面…

HuggingFace Bert语言模型抽取句子表征向量

1. 安装相应的包 pip install transformers2. 具体代码 from transformers import BertTokenizer, BertModeltokenizer BertTokenizer.from_pretrained(bert_base_cased, cache_dir"dblp/bert_base_cased", local_files_onlyTrue) model BertModel.from_pretrain…

第七十一天学习记录:对陈正冲编著《C 语言深度解剖》中关于1、2、4,5章作者留下部分问题的学习

问&#xff1a;有如下代码。 static int j;void fun1(void) {static int i 0;i; }void fun2(void) {j 0; j; }int main() {int k 0;for (k 0; k<10; k){fun1();fun2();}return 0; }i 和 j 的值分别是什么,为什么&#xff1f; 答&#xff1a; 在这份代码中&#xff0c;变…

Dtop环球嘉年华推动Web 3.0国际电商领域的革新

随着互联网技术的与日俱进和数字经济的高速发展,国际贸易正在经历一场重大变革。其中,跨境电商作为一种新兴的商业模式,正在全球迅速崛起。然而,自2021年以来,跨境电商行业也面临着各种风险和挑战,如各国增值税新规、电商账号封禁事件以及海运价格持续上涨等冲击。这些因素共同…

Spark SQL数据帧与数据集

文章目录 一、数据帧 - DataFrame&#xff08;一&#xff09;DataFrame概述&#xff08;二&#xff09;将RDD转成DataFrame 二、数据集 - Dataset&#xff08;一&#xff09;Dataset概述&#xff08;二&#xff09;将RDD转成DataSet&#xff08;三&#xff09;DataFrame与Datas…

ATA-4000系列高压功率放大器——超声导波中的典型应用

ATA-4000系列高压功率放大器——超声导波中的典型应用 超声导波技术 超声导波(guided waves ultrasonic)技术是一项近年来广受关注的无损检测技术。导波是由于介质边界存在而被限制在介质中传播的、同时其传播方向平行于介质边界的波。超声导波较早期的研究由一些著名学者完成&…

react + antd实现动态切换主题功能(适用于antd5.x版本)

前言 在之前的几篇文章中&#xff08;React Antd实现动态切换主题功能之二&#xff08;默认主题与暗黑色主题切换&#xff09;、React Antd实现动态切换主题功能&#xff09;介绍了antd实现动态切换主题功能&#xff0c;文章里介绍的方法在antd5.x版本却不适用&#xff0c;因…

【UE5】分分钟简单使用像素流云服务(Pixel Streaming)

【UE5】分分钟简单使用像素流云服务&#xff08;Pixel Streaming&#xff09; 前言 UE5的Pixel Streaming已经封装的很好&#xff0c;简单三步实现简单的服务搭建。 安装插件打包项目运行服务 注&#xff1a;实例平台为Windows 安装插件 编辑→插件→输入查询Pixel Strea…

Java之~Bigdicmal的长度,小数点后用0补位

/*** 获取bigdicmal的长度* param number 需要截取的数字* param num 需要保留小数点后的位数* return*/ public static int checkBigDecimalLength(String number ,int num){String s getSupString(number,num);return s.length(); }/*** 补充后面数值0* param number 需要截…

5G技术学习——5GNR帧结构和空口资源

这里写目录标题 4G时域定义&#xff1a;资源划分 5GNR中时域 频域 与空域资源 循环前缀CP:背景和原理5G帧结构&#xff1a;基本框架5G slot分类 5G 频域资源5G频域资源基本概念信道带宽与传输带宽BWP定义及其应用场景 4G 时域定义&#xff1a; 帧&#xff1a;10ms&#xff0c;…

【go-zero】go-zero微服务实战系列九 聚合策略 极致优化秒杀性能(转)

上一篇文章中引入了消息队列对秒杀流量做削峰的处理,我们使用的是Kafka,看起来似乎工作的不错,但其实还是有很多隐患存在,如果这些隐患不优化处理掉,那么秒杀抢购活动开始后可能会出现消息堆积、消费延迟、数据不一致、甚至服务崩溃等问题,那么后果可想而知。本篇文章我们…

深度学习-pytorch_lightning框架使用实例

下面是我写过的一个pytorch_lightning项目的代码框架。关键代码已经省略。 模型构建 import pytorch_lightning as pl from pytorch_lightning.plugins.io import TorchCheckpointIO as tcio import torch from torch import nn import torch.nn.functional as F from torch.…

师生相逢,潇潇洒洒出品

师生相逢2023年6月10日潇潇洒洒出品骑行肩并肩 相望曾相识①遥忆多年前 青春勃发时豪情梦摘星 清纯玉壶冰感叹岁月老 友情弥久新寄情山水间 天涯不负卿①骑行路上&#xff0c;偶遇30年前的学生&#xff0c;现在是骑行群的骑友&#xff0c;共同的兴趣爱好使我们30年后再相逢&…

【服务器数据恢复】热备盘激活失败导致raid5瘫痪的数据恢复案例

服务器数据恢复环境&#xff1a; 一台EMC存储中数块磁盘组建了一组raid5磁盘阵列&#xff0c;阵列中有2块热备盘&#xff1b;上层采用ZFS文件系统&#xff0c;划分了一个lun&#xff0c;供sun小机使用。 服务器故障&检测&#xff1a; 存储在正常运行中突然崩溃无法使用&…

STM32单片机(五)第一节:EXTI外部中断

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

AST反混淆js代码——猿人学竞赛第二题

猿人学JS比赛第二题解混淆 分析代码解混淆代码结果 前一段时间参加猿人学js比赛&#xff0c;今天把第二题的还原做一下笔记。 分析代码 首先&#xff0c;我们需要对混淆js代码进行分析&#xff0c;确定还原的思路&#xff0c;才能书写解混淆代码进行还原。代码是静态的&#x…

KYOCERA Programming Contest 2023(AtCoder Beginner Contest 305)(A、B、C、D)[施工中]

文章目录 A - Water Station(模拟)B - ABCDEFG&#xff08;模拟&#xff09;C - Snuke the Cookie Picker(模拟、暴力)D - Sleep Log&#xff08;二分&#xff0c;前缀&#xff09; A - Water Station(模拟) 题意&#xff1a;在[0,100]所有 x % 5 0的地方设置一个水站&#x…

由源码生成Python可调用的dll

1. 不带参数的函数与调用 blog.csdn.net/qq 40833391/article/details/128000638python编程&#xff08;python调用dll程序&#xff09;_python 调用dll_嵌入式-老费的博客-CSDN博客思路很简单&#xff0c;只需要在Visual Studio中设置输出类型即可 1.1. 创建项目 首先创建一…