【外设篇】STMG4芯片ADC的STM32CubeMx配置(基础工程)

引言:ADC比较复杂,本文先给大家HAL库调用所用到的函数,然后介绍原理和配置的参数,最后配置stm32cubemx软件,实现ADC读取三相电机的母线电压(读电流然后转换为电压)和U、V、W三相电流,内容较多较难。

1.HAL库ADC的API调用

序号HAL库函数说明
1HAL_ADC_Start()启动 ADC 转换(单次或连续转换)
2HAL_ADC_Stop()停止 ADC 转换。
3HAL_ADC_PollForConversion()轮询等待 ADC 转换完成。
4HAL_ADC_GetValue()获取当前 ADC 转换的数值
5HAL_ADC_GetState()获取 ADC 当前的状态(空闲、忙碌、转换完成等)
6HAL_ADC_ConvCpltCallback()当 ADC 转换完成时会触发此回调函数
7HAL_ADC_ConvHalfCpltCallback()当使用 DMA 模式时,半传输完成会触发此回调函数
8HAL_ADC_LevelOutOfWindowCallback()当 ADC 的输入超出设定的模拟窗口值时触发此回调函数
9HAL_ADCEx_Calibration_Start( &hadc1, ADC_SINGLE_ENDED);ADC自校验
10__HAL_ADC_CLEAR_FLAG( &hadc1, ADC_FLAG_JEOC);
    __HAL_ADC_CLEAR_FLAG( &hadc1, ADC_FLAG_EOC);
清除标志位

2.ADC原理介绍

2.1定义

        将电信号转换为数字信号,又称模数转换器

2.2类型

        分为并联比较器和逐次逼近比较器,其中并联比较器转换快但成本高,还不准确;而逐次逼近型分辨率高,但读取速度慢,因为需要反复比较数值大小,保证分辨率。

2.3框图简介

来自正点原子hal库教程,第三节会按照这个流程介绍个部分组成、类型、作用。

3.ADC配置选项介绍

3.1参考电压

VDDA既是模拟电压,也是ADC的参考电压,通常是将+3.3V电源滤波再给个电感,使VDDA稳定在3.3V,保证ADC读取模拟量的准确性,注意VDDA不能给其他外设使用,会影响其稳定性的。

3.2转换序列

        一般情况,规则组有16个,注入组有4个。

        涉及到使用的话,只需要知道注入组的优先级高,在规则组运行过程中,注入组被触发可以打断规则组运行,如图所示(来源正点原子hal库教程)

3.3触发源

分为:软件触发和硬件触发

(1)软件触发,比如先使能ADC,然后直接用程序触发即可

        HAL_ADC_Start(&hadc2);
        adc_vbus = HAL_ADC_GetValue(&hadc2);

(2)硬件触发,则可以通过时钟触发或者外部事件触发,比如用时钟1的通道4,不输出PWM形式,当其TIM1_IN4输出高电平,有上升沿时,触发ADC中断;外部事件,比如GPIO输入模式接受到一个上升沿触发ADC、或者芯片接受到其他外设的上升沿。如下既是ADC注入组中断回调函数

        void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef *hadc)

3.4转换时间

1)首先计算ADC周期时间

        ADC的时钟源,根据下图的芯片手册说明,system时钟是ADC的异步时钟源

        我们选择异步时钟4分频,系统时钟是160MHZ,那么ADC的时钟频率是40MHZ

(2)最后计算转换时间

        根据芯片手册,ADC采样时间是图中的第一个公式

        我们的ADC频率是40MHZ,SMP选择6.5周期的,那么ADC采样时间计算如下

T_{CONV} = (6.5 + 12.5)*(1/30) = 0.6us

3.5数据寄存器(如何读取数据)

(1)寄存器方式(建议)

adc1_in1 = hadc1.Instance->JDR1 ;
adc1_in2 = hadc1.Instance->JDR2 ;
adc1_in3 = hadc1.Instance->JDR3 ;

(2)Hal库函数

规则组只有一个

软件触发模式下,调用 HAL_ADC_Start() ,规则组的转换结果就会更新,再调用HAL_ADC_GetValue() 读取 ADCx->DR 寄存器中的值,只能用于读取规则组的值

