|
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 |
|