برای ورود به صفحه آموزش STM32 اینجا کلیک کنید.
در بخشهای هشتم و نهم، به ترتیب با نحوه ارسال و دریافت اطلاعات توسط واحد USART آشنا شدیم و در قسمت قبلی هم با Timer-Input capture آشنا شدیم. در این بخش، میخواهیم واحد 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 انتخابشده است.
بخش بعدی برای ریدایرکت printf و scanf به کمک USART که باید تنظیم شود، وقفه واحد USART است که در صورت نیاز باید از بخش تنظیمات وقفه (NVIC) این کار را انجام دهیم. در صورتی نیاز به دریافت اطلاعات بهتر است وقفه این واحد را فعال کنیم.
قابلذکر است که همانند همه وقفههای دیگر، فعالسازی این وقفه از منوی System Core بخش NVIC نیز قابل انجام است. پسازاین تنظیمات در صورت نیاز پینهای ورودی، خروجی دیگر نیز با کلیک روی آنها روی IC نمایش دادهشده و انتخاب GPIO_Input یا GPIO_Output تنظیم میشوند.
تنظیم کلاک
برای ریدایرکت printf و scanf به کمک USART باید به سراغ تنظیم کلاک برویم. با انتخاب 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 حتما باید دستور زیر را به برنامه اضافه نمود:
1 | 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 را چک کنیم و در صورت آزاد بودن داده موردنظر را ارسال کنیم:
1 2 3 4 5 | void write_uart(char data) { while(!LL_USART_IsActiveFlag_TXE(USART1)); LL_USART_TransmitData8(USART1, (uint8_t)data); } |
اما برای تابع read_uart موضوع کمی پیچیدهتر است. همانطور که گفته شد دریافت اطلاعات را توسط وقفه انجام میدهیم. پس باید ابتدا در روال وقفه اطلاعات دریافت شده توسط میکرو را بایت به بایت بخوانیم، سپس اطلاعات خواندهشده رو در یک بافر ذخیره کنیم و توسط تابع read_uart این بافر را خوانده و برگردانیم. برای نمونه روال وقفه خواندن واحد USART را میتوان بهصورت زیر نوشت:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 میکنیم؛
1 2 3 4 | volatile int r_index=0; volatile int w_index=0; volatile char buffer[100]; volatile int counter = 0; |
حالا باید تابع read_uart را برای خواندن از بافر تعریف کنیم؛
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /* 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) را تعریف کنیم. با استفاده از توابع خواندن و نوشتن که تعریفشدهاند این دو تابع را میتوان بهسادگی بهصورت زیر تعریف کرد:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 میتوان دستورات زیر را برای تست نوشت و یک اسم از کاربر دریافت کرد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 متصل به کامپیوتر وصل کنیم.
خطاهای احتمالی
- دستور مربوط به فعالسازی وقفه دریافت اطلاعات توسط USART (نکته 1) باید حتماً در تابع int main و بعد از فراخوانی SystemClock_Config نوشته شود. در غیر این صورت، برنامه دچار خطا خواهد شد. همچنین درصورتیکه نوشتن این دستور فراموش شود همین نتیجه را خواهیم گرفت و روال وقفه اجرا نخواهد شد.
- در بدنه تابع خواندن اطلاعات ورودی از واحد USART، مانند کد نمونه، باید از یک بافر استفاده شود، زیرا اطلاعات ممکن است بهصورت رشته وارد شوند. درصورتیکه از بافر استفاده نکنیم ممکن است در اجرای کد و در دریافت اطلاعات خطا ایجاد شود.
- یکی از راههای توصیهشده در وبسایتهای مختلف، برای ریدایرکت کردن توابع scanf و printf، استفاده از قالبهای کد آمادهای است که در منوی User Code Template (با کلیک راست کردن روی پوشه کدها در سمت چپ برنامه و انتخاب گزینه Add new item.. به این منو دسترسی داریم) قرار دارند. اما استفاده از این روش با خطاهای زیادی مواجه میشود. پیشنهاد میکنیم که همانند توضیحات دادهشده، تنها فرمت تعریف دو تابع putchar و getchar که در این فایلها وجود دارد استفاده کنید.
در این بخش نحوه ریدایرکت printf و scanf به کمک USART را آموختیم و در بخش بعدی با چگونگی راهاندازی واحد ADC و گرفتن اطلاعات آن توسط DMA، آشنا خواهیم شد.
سلام
تنظیم scanf و printf به این صورت چه مزیتی نسبت به کارکردن مستقیم با خود usart داره؟
چون عملا میشه با usart و یه رابط USB to TTL دیتا به کامپیوتر ارسال و دریافت کرد…
سلام
با کمک این دستورات میتونید رشته ها رو به صورت پیشرفته تر ارسال یا دریافت کنید
مثلا با printf شما میتونید یک رشته ارسال کنید که داخلش متغیر های عددی هم باشه
یا با کمک scanf میتونید یک رشته دریافتی رو پردازش کنید و مثلا بگید با این الگو بخون رشته رو و دوتا int از توش در بیار و بریز توی یه متغیر
اطلاعات بیشتر :
https://www.decodejava.com/c-scanf-and-printf-function.htm
بینظیر هستید…
کلی توی اینترنت گشتم، اما نتونستم با HAL ریتارگت کنم! کل میکرو هنگ میکرد و آخرش نفهمیدم برای چیه.
آخرش با LL این کارو کردم.
حتی مرحله ی:
تغییر تعریف توابع ورودی STDIN و خروجیSTDOUT به حالت User
، رو هم با HAL انجام دادم ولی نشد.(یه توضیح میدین که اصلا این توابعی که فعال کردیم چی هست؟)
در کل LL خیلی بهتره ولی خب یه کم پیچیده تر.
سلام
برای ریدایرکت کردن در CubeIDE چکار باید کرد؟
سلام دوست عزیز
خوب لازمه بدونید که CubeIDE از کامپایلر gcc استفاده میکنه و اگر به دنبال راه حل برای gcc باشید به راحتی به جواب خواهید رسید، برای نمونه لینک زیر رو ببینید
ریدایرکت printf در cubeide
ایول
بعد مدت ها دوباره اینو ادامه دادین.
کاش dsp هم یاد بدین شما که اینقد خوب میگید.
مرسی از همراهی و انرژی مثبتتون.
سعی میکنیم در صورت امکان اون آموزش رو هم توی برنامهمون قرار بدیم.
مرسی که ادامه دادید
خواهش میکنم.
مرسی از همراهیتون. به زودی قسمتهای بعدی آموزش رو هم منتشر میکنیم.