2022年7月30日 星期六

用 dart 語法將 function/task 的迴圈改成遞迴函式運作

當一個  function/task 用迴圈(while/for/do-while)處理狀態機(state machine)時, 如下所述:
import 'dart:async';
int step = 0;
void functionTask( )  async {
    for (int i = 0; i < loopMax; i++) {
        switch(step) {
            case 0: // ...
                    step ++;
                    break;
            case 1: //...
                    step ++;
                    break;
            // ...
            // case n: step ++;
            //      break ;
            default: step = 0;
                    break;
        }
       await  Future.delayed(Duration(microseconds: 0)); // schedule, to run ASAP
       if (step == 0) break;
    }
    return;
}

這樣的寫法會造成程序(thread)堵(blocking)在迴圈內, 如果用在 Flutter 平台(Platform)上, 將沒機會處理內部訊息(message queue), 導致 Widget 無法同步更新, 或者看不到畫面, 此外 StatefulWidget  管理狀態(state)時,  在 initState( ) 或是 build( ) 的 function/task 內是不能使用 async/await 的,  但可以註冊類同步(then)方式來運作,  搭配遞迴函式編寫方式移除迴圈, 同上述  function/task 的狀態機處理函式就能活動不停, 同步更新畫面, 直到結束:
import 'dart:async';
int step = 0;
Future<int>   async_Task( )  async { // 非同步涵式    
        switch(step) {// 現在函式
            case 0: // ...
                    step ++;
                    break;
            case 1: //...
                    step ++;
                    break;
            // ...
            // case n: step ++;
            //      break ;
            default: step = 0;
                    break
        }  
        await Future.delayed(Duration(microseconds: 1000000)); // return the 'Future'.
        // 未來函式 ...
        return step ;// 未來函數, 接觸點
}

        // ... in  stateManagement  class  ...
        void recursiveLoop( ) {// 類同步註冊: 接觸過往, 持續未來
           
async_Task( ) .then((result) {
                if (result > 0) setState ( ( ) =>
recursiveLoop( ) );// 接序函式: 建新 widget, 更新畫面
            });
        }

       
        @override  void  initState( ) {
            super .initState( );
            recursiveLoop( );
        }

2022年7月27日 星期三

dart Iterator 的運作方法

 為了讓類型可以運用在 for (  in )  { } 迴圈上, 需要繼承 IterableBase 並實現 Iterator 介面類型, 總共有 3 個方法需要實現, 詳見以下範例程式 main.dart:

import 'dart:collection';
import 'dart:io';
class  Element {
  final id;
  static int _sn = 0;
  Element(): id = ++ _sn;
}

class ABC extends IterableBase<Element> implements Iterator<Element> {
  final List<Element> array;
  int lastone = 0;// length snapshot
  int index   = 0;// start snapshot
  @override Element get current => array[index];//get 方法 current 實現
  @override bool moveNext( ) => index != lastone && (++ index >= 0);//方法 moveNext( )實現
  @override Iterator<Element> get iterator {//get 方法 iterator 實現
    lastone = array.length - 1;
    index  = -1;
    return this;
  }
  ABC([List<Element>? mjList]): array = mjList ?? [ ] {
    for(int i = 0; i< 10; i++) array.add(Element( ));
  }
}

void main( ) {
  final a = ABC();
  for (final e in a) {
    stdout.write("[${a.index}] = ${e.id}, ");
  };
  print(":: index = ${a.index} lastone= ${a.lastone}\n");

  for (final e in a) {
    stdout.write("[${a.index}] = ${e.id}, ");
  };
  print(":: index = ${a.index} lastone= ${a.lastone}\n");
}

執行 dart main.dart 看結果:

[0] = 1, [1] = 2, [2] = 3, [3] = 4, [4] = 5, [5] = 6, [6] = 7, [7] = 8, [8] = 9, [9] = 10, :: index = 9 lastone= 9

[0] = 1, [1] = 2, [2] = 3, [3] = 4, [4] = 5, [5] = 6, [6] = 7, [7] = 8, [8] = 9, [9] = 10, :: index = 9 lastone= 9

2022年7月20日 星期三

複習 Dart 語法

dart 語法跟 c++ 非常類似, 習慣 c++ 語言應該很容易進入狀況, 參考官方文件: https://dart.dev/guides/language/language-tour


1. 常用類型: bool, int, double, String, List, Set, Map, BigInt
    BigInt: 大整數, 無限整數類型
    List 陣列, 用 [value, ... ] 可用中括號表列陣列值, 有序, 從 0 開始
    Set  集合, 用 {value, ... } 可用大括號列舉集合值, 無序
    Map  字典, 用 {key:value, ...} 可用大括號列舉成對 key:value, HashMap 無序, LinkedHashMap 依插入順序為序, SplayTreeMap 則排序過, 若無特別指定 Map 則用 LinkedHashMap 來實現. Map 以 [key] 當索引鍵, 存取內容時, 操作起來就像是陣列.
    整數 (char, int, int64, long, long long) 統一用 int, 浮點數(float, double) 統一用 double
    String: 可用成對單引號 '這是字串' 或 成對雙引號 "This 'is' string", 單雙引號混用時要注意成對的先後次序, 不得有穿插的情形
    Note:
        Sting: 使用 String.fromCharCodes([ ]) 將整數陣列轉為字串, 使用 .codeUnitAt(0) 將字元轉為整數, dart 沒有 char 類型, 字元可使用 int 或 String 來處理, 例如:
        int c = 'c'.codeUnitAt(0);
        String str = String.fromCharCodes([97]);
        字串串接, 在引號內使用 ${ } 當作串接位置(place holder), 例如: '... ${變數 或 運算式} ...'
    宣告關鍵字:
        const   固定值, 類型, 物件內容, 成員都不能改變
        final   最終類型的物件, 類型物件不能再變, 但物件裡的成員可改變
        var  固定的類型, 類型無法再變, 可重新指定為同類型的物件
        dynamic 變動的類型(類型, 物件, 內容全都能重新指定)
        
2. 類型無須使用 new, 直接呼叫新類型建構式就能實例化物件, 可使用 is 來檢驗物件類型, dart 不用指標, 只有物件, 使用 . 取用物件的成員

