博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
STM32CubeMX之串口封装详解
阅读量:3782 次
发布时间:2019-05-22

本文共 16149 字,大约阅读时间需要 53 分钟。

概述

​ 上一篇写了 STM32CubeMX 的串口的使用,而这篇来扒一扒,它是怎么进行封装的。其实在标准库中也类似如下过程。

文章目录

一.串口实例

​ 我们都知道,其实单片机最后其实都是对 串口相关的寄存器 进行操作,那么我们想扒一扒它的流程,必然要先知道串口相关的寄存器是哪些,因此查阅 STM32F4xx中文参考手册 ,我们可以在 第711页 找到以下相关寄存器:

  • 状态寄存器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QkmCCDd8-1593537240013)(/image/串口状态寄存器.png)]

  • 数据寄存器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWj7qy0U-1593537240016)(/image/串口数据寄存器.png)]

  • 波特率寄存器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dZGEjeOD-1593537240018)(/image/波特率寄存器.png)]

  • 控制寄存器1

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dgaEzY5R-1593537240020)(/image/串口控制寄存器1.png)]

  • 控制寄存器2

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yjCi97QR-1593537240022)(/image/串口寄存器2.png)]

  • 控制寄存器3

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QUPmKDRx-1593537240026)(/image/串口控制寄存器3.png)]

  • 保护时间和预分频器寄存器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m90u99HG-1593537240027)(/image/串口时间和分频寄存器.png)]

​ 这些就是操作串口所需要的寄存器,那么我们应该怎么和 HAL库 的东西对应起来,去HAL库中找找,我们就会在 stm32f407xx.h 的头文件中找到以下结构体

/**   * @brief Universal Synchronous Asynchronous Receiver Transmitter  */ typedef struct{
__IO uint32_t SR; /*!< USART Status register, Address offset: 0x00 */ __IO uint32_t DR; /*!< USART Data register, Address offset: 0x04 */ __IO uint32_t BRR; /*!< USART Baud rate register, Address offset: 0x08 */ __IO uint32_t CR1; /*!< USART Control register 1, Address offset: 0x0C */ __IO uint32_t CR2; /*!< USART Control register 2, Address offset: 0x10 */ __IO uint32_t CR3; /*!< USART Control register 3, Address offset: 0x14 */ __IO uint32_t GTPR; /*!< USART Guard time and prescaler register, Address offset: 0x18 */} USART_TypeDef;

你会发现,寄存器和结构体中的变量名一一对应,那就是它了,我们再深入研究以下,为什么每个声明都是用uint32_t 的类型?我们还可以在 STM32F4xx中文参考手册 找到以下这张图,更直观的解释原因

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vzyrBIB4-1593537240029)(/image/串口偏移.png)]

​ 从上图可以看出,每个寄存器都是之间的地址偏移都是 4个字节,也就是 32 bit ,所以是用 uint32_t ,使每个寄存器占用 4 个字节,符合芯片文档。

​ 可是不知道你发现了没有,这边都没有说串口在内存的实际地址,有些小伙伴可能也不知道,就算有地址要怎么操作,这里我们稍微复习一下C 语言中的 常量指针 的知识,下面给出一小段代码

*(uint32_t *)0x40011000 = 0xFF;

​ 这段代码的含义,就是将 0x40011000 这个值转为指向一个32 bit 的内存空间的指针,* 取地址符号就是使得0xFF存入该 32 bit的空间内。

​ 因此我们应该要找到我们所使用的 串口1在内存的地址 ,说了这么多遍内存,我们就来看看 STM32 的内存到底长什么样子,我们可以在 对应的芯片手册上找到其内存映射图,当前芯片是 STM32F407,因此我们在芯片手册中会看到

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fFjksyGO-1593537240031)(/image/内存映射图.png)]

​ 从上图可以看到 STM32F407 的内存映射图,内存被分成了很多块,而串口是属于外设,因此现在只要关心从下往上数第三块内存块就行了,但是目前我们也只能从中看到有 AHB1,AHB2,APB1,APB2,用标准库的小伙伴,应该对这些词很熟悉了,就是经常要对其进行时钟初始化的外设总线。

