搜索
bottom↓
回复: 0

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

[复制链接]

出0入234汤圆

发表于 2022-8-12 15:17:30 | 显示全部楼层 |阅读模式
电机开发板:
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


第6章 直流有刷驱动板电压温度电流采集

本章我们主要来学习直流有刷驱动板的电压、温度、电流采集原理,并实现相关的采集实验。
本章分为如下几个小节:
6.1 电压温度电流采集原理
6.2 硬件设计
6.3 程序设计
6.4 下载验证


6.1 电压温度电流采集原理        1)电压采集原理
由于直流有刷电机驱动板的电源电压远超STM32内部ADC所能采集的范围,我们并不能直接使用ADC进行电压采集,而是需要使用一些硬件电路对电源电压进行处理,使其减小到ADC采集范围,具体原理图如图6.1.1所示:
image002.jpg
图6.1.1 电压采集电路

图6.1.1中电压采集电路的实现原理如下:
① 电阻分压,利用三个电阻(R13、R17、R21)对电源电压POWER进行分压,可得a点的电压Va = POWER /(12+12+1);
② 电压跟随电路,其作用就是让VBUS = Va,即VBUS = POWER /(12+12+1)= POWER/25;
通过这两部分电路的处理,我们就可以直接使用ADC采集VBUS的电压,再根据公式:POWER= 25 * VBUS,即可算出电源电压。
        2)电流采集原理
        由于STM32内部ADC并不能直接对电流进行采集,所以我们需要先把电流的信号转换为电压的信号,具体的原理图如图6.1.2所示:
image004.jpg
图6.1.2 采样电路

图6.1.2中,我们在H桥的回路中串接一个20mR的采样电阻,这样就可以得到一个采样电压I-V = 0.02R * 实际电流I,但是这个采样电压太小了,直接用ADC进行采集的话偏差较大,我们需要对它进行差分放大,具体的原理图如图6.1.3所示:
image006.jpg
图6.1.3 电流采集电路

        图6.1.3中,差分放大倍数= 12 /(1+1)=6倍,参考电压为1.27V,由此可得输出电压Iout(即CURT点的电压)=6 * 0.02R * 实际电流I + 1.27V,我们只需要采集Iout的电压,再根据公式:实际电流I=(输出电压Iout-1.27)/0.12A,即可算出实际电流。
        3)温度采集原理
        本实验中我们采集的是驱动板的温度,同样需要用到ADC进行采集,具体的原理图如图6.1.4所示:
image008.jpg
图6.1.4温度采集电路

        图6.1.4中,我们利用NCP18XH103F03RB热敏电阻对温度进行检测,该热敏电阻是负温度系数电阻,温度越高,其电阻值越低。温度采集电路的原理实现如下:
① 电阻分压,利用热敏电阻和R35电阻(4.7K)对VCC3.3进行分压,可得a点的电压Va = 4.7 * 3.3 V /(4.7 + 热敏电阻阻值Rt);
② 电压跟随电路,其作用就是让VTEMP = Va,即VTEMP = 4.7 * 3.3 V /(4.7 + 热敏电阻阻值Rt);
通过这两部分电路的处理,我们就可以直接使用ADC采集VTEMP的电压,再通过公式即可算出热敏电阻阻值Rt。最后我们再根据厂家给出的公式,即可算出该阻值所对应的实际温度值,具体的换算公式将会在代码注释中介绍。
注意:我们所使用的温度换算公式以及相关参数均来自《NCP18XH103F03RB 热敏电阻.pdf》这个数据手册,路径:A盘7,硬件资料2,芯片资料 文件夹下可以找到。
6.2 硬件设计
1. 例程功能
1、本实验以电机开发板的直流有刷电机驱动接口1为例,基于基础驱动代码功能,加入多通道ADC采集,利用ADC1_CH9(由PB1复用)、ADC1_CH0(由PA0复用)和ADC1_CH8(由PB0复用)三个通道,分别检测电压、温度和电流采集电路对应的输出电压,然后进一步算出电压、电流和温度值。
2、当按键0按下,就增大PWM的比较值变量,当按键1按下,就减小PWM的比较值变量。比较值变量的绝对值越大,电机速度越快。当比较值变量为正数时电机正转,反之电机反转,按下按键2则马上停止电机。
3、屏幕显示按键功能信息。
4、串口1打印驱动板电压、电流、温度信息。
5、LED0闪烁指示程序运行。
2. 硬件资源
1)LED灯
LED0 – PE0
2)独立按键
KEY0 – PE2
KEY1 – PE3
KEY2 – PE4
3)定时器1
TIM1正常输出通道 PA8
    TIM1互补输出通道 PB13
