পাঠ ২২ · ৫৭-এর মধ্যে · মডিউল ৬

UART সিরিয়াল কমিউনিকেশন

UART serial communication
১০ মিনিট পড়া মধ্যম · Intermediate Python সিমুলেশনসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • UART কেন "অ্যাসিনক্রোনাস" -- আলাদা ক্লক তার ছাড়া দুই ডিভাইস কীভাবে সিঙ্ক্রোনাইজড থাকে
  • বড রেট থেকে বিট-টাইম গণনার সূত্র এবং তার বাস্তব প্রভাব
  • UART ফ্রেমের সঠিক গঠন -- start/data/parity/stop বিট
  • একটি সত্যিকারের সিমুলেশন -- ক্লিন ট্রান্সমিশন এবং একটি করাপ্ট বিট দিয়ে প্যারিটি এরর ধরা পড়া

১ · UART কেন "অ্যাসিনক্রোনাস"

UARTUARTUniversal Asynchronous Receiver/Transmitter -- দুটো তার (TX, RX) দিয়ে সিরিয়াল ডেটা পাঠানোর একটি ক্লকবিহীন প্রোটোকল, বড রেটের উপর নির্ভরশীল। হলো এই কোর্সের প্রথম সিরিয়াল প্রোটোকল -- এবং সবচেয়ে সরল। এতে মাত্র দুটো তার লাগে: TX (Transmit) ও RX (Receive), সাথে একটি কমন গ্রাউন্ড। SPI বা I2C-এর (পরের দুই পাঠ) মতো এতে কোনো আলাদা ক্লক তার নেই -- তাই একে অ্যাসিনক্রোনাস বলা হয়।

কিন্তু ক্লক তার ছাড়া রিসিভার কীভাবে জানে প্রতিটি বিট কখন শুরু-শেষ হচ্ছে? সমাধান হলো -- দুই পাশ আগে থেকেই একটি নির্দিষ্ট বড রেটে (baud rate, প্রতি সেকেন্ডে কতগুলো বিট পাঠানো হবে) একমত হয়ে নেয়। রিসিভার একটি "start bit" দেখে টাইমিং শুরু করে, তারপর সেই সম্মত বড রেট অনুযায়ী নির্দিষ্ট বিরতিতে বিট স্যাম্পল করে -- ক্লক তার ছাড়াই দুই পাশ কার্যত সিঙ্ক্রোনাইজড থাকে, যতক্ষণ দুই পাশের বড রেট মিলে যায়।

Computer Architecture কোর্সের সাথে সম্পর্ক

Computer Architecture & Digital Logic কোর্সে সাধারণ সিস্টেম বাস আর্কিটেকচার কভার করা হয়েছে। UART/SPI/I2C (এই পুরো মডিউল) তার একধাপ নিচের স্তর -- চিপের বাইরে দুটো ডিভাইসের মধ্যে ডেটা আদান-প্রদানের জন্য ব্যবহৃত ডিভাইস-লেভেল সিরিয়াল প্রোটোকল।

২ · UART ফ্রেমের গঠন

প্রতিটি বাইট একটি "ফ্রেমে" মোড়া হয়ে পাঠানো হয়:

  • Start bit -- সবসময় ০ (লাইনকে idle HIGH থেকে LOW-এ নামিয়ে রিসিভারকে জানায় ডেটা আসছে)
  • ৮টি Data bit -- সবচেয়ে কম গুরুত্বপূর্ণ বিট (LSB) আগে পাঠানো হয়
  • Parity bit (ঐচ্ছিক) -- এরর-ডিটেকশনের জন্য, ডেটা বিটগুলোর মধ্যে ১-এর সংখ্যা জোড় (even parity) বা বিজোড় (odd parity) রাখতে ব্যবহৃত হয়
  • Stop bit -- সবসময় ১ (লাইন idle HIGH-এ ফিরে যায়, ফ্রেম শেষের সংকেত)
