你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

经验分享】STM32开发,野火ADC—独立模式-单通道-DMA例程BUG

[复制链接]
STMCU小助手 发布时间:2022-5-5 16:38
1 概述
实验的代码已经上传,无需积分。

1.1 资源概述
开发板:正点原子STM32F103 Nano开发板
CUBEMX版本:1.3.0
MDK版本:5.27
主控芯片型号:STM32F103RBT6

1OW)R5@@XAVNC_NZT627XPB.png

1.2 实现功能
1,移植野火ADC使用DMA传输例程,实现读取B01的电压,并通过串口打印出来。野火的程序使用的RCT6芯片,引脚与RBT6相同,改动比较简单。

2 程序实现
2.1主程序

  1. /* Includes ------------------------------------------------------------------*/
  2. #include "main.h"
  3. #include "stm32f1xx.h"
  4. #include "./usart/bsp_debug_usart.h"
  5. #include "./led/bsp_led.h"
  6. #include <stdio.h>
  7. #include "./adc/bsp_adc.h"

  8. // ADC1转换的电压值通过MDA方式传到SRAM
  9. extern __IO uint32_t ADC_ConvertedValue;

  10. // 局部变量,用于保存转换计算后的电压值         
  11. float ADC_Vol;

  12. static void Delay(__IO uint32_t nCount)         //简单的延时函数
  13. {
  14.         for(; nCount != 0; nCount--);
  15. }
  16. /**
  17.   * @brief  主函数
  18.   * @param  无
  19.   * @retval 无
  20.   */
  21. int main(void)
  22. {   
  23.     /* 配置系统时钟为72 MHz */
  24.     SystemClock_Config();

  25.     /* 初始化USART1 配置模式为 115200 8-N-1 */
  26.     DEBUG_USART_Config();

  27.     Rheostat_Init();
  28.     while (1)
  29.     {
  30.         ADC_Vol =(float) ADC_ConvertedValue/4096*(float)3.3; // 读取转换的AD值
  31.         printf("\r\n The current AD value = 0x%04X \r\n", ADC_ConvertedValue);
  32.         printf("\r\n The current AD value = %f V \r\n",ADC_Vol);     
  33.         Delay(0x8fffff);  
  34.     }   
  35. }

  36. /**
  37.   * @brief  System Clock Configuration
  38.   *         The system Clock is configured as follow :
  39.   *            System Clock source            = PLL (HSE)
  40.   *            SYSCLK(Hz)                     = 72000000
  41.   *            HCLK(Hz)                       = 72000000
  42.   *            AHB Prescaler                  = 1
  43.   *            APB1 Prescaler                 = 2
  44.   *            APB2 Prescaler                 = 1
  45.   *            HSE Frequency(Hz)              = 8000000
  46.   *            HSE PREDIV1                    = 1
  47.   *            PLLMUL                         = 9
  48.   *            Flash Latency(WS)              = 2
  49.   * @param  None
  50.   * @retval None
  51.   */
  52. void SystemClock_Config(void)
  53. {
  54.   RCC_ClkInitTypeDef clkinitstruct = {0};
  55.   RCC_OscInitTypeDef oscinitstruct = {0};

  56.   /* Enable HSE Oscillator and activate PLL with HSE as source */
  57.   oscinitstruct.OscillatorType  = RCC_OSCILLATORTYPE_HSE;
  58.   oscinitstruct.HSEState        = RCC_HSE_ON;
  59.   oscinitstruct.HSEPredivValue  = RCC_HSE_PREDIV_DIV1;
  60.   oscinitstruct.PLL.PLLState    = RCC_PLL_ON;
  61.   oscinitstruct.PLL.PLLSource   = RCC_PLLSOURCE_HSE;
  62.   oscinitstruct.PLL.PLLMUL      = RCC_PLL_MUL9;
  63.   if (HAL_RCC_OscConfig(&oscinitstruct)!= HAL_OK)
  64.   {
  65.     /* Initialization Error */
  66.     while(1);
  67.   }

  68.   /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2
  69.      clocks dividers */
  70.   clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
  71.   clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  72.   clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  73.   clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1;
  74.   clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2;  
  75.   if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK)
  76.   {
  77.     /* Initialization Error */
  78.     while(1);
  79.   }
  80. }
复制代码

