原创 我做的电池电量计量程序,附带又发现MSP430的两个令人无语之处,下面经验和程序对同学们会有用的

2011-1-6 00:37 7210 8 12 分类: MCU/ 嵌入式

要完成这样一个任务,对电动车的电池和电机状态做测量和显示。


电池(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


结果从这幅图上发现了问题:


bdd714a0-3657-438d-9875-dc8bf8922647.JPG


一个序列结束后竟然回到的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参加电赛的同学。


 

PARTNER CONTENT

文章评论4条评论)

登录后参与讨论

用户1642475 2013-8-10 16:08

谢谢楼主的细心和分享!

用户480583 2012-11-13 09:48

不错、不错!!!

用户1548781 2012-11-3 16:47

刚用430,正好用到TB触发AD这块,学习了

用户1601743 2011-7-26 11:39

写的不错,正在用这个系列的芯片,多谢分享
相关推荐阅读
用户311125 2011-07-30 23:14
发表科技论文的基本知识
Dr Leslie T Falkingham来我校做了个名叫The Writing of Technical Papers for Publication的报告,以下是对该报告的一个总结。1、为什么要...
用户311125 2011-03-28 22:43
15个编程好习惯[转]
15个编程好习惯 转自http://www.jobbole.com/伯乐在线 这是国外程序员Al katib总结的一些编程习惯。  1. 动手编码之前,你需要对要编码实现的解决方案有一个正式的或粗...
用户311125 2011-03-12 11:08
如何面试别人
今天竟然有一次面试别人的机会。这个我从来没有干过。______________________________________________________先转一篇比较好的文章——应聘者到来后,态度...
用户311125 2011-02-23 21:07
三相逆变(SPWM)仿真与分析
三相逆变(SPWM)仿真与分析要点:1、    下面是错的,假如定Vg的负极为参考地,那么三相输出的中性点不能接地。2、这样的SPWM是双极性的(d=0.5输出0V),但调制既可以用-1~1的载波,也...
用户311125 2011-02-19 11:46
精挑细选买相机,最后还是被宰了
昨天入手了松下的ZS7,单机价格只要1900,加松下原装4gSDHC卡和某品牌电池,再加发票之后要价2100。这个价格是绝对没有问题的,而且东西是正品行货也没有问题。关键是我鬼使神差又多花了100块钱...
用户311125 2011-02-15 00:40
大学最后一个学期的计划
明天返校,迎接新的学期,也是大学的最后几个月。 至少从意识上,应当认识到这几个月的宝贵,是万万不可浪费、万万要把时间花到最有价值的地方的。这几个月将很忙。 首先,在接到具体的毕业设计任务之前,有必要把...
EE直播间
更多
我要评论
4
8
关闭 站长推荐上一条 /3 下一条