2015/08/06

PIR on Raspberry Pi

1. Introduction

生活中有個應用是, 大廳、走廊、或是電梯裡
燈是暗的, 感應到有人經過時燈就亮了
我們可以用 PIR 做到同樣的應用

2. Connecting to a PIR

PIR 通常有 3 根pin, 分別是 VDD, GND, 以及 Signal out

我手上這顆 PIR 型號是 Panasonic PIR AMN44122:


然後如下圖這樣接, 其中  PIR 的 OUT 接到 RPi 的某根 GPIO 上來讀取狀態, 這邊選的是板子編號 12 的 pin 腳
另外 LED 接的 GPIO 是板子編號 16 的 pin 腳



3. coding

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)

pir = 12
led = 16

GPIO.setup(pir, GPIO.IN)
GPIO.setup(led, GPIO.OUT)

prev_pir_state = 0
count_down = 0;

GPIO.output(led, 0)

try:
 while True:
  current_pir_state = GPIO.input(pir)
  if current_pir_state == True:
   if prev_pir_state == False:
    GPIO.output(led, 1)
   count_down = 3
  else:
   if count_down > 0:
    count_down = count_down - 1
    if count_down == 0:
     GPIO.output(led, 0)

  prev_pir_state = current_pir_state

  time.sleep(1)
except KeyboardInterrupt:
 GPIO.cleanup()



程式邏輯是, 如果從 PIR 讀到的值由 0 變 1, 就點亮 LED, 並啟動 count down
當 PIR 變 0, 就等個 3 秒之後再把 LED 關掉
中間如果 PIR 又變回 1 了, 那麼 count down 就重新算
最後如果用Ctrl-C離開程式, 就把 GPIO 全關



2015/07/22

Battery

1. Introduction


電量 (Power Capacity) 指的是電池裡儲存了多少能量, 單位通常是瓦特小時 (Watt-hour, Wh)

  • Voltage * Amp * hour = Wh

舉例來說, 3V的額定電池 (nominal battery), 輸出1A的電, 使用了1小時, 所以它的電量是 3 V * 1 A * 1 h = 3 Wh

通常同一種電池類型裡, 電壓是固定的, 所以規格裡的單位只會標示安培小時 (Amps-hour, Ah)
注意 V x Ah = Wh, 所以兩個單位可以乘以或除以電壓來互換

前個例子裡, 3V額定電池有 1Ah 的電量, 所以它也可以 0.1 A 使用 10 h, 或是 10 A 使用 0.1 h

但是通常 1 Ah 的電池實際上無法使用到這麼多, 當輸出的電流越大, 能使用的時間就非線性地越短, 也就是說, 1 Ah 的電池如果輸出 10 A, 那麼它使用的時間會小於 0.1h

以勁量電池 (Energizer Battery) 的 AAA電池 (型號E92) 來說, 可以看到同個電壓裡, 當輸出電流越大 (線條往右走), 使用的時間並不是線性減少, 而是稍微往下偏移


另外, 電池電量跟電池的額定容量 (nominal capacity) 是不一樣的, 一般電池如果內裝是 3.6V 或 3.7V, 為了升壓到 5V 做一般性的使用會造成能量消耗, 原本的電量也會減少

電量密度 (power density) 是指單位重量可提供的電量, 密度越高, 代表輕便, 也通常代表價錢越貴

2. Lead Acid Battery

鉛蓄電池的特點是便宜, 可充電, 隨處可得, 通常用在需要大量電源, 並且重量不是問題的地方
它通常是以 2V 的核心組成, 所以可以看到 2V, 6V, 12V, 24V 的鉛蓄電池

3. Alkaline Battery


鹼性電池是常見的電池, 它的 power density 比鎳鎘電池高, 也比鎳氫電池略高, 電量低, 但在低電量的用途有較長的使用時間, 以一般性的使用來說很方便, 但不能充電

它以 1.5V 的核心組成, 大小則從 AAAA ~ D 不等

6V 的電池是用大一些的鹼性電池做成, 電量大, 所以使用起來也很方便

9V 的電池則是由 6 個 1.5V 電組成, 電量低, 體積又省不到多少

4. Ni-Cad Battery


鎳鎘電池是舊型的充電電池, 大小有 AA, AAA, C, 但現在較少看到, 因為鎳氫電池有較高的 power density

另外一方面, 它放電速度比鎳氫電池慢, 也比鎳氫電池便宜, 所以仍可以看到它用在一些價錢考量的裝置上

它以 1.2V 的核心組成, 常看到它以 3 個組成 3.6 V的電池

5. Ni-HM Battery


