控制板MCU软件编写: 同样,写之前。我们先了解下,软件需要完成什么工作。 1.)首当其冲的肯定是通过PC2口识别转把的电压信号。 2.)根据PC2口检测到的电压,转化为PWM信号从PC4输出。 3.)控制电调供电,完成量程校准等操作。
了解了需要做什么之后,思路就清晰了。那我就简单说明下软件流程,注意,此处主要注重流程。因为大家使用的MCU各不相同,没必要了解内部实现源码。只需要了解清楚需要做什么就行。: 1.)第一步,没的说。肯定是先将各外设功能初始化好。
/**************************************
**名字:Init_Mcu
**功能:初始化MCU
**入参:无
**出参:无
***************************************/
void Init_Mcu(void)
{
Init_sfr(); //初始化MCU运行频率
InitPort(); //初始化MCU 各IO口
ADC_Init(); //初始化AD功能
PWM_Init(); //初始化PWM功能
//上电等待100MS
for(SysTimer=0;SysTimer<50;)
TimeQuery();
}
2.)其中AD类检测函数说明下: AD在上电时初始化开启后便不再关闭,参考电压源为VDD(5V),便于覆盖转把提供过来的电压信号。
/**********************************************************
**函数名称:ADC_Init
**函数功能:启动AD
**输入参数:无
**输出参数:无
**********************************************************/
void ADC_Init(void)
{
ADCON1 = 0x60;
ADCON0 = 0x99; //右对齐,参考电压为VDD电压,AN6通道,使能ADC
}
单独的转换函数,调用即启动一次转换。并将10位的AD值返回。
/**********************************************************
**函数名称:ADC_GO
**函数功能:启动一次转换 并等待结束
**输入参数:无
**输出参数:无
**********************************************************/
word ADC_GO(void)
{
word buf;
GO_DONE = 1;
while(GO_DONE)
TimeQuery();
buf = (word)(ADRESH<<8);
buf += ADRESL;
return buf;
}
这个函数直接通过检测到的电压换算为PWM档位,并赋值给PWM档位缓存。 注释中的解释 1:参考电压为VCC(5V),AD测量宽度为10bit。故得出AD值中的 1 = 5/1024 = 0.0048828125v
2:PWM范围,待会到了PWM初始化时再说明。
3:转把输出电压为实际测量,全范围大概在0.87-4.2v左右。留点余量故截取0.9V-4.1V,买回来油门控制器时,最好用万用表实际测量下范围。
4:将198档均分给3.2V电压,则每0.016v为一档,换算成AD值为3.2768。
函数执行内容:首先复位检测时间,先转换5次作为预热。由于MCU的工作量非常轻松,所以多做点无所谓。接下来是连续转换8次且将每次的结果累加起来,求出平均值。去掉0.9V的基础电压,剩下的则是属于需要转化为PWM档位的AD值。直接除3.2768得出档位。PWM档位缓存在基础值(后面PWM函数会介绍)上再加上转把转动的值即可,当然了,不能超过最大值。
//采样ADC 根据转换值选择档位
//1.数值1表示电压为:5/1024=0.0048828125
//2.PWM范围是198档
//3.转把输出电压范围0.9V-4.1V =3.2V
//4.将198档PWM分给转把电压 0.016V = 3.2768 1档
void ADC_config(void)
{
byte cnt;
AD_buf = 0;
AdcTimer = 0; //复位检测时间
for(cnt=0;cnt<5;cnt++) //空转5次
ADC_GO();
for(cnt=0;cnt<8;cnt++) //转换8次
ADC_Avg[cnt] = ADC_GO();
for(cnt=0;cnt<8;cnt++)
AD_buf += ADC_Avg[cnt];
AD_buf >>=3; //求出均值
//扣除0.9V基础电压
if(AD_buf>=185)
AD_buf -= 185;
else
AD_buf = 0;
//换算档位
AD_buf/=3.2768;
PWM_Cache = AD_buf+Pwm_Num_Base;
//不超过最大值
if(PWM_Cache > Pwm_Num_Max)
PWM_Cache = Pwm_Num_Max;
}
AD转换部分介绍完毕,接下来是PWM。 先说明下PWM控制电调的注意事项: 适合的频率为100hz左右。 油门最低到最高的范围是1MS-2MS(高电平) 知道了以上信息,则可以开始编写。这个函数的内容主要是初始化PWM,使其PWM周期到100hz附近,且高电平占空比输出到最低范围1MS。大家不用太在意源码内的寄存器是做什么的,只要知道这个函数做了什么就行。
由于量程的范围是在1MS-2MS。得出匹配寄存器内需要写入的值为0xC6(1MS)-0x18C(2MS), 中间的范围则是0xC6。PWM最低都是要有0xC6的匹配值来保证有最低1MS的输出,算一个基础值,也就是上面AD检测时的Pwm_Num_Base变量值。大家可以在我的工程文件内找到定义,Pwm_Num_Base就是0xC6。
OK,按照如下配置后。PWM就以及启动成功,频率为97hz,高电平占1MS。
/**********************************************************
**函数名称:PWM_Init 周期为97HZ
**函数功能:开启PWM,并初始化为最低值 1MS
**输入参数:无 量程1MS-2MS=0xc6 - 0x18c = 198-396=198 1=1.25us
**输出参数:无
**********************************************************/
void PWM_Init(void)
{
TRISC |= PWM; //开启PWM前需要将IO置为输入端
PWM3CR0 = 0x60; //12位PWM
PWM3CR1 = 0x90;
T3CKDIV = 19;
TMR3H = 0x00; //7:4TIMER3 计数结果高 4 位 3:0 PWM3 匹配寄存器 PR3 高 4 位
TMR3L = 0; //TIMER3 计数结果低 8 位
PR3L = 0xc6; //PWM3 匹配寄存器 PR3 低 8 位
TMR3IF = 0;
TMR3ON = 1;
TRISC &= ~PWM; //切换为输出
}
主要的外设都已经初始化完毕了。下面是一些辅助函数,从AD转换函数而知,能将转把的电压即时的换算为对应的档位值并存储到PWM_Cache内。这里面所存的值就是可以直接写入到PWM匹配值寄存器的,但是最好不要直接写。
你可以想象,如果你不小心瞬间扭转把扭得太多。而且是将这个值直接生效的话,你将会…没错!你将会体验到非常爽的推背感!再猛些也许可以翘车头。但是不推荐如此,别搞的妹没有撩成,反而扑街了。
所以我们先将值存储到一个缓存内,再让实际值慢慢靠近过去。这就是CheckPwm函数所做的工作。程序开启的定时器是2MS溢出一次,也就是说每2MS就会进入此处判断一次缓存值和现在值是否匹配。不匹配的话就比较大小,该增增,该减减。更改完后将值写入到PWM匹配值寄存器。
有细心的朋友可能已经发现,当实际值小于目标值时是一次步进1。但是实际值大于目标值时则是调用了Pwm_ChangeNow 函数。这个函数是立即生效的作用,直接将目标值写入到现在值与匹配值寄存器内。
为什么需要这么做,你可以想象下。当你在飙车的时候,前面突然跑出来了祖国的花朵 。这个时候正常的做法肯定是松开转把到最低,狠掐刹车。程序内的目标值就会直接到达最低范围,而如果现在值是慢慢靠过的话。就会导致刹车刹不死,对电机也损伤很大。所以降速时需要立即生效,提速可以有缓冲。
关于比对的时间2MS,可以根据大家实际的PWM匹配寄存器装填范围来设置。范围少的可以再延长比对时间。范围多的则可以让响应快些。我的建议是最好将范围设置的广一些,比如我的1MS-2MS之间可以装填的值有198级。分的比较细,扭动转把时就不会感觉到有突兀加速的感觉,比较柔顺。 不想一扭油门就猛的加速的同学,就可以将比对时间加长。或者可以限速,最大不能到2MS。 DIY就是这么爽,想咋滴就咋滴。
/**********************************************************
**函数名称:CheckPwm
**函数功能:比对PWM 现在值与缓存值
**输入参数:无
**输出参数:无
**********************************************************/
void CheckPwm(void)
{
TimeQuery();
//到达比对时间 且 值不相等时进入匹配
if((PwmTimer >= 1) && (PWM_Num != PWM_Cache))
{
PwmTimer = 0;
if(PWM_Num > PWM_Cache)
{
Pwm_ChangeNow();
return;
}
else
PWM_Num++;
TMR3H = (TMR3H&0xF0)|PWM_Num>>8;
PR3L = (byte)PWM_Num;
}
}
/**********************************************************
**函数名称: Pwm_ChangeNow
**函数功能:立刻生效PWM
**输入参数:无
**输出参数:无
**********************************************************/
void Pwm_ChangeNow(void)
{
PWM_Num = PWM_Cache;
TMR3H = (TMR3H&0xF0)|PWM_Num>>8;
PR3L = (byte)PWM_Num;
}
好了,各方面都以及配置完毕。下面进入主函数: 第一步将各外设初始化,上面已经说过了。
第二步就比较重要了,之前说过。第一次上电都需要和电调校准量程,它需要知道你的最低和最高的量程是多少。仅第一次上电需要校准,后续上电都不再需要。那怎么MCU怎么判断是否第一次上电呢? 我们可以将一个值写入到非易失性存储内。一般芯片都会有EEPROM,或者支持FLASH写入。上电就去读取这个空间,如果不是某个特定数字就认为是第一次上电。之后再把这个特定数字写入到这个空间,之后上电就会读取且匹配成功,不会再认为是第一次上电。
第一次上电的主要工作就是校准行程: 1.)先将油门开启到最大,且立即生效,生效后稳定3秒。
2.)开启电调电源。
3.)此时你会听到电机哔哔两声,MCU是没法知道的。所以我们一样直接等一段时间即可。如果电机未发出此声音,则需要检查问题。
4.)哔哔完后,将油门直接设置到最低且立即生效。此时会听到电机长哔一声。为了稳定,同样等待3秒。至此油门行程校准完毕。
5.)写入特定标识,下次上电不再校准。
下面则是再次启动一次电调电源(不需要校准时)。进入循环工作,每10ms检测一次油门电压。执行PWM比对函数。
void main(void)
{
Init_Mcu();
//判断是否第一次上电,第一次上电则需要校准量程
//流程为:油门达到最大量程时给电调供电。响铃后再将量程调整至最小。
if(ReadEEP(First_ProwerUp_Addr) != First_ProwerUp_Num)
{
//开启PWM输出至最大
PWM_Cache = Pwm_Num_Max;
Pwm_ChangeNow();
//等待PWM稳定后启动电调
for(SysTimer=0;SysTimer<50;)
TimeQuery();
Set_MOS();
//等待电调识别成功并响铃(bibi两声) 大概3秒
for(SysTimer=0;SysTimer<1500;)
TimeQuery();
//再将PWM调为最低
PWM_Cache = Pwm_Num_Min;
Pwm_ChangeNow();
//等待电调识别成功并响铃(长b一声) 大概3秒
for(SysTimer=0;SysTimer<1500;)
TimeQuery();
//写入非第一次上电标识
WriteEEP(First_ProwerUp_Addr,First_ProwerUp_Num);
}
ResetPwm();
Set_MOS();
while(1)
{
CheckPwm();
//每10MS检测一次AD 并更新PWM
if(AdcTimer >= 5)
ADC_config();
}
}