收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
txconfig:使能/失能USART发送器,详细列表如下:
值 | 含义 |
---|---|
USART_TRANSMIT_ENABLE | 使能USART发送 |
USART_TRANSMIT_DISABLE | 失能USART发送 |
2.9 usart_receive_config
功能 | USART/UART接收器配置 |
---|---|
函数定义 | void usart_receive_config(uint32_t usart_periph, uint32_t rxconfig) |
参数 | usart_periph:USARTx(x=0,1,2)或UARTx(x=3,4)rxconfig:使能/失能USART接收器 |
返回 | 无 |
rxconfig:使能/失能USART接收器,详细列表如下:
值 | 含义 |
---|---|
USART_RECEIVE_ENABLE | 使能USART接收 |
USART_RECEIVE_DISABLE | 失能USART接收 |
2.10 usart_enable
功能 | 使能USART/UART |
---|---|
函数定义 | void usart_enable(uint32_t usart_periph) |
参数 | usart_periph:USARTx(x=0,1,2)或UARTx(x=3,4) |
返回 | 无 |
2.11 usart_data_transmit
功能 | USART/UART发送数据功能 |
---|---|
函数定义 | void usart_data_transmit(uint32_t usart_periph, uint16_t data) |
参数 | usart_periph:USARTx(x=0,1,2)或UARTx(x=3,4)data:发送的数据 |
返回 | 无 |
2.12 usart_data_receive
功能 | USART/UART接收数据功能 |
---|---|
函数定义 | uint16_t usart_data_receive(uint32_t usart_periph) |
参数 | usart_periph:USARTx(x=0,1,2)或UARTx(x=3,4) |
返回 | 接收的数据 |
2.13 usart_interrupt_enable
功能 | 使能USART中断 |
---|---|
函数定义 | void usart_interrupt_enable(uint32_t usart_periph, uint32_t int_flag) |
参数 | usart_periph:USARTx(x=0,1,2)或UARTx(x=3,4)int_flag:USART中断标志 |
返回 | 无 |
int_flag:USART中断标志,详细列表如下:
值 | 含义 |
---|---|
USART_INT_PERR | 校验错误中断 |
USART_INT_TBE | 发送缓冲区空中断 |
USART_INT_TC | 发送完成中断 |
USART_INT_RBNE | 读数据缓冲区非空中断和过载错误中断 |
USART_INT_IDLE | 空闲线检测中断 |
USART_INT_LBD | LIN断开信号检测中断 |
USART_INT_CTS | CTS中断 |
USART_INT_ERR | 错误中断 |
2.14 usart_flag_get
功能 | 获取USART状态寄存器标志位 |
---|---|
函数定义 | FlagStatus usart_flag_get(uint32_t usart_periph, usart_flag_enum flag) |
参数 | usart_periph:USARTx(x=0,1,2)或UARTx(x=3,4)flag:USART标志位 |
返回 | SET或RESET |
flag:USART标志位,详细列表如下:
值 | 含义 |
---|---|
USART_FLAG_CTSF | CTS变化标志 |
USART_FLAG_LBDF | LIN断开检测标志 |
USART_FLAG_TBE | 发送数据缓冲区空 |
USART_FLAG_TC | 发送完成 |
USART_FLAG_RBNE | 读数据缓冲区非空 |
USART_FLAG_IDLEF | 空闲线检测标志 |
USART_FLAG_ORERR | 溢出错误 |
USART_FLAG_NERR | 噪声错误标志 |
USART_FLAG_FERR | 帧错误标志 |
USART_FLAG_PERR | 校验错误标志 |
2.15 usart_interrupt_flag_get
功能 | 获取USART中断标志位状态 |
---|---|
函数定义 | FlagStatus usart_interrupt_flag_get(uint32_t usart_periph, uint32_t int_flag) |
参数 | usart_periph:USARTx(x=0,1,2)或UARTx(x=3,4)int_flag:USART中断标志 |
返回 | SET或RESET |
int_flag:USART中断标志,详细列表如下:
值 | 含义 |
---|---|
USART_INT_FLAG_PERR | 校验错误中断和标志 |
USART_INT_FLAG_TBE | 发送缓冲区空中断和标志 |
USART_INT_FLAG_TC | 发送完成中断和标志 |
USART_INT_FLAG_RBNE | 读数据缓冲区非空中断和标志 |
USART_INT_FLAG_RBNE_ORERR | 读数据缓冲区非空中断和过载错误中断标志 |
USART_INT_FLAG_IDLE | 空闲线检测中断和标志 |
USART_INT_FLAG_LBD | LIN断开检测中断和标志 |
USART_INT_FLAG_CTS | CTS中断和标志 |
USART_INT_FLAG_ERR_ORERR | 错误中断和过载错误 |
USART_INT_FLAG_ERR_NERR | 错误中断和噪声错误标志 |
USART_INT_FLAG_ERR_FERR | 错误中断和镇错误标志 |
三、USART收发通信
3.1 引脚分布
总线 | APB2总线 | APB1总线 | APB1总线 | APB1总线 | APB1总线 |
---|---|---|---|---|---|
引脚 | USART0 | USART1 | USART2 | UART3 | UART4 |
TX | PA9(重映射PB6) | PA2(重映射PD5) | PB10(重映射PC10) | PC10 | PC12 |
RX | PA10(重映射PB7) | PA3(重映射PD6) | PB11(重映射PC11) | PC11 | PD2 |
SCLK | PA8 | PA4(重映射PD7) | PB12(重映射PC12) | ||
CTS | PA11 | PA0(重映射PD3) | PB13 | ||
RTS | PA12 | PA1(重映射PD4) | PB14 |
STM32F103RCT6 系统控制器有三个 USART 和两个 UART,其中 USART0 时钟来源于 APB2 总线时钟,其最大频率为 108MHz,其他四个的时钟来源于 APB1 总线时钟,其最大频率为 54MHz。UART 只是异步传输功能,所以没有 SCLK、CTS 和 RTS 功能引脚。
3.2 编程要点
- 使能 RX 和 TX 引脚 GPIO 时钟和 USART 时钟
- 初始化 GPIO,并将 GPIO 复用到 USART 上
- 配置 USART 参数
- 配置中断控制器并使能 USART 接收中断
- 使能 USART
- 在 USART 接收中断服务函数实现接收和发送
3.3 USART初始化
使用 GPIO 之前都需要初始化配置它,并且还要添加特殊设置,因为我们使用它作为外设的引脚,一般都有特殊功能。我们在初始化时需要把它的模式设置为复用功能。这里把串口的 Tx 引脚配置为复用推挽输出,Rx 引脚为浮空输入,数据完全由外部输入决定。
接下来,我们配置 USART0 通信参数为:波特率 115200,字长为 8,1 个停止位,没有校验位,不使用硬件流控制,收发一体工作模式,然后调用 USART 初始化函数完成配置。
// GPIO时钟使能
rcu\_periph\_clock\_enable(RCU_GPIOA);
// USART时钟使能
rcu\_periph\_clock\_enable(RCU_USART0);
// 配置TX为推挽复用模式
gpio\_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
// 配置RX为浮空输入模式
gpio\_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
// 配置串口的工作参数
usart\_deinit(USART0);
usart\_baudrate\_set(USART0, 115200U); // 波特率
usart\_word\_length\_set(USART0, USART_WL_8BIT); // 帧数据字长
usart\_stop\_bit\_set(USART0, USART_STB_1BIT); // 停止位
usart\_parity\_config(USART0, USART_PM_NONE); // 奇偶校验位
usart\_hardware\_flow\_rts\_config(USART0, USART_RTS_DISABLE); // 硬件流控制RTS
usart\_hardware\_flow\_cts\_config(USART0, USART_CTS_DISABLE); // 硬件流控制CTS
usart\_receive\_config(USART0, USART_RECEIVE_ENABLE); // 使能接收
usart\_transmit\_config(USART0, USART_TRANSMIT_ENABLE); // 使能发送
usart\_enable(USART0); // 使能串口
3.4 串口中断初始化
- 抢占优先级,数字越小,优先级越高
- 若抢占优先级相同,判断子优先级,同样,数字越小,优先级越高
// 使能USART中断
nvic\_irq\_enable(USART0_IRQn, 0, 0);
// 使能串口接收中断
usart\_interrupt\_enable(USART0, USART_INT_RBNE);
3.5 串口中断处理函数
使能了 USART0 接收中断,当 USART0 有接收到数据就会执行 USART0_IRQHandler()
函数。usart_interrupt_flag_get()
函数与 usart_flag_get()
函数类似用来获取标志位状态,但 usart_interrupt_flag_get()
函数是专门用来获取中断事件标志的,并返回该标志位状态。使用 if 语句来判断是否是真的产生 USART 数据接收这个中断事件,如果是真的就使用 USART 数据读取函数 usart_data_receive()
读取数据到指定存储区。然后再调用 USART 数据发送函数 usart_data_transmit()
把数据又发送给源设备,即 PC 端的串口调试助手。
/\*!
\brief this function handles USART RBNE interrupt request and TBE interrupt request
\param[in] none
\param[out] none
\retval none
\*/
void USART0\_IRQHandler(void)
{
unsigned char data;
if(RESET != usart\_interrupt\_flag\_get(USART0, USART_INT_FLAG_RBNE))
{
data = usart\_data\_receive(USART0);
usart\_data\_transmit(USART0, (uint8\_t)data);
while(RESET == usart\_flag\_get(USART0, USART_FLAG_TBE));//发送完成判断
}
}
串口中断处理函数名称要与串口相对应,可在 startup_gd32f10x_hd.s
启动文件中查看:
; /\* external interrupts handler \*/
DCD WWDGT_IRQHandler ; 16:Window Watchdog Timer
DCD LVD_IRQHandler ; 17:LVD through EXTI Line detect
DCD TAMPER_IRQHandler ; 18:Tamper Interrupt
DCD RTC_IRQHandler ; 19:RTC through EXTI Line
DCD FMC_IRQHandler ; 20:FMC
DCD RCU_IRQHandler ; 21:RCU
DCD EXTI0_IRQHandler ; 22:EXTI Line 0
DCD EXTI1_IRQHandler ; 23:EXTI Line 1
DCD EXTI2_IRQHandler ; 24:EXTI Line 2
DCD EXTI3_IRQHandler ; 25:EXTI Line 3
DCD EXTI4_IRQHandler ; 26:EXTI Line 4
DCD DMA0_Channel0_IRQHandler ; 27:DMA0 Channel 0
DCD DMA0_Channel1_IRQHandler ; 28:DMA0 Channel 1
DCD DMA0_Channel2_IRQHandler ; 29:DMA0 Channel 2
DCD DMA0_Channel3_IRQHandler ; 30:DMA0 Channel 3
DCD DMA0_Channel4_IRQHandler ; 31:DMA0 Channel 4
DCD DMA0_Channel5_IRQHandler ; 32:DMA0 Channel 5
DCD DMA0_Channel6_IRQHandler ; 33:DMA0 Channel 6
DCD ADC0_1_IRQHandler ; 34:ADC0 and ADC1
DCD USBD_HP_CAN0_TX_IRQHandler ; 35:USBD and CAN0 TX
DCD USBD_LP_CAN0_RX0_IRQHandler ; 36:USBD and CAN0 RX0
DCD CAN0_RX1_IRQHandler ; 37:CAN0 RX1
DCD CAN0_EWMC_IRQHandler ; 38:CAN0 EWMC
DCD EXTI5_9_IRQHandler ; 39:EXTI Line 5 to EXTI Line 9
DCD TIMER0_BRK_IRQHandler ; 40:TIMER0 Break
DCD TIMER0_UP_IRQHandler ; 41:TIMER0 Update
DCD TIMER0_TRG_CMT_IRQHandler ; 42:TIMER0 Trigger and Commutation
DCD TIMER0_Channel_IRQHandler ; 43:TIMER0 Channel Capture Compare
DCD TIMER1_IRQHandler ; 44:TIMER1
DCD TIMER2_IRQHandler ; 45:TIMER2
DCD TIMER3_IRQHandler ; 46:TIMER3
DCD I2C0_EV_IRQHandler ; 47:I2C0 Event
DCD I2C0_ER_IRQHandler ; 48:I2C0 Error
DCD I2C1_EV_IRQHandler ; 49:I2C1 Event
DCD I2C1_ER_IRQHandler ; 50:I2C1 Error
DCD SPI0_IRQHandler ; 51:SPI0
DCD SPI1_IRQHandler ; 52:SPI1
DCD USART0_IRQHandler ; 53:USART0
DCD USART1_IRQHandler ; 54:USART1
DCD USART2_IRQHandler ; 55:USART2
DCD EXTI10_15_IRQHandler ; 56:EXTI Line 10 to EXTI Line 15
DCD RTC_Alarm_IRQHandler ; 57:RTC Alarm through EXTI Line
DCD USBD_WKUP_IRQHandler ; 58:USBD WakeUp from suspend through EXTI Line
DCD TIMER7_BRK_IRQHandler ; 59:TIMER7 Break Interrupt
DCD TIMER7_UP_IRQHandler ; 60:TIMER7 Update Interrupt
DCD TIMER7_TRG_CMT_IRQHandler ; 61:TIMER7 Trigger and Commutation Interrupt
DCD TIMER7_Channel_IRQHandler ; 62:TIMER7 Channel Capture Compare
DCD ADC2_IRQHandler ; 63:ADC2
DCD EXMC_IRQHandler ; 64:EXMC
DCD SDIO_IRQHandler ; 65:SDIO
DCD TIMER4_IRQHandler ; 66:TIMER4
DCD SPI2_IRQHandler ; 67:SPI2
DCD UART3_IRQHandler ; 68:UART3
DCD UART4_IRQHandler ; 69:UART4
DCD TIMER5_IRQHandler ; 70:TIMER5
DCD TIMER6_IRQHandler ; 71:TIMER6
DCD DMA1_Channel0_IRQHandler ; 72:DMA1 Channel0
DCD DMA1_Channel1_IRQHandler ; 73:DMA1 Channel1
DCD DMA1_Channel2_IRQHandler ; 74:DMA1 Channel2
DCD DMA1_Channel3_4_IRQHandler ; 75:DMA1 Channel3 and Channel4
3.6 printf重定向
/\*\*
\* @brief 重定向c库函数printf到USARTx
\* @retval None
\*/
int fputc(int ch, FILE \*f)
{
usart\_data\_transmit(USART0, (uint8\_t)ch);
while(RESET == usart\_flag\_get(USART0, USART_FLAG_TBE));
return ch;
}
/\*\*
\* @brief 重定向c库函数getchar,scanf到USARTx
\* @retval None
\*/
int fgetc(FILE \*f)
{
uint8\_t ch = 0;
ch = usart\_data\_receive(USART0);
return ch;
}
四、USART0串口中断收发
4.1 board_usart.c
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\* INCLUDES
\*/
#include <stdio.h>
#include "gd32f10x.h"
#include "board\_usart.h"
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\* PUBLIC FUNCTIONS
\*/
/\*\*
@brief 串口驱动初始化
@param 无
@return 无
\*/
void USART\_Init(void)
{
// GPIO时钟使能
rcu\_periph\_clock\_enable(RCU_GPIOA);
// USART时钟使能
rcu\_periph\_clock\_enable(RCU_USART0);
// 配置TX为推挽复用模式
gpio\_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
// 配置RX为浮空输入模式
gpio\_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
// 配置串口的工作参数
usart\_deinit(USART0);
usart\_baudrate\_set(USART0, 115200U); // 波特率
usart\_word\_length\_set(USART0, USART_WL_8BIT); // 帧数据字长
usart\_stop\_bit\_set(USART0, USART_STB_1BIT); // 停止位
usart\_parity\_config(USART0, USART_PM_NONE); // 奇偶校验位
usart\_hardware\_flow\_rts\_config(USART0, USART_RTS_DISABLE); // 硬件流控制RTS
usart\_hardware\_flow\_cts\_config(USART0, USART_CTS_DISABLE); // 硬件流控制CTS
usart\_receive\_config(USART0, USART_RECEIVE_ENABLE); // 使能接收
usart\_transmit\_config(USART0, USART_TRANSMIT_ENABLE); // 使能发送
usart\_enable(USART0); // 使能串口
// 使能USART中断
nvic\_irq\_enable(USART0_IRQn, 0, 0);
// 使能串口接收中断
usart\_interrupt\_enable(USART0, USART_INT_RBNE);
}
/\*\*
@brief 串口写入数据
@param pData -[in] 写入数据
@param dataLen -[in] 写入数据长度
@return 无
\*/
void UART\_Write(uint8\_t \*pData, uint32\_t dataLen)
{
uint8\_t i;
for(i = 0; i < dataLen; i++)
{
usart\_data\_transmit(USART0, pData[i]); // 发送一个字节数据
while(RESET == usart\_flag\_get(USART0, USART_FLAG_TBE)); // 发送完成判断
}
}
/\*\*
\* @brief 重定向c库函数printf到USARTx
\* @retval None
\*/
int fputc(int ch, FILE \*f)
{
usart\_data\_transmit(USART0, (uint8\_t)ch);
while(RESET == usart\_flag\_get(USART0, USART_FLAG_TBE));
return ch;
}
/\*\*
\* @brief 重定向c库函数getchar,scanf到USARTx
\* @retval None
\*/
int fgetc(FILE \*f)
{
uint8\_t ch = 0;
ch = usart\_data\_receive(USART0);
return ch;
}
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*END OF FILE\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
4.2 board_usart.h
#ifndef \_BOARD\_USART\_H\_
#define \_BOARD\_USART\_H\_
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\* INCLUDES
\*/
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\* DEFINITIONS
\*/
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\* API FUNCTIONS
\*/
void USART\_Init(void);
void UART\_Write(uint8\_t \*pData, uint32\_t dataLen);
#endif /\* \_BOARD\_USART\_H\_ \*/
4.3 gd32f10x_it.c
/\*!
\file gd32f10x\_it.c
\brief interrupt service routines
\version 2014-12-26, V1.0.0, firmware for GD32F10x
\version 2017-06-20, V2.0.0, firmware for GD32F10x
\version 2018-07-31, V2.1.0, firmware for GD32F10x
\version 2020-09-30, V2.2.0, firmware for GD32F10x
\*/
/\*
Copyright (c) 2020, GigaDevice Semiconductor Inc.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
![img](https://img-blog.csdnimg.cn/img_convert/bb4982d0d514ba8b007198c0a02930a1.png)
![img](https://img-blog.csdnimg.cn/img_convert/a529270cc409c4cbfeb9a97401caaf03.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**
**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**
ND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
[外链图片转存中...(img-w5evIKaa-1715881591609)]
[外链图片转存中...(img-wFiGu0jF-1715881591610)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**
**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**