鎳氫電池是充電電池, 電量比鹼性電池高, 但是放電速度比鎳鎘電池高, 生命周期也較低

它以 1.25V 的核心組成

6. Li-Ion (Lithium-Ion) and LiPoly (Lithium Polymer)


鋰離子電池和鋰聚合物電池是充電電池, 它很輕, 低放電, 高power density, 但它需要一些迴路保護它爆炸

它通常以 3.6V 核心組成, 所以可以看到 3.6V 或 7.2V 的規格

7. Lithium Battery and Coin Cells


鋰電池通常做成鈕扣形狀, 用在需要電池體積小的情況, 不可充電, 安全性高, 生命周期長, 也不貴

常見的規格是 CR2032, (直徑 20mm x 厚 3.2mm), 提供 3V 200mAh






2015/06/16

cmake

1. Introduction


CMake 是跨平台自動化建構系統, 但用來管理Makefile也很方便
只要安裝 cmake 套件就可以使用

sudo apt-get install cmake


2. 簡易範例


假如有底下這些檔案
  • myproj/
    • main.c
    • prime.c
    • prime.h
    • CMakeLists.txt
其中 CMakeLists.txt 的內容為

add_executable(myproj main.c prime.c)

CMake 預設會去找 CMakeLists.txt, 並解譯裡面的內容
add_executable() 裡面, 第一個參數是 executable target, 名字可以自定, 這個 executable target 會由後面的 source file 所編譯成。
CMakeLists.txt 可以放很多個 add_executable(), 代表同個 Makefile 可以生出不同的 executable target

接著我們移到 myproj 的目錄底下, 並下這個 command

$ cmake .

後面的點代表當前目錄
執行後就會生出 Makefile, 要注意的是這個 Makefile 無法在沒有 CMake 的環境下單獨使用。如果將整包檔案換環境, 就應該重下cmake command

接著可以操作 Makefile 的一些 command, 像是 make, 或 make clean

3. 簡易專案


假如有底下這些檔案
  • myproj/
    • build/
    • src/
      • CMakeLists.txt
      • main.c
      • prime.c
      • prime.h

那麼移動到 build目錄底下:
$ cmake ../src

這樣就可以將 source code 跟 target file 分開

4. optimization


cmake 提供幾組 compile config, 預設值是None

  • None: default
  • Debug: 產生 debug information
  • Release: 速度最佳化
  • RelWithDebInfo: 速度最加化, 但包含 debug flag
  • MinSizeRel: 程式碼最小化

假如現在我們有這些檔案
  • myproj/
    • build/
    • release/
    • src/
      • CMakeLists.txt
      • main.c
      • prime.c
      • prime.h
然後移動到 release 目錄底下, 這樣 release 的 make file 就會包含速度最佳化

$ cmake -DCMAKE_BUILD_TYPE=Release ../src


5. my project


底下是我的 project euler 使用的 example, 裡面的檔案有:

  • pe/
    • bin/
    • debug/
      • CMakeLists.txt
    • src/
      • main.cpp
      • prime.cpp
      • CMakeLists.txt
    • release/
      • CMakeLists.txt
    • inc/
      • prime.h
其中 pe/debug/CMakeLists.txt 的內容和 pe/release/CMakeLists.txt 的內容一樣
cmake_minimum_required(VERSION 2.6)

project(pe)
subdirs(../src)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ../bin)


然後 pe/src/CMakeLists.txt 的內容
include_directories(../inc)
add_executable(pe main.cpp prime.cpp)

要建 debug 的 makefile 就轉到 pe/debug/ 目錄底下打這個 command
$ cmake -DCMAKE_BUILD_TYPE=Debug

要建 release 的 makefile 就轉到 pe/release/ 目錄底下打這個 command
$ cmake -DCMAKE_BUILD_TYPE=Release


2015/04/30

OpenMP in linux

1. Introduction


寫 project euler 的時候, 有些問題想不到速度快的解法, 只好用平行運算來加速
平常程式都以一條 thread 在執行, 要開多條 thread 來跑, 在管理上就很麻煩
OpenMP 讓人可以平行處理, 又可以減少管理的麻煩

2. 對 for 做 parallel


底下是範例程式

#include <stdio.h>
#include <omp.h>

int main() {

 #pragma omp parallel for
 for (int i=0; i<6; i++) {
  printf("%d ", i);
 }
 printf("\r\n");

 return 0;
}

然後compile它, 這邊使用c99來compile:

c99 -o openmp_example_01 openmp_example_01.c -fopenmp

幾個重點
  • 在 header 檔要加入 omp.h
  • 在 for 迴圈前加上 #pragma omp, 這個是用來使用omp的功能, 後面是控制敘述
  • compile 的時候加上 -fopenmp
