راه اندازی سون سگمنت با STM32 همانند آنچه در نوشتۀ «راه اندازی سون سگمنت با AVR» گفتیم، انجام می شود. در آن نوشته اشاره ای به چند ماکرو برای مقداردهی به رجیسترهای پورت IO در AVR کردیم. دیدیم که چگونه می توان به راحتی با اجرای ماکروها، پایه های پورت IO را تنظیم کرد. در این نوشته می بینم با توابعی که برای GPIO در STM32F نوشته ایم، تنظیمات GPIO را انجام می دهیم. حسن این توابع، راحت بودن کار با آنها و سرعت بخشیدن به روند پروژه است. با این توابع می توان بدون توجه به رجیسترها، مدهای GPIO، ورودی/خروجی بودن آنها و دیگر تنظیمات را پیکربندی کرد. برای راه اندازی 7-segment با STM32 می توان پایه های خروجی میکروکنترلر را در حالت خروجی پوش پول قرار داد. پس از تنظیم پایه های میکروکنترلر، با مقداردهی صحیح به آنها، سون سگمنت عدد مربوط را نمایش می دهد. در پروژه ای که بررسی خواهد شد تنها به نوشتن تابع برای GPIO اکتفا نکرده ایم. بلکه نمایش در سون سگمنت را نیز به صورت تابع درآورده ایم. در ادامه به بررسی برنامۀ نوشته شده می پردازیم. برای آشنایی با ساختار سون سگمنت و نحوۀ مقداردهی به پایه های آن، نوشته های «راه اندازی سون سگمنت با AVR» و «سون سگمنت» را مطالعه نمایید.
نکته: پروژۀ این نوشته هم به صورت رجیستری و هم با توابع HAL در نرم افزار Keil و هم با استفاده از توابع HAL در نرم افزار STM32CubeIDE نوشته شده است. در متن نوشته پروژه های نوشته شده در Keil توضیح داده می شوند. هر سه پروژۀ مذکور در پیوست موجودند.
نکته: ابتدا باید به پیکربندی کلاک سیستم می پرداختیم. اما با توجه به این که موضوع نوشته، کار با GPIO است، ابتدا GPIO بررسی شده است. در ادامه نکات مربوط به تنظیمات کلاک سیستم آمده است.
راه اندازی سون سگمنت با آردوینو
تصویر 1 – راه اندازی سون سگمنت با STM32 روی برد توسعۀ STM32F10xVxxx
مقداردهی به رجیسترهای GPIO با تابع
در این بخش به شرح توابع GPIO مورد استفاده در راه اندازی سون سگمنت با STM32می پردازیم. سه تابع gpio_config و gpio_write و gpio_read در کتابخانۀ gpio.h وجود دارد. تابع gpio_config برای تعیین مد یک پایه است. مدهای مورد نظر که مطابق دیتاشیت هستند در ابتدای کتابخانۀ gpio.h دیفاین شده اند. با قرار دادن یکی از این دیفاین ها در ورودی سوم تابع gpio_config، پایۀ مورد نظر که ورودی اول و دوم تعیین کنندۀ آن است، در مد دیفاین شده تنظیم می شود. آرگومان ورودی آخر نیز تعیین کنندۀ سرعت پایۀ مورد نظر در مدهای خروجی است. این ورودی می تواند مقادیر 2 و 10 و 50 را داشته باشد. مدهای GPIO با مقداردهی به رجیسترهای CRL و CRH و همچنین ODR به دست می آیند. همان طور که مشاهده می کنید این رجیسترها در متن تابع gpio_config مقداردهی شده اند. همچنین با قرار گرفتن هر یک از پورت ها در ورودی، کلاک آن، در رجیستر APB2ENR فعال می شود.
#define output_push_pull 0
#define output_open_drain 1
#define input_analog 2
#define input_floating 3
#define input_pull_down 4
#define input_pull_up 5
#define alternate_function_output_push_pull 6
#define alternate_function_output_open_drain 7
void gpio_config(GPIO_TypeDef* port_num, unsigned char pin_num, unsigned char mode, unsigned char speed)
{
if (port_num==GPIOA) RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
else if(port_num==GPIOB) RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
else if(port_num==GPIOC) RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
else if(port_num==GPIOD) RCC->APB2ENR |= RCC_APB2ENR_IOPDEN;
else if(port_num==GPIOE) RCC->APB2ENR |= RCC_APB2ENR_IOPEEN;
if(pin_num<=7)
{
if (mode==output_push_pull) {port_num->CRL &=~ (0xF<<(pin_num*4));}
else if(mode==output_open_drain){port_num->CRL &=~ (0xF<<(pin_num*4));
port_num->CRL |= (0x4<<(pin_num*4));}
else if(mode==alternate_function_output_push_pull)
{port_num->CRL &=~ (0xF<<(pin_num*4)); port_num->CRL |= (0x8<<(pin_num*4));}
else if(mode==alternate_function_output_open_drain)
{port_num->CRL &=~ (0xF<<(pin_num*4)); port_num->CRL |= (0xC<<(pin_num*4));}
else if(mode==input_analog) {port_num->CRL &=~ (0xF<<(pin_num*4));}
else if(mode==input_floating){port_num->CRL &=~ (0xF<<(pin_num*4));
port_num->CRL |= (0x4<<(pin_num*4));}
else if(mode==input_pull_down){port_num->CRL &=~ (0xF<<(pin_num*4));
port_num->CRL |= (0x8<<(pin_num*4));}
else if(mode==input_pull_up) {port_num->CRL &=~ (0xF<<(pin_num*4));
port_num->CRL |= (0x8<<(pin_num*4));}
if((mode==output_push_pull)||(mode==output_open_drain)||
(mode==alternate_function_output_push_pull)||
(mode==alternate_function_output_open_drain))
{
if (speed==2) port_num->CRL |= (0x2<<(pin_num*4));
else if(speed==10) port_num->CRL |= (0x1<<(pin_num*4));
else if(speed==50) port_num->CRL |= (0x3<<(pin_num*4));
}
If (mode==input_pull_down) port_num->ODR &=~ (1<<pin_num);
else if(mode==input_pull_up) port_num->ODR |= (1<<pin_num);
}
else if((pin_num>7)&&(pin_num<=15))
{
pin_num-=8;
if (mode==output_push_pull) {port_num->CRH &=~ (0xF<<(pin_num*4));}
else if(mode==output_open_drain){port_num->CRH &=~ (0xF<<(pin_num*4));
port_num->CRH |= (0x4<<(pin_num*4));}
else if(mode==alternate_function_output_push_pull)
{port_num->CRH &=~ (0xF<<(pin_num*4));port_num->CRH |= (0x8<<(pin_num*4));}
else if(mode==alternate_function_output_open_drain)
{port_num->CRH &=~ (0xF<<(pin_num*4));port_num->CRH |= (0xC<<(pin_num*4));}
else if(mode==input_analog) {port_num->CRH &=~ (0xF<<(pin_num*4));}
else if(mode==input_floating) {port_num->CRH &=~ (0xF<<(pin_num*4));
port_num->CRH |= (0x4<<(pin_num*4));}
else if(mode==input_pull_down){port_num->CRH &=~ (0xF<<(pin_num*4));
port_num->CRH |= (0x8<<(pin_num*4));}
else if(mode==input_pull_up){port_num->CRH &=~ (0xF<<(pin_num*4));
port_num->CRH |= (0x8<<(pin_num*4));}
if((mode==output_push_pull)||(mode==output_open_drain)||
(mode==alternate_function_output_push_pull)||
(mode==alternate_function_output_open_drain))
{
if (speed==2) port_num->CRH |= (0x2<<(pin_num*4));
else if(speed==10) port_num->CRH |= (0x1<<(pin_num*4));
else if(speed==50) port_num->CRH |= (0x3<<(pin_num*4));
}
If (mode==input_pull_down) {pin_num+=8; port_num->ODR &=~ (1<<pin_num);}
else if(mode==input_pull_up) {pin_num+=8; port_num->ODR |= (1<<pin_num);}
}
}
تابع gpio_write نیز هنگامی که پایۀ مورد نظر به عنوان خروجی تنظیم شده باشد استفاده می شود. این تابع وضعیت صفر یا یک نوشته شده در آرگومان ورودی سوم را به پایۀ تعیین شده می دهد. این پایه در آرگومان ورودی اول و دوم تعیین می شود. در این تابع رجیسترهای BRR و BSRR مقداردهی می شوند.
void gpio_write(GPIO_TypeDef* port_num, unsigned char pin_num,unsigned char value)
{
if(value==0) port_num->BRR |= (1<<pin_num);
else port_num->BSRR |= (1<<pin_num);
}
تابع gpio_read این کتابخانه وضعیت پایۀ ورودی تعیین شده در آرگومان های ورودی را برمی گرداند.
unsigned char gpio_read(GPIO_TypeDef* port_num, unsigned char pin_num)
{
return (port_num->IDR &= (1<<pin_num));
}
نحوۀ استفاده از این توابع به شکل مثال زیر است.
gpio_config(GPIOB, 3, output_push_pull,10); // Set PB3 as open drain output
gpio_config(GPIOC, 11, output_push_pull,10); // Set PC11 as open drain output
gpio_config(GPIOD, 5, output_push_pull,10); // Set PD5 as open drain output
gpio_config(GPIOD, 3, output_push_pull,10); // Set PD3 as open drain output
gpio_config(GPIOC, 9, output_push_pull,10); // Set PC9 as open drain output
gpio_write(GPIOB, 3, 0); // Clear PB3
gpio_write(GPIOC, 11, 1); // Set PC11
gpio_write(GPIOD, 5, 1); // Set PD5
gpio_write(GPIOD, 3, 1); // Set PD3
gpio_write(GPIOC, 9, 1); // Set PC9
if(gpio_read(GPIOE,3)==0) // Read PE3
{
... ;
}
تصویر 2 – نتیجۀ مقداردهی به پایه های سون سگمنت مطابق مثال بالا
استفاده از توابع GPIO برای نمایش در سون سگمنت
برای نمایش عدد در سون سگمنت لازم است تا با آرایشی خاص به پایه های آن مقدار بدهیم. برای مثال اگر بخواهیم عدد 1 در سون سگمنت آند مشترک نمایش داده شود، باید پایۀ آند به VCC متصل شود، پایه های B و C سون سگمنت صفر و باقی پایه ها 1 شوند. در این صورت با روشن شدن سگمنت های B و C، عدد 1 نمایش داده می شود. در سون سگمنت کاتد مشترک باید پایۀ کاتد صفر، پایۀ B و C یک و باقی پایه ها صفر شوند. در این پروژه از سون سگمنت آند مشترک استفاده کرده ایم. مقداردهی پایه های سون سگمنت آند مشترک برای نمایش عدد 1 به صورت زیر است.
gpio_write(seven_segment_a_port ,seven_segment_a_pin ,set);
gpio_write(seven_segment_b_port ,seven_segment_b_pin ,clear);
gpio_write(seven_segment_c_port ,seven_segment_c_pin ,clear);
gpio_write(seven_segment_d_port ,seven_segment_d_pin ,set);
gpio_write(seven_segment_e_port ,seven_segment_e_pin ,set);
gpio_write(seven_segment_f_port ,seven_segment_f_pin ,set);
gpio_write(seven_segment_g_port ,seven_segment_g_pin ,set);
gpio_write(seven_segment_dp_port ,seven_segment_dp_pin ,set);
این روش تنها برای سون سگمنت های تک رقمی مورد استفاده قرار می گیرد. برای راه اندازی سون سگمنت 4 تایی باید ابتدا عددی را در یکی از ارقام و پس از مقداری تأخیر، عدد بعدی را در رقم بعدی نمایش دهیم. در کد زیر ابتدا عدد 1 در رقم دهگان و عدد 5 در رقم یکان نمایش داده می شود. مقادیر set و clear و دیگر ورودی های تابع زیر در ابتدای کتابخانۀ سون سگمنت دیفاین شده اند.
gpio_write(seven_segment_d1_port ,seven_segment_d1_pin ,clear);
gpio_write(seven_segment_d2_port ,seven_segment_d2_pin ,clear);
gpio_write(seven_segment_d3_port ,seven_segment_d3_pin ,set);
gpio_write(seven_segment_d4_port ,seven_segment_d4_pin ,clear);
gpio_write(seven_segment_a_port ,seven_segment_a_pin ,set);
gpio_write(seven_segment_b_port ,seven_segment_b_pin ,clear);
gpio_write(seven_segment_c_port ,seven_segment_c_pin ,clear);
gpio_write(seven_segment_d_port ,seven_segment_d_pin ,set);
gpio_write(seven_segment_e_port ,seven_segment_e_pin ,set);
gpio_write(seven_segment_f_port ,seven_segment_f_pin ,set);
gpio_write(seven_segment_g_port ,seven_segment_g_pin ,set);
gpio_write(seven_segment_dp_port ,seven_segment_dp_pin ,set);
delay_ms(4);
gpio_write(seven_segment_d1_port ,seven_segment_d1_pin ,clear);
gpio_write(seven_segment_d2_port ,seven_segment_d2_pin ,clear);
gpio_write(seven_segment_d3_port ,seven_segment_d3_pin ,clear);
gpio_write(seven_segment_d4_port ,seven_segment_d4_pin ,set);
gpio_write(seven_segment_a_port ,seven_segment_a_pin , set);
gpio_write(seven_segment_b_port ,seven_segment_b_pin , clear);
gpio_write(seven_segment_c_port ,seven_segment_c_pin , clear);
gpio_write(seven_segment_d_port ,seven_segment_d_pin , set);
gpio_write(seven_segment_e_port ,seven_segment_e_pin , set);
gpio_write(seven_segment_f_port ,seven_segment_f_pin , clear);
gpio_write(seven_segment_g_port ,seven_segment_g_pin , clear);
gpio_write(seven_segment_dp_port ,seven_segment_dp_pin , set);
نمایش هر عدد روی رقم مورد نظر در تابع segment_puts_digit انجام می شود. و این تابع در تابع seven_segment_puts_number استفاده می شود. توابع ذکر شده در کتابخانۀ seven_segment.h قرار دارند. ما برای نمایش عدد یک تا چهار رقمی، کافی است آن عدد را در ورودی تابع seven_segment_puts_number قرار دهیم. فراخوانی این تابع به صورت پی در پی با تأخیر حدود 0.5 میلی ثانیه، نمایش عدد نوشته شده در ورودی را در پی خواهد داشت. در کتابخانۀ seven_segment.h تابع seven_segment_config نیز وجود دارد که برای تعیین مد و مقداردهی اولیه پایه های میکروکنترلر است. تابع seven_segment_puts_number به صورت زیر است.
void seven_segment_puts_number(unsigned int number)
{
unsigned char dig1num=0, dig2num=0, dig3num=0, dig4num=0;
dig4num= number%10;
dig3num=(number/10)%10;
dig2num=(number/100)%10;
dig1num= number/1000;
segment_puts_digit (dig1num,hezargan);
delay_us(500);
segment_puts_digit (dig2num,sadgan);
delay_us(500);
segment_puts_digit (dig3num,dahgan);
delay_us(500);
segment_puts_digit (dig4num,yekan);
delay_us(500);
}
تصویر 3 – راه اندازی سون سگمنت با STM32، نمایش اعداد 1 تا 4 در هر رقم طی مراحل مالتی پلکس
شرح برنامۀ راه اندازی سون سگمنت با STM32
پروژۀ حاضر، به عنوان برنامه نویسی سون سگمنت با STM32 برای برد توسعۀ STM3F10xVxxx یوبرد نوشته شده است. در این پروژه می خواهیم یک عدد را در سون سگمنت نصب شده روی برد توسعه نمایش دهیم. همچنین قصد داریم با دو کلید چپ و راست روی برد، مقدار عدد را کم و زیاد کنیم. در ابتدای تابع main یک متغیر تعریف و در ادامه، تابع system_init آورده شده است. این تابع را برای تنظیم کلاک میکروکنترلر و همچنین غیر فعال کردن دیباگ از طریق JTAG نوشته ایم. پس از آن، تابع seven_segment_config که پایه های میکروکنترلر را برای اتصال سون سگمنت تنظیم می کند، آمده است. سپس برای تنظیم کلیدهای چپ و راست برد توسعه در حالت ورودی پول آپ، تابع keys_init را فراخوانی کرده ایم. در حلقۀ while تابع seven_segment_puts_number با تأخیر 0.5 میلی ثانیه دائماً اجرا می شود. همچنین دو شرط برای خواندن وضعیت پایه های متصل به کلیدهای چپ و راست نوشته شده است. متغیر number با فشردن کلید سمت چپ، کم و با فشردن کلید سمت راست، زیاد می شود. این متغیر که در تابع seven_segment_puts_number قرار می گیرد، در سون سگمنت نمایش داده می شود. این پروژه نکاتی دارد که در ادامه آنها را توضیح می دهیم.
int main(void)
{
unsigned int number=322;
system_init();
seven_segment_config();
keys_init();
while(1)
{
delay_us(500);
seven_segment_puts_number(number);
if(gpio_read(left_key_port,left_key_pin)==0)
{
delay_us(40000);
while(gpio_read(left_key_port,left_key_pin)==0);
number--;
if(number<=0) number=0;
}
else if(gpio_read(right_key_port,right_key_pin)==0)
{
delay_us(40000);
while(gpio_read(right_key_port,right_key_pin)==0);
number++;
if(number>=9999) number=9999;
}
}
}
تصویر 4 – راه اندازی سون سگمنت 4 تایی با STM32
نکات پروژۀ راه اندازی سون سگمنت با STM32
نکتۀ نخست این است که تابع seven_segment_puts_number باید با تأخیری صحیح به صورت مداوم اجرا شود. بنابراین اگر در حلقۀ while دستورهای دیگری اجرا کنیم که اجرا شدن این تابع را به تعویق بیندازد، چشمک زدن سون سگمنت مشهود خواهد شد. یکی از راه های برطرف کردن این مشکل، استفاده از تأخیر با تایمر در میکروکنترلرها است. نکتۀ دوم در شرط های بررسی وضعیت کلیدهاست. در این شرط ها از تأخیر و حلقۀ while بدون دستور استفاده شده است. با هر بار فشردن کلید، برنامه متوقف و نمایش در سون سگمنت مختل می شود. با برطرف شدن مشکل قبلی، این مشکل نیز رفع می شود. البته راه های دیگری هم برای رفع آن وجود دارد که یافتن آنها را به عهدۀ علاقه مندان می گذاریم. دو نکتۀ قبل تنها مربوط به راه اندازی سون سگمنت با STM32 نیست. بلکه کلاً مربوط به راه اندازی سون سگمنت هاست.
تصویر 5 – نتیجۀ تأخیر نامناسب در اجرای تابع سون سگمنت و چشمک زدن آن
نکتۀ سوم این است که برای استفاده از پایۀ PB3 و PB4 باید قابلیت دیباگ با JTAG غیر فعال شود. برای این کار کافی است بیت 25 از رجیستر MAPR یک و بیت های 24 و 26 صفر شوند. مقداردهی به این بیت ها با دادن مقدار AFIO_MAPR_SWJ_CFG_JTAGDISABLE به این رجیستر به صورت زیر انجام می شود. رجیستر MAPR جزء رجیسترهای AFIO است. بنابراین برای تغییرات در آن، لازم است بیت AFIOEN از رجیستر APB2ENR (در بخش RCC) یک شود. همان طور که ملاحظه می شود، این بیت قبل از مقداردهی به رجیستر MAPR یک شده است.
RCC->APB2ENR |= (1<<0);
AFIO->MAPR |= AFIO_MAPR_SWJ_CFG_JTAGDISABLE;
نکتۀ چهارم قرار دادن عبارات دیفاین شده در رجیسترهاست. از این عبارات که در فایل stm3f10x.h دیفاین شده اند، می توان در مقداردهی به رجیسترها استفاده کرد. مقداردهی رجیسترهای RCC که در زیر آمده، کلاک بخش های مختلف میکروکنترلر را به صورت تصویر زیر پیکربندی می کند.
// clock source and pll setting for HCLK=72MHz
RCC->CR |= RCC_CR_PLLON | RCC_CR_HSEON;
// set pll freq , ... , PCLK1 freq = Fpclk1 = 9MHz , Fpclk2 = 9MHz
RCC->CFGR |= RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9 | RCC_CFGR_SW_PLL |
RCC_CFGR_PPRE1_DIV8 | RCC_CFGR_PPRE2_DIV8;
تصویر 6 – تنظیمات کلاک STM32F103VE برای این پروژه
راه اندازی سون سگمنت با توابع HAL
در بخش های قبل راه اندازی سون سگمنت با STM32 را به صورت رجیستری دیدیم. در این بخش با استفاده از نرم افزار STM32CubeMX و Keil به راه اندازی آن با توابع HAL می پردازیم. پس از اجرای نرم افزار STM32CubeMX از منوی File روی New Project کلیک می کنیم. در پنجرۀ New Project از بخش Part Number Search میکروکنترلر STM خود را جست و جو می کنیم (1). از میان لیست نتایج روی میکروکنترلر مورد نظر دابل کلیک می کنیم (2). در محیط STM32CubeMX از پنل System Core روی RCC کلیک می کنیم (3 و 4). در بخش RCC Mode and Configuration کلاک سیستم را انتخاب می کنیم. ما با توجه به استفاده از یک کریستال 8 مگاهرتز، High Speed را روی Crystal/Ceramic Resonator قرار می دهیم (5). سپس روی تب Clock Configuration کلیک می کنیم (6). در این تب کلاک سیستم را مطابق تصویر بخش قبل تنظیم می کنیم.
نکته: شما می توانید تنظیمات کلاک را مطابق میل خود انجام دهید. توضیح پیکربندی کلاک در STM32 خارج از مبحث این نوشته است.
تصویر 7 – ساخت پروژه در STM32CubeMX و انتخاب منبع کلاک
دوباره به تب Pinout & Configuration برمی گردیم (1). در قسمت System Core روی GPIO کلیک می کنیم (2). در محیط گرافیکی سمت راست روی پایه های میکروکنترلر کلیک و مد آنها را انتخاب می کنیم (3 و 4). بعد از آن از پنل GPIO Mode and Configuration تب GPIO را باز می کنیم (6). سپس روی هر پایه کلیک می کنیم و در قسمت Pxy Configuration در پایین تب GPIO تنظیمات مد انتخاب شده را انجام می دهیم (7 و 8). می توانیم در جلوی User Label یک برچسب برای پایه های انتخاب شده تعیین کنیم (9).
نکته: ممکن است پنل GPIO Mode and Configuration نمایش داده نشود. برای ظاهر شدن این پنل باید روی علامت فلش به سمت راست کلیک کنیم (5).
تصویر 8 – تنظیمات GPIO در STM32CubeMX برای راه اندازی سون سگمنت با STM32
نوشتن پروژه در نرم افزار Keil
تا اینجا کلاک سیستم و تنظیمات پایه ها را مطابق اتصالات برد توسعۀ یوبرد انجام دادیم. پس از انجام این تنظیمات در تب Project Manager نام پروژه را در قسمت Project Name تایپ می کنیم (1). سپس در بخش Project Location محل ذخیرۀ پروژه را تعیین می کنیم (2). بعد از آن در بخش Toolchain / IDE گزینۀ MDK-ARM را انتخاب می کنیم (3). در نهایت روی دکمۀ GENERATE CODE کلیک می کنیم (4). در این مرحله باید منتظر بمانیم تا پروژه ساخته شود. پس از ساخته شدن پروژه یک پیغام ظاهر می شود. در پنجرۀ این پیغام می توان انتخاب کرد که فایل پروژۀ Keil یا پوشۀ حاوی آن باز شود. برای باز شدن پروژۀ ساخته شده در نرم افزار Keil، روی Open Project کلیک می کنیم (5). پس از این کار نرم افزار Keil پروژۀ ساخته شده را باز می کند. در این مرحله می توانیم نرم افزار STM32CubeMX را ببندیم.
تصویر 9 – گرفتن خروجی Keil از STM32CubeMX برای راه اندازی سون سگمنت با STM32
در پروژۀ باز شده در Keil فایل main.c را باز می کنیم. در ابتدای آن، فایل main.h به پروژه اضافه شده است. در این فایل می بینیم برچسب هایی که برای پایه ها انتخاب کرده بودیم، دیفاین شده اند.
#define left_key_Pin GPIO_PIN_2
#define left_key_GPIO_Port GPIOE
#define right_key_Pin GPIO_PIN_3
#define right_key_GPIO_Port GPIOE
#define seven_segment_b_Pin GPIO_PIN_8
#define seven_segment_b_GPIO_Port GPIOC
#define seven_segment_d4_Pin GPIO_PIN_9
#define seven_segment_d4_GPIO_Port GPIOC
#define seven_segment_d1_Pin GPIO_PIN_11
#define seven_segment_d1_GPIO_Port GPIOC
#define seven_segment_d3_Pin GPIO_PIN_3
#define seven_segment_d3_GPIO_Port GPIOD
#define seven_segment_g_Pin GPIO_PIN_4
#define seven_segment_g_GPIO_Port GPIOD
#define seven_segment_d2_Pin GPIO_PIN_5
#define seven_segment_d2_GPIO_Port GPIOD
#define seven_segment_c_Pin GPIO_PIN_6
#define seven_segment_c_GPIO_Port GPIOD
#define seven_segment_f_Pin GPIO_PIN_7
#define seven_segment_f_GPIO_Port GPIOD
#define seven_segment_a_Pin GPIO_PIN_3
#define seven_segment_a_GPIO_Port GPIOB
#define seven_segment_dp_Pin GPIO_PIN_4
#define seven_segment_dp_GPIO_Port GPIOB
#define seven_segment_d_Pin GPIO_PIN_9
#define seven_segment_d_GPIO_Port GPIOB
#define seven_segment_e_Pin GPIO_PIN_0
#define seven_segment_e_GPIO_Port GPIOE
در فایل main.c پس از عبارت p#include “main.h”، فایل seven_segment.h را به برنامه اضافه می کنیم.
/* Includes ------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------*/
/* USER CODE BEGIN Includes */
#include "seven_segment.h"
/* USER CODE END Includes */
در تابع main متغیر number را تعریف و پس از تابع MX_GPIO_Init، تابع seven_segment_config را قرار می دهیم.
/* USER CODE BEGIN 1 */
unsigned int number=1399;
/* USER CODE END 1 */
.
.
.
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
seven_segment_config();
/* USER CODE END 2 */
در حلقۀ while نیز برنامۀ قبلی را به شکل زیر تغییر می دهیم.
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_Delay(1);
seven_segment_puts_number(number);
if(HAL_GPIO_ReadPin(left_key_GPIO_Port,left_key_Pin)==0)
{
HAL_Delay(100);
while(HAL_GPIO_ReadPin(left_key_GPIO_Port,left_key_Pin)==0);
number--;
if(number<=1) number=1;
}
else if(HAL_GPIO_ReadPin(right_key_GPIO_Port,right_key_Pin)==0)
{
HAL_Delay(100);
while(HAL_GPIO_ReadPin(right_key_GPIO_Port,right_key_Pin)==0);
number++;
if(number>=9999) number=9999;
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
حال کافی است تغییرات لازم در کتابخانۀ سون سگمنت اعمال شود. تابع seven_segment_config به شکل زیر می شود.
void seven_segment_config(void)
{
HAL_GPIO_WritePin(seven_segment_d1_GPIO_Port, seven_segment_d1_Pin, clear);
HAL_GPIO_WritePin(seven_segment_d2_GPIO_Port, seven_segment_d2_Pin, clear);
HAL_GPIO_WritePin(seven_segment_d3_GPIO_Port, seven_segment_d3_Pin, clear);
HAL_GPIO_WritePin(seven_segment_d4_GPIO_Port, seven_segment_d4_Pin, clear);
HAL_GPIO_WritePin(seven_segment_a_GPIO_Port, seven_segment_a_Pin, clear);
HAL_GPIO_WritePin(seven_segment_b_GPIO_Port, seven_segment_b_Pin, clear);
HAL_GPIO_WritePin(seven_segment_c_GPIO_Port, seven_segment_c_Pin, clear);
HAL_GPIO_WritePin(seven_segment_d_GPIO_Port, seven_segment_d_Pin, clear);
HAL_GPIO_WritePin(seven_segment_e_GPIO_Port, seven_segment_e_Pin, clear);
HAL_GPIO_WritePin(seven_segment_f_GPIO_Port, seven_segment_f_Pin, clear);
HAL_GPIO_WritePin(seven_segment_g_GPIO_Port, seven_segment_g_Pin, clear);
HAL_GPIO_WritePin(seven_segment_dp_GPIO_Port, seven_segment_dp_Pin, clear);
}
در تابع seven_segment_puts_number تنها نیاز به تغییر تابع delay است.
void seven_segment_puts_number(unsigned int number)
{
unsigned char dig1num=0, dig2num=0, dig3num=0, dig4num=0;
dig4num= number%10;
dig3num=(number/10)%10;
dig2num=(number/100)%10;
dig1num= number/1000;
segment_puts_digit (dig1num,hezargan);
HAL_Delay(1);
segment_puts_digit (dig2num,sadgan);
HAL_Delay(1);
segment_puts_digit (dig3num,dahgan);
HAL_Delay(1);
segment_puts_digit (dig4num,yekan);
HAL_Delay(1);
}
در تابع segment_puts_digit نیز تمامی توابع gpio_write به تابع HAL نظیر آن تغییر می کند. برای مثال تابع
gpio_write(seven_segment_d2_port ,seven_segment_d2_pin , clear);
به شکل زیر درمی آید.
HAL_GPIO_WritePin(seven_segment_d2_GPIO_Port, seven_segment_d2_Pin, clear);
تصویر 10 – نتیجۀ راه اندازی سون سگمنت با STM32 با استفاده از توابع HAL
نتایج راه اندازی سون سگمنت با STM32
- توابعی که برای راه اندازی GPIO در STM32نوشتیم، تنها برای کاربرد سون سگمنت نیست. هر جا نیاز به پیکربندی، تغییر و خواندن وضعیت پایه ها داشته باشیم، می توانیم از این توابع استفاده کنیم. در این صورت بدون درگیر شدن با رجیسترها، پیکربندی صحیح را به آسانی انجام می دهیم.
- راه اندازی سون سگمنت با میکروکنترلرهای STM32 تفاوت چشمگیری با راه اندازی آن با AVR و میکروکنترلرهای دیگر ندارد. تنها تفاوت آن در تنظیم و مقداردهی به رجیسترهای GPIO است. همان طور که دیدیم، راه اندازی سون سگمنت مالتی پلکس با STM32 نیز همانند راه اندازی آن با میکروکنترلرهای AVR است.
- راه اندازی سون سگمنت تک رقمی با STM32 نیز مانند راه اندازی سون سگمنت چند رقمی است که بررسی کردیم. کافی است در تابع segment_puts_digit آرگومان ورودی دوم و چهار شرط اول مربوط به آن پاک شوند. و به جای آنها پایۀ آند (یا کاتد) مشترک مقداردهی شود. همچنین دیگر نیاز نیست از تابع seven_segment_puts_number استفاده کنیم. برای نمایش یک عدد در سون سگمنت تک رقمی می توان تنها تابع segment_puts_digit را فراخوانی کرد.
- برای نمایش اعداد روی سون سگمنت چند رقمی باید ابتدا عدد مربوط به یک رقم نمایش داده شود. در این لحظه باید آن رقم فعال و باقی ارقام غیر فعال شده باشد. سپس عدد مربوط به رقم بعدی نمایش داده و آن رقم فعال و دیگر ارقام غیر فعال می شود. به همین ترتیب، ارقام در حالی که عدد مربوط به خود را نمایش می دهند، پشت سر هم فعال می شوند. برای این که روشن شدن سگمنت های هر رقم به چشم بیاید، لازم است برای لحظاتی روشن بمانند. بنابراین بین تغییر رقم باید یک تأخیر وجود داشته باشد. تأخیر 0.5 میلی ثانیه برای 4 رقم مقدار مناسبی است.
- برای نمایش یک عدد به طور پیوسته در سون سگمنت باید تابع آن به طور پیوسته با تأخیر مناسب اجرا شود. در غیر این صورت سون سگمنت چشمک می زند یا ارقام به صورت تکی با تأخیر نمایش داده می شوند. بنابرین نمی توان چنین برنامه ای را برای ساخت شمارنده با سون سگمنت به کار گرفت. مگر این که تابع نمایش در سون سگمنت به روشی به طور دائم اجرا شود.
- در درایو کردن سون سگمنت با میکروکنترلر STM32F1 با پایه های PB3 و PB4 برخورد کردیم. برای این که بتوان این پایه ها را به عنوان GPIOبه کار گرفت، باید دیباگ JTAG با مقداردهی به بیت های 24 تا 26 رجیستر MAPR غیر فعال شود.
- با استفاده از نرم افزار STM32CubeIDE تنظیمات GPIO و کلاک به راحتی با چند کلیک انجام می شود. پس از آن برای نوشتن روی پایه ها و خواندن از آنها کافی است توابع HAL را فراخوانی کنیم.
- بهتر است به جای استفاده از تابع delay که خود آن را نوشته بودیم، از تابع HAL_Delay استفاده کنیم.
آموزش های یوبرد مرتبط با این نوشته:
آقای بداغی سلام. وقتتون بخیر. بابت به اشتراک گذاشتن کتابخونه gpio stm32 دستتون درد نکنه چند وقتیه دارم با کیل به صورت رجیستری می نویسم. هر وقت میخوام یه led روشن و خاموش کنم یا یه کلید وصل کنم باید کلی کد بنویسم. الان با توابعی که در اختیارم گذاشتید کارم خیلی راحت شده
بازم تشکر
سلام. ممنون. خواهش میکنم. امیدوارم مفید بوده باشه. موفق و پیروز باشید
سلام آقای بداغی
میخواستم بدونم آموزش های یوبرد رو چه جوری میشه تهیه کرد؟
روی سایت که قسمت پیشرفته رو زده به زودی .
ممنون
سلام. این آموزش هنوز در دست ساخته و بنده نمیتونم زمان دقیق تکمیل شدنش رو تخمین بزنم. ان شاءالله بعد از این که تکمیل شد از حساب کاربریتون میتونید تهیه ش کنید. خواهش میکنم.
بسیار عالی سپاس از به اشتراک گزاشتن تجربیاتتون
سلام. خیلی ممنون دوست عزیز. بزرگوارید. خواهش میکنم.
با سلام
کتابخانه seven_segment.h رو از کجا بایددانلود کرد ؟
من لینک رو پیدا نکردم یا اصلا قرار ندادید ؟!
سلام. لینک دانلود فایل پیوست در انتهای نوشته قرار گرفت.