2014年8月26日火曜日

PICkit3のProgrammer-To-Goを試す

さて、前回の記事から結構時間が開いてしまいました。
なにせただの趣味プログラマーですので、別のことに忙しかったり、もしくは何かを作ろうといった発想がないとなかなかプログラミングに手がつかないもの仕方ないことですね。特に何かの言語の基礎といったものを修得するような段階なわけでもないですし。

今回は、ちょっとプログラミングっぽくないかもしれませんが、PICkit3のProgrammer-To-Goを試してみようかと思います。

Programmer-To-Goって一体何?

PICkit3は、実はパソコンが無くてもスタンドアロンでPICの書き込みができてしまうのです。その機能がProgrammer-To-Goです(PICkit2にもProgrammer-To-Goはあります)。
とは言っても、まさかPICkit3の上でソフトウェアを開発するわけにもいきません。何をやってるのかというと、PICkit3が内蔵しているEEPROMにMPLABから転送したhexファイルを保持し、適当な外部電源とPICkit3にあるスイッチでPICにプログラムを転送できちゃうよって話です。

で、なぜこれを使おうと思ったか。
私は例のNTP時計を2つ作って動かしているのですが、1つは開発用PCから遠いところに固定してしまっていて、プログラムの更新をするのが非常に面倒なんです。NTP時計を外して持ってきたり、デスクトップパソコンをNTP時計の場所に持って行ったり、もしくはノートPCにPICkit3周りの環境をセットアップしてその場に持っていったりするのはとても面倒です。そこで、Programmer-To-Goなわけです。

それでは、Programmer-To-Goの準備をしていきましょう。

まずは、MPLAB Xでプロジェクトのプロパティを開き、CategoriesのPICkit3を選択して、Option categoriesからProgrammer-To-Goを選択します。



Image Nameの項目にこのProgrammer-To-Goのイメージの名前を入れてあげます。特にこれが完成したプログラムに影響を与えるといったことはない(と私は認識しています)です。単に、今PICkit3に入ってるプログラムが何なのかを識別できるようにしたいってだけなはずです。


次に、ツールバーの書き込みボタンの三角印を押してドロップダウンメニューを表示させます。この中にProgrammer To Go PICkit3 Main Projectという項目があるので、これを押すとhexファイルがPICkit3に転送されます。もちろん、この段階でPICkit3にPICを接続している必要はありません。

ここで、すでにPICkit3の中にProgrammer-To-Go用のプログラムが書き込まれていた場合、このような警告が出てきます。


読めばわかることですが、「PICkit3はすでにProgrammer-To-Goモードで"NTPCLOCK"っていうイメージが入っています。このデータを保持しますか?(つまり、PICkit3内のイメージを消去しないってことです)」と言っていますね。わざわざ"つまり"でわかりやすく言い換えてくれています。ここで、先ほど設定したイメージ名が役に立つわけです。

プログラムを更新したいので、PICkit3の中のメモリは消去する必要があります。なので、Noを押せばいいですね。

そうすると、出力一覧のPICkit3のところに
Connecting to MPLAB PICkit 3...
PICkit 3 is not in programmer-to-go any more.
Firmware Suite Version.....01.30.09
Firmware type..............dsPIC33F/24F/24H


The following memory area(s) will be programmed:
program memory: start address = 0x0, end address = 0x73ff
configuration memory

Programming...
Programming/Verify complete

PICkit 3 is now in Programmer to go mode. The next time you connect to this unit, you will have the choice to take it out of Programmer to go mode.
このようなメッセージが出てきます。
無事、プログラムの書き込みとベリファイが終了し、Programmer-To-Goモードになったということを示しています。
一方、PICkit3のほうを見るとACTIVEランプが点滅し、STATUSランプが緑色に点灯しています。これが成功の証です。

この後、PICkit3はパソコンから取り外し、モバイルバッテリー等の適当なUSB電源に接続してやります。
そうすると、全ランプが10秒くらい点灯した後に、STATUSランプが消え、ACTIVEランプが点滅し始めます。これが書き込みの準備ができたことを示す表示です。
そうしたら、PICkit3をPICに接続し、タクトスイッチを押すだけです。書き込み中はSTATUSランプがオレンジ色になります。書き込みが終了すると書き込まれたPICは動作をはじめ、PICkit3はACTIVEランプの点灯とSTATUSランプの緑色の点灯をして成功を示します。

