|
【TI首届低功耗设计大赛】【超低功耗码表】完结篇
[复制链接]
本帖最后由 mark86739851 于 2014-12-31 12:13 编辑
作品终于告一段落了,虽然没有多少技术难度,不过在这个过程当中熟悉了FR系列单片机,熟悉了CCS开发环境,熟悉了TI的430代码库,还是很不错的。
我的超低功耗码表原理很简单,用32768晶体作为ACLK时钟源,不分频,直接给定时器使用。单片机P1.3管脚用做外部中断,检测自行车的触发信号,市场上几乎所有的码表都用的干簧管加磁铁,这样可以保证系统的低功耗要求。干簧管配上少量外部电路就能产生方波信号。每接收到一次下降沿就证明车轮转了一圈,累加车轮周长就是路程,计算两次中断间的时间差就可以得到瞬时速度,总路程除以总时间就是平均速度。下面是估算过程:
速度计算考虑到自行车触发频率不是很高,采用测周期法测频率误差小:
触发频率估算:普通人平路上骑车最高速度40km/h下山冲刺可能加速到60~70km/h,再高就相当危险了,达到100km/h随时都可能丧命,普通人是不可能做到的。当速度为100km/h假设自行车轮胎为26英寸(约660.4mm),那么干簧管的触发频率为(100/3.6)*1000/660.4≈42Hz,即便速度达到200km/h,也只上升到84Hz左右,这对单片机来说是很低的频率了,
这时用周期法测频率最准确了。当人推车步行时一般为3km/h,触发频率大概为1.6Hz,非常慢的情况下1km/h(女生散步也比这快吧),频率降到0.5Hz,大概每2s触发一次。如果速度再低是不太可能的现象(没有人骑车或推车走这么慢)不过还是照顾下“叫真儿”的群众们,超过2s不触发,就当作速度为0处理。
接下来是测量精度问题。周期法的特点是频率越低测量越精准,设速度为SPD(km/h),显示精确到0.1km/h,定时器最小定时单位为 tick,则为了满足0.1km/h的精度,测量周期时精度要小于等于0.1/SPD:
tick 0.1
----------------------- ≤ --------- , 即:tick ≤ 42ms,用32768做定时器时钟源最小计数单位为30.5us,绝对够用,
SPD x 1000 x 1 SPD
3.6 660.4
同时,16位的计数器刚好能计量2s,作为低速溢出判断很合适。
在管脚外部中断函数里判断定时器的状态:
- void Port_1(void)
- {
- uint16_t TimerA_counterValue;//暂存定时器计数值
- if(P1IFG&BIT3){
- TimerA_counterValue = Timer_A_getCounterValue(TIMER_A1_BASE);
- Timer_A_clear(TIMER_A1_BASE);
- //如果计数为0,说明之前定时器处于关闭状态(刚好溢出的情况非常小,即便出现了忽略掉影响甚微),应当开启定时器
- if(TimerA_counterValue == 0){
- //开启定时器
- Timer_A_startCounter(TIMER_A1_BASE,TIMER_A_CONTINUOUS_MODE);
- ridingFlag = true;
- // 开启LED
- GPIO_setOutputHighOnPin(GPIO_PORT_P1, GPIO_PIN0);
- }
- //如果定时器值不等于0,说明这不是第一次进入该中断了,定时器里的值就是两次中断的时间差,开始计算相关参数
- else{
- ODO += WHEEL_SIZE;//总行程计数
- singalTrip.DST += WHEEL_SIZE;//单次行程计数
- // (WHEEL_SIZE/10^6) * 3600
- // SPD= ----------------------------------------------= 779039.5392/n
- // n
- // ----------
- // 32768
- singalTrip.SPD = (uint16_t)(((uint32_t)77903954/TimerA_counterValue)/100); //单位:百米/小时
- //当首次开始计算时骑行时间为0s,无法计算平均速度,用此时的瞬时速度代替,防止出现分母为0的现象
- if(singalTrip.TM.totalSeconds == 0){
- singalTrip.AVS = singalTrip.SPD;
- }
- else{//平均速度=骑行路程/骑行时间
- // (DST/10^6)*3600 DST*0.0036 DST
- // ----------------------------- = -------------------------- =( ------------------- * 36 )/10000
- // Seconds Seconds Seconds
- singalTrip.AVS = (uint16_t)(((singalTrip.DST/singalTrip.TM.totalSeconds)*36)/10000);
- }
- }
- }
- if(P1IFG&BIT1){ //清除单次骑行记录
- singalTrip.AVS = 0;
- singalTrip.DST = 0;
- singalTrip.TM.totalSeconds = 0;
- singalTrip.TM.Seconds = 0;
- singalTrip.TM.Minutes = 0;
- singalTrip.TM.Hours = 0;
- }
- P1IFG = 0;
- // GPIO_clearInterruptFlag(GPIO_PORT_P1, GPIO_PIN0+GPIO_PIN3);
- }
- <font size="4">定时器里要做的就简单多了:</font>
- <div class="blockcode"><blockquote>void TIMER1_A1_ISR(void)//最长2s中断一次
- {
- //Any access, read or write, of the TAIV register automatically resets the
- //highest "pending" interrupt flag
- switch(__even_in_range(TA1IV,14)) //0~14内TA1IV为偶数才进switch
- {
- case 0: break; //No interrupt
- case 2: break; //CCR1 not used
- case 4: break; //CCR2 not used
- case 6: break; //CCR3 not used
- case 8: break; //CCR4 not used
- case 10: break; //CCR5 not used
- case 12: break; //CCR6 not used
- case 14:
- //如果定时器溢出,说明用户没有在骑自行车,停止计算速度,关闭定时器
- timerA_OverFlowCnts++;//溢出一次加一次,相当于扩展成24位定时器
- Timer_A_stop (TIMER_A1_BASE);
- Timer_A_clear(TIMER_A1_BASE);
- ridingFlag = false;
- singalTrip.SPD = 0;
- // 关闭LED
- GPIO_setOutputLowOnPin(GPIO_PORT_P1, GPIO_PIN0);
- break;
- default: break;
- }
- }
复制代码 定时器溢出,说明速度太慢,只是偶尔触发,并没有骑行,将速度归零,骑行标志复位。
整个过程都是用中断来实现的,系统实时性强,但显示操作相对来说比较费时,不过它的实时性要求低,因此可以放到主循环里执行:
整过过程无需全程开启CPU,只在RTC时钟1s中断到来后唤醒CPU,显示一下就行了,平时进入LPM3休眠模式。
- _Bool reflashFlag = true;//LCD刷新标志
- void main(void) {
- SystemInit();
- while(1){
- if(reflashFlag == true){
- DispRealTime(¤tTime);
- DispSpeed();
- reflashFlag = false;//刷新过了标志失效
- }
- __bis_SR_register(LPM3_bits + GIE);
- }
- }
复制代码 来看一下有方波信号和没方波信号时候的功耗差别吧,
有输入信号:
下面是无输入信号时候:
主要是因为有信号输入时每秒钟都会刷新一次LCD,刷新过程CPU全速运行。
CCS6.0的功能还是很赞的,虽然速度上比不上IAR,但功能上超出了不少。
回到正题,这个码表的创新点就是利用FRAM的非易失性,将要保存的变量定义在不用初始化的FRAM区,这样复位也好,掉电也好都不会对变量的值清零。我是看了一个老外发的帖子学来的:http://processors.wiki.ti.com/in ... Variables_Using_CCS
首先要修改下lnk_msp430fr5969.cmd文件,把原来的FRAM区域声明注释掉,重新开辟一个FRAM_VARS区域- MEMORY
- {
- SFR : origin = 0x0000, length = 0x0010
- PERIPHERALS_8BIT : origin = 0x0010, length = 0x00F0
- PERIPHERALS_16BIT : origin = 0x0100, length = 0x0100
- RAM : origin = 0x1C00, length = 0x0800
- INFOA : origin = 0x1980, length = 0x0080
- INFOB : origin = 0x1900, length = 0x0080
- INFOC : origin = 0x1880, length = 0x0080
- INFOD : origin = 0x1800, length = 0x0080
- <font color="Red">//FRAM : origin = 0x4400, length = 0xBB80
- FRAM_VARS : origin = 0x4400, length = 0x0400 //1k byte
- FRAM : origin = 0x4800, length = 0xB780</font>
- FRAM2 : origin = 0x10000,length = 0x4000
- JTAGSIGNATURE : origin = 0xFF80, length = 0x0004, fill = 0xFFFF
- BSLSIGNATURE : origin = 0xFF84, length = 0x0004, fill = 0xFFFF
- IPESIGNATURE : origin = 0xFF88, length = 0x0008, fill = 0xFFFF
- INT00 : origin = 0xFF90, length = 0x0002
- INT01 : origin = 0xFF92, length = 0x0002
- INT02 : origin = 0xFF94, length = 0x0002
复制代码 将这个区域的性质定义成不用初始化,这样一来上电复位变量值就不会被清零了。
- .infoA : {} > INFOA /* MSP430 INFO FRAM MEMORY SEGMENTS */
- .infoB : {} > INFOB
- .infoC : {} > INFOC
- .infoD : {} > INFOD
- <font color="Red">.fram_vars : {} > FRAM_VARS type=NOINIT</font>
复制代码 最后是定义变量了,一定要区分开是定义在SRAM中的变量还是定义在FRAM_VARS区中的变量。所以像平常那样声明是不行的。
IAR可以用__no_init关键字,但CCS里没有类似的关键字,好像只能修改 .cmd 文件开辟一个NOINIT空间来实现,然后在代码中这样声明
- <font color="Red">#pragma SET_DATA_SECTION(".fram_vars")</font>
- Calendar currentTime; //当前系统时间
- _Bool ridingFlag = false; //用户是否在骑行
- unsigned char timerA_OverFlowCnts = 0; //记录TimerA溢出次数
- singalTripData singalTrip; //单次骑行数据
- uint64_t ODO; //总行驶路程,单位0.1mm(因为轮胎外径精确到0.5mm)
- <font color="Red">#pragma SET_DATA_SECTION()</font>
复制代码 把你要放在不用初始化的FRAM区域的变量包含在两条红色语句中就行了。个人感觉没有IAR的关键字方便。还有一个不方便的地方就是CCS没有@Address功能,不能在定义变量时就直接给定绝对地址(或许有但我没找到)。
下面附上视频
由于其电路和原理简单,就没必要画原理图和PCB了
已发的相关贴子:
【TI首届低功耗设计大赛】【超低功耗码表】
【TI首届低功耗设计大赛】【超低功耗码表】--LCD驱动
【TI首届低功耗设计大赛】【超低功耗码表】--方便的Calendar
|
|