4)SD(刹车)信号输出 PF10
5)ADC1
        ADC1通道9  PB1(电压)
ADC1通道0  PA0(温度)
ADC1通道8  PB0(电流)
6)串口1
        USART1_TX  PB6(发送)
    USART1_RX  PB7(接收)

3. 原理图
image010.jpg
图6.2.1 直流有刷电机接口原理图

        图6.2.1就是我们DMF407电机开发板的直流有刷电机接口1原理图,本实验我们只需要用到了PM1_PWM_UH(PA8)、PM1_PWM_UL(PB13)、PM1_CTRL_SD(PF10)、PM1_VBUS(PB1)、PM1_AMPU(PB0)和PM1_VTEMP(PA0)这6个引脚,其中PA8和PB13用于输出所需的PWM控制信号,PF10用于输出刹车信号,PB1、PA0和PB0分别用于检测电源电压、温度和电流采集电路对应的输出电压。
        本实验的硬件接线部分和基础驱动实验基本一致,这里需要用一根USB-C数据线连接串口1到电脑,才可以在串口调试助手查看数据,接线如图6.2.2所示:
image012.jpg
图6.2.2 串口1连接电脑示意图

6.3 程序设计
本实验是基于基础驱动实验实现的,其中所用到的HAL库驱动请回顾高级定时器以及ADC章节的内容。下面介绍一下电压电流温度采集相关的ADC、DMA配置步骤。
ADC、DMA配置步骤
1)开启ADCx和输入通道的GPIO时钟,配置该IO口的模拟输入功能。
首先开启ADCx的时钟,然后配置GPIO为模拟输入模式。本实验我们默认用到ADC1通道9、0、8,对应IO是PB1、PA0和PB0,它们的时钟开启方法如下:
__HAL_RCC_ADC1_CLK_ENABLE ();            /* 使能ADC1时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE ();              /* 开启GPIOA时钟 */
__HAL_RCC_GPIOB_CLK_ENABLE ();                        /* 开启GPIOB时钟 */
        IO口模拟输入功能是通过函数HAL_GPIO_Init来配置的。
2)初始化ADCx,设置ADCx的参数。
通过HAL_ADC_Init函数来设置ADCx时钟分频系数、分辨率、模式、扫描方式、对齐方式等信息。
注意:该函数会调用:HAL_ADC_MspInit函数,但是为了方便代码管理和移植,我们就直接在adc_nch_dma_init函数中,使能ADC时钟和GPIO时钟,初始化通道对应IO引脚等。
3)配置ADC通道
通过HAL_ADC_ConfigChannel函数来配置ADC的通道,根据需求设置通道、序列、采样时间,等等。
4)配置DMA
首先开启DMA的时钟,然后通过HAL_DMA_Init函数配置DMA,包括:数据流、通道、外设地址、存储器地址、传输方向,等等。
HAL库为了处理各类外设的DMA请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接DMA和外设句柄。这个宏定义为__HAL_LINKDMA。
5)使能DMA对应数据流中断,配置DMA中断优先级,使能ADC和DMA
通过HAL_NVIC_EnableIRQ函数使能DMA数据流中断。
通过HAL_NVIC_SetPriority函数设置中断优先级。
通过HAL_ADC_Start_DMA函数开启ADC转换和DMA传输。
6)编写中断服务函数
DMA的每个数据流都有一个对应的中断服务函数,比如DMA2_Stream4的中断服务函数为DMA2_Stream4_IRQHandler。如果使用HAL库的中断回调机制,可以在相关中断服务函数中直接调用DMA中断公共处理函数HAL_DMA_IRQHandler,然后我们直接重定义相关的中断回调函数来编写中断程序即可。
6.3.1 程序流程图
image014.png
图6.3.1.1 电压、温度、电流采集程序流程图

