stm32G4的ADC模数转换器编程
1.ADC介绍及电路原理图
ADC模数转换器就是将变化的模拟量转化成变化的数字量的器件。
打开《CT117E-M4产品手册》第10页可以看到这部分的电路图
可以看到PB15和PB12分别通过J11和J12两个跳线帽连接到R37和R38两个旋转定位器上。从而可以将电阻的分压值从0~VDD(3.3V)进行调节。
STM32G431内部集成有2个最高12位的ADC(ADC1和ADC2)。
在《STM32G4系列微控制器参考手册》中可以查看到详细介绍:
为什么是最高12位呢?因为ADC的位数是可以改变的,但是我们一般还是用12位,不会浪费他的高分辨率。
12位的话说明它的范围就是0~2^12 -1,也就是0~4095。
所以要将读到的值转换成电压值的方法就是: x/4096*3.3V即可
按理说应该是4095对应3.3V,所以应该是x/4095,但是其实差别不大,而除以4096还是2的倍数,计算效率更高,所以一般除以4096而不是除以4095。
2.ADC编程实践
步骤:
- 【模板】作为STM32CUBEMX生成代码的工程;
- 设置ADC相关的GPIO为【ADC输入】模式,并设置成【单端模式】 ;(PB15,PB12)
- 从【模板】复制 【adc.c和,h】 文件到【编程工程】,配置【stm32g4xx hal conf.h】 文件;#define HAL_ADC_MODULE_ENABLED)
- 添加AD相关的HAL库驱动文件 (stm32g4xx hal adc.c和stm32g4xx hal adc_ex.c);
- 在main.c添加adc.h,并添加ADC初始化代码;【注]】外设时钟一定要初始化!
- 测试HAL_ADC_Start的ADC启动函数和HAL_ADC_GetValue的ADC读取函数;
进入编程工程,将PB15和PB12设置成ADC通道模式。
可以看到配置好之后两个引脚都变为黄色,此时说明还需要配置额外的一些东西才能生成代码。
点击左侧的“Analog”,设置其中的ADC1的IN11和ADC2的IN15为“Single edged”模式,可发现右侧的PB15和PB12已经由黄色标变为绿色标,则说明已经配置好,可以生成代码。
【Single edged模式和Differential模式的区别是:Single edged模式就是接受的单一的电压源,而Differential模式需要接受两个电压源并计算他们的差值,然后返回这两个电压的差值。
我们此处都配置为“Single edged”即可。】
进行时钟配置:
然后看下时钟部分,由于我们开启了adc,所以对应的adc的时钟也被激活,我们可以选择systick作为adc时钟源,也可以选择PLLP。这里默认选择Systick,我们保持不变就好。并且要特别注意的是此处我们激活了新的外设时钟,那么移植工程文件的时候也一定要记得移植外设时钟相关部分的代码!!!
接下来来到工程配置界面,将IDE改成keilV5。在code generator中将“生成.c/.h文件”这个选项勾上。
点击“生成代码”,进入模板文件,然后把adc.c和adc.h文件复制到工程文件中去,并且要记得把时钟配置也移植过去:
时钟配置是在void SystemClock_Config(void)
这个函数里面:
可以看到多出了如下几行代码:
/** Initializes the peripherals clocks
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC12;
PeriphClkInit.Adc12ClockSelection = RCC_ADC12CLKSOURCE_SYSCLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
这就是初始化外设时钟的代码。然后我们把它复制到工程文件中去。
但是复制进去之后出现报错:PeriphClkInit结构体没有定义。
所以我们还需要做的就是把PeriphClkInit结构体的定义复制过来:(还是在模板工程的void SystemClock_Config(void)
函数中)
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
也就是我们编程工程里面的SystemClock_Config(void)
函数最终也应该和模板工程中一样,是这样的:
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV2;
RCC_OscInitStruct.PLL.PLLN = 20;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
{
Error_Handler();
}
/** Initializes the peripherals clocks
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC12;
PeriphClkInit.Adc12ClockSelection = RCC_ADC12CLKSOURCE_SYSCLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
将adc_config的define取消注释:
- 进入
main.h
文件 - 进入
stm32g4xx_hal.h
文件 - 进入
stm32g4xx_hal_conf.h
文件 - 将
#define HAL_ADC_MODULE_ENABLED
取消注释
添加adc对应的库文件
在“ USER CODE BEGIN 3 ”之后编写自己的代码:
打开"adc.h"可以看到已经为我们生成了编辑ADC1和ADC2的结构体:
那么我们在main函数中就这样写(以ADC1为例):
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "gpio.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 ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
//adc
u16 adc1_value;//用于接收HAL_ADC_GetValue函数的返回值。虽然其返回值是u32类型的,但是由于adc最高是12位,所以用u16数据类型即可。
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_ADC1_Init();
MX_ADC2_Init();
/* USER CODE BEGIN 2 */
LCD_Init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
LCD_Clear(Blue);
LCD_SetBackColor(Blue);
LCD_SetTextColor(White);
LCD_DisplayStringLine(Line0, (uint8_t *)" ");
LCD_DisplayStringLine(Line1, (uint8_t *)" ");
LCD_DisplayStringLine(Line2, (uint8_t *)" LCD Test ");
LCD_DisplayStringLine(Line3, (uint8_t *)" ");
LCD_DisplayStringLine(Line4, (uint8_t *)" ");
LCD_SetBackColor(White);
LCD_SetTextColor(Blue);
LCD_DisplayStringLine(Line5, (uint8_t *)" ");
LCD_DisplayStringLine(Line6, (uint8_t *)" HAL LIB ");
LCD_DisplayStringLine(Line7, (uint8_t *)" ");
LCD_DisplayStringLine(Line8, (uint8_t *)" @80 ");
LCD_DisplayStringLine(Line9, (uint8_t *)" ");
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_ADC_Start(&hadc1);
adc1_value=HAL_ADC_GetValue(&hadc1);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV2;
RCC_OscInitStruct.PLL.PLLN = 20;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
{
Error_Handler();
}
/** Initializes the peripherals clocks
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC12;
PeriphClkInit.Adc12ClockSelection = RCC_ADC12CLKSOURCE_SYSCLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
编译完成后即可下载到开发板。
3.仿真环境运行代码:
之后可以用仿真来观察adc1_value的变化情况:
点击仿真按钮进入仿真环境:
点击"watch"调出变量观察窗口:
将adc1_value复制到变量观察器中:
![在这里插入图片描述](https://img-blog.csdnimg.cn/03d89b6a3000460d9ac13d69c7a9fc23.png
点击运行:
可以发现在转动旋转定位器的过程中adc1_value的值会在0-4095范围之间变化。说明试验成功!
如果有想要工程文件的小伙伴可以留言,我看有没有人要,有人要的话我就传一下。