エレキ素人が何か考える（その14）＆FreeRTOS理解:番外編3：Dフリップフロップ

# Dフリップフロップを制御
秋月電子で購入した「[２回路Ｄフリップフロップ　ＴＣ７４ＨＣ７](https://akizukidenshi.com/catalog/g/gI-10879/)」をArduinoから制御する。たまたまであるが、FreeRTOSを使うことになった。

## データシート情報
[ここ](https://akizukidenshi.com/download/ds/toshiba/TC74HC74AP_datasheet_ja_20140301.pdf)にデータシートあり。
### ピンアサイン
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/2d4f4748-be8d-b355-0c1e-ba626025a9e5.png", width=50%>
### 真理値表
![TruthTable.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/7fdfd250-ab30-2bb4-0cc8-b9af66b8834f.png)
この表を確認する。

## 準備
### Arduinoとの配線
ここでは、2nd D Flip Flopを利用。
|Arduino|D Flipflop|
|:-:|:-:|
|2|2Q|
|4|2CLR~|
|5|2PR~|
|6|2CLK|
|7|2D|

ソフトウェアで「Q」の変化を検知（インターラプトルーチン利用）したいため、「Q」を、Arduinoのピン2にアサインした。（Arduino Unoでのインターラプトルーチン利用は、ピン2及びピン3に限られる。）

下記のような状況。中央に見えるものが、D Flip Flopである。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/97b5e38e-6b2f-f4fb-5825-06bce2c5c842.png", width=70%>

### ソースコード
PC（シリアル）から入力した数値により、入力ピンを制御する。
```c++
#include <Arduino_FreeRTOS.h>

#define PIN_Q   2     // in addition, for Arduino UNO interrupt
#define PIN_CLR 4
#define PIN_PR  5
#define PIN_CLK 6
#define PIN_D   7
#define DELAY   2000  // for clock (approximately 2000 ms)

TaskHandle_t xClk, xInput, xMonitor;  // for task
uint8_t cur_d, cur_clr, cur_pr;       // current value
volatile bool flag;                   // for interrupt routine
char *msg[] = {                       // command from PC
  "< Set LOW to D >", "< Set HIGH to D >", "< Set LOW to CLR~ >", "< Set HIGH to CLR~ >",
  "< Set LOW to PR~ >", "< Set HIGH to PR~ >", "< Read Q value >"};
```
- ピンアサイン
- クロック用変数（Delay）、ここではおよそ4秒で1サイクル
- タスク用変数、現在の入力信号値（D, CLR~, PR~）を保持する変数
- インターラプトごとに反転するフラグ
- PCからのシリアル入力数値に対応する名称（コマンド）

```c++
uint8_t readQvalue() {
  uint8_t val = digitalRead(PIN_Q);
  return(val);
}

void setDvalue(uint8_t val) {
  cur_d = (val == 0)? LOW: HIGH;
  digitalWrite(PIN_D, cur_d);
}

void setClear(uint8_t val) {
  cur_clr = (val == 2)? LOW: HIGH;
  digitalWrite(PIN_CLR, cur_clr);
}

void setPrset(uint8_t val) {
  cur_pr = (val == 4)? LOW: HIGH;
  digitalWrite(PIN_PR, cur_pr);
}
```
- readQvalue()：現在のQをread
- setDvalue()：Dへのセット及び値の保持
- setClear()：CLR~へのセット及び値の保持
- setPrset()：PR~へのセット及び値の保持

```c++
void detection() {
  flag = !flag;
}
```
インターラプトルーチンであり、フラグを反転させる。

```c++
void TaskClock(void *arg) {
  pinMode(PIN_CLK, OUTPUT);
  for (;;) {
    digitalWrite(PIN_CLK, HIGH);
    Serial.println("***** Clock Rising *****");
    vTaskDelay(DELAY/portTICK_PERIOD_MS);
    digitalWrite(PIN_CLK, LOW);
    vTaskDelay(DELAY/portTICK_PERIOD_MS);
  }
}
```
CLK入力用タスク。CLK HIGH、DELAY sleep、CLK LOW、DELAY Sleepを繰り返す。1サイクルは、およそ、2 x DELAY msである。クロックの立ち上がりで、D値を読み取るので、長いサイクル（周期）の方が状況を把握しやすい。そのため、FreeRTOSを利用した。

```c++
void TaskInput(void *arg) {
  char buf[32];
  for (;;) {
    if (Serial.available() > 0) {
      String data = Serial.readStringUntil('\n');
      uint8_t val = data.toInt();
      if (val > 5) {
        val = 6;
      }
      Serial.println(msg[val]);
      if ((val == 0) || (val == 1)) {         // Set LOW(val=0) or HIGH(val=1) to D
        setDvalue(val);
      } else if ((val == 2) || (val == 3)) {  // Set LOW(val=2) or HIGH(val=3) to Clear~
        setClear(val);
      } else if ((val == 4) || (val == 5)) {  // Set LOW(val=4) or HIGH(val=5) to Prset~
        setPrset(val);            
      } else {                                // Read Q value
        sprintf(buf, "Q:%s", ((readQvalue() == 0)? "LOW": "HIGH"));
        Serial.println(buf);
      }
      sprintf(buf, "D:%s  CLR~:%s  PR~:%s\n", ((cur_d == LOW)? "LOW": "HIGH"),
        ((cur_clr == LOW)? "LOW": "HIGH"), ((cur_pr == LOW)? "LOW": "HIGH"));
      Serial.println(buf);
    }
    vTaskDelay(1); 
  }
}
```
シリアル入力タスクである。
- 0, 1：Dへの入力（LOW, HIGH）
- 2, 3：CLR~への入力（LOW, HIGH）
- 4, 5：PR~への入力（LOW, HIGH）
- それ以外の数値：Qを読み込み（入力値のエラー処理は未実施）
- デバッグ用出力（各信号）

```c++
void TaskMonitor(void *arg) {
  bool current = flag;
  for (;;) {
    if (current != flag) {
      char buf[32];
      Serial.println("< Q value change >");
      sprintf(buf, "Q:%s", ((readQvalue() == 0)? "LOW": "HIGH"));
      Serial.println(buf);
      sprintf(buf, "D:%s  CLR~:%s  PR~:%s\n", ((cur_d == LOW)? "LOW": "HIGH"),
        ((cur_clr == LOW)? "LOW": "HIGH"), ((cur_pr == LOW)? "LOW": "HIGH"));
      Serial.println(buf);  
      current = flag;
    }
    vTaskDelay(1);
  }
}
```
インターラプト検知時のタスク（インターラプトルーチンは短時間で処理するのが基本のため）。デバッグ用出力（各信号）実施。

```c++
void setup() {
  Serial.begin(9600);
  pinMode(PIN_CLR, OUTPUT);
  pinMode(PIN_PR, OUTPUT);
  pinMode(PIN_D, OUTPUT);
  cur_clr = HIGH; digitalWrite(PIN_CLR, cur_clr);
  cur_pr = HIGH; digitalWrite(PIN_PR, cur_pr);
  cur_d = HIGH; digitalWrite(PIN_D, cur_d);
  
  pinMode(PIN_Q, INPUT);  // INPUT_PULLUP
  flag = true;
  attachInterrupt(digitalPinToInterrupt(PIN_Q), detection, CHANGE);

  xTaskCreate(TaskClock, "TaskClock", 128, NULL, 1, &xClk);
  xTaskCreate(TaskInput, "TaskInput", 128, NULL, 1, &xInput);
  xTaskCreate(TaskMonitor, "TaskMonitor", 128, NULL, 1, &xMonitor);
    
  vTaskStartScheduler();
}

void loop() {
  // Nothing
}
```
Arduinoお決まりの部分。
- 各種ピン（信号）のモード設定及び初期化
- インターラプトルーチンの設定
- タスク起動およびスケジューリング

### 実験
以下、シリアル表示（ログ）。下記点がポイント。
- 「***** Clock Rising *****」は定期的に表示される
- 「< Set LOW to X >」および「< Read Q value >」はPCから指示されて表示される
- 「< Q value change >」は「Q」の値が変化したときのみ表示される


```
14:46:45.092 -> ***** Clock Rising *****
14:46:48.115 -> < Read Q value >
14:46:48.161 -> Q:HIGH
14:46:48.161 -> D:HIGH  CLR~:HIGH  PR~:HIGH
14:46:48.208 -> 
14:46:49.472 -> ***** Clock Rising *****
14:46:51.529 -> < Set LOW to D >
14:46:51.529 -> D:LOW  CLR~:HIGH  PR~:HIGH
14:46:51.575 -> 
14:46:52.459 -> < Read Q value >
14:46:52.459 -> Q:HIGH
14:46:52.459 -> D:LOW  CLR~:HIGH  PR~:HIGH
14:46:52.506 -> 
14:46:53.814 -> ***** Clock Rising *****
14:46:53.862 -> < Q value change >
14:46:53.862 -> Q:LOW
14:46:53.909 -> D:LOW  CLR~:HIGH  PR~:HIGH
```
- 初期状態のQはHIGH。
- DをLOWにセット。
- 直後にQを読むとまだHIGHのまま。
- Clockの立ち上がりで、Qが変化し、LOWとなる。期待どおり。
- Clockの1サイクルの実測値は、約4.4秒程度。各種オーバーヘッドあり。

```
14:47:06.924 -> ***** Clock Rising *****
14:47:06.971 -> < Q value change >
14:47:06.971 -> Q:HIGH
14:47:06.971 -> D:HIGH  CLR~:HIGH  PR~:HIGH
14:47:07.019 -> 
14:47:11.276 -> ***** Clock Rising *****
14:47:12.999 -> < Set LOW to CLR~ >
14:47:12.999 -> D:HIGH  CLR~:LOW  PR~:HIGH
14:47:13.046 -> 
14:47:13.046 -> < Q value change >
14:47:13.093 -> Q:LOW
14:47:13.093 -> D:HIGH  CLR~:LOW  PR~:HIGH
```
- QがHIGHの状態。
- CLR~をLOWにセット。
- 即、QがLOWになる（Clockの立ち上がりを待たずに）。真理値表を確認すると、これも期待どおり。
- 下記に続く。

```
14:47:21.085 -> < Set HIGH to CLR~ >
14:47:21.132 -> D:HIGH  CLR~:HIGH  PR~:HIGH
14:47:21.132 -> 
14:47:24.391 -> ***** Clock Rising *****
14:47:24.391 -> < Q value change >
14:47:24.437 -> Q:HIGH
14:47:24.437 -> D:HIGH  CLR~:HIGH  PR~:HIGH
```
- QがLOWの状態で、CLR~をLOWからHIGHに戻す。
- ここでは、即QがHIGHにならず。
- Clockの立ち上がりで、Qが変化し、HIGHとなる。

```
14:47:44.738 -> < Set LOW to D >
14:47:44.738 -> D:LOW  CLR~:HIGH  PR~:HIGH
14:47:44.785 -> 
14:47:46.189 -> ***** Clock Rising *****
14:47:46.235 -> < Q value change >
14:47:46.235 -> Q:LOW
14:47:46.235 -> D:LOW  CLR~:HIGH  PR~:HIGH
14:47:46.281 -> 
14:47:50.539 -> ***** Clock Rising *****
14:47:52.644 -> < Set LOW to PR~ >
14:47:52.691 -> D:LOW  CLR~:HIGH  PR~:LOW
14:47:52.691 -> 
14:47:52.691 -> < Q value change >
14:47:52.738 -> Q:HIGH
14:47:52.738 -> D:LOW  CLR~:HIGH  PR~:LOW
14:47:52.738 -> 
14:47:54.929 -> ***** Clock Rising *****
14:47:59.270 -> ***** Clock Rising *****
14:48:00.725 -> < Set HIGH to PR~ >
14:48:00.771 -> D:LOW  CLR~:HIGH  PR~:HIGH
14:48:00.771 -> 
14:48:03.660 -> ***** Clock Rising *****
14:48:03.660 -> < Q value change >
14:48:03.708 -> Q:LOW
14:48:03.708 -> D:LOW  CLR~:HIGH  PR~:HIGH
```

CLR~やPR~のLOW/HIGHは、LOWの場合Qが即変化、HIGHの場合Clockの立ち上がりで変化となる。真理値表とおり。

すべての状態変化の組み合わせの結果については略。

## おわりに
単純ではあるが、これがコンピューターのハードウェアの基本らしい。