結果可以看到
4 0 1 5 2 3

每次執行的結果都不一樣

3. 語法


它的語法大改長這樣

#pragma omp <directive> [clause[[,] clause] ...]

前個例子裡, 其實 parallel 和 for 都是 directive 的敘述, 所以它可以拆成兩個
#pragma omp parallel
{
 #pragma omp for
 for (int i=0; i<6; i++) {
  printf("%d ", i);
 }
}

可以看 wiki 上面的指令清單
OpenMP in wiki

4. thread information


如果要知道目前的 thread number, 可以用 omp_get_thread_num()
#include <stdio.h>
#include <omp.h>

int main() {

 #pragma omp parallel for
 for (int i=0; i<6; i++) {
  printf("T:%d i:%d\r\n", omp_get_thread_num(), i );
 }

 return 0;
}

執行結果
T:2 i:4
T:1 i:2
T:1 i:3
T:0 i:0
T:0 i:1
T:3 i:5

可以看到我的電腦上, 它跑了4條thread, 其中 thread 0處理 i 是 0 & 1的情況, thread 1 處理 i 是 2 & 3 的情況....

5. 控制 thread number


可以用 num_threads(n) 來控制要開多少 thread
#include <stdio.h>
#include <omp.h>

int main() {

 #pragma omp parallel for num_threads(2)
 for (int i=0; i<6; i++) {
  printf("T:%d i:%d\r\n", omp_get_thread_num(), i );
 }

 return 0;
}

執行結果可以看到只開2條thread了
T:1 i:3
T:1 i:4
T:1 i:5
T:0 i:0
T:0 i:1
T:0 i:2

6. 手動切割 section 做平行處理


#include <stdio.h>
#include <omp.h>

int main() {

 #pragma omp parallel sections
 {
  #pragma omp section
  {
   printf("T:%d section 0\r\n", omp_get_thread_num());
  }
  #pragma omp section
  {
   printf("T:%d section 1\r\n", omp_get_thread_num());
  }
  #pragma omp section
  {
   printf("T:%d section 2\r\n", omp_get_thread_num());
  }
 }

 return 0;
}

執行結果
T:2 section 0
T:3 section 1
T:0 section 2

要注意的是 section 之間不可以有相依性, 不然要額外做處理

7. for 裡面處理共同變數


要注意的是 parallel for 只會將 section 裡的變數做平行處理,
section 外的變數則會當成是所有thread 共用的變數,
要讓 openmp知道 section 裡的變數是每個thread自己有一份的話, 則要加上private

#include <stdio.h>
#include <omp.h>

int main() {

 int i, j;

 #pragma omp parallel for private ( j )
 for (i=0; i<2; i++) {
  for (j=0; j<2; j++) {
   printf("(%d,%d)\r\n", i, j);
  }
 }

 return 0;
}

如果不加上private, 那麼當外面的 thread 在做處理時,
就有可能不小心把別的 thread 的變數 j 加了 1

如果希望 section 裡面每個 thread 共用一份變數,
並且希望它不會有處理先後順序造成的問題, 可以使用 atomic

#include <stdio.h>
#include <omp.h>

int main() {

 int count = 0;
 #pragma omp parallel for
 for (int i=0; i<10; i++) {
  #pragma omp atomic
  count++;
 }
 printf("count: %d\r\n", count);

 return 0;
}

如果沒有加上 atomic 的話, count 最後的結果就有可能不是10







2015/01/05

FreeRTOS Low Power Support

1. Introduction


在 FreeRTOS 裡, Power Saving 相關的工作通常會放在 idle task 裡面作, 它的作法是限制periodic tick interrupt, 這樣可以減少 period task的耗電

實際作法是, 它停止 periodic tick interrupt, 然後在 restart periodic tick interrupt實調整 RTOS tick count

在 tick interrupt 停止的時候, microcontroller 就可以進 deep power saving state, 直到有interrupt發生

2. Idle Task


idle task 放在 task.c
static portTASK_FUNCTION( prvIdleTask, pvParameters )

裡面有 infinite for loop 不停地執行一些工作, 包括:

  1. 檢查有沒有 task 已經被刪除
  2. 檢查 preemption 相關的工作
  3. 呼叫 idle hook funtion
  4. Power Saving Task

其中有兩個 hook function 會因為 power saving 的機制受到影響

  • vApplicationIdleHook : 在 idle task 裡每次呼叫
  • vApplicationTickHook : 在 idle task 裡, 並且只有在 sytem tick 增加時才呼叫
