• STM32 HAL库F103系列之ADC实验(二)


    多通道ADC采集(DMA读取)实验

    这个实验与单通道ADC(DMA读取)实验大致相同

    实验简要:

    1,功能描述   通过DMA读取数据

            通过ADC1通道0/1/2/3/4/5PA0/1/2/3/4/5)采集测试电压,并显示ADC转换的数字量及换算后的电压值

    2,确定最小刻度

            VREF+ = 3.3V  0VVIN3.3V  最小刻度 = 3.3 / 4096          F4/F7/H7系列还需要考虑ADC分辨率

    3,确定转换时间

            采样时间239.5ADC时钟周期为例,可以得到转换时间为21us

    4,模式组合

            连续转换模式、使用扫描模式

    源码

    adc.c

    1. #include "./BSP/ADC/adc.h"
    2. DMA_HandleTypeDef g_dma_nch_adc_handle;
    3. ADC_HandleTypeDef g_adc_nch_dma_handle;
    4. uint8_t g_adc_dma_sta;
    5. /* ADC N通道(6通道) DMA读取 初始化函数 */
    6. void adc_nch_dma_init(uint32_t mar)
    7. {
    8. ADC_ChannelConfTypeDef adc_ch_conf;
    9. __HAL_RCC_DMA1_CLK_ENABLE();
    10. g_dma_nch_adc_handle.Instance = DMA1_Channel1;
    11. g_dma_nch_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
    12. g_dma_nch_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;
    13. g_dma_nch_adc_handle.Init.MemInc = DMA_MINC_ENABLE;
    14. g_dma_nch_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    15. g_dma_nch_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    16. g_dma_nch_adc_handle.Init.Mode = DMA_NORMAL;
    17. g_dma_nch_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;
    18. HAL_DMA_Init(&g_dma_nch_adc_handle);
    19. __HAL_LINKDMA(&g_adc_nch_dma_handle, DMA_Handle, g_dma_nch_adc_handle);
    20. g_adc_nch_dma_handle.Instance = ADC1;
    21. g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    22. g_adc_nch_dma_handle.Init.ScanConvMode = ADC_SCAN_ENABLE;
    23. g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE;
    24. g_adc_nch_dma_handle.Init.NbrOfConversion = 6;
    25. g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE;
    26. g_adc_nch_dma_handle.Init.NbrOfDiscConversion = 0;
    27. g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    28. HAL_ADC_Init(&g_adc_nch_dma_handle);
    29. HAL_ADCEx_Calibration_Start(&g_adc_nch_dma_handle);
    30. adc_ch_conf.Channel = ADC_CHANNEL_0;
    31. adc_ch_conf.Rank = ADC_REGULAR_RANK_1;
    32. adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
    33. HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);
    34. adc_ch_conf.Channel = ADC_CHANNEL_1;
    35. adc_ch_conf.Rank = ADC_REGULAR_RANK_2;
    36. HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);
    37. adc_ch_conf.Channel = ADC_CHANNEL_2;
    38. adc_ch_conf.Rank = ADC_REGULAR_RANK_3;
    39. HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);
    40. adc_ch_conf.Channel = ADC_CHANNEL_3;
    41. adc_ch_conf.Rank = ADC_REGULAR_RANK_4;
    42. HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);
    43. adc_ch_conf.Channel = ADC_CHANNEL_4;
    44. adc_ch_conf.Rank = ADC_REGULAR_RANK_5;
    45. HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);
    46. adc_ch_conf.Channel = ADC_CHANNEL_5;
    47. adc_ch_conf.Rank = ADC_REGULAR_RANK_6;
    48. HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);
    49. HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);
    50. HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
    51. HAL_DMA_Start_IT(&g_dma_nch_adc_handle, (uint32_t)&ADC1->DR, mar, 0);
    52. HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, &mar ,0);
    53. }
    54. /* ADC MSP初始化函数 */
    55. void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
    56. {
    57. if(hadc->Instance == ADC1)
    58. {
    59. GPIO_InitTypeDef gpio_init_struct;
    60. RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
    61. __HAL_RCC_GPIOA_CLK_ENABLE();
    62. __HAL_RCC_ADC1_CLK_ENABLE();
    63. gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
    64. gpio_init_struct.Mode = GPIO_MODE_ANALOG;
    65. HAL_GPIO_Init(GPIOA, &gpio_init_struct);
    66. adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
    67. adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
    68. HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
    69. }
    70. }
    71. /* 使能一次ADC DMA传输函数 */
    72. void adc_dma_enable(uint16_t cndtr)
    73. {
    74. ADC1->CR2 &= ~(1 << 0);
    75. DMA1_Channel1->CCR &= ~(1 << 0);
    76. while (DMA1_Channel1->CCR & (1 << 0));
    77. DMA1_Channel1->CNDTR = cndtr;
    78. DMA1_Channel1->CCR |= 1 << 0;
    79. ADC1->CR2 |= 1 << 0;
    80. ADC1->CR2 |= 1 << 22;
    81. }
    82. /* ADC DMA采集中断服务函数 */
    83. void DMA1_Channel1_IRQHandler(void)
    84. {
    85. if (DMA1->ISR & (1<<1))
    86. {
    87. g_adc_dma_sta = 1;
    88. DMA1->IFCR |= 1 << 1;
    89. }
    90. }

    adc.h

    1. #ifndef __ADC_H
    2. #define __ADC_H
    3. #include "./SYSTEM/sys/sys.h"
    4. void adc_nch_dma_init(uint32_t mar);
    5. void adc_dma_enable(uint16_t cndtr);
    6. #endif

    main.c

    1. #include "./SYSTEM/sys/sys.h"
    2. #include "./SYSTEM/usart/usart.h"
    3. #include "./SYSTEM/delay/delay.h"
    4. #include "./BSP/LED/led.h"
    5. #include "./BSP/LCD/lcd.h"
    6. #include "./BSP/ADC/adc.h"
    7. #define ADC_DMA_BUF_SIZE 50 * 6 /* ADC DMA采集 BUF大小, 应等于ADC通道数的整数倍 */
    8. uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMA BUF */
    9. extern uint8_t g_adc_dma_sta; /* DMA传输状态标志, 0,未完成; 1, 已完成 */
    10. int main(void)
    11. {
    12. uint16_t i,j;
    13. uint16_t adcx;
    14. uint32_t sum;
    15. float temp;
    16. HAL_Init(); /* 初始化HAL库 */
    17. sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    18. delay_init(72); /* 延时初始化 */
    19. usart_init(115200); /* 串口初始化为115200 */
    20. led_init(); /* 初始化LED */
    21. lcd_init(); /* 初始化LCD */
    22. adc_nch_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
    23. lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    24. lcd_show_string(30, 70, 200, 16, 16, "ADC 6CH DMA TEST", RED);
    25. lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    26. lcd_show_string(30, 110, 200, 12, 12, "ADC1_CH0_VAL:", BLUE);
    27. lcd_show_string(30, 122, 200, 12, 12, "ADC1_CH0_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
    28. lcd_show_string(30, 140, 200, 12, 12, "ADC1_CH1_VAL:", BLUE);
    29. lcd_show_string(30, 152, 200, 12, 12, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
    30. lcd_show_string(30, 170, 200, 12, 12, "ADC1_CH2_VAL:", BLUE);
    31. lcd_show_string(30, 182, 200, 12, 12, "ADC1_CH2_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
    32. lcd_show_string(30, 200, 200, 12, 12, "ADC1_CH3_VAL:", BLUE);
    33. lcd_show_string(30, 212, 200, 12, 12, "ADC1_CH3_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
    34. lcd_show_string(30, 230, 200, 12, 12, "ADC1_CH4_VAL:", BLUE);
    35. lcd_show_string(30, 242, 200, 12, 12, "ADC1_CH4_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
    36. lcd_show_string(30, 260, 200, 12, 12, "ADC1_CH5_VAL:", BLUE);
    37. lcd_show_string(30, 272, 200, 12, 12, "ADC1_CH5_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
    38. adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */
    39. while (1)
    40. {
    41. if (g_adc_dma_sta == 1)
    42. {
    43. /* 循环显示通道0~通道5的结果 */
    44. for(j = 0; j < 6; j++) /* 遍历6个通道 */
    45. {
    46. sum = 0; /* 清零 */
    47. for (i = 0; i < ADC_DMA_BUF_SIZE / 6; i++) /* 每个通道采集了50次数据,进行50次累加 */
    48. {
    49. sum += g_adc_dma_buf[(6 * i) + j]; /* 相同通道的转换数据累加 */
    50. }
    51. adcx = sum / (ADC_DMA_BUF_SIZE / 6); /* 取平均值 */
    52. /* 显示结果 */
    53. lcd_show_xnum(108, 110 + (j * 30), adcx, 4, 12, 0, BLUE); /* 显示ADC采样后的原始值 */
    54. temp = (float)adcx * (3.3 / 4096); /* 获取计算后的带小数的实际电压值,比如3.1111 */
    55. adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
    56. lcd_show_xnum(108, 122 + (j * 30), adcx, 1, 12, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
    57. temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
    58. temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
    59. lcd_show_xnum(120, 122 + (j * 30), temp, 3, 12, 0X80, BLUE);/* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
    60. }
    61. g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */
    62. adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */
    63. }
    64. LED0_TOGGLE();
    65. delay_ms(100);
    66. }
    67. }

    单通道ADC过采样实验

    这个实验与单通道ADC(DMA读取)实验大致相同

    如何用过采样和求平均值的方式提高ADC的分辨率?

    1)如何确定过采样率

            根据要增加的分辨率位数计算过采样频率方程:

                         

    2)如何求均值

            举个例子:12位分辨率的ADC提高4位分辨率,采样频率就要提高256

            即需要256次采集才能得到一次16位分辨率的数据

            然后将这256次采集结果求和,求和的结果再右移4位,就得到提高分辨率后的结果

            注意:提高N 位分辨率,需要 右移N

    实验简要:

    1,功能描述        通过DMA读取数据

            通过ADC1通道1PA1)过采样实现16位分辨率采集电压,并显示ADC转换的数字量及换算后的电压值

    2,确定最小刻度

            VREF+ = 3.3V  0VVIN3.3V  最小刻度 = 3.3 / 4096          F4/F7/H7系列还需要考虑ADC分辨率

    3,确定转换时间

            采样时间设置为最小值1.5ADC时钟周期,可以得到转换时间为1.17us * 256

    4,模式组合

            连续转换模式、不使用扫描模式

    源码

    adc.c

    1. #include "./BSP/ADC/adc.h"
    2. DMA_HandleTypeDef g_dma_adc_handle;
    3. ADC_HandleTypeDef g_adc_dma_handle;
    4. uint8_t g_adc_dma_sta;
    5. /* ADC DMA读取 初始化函数 */
    6. void adc_dma_init(uint32_t mar)
    7. {
    8. ADC_ChannelConfTypeDef adc_ch_conf;
    9. __HAL_RCC_DMA1_CLK_ENABLE();
    10. g_dma_adc_handle.Instance = DMA1_Channel1;
    11. g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
    12. g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;
    13. g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE;
    14. g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    15. g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    16. g_dma_adc_handle.Init.Mode = DMA_NORMAL;
    17. g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;
    18. HAL_DMA_Init(&g_dma_adc_handle);
    19. __HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle);
    20. g_adc_dma_handle.Instance = ADC1;
    21. g_adc_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    22. g_adc_dma_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
    23. g_adc_dma_handle.Init.ContinuousConvMode = ENABLE;
    24. g_adc_dma_handle.Init.NbrOfConversion = 1;
    25. g_adc_dma_handle.Init.DiscontinuousConvMode = DISABLE;
    26. g_adc_dma_handle.Init.NbrOfDiscConversion = 0;
    27. g_adc_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    28. HAL_ADC_Init(&g_adc_dma_handle);
    29. HAL_ADCEx_Calibration_Start(&g_adc_dma_handle);
    30. adc_ch_conf.Channel = ADC_CHANNEL_1;
    31. adc_ch_conf.Rank = ADC_REGULAR_RANK_1;
    32. adc_ch_conf.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
    33. HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf);
    34. HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);
    35. HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
    36. HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);
    37. HAL_ADC_Start_DMA(&g_adc_dma_handle, &mar ,0);
    38. }
    39. /* ADC MSP初始化函数 */
    40. void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
    41. {
    42. if(hadc->Instance == ADC1)
    43. {
    44. GPIO_InitTypeDef gpio_init_struct;
    45. RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
    46. __HAL_RCC_GPIOA_CLK_ENABLE();
    47. __HAL_RCC_ADC1_CLK_ENABLE();
    48. gpio_init_struct.Pin = GPIO_PIN_1;
    49. gpio_init_struct.Mode = GPIO_MODE_ANALOG;
    50. HAL_GPIO_Init(GPIOA, &gpio_init_struct);
    51. adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
    52. adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
    53. HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
    54. }
    55. }
    56. /* 使能一次ADC DMA传输函数 */
    57. void adc_dma_enable(uint16_t cndtr)
    58. {
    59. ADC1->CR2 &= ~(1 << 0);
    60. DMA1_Channel1->CCR &= ~(1 << 0);
    61. while (DMA1_Channel1->CCR & (1 << 0));
    62. DMA1_Channel1->CNDTR = cndtr;
    63. DMA1_Channel1->CCR |= 1 << 0;
    64. ADC1->CR2 |= 1 << 0;
    65. ADC1->CR2 |= 1 << 22;
    66. // __HAL_ADC_DISABLE(&g_adc_dma_handle);
    67. //
    68. // __HAL_DMA_DISABLE(&g_dma_adc_handle);
    69. // while (__HAL_DMA_GET_FLAG(&g_dma_adc_handle, __HAL_DMA_GET_TC_FLAG_INDEX(&g_dma_adc_handle)));
    70. // DMA1_Channel1->CNDTR = cndtr;
    71. // __HAL_DMA_ENABLE(&g_dma_adc_handle);
    72. //
    73. // __HAL_ADC_ENABLE(&g_adc_dma_handle);
    74. // HAL_ADC_Start(&g_adc_dma_handle);
    75. }
    76. /* ADC DMA采集中断服务函数 */
    77. void DMA1_Channel1_IRQHandler(void)
    78. {
    79. if (DMA1->ISR & (1<<1))
    80. {
    81. g_adc_dma_sta = 1;
    82. DMA1->IFCR |= 1 << 1;
    83. }
    84. }

    adc.h

    1. #ifndef __ADC_H
    2. #define __ADC_H
    3. #include "./SYSTEM/sys/sys.h"
    4. void adc_dma_init(uint32_t mar);
    5. void adc_dma_enable(uint16_t cndtr);
    6. #endif

    main.c

    1. #include "./SYSTEM/sys/sys.h"
    2. #include "./SYSTEM/usart/usart.h"
    3. #include "./SYSTEM/delay/delay.h"
    4. #include "./BSP/LED/led.h"
    5. #include "./BSP/LCD/lcd.h"
    6. #include "./BSP/ADC/adc.h"
    7. /* ADC过采样技术, 是利用ADC多次采集的方式, 来提高ADC精度, 采样速度每提高4倍
    8. * 采样精度提高 1bit, 同时, ADC采样速度降低4倍, 如提高4bit精度, 需要256次采集
    9. * 才能得出1次数据, 相当于ADC速度慢了256倍. 理论上只要ADC足够快, 我们可以无限
    10. * 提高ADC精度, 但实际上ADC并不是无限快的, 而且由于ADC性能限制, 并不是位数无限
    11. * 提高结果就越好, 需要根据自己的实际需求和ADC的实际性能来权衡.
    12. */
    13. #define ADC_OVERSAMPLE_TIMES 256 /* ADC过采样次数, 这里提高4bit分辨率, 需要256倍采样 */
    14. #define ADC_DMA_BUF_SIZE ADC_OVERSAMPLE_TIMES * 10 /* ADC DMA采集 BUF大小, 应等于过采样次数的整数倍 */
    15. uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMA BUF */
    16. extern uint8_t g_adc_dma_sta; /* DMA传输状态标志, 0,未完成; 1, 已完成 */
    17. int main(void)
    18. {
    19. uint16_t i;
    20. uint32_t adcx;
    21. uint32_t sum;
    22. float temp;
    23. HAL_Init(); /* 初始化HAL库 */
    24. sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    25. delay_init(72); /* 延时初始化 */
    26. usart_init(115200); /* 串口初始化为115200 */
    27. led_init(); /* 初始化LED */
    28. lcd_init(); /* 初始化LCD */
    29. adc_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
    30. lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    31. lcd_show_string(30, 70, 200, 16, 16, "ADC OverSample TEST", RED);
    32. lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    33. lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH1_VAL:", BLUE);
    34. lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
    35. adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */
    36. while (1)
    37. {
    38. if (g_adc_dma_sta == 1)
    39. {
    40. /* 计算DMA 采集到的ADC数据的平均值 */
    41. sum = 0;
    42. for (i = 0; i < ADC_DMA_BUF_SIZE; i++) /* 累加 */
    43. {
    44. sum += g_adc_dma_buf[i];
    45. }
    46. adcx = sum / (ADC_DMA_BUF_SIZE / ADC_OVERSAMPLE_TIMES); /* 取平均值 */
    47. adcx >>= 4; /* 除以2^4倍, 得到12+4位 ADC精度值, 注意: 提高 N bit精度, 需要 >> N */
    48. /* 显示结果 */
    49. lcd_show_xnum(134, 110, adcx, 5, 16, 0, BLUE); /* 显示ADC采样后的原始值 */
    50. temp = (float)adcx * (3.3 / 65536); /* 获取计算后的带小数的实际电压值,比如3.1111 */
    51. adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
    52. lcd_show_xnum(134, 130, adcx, 1, 16, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
    53. temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
    54. temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
    55. lcd_show_xnum(150, 130, temp, 3, 16, 0X80, BLUE); /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
    56. g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */
    57. adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */
    58. }
    59. LED0_TOGGLE();
    60. delay_ms(100);
    61. }
    62. }

  • 相关阅读:
    基于ssm企业人事管理系统
    Git:起步 - 关于版本控制
    ZenCart 如何设置多个地区多个运费
    LeetCode 第 388 场周赛个人题解
    js实现图片加水印
    MySQL第六讲·where和having的异同?
    SpringBoot静态资源路径问题、拦截器基础配置
    面试题:Kafka 为什么会丢消息?
    OTA设计思路
    基于儿童绘画发展优化算法的函数寻优算法
  • 原文地址:https://blog.csdn.net/2302_79878697/article/details/137928808