搜索
bottom↓
回复: 0

《DMF407电机控制专题教程_V1.0》第23章

[复制链接]

出0入234汤圆

发表于 2022-8-20 10:05:42 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2022-8-19 15:34 编辑

1)实验平台:正点原子DMF407电机开发板
2)平台购买地址: https://detail.tmall.com/item.htm?&id=677230699323
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/stm32dj/ATK-DMF407.html
4)对正点原子电机开发板感兴趣的同学可以加群讨论: 592929122 lQLPJxaFi2zaB4UWWrDAMgIsFEW2pwLb3abnwDMA_90_22.png
lQDPJxaFi2nfFizMjM0CbLCPlxn_FVheIQLb3aGrwFQA_620_140.jpg

lQLPJxaFi2nfFhLMkM0BXrDNvOUyeU_FPgLb3aGvQNIA_350_144.png


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


        前面我们已经学会了无刷电机的基本驱动,本章我们将利用无刷驱动板的电路功能,来检测电机的三相电流以及驱动板的电源电压和温度。
本章分为如下几个小节:
23.1 驱动板硬件电路原理分析
23.2 硬件设计
23.3 程序设计
23.4 下载验证


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

下面我们主要分为三部分来讲解驱动板的硬件电路,包括:三相电流采集电路分析、电源电压采集电路分析、温度采集电路分析共三部分。
23.1.1 三相电流采集电路分析
image002.jpg
图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的精度问题,所以我还需将电压经过差分放大电路放大后在来采集,如下图:
image004.jpg
图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 电源电压采集电路分析
image006.jpg
图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 温度采集电路分析
image008.jpg
图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(绝对温度) + 摄氏度;其中:
        T2         :273.15 + 25(25摄氏度下的K度值)
        T1         :实际温度
        Rt        :热敏电阻在T1温度下的阻值;
        R0        :热敏电阻在T2常温下的标称阻值;
        B值        :热敏电阻的重要恒定参数;
其中T1是我们需要求解的温度值,Rt的大小由前面的温度采集电路可以算出,其他的参数都是可以在数据手册中查找得出,代入温度求解公式即可算出T1温度值,注意该值为K度。
注意:我们所使用的温度换算公式以及相关参数均来自《NCP18XH103F03RB 热敏电阻.pdf》这个数据手册,路径:A盘7,硬件资料2,芯片资料文件夹下可以找到。
23.2 硬件设计
1、例程功能
        本实验使用无刷电机接口一连接无刷电机驱动板
        当按下KEY0一次加速旋转,按一次KEY1减速旋转,按下KEY2则停止旋转。
        LED0作为程序运行状态指示灯,闪烁周期200ms。
        测量并在LCD和串口显示无刷电机电压、电流和温度
2、硬件资源
1)LED灯:LED0 – PE0
2)独立按键
    KEY0 – PE2
        KEY1 – PE3
        KEY2 – PE4
3)        定时器:
        TIM1_CH1:PA8
        TIM1_CH2:PA9
        TIM1_CH3:PA10
   IO:PB13\14\15
   使能引脚:SHDN: PF10
4)无刷电机
5)无刷电机驱动板
6)12-60V的DC电源
7)ADC:ADC1_CH9:PB1
         ADC1_CH0:PA0
         ADC1_CH8:PB0
         ADC1_CH6:PA6
         ADC1_CH3:PA3
3、原理图
image010.jpg
图23.2.1 无刷电机接口1原理图

        相较于上一章,本次实验多了5个模拟采集引脚,分别是:电源电压(ADC1_CN9/PB1)、 温度(ADC1_CN0/PA0)、三相电流UVW (ADC1_CN8/PB0、ADC1_CN6/PA6、ADC1_CN3/PA3) 。接口所涉及的IO如下:
lQLPJxaa6Ri-OaLNAXvNAQCw6ReZEthGukoC_t8pzcCWAA_256_379.png
表23.2.1 无刷相关IO口说明

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

23.3 程序设计
23.3.1 无刷电机参数采集的配置步骤

1)实现电机基本驱动
        实现电机的基础驱动函数,启停、6步换向组合等等
2)初始化ADC&DMA
        初始化ADC通道的IO,设置ADC工作方式、DMA等等
