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

1 概述

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

1.1 资源概述

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

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。
正确程序和不正确程序

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位不会丢失

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

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你可以参考以下的代码示例,该示例演示了如何在STM32F103系列的MCU上使用自动读取多通道的内部ADC数据。 ```c #include "stm32f10x.h" #define ADC1_DR_ADDRESS ((uint32_t)0x4001244C) void ADC_DMA_Config(void) { ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 使能DMA1时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 使能ADC1和GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置GPIOA.1和GPIOA.2为模拟输入模式 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure); // ADC1配置 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = ENABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 2; ADC_Init(ADC1, &ADC_InitStructure); // 配置ADC1通道1和通道2为采样时间为55.5个周期的单次转换模式 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 2, ADC_SampleTime_55Cycles5); // 使能ADC1 DMA ADC_DMACmd(ADC1, ENABLE); // DMA配置 DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_ADDRESS; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_Values; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 2; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); // 使能DMA1通道1 DMA_Cmd(DMA1_Channel1, ENABLE); // 使能ADC1 ADC_Cmd(ADC1, ENABLE); // 开始ADC1的软件转换 ADC_SoftwareStartConvCmd(ADC1, ENABLE); } int main(void) { // 初始化系统时钟等 // 配置ADCDMA ADC_DMA_Config(); while (1) { // 处理ADC采样数据 // 延时等待一段时间 Delay(1000); } } ``` 上述代码中,首先需要调用`ADC_DMA_Config()`函数来配置ADCDMA。在该函数中,首先使能了DMA1的时钟,然后使能了ADC1和GPIOA的时钟。接下来,配置了GPIOA.1和GPIOA.2为模拟输入模式。然后,配置了ADC1的工作模式、采样模式和通道数,并设置了通道1和通道2的采样时间。接着,使能了ADC1的DMA功能,并配置了DMA1_Channel1通道的参数。最后,使能了DMA1_Channel1通道和ADC1,并启动了ADC1的软件转换。 在主函数中,可以在一个无限循环中处理ADC采样数据并进行其他操作。这里使用了一个简单的延时函数`Delay()`来等待一段时间,你可以根据实际需求修改延时时间或使用其他方式来控制采样数据的处理频率。 注意,上述代码中的`ADC_Values`是一个数组用于存储ADC采样值,你需要根据实际需要定义并处理这些采样值。 希望以上代码能帮助到你!如果有任何疑问,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值