不注意でよく物を壊す

アクアリウムとかマイクロマウスとかアリュージョニストとかそのへん

2019年振り返りエントリ

はじめに

去年書こうと思ったけど面倒くさくなって書かなかったので今年は振り返りエントリ書きます.

今年は大学院に入学したり競技プログラミングに触れてみたりKaggleをまともに始めたりといろんなことがありました.本記事では「今年やったこと」「来年やりたいこと」を書いていこうと思います.

今年やったこと

AtCoderを始めた

元々所属しているサークル周辺の界隈では競技プログラミングをやっている人は少なからずいて,腕試しがてら就活の助けになればいいなーというくらいの気持ちで始めたような気がします.

言語はNimというPythonに似た書き方ができる静的型付言語を使っていて,コンパイルすることでC言語が吐き出されるという特徴があります. サポーターズという就活生向けサービス主催の学生LT会などでも発表したりしました.

アカウント情報を見ると2月ごろから始めて,6月くらいに茶色と緑色の狭間をゆらゆらするようになったようです.
ABC(AtCoder Beginner Contest)が6問制になってから伸び悩み,新しいこと(DPとかグラフとか)を勉強しないと上に上がれないことを悟りました.
今は後述するKaggleが楽しく自分の興味にも合っているので停滞中です.来年は新しいアルゴリズム周りを勉強しないとなーと感じています.

Kaggleをまともに始めた

Kaggle自体は大学の同期5人で去年に始めたのですが,予定が合わなかったり興味の方向が違っていたり研究に忙殺されたりなどの理由で徐々にチームでは取り組まなくなりました.
自分は大学院でも画像認識を専攻していることもあり,主に画像コンペに取り組んでいます. 今年取り組んだコンペは以下の5つです.

Humpback Whale Identification (2059th/2129)

www.kaggle.com

この頃の自分はResNetを知っているくらいでkernelのSiamese Networkを写経したりしていました.チューニングの勘所なども今に比べれば全然わかっていなかったためスコアが上がらず途中で気力切れしました.

Don't overfit II (211th/2330)

www.kaggle.com

このコンペは前述した同期5人で取り組みました. Kernelを読みながらパラメータをいじったりensembleしたりしていた記憶があります.

iMaterialist (Fashion) 2019 at FGVC6 (159th/241)

www.kaggle.com

このコンペで初めて開催期間中にメダル圏内に入りました.
セグメンテーションのタスクだったのでPix2Pixを改良したモデルで銀メダル圏内に一時入ってとても嬉しかったことを覚えています. しかし,コンペ中盤からMask-RCNNの方が向いているという情報が出回り始め,実装は間に合ったのですが実験回数を稼ぐことができず振り落とされてしまいました.

Recursion Cellular Image Classification (39th/866)

www.kaggle.com

Kaggleで初めてメダルを獲ることができました.
このコンペでKaggleのコンペの進め方を自分なりに固めることができたと思っています. まとめはこちら

tattakaaqua.hatenablog.com

にあるので興味がある方はぜひ読んでみてください.

Understanding Clouds from Satellite Images (59th/1538)

www.kaggle.com

このコンペで意識したことは

  • ChainerからPyTorchへの移行
  • 使い回しを意識したコーディング

の2点です.
segmentation_models.pytorchをベースとして実装されていないモデル(RefineNetやFastFCN,DeepLabv3+など)を新しく実装したり,チューニングを楽にするためにパラメータをどう管理するかなどを考えたりしていました.こちらもコンペに使ったコードをGitHubに公開しています.
github.com

本コンペではRecursion Cellular Image Classificationの時のようにEDAなどはあまりしても進展がありませんでしたが,ひたすらチューニング回数を稼いでこの順位に入ることができました.

大学・研究関係

研究

査読なし学会で2本発表しました.

ロボット周り

研究室からRobocup@HomeとWorld Robot Summit 2019に参加しました.Robocupの方はROSとMoveIt!を使ったマニピュレーションを中心に取り組み,WRSの方は自分の研究成果の実装をしていました.
どちらも賞状はもらえる悪くはない成績でしたが多少の悔いは残ったため来年も研究に支障がない程度に頑張ろうと思います.

その他技術系アウトプット

Kaggleをやっているからか研究分野以外の論文を読む習慣がついてきたのでいくつかまとめたり実装したりしました.

www.slideshare.net

www.slideshare.net

www.slideshare.net

github.com

github.com

