使用DMA传输实现单片机高效串口转发——以STM32系列为例

news2024/12/27 14:53:28

使用DMA传输实现单片机高效串口转发——以STM32系列为例

DateAuthorVersionNote
2023.08.06Dog TaoV1.01. 完成了文档的撰写。

文章目录

  • 使用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为例,间接转发的主要实现流程为:

  1. 初始化串口:初始化USART1和USART3,配置波特率、数据位、停止位、奇偶校验等。

  2. 配置USART1用于中断接收和DMA转发:启用USART1的接收中断功能,并配置相关NVIC。选择适当的DMA通道,关联USART1的发送功能。设置DMA源地址(例如缓冲区)和目标地址(USART3的数据发送寄存器)。配置DMA的大小、方向、优先级、模式等。

  3. 配置USART3用于中断接收和DMA转发:与USART1类似,配置USART3以使用中断进行接收,并选择适当的DMA通道用于发送。设置DMA源地址(例如缓冲区)和目标地址(USART1的数据发送寄存器)。配置DMA的大小、方向、优先级、模式等。

  4. 启用USART和DMA:启用USART1、USART3以及相关的DMA通道。

  5. 中断服务程序处理:在USART1的中断服务程序中,读取接收到的数据,并触发与USART3关联的DMA传输。在USART3的中断服务程序中,读取接收到的数据,并触发与USART1关联的DMA传输。

  6. 错误处理和同步:监视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-4
    DMA_InitTypeDef DMA_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);    //开启DMA时钟 
    DMA_DeInit(DMA2_Stream7);
    while(DMA_GetCmdStatus(DMA2_Stream7) != DISABLE){}   //等待stream可配置,即DMAy_SxCR.EN变为0
   
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;          //从8个channel中选择一个
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;            //外设地址
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff;      //存储器0地址,双缓存模式还要使用M1AR
    DMA_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变为0
   
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;          //从8个channel中选择一个
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART3->DR;            //外设地址
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff;      //存储器0地址,双缓存模式还要使用M1AR
    DMA_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 数据接收完成后,转发数据到串口1
                USART3_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报文事件
	}
}

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

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

相关文章

Java中String方法魔性学习

这里写目录标题 先进行专栏介绍String详解常用构造方法代码演示常用成员方法代码示例总结 先进行专栏介绍 本专栏是自己学Java的旅途&#xff0c;纯手敲的代码&#xff0c;自己跟着黑马课程学习的&#xff0c;并加入一些自己的理解&#xff0c;对代码和笔记 进行适当修改。希望…

【redis】SpringBoot集成redis

目录 1.添加redis依赖2.配置redis3.操作redis3.1 操作string 3.1 操作其它数据类型 4. Spring-Session基于Redis解决共享Session问题4.1 问题提出 4.1 添加依赖 4.2 修改配置4.3 存储和读取 1.添加redis依赖 方法①&#xff1a; <dependency><groupId>org.springf…

ChatGPT已闯入学术界,Elsevier推出AI工具

2022年11月&#xff0c;OpenAI公司发布了ChatGPT&#xff0c;这是迄今为止人工智能在现实世界中最重要的应用之一。 当前&#xff0c;互联网搜索引擎中出现了越来越多的人工智能&#xff08;AI&#xff09;聊天机器人&#xff0c;例如谷歌的Bard和微软的Bing&#xff0c;看起来…

微信小游戏流量主结算财务信息填写指引

微信小游戏个人开发者: 流量主结算财务信息填写指南 一,登录公众平台二,补充财务信息三,补充信息指引四,提交审核五,绑定通知对于微信小游戏个人开发者来说,流量主结算财务信息的填写是非常重要的一步。正确填写可以保证收入的及时结算,而填写不当则可能会导致收入无法到…

wxRibbonBar 常用三种控件Button,DropdownButton,HybridButton

这三种控件的效果如下所示&#xff1a; 点击下拉的效果&#xff1a; 这一部分可以设置wxITEM_CHECK&#xff0c;wxITEM_RADIO等效果 但我们可能更关注实现实例&#xff1a; &#xff08;1&#xff09;MyFrame.h #pragma once #include <wx/wx.h> #include "wx/wx…

vue2-diff算法

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

(学习笔记-进程管理)进程

进程 我们编写的代码只是一个存储在硬盘的静态文件&#xff0c;通过编译后会生成二进制可执行文件&#xff0c;当我们运行这个可执行文件后&#xff0c;它会被装载到内存中&#xff0c;接着CPU会执行程序中的每一条指令&#xff0c;那么这个运行中的程序就被称为进程。 现在我…

怎么加密文件夹才更安全?安全文件夹加密软件推荐

