Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Papirus hat MCP7940N examples #55

Merged
merged 1 commit into from
Mar 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions RTC-Hat-Examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Using the MCP7940N Hardware Clock on the Papirus HAT

During the Papirus Kickstarter campaign a lot of time has been spent on finding a hardware clock
with a proper alarm function and the right pogo pin to connect the alarm output to the Pi reset header.
This to make a wake-on-alarm function possible.
However a wake-on-alarm software example was not provided. The two examples in [RTCreboot](RTCreboot) and [RTCgpio](RTCgpio)
should give you sufficient information to use the hardware clock.

The Papirus HAT (not the Papirus Zero) has a MCP7940N battery backed-up hardware clock.
The MCP7940N is controlled via the i2c bus. Its i2c address is 0x6f.
For all the details on the MCP7940N see the [datasheet](mcp7940n.pdf).

The MCP7940N alarm output (MFP pin) on the Papirus hat is connected to a pulse shaping circuit. The output of this
circuit (signal RTC_OUT on the Papirus hat schematic) is connected to both the pogo pin and to RPi's GPIO 27
(pin 13 on the GPIO connector). The normal state of RTC_OUT is high. When the MFP output goes from high to low
a low pulse of about 12 msec is output on RTC_OUT. A low to high transition of the MFP output causes no change of RTC_OUT.

When the pogo pin is connected to the Raspberry Pi reset header the RTC_OUT pulse will cause
a (re)boot of the Pi even after it is shut down (but with power still connected).
When the pogo pin is not connected to the Pi reset header the RTC_OUT pulse can still be detected with GPIO 27.

The example in the [RTCreboot](RTCreboot) directory shows how to use the wake-on-alarm function.
When the pogo pin is not connected to the Raspberry Pi reset header you can stil detect the RTC_OUT pulses
using GPIO 27. See [RTCgpio](RTCgpio).

But first how to use the clock function to keep the time on the Pi between boots.

# Using the Hardware Clock

Raspbian Jessie has built-in support for the MCP7940N provided by the rtc_ds1307 module.
This module supports various i2c based real time clocks including the MCP7940N.
Add the following line to `/boot/config.txt`:
```
dtoverlay=i2c-rtc,mcp7940x=1
```
In order to set the system time from the hardware clock at boot you need to modify `/lib/udev/hwclock-set` by
commenting out the check for systemd:
```
#!/bin/sh
# Reset the System Clock to UTC if the hardware clock from which it
# was copied by the kernel was in localtime.

dev=$1

#if [ -e /run/systemd/system ] ; then
# exit 0
#fi

if [ -e /run/udev/hwclock-set ]; then
exit 0
fi
```
The reason for this is that standard Debian assumes the system time is already set from the hardware clock by systemd.
This requires a built-in hardware clock driver in the kernel since systemd does this before any modules are loaded.
Since the Pi can only use external hardware clocks, Raspbian only supports the hardware clock driver as a module.
Therefore we have to set the system clock from the hardware clock when udev detects the hardware clock.
Note that the system clock (and hence the hardware clock) on a Linux system is usually kept in UTC.

After a reboot you can check if the MCP7940N has been recognized.
In the dmesg output you should find something like:
```
[ 5.735640] rtc-ds1307 1-006f: rtc core: registered mcp7940x as rtc0
[ 5.735692] rtc-ds1307 1-006f: 64 bytes nvram
```
If you have i2c-tools installed you can look at the i2cdetect output:
```
pi@papirus-hat:~ $ i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- UU
70: -- -- -- -- -- -- -- --
```
The `UU` for MCP7940N address 0x6f means this address is used by a kernel driver (rtc_ds1307).
Address 0x48 is the temperature sensor on the Papirus hat.

