در بخشهای هشتم و نهم، به ترتیب با نحوه ارسال و دریافت اطلاعات توسط واحد USART آشنا شدیم. در این بخش، میخواهیم واحد USART بورد Blue Pill را راهاندازی کنیم و دستورات printf و scanf را به کمک USART ریدایرکت یا ریتارگت کنیم. همانطور که میدانید برای دریافت یا ارسال اطلاعات از طریق USART، باید از طریق دستورات سطح پایین (تنظیم و چک کردن رجیسترها) استفاده کنیم. درحالیکه کار با توابع کتابخانههای ورودی/خروجی استاندارد زبان C، آسان است و قابلیتهای بسیار خوبی به ما میدهد. با ریدایرکت کردن این توابع با USART، میتوانیم از این امکانات در کار با این واحد، بهره ببریم.
با ما همراه باشید.
مرحله اول – ایجاد پروژه در محیط STM32CubeMX
ابتدا باید روال عادی ایجاد پروژه در نرمافزار CubeMX طی شود:
از منوی File، New Project یک پروژه جدید میسازیم. اسم میکروی موردنظرمان را در قسمت Part Number جستوجو و آن را انتخاب میکنیم. سپس باید از تب Pinout & Configuration منوی System core، کلاک و رابط دیباگ را تنظیم کنیم، بدین منظور از قسمت RCC برای کلاک HSE، کریستال انتخاب میشود:
تنظیم رابط دیباگ
برای رابط دیباگ نیز باید از بخش SYS، Serial Wire بهعنوان رابط دیباگ انتخاب شود. سپس باید تنظیمات واحد USART را انجام دهیم. برای این منظور از همین تب Pinout & Configuration، منوی Connectivity و از این منو USART1 را انتخاب میکنیم. در منوی باز شده حالت آسنکرون (Asynchonous) را برمیگزینیم و در بخش Parameter Settings که در زیر این منو قرار دارد تنظیمات Buad Rate، تعداد بیت داده، بیت آغاز و پایان و همچنین جهت اطلاعات را به صورت زیر انجام میدهیم:
توجه شود که این مقادیر به صورت دلخواه انتخاب شدهاند و بنا به نیاز هر کدام میتوانند تغییر کنند. جهت اطلاعات نیز بسته به این که میخواهیم از این واحد USART1، تنها اطلاعات بفرستیم یا بخوانیم، یا اینکه هر دو عمل را انجام دهیم تعیین میشود. در اینجا چون هر دو عمل مورد نیاز است Receive and Transmit انتخاب شده است.
بخش بعدی که باید تنظیم شود، وقفه واحد USART است که در صورت نیاز باید از بخش تنظیمات وقفه (NVIC) این کار را انجام دهیم. در صورتی نیاز به دریافت اطلاعات بهتر است وقفه این واحد را فعال کنیم.
قابل ذکر است که همانند همه وقفههای دیگر، فعالسازی این وقفه از منوی System Core بخش NVIC نیز قابل انجام است. پس از این تنظیمات در صورت نیاز پینهای ورودی، خروجی دیگر نیز با کلیک روی آنها روی IC نمایش داده شده و انتخاب GPIO_Input یا GPIO_Output تنظیم میشوند.
تنظیم کلاک
حالا باید به سراغ تنظیم کلاک برویم. با انتخاب Crystal به عنوان منبع کلاک و با تنظیمات پیشفرض، کلاک برابر با MHz8 قرار داده شده است. اگر مقدار دیگری برای کلاک مدنظر باشد باید در این بخش و در قسمت HCLK این مقدار را تنظیم کنیم. همچنین برای تنظیم کلاک هر بخش به پارامترهای PLL و Prescaler نیز دسترسی داریم.
تنظیمات نهایی
در آخر باید به سراغ تب Project Manager برویم. در اولین بخش یعنی Project، نام و مسیر پروژه مشخص میشود. در قسمتIDE Toolchain /، باید نرمافزاری مورد نظر برای توسعه پروژه انتخاب شود که چون در اینجا با نرمافزار Keil کار میکنیم روی گزینه MDK-ARM کلیک میکنیم.
در منوی بعدی یعنی Code Generator حتما چک شود که گزینه Keep User Code when re-gererating، فعال باشد، زیرا در غیر این صورت هرگاه که با برنامه CubeMX تغییراتی در پروژه ایجاد کنیم کدهای نوشته شده توسط ما پاک میشوند.
آخرین منو که همان Advanced Setting میباشد، مربوط به نوع درایورها یا توابع مورد استفاده است که میتوانند از نوع HAL یا LL انتخاب شوند، در اینجا ما با توابع LL کار میکنیم.
اکنون تمامی تنظیمات مورد نظر ما انجام شده است. روی GENERATE CODE کلیک میکنیم تا پروژه ایجاد شود. حالا روی Open Project کلیک میکنیم تا پروژه در محیط Keil باز شود.
مرحله دوم – تعریف توابع مورد نیاز در محیط Keil
برای ریدایرکت و ایجاد امکان استفاده از دستورات scanf و printf به وسیله USART باید اقداماتی که در ادامه گفته میشود (منبع) را در محیط برنامه Keil در پروژه انجام داد؛ در پنجره اصلی برنامه Keil، گزینه Project را انتخاب میکنیم و از بخش Manage، Run-Time Environment را کلیک میکنیم. در پنجره باز شده از زیرمنوی Compiler و سپس I/O دو پارامتر STDIN و STDOUT را به صورت زیر تغییر میدهیم:
نکته1: برای امکان از وقفه دریافت اطلاعات توسط واحد USART حتما باید دستور زیر را به برنامه اضافه نمود:
LL_USART_EnableIT_RXNE(USART1);
نکته2: فراموش نشود که برای استفاده از توابع scanf و printf باید کتابخانه “stdio.h” را اضافه کرد.
اکنون باید تعریف دو تابع stdout_putchar و stdin_getchar را در ابتدای فایل اصلی برنامه (main.c) و یا هر فایلی که دسترسی به آن تعریف شده است، انجام داد. بدین وسیله توابع scanf و printf را ریدایرکت خواهیم کرد. اما قبل از انجام این کار، برای دریافت دادههای ورودی از USART (به وسیله وقفه) و همچنین ارسال اطلاعات دو تابع read_uart و write_uart را تعریف میکنیم.
در تابع write_uart میتوانیم به سادگی پرچم ارسال اطلاعات واحد USART را چک کنیم و در صورت آزاد بودن داده مورد نظر را ارسال کنیم:
void write_uart(char data) { while(!LL_USART_IsActiveFlag_TXE(USART1)); LL_USART_TransmitData8(USART1, (uint8_t)data); }
اما برای تابع read_uart موضوع کمی پیچیدهتر است. همانطور که گفته شد دریافت اطلاعات را توسط وقفه انجام میدهیم. پس باید ابتدا در روال وقفه اطلاعات دریافت شده توسط میکرو را بایت به بایت بخوانیم، سپس اطلاعات خوانده شده رو در یک بافر ذخیره کنیم و توسط تابع read_uart این بافر را خوانده و برگردانیم. برای نمونه روال وقفه خواندن واحد USART را میتوان به صورت زیر نوشت:
void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ if(LL_USART_IsActiveFlag_RXNE(USART1)) { buffer[w_index++] = LL_USART_ReceiveData8(USART1); if (w_index >= sizeof(buffer)) { w_index=0; } counter++; } /* USER CODE END USART1_IRQn 0 */ /* USER CODE BEGIN USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */ }
که در آن متغیرهای buffer، w_index و counter به صورت گلوبال و همچنین از نوع volatile ساخته شدهاند، همچنین متغیر r_index را نیز برای اندیس خواندن در فایل تعریف stm32f1xx_it.c میکنیم؛
volatile int r_index=0; volatile int w_index=0; volatile char buffer[100]; volatile int counter = 0;
حالا باید تابع read_uart را برای خواندن از بافر تعریف کنیم؛
/* USER CODE BEGIN 1 */ char read_uart(void) { char data = 0; while(!counter); data = buffer[r_index++]; if (r_index >= sizeof(buffer)) { r_index=0; } __disable_irq(); counter--; __enable_irq(); return data; } /* USER CODE END 1 */
در این تابع ابتدا متغیر counter (که تعداد کاراکترهای دریافت شده توسط وقفه و خوانده نشده توسط تابع read_uart را نشان میدهد)، چک میشود تا در صورت صفر بودن این مقدار، برنامه منتظر دریافت کاراکتر بعدی باشد. سپس کاراکتری از بافر که با اندیس r_index تعیین میشود، خوانده شده و در متغیر data قرار داده میشود تا توسط تابع برگردانده شود. پس از هر بار خواندن بافر متغیر counter یک واحد کاهش مییابد. همچنین میتوان برای اطمینان بیشتر و در جایی که امکان ایجاد وقفه قبل از کاهش counter وجود دارد، وقفه را غیر فعال، و سپس دوباره فعال کرد. بدین وسیله مقدار counter همیشه درست خواهد بود. توجه شود در صورتی که این تابع را در فایل stm32f1xx_it.c تعریف کردهایم، باید در تابع main قبل از استفاده از آن، تابع را اعلان کنیم.
اکنون که توابع مورد نیاز نوشته شدهاند، باید عمل ریدایرکت کردن printf و scanf را انجام دهیم. همانطور که گفته شد برای اینکار باید دو تابع stdout_putchar (به کار رفته در printf) و stdin_getchar (به کار رفته در scanf) را تعریف کنیم. با استفاده از توابع خواندن و نوشتن که تعریف شدهاند این دو تابع را میتوان به سادگی به صورت زیر تعریف کرد:
int stdin_getchar (void) { int ch; ch = read_uart(); write_uart(ch); return (ch); } int stdout_putchar (int ch) { write_uart(ch); return (ch); }
مرحله سوم) استفاده از توابع نوشته شده برای دریافت و ارسال اطلاعات با scanf و printf توسط واحد USART:
اکنون که عمل ریدایرکت کردن را انجام دادهایم، میتوانیم از توابع scanf و printf استفاده کنیم و اطلاعات را دریافت یا ارسال کنیم، برای مثال در بدنه while میتوان دستورات زیر را برای تست نوشت و یک اسم از کاربر دریافت کرد.
while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ char data[100]; printf("\r\nEnter name: "); //read_uart(data); scanf("%s",data); printf("\r\n\tyour name is %s\r\n",data); } /* USER CODE END 3 */
متغیر data را میتوان خارج از بدنه (1)while تعریف کرد.
اکنون که نوشتن کد به پایان رسیده است، از برنامه build گرفته و آن را روی میکرو لود میکنیم. قابل ذکر است که اگر از برنامه Keil برای download کد روی میکرو استفاده میشود، بهتر است از قسمت project، Options for Target را انتخاب کنیم، سپس از قسمت سمت راست در تب Debug، روی Setting کلیک کرده و در پنجره باز شده، از تب Flash Download، گزینه Reset and Run را فعال کنیم.
برای دریافت و ارسال اطلاعات بین میکرو و کامپیوتر، احتیاج به یک تبدیل TTL به USB داریم که باید با توجه به پایههایی که در میکرو برای TX و RX تعریف شدهاند، به ترتیب پایه مربوط به TX میکرو را به RX متصل به کامپیوتر و پایه RX میکرو را به TX متصل به کامپیوتر وصل کنیم.
پس از ران کردن میکرو، نتیجه اجرای کد را میتوان در ترمینال مشاهده کرد. در اینجا برای نمایش اطلاعات، از ترمینال RealTerm استفاده شده است.
خطاهای احتمالی
- دستور مربوط به فعالسازی وقفه دریافت اطلاعات توسط USART (نکته 1) باید حتما در تابع int main و بعد از فراخوانی SystemClock_Config نوشته شود. در غیر این صورت، برنامه دچار خطا خواهد شد. همچنین در صورتی که نوشتن این دستور فراموش شود همین نتیجه را خواهیم گرفت و روال وقفه اجرا نخواهد شد.
- در بدنه تابع خواندن اطلاعات ورودی از واحد USART، مانند کد نمونه، باید از یک بافر استفاده شود، زیرا اطلاعات ممکن است به صورت رشته وارد شوند. در صورتی که از بافر استفاده نکنیم ممکن است در اجرای کد و در دریافت اطلاعات خطا ایجاد شود.
- یکی از راههای توصیه شده در وبسایتهای مختلف، برای ریدایرکت کردن توابع scanf و printf، استفاده از قالبهای کد آمادهای است که در منوی User Code Template (با کلیک راست کردن روی پوشه کدها در سمت چپ برنامه و انتخاب گزینه Add new item.. به این منو دسترسی داریم) قرار دارند. اما استفاده از این روش با خطاهای زیادی مواجه میشود. پیشنهاد میکنیم که همانند توضیحات داده شده، تنها فرمت تعریف دو تابع putchar و getchar که در این فایلها وجود دارد استفاده کنید.
در بخش بعدی با چگونگی راهاندازی واحد ADC و گرفتن اطلاعات آن توسط DMA، آشنا خواهیم شد.
منبع:سیسوگ