2022年11月5日 星期六

玩 Arch Linux

1. 官網下載 Iso 檔 https://archlinux.org/download/, 目前 2022.11.01 版支援 Kernel 6.06, youtube 有許多影片教導重頭至尾的安裝步驟
2. 我用 pacstrap下載以下套件,就足以實現一套完整的中文 linux x11 桌面系統在 VirtualBox 上跑:
base   linux   linux-firmware   base-devel   linux-headers    grub   dosfstools  squashfs-tools   xorriso   efibootmgr   os-prober   gdisk   parted   dhcpcd   networkmanager   network-manager-applet    dialog   wireless_tools   wpa_supplicant    openssh   git   virtualbox-guest-utils   xorg   lightdm   lightdm-gtk-greeter   mate   mate-extra     xorg-server     wqy-bitmapfont   wqy-zenhei   wqy-microhei   wqy-microhei-lite   opendesktop-fonts   ttf-arphic-ukai   ttf-arphic-uming    pulseaudio   pulseaudio-alsa   pavucontrol   materia-gtk-theme   papirus-icon-theme    firefox    xed    geany    gcin   vim

後記:
1. 下載並安裝完後再將  /var/cache/pacman/pkg 刪除可以能節省 1G 左右空間
2. 假設 EFI filesystem 在 /dev/sda1, 安裝 grub bootloader 到 /dev/sda2:
   mkdir   /mnt/efi   &&   mkdir   /mnt/boot
   mount   /dev/sda1  /mnt/efi   &&   mount   /dev/sda2    /mnt/boot
   grub-install   --target=x86_64-efi    --efi-directory=/mnt/efi    --boot-directory=/mnt/boot --bootloader-id=grub    /dev/sda

2022年8月18日 星期四

c++ 語言將陣列當成 bitmap 畫線方式

參考畫線演算法: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
用 c++ 語言將 unsigned char arra[w * h] 陣列當灰階 bitmap, 在裏面畫線範例程式:         
// draw.cpp
#include "stdio.h"
void drawline (unsigned char *p, int w, int h, int cch, int x1, int y1, int x2, int y2, int c=0xffffff) {
        unsigned char *pixel = p + (y1 * w + x1) * cch;
        int dx = x2 - x1;
        int dy = y2 - y1;
        int sx = dx > 0 ? 1 : -1;
        int sy = dy > 0 ? 1 : -1;
        int xbytes = cch * sx;
        int ybytes = w * cch * sy;
        
        if (dx < 0) dx = -dx;// abs, positive
        if (dy > 0) dy = -dy;//-abs, negative
        int de = dx + dy;
        while (true) {
            if (0 <= x1 && x1 < w &&
                0 <= y1 && y1 < h) {                    
                if (cch == 1) *pixel = c & 0xff;
                else {
                    pixel[2] = c >> 16 & 0xff;// R
                    pixel[1] = c >> 8  & 0xff;// G
                    pixel[0] = c       & 0xff;// B                    
                }
            }
            if (x1 == x2 && y1 == y2) break;
            int ex2 = de << 1;
            if (ex2 >= dy) {
                if (x1 == x2) break;
                de += dy;
                x1 += sx;
                pixel += xbytes;
            }
            if (ex2 <= dx) {
                if (y1 == y2) break;
                de += dx;
                y1 += sy;
                pixel += ybytes;
            }
        }
    };
    int main( ){
        int w = 80;
        int h = 25;
        unsigned char bitmap[w * h] ={0};// bitmap:  width * height, gray set black color
        
        auto grayDraw= [&](int x1, int y1, int x2, int y2) {
                int cch = 1;// color channel
                int grayLevel = 0xff;// white color
                drawline(bitmap, w, h, cch, x1, y1, x2, y2, grayLevel);
        };
       
        grayDraw(    0  ,   0,      0, h-1);
        grayDraw(    0,     0, w-1,     0);
        grayDraw(    0, h-1, w-1, h-1);
        grayDraw(w-1,    0, w-1, h-1);
        
        grayDraw(  0,   0, w-1, h-1);
        grayDraw(  0, h-1, w-1,   0);
       
        unsigned char *pixel = bitmap;
        for(int y = 0; y < h; y++) {
            for(int x = 0; x < w; x ++){
                *pixel ++ > 0 ? printf("O") : printf("."); // 文字模式         
            }           
            printf("\n");
        }
    }
