[ENC28J60] Bài 2: Khởi tạo module enc28j60 – giao tiếp ENC28J60

ENC28j60

Theo dõi toàn bộ tutorial hướng dẫn giao tiếp với enc28j60 tại đây

Hôm nay chúng ta sẽ bước đầu làm quen với các hàm khởi tạo, thiết lập module ENC28J60 để giao tiếp với enc28j60

Do khối lượng chương trình của chúng ta khá là đồ sộ nên việc viết hết code vào trong 1 file main không phải là ý kiến hay. Do vậy, ngay từ bây giờ mình sẽ chia file ra thành các phần nhỏ hơn để tiện cho việc viết code và chỉnh sửa, nâng cấp !

Mình sẽ tạo 2 file tên là enc28j60.henc28j60.c

File enc28j60.h sẽ là nơi khai báo các thư viện, các định nghĩa (#define) và khai báo nguyên mẫu hàm. Còn file enc28j60.c sẽ chứa nội dung của code

Các bạn vào File -> New, tích vào source để tạo file và lưu cùng cấp với chỗ để file uart.c uart.h luôn nhé !

Tạo xong thì add file enc28j60.c vào project bằng cách vào Project -> Configure rồi làm như cách mà các bạn đã add file uart.cbài 1 ấy !

Ở file enc28j60.h mình sẽ thêm đoạn chương trình khai báo các thư viện cho file này !

Do mình kết nối chân CS của ENC29J60 với chân B2 nên mình khai báo ở trong file này luôn !
Lệnh SS_SELECT() có tác dụng đưa chân B2 xuống 0 để enable giao tiếp SPI, còn SS_DESELECT() kéo chân B2 lên 1 để kết thúc quá trình giao tiếp ( nhường đường SPI cho thiết bị khác)

Tiếp đến, mình mở file enc28j60.c để bắt đầu viết các hàm giao tiếp với chip ENC28J60. Chúng ta sẽ bắt đầu với hàm enc28j60_ini để khởi tạo chip. Mình cũng viết sẵn thêm hàm ENC28J60_error có nhiệm vụ in thông báo lỗi ra màn uart debug nếu việc khởi tạo thất bại !

Các bạn nhớ khai báo nguyên mẫu hàm của các hàm trong file .c vào file .h nhé !

Viết chương trình khởi tạo chip enc28j60

Hoạt động của chíp ENC28J60 phụ thuộc hoàn toàn vào các lệnh mà vi điều khiển đưa vào các thanh ghi điều khiển thông qua giao tiếp SPI. Các lệnh này có thể là 1 hoặc nhiều byte, mình sẽ gọi nó là các command. Mỗi command sẽ có 1 địa chỉ riêng và 1 value chứa các bit setup cho nó !

Cấu trúc địa chỉ của các command gồm: 3 bit Opcode + 5 bit Argument (tức 5bit địa chỉ) theo sau đó là 1 hoặc nhiều byte dữ liệu

Các command này có thể chia thành 7 nhóm theo bảng sau:

Như vậy 3 bit Opcode quyết định lệnh bạn gửi tới thuộc nhóm lệnh nào.

Hãy xem cách 1 command được gửi tới chip

Việc đọc dữ liệu trong thanh ghi cũng tương tự

Viết chương trình đọc ghi cơ bản

Trước tiên mình sẽ viết 2 hàm phục vụ cho giao tiếp SPI ở file enc28j60.c đã

Hàm spi là hàm trong thư viện <spi.h> mà phần mềm CodeVision AVR đã hỗ trợ chúng ta, chỉ việc lấy sai thui !

Hàm SPI_SendByte có nhiệm vụ gửi 1 byte data ra cổng SPI, còn hàm SPI_ReceiveByte dùng để đọc 1 byte data từ cổng SPI

Ở table 4.1, các bạn có thể thấy có tới 3 nhóm command có Opcode + Argument (5 bit Addres ) cố định, đó là RBM=0x3A dùng để đọc Buffer, WBM=0x7A dùng để ghi Buffer và SC=0xFF dùng reset chip


Tại file enc28j60.c mình sẽ viết 1 hàm để ghi 1 command = Opcode + Argument (nhớ thêm nguyên mẫu hàm vào file .h)

Như đã nói ở trên, 1 byte command gửi tới được gép với 3 bit Opcode và 5 bit Addres, do vậy đoạn lệnh op|(addres&0x1F) sẽ có nhiệm vụ gép op addres vào thanh 1 byte, tuy nhiên, trước khi ghép bằng lệnh OR. Mình sẽ cho addres&0x1F để xóa 3bit cao nhất về 0 đã (dành chỗ cho 3 bit Opcode)
0x1F = 0b0001.1111

Hàm ghi đã xong, giờ viết tiếp hàm đọc (nhớ thêm nguyên mẫu hàm vào file .h)

Thiết lập bank

Tuy nhiên, hay nhìn vào bản đồ của các thanh ghi điều khiển

Cùng 1 địa chỉ có tới 4 loại command, để phân biệt các command có cùng địa chỉ, người ta thêm vào cái dở hơi gọi là bank, có 4 bank BANK0 BANK1 BANK2 BANK3. Trước thao tác đọc ghi command, chúng ta bắt buộc phải thông báo cho chip biết command này thuộc bank nào ! Thanh ghi chịu trách nhiệm lưu vị trí bank là ECON1 có địa chỉ là 0x1F. Cấu trúc của ECON1 như sau:

Như vậy chỉ có 2 bit thấp nhất BSEL1 và BSEL0 quyết định việc chọn bank. Mình sẽ định nghĩa thanh ghi ECON1 và vị trí trọng số của tất cả các bit trong thanh ghi này vào file .h luôn

Thiết kế chương trình chọn bank

Do có tới 4 command có cùng 1 địa chỉ, ví dụ:
ERDPTL và EHT0 và MACON1 và MAADR1 đều có địa chỉ là 0x00

Bản thân chúng ta khi lập trình phải có thêm thông tin để phân biệt địa chỉ này là của bank nào, các bạn hãy để ý table 3-1 giải địa chỉ của các command chỉ bắt đầu từ 0x00 – > 0x1F là hết. Tức là chỉ cần 5bit đầu tiên (từ bit0 đến bit 4) để lưu địa chỉ là đủ rồi, chúng ta sẽ sử dụng bit5 và bit6 để lưu bank của command đó.

Tức là với bank 0 mình sẽ OR với 0x00, với bank1 mình OR thêm 0x20, với bank2 thì OR thêm 0x40, với bank3 thì OR thêm 0x60

Ví dụ: ERDPTL có địa chỉ 0x00 thuộc bank 0 nên địa chỉ có nó sẽ là 0x00
EHT0 thuộc bank 1 nên địa chỉ của nó sẽ là 0x00|0x20
MACON1 thuộc bank 2 nên địa chỉ của nó sẽ là 0x00|0x40
MAADR1 thuộc bank3 nên địa chỉ của nó là 0x00|0x60

Bây giờ khi ghi 1 command, mình sẽ tách ra để lấy 2 bit chưa bank tại vị trí bit6 và bit5, nhưng khi truyền ra cho ENC28J60 thì 2 bit này phải ở vị trí bit1 và bit0 nên mình sẽ dịch sang phải 5 phát cho nó đúng vị trí

Nhưng mà có tận 4 thanh ghi ECON1 trong 4 bank

Chúng ta sẽ sử dụng nhóm lệnh BFS ( Bit Field Set ) và BFC ( Bit Field Clear ) để thao tác 1 phát lên cả 4 thanh ghi ECON1 trên cả 4 bank.
Với Bit Field Set có Opcode là 0x80 dùng để set bit lên 1
và Bit Field Clear có Opcode là 0xA0 dùng để xóa bit về 0

Trước khi code cho hàm setbank mình sẽ #define các Opcode vào file .h để nhìn cho trực quan thay vì gõ thẳng mã hex nhìn không chuyên nghiệp 🙂

Các bước để ghi bank như sau:

  1. Xóa dữ liệu của BSEL1 và BSEL0 trong ECON1 bằng nhóm lệnh có Opcode là Bit Field Clear (BFC)
  2. Set dữ liệu bank mới của BSEL1 và BSEL0 vào các thanh ghi ECON1 bằng nhóm lệnh có Opcode là Bit Field Set (BFS)

Ở đây, mình có khởi tạo thêm biến Enc28j60Bank để lưu bank đã cài lại, như vậy lần sau chỉ cần kiểm tra nếu đã trùng bank rồi thì thôi không cần set lại nữa ! ( nhớ thêm nguyên mẫu hàm vào file .h)

Từ giờ khi #define địa chỉ của các thanh ghi hãy nhớ OR thêm bank tương ứng của nó vào nhé !

Viết các hàm điều khiển các thanh ghi thông thường

Để đọc ghi các thanh ghi thông thường mình sẽ sử dụng Opcode là Read Control Register=0x00 (RCR) và Write Control Register=0x40 ( WCR), do đó mình tiếp tục tạo #define cho 2 thằng này bên file .h nhé !

Và đây là 2 hàm để đọc ghi vào các thanh ghi điều khiển

Trong bài tiếp , chúng ta sẽ tiếp tục hoàn thành chức năng khởi tạo ENC29J60, chứ viết bài dài quá các bạn chán không buồn đọc 🙁

Download

Toàn bộ source code cho bài này các bạn tải ở đây

Bài tiếp theo trong chuỗi bài hướng dẫn giao tiếp với enc28j60

 

Từ tác giả:

Nếu có bất kì thắc mắc nào trong bài viết, vui lòng để lại comment dưới mỗi bài ! Mình sẽ không trả lời thắc mắc của các bạn ở facebook hay email !

Nếu trong phần code bạn nhìn thấy nhưng thứ kiểu như &amp; thì đó là lỗi hiển thị, cụ thể 3 kí tự < > & bị biến đổi thành như thế
&amp; là &
&lt;  là <
&gt; là >

Giới thiệu Đào Nguyện 80 bài viết
DIY,chế cháo, viết blog chia sẽ kiến thức về lập trình,điện tử - IoT. Rất mong được giao lưu, kết bạn với các bạn cùng đam mê. Địa chỉ Facebook: https://www.facebook.com/nguyendao207

11 bình luận

  1. Cho mình hỏi hàm spi trong thư viện spi.h mà bạn dùng có tác dụng gì vậy?
    Tại sao trong hàm receive_byte giá trị bên trong lại là spi(0xFF) ?

  2. 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;
    }
    Tôi đã đọc mã nguồn của bài này, có đoạn này tôi không hiểu. Trong mã nguồn đã tải về và code hiển thị trên web thì khác nhau chỗ lệnh if(addres&0x80) SPI_ReceiveByte();
    Làm ơn giải thích giùm tôi tại sao phải and với 0x80?

  3. mình dùng Stm32 gửi các lệnh đến ENC28j60 đều ko thấy phản hồi gì từ module, đo tín hiệu rồi so sánh với datasheet thấy giống mà vẫn ko có bất kỳ phản hồi nào

    đoạn code test của mình như sau:
    uint8_t Rx_Buffer[2];
    uint8_t Tx_Buffer1[2] = {0x1E,0xFF}; // 0001 1110 doc gia thanh ghi ECON2
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET );
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET ); // bat dau doc
    HAL_SPI_TransmitReceive (&hspi2 ,&Tx_Buffer1,&Rx_Buffer,2,100);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET );

  4. tôi có mua module ENC28 nhưng gửi lệnh SPi mà chân SO của chip luôn ở mức cao
    Bác có recommend lỗi gì có thể xảy ra để tôi khắc phục ko? (phần cứng, phần mềm, timing… chẳng hạn)

Đã đóng bình luận.