• 【STM32H7教程】第46章 STM32H7的ADC应用之DMA方式多通道采样


    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

    第46章       STM32H7的ADC应用之DMA方式多通道采样

    本章教程为大家讲解ADC+DMA方式的多通道数据采集,实际项目中有一定的使用价值,使用一路ADC就可以采集多个通道的数据。

    46.1 初学者重要提示

    46.2 ADC稳压基准硬件设计

    46.3 ADC驱动设计

    46.4 ADC板级支持包(bsp_adc.c)

    46.5 ADC驱动移植和使用

    46.6 实验例程设计框架

    46.7 实验例程说明(MDK)

    46.8 实验例程说明(IAR)

    46.9 总结

    46.1 初学者重要提示

    1.   学习本章节前,务必优先学习第44章,需要对ADC的基础知识和HAL库的几个常用API有个认识。
    2.   开发板右上角有个跳线帽,可以让ADC的稳压基准接3.3V或者2.5V,本章例子是接到3.3V。
    3.   STM32H7的ADC支持偏移校准和线性度校准。如果使用线性度校准的话,特别要注意此贴的问题:http://www.armbbs.cn/forum.php?mod=viewthread&tid=91436
    4.   ADC的专业术语诠释文档,推荐大家看看:http://www.armbbs.cn/forum.php?mod=viewthread&tid=89414
    5.   STM32H7的ADC多通道并不是同步采样的,本质上是通过内部的多路选择器不断切换实现的,一个采集完毕了才会采集另一个。

    46.2 ADC稳压基准硬件设计

    注:学习前务必优先看第14章的2.1小节,对电源供电框架有个了解。

    ADC要采集的准确,就需要有一个稳定的稳压基准源,V7开发板使用的LM285D-2.5,即2.5V的基准源。硬件设计如下:

     

    关于这个原理图要注意以下问题:

    LM285D-2.5输出的是2.5V的稳压基准,原理图这里做了一个特别的处理,同时接了一个上拉电阻到VDDA(3.3V),然后用户可以使用开发板右上角的跳线帽设置Vref选择3.3V稳压还是2.5V稳压。

     

    下面再来了解下LM285的电气特性:

     

    通过这个表,我们要了解以下几点知识:

    1.   LM285的典型值是2.5V,支持的最小值2.462V,最大值2.538V。工作电流是20uA到20mA,温飘是±20ppm/℃
    2.   Iz是Reference current参考电流的意思:
      •   参考电流是20uA到1mA,温度25℃,参考电压最大变化1mV。
      •   参考电流是20uA到1mA,全范围温度(−40°C to 85°C),参考电压最大变化1.5mV。
      •   参考电流是1mA到20mA,温度25℃,参考电压最大变化10mV。
      •   参考电流是1mA到20mA,全范围温度(−40°C to 85°C),参考电压最大变化30mV。

    那么问题来了,V7开发板上LM285的参考电流是多少? 简单计算就是:

    (VDDA – 2.5V) /  1K  =(3.3 – 2.5V) / 1K = 0.8mA。

    46.3 ADC驱动设计

    ADC做DMA数据传输的实现思路框图如下:

     

    下面将程序设计中的相关问题逐一为大家做个说明。

    46.3.1 ADC软件触发  

    ADC转换既可以选择外部触发也可以选择软件触发。我们这里选择的是软件触发方式的多通道转换,即连续转换序列,软件触发。对应的时序如下(在第44章的2.7小节有详细讲解软件触发和硬件触发的时序):。

    ADSTART表示软件启动转换。

    ADSTP表示停止转换。

    EOC表示一个通道转换结束。

    EOS表示所有通道转换结束。

     

    关于这个时序图的解读:

    •   配置为连续转换的话,软件启动ADSTART会开启所有通道转换,全部转换完毕后,继续进行下一轮转换。调用了停止转换ADSTP后,会停止转换。
    •   每个通过转换完毕有个EOC标志,所有通道转换完毕有个EOS标志。

    46.3.2 ADC时钟源选择

    根据第44章2.2小节的讲解,我们知道ADC有两种时钟源可供选择,可以使用来自AHB总线的系统时钟,也可以使用PLL2,PLL3,HSE,HSI或者CSI时钟。

    如果采用AHB时钟,不需要做专门的配置,而采用PLL2,PLL3时钟需要特别的配置,下面是使用AHB或者PLL2时钟的配置。

    •   通过宏定义设置选择的时钟源

    使用哪个时钟源,将另一个注释掉即可:

    /* 选择ADC的时钟源 */
    #define ADC_CLOCK_SOURCE_AHB     /* 选择AHB时钟源 */
    //#define ADC_CLOCK_SOURCE_PLL   /* 选择PLL时钟源 */
    •   PLL2或者AHB时钟源配置
    #if defined (ADC_CLOCK_SOURCE_PLL)
        /* 配置PLL2时钟为的72MHz,方便分频产生ADC最高时钟36MHz */
        RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
        PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADC;
        PeriphClkInitStruct.PLL2.PLL2M = 25;
        PeriphClkInitStruct.PLL2.PLL2N = 504;
        PeriphClkInitStruct.PLL2.PLL2P = 7;
        PeriphClkInitStruct.PLL2.PLL2Q = 7;
        PeriphClkInitStruct.PLL2.PLL2R = 7;
        PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_0;
        PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE;
        PeriphClkInitStruct.PLL2.PLL2FRACN = 0;
        PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2;
        if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
        {
            Error_Handler(__FILE__, __LINE__);  
        }
    #elif defined (ADC_CLOCK_SOURCE_AHB)
      
      /* 使用AHB时钟的话,无需配置,默认选择*/
      
    #endif

    对于PLL2的时钟输出,直接使用STM32CubeMX里面的时钟树配置即可,效果如下:

     

    选择PLL2P输出作为ADC时钟源:

     

    •   ADC分频设置

    无论是使用AHB时钟还是PLL2时钟都支持分频设置:

     

    AHB支持下面三种分频设置:

    #define ADC_CLOCK_SYNC_PCLK_DIV1   ((uint32_t)ADC_CCR_CKMODE_0)  
    #define ADC_CLOCK_SYNC_PCLK_DIV2   ((uint32_t)ADC_CCR_CKMODE_1) 
    #define ADC_CLOCK_SYNC_PCLK_DIV4   ((uint32_t)ADC_CCR_CKMODE)   
    
    #define ADC_CLOCKPRESCALER_PCLK_DIV1   ADC_CLOCK_SYNC_PCLK_DIV1   /* 这三个仅仅是为了兼容,已经不推荐使用 */
    #define ADC_CLOCKPRESCALER_PCLK_DIV2   ADC_CLOCK_SYNC_PCLK_DIV2   
    #define ADC_CLOCKPRESCALER_PCLK_DIV4   ADC_CLOCK_SYNC_PCLK_DIV4    

    PLL2支持下面几种分频设置:

    #define ADC_CLOCK_ASYNC_DIV1       ((uint32_t)0x00000000)                                       
    #define ADC_CLOCK_ASYNC_DIV2       ((uint32_t)ADC_CCR_PRESC_0)                                  
    #define ADC_CLOCK_ASYNC_DIV4       ((uint32_t)ADC_CCR_PRESC_1)                                   
    #define ADC_CLOCK_ASYNC_DIV6       ((uint32_t)(ADC_CCR_PRESC_1|ADC_CCR_PRESC_0))                 
    #define ADC_CLOCK_ASYNC_DIV8       ((uint32_t)(ADC_CCR_PRESC_2))                                
    #define ADC_CLOCK_ASYNC_DIV10      ((uint32_t)(ADC_CCR_PRESC_2|ADC_CCR_PRESC_0))                 
    #define ADC_CLOCK_ASYNC_DIV12      ((uint32_t)(ADC_CCR_PRESC_2|ADC_CCR_PRESC_1))                 
    #define ADC_CLOCK_ASYNC_DIV16      ((uint32_t)(ADC_CCR_PRESC_2|ADC_CCR_PRESC_1|ADC_CCR_PRESC_0)) 
    #define ADC_CLOCK_ASYNC_DIV32      ((uint32_t)(ADC_CCR_PRESC_3))                                
    #define ADC_CLOCK_ASYNC_DIV64      ((uint32_t)(ADC_CCR_PRESC_3|ADC_CCR_PRESC_0))                 
    #define ADC_CLOCK_ASYNC_DIV128     ((uint32_t)(ADC_CCR_PRESC_3|ADC_CCR_PRESC_1))                
    #define ADC_CLOCK_ASYNC_DIV256     ((uint32_t)(ADC_CCR_PRESC_3|ADC_CCR_PRESC_1|ADC_CCR_PRESC_0)) 

    有了这些认识后再看实际的分频配置就好理解了:

    #if defined (ADC_CLOCK_SOURCE_PLL)
    /* 采用PLL异步时钟,2分频,即72MHz/2 = 36MHz */
        AdcHandle.Init.ClockPrescaler        = ADC_CLOCK_ASYNC_DIV2;     
    /* 采用AHB同步时钟,4分频,即200MHz/4 = 50MHz */     
    #elif defined (ADC_CLOCK_SOURCE_AHB)
        AdcHandle.Init.ClockPrescaler        = ADC_CLOCK_SYNC_PCLK_DIV4;      
    #endif

    46.3.3 ADC的DMA配置

    由于函数HAL_ADC_Start_DMA封装的DMA传输函数是HAL_DMA_Start_IT。而我们这里仅需要用到DMA传输,而用不到中断,所以不开启对应的NVIC即可,这里使用的是DMA1_Stream1,测量了PC0,Vbat/4,VrefInt和温度四个通道。

    1.    /*
    2.    ******************************************************************************************************
    3.    *    函 数 名: bsp_InitADC
    4.    *    功能说明: 初始化ADC,采用DMA方式进行多通道采样,采集了PC0, Vbat/4, VrefInt和温度
    5.    *    形    参: 无
    6.    *    返 回 值: 无
    7.    ******************************************************************************************************
    8.    */
    9.    void bsp_InitADC(void)
    10.    {
    11.        ADC_HandleTypeDef   AdcHandle = {0};
    12.        DMA_HandleTypeDef   DMA_Handle = {0};
    13.        ADC_ChannelConfTypeDef   sConfig = {0};
    14.        GPIO_InitTypeDef          GPIO_InitStruct;
    15.    
    16.      /* ## - 1 - 配置ADC采样的时钟 ####################################### */
    17.    #if defined (ADC_CLOCK_SOURCE_PLL)
    18.        /* 配置PLL2时钟为的72MHz,方便分频产生ADC最高时钟36MHz */
    19.         RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
    20.        PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADC;
    21.        PeriphClkInitStruct.PLL2.PLL2M = 25;
    22.        PeriphClkInitStruct.PLL2.PLL2N = 504;
    23.        PeriphClkInitStruct.PLL2.PLL2P = 7;
    24.        PeriphClkInitStruct.PLL2.PLL2Q = 7;
    25.        PeriphClkInitStruct.PLL2.PLL2R = 7;
    26.        PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_0;
    27.        PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE;
    28.        PeriphClkInitStruct.PLL2.PLL2FRACN = 0;
    29.        PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2;
    30.        if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
    31.        {
    32.            Error_Handler(__FILE__, __LINE__);  
    33.        }
    34.    #elif defined (ADC_CLOCK_SOURCE_AHB)
    35.      
    36.      /* 使用AHB时钟的话,无需配置,默认选择*/
    37.      
    38.    #endif
    39.    
    40.        /* ## - 2 - 配置ADC采样使用的时钟 ####################################### */
    41.        __HAL_RCC_GPIOC_CLK_ENABLE();
    42.    
    43.        GPIO_InitStruct.Pin = GPIO_PIN_0;
    44.        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    45.        GPIO_InitStruct.Pull = GPIO_NOPULL;
    46.        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
    47.      
    48.        /* ## - 3 - 配置ADC采样使用的时钟 ####################################### */
    49.        __HAL_RCC_DMA1_CLK_ENABLE();
    50.        DMA_Handle.Instance                 = DMA1_Stream1;            /* 使用的DMA1 Stream1 */
    51.        DMA_Handle.Init.Request             = DMA_REQUEST_ADC3;         /* 请求类型采用DMA_REQUEST_ADC3 */  
    52.        DMA_Handle.Init.Direction           = DMA_PERIPH_TO_MEMORY;    /* 传输方向是从外设到存储器*/  
    53.        DMA_Handle.Init.PeriphInc           = DMA_PINC_DISABLE;        /* 外设地址自增禁止 */ 
    54.        DMA_Handle.Init.MemInc              = DMA_MINC_ENABLE;         /* 存储器地址自增使能 */  
    55.        DMA_Handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; /* 外设数据位宽选择半字,即16bit */     
    56.        DMA_Handle.Init.MemDataAlignment    = DMA_MDATAALIGN_HALFWORD; /* 存储器数据位宽选择半字,即16bit */    
    57.        DMA_Handle.Init.Mode                = DMA_CIRCULAR;            /* 循环模式 */   
    58.        DMA_Handle.Init.Priority            = DMA_PRIORITY_LOW;        /* 优先级低 */  
    59.        DMA_Handle.Init.FIFOMode            = DMA_FIFOMODE_DISABLE;    /* 禁止FIFO*/
    60.        DMA_Handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* 禁止FIFO此位不起作用,用于设置阀值 */
    61.        DMA_Handle.Init.MemBurst   = DMA_MBURST_SINGLE;       /* 禁止FIFO此位不起作用,用于存储器突发 */
    62.        DMA_Handle.Init.PeriphBurst = DMA_PBURST_SINGLE;      /* 禁止FIFO此位不起作用,用于外设突发 */
    63.    
    64.        /* 初始化DMA */
    65.        if(HAL_DMA_Init(&DMA_Handle) != HAL_OK)
    66.        {
    67.            Error_Handler(__FILE__, __LINE__);     
    68.        }
    69.        
    70.        /* 关联ADC句柄和DMA句柄 */
    71.        __HAL_LINKDMA(&AdcHandle, DMA_Handle, DMA_Handle);
    72.        
    73.        
    74.        /* ## - 4 - 配置ADC ########################################################### */
    75.        __HAL_RCC_ADC3_CLK_ENABLE();
    76.        AdcHandle.Instance = ADC3;
    77.    
    78.    #if defined (ADC_CLOCK_SOURCE_PLL)
    79.        AdcHandle.Init.ClockPrescaler   = ADC_CLOCK_ASYNC_DIV8;   /* 采用PLL异步时钟,8分频,即72MHz/8
    80.                                                                          = 36MHz */
    81.    #elif defined (ADC_CLOCK_SOURCE_AHB)
    82.        AdcHandle.Init.ClockPrescaler   = ADC_CLOCK_SYNC_PCLK_DIV4; /* 采用AHB同步时钟,4分频,即200MHz/4
    83.                                                                        = 50MHz */
    84.    #endif
    85.        
    86.        AdcHandle.Init.Resolution            = ADC_RESOLUTION_16B;   /* 16位分辨率 */
    87.        AdcHandle.Init.ScanConvMode          = ADC_SCAN_ENABLE;      /* 禁止扫描,因为仅开了一个通道 */
    88.        AdcHandle.Init.EOCSelection          = ADC_EOC_SINGLE_CONV;  /* EOC转换结束标志 */
    89.        AdcHandle.Init.LowPowerAutoWait      = DISABLE;              /* 禁止低功耗自动延迟特性 */
    90.        AdcHandle.Init.ContinuousConvMode    = ENABLE;               /* 禁止自动转换,采用的软件触发 */
    91.        AdcHandle.Init.NbrOfConversion       = 4;                    /* 使用了4个转换通道 */
    92.        AdcHandle.Init.DiscontinuousConvMode = DISABLE;              /* 禁止不连续模式 */
    93.        AdcHandle.Init.NbrOfDiscConversion   = 1;   /* 禁止不连续模式后,此参数忽略,此位是用来配置不连续
    94.                                                        子组中通道数 */
    95.    
    96.        AdcHandle.Init.ExternalTrigConv      = ADC_SOFTWARE_START;              /* 采用软件触发 */
    97.        AdcHandle.Init.ExternalTrigConvEdge  = ADC_EXTERNALTRIGCONVEDGE_RISING; /* 软件触发,此位忽略 */
    98.        AdcHandle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR; /* DMA循环模式接收*/
    99.        AdcHandle.Init.BoostMode  = DISABLE;                /* ADC时钟低于20MHz的话,可以禁止boost */
    100.        AdcHandle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;  /* ADC转换溢出的话,覆盖ADC的数据寄存器 */
    101.        AdcHandle.Init.OversamplingMode      = DISABLE;     /* 禁止过采样 */
    102.    
    103.        /* 初始化ADC */
    104.        if (HAL_ADC_Init(&AdcHandle) != HAL_OK)
    105.        {
    106.            Error_Handler(__FILE__, __LINE__);
    107.        }
    108.      
    109.      
    110.        /* 校准ADC,采用偏移校准 */
    111.        if (HAL_ADCEx_Calibration_Start(&AdcHandle, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK)
    112.        {
    113.            Error_Handler(__FILE__, __LINE__);
    114.        }
    115.      
    116.        /* 配置ADC通道,序列1,采样PC0引脚 */
    117.        /*
    118.            采用PLL2时钟的话,ADCCLK = 72MHz / 8 = 9MHz
    119.            ADC采样速度,即转换时间 = 采样时间 + 逐次逼近时间
    120.                                    = 810.5 + 8.5(16bit)
    121.                                    = 820个ADC时钟周期
    122.            那么转换速度就是9MHz / 820 = 10975Hz
    123.        */
    124.        sConfig.Channel      = ADC_CHANNEL_10;              /* 配置使用的ADC通道 */
    125.        sConfig.Rank         = ADC_REGULAR_RANK_1;          /* 采样序列里的第1个 */
    126.        sConfig.SamplingTime = ADC_SAMPLETIME_810CYCLES_5;  /* 采样周期 */
    127.        sConfig.SingleDiff   = ADC_SINGLE_ENDED;            /* 单端输入 */
    128.        sConfig.OffsetNumber = ADC_OFFSET_NONE;             /* 无偏移 */ 
    129.        sConfig.Offset = 0;                                 /* 无偏移的情况下,此参数忽略 */
    130.        sConfig.OffsetRightShift       = DISABLE;           /* 禁止右移 */
    131.        sConfig.OffsetSignedSaturation = DISABLE;           /* 禁止有符号饱和 */
    132.        
    133.        if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK)
    134.        {
    135.            Error_Handler(__FILE__, __LINE__);
    136.        }
    137.        
    138.        /* 配置ADC通道,序列2,采样Vbat/4 */
    139.        sConfig.Channel      = ADC_CHANNEL_VBAT_DIV4;       /* 配置使用的ADC通道 */
    140.        sConfig.Rank         = ADC_REGULAR_RANK_2;          /* 采样序列里的第1个 */
    141.        sConfig.SamplingTime = ADC_SAMPLETIME_810CYCLES_5;  /* 采样周期 */
    142.        sConfig.SingleDiff   = ADC_SINGLE_ENDED;            /* 单端输入 */
    143.        sConfig.OffsetNumber = ADC_OFFSET_NONE;             /* 无偏移 */ 
    144.        sConfig.Offset = 0;                                 /* 无偏移的情况下,此参数忽略 */
    145.        sConfig.OffsetRightShift       = DISABLE;           /* 禁止右移 */
    146.        sConfig.OffsetSignedSaturation = DISABLE;           /* 禁止有符号饱和 */
    147.        
    148.        if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK)
    149.        {
    150.            Error_Handler(__FILE__, __LINE__);
    151.        }
    152.      
    153.        /* 配置ADC通道,序列3,采样VrefInt */
    154.        sConfig.Channel      = ADC_CHANNEL_VREFINT;         /* 配置使用的ADC通道 */
    155.        sConfig.Rank         = ADC_REGULAR_RANK_3;          /* 采样序列里的第1个 */
    156.        sConfig.SamplingTime = ADC_SAMPLETIME_810CYCLES_5;  /* 采样周期 */
    157.        sConfig.SingleDiff   = ADC_SINGLE_ENDED;            /* 单端输入 */
    158.        sConfig.OffsetNumber = ADC_OFFSET_NONE;             /* 无偏移 */ 
    159.        sConfig.Offset = 0;                                 /* 无偏移的情况下,此参数忽略 */
    160.        sConfig.OffsetRightShift       = DISABLE;           /* 禁止右移 */
    161.        sConfig.OffsetSignedSaturation = DISABLE;           /* 禁止有符号饱和 */
    162.        
    163.        if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK)
    164.        {
    165.            Error_Handler(__FILE__, __LINE__);
    166.        }
    167.    
    168.        /* 配置ADC通道,序列4,采样温度 */
    169.        sConfig.Channel      = ADC_CHANNEL_TEMPSENSOR;      /* 配置使用的ADC通道 */
    170.        sConfig.Rank         = ADC_REGULAR_RANK_4;          /* 采样序列里的第1个 */
    171.        sConfig.SamplingTime = ADC_SAMPLETIME_810CYCLES_5;  /* 采样周期 */
    172.        sConfig.SingleDiff   = ADC_SINGLE_ENDED;            /* 单端输入 */
    173.        sConfig.OffsetNumber = ADC_OFFSET_NONE;             /* 无偏移 */ 
    174.        sConfig.Offset = 0;                                 /* 无偏移的情况下,此参数忽略 */
    175.        sConfig.OffsetRightShift       = DISABLE;           /* 禁止右移 */
    176.        sConfig.OffsetSignedSaturation = DISABLE;           /* 禁止有符号饱和 */
    177.        
    178.        if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK)
    179.        {
    180.            Error_Handler(__FILE__, __LINE__);
    181.        }    
    182.      
    183.    
    184.        /* ## - 6 - 启动ADC的DMA方式传输 ####################################### */
    185.        if (HAL_ADC_Start_DMA(&AdcHandle, (uint32_t *)ADCxValues, 4) != HAL_OK)
    186.        {
    187.            Error_Handler(__FILE__, __LINE__);
    188.        }
    189.    }

    这里把几个关键的地方阐释下:

    •   第11 - 13行,对作为局部变量的HAL库结构体做初始化,防止不确定值配置时出问题。
    •   第17 - 38行,前面2.2小节已经讲解,ADC时钟源选择AHB时钟还是PLL时钟。
    •   第41 – 46行,选择PC0作为数据采集引脚。
    •   第49- 68行,配置DMA的基本参数,注释较详细。这里是采用的ADC外设到内部SRAM的传输方向,数据带宽设置16bit,循环传输模式。
    •   第71行,这行代码比较重要,应用中容易被遗忘,用于关联ADC句柄和DMA句柄。在用户调用ADC的DMA传输方式函数HAL_ADC_Start_DMA时,此函数内部调用的HAL_DMA_Start_IT会用到DMA句柄。
    •   第75 - 107行,主要是ADC的配置,注释较详细,配置ADC3为16bit模式,扫描多通道,连续转换,软件触发。
    •   第111 – 114行,这里的是采用的ADC偏移校准,如果要采用线性度校准,务必要注意此贴的问题:http://www.armbbs.cn/forum.php?mod=viewthread&tid=91436
    •   第119 -129行,配置ADC多通道采样的第1个序列。这里使用的通道10是PC0引脚的复用功能,不是随意设置的。另外注意转换速度的计算,在程序里面有注释。
    •   第139 – 151行,配置ADC多通道采样的第2个序列,采样的Vbat/4电压。
    •   第154 – 166行,配置ADC多通道采样的第3个序列,采样的VrefInt电压。
    •   第169 – 181行,配置ADC多通道采样的第4个序列,采样的温度。
    •   第185 – 188行,启动ADC的DMA方式数据传输。

    46.3.4 DMA存储器选择注意事项

    由于STM32H7 Cache的存在,凡是CPU和DMA都会操作到的存储器,我们都要注意数据一致性问题。对于本章节要实现的功能,要注意读Cache问题,防止DMA已经更新了缓冲区的数据,而我们读取的却是Cache里面缓存的。这里提供两种解决办法:

    •   方法一:

    关闭DMA所使用SRAM存储区。

    /* 配置SRAM的MPU属性为Device或者Strongly Ordered,即关闭Cache */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    •   方法二:

    设置SRAM的缓冲区做32字节对齐,大小最好也是32字节整数倍,然后调用函数SCB_InvalidateDCache_by_Addr做无效化操作即可,保证CPU读取到的数据是刚更新好的。

    本章节配套例子是直接使用的方法二。例子中变量的定义方式如下:

    /* 方便Cache类的API操作,做32字节对齐 */
    #if defined ( __ICCARM__ )
    #pragma location = 0x38000000
    uint16_t ADCxValues[4];
    #elif defined ( __CC_ARM )
    ALIGN_32BYTES(__attribute__((section (".RAM_D3"))) uint16_t ADCxValues[4]);
    #endif

    对于IAR需要#pragma location指定位置,而MDK通过分散加载即可实现,详情看前面第26章,有详细讲解。

    46.3.5 读取DMA缓冲数据

    程序中配置的DMA缓冲区可以存储4次ADC的转换数据,正好ADCxValues[0]对应PC0引脚的采样电压,ADCxValues[1]对应Vbat/4电压,ADCxValues[2]对应VrefInt采样的电源,ADCxValues[3]对应温度采样值。

    具体实现代码如下:

    /*
    *********************************************************************************************************
    *    函 数 名: bsp_GetAdcValues
    *    功能说明: 获取ADC的数据并打印
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_GetAdcValues(void)
    {
        float AdcValues[5];
        uint16_t TS_CAL1;
        uint16_t TS_CAL2;
        
        /*
           使用此函数要特别注意,第1个参数地址要32字节对齐,第2个参数要是32字节的整数倍
        */
        SCB_InvalidateDCache_by_Addr((uint32_t *)ADCxValues,  sizeof(ADCxValues));
        AdcValues[0] = ADCxValues[0] * 3.3 / 65536;
        AdcValues[1] = ADCxValues[1] * 3.3 / 65536; 
        AdcValues[2] = ADCxValues[2] * 3.3 / 65536;     
        
        /*根据参考手册给的公式计算温度值 */
        TS_CAL1 = *(__IO uint16_t *)(0x1FF1E820);
        TS_CAL2 = *(__IO uint16_t *)(0x1FF1E840);
        
        AdcValues[3] = (110.0 - 30.0) * (ADCxValues[3] - TS_CAL1)/ (TS_CAL2 - TS_CAL1) + 30;  
        
        printf("PC0 = %5.3fV, Vbat/4 = %5.3fV, VrefInt = %5.3fV, TempSensor = %5.3f℃
    ", 
                AdcValues[0],  AdcValues[1], AdcValues[2], AdcValues[3]);
    
    }

    46.4 ADC板级支持包(bsp_adc.c)

    ADC驱动文件bsp_adc.c提供了如下函数:

    •   bsp_InitADC
    •   bsp_GetAdcValues

    46.4.1 函数bsp_InitADC

    函数原型:

    void bsp_InitADC(void)

    函数描述:

    此函数用于初始化ADC,采用DMA方式进行多通道采样,采集了PC0, Vbat/4, VrefInt和温度。

    注意事项:

    1. 关于此函数的讲解在本章2.3小节。

    使用举例:

    作为初始化函数,直接在bsp.c文件的bsp_Init函数里面调用即可。

    46.4.2 函数bsp_GetAdcValues

    函数原型:

    void bsp_GetAdcValues(void)

    函数描述:

    此函数用于获取ADC的转换数据。

    注意事项:

    1. 关于此函数的讲解在本章2.4和2.5小节。

    使用举例:

    根据需要,周期性调用即可。

    46.5 ADC驱动移植和使用

    ADC驱动的移植比较方便:

    •   第1步:复制bsp_adc.c和bsp_adc.h到自己的工程目录,并添加到工程里面。
    •   第2步:这几个驱动文件主要用到HAL库的GPIO、TIM,DMA和ADC驱动文件,简单省事些可以添加所有HAL库.C源文件进来。
    •   第3步,应用方法看本章节配套例子即可,另外就是根据自己的需要做配置修改。

    46.6 实验例程设计框架

    通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如

     

      第1阶段,上电启动阶段:

    • 这部分在第14章进行了详细说明。

      第2阶段,进入main函数:   

    •  第1步,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器,LED,串口和ADC。
    •  第2步,周期性的打印ADC采集的多通道数据。

    46.7 实验例程说明(MDK)

    配套例子:

    V7-024-ADC+DMA的多通道采集

    实验目的:

    1. 学习ADC + DMA的多通道采集实现。

    实验内容:

    1. 例子默认用的PLL时钟供ADC使用,大家可以通过bsp_adc.c文件开头宏定义切换到AHB时钟。
    2. 采用DMA方式进行多通道采样,采集了PC0, Vbat/4, VrefInt和温度。
    3. 每隔500ms,串口会打印一次。
    4. 板子正常运行时LED2闪烁。

    PC0引脚位置(稳压基准要短接3.3V):

     

    上电后串口打印的信息:

    波特率 115200,数据位 8,奇偶校验位无,停止位 1

     

    程序设计:

      系统栈大小分配:

     

      RAM空间用的DTCM:

     

      硬件外设初始化

    硬件外设的初始化是在 bsp.c 文件实现:

    /*
    *********************************************************************************************************
    *    函 数 名: bsp_Init
    *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    *    形    参:无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_Init(void)
    {
        /* 配置MPU */
        MPU_Config();
        
        /* 使能L1 Cache */
        CPU_CACHE_Enable();
    
        /* 
           STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
           - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
           - 设置NVIV优先级分组为4。
         */
        HAL_Init();
    
        /* 
           配置系统时钟到400MHz
           - 切换使用HSE。
           - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
        */
        SystemClock_Config();
    
        /* 
           Event Recorder:
           - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
           - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
        */    
    #if Enable_EventRecorder == 1  
        /* 初始化EventRecorder并开启 */
        EventRecorderInitialize(EventRecordAll, 1U);
        EventRecorderStart();
    #endif
        
        bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
        bsp_InitTimer();      /* 初始化滴答定时器 */
        bsp_InitUart();    /* 初始化串口 */
        bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    
        bsp_InitLed();        /* 初始化LED */    
        bsp_InitADC();     /* 初始化ADC */
    }

      MPU配置和Cache配置:

    数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区和D3域的SRAM4。

    /*
    *********************************************************************************************************
    *    函 数 名: MPU_Config
    *    功能说明: 配置MPU
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    static void MPU_Config( void )
    {
        MPU_Region_InitTypeDef MPU_InitStruct;
    
        /* 禁止 MPU */
        HAL_MPU_Disable();
    
        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
        MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
        MPU_InitStruct.BaseAddress      = 0x24000000;
        MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
        MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
        MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
        MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
        MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
        MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
        MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
        MPU_InitStruct.SubRegionDisable = 0x00;
        MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
        HAL_MPU_ConfigRegion(&MPU_InitStruct);
        
        
        /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
        MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
        MPU_InitStruct.BaseAddress      = 0x60000000;
        MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
        MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
        MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
        MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
        MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
        MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
        MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
        MPU_InitStruct.SubRegionDisable = 0x00;
        MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
        
        HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
        /* 配置SRAM4的属性为Write through, read allocate,no write allocate */
        MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
        MPU_InitStruct.BaseAddress      = 0x38000000;
        MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
        MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
        MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
        MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
        MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
        MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
        MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
        MPU_InitStruct.SubRegionDisable = 0x00;
        MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
        /*使能 MPU */
        HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: CPU_CACHE_Enable
    *    功能说明: 使能L1 Cache
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    static void CPU_CACHE_Enable(void)
    {
        /* 使能 I-Cache */
        SCB_EnableICache();
    
        /* 使能 D-Cache */
        SCB_EnableDCache();
    }

      主功能:

    主程序实现如下操作:

    •  每隔500ms,串口会打印一次ADC采集 的PC0, Vbat/4, VrefInt和温度。
    /*
    *********************************************************************************************************
    *    函 数 名: main
    *    功能说明: c程序入口
    *    形    参: 无
    *    返 回 值: 错误代码(无需处理)
    *********************************************************************************************************
    */
    int main(void)
    {
        uint8_t ucKeyCode;        /* 按键代码 */
        
    
    #if defined ( __CC_ARM )    
        TempValues1 = 0; /* 避免MDK警告 */  
        TempValues2 = 0;    
    #endif
        
        bsp_Init();        /* 硬件初始化 */
        
        PrintfLogo();    /* 打印例程名称和版本等信息 */
        PrintfHelp();    /* 打印操作提示 */
    
        bsp_StartAutoTimer(0, 500);    /* 启动1个500ms的自动重装的定时器 */
        
        /* 进入主程序循环体 */
        while (1)
        {
            bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    
            /* 判断定时器超时时间 */
            if (bsp_CheckTimer(0))    
            {
                bsp_GetAdcValues();
                
                /* 每隔500ms 进来一次 */  
                bsp_LedToggle(2);
            }
    
            /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
            ucKeyCode = bsp_GetKey();    /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
            if (ucKeyCode != KEY_NONE)
            {
                switch (ucKeyCode)
                {
                    case KEY_DOWN_K1:        /* K1键按下 */
                        printf("K1按键按下
    ");
                        break;
    
                    default:
                        /* 其它的键值不处理 */
                        break;
                }
            }
        }
    }

    46.8 实验例程说明(IAR)

    配套例子:

    V7-024-ADC+DMA的多通道采集

    实验目的:

    1. 学习ADC + DMA的多通道采集实现。

    实验内容:

    1. 例子默认用的PLL时钟供ADC使用,大家可以通过bsp_adc.c文件开头宏定义切换到AHB时钟。
    2. 采用DMA方式进行多通道采样,采集了PC0, Vbat/4, VrefInt和温度。
    3. 每隔500ms,串口会打印一次。
    4. 板子正常运行时LED2闪烁。

    PC0引脚位置(稳压基准要短接3.3V):

     

    上电后串口打印的信息:

    波特率 115200,数据位 8,奇偶校验位无,停止位 1

     

    程序设计:

      系统栈大小分配:

     

      RAM空间用的DTCM:

     

      硬件外设初始化

    硬件外设的初始化是在 bsp.c 文件实现:

    /*
    *********************************************************************************************************
    *    函 数 名: bsp_Init
    *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    *    形    参:无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    void bsp_Init(void)
    {
        /* 配置MPU */
        MPU_Config();
        
        /* 使能L1 Cache */
        CPU_CACHE_Enable();
    
        /* 
           STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
           - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
           - 设置NVIV优先级分组为4。
         */
        HAL_Init();
    
        /* 
           配置系统时钟到400MHz
           - 切换使用HSE。
           - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
        */
        SystemClock_Config();
    
        /* 
           Event Recorder:
           - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
           - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
        */    
    #if Enable_EventRecorder == 1  
        /* 初始化EventRecorder并开启 */
        EventRecorderInitialize(EventRecordAll, 1U);
        EventRecorderStart();
    #endif
        
        bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
        bsp_InitTimer();      /* 初始化滴答定时器 */
        bsp_InitUart();    /* 初始化串口 */
        bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    
        bsp_InitLed();        /* 初始化LED */    
        bsp_InitADC();     /* 初始化ADC */
    }

      MPU配置和Cache配置:

    数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区和D3域的SRAM4。

    /*
    *********************************************************************************************************
    *    函 数 名: MPU_Config
    *    功能说明: 配置MPU
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    static void MPU_Config( void )
    {
        MPU_Region_InitTypeDef MPU_InitStruct;
    
        /* 禁止 MPU */
        HAL_MPU_Disable();
    
        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
        MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
        MPU_InitStruct.BaseAddress      = 0x24000000;
        MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
        MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
        MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
        MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
        MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
        MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
        MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
        MPU_InitStruct.SubRegionDisable = 0x00;
        MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
        HAL_MPU_ConfigRegion(&MPU_InitStruct);
        
        
        /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
        MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
        MPU_InitStruct.BaseAddress      = 0x60000000;
        MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
        MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
        MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
        MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
        MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
        MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
        MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
        MPU_InitStruct.SubRegionDisable = 0x00;
        MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
        
        HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
        /* 配置SRAM4的属性为Write through, read allocate,no write allocate */
        MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
        MPU_InitStruct.BaseAddress      = 0x38000000;
        MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
        MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
        MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
        MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
        MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
        MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
        MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
        MPU_InitStruct.SubRegionDisable = 0x00;
        MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
        /*使能 MPU */
        HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    }
    
    /*
    *********************************************************************************************************
    *    函 数 名: CPU_CACHE_Enable
    *    功能说明: 使能L1 Cache
    *    形    参: 无
    *    返 回 值: 无
    *********************************************************************************************************
    */
    static void CPU_CACHE_Enable(void)
    {
        /* 使能 I-Cache */
        SCB_EnableICache();
    
        /* 使能 D-Cache */
        SCB_EnableDCache();
    }

      主功能:

    主程序实现如下操作:

    •  每隔500ms,串口会打印一次ADC采集 的PC0, Vbat/4, VrefInt和温度。
    /*
    *********************************************************************************************************
    *    函 数 名: main
    *    功能说明: c程序入口
    *    形    参: 无
    *    返 回 值: 错误代码(无需处理)
    *********************************************************************************************************
    */
    int main(void)
    {
        uint8_t ucKeyCode;        /* 按键代码 */
        
    
    #if defined ( __CC_ARM )    
        TempValues1 = 0; /* 避免MDK警告 */  
        TempValues2 = 0;    
    #endif
        
        bsp_Init();        /* 硬件初始化 */
        
        PrintfLogo();    /* 打印例程名称和版本等信息 */
        PrintfHelp();    /* 打印操作提示 */
    
        bsp_StartAutoTimer(0, 500);    /* 启动1个500ms的自动重装的定时器 */
        
        /* 进入主程序循环体 */
        while (1)
        {
            bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    
            /* 判断定时器超时时间 */
            if (bsp_CheckTimer(0))    
            {
                bsp_GetAdcValues();
                
                /* 每隔500ms 进来一次 */  
                bsp_LedToggle(2);
            }
    
            /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
            ucKeyCode = bsp_GetKey();    /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
            if (ucKeyCode != KEY_NONE)
            {
                switch (ucKeyCode)
                {
                    case KEY_DOWN_K1:        /* K1键按下 */
                        printf("K1按键按下
    ");
                        break;
    
                    default:
                        /* 其它的键值不处理 */
                        break;
                }
            }
        }
    }

    46.9 总结

    本章节就为大家讲解这么多,ADC多通道采样在实际项目中也比较实用,望初学者熟练掌握。

  • 相关阅读:
    移植nand驱动补缺:make mrproper与make clean以及make distclean,find/grep. makefile
    repo使用
    git使用总结
    notepade++使用
    linux内核源代码、配置与编译
    linux内核介绍
    块设备
    PHP和javascript中url编码解码详解
    python中的类方法、静态方法、对象方法
    webpack+vue中安装使用vue-layer弹窗插件
  • 原文地址:https://www.cnblogs.com/armfly/p/12200633.html
Copyright © 2020-2023  润新知