文件夹加密可以让其中数据更加安全&#xff0c;但并非所有加密方式都能够提高极高的安全强度。那么&#xff0c;怎么加密文件夹才更安全呢&#xff1f;下面我们就来了解一下那些安全的文件夹加密软件。 文件夹加密超级大师 如果要评选最安全的文件夹加密软件&#xff0c;那么文…

python GUI nicegui初识一(登录界面创建)

最近尝试了python的nicegui库&#xff0c;虽然可能也有一些不足&#xff0c;但个人感觉对于想要开发不过对ui设计感到很麻烦的人来说是很友好的了&#xff0c;毕竟nicegui可以利用TailwindCSS和Quasar进行ui开发&#xff0c;并且也支持定制自己的css样式。 这里记录一下自己利…

spring security + oauth2 使用RedisTokenStore 以json格式存储

1.项目架构 2.自己对 TokenStore 的 redis实现 package com.enterprise.auth.config;import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis…

leetcode刷题:1657. 确定两个字符串是否接近、1004. 最大连续1的个数 III

leetcode刷题:1657. 确定两个字符串是否接近、1004. 最大连续1的个数 III 1. 前言2. 1657. 确定两个字符串是否接近3. 1004. 最大连续1的个数 III4. 总结 1. 前言 上述两个题目位于leetcode75中&#xff0c;难度为中等&#xff0c;虽然对于大佬而言&#xff0c;可能很简单&…

2023年C++面试宝典

目录 第一章&#xff1a;C基础知识1.1 C语言起源与发展1.2 C的重要特点和优点1.3 C的数据类型和变量1.4 函数和命名空间1.5 运算符和表达式 第二章&#xff1a;面向对象编程2.1 类与对象的概念2.2 封装、继承和多态2.3 构造函数和析构函数2.4 静态成员和常量成员2.5 虚函数和纯…

maven install命令:将包安装在本地仓库,供本地的其它工程或者模块依赖

说明 有时候&#xff0c;自己本地的maven工程依赖于本地的其它工程&#xff0c;或者manven工程中的一个模块依赖于另外的模块&#xff0c;可以执行maven的install命令&#xff0c;将被依赖的包安装在maven本地仓库。 示例 一个工程包含几个模块&#xff0c;模块之间存在依赖…

【笔记】第94期-冯永吉-《湖仓集一体关键技术解读》-大数据百家讲坛-厦大数据库实验室主办20221022

https://www.bilibili.com/video/BV1714y1j7AU/?spm_id_from333.337.search-card.all.click&vd_sourcefa36a95b3c3fa4f32dd400f8cabddeaf

VSCode配置SSH远程免密登录服务器

VScode远程开发时&#xff0c;每次都需要输入密码&#xff0c;其实同理可以和其他应用类似配置免密登录&#xff0c;流程也类似。 1.在本地主机生成公钥和秘钥 ssh-keygen 2.将公钥内容添加至服务器 将生成钥对时会给出其保存路径&#xff0c;找到公钥&#xff0c;复制内容&am…

废弃的 电信光猫 改为 免费的wifi

修改为桥接模式即可。 1.修改电脑IP地址与光猫同一网段&#xff0c;例如192.168.1.2 掩码255.255.255.0。 电信光猫默认地址为 192.168.1.1 掩码为 255.255.255.0 2.网线或者无线连接光猫&#xff0c;无线wifi 名称&#xff08;SSID&#xff09;和密码 光猫背后都有。 3.浏…

事务到底是隔离的还是不隔离的 (具体)

遇到不明白的慢慢往后读&#xff0c;一下你就明白了。 下面是一个只有两行的表的初始化语句 mysql> CREATE TABLE t ( id int(11) NOT NULL, k int(11) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB; insert into t(id, k) values(1,1),(2,2); 这里&#xff0c;我们需要注…

ADS版图画封装联合仿真学习视频

ADS版图画封装 因为晶体管ATF54143在ADS中是没有封装的&#xff0c;所以要在ADS中画ATF54143的封装&#xff0c;操作步骤如下&#xff1a; 在ADS中新建layout&#xff0c;命名为ATF54143_layout&#xff0c; 根据datasheet知道封装的大小&#xff0c;进行绘制 在layout的con…

GO语言基础语法探究:简洁高效的编程之道

文章目录 前言Go词法单元token标识符关键字&#xff08; 25个 &#xff09;内置数据类型标识符&#xff08; 20个 &#xff09;内置函数&#xff08; 15个 &#xff09;常量值标识符&#xff08; 4个&#xff09;空白标识符&#xff08; 1个 &#xff09; 操作符和分隔符字面常…

通向架构师的道路之基于数据库的权限系统的设计

一、权限系统 这一天将讲述一个基本的基于数据库的权限管理系统的设计&#xff0c;在这一天的课程的最后将讲述“左右值无限分类实现算法”如何来优化“系统菜单”的结构而告终。今天的内容和前几天的基础框架是一样的它们都属于基础知识&#xff0c;在这些基础知识上还可以扩…