aboutsummaryrefslogtreecommitdiff
path: root/arduino_nano/main.c
blob: c8b14fac7248f127fded21515e7ff49c75d13072 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
/* Copyright (c) 2015 King Kévin <kingkevin@cuvoodoo.info> */
/* the spark counter transmits electricity measurements over radio
 * the electricity measurements (voltage, current, power, energy) are query everry second from a peacefair PZEM-004 power meter
 * they are then transmitted using an nRF2L01+ transceiver
 */

#include <stdint.h> // Standard Integer Types
#include <stdio.h> // Standard IO facilities
#include <stdlib.h> // General utilities
#include <stdbool.h> // Boolean
#include <string.h> // Strings

#include <avr/io.h> // AVR device-specific IO definitions
#include <util/delay.h> // Convenience functions for busy-wait delay loops
#include <avr/interrupt.h> // Interrupts
#include <avr/wdt.h> // Watchdog timer handling
#include <avr/pgmspace.h> // Program Space Utilities
#include <avr/sleep.h> // Power Management and Sleep Modes

#include "main.h" // main definitions
#include "usart.h" // basic USART functions
#include "nrf24.h" // nRF24L01 functions

/* variables */
bool query_pzem004 = false; // flag to query the PZEM-004 power meter
volatile uint8_t timer_seconds = 0; // how many seconds have passed
volatile bool watchdog = true; // set when watchdog interrupt occurred

/* disable watchdog when booting */
void wdt_init(void) __attribute__((naked)) __attribute__((section(".init3")));
void wdt_init(void)
{
	MCUSR = 0;
	wdt_disable();
}

/* enable watchdog with interrupt and system reset (modified wdt_enable) */
#define wdt_set(value) \
__asm__ __volatile__ ( \
    "in __tmp_reg__,__SREG__" "\n\t" \
    "cli" "\n\t" \
    "wdr" "\n\t" \
    "sts %0,%1" "\n\t" \
    "out __SREG__,__tmp_reg__" "\n\t" \
    "sts %0,%2" "\n\t" \
    : /* no outputs */ \
    : "M" (_SFR_MEM_ADDR(_WD_CONTROL_REG)), \
    "r" (_BV(_WD_CHANGE_BIT) | _BV(WDE) | _BV(WDIE)), \
    "r" ((uint8_t) ((value & 0x08 ? _WD_PS3_MASK : 0x00) | _BV(WDE)  | _BV(WDIE) | (value & 0x07)) ) \
    : "r0"  \
)

/* initialize GPIO */
void io_init(void)
{
	/* use UART as terminal */
	usart_init();
	stdout = &usart_output;
	stdin  = &usart_input;
	/* use nRF24L01 */
	nrf24_init();
	/* set up timer 1 to regularly wake up and request data from the power meter */
	TCCR1B |= (0<<WGM13)|(1<<WGM12); // use timer 1 in CTC mode
	TCCR1A |= (0<<WGM11)|(0<<WGM10); // use timer 1 in CTC mode
	TCNT1 = 0; // reset timer 1 counter
	OCR1A = F_CPU/256UL; // the timer to trigger every second with a prescale of 256 (the max is 1.05s)
	TIMSK1 |= (1<<OCIE1A); // enable interrupt
	
	sei(); // enable interrupts
}