3)转速控制
        设置电机旋转方向及PWM占空比
4)编写中断服务函数
        PWM中断用于换向控制、采集电机未开始转时的基准电压,在将旋转后得到的电压进行相减得到实际电压等等
23.3.2 程序流程图
image014.png
图23.3.2.1 无刷电机基本控制流程图

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

#define ADC_ADCX_CH0_GPIO_PORT                                GPIOB
#define ADC_ADCX_CH0_GPIO_PIN                                 GPIO_PIN_1
#define ADC_ADCX_CH0_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)      /* PB口时钟使能 */

#define ADC_ADCX_CH1_GPIO_PORT                                GPIOA
#define ADC_ADCX_CH1_GPIO_PIN                                 GPIO_PIN_0
#define ADC_ADCX_CH1_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)      /* PA口时钟使能 */

#define ADC_ADCX_CH2_GPIO_PORT                                GPIOB
#define ADC_ADCX_CH2_GPIO_PIN                                 GPIO_PIN_0
#define ADC_ADCX_CH2_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)      /* PB口时钟使能 */

#define ADC_ADCX_CH3_GPIO_PORT                                GPIOA
#define ADC_ADCX_CH3_GPIO_PIN                                 GPIO_PIN_6
#define ADC_ADCX_CH3_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)              /* PA口时钟使能 */

#define ADC_ADCX_CH4_GPIO_PORT                      GPIOA
#define ADC_ADCX_CH4_GPIO_PIN                       GPIO_PIN_3
#define                                                                                ADC_ADCX_CH4_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)                      /* PA口时钟使能 */

#define ADC_ADCX                                    ADC1
#define ADC_ADCX_CH0                                ADC_CHANNEL_9                                       
#define ADC_ADCX_CH1                                ADC_CHANNEL_0
#define ADC_ADCX_CH2                                ADC_CHANNEL_8
#define ADC_ADCX_CH3                                ADC_CHANNEL_6
#define ADC_ADCX_CH4                                ADC_CHANNEL_3

#define                                                                                ADC_ADCX_CHY_CLK_ENABLE()           do{ __HAL_RCC_ADC1_CLK_ENABLE(); }while(0)          /* ADC1 时钟使能 */

#define ADC_CH_NUM                                  5        /* 需要转换的通道数目 */
#define ADC_COLL                                    50            /* 单采集次数 */
#define ADC_SUM                                     ADC_CH_NUM        *        ADC_COLL                      /* 总采集次数 */

/* ADC单通道/多通道 DMA采集 DMA数据流相关 定义
* 注意: 这里我们的通道还是使用上面的定义.
*/
#define ADC_ADCX_DMASx                              DMA2_Stream4
#define ADC_ADCX_DMASx_Chanel                       DMA_CHANNEL_0 /* ADC1_DMA请求源 */
#define ADC_ADCX_DMASx_IRQn                         DMA2_Stream4_IRQn
#define ADC_ADCX_DMASx_IRQHandler                   DMA2_Stream4_IRQHandler

#define                                                                                ADC_ADCX_DMASx_IS_TC()              ( __HAL_DMA_GET_FLAG(&g_dma_nch_adc_handle, DMA_FLAG_TCIF0_4) )  
/* 判断DMA2 Stream4传输完成标志, 这是一个假函数形式,
                                                                                                          * 不能当函数使用, 只能用在if等语句里面
                                                                                                          */
#define ADC_ADCX_DMASx_CLR_TC()             do{ __HAL_DMA_CLEAR_FLAG(&g_dma_nch_adc_handle, DMA_FLAG_TCIF0_4); }while(0)   /* 清除DMA2 Stream4传输完成标志 */
        这里主要分为三部分,第一部分ADC通道所对应的IO以及时钟使能的宏定义;第二部分为本例程所使用的ADC通道宏定义,第三部分为ADC相关的DMA宏定义以及ADC的转换次数的宏定义。接着看下ADC的初始化程序,如下:
void adc_init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};

    g_adc_nch_dma_handle.Instance = ADC_ADCX;
