nes-proj/platform/avr-raven/apps/raven-lcd-interface/raven-lcd.c
Cristiano De Alti d2528caa85 Implement battery and temperature sensors.
On the raven, the battery and temperature readings are available
from the companion 3290 cpu over the serial line.
Modify the existing raven-lcd-interface application to export
these sensors.
2015-09-21 22:45:57 +02:00

529 lines
17 KiB
C

/* 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>
* \author Cristiano De Alti <cristiano_dealti@hotmail.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"
#if AVR_WEBSERVER
#include "webserver-nogui.h"
#include "httpd-cgi.h"
#endif
#include "raven-lcd.h"
#include "lib/sensors.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
static int battery_value;
static int temperature_value;
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)
{
#define PING_GOOGLE 0
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();
}
/*---------------------------------------------------------------------------*/
/* 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);
}
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 CLOCK_CONF_SECOND 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 */
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
/*---------------------------------------------------------------------------*/
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);
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:
#if AVR_WEBSERVER
/* Set temperature string in web server */
web_set_temp((char *)cmd.frame);
#endif
temperature_value = atoi((char *)cmd.frame);
break;
case SEND_ADC2:
#if AVR_WEBSERVER
/* Set ext voltage string in web server */
web_set_voltage((char *)cmd.frame);
#endif
battery_value = atoi((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. */
#if UIP_CONF_TCP
/* Only sleep this cycle if no active TCP/IP connections, for fast browser responsed */
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;
}
#endif
}
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 AVR_WEBSERVER
static void
lcd_show_servername(void) {
//extern uint8_t eemem_mac_address[8]; //These are defined in httpd-fsdata.c via makefsdata.h
extern uint8_t eemem_server_name[16];
//extern uint8_t eemem_domain_name[30];
char buf[sizeof(eemem_server_name)+1];
eeprom_read_block (buf,eemem_server_name, sizeof(eemem_server_name));
buf[sizeof(eemem_server_name)]=0;
raven_lcd_show_text(buf); //must fit in all the buffers or it will be truncated!
}
#endif
/*---------------------------------------------------------------------------*/
int
value_temperature(int type) {
return temperature_value;
}
int
value_battery(int type) {
return battery_value;
}
/*---------------------------------------------------------------------------*/
int
configure(int type, int value) {
/* prevent compiler warnings */
return type = value = 1;
}
/*---------------------------------------------------------------------------*/
int
status(int type) {
switch(type) {
case SENSORS_ACTIVE:
case SENSORS_READY:
return 1;
}
return 0;
}
/*---------------------------------------------------------------------------*/
SENSORS_SENSOR(temperature_sensor, "Temperature",
value_temperature, configure, status);
SENSORS_SENSOR(battery_sensor, "Battery",
value_battery, configure, status);
/*---------------------------------------------------------------------------*/
PROCESS(raven_lcd_process, "Raven LCD interface process");
PROCESS_THREAD(raven_lcd_process, ev, data)
{
PROCESS_BEGIN();
#if AVR_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();
}
/** @} */
/** @} */