【2】GD32H7xx 串口Idle + DMA接收不定长数据

news2024/11/14 11:58:06

目录

  • 1. IDLE中断相关介绍
  • 2. D-Cache与DMA同时使用
    • 2.1 I-Cache与D-Cache
    • 2.2 D-Cache与DMA同时使用时的数据一致性问题
      • 2.2.1 CPU读取DMA写入到SRAM的数据
      • 2.2.2 DMA读取CPU写入到SRAM的数据
  • 3. Uart Idle + DMA收发程序
  • 4. 程序测试

1. IDLE中断相关介绍

在 GD32H7xx MCU 中,串口的 Idle 中断检测机制如下:
  首先,当有数据开始被接收时,串口的接收数据寄存器非空(RXNE 位被置位),此时开始启动对空闲状态的检测。从检测到有数据接收这一刻起,串口会持续监测数据总线。如果在接下来一个字节对应的传输周期内,总线上都没有新的数据被接收,串口的硬件逻辑就会将空闲中断标志位(IDLE 位)置 1,从而触发空闲中断。
  需要注意的是,空闲中断标志位(IDLE 位)并非在空闲期间一直保持置 1 的状态。另外,空闲中断标志位(IDLE 位)不会被硬件自动清零。在系统响应中断后,需要在中断服务程序中通过软件手动将其清零,以便为下一次的空闲检测做好准备。
这样的机制能够准确且高效地检测到串口数据接收的空闲状态,尤其适用于处理不定长的数据接收,为系统提供了及时处理接收数据的时机。

  串口Idle + DMA 收发数据的优势:

  • 高效性:DMA 可以在后台自动完成数据从串口缓冲区到内存的传输,无需 CPU 频繁参与,释放 CPU 资源用于其他重要任务。
  • 实时性:通过 Idle 中断能及时检测到一帧数据接收完毕,迅速进行处理,减少数据处理的延迟。
  • 降低 CPU 负载:CPU 不必一直监控串口状态和进行数据搬移,专注于核心业务逻辑。
  • 处理不定长数据:由于能在 Idle 中断时确定一帧数据结束,所以适合接收长度不固定的数据,提高了系统对不同数据格式的适应性。
  • 稳定性:减少了由于 CPU 繁忙导致的数据丢失或处理不及时的风险。

2. D-Cache与DMA同时使用

2.1 I-Cache与D-Cache

  GD32H7 MCU 中的 I-Cache(Instruction Cache,指令缓存)和 D-Cache(Data Cache,数据缓存)具有以下特点和作用:

  • I-Cache(指令缓存):
    • 加速指令获取:存储最近使用的指令,减少从较慢的主存储器(如闪存)中获取指令的次数,从而提高 CPU 取指的速度,提升程序执行效率。
    • 提高系统性能:有助于减少指令访问的延迟,使 CPU 能够更快地获取下一条要执行的指令,尤其在处理复杂和大型程序时效果显著。
  • D-Cache(数据缓存):
    • 数据暂存:用于缓存经常访问的数据,避免频繁从主存读取数据,加快数据访问速度。
    • 优化数据读写:提高数据读写的局部性,减少对主存的访问,降低数据访问的功耗和延迟。

  当CPU读取指令或数据时,CPU首先将访问地址发送到I-Cache或D-Cache进行比对,如果比对匹配,则说明数据在I-Cache或D-Cache中,然后CPU直接从I-Cache或D-Cache中取出数据并继续执行指令或访问数据。如果比对不匹配,则说明数据不在I-Cache或D-Cache中,此时CPU就会从主存储器中读取数据。GD32H7xx的I-Cache和D-Cache大小都是32KB。
  这两种缓存的存在有助于提高 GD32H7 MCU 的整体性能,使其能够更高效地处理各种任务,特别是在对实时性和性能要求较高的应用场景中具有重要意义。然而,在某些特殊应用中,可能需要对缓存进行精细的管理和控制,以确保数据的一致性和准确性。

2.2 D-Cache与DMA同时使用时的数据一致性问题

  GD32H7xx 在Cache未命中时将为数据分配高速缓存行,将32字节数据从主存储器存放至Cache,后续访问这些主存储器地址时,Cache会被命中,直接从Cache中读取数据。当SRAM上启用了Cache时,CPU和DMA同时访问SRAM会存在一致性问题。

