ぱぷりかの機械系技術ノート

なにかのお役に立てばうれしいです

アンカーボルト(あと施工アンカー)について

基礎と機械をボルトで固定するためには一般にアンカーボルトを使用する.

アンカーボルトには基礎施工時にあらかじめアンカーボルトを埋め込んでおく『埋め込みアンカー』と 基礎施工後にアンカーボルトを基礎に固定する『あと施工アンカー』がある.

一般に埋め込みアンカーの方が引き抜き強度は大きいが, あと施工アンカーの方が施工に手間がかからず, 基礎打設前までにアンカーの位置を決定しておく必要がない. (もちろん決定しておくに越したことはないが)

そのようなわけで特別な強度を要求されない場合にはあと施工アンカーを使用する。

私のグループでは次のあと施工アンカーボルトを使用している

  • 箱抜きアンカー
  • ケミカルアンカー
  • オールアンカー

この3種類である.それぞれどのような特徴があるのかを書く.

箱抜きアンカー

長所

鉄筋にあたってアンカーが入らないということはない

短所

  • あらかじめ コンクリートに穴を開けないといけない
  • 埋め戻しに手間がかかる

ケミカルアンカー

長所

  • メーカーによって強度が保証されているので、強度計算がしやすい
  • 同じ径のオールアンカーより引っ張り強度が大きい

## 短所 - オールアンカーより施工に手間がかかる - 基礎穿孔時に鉄筋に当たるときがある

オールアンカー

長所

他のアンカーに比べると施工が楽(穴を開けてアンカーを入れてハンマーで叩けば良い)

## 短所 - ケミカルアンカーに比べると強度が小さい - 基礎穿孔時に鉄筋に当たるときがある

TWELITEとArduinoを使った測定データの収集について8(Arduino用ソースコード)

前回のハードウェア構成に対するArduinoのスケッチについて考える.

このブログ上ではID=0d42のエンドデバイスのスケッチのみ示し,その他のスケッチはGitHubに上げることにする(こちら). スケッチ中に説明が入っているので,やっていることはすぐわかると思う. 中継デバイスでもほとんど同じコードが利用できるようになっている. なお,『■■』の箇所はデバイスごとに見直しが必要な箇所である.

enddevice_0d42.ino

メインとなるスケッチ.関数acquire内のコードを書き換えることで,様々な測定対象に対応できる.

#include <SoftwareSerial.h>
#include "peripheral.h"
#include "handler.h"

static const unsigned long MeasInterval = 3000;  // ■■測定間隔
SoftwareSerial mySerial(RX_PIN, TX_PIN);
static unsigned long previousMillis = 0;
static char rxMessage[RX_HEADER_LEN + MAX_CONTENT_LEN + 3] = "";
static int rxMsgLength;

void acquire(int *measValue);
void transmit(const int *measValue);
bool parseMessage(MessageHandler *pHandler, const char *rxMessage);

RxMsgHandler ignoreHandler = {{handleIgnore}, 0, 0};
RxMsgHandler normalMsgHandler = {{handleNormalMsg}, NORMAL_ID, MAX_CONTENT_LEN};
ChainedHandler normalMsgChain = {{isHandled}, &normalMsgHandler.base, NULL};
ChainedHandler ignoreChain = {{isHandled}, &ignoreHandler.base, &normalMsgChain.base};

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
  while (!Serial) yield();
  mySerial.begin(9600);
}

void loop() {
  // 一定時間おきにデータを測定して,送信するための処理
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= MeasInterval) {
    int measValue[NUM_OF_MEAS_ITEMS];
    acquire(measValue);
    transmit(measValue);
    previousMillis = currentMillis;
  }

  //データを受信した場合の処理
  while (mySerial.available()) {
    char c = mySerial.read();
    
    // 受信した文字が改行コードLFの場合には受信した文字列を解析する
    // LFとCR以外の場合には受信文字をrxMessageに蓄積する
    if (c == 10) {
      rxMessage[rxMsgLength] = '\0';
      Serial.print("RX: ");
      Serial.println(rxMessage);
      if (!parseMessage(&ignoreChain.base, rxMessage))
        Serial.println("Handler: None");
      rxMessage[0] = '\0';
      rxMsgLength = 0;
    } else if (c != 13) {
      rxMessage[rxMsgLength] = c;
      rxMsgLength++;
    }
  }
}

