これはハブレに関する私の最初の記事なので、重いものを投げないようにお願いします。前もって感謝します。
背景から始めましょう。昔々、私はSTARMマイクロコントローラーに切り替えなければなりませんでした。これは、PICとAVRがすでに不足していて、新しい冒険を望んでいたという事実によるものでした。ベーカリーストアで入手可能なものと「クイックスタート」に関する多数の記事から、選択はSTM32F100に落ちました。
私はIARで働くことに慣れています。はい、他のIDEもありますが、IAR機能で十分です。比較的便利なエディターであり、悪いデバッガーではなく、デバッグ中にレジスターを操作するのに非常に便利です。
私が最初のプロジェクトを作ろうとしたとき、私はがっかりしました-CMSIS!他の誰か、しかし私にとってそれは恐怖でした(そして今も残っています):たくさんのブナ、長くて理解できない構造。これらすべてを掘り下げることは面白くありませんでした。いくつかの例をまとめてみましたが、これは私たちの方法ではないことに気づきました。
他に選択肢はありませんか?有る。IARに組み込まれているもの:iostm32f10xx4.hなどが含まれます。悪くない、全く:
RCC_APB2ENR_bit.ADC1EN = 1; // ADC
残ったのは、それをクラスに詰めて使用することだけでした。そして彼はそうしました。しばらくして、STM32f4xxのコードを作成するのに時間がかかりました。そしてここでも待ち伏せ-包括主義者はいません。何をすべきか?-自分で書いてください。私は既存の自作ライブラリを分析し、少し違う方法で行うことにしました。これが物語の内容です。
開始
デバッガー用のIARとドライバーのインストールについては説明しません。ここに新しいものは何もありません。32kBのコード制限があるIAR8があります。プルーピルボードに取り付けられたSTM32F103コントローラーが操作用に選択されています。
IARを起動し、c ++プロジェクトを作成し、目的のコントローラーを選択します。
次のステップは、ドキュメントを調べることです。リファレンスマニュアルRM0008に興味があります。主なことは注意深く読むことです。
一般に、私が労働者にコントローラーをプログラムするように教えたとき、私はタスクを与えました-LED(コントローラーレッグに接続されている)をオンにし、デバッガーを使用し、レジスターを編集し、ドキュメントを読みます。
RCCモジュール。タック
このモジュールは通常忘れられます。彼らは、LEDを点滅させることが不可能な場合にのみ覚えています。
覚えておいてください!周辺機器をオンにするには、それにクロックパルスを適用する必要があります!それなしではできません。
I / OポートはAPB2バス上にあります。ドキュメントには、このバスのクロッキングを制御するためのレジスタがあります。これはRCC_APB2ENRです。
ポートCのクロッキングを有効にするには(LEDはPC13にはんだ付けされているだけです)、IOPCENビットに書き込む必要があります。
次に、レジスタRCC_APB2ENRのアドレスを見つけましょう。そのオフセットは0x18で、RCCレジスタのベースアドレスは0x40021000です。
ビットの操作を便利にするために、構造を作成しましょう。
typedef struct
{
uint32_t AFIOEN : 1;
uint32_t : 1;
uint32_t IOPAEN : 1;
uint32_t IOPBEN : 1;
uint32_t IOPCEN : 1;
uint32_t IOPDEN : 1;
uint32_t IOPEEN : 1;
uint32_t : 2;
uint32_t ADC1EN : 1;
uint32_t ADC2EN : 1;
uint32_t TIM1EN : 1;
uint32_t SPI1EN : 1;
uint32_t : 1;
uint32_t USART1EN : 1;
uint32_t :17;
} RCC_APB2ENR_b;
後で苦しむことがないように、すぐにすべてのレジスタアドレスをリストします。
enum AddrRCC
{
RCC_CR = 0x40021000,
RCC_CFGR = 0x40021004,
RCC_CIR = 0x40021008,
RCC_APB2RSTR = 0x4002100C,
RCC_APB1RSTR = 0x40021010,
RCC_AHBENR = 0x40021014,
RCC_APB2ENR = 0x40021018,
RCC_APB1ENR = 0x4002101C,
RCC_BDCR = 0x40021020,
RCC_CSR = 0x40021024
};
現在、周辺機器を有効にするコードを書く必要があります。
static void EnablePort(uint8_t port_name)
{
volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
switch (port_name)
{
case 'A': apb2enr->IOPAEN = 1; break;
case 'a': apb2enr->IOPAEN = 1; break;
case 'B': apb2enr->IOPBEN = 1; break;
case 'b': apb2enr->IOPBEN = 1; break;
case 'C': apb2enr->IOPCEN = 1; break;
case 'c': apb2enr->IOPCEN = 1; break;
case 'D': apb2enr->IOPDEN = 1; break;
case 'd': apb2enr->IOPDEN = 1; break;
case 'E': apb2enr->IOPEEN = 1; break;
case 'e': apb2enr->IOPEEN = 1; break;
}
}
レジスターを操作するときは、volatileを忘れないでください。そうしないと、コンパイラーによる最適化の後、長い間エラーを探し、コンパイラー開発者を叱ります。
他の周辺機器のクロッキングを有効にするためにも同じことを行います。
その結果、次のクラスが取得されました(すべてがリストされているわけではありません)。
STM32F1xx_RCC.h
#pragma once
#include "stdint.h"
namespace STM32F1xx
{
class RCC
{
protected:
enum AddrRCC
{
RCC_CR = 0x40021000,
RCC_CFGR = 0x40021004,
RCC_CIR = 0x40021008,
RCC_APB2RSTR = 0x4002100C,
RCC_APB1RSTR = 0x40021010,
RCC_AHBENR = 0x40021014,
RCC_APB2ENR = 0x40021018,
RCC_APB1ENR = 0x4002101C,
RCC_BDCR = 0x40021020,
RCC_CSR = 0x40021024
};
typedef struct {
uint32_t HSION : 1;
uint32_t HSIRDY : 1;
uint32_t : 1;
uint32_t HSI_TRIM : 5;
uint32_t HSI_CAL : 8;
uint32_t HSEON : 1;
uint32_t HSERDY : 1;
uint32_t HSEBYP : 1;
uint32_t CSSON : 1;
uint32_t : 4;
uint32_t PLLON : 1;
uint32_t PLLRDY : 1;
uint32_t : 6;
} RCC_CR_b;
typedef struct {
uint32_t SW : 2;
uint32_t SWS : 2;
uint32_t HPRE : 4;
uint32_t PPRE1 : 3;
uint32_t PPRE2 : 3;
uint32_t ADC_PRE : 2;
uint32_t PLLSRC : 1;
uint32_t PLLXTPRE : 1;
uint32_t PLLMUL : 4;
uint32_t USBPRE : 1;
uint32_t : 1;
uint32_t MCO : 3;
uint32_t : 5;
} RCC_CFGR_b;
typedef struct
{
uint32_t TIM2EN : 1;
uint32_t TIM3EN : 1;
uint32_t TIM4EN : 1;
uint32_t : 8;
uint32_t WWDGEN : 1;
uint32_t : 2;
uint32_t SPI2EN : 1;
uint32_t : 2;
uint32_t USART2EN : 1;
uint32_t USART3EN : 1;
uint32_t : 2;
uint32_t I2C1EN : 1;
uint32_t I2C2EN : 1;
uint32_t USBEN : 1;
uint32_t : 1;
uint32_t CANEN : 1;
uint32_t : 1;
uint32_t BKPEN : 1;
uint32_t PWREN : 1;
uint32_t : 3;
} RCC_APB1ENR_b;
typedef struct
{
uint32_t AFIOEN : 1;
uint32_t : 1;
uint32_t IOPAEN : 1;
uint32_t IOPBEN : 1;
uint32_t IOPCEN : 1;
uint32_t IOPDEN : 1;
uint32_t IOPEEN : 1;
uint32_t : 2;
uint32_t ADC1EN : 1;
uint32_t ADC2EN : 1;
uint32_t TIM1EN : 1;
uint32_t SPI1EN : 1;
uint32_t : 1;
uint32_t USART1EN : 1;
uint32_t :17;
} RCC_APB2ENR_b;
typedef struct {
uint32_t DMAEN : 1;
uint32_t : 1;
uint32_t SRAMEN : 1;
uint32_t : 1;
uint32_t FLITFEN : 1;
uint32_t : 1;
uint32_t CRCEN : 1;
uint32_t :25;
} RCC_AHBENR_r;
public:
static void EnablePort(uint8_t port_name)
{
volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
switch (port_name)
{
case 'A': apb2enr->IOPAEN = 1; break;
case 'a': apb2enr->IOPAEN = 1; break;
case 'B': apb2enr->IOPBEN = 1; break;
case 'b': apb2enr->IOPBEN = 1; break;
case 'C': apb2enr->IOPCEN = 1; break;
case 'c': apb2enr->IOPCEN = 1; break;
case 'D': apb2enr->IOPDEN = 1; break;
case 'd': apb2enr->IOPDEN = 1; break;
case 'E': apb2enr->IOPEEN = 1; break;
case 'e': apb2enr->IOPEEN = 1; break;
}
}
static void DisablePort(char port_name)
{
volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
switch (port_name)
{
case 'A': apb2enr->IOPAEN = 0; break;
case 'a': apb2enr->IOPAEN = 0; break;
case 'B': apb2enr->IOPBEN = 0; break;
case 'b': apb2enr->IOPBEN = 0; break;
case 'C': apb2enr->IOPCEN = 0; break;
case 'c': apb2enr->IOPCEN = 0; break;
case 'D': apb2enr->IOPDEN = 0; break;
case 'd': apb2enr->IOPDEN = 0; break;
case 'E': apb2enr->IOPEEN = 0; break;
case 'e': apb2enr->IOPEEN = 0; break;
}
}
static void EnableAFIO()
{
volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
apb2enr->AFIOEN = 1;
}
static void DisableAFIO()
{
volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
apb2enr->AFIOEN = 0;
}
static void EnableI2C(int PortNumber)
{
switch (PortNumber)
{
case 1:
{
volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
apb1enr->I2C1EN = 1;
break;
}
case 2:
{
volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
apb1enr->I2C2EN = 1;
break;
}
}
}
static void EnableUART(int PortNumber)
{
switch (PortNumber)
{
case 1:
{
volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
apb2enr->USART1EN = 1;
break;
}
case 2:
{
volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
apb1enr->USART2EN = 1;
break;
}
case 3:
{
volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
apb1enr->USART3EN = 1;
break;
}
}
}
static void DisableUART(int PortNumber)
{
switch (PortNumber)
{
case 1:
{
volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
apb2enr->USART1EN = 0;
break;
}
case 2:
{
volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
apb1enr->USART2EN = 0;
break;
}
case 3:
{
volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
apb1enr->USART3EN = 0;
break;
}
}
}
static void EnableSPI(int PortNumber)
{
switch (PortNumber)
{
case 1:
{
volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
apb2enr->SPI1EN = 1;
break;
}
case 2:
{
volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
apb1enr->SPI2EN = 1;
break;
}
}
}
static void DisableSPI(int PortNumber)
{
switch (PortNumber)
{
case 1:
{
volatile RCC_APB2ENR_b* apb2enr = reinterpret_cast<RCC_APB2ENR_b*>(RCC_APB2ENR);
apb2enr->SPI1EN = 0;
break;
}
case 2:
{
volatile RCC_APB1ENR_b* apb1enr = reinterpret_cast<RCC_APB1ENR_b*>(RCC_APB1ENR);
apb1enr->SPI2EN = 0;
break;
}
}
}
static void EnableDMA()
{
volatile RCC_AHBENR_r* ahbenr = reinterpret_cast<RCC_AHBENR_r*>(RCC_AHBENR);
ahbenr->DMAEN = 1;
}
static void DisableDMA()
{
volatile RCC_AHBENR_r* ahbenr = reinterpret_cast<RCC_AHBENR_r*>(RCC_AHBENR);
ahbenr->DMAEN = 0;
}
};
}
これで、main.cppにファイルを添付して、次を使用できます。
#include "STM32F1xx_RCC.h"
using namespace STM32F1xx;
int main()
{
RCC::EnablePort('c');
return 0;
}
これで、ポートを操作できます。GPIO
ドキュメントの汎用および代替機能のI / Osセクションを開きます。ポートビット構成テーブルを見つけます
。CNF[1:0]ビットはポート動作モード(アナログ入力、デジタル入力、出力)を設定し、MODE [1:0]ビットは出力モードでのポート動作速度に対応します。
レジスタGPIOx_CRLとGPIOx_CRH(x = A、B、C、...)を見てみましょう。
ビットが順番に移動することがわかります:
CNF [1:0]、MODE [1:0]
次に、ポートの動作モードで定数を作成します。
enum mode_e
{
ANALOGINPUT = 0,
INPUT = 4,
INPUTPULLED = 8,
OUTPUT_10MHZ = 1,
OUTPUT_OD_10MHZ = 5,
ALT_OUTPUT_10MHZ = 9,
ALT_OUTPUT_OD_10MHZ = 13,
OUTPUT_50MHZ = 3,
OUTPUT_OD_50MHZ = 7,
ALT_OUTPUT_50MHZ = 11,
ALT_OUTPUT_OD_50MHZ = 15,
OUTPUT_2MHZ = 2,
OUTPUT_OD_2MHZ = 6,
ALT_OUTPUT_2MHZ = 10,
ALT_OUTPUT_OD_2MHZ = 14,
OUTPUT = 3,
OUTPUT_OD = 7,
ALT_OUTPUT = 11,
ALT_OUTPUT_OD = 15
};
その場合、構成方法は次のようになります。
// pin_number -
void Mode(mode_e mode)
{
uint32_t* addr;
if(pin_number > 7)
addr = reinterpret_cast<uint32_t*>(GPIOA_CRH);
else
addr = reinterpret_cast<uint32_t*>(GPIOA_CRL);
int bit_offset;
if(pin_number > 7)
bit_offset = (pin_number - 8) * 4;
else
bit_offset = pin_number * 4;
uint32_t mask = ~(15 << bit_offset);
*addr &= mask;
*addr |= ((int)mode) << bit_offset;
}
これで、モードを選択するためのより便利な方法を作成できます。
void ModeInput() { Mode(INPUT); }
void ModeAnalogInput() { Mode(ANALOGINPUT); }
void ModeInputPulled() { Mode(INPUTPULLED); }
void ModeOutput() { Mode(OUTPUT); }
void ModeOutputOpenDrain() { Mode(OUTPUT_OD); }
void ModeAlternate() { Mode(ALT_OUTPUT); }
void ModeAlternateOpenDrain() { Mode(ALT_OUTPUT_OD); }
ドキュメントでは、ポートの制御レジスタのアドレスを見つけて、それらをリストしています。
enum AddrGPIO
{
PortA = 0x40010800,
GPIOA_CRL = 0x40010800,
GPIOA_CRH = 0x40010804,
GPIOA_IDR = 0x40010808,
GPIOA_ODR = 0x4001080C,
GPIOA_BSRR = 0x40010810,
GPIOA_BRR = 0x40010814,
GPIOA_LCKR = 0x40010818,
PortB = 0x40010C00,
PortC = 0x40011000,
PortD = 0x40011400,
PortE = 0x40011800,
PortF = 0x40011C00,
PortG = 0x40012000
};
ベースアドレスとオフセットまたは絶対アドレスを使用することを長い間考えてきました。結局、私は後者に立ち寄った。これによりオーバーヘッドが追加されますが、デバッグ中にメモリ内で見つけやすくなります。
メソッドを最新化しましょう:
if(pin_number > 7)
addr = reinterpret_cast<uint32_t*>(GPIOA_CRH - PortA + PortAddr);
else
addr = reinterpret_cast<uint32_t*>(GPIOA_CRL - PortA + PortAddr);
誰かが目のけいれんを起こすかもしれませんが、私はまだもっと美しいものを思い付いていません。
レッグを目的の論理状態に転送するには、対応するビットをODRxレジスタに書き込むだけで十分です。たとえば、次のようになります。
void Set(bool st)
{
uint32_t* addr;
addr = reinterpret_cast<uint32_t*>(GPIOA_ODR - PortA + PortAddr);
if(st)
*addr |= 1 << pin_number;
else
{
int mask = ~(1 << pin_number);
*addr &= mask;
}
}
GPIOx_BSRRレジスタを使用して状態を制御することもできます。
同様に、ポートの状態を読み取るためのメソッド、構成および初期化のためのメソッドを作成します(クロッキングを有効にすることを忘れないでください)。その結果、ポートを操作するための次のクラスを取得しました。
STM32F1xx_Pin.h
#pragma once
#include <stdint.h>
#include "STM32F1xx_RCC.h"
namespace STM32F1xx
{
class Pin
{
public:
enum mode_e
{
ANALOGINPUT = 0,
INPUT = 4,
INPUTPULLED = 8,
OUTPUT_10MHZ = 1,
OUTPUT_OD_10MHZ = 5,
ALT_OUTPUT_10MHZ = 9,
ALT_OUTPUT_OD_10MHZ = 13,
OUTPUT_50MHZ = 3,
OUTPUT_OD_50MHZ = 7,
ALT_OUTPUT_50MHZ = 11,
ALT_OUTPUT_OD_50MHZ = 15,
OUTPUT_2MHZ = 2,
OUTPUT_OD_2MHZ = 6,
ALT_OUTPUT_2MHZ = 10,
ALT_OUTPUT_OD_2MHZ = 14,
OUTPUT = 3,
OUTPUT_OD = 7,
ALT_OUTPUT = 11,
ALT_OUTPUT_OD = 15
};
private:
enum AddrGPIO
{
PortA = 0x40010800,
GPIOA_CRL = 0x40010800,
GPIOA_CRH = 0x40010804,
GPIOA_IDR = 0x40010808,
GPIOA_ODR = 0x4001080C,
GPIOA_BSRR = 0x40010810,
GPIOA_BRR = 0x40010814,
GPIOA_LCKR = 0x40010818,
PortB = 0x40010C00,
PortC = 0x40011000,
PortD = 0x40011400,
PortE = 0x40011800,
PortF = 0x40011C00,
PortG = 0x40012000
};
private:
int pin_number;
int PortAddr;
public:
Pin() { }
Pin(char port_name, int pin_number) { Init(port_name, pin_number); }
~Pin()
{
Off();
ModeAnalogInput();
}
public:
void Init(char port_name, int pin_number)
{
this->pin_number = pin_number;
RCC::EnablePort(port_name);
switch (port_name)
{
case 'A': PortAddr = PortA; break;
case 'a': PortAddr = PortA; break;
case 'B': PortAddr = PortB; break;
case 'b': PortAddr = PortB; break;
case 'C': PortAddr = PortC; break;
case 'c': PortAddr = PortC; break;
case 'D': PortAddr = PortD; break;
case 'd': PortAddr = PortD; break;
case 'E': PortAddr = PortE; break;
case 'e': PortAddr = PortE; break;
}
}
void ModeInput() { Mode(INPUT); }
void ModeAnalogInput() { Mode(ANALOGINPUT); }
void ModeInputPulled() { Mode(INPUTPULLED); }
void ModeOutput() { Mode(OUTPUT); }
void ModeOutputOpenDrain() { Mode(OUTPUT_OD); }
void ModeAlternate() { Mode(ALT_OUTPUT); }
void ModeAlternateOpenDrain() { Mode(ALT_OUTPUT_OD); }
void NoPullUpDown()
{
uint32_t* addr;
if(pin_number > 7)
addr = reinterpret_cast<uint32_t*>(GPIOA_CRH - PortA + PortAddr);
else
addr = reinterpret_cast<uint32_t*>(GPIOA_CRL - PortA + PortAddr);
int bit_offset;
if(pin_number > 7)
bit_offset = (pin_number - 8) * 4;
else
bit_offset = pin_number * 4;
int mask = ~((1 << 3) << bit_offset);
*addr &= mask;
}
void Mode(mode_e mode)
{
uint32_t* addr;
if(pin_number > 7)
addr = reinterpret_cast<uint32_t*>(GPIOA_CRH - PortA + PortAddr);
else
addr = reinterpret_cast<uint32_t*>(GPIOA_CRL - PortA + PortAddr);
int bit_offset;
if(pin_number > 7)
bit_offset = (pin_number - 8) * 4;
else
bit_offset = pin_number * 4;
uint32_t mask = ~(15 << bit_offset);
*addr &= mask;
*addr |= ((int)mode) << bit_offset;
}
void Set(bool st)
{
uint32_t* addr;
addr = reinterpret_cast<uint32_t*>(GPIOA_ODR - PortA + PortAddr);
if(st)
*addr |= 1 << pin_number;
else
{
int mask = ~(1 << pin_number);
*addr &= mask;
}
}
void On()
{
uint32_t* addr;
addr = reinterpret_cast<uint32_t*>(GPIOA_ODR - PortA + PortAddr);
int bit_offset = pin_number;
*addr |= 1 << bit_offset;
}
void Off()
{
uint32_t* addr;
addr = reinterpret_cast<uint32_t*>(GPIOA_ODR - PortA + PortAddr);
int bit_offset = pin_number;
int mask = ~(1 << bit_offset);
*addr &= mask;
}
bool Get()
{
uint32_t* addr = reinterpret_cast<uint32_t*>(GPIOA_IDR - PortA + PortAddr);
int bit_offset = pin_number;
int mask = (1 << bit_offset);
bool ret_val = (*addr & mask);
return ret_val;
}
};
};
さて、試してみましょう:
#include "STM32F1xx_Pin.h"
using namespace STM32F1xx;
Pin led('c', 13);
int main()
{
led.ModeOutput();
led.On();
led.Off();
return 0;
}
デバッガーを調べて、LEDが最初に点灯することを確認し(led.ModeOutput();の後)、次に消灯し(led.On();)、再び点灯します(led.Off();)。これは、LEDが電力線を介して脚に接続されているためです。そのため、ピンが低い場合はLEDが点灯します。
合計は大きくない
この記事では、人生を少し簡素化し、コードを読みやすくする方法を示しました(成功したことを願っています)。またはその逆-それをしない方法。誰もが自分で決めるでしょう。
CMSISのラッパーを作成することは可能でしたが、これは興味深いことではありません。
お時間をいただきありがとうございます。続編に興味があれば教えてください。