🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Arduino

Arduinoは、AVRマイコン、単純なI/O(入出力)ポートを備えた基板、C言語を元としたArduinoのプログラム言語と、それを実装した統合開発環境から構成されたシステムです。

Q&A

解決済

2回答

1794閲覧

arduino DS1307の日付設定について

DMR

総合スコア6

Arduino

Arduinoは、AVRマイコン、単純なI/O(入出力)ポートを備えた基板、C言語を元としたArduinoのプログラム言語と、それを実装した統合開発環境から構成されたシステムです。

0グッド

0クリップ

投稿2021/01/06 14:49

#include <Wire.h>
int DS1307_ADDRESS=0x68;
int val=0;
byte command;

void setDay(){
byte year=(Serial.read()<<4);
year=year+(Serial.read()&0x0F);
byte month=(Serial.read()<<4);
month=month+(Serial.read()&0x0F);
byte day=(Serial.read()<<4);
day=day+(Serial.read()&0x0F);
byte day_of_week=(Serial.read()&0x07);
Wire.beginTransmission(DS1307_ADDRESS);
Wire.write(0x03);
Wire.write(day_of_week);
Wire.write(day);
Wire.write(month);
Wire.write(year);
Wire.endTransmission();
}

DS1307 RTCの日付設定のスケッチなんですが、Serial.read()の後の<<4、&0x0Fと&0x07は何を意味していますか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

前提:
シリアル入力からASCIIコード数字でYYMMDDWの順でデータが流し込まれていること。(Serial.available()等で全てのデータが受信済みであることを確認するとか、ある一定時間内にデータが流し込まれる手順が保証されている等。Serial.read()は受信データがないと-1を返して来ますから)

ASCIIコードは、数字'0'に対し数値0x30(十進数では48, 二進数では0b00110000)、'1'に対し0x31(49,0b00110001)...'9'に対し0x39(57,0b00111001)が対応しています。

送られてきた数値48~57を元の数字が表す数値0~9に変換するには...
普通は'0'に対応した値を引くという処理をする(48-48=0 ~ 57-48=9)ことが多いのですが、
そのサンプルでは下位4bit(16進数で言えば下位1桁)を取り出す、という考え方をしたようです。

&はビット論理演算積(and演算)の演算子です。まぁ、記号って検索するのは難しいですけど、C/C++の入門書の一冊も読み通せば出てきていると思うので確認しておいてください。
二進数で表記したとき、二つの数の各桁の両方が1になっていればその桁に1を出力、そうでなければその桁を0を出力するという働きを持ちます。そうすると、0xfとandを取るということは
'0' & 0xf = 0b00110000 & 0b00001111 同じ桁で両方1のビットは無いので 結果は0
'1' & 0xf = 0b00110001 & 0b00001111 最下位が両方1 結果は0b00000001 (つまり1)
'2' & 0xf = 0b00110010 & 0b00001111 結果は0b00000010つまり2
<略>
'9' & 0xf = 0b00111001 & 0b00001111 結果は0b00001001つまり9
(andの演算はbyteデータに対してもint幅に拡張して行われますが、この場合は特に気にしなくてもいいでしょう。符号付き整数ではたまにハマることがありますがそれは別の話)
これで送られてきた'0'~'9'の「数字」から「数値」0~9を取り出すことが出来ました。

曜日は、元データが'0'~'6'の可能性しか無いので、別に0xfとのandでもよかったのでしょうがなんとなく(?)7=0b00000111とのandをとることにしたのでしょう。'0'~'6'ではbit3が1になることはないので0xfとandをとっても0x7とandをとっても同じことになります。

このように&演算で特定のビットだけを取り出すこと(言い方を変えれば特定のビットを0にすること)を(ビット)マスクと言ったりもします。マイコンをいじる人なら覚えておくべき言葉かと思います。

さて。DS3107データシート 8ページを見ると、設定すべき値の形式がわかります。
8bitデータの上位に日/月/年の10の桁が入るような形式になっていますね(二進化十進数:BCDってやつ)。

ここで、<<はビット左シフトの演算子。二進数の各桁を指定数だけ左にシフトします。
例えば、1 << 3は 0b00000001を3ビット左シフトして0b00001000が得られます。
(「はみ出した」分はなくなってしまいます。0b1111 0000 0000 0001 << 2 をintの範囲で演算すれば 0b1100 0000 0000 0100)

今回は、例えば'1'を4ビット左シフトしてbyte型の変数に格納するなら
0b00110001 << 4 = 0b001100010000 この下位8bitが変数に入るので
0b00010000(十六進では0x10)が得られます。ちなみに、二進数を一桁左シフトすることは2を掛けることと同義ですから、0b00110001 << 4は0b00110001 * 2 * 2 * 2 * 2と同じ結果になります。つまり、
0b00110001 << 4 は 0b00110001 * 16 と置き換え可能で、そういう表記をすることも時々ある、というのは覚えておいて損はないと思います。(単純に考えれば、シフト演算の方が簡単なので速度的には有利です。ただし、コンパイラもお利口なので、*16を見ると<<4に置き換えてたりすることもあるようです)

余談ですが、<<は優先度の低い演算子なので、
0b00110001 << 4 + 1 とすると4+1が優先的に行われて0b00100000になります。1+0b00110001<<4では先に0b00110001+1が計算されて0b00110010 << 4の演算になります。
'10'に1を足して11を求めるイメージのときには(0b00110001 << 4 )+1としないといけません。これ、割とハマります。

以上より、

C++

1byte year=(Serial.read()<<4); 2year=year+(Serial.read()&0x0F);

に対し、シリアルからデータ'2''1'が流し込まれれば
byte year=(Serial.read()<<4);
でyearは0b00100000(0x20)になり
year=year+(Serial.read()&0x0F);
でyearに0b00100001(0x21)が求まり、これをRTCに流し込めばよい、ということになります。

投稿2021/01/06 23:44

編集2021/01/07 22:14
thkana

総合スコア7703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

DMR

2021/01/07 15:30

分かりやすく説明していだだき有難うございました。理解できました。
guest

0

はっきり言ってこのコードは動きません
こんな都合よくSerial.readで通信データは読み込めません


<<4 左辺の値を4bit左シフトします
&0x0F 左辺の値の下位4ビットのみにマスクします
&0x07 左辺の値の下位3ビットのみにマスクします

んで、そのDS1307なるもののデータシートは読んでるでしょうか。

投稿2021/01/06 15:15

編集2021/01/06 23:13
y_waiwai

総合スコア88038

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問