這兩個 hook function 都是直接 hard code 實作, 預設是空的, 為了避免影響其它task, 這兩個hook function 裡面不可以擺可能會 blocking 的 task

3. Power Saving


底下是 idle task 裡的 power saving code:
/* This conditional compilation should use inequality to 0, not equality
to 1.  This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when
user defined low power mode implementations require
configUSE_TICKLESS_IDLE to be set to a value other than 1. */
#if ( configUSE_TICKLESS_IDLE != 0 )
{
TickType_t xExpectedIdleTime;

 /* It is not desirable to suspend then resume the scheduler on
 each iteration of the idle task.  Therefore, a preliminary
 test of the expected idle time is performed without the
 scheduler suspended.  The result here is not necessarily
 valid. */
 xExpectedIdleTime = prvGetExpectedIdleTime();

 if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
 {
  vTaskSuspendAll();
  {
   /* Now the scheduler is suspended, the expected idle
   time can be sampled again, and this time its value can
   be used. */
   configASSERT( xNextTaskUnblockTime >= xTickCount );
   xExpectedIdleTime = prvGetExpectedIdleTime();

   if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
   {
    traceLOW_POWER_IDLE_BEGIN();
    portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
    traceLOW_POWER_IDLE_END();
   }
   else
   {
    mtCOVERAGE_TEST_MARKER();
   }
  }
  ( void ) xTaskResumeAll();
 }
 else
 {
  mtCOVERAGE_TEST_MARKER();
 }
}
#endif /* configUSE_TICKLESS_IDLE */

它第一步先計算預計會idle的時間 xExpectedIdleTime, 如果這個時間大於 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 才真的 sleep, 不然的話, idle時間太短又跑去sleep會造成額外的 effort

接著 vTaskSuspendAll(), 這裡將 uxSchedulerSuspended 加 1, 於是task scheduler就被停止

然後重新計算 idle time, 接著進入sleep
traceLOW_POWER_IDLE_BEGIN();
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
traceLOW_POWER_IDLE_END();

最後 xTaskResumeAll(), 將 uxSchedulerSuspended 減 1, 將所有應該要切入 ready state 的 task 喚醒, 最後將 system tick 加回來

4. portSUPPRESS_TICKS_AND_SLEEP


portSUPPRESS_TICKS_AND_SLEEP() 這個macro根據不同的MCU而有不同的實作, 在GCC, IAR, Keil都有default的實作

通常 SysTick 跟 Cortex-M Microcontrollers Clock 是一樣的 frequency, 這種情況下, 這兩個值是一樣的, configSYSTICK_CLOCK_HZ 以及 configCPU_CLOCK_HZ

如果 SysTick 比 Core Clock還慢的話, 就要定義 configSYSTICK_CLOCK_HZ 這個值

其中sleep的機制會呼叫到Cortex-M的 function call
/* Sleep until something happens.  configPRE_SLEEP_PROCESSING() can
set its parameter to 0 to indicate that its implementation contains
its own wait for interrupt or wait for event instruction, and so wfi
should not be executed again.  However, the original expected idle
time variable must remain unmodified, so a copy is taken. */
xModifiableIdleTime = xExpectedIdleTime;
configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
if( xModifiableIdleTime > 0 )
{
 __DSB();
 __WFI();
 __ISB();
}
configPOST_SLEEP_PROCESSING( xExpectedIdleTime );

其中會有個 pre & post processing, 中間sleep的部份取決於不同MCU而有不同的code

DSP 表示 Data Synchronization Barrier, 它將 processor目前做到一半的工作做完
WFI 表示 Wait For Interrupt, 它讓 processor 進 suspend, 直到底下其中一個發生:

  • non-masked interrupt 發生
  • 被 PRIMASK mask 的 interrupt 被 pending
  • Debug Entery 的 request

ISB 表示 Instruction Synchronization Barrier, 它將 processor 的pipe line清空, 這樣才不會拿到之前未執行的 code 來執行




2014/12/12

Symbols of object file

1. Symbol Table


在 ELF file 裡, 其中有些 section 它的 type 是 DYNSYM 或 SYMTAB, 裡面含有symbol table,
我們可以用readelf來dump這些symbols, 這邊以簡單的 hello.c 當範例, compile & link 之後
$ readelf --syms hello
Symbol table '.dynsym' contains 4 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

Symbol table '.symtab' contains 65 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000400238     0 SECTION LOCAL  DEFAULT    1 
     2: 0000000000400254     0 SECTION LOCAL  DEFAULT    2 
     3: 0000000000400274     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000400298     0 SECTION LOCAL  DEFAULT    4
....

會看到有兩個section有symbol table, 分別是 dynsym 以及 symtab