なおFilter-Response-Normalization-PyTorchに関してはTwitterで反響をもらったり,paperswithcodeに載ったり前述のsegmentation_models.pytorchの作者さんからissueをもらって実装の議論をしたりなど貴重な経験をすることができました.

料理とかその他趣味

料理

前々から欲しかった蒸籠を購入しました.

パール金属 和の里 中華せいろ 18cm H-5713

パール金属 和の里 中華せいろ 18cm H-5713

  • メディア: ホーム&キッチン

用途としては主に蒸し野菜を多く作っていて「適当な季節の野菜+豚バラor胸肉+塩」の組み合わせで蒸しています.素朴な味であまり飽きることがないので後述のダイエットを意識していることもあり3日に1回ほどのペースで食べています.

ダイエット

今年の4月くらいからダイエットを始めました.
結果としては○○kg->△△kgでピーク時には13kgほど落とすことができました.(12月は外食が多かったので現在の通算は-10kgほどですが)
これまで鳥もも肉を使っていたところを胸肉を使うようになり,意識して炭水化物を避けるようになりました.(昼か夜のどちらかで炭水化物を抜くくらいの緩い糖質制限)
また,あまり気にしていなかった栄養バランスにも気を配るようになり健康に近づいているのを感じます.

筋トレ

上のダイエットと並行して軽い筋トレを始めました.
初心者用の腹筋ローラを購入し一時期3ヶ月間ほどは2日おきにやっていましたが,忙しい時期になり間が開いたりして徐々にやらなくなってきました.
最近はリングフィットアドベンチャーを購入したのでがんばっていきます.

store.nintendo.co.jp

来年やりたいこと

AtCoder

  • DPやグラフなどアルゴリズムの勉強
    あわよくば卒業までには水色になりたいなーと言うふんわりとした気持ちがあります.(あくまで優先度は低め)

Kaggle

  • 画像以外のコンペでメダル獲得
    現在知り合いに誘われてData Science Bowl2019に参加しているのでメダル獲りたい......
    手元にKaggle本があるので読んだり実践したりしていきたいです.

    Kaggleで勝つデータ分析の技術

    Kaggleで勝つデータ分析の技術

  • 画像コンペで金メダル(目指せMaster)
    現在開催されているのはベンガル語の分類コンペとDeepFakeコンペでDSB2019が落ち着いたらゆったり参加しにいきたいなと考えています.

大学・研究関係

研究

今年はロボットの大会に出場していたのでそちらの比重を来年は軽めにして研究に打ち込みたいです.

ロボット周り

比重を軽めにと書きましたが今年は若干悔いが残ったので来年はRobocupもWRSも優勝を狙いにいきたいです.
後輩がとても優秀なのでタスクをいっぱい投げれる今年よりは楽ができるだろうという目論見もあります.

その他技術系アウトプット

来年は今年にもまして論文を読んだり実装したり続けていきたいです.

料理とかその他趣味

ダイエット

あと5~6kgほどは痩せたいですね.

料理

痩せたいと書いておきながらなんですが,大学1年の頃くらいからの憧れで製麺を始めたいという気持ちがあります. 来年こそはしっかり機材を揃えて麺を打っていきたいです.

Recursion Cellular Image Classification振り返り(39th)

はじめに

まずは興味深く楽しいコンペティションを開催してくれたkaggleに感謝します. 今回のRecursion Cellular Image Classificationは自分にとって初めてメダルを獲得でき,またそれ以上に多くの学びを得られたコンペティションでした. 以下,時系列ごとにやったことをずらっと並べてみました(まとめだけ見たい人は下までスクロールしてください......).

試した手法一覧

ベースラインモデル作り

まずはsoftmax_cross_entropyを用いた単純な分類モデルを作成しました(LB:0.11).

ベースはDenseNet121で入力は3x256x256のRGBに変換された画像を用いていました.最適化にはCorrectedMomentumSGDを用いました.(このころの学習率の設定などは散逸していしまいました)
Data Augmentationはrandom_sized_croprandom_flipを用いており,特にデータの正規化などはしていませんでした.

このころdiscussionでは単純な分類モデルでLB:0.3くらいは出ると言われているのでloss関数を改良しはじめました.
またCross Validationはcell typeで3foldで分割していましたがCVとLBのスコアは乖離していました.

Focal Lossを実装し,スコアが少し改善されました.(CV0.27->LB0.124) この時期にAdaboundも試しましたが機能しませんでした.

