2017年8月31日 星期四

定時灑水器

由於曾經開發過紅外線沖水產品,因此此類的用水控制大同小異,也將經驗分享一下給大家!
定時灑水器電路設計及軟體設計都比較單純,大致上的架構如下為使用MCU控制H bridge來驅動脈衝電磁閥,其實這種電磁閥就是一個電磁鐵的裝置,用脈波控制水閥開啟或關閉,控制脈波大概幾十ms的方波就可以了,也可以搭配紅外線測距完成紅外線小便斗沖水器及紅外線水龍頭!

水用電磁閥圖片

控制電路 : 
動作原理如下,圖中負載為電磁閥開啟或關閉分別為不同方向的電流,簡單的控制方式為
Direction 1 : 
P1B = 0;
P1A = 1;
delay_ms(50); 
P1A = 0;

Direction 2:
P1A = 0;
P1B = 1;
delay_ms(50);
P1B = 0;

MCU可以隨便選擇一個Microchip pic16 8bit的MCU即可, Program memory 1k or 2k word, Data memory 128~256 bytes, 可以搭配Timer1 32.768k振盪器,做計時器的功能,宣告三個變數Hour / Minute / Second,每秒產生一次timer中斷,然後將利用定時器寫一個可以設定時間的時鐘,然後看要隔多久自動沖水灑水一次即可,定時器的精準度要靠軟體去撰寫,盡量少除頻,然後不要去更新Timer的數值,讓timer自動跑到溢位發生中斷,32.768kHz crystal也可以選擇精度較高的(20ppm或以下),另外需要使用七段顯示器或者TN/STN LCD來顯示時間,並預留按鍵調整時間!

Microchip台灣的論壇有相關的討論:http://www.microchip.com.tw/modules/newbb/viewtopic.php?topic_id=177

Timer程式虛擬碼:
volatile unsigned char Hour = 0, Minute = 0, Second = 0;
void interrupt ISR(void)
{
    if(TMR1IF && TMR1IE)
   {
        TMR1IF = 0;
        if(++Second == 60)
        {
             Second = 0;
             if(++Minute == 60)
             {
                  Minute = 0;
                  if(++Hour == 24)
                  {
                       Hour = 0;
                  }
              }
        } 
    }
}



利用GPIO達到Software UART TX

有時在開發過程中撰寫程式時,礙於成本考量,使用Low pin count MCU,因此對於IC的開發工具不支援Low pin count的Debug模式,要如何偵錯就是一個課題,在這邊將簡單的介紹利用GPIO pin將UART的資料送出,經由UART轉USB連接線接到電腦去觀看數據資料,因此需了解UART傳送格式如以下 :
Idle : High
Start bit : Low
Data : LSB first


也可以透過電腦終端機軟體Tera Term了解以下資訊 :
Port : 跟PC的哪個COM port連線
Baud rate : 傳送速度(每秒多少位元,常見9600 bit per second)
Data : 8bit or 9bit(常見為8bit)
Parity : 同位元檢查(錯誤檢查用,若只是用來做開發的Debug用途,可以選none)
Stop : 1 / 1.5 / 2 bit(通常為1bit)
Flow control : 流量控制(基本上MCU不需要使用到)

廢話不多說,假設要使用最常見的9600/8/N/1, 這邊會說明以下幾個步驟 :

(1)設定GPIO為輸出

(2)設定timer中斷或者使用delay,我最常用是使用timer中斷,設定方法為先知道傳送速度,假設為9600bps,也就是說每104us送一個bit,因此timer interval先設定為104us,當要傳送時,開啟timer計數等待中斷發生之後傳送第一個Start bit,接著等下一次中斷傳送LSB,繼續等待下次中斷直到LSB送完之後並且送完Stop bit;一個bit一個bit緊接著傳送的方式要有移位的觀念,假設MCU使用的是PIC16系列程式如下:

volatile unsigned char TX_Data;
volatile unsigned char Cnt = 0;

#define TX  LATCbits.LATC7

