フタなしカンヅメ

徒然なるままに @happytar0

CoreDataのFetchRequestテンプレートで配列を指定

クライアントアプリのコード内にSQL分を書くのがどうもしっくりこないので、FetchRequestのテンプレを使うようにしている。
単純なデータを格納することが多いせいか、テンプレだけで問題になったことはない。

fetchRequestFromTemplateWithNameメソッドでテンプレ名とプレースホルダの変数を渡すのだけど、IN句を使う必要が出てきて少し悩んだ。

テンプレのSQL分には最初、field_name IN ($hoge) のような形で設定してみたのだけど勝手に括弧が外れて、 field_name IN $hoge になった。
こういうもんなのかと思い、プレースホルダにカンマ区切りの文字列を渡してみたがエラー・・・。

どうやらNSArrayをそのまま渡せばいいだけのようだ。

NSArray *ids = @[@1, @2, @3, @4];
NSDictionary *subs = @{@"hoge":ids};
NSFetchRequest *fetchRequest = [managedObjectModel fetchRequestFromTemplateWithName:@"findByIds" 
      substitutionVariables:subs];

何度も使う場合、テンプレを使うことでSQL文のパース処理を省くことができるので若干高速化するようだ。
NSPredicateなんてのもあるんだなぁ・・・

PSCollectionViewでハマる

Pinterest風の表示にしたいと思って色々ライブラリをあさって見たところ、PSCollectionViewという便利そうなライブラリを発見した。
iOS6以上であれば、UICollectionViewというそのものズバリのものがあるようだ。

heightForRowAtIndexメソッドの戻り値に個々の高さを指定してやることで、高さを個別に設定することも可能だった。
戻り値にCGFloatで値を返すのだけど、小数部の桁数が増えると誤差が生じて、上手くタップを認識しなくなることがあるみたい。

調べてみると、セルの大きさをNSStringFromCGRectでNSString化して管理しているようだ。

// Calculate index to rect mapping
self.colWidth = floorf((self.width - kMargin * (self.numCols + 1)) / self.numCols);
for (NSInteger i = 0; i < numViews; i++) {
    NSString *key = PSCollectionKeyForIndex(i);
    
    // Find the shortest column
    NSInteger col = 0;
    CGFloat minHeight = [[colOffsets objectAtIndex:col] floatValue];
    for (int i = 1; i < [colOffsets count]; i++) {
        CGFloat colHeight = [[colOffsets objectAtIndex:i] floatValue];
        
        if (colHeight < minHeight) {
            col = i;
            minHeight = colHeight;
        }
    }
    
    CGFloat left = kMargin + (col * kMargin) + (col * self.colWidth);
    CGFloat top = [[colOffsets objectAtIndex:col] floatValue];
    CGFloat colHeight = [self.collectionViewDataSource collectionView:self heightForRowAtIndex:i];
    
    CGRect viewRect = CGRectMake(left, top, self.colWidth, colHeight);
    
    // Add to index rect map
    [self.indexToRectMap setObject:NSStringFromCGRect(viewRect) forKey:key];
    
    // Update the last height offset for this column
    CGFloat heightOffset = colHeight > 0 ? top + colHeight + kMargin : top;
    
    [colOffsets replaceObjectAtIndex:col withObject:[NSNumber numberWithFloat:heightOffset]];
}

セルのタップは、UITapGestureRecognizerを使っているようで、shouldReceiveTouchメソッドで、どのセルがタップされたか判定して、didSelectメソッドが呼びされるようだ。
heightForRowAtIndexで返される高さの小数部が増えてくると、下記のrectStringと値が一致しなくなり、タップを認識しなくなる。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if (![gestureRecognizer isMemberOfClass:[PSCollectionViewTapGestureRecognizer class]]) return YES;
    
    NSString *rectString = NSStringFromCGRect(gestureRecognizer.view.frame);
    NSArray *matchingKeys = [self.indexToRectMap allKeysForObject:rectString];
    NSString *key = [matchingKeys lastObject];
    
    if ([touch.view isMemberOfClass:[[self.visibleViews objectForKey:key] class]]) {
        return YES;
    } else {
        return NO;
    }
}

