1.Lwrb的介绍(博主功能的实现是基于RT-thread系统实现)

        Lwrb是由Tilen Majerle编写的一个线程安全的环形队列,通常与DMA配合实现数据的无阻塞性收发,同时,配合DMA的传输过半中断,传输完成中断,以及串口的空闲中断实现实现数据的无阻塞性收发,同时环形队列,在一定程度上给CPU处理数据提供了一定的时间,具体的原理在后续的内容中会有详细解释。

2.Lwrb源码的获取及移植

        源码链接如下:若不能访问,则挂载VPN即可。MaJerle/lwrb: Lightweight generic ring buffer manager libraryhttps://github.com/MaJerle/lwrb        Lwrb的源码结构如下:

        该文件夹内是Lwrb的源码,以及对应源码操作的示例。移植时只需将如下文件添加到我们的工程文件即可:

       这个Lwrb开源库只需要把文件添加到工程中,就算是移植完成了。

3.环形队列实现无阻塞性接收数据的解释

        串口是提供移位寄存器实现的数据接收,当MCU检测到串口的RDR寄存器不为空时,串口的移位寄存器将会把RDR寄存器中的数据一个一个的移出,这时,移位寄存器移出的数据写入至对应的环形队列中,当串口数据接收完成时,则会进入串口的空闲中断,我们在串口的空闲中断中设置一个标记位(若我们的项目中已经添加了RTOS,则可以在空闲中断中释放一个信号量),当我们的主程序检测到对应的标记被置位,则说明串口已经完成了数据接收,此时将环形队列中的数据读出,然后检测对应的数据即可。但是这样有一个弊端,此时没有DMA配合时,只能通过串口的空闲中断判断数据传输是否已经完成,当串口一次性接收的数据大于环形队列的最大长度时,则会导致环形队列中之前接收的数据没有被及时处理进而导致数据被覆盖,而造成数据的丢失。若是将串口的空闲中断和DMA的传输一半中断以及传输完成中断进行结合,此时就可实现数据的及时处理,实现方法如下:

        这里用图表的方式表示,具体的代码试下在下面的章节中会有详细的内容解释。

        解释一下为什么这样做:当串口一次性接收的数据太多时,接收数据的环形队列中的数据被覆盖,进而这样设计,意思就是说当数据过多时,DMA接收的数据已经填充了缓冲区的一半数据,此时触发DMA的传输完成一半中断,此时将对应的数据处理标记置1,我们主程序中将会开始读取环形队列中的数据,这样就会及时处理对应的数据,不会导致环形队列中的数据被覆盖。(注意:这要求我们数据处理的速度大于DMA搬运数据的速度)。

        此方法避免了DMA使用双缓冲区实现无阻塞性的接收而造成的内存浪费问题,同时也提高了我们的传输效率。

4.实现串口无阻塞性收发的具体实现

4.1 串口的配置

        配置代码具体如下:

/*-------------------------------封装数据,减少全局变量的使用-------------------------------------*/
typedef struct Drv_uart{lwrb_t uart1_tx_rb;//串口的发送lwrb_t uart1_rx_rb;//串口的接收环形队列uint8_t uart_tx_rb_data[UART1_TX_RB_LENGTH];//这个是发送缓冲区uint8_t uart1_rx_rb_data[UART1_RX_RB_LENGTH];//接收缓冲区volatile size_t _uart1_tx_dma_current_len;
}Drv_Uart_t;/*** @brief  GPIO初始化* @retval 无*/
void Uart1_Gpio_Init(void)
{/* GPIO外设时钟使能 */std_rcc_gpio_clk_enable(RCC_PERIPH_CLK_GPIOA);std_gpio_init_t usart_gpio_init = {0};usart_gpio_init.pin = UART1_TX_GPIO_PIN;usart_gpio_init.mode = GPIO_MODE_ALTERNATE;usart_gpio_init.output_type = GPIO_OUTPUT_PUSHPULL;usart_gpio_init.pull = GPIO_PULLUP;usart_gpio_init.alternate = GPIO_AF1_USART1;std_gpio_init(UART1_TX_GPIO_PORT, &usart_gpio_init);usart_gpio_init.pin = UART1_RX_GPIO_PIN;usart_gpio_init.mode = GPIO_MODE_ALTERNATE;usart_gpio_init.output_type = GPIO_OUTPUT_PUSHPULL;usart_gpio_init.pull = GPIO_PULLUP;usart_gpio_init.alternate = GPIO_AF1_USART1;std_gpio_init(UART1_RX_GPIO_PORT, &usart_gpio_init);
}/*** @brief  USART1初始化* @retval 无*/
void _Uart1_Init(uint32_t baudrate, uint32_t par)
{/* USART1时钟使能 */std_rcc_apb2_clk_enable(RCC_PERIPH_CLK_USART1);std_usart_init_t usart_config = {0};usart_config.direction = USART_DIRECTION_SEND_RECEIVE;usart_config.baudrate = baudrate;usart_config.wordlength = USART_WORDLENGTH_8BITS;usart_config.stopbits = USART_STOPBITS_1;usart_config.parity = par;usart_config.hardware_flow = USART_FLOWCONTROL_NONE;/* USART初始化 */if (STD_OK != std_usart_init(USART1, &usart_config)){/* 波特率配置不正确处理代码 */while (1);}/* NVIC初始化 */NVIC_SetPriority(USART1_IRQn, 0);NVIC_EnableIRQ(USART1_IRQn);std_usart_cr1_interrupt_enable(USART1, USART_CR1_INTERRUPT_PE);std_usart_cr1_interrupt_enable(USART1, USART_CR3_INTERRUPT_ERR);std_usart_cr1_interrupt_enable(USART1, USART_CR1_INTERRUPT_IDLE);/* 使能USART DMA接收 */std_usart_dma_rx_enable(USART1); // 串口DMA接收使能std_usart_enable(USART1);        // 串口使能RTT_LOG_I("USART1 init success");
}void UART1_Init(uint32_t baudrate, uint32_t par)
{/* Initialize ringbuff */lwrb_init(&Usart1.uart1_rx_rb, Usart1.uart1_rx_rb_data, sizeof(Usart1.uart1_rx_rb_data));//这是关于串口接收环形队列的初始化lwrb_init(&Usart1.uart1_tx_rb, Usart1.uart_tx_rb_data, sizeof(Usart1.uart_tx_rb_data));//这是关于串口发送环形队列的初始化/* 串口DMA配置 */Uart1_Dma_Init();/* GPIO初始化 */Uart1_Gpio_Init();/* UASRT1初始化 */_Uart1_Init(baudrate, par);
}

4.2 串口DMA的配置

        由于CIU32L051这一系列的MCU只有两个DMA通道,具体的通道映射有在之前的那篇CIU32关于DMA的无阻塞性收发中写,各位需要的可以去查看对应的内容。

        这里根据自己IC的情况进行配置即可,博主的配置流程具体如下:

/*** @brief  DMA配置函数* @param  distination DMA传输目的地址* @param  number DMA传输字符数* @retval 无*/
void Uart1_Dma_Rec_Data_Cfg(uint8_t *distination)
{std_dma_config_t dma_config = {0};/* 配置DMA 源地址、目的地址和传输数据大小,并使能DMA */dma_config.src_addr = (uint32_t)&USART1->RDR;dma_config.dst_addr = (uint32_t)distination;// dma_config.data_number = LWUTIL_ARRAYSIZE(distination);dma_config.data_number = 128;dma_config.dma_channel = DMA_CHANNEL_0;std_dma_start_transmit(&dma_config);
}/*** @brief  DMA配置函数* @param  source DMA源地址* @param  number DMA传输字符数* @retval 无*/
void Uart1_Dma_Send_Data(uint32_t *source, uint32_t number)
{std_dma_config_t dma_config = {0};/* 配置DMA 源地址、目的地址和传输数据大小,并使能DMA  */dma_config.src_addr = (uint32_t)source;dma_config.dst_addr = (uint32_t)&USART1->TDR;dma_config.data_number = number;dma_config.dma_channel = DMA_CHANNEL_1;std_dma_start_transmit(&dma_config);
}/*** @brief  DMA通道0初始化* @retval 无*/
void Uart1_Dma_Init(void)
{std_dma_init_t dma_init_param = {0};/* DMA外设时钟使能 */std_rcc_ahb_clk_enable(RCC_PERIPH_CLK_DMA);/* dma_init_param 结构体初始化 */dma_init_param.dma_channel = UART1_DMA_RX_CHANNEL;//MDA的通道0映射为串口的接收引脚dma_init_param.dma_req_id = DMA_REQUEST_USART1_RX;//这里是指DMA传输的触发条件dma_init_param.transfer_type = DMA_BLOCK_TRANSFER;//这里就是设置了DMA的传输类型,具体就不解释了dma_init_param.src_addr_inc = DMA_SRC_INC_DISABLE;//失能数据递增递增dma_init_param.dst_addr_inc = DMA_DST_INC_ENABLE;//使能目标地址递增dma_init_param.data_size = DMA_DATA_SIZE_BYTE;//每次传输一个字节dma_init_param.mode = DMA_MODE_CIRCULAR;//循环接收std_dma_init(&dma_init_param);/* dma_init_param 结构体初始化 */dma_init_param.dma_channel = UART1_DMA_TX_CHANNEL;dma_init_param.dma_req_id = DMA_REQUEST_USART1_TX;dma_init_param.transfer_type = DMA_BLOCK_TRANSFER;dma_init_param.src_addr_inc = DMA_SRC_INC_ENABLE;dma_init_param.dst_addr_inc = DMA_DST_INC_DISABLE;dma_init_param.data_size = DMA_DATA_SIZE_BYTE;dma_init_param.mode = DMA_MODE_NORMAL;std_dma_init(&dma_init_param);/* 使能接收中断 */std_dma_interrupt_enable(UART1_DMA_RX_CHANNEL, DMA_INTERRUPT_TF);/**< DMA传输完成中断  */std_dma_interrupt_enable(UART1_DMA_RX_CHANNEL, DMA_INTERRUPT_TH);/**< DMA传输一半中断  */std_dma_interrupt_enable(UART1_DMA_RX_CHANNEL, DMA_INTERRUPT_TE); /**< DMA传输错误中断  *//* NVIC初始化 */NVIC_SetPriority(UART1_DMA_RX_IRQ_CHANNEL, 0);NVIC_EnableIRQ(UART1_DMA_RX_IRQ_CHANNEL);NVIC_SetPriority(UART1_DMA_TX_IRQ_CHANNEL, 0);NVIC_EnableIRQ(UART1_DMA_TX_IRQ_CHANNEL);Uart1_Dma_Rec_Data_Cfg(_uart1_rx_dma_buffer); // DMA接收数据配置std_dma_enable(UART1_DMA_RX_CHANNEL);
}

        以上内容则是关于串口的硬件配置。

4.3 串口的中断服务函数和DMA的中断服务函数

        具体内容:

/*** @brief  USART1 中断服务函数* @retval 无*/
void USART1_IRQHandler(void)
{/* enter interrupt */rt_interrupt_enter();//这是博主使用的RT-thread系统必须要添加的中断处理程序,若各位没有加RT-THREAD,此处可以删除/* 检查到奇偶校验错误中断使能 */if (((std_usart_get_cr1_interrupt_enable(USART1, USART_CR1_INTERRUPT_PE)) && (std_usart_get_flag(USART1, USART_FLAG_PE))) != RESET){std_usart_clear_flag(USART1, USART_FLAG_PE);}/* USART 错误中断(帧错误,噪声错误,溢出错误)  */if (((std_usart_get_cr1_interrupt_enable(USART1, USART_CR3_INTERRUPT_ERR)) && (std_usart_get_flag(USART1, USART_CR3_INTERRUPT_ERR))) != RESET){std_usart_clear_flag(USART1, USART_CR3_INTERRUPT_ERR);}/* USART 空闲中断  */if (((std_usart_get_cr1_interrupt_enable(USART1, USART_CR1_INTERRUPT_IDLE)) && (std_usart_get_flag(USART1, USART_FLAG_IDLE))) != RESET){std_usart_clear_flag(USART1, USART_CLEAR_IDLE);rt_sem_release(uart1_rx_check_sem);//这里释放了对应的信号量(对应裸机的标记为置1)
#ifdef DEBUG_OUTPUT_SELECT//这里是博主自己项目中添加的一些调试内容,可以省略rt_sem_release(uart1_rx_ok_sem);
#endif}/* leave interrupt */rt_interrupt_leave();//这是博主使用的RT-thread系统必须要添加的中断处理程序,若各位没有加RT-THREAD,此处可以删除
}/*** @brief  DMA通道0中断服务函数  UART1 RX* @retval 无*/
void DMA_Channel0_IRQHandler(void)
{/* enter interrupt */rt_interrupt_enter();//这是博主使用的RT-thread系统必须要添加的中断处理程序,若各位没有加RT-THREAD,此处可以删除if ((std_dma_get_interrupt_enable(UART1_DMA_RX_CHANNEL, DMA_INTERRUPT_TH)) && (std_dma_get_flag(DMA_FLAG_TH0))){std_dma_clear_flag(DMA_CLEAR_TH0);rt_sem_release(uart1_rx_check_sem);}if ((std_dma_get_interrupt_enable(UART1_DMA_RX_CHANNEL, DMA_INTERRUPT_TF)) && (std_dma_get_flag(DMA_FLAG_TF0))){std_dma_clear_flag(DMA_CLEAR_TF0);rt_sem_release(uart1_rx_check_sem);}if ((std_dma_get_interrupt_enable(UART1_DMA_RX_CHANNEL, DMA_INTERRUPT_TE)) && (std_dma_get_flag(DMA_FLAG_TE0)))//这里就是DMA的数据传输错误,可以编写对应的错误处理程序{std_dma_clear_flag(DMA_CLEAR_TE0);}/* Implement other events when needed *//* leave interrupt */rt_interrupt_leave();//这是博主使用的RT-thread系统必须要添加的中断处理程序,若各位没有加RT-THREAD,此处可以删除
}/*** @brief  DMA通道1中断服务函数* @retval 无*/
void DMA_Channel1_IRQHandler(void)
{/* enter interrupt */rt_interrupt_enter();//这是博主使用的RT-thread系统必须要添加的中断处理程序,若各位没有加RT-THREAD,此处可以删除/* DMA传输完成中断服务 */if ((std_dma_get_interrupt_enable(UART1_DMA_TX_CHANNEL, DMA_INTERRUPT_TF)) && (std_dma_get_flag(DMA_FLAG_TF1))){std_dma_interrupt_disable(UART1_DMA_TX_CHANNEL, DMA_INTERRUPT_TF); // 发送完成关闭DMA通道中断std_dma_clear_flag(DMA_CLEAR_TF1);lwrb_skip(&Usart1.uart1_tx_rb, Usart1._uart1_tx_dma_current_len); /* Skip buffer, it has been successfully sent out */Usart1._uart1_tx_dma_current_len = 0;                             /* Reset data length */}/* leave interrupt */rt_interrupt_leave();//这是博主使用的RT-thread系统必须要添加的中断处理程序,若各位没有加RT-THREAD,此处可以删除
}

4.4 DMA的发送

        发送功能比较简单,由于是单次发送,DMA传输完成一次后,只需将发送缓冲区更换,即换源后重新启动DMA的发送即可实现无阻塞性发送。DMA是外设进行的数据搬运不经过CPU,由此DMA的发送才叫无阻塞性的发送。

        具体源码如下:

/*** \brief       Check if DMA is active and if not try to send data* \return      `1` if transfer just started, `0` if on-going or no data to transmit*/
static uint8_t _UART1_StartTxDMATransfer(void)
{uint8_t started = 0;rt_enter_critical(); // 调度器上锁,保证DMA数据的正常发送(没有操作系统的这里可以省略)if (Usart1._uart1_tx_dma_current_len == 0 && (Usart1._uart1_tx_dma_current_len = lwrb_get_linear_block_read_length(&Usart1.uart1_tx_rb)) > 0)//这里是用于保证环形队列的发送缓冲区的线性安全{/* Disable channel if enabled */std_dma_disable(UART1_DMA_TX_CHANNEL); // 如果DMA通道只给串口1使用,那只需要在初始化使能就行,不需要关闭std_usart_dma_tx_disable(USART1);/* Clear all flags */std_dma_clear_flag(DMA_CLEAR_TF1);Uart1_Dma_Send_Data(lwrb_get_linear_block_read_address(&Usart1.uart1_tx_rb), Usart1._uart1_tx_dma_current_len);std_dma_interrupt_enable(DMA_CHANNEL_1, DMA_INTERRUPT_TF); // 发送时打开DMA通道中断/* enable transfer */std_dma_enable(UART1_DMA_TX_CHANNEL);std_usart_dma_tx_enable(USART1);started = 1;}rt_exit_critical();return started;
}rt_uint32_t UART1_Write(const void *data, rt_size_t len)
{rt_uint32_t ret = 0;//获取用于写操作的缓冲区可用大小 是否大于需要写入的数据长度if (lwrb_get_free(&Usart1.uart1_tx_rb) >= len){ret = lwrb_write(&Usart1.uart1_tx_rb, data, len);_UART1_StartTxDMATransfer(); /* Then try to start transfer */}return ret;
}

4.5 DMA接收数据

       这里接收数据的处理是轮询检测串口的接收状态,再根据DMA传输数据的位置情况,将DMA缓冲区中的数据写入到对应的环形队列中。

        具体源码如下:

/*** \brief           Process received data over UART1* Data are written to RX ringbuffer for application processing at latter stage* \param[in]       data: Data to process* \param[in]       len: Length in units of bytes*/
rt_inline void _UART1_ProcessData(const void *data, size_t len)
{/* Write data to receive buffer *///将接收到的数据写入到接收的环形队列中lwrb_write(&Usart1.uart1_rx_rb, data, len);
}static void _UART1_RxCheck(void)
{static size_t old_pos;size_t pos;/* Calculate current position in buffer and check for new data available */pos = LWUTIL_ARRAYSIZE(_uart1_rx_dma_buffer) - std_dma_get_transfer_data_number(UART1_DMA_RX_CHANNEL);//计算缓冲区中的当前位置并检查可用的新数据// RTT_LOG_D("std_dma_get_transfer_data_number(DMA_CHANNEL_0): %d", std_dma_get_transfer_data_number(DMA_CHANNEL_0));/* Check change in received data */if (pos != old_pos)//说明串口接收到了对应的数据{/* Current position is over previous one */if (pos > old_pos){//_uart1_rx_dma_buffer就是DMA搬运数据的目的地址_UART1_ProcessData(&_uart1_rx_dma_buffer[old_pos], pos - old_pos);}else{_UART1_ProcessData(&_uart1_rx_dma_buffer[old_pos], LWUTIL_ARRAYSIZE(_uart1_rx_dma_buffer) - old_pos);if (pos > 0){_UART1_ProcessData(&_uart1_rx_dma_buffer[0], pos);}}old_pos = pos; /* Save current position as old for next transfers */}
}//这里是在线程中轮询检测串口数据的接收情况
static void Uart1_Rx_Thread_Entry(void *parameter)
{// RTT_LOG_D("Uart1_Rx_Thread_Entry");char buf[128];uint8_t len;while (1){//这相当于是裸机状态下判断数据是否接收完成的标记rt_sem_take(uart1_rx_check_sem, RT_WAITING_FOREVER);_UART1_RxCheck();}
}

        上述内容将串口接收到的数据写入到了接收的环形队列中,再进行数据解析时将环形队列中的数据读出即可,这便实现串口数据的无阻塞性接收。(同时我们应在对应的程序中及时处理串口接收到的数据,若长时间不读环形队列中的数据,后续在dma的传输中,会将未及时读取的数据覆盖,最终造成数据丢失)