2.2.1 CPU读取DMA写入到SRAM的数据

  当DMA从外设读取数据并更新到SRAM中的接收缓冲区recv_buf[ ]时,此时CPU尝试读取recv_buf[ ],读取到的是Cache中缓存的数据,而不是SRAM中的新数据。
解决方式:
1.在DMA接收数据完成之后,对Cache中的recv_buf[ ]进行无效操作。执行Cache无效操作后使得Cache中的recv_buf[ ]无效,CPU在尝试读取recv_buf[ ]时,Cache不会命中。

// destination : DMA接收buffer
// RECV_BUF_SIZE: DMA接收buffer size
SCB_InvalidateDCache_by_Addr ((uint32_t *)recv_buf, RECV_BUF_SIZE); 

2.为保证Cache line 边界对齐,recv_buf[ ]的大小必须是32字节对齐

__attribute__ ((aligned(32))) uint8_t recv_buf[RECV_BUF_SIZE];

2.2.2 DMA读取CPU写入到SRAM的数据

  当CPU在更新传输缓冲区send_buf[ ]中要待传输数据时,将仅更新Cache中数据而不会更新SRAM中的数据,当DMA读取send_buf[ ]中数据时,会从SRAM中读取,而不是CPU更新的Cache中的新数据。
解决方式:
1.在启动DMA传输之前,执行Cache清理操作,将Cache中send_buf[ ]刷新到SRAM中。

SCB_CleanDCache_by_Addr((uint32_t*)send_buf, SEND_BUF_SIZE); // cache 清理

2.send_buf[ ]的大小必须是32字节对齐

__attribute__ ((aligned(32))) uint8_t send_buf[SEND_BUF_SIZE];

3. Uart Idle + DMA收发程序

#define UART7_RDATA_ADDRESS      (&USART_RDATA(UART7))
#define UART7_TDATA_ADDRESS      (&USART_TDATA(UART7))
#define ARRAYNUM(arr_nanme)       (uint32_t)(sizeof(arr_nanme) / sizeof(*(arr_nanme)))

#define UART7_DMA_TX_BUF_LEN	64
#define UART7_DMA_RX_BUF_LEN	128

__attribute__ ((aligned(32))) uint8_t txbuffer[UART7_DMA_TX_BUF_LEN];
__attribute__ ((aligned(32))) uint8_t rxbuffer[UART7_DMA_RX_BUF_LEN];


osSemaphoreId_t uart7_rx_sem;	
osSemaphoreId_t uart7_tx_sem;	
uint8_t g_uart7_rx_len;

// 信号量属性
const osSemaphoreAttr_t uart7_rx_sem_attr = 
{
	.name = "uart7_rx_sem",
};

// 信号量属性
const osSemaphoreAttr_t uart7_tx_sem_attr = 
{
	.name = "uart7_tx_sem",
};

/*
    \brief      enable uart7 rx
    \param[in]  none
    \retval		none
*/
static void uart7_rx_en(void)
{
	dma_single_data_parameter_struct dma_init_struct;
	dma_deinit(DMA0, DMA_CH1);
	dma_single_data_para_struct_init(&dma_init_struct);
	dma_init_struct.request 	 = DMA_REQUEST_UART7_RX;
	dma_init_struct.direction	 = DMA_PERIPH_TO_MEMORY;
	dma_init_struct.memory0_addr  = (uint32_t)rxbuffer;
	dma_init_struct.memory_inc	 = DMA_MEMORY_INCREASE_ENABLE;
	dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
	dma_init_struct.number		 = UART7_DMA_RX_BUF_LEN;
	dma_init_struct.periph_addr  = (uint32_t)UART7_RDATA_ADDRESS;
	dma_init_struct.periph_inc	 = DMA_PERIPH_INCREASE_DISABLE;
	dma_init_struct.priority	 = DMA_PRIORITY_ULTRA_HIGH;
	dma_single_data_mode_init(DMA0, DMA_CH1, &dma_init_struct);

	dma_circulation_disable(DMA0, DMA_CH1);
	usart_dma_receive_config(UART7, USART_RECEIVE_DMA_ENABLE);
	dma_interrupt_enable(DMA0, DMA_CH1, DMA_INT_FTF);
	dma_channel_enable(DMA0, DMA_CH1);
	
}

/*
    \brief      disable uart7 rx
    \param[in]  none
    \retval		none
*/
static void uart7_rx_dis(void)
{
	dma_channel_disable(DMA0,DMA_CH1);
}