次にRGBに変換されていない6chの画像を入力に取るようにしました. kernelを見ているとmixupしているものがあったのでmixupとcutmixを用いましたが精度向上しませんでした.

次に単純な方策として学習epoch数を70から90に増やしスコアを上げました(CV:0.32 -> LB:0.19).

2stage学習

某神discussionのやり方に乗っ取って実装しました.
stage2のみでのスコアはCV:0.52->LB:0.21でstage1との重み付きensemble(2:8)を行うとLBスコアは0.225に上昇しました.
このころ例のleakが発表されLBスコアが0.303に改善されました.

みんな大好きArcFace

この辺りとか神々のクジラ解法などを参考にしながらChainerで実装し直しました.
実際CVスコアは0.5あたり出ていたのですが,ソースコードにバグがあったようで結局正しいスコアは出ていません.(LB:0.06)

AdaCos

ArcFaceハイパラなんもわからんってなってる時にTwitterに流れてきて良さげだったのでこれもPyTorch実装を参考にして実装しました. stage1でLB:0.298(Leakなし)に改善されましたがstage2でNaNが発生し,適当にClipingして直しましたがstage1に比べてスコアは上昇しませんでした.これはstage1を90epoch回していたので過学習していたのではと思います.ここからはstage2の細胞別で学習を行なうことはしていません(学習時間が流石にかかりすぎだと感じたので).

AutoFocalLoss

FocalLossのハイパラなんもわからんだったのでハイパラ調整自動化してくれるのないかなーと思って探したら見つけました.
FocalLossのgammaをいい感じに適応的に動かすパラメータに置き換えるというもので,ざっくり見た感じGitHubに上がっている様子もなかったのでFocalLossを改造して論文読みながら実装しました.記録は残っていませんが0.01~0.02くらいのスコア改善がありました.

random_sized_cropからrandom_crop

細胞の大きさはtrainとtestで違ってこないだろう(顕微鏡写真だし)と考えて,scaleのバリエーションを増やすのは害にしかならないと考えrandom_cropで512x512から256x256を切り出すようにしました.これによりLBは0.428(Leak込み)に改善しました.

Label Smoothingとか色々

label smoothingしましたが特に精度向上はしませんでした.過学習が怖かったのでとりあえずお祈りがわりに最後まで使用しました.
細胞の種類を予測するヘッドを追加すると良いのではと考えましたが全てがバグって辛かったです(ものすごい勢いで過学習する).

Metric LearningとFocal Lossの学習比率チューニング

これまでは固定の割合で学習させていましたが学習曲線を眺めているとMetric Learningの比率が高い場合,収束が早く心なし精度の上限が低く,Focal Lossの比率が高い場合,収束が遅かったのではじめはMetric Learningの比率を高めにして(1.0:0.1)epoch数が進むごとにFocal Lossの比率を上げていく(最終的には0.1:1.0)のを実装したところ0.05ほどのスコア改善がありました.鯨7位解法でもMetric LearningしたものをFineTuningするとよかったという話はあったような気がするのでそんな感じです.

per-Image-Normalize

それでも上位陣のスコアに程遠く,データやdiscussionを見ているとtrainとtestで明るさがかなり違うことに気が付いたのでクジラコンペ3位解法で用いられている画像ごとにNormalizeする手法を使うことでCV:0.6 -> LB: 0.55 -> 0.65(Leak)に改善しました.このころからCVスコアがある程度信用できるようになりました.

Data Augmentationの再考

コントラストや明るさを使ったデータ増強を用いましたが精度が低下しました. また,試したのはこの時点ではないですがShiftScaleRotateなども試しましたが精度がよくありませんでした. 90, 180, 270, 360 rotateを追加し,多少のスコア向上を得ました.

Cell Typeの分布でFocal Lossを重み付け

機能しませんでした.

GlobalConcatPooling(GAP+GMP)の実装

機能しませんでした.おそらくパラメータ数を落としすぎたためな気がします.

EfficientNet・DenseNet169・SEResNext101

機能しませんでした.DenseNet169とSEResNext101は過学習していました.EfficientNetは学習率を変えたりしていましたがうまく学習させることができませんでした.

ベースモデルの学習率を落とす

学習時のCVの上限が明らかに低下したのでsubmissionを作成しませんでした.

random crop から 画像全体を見るためにresizeに

CVは向上しましたがLBは低下しました.

Pseudo Labeling

