├── .gitignore ├── .vscode ├── c_cpp_properties.json └── settings.json ├── EtherCard.cpp ├── EtherCard.h ├── LICENSE ├── Makefile ├── README.md ├── artnet.cpp ├── artnet.h ├── dhcp.cpp ├── display.cpp ├── display.h ├── dmx.cpp ├── dmx.h ├── dns.cpp ├── enc28j60.cpp ├── enc28j60.h ├── images ├── ArtNetNode_Display.jpeg ├── ArtNetNode_Front1.jpeg ├── ArtNetNode_FullHousing.jpeg ├── ArtNetNode_PCB.jpeg ├── ArtNetNode_Prototype_1.jpeg └── ArtNetNode_Prototype_2.jpeg ├── iox32a4.h ├── main.cpp ├── net.h ├── system.h ├── tcpip.cpp ├── timer.cpp ├── timer.h ├── udpserver.cpp └── webutil.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.map 2 | *.hex 3 | *.lss 4 | *.elf 5 | *.eep 6 | *.sym 7 | obj/** 8 | .dep/** 9 | 10 | .directory 11 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": [ 6 | "/usr/include", 7 | "/usr/local/include", 8 | "/usr/lib/avr/include", 9 | "${workspaceRoot}" 10 | ], 11 | "defines": [], 12 | "intelliSenseMode": "clang-x64", 13 | "browse": { 14 | "path": [ 15 | "/usr/include", 16 | "/usr/local/include", 17 | "${workspaceRoot}" 18 | ], 19 | "limitSymbolsToIncludedHeaders": true, 20 | "databaseFilename": "" 21 | }, 22 | "macFrameworkPath": [ 23 | "/System/Library/Frameworks", 24 | "/Library/Frameworks" 25 | ] 26 | }, 27 | { 28 | "name": "Linux", 29 | "includePath": [ 30 | "/usr/include/c++/6", 31 | "/usr/include/x86_64-linux-gnu/c++/6", 32 | "/usr/include/c++/6/backward", 33 | "/usr/lib/gcc/x86_64-linux-gnu/6/include", 34 | "/usr/local/include", 35 | "/usr/lib/gcc/x86_64-linux-gnu/6/include-fixed", 36 | "/usr/include/x86_64-linux-gnu", 37 | "/usr/include", 38 | "/usr/lib/gcc/avr/4.9.2/include", 39 | "${workspaceRoot}" 40 | ], 41 | "defines": [], 42 | "intelliSenseMode": "clang-x64", 43 | "browse": { 44 | "path": [ 45 | "/usr/include/c++/6", 46 | "/usr/include/x86_64-linux-gnu/c++/6", 47 | "/usr/include/c++/6/backward", 48 | "/usr/lib/gcc/x86_64-linux-gnu/6/include", 49 | "/usr/local/include", 50 | "/usr/lib/gcc/x86_64-linux-gnu/6/include-fixed", 51 | "/usr/include/x86_64-linux-gnu", 52 | "/usr/include", 53 | "/usr/lib/gcc/avr/4.9.2/include", 54 | "${workspaceRoot}" 55 | ], 56 | "limitSymbolsToIncludedHeaders": true, 57 | "databaseFilename": "" 58 | } 59 | }, 60 | { 61 | "name": "Win32", 62 | "includePath": [ 63 | "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include", 64 | "${workspaceRoot}" 65 | ], 66 | "defines": [ 67 | "_DEBUG", 68 | "UNICODE" 69 | ], 70 | "intelliSenseMode": "msvc-x64", 71 | "browse": { 72 | "path": [ 73 | "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include/*", 74 | "${workspaceRoot}" 75 | ], 76 | "limitSymbolsToIncludedHeaders": true, 77 | "databaseFilename": "" 78 | } 79 | } 80 | ], 81 | "version": 3 82 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "io.h": "c", 4 | "delay.h": "c", 5 | "*.ipp": "cpp", 6 | "enc28j60.h": "c", 7 | "array": "c", 8 | "functional": "c", 9 | "dmx.h": "c", 10 | "enc28j60_defs.h": "c" 11 | } 12 | } -------------------------------------------------------------------------------- /EtherCard.cpp: -------------------------------------------------------------------------------- 1 | // This code slightly follows the conventions of, but is not derived from: 2 | // EHTERSHIELD_H library for Arduino etherShield 3 | // Copyright (c) 2008 Xing Yu. All right reserved. (this is LGPL v2.1) 4 | // It is however derived from the enc28j60 and ip code (which is GPL v2) 5 | // Author: Pascal Stang 6 | // Modified by: Guido Socher 7 | // DHCP code: Andrew Lindsay 8 | // Hence: GPL V2 9 | // 10 | // 2010-05-19 11 | 12 | #include "EtherCard.h" 13 | #include 14 | #include 15 | #include 16 | 17 | #define WRITEBUF 0 18 | #define READBUF 1 19 | #define BUFCOUNT 2 20 | 21 | //#define FLOATEMIT // uncomment line to enable $T in emit_P for float emitting 22 | 23 | //from Arduino.h(LGPL) 24 | #define bitRead(value, bit) (((value) >> (bit)) & 0x01) 25 | #define bitSet(value, bit) ((value) |= (1UL << (bit))) 26 | #define bitClear(value, bit) ((value) &= ~(1UL << (bit))) 27 | #define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit)) 28 | 29 | uint8_t Stash::map[SCRATCH_MAP_SIZE]; 30 | Stash::Block Stash::bufs[BUFCOUNT]; 31 | 32 | uint8_t Stash::allocBlock () { 33 | for (uint8_t i = 0; i < sizeof map; ++i) 34 | if (map[i] != 0) 35 | for (uint8_t j = 0; j < 8; ++j) 36 | if (bitRead(map[i], j)) { 37 | bitClear(map[i], j); 38 | return (i << 3) + j; 39 | } 40 | return 0; 41 | } 42 | 43 | void Stash::freeBlock (uint8_t block) { 44 | bitSet(map[block>>3], block & 7); 45 | } 46 | 47 | uint8_t Stash::fetchByte (uint8_t blk, uint8_t off) { 48 | return blk == bufs[WRITEBUF].bnum ? bufs[WRITEBUF].bytes[off] : 49 | blk == bufs[READBUF].bnum ? bufs[READBUF].bytes[off] : 50 | ether.peekin(blk, off); 51 | } 52 | 53 | 54 | // block 0 is special since always occupied 55 | void Stash::initMap (uint8_t last /*=SCRATCH_PAGE_NUM*/) { 56 | last = SCRATCH_PAGE_NUM; 57 | while (--last > 0) 58 | freeBlock(last); 59 | } 60 | 61 | // load a page/block either into the write or into the readbuffer 62 | void Stash::load (uint8_t idx, uint8_t blk) { 63 | if (blk != bufs[idx].bnum) { 64 | if (idx == WRITEBUF) { 65 | ether.copyout(bufs[idx].bnum, bufs[idx].bytes); 66 | if (blk == bufs[READBUF].bnum) 67 | bufs[READBUF].bnum = 255; // forget read page if same 68 | } else if (blk == bufs[WRITEBUF].bnum) { 69 | // special case: read page is same as write buffer 70 | memcpy(&bufs[READBUF], &bufs[WRITEBUF], sizeof bufs[0]); 71 | return; 72 | } 73 | bufs[idx].bnum = blk; 74 | ether.copyin(bufs[idx].bnum, bufs[idx].bytes); 75 | } 76 | } 77 | 78 | uint8_t Stash::freeCount () { 79 | uint8_t count = 0; 80 | for (uint8_t i = 0; i < sizeof map; ++i) 81 | for (uint8_t m = 0x80; m != 0; m >>= 1) 82 | if (map[i] & m) 83 | ++count; 84 | return count; 85 | } 86 | 87 | // create a new stash; make it the active stash; return the first block as a handle 88 | uint8_t Stash::create () { 89 | uint8_t blk = allocBlock(); 90 | load(WRITEBUF, blk); 91 | bufs[WRITEBUF].head.count = 0; 92 | bufs[WRITEBUF].head.first = bufs[0].head.last = blk; 93 | bufs[WRITEBUF].tail = sizeof (StashHeader); 94 | bufs[WRITEBUF].next = 0; 95 | return open(blk); // you are now the active stash 96 | } 97 | 98 | // the stashheader part only contains reasonable data if we are the first block 99 | uint8_t Stash::open (uint8_t blk) { 100 | curr = blk; 101 | offs = sizeof (StashHeader); // goto first byte 102 | load(READBUF, curr); 103 | memcpy((StashHeader*) this, bufs[READBUF].bytes, sizeof (StashHeader)); 104 | return curr; 105 | } 106 | 107 | // save the metadata of current block into the first block 108 | void Stash::save () { 109 | load(WRITEBUF, first); 110 | memcpy(bufs[WRITEBUF].bytes, (StashHeader*) this, sizeof (StashHeader)); 111 | if (bufs[READBUF].bnum == first) 112 | load(READBUF, 0); // invalidates original in case it was the same block 113 | } 114 | 115 | // follow the linked list of blocks and free every block 116 | void Stash::release () { 117 | while (first > 0) { 118 | freeBlock(first); 119 | first = ether.peekin(first, 63); 120 | } 121 | } 122 | 123 | void Stash::put (char c) { 124 | load(WRITEBUF, last); 125 | uint8_t t = bufs[WRITEBUF].tail; 126 | bufs[WRITEBUF].bytes[t++] = c; 127 | if (t <= 62) 128 | bufs[WRITEBUF].tail = t; 129 | else { 130 | bufs[WRITEBUF].next = allocBlock(); 131 | last = bufs[WRITEBUF].next; 132 | load(WRITEBUF, last); 133 | bufs[WRITEBUF].tail = bufs[WRITEBUF].next = 0; 134 | ++count; 135 | } 136 | } 137 | 138 | char Stash::get () { 139 | load(READBUF, curr); 140 | if (curr == last && offs >= bufs[READBUF].tail) 141 | return 0; 142 | uint8_t b = bufs[READBUF].bytes[offs]; 143 | if (++offs >= 63 && curr != last) { 144 | curr = bufs[READBUF].next; 145 | offs = 0; 146 | } 147 | return b; 148 | } 149 | 150 | // fetchbyte(last, 62) is tail, i.e., number of characters in last block 151 | uint16_t Stash::size () { 152 | return 63 * count + fetchByte(last, 62) - sizeof (StashHeader); 153 | } 154 | 155 | static char* wtoa (uint16_t value, char* ptr) { 156 | if (value > 9) 157 | ptr = wtoa(value / 10, ptr); 158 | *ptr = '0' + value % 10; 159 | *++ptr = 0; 160 | return ptr; 161 | } 162 | 163 | // write information about the fmt string and the arguments into special page/block 0 164 | // block 0 is initially marked as allocated and never returned by allocateBlock 165 | void Stash::prepare (const char* fmt PROGMEM, ...) { 166 | Stash::load(WRITEBUF, 0); 167 | uint16_t* segs = Stash::bufs[WRITEBUF].words; 168 | *segs++ = strlen_P(fmt); 169 | #ifdef __AVR__ 170 | *segs++ = (uint16_t) fmt; 171 | #else 172 | *segs++ = (uint32_t) fmt; 173 | *segs++ = (uint32_t) fmt >> 16; 174 | #endif 175 | va_list ap; 176 | va_start(ap, fmt); 177 | for (;;) { 178 | char c = pgm_read_byte(fmt++); 179 | if (c == 0) 180 | break; 181 | if (c == '$') { 182 | #ifdef __AVR__ 183 | uint16_t argval = va_arg(ap, uint16_t), arglen = 0; 184 | #else 185 | uint32_t argval = va_arg(ap, int), arglen = 0; 186 | #endif 187 | switch (pgm_read_byte(fmt++)) { 188 | case 'D': { 189 | char buf[7]; 190 | wtoa(argval, buf); 191 | arglen = strlen(buf); 192 | break; 193 | } 194 | case 'S': 195 | arglen = strlen((const char*) argval); 196 | break; 197 | case 'F': 198 | arglen = strlen_P((const char*) argval); 199 | break; 200 | case 'E': { 201 | uint8_t* s = (uint8_t*) argval; 202 | char d; 203 | while ((d = eeprom_read_byte(s++)) != 0) 204 | ++arglen; 205 | break; 206 | } 207 | case 'H': { 208 | Stash stash (argval); 209 | arglen = stash.size(); 210 | break; 211 | } 212 | } 213 | #ifdef __AVR__ 214 | *segs++ = argval; 215 | #else 216 | *segs++ = argval; 217 | *segs++ = argval >> 16; 218 | #endif 219 | Stash::bufs[WRITEBUF].words[0] += arglen - 2; 220 | } 221 | } 222 | va_end(ap); 223 | } 224 | 225 | uint16_t Stash::length () { 226 | Stash::load(WRITEBUF, 0); 227 | return Stash::bufs[WRITEBUF].words[0]; 228 | } 229 | 230 | void Stash::extract (uint16_t offset, uint16_t count, void* buf) { 231 | Stash::load(WRITEBUF, 0); 232 | uint16_t* segs = Stash::bufs[WRITEBUF].words; 233 | #ifdef __AVR__ 234 | const char* fmt PROGMEM = (const char*) *++segs; 235 | #else 236 | const char* fmt PROGMEM = (const char*)((segs[2] << 16) | segs[1]); 237 | segs += 2; 238 | #endif 239 | Stash stash; 240 | char mode = '@', tmp[7], *ptr = NULL, *out = (char*) buf; 241 | for (uint16_t i = 0; i < offset + count; ) { 242 | char c = 0; 243 | switch (mode) { 244 | case '@': { 245 | c = pgm_read_byte(fmt++); 246 | if (c == 0) 247 | return; 248 | if (c != '$') 249 | break; 250 | #ifdef __AVR__ 251 | uint16_t arg = *++segs; 252 | #else 253 | uint32_t arg = *++segs; 254 | arg |= *++segs << 16; 255 | #endif 256 | mode = pgm_read_byte(fmt++); 257 | switch (mode) { 258 | case 'D': 259 | wtoa(arg, tmp); 260 | ptr = tmp; 261 | break; 262 | case 'S': 263 | case 'F': 264 | case 'E': 265 | ptr = (char*) arg; 266 | break; 267 | case 'H': 268 | stash.open(arg); 269 | ptr = (char*) &stash; 270 | break; 271 | } 272 | continue; 273 | } 274 | case 'D': 275 | case 'S': 276 | c = *ptr++; 277 | break; 278 | case 'F': 279 | c = pgm_read_byte(ptr++); 280 | break; 281 | case 'E': 282 | c = eeprom_read_byte((uint8_t*) ptr++); 283 | break; 284 | case 'H': 285 | c = ((Stash*) ptr)->get(); 286 | break; 287 | } 288 | if (c == 0) { 289 | mode = '@'; 290 | continue; 291 | } 292 | if (i >= offset) 293 | *out++ = c; 294 | ++i; 295 | } 296 | } 297 | 298 | void Stash::cleanup () { 299 | Stash::load(WRITEBUF, 0); 300 | uint16_t* segs = Stash::bufs[WRITEBUF].words; 301 | #ifdef __AVR__ 302 | const char* fmt PROGMEM = (const char*) *++segs; 303 | #else 304 | const char* fmt PROGMEM = (const char*)((segs[2] << 16) | segs[1]); 305 | segs += 2; 306 | #endif 307 | for (;;) { 308 | char c = pgm_read_byte(fmt++); 309 | if (c == 0) 310 | break; 311 | if (c == '$') { 312 | #ifdef __AVR__ 313 | uint16_t arg = *++segs; 314 | #else 315 | uint32_t arg = *++segs; 316 | arg |= *++segs << 16; 317 | #endif 318 | if (pgm_read_byte(fmt++) == 'H') { 319 | Stash stash (arg); 320 | stash.release(); 321 | } 322 | } 323 | } 324 | } 325 | 326 | void BufferFiller::emit_p(const char* fmt PROGMEM, ...) { 327 | va_list ap; 328 | va_start(ap, fmt); 329 | for (;;) { 330 | char c = pgm_read_byte(fmt++); 331 | if (c == 0) 332 | break; 333 | if (c != '$') { 334 | *ptr++ = c; 335 | continue; 336 | } 337 | c = pgm_read_byte(fmt++); 338 | switch (c) { 339 | case 'D': 340 | #ifdef __AVR__ 341 | wtoa(va_arg(ap, uint16_t), (char*) ptr); 342 | #else 343 | wtoa(va_arg(ap, int), (char*) ptr); 344 | #endif 345 | break; 346 | #ifdef FLOATEMIT 347 | case 'T': 348 | dtostrf ( va_arg(ap, double), 10, 3, (char*)ptr ); 349 | break; 350 | #endif 351 | case 'H': { 352 | #ifdef __AVR__ 353 | char p1 = va_arg(ap, uint16_t); 354 | #else 355 | char p1 = va_arg(ap, int); 356 | #endif 357 | char p2; 358 | p2 = (p1 >> 4) & 0x0F; 359 | p1 = p1 & 0x0F; 360 | if (p1 > 9) p1 += 0x07; // adjust 0x0a-0x0f to come out 'a'-'f' 361 | p1 += 0x30; // and complete 362 | if (p2 > 9) p2 += 0x07; // adjust 0x0a-0x0f to come out 'a'-'f' 363 | p2 += 0x30; // and complete 364 | *ptr++ = p2; 365 | *ptr++ = p1; 366 | continue; 367 | } 368 | case 'L': 369 | ltoa(va_arg(ap, long), (char*) ptr, 10); 370 | break; 371 | case 'S': 372 | strcpy((char*) ptr, va_arg(ap, const char*)); 373 | break; 374 | case 'F': { 375 | const char* s PROGMEM = va_arg(ap, const char*); 376 | char d; 377 | while ((d = pgm_read_byte(s++)) != 0) 378 | *ptr++ = d; 379 | continue; 380 | } 381 | case 'E': { 382 | uint8_t* s = va_arg(ap, uint8_t*); 383 | char d; 384 | while ((d = eeprom_read_byte(s++)) != 0) 385 | *ptr++ = d; 386 | continue; 387 | } 388 | default: 389 | *ptr++ = c; 390 | continue; 391 | } 392 | ptr += strlen((char*) ptr); 393 | } 394 | va_end(ap); 395 | } 396 | 397 | //EtherCard ether; 398 | 399 | uint8_t EtherCard::mymac[ETH_LEN]; // my MAC address 400 | uint8_t EtherCard::myip[IP_LEN]; // my ip address 401 | uint8_t EtherCard::netmask[IP_LEN]; // subnet mask 402 | uint8_t EtherCard::broadcastip[IP_LEN]; // broadcast address 403 | uint8_t EtherCard::gwip[IP_LEN]; // gateway 404 | uint8_t EtherCard::dhcpip[IP_LEN]; // dhcp server 405 | uint8_t EtherCard::dnsip[IP_LEN]; // dns server 406 | uint8_t EtherCard::hisip[IP_LEN]; // ip address of remote host 407 | uint16_t EtherCard::hisport = HTTP_PORT; // tcp port to browse to 408 | bool EtherCard::using_dhcp = false; 409 | bool EtherCard::persist_tcp_connection = false; 410 | uint16_t EtherCard::delaycnt = 0; //request gateway ARP lookup 411 | 412 | uint8_t EtherCard::begin (const uint16_t size, 413 | const uint8_t* macaddr) { 414 | using_dhcp = false; 415 | copyMac(mymac, macaddr); 416 | return initialize(size, mymac); 417 | } 418 | 419 | bool EtherCard::staticSetup (const uint8_t* my_ip, 420 | const uint8_t* gw_ip, 421 | const uint8_t* dns_ip, 422 | const uint8_t* mask) { 423 | using_dhcp = false; 424 | 425 | if (my_ip != 0) 426 | copyIp(myip, my_ip); 427 | if (gw_ip != 0) 428 | setGwIp(gw_ip); 429 | if (dns_ip != 0) 430 | copyIp(dnsip, dns_ip); 431 | if(mask != 0) 432 | copyIp(netmask, mask); 433 | updateBroadcastAddress(); 434 | delaycnt = 0; //request gateway ARP lookup 435 | return true; 436 | } 437 | -------------------------------------------------------------------------------- /EtherCard.h: -------------------------------------------------------------------------------- 1 | // This code slightly follows the conventions of, but is not derived from: 2 | // EHTERSHIELD_H library for Arduino etherShield 3 | // Copyright (c) 2008 Xing Yu. All right reserved. (this is LGPL v2.1) 4 | // It is however derived from the enc28j60 and ip code (which is GPL v2) 5 | // Author: Pascal Stang 6 | // Modified by: Guido Socher 7 | // DHCP code: Andrew Lindsay 8 | // Hence: GPL V2 9 | // 10 | // 2010-05-19 11 | // 12 | // 13 | // PIN Connections (Using Arduino UNO): 14 | // VCC - 3.3V 15 | // GND - GND 16 | // SCK - Pin 13 17 | // SO - Pin 12 18 | // SI - Pin 11 19 | // CS - Pin 8 20 | // 21 | /** @file */ 22 | 23 | #ifndef EtherCard_h 24 | #define EtherCard_h 25 | #ifndef __PROG_TYPES_COMPAT__ 26 | #define __PROG_TYPES_COMPAT__ 27 | #endif 28 | 29 | 30 | #define WRITE_RESULT size_t 31 | #define WRITE_RETURN return 1; 32 | 33 | #define gPB ether.buffer 34 | 35 | #include 36 | #include 37 | #include "enc28j60.h" 38 | #include "net.h" 39 | 40 | /** Enable DHCP. 41 | * Setting this to zero disables the use of DHCP; if a program uses DHCP it will 42 | * still compile but the program will not work. Saves about 60 bytes SRAM and 43 | * 1550 bytes flash. 44 | */ 45 | #define ETHERCARD_DHCP 0 46 | 47 | /** Enable client connections. 48 | * Setting this to zero means that the program cannot issue TCP client requests 49 | * anymore. Compilation will still work but the request will never be 50 | * issued. Saves 4 bytes SRAM and 550 byte flash. 51 | */ 52 | #define ETHERCARD_TCPCLIENT 0 53 | 54 | /** Enable TCP server functionality. 55 | * Setting this to zero means that the program will not accept TCP client 56 | * requests. Saves 2 bytes SRAM and 250 bytes flash. 57 | */ 58 | #define ETHERCARD_TCPSERVER 1 59 | 60 | /** Enable UDP server functionality. 61 | * If zero UDP server is disabled. It is 62 | * still possible to register callbacks but these will never be called. Saves 63 | * about 40 bytes SRAM and 200 bytes flash. If building with -flto this does not 64 | * seem to save anything; maybe the linker is then smart enough to optimize the 65 | * call away. 66 | */ 67 | #define ETHERCARD_UDPSERVER 1 68 | 69 | /** Enable automatic reply to pings. 70 | * Setting to zero means that the program will not automatically answer to 71 | * PINGs anymore. Also the callback that can be registered to answer incoming 72 | * pings will not be called. Saves 2 bytes SRAM and 230 bytes flash. 73 | */ 74 | #define ETHERCARD_ICMP 1 75 | 76 | /** Enable use of stash. 77 | * Setting this to zero means that the stash mechanism cannot be used. Again 78 | * compilation will still work but the program may behave very unexpectedly. 79 | * Saves 30 bytes SRAM and 80 bytes flash. 80 | */ 81 | #define ETHERCARD_STASH 1 82 | 83 | 84 | 85 | /** This type definition defines the structure of a UDP server event handler callback funtion */ 86 | typedef void (*UdpServerCallback)( 87 | uint16_t dest_port, ///< Port the packet was sent to 88 | uint8_t src_ip[IP_LEN], ///< IP address of the sender 89 | uint16_t src_port, ///< Port the packet was sent from 90 | const char *data, ///< UDP payload data 91 | uint16_t len); ///< Length of the payload data 92 | 93 | /** This type definition defines the structure of a DHCP Option callback funtion */ 94 | typedef void (*DhcpOptionCallback)( 95 | uint8_t option, ///< The option number 96 | const uint8_t* data, ///< DHCP option data 97 | uint8_t len); ///< Length of the DHCP option data 98 | 99 | 100 | 101 | /** This structure describes the structure of memory used within the ENC28J60 network interface. */ 102 | typedef struct { 103 | uint8_t count; ///< Number of allocated pages 104 | uint8_t first; ///< First allocated page 105 | uint8_t last; ///< Last allocated page 106 | } StashHeader; 107 | 108 | /** This class provides access to the memory within the ENC28J60 network interface. */ 109 | class Stash : private StashHeader { 110 | uint8_t curr; //!< Current page 111 | uint8_t offs; //!< Current offset in page 112 | 113 | typedef struct { 114 | union { 115 | uint8_t bytes[64]; 116 | uint16_t words[32]; 117 | struct { 118 | StashHeader head; // StashHeader is only stored in first block 119 | uint8_t filler[59]; 120 | uint8_t tail; // only meaningful if bnum==last; number of bytes in last block 121 | uint8_t next; // pointer to next block 122 | }; 123 | }; 124 | uint8_t bnum; 125 | } Block; 126 | 127 | static uint8_t allocBlock (); 128 | static void freeBlock (uint8_t block); 129 | static uint8_t fetchByte (uint8_t blk, uint8_t off); 130 | 131 | static Block bufs[2]; 132 | static uint8_t map[SCRATCH_MAP_SIZE]; 133 | 134 | public: 135 | static void initMap (uint8_t last=SCRATCH_PAGE_NUM); 136 | static void load (uint8_t idx, uint8_t blk); 137 | static uint8_t freeCount (); 138 | 139 | Stash () : curr (0) { first = 0; } 140 | Stash (uint8_t fd) { open(fd); } 141 | 142 | uint8_t create (); 143 | uint8_t open (uint8_t blk); 144 | void save (); 145 | void release (); 146 | 147 | void put (char c); 148 | char get (); 149 | uint16_t size (); 150 | 151 | virtual WRITE_RESULT write(uint8_t b) { put(b); WRITE_RETURN } 152 | 153 | // virtual int available() { 154 | // if (curr != last) 155 | // return 1; 156 | // load(1, last); 157 | // return offs < bufs[1].tail; 158 | // } 159 | // virtual int read() { 160 | // return available() ? get() : -1; 161 | // } 162 | // virtual int peek() { 163 | // return available() ? bufs[1].bytes[offs] : -1; 164 | // } 165 | // virtual void flush() { 166 | // curr = last; 167 | // offs = 63; 168 | // } 169 | 170 | static void prepare (const char* fmt PROGMEM, ...); 171 | static uint16_t length (); 172 | static void extract (uint16_t offset, uint16_t count, void* buf); 173 | static void cleanup (); 174 | 175 | friend void dumpBlock (const char* msg, uint8_t idx); // optional 176 | friend void dumpStash (const char* msg, void* ptr); // optional 177 | }; 178 | 179 | /** This class populates network send and receive buffers. 180 | * 181 | * This class provides formatted printing into memory. Users can use it to write into send buffers. 182 | * 183 | * Nota: PGM_P: is a pointer to a string in program space (defined in the source code, updated to PROGMEM) 184 | * 185 | * # Format string 186 | * 187 | * | Format | Parameter | Output 188 | * |--------|-------------|---------- 189 | * | $D | uint16_t | Decimal representation 190 | * | $T ¤ | double | Decimal representation with 3 digits after decimal sign ([-]d.ddd) 191 | * | $H | uint16_t | Hexadecimal value of lsb (from 00 to ff) 192 | * | $L | long | Decimal representation 193 | * | $S | const char* | Copy null terminated string from main memory 194 | * | $F | PGM_P | Copy null terminated string from program space 195 | * | $E | byte* | Copy null terminated string from EEPROM space 196 | * | $$ | _none_ | '$' 197 | * 198 | * ¤ _Available only if FLOATEMIT is defined_ 199 | * 200 | * # Examples 201 | * ~~~~~~~~~~~~~{.c} 202 | * uint16_t ddd = 123; 203 | * double ttt = 1.23; 204 | * uint16_t hhh = 0xa4; 205 | * long lll = 123456789; 206 | * char * sss; 207 | * char fff[] PROGMEM = "MyMemory"; 208 | * 209 | * sss[0] = 'G'; 210 | * sss[1] = 'P'; 211 | * sss[2] = 'L'; 212 | * sss[3] = 0; 213 | * buf.emit_p( PSTR("ddd=$D\n"), ddd ); // "ddd=123\n" 214 | * buf.emit_p( PSTR("ttt=$T\n"), ttt ); // "ttt=1.23\n" **TO CHECK** 215 | * buf.emit_p( PSTR("hhh=$H\n"), hhh ); // "hhh=a4\n" 216 | * buf.emit_p( PSTR("lll=$L\n"), lll ); // "lll=123456789\n" 217 | * buf.emit_p( PSTR("sss=$S\n"), sss ); // "sss=GPL\n" 218 | * buf.emit_p( PSTR("fff=$F\n"), fff ); // "fff=MyMemory\n" 219 | * ~~~~~~~~~~~~~ 220 | * 221 | */ 222 | class BufferFiller { 223 | uint8_t *start; //!< Pointer to start of buffer 224 | uint8_t *ptr; //!< Pointer to cursor position 225 | public: 226 | /** @brief Empty constructor 227 | */ 228 | BufferFiller () {} 229 | 230 | /** @brief Constructor 231 | * @param buf Pointer to the ethernet data buffer 232 | */ 233 | BufferFiller (uint8_t* buf) : start (buf), ptr (buf) {} 234 | 235 | /** @brief Add formatted text to buffer 236 | * @param fmt Format string (see Class description) 237 | * @param ... parameters for format string 238 | */ 239 | void emit_p (const char* fmt PROGMEM, ...); 240 | 241 | /** @brief Add data to buffer from main memory 242 | * @param s Pointer to data 243 | * @param n Number of characters to copy 244 | */ 245 | void emit_raw (const char* s, uint16_t n) { memcpy(ptr, s, n); ptr += n; } 246 | 247 | /** @brief Add data to buffer from program space string 248 | * @param p Program space string pointer 249 | * @param n Number of characters to copy 250 | */ 251 | void emit_raw_p (const char* p PROGMEM, uint16_t n) { memcpy_P(ptr, p, n); ptr += n; } 252 | 253 | /** @brief Get pointer to start of buffer 254 | * @return uint8_t* Pointer to start of buffer 255 | */ 256 | uint8_t* buffer () const { return start; } 257 | 258 | /** @brief Get cursor position 259 | * @return uint16_t Cursor postion 260 | */ 261 | uint16_t position () const { return ptr - start; } 262 | 263 | /** @brief Write one byte to buffer 264 | * @param v Byte to add to buffer 265 | */ 266 | virtual WRITE_RESULT write (uint8_t v) { *ptr++ = v; WRITE_RETURN } 267 | }; 268 | 269 | 270 | /** This class provides the main interface to a ENC28J60 based network interface card and is the class most users will use. 271 | * @note All TCP/IP client (outgoing) connections are made from source port in range 2816-3071. Do not use these source ports for other purposes. 272 | */ 273 | class EtherCard : public ENC28J60 { 274 | public: 275 | static uint8_t mymac[ETH_LEN]; ///< MAC address 276 | static uint8_t myip[IP_LEN]; ///< IP address 277 | static uint8_t netmask[IP_LEN]; ///< Netmask 278 | static uint8_t broadcastip[IP_LEN]; ///< Subnet broadcast address 279 | static uint8_t gwip[IP_LEN]; ///< Gateway 280 | static uint8_t dhcpip[IP_LEN]; ///< DHCP server IP address 281 | static uint8_t dnsip[IP_LEN]; ///< DNS server IP address 282 | static uint8_t hisip[IP_LEN]; ///< DNS lookup result 283 | static uint16_t hisport; ///< TCP port to connect to (default 80) 284 | static bool using_dhcp; ///< True if using DHCP 285 | static bool persist_tcp_connection; ///< False to break connections on first packet received 286 | static uint16_t delaycnt; ///< Counts number of cycles of packetLoop when no packet received - used to trigger periodic gateway ARP request 287 | 288 | // EtherCard.cpp 289 | /** @brief Initialise the network interface 290 | * @param size Size of data buffer 291 | * @param macaddr Hardware address to assign to the network interface (6 bytes) 292 | * @param csPin Arduino pin number connected to chip select. Default = 8 293 | * @return uint8_t Firmware version or zero on failure. 294 | */ 295 | static uint8_t begin (const uint16_t size, const uint8_t* macaddr); 296 | 297 | /** @brief Configure network interface with static IP 298 | * @param my_ip IP address (4 bytes). 0 for no change. 299 | * @param gw_ip Gateway address (4 bytes). 0 for no change. Default = 0 300 | * @param dns_ip DNS address (4 bytes). 0 for no change. Default = 0 301 | * @param mask Subnet mask (4 bytes). 0 for no change. Default = 0 302 | * @return bool Returns true on success - actually always true 303 | */ 304 | static bool staticSetup (const uint8_t* my_ip, 305 | const uint8_t* gw_ip = 0, 306 | const uint8_t* dns_ip = 0, 307 | const uint8_t* mask = 0); 308 | 309 | // tcpip.cpp 310 | /** @brief Sends a UDP packet to the IP address of last processed received packet 311 | * @param data Pointer to data payload 312 | * @param len Size of data payload (max 220) 313 | * @param port Source IP port 314 | */ 315 | static void makeUdpReply (const char *data, uint8_t len, uint16_t port); 316 | 317 | /** @brief Parse received data 318 | * @param plen Size of data to parse (e.g. return value of packetReceive()). 319 | * @return uint16_t Offset of TCP payload data in data buffer or zero if packet processed 320 | * @note Data buffer is shared by receive and transmit functions 321 | * @note Only handles ARP and IP 322 | */ 323 | static uint16_t packetLoop (uint16_t plen); 324 | 325 | /** @brief Accept a TCP/IP connection 326 | * @param port IP port to accept on - do nothing if wrong port 327 | * @param plen Number of bytes in packet 328 | * @return uint16_t Offset within packet of TCP payload. Zero for no data. 329 | */ 330 | static uint16_t accept (uint16_t port, uint16_t plen); 331 | 332 | /** @brief Send a response to a HTTP request 333 | * @param dlen Size of the HTTP (TCP) payload 334 | */ 335 | static void httpServerReply (uint16_t dlen); 336 | 337 | /** @brief Send a response to a HTTP request 338 | * @param dlen Size of the HTTP (TCP) payload 339 | * @param flags TCP flags 340 | */ 341 | static void httpServerReply_with_flags (uint16_t dlen , uint8_t flags); 342 | 343 | /** @brief Acknowledge TCP message 344 | * @todo Is this / should this be private? 345 | */ 346 | static void httpServerReplyAck (); 347 | 348 | /** @brief Set the gateway address 349 | * @param gwipaddr Gateway address (4 bytes) 350 | */ 351 | static void setGwIp (const uint8_t *gwipaddr); 352 | 353 | /** @brief Updates the broadcast address based on current IP address and subnet mask 354 | */ 355 | static void updateBroadcastAddress(); 356 | 357 | /** @brief Check if got gateway hardware address (ARP lookup) 358 | * @return unit8_t True if gateway found 359 | */ 360 | static uint8_t clientWaitingGw (); 361 | 362 | /** @brief Check if got gateway DNS address (ARP lookup) 363 | * @return unit8_t True if DNS found 364 | */ 365 | static uint8_t clientWaitingDns (); 366 | 367 | /** @brief Prepare a TCP request 368 | * @param result_cb Pointer to callback function that handles TCP result 369 | * @param datafill_cb Pointer to callback function that handles TCP data payload 370 | * @param port Remote TCP/IP port to connect to 371 | * @return unit8_t ID of TCP/IP session (0-7) 372 | * @note Return value provides id of the request to allow up to 7 concurrent requests 373 | */ 374 | static uint8_t clientTcpReq (uint8_t (*result_cb)(uint8_t,uint8_t,uint16_t,uint16_t), 375 | uint16_t (*datafill_cb)(uint8_t),uint16_t port); 376 | 377 | /** @brief Prepare HTTP request 378 | * @param urlbuf Pointer to c-string URL folder 379 | * @param urlbuf_varpart Pointer to c-string URL file 380 | * @param hoststr Pointer to c-string hostname 381 | * @param additionalheaderline Pointer to c-string with additional HTTP header info 382 | * @param callback Pointer to callback function to handle response 383 | * @note Request sent in main packetloop 384 | */ 385 | static void browseUrl (const char *urlbuf, const char *urlbuf_varpart, 386 | const char *hoststr, const char *additionalheaderline, 387 | void (*callback)(uint8_t,uint16_t,uint16_t)); 388 | 389 | /** @brief Prepare HTTP request 390 | * @param urlbuf Pointer to c-string URL folder 391 | * @param urlbuf_varpart Pointer to c-string URL file 392 | * @param hoststr Pointer to c-string hostname 393 | * @param callback Pointer to callback function to handle response 394 | * @note Request sent in main packetloop 395 | */ 396 | static void browseUrl (const char *urlbuf, const char *urlbuf_varpart, 397 | const char *hoststr, 398 | void (*callback)(uint8_t,uint16_t,uint16_t)); 399 | 400 | /** @brief Prepare HTTP post message 401 | * @param urlbuf Pointer to c-string URL folder 402 | * @param hoststr Pointer to c-string hostname 403 | * @param additionalheaderline Pointer to c-string with additional HTTP header info 404 | * @param postval Pointer to c-string HTML Post value 405 | * @param callback Pointer to callback function to handle response 406 | * @note Request sent in main packetloop 407 | */ 408 | static void httpPost (const char *urlbuf, const char *hoststr, 409 | const char *additionalheaderline, const char *postval, 410 | void (*callback)(uint8_t,uint16_t,uint16_t)); 411 | 412 | /** @brief Send NTP request 413 | * @param ntpip IP address of NTP server 414 | * @param srcport IP port to send from 415 | */ 416 | static void ntpRequest (uint8_t *ntpip,uint8_t srcport); 417 | 418 | /** @brief Process network time protocol response 419 | * @param time Pointer to integer to hold result 420 | * @param dstport_l Destination port to expect response. Set to zero to accept on any port 421 | * @return uint8_t True (1) on success 422 | */ 423 | static uint8_t ntpProcessAnswer (uint32_t *time, uint8_t dstport_l); 424 | 425 | /** @brief Prepare a UDP message for transmission 426 | * @param sport Source port 427 | * @param dip Pointer to 4 byte destination IP address 428 | * @param dport Destination port 429 | */ 430 | static void udpPrepare (uint16_t sport, const uint8_t *dip, uint16_t dport); 431 | 432 | /** @brief Transmit UDP packet 433 | * @param len Size of payload 434 | */ 435 | static void udpTransmit (uint16_t len); 436 | 437 | /** @brief Sends a UDP packet 438 | * @param data Pointer to data 439 | * @param len Size of payload (maximum 220 octets / bytes) 440 | * @param sport Source port 441 | * @param dip Pointer to 4 byte destination IP address 442 | * @param dport Destination port 443 | */ 444 | static void sendUdp (const char *data, uint8_t len, uint16_t sport, 445 | const uint8_t *dip, uint16_t dport); 446 | 447 | /** @brief Resister the function to handle ping events 448 | * @param cb Pointer to function 449 | */ 450 | static void registerPingCallback (void (*cb)(uint8_t*)); 451 | 452 | /** @brief Send ping 453 | * @param destip Ponter to 4 byte destination IP address 454 | */ 455 | static void clientIcmpRequest (const uint8_t *destip); 456 | 457 | /** @brief Check for ping response 458 | * @param ip_monitoredhost Pointer to 4 byte IP address of host to check 459 | * @return uint8_t True (1) if ping response from specified host 460 | */ 461 | static uint8_t packetLoopIcmpCheckReply (const uint8_t *ip_monitoredhost); 462 | 463 | /** @brief Send a wake on lan message 464 | * @param wolmac Pointer to 6 byte hardware (MAC) address of host to send message to 465 | */ 466 | static void sendWol (uint8_t *wolmac); 467 | 468 | // new stash-based API 469 | /** @brief Send TCP request 470 | */ 471 | static uint8_t tcpSend (); 472 | 473 | /** @brief Get TCP reply 474 | * @return char* Pointer to TCP reply payload. NULL if no data. 475 | */ 476 | static const char* tcpReply (uint8_t fd); 477 | 478 | /** @brief Configure TCP connections to be persistent or not 479 | * @param persist True to maintain TCP connection. False to finish TCP connection after first packet. 480 | */ 481 | static void persistTcpConnection(bool persist); 482 | 483 | //udpserver.cpp 484 | /** @brief Register function to handle incomint UDP events 485 | * @param callback Function to handle event 486 | * @param port Port to listen on 487 | */ 488 | static void udpServerListenOnPort(UdpServerCallback callback, uint16_t port); 489 | 490 | /** @brief Pause listing on UDP port 491 | * @brief port Port to pause 492 | */ 493 | static void udpServerPauseListenOnPort(uint16_t port); 494 | 495 | /** @brief Resume listing on UDP port 496 | * @brief port Port to pause 497 | */ 498 | static void udpServerResumeListenOnPort(uint16_t port); 499 | 500 | /** @brief Check if UDP server is listening on any ports 501 | * @return bool True if listening on any ports 502 | */ 503 | static bool udpServerListening(); //called by tcpip, in packetLoop 504 | 505 | /** @brief Passes packet to UDP Server 506 | * @param len Not used 507 | * @return bool True if packet processed 508 | */ 509 | static bool udpServerHasProcessedPacket(uint16_t len); //called by tcpip, in packetLoop 510 | 511 | // dhcp.cpp 512 | /** @brief Update DHCP state 513 | * @param len Length of received data packet 514 | */ 515 | static void DhcpStateMachine(uint16_t len); 516 | 517 | /** @brief Not implemented 518 | * @todo Implement dhcpStartTime or remove declaration 519 | */ 520 | static uint32_t dhcpStartTime (); 521 | 522 | /** @brief Not implemented 523 | * @todo Implement dhcpLeaseTime or remove declaration 524 | */ 525 | static uint32_t dhcpLeaseTime (); 526 | 527 | /** @brief Not implemented 528 | * @todo Implement dhcpLease or remove declaration 529 | */ 530 | static bool dhcpLease (); 531 | 532 | /** @brief Configure network interface with DHCP 533 | * @param hname The hostname to pass to the DHCP server 534 | * @param fromRam Set true to indicate whether hname is in RAM or in program space. Default = false 535 | * @return bool True if DHCP successful 536 | * @note Blocks until DHCP complete or timeout after 60 seconds 537 | */ 538 | static bool dhcpSetup (const char *hname = 0, bool fromRam =false); 539 | 540 | /** @brief Register a callback for a specific DHCP option number 541 | * @param option The option number to request from the DHCP server 542 | * @param callback The function to be call when the option is received 543 | */ 544 | static void dhcpAddOptionCallback(uint8_t option, DhcpOptionCallback callback); 545 | 546 | // dns.cpp 547 | /** @brief Perform DNS lookup 548 | * @param name Host name to lookup 549 | * @param fromRam Set true to indicate whether name is in RAM or in program space. Default = false 550 | * @return bool True on success. 551 | * @note Result is stored in hisip member 552 | */ 553 | static bool dnsLookup (const char* name, bool fromRam =false); 554 | 555 | // webutil.cpp 556 | /** @brief Copies an IP address 557 | * @param dst Pointer to the 4 byte destination 558 | * @param src Pointer to the 4 byte source 559 | * @note There is no check of source or destination size. Ensure both are 4 bytes 560 | */ 561 | static void copyIp (uint8_t *dst, const uint8_t *src); 562 | 563 | /** @brief Copies a hardware address 564 | * @param dst Pointer to the 6 byte destination 565 | * @param src Pointer to the 6 byte destination 566 | * @note There is no check of source or destination size. Ensure both are 6 bytes 567 | */ 568 | static void copyMac (uint8_t *dst, const uint8_t *src); 569 | 570 | 571 | /** @brief Search for a string of the form key=value in a string that looks like q?xyz=abc&uvw=defgh HTTP/1.1\\r\\n 572 | * @param str Pointer to the null terminated string to search 573 | * @param strbuf Pointer to buffer to hold null terminated result string 574 | * @param maxlen Maximum length of result 575 | * @param key Pointer to null terminated string holding the key to search for 576 | * @return unit_t Length of the value. 0 if not found 577 | * @note Ensure strbuf has memory allocated of at least maxlen + 1 (to accomodate result plus terminating null) 578 | */ 579 | static uint8_t findKeyVal(const char *str,char *strbuf, 580 | uint8_t maxlen, const char *key); 581 | 582 | /** @brief Decode a URL string e.g "hello%20joe" or "hello+joe" becomes "hello joe" 583 | * @param urlbuf Pointer to the null terminated URL 584 | * @note urlbuf is modified 585 | */ 586 | static void urlDecode(char *urlbuf); 587 | 588 | /** @brief Encode a URL, replacing illegal charaters like ' ' 589 | * @param str Pointer to the null terminated string to encode 590 | * @param urlbuf Pointer to a buffer to contain the null terminated encoded URL 591 | * @note There must be enough space in urlbuf. In the worst case that is 3 times the length of str 592 | */ 593 | static void urlEncode(char *str,char *urlbuf); 594 | 595 | /** @brief Convert an IP address from dotted decimal formated string to 4 bytes 596 | * @param bytestr Pointer to the 4 byte array to store IP address 597 | * @param str Pointer to string to parse 598 | * @return uint8_t 0 on success 599 | */ 600 | static uint8_t parseIp(uint8_t *bytestr,char *str); 601 | 602 | /** @brief Convert a byte array to a human readable display string 603 | * @param resultstr Pointer to a buffer to hold the resulting null terminated string 604 | * @param bytestr Pointer to the byte array containing the address to convert 605 | * @param len Length of the array (4 for IP address, 6 for hardware (MAC) address) 606 | * @param separator Delimiter character (typically '.' for IP address and ':' for hardware (MAC) address) 607 | * @param base Base for numerical representation (typically 10 for IP address and 16 for hardware (MAC) address 608 | */ 609 | static void makeNetStr(char *resultstr,uint8_t *bytestr,uint8_t len, 610 | char separator,uint8_t base); 611 | 612 | /** @brief Return the sequence number of the current TCP package 613 | */ 614 | static uint32_t getSequenceNumber(); 615 | 616 | /** @brief Return the payload length of the current Tcp package 617 | */ 618 | static uint16_t getTcpPayloadLength(); 619 | }; 620 | 621 | extern EtherCard ether; //!< Global presentation of EtherCard class 622 | 623 | #endif 624 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Hey Emacs, this is a -*- makefile -*- 2 | #---------------------------------------------------------------------------- 3 | # WinAVR Makefile Template written by Eric B. Weddington, Jörg Wunsch, et al. 4 | # 5 | # Released to the Public Domain 6 | # 7 | # Additional material for this makefile was written by: 8 | # Peter Fleury 9 | # Tim Henigan 10 | # Colin O'Flynn 11 | # Reiner Patommel 12 | # Markus Pfaff 13 | # Sander Pool 14 | # Frederik Rouleau 15 | # Carlos Lamas 16 | # 17 | #---------------------------------------------------------------------------- 18 | # On command line: 19 | # 20 | # make all = Make software. 21 | # 22 | # make clean = Clean out built project files. 23 | # 24 | # make coff = Convert ELF to AVR COFF. 25 | # 26 | # make extcoff = Convert ELF to AVR Extended COFF. 27 | # 28 | # make program = Download the hex file to the device, using avrdude. 29 | # Please customize the avrdude settings below first! 30 | # 31 | # make debug = Start either simulavr or avarice as specified for debugging, 32 | # with avr-gdb or avr-insight as the front end for debugging. 33 | # 34 | # make filename.s = Just compile filename.c into the assembler code only. 35 | # 36 | # make filename.i = Create a preprocessed source file for use in submitting 37 | # bug reports to the GCC project. 38 | # 39 | # To rebuild project do "make clean" then "make all". 40 | #---------------------------------------------------------------------------- 41 | 42 | 43 | # MCU name 44 | MCU = atxmega32a4 45 | 46 | 47 | # Processor frequency. 48 | # This will define a symbol, F_CPU, in all source code files equal to the 49 | # processor frequency. You can then use this symbol in your source code to 50 | # calculate timings. Do NOT tack on a 'UL' at the end, this will be done 51 | # automatically to create a 32-bit value in your source code. 52 | # Typical values are: 53 | F_CPU = 32000000 54 | # F_CPU = 1843200 55 | # F_CPU = 2000000 56 | # F_CPU = 3686400 57 | # F_CPU = 4000000 58 | # F_CPU = 7372800 59 | # F_CPU = 8000000 60 | # F_CPU = 11059200 61 | # F_CPU = 14745600 62 | # F_CPU = 16000000 63 | # F_CPU = 18432000 64 | # F_CPU = 20000000 65 | # F_CPU = 8000000 66 | 67 | 68 | # Output format. (can be srec, ihex, binary) 69 | FORMAT = ihex 70 | 71 | 72 | # Target file name (without extension). 73 | TARGET = main 74 | 75 | 76 | # Object files directory 77 | OBJDIR = obj 78 | 79 | 80 | # List C source files here. (C dependencies are automatically generated.) 81 | SRC = 82 | 83 | 84 | # List C++ source files here. (C madependencies are automatically generated.) 85 | CPPSRC = $(TARGET).cpp display.cpp dmx.cpp timer.cpp artnet.cpp EtherCard.cpp enc28j60.cpp tcpip.cpp dhcp.cpp dns.cpp udpserver.cpp webutil.cpp 86 | 87 | 88 | 89 | # List Assembler source files here. 90 | # Make them always end in a capital .S. Files ending in a lowercase .s 91 | # will not be considered source files but generated files (assembler 92 | # output from the compiler), and will be deleted upon "make clean"! 93 | # Even though the DOS/Win* filesystem matches both .s and .S the same, 94 | # it will preserve the spelling of the filenames, and gcc itself does 95 | # care about how the name is spelled on its command-line. 96 | ASRC = 97 | 98 | 99 | # Optimization level, can be [0, 1, 2, 3, s]. 100 | # 0 = turn off optimization. s = optimize for size. 101 | # (Note: 3 is not always the best optimization level. See avr-libc FAQ.) 102 | OPT = s 103 | 104 | 105 | # Debugging format. 106 | # Native formats for AVR-GCC's -g are dwarf-2 [default] or stabs. 107 | # AVR Studio 4.10 requires dwarf-2. 108 | # AVR [Extended] COFF format requires stabs, plus an avr-objcopy run. 109 | DEBUG = dwarf-2 110 | 111 | 112 | # List any extra directories to look for include files here. 113 | # Each directory must be seperated by a space. 114 | # Use forward slashes for directory separators. 115 | # For a directory that has spaces, enclose it in quotes. 116 | EXTRAINCDIRS = 117 | 118 | 119 | # Compiler flag to set the C Standard level. 120 | # c89 = "ANSI" C 121 | # gnu89 = c89 plus GCC extensions 122 | # c99 = ISO C99 standard (not yet fully implemented) 123 | # gnu99 = c99 plus GCC extensions 124 | CSTANDARD = -std=gnu99 125 | 126 | 127 | # Place -D or -U options here for C sources 128 | CDEFS = -DF_CPU=$(F_CPU)UL 129 | 130 | 131 | # Place -D or -U options here for C++ sources 132 | CPPDEFS = -DF_CPU=$(F_CPU)UL 133 | #CPPDEFS += -D__STDC_LIMIT_MACROS 134 | #CPPDEFS += -D__STDC_CONSTANT_MACROS 135 | 136 | 137 | 138 | #---------------- Compiler Options C ---------------- 139 | # -g*: generate debugging information 140 | # -O*: optimization level 141 | # -f...: tuning, see GCC manual and avr-libc documentation 142 | # -Wall...: warning level 143 | # -Wa,...: tell GCC to pass this to the assembler. 144 | # -adhlns...: create assembler listing 145 | CFLAGS = -g$(DEBUG) 146 | CFLAGS += $(CDEFS) 147 | CFLAGS += -O$(OPT) 148 | #CFLAGS += -mint8 149 | #CFLAGS += -mshort-calls 150 | CFLAGS += -funsigned-char 151 | CFLAGS += -funsigned-bitfields 152 | CFLAGS += -fpack-struct 153 | CFLAGS += -fshort-enums 154 | #CFLAGS += -fno-unit-at-a-time 155 | CFLAGS += -Wall 156 | CFLAGS += -Wstrict-prototypes 157 | CFLAGS += -Wundef 158 | #CFLAGS += -Wunreachable-code 159 | #CFLAGS += -Wsign-compare 160 | CFLAGS += -Wa,-adhlns=$(<:%.c=$(OBJDIR)/%.lst) 161 | CFLAGS += $(patsubst %,-I%,$(EXTRAINCDIRS)) 162 | CFLAGS += $(CSTANDARD) 163 | 164 | 165 | #---------------- Compiler Options C++ ---------------- 166 | # -g*: generate debugging information 167 | # -O*: optimization level 168 | # -f...: tuning, see GCC manual and avr-libc documentation 169 | # -Wall...: warning level 170 | # -Wa,...: tell GCC to pass this to the assembler. 171 | # -adhlns...: create assembler listing 172 | CPPFLAGS = -g$(DEBUG) 173 | CPPFLAGS += $(CPPDEFS) 174 | CPPFLAGS += -O$(OPT) 175 | #CPPFLAGS += -mint8 176 | #CPPFLAGS += -mshort-calls 177 | CPPFLAGS += -funsigned-char 178 | CPPFLAGS += -funsigned-bitfields 179 | CPPFLAGS += -fpack-struct 180 | CPPFLAGS += -fshort-enums 181 | CPPFLAGS += -fno-exceptions 182 | #CPPFLAGS += -fno-unit-at-a-time 183 | CPPFLAGS += -Wall 184 | #CPPFLAGS += -Wstrict-prototypes 185 | CFLAGS += -Wundef 186 | #CPPFLAGS += -Wunreachable-code 187 | #CPPFLAGS += -Wsign-compare 188 | CPPFLAGS += -Wa,-adhlns=$(<:%.cpp=$(OBJDIR)/%.lst) 189 | CPPFLAGS += $(patsubst %,-I%,$(EXTRAINCDIRS)) 190 | #CPPFLAGS += $(CSTANDARD) 191 | 192 | 193 | #---------------- Assembler Options ---------------- 194 | # -Wa,...: tell GCC to pass this to the assembler. 195 | # -ahlms: create listing 196 | # -gstabs: have the assembler create line number information; note that 197 | # for use in COFF files, additional information about filenames 198 | # and function names needs to be present in the assembler source 199 | # files -- see avr-libc docs [FIXME: not yet described there] 200 | ASFLAGS = -Wa,-adhlns=$(<:%.S=$(OBJDIR)/%.lst),-gstabs 201 | 202 | 203 | #---------------- Library Options ---------------- 204 | # Minimalistic printf version 205 | PRINTF_LIB_MIN = -Wl,-u,vfprintf -lprintf_min 206 | 207 | # Floating point printf version (requires MATH_LIB = -lm below) 208 | PRINTF_LIB_FLOAT = -Wl,-u,vfprintf -lprintf_flt 209 | 210 | # If this is left blank, then it will use the Standard printf version. 211 | PRINTF_LIB = 212 | #PRINTF_LIB = $(PRINTF_LIB_MIN) 213 | #PRINTF_LIB = $(PRINTF_LIB_FLOAT) 214 | 215 | 216 | # Minimalistic scanf version 217 | SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min 218 | 219 | # Floating point + %[ scanf version (requires MATH_LIB = -lm below) 220 | SCANF_LIB_FLOAT = -Wl,-u,vfscanf -lscanf_flt 221 | 222 | # If this is left blank, then it will use the Standard scanf version. 223 | SCANF_LIB = 224 | #SCANF_LIB = $(SCANF_LIB_MIN) 225 | #SCANF_LIB = $(SCANF_LIB_FLOAT) 226 | 227 | 228 | MATH_LIB = -lm 229 | 230 | 231 | 232 | #---------------- External Memory Options ---------------- 233 | 234 | # 64 KB of external RAM, starting after internal RAM (ATmega128!), 235 | # used for variables (.data/.bss) and heap (malloc()). 236 | #EXTMEMOPTS = -Wl,-Tdata=0x801100,--defsym=__heap_end=0x80ffff 237 | 238 | # 64 KB of external RAM, starting after internal RAM (ATmega128!), 239 | # only used for heap (malloc()). 240 | #EXTMEMOPTS = -Wl,--defsym=__heap_start=0x801100,--defsym=__heap_end=0x80ffff 241 | 242 | EXTMEMOPTS = 243 | 244 | 245 | 246 | #---------------- Linker Options ---------------- 247 | # -Wl,...: tell GCC to pass this to linker. 248 | # -Map: create map file 249 | # --cref: add cross reference to map file 250 | LDFLAGS = -Wl,-Map=$(TARGET).map,--cref 251 | LDFLAGS += $(EXTMEMOPTS) 252 | LDFLAGS += $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB) 253 | #LDFLAGS += -T linker_script.x 254 | 255 | 256 | 257 | #---------------- Programming Options (avrdude) ---------------- 258 | 259 | # Programming hardware: alf avr910 avrisp bascom bsd 260 | # dt006 pavr picoweb pony-stk200 sp12 stk200 stk500 261 | # 262 | # Type: avrdude -c ? 263 | # to get a full listing. 264 | # 265 | AVRDUDE_PROGRAMMER = pony-stk200 266 | 267 | # com1 = serial port. Use lpt1 to connect to parallel port. 268 | AVRDUDE_PORT = lpt1 269 | 270 | AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex 271 | #AVRDUDE_WRITE_EEPROM = -U eeprom:w:$(TARGET).eep 272 | 273 | 274 | # Uncomment the following if you want avrdude's erase cycle counter. 275 | # Note that this counter needs to be initialized first using -Yn, 276 | # see avrdude manual. 277 | #AVRDUDE_ERASE_COUNTER = -y 278 | 279 | # Uncomment the following if you do /not/ wish a verification to be 280 | # performed after programming the device. 281 | #AVRDUDE_NO_VERIFY = -V 282 | 283 | # Increase verbosity level. Please use this when submitting bug 284 | # reports about avrdude. See 285 | # to submit bug reports. 286 | #AVRDUDE_VERBOSE = -v -v 287 | 288 | AVRDUDE_FLAGS = -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) 289 | AVRDUDE_FLAGS += $(AVRDUDE_NO_VERIFY) 290 | AVRDUDE_FLAGS += $(AVRDUDE_VERBOSE) 291 | AVRDUDE_FLAGS += $(AVRDUDE_ERASE_COUNTER) 292 | 293 | 294 | 295 | #---------------- Debugging Options ---------------- 296 | 297 | # For simulavr only - target MCU frequency. 298 | DEBUG_MFREQ = $(F_CPU) 299 | 300 | # Set the DEBUG_UI to either gdb or insight. 301 | DEBUG_UI = gdb 302 | # DEBUG_UI = insight 303 | 304 | # Set the debugging back-end to either avarice, simulavr. 305 | #DEBUG_BACKEND = avarice 306 | DEBUG_BACKEND = simulavr 307 | 308 | # GDB Init Filename. 309 | GDBINIT_FILE = __avr_gdbinit 310 | 311 | # When using avarice settings for the JTAG 312 | JTAG_DEV = /dev/com1 313 | 314 | # Debugging port used to communicate between GDB / avarice / simulavr. 315 | DEBUG_PORT = 4242 316 | 317 | # Debugging host used to communicate between GDB / avarice / simulavr, normally 318 | # just set to localhost unless doing some sort of crazy debugging when 319 | # avarice is running on a different computer. 320 | DEBUG_HOST = localhost 321 | 322 | 323 | 324 | #============================================================================ 325 | 326 | 327 | # Define programs and commands. 328 | SHELL = sh 329 | CC = avr-gcc 330 | OBJCOPY = avr-objcopy 331 | OBJDUMP = avr-objdump 332 | SIZE = avr-size 333 | NM = avr-nm 334 | AVRDUDE = avrdude 335 | REMOVE = rm -f 336 | REMOVEDIR = rm -rf 337 | COPY = cp 338 | WINSHELL = cmd 339 | 340 | 341 | # Define Messages 342 | # English 343 | MSG_ERRORS_NONE = Errors: none 344 | MSG_BEGIN = -------- begin -------- 345 | MSG_END = -------- end -------- 346 | MSG_SIZE_BEFORE = Size before: 347 | MSG_SIZE_AFTER = Size after: 348 | MSG_COFF = Converting to AVR COFF: 349 | MSG_EXTENDED_COFF = Converting to AVR Extended COFF: 350 | MSG_FLASH = Creating load file for Flash: 351 | MSG_EEPROM = Creating load file for EEPROM: 352 | MSG_EXTENDED_LISTING = Creating Extended Listing: 353 | MSG_SYMBOL_TABLE = Creating Symbol Table: 354 | MSG_LINKING = Linking: 355 | MSG_COMPILING = Compiling C: 356 | MSG_COMPILING_CPP = Compiling C++: 357 | MSG_ASSEMBLING = Assembling: 358 | MSG_CLEANING = Cleaning project: 359 | MSG_CREATING_LIBRARY = Creating library: 360 | 361 | 362 | 363 | 364 | # Define all object files. 365 | OBJ = $(SRC:%.c=$(OBJDIR)/%.o) $(CPPSRC:%.cpp=$(OBJDIR)/%.o) $(ASRC:%.S=$(OBJDIR)/%.o) 366 | 367 | # Define all listing files. 368 | LST = $(SRC:%.c=$(OBJDIR)/%.lst) $(CPPSRC:%.cpp=$(OBJDIR)/%.lst) $(ASRC:%.S=$(OBJDIR)/%.lst) 369 | 370 | 371 | # Compiler flags to generate dependency files. 372 | GENDEPFLAGS = -MD -MP -MF .dep/$(@F).d 373 | 374 | 375 | # Combine all necessary flags and optional flags. 376 | # Add target processor to flags. 377 | ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) $(GENDEPFLAGS) 378 | ALL_CPPFLAGS = -mmcu=$(MCU) -I. -x c++ $(CPPFLAGS) $(GENDEPFLAGS) 379 | ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS) 380 | 381 | 382 | 383 | 384 | 385 | # Default target. 386 | all: begin gccversion sizebefore build sizeafter end 387 | 388 | # Change the build target to build a HEX file or a library. 389 | build: elf hex eep lss sym 390 | #build: lib 391 | 392 | 393 | elf: $(TARGET).elf 394 | hex: $(TARGET).hex 395 | eep: $(TARGET).eep 396 | lss: $(TARGET).lss 397 | sym: $(TARGET).sym 398 | LIBNAME=lib$(TARGET).a 399 | lib: $(LIBNAME) 400 | 401 | 402 | 403 | # Eye candy. 404 | # AVR Studio 3.x does not check make's exit code but relies on 405 | # the following magic strings to be generated by the compile job. 406 | 407 | begin: 408 | @echo 409 | @echo $(MSG_BEGIN) 410 | 411 | end: 412 | @echo $(MSG_END) 413 | @echo 414 | 415 | 416 | # Display size of file. 417 | HEXSIZE = $(SIZE) --target=$(FORMAT) $(TARGET).hex 418 | ELFSIZE = $(SIZE) -A $(TARGET).elf 419 | AVRMEM = avr-mem.sh $(TARGET).elf $(MCU) 420 | 421 | sizebefore: 422 | @if test -f $(TARGET).elf; then echo; echo $(MSG_SIZE_BEFORE); $(ELFSIZE); \ 423 | $(AVRMEM) 2>/dev/null; echo; fi 424 | 425 | sizeafter: 426 | @if test -f $(TARGET).elf; then echo; echo $(MSG_SIZE_AFTER); $(ELFSIZE); \ 427 | $(AVRMEM) 2>/dev/null; echo; fi 428 | 429 | 430 | 431 | # Display compiler version information. 432 | gccversion : 433 | @$(CC) --version 434 | 435 | 436 | 437 | # Program the device. 438 | program: $(TARGET).hex $(TARGET).eep 439 | $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) 440 | $(AVRDUDE_WRITE_EEPROM) 441 | 442 | 443 | # Generate avr-gdb config/init file which does the following: 444 | # define the reset signal, load the target file, connect to target, and set 445 | # a breakpoint at main(). 446 | gdb-config: 447 | @$(REMOVE) $(GDBINIT_FILE) 448 | @echo define reset >> $(GDBINIT_FILE) 449 | @echo SIGNAL SIGHUP >> $(GDBINIT_FILE) 450 | @echo end >> $(GDBINIT_FILE) 451 | @echo file $(TARGET).elf >> $(GDBINIT_FILE) 452 | @echo target remote $(DEBUG_HOST):$(DEBUG_PORT) >> $(GDBINIT_FILE) 453 | ifeq ($(DEBUG_BACKEND),simulavr) 454 | @echo load >> $(GDBINIT_FILE) 455 | endif 456 | @echo break main >> $(GDBINIT_FILE) 457 | 458 | debug: gdb-config $(TARGET).elf 459 | ifeq ($(DEBUG_BACKEND), avarice) 460 | @echo Starting AVaRICE - Press enter when "waiting to connect" message displays. 461 | @$(WINSHELL) /c start avarice --jtag $(JTAG_DEV) --erase --program --file \ 462 | $(TARGET).elf $(DEBUG_HOST):$(DEBUG_PORT) 463 | @$(WINSHELL) /c pause 464 | 465 | else 466 | @$(WINSHELL) /c start simulavr --gdbserver --device $(MCU) --clock-freq \ 467 | $(DEBUG_MFREQ) --port $(DEBUG_PORT) 468 | endif 469 | @$(WINSHELL) /c start avr-$(DEBUG_UI) --command=$(GDBINIT_FILE) 470 | 471 | 472 | 473 | 474 | # Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB. 475 | COFFCONVERT = $(OBJCOPY) --debugging 476 | COFFCONVERT += --change-section-address .data-0x800000 477 | COFFCONVERT += --change-section-address .bss-0x800000 478 | COFFCONVERT += --change-section-address .noinit-0x800000 479 | COFFCONVERT += --change-section-address .eeprom-0x810000 480 | 481 | 482 | 483 | coff: $(TARGET).elf 484 | @echo 485 | @echo $(MSG_COFF) $(TARGET).cof 486 | $(COFFCONVERT) -O coff-avr $< $(TARGET).cof 487 | 488 | 489 | extcoff: $(TARGET).elf 490 | @echo 491 | @echo $(MSG_EXTENDED_COFF) $(TARGET).cof 492 | $(COFFCONVERT) -O coff-ext-avr $< $(TARGET).cof 493 | 494 | 495 | 496 | # Create final output files (.hex, .eep) from ELF output file. 497 | %.hex: %.elf 498 | @echo 499 | @echo $(MSG_FLASH) $@ 500 | $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@ 501 | 502 | %.eep: %.elf 503 | @echo 504 | @echo $(MSG_EEPROM) $@ 505 | -$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \ 506 | --change-section-lma .eeprom=0 -O $(FORMAT) $< $@ 507 | 508 | # Create extended listing file from ELF output file. 509 | %.lss: %.elf 510 | @echo 511 | @echo $(MSG_EXTENDED_LISTING) $@ 512 | $(OBJDUMP) -h -S $< > $@ 513 | 514 | # Create a symbol table from ELF output file. 515 | %.sym: %.elf 516 | @echo 517 | @echo $(MSG_SYMBOL_TABLE) $@ 518 | $(NM) -n $< > $@ 519 | 520 | 521 | 522 | # Create library from object files. 523 | .SECONDARY : $(TARGET).a 524 | .PRECIOUS : $(OBJ) 525 | %.a: $(OBJ) 526 | @echo 527 | @echo $(MSG_CREATING_LIBRARY) $@ 528 | $(AR) $@ $(OBJ) 529 | 530 | 531 | # Link: create ELF output file from object files. 532 | .SECONDARY : $(TARGET).elf 533 | .PRECIOUS : $(OBJ) 534 | %.elf: $(OBJ) 535 | @echo 536 | @echo $(MSG_LINKING) $@ 537 | $(CC) $(ALL_CFLAGS) $^ --output $@ $(LDFLAGS) 538 | 539 | 540 | # Compile: create object files from C source files. 541 | $(OBJDIR)/%.o : %.c 542 | @echo 543 | @echo $(MSG_COMPILING) $< 544 | $(CC) -c $(ALL_CFLAGS) $< -o $@ 545 | 546 | 547 | # Compile: create object files from C++ source files. 548 | $(OBJDIR)/%.o : %.cpp 549 | @echo 550 | @echo $(MSG_COMPILING_CPP) $< 551 | $(CC) -c $(ALL_CPPFLAGS) $< -o $@ 552 | 553 | 554 | # Compile: create assembler files from C source files. 555 | %.s : %.c 556 | $(CC) -S $(ALL_CFLAGS) $< -o $@ 557 | 558 | 559 | # Compile: create assembler files from C++ source files. 560 | %.s : %.cpp 561 | $(CC) -S $(ALL_CPPFLAGS) $< -o $@ 562 | 563 | 564 | # Assemble: create object files from assembler source files. 565 | $(OBJDIR)/%.o : %.S 566 | @echo 567 | @echo $(MSG_ASSEMBLING) $< 568 | $(CC) -c $(ALL_ASFLAGS) $< -o $@ 569 | 570 | 571 | # Create preprocessed source for use in sending a bug report. 572 | %.i : %.c 573 | $(CC) -E -mmcu=$(MCU) -I. $(CFLAGS) $< -o $@ 574 | 575 | 576 | # Target: clean project. 577 | clean: begin clean_list end 578 | 579 | clean_list : 580 | @echo 581 | @echo $(MSG_CLEANING) 582 | $(REMOVE) $(TARGET).hex 583 | $(REMOVE) $(TARGET).eep 584 | $(REMOVE) $(TARGET).cof 585 | $(REMOVE) $(TARGET).elf 586 | $(REMOVE) $(TARGET).map 587 | $(REMOVE) $(TARGET).sym 588 | $(REMOVE) $(TARGET).lss 589 | $(REMOVEDIR) $(OBJDIR) 590 | $(REMOVE) $(SRC:.c=.s) 591 | $(REMOVE) $(SRC:.c=.d) 592 | $(REMOVEDIR) .dep 593 | 594 | 595 | # Create object files directory 596 | $(shell mkdir $(OBJDIR) 2>/dev/null) 597 | 598 | 599 | # Include the dependency files. 600 | -include $(shell mkdir .dep 2>/dev/null) $(wildcard .dep/*) 601 | 602 | 603 | # Listing of phony targets. 604 | .PHONY : all begin finish end sizebefore sizeafter gccversion \ 605 | build elf hex eep lss sym coff extcoff \ 606 | clean clean_list program debug gdb-config 607 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # ArtNetNode 4 3 | 4 | Small Xmega32 and enc28j60 based DYI ArtNet Node with 4 DMX ports. 5 | 6 | ## Features: 7 | - 4 DMX Outputs(full 512 channels) 8 | - Testes framerate: 4x 52 FPS 9 | - Web Interface for configuration(WIP, see TODO) 10 | - industry standard 19" 1U housing 11 | - Ported [EtherCard](https://github.com/jcw/ethercard) to XMega32 12 | 13 | 14 | ## TODO: 15 | - [ ] Finish WebInterface 16 | - [ ] Persist Settings(Name, IP, Universes) 17 | - [ ] Use DMX Ports as Input 18 | - [ ] Implement ArtPoolReplay 19 | - [ ] Implement other ArtNet functions(setName,...) 20 | - [ ] Upload Eagle-File's 21 | 22 | 23 | ## Images 24 | 25 | ![First Prototype](images/ArtNetNode_Prototype_1.jpeg "First Prototype") 26 | First Prototype 27 | 28 | ![PCB](images/ArtNetNode_PCB.jpeg "PCB") 29 | Fresh PCB's 30 | 31 | ![Soldered PCB's](images/ArtNetNode_Prototype_2.jpeg "Soldered PCB's") 32 | Soldered PCB's 33 | 34 | ![Mounted front plate](images/ArtNetNode_Front1.jpeg "Mounted front plate") 35 | Mounted front plate 36 | 37 | ![Full Housing](images/ArtNetNode_FullHousing.jpeg "Full Housing") 38 | Full Housing 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /artnet.cpp: -------------------------------------------------------------------------------- 1 | #include "artnet.h" 2 | #include "dmx.h" 3 | #include "EtherCard.h" 4 | 5 | #include //memcpy 6 | 7 | 8 | volatile uint8_t universeFPS[DMX_NUM_UNIVERSES]; 9 | volatile uint8_t universeFPSCounter[DMX_NUM_UNIVERSES] = {0}; 10 | 11 | ArtNet artnet; //Global instance of ArtNet class 12 | 13 | // Opcodes 14 | #define ART_POLL 0x2000 15 | #define ART_POLL_REPLAY 0x2100 16 | #define ART_DMX 0x5000 17 | // Buffers 18 | #define MAX_BUFFER_ARTNET 530 19 | // Packet 20 | #define ART_NET_ID "Art-Net\0" 21 | 22 | 23 | 24 | /** 25 | * Receive ArtNet Packages from Ethercard 26 | * 27 | * 28 | **/ 29 | void ArtNet::udpArtNet(uint16_t dest_port, uint8_t src_ip[IP_LEN], uint16_t src_port, const char *data, uint16_t len) { 30 | uint8_t result = strncmp(data, "Art-Net",7); 31 | 32 | uint16_t opCode = (data[9] << 8) | data[8]; 33 | uint16_t protocolVersion = data[10] << 8 | data[11]; 34 | 35 | uint16_t universe = data[14] | data[15] << 8 ; 36 | uint16_t length = data[16] << 8 | data[17]; 37 | 38 | 39 | if(opCode == ART_POLL) { 40 | PORTA.OUTTGL = 0x02; 41 | sendArtPollReplay(); 42 | } else if(opCode == ART_DMX) { 43 | if (universe < 4) { 44 | memcpy(dmxBuffer[universe], data + 18, length); 45 | universeReceivedTMP(universe); 46 | } 47 | } 48 | } 49 | 50 | uint8_t ArtNet::getUniverseFPS(uint8_t universe) { 51 | if(universe < DMX_NUM_UNIVERSES) { 52 | return universeFPS[universe]; 53 | } else { 54 | return 0; 55 | } 56 | } 57 | 58 | void ArtNet::universeReceivedTMP(uint8_t universe) { 59 | if(universe < DMX_NUM_UNIVERSES) { 60 | universeFPSCounter[universe]++; 61 | } 62 | } 63 | 64 | void ArtNet::doTimerStuff1Hz(void) { 65 | for(uint8_t i=0; i < 4; i++) { 66 | universeFPS[i] = universeFPSCounter[i]; 67 | universeFPSCounter[i] = 0; 68 | } 69 | } 70 | 71 | void ArtNet::sendArtPollReplay(void) { 72 | //Build ArtPollReplay Package 73 | struct ArtPollReplay_t { 74 | uint8_t id[8]; 75 | uint8_t opCode[2]; 76 | uint8_t ipAddress[4]; 77 | uint8_t port[2]; 78 | uint8_t versInfoH; 79 | uint8_t versInfoL; 80 | uint8_t netSwitch; 81 | uint8_t subSwitch; 82 | uint8_t oemHi; 83 | uint8_t oem; 84 | uint8_t ubeaVersion; 85 | uint8_t status1; 86 | uint8_t estaManLo; 87 | uint8_t estaManHi; 88 | uint8_t shortName[18]; 89 | uint8_t longName[64]; 90 | uint8_t nodeReport[64]; 91 | uint8_t numPortsHi; 92 | uint8_t numPortsLo; 93 | uint8_t portTypes[4]; 94 | uint8_t goodInput[4]; 95 | uint8_t goodOutput[4]; 96 | uint8_t swIn[4]; 97 | uint8_t swOut[4]; 98 | uint8_t swVideo; 99 | uint8_t swMacro; 100 | uint8_t swRemote; 101 | uint8_t spare[3]; 102 | uint8_t style; 103 | uint8_t mac[6]; 104 | uint8_t bindIp[4]; 105 | uint8_t bindIndex; 106 | uint8_t status2; 107 | uint8_t filler[26]; 108 | } replay; 109 | 110 | memcpy(replay.id,ART_NET_ID, 8); 111 | replay.opCode[0] = ART_POLL_REPLAY; 112 | replay.opCode[1] = ART_POLL_REPLAY >> 8; 113 | 114 | memcpy(replay.ipAddress, ether.myip, IP_LEN); 115 | 116 | replay.port[0] = 0x36; 117 | replay.port[1] = 0x19; 118 | 119 | replay.versInfoH = 0; 120 | replay.versInfoL = 1; 121 | 122 | replay.netSwitch = 0; 123 | replay.subSwitch = 0; 124 | 125 | replay.oemHi = 0; 126 | replay.oem = 0; 127 | 128 | replay.ubeaVersion = 0; 129 | 130 | replay.status1 = 0xF0; 131 | 132 | replay.estaManLo = 'E'; 133 | replay.estaManHi = 'T'; 134 | 135 | memcpy(replay.shortName,"ArtNetNode 4 ", 18); 136 | memcpy(replay.longName,"ArtNetNode 4 LONG ", 22); 137 | //memcpy(replay.nodeReport,"ArtNetNode 4 LONG ", 64); 138 | 139 | replay.numPortsHi = 0; 140 | replay.numPortsLo = 4; 141 | 142 | //4x ArtNet Output + DMX512 Mode 143 | replay.portTypes[0] = 1 << 7; 144 | replay.portTypes[1] = 1 << 7; 145 | replay.portTypes[2] = 1 << 7; 146 | replay.portTypes[3] = 1 << 7; 147 | 148 | 149 | //replay.goodInput[] //TODO: Unused, transmit as zero 150 | 151 | //TODO: Relay check transmission status 152 | replay.goodOutput[0] = 1 << 7; 153 | replay.goodOutput[1] = 1 << 7; 154 | replay.goodOutput[2] = 1 << 7; 155 | replay.goodOutput[3] = 1 << 7; 156 | 157 | replay.style = 0x00; //Style 0x00 -> A DMX to7from Art-Net device 158 | 159 | memcpy(replay.mac, ether.mymac, ETH_LEN); 160 | memcpy(replay.bindIp, ether.myip, IP_LEN); 161 | 162 | replay.bindIndex = 1; //Root device 163 | 164 | replay.status2 = 1 << 3; //Supports ArtNed 3 + 4 Port adresses 165 | 166 | //TODO: Don't use static ip 167 | uint8_t hisip[4] = {0x02, 0x0, 0x0, 123}; 168 | 169 | //static void sendUdp (char *data,uint8_t len,uint16_t sport, uint8_t *dip, uint16_t dport); 170 | ether.sendUdp((char*)&replay, sizeof(replay), UDP_PORT_ARTNET_REPLY, hisip, UDP_PORT_ARTNET_REPLY ); //*/ 171 | } -------------------------------------------------------------------------------- /artnet.h: -------------------------------------------------------------------------------- 1 | #ifndef _ARTNET_H 2 | #define _ARTNET_H 1 3 | 4 | #include 5 | #include "net.h" 6 | 7 | // Port to listen on 8 | #define UDP_PORT_ARTNET 6454 /* (0x1936) */ 9 | // Port to reply on 10 | #define UDP_PORT_ARTNET_REPLY (UDP_PORT_ARTNET + 1) 11 | 12 | 13 | 14 | 15 | class ArtNet { 16 | 17 | public: 18 | static void udpArtNet(uint16_t dest_port, uint8_t src_ip[IP_LEN], uint16_t src_port, const char *data, uint16_t len); 19 | 20 | static uint8_t getUniverseFPS(uint8_t universe); 21 | 22 | static void universeReceivedTMP(uint8_t universe); 23 | 24 | static void doTimerStuff1Hz(void); 25 | 26 | private: 27 | static void sendArtPollReplay(void); 28 | }; 29 | 30 | 31 | extern ArtNet artnet; 32 | 33 | 34 | 35 | #endif -------------------------------------------------------------------------------- /dhcp.cpp: -------------------------------------------------------------------------------- 1 | // DHCP look-up functions based on the udp client 2 | // http://www.ietf.org/rfc/rfc2131.txt 3 | // 4 | // Author: Andrew Lindsay 5 | // Rewritten and optimized by Jean-Claude Wippler, http://jeelabs.org/ 6 | // 7 | // Rewritten dhcpStateMachine by Chris van den Hooven 8 | // as to implement dhcp-renew when lease expires (jun 2012) 9 | // 10 | // Various modifications and bug fixes contributed by Victor Aprea (oct 2012) 11 | // 12 | // Copyright: GPL V2 13 | // See http://www.gnu.org/licenses/gpl.html 14 | 15 | //#define DHCPDEBUG 16 | 17 | #include 18 | 19 | #include "EtherCard.h" 20 | #include "net.h" 21 | #include "timer.h" 22 | 23 | 24 | #include 25 | 26 | 27 | 28 | 29 | #define DHCP_BOOTREQUEST 1 30 | #define DHCP_BOOTRESPONSE 2 31 | 32 | // DHCP Message Type (option 53) (ref RFC 2132) 33 | #define DHCP_DISCOVER 1 34 | #define DHCP_OFFER 2 35 | #define DHCP_REQUEST 3 36 | #define DHCP_DECLINE 4 37 | #define DHCP_ACK 5 38 | #define DHCP_NAK 6 39 | #define DHCP_RELEASE 7 40 | 41 | // DHCP States for access in applications (ref RFC 2131) 42 | enum { 43 | DHCP_STATE_INIT, 44 | DHCP_STATE_SELECTING, 45 | DHCP_STATE_REQUESTING, 46 | DHCP_STATE_BOUND, 47 | DHCP_STATE_RENEWING, 48 | }; 49 | 50 | /* 51 | op 1 Message op code / message type. 52 | 1 = BOOTREQUEST, 2 = BOOTREPLY 53 | htype 1 Hardware address type, see ARP section in "Assigned 54 | Numbers" RFC; e.g., '1' = 10mb ethernet. 55 | hlen 1 Hardware address length (e.g. '6' for 10mb 56 | ethernet). 57 | hops 1 Client sets to zero, optionally used by relay agents 58 | when booting via a relay agent. 59 | xid 4 Transaction ID, a random number chosen by the 60 | client, used by the client and server to associate 61 | messages and responses between a client and a 62 | server. 63 | secs 2 Filled in by client, seconds elapsed since client 64 | began address acquisition or renewal process. 65 | flags 2 Flags (see figure 2). 66 | ciaddr 4 Client IP address; only filled in if client is in 67 | BOUND, RENEW or REBINDING state and can respond 68 | to ARP requests. 69 | yiaddr 4 'your' (client) IP address. 70 | siaddr 4 IP address of next server to use in bootstrap; 71 | returned in DHCPOFFER, DHCPACK by server. 72 | giaddr 4 Relay agent IP address, used in booting via a 73 | relay agent. 74 | chaddr 16 Client hardware address. 75 | sname 64 Optional server host name, null terminated string. 76 | file 128 Boot file name, null terminated string; "generic" 77 | name or null in DHCPDISCOVER, fully qualified 78 | directory-path name in DHCPOFFER. 79 | options var Optional parameters field. See the options 80 | documents for a list of defined options. 81 | */ 82 | 83 | 84 | 85 | 86 | // size 236 87 | typedef struct { 88 | uint8_t op, htype, hlen, hops; 89 | uint32_t xid; 90 | uint16_t secs, flags; 91 | uint8_t ciaddr[IP_LEN], yiaddr[IP_LEN], siaddr[IP_LEN], giaddr[IP_LEN]; 92 | uint8_t chaddr[16], sname[64], file[128]; 93 | // options 94 | } DHCPdata; 95 | 96 | #define DHCP_SERVER_PORT 67 97 | #define DHCP_CLIENT_PORT 68 98 | 99 | // timeouts im ms 100 | #define DHCP_REQUEST_TIMEOUT 10000 101 | 102 | #define DHCP_HOSTNAME_MAX_LEN 32 103 | 104 | // RFC 2132 Section 3.3: 105 | // The time value of 0xffffffff is reserved to represent "infinity". 106 | #define DHCP_INFINITE_LEASE 0xffffffff 107 | 108 | static uint8_t dhcpState = DHCP_STATE_INIT; 109 | static char hostname[DHCP_HOSTNAME_MAX_LEN] = "Arduino-ENC28j60-00"; // Last two characters will be filled by last 2 MAC digits ; 110 | static uint32_t currentXid; 111 | static uint32_t stateTimer; 112 | static uint32_t leaseStart; 113 | static uint32_t leaseTime; 114 | static uint8_t* bufPtr; 115 | 116 | static uint8_t dhcpCustomOptionNum = 0; 117 | static DhcpOptionCallback dhcpCustomOptionCallback = 0; 118 | 119 | extern uint8_t allOnes[];// = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; 120 | 121 | static void addToBuf (uint8_t b) { 122 | *bufPtr++ = b; 123 | } 124 | 125 | static void addBytes (uint8_t len, const uint8_t* data) { 126 | while (len-- > 0) 127 | addToBuf(*data++); 128 | } 129 | 130 | static void addOption (uint8_t opt, uint8_t len, const uint8_t* data) { 131 | addToBuf(opt); 132 | addToBuf(len); 133 | addBytes(len, data); 134 | } 135 | 136 | 137 | // Main DHCP sending function 138 | 139 | // implemented 140 | // state / msgtype 141 | // INIT / DHCPDISCOVER 142 | // SELECTING / DHCPREQUEST 143 | // BOUND (RENEWING) / DHCPREQUEST 144 | 145 | // ---------------------------------------------------------- 146 | // | |SELECTING |RENEWING |INIT | 147 | // ---------------------------------------------------------- 148 | // |broad/unicast |broadcast |unicast |broadcast | 149 | // |server-ip |MUST |MUST NOT |MUST NOT | option 54 150 | // |requested-ip |MUST |MUST NOT |MUST NOT | option 50 151 | // |ciaddr |zero |IP address |zero | 152 | // ---------------------------------------------------------- 153 | 154 | // options used (both send/receive) 155 | #define DHCP_OPT_SUBNET_MASK 1 156 | #define DHCP_OPT_ROUTERS 3 157 | #define DHCP_OPT_DOMAIN_NAME_SERVERS 6 158 | #define DHCP_OPT_HOSTNAME 12 159 | #define DHCP_OPT_REQUESTED_ADDRESS 50 160 | #define DHCP_OPT_LEASE_TIME 51 161 | #define DHCP_OPT_MESSAGE_TYPE 53 162 | #define DHCP_OPT_SERVER_IDENTIFIER 54 163 | #define DHCP_OPT_PARAMETER_REQUEST_LIST 55 164 | #define DHCP_OPT_RENEWAL_TIME 58 165 | #define DHCP_OPT_CLIENT_IDENTIFIER 61 166 | #define DHCP_OPT_END 255 167 | 168 | #define DHCP_HTYPE_ETHER 1 169 | 170 | static void send_dhcp_message(uint8_t *requestip) { 171 | 172 | memset(gPB, 0, UDP_DATA_P + sizeof( DHCPdata )); 173 | 174 | EtherCard::udpPrepare(DHCP_CLIENT_PORT, 175 | (dhcpState == DHCP_STATE_BOUND ? EtherCard::dhcpip : allOnes), 176 | DHCP_SERVER_PORT); 177 | 178 | // If we ever don't do this, the DHCP renewal gets sent to whatever random 179 | // destmacaddr was used by other code. Rather than cache the MAC address of 180 | // the DHCP server, just force a broadcast here in all cases. 181 | EtherCard::copyMac(gPB + ETH_DST_MAC, allOnes); //force broadcast mac 182 | 183 | // Build DHCP Packet from buf[UDP_DATA_P] 184 | DHCPdata *dhcpPtr = (DHCPdata*) (gPB + UDP_DATA_P); 185 | dhcpPtr->op = DHCP_BOOTREQUEST; 186 | dhcpPtr->htype = 1; 187 | dhcpPtr->hlen = 6; 188 | dhcpPtr->xid = currentXid; 189 | if (dhcpState == DHCP_STATE_BOUND) { 190 | EtherCard::copyIp(dhcpPtr->ciaddr, EtherCard::myip); 191 | } 192 | EtherCard::copyMac(dhcpPtr->chaddr, EtherCard::mymac); 193 | 194 | // options defined as option, length, value 195 | bufPtr = gPB + UDP_DATA_P + sizeof( DHCPdata ); 196 | // DHCP magic cookie 197 | static const uint8_t cookie[] PROGMEM = { 0x63,0x82,0x53,0x63 }; 198 | for (uint8_t i = 0; i < sizeof(cookie); i++) 199 | addToBuf(pgm_read_byte(&cookie[i])); 200 | addToBuf(DHCP_OPT_MESSAGE_TYPE); // DHCP_STATE_SELECTING, DHCP_STATE_REQUESTING 201 | addToBuf(1); // Length 202 | addToBuf(dhcpState == DHCP_STATE_INIT ? DHCP_DISCOVER : DHCP_REQUEST); 203 | 204 | // Client Identifier Option, this is the client mac address 205 | addToBuf(DHCP_OPT_CLIENT_IDENTIFIER); 206 | addToBuf(1 + ETH_LEN); // Length (hardware type + client MAC) 207 | addToBuf(DHCP_HTYPE_ETHER); 208 | addBytes(ETH_LEN, EtherCard::mymac); 209 | 210 | if (hostname[0]) { 211 | addOption(DHCP_OPT_HOSTNAME, strlen(hostname), (uint8_t*) hostname); 212 | } 213 | 214 | if (requestip != 0) { 215 | addOption(DHCP_OPT_REQUESTED_ADDRESS, IP_LEN, requestip); 216 | addOption(DHCP_OPT_SERVER_IDENTIFIER, IP_LEN, EtherCard::dhcpip); 217 | } 218 | 219 | // Additional info in parameter list - minimal list for what we need 220 | uint8_t len = 3; 221 | if (dhcpCustomOptionNum) 222 | len++; 223 | addToBuf(DHCP_OPT_PARAMETER_REQUEST_LIST); 224 | addToBuf(len); // Length 225 | addToBuf(DHCP_OPT_SUBNET_MASK); 226 | addToBuf(DHCP_OPT_ROUTERS); 227 | addToBuf(DHCP_OPT_DOMAIN_NAME_SERVERS); 228 | if (dhcpCustomOptionNum) 229 | addToBuf(dhcpCustomOptionNum); // Custom option 230 | 231 | addToBuf(DHCP_OPT_END); 232 | 233 | // packet size will be under 300 bytes 234 | EtherCard::udpTransmit((bufPtr - gPB) - UDP_DATA_P); 235 | } 236 | 237 | static void process_dhcp_offer(uint16_t len, uint8_t *offeredip) { 238 | // Map struct onto payload 239 | DHCPdata *dhcpPtr = (DHCPdata*) (gPB + UDP_DATA_P); 240 | 241 | // Offered IP address is in yiaddr 242 | EtherCard::copyIp(offeredip, dhcpPtr->yiaddr); 243 | 244 | // Search for the server IP 245 | uint8_t *ptr = (uint8_t*) (dhcpPtr + 1) + 4; 246 | do { 247 | uint8_t option = *ptr++; 248 | uint8_t optionLen = *ptr++; 249 | if (option == DHCP_OPT_SERVER_IDENTIFIER) { 250 | EtherCard::copyIp(EtherCard::dhcpip, ptr); 251 | break; 252 | } 253 | ptr += optionLen; 254 | } while (ptr < gPB + len); 255 | } 256 | 257 | static void process_dhcp_ack(uint16_t len) { 258 | // Map struct onto payload 259 | DHCPdata *dhcpPtr = (DHCPdata*) (gPB + UDP_DATA_P); 260 | 261 | // Allocated IP address is in yiaddr 262 | EtherCard::copyIp(EtherCard::myip, dhcpPtr->yiaddr); 263 | 264 | // Scan through variable length option list identifying options we want 265 | uint8_t *ptr = (uint8_t*) (dhcpPtr + 1) + 4; 266 | bool done = false; 267 | do { 268 | uint8_t option = *ptr++; 269 | uint8_t optionLen = *ptr++; 270 | switch (option) { 271 | case DHCP_OPT_SUBNET_MASK: 272 | EtherCard::copyIp(EtherCard::netmask, ptr); 273 | break; 274 | case DHCP_OPT_ROUTERS: 275 | EtherCard::copyIp(EtherCard::gwip, ptr); 276 | break; 277 | case DHCP_OPT_DOMAIN_NAME_SERVERS: 278 | EtherCard::copyIp(EtherCard::dnsip, ptr); 279 | break; 280 | case DHCP_OPT_LEASE_TIME: 281 | case DHCP_OPT_RENEWAL_TIME: 282 | leaseTime = 0; 283 | for (uint8_t i = 0; i<4; i++) 284 | leaseTime = (leaseTime << 8) + ptr[i]; 285 | if (leaseTime != DHCP_INFINITE_LEASE) { 286 | leaseTime *= 1000; // milliseconds 287 | } 288 | break; 289 | case DHCP_OPT_END: 290 | done = true; 291 | break; 292 | default: { 293 | // Is is a custom configured option? 294 | if (dhcpCustomOptionCallback && option == dhcpCustomOptionNum) { 295 | dhcpCustomOptionCallback(option, ptr, optionLen); 296 | } 297 | } 298 | } 299 | ptr += optionLen; 300 | } while (!done && ptr < gPB + len); 301 | } 302 | 303 | static bool dhcp_received_message_type (uint16_t len, uint8_t msgType) { 304 | // Map struct onto payload 305 | DHCPdata *dhcpPtr = (DHCPdata*) (gPB + UDP_DATA_P); 306 | 307 | if (len >= 70 && gPB[UDP_SRC_PORT_L_P] == DHCP_SERVER_PORT && 308 | dhcpPtr->xid == currentXid ) { 309 | 310 | uint8_t *ptr = (uint8_t*) (dhcpPtr + 1) + 4; 311 | do { 312 | uint8_t option = *ptr++; 313 | uint8_t optionLen = *ptr++; 314 | if(option == DHCP_OPT_MESSAGE_TYPE && *ptr == msgType ) { 315 | return true; 316 | } 317 | ptr += optionLen; 318 | } while (ptr < gPB + len); 319 | } 320 | return false; 321 | } 322 | 323 | static char toAsciiHex(uint8_t b) { 324 | char c = b & 0x0f; 325 | c += (c <= 9) ? '0' : 'A'-10; 326 | return c; 327 | } 328 | 329 | bool EtherCard::dhcpSetup (const char *hname, bool fromRam) { 330 | // Use during setup, as this discards all incoming requests until it returns. 331 | // That shouldn't be a problem, because we don't have an IP-address yet. 332 | // Will try 60 secs to obtain DHCP-lease. 333 | 334 | using_dhcp = true; 335 | 336 | if(hname != 0) { 337 | if(fromRam) { 338 | strncpy(hostname, hname, DHCP_HOSTNAME_MAX_LEN); 339 | } 340 | else { 341 | strncpy_P(hostname, hname, DHCP_HOSTNAME_MAX_LEN); 342 | } 343 | } 344 | else { 345 | // Set a unique hostname, use Arduino-?? with last octet of mac address 346 | hostname[strlen(hostname) - 2] = toAsciiHex(mymac[5] >> 4); // Appends mac to last 2 digits of the hostname 347 | hostname[strlen(hostname) - 1] = toAsciiHex(mymac[5]); // Even if it's smaller than the maximum 348 | } 349 | 350 | dhcpState = DHCP_STATE_INIT; 351 | uint16_t start = millis(); 352 | 353 | while (dhcpState != DHCP_STATE_BOUND && uint16_t(millis()) - start < 60000) { 354 | if (isLinkUp()) DhcpStateMachine(packetReceive()); 355 | } 356 | updateBroadcastAddress(); 357 | delaycnt = 0; 358 | return dhcpState == DHCP_STATE_BOUND ; 359 | } 360 | 361 | void EtherCard::dhcpAddOptionCallback(uint8_t option, DhcpOptionCallback callback) 362 | { 363 | dhcpCustomOptionNum = option; 364 | dhcpCustomOptionCallback = callback; 365 | } 366 | 367 | void EtherCard::DhcpStateMachine (uint16_t len) 368 | { 369 | 370 | #ifdef DHCPDEBUG 371 | if (dhcpState != DHCP_STATE_BOUND) { 372 | Serial.print(millis()); 373 | Serial.print(" State: "); 374 | } 375 | switch (dhcpState) { 376 | case DHCP_STATE_INIT: 377 | Serial.println("Init"); 378 | break; 379 | case DHCP_STATE_SELECTING: 380 | Serial.println("Selecting"); 381 | break; 382 | case DHCP_STATE_REQUESTING: 383 | Serial.println("Requesting"); 384 | break; 385 | case DHCP_STATE_RENEWING: 386 | Serial.println("Renew"); 387 | break; 388 | } 389 | #endif 390 | 391 | switch (dhcpState) { 392 | 393 | case DHCP_STATE_BOUND: 394 | //!@todo Due to millis() wrap-around, DHCP renewal may not work if leaseTime is larger than 49days 395 | if (leaseTime != DHCP_INFINITE_LEASE && millis() - leaseStart >= leaseTime) { 396 | send_dhcp_message(myip); 397 | dhcpState = DHCP_STATE_RENEWING; 398 | stateTimer = millis(); 399 | } 400 | break; 401 | 402 | case DHCP_STATE_INIT: 403 | currentXid = millis(); 404 | memset(myip,0,IP_LEN); // force ip 0.0.0.0 405 | send_dhcp_message(0); 406 | enableBroadcast(true); //Temporarily enable broadcasts 407 | dhcpState = DHCP_STATE_SELECTING; 408 | stateTimer = millis(); 409 | break; 410 | 411 | case DHCP_STATE_SELECTING: 412 | if (dhcp_received_message_type(len, DHCP_OFFER)) { 413 | uint8_t offeredip[IP_LEN]; 414 | process_dhcp_offer(len, offeredip); 415 | send_dhcp_message(offeredip); 416 | dhcpState = DHCP_STATE_REQUESTING; 417 | stateTimer = millis(); 418 | } else { 419 | if (millis() - stateTimer > DHCP_REQUEST_TIMEOUT) { 420 | dhcpState = DHCP_STATE_INIT; 421 | } 422 | } 423 | break; 424 | 425 | case DHCP_STATE_REQUESTING: 426 | case DHCP_STATE_RENEWING: 427 | if (dhcp_received_message_type(len, DHCP_ACK)) { 428 | disableBroadcast(true); //Disable broadcast after temporary enable 429 | process_dhcp_ack(len); 430 | leaseStart = millis(); 431 | if (gwip[0] != 0) setGwIp(gwip); // why is this? because it initiates an arp request 432 | dhcpState = DHCP_STATE_BOUND; 433 | } else { 434 | if (millis() - stateTimer > DHCP_REQUEST_TIMEOUT) { 435 | dhcpState = DHCP_STATE_INIT; 436 | } 437 | } 438 | break; 439 | 440 | } 441 | } 442 | 443 | 444 | 445 | -------------------------------------------------------------------------------- /display.cpp: -------------------------------------------------------------------------------- 1 | #include "display.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #define Takt_TWI 400000 10 | #define TWI_BAUD(F_SYS, F_TWI) ((F_SYS / (2 * F_TWI)) - 5) 11 | #define TWI_BAUDRATE TWI_BAUD(F_CPU, Takt_TWI) 12 | 13 | 14 | uint8_t displayfunction = 0x00; 15 | uint8_t displaycontrol = 0x00; 16 | uint8_t displaymode = 0x00; 17 | uint8_t backlightVal = LCD_BACKLIGHT; 18 | 19 | void setupTWI(void) { 20 | TWIC.MASTER.BAUD = TWI_BAUDRATE; 21 | 22 | TWIC.MASTER.CTRLA = TWI_MASTER_WIF_bm | TWI_MASTER_ENABLE_bm | TWI_MASTER_INTLVL_gm; //Enable Write Interupt Flag, Enable Master, Interrupt Level 0 23 | 24 | //RESET Bus State 25 | TWIC.MASTER.STATUS = TWI_MASTER_BUSSTATE_IDLE_gc; 26 | } 27 | 28 | 29 | void twiWrite(unsigned char byte) { 30 | TWIC.MASTER.ADDR = LCD_ADDR; 31 | while((TWIC.MASTER.STATUS & TWI_MASTER_WIF_bm) == 0); 32 | 33 | TWIC.MASTER.DATA = byte; 34 | while((TWIC.MASTER.STATUS & TWI_MASTER_WIF_bm) == 0); 35 | } 36 | 37 | 38 | 39 | void send(uint8_t value, uint8_t mode); 40 | 41 | void write(uint8_t value) { 42 | send(value, Rs); 43 | } 44 | 45 | void writeDirect(uint8_t b) { 46 | twiWrite(b | backlightVal); 47 | } 48 | 49 | void pulseEnable(uint8_t _data){ 50 | writeDirect(_data | En); // En high 51 | _delay_us(1); // enable pulse must be >450ns 52 | 53 | writeDirect(_data & ~En); // En low 54 | _delay_us(38); // commands need > 37us to settle 55 | } 56 | 57 | void write4bits(uint8_t data) { 58 | writeDirect(data); 59 | pulseEnable(data); 60 | } 61 | 62 | 63 | void send(uint8_t value, uint8_t mode) { 64 | uint8_t highnib=value&0xf0; 65 | uint8_t lownib=(value<<4)&0xf0; 66 | write4bits((highnib)|mode); 67 | write4bits((lownib)|mode); 68 | } 69 | 70 | void command(uint8_t value) { 71 | send(value, 0); 72 | } 73 | 74 | void writeStr(const char value[]) { 75 | while(*value != 0) { 76 | send(*value++, Rs); 77 | } 78 | } 79 | 80 | void writeInt(uint8_t value) { 81 | char str[4]; 82 | 83 | itoa(value, str, 10); 84 | writeStr(str); 85 | } 86 | 87 | void writeIntWidth(uint8_t value, uint8_t width) { 88 | char str[4]; 89 | 90 | itoa(value, str, 10); 91 | 92 | if(width >= 3 && value < 100) { 93 | write(' '); 94 | } 95 | 96 | if(width >= 2 && value < 10) { 97 | write(' '); 98 | } 99 | 100 | writeStr(str); 101 | } 102 | 103 | /** 104 | * Setup the Display 105 | */ 106 | void setupDisplay(void) { 107 | displayfunction = LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS; 108 | 109 | 110 | //put the LCD into 4 bit mode 111 | // this is according to the hitachi HD44780 datasheet 112 | // figure 24, pg 46 113 | 114 | //_delay_ms(1000); 115 | 116 | // we start in 8bit mode, try to set 4 bit mode 117 | write4bits(0x03 << 4); 118 | _delay_ms(5); // wait min 4.1ms 119 | 120 | // second try 121 | write4bits(0x03 << 4); 122 | _delay_ms(5); // wait min 4.1ms 123 | 124 | // third go! 125 | write4bits(0x03 << 4); 126 | _delay_ms(1); 127 | 128 | // finally, set to 4-bit interface 129 | write4bits(0x02 << 4); 130 | 131 | // set # lines, font size, etc. 132 | command(LCD_FUNCTIONSET | displayfunction); 133 | 134 | // turn the display on with no cursor or blinking default 135 | displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; 136 | command(LCD_DISPLAYCONTROL | displaycontrol); 137 | 138 | 139 | command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero 140 | _delay_ms(20); // this command takes a long time! 141 | 142 | // Initialize to default text direction (for roman languages) 143 | displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; 144 | 145 | // set the entry mode 146 | command(LCD_ENTRYMODESET | displaymode); 147 | 148 | command(LCD_RETURNHOME); // set cursor position to zero 149 | _delay_ms(20); // this command takes a long time! 150 | 151 | backlightVal = LCD_BACKLIGHT; 152 | } 153 | 154 | void setCursor(uint8_t col, uint8_t row) { 155 | int numlines = 2; 156 | int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 }; 157 | if ( row > numlines ) { 158 | row = numlines-1; // we count rows starting w/0 159 | } 160 | command(LCD_SETDDRAMADDR | (col + row_offsets[row])); 161 | } 162 | 163 | void clearDisplay(void) { 164 | command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero 165 | _delay_ms(20); // this command takes a long time! 166 | } -------------------------------------------------------------------------------- /display.h: -------------------------------------------------------------------------------- 1 | #ifndef _DISPLAY_H 2 | #define _DISPLAY_H 1 3 | 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define LCD_ADDR (0x3F << 1) 10 | 11 | 12 | // commands 13 | #define LCD_CLEARDISPLAY 0x01 14 | #define LCD_RETURNHOME 0x02 15 | #define LCD_ENTRYMODESET 0x04 16 | #define LCD_DISPLAYCONTROL 0x08 17 | #define LCD_CURSORSHIFT 0x10 18 | #define LCD_FUNCTIONSET 0x20 19 | #define LCD_SETCGRAMADDR 0x40 20 | #define LCD_SETDDRAMADDR 0x80 21 | 22 | // flags for display entry mode 23 | #define LCD_ENTRYRIGHT 0x00 24 | #define LCD_ENTRYLEFT 0x02 25 | #define LCD_ENTRYSHIFTINCREMENT 0x01 26 | #define LCD_ENTRYSHIFTDECREMENT 0x00 27 | 28 | // flags for display on/off control 29 | #define LCD_DISPLAYON 0x04 30 | #define LCD_DISPLAYOFF 0x00 31 | #define LCD_CURSORON 0x02 32 | #define LCD_CURSOROFF 0x00 33 | #define LCD_BLINKON 0x01 34 | #define LCD_BLINKOFF 0x00 35 | 36 | // flags for display/cursor shift 37 | #define LCD_DISPLAYMOVE 0x08 38 | #define LCD_CURSORMOVE 0x00 39 | #define LCD_MOVERIGHT 0x04 40 | #define LCD_MOVELEFT 0x00 41 | 42 | // flags for function set 43 | #define LCD_8BITMODE 0x10 44 | #define LCD_4BITMODE 0x00 45 | #define LCD_2LINE 0x08 46 | #define LCD_1LINE 0x00 47 | #define LCD_5x10DOTS 0x04 48 | #define LCD_5x8DOTS 0x00 49 | 50 | // flags for backlight control 51 | #define LCD_BACKLIGHT 0x08 52 | #define LCD_NOBACKLIGHT 0x00 53 | 54 | #define En 1 << 2 // B00000100 // Enable bit 55 | #define Rw 1 << 1 // B00000010 // Read/Write bit 56 | #define Rs 1 << 0 // B00000001 // Register select bit 57 | 58 | 59 | extern void setupTWI(void); 60 | extern void twiWrite(unsigned char byte); 61 | extern void writeStr(const char* value); 62 | extern void writeInt(uint8_t value); 63 | extern void writeIntWidth(uint8_t value, uint8_t width); 64 | extern void write(uint8_t value); 65 | 66 | extern void setupDisplay(void); 67 | extern void setCursor(uint8_t col, uint8_t row); 68 | 69 | extern void clearDisplay(void); 70 | 71 | #endif -------------------------------------------------------------------------------- /dmx.cpp: -------------------------------------------------------------------------------- 1 | #include "dmx.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | uint8_t dmxBuffer[DMX_NUM_UNIVERSES][DMX_UNIVERSE_SIZE]; 9 | 10 | uint8_t* getUniverseBuffer(uint8_t universe) { 11 | return dmxBuffer[universe]; 12 | } 13 | 14 | 15 | enum DMX_STATE { 16 | DMX_IDLE, 17 | DMX_BREAK, 18 | DMX_START, 19 | DMX_RUN 20 | }; 21 | 22 | typedef struct DMX_PORT { 23 | PORT_t* PORT; 24 | uint8_t PIN; 25 | register8_t* PIN_CTRL; 26 | USART_t* USART; 27 | uint8_t UNIVERSE; 28 | uint8_t OUTPUT_PORT; 29 | enum DMX_STATE TX_STATE; 30 | uint16_t TX_POSITION; 31 | 32 | } DMX_t; 33 | 34 | 35 | DMX_t dmxPorts[DMX_NUM_UNIVERSES]; 36 | 37 | 38 | #define DMX_BAUD_BSCALE 0 // vorher 0b1001 -> -1 39 | #define DMX_BAUD_RESET_BSEL 15 // 125k BAUD @32Mhz, used for RESET 40 | #define DMX_BAUD_NORMAL_BSEL 7 // 250k BAUD @32Mhz 41 | 42 | void setupDMXPort(DMX_t* PORT) { 43 | PORT->PORT->DIRSET = 1 << PORT->PIN; // Pin 3 -> UART 0 TX 44 | *PORT->PIN_CTRL = PORT_INVEN_bm; 45 | 46 | PORT->USART->BAUDCTRLB = DMX_BAUD_BSCALE << 4; 47 | PORT->USART->BAUDCTRLA = DMX_BAUD_RESET_BSEL; 48 | 49 | PORT->USART->CTRLA = USART_TXCINTLVL_gm; // TX Highest INT Level 50 | PORT->USART->CTRLC = USART_SBMODE_bm | USART_CHSIZE_8BIT_gc; //2 StopBits, 8 DataBits 51 | 52 | PORT->USART->CTRLB = USART_TXEN_bm; //Enable TX 53 | } 54 | 55 | void setupDMX(void) { 56 | dmxPorts[0].PORT = &PORTC; 57 | dmxPorts[0].PIN = 3; 58 | dmxPorts[0].PIN_CTRL = &PORTC.PIN3CTRL; 59 | dmxPorts[0].USART = &USARTC0; 60 | dmxPorts[0].UNIVERSE = 0; 61 | dmxPorts[0].OUTPUT_PORT = 1; 62 | dmxPorts[0].TX_STATE = DMX_IDLE; 63 | 64 | dmxPorts[1].PORT = &PORTC; 65 | dmxPorts[1].PIN = 7; 66 | dmxPorts[1].PIN_CTRL = &PORTC.PIN7CTRL; 67 | dmxPorts[1].USART = &USARTC1; 68 | dmxPorts[1].UNIVERSE = 1; 69 | dmxPorts[1].OUTPUT_PORT = 2; 70 | dmxPorts[1].TX_STATE = DMX_IDLE; 71 | 72 | dmxPorts[2].PORT = &PORTD; 73 | dmxPorts[2].PIN = 3; 74 | dmxPorts[2].PIN_CTRL = &PORTD.PIN3CTRL; 75 | dmxPorts[2].USART = &USARTD0; 76 | dmxPorts[2].UNIVERSE = 2; 77 | dmxPorts[2].OUTPUT_PORT = 3; 78 | dmxPorts[2].TX_STATE = DMX_IDLE; 79 | 80 | dmxPorts[3].PORT = &PORTE; 81 | dmxPorts[3].PIN = 3; 82 | dmxPorts[3].PIN_CTRL = &PORTE.PIN3CTRL; 83 | dmxPorts[3].USART = &USARTE0; 84 | dmxPorts[3].UNIVERSE = 3; 85 | dmxPorts[3].OUTPUT_PORT = 4; 86 | dmxPorts[3].TX_STATE = DMX_IDLE; 87 | 88 | 89 | for(uint8_t i=0; i < DMX_NUM_UNIVERSES; i++) { 90 | setupDMXPort(&dmxPorts[i]); 91 | } 92 | } 93 | 94 | 95 | void startDMX_TX(void) { 96 | for(uint8_t i=0; i < DMX_NUM_UNIVERSES; i++) { 97 | if(dmxPorts[i].TX_STATE == DMX_IDLE) { 98 | //Set Baud 99 | dmxPorts[i].USART->BAUDCTRLA = DMX_BAUD_RESET_BSEL; 100 | dmxPorts[i].USART->BAUDCTRLB = DMX_BAUD_BSCALE << 4; 101 | 102 | //Send Break Byte 103 | dmxPorts[i].USART->DATA = 0x00; 104 | 105 | dmxPorts[i].TX_STATE = DMX_START; 106 | } 107 | } 108 | } 109 | 110 | /** 111 | * Handle DMX Tx Interrupt 112 | */ 113 | void txISR(DMX_t* PORT) { 114 | switch(PORT->TX_STATE) { 115 | case DMX_START: 116 | //Change Baudrate 117 | PORT->USART->BAUDCTRLA = DMX_BAUD_NORMAL_BSEL; 118 | PORT->USART->BAUDCTRLB = DMX_BAUD_BSCALE << 4; 119 | 120 | //SEND START Byte 121 | PORT->USART->DATA = 0x00; 122 | 123 | PORT->TX_POSITION = 0; 124 | PORT->TX_STATE = DMX_RUN; 125 | break; 126 | case DMX_RUN: 127 | PORT->USART->DATA = dmxBuffer[PORT->UNIVERSE][PORT->TX_POSITION++]; 128 | 129 | if(PORT->TX_POSITION >= 512) { 130 | PORT->TX_STATE = DMX_IDLE; 131 | } 132 | break; 133 | 134 | } 135 | } 136 | 137 | ISR(USARTC0_TXC_vect) //Transimission complete 138 | { 139 | txISR(&dmxPorts[0]); 140 | } 141 | 142 | ISR(USARTC1_TXC_vect) //Transimission complete 143 | { 144 | txISR(&dmxPorts[1]); 145 | } 146 | 147 | ISR(USARTD0_TXC_vect) //Transimission complete 148 | { 149 | txISR(&dmxPorts[2]); 150 | } 151 | 152 | ISR(USARTE0_TXC_vect) //Transimission complete 153 | { 154 | txISR(&dmxPorts[3]); 155 | } 156 | -------------------------------------------------------------------------------- /dmx.h: -------------------------------------------------------------------------------- 1 | #ifndef _DMX_H 2 | #define _DMX_H 1 3 | 4 | #include 5 | 6 | #define DMX_NUM_UNIVERSES 4 7 | #define DMX_UNIVERSE_SIZE 512 8 | 9 | extern uint8_t my_mac[6]; 10 | extern uint8_t my_ip[4]; 11 | 12 | extern uint8_t dmxBuffer[DMX_NUM_UNIVERSES][DMX_UNIVERSE_SIZE]; 13 | 14 | 15 | extern uint8_t* getUniverseBuffer(uint8_t universe); 16 | 17 | 18 | extern void setupDMX(void); 19 | extern void startDMX_TX(void); 20 | //extern void startDMX_TX(void); 21 | 22 | 23 | #endif -------------------------------------------------------------------------------- /dns.cpp: -------------------------------------------------------------------------------- 1 | // DNS look-up functions based on the udp client 2 | // Author: Guido Socher 3 | // Copyright: GPL V2 4 | // 5 | // 2010-05-20 6 | 7 | #include "EtherCard.h" 8 | #include "net.h" 9 | 10 | #include "timer.h" 11 | 12 | #include 13 | 14 | #define gPB ether.buffer 15 | 16 | static uint8_t dnstid_l; // a counter for transaction ID 17 | #define DNSCLIENT_SRC_PORT_H 0xE0 18 | 19 | #define DNS_TYPE_A 1 20 | #define DNS_CLASS_IN 1 21 | 22 | static void dnsRequest (const char *hostname, bool fromRam) { 23 | ++dnstid_l; // increment for next request, finally wrap 24 | if (ether.dnsip[0] == 0) 25 | memset(ether.dnsip, 8, IP_LEN); // use 8.8.8.8 Google DNS as default 26 | ether.udpPrepare((DNSCLIENT_SRC_PORT_H << 8) | dnstid_l, ether.dnsip, DNS_PORT); 27 | memset(gPB + UDP_DATA_P, 0, 12); 28 | 29 | uint8_t *p = gPB + UDP_DATA_P + 12; 30 | char c; 31 | do { 32 | uint8_t n = 0; 33 | for(;;) { 34 | c = fromRam ? *hostname : pgm_read_byte(hostname); 35 | ++hostname; 36 | if (c == '.' || c == 0) 37 | break; 38 | p[++n] = c; 39 | } 40 | *p++ = n; 41 | p += n; 42 | } while (c != 0); 43 | 44 | *p++ = 0; // terminate with zero, means root domain. 45 | *p++ = 0; 46 | *p++ = DNS_TYPE_A; 47 | *p++ = 0; 48 | *p++ = DNS_CLASS_IN; 49 | uint8_t i = p - gPB - UDP_DATA_P; 50 | gPB[UDP_DATA_P] = i; 51 | gPB[UDP_DATA_P+1] = dnstid_l; 52 | gPB[UDP_DATA_P+2] = 1; // flags, standard recursive query 53 | gPB[UDP_DATA_P+5] = 1; // 1 question 54 | ether.udpTransmit(i); 55 | } 56 | 57 | /** @brief Check if packet is DNS response. 58 | @param plen Size of packet 59 | @return bool True if DNS response has error. False if not DNS response or DNS response OK. 60 | @note hisip contains IP address of requested host or 0.0.0.0 on failure 61 | */ 62 | static bool checkForDnsAnswer (uint16_t plen) { 63 | uint8_t *p = gPB + UDP_DATA_P; //start of UDP payload 64 | if (plen < 70 || gPB[UDP_SRC_PORT_L_P] != DNS_PORT || //from DNS source port 65 | gPB[UDP_DST_PORT_H_P] != DNSCLIENT_SRC_PORT_H || //response to same port as we sent from (MSB) 66 | gPB[UDP_DST_PORT_L_P] != dnstid_l || //response to same port as we sent from (LSB) 67 | p[1] != dnstid_l) //message id same as we sent 68 | return false; //not our DNS response 69 | if((p[3] & 0x0F) != 0) 70 | return true; //DNS response received with error 71 | 72 | p += *p; // we encoded the query len into tid 73 | for (;;) { 74 | if (*p & 0xC0) 75 | p += 2; 76 | else 77 | while (++p < gPB + plen) { 78 | if (*p == 0) { 79 | ++p; 80 | break; 81 | } 82 | } 83 | if (p + 14 > gPB + plen) 84 | break; 85 | if (p[1] == DNS_TYPE_A && p[9] == 4) { // type "A" and IPv4 86 | ether.copyIp(ether.hisip, p + 10); 87 | break; 88 | } 89 | p += p[9] + 10; 90 | } 91 | return false; //No error 92 | } 93 | 94 | // use during setup, as this discards all incoming requests until it returns 95 | bool EtherCard::dnsLookup (const char* name, bool fromRam) { 96 | uint16_t start = millis(); 97 | 98 | while(!isLinkUp()) 99 | { 100 | if (uint16_t(millis()) - start >= 30000) 101 | return false; //timeout waiting for link 102 | } 103 | while(clientWaitingDns()) 104 | { 105 | packetLoop(packetReceive()); 106 | if (uint16_t(millis()) - start >= 30000) 107 | return false; //timeout waiting for gateway ARP 108 | } 109 | 110 | memset(hisip, 0, IP_LEN); 111 | dnsRequest(name, fromRam); 112 | 113 | start = millis(); 114 | while (hisip[0] == 0) { 115 | if (uint16_t(millis()) - start >= 30000) 116 | return false; //timout waiting for dns response 117 | uint16_t len = packetReceive(); 118 | if (len > 0 && packetLoop(len) == 0) //packet not handled by tcp/ip packet loop 119 | if(checkForDnsAnswer(len)) 120 | return false; //DNS response recieved with error 121 | } 122 | 123 | return true; 124 | } 125 | -------------------------------------------------------------------------------- /enc28j60.cpp: -------------------------------------------------------------------------------- 1 | // Microchip ENC28J60 Ethernet Interface Driver 2 | // Author: Guido Socher 3 | // Copyright: GPL V2 4 | // 5 | // Based on the enc28j60.c file from the AVRlib library by Pascal Stang. 6 | // For AVRlib See http://www.procyonengineering.com/ 7 | // Used with explicit permission of Pascal Stang. 8 | // 9 | // 2010-05-20 10 | 11 | #include "enc28j60.h" 12 | 13 | #include "timer.h" 14 | 15 | #include 16 | #include 17 | 18 | uint16_t ENC28J60::bufferSize; 19 | bool ENC28J60::broadcast_enabled = false; 20 | bool ENC28J60::promiscuous_enabled = false; 21 | 22 | // ENC28J60 Control Registers 23 | // Control register definitions are a combination of address, 24 | // bank number, and Ethernet/MAC/PHY indicator bits. 25 | // - Register address (bits 0-4) 26 | // - Bank number (bits 5-6) 27 | // - MAC/PHY indicator (bit 7) 28 | #define ADDR_MASK 0x1F 29 | #define BANK_MASK 0x60 30 | #define SPRD_MASK 0x80 31 | // All-bank registers 32 | #define EIE 0x1B 33 | #define EIR 0x1C 34 | #define ESTAT 0x1D 35 | #define ECON2 0x1E 36 | #define ECON1 0x1F 37 | // Bank 0 registers 38 | #define ERDPT (0x00|0x00) 39 | #define EWRPT (0x02|0x00) 40 | #define ETXST (0x04|0x00) 41 | #define ETXND (0x06|0x00) 42 | #define ERXST (0x08|0x00) 43 | #define ERXND (0x0A|0x00) 44 | #define ERXRDPT (0x0C|0x00) 45 | // #define ERXWRPT (0x0E|0x00) 46 | #define EDMAST (0x10|0x00) 47 | #define EDMAND (0x12|0x00) 48 | // #define EDMADST (0x14|0x00) 49 | #define EDMACS (0x16|0x00) 50 | // Bank 1 registers 51 | #define EHT0 (0x00|0x20) 52 | #define EHT1 (0x01|0x20) 53 | #define EHT2 (0x02|0x20) 54 | #define EHT3 (0x03|0x20) 55 | #define EHT4 (0x04|0x20) 56 | #define EHT5 (0x05|0x20) 57 | #define EHT6 (0x06|0x20) 58 | #define EHT7 (0x07|0x20) 59 | #define EPMM0 (0x08|0x20) 60 | #define EPMM1 (0x09|0x20) 61 | #define EPMM2 (0x0A|0x20) 62 | #define EPMM3 (0x0B|0x20) 63 | #define EPMM4 (0x0C|0x20) 64 | #define EPMM5 (0x0D|0x20) 65 | #define EPMM6 (0x0E|0x20) 66 | #define EPMM7 (0x0F|0x20) 67 | #define EPMCS (0x10|0x20) 68 | // #define EPMO (0x14|0x20) 69 | #define EWOLIE (0x16|0x20) 70 | #define EWOLIR (0x17|0x20) 71 | #define ERXFCON (0x18|0x20) 72 | #define EPKTCNT (0x19|0x20) 73 | // Bank 2 registers 74 | #define MACON1 (0x00|0x40|0x80) 75 | #define MACON2 (0x01|0x40|0x80) 76 | #define MACON3 (0x02|0x40|0x80) 77 | #define MACON4 (0x03|0x40|0x80) 78 | #define MABBIPG (0x04|0x40|0x80) 79 | #define MAIPG (0x06|0x40|0x80) 80 | #define MACLCON1 (0x08|0x40|0x80) 81 | #define MACLCON2 (0x09|0x40|0x80) 82 | #define MAMXFL (0x0A|0x40|0x80) 83 | #define MAPHSUP (0x0D|0x40|0x80) 84 | #define MICON (0x11|0x40|0x80) 85 | #define MICMD (0x12|0x40|0x80) 86 | #define MIREGADR (0x14|0x40|0x80) 87 | #define MIWR (0x16|0x40|0x80) 88 | #define MIRD (0x18|0x40|0x80) 89 | // Bank 3 registers 90 | #define MAADR1 (0x00|0x60|0x80) 91 | #define MAADR0 (0x01|0x60|0x80) 92 | #define MAADR3 (0x02|0x60|0x80) 93 | #define MAADR2 (0x03|0x60|0x80) 94 | #define MAADR5 (0x04|0x60|0x80) 95 | #define MAADR4 (0x05|0x60|0x80) 96 | #define EBSTSD (0x06|0x60) 97 | #define EBSTCON (0x07|0x60) 98 | #define EBSTCS (0x08|0x60) 99 | #define MISTAT (0x0A|0x60|0x80) 100 | #define EREVID (0x12|0x60) 101 | #define ECOCON (0x15|0x60) 102 | #define EFLOCON (0x17|0x60) 103 | #define EPAUS (0x18|0x60) 104 | 105 | // ENC28J60 ERXFCON Register Bit Definitions 106 | #define ERXFCON_UCEN 0x80 107 | #define ERXFCON_ANDOR 0x40 108 | #define ERXFCON_CRCEN 0x20 109 | #define ERXFCON_PMEN 0x10 110 | #define ERXFCON_MPEN 0x08 111 | #define ERXFCON_HTEN 0x04 112 | #define ERXFCON_MCEN 0x02 113 | #define ERXFCON_BCEN 0x01 114 | // ENC28J60 EIE Register Bit Definitions 115 | #define EIE_INTIE 0x80 116 | #define EIE_PKTIE 0x40 117 | #define EIE_DMAIE 0x20 118 | #define EIE_LINKIE 0x10 119 | #define EIE_TXIE 0x08 120 | #define EIE_WOLIE 0x04 121 | #define EIE_TXERIE 0x02 122 | #define EIE_RXERIE 0x01 123 | // ENC28J60 EIR Register Bit Definitions 124 | #define EIR_PKTIF 0x40 125 | #define EIR_DMAIF 0x20 126 | #define EIR_LINKIF 0x10 127 | #define EIR_TXIF 0x08 128 | #define EIR_WOLIF 0x04 129 | #define EIR_TXERIF 0x02 130 | #define EIR_RXERIF 0x01 131 | // ENC28J60 ESTAT Register Bit Definitions 132 | #define ESTAT_INT 0x80 133 | #define ESTAT_LATECOL 0x10 134 | #define ESTAT_RXBUSY 0x04 135 | #define ESTAT_TXABRT 0x02 136 | #define ESTAT_CLKRDY 0x01 137 | // ENC28J60 ECON2 Register Bit Definitions 138 | #define ECON2_AUTOINC 0x80 139 | #define ECON2_PKTDEC 0x40 140 | #define ECON2_PWRSV 0x20 141 | #define ECON2_VRPS 0x08 142 | // ENC28J60 ECON1 Register Bit Definitions 143 | #define ECON1_TXRST 0x80 144 | #define ECON1_RXRST 0x40 145 | #define ECON1_DMAST 0x20 146 | #define ECON1_CSUMEN 0x10 147 | #define ECON1_TXRTS 0x08 148 | #define ECON1_RXEN 0x04 149 | #define ECON1_BSEL1 0x02 150 | #define ECON1_BSEL0 0x01 151 | // ENC28J60 MACON1 Register Bit Definitions 152 | #define MACON1_LOOPBK 0x10 153 | #define MACON1_TXPAUS 0x08 154 | #define MACON1_RXPAUS 0x04 155 | #define MACON1_PASSALL 0x02 156 | #define MACON1_MARXEN 0x01 157 | // ENC28J60 MACON2 Register Bit Definitions 158 | #define MACON2_MARST 0x80 159 | #define MACON2_RNDRST 0x40 160 | #define MACON2_MARXRST 0x08 161 | #define MACON2_RFUNRST 0x04 162 | #define MACON2_MATXRST 0x02 163 | #define MACON2_TFUNRST 0x01 164 | // ENC28J60 MACON3 Register Bit Definitions 165 | #define MACON3_PADCFG2 0x80 166 | #define MACON3_PADCFG1 0x40 167 | #define MACON3_PADCFG0 0x20 168 | #define MACON3_TXCRCEN 0x10 169 | #define MACON3_PHDRLEN 0x08 170 | #define MACON3_HFRMLEN 0x04 171 | #define MACON3_FRMLNEN 0x02 172 | #define MACON3_FULDPX 0x01 173 | // ENC28J60 MICMD Register Bit Definitions 174 | #define MICMD_MIISCAN 0x02 175 | #define MICMD_MIIRD 0x01 176 | // ENC28J60 MISTAT Register Bit Definitions 177 | #define MISTAT_NVALID 0x04 178 | #define MISTAT_SCAN 0x02 179 | #define MISTAT_BUSY 0x01 180 | 181 | // ENC28J60 EBSTCON Register Bit Definitions 182 | #define EBSTCON_PSV2 0x80 183 | #define EBSTCON_PSV1 0x40 184 | #define EBSTCON_PSV0 0x20 185 | #define EBSTCON_PSEL 0x10 186 | #define EBSTCON_TMSEL1 0x08 187 | #define EBSTCON_TMSEL0 0x04 188 | #define EBSTCON_TME 0x02 189 | #define EBSTCON_BISTST 0x01 190 | 191 | // PHY registers 192 | #define PHCON1 0x00 193 | #define PHSTAT1 0x01 194 | #define PHHID1 0x02 195 | #define PHHID2 0x03 196 | #define PHCON2 0x10 197 | #define PHSTAT2 0x11 198 | #define PHIE 0x12 199 | #define PHIR 0x13 200 | #define PHLCON 0x14 201 | 202 | // ENC28J60 PHY PHCON1 Register Bit Definitions 203 | #define PHCON1_PRST 0x8000 204 | #define PHCON1_PLOOPBK 0x4000 205 | #define PHCON1_PPWRSV 0x0800 206 | #define PHCON1_PDPXMD 0x0100 207 | // ENC28J60 PHY PHSTAT1 Register Bit Definitions 208 | #define PHSTAT1_PFDPX 0x1000 209 | #define PHSTAT1_PHDPX 0x0800 210 | #define PHSTAT1_LLSTAT 0x0004 211 | #define PHSTAT1_JBSTAT 0x0002 212 | // ENC28J60 PHY PHCON2 Register Bit Definitions 213 | #define PHCON2_FRCLINK 0x4000 214 | #define PHCON2_TXDIS 0x2000 215 | #define PHCON2_JABBER 0x0400 216 | #define PHCON2_HDLDIS 0x0100 217 | 218 | // ENC28J60 Packet Control Byte Bit Definitions 219 | #define PKTCTRL_PHUGEEN 0x08 220 | #define PKTCTRL_PPADEN 0x04 221 | #define PKTCTRL_PCRCEN 0x02 222 | #define PKTCTRL_POVERRIDE 0x01 223 | 224 | // SPI operation codes 225 | #define ENC28J60_READ_CTRL_REG 0x00 226 | #define ENC28J60_READ_BUF_MEM 0x3A 227 | #define ENC28J60_WRITE_CTRL_REG 0x40 228 | #define ENC28J60_WRITE_BUF_MEM 0x7A 229 | #define ENC28J60_BIT_FIELD_SET 0x80 230 | #define ENC28J60_BIT_FIELD_CLR 0xA0 231 | #define ENC28J60_SOFT_RESET 0xFF 232 | 233 | // max frame length which the controller will accept: 234 | // (note: maximum ethernet frame length would be 1518) 235 | #define MAX_FRAMELEN 1500 236 | 237 | #define FULL_SPEED 1 // switch to full-speed SPI for bulk transfers 238 | 239 | static uint8_t Enc28j60Bank; 240 | static uint8_t selectPin; 241 | 242 | void ENC28J60::initSPI () { 243 | ////////////////////// 244 | //1. Setup Pin // 245 | ////////////////////// 246 | PORTD.DIRSET = SSPIN; //D4 = CS 247 | PORTD.OUTSET = SSPIN; 248 | 249 | //Other SPI PINs 250 | //PORTD.PIN4CTRL = PORT_OPC_PULLUP_gc; 251 | PORTD.DIRSET = /*PIN4_bm |*/ PIN5_bm | PIN7_bm; //SCK and MOSI = OUT 252 | PORTD.OUTSET = PIN5_bm | PIN7_bm; 253 | 254 | 255 | //FROM http://lb9mg.no/2017/03/31/xmega-high-performance-spi-with-dma/ 256 | PORTD.DIRCLR = PIN6_bm; /*MISO*/ 257 | PORTD.OUTSET = SSPIN; /*CS high*/ 258 | //PORTD.PIN5CTRL = PORT_INVEN_bm; /*CLOCK HAS TO BE INVERTED!!*/ 259 | 260 | ////////////////////// 261 | //2. Setup SPI on // 262 | ////////////////////// 263 | SPID.CTRL = (SPID.CTRL & ~SPI_MODE_gm) | 264 | ((SPI_MODE0_bm) & SPI_MODE_gm); 265 | 266 | SPID.CTRL = SPI_MASTER_bm ; //Prescaler default=00 -> Clk/4 267 | SPID.INTCTRL = 0x00; //Interruptlevel 268 | 269 | 270 | SPID.CTRL |= SPI_ENABLE_bm; 271 | 272 | PORTD.DIRSET = 1 << SSPIN; //D4 = CS 273 | PORTD.OUTSET = 1 << SSPIN; 274 | 275 | 276 | //OLD 277 | //SPCR = bit(SPE) | bit(MSTR); // 8 MHz @ 16 278 | //bitSet(SPSR, SPI2X); //TODO: Check 2X Mode on XMega 32MHz 279 | } 280 | 281 | static void enableChip (void) { 282 | cli(); 283 | PORTD.OUTCLR = 1 << SSPIN; //TODO: Check 284 | } 285 | 286 | static void disableChip (void) { 287 | PORTD.OUTSET = 1 << SSPIN; 288 | sei(); 289 | } 290 | 291 | static void xferSPI (uint8_t data) { 292 | SPID.DATA = data; 293 | 294 | while (!(SPID.STATUS & SPI_IF_bm)); 295 | } 296 | 297 | static uint8_t readOp (uint8_t op, uint8_t address) { 298 | enableChip(); 299 | xferSPI(op | (address & ADDR_MASK)); 300 | xferSPI(0x00); 301 | if (address & 0x80) 302 | xferSPI(0x00); 303 | uint8_t result = SPID.DATA; 304 | disableChip(); 305 | return result; 306 | } 307 | 308 | static void writeOp (uint8_t op, uint8_t address, uint8_t data) { 309 | enableChip(); 310 | xferSPI(op | (address & ADDR_MASK)); 311 | xferSPI(data); 312 | disableChip(); 313 | } 314 | 315 | static void readBuf(uint16_t len, uint8_t* data) { 316 | uint8_t nextByte; 317 | 318 | enableChip(); 319 | if (len != 0) { 320 | xferSPI(ENC28J60_READ_BUF_MEM); 321 | 322 | SPID.DATA = 0x00; 323 | while (--len) { 324 | while (!(SPID.STATUS & SPI_IF_bm)); 325 | 326 | nextByte = SPID.DATA; 327 | SPID.DATA = 0x00; 328 | *data++ = nextByte; 329 | } 330 | while (!(SPID.STATUS & SPI_IF_bm)); 331 | *data++ = SPID.DATA; 332 | } 333 | disableChip(); 334 | } 335 | 336 | static void writeBuf(uint16_t len, const uint8_t* data) { 337 | enableChip(); 338 | if (len != 0) { 339 | xferSPI(ENC28J60_WRITE_BUF_MEM); 340 | 341 | SPID.DATA = *data++; 342 | while (--len) { 343 | uint8_t nextByte = *data++; 344 | 345 | while (!(SPID.STATUS & SPI_IF_bm)); 346 | SPID.DATA = nextByte; 347 | }; 348 | while (!(SPID.STATUS & SPI_IF_bm)); 349 | } 350 | disableChip(); 351 | } 352 | 353 | static void SetBank (uint8_t address) { 354 | if ((address & BANK_MASK) != Enc28j60Bank) { 355 | writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_BSEL1|ECON1_BSEL0); 356 | Enc28j60Bank = address & BANK_MASK; 357 | writeOp(ENC28J60_BIT_FIELD_SET, ECON1, Enc28j60Bank>>5); 358 | } 359 | } 360 | 361 | static uint8_t readRegByte (uint8_t address) { 362 | SetBank(address); 363 | return readOp(ENC28J60_READ_CTRL_REG, address); 364 | } 365 | 366 | static uint16_t readReg(uint8_t address) { 367 | return readRegByte(address) + (readRegByte(address+1) << 8); 368 | } 369 | 370 | static void writeRegByte (uint8_t address, uint8_t data) { 371 | SetBank(address); 372 | writeOp(ENC28J60_WRITE_CTRL_REG, address, data); 373 | } 374 | 375 | static void writeReg(uint8_t address, uint16_t data) { 376 | writeRegByte(address, data); 377 | writeRegByte(address + 1, data >> 8); 378 | } 379 | 380 | static uint16_t readPhyByte (uint8_t address) { 381 | writeRegByte(MIREGADR, address); 382 | writeRegByte(MICMD, MICMD_MIIRD); 383 | while (readRegByte(MISTAT) & MISTAT_BUSY) 384 | ; 385 | writeRegByte(MICMD, 0x00); 386 | return readRegByte(MIRD+1); 387 | } 388 | 389 | static void writePhy (uint8_t address, uint16_t data) { 390 | writeRegByte(MIREGADR, address); 391 | writeReg(MIWR, data); 392 | 393 | while (readRegByte(MISTAT) & MISTAT_BUSY); 394 | } 395 | 396 | uint8_t ENC28J60::initialize (uint16_t size, const uint8_t* macaddr) { 397 | bufferSize = size; 398 | initSPI(); //TODO: Fix it 399 | if ((SPID.CTRL | SPI_ENABLE_bm) == 0) { 400 | initSPI(); 401 | } 402 | 403 | //Setup Pin 404 | disableChip(); 405 | 406 | writeOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET); 407 | _delay_ms(2); // errata B7/2 408 | while (!readOp(ENC28J60_READ_CTRL_REG, ESTAT) & ESTAT_CLKRDY); 409 | 410 | 411 | //TODO: Check full/half duplex 412 | 413 | writeReg(ERXST, RXSTART_INIT); 414 | writeReg(ERXRDPT, RXSTART_INIT); 415 | writeReg(ERXND, RXSTOP_INIT); 416 | writeReg(ETXST, TXSTART_INIT); 417 | writeReg(ETXND, TXSTOP_INIT); 418 | 419 | writeRegByte(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN|ERXFCON_BCEN); 420 | writeReg(EPMM0, 0x303f); 421 | writeReg(EPMCS, 0xf7f9); 422 | writeRegByte(MACON1, MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS); 423 | writeRegByte(MACON2, 0x00); 424 | writeOp(ENC28J60_BIT_FIELD_SET, MACON3, 425 | MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN); 426 | writeReg(MAIPG, 0x0C12); 427 | writeRegByte(MABBIPG, 0x12); 428 | writeReg(MAMXFL, MAX_FRAMELEN); 429 | writeRegByte(MAADR5, macaddr[0]); 430 | writeRegByte(MAADR4, macaddr[1]); 431 | writeRegByte(MAADR3, macaddr[2]); 432 | writeRegByte(MAADR2, macaddr[3]); 433 | writeRegByte(MAADR1, macaddr[4]); 434 | writeRegByte(MAADR0, macaddr[5]); 435 | writePhy(PHCON2, PHCON2_HDLDIS); 436 | writePhy(PHCON1, PHCON1_PDPXMD); //Full Duplex mode 437 | SetBank(ECON1); 438 | writeOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE); 439 | writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN); 440 | 441 | uint8_t rev = readRegByte(EREVID); 442 | // microchip forgot to step the number on the silcon when they 443 | // released the revision B7. 6 is now rev B7. We still have 444 | // to see what they do when they release B8. At the moment 445 | // there is no B8 out yet 446 | if (rev > 5) ++rev; 447 | return rev; 448 | } 449 | 450 | bool ENC28J60::isLinkUp() { 451 | return (readPhyByte(PHSTAT2) >> 2) & 1; 452 | } 453 | 454 | bool ENC28J60::isReceiveError () { 455 | bool result = readRegByte(EIR) & EIR_RXERIF; 456 | writeOp(ENC28J60_BIT_FIELD_CLR, EIR, EIR_RXERIF); //Clear flag 457 | return result; 458 | } 459 | 460 | /* 461 | struct __attribute__((__packed__)) transmit_status_vector { 462 | uint16_t transmitByteCount; 463 | uint8_t transmitCollisionCount : 4; 464 | uint8_t transmitCrcError : 1; 465 | uint8_t transmitLengthCheckError : 1; 466 | uint8_t transmitLengthOutRangeError : 1; 467 | uint8_t transmitDone : 1; 468 | uint8_t transmitMulticast : 1; 469 | uint8_t transmitBroadcast : 1; 470 | uint8_t transmitPacketDefer : 1; 471 | uint8_t transmitExcessiveDefer : 1; 472 | uint8_t transmitExcessiveCollision : 1; 473 | uint8_t transmitLateCollision : 1; 474 | uint8_t transmitGiant : 1; 475 | uint8_t transmitUnderrun : 1; 476 | uint16_t totalTransmitted; 477 | uint8_t transmitControlFrame : 1; 478 | uint8_t transmitPauseControlFrame : 1; 479 | uint8_t backpressureApplied : 1; 480 | uint8_t transmitVLAN : 1; 481 | uint8_t zero : 4; 482 | }; 483 | */ 484 | 485 | struct transmit_status_vector { 486 | uint8_t bytes[7]; 487 | }; 488 | 489 | #if ETHERCARD_SEND_PIPELINING 490 | #define BREAKORCONTINUE retry=0; continue; 491 | #else 492 | #define BREAKORCONTINUE break; 493 | #endif 494 | 495 | void ENC28J60::packetSend(uint16_t len) { 496 | uint8_t retry = 0; 497 | 498 | #if ETHERCARD_SEND_PIPELINING 499 | goto resume_last_transmission; 500 | #endif 501 | while (1) { 502 | // latest errata sheet: DS80349C 503 | // always reset transmit logic (Errata Issue 12) 504 | // the Microchip TCP/IP stack implementation used to first check 505 | // whether TXERIF is set and only then reset the transmit logic 506 | // but this has been changed in later versions; possibly they 507 | // have a reason for this; they don't mention this in the errata 508 | // sheet 509 | writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRST); 510 | writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRST); 511 | writeOp(ENC28J60_BIT_FIELD_CLR, EIR, EIR_TXERIF|EIR_TXIF); 512 | 513 | // prepare new transmission 514 | if (retry == 0) { 515 | writeReg(EWRPT, TXSTART_INIT); 516 | writeReg(ETXND, TXSTART_INIT+len); 517 | writeOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00); 518 | writeBuf(len, buffer); 519 | } 520 | 521 | // initiate transmission 522 | writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS); 523 | #if ETHERCARD_SEND_PIPELINING 524 | if (retry == 0) return; 525 | #endif 526 | 527 | resume_last_transmission: 528 | 529 | // wait until transmission has finished; referrring to the data sheet and 530 | // to the errata (Errata Issue 13; Example 1) you only need to wait until either 531 | // TXIF or TXERIF gets set; however this leads to hangs; apparently Microchip 532 | // realized this and in later implementations of their tcp/ip stack they introduced 533 | // a counter to avoid hangs; of course they didn't update the errata sheet 534 | uint16_t count = 0; 535 | while ((readRegByte(EIR) & (EIR_TXIF | EIR_TXERIF)) == 0 && ++count < 1000U) 536 | ; 537 | 538 | if (!(readRegByte(EIR) & EIR_TXERIF) && count < 1000U) { 539 | // no error; start new transmission 540 | BREAKORCONTINUE 541 | } 542 | 543 | // cancel previous transmission if stuck 544 | writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS); 545 | 546 | #if ETHERCARD_RETRY_LATECOLLISIONS == 0 547 | BREAKORCONTINUE 548 | #endif 549 | 550 | // Check whether the chip thinks that a late collision ocurred; the chip 551 | // may be wrong (Errata Issue 13); therefore we retry. We could check 552 | // LATECOL in the ESTAT register in order to find out whether the chip 553 | // thinks a late collision ocurred but (Errata Issue 15) tells us that 554 | // this is not working. Therefore we check TSV 555 | transmit_status_vector tsv; 556 | uint16_t etxnd = readReg(ETXND); 557 | writeReg(ERDPT, etxnd+1); 558 | readBuf(sizeof(transmit_status_vector), (uint8_t*) &tsv); 559 | // LATECOL is bit number 29 in TSV (starting from 0) 560 | 561 | if (!((readRegByte(EIR) & EIR_TXERIF) && (tsv.bytes[3] & 1<<5) /*tsv.transmitLateCollision*/) || retry > 16U) { 562 | // there was some error but no LATECOL so we do not repeat 563 | BREAKORCONTINUE 564 | } 565 | 566 | retry++; 567 | } 568 | } 569 | 570 | 571 | uint16_t ENC28J60::packetReceive() { 572 | static uint16_t gNextPacketPtr = RXSTART_INIT; 573 | static bool unreleasedPacket = false; 574 | uint16_t len = 0; 575 | 576 | if (unreleasedPacket) { 577 | if (gNextPacketPtr == 0) 578 | writeReg(ERXRDPT, RXSTOP_INIT); 579 | else 580 | writeReg(ERXRDPT, gNextPacketPtr - 1); 581 | unreleasedPacket = false; 582 | } 583 | 584 | if (readRegByte(EPKTCNT) > 0) { 585 | writeReg(ERDPT, gNextPacketPtr); 586 | 587 | struct { 588 | uint16_t nextPacket; 589 | uint16_t byteCount; 590 | uint16_t status; 591 | } header; 592 | 593 | readBuf(sizeof header, (uint8_t*) &header); 594 | 595 | gNextPacketPtr = header.nextPacket; 596 | len = header.byteCount - 4; //remove the CRC count 597 | if (len>bufferSize-1) 598 | len=bufferSize-1; 599 | if ((header.status & 0x80)==0) 600 | len = 0; 601 | else 602 | readBuf(len, buffer); 603 | buffer[len] = 0; 604 | unreleasedPacket = true; 605 | 606 | writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC); 607 | } 608 | return len; 609 | } 610 | 611 | void ENC28J60::copyout (uint8_t page, const uint8_t* data) { 612 | uint16_t destPos = SCRATCH_START + (page << SCRATCH_PAGE_SHIFT); 613 | if (destPos < SCRATCH_START || destPos > SCRATCH_LIMIT - SCRATCH_PAGE_SIZE) 614 | return; 615 | writeReg(EWRPT, destPos); 616 | writeBuf(SCRATCH_PAGE_SIZE, data); 617 | } 618 | 619 | void ENC28J60::copyin (uint8_t page, uint8_t* data) { 620 | uint16_t destPos = SCRATCH_START + (page << SCRATCH_PAGE_SHIFT); 621 | if (destPos < SCRATCH_START || destPos > SCRATCH_LIMIT - SCRATCH_PAGE_SIZE) 622 | return; 623 | writeReg(ERDPT, destPos); 624 | readBuf(SCRATCH_PAGE_SIZE, data); 625 | } 626 | 627 | uint8_t ENC28J60::peekin (uint8_t page, uint8_t off) { 628 | uint8_t result = 0; 629 | uint16_t destPos = SCRATCH_START + (page << SCRATCH_PAGE_SHIFT) + off; 630 | if (SCRATCH_START <= destPos && destPos < SCRATCH_LIMIT) { 631 | writeReg(ERDPT, destPos); 632 | readBuf(1, &result); 633 | } 634 | return result; 635 | } 636 | 637 | // Contributed by Alex M. Based on code from: http://blog.derouineau.fr 638 | // /2011/07/putting-enc28j60-ethernet-controler-in-sleep-mode/ 639 | void ENC28J60::powerDown() { 640 | writeOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_RXEN); 641 | while(readRegByte(ESTAT) & ESTAT_RXBUSY); 642 | while(readRegByte(ECON1) & ECON1_TXRTS); 643 | writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_VRPS); 644 | writeOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PWRSV); 645 | } 646 | 647 | void ENC28J60::powerUp() { 648 | writeOp(ENC28J60_BIT_FIELD_CLR, ECON2, ECON2_PWRSV); 649 | while(!readRegByte(ESTAT) & ESTAT_CLKRDY); 650 | writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN); 651 | } 652 | 653 | void ENC28J60::enableBroadcast (bool temporary) { 654 | writeRegByte(ERXFCON, readRegByte(ERXFCON) | ERXFCON_BCEN); 655 | if(!temporary) 656 | broadcast_enabled = true; 657 | } 658 | 659 | void ENC28J60::disableBroadcast (bool temporary) { 660 | if(!temporary) 661 | broadcast_enabled = false; 662 | if(!broadcast_enabled) 663 | writeRegByte(ERXFCON, readRegByte(ERXFCON) & ~ERXFCON_BCEN); 664 | } 665 | 666 | void ENC28J60::enableMulticast () { 667 | writeRegByte(ERXFCON, readRegByte(ERXFCON) | ERXFCON_MCEN); 668 | } 669 | 670 | void ENC28J60::disableMulticast () { 671 | writeRegByte(ERXFCON, readRegByte(ERXFCON) & ~ERXFCON_MCEN); 672 | } 673 | 674 | void ENC28J60::enablePromiscuous (bool temporary) { 675 | writeRegByte(ERXFCON, readRegByte(ERXFCON) & ERXFCON_CRCEN); 676 | if(!temporary) 677 | promiscuous_enabled = true; 678 | } 679 | 680 | void ENC28J60::disablePromiscuous (bool temporary) { 681 | if(!temporary) 682 | promiscuous_enabled = false; 683 | if(!promiscuous_enabled) { 684 | writeRegByte(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN|ERXFCON_BCEN); 685 | } 686 | } 687 | 688 | uint8_t ENC28J60::doBIST ( uint8_t csPin) { 689 | #define RANDOM_FILL 0b0000 690 | #define ADDRESS_FILL 0b0100 691 | #define PATTERN_SHIFT 0b1000 692 | #define RANDOM_RACE 0b1100 693 | 694 | // init 695 | if ((SPID.CTRL | SPI_ENABLE_bm) == 0) { 696 | initSPI(); 697 | } 698 | 699 | PORTD.DIRSET = SSPIN; //D4 = CS 700 | PORTD.OUTSET = SSPIN; 701 | disableChip(); 702 | 703 | writeOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET); 704 | _delay_ms(2); // errata B7/2 705 | while (!readOp(ENC28J60_READ_CTRL_REG, ESTAT) & ESTAT_CLKRDY) ; 706 | 707 | 708 | // now we can start the memory test 709 | 710 | uint16_t macResult; 711 | uint16_t bitsResult; 712 | 713 | // clear some of the registers registers 714 | writeRegByte(ECON1, 0); 715 | writeReg(EDMAST, 0); 716 | 717 | // Set up necessary pointers for the DMA to calculate over the entire memory 718 | writeReg(EDMAND, 0x1FFFu); 719 | writeReg(ERXND, 0x1FFFu); 720 | 721 | // Enable Test Mode and do an Address Fill 722 | SetBank(EBSTCON); 723 | writeRegByte(EBSTCON, EBSTCON_TME | EBSTCON_BISTST | ADDRESS_FILL); 724 | 725 | // wait for BISTST to be reset, only after that are we actually ready to 726 | // start the test 727 | // this was undocumented :( 728 | while (readOp(ENC28J60_READ_CTRL_REG, EBSTCON) & EBSTCON_BISTST); 729 | writeOp(ENC28J60_BIT_FIELD_CLR, EBSTCON, EBSTCON_TME); 730 | 731 | 732 | // now start the actual reading an calculating the checksum until the end is 733 | // reached 734 | writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_DMAST | ECON1_CSUMEN); 735 | SetBank(EDMACS); 736 | while(readOp(ENC28J60_READ_CTRL_REG, ECON1) & ECON1_DMAST); 737 | macResult = readReg(EDMACS); 738 | bitsResult = readReg(EBSTCS); 739 | // Compare the results 740 | // 0xF807 should always be generated in Address fill mode 741 | if ((macResult != bitsResult) || (bitsResult != 0xF807)) { 742 | return 0; 743 | } 744 | // reset test flag 745 | writeOp(ENC28J60_BIT_FIELD_CLR, EBSTCON, EBSTCON_TME); 746 | 747 | 748 | // Now start the BIST with random data test, and also keep on swapping the 749 | // DMA/BIST memory ports. 750 | writeRegByte(EBSTSD, 0b10101010 | millis()); 751 | writeRegByte(EBSTCON, EBSTCON_TME | EBSTCON_PSEL | EBSTCON_BISTST | RANDOM_FILL); 752 | 753 | 754 | // wait for BISTST to be reset, only after that are we actually ready to 755 | // start the test 756 | // this was undocumented :( 757 | while (readOp(ENC28J60_READ_CTRL_REG, EBSTCON) & EBSTCON_BISTST); 758 | writeOp(ENC28J60_BIT_FIELD_CLR, EBSTCON, EBSTCON_TME); 759 | 760 | 761 | // now start the actual reading an calculating the checksum until the end is 762 | // reached 763 | writeOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_DMAST | ECON1_CSUMEN); 764 | SetBank(EDMACS); 765 | while(readOp(ENC28J60_READ_CTRL_REG, ECON1) & ECON1_DMAST); 766 | 767 | macResult = readReg(EDMACS); 768 | bitsResult = readReg(EBSTCS); 769 | // The checksum should be equal 770 | return macResult == bitsResult; 771 | } 772 | 773 | 774 | void ENC28J60::memcpy_to_enc(uint16_t dest, void* source, int16_t num) { 775 | writeReg(EWRPT, dest); 776 | writeBuf(num, (uint8_t*) source); 777 | } 778 | 779 | void ENC28J60::memcpy_from_enc(void* dest, uint16_t source, int16_t num) { 780 | writeReg(ERDPT, source); 781 | readBuf(num, (uint8_t*) dest); 782 | } 783 | 784 | static uint16_t endRam = ENC_HEAP_END; 785 | uint16_t ENC28J60::enc_malloc(uint16_t size) { 786 | if (endRam-size >= ENC_HEAP_START) { 787 | endRam -= size; 788 | return endRam; 789 | } 790 | return 0; 791 | } 792 | 793 | uint16_t ENC28J60::enc_freemem() { 794 | return endRam-ENC_HEAP_START; 795 | } 796 | 797 | uint16_t ENC28J60::readPacketSlice(char* dest, int16_t maxlength, int16_t packetOffset) { 798 | uint16_t erxrdpt = readReg(ERXRDPT); 799 | int16_t packetLength; 800 | 801 | memcpy_from_enc((char*) &packetLength, (erxrdpt+3)%(RXSTOP_INIT+1), 2); 802 | packetLength -= 4; // remove crc 803 | 804 | int16_t bytesToCopy = packetLength - packetOffset; 805 | if (bytesToCopy > maxlength) bytesToCopy = maxlength; 806 | if (bytesToCopy <= 0) bytesToCopy = 0; 807 | 808 | int16_t startofSlice = (erxrdpt+7+packetOffset)%(RXSTOP_INIT+1); 809 | memcpy_from_enc(dest, startofSlice, bytesToCopy); 810 | dest[bytesToCopy] = 0; 811 | 812 | return bytesToCopy; 813 | } 814 | -------------------------------------------------------------------------------- /enc28j60.h: -------------------------------------------------------------------------------- 1 | // Microchip ENC28J60 Ethernet Interface Driver 2 | // Author: Pascal Stang 3 | // Modified by: Guido Socher 4 | // Copyright: GPL V2 5 | // 6 | // This driver provides initialization and transmit/receive 7 | // functions for the Microchip ENC28J60 10Mb Ethernet Controller and PHY. 8 | // This chip is novel in that it is a full MAC+PHY interface all in a 28-pin 9 | // chip, using an SPI interface to the host processor. 10 | // 11 | // 2010-05-20 12 | /** @file */ 13 | 14 | #ifndef ENC28J60_H 15 | #define ENC28J60_H 16 | 17 | #include 18 | 19 | #define SSPIN 4 //Port D Pin 4 20 | 21 | 22 | // buffer boundaries applied to internal 8K ram 23 | // the entire available packet buffer space is allocated 24 | 25 | #define RXSTART_INIT 0x0000 // start of RX buffer, (must be zero, Rev. B4 Errata point 5) 26 | #define RXSTOP_INIT 0x15FF // end of RX buffer, room for 2 packets 27 | 28 | #define TXSTART_INIT 0x1600 // start of TX buffer, room for 1 packet 29 | #define TXSTOP_INIT 0x1BDC // end of TX buffer 30 | 31 | #define SCRATCH_START 0x1BDD // start of scratch area 32 | #define SCRATCH_LIMIT 0x2000 // past end of area, i.e. 3 Kb 33 | #define SCRATCH_PAGE_SHIFT 6 // addressing is in pages of 64 bytes 34 | #define SCRATCH_PAGE_SIZE (1 << SCRATCH_PAGE_SHIFT) 35 | #define SCRATCH_PAGE_NUM ((SCRATCH_LIMIT-SCRATCH_START) >> SCRATCH_PAGE_SHIFT) 36 | #define SCRATCH_MAP_SIZE (((SCRATCH_PAGE_NUM % 8) == 0) ? (SCRATCH_PAGE_NUM / 8) : (SCRATCH_PAGE_NUM/8+1)) 37 | 38 | // area in the enc memory that can be used via enc_malloc; by default 0 bytes; decrease SCRATCH_LIMIT in order 39 | // to use this functionality 40 | #define ENC_HEAP_START SCRATCH_LIMIT 41 | #define ENC_HEAP_END 0x2000 42 | 43 | /** This class provide low-level interfacing with the ENC28J60 network interface. This is used by the EtherCard class and not intended for use by (normal) end users. */ 44 | class ENC28J60 { 45 | public: 46 | static uint8_t buffer[]; //!< Data buffer (shared by recieve and transmit) 47 | static uint16_t bufferSize; //!< Size of data buffer 48 | static bool broadcast_enabled; //!< True if broadcasts enabled (used to allow temporary disable of broadcast for DHCP or other internal functions) 49 | static bool promiscuous_enabled; //!< True if promiscuous mode enabled (used to allow temporary disable of promiscuous mode) 50 | 51 | static uint8_t* tcpOffset () { return buffer + 0x36; } //!< Pointer to the start of TCP payload 52 | 53 | /** @brief Initialise SPI interface 54 | * @note Configures Arduino pins as input / output, etc. 55 | */ 56 | static void initSPI (); 57 | 58 | /** @brief Initialise network interface 59 | * @param size Size of data buffer 60 | * @param macaddr Pointer to 6 byte hardware (MAC) address 61 | * @return uint8_t ENC28J60 firmware version or zero on failure. 62 | */ 63 | static uint8_t initialize (const uint16_t size, const uint8_t* macaddr); 64 | 65 | /** @brief Check if network link is connected 66 | * @return bool True if link is up 67 | */ 68 | static bool isLinkUp (); 69 | 70 | /** @brief Check if an receive error occured and resets the flag 71 | * @return bool True if an receive error occured(Datasheet 12.1.2) 72 | */ 73 | static bool isReceiveError (); 74 | 75 | /** @brief Sends data to network interface 76 | * @param len Size of data to send 77 | * @note Data buffer is shared by recieve and transmit functions 78 | */ 79 | static void packetSend (uint16_t len); 80 | 81 | /** @brief Copy recieved packets to data buffer 82 | * @return uint16_t Size of recieved data 83 | * @note Data buffer is shared by recieve and transmit functions 84 | */ 85 | static uint16_t packetReceive (); 86 | 87 | /** @brief Copy data from ENC28J60 memory 88 | * @param page Data page of memory 89 | * @param data Pointer to buffer to copy data to 90 | */ 91 | static void copyout (uint8_t page, const uint8_t* data); 92 | 93 | /** @brief Copy data to ENC28J60 memory 94 | * @param page Data page of memory 95 | * @param data Pointer to buffer to copy data from 96 | */ 97 | static void copyin (uint8_t page, uint8_t* data); 98 | 99 | /** @brief Get single byte of data from ENC28J60 memory 100 | * @param page Data page of memory 101 | * @param off Offset of data within page 102 | * @return Data value 103 | */ 104 | static uint8_t peekin (uint8_t page, uint8_t off); 105 | 106 | /** @brief Put ENC28J60 in sleep mode 107 | */ 108 | static void powerDown(); // contrib by Alex M. 109 | 110 | /** @brief Wake ENC28J60 from sleep mode 111 | */ 112 | static void powerUp(); // contrib by Alex M. 113 | 114 | /** @brief Enable reception of broadcast messages 115 | * @param temporary Set true to temporarily enable broadcast 116 | * @note This will increase load on recieved data handling 117 | */ 118 | static void enableBroadcast(bool temporary = false); 119 | 120 | /** @brief Disable reception of broadcast messages 121 | * @param temporary Set true to only disable if temporarily enabled 122 | * @note This will reduce load on recieved data handling 123 | */ 124 | static void disableBroadcast(bool temporary = false); 125 | 126 | /** @brief Enables reception of mulitcast messages 127 | * @note This will increase load on recieved data handling 128 | */ 129 | static void enableMulticast (); 130 | 131 | /** @brief Enables reception of all messages 132 | * @param temporary Set true to temporarily enable promiscuous 133 | * @note This will increase load significantly on recieved data handling 134 | * @note All messages will be accepted, even messages with destination MAC other than own 135 | * @note Messages with invalid CRC checksum will still be rejected 136 | */ 137 | static void enablePromiscuous (bool temporary = false); 138 | 139 | /** @brief Disable reception of all messages and go back to default mode 140 | * @param temporary Set true to only disable if temporarily enabled 141 | * @note This will reduce load on received data handling 142 | * @note In this mode only unicast and broadcast messages will be received 143 | */ 144 | static void disablePromiscuous(bool temporary = false); 145 | 146 | /** @brief Disable reception of mulitcast messages 147 | * @note This will reduce load on recieved data handling 148 | */ 149 | static void disableMulticast(); 150 | 151 | /** @brief Reset and fully initialise ENC28J60 152 | * @param csPin Arduino pin used for chip select (enable SPI bus) 153 | * @return uint8_t 0 on failure 154 | */ 155 | static uint8_t doBIST(uint8_t csPin = 8); 156 | 157 | /** @brief Copies a slice from the current packet to RAM 158 | * @param dest pointer in RAM where the data is copied to 159 | * @param maxlength how many bytes to copy; 160 | * @param packetOffset where within the packet to start; if less than maxlength bytes are available only the remaining bytes are copied. 161 | * @return uint16_t the number of bytes that have been read 162 | * @note At the destination at least maxlength+1 bytes should be reserved because the copied content will be 0-terminated. 163 | */ 164 | static uint16_t readPacketSlice(char* dest, int16_t maxlength, int16_t packetOffset); 165 | 166 | /** @brief reserves a block of RAM in the memory of the enc chip 167 | * @param size number of bytes to reserve 168 | * @return uint16_t start address of the block within the enc memory. 0 if the remaining memory for malloc operation is less than size. 169 | * @note There is no enc_free(), i.e., reserved blocks stay reserved for the duration of the program. 170 | * @note The total memory available for malloc-operations is determined by ENC_HEAP_END-ENC_HEAP_START, defined in enc28j60.h; by default this is 0, i.e., you have to change these values in order to use enc_malloc(). 171 | */ 172 | static uint16_t enc_malloc(uint16_t size); 173 | 174 | /** @brief returns the amount of memory within the enc28j60 chip that is still available for malloc. 175 | * @return uint16_t the amount of memory in bytes. 176 | */ 177 | static uint16_t enc_freemem(); 178 | 179 | /** @brief copies a block of data from SRAM to the enc memory 180 | @param dest destination address within enc memory 181 | @param source source pointer to a block of SRAM in the arduino 182 | @param num number of bytes to copy 183 | @note There is no sanity check. Handle with care 184 | */ 185 | static void memcpy_to_enc(uint16_t dest, void* source, int16_t num); 186 | 187 | /** @brief copies a block of data from the enc memory to SRAM 188 | @param dest destination address within SRAM 189 | @param source source address within enc memory 190 | @param num number of bytes to copy 191 | */ 192 | static void memcpy_from_enc(void* dest, uint16_t source, int16_t num); 193 | }; 194 | 195 | typedef ENC28J60 Ethernet; //!< Define alias Ethernet for ENC28J60 196 | 197 | 198 | /** Workaround for Errata 13. 199 | * The transmission hardware may drop some packets because it thinks a late collision 200 | * occurred (which should never happen if all cable length etc. are ok). If setting 201 | * this to 1 these packages will be retried a fixed number of times. Costs about 150bytes 202 | * of flash. 203 | */ 204 | #define ETHERCARD_RETRY_LATECOLLISIONS 0 205 | 206 | /** Enable pipelining of packet transmissions. 207 | * If enabled the packetSend function will not block/wait until the packet is actually 208 | * transmitted; but instead this wait is shifted to the next time that packetSend is 209 | * called. This gives higher performance; however in combination with 210 | * ETHERCARD_RETRY_LATECOLLISIONS this may lead to problems because a packet whose 211 | * transmission fails because the ENC-chip thinks that it is a late collision will 212 | * not be retried until the next call to packetSend. 213 | */ 214 | #define ETHERCARD_SEND_PIPELINING 0 215 | #endif 216 | -------------------------------------------------------------------------------- /images/ArtNetNode_Display.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/essigt/ArtNetNode4/9308181b668a1f4721cd72f3dd28e485623a6e37/images/ArtNetNode_Display.jpeg -------------------------------------------------------------------------------- /images/ArtNetNode_Front1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/essigt/ArtNetNode4/9308181b668a1f4721cd72f3dd28e485623a6e37/images/ArtNetNode_Front1.jpeg -------------------------------------------------------------------------------- /images/ArtNetNode_FullHousing.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/essigt/ArtNetNode4/9308181b668a1f4721cd72f3dd28e485623a6e37/images/ArtNetNode_FullHousing.jpeg -------------------------------------------------------------------------------- /images/ArtNetNode_PCB.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/essigt/ArtNetNode4/9308181b668a1f4721cd72f3dd28e485623a6e37/images/ArtNetNode_PCB.jpeg -------------------------------------------------------------------------------- /images/ArtNetNode_Prototype_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/essigt/ArtNetNode4/9308181b668a1f4721cd72f3dd28e485623a6e37/images/ArtNetNode_Prototype_1.jpeg -------------------------------------------------------------------------------- /images/ArtNetNode_Prototype_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/essigt/ArtNetNode4/9308181b668a1f4721cd72f3dd28e485623a6e37/images/ArtNetNode_Prototype_2.jpeg -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include //memcpy 5 | 6 | #include "system.h" 7 | #include "timer.h" 8 | #include "display.h" 9 | #include "enc28j60.h" 10 | #include "dmx.h" 11 | #include "artnet.h" 12 | 13 | #include "EtherCard.h" 14 | 15 | uint8_t Ethernet::buffer[590]; 16 | 17 | 18 | BufferFiller bfill; 19 | 20 | volatile uint8_t missedFrames = 0; 21 | 22 | static uint16_t homePage() { 23 | bfill = ether.tcpOffset(); 24 | 25 | char* name = "ArtNetNode 1"; 26 | char* ip = "2.0.0.10"; 27 | uint8_t portA = 1; 28 | uint8_t portB = 2; 29 | uint8_t portC = 3; 30 | uint8_t portD = 4; 31 | 32 | bfill.emit_p(PSTR("ArtNet Node
Settings