// Cnt = 0 : Start bit
// Cnt = 1~8 : Data bit
// Cnt = 9 : Stop bit
void interrupt ISR(void)
{
     if(TMR2IF && TMR2IE)
    {
         TMR2IF = 0;
         if(Cnt == 0)
        {
             TX = 0;           // Start bit
        }
        else if(Cnt < 9)    // Data bit : 1 ~ 8
       {
            if ((TX_Data >> (Cnt - 1)) & 0x01)  /* bit to send */
  TX = 1;                                              
           else
TX = 0;
        }
        else
       {
            TX = 1;    // Stop bit
            TMR2ON = 0;
            Cnt = 0;
            return;
       }
        Cnt++;
}

void main(void)
{
    Init_MCU();
    Init_Timer2();  // 104us interval

    TX_Data = 0x55;
    TMR2ON = 1;   // Start UART TX
    PEIE = 1;
    GIE = 1;

    while(1);

    return;
}







2017年8月30日 星期三

光纖通訊~Fiber Optic

光纖通訊Fiber-optic communication)也作光纖通訊,是指一種利用光纖optical fiber)傳遞資訊的一種方式,屬於有線通訊的一種。光經過調變modulation)後便能攜帶資訊。
利用光纖做為通訊之用通常需經過下列幾個步驟:
·         發射器transmitter)產生光訊號。
·         以光纖傳遞訊號,同時必須確保光訊號在光纖中不會衰減或嚴重變形。

·         接收器receiver)接收光訊號,並且轉換成電訊號


至於發射器及接收器與光纖可以參考Industrial optic的產品 http://i-fiberoptics.com/fiber-optic-leds.php, 要注意的是發射器與接收器光譜波長盡量要相同,這樣做響應才會匹配!

之前曾經在學校中使用IF E91D及IF D91用來做電池平衡板的通訊,由於電池是串連的,多串之後電壓非常高,採四串一片BMS做管理,假設電池有16串,那共有四片BMS,如果將四片BMS共地通訊的話,會因為電池短路產生燒毀,因此必須採用隔離通訊,而光通訊就是一個很好的方式!


光纖Cable

光發射器(IF E91D)





光接收器(光二極體)




 光放大電路很簡單,下圖的建議電路為反向型的,也就是說當有光接收時輸出為LOW,而放大原理也很簡單,光接收器接受光時產生逆向光電流,由於OP輸入阻抗無限大,因此電流不會流入OP的反向輸入端,而會流向RF, V = Id * RF,因此有一個輸出電壓,若邏輯上不習慣可以將IF D91的A(+)接地然後K(-)接OP的反向輸入端,這樣輸出邏輯就會是正的,放大電路完成之後必須考量若要傳送的是數位訊號,就要將訊號變成Full swing,因此OP輸出之後再接比較器去做電壓得比較,就可以完成數位01010101的輸出了!
使用上可以直接將光發射器利用MCU的UART TX去控制訊號的發送,然後接收器接到另外一個MCU的UART RX,利用MCU的UART Module很容易可以解出所送出的資料!
對於光的傳送也可以進行調變,例如使用ASK搭配OOK(ON/OFF Keying)協定,訊號進去OP放大之後會如下圖的正弦波,再透過Logarithmic amplifier將訊號整形(類似低通),然後經由比較器將訊號轉數位01訊號,經過調變之後的訊號在資料的錯誤率會比較低!

Logarithmic Amplifier(對數放大器)訊號處理示意圖如下,INPUT為ASK調變的訊號,經過對數放大器後為OUTPUT,若再經過比較器之後就會變成漂亮的數位輸出!


2017年8月29日 星期二

反射式紅外線測距~Photodiode

常見的非接觸式水龍頭/小便斗/酒精消毒機等...設備都是利用反射式紅外線的原理,利用光反射回來的強度來判定造成反射之障礙物的距離,在此要介紹如何利用紅外線發射器與感測器來設計非接觸距離偵測!

為何要使用紅外線?有何優點?
其實選擇紅外線的原因是因為紅外線為不可見光,選用不可見光的發送器及接收器來設計比較不會受到環境或背景光源的干擾,而發送器及接受器的響應光譜範圍盡量要一致,才能匹配使用!

下圖為一般常見5mm直徑之IR LED

IR LED特性曲線,波長峰值940nm
PIN Photodiode如下,光譜響應範圍為860~1100nm,響應峰值波長為960nm,也就是說當IR接收到的光是960nm時,此時的輸出會比較強




