ESP8266 Projects Blog
Home of CBDB / MPDMv4 /SmartMon Development boards (ESP-12/ESP-07).
Guidelines and ESP8266 programming examples using LUA, Eclipse and Arduino IDE, ESP Basic and many more!
This is Part 4 of the SmartMon Extension Board series.
SmartMon v2.7ex Board is a extension
board fully compatible with Arduino, ESP8266, ARM, PIC & other
available MCu's out there. As long as your MCU/Dev Board has I2C Bus capabilities you are good to go!
It is probably one of the easiest ways to create your own Voltage/Current
and Power consumption monitoring system for your Projects, devices, batteries, power
supplies and many more!
UPDATE !! SmartMon v2.7Ex is available also on Tindie Store !
This time we will go thru a full example on how to monitor your power line and load, read
Voltage/Current and Power consumtion data and upload them on the
Thingspeak related Channel.
This is Part 3 of the SmartMon Extension Board series.
SmartMon v2.7ex Board is a extension
board fully compatible with Arduino, ESP8266, ARM, PIC & other
available MCu's out there. As long as your MCU/Dev Board has I2C Bus capabilities you are good to go!
It is probably one of the easiest ways to create your own Voltage/Current
and Power consumption monitoring system for your Projects, devices, batteries, power
supplies and many more!
UPDATE !! SmartMon v2.7Ex is available also on Tindie Store !
voltage = 0 current = 0 power = 0 peng = 0 eng = 0
2. I2C Init function
function init_i2c() i2c.setup(id, sda, scl, i2c.SLOW) end
3. INA219 Reset function
function reset() write_reg(0x00, 0xFFFF) end
4. Read register function
--read from reg_addr content of dev_addr function read_reg_str(reg_addr) i2c.start(id) i2c.address(id, devaddr, i2c.TRANSMITTER) i2c.write(id,reg_addr) i2c.stop(id) tmr.delay(1) i2c.start(id) i2c.address(id, devaddr, i2c.RECEIVER) c=i2c.read(id, 16) -- read 16bit val i2c.stop(id) return c end
5. Read register function - 16bit--returns 16 bit int
function read_reg_int(reg_addr) i2c.start(id) i2c.address(id, devaddr, i2c.TRANSMITTER) i2c.write(id,reg_addr) i2c.stop(id) tmr.delay(1) i2c.start(id) i2c.address(id, devaddr, i2c.RECEIVER) local c = i2c.read(id, 16) -- read 16bit val i2c.stop(id) --convert to 16 bit int local val = bit.lshift(string.byte(c, 1), 8) local val2 = bit.bor(val, string.byte(c, 2)) return val2 end
6. Write register function
function write_reg(reg_addr, reg_val) print("writing reg:" .. reg_addr .. ", reg_val:" .. reg_val) i2c.start(id) i2c.address(id, devaddr, i2c.TRANSMITTER) local bw = i2c.write(id, reg_addr) local bw2 = i2c.write(id, bit.rshift(reg_val, 8)) local bw3 = i2c.write(id, bit.band(reg_val, 0xFF)) i2c.stop(id) end
7. Calibration / settings function
function setCAL_reg() maxVoltage = 32 maxCurrentmA = 10000 write_reg(0x05,3950) --CALIBRATE FOR YOUR rshunt & stuff local config = 15391 write_reg(0x00, config) end
8. Read Current values (mA)
function getCurrent_mA() local valueInt = read_reg_int(0x04) return valueInt end
9. Read Bus Voltage (V)
function getBusVoltage_V() local valueInt = read_reg_int(0x02) -- Shift to the right 3 to drop CNVR and OVF and multiply by LSB = 4 local val2 = bit.rshift(valueInt, 3) local val2 = val2 * 4 return val2 * 0.001 end
10. Read Shunt resistor voltage drop (mV)
function getShuntVoltage_mV() -- Gets the raw shunt voltage (16-bit signed integer, so +-32767) local valueInt = read_reg_int(0x01) return valueInt * 0.01 end
11. Read Bus Power (W)
function getBusPowerWatts() local valueInt = read_reg_int(0x03) return valueInt*20*0.001 end
12. Print read values
function print_values() volt = getBusVoltage_V() print("\nVoltage : " .. volt.." V") current = getCurrent_mA()/1000 power = getBusPowerWatts() if (current > 65 ) then print("ERR 00.23") current = 0 power = 0 end eng = eng + power/60 print("Current : " .. string.format("%6.3f",current) .." A") print("Power : " .. power .." W\n") print("Energy : " .. string.format("%6.3f",eng) .." Wh\n") end
MAIN Program
---- INA216 Module TEST init_i2c() setCAL_reg() print_values()
If you want to read and update values every minute add function from below:
UPDATE !! SmartMon v2.7Ex is available also on Tindie Store !
This time we are exploring the Real Time Clock implementation and we will see how easy or complicated is to set & program the onboard RTC Clock.
And the story behind: For a complete monitoring/ data logging experience, a nice to have feature is a proper RTC clock. Having this in mind, for the new version of the SmartMon I choose to add it on-board for a very simple and easy integration in your related Voltage, Current, Power projects. The choosen one is the PCF8563 from NXP, a very nice and easy to program CMOS Real-Time Clock (RTC) and Calendar optimized for low power consumption.
A programmable clock output, interrupt output and voltage-low detector
are also provided. All addresses and data are transferred serially via a
two-line bidirectional I2C-bus with a maximum bus speed of 400 kbit/s.
Features:
• Provides year, month, day, weekday, hours, minutes, and seconds based on a
32.768 kHz quartz crystal • Century flag
• Clock operating voltage: 1.0 V to 5.5 V at room temperature
• Low backup current; typical 0.25uA at Vdd =3.0V and Tamb=25C
• 400 kHz two-wire I2C-bus interface (at VDD= 1.8 V to 5.5 V)
• Programmable clock output for peripheral devices (32.768 kHz, 1.024 kHz, 32 Hz, and 1Hz)
• Alarm and timer functions - separate Alarm triggered MOSFET output with separate voltage
input/supply included onboard, can direct drive upto 2A external devices, 3/5/12V
This time we are talking here about ESP8266/LUA driver but also Arduino implementation will follow.
If you use another ESP8266, or Arduino, ARM, PIC, whatever MCU you use there days for your projects, then just be sure that you are connecting the I2C lines (SDA/SCL) on the allocated pins for your setup. Driver implementation
As
PCF8563 has a I2C compatible compatible interface, driver building it
following more or less the same process as before for I2C devices. For more details about the RTC, please take a deeper look at the PCF8563 Datasheet and the related PCF8563 RTC clock driver Article.
1. Data conversion functions:
1.1 Decimal to BCD: function decToBcd(val) local d = string.format("%d",tonumber(val / 10)) local d1 = tonumber(d*10) local d2 = val - d1 return tonumber(d*16+d2) end
1.2 BCD to Decimal:
function bcdToDec(val) local hl=bit.rshift(val, 4) local hh=bit.band(val,0xf) local hr = string.format("%d%d", hl, hh) return string.format("%d%d", hl, hh) end 2. Init I2C bus/interface: address = 0x51, -- A2, A1, A0 = 0 id = 0 init = function (self, sda, scl) self.id = 0 i2c.setup(self.id, sda, scl, i2c.SLOW) end
For testing, pack it together and save the code on ESP as 'pcf8563.lua', restart ESP and run:
-- Set Initial Time and Date require('pcf8563') -- call for new created PCF8563 Module Driver sda, scl = 2, 1 -- declare your I2C interface PIN's pcf8563:init(sda, scl) -- initialize I2C Bus pcf8563:setTime(0,34,13,12,4,3,15) -- setTime(s,min,hour,day,weekday,month, year) -- get Time and Date require('pcf8563') sda, scl = 2, 1 pcf8563:init(sda, scl) s, m, h, d, dt, mn, y = pcf8563:readTime() --ReadTime function call =string.format("%s - %s/%s/20%s",dt, d, mn, y) =string.format(" %s:%s:%s", h, m, s)
Today we will start a new Battery Live Monitor System (BLMS) series related with the SmartMon v.2.7Ex Board.
SmartMonv2.7Ex board has been born as the next generation of BLMS,
bringing more flexibility and a lot of extra functionalities than the
previous one.
If you want to quickly add to your project Voltage, Current and Power monitoring capabilities, plus, as a bonus, a proper hardware RTC with integrated Alarm and Timer functions, this is the extension board that you was looking for.
The modular design will let you to easy connect it and use it with any MCU you want as long as it has I2C bus capabilities and is working in the 3-5.5V voltage range. That includes all the popular existing platforms like Arduino, PIC, ARM, Atmega, ESP8266 and many more!
TI INA219 integrated high-side current shunt and power monitor
High precision, high quality shunt resistor from BOURNS: very low Inductance (10nH) and Temperature Coeficient ( ±20 ppm/°C max.)
Measured Bus Current interval: 0-10A (standard) / 0-20A (optional)
High range Measured Bus Voltages from 0 to 26 V
Bus voltage is measured directly on the load side of the shunt resistor
SmartMon v.2.Ex Power supply voltage range - 3.3 to 5.5V
Standard I2C bus communication ( nEXT Bus connector)
PCF8563 RTC - real-time clock and calendar optimized for low power consumption
RTC Alarm and timer functions with MOSFET driven Interrupt output
On-board regulator option (can be populated and used it when you want to use the same main BUS Voltage input also as a power supply for the SmartMon Board)
Schematics:
High-side Voltage measurement, current shunt and power monitor
As you remember from our previous article about DS3231 RTC Module, we have identified onboard an EEPROM chip, a 32k AT24C32 one. It is independent from the RTC circuit and conected on the I2C bus, perfect companion for a Data Logger System :)
AT24C32 EEPROM
The AT24C32 provides 32,768 bits of serial electrically erasable and programmable read only memory (EEPROM) organized as 4096 words of 8 bits each. Might not sound too much but believe it or not you can log 6 months of data or even more on it depending on your application requests and how you organize your data logging.
For example if you save your data in 1byte, you will have enough for around 170 days or 24 weeks! With an added 16 extra location available for bulding data header/date/time/CRC/whatever your needs ask for. And if you still feel it to small, you can use anytime AT24C64, 64k size (8192 x 8), direct drop-in replacement! Also the device’s cascadable feature allows up to 8 devices to share a common I2C bus. The device is optimized for use in many industrial and commercial applications where low power and low voltage operation are essential. In addition, the entire family is available in 2.7V (2.7V to 5.5V) and 1.8V (1.8V to 5.5V)versions. FEATURES:
• Low-Voltage and Standard-Voltage Operation – 2.7 (VCC = 2.7V to 5.5V) – 1.8 (VCC = 1.8V to 5.5V) • Low-Power Devices (ISB = 2μA at 5.5V) Available • Internally Organized 4096 x 8 • 2-Wire Serial Interface • Schmitt Trigger, Filtered Inputs for Noise Suppression • Bidirectional Data Transfer Protocol • 100 kHz (1.8V, 2.5V, 2.7V) and 400 kHz (5V) Clock Rate • Write Protect Pin for Hardware Data Protection • 32-Byte Page Write Mode (Partial Page Writes Allowed) • Self-Timed Write Cycle (10 ms max) • High Reliability – Endurance: 1 Million Write Cycles – Data Retention: 100 Years • Automotive Grade and Extended Temperature Devices Available • 8-Pin JEDEC PDIP, 8-Pin JEDEC SOIC, 8-Pin EIAJ SOIC, and 8-pin TSSOP Packages
USB adapter (take a look on Part 1 for details how to connect them together)
DS3231 Module from previous article
For programming and uploading the driver and the software we will continue to use the LuaUploader as before.
Driver implementation To be able to access and properly operate with any kind of memory devices we need at least 3 basic functions implemented: addressing, read and write. Plus the proper I2C/SPI/Whaterver bus communication initialisation, ofcourse.
1. Init I2C bus/interface:
address = 0x50, -- A2, A1, A0 = 0 id = 0 init = function (self, sda, scl) self.id = 0 i2c.setup(self.id, sda, scl, i2c.SLOW) end
ADDRESSING:
The 32K EEPROM requires an 8-bit device address word following a start condition to enable the chip for a read or write operation. It uses the three device address bits A2, A1, A0 to allow as many as eight devices on the same bus. These bits must compare to their corresponding hardwired input pins. The A2, A1, and A0 pins use an internal proprietary circuit that biases them to a logic low condition if the pins are allowed to float. The eighth bit of the device address is the read/write operation select bit. A read operation is initiated if this bit is high and a write operation is initiated if this bit is low.
2. WRITE Function
A write operation requires two 8-bit data word addresses following the device address word and acknowledgment. Upon receipt of this address, the EEPROM will again respond with a zero and then clock in the first 8-bit data word. Following receipt of the 8-bit data word, the EEPROM will output a zero and the addressing device, such as a microcontroller, must terminate the write sequence with a stop condition.
At this time the EEPROM enters an internally-timed write cycle, tWR, to the
nonvolatile memory. All inputs are disabled during this write cycle and the EEPROM will
not respond until the write is complete
write_EEPROM = function (self, devadr, memadr, edata) i = 1 length = string.len(edata) adrh=bit.rshift(memadr, 8) adrl=bit.band(memadr,0xff) i2c.start(self.id) i2c.address(self.id, self.address, i2c.TRANSMITTER) i2c.write(self.id, adrh) i2c.write(self.id, adrl) --print(edata) --debug only --print(string.byte(edata,1)) --debug only while i<=length do tmr.wdclr() i2c.write(self.id,string.byte(edata,i)) i = i+1 end i2c.stop(self.id) end
3. READ Function
A random read requires a “dummy” byte write sequence to load in the data word address. Once the device address word and data word address are clocked in and acknowledged by the EEPROM, the microcontroller must generate another start condition.
The microcontroller now initiates a current address read by sending a device address with the
read/write select bit high. The EEPROM acknowledges the device address and serially clocks
out the data word. The microcontroller does not respond with a zero but does generate a following
stop condition
read_EEPROM = function (self, devadr, memadr, length) adrh=bit.rshift(memadr, 8) adrl=bit.band(memadr,0xff) i2c.start(self.id) i2c.address(self.id, self.address, i2c.TRANSMITTER) i2c.write(self.id, adrh) i2c.write(self.id, adrl) i2c.stop(self.id) i2c.start(self.id) i2c.address(self.id, self.address, i2c.RECEIVER) c=i2c.read(self.id, length) i2c.stop(self.id) print(c) return c end
For testing, pack it together and save the code on ESP as 'eeprom.lua', restart ESP and run:
require('eeprom') -- call for new created AT24C32 Module Driver memadr=0x00 -- let's read from begining sda, scl = 2, 1 -- I2C pins setup edata="4.321 - Data from the EEPROM" -- Data to write to EEPROM
eeprom:init(sda,scl) -- Init I2C eeprom:write_EEPROM(0x50,0,edata) -- Write Data edata to EEPROM starting with address=0 eeprom:read_EEPROM(0x50,0,28) -- Read Data from EEPROM, address=0, length=28
If you remember my article about PCF8563 Real Time Clock this one was a looong avaited MailBox hit. Actually I didn't expect it to show anymore after so much time but miracles happening sometime:) The DS3231 is a extremely accurate I2C real-time clock (RTC) with an integrated temperature-compensated crystal oscillator (TCXO) and crystal. The device incorporates a battery input, and maintains accurate timekeeping when main power to the device is interrupted. The integration of the crystal resonator enhances the long-term accuracy of the device as well as reduces the piece-part count in a manufacturing line.
The RTC maintains seconds, minutes, hours, day, date,month, and year information. The date at the end of the month is automatically adjusted for months with fewer than 31 days, including corrections for leap year. The clock operates in either the 24-hour or 12-hour format with an AM/PM indicator.
Two programmable time-of-day alarms and a programmable square-wave output are provided. Address and data are transferred seriallythrough an I2C bidirectional bus.
A precision temperature-compensated voltage reference and comparator circuit monitors the status of VCC to detect power failures, to provide a reset output, and to automatically switch to the backup supply when necessary. Additionally, the RST pin is monitored as a pushbutton input for generating a μP reset.
DS3231 Typical Operating Circuit
FEATURES :
Highly Accurate RTC Completely Manages All Timekeeping Functions
• Real-Time Clock Counts Seconds, Minutes, Hours, Date of the Month, Month, Day of the Week, and Year, with Leap-Year Compensation Valid Up to 2100 • Accuracy ±2ppm from 0°C to +40°C • Accuracy ±3.5ppm from -40°C to +85°C • Digital Temp Sensor Output: ±3°C Accuracy • Register for Aging Trim • Active-Low RST Output/Pushbutton Reset Debounce Input • Two Time-of-Day Alarms • Programmable Square-Wave Output Signal
Simple Serial Interface Connects to Most Microcontrollers
• Fast (400kHz) I2C Interface
Battery-Backup Input for Continuous Timekeeping
• Low Power Operation Extends Battery-Backup Run Time • 3.3V Operation
Operating Temperature Ranges: Commercial (0°C to +70°C) and Industrial (-40°C to +85°C)
As you can see from the pictures above, a nice compact module, with backup battery holder on the back (CR2032). Also you can find on the same module sharing the I2C bus a 24C32N EEPROM ( 32k - 4096 x 8) , totally independent from the RTC circuit. A nice addon for a possible WIFI Datalogger system, what do you think about? :)
32k might sound a small amount of data storage but depending on your application requests might be more than enough for collecting 6 moths or a year data, even more. As I know already from your requests that this subject is of big interest, we will elaborate more about this one in the next article, for now let's go back to our fancy RTC :)
Clock and Calendar - Theory of operation
The time and calendar information is obtained by reading the
appropriate register bytes. The time and calendar data are set or
initialized by writing the appropriate register bytes. The contents of
the time and calendar registers are in the binary-coded decimal (BCD)
format.
The DS3231 can be run in either 12-hour or 24-hour mode. Bit
6 of the hours register is defined as the 12- or 24-hour mode select
bit. When high, the 12-hour mode is selected. In the 12-hour mode, bit 5
is the AM/PM bit with logic-high being PM. In the 24-hour mode, bit 5
is the 20-hour bit (20–23 hours).
The century bit (bit 7 of the month register) is toggled when the years register overflows from 99 to 00.
The
day-of-week register increments at midnight. Values that correspond to
the day of week are user-defined but must be sequential (i.e., if 1
equals Sunday, then 2 equals Monday, and so on). Illogical time and date
entries result in undefined operation.
When reading or writing the
time and date registers, secondary (user) buffers are used to prevent
errors when the internal registers update. When reading the time and
date registers, the user buffers are synchronized to the internal
registers on any START and when the register pointer rolls over to zero.
The time information is read from these secondary registers, while the
clock continues to run. This eliminates the need to reread the registers in case the main registers update during a read.
The
countdown chain is reset whenever the seconds register is written.
Write transfers occur on the acknowledge from the DS3231. Once the
countdown chain is reset, to avoid rollover issues the remaining time
and date registers must be written within 1 second. The 1Hz
square-wave output, if enabled, transitions high 500ms after the seconds
data transfer, provided the oscillator is already running.
USB adapter (take a look on Part 1 for details how to connect them together)
DS3231 Module from above
For programming and uploading the driver and the software we will continue to use the LuaUploader as before.
Driver implementation As DS3231 has a I2C compatible
compatible interface, driver building it following more or less the
same process as before for I2C devices.
1. Data conversion functions:
1.1 Decimal to BCD: function decToBcd(val) local d = string.format("%d",tonumber(val / 10)) local d1 = tonumber(d*10) local d2 = val - d1 return tonumber(d*16+d2) end
1.2 BCD to Decimal:
function bcdToDec(val) local hl=bit.rshift(val, 4) local hh=bit.band(val,0xf) local hr = string.format("%d%d", hl, hh) return string.format("%d%d", hl, hh) end 2. Init I2C bus/interface:
address = 0x51, -- A2, A1, A0 = 0 id = 0 init = function (self, sda, scl) self.id = 0 i2c.setup(self.id, sda, scl, i2c.SLOW) end
For testing, pack it together and save the code on ESP as 'ds3231.lua', restart ESP and run:
-- Set Initial Time and Date require('ds3231') -- call for new created DS3231 Module Driver sda, scl = 2, 1 -- declare your I2C interface PIN's ds3231:init(sda, scl) -- initialize I2C Bus ds3231:setTime(5,08,12,3,6,04,15) -- setTime(s,min,hour,weekday,day,month, year) -- get Time and Date require('ds3231') sda, scl = 2, 1 ds3231:init(sda, scl) s, m, h, d, dt, mn, y = ds3231:readTime() =string.format("%s - %s/%s/20%s",d, dt, mn, y) =string.format(" %s:%s:%s", h, m, s)
First run test
5. Read Time & Date - Print on LCD require('st7032i') sda, scl = 2, 1 st7032i:init_i2c(sda, scl) st7032i:init_LCD()
Time_LCD = function() s, m, h, d, dt, mn, y = ds3231:readTime() date = string.format("%s",dt).."/"..string.format("%s",mn).."/"..string.format("20%s",y) st7032i:lcd_print(3,1,date) time = string.format("%s",h)..":"..string.format("%s",m)..":"..string.format("%s",s) st7032i:lcd_print(4,2,time) end
tmr.alarm(0, 1000, 1, function() Time_LCD() end) -- set call Time_LCD function Timer