​ 但是我们还是没有看到我们想要的 地址,因此我们还是要借助一张时钟树的图,来判断串口1 在哪一条总线之下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SqOEg8Nn-1593537240032)(/image/时钟树1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t7WwVzvD-1593537240032)(/image/时钟树3.png)]

​ 结合时钟树和内存映射图,我们可以看到,串口1的地址在0x400100000x4001 4BFF ,但是还不是真正我们想要的

​ 最终,你会在 STM32F4xx中文参考手册 中发现一张存储的映射表,串口1的地址范围就写在那里

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eyi5Scx3-1593537240034)(/image/存储器映射图1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bPKmmnsr-1593537240034)(/image/存储器映射图2.png)]

​ 这个地址范围就是我们要对其进行串口操作的实际地址,但是我们的操作实际就用到了7个寄存器

​ 为了更形象的说明,我们用以下代码,将前面提到的寄存器的实际的地址打印出来:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DdUj5A8V-1593537240035)(/image/打印寄存器地址.png)]

打印结果如下:

huart1.Instance->SR 40011000huart1.Instance->DR  40011004huart1.Instance->BRR 40011008huart1.Instance->CR1 4001100chuart1.Instance->CR2 40011010huart1.Instance->CR3 40011014huart1.Instance->CTPR 40011018

现在我们就很明白了,串口1的基地址 (40011000)+ 上面所说的地址偏移量 = 每个寄存器的实际地址,

​ 接下来我们再看看,这个基地址是怎么在 HAL 库中用上的。我们从上面的分析中知道,串口1其实是在外设总线上的,并且是 APB2 外设总线上,所以还是在stm32f407.h 的头文件中找,我们可以找到以下宏定义:

  • 外设基地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tR8X0czO-1593537240037)(/image/外设基地址.png)]

  • APB2基地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qQ4m8eXh-1593537240038)(/image/APB2基地址.png)]

  • 串口1基地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NG9zR2LJ-1593537240040)(/image/串口1基地址.png)]

  • 串口1用 USART_TypeDef 结构体封装

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K4bOctfq-1593537240041)(/image/串口寄存器封装.png)]

    这样我们就可以算出,串口1的基地址为:

    USART1_BASE = 0x40000000 + 0x00010000 + 0x1000USART1_BASE = 0x40011000   //正如我们上述文档所看到的一样

    将这个值通过 USART_TypeDef 结构体指针的强制转换,就可以用结构体的方式来对寄存器进行存取值了

    (升华一下下,其实数组的首地址也可以用结构体指针进行强制转换,只要结果符合你的预期就可以了,比如这个技巧可以用在网络传输的时候)

现在来看看 USART1 用在哪里了,我们可以在 usart.c 找到以下代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cjuv4Vlk-1593537240043)(/image/USART1使用.png)]

我们可以看到它用在了串口1初始化的函数(MX_USART1_UART_Init )里面了,也终于看到了我们这个标题要说的实例( huart1 )了

接下来我们看看这个实例的结构体( UART_HandleTypeDef )长什么样子

