برنامه نویسی

STM32F4 Embedded Rust در PAC: UART Communication

🎬 مقدمه

UART (گیرنده / فرستنده ناهمزمان جهانی) یک پروتکل ارتباطی است که برای ارتباط سریال بین دو دستگاه استفاده می شود. در سیستم‌های تعبیه‌شده، UART هنوز معمولاً برای برقراری ارتباط با دستگاه‌های دیگر مانند سنسورها، نمایشگرها و سایر میکروکنترلرها استفاده می‌شود. در این پست، من با پیاده سازی یک فرستنده UART ساده در Rust در سطح PAC (جعبه دسترسی محیطی) کار خواهم کرد. برنامه به سادگی یک کاراکتر را به طور مکرر به یک کامپیوتر میزبان متصل ارسال می کند.

اگر این پست را مفید می‌دانید و برای به‌روز ماندن با پست‌های مشابه، لیست کانال‌هایی را که می‌توانید دنبال کنید/در آنها مشترک شوید، آمده است:

📚 پیش نیاز دانش

برای درک محتوای این پست به موارد زیر نیاز دارید:

  • دانش اولیه کد نویسی در Rust.

  • آشنایی با قالب اولیه ساخت اپلیکیشن های جاسازی شده در Rust.

  • آشنایی با اصول ارتباط UART.

💾 راه اندازی نرم افزار

تمام کدهای ارائه شده در این پست علاوه بر دستورالعمل های محیط و راه اندازی زنجیره ابزار در دسترس است apollolabsdev Nucleo-F401RE git repo. توجه داشته باشید که اگر کد موجود در git repo کمی متفاوت باشد، به این معنی است که برای بهبود کیفیت کد یا به‌روزرسانی هر گونه HAL/Rust تغییر یافته است.

علاوه بر موارد فوق، شما باید نوعی ترمینال ارتباطی سریال را بر روی کامپیوتر میزبان خود نصب کنید. برخی از توصیه ها عبارتند از:

برای ویندوز:

برای مک و لینوکس:

برخی از دستورالعمل های نصب برای سیستم عامل های مختلف در دسترس است کتاب کشف.

🛠 راه اندازی سخت افزار

🧰 مواد

هیئت هسته ای

🔌 اتصالات

نیازی به اتصالات خارجی نخواهد بود. اتصالات Nucleo-F401RE روی برد مورد استفاده قرار خواهد گرفت و شامل موارد زیر است:

  • خط UART Tx که از طریق پل USB داخلی به رایانه شخصی متصل می شود از طریق پین PA2 روی میکروکنترلر است. این یک پین سخت سیمی است، به این معنی که نمی توانید از هیچ پین دیگری برای این تنظیم استفاده کنید. مگر اینکه از برد دیگری غیر از Nucleo-F401RE استفاده می کنید، باید اسناد مربوطه (راهنمای مرجع یا برگه اطلاعات) را برای تعیین تعداد پین بررسی کنید.

👨‍🎨 طراحی نرم افزار

برنامه موجود در این پست یک فرستنده UART ساده است که بارها و بارها همان نامه را ارسال می کند. با این حال، از آنجایی که ما در سطح PAC در حال توسعه هستیم، می‌خواهم مراحل پیکربندی و عملیات را قبل از اجرا پوشش دهم. خوشبختانه، راهنمای مرجع STM32F4 مراحل انتقال یک کاراکتر را در اختیار ما قرار می دهد:

  1. با نوشتن بیت UE در ثبت نام USART_CR1 روی 1، USART را فعال کنید.

  2. بیت M را در USART_CR1 برنامه ریزی کنید تا طول کلمه را تعریف کنید.

  3. تعداد بیت های توقف را در USART_CR2 برنامه ریزی کنید.

  4. با استفاده از رجیستر USART_BRR نرخ باود مورد نظر را انتخاب کنید.

  5. بیت TE را در USART_CR1 تنظیم کنید تا فرستنده را فعال کند (یک فریم غیرفعال را به عنوان اولین انتقال می فرستد).

  6. داده های ارسالی را در رجیستر USART_DR بنویسید (با این کار بیت TXE پاک می شود). این کار را برای هر داده ای که قرار است ارسال شود تکرار کنید.

  7. پس از نوشتن آخرین داده در رجیستر USART_DR، تا TC=1 صبر کنید. این نشان می دهد که انتقال آخرین فریم کامل شده است.