これでバッチリですね。パソコンから遠いPICの書き込みもできました。

なお、もしも書き込みにエラーが発生するとACTIVEランプが消え、STATUSランプが特定のパターンで点滅をします。そのパターンからエラーを特定することができるそうですが、そのあたりはこのマニュアルにその説明を譲りします。


このProgrammer-To-Goという機能は実は昔から知っていましたが、使いどころがないだろうと思っていました。たいてい、組み込み機器の製作なんてトライアンドエラーですし、そういった観点からパソコンのすぐそこにそのデバイスを置いて作業しますもんね。
本来は、大量生産などで同じプログラムをたくさんのPICに書き込んだりするときに、そのプログラムを書き込んだPICkit3を手元に置き、流れ作業でどんどん書き込んでいくなどといった用途を想定しているようです。しかし、個人の趣味で大量生産などをするわけもなく、そのような用途では使わなかったわけです。

ですが、「パソコンから遠いところに設置した装置のプログラムを書き換える」ということで、ここでまた1つ、PICkit3の機能を使いこなすことができました。こういう使い方もできるんだなって。

2014年8月2日土曜日

WPF用縦書きテキストブロック Tategaki ver.1.1.0

さて、前回につづいて縦書き用テキストブロックTategakiの記事です。

[バグフィックス]
  • 比較的短い長さのテキストを表示させようとするとバグる件を修正
  • フォントファミリ名やサイズなどをXAMLで指定しないと表示されないバグを修正
[機能追加]
  • 太字、斜体に対応
  • 複数行(自動折り返し)の縦書きコントロール"TategakiMultiline"を実装
こんな感じですかね。いろいろ問題点があったりバグがまだ眠っていそうだったりしますが、とりあえず公開します。(最初からベータ版とかにしとけばよかったかな…)

まず最初の、比較的短い長さのテキストを入れるとバグる件ですが、これはどうも短いテキストだとグリフインデックスが取得できないようですね…。なぜかよくわかりませんが、Uniscribe側の問題っぽいです。なので、非常にアホらしい回避方法ではありますが、あらかじめ10文字ほど足してからグリフインデックスを取得し、その足した分のグリフインデックスを無視するということをやっています。

ushort[] glyphs = Uniscribe.GetGlyphs("ああああああああああ" + Text, FontFamily.Source, (int)FontSize).Skip(10).ToArray();

そしてフォントの設定をしないと正常に表示されなかった問題は、コントロールのコンストラクタでシステムフォントを適当に代入してあげています。

this.FontSize = SystemFonts.MessageFontSize;
this.FontFamily = SystemFonts.MessageFontFamily;

そして、太字、斜体の対応はこれでおkです。

protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
    switch(e.Property.Name) {
        case "FontWeight":
        case "FontStyle":
            this.glyphs1.StyleSimulations =
                ((FontWeight != FontWeights.Normal) ? StyleSimulations.BoldSimulation : StyleSimulations.None) |
                ((FontStyle != FontStyles.Normal) ? StyleSimulations.ItalicSimulation : StyleSimulations.None);
            break;
    }
    base.OnPropertyChanged(e);
}


さて、今回のメインディッシュの複数行対応縦書きコントロールです。コントロールのXAMLはStackPanelを1つ定義するのみで、コードビハインドのほうで1行の縦書きコントロールを複数生成してそのStackPanelに並べてあげているだけです。ですが、これをコントロールのサイズに合わせて適切に文章を区切り、分散させてやらねばならず、これがとても面倒くさいのです。
Win32APIならGetTextExtentPoint32関数などを使えば文字列の幅が取得できますが、グリフに関してはそのようなものが(多分)無いので、実際にコントロールを作ってその大きさを測定することで文字列の幅を取得しています。1文字ずつ増やしては長さを測って折り返すべき長さに達したらそこで文字列を切り離して~なんてやると文字数分だけ時間が掛かってしまいますが、最初に全体の長さを取得して文字数で割ることで1文字当たりの幅を計算し、おおよそ目標の長さにいきなり合うようにして、後はそこから実際にコントロールの長さを測って調整するという処理をすれば行数に比例する程度の時間で済みます。しかし、それでも結構この処理は重く、このコントロールをリサイズしたりすると結構カクカクします。そういう意味で改善の余地がありそうですが、なんか根本的な方法が無いともうどうしようもないんですよね…。