3. 基本運算
    四則運算: +, -, *, /, %, ~/, ++, --
    邏輯運算: !, &&, ||
    比較運算: >, <. >=, <=, ==, !=
    位元運算: ~, &, |, ^, >>, <<
    條件運算: 條件式 ? 正值 : 負值;
    
4. 標準輸出/輸入函式:
    ptint(""); // 自動輸出換行
    stdout.write(""); // 輸出無換行,  須 import "dart:io"
    stdin.readLineSync();// 輸入,  須 import "dart:io"
    
5. 引入程式庫 import, 引出程式庫 export, 專案底下目錄 lib 是專案程式庫的起始點(project library root)

6.透過 pubspec.yaml 指引, 程式庫相關性用命令 pub get 來管理

7. static 只能用在 class { }內, 不能用於 function( ){ } 內, c 的 static 實際上也是放在 global 區, 用符號連結過去而已, 若有必要, 可將變數宣告在 global 區在 function(){ } 內共享.

8. 函式內的預設參數(default parameter), 可以用中括號 [val, ...] 宣告預設位置參數, 或是大括號{key:value, ...} 宣告預設帶名參數, 兩者僅能擇其一, 不能同時使用. 預設參數宣告時, 必須放在無預設值後面, 但呼叫時, 除了位置參數需遵循宣告位置的順序外, 帶名參數可以放在任何位置, SDK 2.17 版之後, 不需擺在位置參數之後. 帶名參數前若使用 required 修飾, 呼叫時就必須給定該帶名參數的數值

9. 透過 FFI 可以用 C 語言直接與作業系統相互溝通

10.  理解 async/await 與 Isolate.spawn( ) 的運作邏輯:

await 一個 Future<T>(未來物件)必須放在 async 區塊內, 只要將函式區塊宣告成 async 變成非同步函式, 就可以用來 await 未來物件, 同時也可以在其它 async (非同步)區塊內被 await. 呼叫 async 函式, 意味著當執行到 await 時, 後續程序會安排到排程運行. 後續可以註冊(then)一個回調函式(callback function), 一旦在排程內執行完畢, 除了呼叫此回調函式外, 同時將結果一起傳回來. 因此呼叫 async 函式並不會阻塞程式的運行, 而是將非同步函式硬是拆成兩段(以 await 為分界點, 若無 await 就一直執行程式到最後), 前段執行完, 返回一個未來物件, 繼續往下運行, 後段將在排程中運行, 簡單來說就是將非同步程序拆成現在與未來(先後)執行順序. 

子代孵化 Isolate.spawn(void Function(SendPort), SendPort) 則是強迫程序分裂成兩條路徑並行(另類的 multithread, 但相互獨立的執行空間), 新孵化的子代 isolate 運作後便一去不返, 父子代 isolate 只能透過 sendPort 與 receivePort 單向交流(sendPort 負責發送訊息, receivePort 用於聆聽訊息), 若父子代要雙向傳輸, 必須在子代程序內另外產生一個 receivePort, 把成員 sendPort 經由 Isolate.spawn 所附帶的 sendPort 傳給父代, 父代透過所收到的 sendPort 就能向子代發出訊息, 雙向溝通管道才建立完成. 父子代都是名副其實的隔離物件, 無法透過全域或區域變數共享任何資源, 測試程式:

import "dart:async";
import "dart:isolate";
void main( ) {
  print("Main thread isoloate: ${Isolate.current.debugName.toString()}");
 
  final bgIsolate = (SendPort port) {
        print("${DateTime.now()}:new  ${Isolate.current.debugName.toString()}");       
        Isolate.exit(port, "Finish");
  };
 
  final port = ReceivePort( );
  final child = Isolate.spawn(bgIsolate, port.sendPort, debugName: "childIsolate");

  child.whenComplete(() {//注意:並不是指 bgIsolate 程序執行完成,而是當 isoloate 孵化完成!
    print("${DateTime.now()}: spawn complete.");
  });

  port.first.then((value){
        print("${DateTime.now()}: childIsolate return $value");
        port.close( );
  });
 
  Timer(Duration(milliseconds: 3000), ( ) {
    print("${DateTime.now()}: time is up.");
  });
}
開啟終端機執行  dart   main.dart  看結果:
Main thread isoloate: main
2022-07-23 16:32:51.726479:new  childIsolate
2022-07-23 16:32:51.747431: spawn complete.
2022-07-23 16:32:51.753271: childIsolate return Finish
2022-07-23 16:32:54.740046: time is up

11.  dart 不需 compile 直接用 dart VM 執行:  dart  main.dart ,  或是用  dart compile exe main.dart 翻譯成原生系統上可執行的獨立執行檔 main.exe

12. 透過 dart compile 事先編譯, 將 .dart 原始碼轉換成不同平台上可執行的的代碼(exe/aot-snapshot/jit-snapshot/kernel/js)
    a. dart compile js main.dart -o main.js 用來將 dart 語法轉換成 javascript 語法, 用 nodejs main.js 就能執行
    b. dart VM 內含 dart runtime 可執行 kernel snapshot 及 jit-snapshot 代碼.
    c. kernel snapshot 是與硬體系統(x86, arm, powerPC, mips)無關的 dart VM 代碼(dart byte code), dart VM 仍要對他二次解譯(compile), 轉成機械碼(machine code), 才能執行.
    d. 所轉成的 jit-snappsot 代碼可以讓 dart VM 加速翻譯成 machin code 並執行, 因此作業系統上仍必需有相對應的 dart VM, 用來分配記憶體/解譯成 machine code/執行.
    e. aot-snapsot 是作業系統上(windows, ios, linux)可執行的機械碼(machine code), 無需再解譯(compile), 透過 dartaotruntime 執行該 aot-snapshot, 不用透過 dart VM, 就能呼叫作業系統的 runtime 來執行
    f. exe 執行檔是把 aot-snapshot 及 dart runtime 全包在一起, 在作業系統上用 dart compile exe main.dart -o main 轉成獨立執行檔 main, 在 shell 底下 ./main 就能執行
    g. 理論上執行速度: kernel  < jit-snapshot < aot-snapshot < exe

2022年7月17日 星期日

Linux 系統使用 termios2 客製化 baud rate