// ■■測定値を取得する.戻り値は整数とする
void acquire(int *measValue) {
  for (int i = 0; i < NUM_OF_MEAS_ITEMS; i++)
    measValue[i] = random(0, 10); 
}

// 測定値を文字列としてTWELITEへ送信する
void transmit(const int *measValue) {
  char txMessage[TX_HEADER_LEN + MAX_CONTENT_LEN + 3] = "";

  // 送信文字列のヘッダーの作成
  sprintf(txMessage, ":%02xA0%02xFF%02x",
          DOWNSTREAM_TWELITE, NORMAL_ID, THIS_TWELITE);

  // 送信文字列への測定値の追加
  for (int i = 0; i < NUM_OF_MEAS_ITEMS; i++)
    sprintf(txMessage + TX_HEADER_LEN + 2 + i * MEAS_VALUE_LEN,
            "%04x", measValue[i]);

  // 送信文字列へのChecksumの追加
  sprintf(txMessage + TX_HEADER_LEN + 2 + NUM_OF_MEAS_ITEMS * MEAS_VALUE_LEN,
          "%02x", getChecksumOf(txMessage));

  // 送信実行
  toUpperStr(txMessage);
  writeToSerial(txMessage);
}

// 受信メッセージに応じて,チェインオブレスポンシビリティパターンによる適切な処理を行う
// 処理ができた場合にはtrueを,処理ができなかった場合にはfalseを返す
bool parseMessage(MessageHandler *pHandler, const char *rxMessage) {
  RxContent rxContent;

  // 各種データをrxContentに格納する
  rxContent.message = rxMessage;
  sscanf(rxMessage + 1, "%2x", &rxContent.tweliteId);
  sscanf(rxMessage + 5, "%2x", &rxContent.responseId);
  rxContent.contentLength = strlen(rxMessage) - RX_HEADER_LEN - 2;

  // 受信メッセージの内容に応じて,適切に処理する
  return pHandler->handleMessage(pHandler, &rxContent);
}

peripheral.h

各種設定値を入力するためのヘッダファイル.

#ifndef T_SENSOR_UNIT
#define T_SENSOR_UNIT
#include "util.h"

extern SoftwareSerial mySerial;

// 応答IDの設定
typedef enum {
  NORMAL_ID = 80,  // 0x50
} ResponseId;

// メッセージの長さの設定
typedef enum {
  TX_HEADER_LEN = 9,     // 送信メッセージのヘッダー長
  RX_HEADER_LEN = 29,    // 受信メッセージのヘッダー長
  MEAS_VALUE_LEN = 4,    // 測定値の文字列長
  NUM_OF_MEAS_ITEMS = 2, // ■■測定値の個数
  MAX_CONTENT_LEN = 22,  // NUM_OF_MEAS_ITEMS*MEAS_VALUE_LEN+2
} MessageLength;

// LEDのピンの設定
typedef enum {
  SENSOR_PIN = 0,
  RX_PIN = 6,
  TX_PIN = 7,
  LED_PIN = 2,
} PinSetting;

// TWELITE論理IDの設定
typedef enum {
  THIS_TWELITE = 42,       // ■■このTWELITEの論理ID(10進数)
  DOWNSTREAM_TWELITE = 45, // ■■下流側TWELITEの論理ID(10進数)
  IGNORE_TWELITE = 219,    // 0xDB
} TweliteId;

// 受信メッセージを格納する構造体
typedef struct {
  const char *message; // 受信した文字列
  unsigned int tweliteId;       // 送信元のTWELITEの論理ID
  unsigned int responseId;      // 受信メッセージのヘッダーに含まれている応答ID
  int contentLength;   // HEADERとCHECKSUMを除いた文字列長
} RxContent;
#endif

handler.h

チェインオブレスポンシビリティパターンで使用する構造体等を宣言するためのヘッダファイル.

#ifndef HANDLER_H
#define HANDLER_H

// 引数にMessageHandler型のポインタとRxContent型のポインタをもち,
// bool型の変数を返す関数へのポインタを保持する構造体
typedef struct _MessageHandler {
  bool (* const handleMessage)(struct _MessageHandler *pThis, RxContent *pContent);
} MessageHandler;