編譯並執行  g++  draw.cpp && ./a.out

2022年8月10日 星期三

Linux 上讓 Flutter 3.0 支援 opencv

opencv 用在影像處理上, 非常方便, 且原始碼用 c++ 寫的, dart 語言透過 ffi 便能與 c 語言相互交流, 近來 Flutter 3.0 支援 linux desktop 也趨成熟, 運用 cmake 結合 c 程式庫, 在 linux 上寫 GUI 程式, 用來處理影像就不再是難事.
1. 先用終端機建立一個專案
    flutter  create  project1
        
2.在專案的 lib 目錄下建立一個 cpp 目錄, 將  c 原始碼放於此, 另外 build 目錄放編譯時的檔案
    cd  project1/lib  && mkdir  cpp  && mkdir  build
       
3. 安裝 opencv-dev 開發程式庫及 cmake,  pkg-config 等工具程式:
    sudo  apt  install   libopencv-dev   cmake   pkg-config
      
4. 在 lib 目錄下建立一個檔案 CMakeLists.txt, 將以下內容存檔:
# lib/CMakeLists.txt
    cmake_minimum_required(VERSION 3.16.3)
    project("native")      
    find_package(PkgConfig  REQUIRED)
    pkg_check_modules(OpenCV   REQUIRED   IMPORTED_TARGET   opencv4)

    include_directories(
        cpp
        ${OpenCV_INCLUDE_DIRS}
    )
    add_library(native  SHARED
        cpp/native.cpp
    )   
    target_link_libraries(native
        ${OpenCV_LIBRARIES}
    )

    
5. 編輯 porject1/lib/cpp/native.h , porject1/lib/cpp/native.cpp 示範呼叫 opencv:
// lib/cpp/native.cpp
#include <vector>
#include "native.h"
using namespace std;
using namespace cv;
extern "C" { // export as C function
    ImgStruct  grayJpeg(unsigned char *bin = nullptr, int size = 0) {// 解碼 bin, 轉成 jpeg 灰階影像   
        static vector<unsigned char> jpg;
        vector<unsigned char>().swap(jpg);
        if (bin == nullptr) return ImgStruct {.size = 0, .data = nullptr};
        imencode(".jpg",
            imdecode(Mat(1, size, CV_8UC1, bin), IMREAD_GRAYSCALE),
            jpg
        );
        return ImgStruct {
            .size = (int) jpg.size(),
            .data = jpg.data()
        };
    }
}

// lib/cpp/native.h
    #ifdef __cplusplus
        extern "C" {
    #endif
    #ifndef Native_H
    #define Native_H
        struct ImgStruct {
            int  size;
            unsigned char *data;
        };

        ImgStruct  grayJpeg(unsigned char*, int);        
    #endif
    #ifdef __cplusplus
        }
    #endif   

6.進入 project1/linux 建好程式庫目錄的聯結:
            cd   porject1/linux  &&  ln  -sf  ../lib  .
   接著編輯  project1/linux/CMakeLists.txt, 拉到最後面, 加入以下文字:   
    add_subdirectory("./lib")
    set(nativeSO "${PROJECT_BINARY_DIR}/lib/libnative.so")
    install(FILES  ${nativeSO}  DESTINATION  ${INSTALL_BUNDLE_LIB_DIR}  COMPONENT Runtime)
     