上記までのDenseNet121で推定した結果の確信度が高いものの上位をtrainデータの1/5のtestからtrainに加えました.CV:0.67 -> LB:0.6 -> 0.689(Leak)に改善されました.

線形割り当て

終了3日前、ダメもとでHungarian Algolismを用いた線形割り当てを試したところLB:0.822に改善することができました.これに気づかなかったら銅メダルで終わっていたと思います.

Test Time Augmentation

上記のrandom cropをTestの時も使用していたので常にinputが違う画像になっていました.そのためかTest Time Augmentationの効果が大きかったと考えられます.最終日はTTAを増やしてスコアを上げて遊んでいました.

最終的なモデルまとめ

  • input: 6x256x256
  • model: DenseNet121(Leak+Pseudo Labeling+線形割り当て)をPseudo Labelingに用いたSEResNext50(Leakと線形割り当て込み)
  • Data Augmentation:random_flip+random_crop(size=(256, 256))+90rotate
  • per-Image-Normalize
  • 4Fold
  • LR: 0.04(54epoch目、63epoch目で0.1倍)
  • 32TTA
    • trainと同じaugmentationを使用
    • 上記のrandom cropのおかげでTTAの効果が大きかった(と考えられる)
  • CV:0.72 -> LB0.869

ソースコード

GitHub上にあるのですが最終盤で崩壊しかけたので整理して後日上げます......

追記

ここにあげました.

github.com

最後に

とても色々な学びがあり,楽しいコンペティションでした.改めて全てのwinnerに賞賛を,何より開催してくれたkaggleチームの方々に感謝します.
次は雲か脳かをやると思いますがファッションコンペの復讐復習をしたいので雲に気持ちが傾いています.読んでいただいた方もリーダーボードで会えることを楽しみにしています.参加された皆さん、お疲れ様でした!!!

低温調理機『ANOVA』で樹脂を染色する

今年の11月に私の誕生日があったのですが、自分への誕生日プレゼントとして低温調理機『ANOVA』を購入しました.

New: Anova Precision Cooker - WIFI 2nd Gen (900 Watts)

New: Anova Precision Cooker - WIFI 2nd Gen (900 Watts)

自分が購入したのはBluetoothで通信するリンクより1段階下のグレードのものなのですが,主に肉を調理するのに使っています.
f:id:tattaka5X:20171224132350j:plain
f:id:tattaka5X:20171224132429j:plain
f:id:tattaka5X:20171224132435j:plain

この器具を使えば簡単に樹脂のパーツなどの染色ができるのではと考えたので,やってみました.

準備

染めるパーツ

自分が作っているマイクロマウスというロボットで用いるパーツです.DMM.makeの3Dプリントサービスに造形してもらいました.
f:id:tattaka5X:20171224133536j:plain
make.dmm.com

染料

樹脂用染料『SDN』を使いました.キッチンが汚い
f:id:tattaka5X:20171224133632j:plain
ジップロックでパッケージングし、パーツを入れます.
f:id:tattaka5X:20171224133659j:plain
寸胴鍋に水を入れて予備加熱していきます.
f:id:tattaka5X:20171224133824j:plain

染色

予備加熱が終わったら袋の口がちゃんと閉まっているのを確認してから鍋に入れます.
f:id:tattaka5X:20171224133810j:plain

今回は20分ほど加熱しました.
引き上げて触れる温度になるまで冷まし、取り出します.
f:id:tattaka5X:20171224135237j:plainf:id:tattaka5X:20171224135246j:plain

乾かします.
良いのでは
f:id:tattaka5X:20171224135419j:plain

最後に、新しいジップロックに染料を詰め直し、大きめのジップロックに入れて保存します.
f:id:tattaka5X:20171224135654j:plain

反省点

→次からは袋を二重にしてやりましょう(泣きながら鍋を洗剤で洗った)

  • 造形する時に底になってた部分の染めムラがひどい

f:id:tattaka5X:20171224135937j:plain
→紙やすりでやすったらマシになるのでは?

まとめ-低温調理機で染色をやると何が嬉しいか-

  • 使う染料の量を最低限にできる
  • 手をあまり汚さずに作業できる

自分が良く使うHALの関数について自分なりの解説(というかメモ書き)

この記事はstm32 Advent Calendar 2017の10日目の投稿です.
自分が普段よく使うHALの関数について、使い方を忘れてもここを見ればわかるよう備忘録がわりに記していきます。

GPIO