// MessageHandlerとそれ以外に必要なデータを格納する構造体
// 擬似的にMessageHandlerを継承した構造体となっている
typedef struct {
  MessageHandler base;
  const unsigned int responseId;
  const int contentLength;
} RxMsgHandler;

// 処理の連鎖を格納するための構造体
// 擬似的にMessageHandlerを継承した構造体となっている
typedef struct {
  MessageHandler base;
  MessageHandler *pWrapped;
  MessageHandler *pNext;
} ChainedHandler;

// 受信メッセージの処理を試みて,処理ができた場合にはtrueを返す
// 処理がができなかった場合には、後続のHandlerを呼び出し,
// 後続のHandlerの戻り値をそのまま呼び出し元に返す
// 後続のHandlerがいない場合にはfalseを返す
bool isHandled(MessageHandler *pThis, RxContent *pContent);

// 不要な受信文字列を無視するHandler
bool handleIgnore(MessageHandler *pThis, RxContent *pContent);

// 通常の受信メッセージを下流側のデバイスに送信するHandler
bool handleNormalMsg(MessageHandler *pThis, RxContent *pContent);
#endif

handler.ino

チェインオブレスポンシビリティパターンで使用する関数を定義するためのスケッチ.

#include "handler.h"
#include "peripheral.h"

bool isHandled(MessageHandler *pHandler, RxContent *pContent) {
  ChainedHandler *pThis = (ChainedHandler *)pHandler;

  pHandler = pThis->pWrapped;
  if (pHandler->handleMessage(pHandler, pContent))
    return true;

  pHandler = pThis->pNext;
  if (pHandler == NULL)
    return false;
  
  return pHandler->handleMessage(pHandler, pContent);
}

bool handleIgnore(MessageHandler *pHandler, RxContent *pContent) {
  // 下記の条件の場合には即退出
  if (pContent->tweliteId != IGNORE_TWELITE
      && pContent->contentLength <= MAX_CONTENT_LEN)
      return false;

  // 何もせず終了
  Serial.println("Handler: Ignore");
  return true;
}

bool handleNormalMsg(MessageHandler *pHandler, RxContent *pContent) {
  RxMsgHandler *pThis = (RxMsgHandler *)pHandler;

  // 下記の条件の場合には即退出
  if (pContent->responseId != pThis->responseId)
      return false;
  if ((pContent->contentLength % MEAS_VALUE_LEN) != 2)
      return false;

  char txMessage[TX_HEADER_LEN + MAX_CONTENT_LEN + 3];
  sprintf(txMessage, ":%02xA0%02xFF", DOWNSTREAM_TWELITE, pThis->responseId);
  for (int i = 0; i < pContent->contentLength; i++)
    txMessage[i + TX_HEADER_LEN] = rxMessage[i + RX_HEADER_LEN];
  txMessage[TX_HEADER_LEN + pContent->contentLength] = '\0';
  sprintf(txMessage + (TX_HEADER_LEN + pContent->contentLength),
          "%02x", getChecksumOf(txMessage));
          
  toUpperStr(txMessage);
  writeToSerial(txMessage);
  
  Serial.println("Handler: NormalMessage");
  return true;
}

util.h

その他の関数を定義するためのヘッダファイル.

#ifndef UTIL_H
#define UTIL_H
#include "peripheral.h"

// チェックサムを計算するための関数
int getChecksumOf(const char *);

// 大文字に変換するための関数
void toUpperStr(const char *);

// 標準のシリアルポートとSoftwareSerialで擬似的に作成された
// シリアルポートの両方に書き込みを行うための関数
void writeToSerial(const char *);

#endif

util.ino

その他の関数を定義するためのスケッチ.

#include "util.h"

int getChecksumOf(const char *message) {
  char str[3];
  int checksum = 0;
  byte msgLength = strlen(message);
  for (int i = 1; i < msgLength; i = i + 2)
  {
    str[0] = message[i];
    str[1] = message[i + 1];
    str[2] = '\0';
    checksum += strtol(str, NULL, 16);
  }
  return 256 - (checksum % 256);
}

void toUpperStr(const char *message) {
  char *p = (char *)message;
  while (*p)
  { 
    *p = toupper(*p);
    p++;
  }
}

