STM32开发,野火ADC—独立模式-单通道-DMA例程BUG
程序员文章站
2022-06-08 19:56:10
...
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端口不改的情况下,还是不好用。如果开始了漫长的查找过程。路程如下:
- 使用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位不会丢失
原因分析和实际情况一致。
查看野火给的资料,发现里边的这个地方是错的,和程序保持了一致。这个程序应该是被误写(使用自动补全),然后编译后实际验证没有认真检查导致。
整个例程就错了一个字母,而我找这个字母花费了两天的时间。