有了紅外線光發設計及接收器之後,必須要了解如何設計電路來發送光與接收光,發送的部分就很簡單,只要用一個NPN電晶體或NMOS就可以用來控制IR LED ON/OFF,比較會有問題的是紅外線光接收器!我們可利用接收器受光之後會產生微弱的逆向光電流,會由PIN Photodiode的K(-)流向A(+),藉由逆向光電流的特性,我們可以使用電阻將電流轉電壓,然後透過OP放大器將微弱的電壓進行放大!

下圖為整個感測系統的電路設計,光發送器透過MOSFET控制ON/OFF,然後光接收器受光之後產生逆向光電流,電流流經R5會產生電壓,然後經過C3耦合到OP的輸入端,R1及R2的分壓用來做輸入偏壓電路,R3及R4的比例為OP的GAIN,C5用來抑制低頻增益,C1用來抑制高頻增益,最後OP輸出經由C2將交流訊號耦合到R9(移除直流偏壓),R9的電壓可接至比較器或者MCU ADC做偵測!





2017年8月25日 星期五

觸摸感應Sensor

要實現一個簡易的觸摸感應Sensor來控制其他裝置,可以透過簡單的555 ICMCU來實現,
US6844713 B2專利為用來做Stud finder的應用,是用來做牆體木材角料的偵測,但其實可以透過這樣的應用將其改成作為觸摸控制的應用!

要如何實現觸控可以透過規劃555振盪器在無穩態工作模式,

555無穩態電路
無穩態工作模式下555計時器可輸出連續的特定頻率的方波。電阻R1接在VCC與放電引腳(引腳7)之間,另一個電阻(R2)接在引腳7與觸發引腳(引腳2)之間,引腳2與閾值引腳(引腳6)短接。工作時電容通過R1R2充電至2/3 VCC,然後輸出電壓翻轉,電容通過R2放電至1/3 VCC,之後電容重新充電,輸出電壓再次翻轉。
無穩態模式下555振盪器輸出波形的頻率由R1R2C決定:
輸出高電平時間由下式給出:
輸出低電平時間由下式給出:
R1的額定功率要大於.
對於雙極型555而言,若使用很小的R1會造成OC門在放電時達到飽和,使輸出波形的低電平時間遠大於上面計算的結果。為獲得占空比小於50%的矩形波,可以通過給R2並聯一個二極體實現。這一二極體在充電時導通,短路R2,使得電源僅通過R1為電容充電;而在放電時截止以達到減小充電時間降低占空比的效果。

一般自電容感測都是用ADC來檢測因為電容的變化量造成充電時間的改變,因此在取樣時會因為電容變化與沒有電容變化時有電壓差,但本文範例是以偵測震盪頻率為主,當555在固定的電容底下會產生固定的震盪頻率稱為Base line,我們可以將固定電容改成pad(copper pad),以pad當作電容來產生震盪頻率震出Base line的頻率,當有手指觸摸到pad時,電容值改變了,因此震盪頻率改變,與Base line產生頻率差異,藉此可以用MCU計算出變化量,進而產生對應的控制!

以下是範例程式



新增說明文字





2017年8月24日 星期四

函式指標的應用

函式指標 : 顧名思義就是指向函式的指標,宣告方式為
"傳回值型態 (*函式指標名稱)(傳遞參數);
example :
假設要指向void add(int a, int b);的函式指標為void (*math_ptr)(int, int);
以下四串電池主動平衡板的程式當中的BubbleSort的傳遞參數當中用到了函式指標,
也宣告了函式指標陣列ModeFunPtr用來指向幾十個要執行的主動平衡方式,讓主程式loop當中少了一些if else if,讓寫法變得比較簡潔!


























手持裝置之MCU及周邊裝置省電的設計

在有MCU的手持裝置當中,省電的設計是很重要的一個環節,如何讓整個電池的使用壽命更久變得很重要,由於電池通常都是數十mAH至數百mAH,如何讓消耗電流減低考量的是設計者對程式與硬體的掌握能力,本文將介紹一些基本的概念!

