STM32F4で1Wireを使う


## 必要なリソース

　適当なUARTをひとつ、キューとセマフォもひとつづつ。割り込みを少々添えて。ピンは1本だけ。ストロングプルアップを実装。

## 使い方

CubeMXで使用するUARTを`Single Wire (Half-Duplex)`で初期化する。NVICをEnableに設定する。

```c++
HAL_NVIC_SetPriority(EXTI2_IRQn, 3, 0);
__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);
Protocol::One_wire one_wire(one_wire_tx_semaphoreHandle, one_wire_rx_queueHandle,
                            huart2, one_wire_GPIO_Port, one_wire_Pin, GPIO_AF7_USART2);

const uint32_t interval = 5000;
osDelay(interval - HAL_GetTick() % interval);
uint32_t prev_wake = HAL_GetTick();

for (uint16_t i = 0; i < 12; i++)
{
    uint8_t data[9] = {}; // DS18B20 scratchpad and CRC

    if (false)
    {
    }
    else if (printf("%7.3f %3hu  DS18B20 test  ", HAL_GetTick() * 1e-3, i + 1),
                fflush(stdout),
                false == one_wire.bus_reset())
    {
        printf("1st bus reset error  ");
    }
    else if (one_wire.transmit_byte(0xCC),       // skip ROM command
                one_wire.transmit_byte(0x44, true), // convert temperature, use strong pullup
                osDelay(1000),
                false)
    {
    }
    else if (false == one_wire.bus_reset())
    {
        printf("2nd bus reset error  ");
    }
    else if (one_wire.transmit_byte(0xCC), // skip ROM command
                one_wire.transmit_byte(0xBE), // read scratchpad
                false == one_wire.block_read(data))
    {
        printf("block read error  ");
    }
    else if (printf("%+5.1f degC  ", static_cast<int16_t>(data[0] | data[1] << 8) / 16.0),
                false)
    {
    }

    printf("\n");

    osDelayUntil(&prev_wake, interval);
}
```

ストロングプルアップの実装にNVICを使うので、予め優先度を設定しておく。EXTI ISRからはFreeRTOSのリソースは使わない。タイミング要求がシビアなので、上位の割り込みがワーストで10usc未満になるように注意すること。
UARTのRXNE割り込みも有効化しておく。

その後、1Wire(`Protocol::One_wire`)のインスタンスを作る。この際にセマフォ、キュー、UARTハンドラ、GPIOポート、GPIOピン、オルタネート、の情報を渡す。
ポート、ピン、オルタネートの情報はusart.cの`HAL_UART_MspInit`に生成された定数をコピーしてくる。

`Protocol::One_wire`では1wireの最低限のプロトコルしか実装していないので、温度センサとかを使いたい場合は自前で処理する。今回はDS18B20を使った例。

### ロジアナのキャプチャ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264452/e5c7ca22-154d-0d67-2b62-f5808155cd0e.png)

リセットから2バイト送信と9バイト受信で10msecくらいかかる。
一番上(`core_busy`)がHighの時は処理中を示している。セマフォとキューを使っているので通信中もリソース専有にはならない。
一番タイミングがシビアなストロングプルアップはEXTI ISRで完結しているし、それ以外はUARTでタイミングを作っているので、ソフトウェア的にはあまりタイミングは重要ではない。One_wireを処理するタスクの優先度は低めでも問題ない。

## public関数

### `One_wire(SemaphoreHandle_t tx_semaphore, QueueHandle_t rx_queue, UART_HandleTypeDef &UART, GPIO_TypeDef *port, uint16_t pin, uint8_t alternate)`

コンストラクタ(前述の例を参照)。

### `bool bus_reset(void)`

リセットパルスを送信する。リセットパルスが検出できない場合(バスエラー)、プレゼンスパルスが検出できない場合(デバイスが接続されていない)、のいずれかの場合は`false`となる。デバイスの検出ができた場合に`true`を返す。

### `void transmit_byte(uint8_t tx_data, bool strong_pullup_after_last_bit_transmit = false)`

第一引数で指定した1オクテットを送信する。第二引数が`true`なら最終ビットを送信したあとにストロングプルアップを有効化する(後述)。

### `uint8_t receive_byte(uint8_t tx_data = 0xFF, bool strong_pullup_after_last_bit_transmit = false)`

`transmit_byte`と同じ(transmitはreceiveのラッパー)。

### `template <uint16_t N> bool block_read(uint8_t (&data)[N])`

複数オクテットをまとめて受信する。受信後にCRCの計算を行い、結果が0(CRCが正常)の場合は`true`を返す。

### `template <uint16_t N> static uint8_t calc_CRC(const uint8_t (&data)[N])`