/*
    \brief      DMA0 Channel0 IRQ Handle
    \param[in]  none
    \retval		none
*/
void DMA0_Channel0_IRQHandler(void)
{
    if (RESET != dma_interrupt_flag_get(DMA0, DMA_CH0, DMA_INT_FLAG_FTF)) {
        dma_interrupt_flag_clear(DMA0, DMA_CH0, DMA_INT_FLAG_FTF);

		dma_channel_disable(DMA0,DMA_CH0);	
		//LogI("DMA0_Channel0_IRQHandler tx finish\r\n");
    }
}

/*
    \brief      DMA0 Channel1 IRQ Handle
    \param[in]  none
    \retval		none
    本例子中没有用到这个中断
*/
void DMA0_Channel1_IRQHandler(void)
{
	//LogI("DMA0_Channel1_IRQHandler\r\n");
    if (RESET != dma_interrupt_flag_get(DMA0, DMA_CH1, DMA_INT_FLAG_FTF)) {
        dma_interrupt_flag_clear(DMA0, DMA_CH1, DMA_INT_FLAG_FTF);

        //memcpy(txbuffer, rxbuffer, 32);
        //usart_transmit_dma(txbuffer, 32);

/*
        dma_single_data_parameter_struct dma_init_struct;
        dma_deinit(DMA0, DMA_CH1);
        dma_single_data_para_struct_init(&dma_init_struct);
        dma_init_struct.request      = DMA_REQUEST_UART7_RX;
        dma_init_struct.direction    = DMA_PERIPH_TO_MEMORY;
        dma_init_struct.memory0_addr  = (uint32_t)rxbuffer;
        dma_init_struct.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;
        dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
        dma_init_struct.number       = 32;
        dma_init_struct.periph_addr  = (uint32_t)(&USART_RDATA(UART7));
        dma_init_struct.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;
        dma_init_struct.priority     = DMA_PRIORITY_ULTRA_HIGH;
        dma_single_data_mode_init(DMA0, DMA_CH1, &dma_init_struct);

        dma_circulation_disable(DMA0, DMA_CH1);
        usart_dma_receive_config(UART7, USART_RECEIVE_DMA_ENABLE);
        dma_interrupt_enable(DMA0, DMA_CH1, DMA_INT_FTF);
        dma_channel_enable(DMA0, DMA_CH1);
 */
    }
}

/*
    \brief      UART7 IRQ Handle
    \param[in]  none
    \retval		none
*/
void UART7_IRQHandler()
{
	uart7_rx_dis();
	
    if (RESET != usart_interrupt_flag_get(UART7, USART_INT_FLAG_IDLE)) {
        usart_interrupt_flag_clear(UART7, USART_INT_FLAG_IDLE);

		LogI("USART_INT_FLAG_IDLE\r\n");

  		uint16_t remian_len, len;
		remian_len = dma_transfer_number_get(DMA0,DMA_CH1);
	    len = UART7_DMA_RX_BUF_LEN - remian_len;  //本次收到的数据长度

		LogI("recv len = %d ; remian_len = %d\r\n", len, remian_len);
		SCB_InvalidateDCache_by_Addr ((uint32_t *)rxbuffer, UART7_DMA_RX_BUF_LEN); // 对cache中rxbuffer无效

		if(len > 0)
		{
			g_uart7_rx_len = len;
			osSemaphoreRelease(uart7_rx_sem);
		}
    }
	
	uart7_rx_en();
}

void uart7_init(void)
{
    rcu_periph_clock_enable(RCU_GPIOE);
    rcu_periph_clock_enable(RCU_UART7);

    nvic_irq_enable(UART7_IRQn, 2, 0);

    gpio_af_set(GPIOE, GPIO_AF_8, GPIO_PIN_0);
    gpio_af_set(GPIOE, GPIO_AF_8, GPIO_PIN_1);

    gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_0);
    gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_0);

    gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_1);
    gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_100_220MHZ, GPIO_PIN_1);

    usart_deinit(UART7);
    usart_word_length_set(UART7, USART_WL_8BIT);
    usart_stop_bit_set(UART7, USART_STB_1BIT);
    usart_parity_config(UART7, USART_PM_NONE);
    usart_baudrate_set(UART7, 115200U);

	usart_flag_clear(UART7, USART_FLAG_IDLE);
    usart_transmit_config(UART7, USART_TRANSMIT_ENABLE);
    usart_receive_config(UART7, USART_RECEIVE_ENABLE);

    usart_interrupt_enable(UART7, USART_INT_IDLE);
    usart_enable(UART7);
}



