CIU32L051 DMA+Lwrb環形隊列實現串口無阻塞性數據的收發 + 數據百分百不丟失的實現

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…