7. 編輯主程式   project1/lib/main.dart 及繪圖程式 project1/lib/mjpgViewer.dart:
//主程式:  lib/main.dart
    import 'package:flutter/material.dart';
    import 'mjpgViewer.dart';
    void main() {
        WidgetsFlutterBinding.ensureInitialized();
        runApp(MyApp());
    }
    class MyApp extends StatelessWidget {
        const MyApp({Key? key}) : super(key: key);
        @override
        Widget build(BuildContext context) {
            return MaterialApp(
                theme: ThemeData.dark(),
                home: const HomePage(),
                title:"MjpgViewer"
            );
        }
    }
    class HomePage extends StatelessWidget{
        const HomePage({Key? key}) : super(key: key);
        @override
        Widget build(BuildContext context) {
            return Scaffold(
                body: MjpgViewer(MediaQuery.of(context).size)  
            );
        }
    }
   
// 繪圖程式: lib/mjpgViewer.dart
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:ffi' hide Size;
import "package:ffi/ffi.dart";
import 'package:flutter/services.dart';

typedef  CharPtr = Pointer<Uint8>;
class ImgStruct extends Struct {
  @Int32() external int length;
  external CharPtr data;
  Uint8List asTypedList() => data.asTypedList(length); // method to get data
}


    class MjpgViewer extends StatefulWidget {
        final Size bodySize;
        MjpgViewer(Size? size): bodySize = size ?? Size(400, 400);
        @override _mjpgViewerState createState() => _mjpgViewerState();
    }
    class _mjpgViewerState extends State<MjpgViewer> {
        ui.Image? assetsImage;
        @override  void  initState( ) {
            super .initState( );
            final libso = DynamicLibrary.open("libnative.so");// load share library
            
            final grayJpeg = libso.lookupFunction<ImgStruct Function(CharPtr, Int),
               
ImgStruct Function (CharPtr, int)
            >("grayJpeg");


            rootBundle.load("assets/test.jpg").then((ByteData bytes) {
                final Uint8List u8List = bytes.buffer.asUint8List();
                final int len = u8List.lengthInBytes;
                final CharPtr jpg = malloc.allocate<Uint8>(len);//預先分配動態記憶空間         
                jpg.asTypedList(len).setAll(0, u8List);// 因無法獲得 u8List 的指標, 只能複製後再傳過去, 從 u8List 位置 0 開始, 複製到底.
                final ImgStruct  img = grayJpeg(jpg, len);
                decodeImageFromList(img.asTypedList( )).then(
                  (_uiImg) => setState(()=> assetsImage = _uiImg)
                );
                malloc.free(jpg);// 動態分配的空間, 不用時必須釋放掉
            });
        }
        @override Widget build(BuildContext context) => CustomPaint(
            painter: CavasDraw(assetsImage),
            size: widget.bodySize,
        );
    }
    class CavasDraw extends CustomPainter {
        final ui.Image? background;
        CavasDraw(this.background);
        @override bool shouldRepaint(CavasDraw old) => true;
        @override void paint(Canvas canvas, Size size){
            final bg = background;   
            if (bg != null) {
                final pen = Paint();
                pen.strokeWidth = 1;
                pen.style = PaintingStyle.stroke;
                final src = Rect.fromLTWH(0, 0, bg.width.toDouble(), bg.height.toDouble());
                final dst = Rect.fromLTWH(0, 0, size.width, size.height);
                canvas.drawImageRect(bg, src, dst, pen);
            }
        }
    }

// project1/pubspec.yaml
name: project1
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1

environment:
  sdk: ">=2.17.6 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  ffi: ^2.0.1
 
flutter:
  assets:
    - assets/test.jpg
  uses-material-design: true

最後進入專案 project1 的目錄, 建個子目錄 assets, 將 test.jpg 放進該目錄內, 執行:
    cd project1  &&  flutter  pub get  &&  flutter  run  --no-version-check

後記:

1. 整個專案目錄結構
project1/
    pubspec.yaml
    assets/
        test.jpg
    lib/
        main.dart
        mjpgViewer.dart
        CMakeLists.txt
        CMakeLists.android

        cpp/
            native.cpp
            native.h
    linux/
        CMakeLists.txt
        lib  -> ../lib
    android/
        lib/
            CMakeLists.txt  -> ../../lib/CMakeLists.android
            cpp  -> ../../lib/cpp

        app/
           build.gradle

2. 參考之前文章 https://masontseng.blogspot.com/search?q=opencv 用 OpenCV jni 來編譯能在 Android 上跑的 share library, 稍微修改 CMakeLists.txt, 將它取名為 CMakeLists.android
# CMakeLists.android
    cmake_minimum_required(VERSION 3.16.3)
    project("native")   
    set(OpenCV_DIR  /home/mint/Downloads/OpenCV-android-sdk/sdk/native/jni)
    find_package(OpenCV  REQUIRED)

    include_directories(
        cpp
        ${OpenCV_INCLUDE_DIRS}
    )
    add_library(native SHARED
        cpp/native.cpp
    )
    target_link_libraries(native
        -ljnigraphics
        ${OpenCV_LIBS}
    )

 但 cmake 只認得 CMakeLists.txt, 因此用 ln -sf 符號聯結的方式來解決檔名的問題, 也不需複製.
        cd Project1/android &&  mkdir  lib  &&  cd  lib
        ln   -sf   ../../lib/cpp   .
        ln   -sf   ../../lib/CMakeLists.android    CMakeLists.txt
 
最後編輯 project1/android/app/build.gradle, 加入 externalNativeBuild, 將 CMakeLists.txt 路徑設定好就可以了:
 android {
    //...
    externalNativeBuild {
        cmake {
            path "../lib/CMakeLists.txt"
        }
    }

}
p.s. 編譯成 Android 可以用的 share library, 參考文章:   https://levelup.gitconnected.com/port-an-existing-c-c-app-to-flutter-with-dart-ffi-8dc401a69fd7

2022年8月6日 星期六

測試dart 的 c interop 並繪圖

//main.dart
import 'package:flutter/material.dart';
import 'draw2d.dart';
void main( ) {
  runApp(new MyApp( ));
}
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark( ),
      home: HomePage( ),
      title:"draw2d",// id, it's not title name!
    );
  }
}
class HomePage extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Draw2D(MediaQuery.of(context).size)  
    );
  }
}