/**  * @brief  UART handle Structure definition  */typedef struct __UART_HandleTypeDef{
USART_TypeDef *Instance; /*!< UART registers base address */ UART_InitTypeDef Init; /*!< UART communication parameters */ uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */ uint16_t TxXferSize; /*!< UART Tx Transfer size */ __IO uint16_t TxXferCount; /*!< UART Tx Transfer Counter */ uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */ uint16_t RxXferSize; /*!< UART Rx Transfer size */ __IO uint16_t RxXferCount; /*!< UART Rx Transfer Counter */ DMA_HandleTypeDef *hdmatx; /*!< UART Tx DMA Handle parameters */ DMA_HandleTypeDef *hdmarx; /*!< UART Rx DMA Handle parameters */ HAL_LockTypeDef Lock; /*!< Locking object */ __IO HAL_UART_StateTypeDef gState; __IO HAL_UART_StateTypeDef RxState; __IO uint32_t ErrorCode; /*!< UART Error code */ #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Tx Half Complete Callback */ void (* TxCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Tx Complete Callback */ void (* RxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Rx Half Complete Callback */ void (* RxCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Rx Complete Callback */ void (* ErrorCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Error Callback */ void (* AbortCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort CompleCallback */ void (* AbortTransmitCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Transmit Complete Callback */ void (* AbortReceiveCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Receive Complete Callback */ void (* WakeupCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Wakeup Callback */ void (* MspInitCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Msp Init callback*/ void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Msp DeInitcallback*/#endif /* USE_HAL_UART_REGISTER_CALLBACKS */} UART_HandleTypeDef;

​ 我们可以看到在这个结构体里看到,它需要串口基地址,串口初始化所需要的值,以及数据接收和发送所需要的指针,和当前状态标志位。

二. 串口实例初始化

​ 我们可以在串口初始化的函数(MX_USART1_UART_Init )中,找到我们自己在STM32CubeMX 的图形界面中为串口配置的值,如下

huart1.Init.BaudRate = 115200;  huart1.Init.WordLength = UART_WORDLENGTH_8B;  huart1.Init.StopBits = UART_STOPBITS_1;  huart1.Init.Parity = UART_PARITY_NONE;  huart1.Init.Mode = UART_MODE_TX_RX;  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;  huart1.Init.OverSampling = UART_OVERSAMPLING_16;

​ 这不是真正的初始化(因为还没操作到我们之前所说的寄存器),它只是用来记录需要初始化的值

​ 而真正的初始化这些参数,是在 HAL_UART_Init 中的 UART_SetConfig 函数中,如下代码所示

/**  * @brief  Configures the UART peripheral.  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains  *                the configuration information for the specified UART module.  * @retval None  */static void UART_SetConfig(UART_HandleTypeDef *huart){
uint32_t tmpreg; uint32_t pclk; /* Check the parameters */ assert_param(IS_UART_BAUDRATE(huart->Init.BaudRate)); assert_param(IS_UART_STOPBITS(huart->Init.StopBits)); assert_param(IS_UART_PARITY(huart->Init.Parity)); assert_param(IS_UART_MODE(huart->Init.Mode)); /*-------------------------- USART CR2 Configuration -----------------------*/ /* Configure the UART Stop Bits: Set STOP[13:12] bits according to huart->Init.StopBits value */ MODIFY_REG(huart->Instance->CR2, USART_CR2_STOP, huart->Init.StopBits); /*-------------------------- USART CR1 Configuration -----------------------*/ /* Configure the UART Word Length, Parity and mode: Set the M bits according to huart->Init.WordLength value Set PCE and PS bits according to huart->Init.Parity value Set TE and RE bits according to huart->Init.Mode value Set OVER8 bit according to huart->Init.OverSampling value */ tmpreg = (uint32_t)huart->Init.WordLength | huart->Init.Parity | huart->Init.Mode | huart->Init.OverSampling; MODIFY_REG(huart->Instance->CR1, (uint32_t)(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS | USART_CR1_TE | USART_CR1_RE | USART_CR1_OVER8), tmpreg); /*-------------------------- USART CR3 Configuration -----------------------*/ /* Configure the UART HFC: Set CTSE and RTSE bits according to huart->Init.HwFlowCtl value */ MODIFY_REG(huart->Instance->CR3, (USART_CR3_RTSE | USART_CR3_CTSE), huart->Init.HwFlowCtl); /* Check the Over Sampling */ if (huart->Init.OverSampling == UART_OVERSAMPLING_8) {
/*-------------------------- USART BRR Configuration ---------------------*/#if defined(USART6) && defined(UART9) && defined(UART10) if ((huart->Instance == USART1) || (huart->Instance == USART6) || (huart->Instance == UART9) || (huart->Instance == UART10)) {
pclk = HAL_RCC_GetPCLK2Freq(); huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate); }#elif defined(USART6) if ((huart->Instance == USART1) || (huart->Instance == USART6)) {
pclk = HAL_RCC_GetPCLK2Freq(); huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate); }#else if (huart->Instance == USART1) {
pclk = HAL_RCC_GetPCLK2Freq(); huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate); }#endif /* USART6 */ else {
pclk = HAL_RCC_GetPCLK1Freq(); huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate); } } else {
/*-------------------------- USART BRR Configuration ---------------------*/#if defined(USART6) && defined(UART9) && defined(UART10) if ((huart->Instance == USART1) || (huart->Instance == USART6) || (huart->Instance == UART9) || (huart->Instance == UART10)) {
pclk = HAL_RCC_GetPCLK2Freq(); huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate); }#elif defined(USART6) if ((huart->Instance == USART1) || (huart->Instance == USART6)) {
pclk = HAL_RCC_GetPCLK2Freq(); huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate); }#else if (huart->Instance == USART1) {
pclk = HAL_RCC_GetPCLK2Freq(); huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate); }#endif /* USART6 */ else {
pclk = HAL_RCC_GetPCLK1Freq(); huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate); } }}/** * @} */

