در مقاله ی قبلی بررسی کردیم که برای پروژه led چشمک زن چه تغییراتی رو باید در رجیسترها اعمال کنیم ، میدونیم رجیسترهامون 32 بیتی هستند و آدرسشون رو هم داریم. در ادامه باید در زبان سی به محتوای آدرس ها دسترسی پیدا کنیم و مقدار رجیسترها رو تغییر بدیم. برای دسترسی به محتوایی که در آدرس ذخیره شده به پوینتر نیاز داریم. در این مقاله یادمیگرید چجوری پوینتر بسازید.
این مقاله خیلی مهمه
– دوم : دسترسی به رجیسترها بصورت 32 بیتی در قالب اعضای استراکچر برای میکروکنترلرهای STM32
– روش سوم : دسترسی به بیت رجیسترها با بیت فیلد و یونیون برای میکروکنترلرهای STM32
روش اول : دسترسی به رجیسترها بصورت 32 بیتی با ساخت پوینتر برای هر رجیستر برای میکروکنترلر های STM32
ساخت پوینتر برای هر رجیستر
فرض کنید میخواهیم پوینتری بسازیم برای دسترسی به رجیستر 32 بیتی RCC_APB2ENR. مقدار این پوینتر آدرس رجیستر RCC_APB2ENR خواهد بود یعنی 0x40021018 . برای ساخت پوینتر ثابت ابتدا مقدارش رو مینویسیم ، سپس در یک پرانتر قبل از مقدار باید نوعش رو بنویسیم ، به این کار میگن کست کردن . نوع پوینتر دو بخش داره، ابتدا نوع چیزی رو مینویسید که در آدرسی که مقدار پوینتر هست ذخیره شده. مثلا در در آدرس 0x40021018 یک رجیستر 32 بیتی وجود داره که نوعش میشه uint32_t ( یا unsigned int ) بخش دوم هم علامت استریسک یا dereference operator هست.
پوینتر رو ساختیم ، حالا برای اینکه با استفاده از این پوینتر به رجیستر RCC_APB2ENR دسترسی پیدا کنیم برای اینکه مقدارش رو بخونیم یا تغییر بدیم لازمه قبل از پوینتر علامت استریسک یا dereference operator بذاریم، در مثال اول عدد 10 در رجیستر ذخیره میشه و در مثال دوم مقدار رجیستر خونده میشه ، با 10 جمع میشه و در رجیستر ذخیره میشه.
در مورد رجیستر RCC_APB2ENR باید بیت چهارمش رو یک کنیم تا کلاک پریفرال GPIOC فعال بشه ، برای این منظور به ماسک بیت شماره چهار نیاز داریم که یک عدد 32 بیتی هست که فقط بیت چهارمش یکه. جهت ساخت این عدد از عملگر بیتی left shift استفاده میکنیم ، 1 رو left shift چهار میکنیم.
مقاله ی ” کاربرد عملگرهای بیتی در زبان سی ” رو مطالعه کنید.
وقتی میخواهیم بیتی رو در یک رجیستر یک کنیم باید رجیستر رو bitwise or assignment ماسک بیت کنیم و وقتی میخواهیم بیتی رو صفر کنیم رجیستر رو bitwise and assignment میکنیم با نات ماسک بیت . برای اینکه این عملیات ها رو بهتر درک کنید مقاله کاربرد عملگر های بیتی رو مطالعه کنید.
تا اینجا یادگرفتیم چطور باید پوینتر برای دسترسی به رجیستر رو بسازیم و چطور میتونیم بیت های دلخواهمون رو در یک رجیستر تغییر بدیم. تغییراتی که باید در رجیسترها اعمال بشه رو در قالب یک جدول در کامنت اول فایل main.c نوشتم.
دسترسی به رجیسترها با ساخت پوینتر اختصاصی برای هر رجیستر برای انجام پروژه ی led چشمک زن
در قدم اول برای فعالسازی کلاک پریفرال GPIOC باید بیت IOPCEN ( بیت 4 ) در رجیستر RCC_APB2ENR رو یک کنیم. قبلا یادگرفتیم که چطور با کست کردن آدرس رجیستر پوینتر رو بسازیم. برای دسترسی به محتوای رجیستر پشت پوینتر علامت استریسک میذاریم. برای یک کردن بیت چهار رجیستر رو با ماسک بیت چهار bitwise or assignment میکنیم.
در قدم بعدی برای اینکه پین PC13 در حالت خروجی با سرعت 2MHz تنظیم بشه باید بیت های MODE13 در رجیستر GPIOC_CRH رو 10 کنیم ، یعنی بیت 20 صفر و بیت 21 باید یک بشه. مشابه رجیستر RCC_APB2ENR برای دسترسی به محتوای رجیستر GPIOC_CRH هم پوینتر میسازیم ، آدرس رجیستر GPIOC_CRH رو مینویسیم و قبل مقدار پوینتر در یک پرانتز نوعش رو مشخص میکنیم. نوع این پوینتر هم مشابه قبلی * uint32_t هست. برای دسترسی به محتوای رجیستر GPIOC_CRH پشت پوینتر علامت استریسک میذاریم. جهت یک کردن بیت 21 رجیستر رو bitwise or assignment میکنیم با ماسک بیت 20 که میشه 20<<1 و برای صفر کردن بیت 20 رجیستر رو bitwise and assignment میکنیم با نات ماسک بیت 21 که میشه (21<<1)~ .
برای تنظیم پین PC13 در حالت خروجی Open drain باید بیت های CNF13 در رجیستر GPIOC_CRH رو 01 کنیم ، یعنی بیت 22 یک و بیت 23 باید صفر بشه. مشابه قبل تغییرات رو اعمال میکنیم.
تا الان اقدامات مورد نیاز برای تنظیم پین PC13 در حالت خروجی دیجیتال انجام شده. در قدم بعدی در حلقه ی بینهایت باید خروجی پین PC13 رو با یک تاخیر زمانی صفر و یک کنیم تا led چشمک زن بشه. برای این منظور یک تابع تاخیر با دو for تعریف میکنم تا بعد از صفر کردن و بعد از یک کردن پین این تابع رو فراخوانی کنیم.
برای اینکه led سبز رنگ روی برد Blue pill چشمک زن بشه باید پین PC13 رو در حلقه ی بینهایت متناوبا صفر و یک کنیم ، جهت یک کردن خروجی PC13 باید بیت 13 رجیستر GPIOC_ORD یک بشه و برای صفر کردن پین PC13 باید بیت 13 رجیستر GPIOC_ODR رو صفر کنیم. مشابه رجیستر های قبلی برای GPIOC_ODR هم پوینتر میسازیم و تغییرات رو در بیت 13 اعمال میکنیم. برای اینکه سرعت خاموش و روشن شدن led رو کم کنیم تا با چشمک زدن led رو با چشم ببینیم بعد از صفر و یک کردن باید تابع تاخیر رو فراخوانی کنیم.
پروژه led چشمک زن با روش اول برنامه نویسی رجیستری برای میکروکنترلر های STM32 به پایان رسید. در روش اول برنامه نویسی رجیستری برای هر رجیستر به صورت جداگانه پوینتر ساختیم و با این پوینتر ها به رجیسترها دسترسی پیدا کردیم و با عملگرهای بیتی مقدار بیت ها رو تغییر دادیم.
روش دوم : دسترسی به رجیسترها بصورت 32 بیتی در قالب اعضای استراکچر برای میکروکنترلرهای STM32
در نرم افزار STM32CubeIDE یک پروژه ی جدید به نام structure ایجاد و تابع delay رو هم اضافه کنید.
آموزش STM32 ، ساخت استراکچر قالب رجیسترهای پریفرال GPIO
روش قبلی دسترسی به رجیسترها برای هر رجیستر یک پوینتر درست کردیم، در روش دوم برای دسترسی به همه ی رجیسترهای یک پریفرال فقط به یک پوینتر نیاز داریم و برای هر پریفرال یک پوینتر طراحی میکنیم. پریفرال های GPIO رو در نظر بگیرید، این پریفرال 7 تا رجیستر 32 بیتی داره که پشت سر هم در حافظه قرار دارند، مشابه استراکچری که هفت ها عضو 32 بیتی داره.
پریفرال GPIOC رو در نظر بگیرید ،آدرس اولین رجیستر این پریفرال 0x40011000 و این پریفرال 7 تا رجیستر 32 بیتی داره که پشت سر هم در حافظه قرار دارند، مشابه استراکچری که هفت تا عضو 32 بیتی داره و در آدرس 0x40011000 ذخیره شده. با توجه به رجیستر مپ پریفرال های GPIO یک نوع متغیر استراکچر به نام GPIO_TypeDef قالب رجیسترهای پریفرال های GPIO طراحی میکنیم که هفت تا عضو 32 بیتی از نوع uint32_t داره و اعضای این استراکچر رو هم مثل رجیسترهای پریفرال های GPIO نامگذاری میکنیم.
در نظر بگیرید که یک استراکچر فرضی از نوع GPIO_TypeDef در آدرس 0x40011000 ذخیره شده. اگر به اعضای این استراکچر دسترسی پیدا کنیم مثل اینه که به رجیستر های پریفرال GPIOC دسترسی پیدا کردیم. اگر بخواهیم به رجیستر های پریفرال GPIOC در قالب اعضای این استراکچر فرضی دسترسی پیدا کنیم به پوینتر نیاز داریم .
آمورش STM32 ، ساخت پوینتر به استراکچر برای دسترسی به رجیسترهای پریفرال GPIOC
برای ساخت این پوینتر باید نوع و مقدارش رو بدونیم. مقدار این پوینتر آدرس استراکتراکچر فرضی خواهد بود که آدرس اولین رجیستر پریفرال GPIOC یعنی 0x40011000 هست. نوع پوینتر هم دو قسمت داره ، قسمت اول نوع متغیری که در نظر میگیریم در آدرسی کم مقدار پوینتر هست ذخیره شده و قسمت دوم هم علامت استریسک هست. الان ما میخواهیم فرض کنیم در آدرس 0x40011000 یک استراکچر از نوع GPIO_TypeDef هست ، پس نوع پوینتر میشه * GPIO_TypeDef .
میدونیم برای دسترسی به اعضای استراکچر باید بعد از اسم استراکچر عملگر دات ( . ) بذاریم ولی چطور با استفاده از پوینتر به استراکچر، به اعضا دسترسی پیدا کنیم.
برای دسترسی به محتوای آدرس پوینتر باید قبل از پوینتر عملگر استریسک رو قرار بدیم ، نکته ای که وجود داره اینه که برای استراکچر حتما استریسک پوینتر باید داخل پرانتز باشه ، بعد از پرانتر دات ( . ) میذاریم و اینجوری میتونیم با پوینتر به استراکچر به اعضاش دسترسی پیدا کنیم.
البته راه بهتری هم وجود داره و اون استفاده از عملگر پیکان یا arrow operator هست. اگر پوینتر به استراکچر رو داشته باشیم ، برای دسترسی به اعضای استراکچر کافیه بعد از پوینتر عملگر پیکان <- بذاریم.
با استفاده از یک definition میتونیم کدمون رو خواناتر کنیم. در کد عبارت GPIOC که اسم پریفرال هست رو ((GPIO_TypeDef *) 0x40011000) که پوینتر به رجیسترهای پریفرال هست define میکنم. یعنی هر جا در کد GPIOC رو بنویسی با پوینتر جایگزین میشه.
آموزش STM32 ، دسترسی به رجیسترهای پریفرال GPIOC درقالب اعضای استراکچر
در روش دوم برنامه نویسی رجیسترسی برای میکروکنترلر های STM32 ، قالب رجیسترهای پریفرال GPIO یک نوع متغیر استراکچر ساختیم چون به این نوع متغیر برای ساخت پوینتر به استراکچر فرضی نیاز داریم. با استفاده از پوینتر GPIOC می تونیم به اعضای استراکچر فرضی که در آدرس 0x40011000 ( آدرس اولین رجیستر پریفرال GPIOC ) ذخیره شده دسترسی پیدا کنیم و رجیستر های پریفرال GPIOC رو تغییر بدیم.
در پروژه ی led چشمک زن لازمه رجیستر های CRH و ODR از پریفرال GPIOC رو تغییر بدیم .برای دسترسی به رجیسترهای پریفرال GPIOC که اعضای استراکچر فرضی از نوع GPIO_TypeDef هستند کافیه بعد از پوینتر به استراکچر عملگر پیکان بذاریم ، اینطوری برای هفت ها رجیستر پریفرال GPIOC فقط به یک پوینتر نیاز داریم.
روش دوم برنامه نویسی با رجیستری برای STM32 به پایان رسید ، این روش رو برای رجیستر RCC_APB2ENR از پریفرال RCC اعمال نکردم ، اگر برای این رجیستر هم بخواهیم از روش دوم استفاده کنیم باید قالب رجیسترهای پریفرال RCC یک نوع متغیر استراکچر بسازیم.
لایه ی نرم افزاری CMSIS همین روش رو برای دسترسی به رجیسترهای پریفرال های ارائه میده و توابع HAL و سایر کتابخونه های مشابه هم از همین روش برای دسترسی به رجیسترهای پریفرال ها استفاده میکنند.
روش سوم : دسترسی به بیت رجیسترها با بیت فیلد و یونیون برای میکروکنترلرهای STM32
تا حالا دو روش برنامه نویسی رجیستری برای STM32 ها رو بررسی کردیم، در روش اول برای هر رجیستر یک پوینتر ساختیم و در روش دوم به رجیسترهای در قالب اعضای استراکچر دسترسی پیدا کردیم. چیزی در این دو روش یکسان بود اینه که به رجیسترها بصورت 32 بیتی دسترسی پیدا میکنیم و باید از عملگرهای بیتی برای تغییر بیت رجیسترها استفاده کنیم. در روش سوم دیگه به عملگرهای بیتی نیازی نیست ، مستقیم به بیت ها دسترسی پیدا میکنیم و اعداد مورد نظر رو در اونها مینویسیم. قبل از اینکه به روش سوم بپردازیم باید راجع به بیت فیلد صحبت کنیم.
بیت فیلد
بیت فیلد ها اعضای استراکچر هستند که تعداد بیت هاشون رو خودمون مشخص میکنیم، برای تعریف عضو بیت فیلد در استراکچر ابتدا نوعش رو مینویسیم ، نوع بیت فیلد فقط میتونه اینتیجر باشه و علامت ( signed or un-signed ) و alignment رو برای بیت فیلد مشخص میکنه . در مورد نوع بیت فیلد خیلی نکته وجود داره ولی برای کاری که الان انجام میدیم نوع بیت فیلد همیشه uint32_t هست. بعد از نوع بیت فیلد با یک فاصله اسم بیت فیلد رو مینویسیم ، بعدش دو نقطه میذاریم و تعداد بیت های بیت فیلد رو مشخض میکنیم.
در مثال زیر عضو x و y به ترتیب 5 و 13 بیت دارند. در مثال زیر ابتدا نوع متغیر استراکچر bit_type تعریف شده که شامل دو عضو بیت فیلد هست سپس استراکچر mybit با نوع bit_type تعریف شده و اعضاش هم مقدار دهی اولیه شده اند ، عضو بیت مقدارش 3 شده و عضو y مقدارش 8 شده. در تابع main هم نشون دادم دسترسی به اعضایی که بیت فیلد هستند مشابه دسترسی به اعضای عادی استراکچر هست.
اگر استراکچر mybit دو عضو معمولی از نوع uint32_t داشت 8 بایت در حافظه رو اشغال میکرد ولی الان فقط چهار بایت یا 32 بیت رو اشغال میکنه که 14 بیتش هم استفاده نمیشه. در استراکچر mybit عضو x اولین عضو هست و 5 بیت داره ، 5 بیت کم ارزش به x اختصاص داده میشه یعنی بیت های 0 تا 4 و با توجه به اینکه عضو x پنج بیت داره فقط اعداد 0 تا 31 رو میتونیم در این عضو بریزیم. بعد از عضو x عضو y هست با 8 بیت و بیتهای 5 تا 17 به عضو y اختصاص داده میشه ، کم ارزش ترین بیت عضو y بیت 5 هست و میبینید که عدد 8 چطور در y ذخیره شده. بیت های 18 تا 31 هم خالی میمونند.
آموزش STM32 طراحی استراکچر بیت فیلد دار قالب بیت های رجیستر ODR
رجیستر ODR که برای تعیین وضعیت خروجی پین های ازش استفاده میشه رو در نظر بگیرید. قراره یک نوع استراکچر بیت فیلد دار قالب رجیستر ODR طراحی کنیم.
از کم ارزش ترین بیت رجیستر ODR شروع میکنیم و اعضای بیت فیلد رو مینویسیم ، اولین بیت ODR به نام ODR0 یک بیت داره ، اولین عضو استراکچر هم یک بیت فیلد هست به نام ODR0 که یک بیت داره. اعضای ODR1 تا ODR15 هم همشون یک بیت دارند ، از بیت 16 تا بیت 31 بیت های reserved هستند که استفاده نمیشن ، برای بیت های reserved هم یک عضو بیت فیلد مینویسیم با 16 بیت ولی اسمی براش نمینویسیم ، به اعضای بدون اسم میگن padding که برای پر کردن جاهای خالی استفاده میشن.
الان یک نوع متغیر استراکچر به نام ODR_T تعریف کردم که اندازش 32 بیت هست ، مشابه رجیستر ODR و اعضای بیت فیلد این نوع استراکچر هم مطابق بیت های رجیستر ODR طراحی شدن.
آموزش STM32 ، دسترسی به بیت های رجیستر بدون نیاز به ماسک بیت و عملگرهای بیتی
یک پروژه جدید بسازید و تمام محتویات فایل main.c پروژه قبلی رو درش کپی کنید. تعریف نوع متغیر ODR_T رو هم قبل از تعریف نوع متغیر GPIO_TypeDef اضافه کنید. قراره روش سوم برنامه نویسی رجیستر ها رو فقط برای رجیستر ODR اعمال کنیم.
در نوع GPIO_TypeDef عضو ODR از نوع uint32_t هست ، نوع عضو ODR رو از uint32_t به ODR_T تغییر میدیم. دقت کنید که ODR_T و uint32_t هر دو 32 بیتی هستند و اندازشون 4 بایته و اعضای استراکچر ODR_T رو هم مطابق بیت های رجیستر ODR طراحی کردیم.
قبلا عضو ODR از نوع uint32_t بود، الان خود ODR یک استراکچر از نوع ODR_T هست ، بعد از ODR عملگر دات میذاریم تا به اعضاش دسترسی پیدا کنیم ، اعضای ODR بیت های رجیستر ODR هستند. به عضو ODR13 دسترسی پیدا میکنیم و برای یک کردن این بیت کافیه عدد یک رو در اون بنویسیم و برای صفر کردن بیت ODR13 کافیه عدد صفر رو در اون بنویسیم و دیگه به عملگرهای بیتی و ماسک بیت نیازی نیست.
آموزش STM32 دسترسی به بیت های رجیستر و دسترسی 32 بیتی همزمان با یونیون
با تغییراتی که ایجاد کردیم عضو ODR یک استراکچر شده و دیگه نمیتونیم مثل قبل به رجیستر ODR بصورت 32 بیتی دسترسی پیدا کنیم. هدف اینه که بتونیم به ODR هم بصورت استراکچر و هم بصورت 32 بیتی دسترسی پیدا کنیم برای این منظور باید یک نوع یونیون طراحی کنیم.
یونیون مشابه استراکچر یک نوع متغیر هست که اعضاش انواع دیگه ی متغیر هستند ، فرق یونیون با استراکچر اینه که تمام اعضای یونیون یک آدرس دارند پس وقتی میخواهیم به یک آدرس به دو شکل دسترسی پیدا کنیم از یونیون استفاده میکنیم. یک نوع یونیون به نام ODR_UNION_T تعریف کردم که دو عضو داره. عضو reg از نوع uint32_t و عضو bit از نوع ODR_T.
حالا نوع عضو ODR در نوع استراکچر GPIO_TypeDef رو به ODR_UNION_T تغییر میدیم.
عضو ODR یک یونیون هست، برای دسترسی به اعضای یونیون بعد از اسمش دات میذاریم. این یونیون دو عضو به نام های bit و reg داره. اگر بخواهیم به رجیستر ODR بصورت 32 بیتی دسترسی پیدا کنیم از عضو reg که نوعش uint32_t هست ، استفاده میکنیم. اگر بخواهیم به بیت های رجیستر ODR دسترسی پیدا کنیم از عضو بیت استفاده میکنیم که نوعش ODR_T هست که یک نوع استراکچر با اندازه ی 4 بایته که با توجه به بیت های رجیستر ODR طراحی کردیم. عضو بیت خودش یک استراکچره و برای دسترسی به اعضای این استراکچر که بیت های رجیستر ODR هستند بعدش دات میذاریم و میتونیم به بیتها دسترسی پیدا کنیم.