6.3.2 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。定时器驱动相关源码与基础驱动章节一致,这里不再赘述。我们先看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                                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_CHY_CLK_ENABLE()           
do{ __HAL_RCC_ADC1_CLK_ENABLE(); }while(0)        /* ADC1 时钟使能 */

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

/*  DMA传输相关 定义 */
#define ADC_ADCX_DMASx                                DMA2_Stream4                   /* 数据流4 */
#define ADC_ADCX_DMASx_Chanel                DMA_CHANNEL_0                   /* 通道0 */
#define ADC_ADCX_DMASx_IRQn                        DMA2_Stream4_IRQn
#define ADC_ADCX_DMASx_IRQHandler        DMA2_Stream4_IRQHandler
        可以把上面的宏定义分成两部分,第一部分是ADC通道的IO口以及采集次数的宏定义。第二部分则是ADC相关的DMA宏定义。
下面看adc.c的程序,首先是ADC初始化函数。
/**
* @brief       ADC初始化函数
* @param       无
* @retval      无
*/
void adc_init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};

    g_adc_nch_dma_handle.Instance = ADC_ADCX;                                        /* ADCx */
/* 4分频,ADCCLK = PCLK2/4 = 84/4 = 21Mhz */
    g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;                    
    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;  
/* 开启DMA连续转换 */      
    g_adc_nch_dma_handle.Init.DMAContinuousRequests = ENABLE;
    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_480CYCLES;
    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;
    sConfig.Rank = 3;
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &sConfig);
}
        ADC初始化函数通过HAL_ADC_Init和HAL_ADC_ConfigChannel配置ADC1的相关参数,这一部分的原理和多通道ADC采集(使用DMA)实验是一样的,这里不再赘述。
注意:代码中所使用的ADC通道是通过宏定义传入的,其中的ADC_ADCX_CH0、ADC_ADCX_CH2、ADC_ADCX_CH2实际上分别对应的是ADC1_CH9、ADC1_CH0和ADC1_CH8。
        接下来看ADC相关的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();
   
    /* 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);
   
    adc_init();                                                                /* 初始化ADC */
/* 大于DMA1_Channel7, 则为DMA2的通道 */
if ((uint32_t)ADC_ADCX_DMASx > (uint32_t)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;                /* DMA通道x */
/* DIR = 1 ,  外设到存储器模式 */
g_dma_nch_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;                 
    g_dma_nch_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;/* 外设非增量模式 */
    g_dma_nch_adc_handle.Init.MemInc = DMA_MINC_ENABLE;         /* 存储器增量模式 */
/* 外设数据长度:16位 */
g_dma_nch_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
/* 存储器数据长度:16位 */   
g_dma_nch_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;      
    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);

        /* 把ADC和DMA关联,用DMA传输ADC数据 */
    __HAL_LINKDMA(&g_adc_nch_dma_handle,DMA_Handle,g_dma_nch_adc_handle);
/* ADC DMA中断配置 */
    HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 2, 1);
    HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn);
    /* 开启ADC的DMA传输 */
    HAL_ADC_Start_DMA(&g_adc_nch_dma_handle,
(uint32_t *)g_adc_value,ADC_CH_NUM * ADC_COLL);
}
        该函数首先配置ADC采集相关IO,然后调用adc_init函数对ADC进行初始化,接着开启DMA的时钟以及初始化相关的DMA。最后把ADC和DMA关联,使能DMA中断,并开启ADC和DMA。
下面我们来看ADC采集中断服务回调函数。
/**
* @brief       ADC 采集中断服务回调函数
* @param       无
* @retval      无
*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    if (hadc->Instance == ADC_ADCX)
    {
        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转换。
接下来看电压、电流以及温度计算相关的程序,包括两个文件:dc_motor.c和dc_motor.h,我们这里只介绍本实验新增的内容。
首先看dc_motor.h头文件的几个宏定义:
/* 电流计算公式:
* I=(最终输出电压-初始参考电压)/(6*0.02)A
* ADC值转换为电压值:电压=ADC值*3.3/4096,这里电压单位为V,我们换算成mV,4096/1000=4.096,后面就直接算出为mA
* 整合公式可以得出电流 I= (当前ADC值-初始参考ADC值)* (3.3 / 4.096 / 0.12)
*/
#define ADC2CURT    (float)(3.3f / 4.096f / 0.12f)

/* 电压计算公式:
* V_POWER = V_BUS * 25
* ADC值转换为电压值:电压=ADC值*3.3/4096
* 整合公式可以得出电压V_POWER= ADC值 *(3.3f * 25 / 4096)
*/
#define ADC2VBUS    (float)(3.3f * 25 / 4096)
这里定义的是电流、电压计算相关的宏,其中涉及的公式是根据我们前面介绍的电流、电压采集电路原理得出的,当我们需要计算电流和电压的时候,直接调用相应的宏定义即可,具体的公式推导过程大家可以直接看代码的注释。
下面看dc_motor.c的程序,首先看ADC平均值计算函数。
/**
* @brief       计算ADC的平均值(滤波)
* @param       * p :存放ADC值的指针地址
* @note        此函数对电压、温度、电流对应的ADC值进行滤波,
*               p[0]-p[2]对应的分别是电压、温度和电流
* @retval      无
*/
void calc_adc_val(uint16_t * p)
{
    uint32_t temp[3] = {0,0,0};
    int i;
    for(i=0;i<ADC_COLL;i++)         /* 循环ADC_COLL次取值,累加 */
    {
        temp[0] += g_adc_value[0+i*ADC_CH_NUM];
        temp[1] += g_adc_value[1+i*ADC_CH_NUM];
        temp[2] += g_adc_value[2+i*ADC_CH_NUM];
    }
    temp[0] /= ADC_COLL;            /* 取平均值 */
    temp[1] /= ADC_COLL;
    temp[2] /= ADC_COLL;
    p[0] = temp[0];                 /* 存入电压ADC通道平均值 */
    p[1] = temp[1];                 /* 存入温度ADC通道平均值 */
    p[2] = temp[2];                 /* 存入电流ADC通道平均值 */
}
这个函数非常简单,它的功能就是把电压、温度以及电流采集通道的ADC值分别累计1000次,然后取平均值(滤波),最后再把ADC平均值存入相应的数组元素中。
接下来看温度值计算函数,其定义如下:
/*
    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就是所求的温度,它的默认单位是华氏度,我们最后还需要将其转换成摄氏度。温度计算函数的实现本质上就是这两个公式的代码体现,大家一定要跟着公式去理解代码。
在main.c里面编写如下代码:
int main(void)
{     
    uint8_t key;
    uint16_t t;
    int32_t motor_pwm = 0;
    uint16_t init_adc_val;
   
    HAL_Init();                                             /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);             /* 设置时钟,168Mhz */
    delay_init(168);                                /* 延时初始化 */
    usart_init(115200);                             /* 串口初始化为115200 */
    led_init();                                             /* 初始化LED */
    lcd_init();                                             /* 初始化LCD */
    key_init();                                             /* 初始化按键 */
    atim_timx_cplm_pwm_init(8400 - 1, 0);         /* 168Mhz的计数频率 */
    dcmotor_init();                                                        /* 初始化电机 */
    adc_nch_dma_init();                                                /* ADC DMA传输初始化 */

    g_point_color = WHITE;
    g_back_color  = BLACK;
    lcd_show_string(10, 10, 200, 16, 16, "DcMotor 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);
   
    delay_ms(20);

    /* init_adc_val存储电流测量对应的参考电压ADC值,这里进行滤波 */
    init_adc_val = g_adc_val [2];                        /* 取出第一次得到的值 */
    for(t=0;t<1000;t++)
    {
        init_adc_val += g_adc_val [2];                /* 现在的值和上一次存储的值相加 */
        init_adc_val /= 2;                                        /* 取平均值 */
        delay_ms(1);
    }

    while (1)
    {
        key = key_scan(0);                          /* 按键扫描 */
        if(key == KEY0_PRES)                        /* 当key0按下 */
        {
/* 因为不同的电机最小启动电压不同,可能在第一次增加的时候电机还不能转起来 */
            motor_pwm += 400;
            if (motor_pwm == 0)
            {
                dcmotor_stop();                     /* 停止则立刻响应 */
                motor_pwm = 0;
            }
            else
            {
                dcmotor_start();                    /* 开启电机 */
                if (motor_pwm >= 8400)              /* 限速 */
                {
                    motor_pwm = 8400;
                }               
            }
            motor_pwm_set(motor_pwm);               /* 设置电机方向、转速 */
        }
        
        else if(key == KEY1_PRES)                     /* 当key1按下 */
        {
            motor_pwm -= 400;
            if (motor_pwm == 0)
            {
                dcmotor_stop();                     /* 停止则立刻响应 */
                motor_pwm = 0;
            }
            else
            {
                dcmotor_start();                    /* 开启电机 */
                if (motor_pwm <= -8400)             /* 限速 */
                {
                    motor_pwm = -8400;
                }               
            }
            motor_pwm_set(motor_pwm);               /* 设置电机方向、转速 */
        }
        
        else if(key == KEY2_PRES)                     /* 当key2按下 */
        {
            LED1_TOGGLE();
            dcmotor_stop();                         /* 关闭电机 */
            motor_pwm = 0;
            motor_pwm_set(motor_pwm);               /* 设置电机方向、转速 */
        }
        
        delay_ms(10);
        t++;
        if(t % 20 == 0)
        {
            LED0_TOGGLE();                              /* LED0(红灯) 翻转 */
            printf("KEY0:增加比较值,KEY1:减小比较值,KEY2:停止电机\r\n\r\n");
            printf("Valtage:%.1fV \r\n",g_adc_val[0]*ADC2VBUS);  /* 打印电压值 */
            printf("Temp:%.1fC \r\n",get_temp(g_adc_val[1]));    /* 打印温度值 */
            printf("Current:%.1fmA \r\n\r\n",abs(g_adc_val[2]-
init_adc_val)*ADC2CURT);/* 打印电流值 */
        }
    }
}
        初始化的内容和基础驱动章节基本相同,只是多了adc_nch_dma_init()这个语句,对ADC以及DMA进行初始化。
