Trong phần trước của chuỗi bài giao tiếp enc28j60 chúng ta đã viết các hàm cơ bản để đọc ghi vào các thanh ghi. Phần này chúng ta sẽ tiếp tục hoàn thiện chức năng khởi tạo chip !
Muốn biết làm sao để khởi tạo chip thì các bạn mở datasheet mục 6 người ta có hướng dẫn từng bước 1 luôn nhé !
Các bạn chuyển tới hàm ENC29J600_ini chúng ta đã viết sẵn ngay từ đầu, đầu tiên mình sẽ reset mềm lại module bằng nhón lệnh Opcode = System Command (Soft Reset) (SC) = 0xFF
Các bạn thêm #define cho nó vào file .h nhé
1 |
#define ENC28J60_SOFT_RESET 0xFF |
Trong hàm khởi tạo
1 2 |
UART_putString("Dang khoi tao ENC28J60 ...\r\n"); ENC28J60_write_command(ENC28J60_SOFT_RESET,0x1F,0); //soft reset |
Bởi vì command thuộc nhóm reset nhận data là N/A tức là gì cũng được nên mình sẽ gửi byte 0 cho nó !
Trong datasheet trang 5 có nói, sau khi khởi tạo, nên có 1 khoảng thời gian trễ (khoảng 7500 chu kì máy) và nên kiểm tra bit CLKRDY trong ESTAT. Nếu bit này đã được set bằng 1 thì mọi thứ đã sẵn sàng. Do vậy mình sẽ thêm lệnh kiểm tra bit này nữa !
1 2 |
delay_ms(2); while(!ENC28J60_read_command(ENC28J60_READ_CTRL_REG,ESTAT)&ESTAT_CLKRDY); //cho bit CLKRDY duoc set |
Hãy nhớ thêm định nghĩa cho các bit và thanh ghi ESTAT và file .h nhé !
1 2 3 4 5 6 7 |
// ENC28J60 ESTAT Register Bit Definitions #define ESTAT 0x1D #define ESTAT_INT 0x80 #define ESTAT_LATECOL 0x10 #define ESTAT_RXBUSY 0x04 #define ESTAT_TXABRT 0x02 #define ESTAT_CLKRDY 0x01 |
Ngoài ra, do mình xài delay_ms nên chúng ta cũng cần add thêm thư viện <delay.h> mà phần mềm CodeVison AVR hỗ trợ vào file enc28j60.h
Ở đoạn chương trình trên, sau khi khởi tạo, mình delay 1 khoảng nhỏ rồi liên tục kiểm tra bit ESTAT_CLKRDY bằng vòng lặp while
Sau khi reset xong, để chắc chắn module có hoạt động, mình sẽ kiểm tra thử thanh ghi ERDPTL (ở 0x00 bank0 )trong bảng TABLE 3-2: ENC28J60 CONTROL REGISTER SUMMARY thanh ghi này luôn có giá trị là 0xFA, ta sẽ căn cứ vào nó để kiểm tra module còn hoạt động không !
Các bạn định nghĩa thanh ghi này vào file .h nhé
// Bank 0 registers #define ERDPT (0x00|0x00)
1 2 3 4 5 |
if(ENC28J60_readByte(ERDPT) != 0xFA)ENC28J60_error(); //khoi tao that bai else { } |
Đến thời điểm này, hàm khởi tạo module của mình sẽ như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void ENC29J600_ini(void) { UART_putString("Dang khoi tao ENC28J60 ...\r\n"); ENC28J60_write_command(ENC28J60_SOFT_RESET,0x1F,0); //soft reset delay_ms(2); while(!ENC28J60_read_command(ENC28J60_READ_CTRL_REG,ESTAT)&ESTAT_CLKRDY); //cho bit CLKRDY duoc set if(ENC28J60_readByte(ERDPT) != 0xFA)ENC28J60_error(); //khoi tao that bai else { } } |
Trước khi viết tiếp hàm khởi tạo, chúng ta thử gọi hàm này vào hàm main trước vòng lặp while(1) để test qua tí đã chứ nhỉ 🙂 viết nhiều rồi test thành quả tí đã
Chạy mô phỏng thử khi có kết nối ic ENC28J60 thì sẽ không thấy thông báo thất bại !
Còn khi xóa ic ENC28J60 đi thì dòng khởi tạo thất bại ngay lập tức được in ra !
Rồi ok ! Tiếp tục viết thêm mã khởi tạo trong phần else của hàm khởi tạo nhé !
Các bạn có thể xem mục 6 (INITIALIZATION) trong datasheet để hiểu hơn về cách khởi tạo module nhé !
Mục 6.1 datasheet yêu cầu ta phải khởi tạo cho Receive Buffer qua thanh ghi ERXST và ERXND. Tương tự với Transmission Buffer cũng cần được khởi tạo tại ETXST và ETXND
Thực tế, khi coi bản đó thanh ghi của chip ( TABLE 3-2: ENC28J60 CONTROL REGISTER SUMMARY ) các bạn sẽ không thay cái thanh ghi nào tên ERXST đâu, bởi nó là thanh ghi có độ dài tới 16bit, nên thằng ENC28J60 nó chia ra thành 2 thanh ghi nhỏ 8bit tên là ERXSTL và ERXSTH. Có rất nhiều thanh ghi 16bit như vậy, do đó mình sẽ viết thêm 1 hàm để ghi value 16bit vào 2 cho tiện
1 2 3 4 5 |
static void ENC28J60_writeByte16(uint8_t addres,uint16_t data) { ENC28J60_writeByte(addres, data); ENC28J60_writeByte(addres+1, data>>8); } |
(thêm nguyên mẫu hàm vào file .h nhé)
OK, quay trở lại hàm ENC29J600_ini chúng ta sẽ khởi tạo Receive Buffer (bộ đệm nhận) và ( Transmission Buffer ) như thế nào ?
Ethernet Buffer
Mục 3.2 datasheet
ENC28J60 có 8Kb Ram cho bộ đệm phục vụ truyền nhận các gói tin ! Chúng ta tùy ý cấu hình phần vùng cho miếng nào là nhận, miếng nào là truyền.
Địa chỉ của bộ đệm bắt đầu từ 0x00 đến 0x1FFF
Ở đây mình sẽ chia cho bộ đệm nhận 3K bộ nhớ bắt đầu từ 0x00 đến 0x0BF, còn bộ đệm truyền là 5K bắt đầu từ 0xC00 tới hết (0x1FFF)
Chúng ta #define các thông số sau vào file .h
1 2 3 4 5 6 |
//-------------------------------------------------- #define RXSTART_INIT 0x0000 // start of RX buffer, room for 2 packets #define RXSTOP_INIT 0x0BFF // end of RX buffer //-------------------------------------------------- #define TXSTART_INIT 0x0C00 // start of TX buffer, room for 1 packet #define TXSTOP_INIT 0x11FF // end of TX buffer |
Mình cũng #define địa chỉ của các thanh ghi ERXST ERXND ETXST ETXND
(mình gán địa chỉ của thanh ghi L cho nó)
1 2 3 4 5 6 7 |
#define EWRPT (0x02|0x00) #define ETXST (0x04|0x00) #define ETXND (0x06|0x00) #define ERXST (0x08|0x00) #define ERXND (0x0A|0x00) #define ERXRDPT (0x0C|0x00) #define ERXWRPT (0x0E|0x00) |
Vì các thanh ghi này đều thuộc bank0 nên mình sẽ or thêm 0x00 ( các bạn để cạnh thanh ghi ERDPT lúc nãy đã #define cho tiện theo dõi)
Rồi, bỏ chương trình cấu hình buffer vào chỗ else của hàm ENC29J600_ini nào !
1 2 3 4 5 6 |
//cau hinh kich thuoc bo dem truyen nhan ENC28J60_writeByte16(ERXST,RXSTART_INIT); ENC28J60_writeByte16(ERXND,RXSTOP_INIT); ENC28J60_writeByte16(ETXST,TXSTART_INIT); ENC28J60_writeByte16(ETXND,TXSTOP_INIT); |
Đồng thời mình cũng reset con trỏ của bộ đệm truyền nhận về vị trí Start luôn nhé
1 2 3 |
//reset con tro RX TX ve vi tri start ENC28J60_writeByte16(ERXRDPT,RXSTART_INIT); ENC28J60_writeByte16(ERXWRPT,TXSTART_INIT); |
Tiếp tục
Tiếp tục quá trình khởi tạo, ở mục 6.3 của datasheet , hắn có nói chúng ta nên cấu hình thanh ghi ERXFCON. OK thôi, xem thanh ghi này có gì nào…
Thanh ghi này có chưa các bit điều khiển chức năng lọc các gói tin, đây là chức năng nâng cao, tạm thời mình sẽ không xài đến chức năng này, mình sẽ code nhưng để comment dòng lệnh này đi, chưa cần dùng vội, sau này sẽ tính đến nó sau !
1 2 |
//rx buffer filters //ENC28J60_writeByte(ERXFCON,ERXFCON_UCEN|ERXFCON_ANDOR|ERXFCON_CRCEN); //set 3 bit |
Cũng đừng quên thêm các #define cho thanh ghi này vào file .h nhé
1 2 3 4 5 6 7 8 9 10 |
// ENC28J60 ERXFCON Register Bit Definitions #define ERXFCON (0x18|0x20) //boi vi no thuoc bank1 nen can or them 0x20 #define ERXFCON_UCEN 0x80 #define ERXFCON_ANDOR 0x40 #define ERXFCON_CRCEN 0x20 #define ERXFCON_PMEN 0x10 #define ERXFCON_MPEN 0x08 #define ERXFCON_HTEN 0x04 #define ERXFCON_MCEN 0x02 #define ERXFCON_BCEN 0x01 |
Tiếp tục ở mục 6.4 của datasheet thì mục này chúng ta đã code ngay từ đâu lun rồi
Tiếp tục tới mục 6.5, nó có tên là MAC Initialization Settings tức là cấu hình địa chỉ MAC
Cấu hình địa chỉ MAC
Địa chỉ MAC là cái quái gì ? Nó là địa chỉ vật lí của 1 thiết bị mạng, dùng để phần biệt với các thiết bị khác trong mạng ! Nó là 1 địa chỉ cố định được gán vào vởi nhà sản xuất ( tức chúng ta )
Địa chỉ MAC là một bộ sáu cặp hai ký tự, cách nhau bằng dấu hai chấm. Ví dụ 00:1B:44:11:3A:B7 là một địa chỉ MAC
Địa chỉ MAC khác địa chỉ IP chỗ nào
Địa chỉ IP được sử dụng để truyền dữ liệu từ mạng này sang mạng khác. Địa chỉ MAC được sử dụng để phân phối dữ liệu đến đúng thiết bị trên mạng.
Địa chỉ IP giống như địa chỉ nhà, còn địa chỉ MAC là tên người nhận
ENC28J60 là 1 thiết bị mang, nó cũng cần có 1 địa chỉ MAC, và chính chúng ta là người gán địa chỉ MAC cho nó !
Các bước để cấu hình địa chỉ MAC datasheet 6.5 đã nói rất rõ, các bạn có thể tự đọc thêm nếu muốn nhé:
Đầu tiên là set các bit MARXEN TXPAUS RXPAUS PASSALL trong thanh ghi MACON1. Rồi clear thanh ghi MACON2 .
Rồi set các bit PADCFG0 TXCRCEN FRMLNEN trong thanh ghi MACON3. Rồi set giá trị 0xC12 cho thanh ghi MAIPG.
Thanh ghi MABBIPG cũng nên được set giá trị 0x12 như datasheet khuyên.
Tương tự với thanh ghi MAMXFL cũng nên được cấu hình = 1518 đổ lại
Không lan man nữa, mình sẽ đi vào code luôn, trước tiên phải thêm các #define cho các thanh ghi đã đúng không 🙂
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 |
// Bank 2 registers #define MACON1 (0x00|0x40|0x80) //boi vi la bank 2 nen OR voi 0x40 #define MACON2 (0x01|0x40|0x80) #define MACON3 (0x02|0x40|0x80) #define MACON4 (0x03|0x40|0x80) #define MABBIPG (0x04|0x40|0x80) #define MAIPG (0x06|0x40|0x80) #define MAMXFL (0x0A|0x40|0x80) #define MIREGADR (0x14|0x40|0x80) #define MIWR (0x16|0x40|0x80) // -- cau hinh vi tri cac bit trong MACON1---// #define MACON1_LOOPBK 0x10 #define MACON1_TXPAUS 0x08 #define MACON1_RXPAUS 0x04 #define MACON1_PASSALL 0x02 #define MACON1_MARXEN 0x01 // -- cau hinh vi tri cac bit trong MACON3---// #define MACON3_PADCFG2 0x80 #define MACON3_PADCFG1 0x40 #define MACON3_PADCFG0 0x20 #define MACON3_TXCRCEN 0x10 #define MACON3_PHDRLEN 0x08 #define MACON3_HFRMLEN 0x04 #define MACON3_FRMLNEN 0x02 #define MACON3_FULDPX 0x01 |
Nếu để ý các bạn sẽ thấy các thanh ghi ở bank2 mình còn OR thêm 0x80, để làm gì lát mình sẽ nói
1 2 3 4 5 6 7 |
//cau hinh MAC ENC28J60_writeByte(MACON1,MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS); ENC28J60_writeByte(MACON2,0x00); ENC28J60_write_command(ENC28J60_BIT_FIELD_SET,MACON3,MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN); ENC28J60_writeByte16(MAIPG,0x0C12); ENC28J60_writeByte(MABBIPG,0x12); ENC28J60_writeByte(MAMXFL,1500); |
Điền địa chỉ MAC
Chúng ta sẽ điền địa chỉ MAC cho module vào 6 thanh ghi từ MMADR0 đến MMADR5
Tiếp tục #define 5 địa chỉ này vào file .h nhé, ngoài ra mình cũng #define thêm địa chỉ MAC của module vào
1 2 3 4 5 6 7 8 |
// Bank 3 registers #define MAADR1 (0x00|0x60|0x80) #define MAADR0 (0x01|0x60|0x80) #define MAADR3 (0x02|0x60|0x80) #define MAADR2 (0x03|0x60|0x80) #define MAADR5 (0x04|0x60|0x80) #define MAADR4 (0x05|0x60|0x80) #define MISTAT (0x0A|0x60|0x80) |
Nếu để ý các bạn sẽ thấy các thanh ghi ở bank3 mình còn OR thêm 0x80, để làm gì lát mình sẽ nói
OK, giờ mình sẽ thêm code để điền mac vào thanh ghi chứa MAC nhé
Trước hàm ENC29J600_ini mình khai bảo thêm 1 mảng macaddr[6] để lưu địa chỉ MAC mà mình thiết lập cho chip !
1 |
uint8_t macaddr [6] = {0x00,0x00,0x20,0x07,0x19,0x98}; |
Tiếp tục code khởi tạo
1 2 3 4 5 6 |
ENC28J60_writeByte(MAADR5,macaddr[0]); //ghi dia chi MAC vao chip ENC28J60_writeByte(MAADR4,macaddr[1]); ENC28J60_writeByte(MAADR3,macaddr[2]); ENC28J60_writeByte(MAADR2,macaddr[3]); ENC28J60_writeByte(MAADR1,macaddr[4]); ENC28J60_writeByte(MAADR0,macaddr[5]); |
Để chắc chắn đã ghi MAC vào đúng, mình thử đọc ra xem thế nào nhé !
Mình sẽ khởi tạo thêm mảng mymac[6] để lưu địa chỉ mac đọc ra ! Đồng thời, khởi tạo thêm mảng debug_string[60] để phục vụ việc in ra màn serial debug
1 2 |
uint8_t mymac[6]; uint8_t debug_string[60] |
Tiếp tục thêm code vô hàm khởi tạo
Sửa lại hàm ENC28J60_read_command
Chúng ta sẽ đọc địa chỉ MAC đã set bằng hàm ENC28J60_read_command, tuy nhiên hãy nhìn lại mục 4.2.1 trong datasheet
Khi đọc các thanh ghi liên quan đến MAC và MII thì byte đầu tiên đọc ra sẽ là byte dummy (byte rác), byte thứ hai mới đúng là data ta cần. Do vậy, hàm ENC28J60_read_command mình sẽ sửa lại như sau:
1 2 3 4 5 6 7 8 9 10 |
static uint8_t ENC28J60_read_command(uint8_t op,uint8_t addres) { uint8_t result; SS_SELECT(); SPI_SendByte(op|(addres&0x1F)); if(addres & 0x80) SPI_ReceiveByte(); result=SPI_ReceiveByte(); SS_DESELECT(); return result; } |
Mình đã thêm vào dòng lệnh kiểm tra bit cao nhất của byte địa chỉ có được set không, nếu được set thì sẽ gọi hàm đọc nhưng không lấy kết quả vì đó là byte rác, lần đọc thứ 2 mình mới lấy ! Đó là lí do vì sao khi #define các byte địa chỉ có kiên quan đến MAC ở trên mình có OR nó thêm với 0x80
Do các byte địa chỉ chỉ xài có 5bit thấp, 2 bit tiếp dùng để xác định bank, nên bit cuối (bit cao nhất) sẽ dùng để xác định byte này có thuộc loại MAC hay MII không để tránh byte rác (không hiểu mấy ông design con chip này tính kiểu gì luôn 🙁 )
Rồi, giờ đọc địa chỉ MAC đã ghi xem có đúng với ban đầu không !
1 2 3 4 5 6 7 8 9 10 |
//read MAC mymac[0]=ENC28J60_readByte(MAADR5); mymac[1]=ENC28J60_readByte(MAADR4); mymac[2]=ENC28J60_readByte(MAADR3); mymac[3]=ENC28J60_readByte(MAADR2); mymac[4]=ENC28J60_readByte(MAADR1); mymac[5]=ENC28J60_readByte(MAADR0); sprintf(debug_string,"\r\nYour MAC addr = %02X:%02X:%02X:%02X:%02X:%02X\r\n",mymac[0],mymac[1],mymac[2],mymac[3],mymac[4],mymac[5]); UART_putString(debug_string); |
Kết quả : chúng ta đã ghi thành công địa chỉ MAC
Như vậy, qua trình khởi tạo chip cơ bản là đã xong 70% !
Trong bài tiếp, chúng ta sẽ tiếp tục viết các hàm để làm việc với các thanh ghi thuộc lớp vật lí và hoàn thành hàm khởi tạo chip !
Download
Các bạn tải source cho bài này tại đây
Bài tiếp theo ! của chuỗi bài giao tiếp enc28j60
Em chào a ạ! Anh cho e hỏi “ENC28J60_readByte(ERDPT) != 0xFA” nếu debug chạy từng dòng lệnh thì ra đúng 0xFA còn “run” thì không ra ạ? (em dùng stm32f103cbt6)