I'm trying to connect a smart meter to an arduino to monitor my electricity usage. I'm
using the SoftwareSerial library but the serial port of this meter uses 7 bit, even parity &
1 stop bit with inverse logic and the library uses 8N1.
On the forum I have read solutions (from pylon) with 8E2 and 8O1 but I'm not able to work out
the modifications in the code.
void SoftwareSerial::recv()
{
#if GCC_VERSION < 40302
// Work-around for avr-gcc 4.3.0 OSX version bug
// Preserve the registers that the compiler misses
// (courtesy of Arduino forum user *etracer*)
asm volatile(
"push r18 \n\t"
"push r19 \n\t"
"push r20 \n\t"
"push r21 \n\t"
"push r22 \n\t"
"push r23 \n\t"
"push r26 \n\t"
"push r27 \n\t"
::);
#endif
uint8_t d = 0;
// If RX line is high, then we don't see any start bit
// so interrupt is probably not for us
if (_inverse_logic ? rx_pin_read() : !rx_pin_read())
{
// Wait approximately 1/2 of a bit width to "center" the sample
tunedDelay(_rx_delay_centering);
DebugPulse(_DEBUG_PIN2, 1);
// Read each of the 7 bits
for (uint8_t i=0x1; i != 0x80; i <<= 1)
{
tunedDelay(_rx_delay_intrabit);
DebugPulse(_DEBUG_PIN2, 1);
uint8_t noti = ~i;
if (rx_pin_read())
d |= i;
else // else clause added to ensure function timing is ~balanced
d &= noti;
}
// skip the parity bit
tunedDelay(_rx_delay_stopbit);
DebugPulse(_DEBUG_PIN2, 1);
// skip the stop bits
tunedDelay(_rx_delay_stopbit);
DebugPulse(_DEBUG_PIN2, 1);
if (_inverse_logic)
d = ~d & 0x7F;
// if buffer full, set the overflow flag and return
if ((_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF != _receive_buffer_head)
{
// save new data in buffer: tail points to where byte goes
_receive_buffer[_receive_buffer_tail] = d; // save new byte
_receive_buffer_tail = (_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF;
}
else
{
#if _DEBUG // for scope: pulse pin as overflow indictator
DebugPulse(_DEBUG_PIN1, 1);
#endif
_buffer_overflow = true;
}
}
#if GCC_VERSION < 40302
// Work-around for avr-gcc 4.3.0 OSX version bug
// Restore the registers that the compiler misses
asm volatile(
"pop r27 \n\t"
"pop r26 \n\t"
"pop r23 \n\t"
"pop r22 \n\t"
"pop r21 \n\t"
"pop r20 \n\t"
"pop r19 \n\t"
"pop r18 \n\t"
::);
#endif
}
and
size_t SoftwareSerial::write(uint8_t b)
{
if (_tx_delay == 0) {
setWriteError();
return 0;
}
uint8_t oldSREG = SREG;
cli(); // turn off interrupts for a clean txmit
// Write the start bit
tx_pin_write(_inverse_logic ? HIGH : LOW);
tunedDelay(_tx_delay + XMIT_START_ADJUSTMENT);
uint8_t p = 0;
uint8_t t;
for (t = 0x40; t; t >>= 1)
if (b & t) p++;
// Write each of the 7 bits
if (_inverse_logic)
{
for (byte mask = 0x01; mask != 0x80; mask <<= 1)
{
if (b & mask) // choose bit
tx_pin_write(LOW); // send 1
else
tx_pin_write(HIGH); // send 0
tunedDelay(_tx_delay);
}
// parity
if (p & 0x01)
tx_pin_write(HIGH); // send 0
else
tx_pin_write(LOW); // send 1
tunedDelay(_tx_delay);
tx_pin_write(LOW); // restore pin to natural state
}
else
{
for (byte mask = 0x01; mask != 0x80; mask <<= 1)
{
if (b & mask) // choose bit
tx_pin_write(HIGH); // send 1
else
tx_pin_write(LOW); // send 0
tunedDelay(_tx_delay);
}
// parity
if (p & 0x01)
tx_pin_write(LOW); // send 0
else
tx_pin_write(HIGH); // send 1
tunedDelay(_tx_delay);
tx_pin_write(HIGH); // restore pin to natural state
}
SREG = oldSREG; // turn interrupts back on
tunedDelay(_tx_delay);
return 1;
}
Do you have a datasheet for the device you're trying to read? Perhaps we have to adapt other things like activating the pullup on the RX line or inverted start/stop bit logic.
I do not have it specific for the meter I have but it should comply to the following standard :
There is a protocol description on page 10:
Data readout
The Metering System transmits the data message immediately following the activation
through the Request signal. A series of blocks containing the following are sent:
Have you tried to use a non-inverted 8N1 serial interface to read the values? From the information you provided and that is available I would also assume that it's 7E1 but you can read that with 8N1 and AND every byte you get with 0x7F, that gets equal results for the reading side.
As for the inversion: I guess you're referring to the definition below the table 4-1 in the linked document where SPACE is defined as >4V and MARK as < 1V. No word is said though about the levels of the start and stop bits. This could mean that you have to read serially without inversion and just XOR the value with 0xFF (NOT operation) to get the bit values inverted but leaving the start and stop bits on their usual level. I hope you understand what I mean.
I didn't expect it to be readable, nevertheless can you post the results?
Interesting would be the results of the hardware serial 8N1 as well as the results of the SoftwareSerial (8N1 too) with inversion activated (that way we have both common types of start/stop bit signaling).
May be a silly question, but do you have a scope to attach to the line to see the signal pattern?
In the thread you linked to, they tried with an Arduino too and were successful in using the hardware serial with a pre-connected inverter. Another one wrote he was successful (at least mostly) with the SoftwareSerial class and changed timings. You might try both but I guess the second options is a lot of trial and error without a scope to get the exact timings.
Yes, I would try that one. The SoftwareSerial does work in some cases but in my experience it produces more problems than it solves. Most people new to Arduino think they can just replace a missing hardware serial with the software version but this usually doesn't work. The hardware version does a lot of stuff not possible in the software version because it would need more processing power than the ATmega series does offer (just one example: timing adaptions based on the received bits).
The SoftwareSerial does work OK for me up 38400 baud if connected to the PC (debugging) but with sensors and other devices I had success only with 1 in 7 cases yet. And even that one case required additional software checks to eliminate the occasional failures.
Hi, sorry to bring this thread back, but I have a question (at the end) that is relevant.
I'm using softserial to talk inverted 7E1, 1200 baud (SDI-12 protocol). I used the code changes from this thread
and got the error:
/var/folders/pk/zzpvz5bs0d589468n2bxsyz00000gn/T//ccPjw17q.s: Assembler messages:
/var/folders/pk/zzpvz5bs0d589468n2bxsyz00000gn/T//ccPjw17q.s:243: Error: register r24, r26, r28 or r30 required
Similar to the first post here.
I've applied the fix recommended here:
of changing
: "+r" (delay), "+a" (tmp)
to
: "+w" (delay), "+a" (tmp)
in the tunedDelay function.
My question is why did I need to make this fix in my copy of SoftwareSerial? The regular SoftwareSerial packaged in arduino 1.0.1 does not have this change and compiles fine. diff-ing the code, I made no changes to SoftwareSerial beyond the recv() and write() functions to convert to 7E1.
I had another fix for the same problem by declaring two more variable (parameters for the assembler routine) volatile. This has to do with compiler internals. The compiler keeps care that processor registers are used where appropriate but variables are stored in memory if a register is not suitable. It's feasible that the register allocation changes if there are code changes. If everything is coded correctly this doesn't change anything but it's possible that the declarations in the code are ambiguous and the code compiles anyway. If you then change only a small part of the code you may end in a non-compilable program.
The problem here arises from the combination of machine code (assembler) with C++ code.
JBee:
[...]
Serial.begin(9600);
Serial.println("P1 Smart meter reading - START\n");
[...]
Do you have any suggestions ?
The IEC62056-21 standard dictates an initial rate of 300 baud [edit: sorry, should have looked better, your're trying an electrical interface, I made an optical one, 300 baud refers to the optical standard within IEC62056]. One can negiotiate a higher baud rate after initial communication but that is not needed for readout only.
Some info for interfacing with smart meters:
Type of transmission:
Asynchronous serial bit (Start – Stop) transmission according to ISO/IEC 1177:1985, half-duplex.
Transmission speed:
Initial baud rate – 300
Standard baud rates – 300, 600, 1 200, 2 400, 4 800, 9 600, 19 200.
Character format:
Character format according to ISO/IEC 1177:1985.
(1 start bit, 7 data bits, 1 parity bit, 1 stop bit).
Character code:
Character code according to ISO/IEC 646:1991, international reference version. For local use,
a national replacement code can be used.
Thanks, pylon. You are correct. If it came big endian, and the receiver was expecting little endian, it would take more than just a bit shift. The bit order would need to be reversed, then your code.