与えられた配列のCRCを計算して返す。
正しいCRCを含むデータの場合は`0x00`が帰る。
プログラムでCRCを生成するような使い方は想定していない。

## ソース

### ヘッダ

```c++
#ifndef Protocol__One_wire__H
#define Protocol__One_wire__H

#include <stm32f4xx.h>
#include <cmsis_os.h>

namespace Protocol
{
class One_wire
{
public:
    One_wire(SemaphoreHandle_t tx_semaphore,
             QueueHandle_t rx_queue,
             UART_HandleTypeDef &UART,
             GPIO_TypeDef *port,
             uint16_t pin,
             uint8_t alternate);

    bool bus_reset(void);

    void transmit_byte(uint8_t tx_data, bool strong_pullup_after_last_bit_transmit = false)
    {
        receive_byte(tx_data, strong_pullup_after_last_bit_transmit);
    }

    uint8_t receive_byte(uint8_t tx_data = 0xFF, bool strong_pullup_after_last_bit_transmit = false);

    template <uint16_t N>
    bool block_read(uint8_t (&data)[N])
    {
        for (uint8_t &a : data)
        {
            a = receive_byte();
        }

        return (0 == calc_CRC(data));
    }

    template <uint16_t N>
    static uint8_t calc_CRC(const uint8_t (&data)[N])
    {
        uint8_t crcsr = 0;

        for (uint8_t dat : data)
        {
            crcsr = ((dat ^ crcsr) & 1) != 0 ? (((crcsr ^ 0x18) >> 1) | 0x80) : (crcsr >> 1);
            dat >>= 1;
            crcsr = ((dat ^ crcsr) & 1) != 0 ? (((crcsr ^ 0x18) >> 1) | 0x80) : (crcsr >> 1);
            dat >>= 1;
            crcsr = ((dat ^ crcsr) & 1) != 0 ? (((crcsr ^ 0x18) >> 1) | 0x80) : (crcsr >> 1);
            dat >>= 1;
            crcsr = ((dat ^ crcsr) & 1) != 0 ? (((crcsr ^ 0x18) >> 1) | 0x80) : (crcsr >> 1);
            dat >>= 1;
            crcsr = ((dat ^ crcsr) & 1) != 0 ? (((crcsr ^ 0x18) >> 1) | 0x80) : (crcsr >> 1);
            dat >>= 1;
            crcsr = ((dat ^ crcsr) & 1) != 0 ? (((crcsr ^ 0x18) >> 1) | 0x80) : (crcsr >> 1);
            dat >>= 1;
            crcsr = ((dat ^ crcsr) & 1) != 0 ? (((crcsr ^ 0x18) >> 1) | 0x80) : (crcsr >> 1);
            dat >>= 1;
            crcsr = ((dat ^ crcsr) & 1) != 0 ? (((crcsr ^ 0x18) >> 1) | 0x80) : (crcsr >> 1);
        }

        return (crcsr);
    }

protected:
private:
    static constexpr uint16_t dequeue_timeout = 2;
    static constexpr uint16_t semaphore_take_timeout = 2;

    const SemaphoreHandle_t tx_sem;
    const QueueHandle_t rx_queue;
    UART_HandleTypeDef *const UART;
    GPIO_TypeDef *const port;
    const uint16_t pin;
    const uint8_t position;
    const uint8_t alternate;
    const IRQn_Type EXTI_IRQn;

    enum class Baudrates
    {
        reset_pulse = 18750,
        bitstream = 100 * 1000,
    };

    void transmit_bit(bool bit);

    bool receive_bit(uint8_t &data)
    {
        return (pdPASS == xQueueReceive(rx_queue, &data, dequeue_timeout));
    }

    void pin_mode_set(bool enable_EXTI);

    void change_baudrate(Baudrates baudrate);

    static uint8_t get_number_of_training_zero(uint32_t data);
    static IRQn_Type get_EXT_IRQn(uint8_t position);
};
} // namespace Protocol

#endif
```

### ソース