注意:在电流采集电路的原理介绍中,我们有提到参考电压的值为1.27V。在实际的应用中,为了获取更精确的参考值,我们不直接使用1.27V作为参考电压,而是采用以下方式获得参考电压:在进入while循环之前(此时电机没有启动),读取1000次电流采集通道对应的ADC值,然后取平均值,进而计算出参考电压。
        在while循环里面,除了基础驱动的内容,我们还利用串口1来打印电压、温度以及电流的值,它们的计算过程如下:
数组元素g_adc_val [0]~ g_adc_val [2]所存储的数据分别对应的是电压、温度、电流采集通道的ADC平均值,我们取出相应的ADC平均值,代入公式即可算出结果。值得注意的是,电流是没有负数的,因此需要调用abs函数对电流值取绝对值。
6.4 下载验证
下载代码后,可以看到LED0在闪烁,说明程序已经正常在跑了,LCD显示一些按键功能提示信息,当我们按下KEY0,比较值变量motor_pwm将增大;按下KEY1,比较值变量motor_pwm将减小;按下KEY2,电机将停止。比较值变量motor_pwm为正数时,电机正转,反之电机反转,其绝对值越大,电机的速度越快。我们再打开串口调试助手,选择对应的串口端口,我这边是COM3,可以看到串口打印的按键信息、电压、温度以及电流值,如图6.4.1所示:
image016.jpg
图6.4.1 串口打印按键信息、电压、温度以及电流值 


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

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-4-25 04:58

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

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