📝 توجه: من مراحلی را که برای برنامه در این پست ضروری نیستند حذف می کنم. این شامل تنظیم یک DMA و وقفه می شود.

با این حال، قبل از موارد فوق، باید مطمئن شویم که ساعت ها و پین ها را به درستی پیکربندی کرده ایم.

👨‍💻 پیاده سازی کد

📥 واردات جعبه

در این اجرا سه جعبه به شرح زیر مورد نیاز است:

  • را cortex_m_rt جعبه برای کد راه اندازی و حداقل زمان اجرا برای میکروکنترلرهای Cortex-M.

  • را cortex_m جعبه برای وارد کردن API محیطی Cortex-m.

  • را panic_halt جعبه برای تعریف رفتار وحشت زدگی برای توقف در هراس.

  • را stm32f4xx_pac جعبه برای وارد کردن دستگاه میکروکنترلر STM32F401 PAC API که در اولین پست در این سریال

use cortex_m_rt::entry;
use cortex_m;
use panic_halt as _;
use stm32f401_pac as pac;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

🎛 کد تنظیمات جانبی

1- یک دسته برای دستگاه و تجهیزات جانبی هسته تهیه کنید: همانطور که همیشه انجام می دهیم و بخشی از الگوی singleton در Rust تعبیه شده است، این کار باید قبل از دسترسی به هر ثبت جانبی یا هسته ای انجام شود. در اینجا من یک دستگاه کنترل کننده جانبی به نام ایجاد می کنم dp و core peripheral handler به نام cp به شرح زیر است:

let dp = pac::Peripherals::take().unwrap();
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با استفاده از این دسته، به تجهیزات جانبی دستگاه دسترسی خواهم داشت.

2- پیکربندی ساعت های سیستم: من پیکربندی ساعت را از روی ساده کردم آخرین پست جایی که من فقط از اسیلاتور داخلی HSI استفاده می کنم. نوسان ساز داخلی HSI دارای مقدار 16 مگاهرتز است.

// Enable HSI Oscillator
dp.RCC.cr.modify(|_, w| w.hsion().set_bit());

// Wait for HSI clock to become ready
while dp.RCC.cr.read().hsirdy().bit() {}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

3- Clocks به GPIO و USART Peripheral را فعال کنید: قبل از استفاده از هر وسیله جانبی، ساعت آنها باید فعال باشد. این هم برای GPIO و هم برای UART صدق می کند. در پست قبلی GPIO، دیدیم که ساعت GPIOA از طریق آن فعال می شود gpioaen میدان در RCC_AHB1ENR ثبت نام. از سوی دیگر، USART2 از طریق فعال می شود usart2en میدان در RCC_APB1ENR ثبت نام.

// Enable Clock to GPIOA
dp.RCC.ahb1enr.write(|w| w.gpioaen().set_bit());

// Enable Clock to USART2
dp.RCC.apb1enr.write(|w| w.usart2en().set_bit());
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

4- پین خروجی USART Tx را پیکربندی کنید: برای استفاده از PA2 به عنوان یک پایه انتقال UART، باید به عنوان یک خروجی جایگزین پیکربندی شود. علاوه بر این، تابع جایگزین باید برای اتصال به USART2 پیکربندی شود. پین PA2 به عنوان یک خروجی جایگزین در پیکربندی شده است GPIOA_MODER ثبت نام. علاوه بر این، برای انتخاب تابع جایگزین، باید آن را پیکربندی کنیم afrl2 میدان در GPIOA_AFRL ثبت تابع جایگزین طبق دیتاشیت، afrl2 باید با مقدار 7 پیکربندی شود تا به USART2 متصل شود.

