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 مراحل انتقال یک کاراکتر را در اختیار ما قرار می دهد:
-
با نوشتن بیت UE در ثبت نام USART_CR1 روی 1، USART را فعال کنید.
-
بیت M را در USART_CR1 برنامه ریزی کنید تا طول کلمه را تعریف کنید.
-
تعداد بیت های توقف را در USART_CR2 برنامه ریزی کنید.
-
با استفاده از رجیستر USART_BRR نرخ باود مورد نظر را انتخاب کنید.
-
بیت TE را در USART_CR1 تنظیم کنید تا فرستنده را فعال کند (یک فریم غیرفعال را به عنوان اولین انتقال می فرستد).
-
داده های ارسالی را در رجیستر USART_DR بنویسید (با این کار بیت TXE پاک می شود). این کار را برای هر داده ای که قرار است ارسال شود تکرار کنید.
-
پس از نوشتن آخرین داده در رجیستر 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
ثبت نام.
// 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 بیت است.
کتابچه راهنمای مرجع در واقع فرمولی برای محاسبه این مقادیر برای نرخ باود مورد نظر ارائه می دهد:
جایی که fclk فرکانس ساعت USART است، OVER8
تنظیمات برای نمونه برداری بیش از حد را نشان می دهد. از آنجا که USARTDIV
دارای 4 بیت کسری است، معادله را می توان به صورت زیر تنظیم کرد:
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 مستقر شده است، توسعه یافته است. سوالی دارید؟ نظرات خود را در نظرات زیر به اشتراک بگذارید 👇.
اگر این پست را مفید دیدید، و برای بهروز ماندن از پستهای مشابه، فهرست کانالهایی که میتوانید دنبال کنید/عضو شوید در اینجا آمده است: