欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-08 19:56:10
...

1 概述

实验的代码已经上传,无需积分。

1.1 资源概述

开发板:正点原子STM32F103 Nano开发板
CUBEMX版本:1.3.0
MDK版本:5.27
主控芯片型号:STM32F103RBT6
STM32开发,野火ADC—独立模式-单通道-DMA例程BUG

1.2 实现功能

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

2 程序实现

2.1主程序


/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx.h"
#include "./usart/bsp_debug_usart.h"
#include "./led/bsp_led.h"
#include <stdio.h>
#include "./adc/bsp_adc.h"

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

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

static void Delay(__IO uint32_t nCount)	 //简单的延时函数
{
	for(; nCount != 0; nCount--);
}
/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void)
{   
    /* 配置系统时钟为72 MHz */
    SystemClock_Config();

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

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

/**
  * @brief  System Clock Configuration
  *         The system Clock is configured as follow : 
  *            System Clock source            = PLL (HSE)
  *            SYSCLK(Hz)                     = 72000000
  *            HCLK(Hz)                       = 72000000
  *            AHB Prescaler                  = 1
  *            APB1 Prescaler                 = 2
  *            APB2 Prescaler                 = 1
  *            HSE Frequency(Hz)              = 8000000
  *            HSE PREDIV1                    = 1
  *            PLLMUL                         = 9
  *            Flash Latency(WS)              = 2
  * @param  None
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_ClkInitTypeDef clkinitstruct = {0};
  RCC_OscInitTypeDef oscinitstruct = {0};
  
  /* Enable HSE Oscillator and activate PLL with HSE as source */
  oscinitstruct.OscillatorType  = RCC_OSCILLATORTYPE_HSE;
  oscinitstruct.HSEState        = RCC_HSE_ON;
  oscinitstruct.HSEPredivValue  = RCC_HSE_PREDIV_DIV1;
  oscinitstruct.PLL.PLLState    = RCC_PLL_ON;
  oscinitstruct.PLL.PLLSource   = RCC_PLLSOURCE_HSE;
  oscinitstruct.PLL.PLLMUL      = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&oscinitstruct)!= HAL_OK)
  {
    /* Initialization Error */
    while(1); 
  }

  /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 
     clocks dividers */
  clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
  clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1;
  clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2;  
  if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK)
  {
    /* Initialization Error */
    while(1); 
  }
}

2.2 ADC程序

#include "./adc/bsp_adc.h"

__IO uint32_t ADC_ConvertedValue;
DMA_HandleTypeDef hdma_adcx;
ADC_HandleTypeDef ADC_Handle;
ADC_ChannelConfTypeDef ADC_Config;

static void Rheostat_ADC_GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;

  RHEOSTAT_ADC_CLK_ENABLE(); 
  // 使能 GPIO 时钟
  RHEOSTAT_ADC_GPIO_CLK_ENABLE();
        
  // 配置 IO
  GPIO_InitStructure.Pin = RHEOSTAT_ADC_GPIO_PIN;
  GPIO_InitStructure.Mode = GPIO_MODE_ANALOG;	    
//  GPIO_InitStructure.Pull = GPIO_NOPULL ; //不上拉不下拉
  HAL_GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure);		
}

static void Rheostat_ADC_Mode_Config(void)
{
    // ------------------DMA Init 结构体参数 初始化--------------------------
    // 开启DMA时钟
    RHEOSTAT_ADC_DMA_CLK_ENABLE();
    // 数据传输通道
     hdma_adcx.Instance = RHEOSTAT_ADC_DMA_STREAM;
  
     hdma_adcx.Init.Direction=DMA_PERIPH_TO_MEMORY;;            //存储器到外设
     hdma_adcx.Init.PeriphInc=DMA_PINC_DISABLE;                 //外设非增量模式
     hdma_adcx.Init.MemInc=DMA_MINC_DISABLE;                     //存储器增量模式 
     hdma_adcx.Init.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD;//外设数据长度:16位
     hdma_adcx.Init.MemDataAlignment=DMA_MDATAALIGN_HALFWORD;   //存储器数据长度:16位,错误已经修改
     hdma_adcx.Init.Mode= DMA_CIRCULAR;                         //外设普通模式
     hdma_adcx.Init.Priority=DMA_PRIORITY_MEDIUM;               //中等优先级

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

    __HAL_LINKDMA( &ADC_Handle,DMA_Handle,hdma_adcx);
  
   //---------------------------------------------------------------------------
    RCC_PeriphCLKInitTypeDef ADC_CLKInit;
    // 开启ADC时钟
    ADC_CLKInit.PeriphClockSelection=RCC_PERIPHCLK_ADC;			//ADC外设时钟
    ADC_CLKInit.AdcClockSelection=RCC_ADCPCLK2_DIV8;			  //分频因子6时钟为72M/8=9MHz
    HAL_RCCEx_PeriphCLKConfig(&ADC_CLKInit);					      //设置ADC时钟
   
    ADC_Handle.Instance=RHEOSTAT_ADC;
    ADC_Handle.Init.DataAlign=ADC_DATAALIGN_RIGHT;             //右对齐
    ADC_Handle.Init.ScanConvMode=DISABLE;                      //非扫描模式
    ADC_Handle.Init.ContinuousConvMode=ENABLE;                 //连续转换
    ADC_Handle.Init.NbrOfConversion=1;                         //1个转换在规则序列中 也就是只转换规则序列1 
    ADC_Handle.Init.DiscontinuousConvMode=DISABLE;             //禁止不连续采样模式
    ADC_Handle.Init.NbrOfDiscConversion=0;                     //不连续采样通道数为0
    ADC_Handle.Init.ExternalTrigConv=ADC_SOFTWARE_START;       //软件触发
    HAL_ADC_Init(&ADC_Handle);                                 //初始化 
 
 //---------------------------------------------------------------------------
    ADC_Config.Channel      = RHEOSTAT_ADC_CHANNEL;
    ADC_Config.Rank         = 1;
    // 采样时间间隔	
    ADC_Config.SamplingTime = ADC_SAMPLETIME_55CYCLES_5 ;
    // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期
    HAL_ADC_ConfigChannel(&ADC_Handle, &ADC_Config);

    HAL_ADC_Start_DMA(&ADC_Handle, (uint32_t*)&ADC_ConvertedValue, 1);
}
void Rheostat_Init(void)
{
	Rheostat_ADC_GPIO_Config();
	Rheostat_ADC_Mode_Config();
}

2.3 ADC头文件

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

#ifndef __BSP_ADC_H
#define	__BSP_ADC_H

#include "stm32f1xx.h"

// ADC GPIO 宏定义
#define RHEOSTAT_ADC_GPIO_PORT              GPIOB
#define RHEOSTAT_ADC_GPIO_PIN               GPIO_PIN_1
#define RHEOSTAT_ADC_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOB_CLK_ENABLE()
    
// ADC 序号宏定义
#define RHEOSTAT_ADC                        ADC1
#define RHEOSTAT_ADC_CLK_ENABLE()           __HAL_RCC_ADC1_CLK_ENABLE(); 
#define RHEOSTAT_ADC_CHANNEL                ADC_CHANNEL_9

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

void Rheostat_Init(void);

#endif /* __BSP_ADC_H */

3 程序调试

在程序调试过程中,串口打印一直无法输出正确的值,都是小于1的小数,而且调节滑动电阻时,数值会发生变化。由于是移植的例程,一直怀疑是我自己改程序导致的,但是经过换成别的开发板,而且ADC端口不改的情况下,还是不好用。如果开始了漫长的查找过程。路程如下:

  1. 使用CUBEMX生成一个ADC DMA传输的程序,正常运行没有错误;
  2. 将生成的程序,ADC以及DMA相关部分去掉,加入野火例程中的ADC相关文件;
  3. 将CUBEMX生成的ADC相关文件加到野火程序中,运行正常没有错误。
  4. 运行调试发现不好用,只显示小数;
  5. 将原CUBEMX生成的函数,一块一块加到野火程序中去,替代旧的函数。最后发现在配置半字时出现问题。
    例程中错误的配置
 hdma_adcx.Init.MemDataAlignment=DMA_PDATAALIGN_HALFWORD;   //存储器数据长度:16位

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

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

修改后程序调试OK,下面为更改后和更改前的结果,在错误的程序中,第三位的B丢失了。正确的位B37,错误的为37。
STM32开发,野火ADC—独立模式-单通道-DMA例程BUG

4 查找原因

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

 hdma_adcx.Init.MemDataAlignment=DMA_PDATAALIGN_HALFWORD;   //存储器数据长度:16位
 
 #define DMA_PDATAALIGN_HALFWORD      ((uint32_t)DMA_CCR_PSIZE_0)  /*!< Peripheral data alignment: HalfWord */
 
 #define DMA_CCR_PSIZE_0              (0x1U << DMA_CCR_PSIZE_Pos)        /*!< 0x00000100 */
 
 #define DMA_CCR_PSIZE_Pos            (8U)      
 
 STM32F1中文参考手册V10里边关于DMA配置寄存器的说明
 位9:8 
 PSIZE[1:0]:外设数据宽度 (Peripheral size) 这些位由软件设置和清除 
 0080116103211:保留
 这是错误的设置,导致外设的数据宽度设置了两次,而存储器保持默认00,即为8位,而ADC为12位右对齐,最高4位丢失。 

正确赋值的函数

hdma_adcx.Init.MemDataAlignment=DMA_MDATAALIGN_HALFWORD;   //存储器数据长度:16位
 
 #define DMA_MDATAALIGN_HALFWORD      ((uint32_t)DMA_CCR_MSIZE_0)  /*!< Memory data alignment: HalfWord */
 
 #define DMA_CCR_MSIZE_0              (0x1U << DMA_CCR_MSIZE_Pos)        /*!< 0x00000400 */
 
 #define DMA_CCR_MSIZE_Pos            (10U)   
 
 STM32F1中文参考手册V10里边关于DMA配置寄存器的说明
 位11:10 
 MSIZE[1:0]:存储器数据宽度 (Peripheral size) 这些位由软件设置和清除 
 0080116103211:保留
 这是正确的设置,存储器设置位01,即为16位,最高4位不会丢失

原因分析和实际情况一致。
查看野火给的资料,发现里边的这个地方是错的,和程序保持了一致。这个程序应该是被误写(使用自动补全),然后编译后实际验证没有认真检查导致。
STM32开发,野火ADC—独立模式-单通道-DMA例程BUG
整个例程就错了一个字母,而我找这个字母花费了两天的时间。