|
电机开发板:
1)实验平台:正点原子DMF407电机开发板
2)平台购买地址: https://detail.tmall.com/item.htm?&id=677230699323
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/stm32dj/ATK-DMF407.html
4)对正点原子电机开发板感兴趣的同学可以加群讨论: 592929122
第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盘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. 原理图
图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所示:
图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 程序流程图
图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所示:
图6.4.1 串口打印按键信息、电压、温度以及电流值
|
阿莫论坛20周年了!感谢大家的支持与爱护!!
曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……
|