// Configure PA2 as Alternate Output
dp.GPIOA.moder.modify(|_, w| unsafe { w.moder2().bits(2) });
// Select Alternate Function for PA2
dp.GPIOA.afrl.modify(|_, w| unsafe { w.afrl2().bits(7) });
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

📝 توجه: در مواردی که modify به جای استفاده می شود write، این بخاطر این است که write فیلدهایی را که به صراحت تنظیم نشده اند بازنشانی می کند. این بدان معناست که اگر می‌خواهید بیت‌های خاصی را در یک ثبت‌کننده بدون تأثیرگذاری بر دیگران تغییر دهید، باید از آن استفاده کنید modify به جای write.

4- USART2 را فعال کنید: این کار با بیان بیت UE در قسمت انجام می شود USART2_CR1 ثبت نام.

یکسو کننده CR1

// Enable USART2 by setting the UE bit in USART_CR1 register
dp.USART2.cr1.modify(|_, w| { w.ue().set_bit()});
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

5- طول کلمه را تعریف کنید در USART_CR1: M-bit در CR1 دو گزینه برای طول کلمه ارائه می دهد یا یک بیت شروع، 8 بیت داده، n گزینه توقف بیت (پیش فرض) یا یک بیت شروع، 9 بیت داده، n بیت توقف. از آنجایی که من 8 بیت داده می خواهم، در اینجا نیازی به تغییر نیست.

6- تعداد بیت های استاپ را در USART_CR2 برنامه ریزی کنید: این کار از طریق فیلد STOP در CR2 انجام می شود که 2 بیت عرض دارد. پیکربندی پیش فرض 0b00 عملکرد 1 استاپ بیت را منعکس می کند. باز هم در اینجا نیازی به تغییر چیزی نیست.

7- نرخ Baud مورد نظر را انتخاب کنید: این کار با استفاده از USART_BRR ثبت نام کنید و کمی بیشتر درگیر است. ثبات BRR در واقع دارای دو فیلد است که نشان دهنده ضریب تقسیم USART است USARTDIV; یکی است DIV_Mantissa که عرض آن 12 بیت است و دیگری آن است DIV_Fraction که عرض آن 4 بیت است.

ثبت نام brr

کتابچه راهنمای مرجع در واقع فرمولی برای محاسبه این مقادیر برای نرخ باود مورد نظر ارائه می دهد:

تیایکسآرایکسبآتود=fجلک8×(2بیش از 8)×USARTDIVTxRx_{baud} = \frac{f_{clk}}{8 \times (2 – \text{OVER8}) \times \text{USARTDIV}}

جایی که fclk فرکانس ساعت USART است، OVER8 تنظیمات برای نمونه برداری بیش از حد را نشان می دهد. از آنجا که USARTDIV دارای 4 بیت کسری است، معادله را می توان به صورت زیر تنظیم کرد:

تیایکسآرایکسبآتود=fجلک×168×(2بیش از 8)×USARTDIVTxRx_{baud} = \frac{f_{clk} \times 16}{8 \times (2 – \text{OVER8}) \times \text{USARTDIV}}

OVER8 در مورد ما 1 خواهد بود که نشان دهنده بیش از حد نمونه گیری با ضریب 16 است. در آن صورت، عامل مشترک را می توان حذف کرد و در نهایت به موارد زیر می رسیم:

// Program the UART Baud Rate
dp.USART2.brr.write(|w| unsafe { w.bits(FREQ / BAUD) });
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تعریف کردم FREQ و BAUD در ابتدا در کد به عنوان ثابت هایی که به ترتیب فرکانس ساعت USART (تامین شده توسط PCLK1 از طریق HSI) و نرخ باود مورد نظر را نشان می دهند.

const FREQ: u32 = 16_000_000;
const BAUD: u32 = 115_200;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