解決方法は簡単で、heightForRowAtIndexで値を返す際に、小数部が増えないようにしたり、丸めてやればいい。

- (CGFloat)collectionView:(PSCollectionView *)collectionView heightForRowAtIndex:(NSInteger)index {
    return floorf(100.123456789f);
}

投稿しようとして気づいたのだけど、Objective-C(Cocoa)ネタ書いたの初めてだった。
iPhoneアプリを作っていると、ブログに書くようなネタがなかなか見つからない…
ちょっとググれば情報がたくさん見つかるし書くまでもないかなーとか思っちゃうせいだろうか。

温度センサーとA/D変換

7セグも使うことが出来たので、次は温度センサーを使っていくことに。
これで無事に温度が表示出来れば、ほぼ完成したようなものだ。

温度センサーはLM60というものを使う。
このセンサーには3つの足が付いていて、それぞれVs、Vout、GNDとなる。
Vs(VCC)は電源、GNDにマイナスを接続すると、Voutに現在の温度を表す電圧が出力される。

AVRにはA/D変換の機能が標準で付いているので、これを利用することで現在の温度を取得できる。

A/D変換とは

そのまんまなのだけど、アナログ値をデジタル値として変換することだ。
変換するには、マイコンの変換用ポートPD0〜5を利用する。
変換するために使う基準電圧は、内部基準電圧使うか、外部電圧を使うか選択できます。
ATMEGA328Pの場合、内部基準電圧は1.1Vです。

LM60のデータシートを見る

まずはLM60の仕様を把握することにした。

データシートはこちら
http://akizukidenshi.com/download/LM60.pdf

  • 40度〜125度まで測定出来るらしく、DCオフセット424mV、6.25mV/℃ということらしい。

使用する電圧の範囲は174mV〜1205mVとなる。

基準電圧は出力される電圧以上なければいけないので、1.1Vだと以下になる。
(1100mV - 424mV) / 6.25mV = 108.16度
内部基準電圧だと108度くらいまでしか計測出来ないが、今回は良しとした。(108度を越える環境で使わないし…)

A/D変換を使って温度を取得

まず配線は、マイコンのAREFとGND間に0.1μFのコンデンサ、GNDとAVCCは電源に接続する。
PC0には温度センサーのVoutを接続する。

続いてコードだが、A/D変換を利用するには、ADMUXとADCSRA、ADCH、ADCLレジスタなどを使う。
ADMUXの7ビットと6ビット目で基準電圧を指定する。両方「1」にすることで内部基準電圧となる。
5ビット目は、左揃えにするか右揃えにするかという設定だが、これは取得できる電圧が0〜1023の10ビットの値なので、レジスタ2つ分使うことになる。
その際に上位ビットをどのように格納するかの設定が、この5ビット目になる。

0〜4ビット目は接続しているポートを表すビットを指定する。

ADCLは変換した値の下位ビット、ADCHは上位ビットになる。
ADCSRAで実際にA/D変換を有効にする設定だ。
詳しくは下記のサイトを見るのが手っ取り早いかもしれない。

A/D 変換でボリューム(可変抵抗)値を読む

変換される電圧は前述した通り、0〜1023の範囲で変換されて返される。
174mV〜1205mVを1.1Vの基準電圧を使って、0〜1023の範囲で変換されて返されるのだけど、この精度のことを分解能と言うらしい。

1.1V / 1024 = 0.001
3.0V / 1024 = 0.002

基準となる電圧によって精度も変わってくるようだ。

A/D変換された値を使って温度を求める計算式は下記のようになる。

(A/D変換された値 * 基準電圧 / 1024 - 0.424) / 0.00625 = 温度
(500 * 1.1V / 1024 - 0.424) / 0.00625 = 18.0975度

