/* Copyright (c) 2008, Swedish Institute of Computer Science * All rights reserved. * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ /** \addtogroup raven * @{ */ /** * \defgroup ravenserial Serial interface between Raven processors * * \brief This module contains code to interface a Contiki-based * project on the AVR Raven platform's ATMega1284P chip to the LCD * driver chip (ATMega3290P) on the Raven. * * \author Blake Leverett <bleverett@gmail.com> * * @{ */ /** * \file * This file contains code to connect the two AVR Raven processors via a serial connection. * */ #define DEBUG 0 //Making this 1 will slightly alter command timings #if DEBUG #define PRINTF(FORMAT,args...) printf_P(PSTR(FORMAT),##args) #else #define PRINTF(...) #endif #define DEBUGSERIAL 0 //Making this 1 will significantly alter command timings #include "contiki.h" #include "contiki-lib.h" #include "contiki-net.h" #include "webserver-nogui.h" #include "httpd-cgi.h" #include "raven-lcd.h" #include <string.h> #include <stdio.h> #include <avr/pgmspace.h> #include <avr/eeprom.h> #include <avr/sleep.h> #include <dev/watchdog.h> static uint8_t count = 0; static uint8_t seqno; uip_ipaddr_t dest_addr; #define MAX_CMD_LEN 20 static struct{ uint8_t frame[MAX_CMD_LEN]; uint8_t ndx; uint8_t len; uint8_t cmd; uint8_t done; } cmd; #define UIP_IP_BUF ((struct uip_ip_hdr *)&uip_buf[UIP_LLH_LEN]) #define UIP_ICMP_BUF ((struct uip_icmp_hdr *)&uip_buf[uip_l2_l3_hdr_len]) #define PING6_DATALEN 16 void rs232_send(uint8_t port, unsigned char c); /*---------------------------------------------------------------------------*/ /* Sends a ping packet out the radio */ /* Useful for debugging so allow external calls */ void raven_ping6(void) { #if UIP_CONF_IPV6_RPL||1 /* No default router, so pick on someone else */ #define PING_GOOGLE 1 seqno++; #endif UIP_IP_BUF->vtc = 0x60; UIP_IP_BUF->tcflow = 1; UIP_IP_BUF->flow = 0; UIP_IP_BUF->proto = UIP_PROTO_ICMP6; UIP_IP_BUF->ttl = uip_ds6_if.cur_hop_limit; #if PING_GOOGLE if (seqno==1) { uip_ipaddr_copy(&UIP_IP_BUF->destipaddr, uip_ds6_defrt_choose()); //the default router } else if (seqno==2) { uip_ip6addr(&UIP_IP_BUF->destipaddr,0x2001,0x4860,0x800f,0x0000,0x0000,0x0000,0x0000,0x0093); //ipv6.google.com } else if (seqno==3) { uip_ipaddr_copy(&UIP_IP_BUF->destipaddr, uip_ds6_defrt_choose()); //the default router } else { // uip_ip6addr(&UIP_IP_BUF->destipaddr,0x2001,0x0420,0x5FFF,0x007D,0x02D0,0xB7FF,0xFE23,0xE6DB); //?.cisco.com uip_ip6addr(&UIP_IP_BUF->destipaddr,0x2001,0x0420,0x0000,0x0010,0x0250,0x8bff,0xfee8,0xf800); //six.cisco.com } #else uip_ipaddr_copy(&UIP_IP_BUF->destipaddr, uip_ds6_defrt_choose()); //the default router #endif uip_ds6_select_src(&UIP_IP_BUF->srcipaddr, &UIP_IP_BUF->destipaddr); UIP_ICMP_BUF->type = ICMP6_ECHO_REQUEST; UIP_ICMP_BUF->icode = 0; /* set identifier and sequence number to 0 */ memset((void *)UIP_ICMP_BUF + UIP_ICMPH_LEN, 0, 4); /* put one byte of data */ memset((void *)UIP_ICMP_BUF + UIP_ICMPH_LEN + UIP_ICMP6_ECHO_REQUEST_LEN, count, PING6_DATALEN); uip_len = UIP_ICMPH_LEN + UIP_ICMP6_ECHO_REQUEST_LEN + UIP_IPH_LEN + PING6_DATALEN; UIP_IP_BUF->len[0] = (uint8_t)((uip_len - 40) >> 8); UIP_IP_BUF->len[1] = (uint8_t)((uip_len - 40) & 0x00FF); UIP_ICMP_BUF->icmpchksum = 0; UIP_ICMP_BUF->icmpchksum = ~uip_icmp6chksum(); tcpip_ipv6_output(); } #if defined(__AVR_ATmega128RFA1__) /* No raven 3290p to talk to but the lcd process still needed for ping reporting */ /* Just dummy out send_frame */ static void send_frame(uint8_t cmd, uint8_t len, uint8_t *payload) { } #else /*---------------------------------------------------------------------------*/ /* Send a serial command frame to the ATMega3290 Processsor on Raven via serial port */ static void send_frame(uint8_t cmd, uint8_t len, uint8_t *payload) { uint8_t i; rs232_send(0, SOF_CHAR); /* Start of Frame */ rs232_send(0, len); rs232_send(0, cmd); for (i=0;i<len;i++) rs232_send(0,*payload++); rs232_send(0, EOF_CHAR); } #endif char serial_char_received; /*---------------------------------------------------------------------------*/ /* Sleep for howlong seconds, or until UART interrupt if howlong==0. * Uses TIMER2 with external 32768 Hz crystal to sleep in 1 second multiples. * TIMER2 may have already been set up for 125 ticks/second in clock.c * * Until someone figures out how to get UART to wake from powerdown, * a three second powersave cycle is used with exit based on any character received. * The system clock is adjusted to reflect the sleep time. */ void micro_sleep(uint8_t howlong) { uint8_t saved_sreg = SREG, saved_howlong = howlong; #if AVR_CONF_USE32KCRYSTAL /* Save TIMER2 configuration if clock.c is using it */ uint8_t savedTCNT2=TCNT2, savedTCCR2A=TCCR2A, savedTCCR2B = TCCR2B, savedOCR2A = OCR2A; #endif // if (howlong==0) { // set_sleep_mode(SLEEP_MODE_PWR_DOWN); // UART can't wake from powerdown // } else { set_sleep_mode(SLEEP_MODE_PWR_SAVE); // Sleep for howlong seconds if (howlong==0) howlong=3; // 3*32/(32768/1024) = 3 second sleep cycle if not specified cli(); // Disable interrupts for the present ASSR |= (1 << AS2); // Set TIMER2 asyncronous from external crystal TCCR2A =(1<<WGM21); // CTC mode TCCR2B =((1<<CS22)|(1<<CS21)|(1<<CS20));// Prescale by 1024 = 32 ticks/sec // while(ASSR & (1 << TCR2BUB)); // Wait for TCNT2 write to finish. OCR2A = howlong*32; // Set TIMER2 output compare register // while(ASSR & (1 << OCR2AUB)); // Wait for OCR2 write to finish. SREG = saved_sreg; // Restore interrupt state. // UCSR(USART,B)&= ~(1<<RXCIE(USART)) // Disable the RX Complete interrupt; // UCSR0B|=(1<<RXCIE0); // Enable UART0 RX complete interrupt // UCSR1B|=(1<<RXCIE1); // Enable UART1 RX complete interrupt // TCNT2 = 0; // Reset TIMER2 timer counter value. // while(ASSR & (1 << TCN2UB)); // Wait for TCNT2 write to finish before entering sleep. // TIMSK2 |= (1 << OCIE2A); // Enable TIMER2 output compare interrupt. // } TCNT2 = 0; // Reset timer watchdog_stop(); // Silence annoying distractions while(ASSR & (1 << TCN2UB)); // Wait for TCNT2 write to (which assures TCCR2x and OCR2A are finished!) TIMSK2 |= (1 << OCIE2A); // Enable TIMER2 output compare interrupt SMCR |= (1 << SE); // Enable sleep mode. while (1) { // TCNT2 = 0; // Cleared automatically in CTC mode // while(ASSR & (1 << TCN2UB)); // Wait for TCNT2 write to finish before entering sleep. serial_char_received=0; // Set when chars received by UART sleep_mode(); // Sleep /* Adjust clock.c for the time spent sleeping */ extern void clock_adjust_ticks(uint16_t howmany); clock_adjust_ticks(howlong * CLOCK_SECOND); // if (TIMSK2&(1<<OCIE2A)) break; // Exit sleep if not awakened by TIMER2 PRINTF("."); if (saved_howlong) break; // Exit sleep if nonzero time specified PRINTF("%d",serial_char_received); if (serial_char_received) break; } SMCR &= ~(1 << SE); //Disable sleep mode after wakeup #if AVR_CONF_USE32KCRYSTAL /* Restore clock.c configuration */ // OCRSetup(); cli(); TCCR2A = savedTCCR2A; TCCR2B = savedTCCR2B; OCR2A = savedOCR2A; TCNT2 = savedTCNT2; sei(); #else TIMSK2 &= ~(1 << OCIE2A); //Disable TIMER2 interrupt #endif watchdog_start(); } #if !AVR_CONF_USE32KCRYSTAL /*---------------------------------------------------------------------------*/ /* TIMER2 Interrupt service */ ISR(TIMER2_COMPA_vect) { // TIMSK2 &= ~(1 << OCIE2A); //Just one interrupt needed for waking } #endif /* !AVR_CONF_USE32KCRYSTAL */ #if DEBUGSERIAL uint8_t serialcount; char dbuf[30]; #endif /*---------------------------------------------------------------------------*/ extern uint16_t ledtimer; static uint8_t raven_gui_loop(process_event_t ev, process_data_t data) { uint8_t i,activeconnections,radio_state; // PRINTF("\nevent %d ",ev); #if DEBUGSERIAL printf_P(PSTR("Buffer [%d]="),serialcount); serialcount=0; for (i=0;i<30;i++) { printf_P(PSTR(" %d"),dbuf[i]); dbuf[i]=0; } #endif if(ev == tcpip_icmp6_event) switch(*((uint8_t *)data)) { // case ICMP6_NS: /*Tell the 3290 we are being solicited. */ // send_frame(REPORT_NS,...); // break; //fall through for beep // case ICMP6_RA: /*Tell the 3290 we have a router. */ // send_frame(REPORT_NA,...); // break; //fall through for beep case ICMP6_ECHO_REQUEST: /* We have received a ping request over the air. Tell the 3290 */ // send_frame(REPORT_PING_BEEP, 0, 0); #if RF230BB_CONF_LEDONPORTE1 PORTE|=(1<<PE1);ledtimer=1000; //turn on led, set counter for turnoff #endif break; case ICMP6_ECHO_REPLY: /* We have received a ping reply over the air. Send frame back to 3290 */ // send_frame(REPORT_PING, 1, &seqno); break; } else switch (ev) { case SERIAL_CMD: /* Check for command from serial port, execute it. */ /* Note cmd frame is written in an interrupt - delays here can cause overwriting by next command */ PRINTF("\nCommand %d length %d done %d",cmd.cmd,cmd.len,cmd.done); if (cmd.done){ /* Execute the waiting command */ switch (cmd.cmd){ case SEND_PING: /* Send ping request over the air */ seqno = cmd.frame[0]; raven_ping6(); break; case SEND_TEMP: /* Set temperature string in web server */ // web_set_temp((char *)cmd.frame); break; case SEND_ADC2: /* Set ext voltage string in web server */ // web_set_voltage((char *)cmd.frame); break; case SEND_SLEEP: /* Sleep radio and 1284p. */ if (cmd.frame[0]==0) { //Time to sleep in seconds /* Unconditional sleep. Don't wake until a serial interrupt. */ } else { /* Sleep specified number of seconds (3290p "DOZE" mode) */ /* It sleeps a bit longer so we will be always be awake for the next sleep command. */ /* Only sleep this cycle if no active TCP/IP connections */ activeconnections=0; for(i = 0; i < UIP_CONNS; ++i) { if((uip_conns[i].tcpstateflags & UIP_TS_MASK) != UIP_CLOSED) activeconnections++; } if (activeconnections) { PRINTF("\nWaiting for %d connections",activeconnections); break; } } radio_state = NETSTACK_RADIO.off(); PRINTF ("\nsleep %d radio state %d...",cmd.frame[0],radio_state); /*Sleep for specified time*/ PRINTF("\nSleeping..."); micro_sleep(cmd.frame[0]); radio_state = NETSTACK_RADIO.on(); if (radio_state > 0) { PRINTF("Awake!"); } else { PRINTF("Radio wake error %d\n",radio_state); } break; case SEND_WAKE: /* 3290p requests return message showing awake status */ send_frame(REPORT_WAKE, 0, 0); break; default: break; } /* Reset command done flag. */ cmd.done = 0; } break; default: break; } return 0; } /*---------------------------------------------------------------------------*/ /* Process an input character from serial port. * ** This is called from an ISR!! */ int raven_lcd_serial_input(unsigned char ch) { /* Tell sleep routine if a reception occurred */ /* Random nulls occur for some reason, so ignore those */ if (ch) serial_char_received++; #if DEBUGSERIAL if (serialcount<25) dbuf[serialcount]=ch; serialcount++; #endif /* Don't overwrite an unprocessed command */ // if (cmd.done) return 0; /* Parse frame, */ switch (cmd.ndx){ case 0: /* first byte, must be 0x01 */ if (ch == 0x01){ // cmd.done = false; } else { #if DEBUGSERIAL dbuf[25]++; #endif return 0; } break; case 1: /* Second byte, length of payload */ cmd.len = ch; break; case 2: /* Third byte, command byte */ cmd.cmd = ch; break; default: /* Payload and ETX */ if (cmd.ndx >= (MAX_CMD_LEN+3)) { //buffer overflow! cmd.ndx=0; #if DEBUGSERIAL dbuf[26]++; #endif return 0; } if (cmd.ndx >= cmd.len+3){ /* all done, check ETX */ if (ch == 0x04){ cmd.done = 1; #if DEBUGSERIAL dbuf[27]++; #endif process_post(&raven_lcd_process, SERIAL_CMD, 0); } else { /* Failed ETX */ #if DEBUGSERIAL dbuf[28]++; #endif } cmd.ndx=0; //set up for next command return 0; } else { /* Just grab and store payload */ cmd.frame[cmd.ndx - 3] = ch; } break; } cmd.ndx++; return 0; } /*---------------------------------------------------------------------------*/ void raven_lcd_show_text(char *text) { uint8_t textlen=strlen(text)+1; if (textlen > MAX_CMD_LEN) textlen=MAX_CMD_LEN; send_frame(REPORT_TEXT_MSG, textlen, (uint8_t *) text); } #if WEBSERVER static void lcd_show_servername(void) { //extern uint8_t mac_address[8]; //These are defined in httpd-fsdata.c via makefsdata.h extern uint8_t server_name[16]; //extern uint8_t domain_name[30]; char buf[sizeof(server_name)+1]; eeprom_read_block (buf,server_name, sizeof(server_name)); buf[sizeof(server_name)]=0; raven_lcd_show_text(buf); //must fit in all the buffers or it will be truncated! } #endif /*---------------------------------------------------------------------------*/ PROCESS(raven_lcd_process, "Raven LCD interface process"); PROCESS_THREAD(raven_lcd_process, ev, data) { PROCESS_BEGIN(); #if WEBSERVER lcd_show_servername(); #endif /* Get ICMP6 callbacks from uip6 stack, perform 3290p action on pings, responses, etc. */ if(icmp6_new(NULL) == 0) { while(1) { PROCESS_YIELD(); // if (ev != ?) //trap frequent strobes? raven_gui_loop(ev, data); } } PROCESS_END(); } /** @} */ /** @} */