g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
/* 4分频,ADCCLK = PCLK2/4 = 84/4 = 21Mhz */
g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_12B;  
/* 12位模式 */
g_adc_nch_dma_handle.Init.ScanConvMode = ENABLE;  
/* 扫描模式 多通道使用 */
g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE;     
/* 连续转换模式,转换完成之后接着继续转换 */
g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE;     
/* 禁止不连续采样模式 */
g_adc_nch_dma_handle.Init.ExternalTrigConvEdge =
ADC_EXTERNALTRIGCONVEDGE_NONE; /* 使用软件触发 */
g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;   
/* 软件触发 */
g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
/* 右对齐 */
g_adc_nch_dma_handle.Init.NbrOfConversion = ADC_CH_NUM;
/* 使用转换通道数,需根据实际转换通道去设置 */
g_adc_nch_dma_handle.Init.DMAContinuousRequests = ENABLE;
/* 开启DMA连续转换 */
    g_adc_nch_dma_handle.Init.EOCSelection = ADC_EOC_SEQ_CONV;
    HAL_ADC_Init(&g_adc_nch_dma_handle);

    /* 配置使用的ADC通道,采样序列里的第几个转换,增加或者减少通道需要修改这部分 */
    sConfig.Channel = ADC_ADCX_CH0;                         /* 电源电压采集 */
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);

    sConfig.Channel = ADC_ADCX_CH1;                         /* 温度采集 */
    sConfig.Rank = 2;
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);

    sConfig.Channel = ADC_ADCX_CH2;                         /* U相电压采集 */
    sConfig.Rank = 3;
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);
   
    sConfig.Channel = ADC_ADCX_CH3;                         /* V相电压采集 */
    sConfig.Rank = 4;
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);
   
    sConfig.Channel = ADC_ADCX_CH4;                         /* W相电压采集 */
    sConfig.Rank = 5;
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);   
}
        该函数将ADC设置为软件触发连续转换,并配置了ADC的转换顺序,1~5分别对应:电源电压、温度、U相电压、V相电压、W相电压,而且使用DMA采集,DMA初始化程序,内容如下。
/**
* @brief       ADC DMA读取 初始化函数
* @note      本函数还是使用adc_init对ADC进行大部分配置,有差异的地方再单独配置
* @param       par         : 外设地址
* @param       mar         : 存储器地址
* @retval      无
*/
void adc_nch_dma_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
      
    ADC_ADCX_CHY_CLK_ENABLE();                   /* 使能ADCx时钟 */
    ADC_ADCX_CH0_GPIO_CLK_ENABLE();            /* 开启GPIO时钟 */
    ADC_ADCX_CH1_GPIO_CLK_ENABLE();
    ADC_ADCX_CH2_GPIO_CLK_ENABLE();
    ADC_ADCX_CH3_GPIO_CLK_ENABLE();
    ADC_ADCX_CH4_GPIO_CLK_ENABLE();
   
    /* AD采集引脚模式设置,模拟输入 */
    GPIO_InitStruct.Pin = ADC_ADCX_CH0_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(ADC_ADCX_CH0_GPIO_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = ADC_ADCX_CH1_GPIO_PIN;
    HAL_GPIO_Init(ADC_ADCX_CH1_GPIO_PORT, &GPIO_InitStruct);
   
    GPIO_InitStruct.Pin = ADC_ADCX_CH2_GPIO_PIN;   
    HAL_GPIO_Init(ADC_ADCX_CH2_GPIO_PORT, &GPIO_InitStruct);
   
    GPIO_InitStruct.Pin = ADC_ADCX_CH3_GPIO_PIN;   
    HAL_GPIO_Init(ADC_ADCX_CH3_GPIO_PORT, &GPIO_InitStruct);
   
    GPIO_InitStruct.Pin = ADC_ADCX_CH4_GPIO_PIN;   
    HAL_GPIO_Init(ADC_ADCX_CH4_GPIO_PORT, &GPIO_InitStruct);
   
    adc_init();
   
if ((uint32_t)ADC_ADCX_DMASx > (uint32_t)DMA2)     
/* 大于DMA1_Channel7, 则为DMA2的通道了 */
    {
        __HAL_RCC_DMA2_CLK_ENABLE();         /* DMA2时钟使能 */
    }
    else
    {
        __HAL_RCC_DMA1_CLK_ENABLE();          /* DMA1时钟使能 */
    }

    /* DMA配置 */
    g_dma_nch_adc_handle.Instance = ADC_ADCX_DMASx;                        /* 设置DMA通道 */
    g_dma_nch_adc_handle.Init.Channel = DMA_CHANNEL_0;
g_dma_nch_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;                 
/* DIR = 1 ,  外设到存储器模式 */
    g_dma_nch_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;         /*外设非增量模式*/
    g_dma_nch_adc_handle.Init.MemInc = DMA_MINC_ENABLE;              /*存储器增量模式*/
g_dma_nch_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;   
/* 外设数据长度:16位 */
g_dma_nch_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;      
/* 存储器数据长度:16位 */
    g_dma_nch_adc_handle.Init.Mode = DMA_CIRCULAR;                    /* 外设流控模式 */
    g_dma_nch_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;        /* 中等优先级 */
    HAL_DMA_Init(&g_dma_nch_adc_handle);

    __HAL_LINKDMA(&g_adc_nch_dma_handle,DMA_Handle,g_dma_nch_adc_handle);

    HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 2, 1);
    HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn);
   