2.2 ADC程序
  1. #include "./adc/bsp_adc.h"

  2. __IO uint32_t ADC_ConvertedValue;
  3. DMA_HandleTypeDef hdma_adcx;
  4. ADC_HandleTypeDef ADC_Handle;
  5. ADC_ChannelConfTypeDef ADC_Config;

  6. static void Rheostat_ADC_GPIO_Config(void)
  7. {
  8.   GPIO_InitTypeDef GPIO_InitStructure;

  9.   RHEOSTAT_ADC_CLK_ENABLE();
  10.   // 使能 GPIO 时钟
  11.   RHEOSTAT_ADC_GPIO_CLK_ENABLE();

  12.   // 配置 IO
  13.   GPIO_InitStructure.Pin = RHEOSTAT_ADC_GPIO_PIN;
  14.   GPIO_InitStructure.Mode = GPIO_MODE_ANALOG;            
  15. //  GPIO_InitStructure.Pull = GPIO_NOPULL ; //不上拉不下拉
  16.   HAL_GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure);               
  17. }

  18. static void Rheostat_ADC_Mode_Config(void)
  19. {
  20.     // ------------------DMA Init 结构体参数 初始化--------------------------
  21.     // 开启DMA时钟
  22.     RHEOSTAT_ADC_DMA_CLK_ENABLE();
  23.     // 数据传输通道
  24.      hdma_adcx.Instance = RHEOSTAT_ADC_DMA_STREAM;

  25.      hdma_adcx.Init.Direction=DMA_PERIPH_TO_MEMORY;;            //存储器到外设
  26.      hdma_adcx.Init.PeriphInc=DMA_PINC_DISABLE;                 //外设非增量模式
  27.      hdma_adcx.Init.MemInc=DMA_MINC_DISABLE;                     //存储器增量模式
  28.      hdma_adcx.Init.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD;//外设数据长度:16位
  29.      hdma_adcx.Init.MemDataAlignment=DMA_MDATAALIGN_HALFWORD;   //存储器数据长度:16位,错误已经修改
  30.      hdma_adcx.Init.Mode= DMA_CIRCULAR;                         //外设普通模式
  31.      hdma_adcx.Init.Priority=DMA_PRIORITY_MEDIUM;               //中等优先级

  32.     //初始化DMA流,流相当于一个大的管道,管道里面有很多通道
  33.     HAL_DMA_Init(&hdma_adcx);

  34.     __HAL_LINKDMA( &ADC_Handle,DMA_Handle,hdma_adcx);

  35.    //---------------------------------------------------------------------------
  36.     RCC_PeriphCLKInitTypeDef ADC_CLKInit;
  37.     // 开启ADC时钟
  38.     ADC_CLKInit.PeriphClockSelection=RCC_PERIPHCLK_ADC;                        //ADC外设时钟
  39.     ADC_CLKInit.AdcClockSelection=RCC_ADCPCLK2_DIV8;                          //分频因子6时钟为72M/8=9MHz
  40.     HAL_RCCEx_PeriphCLKConfig(&ADC_CLKInit);                                              //设置ADC时钟

  41.     ADC_Handle.Instance=RHEOSTAT_ADC;
  42.     ADC_Handle.Init.DataAlign=ADC_DATAALIGN_RIGHT;             //右对齐
  43.     ADC_Handle.Init.ScanConvMode=DISABLE;                      //非扫描模式
  44.     ADC_Handle.Init.ContinuousConvMode=ENABLE;                 //连续转换
  45.     ADC_Handle.Init.NbrOfConversion=1;                         //1个转换在规则序列中 也就是只转换规则序列1
  46.     ADC_Handle.Init.DiscontinuousConvMode=DISABLE;             //禁止不连续采样模式
  47.     ADC_Handle.Init.NbrOfDiscConversion=0;                     //不连续采样通道数为0
  48.     ADC_Handle.Init.ExternalTrigConv=ADC_SOFTWARE_START;       //软件触发
  49.     HAL_ADC_Init(&ADC_Handle);                                 //初始化

  50. //---------------------------------------------------------------------------
  51.     ADC_Config.Channel      = RHEOSTAT_ADC_CHANNEL;
  52.     ADC_Config.Rank         = 1;
  53.     // 采样时间间隔        
  54.     ADC_Config.SamplingTime = ADC_SAMPLETIME_55CYCLES_5 ;
  55.     // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期
  56.     HAL_ADC_ConfigChannel(&ADC_Handle, &ADC_Config);

  57.     HAL_ADC_Start_DMA(&ADC_Handle, (uint32_t*)&ADC_ConvertedValue, 1);
  58. }
  59. void Rheostat_Init(void)
  60. {
  61.         Rheostat_ADC_GPIO_Config();
  62.         Rheostat_ADC_Mode_Config();
  63. }
复制代码

