ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《DMG474电机控制专题教程V1.1 》第23章 无刷电机电压温度电流采集

《DMG474电机控制专题教程V1.1 》第23章 无刷电机电压温度电流采集

原创 嵌入式/内核开发 作者:正点原子 时间:2026-04-30 09:39:48 0 删除 编辑

第23章 无刷电机电压温度电流采集


       前面我们已经学会了无刷电机的基本驱动,本章我们将利用无刷驱动板的电路功能,来检测电机的三相电流以及驱动板的电源电压和温度。

       本章分为如下几个小节:

       23.1 驱动板硬件电路原理分析

       23.2 硬件设计

       23.3 程序设计

       23.4 下载验证


        23.1驱动板硬件电路原理分析

       下面我们主要分为三部分来讲解驱动板的硬件电路,包括:三相电流采集电路分析、电源电压采集电路分析、温度采集电路分析共三部分。


       23.1.1 三相电流采集电路分析 


图23.1.1.1 U相电流采集电路


       图23.1.1.1为U相电流采集电路(U、V、W三相同理,这里只以U相为例),其中R17为采样电阻(20mR),当有电流 I 流过采样电阻时,采样电阻上就会产生一个电压,电压大小为: I*0.02Ω(20mR)。我们使用 I-V 表示电流 I 经过采样电阻后形成的电压,所以U相的电压,我们使用 I-V_U表示。

       那么现在只需要测出电压I-V_U的值,就可以换算出U相电流,但是这里我们发现一个问题,假设U相电流为10A,此时电压I-V_U = 10 * 0.02 V = 0.2V,同理当电流为20A时,求得的电压值I-V_U = 0.4V,可以看到当电流比较大的情况下,求得的电压值是非常小的,不利于我们的ADC采集,考虑到ADC的精度问题,所以我还需将电压经过差分放大电路放大后在来采集,如下图:


图23.1.1.2 差分放大电路


       可以从图23.1.1.2中得出该差分放大电路的放大倍数为:Diff = 12K/(1K+1K)= 6倍,并有一抬升电压1.25V,所以电压放大后的输出为: AMP_IU = 6 * ( 0.02*I ) + 1.25V。由于AMP_IU是接到电机开发板的ADC引脚的,所以该值是已知的,此时就可以计算出U相电流I的值。


       23.1.2 电源电压采集电路分析 


图23.1.2.1 电源电压采集电路


       图23.1.2.1 为驱动板板载的电源电压检测电路。驱动板的供电电压POWER经过电阻分压后进入 LMV358 的 A 路运放,该运放为电压跟随电路,所以1脚输出电压VBUS 和电阻分压的值相同,即 VBUS = POWER/(12K+12K+1K)*1K,当ADC采集到VBUS电压后就可以计算出 POWER 电压。


       23.1.3 温度采集电路分析 


图23.1.3.1 温度采集电路


       图23.1.3.1为温度检测电路,可以看到,NTC电阻和固定的4.7K电阻分压,然后接入B路运放,B路运放也是电压跟随电路,所以此时VTMEP = 3.3V/(Rt+4.7K)*4.7K,通过ADC采集VTMEP的电压后就可以算出Rt的值,其中 Rt 就表示 NTC 电阻在当前温度下的电阻值。从NTC电阻的数据手册可以找到温度计算公式:T1=1 / ( In (Rt/R0) / B + 1/T2 ),这里的T1和T2指的是K度即开尔文温度,K度 = 273.15(绝对温度) + 摄氏度;其中:

       l T2  :273.15 + 25(25摄氏度下的K度值)

       l T1  :实际温度

       l Rt :热敏电阻在T1温度下的阻值;

       l R0 :热敏电阻在T2常温下的标称阻值;

       l B值 :热敏电阻的重要恒定参数;

       其中T1是我们需要求解的温度值,Rt的大小由前面的温度采集电路可以算出,其他的参数都是可以在数据手册中查找得出,代入温度求解公式即可算出T1温度值,注意该值为K度。

       注意:我们所使用的温度换算公式以及相关参数均来自《NCP18XH103F03RB 热敏电阻.pdf》这个数据手册,路径:A盘→6,硬件资料→2,芯片资料文件夹下可以找到。


        23.2 硬件设计


       1、例程功能

       l 本实验使用无刷电机接口一连接无刷电机驱动板

       l 当按下KEY0一次加速旋转,按一次KEY1减速旋转,按下KEY2则停止旋转。

       l LED0作为程序运行状态指示灯,闪烁周期200ms。

       l 测量并在LCD和串口显示无刷电机电压、电流和温度


       2、硬件资源

       1)LED灯:LED0 – PE0

       2)独立按键

              KEY0 – PE12

              KEY1 – PE13

              KEY2 – PE14

       3)定时器:  

              TIM1_CH1:PA8

              TIM1_CH2:PA9

              TIM1_CH3:PA10

       4)IO:

              下桥臂IO:PB13\14\15

              霍尔IO: PA0\1\2

              使能IO:SHDN: PC13

       5)无刷电机

       6)无刷电机驱动板

       7)12-60V的DC电源(建议使用电机额定电压24V)

       8)ADC:

              ADC12_CH6(温度)  :PC0

              ADC1_CH4(电源电压) :PA3

              ADC12_CH7(U相电流) :PC1

              ADC12_CH8(V相电流) :PC2

              ADC12_CH9(W相电流) :PC3


       3、原理图


