日記；回すだけⅥ ① C++でプログラミング ＜BLEペリフェラルのコントローラ＞

　CircuitPython 8.0.0-beta.6を使って、マイコン・ボードFeather nRF52840 Expressにつないだジョイスティックからアナログ値を読み出し、角度情報をBLEで送信するプログラムを作ります。
　次回、BLEのセントラルを作り、最終的には、モータを回す予定です。

![IMGP1452.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/0d263c3b-4e58-81c4-0fb0-b99e5a4d9547.png)

### Feather nRF52840 Express
##### おもなスペック

<img width="400" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/dcfbeef0-9d94-cd12-d8e9-d579d18bcddb.png">

　[Adafruit](https://www.adafruit.com/product/4062)のマイコン・ボードです。BLEモジュールの表面には技適のコードが刻印されています。
　スイッチサイエンス [Adafruit Feather nRF52840 Express](https://www.switch-science.com/products/5400)、秋月電子通商 [Ａｄａｆｒｕｉｔ　Ｆｅａｔｈｅｒ　ｎＲＦ５２８４０　Ｅｘｐｒｅｓｓ](https://akizukidenshi.com/catalog/g/gM-16358/)で購入できます。
　ピン配置です。

<img width="500" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/10bcc730-d608-cd24-da0f-8d98b47484f4.png">

- CPU　ARM Cortex M4F（64MHz）
- メモリ　フラッシュ1MB、SRAM 256KB
- 動作電圧　1.7～3.3V
- Bluetooth Low Energy compatible 2.4GHz
- デバッグ用SWDコネクタ
- 3.7 Vリチウム・ポリマ電池用のコネクタと充電器


### CircuitPython 8.0.0-beta.6

##### インストール
　[https://circuitpython.org/](https://circuitpython.org/) から入ります。Downloadsをクリックします。
![f101.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/8d0bb9a7-aaac-9d25-b8a1-c431dde69933.png)

　検索欄にFeatherを入れます。
![f102.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/6dbc6fbc-4b32-cfb3-39c1-c8bde25bb3aa.png)

　見つかったFeather nRF52840 Expressをクリックします。

![f103.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/afb1beff-1962-3c2b-ebd3-bb23fef9a730.png)

　CircuitPython 8.0.0-beta.6のUF2をダウンロードします。
　Resetボタンをダブルクリックしてブートローダのドライブをエクスプローラに表示します。
ダウンロードした.UF2ファイルをこのFTHR840BOOTドライブにドラッグします。コピーが終わると、CIRCUITPYという名前のドライブになります。このマイコン・ボードには2Mバイトのストレージがあるので、2MB弱の容量が表示されていると思います。
![f104.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/5ed8a49a-5e05-e77a-0387-38acca1c5515.png)

　[https://circuitpython.org/](https://circuitpython.org/) に戻ります。Librariesをクリックします。
![f105.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/fdd2382b-aaf4-9b5b-1ce9-f583975da877.png)

　CircuitPython 8の最新版が見えているので、クリックしてダウンロードします。ここでは2020年12月21日版でした。
![f106.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/85833aa6-073b-a729-2c1e-f0927577b49b.png)
　zipファイルをダブルクリックで表示します。examplesとlibフォルダをPCの任意のフォルダにコピー（解凍）します。
![f107.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/c0c2478a-c7b7-2c96-c1de-3598ecab134c.png)

##### エディタはMuを使う

　[Mu](https://codewith.mu/)のページに行ってDownloadをクリックします。

![f108.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/90e14e06-294a-06ad-01e5-b4bfb5a03c82.png)

　Windows用を使いました。現時点で1.2.0です。
![f109.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/8fc0fc85-92ad-3904-6c02-b4313be8ff45.png)

### アナログ入力
　ジョイスティックの電源はマイコン・ボードの3.3VとGNDに、XはA0端子、yはA1端子につなぎました。
　PCとFeather nRF52840 ExpressをUSBケーブルでつなぎます。Muエディタを立ち上げると、ボードが認識され、プログラムを書いて保存をクリックするとアップロードされて実行に移ります。
　その前にシリアル・アイコンをクリックしておきます。

　ジョイスティックはボリュームがつながっていて電圧が2系統出力されます。それをマイコンのアナログ入力読み取ります。
　分解能は高いので、入力値はふらふらするので、readXY()関数では20回読み取り、真ん中付近の10回分を平均しています。
　このプログラムを実行します。ジョイスティックを右、左、触らないときの中央値を記録します。それらの値を利用して、x,yの電圧を求め、そこから角度thとベクトル長Veを計算します。
　-0.502や-0.5088は、ノブに触らないときに、ほぼ0になるようにするための補正値です。



```python
from board import *
import analogio
import time
import math

pinX = analogio.AnalogIn(A0)
pinY = analogio.AnalogIn(A1)

NUM_SAMPLES = 20

def readXY():
    analogDataX=[]
    analogDataY=[]
    for i in range(NUM_SAMPLES):
        analogDataX.append(pinX.value)
        analogDataY.append(pinY.value)
        time.sleep(0.02)
    analogDataX.sort()
    analogDataY.sort()
    meanX = sum(analogDataX[5:NUM_SAMPLES-5]) / float(len(analogDataX[5:NUM_SAMPLES-5]))
    meanY = sum(analogDataY[5:NUM_SAMPLES-5]) / float(len(analogDataY[5:NUM_SAMPLES-5]))
    #print(analogData0[5:NUM_SAMPLES-5])
    x = 2*(((meanX-16300)/33100)-0.502)
    y = 2*(((meanY-15728)/31965)-0.5088)
    th = math.atan2(y,x)*180/3.141592
    Ve = math.sqrt(x*x + y*y)
    return th,Ve

while 1:
    print("---")
    [th,Ve] = readXY()
    print('Theta ={:.4f} Vector ={:.2f}'.format(th,Ve))
    time.sleep(2)
pinX.deinit()
pinY.deinit()

```

　ノブを倒したときの角度の実測値です。倒し切ると1.0付近のベクトル値が得られます。
![c102.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/61e9eb7b-bec5-4923-b21e-aa00710d5bb0.png)

### BLEペリフェラル（UART）
　ジョイスティックの角度とベクトル長をBLEで送信します。ノルディックのUART機能を利用したプログラムです。テキストで、二つのデータを1回で送っています。


```python
from board import *
import analogio
import time
import math
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService

pinX = analogio.AnalogIn(A0)
pinY = analogio.AnalogIn(A1)
NUM_SAMPLES = 20

ble = BLERadio()
ble.name = "Feather nRF52840 Express"
uart = UARTService()
advertisement = ProvideServicesAdvertisement(uart)

def readXY():
    analogDataX=[]
    analogDataY=[]
    for i in range(NUM_SAMPLES):
        analogDataX.append(pinX.value)
        analogDataY.append(pinY.value)
        time.sleep(0.02)
    analogDataX.sort()
    analogDataY.sort()
    meanX = sum(analogDataX[5:NUM_SAMPLES-5]) / float(len(analogDataX[5:NUM_SAMPLES-5]))
    meanY = sum(analogDataY[5:NUM_SAMPLES-5]) / float(len(analogDataY[5:NUM_SAMPLES-5]))
    #print(analogData0[5:NUM_SAMPLES-5])
    x = 2*(((meanX-16300)/33100)-0.502)
    y = 2*(((meanY-15728)/31965)-0.5088)
    th = math.atan2(y,x)*180/3.141592
    Ve = math.sqrt(x*x + y*y)
    return th,Ve

while 1:
    ble.start_advertising(advertisement)
    print('wait')
    while not ble.connected:
        pass
    while ble.connected:
        print('connected ')
        [th,Ve] = readXY()
        print('Theta ={:.4f} Vector ={:.2f}'.format(th,Ve))
        uart.write('{:.1f} {:.1f}'.format(th,Ve))
        time.sleep(0.2)
        
pinX.deinit()
pinY.deinit()
```

　実行すると、

``ImportError: 'adafruit_ble' という名前のモジュールはありません``
と下のエリアに表示されるので、CIRCUITPYドライブのlibに、PCに保存していたlibの中にあるadafruit_bleフォルダをコピーします。
　動き始めます。

##### セントラルはRSL10 Bluetooth Low Enaergy Exploer
　オンセミのRSL10 Bluetooth Low Enaergy Exploerを起動し、「Feather nRF52840 Express」を見つけ、connectします。
　Notificatiopnをチェックすると、最新のデータを送ってくることが確認できます。

![f110.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/3391f86f-e2df-0205-c6d2-b4c72d6b58a0.png)

### 実数で送る
　UARTはテキストで二つの値を送りました。
　実数で送るプログラムを作ります。次の実数Characteristicを定義したクラスをSwitchBot.pyの名前で保存します。メインのcode.pyと同じトップのディレクトリにです。
このクラスの定義は、サンプルがなかったので、独自です。記述方法が正しいかわかりません。examplesにあったJSONの事例とAdafruitのソースを参考にしました。
　UUIDは全くの適当な値です。

```python
# SPDX-FileCopyrightText: 2020 Mark Raleson
# SPDX-License-Identifier: MIT
from adafruit_ble.uuid import VendorUUID
from adafruit_ble.services import Service
from adafruit_ble.characteristics import Characteristic
from adafruit_ble.characteristics.float import FloatCharacteristic

class SensorService(Service):

    uuid = VendorUUID("51ad213f-e568-4e35-84e4-67af89c79ef0")

    sensorsTheta = FloatCharacteristic(
        uuid=VendorUUID("e077bdec-f18b-4944-9e9e-8b3a815162b4"),
        properties=Characteristic.READ | Characteristic.NOTIFY,
    )

    sensorsVector = FloatCharacteristic(
        uuid=VendorUUID("528ff74b-fdb8-444c-9c64-3dd5da4135ae"),
        properties=Characteristic.READ | Characteristic.NOTIFY,
    )

    def __init__(self, service=None):
        super().__init__(service=service)
        self.connectable = True
```

　メインのプログラムcode.pyです。

```python
from board import *
import analogio
import time
import math
from adafruit_ble import BLERadio
from SwitchBot import SensorService
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement

pinX = analogio.AnalogIn(A0)
pinY = analogio.AnalogIn(A1)
NUM_SAMPLES = 20

ble = BLERadio()
sService = SensorService()
advertisement = ProvideServicesAdvertisement(sService)
ble.name = "Feather nRF52840 Express"

def readXY():
    analogDataX=[]
    analogDataY=[]
    for i in range(NUM_SAMPLES):
        analogDataX.append(pinX.value)
        analogDataY.append(pinY.value)
        time.sleep(0.02)
    analogDataX.sort()
    analogDataY.sort()
    meanX = sum(analogDataX[5:NUM_SAMPLES-5]) / float(len(analogDataX[5:NUM_SAMPLES-5]))
    meanY = sum(analogDataY[5:NUM_SAMPLES-5]) / float(len(analogDataY[5:NUM_SAMPLES-5]))
    #print(analogData0[5:NUM_SAMPLES-5])
    x = 2*(((meanX-16300)/33100)-0.502)
    y = 2*(((meanY-15728)/31965)-0.5088)
    th = math.atan2(y,x)*180/3.141592
    Ve = math.sqrt(x*x + y*y)
    return th,Ve

while 1:
    print("Advertise services")
    ble.stop_advertising()  # you need to do this to stop any persistent old advertisement
    ble.start_advertising(advertisement)

    print("Waiting for connection...")
    while not ble.connected:
        pass
    ble.stop_advertising()
    print("Connected")
    while ble.connected:
        [th,Ve] = readXY()
        print('Theta ={:.4f} Vector ={:.2f}'.format(th,Ve))
        sService.sensorsTheta = th
        sService.sensorsVector = Ve
        time.sleep(0.2)

pinX.deinit()
pinY.deinit()
print("Disconnected")
```

　実行中の様子です。
![f201.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/fdee55ca-65fa-e86a-e5d9-fdee99dbfb18.png)

　オン・セミコンダクターのRSL10 Bluetooth Low Enaergy Exploerを使ってconnectし、Characteristicの情報を見ます。セントラルの立場です。
　4バイトの実数が届いています。
![f202.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/88f03a91-9827-1a05-7fb4-f793d1a499fc.png)