2.3 ADC头文件
调整端口定义与正点Nano保持一致,这里需要注意的是,GBIOB1对应的是ADC_IN9, 对应的DMA1_Channel1(通道1,而不是11)。

  1. #ifndef __BSP_ADC_H
  2. #define        __BSP_ADC_H

  3. #include "stm32f1xx.h"

  4. // ADC GPIO 宏定义
  5. #define RHEOSTAT_ADC_GPIO_PORT              GPIOB
  6. #define RHEOSTAT_ADC_GPIO_PIN               GPIO_PIN_1
  7. #define RHEOSTAT_ADC_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOB_CLK_ENABLE()

  8. // ADC 序号宏定义
  9. #define RHEOSTAT_ADC                        ADC1
  10. #define RHEOSTAT_ADC_CLK_ENABLE()           __HAL_RCC_ADC1_CLK_ENABLE();
  11. #define RHEOSTAT_ADC_CHANNEL                ADC_CHANNEL_9

  12. // ADC DMA 通道宏定义,这里我们使用DMA传输
  13. #define RHEOSTAT_ADC_DMA_CLK_ENABLE()       __HAL_RCC_DMA1_CLK_ENABLE();
  14. #define RHEOSTAT_ADC_DMA_STREAM             DMA1_Channel1

  15. void Rheostat_Init(void);

  16. #endif /* __BSP_ADC_H */
复制代码

3 程序调试
在程序调试过程中,串口打印一直无法输出正确的值,都是小于1的小数,而且调节滑动电阻时,数值会发生变化。由于是移植的例程,一直怀疑是我自己改程序导致的,但是经过换成别的开发板,而且ADC端口不改的情况下,还是不好用。如果开始了漫长的查找过程。路程如下:
1.使用CUBEMX生成一个ADC DMA传输的程序,正常运行没有错误;
2.将生成的程序,ADC以及DMA相关部分去掉,加入野火例程中的ADC相关文件;
3.将CUBEMX生成的ADC相关文件加到野火程序中,运行正常没有错误。
4.运行调试发现不好用,只显示小数;
5.将原CUBEMX生成的函数,一块一块加到野火程序中去,替代旧的函数。最后发现在配置半字时出现问题。
例程中错误的配置
  1. hdma_adcx.Init.MemDataAlignment=DMA_PDATAALIGN_HALFWORD;   //存储器数据长度:16位
复制代码

而正确的配置为(P改为M)

  1. hdma_adcx.Init.MemDataAlignment=DMA_MDATAALIGN_HALFWORD;   //存储器数据长度:16位
复制代码

修改后程序调试OK,下面为更改后和更改前的结果,在错误的程序中,第三位的B丢失了。正确的位B37,错误的为37。

I%)L04ETGX8I~%HCCXF87)R.png

4 查找原因
查看官方手册继续查找原因,将程序里边的定义一层层翻出来,结果如下。

错误赋值函数

  1. hdma_adcx.Init.MemDataAlignment=DMA_PDATAALIGN_HALFWORD;   //存储器数据长度:16位

  2. #define DMA_PDATAALIGN_HALFWORD      ((uint32_t)DMA_CCR_PSIZE_0)  /*!< Peripheral data alignment: HalfWord */

  3. #define DMA_CCR_PSIZE_0              (0x1U << DMA_CCR_PSIZE_Pos)        /*!< 0x00000100 */

  4. #define DMA_CCR_PSIZE_Pos            (8U)      

  5. STM32F1中文参考手册V10里边关于DMA配置寄存器的说明
  6. 位9:8
  7. PSIZE[1:0]:外设数据宽度 (Peripheral size) 这些位由软件设置和清除
  8. 00:8位
  9. 01:16位
  10. 10:32位
  11. 11:保留
  12. 这是错误的设置,导致外设的数据宽度设置了两次,而存储器保持默认00,即为8位,而ADC为12位右对齐,最高4位丢失。
复制代码

正确赋值的函数


  1. hdma_adcx.Init.MemDataAlignment=DMA_MDATAALIGN_HALFWORD;   //存储器数据长度:16位

  2. #define DMA_MDATAALIGN_HALFWORD      ((uint32_t)DMA_CCR_MSIZE_0)  /*!< Memory data alignment: HalfWord */

  3. #define DMA_CCR_MSIZE_0              (0x1U << DMA_CCR_MSIZE_Pos)        /*!< 0x00000400 */

  4. #define DMA_CCR_MSIZE_Pos            (10U)   

  5. STM32F1中文参考手册V10里边关于DMA配置寄存器的说明
  6. 位11:10
  7. MSIZE[1:0]:存储器数据宽度 (Peripheral size) 这些位由软件设置和清除
  8. 00:8位
  9. 01:16位
  10. 10:32位
  11. 11:保留
  12. 这是正确的设置,存储器设置位01,即为16位,最高4位不会丢失
复制代码

原因分析和实际情况一致。
查看野火给的资料,发现里边的这个地方是错的,和程序保持了一致。这个程序应该是被误写(使用自动补全),然后编译后实际验证没有认真检查导致。



收藏 评论0 发布时间:2022-5-5 16:38

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版