というわけで結構複雑な処理をしているのでコードがごちゃごちゃしてわかりにくくなっていますが、その部分のコードを貼ります。

void RedrawText()
{
    double height = ParentHeight;

    if((beforeHeight == null) || (beforeHeight.Value != height) || !object.Equals(Text, beforeText)) {    //これをやらないと無限ループに陥る
        stackPane1.Children.Clear();

        if(!string.IsNullOrEmpty(Text)) {
            string[] Lines = Text.Split('\n');
            Size infinitySize = new Size(double.PositiveInfinity, double.PositiveInfinity);
            Thickness margin = new Thickness(0, 0, LineMargin, 0);

            if((height <= 0) || (Lines.Length == 0)) {
                stackPane1.Children.Add(new TategakiText() { Text = Text, FontFamily = FontFamily, FontSize = FontSize, Spacing = Spacing, Margin = margin });
            } else {
                foreach(string line in Lines) {
                    string text = line;

                    if(line.Length == 0) {
                        TategakiText tategaki = new TategakiText() { Text = string.Empty, FontFamily = FontFamily, FontSize = FontSize, Spacing = Spacing, Margin = margin };
                        stackPane1.Children.Insert(0, tategaki);
                    } else {
                        while(text.Length > 1) {
                            TategakiText tategaki = new TategakiText() { Text = text, FontFamily = FontFamily, FontSize = FontSize, Spacing = Spacing, Margin = margin };

                            tategaki.UpdateLayout();
                            tategaki.Measure(infinitySize);
                            double charHeight = tategaki.DesiredSize.Height / text.Length;    //1文字の平均長(縦書きはだいたい等幅)

                            int i;
                            if(charHeight == 0)    //ゼロ除算回避
                                i = text.Length;
                            else {
                                for(i = (int)(height / charHeight) + 1; i < text.Length; i++) {    //平均長から1行のおよその文字長を割り出す
                                    tategaki.Text = text.Substring(0, i);
                                    tategaki.UpdateLayout();
                                    tategaki.Measure(infinitySize);
                                    if(tategaki.DesiredSize.Height > height)    //長さが超えていたらブレーク
                                        break;
                                }
                                i = Math.Max(Math.Min(i - 1, text.Length), 1);    //長さが超えたらその1つ小さくなったものから調べればよく、また、最初に決め打った長さがtext.Lengthを超えてる可能性があるのでそれを合わせ込むが、1より小さくはしない。
                            }
                            for(; i > 0; i--) {
                                tategaki.Text = text.Substring(0, i);
                                tategaki.UpdateLayout();
                                tategaki.Measure(infinitySize);
                                if(tategaki.DesiredSize.Height <= height)    //減らしていって長さが切ったらブレーク
                                    break;
                            }
                            stackPane1.Children.Insert(0, tategaki);

                            text = text.Substring(Math.Max(i, 1));    //iが0になってきたらそれは1文字
                        }
                        if(text.Length == 1) {    //文字列が1文字だったら強制的に書きだす
                            TategakiText tategaki = new TategakiText() { Text = text, FontFamily = FontFamily, FontSize = FontSize, Spacing = Spacing };
                            stackPane1.Children.Insert(0, tategaki);
                            text = string.Empty;
                        }
                    }
                }
            }
        }
        beforeText = Text;
        beforeHeight = height;
    }
}