/*
    \brief      uart7_dma_send
    \param[in]  buf: send data
	\param[in]	len: data len
    \retval		none
*/
void uart7_dma_send(char* buff, int size)
{
    memcpy(txbuffer, buff, size);
	SCB_CleanDCache_by_Addr((uint32_t*)txbuffer, size); // cache 清理
    dma_memory_address_config(DMA0, DMA_CH0, DMA_MEMORY_0, (uint32_t)txbuffer);
    dma_transfer_number_config(DMA0, DMA_CH0, size);
	LogI("uart7_dma tx len:%d\r\n", size);
    dma_channel_enable(DMA0, DMA_CH0);
}



void usart_transmit(char* buff, int size)
{
    for (int i = 0; i < size; ++i) {
        usart_data_transmit(UART7, buff[i]);
        while (RESET == usart_flag_get(UART7, USART_FLAG_TBE)) {}
    }
}



/*
    \brief      uart7 dma config
    \param[in]  none
	\param[in]	none
    \retval		none
*/
void uart7_dma_config(void)
{
    rcu_periph_clock_enable(RCU_DMA0);
    rcu_periph_clock_enable(RCU_DMAMUX);
    nvic_irq_enable(DMA0_Channel0_IRQn, 2, 0);
    //nvic_irq_enable(DMA0_Channel1_IRQn, 2, 1);

    dma_single_data_parameter_struct dma_init_struct;

	SCB_CleanDCache_by_Addr((uint32_t*)txbuffer, UART7_DMA_TX_BUF_LEN);

    //TX
    dma_deinit(DMA0, DMA_CH0);
    dma_single_data_para_struct_init(&dma_init_struct);
    dma_init_struct.request      = DMA_REQUEST_UART7_TX;
    dma_init_struct.direction    = DMA_MEMORY_TO_PERIPH;
    dma_init_struct.memory0_addr  = (uint32_t)txbuffer;
    dma_init_struct.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;
    dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
    dma_init_struct.periph_addr  = (uint32_t)UART7_TDATA_ADDRESS;
    dma_init_struct.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;
    dma_init_struct.priority     = DMA_PRIORITY_MEDIUM;
    dma_single_data_mode_init(DMA0, DMA_CH0, &dma_init_struct);

    dma_circulation_disable(DMA0, DMA_CH0);
    usart_dma_transmit_config(UART7, USART_TRANSMIT_DMA_ENABLE);
    dma_interrupt_enable(DMA0, DMA_CH0, DMA_INT_FTF);
	//dma_channel_enable(DMA0, DMA_CH0);

    //RX
    dma_deinit(DMA0, DMA_CH1);
    dma_single_data_para_struct_init(&dma_init_struct);
    dma_init_struct.request      = DMA_REQUEST_UART7_RX;
    dma_init_struct.direction    = DMA_PERIPH_TO_MEMORY;
    dma_init_struct.memory0_addr  = (uint32_t)rxbuffer;
    dma_init_struct.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;
    dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
    dma_init_struct.number       = UART7_DMA_RX_BUF_LEN;
    dma_init_struct.periph_addr  = (uint32_t)UART7_RDATA_ADDRESS;
    dma_init_struct.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;
    dma_init_struct.priority     = DMA_PRIORITY_ULTRA_HIGH;
    dma_single_data_mode_init(DMA0, DMA_CH1, &dma_init_struct);

    dma_circulation_disable(DMA0, DMA_CH1);
    usart_dma_receive_config(UART7, USART_RECEIVE_DMA_ENABLE);
 	//dma_interrupt_enable(DMA0, DMA_CH1, DMA_INT_FTF);
    dma_channel_enable(DMA0, DMA_CH1);
}