関数 HAL_GPIO_WritePin (GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
ヘッダ stm32f4xx_hal_gpio.h
型名 void
機能 Pinstate(High: GPIO_PIN_SET, Low: GPIO_PIN_RESET)を操作する
HAL_GPIO_WritePin (GPIOA, GPIO_PIN_1, GPIO_PIN_SET)
関数 HAL_GPIO_TogglePin (GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
ヘッダ stm32f4xx_hal_gpio.h
型名 void
機能 Pinstateを反転させる
HAL_GPIO_TogglePin (GPIOA, GPIO_PIN_1)
関数 HAL_GPIO_ReadPin (GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
ヘッダ stm32f4xx_hal_gpio.h
型名 GPIO_PinState
機能 指定したピン(インプットモード)の状態を読み取る
HAL_GPIO_ReadPin (GPIOA, GPIO_PIN_1)

Timer

ベース
関数 HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)
ヘッダ stm32f4xx_hal_tim.h
型名 HAL_StatusTypeDef
機能 指定した構造体でタイマを設定する
HAL_TIM_Base_Init(&htim1, TIM_CHANNEL_1)
関数 HAL_TIM_Base_Start (TIM_HandleTypeDef *htim)
ヘッダ stm32f4xx_hal_tim.h
型名 HAL_StatusTypeDef
機能 指定したタイマを有効にする(割り込みは有効にならない?)
HAL_TIM_Base_Start(&htim1)
関数 HAL_TIM_Base_Stop (TIM_HandleTypeDef *htim)
ヘッダ stm32f4xx_hal_tim.h
型名 HAL_StatusTypeDef
機能 指定したタイマをストップさせる
HAL_TIM_Base_Stop(&htim1)
関数 HAL_TIM_Base_Start_IT (TIM_HandleTypeDef *htim)
ヘッダ stm32f4xx_hal_tim.h
型名 HAL_StatusTypeDef
機能 割り込みを有効にしてから指定したタイマを有効にする
HAL_TIM_Base_Start_IT(&htim1)
関数 HAL_TIM_Base_Stop_IT (TIM_HandleTypeDef *htim)
ヘッダ stm32f4xx_hal_tim.h
型名 HAL_StatusTypeDef
機能 割り込みを無効にしてから指定したタイマをストップさせる
HAL_TIM_Base_Stop_IT(&htim1)
PWM
関数 HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim, TIM_OC_InitTypeDef* sConfig, uint32_t Channel)
ヘッダ stm32f4xx_hal_tim.h
型名 HAL_StatusTypeDef
機能 指定したタイマ・チャンネルでPWMを設定する
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1)(sConfigOCはPWM設定のための構造体)
関数 HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
ヘッダ stm32f4xx_hal_tim.h
型名 HAL_StatusTypeDef
機能 指定したパルスとデューティでPWM波を出力する
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1)
関数 HAL_TIM_PWM_Stop(TIM_HandleTypeDef *htim, uint32_t Channel)
ヘッダ stm32f4xx_hal_tim.h
型名 HAL_StatusTypeDef
機能 出力されているPWM波を止める
HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1)
ADC
関数 HAL_ADC_ConfigChannel(ADC_HandleTypeDef* hadc, ADC_ChannelConfTypeDef* sConfig)
ヘッダ stm32f4xx_hal_adc.h
型名 HAL_StatusTypeDef
機能 指定したADCを設定する
HAL_ADC_ConfigChannel(&hadc1, &sConfig)(sConfigOCはADC設定のための構造体)
関数 HAL_ADC_Start(ADC_HandleTypeDef* hadc)
ヘッダ stm32f4xx_hal_adc.h
型名 HAL_StatusTypeDef
機能 ADCをスタートさせる(割り込みは有効化されない)
HAL_ADC_Start(&hadc1)
関数 HAL_ADC_Stop(ADC_HandleTypeDef* hadc)
ヘッダ stm32f4xx_hal_adc.h
型名 HAL_StatusTypeDef
機能 ADCをストップさせる
HAL_ADC_Stop(&hadc1)
関数 HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc)
ヘッダ stm32f4xx_hal_adc.h
型名 HAL_StatusTypeDef
機能 ADCをスタートさせ割り込みを有効化する
HAL_ADC_Start_IT(&hadc1)
関数 HAL_ADC_Stop_IT(ADC_HandleTypeDef* hadc)
ヘッダ stm32f4xx_hal_adc.h
型名 HAL_StatusTypeDef
機能 ADCをストップさせ割り込みを無効化する
HAL_ADC_Stop_IT(&hadc1)
関数 HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length)
ヘッダ stm32f4xx_hal_adc.h
型名 HAL_StatusTypeDef
機能 ADCをDMAモードでスタートさせる
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)sensor_value, SENSOR_NUM)(sensor_valueは格納用の配列、SENSOR_NUMは行うADCの数)
関数 HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc)
ヘッダ stm32f4xx_hal_adc.h
型名 HAL_StatusTypeDef
機能 DMAモードで動いているADCをストップさせる
HAL_ADC_Start_DMA(&hadc1)
Encoder Interface