ちなみに、コントロールのサイズを測るには、まずUpdateLayoutメソッドを呼び、そのあとにMeasureレイアウトを呼ぶことで、DesiredSizeが更新されます。これが、要するにその1行の縦書きを表示するのに要求されるサイズですね。こうやって測っています。なんかコントロールを一旦インスタンス化して測定してるあたり、結構時間の掛かりそうな処理ですよね…。

何かいい方法知ってる人がいたらぜひ教えてください。

ちなみに、この辺苦労しただけあって(?)割と簡単に複数行の書き出しはできます。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="100" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" Name="TategakiRow" />
    </Grid.RowDefinitions>

    <TextBox Grid.Row="0" Name="textBox1" HorizontalScrollBarVisibility="Auto"  VerticalScrollBarVisibility="Visible" AcceptsReturn="True"
             Text="{Binding ElementName=tategakiMul1, Path=Text, UpdateSourceTrigger=PropertyChanged}" />
    <Slider Grid.Row="1" Name="slider1" Minimum="0" Maximum="30" Value="10"/>
    <StackPanel Grid.Row="2" Orientation="Horizontal" FlowDirection="RightToLeft" Margin="0,0,0,10">
        <tg:TategakiText Text="羅生門" 
                        FontFamily="メイリオ" FontSize="36" Spacing="200" FontWeight="Bold"
                        HorizontalAlignment="Center" VerticalAlignment="Center"
                        RenderTransformOrigin="0.5,0.5">
            <tg:TategakiText.RenderTransform>
                <ScaleTransform ScaleX="-1" />
            </tg:TategakiText.RenderTransform>
        </tg:TategakiText>
        <tg:TategakiText Text="芥川龍之介"
                        FontFamily="メイリオ" FontSize="20" Spacing="100"
                        HorizontalAlignment="Center" VerticalAlignment="Bottom"
                        RenderTransformOrigin="0.5,0.5">
            <tg:TategakiText.RenderTransform>
                <ScaleTransform ScaleX="-1" />
            </tg:TategakiText.RenderTransform>
        </tg:TategakiText>
        <tg:TategakiMultiline Text="すると、老婆は、見開いていた眼を、一層大きくして、じっとその下人の顔を見守った。まぶたの赤くなった、肉食鳥のような、鋭い眼で見たのである。それから、皺で、ほとんど、鼻と一つになった唇を、何か物でも噛んでいるように動かした。細い喉で、尖った喉仏(のどぼとけ)の動いているのが見える。その時、その喉から、鴉(からす)の啼くような声が、喘(あえ)ぎ喘ぎ、下人の耳へ伝わって来た。&#xa;「この髪を抜いてな、この髪を抜いてな、鬘(かずら)にしようと思うたのじゃ。」&#xa; 下人は、老婆の答が存外、平凡なのに失望した。そうして失望すると同時に、また前の憎悪が、冷やかな侮蔑(ぶべつ)と一しょに、心の中へはいって来た。すると、その気色(けしき)が、先方へも通じたのであろう。老婆は、片手に、まだ死骸の頭から奪った長い抜け毛を持ったなり、蟇(ひき)のつぶやくような声で、口ごもりながら、こんな事を云った。" 
                            FontFamily="MS 明朝" FontSize="18" Spacing="100" LineMargin="{Binding ElementName=slider1, Path=Value}" FontStyle="Normal" FontWeight="Normal" Foreground="Black"
                            RenderTransformOrigin="0.5,0.5" Name="tategakiMul1">
            <tg:TategakiMultiline.RenderTransform>
                <ScaleTransform ScaleX="-1" />
            </tg:TategakiMultiline.RenderTransform>
        </tg:TategakiMultiline>
    </StackPanel>
</Grid>

XAMLの中のテキストで改行をするには、\nではなく&#xa;を使えば良いようです。改行コードそのままです。例によってStackPanelは右から左に並べるとコントロールの中身が左右反転してしまうので(おそらくアラビア語とかのためのコントロール)、あらかじめコントロールをRenderTransformで左右反転してからStackPanelに与えています。

さて、 ダウンロードはこちらからどうぞ。
WPF用縦書きテキストブロック Tategaki ver.1.1.0
((2015/1/22)都合により削除しました。ver1系はver.1.1.2を使ってください)