同樣的事情也可以用nm來做
$ nm hello
0000000000601040 B __bss_start
0000000000601040 b completed.6972
0000000000601030 D __data_start
0000000000601030 W data_start
......

會發現, 用 readelf 讀出來的 symbole item 比較多, 但是 readelf 裡面有不少是空的item, FILE type 的 item 也不會在 nm 裡出現

如果我們將檔案 strip 過, 會發現 symtab 被清掉了
$ strip hello

$ nm hello
nm: hello: no symbols

$ readelf --syms hello

Symbol table '.dynsym' contains 4 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__


2. Symbol type


用 nm dump 出來的 symbol 的 type , 如果是大寫表示global, 小寫表示local

text section

  • T : symbol 位於 text section
data section

  • D : symbol 位於已初始化的 data section
  • G : symbol 位於小物件使用的已初始化 data section. (NOTE: 有些環境用近距離symbol效率較好)
read-only data

  • R : symbol 位於read-only data section
BSS (non-initialize data)

  • B : symbol 位於未初始化 data section (BSS)
  • S : symbol 位於小物件使用的未初始化 data section
Weak  object

  • V : symbol 是 weak object
Weak symbol

  • W : symbol 是未解析 weak object 的 weak symbol
Shared

  • C : common symbol, 未初始化資料
Debug purpose

  • N : debug purpose symbol
  • - : symbol 是 a.out object file 的 stabs symbol (for debug information)
Absolute value

  • A : symbol 是absolute value, linking的時候不會改變
Undefined

  • U : undefined symbol, instance應該在其它object file或是shared library
Indirect reference

  • I : Indirect reference symbol, 是a.out 的 GNU extension feature
Unknown

  • ? : 






2014/12/08

NFC NXP 203 Tag content example

1. Plain text example


底下是用一般的 nfc tag writter 寫進ndef plain text "123456789"的tag content

page 0     0469b055 c22d3580 5a480000 e1101200
page 4     0103a010 440310d1 010c5402 656e3132
page 8     33343536 373839fe 00000000 00000000
page 12    00000000 00000000 00000000 00000000
page 16    00000000 00000000 00000000 00000000
page 20    00000000 00000000 00000000 00000000
page 24    00000000 00000000 00000000 00000000
page 28    00000000 00000000 00000000 00000000
page 32    00000000 00000000 00000000 00000000
page 36    00000000 00000000 00000000 00000000
page 40    00000000 00000000

可以看到它總共有42個page, 根據 NFX 203F 的 format, user data 存在 page 4~39
而page 0~3, 40~41 用於紀錄 ID, checksum, read only, counter

1.1 TLV Blocks


Tag 內容格式為 TLV (Tag, Length, Value)
Tag 的值為底下幾種

  • NULL TLV (0x00) : 用於對齊 memory
  • Lock Control TLV (0x01) : 對 lock bit 做進一步描述
  • Memory Control TLV (0x02) : 定義剩下的memory
  • NDEF Message TLV (0x03)
  • Proprietary TLV (0xFD)
  • Terminator TLV (0xFE) : 最後一個TLV
上面的例子裡, 可以看到3個TLV
TLV 1: (Lock Control)
01 03 a01044

TLV 2: (NDEF Message)
03 10 d1010c5402656e313233343536373839

TLV 3: (Terminator)
fe


1.2 NDEF Message


在 NDEF TLV 的 VALUE 為 NDEF message
d1010c5402656e313233343536373839

第一個byte 0xd1為 NDEF Flag + TNF

  • MB = 1
  • ME = 1
  • CF = 0
  • SR = 1
  • IL = 0
  • TNF = 1

接下來的 1 個byte是 Type Length
Type Length = 0x01

因為 SR (Short Record) 為 1, 所以接下來的Payload length 長度為1個byte
Payload Length = 0x0c

因為 IL (ID Length is present) 為0, 所以沒有 ID 相關的欄位

接著是1個 byte 的 Payload Type
Payload Type = 0x54 (WELL-KNOWN: urn:nfc:wkt:T (Text))

最後是NDEF Text content:
02 65 6e 31 32 33 34 35 36 37 38 39
   E  N  1  2  3  4  5  6  7  8  9

第一個byte 0x02為Status

  • bit 7 : 0表示UTF-8 encoded, 1表示UTF16 encoded
  • bit 6 : RFU (must be set to zero)
  • bit 5..0 : IANA language code的長度
可以看到它用UTF-8, IANA language code的長度為2
所以接著2個byte為 0x65, 0x6e 為 "EN" 是它的 IANA language code
接著是我們的message "123456789"