2017年4月16日 星期日

Low Power WiFi Weather Display


總之這是一種試驗看看WiFi的耗電量究竟可以做到什麼程度,設計上以耗電量作為主要的考量
螢幕用的是Sharp Memory LCD,之前用過的感想覺得效果真的很棒,所以看到秋葉原賣另一個型號就請別人帶回來了,這次用的是2.7" 400x240的版本,不過技術和之前96x96的有差異,這個比較像是以前的TFT LCD,只是增強了反射性,但是耗電量還是相當的低,Datasheet規格是50uW = 5V x 10uA (這個螢幕真需要5V),雖然不像之前的那個拔掉一兩分鐘都還有顯示就是了XD

功能目前是每六個小時去撈中央氣象局的OpenData,抓36小時的天氣預報顯示這樣

核心這次有兩個,分別是WiFi SoC 的 MT7687,與 Power Management 的 MSP430G2553

首先來講一下MT7687,這顆是聯發科新推出的WiFi SoC, 標準的ARM Cortex - M4, 特色是記憶體有300K,而且有Arduino IDE支援, Prototype很快.
https://docs.labs.mediatek.com/resource/mt7687-mt7697/zh_tw/downloads
另外一個是MSP430G2553,由於Sharp Memory LCD需要一個Low Freq的Clock去翻Vcom的電位,所以單純只用WiFi Soc耗電量我想會有點太大,所以想找一顆小的MCU專門開關MT7687以及生出Memory LCD需要的Clock.最先的目標其實是手邊有一堆的Attiny84/85,但是後來研究了一下才發現這系列的待機耗電量還頗大...想想倒不如拿MSP430來做,發現便宜的G2553待機耗電量(With LPM3)就遠遠超過了Attiny85的能耐,只有1uA,而且可以用32Khz的Clock做timer,就拿MSP430G2553做了


然後是電源供應的設計,由於Memory LCD需要5V的電壓,所以勢必是需要一個5V的Step-up,
又因為這個螢幕超省電的,所以目光集中在低附載的效率與Iq,找到一顆Ti的TPS61099,BGA封裝還滿省空間的,而且從Datasheet看起來,感覺就是為了這目的(Low Power LCD Bais)設計的XDD

而3.3V因為MT7687的電流不小,所以決定還是用一個Buck-Boost增加效率,同樣找Iq與輕載效率高的,找到的是Ti的TPS62740,然後想說乾脆就在Ti找一個鋰電池的充電IC,就挑了個BQ24045

另外,由於PCB需要Cover到Memory LCD作為支撐,所以有一整面的空白覺得太空虛了,所以找了一個NFC Tag作為設定Wifi AP/Passwd用,然後再LCD底下放滿滿的NFC天線


然後就開始寫程式了
首先當然是要先寫個Parser去處理Wifi設定,Wifi 為了方便使用,有些廠商會在AP上面裝上NFC Tag,格式是同一個=>application/vnd.wfa.wsc

稍微查了一下格式長這樣:
Header : 0x10
Data Type: one byte
Data Length:two byte
Data[len]

Data type:
0x26 : Network index
0x45 : SSID
0x03 : authentication type
0x0F : encryption type
0x27 : Password
0x20 : MAC Address

encryption type分為:
0x0001 None
0x0002 WEP Deprecated.
0x0004 TKIP Deprecated. Use only for mixed mode.
0x0008 AES Includes both CCMP and GCMP

Authentication Type分為:
0x0001 Open
0x0002 WPA-Personal deprecated in version 2.0
0x0004 Shared deprecated in version 2.0
0x0008 WPA-Enterprise deprecated in version 2.0
0x0010 WPA2-Enterprise includes both CCMP and GCMP
0x0020 WPA2-Personal includes both CCMP and GCMP

Code 大概是這樣
使用方式是把NFC的資料Array和SSID,Passward與有無加密的Variable address pass進去
阿記得就是SSID,Passward的陣列開大一點,我在寫的時候沒檢查喔O.<
void parsing(uint8_t *data,uint8_t len,uint8_t *SSID,uint8_t*PASSWD,bool* auth){

  for(int i=0;i<len;i++){
    if(data[i]==0x10){  //Headder get
       i = i + 1;
       uint16_t len = (uint16_t)data[i+1]<<8 | (uint16_t)data[i+2];
       uint8_t type = data[i];
       //Serial.print("Type:");Serial.print(type,HEX);Serial.print(" Len:");Serial.println(len);
       if(type == 0x45){  //SSID
           display.println("SSID:");
           for(int k=0;k<len;k++){
             SSID[k] = data[i+3+k];
             //Serial.println(SSID[k]);
             display.write(SSID[k]);
           }
           //display.write((char*)SSID);
           display.println();
           display.refresh();
       }
       if(type == 0x0E){
          continue;
       }
       if(type == 0x27){   //PASSWD
           display.println("Passward GET");
           for(int k=0;k<len;k++){
            PASSWD[k] = data[i+3+k];
            //Serial.println(PASSWD[k]);
            //display.write(PASSWD[k]);
           }
           //display.write((char*)PASSWD);
           //display.println();
           display.refresh();
       }
       if(type == 0x03){
          display.println("Auth:");
          if(data[i+4]==0x01){
            *auth = 0;
            display.println("OPEN");
          }
          else{
            *auth = 1;
            display.println("WPA");
          }
          display.refresh();
       }
       i = i + 2 + len;
    }
  }
}