簡單的 class,用來與 uBit V1.5 的 Serial port (/dev/ttyACM0)做溝通, 原始程式碼:

 #ifndef MySerial_H
#define MySerial_H
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <asm/termbits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#define defaultBaudrate 115200

class MySerial {
  private:
    int fd = 0;
  public:
    ~MySerial() { if (fd) close(fd);  }
    MySerial(const char *dev = "/dev/ttyACM0", int baud = defaultBaudrate) {
      int ttyFD = open(dev, O_RDWR | O_NONBLOCK);
      if (ttyFD > 0) {
        fd = ttyFD;
        struct termios2 uart2;
        uart2.c_iflag     = 0;
        uart2.c_oflag     = 0;
        uart2.c_lflag     = 0;
        uart2.c_cc[VMIN]  = 1;
        uart2.c_cc[VTIME] = 0;
        uart2.c_cflag     = CS8 | CREAD | CLOCAL;
        uart2.c_cflag    &= ~CBAUD;
        uart2.c_cflag    |= CBAUDEX;
        uart2.c_ispeed = baud;
        uart2.c_ospeed = baud;
        ioctl(fd, TCSETS2, &uart2);       
      }
    }
    bool ready() { return fd > 0;  }

    int available() {
      if (fd > 0) {
          int bytes;
          ioctl(fd, FIONREAD, &bytes);
          return bytes;
      }
      return 0;
    }

    ssize_t write(char *buffer) {
      if (fd && buffer) return ::write(fd, buffer, strlen(buffer));// use global write      
      return 0;
    }

    ssize_t read(char *buffer, int n) {
      if (fd && buffer && n) return ::read(fd, buffer, n); // use global read
      return 0;
    }

    int printf(const char *fmt, ...) {
      static char strBuffer[1024];// Note: upto 1024 characters
      va_list parameters;
      va_start(parameters, fmt);
      vsprintf(strBuffer, fmt, parameters);
      va_end(parameters);
      return write(strBuffer);// this->write
    }
};
#endif

2022年6月29日 星期三

關於 microbit V1.5

之前姪子小學上完的 microbit V1.5 開發板不用了, 上網找看能不能用來玩 Arduino, 參考 https://learn.adafruit.com/use-micro-bit-with-arduino/overview
1. 上 Arduino 官網下載程式並解壓到適當的目錄, 我惜慣用 vscode 來寫程式, 要在 settings.json 內容填入 arduino.path 的設定:
    "arduino.path": "/home/mint/Project/arduino-1.8.9"


2. vscode 支援 Arduino IDE, 因此先安裝好 arduino plugin, 在 settings.json 內容填入 arduino.additionalUrls 的設定, 若有其他開發板, 可以一起將它列在陣列內,之後上網時才可搜尋到並安裝相關的開發板系統開發工具(System Development Kit).
    "arduino.additionalUrls": ["https://sandeepmistry.github.io/arduino-nRF5/package_nRF5_boards_index.json"]


3. 由於筆電速度比較慢,另外將以下內容加入 settings.json
    "editor.minimap.enabled": false,
    "update.showReleaseNotes": false,
    "extensions.autoUpdate": false,
    "extensions.autoCheckUpdates": false,
    "update.mode": "none",
    "telemetry.enableTelemetry": false,
    "telemetry.enableCrashReporter": false,
    "editor.quickSuggestions": false,
    "extensions.ignoreRecommendations": true,
    "editor.hover.enabled": false,
    "editor.hover.sticky": false
   我的 settings.json 完整內容是:
   {
    "editor.minimap.enabled": false,
    "update.showReleaseNotes": false,
    "extensions.autoUpdate": false,
    "extensions.autoCheckUpdates": false,
    "update.mode": "none",
    "telemetry.enableTelemetry": false,
    "telemetry.enableCrashReporter": false,
    "editor.quickSuggestions": false,
    "arduino.path": "/home/mint/Project/arduino-1.8.9",
    "extensions.ignoreRecommendations": true,
    "editor.hover.enabled": false,
    "editor.hover.sticky": false,
    "workbench.startupEditor": "newUntitledFile",
    "arduino.additionalUrls": [
        "https://dl.espressif.com/dl/package_esp32_index.json",
        "https://arduino.esp8266.com/stable/package_esp8266com_index.json",
        "https://sandeepmistry.github.io/arduino-nRF5/package_nRF5_boards_index.json"
    ]
   }


4. vscode 開啟專案檔案夾(open folder)後, 點選 view -> command Palette -> Arduino:Initialize 先初始一個新檔案,例如填入 app.ino


5. 在 vscode 上面點選 view -> command Palette -> Arduino:Board Manager 搜尋 nRF, 就可以找到 Nordic Semiconductor nRF5 Boards 開發程式, 點選並安裝它, 需要點時間上網抓檔案. 安裝完接著才可在 vscode 上面點選 view -> command Palette -> Arduino:Board Config 搜尋 BBC, 就能找到 BBC micro:bit, 並點取 S110,   完成後將會產生一個 arduino.json 檔案,內容像是:
   {
    "sketch":"app.ino",
    "board": "sandeepmistry:nRF5:BBCmicrobit",
    "configuration": "softdevice=s110",
   }
 另外可以將 serial port 溝通管道 /dev/ttyACM0 添加進去, 並將編譯程式的輸出檔他丟到 build 目錄內(要先開啟終端機 mkdir build),   我的 arduino.json 完整內容是:
   {
    "sketch": "adc.ino",
    "board": "sandeepmistry:nRF5:BBCmicrobit",
    "configuration": "softdevice=s110",
    "port": "/dev/ttyACM0",
    "output": "build"
   }

6. microbit arduino 需透過 openOCD 燒入程式, 因為只支援 i386 系統, 因此要先在 linux 上安裝 i386 的驅動程式庫:
       sudo apt-get install libudev1:i386
   接著再編寫一個檔案:
      sudo gedit /etc/udev/rules.d/99-microbit.rules
   把以下內容加入並存檔:
      ATTRS{idVendor}=="0d28", ATTRS{idProduct}=="0204", MODE="664", GROUP="plugdev"

後記: 

