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
}

簡單 c 程式碼, 根據五行八卦相生相剋推斷吉凶

#include "stdio.h" // 五行: //               木2 //      水1           火3 //         金0     土4 // // 先天八卦 vs 五行 //                    ...