ITPub博客

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

《DMG474电机控制专题教程V1.1 》第6章 直流有刷驱动板电压温度电流采集

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

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


       本章我们主要来学习直流有刷驱动板的电压、温度、电流采集原理,并实现相关的采集实验。 

       本章分为如下几个小节:

       6.1 电压温度电流采集原理

       6.2 硬件设计

       6.3 程序设计

       6.4 下载验证


        6.1 电压温度电流采集原理

       1)电压采集原理

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


图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所示:


图6.1.2 采样电路


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


图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所示:


图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盘→6,硬件资料 →2,芯片资料 文件夹下可以找到。


        6.2 硬件设计


       1. 例程功能

       1、本实验以电机开发板的直流有刷电机驱动接口1为例,基于基础驱动代码功能,加入多通道ADC采集,利用ADC1_CH4(由PA3复用)、ADC1_CH6(由PC0复用)和ADC1_CH7由PC1复用)三个通道,分别检测电压、温度和电流采集电路对应的输出电压,然后进一步算出电压、电流和温度值。

       2、当按键0按下,就增大PWM的比较值变量,当按键1按下,就减小PWM的比较值变量。比较值变量的绝对值越大,电机速度越快。当比较值变量为正数时电机正转,反之电机反转,按下按键2则马上停止电机。

       3、屏幕显示按键功能信息。

       4、串口1打印驱动板电压、电流、温度信息。

       5、LED0闪烁指示程序运行。


       2. 硬件资源

       1)LED灯

              LED0 – PE0

       2)独立按键

              KEY0 – PE12

              KEY1 – PE13

              KEY2 – PE14

       3)定时器1

              TIM1正常输出通道 PA8

              TIM1互补输出通道 PB13

       4)SD(刹车)信号输出 PC13

       5)ADC1

       ADC1通道4  PA3(电压)

              ADC1通道6  PC0(温度)

              ADC1通道7  PC1(电流)

       6)串口1

              USART1_TX  PB6(发送)

              USART1_RX  PB7(接收)

 

       3. 原理图


图6.2.1 直流有刷电机接口1原理图


       图6.2.1就是我们DMG474电机开发板的直流有刷电机接口1原理图,本实验我们只需要用到了PM1_PWM_UH(PA8)、PM1_PWM_UL(PB13)、PM1_CTRL_SD(PC13)、PM1_VBUS(PA3)、PM1_AMPU(PC1)和PM1_VTEMP(PC0)这6个引脚,其中PA8和PB13用于输出所需的PWM控制信号,PC13用于输出刹车信号,PA3、PC0和PC1分别用于检测电源电压、温度和电流采集电路对应的输出电压。

       本实验的硬件接线部分和基础驱动实验基本一致,这里需要用一根USB-C数据线连接串口1到电脑,才可以在串口调试助手查看数据,接线如图6.2.2所示:


图6.2.2 串口1连接电脑示意图


        6.3 程序设计

       本实验是基于基础驱动实验实现的,其中所用到的HAL库驱动请回顾高级定时器以及ADC章节的内容。下面介绍一下电压电流温度采集相关的ADC、DMA配置步骤。

 

       ADC、DMA配置步骤 

       1)开启ADCx和输入通道的GPIO时钟,配置该IO口的模拟输入功能。

       首先开启ADCx的时钟,然后配置GPIO为模拟输入模式。本实验我们默认用到ADC1通道4、6、7,对应IO是PA3、PC0和PC1,它们的时钟开启方法如下:

__HAL_RCC_ADC12_CLK_ENABLE ();             /* 使能ADC1时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE ();        /* 开启GPIOA时钟 */
__HAL_RCC_GPIOC_CLK_ENABLE ();    /* 开启GPIOC时钟 */

       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的每个通道都有一个对应的中断服务函数,比如DMA1_Channel1的中断服务函数为DMA1_Channel1_IRQHandler。如果使用HAL库的中断回调机制,可以在相关中断服务函数中直接调用DMA中断公共处理函数HAL_DMA_IRQHandler,然后我们直接重定义相关的中断回调函数来编写中断程序即可。


       6.3.1 程序流程图


图6.3.1.1 电压、温度、电流采集程序流程图


       6.3.2 程序解析

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

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

/* ADC及引脚 定义 */
#define ADC_ADCX_CH0_GPIO_PORT               GPIOA
#define ADC_ADCX_CH0_GPIO_PIN                GPIO_PIN_3
#define ADC_ADCX_CH0_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
 
#define ADC_ADCX_CH1_GPIO_PORT               GPIOC
#define ADC_ADCX_CH1_GPIO_PIN                GPIO_PIN_0
#define ADC_ADCX_CH1_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)    /* PC口时钟使能 */
 
#define ADC_ADCX_CH2_GPIO_PORT               GPIOC
#define ADC_ADCX_CH2_GPIO_PIN                GPIO_PIN_1
#define ADC_ADCX_CH2_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)    /* PC口时钟使能 */
 
#define ADC_ADCX                             ADC1
#define ADC_ADCX_CH0                        ADC_CHANNEL_4                                       /* 电压测量通道 */ 
#define ADC_ADCX_CH1                        ADC_CHANNEL_6                                       /* 温度测量通道 */ 
#define ADC_ADCX_CH2                        ADC_CHANNEL_7                                       /* 电流测量通道 */ 
 
#define ADC_ADCX_CHY_CLK_ENABLE()           do{ __HAL_RCC_ADC12_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                      DMA1_Channel1  /* ADC1_DMA通道 */
#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相关的DMA宏定义。

       下面看adc.c的程序,首先是ADC初始化函数。

/**
 * @brief       ADC初始化函数
 * @param       无
 * @retval      无
 */
void adc_init(void)
{
g_adc_nch_dma_handle.Instance = ADC_ADCX;
/* 4分频,ADCCLK = SYSCLK/4 = 170/4 = 42.5Mhz */
g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;  
/* 12位模式 */          
g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_12B;     
/* 不需要增益补偿 */                 
g_adc_nch_dma_handle.Init.GainCompensation = 0;         
/* 右对齐 */                        
g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;      
/* 扫描模式 */                
g_adc_nch_dma_handle.Init.ScanConvMode = ADC_SCAN_ENABLE;  
/* 规则序列转换结束标志 */                     
g_adc_nch_dma_handle.Init.EOCSelection = ADC_EOC_SEQ_CONV; 
/* 禁用低功耗延迟模式 */                     
g_adc_nch_dma_handle.Init.LowPowerAutoWait = DISABLE;   
/* 使能连续转换 */                        
g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE; 
/* 3个转换在规则序列中 */                         
g_adc_nch_dma_handle.Init.NbrOfConversion = 3;       
/* 禁止不连续采样模式 */                           
g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE;  
/* 不连续采样通道数为0 */              
g_adc_nch_dma_handle.Init.NbrOfDiscConversion = 0;        
/* 软件触发 */                      
    g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;                
g_adc_nch_dma_handle.Init.ExternalTrigConvEdge = 
ADC_EXTERNALTRIGCONVEDGE_NONE; /* 使用软件触发 */
/* 开启DMA请求 */
g_adc_nch_dma_handle.Init.DMAContinuousRequests = ENABLE;                      
/* 数据溢出时覆盖ADC数据 */ 
g_adc_nch_dma_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; 
/* 禁用过采样功能 */                  
    g_adc_nch_dma_handle.Init.OversamplingMode = DISABLE;                           
    HAL_ADC_Init(&g_adc_nch_dma_handle);     /* 初始化 */                                       
    /* 以单端模式运行ADC校准 */
    HAL_ADCEx_Calibration_Start(&g_adc_nch_dma_handle, ADC_SINGLE_ENDED);           
}
 
/**
 * @brief       ADC底层驱动,引脚配置,时钟使能
 * @param       hadc:ADC句柄
 * @retval      无
 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
    if(hadc->Instance == ADC_ADCX)
    {
        GPIO_InitTypeDef gpio_init_struct;
        RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
        
        /* 设置ADC的时钟源 */
        PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC12;
        PeriphClkInit.Adc12ClockSelection = RCC_ADC12CLKSOURCE_SYSCLK;
        HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
        
        /* 使能ADCx时钟 */
        ADC_ADCX_CHY_CLK_ENABLE();
        
        /* 开启GPIO时钟 */
        ADC_ADCX_CH0_GPIO_CLK_ENABLE();
        ADC_ADCX_CH1_GPIO_CLK_ENABLE();
        ADC_ADCX_CH2_GPIO_CLK_ENABLE();
        
        /* AD采集引脚模式设置,模拟输入 */
        gpio_init_struct.Pin = ADC_ADCX_CH0_GPIO_PIN;
        gpio_init_struct.Mode = GPIO_MODE_ANALOG;
        gpio_init_struct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(ADC_ADCX_CH0_GPIO_PORT, &gpio_init_struct);
        
        gpio_init_struct.Pin = ADC_ADCX_CH1_GPIO_PIN;
        HAL_GPIO_Init(ADC_ADCX_CH1_GPIO_PORT, &gpio_init_struct);
        
        gpio_init_struct.Pin = ADC_ADCX_CH2_GPIO_PIN;
        HAL_GPIO_Init(ADC_ADCX_CH2_GPIO_PORT, &gpio_init_struct);
    }
}

       ADC初始化函数通过HAL_ADC_Init和HAL_ADC_ConfigChannel配置ADC1的相关参数,这一部分的原理和多通道ADC采集(使用DMA)实验是一样的,这里不再赘述。

       注意:代码中所使用的ADC通道是通过宏定义传入的,其中的ADC_ADCX_CH0、ADC_ADCX_CH1、ADC_ADCX_CH2实际上分别对应的是ADC1_CH4、ADC1_CH6和ADC1_CH7。

       接下来看ADC相关的DMA传输初始化函数:

/**
 * @brief       ADC DMA读取 初始化函数
 *   @note      本函数还是使用adc_init对ADC进行大部分配置,有差异的地方再单独配置
 * @param       par         : 外设地址
 * @param       mar         : 存储器地址
 * @retval      无
 */
void adc_nch_dma_init(void)
{
    __HAL_RCC_DMAMUX1_CLK_ENABLE();
    __HAL_RCC_DMA1_CLK_ENABLE();
 
    /* DMA配置 */
g_dma_nch_adc_handle.Instance = ADC_ADCX_DMASx;  /* 设置DMA通道 */ 
/* 设置DMA请求 */                        
g_dma_nch_adc_handle.Init.Request= ADC_ADCX_DMASx_REQUEST;        
/* 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);   /* 初始化DMA */                                      
    
    adc_init();                           /* 初始化ADC */                                       
    /* 配置对应ADC通道 */
adc_channel_set(&g_adc_nch_dma_handle , ADC_ADCX_CH0, ADC_REGULAR_RANK_1, 
ADC_SAMPLETIME_640CYCLES_5);
adc_channel_set(&g_adc_nch_dma_handle , ADC_ADCX_CH1, ADC_REGULAR_RANK_2, 
ADC_SAMPLETIME_640CYCLES_5);
adc_channel_set(&g_adc_nch_dma_handle , ADC_ADCX_CH2, ADC_REGULAR_RANK_3, 
ADC_SAMPLETIME_640CYCLES_5);
__HAL_LINKDMA(&g_adc_nch_dma_handle,DMA_Handle,g_dma_nch_adc_handle);
 
    /* 设置DMA中断优先级为3,子优先级为3 */
    HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 3, 3);                            
    HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn); /* 使能DMA中断 */                               
    /* 开启ADC的DMA传输 */
    HAL_ADC_Start_DMA(&g_adc_nch_dma_handle,(uint32_t *)g_adc_value,ADC_SUM);   
}

       该函数首先配置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盘→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就是所求的温度,它的默认单位是华氏度,我们最后还需要将其转换成摄氏度。温度计算函数的实现本质上就是这两个公式的代码体现,大家一定要跟着公式去理解代码。

       在main.c里面编写如下代码:

int main(void)
{
    uint8_t key;
    uint16_t t;
    int32_t motor_pwm = 0;                      /* PWM比较值 */
    uint16_t init_adc_val;
    
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(85, 2, 2, 4, 8);       /* 设置时钟,170Mhz */
    delay_init(170);                            /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
    led_init();                                 /* 初始化LED */
    lcd_init();                                 /* 初始化LCD */
key_init();                                 /* 初始化按键 */
/* 170 000 000 / 1 = 170 000 000 170Mhz的计数频率,计数8500次为50us */
    atim_timx_cplm_pwm_init(8500 - 1, 0);       
    dcmotor_init();                             /* 初始化电机 */
    adc_nch_dma_init();                         /* ADC DMA传输初始化 */
    
    lcd_show_string(10, 10, 200, 24, 24, "DcMotor Test", RED);
    lcd_show_string(10, 40, 200, 24, 24, "KEY0:Forward", RED);
    lcd_show_string(10, 70, 200, 24, 24, "KEY1:Backward", RED);
    lcd_show_string(10, 100, 200, 24, 24, "KEY2:Stop", RED);
    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 >= 8200)          /* 限速 */
                {
                    motor_pwm = 8200;
                }               
            }
            motor_pwm_set(motor_pwm);           /* 设置电机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 <= -8200)         /* 限速 */
                {
                    motor_pwm = -8200;
                }               
            }
            motor_pwm_set(motor_pwm);           /* 设置电机PWM的占空比 */
        }
        else if(key == KEY2_PRES)               /* 当key2按下 */
        {
            LED1_TOGGLE();
            dcmotor_stop();                     /* 关闭电机 */
            motor_pwm = 0;
            motor_pwm_set(motor_pwm);           /* 设置电机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所示:


图6.4.1 串口打印按键信息、电压、温度以及电流值

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

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

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    764146