ChemE
Well-Known Member
Xively feed from this code These charts might not be recording data at any given moment as I test new code
Earlier this year I became interested in FuzzeWuzze's excellent guide to a Real Time Online Fermentation Temperature Monitor for ~$60 which I bought the parts for and implemented. However, I noticed that once I got the code working which really wasn't doing all that much, it was consuming a very large portion of the space on the microcontroller. How much space you might ask? 21,244 bytes out of 32,256 for the sketch size (code) and 740 bytes out of 2,040 of SRAM. So the code which took two temperature readings and pushed the data up to Xively (Cosm back then) was taking up 66% of my microprocessor. A very big part of this code footprint was due to the inclusion of several libraries which make these Arduinos very easy to get started with namely OneWire, DallasTemperature, SPI, and Ethernet.
I then became interested in compacting my code and lifting out only those parts of the libraries mentioned above that were required to make the project work. I wound up learning how the OneWire protocol works (you are just turning a pin high and low in a timed sequence in order to send ones and zeros down the line), how to talk directly to the DS18B20s that we use, and how to talk directly to the W5100 that runs the ethernet shield. My code is certainly not basic although it is decently commented nor is it compact; the code itself is 450 lines. What it is however is extraordinarily compact when compiled. The code below takes only 2,444 bytes of sketch size; a reduction of 88.5% as compared to the original easy code and 118 bytes of SRAM; a reduction of 84% as compared to the easy code. This leaves well over 90% of the microprocessor's memory free for other cool things you might want to do in a project. This won't be for everyone but for anyone who is starting to run out of space on their Arduino it should be a breath of fresh air.
Earlier this year I became interested in FuzzeWuzze's excellent guide to a Real Time Online Fermentation Temperature Monitor for ~$60 which I bought the parts for and implemented. However, I noticed that once I got the code working which really wasn't doing all that much, it was consuming a very large portion of the space on the microcontroller. How much space you might ask? 21,244 bytes out of 32,256 for the sketch size (code) and 740 bytes out of 2,040 of SRAM. So the code which took two temperature readings and pushed the data up to Xively (Cosm back then) was taking up 66% of my microprocessor. A very big part of this code footprint was due to the inclusion of several libraries which make these Arduinos very easy to get started with namely OneWire, DallasTemperature, SPI, and Ethernet.
I then became interested in compacting my code and lifting out only those parts of the libraries mentioned above that were required to make the project work. I wound up learning how the OneWire protocol works (you are just turning a pin high and low in a timed sequence in order to send ones and zeros down the line), how to talk directly to the DS18B20s that we use, and how to talk directly to the W5100 that runs the ethernet shield. My code is certainly not basic although it is decently commented nor is it compact; the code itself is 450 lines. What it is however is extraordinarily compact when compiled. The code below takes only 2,444 bytes of sketch size; a reduction of 88.5% as compared to the original easy code and 118 bytes of SRAM; a reduction of 84% as compared to the easy code. This leaves well over 90% of the microprocessor's memory free for other cool things you might want to do in a project. This won't be for everyone but for anyone who is starting to run out of space on their Arduino it should be a breath of fresh air.
Code:
// This sketch assumes two DS18B20s are on pin 3 and are not being operated in parasite mode. Place your sensor ROMS on
// lines 14 and 15 and if your sensors are plugged into a different pin, adjust line 8 to match the pin you use
//
// ======================================================= Includes =======================================================
#include <util/delay.h> // Using _delay_ms(#) rather than delay(#) saves 146 bytes of sketch size!
// ========================================= User-Specific Definitions ========================================== //
#define Pin PD3 // Must choose a pin on port D (Digital pins 2-7)
#define Sensor1 "Carboy_1_Temperature"
#define Sensor2 "Ambient_Temperature"
#define FEEDID "102607"
#define APIKEY "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
#define USERAGENT "Test"
const uint8_t Sensor[2][8] = {{ 0x28, 0xE0, 0xF7, 0x83, 0x04, 0x00, 0x00, 0x0F }, // Thermowell sensor
{ 0x28, 0x68, 0xEF, 0x7D, 0x04, 0x00, 0x00, 0x62 }}; // Ambient room air sensor
#define MYDELAY 5000-67 // Code execution takes 67 milliseconds
#define DEBUG 0 // Controls the inclusion or exclusion of serial debug information; 0 for no serial output 1 for serial output
#if DEBUG
unsigned long startClock, endClock;
#endif
// ===================================================== OneWire Definitions ======================================================
#define DIRECT_MODE_OUTPUT DDRD |= _BV(Pin)
#define DIRECT_MODE_INPUT DDRD &= ~_BV(Pin)
#define DIRECT_WRITE_HIGH PORTD |= _BV(Pin)
#define DIRECT_WRITE_LOW PORTD &= ~_BV(Pin)
#define DIRECT_READ PIND & _BV(Pin)
#define STARTCONVO 0x44 // Tells device to take a temperature reading and put it on the scratchpad
#define READSCRATCH 0xBE // Read EEPROM
#define SKIPROM 0xCC
#define CHOOSEROM 0x55
#define TEMP_LSB 0
#define TEMP_MSB 1
// ================================================= W5100 Definitions =====================================================================
// AVRJazz Mega328 SPI I/O
#define SPI_PORT PORTB
#define SPI_DDR DDRB
#define SPI_CS PORTB2
// Wiznet W5100 Op Code
#define WIZNET_WRITE_OPCODE 0xF0
#define WIZNET_READ_OPCODE 0x0F
// Wiznet W5100 Register Addresses
#define MR 0x0000 // Mode Register
#define GAR 0x0001 // Gateway Address: 0x0001 to 0x0004
#define SUBR 0x0005 // Subnet mask Address: 0x0005 to 0x0008
#define SAR 0x0009 // Source Hardware Address (MAC): 0x0009 to 0x000E
#define SIPR 0x000F // Source IP Address: 0x000F to 0x0012
#define RMSR 0x001A // RX Memory Size Register
#define TMSR 0x001B // TX Memory Size Register
#define S0_MR 0x0400 // Socket 0: Mode Register Address
#define S0_CR 0x0401 // Socket 0: Command Register Address
#define S0_IR 0x0402 // Socket 0: Interrupt Register Address
#define S0_SR 0x0403 // Socket 0: Status Register Address
#define S0_DIPR 0x040C // Socket 0: Destination IP Address: 0x040C to 0x040F
#define S0_DPORT 0x0410 // Socket 0: Destination Port: 0x0410 to 0x0411
#define S0_PORT 0x0404 // Socket 0: Source Port: 0x0404 to 0x0405
#define SO_TX_FSR 0x0420 // Socket 0: Tx Free Size Register: 0x0420 to 0x0421
#define S0_TX_RD 0x0422 // Socket 0: Tx Read Pointer Register: 0x0422 to 0x0423
#define S0_TX_WR 0x0424 // Socket 0: Tx Write Pointer Register: 0x0424 to 0x0425
#define S0_RX_RSR 0x0426 // Socket 0: Rx Received Size Pointer Register: 0x0425 to 0x0427
#define S0_RX_RD 0x0428 // Socket 0: Rx Read Pointer: 0x0428 to 0x0429
#define TXBUFADDR 0x4000 // W5100 Send Buffer Base Address
#define RXBUFADDR 0x6000 // W5100 Read Buffer Base Address
// S0_MR values
#define MR_CLOSE 0x00 // Unused socket
#define MR_TCP 0x01 // TCP
// S0_CR values
#define CR_OPEN 0x01 // Initialize or open socket
#define CR_CONNECT 0x04 // Send connection request in tcp mode(Client mode)
#define CR_DISCON 0x08 // Send closing reqeuset in tcp mode
#define CR_CLOSE 0x10 // Close socket
#define CR_SEND 0x20 // Update Tx memory pointer and send data
// S0_SR values
#define SOCK_INIT 0x13 // Init state
#define SOCK_ESTABLISHED 0x17 // Success to connect
#define TX_BUF_MASK 0x07FF // Tx 2K Buffer Mask:
#define RX_BUF_MASK 0x07FF // Rx 2K Buffer Mask:
#define NET_MEMALLOC 0x05 // Use 2K of Tx/Rx Buffer
#define TCP_PORT 80 // TCP/IP Port
#define MAX_BUF 210
// =================================================================== Begin W5100 Functions ===================================================================
static inline void SPI_Write(uint16_t addr,uint8_t data) {
SPI_PORT &= ~(1<<SPI_CS); // Activate the CS pin
SPDR = WIZNET_WRITE_OPCODE; // Start Wiznet W5100 Write OpCode transmission
while(!(SPSR & (1<<SPIF))); // Wait for transmission complete
SPDR = (addr & 0xFF00) >> 8; // Start Wiznet W5100 Address High Bytes transmission
while(!(SPSR & (1<<SPIF))); // Wait for transmission complete
SPDR = addr & 0x00FF; // Start Wiznet W5100 Address Low Bytes transmission
while(!(SPSR & (1<<SPIF))); // Wait for transmission complete
SPDR = data; // Start Data transmission
while(!(SPSR & (1<<SPIF))); // Wait for transmission complete
SPI_PORT |= (1<<SPI_CS); // CS pin is not active
}
static inline unsigned char SPI_Read(uint16_t addr) {
SPI_PORT &= ~(1<<SPI_CS); // Activate the CS pin
SPDR = WIZNET_READ_OPCODE; // Start Wiznet W5100 Read OpCode transmission
while(!(SPSR & (1<<SPIF))); // Wait for transmission complete
SPDR = (addr & 0xFF00) >> 8; // Start Wiznet W5100 Address High Bytes transmission
while(!(SPSR & (1<<SPIF))); // Wait for transmission complete
SPDR = addr & 0x00FF; // Start Wiznet W5100 Address Low Bytes transmission
while(!(SPSR & (1<<SPIF))); // Wait for transmission complete
SPDR = 0x00; // Send Dummy transmission for reading the data
while(!(SPSR & (1<<SPIF))); // Wait for transmission complete
SPI_PORT |= (1<<SPI_CS); // CS pin is not active
return(SPDR);
}
static inline void W5100_Init(void)
{
// Ethernet Setup
const unsigned char mac_addr[] = {0x00, 0x1D, 0x0D, 0x2C, 0x55, 0x3D};
const unsigned char ip_addr[] = {192,168,11,8};
const unsigned char sub_mask[] = {255,255,255,0};
const unsigned char gtw_addr[] = {192,168,11,1};
const unsigned char cosm_addr[] = {216,52,233,121};
// Setting the Wiznet W5100 Mode Register: 0x0000
SPI_Write(MR,0x80); // MR = 0b10000000;
// Setting the Wiznet W5100 Gateway Address (GAR): 0x0001 to 0x0004
SPI_Write(GAR + 0,gtw_addr[0]);
SPI_Write(GAR + 1,gtw_addr[1]);
SPI_Write(GAR + 2,gtw_addr[2]);
SPI_Write(GAR + 3,gtw_addr[3]);
// Setting the Wiznet W5100 Source Address Register (SAR): 0x0009 to 0x000E
SPI_Write(SAR + 0,mac_addr[0]);
SPI_Write(SAR + 1,mac_addr[1]);
SPI_Write(SAR + 2,mac_addr[2]);
SPI_Write(SAR + 3,mac_addr[3]);
SPI_Write(SAR + 4,mac_addr[4]);
SPI_Write(SAR + 5,mac_addr[5]);
// Setting the Wiznet W5100 Sub Mask Address (SUBR): 0x0005 to 0x0008
SPI_Write(SUBR + 0,sub_mask[0]);
SPI_Write(SUBR + 1,sub_mask[1]);
SPI_Write(SUBR + 2,sub_mask[2]);
SPI_Write(SUBR + 3,sub_mask[3]);
// Setting the Wiznet W5100 IP Address (SIPR): 0x000F to 0x0012
SPI_Write(SIPR + 0,ip_addr[0]);
SPI_Write(SIPR + 1,ip_addr[1]);
SPI_Write(SIPR + 2,ip_addr[2]);
SPI_Write(SIPR + 3,ip_addr[3]);
// Setting the Wiznet W5100 RX and TX Memory Size (2KB),
SPI_Write(RMSR,NET_MEMALLOC);
SPI_Write(TMSR,NET_MEMALLOC);
// Setting the Wiznet to connect to the Cosm server port 80
SPI_Write(S0_DIPR + 0,cosm_addr[0]);
SPI_Write(S0_DIPR + 1,cosm_addr[1]);
SPI_Write(S0_DIPR + 2,cosm_addr[2]);
SPI_Write(S0_DIPR + 3,cosm_addr[3]);
SPI_Write(S0_DPORT,((TCP_PORT & 0xFF00) >> 8 ));
SPI_Write(S0_DPORT + 1,(TCP_PORT & 0x00FF));
SPI_Write(S0_PORT,((1 & 0xFF00) >> 8 )); // Set the source port to 1
SPI_Write(S0_PORT + 1,(1 & 0x00FF)); // Set the source port to 1
// Set the Wiznet to use TCP
SPI_Write(S0_MR,MR_TCP); // Set the mode to TCP
}
static inline void Connect(void) {
SPI_Write(S0_CR,CR_OPEN);
while(SPI_Read(S0_CR));
uint16_t retries=1000;
while(SPI_Read(S0_SR)!=SOCK_INIT) {;
// Could get stuck here forever!!! ================================================================================================================================
// Break out and cancel data point upload if we spend more than 1 second waiting for the sock to initialize
if(!--retries) return;
_delay_ms(1);
}
SPI_Write(S0_CR,CR_CLOSE); // Send Close Command
while(SPI_Read(S0_CR)); // Waiting until the S0_CR is clear
SPI_Write(S0_MR,MR_TCP);
SPI_Write(S0_CR,CR_CONNECT); // Open Socket
while(SPI_Read(S0_CR)); // Wait for Opening Process
}
static inline uint16_t send(const uint8_t *buf,uint16_t buflen) {
uint16_t ptr,offaddr,realaddr,txsize,timeout;
if (buflen <= 0) return 0;
// Make sure the TX Free Size Register is available
txsize=SPI_Read(SO_TX_FSR);
txsize=(((txsize & 0x00FF) << 8 ) + SPI_Read(SO_TX_FSR + 1));
timeout=0;
while (txsize < buflen) {
_delay_ms(1);
txsize=SPI_Read(SO_TX_FSR);
txsize=(((txsize & 0x00FF) << 8 ) + SPI_Read(SO_TX_FSR + 1));
// Timeout for approx 1000 ms
if (timeout++ > 1000) {
// Disconnect the connection
SPI_Write(S0_CR,CR_DISCON); // Send Disconnect Command
while(SPI_Read(S0_CR)); // Wait for Disconecting Process
return 0;
}
}
// Read the Tx Write Pointer
ptr = SPI_Read(S0_TX_WR);
offaddr = (((ptr & 0x00FF) << 8 ) + SPI_Read(S0_TX_WR + 1));
while(buflen) {
buflen--;
// Calculate the real W5100 physical Tx Buffer Address
realaddr = TXBUFADDR + (offaddr & TX_BUF_MASK);
// Copy the application data to the W5100 Tx Buffer
SPI_Write(realaddr,*buf);
offaddr++;
buf++;
}
// Increase the S0_TX_WR value, so it point to the next transmit
SPI_Write(S0_TX_WR,(offaddr & 0xFF00) >> 8 );
SPI_Write(S0_TX_WR + 1,(offaddr & 0x00FF));
// Now Send the SEND command
SPI_Write(S0_CR,CR_SEND);
// Wait for Sending Process
while(SPI_Read(S0_CR));
return 1;
}
// =================================================================== Begin OneWire Functions ===================================================================
static inline uint8_t read() {
uint8_t r=0;
noInterrupts();
for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) {
DIRECT_MODE_OUTPUT;
DIRECT_WRITE_LOW;
_delay_us(3);
DIRECT_MODE_INPUT;
_delay_us(10);
if (DIRECT_READ) r |= bitMask;
_delay_us(53);
}
interrupts();
return r;
}
static inline void write(uint8_t v) {
noInterrupts();
for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) {
DIRECT_WRITE_LOW;
DIRECT_MODE_OUTPUT;
if (bitMask & v) {
_delay_us(10);
DIRECT_WRITE_HIGH;
_delay_us(55);
} else {
_delay_us(65);
DIRECT_WRITE_HIGH;
_delay_us(5);
}
}
DIRECT_MODE_INPUT;
DIRECT_WRITE_LOW;
interrupts();
}
static inline void reset(void) {
uint8_t retries = 125;
noInterrupts();
DIRECT_MODE_INPUT;
// wait until the wire is high... just in case
while (!DIRECT_READ) {
if (--retries == 0) break;
_delay_us(2);
}
DIRECT_WRITE_LOW;
DIRECT_MODE_OUTPUT;
_delay_us(500);
DIRECT_MODE_INPUT;
_delay_us(80);
retries = !DIRECT_READ;
interrupts();
_delay_us(420);
}
// =================================================================== End OneWire Functions ===================================================================
// =================================================================== Begin DS18B20 Functions ===================================================================
static inline int16_t ReadSensor(const uint8_t sensorIndex) {
uint8_t scratchPad[2];
reset();
write(0x55); // Choose ROM
for(uint8_t i = 0; i < 8; i++) write(Sensor[sensorIndex][i]);
write(READSCRATCH);
scratchPad[TEMP_LSB] = read();
scratchPad[TEMP_MSB] = read();
return (((int16_t)scratchPad[TEMP_MSB]) << 8) | scratchPad[TEMP_LSB];
}
// Converts the raw temperature from a DS18B20 directly to a string containing the temperature in °F with 4 decimal places
// avoids unnecessary floating point math, float variables, and casts
// TODO: May not work properly with temperatures below 32°F
static inline void Tiny_RawTempToString(uint32_t raw, char * buffer) {
uint8_t decimalPos=2; // default case of a temp between 0 and 99.9
uint8_t nullPos=7; // default case of a temp between 0 and 99.9
raw=raw*1125ul+320000ul; // Hold the temperature
if(raw>1000000ul) { // We're looking at a positive number with three digits
decimalPos=3;
nullPos=8;
}
for(int8_t i=nullPos;i>-1;i--) {
if (i==decimalPos) {
buffer[i]='.';
}
else if (i==nullPos) {
buffer[i]='\0';
}
else {
buffer[i]=raw%10 + '0';
raw/=10;
}
}
}
// =================================================================== End DS18B20 Functions ===================================================================
// Super cheater itoa that only accepts 0-255; only appropriate for the length of the csv data
void tiny_itoa(uint8_t inp, char * buf) {
if (inp>=0 && inp<=9) {
buf[0]=inp + '0';
buf[1]='\0';
}
else if (inp<100) {
buf[1]=inp%10 + '0';
inp/=10;
buf[0]=inp%10 + '0';
buf[2]='\0';
}
}
static inline void BuildPut(uint16_t T1, uint16_t T2, uint8_t * buf) {
// Convert raw temperatures into strings
char Temp1[8];
char Temp2[8];
Tiny_RawTempToString(T1, (char *)Temp1);
Tiny_RawTempToString(T2, (char *)Temp2);
// Set up data buffer to hold the csv data
char * dataBuffer[strlen(Sensor1)+strlen(Temp1)+strlen(Sensor2)+strlen(Temp2)+4];
strcpy((char *)dataBuffer,Sensor1);
strcat_P((char *)dataBuffer,PSTR(","));
strcat((char *)dataBuffer,Temp1);
strcat_P((char *)dataBuffer,PSTR("\n"));
strcat((char *)dataBuffer,Sensor2);
strcat_P((char *)dataBuffer,PSTR(","));
strcat((char *)dataBuffer,Temp2);
strcat_P((char *)dataBuffer,PSTR("\n"));
// Convert the length of the data buffer into a string
char dataLength[2];
tiny_itoa(strlen((char *)dataBuffer)-1,(char *)dataLength);
// Construct the HTTP Put
strcpy_P((char *)buf,PSTR("PUT /v2/feeds/"));
strcat((char *)buf,FEEDID);
strcat_P((char *)buf,PSTR(".csv HTTP/1.1\nHost: api.cosm.com\n"));
strcat_P((char *)buf,PSTR("X-ApiKey: "));
strcat((char *)buf,APIKEY);
strcat_P((char *)buf,PSTR("\nUser-Agent: "));
strcat((char *)buf,USERAGENT);
strcat_P((char *)buf,PSTR("\nContent-Length: "));
strcat((char *)buf,dataLength);
strcat_P((char *)buf,PSTR("\n\n"));
strcat((char *)buf,(char *)dataBuffer);
}
int main() {
uint8_t buf[MAX_BUF]; // The character buffer in which the HTTP Put will be constructed
// This code will only run once, after each powerup or reset of the board
// Set pin 3 to input
//pinMode(pin, INPUT); // This takes 136 bytes of sketch size
//DDRD = DDRD | B11110100; // This does the same thing and only takes 4 bytes of sketch size
// This does the same thing since pin 3 is set to input by default
SPI_DDR = (1<<PORTB3)|(1<<PORTB5)|(1<<PORTB2); // Set MOSI (PORTB3),SCK (PORTB5) and PORTB2 (SS) as output, others as input
SPI_PORT |= (1<<SPI_CS); // CS pin is not active
SPCR = (1<<SPE)|(1<<MSTR); // Enable SPI, Master Mode 0
#if DEBUG
init();
Serial.begin(9600);
_delay_ms(100);
#endif
W5100_Init();
for (;;) {
// Request a temperature reading from all Dallas sensors on the OneWire bus, takes 750ms so do it right before sleeping
reset();
write(SKIPROM);
write(STARTCONVO);
Connect();// Open a socket to the Cosm server, takes around 300ms to establish so do it right before sleeping
_delay_ms(MYDELAY); // Delay right now, eventually sleep
send(buf,strlen((char *)buf));// Send the previously constructed HTTP put
// Read the temperatures previously requested
int16_t rawTemperature1 = ReadSensor(0); // Read the first sensor
int16_t rawTemperature2 = ReadSensor(1); // Read the second sensor
BuildPut(rawTemperature1, rawTemperature2, buf);
#if DEBUG
endClock=millis();
Serial.print("Execution took ");
Serial.print(endClock-startClock);
Serial.print(" milliseconds\n");
startClock = millis();
#endif
}
}