HAL_ADC_Start(&hadc2);                              // 启动规则组转换
HAL_ADC_PollForConversion(&hadc2, HAL_MAX_DELAY);  // 轮询等待转换完成
uint32_t adc_value = HAL_ADC_GetValue(&hadc2);     // 获取规则组的值

规则组有两个

需要设置为扫描模式、DMA储存,其中DMA只需要启动一次,就能连续转换并存储DMA中

uint32_t adc_values[2];  // 存储两个通道的结果

// 启动规则组转换,并使用 DMA
HAL_ADC_Start_DMA(&hadc2, (uint32_t*)adc_values, 2);

// adc_values[0] 为通道 1 的值
// adc_values[1] 为通道 2 的值

3.6中断(ADC中断回调、DMA回调、溢出回调)

中断回调、DMA回调、溢出回调

HAL_ADC_ConvCpltCallback();//中断回调
HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef *hadc);//注入组中断回调
HAL_ADC_ConvHalfCpltCallback();//DMA中断回调
HAL_ADC_LevelOutOfWindowCallback();//溢出回调

3.7转换模式(转换手动?自动?)

单次转换:只触发一次转换

连续转换:自动触发下一次转换

3.扫描模式(一次转换多少个ADC)

扫描关闭:仅规则组(若触发)和注入组(若触发)的第一次转换

扫描开启:规则组(若触发)和注入组(若触发)全部转换

3.不同模式组合及应用场合

        当你需要读取多个ADC,那你就选多通道,扫描模式;反之,你只需要读一个ADC,就单通道,不扫描模式

        当你不想用DMA,那就是单次扫描,每次想读ADC时,需要触发ADC去读一次;反之就是连续扫描,系统自动读,不再需要触发ADC,再读ADC

        至于规则组和注入组,重要的数据给注入组,不重要的数据给规则组,那些不常变化、变化小的、对系统影响相对小的数据,比如:母线电压,就是规则组数据

转换模式扫描模式
单次不扫描ADC单通道,对规则组第1个或注入组第1个一次转换
单次扫描ADC多通道,所有通道都转换一次
连续不扫描ADC单通道,对规则组第1个或注入组第1个连续转换
连续扫描ADC多通道,对所有通道连续转换

4.stm32cubemx配置

        实验目标:读取母线电压和三相电流,配置为母线电压为规则组,三相电流为注入组,ADC多通道扫描模式,单次转换(也可以开启DMA,配置为连续转换)。

先开启4个ADC

配置母线电压,软件触发,数据不是太重要,采样时间可以短一点

配置三相电流,硬件触发,TIM1_IN4作为触发源,数据重要,采样时间可以长点

最后打开中断(如果你选择连续转换,还需要打开DMA)

5.keil代码解析

首先在main初始化ADC,在主循环那读取母线电压

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_USART2_UART_Init();
  MX_ADC1_Init();
  MX_TIM1_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
  //ADC
  HAL_ADCEx_Calibration_Start( &hadc1, ADC_SINGLE_ENDED);
	__HAL_ADC_CLEAR_FLAG( &hadc1, ADC_FLAG_JEOC);
	__HAL_ADC_CLEAR_FLAG( &hadc1, ADC_FLAG_EOC);
	HAL_ADCEx_InjectedStart_IT(&hadc1);
  //TIM4编码器模式
  HAL_TIM_Base_Start(&htim3);
  HAL_TIM_Encoder_Start_DMA(&htim3,TIM_CHANNEL_1|TIM_CHANNEL_2,&encoderValue[0],&encoderValue[1],2);
  //TIM1高级定时器
  TIM1->ARR = 8000 - 1;
	TIM1->CCR4 = 8000 - 2;
	HAL_TIM_Base_Start( &htim1);
	HAL_TIM_PWM_Start( &htim1, TIM_CHANNEL_4);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    //ADC规则组软件触发读取母线电压
    HAL_ADC_Start(&hadc1);
		adc_vbus = HAL_ADC_GetValue(&hadc1);
		rtU.v_bus = adc_vbus*0.0887694f;
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

        最后是在ADC注入组中断回调函数里,读取三相电流,其他的代码讲解就不罗列在此了,可以看后续的外设篇讲解或在生成式AI里询问。

            adc1_in1 = hadc1.Instance->JDR1 ;
            adc1_in2 = hadc1.Instance->JDR2 ;
            adc1_in3 = hadc1.Instance->JDR3 ;