它真正操作到我们所说那些串口相关的寄存器(CR1, CR2, CR3, BRR)的配置,这里没看到 (SR,DR),因为它在数据的发送和接收时用到了。

三. 串口数据的发送(阻塞模式)

接下来就是对数据发送进行说明,先放出函数

/**  * @brief  Sends an amount of data in blocking mode.  * @note   When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),  *         the sent data is handled as a set of u16. In this case, Size must indicate the number  *         of u16 provided through pData.  * @param  huart Pointer to a UART_HandleTypeDef structure that contains  *               the configuration information for the specified UART module.  * @param  pData Pointer to data buffer (u8 or u16 data elements).  * @param  Size  Amount of data elements (u8 or u16) to be sent  * @param  Timeout Timeout duration  * @retval HAL status  */HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout){
uint16_t *tmp; uint32_t tickstart = 0U; /* Check that a Tx process is not already ongoing */ if (huart->gState == HAL_UART_STATE_READY) {
if ((pData == NULL) || (Size == 0U)) {
return HAL_ERROR; } /* Process Locked */ __HAL_LOCK(huart); huart->ErrorCode = HAL_UART_ERROR_NONE; huart->gState = HAL_UART_STATE_BUSY_TX; /* Init tickstart for timeout managment */ tickstart = HAL_GetTick(); huart->TxXferSize = Size; huart->TxXferCount = Size; /* Process Unlocked */ __HAL_UNLOCK(huart); while (huart->TxXferCount > 0U) {
huart->TxXferCount--; if (huart->Init.WordLength == UART_WORDLENGTH_9B) {
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK) {
return HAL_TIMEOUT; } tmp = (uint16_t *) pData; huart->Instance->DR = (*tmp & (uint16_t)0x01FF); if (huart->Init.Parity == UART_PARITY_NONE) {
pData += 2U; } else {
pData += 1U; } } else {
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK) {
return HAL_TIMEOUT; } huart->Instance->DR = (*pData++ & (uint8_t)0xFF); } } if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK) {
return HAL_TIMEOUT; } /* At end of Tx process, restore huart->gState to Ready */ huart->gState = HAL_UART_STATE_READY; return HAL_OK; } else {
return HAL_BUSY; }}
  • 进来该函数 检查当前串口的状态,若处于准备状态,将串口状态设置为 huart->gState = HAL_UART_STATE_BUSY_TX; 这个其实是为了对串口做保护,防止同时使用串口,导致数据发送错误

  • huart->TxXferCount = Size这变量记录当前还需要 发送的数据个数,在 while 中不断进行判断,如果需要发送数据就通过下面函数查询 SR 寄存器中的状态,超过指定时间(Timeout )就是发送失败了,返回超时。

    if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK) {
    return HAL_TIMEOUT; }//查询 UART_FLAG_TXE 标志位
  • 如果没有超时,就通过下面代码进行发送,pData 指针 指向我们想要发送的数据的首地址

    huart->Instance->DR = (*pData++ & (uint8_t)0xFF);

    总结

    ​ 该函数就是一直在查询是否是发送状态,如果可以发送就发送,不能发送,就等到Timeout 结束,返回超时

四. 串口数据接收

接下来就是对数据接收进行说明,再放出函数