5.总结

        环形队列实现无阻塞性接收的原理就是,利用串口接收数据的时间间隙,处理存储在环形队列中的数据,循环往复的进行数据接收。

        以上内容则是Lwrb环形队列实现DMA串口无阻塞性收发的实现。

        同时推理其他协议的无阻塞性接收也可以通过环形队列实现,其原理都是相同的。

各位对于上述Lwrb环形队列有不懂的地方,可以加博主的联系方式相互交流。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】C++ 的入门知识2

本篇文章主要讲解 C 的入门语法知识引用、inline 关键字与 nullptr 关键字。 目录 1 引用 1&#xff09; 引用的概念与定义 &#xff08;1&#xff09; 引用的概念 &#xff08;2&#xff09; 引用的定义 2&#xff09; 引用的特性 3&#xff09; 引用的使用场…

基于Kafka实现动态监听topic功能

生命无罪&#xff0c;健康万岁&#xff0c;我是laity。 我曾七次鄙视自己的灵魂&#xff1a; 第一次&#xff0c;当它本可进取时&#xff0c;却故作谦卑&#xff1b; 第二次&#xff0c;当它在空虚时&#xff0c;用爱欲来填充&#xff1b; 第三次&#xff0c;在困难和容易之间&…

机械学习初识--什么是机械学习--机械学习有什么重要算法

一、什么是机械学习机器学习&#xff08;Machine Learning&#xff09;是人工智能&#xff08;AI&#xff09;的一个重要分支&#xff0c;它使计算机能够通过数据自动学习规律、改进性能&#xff0c;并在没有明确编程的情况下完成特定任务。其核心思想是让机器从数据中 “学习”…

普通大学生大三这一年的想法

目录 大三期间的经历与反思 公益活动&#xff1a;社会责任感的体现 比赛&#xff1a;个人成长的助推器 培训与思想提升 大学教育的本质与人才培养 构建自我的道与未来规划 大学教育的未来与个人定位 结语 大三期间的经历与反思 大三&#xff0c;大学生活的分水岭&#…

Python——入门

目录 变量 变量类型 动态类型 注释 输出输入 运算符 算术运算符 关系运算符 逻辑运算符 赋值运算符 条件语句 循环语句 函数 函数作用域 函数嵌套调用 函数默认参数 关键字参数 列表 切片 列表遍历 新增元素 查找元素 删除元素 列表拼接 元组…

华为荣耀部分机型从鸿蒙降回EMUI的一种方法

一、准备说明 1、这里介绍使用华为手机助手、海外代理软件结合固件将部分华为荣耀手机鸿蒙系统降级回EMUI系 统的一种方式&#xff1b; 2、需要降级的手机需要再出厂时内置系统为EMUI&#xff0c;出厂时为鸿蒙系统的无法进行降级操作&#xff1b; 3、降级有风险&#xff0…

maven <dependencyManagement>标签的作用

作用 dependencyManagement标签的作用&#xff1a;在父工程pom文件中声明依赖&#xff0c;但不引入&#xff1b;在子工程中用到声明的依赖时&#xff0c;可以不加依赖的版本号&#xff0c;这样可以统一管理工程中用到的依赖版本。 示例 先创建一个项目 dependencyManagement-de…

JSON格式化与结构对比

说明 功能格式化json字符串为最简格式&#xff0c;并标识值类型&#xff1b;比对json字符串结构。第三方依赖fastjson: 用于解析json、判断json值类型&#xff1b;springframework自带的字符串判断&#xff0c;可以不依赖该方法&#xff0c;改为自行实现&#xff1b;slf4j: 用于…

编程与数学 03-002 计算机网络 03_物理层基础

编程与数学 03-002 计算机网络 03_物理层基础一、物理层的作用与任务&#xff08;一&#xff09;传输媒体的类型&#xff08;二&#xff09;信号的传输方式二、数据编码技术&#xff08;一&#xff09;数字数据的数字信号编码&#xff08;二&#xff09;模拟数据的数字信号编码…