HAL_ADC_Start_DMA(&g_adc_nch_dma_handle,(uint32_t *)g_adc_value,
ADC_CH_NUM * ADC_COLL);
}
        首先初始化ADC通道管脚,并初始化DMA。DMA存储的地址就是数组g_adc_value,传输的数据量大小为:ADC_CH_NUM * ADC_COLL ,如果对于ADC与DMA的配置部分还不熟悉,大家可以回顾下4.4小节。
        接着来到ADC转换完成中断回调函数用于计算ADC平均值,程序如下。
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    if (hadc->Instance == ADC1)                                 /* 大约2.6ms采集完成进入中断 */
    {
        HAL_ADC_Stop_DMA(&g_adc_nch_dma_handle);/* 关闭DMA转换 */
        calc_adc_val(g_adc_val);                              /* ADC数值转换 */
        HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, (uint32_t *)&g_adc_value,
(uint32_t)(ADC_SUM));         /* 再启动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)           
这里定义的是电流、电压计算相关的宏,其中涉及的公式是根据我们前面介绍的电流、电压采集电路原理得出的,当我们需要计算电流和电压的时候,直接调用相应的宏定义即可,具体的公式推导过程大家可以直接看代码的注释。
下面看bldc.c的程序,首先看ADC平均值计算函数。
平均值计算函数calc_adc_val在bldc.c原文件中实现,内容如下。
/**
* @brief       计算ADC的平均值(滤波)
* @param       * p :存放ADC值的指针地址
* @note        此函数对电压、温度、电流对应的ADC值进行滤波
* @retval      无
*/
void calc_adc_val(uint16_t * p)
{
    uint32_t temp[ADC_CH_NUM] = {0,0,0};           /* 定义一个缓存数组 */
    int i,j;
    for(i = 0; i < ADC_COLL; i++)                   /* 循环采集ADC_COLL次数 */
    {
        for(j = 0; j < ADC_CH_NUM; j++)            /* 根据ADC通道数循环获取,并累加 */
        {
            temp[j] += g_adc_value[j+i*ADC_CH_NUM];         /* 各通道进行累加 */
        }
    }
    for(j = 0; j < ADC_CH_NUM; j++)
    {
        temp[j] /= ADC_COLL;                                /* 获取平均值 */
        p[j] = temp[j];                                             /* 存到*p */
    }
}
        该函数将5个通道采集的数据,进行软件滤波取平均并将滤波后的数据保留在入口参数指针p的地址中。