8- فرستنده را فعال کنید: این کار با تنظیم بیت TE در USART2_CR1 به صورت زیر انجام می شود:

// Enable the Transmitter
dp.USART2.cr1.modify(|_, w| w.te().set_bit());
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

📱 کد برنامه

در کد برنامه، ما یک حلقه راه‌اندازی می‌کنیم که کاراکتر A را به طور مکرر ارسال می‌کند. برای آن، باید داده‌هایی را که می‌خواهیم ارسال کنیم، بنویسیم USART_DR ثبت نام. پس از آن، باید منتظر بمانیم TC میدان در USART_SR ثبت وضعیت برای بالا رفتن نشان دهنده اتمام انتقال، سپس حلقه.

loop {
     // Put Data in Data Register
     dp.USART2.dr.write(|w| unsafe { w.dr().bits(b'A' as u16) });
     // Wait for data to get transmitted
     while dp.USART2.sr.read().tc().bit_is_clear() {}
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

📀 کد برنامه کامل

در اینجا کد کامل پیاده سازی شرح داده شده در این پست است. همچنین می‌توانید پروژه کامل و سایر پروژه‌های موجود را در آن بیابید apollolabsdev Nucleo-F401RE git repo.

#![no_std]
#![no_main]

// Imports
use cortex_m_rt::entry;
use panic_halt as _;
use stm32f401_pac as pac;

const FREQ: u32 = 16_000_000;
const BAUD: u32 = 115_200;

#[entry]
fn main() -> ! {
    // Setup handler for device peripherals
    let dp = pac::Peripherals::take().unwrap();

    // Enable HSI Oscillator
    dp.RCC.cr.modify(|_, w| w.hsion().set_bit());

    // Wait for HSI clock to become ready
    while dp.RCC.cr.read().hsirdy().bit() {}

    // Enable Clock to GPIOA
    dp.RCC.ahb1enr.write(|w| w.gpioaen().set_bit());

    // Enable Clock to USART2
    dp.RCC.apb1enr.write(|w| w.usart2en().set_bit());

    // Select Alternate Function for PA2
    dp.GPIOA.afrl.modify(|_, w| unsafe { w.afrl2().bits(7) });

    // Configure PA2 as Alternate Output
    dp.GPIOA.moder.modify(|_, w| unsafe { w.moder2().bits(2) });

    // Enable USART2 by setting the UE bit in USART_CR1 register
    dp.USART2.cr1.reset();
    dp.USART2.cr1.modify(|_, w| {
        w.ue().set_bit() // USART enabled
    });

    // Program the UART Baud Rate
    dp.USART2.brr.write(|w| unsafe { w.bits(FREQ / BAUD) });

    // Enable the Transmitter
    dp.USART2.cr1.modify(|_, w| w.te().set_bit());

    // Wait until TXE flag is set
    while dp.USART2.sr.read().txe().bit_is_clear() {}

    loop {
        // Put Data in Data Register
        dp.USART2.dr.write(|w| unsafe { w.dr().bits(b'A' as u16) });
        // Wait for data to get transmitted
        while dp.USART2.sr.read().tc().bit_is_clear() {}
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

🔬 آزمایش/ایده های بیشتر

  • برد را طوری پیکربندی کنید که یک کاراکتر را از کامپیوتر میزبان دریافت کند و آن را بازتاب دهد.

نتیجه

در این پست، کد Rust برای انتقال یک حرف با استفاده از دستگاه STM32 UART جانبی منحصراً در سطح جعبه دسترسی محیطی (PAC) توسعه داده شد. این برنامه برای یک میکروکنترلر STM32F401RE که بر روی برد توسعه Nucleo-F401RE مستقر شده است، توسعه یافته است. سوالی دارید؟ نظرات خود را در نظرات زیر به اشتراک بگذارید 👇.

اگر این پست را مفید دیدید، و برای به‌روز ماندن از پست‌های مشابه، فهرست کانال‌هایی که می‌توانید دنبال کنید/عضو شوید در اینجا آمده است:

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا