خب پایه کار ارتباط با ENC گذاشته شده. حالا فقط نیاز هست ENC رو در ابتدای کار با مقادیر مناسب راهاندازی (Initialize) کنیم:
ENC28J60_Init
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | void ENC28J60_Init(void) { HAL_Delay(500); HAL_GPIO_WritePin(ENC28J60_RESET_PORT, ENC28J60_RESET_PIN, GPIO_PIN_RESET); HAL_Delay(50); HAL_GPIO_WritePin(ENC28J60_RESET_PORT, ENC28J60_RESET_PIN, GPIO_PIN_SET); HAL_Delay(500); SystemReset();//software reset HAL_Delay(500); // Rx/Tx buffers WriteControlRegPair(ERXSTL, ENC28J60_RX_BUF_START); WriteControlRegPair(ERXNDL, ENC28J60_RX_BUF_END); WriteControlRegPair(ERDPTL, ENC28J60_RX_BUF_START); // MAC address WriteControlReg(MAADR1, macAddr[0]); WriteControlReg(MAADR2, macAddr[1]); WriteControlReg(MAADR3, macAddr[2]); WriteControlReg(MAADR4, macAddr[3]); WriteControlReg(MAADR5, macAddr[4]); WriteControlReg(MAADR6, macAddr[5]); WriteControlReg(MACON1, MACON1_TXPAUS_BIT | MACON1_RXPAUS_BIT | MACON1_MARXEN_BIT); WriteControlReg(MACON3, MACON3_PADCFG0_BIT | MACON3_TXCRCEN_BIT | MACON3_FRMLNEN_BIT); WriteControlRegPair(MAIPGL, ENC28J60_NBB_PACKET_GAP); WriteControlReg(MABBIPG, ENC28J60_BB_PACKET_GAP); WriteControlRegPair(MAMXFLL, ENC28J60_FRAME_DATA_MAX); // PHY resisters WritePhyReg(PHCON2, PHCON2_HDLDIS_BIT); ENC28J60_StartReceiving(); } |
حالا ENC آماده ست که فریم های اصلی اترنت رو دریافت یا ارسال کنه.
قبل از هر چیز بریم ببینیم، در يك فریم اترنت چی اطلاعاتي قرار داشت. قبلاً هم گفتیم که یک فریم اترنت در استاندارد Ethernet ii شامل 7 بایت (octet) ثابت با مقدار 0x55؛ یک بایت ثابت با مقدار 0xD5 بهعنوان شروع فریم یا SFD (اين دو قسمت، جزو بخش اصلي فريم نيستند). سپس 12 بایت شامل مک آدرس مقصد و منبع (گیرنده و فرستنده)؛ دوبایت بهعنوان نوع فریم که شامل عددی بزرگتر از 0x0600 هستند و ما با دوتاش کار خواهیم داشت: 0x0800 برای پروتکل Ipv4 و 0x0806 برای پروتکل ARP.
بعد از اون دادههای اصلی قرار میگیرند. در ادامه 4 بایت CRC بهعنوان پایان فریم یا FSC. همچنین به فاصله 12 بایت روی خط با عنوان IPG فاصله یا سکوت برقرار ميشه. IPG هم جزو بخشهای اصلي فريم بهحساب نمياد.
اگه خاطرتون باشه گفتیم در استاندارد IEEE 802.3 در دو بایت Ethe Type، اندازه داده مشخص میشه؛ لذا ما میتونیم بفهمیم تعداد دادهها چنتاست؛ CRC کجا قرار داره و انتهای فریم کجاست. اما در استاندارد Ethernet ii که ما باهاش سروکار داریم، تعداد دادهها مشخص نیست، پس از کجا متوجه میشیم (یا بهتره بگیم ENC متوجه میشه) که انتهای فریم کجاست؟ جواب اینه: در ارتباط 10Mb از محل یا وقوع 12 بایت توقف (IPG) و در ارتباط 100Mb از وقوع سیگنالی که به نام /T/R/ شناخته میشه.
در واقع ENC؛ دادهها رو میخونه تا برسه به IPG؛ سپس این دادهها رو بافر میکنه. البته شروطی هم چک میشه که عموماً ما براش تعیین میکنیم؛ مثل بررسی CRC و آدرس صحیح (آیا فریم برای ما ارسال شده یا نه؟). دادههای اضافی مثل preamble,SFD و CRC جدا میشه و قسمت اصلی فریم؛ شامل آدرسهای مقصد و منبع، نوع فریم؛ payload و CRC بافر میشه (درون حافظه ذخیره میشه) تا بعد توسط میکروكنترلر اون ها رو بخونیم و پردازش کنیم.
ENC دارای یه حافظه به اندازه 8KB هست که ازش برای دریافت و ارسال فریمها استفاده میکنه. در واقع این حافظه بین دو بخش ارسال و دریافت تقسیم میشه.
ما تعیین میکنیم که چه مقدار حافظه برای بخش ارسال و چه مقدار براي دریافت، قرار داده بشه. بهاینترتیب که ابتدا و انتهای حافظه دریافت رو مشخص میکنیم. هرچی باقي بمونه؛ بهعنوان حافظه ارسال استفاده میشه.
از اونجایی که حداکثر اندازه فریم ارسالی 1518 بایت هست؛ از طرفی نیاز هست براي یه مقدار اطلاعات اضافه در ابتدا و انتهای فریمها؛ فضا وجود داشته باشه (ديتاشيت رو ببينيد) ما 1536=0x600 بایت برای حافظه ارسال در نظر میگیریم؛ لذا بقیه حافظه برای دریافت اختصاص داده میشه که مقدار مناسبی هست و چندین فریم، قبل از پردازش توسط ما (میکروکنترلر) میتونه در این حافظه ذخیره بشه.
این حافظه بهصورت یک صف چرخشی (circular queue) مصرف میشه. بخوام ساده بگم؛ پکت ها به ترتیب دریافت؛ داخل حافظه (البته با تمهیداتی که داخل دیتاشیت گفته شده) ذخیره میشوند. اگر به انتهای حافظه برسیم؛ برمیگردیم از ابتدای حافظه شروع میکنیم. فقط در برنامه مون (داخل میکروکنترلر) باید حواسمون باشه که تعداد فریمهای رسیده؛ اونقدر زیاد نشه که حافظهای برای دریافت فریمهای جدید باقی نمونه. اگر نمیدونید صف چرخشی چیه به کتابهای “ساختمان دادهها” رجوع کنید.
بعد از تعیین تکلیف وضعیت حافظه بافر؛ بریم سر وقت ارسال فریم. هنگام ارسال فریم؛ یک بایت کنترلی در ابتدای فریم و هفت بایت تعیین وضعیت در انتهای فریم، اضافه میشه؛ بعلاوه برای CRC هم با وجود اینکه توسط ENC محاسبه میشه، باید 4 بایت فضا در نظر بگیریم. یک فریم ارسالی ظاهری شبیه به شکل 13 خواهد داشت.
ساختار بافر ارسال
تعريف بایت کنترلی رو هم که از ديتاشيت ENC برداشتیم در شكل زیر ميبينيد:
بایت کنترلی ارسال
در مورد padding هم توضیح دادیم و اما بیت PCRCEN؛ اگر خودتون میخواید که CRC رو حساب کنید؛ کافیه اون رو در انتهای فریم اضافه کنید و به ENC بگید که اینکار توسط شما انجام شده و 4 بایت انتهایی در بخش
دادهها؛ CRC هست. اما اگر قراره ENC این کار رو انجام بده؛ با این بیت به ENC اجازه یا دستور محاسبه رو میدیم، CRC محاسبه شده در 4 بایت انتهایی قرار میگیره و ما فقط باید به اندازه 4 بایت براش جا در نظر بگیریم (ما از این روش استفاده کردیم)
در بیت POVERRIDE هم میگیم که آیا از تنظیمات داخل رجیستر MACON3 استفاده بشه یا از بیتهای معرفی شده که ما از این وضعیت استفاده میکنیم. کد مورد نظر داخل تابع Initialize بهصورت زیر نوشته شده:
1 | WriteControlReg(MACON3, MACON3_PADCFG0_BIT | MACON3_TXCRCEN_BIT | MACON3_FRMLNEN_BIT); |
4 ثبات 16 بیتی با نام های زیر، برای تعیین ابتدا و انتهای بافرهای ارسال/دریافت در بخش رجیسترهای کنترلی ENC موجود هست:
دو ثبات آخر برای تعیین محدوده بافر دریافت استفاده میشن و مابقی حافظه به طور پیشفرض، محدوده بافر ارسال (تعریف شده در دو ثبات 16 بیتی اول) رو مشخص میکنند.
چون به اندازه تقریبی یک فریم در حافظه ارسالی فضا اختصاص دادیم؛ پس در هر لحظه از زمان؛ فقط یک فریم در حال ارسال خواهد بود. از طرفی قرار هست این برنامه روی میکروکنترلر پیادهسازی بشه.
از اونجایی که یکی از اصلیترین محدودیتها در میکروکنترلرها؛ کمبودن میزان حافظه دیتا (RAM) در اونهاست؛ پس ما فقط یک بخش از حافظه RAM رو برای دریافت، پردازش و در صورت نیاز ارسال پاسخ (با همون حافظه) درون میکروکنترلر اختصاص میدیم. در واقع صبر میکنیم تا یک فریم برسه؛ اون رو بررسی و پردازش میکنیم و چنانچه نیاز به پاسخ داشته باشه؛ دادهها رو در همون قسمت از RAM میکروکنترلر مینویسیم و بعد ارسال میکنیم به ENC تا روی خط ارسال بشه.
برای ارسال یک فریم، این عملیات انجام میشه:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | Void ENC28J60_TransmitFrame(uint8_t *data, uint16_t size) { while((ReadControlReg(ECON1) & ECON1_TXRTS_BIT) != 0) { if((ReadControlReg(EIR) & EIR_TXERIF_BIT) != 0) { BitFieldSet(ECON1, ECON1_TXRST_BIT); BitFieldClear(ECON1, ECON1_TXRST_BIT); } } WriteControlRegPair(EWRPTL, ENC28J60_TX_BUF_START); uint8_t controlByte = 0x00; WriteBufferMem(&controlByte, 1); WriteBufferMem(data, size); WriteControlRegPair(ETXSTL, ENC28J60_TX_BUF_START); WriteControlRegPair(ETXNDL, ENC28J60_TX_BUF_START + size); BitFieldSet(ECON1, ECON1_TXRTS_BIT); } |
این نکته هم قابلتوجه هست که چنانچه هنگام ارسال پکت قبلی خطایی رخ داده باشه، سختافزار نمیتونه بیت TXRTS رو پاک کنه؛ لذا ما همواره بیت TXERIF از ثبات EIR رو چک میکنیم تا اگر خطایی رخ داده باشه، طبق گفته دیتاشیت، یکبار بیت TXRST رو ست و ریست کنیم تا قادر به ارسال پکت جدید باشیم. لازم به ذکره که با بررسی بایتهای کنترلی در انتهای فریم قبل میشه به نوع خطا پی ببریم و روال تصحیح اون مثل ارسال دوباره فریم قبلی رو انجام بدیم. تفسیر مقادیر بایتهای کنترلی TSV در انتهای فریم ارسال در شکل 15 نمایشدادهشده است. دو بایت ابتدایی نشاندهنده تعداد بایتهای ارسالی ست.
ما قبلاً محدوده بافر دریافت رو مشخص کردهایم، همچنین باید بگیم که ENC قابلیت فیلتر فریمهای دریافتی رو با استفاده از ثبات ERXFCON رو در اختیار ما قرار داده:
این فیلترها قابل ترکیب منطقی با هم هستند. برای مطالعه کامل این فیلترها به دیتاشیت مراجعه کنید.
بعد از تنظیمات اولیه (از جمله فیلترها) برای فعالسازی بخش دریافت کافیه بیت RXEN از ثبات ECON1 ست بشه که اون رو هم بهصورت یه تابع پیادهسازی کردیم.
1 2 3 4 5 6 7 | Void ENC28J60_StartReceiving() { BitFieldSet(ECON1, ECON1_RXEN_BIT); } |
فریمهای دریافتی بهصورت شکل 16 در بافر دریافت قرار میگیرند. ابتدا دو بایت برای اشاره به آدرس شروع فریم دریافتی بعدی، 4 بایت برای بررسی وضعیت فریم، سپس داده های دریافتی فریم فعلی شامل مک آدرس ها و دو بایت نوع اترنت (ترکیب فریم استاندارد)؛ داده های اصلی و در انتها نیز 4 بایت CRC قرار می گیرند.
چنانچه تعداد کل بایت ها فرد باشد؛ یک بایت در انتها خالی گذاشته میشود تا شروع فریم بعدی روی آدرس زوج باشد. اطلاعاتیکه درون 4 بایت وضعیت دریافت قرار می گیرند، به صورت شکل 17 تفسیر میشوند. همانطور که در شکل 17 مشخص شده است، دو بایت کم ارزش نشاندهنده تعداد بایت های دریافتی ست.
نحوه ثبت فریم های دریافتی در بافر
ثبات وضعیت فریم دریافتی
همونطور که قبلاً گفتیم هسته برنامه ما (با فرض کمبود حافظه RAM) اینطور عمل میکنه که از همون حافظه اختصاصیافته برای دریافت فریمها (درون میکروکنترلر و نه ENC) برای تهیه و ارسال پاسخ ارسال میشه؛ به همین دلیل هست که ما نوع ارتباط رو HalfDouplex در نظر گرفتیم. یک فریم رو دریافت و پردازش میکنیم؛ و چنانچه نیاز به پاسخ باشه؛ پاسخ رو در همون حافظه میچینیم و بعد به بافر ارسال ENC منتقل و دستور ارسال فريم رو صادر میکنیم.
بعد از دریافت هر فریم، اونها رو درون یک ساختار (structure) قرار میدیم. تعریف این استراکچر در زبان C همچین چیزیه:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | typedef struct ENC28J60_Frame { uint16_t nextPtr; uint16_t length; uint16_t status; uint8_t data[ENC28J60_FRAME_DATA_MAX]; uint32_t checksum; } ENC28J60_Frame; |
و تابع دریافت هم به صورت زیر هست:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | uint16_t ENC28J60_ReceiveFrame(ENC28J60_Frame* frame) { uint16_t dataSize = 0; uint8_t packetsNum = ReadControlReg(EPKTCNT); if (packetsNum > 0) { WriteControlRegPair(ERDPTL, curPtr); ReadBufferMem((uint8_t*)frame, ENC28J60_HEADER_SIZE); curPtr = frame->nextPtr; if ((frame->status & ENC28J60_FRAME_RX_OK_MASK) != 0) { dataSize = frame->length – ENC28J60_CRC_SIZE; if (dataSize > ENC28J60_FRAME_DATA_MAX) { dataSize = ENC28J60_FRAME_DATA_MAX; } ReadBufferMem((uint8_t*)&(frame->data[0]), dataSize); ReadBufferMem((uint8_t*)&(frame->hecksum), ENC28J60_CRC_SIZE); } uint16_t nextPtr = frame->nextPtr – 1; if (nextPtr > ENC28J60_RX_BUF_END) { nextPtr = ENC28J60_RX_BUF_END; } WriteControlRegPair(ERXRDPTL, nextPtr); BitFieldSet(ECON2, ECON2_PKTDEC_BIT); } return dataSize; } |
ابتدا توسط رجیستر EPKTCNT بررسی میکنیم که آیا فریمی برای خواندن درون ENC هست یا نه؟ چنانچه شمارنده تعداد فریمها، بزرگتر از صفر باشه؛ با استفاده از اشارهگر به پکت فعلی دادهها، کارهای زیر رو انجام میدیم:
در قدم اول 6 بایت شامل 2 بایت اشاره گر به فریم بعدی و 4 بایت وضعیت رو میخونیم. یادمون هم نمیره اشاره گر به فریم بعدی رو هم ذخیره کنیم که بتونیم فریم بعدی رو بخونیم. اگر فریم دریافت شده، بدون خطا باشه (بیت 23 از بایت های وضعیت رو ببینید) با توجه به تعداد بایت های دریافتی در این فریم (دو بايت اول در بايت هاي وضعيت)، بایت های اصلی رو میخونیم.
کار ما اینجا با ENC تموم میشه. این نکته بسیار مهم که قبلاً هم اشاره کردیم؛ مجدد یادآوری میکنیم. شما هر مداری با هر نوع سختافزار و هر کدی داشته باشید؛ در نهایت باید دو تابع داشته باشید که با اون ها بتونید فریمهای موجود در شبکه رو بخونید و یا فریمهای خودتون رو ارسال کنید، مابقی اجراي (Implementation) شبکه در اختیار نرمافزار میکروکنترلر شماست.
نویسنده شو !
سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.