然後接下來是去撈Opendata......
所以我說那個中央氣象局為啥要用XML啊啊啊啊啊啊啊啊

真心覺得崩潰,所以我就不貼上來了Orz....
有需要再問,我再整理放上來

接下來是要把資料顯示出來,首先就是要顯示中文,然後就崩潰惹
因為再沒有外掛Flash的狀況下,一個中文字型檔案~1Mb是放不進去MCU的
所以我需要做兩件事情,地一個是把我要顯示的中文字列出來,第二件事情是找出一個方法
讓我可以把UTF-8的字找到對應的點陣字的Array index

第一件事情很簡單,拿個Python Script把CWB的Opendata說明檔的每個字抓出來比對就好
第二件事情我用的方式是Hash function,透過事先計算出一個UTF-8->index的Hash Function

我目前用的Graphics library是Adafruit GFX,所以我稍微修改了一下library之後
用Python做出前面的兩件事情之後,生出GFX用的fontfile.
其他的我就丟Github了:https://github.com/will127534/AdafruitGFX-ChineseFont-Addon

最後是MSP430惹,MSP430的工作非常簡單,就是要生出Clock還有關MT7687
MT7687結束的時候,會拉高一個pin,通知MSP430關電源這樣
我有點偷懶的是都寫在timer interrupt裡面,沒有特別開GPIO Interrupt就是了

#include  <msp430g2553.h>

#define Power P3_6

volatile unsigned int MT7687SleepCount = 0;
volatile bool MT7687Sleeped = 0;

void setup(void)
{
WDTCTL = WDTPW + WDTHOLD; // Stop WDT
     
BCSCTL1 |= DIVA_3; // ACLK/8
BCSCTL3 |= XCAP_3; //12.5pF cap- setting for 32768Hz crystal


        P1DIR = 0xFF;
        P2DIR = 0xFF;
        P3DIR = 0xFF;
     
        P1OUT = 0x00;
        P2OUT = 0x00;
        P3OUT = 0x00;
     
 
        pinMode(P3_7,OUTPUT);
     
        pinMode(Power,OUTPUT);
        digitalWrite(Power,LOW);
        delayMicroseconds(500);
     
        pinMode(P1_4,INPUT);
        pinMode(P1_5,INPUT);


CCTL0 = CCIE; // CCR0 interrupt enabled
CCR0 = 255; // 512 -> 1 sec, 30720 -> 1 min
TACTL = TASSEL_1 + ID_3 + MC_1; // ACLK, /8, upmode

_BIS_SR(LPM3_bits + GIE); // Enter LPM3 w/ interrupt
}
void loop(){
    _BIS_SR(LPM3_bits + GIE); // Enter LPM3 w/ interrupt

}
// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A (void)
{
digitalWrite(P3_7,!digitalRead(P3_7));

        if(digitalRead(P1_5)==HIGH){
          digitalWrite(Power,HIGH);
          pinMode(P1_4,OUTPUT);
          digitalWrite(P1_4,LOW);
          MT7687SleepCount = 0;
          MT7687Sleeped = 1;
        }
     
        if(sleep){
          MT7687SleepCount ++;
          if(MT7687SleepCount>43200){
            digitalWrite(Power,LOW);
            delayMicroseconds(50000);
            pinMode(P1_4,INPUT);
            MT7687Sleeped = 0;
          }
        }
     
}

每隔半秒就翻轉一次Pin,這樣耗電量<1uA


少數幾個缺點大概列一下

首先就是該隔離的就該隔離,MT7687因為要控制LCD,所以SPI一定要接在一起,但只要MT7687電源關閉,那SPI拉高的CS就會倒灌回去MT7687造成很大的耗電量,我原先以為MT7687的Floating Well就是設計來擋掉這件事情的,但是實測發現很明顯地沒有,所以才多了那一堆銅線Orz

第二個就是沒辦法量電壓,雖然板子上有預留測量Vbat的電阻,但是我沒有斷開分壓電阻的機制,這樣一來會造成漏電.

應該是不會繼續改版了,畢竟這樣穩穩用也沒啥問題
一切都藏得好好der