图23.2.1 无刷电机接口1原理图


       相较于上一章,本实验多了5个模拟采集引脚,分别是:电源电压(ADC1_CN4/PA3)、 温度(ADC1_CN6/PC0)、三相电流UVW (ADC1_CN7/PC1、ADC1_CN8/PC2、ADC1_CN9/PC3) 。接口所涉及的IO如下:


表23.2.1 无刷相关IO口说明


       本章节的硬件接线部分和基础驱动一模一样,注意:驱动板的JP3跳线帽短接H&Z与HALL,实物连接如下:


2.2 开发板&驱动板连接图


        23.3 程序设计


       23.3.1 无刷电机参数采集的配置步骤 

       1)实现电机基本驱动

       实现电机的基础驱动函数,启停、6步换向组合等。

       2)初始化ADC&DMA

       初始化ADC通道的IO,设置ADC工作方式、DMA等。

       3)转速控制

       设置电机旋转方向及PWM占空比。

       4)编写中断服务函数

       PWM中断用于换向控制、采集电机未开始转时的基准电压,在将旋转后得到的电压进行相减得到实际电压等。


       23.3.2 程序流程图


图23.3.2.1 无刷电机基本控制流程图


       23.3.3 程序解析

       该程序是在上一章的工程基础上添加的,大部分相同,这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。定时器驱动相关源码与上一章节一致,这里不再赘述。我们先看ADC相关的程序,ADC驱动源码包括两个文件:adc.c和adc.h。

       首先看adc.h头文件的内容:

/* ADC及引脚 定义 */
#define ADC_VBUS_CHX_GPIO_PORT              GPIOA                                               /* 电源电压采集引脚 */
#define ADC_VBUS_CHX_GPIO_PIN               GPIO_PIN_3
#define ADC_VBUS_CHX_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
 
#define ADC_VTEMP_CHX_GPIO_PORT             GPIOC                                               /* 温度采集引脚 */
#define ADC_VTEMP_CHX1_GPIO_PIN             GPIO_PIN_0
#define ADC_VTEMP_CHX_GPIO_CLK_ENABLE()     do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
 
#define ADC_AMPU_CHX_GPIO_PORT              GPIOC                                               /* U相采集引脚 */
#define ADC_AMPU_CHX_GPIO_PIN               GPIO_PIN_1
#define ADC_AMPU_CHX_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
 
#define ADC_AMPV_CHX_GPIO_PORT              GPIOC                                               /* V相采集引脚 */
#define ADC_AMPV_CHX_GPIO_PIN               GPIO_PIN_2
#define ADC_AMPV_CHX_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
 
#define ADC_AMPW_CHX_GPIO_PORT              GPIOC                                               /* W相采集引脚 */
#define ADC_AMPW_CHX_GPIO_PIN               GPIO_PIN_3
#define ADC_AMPW_CHX_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
 
 
#define ADC_ADCX                            ADC1
#define ADC_VBUS_CHX                        ADC_CHANNEL_4                                       /* 电源电压采集ADC通道 */ 
#define ADC_VTEMP_CHX                       ADC_CHANNEL_6                                       /* 驱动板温度采集ADC通道 */ 
#define ADC_AMPU_CHX                        ADC_CHANNEL_7                                       /* U相电流采集ADC通道 */ 
#define ADC_AMPV_CHX                        ADC_CHANNEL_8                                       /* V相电流采集ADC通道 */ 
#define ADC_AMPW_CHX                        ADC_CHANNEL_9                                       /* W相电流采集ADC通道 */ 
#define ADC_ADCX_CHY_CLK_ENABLE()           do{ __HAL_RCC_ADC12_CLK_ENABLE(); }while(0)   /* ADC1 时钟使能 */
 