c语言--文件操作

思维导图:1. 为什么使用文件&#xff1f; 如果没有文件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失了&#xff0c;等再次运⾏程序&#xff0c;是看不到上次程序的数据的&#xff0c;如果要将数据进…

SQL中的占位符、@Param注解和方法参数

代码中出现的多个 username 和 password 代表不同层面的变量&#xff0c;具体含义如下&#xff08;按执行顺序&#xff09;&#xff1a;### 1. Param("username") String username - 位置 &#xff1a;方法参数前的注解 - 作用 &#xff1a;- Param("username&q…

【SpringAI实战】FunctionCalling实现企业级自定义智能客服

一、前言 二、实现效果 三、代码实现 3.1 后端实现 3.2 前端实现 一、前言 Spring AI详解&#xff1a;【Spring AI详解】开启Java生态的智能应用开发新时代(附不同功能的Spring AI实战项目)-CSDN博客 二、实现效果 一个24小时在线的AI智能客服&#xff0c;可以给用户提供培…

kotlin基础【2】

变量类型var 和 val 的核心区别&#xff1a;关键字含义能否重新赋值类似概念&#xff08;Java&#xff09;varvariable&#xff08;可变变量&#xff09;可以普通变量&#xff08;无 final&#xff09;valvalue&#xff08;不可变变量&#xff09;不可以被 final 修饰的变量var…

【Spring AI】阿里云DashScope灵积模型

DashScope&#xff08;灵积模型&#xff09;是阿里云提供的大模型服务平台&#xff0c;集成了阿里自研的 通义千问&#xff08;Qwen&#xff09;系列大语言模型&#xff08;LLM&#xff09;以及多模态模型&#xff0c;为企业与开发者提供开箱即用的 AI 能力。官网地址 https://…

Rust Web框架性能对比与实战指南

Rust Actix Web Rust Web 框架的实用对比分析 以下是 Rust Web 框架的实用对比分析,涵盖主要框架(如 Actix-web、Rocket、Warp、Axum 等)的常见使用场景示例,按功能分类整理: 基础路由设置 Actix-web use actix_web::{get, App, HttpResponse, HttpServer, Responder}…

【解决vmware ubuntu不小心删boot分区,进不去系统】

如果仍然提示 Unable to locate package testdisk&#xff0c;有可能是源中不包含该工具&#xff08;LiveCD 使用的是“最小环境”&#xff09;。 &#x1fa9b; 解决方法&#xff1a;切换到国内完整软件源&#xff08;推荐&#xff09; 编辑 sources.list&#xff1a; sudo na…

04-netty基础-Reactor三种模型

1 基本概念Reactor模型是一种事件驱动&#xff08;Event-Driven&#xff09;的设计模式&#xff0c;主要用于高效处理高并发、I/O密集型场景&#xff08;如网络、服务器、分布式等&#xff09;。其核心思想就是集中管理事件&#xff0c;将I/O操作与业务逻辑解耦&#xff0c;避免…

踩坑无数!NFS服务从入门到放弃再到真香的血泪史

前言 说起NFS&#xff0c;我估计很多搞运维的兄弟都有一肚子话要说。这玩意儿吧&#xff0c;看起来简单&#xff0c;用起来坑多&#xff0c;但是真正搞明白了又觉得挺香的。 前几天有个朋友问我&#xff0c;说他们公司要搭建一个文件共享系统&#xff0c;问我推荐什么方案。我…

矩阵谱分解的证明及计算示例

1. 矩阵谱分解的条件矩阵的谱分解&#xff08;也称为特征分解&#xff09;是将一个矩阵分解为一系列由其特征向量和特征值构成的矩阵乘积的过程。进行谱分解的前提条件包括&#xff1a;<1.> 矩阵是可对角化的&#xff08;Diagonalizable&#xff09;&#xff0c;即矩阵存…

Leetcode 07 java

169. 多数元素 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。 多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 示例 1&#xff1a; 输入&#xff1a;nums [3,2,3] 输出&a…