1. microbit V15 使用的處理器是 nRF51822, 算蠻強的一棵 32 位元微處理機, 內建 256k flash 及 16k 的 RAM. Arduino 編譯 nRF51 時, 預留 2k stack 及 2k heap RAM 空間, 若勾選了 Softdevice 就會保留 8k RAM 給它用,  應用程式可用的 RAM 其實剩不到 4k. Softdevice 是一個 BLE (Bluetooth Low Energy) protocol stack (協定層的軔體), 作用是方便跟低功耗藍牙(BLE)裝置相互溝通, 且無需重複燒錄, 地位類似 PC 上 BIOS 的角色. Arduino 應用程式只需引用 BLE api (應用程式介面)的標頭檔, 所有跟 BLE 相關的的功能都是透過 api 函式, 去呼叫 BLE 軔體來完成.

2. 若不需要 BLE的功能, 就可移除 Softdevice 擠出 8k RAM 來使用: 只要在設定 Arduino:Board Config 時, 選項 Softdevice 改選擇 "None". Arduino 其實並不含 Softdevice ROM 內容, 只是將空間預留給它用(不用燒錄 Softdevice 軔體).若選項是 "None" Softdevice, 編譯產生的 Hex (ROM)檔, 就不會針對 Softdevice 預留空間, 因此燒錄後, 原先留在 microibit 內的 Softdevice 也會跟著抹除. 若要恢復 BLE 功能,就得重新燒錄 Softdevice 一次,可以上網站:

      https://learn.adafruit.com/use-micro-bit-with-arduino/install-board-and-blink

點選"Download Microbit BTLE Advertising Demo"下載 microbit-adv.hex(裏面包了 Softdevice 軔體), 將它存檔備用, 再用滑鼠把它拉進(複製)到 MICROBIT 隨身碟內就可以了.

3.  實驗發現 Microbit 開發板的 ADC 設定在 8 bits 取樣時, 取樣率可到  30~40 kHz之間, 但似乎雜訊比較多, 當設定在 10 bits 取樣時, 最高不會超過 14k Hz 的取樣率, 訊號也比較穩定, 這也許是它的極限, 但一般而言這樣的取樣率還是很夠用的, 測試程式:

uint8_t adc_data[2048];
const    uint32_t sizeADC = sizeof(adc_data) / sizeof(adc_data[0]);
volatile uint32_t qhead   = 0;
volatile uint32_t qtail   = 0;
extern "C" {
  void ADC_IRQHandler(void) {
    if (NRF_ADC->INTENSET && NRF_ADC->EVENTS_END) {
      NRF_ADC->EVENTS_END = 0;
      uint32_t advance = qtail + 1;
      if (advance == sizeADC) advance = 0;
      if (advance != qhead) {
        adc_data[qtail] = NRF_ADC->RESULT;
        qtail = advance;
      }
      NRF_ADC->TASKS_START = 1;
    }
  }
}

void setup() {
  Serial.begin(115200);
  Serial.print("ADC test\n");
  NRF_ADC->CONFIG    = ADC_CONFIG_PSEL_AnalogInput2 << 8 | ADC_CONFIG_RES_8bit;
  NRF_ADC->INTENSET = 1;
  NRF_ADC->ENABLE = 1;
  NVIC_EnableIRQ(ADC_IRQn);
  NRF_ADC->TASKS_START = 1;
}

void loop() {
  static uint32_t timeNow = 0;
  uint32_t   us = micros();
  uint32_t tail = qtail;
  uint32_t head = qhead;
  uint32_t dt   = us >= timeNow ? us - timeNow : 0xffffffff - timeNow +  us + 1;
  uint32_t dn   = tail >= head  ? tail - head  : sizeADC - head + tail + 1;
  timeNow = us;

  while (head != tail) {
    static int samples = 0;
    // Serial.write(adc_data[head]);// send to uart
    if (++head == sizeADC) head = 0;
    samples ++;
  }
  qhead = tail;

  Serial.print(",dn=");
  Serial.print(dn);
  Serial.print(",dt=");
  Serial.print(dt);
  Serial.print("SPS=");
  Serial.print(dn*1000000l / dt);// SPS = dn /( dt*1e-6)  = 1e6*dn/dt
  Serial.print("\n");
  delay(20); // wait 0.02 Second to sample ADC by ADC_IRQHandler
}

實驗結果:

SPS=35419,dn=815,dt=23010

4.透過 RTC, 將訊號繞道經 PPI 定時取樣, 不管 10/8 bits,最高取樣率只能到 10923Hz, 測試程式:

uint32_t prescalar = 2; // minimum 2,  SPS = 32768/(prescalar + 1)
uint8_t adc_data[512];
const    uint32_t sizeADC = sizeof(adc_data) / sizeof(adc_data[0]);
volatile uint32_t qhead   = 0;
volatile uint32_t qtail   = 0;
extern "C" {
  void ADC_IRQHandler(void) {
    if (NRF_ADC->INTENSET && NRF_ADC->EVENTS_END) {
      NRF_ADC->EVENTS_END = 0;
      uint32_t advance = qtail + 1;
      if (advance == sizeADC) advance = 0;
      if (advance != qhead) {
        adc_data[qtail] = NRF_ADC->RESULT;
        qtail = advance;
      }
    }
  }
}
void setup() {
  Serial.begin(115200);
  Serial.print("ADC test\n");
  NRF_ADC->CONFIG    = ADC_CONFIG_PSEL_AnalogInput2 << 8 | ADC_CONFIG_RES_8bit;
  NRF_ADC->INTENSET = 1;
  NRF_ADC->ENABLE = 1;
  NVIC_EnableIRQ(ADC_IRQn);

  NRF_RTC0->PRESCALER   = prescalar; // SPS = 32768/(prescalar + 1)
  NRF_RTC0->EVTENSET    = 1;
  NRF_RTC0->TASKS_START = 1;
  NRF_PPI->CH[0].EEP = (uint32_t)&NRF_RTC0->EVENTS_TICK;
  NRF_PPI->CH[0].TEP = (uint32_t)&NRF_ADC->TASKS_START;
  NRF_PPI->CHEN = 1;
}
void loop() {
  static uint32_t timeNow = 0;
  uint32_t   us = micros();
  uint32_t tail = qtail;
  uint32_t head = qhead;
  uint32_t dt   = us >= timeNow ? us - timeNow : 0xffffffff - timeNow +  us + 1;
  uint32_t dn   = tail >= head  ? tail - head  : sizeADC - head + tail + 1;
  timeNow = us;
  while (head != tail) {
    static int samples = 0;
    // Serial.write(adc_data[head]);// send to uart
    if (++head == sizeADC) head = 0;
    samples ++;
  }
  qhead = tail;

  Serial.print(",dn=");
  Serial.print(dn);
  Serial.print(",dt=");
  Serial.print(dt);
  Serial.print("SPS=");
  Serial.print(dn*1000000l / dt);// SPS = dn /( dt*1e-6)  = 1e6*dn/dt
  Serial.print("\n");
  delay(20); // wait 0.02 Second to sample ADC by ADC_IRQHandler
}

