এই পর্বে যা শিখবেন
- একটি মাইক্রোকন্ট্রোলারের ভেতরে কী কী থাকে, আর সাধারণ কম্পিউটার থেকে এটি কোথায় আলাদা
- Memory-mapped রেজিস্টারে বিট লিখে হার্ডওয়্যার চালানো — নিরাপদে, অন্য বিট না ছুঁয়ে
- Super-loop, polling আর interrupt — কোনটি কখন, আর ISR লেখার নিয়ম
- বাটন debounce, fixed-point হিসাব, watchdog — ছোট চিপের বাস্তব কৌশল
- Cross-compiler থেকে flash পর্যন্ত embedded টুলচেইন, আর AI-এর লেখা embedded কোড যাচাইয়ের চেকলিস্ট
১ · আপনার চারপাশে লুকানো কম্পিউটার
সকালে উঠে রাইস কুকারে চাল দিলেন। কুকার নিজেই বুঝল কখন পানি শুকিয়েছে, তারপর “cook” থেকে “warm” মোডে চলে গেল। রাস্তায় বেরিয়ে দেখলেন ট্রাফিক সিগন্যাল সময় মেপে রং বদলাচ্ছে। দোকানে bKash বা কার্ডে বিল দিলেন একটি POS মেশিনে। মেট্রো রেলের গেটে কার্ড ছোঁয়ালেন। প্রতিটি জায়গায় একটি করে ছোট কম্পিউটার কাজ করছে, অথচ কোথাও কোনো কীবোর্ড, মাউস বা Windows চোখে পড়েনি।
এই লুকানো কম্পিউটারগুলোকে বলে embedded system — বড় যন্ত্রের ভেতরে বসানো, একটিমাত্র কাজের জন্য তৈরি কম্পিউটার। আর এদের মস্তিষ্ক সাধারণত একটি মাইক্রোকন্ট্রোলার: একটি চিপ, যার ভেতরেই CPU, মেমরি আর বাইরের জগতের সাথে যোগাযোগের অংশ একসাথে। এদের ভেতরের প্রোগ্রামকে বলে firmware, আর পৃথিবীর বেশিরভাগ firmware আজও লেখা হয় C-তে।
🏠 ঘরে
ওয়াশিং মেশিন, রাইস কুকার, মাইক্রোওয়েভ, ইনভার্টার এসি, টিভির রিমোট, বিদ্যুতের প্রি-পেইড মিটার।
ছোট, সস্তা, কম বিদ্যুৎ🏦 ব্যবসায়
ATM, POS মেশিন, গার্মেন্টসের সেলাই-মেশিনের কন্ট্রোলার, ফিঙ্গারপ্রিন্ট হাজিরা যন্ত্র।
নির্ভরযোগ্যতা জরুরি🚗 যানবাহনে
একটি আধুনিক গাড়িতে কয়েক ডজন, দামি গাড়িতে শতাধিক ECU — ইঞ্জিন, ব্রেক, এয়ারব্যাগ, জানালা আলাদা আলাদা চিপে।
নিরাপত্তা-সংবেদনশীল❤️ জীবন রক্ষায়
পেসমেকার, ইনসুলিন পাম্প, ভেন্টিলেটর, ড্রোনের ফ্লাইট কন্ট্রোলার, কৃষিজমির IoT সেন্সর।
ভুলের দাম মানুষের জীবনযাঁরা শুরু করতে চান, তাঁদের জন্য কয়েকটি জনপ্রিয় বোর্ড ও চিপ। ঢাকার এলিফ্যান্ট রোড বা অনলাইন দোকানে এগুলোর কিট এখন সহজেই পাওয়া যায়, আর বিশ্ববিদ্যালয়ের প্রজেক্ট থেকে শুরু করে দেশি স্টার্টআপের IoT যন্ত্র পর্যন্ত — সবখানে এগুলোর ব্যবহার বাড়ছে। সংখ্যাগুলো খেয়াল করুন:
| বোর্ড (চিপ) | CPU ও গতি | Flash | RAM | বিশেষত্ব |
|---|---|---|---|---|
| Arduino Uno (ATmega328P) | 8-bit AVR, 16 MHz | 32 KB | 2 KB | শেখার জন্য সবচেয়ে জনপ্রিয় |
| ESP32 (সাধারণ মডেল) | 32-bit, অনেক মডেলে দুই কোর, 240 MHz পর্যন্ত | আলাদা চিপে, সাধারণত 4 MB | 520 KB | Wi-Fi ও Bluetooth ভেতরেই |
| STM32F103 (“Blue Pill” বোর্ড) | 32-bit ARM Cortex-M3, 72 MHz | 64 KB | 20 KB | শিল্প-কারখানায় বহুল ব্যবহৃত পরিবারের একটি |
| Raspberry Pi Pico (RP2040) | দুই কোর ARM Cortex-M0+, 133 MHz | আলাদা চিপে, 2 MB | 264 KB | ২০২১ সালে এসেছে, দামে সস্তা |
একটি সাধারণ ল্যাপটপে ৮ GB RAM থাকে। Arduino Uno-র চিপে আছে ২ KB — প্রায় ৪০ লাখ ভাগের এক ভাগ! এই পর্বে আপনার পরিচিত printf, malloc বা float যে এখানে বিলাসিতা, তার কারণ এই সংখ্যাটিই। আরেকটি বিভ্রান্তি দূর করে নিই: সাধারণ Raspberry Pi বোর্ড (যেমন Pi 4) আসলে Linux চালানো একটি পূর্ণ কম্পিউটার; আর Raspberry Pi Pico একটি মাইক্রোকন্ট্রোলার বোর্ড, যেখানে OS নেই। নাম কাছাকাছি, জগৎ আলাদা।
১৯৭১ সালে Intel বাজারে আনে 4004 — প্রথম বাণিজ্যিক একক-চিপ মাইক্রোপ্রসেসর, যা আসলে জাপানের Busicom কোম্পানির একটি ক্যালকুলেটরের জন্য বানানো হয়েছিল। অর্থাৎ ইতিহাসের প্রথম “চিপে CPU” নিজেই ছিল একটি embedded যন্ত্রের অংশ! ১৯৮০ সালে Intel আনে 8051 মাইক্রোকন্ট্রোলার, যা চার দশক পরেও নানা রূপে তৈরি হচ্ছে এবং বাংলাদেশসহ বহু দেশের বিশ্ববিদ্যালয়ে microprocessor বিষয়ে পড়ানো হয়।
২ · মাইক্রোকন্ট্রোলারের ভেতরে কী আছে
আপনার ল্যাপটপে CPU একটি চিপ, RAM আলাদা কার্ড, SSD আলাদা, Wi-Fi আলাদা। মাইক্রোকন্ট্রোলারে সবকিছু একটি ছোট চিপের ভেতরে গুটিয়ে রাখা। একটি টিনের ছোট দোকানের কথা ভাবুন — দোকানদারই ক্যাশিয়ার, তিনিই মাল তোলেন, তিনিই হিসাব লেখেন। জায়গা কম, কিন্তু সব হাতের কাছে।
একটি মাইক্রোকন্ট্রোলারের ভেতর
One chip: CPU core, flash, SRAM and peripherals on a shared bus
- CPU core
Cortex-M / AVRনির্দেশ চালায় - Flash + SRAM
কোড / ভ্যারিয়েবলKB-র হিসাবে - Peripherals
GPIO, Timer, ADC…বাইরের জগতের দরজা
- CPU core: নির্দেশ চালায়। গতি মাপা হয় clock speed দিয়ে — Arduino Uno-তে ১৬ MHz, অর্থাৎ সেকেন্ডে ১ কোটি ৬০ লাখ টিক। ল্যাপটপের কয়েক GHz-এর তুলনায় ধীর, কিন্তু একটি বাতি বা মোটর চালাতে যথেষ্টর চেয়ে বেশি।
- Flash: প্রোগ্রামের কোড আর স্থির ডেটা। বিদ্যুৎ গেলেও মোছে না — তাই লোডশেডিংয়ের পর কুকার আবার নিজের প্রোগ্রাম নিয়েই জেগে ওঠে।
- SRAM: ভ্যারিয়েবল আর stack. দ্রুত, কিন্তু খুবই কম, আর বিদ্যুৎ গেলে সব মুছে যায়।
- Peripheral: CPU-র পাশে বসানো ছোট ছোট বিশেষজ্ঞ যন্ত্র। নিচের টেবিলে প্রধানগুলো।
| Peripheral | কী করে | বাস্তব উদাহরণ |
|---|---|---|
| GPIO | পিনে 0 বা 1 লেখা/পড়া | LED জ্বালানো, বাটন পড়া, রিলে চালু করা |
| Timer | নিজে নিজে সময় গোনা | ট্রাফিক লাইটের ৩০ সেকেন্ড, প্রতি ১ ms-এ একটি টিক |
| ADC | ভোল্টেজ মেপে সংখ্যা বানানো | তাপমাত্রা, ব্যাটারির চার্জ, মাটির আর্দ্রতা |
| PWM | দ্রুত চালু-বন্ধ করে গড় শক্তি নিয়ন্ত্রণ | ফ্যানের গতি, LED-এর উজ্জ্বলতা |
| UART | দুই তারে লেখা পাঠানো | কম্পিউটারে লগ পাঠানো, GSM বা GPS মডিউল |
| I2C / SPI | অন্য চিপের সাথে কথা | সেন্সর, OLED ডিসপ্লে, SD কার্ড |
OS নেই — তাহলে কে সামলায়?
আপনার ল্যাপটপে প্রোগ্রাম চালু হলে OS তাকে মেমরি দেয়, ফাইল খোলে, স্ক্রিনে লেখা দেখায়, আর ভুল ঠিকানায় হাত দিলে Segmentation fault দিয়ে থামিয়ে দেয়। এই শেষ কাজটি করে CPU-র MMU নামের অংশ। ছোট মাইক্রোকন্ট্রোলারে MMU নেই, OS-ও সাধারণত নেই। একে বলে bare metal প্রোগ্রামিং — আপনার কোড আর খালি হার্ডওয়্যার, মাঝখানে কেউ নেই। ভুল ঠিকানায় লিখলে কেউ থামাবে না; হয়তো চুপচাপ অন্য একটি পিন বদলে যাবে।
কাজ যখন বেশি জটিল হয় — একসাথে Wi-Fi সামলানো, সেন্সর পড়া, ডিসপ্লে আপডেট — তখন ব্যবহার হয় একটি RTOS. সবচেয়ে পরিচিত উদাহরণ FreeRTOS, যা ESP32-এর অফিশিয়াল সফটওয়্যার কাঠামোর ভেতরেই চলে। RTOS কয়েকটি task-কে অগ্রাধিকার অনুযায়ী ভাগ করে চালায়, অনেকটা পর্ব ৪৯-এর thread-এর মতো, কিন্তু অনেক ছোট পরিসরে।
Embedded জগতের সবচেয়ে বড় পার্থক্য সময়ে। একটি real-time system-এ শুধু সঠিক উত্তর যথেষ্ট নয়, নির্দিষ্ট সময়ের মধ্যে উত্তর দিতে হয়। গাড়ির এয়ারব্যাগ সংঘর্ষের পর কয়েক দশ মিলিসেকেন্ডের মধ্যে না ফুললে, পরে ফুলে লাভ নেই। দেরিতে আসা সঠিক উত্তরও এখানে ভুল উত্তর।
৩ · রেজিস্টার — যে ঠিকানায় লিখলে বাতি জ্বলে
পর্ব ৪৫-এ memory-mapped I/O-র কথা প্রথম শুনেছেন। এবার পুরো ছবিটা দেখি। মাইক্রোকন্ট্রোলারের প্রতিটি peripheral-এর কিছু নিয়ন্ত্রণ-ঘর আছে, যাকে বলে register. প্রতিটি রেজিস্টার একটি নির্দিষ্ট মেমরি-ঠিকানায় বসানো, আর তার প্রতিটি বিট হার্ডওয়্যারের একটি তারের সাথে যুক্ত। একটি GPIO পোর্টের output data register (ODR)-এর বিট ৫-এ 1 লিখলে পিন ৫-এ ভোল্টেজ আসে, আর সেই পিনে জোড়া LED জ্বলে ওঠে। আপনার C কোড সত্যিই বিদ্যুৎ নিয়ন্ত্রণ করছে।
পুরোনো বাড়ির মিটার রুমের সুইচবোর্ডের কথা ভাবুন। একসারি ৩২টি সুইচ, প্রতিটির নিচে লেখা: “বসার ঘরের বাতি”, “রান্নাঘরের ফ্যান”, “পানির পাম্প”… সুইচবোর্ডটির একটি ঠিকানা আছে (কোন দেয়ালে), আর প্রতিটি সুইচের একটি নম্বর। রেজিস্টার ঠিক এমন একটি সুইচবোর্ড। আর পর্ব ০৯-এ শেখা bit mask হলো এমন একটি আঙুল, যা শুধু নির্দিষ্ট সুইচটিই টেপে — পাশের পাম্পের সুইচে ভুলেও হাত লাগে না।
C-তে একটি নির্দিষ্ট ঠিকানাকে রেজিস্টার হিসেবে ব্যবহার করার প্রচলিত লেখা এরকম। নিচের ঠিকানাগুলো উদাহরণ — STM32F4 সিরিজের reference manual-এ GPIOA পোর্টের ঠিকানা; অন্য চিপে এগুলো সম্পূর্ণ আলাদা হবে:
/* উদাহরণ: STM32F4-এর GPIOA (ঠিকানা চিপের reference manual থেকে) */
#define GPIOA_ODR (*(volatile uint32_t *)0x40020014u)
#define GPIOA_BSRR (*(volatile uint32_t *)0x40020018u)
GPIOA_ODR |= (1u << 5); /* পিন PA5 চালু — LED জ্বলো */
GPIOA_ODR &= ~(1u << 5); /* পিন PA5 বন্ধ — LED নেভো */
লাইনটি ভেতর থেকে পড়ুন। 0x40020014u একটি সাধারণ সংখ্যা। (volatile uint32_t *) compiler-কে জানায়: “এটি একটি ৩২ বিটের রেজিস্টারের ঠিকানা, আর এর মান যেকোনো সময় বাইরে থেকে বদলাতে পারে।” সামনের * সেই ঠিকানার ভেতরের মানটি দেয়। ফলে GPIOA_ODR লিখলে সেটি একটি সাধারণ ভ্যারিয়েবলের মতো ব্যবহার করা যায়। টাইপ হিসেবে int নয়, stdint.h-এর uint32_t — কারণ রেজিস্টার ঠিক ৩২ বিটের, আর embedded কোডে “কত বিট” কখনো অনুমানের ওপর ছাড়া হয় না।
কেন volatile? পর্ব ৪৫-এর polling লুপের কথা মনে করুন: volatile ছাড়া compiler একবার পড়ে ধরে নিত মান আর বদলাবে না, অথবা পরপর দুটি লেখার প্রথমটি “অকারণ” ভেবে মুছে দিত। রেজিস্টারে প্রতিটি পড়া ও লেখা একটি সত্যিকার ঘটনা, তাই একটিও বাদ দেওয়া চলবে না।
এবার সবচেয়ে গুরুত্বপূর্ণ প্রশ্ন: GPIOA_ODR |= (1u << 5) লাইনটি আসলে কয়টি ধাপে চলে? নিচের ডায়াগ্রামে দেখুন। ধরা যাক পিন ০ (একটি বাজার) আর পিন ৩ (একটি ফ্যানের রিলে) আগে থেকেই চালু:
| ODR (বাইনারি) | |
| ODR (hex) | |
| CPU-র ভেতরের কপি | |
| LED (পিন ৫) |
bit 7 6 5 4 3 2 1 0 ODR 0 0 0 0 1 0 0 1 = 0x09
bit 7 6 5 4 3 2 1 0 mask 0 0 1 0 0 0 0 0 = 0x20 = 32
ldr r1, [r0] ; r1 = ODR = 0x09
r1 0 0 0 0 1 0 0 1 mask 0 0 1 0 0 0 0 0 OR --------------- r1 0 0 1 0 1 0 0 1 = 0x29
str r1, [r0] ; ODR = 0x29 → পিন ৫-এ ভোল্টেজ
GPIOA_BSRR = (1u << 5); /* শুধু পিন ৫ চালু */ GPIOA_BSRR = (1u << (5 + 16)); /* শুধু পিন ৫ বন্ধ */ /* 0 লেখা বিটগুলো কিছুই বদলায় না — পড়ার দরকারই নেই */
- শুরুর অবস্থা। ODR-এ
0x09: বিট ০ আর বিট ৩ চালু, অর্থাৎ বাজার আর ফ্যান চলছে। আমাদের কাজ শুধু বিট ৫ চালু করা, বাকি ৩১টি বিট যেমন আছে তেমন রেখে। - Mask বানানো।
1u << 5=0x20, শুধু বিট ৫-এ 1. পর্ব ০৯-এর নিয়ম: OR-এ mask-এর 0 যেখানে, সেখানে কিছুই বদলায় না। শেষেরuজরুরি — signed1 << 31লিখলে undefined behavior, আর রেজিস্টারে বিট ৩১ ব্যবহার খুবই সাধারণ। - পড়া (Read). CPU সরাসরি রেজিস্টারের ভেতরে OR করতে পারে না। প্রথমে পুরো মানটি নিজের ভেতরের একটি রেজিস্টারে (r1) কপি করে আনে।
- বদলানো (Modify). CPU-র ভেতরে OR হলো:
0x09 | 0x20 = 0x29. খেয়াল করুন, আসল ODR এখনো0x09— LED এখনো নেভা। - লেখা (Write). নতুন মান ফেরত লেখা হলো। এই মুহূর্তে পিন ৫-এ ভোল্টেজ এলো, LED জ্বলল। বিট ০ আর ৩ আগের মতোই চালু। এই তিন ধাপের নাম read-modify-write.
- লুকানো বিপদ, আর সমাধান। ধাপ ৩ আর ৫-এর মাঝখানে যদি একটি interrupt এসে ODR-এর বিট ০ বন্ধ করে দেয়, ধাপ ৫ পুরোনো কপি লিখে সেটি আবার চালু করে দেবে — interrupt-এর কাজ হারিয়ে গেল! পর্ব ৪৯-এর lost update-এর হুবহু হার্ডওয়্যার রূপ। তাই অনেক চিপে থাকে atomic set/reset register, যেমন STM32-র BSRR: শুধু যে বিটে 1 লেখা হয়, সেই পিনই বদলায়। পড়ার ধাপই নেই, তাই মাঝখানে ঢোকার সুযোগও নেই।
বাস্তবে LED জ্বালাতে আরও দুটি প্রস্তুতি লাগে, যা নতুনরা (এবং AI) প্রায়ই ভোলে: পোর্টের clock চালু করা (বিদ্যুৎ বাঁচাতে চিপ শুরুতে বেশিরভাগ peripheral বন্ধ রাখে), আর পিনটিকে output মোডে বসানো। এই দুটি না করলে ODR-এ যত খুশি লিখুন, LED জ্বলবে না। Arduino Uno-তেও একই কথা: ১৩ নম্বর পিনের LED আসলে ATmega328P-এর PORTB রেজিস্টারের বিট ৫, আর pinMode(13, OUTPUT) ভেতরে DDRB রেজিস্টারের বিট ৫ চালু করে।
একটি করে ঠিকানা নয় — পুরো রেজিস্টার-ব্লক একটি struct
একটি GPIO পোর্টে ডজনখানেক রেজিস্টার পাশাপাশি থাকে, প্রতিটি ৪ বাইট দূরে। প্রত্যেকটির জন্য আলাদা #define না লিখে পেশাদার কোডে পুরো ব্লকটিকে একটি struct হিসেবে ঘোষণা করা হয় (পর্ব ২২), তারপর ব্লকের শুরুর ঠিকানাকে সেই struct-এর পয়েন্টার বানানো হয়:
typedef struct { /* উদাহরণ: STM32F4 GPIO-র রেজিস্টার ক্রম */
volatile uint32_t MODER; /* +0x00 পিনের মোড */
volatile uint32_t OTYPER; /* +0x04 */
volatile uint32_t OSPEEDR; /* +0x08 */
volatile uint32_t PUPDR; /* +0x0C */
volatile uint32_t IDR; /* +0x10 ইনপুট পড়া */
volatile uint32_t ODR; /* +0x14 আউটপুট লেখা */
volatile uint32_t BSRR; /* +0x18 atomic set/reset */
} GPIO_TypeDef;
#define GPIOA ((GPIO_TypeDef *)0x40020000u)
GPIOA->BSRR = (1u << 5); /* LED জ্বলো */
এখানে struct-এর প্রতিটি সদস্য ঠিক ৪ বাইট, তাই কোনো padding ঢোকে না, আর সদস্যের ক্রম হুবহু হার্ডওয়্যারের ক্রম মেনে চলে। এই ধাঁচটিকে সব কোম্পানির জন্য একই রকম করেছে ARM-এর CMSIS — STM32, NXP বা Nordic, যার Cortex-M চিপই হোক, header ফাইলে প্রায় একই চেহারার struct পাবেন। ARM Cortex-M3/M4-এর কিছু চিপে আরেকটি কৌশলও আছে, bit-banding: একটি বিশেষ ঠিকানা-অঞ্চলে প্রতিটি বিটের নিজস্ব ঠিকানা, সেখানে লিখলে একটি বিট একবারেই বদলায়।
৪ · Super-loop — যে লুপ কখনো শেষ হয় না
আপনার ল্যাপটপের প্রোগ্রাম কাজ শেষে return 0 দিয়ে OS-এর কাছে ফেরত যায়। কিন্তু মাইক্রোকন্ট্রোলারে ফেরার কোনো জায়গা নেই — main শেষ হয়ে গেলে CPU কী করবে, কেউ জানে না। তাই embedded প্রোগ্রামের সবচেয়ে প্রচলিত কাঠামো হলো super-loop:
int main(void) {
hardware_init(); /* clock, পিনের মোড, টাইমার — একবারই */
while (1) { /* চিরকাল */
read_inputs(); /* বাটন, সেন্সর */
update_logic(); /* সিদ্ধান্ত */
write_outputs(); /* LED, মোটর, ডিসপ্লে */
}
}
চায়ের দোকানের মামার কথা ভাবুন: চুলা দেখেন, কেটলিতে পানি দেন, কাপ ধোন, টাকা নেন — আবার চুলা দেখেন। দোকান বন্ধ না হওয়া পর্যন্ত এই চক্র থামে না। Super-loop ঠিক তাই, আর বিদ্যুৎ বন্ধ না হওয়া পর্যন্ত চলে।
Embedded জগতের “Hello, World” হলো LED জ্বালানো-নেভানো (blink). সবচেয়ে সরল রূপটি এমন:
while (1) {
GPIOA->ODR ^= (1u << 5); /* toggle */
delay_ms(500); /* অর্ধেক সেকেন্ড অপেক্ষা */
}
এখানে delay_ms সাধারণত একটি busy-wait: একটি ফাঁকা লুপ, যা শুধু গুনে গুনে সময় নষ্ট করে। সহজ, কিন্তু দুটি সমস্যা। এক, অপেক্ষার পুরো ৫০০ ms CPU আর কিছুই করতে পারে না — বাটন চাপলেও শুনবে না। দুই, গোনা লুপের সময় নির্ভর করে clock-এর গতি আর compiler-এর optimization-এর ওপর (volatile ছাড়া optimizer পুরো ফাঁকা লুপটাই মুছে দিতে পারে)। পেশাদার সমাধান হলো হার্ডওয়্যার টাইমার: টাইমার নিজে নিজে গোনে, প্রতি ১ ms-এ একটি “টিক” দেয়, আর মূল লুপ শুধু দেখে, “৫০০ টিক পেরিয়েছে কি?” অপেক্ষার ফাঁকে CPU বাকি কাজ সেরে নেয়।
আমাদের অনলাইন compiler একটি সাধারণ Linux সার্ভারে চলে, সেখানে কোনো LED নেই। তাই নিচের কোডে হার্ডওয়্যারের অনুকরণ করা হয়েছে: RAM-এর একটি volatile struct আসল রেজিস্টার-ব্লকের জায়গা নিয়েছে। লুপটি ৬টি টিক চলে (আসল চিপে চলত চিরকাল), প্রতিবার LED উল্টে দেয়, আর রেজিস্টারের নিচের ৮টি বিট ছাপে:
#include <stdio.h>
#include <stdint.h>
typedef struct { volatile uint32_t IDR, ODR; } GPIO_Type; /* register block */
static GPIO_Type fake_port; /* RAM stands in for the hardware */
#define GPIOA (&fake_port) /* real chip: a fixed address */
#define LED (1u << 5) /* LED on pin 5 */
static void show(int t, uint32_t v) {
printf("tick %d ODR=0x%02X ", t, (unsigned)v);
for (int b = 7; b >= 0; b--) putchar(((v >> b) & 1u) ? '1' : '0');
printf(" LED %s\n", (v & LED) ? "ON" : "off");
}
int main(void) {
GPIOA->ODR = 0x01; /* pin 0 is already on */
for (int tick = 1; tick <= 6; tick++) { /* super-loop, 6 rounds */
GPIOA->ODR ^= LED; /* toggle = read-modify-write */
show(tick, GPIOA->ODR);
}
GPIOA->ODR = (GPIOA->ODR | (1u << 3)) & ~0x01u; /* set 3, clear 0 */
show(7, GPIOA->ODR);
return 0;
}শুরুতে পিন ০ চালু (0x01). প্রতি টিকে ^= LED শুধু বিট ৫ উল্টায়: টিক ১-এ 0x21 = 00100001, LED ON; টিক ২-এ আবার 0x01, LED off — এভাবে ৬ টিকে তিনবার জ্বলল, তিনবার নিভল। পুরো সময় বিট ০ একবারও নড়েনি, কারণ mask-এ শুধু বিট ৫ ছিল। শেষ লাইনে একটি লেখাতেই বিট ৩ চালু আর বিট ০ বন্ধ হলো, ফল 0x08 = 00001000. GPIOA macro-টি খেয়াল করুন: আসল চিপে এর জায়গায় থাকত ((GPIO_Type *)0x40020000u)-এর মতো একটি নির্দিষ্ট ঠিকানা; বাকি কোড একটি অক্ষরও বদলাত না। এভাবেই embedded দল ল্যাপটপে বসে হার্ডওয়্যার ছাড়াই লজিক পরীক্ষা করে।
৫ · Polling না interrupt — কে আগে খবর পায়
ধরুন আপনার super-loop-এ একটি ডিসপ্লে আপডেট আছে, যাতে প্রতিবার ৪০ ms লাগে। এর মধ্যে কেউ বাটন চাপল। প্রোগ্রাম কীভাবে জানবে? দুটি উপায় আছে।
🔁 Polling
লুপের প্রতি ঘুরে বাটনের পিন পড়ে দেখা: “চাপা হয়েছে?” সহজ ও অনুমানযোগ্য, কিন্তু লুপ যতক্ষণ অন্য কাজে ব্যস্ত, ততক্ষণ খবর জানা যায় না।
বারবার দরজা খুলে দেখা🔔 Interrupt
পিনে পরিবর্তন হলেই হার্ডওয়্যার CPU-কে ডাকে। CPU চলতি কাজ মাঝপথে থামিয়ে একটি ছোট ফাংশন চালায়, তারপর ঠিক যেখানে ছিল সেখানে ফেরে।
কলিং বেলএকটি দ্রুত টোকা — বাটন মাত্র ১৮ ms চাপা থাকল। দুই পদ্ধতিতে কী ঘটে, পাশাপাশি দেখুন:
| সময় | |
| বাটনের পিন | |
| Polling প্রোগ্রাম | |
| Interrupt প্রোগ্রাম | |
| button_flag |
- শুরু। দুটি প্রোগ্রামই লুপের শুরুতে ৪০ ms-এর ডিসপ্লে আপডেট শুরু করল। Polling প্রোগ্রাম বাটন দেখবে শুধু এই কাজ শেষ হলে।
- ১২ ms-এ বাটন চাপা হলো। Polling প্রোগ্রাম কিছুই টের পেল না — সে ব্যস্ত। Interrupt প্রোগ্রামে পিনের পরিবর্তন সরাসরি interrupt controller-এ গেল; CPU চলতি নির্দেশ শেষ করে নিজের রেজিস্টারগুলো stack-এ রেখে vector table থেকে ঠিকানা নিয়ে ISR-এ লাফ দিল। ARM Cortex-M3/M4-এ এতে লাগে প্রায় ১২টি clock cycle — ১৬ MHz-এ এক মাইক্রোসেকেন্ডেরও কম।
- ISR তার কাজ করল — এবং কিছুই না। ভালো ISR শুধু একটি
volatileflag-এ 1 লেখে, interrupt-এর হার্ডওয়্যার flag মুছে দেয়, তারপর বেরিয়ে যায়। CPU রেজিস্টার ফেরত এনে ডিসপ্লের কাজ ঠিক যেখানে থেমেছিল সেখান থেকে চালাল। ডিসপ্লে টেরও পেল না। - ৩০ ms-এ বাটন ছেড়ে দেওয়া হলো। চাপটি মাত্র ১৮ ms টিকল। Polling প্রোগ্রাম তখনো ডিসপ্লে আঁকছে। কিন্তু interrupt প্রোগ্রামের কাছে খবরটি flag-এ জমা আছে — হারানোর ভয় নেই।
- ৪০ ms — লুপের পরের ঘুর। Polling প্রোগ্রাম এখন পিন পড়ল: 0. তার কাছে বাটন কখনো চাপাই হয়নি! ব্যবহারকারী ভাববেন যন্ত্র নষ্ট। Interrupt প্রোগ্রামের মূল লুপ flag দেখল, flag 0 করল, তারপর ধীরে-সুস্থে চাপটি সামলাল — মেনু বদলানো, শব্দ করা, যা দরকার।
- শিক্ষা। Polling-এ সবচেয়ে খারাপ দেরি হলো পুরো একটি লুপের সময়; লুপের চেয়ে ছোট ঘটনা হারিয়েও যেতে পারে। Interrupt-এ latency মাইক্রোসেকেন্ডে। তবে polling খারাপ নয়: লুপ দ্রুত হলে আর ঘটনা ধীর হলে (যেমন প্রতি সেকেন্ডে তাপমাত্রা পড়া) polling-ই সহজ ও নিরাপদ। বাস্তবের বেশিরভাগ firmware দুটিই মিলিয়ে ব্যবহার করে।
ISR লেখার চারটি নিয়ম
volatile uint8_t button_flag = 0; /* ISR আর main দুজনেই দেখে */
void EXTI0_IRQHandler(void) { /* উদাহরণ: STM32-র ISR-এর নাম */
EXTI->PR = 1u; /* interrupt-এর flag মুছে দেওয়া */
button_flag = 1; /* শুধু খবর রেখে বেরিয়ে যাওয়া */
}
int main(void) {
hardware_init();
for (;;) {
if (button_flag) { button_flag = 0; handle_button(); }
update_display();
}
}
- ছোট রাখুন। ISR চলার সময় মূল লুপ পুরো থেমে থাকে, আর একই বা কম অগ্রাধিকারের অন্য interrupt-ও অপেক্ষা করে। ভারী কাজ (ডিসপ্লে, হিসাব, লগ) মূল লুপে পাঠান।
- ভাগ করা ভ্যারিয়েবল
volatile. নইলে মূল লুপেরif (button_flag)compiler একবার পড়ে চিরকালের জন্য ধরে রাখতে পারে — পর্ব ৪৫-এর সেই আটকে যাওয়া লুপ। - ISR-এ printf, malloc বা দীর্ঘ delay নয়। এগুলো ধীর, অনেক সময় নিজেরাই interrupt-এর ওপর নির্ভর করে, আর reentrant নয় — পর্ব ৪৫-এর signal handler-এর নিয়মের মতোই।
- Hardware flag মুছে দিন। অনেক চিপে ISR-এর ভেতরে interrupt-এর “pending” বিট নিজে মুছতে হয়; না মুছলে বেরোনোর সাথে সাথে ISR আবার চলে, প্রোগ্রাম চিরকাল সেখানেই ঘুরতে থাকে।
Critical section — যখন main আর ISR একই ডেটা ছোঁয়
একটি টাইমার ISR প্রতি ১ ms-এ একটি ৩২ বিটের counter বাড়ায়: ticks++. মূল লুপ সেটি পড়ে। ৩২ বিটের ARM-এ একটি পড়া একবারেই হয়। কিন্তু ৮ বিটের AVR-এ ৩২ বিটের মান পড়তে লাগে চারটি আলাদা নির্দেশ! ধরুন counter 0x000000FF. মূল লুপ নিচের byte পড়ল (FF), ঠিক তখন interrupt এসে counter-কে 0x00000100 বানাল, তারপর লুপ বাকি byte-গুলো পড়ল (01). হাতে এলো 0x000001FF — এমন একটি মান, যা counter কখনো ছিলই না। একে বলে torn read, পর্ব ৪৯-এর race condition-এর হার্ডওয়্যার সংস্করণ।
সমাধান হলো ছোট একটি critical section: পড়ার মুহূর্তটুকু interrupt বন্ধ রাখা।
__disable_irq(); /* CMSIS (ARM); AVR-এ cli() */
uint32_t now = ticks; /* পুরো মান নিরাপদে কপি */
__enable_irq(); /* AVR-এ sei() */
এখানে mutex নেই, thread নেই — মূল লুপ আর ISR-এর মাঝে একমাত্র তালা হলো “interrupt বন্ধ”। তবে তালা যত লম্বা, interrupt তত দেরিতে সাড়া পায়; তাই critical section কয়েক নির্দেশের বেশি লম্বা করা চলবে না।
৬ · বাটনের কাঁপুনি — debounce আর state machine
প্রথমবার বাটন দিয়ে counter বানালে প্রায় সবাই একটি অদ্ভুত জিনিস দেখেন: একবার চাপলে counter ৩ বা ৫ বেড়ে যায়! কোড ঠিক, দোষ বাটনের। বাটনের ভেতরে দুটি ধাতব পাত। চাপ দিলে তারা ঠোকাঠুকি খেয়ে কয়েক মিলিসেকেন্ড কাঁপে — লেগে যায়, ছিটকে যায়, আবার লাগে। মানুষের চোখে এক চাপ, কিন্তু মাইক্রোসেকেন্ডে পিন পড়া চিপের কাছে সেটা অনেকগুলো চাপ। এই সমস্যার সমাধানকে বলে debouncing.
সফটওয়্যারে debounce-এর একটি পরিষ্কার উপায় হলো state machine. প্রোগ্রাম সবসময় চারটি অবস্থার একটিতে থাকে: UP (ছাড়া), MAYBE_DOWN (চাপা মনে হচ্ছে), DOWN (নিশ্চিত চাপা), MAYBE_UP (ছাড়া মনে হচ্ছে)। প্রতি ৫ ms-এ টাইমারের টিকে একবার পিন পড়া হয়, আর পরপর দুবার একই মান এলে তবেই পরিবর্তন মেনে নেওয়া হয়। নিচে একটি কাঁপা সংকেত 0, 1, 0, 1, 1, 1, 1:
| সময় | |
| পিনের কাঁচা মান | |
| অবস্থা (state) | |
| সরল গোনা (প্রতি 0→1) | |
| আসল চাপ (debounced) |
- ০ ms — বিশ্রাম। পিন 0, অবস্থা
UP. কেউ বাটন ছোঁয়নি। - ৫ ms — প্রথম ছোঁয়া। পিন 1. সরল কোড এখনই “চাপ ১” গুনে ফেলত। State machine শুধু সন্দেহ করে: অবস্থা
MAYBE_DOWN, এখনো কোনো ঘোষণা নয়। - ১০ ms — পাত ছিটকে গেল। পিন আবার 0. সন্দেহ ভুল প্রমাণিত, অবস্থা ফিরে গেল
UP-এ। কোনো ক্ষতি হয়নি। - ১৫ ms — আবার লাগল। পিন 1. সরল গোনা এখন ২ — অথচ মানুষ একবারই চেপেছেন! State machine আবার
MAYBE_DOWN-এ। - ২০ ms — নিশ্চিত। পরপর দুটি নমুনায় 1. এবার অবস্থা
DOWN, আর ঠিক এই রূপান্তরের মুহূর্তে একবার “PRESS” ঘোষণা হলো। - ২৫ ms — চাপা আছে। পিন 1, অবস্থা
DOWN-ই থাকল। নতুন কোনো চাপ গোনা হলো না; চাপ গোনা হয় শুধু অবস্থা বদলের সময়, অবস্থায় থাকার সময় নয়। - ৩০ ms — ফল। সরল গোনা বলছে ২, debounce বলছে ১ — আর মানুষের আঙুলও বলছে ১. বাস্তবে বাটনের কাঁপুনি ১–২০ ms পর্যন্ত চলতে পারে, তাই কতগুলো নমুনা মিললে মেনে নেবেন, তা বাটনের ধরন দেখে ঠিক করা হয় (প্রায়ই ৩–৫টি)।
এবার একই state machine C-তে, একটি নির্দিষ্ট নমুনা-তালিকার ওপর। আসল চিপে raw[i]-এর জায়গায় থাকত টাইমারের প্রতি টিকে পড়া GPIOA->IDR-এর একটি বিট:
#include <stdio.h>
enum state { UP, MAYBE_DOWN, DOWN, MAYBE_UP };
int main(void) {
const int raw[] = {0,1,0,1,1,1,1,1,0,1,0,0,0,0,1,1,1,0,0,0}; /* every 5 ms */
enum state s = UP;
int presses = 0, edges = 0;
for (int i = 0; i < 20; i++) {
const int r = raw[i];
if (i > 0 && r && !raw[i - 1]) edges++; /* naive: every 0->1 */
enum state prev = s;
switch (s) {
case UP: s = r ? MAYBE_DOWN : UP; break;
case MAYBE_DOWN: s = r ? DOWN : UP; break;
case DOWN: s = r ? DOWN : MAYBE_UP; break;
case MAYBE_UP: s = r ? DOWN : UP; break;
}
if (prev == MAYBE_DOWN && s == DOWN) printf("t=%3d ms PRESS %d\n", i * 5, ++presses);
}
printf("raw 0->1 edges: %d, real presses: %d\n", edges, presses);
return 0;
}২০টি নমুনা মানে ১০০ ms-এর ইতিহাস। কাঁচা সংকেতে 0 থেকে 1-এ লাফ চারবার (৫, ১৫, ৪৫ আর ৭০ ms-এ), তাই সরল কোড বলত চারটি চাপ। State machine ঘোষণা করে মাত্র দুটি: t= 20 ms PRESS 1 আর t= 75 ms PRESS 2. ৪০–৪৫ ms-এর 0, 1 কাঁপুনিটি খেয়াল করুন: বাটন তখন DOWN-এ ছিল, একটি 0 এসে MAYBE_UP বানাল, কিন্তু পরের 1 তাকে আবার DOWN-এ ফিরিয়ে দিল — ছাড়া হিসেবে গোনাই হলো না। পুরো যুক্তি চারটি case-এ, কোনো delay নেই, কোনো লুপে আটকে থাকা নেই। তাই একই super-loop-এ দশটি বাটনের দশটি state machine অনায়াসে চলতে পারে। ট্রাফিক লাইট, ওয়াশিং মেশিনের চক্র, ATM-এর পর্দা — embedded জগতের বেশিরভাগ লজিক এভাবেই state machine হিসেবে লেখা।
পরীক্ষা ও ইন্টারভিউতে — বিশ্ববিদ্যালয়ের microprocessor/embedded বিষয়ের পরীক্ষা, চাকরির লিখিত পরীক্ষা বা embedded কোম্পানির ইন্টারভিউতে এই ধরনের প্রশ্ন আসতে পারে:
- Microprocessor আর microcontroller-এর পার্থক্য কী? Microcontroller-এ CPU, মেমরি আর peripheral এক চিপে; microprocessor-এ মেমরি ও I/O আলাদা চিপে লাগে।
- একটি রেজিস্টারের বিট n চালু, বন্ধ আর উল্টানোর C লেখা দিন।
r |= 1u << n;r &= ~(1u << n);r ^= 1u << n; - Hardware register-এর পয়েন্টারে volatile কেন লাগে? মান প্রোগ্রামের বাইরে বদলায়; volatile ছাড়া compiler পড়া-লেখা বাদ দিতে বা জোড়া লাগাতে পারে।
- ISR-এর ভেতরে printf কেন ডাকবেন না? ধীর, reentrant নয়, আর পুরো সিস্টেমের interrupt latency বাড়িয়ে দেয়।
- Watchdog timer কী? একটি স্বাধীন টাইমার, যা নির্দিষ্ট সময়ে “kick” না পেলে চিপ reset করে দেয় — আটকে যাওয়া প্রোগ্রাম থেকে মুক্তির পথ।
৭ · ছোট চিপের কড়া নিয়ম — malloc নেই, printf নেই, float নেই
২ KB RAM-এ বেঁচে থাকতে হলে আপনার অভ্যাসগুলো বদলাতে হবে। নিচের প্রতিটি নিয়মের পেছনে একটি বাস্তব কারণ আছে।
| ল্যাপটপে যা করেন | ছোট চিপে যা করবেন | কেন |
|---|---|---|
malloc/free (পর্ব ২০) | সব স্থির আকারের: static uint8_t buf[64]; | Heap টুকরো টুকরো হয়ে একদিন জায়গা ফুরোয়; কখন ফুরোবে আগে বলা যায় না, আর বলার মতো OS-ও নেই |
printf দিয়ে debug | UART-এ ছোট লগ, বা একটি LED জ্বালিয়ে সংকেত | পূর্ণ printf কয়েক KB flash খায়, আর ধীর |
float, double | Fixed-point পূর্ণসংখ্যা | AVR বা Cortex-M0-তে FPU নেই, প্রতিটি float হিসাব সফটওয়্যারে শত শত নির্দেশ |
| বড় local অ্যারে, গভীর recursion | ছোট stack-এর হিসাব রাখা | Stack হয়তো মাত্র কয়েকশো বাইট; উপচে পড়লে চুপচাপ অন্য ডেটা মুছে যায় |
int, long | uint8_t, uint16_t, int32_t | AVR-এ int মাত্র ১৬ বিট; আকার নিশ্চিত না জানলে overflow |
Fixed-point — দশমিক ছাড়া দশমিকের হিসাব
পর্ব ০৮-এ দেখেছেন, টাকাকে পয়সায় গুনলে দশমিক ছাড়াই নির্ভুল হিসাব হয়। Embedded জগতে এই কৌশলের নাম fixed-point. একটি তাপমাত্রা সেন্সর (LM35-এর মতো, যা প্রতি °C-এ ১০ mV দেয়) একটি ১০ বিটের ADC-তে জোড়া, রেফারেন্স ভোল্টেজ ৫ V. ADC পড়ল 62. তাপমাত্রা কত? float ছাড়া হিসাবটি ধাপে ধাপে দেখুন:
| কী হিসাব হচ্ছে | |
| মান | |
| টাইপ |
uint32_t mv = ((uint32_t)adc * 5000u + 512u) >> 10; /* 303 */ int16_t t10 = (int16_t)mv; /* LM35: 10 mV = 1 C */ uart_printf("%d.%d C", t10 / 10, t10 % 10); /* "30.3 C" */
- কাঁচা মান। ১০ বিটের ADC ০ থেকে ১০২৩-এর মধ্যে একটি সংখ্যা দেয়: ০ মানে ০ V, ১০২৩ মানে প্রায় ৫ V. আমাদের হাতে 62.
- গুণ আগে। ভোল্টেজ (mV) = adc × 5000 / 1024. আগে ভাগ করলে
62 / 1024 = 0— পূর্ণসংখ্যার ভাগে সব হারিয়ে যেত, তাই গুণ আগে। কিন্তু 62 × 5000 = 310000, যা ১৬ বিটে ধরে না (সর্বোচ্চ ৬৫৫৩৫)। AVR-এint১৬ বিট, তাই এখানেuint32_t-তে cast না করলে overflow — পর্ব ০৮-এর type conversion-এর ফাঁদ, যা ল্যাপটপে কখনো ধরা পড়ে না কারণ সেখানেint৩২ বিট। - গোল করে ভাগ। 1024 দিয়ে ভাগ মানে ১০ ঘর ডানে শিফট (
>> 10), যা ছোট চিপে ভাগের চেয়ে অনেক দ্রুত। ভাগের আগে অর্ধেক (512) যোগ করলে কেটে ফেলার বদলে কাছের পূর্ণসংখ্যায় গোল হয়: 310512 / 1024 = 303.2… → 303 mV. - এককের খেলা। LM35 প্রতি °C-এ ১০ mV দেয়, তাই °C = mV / 10. আমরা ভাগ না করে মানটিকে “দশমাংশ °C” এককে রেখে দিলাম: 303 মানে ৩০.৩ °C. দশমিক বিন্দু আছে, শুধু আমরা জানি সেটি কোথায় — এটাই fixed-point.
- ছাপা।
303 / 10 = 30আর303 % 10 = 3, তাই লেখা হলো “30.3 C”. float দিয়ে হিসাব করলে আসত ৩০.২৭ °C — কিন্তু ADC-র এক ধাপই প্রায় ৪.৯ mV, মানে প্রায় ০.৫ °C. দ্বিতীয় দশমিকের অঙ্কটি আসলে ভুয়া নির্ভুলতা ছিল। পূর্ণসংখ্যার উত্তর এখানে সমান ভালো, এবং অনেক দ্রুত।
Watchdog — প্রোগ্রাম আটকালে কে উদ্ধার করবে?
ল্যাপটপে প্রোগ্রাম আটকালে আপনি Task Manager খুলে বন্ধ করেন। কিন্তু পাহাড়ের চূড়ায় বসানো একটি আবহাওয়া সেন্সর আটকালে কে বন্ধ করবে? উত্তর watchdog timer: চিপের ভেতরের একটি স্বাধীন টাইমার, যা উল্টো দিকে গুনছে। প্রোগ্রামকে নিয়মিত তাকে “kick” করতে হয় (গোনা আবার শুরু করাতে হয়)। কোনো লুপে আটকে গেলে kick আসবে না, গোনা শূন্যে পৌঁছাবে, আর watchdog পুরো চিপ reset করে দেবে।
for (;;) {
read_sensors();
control_motor();
watchdog_kick(); /* “আমি বেঁচে আছি” — না বললে চিপ reset */
}
সতর্কতা: kick শুধু লুপের এক জায়গায় রাখুন, যেখানে পৌঁছানো মানে সব কাজ ঠিকঠাক হয়েছে। টাইমার ISR-এর ভেতরে kick বসালে মূল লুপ আটকে থাকলেও ISR চলতে থাকবে, watchdog কখনো কামড়াবে না — পাহারাদার ঘুমিয়ে পড়বে।
বিদ্যুৎ বাঁচানো — ঘুমিয়ে থাকাই কাজ
একটি ব্যাটারিচালিত সেন্সর হয়তো প্রতি মিনিটে একবার তাপমাত্রা পাঠায়। বাকি ৫৯ সেকেন্ড CPU জেগে থাকলে ব্যাটারি কয়েক দিনে শেষ। তাই চিপকে sleep mode-এ পাঠানো হয়: CPU আর অপ্রয়োজনীয় peripheral বন্ধ, খরচ মাইক্রোঅ্যাম্পিয়ারে নেমে আসে, আর একটি টাইমার বা বাটনের interrupt তাকে জাগায়। ARM Cortex-M-এ একটি নির্দেশেই (__WFI(), “wait for interrupt”) CPU ঘুমিয়ে পড়ে। ভালো নকশায় এমন যন্ত্র একটি ছোট ব্যাটারিতে মাস থেকে বছর পর্যন্ত চলতে পারে।
৮ · টুলচেইন, ডিবাগিং আর নিরাপত্তার মান
পর্ব ০৩-এর চার ধাপের toolchain এখানেও আছে, তবে একটি মোড় নিয়ে। আপনার ল্যাপটপের CPU x86 বা ARM64, আর লক্ষ্য চিপ AVR বা Cortex-M. তাই এমন একটি compiler লাগে, যা ল্যাপটপে চলে কিন্তু অন্য CPU-র machine code বানায় — একে বলে cross-compiler. ARM-এর জন্য arm-none-eabi-gcc (“none” মানে কোনো OS নেই), AVR-এর জন্য avr-gcc.
# ARM Cortex-M4 (উদাহরণ: STM32F401)
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Os -T stm32f401.ld startup.s main.c -o blink.elf
arm-none-eabi-objcopy -O binary blink.elf blink.bin
# AVR (Arduino Uno), তারপর bootloader দিয়ে flash-এ লেখা
avr-gcc -mmcu=atmega328p -Os -DF_CPU=16000000UL blink.c -o blink.elf
avr-objcopy -O ihex blink.elf blink.hex
avrdude -c arduino -p m328p -P COM3 -b 115200 -U flash:w:blink.hex- Linker script (
-T stm32f401.ld): ল্যাপটপে OS ঠিক করে প্রোগ্রাম মেমরির কোথায় বসবে। এখানে আপনাকেই বলতে হয়: flash শুরু0x08000000, RAM শুরু0x20000000, কোড যাবে flash-এ, ভ্যারিয়েবল RAM-এ। - Startup code আর vector table: চিপ চালু হলে CPU flash-এর শুরুর vector table পড়ে — প্রথম ঘর থেকে stack-এর শুরুর ঠিকানা, দ্বিতীয় ঘর থেকে reset handler-এর ঠিকানা। Reset handler (startup code) প্রাথমিক মানওয়ালা ভ্যারিয়েবল flash থেকে RAM-এ কপি করে, বাকি global ভ্যারিয়েবল শূন্য করে, তারপর
main-কে ডাকে। পর্ব ০৪-এ যে প্রশ্ন ছিল — “main-এর আগে কী চলে?” — এখানে তার উত্তর আপনার নিজের প্রজেক্টেই দেখা যায়। - Flash-এ লেখা: একটি programmer যন্ত্র (যেমন ST-Link) দিয়ে, অথবা চিপে আগে থেকে রাখা bootloader দিয়ে USB-serial পথে। Arduino-র “Upload” বোতাম ভেতরে ঠিক avrdude আর bootloader-ই ব্যবহার করে।
- Debug: SWD বা JTAG পথে ল্যাপটপের gdb সরাসরি চিপের CPU থামাতে, breakpoint বসাতে আর রেজিস্টার পড়তে পারে — পর্ব ৩১-এর gdb, শুধু অপর প্রান্তে একটি আসল চিপ (সাধারণত OpenOCD নামের একটি সংযোগ-প্রোগ্রামের মাধ্যমে)।
Arduino IDE হলো রেডিমেড প্যাকেজ ট্যুরের মতো — টিকিট, হোটেল, গাইড সব ঠিক করা, আপনি শুধু বেড়ান। Linker script, startup code, vector table, compiler flag সবই লুকানো। শুরুর জন্য দারুণ। কিন্তু পেশাদার কাজে ভেতরের এই ধাপগুলো জানা জরুরি, কারণ বাগ প্রায়ই সেখানেই লুকিয়ে থাকে। মনে রাখবেন, Arduino-র sketch আসলে C++, যা avr-gcc-এর C++ সংস্করণ দিয়েই compile হয়; digitalWrite-এর ভেতরে শেষ পর্যন্ত সেই একই রেজিস্টারে বিট লেখা।
নিরাপত্তা যেখানে জীবন — MISRA C আর undefined behavior
পর্ব ৪৬-এ দেখেছেন, undefined behavior মানে “যা খুশি ঘটতে পারে”। ল্যাপটপে তার ফল একটি crash. ব্রেক কন্ট্রোলারে তার ফল দুর্ঘটনা। তাই গাড়ি, বিমান, চিকিৎসা-যন্ত্রের কোড লেখা হয় কড়া নিয়ম মেনে। সবচেয়ে পরিচিত নিয়মের সংকলন MISRA C — ব্রিটেনের Motor Industry Software Reliability Association ১৯৯৮ সালে প্রথম প্রকাশ করে, পরে ২০০৪ ও ২০১২ সালে বড় সংস্করণ আসে। এর কিছু নিয়ম: dynamic memory (malloc) নিষেধ, recursion নিষেধ, প্রতিটি switch-এ default বাধ্যতামূলক, signed আর unsigned-এর লুকানো মিশ্রণ নিষেধ। উদ্দেশ্য একটাই: কোড এমন হবে যার আচরণ টুল দিয়ে প্রমাণ করা যায়।
১৯৮৫ থেকে ১৯৮৭ সালের মধ্যে কানাডা ও যুক্তরাষ্ট্রে Therac-25 নামের একটি রেডিয়েশন থেরাপি যন্ত্র অন্তত ছয়জন রোগীকে মাত্রাতিরিক্ত বিকিরণ দেয়, যাঁদের কয়েকজন মারা যান। তদন্তে পাওয়া যায় সফটওয়্যারের race condition: অপারেটর খুব দ্রুত তথ্য সম্পাদনা করলে দুটি কাজের মাঝের সময়ের ফাঁকে যন্ত্র ভুল অবস্থায় চলে যেত, আর আগের মডেলের হার্ডওয়্যার সুরক্ষাগুলো বাদ দিয়ে সফটওয়্যারের ওপর ভরসা করা হয়েছিল। কোডটি C-তে নয়, assembly-তে লেখা ছিল — কিন্তু শিক্ষা চিরকালের: embedded সফটওয়্যারের concurrency বাগ মানুষের জীবন নিতে পারে। আজকের নিরাপত্তা-মান আর পরীক্ষার কড়াকড়ির পেছনে এই ঘটনার বড় ভূমিকা।
৯ · AI-এর যুগে embedded কোড — compile হলেই চলবে না
AI দিয়ে embedded কোড লেখানো খুবই লোভনীয়, আর অনেক সময় কাজেও লাগে। কিন্তু এখানে একটি বিশেষ বিপদ আছে: ল্যাপটপের কোডে ভুল হলে প্রোগ্রাম crash করে, আপনি দেখতে পান। Embedded কোডে ভুল হলে প্রায়ই কিছুই ঘটে না — LED জ্বলে না, অথবা সপ্তাহে একবার যন্ত্র আটকে যায়। আর AI আপনার চিপের datasheet পড়েনি; সে ইন্টারনেটের হাজারো আলাদা চিপের কোড মিশিয়ে উত্তর বানায়।
| AI-এর ভুল | ফল | যেভাবে ধরবেন |
|---|---|---|
রেজিস্টার বা ISR-এর ভাগ করা flag-এ volatile নেই | -O0-তে চলে, -Os-এ আটকে যায় | ISR আর main দুজনেই ছোঁয় এমন প্রতিটি ভ্যারিয়েবল খুঁজুন |
| ISR-এর ভেতরে printf, delay বা লম্বা হিসাব | অন্য interrupt হারায়, সময় গোলমাল | ISR-এ শুধু flag বা buffer-এ একটি মান রাখা আছে কি না |
ছোট চিপে float, malloc, পূর্ণ printf | Flash ভরে যায়, ধীর, হঠাৎ মেমরি শেষ | Build-এর পর flash আর RAM-এর ব্যবহার দেখুন |
REG = (1 << 5) — OR ছাড়া সরাসরি লেখা | রেজিস্টারের বাকি সব বিট শূন্য হয়ে যায় | প্রতিটি রেজিস্টার-লেখায় |=, &= ~ বা set/reset রেজিস্টার |
| ISR আর main দুজনেই একই রেজিস্টারে read-modify-write | বিরল, অনিয়মিত বাগ | Critical section বা atomic set/reset রেজিস্টার |
| অন্য চিপের রেজিস্টার-ঠিকানা (যেমন STM32F1-এর ঠিকানা F4-এ) | ভুল পিন বদলায়, বা কিছুই হয় না | প্রতিটি ঠিকানা নিজের চিপের datasheet/reference manual-এ মিলিয়ে দেখুন |
AI-কে embedded কোড চাইলে চিপ, বোর্ড, clock, toolchain আর সীমাবদ্ধতা — সব আগেই বলে দিন। শেষ দুটি অনুরোধ খেয়াল করুন: ঠিকানার উৎস জানতে চাওয়া, আর অনুমানগুলো আলাদা করে লিখতে বলা।
AI-এর embedded কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১Python এত জনপ্রিয়, তবু মাইক্রোকন্ট্রোলারে আজও C কেন রাজত্ব করে? MicroPython তাহলে কোথায় মানায়?
কারণগুলো ভাষার সৌন্দর্যে নয়, পদার্থবিদ্যায় — মেমরি, সময় আর বিদ্যুৎ।
- মেমরি: C প্রোগ্রাম compile হয়ে সরাসরি machine code হয়; একটি LED blink কয়েকশো বাইটেই হয়ে যায়। Python চালাতে আগে একটি interpreter লাগে। MicroPython-এর interpreter-এর জন্যই কয়েকশো KB flash আর কয়েক ডজন KB RAM দরকার, তাই ATmega328P-এর মতো ৩২ KB flash আর ২ KB RAM-এর চিপে এটি চলেই না।
- সময়ের নিশ্চয়তা: C-তে আপনি মোটামুটি জানেন কোন লাইন কত cycle নেবে। Python-এ garbage collector যেকোনো মুহূর্তে কয়েক মিলিসেকেন্ড থামিয়ে দিতে পারে। মোটর কন্ট্রোল বা এয়ারব্যাগের মতো hard real-time কাজে এই অনিশ্চয়তা অগ্রহণযোগ্য।
- হার্ডওয়্যারের কাছাকাছি: নির্দিষ্ট ঠিকানায় পয়েন্টার, বিটের ওপর পুরো নিয়ন্ত্রণ, struct-এর বাইট-বিন্যাস — এগুলোই রেজিস্টারের ভাষা, আর C ঠিক এই কাজের জন্য তৈরি।
- ইকোসিস্টেম: প্রতিটি চিপ নির্মাতা প্রথম দিনেই C header, C driver আর C উদাহরণ দেয়। কয়েক দশকের কোড, টুল আর নিরাপত্তা-সনদ (MISRA, ISO 26262) C-কে ঘিরে।
MicroPython কোথায় মানায়? ২০১৩ সালে Damien George একটি Kickstarter প্রচারণার মাধ্যমে এটি শুরু করেন। ESP32 বা RP2040-এর মতো বড় চিপে দ্রুত প্রোটোটাইপ, শিক্ষা, আর এমন IoT যন্ত্রে, যেখানে সময়ের হিসাব মিলিসেকেন্ডে নয়, সেকেন্ডে, MicroPython দারুণ। মজার ব্যাপার, MicroPython নিজেই C-তে লেখা — রেজিস্টারে লেখার শেষ কাজটি সেই C-ই করে। আর নতুন প্রজন্মের প্রতিযোগী হিসেবে Rust-ও embedded জগতে ধীরে ধীরে জায়গা করে নিচ্ছে, মূলত মেমরি-নিরাপত্তার কারণে।
মূল উপলব্ধি: ভাষা বাছাই হয় সীমাবদ্ধতা দেখে। যত কম মেমরি, যত কড়া সময়, যত বেশি নিরাপত্তার দায় — তত বেশি C (বা Rust). সুবিধার ভাষা ওপরের স্তরে, নিয়ন্ত্রণের ভাষা নিচের স্তরে, আর বাস্তব যন্ত্রে প্রায়ই দুটিই একসাথে থাকে।
প্র ০২একটি আধুনিক গাড়িতে কয়েক ডজন থেকে শতাধিক কম্পিউটার — এরা একে অপরের সাথে কথা বলে কীভাবে, আর গাড়ির সফটওয়্যার আপডেট এত গুরুত্বপূর্ণ কেন?
একটি গাড়িতে ইঞ্জিন, গিয়ারবক্স, ABS ব্রেক, এয়ারব্যাগ, স্টিয়ারিং, এসি, জানালা, ড্যাশবোর্ড — প্রায় প্রতিটির জন্য আলাদা ECU. আলাদা রাখার কারণ নিরাপত্তা আর ভাগ করা দায়িত্ব: বিনোদন-ব্যবস্থার একটি বাগ যেন ব্রেককে স্পর্শ করতে না পারে।
- যোগাযোগ: ECU-গুলো সাধারণত CAN bus-এ কথা বলে — Bosch-এর ১৯৮০-এর দশকে তৈরি একটি দুই তারের নেটওয়ার্ক, যেখানে প্রতিটি বার্তার একটি অগ্রাধিকার থাকে। ব্রেকের বার্তা সবসময় রেডিওর বার্তার আগে যায়।
- নিরাপত্তার ফাঁক: পুরোনো CAN-এ কোনো প্রমাণীকরণ ছিল না — যে বার্তা পাঠায়, সবাই তাকে বিশ্বাস করে। ২০১৫ সালে দুই নিরাপত্তা গবেষক Charlie Miller ও Chris Valasek দূর থেকে, গাড়ির বিনোদন-ব্যবস্থার ইন্টারনেট সংযোগ দিয়ে ঢুকে একটি Jeep Cherokee-র নিয়ন্ত্রণ নেওয়া দেখান। এর পরে Chrysler প্রায় ১৪ লাখ গাড়ি রিকল করে সফটওয়্যার আপডেট দেয়।
- আপডেট: আগে সফটওয়্যারের ভুল মানে ওয়ার্কশপে গাড়ি নেওয়া। এখন অনেক কোম্পানি OTA update দেয় — ফোনের মতো রাতারাতি নতুন firmware. সুবিধা বিশাল, কিন্তু ঝুঁকিও: আপডেট মাঝপথে বিদ্যুৎ হারালে ECU অচল হতে পারে, আর জাল আপডেট ঢোকাতে পারলে আক্রমণকারী গাড়ির ভেতরে। তাই আপডেট ডিজিটাল স্বাক্ষর দিয়ে যাচাই, আর পুরোনো সংস্করণে ফেরার ব্যবস্থা রাখা হয়।
গাড়ির কার্যকরী নিরাপত্তার জন্য আলাদা আন্তর্জাতিক মানও আছে (ISO 26262), আর ECU সফটওয়্যারের কাঠামো ভাগাভাগি করতে অনেক কোম্পানি AUTOSAR নামের একটি সাধারণ স্থাপত্য মেনে চলে।
মূল উপলব্ধি: আধুনিক গাড়ি চাকার ওপর একটি নেটওয়ার্ক — আর পর্ব ৫০-এর শিক্ষা এখানেও খাটে: নেটওয়ার্ক কাউকে বিশ্বাস করে না। Embedded প্রকৌশলীকে এখন শুধু রেজিস্টার নয়, নিরাপত্তা আর আপডেটের নকশাও ভাবতে হয়।
প্র ০৩মঙ্গলগ্রহের রোভার বা মহাকাশযান কেন বহু বছরের পুরোনো, ধীর প্রসেসর ব্যবহার করে? watchdog সেখানে কী ভূমিকা রাখে?
NASA-র Curiosity (২০১২ সালে মঙ্গলে নামে) আর Perseverance (২০২১) রোভারের মূল কম্পিউটার RAD750 — ১৯৯০-এর দশকের PowerPC 750 নকশার একটি radiation-hardened সংস্করণ, গতি মাত্র ২০০ MHz-এর কাছাকাছি। আপনার ফোনের প্রসেসর এর চেয়ে বহুগুণ দ্রুত। তবু কেন?
- তেজস্ক্রিয়তা: পৃথিবীর বায়ুমণ্ডল ও চৌম্বকক্ষেত্র আমাদের মহাজাগতিক কণা থেকে বাঁচায়; মহাকাশে সেই ঢাল নেই। একটি উচ্চশক্তির কণা চিপে আঘাত করলে মেমরির একটি বিট উল্টে যেতে পারে (single-event upset), এমনকি চিপ স্থায়ীভাবে নষ্টও হতে পারে। বিশেষ নকশার চিপ এসব সহ্য করে, কিন্তু তৈরি ও পরীক্ষা করতে বহু বছর লাগে — তাই তারা সবসময় প্রজন্মখানেক পিছিয়ে।
- প্রমাণিত নির্ভরযোগ্যতা: মঙ্গলে কেউ গিয়ে reset বোতাম চাপবে না। নতুন দ্রুত চিপের চেয়ে বহুবার উড়ে আসা পুরোনো চিপ বেশি বিশ্বাসযোগ্য।
- স্তরে স্তরে সুরক্ষা: ভুল-সংশোধনী (ECC) মেমরি, দুটি আলাদা কম্পিউটার (একটি নষ্ট হলে অন্যটি দায়িত্ব নেয় — ২০১৩ সালে Curiosity মেমরির সমস্যায় সত্যিই তার দ্বিতীয় কম্পিউটারে চলে গিয়েছিল), আর watchdog.
Watchdog-এর বাস্তব গল্প: ১৯৯৭ সালে Mars Pathfinder মঙ্গলে নামার কয়েক দিন পর বারবার নিজে নিজে reset হতে থাকে। কারণ ছিল priority inversion (পর্ব ৪৯): একটি কম গুরুত্বের task একটি তালা ধরে রাখায় গুরুত্বপূর্ণ task সময়মতো চলতে পারছিল না, আর সময় পেরোতেই সুরক্ষা-ব্যবস্থা পুরো সিস্টেম reset করছিল। এই reset-ই যানটিকে বাঁচিয়ে রাখে — আটকে থাকার বদলে আবার শুরু। পৃথিবী থেকে প্রকৌশলীরা সমস্যা খুঁজে বের করে RTOS-এর একটি সেটিং (priority inheritance) চালু করার আপডেট পাঠান।
এই ধরনের অভিজ্ঞতা থেকেই NASA-র JPL-এর Gerard Holzmann ২০০৬ সালে “Power of 10” নামে নিরাপদ C লেখার দশটি নিয়ম প্রস্তাব করেন: recursion নয়, প্রতিটি লুপের নির্দিষ্ট সর্বোচ্চ সীমা, শুরুর পর dynamic memory নয়, ছোট ফাংশন, সব warning চালু।
মূল উপলব্ধি: নির্ভরযোগ্যতা গতির চেয়ে দামি। ভালো embedded নকশা ধরে নেয় যে কিছু না কিছু ভুল হবেই, আর সেই ভুল থেকে নিজে নিজে সেরে ওঠার পথ আগেই বানিয়ে রাখে।
প্র ০৪বাটনে চাপ পড়া থেকে ISR-এর প্রথম লাইন চলা পর্যন্ত হার্ডওয়্যারের ভেতরে আসলে কী কী ঘটে?
ARM Cortex-M দিয়ে পুরো যাত্রাটি দেখি। পুরোটা কয়েক ডজন clock cycle-এর ব্যাপার, কিন্তু প্রতিটি ধাপে একটি নকশার সিদ্ধান্ত আছে।
- সংকেত ধরা: পিনের ভোল্টেজ 0 থেকে 1 হলো। GPIO-র পাশের edge-detector সার্কিট এটি দেখে একটি “pending” বিট তোলে।
- Interrupt controller-এর বিচার: Cortex-M-এ এর নাম NVIC. এটি দেখে: এই interrupt কি চালু করা আছে? এর অগ্রাধিকার কি এখন যা চলছে তার চেয়ে বেশি? হ্যাঁ হলে CPU-কে সংকেত দেয়। অগ্রাধিকার কম হলে অপেক্ষা।
- অবস্থা সংরক্ষণ: CPU চলতি নির্দেশ শেষ করে হার্ডওয়্যার নিজেই আটটি রেজিস্টার (R0–R3, R12, LR, PC আর status register xPSR) stack-এ রাখে। এই কাজ হার্ডওয়্যারে হয় বলেই Cortex-M-এর ISR একটি সাধারণ C ফাংশন হতে পারে — বিশেষ কোনো assembly লাগে না।
- Vector table থেকে ঠিকানা: একই সময়ে CPU vector table থেকে এই interrupt নম্বরের ঘরটি পড়ে — সেখানে লেখা আছে আপনার
EXTI0_IRQHandlerফাংশনের ঠিকানা। এই টেবিলটিই startup ফাইলে আর linker script-এ সাজানো থাকে। - ISR চলে, তারপর ফেরা: LR-এ একটি বিশেষ “ফেরার সংকেত” মান রাখা হয়। ISR শেষে সেই মানে ফিরলে হার্ডওয়্যার বোঝে এটি interrupt থেকে ফেরা, আর stack থেকে আটটি রেজিস্টার ফেরত আনে। মূল প্রোগ্রাম জানতেও পারে না যে সে থেমেছিল।
Cortex-M3/M4-এ ধাপ ৩ থেকে ISR-এর প্রথম নির্দেশ পর্যন্ত লাগে প্রায় ১২ cycle. পরপর দুটি interrupt এলে মাঝখানে রেজিস্টার ফেরত না এনে সরাসরি পরেরটিতে যাওয়ার কৌশলও আছে (tail-chaining). তুলনায় ৮ বিটের AVR হার্ডওয়্যারে শুধু PC রাখে; বাকি রেজিস্টার রাখার কোড compiler নিজে ISR-এর শুরুতে বসিয়ে দেয়। তাই AVR-এ ISR যত বেশি রেজিস্টার ব্যবহার করে, প্রবেশ-প্রস্থান তত ধীর।
মূল উপলব্ধি: Interrupt কোনো জাদু নয় — এটি একটি হার্ডওয়্যার-নিয়ন্ত্রিত ফাংশন কল, যেখানে “কে ডাকল” আর “কোথায় যেতে হবে” ঠিক করে একটি টেবিল। পর্ব ১৩-এর stack frame, পর্ব ২৮-এর ফাংশন পয়েন্টারের টেবিল আর পর্ব ০৩-এর linker — সব এখানে একসাথে কাজ করছে।
প্র ০৫২০১৬ সালের Mirai botnet লাখো ক্যামেরা ও রাউটার দখল করেছিল। ছোট IoT যন্ত্রের নিরাপত্তা এত দুর্বল কেন, আর একজন firmware প্রকৌশলী কী করতে পারেন?
২০১৬ সালে Mirai নামের একটি ম্যালওয়্যার ইন্টারনেটে খুঁজে খুঁজে IP ক্যামেরা, DVR আর হোম রাউটার দখল করে। কৌশলটি ছিল লজ্জাজনকভাবে সহজ: যন্ত্রগুলোর Telnet পোর্টে কয়েক ডজন কারখানা-নির্ধারিত ইউজারনেম-পাসওয়ার্ড (যেমন admin/admin) একে একে চেষ্টা করা। লক্ষ লক্ষ মালিক কখনো পাসওয়ার্ড বদলাননি, অনেক যন্ত্রে বদলানোর উপায়ই ছিল না।
দখল করা যন্ত্রগুলো দিয়ে তৈরি হয় একটি বিশাল botnet. সেপ্টেম্বরে এটি নিরাপত্তা-সাংবাদিক Brian Krebs-এর ওয়েবসাইটে সেই সময়ের রেকর্ড আকারের আক্রমণ চালায়। অক্টোবরে আক্রমণ হয় DNS সেবাদাতা Dyn-এর ওপর, ফলে যুক্তরাষ্ট্র ও ইউরোপের অনেক জায়গায় Twitter, Netflix, Reddit, GitHub-এর মতো বড় সাইট কয়েক ঘণ্টা ঠিকমতো খোলেনি — একধরনের denial-of-service (পর্ব ৪৮-এর fork bomb-এর মতোই সেবা অচল করা), তবে এবার অস্ত্র ছিল ঘরের ক্যামেরা। পরে জানা যায়, এর নির্মাতারা ছিলেন যুক্তরাষ্ট্রের কয়েকজন তরুণ, যাঁরা আদালতে দোষ স্বীকার করেন। তার আগেই তাঁরা সোর্স কোড অনলাইনে প্রকাশ করে দেওয়ায় অসংখ্য অনুকরণ-botnet তৈরি হয়।
- কেন দুর্বল: দাম কয়েক ডলারে নামাতে নিরাপত্তায় খরচ কাটা হয়; প্রতিটি যন্ত্রে একই পাসওয়ার্ড; আপডেটের ব্যবস্থা নেই; আর ছোট চিপে শক্তিশালী এনক্রিপশন চালানো কঠিন মনে করা হয়।
- Firmware প্রকৌশলীর করণীয়: প্রতিটি যন্ত্রে আলাদা পাসওয়ার্ড বা প্রথম চালুতে বদলানো বাধ্যতামূলক; অপ্রয়োজনীয় পোর্ট (Telnet) বন্ধ; স্বাক্ষর-যাচাই করা নিরাপদ আপডেট; ইনপুট যাচাই আর buffer overflow থেকে মুক্ত কোড (পর্ব ৪৬); আর যন্ত্রের পুরো জীবনকাল ধরে নিরাপত্তা-সংশোধনী দেওয়া।
এই ঘটনার পর বিভিন্ন দেশ আইন করতে শুরু করে। যেমন যুক্তরাজ্যে ২০২৪ সাল থেকে ভোক্তা-যন্ত্রে সবার জন্য একই ডিফল্ট পাসওয়ার্ড রাখা নিষিদ্ধ। বাংলাদেশেও স্মার্ট মিটার, CCTV আর IoT যন্ত্রের ব্যবহার দ্রুত বাড়ছে, তাই এই শিক্ষা আমাদের জন্যও প্রাসঙ্গিক।
মূল উপলব্ধি: ইন্টারনেটে যুক্ত হওয়া মাত্র একটি ছোট সেন্সরও পুরো ইন্টারনেটের নিরাপত্তার অংশ হয়ে যায়। Embedded কোডের দায় তাই শুধু যন্ত্রটি চালানো নয় — যন্ত্রটি যেন অন্যের অস্ত্র না হয়, সেটিও।
অনুশীলন
বেশিরভাগ কাজ কাগজ-কলমে — হার্ডওয়্যার ছাড়াই embedded চিন্তার অভ্যাস।
-
রেজিস্টার হাতে হিসাব: একটি ODR-এ এখন
0xF0. পরপর তিনটি লাইন চলল:ODR &= ~(1u << 5);তারপরODR |= (1u << 1);তারপরODR ^= (1u << 7);শেষে ODR-এর মান কত? কোন কোন পিন চালু?0xF0=1111 0000(পিন ৪–৭ চালু)। বিট ৫ বন্ধ:1101 0000=0xD0. বিট ১ চালু:1101 0010=0xD2. বিট ৭ উল্টানো (1 থেকে 0):0101 0010=0x52. চালু পিন: ১, ৪ আর ৬। খেয়াল করুন, প্রতিটি লাইন শুধু নিজের বিটটি ছুঁয়েছে, বাকি সব অপরিবর্তিত। -
Fixed-point নিজে করুন: একই LM35 সেন্সর, কিন্তু এবার ৩.৩ V-এ চলা একটি চিপ, যার ১০ বিটের ADC-র রেফারেন্সও ৩.৩ V. ADC পড়ল 93. গোল করার নিয়মসহ শুধু পূর্ণসংখ্যায় mV আর তাপমাত্রা বের করুন। ১৬ বিটের
int-এ গুণফলটি ধরত কি?mV = (93 × 3300 + 512) / 1024 = (306900 + 512) / 1024 = 307412 / 1024 = 300.2… → 300 mV. দশমাংশ °C = 300, অর্থাৎ 30.0 °C. float-এ হিসাব করলে আসত প্রায় ২৯.৯৭ °C — এক দশমিক পর্যন্ত একই উত্তর। গুণফল 306900 ষোলো বিটের সীমা (৬৫৫৩৫) অনেক ছাড়িয়ে যায়, তাই
uint32_tছাড়া উপায় নেই। ১০২৩ × ৩৩০০ = ৩৩,৭৫,৯০০ পর্যন্ত মান হতে পারে — ৩২ বিটে নিরাপদে ধরে। -
Debounce-এ ছাড়ার ঘটনাও ছাপুন: অংশ ৬-এর
debounce.c-এ একটি লাইন যোগ করুন, যাতে বাটন নিশ্চিতভাবে ছাড়া হলেRELEASEছাপে। কোন কোন সময়ে ছাপবে, আগে হাতে হিসাব করে তারপর চালিয়ে মিলিয়ে নিন।PRESS-এর লাইনের ঠিক নিচে যোগ করুন:
if (prev == MAYBE_UP && s == UP) printf("t=%3d ms RELEASE\n", i * 5);ছাপবে
t= 55 ms RELEASEআরt= 90 ms RELEASE. প্রথম চাপের পর ৪০ ms-এ 0 এলো (MAYBE_UP), ৪৫ ms-এ 1 এসে আবারDOWN— তাই সেখানে ছাড়া গোনা হয়নি। ৫০ আর ৫৫ ms-এ পরপর দুটি 0, তাই ৫৫ ms-এ নিশ্চিত ছাড়া। দ্বিতীয় চাপের পর ৮৫ আর ৯০ ms-এ পরপর দুটি 0 — ৯০ ms-এ ছাড়া। প্রতিটি PRESS-এর পরে ঠিক একটি RELEASE, যেমনটা হওয়ার কথা। -
ঘরের যন্ত্রের ময়নাতদন্ত: আপনার বাসার তিনটি যন্ত্র বেছে নিন (যেমন রাইস কুকার, ওয়াশিং মেশিন, আইপিএস)। প্রতিটির ভেতরের মাইক্রোকন্ট্রোলার কোন কোন peripheral ব্যবহার করছে বলে আপনার মনে হয়? কোন কাজে interrupt আর কোন কাজে polling মানাবে?
একটি নমুনা উত্তর:
- রাইস কুকার: ADC দিয়ে তলার তাপমাত্রা সেন্সর পড়া, GPIO দিয়ে হিটারের রিলে আর “cook/warm” বাতি, টাইমার দিয়ে সময় গোনা। তাপমাত্রা ধীরে বদলায়, তাই প্রতি সেকেন্ডে polling যথেষ্ট।
- ওয়াশিং মেশিন: PWM দিয়ে মোটরের গতি, GPIO দিয়ে পানির ভালভ, পানির স্তরের সেন্সর, আর পুরো ধোয়ার চক্র একটি state machine (পানি ভরা → ধোয়া → নিষ্কাশন → নিংড়ানো)। দরজা খোলার সুইচ interrupt-এ, কারণ ঘুরন্ত ড্রামে সঙ্গে সঙ্গে থামা নিরাপত্তার প্রশ্ন।
- আইপিএস/ইনভার্টার: ADC দিয়ে লাইনের ভোল্টেজ আর ব্যাটারির চার্জ মাপা, উচ্চ-গতির PWM দিয়ে AC তৈরি, আর বিদ্যুৎ চলে যাওয়া ধরা interrupt-এ, কারণ মিলিসেকেন্ডের মধ্যে ব্যাটারিতে যেতে না পারলে কম্পিউটার বন্ধ হয়ে যাবে।
নিয়মটা লক্ষ করুন: ধীর, নিয়মিত ঘটনা → polling; দ্রুত, অনিয়মিত বা নিরাপত্তা-সংবেদনশীল ঘটনা → interrupt.
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি Cortex-M চিপ বিদ্যুৎ পাওয়ার পর কী কী ঘটে, সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 🔌 বিদ্যুৎ এলো বা reset বোতাম চাপা হলো
- 📋 CPU vector table থেকে stack-এর ঠিকানা আর reset handler-এর ঠিকানা পড়ল
- 🧹 Startup code ভ্যারিয়েবলের প্রাথমিক মান RAM-এ কপি করল, বাকিগুলো শূন্য করল
- ⚙️
mainশুরু: clock, পিনের মোড আর টাইমার সেট করা হলো - 🔁 Super-loop চিরকাল চলতে থাকল, interrupt এলে ISR
Arduino Uno-র ATmega328P চিপে কতটুকু RAM (SRAM) আছে?
- 32 KB
- 2 KB
- 520 KB
- 2 GB
একটি রেজিস্টারের শুধু বিট ৫ বন্ধ করতে হবে, বাকি সব বিট অপরিবর্তিত রেখে। কোনটি ঠিক?
REG = (1u << 5);REG |= (1u << 5);REG &= ~(1u << 5);REG ^= (1u << 5);
ISR একটি flag-এ 1 লেখে, আর main লুপ while (!flag) { } দিয়ে অপেক্ষা করে। -O0-তে কাজ করে, -Os-এ চিরকাল আটকে থাকে। সবচেয়ে সম্ভাব্য কারণ?
- flag-টি
volatileঘোষণা করা হয়নি - flag-টি
uint8_tনা হয়েuint32_tহওয়া উচিত ছিল - -Os flag-টি compiler-এ একটি বাগ তৈরি করে
- ISR কখনো চলেই না
একটি ভালো ISR-এর জন্য কোনটি সবচেয়ে ভালো অভ্যাস?
- কী ঘটল তা printf দিয়ে বিস্তারিত ছাপা
- বাটনের কাঁপুনি থামা পর্যন্ত ISR-এর ভেতরে ২০ ms অপেক্ষা
- ডেটা রাখার জন্য malloc দিয়ে নতুন buffer নেওয়া
- Interrupt flag মুছে একটি volatile flag-এ 1 লিখে দ্রুত বেরিয়ে যাওয়া
একবার বাটন চাপলে counter ৩ বা ৪ বেড়ে যাচ্ছে। এর মূল কারণ কী?
- Counter-এর টাইপ ছোট, তাই overflow
- বাটনের ধাতব সংযোগ কাঁপে (bounce), একটি চাপ কয়েকটি 0→1 পরিবর্তন তৈরি করে
- চিপের clock খুব দ্রুত
- Compiler counter++ লাইনটি কয়েকবার চালায়
৮ বিটের AVR-এ int mv = adc * 5000 / 1024; লিখলে adc = 62-তে ভুল ফল আসে, অথচ ল্যাপটপে ঠিক আসে। কেন?
- C-তে ভাগ গুণের আগে হয়
- AVR ভাগ করতে পারে না
- AVR-এ
int১৬ বিট, তাই গুণফল 310000 overflow করে - ADC-র মান ১০ বিটের বেশি
Watchdog timer-এর মূল কাজ কী?
- প্রোগ্রাম আটকে গেলে নির্দিষ্ট সময় পর চিপ নিজে থেকে reset করা
- দিনের সময় আর তারিখ গুনে রাখা
- প্রোগ্রামের প্রতিটি লাইন কম্পিউটারে পাঠিয়ে দেখা
- CPU-কে ঘুম পাড়িয়ে বিদ্যুৎ বাঁচানো
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- মাইক্রোকন্ট্রোলার = CPU + flash + SRAM + peripheral এক চিপে; প্রায়ই OS নেই, MMU নেই, মেমরি KB-র হিসাবে।
- Peripheral চালানো মানে নির্দিষ্ট ঠিকানার রেজিস্টারে বিট লেখা:
volatile uint32_tপয়েন্টার,|=দিয়ে চালু,&= ~দিয়ে বন্ধ; ঠিকানা আসে শুধু datasheet থেকে। - Read-modify-write-এর মাঝে interrupt ঢুকতে পারে — set/reset রেজিস্টার বা critical section দিয়ে বাঁচুন।
- Super-loop-এ ধীর কাজ polling-এ, জরুরি ঘটনা interrupt-এ; ISR ছোট, ভাগ করা flag volatile; বাটনে debounce state machine.
- ছোট চিপে malloc, printf, float এড়িয়ে স্থির buffer, UART লগ আর fixed-point; আটকে গেলে উদ্ধার করে watchdog.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫২ · ক্যাপস্টোন — একটা আসল সিস্টেমপরবর্তী পর্বপুরো কোর্সের জ্ঞান জুড়ে একটি সম্পূর্ণ প্রজেক্ট।
- C প্রোগ্রামিং · কোর্স হোম৫৬টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Microprocessors, Embedded Systems & IoTগভীরে যানমাইক্রোকন্ট্রোলারের স্থাপত্য, peripheral, RTOS আর IoT প্রজেক্ট বিস্তারিত।
- Computer Architecture & Digital Logicপাশের ট্র্যাকরেজিস্টার, বাস, interrupt আর মেমরি-ম্যাপ — CPU-র ভেতর থেকে।
- Digital Signal Processingপাশের ট্র্যাকADC থেকে আসা সংকেত ফিল্টার করা, নমুনা নেওয়ার তত্ত্ব আর fixed-point DSP.