はじめに - カジュアルチャット
組み込みシステムとは何ですか? 専用のコンピュータ システム。特殊な機能に関しては、コンピュータ アーキテクチャ、周辺機器などでいくつかのトレードオフが発生する場合があります。
通常の制限: コスト (多数のセンサー ノードの導入など)、サイズと重量の制限 (小さなノードを必要とする下水流検出システムなどの特定のアプリケーション シナリオ)、電力とエネルギーの制限 (極端な環境での導入、ヒマラヤ山頂採集ノード、充電不便)、環境(防水、耐高温等)
MCU と MPU の 2 つの組み込みシステムの違い: 制御または処理に焦点を当てます。照明、ロボットアーム、モーターなどの制御がすべてです。画像処理のためにカメラによって収集されたデータなどの処理。
プログラミング言語: コンピューターの下部に近く、主にアセンブリと C を使用します。
OS: 組み込みシステムには必ずしもオペレーティング システム構造が存在するわけではありません。率直に言うと、オペレーティング システムは、コンピューター リソースのスケジュール管理をより適切に支援するためのものです。次に、lab2 コードの main 関数を分析しましょう。
void main(){
//background
while(1){
}
}
void IRQ_Handler(){
//interrupt handler function, frontground
}
バックグラウンド部分: 実行すべきタスクを繰り返し実行するループ。
このアプローチは一見すると問題ないように見えます。しかし、そのようなコンピューターで何ができるかを考えてみましょう。コンピューターはすべてのタスクを順番に繰り返し実行することしかできず、順序を変更する方法はありません。
前景部分: 割り込み処理。ラボ 2 の uart_rx_isr 関数は通常、IRQ_Handler を使用します (実際、ラボ 2 で uart_rx_isr のソースを追跡すると、実際には IRQ_Handler によって呼び出されていることがわかります。このメソッドは、対応する割り込みが発生したときに割り込みをトリガーします) starting は自動的に呼び出されます)。
前後を組み合わせたシステムはまだベアメタル OS フリーのシステムですが、割り込みが追加された後は、割り込みタスクを使用してバックグラウンド ポーリングを中断し、実行順序を変更することができます。たとえば、シリアル ポート割り込みの数が送信されると、CPU はバックグラウンドでの作業を脇に置き、フォアグラウンド割り込みを処理し、処理後に戻ります。
私たちのコースは、ベアメタル上で開発されたコンテンツに限定されています。
コンピュータシステムの簡単な紹介
フォン・ノイマン建築
演算器コントローラ(CPUに内蔵) メモリ メインメモリ 入力デバイス 出力デバイス IO、およびデータ、コントロール、アドレスの3つの伝送バス データバス/コントロールバス/アドレスバス。
前述したように、MPU はデータの計算と処理に重点を置き、MCU は制御に重点を置くため、MPU は外部コンポーネントを制御するための周辺機器を必要としません。
ハーバード大学の建築
ノイマン型との違いは、命令とデータが別々に保存されることです。このようにして、インデックスのシーク、インデックスのフェッチ、および番号のフェッチの効率が高くなります。
ストアドプログラムの概念
主要な部分は 2 つあります。RAM にはプログラムとデータが保存され、ROM には変更されていない読み取り専用のプログラムとデータが保存されます。
CPU 実行命令は、次の 3 つのステップを繰り返し実行します。 フェッチ デコード 実行 フェッチ命令 デコード 実行
組み立て
高級言語が人間の言葉をコンピューターに翻訳することに相当する場合、アセンブリ言語はコンピューター言語を私たちに翻訳することに相当します。最下層に近いため動作効率が高く、ハードウェアを直接操作できます。
ADD r3, r1, r2 ;r3 = r1 + r2
SUB r3, r0, r3
MOV r2, r1 ;r2 = r1
; はコメントです。変数r123はレジスタレジスタというハードウェアを操作できる部分で、これに値を代入することでハードウェアを操作することができます。
高級言語はコンパイラを通じてアセンブリ言語に翻訳され、アセンブリ言語はアセンブラを通じてバイナリ機械語に翻訳されます。
ARM アーキテクチャ
ARM は命令セットであり、上記のアセンブリ命令もすべて命令としてカウントされます。
ARM の興味深い点は、ARM デバイスを製造しているのではなく、命令セット アーキテクチャのみを設計し、その後 (知的財産コア、IP コア) を他の半導体メーカーに認可していることです。
A: アプリケーションは高性能を重視しており、多くのモバイル コンピュータは ARM アーキテクチャに基づいています。
R: リアルタイム、リアルタイムに焦点を当てる たとえば、車両のインターネットにはリアルタイム パフォーマンスに対する高い要件があります。
M: マイクロコントローラー。小規模な組み込みシステムで使用され、私たちが使用するボードです。
mシリーズにはm0~m7(簡単に言うと徐々に性能が上がっていく?)があり、下位互換性があり、m7はm0~m6と互換性があります。
SoC
基板上に stm32blabla という文字列が書かれた小さな黒いチップがあります。これはボード全体の中核であり、前述のコンピュータ アーキテクチャ、システム オン チップを含むチップ構造に相当します。
Soc 設計ルール: 最初に IP コアを選択し、ARM プロセッサと一連のストレージおよび IO 周辺構造を設計し、すべてブラック チップ上に統合します。
ARM プロセッサプロセッサはアーキテクチャを具体的にカバーしており、タイマーなど多くの新しいコンテンツがあります。
主にm4アーキテクチャを学びます。
概要、プロセッサ コア アクセス コード、およびデータ インターフェイスについては、オプション以外を参照してください。
登録
レジスターについては以前簡単に紹介しました。実際、メモリ内のデータを処理したい場合は、まずプロセッサ コアのレジスタにデータを取り込んで計算を行ってから、データを戻す必要があります。
腕レジスタは次のとおりです。
汎用レジスタ:計算データなどを格納できる一時変数。
SP: スタックの先頭を指すスタック先頭ポインタ レジスタ。
LR: 関数リターンの場合、戻りアドレスを保存します。たとえば、関数を呼び出すには、PC の値を LR に格納し、その後 PC は関数の開始位置にジャンプし、関数が戻ると LR の値が PC に返されます。
PC: プログラムが現在実行されているプログラム カウンタ (次に実行される命令のアドレス) を指します。各命令がフェッチされた後、PC は 32 ビット命令セット PC+=4B などの命令を自動的に追加します。
PSRシリーズは、現在のプログラムの状態を示すステータスレジスタです。たとえば、現在のユーザー モードはカーネル モードですか? IPSR は、現在割り込みが有効になっているかどうかを示します。待って。
xPSR には次のものが含まれます。
- APSR: フラグが立っているかどうか、結果が 0 であるかどうか、負であるかどうか、オーバーフローしているかどうかなどの計算用。
- IPSR: 割り込み処理関連。
- EPSR: 実行関連。命令セット、割り込みが継続するかどうか、およびその他の情報を示します。
メモリマップ
m4 にはデフォルトで 4g のメモリ空間がスペースにマップされており、ユーザーは好みに応じて変更することもできます。コードを格納するコード領域、データを格納する sram 領域、ペリフェラルを格納するペリフェラル領域、外部 ram 領域、外部デバイス領域、内部プライベート ペリフェラル バス (PPB) があります。
ビットバンド操作
ビットバンド動作。
3 番目のビット (左から右へ 31:0、3 番目のビットは右の 4 番目) など、32 ビット データの特定のビットを読み書きしたい場合、一部のレジスタでは r[ を直接取得できます。 3], しかし、それらのほとんどは直接入手することを許可されていません。
どうやって対処すればいいのでしょうか?1 を書き込むと、r|0000 0000 0000 0000 0000 0000 0000 1000 となります。
0 を書き込む場合は、r & 1111 1111 1111 1111 1111 1111 1111 0111 となります。
読み取り: r & 0000 0000 0000 0000 0000 0000 0000 1000 の結果が 0 であるかどうかを確認します。
これは非常に面倒で、例えば0x2000 0000のデータの3ビット目に1を書き込む必要があります。
LDR は次のデータをフロントレジスタにロードすること、[R1] は R1 の値をアドレスとして使用し、そこに格納されているデータを取得することです。
これは面倒ですが、メモリ マッピングのおかげで、「ビット エイリアス アドレス」にデータを直接書き込んで取得することができます。
0x2000 0000 のビット 0 ~ 31 は次のとおりです。
0x2200 0000
0x2200 0004
0x2200 0008
0x2200 000c……
0x2200 007c
したがって、それを直接取得して、0x2200 000c のデータを変更します。
0x2000 0000 は 0x2200 0000 にマッピングされ、SRAM 領域のマッピングです。0x4000 0000 は 0x4200 0000 にマッピングされ、周辺領域のマッピングです。
動作が速く、命令が少なく、1 ビットのみにアクセスする方が安全です たとえば、0x2000 0000 の 32 ビット データが取り出されたばかりです。このとき、0x2000 0000 のデータは中断されて変更されます。 . この時点で取得したデータは古い間違ったデータです 修正 3桁目終了後に書き戻します、これは変更を無駄に中断するのと同じです。
プログラムイメージ
ベクター: メインスタック (MSP) のアドレス、例外のアドレスなどの情報を格納するベクター テーブル。
start-up: ボードの電源が入ったときまたは最初のときのスタートアップ コード。
プログラム コード: 焼き付けたプログラム コード。
c lib code: ライブラリ関数コード。
リセットすると、まず msp アドレスを読み取り、メインがどこにあるかを見つけます。次に、リセット ベクターを読み取って BIOS 初期化コードを実行し、最初と 2 番目の命令の読み取りを開始します。
エンディアンネス
2収納仕様。
10 進数、1234、千二百三十四など。次に、それをデータベースに記録します。アドレスは下位から上位まで 4321 として保存され、最大の重みを持つビット 1 が最上位のアドレスに保存されます。これがビッグ エンディアン ストレージ ビッグ エンディアンです。それ以外の場合は、重みの大きいビットが下位アドレスに存在し、1234 のアドレスは下位から上位へ、つまりリトル エンディアンで格納されます。どちらの方法も m4 でサポートされています。
「リトル エンディアン」と「ビッグ エンディアン」という用語は、ジョナサン スウィフトの著書「ガリバー旅行記」に由来しており、その中で、対立する 2 つの派閥がどちらの端 (リトル エンディアン) から来るかを決めることができません。依然としてビッグ エンディアンです)同意する卵。
1726 年のビッグ エンディアンとスモール エンディアンの論争の歴史についてジョナサン スウィフトが説明したものは次のとおりです。
「...リリパットとブレフスクの二大国が過去36ヶ月にわたって激しく戦ってきたことをお伝えしたい。戦争は次の理由で始まった。私たちは皆、卵を食べる前に原始的な方法は割ることだと信じている」しかし、今の天皇の祖父は子供の頃卵を食べていて、昔ながらの方法で卵を割った際に指を骨折してしまい、父親である当時の天皇が勅許を出しました。卵を食べるときはすべての臣民に卵を割るよう命じる法令があり、その命令に違反した者は厳しく罰せられました。庶民はこの命令に極度に嫌悪感を抱きました。歴史によれば、このような反乱は6回ありました, 一人の皇帝が亡くなり、もう一人の皇帝が王位を失いました。これらの反乱のほとんどはすべてブレフスクの国王と大臣によって扇動されたものでした。反乱が沈静化した後、亡命者たちは常に救助と避難を求めてその帝国に逃げました。推定されています。 「卵を割るくらいなら死んだほうがましだ」という人が何度もいたが、この論争に関しては何百もの主要著作が出版されているが、大端派の書籍は常に発禁となっており、法律でもこの派閥の者は議員になれないと定められている。公式。」 (この翻訳は、インターネット上の Jiang Jianfeng から引用したものです。翻訳された「ガリバー旅行記」第 1 巻、第 4 章)
当時、スウィフトはイングランド(リリパット)とフランス(ブレファスク)の間で進行中の紛争を風刺していました。ネットワーク プロトコルの初期のパイオニアであるダニー コーエンは、バイト オーダーを指すためにこれら 2 つの用語を初めて使用し、それ以来、この用語は広く採用されるようになりました。
指図書
指図書。初期の arm 命令セットは 32 ビットで、優れたパフォーマンスと強力な機能を備えていました。ただし、長すぎると処理効率が低くなります。
thumb-1 命令セットは 16 ビットであり、処理効率は高いですが、パフォーマンスも低下します。初期の arm アーキテクチャが 2 つの命令セットをサポートしている場合、モードを頻繁に切り替える必要があり、非効率的です。
その後、thumb-2 命令セットには初期の 16 ビットと新しい 32 ビットが含まれ、arm 命令セットとの混合命令セットのパフォーマンスはそれほど低下せず、コード サイズと処理効率は依然として高かったです。
組み立て
アセンブリ構文。
シーケンシャル構造
label ; 可省略,用于跳转到此位置
助记符 operand1, operand2, … ; Comments
MOV r1, #0x01 ; 数据0x01放入r1
MOV r1, #'A' ; 数据A的ascii码放入r1
MOV R0, R1 ; move R1 into R0
MOVS R0, R1 ; move R1 into R0, 并且更新APSR的状态
LDR R1, [R0] ; R0存的是一个地址值如0x2000 0000, 这个指令是取出R0代表的地址中的数据存入R1
STR R1, [R0] ; 写回去
LDR R0, =0x12345678 ; Set R0 to 0x12345678
; 等效于:
; LDR R0, [PC, #offset]
; ...
; DCD 0x12345678
; 也就是先在文档末尾的一条指令里写入数据0x12345678,然后编译器自动计算PC+多少offset到达DCD的位置,把其值返给R0
; DCD是声明一个字 32bit,DCB是声明一个Byte
; 如果多个数值的声明可以用标签声明
LDR R3, =MY_NUMBER
ALIGN 4 ; 字要先用这个声明,代表停止长度
MY_NUMBER DCD 0x2000ABCC
HELLO_TEXT DCB “Hello\n”, 0 ; Null terminated string
LDRB R1, [R0] ; B: 只写8位,就是说R0地址处的数据写入R1后,R1高24位清零
SDRH R1, [R0] ; H: 只写16位
LDRSH R1, [R0] ; 视作signed有符号数,写16位
LDRB R0, [R1, #0x3] ; 从R1+3读取一个字节给R0
LDR R3, [R0, R2, LSL #2] ; 从R0+(R2<<2)读取一个字节给R3
LDR R0, [R1], #4 ; 赋完值后,令R1=R1+4
ADD R0, R0, R1
ADDS R0, R0, R1 ; 加完更新APSR状态,比如有溢出或者进位则更新
ADC R0, R1, R2 ; R1+R2还要+APSR的carry位
; SUB SBC类似
MUL R0, R1, R2
UDIV R0, R1, R2
SDIV R0, R1, R2 ; signed
例: マイナスになる可能性があるため、署名する必要があります。
命令は 1 ワードと半ワードの長さです。hw1 は関数の指定に使用され、hw2 はイミディエイト データなどの拡張子です。
アドレスは下位から上位まで: 4F F0 0A 00 0A 68 10 44...
PC はハーフワードハードウェアをフェッチするたびに、+2B で次のハードウェアにジャンプします。
構造を選択する
CMP R0, R1 ; 相当于if,比较后更新APSR。EQ= LT< GT> LE<= GE >=
BEQ BRANCH_1 ; B是跳转,BL是跳转到函数执行完后返回,BX是根据地址最低位判断目标地址是arm还是thumb在决定跳转到整字还是半字。bx操作数不能是立即数,必须是寄存器
B BRANCH_2
BRANCH_1
...
B IFEND ; 不写这个就继续执行BRANCH_2了,像switch的break
BRANCH_2
...
B IFEND
ループ構造
WHILE_BEGIN
UDIV R2, R0, R1 ; R2 = n / x
MUL R3, R2, R1 ; R3 = R2 * x
CMP R0, R3 ; n == (n / x) * x
BEQ WHILE_END
SUBS R1, R1, #1 ; x--
B WHILE_BEGIN ; loop back
WHILE_END
スタック
メモリ空間にはスタックに似たデータ構造があります。SP ポインタはスタックの先頭を指します。
このスタックのアドレスは上位から下位、つまりデータ SP– を格納し、データ SP++ を取り出します。これは、本をひっくり返した山に似ています。
フルスタック: sp ポインタは、スタックの先頭にある最後のデータを指します。
空のスタック: データを配置する最後のデータの次の空の場所を指します。
このコースでは、次の空の場所を指す空のスタックを使用します。データを保存するには、まず SP-4 に保存し、データをフェッチするには、最初に SP+4 にしてからスタックから取り出します。ただし、これら 2 つの命令を手動で実行する必要はありません。特別な命令があります。
PUSH {R0, R4-R7} ; Push r0, r4, r5, r6, r7
POP {R2-R3, R5} ; Pop to r2, r3, r5。入栈出栈顺序不是按照书写顺序而是自动根据寄存器地址,高地址值给高地址寄存器
5つのデータを保存し、3つのデータを引き出します。
機能
BL はまず現在の PC 値を LR に保存し、次に PC が関数アドレスにジャンプします。
BX LR は関数リターンのために LR のアドレスにジャンプします。
Architecture Procedure Call Standard (AAPCS): この仕様では、どのレジスタがメイン関数に共通で、どのレジスタが固有であるかを定義します。
arm AAPCS では、r0 ~ r3 は汎用レジスタ (グローバル変数と同様) ですが、メインと関数の R4 ~ R8、R10 ~ R11 は共通ではありません (一時変数と同様、これらの値は関数内で変化します)。元の関数ではありません)、スタックにプッシュされて保存されます。汎用レジスタの値は保存され、関数が呼び出されて返されたときに復元されます。これらは、元の関数を呼び出すサブ関数 callee-procedure によって実行されます。
単純なパラメータを使用した関数呼び出し: パラメータを関数パラメータとして R0 ~ R3 に渡し、R4 ~ R11 をスタックにプッシュしてから、関数にジャンプします。
プログラムメモリの使用量
ROM は定数などの読み取り専用データです。
定数、静的、揮発性
具体的なコード実装に関わる部分はそれほど多くないと思われるので、まずは簡単に紹介します。
const は定数変数を定義します。定数変数は、定義後に再度変更することはできません。
static は通常、静的関数を定義し、静的関数の値はユニバーサルです。つまり、関数への各呼び出しの値は、関数への最後の呼び出しの値から継続されます。
volatile: 組み込みでは非常に重要なことであり、ソフト試験問題で何度か登場しました。おそらく、不必要なエラーを防ぐためにコンパイラーが変数を最適化することを禁止するためです。
たとえば、コンパイラは num 変数を最適化するため、num 変数の値が変更されるたびに、すぐにメモリに書き込まれるのではなく、変更された値を最初にレジスタに書き込み、その後レジスタに書き戻すことがあります。関数が戻ったときのメモリ。
ここで、たとえば main で num+=5 の場合、値を変更した後の num がレジスタに一時的に格納されます。次に、割り込みを呼び出し、メモリから現在の num 値を読み取り、1 を加算します。ただし、メモリ内の値は変更されておらず、元の値のままです。戻った後、main は独自の num 値をメモリに書き込みます。最終的にメモリ内の num 値は、期待した +6 ではなく、+5 のみになります。
volatile で宣言された変数はこの方法では最適化されません。値が変更された場合はすぐにメモリに書き戻されます。非効率ではありますが、安全です。
割り込み
たとえば、私たちのプログラムのロジックは、ボタンが押されたときに小さなライトを点灯することです。最初の方法はポーリングです。ボタンが押されているかどうかを監視し続けます。いいえ。押された?それなし。押された?...
これは主に効率の低下と CPU リソースの浪費が原因であり、リソースを節約するためにポーリング間隔を長くすると、時間内に応答できなくなります。
割り込みにより、CPU は背景の処理に集中できるようになり、割り込みがトリガーされると、まず背景が停止されて前景が処理されます。OSを使用しないベアメタルでも簡単なマルチスレッド切り替えを実現できます。
例外処理プロセス
-
現在実行中のコマンドを終了します。
-
現在のモード レジスタ値はスタックにプッシュされて保存されます。
-
モードを切り替えます。
-
PC LR は (例外ハンドラーによって提供される値に従って) 更新されます。PC は割り込みベクタ テーブルをチェックしてどこにジャンプするかを確認し、EXC_RETURN コードを LR に割り当てます。
-
IPSR ステータスを更新します。
-
例外コードの実行を開始します。
-
終了すると、BX LR は EXC_RETURN コード値を PC に返します。
-
ポップ。
タイミング
実行の中断にも時間がかかり、ソースプログラムの状態を保存し、中断を実行し、復帰するまでにある程度の時間がかかります。
FMax_Int: 最大割り込み実行頻度、つまり単位時間当たりに実行される最大割り込み数。
F_CPU: CPU 周波数、つまり、CPU が単位時間あたりに持つ命令サイクル数。
C_ISR: 割り込み内容を実行するサイクル数。
C_Overhd: データの保存や復元などの準備作業を中断するために使用されるサイクル数。
実行を中断するために必要なサイクル: C_Overhd+C_ISR
したがって、FM ax _ I nt = FCPU / ( CISR + CO verhd ) F_{Max\_Int=}F_{CPU}/(C_{ISR}+C_{Overhd})FMax _ I n t = _ _FCPU _/ ( C私はSR+CオーバーHD _ _ _)
U_int: 実際に割り込み処理で消費される使用率、結局のところ上記が最大値です。
U int = FI nt / FM ax _ I nt U_{int}=F_{Int}/F_{Max\_Int}Uで_=Fない_ _/ Fマックス_イント_ _ _ _
割り込み実行速度(周波数と同じ): F_Int
非割り込み実行速度: (1-U_Int)*F_Int
GPIO
汎用入力出力、
メモリマップド IO
デバイス、制御、その他のレジスタをメモリにマッピングします。メリットはデバイスへのアクセス方法がメモリと同じで複雑なIO回路を設計する必要がないので便利ですが、デメリットはメモリのスペースを占有することです。
ペリフェラルマップされた IO
IOはメモリとは異なり専用の記憶領域を持ち、IOにアクセスするための特殊な回路命令もあります。メリットはメモリ容量を節約でき、いつIOが発生するかを明確に把握できることですが、デメリットは開発・設計コストが増加することです。
GPIO
汎用IOは端子のハイレベル、ローレベルを判定し、端子にハイレベル、ローレベルを割り当てて制御することができます。
stm32 には GPIO のグループがいくつかあり、それぞれに 16 ピンがあり、入出力プルアップ プルアップなどのモードや、タイマー、シリアル ポート、割り込みなどの機能として構成できます。
プルアップモードとは何ですか? プルアップ、プルダウンの設定がされていない場合、端子がフローティング状態(入力がハイレベルまたはローレベルに設定されていない状態)の場合、フローティング端子が電磁波干渉等を受け、入力状態が不定となる可能性があります。 0と1、間違いやすい。
プルダウン: トランジスタ制御のデフォルトはグランドであり、入力がない場合はデフォルトでローレベルになります。
プルアップ: トランジスタ制御はデフォルトで Vdd チップ動作電圧に接続されます。
ほとんどのピンにはこの 2 つの機能があり、GPIO を初期化するときにどちらかを選択し、その値に応じてレジスタが対応する回路を制御します。
入力信号と出力信号は実際には「信号」と呼ぶことができます。入力は0~0.5をローレベル、0.5~Vddをハイレベルとして定義されており、範囲外の値は無効となります。出力電流はわずか5mA程度なので、一部のデバイスを直接駆動することはできませんが、三極管やアンプなどの回路を使用することで、回路は信号を受信して「駆動電流を出力する必要がある」ことを認識し、そして大電流を出力します。
コントロール
各 GPIO ポートには次の機能があります。
4 * 32 ビット構成レジスタ: 入力/出力、プルアップとプルダウン、オープンドレイン出力またはプッシュプル出力、出力周波数などの構成関連情報。
- プッシュプル出力プッシュプル:ハイレベルとローレベルを出力できます。
- オープンドレイン出力 オープンドレイン:ハイレベルを出力する機能はありません。ハイレベルを出力したい場合は、プルアップ回路を設けて出力する必要があります。
2 * 32 ビット データ レジスタ: 入力および出力データ レジスタ。
1 * 32 ビット セット/リセット レジスタ: レジスタをセットまたはリセットします。
1 * 32 ビット ロック レジスタ: ロック レジスタ。
2 * 32 ビット代替機能選択レジスタ。
モード
図に示すように、32 個のピンがあり、それぞれに 4 つのモード (入力および出力はオプションのアナログ) を設定するための 2 ビットがあります。
引く
モードは 3 つだけです (プルなし、プルアップ、プルダウン)。
データ
入力データレジスタと出力データレジスタは分離されています。
CMSIS
まずテストの定義について話しましょう。
CMSIS はメモリマップされたレジスタを C 構造体に変換します
#define PORT0 ((struct PORT*)0x2000030)
組み込みの先輩たちと話し合った理解について話させていただきますが、以下の内容は試験での記述が禁止されています。
Li Ken 先生: arm-M によって開始された一連の API とソフトウェア コンポーネントには、コア機能、DSP ライブラリ、RTOS サポート、デバッグ インターフェイスが含まれます。
Li Ken 先生: チップ工場が別の層を追加したくない場合は、CMSIS で十分ですが、メーカーによっては、ドライバー層と呼ばれる別の層をその上に封入する場合もあります。
Li Ken 先生: さらに、CMSIS には ARM の ARM Cortex-M プロセッサという制限があります。これは非常に一般的ですが、すべてのプロセッサにこのコアが搭載されているわけではありません。これには注意が必要です。
榊:スタートアップファイルやカーネルファイルなど、カーネル関連のファイルはCMSISで規定されています。
榊: STM32F103 と GD32E23 のスタートアップ ファイルを比較すると、同じであることがわかります。
榊:チップメーカーがやるべきことは、このアームが指定するインターフェースに合わせてライブラリ機能を再開発することです。
リー・ケン氏のCステーションアカウント:建築家リー・ケンのブログ_CSDN Blog-Programming Life、ファン福祉分野のブロガー
榊さんのCステーションアカウント:Feng Zhenghao’s blog_CSDN blog-C言語、MSP430F5529、Linuxドメインブロガー
通常、Li Ken 氏の交換グループでは多くの組み込み関連の問題について議論します。興味のある学生は [Doge] を学ぶことを歓迎します。
上記内容にご興味がございましたら、ぜひご覧ください。
例:
typedef enum {
Reset, //!< Resets the pin-mode to the default value.
Input, //!< Sets the pin as an input with no pull-up or pull-down.
Output, //!< Sets the pin as a low impedance output.
PullUp, //!< Enables the internal pull-up resistor and sets as input.
PullDown //!< Enables the internal pull-down resistor and sets as input.
} PinMode;
gpio_set_mode(P1_10, Input);
gpio_set_mode(P2_8, Output);
int PBstatus=gpio_get(P1_10);
gpio_set(P2_8, 1);
上記のコードは教師が提供したドライバーであり、一般的なアイデアは、ピンを選択し、特定のパラメーターを渡してモードと出力を設定することです。
興味があれば、私の記事をご覧ください。arm によって定義された cmsis を使用して直接開発することも可能です。
STM32勉強記_4 GPIO:LED、ブザー、ボタン、センサーの使い方_グレーシールーズブログ - CSDNブログ
#include "stm32f10x.h"
int main(void){
/* 控制gpio需要三个步骤:开启rcc时钟,初始化,输入输出函数控制 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_0);
while(1){
}
}
ドライバーの二次開発は簡素化に役立ちます。
もちろん、この段落は主題から外れています。この試験は、「cmsis はレジスタに直接マップされる変数マクロ定義であり、ドライバーはそれにさらに動作を追加するものである」と理解されます。
シリアル通信
シリアル通信。メッセージを送信する通信方式。
文字列はデータの送信方法を指します。シリアル送信は 1 つずつ、パラレルには複数のチャネルがあり、各チャネルは同時にデータを送信し、複数のチャネルが同時に到着します。
シリアルポート通信には、単信 Simplex、半二重 Half Duplex、全二重 Full Duplex があります。
2 つの送信方法: クロックを共有する同期と、独自のクロックを持つ非同期。
同期は非常に簡単で、例えば送信側と受信側の両方でクロック信号の立ち下がりエッジを指定して送受信します。
非同期: 非同期通信プロトコル Asynchronous Comm. Protocol を通じて調整する必要があります。
送信を開始する 1 つのスタート ビット フラグ、7/8/9 データ ビット、1 つのオプションのパリティ ビット、1 つのストップ ビット。
両方の RT パーティが同じボーレートを持っている必要があります。
もちろん、当事者が 2 人しかいないため、これは最も単純なシリアル通信にすぎません。より多くの当事者が通信する場合は、アドレスを検証して、どれがどれに送信されるかを決定する必要があり、データにはより複雑な検証方法が必要です。
非同期通信は同期クロックなどの回路が不要でオーバーヘッドは小さいですが、スタートビットやエンドビットが必要となるため開発が難しくなります。
RS232
非同期通信、極性反転標準電圧(-3 -15が1、3 15が0。TTLが+5が1、-5が0など他にも規格あり)
送信データにはアスキーコードとバイナリの2種類があり、どちらもバイナリ送信に変換する必要があります。
アート
stm32f401の場合。
全二重非同期シリアルポート。
RT バッファ データを処理するには (データの送受信に時間がかかるため)、バッファ配列を渡すことができます。先頭ポインタは送信された位置を示し、末尾ポインタは送信されるデータの終わりを示します。 。新しいデータ、末尾ポインタ ++ を追加し、最後に触れるまでデータ、先頭ポインタ ++ を送信します。
送信者は常に高レベルを送信しており、開始フレームはデータの送信を開始したことを示す低レベルのフレームであることがわかります。
1フレームが低レベルかどうかの判断は?このフレーム内で複数回サンプリングすることで、本当に低レベルのフレームであるかどうかを判定します。
なぜ複数のサンプリングを行うのでしょうか? 2 つの非同期信号には一定のオフセットがあるため、マルチサンプリングは正確であり、フレーム全体で本当に低いかどうかを判断できます。
サンプリングには一定のサンプリング レートがありますが、実際にアナログ信号のようにサンプリングできるわけではありません。
サンプリング レート オーバーサンプリング = 16: これは、フレームごとに 16 回サンプリングする代わりに、達成可能な最大サンプリング周波数です。
受信機は最初に 0 ビットを初めて検出し、シリアル ポートにメッセージがあるのではないかと疑い始めます。これは開始フレームの最初のサンプルです。
次に、1 フレームおきにチェックし、3 5 7 を 3 回チェックします。両方とも 0 であれば、実際に可能であることを意味します。
次に、引き続き 8910 をチェックします。まだ 2 つの 0 がある場合は、それが実際に開始フレームであることを意味します。
8 サンプリングレート サンプリング間隔が長いため、左右の境界に高いレベルが発生しやすく、エラー許容率が低くなります。しかし、より速く。
計算する
ボーレートの計算:
T x / R x ( baud ) = f PCLK 8 ∗ ( 2 − OVER 8 ) ∗ USARTDIV T_x/R_x(baud)=\frac{f_{PCLK}}{8*(2-OVER8)*USARTDIV}T×/ R×(悪い) _ _=8 ∗ ( 2 − O VER 8 ) ∗ USART DIV _ _ _ _ _ _fPC L K
OVER8 はオーバーサンプリング レート、fPCLK はクロック周波数です。
USARTDIV は浮動小数点数です
USARTDIV 浮動小数点数はどのように格納されますか? アルゴリズムにより16進数に変換します。
小数部は16進数で表され、例1の場合Cは12ですが、変換後は12/16で0.75となります。
例 2 は 16 進数に変換されます。つまり、0.62*16 は 10、つまり A にほぼ等しくなります。
整数部分は直接 16 進数に変換でき、例 2 の 25 は 19 に、例 1 の 27 は 1B に変換されます。
次に、整数の小数部が連結されます (最大 3 整数ビット、小数点以下 1 桁、32 ビット レジスタ)。
タイマー
たとえば、LED の 1 が 1 回点滅するなど、プログラムを定期的に実行したいと考えています。どうやってするの?
最初の方法は、愚かな遅延です。私はそれを自分で見積もっていますが、遅延(2000) は約 1 秒です。その後、プログラムで遅延、点灯、遅延、消灯...
なんという資源の無駄遣いでしょう。
2 番目のメソッド 32 にはタイマー割り込みがあります。
タイマー割り込みの一般原理は、32 個のクロック水晶発振器が固定周波数周期で 0101010 を出力することです。タイマーには cnt があり、クロック水晶発振器を受信すると ++ が受信されます。
タイマーのオーバーフロー値を設定できます。たとえば、オーバーフロー値が 1000 の場合、cnt を 1000 に追加すると、タイマー割り込みが自動的にトリガーされます。その後、0 に戻り、++ に進みます。
実行サイクル数:1+1+1+1+(0xFFFFFFFF-1-1-1から0x00FFFFFFになるまで)+(r0+1実行回数、1回)
タイマーにはいくつかの拡張メソッドもあり、たとえば、++ または – を設定でき、信号ソースをクロックまたは外部入力方形波信号として設定でき、カウント値を読み取ることができます...
私たちのコースウェアの一般的な方法は、0 で割り込みをトリガーし、その後初期値に戻すことのようです。
PWM
PWMとは何ですか?
PWM(パルス幅変調)パルス幅変調は、慣性のあるシステムにおいて、一連のパルスの幅を変調することで必要なアナログパラメータを等価的に得ることができ、モーターの速度制御などの分野でよく使用されます。
たとえば、自転車の速度は 100 と 0 のみであり、アナログ電気信号は High と Low のみを出力できます。
しかし、自転車に乗るときは慣性があり、100の速度でペダルを踏み、0の速度でペダルを踏み、100の速度でペダルを踏みます...
全体的な自転車の平均速度は 50 です (加速には時間がかからないと仮定しています (笑))
これには、LED のフリッカー周波数を高、低、高、低、高、低... に設定するなど、多くのアプリケーション シナリオがあります。周波数が非常に高いため、肉眼ではちらつきを確認できません。私たちに提示された視覚効果は、半分の明るさで明るくなります。高低低高低低は1/3の明るさになります。
たとえば、モーターの速度はこのように調整されます。
それでは、彼はどのような応用シナリオを持っているのでしょうか。まずはインプットキャプチャ インプットキャプチャです。
このような慣性系の場合、その波形を逆から読んで速度を判断することもできます。例えば、モーターに速度検出センサーを付け、その入力波形をタイマーのクロックソース信号として使用する場合、タイマーは常に++:立ち上がりエッジと立ち下がりエッジを検出したときのcnt値を記録し、時間間隔を計算します。違いを比較することで。
2 番目は、出力コンペア、出力コンペアです。
タイマーは常に ++ であり、事前に設定されたしきい値と比較され、それらが等しい場合、割り込み出力がトリガーされます。
これがPWMです。デューティサイクルはかなり良好です。
低電力タイマー
現在、CPU は常に動作しており、バックグラウンドとフォアグラウンドを切り替えているだけであると想定しています。タイマー割り込みが発生していないときに CPU を低電力状態にし、タイマー割り込みが発生した場合にのみ開始される低電力タイマーがあります。(__WFI() を使用して命令命令を待ちます)
システムティック
M シリーズの内蔵システムクロックは、プロセッサクロックまたはリファレンスクロックをクロックソースとして使用します。
4 つのビット レジスタがあります。
各割り当ては、0 に達して割り当てを再ロードするまでロードされます。ctrl はシステムクロックを有効にするコントロールです。これは、CMSIS が提供するデータ構造のクロック処理部分と関連する演算機能です。
init パラメータは、割り込み間のミリ秒数です。timer_set_callback() の後には、それ自体で定義できる関数が続き、タイマー割り込みがトリガーされたときに関数が実行されます。上記のコードは、LED ライトが 100 ミリ秒ごとに反転し、通常の状態では CPU が低消費電力状態にあることを意味します。
2C
複数のモジュールを接続するための伝送方式: I2C、2 つのバスを使用。
2 つのバスは、クロック バス SCL とデータ バス SDA です。
コミュニケーションプロセス
次に、I2C 上で 1 つのモジュール (マスター) から別のモジュール (スレーブ) にメッセージを送信するプロセスを見てみましょう。
- MCU は特定の方法を使用して自身を識別し、送信を開始します。
- MCU は LCD スレーブのアドレス + 読み書きビットを送信し、他のモジュールはそれを受信し、そのアドレスが自分のものではないことがわかるため、処理しません。
- LCD はそれを受信すると、ターゲットが自分自身であることを認識しているため、ack を返します。
- MCU は ACK を受信した後、データのフレームを送信します。
- 送信後、MCU は ACK を待ち、ACK を受信した後、次のデータ フレームの送信を続けます。
- 送信ストップビットが終了するまで送信します。
データ長は789などの設定が可能です。
バス上のデバイスは、オープンドレイン出力を備えた半二重通信を行います。
デフォルトのバスは、プルアップ抵抗によって High にプルアップされます。
デバイスの出力出力が Low の場合、バスはグランドにオンになり、バスは Low に引き下げられます (バス全体が Low に引き下げられます)。江謝科技氏の例はとても良いです。バスのクロスバーのようなものです。誰かがクロスバーを引っ張って引き下げます。クロスバー全体が引き下げられます。低くなると、誰かがバスを使用します。」
次に、バスがデータを送信する方法ですが、SCLとSDAの2つのバスはどのような状況でスタートストップ0 1ビットを示すのでしょうか?
まず、SDA の値は、SCL がハイレベルの場合にのみ意味を持ちます。
SDA は High から Low へ変化し、開始ビットを示します。Low から High まで、ストップ ビットを示します。
スタートビット以降、SDA のハイレベルは 1、ローレベルは 0 を意味します。
1 バイトのデータを送信した後、バスは High になり続けます。受信者がバスをプルダウンし、送信者がバスが 1 → 0 であることを発見した場合 (送信者自身がバスをプルダウンしたのではなく、受信者がバスをプルダウンしましたが、送信者はそれを検出できます)、受信者が正常に受信したことを意味します。 and pull バスを引っ張って「受信」を表示します。SDA がまだ高レベルにある場合は、受信機が ACK を正常に受信または送信できなかったことを意味します。
問題解決
I2C は非常に単純なマスター/スレーブ通信プロトコルですが、多くの制限があります。たとえば、7 ビット アドレス ラインでは 2^7 デバイスのみが許可されます。一度にマスター/スレーブ通信できるデバイスは最大 2 つです。デバイスの速度は制限されます。バス全体の通信などに影響を与えます。
質問 1: スレーブ デバイスの処理速度が遅すぎて、次のクロック サイクルで新しいデータ フレームを受信できない場合はどうすればよいですか?
方法: クロック ストレッチング、一定期間 SCL をプルダウンして、次のクロック サイクルがまだ到着していないかのように見せかけます。
質問 2: 複数のデバイスが競合するデータを同時に送信した場合はどうすればよいですか?
方法: バス アリビテーション。バスがデバイスによってプルダウンされていること、およびすべてのデバイスがバスがプルダウンされた信号を受信できることがわかっています。したがって、2 つのデバイスが同時に情報の送信を開始した場合、前のデータが一致しているかどうかは関係ありません。初めてデータが不一致の場合、一方のデバイスはデータ 0 を送信し、もう一方のデバイスはデータ 1 を送信します。 、SDA バスは DATA2 の 0 によってプルダウンされます。
DATA1 データを送信するデバイスは、誰かが私と同時にデータを送信しているため、バスは予想どおり 1 ではなく、その人によって 0 に引き下げられることを理解します。それから私は辞めます、あなたはそれを送ります。この場合、DATA2 によって送信されるデータのみが存在します。
質問 3: 上記で送信されるデータは毎回 1byte 8bit ですが、これでちょうどいいです。送信するアドレスが8ビットではない場合はどうなりますか?
方法: 8 ビット未満のアドレスには固定の追加スタート ビットが埋められ、8 ビットを超えるアドレスには 2 バイトが埋められ、不十分なアドレスにも追加のスタート ビットが埋められます。
質問 3: マスターがデータの送信を終了し、すぐにデータを受信してスレーブになりたい場合、それは可能ですか?
方法: sr 信号を通じてスタート ビットを再送信する (リピート スタート)。これにより、自身が書き込みではなく読み取りであることを識別し、通信を再開します。
アドレス指定形式
スレーブアドレスのアドレッシングにはいくつかの固定フォーマットがあります。
0000 000 0: ブロードキャスト、すべてのスレーブ ノードと通信します。スレーブが無視 (NACK) した場合、ブロードキャストには参加しません。ACKが返れば参加します。ただし、複数のスレーブが ACK を返した場合、マスターは誰が応答したかを知りません。
2 番目のバイトは、ソフトウェアの起動、クリア、リセットなどの動作に関連するものを送信します。
プログラミングアプリケーション
スレーブモード:
- I2C デバイスはデフォルトでスレーブ モードで動作します。
- 周辺クロックは I2C_CR2 レジスタにプログラムされます。周波数は2kHz〜100kHzです。
- ハードウェアは、送信される開始情報とアドレス情報を自動的に待ちます。
- addr 情報が OAR1 に格納されているアドレスと同じ場合、ターゲットは自身であることを意味します。ACK ビットが 1 の場合、ACK パルスを送信します。
- ADDR ビットを設定します。1 は一致を意味します。
- ITEVFEN が割り込みイベントフラグが 1 の場合、割り込みが発生します。
- TRA ビットは、スレーブが R モードか T モード (受信または送信) のどちらであるかを示します。
- BTFビットマークは没収されました。
そう言うとまだ少しわかりにくいのですが、I2C ではデータをスムーズに送信するために具体的にどのような処理が行われているのでしょうか?
まずはメインモードの概念から。マスター マスター モードはクロック信号を駆動して転送を開始し、スレーブ スレーブ モードは転送に応答します。
メインモード
送信:
すべての EV イベントは、対応するソフトウェア シーケンスが実行されるまで SCL を Low にプルします。
S: イベントを開始します。たとえば、CR2 レジスタにペリフェラル クロックを設定し、クロック レジスタを設定し、クロック レジスタを立ち上げ、CR1 をイネーブルしてクロックをイネーブルにし、CR1 にスタート ビットを設定し、バスが Low になって準備完了を示すのを待ち、スタート信号が発生し、メインモードに切り替わります。
EV5: イベントが正常に開始され、SB レジスタ = 1 に設定されます。SB レジスタ = 1 の後にのみアドレス フェーズを実行でき、SB および EV5 イベントはアドレス フェーズの実行後に自動的にクリアされます。
アドレス: アドレス段階。7 ビット アドレス + 1 リード/ライト ビットを送信し、スレーブの ACK を待ちます。ackを受信してEV6に入る。
EV6: addr ビット = 1 の設定は、アドレス フェーズが正常に実行され、マスターが ack を受信したことを意味します。EV6をクリアすると自動的にEV8に突入します。
EV8: TxE を設定し、ホストから送信されるデータを書き込む準備をします。TxE は、データ レジスタが空であり書き込み可能であることを示します。データが DR に書き込まれるたびに、TxE および EV8 イベントはクリアされます。データの書き込み後、データが送信され、ホストは ack を受信した後も送信を続けます。データ転送の終了を示すには、BTF=1 を使用します。
void i2c_write(uint8_t address, uint8_t *buffer, int buff_len) {
int i = 0;
// Send in sequence: Start bit, Contents of buffer 0..buff_len, Stop
while (((I2C1->SR2>>1)&1)); // wait until I2C1 is not busy anymore
I2C_GenerateSTART(I2C1, ENABLE); // Send I2C1 START condition
// wait for I2C1 EV5 --> Slave has acknowledged start condition
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// Send slave Address for write then wait for EV6
I2C_Send7bitAddress(I2C1, address, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
while (i < buff_len){
I2C_SendData(I2C1, buffer[i]); // send data then wait for EV8_2
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
i++;
}
I2C_GenerateSTOP(I2C1, ENABLE); // send stop bit
}
買収:
フロントはマスター送信と同じです。
TxE は RxE に変更され、=1 はデータが受信されたことを示します。
マスターが停止イベントを設定(NACK を送信)すると、受信を停止します。
void i2c_read(uint8_t address, uint8_t *buffer, int buff_len) {
int i = 0;
// Start bit, Contents of buffer from 0..buff_len, sending a NACK
// for the last item and an ACK otherwise, Stop bit
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); //EV5
// Send slave Address for write then wait for EV6
I2C_Send7bitAddress(I2C1, address, I2C_Direction_Receiver);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
I2C_AcknowledgeConfig(I2C1, ENABLE); // going to send ACK
while (i < buff_len - 1){
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); //EV7
buffer[i] = I2C_ReceiveData(I2C1); // get data byte
i++;
}
I2C_AcknowledgeConfig(I2C1, DISABLE); // going to send NACK
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); //EV7
buffer[i] = I2C_ReceiveData(I2C1); // get the last byte
I2C_GenerateSTOP(I2C1, ENABLE); // send stop
}
スレーブモード
送信:
start start イベントはマスターによって開始されます。スレーブはアドレスをチェックし、ack ビットを送信するかどうかを決定します。
EV1: addr ビットのセットはアドレスの一致を示します。
EV3-1: TxE ビットをセットし、受信データを開始します。ホストがこれ以上データを必要としないことを示すために NACK を返すか、ACK が失敗したことを示す AF=1 が返されるまで。
買収:
フロントからEV1、スレーブ送信までは同じです。
- データは DR レジスタから読み出されます。
- バイトを読み取った後、ack ビットが設定されている場合は、ack 情報を返します。
- RxE ビットは、受信データのステータス レジスタです。
- マスターがストップコンディションを生成すると停止します。
異常事態:
バスエラー、NACK、調停失敗、クロック例外タイムアウト。