打印

430的电量计

[复制链接]
1317|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
msp430ing|  楼主 | 2011-5-12 16:26 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
要完成这样一个任务,对电动车的电池和电机状态做测量和显示。
电池(12V*4,60Ah)方面,要对电量做比较精确地计量,能显示电池的电量百分比,电池消耗的功率。
电机方面要能测出转速,时速,记录里程累积。
电量计量方案:因为电流很大,50A的典型值,用电阻采样需要很小的精密电阻,和复杂的调理电路,或者专用芯片——比如TI的bq26200(这是个可能可行的方案),最终选用了霍尔电流传感器+AD采样的方法。
测量思路:在足够短的时间△t 内认为电流恒定,测量电流大小I,计算 t 内的电量变化
                    △Q=I△t(I有正负)
                    Q=Q0+△Q(Q为实时电量,Q­0为初始电量)
功率测量方案:测电流的同时测电压,跟据平均功率是瞬时功率的平均值,可以算每秒的平均功率,近似该秒的瞬时功率。
转速测量方案:电机装有霍尔传感器,每转一圈会发出若干个脉冲,用MSP430的捕获功能记录脉冲间隔时间就可以算出转速(电机和车轮速比1:1),在通过轮子的周长就可以算出时速和里程。


下面是用MSP430 f5438 实现的程序代码:
/*
电动车 电量计量、功率计量、转速测量 子程序

老沈 2011.1.1
说明:主程序为用法示例
函数说明:
initCLK() 将时钟配置在MCLK=SMCLK=12.288MHz,ACLK=XT1=32768Hz
initADC() 初始化AD,每10ms进行一次序列采样,采集 CH1(电池电压)和 CH2(电流信号的霍尔电压)
initTB()  配置TB:CCR0用作定时——周期匹配,CCR1用作比较输出——触发AD,
      CCR6用作捕获,CCR2输出仅作捕获测试信号
display() 用于显示当前电压和电量,1s更新一次
storeinfo() 用于将 里程和电量 累计值存入flash,放置CPU掉电失去信息。(10min更新一次)
restoreinfo() 用于从flash里恢复 里程和电量 (需要另外写程序把最初的Q和mileage存入flash)

变量说明:
全局变量:
Isample   电流的霍尔电压采样值 (抬高后)
Vsample   电池电压采样值
VIbattery 电流的霍尔电压值(抬高前)mV
Ibattery  电流值 A (应配置霍尔方向使电流输出为正)
Vbattery  电池电压值 V
Percent   电量百分比 %
Q         当前电量  C
deltaQ    1s内减少的电量 mC
Power     电池输出平均功率 Power= (ΣVbattery*Ibattery)/ 100 = P/100
interval  两次捕获脉冲的间隔 ms
speed     电机转速   r/min
velocity  时速       km/h
mileage   里程累积

参数常量:
Q0 216000     电池充满时的电量
KVI 20.0      霍尔比例系数 霍尔电压/电流
KVV 0.047619  电池电压衰减比例 由调理电路决定
ts 10         采样间隔 10ms
circle 1000.0 轮子周长 单位mm
AVCC 3300     霍尔输出调理电路的供电电压
PULSEof1000r  12000   每转如果输出12个脉冲,那么1000转有12000脉冲
PULSEof1r     12      每转的脉冲数


*/


#include  <msp430x54x.h>

#define Q0 216000   //60Ah=216000C
#define KVI 20.0   //4000mV/200A
#define KVV 0.047619      //10/210=0.047619
#define ts 10
#define circle 1000.0     //轮子周长 单位mm
#define AVCC 3300    // 霍尔输出调理电路的供电电压
#define PULSEof1000r  12000   // 每转如果输出12个脉冲,那么1000转有12000脉冲
#define PULSEof1r     12