/*
    \brief      uart7_idle_dma_test_thread
    \param[in]  none
	\param[in]	none
    \retval		none
*/
void uart7_idle_dma_test_thread(void)
{
	LogI("uart7_idle_dma_test_thread run...\r\n");
	uint32_t ret;
	static uint16_t pollcnt = 0;

	uart7_rx_sem = osSemaphoreNew(2, 0, &uart7_rx_sem_attr);	
	
	uart7_init();
	uart7_dma_config();

	while(1)
	{
		ret = osSemaphoreAcquire(uart7_rx_sem, 100);   //等待接收数据
		if(ret == osErrorTimeout)                          //超时
		{
			//LogI("Acquire uart7_rx_sem timeout\r\n");
		}
		else
		{
			LogI("uart7 recv data:");
			uint8_t i;
			for(i=0; i < g_uart7_rx_len; i++)
			{
				printf("[%d]=%d,", i, rxbuffer[i]);
			}
			printf("\r\n");

			uart7_dma_send((char *)rxbuffer, g_uart7_rx_len);
		}
	}

}

4. 程序测试

程序功能:通过IDLE中断和串口的DMA收发功能实现将收到的数据再发出去。
经过测试程序功能正常。
在这里插入图片描述


To Be Continue …

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

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

相关文章

证书学习(六)TSA 时间戳服务器原理 + 7 个免费时间戳服务器地址

目录 一、简介1.1 什么是时间戳服务器1.2 名词扩展1.3 用时间戳标记顺序1.4 7 个免费TSA时间戳服务器地址(亲测可用)1.5 RFC 3161 标准二、时间戳原理2.1 时间戳服务工作流程2.2 验证工作流程2.3 举个例子2.4 时间戳原理总结三、代码实现3.1 curl 命令请求时间戳3.2 java 代码…

一步一步从asp.net core mvc中访问asp.net core WebApi

"从asp.net core mvc中访问asp.net core WebApi"看到这个标题是不是觉得很绕口啊&#xff0c;但的确就是要讲一讲这样的访问。前面我们介绍了微信小程序访问asp.net core webapi(感兴趣的童鞋可以看看前面的博文有关WEBAPI的搭建)&#xff0c;这里我们重点不关心如何…

信捷 XD PLC C语言 FB和FC 不同

信捷 XD PLC 的C语言下 FB和FC 的使用&#xff0c;如果你有困惑&#xff0c;本文可能会帮到你&#xff01; 调用FB要带后缀_Body的&#xff0c;这个地方很容易忽视和出错。 不同之处FBFC可以在全局变量表中建立此类型对象可以1个&#xff0c;也可以多个不可以参数类型及数量有…

「Mac畅玩鸿蒙与硬件28」UI互动应用篇5 - 滑动选择器实现

本篇将带你实现一个滑动选择器应用&#xff0c;用户可以通过滑动条选择不同的数值&#xff0c;并实时查看选定的值和提示。这是一个学习如何使用 Slider 组件、状态管理和动态文本更新的良好实践。 关键词 UI互动应用Slider 组件状态管理动态数值更新用户交互 一、功能说明 在…

共享汽车管理:SpringBoot框架的高效实现

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了共享汽车管理系统的开发全过程。通过分析共享汽车管理系统管理的不足&#xff0c;创建了一个计算机管理共享汽车管理系统的方案。文章介绍了共享汽车管理系统的系…

艾体宝产品丨加速开发!Redis Copilot智能助手上线

我们最近发布了 Redis Copilot&#xff0c;旨在帮助开发者更加高效地使用 Redis 构建应用。提升应用性能&#xff0c;简化构建过程是我们不懈的追求。Redis Copilot 正是为此而生的人工智能助手&#xff0c;助力开发者迅速掌握 Redis 的使用技巧。现在您可以在 Redis Insight 中…

阿里云centos7.9服务器磁盘挂载,切换服务路径

项目背景 1、项目使用的服务器为阿里云centos7.9&#xff0c;默认的磁盘为vda&#xff0c;文件系统挂载在这个磁盘上&#xff0c;项目上使用的文件夹为/home/hnst/uploadPath 2、vda使用率已达到91% 3、现购置一块新的磁盘为vdb&#xff0c;大小为2T 目的 切换服务所使用的…

Electron + Vue3 开发桌面应用+附源码

什么是 Electron&#xff1f; Electron 是一个使用 JavaScript、HTML 和 CSS 构建跨平台桌面应用程序的框架。它由 GitHub 开发并维护&#xff0c;允许开发者使用现代 Web 技术创建原生应用程序。Electron 结合了 Chromium 渲染引擎和 Node.js 运行时环境&#xff0c;使得开发…

【Leecode】Leecode刷题之路第44天之通配符匹配