エンコーダの値を知りたければTIMx->CNTにアクセスする

関数 HAL_TIM_Encoder_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
ヘッダ stm32f4xx_hal_tim.h
型名 HAL_StatusTypeDef
機能 エンコーダインフェース有効化し読み取りをスタートさせる
HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL)(2相エンコーダの場合両方読み取る)

SPI

関数 HAL_SPI_Init(SPI_HandleTypeDef *hspi)
ヘッダ stm32f4xx_hal_spi.h
型名 HAL_StatusTypeDef
機能 指定した構造体でSPIを設定する
HAL_SPI_Init(&hspi1)
関数 HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout)
ヘッダ stm32f4xx_hal_spi.h
型名 HAL_StatusTypeDef
機能 Sizeで送信するデータ数を決め、pTxDataをスレーブに送り、帰ってきたデータをpRxDataに格納する
HAL_SPI_TransmitReceive(&hspi1, send_data, rcv_data, size, 100)

UART

関数 HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
ヘッダ stm32f4xx_hal_uart.h
型名 HAL_StatusTypeDef
機能 Sizeで送信するデータ数を決め、pTxDataをスレーブに送る
HAL_UART_Transmit(&huart1, &char, 1, 1)

情弱でもNimでLチカしたい!on Nucleo-F401RE

この記事はstm32 Advent Calendar 2017の7日目の投稿です.間に合ったぜ.

動機

わたしの近況なのですが,最近はマウスのモチベが下がって研究室配属などがあり,マイコンプログラミングから離れてPythonばかり書いています.なので久しぶりにCを書こうとするととても辛い.そこでCの資産が使えて書きやすい言語がないかなーと探していたところ,Nimという言語に行き当たりました.

Nimについて

詳しくは以下の記事などを読んでください.

インストールに関しては
公式から落としてくるか,Githubを参照してください.
簡単にいうとPythonっぽい記法でC並の速度が出るモダンな感じの静的言語です.

下準備

前回の記事にしたがって準備を進めます.
一点,前回とは違い,今回は話を簡単にするために.c/.hを別ファイルにするチェックを外します.
f:id:tattaka5X:20171206205219p:plain
生成形式は前回の記事と同じくmakefile形式です.
これで初期化コードを生成します.

手段

自分は全体的な作業の流れとして,以下のようなやり方を考えました.
Nimの公式から提供されている変換ツール「c2nim」を用いてmain.cに依存するヘッダーファイルをNimファイルに変換してライブラリごとバインドするというやり方です.
このやり方は結果として失敗に終わるので詳細は割愛しますが,まずc2nimのビルドに失敗します.ソースのバージョンを落とし,中身をコメントアウトしてビルド成功,実際に使ってみるとマクロがうまく変換されず無限にエラーが出ます.楽しいですね.
次に考えたのが,cファイルで関数をラップしてnimファイルに渡し,それをcファイルにコンパイルし(Nimは一旦cファイルに変換してからgccコンパイルしてるっぽい)他のファイルとリンクさせてビルドするというやり方です.

実装

基本的にProjectName/Srcの中にファイルを置いていきます.まずはビルドに必要になるnimファイルを作成していきましょう.nimファイルは--os:standaloneオプションをつけてコンパイルするときpanicoverride.nimというファイルが必要になります.

proc printf(frmt: cstring) {.varargs, importc, header: "<stdio.h>", cdecl.}
proc exit(code: int) {.importc, header: "<stdlib.h>", cdecl.}

{.push stack_trace: off, profiler:off.}

proc rawoutput(s: string) =
  printf("%s\n", s)

proc panic(s: string) =
  rawoutput(s)
  exit(1)

{.pop.}

このコードの必要性がよくわからないんですがないとビルドできないので置きましょう.偉い人はなんでこのコードがいるのか僕に教えてください.

