1. Trang chủ
  2. » Công Nghệ Thông Tin

Practical Arduino Cool Projects for Open Source Hardware- P14 pps

10 481 0

Đang tải... (xem toàn văn)

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 10
Dung lượng 99,69 KB

Nội dung

CHAPTER 7  ONLINE THERMOMETER #include "etherShield.h" // Modify the following two lines to suit your local network // configuration. The MAC and IP address have to be unique on your LAN: static uint8_t myMac[6] = {0x54,0x55,0x58,0x10,0x00,0x24}; static uint8_t myIp[4] = {192,168,1,15}; static char baseurl[] = "http://192.168.1.15/"; static uint16_t myPort = 80; // Listen port for tcp/www (range 1-254) It’s essential that the MAC address setting be unique on your network, so if you have multiple Arduinos connected you could decrement the last value (0x24) for each subsequent device. Make sure you keep track of what value you’ve assigned to each one. Likewise, the IP address needs to be unique on your network, and you need to enter it twice: once as the four elements in the myIp array and then as a string in baseurl. These values are different because the TCP/IP stack needs to use the actual local IP address in the headers, but the web page that is created also needs to display a form that submits to itself, and the address used in your browser may be different to the actual IP address if your device is behind a NAT (network address translation) device such as as firewall. If you want to expose your device externally you should set the actual local IP address in the myIp variable, and set the external address in the baseurl variable. The myPort value can be changed if you want your device to listen on a port other than the default port 80. However, if you change it make sure you also change the baseurl variable to include the port number or the form won’t work. For example, if you wanted to listen on port 81, you could change those lines to the following: static char baseurl[] = "http://192.168.1.15:81/"; static uint16_t myPort = 81; // Listen port for tcp/www (range 1-254) The program then creates two buffers used in creating the TCP/IP packet, and creates an instance of the EtherShield object called “es” as follows: // Set up variables for the TCP/IP buffer #define BUFFER_SIZE 500 static uint8_t buf[BUFFER_SIZE+1]; #define STR_BUFFER_SIZE 22 static char strbuf[STR_BUFFER_SIZE+1]; // Create an instance of the EtherShield object named "es" EtherShield es=EtherShield(); // Prepare the web page by writing the data to the TCP send buffer uint16_t print_webpage(uint8_t *buf); int8_t analyse_cmd(char *str); We’ve set up our shield with six connectors for temperature sensors, so the program defines which digital I/O lines to use for sensors A through F as follows: // Specify data pins for connected DS18B20 temperature sensors #define SENSOR_A 3 #define SENSOR_B 4 #define SENSOR_C 5 #define SENSOR_D 6 #define SENSOR_E 7 #define SENSOR_F 8 109 CHAPTER 7  ONLINE THERMOMETER The setup function does some setup of the Ethernet connection, and then sets all the sensor data pins to be inputs as follows: void setup() { /*initialize enc28j60*/ es.ES_enc28j60Init(myMac); // Change clkout from 6.25MHz to 12.5MHz es.ES_enc28j60clkout(2); delay(10); /* Magjack leds configuration, see enc28j60 datasheet, page 11 */ // LEDA=green LEDB=yellow // 0x880 is PHLCON LEDB=on, LEDA=on es.ES_enc28j60PhyWrite(PHLCON, 0x880); delay(500); // 0x990 is PHLCON LEDB=off, LEDA=off es.ES_enc28j60PhyWrite(PHLCON, 0x990); delay(500); // 0x880 is PHLCON LEDB=on, LEDA=on es.ES_enc28j60PhyWrite(PHLCON, 0x880); delay(500); // 0x990 is PHLCON LEDB=off, LEDA=off es.ES_enc28j60PhyWrite(PHLCON, 0x990); delay(500); // 0x476 is PHLCON LEDA=links status, LEDB=receive/transmit es.ES_enc28j60PhyWrite(PHLCON, 0x476); delay(100); //init the ethernet/ip layer: es.ES_init_ip_arp_udp_tcp(myMac, myIp, myPort); // Set up the data pins for communication with DS18B20 sensors digitalWrite(SENSOR_A, LOW); pinMode(SENSOR_A, INPUT); digitalWrite(SENSOR_B, LOW); pinMode(SENSOR_B, INPUT); digitalWrite(SENSOR_C, LOW); pinMode(SENSOR_C, INPUT); digitalWrite(SENSOR_D, LOW); pinMode(SENSOR_D, INPUT); digitalWrite(SENSOR_E, LOW); pinMode(SENSOR_E, INPUT); digitalWrite(SENSOR_F, LOW); pinMode(SENSOR_F, INPUT); } The main program loop is quite complex because it checks the Ethernet receive buffer each time through and manages the appropriate responses. The interesting part is toward the end where it performs three checks on the request to determine if it needs to respond with the web page. The first check is for a request to the base URL, and if that matches it calls print_webpage() to generate the default page containing sensor data. The second and third checks are both done using a call to analyse_cmd(), which processes the request header to find the value of the cmd variable passed through in the request. If the value is 1 it 110 CHAPTER 7  ONLINE THERMOMETER responds with the default page once again. This check is not strictly necessary because if you only ever wanted your program to return the page with the sensor data on it, you won’t care what arguments are passed through: you want your Arduino to always just respond with the same page. However, we’ve included it in this example because it demonstrates how you can create a crude navigation system and have your Arduino return different pages depending on what argument has been sent through. In the example it does this by checking for a cmd value of 2, in which case it knows the user is specifically requesting the About page rather than the page with sensor data and makes a call to print_webpage_about() instead of print_webpage(). You could define a whole bunch of pages and store them in your program, then use identifiers to load different pages on demand. Or alternatively you could modify the print_webpage() function so that it only returns the value from one sensor, and have it use a value submitted by the user to determine which sensor to process. That way, you could connect to a URL such as http://192.168.1.15/?cmd=4, to access the data for sensor 4. Just remember the restriction on page size and don’t try to create the next big CMS (content management system) on your Arduino! void loop(){ uint16_t plen, dat_p; int8_t cmd; plen = es.ES_enc28j60PacketReceive(BUFFER_SIZE, buf); /*plen will ne unequal to zero if there is a valid packet (without crc error) */ if(plen!=0) { // arp is broadcast if unknown but a host may also verify the mac address by sending it to a unicast address. if (es.ES_eth_type_is_arp_and_my_ip (buf,plen)) { es.ES_make_arp_answer_from_request (buf); return; } // check if ip packets are for us: if (es.ES_eth_type_is_ip_and_my_ip (buf,plen) == 0) { return; } if (buf[IP_PROTO_P]==IP_PROTO_ICMP_V && buf[ICMP_TYPE_P]==ICMP_TYPE_ECHOREQUEST_V) { es.ES_make_echo_reply_from_request (buf,plen); return; } // tcp port www start, compare only the lower byte if (buf[IP_PROTO_P] == IP_PROTO_TCP_V && buf[TCP_DST_PORT_H_P] == 0 && buf[TCP_DST_PORT_L_P] == myPort) { if (buf[TCP_FLAGS_P] & TCP_FLAGS_SYN_V) { es.ES_make_tcp_synack_from_syn (buf); // make_tcp_synack_from_syn does already send the syn,ack return; } if (buf[TCP_FLAGS_P] & TCP_FLAGS_ACK_V) { es.ES_init_len_info (buf); // init some data structures dat_p = es.ES_get_tcp_data_pointer(); if (dat_p==0) { // we can possibly have no data, just ack: if (buf[TCP_FLAGS_P] & TCP_FLAGS_FIN_V) { es.ES_make_tcp_ack_from_any (buf); } 111 CHAPTER 7  ONLINE THERMOMETER return; } if (strncmp ("GET ", (char *) & (buf[dat_p]), 4) != 0) { // head, post, and other methods for possible status codes see: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html plen = es.ES_fill_tcp_data_p (buf,0,PSTR ("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>200 OK</h1>")); goto SENDTCP; } if (strncmp("/ ", (char *) & (buf[dat_p+4]), 2) == 0){ plen = print_webpage (buf); goto SENDTCP; } cmd = analyse_cmd ((char *) & (buf[dat_p+5])); if (cmd == 1){ plen = print_webpage (buf); // Send the "Data" page } if (cmd == 2){ plen = print_webpage_about (buf); // Send the "About" page } SENDTCP: es.ES_make_tcp_ack_from_any(buf); // send ack for http get es.ES_make_tcp_ack_with_data(buf,plen); // send data } } } } The next function is for doing what C does worst—text processing. It’s a utility function called a little later in the program to process the arguments passed through in the HTTP request, as follows: // The returned value is stored in the global var strbuf uint8_t find_key_val (char *str,char *key) { uint8_t found = 0; uint8_t i = 0; char *kp; kp = key; while (*str && *str!=' ' && found==0) { if (*str == *kp) { kp++; if (*kp == '\0') { str++; kp=key; if (*str == '=') { found = 1; } } } else { kp = key; } str++; } if (found == 1) { // copy the value to a buffer and terminate it with '\0' 112 CHAPTER 7  ONLINE THERMOMETER while (*str && *str!=' ' && *str!='&' && i<STR_BUFFER_SIZE) { strbuf[i]=*str; i++; str++; } strbuf[i]='\0'; } return(found); } Next is the function that calls it. The analyse_cmd() function looks specifically for a request argument called “cmd,” and returns the value if it’s a number. If you wanted to pass another argument through to your device and have it process them both (such as one argument for the page and the other argument for the device ID), you could make another version of this function to process the other argument: int8_t analyse_cmd (char *str) { int8_t r = -1; if (find_key_val (str,"cmd")) { if (*strbuf < 0x3a && *strbuf > 0x2f) { // is a ASCII number, return it r = (*strbuf-0x30); } } return r; } The function to print the web page with the sensor data is long, but that’s only because it’s very repetitive; the structure of the function is quite simple. Before any HTML purists look at what is being returned and start complaining about things such as lack of a doctype declaration or even basic things such as HTML body tags, we should point out that the page defined in this function is designed to be absolutely minimal in size rather than technically correct. The reality is that in 500 or so bytes it’s simply not possible to return a fully formed HTML page including all the headers and the content required, so we’ve taken more than a few shortcuts that would have us on trial for crimes against RFCs if we tried to pass this off as a valid web page. It gets the job done, though, which is what matters. The function first defines a set of arrays to hold the values returned by each sensor, as well as variables for a counter (to be used a little later in the function) and for the packet length. If you want to expand this project to support more sensors you would need to define more arrays to suit. uint16_t print_webpage (uint8_t *buf) { // Arrays to hold the temperature reading from each sensor char temp_string_a[10]; char temp_string_b[10]; char temp_string_c[10]; char temp_string_d[10]; char temp_string_e[10]; char temp_string_f[10]; int i; // Counter used while iterating over reading arrays uint16_t plen; // Length of response packet It then reads all the temperature sensors. This can safely be done even if you don’t have six sensors connected because it will simply return 0.0 degrees for any unconnected channels. // Read all the temperature sensors getCurrentTemp(SENSOR_A, temp_string_a); 113 CHAPTER 7  ONLINE THERMOMETER getCurrentTemp(SENSOR_B, temp_string_b); getCurrentTemp(SENSOR_C, temp_string_c); getCurrentTemp(SENSOR_D, temp_string_d); getCurrentTemp(SENSOR_E, temp_string_e); getCurrentTemp(SENSOR_F, temp_string_f); Then the function pushes the HTTP response header into the packet buffer as follows: // Send HTTP content-type header plen = es.ES_fill_tcp_data_p (buf, 0, PSTR ("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n")); Now for the bit that actually sends you the value you’re interested in. The packet buffer has the string "Sensor A:" pushed into it, then the counter that was defined earlier is used to loop through the value array for this particular sensor and push each element into the buffer. While that is happening the packet length variable, plen, is incremented so it will correctly reflect the number of characters in the buffer. Finally, an HTML <br /> tag is appended so each row of data will appear on a separate line, and the whole shebang is repeated for each of the six sensor channels. // Read sensor A plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("Sensor A:")); i=0; while (temp_string_a[i]) { buf[TCP_CHECKSUM_L_P+3+plen]=temp_string_a[i++]; plen++; } plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("<br />")); // Read sensor B plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("Sensor B:")); i=0; while (temp_string_b[i]) { buf[TCP_CHECKSUM_L_P+3+plen]=temp_string_b[i++]; plen++; } plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("<br />")); // Read sensor C plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("Sensor C:")); i=0; while (temp_string_c[i]) { buf[TCP_CHECKSUM_L_P+3+plen]=temp_string_c[i++]; plen++; } plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("<br />")); // Read sensor D plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("Sensor D:")); i=0; while (temp_string_d[i]) { buf[TCP_CHECKSUM_L_P+3+plen]=temp_string_d[i++]; plen++; } plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("<br />")); // Read sensor E 114 CHAPTER 7  ONLINE THERMOMETER plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("Sensor E:")); i=0; while (temp_string_e[i]) { buf[TCP_CHECKSUM_L_P+3+plen]=temp_string_e[i++]; plen++; } plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("<br />")); // Read sensor F plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("Sensor F:")); i=0; while (temp_string_f[i]) { buf[TCP_CHECKSUM_L_P+3+plen]=temp_string_f[i++]; plen++; } plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("<br />")); At the end of the page we append two HTML forms to display navigation buttons. The first form causes a hidden input field, cmd, to be submitted with a value of 1, while the second does almost the exact same thing but submits a value of 2. // Display a form button to update the display plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("<form METHOD=get action=\"")); plen = es.ES_fill_tcp_data (buf, plen, baseurl); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("\">")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR("<input type=hidden name=cmd value=1>")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR("<input type=submit value=\"Data\">")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR("</form>")); // Display a form button to access the "About" page plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("<form METHOD=get action=\"")); plen = es.ES_fill_tcp_data (buf, plen, baseurl); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("\">")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR("<input type=hidden name=cmd value=2>")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR("<input type=submit value=\"About\">")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR("</form>")); return (plen); } The function to generate the About page is similarto the previous function but is much simpler. Rather than reading the temperature sensors, it simply sends back some static text followed by the navigation links. /** * Generate a web page containing the "About" text */ uint16_t print_webpage_about (uint8_t *buf) { uint16_t plen; // Length of response packet // Send HTTP content-type header plen = es.ES_fill_tcp_data_p (buf, 0, PSTR ("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n")); // Display the text for the "About" page plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("<h1>Online Thermometer v1.0</h1>")); 115 CHAPTER 7  ONLINE THERMOMETER plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("As featured in Practical Arduino.<br />")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("See <a href=\"http://practicalarduino.com\">practicalarduino.com</a> for more info.")); // Display a form button to update the display plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("<form METHOD=get action=\"")); plen = es.ES_fill_tcp_data (buf, plen, baseurl); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("\">")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR("<input type=hidden name=cmd value=1>")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR("<input type=submit value=\"Data\">")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR("</form>")); // Display a form button to access the "About" page plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("<form METHOD=get action=\"")); plen = es.ES_fill_tcp_data (buf, plen, baseurl); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("\">")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR("<input type=hidden name=cmd value=2>")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR("<input type=submit value=\"About\">")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR("</form>")); return (plen); } The next few functions are utility functions for managing the communications with the DS18B20 sensors using the Dallas 1-wire bus. These functions use a technique called “bit-banging” to send data to the communications pin or read data from it. Bit-banging requires the main processor to use software timing to literally send and receive every individual bit directly on a serial port, which is obviously quite inefficient compared to handing the job off to dedicated hardware. Serial communications is usually offloaded from the main processor to dedicated hardware, such as a USART (universal synchronous/asynchronous receiver/transmitter), that handles all the timing issues and buffers data transparently so the main processor can just send it bytes to be transmitted and then get on with other things, or read data from the USART’s buffer even if it arrived while the processor was busy. A USART acts as a messaging service for the main processor, sending out messages on request and holding incoming messages until the CPU is ready to deal with them. Arduino boards do have at least one USART but it’s tied up managing serial communications via the USB connection on pins 0 and 1, and even the Arduino Mega only has a total of four USARTs including the one used for the USB connection. It is possible to add more USARTs externally but that can be expensive and complicated, and since we’re only dealing with low-volume, low-speed communications, it makes sense to resort to bit-banging instead in this case. The big benefit of bit-banging in this project is that it allows us to use any digital I/O pin we like as a serial communications port simply by switching it high and low with the correct timing. If you’re curious about the details of how bit-banging works you can find out a lot more in the SoftSerial Arduino library, which uses bit-banging to let you turn any digital pin into a communications port. There’s also more information about bit-banging on Wikipedia at en.wikipedia.org/wiki/Bit- banging./** */ void OneWireReset(int Pin) // reset. Should improve to act as a presence pulse { digitalWrite(Pin, LOW); pinMode(Pin, OUTPUT); // bring low for 500 us delayMicroseconds(500); 116 CHAPTER 7  ONLINE THERMOMETER pinMode(Pin, INPUT); delayMicroseconds(500); } /** */ void OneWireOutByte(int Pin, byte d) // output byte d (least sig bit first). { byte n; for (n=8; n!=0; n ) { if ((d & 0x01) == 1) // test least sig bit { digitalWrite(Pin, LOW); pinMode(Pin, OUTPUT); delayMicroseconds(5); pinMode(Pin, INPUT); delayMicroseconds(60); } else { digitalWrite(Pin, LOW); pinMode(Pin, OUTPUT); delayMicroseconds(60); pinMode(Pin, INPUT); } d = d>>1; // now the next bit is in the least sig bit position. } } /** */ byte OneWireInByte(int Pin) // read byte, least sig byte first { byte d, n, b; for (n=0; n<8; n++) { digitalWrite (Pin, LOW); pinMode (Pin, OUTPUT); delayMicroseconds (5); pinMode (Pin, INPUT); delayMicroseconds (5); b = digitalRead (Pin); delayMicroseconds (50); d = (d >> 1) | (b<<7); // shift d to right and insert b in most sig bit position } return (d); } The function to actually read a value from one of the temperature sensors uses the previous utility functions to send commands out the appropriate pin and receive the response. This function works by 117 CHAPTER 7  ONLINE THERMOMETER populating the received value into an array passed in by the calling function, which is a sneaky way of getting around the problem that a function can’t simply return an array directly. /** * Read temperature from a DS18B20. * int sensorPin: Arduino digital I/O pin connected to sensor * char *temp: global array to be populated with current reading */ void getCurrentTemp (int sensorPin, char *temp) { int HighByte, LowByte, TReading, Tc_100, sign, whole, fract; OneWireReset (sensorPin); OneWireOutByte (sensorPin, 0xcc); OneWireOutByte (sensorPin, 0x44); // Perform temperature conversion, strong pullup for one sec OneWireReset (sensorPin); OneWireOutByte (sensorPin, 0xcc); OneWireOutByte (sensorPin, 0xbe); LowByte = OneWireInByte (sensorPin); HighByte = OneWireInByte (sensorPin); TReading = (HighByte << 8) + LowByte; sign = TReading & 0x8000; // test most sig bit if (sign) // negative { TReading = (TReading ^ 0xffff) + 1; // 2's complement } Tc_100 = (6 * TReading) + TReading / 4; // multiply by (100 * 0.0625) or 6.25 whole = Tc_100 / 100; // separate off the whole and fractional portions fract = Tc_100 % 100; if (sign) { temp[0] = '-'; } else { temp[0] = '+'; } if (whole/100 == 0) { temp[1] = ' '; } else { temp[1] = whole/100+'0'; } temp[2] = (whole-(whole/100)*100)/10 +'0' ; temp[3] = whole-(whole/10)*10 +'0'; temp[4] = '.'; temp[5] = fract/10 +'0'; temp[6] = fract-(fract/10)*10 +'0'; temp[7] = '\0'; } It looks like a large, complicated program, but most of that is because of the TCP/IP details included in the program rather than concealed by the library. The actual logical structure of the program is relatively simple. After you’ve compiled and uploaded the program to your Arduino, plug it into a spare Ethernet connection on your network, open up a web browser, and open the address you configured in the baseurl variable (http://192.168.1.15/). 118 . in Practical Arduino. <br />")); plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("See <a href="http://practicalarduino.com">practicalarduino.com</a> for. shield with six connectors for temperature sensors, so the program defines which digital I/O lines to use for sensors A through F as follows: // Specify data pins for connected DS18B20 temperature. (buf, plen, PSTR("</form>")); // Display a form button to access the "About" page plen = es.ES_fill_tcp_data_p (buf, plen, PSTR ("<form METHOD=get action=""));

Ngày đăng: 03/07/2014, 20:20

TỪ KHÓA LIÊN QUAN