You can check the current hardware clock time with the command `sudo hwclock -r`.
For more info try `sudo hwclock -r --debug`. Here is a sample output:
```
pi@papirus-hat:~ $ sudo hwclock -r --debug
hwclock from util-linux 2.25.2
Using the /dev interface to the clock.
Last drift adjustment done at 1470071156 seconds after 1969
Last calibration done at 1470071156 seconds after 1969
Hardware clock is on UTC time
Assuming hardware clock is kept in UTC time.
Waiting for clock tick...
/dev/rtc does not have interrupt functions. Waiting in loop for time from /dev/rtc to change
...got clock tick
Time read from Hardware Clock: 2016/08/05 19:33:53
Hw clock time : 2016/08/05 19:33:53 = 1470425633 seconds since 1969
Fri 05 Aug 2016 21:33:53 CEST -0.133460 seconds
```
Note the hardware clock is in UTC, but the time is presented in CEST (Central European Summer Time).

You can set the hardware clock to the system clock with `sudo hwclock -w`
When you are connected to the internet you can just wait about 15 minutes.
The system (provided the ntp daemon is enabled, which it normally is in Raspbian) will copy the system
time to the hardware clock approximately once every 15 minutes.

Systemd provides a hwclock-save service which is run at shutdown. However when ntp is installed (/usr/sbin/ntpd
is an executable file) the system time is *not* copied to the hardware clock. Therefore I recommend to
comment out the ntpd check in `/lib/systemd/system/hwclock-save.service`:
```
[Unit]
Description=Synchronise Hardware Clock to System Clock
DefaultDependencies=no
Before=shutdown.target
#ConditionFileIsExecutable=!/usr/sbin/ntpd
ConditionFileIsExecutable=!/usr/sbin/openntpd
ConditionFileIsExecutable=!/usr/sbin/chrony
ConditionVirtualization=!container

[Service]
Type=oneshot
ExecStart=/sbin/hwclock -D --systohc

[Install]
WantedBy=reboot.target halt.target poweroff.target
```
This ensures the hardware clock is always set from the system clock at shutdown independent from the network and ntp.

# Accessing the MCP7940N from Python with the kernel driver (rtc_ds1307) loaded

If you try to access the MCP7940N registers with i2cget or i2cset (from i2c-tools) or from Python with the smbus
module you will get errors since the kernel has already claimed i2c address 0x6f.
In order to be able to set the MCP7940N alarm registers from Python we need to be able to access i2c addres 0x6f.

I have modified the smbus module (renamed to smbusf) so it allows access to an i2c device which the kernel has
already claimed. Smbusf uses the same mechanism as the '-f' option of i2cget/i2cset.
See the [py-smbusf](py-smbusf) directory for details how to build and install this module.
Both the MCP7940N Python examples use the smbusf module. The smbusf calling interface is identical to the standard smbus
module.

# The Sample Programs

## Pogo pin connected to the Raspberry Pi reset header

See the [RTCreboot](RTCreboot) directory for details.

## Detecting the alarm output on GPIO 27 when the pogo pin is not connected

See the [RTCgpio](RTCgpio) directory for details.

## Video