// draw2d.dart
import 'dart:ffi'  hide Size;// to use Size in dart:ui
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart'  hide TextStyle;// to use TextStyle in dart:ui
import "package:ffi/ffi.dart";
import "dart:async";
import "dart:isolate";
class Draw2D extends StatefulWidget {
  final Size bodySize;
  Draw2D(Size? size): bodySize = size ?? Size(400, 400);
  @override _Draw2DState createState( ) => _Draw2DState( );
}
class _Draw2DState extends State<Draw2D> {
  List<double> figure = [ ];
  final iso = ReceivePort( );
  final dataOffset = 16;
  static Future<void> bgIsolate(SendPort port) async { // isolate function must be static
    final tag = Isolate.current.debugName.toString( );
    final tagLog= (_)=> print("${DateTime.now( )}(${tag}) $_");
    final libso = DynamicLibrary.open("lib/libnative.so");// load share library
    final sttyReady = libso.lookupFunction<Bool Function( ),
        bool Function ( )
      >("ready");        
    final sopen = libso.lookupFunction<Int32 Function(Pointer<Utf8>, Int32),
        int Function(Pointer<Utf8>, int)
      >("sopen");
    final sttyOpen = ([String dev = "/dev/ttyACM0", baud = 250000]) {
      final _str = dev.toNativeUtf8( );
      sopen(_str, baud);
      malloc.free(_str);
    };
    final swrite = libso.lookupFunction<Int32 Function(Pointer<Utf8>),
      int Function(Pointer<Utf8>)
    >("swrite");    
    final sttyWrite = (String str) {
      final _str = str.toNativeUtf8( );
      swrite(_str);
      malloc.free(_str);
    };
    final sttyPoll = libso.lookupFunction<Int32 Function( ),
        int Function ( )
      >("spoll");    
    final sttyBuffer = libso.lookupFunction<Pointer<Uint8> Function( ),
        Pointer<Uint8> Function ( )
      >("adcBuffer");
    final portSendADC = ( ) {
      final n = sttyPoll( );
      if (n > 0) {// make sure null safety
        port.send(sttyBuffer( ).asTypedList(n));
      }
    };
    tagLog("debugName is $tag\n");  
    if (!sttyReady( )) sttyOpen("/dev/ttyACM0");

    if (sttyReady( )) {
      sttyWrite("fson\n");// command to send ADC data
      while (true) {// event loop
        await Future.delayed(Duration(milliseconds: 0));
        portSendADC( );
      }
      sttyWrite("fsoff\n");// command to turn off ADC
    }
    Isolate.exit(port, null);// isolate bgChild exit and return a null back.
  }
  @override void dispose( ) {
    iso.close( );
    super.dispose( );
  }
  @override void initState( ) {
    super.initState( );
    final mainLog = (_) => print("${DateTime.now( )}(${Isolate.current.debugName}) $_");
    mainLog("Future<Isolate> attach sendPort");
    figure = List<double>.filled(widget.bodySize.width.toInt( ), 0);
    final flen = figure.length;
    for (int i = 0; i < flen; i ++) {// normalize f(θ) =  (1 + sin(θ)) / 2;
      figure[i] = (1 - sin(2 * 3.14159 * i / flen)) / 2;// normallize, mirror to speed up CavasDraw.paint
    }
    Isolate.spawn(bgIsolate, iso.sendPort, debugName: "bgChild");
    iso.listen((adcData) {
      if (adcData == null) {
        iso.close( );
      } else {
        int i = 0;
        for (final adc in adcData) {
          if (i < dataOffset) figure[i] = 0.5;// header, fill base point 128 / 256 = 0.5
          else if (i < flen)  figure[i] = adc / 256.0;// normallize adc data, mirror already.
          i ++;// next location
        }
        setState(( ){ });
      }
    });
  }
  @override Widget build(BuildContext context) => Listener (
    onPointerSignal: (evt) => setState(( ) {
      // print(evt);
    }),
    onPointerHover: (evt)  => setState(( ) {
      // print(evt);
    }),
    child: CustomPaint(
      painter: CavasDraw(widget.bodySize, figure),
      size: widget.bodySize,
    )
  );
}
class CavasDraw extends CustomPainter {
  final Size drawSize;
  final List<double> figure;// normalize/mirror figure [0 ~ 1]
  final pen     = Paint( );
  final outline = Path( );
  final dx, cx, cy, w, h;
  late Offset east, west, south, north, center;    
  Paragraph sentence(String str, [double? sz, Color? c]) {
    final font = ParagraphBuilder(ParagraphStyle(fontSize: sz ?? 24))..
                  pushStyle(TextStyle(color: c ?? Colors.white))..
                  addText(str);
    return font.build( ).. layout(ParagraphConstraints(width: drawSize.width));
  }
  CavasDraw(this.drawSize, this.figure):
    dx    = drawSize.width  / figure.length,
    cx    = drawSize.width  / 2,
    cy    = drawSize.height / 2,
    w     = drawSize.width,
    h     = drawSize.height {
    pen.style = PaintingStyle.stroke;
    center = Offset(cx, cy);
    east   = Offset(w - 1, cy);
    west   = Offset(0, cy);
    south  = Offset(cx, h - 1);
    north  = Offset(cx, 0);
    outline.moveTo(0, 0);
    outline.lineTo(0, h - 1);
    outline.lineTo(w - 1, h - 1);
    outline.lineTo(w - 1, 0);
    outline.lineTo(0, 0);
    outline.close( );
  }
  @override bool shouldRepaint(CavasDraw old) => true;
  @override void paint(Canvas canvas, Size size) {
    pen.strokeWidth = 1;
    pen.color = Colors.green;
    double x = 0.0;
    Offset lastone = Offset(x, cy);// base point
    for (final y in figure) {// draw the figure
      final fxy = Offset(x, h * y);
      canvas.drawLine(lastone, fxy, pen);
      lastone = fxy;
      x += dx;
    }
    pen.color = Colors.red;
    canvas.drawLine(north, south, pen);
    canvas.drawLine(west, east, pen);
    canvas.drawParagraph(sentence("觸發點0"), west);// draw text
    pen.strokeWidth = 8;
    pen.color = Colors.grey;
    canvas.drawPath(outline, pen);
  }
}

