Redesign based on libgpiod and dma PWM for MC 4

This commit is contained in:
P.M. Kuipers 2021-04-23 13:12:32 +02:00
parent 39a3e5d9bc
commit e661ed5902
33 changed files with 2767 additions and 3171 deletions

851
Makefile

File diff suppressed because it is too large Load Diff

View File

@ -6,35 +6,25 @@ AM_CPPFLAGS = $(DEPS_CFLAGS)
DISTCLEANFILES = src/mc-hid-server-glue.hpp \
script/init.d/mediacore-hid
I2C_SRC = src/i2c/i2c.c \
src/i2c/i2c.h
MCP_GPIO_SRC = src/gpio/gpio.hpp \
src/gpio/gpio.cpp \
src/mcp23017/mcp23017.hpp \
src/mcp23017/mcp23017.cpp \
src/exception/baseexceptions.hpp \
src/exception/baseexceptions.cpp \
$(I2C_SRC)
SUPPORT_SRC = src/log/log.hpp \
src/log/log.cpp \
src/thread/thread.hpp \
src/thread/thread.cpp \
src/buttontimer/buttontimer.hpp \
src/buttontimer/buttontimer.cpp
src/buttontimer/buttontimer.cpp \
src/gpio/gpio.hpp \
src/gpio/gpio.cpp \
src/gpio/dma.hpp \
src/gpio/dma.cpp \
src/gpio/mailbox.h \
src/gpio/mailbox.c
sbin_PROGRAMS = mediacore-hid-server
check_PROGRAMS = mcp23017-i2ctest
TESTS = mcp23017-i2ctest
EXTRA_DIST = \
cfg/dbus/nl.miqra.MediaCore.Hid.conf \
EXTRA_DIST = cfg/dbus/nl.miqra.MediaCore.Hid.conf \
cfg/init.d/mediacore-hid.in \
cfg/rsyslog/syslog.MediaCore.Hid.conf \
src/mc-hid-introspect.xml \
cfg/modules \
cfg/modprobe.d/raspi-blacklist.conf
src/mc-hid-introspect.xml
init_d_dirdir = $(sysconfdir)/init.d
init_d_dir_SCRIPTS = cfg/init.d/mediacore-hid
@ -45,31 +35,21 @@ dbus_conf_DATA = cfg/dbus/nl.miqra.MediaCore.Hid.conf
rsyslog_confdir = $(sysconfdir)/rsyslog.d
rsyslog_conf_DATA = cfg/rsyslog/syslog.MediaCore.Hid.conf
etc_dirdir = $(sysconfdir)
etc_dir_DATA = cfg/modules
modprobe_d_dirdir = $(sysconfdir)/modprobe.d
modprobe_d_dir_DATA = cfg/modprobe.d/raspi-blacklist.conf
src/mc-hid-server-glue.hpp: src/mc-hid-introspect.xml
dbusxx-xml2cpp $^ --adaptor=$@
BUILT_SOURCES = src/mc-hid-server-glue.hpp
mediacore_hid_server_CPPFLAGS = -std=c++17 -I/usr/include/dbus-c++-1/ -I/opt/vc/include/
mediacore_hid_server_SOURCES = src/mc-hid-server.cpp \
src/mc-hid-server.hpp \
src/mc-hid-server-glue.hpp \
$(MCP_GPIO_SRC) \
src/exception/baseexceptions.hpp \
src/exception/baseexceptions.cpp \
$(SUPPORT_SRC)
mediacore_hid_server_LDADD = $(DEPS_LIBS) -lpthread -lrt
mediacore_hid_server_LDADD = $(DEPS_LIBS) -L/opt/vc/lib -lpthread -lrt -lgpiodcxx -lbcm_host
mcp23017_i2ctest_SOURCES = src/test/mcp23017-i2ctest.c \
$(I2C_SRC)
mcp23017_i2ctest_LDADD = -lpthread
cfg/init.d/mediacore-hid: cfg/init.d/mediacore-hid.in
cat $^ > $@

163
cfg/init.d/mediacore-hid Normal file
View 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
:

View File

@ -1,4 +0,0 @@
# blacklist spi and i2c by default (many users don't need them)
blacklist spi-bcm2708
#blacklist i2c-bcm2708

View File

@ -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

View File

@ -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;
}
}
}
*/

View File

@ -11,7 +11,7 @@
#define PACKAGE_NAME "MediaCore HID Server"
/* Define to the full name and version of this package. */
#define PACKAGE_STRING "MediaCore HID Server 1.0.0"
#define PACKAGE_STRING "MediaCore HID Server 4.0.0"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "mediacore-hid"
@ -20,4 +20,4 @@
#define PACKAGE_URL "http://www.miqra.nl/"
/* Define to the version of this package. */
#define PACKAGE_VERSION "1.0.0"
#define PACKAGE_VERSION "4.0.0"

View File