IP
Name
Port A
Port B
Port C

") 33 | ,name, ip, portA, portB, portC, portD); 34 | return bfill.position(); 35 | } 36 | 37 | 38 | 39 | //TODO: Move WebPage Stuff into other module 40 | int main (void) { 41 | cli();/* disable interrupts */ 42 | setUp32MhzInternalOsc(); 43 | 44 | setupTimer0(); 45 | 46 | //Setup debug leds 47 | PORTA.DIR = 0x03; 48 | PORTA.OUTCLR = 0x03; 49 | 50 | setupTWI(); 51 | setupDisplay(); 52 | 53 | setCursor(0,0); 54 | writeStr("Setup Eth0"); 55 | 56 | static uint8_t macaddr[] = { 0x70,0x69,0x69,0x2D,0x30,0x31 }; 57 | //TODO: Load from EEPROM 58 | static uint8_t ipaddr[] = { 2,0,0,10 }; 59 | 60 | setCursor(0,0); 61 | writeStr("IP:"); 62 | writeInt(ipaddr[0]); 63 | writeStr("."); 64 | writeInt(ipaddr[1]); 65 | writeStr("."); 66 | writeInt(ipaddr[2]); 67 | writeStr("."); 68 | writeInt(ipaddr[3]); 69 | 70 | 71 | ether.begin(sizeof Ethernet::buffer, macaddr); 72 | ether.staticSetup(ipaddr); 73 | ether.enableBroadcast(); 74 | ether.udpServerListenOnPort(artnet.udpArtNet, UDP_PORT_ARTNET); 75 | 76 | clearDisplay(); 77 | 78 | setCursor(0,0); 79 | writeStr("Setup DMX"); 80 | setupDMX(); 81 | 82 | ether.buffer[0] = 0; 83 | 84 | uint16_t counter = 0; 85 | 86 | PMIC.CTRL |= PMIC_LOLVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_HILVLEN_bm; 87 | 88 | sei(); 89 | 90 | setCursor(0,0); 91 | writeStr("DMX 1 2 3 4"); 92 | 93 | 94 | uint8_t receivedPost = 0; 95 | 96 | while(1) { 97 | uint16_t pos = ether.packetLoop(ether.packetReceive()); 98 | 99 | //Handle TCP packages 100 | if(pos) { 101 | char* data = (char *) Ethernet::buffer + pos; 102 | 103 | if (strncmp("GET / ", data, 7) == 0) { 104 | ether.httpServerReply(homePage()); // send web page data 105 | } else if (strncmp("POST / ", data, 7) == 0) { 106 | receivedPost = 1; 107 | 108 | char result[5]; result[0] = 0; 109 | EtherCard::findKeyVal(data, result, 4, "a"); 110 | /* setCursor(0,0); 111 | writeStr(result);*/ 112 | 113 | ether.httpServerReply(homePage()); // send web page data 114 | } 115 | } 116 | 117 | if(ether.isReceiveError()) { 118 | PORTA.OUTTGL = 0x02; 119 | missedFrames++; 120 | } 121 | 122 | 123 | if(counter == 0) { 124 | counter = 12000; 125 | 126 | setCursor(15,0); 127 | if(ether.isLinkUp()) { 128 | writeStr("L"); 129 | } else { 130 | writeStr(" "); 131 | } 132 | 133 | setCursor(15,1); 134 | if(receivedPost == 1) { 135 | writeStr("P"); 136 | } else { 137 | writeStr(" "); 138 | } 139 | 140 | setCursor(0,1); 141 | writeStr("FPS "); 142 | writeIntWidth( artnet.getUniverseFPS(0), 2); 143 | writeStr(" "); 144 | writeIntWidth( artnet.getUniverseFPS(1), 2); 145 | writeStr(" "); 146 | writeIntWidth( artnet.getUniverseFPS(2), 2); 147 | writeStr(" "); 148 | writeIntWidth( artnet.getUniverseFPS(3), 2); 149 | } 150 | 151 | counter--; 152 | 153 | } 154 | 155 | return 0; 156 | } 157 | 158 | -------------------------------------------------------------------------------- /net.h: -------------------------------------------------------------------------------- 1 | // Based on the net.h file from the AVRlib library by Pascal Stang. 2 | // Author: Guido Socher 3 | // Copyright: GPL V2 4 | // 5 | // For AVRlib See http://www.procyonengineering.com/ 6 | // Used with explicit permission of Pascal Stang. 7 | // 8 | // 2010-05-20 9 | 10 | // notation: _P = position of a field 11 | // _V = value of a field 12 | 13 | #ifndef NET_H 14 | #define NET_H 15 | 16 | // ******* SERVICE PORTS ******* 17 | #define HTTP_PORT 80 18 | #define DNS_PORT 53 19 | #define NTP_PORT 123 20 | 21 | // ******* ETH ******* 22 | #define ETH_HEADER_LEN 14 23 | #define ETH_LEN 6 24 | // values of certain bytes: 25 | #define ETHTYPE_ARP_H_V 0x08 26 | #define ETHTYPE_ARP_L_V 0x06 27 | #define ETHTYPE_IP_H_V 0x08 28 | #define ETHTYPE_IP_L_V 0x00 29 | // byte positions in the ethernet frame: 30 | // 31 | // Ethernet type field (2bytes): 32 | #define ETH_TYPE_H_P 12 33 | #define ETH_TYPE_L_P 13 34 | // 35 | #define ETH_DST_MAC 0 36 | #define ETH_SRC_MAC 6 37 | 38 | 39 | // ******* ARP ******* 40 | #define ETH_ARP_OPCODE_REPLY_H_V 0x0 41 | #define ETH_ARP_OPCODE_REPLY_L_V 0x02 42 | #define ETH_ARP_OPCODE_REQ_H_V 0x0 43 | #define ETH_ARP_OPCODE_REQ_L_V 0x01 44 | // start of arp header: 45 | #define ETH_ARP_P 0xe 46 | // 47 | #define ETHTYPE_ARP_L_V 0x06 48 | // arp.dst.ip 49 | #define ETH_ARP_DST_IP_P 0x26 50 | // arp.opcode 51 | #define ETH_ARP_OPCODE_H_P 0x14 52 | #define ETH_ARP_OPCODE_L_P 0x15 53 | // arp.src.mac 54 | #define ETH_ARP_SRC_MAC_P 0x16 55 | #define ETH_ARP_SRC_IP_P 0x1c 56 | #define ETH_ARP_DST_MAC_P 0x20 57 | #define ETH_ARP_DST_IP_P 0x26 58 | 59 | // ******* IP ******* 60 | #define IP_HEADER_LEN 20 61 | #define IP_LEN 4 62 | // ip.src 63 | #define IP_SRC_P 0x1a 64 | #define IP_DST_P 0x1e 65 | #define IP_HEADER_LEN_VER_P 0xe 66 | #define IP_CHECKSUM_P 0x18 67 | #define IP_TTL_P 0x16 68 | #define IP_FLAGS_P 0x14 69 | #define IP_P 0xe 70 | #define IP_TOTLEN_H_P 0x10 71 | #define IP_TOTLEN_L_P 0x11 72 | 73 | #define IP_PROTO_P 0x17 74 | 75 | #define IP_PROTO_ICMP_V 1 76 | #define IP_PROTO_TCP_V 6 77 | // 17=0x11 78 | #define IP_PROTO_UDP_V 17 79 | // ******* ICMP ******* 80 | #define ICMP_TYPE_ECHOREPLY_V 0 81 | #define ICMP_TYPE_ECHOREQUEST_V 8 82 | // 83 | #define ICMP_TYPE_P 0x22 84 | #define ICMP_CHECKSUM_P 0x24 85 | #define ICMP_CHECKSUM_H_P 0x24 86 | #define ICMP_CHECKSUM_L_P 0x25 87 | #define ICMP_IDENT_H_P 0x26 88 | #define ICMP_IDENT_L_P 0x27 89 | #define ICMP_DATA_P 0x2a 90 | 91 | // ******* UDP ******* 92 | #define UDP_HEADER_LEN 8 93 | // 94 | #define UDP_SRC_PORT_H_P 0x22 95 | #define UDP_SRC_PORT_L_P 0x23 96 | #define UDP_DST_PORT_H_P 0x24 97 | #define UDP_DST_PORT_L_P 0x25 98 | // 99 | #define UDP_LEN_H_P 0x26 100 | #define UDP_LEN_L_P 0x27 101 | #define UDP_CHECKSUM_H_P 0x28 102 | #define UDP_CHECKSUM_L_P 0x29 103 | #define UDP_DATA_P 0x2a 104 | 105 | // ******* TCP ******* 106 | #define TCP_SRC_PORT_H_P 0x22 107 | #define TCP_SRC_PORT_L_P 0x23 108 | #define TCP_DST_PORT_H_P 0x24 109 | #define TCP_DST_PORT_L_P 0x25 110 | // the tcp seq number is 4 bytes 0x26-0x29 111 | #define TCP_SEQ_H_P 0x26 112 | #define TCP_SEQACK_H_P 0x2a 113 | // flags: SYN=2 114 | #define TCP_FLAGS_P 0x2f 115 | #define TCP_FLAGS_SYN_V 2 116 | #define TCP_FLAGS_FIN_V 1 117 | #define TCP_FLAGS_RST_V 4 118 | #define TCP_FLAGS_PUSH_V 8 119 | #define TCP_FLAGS_SYNACK_V 0x12 120 | #define TCP_FLAGS_ACK_V 0x10 121 | #define TCP_FLAGS_PSHACK_V 0x18 122 | // plain len without the options: 123 | #define TCP_HEADER_LEN_PLAIN 20 124 | #define TCP_HEADER_LEN_P 0x2e 125 | #define TCP_WIN_SIZE 0x30 126 | #define TCP_CHECKSUM_H_P 0x32 127 | #define TCP_CHECKSUM_L_P 0x33 128 | #define TCP_OPTIONS_P 0x36 129 | // 130 | #endif 131 | -------------------------------------------------------------------------------- /system.h: -------------------------------------------------------------------------------- 1 | #ifndef _SYSTEM_H 2 | #define _SYSTEM_H 1 3 | 4 | #include 5 | 6 | #define F_CPU 32000000UL 7 | 8 | void setUp32MhzInternalOsc(void) 9 | { 10 | OSC_CTRL |= OSC_RC32MEN_bm; //Setup 32Mhz crystal 11 | 12 | while(!(OSC_STATUS & OSC_RC32MRDY_bm)); 13 | 14 | CCP = CCP_IOREG_gc; //Trigger protection mechanism 15 | CLK_CTRL = CLK_SCLKSEL_RC32M_gc; //Enable internal 32Mhz crystal 16 | } 17 | 18 | 19 | 20 | #endif -------------------------------------------------------------------------------- /tcpip.cpp: -------------------------------------------------------------------------------- 1 | // IP, ARP, UDP and TCP functions. 2 | // Author: Guido Socher 3 | // Copyright: GPL V2 4 | // 5 | // The TCP implementation uses some size optimisations which are valid 6 | // only if all data can be sent in one single packet. This is however 7 | // not a big limitation for a microcontroller as you will anyhow use 8 | // small web-pages. The web server must send the entire web page in one 9 | // packet. The client "web browser" as implemented here can also receive 10 | // large pages. 11 | // 12 | // 2010-05-20 13 | 14 | #include "net.h" 15 | #include "EtherCard.h" 16 | 17 | 18 | #include 19 | 20 | #define gPB ether.buffer 21 | 22 | #define PINGPATTERN 0x42 23 | 24 | // Avoid spurious pgmspace warnings - http://forum.jeelabs.net/node/327 25 | // See also http://gcc.gnu.org/bugzilla/show_bug.cgi?id=34734 26 | //#undef PROGMEM 27 | //#define PROGMEM __attribute__(( section(".progmem.data") )) 28 | //#undef PSTR 29 | //#define PSTR(s) (__extension__({static prog_char c[] PROGMEM = (s); &c[0];})) 30 | 31 | #define TCP_STATE_SENDSYN 1 32 | #define TCP_STATE_SYNSENT 2 33 | #define TCP_STATE_ESTABLISHED 3 34 | #define TCP_STATE_NOTUSED 4 35 | #define TCP_STATE_CLOSING 5 36 | #define TCP_STATE_CLOSED 6 37 | 38 | #define TCPCLIENT_SRC_PORT_H 11 //Source port (MSB) for TCP/IP client connections - hardcode all TCP/IP client connection from ports in range 2816-3071 39 | static uint8_t tcpclient_src_port_l=1; // Source port (LSB) for tcp/ip client connections - increments on each TCP/IP request 40 | static uint8_t tcp_fd; // a file descriptor, will be encoded into the port 41 | static uint8_t tcp_client_state; //TCP connection state: 1=Send SYN, 2=SYN sent awaiting SYN+ACK, 3=Established, 4=Not used, 5=Closing, 6=Closed 42 | static uint8_t tcp_client_port_h; // Destination port (MSB) of TCP/IP client connection 43 | static uint8_t tcp_client_port_l; // Destination port (LSB) of TCP/IP client connection 44 | static uint8_t (*client_tcp_result_cb)(uint8_t,uint8_t,uint16_t,uint16_t); // Pointer to callback function to handle response to current TCP/IP request 45 | static uint16_t (*client_tcp_datafill_cb)(uint8_t); //Pointer to callback function to handle payload data in response to current TCP/IP request 46 | static uint8_t www_fd; // ID of current http request (only one http request at a time - one of the 8 possible concurrent TCP/IP connections) 47 | static void (*client_browser_cb)(uint8_t,uint16_t,uint16_t); // Pointer to callback function to handle result of current HTTP request 48 | static const char *client_additionalheaderline; // Pointer to c-string additional http request header info 49 | static const char *client_postval; 50 | static const char *client_urlbuf; // Pointer to c-string path part of HTTP request URL 51 | static const char *client_urlbuf_var; // Pointer to c-string filename part of HTTP request URL 52 | static const char *client_hoststr; // Pointer to c-string hostname of current HTTP request 53 | static void (*icmp_cb)(uint8_t *ip); // Pointer to callback function for ICMP ECHO response handler (triggers when localhost recieves ping respnse (pong)) 54 | static uint8_t destmacaddr[ETH_LEN]; // storing both dns server and destination mac addresses, but at different times because both are never needed at same time. 55 | static bool waiting_for_dns_mac = false; //might be better to use bit flags and bitmask operations for these conditions 56 | static bool has_dns_mac = false; 57 | static bool waiting_for_dest_mac = false; 58 | static bool has_dest_mac = false; 59 | static uint8_t gwmacaddr[ETH_LEN]; // Hardware (MAC) address of gateway router 60 | static uint8_t waitgwmac; // Bitwise flags of gateway router status - see below for states 61 | //Define gatweay router ARP statuses 62 | #define WGW_INITIAL_ARP 1 // First request, no answer yet 63 | #define WGW_HAVE_GW_MAC 2 // Have gateway router MAC 64 | #define WGW_REFRESHING 4 // Refreshing but already have gateway MAC 65 | #define WGW_ACCEPT_ARP_REPLY 8 // Accept an ARP reply 66 | 67 | static uint16_t info_data_len; // Length of TCP/IP payload 68 | static uint8_t seqnum = 0xa; // My initial tcp sequence number 69 | static uint8_t result_fd = 123; // Session id of last reply 70 | static const char* result_ptr; // Pointer to TCP/IP data 71 | static unsigned long SEQ; // TCP/IP sequence number 72 | 73 | #define CLIENTMSS 550 74 | #define TCP_DATA_START ((uint16_t)TCP_SRC_PORT_H_P+(gPB[TCP_HEADER_LEN_P]>>4)*4) // Get offset of TCP/IP payload data 75 | 76 | const unsigned char arpreqhdr[] PROGMEM = { 0,1,8,0,6,4,0,1 }; // ARP request header 77 | const unsigned char iphdr[] PROGMEM = { 0x45,0,0,0x82,0,0,0x40,0,0x20 }; //IP header 78 | const unsigned char ntpreqhdr[] PROGMEM = { 0xE3,0,4,0xFA,0,1,0,0,0,1 }; //NTP request header 79 | extern const uint8_t allOnes[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; // Used for hardware (MAC) and IP broadcast addresses 80 | 81 | static void fill_checksum(uint8_t dest, uint8_t off, uint16_t len,uint8_t type) { 82 | const uint8_t* ptr = gPB + off; 83 | uint32_t sum = type==1 ? IP_PROTO_UDP_V+len-8 : 84 | type==2 ? IP_PROTO_TCP_V+len-8 : 0; 85 | while(len >1) { 86 | sum += (uint16_t) (((uint32_t)*ptr<<8)|*(ptr+1)); 87 | ptr+=2; 88 | len-=2; 89 | } 90 | if (len) 91 | sum += ((uint32_t)*ptr)<<8; 92 | while (sum>>16) 93 | sum = (uint16_t) sum + (sum >> 16); 94 | uint16_t ck = ~ (uint16_t) sum; 95 | gPB[dest] = ck>>8; 96 | gPB[dest+1] = ck; 97 | } 98 | 99 | static void setMACs (const uint8_t *mac) { 100 | EtherCard::copyMac(gPB + ETH_DST_MAC, mac); 101 | EtherCard::copyMac(gPB + ETH_SRC_MAC, EtherCard::mymac); 102 | } 103 | 104 | static void setMACandIPs (const uint8_t *mac, const uint8_t *dst) { 105 | setMACs(mac); 106 | EtherCard::copyIp(gPB + IP_DST_P, dst); 107 | EtherCard::copyIp(gPB + IP_SRC_P, EtherCard::myip); 108 | } 109 | 110 | static uint8_t check_ip_message_is_from(const uint8_t *ip) { 111 | return memcmp(gPB + IP_SRC_P, ip, IP_LEN) == 0; 112 | } 113 | 114 | static bool is_lan(const uint8_t source[IP_LEN], const uint8_t destination[IP_LEN]) { 115 | if(source[0] == 0 || destination[0] == 0) { 116 | return false; 117 | } 118 | for(int i = 0; i < IP_LEN; i++) 119 | if((source[i] & EtherCard::netmask[i]) != (destination[i] & EtherCard::netmask[i])) { 120 | return false; 121 | } 122 | return true; 123 | } 124 | 125 | static uint8_t eth_type_is_arp_and_my_ip(uint16_t len) { 126 | return len >= 41 && gPB[ETH_TYPE_H_P] == ETHTYPE_ARP_H_V && 127 | gPB[ETH_TYPE_L_P] == ETHTYPE_ARP_L_V && 128 | memcmp(gPB + ETH_ARP_DST_IP_P, EtherCard::myip, IP_LEN) == 0; 129 | } 130 | 131 | //Updatet fuction for ArtNet 132 | static uint8_t eth_type_is_ip_and_my_ip(uint16_t len) { 133 | return len >= 42 && gPB[ETH_TYPE_H_P] == ETHTYPE_IP_H_V && 134 | gPB[ETH_TYPE_L_P] == ETHTYPE_IP_L_V && 135 | gPB[IP_HEADER_LEN_VER_P] == 0x45 && 136 | (memcmp(gPB + IP_DST_P, EtherCard::myip, IP_LEN) == 0 //not my IP 137 | || gPB[IP_DST_P + 3] == 0xff); //not global broadcasts 138 | //!@todo Handle multicast 139 | } 140 | 141 | /* 142 | * Original Function 143 | * static uint8_t eth_type_is_ip_and_my_ip(uint16_t len) { 144 | return len >= 42 && gPB[ETH_TYPE_H_P] == ETHTYPE_IP_H_V && 145 | gPB[ETH_TYPE_L_P] == ETHTYPE_IP_L_V && 146 | gPB[IP_HEADER_LEN_VER_P] == 0x45 && 147 | (memcmp(gPB + IP_DST_P, EtherCard::myip, IP_LEN) == 0 //not my IP 148 | || (memcmp(gPB + IP_DST_P, EtherCard::broadcastip, IP_LEN) == 0) //not subnet broadcast 149 | || (memcmp(gPB + IP_DST_P, allOnes, IP_LEN) == 0)); //not global broadcasts 150 | //!@todo Handle multicast 151 | }*/ 152 | 153 | static void fill_ip_hdr_checksum() { 154 | gPB[IP_CHECKSUM_P] = 0; 155 | gPB[IP_CHECKSUM_P+1] = 0; 156 | gPB[IP_FLAGS_P] = 0x40; // don't fragment 157 | gPB[IP_FLAGS_P+1] = 0; // fragement offset 158 | gPB[IP_TTL_P] = 64; // ttl 159 | fill_checksum(IP_CHECKSUM_P, IP_P, IP_HEADER_LEN,0); 160 | } 161 | 162 | static void make_eth_ip() { 163 | setMACs(gPB + ETH_SRC_MAC); 164 | EtherCard::copyIp(gPB + IP_DST_P, gPB + IP_SRC_P); 165 | EtherCard::copyIp(gPB + IP_SRC_P, EtherCard::myip); 166 | fill_ip_hdr_checksum(); 167 | } 168 | 169 | static void step_seq(uint16_t rel_ack_num,uint8_t cp_seq) { 170 | uint8_t i; 171 | uint8_t tseq; 172 | i = 4; 173 | while(i>0) { 174 | rel_ack_num = gPB[TCP_SEQ_H_P+i-1]+rel_ack_num; 175 | tseq = gPB[TCP_SEQACK_H_P+i-1]; 176 | gPB[TCP_SEQACK_H_P+i-1] = rel_ack_num; 177 | if (cp_seq) 178 | gPB[TCP_SEQ_H_P+i-1] = tseq; 179 | else 180 | gPB[TCP_SEQ_H_P+i-1] = 0; // some preset value 181 | rel_ack_num = rel_ack_num>>8; 182 | i--; 183 | } 184 | } 185 | 186 | static void make_tcphead(uint16_t rel_ack_num,uint8_t cp_seq) { 187 | uint8_t i = gPB[TCP_DST_PORT_H_P]; 188 | gPB[TCP_DST_PORT_H_P] = gPB[TCP_SRC_PORT_H_P]; 189 | gPB[TCP_SRC_PORT_H_P] = i; 190 | uint8_t j = gPB[TCP_DST_PORT_L_P]; 191 | gPB[TCP_DST_PORT_L_P] = gPB[TCP_SRC_PORT_L_P]; 192 | gPB[TCP_SRC_PORT_L_P] = j; 193 | step_seq(rel_ack_num,cp_seq); 194 | gPB[TCP_CHECKSUM_H_P] = 0; 195 | gPB[TCP_CHECKSUM_L_P] = 0; 196 | gPB[TCP_HEADER_LEN_P] = 0x50; 197 | } 198 | 199 | static void make_arp_answer_from_request() { 200 | setMACs(gPB + ETH_SRC_MAC); 201 | gPB[ETH_ARP_OPCODE_H_P] = ETH_ARP_OPCODE_REPLY_H_V; 202 | gPB[ETH_ARP_OPCODE_L_P] = ETH_ARP_OPCODE_REPLY_L_V; 203 | EtherCard::copyMac(gPB + ETH_ARP_DST_MAC_P, gPB + ETH_ARP_SRC_MAC_P); 204 | EtherCard::copyMac(gPB + ETH_ARP_SRC_MAC_P, EtherCard::mymac); 205 | EtherCard::copyIp(gPB + ETH_ARP_DST_IP_P, gPB + ETH_ARP_SRC_IP_P); 206 | EtherCard::copyIp(gPB + ETH_ARP_SRC_IP_P, EtherCard::myip); 207 | EtherCard::packetSend(42); 208 | } 209 | 210 | static void make_echo_reply_from_request(uint16_t len) { 211 | make_eth_ip(); 212 | gPB[ICMP_TYPE_P] = ICMP_TYPE_ECHOREPLY_V; 213 | if (gPB[ICMP_CHECKSUM_P] > (0xFF-0x08)) 214 | gPB[ICMP_CHECKSUM_P+1]++; 215 | gPB[ICMP_CHECKSUM_P] += 0x08; 216 | EtherCard::packetSend(len); 217 | } 218 | 219 | void EtherCard::makeUdpReply (const char *data,uint8_t datalen,uint16_t port) { 220 | if (datalen>220) 221 | datalen = 220; 222 | gPB[IP_TOTLEN_H_P] = (IP_HEADER_LEN+UDP_HEADER_LEN+datalen) >>8; 223 | gPB[IP_TOTLEN_L_P] = IP_HEADER_LEN+UDP_HEADER_LEN+datalen; 224 | make_eth_ip(); 225 | gPB[UDP_DST_PORT_H_P] = gPB[UDP_SRC_PORT_H_P]; 226 | gPB[UDP_DST_PORT_L_P] = gPB[UDP_SRC_PORT_L_P]; 227 | gPB[UDP_SRC_PORT_H_P] = port>>8; 228 | gPB[UDP_SRC_PORT_L_P] = port; 229 | gPB[UDP_LEN_H_P] = (UDP_HEADER_LEN+datalen) >> 8; 230 | gPB[UDP_LEN_L_P] = UDP_HEADER_LEN+datalen; 231 | gPB[UDP_CHECKSUM_H_P] = 0; 232 | gPB[UDP_CHECKSUM_L_P] = 0; 233 | memcpy(gPB + UDP_DATA_P, data, datalen); 234 | fill_checksum(UDP_CHECKSUM_H_P, IP_SRC_P, 16 + datalen,1); 235 | packetSend(UDP_HEADER_LEN+IP_HEADER_LEN+ETH_HEADER_LEN+datalen); 236 | } 237 | 238 | static void make_tcp_synack_from_syn() { 239 | gPB[IP_TOTLEN_H_P] = 0; 240 | gPB[IP_TOTLEN_L_P] = IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+4; 241 | make_eth_ip(); 242 | gPB[TCP_FLAGS_P] = TCP_FLAGS_SYNACK_V; 243 | make_tcphead(1,0); 244 | gPB[TCP_SEQ_H_P+0] = 0; 245 | gPB[TCP_SEQ_H_P+1] = 0; 246 | gPB[TCP_SEQ_H_P+2] = seqnum; 247 | gPB[TCP_SEQ_H_P+3] = 0; 248 | seqnum += 3; 249 | gPB[TCP_OPTIONS_P] = 2; 250 | gPB[TCP_OPTIONS_P+1] = 4; 251 | gPB[TCP_OPTIONS_P+2] = 0x05; 252 | gPB[TCP_OPTIONS_P+3] = 0x0; 253 | gPB[TCP_HEADER_LEN_P] = 0x60; 254 | gPB[TCP_WIN_SIZE] = 0x5; // 1400=0x578 255 | gPB[TCP_WIN_SIZE+1] = 0x78; 256 | fill_checksum(TCP_CHECKSUM_H_P, IP_SRC_P, 8+TCP_HEADER_LEN_PLAIN+4,2); 257 | EtherCard::packetSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+4+ETH_HEADER_LEN); 258 | } 259 | 260 | uint16_t EtherCard::getTcpPayloadLength() { 261 | int16_t i = (((int16_t)gPB[IP_TOTLEN_H_P])<<8)|gPB[IP_TOTLEN_L_P]; 262 | i -= IP_HEADER_LEN; 263 | i -= (gPB[TCP_HEADER_LEN_P]>>4)*4; // generate len in bytes; 264 | if (i<=0) 265 | i = 0; 266 | return (uint16_t)i; 267 | } 268 | 269 | static void make_tcp_ack_from_any(int16_t datlentoack,uint8_t addflags) { 270 | gPB[TCP_FLAGS_P] = TCP_FLAGS_ACK_V|addflags; 271 | if (addflags!=TCP_FLAGS_RST_V && datlentoack==0) 272 | datlentoack = 1; 273 | make_tcphead(datlentoack,1); // no options 274 | uint16_t j = IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN; 275 | gPB[IP_TOTLEN_H_P] = j>>8; 276 | gPB[IP_TOTLEN_L_P] = j; 277 | make_eth_ip(); 278 | gPB[TCP_WIN_SIZE] = 0x4; // 1024=0x400, 1280=0x500 2048=0x800 768=0x300 279 | gPB[TCP_WIN_SIZE+1] = 0; 280 | fill_checksum(TCP_CHECKSUM_H_P, IP_SRC_P, 8+TCP_HEADER_LEN_PLAIN,2); 281 | EtherCard::packetSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+ETH_HEADER_LEN); 282 | } 283 | 284 | static void make_tcp_ack_with_data_noflags(uint16_t dlen) { 285 | uint16_t j = IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+dlen; 286 | gPB[IP_TOTLEN_H_P] = j>>8; 287 | gPB[IP_TOTLEN_L_P] = j; 288 | fill_ip_hdr_checksum(); 289 | gPB[TCP_CHECKSUM_H_P] = 0; 290 | gPB[TCP_CHECKSUM_L_P] = 0; 291 | fill_checksum(TCP_CHECKSUM_H_P, IP_SRC_P, 8+TCP_HEADER_LEN_PLAIN+dlen,2); 292 | EtherCard::packetSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+dlen+ETH_HEADER_LEN); 293 | } 294 | 295 | void EtherCard::httpServerReply (uint16_t dlen) { 296 | make_tcp_ack_from_any(info_data_len,0); // send ack for http get 297 | gPB[TCP_FLAGS_P] = TCP_FLAGS_ACK_V|TCP_FLAGS_PUSH_V|TCP_FLAGS_FIN_V; 298 | make_tcp_ack_with_data_noflags(dlen); // send data 299 | } 300 | 301 | static uint32_t getBigEndianLong(uint8_t offs) { //get the sequence number of packets after an ack from GET 302 | return (((unsigned long)gPB[offs]*256+gPB[offs+1])*256+gPB[offs+2])*256+gPB[offs+3]; 303 | } //thanks to mstuetz for the missing (unsigned long) 304 | 305 | static void setSequenceNumber(uint32_t seq) { 306 | gPB[TCP_SEQ_H_P] = (seq & 0xff000000 ) >> 24; 307 | gPB[TCP_SEQ_H_P+1] = (seq & 0xff0000 ) >> 16; 308 | gPB[TCP_SEQ_H_P+2] = (seq & 0xff00 ) >> 8; 309 | gPB[TCP_SEQ_H_P+3] = (seq & 0xff ); 310 | } 311 | 312 | uint32_t EtherCard::getSequenceNumber() { 313 | return getBigEndianLong(TCP_SEQ_H_P); 314 | } 315 | 316 | void EtherCard::httpServerReplyAck () { 317 | make_tcp_ack_from_any(getTcpPayloadLength(),0); // send ack for http request 318 | SEQ = getSequenceNumber(); //get the sequence number of packets after an ack from GET 319 | } 320 | 321 | void EtherCard::httpServerReply_with_flags (uint16_t dlen , uint8_t flags) { 322 | setSequenceNumber(SEQ); 323 | gPB[TCP_FLAGS_P] = flags; // final packet 324 | make_tcp_ack_with_data_noflags(dlen); // send data 325 | SEQ=SEQ+dlen; 326 | } 327 | 328 | void EtherCard::clientIcmpRequest(const uint8_t *destip) { 329 | if(is_lan(EtherCard::myip, destip)) { 330 | setMACandIPs(destmacaddr, destip); 331 | } else { 332 | setMACandIPs(gwmacaddr, destip); 333 | } 334 | gPB[ETH_TYPE_H_P] = ETHTYPE_IP_H_V; 335 | gPB[ETH_TYPE_L_P] = ETHTYPE_IP_L_V; 336 | memcpy_P(gPB + IP_P,iphdr,sizeof iphdr); 337 | gPB[IP_TOTLEN_L_P] = 0x54; 338 | gPB[IP_PROTO_P] = IP_PROTO_ICMP_V; 339 | fill_ip_hdr_checksum(); 340 | gPB[ICMP_TYPE_P] = ICMP_TYPE_ECHOREQUEST_V; 341 | gPB[ICMP_TYPE_P+1] = 0; // code 342 | gPB[ICMP_CHECKSUM_H_P] = 0; 343 | gPB[ICMP_CHECKSUM_L_P] = 0; 344 | gPB[ICMP_IDENT_H_P] = 5; // some number 345 | gPB[ICMP_IDENT_L_P] = EtherCard::myip[3]; // last byte of my IP 346 | gPB[ICMP_IDENT_L_P+1] = 0; // seq number, high byte 347 | gPB[ICMP_IDENT_L_P+2] = 1; // seq number, low byte, we send only 1 ping at a time 348 | memset(gPB + ICMP_DATA_P, PINGPATTERN, 56); 349 | fill_checksum(ICMP_CHECKSUM_H_P, ICMP_TYPE_P, 56+8,0); 350 | packetSend(98); 351 | } 352 | 353 | void EtherCard::ntpRequest (uint8_t *ntpip,uint8_t srcport) { 354 | if(is_lan(myip, ntpip)) { 355 | setMACandIPs(destmacaddr, ntpip); 356 | } else { 357 | setMACandIPs(gwmacaddr, ntpip); 358 | } 359 | gPB[ETH_TYPE_H_P] = ETHTYPE_IP_H_V; 360 | gPB[ETH_TYPE_L_P] = ETHTYPE_IP_L_V; 361 | memcpy_P(gPB + IP_P,iphdr,sizeof iphdr); 362 | gPB[IP_TOTLEN_L_P] = 0x4c; 363 | gPB[IP_PROTO_P] = IP_PROTO_UDP_V; 364 | fill_ip_hdr_checksum(); 365 | gPB[UDP_DST_PORT_H_P] = 0; 366 | gPB[UDP_DST_PORT_L_P] = NTP_PORT; // ntp = 123 367 | gPB[UDP_SRC_PORT_H_P] = 10; 368 | gPB[UDP_SRC_PORT_L_P] = srcport; // lower 8 bit of src port 369 | gPB[UDP_LEN_H_P] = 0; 370 | gPB[UDP_LEN_L_P] = 56; // fixed len 371 | gPB[UDP_CHECKSUM_H_P] = 0; 372 | gPB[UDP_CHECKSUM_L_P] = 0; 373 | memset(gPB + UDP_DATA_P, 0, 48); 374 | memcpy_P(gPB + UDP_DATA_P,ntpreqhdr,10); 375 | fill_checksum(UDP_CHECKSUM_H_P, IP_SRC_P, 16 + 48,1); 376 | packetSend(90); 377 | } 378 | 379 | uint8_t EtherCard::ntpProcessAnswer (uint32_t *time,uint8_t dstport_l) { 380 | if ((dstport_l && gPB[UDP_DST_PORT_L_P]!=dstport_l) || gPB[UDP_LEN_H_P]!=0 || 381 | gPB[UDP_LEN_L_P]!=56 || gPB[UDP_SRC_PORT_L_P]!=0x7b) 382 | return 0; 383 | ((uint8_t*) time)[3] = gPB[0x52]; 384 | ((uint8_t*) time)[2] = gPB[0x53]; 385 | ((uint8_t*) time)[1] = gPB[0x54]; 386 | ((uint8_t*) time)[0] = gPB[0x55]; 387 | return 1; 388 | } 389 | 390 | void EtherCard::udpPrepare (uint16_t sport, const uint8_t *dip, uint16_t dport) { 391 | if(is_lan(myip, dip)) { // this works because both dns mac and destinations mac are stored in same variable - destmacaddr 392 | setMACandIPs(destmacaddr, dip); // at different times. The program could have separate variable for dns mac, then here should be 393 | } else { // checked if dip is dns ip and separately if dip is hisip and then use correct mac. 394 | setMACandIPs(gwmacaddr, dip); 395 | } 396 | // see http://tldp.org/HOWTO/Multicast-HOWTO-2.html 397 | // multicast or broadcast address, https://github.com/jcw/ethercard/issues/59 398 | if ((dip[0] & 0xF0) == 0xE0 || *((unsigned long*) dip) == 0xFFFFFFFF || !memcmp(broadcastip,dip,IP_LEN)) 399 | EtherCard::copyMac(gPB + ETH_DST_MAC, allOnes); 400 | gPB[ETH_TYPE_H_P] = ETHTYPE_IP_H_V; 401 | gPB[ETH_TYPE_L_P] = ETHTYPE_IP_L_V; 402 | memcpy_P(gPB + IP_P,iphdr,sizeof iphdr); 403 | gPB[IP_TOTLEN_H_P] = 0; 404 | gPB[IP_PROTO_P] = IP_PROTO_UDP_V; 405 | gPB[UDP_DST_PORT_H_P] = (dport>>8); 406 | gPB[UDP_DST_PORT_L_P] = dport; 407 | gPB[UDP_SRC_PORT_H_P] = (sport>>8); 408 | gPB[UDP_SRC_PORT_L_P] = sport; 409 | gPB[UDP_LEN_H_P] = 0; 410 | gPB[UDP_CHECKSUM_H_P] = 0; 411 | gPB[UDP_CHECKSUM_L_P] = 0; 412 | } 413 | 414 | void EtherCard::udpTransmit (uint16_t datalen) { 415 | gPB[IP_TOTLEN_H_P] = (IP_HEADER_LEN+UDP_HEADER_LEN+datalen) >> 8; 416 | gPB[IP_TOTLEN_L_P] = IP_HEADER_LEN+UDP_HEADER_LEN+datalen; 417 | fill_ip_hdr_checksum(); 418 | gPB[UDP_LEN_H_P] = (UDP_HEADER_LEN+datalen) >>8; 419 | gPB[UDP_LEN_L_P] = UDP_HEADER_LEN+datalen; 420 | fill_checksum(UDP_CHECKSUM_H_P, IP_SRC_P, 16 + datalen,1); 421 | packetSend(UDP_HEADER_LEN+IP_HEADER_LEN+ETH_HEADER_LEN+datalen); 422 | } 423 | 424 | void EtherCard::sendUdp (const char *data, uint8_t datalen, uint16_t sport, 425 | const uint8_t *dip, uint16_t dport) { 426 | udpPrepare(sport, dip, dport); 427 | if (datalen>220) 428 | datalen = 220; 429 | memcpy(gPB + UDP_DATA_P, data, datalen); 430 | udpTransmit(datalen); 431 | } 432 | 433 | void EtherCard::sendWol (uint8_t *wolmac) { 434 | setMACandIPs(allOnes, allOnes); 435 | gPB[ETH_TYPE_H_P] = ETHTYPE_IP_H_V; 436 | gPB[ETH_TYPE_L_P] = ETHTYPE_IP_L_V; 437 | memcpy_P(gPB + IP_P,iphdr,9); 438 | gPB[IP_TOTLEN_L_P] = 0x82; 439 | gPB[IP_PROTO_P] = IP_PROTO_UDP_V; 440 | fill_ip_hdr_checksum(); 441 | gPB[UDP_DST_PORT_H_P] = 0; 442 | gPB[UDP_DST_PORT_L_P] = 0x9; // wol = normally 9 443 | gPB[UDP_SRC_PORT_H_P] = 10; 444 | gPB[UDP_SRC_PORT_L_P] = 0x42; // source port does not matter 445 | gPB[UDP_LEN_H_P] = 0; 446 | gPB[UDP_LEN_L_P] = 110; // fixed len 447 | gPB[UDP_CHECKSUM_H_P] = 0; 448 | gPB[UDP_CHECKSUM_L_P] = 0; 449 | copyMac(gPB + UDP_DATA_P, allOnes); 450 | uint8_t pos = UDP_DATA_P; 451 | for (uint8_t m = 0; m < 16; ++m) { 452 | pos += 6; 453 | copyMac(gPB + pos, wolmac); 454 | } 455 | fill_checksum(UDP_CHECKSUM_H_P, IP_SRC_P, 16 + 102,1); 456 | packetSend(pos + 6); 457 | } 458 | 459 | // make a arp request 460 | static void client_arp_whohas(uint8_t *ip_we_search) { 461 | setMACs(allOnes); 462 | gPB[ETH_TYPE_H_P] = ETHTYPE_ARP_H_V; 463 | gPB[ETH_TYPE_L_P] = ETHTYPE_ARP_L_V; 464 | memcpy_P(gPB + ETH_ARP_P, arpreqhdr, sizeof arpreqhdr); 465 | memset(gPB + ETH_ARP_DST_MAC_P, 0, ETH_LEN); 466 | EtherCard::copyMac(gPB + ETH_ARP_SRC_MAC_P, EtherCard::mymac); 467 | EtherCard::copyIp(gPB + ETH_ARP_DST_IP_P, ip_we_search); 468 | EtherCard::copyIp(gPB + ETH_ARP_SRC_IP_P, EtherCard::myip); 469 | EtherCard::packetSend(42); 470 | } 471 | 472 | uint8_t EtherCard::clientWaitingGw () { 473 | return !(waitgwmac & WGW_HAVE_GW_MAC); 474 | } 475 | 476 | uint8_t EtherCard::clientWaitingDns () { 477 | if(is_lan(myip, dnsip)) 478 | return !has_dns_mac; 479 | return !(waitgwmac & WGW_HAVE_GW_MAC); 480 | } 481 | 482 | static uint8_t client_store_mac(uint8_t *source_ip, uint8_t *mac) { 483 | if (memcmp(gPB + ETH_ARP_SRC_IP_P, source_ip, IP_LEN) != 0) 484 | return 0; 485 | EtherCard::copyMac(mac, gPB + ETH_ARP_SRC_MAC_P); 486 | return 1; 487 | } 488 | 489 | // static void client_gw_arp_refresh() { 490 | // if (waitgwmac & WGW_HAVE_GW_MAC) 491 | // waitgwmac |= WGW_REFRESHING; 492 | // } 493 | 494 | void EtherCard::setGwIp (const uint8_t *gwipaddr) { 495 | delaycnt = 0; //request gateway ARP lookup 496 | waitgwmac = WGW_INITIAL_ARP; // causes an arp request in the packet loop 497 | copyIp(gwip, gwipaddr); 498 | } 499 | 500 | void EtherCard::updateBroadcastAddress() 501 | { 502 | for(uint8_t i=0; i>4) * 4 526 | gPB[TCP_FLAGS_P] = TCP_FLAGS_SYN_V; 527 | gPB[TCP_WIN_SIZE] = 0x3; // 1024 = 0x400 768 = 0x300, initial window 528 | gPB[TCP_WIN_SIZE+1] = 0x0; 529 | gPB[TCP_CHECKSUM_H_P] = 0; 530 | gPB[TCP_CHECKSUM_L_P] = 0; 531 | gPB[TCP_CHECKSUM_L_P+1] = 0; 532 | gPB[TCP_CHECKSUM_L_P+2] = 0; 533 | gPB[TCP_OPTIONS_P] = 2; 534 | gPB[TCP_OPTIONS_P+1] = 4; 535 | gPB[TCP_OPTIONS_P+2] = (CLIENTMSS>>8); 536 | gPB[TCP_OPTIONS_P+3] = (uint8_t) CLIENTMSS; 537 | fill_checksum(TCP_CHECKSUM_H_P, IP_SRC_P, 8 +TCP_HEADER_LEN_PLAIN+4,2); 538 | // 4 is the tcp mss option: 539 | EtherCard::packetSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+ETH_HEADER_LEN+4); 540 | } 541 | 542 | uint8_t EtherCard::clientTcpReq (uint8_t (*result_cb)(uint8_t,uint8_t,uint16_t,uint16_t), 543 | uint16_t (*datafill_cb)(uint8_t),uint16_t port) { 544 | client_tcp_result_cb = result_cb; 545 | client_tcp_datafill_cb = datafill_cb; 546 | tcp_client_port_h = port>>8; 547 | tcp_client_port_l = port; 548 | tcp_client_state = TCP_STATE_SENDSYN; // Flag to packetloop to initiate a TCP/IP session by send a syn 549 | tcp_fd = (tcp_fd + 1) & 7; 550 | return tcp_fd; 551 | } 552 | 553 | static uint16_t www_client_internal_datafill_cb(uint8_t fd) { 554 | BufferFiller bfill = EtherCard::tcpOffset(); 555 | if (fd==www_fd) { 556 | if (client_postval == 0) { 557 | bfill.emit_p(PSTR("GET $F$S HTTP/1.0\r\n" 558 | "Host: $F\r\n" 559 | "$F\r\n" 560 | "\r\n"), client_urlbuf, 561 | client_urlbuf_var, 562 | client_hoststr, client_additionalheaderline); 563 | } else { 564 | const char* ahl = client_additionalheaderline; 565 | bfill.emit_p(PSTR("POST $F HTTP/1.0\r\n" 566 | "Host: $F\r\n" 567 | "$F$S" 568 | "Accept: */*\r\n" 569 | "Content-Length: $D\r\n" 570 | "Content-Type: application/x-www-form-urlencoded\r\n" 571 | "\r\n" 572 | "$S"), client_urlbuf, 573 | client_hoststr, 574 | ahl != 0 ? ahl : PSTR(""), 575 | ahl != 0 ? "\r\n" : "", 576 | strlen(client_postval), 577 | client_postval); 578 | } 579 | } 580 | return bfill.position(); 581 | } 582 | 583 | static uint8_t www_client_internal_result_cb(uint8_t fd, uint8_t statuscode, uint16_t datapos, uint16_t len_of_data) { 584 | if (fd!=www_fd) 585 | (*client_browser_cb)(4,0,0); 586 | else if (statuscode==0 && len_of_data>12 && client_browser_cb) { 587 | uint8_t f = strncmp("200",(char *)&(gPB[datapos+9]),3) != 0; 588 | (*client_browser_cb)(f, ((uint16_t)TCP_SRC_PORT_H_P+(gPB[TCP_HEADER_LEN_P]>>4)*4),len_of_data); 589 | } 590 | return 0; 591 | } 592 | 593 | void EtherCard::browseUrl (const char *urlbuf, const char *urlbuf_varpart, const char *hoststr, void (*callback)(uint8_t,uint16_t,uint16_t)) { 594 | browseUrl(urlbuf, urlbuf_varpart, hoststr, PSTR("Accept: text/html"), callback); 595 | } 596 | 597 | void EtherCard::browseUrl (const char *urlbuf, const char *urlbuf_varpart, const char *hoststr, const char *additionalheaderline, void (*callback)(uint8_t,uint16_t,uint16_t)) { 598 | client_urlbuf = urlbuf; 599 | client_urlbuf_var = urlbuf_varpart; 600 | client_hoststr = hoststr; 601 | client_additionalheaderline = additionalheaderline; 602 | client_postval = 0; 603 | client_browser_cb = callback; 604 | www_fd = clientTcpReq(&www_client_internal_result_cb,&www_client_internal_datafill_cb,hisport); 605 | } 606 | 607 | void EtherCard::httpPost (const char *urlbuf, const char *hoststr, const char *additionalheaderline, const char *postval, void (*callback)(uint8_t,uint16_t,uint16_t)) { 608 | client_urlbuf = urlbuf; 609 | client_hoststr = hoststr; 610 | client_additionalheaderline = additionalheaderline; 611 | client_postval = postval; 612 | client_browser_cb = callback; 613 | www_fd = clientTcpReq(&www_client_internal_result_cb,&www_client_internal_datafill_cb,hisport); 614 | } 615 | 616 | static uint16_t tcp_datafill_cb(uint8_t fd) { 617 | uint16_t len = Stash::length(); 618 | Stash::extract(0, len, EtherCard::tcpOffset()); 619 | Stash::cleanup(); 620 | EtherCard::tcpOffset()[len] = 0; 621 | #if SERIAL 622 | Serial.print("REQUEST: "); 623 | Serial.println(len); 624 | Serial.println((char*) EtherCard::tcpOffset()); 625 | #endif 626 | result_fd = 123; // bogus value 627 | return len; 628 | } 629 | 630 | static uint8_t tcp_result_cb(uint8_t fd, uint8_t status, uint16_t datapos, uint16_t datalen) { 631 | if (status == 0) { 632 | result_fd = fd; // a valid result has been received, remember its session id 633 | result_ptr = (char*) ether.buffer + datapos; 634 | // result_ptr[datalen] = 0; 635 | } 636 | return 1; 637 | } 638 | 639 | uint8_t EtherCard::tcpSend () { 640 | www_fd = clientTcpReq(&tcp_result_cb, &tcp_datafill_cb, hisport); 641 | return www_fd; 642 | } 643 | 644 | const char* EtherCard::tcpReply (uint8_t fd) { 645 | if (result_fd != fd) 646 | return 0; 647 | result_fd = 123; // set to a bogus value to prevent future match 648 | return result_ptr; 649 | } 650 | 651 | void EtherCard::registerPingCallback (void (*callback)(uint8_t *srcip)) { 652 | icmp_cb = callback; 653 | } 654 | 655 | uint8_t EtherCard::packetLoopIcmpCheckReply (const uint8_t *ip_monitoredhost) { 656 | return gPB[IP_PROTO_P]==IP_PROTO_ICMP_V && 657 | gPB[ICMP_TYPE_P]==ICMP_TYPE_ECHOREPLY_V && 658 | gPB[ICMP_DATA_P]== PINGPATTERN && 659 | check_ip_message_is_from(ip_monitoredhost); 660 | } 661 | 662 | uint16_t EtherCard::accept(const uint16_t port, uint16_t plen) { 663 | uint16_t pos; 664 | 665 | if (gPB[TCP_DST_PORT_H_P] == (port >> 8) && 666 | gPB[TCP_DST_PORT_L_P] == ((uint8_t) port)) 667 | { //Packet targetted at specified port 668 | if (gPB[TCP_FLAGS_P] & TCP_FLAGS_SYN_V) 669 | make_tcp_synack_from_syn(); //send SYN+ACK 670 | else if (gPB[TCP_FLAGS_P] & TCP_FLAGS_ACK_V) 671 | { //This is an acknowledgement to our SYN+ACK so let's start processing that payload 672 | info_data_len = getTcpPayloadLength(); 673 | if (info_data_len > 0) 674 | { //Got some data 675 | pos = TCP_DATA_START; // TCP_DATA_START is a formula 676 | //!@todo no idea what this check pos<=plen-8 does; changed this to pos<=plen as otw. perfectly valid tcp packets are ignored; still if anybody has any idea please leave a comment 677 | if (pos <= plen) 678 | return pos; 679 | } 680 | else if (gPB[TCP_FLAGS_P] & TCP_FLAGS_FIN_V) 681 | make_tcp_ack_from_any(0,0); //No data so close connection 682 | } 683 | } 684 | return 0; 685 | } 686 | 687 | uint16_t EtherCard::packetLoop (uint16_t plen) { 688 | uint16_t len; 689 | 690 | #if ETHERCARD_DHCP 691 | if(using_dhcp) { 692 | ether.DhcpStateMachine(plen); 693 | } 694 | #endif 695 | 696 | if (plen==0) { 697 | //Check every 65536 (no-packet) cycles whether we need to retry ARP request for gateway 698 | if ((waitgwmac & WGW_INITIAL_ARP || waitgwmac & WGW_REFRESHING) && 699 | delaycnt==0 && isLinkUp()) { 700 | client_arp_whohas(gwip); 701 | waitgwmac |= WGW_ACCEPT_ARP_REPLY; 702 | } 703 | delaycnt++; 704 | 705 | #if ETHERCARD_TCPCLIENT 706 | //Initiate TCP/IP session if pending 707 | if (tcp_client_state==TCP_STATE_SENDSYN && (waitgwmac & WGW_HAVE_GW_MAC)) { // send a syn 708 | tcp_client_state = TCP_STATE_SYNSENT; 709 | tcpclient_src_port_l++; // allocate a new port 710 | client_syn(((tcp_fd<<5) | (0x1f & tcpclient_src_port_l)),tcp_client_port_h,tcp_client_port_l); 711 | } 712 | #endif 713 | 714 | //!@todo this is trying to find mac only once. Need some timeout to make another call if first one doesn't succeed. 715 | if(is_lan(myip, dnsip) && !has_dns_mac && !waiting_for_dns_mac) { 716 | client_arp_whohas(dnsip); 717 | waiting_for_dns_mac = true; 718 | } 719 | 720 | //!@todo this is trying to find mac only once. Need some timeout to make another call if first one doesn't succeed. 721 | if(is_lan(myip, hisip) && !has_dest_mac && !waiting_for_dest_mac) { 722 | client_arp_whohas(hisip); 723 | waiting_for_dest_mac = true; 724 | } 725 | 726 | return 0; 727 | } 728 | 729 | if (eth_type_is_arp_and_my_ip(plen)) 730 | { //Service ARP request 731 | if (gPB[ETH_ARP_OPCODE_L_P]==ETH_ARP_OPCODE_REQ_L_V) 732 | make_arp_answer_from_request(); 733 | if (waitgwmac & WGW_ACCEPT_ARP_REPLY && (gPB[ETH_ARP_OPCODE_L_P]==ETH_ARP_OPCODE_REPLY_L_V) && client_store_mac(gwip, gwmacaddr)) 734 | waitgwmac = WGW_HAVE_GW_MAC; 735 | if (!has_dns_mac && waiting_for_dns_mac && client_store_mac(dnsip, destmacaddr)) { 736 | has_dns_mac = true; 737 | waiting_for_dns_mac = false; 738 | } 739 | if (!has_dest_mac && waiting_for_dest_mac && client_store_mac(hisip, destmacaddr)) { 740 | has_dest_mac = true; 741 | waiting_for_dest_mac = false; 742 | } 743 | return 0; 744 | } 745 | 746 | if (eth_type_is_ip_and_my_ip(plen)==0) 747 | { //Not IP so ignoring 748 | //!@todo Add other protocols (and make each optional at compile time) 749 | return 0; 750 | } 751 | 752 | #if ETHERCARD_ICMP 753 | if (gPB[IP_PROTO_P]==IP_PROTO_ICMP_V && gPB[ICMP_TYPE_P]==ICMP_TYPE_ECHOREQUEST_V) 754 | { //Service ICMP echo request (ping) 755 | if (icmp_cb) 756 | (*icmp_cb)(&(gPB[IP_SRC_P])); 757 | make_echo_reply_from_request(plen); 758 | return 0; 759 | } 760 | #endif 761 | #if ETHERCARD_UDPSERVER 762 | if (ether.udpServerListening() && gPB[IP_PROTO_P]==IP_PROTO_UDP_V) 763 | { //Call UDP server handler (callback) if one is defined for this packet 764 | if(ether.udpServerHasProcessedPacket(plen)) 765 | return 0; //An UDP server handler (callback) has processed this packet 766 | } 767 | #endif 768 | 769 | if (plen<54 || gPB[IP_PROTO_P]!=IP_PROTO_TCP_V ) 770 | return 0; //from here on we are only interested in TCP-packets; these are longer than 54 bytes 771 | 772 | #if ETHERCARD_TCPCLIENT 773 | if (gPB[TCP_DST_PORT_H_P]==TCPCLIENT_SRC_PORT_H) 774 | { //Source port is in range reserved (by EtherCard) for client TCP/IP connections 775 | if (check_ip_message_is_from(hisip)==0) 776 | return 0; //Not current TCP/IP connection (only handle one at a time) 777 | if (gPB[TCP_FLAGS_P] & TCP_FLAGS_RST_V) 778 | { //TCP reset flagged 779 | if (client_tcp_result_cb) 780 | (*client_tcp_result_cb)((gPB[TCP_DST_PORT_L_P]>>5)&0x7,3,0,0); 781 | tcp_client_state = TCP_STATE_CLOSING; 782 | return 0; 783 | } 784 | len = getTcpPayloadLength(); 785 | if (tcp_client_state==TCP_STATE_SYNSENT) 786 | { //Waiting for SYN-ACK 787 | if ((gPB[TCP_FLAGS_P] & TCP_FLAGS_SYN_V) && (gPB[TCP_FLAGS_P] &TCP_FLAGS_ACK_V)) 788 | { //SYN and ACK flags set so this is an acknowledgement to our SYN 789 | make_tcp_ack_from_any(0,0); 790 | gPB[TCP_FLAGS_P] = TCP_FLAGS_ACK_V|TCP_FLAGS_PUSH_V; 791 | if (client_tcp_datafill_cb) 792 | len = (*client_tcp_datafill_cb)((gPB[TCP_SRC_PORT_L_P]>>5)&0x7); 793 | else 794 | len = 0; 795 | tcp_client_state = TCP_STATE_ESTABLISHED; 796 | make_tcp_ack_with_data_noflags(len); 797 | } 798 | else 799 | { //Expecting SYN+ACK so reset and resend SYN 800 | tcp_client_state = TCP_STATE_SENDSYN; // retry 801 | len++; 802 | if (gPB[TCP_FLAGS_P] & TCP_FLAGS_ACK_V) 803 | len = 0; 804 | make_tcp_ack_from_any(len,TCP_FLAGS_RST_V); 805 | } 806 | return 0; 807 | } 808 | if (tcp_client_state==TCP_STATE_ESTABLISHED && len>0) 809 | { //TCP connection established so read data 810 | if (client_tcp_result_cb) { 811 | uint16_t tcpstart = TCP_DATA_START; // TCP_DATA_START is a formula 812 | if (tcpstart>plen-8) 813 | tcpstart = plen-8; // dummy but save 814 | uint16_t save_len = len; 815 | if (tcpstart+len>plen) 816 | save_len = plen-tcpstart; 817 | (*client_tcp_result_cb)((gPB[TCP_DST_PORT_L_P]>>5)&0x7,0,tcpstart,save_len); //Call TCP handler (callback) function 818 | 819 | if(persist_tcp_connection) 820 | { //Keep connection alive by sending ACK 821 | make_tcp_ack_from_any(len,TCP_FLAGS_PUSH_V); 822 | } 823 | else 824 | { //Close connection 825 | make_tcp_ack_from_any(len,TCP_FLAGS_PUSH_V|TCP_FLAGS_FIN_V); 826 | tcp_client_state = TCP_STATE_CLOSED; 827 | } 828 | return 0; 829 | } 830 | } 831 | if (tcp_client_state != TCP_STATE_CLOSING) 832 | { // 833 | if (gPB[TCP_FLAGS_P] & TCP_FLAGS_FIN_V) { 834 | if(tcp_client_state == TCP_STATE_ESTABLISHED) { 835 | return 0; // In some instances FIN is received *before* DATA. If that is the case, we just return here and keep looking for the data packet 836 | } 837 | make_tcp_ack_from_any(len+1,TCP_FLAGS_PUSH_V|TCP_FLAGS_FIN_V); 838 | tcp_client_state = TCP_STATE_CLOSED; // connection terminated 839 | } else if (len>0) { 840 | make_tcp_ack_from_any(len,0); 841 | } 842 | } 843 | return 0; 844 | } 845 | #endif 846 | 847 | #if ETHERCARD_TCPSERVER 848 | //If we are here then this is a TCP/IP packet targetted at us and not related to our client connection so accept 849 | return accept(hisport, plen); 850 | #endif 851 | } 852 | 853 | void EtherCard::persistTcpConnection(bool persist) { 854 | persist_tcp_connection = persist; 855 | } 856 | -------------------------------------------------------------------------------- /timer.cpp: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | #include "artnet.h" 4 | #include "dmx.h" 5 | 6 | #include 7 | #include 8 | 9 | 10 | //Different counters 11 | volatile uint16_t timer01hzCounter = 0; 12 | volatile uint8_t timer022HZCounter = 0; 13 | 14 | volatile uint32_t millisCounter = 0; 15 | 16 | 17 | void setupTimer0(void) { 18 | 19 | //################# Timer0 - 1Khz 20 | TCC0.CTRLA = TC_CLKSEL_DIV1024_gc; // Presacler 1024 21 | TCC0.CTRLB = 0x00; // select Modus: Normal 22 | TCC0.PER = 32; // Zähler Top-Wert 1000 Hz 23 | TCC0.CNT = 0x00; // Zähler zurücksetzen 24 | TCC0.INTCTRLA = 0b00000011; // Interrupt Highlevel 25 | } 26 | 27 | 28 | uint32_t millis(void) { 29 | return millisCounter; 30 | } 31 | 32 | ISR(TCC0_OVF_vect) 33 | { 34 | timer01hzCounter++; 35 | millisCounter++; 36 | 37 | if(timer01hzCounter >= 1000) { 38 | timer01hzCounter = 0; 39 | artnet.doTimerStuff1Hz(); //Art-Net 40 | } 41 | 42 | timer022HZCounter++; 43 | if(timer022HZCounter >= 23) { 44 | timer022HZCounter = 0; 45 | startDMX_TX(); //Reicht? TODO: Timer, 44mal/sec 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /timer.h: -------------------------------------------------------------------------------- 1 | #ifndef _TIMER_H 2 | #define _TIMER_H 1 3 | 4 | #include 5 | 6 | 7 | extern void setupTimer0(void); 8 | 9 | extern uint32_t millis(void); 10 | 11 | #endif -------------------------------------------------------------------------------- /udpserver.cpp: -------------------------------------------------------------------------------- 1 | // Simple UDP listening server 2 | // 3 | // Author: Brian Lee 4 | // 5 | // Copyright: GPL V2 6 | // See http://www.gnu.org/licenses/gpl.html 7 | 8 | #include "EtherCard.h" 9 | #include "net.h" 10 | 11 | #define gPB ether.buffer 12 | 13 | #define UDPSERVER_MAXLISTENERS 8 //the maximum number of port listeners. 14 | 15 | typedef struct { 16 | UdpServerCallback callback; 17 | uint16_t port; 18 | bool listening; 19 | } UdpServerListener; 20 | 21 | UdpServerListener listeners[UDPSERVER_MAXLISTENERS]; 22 | uint8_t numListeners = 0; 23 | 24 | void EtherCard::udpServerListenOnPort(UdpServerCallback callback, uint16_t port) { 25 | if(numListeners < UDPSERVER_MAXLISTENERS) 26 | { 27 | listeners[numListeners] = (UdpServerListener) { 28 | callback, port, true 29 | }; 30 | numListeners++; 31 | } 32 | } 33 | 34 | void EtherCard::udpServerPauseListenOnPort(uint16_t port) { 35 | for(uint8_t i = 0; i < numListeners; i++) 36 | { 37 | if(gPB[UDP_DST_PORT_H_P] == (listeners[i].port >> 8) && gPB[UDP_DST_PORT_L_P] == ((uint8_t) listeners[i].port)) { 38 | listeners[i].listening = false; 39 | } 40 | } 41 | } 42 | 43 | void EtherCard::udpServerResumeListenOnPort(uint16_t port) { 44 | for(uint8_t i = 0; i < numListeners; i++) 45 | { 46 | if(gPB[UDP_DST_PORT_H_P] == (listeners[i].port >> 8) && gPB[UDP_DST_PORT_L_P] == ((uint8_t) listeners[i].port)) { 47 | listeners[i].listening = true; 48 | } 49 | } 50 | } 51 | 52 | bool EtherCard::udpServerListening() { 53 | return numListeners > 0; 54 | } 55 | 56 | bool EtherCard::udpServerHasProcessedPacket(uint16_t plen) { 57 | bool packetProcessed = false; 58 | for(uint8_t i = 0; i < numListeners; i++) 59 | { 60 | if(gPB[UDP_DST_PORT_H_P] == (listeners[i].port >> 8) && gPB[UDP_DST_PORT_L_P] == ((uint8_t) listeners[i].port) && listeners[i].listening) 61 | { 62 | uint16_t datalen = (uint16_t) (gPB[UDP_LEN_H_P] << 8) + gPB[UDP_LEN_L_P] - UDP_HEADER_LEN; 63 | listeners[i].callback( 64 | listeners[i].port, 65 | gPB + IP_SRC_P, 66 | (gPB[UDP_SRC_PORT_H_P] << 8) | gPB[UDP_SRC_PORT_L_P], 67 | (const char *) (gPB + UDP_DATA_P), 68 | datalen); 69 | packetProcessed = true; 70 | } 71 | } 72 | return packetProcessed; 73 | } 74 | -------------------------------------------------------------------------------- /webutil.cpp: -------------------------------------------------------------------------------- 1 | // Some common utilities needed for IP and web applications 2 | // Author: Guido Socher 3 | // Copyright: GPL V2 4 | // 5 | // 2010-05-20 6 | 7 | #include "EtherCard.h" 8 | #include //TODO: Copy needed function? 9 | #include //TODO: Dito 10 | 11 | void EtherCard::copyIp (uint8_t *dst, const uint8_t *src) { 12 | memcpy(dst, src, IP_LEN); 13 | } 14 | 15 | void EtherCard::copyMac (uint8_t *dst, const uint8_t *src) { 16 | memcpy(dst, src, ETH_LEN); 17 | } 18 | 19 | // search for a string of the form key=value in 20 | // a string that looks like q?xyz=abc&uvw=defgh HTTP/1.1\r\n 21 | // 22 | // The returned value is stored in strbuf. You must allocate 23 | // enough storage for strbuf, maxlen is the size of strbuf. 24 | // I.e the value it is declated with: strbuf[5]-> maxlen=5 25 | uint8_t EtherCard::findKeyVal (const char *str,char *strbuf, uint8_t maxlen,const char *key) 26 | { 27 | bool found = false; 28 | uint8_t i=0; 29 | const char *kp; 30 | kp=key; 31 | while(*str && *str!=' ' && *str!='\n' && !found) { 32 | if (*str == *kp) { 33 | kp++; 34 | if (*kp == '\0') { 35 | str++; 36 | kp=key; 37 | if (*str == '=') { 38 | found = true; 39 | } 40 | } 41 | } else { 42 | kp=key; 43 | } 44 | str++; 45 | } 46 | if (found) { 47 | // copy the value to a buffer and terminate it with '\0' 48 | while(*str && *str!=' ' && *str!='\n' && *str!='&' && i9) { 98 | hstr[1]=(c & 0xf) - 10 + 'a'; 99 | } 100 | c=(c>>4)&0xf; 101 | hstr[0]=c+'0'; 102 | if (c > 9) { 103 | hstr[0]=c - 10 + 'a'; 104 | } 105 | hstr[2]='\0'; 106 | } 107 | 108 | // there must be enough space in urlbuf. In the worst case that is 109 | // 3 times the length of str 110 | void EtherCard::urlEncode (char *str,char *urlbuf) 111 | { 112 | char c; 113 | while ((c = *str) != 0) { 114 | if (c == ' '||isalnum(c)) { 115 | if (c == ' ') { 116 | c = '+'; 117 | } 118 | *urlbuf=c; 119 | str++; 120 | urlbuf++; 121 | continue; 122 | } 123 | *urlbuf='%'; 124 | urlbuf++; 125 | int2h(c,urlbuf); 126 | urlbuf++; 127 | urlbuf++; 128 | str++; 129 | } 130 | *urlbuf='\0'; 131 | } 132 | 133 | // parse a string and extract the IP to bytestr 134 | uint8_t EtherCard::parseIp (uint8_t *bytestr,char *str) 135 | { 136 | char *sptr; 137 | uint8_t i=0; 138 | sptr=0; 139 | while(i