void writeToSerial(const char *txMessage) {
  digitalWrite(LED_PIN, HIGH);
  Serial.print("TX: ");
  Serial.println(txMessage);
  mySerial.println(txMessage);
  digitalWrite(LED_PIN, LOW);
}

コーディングに際して,花井志生著『モダンC言語プログラミング』(KADOKAWA,2013年)を参考にした.

TWELITEとArduinoを使った測定データの収集について7(中継デバイスを使用した場合)

次なる例ではエンドデバイスとセントラルデバイスの間に中継デバイスを設け,それを経由してデータを送信する.

また,エンドデバイスの数を2つとする.その片方のエンドデバイスにおいて,2種類の測定値を下流側のデバイスへ送信する.(前回の例では1つのエンドデバイスで1種類の測定値のみ取得し送信した)

さらに中継デバイスでも,なんらかのデータを取得し,その測定値を下流側のデバイスへ送信することにする.

構成を図示すると以下のようになる.

f:id:pearlite:20190618181117p:plain
中継デバイスを使用した場合

図中に示したように,今回は測定すること自体が目的ではないので,ID=0d41以外のデバイスにおいて,実際にはセンサーを使用しない.単にArduinoでランダムな数値を発生させ,それをダミーの測定値として取り扱う.

次に,この機器構成に対応する具体的な回路について検討する.理想的なものではないが,今回実際に試作してみた回路の回路図を示す.

f:id:pearlite:20190618182038p:plain
回路図

上図において,ArduinoにはArduino Pro MiniとArduino Nanoの2種類が混在している.TWELITEと使用電圧を合わせるという意味では,Arduino Pro Miniに統一した方が良い.

このような構成になっている理由は,当方の手元に,使っていないArduino Pro Miniが1つしかなかったため,そして,たまたまArduinoNanoが余っていたためである(実に消極的な理由です).

そのようなわけで理想的な構成ではないが,原理的には問題ないのでこのまま進めてみる.なお,レベル変換のために秋月電子FXMA108レベル変換モジュールを使用している.

また,本来であれば,各デバイスが近接して設置されることはまずないと思われるが,今回はテスト環境ということで1つのブレッドボード上に上記の回路を配置した.

次回でArduinoのスケッチをみていく.

TWELITEとArduinoを使った測定データの収集について6(これまでのまとめ)

ここまでの投稿 (その2その3その4その5)において, 下図のようなエンドデバイスで測定されたデータをセントラルデバイスに送信するシステムについて考えてみた.

f:id:pearlite:20190514212621p:plain
温度データ収集システム(再掲)

エンドデバイスにおいて,Arduinoがセンサーによる測定値を受け取り,それをシリアル通信でTWELITEへ渡す. TWELITEはAruinoから渡された文字列を子機(セントラルデバイス)のTWELITEへ送信する. セントラルデバイスはTWELITE STICKとWindows PCからなり,自作のプログラムによって受け取ったデータを 表示するようになっている.

このような構成にすることで,以下のような利点があると考えている.

  • TWELITEとセンサーの間のデータのやり取りをArduinoが仲介するために 様々なセンサーに対応可能
  • TWELITEはメーカー製のシリアル通信アプリを使用することにより,学習コストをほとんどかけずに使用することが可能
  • 今後みていくように,センサーのネットワークを複雑化した場合にも,経路を簡単に制御できる

この構成では単純化のため,中継デバイスを使用せず,エンドデバイスで測定したデータを そのままセントラルデバイスに転送した.エンドデバイスでの測定データは1種類のみだった.

次回以降でこれを拡張することを考えていきたい.

TWELITEとArduinoを使った測定データの収集について5(セントラルデバイス)

セントラルデバイスについては,受け取った情報を表示させるという機能のみ実装する.(今後機能を拡張していく予定です)

具体的には,セントラルデバイスはUSBポートにTWELITEスティックを差したWindows PCとする.

TWELITEスティックはシリアル通信アプリ(App_Uart)を書き込んでおき,インタラクティブモードで以下の設定を行っておく.

  • Device IDを40(0x28)とする (任意のID)
  • UART modeをアスキーモード(A)とする
  • その他の設定はデフォルトのままでOK

Windows PC上のアプリは以下のようにした.