```c++
#include <Protocol/One_wire.h>

Protocol::One_wire::One_wire(const SemaphoreHandle_t tx_semaphore,
                             const QueueHandle_t rx_queue,
                             UART_HandleTypeDef &UART,
                             GPIO_TypeDef *const port,
                             const uint16_t pin,
                             const uint8_t alternate)
    : tx_sem(tx_semaphore),
      rx_queue(rx_queue),
      UART(&UART),
      port(port),
      pin(pin),
      position(get_number_of_training_zero(pin)),
      alternate(alternate),
      EXTI_IRQn(get_EXT_IRQn(get_number_of_training_zero(pin)))
{
}

bool Protocol::One_wire::bus_reset(void)
{
    xQueueReset(rx_queue);

    pin_mode_set(false);
    change_baudrate(Baudrates::reset_pulse);

    transmit_bit(false); // reset pulse

    uint8_t tmp = 0;

    bool status = false;

    if (0)
    {
    }
    else if (pdPASS != xQueueReceive(rx_queue, &tmp, dequeue_timeout))
    { // bus error
    }
    else if (pdPASS != xQueueReceive(rx_queue, &tmp, dequeue_timeout))
    { // device not detected
    }
    else
    {
        status = true;
    }

    change_baudrate(Baudrates::bitstream);

    return (status);
}

uint8_t Protocol::One_wire::receive_byte(const uint8_t tx_data, bool strong_pullup_after_last_bit_transmit)
{
    transmit_bit((tx_data >> 0 & 1) != 0);
    transmit_bit((tx_data >> 1 & 1) != 0);
    transmit_bit((tx_data >> 2 & 1) != 0);
    transmit_bit((tx_data >> 3 & 1) != 0);
    transmit_bit((tx_data >> 4 & 1) != 0);
    transmit_bit((tx_data >> 5 & 1) != 0);
    transmit_bit((tx_data >> 6 & 1) != 0);
    transmit_bit((tx_data >> 7 & 1) != 0);

    if (strong_pullup_after_last_bit_transmit)
    {
        while (!__HAL_UART_GET_FLAG(UART, UART_FLAG_TXE))
        {
        }

        pin_mode_set(true);
    }

    uint8_t buff[8] = {};

    const bool status = receive_bit(buff[0]) &&
                        receive_bit(buff[1]) &&
                        receive_bit(buff[2]) &&
                        receive_bit(buff[3]) &&
                        receive_bit(buff[4]) &&
                        receive_bit(buff[5]) &&
                        receive_bit(buff[6]) &&
                        receive_bit(buff[7]);

    const uint8_t rx_data =
        !status
            ? 0
            : ((buff[0] == 0xFF ? 0x01 : 0) |
               (buff[1] == 0xFF ? 0x02 : 0) |
               (buff[2] == 0xFF ? 0x04 : 0) |
               (buff[3] == 0xFF ? 0x08 : 0) |
               (buff[4] == 0xFF ? 0x10 : 0) |
               (buff[5] == 0xFF ? 0x20 : 0) |
               (buff[6] == 0xFF ? 0x40 : 0) |
               (buff[7] == 0xFF ? 0x80 : 0));

    return (rx_data);
}

void Protocol::One_wire::transmit_bit(bool bit)
{
    if (!__HAL_UART_GET_FLAG(UART, UART_FLAG_TXE))
    {
        __HAL_UART_ENABLE_IT(UART, UART_IT_TXE);
        xSemaphoreTake(tx_sem, semaphore_take_timeout);
    }

    while (!__HAL_UART_GET_FLAG(UART, UART_FLAG_TXE))
    {
    }

    UART->Instance->DR = bit ? 0xFF : 0x00;
}

void Protocol::One_wire::pin_mode_set(const bool enable_EXTI)
{
    GPIO_InitTypeDef s = {
        .Pin = pin,
        .Mode = GPIO_MODE_AF_OD,
        .Pull = GPIO_PULLUP,
        .Speed = GPIO_SPEED_FREQ_HIGH,
        .Alternate = alternate,
    };

    HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET);
    HAL_GPIO_Init(port, &s);

    if (enable_EXTI)
    {
        __HAL_RCC_SYSCFG_CLK_ENABLE();

        const uint32_t iocurrent = pin & (1 << position);

        {
            uint32_t temp = SYSCFG->EXTICR[position >> 2U];
            temp &= ~(0x0FU << (4U * (position & 0x03U)));
            temp |= GPIO_GET_INDEX(port) << (4U * (position & 0x03U));
            SYSCFG->EXTICR[position >> 2U] = temp;
        }

        EXTI->IMR |= iocurrent;   // enable interrupt
        EXTI->EMR &= ~iocurrent;  // disable event
        EXTI->RTSR |= iocurrent;  // enable rising trigger
        EXTI->FTSR &= ~iocurrent; // disable faling trigger

        HAL_NVIC_EnableIRQ(EXTI_IRQn);
    }
    else
    {
        const uint32_t iocurrent = pin & (1 << position);

        EXTI->IMR &= ~iocurrent; // disable interrupt
        EXTI->EMR &= ~iocurrent; // disable event
    }
}

void Protocol::One_wire::change_baudrate(const Baudrates baudrate)
{
    // code copy from UART_SetConfig() @ stm32f4xx_hal_uart.c

    UART->Init.BaudRate = static_cast<uint32_t>(baudrate);

    if (UART->Init.OverSampling == UART_OVERSAMPLING_8)
    {
#if defined(USART6)
        if ((UART->Instance == USART1) || (UART->Instance == USART6))
        {
            UART->Instance->BRR = UART_BRR_SAMPLING8(HAL_RCC_GetPCLK2Freq(), UART->Init.BaudRate);
        }
#else
        if (UART->Instance == USART1)
        {
            UART->Instance->BRR = UART_BRR_SAMPLING8(HAL_RCC_GetPCLK2Freq(), UART->Init.BaudRate);
        }
#endif
        else
        {
            UART->Instance->BRR = UART_BRR_SAMPLING8(HAL_RCC_GetPCLK1Freq(), UART->Init.BaudRate);
        }
    }
    else
    {
#if defined(USART6)
        if ((UART->Instance == USART1) || (UART->Instance == USART6))
        {
            UART->Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK2Freq(), UART->Init.BaudRate);
        }
#else
        if (UART->Instance == USART1)
        {
            UART->Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK2Freq(), UART->Init.BaudRate);
        }
#endif
        else
        {
            UART->Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK1Freq(), UART->Init.BaudRate);
        }
    }
}

uint8_t Protocol::One_wire::get_number_of_training_zero(const uint32_t data)
{
    for (uint8_t i = 0;; i++)
    {
        if (data >> i & 1)
        {
            return (i);
        }
    }
}

IRQn_Type Protocol::One_wire::get_EXT_IRQn(const uint8_t position)
{
    switch (position)
    {
    case 0:
        return (EXTI0_IRQn);

    case 1:
        return (EXTI1_IRQn);

    case 2:
        return (EXTI2_IRQn);

    case 3:
        return (EXTI3_IRQn);

    case 4:
        return (EXTI4_IRQn);

    default:
        return (position <= 9 ? EXTI9_5_IRQn : EXTI15_10_IRQn);
    }
}
```