(1)MCU系統頻率別太高,也別太低 : 系統頻率太高,優點是程式執行的速度快,很快就可以完成所設計的工作,但MCU本身一定是耗電的;系統頻率低,缺點是執行速度慢,所設計的工作要執行很久才能完成,也會比較省電!可是為何頻率別太高也別太低?頻率快耗電,頻率慢省電不是很合理嗎?可是必須考量一件事,我們可以適時地將MCU近入Sleep mode並關閉耗電的周邊電源,系統頻率快代表很快完成工作進入睡眠,系統頻率慢代表很慢才能完成工作再能進入睡眠,因此這筆需考量在一秒內工作多久睡眠多久加上周邊關閉多久才能決定的,簡單的方式就是量測平均號電流就可以了!


(2)如何進入睡眠?如何喚醒?
如何進入睡眠模式是個很簡單完成的工作,以Microchip PIC16為例,只要下一個sleep();這樣的巨集指令就可以進入睡眠模式了!
如何喚醒就有很多種方式了,可以是外部中斷喚醒看門狗計時器WDT溢位喚醒、跟系統頻率非同步的Timer1中斷喚醒等等...

外部中斷喚醒 : 將MCU的外部中斷Pin設為輸入,並選擇pull high or pull down,選擇Rising or Falling edge的中斷觸發方式,並清除外部中斷旗標並開啟外部中斷所需相關設定如INTE(INT中斷開關)、GIE(總中斷開關),然後程式中需有中斷服務程式來處理中斷事件,即可進入睡眠等待中斷發生!
中斷服務程式 :  
void interrupt ISR(void)
{
    if(INTE && INTF)
   {
        INTF = 0;
   }
}

外部中斷Initialize :
void Initialize_INT(void)
{
    TRISB0 = 1;                 // Interrupt pin
    OPTION_REGbits.INTEDG = 1;  // Rising edge
    INTCONbits.INTF = 0;
    INTCONbits.INTE = 1;
}

看門計時器WDT溢位喚醒 : Enable WDT,並設定看門狗計時器溢位的時間間隔(Interval),假設為100ms,在睡眠之前先執行CLRWDT();已清除WDT計時器的時間,然後下NOP();及sleep();指令進入睡眠,等100ms時間溢位了就自動會喚醒了!

系統頻率非同步的Timer1中斷喚醒  :  由於PIC16的Timer1可以外接32.768kHz振盪器,並可設定與系統clock非同步,也就是說當進入睡眠時timer1可以繼續計數,等timer溢位之後,就可以發生中斷來喚醒MCU了!



Initialize Timer 1程式 : 
void Initialize_TMR1(void)
{
        TMR1H = (65536 - 82) >> 8;
        TMR1L = (65536 - 82) & 0xff;         // 10ms interval
    
        T1CON = 0b10101100;                
        // LP OSC enable(32.768kHz), Do not synchronize asynchronous    clock input
        TMR1ON = 1;                  
        TMR1IF = 0;
        TMR1IE = 1;
}

中斷服務程式 : 
void interrupt ISR(void)
{
    if(TMR1IE && TMR1IF) // 10ms interval
    {

        TMR1IF = 0; // Clear the flag

        TMR1H = (65536 - 82) >> 8; // Update timer1 high byte

        TMR1L = (65536 - 82) & 0xff;    // Update timer1 low byte
        if(++timertick == 10) //
        {
            timertick = 0;
            FLAGbits.Timer100ms = 1;
        }

    }
}

主程式範例
void main(void)
{
    Initialize_CPU();
    Initialize_TMR1();
    Initialize_INT();
    Initialize_LCD();
    Initialize_ADC();
    PEIE = 1;
    GIE = 1;

    SWDTEN = 1;
 
    while(1)
    { 
if(FLAGbits.Timer100ms)
{
            FLAGbits.Timer100ms = 0;
            ScanBtnEvent();
            MeasureVBAT();
            MainStateMachine();
}
CLRWDT();
NOP();
SLEEP();
NOP();
    } 
    return;
}

(3)睡眠前須先將供電的周邊耗電的裝置必須關閉,並將IO port設定到不耗電的狀態!


2017年8月9日 星期三

ADC平均方式