#CMakeLists.txt
cmake_minimum_required(VERSION 3.7)
project(native)
add_library(native SHARED
    native.cpp
 )

//native.h
#ifdef __cplusplus
   extern "C" {
#endif
#ifndef Native_H
#define Native_H
bool    ready( );// device ready
int     sopen(char *buffer, int baud);
int     spoll( );// number of data available in serial port
int     swrite(char *buffer);// serial port write c_str
unsigned char *adcBuffer( );
#endif
#ifdef __cplusplus
   }
#endif
   
//native.cpp
#include "UBitSerial2.h"
#include "native.h"
static UBitSerial   serialPort = UBitSerial( );
extern "C" { // export as C function
        bool ready( ) {
                return serialPort.ready( );
        }              
        int spoll( ) {
                return serialPort.adcPoll( );
        }
        int swrite(char *buffer){
                return serialPort.write(buffer);
        }
        int sopen(char *buffer, int baud) {
                return serialPort.sopen(buffer, baud);
        }        
        unsigned char *adcBuffer( ) {
                return serialPort.adcBuffer( );
        }        
}

//UbitSerial2.h
#ifndef UBitSerial2_H
#define UBitSerial2_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 250000
#define maxCountSPF 2048
#define dataOffset  16
class UBitSerial {
  private:
    int fd = 0;
  public:
    ~UBitSerial( ) {
      if (fd) close(fd);
      fd = 0;
    }
    int sopen(char *dev, int baud) {
      if (fd > 0) close(fd);
      int ttyFD = open(dev, O_RDWR | O_NONBLOCK);
      if (ttyFD > 0) {
        fd = ttyFD;
        struct termios2 uart2;
        ioctl(fd, TCGETS2, &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);
        ::printf("%s baud=%d ready. serial port fd=%d\n", dev, baud, fd); // use global printf
      }
      return ttyFD;
    }
    UBitSerial(const char *dev = "/dev/ttyUSB0", int baud = defaultBaudrate) {   
      sopen((char *)dev, baud);
    }
    bool ready( ) { return fd > 0;  }
    unsigned char rawBuffer[2][maxCountSPF];// byte buffer
    int qSize = sizeof(rawBuffer)/sizeof(rawBuffer[0]);
    int head = 0;
    int tail = 0;
    int frameSync = 0;
    int nIndex  = 0;
    char chunkBuffer[1024];
    const int lastone = sizeof(chunkBuffer) - 1;
    int adcPoll( ) {
        if (fd > 0) {
        int fig_1  = 0;
        int chunk = 0;    
        unsigned char *fillBuffer = rawBuffer[head];// raw figure buffer
        while (true) {
          ioctl(fd, FIONREAD, &chunk);
          if (chunk <= 0) break;// poll until empty
          if (chunk > lastone) chunk = lastone;
          ::read(fd, chunkBuffer, chunk);
          chunkBuffer[chunk] = 0;// append EOS
          if (strstr(chunkBuffer, "stop!!!")) continue;
          
          for (int i = 0; i < chunk; i ++) {// check one by one          
              unsigned char figY = (unsigned char) chunkBuffer[i];
              switch (frameSync) {// state macine to detect frameSync 0xff 0x0 ...
               case 0: if (figY == 0xff) {
                              frameSync = 1;
                              fig_1 = 0xff;
                          }
                          break;
                  case 1: if ((figY & 0xf0) != 0 || fig_1 != 0xff) frameSync = 0;
                          else {
                              frameSync = 2;
                              nIndex = 0;
                              fillBuffer[nIndex ++] = 0xff;//insert 0xff at the begining
                          }
                          break;
                  default:break;
              }
              if (frameSync != 2) continue;// until frameSync == 2
              if (nIndex < dataOffset) fillBuffer[nIndex ++] = figY;
              else fillBuffer[nIndex ++]  = ~figY;
              if (nIndex == maxCountSPF) {
                frameSync = 0;
                int next = head + 1;
                if (next == qSize) next = 0;
                if (next != tail) head = next;// head to next rawBuffer
            }
          }
        }
      }
      return (head == tail) ? 0 : maxCountSPF;
    }
    unsigned char *adcBuffer( ) {// no copy
      if (adcPoll( ) > 0) {
        unsigned char *ptr = rawBuffer[tail];
        if (++ tail == qSize) tail = 0;
        return ptr;
      }
      return nullptr;
    }
    int write(char *buffer) {
      if (fd && buffer) return ::write(fd, buffer, strlen(buffer));// use global write      
      return 0;
    }
    int printf(const char *fmt, ...) {// todo: prevent size of strBuffer overflow.
      static char strBuffer[1024];// upto 1024 characters
      va_list parameters;
      va_start(parameters, fmt);
      vsprintf(strBuffer, fmt, parameters);
      va_end(parameters);
      return write(strBuffer);// this->write
    }
};
#endif
 

