目录
一. 电压检测原理
电压检测是为了检测机器人的电池电量,锂电池的电量与电压值是呈正相关的。锂电池是严禁过放的,不然很容易损坏,尤其是对于水下机器人,电池坏了就要开舱维修,重新密封,代价很大。
电压检测原理很简单,初中都学过的分压定理。只需要两个电阻串联,将12V或24V的锂电电压降到3.3V以下的ADC检测范围,然后使用AD采样就可以读出电压值了。本项目中其实就是外连了两个电阻,然后用STM32的ADC功能读取电压,再推算电池电压。
二. ADC读电压驱动代码
新建一个adc.c和adc.h文件
adc.h文件内容如下:
#ifndef __ADC_H
#define __ADC_H
#include "sys.h"
#define MAX_VOLTAGE 25.2f/11.0f
void MY_ADC_Init(void); //ADC通道初始化
u16 Get_Adc(u32 ch); //获得某个通道值
u16 Get_Adc_Average(u32 ch,u8 times);//得到某个通道给定次数采样的平均值
#endif
因为使用的6S电池,满电25.2V,分压为1/11,所以最大电压25.2/11.
adc.c文件内容如下:
#include "adc.h"
#include "delay.h"
ADC_HandleTypeDef ADC1_Handler;//ADC句柄
//初始化ADC
//ch: ADC_channels
//通道值 0~16取值范围为:ADC_CHANNEL_0~ADC_CHANNEL_16
void MY_ADC_Init(void)
{
ADC1_Handler.Instance=ADC1;
ADC1_Handler.Init.ClockPrescaler=ADC_CLOCK_SYNC_PCLK_DIV4; //4分频,ADCCLK=PCLK2/4=108/4=27MHZ
ADC1_Handler.Init.Resolution=ADC_RESOLUTION_12B; //12位模式
ADC1_Handler.Init.DataAlign=ADC_DATAALIGN_RIGHT; //右对齐
ADC1_Handler.Init.ScanConvMode=DISABLE; //非扫描模式
ADC1_Handler.Init.EOCSelection=DISABLE; //关闭EOC中断
ADC1_Handler.Init.ContinuousConvMode=DISABLE; //关闭连续转换
ADC1_Handler.Init.NbrOfConversion=1; //1个转换在规则序列中 也就是只转换规则序列1
ADC1_Handler.Init.DiscontinuousConvMode=DISABLE; //禁止不连续采样模式
ADC1_Handler.Init.NbrOfDiscConversion=0; //不连续采样通道数为0
ADC1_Handler.Init.ExternalTrigConv=ADC_SOFTWARE_START; //软件触发
ADC1_Handler.Init.ExternalTrigConvEdge=ADC_EXTERNALTRIGCONVEDGE_NONE;//使用软件触发
ADC1_Handler.Init.DMAContinuousRequests=DISABLE; //关闭DMA请求
HAL_ADC_Init(&ADC1_Handler); //初始化
}
//ADC底层驱动,引脚配置,时钟使能
//此函数会被HAL_ADC_Init()调用
//hadc:ADC句柄
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_ADC1_CLK_ENABLE(); //使能ADC1时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); //开启GPIOA时钟
GPIO_Initure.Pin=GPIO_PIN_4; //PA5
GPIO_Initure.Mode=GPIO_MODE_ANALOG; //模拟
GPIO_Initure.Pull=GPIO_NOPULL; //不带上下拉
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
}
//获得ADC值
//ch: 通道值 0~16,取值范围为:ADC_CHANNEL_0~ADC_CHANNEL_16
//返回值:转换结果
u16 Get_Adc(u32 ch)
{
ADC_ChannelConfTypeDef ADC1_ChanConf;
ADC1_ChanConf.Channel=ch; //通道
ADC1_ChanConf.Rank=1; //1个序列
ADC1_ChanConf.SamplingTime=ADC_SAMPLETIME_480CYCLES; //采样时间
ADC1_ChanConf.Offset=0;
HAL_ADC_ConfigChannel(&ADC1_Handler,&ADC1_ChanConf); //通道配置
HAL_ADC_Start(&ADC1_Handler); //开启ADC
HAL_ADC_PollForConversion(&ADC1_Handler,10); //轮询转换
return (u16)HAL_ADC_GetValue(&ADC1_Handler); //返回最近一次ADC1规则组的转换结果
}
//获取指定通道的转换值,取times次,然后平均
//times:获取次数
//返回值:通道ch的times次转换结果平均值
u16 Get_Adc_Average(u32 ch,u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t<times;t++)
{
temp_val+=Get_Adc(ch);
delay_ms(5);
}
return temp_val/times;
}
这里面的调用函数就是u16 Get_Adc_Average(u32 ch,u8 times)
,times参数是设置平均的次数。
三. 传感任务增加读电压应用代码
回到之前我们创建的sensor.c和sensor.h文件,之前我们在里面增加了读姿态和水深的代码,现在我们继续增加读电压的代码。
float filterVolt[10];
float sumVolt;
u8 count_volt;
void sensorReadVoltage(float *volt)
{
u16 adc_value;
float adc_result;
float temp;
adc_value = Get_Adc(ADC_CHANNEL_4);
adc_result = (float)adc_value * (3.3/4096);
temp = filterVolt[count_volt];
filterVolt[count_volt] = adc_result;
sumVolt += filterVolt[count_volt] - temp;
count_volt++;
if (count_volt == 10) count_volt = 0;
*volt = sumVolt / 10.0f * 7.635; // 分压1/11
}
这个地方没有使用前面adc.c文件中的u16 Get_Adc_Average(u32 ch,u8 times)
,这个函数其实是分段平均。我这里依然使用的滑动平均滤波,读取速度会更快,误差也更小。
main文件传感任务中增加读电压值的代码,其实只增加了一行代码,sensorReadVoltage(&sensorData.voltage)
sensor_task内容变成这样(建议与上一篇对照阅读):
u8 sensor_task(void *p_arg)
{
OS_ERR err;
CPU_SR_ALLOC();
float Gyro[3], Angle[3];
float wDepth;
u8 count = 20;
//滤波初始化
while (count--)
{
sensorReadAngle(Gyro, Angle);
sensorReadDepth(&wDepth);
}
// 初始化之后,所有期望值复制为实际值
state.realAngle.roll = Angle[0];
state.realAngle.pitch = Angle[1];
state.realAngle.yaw = Angle[2];
state.realDepth = wDepth;
setstate.expectedAngle.roll = state.realAngle.roll;
setstate.expectedAngle.pitch = state.realAngle.pitch;
setstate.expectedAngle.yaw = state.realAngle.yaw; //初始化之后将当前的姿态角作为期望姿态角初值
setstate.expectedDepth = state.realDepth;
while (1)
{
/********************************************** 获取期望值与测量值*******************************************/
sensorReadAngle(Gyro, Angle);
sensorReadDepth(&wDepth);
//反馈值
state.realAngle.roll = Angle[0];
state.realAngle.pitch = Angle[1];
state.realAngle.yaw = Angle[2];
tate.realDepth = wDepth;
state.realRate.roll = Gyro[0];
state.realRate.pitch = Gyro[1];
state.realRate.yaw = Gyro[2];
sensorReadVoltage(&sensorData.voltage);
delay_ms(5); // 水深传感器单次读取需要20ms+, 所以这里的延时小一点
}
}