int main(void)
{
	io_init(); // initialize IOs
	
	//printf(PSTR("welcome to the spar counter power meter monitor\n"));
	
	/* configure nRF24 transceiver */
	nrf24_set_rx_addr(conf.rx_addr); // set device receiving address
	nrf24_set_tx_addr(conf.tx_addr); // set transmission destination address
	nrf24_set_rf_channel(conf.channel); // set transceiver channel
	
	/* set the PZEM-004 IP address (it won't reply to other requests before) */
	uint8_t cmd[7] = {0xB4,0xC0,0xA8,0x01,0x01,0x00,0x1E}; // set the address 192.168.1.1 (keep this address for the other commands)
	for (uint8_t i=0; i<sizeof(cmd); i++) {
		usart_putchar_nonblocking(cmd[i],NULL);
	}
	
	/* start timer to periodically query the power meter */
	timer_seconds = 0; // restart seconds counter
	TCCR1B |= (1<<CS12)|(0<<CS11)|(0<<CS10); // set prescale to 256 (starting the timer)
	query_pzem004 = false; // immediately start querying the power meter
	bool queried_pzem004 = true; // all values have been queried from the power meter

	/* PZEM-004 power meter values */
	float voltage = 0, current = 0, power = 0, energy = 0; // the read values
	bool send_values = false; // set to true to send out the values
	
	bool action = false; // to know if we performed any king of action during which some other activity could have happened

	while (true) { // endless loop for micro-controller
		action = false; // new cycle of actions
		if (watchdog) { // watchdog interrupt trigger. reset watchdog before system reset
			wdt_set(WDTO_2S); // set to 4s (interrupt and reset mode)
			sei(); // re-enable interrupts
			watchdog = false; // wait for next reset
		}
		if (timer_seconds!=0 && timer_seconds==conf.request_period) {
			action = true; // an action is performed
			timer_seconds = 0; // restart timer
			query_pzem004 = true; // remember to query power meter
		}
		if (query_pzem004) {
			action = true; // an action is performed
			query_pzem004 = false; // clear flag
			if (queried_pzem004) { // wait until previous queries finished
				queried_pzem004 = false;
				// query all values (voltage, current, power, energy)
				// start with voltage, the other will be queried after the power meter answered
				cmd[0] = 0xb0; // query voltage
				cmd[6] = 0x1a; // update checksum  
				for (uint8_t i=0; i<sizeof(cmd); i++) {
					usart_putchar_nonblocking(cmd[i],NULL);
				}
			}
		}
		if (usart_incoming>=7) { // wait for an answer to be available
			action = true; // an action is performed
			//printf(PSTR("got value\n"));
			// store answer
			uint8_t answer[7];
			for (uint8_t i=0; i<sizeof(answer); i++) {
				answer[i] = usart_getchar(NULL); // save input (this updates usart_incoming)
			}
			// verify checksum
			uint8_t checksum = 0;
			for (uint8_t i=0; i<sizeof(answer)-1; i++) {
				checksum += answer[i];
			}
			if (checksum==answer[sizeof(answer)-1]) { // checksum is correct
				char str[5+1+3+1]; // store the value as string
				switch (answer[0]) {
					case 0xa0: // voltage
						snprintf(str,sizeof(str),"%u.%u",(answer[1]<<8)+answer[2],answer[3]);
						voltage = atof(str);
						cmd[0] = 0xb1; // query current
						cmd[6] = 0x1b; // update checksum  
						for (uint8_t i=0; i<sizeof(cmd); i++) {
							usart_putchar_nonblocking(cmd[i],NULL);
						}
						break;
					case 0xa1: // current
						snprintf(str,sizeof(str),"%u.%u",(answer[1]<<8)+answer[2],answer[3]);
						current = atof(str);
						cmd[0] = 0xb2; // query power
						cmd[6] = 0x1c; // update checksum  
						for (uint8_t i=0; i<sizeof(cmd); i++) {
							usart_putchar_nonblocking(cmd[i],NULL);
						}
						break;
					case 0xa2: // power
						snprintf(str,sizeof(str),"%u.%u",(answer[1]<<8)+answer[2],answer[3]);
						power = atof(str);
						cmd[0] = 0xb3; // query energy
						cmd[6] = 0x1d; // update checksum  
						for (uint8_t i=0; i<sizeof(cmd); i++) {
							usart_putchar_nonblocking(cmd[i],NULL);
						}
						break;
					case 0xa3: // energy
						energy = (((uint32_t)answer[1])<<16)+(answer[2]<<8)+answer[3];
						queried_pzem004 = true; // all have been queried
						send_values = true; // this should be the last of the 4 values we requested. now send them
						break;
					case 0xa4: // address
						break;
				}
			}
		}
		if (send_values) { // send the stored values from the power meter using the nRF24L01
			action = true; // an action is performed
			send_values = false; // clear flag
			uint8_t message[3+6+6+6+6]; // create TLV based message
			uint8_t i = 0;
			message[i++] = 0; // type: message source
			message[i++] = 1; // length
			message[i++] = 1; // value: home 1
			message[i++] = 1; // type: voltage
			message[i++] = 4; // length
			memcpy(&message[i],&voltage,4); // value
			i += 4; // value
			message[i++] = 2; // type: current
			message[i++] = 4; // length
			memcpy(&message[i],&current,4); // value
			i += 4; // value
			message[i++] = 3; // type: power
			message[i++] = 4; // length
			memcpy(&message[i],&power,4); // value
			i += 4; // value
			message[i++] = 4; // type: energy
			message[i++] = 4; // length
			memcpy(&message[i],&energy,4); // value
			i += 4; // value
			nrf24_transmit(message,i);
		}
		if (nrf24_flag) {
			action = true; // an action was performed 
			nrf24_flag = false; // reset flag
			uint8_t activity = nrf24_activity();
			if (activity&TX_SUCCEEDED) {
				//printf(PSTR("transmission succeeded\n"));
			}
			if (activity&TX_FAILED) {
				//printf(PSTR("transmission failed\n"));
			}
			if (activity&RX_RECEIVED) {
				//printf(PSTR("received data\n"));
			}
			if (activity&RX_AVAILABLE) {
				//printf(PSTR("data available\n"));
				// read all RX FIFO to clear them
				while (nrf24_data_available()) {
					uint8_t data[32];
					nrf24_rx_payload(data,sizeof(data));
					//uint8_t size = nrf24_rx_payload(data,sizeof(data));
					//printf("got %d bytes\n",size);
				}
			}
		}
		/* go to sleep and wait for next interrupt */
		if (!action) { // only go to sleep if no action had to be performed
			set_sleep_mode(SLEEP_MODE_IDLE);
			sleep_mode();
		}
	}
	return 0;
}

/* timer 1 triggered */
ISR(TIMER1_COMPA_vect)
{
	timer_seconds++; // count seconds
}

/* watchdog interrupt */
ISR(WDT_vect)
{
	watchdog = true;
}