2021年12月5日 星期日

DIT FFT 範例修改

參考原始碼: https://cnx.org/contents/qAa9OhlP@2.44:zmcmahhR@7/Decimation-in-time-DIT-Radix-2-FFT 

範例修改: mfft.c

#include "math.h"
#include "stdio.h"
void mfft(double *real, double *imag, int nT) {
  int m = 0;
  for (int i = 0; i < 30; i++) if (nT == (1 << i)) {
    m = i;
    break;
  }
  if ( m < 1) return;

  const int halfT = nT >> 1;
  for (int i = 1, j = 0; i < nT - 1; i++) {// bit-reverse
    int k = halfT;
    while (j >= k) {
      j -= k;
      k >>= 1;
    }
    j += k;
    if (i < j) {
      double temp = real[i];
      real[i] = real[j];
      real[j] = temp;
      temp = imag[i];
      imag[i] = imag[j];
      imag[j] = temp;
    }
  }
  double PIx2 = - M_PI * 2;
  for (int i = 0, nx2 = 1; i < m; i++) { // radix-2 DIT FFT
    int k = nx2;
    nx2 += nx2;
    double delta = PIx2 / nx2;
    double theta = 0.0;
    for (int j = 0; j < k; j++) {
      double c = cos(theta);
      double s = sin(theta);
      theta += delta;
      for (int n = j; n < nT; n += nx2) {
        int h = n + k;
        double w1 = c * real[h] - s * imag[h];
        double w2 = s * real[h] + c * imag[h];
        real[h] = real[n] - w1;
        imag[h] = imag[n] - w2;
        real[n] += w1;
        imag[n] += w2;
      }
    }
  }
}   

int main() {
  double x[16];
  int n = sizeof(x) / sizeof(x[0]);
  double y[n];
  for (int i = 0; i < n; i++) {
    x[i] = i;
    y[i] = 0;
  }
  mfft(x, y , n);
  for (int i = 0; i < n; i++) printf("%.3f,\t %.3f\n", x[i], y[i]);
 
  return 0;
}

編譯並執行: g++ mfft.c && ./a.out

後記: 改成 Javascript 版本 mfft.js :
    mfft = (real, imag, nT) => {
        let m = 0;
        for (let i = 0; i < 30; i++) if (nT == (1 << i)) {
            m = i;
            break;
        }
        if ( m < 1) return;       
        const halfT = nT >> 1;
        for (let i = 1, j = 0; i < nT - 1; i++) {// bit-reverse
            let k = halfT;
            while (j >= k) {
                j -= k;
                k >>= 1;
            }
            j += k;
            if (i < j) {
                let temp = real[i];
                real[i] = real[j];
                real[j] = temp;
                temp = imag[i];
                imag[i] = imag[j];
                imag[j] = temp;
            }
        }
        let PIx2 = - Math.PI * 2;
        for (let i = 0, nx2 = 1; i < m; i++) { // radix-2 DIT FFT
            let k = nx2;
            nx2 += nx2;
            let delta = PIx2 / nx2;
            let theta = 0.0;
            for (let j = 0; j < k; j++) {
                let c = Math.cos(theta);
                let s = Math.sin(theta);
                theta += delta;
                for (let n = j; n < nT; n += nx2) {
                    let h = n + k;
                    let w1 = c * real[h] - s * imag[h];
                    let w2 = s * real[h] + c * imag[h];
                    real[h] = real[n] - w1;
                    imag[h] = imag[n] - w2;
                    real[n] += w1;
                    imag[n] += w2;
                }
            }
        }
    };   
    let n = 16;
    let x = new Float32Array(n);
    let y = new Float32Array(n);
    for (let i = 0; i < n; i++) {
        x[i] = i;
        y[i] = 0;
    }
    mfft(x, y , n);
    for (let i = 0; i < n; i++) console.log(`${Math.floor(x[i]*1e3 + 0.5)/1e3},\t ${Math.floor(y[i]*1e3 + 0.5)/1e3}`);
       

2021年10月19日 星期二

關於 atmeg328p 的 adc (類比轉數位) 取樣速率

當 atmega328p 使用 16MHz 振盪器運作時, adc clcok 除頻器設定可調整成 125kHz, 250kHz, 500kHz, 1MHz, 2MHz, 4MHz, 而完成一次 adc 取樣(sample)約需 13.5 個 adc clock cycle, 因此理論上每秒可以達到 9.259k, 18.518k, 37k, 74k, 148k, 296k 的取樣率(SPS:sample per second), 當 adc clcok 小於 1M 時, 用 cpu 搬動記憶體的速度遠比 adc 的取樣時間還快, 在 1M adc clock(含)以下, 要達到 74k SPS 應該不成問題. 一旦超過像是 2M 或 4M 時, 記憶體搬動速度, 開始小於取樣時間, 取樣間隔將由記憶體搬動的時間為準, 除非有 DMA 加速, 否則用 CPU 搬動是一大瓶頸, 我實驗的結果, 保守估計:2M adc clock 時應可達到 140k SPS, 但 4M adc clock 時雜訊增多, 無法正確估計. 另外 2k 記憶體是造成取樣時間無法持續太久的主因, 但若是用來分析像是AC 110V/220V 50/60Hz 市電(通過電阻分壓器降壓至 5V 以下), 應當能勝任,且綽綽有餘.

備註:

