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 \
|
DISTCLEANFILES = src/mc-hid-server-glue.hpp \
|
||||||
script/init.d/mediacore-hid
|
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 \
|
SUPPORT_SRC = src/log/log.hpp \
|
||||||
src/log/log.cpp \
|
src/log/log.cpp \
|
||||||
src/thread/thread.hpp \
|
src/thread/thread.hpp \
|
||||||
src/thread/thread.cpp \
|
src/thread/thread.cpp \
|
||||||
src/buttontimer/buttontimer.hpp \
|
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
|
sbin_PROGRAMS = mediacore-hid-server
|
||||||
check_PROGRAMS = mcp23017-i2ctest
|
|
||||||
TESTS = mcp23017-i2ctest
|
|
||||||
|
|
||||||
EXTRA_DIST = \
|
EXTRA_DIST = cfg/dbus/nl.miqra.MediaCore.Hid.conf \
|
||||||
cfg/dbus/nl.miqra.MediaCore.Hid.conf \
|
|
||||||
cfg/init.d/mediacore-hid.in \
|
cfg/init.d/mediacore-hid.in \
|
||||||
cfg/rsyslog/syslog.MediaCore.Hid.conf \
|
cfg/rsyslog/syslog.MediaCore.Hid.conf \
|
||||||
src/mc-hid-introspect.xml \
|
src/mc-hid-introspect.xml
|
||||||
cfg/modules \
|
|
||||||
cfg/modprobe.d/raspi-blacklist.conf
|
|
||||||
|
|
||||||
init_d_dirdir = $(sysconfdir)/init.d
|
init_d_dirdir = $(sysconfdir)/init.d
|
||||||
init_d_dir_SCRIPTS = cfg/init.d/mediacore-hid
|
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_confdir = $(sysconfdir)/rsyslog.d
|
||||||
rsyslog_conf_DATA = cfg/rsyslog/syslog.MediaCore.Hid.conf
|
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
|
src/mc-hid-server-glue.hpp: src/mc-hid-introspect.xml
|
||||||
dbusxx-xml2cpp $^ --adaptor=$@
|
dbusxx-xml2cpp $^ --adaptor=$@
|
||||||
|
|
||||||
BUILT_SOURCES = src/mc-hid-server-glue.hpp
|
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 \
|
mediacore_hid_server_SOURCES = src/mc-hid-server.cpp \
|
||||||
src/mc-hid-server.hpp \
|
src/mc-hid-server.hpp \
|
||||||
src/mc-hid-server-glue.hpp \
|
src/mc-hid-server-glue.hpp \
|
||||||
$(MCP_GPIO_SRC) \
|
src/exception/baseexceptions.hpp \
|
||||||
|
src/exception/baseexceptions.cpp \
|
||||||
$(SUPPORT_SRC)
|
$(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
|
cfg/init.d/mediacore-hid: cfg/init.d/mediacore-hid.in
|
||||||
cat $^ > $@
|
cat $^ > $@
|
||||||
sed -i "s#@BIN_DIR@#$(bindir)#" $@
|
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 PACKAGE_NAME "MediaCore HID Server"
|
||||||
|
|
||||||
/* Define to the full name and version of this package. */
|
/* 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 to the one symbol short name of this package. */
|
||||||
#define PACKAGE_TARNAME "mediacore-hid"
|
#define PACKAGE_TARNAME "mediacore-hid"
|
||||||
|
@ -20,4 +20,4 @@
|
||||||
#define PACKAGE_URL "http://www.miqra.nl/"
|
#define PACKAGE_URL "http://www.miqra.nl/"
|
||||||
|
|
||||||
/* Define to the version of this package. */
|
/* 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])
|
AC_PREREQ([2.59])
|
||||||
AM_INIT_AUTOMAKE([1.11 no-define foreign subdir-objects])
|
AM_INIT_AUTOMAKE([1.11 no-define foreign subdir-objects])
|
||||||
AC_CONFIG_HEADERS([config.hpp])
|
AC_CONFIG_HEADERS([config.hpp])
|
||||||
|
|
|
@ -3,4 +3,5 @@ libboost-all-dev
|
||||||
libdbus-c++-bin
|
libdbus-c++-bin
|
||||||
libdbus-c++-dev
|
libdbus-c++-dev
|
||||||
libdbus-1-dev
|
libdbus-1-dev
|
||||||
|
libpigpio-dev
|
||||||
|
|
||||||
|
|
8
debian/control
vendored
8
debian/control
vendored
|
@ -1,15 +1,13 @@
|
||||||
Source: mediacore-hid
|
Source: mediacore-hid
|
||||||
Section: misc
|
Section: misc
|
||||||
Suite: stable
|
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Miqra Engineering Packaging <packaging@miqra.nl>
|
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
|
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: 3.9.3
|
Standards-Version: 4.3.0
|
||||||
Debian-Version: 1
|
|
||||||
Homepage: <insert the upstream URL, if relevant>
|
Homepage: <insert the upstream URL, if relevant>
|
||||||
|
|
||||||
Package: mediacore-hid
|
Package: mediacore-hid
|
||||||
Architecture: any
|
Architecture: armhf
|
||||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||||
Description: I/O System for Mediacore Interface
|
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
|
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
|
print "Entering dir " + PKGDIR
|
||||||
os.chdir(PKGDIR)
|
os.chdir(PKGDIR)
|
||||||
print "Now in ", os.getcwd()
|
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","*")):
|
for f in glob.glob(os.path.join(CWD,"debian","*")):
|
||||||
dst = os.path.join(CWD,PKGPATH,"debian",os.path.basename(f))
|
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();
|
MutexLock();
|
||||||
// Prevent trouble when calling this from within one of our event listeners
|
// 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();
|
MutexLock();
|
||||||
// Prevent trouble when calling this from within one of our event listeners
|
// Prevent trouble when calling this from within one of our event listeners
|
||||||
if(eventLock) { MutexUnlock(); return; }
|
if(eventLock) { MutexUnlock(); return; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(pressRegistry.count(keycode))
|
if(pressRegistry.count(keycode))
|
||||||
{
|
{
|
||||||
// if it was a long press, the key code would already have been erased, so we
|
// if it was a long press, the key code would already have been erased, so we
|
||||||
// can safely fire the onShortPress event
|
// 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
|
// remove from registry after release, if it was a long press, the event should have already been fired
|
||||||
pressRegistry.erase(keycode);
|
pressRegistry.erase(keycode);
|
||||||
|
|
||||||
|
@ -60,7 +57,7 @@ void ButtonTimer::RegisterRelease(uint16_t keycode)
|
||||||
MutexUnlock();
|
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)
|
// remove button id from map (but only if it is in the map already)
|
||||||
MutexLock();
|
MutexLock();
|
||||||
|
@ -77,12 +74,12 @@ void ButtonTimer::CancelPress(uint16_t keycode)
|
||||||
void ButtonTimer::ThreadLoop()
|
void ButtonTimer::ThreadLoop()
|
||||||
{
|
{
|
||||||
uint64_t now = now_ms();
|
uint64_t now = now_ms();
|
||||||
std::list<uint16_t> btnList;
|
std::list<uint8_t> btnList;
|
||||||
boost::optional<bool> valid;
|
boost::optional<bool> valid;
|
||||||
MutexLock();
|
MutexLock();
|
||||||
// list through all the items
|
// 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
|
if(now - (ii->second) >= longpressTime) // if it is in overtime
|
||||||
{
|
{
|
||||||
|
@ -92,7 +89,7 @@ void ButtonTimer::ThreadLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process listed items
|
// 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);
|
pressRegistry.erase(*it);
|
||||||
// If any validators are connected, they can retun false to indicate that this connection is not
|
// 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(uint32_t shortpress_min_ms, uint32_t longpress_ms);
|
||||||
~ButtonTimer();
|
~ButtonTimer();
|
||||||
|
|
||||||
void RegisterPress(uint16_t keycode);
|
void RegisterPress(const uint8_t keycode);
|
||||||
void RegisterRelease(uint16_t keycode);
|
void RegisterRelease(const uint8_t keycode);
|
||||||
void CancelPress(uint16_t id);
|
void CancelPress(const uint8_t keycode);
|
||||||
|
|
||||||
boost::signals2::signal<void (uint16_t keycode)> onShortPress;
|
boost::signals2::signal<void (const uint8_t keycode)> onShortPress;
|
||||||
boost::signals2::signal<void (uint16_t keycode)> onLongPress;
|
boost::signals2::signal<void (const uint8_t keycode)> onLongPress;
|
||||||
boost::signals2::signal<bool (uint16_t keycode)> onValidatePress;
|
boost::signals2::signal<bool (const uint8_t keycode)> onValidatePress;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void ThreadLoop(void);
|
virtual void ThreadLoop(void);
|
||||||
|
@ -26,7 +26,7 @@ class ButtonTimer : protected Thread
|
||||||
boost::signals2::connection onThreadErrorConnection;
|
boost::signals2::connection onThreadErrorConnection;
|
||||||
uint32_t longpressTime;
|
uint32_t longpressTime;
|
||||||
uint32_t shortpressMinTime;
|
uint32_t shortpressMinTime;
|
||||||
std::map<uint16_t, uint64_t> pressRegistry;
|
std::map<const uint8_t, uint64_t> pressRegistry;
|
||||||
|
|
||||||
bool eventLock;
|
bool eventLock;
|
||||||
static int64_t now_ms(void);
|
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 "gpio.hpp"
|
||||||
#include "../log/log.hpp"
|
#include "dma.hpp"
|
||||||
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <syslog.h>
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
using namespace wpp;
|
||||||
|
|
||||||
/****************************
|
/********************************
|
||||||
* *
|
* *
|
||||||
* PRIVATE DEFINTIONS *
|
* Internal singleton class *
|
||||||
* *
|
* to ensure proper teardown *
|
||||||
*****************************/
|
* of dma channel *
|
||||||
|
* *
|
||||||
|
********************************/
|
||||||
|
|
||||||
static unsigned int HardwareRevision(void);
|
|
||||||
|
|
||||||
/****************************
|
class DmaPwmGenerator
|
||||||
* *
|
|
||||||
* IOPIN OBJECT FUNCS *
|
|
||||||
* *
|
|
||||||
*****************************/
|
|
||||||
|
|
||||||
//! Create new IOPin object
|
|
||||||
GpioPin::GpioPin(int gpiopin, GpioDirection direction, GpioEdge edge)
|
|
||||||
{
|
{
|
||||||
int result;
|
private:
|
||||||
int pin_id;
|
DmaPwmGenerator()
|
||||||
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
|
|
||||||
{
|
{
|
||||||
pinPreExported = !(exportPin(pin_id));
|
int ch = (DmaHardware::GetInstance().host_is_model_pi4)?7:14;
|
||||||
}
|
clog << LOG_DEBUG << endl << " -- Creating DmaPwmGenerator using channel " << ch << " --" << endl;
|
||||||
catch(OperationFailedException x)
|
|
||||||
{
|
DmaChannelConfig config{};
|
||||||
throw x;
|
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
|
std::shared_ptr<DmaChannel> channel;
|
||||||
pin = pin_id;
|
std::unordered_map<int, std::shared_ptr<PwmPin>> pins;
|
||||||
|
|
||||||
// Prepare the file names for the different files
|
public:
|
||||||
snprintf(fTemp, GPIO_FN_MAXLEN-1, "/sys/class/gpio/gpio%d/direction", pin_id);
|
// delete copy constructor and assignmen operator to prevent accidentally creating instance doubles
|
||||||
fnDirection = std::string(fTemp);
|
DmaPwmGenerator(DmaPwmGenerator const&) = delete;
|
||||||
snprintf(fTemp, GPIO_FN_MAXLEN-1, "/sys/class/gpio/gpio%d/edge", pin_id);
|
void operator=(DmaPwmGenerator const&) = delete;
|
||||||
fnEdge = std::string(fTemp);
|
|
||||||
snprintf(fTemp, GPIO_FN_MAXLEN-1, "/sys/class/gpio/gpio%d/value", pin_id);
|
|
||||||
fnValue = std::string(fTemp);
|
|
||||||
|
|
||||||
// Initialize callbacks to NULL
|
|
||||||
|
|
||||||
try
|
static DmaPwmGenerator& Instance()
|
||||||
{
|
{
|
||||||
// set initial direction or die trying
|
static DmaPwmGenerator instance;
|
||||||
setDirection(direction);
|
return instance;
|
||||||
// set initial edge or die trying
|
};
|
||||||
setEdge(edge);
|
|
||||||
}
|
~DmaPwmGenerator()
|
||||||
catch(OperationFailedException x)
|
|
||||||
{
|
{
|
||||||
if(!pinPreExported)
|
this->channel->DeactivateChannel();
|
||||||
unexportPin(pin);
|
clog << LOG_DEBUG << " -- DmaPwmGenerator destroyed -- " << endl;
|
||||||
throw x;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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()
|
GpioPin::~GpioPin()
|
||||||
{
|
{
|
||||||
if(!pinPreExported)
|
if(this->dir == GpioDirection::PwmOutput)
|
||||||
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
|
|
||||||
{
|
{
|
||||||
default :
|
DmaPwmGenerator::Instance().UnregisterPin(this->line.offset());
|
||||||
case 'n':
|
}
|
||||||
case 'N':
|
this->line.release();
|
||||||
return kEdgeNone;
|
|
||||||
case 'r':
|
|
||||||
case 'R':
|
|
||||||
return kEdgeRising;
|
|
||||||
case 'f':
|
|
||||||
case 'F':
|
|
||||||
return kEdgeFalling;
|
|
||||||
case 'b':
|
|
||||||
case 'B':
|
|
||||||
return kEdgeBoth;
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
bool GpioPin::DigitalRead()
|
||||||
void GpioPin::setEdge(GpioEdge edge)
|
|
||||||
{
|
{
|
||||||
if (edge == kEdgeNone) writeFile(fnEdge,"none\n");
|
if(this->line.is_requested()){
|
||||||
else if (edge == kEdgeRising) writeFile(fnEdge,"rising\n");
|
return (bool)(this->line.get_value());
|
||||||
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;
|
|
||||||
else
|
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();
|
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();
|
gpiod::line_event evt = this->line.event_read();
|
||||||
|
if(evt.event_type == gpiod::line_event::RISING_EDGE)
|
||||||
|
{
|
||||||
|
this->onChange(evt.source.offset(),GpioEdge::Rising,true);
|
||||||
|
}
|
||||||
|
else //if(evt.event_type == gpiod::line_event::FALLING_EDGE)
|
||||||
|
{
|
||||||
|
this->onChange(evt.source.offset(),GpioEdge::Falling,false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************
|
||||||
|
* *
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
pins.push_back((unsigned int)(*it));
|
||||||
|
}
|
||||||
|
|
||||||
|
this->lines = this->gpioChip.get_lines(pins);
|
||||||
|
}
|
||||||
|
|
||||||
|
GpioPins::~GpioPins()
|
||||||
|
{
|
||||||
|
if(this->dir == GpioDirection::PwmOutput)
|
||||||
|
{
|
||||||
|
for(auto& line : this->lines)
|
||||||
|
{
|
||||||
|
DmaPwmGenerator::Instance().UnregisterPin(line.offset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
{
|
||||||
|
auto& line = this->lines.get(index);
|
||||||
|
DmaPwmGenerator::Instance().SetWidth(line.offset(),width);
|
||||||
|
}
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GpioPin::ThreadFunc()
|
void GpioPins::PinMode(GpioDirection dir, GpioPullup pu, GpioOutput op, bool active_low)
|
||||||
{
|
{
|
||||||
int fd,ret;
|
this->lines.release();
|
||||||
struct pollfd pfd;
|
gpiod::line_request cfg = this->init_linerq(dir,GpioEdge::None,pu,op, active_low);
|
||||||
char rdbuf[RDBUF_LEN];
|
this->lines.request(cfg);
|
||||||
|
this->dir = dir; // store directionality
|
||||||
|
|
||||||
memset(rdbuf, 0x00, RDBUF_LEN);
|
if(dir == GpioDirection::PwmOutput)
|
||||||
|
|
||||||
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);
|
for(auto& line : this->lines)
|
||||||
throw OperationFailedException("Could not read from %s: [%d] %s",fnValue.c_str(), errno, strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
while(ThreadRunning())
|
|
||||||
{
|
{
|
||||||
memset(rdbuf, 0x00, RDBUF_LEN);
|
DmaPwmGenerator::Instance().RegisterPin(line.offset(),0);
|
||||||
lseek(fd, 0, SEEK_SET);
|
|
||||||
ret=poll(&pfd, 1, POLL_TIMEOUT);
|
|
||||||
if(ret<0) // negative result is error
|
|
||||||
{
|
|
||||||
close(fd);
|
|
||||||
throw OperationFailedException("Could not poll %s: [%d] %s",fnValue.c_str(), errno, strerror(errno));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
close(fd);
|
|
||||||
throw OperationFailedException("Could not read from %s: [%d] %s",fnValue.c_str(), errno, strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
throw OperationFailedException("Got empty string reading from %s: [%d] %s",fname.c_str(), errno, strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
rdbuf[ret] = '\0'; // Ensure null termination
|
|
||||||
|
|
||||||
return std::string(rdbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//! 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)
|
|
||||||
{
|
|
||||||
// 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(gpiopin == validpinsRev1[i])
|
|
||||||
return gpiopin;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // REV 2 BOARD
|
|
||||||
|
|
||||||
// Verify that the pin number is valid, otherwise return -1
|
|
||||||
for(i=0; i<sizeof(validpinsRev2);i++)
|
|
||||||
{
|
{
|
||||||
if(gpiopin == validpinsRev2[i])
|
for(auto& line : this->lines)
|
||||||
return gpiopin;
|
{
|
||||||
|
DmaPwmGenerator::Instance().UnregisterPin(line.offset());
|
||||||
}
|
}
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Export a certain Gpio pin
|
|
||||||
/*!
|
void GpioPins::Listen(GpioEdge edge, GpioPullup pu, bool active_low)
|
||||||
\param gpiopin The gpio pin to export
|
|
||||||
\return true if pin was exported by us, false if previously exported
|
|
||||||
*/
|
|
||||||
bool GpioPin::exportPin(int gpiopin)
|
|
||||||
{
|
{
|
||||||
FILE *fd ;
|
this->lines.release();
|
||||||
int pin_id;
|
gpiod::line_request cfg = this->init_linerq(GpioDirection::Input,edge,pu,GpioOutput::Both, active_low);
|
||||||
|
this->lines.request(cfg);
|
||||||
|
|
||||||
pin_id = verifyPin(gpiopin); // verify that the pin is correct
|
this->ThreadStart();
|
||||||
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)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Unexport a certain Gpio pin
|
void GpioPins::ThreadLoop()
|
||||||
/*!
|
|
||||||
\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 ;
|
// wait 20 ms for an event
|
||||||
int pin_id;
|
// if it happens, process, otherwise wait another 20 ms
|
||||||
|
auto event_lines = this->lines.event_wait(std::chrono::milliseconds(20));
|
||||||
pin_id = verifyPin(gpiopin); // verify that the pin is correct
|
if(event_lines)
|
||||||
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& it: event_lines)
|
||||||
}
|
|
||||||
|
|
||||||
fprintf (fd, "%d\n", pin_id) ;
|
|
||||||
|
|
||||||
if(fclose (fd) != 0)
|
|
||||||
{
|
{
|
||||||
// fprintf(stderr, "Got error code %d - %s\n", errno,strerror(errno));
|
gpiod::line_event evt = it.event_read();
|
||||||
|
if(evt.event_type == gpiod::line_event::RISING_EDGE)
|
||||||
if(errno == EINVAL) // indicates the pin is not currently exported
|
{
|
||||||
return false;
|
this->onChange(evt.source.offset(),GpioEdge::Rising,true);
|
||||||
else
|
}
|
||||||
throw OperationFailedException("Pin %d cannot be unexported",pin_id);
|
else //if(evt.event_type == gpiod::line_event::FALLING_EDGE)
|
||||||
|
{
|
||||||
|
this->onChange(evt.source.offset(),GpioEdge::Falling,false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned int HardwareRevision(void)
|
/************************
|
||||||
|
* *
|
||||||
|
* RgbLeD functions *
|
||||||
|
* *
|
||||||
|
************************/
|
||||||
|
|
||||||
|
RgbLed::RgbLed(const uint8_t rPin, const uint8_t gPin, const uint8_t bPin) : GpioPins(std::vector<uint8_t>{rPin,gPin,bPin})
|
||||||
{
|
{
|
||||||
FILE * filp;
|
this->PinMode(GpioDirection::PwmOutput);
|
||||||
unsigned rev;
|
}
|
||||||
char buf[512];
|
|
||||||
char term;
|
uint32_t RgbLed::gamma(uint8_t value)
|
||||||
|
{
|
||||||
rev = 0;
|
const uint16_t gamma_lut[256] = {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
filp = fopen ("/proc/cpuinfo", "r");
|
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,
|
||||||
if (filp != NULL)
|
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,
|
||||||
while (fgets(buf, sizeof(buf), filp) != NULL)
|
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,
|
||||||
if (!strncasecmp("revision\t", buf, 9))
|
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,
|
||||||
if (sscanf(buf+strlen(buf)-5, "%x%c", &rev, &term) == 2)
|
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,
|
||||||
if (term == '\n') break;
|
725, 736, 748, 760, 772, 784, 796, 808, 821, 833, 846, 859, 872, 885, 898, 911,
|
||||||
rev = 0;
|
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,
|
||||||
fclose(filp);
|
};
|
||||||
}
|
|
||||||
return rev;
|
|
||||||
|
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_
|
#ifndef __MC_GPIOBUTTON_HPP
|
||||||
#define __GPIO_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 "../thread/thread.hpp"
|
||||||
|
|
||||||
#include <boost/signals2.hpp>
|
enum class GpioEdge
|
||||||
|
|
||||||
/*! \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
|
|
||||||
{
|
{
|
||||||
protected:
|
None = 0,
|
||||||
virtual std::string type() { return "GpioException"; }
|
Rising = 1,
|
||||||
|
Falling = 2,
|
||||||
|
Both = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Enum for specifying input/output direction
|
enum class GpioDirection
|
||||||
enum GpioDirection
|
|
||||||
{
|
{
|
||||||
kDirectionOut = 0,
|
Input = 0,
|
||||||
kDirectionIn = 1
|
Output = 1,
|
||||||
|
PwmOutput = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
//! Enum for specifying edge detection type
|
enum class GpioPullup
|
||||||
enum GpioEdge
|
|
||||||
{
|
{
|
||||||
kEdgeNone = 0,
|
None = 0,
|
||||||
kEdgeRising = 1,
|
PullUp = 1,
|
||||||
kEdgeFalling = 2,
|
PullDown = 2,
|
||||||
kEdgeBoth = 3
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class GpioPin : public Thread
|
enum class GpioOutput
|
||||||
{
|
{
|
||||||
public:
|
Both = 0,
|
||||||
GpioPin(int pinnr, GpioDirection direction, GpioEdge edge);
|
OpenDrain = 1,
|
||||||
|
OpenSource = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
enum class GpioPolarity
|
||||||
|
{
|
||||||
|
ActiveHigh = 0,
|
||||||
|
ActiveLow = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class GpioBase : protected Thread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GpioBase();
|
||||||
|
~GpioBase();
|
||||||
|
|
||||||
|
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();
|
~GpioPin();
|
||||||
|
|
||||||
//! Get the actual used pin number of the IO Pin
|
virtual void DigitalWrite(bool value);
|
||||||
int getPinNr();
|
virtual bool DigitalRead(void);
|
||||||
|
|
||||||
//! Get current direction of pin
|
virtual void PwmWrite(uint32_t width);
|
||||||
GpioDirection getDirection();
|
|
||||||
//! Set new direction of pin
|
|
||||||
void setDirection(GpioDirection direction);
|
|
||||||
|
|
||||||
//! Get current edge detection type
|
virtual void Listen(GpioEdge edge, GpioPullup pu, bool active_low = false);
|
||||||
GpioEdge getEdge();
|
|
||||||
//! Set edge detection type
|
|
||||||
void setEdge(GpioEdge edge);
|
|
||||||
|
|
||||||
//! Get current value of pin
|
virtual bool IsAvailable();
|
||||||
bool getValue();
|
protected:
|
||||||
//! Set new value of pin
|
virtual void PinMode(GpioDirection dir, GpioPullup pu = GpioPullup::None, GpioOutput op = GpioOutput::Both,bool active_low = false);
|
||||||
void setValue(bool value);
|
|
||||||
|
|
||||||
//! Start interrupt listener
|
virtual void ThreadLoop();
|
||||||
void InterruptStart();
|
gpiod::line line;
|
||||||
//! Stop interrupt listener
|
|
||||||
void InterruptStop();
|
|
||||||
|
|
||||||
//! Signal on interrupt
|
};
|
||||||
boost::signals2::signal<void (GpioPin *, GpioEdge, bool)> onInterrupt;
|
|
||||||
|
|
||||||
// Tag to store application-dependant data
|
class GpioPins : public GpioBase
|
||||||
void * Tag;
|
{
|
||||||
|
public:
|
||||||
|
GpioPins(std::vector<uint8_t> gpios);
|
||||||
|
~GpioPins();
|
||||||
|
|
||||||
virtual void ThreadFunc();
|
virtual void DigitalWrite(std::vector<bool> values);
|
||||||
|
virtual std::vector<bool> DigitalRead(void);
|
||||||
private:
|
virtual void PwmWrite(std::vector<uint32_t> widths);
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
//! Verifies gpio pin number, and translates pin numbers (REV2) to the proper REV1 or REV2 board gpio pin numbers
|
virtual void Listen(GpioEdge edge, GpioPullup pu, bool active_low = false);
|
||||||
static int verifyPin(int gpiopin);
|
|
||||||
|
|
||||||
//! Export a certain Gpio pin
|
protected:
|
||||||
static bool exportPin(int gpiopin);
|
virtual void PinMode(GpioDirection dir, GpioPullup pu = GpioPullup::None, GpioOutput op = GpioOutput::Both,bool active_low = false);
|
||||||
|
|
||||||
//! Unexport a certain Gpio pin
|
virtual void ThreadLoop();
|
||||||
static bool unexportPin(int gpiopin);
|
gpiod::line_bulk lines;
|
||||||
|
|
||||||
//! 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);
|
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() {
|
int Log::sync() {
|
||||||
if (buffer_.length()) {
|
if (buffer_.length()) {
|
||||||
syslog(priority_, buffer_.c_str());
|
syslog(priority_, "%s", buffer_.c_str());
|
||||||
buffer_.erase();
|
buffer_.erase();
|
||||||
priority_ = LOG_DEBUG; // default to debug for each message
|
priority_ = LOG_DEBUG; // default to debug for each message
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,17 +14,12 @@
|
||||||
<arg type="y" name="b" direction="in" />
|
<arg type="y" name="b" direction="in" />
|
||||||
<arg type="d" name="interval" direction="in" />
|
<arg type="d" name="interval" direction="in" />
|
||||||
</method>
|
</method>
|
||||||
<method name="JackState">
|
|
||||||
<arg type="b" name="jackedin" direction="out" />
|
|
||||||
</method>
|
|
||||||
<signal name="ButtonDown">
|
<signal name="ButtonDown">
|
||||||
<arg name="button" type="s"/>
|
<arg name="button" type="s"/>
|
||||||
</signal>
|
</signal>
|
||||||
<signal name="ButtonUp">
|
<signal name="ButtonUp">
|
||||||
<arg name="button" type="s"/>
|
<arg name="button" type="s"/>
|
||||||
</signal>
|
</signal>
|
||||||
<signal name="JackIn" />
|
|
||||||
<signal name="JackOut" />
|
|
||||||
<signal name="ButtonPress">
|
<signal name="ButtonPress">
|
||||||
<arg name="button" type="s"/>
|
<arg name="button" type="s"/>
|
||||||
</signal>
|
</signal>
|
||||||
|
|
|
@ -24,7 +24,6 @@ public:
|
||||||
register_method(Hid_adaptor, SetColor, _SetColor_stub);
|
register_method(Hid_adaptor, SetColor, _SetColor_stub);
|
||||||
register_method(Hid_adaptor, ClearColor, _ClearColor_stub);
|
register_method(Hid_adaptor, ClearColor, _ClearColor_stub);
|
||||||
register_method(Hid_adaptor, PulseColor, _PulseColor_stub);
|
register_method(Hid_adaptor, PulseColor, _PulseColor_stub);
|
||||||
register_method(Hid_adaptor, JackState, _JackState_stub);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::DBus::IntrospectedInterface *introspect() const
|
::DBus::IntrospectedInterface *introspect() const
|
||||||
|
@ -48,11 +47,6 @@ public:
|
||||||
{ "interval", "d", true },
|
{ "interval", "d", true },
|
||||||
{ 0, 0, 0 }
|
{ 0, 0, 0 }
|
||||||
};
|
};
|
||||||
static ::DBus::IntrospectedArgument JackState_args[] =
|
|
||||||
{
|
|
||||||
{ "jackedin", "b", false },
|
|
||||||
{ 0, 0, 0 }
|
|
||||||
};
|
|
||||||
static ::DBus::IntrospectedArgument ButtonDown_args[] =
|
static ::DBus::IntrospectedArgument ButtonDown_args[] =
|
||||||
{
|
{
|
||||||
{ "button", "s", false },
|
{ "button", "s", false },
|
||||||
|
@ -63,14 +57,6 @@ public:
|
||||||
{ "button", "s", false },
|
{ "button", "s", false },
|
||||||
{ 0, 0, 0 }
|
{ 0, 0, 0 }
|
||||||
};
|
};
|
||||||
static ::DBus::IntrospectedArgument JackIn_args[] =
|
|
||||||
{
|
|
||||||
{ 0, 0, 0 }
|
|
||||||
};
|
|
||||||
static ::DBus::IntrospectedArgument JackOut_args[] =
|
|
||||||
{
|
|
||||||
{ 0, 0, 0 }
|
|
||||||
};
|
|
||||||
static ::DBus::IntrospectedArgument ButtonPress_args[] =
|
static ::DBus::IntrospectedArgument ButtonPress_args[] =
|
||||||
{
|
{
|
||||||
{ "button", "s", false },
|
{ "button", "s", false },
|
||||||
|
@ -86,15 +72,12 @@ public:
|
||||||
{ "SetColor", SetColor_args },
|
{ "SetColor", SetColor_args },
|
||||||
{ "ClearColor", ClearColor_args },
|
{ "ClearColor", ClearColor_args },
|
||||||
{ "PulseColor", PulseColor_args },
|
{ "PulseColor", PulseColor_args },
|
||||||
{ "JackState", JackState_args },
|
|
||||||
{ 0, 0 }
|
{ 0, 0 }
|
||||||
};
|
};
|
||||||
static ::DBus::IntrospectedMethod Hid_adaptor_signals[] =
|
static ::DBus::IntrospectedMethod Hid_adaptor_signals[] =
|
||||||
{
|
{
|
||||||
{ "ButtonDown", ButtonDown_args },
|
{ "ButtonDown", ButtonDown_args },
|
||||||
{ "ButtonUp", ButtonUp_args },
|
{ "ButtonUp", ButtonUp_args },
|
||||||
{ "JackIn", JackIn_args },
|
|
||||||
{ "JackOut", JackOut_args },
|
|
||||||
{ "ButtonPress", ButtonPress_args },
|
{ "ButtonPress", ButtonPress_args },
|
||||||
{ "ButtonLongPress", ButtonLongPress_args },
|
{ "ButtonLongPress", ButtonLongPress_args },
|
||||||
{ 0, 0 }
|
{ 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 SetColor(const uint8_t& r, const uint8_t& g, const uint8_t& b) = 0;
|
||||||
virtual void ClearColor() = 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 void PulseColor(const uint8_t& r, const uint8_t& g, const uint8_t& b, const double& interval) = 0;
|
||||||
virtual bool JackState() = 0;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -147,16 +129,6 @@ public:
|
||||||
wi << arg1;
|
wi << arg1;
|
||||||
emit_signal(sig);
|
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)
|
void ButtonPress(const std::string& arg1)
|
||||||
{
|
{
|
||||||
::DBus::SignalMessage sig("ButtonPress");
|
::DBus::SignalMessage sig("ButtonPress");
|
||||||
|
@ -207,16 +179,6 @@ private:
|
||||||
::DBus::ReturnMessage reply(call);
|
::DBus::ReturnMessage reply(call);
|
||||||
return reply;
|
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>
|
#include <config.hpp>
|
||||||
#endif
|
//#endif
|
||||||
|
|
||||||
#include "mc-hid-server.hpp"
|
#include "mc-hid-server.hpp"
|
||||||
#include "log/log.hpp"
|
#include "log/log.hpp"
|
||||||
|
|
||||||
|
#include <pigpio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
@ -12,50 +13,67 @@
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <pthread.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;
|
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_NAME = "nl.miqra.MediaCore.Hid";
|
||||||
static const char *HID_SERVER_PATH = "/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
|
// signal handler
|
||||||
void niam(int sig);
|
void niam(int sig);
|
||||||
|
|
||||||
|
@ -63,14 +81,11 @@ HidServer::HidServer(DBus::Connection &connection)
|
||||||
: DBus::ObjectAdaptor(connection, HID_SERVER_PATH)
|
: DBus::ObjectAdaptor(connection, HID_SERVER_PATH)
|
||||||
{
|
{
|
||||||
|
|
||||||
// Initialize class variables if needed
|
|
||||||
noiseTimeout = 0; // Value of 0 means: no noise timeout
|
|
||||||
|
|
||||||
// Initialize button timer
|
// Initialize button timer
|
||||||
btnTimer = new ButtonTimer(25,6000); // Short press should take at leas 25 ms, and a Long press takes 6 seconds
|
btnTimer = new ButtonTimer(10,5000); // Short press should take at leas 10 ms, and a Long press takes 5 seconds
|
||||||
onShortPressConnection = btnTimer->onShortPress.connect(boost::bind(&HidServer::onShortPress, this, _1));
|
onShortPressConnection = this->btnTimer->onShortPress.connect( boost::bind( &HidServer::onShortPress, this, _1) );
|
||||||
onLongPressConnection = btnTimer->onLongPress.connect(boost::bind(&HidServer::onLongPress, this, _1));
|
onLongPressConnection = this->btnTimer->onLongPress.connect( boost::bind( &HidServer::onLongPress, this, _1) );
|
||||||
onValidatePressConnection = btnTimer->onValidatePress.connect(boost::bind(&HidServer::onValidatePress, this, _1));
|
onValidatePressConnection = this->btnTimer->onValidatePress.connect( boost::bind( &HidServer::onValidatePress, this, _1) );
|
||||||
|
|
||||||
// **** Initialize the GPIO Interrupt pin
|
// **** Initialize the GPIO Interrupt pin
|
||||||
|
|
||||||
|
@ -78,12 +93,33 @@ HidServer::HidServer(DBus::Connection &connection)
|
||||||
|
|
||||||
try
|
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;
|
clog << kLogCrit << endl << "Fatal error during hardware initialization: " << x.what() << endl << "Quitting now... " << endl;
|
||||||
niam(0);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
clog << kLogInfo << "Initialization complete, listening to HID requests." << endl;
|
clog << kLogInfo << "Initialization complete, listening to HID requests." << endl;
|
||||||
|
@ -93,127 +129,27 @@ HidServer::~HidServer()
|
||||||
{
|
{
|
||||||
ClearColor();
|
ClearColor();
|
||||||
|
|
||||||
onInterruptErrorConnection.disconnect();
|
onBtHomeConnection.disconnect();
|
||||||
onInterruptConnection.disconnect();
|
onBtOtherConnection.disconnect();
|
||||||
|
onShortPressConnection.disconnect();
|
||||||
|
|
||||||
delete intpin; intpin = NULL;
|
onLongPressConnection.disconnect();
|
||||||
delete mcp; mcp = NULL;
|
onValidatePressConnection.disconnect();
|
||||||
delete btnTimer; btnTimer = NULL;
|
|
||||||
|
delete this->btnTimer; this->btnTimer = NULL;
|
||||||
|
delete this->btOther;
|
||||||
|
delete this->btHome;
|
||||||
|
delete this->rgbLed;
|
||||||
|
|
||||||
clog << kLogInfo << "Stopping normally" << endl;
|
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)
|
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 << ") - ";
|
clog << kLogDebug << "Got color request: RGB("<< (int)r << "," << (int)g << "," << (int)b << ") - " << endl;
|
||||||
// Check if we can do solid colors (no need for PWM)
|
this->ThreadStop();
|
||||||
if( (r == 0 || r==255) &&
|
this->rgbLed->SetColor(r,g,b);
|
||||||
(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HidServer::PulseColor(const uint8_t& r, const uint8_t& g, const uint8_t& b, const double& interval)
|
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;
|
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
|
// Start pulse thread and PWM system
|
||||||
ThreadStart();
|
ThreadStart();
|
||||||
mcp->PwmStart();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// contains the pulse loop
|
// contains the pulse loop (note: faster pulsing does use more cpu)
|
||||||
void HidServer::ThreadLoop()
|
void HidServer::ThreadLoop()
|
||||||
{
|
{
|
||||||
uint8_t myR, myG, myB;
|
uint8_t myR, myG, myB;
|
||||||
int32_t factor = pulseI*100;
|
int32_t factor = pulseI*100;
|
||||||
|
|
||||||
|
|
||||||
myR = (pulseR * factor)/(100 * PULSE_STEPS);
|
myR = (pulseR * factor)/(100 * pulseSteps);
|
||||||
myG = (pulseG * factor)/(100 * PULSE_STEPS);
|
myG = (pulseG * factor)/(100 * pulseSteps);
|
||||||
myB = (pulseB * factor)/(100 * PULSE_STEPS);
|
myB = (pulseB * factor)/(100 * pulseSteps);
|
||||||
|
|
||||||
mcp->setPwmLedValue(PIN_RED, myR);
|
|
||||||
mcp->setPwmLedValue(PIN_GREEN, myG);
|
|
||||||
mcp->setPwmLedValue(PIN_BLUE, myB);
|
|
||||||
|
|
||||||
|
this->rgbLed->SetColor(myR,myG,myB);
|
||||||
|
|
||||||
if(pulseDirUp)
|
if(pulseDirUp)
|
||||||
{
|
{
|
||||||
pulseI++;
|
pulseI++;
|
||||||
if(pulseI>= PULSE_STEPS)
|
if(pulseI>= pulseSteps)
|
||||||
pulseDirUp = false;
|
pulseDirUp = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -270,216 +210,50 @@ void HidServer::ThreadLoop()
|
||||||
|
|
||||||
void HidServer::ClearColor()
|
void HidServer::ClearColor()
|
||||||
{
|
{
|
||||||
clog << kLogDebug << "Got request to clear colors" << endl;
|
clog << kLogDebug << "Clearing color" << endl;
|
||||||
|
|
||||||
// Stop pulse thread
|
// Stop pulse thread
|
||||||
ThreadStop();
|
ThreadStop();
|
||||||
|
|
||||||
// Disable PWM and turn all colors off
|
// Disable PWM and turn all colors off
|
||||||
mcp->PwmStop(); // waits for completion
|
this->rgbLed->SetColor(0,0,0);
|
||||||
mcp->setPin(PIN_RED, false);
|
|
||||||
mcp->setPin(PIN_GREEN, false);
|
|
||||||
mcp->setPin(PIN_BLUE, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HidServer::JackState()
|
void HidServer::onBtChange(uint8_t gpio, GpioEdge edge, bool level)
|
||||||
{
|
{
|
||||||
uint16_t value;
|
if(edge == GpioEdge::Rising){
|
||||||
|
this->btnTimer->RegisterPress((uint8_t)gpio);
|
||||||
try
|
// clog << kLogDebug << " (Button down : " << getBtnName((uint8_t)gpio) << ")" << endl;
|
||||||
{
|
|
||||||
value = mcp->getValue();
|
|
||||||
}
|
}
|
||||||
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
|
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
|
// Check if the key is still pressed before sending out a long press
|
||||||
|
return true; //((bool)gpioRead(gpio)) == BUTTONS_ACTIVE.at(gpio);
|
||||||
if(mcp->getValue() & keycode)
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HidServer::onShortPress(uint16_t keycode)
|
void HidServer::onShortPress(uint8_t gpio)
|
||||||
{
|
{
|
||||||
clog << kLogDebug << "SHORT PRESS : " << getBtnName(keycode) << endl;
|
clog << kLogDebug << "SHORT PRESS : " << getBtnName(gpio) << endl;
|
||||||
ButtonPress(getBtnName(keycode));
|
ButtonPress(getBtnName(gpio));
|
||||||
}
|
}
|
||||||
|
|
||||||
void HidServer::onLongPress(uint16_t keycode)
|
void HidServer::onLongPress(uint8_t gpio)
|
||||||
{
|
{
|
||||||
clog << kLogDebug << "LONG PRESS : " << getBtnName(keycode) << endl;
|
clog << kLogDebug << "LONG PRESS : " << getBtnName(gpio) << endl;
|
||||||
ButtonLongPress(getBtnName(keycode));
|
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)
|
return BUTTONS.at(gpio);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -494,19 +268,24 @@ int main()
|
||||||
{
|
{
|
||||||
signal(SIGTERM, niam);
|
signal(SIGTERM, niam);
|
||||||
signal(SIGINT, 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"
|
// Initialize clog to be redirected to syslog key "mediacore.hid.server"
|
||||||
Log::Init("mediacore.hid");
|
// Log::Init("mediacore.hid");
|
||||||
|
|
||||||
DBus::Connection conn = DBus::Connection::SystemBus();
|
DBus::Connection conn = DBus::Connection::SystemBus();
|
||||||
conn.request_name(HID_SERVER_NAME);
|
conn.request_name(HID_SERVER_NAME);
|
||||||
|
|
||||||
|
//gpioCfgClock(10,1,0);
|
||||||
|
//gpioInitialise();
|
||||||
|
|
||||||
HidServer server(conn);
|
HidServer server(conn);
|
||||||
|
|
||||||
|
|
||||||
dispatcher.enter();
|
dispatcher.enter();
|
||||||
|
|
||||||
|
//gpioTerminate();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
|
@ -2,15 +2,14 @@
|
||||||
#define __MC_HID_SERVER_HPP
|
#define __MC_HID_SERVER_HPP
|
||||||
|
|
||||||
#include <stdint.h>
|
#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 <boost/signals2.hpp>
|
||||||
|
|
||||||
#include "mc-hid-server-glue.hpp"
|
#include "mc-hid-server-glue.hpp"
|
||||||
|
|
||||||
#include "gpio/gpio.hpp"
|
|
||||||
#include "mcp23017/mcp23017.hpp"
|
|
||||||
#include "thread/thread.hpp"
|
#include "thread/thread.hpp"
|
||||||
#include "buttontimer/buttontimer.hpp"
|
#include "buttontimer/buttontimer.hpp"
|
||||||
|
#include "gpio/gpio.hpp"
|
||||||
|
|
||||||
class HidServer
|
class HidServer
|
||||||
: public nl::miqra::MediaCore::Hid_adaptor, // << This will be generated by the makefile using dbusxx-xml2cpp on mc-hid-introspect.xml
|
: 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 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 PulseColor(const uint8_t& r, const uint8_t& g, const uint8_t& b, const double& interval);
|
||||||
virtual void ClearColor();
|
virtual void ClearColor();
|
||||||
virtual bool JackState();
|
|
||||||
|
|
||||||
void onInterrupt(GpioPin * sender, GpioEdge edge, bool pinval);
|
bool onValidatePress(uint8_t gpio);
|
||||||
void onInterruptError(Thread * sender, ThreadException x);
|
void onShortPress(uint8_t gpio);
|
||||||
|
void onLongPress(uint8_t gpio);
|
||||||
bool onValidatePress(uint16_t keycode);
|
void onBtChange(uint8_t gpio, GpioEdge edge, bool level);
|
||||||
void onShortPress(uint16_t keycode);
|
|
||||||
void onLongPress(uint16_t keycode);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void ThreadLoop();
|
virtual void ThreadLoop();
|
||||||
|
//void onChange(int gpio, int level, uint32_t tick);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Mcp23017 *mcp;
|
|
||||||
GpioPin *intpin;
|
|
||||||
ButtonTimer *btnTimer;
|
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 pulseTime;
|
||||||
|
int32_t pulseSteps;
|
||||||
bool pulseDirUp;
|
bool pulseDirUp;
|
||||||
|
|
||||||
void initHardware(void);
|
void initHardware(void);
|
||||||
|
|
||||||
void keyUp(uint16_t keycode);
|
std::string getBtnName(uint8_t gpio);
|
||||||
void keyDown(uint16_t keycode);
|
|
||||||
std::string getBtnName(uint16_t keycode);
|
|
||||||
|
|
||||||
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 onInterruptErrorConnection;
|
||||||
boost::signals2::connection onShortPressConnection;
|
boost::signals2::connection onShortPressConnection;
|
||||||
boost::signals2::connection onLongPressConnection;
|
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;
|
Thread * wt = (Thread*)obj;
|
||||||
wt->ThreadStarter();
|
wt->ThreadStarter();
|
||||||
|
return (void*) nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
PACKAGE="mediacore-hid"
|
PACKAGE="mediacore-hid"
|
||||||
VERSION="1.0.0"
|
VERSION="4.0.0"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user