一般在MCU進行ADC時,通常會因為雜訊等問題而需要做濾波,最常見的軟體濾波方式就是平均,在此將介紹幾種平均方式:
(1)算術平均,原理可以參考WIKI : 算術平均數(Arithmetic mean)是表征數據集中趨勢的一個統計指標。它是一組數據之和,除以這組數據個數/項數。算術平均數在統計學上的優點,就是它較中位數、眾數更少受到隨機因素影響,缺點是它更容易受到極端值影響。計算公式為:
在MCU的使用當中,由於程式的設計攸關效能及所佔記憶體空間大小,因此通常會將平均方式調整為2^n,假設要做八次的平均,則為(X[0]+X[1]+X[2]+X[3]+...X[7]) / 8, 除8有必要提出來說明一下,因為很多MCU沒有除法這樣的硬體,因此通常都會將除以8(8 = 2^3)改為右移3bit來達到同樣的功能!
(2)簡單的移動平均(節省data memory的做法) : 簡單來說就是將前8筆資料加總為Sum, 然後Avg = Sum / 8; 就得到第一次8筆資料的算術平均結果為Result, 下一次做平均時則先進行Sum -= Sum / 8; 然後再將新的資料加進來Sum += New; 再將結果Sum /= 8; 得到新的平均Avg
Example code : 
for(Samples = 0; Samples < 32; Samples++) // Over sampling 32 times
{
        __delay_us(20);
        ADGO = 1;
        while(!ADIF);
        ADIF = 0;
        ADC_Result.Byte[0] = ADRESL;
        ADC_Result.Byte[1] = ADRESH;
        ADC_SUM += ADC_Result.Word;
}
ADC_SUM >>= 5; // ADC_SUM / 32
if(!FLAGbits.ADCMovAvgOk)
{
        ADCMovAvgSUM += ADC_SUM;
        ADCMovAvgIndex++;
        AdcMovAvg = (ADCMovAvgSUM / ADCMovAvgIndex);
        if(ADCMovAvgIndex == 8)
{
ADCMovAvgIndex = 0;
FLAGbits.ADCMovAvgOk = 1;
}
}
else
{
ADCMovAvgSUM -= (ADCMovAvgSUM >> 3);
ADCMovAvgSUM += ADC_SUM;
AdcMovAvg = (ADCMovAvgSUM >> 3);
}
(3)複雜移動平均(耗費data memory空間) : 假設做8筆的移動平均,則需先開一個陣列Buf可存放8筆數據的大小,並宣告一個陣列的索引Index且設定初值為0, 每次存放一筆資料於Buf[Index]則將Index加1,等Index等於8時,陣列為放滿的階段,Index歸零,並執行第一次加總平均得到結果Sum = (X[0]+X[1]+...+X[7] / 8); Result = Sum / 8; 下一次新的資料進來時,此時Index會指向Buf最舊的那筆數據的空間,並將新的數據存入Buf[Index]一筆數據的位置,然後Index加1,再重新做加總平均得到新的平均值,以此類推重複循環!



2017年8月6日 星期日

使用Oversampling提高ADC解析度

在使用ADC過程當中,解析度是一個用來表示ADC性能的指標,其計算方式可以透過如下方程式表示 : 1LSB = VREF / (2^n - 1), VREF為ADC的參考電壓, n為表示解析度的位元數,假設使用10-bits ADC, ADC參考電壓為5V, 可以計算1LSB = 5 / (2^10 - 1) =  0.0048875855327468, 大約為4.88mV,也就是說當ADC轉換得到512時,實際的輸入電壓約為512 * 4.88 = 2499mV, 若輸入電壓變化小於4.88mV, 可以說ADC是看不出來的,因此若要設計高精度的產品,解析度要高的話,10-bits ADC是不夠的,我曾使用過TI 18-bits delta-sigma ADC,解析度很高,只是轉換起來很慢,18-bits模式下一秒大概只能拿四筆資料,也就是說Report rate : 4Hz, 而且成本也高,對於低價的產品來說不適用,為了在有限成本內壓榨出性能,於是乎就有Oversampling的技術出現,透過軟體來實現提高ADC解析度!
要做訊號的取樣必須先了解取樣頻率及"Nyquist theorem", Nyquist theorem簡單來說若要還原訊號原本的樣子,必須遵循f nyquist > 2 f signal, 關於Nyquist theorem詳細的內容可以自行google查詢,Oversampling則透過公式f oversampling = 4^n * f nyquist來完成!
實作:
我實作的方式是將4^n的取樣結果加總之後,再將加總的結果右移n,然後再結果取出使用,假設要將ADC解析度由10-bits提高至12-bits, n = 12 - 10 = 2, 因此取樣數必須為原本的4^n = 4^2 = 16,也就是說每提升1-bit要取樣4^n, 接著將16筆資料加總,再將加總結果右移n, 就可以將得到的結果透過12-bits的計算公式來使用,假設VREF = 5V, 1LSB = 5 / (2^12 - 1) = 1.22mV, 解析度優於10-bits的4.88mV, 需要使用到透過軟體提升ADC 解析度的朋友,可以參考Microchip AN1152 Achieving Higher ADC Resolution Using Oversampling及ATMEL AVR121: Enhancing ADC resolution by oversampling這兩份文件,搭配程式實際試試看!