সময় (প্রেরণের দিক) → Start (0) D0 D1 D2 D3 D4 D5 D6 D7 Parity Stop (1) D0..D7 = LSB আগে পাঠানো হয় (LSB-first) প্যারিটি ঐচ্ছিক -- না থাকলে সরাসরি stop bit আসে
একটি UART ফ্রেম: start bit + ৮টি ডেটা বিট (LSB প্রথমে) + ঐচ্ছিক প্যারিটি বিট + stop bit।

৩ · বড রেট ও বিট-টাইম গণনা

বড রেট (baud rate) মানে প্রতি সেকেন্ডে কতগুলো বিট পাঠানো হচ্ছে। একটি বিট পাঠাতে ঠিক কত সময় লাগে তা সরাসরি এই সূত্র থেকে বের হয়:

$$bit\_time_{\mu s} = \frac{10^6}{baud\_rate}$$

অর্থাৎ বড রেট যত বেশি, প্রতিটি বিটের সময় তত কম। নিচের কোড সেলে কয়েকটি সাধারণ বড রেটের জন্য সত্যিকারের বিট-টাইম এবং একটি পুরো ১০-বিট ফ্রেম (start + ৮ data + stop, প্যারিটি ছাড়া) পাঠাতে লাগা সময় গণনা করা হয়েছে।

Python
def compute_bit_time_us(baud_rate):
    return 1e6 / baud_rate


baud_rates = [9600, 19200, 38400, 115200]

print("বড রেট বনাম বিট-টাইম (start+৮ data+stop = ১০-বিট ফ্রেম ধরে):")
for baud in baud_rates:
    bit_time_us = compute_bit_time_us(baud)
    frame_time_us = bit_time_us * 10
    print(f"  {baud:>7} baud  ->  bit_time = {bit_time_us:8.3f} µs,  "
          f"১টি ফ্রেম (১০ বিট) পাঠাতে সময় = {frame_time_us:8.3f} µs")

print(f"\n১১৫২০০ baud, ৯৬০০ baud-এর তুলনায় "
      f"{compute_bit_time_us(9600) / compute_bit_time_us(115200):.1f} গুণ দ্রুত।")

    

৪ · ফ্রেম সিমুলেশন -- ক্লিন বনাম করাপ্টেড ট্রান্সমিশন

নিচের কোড সেলে একটি সত্যিকারের UART ফ্রেম তৈরি করা হয়েছে (start bit + ৮টি ডেটা বিট, LSB আগে + even parity বিট + stop bit), তারপর একজন "রিসিভার" ফাংশন সেই ফ্রেম থেকে বাইট পুনর্গঠন করে এবং প্যারিটি/ফ্রেমিং চেক করে। প্রথমে একটি ক্লিন ট্রান্সমিশন, তারপর একই ফ্রেমের একটি ডেটা বিট ইচ্ছাকৃতভাবে উল্টে দিয়ে দেখানো হয়েছে রিসিভার সেই এরর সত্যিই ধরে ফেলে -- কারণ একটি বিট উল্টে গেলে ১-এর সংখ্যার জোড়/বিজোড়ত্বও বদলে যায়, তাই even-parity চেক নিশ্চিতভাবে মিসম্যাচ ধরবে।

Python
def even_parity_bit(data_bits):
    return sum(data_bits) % 2


def build_uart_frame(byte_value, use_parity=True):
    data_bits = [(byte_value >> i) & 1 for i in range(8)]  # LSB প্রথমে
    frame = [0]                       # start bit
    frame.extend(data_bits)
    if use_parity:
        frame.append(even_parity_bit(data_bits))
    frame.append(1)                   # stop bit
    return frame