2022年8月3日 星期三

關於 flutter Matrix4 部件

 Flutter 可以將部件用 Matrix4 包裝起來, 透過它將部件作旋轉, 位移, 放大等特效.可以參考文章:https://medium.com/flutter-community/advanced-flutter-matrix4-and-perspective-transformations-a79404a0d828

1. 旋轉 X 軸時, 使用的轉換矩陣是[1,0,0,0;  0, cos, sin, 0;  0,-sin, cos, 0; 0,0,0,1], 座標運算一下:

  x' = x,   y' = y * cos(θ) + z * sin(θ),   z' = -y * sin(θ) + z * cos(θ)

因為畫面 z = 0 =>  x' = x,    y' = y * cos(θ), 可以預見 X 軸不變, 但 Y 軸變短了, 因為|cos(θ)| <= 1

 

2. 旋轉 Y 軸時, 使用的轉換矩陣是[cos,0,-sin,0;  0, 1, 0, 0;  sin,0,cos,0;  0,0,0,1], 座標運算一下:

  x' = x * cos - z *sin,     y' = y,     z' = x * sin + z *cos

因為畫面 z = 0 =>  x' =  x * cos,    y' = y , 可以預見 Y 軸不變, 但 X 軸變短了, 因為|cos(θ)| <= 1

 

3.旋轉 Z 軸, 使用的轉換矩陣是[cos, sin,0, 0;  -sin,cos,0,0;  0,0,1, 0;  0,0,0,1], 座標運算一下:

         x' = x * cos(θ) + y *sin(θ),          y' = -x * sin(θ) + y * cos(θ),         z' = z

當所有座標點, 以左上角當原點 (0,0, 0)為參考點, 同時旋轉旋轉 Z 軸 θ 時, 各座標點相對位置將一如往常, 物體並不會變形

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}`);
       

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

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