Here you can find a step-by-step tutorial about the Bascom-AVR code to drive an AVR
and an ENC28J60 ethernetcontroller.



There has been een problem with the newer Bascom-AVR-version,
from 1.11.90 on. But it has been solved.

This message was posted in the www.mcselec.com Bascom-AVR forum:

I have left the tutorial untouched, so you will have to make to two corrections in the
code on your own.

This tutorial is based on the hardware from www.tuxgraphics.org

Step 1. The first steps.....

You can cut and paste all green parts of the next text and place it into the Bascom-AVR IDE.

The board from www.tuxgraphics.org


The Atmega168 microcontroller that is used on the above board has some fusebits. Care should be taken while programming these fusebits.

First, start with a small description what you are planning to do with date and/or version-number.

'-----------------------------------------------------------
' Atmega168 and ENC28J60
'-----------------------------------------------------------
' Version 1.0 - june 2007

Make a small note what you did with the fusebits of the microcontroller
For this program the following fusebits should be set:

We are going to use the CLKOUT of the ENC28J60. On power-up the clockfrequency is 6.25 Mhz (1/4 of the ENC28J60 main clock), but by changing the settings of the ENC28J60 we will be using 12.5 Mhz.

At the Bascom-AVR options put Hardware stack, Soft stack and Framesize all at 64 Bytes.




$crystal = 6250000
$regfile = "M168def.dat"

You can also use these commands to override the Bascom-AVR-options

$hwstack = 64
$swstack = 64
$framesize = 64
 

$crystal is used to give the Mhz or MegaCycles you are using. In this case a external clock of 6.25 Mhz is used.
$regfile is used to have Bascom-AVR use the right registers for the right microcontroller. In this case we are using a Atmega168.
 

Step 2: Preparing the SPI-port and Chip-select-pin

The next piece of code is to configure two output pins. PORTB.2 is the chipselect of the ENC28J60, PORTB.1 is the LED.

Config Portb.1 = Output
Config Portb.2 = Output

Enc28j60_cs Alias Portb.2

To make your code a bit more readable, you can use aliases. Here I give Portb.2 the name ENC28J60_cs and throughout the program I can refer to this alias.

The ENC28J60 is a stand-alone Ethernet controller with an industry standard Serial Peripheral Interface (SPI). It is designed to serve as an Ethernet network interface for any controller equipped with SPI. With a single Config SPI command Bascom-AVR configures the complete SPI-bus. See the Help for Bascom-AVR for every single parameter of the Config SPI command.

'Configuration of the SPI-bus
Config Spi = Hard , Interrupt = Off , Data Order = Msb , Master = Yes , Polarity = Low , Phase = 0 , Clockrate = 4 , Noss = 0

'init the spi pins
Spiinit

 

This is all you need to communicate with the ENC28J60 ethernet chip.

Step 3: Different kinds of registers....

 

From the datasheet: Memory organization.

The ENC28J60 consists of Control Registers which are used to control and monitor the ENC28J60. The Control registers memory contains the registers that are used for configuration, control and status retrieval of the ENC28J60. The Control registers are directly read and written to by the SPI interface.

In this tutorial we will make two subroutines to read and write the Control registers:

Declare Sub Enc28j60readcontrolregbyte(byval Register As Byte)
Declare Sub Enc28j60writecontrolregbyte(byval Register As Byte , Byval Value As Byte)
 

The Ethernet buffer contains transmit and receive memory used by the Ethernet controller in a single memory space. The sizes of the memory areas are programmable by the host controller using the SPI interface. The Ethernet buffer memory can only be accessed via the read buffer memory and write buffer memory SPI commands.

We will make two subroutines to read and write the buffer memory:

Declare Sub Enc28j60readbuffer
Declare Sub Enc28j60writebuffer
 

The PHY registers are used for configuration, control and status retrieval of the PHY module. The registers are not directly accessible trhough the SPI interface; they can only be accessed through Media Independent Interface Management (MIM) implemented in the MAC.

We will make two subroutines to read and write the PHY registers:

Declare Sub Enc28j60readphyword(byval Phyregister As Byte)
Declare Sub Enc28j60writephyword(byval Phyregister As Byte , Byval Wdata As Word)
 