@ -1,4 +1,4 @@
AC_INIT([MediaCore HID Server], [1.0.0], [bugs@miqra.nl], [mediacore-hid], [http://www.miqra.nl/])
AC_INIT([MediaCore HID Server], [4.0.0], [bugs@miqra.nl], [mediacore-hid], [http://www.miqra.nl/])
AC_PREREQ([2.59])
AM_INIT_AUTOMAKE([1.11 no-define foreign subdir-objects])
AC_CONFIG_HEADERS([config.hpp])

View File

@ -3,4 +3,5 @@ libboost-all-dev
libdbus-c++-bin
libdbus-c++-dev
libdbus-1-dev
libpigpio-dev

8
debian/control vendored
View File

@ -1,15 +1,13 @@
Source: mediacore-hid
Section: misc
Suite: stable
Priority: optional
Maintainer: Miqra Engineering Packaging <packaging@miqra.nl>
Build-Depends: debhelper (>= 8.0.0), autotools-dev, autoconf-archive, libboost-all-dev, libdbus-c++-bin, libdbus-c++-dev, libdbus-1-dev, libtclap-dev
Standards-Version: 3.9.3
Debian-Version: 1
Build-Depends: debhelper (>= 8.0.0), autotools-dev, autoconf-archive, libboost-all-dev, libdbus-c++-bin, libdbus-c++-dev, libdbus-1-dev, libgpiod-dev, libraspberrypi-dev
Standards-Version: 4.3.0
Homepage: <insert the upstream URL, if relevant>
Package: mediacore-hid
Architecture: any
Architecture: armhf
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: I/O System for Mediacore Interface
I/O server system for mediacore that exposes the i/o through DBUS for use in other applications

View File

@ -46,7 +46,7 @@ call(["tar","-xzvf",DEBSOURCEPKG])
print "Entering dir " + PKGDIR
os.chdir(PKGDIR)
print "Now in ", os.getcwd()
call(["dh_make --single -yes --copyright bsd"],shell=True)
call(["dh_make --single --yes --copyright lgpl"],shell=True)
for f in glob.glob(os.path.join(CWD,"debian","*")):
dst = os.path.join(CWD,PKGPATH,"debian",os.path.basename(f))

BIN
mediacore-hid-server Executable file

Binary file not shown.

View File

@ -20,7 +20,7 @@ ButtonTimer::~ButtonTimer()
}
void ButtonTimer::RegisterPress(uint16_t keycode)
void ButtonTimer::RegisterPress(const uint8_t keycode)
{
MutexLock();
// Prevent trouble when calling this from within one of our event listeners
@ -31,21 +31,18 @@ void ButtonTimer::RegisterPress(uint16_t keycode)
}
void ButtonTimer::RegisterRelease(uint16_t keycode)
void ButtonTimer::RegisterRelease(const uint8_t keycode)
{
uint64_t now = now_ms();
uint64_t then;
MutexLock();
// Prevent trouble when calling this from within one of our event listeners
if(eventLock) { MutexUnlock(); return; }
if(pressRegistry.count(keycode))
{
// if it was a long press, the key code would already have been erased, so we
// can safely fire the onShortPress event
then = pressRegistry[keycode];
uint64_t now = now_ms();
uint64_t then = pressRegistry[keycode];
// remove from registry after release, if it was a long press, the event should have already been fired
pressRegistry.erase(keycode);
@ -60,7 +57,7 @@ void ButtonTimer::RegisterRelease(uint16_t keycode)
MutexUnlock();
}
void ButtonTimer::CancelPress(uint16_t keycode)
void ButtonTimer::CancelPress(const uint8_t keycode)
{
// remove button id from map (but only if it is in the map already)
MutexLock();
@ -77,12 +74,12 @@ void ButtonTimer::CancelPress(uint16_t keycode)
void ButtonTimer::ThreadLoop()
{
uint64_t now = now_ms();
std::list<uint16_t> btnList;
std::list<uint8_t> btnList;
boost::optional<bool> valid;
MutexLock();
// list through all the items
for( std::map<uint16_t,uint64_t>::iterator ii=pressRegistry.begin(); ii!=pressRegistry.end(); ++ii)
for( std::map<uint8_t,uint64_t>::iterator ii=pressRegistry.begin(); ii!=pressRegistry.end(); ++ii)
{
if(now - (ii->second) >= longpressTime) // if it is in overtime
{
@ -92,7 +89,7 @@ void ButtonTimer::ThreadLoop()
}
// Process listed items
for (std::list<uint16_t>::iterator it=btnList.begin(); it != btnList.end(); ++it)
for (std::list<uint8_t>::iterator it=btnList.begin(); it != btnList.end(); ++it)
{
pressRegistry.erase(*it);
// If any validators are connected, they can retun false to indicate that this connection is not

View File

@ -11,13 +11,13 @@ class ButtonTimer : protected Thread
ButtonTimer(uint32_t shortpress_min_ms, uint32_t longpress_ms);
~ButtonTimer();
void RegisterPress(uint16_t keycode);
void RegisterRelease(uint16_t keycode);
void CancelPress(uint16_t id);
void RegisterPress(const uint8_t keycode);
void RegisterRelease(const uint8_t keycode);
void CancelPress(const uint8_t keycode);
boost::signals2::signal<void (uint16_t keycode)> onShortPress;
boost::signals2::signal<void (uint16_t keycode)> onLongPress;
boost::signals2::signal<bool (uint16_t keycode)> onValidatePress;
boost::signals2::signal<void (const uint8_t keycode)> onShortPress;
boost::signals2::signal<void (const uint8_t keycode)> onLongPress;
boost::signals2::signal<bool (const uint8_t keycode)> onValidatePress;
protected:
virtual void ThreadLoop(void);
@ -26,7 +26,7 @@ class ButtonTimer : protected Thread
boost::signals2::connection onThreadErrorConnection;
uint32_t longpressTime;
uint32_t shortpressMinTime;
std::map<uint16_t, uint64_t> pressRegistry;
std::map<const uint8_t, uint64_t> pressRegistry;
bool eventLock;
static int64_t now_ms(void);

796
src/gpio/dma.cpp Normal file
View 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
View 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

View File

@ -1,458 +1,472 @@
#include <stdio.h>
#include <poll.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <malloc.h>
#include <unistd.h>
#include "gpio.hpp"
#include "../log/log.hpp"
#include <iostream>
#define GPIO_0_2_R1 0 /*!< \def Gpio pin 0/2 (rev1/rev2) with rev2 board code */
#define GPIO_1_3_R1 1 /*!< \def Gpio pin 1/3 (rev1/rev2) with rev2 board code */
#define GPIO_21_27_R1 21 /*!< \def Gpio pin 21/27 (rev1/rev2) with rev2 board code */
#define RDBUF_LEN 10 // length of read buffer
#define POLL_TIMEOUT 100 // timeout for polling function in ms (also the maximum delay time before thread is stopped
#define FALSE 0
#define TRUE 1
using namespace std;
/****************************
* *
* PRIVATE DEFINTIONS *
* *
*****************************/
static unsigned int HardwareRevision(void);
/****************************
* *
* IOPIN OBJECT FUNCS *
* *
*****************************/
//! Create new IOPin object
GpioPin::GpioPin(int gpiopin, GpioDirection direction, GpioEdge edge)
{
int result;
int pin_id;
char fTemp[GPIO_FN_MAXLEN];
// Set tag to empty pointer
Tag = NULL;
pin_id = verifyPin(gpiopin);
if(pin_id < 0)
throw OperationFailedException("Gpio pin %d is not a valid Gpio for this Raspberry Pi board revision",gpiopin);
// ensure that the Gpio pin is exported
try
{
pinPreExported = !(exportPin(pin_id));
}
catch(OperationFailedException x)
{
throw x;
}
// Prepare the iopin object
pin = pin_id;
// Prepare the file names for the different files
snprintf(fTemp, GPIO_FN_MAXLEN-1, "/sys/class/gpio/gpio%d/direction", pin_id);
fnDirection = std::string(fTemp);
snprintf(fTemp, GPIO_FN_MAXLEN-1, "/sys/class/gpio/gpio%d/edge", pin_id);
fnEdge = std::string(fTemp);
snprintf(fTemp, GPIO_FN_MAXLEN-1, "/sys/class/gpio/gpio%d/value", pin_id);
fnValue = std::string(fTemp);
// Initialize callbacks to NULL
try
{
// set initial direction or die trying
setDirection(direction);
// set initial edge or die trying
setEdge(edge);
}
catch(OperationFailedException x)
{
if(!pinPreExported)
unexportPin(pin);
throw x;
}
}
//! Close the IOPin connection
GpioPin::~GpioPin()
{
if(!pinPreExported)
unexportPin(pin);
}
//! Get the actual used pin number of the IO Pin
int GpioPin::getPinNr()
{
return pin;
}
//! Get current direction of pin
GpioDirection GpioPin::getDirection()
{
std::string s = readFile(fnDirection);
// got enough info in the first byte
if(s[0] == 'i')
return kDirectionIn;
else
return kDirectionOut;
}
//! Set new value of pin
void GpioPin::setDirection(GpioDirection direction)
{
if (direction == kDirectionIn) writeFile(fnDirection,"in\n");
else if (direction == kDirectionOut) writeFile(fnDirection,"out\n");
}
//! Get current edge detection type
GpioEdge GpioPin::getEdge()
{
std::string s = readFile(fnEdge);
switch(s[0]) // as the first letters of each result are all different
{
default :
case 'n':
case 'N':
return kEdgeNone;
case 'r':
case 'R':
return kEdgeRising;
case 'f':
case 'F':
return kEdgeFalling;
case 'b':
case 'B':
return kEdgeBoth;
}
}
//! Set edge detection type
void GpioPin::setEdge(GpioEdge edge)
{
if (edge == kEdgeNone) writeFile(fnEdge,"none\n");
else if (edge == kEdgeRising) writeFile(fnEdge,"rising\n");
else if (edge == kEdgeFalling) writeFile(fnEdge,"falling\n");
else if (edge == kEdgeBoth) writeFile(fnEdge,"both\n");
}
//! Get current value of pin
bool GpioPin::getValue()
{
std::string s = readFile(fnValue);
// got enough info in the first byte
if(s[0] == '1')
return true;
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())
{
this->ThreadStart();
}
}
void GpioPin::InterruptStop()
{
if(ThreadRunning())
{
this->ThreadStop();
}
}
void GpioPin::ThreadFunc()
{
int fd,ret;
struct pollfd pfd;
char rdbuf[RDBUF_LEN];
memset(rdbuf, 0x00, RDBUF_LEN);
fd=open(fnValue.c_str(), O_RDONLY);
if(fd<0)
throw OperationFailedException("Could not open file %s for reading: [%d] %s",fnValue.c_str(), errno, strerror(errno));
pfd.fd=fd;
pfd.events=POLLPRI;
ret=read(fd, rdbuf, RDBUF_LEN-1);
if(ret<0)
{
close(fd);
throw OperationFailedException("Could not read from %s: [%d] %s",fnValue.c_str(), errno, strerror(errno));
}
while(ThreadRunning())
{
memset(rdbuf, 0x00, RDBUF_LEN);
lseek(fd, 0, SEEK_SET);
ret=poll(&pfd, 1, POLL_TIMEOUT);
if(ret<0) // negative result is error
{
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
{ // REV 2 BOARD
// Verify that the pin number is valid, otherwise return -1
for(i=0; i<sizeof(validpinsRev2);i++)
{
if(gpiopin == validpinsRev2[i])
return gpiopin;
}
return -1;
}
}
//! Export a certain Gpio pin
/*!
\param gpiopin The gpio pin to export
\return true if pin was exported by us, false if previously exported
*/
bool GpioPin::exportPin(int gpiopin)
{
FILE *fd ;
int pin_id;
pin_id = verifyPin(gpiopin); // verify that the pin is correct
if(pin_id < 0)
throw InvalidArgumentException("Pin %d is not a usable Raspberry Pi GPIO pin",pin_id);
if ((fd = fopen ("/sys/class/gpio/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
/*!
\param gpiopin The gpio pin to unexport
\return true if pin was unexported by us, false if previously unexported
*/
bool GpioPin::unexportPin(int gpiopin)
{
FILE *fd ;
int pin_id;
pin_id = verifyPin(gpiopin); // verify that the pin is correct
if(pin_id < 0)
throw InvalidArgumentException("Pin %d is not a usable Raspberry Pi GPIO pin",pin_id);
if ((fd = fopen ("/sys/class/gpio/unexport", "w")) == NULL)
{
throw OperationFailedException("Pin %d cannot be unexported (cannot write to /sys/class/gpio/unexport)",pin_id);
}
fprintf (fd, "%d\n", pin_id) ;
if(fclose (fd) != 0)
{
// fprintf(stderr, "Got error code %d - %s\n", errno,strerror(errno));
if(errno == EINVAL) // indicates the pin is not currently exported
return false;
else
throw OperationFailedException("Pin %d cannot be unexported",pin_id);
}
else
return true;
}
static unsigned int HardwareRevision(void)
{
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;
#include "gpio.hpp"
#include "dma.hpp"
#include <fstream>
#include <iostream>
#include <syslog.h>
using namespace std;
using namespace wpp;
/********************************
* *
* Internal singleton class *
* to ensure proper teardown *
* of dma channel *
* *
********************************/
class DmaPwmGenerator
{
private:
DmaPwmGenerator()
{
int ch = (DmaHardware::GetInstance().host_is_model_pi4)?7:14;
clog << LOG_DEBUG << endl << " -- Creating DmaPwmGenerator using channel " << ch << " --" << endl;
DmaChannelConfig config{};
config.chNum = ch;
// cycle_time_us: typical value 10240=10.24ms each cycle
config.cycleTimeUs = 10240;
// step_time_us: typical value 10=10us each step
// (total steps = 10240/5=2048 steps)
config.stepTimeUs = 5;
// We will only use DELAY_VIA_PWM, since PCM is in use by the audio chip
config.delay_hw = DelayHardware::DELAY_VIA_PWM;
// invert: don't invert high/low output
config.invert = false;
auto channel = DmaChannel::CreateInstance(config);
this->channel = channel;
}
std::shared_ptr<DmaChannel> channel;
std::unordered_map<int, std::shared_ptr<PwmPin>> pins;
public:
// delete copy constructor and assignmen operator to prevent accidentally creating instance doubles
DmaPwmGenerator(DmaPwmGenerator const&) = delete;
void operator=(DmaPwmGenerator const&) = delete;
static DmaPwmGenerator& Instance()
{
static DmaPwmGenerator instance;
return instance;
};
~DmaPwmGenerator()
{
this->channel->DeactivateChannel();
clog << LOG_DEBUG << " -- DmaPwmGenerator destroyed -- " << endl;
}
// initialize pin and set default puse width in steps
bool RegisterPin(uint8_t gpio, uint32_t width = 0)
{
if (this->pins.find(gpio) != this->pins.end()) {
return false;
}
DmaPwmPinConfig config{};
config.gpioPinNum = gpio;
config.widthInSteps = width;
auto pin = channel->CreatePin(config);
pins[gpio] = pin;
return true;
}
// unregister pin from pwm
bool UnregisterPin(int gpio) {
auto pin = this->pins.find(gpio);
if (pin == this->pins.end()) {
// pin don't exist?
return false;
}
pin->second->DeactivatePin();
pins.erase(pin);
return true;
}
// Change pulse width in steps
bool SetWidth(uint8_t gpio, uint32_t width)
{
auto pin = pins.find(gpio);
if (pin == pins.end()) {
// pin don't exist?
return false;
}
pin->second->SetByWidth(width);
return true;
}
};
/************************
* *
* GpioBase functions *
* *
************************/
GpioBase::GpioBase()
{
this->gpioChip.open("gpiochip0",gpiod::chip::OPEN_BY_NAME);
}
GpioBase::~GpioBase()
{
}
gpiod::line_request GpioBase::init_linerq(GpioDirection dir, GpioEdge edge, GpioPullup pu, GpioOutput op, bool active_low)
{
// find process name from /proc file system
std::ifstream comm("/proc/self/comm");
std::string name;
getline(comm, name);
// setup line request with process name as consumer name
gpiod::line_request rq;
rq.consumer = name;
// determine the request type from requested Direction and Edge
rq.request_type = gpiod::line_request::DIRECTION_AS_IS;
if(dir == GpioDirection::Output || dir == GpioDirection::PwmOutput) {
rq.request_type = gpiod::line_request::DIRECTION_OUTPUT;
}
else if(edge == GpioEdge::Both) {
rq.request_type = gpiod::line_request::EVENT_BOTH_EDGES;
}
else if (edge == GpioEdge::Rising) {
rq.request_type = gpiod::line_request::EVENT_RISING_EDGE;
}
else if (edge == GpioEdge::Falling) {
rq.request_type = gpiod::line_request::EVENT_FALLING_EDGE;
}
else //if (edge == GpioEdge::None)
{
rq.request_type = gpiod::line_request::DIRECTION_INPUT;
}
rq.flags = 0;
// Determine flags from the other
if(dir == GpioDirection::Output)
{
if(op == GpioOutput::OpenDrain){
rq.flags |= ::gpiod::line_request::FLAG_OPEN_DRAIN;
}
else if(op == GpioOutput::OpenSource){
rq.flags |= ::gpiod::line_request::FLAG_OPEN_SOURCE;
}
}
else // if(dir = GpioDirection::Input)
{
// next lines require libgpiod 1.6.2
if(pu == GpioPullup::PullUp){
// rq.flags |= ::gpiod::line_request::FLAG_BIAS_PULL_UP;
}
else if(pu == GpioPullup::PullDown){
// rq.flags |= ::gpiod::line_request::FLAG_BIAS_PULL_DOWN;
}
}
if(active_low){
rq.flags |= ::gpiod::line_request::FLAG_ACTIVE_LOW;
}
return rq;
}
void GpioBase::PinMode(GpioDirection dir, GpioOutput op, bool active_low)
{
this->PinMode(dir,GpioPullup::None,op, active_low);
}
void GpioBase::PinMode(GpioDirection dir, GpioPullup pu, bool active_low)
{
this->PinMode(dir,pu,GpioOutput::Both, active_low);
}
void GpioBase::PinMode(GpioDirection dir, bool active_low)
{
this->PinMode(dir,GpioPullup::None,GpioOutput::Both, active_low);
}
void GpioBase::Listen(GpioEdge edge, bool active_low)
{
this->Listen(edge, GpioPullup::None, active_low);
}
/************************
* *
* GpioPin functions *
* *
************************/
GpioPin::GpioPin(uint8_t gpio)
{
this->line = this->gpioChip.get_line(gpio);
}
GpioPin::~GpioPin()
{
if(this->dir == GpioDirection::PwmOutput)
{
DmaPwmGenerator::Instance().UnregisterPin(this->line.offset());
}
this->line.release();
}
void GpioPin::DigitalWrite(bool value)
{
if(this->line.is_requested() && this->line.direction() == gpiod::line::DIRECTION_OUTPUT){
this->line.set_value(value?1:0);
}
}
bool GpioPin::DigitalRead()
{
if(this->line.is_requested()){
return (bool)(this->line.get_value());
}
else
{
return false; // fallback
}
}
bool GpioPin::IsAvailable(){
return !(this->line.is_used() && !this->line.is_requested());
}
void GpioPin::PinMode(GpioDirection dir, GpioPullup pu, GpioOutput op, bool active_low)
{
if(this->IsAvailable())
{
this->line.release();
gpiod::line_request cfg = this->init_linerq(dir,GpioEdge::None,pu,op, active_low);
this->line.request(cfg);
this->dir = dir; // store directionality
if(dir == GpioDirection::PwmOutput)
{
DmaPwmGenerator::Instance().RegisterPin(this->line.offset());
}
else
{
DmaPwmGenerator::Instance().UnregisterPin(this->line.offset());
}
}
}
void GpioPin::PwmWrite(uint32_t width)
{
// only do this if the pin was set to PwmOutput
if(this->dir == GpioDirection::PwmOutput)
{
DmaPwmGenerator::Instance().SetWidth(this->line.offset(),width);
}
}
void GpioPin::Listen(GpioEdge edge, GpioPullup pu, bool active_low)
{
if(this->IsAvailable())
{
this->line.release();
gpiod::line_request cfg = this->init_linerq(GpioDirection::Input,edge,pu,GpioOutput::Both, active_low);
this->line.request(cfg);
this->ThreadStart();
}
}
void GpioPin::ThreadLoop()
{
// 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)
{
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 GpioPins::PinMode(GpioDirection dir, GpioPullup pu, GpioOutput op, bool active_low)
{
this->lines.release();
gpiod::line_request cfg = this->init_linerq(dir,GpioEdge::None,pu,op, active_low);
this->lines.request(cfg);
this->dir = dir; // store directionality
if(dir == GpioDirection::PwmOutput)
{
for(auto& line : this->lines)
{
DmaPwmGenerator::Instance().RegisterPin(line.offset(),0);
}
}
else
{
for(auto& line : this->lines)
{
DmaPwmGenerator::Instance().UnregisterPin(line.offset());
}
}
}
void GpioPins::Listen(GpioEdge edge, GpioPullup pu, bool active_low)
{
this->lines.release();
gpiod::line_request cfg = this->init_linerq(GpioDirection::Input,edge,pu,GpioOutput::Both, active_low);
this->lines.request(cfg);
this->ThreadStart();
}
void GpioPins::ThreadLoop()
{
// wait 20 ms for an event
// if it happens, process, otherwise wait another 20 ms
auto event_lines = this->lines.event_wait(std::chrono::milliseconds(20));
if(event_lines)
{
for(auto& it: event_lines)
{
gpiod::line_event evt = it.event_read();
if(evt.event_type == gpiod::line_event::RISING_EDGE)
{
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);
}
}
}
}
/************************
* *
* RgbLeD functions *
* *
************************/
RgbLed::RgbLed(const uint8_t rPin, const uint8_t gPin, const uint8_t bPin) : GpioPins(std::vector<uint8_t>{rPin,gPin,bPin})
{
this->PinMode(GpioDirection::PwmOutput);
}
uint32_t RgbLed::gamma(uint8_t value)
{
const uint16_t gamma_lut[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6,
6, 7, 7, 8, 9, 9, 10, 11, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 23, 24, 25, 27, 28, 29, 31, 32, 34, 36, 37, 39, 41,
43, 45, 47, 49, 51, 53, 55, 57, 59, 62, 64, 67, 69, 72, 74, 77,
80, 83, 85, 88, 91, 94, 98, 101, 104, 107, 111, 114, 118, 121, 125, 129,
133, 137, 141, 145, 149, 153, 157, 162, 166, 171, 175, 180, 185, 189, 194, 199,
204, 210, 215, 220, 226, 231, 237, 242, 248, 254, 260, 266, 272, 278, 284, 291,
297, 304, 310, 317, 324, 331, 338, 345, 352, 359, 367, 374, 382, 390, 397, 405,
413, 421, 430, 438, 446, 455, 463, 472, 481, 490, 499, 508, 517, 526, 536, 545,
555, 565, 575, 585, 595, 605, 615, 626, 636, 647, 658, 669, 680, 691, 702, 713,
725, 736, 748, 760, 772, 784, 796, 808, 821, 833, 846, 859, 872, 885, 898, 911,
925, 938, 952, 966, 980, 994,1008,1022,1037,1051,1066,1081,1096,1111,1126,1142,
1157,1173,1189,1204,1221,1237,1253,1270,1286,1303,1320,1337,1354,1371,1389,1406,
1424,1442,1460,1478,1496,1515,1533,1552,1571,1590,1609,1629,1648,1668,1687,1707,
1727,1748,1768,1789,1809,1830,1851,1872,1894,1915,1937,1958,1980,2002,2025,2047,
};
return (uint32_t) gamma_lut[value];
}
void RgbLed::SetColor(const uint8_t r, const uint8_t g, const uint8_t b)
{
std::vector<uint32_t> values = {this->gamma(r),this->gamma(g),this->gamma(b)};
this->PwmWrite(values);
}

View File

@ -1,125 +1,137 @@
#ifndef __GPIO_HPP_
#define __GPIO_HPP_
#include "../exception/baseexceptions.hpp"
#include "../thread/thread.hpp"
#include <boost/signals2.hpp>
/*! \file Gpio interrupt capture functions. Header file.
*/
// P1 Header pins (both board revisions)
#define GPIO_0_2 2 /*!< \def Gpio pin 0/2 (rev1/rev2) */
#define GPIO_1_3 3 /*!< \def Gpio pin 1/3 (rev1/rev2) */
#define GPIO_4 4 /*!< \def Gpio pin 4 */
#define GPIO_7 7 /*!< \def Gpio pin 7 */
#define GPIO_8 8 /*!< \def Gpio pin 8 */
#define GPIO_9 9 /*!< \def Gpio pin 9 */
#define GPIO_10 10 /*!< \def Gpio pin 10 */
#define GPIO_11 11 /*!< \def Gpio pin 11 */
#define GPIO_14 14 /*!< \def Gpio pin 14 */
#define GPIO_15 15 /*!< \def Gpio pin 15 */
#define GPIO_17 17 /*!< \def Gpio pin 17 */
#define GPIO_18 18 /*!< \def Gpio pin 18 */
#define GPIO_21_27 27 /*!< \def Gpio pin 21/27 (rev1/rev2) */
#define GPIO_22 22 /*!< \def Gpio pin 22 */
#define GPIO_23 23 /*!< \def Gpio pin 23 */
#define GPIO_24 24 /*!< \def Gpio pin 24 */
#define GPIO_25 25 /*!< \def Gpio pin 25 */
// P5 Header pins (only rev 2)
#define GPIO_28 28 /*!< \def Gpio pin 28 */
#define GPIO_29 29 /*!< \def Gpio pin 29 */
#define GPIO_30 30 /*!< \def Gpio pin 30 */
#define GPIO_31 31 /*!< \def Gpio pin 31 */
#define GPIO_FN_MAXLEN 128 // length of file name field
class GpioException : MsgException
{
protected:
virtual std::string type() { return "GpioException"; }
};
//! Enum for specifying input/output direction
enum GpioDirection
{
kDirectionOut = 0,
kDirectionIn = 1
};
//! Enum for specifying edge detection type
enum GpioEdge
{
kEdgeNone = 0,
kEdgeRising = 1,
kEdgeFalling = 2,
kEdgeBoth = 3
};
class GpioPin : public Thread
{
public:
GpioPin(int pinnr, GpioDirection direction, GpioEdge edge);
~GpioPin();
//! Get the actual used pin number of the IO Pin
int getPinNr();
//! Get current direction of pin
GpioDirection getDirection();
//! Set new direction of pin
void setDirection(GpioDirection direction);
//! Get current edge detection type
GpioEdge getEdge();
//! Set edge detection type
void setEdge(GpioEdge edge);
//! Get current value of pin
bool getValue();
//! Set new value of pin
void setValue(bool value);
//! Start interrupt listener
void InterruptStart();
//! Stop interrupt listener
void InterruptStop();
//! Signal on interrupt
boost::signals2::signal<void (GpioPin *, GpioEdge, bool)> onInterrupt;
// Tag to store application-dependant data
void * Tag;
virtual void ThreadFunc();
private:
int pin; // Gpio pin number
std::string fnDirection; // File name for Direction file
std::string fnEdge; // File name for Edge file
std::string fnValue; // File name for Value file
bool pinPreExported; // Bool indicates if the pin was already exported
//! Verifies gpio pin number, and translates pin numbers (REV2) to the proper REV1 or REV2 board gpio pin numbers
static int verifyPin(int gpiopin);
//! Export a certain Gpio pin
static bool exportPin(int gpiopin);
//! Unexport a certain Gpio pin
static bool unexportPin(int gpiopin);
//! 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);
//! open a file for reading and read some text from it
static std::string readFile(std::string &fname);
};
#endif
#ifndef __MC_GPIOBUTTON_HPP
#define __MC_GPIOBUTTON_HPP
#include <stdint.h>
#include <vector>
#include <boost/signals2.hpp>
#include <gpiod.hpp>
#include "../thread/thread.hpp"
enum class GpioEdge
{
None = 0,
Rising = 1,
Falling = 2,
Both = 3
};
enum class GpioDirection
{
Input = 0,
Output = 1,
PwmOutput = 2,
};
enum class GpioPullup
{
None = 0,
PullUp = 1,
PullDown = 2,
};
enum class GpioOutput
{
Both = 0,
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();
virtual void DigitalWrite(bool value);
virtual bool DigitalRead(void);
virtual void PwmWrite(uint32_t width);
virtual void Listen(GpioEdge edge, GpioPullup pu, bool active_low = false);
virtual bool IsAvailable();
protected:
virtual void PinMode(GpioDirection dir, GpioPullup pu = GpioPullup::None, GpioOutput op = GpioOutput::Both,bool active_low = false);
virtual void ThreadLoop();
gpiod::line line;
};
class GpioPins : public GpioBase
{
public:
GpioPins(std::vector<uint8_t> gpios);
~GpioPins();
virtual void DigitalWrite(std::vector<bool> values);
virtual std::vector<bool> DigitalRead(void);
virtual void PwmWrite(std::vector<uint32_t> widths);
virtual void Listen(GpioEdge edge, GpioPullup pu, bool active_low = false);
protected:
virtual void PinMode(GpioDirection dir, GpioPullup pu = GpioPullup::None, GpioOutput op = GpioOutput::Both,bool active_low = false);
virtual void ThreadLoop();
gpiod::line_bulk lines;
};
class RgbLed : protected GpioPins
{
protected:
uint32_t gamma(uint8_t value);
public:
RgbLed(const uint8_t rPin, const uint8_t gPin, const uint8_t bPin);
/*!
\param r The red component (0 to 255)
\param g The green component (0 to 255)
\param b The blue component (0 to 255)
*/
void SetColor(const uint8_t r, const uint8_t g, const uint8_t b);
};
#endif//__MC_HID_SERVER_HPP

299
src/gpio/mailbox.c Normal file
View 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
View 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

View File

@ -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;
}

View File

@ -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

View File

@ -32,7 +32,7 @@ Log::Log(std::string ident, int facility) {
int Log::sync() {
if (buffer_.length()) {
syslog(priority_, buffer_.c_str());
syslog(priority_, "%s", buffer_.c_str());
buffer_.erase();
priority_ = LOG_DEBUG; // default to debug for each message
}

View File

@ -14,17 +14,12 @@
<arg type="y" name="b" direction="in" />
<arg type="d" name="interval" direction="in" />
</method>
<method name="JackState">
<arg type="b" name="jackedin" direction="out" />
</method>
<signal name="ButtonDown">
<arg name="button" type="s"/>
</signal>
<signal name="ButtonUp">
<arg name="button" type="s"/>
</signal>
<signal name="JackIn" />
<signal name="JackOut" />
<signal name="ButtonPress">
<arg name="button" type="s"/>
</signal>

View File

@ -24,7 +24,6 @@ public:
register_method(Hid_adaptor, SetColor, _SetColor_stub);
register_method(Hid_adaptor, ClearColor, _ClearColor_stub);
register_method(Hid_adaptor, PulseColor, _PulseColor_stub);
register_method(Hid_adaptor, JackState, _JackState_stub);
}
::DBus::IntrospectedInterface *introspect() const
@ -48,11 +47,6 @@ public:
{ "interval", "d", true },
{ 0, 0, 0 }
};
static ::DBus::IntrospectedArgument JackState_args[] =
{
{ "jackedin", "b", false },
{ 0, 0, 0 }
};
static ::DBus::IntrospectedArgument ButtonDown_args[] =
{
{ "button", "s", false },
@ -63,14 +57,6 @@ public:
{ "button", "s", false },
{ 0, 0, 0 }
};
static ::DBus::IntrospectedArgument JackIn_args[] =
{
{ 0, 0, 0 }
};
static ::DBus::IntrospectedArgument JackOut_args[] =
{
{ 0, 0, 0 }
};
static ::DBus::IntrospectedArgument ButtonPress_args[] =
{
{ "button", "s", false },
@ -86,15 +72,12 @@ public:
{ "SetColor", SetColor_args },
{ "ClearColor", ClearColor_args },
{ "PulseColor", PulseColor_args },
{ "JackState", JackState_args },
{ 0, 0 }
};
static ::DBus::IntrospectedMethod Hid_adaptor_signals[] =
{
{ "ButtonDown", ButtonDown_args },
{ "ButtonUp", ButtonUp_args },
{ "JackIn", JackIn_args },
{ "JackOut", JackOut_args },
{ "ButtonPress", ButtonPress_args },
{ "ButtonLongPress", ButtonLongPress_args },
{ 0, 0 }
@ -127,7 +110,6 @@ public:
virtual void SetColor(const uint8_t& r, const uint8_t& g, const uint8_t& b) = 0;
virtual void ClearColor() = 0;
virtual void PulseColor(const uint8_t& r, const uint8_t& g, const uint8_t& b, const double& interval) = 0;
virtual bool JackState() = 0;
public:
@ -147,16 +129,6 @@ public:
wi << arg1;
emit_signal(sig);
}
void JackIn()
{
::DBus::SignalMessage sig("JackIn");
emit_signal(sig);
}
void JackOut()
{
::DBus::SignalMessage sig("JackOut");
emit_signal(sig);
}
void ButtonPress(const std::string& arg1)
{
::DBus::SignalMessage sig("ButtonPress");
@ -207,16 +179,6 @@ private:
::DBus::ReturnMessage reply(call);
return reply;
}
::DBus::Message _JackState_stub(const ::DBus::CallMessage &call)
{
::DBus::MessageIter ri = call.reader();
bool argout1 = JackState();
::DBus::ReturnMessage reply(call);
::DBus::MessageIter wi = reply.writer();
wi << argout1;
return reply;
}
};
} } }

View File

@ -1,10 +1,11 @@
#ifdef HAVE_CONFIG_H
//#ifdef HAVE_CONFIG_H
#include <config.hpp>
#endif
//#endif
#include "mc-hid-server.hpp"
#include "log/log.hpp"
#include <pigpio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
@ -12,50 +13,67 @@
#include <limits.h>
#include <pthread.h>
// For getting current time
#include <time.h>
#ifndef CLOCK_MONOTIC
# define CLOCK_MONOTIC CLOCK_REALTIME
#endif
#define NOISE_TIMEOUT_MS 400
#define GPIO_INT_PIN 4
#define MCP23017_ADR 0x20
#define BTN_ESCAPE 0x0001
#define BTN_FAVORITES 0x0002
#define BTN_RECENT 0x0004
#define BTN_REWIND 0x0008
#define BTN_PLAYPAUSE 0x0010
#define BTN_FASTFORWARD 0x0020
#define BTN_CANCEL 0x0040
#define BTN_OK 0x0080
#define BTN_UP 0x0100
#define BTN_DOWN 0x0200
#define SENSE_MINIJACK 0x0400
#define PIN_RED 12
#define PIN_GREEN 13
#define PIN_BLUE 14
#define PULSE_STEPS 32
#define PULSE_WAIT_US 20000
#define TRUE 1
#define FALSE 0
using namespace std;
const uint8_t BTN_HOME = 7;
const uint8_t BTN_CANCEL = 8;
const uint8_t BTN_UP = 9;
const uint8_t BTN_DOWN = 10;
const uint8_t BTN_PLAY = 11;
const uint8_t BTN_FWD = 12;
const uint8_t BTN_RWD = 16;
const uint8_t BTN_OK = 17;
// map the buttons to names
const std::map<uint8_t,std::string> BUTTONS = {
{ BTN_HOME, "BTN_ESCAPE"},
{ BTN_CANCEL, "BTN_CANCEL"},
{ BTN_UP, "BTN_UP"},
{ BTN_DOWN, "BTN_DOWN"},
{ BTN_FWD, "BTN_FASTFORWARD"},
{ BTN_PLAY, "BTN_PLAYPAUSE"},
{ BTN_RWD, "BTN_REWIND"},
{ BTN_OK, "BTN_OK"},
};
// define the active state of the
const std::map<uint8_t,bool> BUTTONS_ACTIVE = {
{ BTN_HOME, true},
{ BTN_CANCEL, false},
{ BTN_UP, false},
{ BTN_DOWN, false},
{ BTN_FWD, false},
{ BTN_PLAY, false},
{ BTN_RWD, false},
{ BTN_OK, false},
};
const std::vector<uint8_t> BUTTONS_REGULAR = {
BTN_CANCEL,
BTN_UP,
BTN_DOWN,
BTN_FWD,
BTN_PLAY,
BTN_RWD,
BTN_OK,
};
const uint8_t LED_RED = 4;
const uint8_t LED_GREEN = 5;
const uint8_t LED_BLUE = 6;
const uint8_t FAN_CTL = 13;
const int PULSE_STEPS = 32;
static const char *HID_SERVER_NAME = "nl.miqra.MediaCore.Hid";
static const char *HID_SERVER_PATH = "/nl/miqra/MediaCore/Hid";
// function to get current time in ms
int64_t now_ms(void);
// signal handler
void niam(int sig);
@ -63,14 +81,11 @@ HidServer::HidServer(DBus::Connection &connection)
: DBus::ObjectAdaptor(connection, HID_SERVER_PATH)
{
// Initialize class variables if needed
noiseTimeout = 0; // Value of 0 means: no noise timeout
// Initialize button timer
btnTimer = new ButtonTimer(25,6000); // Short press should take at leas 25 ms, and a Long press takes 6 seconds
onShortPressConnection = btnTimer->onShortPress.connect(boost::bind(&HidServer::onShortPress, this, _1));
onLongPressConnection = btnTimer->onLongPress.connect(boost::bind(&HidServer::onLongPress, this, _1));
onValidatePressConnection = btnTimer->onValidatePress.connect(boost::bind(&HidServer::onValidatePress, this, _1));
btnTimer = new ButtonTimer(10,5000); // Short press should take at leas 10 ms, and a Long press takes 5 seconds
onShortPressConnection = this->btnTimer->onShortPress.connect( boost::bind( &HidServer::onShortPress, this, _1) );
onLongPressConnection = this->btnTimer->onLongPress.connect( boost::bind( &HidServer::onLongPress, this, _1) );
onValidatePressConnection = this->btnTimer->onValidatePress.connect( boost::bind( &HidServer::onValidatePress, this, _1) );
// **** Initialize the GPIO Interrupt pin
@ -78,12 +93,33 @@ HidServer::HidServer(DBus::Connection &connection)
try
{
initHardware();
// Setup all buttons for pullup and input
// Setup home button (which is active-high)
clog << kLogInfo << "Connecting to Home button";
this->btHome = new GpioPin(BTN_HOME);
clog << kLogInfo << ".";
onBtHomeConnection = this->btHome->onChange.connect( boost::bind( &HidServer::onBtChange, this, _1, _2, _3) );
clog << kLogInfo << ".";
this->btHome->Listen(GpioEdge::Both, GpioPullup::PullUp, false);
clog << kLogInfo << "." << endl;
// Setup theo ther butons (which are active-low)
clog << kLogInfo << "Connecting to other buttons";
this->btOther = new GpioPins(BUTTONS_REGULAR);
clog << kLogInfo << ".";
onBtOtherConnection = this->btOther->onChange.connect( boost::bind( &HidServer::onBtChange, this, _1, _2, _3) );
clog << kLogInfo << ".";
this->btOther->Listen(GpioEdge::Both, GpioPullup::PullUp, true);
clog << kLogInfo << "." << endl;
// setup the LED
this->rgbLed = new RgbLed(LED_RED,LED_GREEN,LED_BLUE);
}
catch(std::exception x)
catch(const std::exception& x)
{
clog << kLogCrit << "Fatal error during hardware initialization: " << x.what() << endl << "Quitting now... " << endl;
niam(0);
clog << kLogCrit << endl << "Fatal error during hardware initialization: " << x.what() << endl << "Quitting now... " << endl;
exit(-1);
}
clog << kLogInfo << "Initialization complete, listening to HID requests." << endl;
@ -93,127 +129,27 @@ HidServer::~HidServer()
{
ClearColor();
onInterruptErrorConnection.disconnect();
onInterruptConnection.disconnect();
delete intpin; intpin = NULL;
delete mcp; mcp = NULL;
delete btnTimer; btnTimer = NULL;
onBtHomeConnection.disconnect();
onBtOtherConnection.disconnect();
onShortPressConnection.disconnect();
onLongPressConnection.disconnect();
onValidatePressConnection.disconnect();
delete this->btnTimer; this->btnTimer = NULL;
delete this->btOther;
delete this->btHome;
delete this->rgbLed;
clog << kLogInfo << "Stopping normally" << endl;
}
void HidServer::initHardware(void)
{
HWConfig hwConfig;
unsigned short value;
clog << kLogInfo << "Opening GPIO pin " << GPIO_INT_PIN << " for interrupt listening" << endl;
// open pin
intpin = new GpioPin( GPIO_INT_PIN, // Pin number
kDirectionIn, // Data direction
kEdgeFalling); // Interrupt edge
// **** Initialize the MCP23017
// Hardware config
hwConfig.DISSLEW = false; // Leave slew rate control enabled
hwConfig.INT_MIRROR = true; // Interconnect I/O pins
hwConfig.INT_ODR = false; // Interrupt is not an open drain
hwConfig.INT_POL = false; // Interrupt is Active-Low
clog << kLogInfo << "Opening MCP23017 IO expander on I2C address " << MCP23017_ADR << endl;
try
{
// Initialize chip system
mcp = new Mcp23017( MCP23017_ADR, // adr
0x0fff, // iodir
0x0fff, // ipol
0x0fff, // pullup
hwConfig, // Hardware Config
true); // Swap A/B
try
{
clog << kLogInfo << "Enabling interrupts on IO Expander" << endl;
// Configure interrupt
mcp->IntConfig( 0x0000, // defval
0x0000, // intcon
0x07ff); // int enable
clog << kLogInfo << "Starting interrupt listener on GPIO pin " << GPIO_INT_PIN << endl;
// Initialize interrupts
onInterruptErrorConnection.disconnect();
onInterruptConnection.disconnect();
onInterruptErrorConnection = intpin->onThreadError.connect(boost::bind(&HidServer::onInterruptError, this, _1, _2));
onInterruptConnection = intpin->onInterrupt.connect(boost::bind(&HidServer::onInterrupt, this, _1, _2, _3));
// Start interrupt event on interrupt pin
intpin->InterruptStart();
clog << kLogInfo << "Performing initial read of IO Expander values to clear any pending interrupts and initialize Jack state" << endl;
// Read initial value to clear any current interrupts
value = mcp->getValue();
// Pass the initial mini-jack state to any listeners
if(value && SENSE_MINIJACK)
JackIn();
else
JackOut();
// Prepare the R/G/B pins for possible PWM usage
mcp->setPwmState(PIN_RED,TRUE);
mcp->setPwmState(PIN_GREEN,TRUE);
mcp->setPwmState(PIN_BLUE,TRUE);
}
catch(OperationFailedException x)
{
delete mcp; mcp = NULL;
throw x;
}
}
catch(OperationFailedException x)
{
delete intpin; intpin = NULL;
throw x;
}
}
void HidServer::SetColor(const uint8_t& r, const uint8_t& g, const uint8_t& b)
{
clog << kLogDebug << "Got color request: RGB("<< (int)r << "," << (int)g << "," << (int)b << ") - ";
// Check if we can do solid colors (no need for PWM)
if( (r == 0 || r==255) &&
(g == 0 || g==255) &&
(b == 0 || b==255))
{
// if so, then 0 is off and non-zero is on
clog << "Using ON/OFF to produce color" << endl;
// but first disable pwm
mcp->PwmStop(); // waits for completion
mcp->setPin(PIN_RED, (bool)r);
mcp->setPin(PIN_GREEN, (bool)g);
mcp->setPin(PIN_BLUE, (bool)b);
}
else
{
clog << "Using PWM to produce color" << endl;
// we need PWM, so enable it.
mcp->PwmStart();
mcp->setPwmLedValue(PIN_RED, r);
mcp->setPwmLedValue(PIN_GREEN, g);
mcp->setPwmLedValue(PIN_BLUE, b);
}
clog << kLogDebug << "Got color request: RGB("<< (int)r << "," << (int)g << "," << (int)b << ") - " << endl;
this->ThreadStop();
this->rgbLed->SetColor(r,g,b);
}
void HidServer::PulseColor(const uint8_t& r, const uint8_t& g, const uint8_t& b, const double& interval)
@ -228,33 +164,37 @@ void HidServer::PulseColor(const uint8_t& r, const uint8_t& g, const uint8_t& b,
clog << kLogDebug << "Got pulse color request: RGB("<< (int)r << "," << (int)g << "," << (int)b << ")" << endl;
pulseTime = (int32_t)((interval * 1000000) / (2 * PULSE_STEPS));
// Default PULSE_STEPS is 32, reduce accordingly when interval is below 2 seconds
// with 32 pulse steps, this results in min 2.0 / (2*32) = 31 ms per color
pulseSteps = PULSE_STEPS;
if(interval > 2.0)
{
pulseSteps = (int32_t)(interval * pulseSteps);
}
pulseTime = (int32_t)((interval * 1000000) / (2 * pulseSteps));
// Start pulse thread and PWM system
ThreadStart();
mcp->PwmStart();
}
// contains the pulse loop
// contains the pulse loop (note: faster pulsing does use more cpu)
void HidServer::ThreadLoop()
{
uint8_t myR, myG, myB;
int32_t factor = pulseI*100;
myR = (pulseR * factor)/(100 * PULSE_STEPS);
myG = (pulseG * factor)/(100 * PULSE_STEPS);
myB = (pulseB * factor)/(100 * PULSE_STEPS);
mcp->setPwmLedValue(PIN_RED, myR);
mcp->setPwmLedValue(PIN_GREEN, myG);
mcp->setPwmLedValue(PIN_BLUE, myB);
myR = (pulseR * factor)/(100 * pulseSteps);
myG = (pulseG * factor)/(100 * pulseSteps);
myB = (pulseB * factor)/(100 * pulseSteps);
this->rgbLed->SetColor(myR,myG,myB);
if(pulseDirUp)
{
pulseI++;
if(pulseI>= PULSE_STEPS)
if(pulseI>= pulseSteps)
pulseDirUp = false;
}
else
@ -270,216 +210,50 @@ void HidServer::ThreadLoop()
void HidServer::ClearColor()
{
clog << kLogDebug << "Got request to clear colors" << endl;
clog << kLogDebug << "Clearing color" << endl;
// Stop pulse thread
ThreadStop();
// Disable PWM and turn all colors off
mcp->PwmStop(); // waits for completion
mcp->setPin(PIN_RED, false);
mcp->setPin(PIN_GREEN, false);
mcp->setPin(PIN_BLUE, false);
this->rgbLed->SetColor(0,0,0);
}
bool HidServer::JackState()
void HidServer::onBtChange(uint8_t gpio, GpioEdge edge, bool level)
{
uint16_t value;
try
{
value = mcp->getValue();
if(edge == GpioEdge::Rising){
this->btnTimer->RegisterPress((uint8_t)gpio);
// clog << kLogDebug << " (Button down : " << getBtnName((uint8_t)gpio) << ")" << endl;
}
catch(OperationFailedException x)
else
{
// Log the exception
clog << kLogError << "Error while retrieving Jack State: " << x.what() << endl;
// Return false on error
return false;
this->btnTimer->RegisterRelease((uint8_t)gpio);
// clog << kLogDebug << " (Button up : " << getBtnName((uint8_t)gpio) << ")" << endl;
}
// Pass the current mini-jack state to any listeners
if(value && SENSE_MINIJACK)
return true;
else
return false;
}
bool HidServer::onValidatePress(uint16_t keycode)
bool HidServer::onValidatePress(uint8_t gpio)
{
// Check if the key is still pressed before sending out a long press
if(mcp->getValue() & keycode)
return true;
else
return false;
return true; //((bool)gpioRead(gpio)) == BUTTONS_ACTIVE.at(gpio);
}
void HidServer::onShortPress(uint16_t keycode)
void HidServer::onShortPress(uint8_t gpio)
{
clog << kLogDebug << "SHORT PRESS : " << getBtnName(keycode) << endl;
ButtonPress(getBtnName(keycode));
clog << kLogDebug << "SHORT PRESS : " << getBtnName(gpio) << endl;
ButtonPress(getBtnName(gpio));
}
void HidServer::onLongPress(uint16_t keycode)
void HidServer::onLongPress(uint8_t gpio)
{
clog << kLogDebug << "LONG PRESS : " << getBtnName(keycode) << endl;
ButtonLongPress(getBtnName(keycode));
clog << kLogDebug << "LONG PRESS : " << getBtnName(gpio) << endl;
ButtonLongPress(getBtnName(gpio));
}
void HidServer::keyUp(uint16_t keycode)
// Convert gpio into a string for ease of use in e.g. python or other dbus bindings
std::string HidServer::getBtnName(uint8_t gpio)
{
if(keycode == SENSE_MINIJACK)
{
JackOut();
}
else
{
ButtonUp(getBtnName(keycode));
btnTimer->RegisterRelease(keycode);
}
}
void HidServer::keyDown(uint16_t keycode)
{
if(keycode == SENSE_MINIJACK)
{
JackIn();
}
else
{
ButtonDown(getBtnName(keycode));
btnTimer->RegisterPress(keycode);
}
}
// Convert binary keycode into a string for ease of use in e.g. python or other dbus bindings
std::string HidServer::getBtnName(uint16_t keycode)
{
switch(keycode)
{
case BTN_ESCAPE :
return "BTN_ESCAPE";
case BTN_FAVORITES :
return "BTN_FAVORITES";
case BTN_RECENT :
return "BTN_RECENT";
case BTN_REWIND :
return "BTN_REWIND";
case BTN_PLAYPAUSE :
return "BTN_PLAYPAUSE";
case BTN_FASTFORWARD :
return "BTN_FASTFORWARD";
case BTN_CANCEL :
return "BTN_CANCEL";
case BTN_OK :
return "BTN_OK";
case BTN_UP :
return "BTN_UP";
case BTN_DOWN :
return "BTN_DOWN";
case SENSE_MINIJACK :
return "SENSE_MINIJACK";
default:
return "BTN_NONE";
}
}
void HidServer::onInterrupt(GpioPin * sender, GpioEdge edge, bool pinval)
{
uint16_t intf,intcap, keycode;
uint8_t i, bitcount;
if(edge != kEdgeFalling) // Only continue on falling edge
return;
intf = mcp->getIntF();
intcap = mcp->getIntCap();
// Check if we are in a noise timeout, and cancel if so.
if(noiseTimeout !=0 && noiseTimeout > now_ms())
return;
else
noiseTimeout = 0;
// Check if only one bit is set in the interrupt cap: This indicated normal operation.
// If more than one bit is set, we should assume noise
bitcount = 0;
for(i=0;i<16;i++)
{
if(intf & (1 << i)) // Check if this key is set
bitcount++;
}
//printf("Interrupt!\n INTF : 0x%04x\n INTCAP : 0x%04x\n KEYCODE: 0x%04x\n\n", intf, intcap, keycode);
if(bitcount > 0 && bitcount <= 3 ) // anything above 3 key interrupts at the same time is considered noise
{
for(i=0; i < 16; i++)
{
if(intf & (1 << i)) // check if this keycode is set
{
keycode = (1 << i);
if(intcap & keycode)
{
keyDown(keycode);
if(keycode == SENSE_MINIJACK)
clog << kLogDebug << "Jacked IN " << endl;
else
clog << kLogDebug << "Button down : " << getBtnName(keycode) << endl;
}
else
{
keyUp(keycode);
if(keycode == SENSE_MINIJACK)
clog << kLogDebug << "Jacked OUT " << endl;
else
clog << kLogDebug << "Button up : " << getBtnName(keycode) << endl;
}
}
}
}
else
{
clog << kLogDebug << "!!! Input Noise !!! (Timeout: " << NOISE_TIMEOUT_MS << "ms)" << endl;
noiseTimeout = now_ms() + NOISE_TIMEOUT_MS;
}
}
void HidServer::onInterruptError(Thread* sender, ThreadException x)
{
// When this function is called, the interrupt thread will have stopped
clog << kLogErr << "Error in interrupt listener: " << x.what() << endl;
clog << kLogErr << "Attempting to restart interrupt listener... " << endl;
try
{
delete mcp; mcp = NULL;
delete intpin; intpin = NULL;
initHardware(); // Attempt to re-init the chip system. Quit on failure
clog << kLogInfo << "Succesfully restarted interrupt listener" << endl;
}
catch(std::exception x2)
{
clog << kLogCrit << "Fatal error while attempting to restart interrupt listener : " << x2.what() << endl << "Quitting now... " << endl;
niam(0);
}
}
// get current time in milliseconds
int64_t now_ms(void)
{
struct timespec now;
clock_gettime(CLOCK_MONOTIC, &now);
return ((int64_t)now.tv_sec)*1000LL + (now.tv_nsec/1000000);
return BUTTONS.at(gpio);
}
@ -492,21 +266,26 @@ void niam(int sig)
int main()
{
signal(SIGTERM, niam);
signal(SIGINT, niam);
signal(SIGTERM, niam);
signal(SIGINT, niam);
signal(2,niam);
DBus::default_dispatcher = &dispatcher;
DBus::default_dispatcher = &dispatcher;
// Initialize clog to be redirected to syslog key "mediacore.hid.server"
Log::Init("mediacore.hid");
// Initialize clog to be redirected to syslog key "mediacore.hid.server"
// Log::Init("mediacore.hid");
DBus::Connection conn = DBus::Connection::SystemBus();
conn.request_name(HID_SERVER_NAME);
DBus::Connection conn = DBus::Connection::SystemBus();
conn.request_name(HID_SERVER_NAME);
HidServer server(conn);
//gpioCfgClock(10,1,0);
//gpioInitialise();
HidServer server(conn);
dispatcher.enter();
dispatcher.enter();
return 0;
//gpioTerminate();
return 0;
}

View File

@ -2,15 +2,14 @@
#define __MC_HID_SERVER_HPP
#include <stdint.h>
#include <dbus-c++/dbus.h>
//#include <dbus-c++-1/dbus-c++/dbus.h> // Done thhrough <mc-hid-server-glue.hpp>
#include <boost/signals2.hpp>
#include "mc-hid-server-glue.hpp"
#include "gpio/gpio.hpp"
#include "mcp23017/mcp23017.hpp"
#include "thread/thread.hpp"
#include "buttontimer/buttontimer.hpp"
#include "gpio/gpio.hpp"
class HidServer
: public nl::miqra::MediaCore::Hid_adaptor, // << This will be generated by the makefile using dbusxx-xml2cpp on mc-hid-introspect.xml
@ -26,36 +25,36 @@ public:
virtual void SetColor(const uint8_t& r, const uint8_t& g, const uint8_t& b);
virtual void PulseColor(const uint8_t& r, const uint8_t& g, const uint8_t& b, const double& interval);
virtual void ClearColor();
virtual bool JackState();
void onInterrupt(GpioPin * sender, GpioEdge edge, bool pinval);
void onInterruptError(Thread * sender, ThreadException x);
bool onValidatePress(uint16_t keycode);
void onShortPress(uint16_t keycode);
void onLongPress(uint16_t keycode);
bool onValidatePress(uint8_t gpio);
void onShortPress(uint8_t gpio);
void onLongPress(uint8_t gpio);
void onBtChange(uint8_t gpio, GpioEdge edge, bool level);
protected:
virtual void ThreadLoop();
//void onChange(int gpio, int level, uint32_t tick);
private:
Mcp23017 *mcp;
GpioPin *intpin;
ButtonTimer *btnTimer;
RgbLed *rgbLed;
GpioPin *btHome;
GpioPin *btCancel;
GpioPins *btOther;
int64_t noiseTimeout;
uint8_t pulseR, pulseG, pulseB,pulseI,pulseMin;
uint8_t pulseR, pulseG, pulseB, pulseI,pulseMin;
int32_t pulseTime;
int32_t pulseSteps;
bool pulseDirUp;
void initHardware(void);
void keyUp(uint16_t keycode);
void keyDown(uint16_t keycode);
std::string getBtnName(uint16_t keycode);
boost::signals2::connection onInterruptConnection;
std::string getBtnName(uint8_t gpio);
//static void onChangeEx(int gpio, int level, uint32_t tick, void *user);
boost::signals2::connection onBtHomeConnection;
boost::signals2::connection onBtOtherConnection;
boost::signals2::connection onInterruptErrorConnection;
boost::signals2::connection onShortPressConnection;
boost::signals2::connection onLongPressConnection;

View File

@ -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();
}

View File

@ -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

View File

@ -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;
}

View File

@ -142,4 +142,5 @@ void * thread_threadStarter(void * obj)
{
Thread * wt = (Thread*)obj;
wt->ThreadStarter();
return (void*) nullptr;
}

View File

@ -40,7 +40,7 @@ class Thread
pthread_t wthread;
pthread_mutex_t mutex;
pthread_mutexattr_t mutexAttr;
bool running;
bool running;
void ThreadStarter();
};

View File

@ -1,2 +1,2 @@
PACKAGE="mediacore-hid"
VERSION="1.0.0"
VERSION="4.0.0"