void initIO();
void initCLK();
void initTB();
void initADC();
unsigned int Isample,Vsample,VIbattery,Percent,k=0;
float Vbattery,Ibattery;
float Q=Q0,deltaQ=0,P=0,Power=0;
unsigned int timecap=0,pretimecap=0,period=0,number_of_pulse=0;
float interval=0,speed=0,velocity=0,mileage=0;
//事实上,Q和mileage 应每隔一段时间存入flash, 程序复位时从flash读入
void main()
{
  initIO();
  initCLK();
  //restoreinfo();
  initADC();     
  initTB();
  
  _EINT();
  
  while(1)
  {
     LPM0;
     //display();//显示变量float speed,float Power,int Percent,float Ibattery, float mileage, float velocity
      //if(min%10==0) storeinfo(); //存储mileage 和 Q到 flash
  }     
}
void initTB()
{
//  CCR0用作定时——周期匹配,CCR1用作比较输出——触发AD,CCR6用作捕获,CCR2输出仅作捕获测试信号
  P4SEL |= BIT6;  //P4.6/TB0.6 作为捕获信号的输入引脚 49号
  P4DIR &= ~BIT6;
  
  P4SEL |= BIT1 + BIT2;         //TB1输出观察点 44号, TB2 45 号
  
  TBCCR0 =15360;  //10ms
  TBCCR1 =5;   //几微秒的时间,只是一个脉冲而已
  TBCCR2 =5000;                 //产生100Hz的方波输出,用于调试捕获功能
  
  TBCTL = ID_3 + TBSSEL_2 + MC_1 + TBCLR;       // SMCLK/8=1536k, MC1:up mode, clear TBR
  
  TBCCTL0 = CCIE;      // TBCCR0 interrupt enabled
  TBCCTL1 |= OUTMOD_7; //reset-set 模式
  TBCCTL2 |= OUTMOD_7; //reset-set 模式
  TBCCTL6 = CM_1 + CCIS_0 + SCS + CAP + CCIE; //上升沿捕获,输入信号为CCI6A(TB的CCIxA和CCIB接在了一个引脚,TA则分开了)
}

void initADC()
{
  int i;
  ADC12CTL0 &= ~ADC12ENC;
  
  P6SEL = BIT1|BIT2;           //使能AD输入 A1=P6.1(98号),A2=P6.2(99号)
  ADC12CTL0 = ADC12ON + ADC12MSC + ADC12SHT0_4 +  ADC12REFON + ADC12REF2_5V; // Turn on ADC12, extend sampling time(64*ADCclk=5.33us)
  for(i=0;i<100;i++) ;         //稳定参考源
  ADC12CTL1 = ADC12CSTARTADD_1 + ADC12SHS_3 + ADC12CONSEQ_1 + ADC12SSEL_3 + ADC12SHP;      // 触发源用TB1,单序列采样
  ADC12MCTL1 = ADC12INCH_1 + ADC12SREF_1;     //channel = A1 测电压
  ADC12MCTL2 = ADC12INCH_2 + ADC12SREF_1 + ADC12EOS;   // channel = A2 测电流
  ADC12IE = BIT2;                           // Enable ADC12IFG.2
  
  ADC12CTL0 |= ADC12ENC;                    // Enable conversions
}
void initCLK(void)
{
  P7SEL |= 0x03;                            // Select XT1
  UCSCTL6 &= ~(XT1OFF);                     // XT1 On
  UCSCTL6 |= XCAP_3;                        // Internal load cap
  UCSCTL3 = 0;                              // FLL Reference Clock = XT1   
  // XT1起振
  UCSCTL1 = DCORSEL_6;                      // 选择DCO的范围
  UCSCTL2 = 374;                            // 设置DCO频率为12M (12,288k)
  UCSCTL4 = SELM_4 + SELA_0 + SELS_4;       // 设置 MCLK = DCOC,SMCLK =DCO,ACLK=XT1(32768)
  // Loop until XT1 & DCO stabilizes
  while ( (SFRIFG1 &OFIFG))
  {
    UCSCTL7 &= ~(XT1LFOFFG + DCOFFG);       // Clear XT1,DCO fault flags
    SFRIFG1 &= ~OFIFG;                      // Clear fault flags
  }
    UCSCTL6 &= ~(XT1DRIVE_3);                 // Xtal is now stable, reduce drive strength
}
void initIO()
{
    WDTCTL = WDTPW+WDTHOLD;                   // Stop watchdog timer
           
    PADIR  = 0xFFFF;                          // Tie unused ports
    PAOUT  = 0;
    PASEL  = 0;
    PBDIR  = 0xFFFF;
    PBOUT  = 0;
    PBSEL  = 0;
    PCDIR  = 0xFFFF;
    PCOUT  = 0;
    PCSEL  = 0;
    PDDIR  = 0xFFFF;
    PDOUT  = 0;
    PDSEL  = 0;
    PEDIR  = 0xFFFF;
    PEOUT  = 0;
    PESEL  = 0;
    P11DIR = 0xFF;
    P11OUT = 0;
    P11SEL = 0;  
    PJDIR = 0xFF;
    PJOUT = 0;  

}
#pragma vector=ADC12_VECTOR
__interrupt void ADC12(void)
{
Isample=ADC12MEM2;
Vsample=ADC12MEM1;
        
        ADC12CTL0 &= ~ADC12ENC;                   // 开启下一个序列必须重给一个ENC上升沿
        ADC12CTL0 |= ADC12ENC;                    // Enable conversions

        //10ms内的累加
VIbattery=(long)Isample*2500/4095;
VIbattery=AVCC-VIbattery*2;
Ibattery= VIbattery/KVI;
    //    Ibattery=60;
deltaQ += Ibattery * ts;
Vbattery =Vsample*2.5/4095/KVV;
    //    Vbattery=48;
P += Vbattery * Ibattery;
k++;
//1s后的累加
if(k==100)
{
  k=0;
  Q=Q-deltaQ/1000;  //当前电量
  deltaQ=0;
  Percent = (int)(100*Q/Q0); //剩余电量比例
  Power=P/100;  //当前功率
  P=0;
  LPM0_EXIT;
}
}
//CCR0用作定时——周期计数
#pragma vector=TIMERB0_VECTOR
__interrupt void TB_Peroid (void)
{
        period++;
}
//CCR6用作捕获——计录每转时间,求速度   (引脚TB0.6/P4.6 (49号) 作为输入)
#pragma vector=TIMERB1_VECTOR
__interrupt void TB_CAP(void)
{
       switch( TBIV )
       {
             case  2: break;                     // CCR1 中断服务程序
             case  4: break;       // CCR2 中断服务程序
             case  6: break;                            //CCR3
             case  8: break;                            //CCR4
             case  10: break;                           //CCR5
            
             case  12:                                  //CCR6
              timecap = TBCCR6;
                //TBCCTL6 &=~COV;
              interval = period*10.0 + (timecap-pretimecap)*10.0/15360; //以ms为单位记录两次捕获的间隔
                speed = 60000 /interval /PULSEof1r ;    //转速单位 r/min (关系式speed*interval/60000=1/PULSEof1r)
               
                velocity = speed /16667 *circle  ; //速度单位 km/h    (关系式:转速*周长*60= 速度 mm/h)
                number_of_pulse++;
               
                if(number_of_pulse == PULSEof1000r)
                {
                   mileage += circle/1000;            //单位km   (1000*circle mm = circle m)
                   number_of_pulse =0;
                }
                                 
                pretimecap = timecap;
                period=0;
               break;
               
             case  14:break;                       // TAIFG 中断服务程序
       }
}
本程序关于MSP430的两个关键点:
我自以为用430用的可以了,没想到调程序时还是被卡住了。
这是因为我尝试了两个新功能,第一、用TB1OUT触发AD采样,而没用SC命令,第二、我第一次用MSP430的捕获功能。
刚写完程序我还夸430设计的好来着,因为AD的采样时间和转换时间完全由自己掌握,可以自己选择尽量快的采样时间,而转换只需要13个ADC12CLK,而AD的时钟可以给的很快。用定时器触发AD采样不用经过CPU,直接在后台开始,可以使采样相当准时。另外430的捕获功能不仅可以捕获,还可以直接读出当前的电平。
结果AD采一次就不能再采了,捕获根本就不进中断。
我检查了引脚的PxSEL已经确定选了第二功能,也确定开了模块中断和总中断。
这就奇怪了。
于是仔细研究user guide
结果从这幅图上发现了问题:

一个序列结束后竟然回到的wait for enable而不是wait for trigger
往常用的SC触发AD采样,是走左边一条路直接跳过wait for trigger(我本觉得SC也算trigger的一种的)开始Sample,如今用TB1out触发,就必须再来一次ENC的下降沿,才能进入wait for trigger,然后我的定时触发才有效。怪不得我的AD只采一次呢!可是TI的例程上全是SC触发,于是从来都只使能一次ENC就不用管了,这思维定势也不能怪我吧。。。只希望对同学们少走弯路有帮助。


那么捕获的问题呢,在IO那一章,430user guide要求把PxSEL设1选第二功能的时候说,有的模块第二功能要配合设置正确的IO方向,而我初始化系统时都把引脚设成输出了(不用的引脚悬空设成输出——为了低功耗嘛)AD输入引脚就不用管PxDIR,只要PxSEL=1,而捕获就偏偏PxDIR和PxSEL都要设置,我想你user guide怎么不说清楚呢,怎么这么不周到呢!!!而且引脚默认PxDIR=0,肯定有很多人不配置IO方向也照样能用捕获,所以网上查的程序里十有**没有专门写PxDIR&=~BITx这一句。我加上这句,程序就好了。。。不知道谁在误人子弟。
罗嗦了这么多,不好意思,在程序里就是这样两句
        ADC12CTL0 &= ~ADC12ENC;                   // 开启下一个序列必须重给一个ENC上升沿
        ADC12CTL0 |= ADC12ENC;                    // Enable conversions

       P4DIR &= ~BIT6;
有了这两句,我的程序正常了。


最后希望对大家有用,尤其是以后要用430参加电赛的同学。

相关帖子

沙发
3B1105| | 2011-5-13 12:39 | 只看该作者
太有用了,楼主太慷慨了

使用特权

评论回复
板凳
金鱼木鱼| | 2011-5-17 12:43 | 只看该作者
太有收藏价值了!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

0

主题

730

帖子

1

粉丝