See both in action on [YouTube](https://youtu.be/H8aviSTrx4Q).
27 changes: 27 additions & 0 deletions RTC-Hat-Examples/RTCgpio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# RTC GPIO

This demo shows the 3 different output modes for the MFP (Multi Function Pin) of the MCP7940N hardware clock:
- Alarm output from the two independent alarm clocks.
- Square wave output of various frequencies (the program only uses the lowest one: 1 Hz).
- Direct programming of the MFP output.

Remember that the circuit on the PaPiRus only generates a pulse on GPIO 27 on a high to low transition
of the MFP output.

Also this program relies on the smbusf module (See [py_smbusf](../py-smbusf) for build and installation instructions).

## Alarm output

We set the two alarms 10 seconds apart and wait for them to trigger.
Only one alarm can be enabled at the same time. The first alarm is enabled.
When the first alarm triggers, we disable it and enable the second alarm.
See the datasheet, section 5.5 for details.

## Square wave output

A square wave can be output of 1 Hz, 4096 kHz, 8192 kHz and 32768 kHz.
The demo uses the 1 Hz output becuase we want to show the individual interrupts on the Papirus display.

## Direct programming

The demo generates 5 interrupts 1 second apart followed by 5 interrupts 2 seconds apart.
1 change: 1 addition & 0 deletions RTC-Hat-Examples/RTCgpio/prtc.py
1 change: 1 addition & 0 deletions RTC-Hat-Examples/RTCgpio/pwrite_text.py
159 changes: 159 additions & 0 deletions RTC-Hat-Examples/RTCgpio/rtcgpio
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/usr/bin/env python

from smbusf import SMBus
from datetime import datetime, timedelta
from prtc import readrtc, writealm, readalm
from prtc import enablealm0, enablealm1, disablealm0, disablealm1
from prtc import enablesqw, disablesqw, mfpoutput
from papirus import Papirus
from pwrite_text import write_text, replace_line, update_lines
import os
import time
import RPi.GPIO as GPIO
import threading

def alm_callback(channel):
global nrint, alm0time, alm1time
if nrint == 0:
# If you want to use both alarms to trigger the interrupt, then only one alarm can be enabled.
# See the datasheet section 5.5 for details.
# On the first interrupt disable alarm 0 en enable alarm 1
disablealm0(i2cbus)
enablealm1(i2cbus)
alm0time = readrtc(i2cbus).strftime('%H:%M:%S') + ' UTC'
elif nrint == 1:
alm1time = readrtc(i2cbus).strftime('%H:%M:%S') + ' UTC'
nrint = nrint + 1

def sqw_callback(channel):
global nrint
nrint = nrint + 1

def mfpoutputtask():
for i in range(4):
mfpoutput(i2cbus, 0)
time.sleep(0.5)
mfpoutput(i2cbus, 1)
time.sleep(0.5)
for i in range(6):
mfpoutput(i2cbus, 0)
time.sleep(1)
mfpoutput(i2cbus, 1)
time.sleep(1)
mfpoutput(i2cbus, 0)
mfpoutput(i2cbus, 1)

i2cbus = SMBus(1)
papirus = Papirus()
papirus.clear()
GPIO.setmode(GPIO.BCM)
GPIO.setup(27, GPIO.IN)

# --------- 1. Use of the two alarms ---------

# Set up the GPIO 27 interrupt and disable both alarms on the MCP7940N

nrint = 0
GPIO.add_event_detect(27, GPIO.FALLING, callback = alm_callback)
disablealm0(i2cbus)
disablealm1(i2cbus)

# Set the two alarm times

dtrtc = readrtc(i2cbus)
alarm0 = dtrtc + timedelta(seconds = 15)
alarm1 = dtrtc + timedelta(seconds = 25)
writealm(i2cbus, 0, alarm0)
writealm(i2cbus, 1, alarm1)

# Display the two alarm times on the Papirus diplay

alm0time = readalm(i2cbus, 0).strftime('%H:%M:%S')
alm1time = readalm(i2cbus, 1).strftime('%H:%M:%S')
write_text(papirus, 'Alarm Times Set:', x=20)
replace_line(papirus, 15, 20, 'Alm0: ' + alm0time + ' UTC')
replace_line(papirus, 15, 40, 'Alm1: ' + alm1time + ' UTC')
update_lines(papirus)

# Enable alarm 0
enablealm0(i2cbus)

# Display the hardware clock time and wait for the 2 alarms
prevsec = -1
alm0time = alm1time = ''
while nrint <= 2:
dtrtc = readrtc(i2cbus)
if dtrtc.second != prevsec:
replace_line(papirus, 60, 150, dtrtc.strftime('%H:%M:%S') + ' UTC')
prevsec = dtrtc.second
if alm0time != '':
replace_line(papirus, 15, 80, 'Alm0 at ' + alm0time)
alm0time = ''
if alm1time != '':
replace_line(papirus, 15, 100, 'Alm1 at ' + alm1time)
alm1time = ''
update_lines(papirus)
if nrint == 2:
break
time.sleep(0.1)

GPIO.remove_event_detect(27)
time.sleep(0.5)
replace_line(papirus, 60, 150, '')
update_lines(papirus)
time.sleep(2)

# --------- 2. 1 Hz swuare wave ---------

write_text(papirus, '1 Hz square wave', x=15)

GPIO.add_event_detect(27, GPIO.FALLING, callback = sqw_callback)
enablesqw(i2cbus)

nrint = prevnrint = 0
prevsec = -1
while nrint < 21:
dtrtc = readrtc(i2cbus)
if dtrtc.second != prevsec:
replace_line(papirus, 60, 150, dtrtc.strftime('%H:%M:%S') + ' UTC')
prevsec = dtrtc.second
if nrint != prevnrint:
replace_line(papirus, 60, 65, 'Interrupt ' + format(nrint))
prevnrint = nrint
update_lines(papirus)
time.sleep(0.1)

GPIO.remove_event_detect(27)
disablesqw(i2cbus)
time.sleep(0.5)
replace_line(papirus, 60, 150, '')
update_lines(papirus)
time.sleep(2)

# --------- 3. Set mfp output directly ---------

write_text(papirus, 'Programmed output', x=20)

GPIO.add_event_detect(27, GPIO.FALLING, callback = sqw_callback)

t = threading.Thread(target=mfpoutputtask)
t.start()

nrint = prevnrint = 0
prevsec = -1
while t.isAlive():
dtrtc = readrtc(i2cbus)
if dtrtc.second != prevsec:
replace_line(papirus, 60, 150, dtrtc.strftime('%H:%M:%S') + ' UTC')
prevsec = dtrtc.second
if nrint != prevnrint:
replace_line(papirus, 60, 65, 'Interrupt ' + format(nrint))
prevnrint = nrint
update_lines(papirus)
time.sleep(0.1)

replace_line(papirus, 60, 150, '')
update_lines(papirus)
time.sleep(2)

write_text(papirus, 'End of Demo', x=50, y=papirus.height/2 - 10)
27 changes: 27 additions & 0 deletions RTC-Hat-Examples/RTCreboot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Rebooting the PI using the wake-on-alarm function of the MCP7940N

This example uses two programs:

* `rtcreboot` which sets the alarm a number of seconds in the future and then shuts down the RPi.
The default is 120 seconds. You can specify a different delay (in seconds) as an argument to rtcreboot.
`rtcreboot` writes the expected reboot time on the Papirus display.

* the second program is `bootinfo` which should be run at system boot. This writes the start time (the output
from `uptime -s`, the current system time and the hardware clock time (normally in UTC) to the Papirus display.

Both programs rely on the smbusf module (See [py_smbusf](../py-smbusf) for build and installation instructions).
In order to run `bootinfo` at system boot add the following line to `/etc/rc.local`:
```
sudo -u pi /home/pi/PaPiRus/RTC-Hat-Examples/RTCreboot/bootinfo
```
Modify the path as required for your installation.
`bootinfo` has to be run as user pi, since the smbusf module is only installed for user pi when you follow the
instructions in [py_smbusf](../py-smbusf).

Both programs use support functions in `prtc.py` (hardware clock access functions) and `pwrite_text.py` (custom
version to write text on the Papirus display)

The programs text output is layed out for the large Papirus display (2.7 inch 264 x 176).

For the programming details of the MCP7940N the [datasheet](../mcp7940n.pdf) is the best reference.
See section 5 and especially section 5.4 about alarms.
21 changes: 21 additions & 0 deletions RTC-Hat-Examples/RTCreboot/bootinfo
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#! /usr/bin/env python

from papirus import Papirus
from prtc import readrtc, disablealm0
from smbusf import SMBus
from pwrite_text import write_text
from subprocess import check_output
from datetime import datetime
import os

papirus = Papirus()
i2cbus = SMBus(1)

disablealm0(i2cbus)

upsince = check_output(["uptime", "-s"])
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
rtc = readrtc(i2cbus).strftime('%Y-%m-%d %H:%M:%S')

write_text(papirus, 'System up since ' + upsince + ' Current time ' + now + ' Real Time Clock time ' + rtc, y = 50, load = True, ldfile = os.environ['HOME'] + '/rtcreboot.bmp')

Loading