串口DMA接收数据基本思路
串口DMA接收基本思路
- 串口DMA接收数据基本思路
- 一、串口处理使用背景及常用处理方法
- 二、串口DMA接收相关思路
- 三、串口DMA发送相关思路
串口DMA接收数据基本思路
一、串口处理使用背景及常用处理方法
单片机经常有串口处理大量数据的场景,常用的串口处理数据方式有如下方式:
a.串口接收中断接收 + 串口阻塞发送,即使用串口接收中断对数据进行接收。然后处理完毕后在主程序中调用串口发送函数进行发送。该方式每接收一个数据便进入一次接收中断,且发送是阻塞发送,会阻碍其他处理操作运行。适合数据量不大的场景。
b.串口空闲中断(接收中断) + 环形缓冲区 + 串口阻塞发送,该方法使用串口空闲中断和接收中断配合环形缓冲区可以解决大部分的串口数据处理场景。适合数据量较大的场景。
c.串口中断 + DMA接收 + DMA发送,该方法使用了DMA可以释放CPU的占用,CPU由每次接收触发中断变成接收完毕触发或DMA发送完毕触发,减少中断触发频率。适合数据量较大的场景。
以上三种方式各有自己的优缺点,DMA串口尽管处理效率看起来是最高的,但是耗费了DMA资源。而DMA资源往往是单片机较为稀缺的资源。因此还是需要根据项目需求合理选择。本次主要是在资源富裕的情况下使用DMA进行串口的收发测试。
二、串口DMA接收相关思路
DMA串口接收,大致流程如上图所示;因为本次使用的是STM32F1系列的DMA,只有传送任务完成一半的中断(DMA1_IT_HTx)。因此借用HTx中断实现双缓冲区效果进行接收。[本篇只针对串口DMA进行解析流程,DMA相关基础知识请参考其他文章]。
如上图中,需要准备一个缓冲区(即用户自己管理的数组),这个数组需要在程序编译后有确定的地址(不要使用会被析构掉的数组地址)。原因是:开启DMA传送后,DMA传送是不经过CPU的直接地址之间的快速传送。此时CPU也会同步正常运行,而如果该地址会因CPU程序运行用作别的功能使用,则会使DMA传送数据出错。
简单点来说,串口接收DMA传送过程类似:CPU跟DMA控制器下发一个指令说,等串口收到数据了,你把收到的数据搬运到仓库里存着(发送缓冲区),这次就先搬运100个。CPU下完这个指令后,就能去执行别的指令处理任务,DMA同时也会开始搬运的工作;这样就可以实现CPU等100个数据都接收完了,再一次性处理,降低了CPU一个一个数据处理的工作量。这里的等串口收到数据了,就是DMA请求,串口每接收完毕一个数据,就会通知DMA,我收到了一个数据,你可以来搬运了。而100个就是DMA传送的数量,即这次DMA只搬运100个数据就完成本次DMA传送。
这样的好处就是,CPU只在下发DMA指令的时候占用一瞬间,便可以去干别的工作,等DMA传送完毕之后再进行数据批量处理,极大减少了CPU占用的时间。那么想想一下如下的场景,假如一次DMA传送完成了,CPU正在处理数据的时间,串口又来了新数据,这时候因为数据还没处理完,发送缓冲区还不能释放出来进行下一次DMA接收,这个时候就会导致数据丢失。于是这种机制还需要进一步改进。
假如我们将接收缓冲区Buffer人工区分成两个片区,CPU要一次性处理100个数据,分为每次处理50个数据,再借助半满中断就能实现如上图的过程。
1、CPU下发DMA传送指令,传送100个数据。此时设置接收缓冲区大小为100个。
2、当接收到50个数据时,即接收的数据填满接收缓冲区前50个数据时,进入DMA半满中断。在半满中断中通知CPU将接收到的数据搬入FIFO中(此时DMA仍旧在向后50个地址写入数据)。
3、当接收到100个数据时,进入DMA完成中断。在DMA完成中断中,首先重置DMA继续进入下一轮的100个数据接收状态(这时由于接收缓冲区前50个地址数据已经被搬入FIFO中,可以直接使能DMA进行继续接收),然后CPU将后50个数据搬入FIFO中。
4、CPU在前台对FIFO中数据进行逐一处理。
可以看到,如果整体流程变为这样时,CPU提前对数据进行搬运,耗费一定CPU资源,但能够做到串口丢数据最少的操作。当然这样能够持续进行的前提一定是CPU将数据搬入FIFO的时间,要小于DMA将数据搬入数组的时间。以串口波特率为115200(bit/s)为例,数据位8位,停止位1位,总计一个有效数据为9位。接收1个数据耗时:1/115200 * 9 = 7.8e-5 (该时间还未加上DMA搬运时间);而单片机假如按照1M的速率进行处理1Byte数据,也能到达1e-6速率。因此CPU肯定是能够在DMA传送完毕之前,完成前半个缓冲区域的数据处理。
即使这样,目前还剩最后一个问题需要解决;假设我们串口目前只有80个数据需要接收,按照我们优化后的程序去执行,想想会发生什么?由于我们CPU给DMA下发的是接收100个数据才完成。在接收到50个数据时,进入半满中断搬运;然后继续接收后面的50个数据。显而易见,DMA只能再接收到30个数据,便再也等不到数据了。这时会一直等不到DMA完成中断,等不到DMA完成中断,DMA接收的数据便无法被存在入用户FIFO中。造成数据本次不能完整的接收(只有再接收20个数据,凑满100个才能触发搬运)。
为了解决该问题,需要加入串口空闲中断,即在串口空闲时,通知CPU来将数据搬运到FIFO中,同时重置DMA接收。这样便能接收不定长的数据,下一次数据变成30个也能完成接收。而串口空闲中断需要的信息有:当前接收缓冲区的首地址(是起始地址?还是一半的地址?)和需要搬移的数量。缓冲区首地址可以通过变量进行记录,DMA启动后起始地址为0,DMA半满中断触发后起始地址为缓冲区的一半。搬移数据量可以通过DMA计数器获得,DMA内部存在一个计数器,比如我们这里给的是初值100,DMA控制器每搬运一个,该值就会自减1,如变为99,98,97……。计数器变为50,触发半满中断;直到计数器变为0,则触发DMA完成中断。这样我们便可以通过获取该计数器值来计算本次DMA已经传送了多少数据。如上面所说的80个数据,此时计算规则为:100-20-50 = 30;100为DMA总传送数量;20为当前DMA计数器的值;50为buffer的起始地址(前50个数据已经传送完毕),程序部分如下:
#define USART1_DMA_BUF_MAX_LEN 128
#define USART1_DMA_FIFO_MAX_LEN 512_fifo_t dma_rx_fifo_uart1;
uint8_t dma_uart1_fifo_buf[USART1_DMA_FIFO_MAX_LEN]={0};
uint8_t dma_uart1_rx_buf[USART1_DMA_BUF_MAX_LEN] = {0};
uint8_t dma_uart1_tx_buf[USART1_DMA_BUF_MAX_LEN] = {0x01,0x02};
uint8_t lock_state = 0;uart_dev uart_dev_uart1 =
{USART1 , //串口外设1115200 , //buad&dma_rx_fifo_uart1, //DMA_RX_FIFOdma_uart1_rx_buf , //DMA_RX_BUFdma_uart1_tx_buf //DMA_TX_BUF
};/*** @brief 初始化串口GPIO* @param * @retval 0:成功; 1:失败
*/
uint32_t uart_dev_gpio_init()
{GPIO_InitTypeDef gpio_initstruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//PA9 TXDgpio_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;gpio_initstruct.GPIO_Pin = GPIO_Pin_9;gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpio_initstruct);//PA10 RXDgpio_initstruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;gpio_initstruct.GPIO_Pin = GPIO_Pin_10;gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpio_initstruct);return 0;
}/*** @brief 初始化串口外设* @param * @retval 0:成功; 1:失败
*/
uint32_t uart_dev_uart_init (uart_dev* uart_dev)
{ USART_InitTypeDef usart_initstruct;NVIC_InitTypeDef nvic_initstruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);usart_initstruct.USART_BaudRate = uart_dev ->baud;usart_initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控usart_initstruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //接收和发送usart_initstruct.USART_Parity = USART_Parity_No; //无校验usart_initstruct.USART_StopBits = USART_StopBits_1; //1位停止位usart_initstruct.USART_WordLength = USART_WordLength_8b; //8位数据位USART_Init(uart_dev->USARTx, &usart_initstruct);USART_ClearFlag(USART1, USART_FLAG_TC|USART_FLAG_RXNE); //清除默认标志位(主要是TC)USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //串口DMA接收使能USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //串口DMA发送使能USART_Cmd(USART1, ENABLE); //使能串口USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //使能空闲中断nvic_initstruct.NVIC_IRQChannel = USART1_IRQn;nvic_initstruct.NVIC_IRQChannelCmd = ENABLE;nvic_initstruct.NVIC_IRQChannelPreemptionPriority = 0;nvic_initstruct.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&nvic_initstruct);return 0;
}/*** @brief 初始化串口DMA* @param * @retval 0:成功; 1:失败
*/
uint32_t uart_dev_DMA_init(uart_dev* uart_dev)
{NVIC_InitTypeDef nvic_initstruct;DMA_InitTypeDef DMA_USART1_InitStructure;RCC_AHBPeriphClockCmd (RCC_AHBPeriph_DMA1,ENABLE);DMA_DeInit(DMA1_Channel5);DMA_USART1_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &USART1->DR ; //USART1数据接收寄存器DMA_USART1_InitStructure.DMA_MemoryBaseAddr = (uint32_t) uart_dev ->dma_rx_buff; //接收寄存器区间地址DMA_USART1_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC ; //从外设读取; 外设 ——> 内存DMA_USART1_InitStructure.DMA_BufferSize = USART1_DMA_BUF_MAX_LEN ; //传送长度DMA_USART1_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable ; //外设地址不增DMA_USART1_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable ; //内存地址自增DMA_USART1_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //传送数据为8位DMA_USART1_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte ; //MemoryDataSizeDMA_USART1_InitStructure.DMA_Mode = DMA_Mode_Normal ; //普通模式 单次DMA_USART1_InitStructure.DMA_Priority = DMA_Priority_VeryHigh ; //传送优先级非常高DMA_USART1_InitStructure.DMA_M2M = DMA_M2M_Disable ; //从外设触发DMA_Init(DMA1_Channel5,&DMA_USART1_InitStructure);//开启DMADMA_Cmd(DMA1_Channel5,DISABLE);DMA_SetCurrDataCounter(DMA1_Channel5, USART1_DMA_BUF_MAX_LEN);DMA_Cmd(DMA1_Channel5,ENABLE);DMA_ClearFlag(DMA1_IT_TC5|DMA1_IT_HT5); DMA_ITConfig(DMA1_Channel5,DMA_IT_HT|DMA_IT_TC|DMA_IT_TE,ENABLE); //使能传送一半及全部传送完毕中断nvic_initstruct.NVIC_IRQChannel = DMA1_Channel5_IRQn;nvic_initstruct.NVIC_IRQChannelCmd = ENABLE;nvic_initstruct.NVIC_IRQChannelPreemptionPriority = 0;nvic_initstruct.NVIC_IRQChannelSubPriority = 2;NVIC_Init(&nvic_initstruct); return 0;
}/*** @brief 清除DMAx通道* @param * @retval 0:成功; 1:失败
*/uint32_t uart_dev_clear_dma_channel(DMA_Channel_TypeDef* DMAy_Channelx)
{DMA_Cmd(DMAy_Channelx,DISABLE);DMA_SetCurrDataCounter(DMAy_Channelx,USART1_DMA_BUF_MAX_LEN);DMA_Cmd(DMAy_Channelx,ENABLE);return 0;
}/*
************************************************************
* 函数名称: USART1_IRQHandler
*
* 函数功能: 串口1收发中断
*
* 入口参数: 无
*
* 返回参数: 无
*
* 说明:
************************************************************
*/
void USART1_IRQHandler(void)
{uint8_t temp = 0;uint16_t recv_size = 0;if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断{temp = USART1->DR;USART_ClearFlag(USART1, USART_FLAG_RXNE);}else if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) //空闲中断{temp = USART1->SR; //清除中断标志temp = USART1->DR;USART_ClearFlag(USART1, USART_IT_IDLE);recv_size = USART1_DMA_BUF_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5) - uart_dev_uart1.dma_rx_buff_curr_addr; //重置DMAuart_dev_clear_dma_channel(DMA1_Channel5);fifo_write(uart_dev_uart1.dma_rx_fifo, &uart_dev_uart1.dma_rx_buff[uart_dev_uart1.dma_rx_buff_curr_addr], recv_size);uart_dev_uart1.dma_rx_buff_curr_addr = 0; }}/*
************************************************************
* 函数名称: DMA1_Channel5_IRQHandler(串口接收中断)
*
* 函数功能: DMA1_Channel5中断
*
* 入口参数: 无
*
* 返回参数: 无
*
* 说明:
************************************************************
*/
void DMA1_Channel5_IRQHandler(void)
{uint16_t recv_size = 0;if(DMA_GetITStatus(DMA1_IT_HT5) != RESET) //DMA传送一半中断{DMA_ClearITPendingBit(DMA1_IT_HT5); // 清除传输传送一半中断中断标志位 //搬移前半个buf数据recv_size = USART1_DMA_BUF_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5); fifo_write(uart_dev_uart1.dma_rx_fifo, uart_dev_uart1.dma_rx_buff, recv_size); //更新当前地址uart_dev_uart1.dma_rx_buff_curr_addr += recv_size;}else if(DMA_GetITStatus(DMA1_IT_TC5) != RESET) //DMA传送完成中断{DMA_ClearITPendingBit(DMA1_IT_TC5); // 清除传输完成中断标志位 recv_size = USART1_DMA_BUF_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5) - uart_dev_uart1.dma_rx_buff_curr_addr; //重置DMAuart_dev_clear_dma_channel(DMA1_Channel5);fifo_write(uart_dev_uart1.dma_rx_fifo, &uart_dev_uart1.dma_rx_buff[uart_dev_uart1.dma_rx_buff_curr_addr], recv_size);uart_dev_uart1.dma_rx_buff_curr_addr = 0;}else if(DMA_GetITStatus(DMA1_IT_TE5) != RESET){UsartPrintf(USART2," DMA ERROR \r\n");}}
三、串口DMA发送相关思路
如果理解了发送原理,接收原理相对来说便简单的多;将需要发送的数据填入发送buff,通知DMA将数据进行发送便可以实现。这里需要注意的是因为串口接收和发送DMA同属于一个DMA的不同通道,两个是没法同时进行的,如果是收发双工同时使用这一个DMA,可能会地效率有所影响。
需要注意的是,因为发送是非阻塞进行的;一定要获取该次发送完成后,再进行下一次发送;当然若发送大量数据可以仿照接收,也使用类似与双buffer的方式进行发送。
/*** @brief 初始化串口DMA 并发送数据(发送串口)* @param * @retval
*/
void uart_dev_DMA_tx_data(uart_dev* uart_dev )
{NVIC_InitTypeDef nvic_initstruct;DMA_InitTypeDef DMA_USART1_InitStructure;//使能DMA1 时钟RCC_AHBPeriphClockCmd (RCC_AHBPeriph_DMA1,ENABLE);DMA_DeInit(DMA1_Channel4);DMA_USART1_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &USART1->DR ; //USART1数据接收寄存器DMA_USART1_InitStructure.DMA_MemoryBaseAddr = (uint32_t) uart_dev ->dma_tx_buff; //发送寄存器区间地址DMA_USART1_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST ; //往外设发送; 内村 ——> 外设DMA_USART1_InitStructure.DMA_BufferSize = USART1_DMA_BUF_MAX_LEN ; //传送长度DMA_USART1_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable ; //外设地址不增DMA_USART1_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable ; //内存地址自增DMA_USART1_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //传送数据为8位DMA_USART1_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte ; //MemoryDataSizeDMA_USART1_InitStructure.DMA_Mode = DMA_Mode_Normal ; //普通模式 单次DMA_USART1_InitStructure.DMA_Priority = DMA_Priority_High ; //传送优先级高DMA_USART1_InitStructure.DMA_M2M = DMA_M2M_Disable ; //从外设触发DMA_Init(DMA1_Channel4,&DMA_USART1_InitStructure);//开启DMADMA_Cmd(DMA1_Channel4,DISABLE);DMA_SetCurrDataCounter(DMA1_Channel4, USART1_DMA_BUF_MAX_LEN);DMA_Cmd(DMA1_Channel4,ENABLE);DMA_ClearFlag(DMA1_IT_TC4); DMA_ITConfig(DMA1_Channel4,DMA_IT_TC|DMA_IT_TE,ENABLE); //使能传送一半及全部传送完毕中断nvic_initstruct.NVIC_IRQChannel = DMA1_Channel4_IRQn;nvic_initstruct.NVIC_IRQChannelCmd = ENABLE;nvic_initstruct.NVIC_IRQChannelPreemptionPriority = 0;nvic_initstruct.NVIC_IRQChannelSubPriority = 3;NVIC_Init(&nvic_initstruct); }/*
************************************************************
* 函数名称: DMA1_Channel4_IRQHandler
*
* 函数功能: DMA1_Channel4中断
*
* 入口参数: 无
*
* 返回参数: 无
*
* 说明:
************************************************************
*/void DMA1_Channel4_IRQHandler(void)
{uint16_t recv_size = 0;if(DMA_GetITStatus(DMA1_IT_TC4) != RESET) //DMA传送完成{uart_dev_uart1.dma_tx_idle_state = 1;DMA_ClearITPendingBit(DMA1_IT_TC4); // 清除传输完成 }else if(DMA_GetITStatus(DMA1_IT_TE4) != RESET){UsartPrintf(USART2," DMA uart tx ERROR \r\n");}}
相关文章:

串口DMA接收数据基本思路
串口DMA接收基本思路 串口DMA接收数据基本思路一、串口处理使用背景及常用处理方法二、串口DMA接收相关思路三、串口DMA发送相关思路 串口DMA接收数据基本思路 一、串口处理使用背景及常用处理方法 单片机经常有串口处理大量数据的场景,常用的串口处理数据方式有如…...

数据结构复习 (二叉查找树,高度平衡树AVL)
1.二叉查找树: 为了更好的实现动态的查找(可以插入/删除),并且不超过logn的时间下达成目的 定义: 二叉查找树(亦称二叉搜索树、二叉排序树)是一棵二叉树,其各结点关键词互异,且中根序列按其关键词递增排列。 等价描述: 二叉查找…...

FreeSWITCH 简单图形化界面39 - Windows安装FreeSWITCH For IPPBX(WSL环境)
FreeSWITCH 简单图形化界面39 - Windows安装FreeSWITCH For IPPBX(WSL环境) 0、界面预览1、部署WSL1.1 安装WSL1.2 安装Windows Terminal1.3 安装WSL配置工具 2、安装Ubuntu24.043、安装FreeSWITCH4、登录Web4.1 80端口占用了 5、测试6、卸载 0、界面预览…...

uniapp - 小程序实现摄像头拍照 + 水印绘制 + 反转摄像头 + 拍之前显示时间+地点 + 图片上传到阿里云服务器
前言 uniapp,碰到新需求,反转摄像头,需要在打卡的时候对上传图片加上水印,拍照前就显示当前时间日期地点,拍摄后在呈现刚才拍摄的图加上水印,最好还需要将图片上传到阿里云。 声明 水印部分代码是借鉴的…...

Qt天气预报系统设计界面布局第四部分左边
Qt天气预报系统设计 1、第四部分左边的第一部分1.1添加控件1.2修改控件名字 2、第四部分左边的第二部分2.1添加控件2.2修改控件名字 3、第四部分左边的第三部分3.1添加控件3.2修改控件名字 4、对整个widget04l调整 1、第四部分左边的第一部分 1.1添加控件 拖入一个widget&…...

VS无法找到低版本的.net,vs2022创建不了.net6的项目
很多人会遇到安装完vs最新版(目前是2022)之后,创建不了旧版本的.net项目了,比如我在学习.net core 6,我的2022无法创建,只能创建.netcore8的项目,以及又安装了2019,同样无法创建,接下来介绍怎么…...
C++软件设计模式之解释器模式
解释器模式的目的和意图 解释器模式(Interpreter Pattern)是一种行为设计模式,主要用于定义一种语言的文法,并通过该文法解释语言中的句子(表达式)。解释器模式的核心思想是将一个特定的语言表示为其文法规…...

小程序发版后,用户使用时,强制更新为最新版本
为什么要强制更新为最新版本? 在小程序的开发和运营过程中,强制用户更新到最新版本是一项重要的策略,能够有效提升用户体验并保障系统的稳定性与安全性。以下是一些主要原因: 1. 功能兼容 新功能或服务通常需要最新版本的支持&…...

如何使用AI工具cursor(内置ChatGPT 4o+claude-3.5)
⚠️温馨提示: 禁止商业用途,请支持正版,充值使用,尊重知识产权! 免责声明: 1、本教程仅用于学习和研究使用,不得用于商业或非法行为。 2、请遵守Cursor的服务条款以及相关法律法规。 3、本…...

说说缓存使用的具体场景都有哪些?缓存和数据库一致性问题该如何解决?缓存使用常见问题有哪些?
面试官:说说缓存使用的具体场景都有哪些?缓存和数据库一致性问题该如何解决?缓存使用常见问题有哪些? 缓存的具体使用场景有这些: 数据频繁读取: 当某些数据频繁被读取而不常变化时,可以将这些…...

2025-01-01 NO2. XRHands 介绍
文章目录 软件配置1 XR Hands 简介2 XRHand2.1 Pose2.2 Handedness 3 XRHandJoint3.1 XRHandJointID3.2 XRHandJointTrackingState 4 XRHandSubsystem4.1 数据属性4.1.1 UpdateSuccessFlags4.1.2 UpdateType 4.2 处理器管理:注册和注销4.3 更新手部数据:…...

Java开发-后端请求成功,前端显示失败
文章目录 报错解决方案1. 后端未配置跨域支持2. 后端响应的 Content-Type 或 CORS 配置问题3. 前端 request 配置问题4. 浏览器缓存或代理问题5. 后端端口未被正确映射 报错 如下图,后端显示请求成功,前端显示失败 解决方案 1. 后端未配置跨域支持 …...
未来20年在大语言模型相关研究方向--大语言模型的优化与改进
未来20年在大语言模型相关研究方向 模型性能优化 模型架构创新:研究新型的模型架构,如探索更高效的Transformer变体、融合递归神经网络(RNN)和卷积神经网络(CNN)的优点,以提高模型的性能、可扩展性和适应性,满足不同应用场景对模型效率和效果的要求。高效训练算法:开…...

[react] 纯组件优化子
有组件如下,上面变化秒数, 下面是大量计算的子组件,上面每一秒钟变化一次,这时候子组件会不断重新渲染, 浪费资源 父组件如下 import React, { memo, useEffect, useMemo, useState } from react; import type { ReactNode, FC } from react; import HugeCount from ./Te; int…...

美观强大的文件保险库Chibisafe
简介 什么是 Chibisafe ? Chibisafe 是一款用 Typescript 编写的快速文件上传服务,非常实用。它接受文件、照片、文档以及您能想到的任何内容,并返回可共享的链接,供您发送给其他人。它易于使用、易于部署、免费且开源࿰…...

详细教程:SQL2008数据库备份与还原全流程!
数据的安全性至关重要,无论是操作系统、重要文件、磁盘存储,还是企业数据库,备份都是保障其安全和完整性的关键手段。拥有备份意味着即使发生误删、系统崩溃或病毒攻击等问题,也能迅速通过恢复功能解决,避免数据丢失带…...
HTML——49.header和footer标签
<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>header和footer标签</title></head><body><!--header和footer标签:是html5中新标签--><!--header:定义文档的页眉,通常用来定义可见…...

【蓝桥杯选拔赛真题87】python输出字符串 第十五届青少年组蓝桥杯python选拔赛真题 算法思维真题解析
目录 python输出字符串 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python输出字符串 第十五届蓝桥杯青少年组python比赛选拔赛真题详细解析…...
OpenStack-Dashboard界面简单修改
OpenStack Dashboard界面替换图片 一、dashboard界面Logo的路径及文件 dashboard的Logo存放(在Controller节点)的路径: /usr/share/openstack-dashboard/openstack_dashboard/static/dashboard/img/涉及需要修改的文件(3个&…...

DevOps工程技术价值流:Ansible自动化与Semaphore集成
在DevOps的浪潮中,自动化运维工具扮演着举足轻重的角色。Ansible,作为一款新兴的自动化运维工具,凭借其强大的功能和灵活性,在运维领域迅速崭露头角。本文将深入探讨Ansible的特点、架构、工作原理,以及其应用场景&…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
Spring Boot面试题精选汇总
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...

【堆垛策略】设计方法
堆垛策略的设计是积木堆叠系统的核心,直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法,涵盖基础规则、优化算法和容错机制: 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则: 大尺寸/重量积木在下…...