هذه أول مقال لي عن حبري ، لذا أطلب منك عدم إلقاء أشياء ثقيلة. شكرا مقدما.
لنبدأ بالخلفية. ذات مرة اضطررت للتبديل إلى ميكروكنترولر ST ARM. كان هذا بسبب حقيقة أن PIC و AVR كانا بالفعل في نقص المعروض ويريدان مغامرات جديدة. من بين تلك المتوفرة في متاجر المخابز وعدد كبير من المقالات حول "البدء السريع" ، وقع الاختيار على STM32F100.
اعتدت على العمل في IAR. نعم ، هناك IDEs أخرى ، لكن ميزة IAR كافية بالنسبة لي: محرر مناسب نسبيًا ، وليس مصحح أخطاء سيئًا ، ومن الملائم تمامًا العمل مع السجلات أثناء تصحيح الأخطاء.
عندما حاولت القيام بالمشروع الأول شعرت بخيبة أمل - CMSIS! أي شخص آخر ، لكن بالنسبة لي كان (ولا يزال) رعبًا: الكثير من الزان ، الهياكل الطويلة وغير المفهومة بالنسبة لي. لم يكن من المثير للاهتمام الخوض في كل هذا. حاولت تجميع بعض الأمثلة وأدركت أن هذه ليست طريقتنا.
ألا توجد خيارات أخرى؟ هناك. الجهاز المدمج في IAR: iostm32f10xx4.h وما شابه ذلك. ليس سيئا على الإطلاق:
RCC_APB2ENR_bit.ADC1EN = 1; // ADC
كل ما تبقى هو حشوها في فصول واستخدامها. وهكذا فعل. بعد مرور بعض الوقت ، استغرق الأمر إنشاء رمز STM32f4xx. وهنا مرة أخرى نصب كمين - لا يوجد من يضمهم. ماذا أفعل؟ - اكتب لنفسك. لقد قمت بتحليل المكتبات الحالية المكتوبة ذاتيًا وقررت أن أقوم بعمل مختلف قليلاً. هذا ما ستكون القصة حوله.
بداية
لن أتحدث عن تثبيت IAR وبرامج تشغيل لمصحح الأخطاء. لا يوجد شيء جديد هنا. لدي IAR 8 بحد رمز 32 كيلو بايت. يتم تحديد وحدة التحكم STM32F103 المثبتة على لوحة plue حبة للتشغيل.
قم بتشغيل IAR ، وقم بإنشاء مشروع c ++ ، وحدد وحدة التحكم المطلوبة ،
والخطوة التالية هي دراسة الوثائق. سنكون مهتمين بالدليل المرجعي RM0008. الشيء الرئيسي هو أن تقرأ بعناية.
بشكل عام ، عندما علمت موظفيي برمجة وحدات التحكم ، أعطيت المهمة - لتشغيل LED (متصل بساق وحدة التحكم) ، واستخدام مصحح الأخطاء وتحرير السجلات وقراءة الوثائق.
وحدة RCC. دس
عادة ما يتم نسيان هذه الوحدة. يتذكرون فقط عندما يكون من المستحيل وميض LED.
تذكر! لتشغيل أي أجهزة طرفية ، تحتاج إلى تطبيق نبضات الساعة عليها! لا يمكنك الاستغناء عنها.
توجد منافذ الإدخال / الإخراج على ناقل 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;
}
}
عند العمل مع المسجلات ، لا تنسى التقلبات ، وإلا بعد التحسين من قبل المترجم سنبحث عن الأخطاء لفترة طويلة ونوبخ مطوري المترجم.
نفعل الشيء نفسه لتمكين تسجيل الوقت للأجهزة الطرفية الأخرى.
نتيجة لذلك ، حصلنا على الفئة التالية (لم يتم سرد كل شيء):
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
افتح قسم الإدخال / الإخراج للأغراض العامة والبديلة في الوثائق. ابحث عن جدول تكوين بت المنفذ:
تضبط بتات 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 ، لكن هذا ليس مثيرًا للاهتمام.
شكرا لوقتك. إذا كنت مهتمًا بالتتمة ، فأخبرني بذلك.