使用DMA传输实现单片机高效串口转发——以STM32系列为例
使用DMA传输实现单片机高效串口转发——以STM32系列为例
Date | Author | Version | Note |
---|---|---|---|
2023.08.06 | Dog Tao | V1.0 | 1. 完成了文档的撰写。 |
文章目录
- 使用DMA传输实现单片机高效串口转发——以STM32系列为例
- 应用场景
- 实现流程
- 源码示例
- 串口与中断配置
- DMA外设配置
- DMA发送数据函数
- 串口中断服务函数
- DMA中断服务函数
- Modbus协议代码
应用场景
在许多现实应用场景中,例如工业自动化控制、嵌入式通信设备等领域,单片机需要实时地从一个串口读取数据,并转发到另一个串口。如果使用常规的轮询或中断方法来完成这样的任务,会消耗大量的CPU资源,效率较低。此时如果采用DMA(直接存储器访问)进行串口数据转发则可以具备很多优势,例如降低数据转发延时、减轻CPU的运行负载、提高系统的实时性等。通过串口转发也可以实现多个不同通讯形式(例如无线传输与有线传输)、不同通讯协议(例如自定协议与Modbus协议)、不同通讯参数(例如两个设备分别具备不同波特率)的设备通讯中转。
直接存储器访问(DMA,Direct Memory Access)是一种允许外设或内存直接与其他外设或内存交换数据,而不需要通过CPU进行中介处理的技术。DMA可以有效提高整体系统效率,因为它允许数据传输的同时,CPU仍可以执行其他任务。STM32的DMA系统是一项强大的功能,允许高效的数据传输,同时减轻了CPU的负担。其灵活的配置选项和与多种外设的兼容性使其适用于许多应用,从简单的数据复制到复杂的外设管理。正确使用DMA可以显著提高STM32微控制器的性能和功能。
From STM32F103 datasheet:
The flexible 7-channel general-purpose DMA is able to manage memory-to-memory, peripheral-to-memory and memory-to-peripheral transfers. The DMA controller supports circular buffer management avoiding the generation of interrupts when the controller reaches the end of the buffer. Each channel is connected to dedicated hardware DMA requests, with support for software trigger on each channel. Configuration is made by software and transfer sizes between source and destination are independent.The DMA can be used with the main peripherals: SPI, I2C, USART, general-purpose and advanced-control timers TIMx and ADC.
From STM32F407 datasheet:
The devices feature two general-purpose dual-port DMAs (DMA1 and DMA2) with 8 streams each. They are able to manage memory-to-memory, peripheral-to-memory and memory-to-peripheral transfers. They feature dedicated FIFOs for APB/AHB peripherals, support burst transfer and are designed to provide the maximum peripheral bandwidth (AHB/APB). The two DMA controllers support circular buffer management, so that no specific code is needed when the controller reaches the end of the buffer. The two DMA controllers also have a double buffering feature, which automates the use and switching of two memory buffers without requiring any special code. Each stream is connected to dedicated hardware DMA requests, with support for software trigger on each stream. Configuration is made by software and transfer sizes between source and destination are independent. The DMA can be used with the main peripherals: SPI and I2S, I2C, USART, General-purpose, basic and advanced-control timers TIMx, DAC, SDIO, Camera interface (DCMI) and ADC.
实现流程
使用单片机实现串口转发可以分为两种主要的模式:直接转发模式与选择转发模式。直接转发模式是指单片机从一个串口中接收到的数据不经CPU的判断与处理,直接通过DMA传输从另个一串口发送出去。间接转发模式是指单片机从一个串口中接收到的数据需经过CPU的判断与处理,选择性的将部分数据或者修改后的数据通过DMA传输从另个一串口发送出去。
直接转发模式的核心实现过程为:对于接收数据的DMA通道,将串口的数据寄存器地址设置为源地址,并设置一个内存地址为目标地址。对于发送数据的DMA通道,将之前设置的内存地址设置为源地址,将另一个串口的数据寄存器地址设置为目标地址。
间接转发模式由于CPU的恰当介入而具备更好的灵活性与多场景的适应性,因此得到更为广泛的应用。以USART1与USART3为例,间接转发的主要实现流程为:
-
初始化串口:初始化USART1和USART3,配置波特率、数据位、停止位、奇偶校验等。
-
配置USART1用于中断接收和DMA转发:启用USART1的接收中断功能,并配置相关NVIC。选择适当的DMA通道,关联USART1的发送功能。设置DMA源地址(例如缓冲区)和目标地址(USART3的数据发送寄存器)。配置DMA的大小、方向、优先级、模式等。
-
配置USART3用于中断接收和DMA转发:与USART1类似,配置USART3以使用中断进行接收,并选择适当的DMA通道用于发送。设置DMA源地址(例如缓冲区)和目标地址(USART1的数据发送寄存器)。配置DMA的大小、方向、优先级、模式等。
-
启用USART和DMA:启用USART1、USART3以及相关的DMA通道。
-
中断服务程序处理:在USART1的中断服务程序中,读取接收到的数据,并触发与USART3关联的DMA传输。在USART3的中断服务程序中,读取接收到的数据,并触发与USART1关联的DMA传输。
-
错误处理和同步:监视DMA和USART的错误标志,并采取适当措施响应任何潜在问题。根据需要,实现缓冲区管理和同步机制,以确保数据的完整性和时序。
源码示例
以STM32F407的USART1与USART3双向互发为例,展示核心功能实现的源码。其中部分自定外设配置函数(例如USART_ConfigNVIC
, USART_ConfigPort
等)来自笔者自定的HAL库。
示例代码中,本机为Modbus-RTU从机设备,其USART1为Modbus-RTU/无线433MHz通讯口,USART3为RS485通讯口(485总线上连接多台Modbus-RTU从机设备)。单片机从USART1中接收到Modbus-RTU请求报文之后,会首先判断从机地址是否为本机,如果从机地址为本机地址,则进行正常的报文回复处理。如果从机地址不是本机地址,则通过USART3/485端口进行数据转发。接收到来自USART3/485端口上对应从机的回复后,再通过USART1/无线433MHz通讯端口将报文进一步封装后发送到主机。
通过此方法,可以实现一对一的无线通讯与一对多的Modbus/RS485的混合组网。其通讯系统示意图如下所示:
串口与中断配置
void NVIC_Config()
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);USART_ConfigNVIC(1, 0, 0);USART_ConfigNVIC(2, 0, 0);USART_ConfigNVIC(3, 0, 0);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
}void USART_Config()
{USART_ConfigPort(1, 115200, WordLength_8b, StopBits_1, Parity_No);USART_ConfigPort(2, 115200, WordLength_8b, StopBits_1, Parity_No);USART_ConfigPort(3, 115200, WordLength_8b, StopBits_1, Parity_No);// 使能串口发送完成中断// USART_ITConfig(USART1, USART_IT_TC, ENABLE);// USART_ITConfig(USART2, USART_IT_TC, ENABLE);// USART_ITConfig(USART3, USART_IT_TC, ENABLE);USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);USART_Config_DMA();// 初始化串口接收缓冲区USART_RevInitAll();
}
DMA外设配置
void USART_Config_DMA()
{//配置USART1_TX-Stream: DMA-2 Stream-7 Channel-4DMA_InitTypeDef DMA_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); //开启DMA时钟 DMA_DeInit(DMA2_Stream7);while(DMA_GetCmdStatus(DMA2_Stream7) != DISABLE){} //等待stream可配置,即DMAy_SxCR.EN变为0DMA_InitStructure.DMA_Channel = DMA_Channel_4; //从8个channel中选择一个DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; //外设地址DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff; //存储器0地址,双缓存模式还要使用M1ARDMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //存储器到外设模式DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; //数据传输量,以外设数据项为单位 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址保持不变DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址递增DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据位宽:8位DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据位宽:8位DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //普通模式(与循环模式对应)DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //中等优先级DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //禁止FIFO模式 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //单次传输DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //单次传输DMA_ITConfig(DMA2_Stream7, DMA_IT_TC, ENABLE);// DMA_ITConfig(DMA2_Stream7, DMA_IT_TE, ENABLE);DMA_Init(DMA2_Stream7, &DMA_InitStructure);//配置USART3_TX-Stream: DMA-1 Stream-3 Channel-4// DMA_InitTypeDef DMA_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); //开启DMA时钟 DMA_DeInit(DMA1_Stream3);while(DMA_GetCmdStatus(DMA1_Stream3) != DISABLE){} //等待stream可配置,即DMAy_SxCR.EN变为0DMA_InitStructure.DMA_Channel = DMA_Channel_4; //从8个channel中选择一个DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART3->DR; //外设地址DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff; //存储器0地址,双缓存模式还要使用M1ARDMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //存储器到外设模式DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; //数据传输量,以外设数据项为单位 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址保持不变DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址递增DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据位宽:8位DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据位宽:8位DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //普通模式(与循环模式对应)DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //中等优先级DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //禁止FIFO模式 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //单次传输DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //单次传输DMA_ITConfig(DMA1_Stream3, DMA_IT_TC, ENABLE);// DMA_ITConfig(DMA1_Stream3, DMA_IT_TE, ENABLE);DMA_Init(DMA1_Stream3, &DMA_InitStructure);
}
DMA发送数据函数
由于USART3是RS485协议传输,需要选择收发状态。本文源码中,通过RS485_CTRL_ADDR
的值实现收发转换。在发送数据前,先将RS485_CTRL_ADDR
置1。在DMA中断服务函数中(发送完成中断),将RS485_CTRL_ADDR
置0,恢复RS485的数据接收状态。
void USART1_DMA_SendData(uint8_t *tx_buffer,uint16_t length)
{// DMA_InitTypeDef DMA_InitStructure;DMA_Cmd(DMA2_Stream7, DISABLE); //关闭DMA通道DMA_SetCurrDataCounter(DMA2_Stream7, (uint16_t)length); //设置传输字节数DMA2_Stream7->CR |= (1 << 10); //发送DMA流的地址不自增DMA2_Stream7->M0AR = (uint32_t)tx_buffer; //设置接收和发送的内存地址DMA_Cmd(DMA2_Stream7, ENABLE); //打开DMA通道USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送// while( DMA_GetFlagStatus(DMA2_Stream7, DMA_FLAG_TCIF7) == RESET); //等待传输完成 // DMA_Cmd(DMA2_Stream7, DISABLE); //关闭DMA通道 // DMA_ClearFlag(DMA2_Stream7, DMA_FLAG_TCIF7); //清除DMA传输完成标志
}void USART3_DMA_SendData(uint8_t *tx_buffer,uint16_t length)
{// DMA_InitTypeDef DMA_InitStructure;DMA_Cmd(DMA1_Stream3, DISABLE); //关闭DMA通道DMA_SetCurrDataCounter(DMA1_Stream3, (uint16_t)length); //设置传输字节数DMA1_Stream3->CR |= (1 << 10); //发送DMA流的地址不自增DMA1_Stream3->M0AR = (uint32_t)tx_buffer; //设置接收和发送的内存地址DMA_Cmd(DMA1_Stream3, ENABLE); //打开DMA通道USART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送// while( DMA_GetFlagStatus(DMA1_Stream3, DMA_FLAG_TCIF3) == RESET); //等待传输完成 // DMA_Cmd(DMA1_Stream3, DISABLE); //关闭DMA通道 // DMA_ClearFlag(DMA1_Stream3, DMA_FLAG_TCIF7); //清除DMA传输完成标志
}void USART1_WSN32_SendData(uint8_t *tx_buffer, uint16_t length)
{static uint8_t data_temp[300];memcpy(data_temp, MB_CommParam.MB_PreTrans_Data, MB_CommParam.MB_PreTrans_Num);memcpy(data_temp + MB_CommParam.MB_PreTrans_Num, tx_buffer, length);USART1_DMA_SendData(data_temp, length + MB_CommParam.MB_PreTrans_Num);// USART_SendData(USART1, MB_CommParam.MB_PreTrans_Data, MB_CommParam.MB_PreTrans_Num);// USART_SendData(USART1, tx_buffer, length);
}void USART3_RS485_SendData(uint8_t *tx_buffer, uint16_t length)
{if(tx_buffer[0] == MB_CommParam.MB_SlaveAddr){// 本机地址,不发送return;}*RS485_CTRL_ADDR = 1;// delay_ms(1);USART3_DMA_SendData(tx_buffer, length);// 恢复RS485控制信号为接收状态的操作放到DMA发送完成中断中// vtaskDelay(100);// *RS485_CTRL_ADDR = 0;
}
串口中断服务函数
示例代码中,本机为Modbus-RTU从机设备,其USART1为Modbus-RTU/无线433MHz通讯串口,USART3为RS485通讯串口(485总线上连接多台Modbus-RTU从机设备)。因此,MB_CommParam.MB_PortNum
的值为1。
USART1: 在串口接收中断USART_IT_RXNE
的服务函数中调用Modbus-RTU协议的数据接收函数pxMBFrameCBByteReceived
。
USART3: 在串口接收中断USART_IT_RXNE
的服务函数中往FIFO队列缓冲中添加接收到的数据。在串口空闲中断USART_IT_IDLE
的服务函数中判断数据接收完成并实现数据转发的操作。
void USART1_IRQHandler(void)
{/*** 如果使能串口接收中断,那么ORE为1时也会产生中断。* 在应用中对ORE标志进行处理,当判断发生ORE中断的时候,* 我们再读一次USART_DR的值,* 这样如果没有新的Overrun 溢出事件发生的时候,ORE会被清除,* 然后程序就不会因为ORE未被清除而一直不断的进入串口中断*/if (USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET){USART_ReceiveByte(USART1);}if (MB_CommParam.MB_PortNum == 1){if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){pxMBFrameCBByteReceived();USART_ClearITPendingBit(USART1, USART_IT_RXNE);}else if (USART_GetITStatus(USART1, USART_IT_TC) != RESET){pxMBFrameCBTransmitterEmpty();USART_ClearITPendingBit(USART1, USART_IT_TC);}else if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET){// 串口接收完数据后空闲必须清除空闲标志位。// 通过读串口DR寄存器里的值来清除IDLE标志位,否则将一直触发空闲中断uint16_t data_temp = USART1->DR; // 先读取接收缓存中数据,清除空闲标志位data_temp = USART1->SR;}else{}}else{/* 省略无关代码 */}
}void USART3_IRQHandler(void)
{if (USART_GetFlagStatus(USART3, USART_FLAG_ORE) != RESET){USART_ReceiveByte(USART3);}if (MB_CommParam.MB_PortNum == 3){/* 省略无关代码 */}else{if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET){USART_ClearITPendingBit(USART3, USART_IT_RXNE);USART_WriteFIFO(2, USART_ReceiveByte(USART3)); // 将接收到的数据添加到FIFO缓冲区}else if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) // 串口空闲(数据接收完成)时,转发数据到USART1{uint16_t data_temp = USART3->DR; // 先读取接收缓存中数据,清除空闲标志位data_temp = USART3->SR;if (IsEnablePortForwarding != 0){// USART3 数据接收完成后,转发数据到串口1USART3_RevBuffer_Handler(USART1_WSN32_SendData);// USART3_RevBuffer_Handler(USART2_RS232_SendData);}}else if (USART_GetITStatus(USART3, USART_IT_TC) != RESET){USART_ClearITPendingBit(USART3, USART_IT_TC);// do something}else{}}
}
DMA中断服务函数
在DMA中断服务函数中(发送完成中断),将RS485_CTRL_ADDR
置0,恢复RS485的数据接收状态。
void DMA2_Stream7_IRQHandler(void) // USART-1-TX DMA
{// 判断是否为DMA发送完成中断if (DMA_GetFlagStatus(DMA2_Stream7, DMA_FLAG_TCIF7) == SET){DMA_Cmd(DMA2_Stream7, DISABLE); // 关闭DMA通道DMA_ClearFlag(DMA2_Stream7, DMA_FLAG_TCIF7);}
}void DMA1_Stream3_IRQHandler(void) // USART3-TX DMA
{// 判断是否为DMA发送完成中断if (DMA_GetFlagStatus(DMA1_Stream3, DMA_FLAG_TCIF3) == SET){DMA_Cmd(DMA1_Stream3, DISABLE); // 关闭DMA通道DMA_ClearFlag(DMA1_Stream3, DMA_FLAG_TCIF3);// delay_ms(1);delay_us(500);*RS485_CTRL_ADDR = 0; // 恢复RS485的数据接收状态}
}
Modbus协议代码
Modbus-RTU协议通过移植freemodbus库实现,笔者在此库中增加了报文接收的函数指针:
/// @brief When modbus-RTU ADU received, this function will be called.
extern RTU_ADU_ReceivedHandler_Type RTU_ADU_ReceivedHandler;
因此,可以设计一个回调函数USART3_RS485_SendData
(已在上文提供实现源码)注册给RTU_ADU_ReceivedHandler
指针,实现非本机地址的modbus请求指令通过USART3转发。
User_Init
函数首先通过读取两个拨码开关的值来判断当前设备的功能设定,如果处于无线通讯状态(主机与本机一对一)则使能串口转发功能。通过将不是本机地址的Modbus报文通过RS485总线发送出去,再将接收到的RS485回复数据通过无线通讯转发到主机,则可以实现多机通讯与无线/有线混合组网。
void User_Init()
{// 读取拨码开关的拨码值DevAddr_Val = Debug_GetDipSwitchValue(GPIO_Array_DevAddr, 4, 0);SigChan_Val = Debug_GetDipSwitchValue(GPIO_Array_SigChan, 4, 0);if ((DevAddr_Val != 0) && (SigChan_Val != 0)) // The current work mode is WSN32{IsEnablePortForwarding = 1;}else{IsEnablePortForwarding = 0;}// 初始化Modbus四种寄存器User_MB_InitRegs();if(IsEnablePortForwarding != 0){RTU_ADU_ReceivedHandler = USART3_RS485_SendData; // 注册回调函数,处理接收到Modbus报文事件}
}
相关文章:

使用DMA传输实现单片机高效串口转发——以STM32系列为例
使用DMA传输实现单片机高效串口转发——以STM32系列为例 DateAuthorVersionNote2023.08.06Dog TaoV1.01. 完成了文档的撰写。 文章目录 使用DMA传输实现单片机高效串口转发——以STM32系列为例应用场景实现流程源码示例串口与中断配置DMA外设配置DMA发送数据函数串口中断服务函…...

一文了解 Android Auto 车载开发~
作者:牛蛙点点申请出战 背景 我的的产品作为一个海外音乐播放器,在车载场景听歌是一个很普遍的需求。在用户反馈中,也有很多用户提到希望能在车上播放音乐。同时车载音乐也可以作为提升用户消费时长一个抓手。 出海产品,主要服务…...

Pixel4 安卓源码及内核修改编译教程 | 基于Android12 AOSP
之前整理了 Pixel4上的源码过程,下载的话大家可以去镜像网站下载,可以节约很多时间。 实验设备:Ubuntu18.04 32G2T Pixel4 文章目录 一、安卓源码下载1.准备下载环境(1)安装Python 3.9(2)安装g…...

如何做好Code Review
本文主要从我们为什么需要CR?CR面临哪些挑战?CR的最佳实践几个方面分析,希望可以给读者一些参考。 为什么需要CR? 代码质量 定性来看,大家都认可Code Review(后文简称CR)能显著改善代码质量&…...

Unity技术框架集合、Unity技术栈汇总
引擎技术尝试 [Animancer-Pro] (https://assetstore.unity.com/packages/tools/animation/animancer-pro-116514) (基于Playable的简单强大的动画解决方案)[ProBuilder/UModeler] (https://assetstore.unity.com/packages/tools/modeling/umodeler-80868) (快速关卡原型构建…...

安卓SDK开发的一些疑问
目前,公司需要开发一套iOS和安卓的sdk,主要包含蓝牙管理、网络请求、倒计时等方案执行、蓝牙数据交互等功能。之前没有过开发安卓sdk的经历,写个笔记用以记录。 现在iOS sdk已经写了一部分,安卓开发我也习惯从iOS的角度类比来开发…...

【基础类】—三栏页面布局的方案和优缺点
一、假设高度已知,中间宽度自适应,三栏(列)布局的方案有哪些? float浮动、absolute绝对定位、flex弹性盒子、table表格布局、grid网格布局 浮动 float <style>* {margin: 0;padding: 0;}.container {width: 1…...

OPENCV C++(四)形态学操作+连通域统计
形态学操作 先得到一个卷积核 Mat kernel getStructuringElement(MORPH_RECT,Size(5,5)); 第一个是形状 第二个是卷积核大小 依次为腐蚀 膨胀 开运算 闭运算 Mat erodemat,dilatemat,openmat,closemat;morphologyEx(result1, erodemat, MORPH_ERODE, kernel);morphologyEx…...

tomcat上部署jpress
一.确保有jdk,tomcat和mysql环境 二.新建jpress数据库,新建jpress用户并赋予所有权限 三.将jpress的war上传到tomcat/apache-tomcat-8.5.70/webapps,具体根据你的实际tomcat安装路径为准,上传完成后他会自己解包 四.到浏览器完…...

篇十:外观模式:简化复杂系统
篇十:“外观模式:简化复杂系统” 开始本篇文章之前先推荐一个好用的学习工具,AIRIght,借助于AI助手工具,学习事半功倍。欢迎访问:http://airight.fun/。 另外有2本不错的关于设计模式的资料,分…...

linux gcc __attribute__
__attribute__ 1. 函数属性1.1 __attribute__((noreturn))1.2 __attribute__((format))1.3 __attribute__((const)) 2. 变量属性2.1. __attribute__((aligned))2.2. __attribute__((packed)) 3. 类型属性 __attribute__ 是 GCC 编译器提供的一种特殊语法,它可以用于…...

【SpringCloud】RabbitMQ基础
1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式: 同步通讯:就像打电话,需要实时响应。 异步通讯:就像发邮件,不需要马上回复。 两种方式各有优劣,打电话可以立即得到响应,…...

css, resize 拖拉宽度
效果如下: 可直接复制预览查看属性值: 关键样式属性: resize: horizontal; overflow-x: auto; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content…...

Python识别抖音Tiktok、巨量引擎滑块验证码识别
由于最近比较忙,所以本周搞了一个相对简单的验证码,就是抖音Tiktok的滑块验证码,这也是接到客户的一个需求。这种验证码通常在电脑端登录抖音、巨量引擎的的时候出现。 首先看一下最终的效果: 验证码识别过程 1、利用爬虫采集图…...

EvilBox One靶场笔记
EvilBox: One靶场笔记 信息收集 先fscan找主机192.168.1.102 namp扫端口 开放80,22端口 然后扫目录 └─$ gobuster dir -r -u http://192.168.1.102/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,txt,bak,html在扫secret目录,找…...

shell脚本中的export无效
写了一段shell脚本: #!/bin/bash source Tools/simulation/gazebo-classic/setup_gazebo.bash $(pwd) $(pwd)/build/px4_sitl_default export ROS_PACKAGE_PATH$ROS_PACKAGE_PATH:$(pwd) export ROS_PACKAGE_PATH$ROS_PACKAGE_PATH:$(pwd)/Tools/simulation/gazebo…...

前沿分享-鱼形机器人
可能并不太前沿了,是21年底的新闻了,但是看见了就顺便发一下吧。 大概就是,通过在pH响应型水凝胶中编码不同的膨胀速率而构建了一种环境适应型变形微机器人,让微型机器人直接向癌细胞输送药物从而减轻药物带来副作用。 技术原理是,…...

摄像机终端IP地址白名单配置流程
海康摄像头配置白名单流程 1.登录海康摄像机前端 2.进入配置-系统-安全管理-IP地址过滤 3.IP地址过滤方式选择“允许” 4.点击添加按钮输入对应的IP地址或者IP网段 5.最后勾选启用IP地址过滤,然后保存 大华摄像头配置白名单流程 1.登录大华摄像机前端 2.进入设…...

Glibc—查看版本
方式1:直接查看ldd版本 ldd --versionldd (Buildroot) 2.30 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICU…...

C++物理引擎Box2D的下载,编译,VS2013配置环境
文章目录 网站和下载地址编译工具:编译box2dhelloworld测试网站和下载地址 https://box2d.org/ 下载地址 https://hub.nuaa.cf/erincatto/box2d/tags 编译工具: 1.VS2013 2.cmake 下载地址 https://cmake.org/ 编译box2d 下载box2d源码2.4.0,解压。在box2d-2.4.0目录下…...

STL容器详解——map容器
一、map容器介绍 作为关联式容器的一种,map 容器存储的都是 pair 对象,也就是用 pair 类模板创建的键值对。其中,各个键值对的键和值可以是任意数据类型,包括 C 基本数据类型(int、double 等)、使用结构体…...

VR全景在建筑工程行业能起到哪些作用?
在建筑工程领域,数字化技术为行业的发展起到巨大的推动作用,虽然建筑施工行业主要是依赖于工人劳动力和施工设备,但是VR全景在该行业中方方面面都能应用,从设计建模到项目交付,帮助建筑师以及项目方更好的理解每个环节…...

P1257 平面上的最接近点对
题目 思路 详见加强加强版 代码 #include<bits/stdc.h> using namespace std; #define int long long const int maxn4e510; pair<int,int> a[maxn]; int n; double d1e16; pair<int,int> vl[maxn],vr[maxn]; void read() { cin>>n;for(int i1;i<…...

8月1日上课内容 第一章web基础与http协议
dns与域名 网络是基于tcp/ip协议进行通信和连接的 应用层--传输层---网络层----数据链路层-----物理层 ip地址,我们每一台主机都有一个唯一的地址标识(固定的ip地址),区分用户和计算机通信。 ip地址:32位二进制数组成的,不方便记忆 192.168.…...

Gson 添加数据默认值问题记录
问题:在用Gson add(key(string类型),value(必须是JsonElement子类))时发现,value 传了 "" 空字符串(非null),默认解析后返回null&#…...

利用Arthas+APM监控进行Java性能深度定位
大家可能都用过APM监控,包括开源的Skywalking、商用的卓豪(ZOHO)ManageEngine APM应用性能监控、以及云监控产品如听云(Server监控),这些APM监控产品大大方便了我们实时监控应用性能,并实现性能…...

【BASH】回顾与知识点梳理(十一)
【BASH】回顾与知识点梳理 十一 十一. 八至十章知识点总结及练习11.1 总结11.2 练习情境模拟题一:透过 grep 搜寻特殊字符串,并配合数据流重导向来处理大量的文件搜寻问题。情境模拟题二:使用管线命令配合正规表示法建立新指令与新变量。 该系…...

vue2-diff算法
1、diff算法是什么? diff算法是一种通过同层的树节点进行比较的高效算法。 其有两个特点: 比较只会在同层级进行,不会跨层级进行。 在diff比较的过程中,循环从两边向中间比较。 diff算法在很多场景中都有应用,在vue中&…...

SpringBoot使用redis作为缓存的实例
目录 什么是缓存? 缓存的作用? 缓存的成本? 实际项目中的应用 代码展示 什么是缓存? 缓存就是数据交换的缓冲区(称作Cache [ kʃ ] ),是存贮数据的临时地方,一般读写性能较高。 缓…...

vue3使用vue3-seamless-scroll插件
1、局部引入 import vueSeamlessScroll from "vue-seamless-scroll"; 2、注册 components: { vueSeamlessScroll, }, 3、使用 <vue3-seamless-scroll :list"list1" class"scroll" step"0.2"><div class"item"…...