def receive_uart_frame(frame, use_parity=True):
    events = []
    if frame[0] != 0:
        events.append("ফ্রেমিং এরর: start bit 0 হওয়ার কথা ছিল")
        return None, events

    idx = 1
    data_bits = frame[idx:idx + 8]
    idx += 8
    events.append(f"৮টি ডেটা বিট গৃহীত (LSB প্রথমে): {data_bits}")

    received_byte = 0
    for i, b in enumerate(data_bits):
        received_byte |= (b << i)

    if use_parity:
        received_parity = frame[idx]
        idx += 1
        expected_parity = even_parity_bit(data_bits)
        if received_parity != expected_parity:
            events.append(
                f"প্যারিটি এরর ধরা পড়েছে! প্রাপ্ত প্যারিটি বিট={received_parity}, "
                f"গৃহীত ডেটা থেকে প্রত্যাশিত={expected_parity}")
            return None, events
        events.append(f"প্যারিটি চেক পাস (received={received_parity} == expected={expected_parity})")

    stop_bit = frame[idx]
    if stop_bit != 1:
        events.append("ফ্রেমিং এরর: stop bit 1 হওয়ার কথা ছিল")
        return None, events
    events.append("stop bit সঠিক")
    return received_byte, events


sample_byte = 0b01011010  # ৯০
print(f"পাঠাতে চাওয়া বাইট: {sample_byte} = {format(sample_byte, '08b')}b\n")

clean_frame = build_uart_frame(sample_byte, use_parity=True)
print(f"তৈরি ফ্রেম [start, D0..D7, parity, stop]: {clean_frame}")

print("\n--- দৃশ্য ১: ক্লিন ট্রান্সমিশন ---")
received_value, log = receive_uart_frame(clean_frame, use_parity=True)
for line in log:
    print(" ", line)
status = "সঠিক" if received_value == sample_byte else "ভুল"
print(f"রিসিভড বাইট: {received_value} = {format(received_value, '08b')}b  ->  মূল বাইটের তুলনায় {status}")

print("\n--- দৃশ্য ২: একটি ডেটা বিট করাপ্ট করে ট্রান্সমিশন ---")
corrupted_frame = clean_frame.copy()
corrupted_frame[3] = 1 - corrupted_frame[3]   # তৃতীয় ডেটা বিটটি (D2) উল্টে দেওয়া হলো
print(f"করাপ্ট ফ্রেম [start, D0..D7, parity, stop]: {corrupted_frame}")
received_value2, log2 = receive_uart_frame(corrupted_frame, use_parity=True)
for line in log2:
    print(" ", line)
if received_value2 is None:
    print("ফলাফল: রিসিভার এই ফ্রেমটি বাতিল করেছে -- এরর সত্যিই ধরা পড়েছে, ভুল ডেটা গ্রহণ করা হয়নি।")

    
লক্ষ্য করুন -- প্যারিটি বিট সব ধরনের এরর ধরতে পারে না। যদি দুটো ডেটা বিট একসাথে উল্টে যায়, তাহলে ১-এর সংখ্যার জোড়/বিজোড়ত্ব অপরিবর্তিত থেকে যেতে পারে, এবং প্যারিটি চেক সেই এররটি মিস করে যাবে। এই সীমাবদ্ধতার কারণেই বেশি নির্ভরযোগ্য সিস্টেমে (যেমন M8-এর reliability পাঠে) CRC-এর মতো শক্তিশালী চেকসাম ব্যবহৃত হয়।
মূল কথা · Key takeaway

UART = দুই তার (TX/RX) + আগে-থেকে-সম্মত বড রেট + start/data/parity/stop ফ্রেমিং। এটি সরল ও পয়েন্ট-টু-পয়েন্ট (একসাথে একটি মাত্র ডিভাইসের সাথে), কিন্তু একাধিক ডিভাইস একই বাসে যুক্ত করার সুবিধা দেয় না -- সেই সমস্যার সমাধান নিয়েই পরের দুই পাঠে SPI ও I2C আসছে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন -- তারপর "→ উত্তর" চাপুন।

প্র ০১ দুই ডিভাইসের UART বড রেট সেটিং যদি না মেলে (একটি ৯৬০০, আরেকটি ১৯২০০), তাহলে কী হবে বলে আপনার মনে হয়?

রিসিভার ভুল বিট-টাইমে ডেটা স্যাম্পল করবে, ফলে ভুল সময়ে লাইনের ভোল্টেজ পড়বে -- এলোমেলো/অর্থহীন বাইট, এবং প্রায়ই stop bit-এর জায়গায় ভুল মান পাওয়ায় একটি ফ্রেমিং এরর দেখা দেবে। এই কারণেই দুই পাশের বড রেট সেটিং মেলানো UART-এর সবচেয়ে সাধারণ প্র্যাকটিক্যাল সমস্যাগুলোর একটি।