### 割り込み処理

#### UART割り込み

`USART2`を使う場合の例

```c++
void USART2_IRQHandler(void)
{
    /* USER CODE BEGIN USART2_IRQn 0 */

    if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE))
    {
        uint8_t msg = huart2.Instance->DR;

        extern osMessageQId one_wire_rx_queueHandle;
        portBASE_TYPE task_woken = pdFALSE;
        xQueueSendFromISR(one_wire_rx_queueHandle, &msg, &task_woken);
        portEND_SWITCHING_ISR(task_woken);
        return;
    }

    if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TXE))
    {
        __HAL_UART_DISABLE_IT(&huart2, UART_IT_TXE);

        extern osSemaphoreId one_wire_tx_semaphoreHandle;
        portBASE_TYPE task_woken = pdFALSE;
        xSemaphoreGiveFromISR(one_wire_tx_semaphoreHandle, &task_woken);
        portEND_SWITCHING_ISR(task_woken);
        return;
    }

    /* USER CODE END USART2_IRQn 0 */
    HAL_UART_IRQHandler(&huart2);
    /* USER CODE BEGIN USART2_IRQn 1 */

    /* USER CODE END USART2_IRQn 1 */
}
```

#### EXTI割り込み

`EXTI2`(`GPIOx-2`)を使う場合の例。

```
void EXTI2_IRQHandler(void)
{
    if (__HAL_GPIO_EXTI_GET_IT(EXTI_LINE_2))
    {
        GPIO_InitTypeDef s = {
            .Pin = one_wire_Pin,
            .Mode = GPIO_MODE_OUTPUT_PP,
            .Pull = GPIO_PULLUP,
            .Speed = GPIO_SPEED_FREQ_HIGH,
            .Alternate = 0,
        };
        HAL_GPIO_Init(one_wire_GPIO_Port, &s);

        __HAL_GPIO_EXTI_CLEAR_IT(EXTI_LINE_2);
        HAL_NVIC_DisableIRQ(EXTI2_IRQn);

        return;
    }
}
```

## その他

GPIOをオルタネートで使いながらEXTIを設定するのが面倒(HALでは初期化できないので直接レジスタを叩いている)。
UARTのボーレートを変更するのは、UART自体の設定を変える関数を呼ばなきゃいけないので、ボーレート周りだけコピーしてきている。#defineいっぱい。。。

1ビット転送をpublicに作ってないので、オクテット転送しかできない。複数デバイスを使いたいときに困るかも。

DS18B20はストロングプルアップが足りない状態でパラサイトパワーで駆動すると、微妙に誤った温度を返す。気が付かない程度に温度が低く出ていたりするので、気がつくのに時間がかかる。DS18B20をパラサイトパワーで使う場合は、変換中に電力が足りているかオシロスコープで確認する必要がある(マルチテスターやロジックアナライザでは確認できないのでオシロが必要)。このあたり気が向いたら評価したい。