あとは計算して出てきた値を7セグへ表示するだけだ。
無事に成功!
f:id:happytar0:20130204061212j:plain

実はドハマリしてた

なぜか上手く温度が取得出来なくて、あーだこーだと試行錯誤していた。
原因はマイコン右側ポートのAVCCとGNDを接続していなかったことだ。
内部基準電圧を使うから必要ないと思っていたが、内部基準電圧はAVCCとGNDから確保されるらしい…。

今回の温度を測るコードはこちら
pontago/avr-7SegLedTemp · GitHub

7セグLEDの3桁表示に挑戦

前回の記事で1桁の7セグ表示することができたので、今回は3桁点灯させてみることにした。
3桁点灯させるにはトランジスタが必要ということで、いよいよトランジスタを使うことになる。

ここでトランジスタやダイナミックドライブについて調べていくと、
ATMEGA328PのI/Oピンは最大40mAなので、カソードコモンをマイコンに接続すると最大電流量を越えてしまうようだ。

各セグメント10mAで8本、合計80mA流すと、カソード側が80mAになる。
これを回避するためにトランジスタを使うらしく、少ない電流から大きい電流に増幅出来るので、最大40mAを超えないで制御できるそうだ。

トランジスタを使う

とにかくトランジスタを使ってみることにした。
使うトランジスタはNPN型の2SC1815GRというものだ。
他にPNP型というものもあるようだけど、今回はカソードコモンの7セグなので出番はないようだ。

トランジスタには、コレクタ、ベース、エミッタという3本の足があり、ベースに電流を流すことで、エミッタに増幅された電流が流れていくらしい。
この増幅率をhFEというようだ。

エミッタを7セグのコモン端子に繋ぎ、ベースにマイコンのPB0〜2ポートへと繋ぐ。
また、エミッタとベース間には安定化させるための抵抗10kΩ、ベースとマイコンポート間には4.7kΩを繋ぐ。

以前にすでに計算しているのだけど、マイコンとトランジスタ間に繋ぐ電流制限抵抗値は、以下のようにして計算した。

このトランジスタの電流増幅率(hFE)は100で、
トランジスタを通すことによって0.7Vほど電圧降下がすることを考慮して、以下ように計算した。
(5V - 0.7V) * 100hFE / 100mA = 4.3kΩ
4.3kΩに近い4.7kΩの抵抗を使うこととした。

ダイナミックドライブさせる

3桁の7セグを高速で切り替えることで、3桁とも点灯しているように見せるのが、ダイナミックドライブ(ダイナミック点灯)と言うらしい。

表示する桁のポートにビットを立てながら切り替えるだけなので、コードはさほど難しくなかった。
下記のように関数を作って、表示したい桁と数字(ドットなど)を指定できるようにした。

#define DIG_MAX 3
#define NUM_MAX 11

void showNumber(char dig, char num) {
  char nums[] = {0b00111111, 0b00000110, 0b01011011, 0b01001111,
    0b01100110, 0b01101101, 0b01111101, 0b00000111, 0b01111111,
    0b01101111, 0b10000000};

  if (dig < DIG_MAX && num < NUM_MAX) {
    PORTB = 1 << dig;

    if (num == -1) {
      PORTD = 0b00000000;
    }
    else {
      PORTD = nums[num];
    }
  }
}

3桁順番に数字などを表示させてみた。配線が汚くて恥ずかしい…。
f:id:happytar0:20130204043707j:plain

今回のコードはこちら
pontago/avr-7SegLed3Dig · GitHub

参考
今から始めるAVR #2 ATtiny2313 7セグ4桁ボード〜そこ(7セグ)んとこ、詳しく

7セグLEDを使う

いよいよ7セグLEDを使う時がきた。
7セグLEDを使えば、0から9までの数字(あとドット)を表示させることができるのだ!
温度計には無くなてはならないものと言っていい。

