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位不會丟失

原因分析和實際情況一致。
查看野火給的資料,發現裏邊的這個地方是錯的,和程序保持了一致。這個程序應該是被誤寫(使用自動補全),然後編譯後實際驗證沒有認真檢查導致。
野火書中的錯誤
整個例程就錯了一個字母,而我找這個字母花費了兩天的時間。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章