#define ADC_DMA_BUF_SIZE        50           /* 每个ADC通道的 DMA采集次数 */
#define ADC_CH_NUM              5           /* ADC 通道数量 */
#define ADC_SUM                 ADC_CH_NUM * ADC_DMA_BUF_SIZE  /* 总采集次数 */
/* ADC单通道/多通道 DMA采集 DMA通道相关 定义
 * 注意: 这里我们的通道还是使用上面的定义.
 */
#define ADC_ADCX_DMASx                      DMA1_Channel1
#define ADC_ADCX_DMASx_REQUEST              DMA_REQUEST_ADC1        
#define ADC_ADCX_DMASx_IRQn                 DMA1_Channel1_IRQn
#define ADC_ADCX_DMASx_IRQHandler           DMA1_Channel1_IRQHandler

       这里主要分为三部分,第一部分ADC通道所对应的IO以及时钟使能的宏定义;第二部分为本例程所使用的ADC通道宏定义,第三部分为ADC相关的DMA宏定义以及ADC的转换次数的宏定义。接着看下ADC的初始化程序,如下:

/**
 * @brief       ADC初始化函数
 *   @note      本函数支持ADC1/ADC2任意通道, 但是不支持ADC3
 *              我们使用12位精度, ADC采样时钟=170/4 = 42.5M,
转换时间为:采样周期+ 12.5个ADC周期
 *              设置最大采样周期: 640.5, 则转换时间 = 653 个ADC周期 = 0.15us
 * @param       无
 * @retval      无
 */
void adc_init(void)
{
g_adc_handle.Instance = ADC_ADCX;
/* 4分频,ADCCLK = SYSCLK/4 = 170/4 = 42.5Mhz */
    g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;            
    g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B;   /* 12位模式 */
    g_adc_handle.Init.GainCompensation = 0;               /* 不需要增益补偿 */
    g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;   /* 右对齐 */
    g_adc_handle.Init.ScanConvMode = ADC_SCAN_ENABLE;     /* 扫描模式 */
    g_adc_handle.Init.EOCSelection = ADC_EOC_SEQ_CONV; /* 规则序列转换结束标志 */
    g_adc_handle.Init.LowPowerAutoWait = DISABLE;     /* 禁用低功耗延迟模式 */
g_adc_handle.Init.ContinuousConvMode = ENABLE;    /* 使能连续转换 */
/* ADC_CH_NUM个转换在规则序列中 */
g_adc_handle.Init.NbrOfConversion = ADC_CH_NUM;  
/* 禁止不连续采样模式 */
g_adc_handle.Init.DiscontinuousConvMode = DISABLE;
    g_adc_handle.Init.NbrOfDiscConversion = 0;      /* 不连续采样通道数为0 */
g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;  /* 软件触发 */
/* 使用软件触发 */
    g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; 
g_adc_handle.Init.DMAContinuousRequests = ENABLE;  /* 开启DMA请求 */
/* 数据溢出时覆盖ADC数据 */
g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
    g_adc_handle.Init.OversamplingMode = DISABLE;    /* 禁用过采样功能 */
    HAL_ADC_Init(&g_adc_handle);                      /* 初始化 */
    /* 以单端模式运行ADC校准 */
    HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);           
}

       该函数将ADC设置为软件触发连续转换,并配置了ADC采集的通道数等等信息,关于IO的初始化在回调函数HAL_ADC_MspInit中执行,感兴趣可以打开源码查看,接着我们看下DMA的初始化函数,内容如下:

/**
 * @brief       ADC DMA读取 初始化函数
 *   @note      本函数还是使用adc_init对ADC进行大部分配置,有差异的地方再单独配置
 * @param       par         : 外设地址
 * @param       mar         : 存储器地址
 * @retval      无
 */