如果固定使用 1000 取樣點作為示波器, 觀看波形: 當使用取樣率 fs = 100kHz 時, 每個取樣點間隔是 1/100k 秒, 1000 點就等於 1k * (1/100k) = 1/100 秒 = 0.01 秒 , 用來紀錄 100Hz 的波形時, 這 1000 點的時間(0.01 秒), 正好看到一個週期(1/100). 若提高取樣率成 2 倍, 例如 fs = 200k, 則一個取樣點是 1/200k 秒, 取樣 1000 點等於 1k * (1/200k) = 0.005 秒, 同樣觀看 100Hz 的頻率時,時間只足以看到半個週期(0.005 秒 = 0.5/100), 以此類推, 4 倍取樣率取 1000 點, 就只能看到 1/4 周期 ...

實驗一個取樣 1280 點來看 60Hz 旋波(測量點, 只要空接一條長導線, 就能感應到市電 60Hz 的旋波), 切到 74k SPS (1M adc clock)取樣率時, 能完整紀錄一個周期, 切換成 2M adc clock 樣取時, 看到半周期, 拉到 4M 時約略看到 1/4 周期的旋波. 但測量出的週期時間約莫多出 24% 左右, 因此估計 atmega328p 最大取樣率可能落在  296/1.24 = 238.7k SPS 左右.

文章閱讀: http://www.openmusiclabs.com/learning/digital/atmega-adc/

2021年8月25日 星期三

X window 簡單的繪圖及貼圖程式

 主程式 main.cpp:

#include <opencv2/opencv.hpp> // should include first to prevent conflic
#include <opencv2/highgui.hpp>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/cursorfont.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
using namespace std;
typedef unsigned char u8int;
typedef const char*   c_str;
Display *display;
Window     child;
GC        gCtx;      
XColor     fgColor, black, white ,red, green, blue;
XImage  *bg = nullptr;
int width   = 0;
int height  = 0;
void drawRect(int x, int y, int w, int h){
    XDrawRectangle(display, child, gCtx, x, y, w, h);
}
void drawLine(int x1, int y1, int x2, int y2){
    XDrawLine(display, child, gCtx, x1, y1, x2, y2);
}
void drawArc(int x, int y, int w, int h, int ang1, int ang2){
    XDrawArc(display, child, gCtx, x, y, w, h, ang1 << 6, ang2 << 6);
}
void setColorMap(XColor &c, const char *str){
    auto colormap = DefaultColormap(display, 0);
    XParseColor(display, colormap, str, &c);
    XAllocColor(display, colormap, &c);
}
void fgSetColor(XColor &c) {
    fgColor = c;
    XSetForeground(display, gCtx, c.pixel);
}
void setBackGround(u8int *src=nullptr, int sw=0, int sh=0, int sch=0){
    if (bg) XDestroyImage(bg);
    XWindowAttributes info;
    XGetWindowAttributes(display, child, &info);
    Visual *v = info.visual;
    width  = info.width ;
    height = info.height;
    int depth = info.depth;
    int sline = sch * sw;
    int dline= 4 * width;
    int size = dline * height;
    u8int *fb = (u8int *)malloc(size);
    if(! fb) {
        bg = nullptr;
        return;
    }
    bg = XCreateImage(display, v, depth,
        ZPixmap, 0, (char *)fb, width, height, 32, 0
    );
    if(! bg) {
        free(fb);
        return;
    }
    if(! src) {
        memset(fb, 0, size);
        return;
    }
    u8int *rgb = src;
    int zch = sch > 4 ? 4 : sch;
    float xs = (float)sw / width;
    float ys = (float)sh / height;
    float fy = 0;
    for(int y = 0; y < height; y ++) {
        float fx = 0;
        int d = 0;
        int s = 0;
        for (int x = 0; x < width; x ++) {
            fb[d] = rgb[s];
            for(int z = 1; z < zch; z ++) fb[d + z] = rgb[s + z];
            fx += xs;
            d += 4;
            s = sch * (int)fx;
        }
        fy += ys;
        fb += dline;
        rgb = src + sline * (int)fy;
    }
    XPutImage(display, child, gCtx, bg, 0, 0, 0, 0, width, height);
}
void setBackGround(cv::Mat &cam){
    return setBackGround((u8int *)cam.data,
        cam.cols,
        cam.rows,
        cam.channels()
    );
}
void bgRestore(int x0, int y0, int sw = 0, int sh = 0) {
    if (x0 < width && y0 < height){
        if (x0 < 0) x0 = 0;
        if (y0 < 0) y0 = 0;
        if (x0 + sw >=  width) sw = width  - x0;
        if (y0 + sh >= height) sh = height - y0;
        if (sw > 0 && sh > 0) XPutImage(display, child, gCtx,
            bg, x0, y0,
            x0, y0, sw, sh
        );
    }
}
int main(int argc, char **argv){    
    if(argc < 2) {
        printf("no file\n");
        return 1;
    }
    cv::Mat jpeg = cv::imread(argv[1]);// 8UC3
    display = XOpenDisplay(getenv("DISPLAY"));
    if (! display)  {
        printf("X error\n");
        return 3;
    }
    child = XCreateSimpleWindow(display,
        XDefaultRootWindow(display),
        0, 0, 600, 600,
        0,// border width
        WhitePixel(display, 0),
        BlackPixel(display, 0)
    );
    XSetStandardProperties(display, child, "MyXapp",
        "Icon", None, nullptr, 0, nullptr
    );
    gCtx = XCreateGC(display, child, 0, 0);
    setColorMap(black , "#000000");
    setColorMap(white , "#FFFFFF");
    setColorMap(red   , "#FF0000");
    setColorMap(green , "#00FF00");
    setColorMap(blue  , "#0000FF");  
    unsigned char shape[8] = {0xff, 0xff,0xff, 0xff, 0xff, 0xff,0xff, 0xff};
    unsigned char mask[8]  = {0xff, 0xff,0xff, 0, 0, 0xff,0xff, 0xff};// 1: show, 0: hidden
    Pixmap shapeid = XCreateBitmapFromData(display, child, (char *)shape, 8, 8);
    Pixmap maskid  = XCreateBitmapFromData(display, child, (char *)mask, 8, 8);
    Cursor cursorID = XCreatePixmapCursor(display, shapeid, maskid, &green, &black, 0, 0);
    XDefineCursor(display, child, cursorID);
    XFreePixmap(display, shapeid);
    XFreePixmap(display, maskid);
    XFreeCursor(display, cursorID);
    XSelectInput(display, child, ExposureMask | ButtonPressMask);
    XMapWindow(display, child);
    XFlush(display);
    fgSetColor(red);
    XEvent evt;
    
    printf("press mouse right button to exit.\n");
    while(true) {
        XNextEvent(display, &evt);
        if (evt.type == Expose) {
            if(evt.xexpose.count > 0) continue; // until count = 0
            setBackGround(jpeg);
            int right = width  - 1;
            int down  = height - 1;
            int cx    = width  / 2;
            int cy    = height / 2;
            drawLine(0, cy, right, cy);
            drawLine(cx, 0, cx, down);
            drawArc(0, 0, right, down, 0, 360);
            drawRect(0, 0, right, down);
            bgRestore(cx - 100, cy - 100, 200, 200);
        }        
        if (evt.type == ButtonPress)  {
            if(evt.xbutton.button == 3) {
                printf("press right\n");
                break;
            }
        }
    }
    if (bg) XDestroyImage(bg);
    XFreeGC(display, gCtx);
    XDestroyWindow(display, child);
    XCloseDisplay(display);    
}

