我的使用环境:
硬件:STM32F767ZGT6、串口1、ADC1、16MHz晶振、216MHz主频
软件:STM32 CUBE IDE
优点:不用定时触发采样,ADC数据是不停的实时更新,ADC数据的更新频率根据采样时钟和采样周期决定,后期只需要定时取用数据即可。
串口1用 printf 的打印ADC采集到的数据。或者用仿真查看数据变化也行。
一、基础知识
ADC的时钟是APB2,与时钟树的配置有关,同时规格书中ADC的时钟最大频率又跟电源电压有关,3.3V下最大时钟频率为36MHz。所以预分频后的频率肯定是在这个规定最大时钟频率之内的。
二、STM32Cube IDE 具体配置
串口配置:使能后更改合适波特率。
ADC和DMA 配置
- 预分频之后的时钟应该是在ADC时钟范围之内的,用APB2频率除以分频数。
- DMA要开启扫描模式、连续转换模式、DMA连续请求
- 每个ADC通道的采样时间一定要高于分辨率要求的采样时钟周期,否则会导致初始化失败或者数据没有更新。
4、配置详细解释:
长的采样时间可以减少 ADC 读数的噪声,从而提高精度。在更长的采样时间内,模拟信号的值可以平稳地积累,使得读数更加准确。虽然长的采样时间可以提高 ADC 的精度,但是会影响采样频率。因此,在不同的应用场景中,需要选择合适的采样时间,以达到较高的精度和采样频率的平衡。
ADC Injected Conversion Mode
它允许在特定序列中执行多次ADC转换,每次转换有单独的触发。注入转换通常用于高级任务,例如过采样或偏移校正,并且在ADC的触发输入或软件命令触发的常规转换之外执行。
在注入转换模式下,ADC执行一系列转换,每次转换由单独的触发输入触发。注入转换的ADC结果寄存器与常规转换的结果寄存器分开,结束转换中断可以为每种类型的转换单独生成。这允许CPU监视ADC的状态,并确定注入转换何时完成。
是否使用注入转换模式取决于ADC应用程序的具体要求,在使用注入转换模式之前了解注入转换模式的功能和限制是非常重要的。
三、验证程序
生成程序后,main.c中开始,使用下面的函数打开DMA传输,每当ADC完成一次数据采样,DMA就会将ADC数据更新至 *pData指定的数组。在任意时刻取用数组中的数据即可。
HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length)
串口用 printf 打印:
主体部分:
四、测试结果
main.c 代码如下:
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
UART_HandleTypeDef huart1;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC1_Init(void);
static void MX_USART1_UART_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
#endif
uint16_t ADC_Value_buffer[2];
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("**************************\r\n");
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)ADC_Value_buffer, 2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
printf("ADC_Value_buffer: %d %d\r\n", ADC_Value_buffer[0], ADC_Value_buffer[1]);
HAL_Delay(500);
}
/* USER CODE END 3 */
}
测试结果如下:
五、注意事项
问题:当涉及到很多外设的时候,用以上配置发现指定数组的数据数据,没有变化更新。
修改函数生成的顺序,保证DMA初始化函数在ADC初始化函数(或你需要传输的外设数据)之前。同理,用DMA传输串口或者DAC也没有数据变化的时候也可能是这种情况。选中功能项,选表格右上角的调节按钮可以调节顺序,如果遇到ADC函数在第一个同时在DMA上方不能调节的情况,在Pinout&Configuration 配置页面去掉ADC选项生成一次代码,按开始步骤再配置一次ADC,然后再生成一次代码,ADC初始化函数就在最底下了。
底层代码逻辑:ADC初始化函数中有一个硬件层面的Msp初始化函数,这个函数定义了ADC的硬件层,同时将ADC和DMA传输用__HAL_LINKDMA 函数连接到一起 。
如果先初始化 ADC,再初始化 DMA,可能会导致 ADC 采样的数据无法正确地传输到内存。这是因为,在初始化 ADC 之前,DMA 无法确定将要采样的数据存储在哪个内存地址。因此,在 DMA 初始化期间,必须指定数据存储地址,以便 DMA 正确地传输数据。
因此,为了避免这种情况,在初始化 ADC 之前,必须先初始化 DMA。在 DMA 初始化过程中,将会指定数据存储地址,这样,当 ADC 开始采样时,DMA 就可以正确地传输数据。
另外,还需要在 DMA 和 ADC 之间建立正确的链接,使得 DMA 可以监视 ADC 的数据采样并自动进行传输。这通常可以通过使用 __HAL_LINKDMA 宏函数实现。
正确的顺序是:先初始化 DMA,指定数据存储地址,然后链接 DMA 和 ADC,最后初始化 ADC。这样可以保证 ADC 采样的数据能够正确地传输到内存。
HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef* hadc)
{
HAL_ADC_MspInit(hadc);
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
__HAL_LINKDMA(hadc,DMA_Handle,hdma_adc1);
}