We are nearly there.... The Control register memory is partitioned into four banks, selectable by the bank select bits BSEL1:BSEL0 in the ECON1 register (don't worry, later more about this). Each bank is 32 bytes long and addressed by a 5-bit address value....

Hmmmm. Only 5 bits to address a Control register but they are split into four banks. Perhaps the remaining three bits of the byte can be used to select the right bank.

What about this:

'for bank selection
Const Bank0 = &B00_000000
Const Bank1 = &B01_000000
Const Bank2 = &B10_000000
Const Bank3 = &B11_000000

 

Assigned constants consume no program memory because they only serve as a reference to the compiler. The compiler will replace all occurrences of the symbol with the assigned value.

Const Epausl = &H18 Or Bank3
Const Epaush = &H19 Or Bank3

From a byte we will be using two bits for the bankselection, and five bits for the register. Leaving a single bit we will be needing later on (difference between Ethernet-, MAC- and Phy-registers)

If we check register epausl we will have:

00011000 OR 11000000 = 11011000

All these registers, banks etc. are saved in a seperate include file which can be found here:

$include "enc28j60.inc"

You will have to download this file and save it in the same directory where your mainprogram is.
 

Step 4: The first real steps...

Before we can initialize the ENC28J60 we start with a general reset. With the ENC28J60_cs = 0 we select the device. From the include-file we fetch the ENC28J60_soft_reset bitsequence and put this on the SPI-bus. We must use a variable instead of a constant so we use X as a temporary variable. The Bascom-AVR command SPIOUT sends a value or a variable to the SPI-bus. Bascom-AVR handles the transmission. When SPI is used in hardware mode, there might be a small delay/pause after each byte that is sent. This is caused by the SPI hardware and the speed of the bus. After a byte is transmitted, SPSR bit 7 is checked. This bit 7 indicates that the SPI is ready for sending a new byte.

Enc28j60_cs = 0
'reset ENC28J60
X = Enc28j60_soft_reset
Spiout X , 1
Enc28j60_cs = 1
 

And there it is, a call to our first subroutine. ENC28J60readcontrolregbyte followed with the registername we would like to read.

Do
Call Enc28j60readcontrolregbyte(estat)
X = Enc28j60_data.estat_clkrdy
Loop Until X = 1

From the include file bank and position of ESTAT in the Control register is fetched (bank 0 and register &H1D). The DO-LOOP will keep reading until bit estat_clkrdy (bit 0) of this register will be 1. Meaning the softreset has finished. The subroutine Enc28J60readcontrolregbyte will be explained later on.

Now we will write (yet another subroutine) to register ECOCON, the Value &B00000010 to output main clock divided by 2 on CLKOUT. Meaning the Atmega168 will get a 12.5 Mhz clock instead of 6.25 Mhz as default.

'clock from default divide/4 (6.25 Mhz) to divide/2 (12.5 Mhz)
Call Enc28j60writecontrolregbyte(ecocon , &B00000010)
Waitms 250
 

From the datasheet. Register ECOCON.

In the meantime we have used two subroutines I will try to explain here. A third, very important one is the Enc28j60selectbank-subroutine. In Bascom-AVR you first have to declare a subroutine before you can use it. In the declare you also tell the compiler what parameters to use. In this example bank is used as a parameter. This routine will switch to the right bank of the Control registers. Register ECON1 (&H1F in all banks) is used, and in this register bit BSEL1 and BSEL0 (bit 1 and 0).

The SPI-command works with an array of values. Do I want to send 5 bytes I have to fill array-elements A(1), A(2), A(3), A(4) and A(5). After that I do a SPIOUT A(1),5 telling the compiler to send this array of 5 bytes over the SPI-bus.

First I fill array-element A(1) with the Read Control Register instruction. In this subroutine the instruction is &B000 followed by the register we want to read. We want to read ECON1 and that is &H1F or &B11111, so the complete command will be &B000_11111 The underscore can be used in Bascom-AVR to improve readability. With the SPIIN-command we will get a reply of the ENC28J60. It will first send a dummybyte and after that the content of the ECON1 register.

This is the declare of the subroutine ENC28J60selectbank. I put all declare together on the top of the program.

Declare Sub Enc28j60selectbank(byval Bank As Byte)
 

All subroutines are placed after the END of your main program. If you all get mixed up, don't worry, the complete program will be published for download.

Sub Enc28j60selectbank(bank As Byte)
'get ECON1 (BSEL1 en BSEL0)
A(1) = &B000_11111
Enc28j60_cs = 0
Spiout A(1) , 1
Spiin A(1) , 2
Enc28j60_cs = 1
A(2) = A(2) And &B1111_1100                               'strip bank part
A(2) = A(2) Or Bank
A(1) = &B010_11111
Enc28j60_cs = 0
Spiout A(1) , 2
Enc28j60_cs = 1
End Sub

A(1) will contain the dummybyte, so in A(2) the value of register ECON1. We strip BSEL1 and BSEL0 bits, put in the bank we want to switch to, and send the result back to the ENC28J60. The instruction for a write control register is &B010 followed by the register we want to write.

Here an overview of SPI Instruction set for the ENC28J60:

Instruction Opcode Argument Data
Read Control Register 000 aaaaa N/A
Read Buffer Memory 001 11010 N/A
Write Control Register 010 aaaaa dddddddd
Write Buffer Memory 011 11010 dddddddd
Bit Field Set 100 aaaaa dddddddd
Bit Field Clear 101 aaaaa dddddddd
System Reset Command (soft reset) 111 11111 N/A

a=address d=data      

So, that is a lot of information, we now know how to read a register, we know how to write and with these commands we have seen how to switch banks in the Control register.

Got two more subroutines to explain: ENC28J60readcontrolregbyte and ENC28J60writecontrolregbyte.

Here the declares:

Declare Sub Enc28j60readcontrolregbyte(byval Register As Byte)
Declare Sub Enc28j60writecontrolregbyte(byval Register As Byte , Byval Value As Byte)
 

And here the read subroutine:

Sub Enc28j60readcontrolregbyte(register As Byte)
Local Mcphy As Byte
Bank = 0
Mcphy = 0
If Register.7 = 1 Then Bank = 2
If Register.6 = 1 Then Bank = Bank + 1
If Register.5 = 1 Then Mcphy = 1
Register = Register And &B00011111
Call Enc28j60selectbank(bank)
A(1) = Register
Enc28j60_cs = 0
Spiout A(1) , 1
Spiin A(1) , 3
Enc28j60_cs = 1
'Depending of register (E, MAC, MII) yes or no dummybyte
If Mcphy = 1 Then
Enc28j60_data = A(2)
Else
Enc28j60_data = A(3)
End If
End Sub

First, if I know I am going to use a variable only within a subroutine, I can make use of locals. A local is a variable which space (in this case with Local Mcphy as Byte) is only one byte, is reserved while entering the subroutine, and after leaving the subroutine, this space is free to be used for another variable. This way you can keep the use of internal RAM as low as possible.

As I have explained before, bit 7 and 6 are used in the include file, to show in which bank a register resides. The address of the register occupies only 5 bit, leaving one single bit for other purposes. Well there is a small difference between Ethernet registers and MAC or PHY registers. When reading a Ethernet register you will always get first a dummybyte before you get the real contents of the register. With MAC and PHY registers there is no dummybyte. To make a difference between the Ethernet and the MAC/PHY registers, I have used bit 5. Is it a 1, then it is a MAC or PHY register and the dummybye should be skipped. Is it a 0 the dummybyte should be skipped.

With the register.7, register.6 and register.5 I can read the bank of the register and if it is a Ethernet register or a MAC/PHY register. A Enc28J60selectbank is done, switching the ENC28J60 to the right register, and a stripped register-value, with only the 5 bit for the address is send to retrieve the contents of the register. (SPIOUT). With the SPIIN three bytes are fethed, and at the end of the routine the local variable Mcphy takes care of skipping or not skipping the dummybyte. In ENC28J60_data the value of the register is returned. This subroutine could also have been a Bascom-AVR function. Perhaps later....

And here the write-routine

Sub Enc28j60writecontrolregbyte(register As Byte , Value As Byte)
Bank = 0
If Register.7 = 1 Then Bank = 2
If Register.6 = 1 Then Bank = Bank + 1
Register = Register And &B00011111
Call Enc28j60selectbank(bank)
Register.6 = 1 'to get a 010_register
A(1) = Register
A(2) = Value
Enc28j60_cs = 0
Spiout A(1) , 2
Enc28j60_cs = 1
End Sub

First the right bank is selected, A(1) gets &B010 (write control register bit sequence) and register-address after a strip, A(2) the value, bankswitching is done, and there is goes, on the SPI-bus. Done.

Step 5: Initializing the ENC28J60

We have got all subroutines covered by now, just a few to go. In the include-file you will find a small section about the initialisation of the buffermemory.

' buffer boundaries applied to internal 8K ram
' entire available packet buffer space is allocated

Const Txstart_init = &H0000 ' start TX buffer at 0
Const Rxstart_init = &H1A19 ' give TX buffer space for one full ethernet frame (~1500 bytes)

Const Rxstop_init = &H1FF0 ' receive buffer gets the rest
Const Max_framelen = 1500 ' maximum ethernet frame length

See the picture for the registernames.
 

From the datasheet: Ethernet Buffer organization

Sub Enc28j60init
'do bank 0 stuff
'initialize receive buffer
'16-bit transfers, must write low byte first
'set receive buffer start address
Nextpacketptr = Rxstart_init
Value = Low(rxstart_init)
Call Enc28j60writecontrolregbyte(
erxstl , Value)
Value = High(rxstart_init)
Call Enc28j60writecontrolregbyte(
erxsth , Value)
'set receive pointer address
Value = Low(rxstart_init)
Call Enc28j60writecontrolregbyte(
erxrdptl , Value)
Value = High(rxstart_init)
Call Enc28j60writecontrolregbyte(
erxrdpth , Value)
'set receive buffer end
Value = Low(rxstop_init)
Call Enc28j60writecontrolregbyte(
erxndl , Value)
Value = High(rxstop_init)
Call Enc28j60writecontrolregbyte(
erxndh , Value)
'set transmit buffer start
Value = Low(txstart_init)
Call Enc28j60writecontrolregbyte(
etxstl , Value)
Value = High(txstart_init)
Call Enc28j60writecontrolregbyte(
etxsth , Value)
'do bank 2 stuff
'enable MAC receive
Value = 0
Value.macon1_marxen = 1
Value.macon1_txpaus = 1
Value.macon1_rxpaus = 1
Call Enc28j60writecontrolregbyte(macon1 , Value)
'enable automatic padding and CRC operations
Value = 0
Value.macon3_padcfg0 = 1
Value.macon3_txcrcen = 1
Value.macon3_frmlnen = 1
Call Enc28j60writecontrolregbyte(macon3 , Value)

'set inter-frame gap (non-back-to-back)
Call Enc28j60writecontrolregbyte(maipgl , &H12)
Call Enc28j60writecontrolregbyte(maipgh , &H0C)
'set inter-frame gap (back-to-back)
Call Enc28j60writecontrolregbyte(mabbipg , &H12)
'set the maximum packet size which the controller will accept
Value = Low(max_framelen)
Call Enc28j60writecontrolregbyte(mamxfll , Value)
Value = High(max_framelen)
Call Enc28j60writecontrolregbyte(mamxflh , Value)

'bank 3 stuff
Call Enc28j60writecontrolregbyte(maadr5 , Ethaddr0)
Call Enc28j60writecontrolregbyte(maadr4 , Ethaddr1)
Call Enc28j60writecontrolregbyte(maadr3 , Ethaddr2)
Call Enc28j60writecontrolregbyte(maadr2 , Ethaddr3)
Call Enc28j60writecontrolregbyte(maadr1 , Ethaddr4)
Call Enc28j60writecontrolregbyte(maadr0 , Ethaddr5)

'no loopback of transmitted frames
Call Enc28j60writephyword(phcon2 , Phcon2_hdldis)
'switch to bank 0
Call Enc28j60selectbank(0)
'enable interrupts
Value = 0
Value.eie_intie = 1
Value.eie_pktie = 1
Call Enc28j60bitfield_set(eie , Value)
'enable packet reception
Value = 0
Value.econ1_rxen = 1
Call Enc28j60bitfield_set(econ1 , Value)
End Sub

This is all to initialize the ENC28J60. Setting the buffermemory-registers. Putting the MAC-address on the right location. And three calls to new subroutines. The BITFIELD_SET and BITFIELD_CLEAR subroutines, and a write to the PHY-registers.

Step 6: Bitfield_set and Bitfield_clear

Declare Sub Enc28j60bitfield_set(byval Register As Byte , Byval Value As Byte)
Declare Sub Enc28j60bitfield_clear(byval Register As Byte , Byval Value As Byte)
 

Sub Enc28j60bitfield_set(register As Byte , Value As Byte)
Bank = 0
If Register.7 = 1 Then Bank = 2
If Register.6 = 1 Then Bank = Bank + 1
Register = Register And &B00011111
Call Enc28j60selectbank(bank)
Register = Register Or &B100_00000
A(1) = Register
A(2) = Value
Enc28j60_cs = 0
Spiout A(1) , 2
Enc28j60_cs = 1
End Sub


Sub Enc28j60bitfield_clear(register As Byte , Value As Byte)
Bank = 0
If Register.7 = 1 Then Bank = 2
If Register.6 = 1 Then Bank = Bank + 1
Register = Register And &B00011111
Call Enc28j60selectbank(bank)
Register = Register Or &B101_00000
A(1) = Register
A(2) = Value
Enc28j60_cs = 0
Spiout A(1) , 2
Enc28j60_cs = 1
End Sub

The BITFIELD_SET and BITFIELD_CLEAR is used to set or clear 8 bits in any of the Ethernet Control registers. Note that this command cannot be used on the MAC registers, MII registers, PHY-registers or buffer memory. The BITFIELD-command uses the provided data byte to perform a bit-wise OR (SET) or NOTAND (CLEAR) operation on the addressed register contents.

Step 7: Enc28j60writephyword

Declare Sub Enc28j60writephyword(byval Phyregister As Byte , Byval Wdata As Word)
 

Sub Enc28j60writephyword(phyregister As Byte , Wdata As Word)
Call Enc28j60readphyword(phyregister)
Local Temp As Byte
'set the PHY register address
Call Enc28j60writecontrolregbyte(miregadr , Phyregister)
Call Enc28j60readcontrolregbyte(
miregadr)
Temp = Miregadr
Value = Low(wdata)
Call Enc28j60writecontrolregbyte(
miwrl , Value)
Value = High(wdata)
Call Enc28j60writecontrolregbyte(
miwrh , Value)
Do
Call Enc28j60readcontrolregbyte(mistat)
Loop Until Enc28j60_data.mistat_busy = 0
End Sub

This subroutine will get a make-over, have to preserve the 'old' contents of the PHY-register first. But for our first example not important now.

When we follow this subroutine we see: write the address of the PHY-register into the MIREGADR-register. Write the lower 8 bits of data to write into the MIWRL-register. Write the upper 8 bits of data to write into the MIWRH register. Writing to this register automatically begins the MIIM transaction, so it must be written to after MIWRL. The MISTAT.BUSY bit becomes set.

Step 8: Enc28j60readphyword

Declare Sub Enc28j60readphyword(byval Phyregister As Byte)
 

Sub Enc28j60readphyword(phyregister As Byte)
'set the right address and start the register read operation
Call Enc28j60writecontrolregbyte(
miregadr , Phyregister)
Call Enc28j60writecontrolregbyte(
micmd , Micmd_miird)
'wait until the PHY read completes
Do
Call Enc28j60readcontrolregbyte(
mistat)
Loop Until Enc28j60_data.mistat_busy = 0
'quit reading
Call Enc28j60writecontrolregbyte(
micmd , 0)
'get data value
Call Enc28j60readcontrolregbyte(
mirdl)
Wdata = Enc28j60_data
Shift Wdata , Left , 8
Call Enc28j60readcontrolregbyte(
mirdh)
Wdata = Wdata + Enc28j60_data
End Sub

When a PHY-register is read, the entire 16 bits are obtained. Write the address of the PHY-register into the MIREGADR-register. Set the MICMD.MIIRD-bit. The read operation begins and the MISTAT.BUSY bit is set. Clear after the complete read the MICMD.MIIRD-bit. Read the desired data from the MIRDL and MIRDH-registers. Wdata holds the complete WORD.

Step 9: Enc28j60packetsend

Declare Sub Enc28j60packetsend(byval Pcktlen As Word) ' , Byval Packet As Byte)
 

Sub Enc28j60packetsend(pcktlen As Word)
Value = 0
Value.econ1_txrst = 1
Call Enc28j60bitfield_set(
econ1 , Value)
Value.econ1_txrst = 1
Call Enc28j60bitfield_clear(
econ1 , Value)
'set the write pointer to start of transmit buffer area
Value = Low(txstart_init)
Call Enc28j60writecontrolregbyte(
ewrptl , Value)
Value = High(txstart_init)
Call Enc28j60writecontrolregbyte(
ewrpth , Value)
'set the TXND pointer to correspond to the packet size given
Value = Low(txstart_init)
Value = Value + Low(pcktlen)
Call Enc28j60writecontrolregbyte(
etxndl , Value)
Value = High(txstart_init)
Value = Value + High(pcktlen)
Call Enc28j60writecontrolregbyte(
etxndh , Value)
'send the contents of the transmit buffer onto the network
Value = 0
Value.econ1_txrts = 1
Call Enc28j60bitfield_set(
econ1 , Value)
End Sub

Here you see some registers back that have been worked on in the Enc28j60init subroutine.

Step 10: The last one before we can do a small test: Enc28j60writebuffer

To test if all subroutines are working I am filling the buffermemory with a ethernetpacket to perform a ARP-request. If you want to do the same you have to replace the MAC-address settings of your router and the IP-number.

Declare Sub Enc28j60writebuffer

Sub Enc28j60writebuffer
'temporary test subroutine
A(1) = Enc28j60_write_buf_mem
'control
A(2) = &H00 'per packet byte
'ARP
A(3) = &HFF 'Broadcast
A(4) = &HFF
A(5) = &HFF
A(6) = &HFF
A(7) = &HFF
A(8) = &HFF

A(9) = Ethaddr0 'MAC-address of TUX-board
A(10) = Ethaddr1
A(11) = Ethaddr2
A(12) = Ethaddr3
A(13) = Ethaddr4
A(14) = Ethaddr5

A(15) = &H08
A(16) = &H06

A(17) = &H00
A(18) = &H01

A(19) = &H08
A(20) = &H00

A(21) = &H06

A(22) = &H04

A(23) = &H00
A(24) = &H01

A(25) = &H00
A(26) = &H01
A(27) = &H02
A(28) = &H03
A(29) = &H04
A(30) = &H05

A(31) = &HC0 'own TUX-IP-number
A(32) = &HA8
A(33) = &H00
A(34) = &H3D

A(35) = &H00 'MAC-address router
A(36) = &H0C
A(37) = &H41
A(38) = &HAE
A(39) = &H7A
A(40) = &HDC

A(41) = &HC0 'IP-number router 192.168.0.254
A(42) = &HA8
A(43) = &H00
A(44) = &HFE

A(45) = &H00 'padding
A(46) = &H00
A(47) = &H00
A(48) = &H00
A(49) = &H00
A(50) = &H00
A(51) = &H00
A(52) = &H00
A(53) = &H00
A(54) = &H00
A(55) = &H00
A(56) = &H00
A(57) = &H00
A(58) = &H00
A(59) = &H00
A(60) = &H00
A(61) = &H00
A(62) = &H00


Enc28j60_cs = 0
Spiout A(1) , 62 '60 packetlen + writebuffer command + per-packet command
Enc28j60_cs = 1
End Sub

Step 11: The first test, a ARP-request

This is your main program. A Init of the Enc28j60, filling the writebuffer with a ARP-request packet and send it over the line.....

Call Enc28j60init

Value = 0
Value.econ1_txrst = 1
Call Enc28j60bitfield_set(econ1 , Value)
Call Enc28j60bitfield_clear(econ1 , Value)

Call Enc28j60writebuffer
Call Enc28j60packetsend(60)

End

Step 12: ARP
Address Resolution Protocol (arp)

The address resolution protocol (arp) is a protocol used by the Internet Protocol (IP), specifically IPv4, to map IP network addresses to the hardware addresses (MAC-address) used by a data link protocol. The protocol operates below the network layer as a part of the interface between the OSI network and OSI link layer. 

The term address resolution refers to the process of finding an address of a computer in a network. The address is "resolved" using a protocol in which a piece of information is sent by a client process executing on the local computer to a server process executing on a remote computer. The information received by the server allows the server to uniquely identify the network system for which the address was required and therefore to provide the required address. The address resolution procedure is completed when the client receives a response from the server containing the required address.


Fire up your networksniffer and put power on your Tux-board. This is what I see:

Check with the bytes we have put in the writebuffer-memory

See what my router is responding

 

Here the program up-to this point.....

 

Step 13: Serial port for debugging

In the example program you will find lots of PRINT-statements. I have connected a MAX232 to the RX/TX of the Atmega168 to do the debugging while I am programming.

During the start of the program the serial port is initialized by Bascom-AVR for 9600 Baud. During the initialisation of the ENC28J60 the external clock (CLKOUT from the ENC28J60) is changed from 6.25 Mhz to 12.5 Mhz. Thats why you should put your terminalprogram to 19200 Baud.

As an extra a registerdump can be found in the program. You can do a registerdump anywhere you want in the program to check your registers.
 

Step 14: The readbuffer-routine....

We have to get a routine to read the readbuffer of the ENC28J60 back. Here the full packet returned from the router. Added on front is the Nextpacketpointer, packetlength and receive status. The present Packetpointer is &H1A19. the next packet will be at &H1A60.

Step 15: Enc28j60readbuffer
The full subroutine for reading the readbuffer looks like this. In the Enc28j60packetreceive routine we have to get, from the front of the packet, some single values. This is done with a value as parameter in the Pcktlen.

Sub Enc28j60readbuffer(pcktlen As Word)
Enc28j60_cs = 0
Spdr = &H3A
Do
Loop Until Spsr.spif = 1
Spdr = &HFF 'dummy byte
Do
Loop Until Spsr.spif = 1
Buffer(1) = Spdr
Buffer(1) = Spdr
If Pcktlen > 1 Then
For X = 1 To Pcktlen
Spdr = &HFF 'dummy byte
Buffer(x) = Spdr
Next X
End If
Enc28j60_cs = 1
End Sub

Step 16: Enc28j60packetreceive
And here the last base-subroutine. We got all things together to communicate with the ENC28J60. In this routine from the front of the packet nextpacketpointer, length and status are retrieved. And after that the remaining bytes are fetched and put in the array Buffer().

Declare Sub Enc28j60packetreceive(byval Maxlen As Word)


Sub Enc28j60packetreceive(maxlen As Word)
Print "Sub Enc28j60packetreceive"
Local Wtemp As Word
Call Enc28j60readcontrolregbyte(eir)
Print Bin(enc28j60_data)
If Enc28j60_data.eir_pktif = 0 Then
Rstatus = 0
Print "No packet received"
Exit Sub
End If
'set the read pointer to the start of the received packet
Value = Low(nextpacketptr)
Call Enc28j60writecontrolregbyte(erdptl , Value)
Value = High(nextpacketptr)
Call Enc28j60writecontrolregbyte(erdpth , Value)
Print "Present nextpacketpntr " ; Hex(nextpacketptr)
'read the next packet pointer
Call Enc28j60readbuffer(1)
'Print "Nextpktpntr " ; Hex(buffer(1))
Nextpacketptr = Buffer(1)
Call Enc28j60readbuffer(1)
Wtemp = Buffer(1) * 256
Nextpacketptr = Nextpacketptr + Wtemp
Print "New Nextpacketpntr " ; Hex(nextpacketptr)
'read the packet length
Call Enc28j60readbuffer(1)
Length = Buffer(1)
Call Enc28j60readbuffer(1)
Wtemp = Buffer(1) * 256
Length = Length + Wtemp
Print "Packetlength = " ; Length
'read the receive status
Call Enc28j60readbuffer(1)
Rxstat = Buffer(1)
Call Enc28j60readbuffer(1)
Wtemp = Buffer(1) * 256
Rxstat = Rxstat + Wtemp
Print "Rxstat = " ; Bin(rxstat)
'limit retrieve length
'we reduce the MAC-reported length by 4 to remove the CRC
If Length > Maxlen Then
Length = Maxlen
End If
'copy the packet from the receive buffer
Call Enc28j60readbuffer(length)
'move the rx read pointer to the start of the next received packet
'this frees the memory we just read out
Value = Low(nextpacketptr)
Call Enc28j60writecontrolregbyte(erxrdptl , Value)
Value = High(nextpacketptr)
Call Enc28j60writecontrolregbyte(erxrdpth , Value)
'decrement the packet counter indicate we are done with this packet
Value = 0
Value.econ2_pktdec = 1
Call Enc28j60bitfield_set(econ2 , Value)
'return length
End Sub

Step 17: Replace temporary writebuffer-subroutine
We have been using a temporary write-buffer subroutine, here a more suitable one.

Declare Sub Enc28j60writebuffer(byval Pcktlen As Word)
 

Sub Enc28j60writebuffer(pcktlen As Word)
'Local Index As Word
'Local Index2 As Word
Enc28j60_cs = 0
Spdr = Enc28j60_write_buf_mem
Do
Loop Until Spsr.spif = 1
Spdr = 0
Do
Loop Until Spsr.spif = 1
For X = 1 To Pcktlen
Spdr = Buffer(x)
Do
Loop Until Spsr.spif = 1
Next X
Enc28j60_cs = 1
End Sub

Step 18: Some highlights - Per packet control byte
The ENC28J60 requires a single per packet control byte to precede the packet for transmission.
In our ARP-example we used &H00 as per packet control byte. There are four bits of this control byte which can be set/reset to control the way the packet is send.

Sub Enc28j60writebuffer(pcktlen As Word)
'Local Index As Word
'Local Index2 As Word
Enc28j60_cs = 0
Spdr = Enc28j60_write_buf_mem
Do
Loop Until Spsr.spif = 1

Spdr = &B0000_1110
Do
Loop Until Spsr.spif = 1

For X = 1 To Pcktlen
Spdr = Buffer(x)
Do
Loop Until Spsr.spif = 1
Next X
Enc28j60_cs = 1
End Sub

The four bits are called: PHUGEEN, PPADEN, PCRCEN and POVERRIDE. Bit 7-4 are unused.

Bit 3 set: PHUGEEN. The packet will be transmitted in whole.
Bit 2 set: PPADEN. The packet will be zero padded to 60 bytes if it is less than 60 bytes.
Bit 1 set: PCRCEN. A valid CRC will be calculated and attachmed to the frame (last 4 bytes)
Bit 0 set: POVERRIDE. The values PCRCEN, PPADEN and PHUGEEN will override the configuration defined by MACON3.

We will be using &B0000_1110 in our examples.

Step 19: Some highlights - RAM usage
The Atmega168 has only 1024 bytes of RAM. So we have to be carefull in using global variables. 1024 bytes won't be enough to store a complete ethernetpacket of 1518 bytes. The first 56 bytes are enough to check what kind of packet has been received (ICMP = PING).

An overview of the first 56 bytes of an ethernet-packet:
  ARP IP ICMP TCP UDP
1 Destination
MAC-addr. 1
Destination
MAC-addr. 1
Destination
MAC-addr. 1
Destination
MAC-addr. 1
Destination
MAC-addr. 1
2 Destination
MAC-addr. 2
Destination
MAC-addr. 2
Destination
MAC-addr. 2
Destination
MAC-addr. 2
Destination
MAC-addr. 2
3 Destination
MAC-addr. 3
Destination
MAC-addr. 3
Destination
MAC-addr. 3
Destination
MAC-addr. 3
Destination
MAC-addr. 3
4 Destination
MAC-addr. 4
Destination
MAC-addr. 4
Destination
MAC-addr. 4
Destination
MAC-addr. 4
Destination
MAC-addr. 4
5 Destination
MAC-addr. 5
Destination
MAC-addr. 5
Destination
MAC-addr. 5
Destination
MAC-addr. 5
Destination
MAC-addr. 5
6 Destination
MAC-addr. 6
Destination
MAC-addr. 6
Destination
MAC-addr. 6
Destination
MAC-addr. 6
Destination
MAC-addr. 6
7 Source MAC-
address 1
Source MAC-
address 1
Source MAC-
address 1
Source MAC-
address 1
Source MAC-
address 1
8 Source MAC-
address 2
Source MAC-
address 2
Source MAC-
address 2
Source MAC-
address 2
Source MAC-
address 2
9 Source MAC-
address 3
Source MAC-
address 3
Source MAC-
address 3
Source MAC-
address 3
Source MAC-
address 3
10 Source MAC-
address 4
Source MAC-
address 4
Source MAC-
address 4
Source MAC-
address 4
Source MAC-
address 4
11 Source MAC-
address 5
Source MAC-
address 5
Source MAC-
address 5
Source MAC-
address 5
Source MAC-
address 5
12 Source MAC-
address 6
Source MAC-
address 6
Source MAC-
address 6
Source MAC-
address 6
Source MAC-
address 6
13 Packet
type 0
Packet
type 0
Packet
type 0
Packet
type 0
Packet
type 0
14 Packet
type 1
Packet
type 1
Packet
type 1
Packet
type 1
Packet
type 1
15 hwtype 0 version and
length
version and
length
version and
length
version and
 length
16 hwtype 1 tos tos tos tos
17 prtype 0 packet-
length h
packet-
length h
packet-
length h
packet-
length h
18 prtype 1 packet-
length l
packet-
length l
packet-
length l
packet-
length l
19 hwlen id 0 id 0 id 0 id 0
20 prlen id 1 id 1 id 1 id 1
21 op 0 frag-offset 0 frag-offset 0 frag-offset 0 frag-offset 0
22 op 1 frag-offset 1 frag-offset 1 frag-offset 1 frag-offset 1
23 shaddr Time to live Time to live Time to live Time to live
24 sipaddr Protocol Protocol Protocol Protocol
25 thaddr Header
checksum h
Header
checksum h
Header
checksum h
Header
checksum h
26 tipaddr Header
checksum l
Header
checksum l
Header
checksum l
Header
checksum l
27   Source
address 0
Source
address 0
Source
address 0
Source
address 0
28 Source
address 1
Source
address 1
Source
address 1
Source
address 1
29 Source
address 2
Source
address 2
Source
address 2
Source
address 2
30 Source
address 3
Source
address 3
Source
address 3
Source
address 3
31 Destination
address 0
Destination
address 0
Destination
address 0
Destination
address 0
32 Destination
address 1
Destination
address 1
Destination
address 1
Destination
address 1
33 Destination
address 2
Destination
address 2
Destination
address 2
Destination
address 2
34 Destination
address 3
Destination
address 3
Destination
address 3
Destination
address 3
35 IP-data Type Source
port h
Source
port h
36   Code Source
port l
Source
port l
37 Checksum h Destination
port h
Destination
port h
38 Checksum l Destination
port l
Destination
port l
39 Id h Sequence-
number 3
Length h
40 Id l Sequence
number 2
Length l
41 Sequence
number h
Sequence
number 1
Checksum h
42 Sequence
number l
Sequence
number 0
Checksum l
43 ICMP-data Acknowledge
number 3
UDP-data
44 ICMP-data Acknowledge
number 2
UDP-data
45 ICMP-data Acknowledge
number 1
UDP-data
46 ICMP-data Acknowledge
number 0
UDP-data
47 ICMP-data Header
 
UDP-data
48 ICMP-data Flags
 
UDP-data
49 ICMP-data Window h
 
UDP-data
50 ICMP-data Window l
 
UDP-data
51 ICMP-data Checksum h
 
UDP-data
52 ICMP-data Checksum l
 
UDP-data
53 ICMP-data Urgent-
pointer h
UDP-data
54 ICMP-data Urgent-
pointer l
UDP-data
55 ICMP-data TCP-data
 
UDP-data
56 etc. etc.
etc. etc.
etc. etc

 

With the automatic padding (zeros up to a packetsize of 60 bytes) you would only need this to do a ARP-request

'ARP
Buffer(1) = &HFF : Buffer(2) = &HFF
Buffer(3) = &HFF : Buffer(4) = &HFF
Buffer(5) = &HFF : Buffer(6) = &HFF

Buffer(7) = Ethaddr0 : Buffer(8) = Ethaddr1                     'MAC-address of TUX-board
Buffer(9) = Ethaddr2 : Buffer(10) = Ethaddr3
Buffer(11) = Ethaddr4 : Buffer(12) = Ethaddr5

Buffer(13) = &H08 : BUFFER(14) = &H06
Buffer(15) = &H00 : Buffer(16) = &H01
Buffer(17) = &H08 : Buffer(18) = &H00
Buffer(19) = &H06 : Buffer(20) = &H04
Buffer(21) = &H00 : Buffer(22) = &H01

Buffer(23) = Ethaddr0 : Buffer(24) = Ethaddr1
Buffer(25) = Ethaddr2 : Buffer(26) = Ethaddr3
Buffer(27) = Ethaddr4 : Buffer(28) = Ethaddr5

Buffer(29) = 192
Buffer(30) = 168
Buffer(31) = 0
Buffer(32) = 61

Buffer(33) = &H00                                'MAC-address router
Buffer(34) = &H0C
Buffer(35) = &H41
Buffer(36) = &HAE
Buffer(37) = &H7A
Buffer(38) = &HDC

Buffer(39) = 192                                   'IP-nummer router
Buffer(40) = 168
Buffer(41) = 0
Buffer(42) = 254

Call Enc28j60writebuffer(42)

Just 42 bytes written to the buffer-array and written to the ENC28J60writebuffer.
 

Step 20: Some highlights - LED test
A real highLIGHT, a led test. If your RJ45-connector has LED's for LINK and DATA you can test them with this small line. Put it somewhere after the ENC28J60init.

Call Enc28j60writephyword(phlcon , &H0AA2)
Stop

Both LED's should be blinking at a 1 Hz speed.
 

Step 21: The circular buffer at work...
If you check this picture you will see that the circular buffer of the ENC28J60 is working. A PING is started, if you check this in a network sniffer your will see that the PING takes 74 bytes, if you add the Next Packetpointer (2 bytes), the packetlength (2 bytes) and the status (2 bytes) and at the end of the packet CRC (4 bytes) you will get exactly the difference between the present nextpacketpntr and the new nextpacketpntr.

If you check the enc28j60.inc file you will find two settings for the receivebuffer. If you check the line where the Present nextpacketpntr starts at 1F80 and the New Nextpacketpntr is 1AAA, and you know the packetlength is 251 byte, you can see that the rollover is working good. 

const Rxstart_init = &H1A19
Const Rxstop_init = &H1FF0

Calculations while rolling over....
&H1F80 &H70 112
&H1FF0 Rxstop_init  
memory boundry
&H1A19 Rxstart_init &H91 145
&H1AAA  

+

Total (packetlength + nextpacketpointer + status) 257 (251 + 6)

 

Step 22: Read the errata!!!


Sub Enc28j60packetreceive(maxlen As Word)
Print "Sub Enc28j60packetreceive"
Local Wtemp As Word
Call Enc28j60readcontrolregbyte(eir)
Print Bin(enc28j60_data)

If Enc28j60_data.eir_pktif = 0 Then
Rstatus = 0
Print "No packet received"
Exit Sub
End If

have replaced this by

Call Enc28j60readcontrolregbyte(epktcnt)
if Enc28j60_data = 0 then
Rstatus = 0
Print "No packet received"
Exit Sub
End if

Typically the poll routine will check the packet count to see if the ENC's buffer contains completed read packets waiting to be processed. I noticed that on some occasions when the packet count got to 2 or more waiting it would loose one packet, so the register would be one message behind the actual packet count. The net effect would be to stall, especially on TCP connections. We will make a workaround for this later on. 

Bascom-AVR-code up to this point.....

Step 23: Receive filters....
This is going to be a difficult part.

To minimize the processing requirements of the host controller, the ENC28J60 incorporates several different receive filters which can automatically reject packets which are not needed.

Six different types of packet filters are implemented:

We will be filtering on Broadcast (ARP) and Unicast to our own MAC-address.

First we will add some constants to our ENC28J60.inc file.

'ENC28J60 ETHERNET RECEIVE FILTER CONTROL REGISTER

Const Erxfcon_ucen = 7 'default 1 - Unicast Filter Enable bit
Const Erxfcon_andor = 6 'default 0
Const Erxfcon_crcen = 5 'default 1
Const Erxfcon_pmen = 4 'default 0 - Pattern Match Filter Enable bit
Const Erxfcon_mpen = 3 'default 0 - Magic Packet Filter Enable bit
Const Erxfcon_hten = 2 'default 0 - Hash Table Filter Enable bit
Const Erxfcon_mcen = 1 'default 0 - Multicast Filter Enable bit
Const Erxfcon_bcen = 0 'default 1 - Broadcast Filter Enable bit

 
Default:
Erxfcon_andor = 0
OR = Packets will be accepted unless all enabled filters reject the packet
 
Erxfcon_ucent = 1
Packets with a destination address matching the local MAC address will be accepted
 
Erxfcon_crcen = 1
All packets with an invalid CRC will be discarded
 
Erxfcon_pmen (default = 0) we will put this on 1
Packets which meet the Pattern Match criteria will be accepted
 
Erxfcon_bcen = 1
Packets which have a destination address of FF-FF-FF-FF-FF-FF will be accepted
 
Perhaps later on, here a setting to remember:
Erxfcon_MPEN = 0 (if you put this on 1, Magic Pack Filter Enable bit)

We have to fill four registers and set one bit to get the receive filtering working.

First we will startup Bascom-AVR and use the simulator to explain calculating a checksum from parts of a ethernetpacket. 

Copy this in Bascom-AVR, compile it and start the simulator

$regfile = "m168def.dat"
$hwstack = 64
$swstack = 64
$framesize = 64

'Small test for the simulator
'Receive filters, caluclulation of the checksum

Dim Buffer(4) As Word
Dim Totaal As Long
Dim Totaal1 As Word
Dim Totaal2 As Word
Dim Totaal3 As Word
Dim Totaal4 As Word

Buffer(4) = &H0608                                                      'ARP'
Buffer(3) = &HFFFF                                                      'Broadcast
Buffer(2) = &HFFFF
Buffer(1) = &HFFFF
Totaal = Buffer(1) + Buffer(2)                                    'Add all as WORDS
Totaal = Totaal + Buffer(3)
Totaal = Totaal + Buffer(4)
Print "Sum off all words " ; Hex(totaal)
Print
Print "Split lower and upper part"
Totaal1 = Highw(totaal) '0003
Totaal2 = Totaal '0605
Totaal3 = Totaal1 + Totaal2
Print "Upper part " ; Hex(totaal1)
Print "Lower part " ; Hex(totaal2)
Print
Print "Sum of lower and upper part " ; Hex(totaal3)
Totaal4 = Not Totaal3
Print
Print "Ones compliment " ; Hex(totaal4)
End

Call Enc28j60writecontrolregbyte(epmcsl , &HF9)
Call Enc28j60writecontrolregbyte(
epmcsh , &HF7)

Call Enc28j60writecontrolregbyte(epmm0, &H3F)
Call Enc28j60writecontrolregbyte(
epmm1 ,&H30)

The checksum is calculated (we have done that) over all bytes that has a 1 in the pattern. From right to left we see the first 14 bytes of a ARP request. The result of the checksum is stored in epmcsl and epmcsh.

Then the pattern is stored in epmm0 and epmm1. This pattern can be 64-bit wide and, if used, can be stored in epmm0, epmm1, epmm2, epmm3, epmm4, epmm5, epmm6 and epmm7.

There even is an offset. In the registerpair empol and empoh you can store an offset and the 64-bit (or less) pattern will start from there.

And after these settings we enable the Pattern Match Filter Enable bit.

Value = 0
Value.erxfcon_pmen = 1
Call Enc28j60bitfield_set(erxfcon , Value)
 

Step 24: ARP, PING and Viktor....
Got an email with an attachment. Viktor Varga from Hungary got his ENC28J60 working. He has used the above base-routines and has written the ARP and PING routines.

In the remarks of his program "Something is wrong with the Broadcast filter (or the whole theory), it seems like every packet is coming in". Have to check that.

Here the unmodified code from Viktor. IP-number and MAC-address is different.

PING response 2 milliseconds (if you remark all PRINT-statements). Will continue the tutorial with this code.

Thanks Viktor Varga!!

Enc28J60_arp_ping.bas

Step 25: Some more code
After the init of the ENC28J60 in a loop the subroutine Enc28J60poll is called to check if the packetcounter is greater than 0. If greater then the Enc28J60packetreceive is called.

Do
Call Enc28j60poll
Loop

Sub Enc28j60poll
Call Enc28j60readcontrolregbyte(epktcnt)
If Enc28j60_data > 0 Then
Call Enc28j60packetreceive
End If
End Sub

The Enc28J60packetreceive and Enc28J60readbuffer have been joined together.

Sub Enc28j60packetreceive
Reset Led
Print "EPKTCNT = " ; Bin(enc28j60_data)
Local Wtemp As Word
'set the read pointer to the start of the received packet
Value = Low(nextpacketptr)
Call Enc28j60writecontrolregbyte(erdptl , Value)
Value = High(nextpacketptr)
Call Enc28j60writecontrolregbyte(erdpth , Value)
Print "Present nextpacketpntr " ; Hex(nextpacketptr)


Enc28j60_cs = 0
'Send Read Buffer Memory command
Spdr = &H3A
Do
Loop Until Spsr.spif = 1

'Get the first 6 byte (3 word: Nextpacketptr, Packetlength, Rxstat)
For X = 1 To 6
Spdr = &HFF 'SPI read
Do
Loop Until Spsr.spif = 1 'SPI ready
Buffer(x) = Spdr
Next X

Nextpacketptr = Buffer(2) * 256
Nextpacketptr = Nextpacketptr + Buffer(1)
Length = Buffer(4) * 256
Length = Length + Buffer(3)
Rxstat = Buffer(6) * 256
Rxstat = Rxstat + Buffer(5)

Print "New Nextpacketpntr " ; Hex(nextpacketptr)
Print "Packetlength = " ; Hex(length)
Print "Rxstat = " ; Bin(rxstat)

And here the complete packet is fetched and put in the buffer(array)


'Get the payload
Length = Length - 4 'Discard CRC
For X = 1 To Length
Spdr = &HFF 'SPI read
Do
Loop Until Spsr.spif = 1 'SPI ready
Buffer(x) = Spdr
Next X

Enc28j60_cs = 1


'move the rx read pointer to the start of the next received packet
'this frees the memory we just read out
Value = Low(nextpacketptr)
Call Enc28j60writecontrolregbyte(erxrdptl , Value)
Value = High(nextpacketptr)
Call Enc28j60writecontrolregbyte(erxrdpth , Value)
'decrement the packet counter indicate we are done with this packet
Value = 0
Value.econ2_pktdec = 1
Call Enc28j60bitfield_set(econ2 , Value)
Set Led

And here the complete packet is fetched and put in the buffer(array)

'Print the packet content
Print "The packet:"
For X = 1 To Length
Print Hex(buffer(x)) ; " " ;
Next X
Print

'Handle the packet
If Buffer(13) = &H08 Then
  If Buffer(14) = &H06 Then 'Type:ARP
    If Buffer(21) = &H00 Then 'ARP request
      If Buffer(22) = &H01 Then 'ARP request
        If Buffer(39) = Ipaddr0 Then 'ARP for us
          If Buffer(40) = Ipaddr1 Then
            If Buffer(41) = Ipaddr2 Then
              If Buffer(42) = Ipaddr3 Then
               
Call Arpreply
             End If
           End If
         End If
       End If
     End If
   End If
End If
If Buffer(14) = &H00 Then 'Type:IP
  If Buffer(15) = &H45 Then 'We handle only simple IP packets
    If Buffer(21) = 0 Then 'We handle only non fragmented packets
      If Buffer(31) = Ipaddr0 Then 'Ip packet for us
        If Buffer(32) = Ipaddr1 Then
          If Buffer(33) = Ipaddr2 Then
            If Buffer(34) = Ipaddr3 Then
              If Buffer(24) = 1 Then 'Protocol:ICMP
                If Buffer(35) = &H08 Then 'ICMP echo request
                 
Call Pingreply
                  End If
                    End If
                    If Buffer(24) = 17 Then 'Protocol:UDP
                   
'will be handled later on
                    End If
                    If Buffer(24) = 6 Then 'Protocol:TCP
                   
'will be handled later on
                    End If
                  End If
               End If
             End If
           End If
         End If
       End If
   End If
End If

Step 26: ARP-subroutine
Declare Sub Arpreply

Sub Arpreply
'The original request packet is in the buffer, we just change some things

Local I As Byte

'Swap MAC addresses
Call Setst_mac

'Copy target MAC in ARP packet - starting at pos 33, copy from pos 1
For I = 1 To 6
Buffer(32 + I) = Buffer(i)
Next I
'Set target IP in ARP packet - starting at pos 39, original starting at pos 29
For I = 1 To 4
Buffer(38 + I) = Buffer(28 + I)
Next I

'Copy source MAC to ARP packet pos 23 from pos 7
For I = 1 To 6
Buffer(22 + I) = Buffer(6 + I)
Next I
'Set source IP to ARP packet pos 29
Buffer(29) = Ipaddr0
Buffer(30) = Ipaddr1
Buffer(31) = Ipaddr2
Buffer(32) = Ipaddr3

'Set ARP type from Request to Reply
Buffer(22) = 2

'Send the reply packet
Call Enc28j60packetsend(42)

End Sub

Step 27: PING-subroutine

Declare Sub Pingreply

Sub Pingreply
'The original request packet is in the buffer, we just change some things
Local I As Byte
Local Packetlength As Word

'Swap MAC addresses
Call Setst_mac

'Set IP ID field
Call Setip_id

'Swap IP addresses
Call Setst_ip

'Zero out original IP checksum fields
Buffer(25) = 0
Buffer(26) = 0
'Calc new IP checksum
Call Calcchecksum(15 , 33 , 25)

'Set ICMP type to Echo reply
Buffer(35) = 0
'Zero out original ICPM checksum fields
Buffer(37) = 0
Buffer(38) = 0
'Calc new ICPM checksum, length calculated from IP packet length
Packetlength = Buffer(17) * 256
Packetlength = Packetlength + Buffer(18)
Packetlength = Packetlength + 13 'We are going to calculate the checksum till the end of packet (IP length + 14 byte of the ethernet stuff), -1 to get word start
Call Calcchecksum(35 , Packetlength , 37)

'Setting packetlength to the correct value
Packetlength = Packetlength + 1

'Send the reply packet
Call Enc28j60packetsend(packetlength)
End Sub

Step 28: Some small helperroutines
Declare Sub Setip_id
Declare Sub Setst_mac
Declare Sub Setst_ip

Sub Setip_id
Buffer(19) = High(ip_id)
Buffer(20) = Low(ip_id)
Ip_id = Ip_id + 1
End Sub

Sub Setst_mac
Local I As Byte
'Set target MAC in ethernet frame - starting at pos 1, original starting at pos 7
For I = 1 To 6
Buffer(i) = Buffer(6 + I)
Next I
'Set source MAC in ethernet frame, pos 7
Buffer(7) = Ethaddr0
Buffer(8) = Ethaddr1
Buffer(9) = Ethaddr2
Buffer(10) = Ethaddr3
Buffer(11) = Ethaddr4
Buffer(12) = Ethaddr5
End Sub

Sub Setst_ip
Local I As Byte
'Set target IP in IP header - starting at pos 31, original starting at pos 27
For I = 1 To 4
Buffer(30 + I) = Buffer(26 + I)
Next I
'Set source IP in IP header, pos 27
Buffer(27) = Ipaddr0
Buffer(28) = Ipaddr1
Buffer(29) = Ipaddr2
Buffer(30) = Ipaddr3
End Sub

Step 29: Checksum routine
Declare Sub Calcchecksum(byval Startptr As Word , Byval Endptr As Word , Byval Result As Word)

Sub Calcchecksum(startptr As Word , Endptr As Word , Result As Word)
Local I As Word
Local Tmp As Word
Local Sum As Long
Sum = 0
For I = Startptr To Endptr Step 2
Sum = Sum + Buffer(i + 1)
Tmp = Buffer(i) * 256
Sum = Sum + Tmp
Next I
I = Highw(sum)
Tmp = Sum
Tmp = Tmp + I
Tmp = Not Tmp
Buffer(result) = High(tmp)
Buffer(result + 1) = Low(tmp)
End Sub

Step 30: And what about the Unicast and Broadcast-filtering?
Have seen the filtering is not working. Viktor has also seen it is not working. Everytime the program enters the Enc28J60Packetreceive routine it puts a LED on and after leaving the routine puts the LED off. I see on my network a lot of traffic is generated and received that is has not the Tuxgraphics-board as destination. Have to check that. The Atmega168 has a lot to do without this filtering. But ARP, PING and even UDP is working well.
Step 31: UDP and the EDTP Electronics Internet Test Panel
If you add a few extra lines you will see at this point UDP is also working. Add these lines

Declare Sub Udp

Sub Udp
Print "UDP **************************"
End Sub

If Buffer(24) = 17 Then 'Protocol:UDP
      Call Udp
End If

If you start the EDTP Electronics Internet Test Panel (you can download it at www.edtp.com), adjust the Adressing and type something. 
Every character you type will generate a
UDP ******************  on your hyperterminal screen. 

 

Step 32: Our little friend again
UDP, perhaps nice to make a small application reading a LM76 temperature sensor... Here is our little friend again (check the tutorial for the RTL8019as, we have used it before).

LM76 on a socket....

Here the DIM, Declare and subroutine to read a LM76-temperature sensor. Place them at the right position in the program.

'LM76
Dim Lm76temp As Word 
Dim Lm76high As Byte At Lm76temp + 1 Overlay
Dim Lm76low As Byte At Lm76temp Overlay
Dim Lm76temp2 As Long
Dim Lm76buf(2) As Byte

Declare Sub Lm76_test

Call Lm76_test

' Routine to test the LM76
'
Sub Lm76_test
I2csend &H90 , 0
I2creceive &H90 , Lm76buf(1) , 0 , 2
Print Bin(lm76buf(1))
Print Bin(lm76buf(2))
Lm76high = Lm76buf(1)
Lm76low = Lm76buf(2)
Shift Lm76temp , Right , 3
Lm76temp2 = Lm76temp
Lm76temp2 = Lm76temp2 * 625
Lm76temp2 = Lm76temp2 / 100
Print "Temperature = " ; Lm76temp2
End Sub

This piece of software can be integrated in the UDP and TCP part of the program.

 

Step 33: UDP
TCP (Transmission Control Protocol) is the most commonly used protocol on the Internet. The reason for this is because TCP offers error correction. When the TCP protocol is used there is a "guaranteed delivery." This is due largely in part to a method called "flow control." Flow control determines when data needs to be re-sent, and stops the flow of data until previous packets are successfully transferred. This works because if a packet of data is sent, a collision may occur. When this happens, the client re-requests the packet from the server until the whole packet is complete and is identical to its original.

UDP (User Datagram Protocol) is another commonly used protocol on the Internet. However, UDP is never used to send important data such as webpages, database information, etc; UDP is commonly used for streaming audio and video. Streaming media such as Windows Media audio files (.WMA) , Real Player (.RM), and others use UDP because it offers speed! The reason UDP is faster than TCP is because there is no form of flow control or error correction. The data sent over the Internet is affected by collisions, and errors will be present. Remember that UDP is only concerned with speed. This is the main reason why streaming media is not high quality.

Could keep refering to buffer(xx) but I can also use overlays like this:

'Ethernet packet destination
Dim T_enetpacketdest0 As Byte At Buffer Overlay
Dim T_enetpacketdest1 As Byte At Buffer + &H01 Overlay
Dim T_enetpacketdest2 As Byte At Buffer + &H02 Overlay
Dim T_enetpacketdest3 As Byte At Buffer + &H03 Overlay
Dim T_enetpacketdest4 As Byte At Buffer + &H04 Overlay
Dim T_enetpacketdest5 As Byte At Buffer + &H05 Overlay
'Ethernet packet source
Dim T_enetpacketsrc0 As Byte At Buffer + &H06 Overlay
Dim T_enetpacketsrc1 As Byte At Buffer + &H07 Overlay
Dim T_enetpacketsrc2 As Byte At Buffer + &H08 Overlay
Dim T_enetpacketsrc3 As Byte At Buffer + &H09 Overlay
Dim T_enetpacketsrc4 As Byte At Buffer + &H0A Overlay
Dim T_enetpacketsrc5 As Byte At Buffer + &H0B Overlay
'Ethernet packet type
Dim T_enetpackettype As Word At Buffer + &H0C Overlay
Dim T_arp_hwtype1 As Byte At Buffer + &H0F Overlay
'Arp
Dim T_arp_prttype1 As Byte At Buffer + &H11 Overlay
Dim T_arp_hwlen As Byte At Buffer + &H12 Overlay
Dim T_arp_prlen As Byte At Buffer + &H13 Overlay
Dim T_arp_op1 As Byte At Buffer + &H15 Overlay
'arp source ip address
Dim T_arp_sipaddr0 As Byte At Buffer + &H1C Overlay
Dim T_arp_sipaddr1 As Byte At Buffer + &H1D Overlay
Dim T_arp_sipaddr2 As Byte At Buffer + &H1E Overlay
Dim T_arp_sipaddr3 As Byte At Buffer + &H1F Overlay
'arp target IP address
Dim T_arp_tipaddr As Long At Buffer + &H26 Overlay
'IP header layout IP version and header length
Dim T_ip_vers_len As Byte At Buffer + &H0E Overlay
Dim T_arp_hwtype0 As Byte At Buffer + &H0E Overlay
'Arp
Dim T_arp_prttype0 As Byte At Buffer + &H10 Overlay
Dim T_arp_op0 As Byte At Buffer + &H14 Overlay
'arp source mac address
Dim T_arp_src_enetpacket0 As Byte At Buffer + &H16 Overlay
Dim T_arp_src_enetpacket1 As Byte At Buffer + &H17 Overlay
Dim T_arp_src_enetpacket2 As Byte At Buffer + &H18 Overlay
Dim T_arp_src_enetpacket3 As Byte At Buffer + &H19 Overlay
Dim T_arp_src_enetpacket4 As Byte At Buffer + &H1A Overlay
Dim T_arp_src_enetpacket5 As Byte At Buffer + &H1B Overlay
'arp source mac address
Dim T_arp_dest_enetpacket0 As Byte At Buffer + &H20 Overlay
Dim T_arp_dest_enetpacket1 As Byte At Buffer + &H21 Overlay
Dim T_arp_dest_enetpacket2 As Byte At Buffer + &H22 Overlay
Dim T_arp_dest_enetpacket3 As Byte At Buffer + &H23 Overlay
Dim T_arp_dest_enetpacket4 As Byte At Buffer + &H24 Overlay
Dim T_arp_dest_enetpacket5 As Byte At Buffer + &H25 Overlay
Dim T_tos As Byte At Buffer + &H0F Overlay
'Buffer length
Dim T_ip_pktlen0 As Byte At Buffer + &H10 Overlay
Dim T_ip_pktlen1 As Byte At Buffer + &H11 Overlay
Dim T_id0 As Byte At Buffer + &H12 Overlay
Dim T_id1 As Byte At Buffer + &H13 Overlay
Dim T_flags As Byte At Buffer + &H14 Overlay
Dim T_offset As Byte At Buffer + &H15 Overlay
Dim T_ttl As Byte At Buffer + &H16 Overlay
'protocol (ICMP=1, TCP=6, UDP=11)
Dim T_ip_proto As Byte At Buffer + &H17 Overlay
'header checksum
Dim T_ip_hdr_cksum0 As Byte At Buffer + &H18 Overlay
Dim T_ip_hdr_cksum1 As Byte At Buffer + &H19 Overlay
Dim T_ip_hdr_cksum As Word At Buffer + &H18 Overlay
'IP address of source
Dim T_ip_srcaddr0 As Byte At Buffer + &H1A Overlay
Dim T_ip_srcaddr1 As Byte At Buffer + &H1B Overlay
Dim T_ip_srcaddr2 As Byte At Buffer + &H1C Overlay
Dim T_ip_srcaddr3 As Byte At Buffer + &H1D Overlay
Dim T_ip_srcaddr As Long At Buffer + &H1A Overlay
'IP address of destination
Dim T_ip_destaddr0 As Byte At Buffer + &H1E Overlay
Dim T_ip_destaddr1 As Byte At Buffer + &H1F Overlay
Dim T_ip_destaddr2 As Byte At Buffer + &H20 Overlay
Dim T_ip_destaddr3 As Byte At Buffer + &H21 Overlay
Dim T_ip_destaddr As Long At Buffer + &H1E Overlay
Dim T_icmp_type As Byte At Buffer + &H22 Overlay
Dim T_icmp_code As Byte At Buffer + &H23 Overlay
Dim T_icmp_cksum0 As Byte At Buffer + &H24 Overlay
Dim T_icmp_cksum1 As Byte At Buffer + &H25 Overlay
Dim T_icmp_cksum As Word At Buffer + &H24 Overlay
Dim Tcp_srcporth As Byte At Buffer + &H22 Overlay
Dim Tcp_srcportl As Byte At Buffer + &H23 Overlay
Dim Tcp_destporth As Byte At Buffer + &H24 Overlay
Dim Tcp_destportl As Byte At Buffer + &H25 Overlay
Dim Tcp_seqnum3 As Byte At Buffer + &H26 Overlay
Dim Tcp_seqnum2 As Byte At Buffer + &H27 Overlay
Dim Tcp_seqnum1 As Byte At Buffer + &H28 Overlay
Dim Tcp_seqnum0 As Byte At Buffer + &H29 Overlay
Dim Tcp_acknum3 As Byte At Buffer + &H2A Overlay
Dim Tcp_acknum2 As Byte At Buffer + &H2B Overlay
Dim Tcp_acknum1 As Byte At Buffer + &H2C Overlay
Dim Tcp_acknum0 As Byte At Buffer + &H2D Overlay
Dim Tcp_hdr As Byte At Buffer + &H2E Overlay
Dim Tcp_flags As Byte At Buffer + &H2F Overlay
Dim Tcp_cksumh As Byte At Buffer + &H32 Overlay
Dim Tcp_cksuml As Byte At Buffer + &H33 Overlay
Dim Tcp_cksum As Word At Buffer + &H32 Overlay
'UDP header
Dim T_udp_srcport0 As Byte At Buffer + &H22 Overlay
Dim T_udp_srcport1 As Byte At Buffer + &H23 Overlay
Dim T_udp_srcport As Word At Buffer + &H22 Overlay
Dim T_udp_destport0 As Byte At Buffer + &H24 Overlay
Dim T_udp_destport1 As Byte At Buffer + &H25 Overlay
Dim T_udp_destport As Word At Buffer + &H24 Overlay
Dim T_udp_len0 As Byte At Buffer + &H26 Overlay
Dim T_udp_len1 As Byte At Buffer + &H27 Overlay
Dim T_udp_chksum0 As Byte At Buffer + &H28 Overlay
Dim T_udp_chksum1 As Byte At Buffer + &H29 Overlay
Dim T_udp_data As Byte At Buffer + &H2A Overlay
Dim T_udp_data1 As Byte At Buffer + &H2B Overlay
Dim T_udp_data2 As Byte At Buffer + &H2C Overlay
Dim T_udp_data3 As Byte At Buffer + &H2D Overlay
Dim T_udp_data4 As Byte At Buffer + &H2E Overlay
Dim T_udp_data5 As Byte At Buffer + &H2F Overlay
Dim T_udp_data6 As Byte At Buffer + &H30 Overlay
Dim T_udp_data7 As Byte At Buffer + &H31 Overlay
Dim T_udp_data8 As Byte At Buffer + &H32 Overlay
Dim T_udp_data9 As Byte At Buffer + &H33 Overlay
Dim T_udp_data10 As Byte At Buffer + &H34 Overlay
Dim T_udp_data11 As Byte At Buffer + &H35 Overlay
Dim T_udp_data12 As Byte At Buffer + &H36 Overlay
Dim T_udp_data13 As Byte At Buffer + &H37 Overlay
Dim T_udp_data14 As Byte At Buffer + &H38 Overlay
Dim T_udp_data15 As Byte At Buffer + &H39 Overlay
Dim T_udp_data16 As Byte At Buffer + &H3A Overlay
Dim T_udp_data17 As Byte At Buffer + &H3B Overlay
Dim T_udp_data18 As Byte At Buffer + &H3C Overlay
Dim T_udp_data19 As Byte At Buffer + &H3D Overlay
Dim T_udp_data20 As Byte At Buffer + &H3E Overlay
Dim T_udp_data21 As Byte At Buffer + &H3F Overlay
Dim T_udp_data22 As Byte At Buffer + &H40 Overlay
Dim T_udp_data23 As Byte At Buffer + &H41 Overlay
Dim T_udp_data24 As Byte At Buffer + &H42 Overlay
Dim T_udp_data25 As Byte At Buffer + &H43 Overlay
Dim T_udp_data26 As Byte At Buffer + &H44 Overlay
Dim T_udp_data27 As Byte At Buffer + &H45 Overlay
Dim T_udp_data28 As Byte At Buffer + &H46 Overlay
Dim T_udp_data29 As Byte At Buffer + &H47 Overlay
Dim T_udp_data30 As Byte At Buffer + &H48 Overlay
Dim T_udp_data31 As Byte At Buffer + &H49 Overlay
Dim T_udp_data32 As Byte At Buffer + &H4A Overlay

I have kept the same names as in the RTL8019as-tutorial. Now we can use subroutines from the RTL8019as for the ENC28J60.

Declare Sub Udp_receive

' Routine to handle UDP-traffic
'
Sub Udp_receive
Print "UDP_Receive"
Local Udp_port As Word
Local Udp_porth As Byte
Local Udp_portl As Byte

Udp_porth = &H88
Udp_portl = &H13

Udp_port = Udp_porth
Shift Udp_port , Left , 8
Udp_port = Udp_port + Udp_portl

'From within a VB-program

'non-echo on PORT 5000
If T_udp_destport = Udp_port Then
Select Case T_udp_data
Case &H00 : Print "Action A"
Case &H01 : Print "Action B"
Case &H02 : Print "Action C"
Case &H03 : Print "Action D"
Case &H04 : Print "Action E"
Case Else : Print "Action F"
End Select
Exit Sub
End If
'echo on PORT 7
If T_udp_destport = &H0700 Then
'Build The Ip Header
Call Setipaddrs
'swap the UDP source and destinations port
Swap T_udp_srcport0 , T_udp_destport0
Swap T_udp_srcport1 , T_udp_destport1
Call Udp_checksum
Call Echopacket
End If
End Sub

Declare Sub Udp_checksum

' Routine to calculate the Udp-checksum
'
Sub Udp_checksum
T_udp_chksum0 = &H00
T_udp_chksum1 = &H00
'checksum TCP header
I_chksum32 = 0
I_value16h = T_ip_srcaddr0
I_value16l = T_ip_srcaddr1
I_chksum32 = I_chksum32 + I_value16
I_value16h = T_ip_srcaddr2
I_value16l = T_ip_srcaddr3
I_chksum32 = I_chksum32 + I_value16
I_value16h = T_ip_destaddr0
I_value16l = T_ip_destaddr1
I_chksum32 = I_chksum32 + I_value16
I_value16h = T_ip_destaddr2
I_value16l = T_ip_destaddr3
I_chksum32 = I_chksum32 + I_value16
'proto
I_chksum32 = I_chksum32 + T_ip_proto
'packet length
I_value16h = T_udp_len0
I_value16l = T_udp_len1
I_chksum32 = I_chksum32 + I_value16
I_odd = T_udp_len1 Mod 2
Result16h = T_udp_len0
Result16l = T_udp_len1
'udp_srcport0 = packet(&h23)
Val1 = &H23
Val2 = &H23 + Result16
Val2 = Val2 - 2
Call General_part_checksum(val1 , Val2)
T_udp_chksum0 = Val3
T_udp_chksum1 = Val4
End Sub

Declare Sub Ip_header_checksum

' Routine to calculate a IP-header checksum
'
Sub Ip_header_checksum
Local Ip_x As Byte
Local Ip_hulp1 As Byte
Local Ip_chksum32 As Long
Local Ip_checksum16 As Word
Local Ip_temp16 As Word
Local Ip_header_length As Byte
'calculate the IP header checksum
T_ip_hdr_cksum = &H00
I_chksum32 = 0
Ip_header_length = T_ip_vers_len And &H0F
Ip_header_length = 4 * Ip_header_length

I_chksum32 = 0
I_odd = 0

Val1 = 15
Val2 = &H0E + Ip_header_length
Call General_part_checksum(val1 , Val2)
T_ip_hdr_cksum0 = Val3
T_ip_hdr_cksum1 = Val4
End Sub
 

Declare Sub General_part_checksum(byval Val1 As Byte , Byval Val2 As Word)

' Overall routine for checksum
'
Sub General_part_checksum(byval Val1 As Byte , Byval Val2 As Word)
For I_x = Val1 To Val2 Step 2
I_value16h = Buffer(i_x)
Hulp3 = I_x + 1
I_value16l = Buffer(hulp3)
I_chksum32 = I_chksum32 + I_value16
Next I_x
If I_odd = 1 Then
Incr Val2
I_value16h = Buffer(val2)
I_value16l = 0
I_chksum32 = I_chksum32 + I_value16
End If
I_checksum16 = Highw(i_chksum32)
I_checksum16 = I_checksum16 + I_chksum32 ' only 16 lower bits of i_chksum32 is taken...
I_checksum16 = Not I_checksum16
Val3 = High(i_checksum16)
Val4 = Low(i_checksum16)
End Sub
 

Declare Sub Echopacket

'Routine to echo packet
'
Sub Echopacket
Print "Echopacket"
'packetlengte
Hulp2 = T_udp_len0 * 256
Hulp2 = Hulp2 + T_udp_len1
Hulp2 = Hulp2 + 34
Call Enc28j60packetsend(hulp2)
End Sub
 

Declare Sub Setipaddrs

' Routine to handle the source/destination address
'
Sub Setipaddrs
T_ip_destaddr = T_ip_srcaddr
'make ethernet module IP address source address
T_ip_srcaddr = My_ip
Call Setst_mac
Call Ip_header_checksum
End Sub

On the Tuxgraphics-board the UDP-traffic arrives like this

 

Nice way to test your UDP traffic on port 7 and 5000.
With the EDTP Electronics Test Panel from www.edtp.com

Here the complete code up-to this point....
tux_upto_udp.txt
enc28j60.inc

The source of the EDTP Electronics Test Panel can be downloaded here

Thanks Fred Eady!!

 

Step 34: Filtering at work

 

BTW, If I change the PING, ARP, UDP and TCP junction a bit I can see Unicast filtering working.
If I change the IP-number in the UDP-client to go to another address, the UDP-data doesn't arrive at the Atmega168, it is filtered by the ENC28J60. Same for PING. Got still to check the broadcast filtering.

If Buffer(14) = &H00 Then 'Type:IP
   If Buffer(15) = &H45 Then 'We handle only simple IP packets
      If Buffer(21) = 0 Then 'We handle only non fragmented packets
    
    'If Buffer(31) = Ipaddr0 Then 'Ip packet for us
            'If Buffer(32) = Ipaddr1 Then
               'If Buffer(33) = Ipaddr2 Then
                  'If Buffer(34) = Ipaddr3 Then
   
                 If Buffer(24) = 1 Then 'Protocol:ICMP
                        If Buffer(35) = &H08 Then 'ICMP echo request
                           Call Pingreply
                        End If
                     End If
                     If Buffer(24) = 17 Then                   'Protocol:UDP
                        Call Udp
                     End If
                     If Buffer(24) = 6 Then                     'Protocol:TCP
                        Call Tcp
                     End If
                  'End If
               'End If
            'End If
         'End If

      End If
   End If
End If
 

Step 35: $crystal, something to take care of
Got a mail from Evert Dekker. Evert is a Bascom-Guru check his website at https://www.evertdekker.com Evert mentioned that I will get in trouble when I won't use the right crystal-frequency in the top of the ENC28J60-program. The Atmega168 will get its clock from the ENC28J60. The Tuxgraphics-board is started with a non-initialized ENC28J60 which gives a frequency of 6.25 Mhz. During the initialization of the ENC28J60 the frequency is doubled by these lines

'clock from default divide/4 (6.25 Mhz) to divide/2 (12.5 Mhz)
Call Enc28j60writecontrolregbyte(ecocon , &B00000010)
 

After the frequency is doubled all time-related commands in Bascom-AVR, like waitms, I2c-routines, LCD, serial etc. etc. will not have the right timing. So to correct this I have changed the 6.25 Mhz in 12.5 Mhz in the top of the program. Only the config spi is done on the wrong frequency, but the rest of the program has it's right frequency reference.

Will use these setting in the rest of the program

$regfile = "m168def.dat"
$crystal = 12500000
$baud = 19200

Thanks Evert!!

 

Step 36: Using Bascom TCPIP-library
For the Easy TCP/IP board from www.mcselec.com there is a special library. We can use a function of this library to save about 1000 bytes of flash.  

$lib "tcpip.lbx" 

By using this command in Bascom, the library TCPIP.LBX is inserted in our code and we can use the TCPCHECKSUM command.

The complete code, will go from > 9400 bytes to 8512 bytes.

tux_120907_compact

Step 37: SNTP Simple Network Time Protocol....

There are several protocols that use UDP. Simple Network Time Protocol is one of them. To show you how easy it is to implement it in to the existing code see below:

Add at the top of the program
$lib "datetime.lbx"

Add this to get the date and time in the right format
Config Date = Dmy , Separator = -

Add a few dimensions
'for NTP-routine
Dim S(4) As Byte
Dim L1 As Long At S Overlay ' Overlay a long variable to receive-string
' with overlay you need no transfer from the byte-array to a long-variable
Dim L2 As Long
 

Add a few lines in the UDP_receive routine

Sub Udp_receive
'Print "UDP_Receive"
Local Udp_port As Word
Local Udp_porth As Byte
Local Udp_portl As Byte

Udp_porth = &H88
Udp_portl = &H13

Udp_port = Udp_porth
Shift Udp_port , Left , 8
Udp_port = Udp_port + Udp_portl

'From within a VB-program
'non-echo on PORT 5000
If T_udp_destport = Udp_port Then
If T_udp_srcport = &H2500 Then ' &h0025 NTP protocol
     Call Ntp
     Exit Sub
End If

 

Add two subroutines

Declare sub Ntp_get
Declare sub Ntp
 

' Routine to get the NetWork Time from a time-server
'
Sub Ntp_get
'MAC-header
'Destination hardware address           
' You have to put your router-mac-address here
T_enetpacketdest0 = &H00                
 ' 00-0c-41-ae-7a-dc MAC-address of my router
T_enetpacketdest1 = &H0C
T_enetpacketdest2 = &H41
T_enetpacketdest3 = &HAE
T_enetpacketdest4 = &H7A
T_enetpacketdest5 = &HDC
' source (own source)
T_enetpacketsrc0 = Mymac(1)
T_enetpacketsrc1 = Mymac(2)
T_enetpacketsrc2 = Mymac(3)
T_enetpacketsrc3 = Mymac(4)
T_enetpacketsrc4 = Mymac(5)
T_enetpacketsrc5 = Mymac(6)
T_enetpackettype = &H0008                              ' = &h0800
' fill IP-header
T_ip_vers_len = &H45
T_tos = &H00
T_ip_pktlen0 = &H00
T_ip_pktlen1 = &H30
T_id0 = &H4A
T_id1 = &HA5
T_flags = &H00
T_offset = &H00
T_ttl = &H80
'protocol (ICMP=1, TCP=6, UDP=11)
T_ip_proto = &H11
'header checksum
'IP address of source
T_ip_srcaddr0 = Myip(1)
T_ip_srcaddr1 = Myip(2)
T_ip_srcaddr2 = Myip(3)
T_ip_srcaddr3 = Myip(4)
'IP address of destination                                      'you have to put the IP-number of
T_ip_destaddr0 =
193                                            ' the NTP-server here
T_ip_destaddr1 =
67
T_ip_destaddr2 =
79
T_ip_destaddr3 =
202
'UDP-header
T_udp_srcport0 = &H13
T_udp_srcport1 = &H88
T_udp_destport0 = &H00                                        'port 0025 = 37 NTP
T_udp_destport1 = &H25
T_udp_len0 = &H00
T_udp_len1 = &H1C
T_udp_data = Asc( "X")
T_udp_data1 = &H0A ' lf
T_udp_data2 = &H0D ' cr

Call Ip_header_checksum
Call Udp_checksum
Call Echopacket
End Sub

' Routine to convert the LONG from the NTP-server in to date and time
'
Sub Ntp
S(1) = T_udp_data
S(2) = T_udp_data1
S(3) = T_udp_data2
S(4) = T_udp_data3
Swap S(1) , S(4) : Swap S(2) , S(3)
L2 = L1 + 1139293696
L2 = L2 + 3600 ' offset UTC + 1 hour
Print "Date : " ; Date(l2)
Print "Time : " ; Time(l2)
End Sub

And to get the time on the startup of the Tuxgraphics-board add

Call Enc28j60init

Call Ntp_get                       'get the Network Time once

Do
Call Enc28j60poll
Loop

End

You can call the Ntp_get-routine as much as you like in every part of the program. Just be carefull not to overdo it, your IP-number might end up on the blacklist of the NTP-server. When you startup the Tuxgraphics-board you will see this

And while we are busy, to reclaim the extra bytes we have used we could put Optimize on in the Bascom-AVR options so get some bytes back. Now 9528 bytes are used.


 

Step 38: TCP and HTTP

We continue the tutorial with HTTP, a TCP-protocol. Earlier I mentioned that with only 1024 RAM Byte it would be difficult to do a full blown HTTP, but we could do just with a single HTML page.

In the En28j60packetreceive we got a selection for the protocols.

If Buffer(24) = 6 Then                                    'Protocol:TCP
     Call Tcp
End If

' TCP
'
Sub Tcp
Print "Subroutine TCP"
Local Work As Byte
Work = Tcp_flags
Tcp_fin = Work.0
Tcp_syn = Work.1
Tcp_rst = Work.2
Tcp_psh = Work.3
Tcp_ack = Work.4
Tcp_urg = Work.5

If Tcp_destporth = 0 Then
     Select Case Tcp_destportl
          Case 80 :
Call Http
     End Select
End If
End Sub

'HTTP
'

Sub Http
Print "Subroutine HTTP"

' If an ACK is received and the destination port address is valid
' and no data is in the packet
 If Tcp_ack = 1 Then
'we still have to build this routine
End If

' This code segment processes the incoming SYN from the client
' and sends back the initial sequence number (ISN) and acknowledges
' the incoming SYN packet
If Tcp_syn = 1 Then
'we still have to build this routine
End If

If Tcp_fin = 1 Then
'we still have to build this routine
End If

 ' If an ACK and PSH is received and the destination port address is valid
If Tcp_ack = 1 Then
   If Tcp_psh = 1 Then
      Print "Tcp_ack = 1 AND Tcp_psh = 1 "
      'we still have to build this routine
   End If
End If

End Sub
 

Step 38 A: Tcp_syn
If you check with a networksniffer the traffic when you go to https://benshobbycorner.nl/bzijlstra you will see a burst of traffic. First of all a ARP-request and a ARP-reply. Then a TCP session is established, with TCP-flags we will see this sequence

Quote from the book "TCP/IP Lean" from Jeremy Bentham:

SYN

When the client wants to open a connection, you create a new sequence number using your connection count as the high word and a value of FFFFh as the low word and respond with an acknowledgment value one greater than the received sequencenumber.

End quote from the book "TCP/IP Lean" from Jeremy Bentham

' This code segment processes the incoming SYN from the client
' and sends back the initial sequence number (ISN) and acknowledges
' the incoming SYN packet
If Tcp_syn = 1 Then
     Print "Tcp_syn = 1"
     'Move IP source address to destination address
     T_ip_destaddr = T_ip_srcaddr
     'Make ethernet module IP address source address
     T_ip_srcaddr = My_ip
     Swap Tcp_srcportl , Tcp_destportl
     Swap Tcp_srcporth , Tcp_destporth
     Tcpdatalen_in = 1
     Client_seqnum0 = Tcp_seqnum0
     Client_seqnum1 = Tcp_seqnum1
     Client_seqnum2 = Tcp_seqnum2
     Client_seqnum3 = Tcp_seqnum3
     Client_seqnum = Client_seqnum + Tcpdatalen_in
     Tcp_acknum0 = Client_seqnum0
     Tcp_acknum1 = Client_seqnum1
     Tcp_acknum2 = Client_seqnum2
     Tcp_acknum3 = Client_seqnum3
     Tcp_seqnum0 = &HFF                                         'Initial sequencenumber
     Tcp_seqnum1 = &HFF
     Tcp_seqnum2 = &H10
     Tcp_seqnum3 = &H11
     'move hardware source address to destination address
     T_enetpacketdest0 = T_enetpacketsrc0
     T_enetpacketdest1 = T_enetpacketsrc1
     T_enetpacketdest2 = T_enetpacketsrc2
     T_enetpacketdest3 = T_enetpacketsrc3
     T_enetpacketdest4 = T_enetpacketsrc4
     T_enetpacketdest5 = T_enetpacketsrc5
     'Make ethernet module mac address the source address
     T_enetpacketsrc0 = Mymac(1)
     T_enetpacketsrc1 = Mymac(2)
     T_enetpacketsrc2 = Mymac(3)
     T_enetpacketsrc3 = Mymac(4)
     T_enetpacketsrc4 = Mymac(5)
     T_enetpacketsrc5 = Mymac(6)
     Set Flags.synflag
     Tcp_flags = 0
     Set Tcp_flags.1
     Set Tcp_flags.4
     T_ip_pktlen0 = &H00
     T_ip_pktlen1 = 48
     Call Ip_header_checksum
     Call Tcp_checksum
     Call Enc28j60packetsend(62)
End If

The HTTP-subroutine, with just the TCP_SYN-part will already establish a connection with the client.

Remember, the Tuxgraphics-board has 192.168.0.61, my PC has 192.168.0.225.

In the Browser of the PC a HTTP://192.168.0.61 is typed
#1 - An ARP-request from the PC. It is a broadcast on the segment 192.168.0.x
#2 - the Tuxgraphicsboard is responding (ARP-Reply)
#3 - this a ACK RST from the PC
#4 - a TCP-session is initiated by the PC sending a SYN
#5 - the Tuxgraphics-board is responding with a SYN and ACK
#6 - the PC sends a ACK

after this the PC will send the HTTP-request with a ACK PSH and with several ACK and ACK PSH the complete HTML-page is send to the PC.
 

Step 38 B: Tcp_ack


SYN and ACK and now waiting for the ACK PSH flags....

Code till this point...

See also the UDP and TCP checksum calculation with Bascom's TCPCHECKSUM
 

Step 38 C: Tcp_ack AND Tcp_psh

 

'If an ACK and PSH is received and the destination port address is valid
If Tcp_ack = 1 Then
   If Tcp_psh = 1 Then
      Print "Tcp_ack = 1 AND Tcp_psh = 1 "

      'ACK and PSH

      'set flags
      Tcp_flags = 0
      Set Tcp_flags.4 'ack

      'Move IP source address to destination address
      T_ip_destaddr = T_ip_srcaddr
      'Make ethernet module IP address source address
      T_ip_srcaddr = My_ip
      Swap Tcp_srcportl , Tcp_destportl
      Swap Tcp_srcporth , Tcp_destporth

      'swap mac
      Call Packetshape

      Incoming_ack0 = Tcp_seqnum0
      Incoming_ack1 = Tcp_seqnum1
      Incoming_ack2 = Tcp_seqnum2
      Incoming_ack3 = Tcp_seqnum3

      Incoming_ack = Incoming_ack + 475                            '489 - 14

      Tcp_seqnum0 = Tcp_acknum0
      Tcp_seqnum1 = Tcp_acknum1
      Tcp_seqnum2 = Tcp_acknum2
      Tcp_seqnum3 = Tcp_acknum3

      Tcp_acknum0 = Incoming_ack0
      Tcp_acknum1 = Incoming_ack1
      Tcp_acknum2 = Incoming_ack2
      Tcp_acknum3 = Incoming_ack3

      Buffer(17) = 0
      Buffer(18) = 40

      '5 x 4 = 20 bytes
      Buffer(47) = &H50
      'flag ACK
      Buffer(48) = &H10

      'padding
      Buffer(55) = 0
      Buffer(56) = 0
      Buffer(57) = 0
      Buffer(58) = 0
      Buffer(59) = 0
      Buffer(60) = 0

      Call Ip_header_checksum
      Call Tcp_checksum
      Call Enc28j60packetsend(60)
      Ackpsh = 1
   End If
End If

In this hardcopy of the Tera Term terminal screen you can see the PSH and ACK the clients browser is sending to get the HTML-page. It is repeated until a time-out or until you push the STOP-button of your browser. That can be seen on the last 7 lines, a ACK RST is send by the client.

BTW This can only be seen if you change this
'/* Maximum frame length - The Atmega168 has only 1024 byte internal RAM! */
Const Max_framelen = 500 '500 byte will be enough for common tasks

 

Step 38 D: Tcp_ack after a ACK and PSH from client
 

If Tcp_ack = 1 Then
   If Buffer(17) = 0 Then
      If Buffer(18) = 40 Then
         If Ackpsh = 1 Then

         'ACK after a ACK-PSH

         'set flags
         Tcp_flags = 0
         Set Tcp_flags.4                               'ack
         Set Tcp_flags.3                               'psh
         Set Tcp_flags.0                               'fin

         'Move IP source address to destination address
         T_ip_destaddr = T_ip_srcaddr
         'Make ethernet module IP address source address
         T_ip_srcaddr = My_ip
         Swap Tcp_srcportl , Tcp_destportl
         Swap Tcp_srcporth , Tcp_destporth

         'swap mac
         Call Packetshape

         Incoming_ack0 = Tcp_seqnum0
         Incoming_ack1 = Tcp_seqnum1
         Incoming_ack2 = Tcp_seqnum2
         Incoming_ack3 = Tcp_seqnum3

         Tcp_seqnum0 = Tcp_acknum0
         Tcp_seqnum1 = Tcp_acknum1
         Tcp_seqnum2 = Tcp_acknum2
         Tcp_seqnum3 = Tcp_acknum3

         Tcp_acknum0 = Incoming_ack0
         Tcp_acknum1 = Incoming_ack1
         Tcp_acknum2 = Incoming_ack2
         Tcp_acknum3 = Incoming_ack3

         Buffer(17) = 0
         Buffer(18) = 40

         Tempword3 = &H37

         Restore Htmlcode
         Do

            Read Msg_temp
            Print Msg_temp
            Msg_temp2 = Right(msg_temp , 8)
            If Msg_temp2 = "endblock" Then
               Exit Do
            End If
            For Y = 1 To Len(msg_temp)
               Tempstring1 = Mid(msg_temp , Y , 1)
               Buffer(tempword3) = Asc(tempstring1)
               Incr Tempword3
            Next Y
         Loop
         Buffer(181) = &H0D
         Buffer(182) = &H0A

         T_ip_pktlen0 = 0
         T_ip_pktlen1 = 48

         Buffer(17) = 0                                                          'HTML-code + 40
         Buffer(18) = &HA8

         Call Ip_header_checksum
         Call Tcp_checksum
         Call Enc28j60packetsend(182)

         End If
     End If
  End If
End If

 

Step 39: Tcp_checksum
 

Dim Tcp_fin As Bit
Dim Tcp_syn As Bit
Dim Tcp_rst As Bit
Dim Tcp_psh As Bit
Dim Tcp_ack As Bit
Dim Tcp_urg As Bit
Dim Tcpdatalen_in As Word
Dim Flags As Byte

Dim Client_seqnum As Long
Dim Client_seqnum0 As Byte At Client_seqnum Overlay
Dim Client_seqnum1 As Byte At Client_seqnum + 1 Overlay
Dim Client_seqnum2 As Byte At Client_seqnum + 2 Overlay
Dim Client_seqnum3 As Byte At Client_seqnum + 3 Overlay

Dim Tempword As Word
Dim Tempwordh As Byte At Tempword + 1 Overlay
Dim Tempwordl As Byte At Tempword Overlay

Dim Val1 As Word
Dim Val2 As Word

Const Synflag = 0
Const Finflag = 1
 

'Tcp-checksum
'
Sub Tcp_checksum
Local Whulp1 As Word
Local Tempword2 As Word
Tcp_cksum = 0
I_chksum32 = 0
Tempwordh = T_ip_srcaddr0
Tempwordl = T_ip_srcaddr1
I_chksum32 = Tempword
Tempwordh = T_ip_srcaddr2
Tempwordl = T_ip_srcaddr3
I_chksum32 = I_chksum32 + Tempword
Tempwordh = T_ip_destaddr0
Tempwordl = T_ip_destaddr1
I_chksum32 = I_chksum32 + Tempword
Tempwordh = T_ip_destaddr2
Tempwordl = T_ip_destaddr3
I_chksum32 = I_chksum32 + Tempword

I_chksum32 = I_chksum32 + T_ip_proto

Tempwordh = T_ip_pktlen0
Tempwordl = T_ip_pktlen1
I_chksum32 = I_chksum32 + Tempword

Tempword2 = T_ip_vers_len And &H0F
Tempword2 = Tempword2 * 4
I_chksum32 = I_chksum32 - Tempword2

Whulp1 = Tempword - 20
Val2 = Highw(i_chksum32)
Val1 = I_chksum32

I_checksum16 = Tcpchecksum(buffer(&H23) , Whulp1 , Val2 , Val1)

Tcp_cksuml = High(i_checksum16)
Tcp_cksumh = Low(i_checksum16)

End Sub

 


Nearly there...

In the Browser of the PC a HTTP://192.168.0.61 is typed
#1 - a TCP-session is intiated by the PC sending a SYN
#2 - the Tuxgraphicsboard is responding with ACK SYN
#3 - ACK is send by client
#4 - ACK and PSH from client with the HTTP GET request
#5 - the Tuxgraphics-board is responding with HTML-page with ACK, PSH and FIN
#6 - the PC sends a ACK
#7 - ACK and FIN from client
#8 - ACK from Tuxgraphics board
#9 - ACK from client

Here the HTML-code

' htmlcode
'
Htmlcode:
Data "HTTP/1.0 200" , &H0D , &H0A , &H0D , &H0A
Data "<html><head><title>Tux</title></head><body>"
Data "<img src=" , &H22 , "https://benshobbycorner.nl/bzijlstra/bens.gif" , &H22 , ">"
Data "</body></html>" , &H0D , &H0A
Data &H0D , &H0A
Data "endblock"

And here the result, a very easy page with a picture from the outside world. It's a beginning.... the code has to be optimized (lots of same commands can be put in a single subroutine) and some finetuning has to be done. 12076 bytes flash used.

Complete code till now, but still have to be optimized and fine-tuned.
tux_udp_ntp_tcp_http.txt

Remember, in the Bascom-options you have to put optimize code on.
Furthermore, if you want to use the NTP-routine you have to fill in the
MAC-address of your own router.
 

Step 40: Optimized code
Well, here it is, the optimized code. With some examples getting date and time

Final, optimized code

tux_udp_ntp_tcp_http_280907.txt

Step 41: Some interaction, putting LED on/off by UDP and TCP

To get some interaction with the LED on the TuxGraphics-board you can add some minor parts

you have to change this...

Const Max_framelen = 590             'still a bit oversized...

' htmlcode
'
Htmlcode:
Data "HTTP/1.0 200" , &H0D , &H0A , &H0D , &H0A
Data "<html><head><meta http-equiv=" , &H22
Data "Pragma" , &H22 , " Content=" , &H22 , "no cache" , &H22 , ">"
Data "<title>Tux</title></head><body>"
Data "<img src=" , &H22 , "https://benshobbycorner.nl/bzijlstra/einstein.jpg" , &H22 , ">"
Data "<body link=" , &H22 , "#FFFFFF " , &H22 , "vlink=" , &H22"
Data "#FFFFFF " , &H22 , "alink=" , &H22 , "#FFFFFF>"
Data "<div align=" , &H22 , "left" , &H22 , ">"
Data "<table border=" , &H22 , "1" , &H22 , " width=" , &H22 , "21%" , &H22 , ">"
Data "<tr><td width=" , &H22 , "98" , &H22 , " align=" , &H22 , "left" , &H22 , ">"
Data "<p align=" , &H22 , "center" , &H22 , ">"
Data "<a href=" , &H22 , "LEDON" , &H22
Data "><font color=" , &H22 , "#990033" , &H22 , ">LED ON</font></a></td>"
Data "<td align=" , &H22 , "left" , &H22 , ">"
Data "<p align=" , &H22 , "center" , &H22 , ">"
Data "<a href=" , &H22 , "LEDOFF" , &H22 , "><font color="
Data &H22 , "#008080" , &H22 , ">LED OFF</font></a></td>"
Data "</tr></table></div>"
Data "sizeused"
Data "endblock"

The line <meta http-equiv="Pragma" Content="no cache">
prevents the page from beeing cached. So if you do a
http://192.168.0.61 it has to refresh the page from the Tuxgraphics-board and not get the page from the PC's cache.

And in the HTTP-routine add this

' If an ACK and PSH is received and the destination port address is valid
If Tcp_ack = 1 Then
If Tcp_psh = 1 Then


'search for GET / and check for a parameter
'
' walk through the packet until GET / is found

For Z = 40 To 200
   Ztemp = Z
   Tempstring2 = Chr(buffer(ztemp))
   Incr Ztemp
   Tempstring2 = Tempstring2 + Chr(buffer(ztemp))
   Incr Ztemp
   Tempstring2 = Tempstring2 + Chr(buffer(ztemp))
   Incr Ztemp
   Tempstring2 = Tempstring2 + Chr(buffer(ztemp))
   Incr Ztemp
   Tempstring2 = Tempstring2 + Chr(buffer(ztemp))
   Incr Ztemp
   Tempstring2 = Tempstring2 + Chr(buffer(ztemp))
   Incr Ztemp
   Tempstring2 = Tempstring2 + Chr(buffer(ztemp))
   Incr Ztemp
   Tempstring2 = Tempstring2 + Chr(buffer(ztemp))
   Incr Ztemp
   Tempstring2 = Tempstring2 + Chr(buffer(ztemp))
   Incr Ztemp
   Tempstring2 = Tempstring2 + Chr(buffer(ztemp))
   Incr Ztemp
   Tempstring2 = Tempstring2 + Chr(buffer(ztemp))
   Incr Ztemp
   Tempstring2 = Tempstring2 + Chr(buffer(ztemp))
   Incr Ztemp
   Tempstring2 = Tempstring2 + Chr(buffer(ztemp))
   If Left(tempstring2 , 5) = "GET /" Then
      Exit For
   End If
Next Z
If Mid(tempstring2 , 6 , 5) = "LEDON" Then
   Reset Led
End If
If Mid(tempstring2 , 6 , 6) = "LEDOFF" Then
   Set Led
End If

Perhaps I can do better next time, code looks a bit funny like this.
BTW this is also the place where you can add your own commands. Perhaps this tempstring2 can be replaced by a string overlaying the place where GET /LEDON and GET /LEDOFF can be found. Strings in Bascom are always terminated by a 00 so we have to be carefull about that.

Working with Linux? There are some comments about browsing with Linux in the tutorial for the RTL8019as somewhere on my homepage. Perhaps you have to change this line For Z = 40 To 200 to make the for/next a bit longer. You also have to change the Z in a word instead of a byte. I will do some testing.

And if you want to switch LED on/off with UDP add this:

Declare Sub Blinky(byval L As Byte)
 

Sub Blinky(l As Byte)
   Local Count As Byte
   For Count = 1 To L
   Reset Led
   Waitms 250
   Set Led
   Waitms 250
   Next Count
End Sub
 

And change this in the UDP-receive routine:

Select Case T_udp_data
   Case &H00 : Call Blinky(1)
   Case &H01 : Call Blinky(2)
   Case &H02 : Call Blinky(3)
   Case &H03 : Call Blinky(4)
   Case &H04 : Call Blinky(5)
End Select

With the EDTP Electronics Internet Test Panel you can blink the LED. Depending on the button you press the LED will blink several times.

tux_udp_ntp_tcp_http_300907.txt

And perhaps some hints:

The RAM of the Atmega168 is fully used now. Perhaps changing GLOBAL variables in LOCAL variables can free up some RAM memory. Care should be taken however. Flash memory, 12808 bytes are used. So still a few kBytes free. You can always consider stripping the UDP and NTP-code when you will be using HTTP only. First of all you can strip the NTP-get routine and the $lib "datetime.lbx", second strip the complete UDP-routine.
 

Step 42: More help, Wake-On-Lan....
In the Bascom Forum on www.mcselec.com an entry from rdagger from Comoros Islands. He has written a Wake-on-Lan routine to wake up other computers. Done by sending a Magic Packet.

What is Wake-On-Lan?

It depends on who you are, there are two companies who are claim credit, and maybe Al Gore and Microsoft will claim credit soon, for the technology behind Wake on Lan (WoL). Both AMD and IBM claim on their web site that WoL was their idea but thankfully we all benefit from the idea. WoL is both a hardware and software solution to allow a computer to be woken up remotely. Much like a modern television set, a computer that is Advanced Configuration Power Interface (ACPI) compliant can be turned on remotely, note that while you can currently only turn your television set on from within a certain distance WoL allows you to remotely start a computer from anywhere in the world, that is as long as it has an internet connection.

Unlike Wake on Modem, which doesn't require any special software, WoL requires a special software program to send a signal to the network card to make it work.

The Magic Packet is at the heart of Wake on Lan although it is not as magic as would first appear. The basic premise is that a specifically formatted packet send over a network is send to every network card and identifying features in this packet allow the network card to identify that the magic packet is intended for it. All the other cards therefore reject or rather dispose of the packet. It is analogous to standing in a crowded room of people and shouting out somebody's name, where nobody in the room has the same name, although everybody would hear you hopefully the only person to answer would be the person who's name you have just called out.

Declare Sub Wol

'Mac-address of PC to be woken
Wake_mac(1) = &H01
Wake_mac(2) = &H02
Wake_mac(3) = &H03
Wake_mac(4) = &H04
Wake_mac(5) = &H05
Wake_mac(6) = &H06

Call Wol

End

Sub Wol
'MAC-header
'Destination hardware address
T_enetpacketdest0 = Myroutermac(1)
T_enetpacketdest1 = Myroutermac(2)
T_enetpacketdest2 = Myroutermac(3)
T_enetpacketdest3 = Myroutermac(4)
T_enetpacketdest4 = Myroutermac(5)
T_enetpacketdest5 = Myroutermac(6)
' source (own source)
T_enetpacketsrc0 = Mymac(1)
T_enetpacketsrc1 = Mymac(2)
T_enetpacketsrc2 = Mymac(3)
T_enetpacketsrc3 = Mymac(4)
T_enetpacketsrc4 = Mymac(5)
T_enetpacketsrc5 = Mymac(6)
T_enetpackettype = &H0008
' fill IP-header
T_ip_vers_len = &H45
T_tos = &H00
T_ip_pktlen0 = &H00
T_ip_pktlen1 = &H30
T_id0 = &H4A
T_id1 = &HA5
T_flags = &H00
T_offset = &H00
T_ttl = &H80
'protocol (ICMP=1, TCP=6, UDP=11)
T_ip_proto = &H11
'header checksum
'IP address of source
T_ip_srcaddr0 = Myip(1)
T_ip_srcaddr1 = Myip(2)
T_ip_srcaddr2 = Myip(3)
T_ip_srcaddr3 = Myip(4)
'IP address of destination                                   'broadcast IP
T_ip_destaddr0 = &HFF
T_ip_destaddr1 = &HFF
T_ip_destaddr2 = &HFF
T_ip_destaddr3 = &HFF

'UDP-header
T_udp_srcport0 = &H13
T_udp_srcport1 = &H88
T_udp_destport0 = &H9C                                      'port 40000
T_udp_destport1 = &H40
T_udp_len0 = &H00
T_udp_len1 = &H6E

T_udp_data = &HFF                                           'WOL 6-byte trailer
T_udp_data1 = &HFF
T_udp_data2 = &HFF
T_udp_data3 = &HFF
T_udp_data4 = &HFF
T_udp_data5 = &HFF
                                                                                  'WOL magic packet = MAC address 16x
Dim Buf_counter As Byte                                     'Next UDP data (T_udp_data6) is buffer 49
Dim Mac_counter As Byte
Mac_counter = 1
For Buf_counter = 49 To 144
   Buffer(buf_counter) = Wake_mac(mac_counter)              'MAC Address of computer to wake
                                                                                                          (6 byte array)
   Incr Mac_counter
   If Mac_counter > 6 Then
      Mac_counter = 1
   End If
Next Buf_counter


Call Ip_header_checksum
Call Udp_checksum
Call Echopacket
End Sub

 

Step 43: DNS-query, an example
To show you how easy it is to do a DNS-query I made this routine. It's UDP, port 53 (DNS), you have to fill in the MAC-address of your own router, and the IP-address of www.home.nl is queried.

Call Udp_dns_query

Sub Udp_dns_query
   Call Rtr2dest
   Call Src2mymac
   T_enetpackettype = &H0008                     ' = &h0800
   T_ip_vers_len = &H45
   T_tos = &H00
   T_ip_pktlen0 = &H00
   T_ip_pktlen1 = &H39
   T_id0 = &H48
   T_id1 = &H15
   T_flags = &H00
   T_offset = &H00
   T_ttl = &H80
   'protocol (ICMP=1, TCP=6, UDP=11)
   T_ip_proto = &H11
   'checksum
   'checksum
   'IP address of source
   T_ip_srcaddr0 = Myip(1)
   T_ip_srcaddr1 = Myip(2)
   T_ip_srcaddr2 = Myip(3)
   T_ip_srcaddr3 = Myip(4)
   'IP address of destination                            'you have to put the IP-number of
   T_ip_destaddr0 = 213                                  ' the DNS-server here (@HOME)
   T_ip_destaddr1 = 51
   T_ip_destaddr2 = 144
   T_ip_destaddr3 = 37
   'UDP-header
   T_udp_srcport0 = &H04
   T_udp_srcport1 = &H12
   T_udp_destport0 = &H00                            ' port &H0035 = 53 DNS
   T_udp_destport1 = &H35
   T_udp_len0 = &H00
   T_udp_len1 = &H25
   'checksum
   'checksum
   'DNS-query
   T_udp_data = &H00                                      'ID
   T_udp_data1 = &HB4
   T_udp_data2 = &H01                                   'flags
   T_udp_data3 = &H00
   T_udp_data4 = &H00                                   'questions
   T_udp_data5 = &H01
   T_udp_data6 = &H00                                   'Answer RRs
   T_udp_data7 = &H00
   T_udp_data8 = &H00                                   'Authority
   T_udp_data9 = &H00
   T_udp_data10 = &H00                                 'Additional
   T_udp_data11 = &H00
   'queries
   T_udp_data12 = &H03                                 'length of www
   T_udp_data13 = Asc( "w")
   T_udp_data14 = Asc( "w")
   T_udp_data15 = Asc( "w")
   T_udp_data16 = &H04                                 'length of home
   T_udp_data17 = Asc( "h")
   T_udp_data18 = Asc( "o")
   T_udp_data19 = Asc( "m")
   T_udp_data20 = Asc( "e")
   T_udp_data21 = &H02                                 'length of nl
   T_udp_data22 = Asc( "n")
   T_udp_data23 = Asc( "l")
   T_udp_data24 = &H00
   'host address
   T_udp_data25 = &H00
   T_udp_data26 = &H01
   'Class: INET
   T_udp_data27 = &H00
   T_udp_data28 = &H01
   Call Ip_header_checksum
   Call Udp_checksum
   Call Echopacket
End Sub

Here www.home.nl is used. If you want to query mail.home.nl change this

   'queries
   T_udp_data12 = &H04                          'length of mail
   T_udp_data13 = Asc( "m")
   T_udp_data14 = Asc( "a")
   T_udp_data15 = Asc( "i")
   T_udp_data16 = Asc( "l" )
   T_udp_data17 = &H04                          'length of home
   T_udp_data18 = Asc( "h")
   T_udp_data19 = Asc( "o")
   T_udp_data20 = Asc( "m")
   T_udp_data21 = Asc( "e")
   T_udp_data22 = &H02                           'length of nl
   T_udp_data23 = Asc( "n")
   T_udp_data24 = Asc( "l")
   T_udp_data25 = &H00

Since the UDP-message is one longer....

   T_udp_len0 = &H00
   T_udp_len1 = &H26

Since the total packet is one longer....

   T_ip_pktlen0 = &H00
   T_ip_pktlen1 = &H3A


Or you can put it in a Function

Declare Sub Rtr2dest
Declare Function Dns_query(byval S As String) As String
Declare Sub Dns


Dim T_udp_data(32) As Byte At Buffer + &H2A Overlay

Call Enc28j60init

'lets try the DNS-query
Call Dns

Do
   Call Enc28j60poll
Loop

Sub Dns
Local Msg_temp As String * 55
Msg_temp =
Dns_query( "www.google.nl")
Msg_temp =
Dns_query( "mail.home.nl")
Msg_temp =
Dns_query( "www.home.nl")
End Sub

Function Dns_query(s As String) As String
Local Dns1 As Byte
Local Dns2 As Byte
Local Dns3 As Byte
Local Dns4 As String * 1
Local Dns5 As Byte
Local Msg_temp As String * 50
Dns1 = Split(s , Dns_name(1) , ".")
Dns2 = Len(s)
Call Rtr2dest
Call Src2mymac
T_enetpackettype = &H0008                         ' = &h0800
T_ip_vers_len = &H45
T_tos = &H00
T_ip_pktlen0 = &H00                                       'this depends on length string
T_ip_pktlen1 = 46 + Dns2
T_id0 = &H48
T_id1 = &H15
T_flags = &H00
T_offset = &H00
T_ttl = &H80
'protocol (ICMP=1, TCP=6, UDP=11)
T_ip_proto = &H11
'IP address of source
T_ip_srcaddr0 = Myip(1)
T_ip_srcaddr1 = Myip(2)
T_ip_srcaddr2 = Myip(3)
T_ip_srcaddr3 = Myip(4)
'IP address of destination                             'you have to put the IP-number of
T_ip_destaddr0 = 213                                    ' the DNS-server here (home.nl)
T_ip_destaddr1 = 51
T_ip_destaddr2 = 144
T_ip_destaddr3 = 37
'UDP-header
T_udp_srcport0 = &H04
T_udp_srcport1 = &H12
T_udp_destport0 = &H00                              'port 0035 = 53 DNS
T_udp_destport1 = &H35

T_udp_len0 = &H00                                       'this depends on length string
T_udp_len1 = 26 + Dns2
'DNS-query
T_udp_data(1) = &H00                                 'ID
T_udp_data(2) = &HB4
T_udp_data(3) = &H01                                 'flags
T_udp_data(4) = &H00
T_udp_data(5) = &H00                                 'questions
T_udp_data(6) = &H01
T_udp_data(7) = &H00                                 'Answer RRs
T_udp_data(8) = &H00
T_udp_data(9) = &H00                                 'Authority
T_udp_data(10) = &H00
T_udp_data(11) = &H00                               'Additional
T_udp_data(12) = &H00
'queries
T_udp_data(13) = Len(dns_name(1))
Dns5 = 14
For Dns3 = 1 To Len(dns_name(1))
Dns4 = Mid(dns_name(1) , Dns3 , 1)
T_udp_data(dns5) = Asc(dns4)
Incr Dns5
Next Dns3
T_udp_data(dns5) = Len(dns_name(2))
Incr Dns5
For Dns3 = 1 To Len(dns_name(2))
Dns4 = Mid(dns_name(2) , Dns3 , 1)
T_udp_data(dns5) = Asc(dns4)
Incr Dns5
Next Dns3
T_udp_data(dns5) = Len(dns_name(3))
Incr Dns5
For Dns3 = 1 To Len(dns_name(3))
Dns4 = Mid(dns_name(3) , Dns3 , 1)
T_udp_data(dns5) = Asc(dns4)
Incr Dns5
Next Dns3
T_udp_data(dns5) = &H00
'host address
T_udp_data(dns5 + 1) = &H00
T_udp_data(dns5 + 2) = &H01
'Class: INET
T_udp_data(dns5 + 3) = &H00
T_udp_data(dns5 + 4) = &H01
Call Ip_header_checksum
Call Udp_checksum
Call Echopacket
End Function


Sub Rtr2dest
T_enetpacketdest0 = &H00                   ' 00-0c-41-ae-7a-dc MAC-address of my router
T_enetpacketdest1 = &H0C
T_enetpacketdest2 = &H41
T_enetpacketdest3 = &HAE
T_enetpacketdest4 = &H7A
T_enetpacketdest5 = &HDC
End Sub

Code to this point. 15 bytes RAM and 2390 bytes FLASH free.
 

Step 44: Telnet, an example

Have stripped down the code until only the base-routines, ARP, PING, ICMP-checksum, IP-checksum and TCP-checksum remains. Using only these subroutines. Put reset and set external clock also in a subroutine. From here on started the Telnet piece of code.

'Declares
Declare Sub Enc28j60_reset
Declare Sub Enc28j60_setextclock
Declare Sub Enc28j60_init
Declare Sub Enc28j60_version
Declare Sub Enc28j60_readcontrolregbyte(byval Register As Byte)
Declare Sub Enc28j60_writecontrolregbyte(byval Register As Byte , Byval Enc28j60_value As Byte)
Declare Sub Enc28j60_selectbank(byval Enc28j60_bank As Byte)
Declare Sub Enc28j60_bitfield_set(byval Register As Byte , Byval Enc28j60_value As Byte)
Declare Sub Enc28j60_bitfield_clear(byval Register As Byte , Byval Enc28j60_value As Byte)
Declare Sub Enc28j60_readphyword(byval Phyregister As Byte)
Declare Sub Enc28j60_writephyword(byval Phyregister As Byte , Byval Enc28j60_wdata As Word)
Declare Sub Enc28j60_packetsend(byval Pcktlen As Word)
Declare Sub Enc28j60_packetreceive
Declare Sub Enc28j60_poll

Declare Sub Arpreply
Declare Sub Pingreply
Declare Sub Setip_id
Declare Sub Setipaddrs
Declare Sub Tcp
Declare Sub Ip_header_checksum
Declare Sub Icmp_checksum
Declare Sub Tcp_checksum
Declare Sub Srcdestchksum
Declare Sub Telnet

Was the HTTP-session easy, just a SYN, ACK SYN, a HTML-page with a ACK PSH FIN, this telnet-routine is a real TCP session. It keeps responding until the connection is closed on the client-side. When connecting a banner is send. Now it is possible to use something like USER NAME and PASSWORD to get a "secure" connection. Will make some kind of menu with options.

And here the code.

Some notes: Session is ended nicely, but instead of a ACK and after that a ACK FIN, I send a ACK FIN once. On a Telnet Windows machine, session is ended and in the Sniffer no problems seen. Will test this on a Linux-machine. Another thing to check is that all packets with a payload are send twice. Same Acknowledgenumber, same Sequencenumber.  Will correct that.

But everything is working like it should.

 

Step 45: DHCP, an example

Atilio Mosca has witten DHCP for the EASY TCP/IP from www.mcselec.com. I have done some rework on his program to get it working on the TuxGraphics board.

This is the flow for DHCP. First client does a DHCP-Discovery, several DHCP-servers can do a DHCP-Offer, a DHCP-Request from the client follows and to end the flow an DHCP-Acknowledge.

Const Dhcp_pack_request = &H01
Const Dhcp_htype10mb = &H01
Const Dhcp_htype100mb = &H02
Const Dhcp_hlenethernet = &H06
Const Dhcp_hops = &H00
Const Dhcp_secs = &H00
Const Dhcp_flags = &H80
Const Dhcpmessagetype = 53
Const Dhcp_discover = &H01
Const Dhcpparamrequest = 55
Const Subnetmask = 1
Const Router = 3
Const Dns = 6
Const Endoption = 255

Declare Sub Send_dhcp_discover
Declare Sub Clearbuff

Call Enc28j60_init
Call Send_dhcp_discover
Stop

Sub Send_dhcp_discover
Local Xx As Word

Call Clearbuff                                           'Clear the complete buffer

'Mac-header

Buffer(1) = &HFF
Buffer(2) = &HFF
Buffer(3) = &HFF
Buffer(4) = &HFF
Buffer(5) = &HFF
Buffer(6) = &HFF

Buffer(7) = Mymac(1)
Buffer(8) = Mymac(2)
Buffer(9) = Mymac(3)
Buffer(10) = Mymac(4)
Buffer(11) = Mymac(5)
Buffer(12) = Mymac(6)

Buffer(13) = &H08
Buffer(14) = &H00

'IP-header

Buffer(15) = &H45
Buffer(16) = &H00

Buffer(17) = &H01                                           'total length
Buffer(18) = &H16

Buffer(19) = &H05                                           'ID
Buffer(20) = &H0D

Buffer(21) = &H00                                           'flags

Buffer(22) = &H00                                           'fragment offset

Buffer(23) = &H80                                           'TTL

Buffer(24) = &H11                                           'protocol UDP

'checksum
'checksum

Buffer(27) = &H00                                           'source address
Buffer(28) = &H00
Buffer(29) = &H00
Buffer(30) = &H00

Buffer(31) = &HFF                                           'destination
Buffer(32) = &HFF
Buffer(33) = &HFF
Buffer(34) = &HFF

Buffer(35) = &H00                                           'source port
Buffer(36) = &H44
Buffer(37) = &H00                                           'destination port
Buffer(38) = &H43
Buffer(39) = &H01                                           'length
Buffer(40) = &H02

'checksum
'checksum

'DHCP
Buffer(43) = Dhcp_pack_request                             'request
Buffer(44) = Dhcp_htype10mb                                 '10 mb ethernet
Buffer(45) = Dhcp_hlenethernet                              'mac-address length
Buffer(46) = Dhcp_hops

Buffer(47) = Xid(1)                                                    'unique number
Buffer(48) = Xid(2)
Buffer(49) = Xid(3)
Buffer(50) = Xid(4)

Buffer(53) = Dhcp_flags                                             'flags
Buffer(71) = Mymac(1)
Buffer(72) = Mymac(2)
Buffer(73) = Mymac(3)
Buffer(74) = Mymac(4)
Buffer(75) = Mymac(5)
Buffer(76) = Mymac(6)

Buffer(279) = 99                                                          'DHCP cookie
Buffer(280) = 130
Buffer(281) = 83
Buffer(282) = 99

Buffer(283) = Dhcpparamrequest                            'request for subnetmask, router and dns
Buffer(284) = 3
Buffer(285) = Subnetmask
Buffer(286) = Router
Buffer(287) = Dns

Buffer(288) = Dhcpmessagetype                              'options
Buffer(289) = 1
Buffer(290) = Dhcp_discover

Buffer(291) = Endoption                                              'end options
Call Ip_header_checksum
Call Udp_checksum
Call Echopacket
End Sub

Sub Clearbuff
Local xx as word
For xx = 1 To Max_framelen
   
Buffer(xx) = 0
Next X
End Sub

If you want to try the DHCP-discover you will have to restore some of the UDP-routines, because we stripped them for the Telnet-part. You can also take the part where NTP works, this uses also a UDP-protocol. 

What I see in my network sniffer is:

The TuxGraphics sends a DHCP-Discover. My router is responding, his first step is do a ARP-request to the IP-number he wants to suggest, this is to check if this is realy free. If it is free a DHCP-Offer is send back by the router. The TuxGraphics-board has to respond with a DHCP-Request and the DHCP-server (my router) will respond with a DHCP-Acknowledge.


Finished projects...
Got a mail from -rdagger from the Comoros Islands, he has send me the Wake-On-Lan routines before. He has made his own ENC28J60-boards, used my base-routines, and the splendid results can be seen here...

https://www.rototron.info/?page=wol/wol.aspx

and here some pictures of his project

The Wake-on-Lan device

Adapter to facilitate prototyping

The adapter on the breadboard
 

Help from Czech Republic

Hi Ben,

While working on the webserver with AVR+ENC28j60 I made this small program.
I send you an easy utility for converting *.html to DATA format for Bascom.
It is helpfull for formatting html code for source code in Bascom.

Example 1 row of html code:
<meta content="text/html; charset=windows-1250"

1 row in Bascom DATA
Data " <meta content=",&H22,"text/html; charset=windows-1250",&H22

If you think this utility is useful then please place it on your webpages at Project AVR+ENC28j60.
Utility is freeware.

With regards

Richard Mrz\EDlek

HTMLtoBASCOM.ZIP


Thanks Richard!!

 

Some pictures
This board has kept me busy for a few months now!!!


And here another board with Bascom-AVR running on it.... With a Atmega644 on 20 Mhz.

www.lochraster.org Etherrape

And here the code. The Telnet-example.


If you want to connect the ENC28J60 to your own AVR microcontroller you will get something like this:

 

Thanks to:


Thanks to Mark Alberts
the creator of Bascom-AVR
www.mcselec.com

Thanks to Guido Socher from
https://tuxgraphics.org/electronics
The more I read the original source-code for the Tuxgraphics-board
the more I think Guido did a GREAT job!!

Thanks to Viktor Varga from Hungary for writing ARP and PING-routines.

Thanks to Fred Eady from www.edtp.com and www.nerdvilla.com
Check his ENC28J60-Framethrower and his EDTP Electronics Test Panel
Fred writes for Nuts & Volts and for Servo.

Thanks to Evert Dekker.
www.evertdekker.com
Evert has seen a 'bug' in the ENC28J60 software.
By using the wrong $crystal frequency all time-related Bascom-AVR commands
will have a wrong frequency-reference.

Thanks to rdagger
from Comoros Islands
He has written a Wake-on-Lan routine

Thanks to Richard Mrz\EDlek
from Czech Republic
He has written HTMLtoBASCOM-utility

Thanks to Piotr Rzeszut
from Poland
He found some errors in the code

 

Flags can be downloaded at www.3DFlags.com


Two nice books!!!

Ben Zijlstra - Ben's HobbyCorner - 2007