購入した7セグLEDは、赤色で3桁表示するもので、C-533SRという型のものだ。
7セグメントLED表示器 超高輝度赤色3文字(3桁)(カソードコモン)C−533SR: LED(発光ダイオード) 秋月電子通商 電子部品 ネット通販

7セグには足が12本付いていて、どこから手を付けていいか全くわからない。
今回はこんな流れでやってみようと計画を立てる。

  • 7セグの各配線の意味を調べる
  • 1セグメント点灯させてみる
  • マイコンから数字を表示させてみる

7セグの足(各配線)はどうなっているのか

3桁表示できる7セグなので3つの7セグがくっついている。
まずは、各配線の意味を調べるためにデータシートを見てみることにした。
http://akizukidenshi.com/download/ds/paralight/C-533SR.pdf

分かるような分からないような…集中して何度も確認してみた。
どうやら各セグメント(数字を構成する各LED)ごとに、AからGまでのアルファベットが割り当てられている。
そのアルファベットの下には数字が書いてあるので、配線の順番ということだろう。

f:id:happytar0:20121226233158p:plain
f:id:happytar0:20121226233206p:plain

アルファベットを各配線に割り当てるとこうなる。

f:id:happytar0:20121226233205p:plain

1セグメント点灯させる

続いて1セグメントだけ点灯させてみることにした。
図でいうと左上の「F」の箇所だ。
ちなみに、DIG1〜3にはカソードコモンなのでGNDを接続する。

データシートを見るとVf1.8〜2.2で、最大20mAまで流せるようだ。
以前と同じ240Ωの抵抗をそのまま使うことにした。

無事に左上が点灯したところ
f:id:happytar0:20121225003048j:plain

マイコンから数字を表示させる

次はマイコンに接続し、数字を表示させることにした。
ここからが勝負の時だ…。

トランジスタを間に挟んで接続する予定だったが、
とりあえず動作確認のためにマイコンと一桁(8本)直結させることにした。

ATMEGA328Pの最大電流量は200mAなので、各セグメントに10mAずつ流しても十分に足りると考えた。

マイコンには、PB0〜7まで各セグメントA〜Gを接続。
こうすることでマイコンから制御する際に分かりやすくなるとのこと。

指定セグメントを点灯させるには、LEDと同じように指定ポートのビットを立てるだけだ。

各セグメントを順番に点灯させるコードはこちら
pontago/avr-7SegLedTest · GitHub

0〜9の数字と.(ドット)を順番に表示させるコードはこちら
pontago/avr-7SegLedNum · GitHub

無事に数字を表示させることができた。
f:id:happytar0:20121225030559j:plain


数字を表示させただけでも嬉しくなってしまった。
たいした事をしてなくてもその気になってしまうのだから危険だ。
そろそろトランジスタと温度センサーの出番なので気は抜けない。

ATMEGA328Pを使う

LED点灯を試すためにATTINY2313を一番最初に買ったのだけど、
7セグLEDを使うためには少しポート数が足りなくなってきた。
思い切ってATMEGA328Pを買うことにした。

ただ単純にATTINYと置き換えればいいと思っていたがそうは問屋が卸さない。
割り当てられているポートの順番なども違うようで、
ATTINYと比べながら置き換えていく必要があるようだ。

流れとしてはこんな感じだ。

  • 三端子レギュレータを使って5V降圧した電源を使う
  • ATMEGAにプログラマを接続して認識させる
  • 以前作ったLED点灯回路・プログラムをそのまま流用し、動作を確認する

プログラマとATMEGAを接続する

各ポートに接続する配線については、以前に接続したことがあるATTINYを参考にした。

また、下記のサイトも見ながら試行錯誤してみた。
始めるAVR

各ポートの用途はデータシートを参考に。
http://akizukidenshi.com/download/mcu/avr/attiny2313.pdf
http://akizukidenshi.com/download/mcu/avr/atmega48-88-168-328_A_P_PA.pdf

結果的には、こんな感じで接続してみると上手く認識させることができた。
f:id:happytar0:20121224234928j:plain