f:id:pearlite:20190606090804p:plain

コードはこちら

ここはこうした方がいいなどありましたら,コメントよろしくお願い致しますm( )m

TWELITEとArduinoを使った測定データの収集について4(Arduino用ソースコード)

Arduinoに書き込むスケッチは以下のようにした.

#include <SoftwareSerial.h>
const unsigned long MeasInterval = 30000;  // 測定間隔

// 応答IDの設定
typedef enum
{
  NORMAL_ID = 80,
} ResponseId;

// メッセージの長さの設定
typedef enum
{
  TX_HEADER_LEN = 9,
  MAX_CONTENT_LEN = 22,  // 送信内容の最大文字長
  MEAS_VALUE_LEN = 4,  // 測定値の文字列長
} MessageLength;

// LEDのピンの設定
typedef enum
{
  SENSOR_PIN = 0,
  RX_PIN = 6,
  TX_PIN = 7,
  LED_PIN = 2,
} PinSetting;

// TWELITE論理IDの設定
typedef enum
{
  THIS_TWELITE = 41,  // 0x29
  DOWNSTREAM_TWELITE = 40,  // 0x28
} TweliteId;

SoftwareSerial mySerial(RX_PIN, TX_PIN);
unsigned long previousMillis = 0;

// プロトタイプ宣言
int acquire();
void transmit(int);
int getChecksumOf(const char *);
void toUpperStr(const char *);
void writeToSerial(const char *);

void setup()
{
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
  while (!Serial) yield();
  mySerial.begin(9600);
}

void loop()
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= MeasInterval)
  {
    int measValue = acquire();
    transmit(measValue);
    previousMillis = currentMillis;
  }
}

// 測定値を取得する.戻り値は整数とする
int acquire()
{
  int rawValue = analogRead(SENSOR_PIN); 
  long volt = map(rawValue, 0, 1023, 0, 3300);
  return ((float)volt - 600.0) / 10.0; 
}

// 測定値を文字列としてTWELITEへ送信する
void transmit(int measValue)
{
  char txMessage[TX_HEADER_LEN + MAX_CONTENT_LEN + 3] = "";
  sprintf(txMessage, ":%02xA0%02xFF%02x%04x",
          DOWNSTREAM_TWELITE, NORMAL_ID, THIS_TWELITE, measValue);
  sprintf(txMessage + (TX_HEADER_LEN + 2 + MEAS_VALUE_LEN * 1), "%02x",
          getChecksumOf(txMessage));
  toUpperStr(txMessage);
  writeToSerial(txMessage);
}

// チェックサムを計算する
int getChecksumOf(const char *message)
{
  char str[3];
  int checksum = 0;
  byte msgLength = strlen(message);
  for (int i = 1; i < msgLength; i = i + 2)
  {
    str[0] = message[i];
    str[1] = message[i + 1];
    str[2] = '\0';
    checksum += strtol(str, NULL, 16);
  }
  return 256 - (checksum % 256);
}

// 大文字に変換する
void toUpperStr(const char *message)
{
  char *p = (char *)message;
  while (*p)
  { 
    *p = toupper(*p);
    p++;
  }
}

// シリアルポートに書き込みを行う
void writeToSerial(const char *txMessage)
{
  digitalWrite(LED_PIN, HIGH);
  Serial.print("TX: ");
  Serial.println(txMessage);
  mySerial.println(txMessage);
  digitalWrite(LED_PIN, LOW);
}

30秒おきにデータを送信するシンプルな内容になっている.

次回はセントラルデバイス側のコードについて書いてみる.

TWELITEとArduinoを使った測定データの収集について3(送受信文字列)

TWELITEとArduinoの間で送受信されるデータ(文字列)のフォーマットは以下のようにした.

f:id:pearlite:20190522201830p:plain
TWELITEとArduinoの間の文字列データフォーマット

なお,TWELITEにはシリアル通信アプリ(App_Uart)を書き込んでおき,インタラクティブモードで以下の設定を行っておく.

  • Device IDを41(0x29)とする (任意のID)
  • UART baudを9600bpsとする
  • UART modeをアスキーモード(A)とする
  • その他の設定はデフォルトのままでOK

次回はArduinoに書き込むコードについて考えてみたい.