事先準備好一個 test.jpeg, 編譯程式並執行:

g++   main.cpp  -lX11  `pkg-config  --cflags  --libs  opencv4`  && ./a.out  test.jpeg

2021年8月20日 星期五

linux 上使用 opencv 抓取螢幕影像

 不囉唆, 直接看原始碼 x1.cpp:
#include <opencv2/opencv.hpp>
#include <X11/Xutil.h>
#include <stdio.h>
using namespace std;
int main(int argc, char ** argv){
    XWindowAttributes x = { 0 };// x window 屬性(參數群)
    auto display = XOpenDisplay(nullptr);   // 打開 x window server 的顯示器
    auto root = DefaultRootWindow(display); // get default root?
    XGetWindowAttributes(display, root, &x);// 取得螢幕參數
    int height = x.height;
    int width  = x.width;
    auto buffer = XGetImage(display, root,
        0, 0, width, height,
        AllPlanes, ZPixmap
    );// 全螢幕影像

    int cvType = CV_8UC4;// default 4 bytes's A,R,G,B
    switch (buffer->bits_per_pixel) {
        case 24: cvType = CV_8UC3; break;// 3 bytes
        case 16: cvType = CV_8UC2; break;// 2 bytes
        case  8: cvType = CV_8UC1; break;// 1 bytes
    }
    const char *name = "ScreenCapture";
    cv::namedWindow(name, cv::WINDOW_AUTOSIZE);
    cv::Mat m(height, width, cvType, buffer->data);  // 暫時包成 cv::Mat
    cv::imshow(name, m); // 顯示全螢幕影像

    cv::resize(m, m, cv::Size(), 0.25, 0.25, cv::INTER_LINEAR);// 長寬各縮 1/4
    XDestroyImage(buffer) ;// 釋放 buffer
    XCloseDisplay(display);// 關閉 x window session

    cv::imshow("1/16 Screen Shot", m); // 顯示 1/16 螢幕影像
    cv::waitKey(0);
}
用 g++ 編譯並執行程式:
     g++  x1.cpp  -lX11 `pkg-config  --cflags  --libs  opencv4`  &&  ./a.out

2021年8月12日 星期四

linux 上是使用 kolinc-jvm 編譯 kotlin 程式, 程式透過 jni 呼叫原生 c 函式

 1. 上官網 https://github.com/JetBrains/kotlin/releases/download/v1.5.21/kotlin-compiler-1.5.21.zip下載 kotlin 編譯器, 開啟終端機進入下載目錄, 將它解壓縮到目錄 kotlinc 內

       cd  Downloads  &&  unzip  kotlin-compiler-1.5.21.zip

2.  安裝 openjdk 11, 將程式安裝到 /usr/lib/jvm/java-11-openjdk-amd64

       sudo  apt-get  install  openjdk-11-jdk

3. 建一個專案 project 目錄(mkdir project), 在裏面編寫一個 Makefile 方便編譯及跑程式:
buildDir  = build
main_jar  = $(buildDir)/main.jar
jniLib_jar= $(buildDir)/jniLib.jar
libjni_so = $(buildDir)/libnative.so

jdk11     = /usr/lib/jvm/java-11-openjdk-amd64/include
openjdk   = -I$(jdk11)  -I$(jdk11)/linux
opencv2   = `pkg-config  --cflags  --libs  opencv4`

kotlinDir =  /home/mint/Downloads/kotlinc
ktc       = $(kotlinDir)/bin/kotlinc  -include-runtime
coroutine = -cp $(kotlinDir)/lib/kotlinx-coroutines-core.jar

javaLib   = $(coroutine):$(jniLib_jar)
cppLib    = -shared  -fPIC  $(openjdk)  $(opencv2)

run: main  native
    java  -Djava.library.path=$(buildDir)  $(javaLib):$(main_jar)  MainKt

main:  $(buildDir)  $(main_jar)

native: $(libjni_so)

$(main_jar): main.kt  $(jniLib_jar)
    $(ktc) -d  $@  $<  $(javaLib)

$(jniLib_jar): jniLib.kt
    $(ktc) -d  $@  $<  $(coroutine)

$(libjni_so): cpp/native.cpp
    g++  -o  $@  $<  $(cppLib)

$(buildDir):
    [ -d $@ ] || mkdir $@ && echo $@ exist already

clean:
    [ -d $(buildDir) ] || echo $(buildDir) not exist && rm -rf $(buildDir)

4. 在專案目錄內寫一個 kotlin 主程式碼 main.kt:

import  kotlinx.coroutines.*
import  jniLib.*
fun  main( ) {
  runBlocking{
     GlobalScope.launch(Dispatchers.IO) { // split nonblocking coroutine
          println("coroutine hello1")
     }
     NativeClass( ).hello( ) // call  jni  c  routine
  }
}

5.  在專案目錄內寫一個 kotlin 介面程式庫 jniLib.kt:

package jniLib
class NativeClass {  
  external fun hello( ) // external  function written in c++ language
  init {
      System.loadLibrary("native")
  }
}

6. 在專案目錄內再建個 cpp 子目錄(mkdir  cpp), 在 cpp 目錄內編寫一個用 c 寫的 jni  程式碼 cpp/native.cpp:

#include <stdio.h>
#include <jni.h>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
const char *window = "mouseEvent";
void mouseEvent(int event, int x, int y, int flags, void *args){
    static cv::Scalar green(0,   255,   0);
    static cv::Scalar red(0,   0,   255);
    cv::Mat &image = *(cv::Mat *)args;
    switch(event) {
        case cv::EVENT_LBUTTONDOWN: // left mouse button click
                printf("left   click row: %d, col: %d\n", x, y);
                cv::circle(image, cv::Point(x, y), 2, red);
                cv::imshow(window, image);
                break;
        case cv::EVENT_RBUTTONDOWN:// right mouse button click
                printf("right  click row: %d, col: %d\n", x, y);
                cv::line(image, cv::Point(0, 0), cv::Point(x, y), green, 2, cv::FILLED);
                cv::imshow(window, image);
                break;
        case cv::EVENT_MBUTTONDOWN:// middle mouse button click
                printf("middle click row: %d, col: %d\n", x, y);
                break;
        case cv::EVENT_MOUSEMOVE:// mouse movment
                // printf("mouse move  row: %d, col: %d\n", x, y);
                break;
    }
}
extern "C" {
    JNIEXPORT void JNICALL Java_jniLib_NativeClass_hello(JNIEnv *env, jobject obj) {        
        cv::Mat fg(800, 800, CV_8UC3);// w x h
        cv::namedWindow(window, cv::WINDOW_NORMAL);
        cv::setMouseCallback(window, mouseEvent, &fg);
        cv::imshow(window, fg);
        cv::waitKey(0);//press any key to exit
    }
}

上述步驟 3,4,5,6 存檔後, 在專案目錄內開啟終端機執行 make run 跑看看, 不用 Gradle, 速度快多了

後記: 使用 cmake 語法, 編譯 c++ 動態程式庫 libnative.so:

1. 先在專案目錄內, 編輯一個 CMakeLists.txt:
cmake_minimum_required(VERSION 3.10.2)
project(native)
include_directories(
    /usr/lib/jvm/java-11-openjdk-amd64/include
    /usr/lib/jvm/java-11-openjdk-amd64/include/linux
    /usr/include/opencv4
)
set(opencvLib   -lopencv_core   -lopencv_highgui   -lopencv_imgproc)
add_library(native   SHARED   cpp/native.cpp)
target_link_libraries(native   PUBLIC   ${opencvLib})

2. 建一個子目錄 build, 在裏面執行 cmake .. 產生 Makefile, 接著 make 就能生成 libnative.so:
    [ -d build ] || mkdir   build
    cd   build   &&   cmake  ..  &&  make

2021年8月1日 星期日

關於 UTF8 與 Unicode

 UTF8 編碼是 1 ~ 4 bytes 字元編碼, Unicode 則是固定 4 bytes 字元編碼:

#include "stdio.h"
typedef unsigned char u8int;
typedef const char*   c_str;

void toHex(u8int *utf8) {
        if (utf8) {
            int wc = 0;// wide char unicode
            while (*utf8 != 0) {
                if ((*utf8 & 0xc0) == 0x80) { // 10 xx xxxx, bits accumulate
                    wc <<= 6;// self-shift 6 bits to left
                    wc |= *utf8 & 0x3f;// accumulate 6 bits
                } else {// ready to decode
                    if (wc > 0x80) printf("%5x, ", wc); // previous one

                    if (*utf8 < 0x80) { // 0x xx xxxx, 1 bytes utf8
                        printf("%2x, ", *utf8);// decode immediatelly
                        wc = 0; // reset to 0 to prevent overflow
                    } // decode later
                    else if (*utf8 < 0xe0) wc = *utf8 & 0x1f;// 2 bytes utf8, 5 bits begin +  6 bits later = 11 bits total                       
                    else if (*utf8 < 0xf0) wc = *utf8 &  0xf;// 3 bytes utf8, 4 bits begin + 12 bits later = 16 bits total                       
                    else                   wc = *utf8 &  0x7;// 4 bytes utf8, 3 bits begin + 18 bits later = 21 bits total                       
                }
                utf8 ++;
            }
            if (wc > 0x80) printf("%5x", wc);
        }
}

void putwc(int wc){ // putchar for wide char unicode
    if(wc < 0x80)       printf("%c", wc); // 7  bits: 0xxxxxxx
    else if(wc < 0x800) printf("%c%c",
            0xc0 | (wc >> 7),
            0x80 | (wc & 0x3f)
        ); // 11 bits: 110xxxxx 10xxxxxx , 5 + 6 bits
    else if(wc < 0x10000) printf("%c%c%c",
            0xe0 |  (wc >> 12),
            0x80 | ((wc >>  6) & 0x3f),
            0x80 |  (wc        & 0x3f)
        );// 16 bits: 1110xxxx 10xxxxxx 10xxxxxx, 4 + 6 + 6 bits
    else printf("%c%c%c%c",
            0xf0 | ((wc >> 18) & 7),
            0x80 | ((wc >> 12) & 0x3f),
            0x80 | ((wc >>  6) & 0x3f),
            0x80 |  (wc        & 0x3f)
        );// 21 bits: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx, 3 + 6 + 6 + 6 bits
}

void toHex(c_str utf8) { return toHex((u8int *)utf8); }
int main(){
    c_str src = "Taiwan,台灣,🀄,0123456789,\U0001F004";
    toHex(src);
    printf("\n%s\n", src);   
    putwc(0x1f004);
    printf("\n");
    return 0;
}

編譯並執行  g++ a.c && ./a.out

54, 61, 69, 77, 61, 6e, 2c,  53f0,  7063, 2c, 1f004, 2c, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 2c, 1f004
Taiwan,台灣,🀄,0123456789,🀄
🀄

用 python 解簡單的常微分方程式

# sudo apt install python3-pip # python3 -m venv venv # cd venv # . bin/activate # pip3 install numpy matplotlib import numpy as np import m...