void adc_dma_init(void)
{
    __HAL_RCC_DMAMUX1_CLK_ENABLE();
    __HAL_RCC_DMA1_CLK_ENABLE();
 
    /* DMA配置 */
    g_dma_adc_handle.Instance = ADC_ADCX_DMASx;             /* 设置DMA通道*/
g_dma_adc_handle.Init.Request= ADC_ADCX_DMASx_REQUEST;   /* 设置DMA请求*/
/* DIR = 1 ,  外设到存储器模式 */
    g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; 
g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;  /*外设非增量模式*/
g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE;        /*存储器增量模式*/
/* 外设数据长度:16位 */
g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;    
/* 存储器数据长度:16位 */
    g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;       
    g_dma_adc_handle.Init.Mode = DMA_CIRCULAR;          /* 外设流控模式 */
    g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;    /* 中等优先级 */
    HAL_DMA_Init(&g_dma_adc_handle);                         /* 初始化DMA */
    
    adc_init();                                               /* 初始化ADC */
 
    /* 配置对应ADC通道 */
adc_channel_set(&g_adc_handle, ADC_VBUS_CHX, ADC_REGULAR_RANK_1, 
ADC_SAMPLETIME_640CYCLES_5);
adc_channel_set(&g_adc_handle, ADC_VTEMP_CHX, ADC_REGULAR_RANK_2, 
ADC_SAMPLETIME_640CYCLES_5);
adc_channel_set(&g_adc_handle, ADC_AMPU_CHX, ADC_REGULAR_RANK_3, 
ADC_SAMPLETIME_640CYCLES_5);
adc_channel_set(&g_adc_handle, ADC_AMPV_CHX, ADC_REGULAR_RANK_4, 
ADC_SAMPLETIME_640CYCLES_5);
adc_channel_set(&g_adc_handle, ADC_AMPW_CHX, ADC_REGULAR_RANK_5, 
ADC_SAMPLETIME_640CYCLES_5);
    
    __HAL_LINKDMA(&g_adc_handle, DMA_Handle,g_dma_adc_handle);
    /* 设置DMA中断优先级为3,子优先级为3 */
    HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 3, 3);                        
    HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn);   /* 使能DMA中断 */
    
HAL_ADC_Start_DMA(&g_adc_handle, (uint32_t*)&g_adc_dma_buf, 
ADC_DMA_BUF_SIZE);         /* 开始DMA数据传输 */
}

       首先初始化DMA的配置,并调用ADC初始化函数。DMA存储的地址就是数组g_adc_dma_buf,传输的数据量大小为:ADC_DMA_BUF_SIZE ,如果对于ADC与DMA的配置部分还不熟悉,大家可以回顾下4.4小节。然后我们还配置了ADC的转换顺序,1~5分别对应:电源电压、温度、U相电压、V相电压、W相电压,并且采样周期我们均配置最长的ADC_SAMPLETIME_640CYCLES_5,这样会使采样的ADC值更为准确。

       接着来到ADC转换完成中断回调函数用于计算ADC平均值,程序如下:

/**
 * @brief       ADC转换完成的回调函数
 * @param       无
 * @retval      无
 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    if (hadc->Instance == ADC1) 
    { 
        HAL_ADC_Stop_DMA(&g_adc_handle);      /* 关闭DMA转换 */ 
/* ADC数值转换 */                                         
        calc_adc_val(g_adc_dma_buf, g_adc_val, ADC_CH_NUM, ADC_DMA_BUF_SIZE);       
        HAL_ADC_Start_DMA(&g_adc_handle, (uint32_t *)&g_adc_dma_buf, 
(uint32_t)(ADC_CH_NUM * ADC_DMA_BUF_SIZE));  /* 再启动DMA转换 */
    }
}

       在ADC中断回调函数中,先关闭DMA转换,然后调用calc_adc_val函数计算ADC平均值(滤波),calc_adc_val函数我们后面再介绍。滤波后的ADC值存储到g_adc_val这个数组当中,我们将通过滤波后的ADC值来进行电压、温度以及电流的计算。计算完ADC平均值后,再次启动DMA转换。

       接下来看电压、电流以及温度计算相关的程序,包括两个文件:bldc.c和bldc.h,我们这里只介绍本实验新增的内容。

       首先看bldc.h头文件的几个宏定义:

/* ADC采集值 * 3.3/4.096 (mv) = 6 * ( 0.02*I ) */
#define ADC2CURT    (float)(3.3f / 4.096f / 0.12f)   
 
/* ADC采集值 * 3.3/4096 (V)= POWER/(12K+12K+1K)*1K */
#define ADC2VBUS    (float)(3.3f * 25 / 4096)

        这里定义的是电流、电压计算相关的宏,其中涉及的公式是根据我们前面介绍的电流、电压采集电路原理得出的,当我们需要计算电流和电压的时候,直接调用相应的宏定义即可,具体的公式推导过程大家可以直接看代码的注释。

       接着我们来看下ADC平均值计算函数。平均值计算函数calc_adc_val在adc.c原文件中实现,内容如下:

/**
 * @brief       计算ADC的平均值(滤波)
 * @param       * p :代入ADC原始值
 * @param       * buf :存放滤波后的ADC值
 * @param       ch_num :采集的ADC通道数量
 * @param       len :每个通道的采集次数
 * @note        此函数对电压、温度、电流对应的ADC值进行滤波
 * @retval      无
 */
void calc_adc_val(uint16_t *p, uint16_t *buf , uint16_t ch_num, uint16_t len)
{
    uint32_t temp[ADC_CH_NUM] = {0};      /* 定义一个缓存数组 */
    int i , j;                                              
    for (i = 0 ; i < len ; i++)              /* 根据ADC通道数循环获取,并累加 */
    {
        for (j = 0 ; j < ch_num ; j++)        /* 将采集到的ADC值,通道进行累加*/
        {
            temp[j] += p[j + i * ch_num];
        }
    }
    for (j = 0 ; j < ch_num ; j++)
    {
        temp[j] /= len;                     /* 获取平均值 */
        buf[j] = temp[j];                    /* 将滤波后的值存到buf里 */
    }
}

       该函数将5个通道采集的数据,进行软件滤波取平均并将滤波后的数据保留在入口参数指针buf的地址中。

       接下来看温度值计算函数,其定义如下:

/*
    Rt = Rp *exp(B*(1/T1-1/T2))
 
    Rt 是热敏电阻在T1温度下的阻值;
    Rp是热敏电阻在T2常温下的标称阻值;
    exp是e的n次方,e是自然常数,就是自然对数的底数,近似等于 2.7182818;
    B值是热敏电阻的重要参数,教程中用到的热敏电阻B值为3380;
    这里T1和T2指的是开尔文温度,T2是常温25℃,即(273.15+25)K
    T1就是所求的温度
*/
const float Rp = 10000.0f;             /* 10K */
const float T2 = (273.15f + 25.0f);  /* T2 */
const float Bx = 3380.0f;              /* B */
const float Ka = 273.15f;
 
/**
 * @brief       计算温度值
 * @param       para: 温度采集对应ADC通道的值(已滤波)
 * @note        计算温度分为两步:
                 1.根据ADC采集到的值计算当前对应的Rt
                 2.根据Rt计算对应的温度值
 * @retval      温度值
 */
float get_temp(uint16_t para)
{
    float Rt;
    float temp;
    
    /*
        第一步:
        Rt = 3.3 * 4700 / VTEMP - 4700 ,其中VTEMP就是温度检测通道采集回来的电压值,VTEMP = ADC值* 3.3/4096
        由此我们可以计算出当前Rt的值:Rt = 3.3f * 4700.0f / (para * 3.3f / 4096.0f ) - 4700.0f;
    */
    Rt = 3.3f * 4700.0f / (para * 3.3f / 4096.0f ) - 4700.0f;    
 
    /*
        第二步:
        根据当前Rt的值来计算对应温度值:Rt = Rp *exp(B*(1/T1-1/T2))
    */
    
    temp = Rt / Rp;    /* 解出exp(B*(1/T1-1/T2)) temp=exp(B*(1/T1-1/T2))*/
    temp = log(temp);   /* 解出B*(1/T1-1/T2) ,即temp = B*(1/T1-1/T2) */
    temp /= Bx;         /* 解出1/T1-1/T2 ,即temp = 1/T1-1/T2 */
    temp += (1.0f / T2); /* 解出1/T1 ,即temp = 1/T1 */
    temp = 1.0f / (temp);  /* 解出T1 ,即temp = T1 */
    temp -= Ka;             /* 计算T1对应的摄氏度 */
    return temp;       /* 返回温度值 */
}

       代码中首先定义了一些关于热敏电阻的参数,各参数的含义我们在代码注释中已经详细介绍,它们的值是根据实际的热敏电阻型号来确定的,详见《NCP18XH103F03RB 热敏电阻.pdf》这个数据手册,路径:A盘→6,硬件资料 →2,芯片资料 文件夹下可以找到。

       温度计算函数的实现分为两个步骤:

       第一步:根据采集到的ADC值计算出当前热敏电阻阻值Rt,公式如下:

Rt = 3.3 * 4700 / VTEMP - 4700

       公式中VTEMP是温度检测通道对应的电压值,结合ADC换算公式VTEMP = ADC值* 3.3/4096可得Rt= 3.3 * 4700/ (ADC值 * 3.3 / 4096 ) - 4700。

       第二步:根据当前Rt的值来计算对应温度值,公式如下:

Rt = Rp * exp( B * ( 1/T1 - 1/T2 ) )

       公式中的T1就是所求的温度,它的默认单位是华氏度,我们最后还需要将其转换成摄氏度。温度计算函数的实现本质上就是这两个公式的代码体现,大家一定要跟着公式去理解代码。

       接着是定时器的中断回调函数,如下:

/**
 * @brief       定时器中断回调
 * @param       htim:定时器句柄
 * @retval      无
 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    uint8_t i;
    static uint8_t times_count = 0;                   /* 定时器时间记录 */
    if(htim->Instance == ATIM_TIMX_PWM)              /* 55us */
    {
#ifdef H_PWM_L_ON
        if (g_bldc_motor1.run_flag == RUN)
        {
            if (g_bldc_motor1.dir == CW)            /* 正转 */
            {
/* 顺序6,2,3,1,5,4 */
                g_bldc_motor1.step_sta = hallsensor_get_state(MOTOR_1);     
            }
            else                                  /* 反转 */
            {
                g_bldc_motor1.step_sta = 7 - hallsensor_get_state(MOTOR_1);
            }
            /* 判断霍尔组合值是否正常 */
            if((g_bldc_motor1.step_sta <= 6)&&(g_bldc_motor1.step_sta >= 1))
            {
/* 通过数组成员查找对应的函数指针 */
                pfunclist_m1[g_bldc_motor1.step_sta-1]();                  
            }
            Else /* 霍尔传感器错误、接触不良、断开等情况 */
            {
                stop_motor1();
                g_bldc_motor1.run_flag = STOP;
            }
            /********************* 三相电流采集 *********************/
            for(i = 0; i < 3; i++)
            {
/* 运动状态ADC值 - 停机状态ADC值 = 实际作用ADC值 */
                adc_val_m1[i] = g_adc_val[i + 2];
                adc_amp[i] = adc_val_m1[i]-
adc_amp_offset[i][ADC_AMP_OFFSET_TIMES];   
                if(adc_amp[i] >= 0) /* 去除反电动势引起的负电流数据 */                                                    
                {
                    adc_amp_un[i] = adc_amp[i];
                }  
            }
            /* 运算母线电流(母线电流为任意两个有开关动作的相电流之和) */
            if(g_bldc_motor1.step_sta == 0x05)
            {  /* UV */
                adc_amp_bus= (adc_amp_un[0] + adc_amp_un[1]) * ADC2CURT;
            }
            else if(g_bldc_motor1.step_sta == 0x01)
            {  /* UW */
                adc_amp_bus= (adc_amp_un[0] + adc_amp_un[2]) * ADC2CURT;   
            }
            else if(g_bldc_motor1.step_sta == 0x03)
            {  /* VW */
                adc_amp_bus= (adc_amp_un[1] + adc_amp_un[2]) * ADC2CURT;   
            }
            else if(g_bldc_motor1.step_sta == 0x02)
            {  /* UV */
                adc_amp_bus= (adc_amp_un[0] + adc_amp_un[1]) * ADC2CURT;   
            }
            else if(g_bldc_motor1.step_sta == 0x06)
            {   /* WU */
                adc_amp_bus= (adc_amp_un[0] + adc_amp_un[2]) * ADC2CURT;  
            }
            else if(g_bldc_motor1.step_sta == 0x04)
            {  /* WV */
                adc_amp_bus= (adc_amp_un[2] + adc_amp_un[1]) * ADC2CURT;   
            } 
        }
#endif
    }
    else if(htim->Instance == BTIM_TIMX_INT)
    {
        /***************** 采集电机停机状态下的偏置电压 ******************/
        times_count++;
        if (g_bldc_motor1.run_flag == STOP)
        {
            uint32_t avg[3] = {0,0,0};
/* 获取电机停机状态下的三相电流 U */
            adc_amp_offset[0][adc_amp_offset_p] = g_adc_val[2];         
            adc_amp_offset[1][adc_amp_offset_p] = g_adc_val[3];  /* V */
            adc_amp_offset[2][adc_amp_offset_p] = g_adc_val[4];  /* W */
            adc_amp_offset_p++;
            /* 如果溢出,从头开始计数 */
            NUM_CLEAR(adc_amp_offset_p, ADC_AMP_OFFSET_TIMES);         
            for (i = 0; i < ADC_AMP_OFFSET_TIMES; i++)
            {
                avg[0] += adc_amp_offset[0][i];      /* 各相数值累加 */
                avg[1] += adc_amp_offset[1][i];
                avg[2] += adc_amp_offset[2][i];
            }
            for (i = 0; i < 3; i++)
            {
                avg[i] /= ADC_AMP_OFFSET_TIMES;          /* 取平均 */
                adc_amp_offset[i][ADC_AMP_OFFSET_TIMES] = avg[i];  /* 赋值 */
            }
        }
    }
}

       关于BLDC的基础驱动部分与上一章一致,不在赘述;这里主要讲下母线电流的采集。

       注意:在电流采集电路的原理介绍中,我们有提到抬升电压的值为1.25V。在实际的应用中,为了使电流采集的值更加精确,我们不是直接减掉该抬升电压,因为驱动板一上电,本身是带有基准电压的,所以会造成电压不准确,所以需在电机还未开始旋转时检测当前的电压值,即在定时器6更新中断里所操作的内容:首先判断电机是否停止,是的话将此时的三相电流所对应的ADC通道采集的ADC值进行累加取平均,即软件滤波,此时滤波后的值才是电机未启动时的基准电压;

       接着当电机旋转时采集此时的三相电压,实际的相电压 = 旋转时电压 – 基准电压;通过该算法求得的即为相电压。母线电压为任意导通的两相电压之和,在乘以前面原理介绍的系数即可换算出电流值。

       最后是main函数:

int main(void)
{
    uint8_t key, t;
    char buf[32];
    int16_t pwm_duty_temp = 0;
    float current[3] = {0.0f};
    float current_lpf[4] = {0.0f};
    
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(85, 2, 2, 4, 8);       /* 设置时钟,170Mhz */
    delay_init(170);                            /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
    led_init();                                 /* 初始化LED */
    key_init();                                 /* 初始化按键 */
lcd_init();                                 /* 初始化LCD */
/* 中断频率18K,约55us, 频率不能过慢或过快,否则都会导致电机堵转 */
    bldc_init((170000 / 18) - 1, 0);            
    btim_timx_int_init(1000 - 1, 170 - 1);      /* 基本定时器初始化,1ms中断一次*/
    bldc_ctrl(MOTOR_1, CW, 0);                  /* 初始无刷电机接口1速度 */
    adc_dma_init();                             /* ADC初始化 */
    
    /* 显示提示信息 */    
    lcd_show_string(10, 10, 239, 24, 24, "BLDC Motor Test", RED);
    lcd_show_string(10, 40, 239, 16, 16, "KEY0:Start forward", RED);
    lcd_show_string(10, 60, 239, 16, 16, "KEY1:Start backward", RED);
    lcd_show_string(10, 80, 239, 16, 16, "KEY2:Stop", RED);
    printf("按下KEY0 开始正转加速\r\n");
    printf("按下KEY1 开始反转加速\r\n");
    printf("按下KEY2 停止电机\r\n");
    
    while (1)
    {
        t++;
        if (t % 20 == 0)
        {
            sprintf(buf,"Power:%.3fV ", g_adc_val[0]*ADC2VBUS);
            lcd_show_string(10, 100, 200, 16, 16, buf, BLUE);
            sprintf(buf,"Temp:%.1fC ", get_temp(g_adc_val[1]));
            lcd_show_string(10, 120, 200, 16, 16, buf, BLUE);           
            LED0_TOGGLE();                           /* LED0(红灯) 翻转 */
            
            current[0] = adc_amp_un[0]* ADC2CURT;    /* 计算出三相电流值,U */
            current[1] = adc_amp_un[1]* ADC2CURT;    /* 计算出三相电流值,V */
            current[2] = adc_amp_un[2]* ADC2CURT;   /* 计算出三相电流值,W */
            
            /*一阶数字滤波 滤波系数0.1 用于显示*/
            FirstOrderRC_LPF(current_lpf[0], current[0], 0.1f); /* U相电流 */
            FirstOrderRC_LPF(current_lpf[1], current[1], 0.1f); /* V相电流 */
            FirstOrderRC_LPF(current_lpf[2], current[2], 0.1f); /* W相电流 */
            FirstOrderRC_LPF(current_lpf[3], adc_amp_bus, 0.1f);/* 母线电流 */
            
            if(g_bldc_motor1.run_flag == STOP)             /* 停机的电流显示 */
            {
                current_lpf[0] = 0;
                current_lpf[1] = 0;
                current_lpf[2] = 0;
                current_lpf[3] = 0;
            }
            /* LCD显示提示信息 */
            sprintf(buf,"Amp U:%.3fmA ", (float)current_lpf[0]);
            lcd_show_string(10, 140, 200, 16, 16, buf, BLUE);
            sprintf(buf,"Amp V:%.3fmA ", (float)current_lpf[1]);
            lcd_show_string(10, 160, 200, 16, 16, buf, BLUE);
            sprintf(buf,"Amp W:%.3fmA ", (float)current_lpf[2]);
            lcd_show_string(10, 180, 200, 16, 16, buf, BLUE);
            sprintf(buf,"Amp Bus:%.3fmA ", (float)current_lpf[3]);
            lcd_show_string(10, 200, 200, 16, 16, buf, BLUE);
            
            /* 串口打印信息 */
            printf("Valtage:%.1fV \r\n", g_adc_val[0] * ADC2VBUS);
            printf("Temp:%.1fC \r\n", get_temp(g_adc_val[1]));
            printf("U相电流为:%.3fmA\r\n", (current_lpf[0]));
            printf("V相电流为:%.3fmA\r\n", (current_lpf[1]));
            printf("W相电流为:%.3fmA\r\n", (current_lpf[2]));
            printf("母线电流为:%.3fmA\r\n", (current_lpf[3]));
            printf("\r\n");
        }
 
        key = key_scan(0);
        if (key == KEY0_PRES)                       /* 按下KEY0设置比较值+500*/
        {
            pwm_duty_temp += 500;
            if (pwm_duty_temp >= MAX_PWM_DUTY / 2)  /* 限速 */
                pwm_duty_temp = MAX_PWM_DUTY / 2;
            if (pwm_duty_temp > 0)                  /* 通过判断正负设置旋转方向*/
            {
                g_bldc_motor1.pwm_duty = pwm_duty_temp;
                g_bldc_motor1.dir = CW;
            }
            else
            {
                g_bldc_motor1.pwm_duty = -pwm_duty_temp;
                g_bldc_motor1.dir = CCW;
            }
            g_bldc_motor1.run_flag = RUN;           /* 开启运行 */
            start_motor1();                         /* 开启运行 */
        }
        else if (key == KEY1_PRES)                  /* 按下KEY1设置比较值-500*/
        {
            pwm_duty_temp -= 500;
            if (pwm_duty_temp <= -MAX_PWM_DUTY / 2)
                pwm_duty_temp = -MAX_PWM_DUTY / 2;
            if (pwm_duty_temp < 0)                  /*通过判断正负号设置旋转方向*/
            {
                g_bldc_motor1.pwm_duty = -pwm_duty_temp;
                g_bldc_motor1.dir = CCW;
            }
            else
            {
                g_bldc_motor1.pwm_duty = pwm_duty_temp;
                g_bldc_motor1.dir = CW;
            }                                                  
            g_bldc_motor1.run_flag = RUN;           /* 开启运行 */
            start_motor1();                         /* 运行电机 */
        }
        else if (key == KEY2_PRES)                  /* 按下KEY2关闭电机 */
        {
            stop_motor1();                          /* 停机 */
            g_bldc_motor1.run_flag = STOP;          /* 标记停机 */
            pwm_duty_temp = 0;                      /* 数据清0 */
            g_bldc_motor1.pwm_duty = 0;
        }
        delay_ms(10);
    }
}

       主函数主要添加了adc初始化函数adc_dma_init,对ADC以及DMA进行初始化。在while循环里面,除了有感方波驱动的内容,我们还利用LDC以及串口1来打印电压、温度以及电流的值,计算过程以及在原理部分讲的很详细了,大家可以原理分析部分。


        23.4 下载验证

       硬件连接好之后下载程序到开发板,液晶屏和串口都会显示程序计算的电压、电流等信息,点击按键KEY0一次可以看到定时器比较值递增500(加速),电机开始旋转,点击一次按键KEY1可以看到定时器比较值递减500(减速),点击按键KEY2可以随时停止无刷电机旋转,并在LCD以及串口显示ADC所采集的电源电压、驱动板温度、以及三相电流。大家可以自行下载程序并点击按键测试效果。


图23.4.1 模拟信号数据


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3117053/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    764313