ATMEGAでLEDを点灯させてみる

試しに以前作った2つのLEDを交互に点灯させる回路を組んでみることにした。
PD3とPD4それぞれにLEDと抵抗を接続した。
プログラムもそのまま同じもので問題ないのだが、
コンパイルする際にATMEGA用に一部Makefileの書き換えが必要になる。

こんな感じでMakefileをATMEGA328P用に書き換えた。

DEVICE     = atmega328p
PROGRAMMER = -c avrispmkII -P usb -p m328p

以前作ったLEDを点滅させる記事はこちら
AVRでLEDを点滅させるプログラム - フタなしカンヅメ

無事に点灯したところ
f:id:happytar0:20121224235240j:plain


三端子レギュレータの問題が解決してからはスムーズに進んで気持ちがいい。
次はいよいよ7セグLEDを使うことになるので、わくわくが止まらない。

12VのACアダプタから5Vに変換

温度計作りを構想してからどのくらい経っただろうか…
なかなか手を付けるタイミングがなく今年が終わろうとしていたのだけど、
意を決してマイコンいじってみることにした。

まずは、今まで電池ボックスを利用していた箇所をACアダプタから電源を確保することにした。
12VのACアダプタを買った理由は、高い電圧を必要とするものでも使い回せそうな気がしたのと、
三端子レギュレータを使ってみたかったのが理由だ。

12Vではマイコンに繋ぐことができないので、5Vほどに降圧する必要がある。
三端子レギュレータはLM7805CVを利用する。これはかなりメジャーなものでよく使われるものらしい。

ACアダプタのDCジャックそのままでは、ブレッドボードに挿すことができないので、
秋月電子で販売されている「ブレッドボード用DCジャックDIP化キット」を使うことにした。

ブレッドボード用DCジャックDIP化キットを組み立てる

このキットには、DCジャック、ブレッドボードに挿すための足、基盤が付属されている。
半田を使って組み立てる必要があるようだ。
半田ごてを使うのは久しぶりな上に、半田する箇所が小さかったので緊張したのだけど、あっさりできた。

しかし、半田した後に基盤を上下逆にしていたことに気づく…これが後々悩む理由になるとは。
本当ならばプラスとマイナスが印刷されている面を上にした方がいい。

三端子レギュレータを使って5Vに降圧

ACアダプタをブレッドボードに挿した後、テスターを使い12Vが出ていることを確認した。
いよいよ三端子レギュレータの出番だ。

型番が印刷されている方を前面と見て、左がIN(12V)、中央がGND、右がOUT(5V)ということらしい。
発振を防ぐために、INとGND間に積層コンデンサ0.33μF、GNDとOUT間に0.1μFを接続する。

わくわくしながらOUTの5Vをテスターで計測してみる…
ん?5Vになってないどころか電圧がふらふらしている。
しかも三端子レギュレータが異常な熱を帯びていて持てないくらいだ。

コンデンサの繋ぎ方が悪いのかと、繋げ方を変えてみるが変化なし。
数日悩んだところで、お手上げ状態なのでマイコン先生に聞いて見ることにした。

三端子レギュレータの謎が解ける

先生に状況を説明している段階で、あることにふと気づいた。
ACアダプタの基盤を逆に半田付けしていたことだ。
どうやらそのせいで、プラスとマイナスも逆転していたらしい。

なんと、INにマイナス、中央にプラスを接続していたのだ…。
話を聞いたところ、これはかなり危ないミスらしい。三端子レギュレータの異常な熱の原因も分かった。
プラマイ逆にしたところ、無事に5Vにすることができた。

なぜテスターで12V測れていたのだろうと考えたところ、
よくよく見るとテスターの左側に「-」の記号が…どうやら「-12V」と表示されていたことに気づかなかったようだ。

f:id:happytar0:20121224234920j:plain


今回はソフトのようにエラーコードを吐かないハードの難しさを思い知ることとなった。
もう少し慎重に進めていきたいと思う。