Redesign based on libgpiod and dma PWM for MC 4
This commit is contained in:
parent
39a3e5d9bc
commit
e661ed5902
46
Makefile.am
46
Makefile.am
|
@ -6,35 +6,25 @@ AM_CPPFLAGS = $(DEPS_CFLAGS)
|
|||
DISTCLEANFILES = src/mc-hid-server-glue.hpp \
|
||||
script/init.d/mediacore-hid
|
||||
|
||||
I2C_SRC = src/i2c/i2c.c \
|
||||
src/i2c/i2c.h
|
||||
|
||||
MCP_GPIO_SRC = src/gpio/gpio.hpp \
|
||||
src/gpio/gpio.cpp \
|
||||
src/mcp23017/mcp23017.hpp \
|
||||
src/mcp23017/mcp23017.cpp \
|
||||
src/exception/baseexceptions.hpp \
|
||||
src/exception/baseexceptions.cpp \
|
||||
$(I2C_SRC)
|
||||
|
||||
SUPPORT_SRC = src/log/log.hpp \
|
||||
src/log/log.cpp \
|
||||
src/thread/thread.hpp \
|
||||
src/thread/thread.cpp \
|
||||
src/buttontimer/buttontimer.hpp \
|
||||
src/buttontimer/buttontimer.cpp
|
||||
src/buttontimer/buttontimer.cpp \
|
||||
src/gpio/gpio.hpp \
|
||||
src/gpio/gpio.cpp \
|
||||
src/gpio/dma.hpp \
|
||||
src/gpio/dma.cpp \
|
||||
src/gpio/mailbox.h \
|
||||
src/gpio/mailbox.c
|
||||
|
||||
sbin_PROGRAMS = mediacore-hid-server
|
||||
check_PROGRAMS = mcp23017-i2ctest
|
||||
TESTS = mcp23017-i2ctest
|
||||
|
||||
EXTRA_DIST = \
|
||||
cfg/dbus/nl.miqra.MediaCore.Hid.conf \
|
||||
EXTRA_DIST = cfg/dbus/nl.miqra.MediaCore.Hid.conf \
|
||||
cfg/init.d/mediacore-hid.in \
|
||||
cfg/rsyslog/syslog.MediaCore.Hid.conf \
|
||||
src/mc-hid-introspect.xml \
|
||||
cfg/modules \
|
||||
cfg/modprobe.d/raspi-blacklist.conf
|
||||
src/mc-hid-introspect.xml
|
||||
|
||||
init_d_dirdir = $(sysconfdir)/init.d
|
||||
init_d_dir_SCRIPTS = cfg/init.d/mediacore-hid
|
||||
|
@ -45,32 +35,22 @@ dbus_conf_DATA = cfg/dbus/nl.miqra.MediaCore.Hid.conf
|
|||
rsyslog_confdir = $(sysconfdir)/rsyslog.d
|
||||
rsyslog_conf_DATA = cfg/rsyslog/syslog.MediaCore.Hid.conf
|
||||
|
||||
etc_dirdir = $(sysconfdir)
|
||||
etc_dir_DATA = cfg/modules
|
||||
|
||||
modprobe_d_dirdir = $(sysconfdir)/modprobe.d
|
||||
modprobe_d_dir_DATA = cfg/modprobe.d/raspi-blacklist.conf
|
||||
|
||||
|
||||
src/mc-hid-server-glue.hpp: src/mc-hid-introspect.xml
|
||||
dbusxx-xml2cpp $^ --adaptor=$@
|
||||
|
||||
BUILT_SOURCES = src/mc-hid-server-glue.hpp
|
||||
|
||||
mediacore_hid_server_CPPFLAGS = -std=c++17 -I/usr/include/dbus-c++-1/ -I/opt/vc/include/
|
||||
mediacore_hid_server_SOURCES = src/mc-hid-server.cpp \
|
||||
src/mc-hid-server.hpp \
|
||||
src/mc-hid-server-glue.hpp \
|
||||
$(MCP_GPIO_SRC) \
|
||||
src/exception/baseexceptions.hpp \
|
||||
src/exception/baseexceptions.cpp \
|
||||
$(SUPPORT_SRC)
|
||||
|
||||
mediacore_hid_server_LDADD = $(DEPS_LIBS) -lpthread -lrt
|
||||
mediacore_hid_server_LDADD = $(DEPS_LIBS) -L/opt/vc/lib -lpthread -lrt -lgpiodcxx -lbcm_host
|
||||
|
||||
|
||||
mcp23017_i2ctest_SOURCES = src/test/mcp23017-i2ctest.c \
|
||||
$(I2C_SRC)
|
||||
|
||||
mcp23017_i2ctest_LDADD = -lpthread
|
||||
|
||||
cfg/init.d/mediacore-hid: cfg/init.d/mediacore-hid.in
|
||||
cat $^ > $@
|
||||
sed -i "s#@BIN_DIR@#$(bindir)#" $@
|
||||
|
|
163
cfg/init.d/mediacore-hid
Normal file
163
cfg/init.d/mediacore-hid
Normal file
|
@ -0,0 +1,163 @@
|
|||
#! /bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: mediacorehid
|
||||
# Required-Start: $syslog $dbus
|
||||
# Required-Stop: $syslog $dbus
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Example initscript
|
||||
# Description: This file should be used to construct scripts to be
|
||||
# placed in /etc/init.d.
|
||||
### END INIT INFO
|
||||
|
||||
# Author: Miqra Engineering <@miqra.nl>
|
||||
#
|
||||
# Please remove the "Author" lines above and replace them
|
||||
# with your own name if you copy and modify this script.
|
||||
|
||||
# Do NOT "set -e"
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
PATH=/sbin:/bin:/usr/sbin:/usr/bin
|
||||
DESC="Mediacore HID interface server"
|
||||
NAME=mediacore-hid-server
|
||||
DAEMON=/usr/local/sbin/$NAME
|
||||
DAEMON_ARGS=""
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
SCRIPTNAME=/usr/local/etc/init.d/$NAME
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x "$DAEMON" ] || exit 0
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
VERBOSE=yes
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
|
||||
# and status_of_proc is working.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
#
|
||||
# Function that starts the daemon/service
|
||||
#
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
start-stop-daemon --start --quiet --background -m --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --background -m --pidfile $PIDFILE --exec $DAEMON -- \
|
||||
$DAEMON_ARGS \
|
||||
|| return 2
|
||||
# Add code here, if necessary, that waits for the process to be ready
|
||||
# to handle requests from services started subsequently which depend
|
||||
# on this one. As a last resort, sleep for some time.
|
||||
sleep 0.3
|
||||
dbus-send --system --dest=nl.miqra.MediaCore.Hid --type=method_call /nl/miqra/MediaCore/Hid nl.miqra.MediaCore.Hid.SetColor byte:128 byte:0 byte:0
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --exec $DAEMON
|
||||
RETVAL="$?"
|
||||
[ "$RETVAL" = 2 ] && return 2
|
||||
# Wait for children to finish too if this is a daemon that forks
|
||||
# and if the daemon is only ever run from this initscript.
|
||||
# If the above conditions are not satisfied then add some other code
|
||||
# that waits for the process to drop all resources that could be
|
||||
# needed by services started subsequently. A last resort is to
|
||||
# sleep for some time.
|
||||
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
|
||||
[ "$?" = 2 ] && return 2
|
||||
# Many daemons don't delete their pidfiles when they exit.
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
#
|
||||
# Function that sends a SIGHUP to the daemon/service
|
||||
#
|
||||
do_reload() {
|
||||
#
|
||||
# If the daemon can reload its configuration without
|
||||
# restarting (for example, when it is sent a SIGHUP),
|
||||
# then implement that here.
|
||||
#
|
||||
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --exec $DAEMON
|
||||
return 0
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||
;;
|
||||
#reload|force-reload)
|
||||
#
|
||||
# If do_reload() is not implemented then leave this commented out
|
||||
# and leave 'force-reload' as an alias for 'restart'.
|
||||
#
|
||||
#log_daemon_msg "Reloading $DESC" "$NAME"
|
||||
#do_reload
|
||||
#log_end_msg $?
|
||||
#;;
|
||||
restart|force-reload)
|
||||
#
|
||||
# If the "reload" option is implemented then remove the
|
||||
# 'force-reload' alias
|
||||
#
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
|
||||
:
|
|
@ -1,4 +0,0 @@
|
|||
# blacklist spi and i2c by default (many users don't need them)
|
||||
|
||||
blacklist spi-bcm2708
|
||||
#blacklist i2c-bcm2708
|
|
@ -1,8 +0,0 @@
|
|||
# /etc/modules: kernel modules to load at boot time.
|
||||
#
|
||||
# This file contains the names of kernel modules that should be loaded
|
||||
# at boot time, one per line. Lines beginning with "#" are ignored.
|
||||
# Parameters can be specified after the module name.
|
||||
|
||||
snd-bcm2835
|
||||
i2c-dev
|
148
cfg/piio.conf
148
cfg/piio.conf
|
@ -1,148 +0,0 @@
|
|||
# Example configuration file for PiIo
|
||||
#
|
||||
# Configuration is done in groups called I/O groups.
|
||||
# Each IO group can only contain I/O's of one type, e.g. only Raspberry Pi GPIOs or only pins on one MCP23017 IO expander
|
||||
# for each type there are parameters
|
||||
# Each group has a unique name
|
||||
|
||||
# Note that in this example config file, all actual code is initially commented out to avoid problems directly after install
|
||||
|
||||
/*
|
||||
# Io Group called GPIO
|
||||
GPIO:
|
||||
{
|
||||
# it is of I/O type "GPIO", indicating it uses Raspberry Pi internal GPIOS
|
||||
type = "GPIO";
|
||||
|
||||
# Settings for this groups PWM generatoer
|
||||
pwm-tickdelay-us = 1600; # Number of microseconds between ticks
|
||||
pwm-ticks = 16; # Number of ticks in a pwm cycle
|
||||
|
||||
# Settings for the individual I/O's
|
||||
# Here too, each I/O has it's own type and it's own id.
|
||||
|
||||
io:
|
||||
{
|
||||
# Input called 'intest'
|
||||
intest:
|
||||
{
|
||||
# It is of type "MULTIBITIN", which means that it takes values from multiple
|
||||
# bits and combines it into a single number value.
|
||||
# (To use a multibit output, you can use the type "MULTIBITOUT", which takes the same configuration, minus the additional settings)
|
||||
type: "MULTIBITIN";
|
||||
|
||||
# The 'pins' options specified the pins that are used for the multibit input.
|
||||
# For the GPIO, it takes the BCM identifiers. Both revision 1 and 2 are supported, and converted to the proper pin automatically
|
||||
# Bit order is important, MSB first, LSB last.
|
||||
pins: [17, 18];
|
||||
|
||||
# Additional config options are:
|
||||
|
||||
// pullup: True/False # Default: False - Enable/disable internal pullup (currently not functional on GPIO type)
|
||||
// invert: True/False # Default: False - Invert the input pins before processing
|
||||
// int-enabled: True/False # Default: True - Trigger an event on value change for this input
|
||||
}
|
||||
|
||||
# Button called 'btn'
|
||||
btn:
|
||||
{
|
||||
# Input of type "BUTTON", which means a single input pin, treated as a button.
|
||||
# Buttons can trigger only 'Pressed' and 'Held' events, which trigger when the button is either pressed
|
||||
# for a minimum of (default) 25 ms, or held for a minimum of (default) 6000 ms
|
||||
# These values can be configured in the IO Group config
|
||||
type: "BUTTON";
|
||||
|
||||
# Button is connected on pin 4.
|
||||
pin: 4;
|
||||
|
||||
# Additional config options are:
|
||||
|
||||
// pullup: True/False # Default: True - Enable/disable internal pullup (currently not functional on GPIO type)
|
||||
// invert: True/False # Default: True - Invert the input pins before processing
|
||||
// int-enabled: True/False # Default: True - Trigger an event on value change for this input
|
||||
|
||||
# Note that defaults for buttons are different from defaults for other inputs (invert and pullup true by default for buttons)
|
||||
|
||||
}
|
||||
|
||||
# Output called 'led1'
|
||||
led1:
|
||||
{
|
||||
# Output of type "OUTPUTPIN", which means a single output pin.
|
||||
type: "OUTPUTPIN";
|
||||
|
||||
# Output on pin 23
|
||||
pin: 23;
|
||||
};
|
||||
# Output called 'led1'
|
||||
led2:
|
||||
{
|
||||
# Output of type "OUTPUTPIN", which means a single output pin.
|
||||
type: "OUTPUTPIN";
|
||||
# Output on pin 24
|
||||
pin: 24;
|
||||
};
|
||||
# PWM Output called 'led3'
|
||||
led3:
|
||||
{
|
||||
# Output of type "PWMPIN", which means pwm on a single output pin.
|
||||
# Note that using PWM takes up processor time,
|
||||
# currently around 5% for mcp23017 pins, and up to 30% for GPIO pins. (This last value is being worked on)
|
||||
type: "PWMPIN";
|
||||
|
||||
# Output on pin 23
|
||||
pin: 25;
|
||||
};
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
# Io Group called GPIO
|
||||
MCP1:
|
||||
{
|
||||
# it is of I/O type "MCP23017", indicating it uses an MCP23017 I2C I/O expander chip
|
||||
type = "MCP23017";
|
||||
|
||||
# The MCP23017 needs an I2C address, and optionally a GPIO I/O pin to receive interrupts on
|
||||
address = 0x20;
|
||||
intpin = 22;
|
||||
|
||||
# Settings for the individual I/O's
|
||||
# Here too, each I/O has it's own type and it's own id.
|
||||
|
||||
# Note that the pin id's for an MCP23017 are counted from the GPA0 as pin 0 up to GPB7 as pin 15
|
||||
io:
|
||||
{
|
||||
# Input called 'sensor1'
|
||||
sensor1:
|
||||
{
|
||||
# It is of type "INPUTPIN", which means that it takes the value from the single input pin
|
||||
type: "INPUTPIN";
|
||||
|
||||
# Input on pin 7
|
||||
pin: 7;
|
||||
}
|
||||
leda:
|
||||
{
|
||||
# Output of type "PWMPIN", which means pwm on a single output pin.
|
||||
# Note that using PWM takes up processor time,
|
||||
# currently around 5% for mcp23017 pins, and up to 30% for GPIO pins. (This last value is being worked on)
|
||||
type: "PWMPIN";
|
||||
|
||||
# Output on pin 8
|
||||
pin: 8;
|
||||
}
|
||||
ledb:
|
||||
{
|
||||
# Output of type "PWMPIN", which means pwm on a single output pin.
|
||||
# Note that using PWM takes up processor time,
|
||||
# currently around 5% for mcp23017 pins, and up to 30% for GPIO pins. (This last value is being worked on)
|
||||
type: "PWMPIN";
|
||||
|
||||
# Output on pin 9
|
||||
pin: 9;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -11,7 +11,7 @@
|
|||
#define PACKAGE_NAME "MediaCore HID Server"
|
||||
|
||||
/* Define to the full name and version of this package. */
|
||||
#define PACKAGE_STRING "MediaCore HID Server 1.0.0"
|
||||
#define PACKAGE_STRING "MediaCore HID Server 4.0.0"
|
||||
|
||||
/* Define to the one symbol short name of this package. */
|
||||
#define PACKAGE_TARNAME "mediacore-hid"
|
||||
|
@ -20,4 +20,4 @@
|
|||
#define PACKAGE_URL "http://www.miqra.nl/"
|
||||
|
||||
/* Define to the version of this package. */
|
||||
#define PACKAGE_VERSION "1.0.0"
|
||||
#define PACKAGE_VERSION "4.0.0"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
AC_INIT([MediaCore HID Server], [1.0.0], [bugs@miqra.nl], [mediacore-hid], [http://www.miqra.nl/])
|
||||
AC_INIT([MediaCore HID Server], [4.0.0], [bugs@miqra.nl], [mediacore-hid], [http://www.miqra.nl/])
|
||||
AC_PREREQ([2.59])
|
||||
AM_INIT_AUTOMAKE([1.11 no-define foreign subdir-objects])
|
||||
AC_CONFIG_HEADERS([config.hpp])
|
||||
|
|
|
@ -3,4 +3,5 @@ libboost-all-dev
|
|||
libdbus-c++-bin
|
||||
libdbus-c++-dev
|
||||
libdbus-1-dev
|
||||
libpigpio-dev
|
||||
|
||||
|
|
8
debian/control
vendored
8
debian/control
vendored
|
@ -1,15 +1,13 @@
|
|||
Source: mediacore-hid
|
||||
Section: misc
|
||||
Suite: stable
|
||||
Priority: optional
|
||||
Maintainer: Miqra Engineering Packaging <packaging@miqra.nl>
|
||||
Build-Depends: debhelper (>= 8.0.0), autotools-dev, autoconf-archive, libboost-all-dev, libdbus-c++-bin, libdbus-c++-dev, libdbus-1-dev, libtclap-dev
|
||||
Standards-Version: 3.9.3
|
||||
Debian-Version: 1
|
||||
Build-Depends: debhelper (>= 8.0.0), autotools-dev, autoconf-archive, libboost-all-dev, libdbus-c++-bin, libdbus-c++-dev, libdbus-1-dev, libgpiod-dev, libraspberrypi-dev
|
||||
Standards-Version: 4.3.0
|
||||
Homepage: <insert the upstream URL, if relevant>
|
||||
|
||||
Package: mediacore-hid
|
||||
Architecture: any
|
||||
Architecture: armhf
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
Description: I/O System for Mediacore Interface
|
||||
I/O server system for mediacore that exposes the i/o through DBUS for use in other applications
|
||||
|
|
|
@ -46,7 +46,7 @@ call(["tar","-xzvf",DEBSOURCEPKG])
|
|||
print "Entering dir " + PKGDIR
|
||||
os.chdir(PKGDIR)
|
||||
print "Now in ", os.getcwd()
|
||||
call(["dh_make --single -yes --copyright bsd"],shell=True)
|
||||
call(["dh_make --single --yes --copyright lgpl"],shell=True)
|
||||
|
||||
for f in glob.glob(os.path.join(CWD,"debian","*")):
|
||||
dst = os.path.join(CWD,PKGPATH,"debian",os.path.basename(f))
|
||||
|
|
BIN
mediacore-hid-server
Executable file
BIN
mediacore-hid-server
Executable file
Binary file not shown.
|
@ -20,7 +20,7 @@ ButtonTimer::~ButtonTimer()
|
|||
|
||||
}
|
||||
|
||||
void ButtonTimer::RegisterPress(uint16_t keycode)
|
||||
void ButtonTimer::RegisterPress(const uint8_t keycode)
|
||||
{
|
||||
MutexLock();
|
||||
// Prevent trouble when calling this from within one of our event listeners
|
||||
|
@ -31,21 +31,18 @@ void ButtonTimer::RegisterPress(uint16_t keycode)
|
|||
}
|
||||
|
||||
|
||||
void ButtonTimer::RegisterRelease(uint16_t keycode)
|
||||
void ButtonTimer::RegisterRelease(const uint8_t keycode)
|
||||
{
|
||||
uint64_t now = now_ms();
|
||||
uint64_t then;
|
||||
MutexLock();
|
||||
// Prevent trouble when calling this from within one of our event listeners
|
||||
if(eventLock) { MutexUnlock(); return; }
|
||||
|
||||
|
||||
|
||||
if(pressRegistry.count(keycode))
|
||||
{
|
||||
// if it was a long press, the key code would already have been erased, so we
|
||||
// can safely fire the onShortPress event
|
||||
then = pressRegistry[keycode];
|
||||
uint64_t now = now_ms();
|
||||
uint64_t then = pressRegistry[keycode];
|
||||
// remove from registry after release, if it was a long press, the event should have already been fired
|
||||
pressRegistry.erase(keycode);
|
||||
|
||||
|
@ -60,7 +57,7 @@ void ButtonTimer::RegisterRelease(uint16_t keycode)
|
|||
MutexUnlock();
|
||||
}
|
||||
|
||||
void ButtonTimer::CancelPress(uint16_t keycode)
|
||||
void ButtonTimer::CancelPress(const uint8_t keycode)
|
||||
{
|
||||
// remove button id from map (but only if it is in the map already)
|
||||
MutexLock();
|
||||
|
@ -77,12 +74,12 @@ void ButtonTimer::CancelPress(uint16_t keycode)
|
|||
void ButtonTimer::ThreadLoop()
|
||||
{
|
||||
uint64_t now = now_ms();
|
||||
std::list<uint16_t> btnList;
|
||||
std::list<uint8_t> btnList;
|
||||
boost::optional<bool> valid;
|
||||
MutexLock();
|
||||
// list through all the items
|
||||
|
||||
for( std::map<uint16_t,uint64_t>::iterator ii=pressRegistry.begin(); ii!=pressRegistry.end(); ++ii)
|
||||
for( std::map<uint8_t,uint64_t>::iterator ii=pressRegistry.begin(); ii!=pressRegistry.end(); ++ii)
|
||||
{
|
||||
if(now - (ii->second) >= longpressTime) // if it is in overtime
|
||||
{
|
||||
|
@ -92,7 +89,7 @@ void ButtonTimer::ThreadLoop()
|
|||
}
|
||||
|
||||
// Process listed items
|
||||
for (std::list<uint16_t>::iterator it=btnList.begin(); it != btnList.end(); ++it)
|
||||
for (std::list<uint8_t>::iterator it=btnList.begin(); it != btnList.end(); ++it)
|
||||
{
|
||||
pressRegistry.erase(*it);
|
||||
// If any validators are connected, they can retun false to indicate that this connection is not
|
||||
|
|
|
@ -11,13 +11,13 @@ class ButtonTimer : protected Thread
|
|||
ButtonTimer(uint32_t shortpress_min_ms, uint32_t longpress_ms);
|
||||
~ButtonTimer();
|
||||
|
||||
void RegisterPress(uint16_t keycode);
|
||||
void RegisterRelease(uint16_t keycode);
|
||||
void CancelPress(uint16_t id);
|
||||
void RegisterPress(const uint8_t keycode);
|
||||
void RegisterRelease(const uint8_t keycode);
|
||||
void CancelPress(const uint8_t keycode);
|
||||
|
||||
boost::signals2::signal<void (uint16_t keycode)> onShortPress;
|
||||
boost::signals2::signal<void (uint16_t keycode)> onLongPress;
|
||||
boost::signals2::signal<bool (uint16_t keycode)> onValidatePress;
|
||||
boost::signals2::signal<void (const uint8_t keycode)> onShortPress;
|
||||
boost::signals2::signal<void (const uint8_t keycode)> onLongPress;
|
||||
boost::signals2::signal<bool (const uint8_t keycode)> onValidatePress;
|
||||
|
||||
protected:
|
||||
virtual void ThreadLoop(void);
|
||||
|
@ -26,7 +26,7 @@ class ButtonTimer : protected Thread
|
|||
boost::signals2::connection onThreadErrorConnection;
|
||||
uint32_t longpressTime;
|
||||
uint32_t shortpressMinTime;
|
||||
std::map<uint16_t, uint64_t> pressRegistry;
|
||||
std::map<const uint8_t, uint64_t> pressRegistry;
|
||||
|
||||
bool eventLock;
|
||||
static int64_t now_ms(void);
|
||||
|
|
796
src/gpio/dma.cpp
Normal file
796
src/gpio/dma.cpp
Normal file
|
@ -0,0 +1,796 @@
|
|||
/*
|
||||
* This file is part of RPIO-PWM.
|
||||
*
|
||||
* Copyright
|
||||
*
|
||||
* Copyright (C) 2020 Xinkai Wang <xinkaiwang1017@gmail.com>
|
||||
*
|
||||
* License
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details at
|
||||
* <http://www.gnu.org/licenses/lgpl-3.0-standalone.html>
|
||||
*
|
||||
* Documentation
|
||||
*
|
||||
* https://github.com/xinkaiwang/rpio-pwm
|
||||
*
|
||||
* dma.c, based on the excellent servod.c by Richard Hirst, provides flexible
|
||||
* PWM via DMA for the Raspberry Pi, supporting a resolution of up to 1us,
|
||||
* all 15 DMA channels, multiple GPIOs per channel, timing by PWM (default)
|
||||
* or PCM, a Python wrapper, and more.
|
||||
*
|
||||
* Feedback is much appreciated.
|
||||
*/
|
||||
|
||||
#include "dma.hpp"
|
||||
|
||||
#include <bcm_host.h>
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "mailbox.h"
|
||||
|
||||
#define DMY 255 // Used to represent an invalid P1 pin, or unmapped servo
|
||||
|
||||
#define NUM_P1PINS 40
|
||||
#define NUM_P5PINS 8
|
||||
|
||||
#define MAX_MEMORY_USAGE \
|
||||
(16 * 1024 * 1024) /* Somewhat arbitrary limit of 16MB */
|
||||
|
||||
#define DEFAULT_CYCLE_TIME_US 20000
|
||||
#define DEFAULT_STEP_TIME_US 10
|
||||
#define DEFAULT_SERVO_MIN_US 500
|
||||
#define DEFAULT_SERVO_MAX_US 2500
|
||||
|
||||
#define DEVFILE "/dev/servoblaster"
|
||||
#define CFGFILE "/dev/servoblaster-cfg"
|
||||
|
||||
#define PAGE_SIZE 4096
|
||||
#define PAGE_SHIFT 12
|
||||
|
||||
#define DMA_CHAN_SIZE 0x100
|
||||
#define DMA_CHAN_MIN 0
|
||||
#define DMA_CHAN_MAX 14
|
||||
#define DMA_CHAN_DEFAULT 14
|
||||
#define DMA_CHAN_PI4 7
|
||||
|
||||
#define DMA_BASE_OFFSET 0x00007000
|
||||
#define DMA_LEN DMA_CHAN_SIZE *(DMA_CHAN_MAX + 1)
|
||||
#define PWM_BASE_OFFSET 0x0020C000
|
||||
#define PWM_LEN 0x28
|
||||
#define CLK_BASE_OFFSET 0x00101000
|
||||
#define CLK_LEN 0xA8
|
||||
#define GPIO_BASE_OFFSET 0x00200000
|
||||
#define GPIO_LEN 0x100
|
||||
#define PCM_BASE_OFFSET 0x00203000
|
||||
#define PCM_LEN 0x24
|
||||
|
||||
#define DMA_VIRT_BASE(hw) ((hw).periph_virt_base + DMA_BASE_OFFSET)
|
||||
#define PWM_VIRT_BASE(hw) ((hw).periph_virt_base + PWM_BASE_OFFSET)
|
||||
#define CLK_VIRT_BASE(hw) ((hw).periph_virt_base + CLK_BASE_OFFSET)
|
||||
#define GPIO_VIRT_BASE(hw) ((hw).periph_virt_base + GPIO_BASE_OFFSET)
|
||||
#define PCM_VIRT_BASE(hw) ((hw).periph_virt_base + PCM_BASE_OFFSET)
|
||||
|
||||
#define PWM_PHYS_BASE(hw) ((hw).periph_phys_base + PWM_BASE_OFFSET)
|
||||
#define PCM_PHYS_BASE(hw) ((hw).periph_phys_base + PCM_BASE_OFFSET)
|
||||
#define GPIO_PHYS_BASE(hw) ((hw).periph_phys_base + GPIO_BASE_OFFSET)
|
||||
|
||||
#define DMA_NO_WIDE_BURSTS (1 << 26)
|
||||
#define DMA_WAIT_RESP (1 << 3)
|
||||
#define DMA_D_DREQ (1 << 6)
|
||||
#define DMA_PER_MAP(x) ((x) << 16)
|
||||
#define DMA_END (1 << 1)
|
||||
#define DMA_RESET (1U << 31)
|
||||
#define DMA_INT (1 << 2)
|
||||
|
||||
#define DMA_CS (0x00 / 4)
|
||||
#define DMA_CONBLK_AD (0x04 / 4)
|
||||
#define DMA_SOURCE_AD (0x0c / 4)
|
||||
#define DMA_DEBUG (0x20 / 4)
|
||||
|
||||
#define GPIO_FSEL0 (0x00 / 4)
|
||||
#define GPIO_SET0 (0x1c / 4)
|
||||
#define GPIO_CLR0 (0x28 / 4)
|
||||
#define GPIO_LEV0 (0x34 / 4)
|
||||
#define GPIO_PULLEN (0x94 / 4)
|
||||
#define GPIO_PULLCLK (0x98 / 4)
|
||||
|
||||
#define GPIO_MODE_IN 0
|
||||
#define GPIO_MODE_OUT 1
|
||||
|
||||
#define PWM_CTL (0x00 / 4)
|
||||
#define PWM_DMAC (0x08 / 4)
|
||||
#define PWM_RNG1 (0x10 / 4)
|
||||
#define PWM_FIFO (0x18 / 4)
|
||||
|
||||
#define PWMCLK_CNTL 40
|
||||
#define PWMCLK_DIV 41
|
||||
|
||||
#define PWMCTL_MODE1 (1 << 1)
|
||||
#define PWMCTL_PWEN1 (1 << 0)
|
||||
#define PWMCTL_CLRF (1 << 6)
|
||||
#define PWMCTL_USEF1 (1 << 5)
|
||||
|
||||
#define PWMDMAC_ENAB (1U << 31)
|
||||
#define PWMDMAC_THRSHLD ((15 << 8) | (15 << 0))
|
||||
|
||||
#define PCM_CS_A (0x00 / 4)
|
||||
#define PCM_FIFO_A (0x04 / 4)
|
||||
#define PCM_MODE_A (0x08 / 4)
|
||||
#define PCM_RXC_A (0x0c / 4)
|
||||
#define PCM_TXC_A (0x10 / 4)
|
||||
#define PCM_DREQ_A (0x14 / 4)
|
||||
#define PCM_INTEN_A (0x18 / 4)
|
||||
#define PCM_INT_STC_A (0x1c / 4)
|
||||
#define PCM_GRAY (0x20 / 4)
|
||||
|
||||
#define PCMCLK_CNTL 38
|
||||
#define PCMCLK_DIV 39
|
||||
|
||||
#define PLLDFREQ_MHZ_DEFAULT 500
|
||||
#define PLLDFREQ_MHZ_PI4 750
|
||||
|
||||
// #define DELAY_VIA_PWM 0
|
||||
// #define DELAY_VIA_PCM 1
|
||||
|
||||
#define ROUNDUP(val, blksz) (((val) + ((blksz)-1)) & ~(blksz - 1))
|
||||
|
||||
#define BUS_TO_PHYS(x) ((x) & ~0xC0000000)
|
||||
|
||||
using namespace wpp;
|
||||
|
||||
namespace {
|
||||
|
||||
// DmaHardware hardware{};
|
||||
|
||||
// L362
|
||||
static struct {
|
||||
int handle; /* From mbox_open() */
|
||||
uint32_t size; /* Required size */
|
||||
unsigned mem_ref; /* From mem_alloc() */
|
||||
unsigned bus_addr; /* From mem_lock() */
|
||||
uint8_t *virt_addr; /* From mapmem() */
|
||||
} mbox;
|
||||
|
||||
void set_servo(DmaChannel &ch, int servo, int width);
|
||||
|
||||
// L375
|
||||
static void udelay(int us) {
|
||||
struct timespec ts = {0, us * 1000};
|
||||
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
// L384
|
||||
// void terminateChannel(DmaChannel &ch) {
|
||||
// if (ch.dma_reg && mbox.virt_addr) {
|
||||
// for (int i = 0; i < maxServoCount; i++) {
|
||||
// if (ch.pins[i]) {
|
||||
// set_servo(ch, i, 0);
|
||||
// }
|
||||
// }
|
||||
// udelay(ch.cycle_time_us);
|
||||
// ch.dma_reg[DMA_CS] = DMA_RESET;
|
||||
// udelay(10);
|
||||
// }
|
||||
// // if (restore_gpio_modes) {
|
||||
// // for (i = 0; i < MAX_SERVOS; i++) {
|
||||
// // if (servo2gpio[i] != DMY)
|
||||
// // gpio_set_mode(servo2gpio[i], gpiomode[i]);
|
||||
// // }
|
||||
// // }
|
||||
// }
|
||||
|
||||
void terminate(int dummy) {
|
||||
if (DmaHardware::GetInstance().current_log_level >= LogLevel::Info) {
|
||||
printf("terminate() %d\n", dummy);
|
||||
}
|
||||
for (auto &it : DmaHardware::GetInstance().channels) {
|
||||
if (auto locked = it.lock()) {
|
||||
locked->DeactivateChannel();
|
||||
}
|
||||
}
|
||||
|
||||
if (mbox.virt_addr != NULL) {
|
||||
unmapmem(mbox.virt_addr, mbox.size);
|
||||
mem_unlock(mbox.handle, mbox.mem_ref);
|
||||
mem_free(mbox.handle, mbox.mem_ref);
|
||||
if (mbox.handle >= 0)
|
||||
mbox_close(mbox.handle);
|
||||
}
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// L480
|
||||
static uint32_t gpio_get_mode(DmaChannel &ch, uint32_t gpio) {
|
||||
uint32_t fsel = ch.gpio_reg[GPIO_FSEL0 + gpio / 10];
|
||||
|
||||
return (fsel >> ((gpio % 10) * 3)) & 7;
|
||||
}
|
||||
|
||||
// L488
|
||||
static void gpio_set_mode(DmaChannel &ch, uint32_t gpio, uint32_t mode) {
|
||||
uint32_t fsel = ch.gpio_reg[GPIO_FSEL0 + gpio / 10];
|
||||
|
||||
fsel &= ~(7 << ((gpio % 10) * 3));
|
||||
fsel |= mode << ((gpio % 10) * 3);
|
||||
ch.gpio_reg[GPIO_FSEL0 + gpio / 10] = fsel;
|
||||
}
|
||||
|
||||
// L498
|
||||
static void gpio_set(DmaChannel &ch, int gpio, int level) {
|
||||
if (level)
|
||||
ch.gpio_reg[GPIO_SET0] = 1 << gpio;
|
||||
else
|
||||
ch.gpio_reg[GPIO_CLR0] = 1 << gpio;
|
||||
}
|
||||
|
||||
// L506
|
||||
static uint32_t mem_virt_to_phys(void *virt) {
|
||||
uint32_t offset = (uint8_t *)virt - mbox.virt_addr;
|
||||
|
||||
return mbox.bus_addr + offset;
|
||||
}
|
||||
|
||||
// L515
|
||||
static void *map_peripheral(uint32_t base, uint32_t len) {
|
||||
int fd = open("/dev/mem", O_RDWR | O_SYNC);
|
||||
void *vaddr;
|
||||
|
||||
if (fd < 0)
|
||||
fatal("rpio-pwm: Failed to open /dev/mem: %m\n");
|
||||
vaddr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, base);
|
||||
if (vaddr == MAP_FAILED)
|
||||
fatal("rpio-pwm: Failed to map peripheral at 0x%08x: %m\n", base);
|
||||
close(fd);
|
||||
|
||||
return vaddr;
|
||||
}
|
||||
|
||||
/* Carefully add or remove bits from the turnoff_mask such that regardless
|
||||
* of where the DMA controller is in its cycle, and whether we are increasing
|
||||
* or decreasing the pulse width, the generated pulse will only ever be the
|
||||
* old width or the new width. If we don't take such care then there could be
|
||||
* a cycle with some pulse width between the two requested ones. That doesn't
|
||||
* really matter for servos, but when driving LEDs some odd intensity for one
|
||||
* cycle can be noticeable. It may be that the servo output has been turned
|
||||
* off via the inactivity timer, which is handled by always setting the turnon
|
||||
* mask appropriately at the end of this function.
|
||||
*/
|
||||
// L556
|
||||
void set_servo(DmaChannel &ch, int servo, int newWidth) {
|
||||
volatile uint32_t *dp;
|
||||
int i;
|
||||
uint32_t mask = 1 << ch.pins[servo]->gpioPinNum;
|
||||
|
||||
int oldWidth = ch.pins[servo]->servowidth;
|
||||
if (ch.hw.current_log_level >= LogLevel::Debug) {
|
||||
printf("set_servo oldWidth=%d new=%d\n", oldWidth, newWidth);
|
||||
}
|
||||
|
||||
if (newWidth > oldWidth) {
|
||||
dp = ch.turnoff_mask + ch.servostart[servo] + newWidth;
|
||||
if (dp >= ch.turnoff_mask + ch.num_samples)
|
||||
dp -= ch.num_samples;
|
||||
|
||||
for (i = newWidth; i > oldWidth; i--) {
|
||||
dp--;
|
||||
if (dp < ch.turnoff_mask)
|
||||
dp = ch.turnoff_mask + ch.num_samples - 1;
|
||||
// printf("%5d, clearing at %p\n", dp - ctl->turnoff, dp);
|
||||
*dp &= ~mask;
|
||||
}
|
||||
} else if (newWidth < oldWidth) {
|
||||
dp = ch.turnoff_mask + ch.servostart[servo] + newWidth;
|
||||
if (dp >= ch.turnoff_mask + ch.num_samples)
|
||||
dp -= ch.num_samples;
|
||||
|
||||
for (i = newWidth; i < oldWidth; i++) {
|
||||
// printf("%5d, setting at %p\n", dp - ctl->turnoff, dp);
|
||||
*dp++ |= mask;
|
||||
if (dp >= ch.turnoff_mask + ch.num_samples)
|
||||
dp = ch.turnoff_mask;
|
||||
}
|
||||
}
|
||||
ch.pins[servo]->servowidth = newWidth;
|
||||
if (newWidth == 0) {
|
||||
ch.turnon_mask[servo] = 0;
|
||||
} else {
|
||||
ch.turnon_mask[servo] = mask;
|
||||
}
|
||||
}
|
||||
|
||||
void set_mask_all(DmaChannel &ch, int servo) {
|
||||
volatile uint32_t *dp;
|
||||
uint32_t mask = 1 << ch.pins[servo]->gpioPinNum;
|
||||
for (dp = ch.turnoff_mask; dp < ch.turnoff_mask + ch.num_samples;) {
|
||||
*dp++ |= mask;
|
||||
}
|
||||
}
|
||||
|
||||
void clear_mask_all(DmaChannel &ch, int servo) {
|
||||
volatile uint32_t *dp;
|
||||
uint32_t mask = 1 << ch.pins[servo]->gpioPinNum;
|
||||
for (dp = ch.turnoff_mask; dp < ch.turnoff_mask + ch.num_samples;) {
|
||||
*dp++ &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
// L596
|
||||
// static void setup_sighandlers(void) {
|
||||
// int i;
|
||||
|
||||
// // Catch all signals possible - it is vital we kill the DMA engine
|
||||
// // on process exit!
|
||||
// for (i = 0; i < 64; i++) {
|
||||
// struct sigaction sa;
|
||||
|
||||
// memset(&sa, 0, sizeof(sa));
|
||||
// sa.sa_handler = terminate;
|
||||
// sigaction(i, &sa, NULL);
|
||||
// }
|
||||
// }
|
||||
|
||||
// L612
|
||||
void init_ctrl_data(DmaHardware &hw, DmaChannel &ch) {
|
||||
dma_cb_t *cbp = ch.cb_base;
|
||||
uint32_t phys_fifo_addr, cbinfo;
|
||||
uint32_t phys_gpclr0;
|
||||
uint32_t phys_gpset0;
|
||||
int curstart = 0;
|
||||
uint32_t maskall = 0;
|
||||
|
||||
if (ch.invert) {
|
||||
phys_gpclr0 = GPIO_PHYS_BASE(hw) + 0x1c;
|
||||
phys_gpset0 = GPIO_PHYS_BASE(hw) + 0x28;
|
||||
} else {
|
||||
phys_gpclr0 = GPIO_PHYS_BASE(hw) + 0x28;
|
||||
phys_gpset0 = GPIO_PHYS_BASE(hw) + 0x1c;
|
||||
}
|
||||
|
||||
if (ch.delay_hw == DelayHardware::DELAY_VIA_PWM) {
|
||||
phys_fifo_addr = PWM_PHYS_BASE(hw) + 0x18;
|
||||
cbinfo = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(5);
|
||||
} else {
|
||||
phys_fifo_addr = PCM_PHYS_BASE(hw) + 0x04;
|
||||
cbinfo = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP | DMA_D_DREQ | DMA_PER_MAP(2);
|
||||
}
|
||||
|
||||
memset(ch.turnon_mask, 0, maxServoCount * sizeof(*(ch.turnon_mask)));
|
||||
|
||||
// for (servo = 0 ; servo < MAX_SERVOS; servo++) {
|
||||
// servowidth[servo] = 0;
|
||||
// if (servo2gpio[servo] != DMY) {
|
||||
// numservos++;
|
||||
// maskall |= 1 << servo2gpio[servo];
|
||||
// }
|
||||
// }
|
||||
|
||||
for (int i = 0; i < ch.num_samples; i++)
|
||||
ch.turnoff_mask[i] = maskall;
|
||||
|
||||
for (uint32_t servo = 0; servo < ch.pins.size(); servo++) {
|
||||
ch.servostart[servo] = curstart;
|
||||
curstart += ch.num_samples / ch.pins.size();
|
||||
}
|
||||
|
||||
for (int i = 0, servo = 0; i < ch.num_samples; i++) {
|
||||
cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP;
|
||||
cbp->src = mem_virt_to_phys(ch.turnoff_mask + i);
|
||||
cbp->dst = phys_gpclr0;
|
||||
cbp->length = 4;
|
||||
cbp->stride = 0;
|
||||
cbp->next = mem_virt_to_phys(cbp + 1);
|
||||
cbp++;
|
||||
if (servo < maxServoCount && i == ch.servostart[servo]) {
|
||||
cbp->info = DMA_NO_WIDE_BURSTS | DMA_WAIT_RESP;
|
||||
cbp->src = mem_virt_to_phys(ch.turnon_mask + servo);
|
||||
cbp->dst = phys_gpset0;
|
||||
cbp->length = 4;
|
||||
cbp->stride = 0;
|
||||
cbp->next = mem_virt_to_phys(cbp + 1);
|
||||
cbp++;
|
||||
servo++;
|
||||
}
|
||||
// Delay
|
||||
cbp->info = cbinfo;
|
||||
cbp->src = mem_virt_to_phys(ch.turnoff_mask); // Any data will do
|
||||
cbp->dst = phys_fifo_addr;
|
||||
cbp->length = 4;
|
||||
cbp->stride = 0;
|
||||
cbp->next = mem_virt_to_phys(cbp + 1);
|
||||
cbp++;
|
||||
}
|
||||
cbp--;
|
||||
cbp->next = mem_virt_to_phys(ch.cb_base);
|
||||
}
|
||||
|
||||
// L695
|
||||
void init_hardware(DmaHardware &hw, DmaChannel &ch) {
|
||||
if (ch.delay_hw == DelayHardware::DELAY_VIA_PWM) {
|
||||
// Initialise PWM
|
||||
ch.pwm_reg[PWM_CTL] = 0;
|
||||
udelay(10);
|
||||
ch.clk_reg[PWMCLK_CNTL] =
|
||||
0x5A000006; // Source=PLLD (500MHz or 750MHz on Pi4)
|
||||
udelay(100);
|
||||
ch.clk_reg[PWMCLK_DIV] =
|
||||
0x5A000000 | (hw.plldfreq_mhz << 12); // set pwm div to give 1MHz
|
||||
udelay(100);
|
||||
ch.clk_reg[PWMCLK_CNTL] = 0x5A000016; // Source=PLLD and enable
|
||||
udelay(100);
|
||||
ch.pwm_reg[PWM_RNG1] = ch.step_time_us;
|
||||
udelay(10);
|
||||
ch.pwm_reg[PWM_DMAC] = PWMDMAC_ENAB | PWMDMAC_THRSHLD;
|
||||
udelay(10);
|
||||
ch.pwm_reg[PWM_CTL] = PWMCTL_CLRF;
|
||||
udelay(10);
|
||||
ch.pwm_reg[PWM_CTL] = PWMCTL_USEF1 | PWMCTL_PWEN1;
|
||||
udelay(10);
|
||||
} else {
|
||||
// Initialise PCM
|
||||
ch.pcm_reg[PCM_CS_A] = 1; // Disable Rx+Tx, Enable PCM block
|
||||
udelay(100);
|
||||
ch.clk_reg[PCMCLK_CNTL] =
|
||||
0x5A000006; // Source=PLLD (500MHz or 750MHz on Pi4)
|
||||
udelay(100);
|
||||
ch.clk_reg[PCMCLK_DIV] =
|
||||
0x5A000000 | (hw.plldfreq_mhz << 12); // Set pcm div to give 1MHz
|
||||
udelay(100);
|
||||
ch.clk_reg[PCMCLK_CNTL] = 0x5A000016; // Source=PLLD and enable
|
||||
udelay(100);
|
||||
ch.pcm_reg[PCM_TXC_A] =
|
||||
0U << 31 | 1 << 30 | 0 << 20 | 0 << 16; // 1 channel, 8 bits
|
||||
udelay(100);
|
||||
ch.pcm_reg[PCM_MODE_A] = (ch.step_time_us - 1) << 10;
|
||||
udelay(100);
|
||||
ch.pcm_reg[PCM_CS_A] |= 1 << 4 | 1 << 3; // Clear FIFOs
|
||||
udelay(100);
|
||||
ch.pcm_reg[PCM_DREQ_A] =
|
||||
64 << 24 | 64 << 8; // DMA Req when one slot is free?
|
||||
udelay(100);
|
||||
ch.pcm_reg[PCM_CS_A] |= 1 << 9; // Enable DMA
|
||||
udelay(100);
|
||||
}
|
||||
|
||||
// Initialise the DMA
|
||||
ch.dma_reg[DMA_CS] = DMA_RESET;
|
||||
udelay(10);
|
||||
ch.dma_reg[DMA_CS] = DMA_INT | DMA_END;
|
||||
ch.dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(ch.cb_base);
|
||||
ch.dma_reg[DMA_DEBUG] = 7; // clear debug error flags
|
||||
ch.dma_reg[DMA_CS] =
|
||||
0x10880001; // go, mid priority, wait for outstanding writes
|
||||
|
||||
if (ch.delay_hw == DelayHardware::DELAY_VIA_PCM) {
|
||||
ch.pcm_reg[PCM_CS_A] |= 1 << 2; // Enable Tx
|
||||
}
|
||||
}
|
||||
|
||||
/* Determining the board revision is a lot more complicated than it should be
|
||||
* (see comments in wiringPi for details). We will just look at the last two
|
||||
* digits of the Revision string and treat '00' and '01' as errors, '02' and
|
||||
* '03' as rev 1, and any other hex value as rev 2. 'Pi1 and Pi2 are
|
||||
* differentiated by the Hardware being BCM2708 or BCM2709.
|
||||
*
|
||||
* NOTE: These days we should just use bcm_host_get_model_type().
|
||||
*/
|
||||
// L945
|
||||
void get_model_and_revision(DmaHardware &hw) {
|
||||
char buf[128], revstr[128], modelstr[128];
|
||||
char *ptr, *end, *res;
|
||||
int board_revision;
|
||||
FILE *fp;
|
||||
|
||||
revstr[0] = modelstr[0] = '\0';
|
||||
|
||||
fp = fopen("/proc/cpuinfo", "r");
|
||||
|
||||
if (!fp)
|
||||
fatal("Unable to open /proc/cpuinfo: %m\n");
|
||||
|
||||
while ((res = fgets(buf, 128, fp))) {
|
||||
if (!strncasecmp("hardware", buf, 8))
|
||||
memcpy(modelstr, buf, 128);
|
||||
else if (!strncasecmp(buf, "revision", 8))
|
||||
memcpy(revstr, buf, 128);
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
if (modelstr[0] == '\0')
|
||||
fatal("rpio-pwm: No 'Hardware' record in /proc/cpuinfo\n");
|
||||
if (revstr[0] == '\0')
|
||||
fatal("rpio-pwm: No 'Revision' record in /proc/cpuinfo\n");
|
||||
|
||||
if (strstr(modelstr, "BCM2708"))
|
||||
hw.board_model = 1;
|
||||
else if (strstr(modelstr, "BCM2709") || strstr(modelstr, "BCM2835") || strstr(modelstr, "BCM2711"))
|
||||
hw.board_model = 2;
|
||||
else
|
||||
fatal("rpio-pwm: Cannot parse the hardware name string\n");
|
||||
|
||||
/* Revisions documented at http://elinux.org/RPi_HardwareHistory */
|
||||
ptr = revstr + strlen(revstr) - 3;
|
||||
board_revision = strtol(ptr, &end, 16);
|
||||
if (end != ptr + 2)
|
||||
fatal("rpio-pwm: Failed to parse Revision string\n");
|
||||
if (board_revision < 1)
|
||||
fatal("rpio-pwm: Invalid board Revision\n");
|
||||
else if (board_revision < 4)
|
||||
hw.gpio_cfg = 1;
|
||||
else if (board_revision < 16)
|
||||
hw.gpio_cfg = 2;
|
||||
else
|
||||
hw.gpio_cfg = 3;
|
||||
|
||||
if (bcm_host_is_model_pi4() || strstr(modelstr, "BCM2711") ) {
|
||||
hw.plldfreq_mhz = PLLDFREQ_MHZ_PI4;
|
||||
hw.host_is_model_pi4 = true;
|
||||
// hw.dma_chan = DMA_CHAN_PI4;
|
||||
} else {
|
||||
hw.plldfreq_mhz = PLLDFREQ_MHZ_DEFAULT;
|
||||
hw.host_is_model_pi4 = false;
|
||||
// hw.dma_chan = DMA_CHAN_DEFAULT;
|
||||
}
|
||||
|
||||
hw.periph_virt_base = bcm_host_get_peripheral_address();
|
||||
hw.dram_phys_base = bcm_host_get_sdram_address();
|
||||
hw.periph_phys_base = 0x7e000000;
|
||||
|
||||
/*
|
||||
* See https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
|
||||
*
|
||||
* 1: MEM_FLAG_DISCARDABLE = 1 << 0 // can be resized to 0 at any time. Use
|
||||
* for cached data
|
||||
* MEM_FLAG_NORMAL = 0 << 2 // normal allocating alias. Don't
|
||||
* use from ARM 4: MEM_FLAG_DIRECT = 1 << 2 // 0xC alias uncached 8:
|
||||
* MEM_FLAG_COHERENT = 2 << 2 // 0x8 alias. Non-allocating in L2 but coherent
|
||||
* MEM_FLAG_L1_NONALLOCATING = // Allocating in L2
|
||||
* (MEM_FLAG_DIRECT | MEM_FLAG_COHERENT)
|
||||
* 16: MEM_FLAG_ZERO = 1 << 4 // initialise buffer to all zeros
|
||||
* 32: MEM_FLAG_NO_INIT = 1 << 5 // don't initialise (default is
|
||||
* initialise to all ones 64: MEM_FLAG_HINT_PERMALOCK = 1 << 6 //
|
||||
* Likely to be locked for long periods of time
|
||||
*
|
||||
*/
|
||||
if (hw.board_model == 1) {
|
||||
hw.mem_flag = 0x0c; /* MEM_FLAG_DIRECT | MEM_FLAG_COHERENT */
|
||||
} else {
|
||||
hw.mem_flag = 0x04; /* MEM_FLAG_DIRECT */
|
||||
}
|
||||
}
|
||||
|
||||
// L1184 main()
|
||||
bool setup_hardware(DmaHardware &hw) {
|
||||
get_model_and_revision(hw);
|
||||
|
||||
// init_idle_timers(hw);
|
||||
// setup_sighandlers();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace wpp {
|
||||
|
||||
// L416
|
||||
void fatal(const char *fmt, ...) {
|
||||
printf("fatal() %s\n", fmt);
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
terminate(0);
|
||||
}
|
||||
|
||||
//************************** DmaHardware ****************************
|
||||
// static
|
||||
DmaHardware &DmaHardware::GetInstance() {
|
||||
static DmaHardware hardware{};
|
||||
static bool inited = false;
|
||||
if (!inited) {
|
||||
inited = setup_hardware(hardware);
|
||||
}
|
||||
return hardware;
|
||||
}
|
||||
|
||||
//************************** DmaChannel ****************************
|
||||
// static
|
||||
std::shared_ptr<DmaChannel>
|
||||
DmaChannel::CreateInstance(const DmaChannelConfig &config) {
|
||||
auto &hw = DmaHardware::GetInstance();
|
||||
auto ch = std::make_shared<DmaChannel>(hw, config);
|
||||
ch->Init();
|
||||
DmaHardware::GetInstance().channels.push_back(ch);
|
||||
return ch;
|
||||
}
|
||||
|
||||
DmaChannel::DmaChannel(DmaHardware &hw, const DmaChannelConfig &config)
|
||||
: hw{hw}, delay_hw{config.delay_hw}, chNum{config.chNum},
|
||||
cycle_time_us{config.cycleTimeUs},
|
||||
step_time_us{config.stepTimeUs}, invert{config.invert} {
|
||||
if (step_time_us < 2 || step_time_us > 1000) {
|
||||
fatal("Invalid step-size specified");
|
||||
}
|
||||
if (cycle_time_us < 1000 || cycle_time_us > 1000000) {
|
||||
fatal("Invalid cycle-time specified");
|
||||
}
|
||||
if (cycle_time_us % step_time_us) {
|
||||
fatal("cycle-time is not a multiple of step-size");
|
||||
}
|
||||
if (cycle_time_us / step_time_us < 100) {
|
||||
fatal("cycle-time must be at least 100 * step-size");
|
||||
}
|
||||
}
|
||||
|
||||
void DmaChannel::Init() {
|
||||
num_samples = cycle_time_us / step_time_us;
|
||||
num_cbs = num_samples * 2 + maxServoCount;
|
||||
num_pages = (num_cbs * sizeof(dma_cb_t) + num_samples * 4 +
|
||||
maxServoCount * 4 + PAGE_SIZE - 1) >>
|
||||
PAGE_SHIFT;
|
||||
|
||||
auto &ch = *this;
|
||||
ch.dma_reg =
|
||||
static_cast<uint32_t *>(map_peripheral(DMA_VIRT_BASE(hw), DMA_LEN));
|
||||
ch.dma_reg += ch.chNum * DMA_CHAN_SIZE / sizeof(uint32_t);
|
||||
ch.pwm_reg =
|
||||
static_cast<uint32_t *>(map_peripheral(PWM_VIRT_BASE(hw), PWM_LEN));
|
||||
ch.pcm_reg =
|
||||
static_cast<uint32_t *>(map_peripheral(PCM_VIRT_BASE(hw), PCM_LEN));
|
||||
ch.clk_reg =
|
||||
static_cast<uint32_t *>(map_peripheral(CLK_VIRT_BASE(hw), CLK_LEN));
|
||||
ch.gpio_reg =
|
||||
static_cast<uint32_t *>(map_peripheral(GPIO_VIRT_BASE(hw), GPIO_LEN));
|
||||
/* Use the mailbox interface to the VC to ask for physical memory */
|
||||
// Use the mailbox interface to request memory from the VideoCore
|
||||
// We specifiy (-1) for the handle rather than calling mbox_open()
|
||||
// so multiple users can share the resource.
|
||||
mbox.handle = -1; // mbox_open();
|
||||
mbox.size = ch.num_pages * 4096;
|
||||
mbox.mem_ref = mem_alloc(mbox.handle, mbox.size, 4096, hw.mem_flag);
|
||||
if (mbox.mem_ref == 0) {
|
||||
fatal("Failed to alloc memory from VideoCore\n");
|
||||
}
|
||||
mbox.bus_addr = mem_lock(mbox.handle, mbox.mem_ref);
|
||||
if (mbox.bus_addr == ~0U) {
|
||||
mem_free(mbox.handle, mbox.size);
|
||||
fatal("Failed to lock memory\n");
|
||||
}
|
||||
mbox.virt_addr =
|
||||
static_cast<uint8_t *>(mapmem(BUS_TO_PHYS(mbox.bus_addr), mbox.size));
|
||||
|
||||
ch.turnoff_mask = (uint32_t *)mbox.virt_addr;
|
||||
ch.turnon_mask =
|
||||
(uint32_t *)(mbox.virt_addr + ch.num_samples * sizeof(uint32_t));
|
||||
ch.cb_base =
|
||||
(dma_cb_t *)(mbox.virt_addr + ROUNDUP(ch.num_samples + maxServoCount, 8) *
|
||||
sizeof(uint32_t));
|
||||
|
||||
init_ctrl_data(hw, ch);
|
||||
init_hardware(hw, ch);
|
||||
ch.isActive = true;
|
||||
if (hw.current_log_level >= LogLevel::Info) {
|
||||
printf("DmaChannel::Init() ch=%d, cycle_time_us=%d, step_time_us=%d, "
|
||||
"num_samples=%d\n",
|
||||
chNum, cycle_time_us, step_time_us, num_samples);
|
||||
}
|
||||
}
|
||||
|
||||
void DmaChannel::DeactivateChannel() {
|
||||
auto &ch = *this;
|
||||
if (!ch.IsActive())
|
||||
return;
|
||||
if (hw.current_log_level >= LogLevel::Info) {
|
||||
printf("DmaChannel::DeactivateChannel() ch=%d\n", ch.chNum);
|
||||
}
|
||||
ch.isActive = false;
|
||||
|
||||
for (int i = 0; i < maxServoCount; i++) {
|
||||
if (ch.pins[i]) {
|
||||
ch.pins[i]->DeactivatePin();
|
||||
}
|
||||
}
|
||||
udelay(ch.cycle_time_us);
|
||||
ch.dma_reg[DMA_CS] = DMA_RESET;
|
||||
udelay(10);
|
||||
}
|
||||
|
||||
std::shared_ptr<PwmPin>
|
||||
DmaChannel::CreatePin(const DmaPwmPinConfig &pinConfig) {
|
||||
auto pin = std::make_shared<PwmPin>(this->shared_from_this(), pinConfig);
|
||||
|
||||
// find next available slot in pins
|
||||
int servo = 0;
|
||||
while (servo < maxServoCount && pins[servo] != nullptr) {
|
||||
servo++;
|
||||
}
|
||||
if (servo == maxServoCount) {
|
||||
fatal("run out of availabel slots");
|
||||
}
|
||||
pins[servo] = pin;
|
||||
pin->Init(servo);
|
||||
set_mask_all(*this, servo);
|
||||
set_servo(*this, servo, pinConfig.widthInSteps);
|
||||
|
||||
return pin;
|
||||
}
|
||||
|
||||
//************************** PwmPin ****************************
|
||||
PwmPin::PwmPin(std::shared_ptr<DmaChannel> dmaChannel,
|
||||
const DmaPwmPinConfig &pinConfig)
|
||||
: ch{dmaChannel}, gpioPinNum{pinConfig.gpioPinNum},
|
||||
restoreOnExit{pinConfig.restoreOnExit} {
|
||||
//
|
||||
}
|
||||
|
||||
void PwmPin::Init(int slotIndex) {
|
||||
this->slotIndex = slotIndex;
|
||||
this->gpiomode = gpio_get_mode(*ch, gpioPinNum);
|
||||
gpio_set(*ch, gpioPinNum, ch->invert ? 1 : 0);
|
||||
gpio_set_mode(*ch, gpioPinNum, GPIO_MODE_OUT);
|
||||
}
|
||||
|
||||
void PwmPin::SetByWidth(const int width) {
|
||||
if (width < 0 || width > ch->num_samples) {
|
||||
fatal("width out of range");
|
||||
}
|
||||
set_servo(*ch, slotIndex, width);
|
||||
}
|
||||
|
||||
void PwmPin::SetByPercentage(const float pct) {
|
||||
if (slotIndex < 0) {
|
||||
fatal("already deactivated?");
|
||||
}
|
||||
if (pct < 0.0f || pct > 100.0f) {
|
||||
fatal("pct out of range");
|
||||
}
|
||||
int newWidth = static_cast<int>(pct * ch->num_samples / 100.0f);
|
||||
set_servo(*ch, slotIndex, newWidth);
|
||||
}
|
||||
|
||||
void PwmPin::SetByActiveTimeUs(const int timeInUs) {
|
||||
if (slotIndex < 0) {
|
||||
fatal("already deactivated?");
|
||||
}
|
||||
if (timeInUs < 0 || timeInUs > ch->cycle_time_us) {
|
||||
fatal("timeInUs out of range");
|
||||
}
|
||||
int newWidth = timeInUs / ch->step_time_us;
|
||||
set_servo(*ch, slotIndex, newWidth);
|
||||
}
|
||||
|
||||
void PwmPin::DeactivatePin() {
|
||||
if (slotIndex < 0) {
|
||||
// already deactivated?
|
||||
return;
|
||||
}
|
||||
// to avoid destruct before exit scope
|
||||
std::shared_ptr<PwmPin> self = shared_from_this();
|
||||
set_servo(*ch, slotIndex, 0 /*newWidth*/);
|
||||
clear_mask_all(*ch, slotIndex);
|
||||
ch->pins[slotIndex] = nullptr;
|
||||
slotIndex = -1;
|
||||
}
|
||||
|
||||
//************************** TestDma ****************************
|
||||
void TestDma() {
|
||||
// setup_hardware(hardware);
|
||||
// auto ch = DmaChannel::CreateInstance(14, 20000,10);
|
||||
std::cout << "TestDma";
|
||||
}
|
||||
|
||||
} // namespace wpp
|
180
src/gpio/dma.hpp
Normal file
180
src/gpio/dma.hpp
Normal file
|
@ -0,0 +1,180 @@
|
|||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace wpp {
|
||||
|
||||
const int maxServoCount = 12; // max to 32
|
||||
|
||||
enum class DelayHardware { DELAY_VIA_PWM = 0, DELAY_VIA_PCM = 1 };
|
||||
|
||||
class DmaChannel;
|
||||
class PwmPin;
|
||||
|
||||
void fatal(const char *fmt, ...);
|
||||
|
||||
typedef struct {
|
||||
uint32_t info, src, dst, length, stride, next, pad[2];
|
||||
} dma_cb_t;
|
||||
|
||||
// need keep sync with index.js
|
||||
enum class LogLevel {
|
||||
Fatal = 0,
|
||||
Error = 1,
|
||||
Warning = 2,
|
||||
Info = 3,
|
||||
Debug = 4,
|
||||
};
|
||||
|
||||
//************************** DmaHardware ****************************
|
||||
struct DmaHardware {
|
||||
// struct timeval *servo_kill_time; // for idle_timeout feature.
|
||||
|
||||
uint32_t plldfreq_mhz;
|
||||
// int dma_chan;
|
||||
// int idle_timeout;
|
||||
// int servo_min_ticks;
|
||||
// int servo_max_ticks;
|
||||
|
||||
int board_model;
|
||||
int gpio_cfg;
|
||||
|
||||
// init by get_model_and_revision() at init time (detect hardware)
|
||||
uint32_t periph_phys_base;
|
||||
uint32_t periph_virt_base;
|
||||
uint32_t dram_phys_base;
|
||||
uint32_t mem_flag;
|
||||
|
||||
bool host_is_model_pi4{false};
|
||||
|
||||
std::vector<std::weak_ptr<DmaChannel>> channels{};
|
||||
|
||||
LogLevel current_log_level {LogLevel::Info};
|
||||
static DmaHardware &GetInstance();
|
||||
};
|
||||
|
||||
//************************** DmaChannel ****************************
|
||||
struct DmaChannelConfig {
|
||||
DelayHardware delay_hw = DelayHardware::DELAY_VIA_PWM;
|
||||
int chNum = 14; // default 14 (for pi2/3/zero), suggest use 7 for pi4
|
||||
int cycleTimeUs = 20000; // 20ms cycle
|
||||
int stepTimeUs = 10; // 10us each step
|
||||
bool invert = false; // default HIGH active
|
||||
};
|
||||
|
||||
struct DmaPwmPinConfig {
|
||||
int gpioPinNum = 21;
|
||||
int widthInSteps = 0;
|
||||
bool restoreOnExit = true; // restore gpio mode when exit
|
||||
};
|
||||
|
||||
class DmaChannel : public std::enable_shared_from_this<DmaChannel> {
|
||||
public:
|
||||
// for Pi2/3 ch can use 14 or 13 etc.
|
||||
// for Pi4 suggest use 7.
|
||||
static std::shared_ptr<DmaChannel>
|
||||
CreateInstance(const DmaChannelConfig &config);
|
||||
|
||||
public:
|
||||
DmaChannel(DmaHardware &hw, const DmaChannelConfig &config);
|
||||
|
||||
DmaChannel(DmaChannel const &) = delete;
|
||||
DmaChannel &operator=(DmaChannel const &) = delete;
|
||||
|
||||
public:
|
||||
// gpioPinNum (for example gpio_21 = P1_40 = wiringPi_29)
|
||||
std::shared_ptr<PwmPin> CreatePin(const DmaPwmPinConfig &pinConfig);
|
||||
|
||||
public:
|
||||
void Init();
|
||||
|
||||
public:
|
||||
void DeactivateChannel();
|
||||
|
||||
inline bool IsActive() { return isActive; }
|
||||
inline void ThrowIfNotActive() {
|
||||
if (!IsActive()) {
|
||||
fatal("not active");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
DmaHardware &hw;
|
||||
const DelayHardware delay_hw;
|
||||
const int chNum;
|
||||
|
||||
// cycle_time_us is the pulse cycle time per servo, in microseconds.
|
||||
// Typically it should be 20ms, or 20000us.
|
||||
|
||||
// step_time_us is the pulse width increment granularity, again in
|
||||
// microseconds. Setting step_time_us too low will likely cause problems as
|
||||
// the DMA controller will use too much memory bandwidth. 10us is a good
|
||||
// value, though you might be ok setting it as low as 2us.
|
||||
|
||||
const int cycle_time_us;
|
||||
const int step_time_us;
|
||||
bool invert{false};
|
||||
// bool restore_gpio_modes{true};
|
||||
|
||||
volatile uint32_t *pwm_reg{};
|
||||
volatile uint32_t *pcm_reg{};
|
||||
volatile uint32_t *clk_reg{};
|
||||
volatile uint32_t *dma_reg{};
|
||||
volatile uint32_t *gpio_reg{};
|
||||
|
||||
int num_samples;
|
||||
int num_cbs;
|
||||
int num_pages;
|
||||
uint32_t *turnoff_mask;
|
||||
uint32_t *turnon_mask;
|
||||
dma_cb_t *cb_base;
|
||||
bool isActive{false};
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<PwmPin>> pins =
|
||||
std::vector<std::shared_ptr<PwmPin>>(maxServoCount);
|
||||
std::vector<int> servostart = std::vector<int>(maxServoCount);
|
||||
};
|
||||
|
||||
//************************** PwmPin ****************************
|
||||
class PwmPin : public std::enable_shared_from_this<PwmPin> {
|
||||
public:
|
||||
PwmPin(std::shared_ptr<DmaChannel> dmaChannel,
|
||||
const DmaPwmPinConfig &pinConfig);
|
||||
|
||||
PwmPin(PwmPin const &) = delete;
|
||||
PwmPin &operator=(PwmPin const &) = delete;
|
||||
|
||||
public:
|
||||
inline bool IsActive() { return slotIndex >= 0; }
|
||||
inline void ThrowIfNotActive() {
|
||||
if (!IsActive()) {
|
||||
fatal("not active");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void SetByWidth(const int width);
|
||||
|
||||
void SetByPercentage(const float pct);
|
||||
|
||||
void SetByActiveTimeUs(const int timeInUs);
|
||||
|
||||
void DeactivatePin();
|
||||
|
||||
public:
|
||||
void Init(int slotIndex);
|
||||
|
||||
public:
|
||||
std::shared_ptr<DmaChannel> ch;
|
||||
const int gpioPinNum;
|
||||
uint32_t gpiomode; // when we exit, we can restore privious mode for this pin.
|
||||
|
||||
public:
|
||||
int slotIndex{-1}; // index in array ch.pins (start with 0)
|
||||
int servowidth{0};
|
||||
bool restoreOnExit{true};
|
||||
};
|
||||
|
||||
void TestDma();
|
||||
|
||||
} // namespace wpp
|
|
@ -1,458 +1,472 @@
|
|||
#include <stdio.h>
|
||||
#include <poll.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <malloc.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gpio.hpp"
|
||||
#include "../log/log.hpp"
|
||||
#include "dma.hpp"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
#define GPIO_0_2_R1 0 /*!< \def Gpio pin 0/2 (rev1/rev2) with rev2 board code */
|
||||
#define GPIO_1_3_R1 1 /*!< \def Gpio pin 1/3 (rev1/rev2) with rev2 board code */
|
||||
#define GPIO_21_27_R1 21 /*!< \def Gpio pin 21/27 (rev1/rev2) with rev2 board code */
|
||||
|
||||
#define RDBUF_LEN 10 // length of read buffer
|
||||
#define POLL_TIMEOUT 100 // timeout for polling function in ms (also the maximum delay time before thread is stopped
|
||||
|
||||
|
||||
#define FALSE 0
|
||||
#define TRUE 1
|
||||
#include <syslog.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace wpp;
|
||||
|
||||
/****************************
|
||||
* *
|
||||
* PRIVATE DEFINTIONS *
|
||||
* *
|
||||
*****************************/
|
||||
/********************************
|
||||
* *
|
||||
* Internal singleton class *
|
||||
* to ensure proper teardown *
|
||||
* of dma channel *
|
||||
* *
|
||||
********************************/
|
||||
|
||||
static unsigned int HardwareRevision(void);
|
||||
|
||||
/****************************
|
||||
* *
|
||||
* IOPIN OBJECT FUNCS *
|
||||
* *
|
||||
*****************************/
|
||||
|
||||
//! Create new IOPin object
|
||||
GpioPin::GpioPin(int gpiopin, GpioDirection direction, GpioEdge edge)
|
||||
class DmaPwmGenerator
|
||||
{
|
||||
int result;
|
||||
int pin_id;
|
||||
char fTemp[GPIO_FN_MAXLEN];
|
||||
|
||||
// Set tag to empty pointer
|
||||
Tag = NULL;
|
||||
|
||||
pin_id = verifyPin(gpiopin);
|
||||
if(pin_id < 0)
|
||||
throw OperationFailedException("Gpio pin %d is not a valid Gpio for this Raspberry Pi board revision",gpiopin);
|
||||
|
||||
// ensure that the Gpio pin is exported
|
||||
try
|
||||
private:
|
||||
DmaPwmGenerator()
|
||||
{
|
||||
pinPreExported = !(exportPin(pin_id));
|
||||
}
|
||||
catch(OperationFailedException x)
|
||||
{
|
||||
throw x;
|
||||
int ch = (DmaHardware::GetInstance().host_is_model_pi4)?7:14;
|
||||
clog << LOG_DEBUG << endl << " -- Creating DmaPwmGenerator using channel " << ch << " --" << endl;
|
||||
|
||||
DmaChannelConfig config{};
|
||||
config.chNum = ch;
|
||||
// cycle_time_us: typical value 10240=10.24ms each cycle
|
||||
config.cycleTimeUs = 10240;
|
||||
// step_time_us: typical value 10=10us each step
|
||||
// (total steps = 10240/5=2048 steps)
|
||||
config.stepTimeUs = 5;
|
||||
// We will only use DELAY_VIA_PWM, since PCM is in use by the audio chip
|
||||
config.delay_hw = DelayHardware::DELAY_VIA_PWM;
|
||||
// invert: don't invert high/low output
|
||||
config.invert = false;
|
||||
|
||||
auto channel = DmaChannel::CreateInstance(config);
|
||||
this->channel = channel;
|
||||
|
||||
}
|
||||
|
||||
// Prepare the iopin object
|
||||
pin = pin_id;
|
||||
std::shared_ptr<DmaChannel> channel;
|
||||
std::unordered_map<int, std::shared_ptr<PwmPin>> pins;
|
||||
|
||||
// Prepare the file names for the different files
|
||||
snprintf(fTemp, GPIO_FN_MAXLEN-1, "/sys/class/gpio/gpio%d/direction", pin_id);
|
||||
fnDirection = std::string(fTemp);
|
||||
snprintf(fTemp, GPIO_FN_MAXLEN-1, "/sys/class/gpio/gpio%d/edge", pin_id);
|
||||
fnEdge = std::string(fTemp);
|
||||
snprintf(fTemp, GPIO_FN_MAXLEN-1, "/sys/class/gpio/gpio%d/value", pin_id);
|
||||
fnValue = std::string(fTemp);
|
||||
public:
|
||||
// delete copy constructor and assignmen operator to prevent accidentally creating instance doubles
|
||||
DmaPwmGenerator(DmaPwmGenerator const&) = delete;
|
||||
void operator=(DmaPwmGenerator const&) = delete;
|
||||
|
||||
// Initialize callbacks to NULL
|
||||
|
||||
try
|
||||
static DmaPwmGenerator& Instance()
|
||||
{
|
||||
// set initial direction or die trying
|
||||
setDirection(direction);
|
||||
// set initial edge or die trying
|
||||
setEdge(edge);
|
||||
}
|
||||
catch(OperationFailedException x)
|
||||
static DmaPwmGenerator instance;
|
||||
return instance;
|
||||
};
|
||||
|
||||
~DmaPwmGenerator()
|
||||
{
|
||||
if(!pinPreExported)
|
||||
unexportPin(pin);
|
||||
throw x;
|
||||
this->channel->DeactivateChannel();
|
||||
clog << LOG_DEBUG << " -- DmaPwmGenerator destroyed -- " << endl;
|
||||
}
|
||||
|
||||
// initialize pin and set default puse width in steps
|
||||
bool RegisterPin(uint8_t gpio, uint32_t width = 0)
|
||||
{
|
||||
if (this->pins.find(gpio) != this->pins.end()) {
|
||||
return false;
|
||||
}
|
||||
DmaPwmPinConfig config{};
|
||||
config.gpioPinNum = gpio;
|
||||
config.widthInSteps = width;
|
||||
auto pin = channel->CreatePin(config);
|
||||
pins[gpio] = pin;
|
||||
return true;
|
||||
}
|
||||
|
||||
// unregister pin from pwm
|
||||
bool UnregisterPin(int gpio) {
|
||||
auto pin = this->pins.find(gpio);
|
||||
if (pin == this->pins.end()) {
|
||||
// pin don't exist?
|
||||
return false;
|
||||
}
|
||||
pin->second->DeactivatePin();
|
||||
pins.erase(pin);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Change pulse width in steps
|
||||
bool SetWidth(uint8_t gpio, uint32_t width)
|
||||
{
|
||||
auto pin = pins.find(gpio);
|
||||
if (pin == pins.end()) {
|
||||
// pin don't exist?
|
||||
return false;
|
||||
}
|
||||
pin->second->SetByWidth(width);
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/************************
|
||||
* *
|
||||
* GpioBase functions *
|
||||
* *
|
||||
************************/
|
||||
|
||||
|
||||
GpioBase::GpioBase()
|
||||
{
|
||||
this->gpioChip.open("gpiochip0",gpiod::chip::OPEN_BY_NAME);
|
||||
}
|
||||
|
||||
GpioBase::~GpioBase()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
gpiod::line_request GpioBase::init_linerq(GpioDirection dir, GpioEdge edge, GpioPullup pu, GpioOutput op, bool active_low)
|
||||
{
|
||||
// find process name from /proc file system
|
||||
std::ifstream comm("/proc/self/comm");
|
||||
std::string name;
|
||||
getline(comm, name);
|
||||
|
||||
// setup line request with process name as consumer name
|
||||
gpiod::line_request rq;
|
||||
rq.consumer = name;
|
||||
|
||||
// determine the request type from requested Direction and Edge
|
||||
rq.request_type = gpiod::line_request::DIRECTION_AS_IS;
|
||||
if(dir == GpioDirection::Output || dir == GpioDirection::PwmOutput) {
|
||||
rq.request_type = gpiod::line_request::DIRECTION_OUTPUT;
|
||||
}
|
||||
else if(edge == GpioEdge::Both) {
|
||||
rq.request_type = gpiod::line_request::EVENT_BOTH_EDGES;
|
||||
}
|
||||
else if (edge == GpioEdge::Rising) {
|
||||
rq.request_type = gpiod::line_request::EVENT_RISING_EDGE;
|
||||
}
|
||||
else if (edge == GpioEdge::Falling) {
|
||||
rq.request_type = gpiod::line_request::EVENT_FALLING_EDGE;
|
||||
}
|
||||
else //if (edge == GpioEdge::None)
|
||||
{
|
||||
rq.request_type = gpiod::line_request::DIRECTION_INPUT;
|
||||
}
|
||||
|
||||
rq.flags = 0;
|
||||
// Determine flags from the other
|
||||
if(dir == GpioDirection::Output)
|
||||
{
|
||||
if(op == GpioOutput::OpenDrain){
|
||||
rq.flags |= ::gpiod::line_request::FLAG_OPEN_DRAIN;
|
||||
}
|
||||
else if(op == GpioOutput::OpenSource){
|
||||
rq.flags |= ::gpiod::line_request::FLAG_OPEN_SOURCE;
|
||||
}
|
||||
}
|
||||
else // if(dir = GpioDirection::Input)
|
||||
{
|
||||
// next lines require libgpiod 1.6.2
|
||||
|
||||
if(pu == GpioPullup::PullUp){
|
||||
// rq.flags |= ::gpiod::line_request::FLAG_BIAS_PULL_UP;
|
||||
}
|
||||
else if(pu == GpioPullup::PullDown){
|
||||
// rq.flags |= ::gpiod::line_request::FLAG_BIAS_PULL_DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
if(active_low){
|
||||
rq.flags |= ::gpiod::line_request::FLAG_ACTIVE_LOW;
|
||||
}
|
||||
|
||||
return rq;
|
||||
}
|
||||
|
||||
void GpioBase::PinMode(GpioDirection dir, GpioOutput op, bool active_low)
|
||||
{
|
||||
this->PinMode(dir,GpioPullup::None,op, active_low);
|
||||
}
|
||||
void GpioBase::PinMode(GpioDirection dir, GpioPullup pu, bool active_low)
|
||||
{
|
||||
this->PinMode(dir,pu,GpioOutput::Both, active_low);
|
||||
}
|
||||
|
||||
void GpioBase::PinMode(GpioDirection dir, bool active_low)
|
||||
{
|
||||
this->PinMode(dir,GpioPullup::None,GpioOutput::Both, active_low);
|
||||
}
|
||||
|
||||
void GpioBase::Listen(GpioEdge edge, bool active_low)
|
||||
{
|
||||
this->Listen(edge, GpioPullup::None, active_low);
|
||||
}
|
||||
|
||||
/************************
|
||||
* *
|
||||
* GpioPin functions *
|
||||
* *
|
||||
************************/
|
||||
|
||||
GpioPin::GpioPin(uint8_t gpio)
|
||||
{
|
||||
this->line = this->gpioChip.get_line(gpio);
|
||||
}
|
||||
|
||||
//! Close the IOPin connection
|
||||
GpioPin::~GpioPin()
|
||||
{
|
||||
if(!pinPreExported)
|
||||
unexportPin(pin);
|
||||
}
|
||||
|
||||
//! Get the actual used pin number of the IO Pin
|
||||
int GpioPin::getPinNr()
|
||||
{
|
||||
return pin;
|
||||
}
|
||||
|
||||
//! Get current direction of pin
|
||||
GpioDirection GpioPin::getDirection()
|
||||
{
|
||||
std::string s = readFile(fnDirection);
|
||||
// got enough info in the first byte
|
||||
if(s[0] == 'i')
|
||||
return kDirectionIn;
|
||||
else
|
||||
return kDirectionOut;
|
||||
}
|
||||
|
||||
//! Set new value of pin
|
||||
void GpioPin::setDirection(GpioDirection direction)
|
||||
{
|
||||
if (direction == kDirectionIn) writeFile(fnDirection,"in\n");
|
||||
else if (direction == kDirectionOut) writeFile(fnDirection,"out\n");
|
||||
}
|
||||
|
||||
//! Get current edge detection type
|
||||
GpioEdge GpioPin::getEdge()
|
||||
{
|
||||
std::string s = readFile(fnEdge);
|
||||
switch(s[0]) // as the first letters of each result are all different
|
||||
if(this->dir == GpioDirection::PwmOutput)
|
||||
{
|
||||
default :
|
||||
case 'n':
|
||||
case 'N':
|
||||
return kEdgeNone;
|
||||
case 'r':
|
||||
case 'R':
|
||||
return kEdgeRising;
|
||||
case 'f':
|
||||
case 'F':
|
||||
return kEdgeFalling;
|
||||
case 'b':
|
||||
case 'B':
|
||||
return kEdgeBoth;
|
||||
DmaPwmGenerator::Instance().UnregisterPin(this->line.offset());
|
||||
}
|
||||
this->line.release();
|
||||
|
||||
}
|
||||
|
||||
void GpioPin::DigitalWrite(bool value)
|
||||
{
|
||||
if(this->line.is_requested() && this->line.direction() == gpiod::line::DIRECTION_OUTPUT){
|
||||
this->line.set_value(value?1:0);
|
||||
}
|
||||
}
|
||||
|
||||
//! Set edge detection type
|
||||
void GpioPin::setEdge(GpioEdge edge)
|
||||
bool GpioPin::DigitalRead()
|
||||
{
|
||||
if (edge == kEdgeNone) writeFile(fnEdge,"none\n");
|
||||
else if (edge == kEdgeRising) writeFile(fnEdge,"rising\n");
|
||||
else if (edge == kEdgeFalling) writeFile(fnEdge,"falling\n");
|
||||
else if (edge == kEdgeBoth) writeFile(fnEdge,"both\n");
|
||||
}
|
||||
|
||||
|
||||
//! Get current value of pin
|
||||
bool GpioPin::getValue()
|
||||
{
|
||||
std::string s = readFile(fnValue);
|
||||
// got enough info in the first byte
|
||||
if(s[0] == '1')
|
||||
return true;
|
||||
if(this->line.is_requested()){
|
||||
return (bool)(this->line.get_value());
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
//! Set new value of pin
|
||||
void GpioPin::setValue(bool value)
|
||||
{
|
||||
if (value) writeFile(fnValue,"1\n");
|
||||
else writeFile(fnValue,"0\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/****************************
|
||||
* *
|
||||
* INTERRUPT FUNCS *
|
||||
* *
|
||||
*****************************/
|
||||
|
||||
void GpioPin::InterruptStart()
|
||||
{
|
||||
if(!ThreadRunning())
|
||||
{
|
||||
return false; // fallback
|
||||
}
|
||||
}
|
||||
|
||||
bool GpioPin::IsAvailable(){
|
||||
return !(this->line.is_used() && !this->line.is_requested());
|
||||
}
|
||||
|
||||
void GpioPin::PinMode(GpioDirection dir, GpioPullup pu, GpioOutput op, bool active_low)
|
||||
{
|
||||
if(this->IsAvailable())
|
||||
{
|
||||
this->line.release();
|
||||
gpiod::line_request cfg = this->init_linerq(dir,GpioEdge::None,pu,op, active_low);
|
||||
this->line.request(cfg);
|
||||
this->dir = dir; // store directionality
|
||||
|
||||
if(dir == GpioDirection::PwmOutput)
|
||||
{
|
||||
DmaPwmGenerator::Instance().RegisterPin(this->line.offset());
|
||||
}
|
||||
else
|
||||
{
|
||||
DmaPwmGenerator::Instance().UnregisterPin(this->line.offset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GpioPin::PwmWrite(uint32_t width)
|
||||
{
|
||||
// only do this if the pin was set to PwmOutput
|
||||
if(this->dir == GpioDirection::PwmOutput)
|
||||
{
|
||||
DmaPwmGenerator::Instance().SetWidth(this->line.offset(),width);
|
||||
}
|
||||
}
|
||||
|
||||
void GpioPin::Listen(GpioEdge edge, GpioPullup pu, bool active_low)
|
||||
{
|
||||
if(this->IsAvailable())
|
||||
{
|
||||
this->line.release();
|
||||
gpiod::line_request cfg = this->init_linerq(GpioDirection::Input,edge,pu,GpioOutput::Both, active_low);
|
||||
this->line.request(cfg);
|
||||
|
||||
this->ThreadStart();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void GpioPin::InterruptStop()
|
||||
void GpioPin::ThreadLoop()
|
||||
{
|
||||
if(ThreadRunning())
|
||||
// wait 20 ms for an event
|
||||
// if it happens, process, otherwise wait another 20 ms
|
||||
bool event = this->line.event_wait(std::chrono::milliseconds(20));
|
||||
if(event)
|
||||
{
|
||||
this->ThreadStop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GpioPin::ThreadFunc()
|
||||
{
|
||||
int fd,ret;
|
||||
struct pollfd pfd;
|
||||
char rdbuf[RDBUF_LEN];
|
||||
|
||||
memset(rdbuf, 0x00, RDBUF_LEN);
|
||||
|
||||
fd=open(fnValue.c_str(), O_RDONLY);
|
||||
if(fd<0)
|
||||
throw OperationFailedException("Could not open file %s for reading: [%d] %s",fnValue.c_str(), errno, strerror(errno));
|
||||
|
||||
pfd.fd=fd;
|
||||
pfd.events=POLLPRI;
|
||||
|
||||
ret=read(fd, rdbuf, RDBUF_LEN-1);
|
||||
if(ret<0)
|
||||
{
|
||||
close(fd);
|
||||
throw OperationFailedException("Could not read from %s: [%d] %s",fnValue.c_str(), errno, strerror(errno));
|
||||
}
|
||||
|
||||
while(ThreadRunning())
|
||||
{
|
||||
memset(rdbuf, 0x00, RDBUF_LEN);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
ret=poll(&pfd, 1, POLL_TIMEOUT);
|
||||
if(ret<0) // negative result is error
|
||||
gpiod::line_event evt = this->line.event_read();
|
||||
if(evt.event_type == gpiod::line_event::RISING_EDGE)
|
||||
{
|
||||
close(fd);
|
||||
throw OperationFailedException("Could not poll %s: [%d] %s",fnValue.c_str(), errno, strerror(errno));
|
||||
this->onChange(evt.source.offset(),GpioEdge::Rising,true);
|
||||
}
|
||||
|
||||
if(ret==0)
|
||||
continue; // 0 bytes read is timeout, we should retry read
|
||||
// ok, poll succeesed, now we read the value
|
||||
ret=read(fd, rdbuf, RDBUF_LEN-1);
|
||||
if(ret<0)
|
||||
else //if(evt.event_type == gpiod::line_event::FALLING_EDGE)
|
||||
{
|
||||
close(fd);
|
||||
throw OperationFailedException("Could not read from %s: [%d] %s",fnValue.c_str(), errno, strerror(errno));
|
||||
this->onChange(evt.source.offset(),GpioEdge::Falling,false);
|
||||
}
|
||||
|
||||
// Kill the loop now if the thread stopped during our poll
|
||||
if(!ThreadRunning())
|
||||
break;
|
||||
// Continue with doing the callback, if we're still enabled.
|
||||
// Now, rdbuf[0] contains 0 or 1 depending on the trigger
|
||||
onInterrupt(this, kEdgeFalling, !(rdbuf[0] == '0'));
|
||||
}
|
||||
close(fd);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/****************************
|
||||
* *
|
||||
* SUPPORT FUNCTIONS *
|
||||
* *
|
||||
*****************************/
|
||||
|
||||
//! open a file for writing and write text to it
|
||||
void GpioPin::writeFile(std::string &fname, std::string &value)
|
||||
{
|
||||
writeFile(fname,value.c_str());
|
||||
}
|
||||
|
||||
void GpioPin::writeFile(std::string &fname, const char *value)
|
||||
{
|
||||
FILE *fd;
|
||||
if ((fd = fopen (fname.c_str(), "w")) == NULL)
|
||||
throw OperationFailedException("Could not open %s for writing",fname.c_str());
|
||||
|
||||
fprintf (fd, value);
|
||||
fclose(fd);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//! open a file for reading and read some text from it
|
||||
/*!
|
||||
function will throw an exception on empty string, since the files we use it on
|
||||
will always return a value. If they don't we have serious problems
|
||||
*/
|
||||
std::string GpioPin::readFile(std::string &fname)
|
||||
{
|
||||
FILE *fd;
|
||||
char rdbuf[RDBUF_LEN];
|
||||
int ret;
|
||||
|
||||
if ((fd = fopen (fname.c_str(), "r")) == NULL)
|
||||
throw OperationFailedException("Could not open %s for reading",fname.c_str());
|
||||
|
||||
ret = fread(rdbuf,1,RDBUF_LEN -1, fd);
|
||||
fclose(fd);
|
||||
|
||||
if(ret<0)
|
||||
{
|
||||
throw OperationFailedException("Error reading from %s: [%d] %s",fname.c_str(), errno, strerror(errno));
|
||||
}
|
||||
else if(ret == 0)
|
||||
}
|
||||
|
||||
/************************
|
||||
* *
|
||||
* GpioPins functions *
|
||||
* *
|
||||
************************/
|
||||
|
||||
GpioPins::GpioPins(std::vector<uint8_t> gpios)
|
||||
{
|
||||
std::vector<unsigned int> pins;
|
||||
for (std::vector<uint8_t>::iterator it = gpios.begin() ; it != gpios.end(); ++it)
|
||||
{
|
||||
throw OperationFailedException("Got empty string reading from %s: [%d] %s",fname.c_str(), errno, strerror(errno));
|
||||
pins.push_back((unsigned int)(*it));
|
||||
}
|
||||
|
||||
rdbuf[ret] = '\0'; // Ensure null termination
|
||||
|
||||
return std::string(rdbuf);
|
||||
this->lines = this->gpioChip.get_lines(pins);
|
||||
}
|
||||
|
||||
|
||||
//! Verifies gpio pin number, and translates pin numbers (REV2) to the proper REV1 or REV2 board gpio pin numbers
|
||||
/*!
|
||||
\param gpiopin The pin number to verify
|
||||
\return verified and translated gpio pin, or -1 if invalid
|
||||
*/
|
||||
int GpioPin::verifyPin(int gpiopin)
|
||||
GpioPins::~GpioPins()
|
||||
{
|
||||
// List of valid Gpio pins
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
||||
int validpinsRev1[17] = { 0, 1, 4, 7, 8, 9,10,11,14,15,17,18,21,22,23,24,25};
|
||||
int validpinsRev2[21] = { 2, 3, 4, 7, 8, 9,10,11,14,15,17,18,22,23,24,25,27,28,29,30,31};
|
||||
unsigned int i;
|
||||
|
||||
// get hardware revision
|
||||
unsigned int rev = HardwareRevision();
|
||||
if (rev < 4)
|
||||
{ // REV 1 BOARD
|
||||
|
||||
// Translate pins to rev 1 equivalent
|
||||
if(gpiopin == GPIO_0_2)
|
||||
gpiopin = GPIO_0_2_R1;
|
||||
else if(gpiopin == GPIO_1_3)
|
||||
gpiopin = GPIO_1_3_R1;
|
||||
else if(gpiopin == GPIO_21_27)
|
||||
gpiopin = GPIO_21_27_R1;
|
||||
|
||||
// Verify that the pin number is valid, otherwise return -1
|
||||
for(i=0; i<sizeof(validpinsRev1);i++)
|
||||
if(this->dir == GpioDirection::PwmOutput)
|
||||
{
|
||||
for(auto& line : this->lines)
|
||||
{
|
||||
if(gpiopin == validpinsRev1[i])
|
||||
return gpiopin;
|
||||
DmaPwmGenerator::Instance().UnregisterPin(line.offset());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{ // REV 2 BOARD
|
||||
}
|
||||
|
||||
// Verify that the pin number is valid, otherwise return -1
|
||||
for(i=0; i<sizeof(validpinsRev2);i++)
|
||||
this->lines.release();
|
||||
|
||||
}
|
||||
|
||||
void GpioPins::DigitalWrite(std::vector<bool> values)
|
||||
{
|
||||
std::vector<int> vs;
|
||||
for (std::vector<bool>::iterator it = values.begin() ; it != values.end(); ++it)
|
||||
{
|
||||
vs.push_back((*it)?1:0);
|
||||
}
|
||||
|
||||
this->lines.set_values(vs);
|
||||
}
|
||||
|
||||
std::vector<bool> GpioPins::DigitalRead()
|
||||
{
|
||||
std::vector<int> vs = this->lines.get_values();
|
||||
std::vector<bool> values;
|
||||
for (std::vector<int>::iterator it = vs.begin() ; it != vs.end(); ++it)
|
||||
{
|
||||
values.push_back((*it)?true:false);
|
||||
}
|
||||
|
||||
return values;
|
||||
|
||||
}
|
||||
|
||||
void GpioPins::PwmWrite(std::vector<uint32_t> widths)
|
||||
{
|
||||
int index = 0;
|
||||
for (uint32_t width: widths)
|
||||
{
|
||||
//
|
||||
if(index < this->lines.size())
|
||||
{
|
||||
if(gpiopin == validpinsRev2[i])
|
||||
return gpiopin;
|
||||
auto& line = this->lines.get(index);
|
||||
DmaPwmGenerator::Instance().SetWidth(line.offset(),width);
|
||||
}
|
||||
return -1;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
//! Export a certain Gpio pin
|
||||
/*!
|
||||
\param gpiopin The gpio pin to export
|
||||
\return true if pin was exported by us, false if previously exported
|
||||
*/
|
||||
bool GpioPin::exportPin(int gpiopin)
|
||||
|
||||
void GpioPins::PinMode(GpioDirection dir, GpioPullup pu, GpioOutput op, bool active_low)
|
||||
{
|
||||
FILE *fd ;
|
||||
int pin_id;
|
||||
this->lines.release();
|
||||
gpiod::line_request cfg = this->init_linerq(dir,GpioEdge::None,pu,op, active_low);
|
||||
this->lines.request(cfg);
|
||||
this->dir = dir; // store directionality
|
||||
|
||||
pin_id = verifyPin(gpiopin); // verify that the pin is correct
|
||||
if(pin_id < 0)
|
||||
throw InvalidArgumentException("Pin %d is not a usable Raspberry Pi GPIO pin",pin_id);
|
||||
|
||||
if ((fd = fopen ("/sys/class/gpio/export", "w")) == NULL)
|
||||
if(dir == GpioDirection::PwmOutput)
|
||||
{
|
||||
throw OperationFailedException("Pin %d cannot be exported (cannot write to /sys/class/gpio/export)",pin_id);
|
||||
}
|
||||
|
||||
fprintf (fd, "%d\n", pin_id) ;
|
||||
|
||||
if(fclose (fd) != 0)
|
||||
{
|
||||
// fprintf(stderr, "Got error code %d - %s\n", errno,strerror(errno));
|
||||
|
||||
if(errno == EBUSY) // indicates the pin is currently already exported
|
||||
return false;
|
||||
else
|
||||
throw OperationFailedException("Pin %d cannot be exported",pin_id);
|
||||
for(auto& line : this->lines)
|
||||
{
|
||||
DmaPwmGenerator::Instance().RegisterPin(line.offset(),0);
|
||||
}
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Unexport a certain Gpio pin
|
||||
/*!
|
||||
\param gpiopin The gpio pin to unexport
|
||||
\return true if pin was unexported by us, false if previously unexported
|
||||
*/
|
||||
bool GpioPin::unexportPin(int gpiopin)
|
||||
{
|
||||
FILE *fd ;
|
||||
int pin_id;
|
||||
|
||||
pin_id = verifyPin(gpiopin); // verify that the pin is correct
|
||||
if(pin_id < 0)
|
||||
throw InvalidArgumentException("Pin %d is not a usable Raspberry Pi GPIO pin",pin_id);
|
||||
|
||||
if ((fd = fopen ("/sys/class/gpio/unexport", "w")) == NULL)
|
||||
{
|
||||
throw OperationFailedException("Pin %d cannot be unexported (cannot write to /sys/class/gpio/unexport)",pin_id);
|
||||
for(auto& line : this->lines)
|
||||
{
|
||||
DmaPwmGenerator::Instance().UnregisterPin(line.offset());
|
||||
}
|
||||
}
|
||||
|
||||
fprintf (fd, "%d\n", pin_id) ;
|
||||
|
||||
if(fclose (fd) != 0)
|
||||
{
|
||||
// fprintf(stderr, "Got error code %d - %s\n", errno,strerror(errno));
|
||||
|
||||
if(errno == EINVAL) // indicates the pin is not currently exported
|
||||
return false;
|
||||
else
|
||||
throw OperationFailedException("Pin %d cannot be unexported",pin_id);
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
static unsigned int HardwareRevision(void)
|
||||
|
||||
void GpioPins::Listen(GpioEdge edge, GpioPullup pu, bool active_low)
|
||||
{
|
||||
FILE * filp;
|
||||
unsigned rev;
|
||||
char buf[512];
|
||||
char term;
|
||||
this->lines.release();
|
||||
gpiod::line_request cfg = this->init_linerq(GpioDirection::Input,edge,pu,GpioOutput::Both, active_low);
|
||||
this->lines.request(cfg);
|
||||
|
||||
rev = 0;
|
||||
this->ThreadStart();
|
||||
}
|
||||
|
||||
filp = fopen ("/proc/cpuinfo", "r");
|
||||
|
||||
if (filp != NULL)
|
||||
{
|
||||
while (fgets(buf, sizeof(buf), filp) != NULL)
|
||||
{
|
||||
if (!strncasecmp("revision\t", buf, 9))
|
||||
{
|
||||
if (sscanf(buf+strlen(buf)-5, "%x%c", &rev, &term) == 2)
|
||||
void GpioPins::ThreadLoop()
|
||||
{
|
||||
// wait 20 ms for an event
|
||||
// if it happens, process, otherwise wait another 20 ms
|
||||
auto event_lines = this->lines.event_wait(std::chrono::milliseconds(20));
|
||||
if(event_lines)
|
||||
{
|
||||
for(auto& it: event_lines)
|
||||
{
|
||||
gpiod::line_event evt = it.event_read();
|
||||
if(evt.event_type == gpiod::line_event::RISING_EDGE)
|
||||
{
|
||||
if (term == '\n') break;
|
||||
rev = 0;
|
||||
this->onChange(evt.source.offset(),GpioEdge::Rising,true);
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(filp);
|
||||
}
|
||||
return rev;
|
||||
else //if(evt.event_type == gpiod::line_event::FALLING_EDGE)
|
||||
{
|
||||
this->onChange(evt.source.offset(),GpioEdge::Falling,false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************
|
||||
* *
|
||||
* RgbLeD functions *
|
||||
* *
|
||||
************************/
|
||||
|
||||
RgbLed::RgbLed(const uint8_t rPin, const uint8_t gPin, const uint8_t bPin) : GpioPins(std::vector<uint8_t>{rPin,gPin,bPin})
|
||||
{
|
||||
this->PinMode(GpioDirection::PwmOutput);
|
||||
}
|
||||
|
||||
uint32_t RgbLed::gamma(uint8_t value)
|
||||
{
|
||||
const uint16_t gamma_lut[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6,
|
||||
6, 7, 7, 8, 9, 9, 10, 11, 11, 12, 13, 14, 15, 16, 17, 18,
|
||||
19, 20, 21, 23, 24, 25, 27, 28, 29, 31, 32, 34, 36, 37, 39, 41,
|
||||
43, 45, 47, 49, 51, 53, 55, 57, 59, 62, 64, 67, 69, 72, 74, 77,
|
||||
80, 83, 85, 88, 91, 94, 98, 101, 104, 107, 111, 114, 118, 121, 125, 129,
|
||||
133, 137, 141, 145, 149, 153, 157, 162, 166, 171, 175, 180, 185, 189, 194, 199,
|
||||
204, 210, 215, 220, 226, 231, 237, 242, 248, 254, 260, 266, 272, 278, 284, 291,
|
||||
297, 304, 310, 317, 324, 331, 338, 345, 352, 359, 367, 374, 382, 390, 397, 405,
|
||||
413, 421, 430, 438, 446, 455, 463, 472, 481, 490, 499, 508, 517, 526, 536, 545,
|
||||
555, 565, 575, 585, 595, 605, 615, 626, 636, 647, 658, 669, 680, 691, 702, 713,
|
||||
725, 736, 748, 760, 772, 784, 796, 808, 821, 833, 846, 859, 872, 885, 898, 911,
|
||||
925, 938, 952, 966, 980, 994,1008,1022,1037,1051,1066,1081,1096,1111,1126,1142,
|
||||
1157,1173,1189,1204,1221,1237,1253,1270,1286,1303,1320,1337,1354,1371,1389,1406,
|
||||
1424,1442,1460,1478,1496,1515,1533,1552,1571,1590,1609,1629,1648,1668,1687,1707,
|
||||
1727,1748,1768,1789,1809,1830,1851,1872,1894,1915,1937,1958,1980,2002,2025,2047,
|
||||
};
|
||||
|
||||
|
||||
return (uint32_t) gamma_lut[value];
|
||||
}
|
||||
|
||||
void RgbLed::SetColor(const uint8_t r, const uint8_t g, const uint8_t b)
|
||||
{
|
||||
std::vector<uint32_t> values = {this->gamma(r),this->gamma(g),this->gamma(b)};
|
||||
this->PwmWrite(values);
|
||||
}
|
|
@ -1,125 +1,137 @@
|
|||
#ifndef __GPIO_HPP_
|
||||
#define __GPIO_HPP_
|
||||
#ifndef __MC_GPIOBUTTON_HPP
|
||||
#define __MC_GPIOBUTTON_HPP
|
||||
|
||||
#include "../exception/baseexceptions.hpp"
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include <boost/signals2.hpp>
|
||||
#include <gpiod.hpp>
|
||||
#include "../thread/thread.hpp"
|
||||
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
/*! \file Gpio interrupt capture functions. Header file.
|
||||
*/
|
||||
|
||||
// P1 Header pins (both board revisions)
|
||||
#define GPIO_0_2 2 /*!< \def Gpio pin 0/2 (rev1/rev2) */
|
||||
#define GPIO_1_3 3 /*!< \def Gpio pin 1/3 (rev1/rev2) */
|
||||
#define GPIO_4 4 /*!< \def Gpio pin 4 */
|
||||
#define GPIO_7 7 /*!< \def Gpio pin 7 */
|
||||
#define GPIO_8 8 /*!< \def Gpio pin 8 */
|
||||
#define GPIO_9 9 /*!< \def Gpio pin 9 */
|
||||
#define GPIO_10 10 /*!< \def Gpio pin 10 */
|
||||
#define GPIO_11 11 /*!< \def Gpio pin 11 */
|
||||
#define GPIO_14 14 /*!< \def Gpio pin 14 */
|
||||
#define GPIO_15 15 /*!< \def Gpio pin 15 */
|
||||
#define GPIO_17 17 /*!< \def Gpio pin 17 */
|
||||
#define GPIO_18 18 /*!< \def Gpio pin 18 */
|
||||
#define GPIO_21_27 27 /*!< \def Gpio pin 21/27 (rev1/rev2) */
|
||||
#define GPIO_22 22 /*!< \def Gpio pin 22 */
|
||||
#define GPIO_23 23 /*!< \def Gpio pin 23 */
|
||||
#define GPIO_24 24 /*!< \def Gpio pin 24 */
|
||||
#define GPIO_25 25 /*!< \def Gpio pin 25 */
|
||||
|
||||
// P5 Header pins (only rev 2)
|
||||
#define GPIO_28 28 /*!< \def Gpio pin 28 */
|
||||
#define GPIO_29 29 /*!< \def Gpio pin 29 */
|
||||
#define GPIO_30 30 /*!< \def Gpio pin 30 */
|
||||
#define GPIO_31 31 /*!< \def Gpio pin 31 */
|
||||
|
||||
#define GPIO_FN_MAXLEN 128 // length of file name field
|
||||
|
||||
class GpioException : MsgException
|
||||
enum class GpioEdge
|
||||
{
|
||||
protected:
|
||||
virtual std::string type() { return "GpioException"; }
|
||||
None = 0,
|
||||
Rising = 1,
|
||||
Falling = 2,
|
||||
Both = 3
|
||||
};
|
||||
|
||||
//! Enum for specifying input/output direction
|
||||
enum GpioDirection
|
||||
enum class GpioDirection
|
||||
{
|
||||
kDirectionOut = 0,
|
||||
kDirectionIn = 1
|
||||
Input = 0,
|
||||
Output = 1,
|
||||
PwmOutput = 2,
|
||||
};
|
||||
|
||||
//! Enum for specifying edge detection type
|
||||
enum GpioEdge
|
||||
enum class GpioPullup
|
||||
{
|
||||
kEdgeNone = 0,
|
||||
kEdgeRising = 1,
|
||||
kEdgeFalling = 2,
|
||||
kEdgeBoth = 3
|
||||
None = 0,
|
||||
PullUp = 1,
|
||||
PullDown = 2,
|
||||
};
|
||||
|
||||
class GpioPin : public Thread
|
||||
enum class GpioOutput
|
||||
{
|
||||
public:
|
||||
GpioPin(int pinnr, GpioDirection direction, GpioEdge edge);
|
||||
~GpioPin();
|
||||
|
||||
//! Get the actual used pin number of the IO Pin
|
||||
int getPinNr();
|
||||
|
||||
//! Get current direction of pin
|
||||
GpioDirection getDirection();
|
||||
//! Set new direction of pin
|
||||
void setDirection(GpioDirection direction);
|
||||
|
||||
//! Get current edge detection type
|
||||
GpioEdge getEdge();
|
||||
//! Set edge detection type
|
||||
void setEdge(GpioEdge edge);
|
||||
|
||||
//! Get current value of pin
|
||||
bool getValue();
|
||||
//! Set new value of pin
|
||||
void setValue(bool value);
|
||||
|
||||
//! Start interrupt listener
|
||||
void InterruptStart();
|
||||
//! Stop interrupt listener
|
||||
void InterruptStop();
|
||||
|
||||
//! Signal on interrupt
|
||||
boost::signals2::signal<void (GpioPin *, GpioEdge, bool)> onInterrupt;
|
||||
|
||||
// Tag to store application-dependant data
|
||||
void * Tag;
|
||||
|
||||
virtual void ThreadFunc();
|
||||
|
||||
private:
|
||||
int pin; // Gpio pin number
|
||||
std::string fnDirection; // File name for Direction file
|
||||
std::string fnEdge; // File name for Edge file
|
||||
std::string fnValue; // File name for Value file
|
||||
bool pinPreExported; // Bool indicates if the pin was already exported
|
||||
Both = 0,
|
||||
OpenDrain = 1,
|
||||
OpenSource = 2,
|
||||
};
|
||||
|
||||
|
||||
//! Verifies gpio pin number, and translates pin numbers (REV2) to the proper REV1 or REV2 board gpio pin numbers
|
||||
static int verifyPin(int gpiopin);
|
||||
enum class GpioPolarity
|
||||
{
|
||||
ActiveHigh = 0,
|
||||
ActiveLow = 1,
|
||||
};
|
||||
|
||||
//! Export a certain Gpio pin
|
||||
static bool exportPin(int gpiopin);
|
||||
|
||||
//! Unexport a certain Gpio pin
|
||||
static bool unexportPin(int gpiopin);
|
||||
class GpioBase : protected Thread
|
||||
{
|
||||
public:
|
||||
GpioBase();
|
||||
~GpioBase();
|
||||
|
||||
//! open a file for writing and write text to it.
|
||||
static void writeFile(std::string &fname, const char *value);
|
||||
static void writeFile(std::string &fname, std::string &value);
|
||||
boost::signals2::signal<void (uint8_t gpio, GpioEdge edge, bool level)> onChange;
|
||||
|
||||
void PinMode(GpioDirection dir, GpioOutput op, bool active_low = false);
|
||||
void PinMode(GpioDirection dir, GpioPullup pu, bool active_low = false);
|
||||
void PinMode(GpioDirection dir, bool active_low = false);
|
||||
|
||||
void Listen(GpioEdge edge, bool active_low = false);
|
||||
|
||||
virtual void Listen(GpioEdge edge, GpioPullup pu, bool active_low = false) = 0;
|
||||
|
||||
protected:
|
||||
virtual void PinMode(GpioDirection dir, GpioPullup pu = GpioPullup::None, GpioOutput op = GpioOutput::Both, bool active_low = false) = 0;
|
||||
gpiod::line_request init_linerq(GpioDirection dir, GpioEdge edge, GpioPullup pu, GpioOutput op, bool active_low);
|
||||
gpiod::chip gpioChip;
|
||||
|
||||
GpioDirection dir;
|
||||
|
||||
};
|
||||
|
||||
class GpioPin : public GpioBase
|
||||
{
|
||||
public:
|
||||
GpioPin(uint8_t gpio);
|
||||
~GpioPin();
|
||||
|
||||
virtual void DigitalWrite(bool value);
|
||||
virtual bool DigitalRead(void);
|
||||
|
||||
virtual void PwmWrite(uint32_t width);
|
||||
|
||||
virtual void Listen(GpioEdge edge, GpioPullup pu, bool active_low = false);
|
||||
|
||||
virtual bool IsAvailable();
|
||||
protected:
|
||||
virtual void PinMode(GpioDirection dir, GpioPullup pu = GpioPullup::None, GpioOutput op = GpioOutput::Both,bool active_low = false);
|
||||
|
||||
virtual void ThreadLoop();
|
||||
gpiod::line line;
|
||||
|
||||
};
|
||||
|
||||
class GpioPins : public GpioBase
|
||||
{
|
||||
public:
|
||||
GpioPins(std::vector<uint8_t> gpios);
|
||||
~GpioPins();
|
||||
|
||||
virtual void DigitalWrite(std::vector<bool> values);
|
||||
virtual std::vector<bool> DigitalRead(void);
|
||||
virtual void PwmWrite(std::vector<uint32_t> widths);
|
||||
|
||||
|
||||
virtual void Listen(GpioEdge edge, GpioPullup pu, bool active_low = false);
|
||||
|
||||
protected:
|
||||
virtual void PinMode(GpioDirection dir, GpioPullup pu = GpioPullup::None, GpioOutput op = GpioOutput::Both,bool active_low = false);
|
||||
|
||||
virtual void ThreadLoop();
|
||||
gpiod::line_bulk lines;
|
||||
|
||||
};
|
||||
|
||||
class RgbLed : protected GpioPins
|
||||
{
|
||||
protected:
|
||||
uint32_t gamma(uint8_t value);
|
||||
public:
|
||||
RgbLed(const uint8_t rPin, const uint8_t gPin, const uint8_t bPin);
|
||||
|
||||
/*!
|
||||
\param r The red component (0 to 255)
|
||||
\param g The green component (0 to 255)
|
||||
\param b The blue component (0 to 255)
|
||||
*/
|
||||
void SetColor(const uint8_t r, const uint8_t g, const uint8_t b);
|
||||
|
||||
//! open a file for reading and read some text from it
|
||||
static std::string readFile(std::string &fname);
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
#endif//__MC_HID_SERVER_HPP
|
299
src/gpio/mailbox.c
Normal file
299
src/gpio/mailbox.c
Normal file
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
Copyright (c) 2012, Broadcom Europe Ltd.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the copyright holder nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sysmacros.h>
|
||||
|
||||
#include "mailbox.h"
|
||||
|
||||
//#define DEBUG
|
||||
|
||||
#define PAGE_SIZE (4*1024)
|
||||
|
||||
void *mapmem(unsigned base, unsigned size)
|
||||
{
|
||||
int mem_fd;
|
||||
unsigned offset = base % PAGE_SIZE;
|
||||
base = base - offset;
|
||||
/* open /dev/mem */
|
||||
if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
|
||||
printf("can't open /dev/mem\nThis program should be run as root. Try prefixing command with: sudo\n");
|
||||
exit (-1);
|
||||
}
|
||||
void *mem = mmap(
|
||||
0,
|
||||
size,
|
||||
PROT_READ|PROT_WRITE,
|
||||
MAP_SHARED/*|MAP_FIXED*/,
|
||||
mem_fd,
|
||||
base);
|
||||
#ifdef DEBUG
|
||||
printf("base=0x%x, mem=%p\n", base, mem);
|
||||
#endif
|
||||
if (mem == MAP_FAILED) {
|
||||
printf("mmap error %d\n", (int)mem);
|
||||
exit (-1);
|
||||
}
|
||||
close(mem_fd);
|
||||
return (char *)mem + offset;
|
||||
}
|
||||
|
||||
void *unmapmem(void *addr, unsigned size)
|
||||
{
|
||||
int s = munmap(addr, size);
|
||||
if (s != 0) {
|
||||
printf("munmap error %d\n", s);
|
||||
exit (-1);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* use ioctl to send mbox property message
|
||||
*/
|
||||
|
||||
static int mbox_property(int file_desc, void *buf)
|
||||
{
|
||||
int fd = file_desc;
|
||||
int ret_val = -1;
|
||||
|
||||
if (fd < 0) {
|
||||
fd = mbox_open();
|
||||
}
|
||||
if (fd >= 0) {
|
||||
ret_val = ioctl(fd, IOCTL_MBOX_PROPERTY, buf);
|
||||
|
||||
if (ret_val < 0) {
|
||||
printf("ioctl_set_msg failed, errno %d: %m\n", errno);
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
unsigned *p = buf; int i; unsigned size = *(unsigned *)buf;
|
||||
for (i=0; i<size/4; i++)
|
||||
printf("%04x: 0x%08x\n", i*sizeof *p, p[i]);
|
||||
#endif
|
||||
if (file_desc < 0)
|
||||
mbox_close(fd);
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
unsigned mem_alloc(int file_desc, unsigned size, unsigned align, unsigned flags)
|
||||
{
|
||||
int i=0;
|
||||
unsigned p[32];
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("Requesting %d bytes\n", size);
|
||||
#endif
|
||||
p[i++] = 0; // size
|
||||
p[i++] = 0x00000000; // process request
|
||||
|
||||
p[i++] = 0x3000c; // (the tag id)
|
||||
p[i++] = 12; // (size of the buffer)
|
||||
p[i++] = 12; // (size of the data)
|
||||
p[i++] = size; // (num bytes? or pages?)
|
||||
p[i++] = align; // (alignment)
|
||||
p[i++] = flags; // (MEM_FLAG_L1_NONALLOCATING)
|
||||
|
||||
p[i++] = 0x00000000; // end tag
|
||||
p[0] = i*sizeof *p; // actual size
|
||||
|
||||
if (mbox_property(file_desc, p) < 0)
|
||||
return -1;
|
||||
else
|
||||
return p[5];
|
||||
}
|
||||
|
||||
unsigned mem_free(int file_desc, unsigned handle)
|
||||
{
|
||||
int i=0;
|
||||
unsigned p[32];
|
||||
p[i++] = 0; // size
|
||||
p[i++] = 0x00000000; // process request
|
||||
|
||||
p[i++] = 0x3000f; // (the tag id)
|
||||
p[i++] = 4; // (size of the buffer)
|
||||
p[i++] = 4; // (size of the data)
|
||||
p[i++] = handle;
|
||||
|
||||
p[i++] = 0x00000000; // end tag
|
||||
p[0] = i*sizeof *p; // actual size
|
||||
|
||||
mbox_property(file_desc, p);
|
||||
return p[5];
|
||||
}
|
||||
|
||||
unsigned mem_lock(int file_desc, unsigned handle)
|
||||
{
|
||||
int i=0;
|
||||
unsigned p[32];
|
||||
p[i++] = 0; // size
|
||||
p[i++] = 0x00000000; // process request
|
||||
|
||||
p[i++] = 0x3000d; // (the tag id)
|
||||
p[i++] = 4; // (size of the buffer)
|
||||
p[i++] = 4; // (size of the data)
|
||||
p[i++] = handle;
|
||||
|
||||
p[i++] = 0x00000000; // end tag
|
||||
p[0] = i*sizeof *p; // actual size
|
||||
|
||||
if (mbox_property(file_desc, p) < 0)
|
||||
return ~0;
|
||||
else
|
||||
return p[5];
|
||||
}
|
||||
|
||||
unsigned mem_unlock(int file_desc, unsigned handle)
|
||||
{
|
||||
int i=0;
|
||||
unsigned p[32];
|
||||
p[i++] = 0; // size
|
||||
p[i++] = 0x00000000; // process request
|
||||
|
||||
p[i++] = 0x3000e; // (the tag id)
|
||||
p[i++] = 4; // (size of the buffer)
|
||||
p[i++] = 4; // (size of the data)
|
||||
p[i++] = handle;
|
||||
|
||||
p[i++] = 0x00000000; // end tag
|
||||
p[0] = i*sizeof *p; // actual size
|
||||
|
||||
mbox_property(file_desc, p);
|
||||
return p[5];
|
||||
}
|
||||
|
||||
unsigned execute_code(int file_desc, unsigned code, unsigned r0, unsigned r1, unsigned r2, unsigned r3, unsigned r4, unsigned r5)
|
||||
{
|
||||
int i=0;
|
||||
unsigned p[32];
|
||||
p[i++] = 0; // size
|
||||
p[i++] = 0x00000000; // process request
|
||||
|
||||
p[i++] = 0x30010; // (the tag id)
|
||||
p[i++] = 28; // (size of the buffer)
|
||||
p[i++] = 28; // (size of the data)
|
||||
p[i++] = code;
|
||||
p[i++] = r0;
|
||||
p[i++] = r1;
|
||||
p[i++] = r2;
|
||||
p[i++] = r3;
|
||||
p[i++] = r4;
|
||||
p[i++] = r5;
|
||||
|
||||
p[i++] = 0x00000000; // end tag
|
||||
p[0] = i*sizeof *p; // actual size
|
||||
|
||||
mbox_property(file_desc, p);
|
||||
return p[5];
|
||||
}
|
||||
|
||||
unsigned qpu_enable(int file_desc, unsigned enable)
|
||||
{
|
||||
int i=0;
|
||||
unsigned p[32];
|
||||
|
||||
p[i++] = 0; // size
|
||||
p[i++] = 0x00000000; // process request
|
||||
|
||||
p[i++] = 0x30012; // (the tag id)
|
||||
p[i++] = 4; // (size of the buffer)
|
||||
p[i++] = 4; // (size of the data)
|
||||
p[i++] = enable;
|
||||
|
||||
p[i++] = 0x00000000; // end tag
|
||||
p[0] = i*sizeof *p; // actual size
|
||||
|
||||
mbox_property(file_desc, p);
|
||||
return p[5];
|
||||
}
|
||||
|
||||
unsigned execute_qpu(int file_desc, unsigned num_qpus, unsigned control, unsigned noflush, unsigned timeout) {
|
||||
int i=0;
|
||||
unsigned p[32];
|
||||
|
||||
p[i++] = 0; // size
|
||||
p[i++] = 0x00000000; // process request
|
||||
p[i++] = 0x30011; // (the tag id)
|
||||
p[i++] = 16; // (size of the buffer)
|
||||
p[i++] = 16; // (size of the data)
|
||||
p[i++] = num_qpus;
|
||||
p[i++] = control;
|
||||
p[i++] = noflush;
|
||||
p[i++] = timeout; // ms
|
||||
|
||||
p[i++] = 0x00000000; // end tag
|
||||
p[0] = i*sizeof *p; // actual size
|
||||
|
||||
mbox_property(file_desc, p);
|
||||
return p[5];
|
||||
}
|
||||
|
||||
int mbox_open(void) {
|
||||
int file_desc;
|
||||
char filename[64];
|
||||
|
||||
// open a char device file used for communicating with kernel mbox driver
|
||||
if ((file_desc = open("/dev/vcio", 0)) >= 0) {
|
||||
/* New kernel, we use /dev/vcio, major 249, since kernel 4.1 */
|
||||
return file_desc;
|
||||
}
|
||||
|
||||
/* Most likely an old kernel, so drop back to the old major=100 device */
|
||||
sprintf(filename, "/tmp/mailbox-%d", getpid());
|
||||
unlink(filename);
|
||||
if (mknod(filename, S_IFCHR|0600, makedev(100, 0)) < 0) {
|
||||
printf("Failed to create mailbox device %s: %m\n", filename);
|
||||
return -1;
|
||||
}
|
||||
file_desc = open(filename, 0);
|
||||
if (file_desc < 0) {
|
||||
printf("Can't open device file %s: %m\n", filename);
|
||||
unlink(filename);
|
||||
return -1;
|
||||
}
|
||||
unlink(filename);
|
||||
|
||||
return file_desc;
|
||||
}
|
||||
|
||||
void mbox_close(int file_desc) {
|
||||
close(file_desc);
|
||||
}
|
54
src/gpio/mailbox.h
Normal file
54
src/gpio/mailbox.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Copyright (c) 2012, Broadcom Europe Ltd.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the copyright holder nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
#define MAJOR_NUM 100
|
||||
#define IOCTL_MBOX_PROPERTY _IOWR(MAJOR_NUM, 0, char *)
|
||||
#define DEVICE_FILE_NAME "/dev/vcio-mb"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int mbox_open(void);
|
||||
void mbox_close(int file_desc);
|
||||
|
||||
unsigned get_version(int file_desc);
|
||||
unsigned mem_alloc(int file_desc, unsigned size, unsigned align, unsigned flags);
|
||||
unsigned mem_free(int file_desc, unsigned handle);
|
||||
unsigned mem_lock(int file_desc, unsigned handle);
|
||||
unsigned mem_unlock(int file_desc, unsigned handle);
|
||||
void *mapmem(unsigned base, unsigned size);
|
||||
void *unmapmem(void *addr, unsigned size);
|
||||
|
||||
unsigned execute_code(int file_desc, unsigned code, unsigned r0, unsigned r1, unsigned r2, unsigned r3, unsigned r4, unsigned r5);
|
||||
unsigned execute_qpu(int file_desc, unsigned num_qpus, unsigned control, unsigned noflush, unsigned timeout);
|
||||
unsigned qpu_enable(int file_desc, unsigned enable);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
127
src/i2c/i2c.c
127
src/i2c/i2c.c
|
@ -1,127 +0,0 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <linux/i2c-dev.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "i2c.h"
|
||||
|
||||
static unsigned int HardwareRevision(void);
|
||||
|
||||
static unsigned int HardwareRevision(void)
|
||||
{
|
||||
FILE * filp;
|
||||
unsigned rev;
|
||||
char buf[512];
|
||||
char term;
|
||||
|
||||
rev = 0;
|
||||
|
||||
filp = fopen ("/proc/cpuinfo", "r");
|
||||
|
||||
if (filp != NULL)
|
||||
{
|
||||
while (fgets(buf, sizeof(buf), filp) != NULL)
|
||||
{
|
||||
if (!strncasecmp("revision\t", buf, 9))
|
||||
{
|
||||
if (sscanf(buf+strlen(buf)-5, "%x%c", &rev, &term) == 2)
|
||||
{
|
||||
if (term == '\n') break;
|
||||
rev = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(filp);
|
||||
}
|
||||
return rev;
|
||||
}
|
||||
|
||||
int i2cInit(unsigned char address)
|
||||
{
|
||||
int fd; // File descrition
|
||||
char *fileName = "/dev/i2c-1"; // Name of the port we will be using (using revision 2 board's /dev/i2c-1 by default)
|
||||
|
||||
unsigned int rev = HardwareRevision();
|
||||
if (rev < 4){ // Switch to revision 1 board's /dev/i2c-0 if revision number is below 4;
|
||||
strcpy(fileName,"/dev/i2c-0");
|
||||
}
|
||||
|
||||
if ((fd = open(fileName, O_RDWR)) < 0) { // Open port for reading and writing
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ioctl(fd, I2C_SLAVE, address) < 0) { // Set the port options and set the address of the device we wish to speak to
|
||||
return -2;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
void i2cClose(int fd)
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int i2cReadReg8(int fd, unsigned char reg)
|
||||
{
|
||||
unsigned char buf[1]; // Buffer for data being read/ written on the i2c bus
|
||||
buf[0] = reg; // This is the register we wish to read from
|
||||
|
||||
if ((write(fd, buf, 1)) != 1) { // Send register to read from
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (read(fd, buf, 1) != 1) { // Read back data into buf[]
|
||||
return -2;
|
||||
}
|
||||
|
||||
return buf[0];
|
||||
}
|
||||
|
||||
int i2cWriteReg8(int fd, unsigned char reg, unsigned char value)
|
||||
{
|
||||
unsigned char buf[2];
|
||||
buf[0] = reg; // Commands for performing a ranging on the SRF08
|
||||
buf[1] = value;
|
||||
|
||||
if ((write(fd, buf, 2)) != 2) { // Write commands to the i2c port
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i2cReadReg16(int fd, unsigned char reg)
|
||||
{
|
||||
unsigned char buf[2]; // Buffer for data being read/ written on the i2c bus
|
||||
buf[0] = reg; // This is the register we wish to read from
|
||||
|
||||
if ((write(fd, buf, 1)) != 1) { // Send register to read from
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (read(fd, buf, 2) != 2) { // Read back data into buf[]
|
||||
return -2;
|
||||
}
|
||||
return (int)(buf[1] << 8) | (int)buf[0];
|
||||
|
||||
|
||||
}
|
||||
|
||||
int i2cWriteReg16(int fd, unsigned char reg,unsigned short value)
|
||||
{
|
||||
unsigned char buf[3];
|
||||
buf[0] = reg; // Commands for performing a ranging on the SRF08
|
||||
buf[1] = (unsigned char)( ( value >> 0 ) & 0xFF );
|
||||
buf[2] = (unsigned char)( ( value >> 8 ) & 0xFF );
|
||||
|
||||
if ((write(fd, buf, 3)) != 3) { // Write commands to the i2c port
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
#ifndef __USER_I2C_H__
|
||||
#define __USER_I2C_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int i2cInit(unsigned char address);
|
||||
void i2cClose(int fd);
|
||||
int i2cReadReg8(int fd, unsigned char reg);
|
||||
int i2cWriteReg8(int fd, unsigned char reg, unsigned char value);
|
||||
int i2cReadReg16(int fd, unsigned char reg);
|
||||
int i2cWriteReg16(int fd, unsigned char reg,unsigned short value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -32,7 +32,7 @@ Log::Log(std::string ident, int facility) {
|
|||
|
||||
int Log::sync() {
|
||||
if (buffer_.length()) {
|
||||
syslog(priority_, buffer_.c_str());
|
||||
syslog(priority_, "%s", buffer_.c_str());
|
||||
buffer_.erase();
|
||||
priority_ = LOG_DEBUG; // default to debug for each message
|
||||
}
|
||||
|
|
|
@ -14,17 +14,12 @@
|
|||
<arg type="y" name="b" direction="in" />
|
||||
<arg type="d" name="interval" direction="in" />
|
||||
</method>
|
||||
<method name="JackState">
|
||||
<arg type="b" name="jackedin" direction="out" />
|
||||
</method>
|
||||
<signal name="ButtonDown">
|
||||
<arg name="button" type="s"/>
|
||||
</signal>
|
||||
<signal name="ButtonUp">
|
||||
<arg name="button" type="s"/>
|
||||
</signal>
|
||||
<signal name="JackIn" />
|
||||
<signal name="JackOut" />
|
||||
<signal name="ButtonPress">
|
||||
<arg name="button" type="s"/>
|
||||
</signal>
|
||||
|
|
|
@ -24,7 +24,6 @@ public:
|
|||
register_method(Hid_adaptor, SetColor, _SetColor_stub);
|
||||
register_method(Hid_adaptor, ClearColor, _ClearColor_stub);
|
||||
register_method(Hid_adaptor, PulseColor, _PulseColor_stub);
|
||||
register_method(Hid_adaptor, JackState, _JackState_stub);
|
||||
}
|
||||
|
||||
::DBus::IntrospectedInterface *introspect() const
|
||||
|
@ -48,11 +47,6 @@ public:
|
|||
{ "interval", "d", true },
|
||||
{ 0, 0, 0 }
|
||||
};
|
||||
static ::DBus::IntrospectedArgument JackState_args[] =
|
||||
{
|
||||
{ "jackedin", "b", false },
|
||||
{ 0, 0, 0 }
|
||||
};
|
||||
static ::DBus::IntrospectedArgument ButtonDown_args[] =
|
||||
{
|
||||
{ "button", "s", false },
|
||||
|
@ -63,14 +57,6 @@ public:
|
|||
{ "button", "s", false },
|
||||
{ 0, 0, 0 }
|
||||
};
|
||||
static ::DBus::IntrospectedArgument JackIn_args[] =
|
||||
{
|
||||
{ 0, 0, 0 }
|
||||
};
|
||||
static ::DBus::IntrospectedArgument JackOut_args[] =
|
||||
{
|
||||
{ 0, 0, 0 }
|
||||
};
|
||||
static ::DBus::IntrospectedArgument ButtonPress_args[] =
|
||||
{
|
||||
{ "button", "s", false },
|
||||
|
@ -86,15 +72,12 @@ public:
|
|||
{ "SetColor", SetColor_args },
|
||||
{ "ClearColor", ClearColor_args },
|
||||
{ "PulseColor", PulseColor_args },
|
||||
{ "JackState", JackState_args },
|
||||
{ 0, 0 }
|
||||
};
|
||||
static ::DBus::IntrospectedMethod Hid_adaptor_signals[] =
|
||||
{
|
||||
{ "ButtonDown", ButtonDown_args },
|
||||
{ "ButtonUp", ButtonUp_args },
|
||||
{ "JackIn", JackIn_args },
|
||||
{ "JackOut", JackOut_args },
|
||||
{ "ButtonPress", ButtonPress_args },
|
||||
{ "ButtonLongPress", ButtonLongPress_args },
|
||||
{ 0, 0 }
|
||||
|
@ -127,7 +110,6 @@ public:
|
|||
virtual void SetColor(const uint8_t& r, const uint8_t& g, const uint8_t& b) = 0;
|
||||
virtual void ClearColor() = 0;
|
||||
virtual void PulseColor(const uint8_t& r, const uint8_t& g, const uint8_t& b, const double& interval) = 0;
|
||||
virtual bool JackState() = 0;
|
||||
|
||||
public:
|
||||
|
||||
|
@ -147,16 +129,6 @@ public:
|
|||
wi << arg1;
|
||||
emit_signal(sig);
|
||||
}
|
||||
void JackIn()
|
||||
{
|
||||
::DBus::SignalMessage sig("JackIn");
|
||||
emit_signal(sig);
|
||||
}
|
||||
void JackOut()
|
||||
{
|
||||
::DBus::SignalMessage sig("JackOut");
|
||||
emit_signal(sig);
|
||||
}
|
||||
void ButtonPress(const std::string& arg1)
|
||||
{
|
||||
::DBus::SignalMessage sig("ButtonPress");
|
||||
|
@ -207,16 +179,6 @@ private:
|
|||
::DBus::ReturnMessage reply(call);
|
||||
return reply;
|
||||
}
|
||||
::DBus::Message _JackState_stub(const ::DBus::CallMessage &call)
|
||||
{
|
||||
::DBus::MessageIter ri = call.reader();
|
||||
|
||||
bool argout1 = JackState();
|
||||
::DBus::ReturnMessage reply(call);
|
||||
::DBus::MessageIter wi = reply.writer();
|
||||
wi << argout1;
|
||||
return reply;
|
||||
}
|
||||
};
|
||||
|
||||
} } }
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
#ifdef HAVE_CONFIG_H
|
||||
//#ifdef HAVE_CONFIG_H
|
||||
#include <config.hpp>
|
||||
#endif
|
||||
//#endif
|
||||
|
||||
#include "mc-hid-server.hpp"
|
||||
#include "log/log.hpp"
|
||||
|
||||
#include <pigpio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
|
@ -12,50 +13,67 @@
|
|||
#include <limits.h>
|
||||
#include <pthread.h>
|
||||
|
||||
|
||||
// For getting current time
|
||||
#include <time.h>
|
||||
#ifndef CLOCK_MONOTIC
|
||||
# define CLOCK_MONOTIC CLOCK_REALTIME
|
||||
#endif
|
||||
|
||||
#define NOISE_TIMEOUT_MS 400
|
||||
#define GPIO_INT_PIN 4
|
||||
#define MCP23017_ADR 0x20
|
||||
|
||||
#define BTN_ESCAPE 0x0001
|
||||
#define BTN_FAVORITES 0x0002
|
||||
#define BTN_RECENT 0x0004
|
||||
|
||||
#define BTN_REWIND 0x0008
|
||||
#define BTN_PLAYPAUSE 0x0010
|
||||
#define BTN_FASTFORWARD 0x0020
|
||||
|
||||
#define BTN_CANCEL 0x0040
|
||||
#define BTN_OK 0x0080
|
||||
#define BTN_UP 0x0100
|
||||
#define BTN_DOWN 0x0200
|
||||
|
||||
#define SENSE_MINIJACK 0x0400
|
||||
|
||||
#define PIN_RED 12
|
||||
#define PIN_GREEN 13
|
||||
#define PIN_BLUE 14
|
||||
|
||||
#define PULSE_STEPS 32
|
||||
#define PULSE_WAIT_US 20000
|
||||
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
using namespace std;
|
||||
|
||||
const uint8_t BTN_HOME = 7;
|
||||
const uint8_t BTN_CANCEL = 8;
|
||||
const uint8_t BTN_UP = 9;
|
||||
const uint8_t BTN_DOWN = 10;
|
||||
const uint8_t BTN_PLAY = 11;
|
||||
const uint8_t BTN_FWD = 12;
|
||||
const uint8_t BTN_RWD = 16;
|
||||
const uint8_t BTN_OK = 17;
|
||||
|
||||
// map the buttons to names
|
||||
const std::map<uint8_t,std::string> BUTTONS = {
|
||||
{ BTN_HOME, "BTN_ESCAPE"},
|
||||
{ BTN_CANCEL, "BTN_CANCEL"},
|
||||
{ BTN_UP, "BTN_UP"},
|
||||
{ BTN_DOWN, "BTN_DOWN"},
|
||||
{ BTN_FWD, "BTN_FASTFORWARD"},
|
||||
{ BTN_PLAY, "BTN_PLAYPAUSE"},
|
||||
{ BTN_RWD, "BTN_REWIND"},
|
||||
{ BTN_OK, "BTN_OK"},
|
||||
};
|
||||
|
||||
// define the active state of the
|
||||
const std::map<uint8_t,bool> BUTTONS_ACTIVE = {
|
||||
{ BTN_HOME, true},
|
||||
{ BTN_CANCEL, false},
|
||||
{ BTN_UP, false},
|
||||
{ BTN_DOWN, false},
|
||||
{ BTN_FWD, false},
|
||||
{ BTN_PLAY, false},
|
||||
{ BTN_RWD, false},
|
||||
{ BTN_OK, false},
|
||||
};
|
||||
|
||||
const std::vector<uint8_t> BUTTONS_REGULAR = {
|
||||
BTN_CANCEL,
|
||||
BTN_UP,
|
||||
BTN_DOWN,
|
||||
BTN_FWD,
|
||||
BTN_PLAY,
|
||||
BTN_RWD,
|
||||
BTN_OK,
|
||||
};
|
||||
|
||||
|
||||
const uint8_t LED_RED = 4;
|
||||
const uint8_t LED_GREEN = 5;
|
||||
const uint8_t LED_BLUE = 6;
|
||||
|
||||
const uint8_t FAN_CTL = 13;
|
||||
|
||||
const int PULSE_STEPS = 32;
|
||||
|
||||
|
||||
|
||||
static const char *HID_SERVER_NAME = "nl.miqra.MediaCore.Hid";
|
||||
static const char *HID_SERVER_PATH = "/nl/miqra/MediaCore/Hid";
|
||||
|
||||
|
||||
// function to get current time in ms
|
||||
int64_t now_ms(void);
|
||||
|
||||
// signal handler
|
||||
void niam(int sig);
|
||||
|
||||
|
@ -63,14 +81,11 @@ HidServer::HidServer(DBus::Connection &connection)
|
|||
: DBus::ObjectAdaptor(connection, HID_SERVER_PATH)
|
||||
{
|
||||
|
||||
// Initialize class variables if needed
|
||||
noiseTimeout = 0; // Value of 0 means: no noise timeout
|
||||
|
||||
// Initialize button timer
|
||||
btnTimer = new ButtonTimer(25,6000); // Short press should take at leas 25 ms, and a Long press takes 6 seconds
|
||||
onShortPressConnection = btnTimer->onShortPress.connect(boost::bind(&HidServer::onShortPress, this, _1));
|
||||
onLongPressConnection = btnTimer->onLongPress.connect(boost::bind(&HidServer::onLongPress, this, _1));
|
||||
onValidatePressConnection = btnTimer->onValidatePress.connect(boost::bind(&HidServer::onValidatePress, this, _1));
|
||||
btnTimer = new ButtonTimer(10,5000); // Short press should take at leas 10 ms, and a Long press takes 5 seconds
|
||||
onShortPressConnection = this->btnTimer->onShortPress.connect( boost::bind( &HidServer::onShortPress, this, _1) );
|
||||
onLongPressConnection = this->btnTimer->onLongPress.connect( boost::bind( &HidServer::onLongPress, this, _1) );
|
||||
onValidatePressConnection = this->btnTimer->onValidatePress.connect( boost::bind( &HidServer::onValidatePress, this, _1) );
|
||||
|
||||
// **** Initialize the GPIO Interrupt pin
|
||||
|
||||
|
@ -78,12 +93,33 @@ HidServer::HidServer(DBus::Connection &connection)
|
|||
|
||||
try
|
||||
{
|
||||
initHardware();
|
||||
// Setup all buttons for pullup and input
|
||||
|
||||
// Setup home button (which is active-high)
|
||||
clog << kLogInfo << "Connecting to Home button";
|
||||
this->btHome = new GpioPin(BTN_HOME);
|
||||
clog << kLogInfo << ".";
|
||||
onBtHomeConnection = this->btHome->onChange.connect( boost::bind( &HidServer::onBtChange, this, _1, _2, _3) );
|
||||
clog << kLogInfo << ".";
|
||||
this->btHome->Listen(GpioEdge::Both, GpioPullup::PullUp, false);
|
||||
clog << kLogInfo << "." << endl;
|
||||
|
||||
// Setup theo ther butons (which are active-low)
|
||||
clog << kLogInfo << "Connecting to other buttons";
|
||||
this->btOther = new GpioPins(BUTTONS_REGULAR);
|
||||
clog << kLogInfo << ".";
|
||||
onBtOtherConnection = this->btOther->onChange.connect( boost::bind( &HidServer::onBtChange, this, _1, _2, _3) );
|
||||
clog << kLogInfo << ".";
|
||||
this->btOther->Listen(GpioEdge::Both, GpioPullup::PullUp, true);
|
||||
clog << kLogInfo << "." << endl;
|
||||
|
||||
// setup the LED
|
||||
this->rgbLed = new RgbLed(LED_RED,LED_GREEN,LED_BLUE);
|
||||
}
|
||||
catch(std::exception x)
|
||||
catch(const std::exception& x)
|
||||
{
|
||||
clog << kLogCrit << "Fatal error during hardware initialization: " << x.what() << endl << "Quitting now... " << endl;
|
||||
niam(0);
|
||||
clog << kLogCrit << endl << "Fatal error during hardware initialization: " << x.what() << endl << "Quitting now... " << endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
clog << kLogInfo << "Initialization complete, listening to HID requests." << endl;
|
||||
|
@ -93,127 +129,27 @@ HidServer::~HidServer()
|
|||
{
|
||||
ClearColor();
|
||||
|
||||
onInterruptErrorConnection.disconnect();
|
||||
onInterruptConnection.disconnect();
|
||||
onBtHomeConnection.disconnect();
|
||||
onBtOtherConnection.disconnect();
|
||||
onShortPressConnection.disconnect();
|
||||
|
||||
delete intpin; intpin = NULL;
|
||||
delete mcp; mcp = NULL;
|
||||
delete btnTimer; btnTimer = NULL;
|
||||
onLongPressConnection.disconnect();
|
||||
onValidatePressConnection.disconnect();
|
||||
|
||||
delete this->btnTimer; this->btnTimer = NULL;
|
||||
delete this->btOther;
|
||||
delete this->btHome;
|
||||
delete this->rgbLed;
|
||||
|
||||
clog << kLogInfo << "Stopping normally" << endl;
|
||||
}
|
||||
|
||||
void HidServer::initHardware(void)
|
||||
{
|
||||
HWConfig hwConfig;
|
||||
unsigned short value;
|
||||
|
||||
clog << kLogInfo << "Opening GPIO pin " << GPIO_INT_PIN << " for interrupt listening" << endl;
|
||||
// open pin
|
||||
intpin = new GpioPin( GPIO_INT_PIN, // Pin number
|
||||
kDirectionIn, // Data direction
|
||||
kEdgeFalling); // Interrupt edge
|
||||
|
||||
// **** Initialize the MCP23017
|
||||
|
||||
// Hardware config
|
||||
hwConfig.DISSLEW = false; // Leave slew rate control enabled
|
||||
hwConfig.INT_MIRROR = true; // Interconnect I/O pins
|
||||
hwConfig.INT_ODR = false; // Interrupt is not an open drain
|
||||
hwConfig.INT_POL = false; // Interrupt is Active-Low
|
||||
|
||||
clog << kLogInfo << "Opening MCP23017 IO expander on I2C address " << MCP23017_ADR << endl;
|
||||
|
||||
try
|
||||
{
|
||||
// Initialize chip system
|
||||
mcp = new Mcp23017( MCP23017_ADR, // adr
|
||||
0x0fff, // iodir
|
||||
0x0fff, // ipol
|
||||
0x0fff, // pullup
|
||||
hwConfig, // Hardware Config
|
||||
true); // Swap A/B
|
||||
|
||||
try
|
||||
{
|
||||
clog << kLogInfo << "Enabling interrupts on IO Expander" << endl;
|
||||
// Configure interrupt
|
||||
mcp->IntConfig( 0x0000, // defval
|
||||
0x0000, // intcon
|
||||
0x07ff); // int enable
|
||||
|
||||
|
||||
clog << kLogInfo << "Starting interrupt listener on GPIO pin " << GPIO_INT_PIN << endl;
|
||||
|
||||
// Initialize interrupts
|
||||
|
||||
onInterruptErrorConnection.disconnect();
|
||||
onInterruptConnection.disconnect();
|
||||
|
||||
onInterruptErrorConnection = intpin->onThreadError.connect(boost::bind(&HidServer::onInterruptError, this, _1, _2));
|
||||
onInterruptConnection = intpin->onInterrupt.connect(boost::bind(&HidServer::onInterrupt, this, _1, _2, _3));
|
||||
|
||||
// Start interrupt event on interrupt pin
|
||||
intpin->InterruptStart();
|
||||
|
||||
clog << kLogInfo << "Performing initial read of IO Expander values to clear any pending interrupts and initialize Jack state" << endl;
|
||||
// Read initial value to clear any current interrupts
|
||||
value = mcp->getValue();
|
||||
|
||||
// Pass the initial mini-jack state to any listeners
|
||||
if(value && SENSE_MINIJACK)
|
||||
JackIn();
|
||||
else
|
||||
JackOut();
|
||||
|
||||
// Prepare the R/G/B pins for possible PWM usage
|
||||
mcp->setPwmState(PIN_RED,TRUE);
|
||||
mcp->setPwmState(PIN_GREEN,TRUE);
|
||||
mcp->setPwmState(PIN_BLUE,TRUE);
|
||||
|
||||
}
|
||||
catch(OperationFailedException x)
|
||||
{
|
||||
delete mcp; mcp = NULL;
|
||||
throw x;
|
||||
}
|
||||
|
||||
}
|
||||
catch(OperationFailedException x)
|
||||
{
|
||||
delete intpin; intpin = NULL;
|
||||
throw x;
|
||||
}
|
||||
}
|
||||
|
||||
void HidServer::SetColor(const uint8_t& r, const uint8_t& g, const uint8_t& b)
|
||||
{
|
||||
clog << kLogDebug << "Got color request: RGB("<< (int)r << "," << (int)g << "," << (int)b << ") - ";
|
||||
// Check if we can do solid colors (no need for PWM)
|
||||
if( (r == 0 || r==255) &&
|
||||
(g == 0 || g==255) &&
|
||||
(b == 0 || b==255))
|
||||
{
|
||||
// if so, then 0 is off and non-zero is on
|
||||
clog << "Using ON/OFF to produce color" << endl;
|
||||
// but first disable pwm
|
||||
mcp->PwmStop(); // waits for completion
|
||||
mcp->setPin(PIN_RED, (bool)r);
|
||||
mcp->setPin(PIN_GREEN, (bool)g);
|
||||
mcp->setPin(PIN_BLUE, (bool)b);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
clog << "Using PWM to produce color" << endl;
|
||||
// we need PWM, so enable it.
|
||||
mcp->PwmStart();
|
||||
|
||||
mcp->setPwmLedValue(PIN_RED, r);
|
||||
mcp->setPwmLedValue(PIN_GREEN, g);
|
||||
mcp->setPwmLedValue(PIN_BLUE, b);
|
||||
}
|
||||
|
||||
clog << kLogDebug << "Got color request: RGB("<< (int)r << "," << (int)g << "," << (int)b << ") - " << endl;
|
||||
this->ThreadStop();
|
||||
this->rgbLed->SetColor(r,g,b);
|
||||
}
|
||||
|
||||
void HidServer::PulseColor(const uint8_t& r, const uint8_t& g, const uint8_t& b, const double& interval)
|
||||
|
@ -228,33 +164,37 @@ void HidServer::PulseColor(const uint8_t& r, const uint8_t& g, const uint8_t& b,
|
|||
|
||||
clog << kLogDebug << "Got pulse color request: RGB("<< (int)r << "," << (int)g << "," << (int)b << ")" << endl;
|
||||
|
||||
pulseTime = (int32_t)((interval * 1000000) / (2 * PULSE_STEPS));
|
||||
// Default PULSE_STEPS is 32, reduce accordingly when interval is below 2 seconds
|
||||
// with 32 pulse steps, this results in min 2.0 / (2*32) = 31 ms per color
|
||||
pulseSteps = PULSE_STEPS;
|
||||
if(interval > 2.0)
|
||||
{
|
||||
pulseSteps = (int32_t)(interval * pulseSteps);
|
||||
}
|
||||
|
||||
pulseTime = (int32_t)((interval * 1000000) / (2 * pulseSteps));
|
||||
// Start pulse thread and PWM system
|
||||
ThreadStart();
|
||||
mcp->PwmStart();
|
||||
|
||||
}
|
||||
|
||||
// contains the pulse loop
|
||||
// contains the pulse loop (note: faster pulsing does use more cpu)
|
||||
void HidServer::ThreadLoop()
|
||||
{
|
||||
uint8_t myR, myG, myB;
|
||||
int32_t factor = pulseI*100;
|
||||
|
||||
|
||||
myR = (pulseR * factor)/(100 * PULSE_STEPS);
|
||||
myG = (pulseG * factor)/(100 * PULSE_STEPS);
|
||||
myB = (pulseB * factor)/(100 * PULSE_STEPS);
|
||||
|
||||
mcp->setPwmLedValue(PIN_RED, myR);
|
||||
mcp->setPwmLedValue(PIN_GREEN, myG);
|
||||
mcp->setPwmLedValue(PIN_BLUE, myB);
|
||||
myR = (pulseR * factor)/(100 * pulseSteps);
|
||||
myG = (pulseG * factor)/(100 * pulseSteps);
|
||||
myB = (pulseB * factor)/(100 * pulseSteps);
|
||||
|
||||
this->rgbLed->SetColor(myR,myG,myB);
|
||||
|
||||
if(pulseDirUp)
|
||||
{
|
||||
pulseI++;
|
||||
if(pulseI>= PULSE_STEPS)
|
||||
if(pulseI>= pulseSteps)
|
||||
pulseDirUp = false;
|
||||
}
|
||||
else
|
||||
|
@ -270,216 +210,50 @@ void HidServer::ThreadLoop()
|
|||
|
||||
void HidServer::ClearColor()
|
||||
{
|
||||
clog << kLogDebug << "Got request to clear colors" << endl;
|
||||
|
||||
clog << kLogDebug << "Clearing color" << endl;
|
||||
// Stop pulse thread
|
||||
ThreadStop();
|
||||
|
||||
// Disable PWM and turn all colors off
|
||||
mcp->PwmStop(); // waits for completion
|
||||
mcp->setPin(PIN_RED, false);
|
||||
mcp->setPin(PIN_GREEN, false);
|
||||
mcp->setPin(PIN_BLUE, false);
|
||||
this->rgbLed->SetColor(0,0,0);
|
||||
}
|
||||
|
||||
bool HidServer::JackState()
|
||||
void HidServer::onBtChange(uint8_t gpio, GpioEdge edge, bool level)
|
||||
{
|
||||
uint16_t value;
|
||||
|
||||
try
|
||||
{
|
||||
value = mcp->getValue();
|
||||
if(edge == GpioEdge::Rising){
|
||||
this->btnTimer->RegisterPress((uint8_t)gpio);
|
||||
// clog << kLogDebug << " (Button down : " << getBtnName((uint8_t)gpio) << ")" << endl;
|
||||
}
|
||||
catch(OperationFailedException x)
|
||||
{
|
||||
// Log the exception
|
||||
clog << kLogError << "Error while retrieving Jack State: " << x.what() << endl;
|
||||
|
||||
// Return false on error
|
||||
return false;
|
||||
}
|
||||
// Pass the current mini-jack state to any listeners
|
||||
if(value && SENSE_MINIJACK)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
{
|
||||
this->btnTimer->RegisterRelease((uint8_t)gpio);
|
||||
// clog << kLogDebug << " (Button up : " << getBtnName((uint8_t)gpio) << ")" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
bool HidServer::onValidatePress(uint16_t keycode)
|
||||
|
||||
bool HidServer::onValidatePress(uint8_t gpio)
|
||||
{
|
||||
// Check if the key is still pressed before sending out a long press
|
||||
|
||||
if(mcp->getValue() & keycode)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
return true; //((bool)gpioRead(gpio)) == BUTTONS_ACTIVE.at(gpio);
|
||||
}
|
||||
|
||||
void HidServer::onShortPress(uint16_t keycode)
|
||||
void HidServer::onShortPress(uint8_t gpio)
|
||||
{
|
||||
clog << kLogDebug << "SHORT PRESS : " << getBtnName(keycode) << endl;
|
||||
ButtonPress(getBtnName(keycode));
|
||||
clog << kLogDebug << "SHORT PRESS : " << getBtnName(gpio) << endl;
|
||||
ButtonPress(getBtnName(gpio));
|
||||
}
|
||||
|
||||
void HidServer::onLongPress(uint16_t keycode)
|
||||
void HidServer::onLongPress(uint8_t gpio)
|
||||
{
|
||||
clog << kLogDebug << "LONG PRESS : " << getBtnName(keycode) << endl;
|
||||
ButtonLongPress(getBtnName(keycode));
|
||||
clog << kLogDebug << "LONG PRESS : " << getBtnName(gpio) << endl;
|
||||
ButtonLongPress(getBtnName(gpio));
|
||||
}
|
||||
|
||||
void HidServer::keyUp(uint16_t keycode)
|
||||
// Convert gpio into a string for ease of use in e.g. python or other dbus bindings
|
||||
std::string HidServer::getBtnName(uint8_t gpio)
|
||||
{
|
||||
if(keycode == SENSE_MINIJACK)
|
||||
{
|
||||
JackOut();
|
||||
}
|
||||
else
|
||||
{
|
||||
ButtonUp(getBtnName(keycode));
|
||||
btnTimer->RegisterRelease(keycode);
|
||||
}
|
||||
}
|
||||
|
||||
void HidServer::keyDown(uint16_t keycode)
|
||||
{
|
||||
if(keycode == SENSE_MINIJACK)
|
||||
{
|
||||
JackIn();
|
||||
}
|
||||
else
|
||||
{
|
||||
ButtonDown(getBtnName(keycode));
|
||||
btnTimer->RegisterPress(keycode);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert binary keycode into a string for ease of use in e.g. python or other dbus bindings
|
||||
std::string HidServer::getBtnName(uint16_t keycode)
|
||||
{
|
||||
switch(keycode)
|
||||
{
|
||||
case BTN_ESCAPE :
|
||||
return "BTN_ESCAPE";
|
||||
case BTN_FAVORITES :
|
||||
return "BTN_FAVORITES";
|
||||
case BTN_RECENT :
|
||||
return "BTN_RECENT";
|
||||
|
||||
case BTN_REWIND :
|
||||
return "BTN_REWIND";
|
||||
case BTN_PLAYPAUSE :
|
||||
return "BTN_PLAYPAUSE";
|
||||
case BTN_FASTFORWARD :
|
||||
return "BTN_FASTFORWARD";
|
||||
|
||||
case BTN_CANCEL :
|
||||
return "BTN_CANCEL";
|
||||
case BTN_OK :
|
||||
return "BTN_OK";
|
||||
case BTN_UP :
|
||||
return "BTN_UP";
|
||||
case BTN_DOWN :
|
||||
return "BTN_DOWN";
|
||||
case SENSE_MINIJACK :
|
||||
return "SENSE_MINIJACK";
|
||||
default:
|
||||
return "BTN_NONE";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void HidServer::onInterrupt(GpioPin * sender, GpioEdge edge, bool pinval)
|
||||
{
|
||||
uint16_t intf,intcap, keycode;
|
||||
uint8_t i, bitcount;
|
||||
|
||||
if(edge != kEdgeFalling) // Only continue on falling edge
|
||||
return;
|
||||
|
||||
|
||||
intf = mcp->getIntF();
|
||||
intcap = mcp->getIntCap();
|
||||
|
||||
// Check if we are in a noise timeout, and cancel if so.
|
||||
if(noiseTimeout !=0 && noiseTimeout > now_ms())
|
||||
return;
|
||||
else
|
||||
noiseTimeout = 0;
|
||||
|
||||
|
||||
// Check if only one bit is set in the interrupt cap: This indicated normal operation.
|
||||
// If more than one bit is set, we should assume noise
|
||||
bitcount = 0;
|
||||
for(i=0;i<16;i++)
|
||||
{
|
||||
if(intf & (1 << i)) // Check if this key is set
|
||||
bitcount++;
|
||||
}
|
||||
|
||||
//printf("Interrupt!\n INTF : 0x%04x\n INTCAP : 0x%04x\n KEYCODE: 0x%04x\n\n", intf, intcap, keycode);
|
||||
|
||||
if(bitcount > 0 && bitcount <= 3 ) // anything above 3 key interrupts at the same time is considered noise
|
||||
{
|
||||
|
||||
for(i=0; i < 16; i++)
|
||||
{
|
||||
if(intf & (1 << i)) // check if this keycode is set
|
||||
{
|
||||
keycode = (1 << i);
|
||||
if(intcap & keycode)
|
||||
{
|
||||
keyDown(keycode);
|
||||
if(keycode == SENSE_MINIJACK)
|
||||
clog << kLogDebug << "Jacked IN " << endl;
|
||||
else
|
||||
clog << kLogDebug << "Button down : " << getBtnName(keycode) << endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
keyUp(keycode);
|
||||
if(keycode == SENSE_MINIJACK)
|
||||
clog << kLogDebug << "Jacked OUT " << endl;
|
||||
else
|
||||
clog << kLogDebug << "Button up : " << getBtnName(keycode) << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clog << kLogDebug << "!!! Input Noise !!! (Timeout: " << NOISE_TIMEOUT_MS << "ms)" << endl;
|
||||
noiseTimeout = now_ms() + NOISE_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void HidServer::onInterruptError(Thread* sender, ThreadException x)
|
||||
{
|
||||
// When this function is called, the interrupt thread will have stopped
|
||||
clog << kLogErr << "Error in interrupt listener: " << x.what() << endl;
|
||||
clog << kLogErr << "Attempting to restart interrupt listener... " << endl;
|
||||
try
|
||||
{
|
||||
delete mcp; mcp = NULL;
|
||||
delete intpin; intpin = NULL;
|
||||
initHardware(); // Attempt to re-init the chip system. Quit on failure
|
||||
clog << kLogInfo << "Succesfully restarted interrupt listener" << endl;
|
||||
}
|
||||
catch(std::exception x2)
|
||||
{
|
||||
clog << kLogCrit << "Fatal error while attempting to restart interrupt listener : " << x2.what() << endl << "Quitting now... " << endl;
|
||||
niam(0);
|
||||
}
|
||||
}
|
||||
|
||||
// get current time in milliseconds
|
||||
int64_t now_ms(void)
|
||||
{
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTIC, &now);
|
||||
|
||||
return ((int64_t)now.tv_sec)*1000LL + (now.tv_nsec/1000000);
|
||||
return BUTTONS.at(gpio);
|
||||
}
|
||||
|
||||
|
||||
|
@ -492,21 +266,26 @@ void niam(int sig)
|
|||
|
||||
int main()
|
||||
{
|
||||
signal(SIGTERM, niam);
|
||||
signal(SIGINT, niam);
|
||||
signal(SIGTERM, niam);
|
||||
signal(SIGINT, niam);
|
||||
signal(2,niam);
|
||||
|
||||
DBus::default_dispatcher = &dispatcher;
|
||||
DBus::default_dispatcher = &dispatcher;
|
||||
|
||||
// Initialize clog to be redirected to syslog key "mediacore.hid.server"
|
||||
Log::Init("mediacore.hid");
|
||||
// Initialize clog to be redirected to syslog key "mediacore.hid.server"
|
||||
// Log::Init("mediacore.hid");
|
||||
|
||||
DBus::Connection conn = DBus::Connection::SystemBus();
|
||||
conn.request_name(HID_SERVER_NAME);
|
||||
DBus::Connection conn = DBus::Connection::SystemBus();
|
||||
conn.request_name(HID_SERVER_NAME);
|
||||
|
||||
HidServer server(conn);
|
||||
//gpioCfgClock(10,1,0);
|
||||
//gpioInitialise();
|
||||
|
||||
HidServer server(conn);
|
||||
|
||||
|
||||
dispatcher.enter();
|
||||
dispatcher.enter();
|
||||
|
||||
return 0;
|
||||
//gpioTerminate();
|
||||
return 0;
|
||||
}
|
|
@ -2,15 +2,14 @@
|
|||
#define __MC_HID_SERVER_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
#include <dbus-c++/dbus.h>
|
||||
//#include <dbus-c++-1/dbus-c++/dbus.h> // Done thhrough <mc-hid-server-glue.hpp>
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
#include "mc-hid-server-glue.hpp"
|
||||
|
||||
#include "gpio/gpio.hpp"
|
||||
#include "mcp23017/mcp23017.hpp"
|
||||
#include "thread/thread.hpp"
|
||||
#include "buttontimer/buttontimer.hpp"
|
||||
#include "gpio/gpio.hpp"
|
||||
|
||||
class HidServer
|
||||
: public nl::miqra::MediaCore::Hid_adaptor, // << This will be generated by the makefile using dbusxx-xml2cpp on mc-hid-introspect.xml
|
||||
|
@ -26,36 +25,36 @@ public:
|
|||
virtual void SetColor(const uint8_t& r, const uint8_t& g, const uint8_t& b);
|
||||
virtual void PulseColor(const uint8_t& r, const uint8_t& g, const uint8_t& b, const double& interval);
|
||||
virtual void ClearColor();
|
||||
virtual bool JackState();
|
||||
|
||||
void onInterrupt(GpioPin * sender, GpioEdge edge, bool pinval);
|
||||
void onInterruptError(Thread * sender, ThreadException x);
|
||||
|
||||
bool onValidatePress(uint16_t keycode);
|
||||
void onShortPress(uint16_t keycode);
|
||||
void onLongPress(uint16_t keycode);
|
||||
bool onValidatePress(uint8_t gpio);
|
||||
void onShortPress(uint8_t gpio);
|
||||
void onLongPress(uint8_t gpio);
|
||||
void onBtChange(uint8_t gpio, GpioEdge edge, bool level);
|
||||
|
||||
protected:
|
||||
virtual void ThreadLoop();
|
||||
//void onChange(int gpio, int level, uint32_t tick);
|
||||
|
||||
private:
|
||||
Mcp23017 *mcp;
|
||||
GpioPin *intpin;
|
||||
ButtonTimer *btnTimer;
|
||||
RgbLed *rgbLed;
|
||||
GpioPin *btHome;
|
||||
GpioPin *btCancel;
|
||||
GpioPins *btOther;
|
||||
|
||||
int64_t noiseTimeout;
|
||||
|
||||
uint8_t pulseR, pulseG, pulseB,pulseI,pulseMin;
|
||||
uint8_t pulseR, pulseG, pulseB, pulseI,pulseMin;
|
||||
int32_t pulseTime;
|
||||
int32_t pulseSteps;
|
||||
bool pulseDirUp;
|
||||
|
||||
void initHardware(void);
|
||||
|
||||
void keyUp(uint16_t keycode);
|
||||
void keyDown(uint16_t keycode);
|
||||
std::string getBtnName(uint16_t keycode);
|
||||
std::string getBtnName(uint8_t gpio);
|
||||
|
||||
boost::signals2::connection onInterruptConnection;
|
||||
//static void onChangeEx(int gpio, int level, uint32_t tick, void *user);
|
||||
|
||||
boost::signals2::connection onBtHomeConnection;
|
||||
boost::signals2::connection onBtOtherConnection;
|
||||
boost::signals2::connection onInterruptErrorConnection;
|
||||
boost::signals2::connection onShortPressConnection;
|
||||
boost::signals2::connection onLongPressConnection;
|
||||
|
|
|
@ -1,968 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <malloc.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
|
||||
#include "mcp23017.hpp"
|
||||
#include "../i2c/i2c.h"
|
||||
|
||||
/****************************
|
||||
* *
|
||||
* DEFINITIONS *
|
||||
* *
|
||||
*****************************/
|
||||
|
||||
//! True/False definitions
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
|
||||
/************************************
|
||||
* *
|
||||
* INTERNAL FUNCTION DEFINITIONS *
|
||||
* *
|
||||
*************************************/
|
||||
|
||||
|
||||
|
||||
/************************************
|
||||
* *
|
||||
* REGISTER ADDRESS DEFINITIONS *
|
||||
* *
|
||||
*************************************/
|
||||
|
||||
// This interface used only BANK=0 addresses, which are defined below
|
||||
// The Init function ensures that the device is started in BANK=0 mode
|
||||
|
||||
#define IODIR 0x00
|
||||
#define IODIRA 0x00
|
||||
#define IODIRB 0x01
|
||||
|
||||
#define IPOL 0x02
|
||||
#define IPOLA 0x02
|
||||
#define IPOLB 0x03
|
||||
|
||||
#define GPINTEN 0x04
|
||||
#define GPINTENA 0x04
|
||||
#define GPINTENB 0x05
|
||||
|
||||
#define DEFVAL 0x06
|
||||
#define DEFVALA 0x06
|
||||
#define DEFVALB 0x07
|
||||
|
||||
#define INTCON 0x08
|
||||
#define INTCONA 0x08
|
||||
#define INTCONB 0x09
|
||||
|
||||
#define IOCON 0x0A
|
||||
#define IOCONA 0x0A
|
||||
#define IOCONB 0x0B
|
||||
|
||||
#define GPPU 0x0C
|
||||
#define GPPUA 0x0C
|
||||
#define GPPUB 0x0D
|
||||
|
||||
#define INTF 0x0E
|
||||
#define INTFA 0x0E
|
||||
#define INTFB 0x0F
|
||||
|
||||
#define INTCAP 0x10
|
||||
#define INTCAPA 0x10
|
||||
#define INTCAPB 0x11
|
||||
|
||||
#define GPIO 0x12
|
||||
#define GPIOA 0x12
|
||||
#define GPIOB 0x13
|
||||
|
||||
#define OLAT 0x14
|
||||
#define OLATA 0x14
|
||||
#define OLATB 0x15
|
||||
|
||||
/************************************
|
||||
* *
|
||||
* PSEUDO-GLOBAL VARIABLES *
|
||||
* *
|
||||
*************************************/
|
||||
|
||||
// Reverse address lookup tables for registers.
|
||||
// Used mainly in error reporting
|
||||
|
||||
// 8 Bit names
|
||||
const char * Registers8[22] =
|
||||
{
|
||||
"IODIRA", // 00
|
||||
"IODIRB", // 01
|
||||
"IPOLA", // 02
|
||||
"IPOLB", // 03
|
||||
"GPINTENA", // 04
|
||||
"GPINTENB", // 05
|
||||
"DEFVALA", // 06
|
||||
"DEFVALB", // 07
|
||||
"INTCONA", // 08
|
||||
"INTCONB", // 09
|
||||
"IOCONA", // 0A
|
||||
"IOCONB", // 0B
|
||||
"GPPUA", // 0C
|
||||
"GPPUB", // 0D
|
||||
"INTFA", // 0E
|
||||
"INTFB", // 0F
|
||||
"INTCAPA", // 10
|
||||
"INTCAPB", // 11
|
||||
"GPIOA", // 12
|
||||
"GPIOB", // 13
|
||||
"OLATA", // 14
|
||||
"OLATB" // 15
|
||||
};
|
||||
|
||||
// 16 bit names
|
||||
const char * Registers16[22] =
|
||||
{
|
||||
"IODIR", // 00
|
||||
"IODIR", // 01
|
||||
"IPOL", // 02
|
||||
"IPOL", // 03
|
||||
"GPINTEN", // 04
|
||||
"GPINTEN", // 05
|
||||
"DEFVAL", // 06
|
||||
"DEFVAL", // 07
|
||||
"INTCON", // 08
|
||||
"INTCON", // 09
|
||||
"IOCON", // 0A
|
||||
"IOCON", // 0B
|
||||
"GPPU", // 0C
|
||||
"GPPU", // 0D
|
||||
"INTF", // 0E
|
||||
"INTF", // 0F
|
||||
"INTCAP", // 10
|
||||
"INTCAP", // 11
|
||||
"GPIO", // 12
|
||||
"GPIO", // 13
|
||||
"OLAT", // 14
|
||||
"OLAT" // 15
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/************************************
|
||||
* *
|
||||
* STRUCT RELATED FUNCTIONS *
|
||||
* *
|
||||
*************************************/
|
||||
|
||||
HWConfig::HWConfig()
|
||||
{
|
||||
DISSLEW = false; // Leave slew rate control enabled
|
||||
INT_MIRROR = true; // Mirror Interrupt pins
|
||||
INT_ODR = false; // Interrupt is not an open drain
|
||||
INT_POL = false; // Interrupt is Active-Low
|
||||
}
|
||||
|
||||
uint8_t HWConfig::parse()
|
||||
{
|
||||
uint8_t val = 0;
|
||||
if(DISSLEW)
|
||||
val |= 0x10;
|
||||
if(INT_MIRROR)
|
||||
val |= 0x40;
|
||||
if(INT_ODR)
|
||||
val |= 0x04;
|
||||
if(INT_POL)
|
||||
val |= 0x02;
|
||||
|
||||
val |= 0x20; // Disable the address pointer
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//! Internal helper function that translates an IOConfig structure to an unsigned int denoting the proper configuration for an MCP23017
|
||||
|
||||
/************************************
|
||||
* *
|
||||
* CONNECTION SETUP *
|
||||
* *
|
||||
*************************************/
|
||||
|
||||
//! Open a new connection to the MCP23017 device, and initialize it.
|
||||
Mcp23017::Mcp23017( uint8_t adr,
|
||||
uint16_t iodir,
|
||||
uint16_t ipol,
|
||||
uint16_t pullup,
|
||||
HWConfig hwcfg,
|
||||
bool swapAB)
|
||||
{
|
||||
int i;
|
||||
// Try opening the port for the specific address
|
||||
fp = i2cInit(adr);
|
||||
if(fp <= 0) // error
|
||||
{
|
||||
if(fp == 0)
|
||||
throw OperationFailedException("Got NULL pointer to I2C File");
|
||||
else if (fp == -1)
|
||||
throw OperationFailedException("Could not open I2C device for reading and writing");
|
||||
else if (fp == -2)
|
||||
throw OperationFailedException("Could not set I2C destination address");
|
||||
else
|
||||
throw OperationFailedException("Got unspecified error [%d] opening I2C device",fp);
|
||||
}
|
||||
|
||||
// Initialize objcect variables
|
||||
this->adr = adr; // set address
|
||||
this->swapAB = swapAB; // set swapAB value;
|
||||
|
||||
this->pwm_enabled = false;
|
||||
this->pwm_mask = 0x0000;
|
||||
this->pwm_prev_val = 0x0000;
|
||||
|
||||
// Initialize the PWM values to null
|
||||
for(i=0; i< 16; i++)
|
||||
{
|
||||
this->pwm_values[i] = 0;
|
||||
this->pwm_v_values[i] = 0;
|
||||
}
|
||||
|
||||
// Initialize to default pwm speed
|
||||
this->pwm_tick_delay_us = 800; // 800us * 16 steps would result in 78Hz
|
||||
this->pwm_ticks = 16;
|
||||
|
||||
// Copy HWConfig to key
|
||||
this->hwConfig = hwcfg;
|
||||
|
||||
// Use the following trick to assure we're talking to the MCP23017 in BANK=0 mode, so we can properly set the IOCON value
|
||||
|
||||
try
|
||||
{
|
||||
// If we're in BANK1 mode, address 0x15 corresponds to the GPINTENB register, which should be inited to 0 anyway;
|
||||
tryI2CWrite8(0x05, 0x00);
|
||||
// Now write the proper IOCON value
|
||||
tryI2CWrite8(IOCON, hwcfg.parse());
|
||||
// Finally, initialize both GPINTENA and GPINTENB to 0x00
|
||||
tryI2CWrite8(GPINTENA, 0x00);
|
||||
tryI2CWrite8(GPINTENB, 0x00);
|
||||
|
||||
// Further initialization
|
||||
|
||||
// Set up the IO direction
|
||||
tryI2CWrite16(IODIR, iodir);
|
||||
// Set up the input polarity
|
||||
tryI2CWrite16(IPOL, ipol);
|
||||
// Set up the pullups
|
||||
tryI2CWrite16(GPPU, pullup);
|
||||
}
|
||||
catch(OperationFailedException x)
|
||||
{
|
||||
i2cClose(fp);
|
||||
throw x;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//! Destructor
|
||||
Mcp23017::~Mcp23017()
|
||||
{
|
||||
PwmStop(); // try to stop the PWM driver;
|
||||
i2cClose(fp);
|
||||
}
|
||||
|
||||
|
||||
/************************************
|
||||
* *
|
||||
* INTERRUPT SETTINGS *
|
||||
* *
|
||||
************************************/
|
||||
|
||||
//! Set interrupt config for the
|
||||
void Mcp23017::IntConfig( uint16_t intcon, uint16_t defval, uint16_t int_enable)
|
||||
{
|
||||
tryI2CWrite16(INTCON,intcon);
|
||||
tryI2CWrite16(DEFVAL,defval);
|
||||
tryI2CWrite16(GPINTEN,int_enable);
|
||||
}
|
||||
|
||||
//! Get Interrupt flags
|
||||
uint16_t Mcp23017::getIntF()
|
||||
{
|
||||
return tryI2CRead16(INTF);
|
||||
}
|
||||
|
||||
//! Get state of input pins on latest interrupt
|
||||
uint16_t Mcp23017::getIntCap()
|
||||
{
|
||||
return tryI2CRead16(INTCAP);
|
||||
}
|
||||
|
||||
//! Set Default value for pins
|
||||
void Mcp23017::setDefault( uint16_t value)
|
||||
{
|
||||
tryI2CWrite16(DEFVAL, value);
|
||||
}
|
||||
|
||||
//! Get Default value for pins
|
||||
uint16_t Mcp23017::getDefault()
|
||||
{
|
||||
return tryI2CRead16(DEFVAL);
|
||||
}
|
||||
|
||||
//! Set Interrupt enable
|
||||
void Mcp23017::setIntEnable( uint16_t value)
|
||||
{
|
||||
tryI2CWrite16(GPINTEN, value);
|
||||
}
|
||||
|
||||
//! Get Interrupt enable
|
||||
uint16_t Mcp23017::getIntEnable()
|
||||
{
|
||||
return tryI2CRead16(GPINTEN);
|
||||
}
|
||||
|
||||
//! Set Interrupt control value
|
||||
void Mcp23017::setIntControl(uint16_t value)
|
||||
{
|
||||
tryI2CWrite16(INTCON, value);
|
||||
}
|
||||
|
||||
//! Get Interrupt control value
|
||||
uint16_t Mcp23017::getIntControl()
|
||||
{
|
||||
return tryI2CRead16(INTCON);
|
||||
}
|
||||
|
||||
/************************************
|
||||
* *
|
||||
* REGULAR I/O FUNCTIONS *
|
||||
* *
|
||||
************************************/
|
||||
|
||||
//! Set input polarity
|
||||
void Mcp23017::setIPol(uint16_t value)
|
||||
{
|
||||
tryI2CWrite16(IPOL, value);
|
||||
}
|
||||
|
||||
//! Get input polarity
|
||||
uint16_t Mcp23017::getIPol()
|
||||
{
|
||||
return tryI2CRead16(IPOL);
|
||||
}
|
||||
|
||||
//! Get output latch value
|
||||
uint16_t Mcp23017::getOLat()
|
||||
{
|
||||
return tryI2CRead16(OLAT);
|
||||
}
|
||||
|
||||
//! Set IO Direction
|
||||
void Mcp23017::setDirection(uint16_t value)
|
||||
{
|
||||
tryI2CWrite16(IODIR, value);
|
||||
}
|
||||
|
||||
//! Get IO Direction
|
||||
uint16_t Mcp23017::getDirection()
|
||||
{
|
||||
return tryI2CRead16(IODIR);
|
||||
}
|
||||
|
||||
//! Set new output value of the I/O pins
|
||||
void Mcp23017::setValue(uint16_t value)
|
||||
{
|
||||
// if pwm is enabled, use a masked write with the inverse of the pwm_mask
|
||||
// to avoid overwriting pwm values
|
||||
if(this->pwm_enabled)
|
||||
tryI2CMaskedWrite16(GPIO,value,~this->pwm_mask);
|
||||
else
|
||||
tryI2CWrite16(GPIO, value);
|
||||
}
|
||||
|
||||
//! Get current input value of the I/O pins
|
||||
uint16_t Mcp23017::getValue()
|
||||
{
|
||||
return tryI2CRead16(GPIO);
|
||||
}
|
||||
|
||||
//! Set new output value for a subset of the I/O pins (masked by 'mask', where high bits indicat bits to set in the output)
|
||||
void Mcp23017::setMaskedValue(uint16_t value, uint16_t mask)
|
||||
{
|
||||
// if pwm is enabled, join the pwm_mask with this mask
|
||||
// to avoid overwriting pwm values
|
||||
if(this->pwm_enabled)
|
||||
mask &= ~this->pwm_mask;
|
||||
|
||||
tryI2CMaskedWrite16(GPIO,value,mask);
|
||||
}
|
||||
|
||||
//! Get the values of a specific pin
|
||||
bool Mcp23017::getPin(uint8_t pin)
|
||||
{
|
||||
uint16_t value;
|
||||
|
||||
if(pin > 15)
|
||||
throw InvalidArgumentException("Pin number exceeds maximum pin id. Ignoring request to set pin value.");
|
||||
|
||||
value = tryI2CRead16(GPIO);
|
||||
|
||||
if(value & (1 << pin))
|
||||
return TRUE;
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
//! Set the value of a specific pin
|
||||
void Mcp23017::setPin(uint8_t pin, bool value)
|
||||
{
|
||||
uint16_t newval, mask;
|
||||
|
||||
if(pin > 15)
|
||||
throw InvalidArgumentException("Pin number exceeds maximum pin id. Ignoring request to set pin value.");
|
||||
|
||||
// set mask
|
||||
mask = ( 1 << pin );
|
||||
|
||||
// if pwm is enabled, join the pwm_mask with this mask
|
||||
// to avoid overwriting pwm values
|
||||
if(this->pwm_enabled)
|
||||
mask &= ~this->pwm_mask;
|
||||
|
||||
if(mask == 0x00)
|
||||
throw InvalidArgumentException("Pin is active in PWM. Ignoring request to set pin value.");
|
||||
|
||||
if(value)
|
||||
{
|
||||
newval = ( 1 << pin );
|
||||
}
|
||||
else
|
||||
{
|
||||
newval = 0;
|
||||
}
|
||||
|
||||
tryI2CMaskedWrite16(GPIO, newval, mask);
|
||||
}
|
||||
|
||||
/************************************
|
||||
* *
|
||||
* PWM FUNCTIONS *
|
||||
* *
|
||||
************************************/
|
||||
|
||||
//! Start the PWM routine for this I/O expander
|
||||
void Mcp23017::PwmStart()
|
||||
{
|
||||
|
||||
if(!ThreadRunning())
|
||||
{
|
||||
ThreadStart();
|
||||
}
|
||||
}
|
||||
|
||||
//! Stop the PWM routine for this I/O expander
|
||||
void Mcp23017::PwmStop()
|
||||
{
|
||||
if(ThreadRunning())
|
||||
{
|
||||
ThreadStop();
|
||||
// Set the PWM pins back to low
|
||||
tryI2CMaskedWrite16(OLAT, 0x0000, this->pwm_mask);
|
||||
}
|
||||
}
|
||||
|
||||
//! Get the PWM value for a specific pin as 0-255 value (can be different from previously set value, due to rounding errors)
|
||||
uint8_t Mcp23017::getPwmValue(uint8_t pin)
|
||||
{
|
||||
|
||||
if(pin > 15)
|
||||
throw InvalidArgumentException("Pin number exceeds maximum pin id. Ignoring request.");
|
||||
|
||||
return this->pwm_v_values[pin]; // Return the cached 0-255 value of the pin
|
||||
}
|
||||
|
||||
//! Set the PWM value for a specific pin as 0-255 value
|
||||
void Mcp23017::setPwmValue(uint8_t pin, uint8_t value)
|
||||
{
|
||||
|
||||
if(pin > 15)
|
||||
throw InvalidArgumentException("Pin number exceeds maximum pin id. Ignoring request.");
|
||||
|
||||
this->pwm_v_values[pin] = value; // cache the provided value for returning and for updating pwm_value on change in pwm_ticks);
|
||||
this->pwm_values[pin] = value / (256/this->pwm_ticks);
|
||||
}
|
||||
|
||||
//! Set the PWM value for a specific pin as 0-255 value and apply gamma correction for LED light levels
|
||||
void Mcp23017::setPwmLedValue(uint8_t pin, uint8_t lightvalue)
|
||||
{
|
||||
// Gamma correction table
|
||||
const uint8_t GammaToLinear[256] =
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 2, 2, 2, 2, 2, 2, 2,
|
||||
3, 3, 3, 3, 3, 4, 4, 4,
|
||||
4, 5, 5, 5, 5, 6, 6, 6,
|
||||
6, 7, 7, 7, 8, 8, 8, 9,
|
||||
9, 9, 10, 10, 11, 11, 11, 12,
|
||||
12, 13, 13, 14, 14, 14, 15, 15,
|
||||
16, 16, 17, 17, 18, 18, 19, 19,
|
||||
20, 21, 21, 22, 22, 23, 23, 24,
|
||||
25, 25, 26, 27, 27, 28, 28, 29,
|
||||
30, 31, 31, 32, 33, 33, 34, 35,
|
||||
36, 36, 37, 38, 39, 39, 40, 41,
|
||||
42, 43, 44, 44, 45, 46, 47, 48,
|
||||
49, 50, 51, 51, 52, 53, 54, 55,
|
||||
56, 57, 58, 59, 60, 61, 62, 63,
|
||||
64, 65, 66, 67, 68, 70, 71, 72,
|
||||
73, 74, 75, 76, 77, 78, 80, 81,
|
||||
82, 83, 84, 86, 87, 88, 89, 91,
|
||||
92, 93, 94, 96, 97, 98, 100, 101,
|
||||
102, 104, 105, 106, 108, 109, 110, 112,
|
||||
113, 115, 116, 118, 119, 120, 122, 123,
|
||||
125, 126, 128, 129, 131, 132, 134, 136,
|
||||
137, 139, 140, 142, 143, 145, 147, 148,
|
||||
150, 152, 153, 155, 157, 158, 160, 162,
|
||||
164, 165, 167, 169, 171, 172, 174, 176,
|
||||
178, 179, 181, 183, 185, 187, 189, 191,
|
||||
192, 194, 196, 198, 200, 202, 204, 206,
|
||||
208, 210, 212, 214, 216, 218, 220, 222,
|
||||
224, 226, 228, 230, 232, 234, 237, 239,
|
||||
241, 243, 245, 247, 249, 252, 254, 255
|
||||
};
|
||||
|
||||
if(pin > 15)
|
||||
throw InvalidArgumentException("Pin number exceeds maximum pin id. Ignoring request.");
|
||||
|
||||
this->pwm_v_values[pin] = GammaToLinear[lightvalue]; // cache the (gamma-corrected) provided value for returning and for updating pwm_value on change in pwm_ticks);
|
||||
this->pwm_values[pin] = GammaToLinear[lightvalue] / (256/this->pwm_ticks);
|
||||
}
|
||||
|
||||
|
||||
//! Get the PWM state (on/off) for a specific pin
|
||||
bool Mcp23017::getPwmState(uint8_t pin)
|
||||
{
|
||||
if(pin > 15)
|
||||
throw InvalidArgumentException("Pin number exceeds maximum pin id. Ignoring request.");
|
||||
|
||||
return ( (this->pwm_mask & (1 << pin)) > 0);
|
||||
}
|
||||
|
||||
//! Set the PWM state (on/off) for a specific pin
|
||||
void Mcp23017::setPwmState(uint8_t pin, bool state)
|
||||
{
|
||||
if(pin > 15)
|
||||
throw InvalidArgumentException("Pin number exceeds maximum pin id. Ignoring request.");
|
||||
|
||||
if(state)
|
||||
this->pwm_mask |= (1 << pin);
|
||||
else
|
||||
this->pwm_mask &= ~(1 << pin);
|
||||
}
|
||||
|
||||
//! Set the PWM Configuration
|
||||
void Mcp23017::setPwmConfig(uint32_t tick_delay_us, uint8_t ticks)
|
||||
{
|
||||
int i;
|
||||
|
||||
this->pwm_tick_delay_us = tick_delay_us;
|
||||
this->pwm_ticks = ticks;
|
||||
|
||||
// update the actual PWM values, according to the
|
||||
for(i=0;i<16;i++)
|
||||
{
|
||||
if(this->pwm_mask & (1 << i)) // Check if pwm_mask is enabled for this pin
|
||||
{
|
||||
// if so, update the actually used pwm value, to reflect the new setting of ticks
|
||||
this->pwm_values[i] = this->pwm_v_values[i] / (256/this->pwm_ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! Get PWM Configuration
|
||||
PwmConfig Mcp23017::getPwmConfig()
|
||||
{
|
||||
|
||||
PwmConfig pcfg;
|
||||
pcfg.tick_delay_us = pwm_tick_delay_us;
|
||||
pcfg.ticks = pwm_ticks;
|
||||
return pcfg;
|
||||
}
|
||||
|
||||
//! Driver function for PWM thread
|
||||
void Mcp23017::ThreadFunc(void)
|
||||
{
|
||||
uint8_t ctr = 0;
|
||||
int i;
|
||||
uint16_t pwm_out = 0x00;
|
||||
//
|
||||
|
||||
MakeRealtime();
|
||||
|
||||
while(ThreadRunning())
|
||||
{
|
||||
|
||||
pwm_out = this->pwm_mask; // start with pin high for all pins that have pwm_enabled
|
||||
|
||||
// Now check for each pin if it should be low in this step...
|
||||
for(i=0;i<16;i++)
|
||||
{
|
||||
if(this->pwm_mask & (1 << i)) // Check if pwm_mask is enabled for this pin
|
||||
{
|
||||
if(ctr >= this->pwm_values[i] ) // check if the counter is greater than the pwm value for this pin. If so, turn it off.
|
||||
{
|
||||
pwm_out &= ~(1 << i); // mask this pin out to 0, for it should be stopped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(pwm_out != this->pwm_prev_val)
|
||||
{
|
||||
// write out the result, masked with the pwm_mask
|
||||
muteI2CMaskedWrite16(OLAT, pwm_out, this->pwm_mask);
|
||||
this->pwm_prev_val = pwm_out;
|
||||
}
|
||||
|
||||
ctr++;
|
||||
if(ctr >= (this->pwm_ticks - 1))
|
||||
{
|
||||
ctr = 0;
|
||||
}
|
||||
|
||||
usleep(this->pwm_tick_delay_us);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/************************************
|
||||
* *
|
||||
* INTERNAL HELPER FUNCTIONS *
|
||||
* *
|
||||
*************************************/
|
||||
|
||||
/*! Try to read an 8 bit value from a register
|
||||
In case of an error, The IOKey error value will be,
|
||||
and the function will return prematurely.
|
||||
*/
|
||||
uint8_t Mcp23017::tryI2CRead8(uint8_t reg)
|
||||
{
|
||||
int ret;
|
||||
// lock process
|
||||
MutexLock();
|
||||
|
||||
// Now start reading
|
||||
ret = i2cReadReg8(this->fp,reg);
|
||||
|
||||
// unlock process
|
||||
MutexUnlock();
|
||||
|
||||
// And properly set any error messages
|
||||
if(ret == -1)
|
||||
throw OperationFailedException("Error writing register address, attempted to read 8bit value from register %s (0x%2x) ",Registers8[reg], reg);
|
||||
else if(ret == -2)
|
||||
throw OperationFailedException("Error reading value, attempted to read 8bit value from register %s (0x%2x)",Registers8[reg], reg);
|
||||
else if(ret < 0)
|
||||
throw OperationFailedException("Unknown error [%d], attempted to read 8bit value from register %s (0x%2x)",ret, Registers8[reg], reg);
|
||||
|
||||
return (uint8_t)ret;
|
||||
}
|
||||
|
||||
/*! Try to write an 8 bit value to a register
|
||||
In case of an error, The IOKey error value will be,
|
||||
and the function will return prematurely.
|
||||
*/
|
||||
void Mcp23017::tryI2CWrite8(uint8_t reg, uint8_t value)
|
||||
{
|
||||
int ret;
|
||||
|
||||
// lock process
|
||||
MutexLock();
|
||||
|
||||
// Now start reading
|
||||
ret = i2cWriteReg8(this->fp,reg,value);
|
||||
|
||||
// unlock process
|
||||
MutexUnlock();
|
||||
|
||||
// And properly set any error messages
|
||||
if(ret == -1)
|
||||
throw OperationFailedException("Error writing to register, attempted to write 8bit value 0x%2x to register %s (0x%2x)",value, Registers8[reg], reg);
|
||||
else if(ret < 0)
|
||||
throw OperationFailedException("Unknown error [%d], attempted to write 8bit value 0x%2x to register %s (0x%2x)",ret, value, Registers8[reg], reg);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*! Try to write specific bits in an 8 bit value to a register
|
||||
In case of an error, The IOKey error value will be,
|
||||
and the function will return prematurely.
|
||||
*/
|
||||
void Mcp23017::tryI2CMaskedWrite8(uint8_t reg, uint8_t value, uint8_t mask)
|
||||
{
|
||||
int ret;
|
||||
uint8_t newval;
|
||||
|
||||
// lock process
|
||||
MutexLock();
|
||||
|
||||
// read current value;
|
||||
if( (ret = i2cReadReg8(this->fp,reg)) < 0)
|
||||
{
|
||||
// unlock process
|
||||
MutexUnlock();
|
||||
// And properly set any error messages
|
||||
if(ret == -1)
|
||||
throw OperationFailedException("Error writing register address, attempted to read 8bit value from register %s (0x%2x) for masked write", Registers8[reg], reg);
|
||||
else if(ret == -2)
|
||||
throw OperationFailedException("Error reading value, attempted to read 8bit value from register %s (0x%2x) for masked write", Registers8[reg], reg);
|
||||
else if(ret < 0)
|
||||
throw OperationFailedException("Unknown error [%d], attempted to read 8bit value from register %s (0x%2x) for masked write",ret, Registers8[reg], reg);
|
||||
}
|
||||
|
||||
// copy result to new variable
|
||||
newval = (uint16_t)(ret);
|
||||
// keep only the non-masked bits
|
||||
newval &= ~mask;
|
||||
// overwrite the masked bits with the new value
|
||||
newval |= (value & mask);
|
||||
|
||||
if( (ret = i2cWriteReg8(this->fp,reg,newval)) < 0)
|
||||
{
|
||||
// unlock process
|
||||
MutexUnlock();
|
||||
|
||||
// And properly set any error messages
|
||||
if(ret == -1)
|
||||
throw OperationFailedException("Error writing to register, attempted to write 8bit value 0x%2x to register %s (0x%2x) for masked write",value, Registers8[reg], reg);
|
||||
else if(ret < 0)
|
||||
throw OperationFailedException("Unknown error [%d], attempted to write 8bit value 0x%2x to register %s (0x%2x) for masked write",ret, value, Registers8[reg], reg);
|
||||
}
|
||||
|
||||
// unlock process
|
||||
MutexUnlock();
|
||||
}
|
||||
|
||||
/*! Try to read a 16 bit value from a register
|
||||
In case of an error, The IOKey error value will be,
|
||||
and the function will return prematurely.
|
||||
*/
|
||||
uint16_t Mcp23017::tryI2CRead16(uint8_t reg)
|
||||
{
|
||||
uint8_t hwreg = reg;
|
||||
int ret;
|
||||
|
||||
// lock process
|
||||
MutexLock();
|
||||
|
||||
// Ensure that we're initially reading from the right register in relation to the AB swap settings
|
||||
// (With swap enabled, we should start reading from the odd register, with swap disabled, we should start reading from the even register)
|
||||
if(this->swapAB)
|
||||
hwreg |= 0x01;
|
||||
else
|
||||
hwreg &= 0xFE;
|
||||
|
||||
// Now start reading
|
||||
ret = i2cReadReg16(this->fp,hwreg);
|
||||
|
||||
// unlock process
|
||||
MutexUnlock();
|
||||
|
||||
// And properly set any error messages
|
||||
if(this->swapAB)
|
||||
{
|
||||
if(ret == -1)
|
||||
throw OperationFailedException("Error writing register address, attempted to read 16bit value from register %s (0x%2x) [AB Swap active, actual register read is %s (0x%2x)]", Registers16[reg], reg, Registers16[hwreg], hwreg);
|
||||
else if(ret == -2)
|
||||
throw OperationFailedException("Error reading value, attempted to read 16bit value from register %s (0x%2x) [AB Swap active, actual register read is %s (0x%2x)]", Registers16[reg], reg, Registers16[hwreg], hwreg);
|
||||
else if(ret < 0)
|
||||
throw OperationFailedException("Unknown error [%d], attempted to read 16bit value from register %s (0x%2x) [AB Swap active, actual register read is %s (0x%2x)]",ret, Registers16[reg], reg, Registers16[hwreg], hwreg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(ret == -1)
|
||||
throw OperationFailedException("Error writing register address, attempted to read 16bit value from register %s (0x%2x)", Registers16[reg], reg);
|
||||
else if(ret == -2)
|
||||
throw OperationFailedException("Error reading value, attempted to read 16bit value from register %s (0x%2x)", Registers16[reg], reg);
|
||||
else if(ret < 0)
|
||||
throw OperationFailedException("Unknown error [%d], attempted to read 16bit value from register %s (0x%2x)",ret, Registers16[reg], reg);
|
||||
}
|
||||
|
||||
return (uint16_t)ret;
|
||||
}
|
||||
|
||||
/*! Try to write a 16 bit value to a register
|
||||
In case of an error, The IOKey error value will be,
|
||||
and the function will return prematurely.
|
||||
*/
|
||||
void Mcp23017::tryI2CWrite16(uint8_t reg,uint16_t value)
|
||||
{
|
||||
uint8_t hwreg = reg;
|
||||
int ret;
|
||||
|
||||
// lock process
|
||||
MutexLock();
|
||||
|
||||
// Ensure that we're initially reading from the right register in relation to the AB swap settings
|
||||
// (With swap enabled, we should start reading from the odd register, with swap disabled, we should start reading from the even register)
|
||||
if(this->swapAB)
|
||||
hwreg |= 0x01;
|
||||
else
|
||||
hwreg &= 0xFE;
|
||||
|
||||
// Now start writing
|
||||
ret = i2cWriteReg16(this->fp,hwreg, value);
|
||||
|
||||
// unlock process
|
||||
MutexUnlock();
|
||||
|
||||
// And properly set any error messages
|
||||
if(this->swapAB)
|
||||
{
|
||||
if(ret == -1)
|
||||
throw OperationFailedException("Error writing to register, attempted to write 16bit value 0x%4x to register %s (0x%2x) [AB Swap active, actual register read is %s (0x%2x)]",value, Registers16[reg], reg, Registers16[hwreg], hwreg);
|
||||
else if(ret < 0)
|
||||
throw OperationFailedException("Unknown error [%d], attempted to write 16bit value 0x%4x to register %s (0x%2x) [AB Swap active, actual register read is %s (0x%2x)]",ret, value, Registers16[reg], reg, Registers16[hwreg], hwreg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(ret == -1)
|
||||
throw OperationFailedException("Error writing to register, attempted to write 16bit value 0x%4x to register %s (0x%2x)",value, Registers16[reg], reg);
|
||||
else if(ret < 0)
|
||||
throw OperationFailedException("Unknown error [%d], attempted to write 16bit value 0x%4x to register %s (0x%2x)",ret, value, Registers16[reg], reg);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*! Try to write specific bits in a 16 bit value to a register
|
||||
In case of an error, The IOKey error value will be,
|
||||
and the function will return prematurely.
|
||||
*/
|
||||
void Mcp23017::tryI2CMaskedWrite16(uint8_t reg, uint16_t value, uint16_t mask)
|
||||
{
|
||||
int ret;
|
||||
uint16_t newval;
|
||||
uint8_t hwreg = reg;
|
||||
|
||||
// lock process
|
||||
MutexLock();
|
||||
|
||||
// Ensure that we're initially reading from the right register in relation to the AB swap settings
|
||||
// (With swap enabled, we should start reading from the odd register, with swap disabled, we should start reading from the even register)
|
||||
if(this->swapAB)
|
||||
hwreg |= 0x01;
|
||||
else
|
||||
hwreg &= 0xFE;
|
||||
|
||||
// read current value;
|
||||
if( (ret = i2cReadReg16(this->fp,hwreg)) < 0)
|
||||
{
|
||||
// unlock process
|
||||
MutexUnlock();
|
||||
|
||||
// And properly set any error messages
|
||||
if(this->swapAB)
|
||||
{
|
||||
if(ret == -1)
|
||||
throw OperationFailedException("Error writing register address, attempted to read 16bit value from register %s (0x%2x) for masked write [AB Swap active, actual register read is %s (0x%2x)]", Registers16[reg], reg, Registers16[hwreg], hwreg);
|
||||
else if(ret == -2)
|
||||
throw OperationFailedException("Error reading value, attempted to read 16bit value from register %s (0x%2x) for masked write [AB Swap active, actual register read is %s (0x%2x)]", Registers16[reg], reg, Registers16[hwreg], hwreg);
|
||||
else if(ret < 0)
|
||||
throw OperationFailedException("Unknown error [%d], attempted to read 16bit value from register %s (0x%2x) for masked write [AB Swap active, actual register read is %s (0x%2x)]",ret, Registers16[reg], reg, Registers16[hwreg], hwreg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(ret == -1)
|
||||
throw OperationFailedException("Error writing register address, attempted to read 16bit value from register %s (0x%2x) for masked write", Registers16[reg], reg);
|
||||
else if(ret == -2)
|
||||
throw OperationFailedException("Error reading value, attempted to read 16bit value from register %s (0x%2x) for masked write", Registers16[reg], reg);
|
||||
else if(ret < 0)
|
||||
throw OperationFailedException("Unknown error [%d], attempted to read 16bit value from register %s (0x%2x) for masked write",ret, Registers16[reg], reg);
|
||||
}
|
||||
}
|
||||
|
||||
// copy result to new variable
|
||||
newval = (uint16_t)(ret);
|
||||
// keep only the non-masked bits
|
||||
newval &= ~mask;
|
||||
// overwrite the masked bits with the new value
|
||||
newval |= (value & mask);
|
||||
|
||||
if( (ret = i2cWriteReg16(this->fp,hwreg,newval)) < 0)
|
||||
{
|
||||
// unlock process
|
||||
MutexUnlock();
|
||||
|
||||
// And properly set any error messages
|
||||
if(this->swapAB)
|
||||
{
|
||||
if(ret == -1)
|
||||
throw OperationFailedException("Error writing to register, attempted to write 16bit value 0x%4x to register %s (0x%2x) for masked write [AB Swap active, actual register read is %s (0x%2x)]",value, Registers16[reg], reg, Registers16[hwreg], hwreg);
|
||||
else if(ret < 0)
|
||||
throw OperationFailedException("Unknown error [%d], attempted to write 16bit value 0x%4x to register %s (0x%2x) for masked write [AB Swap active, actual register read is %s (0x%2x)]",ret, value, Registers16[reg], reg, Registers16[hwreg], hwreg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(ret == -1)
|
||||
throw OperationFailedException("Error writing to register, attempted to write 16bit value 0x%4x to register %s (0x%2x) for masked write",value, Registers16[reg], reg);
|
||||
else if(ret < 0)
|
||||
throw OperationFailedException("Unknown error [%d], attempted to write 16bit value 0x%4x to register %s (0x%2x) for masked write",ret, value, Registers16[reg], reg);
|
||||
}
|
||||
}
|
||||
|
||||
// unlock process
|
||||
MutexUnlock();
|
||||
}
|
||||
|
||||
/*! Attempt to write specific bits in a 16 bit value to a register
|
||||
In case of an error, the function will return prematurely,
|
||||
without setting an error.
|
||||
*/
|
||||
void Mcp23017::muteI2CMaskedWrite16(uint8_t reg, uint16_t value, uint16_t mask)
|
||||
{
|
||||
int result;
|
||||
uint16_t newval;
|
||||
uint8_t hwreg = reg;
|
||||
|
||||
// lock process
|
||||
MutexLock();
|
||||
// Ensure that we're initially reading from the right register in relation to the AB swap settings
|
||||
// (With swap enabled, we should start reading from the odd register, with swap disabled, we should start reading from the even register)
|
||||
if(this->swapAB)
|
||||
hwreg |= 0x01;
|
||||
else
|
||||
hwreg &= 0xFE;
|
||||
|
||||
// read current value;
|
||||
if( (result = i2cReadReg16(this->fp,hwreg)) < 0)
|
||||
{
|
||||
// fprintf(stderr,"WARNING: Got error code [%d] attempting to read\r\n",result);
|
||||
// unlock process
|
||||
MutexUnlock();
|
||||
return;
|
||||
}
|
||||
|
||||
// copy result to new variable
|
||||
newval = (uint16_t)(result);
|
||||
// keep only the non-masked bits
|
||||
newval &= ~mask;
|
||||
// overwrite the masked bits with the new value
|
||||
newval |= (value & mask);
|
||||
|
||||
if( (result = i2cWriteReg16(this->fp,hwreg,newval)) < 0)
|
||||
{
|
||||
// fprintf(stderr,"WARNING: Got error code [%d] attempting to write\r\n",result);
|
||||
// unlock process
|
||||
MutexUnlock();
|
||||
return;
|
||||
}
|
||||
|
||||
// unlock process
|
||||
MutexUnlock();
|
||||
|
||||
}
|
|
@ -1,291 +0,0 @@
|
|||
#ifndef __MCP23017_H_
|
||||
#define __MCP23017_H_
|
||||
|
||||
#include "../exception/baseexceptions.hpp"
|
||||
#include "../thread/thread.hpp"
|
||||
#include <stdint.h>
|
||||
|
||||
/*! \file MCP23017 interface functions. Header file.
|
||||
*/
|
||||
|
||||
/****************************
|
||||
* *
|
||||
* PUBLIC STRUCTS *
|
||||
* *
|
||||
*****************************/
|
||||
|
||||
// Structure for IO Configuration
|
||||
//! \typedef HWConfig Structure containing hardware configuration for the MCP23017
|
||||
class HWConfig
|
||||
{
|
||||
public:
|
||||
bool DISSLEW ; /*!< Disable slew rate control (useful in noisy circuits operating at 400kHz and below), Default: false*/
|
||||
bool INT_MIRROR; /*!< Mirror INTA and INTB pins: Both pins act as a single INT pin responding to both port A and port B, Default: true */
|
||||
bool INT_ODR; /*!< Set interrupt pins as open drain output, Default: false */
|
||||
bool INT_POL; /*!< Set interrupt polatity: 1 is active-high, 0 is active-low, Default: false */
|
||||
|
||||
HWConfig();
|
||||
uint8_t parse(); /*!< parse into usable uint8_t */
|
||||
};
|
||||
|
||||
// Structure for IO Configuration
|
||||
//! \struct PWMConfig Structure containing current PWM configuration (only used as return value)
|
||||
struct PwmConfig
|
||||
{
|
||||
uint32_t tick_delay_us; /*!< Delay between ticks in us */
|
||||
uint8_t ticks; /*!< Number of ticks in a cycle */
|
||||
};
|
||||
|
||||
/************************************
|
||||
* *
|
||||
* MAIN CLASS *
|
||||
* *
|
||||
*************************************/
|
||||
|
||||
class Mcp23017 : public Thread
|
||||
{
|
||||
private:
|
||||
uint8_t adr; // I2C Address of the IO expander chip
|
||||
int fp; // File pointer for the I2C connection
|
||||
bool swapAB; // Option to swap ports A and B for 8bit and 16 bit operations
|
||||
|
||||
HWConfig hwConfig; // Configuration of the port
|
||||
|
||||
bool pwm_enabled; // Boolean used to stop the PWM thread safely
|
||||
uint16_t pwm_mask; // Masks the pins on which PWM operates
|
||||
uint8_t pwm_values[16]; // PWM value for each possible pin
|
||||
uint8_t pwm_v_values[16]; // Cache for the PWM values provided, before downconversion
|
||||
uint32_t pwm_tick_delay_us; // Interval between PWM steps in us
|
||||
uint8_t pwm_ticks; // Number of PWM steps before coming full circle
|
||||
uint16_t pwm_prev_val; // Keeps state of pwm output;
|
||||
|
||||
uint8_t tryI2CRead8 (uint8_t reg);
|
||||
void tryI2CWrite8(uint8_t reg, uint8_t value);
|
||||
void tryI2CMaskedWrite8(uint8_t reg, uint8_t value, uint8_t mask);
|
||||
|
||||
uint16_t tryI2CRead16(uint8_t reg);
|
||||
void tryI2CWrite16(uint8_t reg, uint16_t value);
|
||||
void tryI2CMaskedWrite16(uint8_t reg, uint16_t value, uint16_t mask);
|
||||
void muteI2CMaskedWrite16(uint8_t reg, uint16_t value, uint16_t mask);
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
virtual void ThreadFunc(void); // Override this if you want the entire function customized
|
||||
|
||||
public:
|
||||
//! Open a new connection to the MCP23017 device, and initialize it.
|
||||
/*!
|
||||
\param adr The I2C address of the IC to connect to
|
||||
\param iodir Initial I/O direction mask (HIGH is input, LOW is output)
|
||||
\param ipol Initial input polarity mask (HIGH inverts polarity, LOW keeps it the same)
|
||||
\param pullup Initial pullup mask (HIGH enables pullup, LOW disables)
|
||||
\param hwcfg Hardware configuration for the IC
|
||||
\param swapAB Swap A and B registers in the 16 bit operations
|
||||
\sa MCP23017Close
|
||||
*/
|
||||
Mcp23017( uint8_t adr,
|
||||
uint16_t iodir,
|
||||
uint16_t ipol,
|
||||
uint16_t pullup,
|
||||
HWConfig hwcfg,
|
||||
bool swapAB
|
||||
);
|
||||
|
||||
~Mcp23017();
|
||||
|
||||
/************************************
|
||||
* *
|
||||
* INTERRUPT FUNCTIONS *
|
||||
* *
|
||||
************************************/
|
||||
|
||||
//! Set interrupt config for the MCP23017
|
||||
/*!
|
||||
\param intcon The interrupt control mask (HIGH bit compares to default value, LOW bit to previous value)
|
||||
\param defval Initial default value for the pins
|
||||
\param int_enable The interrupt enable mask (HIGH bit enables, LOW bit disables)
|
||||
*/
|
||||
void IntConfig(uint16_t defval, uint16_t intcon, uint16_t int_enable);
|
||||
|
||||
//! Get the interrupt condition of the interrupt-enabled pins
|
||||
/*!
|
||||
\return Current interrupt trigger state for pins
|
||||
*/
|
||||
uint16_t getIntF();
|
||||
|
||||
//! //! Get state of input pins on latest interrupt
|
||||
/*!
|
||||
\return The state of the interrupt pins on the last interrupt
|
||||
*/
|
||||
uint16_t getIntCap();
|
||||
|
||||
//! Set Default value for pins
|
||||
/*!
|
||||
\param value The new default value for the pins
|
||||
*/
|
||||
void setDefault(uint16_t value);
|
||||
|
||||
//! Get default value for input pins
|
||||
/*!
|
||||
\return Current default value for the input pins
|
||||
*/
|
||||
uint16_t getDefault();
|
||||
|
||||
//! Set Interrupt enable mask
|
||||
/*!
|
||||
\param value The new interrupt enable mask (HIGH bit enables, LOW bit disables)
|
||||
*/
|
||||
void setIntEnable(uint16_t value);
|
||||
|
||||
//! Get Interrupt enable mask
|
||||
/*!
|
||||
\return Current interrupt enable mask
|
||||
*/
|
||||
uint16_t getIntEnable();
|
||||
|
||||
//! Set Interrupt control value
|
||||
/*!
|
||||
\param value The new interrupt control mask (HIGH bit compares to default value, LOW bit to previous value)
|
||||
*/
|
||||
void setIntControl(uint16_t value);
|
||||
|
||||
//! Get Interrupt control value
|
||||
/*!
|
||||
\return Current interrupt control state
|
||||
*/
|
||||
uint16_t getIntControl();
|
||||
|
||||
/************************************
|
||||
* *
|
||||
* REGULAR I/O FUNCTIONS *
|
||||
* *
|
||||
************************************/
|
||||
|
||||
//! Set input polarity
|
||||
/*!
|
||||
\param value The input polarity - HIGH inverts input polarity, LOW does not
|
||||
*/
|
||||
void setIPol(uint16_t value);
|
||||
|
||||
//! Get input polarity
|
||||
/*!
|
||||
\return Current value of the input polarity
|
||||
*/
|
||||
uint16_t getIPol();
|
||||
|
||||
//! Get output latch value
|
||||
/*!
|
||||
\return Current value of the output latches
|
||||
*/
|
||||
uint16_t getOLat();
|
||||
|
||||
//! Set IO Direction
|
||||
/*!
|
||||
\param key The new I/O direction (HIGH bit is input, LOW bit is output)
|
||||
*/
|
||||
void setDirection(uint16_t value);
|
||||
|
||||
//! Get IO Direction
|
||||
/*!
|
||||
\return Current value of the IO direction
|
||||
*/
|
||||
uint16_t getDirection();
|
||||
|
||||
//! Set new output value of the I/O pins
|
||||
/*!
|
||||
\param value The new output value of the pins
|
||||
*/
|
||||
void setValue(uint16_t value);
|
||||
|
||||
//! Get current input value of the I/O pins
|
||||
/*!
|
||||
\return Current value of the port
|
||||
*/
|
||||
uint16_t getValue();
|
||||
|
||||
//! Set new output value for a subset of the I/O pins (masked by 'mask', where high bits indicat bits to set in the output)
|
||||
/*!
|
||||
\param value The new output value of the pins
|
||||
\param mask The mask indicating which bits to apply (HIGH bit indicated bit will be applied)
|
||||
*/
|
||||
void setMaskedValue(uint16_t value, uint16_t mask);
|
||||
//! Get the values of a specific pin;
|
||||
/*!
|
||||
\return The current state (1 or 0) of the pin
|
||||
*/
|
||||
bool getPin(uint8_t pin);
|
||||
|
||||
//! Set the value of a specific pin
|
||||
/*!
|
||||
\param value Boolean indicating the new value of the pin
|
||||
*/
|
||||
void setPin(uint8_t pin, bool value);
|
||||
|
||||
/************************************
|
||||
* *
|
||||
* PWM FUNCTIONS *
|
||||
* *
|
||||
************************************/
|
||||
|
||||
//! Start the PWM routine for this I/O expander
|
||||
void PwmStart();
|
||||
|
||||
//! Stop the PWM routine for this I/O expander
|
||||
void PwmStop();
|
||||
|
||||
//! Get the PWM value for a specific pin (can be different from previously set value, due to rounding errors)
|
||||
/*!
|
||||
\param pin The pin number of which to retrieve the value
|
||||
\return The current PWM value for the pin
|
||||
*/
|
||||
uint8_t getPwmValue(uint8_t pin);
|
||||
|
||||
//! Set the PWM value for a specific pin as 0-255 value
|
||||
/*!
|
||||
\param pin The pin number of which to retrieve the value
|
||||
\param value The new PWM value
|
||||
*/
|
||||
void setPwmValue(uint8_t pin, uint8_t value);
|
||||
|
||||
//! Set the PWM value for a specific pin as 0-255 value and apply gamma correction for LED light levels
|
||||
/*!
|
||||
\param pin The pin number of which to retrieve the value
|
||||
\param value The new PWM value
|
||||
*/
|
||||
void setPwmLedValue(uint8_t, uint8_t lightvalue);
|
||||
|
||||
//! Get the PWM state (on/off) for a specific pin
|
||||
/*!
|
||||
\param pin The pin number of which to retrieve the state
|
||||
\return Boolean indicating the current state of the pin
|
||||
*/
|
||||
bool getPwmState(uint8_t pin);
|
||||
|
||||
//! Set the PWM state (on/off) for a specific pin
|
||||
/*!
|
||||
\param pin The pin number of which to set the state
|
||||
\param state Boolean indicating the new state of pwm on this pin (HIGH is enabled, LOW is disabled)
|
||||
*/
|
||||
void setPwmState(uint8_t pin, bool state);
|
||||
|
||||
//! Set the PWM Configuration
|
||||
/*! Default value results in a 4 bit PWM of around 80Hz
|
||||
Note: PWM has a fairly heavy CPU load. With default settings (4 bit pwm, 800us/step) this is around 4.5% on a raspberry pi.
|
||||
Doubling the resolution to 5 bit, 400us/step takes around 7% cpu load. Use at your own discretion.
|
||||
|
||||
\param tick_delay_us The number of microseconds between PWM ticks (default: 313 us)
|
||||
\param ticks The number of ticks in one PWM cycle (default: 32 ticks)
|
||||
*/
|
||||
void setPwmConfig(uint32_t tick_delay_us, uint8_t ticks);
|
||||
|
||||
//! Get PWM Configuration
|
||||
/*!
|
||||
\return PWMConfig object containing the current settings
|
||||
*/
|
||||
PwmConfig getPwmConfig();
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif
|
|
@ -1,130 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../i2c/i2c.h"
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
int fd,i;
|
||||
int result;
|
||||
|
||||
char * Registers8[22] =
|
||||
{
|
||||
"IODIRA ", // 00
|
||||
"IODIRB ", // 01
|
||||
"IPOLA ", // 02
|
||||
"IPOLB ", // 03
|
||||
"GPINTENA", // 04
|
||||
"GPINTENB", // 05
|
||||
"DEFVALA ", // 06
|
||||
"DEFVALB ", // 07
|
||||
"INTCONA ", // 08
|
||||
"INTCONB ", // 09
|
||||
"IOCON ", // 0A
|
||||
"IOCON ", // 0B
|
||||
"GPPUA ", // 0C
|
||||
"GPPUB ", // 0D
|
||||
"INTFA ", // 0E
|
||||
"INTFB ", // 0F
|
||||
"INTCAPA ", // 10
|
||||
"INTCAPB ", // 11
|
||||
"GPIOA ", // 12
|
||||
"GPIOB ", // 13
|
||||
"OLATA ", // 14
|
||||
"OLATB " // 15
|
||||
};
|
||||
|
||||
char * Registers16[22] =
|
||||
{
|
||||
"IODIR ", // 00
|
||||
"IODIR ", // 01
|
||||
"IPOL ", // 02
|
||||
"IPOL ", // 03
|
||||
"GPINTEN", // 04
|
||||
"GPINTEN", // 05
|
||||
"DEFVAL ", // 06
|
||||
"DEFVAL ", // 07
|
||||
"INTCON ", // 08
|
||||
"INTCON ", // 09
|
||||
"IOCON ", // 0A
|
||||
"IOCON ", // 0B
|
||||
"GPPU ", // 0C
|
||||
"GPPU ", // 0D
|
||||
"INTF ", // 0E
|
||||
"INTF ", // 0F
|
||||
"INTCAP ", // 10
|
||||
"INTCAP ", // 11
|
||||
"GPIO ", // 12
|
||||
"GPIO ", // 13
|
||||
"OLAT ", // 14
|
||||
"OLAT " // 15
|
||||
};
|
||||
|
||||
fd = i2cInit(0x20);
|
||||
|
||||
if(argc > 2)
|
||||
{
|
||||
int reg = -1;
|
||||
int val = -1;
|
||||
|
||||
if(strlen(argv[1]) == 4 && strncmp(argv[1],"0x",2) == 0)
|
||||
reg = strtol(argv[1],NULL,16);
|
||||
|
||||
if( (strlen(argv[2]) == 4 || strlen(argv[2]) == 6) && strncmp(argv[2],"0x",2) == 0)
|
||||
val = strtol(argv[2],NULL,16);
|
||||
|
||||
if(reg < 0)
|
||||
printf ("Please provide an 8 bit hexadecimal value for the register in the form of 0xHH\n");
|
||||
if(val < 0)
|
||||
printf ("Please provide an 8 or 16 bit hexadecimal value for the value in the form of 0xHH\n");
|
||||
|
||||
if(reg >= 0 && val >= 0)
|
||||
{
|
||||
if(strlen(argv[2]) == 4)
|
||||
{
|
||||
printf("Setting register %s (0x%02x) to 8 bit value 0x%02x\n\n", Registers8[reg], reg, val);
|
||||
i2cWriteReg8(fd,(unsigned char)reg, (unsigned char) val);
|
||||
}
|
||||
else if (strlen(argv[2]) == 6)
|
||||
{
|
||||
printf("Setting register %s (0x%02x) to 16 bit value 0x%04x\n\n", Registers16[reg], reg, val);
|
||||
i2cWriteReg16(fd,(unsigned char)reg, (unsigned short) val);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
printf("Reading registers as 8 bit values\n");
|
||||
for(i=0x00; i < 0x16; i++)
|
||||
{
|
||||
result = i2cReadReg8(fd,i);
|
||||
printf(" %s (0x%02x) : 0x%02x\n",Registers8[i], i,result);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
printf("Reading registers as 16 bit values\n");
|
||||
|
||||
for(i=0x00; i < 0x16; i+=2)
|
||||
{
|
||||
result = i2cReadReg16(fd,i);
|
||||
printf(" %s (0x%02x) : 0x%04x\n",Registers16[i], i,result);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
printf("Reading registers as 16 bit values with A/B swapped\n");
|
||||
|
||||
for(i=0x01; i < 0x16; i+=2)
|
||||
{
|
||||
result = i2cReadReg16(fd,i);
|
||||
printf(" %s (0x%02x) : 0x%04x\n",Registers16[i], i,result);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -142,4 +142,5 @@ void * thread_threadStarter(void * obj)
|
|||
{
|
||||
Thread * wt = (Thread*)obj;
|
||||
wt->ThreadStarter();
|
||||
return (void*) nullptr;
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
PACKAGE="mediacore-hid"
|
||||
VERSION="1.0.0"
|
||||
VERSION="4.0.0"
|
||||
|
|
Loading…
Reference in New Issue
Block a user