প্র ০২ কোড সেলে দেখানো হয়েছে একটি বিট উল্টে গেলে even-parity চেক নিশ্চিতভাবে ধরা পড়ে। এটি কেন সবসময় সত্যি, প্রমাণ করুন।

প্যারিটি বিট গণনা হয় ডেটা বিটগুলোর যোগফলের mod ২ হিসেবে (sum(data_bits) % 2)। ঠিক একটি বিট উল্টালে সেই যোগফল ঠিক ১ বাড়ে বা কমে -- আর যেকোনো সংখ্যার সাথে ১ যোগ/বিয়োগ করলে তার mod ২ মান সবসময় উল্টে যায় (জোড় থেকে বিজোড়, বা উল্টো)। তাই একক-বিট এরর সবসময় প্রত্যাশিত ও প্রাপ্ত প্যারিটির মধ্যে মিসম্যাচ তৈরি করবে -- এটাই গাণিতিকভাবে নিশ্চিত।

প্র ০৩ UART-এ ডেটা বিট পাঠানো হয় LSB আগে (LSB-first)। এটি জানা কেন গুরুত্বপূর্ণ?

যদি কেউ ধরে নেয় বিটগুলো MSB-first পাঠানো হচ্ছে (যেমন I2C-তে, যা পরের পাঠে দেখা যাবে), তাহলে বাইট পুনর্গঠনের সময় সম্পূর্ণ ভুল মান পাবে -- বিট অর্ডার উল্টে গেলে বাইটের মান বদলে যায়। প্রতিটি প্রোটোকলের নিজস্ব বিট-অর্ডার কনভেনশন সঠিকভাবে জানা এমবেডেড ডিবাগিংয়ের একটি বাস্তব প্রয়োজনীয়তা।

অনুশীলন

  1. চিন্তা করুন: কোড সেলে baud_rates লিস্টে 230400 যোগ করলে তার বিট-টাইম কত হবে বলে আপনার হিসাব বলে?

    $bit\_time_{\mu s} = 10^6 / 230400 \approx 4.34\,\mu s$ -- অর্থাৎ ১১৫২০০ baud-এর অর্ধেক সময়, কারণ বড রেট দ্বিগুণ হয়েছে।

  2. পরীক্ষা করুন: কোড সেলে corrupted_frame[3]-এর বদলে corrupted_frame[3] ও corrupted_frame[4] দুটোই উল্টে দিন (দুটো বিট একসাথে করাপ্ট), Run চেপে দেখুন প্যারিটি চেক এখনও এরর ধরতে পারে কিনা।

    অনেক ক্ষেত্রে প্যারিটি চেক পাস করে যাবে (ভুলভাবে) -- কারণ দুটো বিট উল্টালে যোগফলের mod ২ মান অপরিবর্তিত থেকে যেতে পারে। এটাই উপরের নোটে উল্লেখ করা প্যারিটির সীমাবদ্ধতা -- এটি শুধু বিজোড় সংখ্যক বিট-এররই নিশ্চিতভাবে ধরতে পারে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ মাইক্রোপ্রসেসর আর্কিটেকচার, এমবেডেড C, GPIO, টাইমার/PWM/ADC, সিরিয়াল প্রোটোকল, RTOS, সেন্সর/অ্যাকচুয়েটর, IoT আর্কিটেকচার, ওয়্যারলেস প্রোটোকল, MQTT/CoAP ও IoT সিকিউরিটি -- বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • Computer Architecture & Digital Logic কোর্স সহোদর কোর্স সাধারণ বাস আর্কিটেকচারের ভিত্তি সেই কোর্সে -- এই পাঠ তার একধাপ নিচের ডিভাইস-লেভেল সিরিয়াল প্রোটোকলে যায়।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Design and Analysis of Algorithms ও আরও অনেক কোর্স -- সব এক জায়গায়।
আগের পাঠ
ডিজিটাল-টু-অ্যানালগ কনভার্সন (DAC)