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端口不改的情況下,還是不好用。如果開始了漫長的查找過程。路程如下:
- 使用CUBEMX生成一個ADC DMA傳輸的程序,正常運行沒有錯誤;
- 將生成的程序,ADC以及DMA相關部分去掉,加入野火例程中的ADC相關文件;
- 將CUBEMX生成的ADC相關文件加到野火程序中,運行正常沒有錯誤。
- 運行調試發現不好用,只顯示小數;
- 將原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) 這些位由軟件設置和清除
00:8位
01:16位
10:32位
11:保留
這是錯誤的設置,導致外設的數據寬度設置了兩次,而存儲器保持默認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) 這些位由軟件設置和清除
00:8位
01:16位
10:32位
11:保留
這是正確的設置,存儲器設置位01,即爲16位,最高4位不會丟失
原因分析和實際情況一致。
查看野火給的資料,發現裏邊的這個地方是錯的,和程序保持了一致。這個程序應該是被誤寫(使用自動補全),然後編譯後實際驗證沒有認真檢查導致。
整個例程就錯了一個字母,而我找這個字母花費了兩天的時間。