STM32CubeMXから吐き出されたコードをいじっていきます.makefileをビルドに用いる都合上,同名のファイルがあった場合一番上の階層のコードをコンパイルする&main関数が複数あるとよろしくないのでmain.cをmain_.cなど別の名前に変えましょう.main_.cの中身ですが,自分はこう書きました.

#include "main.h"
#include "stm32f4xx_hal.h"

void SystemClock_Config(void);
static void MX_GPIO_Init(void);

void main_init(void){
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
}
void LED1_tick(void){
   HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
}

void SystemClock_Config(void)
{
/*************以下略**************/

簡単に説明すると,上で書いた通り,main関数はnimファイルの中に書くので消去,とりあえずLチカだけしたいので初期設定用の関数をmain_initという名前でまとめ,Nucleoの緑LEDをトグルする関数をLED1_tickという関数でラップしました.それ以外はそのままです.


main.nimの中身はこんな感じで実装します.

{.compile: "./main_.c".}
proc main_init(): void {.importc.}
proc LED1_tick(): void {.importc.}
proc HAL_Delay(ms: uint32){.header: "stm32f4xx_hal.h", importc: "HAL_Delay", varargs.}

when isMainModule:
  main_init()
  while true:
    LED1_tick()
    HAL_Delay(1000)

nimファイルにcの関数を渡す方法は何通りかあって,このファイルではcファイルでラップされた関数をインポートするやり方とヘッダーファイルから関数をインポートするやり方の2通りを実装しています.
前者のやり方ですが,1行目でインポートしたい関数がある.cファイルを指定します.この文法はcompile pragmaと言ってコンパイル時に実行されます.
2,3行目で関数を宣言します.この時,右に.cファイルからインポートされたファイルであるということを示すimport pragmaを追加します.
4行目では,ヘッダーファイルから関数を読み込んでいます.1行目とは違い、nimコマンドでcファイルに変換する時には読み込まれず間違っていても変換されてしまうので注意が必要です.パスですが,makefileに記述されているのに従い指定します.
それ以降はmain関数内に読み込んだ関数を使いLチカのコードを書いています.

ビルド

まず,main.nimをビルドします.projectディレクトリで,

nim cc -c --cpu=arm --d:release --gc:none --os:standalone --deadCodeElim:on src/main.nim

を実行します.
実行するとnimcacheなるディレクトリが作成されます.nimcacheの中のファイル構造は次のようになっていると思われます.
f:id:tattaka5X:20171206223521p:plain
main.cとstdlib_system.cの中身はみてはいけません,目が死にますmain.nimからcファイルに変換された結果が入っています.
main_.sha1はこの場合main_.cが変更された時のみ新しくファイルを生成するためのファイルらしいです.
main.jsonにはcファイルをビルドする手順が書かれていますが今回は無視します.

nimコマンドで生成されたcファイルをビルドする前に,生成されたcファイルで読み込んでいるnimbase.hというファイルをnimcacheの中に置きます.
このファイルは本来ならnim/nim_version/nim/libの中にあってそれを呼び出したいんだろうな〜というファイルなのですが残念ながら呼び出してくれないので同じものを作成します.公式をコピペして持ってくるのが早いです.

最後にmakefileを書き換えます.
前回の記事のようにBINPATHにgcc-arm-none-eabiのパスを入力します.
C_SOURCES= 以下のmain.cをmain_.cに変更し,Src/nimcache/main.cとSrc/nimcache/stdlib_system.cを追加します.
これで

make all

を実行するとめでたくバイナリが生成されます.おめでとうございます.(以下のツイートは多分ソースコードの中身が少し違います)


まとめ

とりあえずNimを使ってLチカすることはできました.しかし,わざわざラップしなくてはいけなかったり,ヘッダファイルから読み込む場合でも引数の型が他のファイルに依存していたりすると面倒なので使い勝手がいいとは言えないです.ただ,Cではマクロを使って無理やり書くしかなかったテンプレートなどメタプログラミングに強い面もあるので(どうせ後でCに変換されるんだけどな)有用な場面も出てくるのではないでしょうか.

おまけ

上に書いたmakefileの仕様で同名のファイルは上の階層のものがビルドされることを知らずに改変前のmain.cをビルドして喜んでいるわたしの様子です.


追記

githubにもあげています.makefileのパスは各自対応お願いします.
github.com

STM32CubeMXのMakefile出力機能でSTM32お手軽開発環境構築

最近というほど最近ではないのですが,STM32の初期化コード自動生成ツールであるCubeMXにMakefileを出力する機能が追加され,ソフト文鎮EclipseなどのIDEを使わないで開発することが容易になりました.自分のための備忘録も兼ねて開発環境のセットアップから書き込みまでの手順をまとめておこうと思います.

 開発環境のインストール

必要なソフトウェアは以下の通り

  • gcc-arm-none-eabi

インストールに関しては以下のページの通り

Linuxに関してもMacと同様にインストールできる

qiita.com

  • STM32CubeMX

本来ならここからインストールできるはず(10/11日現在リンクが消えてるので復活するまで待ちましょう......)※追記(11/11) インストールできるようになってました

www.st.com

  • stm32flashやstlinkなどの書き込みツール

stm32flashは以下のリンクから自分のOSに応じてダウンロードしインストールする

sourceforge.net

stlinkは以下のページを参考にOSに応じてインストール

github.com

  • 好きなエディタ

自分はAtomで開発してます.

プロジェクト作成

STM32CubeMXを起動しNew Projectを選択

f:id:tattaka5X:20171011124721p:plain

以下の画面になったら使用したいボード・CPUを選ぶ

f:id:tattaka5X:20171011124735p:plain

今回はNucleoF446ボードを例にやっていきます.

f:id:tattaka5X:20171011125850p:plain

ProjectタブのSettingsを開いて

f:id:tattaka5X:20171011125957p:plain

以下のようにします(プロジェクトの場所は任意で)

f:id:tattaka5X:20171011130151p:plain

f:id:tattaka5X:20171011130324p:plain

設定が終わったらOKを押し,Generateボタン(歯車っぽいボタン)を押してコードを生成します.

先に設定した場所を見るとプロジェクトが生成されていることがわかります.

f:id:tattaka5X:20171011130850p:plain

コンパイル

このままだとコンパイルが通らないので生成されたMakefileの中のBINPATHにgcc-arm-none-eabiのPATHを入力します.

#######################################
# binaries
#######################################
BINPATH = /PATH/gcc-arm-none-eabi/bin/
#gcc-arm-none-eabiのバージョンや場所は各々の環境に応じて変更する

また,自分で新しく.cファイルを作った場合は

######################################
# source
######################################

以下に作った.cファイルを追記しなければいけません.

 

修正が終わったら

make all

してコンパイルができるはずです.

書き込み

コンパイル後のバイナリファイルは新しく作られたbuildディレクトリの中に生成されます.

たくさんファイルが出来ていますがデフォルトだとプロジェクト名と同じ名前のファイル(.bin,.elf,.hex,.map)があると思います.

生成されたバイナリファイルを書き込みツールを用いてマイコンに書き込めれば成功です!

まとめ

これで一通りSTM32の開発環境が整ったと思います. 

自分の使いやすいエディタで良きSTM32ライフを!

 

今年度機体バグ備忘録(順次追加予定)

  • モータ・モータドライバ発熱

いらないクソデカコンデンサを撤去して解決

  • 右側エンコーダが読めない

未解決

手で回すと正しい値が取れるが直進方向に制御をかけると読み取れなくなりおおよそ値が1/4.5くらいになる

全然わからん

  • stm32に書き込めない

参照:OpenSTM32 Community Site | Connect to custom board STM32L053 - STLINK-V2 - SWD

/open-ocd/0.9.0/share/openocd/scripts/board/st_nucleo_f4.cfgの最終行の

reset_config srst_only srst_nogate   を reset_config srst_nogate   に書き換えたら書き込めるようになった

再出現は警戒

  • if文の挙動がおかしい

 

正しく動くなら半周回転して止まるはず

これをループで回してるけど1回目の処理で角度(ジャイロで積分されている値)にM_PI(任意の数字)が代入されてるっぽい

機体をマイナス方向に回すと初期位置に戻る力が加わる

わけわかんね

この機体の中では180°(3.14[rad])が360という数値に近いらしい

ifの条件文を(角度<360)にすると意図通りの挙動をする

おそらくターン速が決まらなかったのもこれが原因

ただしモータに制御をかけず半周機体を回すと180°は3.14[rad]という正しい値を出力する

わけわかんね

  • 減速が明らかにおかしい

減速が明らかに強い

距離を読み違えている?

加速は(おそらく)問題なし

 

総括