變壓器電感量計算

變壓器電感量計算 : AL * N^2 = L
L可用LCR Meter直接量出
AL(1/R,磁阻)若不知道,直接用線圈繞在變壓器一圈,量測出電感值,然後再多繞幾圈,再量出電感值,重複個幾次,就可利用公式推算得知AL為多少了~
取得AL及電感值之後就可直接算出圈數了!

角度平均計算~演算法

在開發角度偵測或相關領域當中,常會使用平均的方式來降低雜訊影響輸出結果的程度,假設有三筆角度個為10度/20度/30度,在數學上透過平均的方式須先將三筆角度加總之後再除以3,平均後得到20度,但單純使用平均會出現一個顯而易見的錯誤,假設有三個角度要做平均,其角度個為 : 355度/5度/15度,做完平均之後角度為125度,125度是對的嗎?當然不對,在圓形分布(Circular distrubution)的數據來說,不適用單純的平均方式,因此我們可以使用“Mean of circular quantities”這樣一個演算法來求得角度的平均,解說如下:


詳細資料可以參考WIKI針對"Mean of circular quantities" 的說明, 或自行google相關paper文獻, https://en.wikipedia.org/wiki/Mean_of_circular_quantities

在台灣Microchip很久之前亦有人提出來討論,但並非使用此演算法解決,需要的人可以參考以下討論http://www.microchip.com.tw/modules/newbb/viewtopic.phptopic_id=3581&viewmode=compact&order=ASC&start=40

2017年8月5日 星期六

消除按鍵彈跳現象~Debunce

按鍵反彈跳設計
按鍵開關在MCU的程式設計中初學者通常會遇到一個問題,就是按鍵彈跳現象,原因由於按鍵屬於機械結構,按下時會有機械彈跳的狀況出現,通常會讓判定按鍵的動作出現BUG,因此在程式當中或者硬體上須作處理才能消除彈跳的現象,下圖為量測按鍵按下時的彈跳現象,可以看出在100ms發生多次彈跳現象!




解決方法:
(1)加入電容做濾波
通常在按鍵的輸入設計來說,會透過上拉電阻將按鍵輸入到MCU的IO做Pull-up,上拉電阻可以是10k or 100k等,然後按鍵另一端接地,也就是說按鍵always pull-up,若按鍵被按下時,則tied GND,可以透過在輸入的IO pin接一顆小電容到GND,等同於按鍵旁並聯一顆電容,從電路上來看,電容電阻形成RC低通濾波器,RC時間為5RC,假設Pull-up 100k,電容為0.1uF, 5RC = 5 * 100k * 0.1uF = 50ms,也就是說當按鍵彈跳時,電容因為RC常數導致輸入pin上面的電壓不會瞬間變化,此時按鍵彈跳現象就被消除了!

(2)程式中加入Debounce機制
程式中可以在使用者按下按鍵之後,delay一段時間避開按鍵機械跳的發生,等彈跳現象消除之後再重新偵測按鍵動作,至於要delay多久可以透過示波器量測所選用的按鍵開關大概會彈跳的時間,不過不建議使用者以delay的方式來實作,會造成程式在delay時無法做其他事情,效率會比較差,因此可以透過timer interrupt,當按鍵按下之後,計時一段時間做Debounce之後再重新偵測,建議Debounce時間不要超過100ms,否則判定案件的動作會有不靈敏的感受,程式實作如下:








二維陣列指標的表示方法