diff --git a/beginner-tutorial/1_STM32CubeIDE.md b/beginner-tutorial/1_STM32CubeIDE.md new file mode 100644 index 0000000..da3c98e Binary files /dev/null and b/beginner-tutorial/1_STM32CubeIDE.md differ diff --git a/beginner-tutorial/2_PINsettings.md b/beginner-tutorial/2_PINsettings.md new file mode 100644 index 0000000..bcc8dea --- /dev/null +++ b/beginner-tutorial/2_PINsettings.md @@ -0,0 +1,19 @@ +# PIN設定をしよう +## そもそもの仕組み +ロボコン(特に低レイヤ)では主に、 +- GPIO + - PWM(GPIOの応用的なもの) + - UART・USART + - SPI + - I2C + - CAN +- TIMER +- ADC + +これらを使ってロボットを動かします。そして、ロボットの心臓ともいえるのがマイコンです。マイコンからたくさん生えている脚のようなもののことを **PIN** といいます。 +マイコンは、PINに加わる電圧によって制御を行います。これが、あの小さなマイコンが動作している基本的な考え方です。 +下の画像のように、STM32CubeIDEに表示されている小さな長方形たちが現実のマイコンのPINと対応しています。よく見ると分かりますが、PINにはそれぞれ、PA0やPC3、その他いろいろな名前がついており、各PINによってそれぞれできることが違います(先ほど挙げたような通信方法も特定のPINで行います)。 + +![alttext](images/scr_2025-08-29_145702.png) + +さて、ここからはいよいよ実際にモノを動かしていきます。ここから先は、次回(第一弾)はGPIO、第二弾はPWM制御、第三弾はUART通信(USART通信もやるかも...?)、第四弾はタイマ割込み、第五弾はCAN通信をやっていく予定です。気合入れていきましょう。 diff --git a/beginner-tutorial/3_GPIO.md b/beginner-tutorial/3_GPIO.md new file mode 100644 index 0000000..d40e517 --- /dev/null +++ b/beginner-tutorial/3_GPIO.md @@ -0,0 +1,168 @@ +# GPIOをやろう +## GPIOとは? +我々が今からやっていくのはGPIOです。GPIOとは、制御方法の中でも比較的簡単なものです。電圧の高い状態(High)と、電圧の低い状態(Low)をそれぞれ1と0に見立てて、LEDのON・OFFなどを行う制御方法です。言葉で説明するより実際に手を動かしたほうがわかると思うので、早速やっていきましょう。 +## PIN設定とコーディング +まずは、STM32CubeIDE(めんどくさいのでこれ以降はSTM呼びでいきます)を起動して、PIN設定の画面を開きます。初回の時点から何もいじっていなければ起動後に勝手に設定画面になっているはずです。もしもコードの画面になっている場合は、下の画像の一番下にある、**test_play.ioc** と書いてあるところをダブルクリックすると開けます。 + +![a](images/s_2025-08-29_174240.png) + +下のようになればOKです。 + +![alt text](images/rimage-0.png) + +PIN設定をします。今回は、基板についている3つのLED1~3を順番に光らせることを最終目的とします。著者が今回使う基板はLED1~3に対応するPIN(つまり、それぞれのLEDに電圧を加えたりするためのPIN)は、それぞれPC10,PC11,PC12だったので、その3つのPINを設定していきます。 +ところで、基板についているLEDに対応するPINは基板によって違います。基板を凝視すればわからないこともないのですが、それはめんどくさいので **回路図** を見ましょう。 +回路図とは、ロボコンの回路班(著者の時代はそう呼ばれている班)が作ってくれる、その基板のどこのPINが何に対応しているかが書いてある図です。 + +![alt text](images/shot.png) + +これはその回路図を拡大したものです。左にはPA0やPC10などのPINと、GPIOやLEDなどの情報が書いてあります。親切な人は右側の青文字のように、どのPINがどこに対応しているのかまとめてくれたりします。 +左側をよく見ると、LED1や、CAN1_RXなど、先端がとがってる方が黄色の四角側に向いてるものと、CAN1_TXなどの尖っているのが外側に向いてるものがあります。これは、入力と出力の関係を表しています。わかりやすく言うなら、 **こちら(マイコン)側から出力を決めるときは外側を向いていて、何かが送られてくるとき(マイコンが受信するとき)は内側を向いている。** ということです。 +...実は著者の回路図は誤りがあります。LED1~3のところを見ると、内側を向いています。しかし、LEDの出力は外部から勝手に決まるわけではなく、もちろん我々が制御するので外側を向いているべきです。 ~~回路図の作成者はあとで叱っておきます。~~ PINのことに関して大体のことはわかってきたのでPIN設定の画面に戻りましょう。 +回路図に従い、PC10と書かれている長方形をクリックすると、このようにいろいろ出てきます。 + +![alt text](images/rimage-1.png) + +これは、このPINに備わっている機能の一覧で、たった一つのPINでもいろいろなことができるのが分かります。この一覧から、今回はGPIO_Outputをクリックしてください。これにより、今回このPINが担う機能が決定しました。 +なぜGPIO_Outputを選んだかというと、先ほども言ったとおり、今回はこちら側から電圧を制御してLEDを光らせるので、われわれが"出力する"つまりはOutputというわけです。GPIO_Outputを押すと、下の画像のように四角が緑色になります。これからいろんなPINを設定していくと、緑ではなく黄色になる場合がありますが、それは今後話します。ちなみに、設定したPINの長方形をクリックして一番上のReset_Stateをおすと設定がリセットできます。 +画面左にあるCategoriesから、System_Coreをクリックすると、下の画像のようにDMAや、GPIOなどが出てきます。 + +![alt text](images/rimage-2.png) + +GPIOをクリックして、下の画像にある、黒の三角を押すと隠れていた設定画面が出てきます。 + +![alt text](images/rimage-3.png) + +下の画像のように、SYS、RCC、USART、GPIOなどがあります。これは、System_Core全体の設定選択画面です。今回はGPIOを設定するので、GPIOを選択します。ほかの機能は今後説明していくので、今はそのままで大丈夫です。 + +![alt text](images/rimage-4.png) + +今回PIN設定したPC10の欄の一番右の欄は、Modifiedの欄になっています。 +これは、何か設定を変更したときに、その目印としての役割をします。System_Coreでは、何か設定を変えると勝手にチェックが付くので何もしないで大丈夫です。次回やる、GPIOの進化系のPWMでは自分でチェックを入れることができます。あくまでもただの目印であって制御には何も影響しないので、次回こいつが出てきたときは、チェックを入れても入れなくてもどちらでも構いません。心配ご無用です。 +それではここでPINの設定はおしまいにして、クロック設定に入ります。 +画面の上の方にあるPinout&Configurationの隣にあるClock Configurationをクリックすると、下の画像のような画面が出てきます。 + +![alt text](images/rimage-5.png) + +これは、このマイコンで行う制御の、タイマーや周波数の大本の設定画面です。画面左下にあるHSEおよびHSIは、それぞれ、 +- High Speed Internal oscillator(内部水晶振動子) +- High Speed External oscillator(外部水晶振動子) + +のことです。外部のほうが内部の物よりも精度がいいと言われているので、基本的にはHSEを選択しておきましょう。 + +![alt text](images/rimage-6.png) + +下の画像のように、画面左のInput frequencyを8にし、PLLMを/4,その右の*Nを84にします。 + +![alt text](images/image-41.png) + +Input frequencyとは、今回で言えば外部クロックの周波数であり、その周波数はものによって違います。基板をよく見ると、下の画像のように銀色の物体がついていると思います。これが外部クロックです。そして外部クロックにその周波数が刻印されています。 + +![alt text](images/image-40.png) + +画面右端にあるAPB1 Timerclocksを見ると、84になっていると思います。なっていない人は、そこを選択して84にしましょう。 + +![alt text](images/image-42.png) + +なぜ84に設定するのかは次回のPWMの時にお話しします。 + +先ほどのPIN設定と同様に、PC11、PC12も設定し、 **Ctrl + s** で保存すると、下の画像のようにDo you want to generate Code?つまり、「コードを生成しますか?」と聞かれます。我々が設定したPIN設定とクロック設定に対応したコードを生成するかどうかということです。下にあるRemember my decisionは、Ctrl + sを押されたら次からはDo you want to generate Code?と聞かずに自動で保存するかどうかを聞かれています。これについてはとある事情があるため、いまはチェックを入れないでください。次回これについてお話しします。 + +![alt text](images/rimage-9.png) + +Do you want to generate Code?の画面で、Remember my decisionにチェックを入れずに右下のYesを押すと、下の画像の様なものが出てきます。これは、コードの編集画面を表示するかどうかを聞いています。Yesを押すとコーディング画面に、Noを押すと、PIN設定を保存し、それに応じたコードを生成し、コーディング画面には移動せずにPIN設定の画面のままになります。どちらを押そうが結局はコーディング画面を後で開けるのでYesを押しましょう。Remember my decisionについては先ほどと同じような感じです。 + +![alt text](images/rimage-10.png) +## コーディングをしよう +Yesを押してしばらく経つと、下の画像のように、コーディング画面が出てきます。いよいよコードを書いていきます。 + +![alt text](images/zimage.png) + +ここからは、GPIOで基板のLED1~3を光らせるコードを書いていきます。C言語及びC++で書いていきますが、C言語の知識がないよ~(;´д`)トホホという方は、下に載せておく外部リンクに飛んでお勉強してから戻ってきてください。成長したあなたをお待ちしております。 + +- [苦しんで覚えるC言語](https://9cguide.appspot.com/index.html) +C言語を詳しく学べるサイトです。 +- [C++入門 AtCoder Programming Guide for beginners (APG4b)](https://atcoder.jp/contests/apg4b?_gl=1*11he24b*_ga*MTQ0MTAzNzY2OC4xNzU1ODQzNDY0*_ga_RC512FD18N*czE3NTY3MDE5OTAkbzQkZzEkdDE3NTY3MDE5OTMkajU3JGwwJGgw) +競技プログラミング用のサイトで提供されている学習サイトです。因みに著者はこのサイトでC言語を学びました。 + +さて、いよいよコーディングをするわけですが、下の画像のように、画面左にあるProject Explorerを見てください。 + +![alt text](images/zimage-1.png) + +ここには、現在自分が持っているプロジェクトの一覧が出ています。 +フォルダ名の横にある、「>」こんな感じのマークを押すと、その中にあるフォルダ一覧が出てきます。先ほどの画像のように、test_play→Core→Srcとクリックしていくと、Srcの真下にmain.cという名前のファイルがあります。 「.c」というのは、このファイルがC言語で書いてあるということを表しています。ここを右クリックすると、下の画像のように、ファイルに対する操作が出てきます。 +このうち、Renameをクリックすると、New nameと出てくるので、main.cを、main.cppに書き換えて、OKを押してください。なぜ名前を変えるのかは次回お話しします。 + +![alt text](images/zimage-2.png)![alt text](images/zimage-3.png) + +main.cppに変更できたら、いよいよ書いていきます と、言いたいところですが、もう少しだけお話することがあります。ごめんなさい。 +下の画像のように、先ほどのPIN設定により自動生成されたコードがたくさんあります。見慣れない関数もあると思いますが、HAL_で始まっているものはすべて、HALライブラリで用意されている専用の関数です。 + +![alt text](images/image.png) + +### HALライブラリとは +HALライブラリとは、Hardware Abstraction Layerの略で、STマイクロエレクトロニクスが提供する専用の関数です。これからたくさん使っていくので、どんどん覚えていきましょう。ただ、最初のうちは何もわからないでしょうから、Qiitaで調べたり、いろんなところからコピペしたりして大丈夫です。AIに聞くのも一つの手ですが、ごくまれに一時代前の関数を出してきたりするので注意が必要です。 +先ほどの画像を見ると分かりますが、このファイルにはところどころにUSER CODE BEGINや、USER CODE ENDと書いてあります。これは、BEGINとENDに挟まれたところが我々がコードを書くところだということです。もしもれらに挟まれていないところにコードを書くと、マイコンにコードを書きこむときに自分の書いたコードが消えてしまいます。十分に気をつけましょう。 +## 今度こそコーディングをしよう +大変お待たせいたしました。今度こそコーディングです。main.cppを開き、102行目あたりにあるwhile文のところを見てください。 + +![alt text](images/image-1.png) + +上の画像のように、/* USER CODE BEGIN 3 */と書かれた部分があるので、Shift + Enterで改行し、コードを書きます。今回はLEDを光らせ続けるので、永遠に繰り返すwhileの中に書きます。 +GPIOを使い、PC10,11,12の電圧をそれぞれHighにすることでそれぞれのLEDが光るので、こちら側からPINにかかる電圧を制御します。 +そのために使う関数がコチラ +`HAL_GPIO_WritePin(GPIO〇, GPIO_PIN_n,GPIO_PIN_SET)` +です。 +関数名がHAL_から始まっているので、この関数はHALライブラリの関数です。その名前の通り、GPIOPINに書き込むことができます。 +実際にどのPINに書き込むかは、(GPIO〇, GPIO_PIN_n,GPIO_SET)の部分で決めます。今回はPC10,11,12なので、`HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10,GPIO_PIN_SET)`のように書きます。GPIO_PIN_SETの部分をGPIO_PIN_RESETにすれば、電圧はLowとなり、LEDを消すことができます。 +順番に光らせるといいましたが、このままだとPC10に対応するLED1がただ光り続けるだけになってしまうので、少しの間光り続ける必要があります。そのために使う関数がコチラ +`HAL_Delay(n)` +です。これもHALライブラリの関数ですね。nに待機する時間を入れることでその時間の間現在の状態を維持し続けてくれます。単位はnミリ秒です。今回は`HAL_Delay(500)`としておきましょう。0.5秒間光り続けてくれます。 +関数の紹介はここまでとして、main.cppに書きましょう。PC10点灯→0.5秒待機→消灯→PC11点灯→0.5秒待機→消灯→PC12点灯→0.5秒待機→消灯→PC10点灯...と繰り返すようにしたいと思います。下の画像のように書いてください。 + +![alt text](images/image-2.png) + +WritePinでSET(点灯)し、Delayで0.5秒待機、WritePinで消灯...という風に続いているのがわかると思います。Ctrl + sをして、Build(ビルド)をします。 +ビルドとは、書いたコードが構文的なエラーなどがないかどうかチェックしてくれる機能です。下の画像のように、画面上部のかなづちマークを押すとビルドを開始します。 + +![alt text](images/image-3.png) + +何処にもラーがなく、ビルドが無事成功すると、下の画像のように「0 errors, 0 warnings.」と表示されます。 + +![alt text](images/image-4.png) + +稀に、構文エラーなどでエラーが出る場合があります。その場合は、ほとんどがセミコロン「;」のつけ忘れや、関数名の間違いです。これらに当てはまらないエラーの場合は、自動生成されたコードのどこかを消してしまったりしている場合があります。エラー文をそのままAIにコピペすると、エラー内容と対処法を出してくれることがあります。 +さて、ビルド(エラーチェック)が完了したら次はマイコンに書き込みます。書いたコードをマイコンに書き込みことで、その指示通りにマイコンが動いてくれます。 +では一体何で書き込むのかというと、ST-Linkで書き込みます。ST-Linkとは、PCと接続して、書いたコードをマイコンに書き込むためのツールです。小泉構文のようになってしまいましたが、それだけ知っていれば十分です。 +ST-LinkとPCを接続するコード(プログラムではなく配線の事)は、主にA to CまたはA to mini-Bです。正しくコードを選んで接続できたら、いよいよ書き込みです。もしも下の画像のように"Reason: No device found on target."と表示されたときは、ST-Linkがうまく認識されていないということです。この場合考えられる原因は、 +- PCとの接触不良 +- コードが壊れている +- ST-Linkが壊れている +- 基板への電源供給不足 + +これらでしょう。マイコンを動かすには、というかLEDを光らせるためにはもちろん動力が必要です。マイコンが載っている基板にある電源ポートにモバイルバッテリーなどをきちんと接続して動かしましょう。そうでないと動きません。著者もこの前やらかしました...。 + +![alt text](images/image-5.png) + +うまく接続できたら、デバッグをして動かします。下の画像のように、画面上部の虫のようなマークをクリックするとデバッグが開始します。デバッグとは、マイコンへ書き込むことを意味します。 + +![alt text](images/image-6.png) + +デバッグが完了したら、下の画像のように、再生ボタンをクリックしてください。そうすれば、基板にあるLEDが順番に光るはずです。 + +![alt text](images/image-8.png) + +こんな感じに光ります(gifです。ごめんなさい。) + +![aaa](images/Videotogif.gif) + +こんな感じにうまく順番に光ったら成功です。下の画像のように、赤い四角の停止ボタンを押して、無限ループなので電源をぶち抜くと止まります。 + +![alt text](images/image-9.png) + +ここまでできれば今回の目的は達成です。見事にGPIOを使ってLEDの制御ができました。次回は、GPIOの進化系、「PWM」についてやっていきます。お楽しみに! + +2025/09/01 NAMATAMAGO314 + + + diff --git a/beginner-tutorial/4_PWM.md b/beginner-tutorial/4_PWM.md new file mode 100644 index 0000000..2616cc5 --- /dev/null +++ b/beginner-tutorial/4_PWM.md @@ -0,0 +1,134 @@ +# GPIOの進化系「PWM」をやろう +## PWMとは? +今回は第二弾ということで、PWM制御をやっていきます。そもそもPWMとは、前回も言った通りGPIOの進化系(と私が勝手に思っているだけ)です。 +前回のGPIOは、SET(電圧がHigh)とRESET(電圧がLow)、つまりは0か1の極端な制御でした。今回やるPWM制御は、0からMAXまで **Duty比** を変え、滑らかに制御できるかなりいい感じの制御です(小並感)。早速仕組みについてお話します。 +## そもそもの仕組み +先ほど出てきました「Duty比」、これが今回の肝です。 +PWMは、点灯(電圧がHighの状態)と消灯(電圧がLowの状態)を高速に切り替えることによって光を表現して、点灯と消灯の時間の長さを変えることによって、人間の眼では明るさが変わっているように見えるのです。PWM波形は、この点灯時間と消灯時間をセットで一つの周期としており、1周期の時間の長さと点灯の時間の長さの比をDuty比といいます。これが基本的なPWMの仕組みです。 +Duty比を実際に動かして理解できる簡単なツールを作ったので、下のリンクからぜひ遊んでみてください。 + +[Duty比体験ツール](https://www.desmos.com/calculator/vnzy8slrl3?lang=ja) + +## 今回やること +早速やっていくわけですが、今回はPWM制御でテープLEDを光らせていきます。 +テープLEDとは、薄くてふにゃふにゃで、R(Red)・G(Green)・B(Blue)の三色で光るLEDです。3色それぞれに対応するPINでPWM制御をし、さまざまな色や明るさを表現できます。 +前回GPIOをやった時は、NUCLEO-F446REという基板を使いましたが、今回はNUCLEO-F303K8という基板を使います。何故違うものを使うのかというと、簡単にいえば「そこまで精度を必要としないから。」です。ロボコンの中でも高い精度を求められるものは主にモーターの制御です。パワーがあり、安全面にも大きくかかわるためです。それに対してLEDはただの光ですので、モーターなどに比べて精度を必要としません。そのため、前回のとは別で新しいプロジェクト(PIN設定やコードファイルの総称)を作ります。 +## 早速やってみよう +前回同様STM32CubeIDEを起動します。起動できたら、下の画像のように、画面左上にあるFileをクリックしてください。 + +![alt text](images/image-10.png) + +そうすると、なんだか英語がいっぱい出てくるので、一番上のNewにカーソルを置き、下の画像のように、STM32 Projectをクリックしてください。似たような名前のものがありますが、確実に、STM32 Projectをクリックしてください。 + +![alt text](images/image-11.png) + +ここから先は初回でやった手順と同じような感じです。Board Selectorを選択し、Commercial Part Numberに今回使うマイコンである303K8を入力すると下の画像のようにNUCLEO-F303K8が出てきます。 + +![alt text](images/image-12.png) + +クリックし、画面中央下の方にある星マークをクリックし、Nextを押します。   +下の画像のようにプロジェクト名と使用言語の選択画面が出てくるので、今回はproject nameはtest_play2にして、前回同様c++を選択してFinishをクリックすると自動作成が始まります。途中、何か質問が来たらYesを押しましょう。 + +![alt text](images/image-13.png) + +さて、どのPINがRGBに対応するのか確認するために、回路班から拝借した回路図を見たところ、下の画像のようにRがPA5、GがPA3、BがPA6に対応しているので、それに従ってPIN設定をしていきます。 + +![alt text](images/image-14.png) + +PA5のところをクリックし、TIM2_CH1を選択します。PWM制御では、PWM波形を出力するPIN(今回で言えばPA3,5,6)はそれぞれ固有のタイマーを持ちます。PIN設定ではそのタイマーを決めます。 +ときどき下の画像のように、選択できるタイマーが複数個ある時がありますが、その場合はタイマー番号の小さい方を優先的に選びましょう。下のケースではTIM3を選びます。 + +![alt text](images/image-15.png) + +PA3,5,6それぞれタイマーを決めると、PINの長方形が灰色から黄色に変わりました。これは、「PINの機能は決まったけど数値が決まってないよ」という状態です。前回のGPIOではそのような数値は必要なかったのでPINの機能を決めたら緑色になっていました。 +ここでいったん、Clock Configurationのほうを開きます。 + +![alt text](images/image-16.png) + +前回同様、画面左のHSIを、HSE(外部クロック)に設定します。 +そうしたら、下の画像のように、HSEのところを/2にしてください。 + +![alt text](images/image-18.png) + +そしたら、下の画像のようにHSI,HSE,PLLCKが並んでいるところを、PLLCKをクリックしてください。 + +![alt text](images/image-19.png) + +画面右のAPB1 timer clocksを60にしてEnterを押してください。 + +![alt text](images/image-17.png) + +クロック設定が出来たら、一度PIN設定のほうに戻り、CategoriesからTimersをクリックしてください。ここでは、PINに設定したタイマーの細かな数値設定ができます。 + +![alt text](images/image-20.png) + +今回はTIM2とTIM3を使うので、まずはTIM2の設定からします。 +一覧からTIM2をクリックすると、いろいろな設定項目が出てきます。そうしたら、下の画像のようにClock SourceをInternal Clockに設定し、Channel1,4をそれぞれPWMGenelationCH1,4に設定してください。 + +![alt text](images/image-21.png) + +大本のクロックは外部クロックですが、TIM2,3はInternal Clock(内部クロック)を使うことで固有の周波数にすることができます。そのため、Clock SourceをInternal Clockに設定したのです。また、PWMGenelationは、その名の通りPWM波形を出力する機能なので、CH1,4をそれぞれPWMGenelationに設定しました。 +次に、下の画像のように先ほど設定していた場所の下にあるConfigurationの、Parameter Settingsをクリックしてください。そうすると、TIM2に関する様々な数値設定が出てきます。 + +![alt text](images/image-22.png) + +そしたら、下の画像のようにCounter SettingsのPrescalerを59,Counter Periodを999に設定してください。 + +![alt text](images/image-29.png) + +ここで、Counter SettingsのPrescalerを59,Counter Periodを999,APB1 timer clocksを60にした理由をお話しします。 +まず、大本のクロックの周波数がAPB1 timer clocksの値であり、今回の場合は60MHzです。そして、Prescalerは「APB1 timer clocksを遅らせて、使いやすくするための値」です。この値が大きいほど、TIMが使うタイマークロックのスピードが遅くなります。 +次に、Counter Periodは、「APB1 timer clocksをPrescalerを使って遅らせて出来上がったタイマークロックの周波数を決める値」です。この値が大きいほど周波数が高くなります。 +APB1 timer clocksの60MHzでは周波数が高すぎるので、これらで使いやすい速さにしているのです。最終的なTIMの周波数fを表す式は、 + +f=(APB1 timer clocks)/((Prescaler+1)(Counter Period+1)) + +です。今回の場合だと、計算した結果、fは1KHzになります。これが、TIM2の周波数です。 +前回のF446REのように、APB1 timer clocksのデフォルト値が84MHzと、比較的高周波ならそのままでよいのですが、今回の303K8はデフォルト値が低すぎるので60MHzに設定しました。これぐらいの計算なら手計算でもできると思いますが、計算合ってるかちょっと心配だよという人のために計算ツールを作っておきました。 + +[周波数計算機](https://www.desmos.com/calculator/01muv0f9tb?lang=ja) + +そして、下の画像のようにHCLKの下に周波数の最大値が描かれています。HCLKはなるべく最大値に近くなるようにAPB1 timer clocksを設定しましょう。これが前回、「次回お話しします」と言っていたAPB1 timer clocksを84にする理由です。 + +![alt text](images/image-24.png) + +TIM3も同じ数値で設定して、Ctrl + sで保存し、前回同様いくつかの質問に対してYesを押すとコードが自動生成されます。 +## コーディングをしよう +さて、今回は何度も言いますがPWMでRGBを制御していろんな光を表現します。今回使う関数はこちら +`HAL_TIM_PWM_Start(&htim〇, TIM_CHANNEL_~)`と、`__HAL_TIM_SET_COMPARE(&htim〇, TIM_CHANNEL_~, x)` +です。どちらもHALライブラリの関数です。 +HAL_TIM_PWM_Start(&htim〇, TIM_CHANNEL_~)は、PWMに使うタイマーを開始させる関数です。 +例えば今回のPA6のタイマー(TIM3 CHANNEL_1)の場合、〇には3,~には1が入り、`HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL1)`となります。これでTIM3CHANNEL1のタイマーが開始しました。 +これは必ず `__HAL_TIM_SET_COMPARE(&htim〇, TIM_CHANNEL_~, x)` よりも前に書いてください。そうしないとタイマーが開始しません。 +続いて`__HAL_TIM_SET_COMPARE(&htim〇, TIM_CHANNEL_~, x)` です。この関数は、指定したPINのタイマーに応じてこれぐらいのDuty比で出力します という関数です。PA6のタイマー(TIM3 CHANNEL_1)の場合、先ほど同様〇には3,~には1が入り、xには点灯時間のカウント数を入れます。 +Duty比についてですが、xに入る値の最大値は我々が設定したCounter Periodの値によって変わります。Counter Periodの説明は先ほどしましたが、もう少しわかりやすく言うと、「1周期の間に何回カウントするか」ということです。この値が何に使えるかというと、もしもCounter Periodが999だった時、1周期の間に1000カウント(Counter Period+1がカウント数になります。)するので、そのうちの何カウント分光らせるかを考えることができます。 +例えば1周期のカウント数が1000、Duty比が50%の時は、1000カウントのうち500カウント点灯させることになります。Duty比が1%の時は、1000カウントのうち10カウント点灯させることになります。 +少しややこしいですが、Duty比(パーセント表記)=100*(x+1)/(Counter Period+1)となります。つまり、xの最大値=Counter Periodなのですが、ややこしい場合はxの最大値=Counter Period+1でも最大出力は出るのでこっちを使っても大丈夫です。 +早速コーディングをしましょう。 +今回は、Rがフェードイン→フェードアウト→Gがフェードイン→フェードアウト→Bがフェードイン→フェードアウト→RとG同時に最大点灯→消灯→BとG同時に最大点灯→消灯→すべて最大点灯→消灯 +をやります。前回同様、下の画像のようにmain.cを開きます。 + +![alt text](images/image-25.png) + +タイマー開始の宣言は一度だけでいいので、100行目の、 USER CODE BEGIN 2の下に書きます。下の画像のようになります。 + +![alt text](images/image-26.png) + +続いて、111行目あたりにある USER CODE BEGIN 3の下に書いていきます。フェードイン及びフェードアウトは段階的に出力を変えるのでfor文を使います。フェードがゆっくりに見えるようにHAL_Delay()を入れておきましょう。同時点灯は__HAL_TIM_SET_COMPARE(&htim〇, TIM_CHANNEL_~, x)を二行書けばいいので簡単です。 +あくまでも一例ですが、下の画像のようになります。 + +![alt text](images/image-27.png) + +同時点灯は下の画像のようになります。 + +![alt text](images/image-28.png) + +Ctrl + sで保存します。そしたら、前回同様にファイル名をmain.cからmain.cppに変えてからビルドをしてください。0 error, 0 warnigsとなれば完了です。 +ここで、前回、「次回お話しします」と言っていたmain.cppにする理由をお話します。 +前回も今回もライブラリの関数を使ってきたわけですが、ライブラリを使うためにはc++にしなければならず、ビルドもcppじゃないと行ってくれない(らしい)ので、コーディング後にはcppにしています。 +cppにした後、時々PIN設定のほうを間違えている場合があり、それをPIN設定画面で修正することがあります。その後、Ctrl + sをした時、mainファイルが.cppだと、それとは別に新しくmain.cファイルが自動生成されます。これは、自動生成が.cファイルでしか行ってくれないため、もともと書いていたmain.cppファイルは別物として認識されてしまうためです。これを避けるためには、自動生成される前に.cに書き換えておく必要があり、前回紹介したRemember my decisionにチェックを入れてしまうと、Ctrl + sをした時に確認の画面を出さずに、つまりはこちらに確認の隙を与えずに生成してしまうため、Remember my decisionはチェックを入れないことを推奨します。 +さて、ビルドが終わったらいよいよ動かします。前回同様ST-Linkを接続し、虫のマークを押してデバッグします。再生ボタンを押したら、いい感じに光り出します。 +もしうまく動かなかった場合は、モバイルバッテリーなどの電源供給ができていないか、どこかの設定をミスってる可能性があります。どこを確認してもミスが無かったのに動かないという場合は、回路班を疑いましょう。はんだ不良などの可能性があるからです。 +今回のPWM制御はこれにて終了です。次回はUART通信をやります。お楽しみに! + +2025/09/03 NAMATAMAGO314 diff --git a/beginner-tutorial/5_UART.md b/beginner-tutorial/5_UART.md new file mode 100644 index 0000000..ae0ad6b --- /dev/null +++ b/beginner-tutorial/5_UART.md @@ -0,0 +1,152 @@ +# UART通信をやろう +## UART通信とは? +今回はUART通信をやっていきます。UARTとは、Universal Asynchronous Receiver/Transmitterの略で、異なる機器同士がデータをやり取りするための仕組みです。 +ロボコンでは、PCと基板(マイコン)、基板と基板など、機器間で通信をすることでデータをやり取りしロボットを動かしています。通信というぐらいなので、 **送信側** と **受信側** +の二者がいます。今回はTera Term という送受信アプリとマイコン(NUCLEO-F446RE)を使って、PC-マイコン間通信をします。具体的には、 +Tera TermからDuty比(パーセンテージ)を送る → マイコン受信成功したらメッセージをTeraTermに送信 → マイコンでPWMでLED光らせる +という通信をします。 +## TeraTermを入れよう +まずはインストールをします。下のリンクをクリックして、下の画像のようにinstallerをクリックしてダウンロードしましょう。2025年9月現在の最新バージョンの画像です。 +[TeraTermインストールリンク](https://github.com/TeraTermProject/teraterm/releases) + +![alt text](images/image-30.png) + +クリックしてファイルダウンロードできたら、どの機能を追加でダウンロードするか選択できるので、下の方の機能は全部クリックしておきましょう。機能は多い方がいいですからね。出来ましたら、一度閉じて、UARTの仕組みについて先にお話しします。 +## そもそもの仕組み +UART通信は、2本のケーブルを使って送信側と受信側でデータをやり取りする通信の事で、クロック信号などの外部信号ではなく、あらかじめ決められた通信速度(ボーレート)で通信することが特徴です。ボーレートとは、1秒間に何bit遅れるかを表す指標で、115200bps,96000bpsなどがよく使われます。bpsはbits per second(ビット パー セカンド)のことです。 +## 早速やってみよう +さて、先ほどもいったとおり、今回はPCとマイコンでUART通信をします。回路班が基板を用意してくれたので、回路図に従って今回もPIN設定をしていきます。 +今回のマイコンは受信者側と送信者側の双方を担います。UARTに使うPINは **USART_TX** と **USART_RX** です。TX(Transmit:送信)と、RX(Receive:受信)のPINです。 +ただ、前回のF303K8はテープLEDを使うために回路班に作ってもらった基板でしたが今回著者が使うのはST公式が提供している基板なので、テープLEDを光らせることに特化していません。つまり、3色それぞれの出力ピンがばらばらに配置されているのです。そのため、テープLEDではなく3つのLEDを別のブレッドボードに用意し3色のLEDを光らせることにしました。下の画像がそれです。回路班のT君に感謝。 + +![alt text](images/image-31.png) + +PA8,PB4,PB10をそれぞれ黄,白,緑に対応させています。 +いつも通り新しいプロジェクトを作成して、PIN設定画面を開きます。PWMは前回やったのでざっくりと説明します。 +PA8,PB4,PB10をそれぞれTIMを設定し、Prescalerは83,Counter Periodは999にしましょう。ここからも分かる通り、今回はAPB1 timer clockは84MHzです。 +UART_TX,RXについては下の画像のようにデフォの状態で既に設定されています。 + +![alt text](images/image-32.png) + +画像では、USART_RXと表示されています。これは、USART(Universal Synchronous/Asynchronous Receiver Transmitter)の略であり、Synchronous/Asynchronousはそれぞれ「同期/非同期」という意味で、最初に言ったクロック信号との同期の事です。USARTは外部クロックとの同期も可能であり、UARTの上位互換というわけです。ここで一度USARTの設定を外し、もう一度付け直してください。そうすると、USART2_TX,USART2_RXのようになります。これは、USART1はPA9やPA10が主に担当しており、PA2などはUSART2を使うからです(多分)。 +ここで、下の画像のようにCategoriesからConnectivityをクリックし、USART2をクリックすると、上の方にMode選択があるので、Asynchronous(非同期)を選択してください。そして、下の画像のようにParameter Settingsを選択すると、いろいろな数字が出てきます。 + +![alt text](images/image-34.png) + +Baud Rateは先述の通り通信速度です。Word Lengthは送る文字の長さです。UARtでは1文字ずつ2進数に変換してから送信するのですが、2進にした後の長さが8bitということです。 +Stop Bitsは「ここで文字列は終わりですよという合図になる文字」の長さです。あとでこれについてお話しますが今はこのままでOKです。 +Data Directionはマイコンの送受信機能に関する項目で、今回はどちらも担うのでReceive and Transmit(送受信両方)のままでOKです。Over Samplingは受信精度向上のための設定であり、そのままでOKです。 +下の画像のようにNVIC Settingsを開くとUSART2 global interruptというところが出てきます。 + +![alt text](images/image-35.png) + +Enabledにはチェックを入れましょう。割り込み処理を行うためです。そしたら、Clock Configulationを開き、いつも通りHSE,PLLCLKを選択し、APB1 timer clockは84にしてEnterを押します。自動調整が終わったら、Ctrl + sで保存し、コード生成が始まります。 +## UARTのお話 +UARTで文字を送る時、データは2進に変換され、スタートビット、パリティビット、ストップビットをくっつけて送信します。 +送信データ構成:[[スタート] [2進のメッセージ] [パリティ] [ストップ]] +こんな感じです。それぞれ、 +- スタートビット(1bit): データ送信開始を示す(常に0) +- データビット(8bit): 2進のデータ(通常8bit) +- パリティビット(1bit): エラー検出用 +- ストップビット(1bit): データ送信終了を示す(常に1) + +となっています。 +## コーディングをしよう +今回はPCからデータを送られたらマイコンで受信→割り込み→PWM出力でLED光らせる→メッセージをPCに送信という流れです。 +今回初めて出てきた「割り込み処理」ですが、例えばUART割り込みの場合は、「UARTで何かを受け取った時に今行っている処理を中断し、割り込み関数の中の処理を行い、完了したら中断した処理に戻る」という動きをします。 +UART割り込みに使う関数がコチラ +`HAL_UART_Receive_IT(&huart〇, &a, n);`と`void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){}`です。どちらもHALライブラリの関数です。 +`HAL_UART_Receive_IT`はその名の通り受信の関数で、〇にはPIN設定で我々が選択した数字が入ります(今回はUSART2だったので2が入る)。 +aは受信したメッセージを入れる変数で、今回はuint8_t型です。型は文字ではなく数ですが、UARTのお話でも言った通りその数により文字を表現しているので、 +`if(rx_data == '/')`のように中身を文字として使うことができます。nは受信するビット数で、nビット(n文字)受信するということです。 +`void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)`(名前長すぎだろ)は割り込み処理の内容を書きます。UART_HandleTypeDef はUARTに必要な情報が入っており、huartはどのuartを使うかを表します。この部分はほぼ弄らないので気にしないでください。先に説明したHAL_UART_Receive_ITの後に書きます。 +下の画像の様に、400~450行目あたりにUSER CODE BEGIN 4というところがあります。割り込み処理はここに書くことがほとんどです。 + +![alt text](images/image-36.png) + +下の画像のように、HAL_UART_Receive_ITをmain関数の中に書いておきます。前回やったPWMのタイマー開始の関数も書きます。これにより、UART通信で最初の一文字目を受信する準備が完了します。 +先ほども言った通り今回はUSART2だったのでhuart2,受信した文字はrx_dataという変数に入れることにしました。 + +![alt text](images/image-37.png) + +PWMのDuty比はテラタームから送りますが、例えば黄32%,白0%,緑100%の場合は032/000/100というように、余った桁を0で埋め、数字同士を/で区切ったものを送ります。そのため、マイコンは受信データからスラッシュを取り除き、パーセンテージをもとに__HAL_TIM_PWM_Startで設定する点灯カウント数を計算する必要があります。 +著者が一例として書いたコードは下の通りです。USER CODE BEGIN 4の下に書くコードです。まぁかなり前回と比べてややこしいのでコピペでもいいですが(もちろんPIN番号とかは各々変えて)、コードになれるために自分で書いてもいいでしょう。 +```cpp +void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){ + if (huart->Instance == USART2){ + if(rx_data != '/' && rx_data != '\n' && rx_data != '\r' && j<3){ + rxbuffer[i][j] = rx_data; + j++; + }else if(j>0 || rx_data == '/'){ + rxbuffer[i][j] = '\0'; + i++; + j=0; + if(i==3){ + for(int num = 0;num<3;num++){ + duty[num] = 0; + for(int k=0;k<3;k++){ + duty[num] = duty[num]*10 + (rxbuffer[num][k]-'0'); + } + if(duty[num]>0){ + duty[num] = duty[num]*10 - 1; + if(duty[num]>999){ + duty[num] = 999; + } + } + } + __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, duty[0]);//Yellow + __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, duty[1]);//White + __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, duty[2]);//Green + char msg[64]; + sprintf(msg, "successful Y:%d, W:%d, G:%d\r\n",duty[0], duty[1], duty[2]); + HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY); + i=0; + } + } + HAL_UART_Receive_IT(&huart2, &rx_data, 1); + } +} +``` +解説していきます。`if (huart->Instance == USART2)`は、UARTで受信したときに、それがUSART2に来たものであるかを確認しています。 +`if(rx_data != '/' && rx_data != '\n' && rx_data != '\r' && j<3)`は、メッセージのうちそれがDuty比を表す数字の一部であるかを確認しています。テラタームの仕様についてはあとでお話ししますが、メッセージの最後ですよという目印として改行コードをメッセージの最後にくっつけて送られます。それが\nと\rです。メッセージのうち改行コードでもなくスラッシュでもない文字、つまりDuty比を表す数字を判別しています。`rxbuffer[i][j]`はchar型の配列で、`rxbuffer[i][j] = rx_data;`によりrx_dataに格納されたデータを一つずつ文字として格納しています。 +`else if(j>0 || rx_data == '/')`は、スラッシュ及び改行コードのうち、2個目のスラッシュかもしくはメッセージの最後の改行コードであるかを確認しています。`rxbuffer[i][j] = '\0';`の\0は終端文字と呼ばれ、これが無いとデータがどんどんずれてしまいます。i==3で3色すべてのメッセージを受信し終えたことを確認し、実際の出力へと変換していきます。 +`duty[num] = duty[num]*10 + (rxbuffer[num][k]-'0');`ですが、duty[num]はint型の配列で、Duty比の情報に計算を加えて最終的にこれが点灯カウント数になります。 +`rxbuffer[num][k]-'0'`はアスキーコードにより文字を数値に変換しています。文字にはそれぞれ固有のアスキーコード(ユニコードみたいなやつ)があります。0~9の数字にももちろんアスキーコードがあり、 **「数字xのアスキーコード」-「0のアスキーコード」** を計算すると、文字としてではなく、数字としてのxの値が出てきます。これによりrxbuffer[num][k]-'0'は、rxbuffer[num][k]に格納された文字を数値に変換しているのです。 +10倍して桁移動し、桁が一つ下のものを足す操作を3回行い、全てのけたを用意できたら、前回やった__HAL_TIM_SET_COMPAREでPWM出力をします。 +`sprintf`は文字列を作る関数で、`#include `により使用できるライブラリの関数です。また、strlen(msg)は文字列の長さを求める関数で、`#include `により使用できるライブラリの関数です。`HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);`はHALライブラリの関数で、UARTで文字を送信する関数です。huart2が今回のUARTで、(uint8_t*)msgが送るメッセージ、先ほども言ったとおりstrlen(msg)は送る文字列の長さで、HAL_MAX_DELAYは待機時間です。 +処理が終わったら`HAL_UART_Receive_IT(&huart2, &rx_data, 1);`で次の受信を待ちます。rxbufferなどは66行目あたりの USER CODE BEGIN 0の下で定義しておきましょう。 +## Tera Termから送信しよう +あとはTeraTermの設定を終えれば通信の準備が整います。頑張りましょう。 +ST-Linkを接続した後にテラタームを起動します。ST-Linkが正しく認識されていると、下の画像のように「新しい接続」の設定画面が出てきます。下のシリアルのところを見ると、COM10の後にST-Linkの名前が表示されます。 + +![alt text](images/image-38.png) + +表示されていない場合は、ST-Linkが正しく接続されていない可能性があります。一度テラタームを閉じ、ST-Linkを付けなおしてから起動してみてください。 +COM10のところは人によって変わります。 +接続できたら、先にSTMで描いたコードをマイコンに書き込みましょう。 +テラタームの設定画面の「シリアル」を選択し、OKを押したら、下の画像のように設定のところからシリアルポートを選択してください。 + +![alt text](images/image-43.png) + +そうすると、下の画像のようにメッセージ送信に関する設定が出てきます。 +ポートはボーレート、その下はUARTのお話で説明したデータの設定です。マイコンの受信及び送信速度を115200bpsにしたので、テラタームのほうもそれに合わせて115200にします。そうしないと、通信速度が合わなくなるからです。必ず受信側と送信側の速度を同じにしましょう。その下の設定は弄らなくて大丈夫です。 +次に、シリアルポートの左の「端末」のところをクリックすると、下の画像のように改行コードなどの設定が出てきます。 + +![alt text](images/image-44.png) + +ローカルエコーにはチェックを入れましょう。これは、テラタームで自分が入力している文字が画面に表示されます。改行コードの受信のところを押すと、下の画像のようにCRやLFが出てきます。 + +![alt text](images/image-45.png) + +CRは改行の判定で、LFは行の先頭へ移動することを意味します。受信をCR+LFに設定しておくと、受信成功のメッセージを受け取った後、次のメッセージを受け取ったら次の行の先頭から表示してくれます。なので、受信はCR+LFに設定します。送信はそのままでOKです。 +設定が出来たら右下のOKを押して、送るメッセージを入力してEnterを押すと、送受信成功の場合は下の画像のようにメッセージが送られてきます。 + +![alt text](images/image-46.png) + +間違えて100よりも大きな数字を送っても、3桁である限り100%判定となり、PWMは999になります。しかし、3桁以外で送ってしまうとその下のメッセージのように値がおかしくなります。 + +![alt text](images/image-47.png) + +うまくできたら、今回はこれにて目標達成です。お疲れさまでした。次回はタイマー割り込みをやります。お楽しみに! + +2025/09/10 NAMATAMAGO314 \ No newline at end of file diff --git a/beginner-tutorial/6_Timer.md b/beginner-tutorial/6_Timer.md new file mode 100644 index 0000000..4dc7d24 --- /dev/null +++ b/beginner-tutorial/6_Timer.md @@ -0,0 +1,69 @@ +# タイマー割り込みをしよう +## タイマー割り込みとは? +今回はタイマー割り込みをやります。タイマー割り込みは、タイマーが設定した時間に達したときにCPUに通知し、割り込み処理を行う仕組みです。つまりは一定間隔で処理をするのです。割り込みの中では恐らく最も簡単です。 +## 早速やってみよう +今回は基板に載っているLEDを使って、0.3秒点灯→0.5秒消灯を繰り返させることを目標とします。 +これぐらいならHAL_Delayやれば割り込みしなくていいだろと思った方もいるかもしれませんが、HAL_Delayの間はなにも動くことができません。そのため、タイマー割り込みすることでHAL_Delayを使わず、かつその間に別の動作もできるような方法にするのです。 +今回はF446REを使ってやります。著者が使う基板の回路図によるとLEDが3つあり、それぞれのPINがLED1:PA10,LED2:PC5,LED3:PC4だったのでこいつを一定間隔でGPIOでやります。早速STMを起動して新しいプロジェクトを作ります。 +PIN設定の画面を開いたら、回路図に従ってPA10,PC4,5をGPIO_Output設定します。次に、下の画像のようにTimerからTIM2を選択し、Clock SourceをInternalClockに設定します。 + +![alt text](images/image-52.png) + +TIMは選択しますがChannelは設定しません。これは、PWMなどはタイマーを使って出力しますが、今回は単純に一定間隔で割り込み処理を呼び出すだけだからです。Prescalerは今回のAPB1timerclockが84MHzなので83,LEDは基本1kHzにするのでCounter Periodは999に設定します。 +ParameterSettingsの横にあるNVICSettingsを開くと、下の画像のようにTIM2 global interruptというものが出てきます。これは、TIM2の割り込みを有効化するかどうかを決めることができます。今回は割り込みをするのでEnabledはチェックを入れましょう。 + +![alt text](images/image-53.png) + +設定できたら、Clock Configurationを開きます。 +下の画像のようにクロックをHSEにし、Input frequencyを20にします。これは前にも言いましたが、使う基板に載っているクロックに刻印されている周波数を使います。著者の基板では20でした。 +APB1timer clockを84にしてEnterを押します。 + +![alt text](images/image-54.png) + +Ctrl + sで保存し、コードが自動生成されます。 +## コーディングをしよう +タイマー割り込みに使う関数がコチラ +`HAL_TIM_Base_Start_IT(&htim2); `と`void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){}`です。どちらもHALライブラリの関数ですね。 +`HAL_TIM_Base_Start_IT(&htim2);`は割り込み開始の関数です。UARTでも似たような関数がありましたね。こいつは1回呼び出せばいいので、main関数に書きます。 +`void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){}`はタイマー割り込み処理の関数です。タイマーが一定のカウントまで到達するとこの関数の中の処理を開始します。前回同様これはUSER CODE BEGIN 4の下に書きます。一例ですが、今回は下の様な感じのコードを書きました。 +```cpp +void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) +{ + if (htim->Instance == TIM2) { // TIM2からの割り込み + led_timer++; + if (led_state == 0 && led_timer >= 500) { // 消灯500ms経過 + HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_SET); + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_SET); + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, GPIO_PIN_SET); + led_state = 1; + led_timer = 0; + } + else if (led_state == 1 && led_timer >= 300) { // 点灯300ms経過 + HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_RESET); + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_4, GPIO_PIN_RESET); + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, GPIO_PIN_RESET); + led_state = 0; + led_timer = 0; + } + } +} +``` +`htim->Instance == TIM2`でTIM2からの割り込みであることを確認し、led_timerを増やしていきます。tim2の周期は1kHzなので、1秒に1000回割り込みをします。つまり、1ミリ秒ごとに割り込みをするわけです。そのたびにled_timerを増やすことで、現在何秒経過したかが分かります。 +予めled_stateとled_timerは0にしておいて、消灯からスタートします。led_timerが500、つまり消灯から0.5秒経過したら点灯させ、led_stateを1にします。led_stateの値で、現在消灯か点灯どちらなのかが分かります。main関数の中と変数の宣言はそれぞれ下の画像のようにしました。 + +![alt text](images/image-55.png) + +書き終わったら、main.cをmain.cppにしてbuildをします。0error , 0warningsになればOKです。エラーが出た場合、設定したtimと割り込み開始関数にかいたtimの番号が違っている場合があります。 +ST-Linkを接続し、虫のマークを押してDebugし、再生ボタンを押せば基板にある小さなLED達が点滅します。Debugできない場合は、ST-Linkの接続不良か、モバイルバッテリーなどの電源供給ができていない可能性があります。 +うまくできたら、今回の目標は達成です。 +## エンコーダーモードについて +タイマー設定には、下の画像のようにEncoder Modeというものがあります。これは、モーターの回転カウント数を読み取る機能です。 + +![alt text](images/image-64.png) + +モーターにロータリーエンコーダ(通称ロリコン)を取り付けると、モーターが今どれだけ回転しているかが分かります。その情報を一定間隔で受信することで、それを使っていろいろな制御ができます。 + + +お疲れさまでした。次回はCAN通信をやります。お楽しみに! + +2025/09/16 NAMATAMAGO314 \ No newline at end of file diff --git a/beginner-tutorial/7_CAN.md b/beginner-tutorial/7_CAN.md new file mode 100644 index 0000000..0ef1739 --- /dev/null +++ b/beginner-tutorial/7_CAN.md @@ -0,0 +1,297 @@ +# CAN通信をやろう +## CAN通信とは? +今回やるのはCAN通信です。CANはController Area Networkの略で、今までのUARTやGPIOとは違う点がいくつかあります。 +前回やったUARTはマイコンと一つのデバイス間で行われる1対1の通信ですが、CANはマイコン一つに対し複数のデバイスと通信することができ、たった2本のケーブルで行うことができます。 +また、他の通信ではモーターの回転量などの情報を取得するためにはこちら側から関数で取得する必要がありますが、CAN通信ではモーターから勝手に送られてきます。CANはノイズに強いという特徴も持っています。これは、UARTなどは一つの線で電圧の大きさでデータのやり取りを行いますが、CANはCAN_HighとCAN_Lowの二線の電圧の差の大きさで通信をしています。 +そして、通信中に何らかの影響でノイズが入り、電圧が乱れてしまうことがあります。CAN以外の通信だと、この場合通常通りの通信ができなくなります。しかし、CANの場合は2線があり、ノイズは必ず両方に同じだけ影響するので結局のところ差が保たれ、通常通りに通信ができます(下の画像の通り)。これがCANがノイズに強い理由です。 + +![alt text](images/image-51.png) + +同じ線で複数の機器と通信できるのは、CAN IDという識別用のIDを機器それぞれが持っていて、それによって判別ができるからです。CANIDは16進数であらわされ、「0x100」や、「0x03A」といった感じになっています。0xは、この数字が16進数であることを表しています。今回はモーターからCANを受け取ってCAN割り込みをするのですが、その際もメーカーによってあらかじめモーター固有のIDを決められています。 +### CANフィルター(feat.回路班) +CAN通信はたくさんのマイコン、PCなどを接続することができます。そうすると大きな問題が出てきます。 +「優先順位をどう決めるか」です。 + +優先順位については、IDの数が小さい方が優先されます。具体的な優先順位の決め方は後述しますが、読まなくて結構です。 +「IDが小さいほうが優先されるから、重要な通信のIDを小さい数にした方が良い」ということだけ覚えておいてください。 +まあ、十分速いのであんまり気にしなくてもよい。 +例)非常停止とモーターを回す指令なら、非常停の方が大事なので小さくする、など + +興味があればどうぞ↓ +例えばIDが0x350と0x314と0x271の通信が同時に行われようとしたとしましょう。 +それぞれを16進数から11bitの2進数(後で説明しますが、IDは基本11bitなので)に変換すると +```cpp +0x350 -> 0b01101010000 +0x314 -> 0b01100010100 +0x271 -> 0b01001110001 +``` +になります。優先順位の決め方は、「上の桁から順番に見ていって他のIDが1のときに0になってたやつが優先される」です。 +この場合、 +①まず、3つのIDが競合します。左から3bit目が1,1,0になっているため、0x271が優先され、0x271の情報が送られます +②残っているのは2つで、0x271が送信終了した後、また競合します。今度は左から5bit目で0と1になって、0x314が送信できます +③送信終了後、0x350は競合がいないので、無事、0x350が送信されます +なお、③のときに0x271がもう一回送ろうとしたら、0x271が優先されます。 +このように比較するため、IDの数が小さい方が優先されます。 + +![alt text](images/image-63.png) + +さて、ここまでCANの説明をしてきましたが、もっと詳しく知りたい方はググってください +これもおすすめです↓ +[初めてのCAN/CANFD](https://cdn.vector.com/cms/content/know-how/VJ/PDF/For_Beginners_CAN_CANFD.pdf) + +では本題に入りましょう。 +```cpp + CAN_FilterTypeDef filter; + filter.FilterIdHigh = 0x001 << 5; // フィルターID1 + filter.FilterIdLow = 0x002 << 5; // フィルターID2 + filter.FilterMaskIdHigh = 0x003 << 5; // フィルターID3 + filter.FilterMaskIdLow = 0x004 << 5; // フィルターID4 + filter.FilterScale = CAN_FILTERSCALE_16BIT; // 16モード + filter.FilterFIFOAssignment = CAN_FILTER_FIFO0; // FIFO0へ格納 + filter.FilterBank = 0; + filter.FilterMode = CAN_FILTERMODE_IDLIST; // IDリストモード + filter.SlaveStartFilterBank = 14; + filter.FilterActivation = ENABLE; + + HAL_CAN_ConfigFilter(&hcan1, &filter); +``` + +以上がフィルターの設定になります + +1行目で"CANFilterTypeDef"という型のfilterという名前の構造体を定義しています +2行目以降の"filter.~~"はfilterという名前の構造体の中の~~という意味になります。 +少し飛んで7行目は、受け取った情報をどこに保存するか、ということを定義しています。STM32において、CANで受け取った情報はFIFO0(フィフォゼロ)かFIFO1のどちらかに保存されます。それぞれ3つずつ、受け取った情報を保存できるMailBoxがあります。このFIFOに情報を保存するかを判別するのがfilterになります。また、CANが2つあるマイコンはそれぞれにFIFO0,FIFO1があります。 +11行目はCANのフィルターを有効にするか、を定義します。有効にするときはENABLE、無効にするときはDISABLEにします。有効にしないと使えないので、基本的には有効にしましょう + +最後の行のHAL_CAN_ConfigFilterはCANのフィルターの設定をする関数です。これより上の行は"CAN_FilterTypeDef filter;"の値を決めていっただけです。 HAL_CAN_ConfigFilterの引数を見てください。第一引数はcan1かcan2を選択します。CANがひとつしかない場合、&hcanとなります。第二引数は"CAN_FilterTypeDef filter;"のポインタを渡します。なので、 +```cpp +HAL_CAN_ConfigFilter(&hcan1,&filter); +``` +になります。 + +まあ、ここまではテンプレ通りに書けば問題ないでしょう。本題の本題に入ります。 + +フィルターにはリストモードとマスクモードがあります。リストモードは設定したいフィルターをひとつひとつ羅列していきます。必要なIDが少ないときはこれでいいでしょう。 +マスクモードはフィルターIDとマスクをセットにして使います。 +仕組みは簡単です。「フィルターIDとマスクの論理積と受信したIDとマスクの論理積が一致したら受けとる」です。 +例えば、0x010~0x01FのIDをすべて受け取るとします。 +2進数に変換すると、00000010000~00000011111です。勘のいい皆さんならお気づきかもしれませんが、右から4bitまでは0でも1でもどちらでもいいのです。つまり、左から7bitが"0000001"であればすべて受け取るようにすればよいのです。 +まあ、論理積というのは軽く知っていれば良いです。実際に使うときは、以下の表のようにすれば良いです。 +| When | ID | MASK | +|:--------------:|:------:|:--------:| +| 1 | 1 | 1 | +| 0 | 0 | 1 | +| どちらでもいい | 0 or 1 | 0 | + +簡単にまとめると、MASKは一致していないといけないbitは1, どっちでもいいbitは0にすればよいのです。 + +では、先ほどの例に戻ります。IDは0x010(0b00000010000)として、MASKは左から7bitは一致してなければならず、それより右の4bitはどちらでもいいので、0b11111110000 = 0x7F0とすればよいです。 + +では、これをプログラムにしていきましょう。 +ちょっと間が空いたので再掲します。 +```cpp + CAN_FilterTypeDef filter; + filter.FilterIdHigh = 0x001 << 5; + filter.FilterIdLow = 0x002 << 5; + filter.FilterMaskIdHigh = 0x003 << 5; + filter.FilterMaskIdLow = 0x004 << 5; + filter.FilterScale = CAN_FILTERSCALE_16BIT; + filter.FilterFIFOAssignment = CAN_FILTER_FIFO0; + filter.FilterBank = 0; + filter.FilterMode = CAN_FILTERMODE_IDLIST; + filter.SlaveStartFilterBank = 14; + filter.FilterActivation = ENABLE; + + HAL_CAN_ConfigFilter(&hcan1, &filter); +``` +9行目のFilterModeでリストモードかMASKモードか設定します。この場合は"CAN_FILTERMODE_IDLIST;"なので、リストモードです。MASKモードにする場合は、 "CAN_FILTERMODE_IDMASK;"(語尾が変わっただけ)にします。 + +下の表を見てください。 + +![alt text](images/image-62.png) + +表のように、32bitx2で構成される"フィルターバンク"という、フィルターの情報を保存できるやつがあります。 +2~4行目でそれぞれ表の位置にアクセスしています。 +上記のプログラムはこのフィルターバンク32bitx2をどう使うかを設定しています。 +filter.FilterScaleで16bitモードか32bitモードかを選択します。 +これが結構重要なのですが、黄色いマスは他の情報が入るので、空けなければなりません。 +しかし、単純に +filter.FilterIdHigh = 0x001; +のようにすると、FilterIdHighの領域のなかで、右詰めされてしまいます。しかし、表を見て分かる通り、左詰めしなければなりません。そのため、ビットシフトという、ビットをずらす操作をします。先ほどの例のプログラムは5bit左にシフトしなければなりません。それを行っているのが、" << 5"です。 +使う価値がないので使いませんが、32bitモードの通常のIDを使う場合は21bit左にシフトする必要があるため。" << 21"となります。 + +少し難しいのが拡張IDです。ロボコンでは使う必要はない(2025年現在)ので、軽く説明だけします。読み飛ばしても結構です。 +29bitのうち、上から16bitをHigh, 下13bitをLowに入れます。 +方針としては、 +(1)3bit左シフト +(2)16bit右シフト→Highに代入 +(3)(1)と0xFFFFの論理積(and)をLowに代入 +こんな感じです。参考用に置いておきます(0x01ABCDE0~0x01ABCDEFをMASKモードで受け取る例) +```cpp +filter.FilterIdHigh = (0x01ABCDE0 << 3) >> 16; +filter.FilterIdLow = (0x01ABCDE0 << 3) & 0xFFFF; +filter.FilterMaskIdHigh = (0x1FFFFFF0 << 3) >> 16; +filter.FilterMaskIdLow = (0x1FFFFFF0 << 3) & 0xFFFF; +``` +さて、残るはあと二つ +```cpp +filter.FilterBank = 0; +filter.SlaveStartFilterBank = 14; +``` +ですね。 +フィルターバンクは32bitx2のやつでしたね。じつは、このフィルターバンクが14個か28個あり、それぞれ0~13(または0~27)の番号が振られています。基本的にCANが2つあるマイコンは28個、1つのマイコンは14個らしいです。 +そのうちのどれを使うかを定義するのがfilter.FilterBankです。 +CANが二つあるマイコンは、フィルターバンクをCAN1とCAN2で共通になっています。 +そこで、SlaveStartFilterBankでCAN2で使うフィルターバンクの一番最初の番号を定義します。そのプログラムの中で、最も先に定義されたものが採用されます。この値は0~28で、0のときフィルターバンクを全てCAN2で使い、CAN1の受信はできなくなります(送信はできる) +逆に、28にすると、CAN2の受信ができなくなります。 +また、偶数はCAN1、奇数はCAN2などという風に分けることはできず、N未満はCAN1、N以上はCAN2という風に分ける必要があります。 +特に事情がない場合は、14に設定しておけば良いでしょう +当たり前ですが、CANが一つしかない場合は関係ありません + +まとめ +```cpp + CAN_FilterTypeDef filter;  //filterという名前の構造体を定義 + filter.FilterIdHigh = 0x001 << 5; //IDや + filter.FilterIdLow = 0x002 << 5; //Maskを + filter.FilterMaskIdHigh = 0x003 << 5; //定義 + filter.FilterMaskIdLow = 0x004 << 5; //する + filter.FilterScale = CAN_FILTERSCALE_16BIT; //16bitモードか32bitモードかの選択 + filter.FilterFIFOAssignment = CAN_FILTER_FIFO0; //受信したデータの保存先(FIFO0 or FIFO1) + filter.FilterBank = 0; //使用するフィルターバンク(0~13 or0~27※マイコンによって違う) + filter.FilterMode = CAN_FILTERMODE_IDLIST; //フィルターのモード(IDLIST or IDMASK) + filter.SlaveStartFilterBank = 14; //CAN2のフィルターバンクの始まり(この場合0~13:CAN1,14~27:CAN2) + filter.FilterActivation = ENABLE; //フィルター(有効:ENABLE, 無効:DISABLE) + + HAL_CAN_ConfigFilter(&hcan1, &filter); //定義した構造体を関数に入れてフィルター設定完了 + ``` +※この関数を実行したら、CAN_FilterTypeDef filterの中身は不要なので、二つ以上フィルターバンクを使う場合は、もう一度定義し直せばよいです。 +```cpp + CAN_FilterTypeDef filter; + + ~~フィルターとかモードとかの定義~~ + HAL_CAN_ConfigFilter(&hcan1, &filter); + + ~~もう一度別の設定を定義~~ + HAL_CAN_ConfigFilter(&hcan1, &filter); + + ~~今度はCAN2で~~ + HAL_CAN_ConfigFilter(&hcan2, &filter); +``` +練習問題 +CAN1で、0x100~0x108と、0x001を受け取り、CAN2で0x201~0x204を受けとるフィルター設定を書いてみましょう + +ヒント(右から読んでね) + +うよ考で別は801x0と701x0~001x0 + +### 送信メッセージの形 +UARTでは、送信するメッセージは二進数に変換してストップビットやパリティビットを付け加えて送信していました。CAN通信も独自の加工をして送信しています。 +CANで送信するメッセージは下のような形をしています。 + +[SOF][ID][RTR][IDE][r0][DLC][DATA][CRC][ACK][EOF] + +メッセージにさまざまなものがくっついているのが分かります。 +- SOF: Start of Frame(フレーム開始) +- ID: CAN ID(11bit) +- RTR: Remote Transmission Request +- IDE: Identifier Extension(IDの長さを示す) +- DLC: Data Length Code(送信データのバイト数) +- DATA: データ部分(0~8bit) +- CRC: Cyclic Redundancy Check(データの破損チェック) +- ACK: Acknowledgment(メッセージ受信成功の合図) + +それぞれの機能はこんな感じで、特に重要なのはID,DLC,DATAで、この3要素は私たちの制御に深くかかわってきます。 +## 早速やってみよう +今回はロボマスモーターというCANで動くモーターを動かします。 +STMのLiveExpressionでマイコンに目標値(ロボマスモーターの目標スピード)を設定→CANでロボマスに送信→ロボマスから情報受信→PCにUARTで送信 +このような手順で今回はやっていきます。 +早速STMを起動して新しいプロジェクトを作ります。F446REを使います。 +CAN通信も当然Tx(送信)とRx(受信)が必要です。今回は公式が出している基板(NUCLEO基板)を使います。PIN配置は公式が出しているシートから確認します。下の画像のように、CAN2_TxがPA2,RxがPA3,USART2TxがPB13,RxがPB12でした。 + +![alt text](images/iiimage.png) + +今回はCAN割り込みとUART割り込みとタイマー割り込みをします。ロボマスから送られるCANはUARTに比べて速度がとても速いので、受信するたびにPCに送信するのではなく、一定時間たったら送ります。そのためにタイマー割り込みを使うのです。今回はAPB1timerclockを90MHzにする予定なので、下の画像のようにClockSourceをInternalClockに、Prescalerを89,Counter Periodを9999にして、10Hzの通信速度でPCに送信します。 + +![alt text](images/image-57.png) + +前回同様Enabledのチェックを入れるのを忘れずに。USART2も前のようにModeをAsynchronousにし、Enabledのチェックを入れます。 +次に、下の画像のようにConnectivityからCAN2を選択し、ModeのActivatedにチェックを入れ、パラメータをこのように設定してください。 + +![alt text]() + +Prescalerは以前説明したので大丈夫だと思います。その下のTime Quanta in Bit...は、サンプリングポイントというものを設定するための値です。Segment1と2があります(以降TSeg1,2と呼ぶ)。Baud Rateはcanの通信速度の様なもので、CANは基本的に1Mbps(1000000bps)です。その下のReSynchronization Jump...(これ以降SJWと呼ぶ)は、これまたサンプリングポイントのための値です。ここは基本的に1に設定しておきます。Baud Rateの計算式は下の通りです。 + + Baud Rate = (APB1timerclockの値(単位はHz))/(Prescaler*(SJW+TSeg1+TSeg2)) + +計算が心配な人のためにボーレート計算ツールを作っておきました。 + +[CANボーレート計算機](https://www.desmos.com/calculator/qr4m3il6hh?lang=ja) + +NVICSettingsをクリックし、下の画像のようにCAN2 RX0 interruptにチェックを入れます。今回はCANを受信したら割り込みを行うので、RXの割り込みを有効にします。受信したメッセージはFIFOというものに格納されるのですが、FIFOにはFIFO0とFIFO1があります。基本的にFIFO0を使うので、RX0を有効にするのです。 + +![alt text]() + +それができたら、下の画像のようにSystem CoreからNVICをクリックしてください。 + +![alt text](images/iiiimage.png) + +ここでは、割り込み設定や割込み処理の優先順位などを設定できます。今回のように、タイマー割り込みやCAN割り込みなど複数の割り込みがある場合、同時に割り込みが発生したときの優先順位を決める必要があります。数字が2列並んでいますが、右の列では先ほど言ったような優先順位を決めることができます。数字が小さいほど優先順位が上になります。今回は画像の通りに設定してください。 + +![alt text](images/iiiiimage.png) + +次に、CloclkConfigurationを開きます。 +いつものように基板の外部クロックの刻印を確認し、それに応じてInput frequencyを設定し、HSEを選択します。APB1timerclockは、下の画像のように90MHzにしてください。理由は特にないですが、なんかうまくいったので90にしました。 +ちゃんと知りたい人はデータシートを読みましょう by回路班 + +![alt text](images/image-61.png) + +Ctrl + sで保存し、コードが自動生成されます。 +## コーディングをしよう +CAN割り込みに使う関数がコチラ +`HAL_CAN_Start(&hcan〇);`と`HAL_CAN_ActivateNotification(&hcan〇, CAN_IT_RX_FIFO0_MSG_PENDING);`と`void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan){}`です。全てHALライブラリの関数です。 +`HAL_CAN_Start(&hcan〇);`は通信開始の関数で、今回はCAN2なので〇には2が入ります。`HAL_CAN_ActivateNotification(&hcan〇, CAN_IT_RX_FIFO0_MSG_PENDING);`は特定の条件を満たすと割込み関数を呼ぶもので、今回の様な`CAN_IT_RX_FIFO0_MSG_PENDING`の場合はCANでメッセージを受信したときに割り込みが発生するようになっています。〇には2が入ります。 `voidHAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan){}`は割り込み処理の内容を書きます。一例ですが、下のような感じです。 +```cpp +void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){ + char msg[64]; + sprintf(msg, "Angle:%d, Torque:%d, Velocity:%d\r\n",data[0], data[1], data[2]); + HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), 100); +} + +void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan){ + CAN_RxHeaderTypeDef RxHeader; + CAN_TxHeaderTypeDef TxHeader; + uint8_t TxData[8] = {0}; + uint32_t TxMailbox; + TxHeader.StdId = 0x200; + TxHeader.RTR = CAN_RTR_DATA; + TxHeader.IDE = CAN_ID_STD; + TxHeader.DLC = 8; + + HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData); + for(int i=0;i<3;i++){ + data[i] = (uint16_t)RxData[2*i]<<8|RxData[2*i+1]; + } + TxData[0] = (send_data >> 8) & 0xFF; + TxData[1] = send_data & 0xFF; + HAL_CAN_AddTxMessage(hcan, &TxHeader, TxData, &TxMailbox); +} +``` +`TxHeader.StdId`は、ロボマスから送られてくるCANのidが0x200番台の為、0x200にしています。ほかにも、先述の`RTR`,`IDE`,`DLC`にそれぞれデータを格納しています。これは任意ではなく、CAN通信の際は必ず書きます。 +機器不足のためここまでで一旦停止。 + + + + +# さいごに +これにて、ロボコンの低レイヤーがやるべき基本制御はコンプリートです。よく頑張りました。自分をいっぱい褒めてあげましょう。しかし、これはまだ本当に基本的な制御に過ぎません。ここから先、自分の力で楽しく質のいい制御をやっていくためには経験や知識、発想力が重要になってきます。自分からすすんで仕事を見つけ、知識を蓄えながらリラックスしてやっていけばきっと、頼れるロボコ二ストの一人として輝けると思います。 +調べる力、楽しむ心、そして自信。この3つを大切にしてください。 + +ここから先、「GitHub」についての資料と、「PID制御」などの少し発展した制御の資料も書くかもしれません。お楽しみに! +### 回路図提供してくれたT君からあなたへ +データーシートを見ましょう。そしてAIの情報は鵜呑みにせずに情報元をきちんと調べましょう。 + +2025/09/17 NAMATAMAGO314 + + + + diff --git a/beginner-tutorial/8_Extra.md b/beginner-tutorial/8_Extra.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/beginner-tutorial/8_Extra.md @@ -0,0 +1 @@ + diff --git a/beginner-tutorial/images/Screenshot from 2025-10-17 16-39-29.png b/beginner-tutorial/images/Screenshot from 2025-10-17 16-39-29.png new file mode 100644 index 0000000..f347473 Binary files /dev/null and b/beginner-tutorial/images/Screenshot from 2025-10-17 16-39-29.png differ diff --git a/beginner-tutorial/images/Screenshot from 2025-10-17 16-39-42.png b/beginner-tutorial/images/Screenshot from 2025-10-17 16-39-42.png new file mode 100644 index 0000000..5c6e009 Binary files /dev/null and b/beginner-tutorial/images/Screenshot from 2025-10-17 16-39-42.png differ diff --git a/beginner-tutorial/images/Videotogif.gif b/beginner-tutorial/images/Videotogif.gif new file mode 100644 index 0000000..0eb5528 Binary files /dev/null and b/beginner-tutorial/images/Videotogif.gif differ diff --git a/beginner-tutorial/images/iiiiimage.png b/beginner-tutorial/images/iiiiimage.png new file mode 100644 index 0000000..7b5bbcf Binary files /dev/null and b/beginner-tutorial/images/iiiiimage.png differ diff --git a/beginner-tutorial/images/iiiimage.png b/beginner-tutorial/images/iiiimage.png new file mode 100644 index 0000000..7e4fa55 Binary files /dev/null and b/beginner-tutorial/images/iiiimage.png differ diff --git a/beginner-tutorial/images/iiimage.png b/beginner-tutorial/images/iiimage.png new file mode 100644 index 0000000..3e53b7e Binary files /dev/null and b/beginner-tutorial/images/iiimage.png differ diff --git a/beginner-tutorial/images/image-1.png b/beginner-tutorial/images/image-1.png new file mode 100644 index 0000000..60601b3 Binary files /dev/null and b/beginner-tutorial/images/image-1.png differ diff --git a/beginner-tutorial/images/image-10.png b/beginner-tutorial/images/image-10.png new file mode 100644 index 0000000..9e43f54 Binary files /dev/null and b/beginner-tutorial/images/image-10.png differ diff --git a/beginner-tutorial/images/image-11.png b/beginner-tutorial/images/image-11.png new file mode 100644 index 0000000..e47d907 Binary files /dev/null and b/beginner-tutorial/images/image-11.png differ diff --git a/beginner-tutorial/images/image-12.png b/beginner-tutorial/images/image-12.png new file mode 100644 index 0000000..6080f13 Binary files /dev/null and b/beginner-tutorial/images/image-12.png differ diff --git a/beginner-tutorial/images/image-13.png b/beginner-tutorial/images/image-13.png new file mode 100644 index 0000000..aac5ed0 Binary files /dev/null and b/beginner-tutorial/images/image-13.png differ diff --git a/beginner-tutorial/images/image-14.png b/beginner-tutorial/images/image-14.png new file mode 100644 index 0000000..8a76b72 Binary files /dev/null and b/beginner-tutorial/images/image-14.png differ diff --git a/beginner-tutorial/images/image-15.png b/beginner-tutorial/images/image-15.png new file mode 100644 index 0000000..36333c2 Binary files /dev/null and b/beginner-tutorial/images/image-15.png differ diff --git a/beginner-tutorial/images/image-16.png b/beginner-tutorial/images/image-16.png new file mode 100644 index 0000000..2fda185 Binary files /dev/null and b/beginner-tutorial/images/image-16.png differ diff --git a/beginner-tutorial/images/image-17.png b/beginner-tutorial/images/image-17.png new file mode 100644 index 0000000..8b1d216 Binary files /dev/null and b/beginner-tutorial/images/image-17.png differ diff --git a/beginner-tutorial/images/image-18.png b/beginner-tutorial/images/image-18.png new file mode 100644 index 0000000..bbf1fe5 Binary files /dev/null and b/beginner-tutorial/images/image-18.png differ diff --git a/beginner-tutorial/images/image-19.png b/beginner-tutorial/images/image-19.png new file mode 100644 index 0000000..58bed53 Binary files /dev/null and b/beginner-tutorial/images/image-19.png differ diff --git a/beginner-tutorial/images/image-2.png b/beginner-tutorial/images/image-2.png new file mode 100644 index 0000000..1a87f3c Binary files /dev/null and b/beginner-tutorial/images/image-2.png differ diff --git a/beginner-tutorial/images/image-20.png b/beginner-tutorial/images/image-20.png new file mode 100644 index 0000000..a5979bb Binary files /dev/null and b/beginner-tutorial/images/image-20.png differ diff --git a/beginner-tutorial/images/image-21.png b/beginner-tutorial/images/image-21.png new file mode 100644 index 0000000..e46cde7 Binary files /dev/null and b/beginner-tutorial/images/image-21.png differ diff --git a/beginner-tutorial/images/image-22.png b/beginner-tutorial/images/image-22.png new file mode 100644 index 0000000..ae2904b Binary files /dev/null and b/beginner-tutorial/images/image-22.png differ diff --git a/beginner-tutorial/images/image-23.png b/beginner-tutorial/images/image-23.png new file mode 100644 index 0000000..c851a4a Binary files /dev/null and b/beginner-tutorial/images/image-23.png differ diff --git a/beginner-tutorial/images/image-24.png b/beginner-tutorial/images/image-24.png new file mode 100644 index 0000000..2598f26 Binary files /dev/null and b/beginner-tutorial/images/image-24.png differ diff --git a/beginner-tutorial/images/image-25.png b/beginner-tutorial/images/image-25.png new file mode 100644 index 0000000..1928524 Binary files /dev/null and b/beginner-tutorial/images/image-25.png differ diff --git a/beginner-tutorial/images/image-26.png b/beginner-tutorial/images/image-26.png new file mode 100644 index 0000000..cbf4a12 Binary files /dev/null and b/beginner-tutorial/images/image-26.png differ diff --git a/beginner-tutorial/images/image-27.png b/beginner-tutorial/images/image-27.png new file mode 100644 index 0000000..2bbb859 Binary files /dev/null and b/beginner-tutorial/images/image-27.png differ diff --git a/beginner-tutorial/images/image-28.png b/beginner-tutorial/images/image-28.png new file mode 100644 index 0000000..694b19f Binary files /dev/null and b/beginner-tutorial/images/image-28.png differ diff --git a/beginner-tutorial/images/image-29.png b/beginner-tutorial/images/image-29.png new file mode 100644 index 0000000..833d591 Binary files /dev/null and b/beginner-tutorial/images/image-29.png differ diff --git a/beginner-tutorial/images/image-3.png b/beginner-tutorial/images/image-3.png new file mode 100644 index 0000000..2ebd009 Binary files /dev/null and b/beginner-tutorial/images/image-3.png differ diff --git a/beginner-tutorial/images/image-30.png b/beginner-tutorial/images/image-30.png new file mode 100644 index 0000000..fb5610a Binary files /dev/null and b/beginner-tutorial/images/image-30.png differ diff --git a/beginner-tutorial/images/image-31.png b/beginner-tutorial/images/image-31.png new file mode 100644 index 0000000..7a9342f Binary files /dev/null and b/beginner-tutorial/images/image-31.png differ diff --git a/beginner-tutorial/images/image-32.png b/beginner-tutorial/images/image-32.png new file mode 100644 index 0000000..55d762c Binary files /dev/null and b/beginner-tutorial/images/image-32.png differ diff --git a/beginner-tutorial/images/image-33.png b/beginner-tutorial/images/image-33.png new file mode 100644 index 0000000..404e7fd Binary files /dev/null and b/beginner-tutorial/images/image-33.png differ diff --git a/beginner-tutorial/images/image-34.png b/beginner-tutorial/images/image-34.png new file mode 100644 index 0000000..f496c00 Binary files /dev/null and b/beginner-tutorial/images/image-34.png differ diff --git a/beginner-tutorial/images/image-35.png b/beginner-tutorial/images/image-35.png new file mode 100644 index 0000000..d10258f Binary files /dev/null and b/beginner-tutorial/images/image-35.png differ diff --git a/beginner-tutorial/images/image-36.png b/beginner-tutorial/images/image-36.png new file mode 100644 index 0000000..ebdb47f Binary files /dev/null and b/beginner-tutorial/images/image-36.png differ diff --git a/beginner-tutorial/images/image-37.png b/beginner-tutorial/images/image-37.png new file mode 100644 index 0000000..01fd490 Binary files /dev/null and b/beginner-tutorial/images/image-37.png differ diff --git a/beginner-tutorial/images/image-38.png b/beginner-tutorial/images/image-38.png new file mode 100644 index 0000000..21e6dd5 Binary files /dev/null and b/beginner-tutorial/images/image-38.png differ diff --git a/beginner-tutorial/images/image-39.png b/beginner-tutorial/images/image-39.png new file mode 100644 index 0000000..ea1914c Binary files /dev/null and b/beginner-tutorial/images/image-39.png differ diff --git a/beginner-tutorial/images/image-4.png b/beginner-tutorial/images/image-4.png new file mode 100644 index 0000000..8105c80 Binary files /dev/null and b/beginner-tutorial/images/image-4.png differ diff --git a/beginner-tutorial/images/image-40.png b/beginner-tutorial/images/image-40.png new file mode 100644 index 0000000..093dde1 Binary files /dev/null and b/beginner-tutorial/images/image-40.png differ diff --git a/beginner-tutorial/images/image-41.png b/beginner-tutorial/images/image-41.png new file mode 100644 index 0000000..f143d7d Binary files /dev/null and b/beginner-tutorial/images/image-41.png differ diff --git a/beginner-tutorial/images/image-42.png b/beginner-tutorial/images/image-42.png new file mode 100644 index 0000000..7805b26 Binary files /dev/null and b/beginner-tutorial/images/image-42.png differ diff --git a/beginner-tutorial/images/image-43.png b/beginner-tutorial/images/image-43.png new file mode 100644 index 0000000..9e2dcf4 Binary files /dev/null and b/beginner-tutorial/images/image-43.png differ diff --git a/beginner-tutorial/images/image-44.png b/beginner-tutorial/images/image-44.png new file mode 100644 index 0000000..14d850c Binary files /dev/null and b/beginner-tutorial/images/image-44.png differ diff --git a/beginner-tutorial/images/image-45.png b/beginner-tutorial/images/image-45.png new file mode 100644 index 0000000..eae824a Binary files /dev/null and b/beginner-tutorial/images/image-45.png differ diff --git a/beginner-tutorial/images/image-46.png b/beginner-tutorial/images/image-46.png new file mode 100644 index 0000000..270a947 Binary files /dev/null and b/beginner-tutorial/images/image-46.png differ diff --git a/beginner-tutorial/images/image-47.png b/beginner-tutorial/images/image-47.png new file mode 100644 index 0000000..f9eebbd Binary files /dev/null and b/beginner-tutorial/images/image-47.png differ diff --git a/beginner-tutorial/images/image-48.png b/beginner-tutorial/images/image-48.png new file mode 100644 index 0000000..a138dee Binary files /dev/null and b/beginner-tutorial/images/image-48.png differ diff --git a/beginner-tutorial/images/image-49.png b/beginner-tutorial/images/image-49.png new file mode 100644 index 0000000..a138dee Binary files /dev/null and b/beginner-tutorial/images/image-49.png differ diff --git a/beginner-tutorial/images/image-5.png b/beginner-tutorial/images/image-5.png new file mode 100644 index 0000000..5bb370d Binary files /dev/null and b/beginner-tutorial/images/image-5.png differ diff --git a/beginner-tutorial/images/image-50.png b/beginner-tutorial/images/image-50.png new file mode 100644 index 0000000..2728bd9 Binary files /dev/null and b/beginner-tutorial/images/image-50.png differ diff --git a/beginner-tutorial/images/image-51.png b/beginner-tutorial/images/image-51.png new file mode 100644 index 0000000..d012d2c Binary files /dev/null and b/beginner-tutorial/images/image-51.png differ diff --git a/beginner-tutorial/images/image-52.png b/beginner-tutorial/images/image-52.png new file mode 100644 index 0000000..e11859c Binary files /dev/null and b/beginner-tutorial/images/image-52.png differ diff --git a/beginner-tutorial/images/image-53.png b/beginner-tutorial/images/image-53.png new file mode 100644 index 0000000..69704e6 Binary files /dev/null and b/beginner-tutorial/images/image-53.png differ diff --git a/beginner-tutorial/images/image-54.png b/beginner-tutorial/images/image-54.png new file mode 100644 index 0000000..8180eb5 Binary files /dev/null and b/beginner-tutorial/images/image-54.png differ diff --git a/beginner-tutorial/images/image-55.png b/beginner-tutorial/images/image-55.png new file mode 100644 index 0000000..0f286ba Binary files /dev/null and b/beginner-tutorial/images/image-55.png differ diff --git a/beginner-tutorial/images/image-56.png b/beginner-tutorial/images/image-56.png new file mode 100644 index 0000000..4b5fbea Binary files /dev/null and b/beginner-tutorial/images/image-56.png differ diff --git a/beginner-tutorial/images/image-57.png b/beginner-tutorial/images/image-57.png new file mode 100644 index 0000000..4512157 Binary files /dev/null and b/beginner-tutorial/images/image-57.png differ diff --git a/beginner-tutorial/images/image-58.png b/beginner-tutorial/images/image-58.png new file mode 100644 index 0000000..9765922 Binary files /dev/null and b/beginner-tutorial/images/image-58.png differ diff --git a/beginner-tutorial/images/image-59.png b/beginner-tutorial/images/image-59.png new file mode 100644 index 0000000..fb1306f Binary files /dev/null and b/beginner-tutorial/images/image-59.png differ diff --git a/beginner-tutorial/images/image-6.png b/beginner-tutorial/images/image-6.png new file mode 100644 index 0000000..995fff5 Binary files /dev/null and b/beginner-tutorial/images/image-6.png differ diff --git a/beginner-tutorial/images/image-60.png b/beginner-tutorial/images/image-60.png new file mode 100644 index 0000000..fb1306f Binary files /dev/null and b/beginner-tutorial/images/image-60.png differ diff --git a/beginner-tutorial/images/image-61.png b/beginner-tutorial/images/image-61.png new file mode 100644 index 0000000..bac52a5 Binary files /dev/null and b/beginner-tutorial/images/image-61.png differ diff --git a/beginner-tutorial/images/image-62.png b/beginner-tutorial/images/image-62.png new file mode 100644 index 0000000..102ac26 Binary files /dev/null and b/beginner-tutorial/images/image-62.png differ diff --git a/beginner-tutorial/images/image-63.png b/beginner-tutorial/images/image-63.png new file mode 100644 index 0000000..3a9d320 Binary files /dev/null and b/beginner-tutorial/images/image-63.png differ diff --git a/beginner-tutorial/images/image-64.png b/beginner-tutorial/images/image-64.png new file mode 100644 index 0000000..77785a0 Binary files /dev/null and b/beginner-tutorial/images/image-64.png differ diff --git a/beginner-tutorial/images/image-7.png b/beginner-tutorial/images/image-7.png new file mode 100644 index 0000000..ffa10de Binary files /dev/null and b/beginner-tutorial/images/image-7.png differ diff --git a/beginner-tutorial/images/image-8.png b/beginner-tutorial/images/image-8.png new file mode 100644 index 0000000..0e23cf3 Binary files /dev/null and b/beginner-tutorial/images/image-8.png differ diff --git a/beginner-tutorial/images/image-9.png b/beginner-tutorial/images/image-9.png new file mode 100644 index 0000000..55e5890 Binary files /dev/null and b/beginner-tutorial/images/image-9.png differ diff --git a/beginner-tutorial/images/image.png b/beginner-tutorial/images/image.png new file mode 100644 index 0000000..a812c3e Binary files /dev/null and b/beginner-tutorial/images/image.png differ diff --git a/beginner-tutorial/images/rimage-0.png b/beginner-tutorial/images/rimage-0.png new file mode 100644 index 0000000..d5ab414 Binary files /dev/null and b/beginner-tutorial/images/rimage-0.png differ diff --git a/beginner-tutorial/images/rimage-1.png b/beginner-tutorial/images/rimage-1.png new file mode 100644 index 0000000..27ca240 Binary files /dev/null and b/beginner-tutorial/images/rimage-1.png differ diff --git a/beginner-tutorial/images/rimage-10.png b/beginner-tutorial/images/rimage-10.png new file mode 100644 index 0000000..b59eb1a Binary files /dev/null and b/beginner-tutorial/images/rimage-10.png differ diff --git a/beginner-tutorial/images/rimage-2.png b/beginner-tutorial/images/rimage-2.png new file mode 100644 index 0000000..70f11ed Binary files /dev/null and b/beginner-tutorial/images/rimage-2.png differ diff --git a/beginner-tutorial/images/rimage-3.png b/beginner-tutorial/images/rimage-3.png new file mode 100644 index 0000000..966d61f Binary files /dev/null and b/beginner-tutorial/images/rimage-3.png differ diff --git a/beginner-tutorial/images/rimage-4.png b/beginner-tutorial/images/rimage-4.png new file mode 100644 index 0000000..3650b96 Binary files /dev/null and b/beginner-tutorial/images/rimage-4.png differ diff --git a/beginner-tutorial/images/rimage-5.png b/beginner-tutorial/images/rimage-5.png new file mode 100644 index 0000000..b45c62e Binary files /dev/null and b/beginner-tutorial/images/rimage-5.png differ diff --git a/beginner-tutorial/images/rimage-6.png b/beginner-tutorial/images/rimage-6.png new file mode 100644 index 0000000..987d3e5 Binary files /dev/null and b/beginner-tutorial/images/rimage-6.png differ diff --git a/beginner-tutorial/images/rimage-7.png b/beginner-tutorial/images/rimage-7.png new file mode 100644 index 0000000..569a7ae Binary files /dev/null and b/beginner-tutorial/images/rimage-7.png differ diff --git a/beginner-tutorial/images/rimage-8.png b/beginner-tutorial/images/rimage-8.png new file mode 100644 index 0000000..cb57b91 Binary files /dev/null and b/beginner-tutorial/images/rimage-8.png differ diff --git a/beginner-tutorial/images/rimage-9.png b/beginner-tutorial/images/rimage-9.png new file mode 100644 index 0000000..13415d3 Binary files /dev/null and b/beginner-tutorial/images/rimage-9.png differ diff --git a/beginner-tutorial/images/s_2025-08-29_174240.png b/beginner-tutorial/images/s_2025-08-29_174240.png new file mode 100644 index 0000000..5bb305c Binary files /dev/null and b/beginner-tutorial/images/s_2025-08-29_174240.png differ diff --git a/beginner-tutorial/images/scr_2025-08-29_145702.png b/beginner-tutorial/images/scr_2025-08-29_145702.png new file mode 100644 index 0000000..d00cd46 Binary files /dev/null and b/beginner-tutorial/images/scr_2025-08-29_145702.png differ diff --git a/beginner-tutorial/images/screenshot_2025-08-27_160445.png b/beginner-tutorial/images/screenshot_2025-08-27_160445.png new file mode 100644 index 0000000..28c5982 Binary files /dev/null and b/beginner-tutorial/images/screenshot_2025-08-27_160445.png differ diff --git a/beginner-tutorial/images/screenshot_2025-08-27_161339.png b/beginner-tutorial/images/screenshot_2025-08-27_161339.png new file mode 100644 index 0000000..53ede54 Binary files /dev/null and b/beginner-tutorial/images/screenshot_2025-08-27_161339.png differ diff --git a/beginner-tutorial/images/screenshot_2025-08-27_162901.png b/beginner-tutorial/images/screenshot_2025-08-27_162901.png new file mode 100644 index 0000000..0abd438 Binary files /dev/null and b/beginner-tutorial/images/screenshot_2025-08-27_162901.png differ diff --git a/beginner-tutorial/images/screenshot_2025-08-27_163049.png b/beginner-tutorial/images/screenshot_2025-08-27_163049.png new file mode 100644 index 0000000..92f9709 Binary files /dev/null and b/beginner-tutorial/images/screenshot_2025-08-27_163049.png differ diff --git a/beginner-tutorial/images/screenshot_2025-08-27_170346.png b/beginner-tutorial/images/screenshot_2025-08-27_170346.png new file mode 100644 index 0000000..181992d Binary files /dev/null and b/beginner-tutorial/images/screenshot_2025-08-27_170346.png differ diff --git a/beginner-tutorial/images/screenshot_2025-08-28_193446.png b/beginner-tutorial/images/screenshot_2025-08-28_193446.png new file mode 100644 index 0000000..b7d6fe5 Binary files /dev/null and b/beginner-tutorial/images/screenshot_2025-08-28_193446.png differ diff --git a/beginner-tutorial/images/shot.png b/beginner-tutorial/images/shot.png new file mode 100644 index 0000000..7580c8a Binary files /dev/null and b/beginner-tutorial/images/shot.png differ diff --git a/beginner-tutorial/images/zimage-1.png b/beginner-tutorial/images/zimage-1.png new file mode 100644 index 0000000..8b2e64b Binary files /dev/null and b/beginner-tutorial/images/zimage-1.png differ diff --git a/beginner-tutorial/images/zimage-2.png b/beginner-tutorial/images/zimage-2.png new file mode 100644 index 0000000..47e9ab8 Binary files /dev/null and b/beginner-tutorial/images/zimage-2.png differ diff --git a/beginner-tutorial/images/zimage-3.png b/beginner-tutorial/images/zimage-3.png new file mode 100644 index 0000000..543f92f Binary files /dev/null and b/beginner-tutorial/images/zimage-3.png differ diff --git a/beginner-tutorial/images/zimage.png b/beginner-tutorial/images/zimage.png new file mode 100644 index 0000000..a4eb223 Binary files /dev/null and b/beginner-tutorial/images/zimage.png differ