必要なリソース
適当なUARTをひとつ、キューとセマフォもひとつづつ。割り込みを少々添えて。ピンは1本だけ。ストロングプルアップを実装。
使い方
CubeMXで使用するUARTをSingle Wire (Half-Duplex)
で初期化する。NVICをEnableに設定する。
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を使った例。
ロジアナのキャプチャ
リセットから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を生成するような使い方は想定していない。
ソース
ヘッダ
#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
ソース
#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
を使う場合の例
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をパラサイトパワーで使う場合は、変換中に電力が足りているかオシロスコープで確認する必要がある(マルチテスターやロジックアナライザでは確認できないのでオシロが必要)。このあたり気が向いたら評価したい。