/**  * @brief  Receives an amount of data in blocking mode.  * @note   When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),  *         the received data is handled as a set of u16. In this case, Size must indicate the number  *         of u16 available through pData.  * @param  huart Pointer to a UART_HandleTypeDef structure that contains  *               the configuration information for the specified UART module.  * @param  pData Pointer to data buffer (u8 or u16 data elements).  * @param  Size  Amount of data elements (u8 or u16) to be received.  * @param  Timeout Timeout duration  * @retval HAL status  */HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout){
uint16_t *tmp; uint32_t tickstart = 0U; /* Check that a Rx process is not already ongoing */ if (huart->RxState == HAL_UART_STATE_READY) {
if ((pData == NULL) || (Size == 0U)) {
return HAL_ERROR; } /* Process Locked */ __HAL_LOCK(huart); huart->ErrorCode = HAL_UART_ERROR_NONE; huart->RxState = HAL_UART_STATE_BUSY_RX; /* Init tickstart for timeout managment */ tickstart = HAL_GetTick(); huart->RxXferSize = Size; huart->RxXferCount = Size; /* Process Unlocked */ __HAL_UNLOCK(huart); /* Check the remain data to be received */ while (huart->RxXferCount > 0U) {
huart->RxXferCount--; if (huart->Init.WordLength == UART_WORDLENGTH_9B) {
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK) {
return HAL_TIMEOUT; } tmp = (uint16_t *) pData; if (huart->Init.Parity == UART_PARITY_NONE) {
*tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF); pData += 2U; } else {
*tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF); pData += 1U; } } else {
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK) {
return HAL_TIMEOUT; } if (huart->Init.Parity == UART_PARITY_NONE) {
*pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF); } else {
*pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F); } } } /* At end of Rx process, restore huart->RxState to Ready */ huart->RxState = HAL_UART_STATE_READY; return HAL_OK; } else {
return HAL_BUSY; }}
  • 进来该函数 检查当前串口的状态,若处于准备状态,将串口状态设置为 huart->gState = HAL_UART_STATE_BUSY_RX; 这也是防止同时使用串口,导致数据接收错误

  • huart->TxXferCount = Size这变量记录当前还需要 接收的数据个数,在 while 中不断进行判断,如果需要接收数据就通过下面函数查询 SR 寄存器中的状态,超过指定时间(Timeout )就是接收失败了,返回超时。

    if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK) {
    return HAL_TIMEOUT; }//查询 UART_FLAG_RXNE 标志位
  • 如果没有超时,就通过下面代码进行数据接收,pData 指针 指向我们想要缓存数据的首地址(一般为数组首地址),将 DR 中的数据读取出来。

    *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);

    总结

    ​ 该函数就是一直在查询是否是接收状态,如果可以接收就接收,不能接收,就等到Timeout 结束,返回超时,值得注意的是 这里如果没有接收到足够的数据也会返回超时,比如你要接收10个,如果只接收到了 9个 它也会返回超时,可以通过计算获取到底接收到几个。

    当接收超时时,计算接收到的个数 = huart1.RxXferSize - huart1.RxXferCount - 1

    全文总结

    • 你会知道如何通过结构体去操作寄存器
    • 你会知道 HAL 库如何封装串口
    • 对串口寄存器有整体的了解
    • …等等知识

后续还会继续分享串口的其他基础知识和使用,感兴趣的小伙伴记得关注我!

-----------------------------------------------结束--------------------------------------------------------
文章有价值,请各位看官点个赞,关注我或者点右边打个赏吧!

转载地址:http://numvn.baihongyu.com/

你可能感兴趣的文章
Java集合 ArrayList原理
查看>>
Git的基本操作
查看>>
简述128陷阱
查看>>
在spring boot项目中修改包名要注意的一些问题
查看>>
编写类实现从后台向前台返回所要求的数据
查看>>
spring boot的学习(1.创建一个初始的spring boot项目)
查看>>
Python的入门学习
查看>>
⑤mpvue 小程序框架 :初始化项目 分析项目结构
查看>>
⑦mpvue Flyio实现前后台交互
查看>>
操作系统:Java模拟CPU调度算法(非抢占短进程优先、可抢占优先权调度、多级反馈队列调度)
查看>>
【前端】在页面中还原英雄联盟客户端?
查看>>
【前端】Vue 纯干货 基础知识分享!
查看>>
3.1servlet入门和MVC模型
查看>>
3.2servlet功能和会话技术
查看>>
泛型详解
查看>>
集合案例:斗地主
查看>>
软件测试进阶篇
查看>>
二叉搜索树的实现
查看>>
连续最大和
查看>>
不要二题目
查看>>