/**************************** 4.ADC+3.ABZ编码器+5.高级定时器+2.串口****************************/
void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef *hadc)
{
	static uint8_t cnt;
  static uint8_t prev_state;
  /* Prevent unused argument(s) compilation warning */
  UNUSED(hadc);
	if(hadc == &hadc1)
	{
		if(ADC_offset == 0)
		{
			cnt++;
			adc1_in1 = hadc1.Instance->JDR1;//读取注入组三相电流
			adc1_in2 = hadc1.Instance->JDR2;
			adc1_in3 = hadc1.Instance->JDR3;

			IA_Offset += adc1_in1;//10次累计再做平均,实习滤波,算出电流0点偏移量
			IB_Offset += adc1_in2;
			IC_Offset += adc1_in3;

			if(cnt >= 10)
			{
				ADC_offset = 1;
				IA_Offset = IA_Offset/10;
				IB_Offset = IB_Offset/10;
				IC_Offset = IC_Offset/10;
			}
		}
		else
		{
			adc1_in1 = hadc1.Instance->JDR1 ;
			adc1_in2 = hadc1.Instance->JDR2 ;
			adc1_in3 = hadc1.Instance->JDR3 ;
                                      
			rtU.ia = (adc1_in1 - IA_Offset)*0.00149025f;
			rtU.ib = (adc1_in2 - IB_Offset)*0.00149025f;
			rtU.ic = (adc1_in3 - IC_Offset)*0.00149025f;

      
      //编码器读取ABZ
      Enc_cnt = __HAL_TIM_GET_COUNTER(&htim3) ;  
      
      m_Theta = (Enc_cnt + amend_Theta)*2*PI/16384;
      while(m_Theta >= 2*PI){m_Theta -= 2*PI;}
      while(m_Theta < 0){m_Theta += 2*PI;}//角度归一化
      //转速计算
      m_speed = (m_Theta - m_Theta_delay)/0.0001;
      m_Theta_delay = m_Theta;
       
      //电角度、机械角度计算
      e_Theta = m_Theta *pole_pairs;
      while(e_Theta >= 2*PI){e_Theta -= 2*PI;}
      while(e_Theta < 0){e_Theta += 2*PI;}//角度归一化
      // 电角度0对齐
      if(prev_state == 2 && state == 3) {
        __HAL_TIM_SET_COUNTER(&htim3, 0);
        e_Theta = 0;
      }
      prev_state = state;
      
      //位置计算
      m_positionfd = Position_Cal(m_prevTheta, m_Theta);
      m_prevTheta = m_Theta;
      
      //赋值给Simulink模型
      rtU.FOC_state = 1;
      rtU.PositionRef = 0;
      rtU.PositionFd = m_positionfd;
      rtU.theta = e_Theta;
      rtU.torque_ref = 1;
      FOC_Model_step();
      
      if(state == 2){
        TIM1->CCR1 = 0;
        TIM1->CCR2 = 30;
        TIM1->CCR3 = 100;
      }
      else{
			TIM1->CCR1 = rtY.tABC[0];
			TIM1->CCR2 = rtY.tABC[1];
			TIM1->CCR3 = rtY.tABC[2];
      }
//			TIM1->CCR1 = rtY.tABC[0];
//			TIM1->CCR2 = rtY.tABC[1];
//			TIM1->CCR3 = rtY.tABC[2];
      
			load_data[0] = rtU.ia;
			load_data[1] = adc1_in1;
			load_data[2] =IA_Offset;
			load_data[3] = state;
			load_data[4] = e_Theta;
			memcpy(tempData, (uint8_t *)&load_data, sizeof(load_data));
			HAL_UART_Transmit_DMA(&huart2,(uint8_t *)tempData,6*4);

		}
	}

  /* NOTE : This function should not be modified. When the callback is needed,
            function HAL_ADCEx_InjectedConvCpltCallback must be implemented in the user file.
  */
}
/**************************** 4.ADC+3.ABZ编码器+5.高级定时器+2.串口****************************/

结果:三相电流处于中间值4096的一半,是因为硬件上有个1.65V的直流偏执,下章TIM里改为时钟触发ADC采样


结语:本文内容十分难懂,建议结合着正点原子hal库视频观看

内容较多,难免出错,如有错误,感谢指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值