题目出处 44-通配符匹配-题目出处 题目描述 个人解法 思路&#xff1a; todo代码示例&#xff1a;&#xff08;Java&#xff09; todo复杂度分析 todo官方解法 44-通配符匹配-官方解法 前言 本题与10. 正则表达式匹配非常类似&#xff0c;但相比较而言&#xff0c;本题稍…

单元/集成测试解决方案

在项目开发的前期针对软件单元/模块功能开展单元/集成测试&#xff0c;可以尽早地发现软件Bug&#xff0c;避免将Bug带入系统测试阶段&#xff0c;有效地降低HIL测试的测试周期&#xff0c;也能有效降低开发成本。单元/集成测试旨在证明被测软件实现其单元/架构设计规范、证明被…

Linux案例:DNS服务器配置

Linux案例&#xff1a;DNS服务器配置 实验一&#xff1a;正向解析 服务端配置&#xff1a; [rootserver ~]# setenforce 0 [rootserver ~]# nmcli c modify ens160 ipv4.method manual ipv4.addresses 192.168.70.131/24 ipv4.gateway 192.168.70.2 ipv4.dns 114.114.114.11…

Linux常用的100个命令

掌握常用的Linux常用命令是作为码农的基本素养&#xff0c;无论你从事软件开发或者运维的的哪个细分领域。本文言简意赅&#xff0c;可作为指导书收藏。 Linux常用命令的分类&#xff1a; 基本文件操作权限与用户管理文件搜索与系统状态网络管理压缩与打包系统管理与维护磁盘与…

数据挖掘全景:从基础理论到经典算法的深度探索

1 绪论--1.1 数据挖掘的概念和任务 1. (单选题)目前数据分析与挖掘领域的现实情况描述不正确的是&#xff08;&#xff09; A. 信息爆炸 B. 数据爆炸 C. 信息贫瘠 D.数据收集能力远远超过人们的分析和理解能力 2. (单选题)你认为下面哪种数据对于数据挖掘算法来说最简单最…

Qt Udp的组播(多播)、广播和单播

UDP通讯的基本概念和特点‌ UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是‌TCP/IP协议族中的一种无连接协议&#xff0c;主要用于那些对实时性要求较高而可靠性要求较低的应用场景。UDP的主要特点包括&#xff1a; ‌无连接‌&#xff1a;…

CSS3中动画的使用animation

1.基本使用 2.其他属性 3.复合属性

前端实现json动画(附带示例)

前端实现json动画&#xff08;附带示例&#xff09; 使用lottie制作动画。1.json动画2.实现效果3.git仓库4.运行5.json动画天堂6.代码7. 经常使用的方法 使用lottie制作动画。 1.json动画 废话不多说&#xff0c;直接看效果图2.实现效果 3.git仓库 https://gitee.com/chaiach…

Ubuntu实现双击图标运行自己的应用软件

我们知道在Ubuntu上编写程序&#xff0c;最后编译得到的是一个可执行文件&#xff0c;大致如下 然后要运行的时候在终端里输入./hello即可 但是这样的话感觉很丑很不方便&#xff0c;下边描述一种可以类似Windows上那种双击运行的实现方式。 我们知道Ubuntu是有一些自带的程序…

【GPTs】Email Responder Pro:高效生成专业回复邮件

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | GPTs应用实例 文章目录 &#x1f4af;GPTs指令&#x1f4af;前言&#x1f4af;Email Responder Pro主要功能适用场景优点缺点 &#x1f4af;小结 &#x1f4af;GPTs指令 Email Craft is a specialized assistant for cra…

阿里云ECS服务器使用限制及不允许做的事情

阿里云ECS&#xff08;Elastic Compute Service&#xff09;是一种高性能的弹性计算服务&#xff0c;允许用户在云端创建和管理虚拟服务器。尽管ECS提供了强大的功能&#xff0c;但在使用过程中&#xff0c;阿里云有一些限制和不允许的行为。以下是一些主要的使用限制和禁止行为…

《Atomic Picnic》进不去游戏解决方法

Atomic Picnic有时候会遇到进不去游戏的情况&#xff0c;这可能是由多种原因造成的&#xff0c;玩家可以采取很多解决方法&#xff0c;比如检查电脑配置、更新系统和驱动或验证游戏文件。 Atomic Picnic进不去游戏怎么办 检查电脑配置 查看自己的电脑配置是否达到了游戏的要求…