接下来看温度值计算函数,其定义如下:
/*
    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盘7,硬件资料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就是所求的温度,它的默认单位是华氏度,我们最后还需要将其转换成摄氏度。温度计算函数的实现本质上就是这两个公式的代码体现,大家一定要跟着公式去理解代码。
        接着是定时器的中断回调函数,如下:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    uint8_t bldc_dir=0;
    uint8_t i;
    static uint8_t times_count=0;                /* 定时器时间记录 */
    int16_t temp_speed=0;                                /* 临时速度存储 */
    if(htim->Instance == ATIM_TIMX_PWM)        /* 55us */
    {
#ifdef H_PWM_L_ON
        if(g_bldc_motor1.run_flag == RUN)
        {
            g_bldc_motor1.count_j++;
            if(g_bldc_motor1.dir == CW)        /* 顺时针旋转 */
            {
                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;
            }
            g_bldc_motor1.hall_sta_edge =
uemf_edge(g_bldc_motor1.hall_single_sta);/* 检测单个霍尔信号的变化 */
            if(g_bldc_motor1.hall_sta_edge == 0)        /* 统计单个霍尔信号的高电平时间 */
            {
                /*计算速度*/
                if(g_bldc_motor1.dir == CW)
                    temp_speed = (SPEED_COEFF/g_bldc_motor1.count_j);
                else
                    temp_speed = -(SPEED_COEFF/g_bldc_motor1.count_j);
                FirstOrderRC_LPF(g_bldc_motor1.speed, temp_speed, 0.2379);   
                g_bldc_motor1.no_single = 0;
                g_bldc_motor1.count_j = 0;
            }
            if(g_bldc_motor1.hall_sta_edge== 1)
            {
                g_bldc_motor1.no_single = 0;
                g_bldc_motor1.count_j = 0;
            }
            if(g_bldc_motor1.hall_sta_edge== 2)
            {
                g_bldc_motor1.no_single++;         /* 不换相时间累计 超时则判定速度为0 */
               
                if(g_bldc_motor1.no_single>15000)
                {
                    
                    g_bldc_motor1.no_single = 0;
                    g_bldc_motor1.speed = 0;         /* 超时换向 判定为停止 速度为0*/
                }
            }
            if(g_bldc_motor1.step_last != g_bldc_motor1.step_sta)
            {
                g_bldc_motor1.hall_keep_t = 0;
                bldc_dir = check_hall_dir(&g_bldc_motor1);
                if(bldc_dir == CCW)
                {
                    g_bldc_motor1.pos -= 1;
                }
                else if(bldc_dir == CW)
                {
                    g_bldc_motor1.pos += 1;
                }
                g_bldc_motor1.step_last = g_bldc_motor1.step_sta;
            }
            else if(g_bldc_motor1.run_flag == RUN)      /* 运行且霍尔保持时 */
            {
                g_bldc_motor1.hall_keep_t++;           
            }      
            
            for(i=0; i<3; i++)
            {
                adc_val_m1 = g_adc_val[i+2];
                adc_amp
= adc_val_m1
adc_amp_offset
[ADC_AMP_OFFSET_TIMES];
                if(adc_amp
>=0)               /* 去除反电动势引起的负电流数据 */
                    adc_amp_un
=adc_amp;
            }
            /*运算母线电流(母线电流为任意两个有开关动作的相电流之和)*/
            if(g_bldc_motor1.step_sta == 0x05)
            {
                adc_amp_bus= (adc_amp_un[0]+ adc_amp_un[1])*ADC2CURT;   /* UV */
            }
            else if(g_bldc_motor1.step_sta== 0x01)
            {
                adc_amp_bus= (adc_amp_un[0]+ adc_amp_un[2])*ADC2CURT;   /* UW */
            }
            else if(g_bldc_motor1.step_sta== 0x03)
            {
                adc_amp_bus= (adc_amp_un[1]+ adc_amp_un[2])*ADC2CURT;   /* VW */
            }
            else if(g_bldc_motor1.step_sta== 0x02)
            {
                adc_amp_bus= (adc_amp_un[0]+ adc_amp_un[1])*ADC2CURT;   /* UV */
            }
            else if(g_bldc_motor1.step_sta == 0x06)
            {
                adc_amp_bus= (adc_amp_un[0]+ adc_amp_un[2])*ADC2CURT;   /* WU */
            }
            else if(g_bldc_motor1.step_sta == 0x04)
            {
                adc_amp_bus= (adc_amp_un[2]+ adc_amp_un[1])*ADC2CURT;   /* WV */
            }         
        }
#endif
    }
    else if(htim->Instance == TIM6)
    {
        /* 计算未开始启动时的基准电压 */
        times_count++;
        if(g_bldc_motor1.run_flag == STOP)
        {
            uint8_t i;
            uint32_t avg[3] = {0,0,0};
/* 获取电机停机状态下的三相电流 */
            adc_amp_offset[0][adc_amp_offset_p] = g_adc_val[2];        /* U */
            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]
;                                    /* 各相数值累加 */
                avg[1] += adc_amp_offset[1]
;
                avg[2] += adc_amp_offset[2]
;
            }
            for(i = 0; i < 3; i++)
            {
                avg
/= ADC_AMP_OFFSET_TIMES;                     /* 取平均 */
                adc_amp_offset
[ADC_AMP_OFFSET_TIMES] = avg;/* 赋值 */
            }
        }
    }
}
关于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(336, 8, 2, 7);            /* 设置时钟,168Mhz */
    delay_init(168);                                 /* 延时初始化 */
    usart_init(115200);                              /* 串口初始化为115200 */
    led_init();                                      /* 初始化LED */
    key_init();                                      /* 初始化按键 */
    lcd_init();                                      /* 初始化LCD */
    bldc_init(10000-1,0);
    bldc_ctrl(MOTOR_1,CCW,0);                            /* 初始无刷电机接口1速度 */

    g_point_color = WHITE;
    g_back_color  = BLACK;
    lcd_show_string(10, 10, 200, 16, 16, "BLDC Motor Test",g_point_color);
    lcd_show_string(10, 30, 200, 16, 16, "KEY0:Start forward", g_point_color);
    lcd_show_string(10, 50, 200, 16, 16, "KEY1:Start backward", g_point_color);
    lcd_show_string(10, 70, 200, 16, 16, "KEY2:Stop", g_point_color);
    adc_nch_dma_init();
   
    while (1)
    {
        t++;
        if(t % 20 == 0)
        {
            sprintf(buf,"PWM_Duty:%.1f%%",(float)((g_bldc_motor1.pwm_duty
/MAX_PWM_DUTY)*100));                                                        /* 显示控制PWM占空比 */
            lcd_show_string(10,110,200,16,16,buf,g_point_color);
            
            sprintf(buf,"Power:%.3fV ",g_adc_val[0]*ADC2VBUS);
            lcd_show_string(10,190,200,16,16,buf,g_point_color);
            sprintf(buf,"Temp:%.1fC ",get_temp(g_adc_val[1]));
            lcd_show_string(10,210,200,16,16,buf,g_point_color);           
            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,230,200,16,16,buf,g_point_color);
            sprintf(buf,"Amp V:%.3fmA ",(float)current_lpf[1]);
            lcd_show_string(10,250,200,16,16,buf,g_point_color);
            sprintf(buf,"Amp W:%.3fmA ",(float)current_lpf[2]);
            lcd_show_string(10,270,200,16,16,buf,g_point_color);
            sprintf(buf,"Amp Bus:%.3fmA ",(float)current_lpf[3]);
            lcd_show_string(10,290,200,16,16,buf,g_point_color);
            
            /* 串口打印信息 */
            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)
        {
            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();                                /* 开启运行 */
        }
        if(key == KEY1_PRES)                   /* 按下KEY1开启电机 */
        {
            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();                                /* 运行电机 */
        }
        if(key == KEY2_PRES)                    /* 按下KEY2关闭电机 */
        {
            stop_motor1();                                 /* 停机 */
            g_bldc_motor1.run_flag=STOP;        /* 标记停机 */
            pwm_duty_temp=0;
            g_bldc_motor1.pwm_duty=0;
        }
        delay_ms(10);
    }
}
        主函数主要添加了adc初始化函数adc_nch_dma_init,对ADC以及DMA进行初始化。
在while循环里面,除了有感方波驱动的内容,我们还利用LDC以及串口1来打印电压、温度以及电流的值,计算过程以及在原理部分讲的很详细了,大家可以原理分析部分。
23.4 下载验证
硬件连接好之后下载程序到开发板,液晶屏和串口都会显示程序计算的电压、电流等信息,点击按键KEY0一次可以看到定时器比较值递增500(加速),电机开始旋转,点击一次按键KEY1可以看到定时器比较值递减500(减速),点击按键KEY2可以随时停止无刷电机旋转,并在LCD以及串口显示ADC所采集的电源电压、驱动板温度、以及三相电流。大家可以自行下载程序并点击按键测试效果。
image016.jpg
图23.4.1 模拟信号数据 


阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-29 02:44

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表