diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3ee190c --- /dev/null +++ b/Makefile @@ -0,0 +1,100 @@ +############################################################################# +# +# Makefile for MySensors +# +# Description: +# ------------ +# use make all and make install to install the gateway +# + +CONFIG_FILE=Makefile.inc + +include $(CONFIG_FILE) + +GATEWAY_BIN=mysGateway +GATEWAY=examples_linux/$(GATEWAY_BIN) +GATEWAY_C_SOURCES=$(wildcard drivers/Linux/*.c) +GATEWAY_CPP_SOURCES=$(wildcard drivers/Linux/*.cpp) examples_linux/mysGateway.cpp +OBJECTS=$(patsubst %.c,%.o,$(GATEWAY_C_SOURCES)) $(patsubst %.cpp,%.o,$(GATEWAY_CPP_SOURCES)) + +DEPS+=$(patsubst %.c,%.d,$(GATEWAY_C_SOURCES)) $(patsubst %.cpp,%.d,$(GATEWAY_CPP_SOURCES)) + +CINCLUDE=-I. -I./core -I./drivers/Linux + +ifeq ($(SOC),$(filter $(SOC),BCM2835 BCM2836)) +RPI_C_SOURCES=$(wildcard drivers/RPi/*.c) +RPI_CPP_SOURCES=$(wildcard drivers/RPi/*.cpp) +OBJECTS+=$(patsubst %.c,%.o,$(RPI_C_SOURCES)) $(patsubst %.cpp,%.o,$(RPI_CPP_SOURCES)) + +DEPS+=$(patsubst %.c,%.d,$(RPI_C_SOURCES)) $(patsubst %.cpp,%.d,$(RPI_CPP_SOURCES)) + +CINCLUDE+=-I./drivers/RPi +endif + +.PHONY: all gateway cleanconfig clean install uninstall force + +all: $(GATEWAY) + +# Gateway Build +$(GATEWAY): $(OBJECTS) + $(CXX) $(LDFLAGS) -o $@ $(OBJECTS) + +# Include all .d files +-include $(DEPS) + +%.o: %.cpp + $(CXX) $(CXXFLAGS) $(CINCLUDE) -MMD -c -o $@ $< + +%.o: %.c + $(CC) $(CFLAGS) $(CINCLUDE) -MMD -c -o $@ $< + +# clear configuration files +cleanconfig: + @echo "[Cleaning configuration]" + rm -rf $(CONFIG_FILE) + +# clear build files +clean: + @echo "[Cleaning]" + rm -rf build $(OBJECTS) $(GATEWAY) $(DEPS) + +$(CONFIG_FILE): + @echo "[Running configure]" + @./configure --no-clean + +install: all install-gateway install-initscripts + +install-gateway: + @echo "Installing $(GATEWAY) to $(GATEWAY_DIR)" + @install -m 0755 $(GATEWAY) $(GATEWAY_DIR) + +install-initscripts: +ifeq ($(INIT_SYSTEM), systemd) + install -m0644 initscripts/mysgateway.systemd /etc/systemd/system/mysgateway.service + @sed -i -e "s|%gateway_dir%|${GATEWAY_DIR}|g" /etc/systemd/system/mysgateway.service + systemctl daemon-reload + @echo "MySensors gateway has been installed, to add to the boot run:" + @echo " sudo systemctl enable mysgateway.service" + @echo "To start the gateway run:" + @echo " sudo systemctl start mysgateway.service" +else ifeq ($(INIT_SYSTEM), sysvinit) + install -m0755 initscripts/mysgateway.sysvinit /etc/init.d/mysgateway + @sed -i -e "s|%gateway_dir%|${GATEWAY_DIR}|g" /etc/init.d/mysgateway + @echo "MySensors gateway has been installed, to add to the boot run:" + @echo " sudo update-rc.d mysgateway defaults" + @echo "To start the gateway run:" + @echo " sudo service mysgateway start" +endif + +uninstall: +ifeq ($(INIT_SYSTEM), systemd) + @echo "Stopping daemon mysgateway (ignore errors)" + -@systemctl stop mysgateway.service + @echo "removing files" + rm /etc/systemd/system/mysgateway.service $(GATEWAY_DIR)/$(GATEWAY_BIN) +else ifeq ($(INIT_SYSTEM), sysvinit) + @echo "Stopping daemon mysgateway (ignore errors)" + -@service mysgateway stop + @echo "removing files" + rm /etc/init.d/mysgateway $(GATEWAY_DIR)/$(GATEWAY_BIN) +endif diff --git a/MyConfig.h b/MyConfig.h index dd5f506..94ce969 100644 --- a/MyConfig.h +++ b/MyConfig.h @@ -415,6 +415,8 @@ #define MY_RF24_CE_PIN 4 #elif defined(ARDUINO_ARCH_SAMD) #define MY_RF24_CE_PIN 27 + #elif defined(LINUX_ARCH_RASPBERRYPI) + #define MY_RF24_CE_PIN 22 #else #define MY_RF24_CE_PIN 9 #endif @@ -429,6 +431,8 @@ #define MY_RF24_CS_PIN 15 #elif defined(ARDUINO_ARCH_SAMD) #define MY_RF24_CS_PIN 3 + #elif defined(LINUX_ARCH_RASPBERRYPI) + #define MY_RF24_CS_PIN 24 #else #define MY_RF24_CS_PIN 10 #endif @@ -610,6 +614,7 @@ //#define MY_GATEWAY_W5100 //#define MY_GATEWAY_ENC28J60 //#define MY_GATEWAY_ESP8266 +//#define MY_GATEWAY_LINUX /** * @def MY_PORT @@ -727,7 +732,49 @@ #define MY_ESP8266_SERIAL_MODE SERIAL_FULL #endif -/** @}*/ // ESP8266 Defaults +/************************************** +* Linux Settings +***************************************/ + +/** + * @def MY_LINUX_SERIAL_PORT + * @brief Serial device port + */ +#ifndef MY_LINUX_SERIAL_PORT +#define MY_LINUX_SERIAL_PORT "/dev/ttyACM0" +#endif + +/** + * @def MY_IS_SERIAL_PTY + * @brief Set serial as a pseudo terminal. + * + * Enable this if you need to connect to a controller running on the same device. + */ +//#define MY_IS_SERIAL_PTY + +/** + * @def MY_LINUX_SERIAL_PTY + * @brief Symlink name for the PTY device. + */ +#ifndef MY_LINUX_SERIAL_PTY +#define MY_LINUX_SERIAL_PTY "/dev/ttyMySensorsGateway" +#endif + +/** + * @def MY_LINUX_SERIAL_GROUPNAME + * @brief Grant access to the specified system group for the serial device. + */ +//#define MY_LINUX_SERIAL_GROUPNAME "tty" + +/** + * @def MY_LINUX_CONFIG_FILE + * @brief Set the filepath for the gateway config file + * + * For now the configuration file is only used to store the emulated eeprom state + */ +#ifndef MY_LINUX_CONFIG_FILE +#define MY_LINUX_CONFIG_FILE "/etc/mysensors.dat" +#endif #endif // MyConfig_h @@ -747,4 +794,5 @@ #define MY_RX_MESSAGE_BUFFER_FEATURE #define MY_RX_MESSAGE_BUFFER_SIZE #define MY_NODE_LOCK_FEATURE +#define MY_LINUX_SERIAL_GROUPNAME #endif diff --git a/MySensors.h b/MySensors.h index 8d5ad27..4eeff85 100644 --- a/MySensors.h +++ b/MySensors.h @@ -40,7 +40,7 @@ * @def MY_NODE_TYPE * @brief Contain a string describing the class of sketch/node (gateway/repeater/sensor). */ -#if defined(MY_GATEWAY_SERIAL) || defined(MY_GATEWAY_W5100) || defined(MY_GATEWAY_ENC28J60) || defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_MQTT_CLIENT) +#if defined(MY_GATEWAY_SERIAL) || defined(MY_GATEWAY_W5100) || defined(MY_GATEWAY_ENC28J60) || defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX) || defined(MY_GATEWAY_MQTT_CLIENT) #define MY_GATEWAY_FEATURE #define MY_IS_GATEWAY (true) #define MY_NODE_TYPE "GW" @@ -69,6 +69,24 @@ #include "core/MyHwATMega328.cpp" #elif defined(ARDUINO_ARCH_SAMD) #include "core/MyHwSAMD.cpp" +#elif defined(__linux__) + // Remove PSTR macros from debug prints + #undef PSTR + #define PSTR(x) (x) + //#undef F + //#define F(x) (x) + #define PROGMEM + #define vsnprintf_P(...) vsnprintf( __VA_ARGS__ ) + #define snprintf_P(...) snprintf( __VA_ARGS__ ) + #define memcpy_P memcpy + #define pgm_read_dword(x) (*x) + #define pgm_read_byte_near(x) (*x) + #include "core/MyHwLinuxGeneric.cpp" + // Ugly hack + #ifdef LINUX_ARCH_RASPBERRYPI + #undef hwDigitalWrite + #define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value)) + #endif #endif // LEDS @@ -154,7 +172,7 @@ #define MY_REPEATER_FEATURE #endif // GATEWAY - COMMON FUNCTIONS - // We only support MQTT Client using W5100 and ESP8266 at the moment + // We support MQTT Client using W5100, ESP8266 and Linux #if !defined(MY_GATEWAY_CLIENT_MODE) #error You must specify MY_CONTROLLER_IP_ADDRESS or MY_CONTROLLER_URL_ADDRESS #endif @@ -165,18 +183,25 @@ #if !defined(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) #error You must specify a topic subscribe prefix MY_MQTT_SUBSCRIBE_TOPIC_PREFIX for this MQTT client #endif - #if !defined(MY_MQTT_CLIENT_ID) + + #if !defined(MY_MQTT_CLIENT_ID) #error You must define a unique MY_MQTT_CLIENT_ID for this MQTT client #endif - #include "drivers/PubSubClient/PubSubClient.cpp" #include "core/MyGatewayTransport.cpp" + #include "core/MyProtocolMySensors.cpp" + + #if defined(MY_GATEWAY_LINUX) + #include "drivers/Linux/EthernetClient.h" + #include "drivers/Linux/EthernetServer.h" + #include "drivers/Linux/IPAddress.h" + #endif + #include "drivers/PubSubClient/PubSubClient.cpp" #include "core/MyGatewayTransportMQTTClient.cpp" #elif defined(MY_GATEWAY_FEATURE) // GATEWAY - COMMON FUNCTIONS #include "core/MyGatewayTransport.cpp" - // We currently only support one protocol at the moment, enable it. #include "core/MyProtocolMySensors.cpp" // GATEWAY - CONFIGURATION @@ -190,6 +215,12 @@ #if defined(MY_GATEWAY_ESP8266) // GATEWAY - ESP8266 #include "core/MyGatewayTransportEthernet.cpp" + #elif defined(MY_GATEWAY_LINUX) + // GATEWAY - Generic Linux + #include "drivers/Linux/EthernetClient.h" + #include "drivers/Linux/EthernetServer.h" + #include "drivers/Linux/IPAddress.h" + #include "core/MyGatewayTransportEthernet.cpp" #elif defined(MY_GATEWAY_W5100) // GATEWAY - W5100 #include "core/MyGatewayTransportEthernet.cpp" @@ -216,6 +247,10 @@ #include "drivers/AVR/DigitalIO/DigitalIO.h" #endif + #if defined(MY_RADIO_NRF24) && defined(__linux__) && !defined(LINUX_ARCH_RASPBERRYPI) + #error No support for nRF24 radio on this platform + #endif + #include "core/MyTransport.cpp" #if (defined(MY_RADIO_NRF24) && defined(MY_RADIO_RFM69)) || (defined(MY_RADIO_NRF24) && defined(MY_RS485)) || (defined(MY_RADIO_RFM69) && defined(MY_RS485)) #error Only one forward link driver can be activated @@ -267,6 +302,8 @@ #if !defined(MY_CORE_ONLY) #if defined(ARDUINO_ARCH_ESP8266) #include "core/MyMainESP8266.cpp" + #elif defined(__linux__) + #include "core/MyMainLinux.cpp" #else #include "core/MyMainDefault.cpp" #endif diff --git a/configure b/configure new file mode 100755 index 0000000..570edde --- /dev/null +++ b/configure @@ -0,0 +1,377 @@ +#!/bin/bash +# This version is heavily based on the work of mz-fuzzy (https://github.com/mz-fuzzy) +# adapted to work with MySensors project. +# Original work: https://github.com/TMRh20/RF24/blob/master/configure + +function help { +cat < CPU defining/optimizing flags to be used. [configure autodetected] + --extra-cxxflags= Extra C flags passed to C/C++ compilation. [] + --extra-ldflags= Extra C flags passed to linking. [] + --cxx_compiler= C++ compiler [arm-linux-gnueabihf-g++][g++] + --no-clean Don't clean previous build artifacts. + +Installation options: + --prefix= Installation prefix path. [/usr/local] + --gateway-dir= Gateway files installation directory. [PREFIX/bin] + +MySensors options: + --my-debug=[enable|disable] Enables or disables MySensors core debugging. [enable] + --my-config-file= Config file path. [/etc/mysensors.dat] + --my-gateway=[none|ethernet|serial|mqtt] + Gateway type, set to none to disable gateway feature. [ethernet] + --my-controller-url-address= + Controller or MQTT broker url. + --my-controller-ip-address= + Controller or MQTT broker ip. + --my-port= The port to keep open on gateway mode. + If gateway is set to mqtt, it sets the broker port. + --my-serial-port= Serial port. [/dev/ttyACM0] + --my-serial-baudrate= Serial baud rate. [115200] + --my-serial-is-pty Set the serial port to be a pseudo terminal. Use this if you want + to connect to a controller running on the same device. + --my-serial-pty= Symlink name for the PTY device. [/dev/ttyMySensorsGateway] + --my-serial-groupname= + Grant access to the specified system group for the serial device. + --my-mqtt-client-id= MQTT client id. + --my-mqtt-publish-topic-prefix= + MQTT publish topic prefix. + --my-mqtt-subscribe-topic-prefix= + MQTT subscribe topic prefix. + --my-radio=[none|nrf24] Radio type, set to none to disable radio feature. [rf24] + --my-rf24-channel=<0-125> RF channel for the sensor net, 0-125. [76] + --my-rf24-pa-level=[RF24_PA_MAX|RF24_PA_LOW] + RF24 PA level. [RF24_PA_MAX] + --my-rf24-irq-pin= Pin number connected to nRF24L01 IRQ pin. + --my-rx-message-buffer-size= + Buffer size for incoming messages when using rf24 interrupts. [20] + --my-leds-err-pin= Error LED pin. + --my-leds-rx-pin= Receive LED pin. + --my-leds-tx-pin= Transmit LED pin. + --my-leds-blinking-inverse Inverse the blinking feature. + +EOF +} + +function die { + echo "[ERROR] $1" + exit $2 +} + +function detect_rpi_revision { + # get PI Revision from cpuinfo + local pirev=$(eval "cat /proc/cpuinfo 2>/dev/null | grep Revision | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$$//'") + echo ${pirev} +} + +function detect_machine { + local cpu=$(eval "uname -m 2>/dev/null") + local machine=$(eval "cat /sys/firmware/devicetree/base/model 2>/dev/null") + local hardware=$(eval "grep sunxi_platform /sys/class/sunxi_info/sys_info 2>/dev/null | sed 's/^.*: \(.*\)$/\1/'") + if [ -z "$hardware" ]; then + local hardware=$(eval "grep Hardware /proc/cpuinfo 2>/dev/null | sed 's/^.*: \(.*\)$/\1/'") + fi + local soc="unknown" + local tp="unknown" + local rev="unknown" + + if [ -z "$cpu" ]; then + cpu="unknown" + fi + + case $hardware in + BCM2708) + soc="BCM2835" + if [[ $machine == "Raspberry"* ]]; then + tp="RPi" + rev=($(detect_rpi_revision)) + fi + ;; + BCM2709) + soc="BCM2836" + if [[ $machine == "Raspberry"* ]]; then + rev=($(detect_rpi_revision)) + if [[ $rev == "a02082" || $rev == "a22082" ]]; then + tp="RPi3" + else + tp="Rpi2" + fi + fi + ;; + sun4i|Sun4iw1p1) + soc="A10" + ;; + sun5i|Sun4iw2p1) + soc="A13" + ;; + Sun4iw2p2) + soc="A12" + ;; + Sun4iw2p3) + soc="A10s" + ;; + sun6i|Sun8iw1p1) + soc="A31" + ;; + Sun8iw1p2) + soc="A31s" + ;; + sun7i|Sun8iw2p1) + soc="A20" + if [[ $machine == "Banana Pi"* ]]; then + tp="BananaPi" + elif [[ $machine == "Banana Pro"* ]]; then + tp="BananaPro" + fi + ;; + sun8i|Sun8iw7p1) + soc="H3" + ;; + Sun8iw3p1) + soc="A23" + ;; + Sun8iw5p1) + soc="A33" + ;; + Sun8iw6p1) + soc="A83t" + ;; + sun9i|Sun9iw1p1) + soc="A80" + ;; + Sun9iw1p2) + soc="A80t" + ;; + sun50i|Sun50iw1p1) + soc="A64" + ;; + 'Generic AM33XX'*) + soc="AM33XX" + ;; + *) + soc="unknown" + esac + echo "${soc} ${tp} ${cpu} ${rev}" +} + +function gcc_cpu_flags { + local soc=$1 + case $soc in + BCM2835) + flags="-march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -DLINUX_ARCH_RASPBERRYPI" + ;; + BCM2836) + flags="-march=armv7-a -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard -DLINUX_ARCH_RASPBERRYPI" + ;; + AM33XX) + flags="-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard" + ;; + A10) + flags="-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard" + ;; + A13) + flags="-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard" + ;; + A20) + flags="-march=armv7-a -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard" + ;; + H3) + flags="-march=armv8-a -mtune=cortex-a53 -mfpu=neon-vfpv4 -mfloat-abi=hard" + ;; + *) + flags="" + esac + echo ${flags} +} + +# Default values +debug=enable +gateway_type=ethernet +radio_type=nrf24 + +params="SOC CXXFLAGS LDFLAGS PREFIX CXX GATEWAY_DIR INIT_SYSTEM" + +for opt do + if [ "$opt" = "-h" ] || [ "$opt" = "--help" ]; then + help + exit 0 + fi + optarg="${opt#*=}" + case "$opt" in + --soc=*) + SOC="$optarg" + ;; + --cpu-flags=*) + CPUFLAGS="$optarg" + ;; + --extra-cxxflags=*) + CXXFLAGS="$optarg" + ;; + --extra-ldflags=*) + LDFLAGS="$optarg" + ;; + --cxx_compiler=*) + CXX="$optarg" + ;; + --no-clean*) + NO_CLEAN="1" + ;; + --prefix=*) + PREFIX="$optarg" + ;; + --gateway-dir=*) + GATEWAY_DIR="$optarg" + ;; + --my-debug=*) + debug=${optarg} + ;; + --my-gateway=*) + gateway_type=${optarg} + ;; + --my-config-file=*) + CXXFLAGS="-DMY_LINUX_CONFIG_FILE=\\\"${optarg}\\\" $CXXFLAGS" + ;; + --my-radio=*) + radio_type=${optarg} + ;; + --my-serial-port=*) + CXXFLAGS="-DMY_LINUX_SERIAL_PORT=\\\"${optarg}\\\" $CXXFLAGS" + ;; + --my-serial-baudrate=*) + CXXFLAGS="-DMY_BAUD_RATE=${optarg} $CXXFLAGS" + ;; + --my-serial-is-pty*) + CXXFLAGS="-DMY_IS_SERIAL_PTY $CXXFLAGS" + ;; + --my-serial-pty=*) + CXXFLAGS="-DMY_LINUX_SERIAL_PTY=\\\"${optarg}\\\" $CXXFLAGS" + ;; + --my-serial-groupname=*) + CXXFLAGS="-DMY_LINUX_SERIAL_GROUPNAME=\\\"${optarg}\\\" $CXXFLAGS" + ;; + --my-rf24-channel=*) + CXXFLAGS="-DMY_RF24_CHANNEL=${optarg} $CXXFLAGS" + ;; + --my-rf24-pa-level=*) + CXXFLAGS="-DMY_RF24_PA_LEVEL=${optarg} $CXXFLAGS" + ;; + --my-controller-url-address=*) + CXXFLAGS="-DMY_CONTROLLER_URL_ADDRESS=\\\"${optarg}\\\" $CXXFLAGS" + ;; + --my-controller-ip-address=*) + controller_ip=`echo ${optarg//./,}` + CXXFLAGS="-DMY_CONTROLLER_IP_ADDRESS=${controller_ip} $CXXFLAGS" + ;; + --my-port=*) + CXXFLAGS="-DMY_PORT=${optarg} $CXXFLAGS" + ;; + --my-mqtt-client-id=*) + CXXFLAGS="-DMY_MQTT_CLIENT_ID=\\\"${optarg}\\\" $CXXFLAGS" + ;; + --my-mqtt-publish-topic-prefix=*) + CXXFLAGS="-DMY_MQTT_PUBLISH_TOPIC_PREFIX=\\\"${optarg}\\\" $CXXFLAGS" + ;; + --my-mqtt-subscribe-topic-prefix=*) + CXXFLAGS="-DMY_MQTT_SUBSCRIBE_TOPIC_PREFIX=\\\"${optarg}\\\" $CXXFLAGS" + ;; + --my-rf24-irq-pin=*) + CXXFLAGS="-DMY_RX_MESSAGE_BUFFER_FEATURE -DMY_RF24_IRQ_PIN=${optarg} $CXXFLAGS" + ;; + --my-rx-message-buffer-size=*) + CXXFLAGS="-DMY_RX_MESSAGE_BUFFER_SIZE=${optarg} $CXXFLAGS" + ;; + --my-leds-err-pin=*) + CXXFLAGS="-DMY_DEFAULT_ERR_LED_PIN=${optarg} $CXXFLAGS" + ;; + --my-leds-rx-pin=*) + CXXFLAGS="-DMY_DEFAULT_RX_LED_PIN=${optarg} $CXXFLAGS" + ;; + --my-leds-tx-pin=*) + CXXFLAGS="-DMY_DEFAULT_TX_LED_PIN=${optarg} $CXXFLAGS" + ;; + --my-leds-blinking-inverse*) + CXXFLAGS="-DMY_WITH_LEDS_BLINKING_INVERSE $CXXFLAGS" + ;; + *) + echo "[WARNING] Unknown option detected:$opt, ignored" + ;; + esac +done + +PREFIX=${PREFIX:-/usr/local} +GATEWAY_DIR=${GATEWAY_DIR:-${PREFIX}/bin} +CXX=${CXX:-g++} + +if [ -z "${SOC}" ]; then + echo "[SECTION] Detecting target machine." + info=($(detect_machine)) + SOC=${info[0]} + TYPE=${info[1]} + CPU=${info[2]} + REV=${info[3]} + echo "[OK] machine detected: SoC=${SOC}, Type=${TYPE}, CPU=${CPU}, REV=${REV}." +fi + +if [ -z "${CPUFLAGS}" ]; then + CPUFLAGS=$(gcc_cpu_flags $SOC) +fi + +CXXFLAGS="$CPUFLAGS -Ofast -g -Wall -Wextra $CXXFLAGS" +CFLAGS="-Ofast -g -Wall -Wextra $CFLAGS" + +if [[ $TYPE == "RPi2" || $TYPE == "RPi3" || $REV == "0010" ]]; then + CXXFLAGS+="-D__RPI_BPLUS" +fi + +if [[ ${debug} == "enable" ]]; then + CXXFLAGS="-DMY_DEBUG $CXXFLAGS" +fi + +if [[ ${gateway_type} == "ethernet" ]]; then + CXXFLAGS="-DMY_GATEWAY_LINUX $CXXFLAGS" +elif [[ ${gateway_type} == "serial" ]]; then + CXXFLAGS="-DMY_GATEWAY_SERIAL $CXXFLAGS" +elif [[ ${gateway_type} == "mqtt" ]]; then + CXXFLAGS="-DMY_GATEWAY_LINUX -DMY_GATEWAY_MQTT_CLIENT $CXXFLAGS" +fi + +if [[ ${radio_type} == "nrf24" ]]; then + CXXFLAGS="-DMY_RADIO_NRF24 $CXXFLAGS" +fi + +LDFLAGS="-pthread $LDFLAGS" + +if [ -x /usr/bin/systemctl ] || [ -x /bin/systemctl ]; then + INIT_SYSTEM=systemd + echo "[OK] init system detected: systemd" +elif [ -f /etc/init.d/cron ] && [ ! -h /etc/init.d/cron ]; then + INIT_SYSTEM=sysvinit + echo "[OK] init system detected: sysvinit" +else + echo "[FAILED] unknown init system" +fi + +echo "[SECTION] Saving configuration." +echo -n "" > Makefile.inc +for param in ${params}; do + if [[ ${!param} ]]; then + echo "${param}=${!param}" >> Makefile.inc + fi +done + +if [ -z "${NO_CLEAN}" ]; then + echo "[SECTION] Cleaning previous builds." + make clean >/dev/null +fi + + +echo "[OK] Finished." diff --git a/core/MyGatewayTransportEthernet.cpp b/core/MyGatewayTransportEthernet.cpp index c27d501..692fbc7 100644 --- a/core/MyGatewayTransportEthernet.cpp +++ b/core/MyGatewayTransportEthernet.cpp @@ -22,7 +22,6 @@ // global variables extern MyMessage _msgTmp; - #if defined(MY_CONTROLLER_IP_ADDRESS) IPAddress _ethernetControllerIP(MY_CONTROLLER_IP_ADDRESS); #endif @@ -30,7 +29,7 @@ extern MyMessage _msgTmp; #if defined(MY_IP_ADDRESS) IPAddress _ethernetGatewayIP(MY_IP_ADDRESS); #endif -byte _ethernetGatewayMAC[] = { MY_MAC_ADDRESS }; +uint8_t _ethernetGatewayMAC[] = { MY_MAC_ADDRESS }; uint16_t _ethernetGatewayPort = MY_PORT; MyMessage _ethernetMsg; @@ -38,8 +37,8 @@ MyMessage _ethernetMsg; typedef struct { - char string[MY_GATEWAY_MAX_RECEIVE_LENGTH]; - uint8_t idx; + char string[MY_GATEWAY_MAX_RECEIVE_LENGTH]; + uint8_t idx; } inputBuffer; #if defined(MY_GATEWAY_ESP8266) @@ -52,27 +51,30 @@ typedef struct IPAddress _gatewayIp(MY_IP_GATEWAY_ADDRESS); IPAddress _subnetIp(MY_IP_SUBNET_ADDRESS); #endif - #if !defined(MY_GATEWAY_CLIENT_MODE) - static bool clientsConnected[MY_GATEWAY_MAX_CLIENTS]; - #endif #endif #if defined(MY_USE_UDP) EthernetUDP _ethernetServer; -#elif !defined(MY_GATEWAY_CLIENT_MODE) +#elif defined(MY_GATEWAY_LINUX) + EthernetServer _ethernetServer(_ethernetGatewayPort, MY_GATEWAY_MAX_CLIENTS); +#elif defined(MY_GATEWAY_CLIENT_MODE) + // Nothing to do here +#else EthernetServer _ethernetServer(_ethernetGatewayPort); #endif - -#if defined(MY_GATEWAY_ESP8266) && !defined(MY_GATEWAY_CLIENT_MODE) +#if defined(MY_GATEWAY_CLIENT_MODE) + static EthernetClient client = EthernetClient(); + static inputBuffer inputString; +#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX) static EthernetClient clients[MY_GATEWAY_MAX_CLIENTS]; + static bool clientsConnected[MY_GATEWAY_MAX_CLIENTS]; static inputBuffer inputString[MY_GATEWAY_MAX_CLIENTS]; #else static EthernetClient client = EthernetClient(); static inputBuffer inputString; #endif - #ifndef MY_IP_ADDRESS void gatewayTransportRenewIP(); #endif @@ -82,25 +84,23 @@ typedef struct #ifdef MY_W5100_SPI_EN void _w5100_spi_en(bool enable) { - if (enable) - { - // Pull up pin - pinMode(MY_W5100_SPI_EN, INPUT); - digitalWrite(MY_W5100_SPI_EN, HIGH); - } - else - { - // Ground pin - pinMode(MY_W5100_SPI_EN, OUTPUT); - digitalWrite(MY_W5100_SPI_EN, LOW); - } + if (enable) + { + // Pull up pin + pinMode(MY_W5100_SPI_EN, INPUT); + digitalWrite(MY_W5100_SPI_EN, HIGH); + } + else + { + // Ground pin + pinMode(MY_W5100_SPI_EN, OUTPUT); + digitalWrite(MY_W5100_SPI_EN, LOW); + } } #else #define _w5100_spi_en(x) #endif - - bool gatewayTransportInit() { _w5100_spi_en(true); #if defined(MY_GATEWAY_ESP8266) @@ -123,7 +123,8 @@ bool gatewayTransportInit() { MY_SERIALDEVICE.print(F("IP: ")); MY_SERIALDEVICE.println(WiFi.localIP()); #endif - + #elif defined(MY_GATEWAY_LINUX) + // Nothing to do here #else #ifdef MY_IP_ADDRESS Ethernet.begin(_ethernetGatewayMAC, _ethernetGatewayIP); @@ -145,33 +146,37 @@ bool gatewayTransportInit() { _ethernetServer.begin(_ethernetGatewayPort); #elif defined(MY_GATEWAY_CLIENT_MODE) #if defined(MY_CONTROLLER_URL_ADDRESS) - if (!client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) { + if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) { #else - if (!client.connect(_ethernetControllerIP, MY_PORT)) { + if (client.connect(_ethernetControllerIP, MY_PORT)) { #endif - client.stop(); - debug(PSTR("Eth: Failed to connect\n")); - } else { debug(PSTR("Eth: connect\n")); _w5100_spi_en(false); gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete.")); _w5100_spi_en(true); presentNode(); + } else { + client.stop(); + debug(PSTR("Eth: Failed to connect\n")); } #else - // we have to use pointers due to the constructor of EthernetServer - _ethernetServer.begin(); - #endif + #if defined(MY_GATEWAY_LINUX) && defined(MY_IP_ADDRESS) + _ethernetServer.begin(_ethernetGatewayIP); + #else + // we have to use pointers due to the constructor of EthernetServer + _ethernetServer.begin(); + #endif + #endif /* USE_UDP */ _w5100_spi_en(false); return true; } bool gatewayTransportSend(MyMessage &message) { - bool ret = true; + int nbytes = 0; char *_ethernetMsg = protocolFormat(message); - setIndication(INDICATION_GW_TX); + setIndication(INDICATION_GW_TX); _w5100_spi_en(true); #if defined(MY_GATEWAY_CLIENT_MODE) @@ -183,28 +188,28 @@ bool gatewayTransportSend(MyMessage &message) #endif _ethernetServer.write(_ethernetMsg, strlen(_ethernetMsg)); // returns 1 if the packet was sent successfully - ret = _ethernetServer.endPacket(); + nbytes = _ethernetServer.endPacket(); #else if (!client.connected()) { client.stop(); #if defined(MY_CONTROLLER_URL_ADDRESS) - if (!client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) { + if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) { #else - if (!client.connect(_ethernetControllerIP, MY_PORT)) { + if (client.connect(_ethernetControllerIP, MY_PORT)) { #endif - // connecting to the server failed! - debug(PSTR("Eth: Failed to connect\n")); - ret = false; - } else { debug(PSTR("Eth: connect\n")); _w5100_spi_en(false); gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete.")); _w5100_spi_en(true); presentNode(); + } else { + // connecting to the server failed! + debug(PSTR("Eth: Failed to connect\n")); + _w5100_spi_en(false); + return false; } - } else { - ret = client.write(_ethernetMsg, strlen(_ethernetMsg)) > 0; } + nbytes = client.write(_ethernetMsg, strlen(_ethernetMsg)); #endif #else // Send message to connected clients @@ -213,20 +218,18 @@ bool gatewayTransportSend(MyMessage &message) { if (clients[i] && clients[i].connected()) { - clients[i].write((uint8_t*)_ethernetMsg, strlen(_ethernetMsg)); + nbytes += clients[i].write((uint8_t*)_ethernetMsg, strlen(_ethernetMsg)); } } #else - ret = _ethernetServer.write(_ethernetMsg) > 0; + nbytes = _ethernetServer.write(_ethernetMsg); #endif #endif /* MY_GATEWAY_CLIENT_MODE */ _w5100_spi_en(false); - return ret; - + return (nbytes > 0); } - -#if defined(MY_GATEWAY_ESP8266) && !defined(MY_GATEWAY_CLIENT_MODE) +#if (defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX)) && !defined(MY_GATEWAY_CLIENT_MODE) bool _readFromClient(uint8_t i) { while (clients[i].connected() && clients[i].available()) { char inChar = clients[i].read(); @@ -296,7 +299,6 @@ bool gatewayTransportAvailable() #endif #ifdef MY_USE_UDP - int packet_size = _ethernetServer.parsePacket(); if (packet_size) { @@ -323,19 +325,19 @@ bool gatewayTransportAvailable() if (!client.connected()) { client.stop(); #if defined(MY_CONTROLLER_URL_ADDRESS) - if (!client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) { + if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) { #else - if (!client.connect(_ethernetControllerIP, MY_PORT)) { + if (client.connect(_ethernetControllerIP, MY_PORT)) { #endif - debug(PSTR("Eth: Failed to connect\n")); - _w5100_spi_en(false); - return false; - } else { debug(PSTR("Eth: connect\n")); _w5100_spi_en(false); gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set("Gateway startup complete.")); _w5100_spi_en(true); presentNode(); + } else { + debug(PSTR("Eth: Failed to connect\n")); + _w5100_spi_en(false); + return false; } } if (_readFromClient()) { @@ -344,7 +346,7 @@ bool gatewayTransportAvailable() return true; } #else - #if defined(MY_GATEWAY_ESP8266) + #if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX) // ESP8266: Go over list of clients and stop any that are no longer connected. // If the server has a new client connection it will be assigned to a free slot. bool allSlotsOccupied = true; @@ -377,7 +379,7 @@ bool gatewayTransportAvailable() // Loop over clients connect and read available data for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) { if (_readFromClient(i)) { - setIndication(INDICATION_GW_RX); + setIndication(INDICATION_GW_RX); _w5100_spi_en(false); return true; } @@ -403,7 +405,7 @@ bool gatewayTransportAvailable() client.stop(); } else { if (_readFromClient()) { - setIndication(INDICATION_GW_RX); + setIndication(INDICATION_GW_RX); _w5100_spi_en(false); return true; } @@ -421,8 +423,7 @@ MyMessage& gatewayTransportReceive() return _ethernetMsg; } - -#if !defined(MY_IP_ADDRESS) && !defined(MY_GATEWAY_ESP8266) +#if !defined(MY_IP_ADDRESS) && !defined(MY_GATEWAY_ESP8266) && !defined(MY_GATEWAY_LINUX) void gatewayTransportRenewIP() { /* renew/rebind IP address diff --git a/core/MyGatewayTransportMQTTClient.cpp b/core/MyGatewayTransportMQTTClient.cpp index a385382..90fa9f0 100644 --- a/core/MyGatewayTransportMQTTClient.cpp +++ b/core/MyGatewayTransportMQTTClient.cpp @@ -32,8 +32,10 @@ IPAddress _gatewayIp(MY_IP_GATEWAY_ADDRESS); IPAddress _subnetIp(MY_IP_SUBNET_ADDRESS); #endif +#elif defined(MY_GATEWAY_LINUX) + // Nothing to do here #else - byte _MQTT_clientMAC[] = { MY_MAC_ADDRESS }; + uint8_t _MQTT_clientMAC[] = { MY_MAC_ADDRESS }; #endif #if defined(MY_IP_ADDRESS) @@ -46,99 +48,20 @@ static bool _MQTT_connecting = true; static bool _MQTT_available = false; static MyMessage _MQTT_msg; -uint8_t protocolH2i(char c) { - uint8_t i = 0; - if (c <= '9') - i += c - '0'; - else if (c >= 'a') - i += c - 'a' + 10; - else - i += c - 'A' + 10; - return i; -} - - bool gatewayTransportSend(MyMessage &message) { if (!_MQTT_client.connected()) return false; setIndication(INDICATION_GW_TX); - char _fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH]; - char _convBuffer[MAX_PAYLOAD * 2 + 1]; - snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR(MY_MQTT_PUBLISH_TOPIC_PREFIX "/%d/%d/%d/%d/%d"), message.sender, message.sensor, mGetCommand(message), mGetAck(message), message.type); - debug(PSTR("Sending message on topic: %s\n"), _fmtBuffer); - return _MQTT_client.publish(_fmtBuffer, message.getString(_convBuffer)); + char *topic = protocolFormatMQTTTopic(MY_MQTT_PUBLISH_TOPIC_PREFIX, message); + debug(PSTR("Sending message on topic: %s\n"), topic); + return _MQTT_client.publish(topic, message.getString(_convBuffer)); } -void incomingMQTT(char* topic, byte* payload, unsigned int length) { +void incomingMQTT(char* topic, uint8_t* payload, unsigned int length) { debug(PSTR("Message arrived on topic: %s\n"), topic); - char *str, *p; - uint8_t i = 0; - uint8_t bvalue[MAX_PAYLOAD]; - uint8_t blen = 0; - uint8_t command = 0; - for (str = strtok_r(topic, "/", &p); str && i <= 5; - str = strtok_r(NULL, "/", &p)) { - switch (i) { - case 0: { - // Topic prefix - if (strcmp(str, MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) != 0) { - // Message not for us or malformed! - return; - } - break; - } - case 1: { - // Node id - _MQTT_msg.destination = atoi(str); - break; - } - case 2: { - // Sensor id - _MQTT_msg.sensor = atoi(str); - break; - } - case 3: { - // Command type - command = atoi(str); - mSetCommand(_MQTT_msg, command); - break; - } - case 4: { - // Ack flag - mSetRequestAck(_MQTT_msg, atoi(str) ? 1 : 0); - break; - } - case 5: { - // Sub type - _MQTT_msg.type = atoi(str); - // Add payload - if (command == C_STREAM) { - blen = 0; - uint8_t val; - while (*payload) { - val = protocolH2i(*payload++) << 4; - val += protocolH2i(*payload++); - bvalue[blen] = val; - blen++; - } - _MQTT_msg.set(bvalue, blen); - } - else { - char* ca; - ca = (char *)payload; - ca += length; - *ca = '\0'; - _MQTT_msg.set((const char*)payload); - } - _MQTT_available = true; - setIndication(INDICATION_GW_RX); - } - } - i++; - } + _MQTT_available = protocolMQTTParse(_MQTT_msg, topic, payload, length); } - bool reconnectMQTT() { debug(PSTR("Attempting MQTT connection...\n")); // Attempt to connect @@ -169,6 +92,10 @@ bool gatewayTransportConnect() { } MY_SERIALDEVICE.print("IP: "); MY_SERIALDEVICE.println(WiFi.localIP()); + #elif defined(MY_GATEWAY_LINUX) + #if defined(MY_IP_ADDRESS) + //TODO + #endif #else #ifdef MY_IP_ADDRESS Ethernet.begin(_MQTT_clientMAC, _MQTT_clientIp); @@ -216,10 +143,7 @@ bool gatewayTransportInit() { return true; } - - bool gatewayTransportAvailable() { - if (_MQTT_connecting) return false; @@ -240,6 +164,3 @@ MyMessage & gatewayTransportReceive() { _MQTT_available = false; return _MQTT_msg; } - - - diff --git a/core/MyGatewayTransportSerial.cpp b/core/MyGatewayTransportSerial.cpp index 74a118e..144ebe8 100644 --- a/core/MyGatewayTransportSerial.cpp +++ b/core/MyGatewayTransportSerial.cpp @@ -17,7 +17,6 @@ * version 2 as published by the Free Software Foundation. */ - #include "MyConfig.h" #include "MyProtocol.h" #include "MyGatewayTransport.h" @@ -31,7 +30,6 @@ char _serialInputString[MY_GATEWAY_MAX_RECEIVE_LENGTH]; // A buffer for incom int _serialInputPos; MyMessage _serialMsg; - bool gatewayTransportSend(MyMessage &message) { setIndication(INDICATION_GW_TX); MY_SERIALDEVICE.print(protocolFormat(message)); @@ -47,7 +45,6 @@ bool gatewayTransportInit() { return true; } - bool gatewayTransportAvailable() { while (MY_SERIALDEVICE.available()) { // get the new byte: diff --git a/core/MyHwATMega328.h b/core/MyHwATMega328.h index 0efd911..29352d4 100644 --- a/core/MyHwATMega328.h +++ b/core/MyHwATMega328.h @@ -59,7 +59,13 @@ do { \ // Define these as macros to save valuable space #define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value)) -#define hwInit() MY_SERIALDEVICE.begin(MY_BAUD_RATE) + +#if defined(MY_DISABLED_SERIAL) + #define hwInit() +#else + #define hwInit() MY_SERIALDEVICE.begin(MY_BAUD_RATE) +#endif + #define hwWatchdogReset() wdt_reset() #define hwReboot() wdt_enable(WDTO_15MS); while (1) #define hwMillis() millis() diff --git a/core/MyHwESP8266.h b/core/MyHwESP8266.h index 9d159c8..5489e62 100644 --- a/core/MyHwESP8266.h +++ b/core/MyHwESP8266.h @@ -35,7 +35,13 @@ // Define these as macros to save valuable space #define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value)) -#define hwInit() MY_SERIALDEVICE.begin(MY_BAUD_RATE, SERIAL_8N1, MY_ESP8266_SERIAL_MODE, 1); MY_SERIALDEVICE.setDebugOutput(true) + +#if defined(MY_DISABLED_SERIAL) + #define hwInit() +#else + #define hwInit() MY_SERIALDEVICE.begin(MY_BAUD_RATE, SERIAL_8N1, MY_ESP8266_SERIAL_MODE, 1); MY_SERIALDEVICE.setDebugOutput(true) +#endif + #define hwWatchdogReset() wdt_reset() #define hwReboot() ESP.restart() #define hwMillis() millis() diff --git a/core/MyHwLinuxGeneric.cpp b/core/MyHwLinuxGeneric.cpp new file mode 100644 index 0000000..76e202f --- /dev/null +++ b/core/MyHwLinuxGeneric.cpp @@ -0,0 +1,190 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Marcelo Aquino + * Copyright (C) 2016 Marcelo Aquino + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include "MyHwLinuxGeneric.h" + +#include +#include +#include +#include +#include +#include +#include "log.h" + +static const char* CONFIG_FILE = MY_LINUX_CONFIG_FILE; +static const size_t _length = 1024; // ATMega328 has 1024 bytes +static uint8_t _config[_length]; + +bool CheckConfigFile() +{ + struct stat fileInfo; + + if (stat(CONFIG_FILE, &fileInfo) != 0) { + //File does not exist. Create it. + debug("Config file %s does not exist, creating new config file.\n", CONFIG_FILE); + std::ofstream myFile(CONFIG_FILE, std::ios::out | std::ios::binary); + if (!myFile) { + debug("Unable to create config file %s.\n", CONFIG_FILE); + return false; + } + myFile.write((const char*)_config, _length); + myFile.close(); + } else if (fileInfo.st_size < 0 || (size_t)fileInfo.st_size != _length) { + debug("Config file %s is not the correct size of %i. Please remove the file and a new one will be created.\n", CONFIG_FILE, _length); + return false; + } else { + //Read config into local memory. + std::ifstream myFile(CONFIG_FILE, std::ios::in | std::ios::binary); + if (!myFile) { + debug("Unable to open config to file %s for reading.\n", CONFIG_FILE); + return false; + } + myFile.read((char*)_config, _length); + myFile.close(); + } + + return true; +} + +void hwInit() +{ + for (size_t i = 0; i < _length; i++) { + _config[i] = 0xFF; + } + + if (!CheckConfigFile()) { + exit(1); + } + + #ifdef MY_GATEWAY_SERIAL + MY_SERIALDEVICE.begin(MY_BAUD_RATE); + #ifdef MY_LINUX_SERIAL_GROUPNAME + if (!MY_SERIALDEVICE.setGroupPerm(MY_LINUX_SERIAL_GROUPNAME)) { + debug("Unable to change permission for serial port device.\n"); + exit(1); + } + #endif + #endif +} + +void hwReadConfigBlock(void* buf, void* addr, size_t length) +{ + unsigned long int offs = reinterpret_cast(addr); + + if (length && offs + length <= _length) { + memcpy(buf, _config+offs, length); + } +} + +void hwWriteConfigBlock(void* buf, void* addr, size_t length) +{ + unsigned long int offs = reinterpret_cast(addr); + + if (length && offs + length <= _length) { + memcpy(_config+offs, buf, length); + + std::ofstream myFile(CONFIG_FILE, std::ios::out | std::ios::in | std::ios::binary); + if (!myFile) { + debug("Unable to write config to file %s.\n", CONFIG_FILE); + return; + } + myFile.seekp(offs); + myFile.write((const char*)buf, length); + myFile.close(); + } +} + +uint8_t hwReadConfig(int adr) +{ + uint8_t value = 0xFF; + hwReadConfigBlock(&value, reinterpret_cast(adr), 1); + return value; +} + +void hwWriteConfig(int adr, uint8_t value) +{ + uint8_t curr = hwReadConfig(adr); + if (curr != value) { + hwWriteConfigBlock(&value, reinterpret_cast(adr), 1); + } +} + +void hwRandomNumberInit() +{ + randomSeed(time(NULL)); +} + +// Not supported! +int8_t hwSleep(unsigned long ms) +{ + (void)ms; + + return MY_SLEEP_NOT_POSSIBLE; +} + +// Not supported! +int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) +{ + (void)interrupt; + (void)mode; + (void)ms; + + return MY_SLEEP_NOT_POSSIBLE; +} + +// Not supported! +int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) +{ + (void)interrupt1; + (void)mode1; + (void)interrupt2; + (void)mode2; + (void)ms; + + return MY_SLEEP_NOT_POSSIBLE; +} + +uint16_t hwCPUVoltage() +{ + // TODO: Not supported! + return 0; +} + +uint16_t hwCPUFrequency() +{ + // TODO: Not supported! + return 0; +} + +uint16_t hwFreeMem() +{ + // TODO: Not supported! + return 0; +} + +#ifdef MY_DEBUG +void hwDebugPrint(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + mys_log_v(LOG_DEBUG, fmt, args); + va_end(args); +} +#endif diff --git a/core/MyHwLinuxGeneric.h b/core/MyHwLinuxGeneric.h new file mode 100644 index 0000000..067b93a --- /dev/null +++ b/core/MyHwLinuxGeneric.h @@ -0,0 +1,84 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Marcelo Aquino + * Copyright (C) 2016 Marcelo Aquino + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef MyHwLinuxGeneric_h +#define MyHwLinuxGeneric_h + +#include +#include +#include "MyHw.h" +#include "SerialPort.h" + +#ifdef MY_IS_SERIAL_PTY + SerialPort Serial = SerialPort(MY_LINUX_SERIAL_PTY, true); +#else + SerialPort Serial = SerialPort(MY_LINUX_SERIAL_PORT); +#endif + +#define MY_SERIALDEVICE Serial + +// Define these as macros (do nothing) +#define hwDigitalWrite(__pin, __value) +#define hwWatchdogReset() +#define hwReboot() +#define hwMillis() millis() + +void hwInit(); +void hwReadConfigBlock(void* buf, void* adr, size_t length); +void hwWriteConfigBlock(void* buf, void* adr, size_t length); +uint8_t hwReadConfig(int adr); +void hwWriteConfig(int adr, uint8_t value); +void hwRandomNumberInit(); + +#ifdef MY_RF24_IRQ_PIN + static pthread_mutex_t hw_mutex = PTHREAD_MUTEX_INITIALIZER; + + static __inline__ void __hwUnlock(const uint8_t *__s) { + pthread_mutex_unlock(&hw_mutex); + (void)__s; + } + + static __inline__ void __hwLock() { + pthread_mutex_lock(&hw_mutex); + } +#endif + +#if defined(DOXYGEN) + #define ATOMIC_BLOCK_CLEANUP +#elif defined(MY_RF24_IRQ_PIN) + #define ATOMIC_BLOCK_CLEANUP uint8_t __atomic_loop \ + __attribute__((__cleanup__( __hwUnlock ))) = 1 +#else + #define ATOMIC_BLOCK_CLEANUP +#endif /* DOXYGEN */ + +#if defined(DOXYGEN) + #define ATOMIC_BLOCK +#elif defined(MY_RF24_IRQ_PIN) + #define ATOMIC_BLOCK for ( ATOMIC_BLOCK_CLEANUP, __hwLock(); \ + __atomic_loop ; __atomic_loop = 0 ) +#else + #define ATOMIC_BLOCK +#endif /* DOXYGEN */ + +#ifndef DOXYGEN + #define MY_CRITICAL_SECTION ATOMIC_BLOCK +#endif /* DOXYGEN */ + +#endif diff --git a/core/MyMainLinux.cpp b/core/MyMainLinux.cpp new file mode 100644 index 0000000..56a2d47 --- /dev/null +++ b/core/MyMainLinux.cpp @@ -0,0 +1,135 @@ +// Initialize library and handle sketch functions like we want to + +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "MySensorsCore.h" + +void handle_sigint(int sig) +{ + if (sig == SIGINT) { + mys_log(LOG_NOTICE, "Received SIGINT\n\n"); + } else if (sig == SIGTERM) { + mys_log(LOG_NOTICE, "Received SIGTERM\n\n"); + } else { + return; + } + + #ifdef MY_RF24_IRQ_PIN + detachInterrupt(MY_RF24_IRQ_PIN); + #endif + + #if defined(MY_GATEWAY_SERIAL) + MY_SERIALDEVICE.end(); + #endif + + exit(0); +} + +static int daemonize(void) +{ + pid_t pid, sid; + + /* Fork off the parent process */ + pid = fork(); + if (pid < 0) { + mys_log(LOG_ERR, "fork: %s", strerror(errno)); + return -1; + } + /* If we got a good PID, then we can exit the parent process. */ + if (pid > 0) { + exit(EXIT_SUCCESS); + } + + /* At this point we are executing as the child process */ + + /* Change the file mode mask */ + umask(0); + + /* Create a new SID for the child process */ + sid = setsid(); + if (sid < 0) { + mys_log(LOG_ERR, "setsid: %s", strerror(errno)); + return -1; + } + + /* Change the current working directory. This prevents the current + directory from being locked; hence not being able to remove it. */ + if ((chdir("/")) < 0) { + mys_log(LOG_ERR, "chdir(\"/\"): %s", strerror(errno)); + return -1; + } + + freopen( "/dev/null", "r", stdin); + freopen( "/dev/null", "r", stdout); + freopen( "/dev/null", "r", stderr); + + return 0; +} + +void print_usage() +{ + printf("Usage: mysGateway [options]\n\n" \ + "Options:\n" \ + " -h Display a short summary of all program options.\n" \ + " -d Enable debug.\n" \ + " -b Become a daemon.\n"); +} + +int main(int argc, char *argv[]) +{ + int opt, log_opts, debug = 0, foreground = 1; + + /* register the signal handler */ + signal(SIGINT, handle_sigint); + signal(SIGTERM, handle_sigint); + + while ((opt = getopt(argc, argv, "hdb")) != -1) { + switch (opt) { + case 'h': + print_usage(); + exit(0); + case 'd': + debug = 1; + break; + case 'b': + foreground = 0; + break; + default: + print_usage(); + exit(0); + } + } + + log_opts = LOG_CONS; + if (foreground && isatty(STDIN_FILENO)) { + // Also print syslog to stderror + log_opts |= LOG_PERROR; + } + if (!debug) { + setlogmask(LOG_UPTO (LOG_INFO)); + } + openlog(NULL, log_opts, LOG_USER); + + if (!foreground && !debug) { + if (daemonize() != 0) { + exit(EXIT_FAILURE); + } + } + + mys_log(LOG_INFO, "Starting gateway...\n"); + mys_log(LOG_INFO, "Protocol version - %s\n", MYSENSORS_LIBRARY_VERSION); + + _begin(); // Startup MySensors library + + for (;;) { + _process(); // Process incoming data + if (loop) loop(); // Call sketch loop + } + return 0; +} diff --git a/core/MyMessage.cpp b/core/MyMessage.cpp index a0ee9d0..d289109 100644 --- a/core/MyMessage.cpp +++ b/core/MyMessage.cpp @@ -21,7 +21,7 @@ #include "MyMessage.h" #include #include - +#include MyMessage::MyMessage() { @@ -117,7 +117,7 @@ char* MyMessage::getString(char *buffer) const { } else if (payloadType == P_ULONG32) { ultoa(ulValue, buffer, 10); } else if (payloadType == P_FLOAT32) { - dtostrf(fValue,2,min(fPrecision, 8),buffer); + dtostrf(fValue,2,min(fPrecision, (uint8_t)8),buffer); } else if (payloadType == P_CUSTOM) { return getCustomString(buffer); } @@ -210,7 +210,7 @@ MyMessage& MyMessage::setDestination(uint8_t _destination) { // Set payload MyMessage& MyMessage::set(void* value, uint8_t length) { - uint8_t payloadLength = value == NULL ? 0 : min(length, MAX_PAYLOAD); + uint8_t payloadLength = value == NULL ? 0 : min(length, (uint8_t)MAX_PAYLOAD); miSetLength(payloadLength); miSetPayloadType(P_CUSTOM); memcpy(data, value, payloadLength); @@ -218,7 +218,7 @@ MyMessage& MyMessage::set(void* value, uint8_t length) { } MyMessage& MyMessage::set(const char* value) { - uint8_t length = value == NULL ? 0 : min(strlen(value), MAX_PAYLOAD); + uint8_t length = value == NULL ? 0 : min(strlen(value), (size_t)MAX_PAYLOAD); miSetLength(length); miSetPayloadType(P_STRING); if (length) { diff --git a/core/MyMessage.h b/core/MyMessage.h index a4fe002..40bf824 100644 --- a/core/MyMessage.h +++ b/core/MyMessage.h @@ -32,7 +32,6 @@ #ifdef __cplusplus #include -#include #include #endif diff --git a/core/MyProtocolMySensors.cpp b/core/MyProtocolMySensors.cpp index 347b9d2..8c96b42 100644 --- a/core/MyProtocolMySensors.cpp +++ b/core/MyProtocolMySensors.cpp @@ -20,6 +20,7 @@ #include "MyConfig.h" #include "MyTransport.h" #include "MyProtocol.h" +#include uint8_t protocolH2i(char c); @@ -32,7 +33,6 @@ bool protocolParse(MyMessage &message, char *inputString) { uint8_t blen = 0; int i = 0; uint8_t command = 0; - uint8_t ack = 0; // Extract command data coming on serial line for (str = strtok_r(inputString, ";", &p); // split using semicolon @@ -51,7 +51,7 @@ bool protocolParse(MyMessage &message, char *inputString) { mSetCommand(message, command); break; case 3: // Should we request ack from destination? - ack = atoi(str); + mSetRequestAck(message, atoi(str)?1:0); break; case 4: // Data type message.type = atoi(str); @@ -86,8 +86,7 @@ bool protocolParse(MyMessage &message, char *inputString) { message.sender = GATEWAY_ADDRESS; message.last = GATEWAY_ADDRESS; - mSetRequestAck(message, ack?1:0); - mSetAck(message, false); + mSetAck(message, false); if (command == C_STREAM) message.set(bvalue, blen); else @@ -100,6 +99,95 @@ char * protocolFormat(MyMessage &message) { return _fmtBuffer; } +char * protocolFormatMQTTTopic(const char* prefix, MyMessage &message) { + snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR("%s/%d/%d/%d/%d/%d"), prefix, message.sender, message.sensor, mGetCommand(message), mGetAck(message), message.type); + return _fmtBuffer; +} + +char * protocolFormatMQTTSubscribe(const char* prefix) { + snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR("%s/+/+/+/+/+"), prefix); + return _fmtBuffer; +} + +#ifdef MY_GATEWAY_MQTT_CLIENT +bool protocolMQTTParse(MyMessage &message, char* topic, uint8_t* payload, unsigned int length) { + char *str, *p; + uint8_t i = 0; + uint8_t bvalue[MAX_PAYLOAD]; + uint8_t blen = 0; + uint8_t command = 0; + + for (str = strtok_r(topic, "/", &p); str && i <= 5; + str = strtok_r(NULL, "/", &p)) { + switch (i) { + case 0: { + // Topic prefix + if (strcmp(str, MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) != 0) { + // Message not for us or malformed! + return false; + } + break; + } + case 1: { + // Node id + message.destination = atoi(str); + break; + } + case 2: { + // Sensor id + message.sensor = atoi(str); + break; + } + case 3: { + // Command type + command = atoi(str); + mSetCommand(message, command); + break; + } + case 4: { + // Ack flag + mSetRequestAck(message, atoi(str)?1:0); + break; + } + case 5: { + // Sub type + message.type = atoi(str); + break; + } + } + i++; + } + + if (i != 6) + return false; + + message.sender = GATEWAY_ADDRESS; + message.last = GATEWAY_ADDRESS; + mSetAck(message, false); + + // Add payload + if (command == C_STREAM) { + blen = 0; + uint8_t val; + while (*payload) { + val = protocolH2i(*payload++) << 4; + val += protocolH2i(*payload++); + bvalue[blen] = val; + blen++; + } + message.set(bvalue, blen); + } else { + char* ca; + ca = (char *) payload; + ca += length; + *ca = '\0'; + message.set((const char*) payload); + } + + return true; +} +#endif + uint8_t protocolH2i(char c) { uint8_t i = 0; if (c <= '9') @@ -110,5 +198,3 @@ uint8_t protocolH2i(char c) { i += c - 'A' + 10; return i; } - - diff --git a/core/MySensorsCore.cpp b/core/MySensorsCore.cpp index 88f3b0e..7778b6e 100644 --- a/core/MySensorsCore.cpp +++ b/core/MySensorsCore.cpp @@ -51,6 +51,10 @@ void _process() { transportProcess(); #endif + #if defined(__linux__) + // To avoid high cpu usage + usleep(10000); // 10ms + #endif } void _infiniteLoop() { @@ -61,17 +65,17 @@ void _infiniteLoop() { #if defined (MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN) ledsProcess(); #endif + #if defined(__linux__) + exit(1); + #endif } } void _begin() { - if (preHwInit) preHwInit(); - #if !defined(MY_DISABLED_SERIAL) - hwInit(); - #endif + hwInit(); debug(PSTR("MCO:BGN:INIT " MY_NODE_TYPE ",CP=" MY_CAPABILITIES ",VER=" MYSENSORS_LIBRARY_VERSION "\n")); diff --git a/core/MyTransport.cpp b/core/MyTransport.cpp index da41340..1519ff0 100644 --- a/core/MyTransport.cpp +++ b/core/MyTransport.cpp @@ -467,7 +467,7 @@ void transportProcessMessage() { // Is message addressed to this node? if (destination == _nc.nodeId) { // prevent buffer overflow by limiting max. possible message length (5 bits=31 bytes max) to MAX_PAYLOAD (25 bytes) - mSetLength(_msg, min(mGetLength(_msg),MAX_PAYLOAD)); + mSetLength(_msg, min(mGetLength(_msg),(uint8_t)MAX_PAYLOAD)); // null terminate data _msg.data[mGetLength(_msg)] = 0x00; @@ -716,4 +716,4 @@ bool transportSendWrite(uint8_t to, MyMessage &message) { return (ok || to==BROADCAST_ADDRESS); } -// EOF MyTransport.cpp \ No newline at end of file +// EOF MyTransport.cpp diff --git a/core/MyTransportNRF24.cpp b/core/MyTransportNRF24.cpp index ffa0889..e458eee 100644 --- a/core/MyTransportNRF24.cpp +++ b/core/MyTransportNRF24.cpp @@ -19,7 +19,9 @@ #include "MyConfig.h" #include "MyTransport.h" + #include "drivers/RF24/RF24.h" + #include "drivers/CircularBuffer/CircularBuffer.h" #if defined(MY_RF24_ENABLE_ENCRYPTION) diff --git a/core/MyTransportRS485.cpp b/core/MyTransportRS485.cpp index 814f721..285c1d1 100644 --- a/core/MyTransportRS485.cpp +++ b/core/MyTransportRS485.cpp @@ -57,12 +57,7 @@ #include "MyTransport.h" #include - -#if defined(ARDUINO) && ARDUINO >= 100 #include -#else -#include -#endif #include "MyTransport.h" diff --git a/drivers/Linux/Arduino.h b/drivers/Linux/Arduino.h new file mode 100644 index 0000000..41081d5 --- /dev/null +++ b/drivers/Linux/Arduino.h @@ -0,0 +1,67 @@ +#ifndef Arduino_h +#define Arduino_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "stdlib_noniso.h" + +#ifdef LINUX_ARCH_RASPBERRYPI + #include "rpi_util.h" + using namespace rpi_util; +#endif + +#define PI 3.1415926535897932384626433832795 +#define HALF_PI 1.5707963267948966192313216916398 +#define TWO_PI 6.283185307179586476925286766559 +#define DEG_TO_RAD 0.017453292519943295769236907684886 +#define RAD_TO_DEG 57.295779513082320876798154814105 +#define EULER 2.718281828459045235360287471352 + +#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt))) +#define radians(deg) ((deg)*DEG_TO_RAD) +#define degrees(rad) ((rad)*RAD_TO_DEG) +#define sq(x) ((x)*(x)) +#define round(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5)) + +#define lowByte(w) ((uint8_t) ((w) & 0xff)) +#define highByte(w) ((uint8_t) ((w) >> 8)) + +#define bitRead(value, bit) (((value) >> (bit)) & 0x01) +#define bitSet(value, bit) ((value) |= (1UL << (bit))) +#define bitClear(value, bit) ((value) &= ~(1UL << (bit))) +#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit)) + +#define bit(b) (1UL << (b)) + +#define GET_MACRO(_0, _1, _2, NAME, ...) NAME +#define random(...) GET_MACRO(_0, ##__VA_ARGS__, randMinMax, randMax, rand)(__VA_ARGS__) + +#ifndef delay + #define delay _delay_ms +#endif + +using std::string; +using std::min; +using std::max; +using std::abs; + +typedef bool boolean; +typedef uint8_t byte; +typedef string String; + +void yield(void); +unsigned long millis(void); +void _delay_ms(unsigned int millis); +void randomSeed(unsigned long seed); +long randMax(long howbig); +long randMinMax(long howsmall, long howbig); + +#endif diff --git a/drivers/Linux/Client.h b/drivers/Linux/Client.h new file mode 100644 index 0000000..4f38ac2 --- /dev/null +++ b/drivers/Linux/Client.h @@ -0,0 +1,50 @@ +/* + Client.h - Base class that provides Client + Copyright (c) 2011 Adrian McEwen. All right reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified by Marcelo Aquino for MySensors use +*/ + +#ifndef client_h +#define client_h + +#include "Stream.h" +#include "IPAddress.h" + +#if !DOXYGEN +class Client : public Stream { + +public: + virtual int connect(IPAddress ip, uint16_t port) = 0; + virtual int connect(const char *host, uint16_t port) = 0; + virtual size_t write(uint8_t) = 0; + virtual size_t write(const uint8_t *buf, size_t size) = 0; + virtual int available() = 0; + virtual int read() = 0; + virtual int read(uint8_t *buf, size_t size) = 0; + virtual int peek() = 0; + virtual void flush() = 0; + virtual void stop() = 0; + virtual uint8_t connected() = 0; + virtual operator bool() = 0; + +protected: + uint8_t* rawIPAddress(IPAddress& addr) { return addr.raw_address(); }; +}; +#endif + +#endif diff --git a/drivers/Linux/EthernetClient.cpp b/drivers/Linux/EthernetClient.cpp new file mode 100644 index 0000000..9a9e65b --- /dev/null +++ b/drivers/Linux/EthernetClient.cpp @@ -0,0 +1,254 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Marcelo Aquino + * Copyright (C) 2016 Marcelo Aquino + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * Based on Arduino ethernet library, Copyright (c) 2010 Arduino LLC. All right reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "EthernetClient.h" + +EthernetClient::EthernetClient() : _sock(-1) { +} + +EthernetClient::EthernetClient(int sock) : _sock(sock) { +} + +int EthernetClient::connect(const char* host, uint16_t port) { + int sockfd; + struct addrinfo hints, *servinfo, *p; + int rv; + char s[INET6_ADDRSTRLEN]; + char port_str[6]; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + sprintf(port_str, "%hu", port); + if ((rv = getaddrinfo(host, port_str, &hints, &servinfo)) != 0) { + mys_log(LOG_ERR, "getaddrinfo: %s\n", gai_strerror(rv)); + return -1; + } + + // loop through all the results and connect to the first we can + for (p = servinfo; p != NULL; p = p->ai_next) { + if ((sockfd = socket(p->ai_family, p->ai_socktype, + p->ai_protocol)) == -1) { + mys_log(LOG_ERR, "socket: %s\n", strerror(errno)); + continue; + } + + if (::connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + mys_log(LOG_ERR, "connect: %s\n", strerror(errno)); + continue; + } + + break; + } + + if (p == NULL) { + mys_log(LOG_ERR, "failed to connect\n"); + return -1; + } + + _sock = sockfd; + + void *addr = &(((struct sockaddr_in*)p->ai_addr)->sin_addr); + inet_ntop(p->ai_family, addr, s, sizeof s); + mys_log(LOG_DEBUG, "connected to %s\n", s); + + freeaddrinfo(servinfo); // all done with this structure + + return 1; +} + +int EthernetClient::connect(IPAddress ip, uint16_t port) { + return connect(ip.toString().c_str(), port); +} + +size_t EthernetClient::write(uint8_t b) { + return write(&b, 1); +} + +size_t EthernetClient::write(const uint8_t *buf, size_t size) { + int rc = 0; + int bytes = 0; + + if (_sock == -1) { + return 0; + } + + while (size > 0) { + rc = send(_sock, buf + bytes, size, MSG_NOSIGNAL | MSG_DONTWAIT); + if (rc == -1) { + mys_log(LOG_ERR, "send: %s\n", strerror(errno)); + close(_sock); + _sock = -1; + break; + } + bytes += rc; + size -= rc; + } + + return bytes; +} + +size_t EthernetClient::write(const char *str) { + if (str == NULL) return 0; + return write((const uint8_t *)str, strlen(str)); +} +size_t EthernetClient::write(const char *buffer, size_t size) { + return write((const uint8_t *)buffer, size); +} + +int EthernetClient::available() { + int count = 0; + + if (_sock != -1) { + ioctl(_sock, FIONREAD, &count); + return count; + } + return 0; +} + +int EthernetClient::read() { + uint8_t b; + if ( recv(_sock, &b, 1, 0) > 0 ) + { + // recv worked + return b; + } else { + // No data available + return -1; + } +} + +int EthernetClient::read(uint8_t *buf, size_t size) { + return recv(_sock, buf, size, 0); +} + +int EthernetClient::peek() { + uint8_t b; + + return recv(_sock, &b, 1, MSG_PEEK | MSG_DONTWAIT); +} + +void EthernetClient::flush() { + // There isn't much we can do here + return; +} + +void EthernetClient::stop() { + if (_sock == -1) + return; + + // attempt to close the connection gracefully (send a FIN to other side) + shutdown(_sock, SHUT_RDWR); + + timeval startTime, curTime; + gettimeofday(&startTime, NULL); + + // wait up to a second for the connection to close + uint8_t s; + do { + s = status(); + if (s == ETHERNETCLIENT_W5100_CLOSED) + break; // exit the loop + usleep(1000); + gettimeofday(&curTime, NULL); + } while (((curTime.tv_sec - startTime.tv_sec) * 1000000) + (curTime.tv_usec - startTime.tv_usec) < 1000000); + + // if it hasn't closed, close it forcefully + if (s != ETHERNETCLIENT_W5100_CLOSED) + close(_sock); + + _sock = -1; +} + +uint8_t EthernetClient::status() { + if (_sock == -1) return ETHERNETCLIENT_W5100_CLOSED; + + struct tcp_info tcp_info; + int tcp_info_length = sizeof(tcp_info); + + if ( getsockopt( _sock, SOL_TCP, TCP_INFO, (void *)&tcp_info, (socklen_t *)&tcp_info_length ) == 0 ) { + switch (tcp_info.tcpi_state) { + case TCP_ESTABLISHED: + return ETHERNETCLIENT_W5100_ESTABLISHED; + case TCP_SYN_SENT: + return ETHERNETCLIENT_W5100_SYNSENT; + case TCP_SYN_RECV: + return ETHERNETCLIENT_W5100_SYNRECV; + case TCP_FIN_WAIT1: + case TCP_FIN_WAIT2: + return ETHERNETCLIENT_W5100_FIN_WAIT; + case TCP_TIME_WAIT: + return TCP_TIME_WAIT; + case TCP_CLOSE: + return ETHERNETCLIENT_W5100_CLOSED; + case TCP_CLOSE_WAIT: + return ETHERNETCLIENT_W5100_CLOSING; + case TCP_LAST_ACK: + return ETHERNETCLIENT_W5100_LAST_ACK; + case TCP_LISTEN: + return ETHERNETCLIENT_W5100_LISTEN; + case TCP_CLOSING: + return ETHERNETCLIENT_W5100_CLOSING; + } + } + + return ETHERNETCLIENT_W5100_CLOSED; +} + +uint8_t EthernetClient::connected() { + if (_sock == -1) return 0; + + if (peek() < 0) { + if (errno == EAGAIN) { + return 1; + } + return 0; + } + return 1; +} + +int EthernetClient::getSocketNumber() { + return _sock; +} + +// the next function allows us to use the client returned by +// EthernetServer::available() as the condition in an if-statement. + +EthernetClient::operator bool() { + return _sock != -1; +} + +bool EthernetClient::operator==(const EthernetClient& rhs) { + return _sock == rhs._sock && _sock != -1 && rhs._sock != -1; +} diff --git a/drivers/Linux/EthernetClient.h b/drivers/Linux/EthernetClient.h new file mode 100644 index 0000000..2cbaca9 --- /dev/null +++ b/drivers/Linux/EthernetClient.h @@ -0,0 +1,191 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Marcelo Aquino + * Copyright (C) 2016 Marcelo Aquino + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * Based on Arduino ethernet library, Copyright (c) 2010 Arduino LLC. All right reserved. + */ + +#ifndef EthernetClient_h +#define EthernetClient_h + +#include "Client.h" +#include "IPAddress.h" + +// State codes from W5100 library +#define ETHERNETCLIENT_W5100_CLOSED 0x00 +#define ETHERNETCLIENT_W5100_LISTEN 0x14 +#define ETHERNETCLIENT_W5100_SYNSENT 0x15 +#define ETHERNETCLIENT_W5100_SYNRECV 0x16 +#define ETHERNETCLIENT_W5100_ESTABLISHED 0x17 +#define ETHERNETCLIENT_W5100_FIN_WAIT 0x18 +#define ETHERNETCLIENT_W5100_CLOSING 0x1A +#define ETHERNETCLIENT_W5100_TIME_WAIT 0x1B +#define ETHERNETCLIENT_W5100_CLOSE_WAIT 0x1C +#define ETHERNETCLIENT_W5100_LAST_ACK 0x1D + +/** + * EthernetClient class + */ +class EthernetClient : public Client { + +private: + int _sock; //!< @brief Network socket. + +public: + /** + * @brief EthernetClient constructor. + */ + EthernetClient(); + /** + * @brief EthernetClient constructor. + * + * @param sock Network socket. + */ + EthernetClient(int sock); + /** + * @brief Initiate a connection with host:port. + * + * @param host host name to resolve or a stringified dotted IP address. + * @param port to connect to. + * @return 1 if SUCCESS or -1 if FAILURE. + */ + virtual int connect(const char *host, uint16_t port); + /** + * @brief Initiate a connection with ip:port. + * + * @param ip to connect to. + * @param port to connect to. + * @return 1 if SUCCESS or -1 if FAILURE. + */ + virtual int connect(IPAddress ip, uint16_t port); + /** + * @brief Write a byte. + * + * @param b byte to write. + * @return 0 if FAILURE or 1 if SUCCESS. + */ + virtual size_t write(uint8_t b); + /** + * @brief Write at most 'size' bytes. + * + * @param buf Buffer to read from. + * @param size of the buffer. + * @return 0 if FAILURE or the number of bytes sent. + */ + virtual size_t write(const uint8_t *buf, size_t size); + /** + * @brief Write a null-terminated string. + * + * @param str String to write. + * @return 0 if FAILURE or number of characters sent. + */ + size_t write(const char *str); + /** + * @brief Write at most 'size' characters. + * + * @param buffer to read from. + * @param size of the buffer. + * @return 0 if FAILURE or the number of characters sent. + */ + size_t write(const char *buffer, size_t size); + /** + * @brief Checks if new data is available. + * + * @return number of bytes available. + */ + virtual int available(); + /** + * @brief Read a byte. + * + * @return 0 if FAILURE or 1 if SUCCESS. + * @note This function will block (until data becomes available or timeout is reached). + */ + virtual int read(); + /** + * @brief Read a number of bytes and store in a buffer. + * + * @return -1 if FAILURE or number of read bytes. + * @note This function will block (until data becomes available or timeout is reached). + */ + virtual int read(uint8_t *buf, size_t size); + /** + * @brief Check if new data are available. + * + * @return 0 if no data, else number of bytes available. + */ + virtual int peek(); + /** + * @brief Flushes the network buffers. + * + * Empty function due to the way TCP works. + */ + virtual void flush(); + /** + * @brief Close the connection gracefully. + * + * Send a FIN and wait 1s for a response. If no response close it forcefully. + */ + virtual void stop(); + /** + * @brief Connection status. + * + * @return state according to W5100 library codes. + */ + uint8_t status(); + /** + * @brief Checks whether the socket is alive. + * + * @return 0 if disconnected or 1 if connected. + */ + virtual uint8_t connected(); + /** + * @brief Get the internal socket file descriptor. + * + * @return an integer, that is the socket number. + */ + int getSocketNumber(); + /** + * @brief Overloaded cast operators. + * + * Allow EthernetClient objects to be used where a bool is expected. + */ + virtual operator bool(); + /** + * @brief Overloaded cast operators. + * + */ + virtual bool operator==(const bool value) { return bool() == value; } + /** + * @brief Overloaded cast operators. + * + */ + virtual bool operator!=(const bool value) { return bool() != value; } + /** + * @brief Overloaded cast operators. + * + */ + virtual bool operator==(const EthernetClient& rhs); + /** + * @brief Overloaded cast operators. + * + */ + virtual bool operator!=(const EthernetClient& rhs) { return !this->operator==(rhs); }; + + friend class EthernetServer; +}; + +#endif diff --git a/drivers/Linux/EthernetServer.cpp b/drivers/Linux/EthernetServer.cpp new file mode 100644 index 0000000..7ac89f4 --- /dev/null +++ b/drivers/Linux/EthernetServer.cpp @@ -0,0 +1,215 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Marcelo Aquino + * Copyright (C) 2016 Marcelo Aquino + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * Based on Arduino ethernet library, Copyright (c) 2010 Arduino LLC. All right reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "EthernetClient.h" +#include "EthernetServer.h" + +EthernetServer::EthernetServer(uint16_t port, uint16_t max_clients) : port(port), max_clients(max_clients) +{ + clients.reserve(max_clients); +} + +void EthernetServer::begin() +{ + begin(IPAddress(0,0,0,0)); +} + +void EthernetServer::begin(IPAddress address) +{ + struct addrinfo hints, *servinfo, *p; + int yes=1; + int rv; + char ipstr[INET_ADDRSTRLEN]; + char portstr[6]; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + sprintf(portstr, "%d", port); + if ((rv = getaddrinfo(address.toString().c_str(), portstr, &hints, &servinfo)) != 0) { + mys_log(LOG_ERR, "getaddrinfo: %s\n", gai_strerror(rv)); + return; + } + + // loop through all the results and bind to the first we can + for (p = servinfo; p != NULL; p = p->ai_next) { + if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { + mys_log(LOG_ERR, "socket: %s\n", strerror(errno)); + continue; + } + + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { + mys_log(LOG_ERR, "setsockopt: %s\n", strerror(errno)); + freeaddrinfo(servinfo); + return; + } + + if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + mys_log(LOG_ERR, "bind: %s\n", strerror(errno)); + continue; + } + + break; + } + + if (p == NULL) { + mys_log(LOG_ERR, "failed to bind\n"); + freeaddrinfo(servinfo); + return; + } + + if (listen(sockfd, ETHERNETSERVER_BACKLOG) == -1) { + mys_log(LOG_ERR, "listen: %s\n", strerror(errno)); + freeaddrinfo(servinfo); + return; + } + + freeaddrinfo(servinfo); + + fcntl(sockfd, F_SETFL, O_NONBLOCK); + + struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr; + void *addr = &(ipv4->sin_addr); + inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr); + mys_log(LOG_DEBUG, "Listening for connections on %s:%s\n", ipstr, portstr); +} + +bool EthernetServer::hasClient() +{ + _accept(); + + return !new_clients.empty(); +} + +EthernetClient EthernetServer::available() +{ + if (new_clients.empty()) { + return EthernetClient(); + } else { + int sock = new_clients.front(); + new_clients.pop_front(); + return EthernetClient(sock); + } +} + +size_t EthernetServer::write(uint8_t b) +{ + return write(&b, 1); +} + +size_t EthernetServer::write(const uint8_t *buffer, size_t size) +{ + size_t n = 0; + size_t i = 0; + + while (i < clients.size()) { + EthernetClient client(clients[i]); + if (client.connected()) { + n += client.write(buffer, size); + i++; + } else if (!client.available()) { + client.stop(); + clients[i] = clients.back(); + clients.pop_back(); + mys_log(LOG_DEBUG, "Client disconnected."); + } + } + + return n; +} + +size_t EthernetServer::write(const char *str) +{ + if (str == NULL) return 0; + return write((const uint8_t *)str, strlen(str)); +} + +size_t EthernetServer::write(const char *buffer, size_t size) +{ + return write((const uint8_t *)buffer, size); +} + +void EthernetServer::_accept() +{ + int new_fd; + socklen_t sin_size; + struct sockaddr_storage client_addr; + char ipstr[INET_ADDRSTRLEN]; + + if (new_clients.size() + clients.size() == max_clients) { + // no free slots, search for a dead client + bool no_free_slots = true; + for (size_t i = 0; i < clients.size();) { + EthernetClient client(clients[i]); + if (client.connected() || client.available()) { + i++; + } else { + clients[i] = clients.back(); + clients.pop_back(); + no_free_slots = false; + break; + } + } + if (no_free_slots) { + for (std::list::iterator it = new_clients.begin(); it != new_clients.end(); ++it) { + EthernetClient client(*it); + if (!client.connected() && !client.available()) { + new_clients.erase(it); + no_free_slots = false; + break; + } + } + } + if (no_free_slots) { + mys_log(LOG_DEBUG, "Max number of ethernet clients reached."); + return; + } + } + + sin_size = sizeof client_addr; + new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size); + if (new_fd == -1) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + mys_log(LOG_ERR, "accept: %s\n", strerror(errno)); + } + return; + } + + new_clients.push_back(new_fd); + clients.push_back(new_fd); + + void *addr = &(((struct sockaddr_in*)&client_addr)->sin_addr); + inet_ntop(client_addr.ss_family, addr, ipstr, sizeof ipstr); + mys_log(LOG_DEBUG, "New connection from %s\n", ipstr); +} diff --git a/drivers/Linux/EthernetServer.h b/drivers/Linux/EthernetServer.h new file mode 100644 index 0000000..4240e96 --- /dev/null +++ b/drivers/Linux/EthernetServer.h @@ -0,0 +1,120 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Marcelo Aquino + * Copyright (C) 2016 Marcelo Aquino + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * Based on Arduino ethernet library, Copyright (c) 2010 Arduino LLC. All right reserved. + */ + +#ifndef EthernetServer_h +#define EthernetServer_h + +#include +#include +#include "Server.h" +#include "IPAddress.h" + +#ifdef ETHERNETSERVER_MAX_CLIENTS + #define ETHERNETSERVER_BACKLOG ETHERNETSERVER_MAX_CLIENTS //!< Maximum length to which the queue of pending connections may grow. +#else + #define ETHERNETSERVER_MAX_CLIENTS 10 //!< Default value for max_clients. + #define ETHERNETSERVER_BACKLOG 10 //!< Maximum length to which the queue of pending connections may grow. +#endif + +class EthernetClient; + +/** + * @brief EthernetServer class + */ +class EthernetServer : public Server { + +private: + uint16_t port; //!< @brief Port number for the network socket. + std::list new_clients; //!< Socket list of new clients. + std::vector clients; //!< @brief Socket list of clients. + uint16_t max_clients; //!< @brief The maximum number of allowed clients. + int sockfd; //!< @brief Network socket used to accept connections. + + /** + * @brief Accept new clients if the total of connected clients is below max_clients. + * + */ + void _accept(); + +public: + /** + * @brief EthernetServer constructor. + * + * @param port number for the socket addresses. + * @param max_clients The maximum number allowed for connected clients. + */ + EthernetServer(uint16_t port, uint16_t max_clients = ETHERNETSERVER_MAX_CLIENTS); + /** + * @brief Listen for inbound connection request. + * + */ + virtual void begin(); + /** + * @brief Listen on the specified ip for inbound connection request. + * + * @param addr IP address to bind to. + */ + void begin(IPAddress addr); + /** + * @brief Verifies if a new client has connected. + * + * @return @c true if a new client has connected, else @c false. + */ + bool hasClient(); + /** + * @brief Get the new connected client. + * + * @return client class object if a new client has connected else -1. + */ + EthernetClient available(); + /** + * @brief Write a byte to all clients. + * + * @param b byte to send. + * @return 0 if FAILURE or 1 if SUCCESS. + */ + virtual size_t write(uint8_t b); + /** + * @brief Write at most 'size' bytes to all clients. + * + * @param buffer to read from. + * @param size of the buffer. + * @return 0 if FAILURE else number of bytes sent. + */ + virtual size_t write(const uint8_t *buffer, size_t size); + /** + * @brief Write a null-terminated string to all clients. + * + * @param str String to write. + * @return 0 if FAILURE else number of characters sent. + */ + size_t write(const char *str); + /** + * @brief Write at most 'size' characters to all clients. + * + * @param buffer to read from. + * @param size of the buffer. + * @return 0 if FAILURE else the number of characters sent. + */ + size_t write(const char *buffer, size_t size); +}; + +#endif diff --git a/drivers/Linux/IPAddress.cpp b/drivers/Linux/IPAddress.cpp new file mode 100644 index 0000000..dd67b59 --- /dev/null +++ b/drivers/Linux/IPAddress.cpp @@ -0,0 +1,106 @@ +/* + * IPAddress.cpp - Base class that provides IPAddress + * Copyright (c) 2011 Adrian McEwen. All right reserved. + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * + * Modified by Marcelo Aquino for MySensors use + */ + +#include +#include +#include "IPAddress.h" + +IPAddress::IPAddress() { + _address.dword = 0; +} + +IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) { + _address.bytes[0] = first_octet; + _address.bytes[1] = second_octet; + _address.bytes[2] = third_octet; + _address.bytes[3] = fourth_octet; +} + +IPAddress::IPAddress(uint32_t address) { + _address.dword = address; +} + +IPAddress::IPAddress(const uint8_t *address) { + memcpy(_address.bytes, address, sizeof(_address.bytes)); +} + +bool IPAddress::fromString(const char *address) { + // TODO: add support for "a", "a.b", "a.b.c" formats + + uint16_t acc = 0; // Accumulator + uint8_t dots = 0; + + while (*address) + { + char c = *address++; + if (c >= '0' && c <= '9') + { + acc = acc * 10 + (c - '0'); + if (acc > 255) { + // Value out of [0..255] range + return false; + } + } + else if (c == '.') + { + if (dots == 3) { + // Too much dots (there must be 3 dots) + return false; + } + _address.bytes[dots++] = acc; + acc = 0; + } + else + { + // Invalid char + return false; + } + } + + if (dots != 3) { + // Too few dots (there must be 3 dots) + return false; + } + _address.bytes[3] = acc; + return true; +} + +IPAddress& IPAddress::operator=(const uint8_t *address) { + memcpy(_address.bytes, address, sizeof(_address.bytes)); + return *this; +} + +IPAddress& IPAddress::operator=(uint32_t address) { + _address.dword = address; + return *this; +} + +bool IPAddress::operator==(const uint8_t* addr) const { + return memcmp(addr, _address.bytes, sizeof(_address.bytes)) == 0; +} + +std::string IPAddress::toString() +{ + char szRet[16]; + sprintf(szRet,"%u.%u.%u.%u", _address.bytes[0], _address.bytes[1], _address.bytes[2], _address.bytes[3]); + return std::string(szRet); +} diff --git a/drivers/Linux/IPAddress.h b/drivers/Linux/IPAddress.h new file mode 100644 index 0000000..c8e336c --- /dev/null +++ b/drivers/Linux/IPAddress.h @@ -0,0 +1,155 @@ +/* + * IPAddress.h - Base class that provides IPAddress + * Copyright (c) 2011 Adrian McEwen. All right reserved. + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * + * Modified by Marcelo Aquino for MySensors use + */ + +#ifndef IPAddress_h +#define IPAddress_h + +#include +#include + +/** + * @brief A class to make it easier to handle and pass around IP addresses + */ +class IPAddress { + private: + union { + uint8_t bytes[4]; //!< IPv4 address as an array + uint32_t dword; //!< IPv4 address in 32 bits format + } _address; + + /** + * @brief Access the raw byte array containing the address. + * + * Because this returns a pointer to the internal structure rather than a copy of the address + * this function should only be used when you know that the usage of the returned uint8_t* will + * be transient and not stored. + * + * @return pointer to the internal structure. + */ + uint8_t* raw_address() { + return _address.bytes; + } + + public: + /** + * @brief IPAddress constructor. + */ + IPAddress(); + /** + * @brief IPAddress constructor. + * + * @param first_octet first octet of the IPv4 address. + * @param second_octet second octet of the IPv4 address. + * @param third_octet third octet of the IPv4 address. + * @param fourth_octet fourth octet of the IPv4 address. + */ + IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet); + /** + * @brief IPAddress constructor. + * + * @param address to be set from a 32 bits integer. + */ + IPAddress(uint32_t address); + /** + * @brief IPAddress constructor. + * + * @param address to be set from a byte array. + */ + IPAddress(const uint8_t *address); + /** + * @brief Set the IP from a array of characters. + * + * @param address to be set. + */ + bool fromString(const char *address); + /** + * @brief Set the IP from a string class type. + * + * @param address to be set. + */ + bool fromString(const std::string &address) { return fromString(address.c_str()); } + /** + * @brief Overloaded cast operator + * + * Allow IPAddress objects to be used where a pointer to a four-byte uint8_t array is expected + */ + operator uint32_t() const { + return _address.dword; + } + /** + * @brief Overloaded cast operator + * + */ + bool operator==(const IPAddress& addr) const { + return _address.dword == addr._address.dword; + } + /** + * @brief Overloaded cast operator + * + */ + bool operator==(uint32_t addr) const { + return _address.dword == addr; + } + /** + * @brief Overloaded cast operator + * + */ + bool operator==(const uint8_t* addr) const; + /** + * @brief Overloaded index operator. + * + * Allow getting and setting individual octets of the address. + * + */ + uint8_t operator[](int index) const { + return _address.bytes[index]; + } + /** + * @brief Overloaded index operator + * + */ + uint8_t& operator[](int index) { + return _address.bytes[index]; + } + /** + * @brief Overloaded copy operators. + * + * Allow initialisation of IPAddress objects from byte array. + */ + IPAddress& operator=(const uint8_t *address); + /** + * @brief Overloaded copy operator. + * + * Allow initialisation of IPAddress objects from a 32 bits integer. + */ + IPAddress& operator=(uint32_t address); + /** + * @brief Convert the IP address to a string. + * + * @return A stringified IP address + */ + std::string toString(); + + friend class Client; +}; + +#endif diff --git a/drivers/Linux/Print.cpp b/drivers/Linux/Print.cpp new file mode 100644 index 0000000..94eb814 --- /dev/null +++ b/drivers/Linux/Print.cpp @@ -0,0 +1,242 @@ +/* + Print.cpp - Base class that provides print() and println() + Copyright (c) 2008 David A. Mellis. All right reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified 23 November 2006 by David A. Mellis + Modified December 2014 by Ivan Grokhotkov + Modified May 2015 by Michael C. Miller - esp8266 progmem support + Modified August 2016 by Marcelo Aquino for MySensors use + */ + +#include +#include +#include +#include +#include "Print.h" + +// Public Methods ////////////////////////////////////////////////////////////// + +size_t Print::write(const uint8_t *buffer, size_t size) { + size_t n = 0; + while(size--) { + n += write(*buffer++); + } + return n; +} + +size_t +#ifdef __GNUC__ +__attribute__((format(printf, 2, 3))) +#endif +Print::printf(const char *format, ...) { + va_list arg; + va_start(arg, format); + char temp[64]; + char* buffer = temp; + size_t len = vsnprintf(temp, sizeof(temp), format, arg); + va_end(arg); + if (len > sizeof(temp) - 1) { + buffer = new char[len + 1]; + if (!buffer) { + return 0; + } + va_start(arg, format); + vsnprintf(buffer, len + 1, format, arg); + va_end(arg); + } + len = write((const uint8_t*) buffer, len); + if (buffer != temp) { + delete[] buffer; + } + return len; +} + +size_t Print::print(const std::string &s) { + return write(s.c_str(), s.length()); +} + +size_t Print::print(const char str[]) { + return write(str); +} + +size_t Print::print(char c) { + return write(c); +} + +size_t Print::print(unsigned char b, int base) { + return print((unsigned long) b, base); +} + +size_t Print::print(int n, int base) { + return print((long) n, base); +} + +size_t Print::print(unsigned int n, int base) { + return print((unsigned long) n, base); +} + +size_t Print::print(long n, int base) { + if(base == 0) { + return write(n); + } else if(base == 10) { + if(n < 0) { + int t = print('-'); + n = -n; + return printNumber(n, 10) + t; + } + return printNumber(n, 10); + } else { + return printNumber(n, base); + } +} + +size_t Print::print(unsigned long n, int base) { + if(base == 0) + return write(n); + else + return printNumber(n, base); +} + +size_t Print::print(double n, int digits) { + return printFloat(n, digits); +} + +size_t Print::println(void) { + return print("\r\n"); +} + +size_t Print::println(const std::string &s) { + size_t n = print(s); + n += println(); + return n; +} + +size_t Print::println(const char c[]) { + size_t n = print(c); + n += println(); + return n; +} + +size_t Print::println(char c) { + size_t n = print(c); + n += println(); + return n; +} + +size_t Print::println(unsigned char b, int base) { + size_t n = print(b, base); + n += println(); + return n; +} + +size_t Print::println(int num, int base) { + size_t n = print(num, base); + n += println(); + return n; +} + +size_t Print::println(unsigned int num, int base) { + size_t n = print(num, base); + n += println(); + return n; +} + +size_t Print::println(long num, int base) { + size_t n = print(num, base); + n += println(); + return n; +} + +size_t Print::println(unsigned long num, int base) { + size_t n = print(num, base); + n += println(); + return n; +} + +size_t Print::println(double num, int digits) { + size_t n = print(num, digits); + n += println(); + return n; +} + +// Private Methods ///////////////////////////////////////////////////////////// + +size_t Print::printNumber(unsigned long n, uint8_t base) { + char buf[8 * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte. + char *str = &buf[sizeof(buf) - 1]; + + *str = '\0'; + + // prevent crash if called with base == 1 + if(base < 2) + base = 10; + + do { + unsigned long m = n; + n /= base; + char c = m - base * n; + *--str = c < 10 ? c + '0' : c + 'A' - 10; + } while(n); + + return write(str); +} + +size_t Print::printFloat(double number, uint8_t digits) { + size_t n = 0; + + if(std::isnan(number)) + return print("nan"); + if(std::isinf(number)) + return print("inf"); + if(number > 4294967040.0) + return print("ovf"); // constant determined empirically + if(number < -4294967040.0) + return print("ovf"); // constant determined empirically + + // Handle negative numbers + if(number < 0.0) { + n += print('-'); + number = -number; + } + + // Round correctly so that print(1.999, 2) prints as "2.00" + double rounding = 0.5; + for(uint8_t i = 0; i < digits; ++i) + rounding /= 10.0; + + number += rounding; + + // Extract the integer part of the number and print it + unsigned long int_part = (unsigned long) number; + double remainder = number - (double) int_part; + n += print(int_part); + + // Print the decimal point, but only if there are digits beyond + if(digits > 0) { + n += print("."); + } + + // Extract digits from the remainder one at a time + while(digits-- > 0) { + remainder *= 10.0; + int toPrint = int(remainder); + n += print(toPrint); + remainder -= toPrint; + } + + return n; +} diff --git a/drivers/Linux/Print.h b/drivers/Linux/Print.h new file mode 100644 index 0000000..b63e7a1 --- /dev/null +++ b/drivers/Linux/Print.h @@ -0,0 +1,76 @@ +/* + Print.h - Base class that provides print() and println() + Copyright (c) 2008 David A. Mellis. All right reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified August 2016 by Marcelo Aquino for MySensors use + */ + +#ifndef Print_h +#define Print_h + +#include +#include +#include + +#if !DOXYGEN +#define DEC 10 +#define HEX 16 +#define OCT 8 +#define BIN 2 + +class Print { + private: + size_t printNumber(unsigned long n, uint8_t base); + size_t printFloat(double number, uint8_t digits); + + public: + virtual size_t write(uint8_t) = 0; + size_t write(const char *str) { + if (str == NULL) + return 0; + return write((const uint8_t *) str, strlen(str)); + } + virtual size_t write(const uint8_t *buffer, size_t size); + size_t write(const char *buffer, size_t size) { + return write((const uint8_t *) buffer, size); + } + + size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3))); + size_t print(const std::string &); + size_t print(const char[]); + size_t print(char); + size_t print(unsigned char, int = DEC); + size_t print(int, int = DEC); + size_t print(unsigned int, int = DEC); + size_t print(long, int = DEC); + size_t print(unsigned long, int = DEC); + size_t print(double, int = 2); + + size_t println(const std::string &s); + size_t println(const char[]); + size_t println(char); + size_t println(unsigned char, int = DEC); + size_t println(int, int = DEC); + size_t println(unsigned int, int = DEC); + size_t println(long, int = DEC); + size_t println(unsigned long, int = DEC); + size_t println(double, int = 2); + size_t println(void); + }; + +#endif +#endif diff --git a/drivers/Linux/SerialPort.cpp b/drivers/Linux/SerialPort.cpp new file mode 100644 index 0000000..79b8484 --- /dev/null +++ b/drivers/Linux/SerialPort.cpp @@ -0,0 +1,232 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Marcelo Aquino + * Copyright (C) 2016 Marcelo Aquino + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "SerialPort.h" + +SerialPort::SerialPort(const char *port, bool isPty) : serialPort(std::string(port)), isPty(isPty) +{ + sd = -1; +} + +void SerialPort::begin(int bauds) +{ + if (!open(bauds)) { + mys_log(LOG_ERR, "Failed to open serial port.\n"); + exit(1); + } +} + +bool SerialPort::open(int bauds) +{ + speed_t speed; + struct termios options; + + if (isPty) { + sd = posix_openpt(O_RDWR | O_NOCTTY | O_NDELAY); + if (sd < 0) { + mys_log(LOG_ERR, "Couldn't open a PTY: %s\n", strerror(errno)); + return false; + } + + if (grantpt(sd) != 0) { + mys_log(LOG_ERR, "Couldn't grant permission to the PTY: %s\n", strerror(errno)); + return false; + } + + if (unlockpt(sd) != 0) { + mys_log(LOG_ERR, "Couldn't unlock the PTY: %s\n", strerror(errno)); + return false; + } + + /* create a symlink with predictable name to the PTY device */ + unlink(serialPort.c_str()); // remove the symlink if it already exists + if (symlink(ptsname(sd), serialPort.c_str()) != 0) { + mys_log(LOG_ERR, "Couldn't create a symlink '%s' to PTY! (%d) %s\n", serialPort.c_str(), errno, strerror(errno)); + return false; + } + } else { + if ((sd = ::open(serialPort.c_str(), O_RDWR | O_NOCTTY | O_NDELAY)) == -1) { + mys_log(LOG_ERR, "Unable to open the serial port %s\n", serialPort.c_str()); + return false; + } + + // nonblocking mode + fcntl(sd, F_SETFL, FNDELAY); + } + + switch (bauds) { + case 50: speed = B50 ; break ; + case 75: speed = B75 ; break ; + case 110: speed = B110 ; break ; + case 134: speed = B134 ; break ; + case 150: speed = B150 ; break ; + case 200: speed = B200 ; break ; + case 300: speed = B300 ; break ; + case 600: speed = B600 ; break ; + case 1200: speed = B1200 ; break ; + case 1800: speed = B1800 ; break ; + case 2400: speed = B2400 ; break ; + case 9600: speed = B9600 ; break ; + case 19200: speed = B19200 ; break ; + case 38400: speed = B38400 ; break ; + case 57600: speed = B57600 ; break ; + case 115200: speed = B115200 ; break ; + default: speed = B115200 ; break ; + } + + // Get the current options of the port + if (tcgetattr(sd, &options) < 0) { + mys_log(LOG_ERR, "Couldn't get term attributes: %s\n", strerror(errno)); + return false; + } + + // Clear all the options + bzero(&options, sizeof(options)); + + // Set the baud rate + cfsetispeed(&options, speed); + cfsetospeed(&options, speed); + + // Configure the device : 8 bits, no parity, no control + options.c_cflag |= ( CLOCAL | CREAD | CS8); + // Ignore framing errors, parity errors and BREAK condition on input. + options.c_iflag |= ( IGNPAR | IGNBRK ); + + // Timer unused + options.c_cc[VTIME]=0; + // At least on character before satisfy reading + options.c_cc[VMIN]=0; + + // Set parameters + if (tcsetattr(sd, TCSANOW, &options) < 0) { + mys_log(LOG_ERR, "Couldn't set term attributes: %s\n", strerror(errno)); + return false; + } + + // flush + if (tcflush(sd, TCIOFLUSH) < 0) { + mys_log(LOG_ERR, "Couldn't flush serial: %s\n", strerror(errno)); + return false; + } + + usleep(10000); + + return true; +} + +bool SerialPort::setGroupPerm(const char *groupName) +{ + struct group* devGrp; + const mode_t ttyPermissions = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; + const char *dev; + int ret; + + if (sd != -1 && groupName != NULL) { + devGrp = getgrnam(groupName); + if (devGrp == NULL) { + mys_log(LOG_ERR, "getgrnam: %s failed. (%d) %s\n", groupName, errno, strerror(errno)); + return false; + } + + if (isPty) { + dev = ptsname(sd); + } else { + dev = serialPort.c_str(); + } + + ret = chown(dev, -1, devGrp->gr_gid); + if (ret == -1) { + mys_log(LOG_ERR, "Could not change PTY owner! (%d) %s\n", errno, strerror(errno)); + return false; + } + + ret = chmod(dev, ttyPermissions); + if (ret != 0) { + mys_log(LOG_ERR, "Could not change PTY permissions! (%d) %s\n", errno, strerror(errno)); + return false; + } + + return true; + } + return false; +} + +int SerialPort::available() +{ + int nbytes = 0; + + if (ioctl(sd, FIONREAD, &nbytes) < 0) { + mys_log(LOG_ERR, "Failed to get byte count on serial.\n"); + exit(-1); + } + return nbytes; +} + +int SerialPort::read() +{ + unsigned char c; + + ::read(sd, &c, 1); + return c; +} + +size_t SerialPort::write(uint8_t b) +{ + return ::write(sd, &b, 1); +} + +size_t SerialPort::write(const uint8_t *buffer, size_t size) +{ + return ::write(sd, buffer, size); +} + +int SerialPort::peek() +{ + FILE * f = fdopen(sd, "r+"); + int c = getc(f); + if (c == EOF) return -1; + ungetc(c, f); + return c; +} + +void SerialPort::flush() +{ + tcflush(sd, TCIFLUSH); +} + +void SerialPort::end() +{ + close(sd); + + if (isPty) { + unlink(serialPort.c_str()); // remove the symlink + } +} diff --git a/drivers/Linux/SerialPort.h b/drivers/Linux/SerialPort.h new file mode 100644 index 0000000..e0f77bb --- /dev/null +++ b/drivers/Linux/SerialPort.h @@ -0,0 +1,113 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Marcelo Aquino + * Copyright (C) 2016 Marcelo Aquino + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef SerialPort_h +#define SerialPort_h + +#include +#include +#include "Stream.h" + +/** + * SerialPort Class + * Class that provides the functionality of arduino Serial library + */ +class SerialPort : public Stream { + +private: + int sd; //!< @brief file descriptor number. + std::string serialPort; //!< @brief tty name. + bool isPty; //!< @brief true if serial is pseudo terminal. + +public: + /** + * @brief SerialPort constructor. + */ + SerialPort(const char *port, bool isPty = false); + /** + * @brief Open the serial port and set the data rate in bits per second (baud). + * + * This function will terminate the program on an error. + * + * @param bauds bits per second. + */ + void begin(int bauds); + /** + * @brief Open the serial port and set the data rate in bits per second (baud). + * + * @param bauds bits per second. + * @return @c true if no errors, else @c false. + */ + bool open(int bauds = 115200); + /** + * @brief Grant access to the specified system group for the serial device. + * + * @param groupName system group name. + */ + bool setGroupPerm(const char *groupName); + /** + * @brief Get the number of bytes available. + * + * Get the numberof bytes (characters) available for reading from + * the serial port. + * + * @return number of bytes avalable to read. + */ + int available(); + /** + * @brief Reads 1 byte of incoming serial data. + * + * @return first byte of incoming serial data available. + */ + int read(); + /** + * @brief Writes a single byte to the serial port. + * + * @param b byte to write. + * @return number of bytes written. + */ + size_t write(uint8_t b); + /** + * @brief Writes binary data to the serial port. + * + * @param buffer to write. + * @param size of the buffer. + * @return number of bytes written. + */ + size_t write(const uint8_t *buffer, size_t size); + /** + * @brief + * + * Returns the next byte (character) of incoming serial data without removing it from + * the internal serial buffer. + * + * @return -1 if no data else character in the buffer. + */ + int peek(); + /** + * @brief Remove any data remaining on the serial buffer. + */ + void flush(); + /** + * @brief Disables serial communication. + */ + void end(); +}; + +#endif diff --git a/drivers/Linux/Server.h b/drivers/Linux/Server.h new file mode 100644 index 0000000..c637f7a --- /dev/null +++ b/drivers/Linux/Server.h @@ -0,0 +1,32 @@ +/* + Server.h - Base class that provides Server + Copyright (c) 2011 Adrian McEwen. All right reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef server_h +#define server_h + +#include "Print.h" + +#if !DOXYGEN +class Server : public Print { + public: + virtual void begin() =0; +}; +#endif + +#endif diff --git a/drivers/Linux/Stream.cpp b/drivers/Linux/Stream.cpp new file mode 100644 index 0000000..1817083 --- /dev/null +++ b/drivers/Linux/Stream.cpp @@ -0,0 +1,258 @@ +/* + Stream.cpp - adds parsing methods to Stream class + Copyright (c) 2008 David A. Mellis. All right reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Created July 2011 + parsing functions based on TextFinder library by Michael Margolis + Modified August 2016 by Marcelo Aquino for MySensors use + */ + +#include +#include + +#define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait +#define NO_SKIP_CHAR 1 // a magic char not found in a valid ASCII numeric field + +// private method to read stream with timeout +int Stream::timedRead() { + int c; + _startMillis = millis(); + do { + c = read(); + if(c >= 0) + return c; + yield(); + } while(millis() - _startMillis < _timeout); + return -1; // -1 indicates timeout +} + +// private method to peek stream with timeout +int Stream::timedPeek() { + int c; + _startMillis = millis(); + do { + c = peek(); + if(c >= 0) + return c; + yield(); + } while(millis() - _startMillis < _timeout); + return -1; // -1 indicates timeout +} + +// returns peek of the next digit in the stream or -1 if timeout +// discards non-numeric characters +int Stream::peekNextDigit() { + int c; + while(1) { + c = timedPeek(); + if(c < 0) + return c; // timeout + if(c == '-') + return c; + if(c >= '0' && c <= '9') + return c; + read(); // discard non-numeric + } +} + +// Public Methods +////////////////////////////////////////////////////////////// + +void Stream::setTimeout(unsigned long timeout) // sets the maximum number of milliseconds to wait +{ + _timeout = timeout; +} + +// find returns true if the target string is found +bool Stream::find(const char *target) { + return findUntil(target, (char*) ""); +} + +// reads data from the stream until the target string of given length is found +// returns true if target string is found, false if timed out +bool Stream::find(const char *target, size_t length) { + return findUntil(target, length, NULL, 0); +} + +// as find but search ends if the terminator string is found +bool Stream::findUntil(const char *target, const char *terminator) { + return findUntil(target, strlen(target), terminator, strlen(terminator)); +} + +// reads data from the stream until the target string of the given length is found +// search terminated if the terminator string is found +// returns true if target string is found, false if terminated or timed out +bool Stream::findUntil(const char *target, size_t targetLen, const char *terminator, size_t termLen) { + size_t index = 0; // maximum target string length is 64k bytes! + size_t termIndex = 0; + int c; + + if(*target == 0) + return true; // return true if target is a null string + while((c = timedRead()) > 0) { + + if(c != target[index]) + index = 0; // reset index if any char does not match + + if(c == target[index]) { + //////Serial.print("found "); Serial.write(c); Serial.print("index now"); Serial.println(index+1); + if(++index >= targetLen) { // return true if all chars in the target match + return true; + } + } + + if(termLen > 0 && c == terminator[termIndex]) { + if(++termIndex >= termLen) + return false; // return false if terminate string found before target string + } else + termIndex = 0; + } + return false; +} + +// returns the first valid (long) integer value from the current position. +// initial characters that are not digits (or the minus sign) are skipped +// function is terminated by the first character that is not a digit. +long Stream::parseInt() { + return parseInt(NO_SKIP_CHAR); // terminate on first non-digit character (or timeout) +} + +// as above but a given skipChar is ignored +// this allows format characters (typically commas) in values to be ignored +long Stream::parseInt(char skipChar) { + boolean isNegative = false; + long value = 0; + int c; + + c = peekNextDigit(); + // ignore non numeric leading characters + if(c < 0) + return 0; // zero returned if timeout + + do { + if(c == skipChar) + ; // ignore this charactor + else if(c == '-') + isNegative = true; + else if(c >= '0' && c <= '9') // is c a digit? + value = value * 10 + c - '0'; + read(); // consume the character we got with peek + c = timedPeek(); + } while((c >= '0' && c <= '9') || c == skipChar); + + if(isNegative) + value = -value; + return value; +} + +// as parseInt but returns a floating point value +float Stream::parseFloat() { + return parseFloat(NO_SKIP_CHAR); +} + +// as above but the given skipChar is ignored +// this allows format characters (typically commas) in values to be ignored +float Stream::parseFloat(char skipChar) { + boolean isNegative = false; + boolean isFraction = false; + long value = 0; + int c; + float fraction = 1.0; + + c = peekNextDigit(); + // ignore non numeric leading characters + if(c < 0) + return 0; // zero returned if timeout + + do { + if(c == skipChar) + ; // ignore + else if(c == '-') + isNegative = true; + else if(c == '.') + isFraction = true; + else if(c >= '0' && c <= '9') { // is c a digit? + value = value * 10 + c - '0'; + if(isFraction) + fraction *= 0.1; + } + read(); // consume the character we got with peek + c = timedPeek(); + } while((c >= '0' && c <= '9') || c == '.' || c == skipChar); + + if(isNegative) + value = -value; + if(isFraction) + return value * fraction; + else + return value; +} + +// read characters from stream into buffer +// terminates if length characters have been read, or timeout (see setTimeout) +// returns the number of characters placed in the buffer +// the buffer is NOT null terminated. +// +size_t Stream::readBytes(char *buffer, size_t length) { + size_t count = 0; + while(count < length) { + int c = timedRead(); + if(c < 0) + break; + *buffer++ = (char) c; + count++; + } + return count; +} + +// as readBytes with terminator character +// terminates if length characters have been read, timeout, or if the terminator character detected +// returns the number of characters placed in the buffer (0 means no valid data found) + +size_t Stream::readBytesUntil(char terminator, char *buffer, size_t length) { + if(length < 1) + return 0; + size_t index = 0; + while(index < length) { + int c = timedRead(); + if(c < 0 || c == terminator) + break; + *buffer++ = (char) c; + index++; + } + return index; // return number of characters, not including null terminator +} + +std::string Stream::readString() { + std::string ret; + int c = timedRead(); + while(c >= 0) { + ret += (char) c; + c = timedRead(); + } + return ret; +} + +std::string Stream::readStringUntil(char terminator) { + std::string ret; + int c = timedRead(); + while(c >= 0 && c != terminator) { + ret += (char) c; + c = timedRead(); + } + return ret; +} diff --git a/drivers/Linux/Stream.h b/drivers/Linux/Stream.h new file mode 100644 index 0000000..dff2074 --- /dev/null +++ b/drivers/Linux/Stream.h @@ -0,0 +1,120 @@ +/* + Stream.h - base class for character-based streams. + Copyright (c) 2010 David A. Mellis. All right reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + parsing functions based on TextFinder library by Michael Margolis + Modified August 2016 by Marcelo Aquino for MySensors use + */ + +#ifndef Stream_h +#define Stream_h + +#include +#include +#include "Print.h" + +// compatability macros for testing +/* + #define getInt() parseInt() + #define getInt(skipChar) parseInt(skipchar) + #define getFloat() parseFloat() + #define getFloat(skipChar) parseFloat(skipChar) + #define getString( pre_string, post_string, buffer, length) + readBytesBetween( pre_string, terminator, buffer, length) + */ + +#if !DOXYGEN +class Stream: public Print { + protected: + unsigned long _timeout; // number of milliseconds to wait for the next char before aborting timed read + unsigned long _startMillis; // used for timeout measurement + int timedRead(); // private method to read stream with timeout + int timedPeek(); // private method to peek stream with timeout + int peekNextDigit(); // returns the next numeric digit in the stream or -1 if timeout + + public: + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + virtual void flush() = 0; + + Stream() { + _timeout = 1000; + } + +// parsing methods + + void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second + + bool find(const char *target); // reads data from the stream until the target string is found + bool find(uint8_t *target) { + return find((char *) target); + } + // returns true if target string is found, false if timed out (see setTimeout) + + bool find(const char *target, size_t length); // reads data from the stream until the target string of given length is found + bool find(const uint8_t *target, size_t length) { + return find((char *) target, length); + } + // returns true if target string is found, false if timed out + + bool find(char target) { return find (&target, 1); } + + bool findUntil(const char *target, const char *terminator); // as find but search ends if the terminator string is found + bool findUntil(const uint8_t *target, const char *terminator) { + return findUntil((char *) target, terminator); + } + + bool findUntil(const char *target, size_t targetLen, const char *terminate, size_t termLen); // as above but search ends if the terminate string is found + bool findUntil(const uint8_t *target, size_t targetLen, const char *terminate, size_t termLen) { + return findUntil((char *) target, targetLen, terminate, termLen); + } + + long parseInt(); // returns the first valid (long) integer value from the current position. + // initial characters that are not digits (or the minus sign) are skipped + // integer is terminated by the first character that is not a digit. + + float parseFloat(); // float version of parseInt + + virtual size_t readBytes(char *buffer, size_t length); // read chars from stream into buffer + virtual size_t readBytes(uint8_t *buffer, size_t length) { + return readBytes((char *) buffer, length); + } + // terminates if length characters have been read or timeout (see setTimeout) + // returns the number of characters placed in the buffer (0 means no valid data found) + + size_t readBytesUntil(char terminator, char *buffer, size_t length); // as readBytes with terminator character + size_t readBytesUntil(char terminator, uint8_t *buffer, size_t length) { + return readBytesUntil(terminator, (char *) buffer, length); + } + // terminates if length characters have been read, timeout, or if the terminator character detected + // returns the number of characters placed in the buffer (0 means no valid data found) + + // Arduino String functions to be added here + std::string readString(); + std::string readStringUntil(char terminator); + + protected: + long parseInt(char skipChar); // as above but the given skipChar is ignored + // as above but the given skipChar is ignored + // this allows format characters (typically commas) in values to be ignored + + float parseFloat(char skipChar); // as above but the given skipChar is ignored +}; +#endif + +#endif diff --git a/drivers/Linux/compatibility.cpp b/drivers/Linux/compatibility.cpp new file mode 100644 index 0000000..b0ceee1 --- /dev/null +++ b/drivers/Linux/compatibility.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include "Arduino.h" + +// For millis() +static unsigned long millis_at_start = 0; + +void yield(void) {} + +unsigned long millis(void) +{ + timeval curTime; + + if (millis_at_start == 0) { + gettimeofday(&curTime, NULL); + millis_at_start = curTime.tv_sec; + } + + gettimeofday(&curTime, NULL); + return ((curTime.tv_sec - millis_at_start) * 1000) + (curTime.tv_usec / 1000); +} + +void _delay_ms(unsigned int millis) +{ + struct timespec sleeper; + + sleeper.tv_sec = (time_t)(millis / 1000); + sleeper.tv_nsec = (long)(millis % 1000) * 1000000; + nanosleep(&sleeper, NULL); +} + +void randomSeed(unsigned long seed) +{ + if (seed != 0) { + srand(seed); + } +} + +long randMax(long howbig) +{ + if (howbig == 0) { + return 0; + } + return rand() % howbig; +} + +long randMinMax(long howsmall, long howbig) +{ + if (howsmall >= howbig) { + return howsmall; + } + long diff = howbig - howsmall; + return randMax(diff) + howsmall; +} diff --git a/drivers/Linux/log.c b/drivers/Linux/log.c new file mode 100644 index 0000000..b45df4e --- /dev/null +++ b/drivers/Linux/log.c @@ -0,0 +1,22 @@ +#include +#include +#include +#include "log.h" + +void mys_log_v(int level, const char *fmt, va_list args) +{ + vsyslog(level, fmt, args); +} + +void +#ifdef __GNUC__ +__attribute__((format(printf, 2, 3))) +#endif +mys_log(int level, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + mys_log_v(level, fmt, args); + va_end(args); +} diff --git a/drivers/Linux/log.h b/drivers/Linux/log.h new file mode 100644 index 0000000..5c93dcc --- /dev/null +++ b/drivers/Linux/log.h @@ -0,0 +1,18 @@ +#ifndef LOG_H +#define LOG_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern void mys_log_v(int level, const char *fmt, va_list args); +extern void mys_log(int level, const char *fmt, ...) __attribute__((format(printf,2,3))); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/Linux/noniso.cpp b/drivers/Linux/noniso.cpp new file mode 100644 index 0000000..f628aa7 --- /dev/null +++ b/drivers/Linux/noniso.cpp @@ -0,0 +1,147 @@ +/* + noniso.cpp - replacements for non-ISO functions used by Arduino core + Copyright © 2016 Ivan Grokhotkov + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + Modified August 2016 by Marcelo Aquino for MySensors use +*/ + +#include +#include +#include +#include +#include +#include +#include "stdlib_noniso.h" + +char* utoa(unsigned value, char* result, int base) { + if(base < 2 || base > 16) { + *result = 0; + return result; + } + + char* out = result; + unsigned quotient = value; + + do { + const unsigned tmp = quotient / base; + *out = "0123456789abcdef"[quotient - (tmp * base)]; + ++out; + quotient = tmp; + } while(quotient); + + reverse(result, out); + *out = 0; + return result; +} + +char* itoa(int value, char* result, int base) { + if(base < 2 || base > 16) { + *result = 0; + return result; + } + + char* out = result; + int quotient = abs(value); + + do { + const int tmp = quotient / base; + *out = "0123456789abcdef"[quotient - (tmp * base)]; + ++out; + quotient = tmp; + } while(quotient); + + // Apply negative sign + if(value < 0) + *out++ = '-'; + + reverse(result, out); + *out = 0; + return result; +} + +int atoi(const char* s) { + return (int) atol(s); +} + +long atol(const char* s) { + char * tmp; + return strtol(s, &tmp, 10); +} + +double atof(const char* s) { + char * tmp; + return strtod(s, &tmp); +} + +void reverse(char* begin, char* end) { + char *is = begin; + char *ie = end - 1; + while(is < ie) { + char tmp = *ie; + *ie = *is; + *is = tmp; + ++is; + --ie; + } +} + +char* ltoa(long value, char* result, int base) { + if(base < 2 || base > 16) { + *result = 0; + return result; + } + + char* out = result; + long quotient = abs(value); + + do { + const long tmp = quotient / base; + *out = "0123456789abcdef"[quotient - (tmp * base)]; + ++out; + quotient = tmp; + } while(quotient); + + // Apply negative sign + if(value < 0) + *out++ = '-'; + + reverse(result, out); + *out = 0; + return result; +} + +char* ultoa(unsigned long value, char* result, int base) { + if(base < 2 || base > 16) { + *result = 0; + return result; + } + + char* out = result; + unsigned long quotient = value; + + do { + const unsigned long tmp = quotient / base; + *out = "0123456789abcdef"[quotient - (tmp * base)]; + ++out; + quotient = tmp; + } while(quotient); + + reverse(result, out); + *out = 0; + return result; +} + +char* dtostrf (double val, signed char width, unsigned char prec, char *s) { + sprintf(s,"%*.*f", width, prec, val); + return s; +} diff --git a/drivers/Linux/stdlib_noniso.h b/drivers/Linux/stdlib_noniso.h new file mode 100644 index 0000000..636cbf4 --- /dev/null +++ b/drivers/Linux/stdlib_noniso.h @@ -0,0 +1,52 @@ +/* + stdlib_noniso.h - nonstandard (but usefull) conversion functions + + Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef STDLIB_NONISO_H +#define STDLIB_NONISO_H + +#ifdef __cplusplus +extern "C"{ +#endif + +int atoi(const char *s); + +long atol(const char* s); + +double atof(const char* s); + +char* itoa (int val, char *s, int radix); + +char* ltoa (long val, char *s, int radix); + +char* utoa (unsigned int val, char *s, int radix); + +char* ultoa (unsigned long val, char *s, int radix); + +char* dtostrf (double val, signed char width, unsigned char prec, char *s); + +void reverse(char* begin, char* end); + +#ifdef __cplusplus +} // extern "C" +#endif + + +#endif diff --git a/drivers/RF24/RF24.cpp b/drivers/RF24/RF24.cpp index bd84c56..aea2bd7 100644 --- a/drivers/RF24/RF24.cpp +++ b/drivers/RF24/RF24.cpp @@ -29,8 +29,13 @@ LOCAL uint8_t MY_RF24_NODE_ADDRESS = AUTO; LOCAL RF24_receiveCallbackType RF24_receiveCallback = NULL; #endif +#ifdef LINUX_ARCH_RASPBERRYPI + uint8_t spi_rxbuff[32+1] ; //SPI receive buffer (payload max 32 bytes) + uint8_t spi_txbuff[32+1] ; //SPI transmit buffer (payload max 32 bytes + 1 byte for the command) +#endif + LOCAL void RF24_csn(bool level) { - digitalWrite(MY_RF24_CS_PIN, level); + digitalWrite(MY_RF24_CS_PIN, level); } LOCAL void RF24_ce(bool level) { @@ -38,6 +43,7 @@ LOCAL void RF24_ce(bool level) { } LOCAL uint8_t RF24_spiMultiByteTransfer(uint8_t cmd, uint8_t* buf, uint8_t len, bool aReadMode) { + uint8_t status; uint8_t* current = buf; #if !defined(MY_SOFTSPI) _SPI.beginTransaction(SPISettings(MY_RF24_SPI_MAX_SPEED, MY_RF24_SPI_DATA_ORDER, MY_RF24_SPI_DATA_MODE)); @@ -45,13 +51,40 @@ LOCAL uint8_t RF24_spiMultiByteTransfer(uint8_t cmd, uint8_t* buf, uint8_t len, RF24_csn(LOW); // timing delayMicroseconds(10); - uint8_t status = _SPI.transfer( cmd ); - while ( len-- ) { - if (aReadMode) { - status = _SPI.transfer( NOP ); - if(buf != NULL) *current++ = status; - } else status = _SPI.transfer(*current++); - } + #ifdef LINUX_ARCH_RASPBERRYPI + uint8_t * prx = spi_rxbuff; + uint8_t * ptx = spi_txbuff; + uint8_t size = len + 1; // Add register value to transmit buffer + + *ptx++ = cmd; + while ( len-- ) { + if (aReadMode) { + *ptx++ = NOP ; + } else { + *ptx++ = *current++; + } + } + _SPI.transfernb( (char *) spi_txbuff, (char *) spi_rxbuff, size); + if (aReadMode) { + if (size == 2) { + status = *++prx; // result is 2nd byte of receive buffer + } else { + status = *prx++; // status is 1st byte of receive buffer + // decrement before to skip status byte + while (--size) { *buf++ = *prx++; } + } + } else { + status = *prx; // status is 1st byte of receive buffer + } + #else + status = _SPI.transfer( cmd ); + while ( len-- ) { + if (aReadMode) { + status = _SPI.transfer( NOP ); + if (buf != NULL) *current++ = status; + } else status = _SPI.transfer(*current++); + } + #endif RF24_csn(HIGH); #if !defined(MY_SOFTSPI) _SPI.endTransaction(); @@ -268,7 +301,7 @@ LOCAL void RF24_irqHandler( void ) // rx coming in during our stay will not be handled and will cause characters to be lost. // As a workaround we re-enable interrupts to allow nested processing of other interrupts. // Our own handler is disconnected to prevent recursive calling of this handler. - #ifdef MY_GATEWAY_SERIAL + #if defined(MY_GATEWAY_SERIAL) && !defined(__linux__) detachInterrupt(digitalPinToInterrupt(MY_RF24_IRQ_PIN)); interrupts(); #endif @@ -283,7 +316,7 @@ LOCAL void RF24_irqHandler( void ) RF24_receiveCallback(); // Must call RF24_readMessage(), which will clear RX_DR IRQ ! } // Restore our interrupt handler. - #ifdef MY_GATEWAY_SERIAL + #if defined(MY_GATEWAY_SERIAL) && !defined(__linux__) noInterrupts(); attachInterrupt(digitalPinToInterrupt(MY_RF24_IRQ_PIN), RF24_irqHandler, FALLING); #endif diff --git a/drivers/RPi/SPI.cpp b/drivers/RPi/SPI.cpp new file mode 100644 index 0000000..8431daf --- /dev/null +++ b/drivers/RPi/SPI.cpp @@ -0,0 +1,106 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Marcelo Aquino + * Copyright (C) 2016 Marcelo Aquino + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * Based on TMRh20 RF24 library, Copyright (c) 2015 Charles-Henri Hallard + */ + +#include "SPI.h" +#include +#include +#include "log.h" + +static pthread_mutex_t spiMutex = PTHREAD_MUTEX_INITIALIZER; + +// Declare a single default instance +SPIClass SPI = SPIClass(); + +uint8_t SPIClass::initialized = 0; + +SPIClass::SPIClass() { + if (!bcm2835_init()) { + mys_log(LOG_ERR, "Failed to initialized bcm2835.\n"); + exit(1); + } +} + +SPIClass::~SPIClass() {} + +uint8_t SPIClass::isInitialized() { + return initialized; +} + +void SPIClass::begin() { + if (!initialized) { + if (!bcm2835_spi_begin()) { + mys_log(LOG_ERR, "You need to be root to use SPI.\n"); + exit(1); + } + } + + initialized++; // reference count +} + +void SPIClass::end() { + if (initialized) { + initialized--; + } + + if (!initialized) { + // End the SPI + bcm2835_spi_end(); + } +} + +void SPIClass::setBitOrder(uint8_t bit_order) { + bcm2835_spi_setBitOrder(bit_order); +} + +void SPIClass::setDataMode(uint8_t data_mode) { + bcm2835_spi_setDataMode(data_mode); +} + +void SPIClass::setClockDivider(uint16_t divider) { + bcm2835_spi_setClockDivider(divider); +} + +void SPIClass::chipSelect(int csn_pin) { + if (csn_pin == RPI_GPIO_P1_26) csn_pin = BCM2835_SPI_CS1; + else if (csn_pin == RPI_GPIO_P1_24) csn_pin = BCM2835_SPI_CS0; + else csn_pin = BCM2835_SPI_CS0; + bcm2835_spi_chipSelect(csn_pin); + delayMicroseconds(5); +} + +void SPIClass::beginTransaction(SPISettings settings) { + pthread_mutex_lock(&spiMutex); + setBitOrder(settings.border); + setDataMode(settings.dmode); + setClockDivider(settings.cdiv); +} + +void SPIClass::endTransaction() { + pthread_mutex_unlock(&spiMutex); +} + +void SPIClass::usingInterrupt(uint8_t interruptNumber) { + (void)interruptNumber; +} + +void SPIClass::notUsingInterrupt(uint8_t interruptNumber) { + (void)interruptNumber; +} diff --git a/drivers/RPi/SPI.h b/drivers/RPi/SPI.h new file mode 100644 index 0000000..84a4117 --- /dev/null +++ b/drivers/RPi/SPI.h @@ -0,0 +1,231 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Marcelo Aquino + * Copyright (C) 2016 Marcelo Aquino + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * Based on TMRh20 RF24 library, Copyright (c) 2015 Charles-Henri Hallard + */ +#ifndef _SPI_H_ +#define _SPI_H_ + +#include +#include "bcm2835.h" + +#define SPI_HAS_TRANSACTION + +// SPI Clock divider +#define SPI_CLOCK_DIV2 2 +#define SPI_CLOCK_DIV4 4 +#define SPI_CLOCK_DIV8 8 +#define SPI_CLOCK_DIV16 16 +#define SPI_CLOCK_DIV32 32 +#define SPI_CLOCK_DIV64 64 +#define SPI_CLOCK_DIV128 128 + +// SPI Data mode +#define SPI_MODE0 BCM2835_SPI_MODE0 +#define SPI_MODE1 BCM2835_SPI_MODE1 +#define SPI_MODE2 BCM2835_SPI_MODE2 +#define SPI_MODE3 BCM2835_SPI_MODE3 + +/** + * SPISettings class + */ +class SPISettings { + +public: + /** + * @brief SPISettings constructor. + * + * Default clock speed is 8Mhz. + */ + SPISettings() { + init(BCM2835_SPI_CLOCK_DIVIDER_32, BCM2835_SPI_BIT_ORDER_MSBFIRST, BCM2835_SPI_MODE0); + } + /** + * @brief SPISettings constructor. + * + * @param clock SPI clock speed in Hz. + * @param bitOrder SPI bit order. + * @param dataMode SPI data mode. + */ + SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { + uint16_t divider; + + switch (clock) { + case 500000: + divider = BCM2835_SPI_CLOCK_DIVIDER_512; + break; + case 1000000: + divider = BCM2835_SPI_CLOCK_DIVIDER_256; + break; + case 2000000: + divider = BCM2835_SPI_CLOCK_DIVIDER_128; + break; + case 4000000: + divider = BCM2835_SPI_CLOCK_DIVIDER_64; + break; + case 8000000: + divider = BCM2835_SPI_CLOCK_DIVIDER_32; + break; + case 16000000: + divider = BCM2835_SPI_CLOCK_DIVIDER_16; + break; + default: + // 8Mhz + divider = BCM2835_SPI_CLOCK_DIVIDER_32; + } + + init(divider, bitOrder, dataMode); + } + + uint16_t cdiv; //!< @brief SPI clock divider. + uint8_t border; //!< @brief SPI bit order. + uint8_t dmode; //!< @brief SPI data mode. + +private: + /** + * @brief Initialized class members. + * + * @param divider SPI clock divider. + * @param bitOrder SPI bit order. + * @param dataMode SPI data mode. + */ + void init(uint16_t divider, uint8_t bitOrder, uint8_t dataMode) { + cdiv = divider; + border = bitOrder; + dmode = dataMode; + } + + friend class SPIClass; +}; + +/** + * SPIClass class + */ +class SPIClass { + +private: + static uint8_t initialized; //!< @brief SPI initialized flag. + +public: + /** + * @brief SPIClass constructor. + */ + SPIClass(); + /** + * @brief SPIClass destructor. + */ + virtual ~SPIClass(); + /** + * @brief Checks if SPI was initialized. + * + * @return 0 if wasn't initialized, else 1 or more. + */ + static uint8_t isInitialized(); + /** + * @brief Send and receive a byte. + * + * @param data to send. + * @return byte received. + */ + inline static uint8_t transfer(uint8_t data); + /** + * @brief Send and receive a number of bytes. + * + * @param tbuf Sending buffer. + * @param rbuf Receive buffer. + * @param len Buffer length. + */ + inline static void transfernb(char* tbuf, char* rbuf, uint32_t len); + /** + * @brief Send and receive a number of bytes. + * + * @param buf Buffer to read from and write to. + * @param len Buffer length. + */ + inline static void transfern(char* buf, uint32_t len); + /** + * @brief Start SPI operations. + */ + static void begin(); + /** + * @brief End SPI operations. + */ + static void end(); + /** + * @brief Sets the SPI bit order. + * + * @param bit_order The desired bit order. + */ + static void setBitOrder(uint8_t bit_order); + /** + * @brief Sets the SPI data mode. + * + * @param data_mode The desired data mode. + */ + static void setDataMode(uint8_t data_mode); + /** + * @brief Sets the SPI clock divider and therefore the SPI clock speed. + * + * @param divider The desired SPI clock divider. + */ + static void setClockDivider(uint16_t divider); + /** + * @brief Sets the chip select pin. + * + * @param csn_pin Specifies the CS pin. + */ + static void chipSelect(int csn_pin); + /** + * @brief Start SPI transaction. + * + * @param settings for SPI. + */ + static void beginTransaction(SPISettings settings); + /** + * @brief End SPI transaction. + */ + static void endTransaction(); + /** + * @brief Not implemented. + * + * @param interruptNumber ignored parameter. + */ + static void usingInterrupt(uint8_t interruptNumber); + /** + * @brief Not implemented. + * + * @param interruptNumber ignored parameter. + */ + static void notUsingInterrupt(uint8_t interruptNumber); +}; + +uint8_t SPIClass::transfer(uint8_t data) { + return bcm2835_spi_transfer(data); +} + +void SPIClass::transfernb(char* tbuf, char* rbuf, uint32_t len) { + bcm2835_spi_transfernb( tbuf, rbuf, len); +} + +void SPIClass::transfern(char* buf, uint32_t len) { + transfernb(buf, buf, len); +} + +extern SPIClass SPI; + +#endif diff --git a/drivers/RPi/bcm2835.c b/drivers/RPi/bcm2835.c new file mode 100644 index 0000000..84a0f0d --- /dev/null +++ b/drivers/RPi/bcm2835.c @@ -0,0 +1,1504 @@ +/* bcm2835.c +// C and C++ support for Broadcom BCM 2835 as used in Raspberry Pi +// http://elinux.org/RPi_Low-level_peripherals +// http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf +// +// Author: Mike McCauley +// Copyright (C) 2011-2013 Mike McCauley +// $Id: bcm2835.c,v 1.23 2015/03/31 04:55:41 mikem Exp mikem $ +// +// Modified September 2016 by Marcelo Aquino +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "log.h" + +#define BCK2835_LIBRARY_BUILD +#include "bcm2835.h" + +/* This define enables a little test program (by default a blinking output on pin RPI_GPIO_PIN_11) +// You can do some safe, non-destructive testing on any platform with: +// gcc bcm2835.c -D BCM2835_TEST +// ./a.out +*/ +/*#define BCM2835_TEST*/ + +/* Uncommenting this define compiles alternative I2C code for the version 1 RPi +// The P1 header I2C pins are connected to SDA0 and SCL0 on V1. +// By default I2C code is generated for the V2 RPi which has SDA1 and SCL1 connected. +*/ +/* #define I2C_V1*/ + +/* Physical address and size of the peripherals block +// May be overridden on RPi2 +*/ +uint32_t *bcm2835_peripherals_base = (uint32_t *)BCM2835_PERI_BASE; +uint32_t bcm2835_peripherals_size = BCM2835_PERI_SIZE; + +/* Virtual memory address of the mapped peripherals block + */ +uint32_t *bcm2835_peripherals = (uint32_t *)MAP_FAILED; + +/* And the register bases within the peripherals block + */ +volatile uint32_t *bcm2835_gpio = (uint32_t *)MAP_FAILED; +volatile uint32_t *bcm2835_pwm = (uint32_t *)MAP_FAILED; +volatile uint32_t *bcm2835_clk = (uint32_t *)MAP_FAILED; +volatile uint32_t *bcm2835_pads = (uint32_t *)MAP_FAILED; +volatile uint32_t *bcm2835_spi0 = (uint32_t *)MAP_FAILED; +volatile uint32_t *bcm2835_bsc0 = (uint32_t *)MAP_FAILED; +volatile uint32_t *bcm2835_bsc1 = (uint32_t *)MAP_FAILED; +volatile uint32_t *bcm2835_st = (uint32_t *)MAP_FAILED; + + +/* This variable allows us to test on hardware other than RPi. +// It prevents access to the kernel memory, and does not do any peripheral access +// Instead it prints out what it _would_ do if debug were 0 +*/ +static uint8_t debug = 0; + +/* I2C The time needed to transmit one byte. In microseconds. + */ +static int i2c_byte_wait_us = 0; + +/* +// Low level register access functions +*/ + +/* Function to return the pointers to the hardware register bases */ +uint32_t* bcm2835_regbase(uint8_t regbase) +{ + switch (regbase) + { + case BCM2835_REGBASE_ST: + return (uint32_t *)bcm2835_st; + case BCM2835_REGBASE_GPIO: + return (uint32_t *)bcm2835_gpio; + case BCM2835_REGBASE_PWM: + return (uint32_t *)bcm2835_pwm; + case BCM2835_REGBASE_CLK: + return (uint32_t *)bcm2835_clk; + case BCM2835_REGBASE_PADS: + return (uint32_t *)bcm2835_pads; + case BCM2835_REGBASE_SPI0: + return (uint32_t *)bcm2835_spi0; + case BCM2835_REGBASE_BSC0: + return (uint32_t *)bcm2835_bsc0; + case BCM2835_REGBASE_BSC1: + return (uint32_t *)bcm2835_st; + } + return (uint32_t *)MAP_FAILED; +} + +void bcm2835_set_debug(uint8_t d) +{ + debug = d; +} + +unsigned int bcm2835_version(void) +{ + return BCM2835_VERSION; +} + +/* Read with memory barriers from peripheral + * + */ +uint32_t bcm2835_peri_read(volatile uint32_t* paddr) +{ + uint32_t ret; + if (debug) + { + printf("bcm2835_peri_read paddr %08X\n", (unsigned) paddr); + return 0; + } + else + { + __sync_synchronize(); + ret = *paddr; + __sync_synchronize(); + return ret; + } +} + +/* read from peripheral without the read barrier + * This can only be used if more reads to THE SAME peripheral + * will follow. The sequence must terminate with memory barrier + * before any read or write to another peripheral can occur. + * The MB can be explicit, or one of the barrier read/write calls. + */ +uint32_t bcm2835_peri_read_nb(volatile uint32_t* paddr) +{ + if (debug) + { + printf("bcm2835_peri_read_nb paddr %08X\n", (unsigned) paddr); + return 0; + } + else + { + return *paddr; + } +} + +/* Write with memory barriers to peripheral + */ + +void bcm2835_peri_write(volatile uint32_t* paddr, uint32_t value) +{ + if (debug) + { + printf("bcm2835_peri_write paddr %08X, value %08X\n", (unsigned) paddr, value); + } + else + { + __sync_synchronize(); + *paddr = value; + __sync_synchronize(); + } +} + +/* write to peripheral without the write barrier */ +void bcm2835_peri_write_nb(volatile uint32_t* paddr, uint32_t value) +{ + if (debug) + { + printf("bcm2835_peri_write_nb paddr %08X, value %08X\n", + (unsigned) paddr, value); + } + else + { + *paddr = value; + } +} + +/* Set/clear only the bits in value covered by the mask + * This is not atomic - can be interrupted. + */ +void bcm2835_peri_set_bits(volatile uint32_t* paddr, uint32_t value, uint32_t mask) +{ + uint32_t v = bcm2835_peri_read(paddr); + v = (v & ~mask) | (value & mask); + bcm2835_peri_write(paddr, v); +} + +/* +// Low level convenience functions +*/ + +/* Function select +// pin is a BCM2835 GPIO pin number NOT RPi pin number +// There are 6 control registers, each control the functions of a block +// of 10 pins. +// Each control register has 10 sets of 3 bits per GPIO pin: +// +// 000 = GPIO Pin X is an input +// 001 = GPIO Pin X is an output +// 100 = GPIO Pin X takes alternate function 0 +// 101 = GPIO Pin X takes alternate function 1 +// 110 = GPIO Pin X takes alternate function 2 +// 111 = GPIO Pin X takes alternate function 3 +// 011 = GPIO Pin X takes alternate function 4 +// 010 = GPIO Pin X takes alternate function 5 +// +// So the 3 bits for port X are: +// X / 10 + ((X % 10) * 3) +*/ +void bcm2835_gpio_fsel(uint8_t pin, uint8_t mode) +{ + /* Function selects are 10 pins per 32 bit word, 3 bits per pin */ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPFSEL0/4 + (pin/10); + uint8_t shift = (pin % 10) * 3; + uint32_t mask = BCM2835_GPIO_FSEL_MASK << shift; + uint32_t value = mode << shift; + bcm2835_peri_set_bits(paddr, value, mask); +} + +/* Set output pin */ +void bcm2835_gpio_set(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPSET0/4 + pin/32; + uint8_t shift = pin % 32; + bcm2835_peri_write(paddr, 1 << shift); +} + +/* Clear output pin */ +void bcm2835_gpio_clr(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPCLR0/4 + pin/32; + uint8_t shift = pin % 32; + bcm2835_peri_write(paddr, 1 << shift); +} + +/* Set all output pins in the mask */ +void bcm2835_gpio_set_multi(uint32_t mask) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPSET0/4; + bcm2835_peri_write(paddr, mask); +} + +/* Clear all output pins in the mask */ +void bcm2835_gpio_clr_multi(uint32_t mask) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPCLR0/4; + bcm2835_peri_write(paddr, mask); +} + +/* Read input pin */ +uint8_t bcm2835_gpio_lev(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPLEV0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = bcm2835_peri_read(paddr); + return (value & (1 << shift)) ? HIGH : LOW; +} + +/* See if an event detection bit is set +// Sigh cant support interrupts yet +*/ +uint8_t bcm2835_gpio_eds(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPEDS0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = bcm2835_peri_read(paddr); + return (value & (1 << shift)) ? HIGH : LOW; +} + +uint32_t bcm2835_gpio_eds_multi(uint32_t mask) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPEDS0/4; + uint32_t value = bcm2835_peri_read(paddr); + return (value & mask); +} + +/* Write a 1 to clear the bit in EDS */ +void bcm2835_gpio_set_eds(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPEDS0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = 1 << shift; + bcm2835_peri_write(paddr, value); +} + +void bcm2835_gpio_set_eds_multi(uint32_t mask) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPEDS0/4; + bcm2835_peri_write(paddr, mask); +} + +/* Rising edge detect enable */ +void bcm2835_gpio_ren(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPREN0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = 1 << shift; + bcm2835_peri_set_bits(paddr, value, value); +} +void bcm2835_gpio_clr_ren(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPREN0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = 1 << shift; + bcm2835_peri_set_bits(paddr, 0, value); +} + +/* Falling edge detect enable */ +void bcm2835_gpio_fen(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPFEN0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = 1 << shift; + bcm2835_peri_set_bits(paddr, value, value); +} +void bcm2835_gpio_clr_fen(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPFEN0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = 1 << shift; + bcm2835_peri_set_bits(paddr, 0, value); +} + +/* High detect enable */ +void bcm2835_gpio_hen(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPHEN0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = 1 << shift; + bcm2835_peri_set_bits(paddr, value, value); +} +void bcm2835_gpio_clr_hen(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPHEN0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = 1 << shift; + bcm2835_peri_set_bits(paddr, 0, value); +} + +/* Low detect enable */ +void bcm2835_gpio_len(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPLEN0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = 1 << shift; + bcm2835_peri_set_bits(paddr, value, value); +} +void bcm2835_gpio_clr_len(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPLEN0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = 1 << shift; + bcm2835_peri_set_bits(paddr, 0, value); +} + +/* Async rising edge detect enable */ +void bcm2835_gpio_aren(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPAREN0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = 1 << shift; + bcm2835_peri_set_bits(paddr, value, value); +} +void bcm2835_gpio_clr_aren(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPAREN0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = 1 << shift; + bcm2835_peri_set_bits(paddr, 0, value); +} + +/* Async falling edge detect enable */ +void bcm2835_gpio_afen(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPAFEN0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = 1 << shift; + bcm2835_peri_set_bits(paddr, value, value); +} +void bcm2835_gpio_clr_afen(uint8_t pin) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPAFEN0/4 + pin/32; + uint8_t shift = pin % 32; + uint32_t value = 1 << shift; + bcm2835_peri_set_bits(paddr, 0, value); +} + +/* Set pullup/down */ +void bcm2835_gpio_pud(uint8_t pud) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPPUD/4; + bcm2835_peri_write(paddr, pud); +} + +/* Pullup/down clock +// Clocks the value of pud into the GPIO pin +*/ +void bcm2835_gpio_pudclk(uint8_t pin, uint8_t on) +{ + volatile uint32_t* paddr = bcm2835_gpio + BCM2835_GPPUDCLK0/4 + pin/32; + uint8_t shift = pin % 32; + bcm2835_peri_write(paddr, (on ? 1 : 0) << shift); +} + +/* Read GPIO pad behaviour for groups of GPIOs */ +uint32_t bcm2835_gpio_pad(uint8_t group) +{ + if (bcm2835_pads == MAP_FAILED) { + return 0; + } + + volatile uint32_t* paddr = bcm2835_pads + BCM2835_PADS_GPIO_0_27/4 + group; + return bcm2835_peri_read(paddr); +} + +/* Set GPIO pad behaviour for groups of GPIOs +// powerup value for all pads is +// BCM2835_PAD_SLEW_RATE_UNLIMITED | BCM2835_PAD_HYSTERESIS_ENABLED | BCM2835_PAD_DRIVE_8mA +*/ +void bcm2835_gpio_set_pad(uint8_t group, uint32_t control) +{ + if (bcm2835_pads == MAP_FAILED) { + return; + } + + volatile uint32_t* paddr = bcm2835_pads + BCM2835_PADS_GPIO_0_27/4 + group; + bcm2835_peri_write(paddr, control | BCM2835_PAD_PASSWRD); +} + +/* Some convenient arduino-like functions +// milliseconds +*/ +void bcm2835_delay(unsigned int millis) +{ + struct timespec sleeper; + + sleeper.tv_sec = (time_t)(millis / 1000); + sleeper.tv_nsec = (long)(millis % 1000) * 1000000; + nanosleep(&sleeper, NULL); +} + +/* microseconds */ +void bcm2835_delayMicroseconds(uint64_t micros) +{ + struct timespec t1; + uint64_t start; + + if (debug) + { + /* Cant access sytem timers in debug mode */ + printf("bcm2835_delayMicroseconds %lld\n", micros); + return; + } + + /* Calling nanosleep() takes at least 100-200 us, so use it for + // long waits and use a busy wait on the System Timer for the rest. + */ + start = bcm2835_st_read(); + + if (micros > 450) + { + t1.tv_sec = 0; + t1.tv_nsec = 1000 * (long)(micros - 200); + nanosleep(&t1, NULL); + } + + bcm2835_st_delay(start, micros); +} + +/* +// Higher level convenience functions +*/ + +/* Set the state of an output */ +void bcm2835_gpio_write(uint8_t pin, uint8_t on) +{ + if (on) + bcm2835_gpio_set(pin); + else + bcm2835_gpio_clr(pin); +} + +/* Set the state of a all 32 outputs in the mask to on or off */ +void bcm2835_gpio_write_multi(uint32_t mask, uint8_t on) +{ + if (on) + bcm2835_gpio_set_multi(mask); + else + bcm2835_gpio_clr_multi(mask); +} + +/* Set the state of a all 32 outputs in the mask to the values in value */ +void bcm2835_gpio_write_mask(uint32_t value, uint32_t mask) +{ + bcm2835_gpio_set_multi(value & mask); + bcm2835_gpio_clr_multi((~value) & mask); +} + +/* Set the pullup/down resistor for a pin +// +// The GPIO Pull-up/down Clock Registers control the actuation of internal pull-downs on +// the respective GPIO pins. These registers must be used in conjunction with the GPPUD +// register to effect GPIO Pull-up/down changes. The following sequence of events is +// required: +// 1. Write to GPPUD to set the required control signal (i.e. Pull-up or Pull-Down or neither +// to remove the current Pull-up/down) +// 2. Wait 150 cycles ? this provides the required set-up time for the control signal +// 3. Write to GPPUDCLK0/1 to clock the control signal into the GPIO pads you wish to +// modify ? NOTE only the pads which receive a clock will be modified, all others will +// retain their previous state. +// 4. Wait 150 cycles ? this provides the required hold time for the control signal +// 5. Write to GPPUD to remove the control signal +// 6. Write to GPPUDCLK0/1 to remove the clock +// +// RPi has P1-03 and P1-05 with 1k8 pullup resistor +*/ +void bcm2835_gpio_set_pud(uint8_t pin, uint8_t pud) +{ + bcm2835_gpio_pud(pud); + delayMicroseconds(10); + bcm2835_gpio_pudclk(pin, 1); + delayMicroseconds(10); + bcm2835_gpio_pud(BCM2835_GPIO_PUD_OFF); + bcm2835_gpio_pudclk(pin, 0); +} + +int bcm2835_spi_begin(void) +{ + volatile uint32_t* paddr; + + if (bcm2835_spi0 == MAP_FAILED) + return 0; /* bcm2835_init() failed, or not root */ + + /* Set the SPI0 pins to the Alt 0 function to enable SPI0 access on them */ + bcm2835_gpio_fsel(RPI_GPIO_P1_26, BCM2835_GPIO_FSEL_ALT0); /* CE1 */ + bcm2835_gpio_fsel(RPI_GPIO_P1_24, BCM2835_GPIO_FSEL_ALT0); /* CE0 */ + bcm2835_gpio_fsel(RPI_GPIO_P1_21, BCM2835_GPIO_FSEL_ALT0); /* MISO */ + bcm2835_gpio_fsel(RPI_GPIO_P1_19, BCM2835_GPIO_FSEL_ALT0); /* MOSI */ + bcm2835_gpio_fsel(RPI_GPIO_P1_23, BCM2835_GPIO_FSEL_ALT0); /* CLK */ + + /* Set the SPI CS register to the some sensible defaults */ + paddr = bcm2835_spi0 + BCM2835_SPI0_CS/4; + bcm2835_peri_write(paddr, 0); /* All 0s */ + + /* Clear TX and RX fifos */ + bcm2835_peri_write_nb(paddr, BCM2835_SPI0_CS_CLEAR); + + return 1; // OK +} + +void bcm2835_spi_end(void) +{ + /* Set all the SPI0 pins back to input */ + bcm2835_gpio_fsel(RPI_GPIO_P1_26, BCM2835_GPIO_FSEL_INPT); /* CE1 */ + bcm2835_gpio_fsel(RPI_GPIO_P1_24, BCM2835_GPIO_FSEL_INPT); /* CE0 */ + bcm2835_gpio_fsel(RPI_GPIO_P1_21, BCM2835_GPIO_FSEL_INPT); /* MISO */ + bcm2835_gpio_fsel(RPI_GPIO_P1_19, BCM2835_GPIO_FSEL_INPT); /* MOSI */ + bcm2835_gpio_fsel(RPI_GPIO_P1_23, BCM2835_GPIO_FSEL_INPT); /* CLK */ +} + +void bcm2835_spi_setBitOrder(uint8_t __attribute__((unused)) order) +{ + /* BCM2835_SPI_BIT_ORDER_MSBFIRST is the only one supported by SPI0 */ +} + +/* defaults to 0, which means a divider of 65536. +// The divisor must be a power of 2. Odd numbers +// rounded down. The maximum SPI clock rate is +// of the APB clock +*/ +void bcm2835_spi_setClockDivider(uint16_t divider) +{ + volatile uint32_t* paddr = bcm2835_spi0 + BCM2835_SPI0_CLK/4; + bcm2835_peri_write(paddr, divider); +} + +void bcm2835_spi_setDataMode(uint8_t mode) +{ + volatile uint32_t* paddr = bcm2835_spi0 + BCM2835_SPI0_CS/4; + /* Mask in the CPO and CPHA bits of CS */ + bcm2835_peri_set_bits(paddr, mode << 2, BCM2835_SPI0_CS_CPOL | BCM2835_SPI0_CS_CPHA); +} + +/* Writes (and reads) a single byte to SPI */ +uint8_t bcm2835_spi_transfer(uint8_t value) +{ + volatile uint32_t* paddr = bcm2835_spi0 + BCM2835_SPI0_CS/4; + volatile uint32_t* fifo = bcm2835_spi0 + BCM2835_SPI0_FIFO/4; + uint32_t ret; + + /* This is Polled transfer as per section 10.6.1 + // BUG ALERT: what happens if we get interupted in this section, and someone else + // accesses a different peripheral? + // Clear TX and RX fifos + */ + bcm2835_peri_set_bits(paddr, BCM2835_SPI0_CS_CLEAR, BCM2835_SPI0_CS_CLEAR); + + /* Set TA = 1 */ + bcm2835_peri_set_bits(paddr, BCM2835_SPI0_CS_TA, BCM2835_SPI0_CS_TA); + + /* Maybe wait for TXD */ + while (!(bcm2835_peri_read(paddr) & BCM2835_SPI0_CS_TXD)) + ; + + /* Write to FIFO, no barrier */ + bcm2835_peri_write_nb(fifo, value); + + /* Wait for DONE to be set */ + while (!(bcm2835_peri_read_nb(paddr) & BCM2835_SPI0_CS_DONE)) + ; + + /* Read any byte that was sent back by the slave while we sere sending to it */ + ret = bcm2835_peri_read_nb(fifo); + + /* Set TA = 0, and also set the barrier */ + bcm2835_peri_set_bits(paddr, 0, BCM2835_SPI0_CS_TA); + + return ret; +} + +/* Writes (and reads) an number of bytes to SPI */ +void bcm2835_spi_transfernb(char* tbuf, char* rbuf, uint32_t len) +{ + volatile uint32_t* paddr = bcm2835_spi0 + BCM2835_SPI0_CS/4; + volatile uint32_t* fifo = bcm2835_spi0 + BCM2835_SPI0_FIFO/4; + uint32_t TXCnt=0; + uint32_t RXCnt=0; + + /* This is Polled transfer as per section 10.6.1 + // BUG ALERT: what happens if we get interupted in this section, and someone else + // accesses a different peripheral? + */ + + /* Clear TX and RX fifos */ + bcm2835_peri_set_bits(paddr, BCM2835_SPI0_CS_CLEAR, BCM2835_SPI0_CS_CLEAR); + + /* Set TA = 1 */ + bcm2835_peri_set_bits(paddr, BCM2835_SPI0_CS_TA, BCM2835_SPI0_CS_TA); + + /* Use the FIFO's to reduce the interbyte times */ + while((TXCnt < len)||(RXCnt < len)) + { + /* TX fifo not full, so add some more bytes */ + while(((bcm2835_peri_read(paddr) & BCM2835_SPI0_CS_TXD))&&(TXCnt < len )) + { + bcm2835_peri_write_nb(fifo, tbuf[TXCnt]); + TXCnt++; + } + /* Rx fifo not empty, so get the next received bytes */ + while(((bcm2835_peri_read(paddr) & BCM2835_SPI0_CS_RXD))&&( RXCnt < len )) + { + rbuf[RXCnt] = bcm2835_peri_read_nb(fifo); + RXCnt++; + } + } + /* Wait for DONE to be set */ + while (!(bcm2835_peri_read_nb(paddr) & BCM2835_SPI0_CS_DONE)) + ; + + /* Set TA = 0, and also set the barrier */ + bcm2835_peri_set_bits(paddr, 0, BCM2835_SPI0_CS_TA); +} + +/* Writes an number of bytes to SPI */ +void bcm2835_spi_writenb(char* tbuf, uint32_t len) +{ + volatile uint32_t* paddr = bcm2835_spi0 + BCM2835_SPI0_CS/4; + volatile uint32_t* fifo = bcm2835_spi0 + BCM2835_SPI0_FIFO/4; + uint32_t i; + + /* This is Polled transfer as per section 10.6.1 + // BUG ALERT: what happens if we get interupted in this section, and someone else + // accesses a different peripheral? + // Answer: an ISR is required to issue the required memory barriers. + */ + + /* Clear TX and RX fifos */ + bcm2835_peri_set_bits(paddr, BCM2835_SPI0_CS_CLEAR, BCM2835_SPI0_CS_CLEAR); + + /* Set TA = 1 */ + bcm2835_peri_set_bits(paddr, BCM2835_SPI0_CS_TA, BCM2835_SPI0_CS_TA); + + for (i = 0; i < len; i++) + { + /* Maybe wait for TXD */ + while (!(bcm2835_peri_read(paddr) & BCM2835_SPI0_CS_TXD)) + ; + + /* Write to FIFO, no barrier */ + bcm2835_peri_write_nb(fifo, tbuf[i]); + + /* Read from FIFO to prevent stalling */ + while (bcm2835_peri_read(paddr) & BCM2835_SPI0_CS_RXD) + (void) bcm2835_peri_read_nb(fifo); + } + + /* Wait for DONE to be set */ + while (!(bcm2835_peri_read_nb(paddr) & BCM2835_SPI0_CS_DONE)) { + while (bcm2835_peri_read(paddr) & BCM2835_SPI0_CS_RXD) + (void) bcm2835_peri_read_nb(fifo); + }; + + /* Set TA = 0, and also set the barrier */ + bcm2835_peri_set_bits(paddr, 0, BCM2835_SPI0_CS_TA); +} + +/* Writes (and reads) an number of bytes to SPI +// Read bytes are copied over onto the transmit buffer +*/ +void bcm2835_spi_transfern(char* buf, uint32_t len) +{ + bcm2835_spi_transfernb(buf, buf, len); +} + +void bcm2835_spi_chipSelect(uint8_t cs) +{ + volatile uint32_t* paddr = bcm2835_spi0 + BCM2835_SPI0_CS/4; + /* Mask in the CS bits of CS */ + bcm2835_peri_set_bits(paddr, cs, BCM2835_SPI0_CS_CS); +} + +void bcm2835_spi_setChipSelectPolarity(uint8_t cs, uint8_t active) +{ + volatile uint32_t* paddr = bcm2835_spi0 + BCM2835_SPI0_CS/4; + uint8_t shift = 21 + cs; + /* Mask in the appropriate CSPOLn bit */ + bcm2835_peri_set_bits(paddr, active << shift, 1 << shift); +} + +int bcm2835_i2c_begin(void) +{ + uint16_t cdiv; + + if ( bcm2835_bsc0 == MAP_FAILED + || bcm2835_bsc1 == MAP_FAILED) + return 0; /* bcm2835_init() failed, or not root */ + +#ifdef I2C_V1 + volatile uint32_t* paddr = bcm2835_bsc0 + BCM2835_BSC_DIV/4; + /* Set the I2C/BSC0 pins to the Alt 0 function to enable I2C access on them */ + bcm2835_gpio_fsel(RPI_GPIO_P1_03, BCM2835_GPIO_FSEL_ALT0); /* SDA */ + bcm2835_gpio_fsel(RPI_GPIO_P1_05, BCM2835_GPIO_FSEL_ALT0); /* SCL */ +#else + volatile uint32_t* paddr = bcm2835_bsc1 + BCM2835_BSC_DIV/4; + /* Set the I2C/BSC1 pins to the Alt 0 function to enable I2C access on them */ + bcm2835_gpio_fsel(RPI_V2_GPIO_P1_03, BCM2835_GPIO_FSEL_ALT0); /* SDA */ + bcm2835_gpio_fsel(RPI_V2_GPIO_P1_05, BCM2835_GPIO_FSEL_ALT0); /* SCL */ +#endif + + /* Read the clock divider register */ + cdiv = bcm2835_peri_read(paddr); + /* Calculate time for transmitting one byte + // 1000000 = micros seconds in a second + // 9 = Clocks per byte : 8 bits + ACK + */ + i2c_byte_wait_us = ((float)cdiv / BCM2835_CORE_CLK_HZ) * 1000000 * 9; + + return 1; +} + +void bcm2835_i2c_end(void) +{ +#ifdef I2C_V1 + /* Set all the I2C/BSC0 pins back to input */ + bcm2835_gpio_fsel(RPI_GPIO_P1_03, BCM2835_GPIO_FSEL_INPT); /* SDA */ + bcm2835_gpio_fsel(RPI_GPIO_P1_05, BCM2835_GPIO_FSEL_INPT); /* SCL */ +#else + /* Set all the I2C/BSC1 pins back to input */ + bcm2835_gpio_fsel(RPI_V2_GPIO_P1_03, BCM2835_GPIO_FSEL_INPT); /* SDA */ + bcm2835_gpio_fsel(RPI_V2_GPIO_P1_05, BCM2835_GPIO_FSEL_INPT); /* SCL */ +#endif +} + +void bcm2835_i2c_setSlaveAddress(uint8_t addr) +{ + /* Set I2C Device Address */ +#ifdef I2C_V1 + volatile uint32_t* paddr = bcm2835_bsc0 + BCM2835_BSC_A/4; +#else + volatile uint32_t* paddr = bcm2835_bsc1 + BCM2835_BSC_A/4; +#endif + bcm2835_peri_write(paddr, addr); +} + +/* defaults to 0x5dc, should result in a 166.666 kHz I2C clock frequency. +// The divisor must be a power of 2. Odd numbers +// rounded down. +*/ +void bcm2835_i2c_setClockDivider(uint16_t divider) +{ +#ifdef I2C_V1 + volatile uint32_t* paddr = bcm2835_bsc0 + BCM2835_BSC_DIV/4; +#else + volatile uint32_t* paddr = bcm2835_bsc1 + BCM2835_BSC_DIV/4; +#endif + bcm2835_peri_write(paddr, divider); + /* Calculate time for transmitting one byte + // 1000000 = micros seconds in a second + // 9 = Clocks per byte : 8 bits + ACK + */ + i2c_byte_wait_us = ((float)divider / BCM2835_CORE_CLK_HZ) * 1000000 * 9; +} + +/* set I2C clock divider by means of a baudrate number */ +void bcm2835_i2c_set_baudrate(uint32_t baudrate) +{ + uint32_t divider; + /* use 0xFFFE mask to limit a max value and round down any odd number */ + divider = (BCM2835_CORE_CLK_HZ / baudrate) & 0xFFFE; + bcm2835_i2c_setClockDivider( (uint16_t)divider ); +} + +/* Writes an number of bytes to I2C */ +uint8_t bcm2835_i2c_write(const char * buf, uint32_t len) +{ +#ifdef I2C_V1 + volatile uint32_t* dlen = bcm2835_bsc0 + BCM2835_BSC_DLEN/4; + volatile uint32_t* fifo = bcm2835_bsc0 + BCM2835_BSC_FIFO/4; + volatile uint32_t* status = bcm2835_bsc0 + BCM2835_BSC_S/4; + volatile uint32_t* control = bcm2835_bsc0 + BCM2835_BSC_C/4; +#else + volatile uint32_t* dlen = bcm2835_bsc1 + BCM2835_BSC_DLEN/4; + volatile uint32_t* fifo = bcm2835_bsc1 + BCM2835_BSC_FIFO/4; + volatile uint32_t* status = bcm2835_bsc1 + BCM2835_BSC_S/4; + volatile uint32_t* control = bcm2835_bsc1 + BCM2835_BSC_C/4; +#endif + + uint32_t remaining = len; + uint32_t i = 0; + uint8_t reason = BCM2835_I2C_REASON_OK; + + /* Clear FIFO */ + bcm2835_peri_set_bits(control, BCM2835_BSC_C_CLEAR_1 , BCM2835_BSC_C_CLEAR_1 ); + /* Clear Status */ + bcm2835_peri_write(status, BCM2835_BSC_S_CLKT | BCM2835_BSC_S_ERR | BCM2835_BSC_S_DONE); + /* Set Data Length */ + bcm2835_peri_write(dlen, len); + /* pre populate FIFO with max buffer */ + while( remaining && ( i < BCM2835_BSC_FIFO_SIZE ) ) + { + bcm2835_peri_write_nb(fifo, buf[i]); + i++; + remaining--; + } + + /* Enable device and start transfer */ + bcm2835_peri_write(control, BCM2835_BSC_C_I2CEN | BCM2835_BSC_C_ST); + + /* Transfer is over when BCM2835_BSC_S_DONE */ + while(!(bcm2835_peri_read(status) & BCM2835_BSC_S_DONE )) + { + while ( remaining && (bcm2835_peri_read(status) & BCM2835_BSC_S_TXD )) + { + /* Write to FIFO */ + bcm2835_peri_write(fifo, buf[i]); + i++; + remaining--; + } + } + + /* Received a NACK */ + if (bcm2835_peri_read(status) & BCM2835_BSC_S_ERR) + { + reason = BCM2835_I2C_REASON_ERROR_NACK; + } + + /* Received Clock Stretch Timeout */ + else if (bcm2835_peri_read(status) & BCM2835_BSC_S_CLKT) + { + reason = BCM2835_I2C_REASON_ERROR_CLKT; + } + + /* Not all data is sent */ + else if (remaining) + { + reason = BCM2835_I2C_REASON_ERROR_DATA; + } + + bcm2835_peri_set_bits(control, BCM2835_BSC_S_DONE , BCM2835_BSC_S_DONE); + + return reason; +} + +/* Read an number of bytes from I2C */ +uint8_t bcm2835_i2c_read(char* buf, uint32_t len) +{ +#ifdef I2C_V1 + volatile uint32_t* dlen = bcm2835_bsc0 + BCM2835_BSC_DLEN/4; + volatile uint32_t* fifo = bcm2835_bsc0 + BCM2835_BSC_FIFO/4; + volatile uint32_t* status = bcm2835_bsc0 + BCM2835_BSC_S/4; + volatile uint32_t* control = bcm2835_bsc0 + BCM2835_BSC_C/4; +#else + volatile uint32_t* dlen = bcm2835_bsc1 + BCM2835_BSC_DLEN/4; + volatile uint32_t* fifo = bcm2835_bsc1 + BCM2835_BSC_FIFO/4; + volatile uint32_t* status = bcm2835_bsc1 + BCM2835_BSC_S/4; + volatile uint32_t* control = bcm2835_bsc1 + BCM2835_BSC_C/4; +#endif + + uint32_t remaining = len; + uint32_t i = 0; + uint8_t reason = BCM2835_I2C_REASON_OK; + + /* Clear FIFO */ + bcm2835_peri_set_bits(control, BCM2835_BSC_C_CLEAR_1 , BCM2835_BSC_C_CLEAR_1 ); + /* Clear Status */ + bcm2835_peri_write_nb(status, BCM2835_BSC_S_CLKT | BCM2835_BSC_S_ERR | BCM2835_BSC_S_DONE); + /* Set Data Length */ + bcm2835_peri_write_nb(dlen, len); + /* Start read */ + bcm2835_peri_write_nb(control, BCM2835_BSC_C_I2CEN | BCM2835_BSC_C_ST | BCM2835_BSC_C_READ); + + /* wait for transfer to complete */ + while (!(bcm2835_peri_read_nb(status) & BCM2835_BSC_S_DONE)) + { + /* we must empty the FIFO as it is populated and not use any delay */ + while (bcm2835_peri_read_nb(status) & BCM2835_BSC_S_RXD) + { + /* Read from FIFO, no barrier */ + buf[i] = bcm2835_peri_read_nb(fifo); + i++; + remaining--; + } + } + + /* transfer has finished - grab any remaining stuff in FIFO */ + while (remaining && (bcm2835_peri_read_nb(status) & BCM2835_BSC_S_RXD)) + { + /* Read from FIFO, no barrier */ + buf[i] = bcm2835_peri_read_nb(fifo); + i++; + remaining--; + } + + /* Received a NACK */ + if (bcm2835_peri_read(status) & BCM2835_BSC_S_ERR) + { + reason = BCM2835_I2C_REASON_ERROR_NACK; + } + + /* Received Clock Stretch Timeout */ + else if (bcm2835_peri_read(status) & BCM2835_BSC_S_CLKT) + { + reason = BCM2835_I2C_REASON_ERROR_CLKT; + } + + /* Not all data is received */ + else if (remaining) + { + reason = BCM2835_I2C_REASON_ERROR_DATA; + } + + bcm2835_peri_set_bits(control, BCM2835_BSC_S_DONE , BCM2835_BSC_S_DONE); + + return reason; +} + +/* Read an number of bytes from I2C sending a repeated start after writing +// the required register. Only works if your device supports this mode +*/ +uint8_t bcm2835_i2c_read_register_rs(char* regaddr, char* buf, uint32_t len) +{ +#ifdef I2C_V1 + volatile uint32_t* dlen = bcm2835_bsc0 + BCM2835_BSC_DLEN/4; + volatile uint32_t* fifo = bcm2835_bsc0 + BCM2835_BSC_FIFO/4; + volatile uint32_t* status = bcm2835_bsc0 + BCM2835_BSC_S/4; + volatile uint32_t* control = bcm2835_bsc0 + BCM2835_BSC_C/4; +#else + volatile uint32_t* dlen = bcm2835_bsc1 + BCM2835_BSC_DLEN/4; + volatile uint32_t* fifo = bcm2835_bsc1 + BCM2835_BSC_FIFO/4; + volatile uint32_t* status = bcm2835_bsc1 + BCM2835_BSC_S/4; + volatile uint32_t* control = bcm2835_bsc1 + BCM2835_BSC_C/4; +#endif + uint32_t remaining = len; + uint32_t i = 0; + uint8_t reason = BCM2835_I2C_REASON_OK; + + /* Clear FIFO */ + bcm2835_peri_set_bits(control, BCM2835_BSC_C_CLEAR_1 , BCM2835_BSC_C_CLEAR_1 ); + /* Clear Status */ + bcm2835_peri_write(status, BCM2835_BSC_S_CLKT | BCM2835_BSC_S_ERR | BCM2835_BSC_S_DONE); + /* Set Data Length */ + bcm2835_peri_write(dlen, 1); + /* Enable device and start transfer */ + bcm2835_peri_write(control, BCM2835_BSC_C_I2CEN); + bcm2835_peri_write(fifo, regaddr[0]); + bcm2835_peri_write(control, BCM2835_BSC_C_I2CEN | BCM2835_BSC_C_ST); + + /* poll for transfer has started */ + while ( !( bcm2835_peri_read(status) & BCM2835_BSC_S_TA ) ) + { + /* Linux may cause us to miss entire transfer stage */ + if(bcm2835_peri_read(status) & BCM2835_BSC_S_DONE) + break; + } + + /* Send a repeated start with read bit set in address */ + bcm2835_peri_write(dlen, len); + bcm2835_peri_write(control, BCM2835_BSC_C_I2CEN | BCM2835_BSC_C_ST | BCM2835_BSC_C_READ ); + + /* Wait for write to complete and first byte back. */ + bcm2835_delayMicroseconds(i2c_byte_wait_us * 3); + + /* wait for transfer to complete */ + while (!(bcm2835_peri_read(status) & BCM2835_BSC_S_DONE)) + { + /* we must empty the FIFO as it is populated and not use any delay */ + while (remaining && bcm2835_peri_read(status) & BCM2835_BSC_S_RXD) + { + /* Read from FIFO */ + buf[i] = bcm2835_peri_read(fifo); + i++; + remaining--; + } + } + + /* transfer has finished - grab any remaining stuff in FIFO */ + while (remaining && (bcm2835_peri_read(status) & BCM2835_BSC_S_RXD)) + { + /* Read from FIFO */ + buf[i] = bcm2835_peri_read(fifo); + i++; + remaining--; + } + + /* Received a NACK */ + if (bcm2835_peri_read(status) & BCM2835_BSC_S_ERR) + { + reason = BCM2835_I2C_REASON_ERROR_NACK; + } + + /* Received Clock Stretch Timeout */ + else if (bcm2835_peri_read(status) & BCM2835_BSC_S_CLKT) + { + reason = BCM2835_I2C_REASON_ERROR_CLKT; + } + + /* Not all data is sent */ + else if (remaining) + { + reason = BCM2835_I2C_REASON_ERROR_DATA; + } + + bcm2835_peri_set_bits(control, BCM2835_BSC_S_DONE , BCM2835_BSC_S_DONE); + + return reason; +} + +/* Sending an arbitrary number of bytes before issuing a repeated start +// (with no prior stop) and reading a response. Some devices require this behavior. +*/ +uint8_t bcm2835_i2c_write_read_rs(char* cmds, uint32_t cmds_len, char* buf, uint32_t buf_len) +{ +#ifdef I2C_V1 + volatile uint32_t* dlen = bcm2835_bsc0 + BCM2835_BSC_DLEN/4; + volatile uint32_t* fifo = bcm2835_bsc0 + BCM2835_BSC_FIFO/4; + volatile uint32_t* status = bcm2835_bsc0 + BCM2835_BSC_S/4; + volatile uint32_t* control = bcm2835_bsc0 + BCM2835_BSC_C/4; +#else + volatile uint32_t* dlen = bcm2835_bsc1 + BCM2835_BSC_DLEN/4; + volatile uint32_t* fifo = bcm2835_bsc1 + BCM2835_BSC_FIFO/4; + volatile uint32_t* status = bcm2835_bsc1 + BCM2835_BSC_S/4; + volatile uint32_t* control = bcm2835_bsc1 + BCM2835_BSC_C/4; +#endif + + uint32_t remaining = cmds_len; + uint32_t i = 0; + uint8_t reason = BCM2835_I2C_REASON_OK; + + /* Clear FIFO */ + bcm2835_peri_set_bits(control, BCM2835_BSC_C_CLEAR_1 , BCM2835_BSC_C_CLEAR_1 ); + + /* Clear Status */ + bcm2835_peri_write(status, BCM2835_BSC_S_CLKT | BCM2835_BSC_S_ERR | BCM2835_BSC_S_DONE); + + /* Set Data Length */ + bcm2835_peri_write(dlen, cmds_len); + + /* pre populate FIFO with max buffer */ + while( remaining && ( i < BCM2835_BSC_FIFO_SIZE ) ) + { + bcm2835_peri_write_nb(fifo, cmds[i]); + i++; + remaining--; + } + + /* Enable device and start transfer */ + bcm2835_peri_write(control, BCM2835_BSC_C_I2CEN | BCM2835_BSC_C_ST); + + /* poll for transfer has started (way to do repeated start, from BCM2835 datasheet) */ + while ( !( bcm2835_peri_read(status) & BCM2835_BSC_S_TA ) ) + { + /* Linux may cause us to miss entire transfer stage */ + if(bcm2835_peri_read_nb(status) & BCM2835_BSC_S_DONE) + break; + } + + remaining = buf_len; + i = 0; + + /* Send a repeated start with read bit set in address */ + bcm2835_peri_write(dlen, buf_len); + bcm2835_peri_write(control, BCM2835_BSC_C_I2CEN | BCM2835_BSC_C_ST | BCM2835_BSC_C_READ ); + + /* Wait for write to complete and first byte back. */ + bcm2835_delayMicroseconds(i2c_byte_wait_us * (cmds_len + 1)); + + /* wait for transfer to complete */ + while (!(bcm2835_peri_read_nb(status) & BCM2835_BSC_S_DONE)) + { + /* we must empty the FIFO as it is populated and not use any delay */ + while (remaining && bcm2835_peri_read(status) & BCM2835_BSC_S_RXD) + { + /* Read from FIFO, no barrier */ + buf[i] = bcm2835_peri_read_nb(fifo); + i++; + remaining--; + } + } + + /* transfer has finished - grab any remaining stuff in FIFO */ + while (remaining && (bcm2835_peri_read(status) & BCM2835_BSC_S_RXD)) + { + /* Read from FIFO */ + buf[i] = bcm2835_peri_read(fifo); + i++; + remaining--; + } + + /* Received a NACK */ + if (bcm2835_peri_read(status) & BCM2835_BSC_S_ERR) + { + reason = BCM2835_I2C_REASON_ERROR_NACK; + } + + /* Received Clock Stretch Timeout */ + else if (bcm2835_peri_read(status) & BCM2835_BSC_S_CLKT) + { + reason = BCM2835_I2C_REASON_ERROR_CLKT; + } + + /* Not all data is sent */ + else if (remaining) + { + reason = BCM2835_I2C_REASON_ERROR_DATA; + } + + bcm2835_peri_set_bits(control, BCM2835_BSC_S_DONE , BCM2835_BSC_S_DONE); + + return reason; +} + +/* Read the System Timer Counter (64-bits) */ +uint64_t bcm2835_st_read(void) +{ + volatile uint32_t* paddr; + uint32_t hi, lo; + uint64_t st; + paddr = bcm2835_st + BCM2835_ST_CHI/4; + hi = bcm2835_peri_read(paddr); + + paddr = bcm2835_st + BCM2835_ST_CLO/4; + lo = bcm2835_peri_read(paddr); + + paddr = bcm2835_st + BCM2835_ST_CHI/4; + st = bcm2835_peri_read(paddr); + + /* Test for overflow */ + if (st == hi) + { + st <<= 32; + st += lo; + } + else + { + st <<= 32; + paddr = bcm2835_st + BCM2835_ST_CLO/4; + st += bcm2835_peri_read(paddr); + } + return st; +} + +/* Delays for the specified number of microseconds with offset */ +void bcm2835_st_delay(uint64_t offset_micros, uint64_t micros) +{ + uint64_t compare = offset_micros + micros; + + while(bcm2835_st_read() < compare) + ; +} + +/* PWM */ + +void bcm2835_pwm_set_clock(uint32_t divisor) +{ + if ( bcm2835_clk == MAP_FAILED + || bcm2835_pwm == MAP_FAILED) + return; /* bcm2835_init() failed or not root */ + + /* From Gerts code */ + divisor &= 0xfff; + /* Stop PWM clock */ + bcm2835_peri_write(bcm2835_clk + BCM2835_PWMCLK_CNTL, BCM2835_PWM_PASSWRD | 0x01); + bcm2835_delay(110); /* Prevents clock going slow */ + /* Wait for the clock to be not busy */ + while ((bcm2835_peri_read(bcm2835_clk + BCM2835_PWMCLK_CNTL) & 0x80) != 0) + bcm2835_delay(1); + /* set the clock divider and enable PWM clock */ + bcm2835_peri_write(bcm2835_clk + BCM2835_PWMCLK_DIV, BCM2835_PWM_PASSWRD | (divisor << 12)); + bcm2835_peri_write(bcm2835_clk + BCM2835_PWMCLK_CNTL, BCM2835_PWM_PASSWRD | 0x11); /* Source=osc and enable */ +} + +void bcm2835_pwm_set_mode(uint8_t channel, uint8_t markspace, uint8_t enabled) +{ + if ( bcm2835_clk == MAP_FAILED + || bcm2835_pwm == MAP_FAILED) + return; /* bcm2835_init() failed or not root */ + + uint32_t control = bcm2835_peri_read(bcm2835_pwm + BCM2835_PWM_CONTROL); + + if (channel == 0) + { + if (markspace) + control |= BCM2835_PWM0_MS_MODE; + else + control &= ~BCM2835_PWM0_MS_MODE; + if (enabled) + control |= BCM2835_PWM0_ENABLE; + else + control &= ~BCM2835_PWM0_ENABLE; + } + else if (channel == 1) + { + if (markspace) + control |= BCM2835_PWM1_MS_MODE; + else + control &= ~BCM2835_PWM1_MS_MODE; + if (enabled) + control |= BCM2835_PWM1_ENABLE; + else + control &= ~BCM2835_PWM1_ENABLE; + } + + /* If you use the barrier here, wierd things happen, and the commands dont work */ + bcm2835_peri_write_nb(bcm2835_pwm + BCM2835_PWM_CONTROL, control); + /* bcm2835_peri_write_nb(bcm2835_pwm + BCM2835_PWM_CONTROL, BCM2835_PWM0_ENABLE | BCM2835_PWM1_ENABLE | BCM2835_PWM0_MS_MODE | BCM2835_PWM1_MS_MODE); */ + +} + +void bcm2835_pwm_set_range(uint8_t channel, uint32_t range) +{ + if ( bcm2835_clk == MAP_FAILED + || bcm2835_pwm == MAP_FAILED) + return; /* bcm2835_init() failed or not root */ + + if (channel == 0) + bcm2835_peri_write_nb(bcm2835_pwm + BCM2835_PWM0_RANGE, range); + else if (channel == 1) + bcm2835_peri_write_nb(bcm2835_pwm + BCM2835_PWM1_RANGE, range); +} + +void bcm2835_pwm_set_data(uint8_t channel, uint32_t data) +{ + if ( bcm2835_clk == MAP_FAILED + || bcm2835_pwm == MAP_FAILED) + return; /* bcm2835_init() failed or not root */ + + if (channel == 0) + bcm2835_peri_write_nb(bcm2835_pwm + BCM2835_PWM0_DATA, data); + else if (channel == 1) + bcm2835_peri_write_nb(bcm2835_pwm + BCM2835_PWM1_DATA, data); +} + +/* Allocate page-aligned memory. */ +void *malloc_aligned(size_t size) +{ + void *mem; + errno = posix_memalign(&mem, BCM2835_PAGE_SIZE, size); + return (errno ? NULL : mem); +} + +/* Map 'size' bytes starting at 'off' in file 'fd' to memory. +// Return mapped address on success, MAP_FAILED otherwise. +// On error print message. +*/ +static void *mapmem(const char *msg, size_t size, int fd, off_t off) +{ + void *map = mmap(NULL, size, (PROT_READ | PROT_WRITE), MAP_SHARED, fd, off); + if (map == MAP_FAILED) + mys_log(LOG_ERR, "bcm2835_init: %s mmap failed: %s\n", msg, strerror(errno)); + return map; +} + +static void unmapmem(void **pmem, size_t size) +{ + if (*pmem == MAP_FAILED) return; + munmap(*pmem, size); + *pmem = MAP_FAILED; +} + +/* Initialise this library. */ +int bcm2835_init(void) +{ + int memfd; + int ok; + FILE *fp; + + if (debug) + { + bcm2835_peripherals = (uint32_t*)BCM2835_PERI_BASE; + + bcm2835_pads = bcm2835_peripherals + BCM2835_GPIO_PADS/4; + bcm2835_clk = bcm2835_peripherals + BCM2835_CLOCK_BASE/4; + bcm2835_gpio = bcm2835_peripherals + BCM2835_GPIO_BASE/4; + bcm2835_pwm = bcm2835_peripherals + BCM2835_GPIO_PWM/4; + bcm2835_spi0 = bcm2835_peripherals + BCM2835_SPI0_BASE/4; + bcm2835_bsc0 = bcm2835_peripherals + BCM2835_BSC0_BASE/4; + bcm2835_bsc1 = bcm2835_peripherals + BCM2835_BSC1_BASE/4; + bcm2835_st = bcm2835_peripherals + BCM2835_ST_BASE/4; + return 1; /* Success */ + } + + /* Figure out the base and size of the peripheral address block + // using the device-tree. Required for RPi2, optional for RPi 1 + */ + if ((fp = fopen(BMC2835_RPI2_DT_FILENAME , "rb"))) + { + unsigned char buf[4]; + fseek(fp, BMC2835_RPI2_DT_PERI_BASE_ADDRESS_OFFSET, SEEK_SET); + if (fread(buf, 1, sizeof(buf), fp) == sizeof(buf)) + bcm2835_peripherals_base = (uint32_t *)(buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3] << 0); + fseek(fp, BMC2835_RPI2_DT_PERI_SIZE_OFFSET, SEEK_SET); + if (fread(buf, 1, sizeof(buf), fp) == sizeof(buf)) + bcm2835_peripherals_size = (buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3] << 0); + fclose(fp); + } + /* else we are prob on RPi 1 with BCM2835, and use the hardwired defaults */ + + /* Now get ready to map the peripherals block + * If we are not root, try for the new /dev/gpiomem interface and accept + * the fact that we can only access GPIO + * else try for the /dev/mem interface and get access to everything + */ + memfd = -1; + ok = 0; + if (geteuid() == 0) + { + /* Open the master /dev/mem device */ + if ((memfd = open("/dev/mem", O_RDWR | O_SYNC) ) < 0) + { + mys_log(LOG_ERR, "bcm2835_init: Unable to open /dev/mem: %s\n", + strerror(errno)) ; + goto exit; + } + + /* Base of the peripherals block is mapped to VM */ + bcm2835_peripherals = mapmem("gpio", bcm2835_peripherals_size, memfd, (uint32_t)bcm2835_peripherals_base); + if (bcm2835_peripherals == MAP_FAILED) goto exit; + + /* Now compute the base addresses of various peripherals, + // which are at fixed offsets within the mapped peripherals block + // Caution: bcm2835_peripherals is uint32_t*, so divide offsets by 4 + */ + bcm2835_gpio = bcm2835_peripherals + BCM2835_GPIO_BASE/4; + bcm2835_pwm = bcm2835_peripherals + BCM2835_GPIO_PWM/4; + bcm2835_clk = bcm2835_peripherals + BCM2835_CLOCK_BASE/4; + bcm2835_pads = bcm2835_peripherals + BCM2835_GPIO_PADS/4; + bcm2835_spi0 = bcm2835_peripherals + BCM2835_SPI0_BASE/4; + bcm2835_bsc0 = bcm2835_peripherals + BCM2835_BSC0_BASE/4; /* I2C */ + bcm2835_bsc1 = bcm2835_peripherals + BCM2835_BSC1_BASE/4; /* I2C */ + bcm2835_st = bcm2835_peripherals + BCM2835_ST_BASE/4; + + ok = 1; + } + else + { + /* Not root, try /dev/gpiomem */ + /* Open the master /dev/mem device */ + if ((memfd = open("/dev/gpiomem", O_RDWR | O_SYNC) ) < 0) + { + mys_log(LOG_ERR, "bcm2835_init: Unable to open /dev/gpiomem: %s\n", + strerror(errno)) ; + goto exit; + } + + /* Base of the peripherals block is mapped to VM */ + bcm2835_peripherals_base = 0; + bcm2835_peripherals = mapmem("gpio", bcm2835_peripherals_size, memfd, (uint32_t)bcm2835_peripherals_base); + if (bcm2835_peripherals == MAP_FAILED) goto exit; + bcm2835_gpio = bcm2835_peripherals; + ok = 1; + } + +exit: + if (memfd >= 0) + close(memfd); + + if (!ok) + bcm2835_close(); + + return ok; +} + +/* Close this library and deallocate everything */ +int bcm2835_close(void) +{ + if (debug) return 1; /* Success */ + + unmapmem((void**) &bcm2835_peripherals, bcm2835_peripherals_size); + bcm2835_peripherals = MAP_FAILED; + bcm2835_gpio = MAP_FAILED; + bcm2835_pwm = MAP_FAILED; + bcm2835_clk = MAP_FAILED; + bcm2835_pads = MAP_FAILED; + bcm2835_spi0 = MAP_FAILED; + bcm2835_bsc0 = MAP_FAILED; + bcm2835_bsc1 = MAP_FAILED; + bcm2835_st = MAP_FAILED; + return 1; /* Success */ +} + +#ifdef BCM2835_TEST +/* this is a simple test program that prints out what it will do rather than +// actually doing it +*/ +int main(int argc, char **argv) +{ + /* Be non-destructive */ + bcm2835_set_debug(1); + + if (!bcm2835_init()) + return 1; + + /* Configure some GPIO pins fo some testing + // Set RPI pin P1-11 to be an output + */ + bcm2835_gpio_fsel(RPI_GPIO_P1_11, BCM2835_GPIO_FSEL_OUTP); + /* Set RPI pin P1-15 to be an input */ + bcm2835_gpio_fsel(RPI_GPIO_P1_15, BCM2835_GPIO_FSEL_INPT); + /* with a pullup */ + bcm2835_gpio_set_pud(RPI_GPIO_P1_15, BCM2835_GPIO_PUD_UP); + /* And a low detect enable */ + bcm2835_gpio_len(RPI_GPIO_P1_15); + /* and input hysteresis disabled on GPIOs 0 to 27 */ + bcm2835_gpio_set_pad(BCM2835_PAD_GROUP_GPIO_0_27, BCM2835_PAD_SLEW_RATE_UNLIMITED|BCM2835_PAD_DRIVE_8mA); + +#if 1 + /* Blink */ + while (1) + { + /* Turn it on */ + bcm2835_gpio_write(RPI_GPIO_P1_11, HIGH); + + /* wait a bit */ + bcm2835_delay(500); + + /* turn it off */ + bcm2835_gpio_write(RPI_GPIO_P1_11, LOW); + + /* wait a bit */ + bcm2835_delay(500); + } +#endif + +#if 0 + /* Read input */ + while (1) + { + /* Read some data */ + uint8_t value = bcm2835_gpio_lev(RPI_GPIO_P1_15); + printf("read from pin 15: %d\n", value); + + /* wait a bit */ + bcm2835_delay(500); + } +#endif + +#if 0 + /* Look for a low event detection + // eds will be set whenever pin 15 goes low + */ + while (1) + { + if (bcm2835_gpio_eds(RPI_GPIO_P1_15)) + { + /* Now clear the eds flag by setting it to 1 */ + bcm2835_gpio_set_eds(RPI_GPIO_P1_15); + printf("low event detect for pin 15\n"); + } + + /* wait a bit */ + bcm2835_delay(500); + } +#endif + + if (!bcm2835_close()) + return 1; + + return 0; +} +#endif diff --git a/drivers/RPi/bcm2835.h b/drivers/RPi/bcm2835.h new file mode 100644 index 0000000..26acee2 --- /dev/null +++ b/drivers/RPi/bcm2835.h @@ -0,0 +1,1648 @@ +/* bcm2835.h + + C and C++ support for Broadcom BCM 2835 as used in Raspberry Pi + + Author: Mike McCauley + Copyright (C) 2011-2013 Mike McCauley + $Id: bcm2835.h,v 1.20 2015/03/31 04:55:41 mikem Exp mikem $ +*/ + +/*! \defgroup BCM2835grp C library for Broadcom BCM 2835 as used in Raspberry Pi + \ingroup internals + + This is a C library for Raspberry Pi (RPi). It provides access to + GPIO and other IO functions on the Broadcom BCM 2835 chip, + allowing access to the GPIO pins on the + 26 pin IDE plug on the RPi board so you can control and interface with various external devices. + + It provides functions for reading digital inputs and setting digital outputs, using SPI and I2C, + and for accessing the system timers. + Pin event detection is supported by polling (interrupts are not supported). + + It is C++ compatible, and installs as a header file and non-shared library on + any Linux-based distro (but clearly is no use except on Raspberry Pi or another board with + BCM 2835). + + The version of the package that this documentation refers to can be downloaded + from http://www.airspayce.com/mikem/bcm2835/bcm2835-1.50.tar.gz + You can find the latest version at http://www.airspayce.com/mikem/bcm2835 + + Several example programs are provided. + + Based on data in http://elinux.org/RPi_Low-level_peripherals and + http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf + and http://www.scribd.com/doc/101830961/GPIO-Pads-Control2 + + You can also find online help and discussion at http://groups.google.com/group/bcm2835 + Please use that group for all questions and discussions on this topic. + Do not contact the author directly, unless it is to discuss commercial licensing. + Before asking a question or reporting a bug, please read http://www.catb.org/esr/faqs/smart-questions.html + + Tested on debian6-19-04-2012, 2012-07-15-wheezy-raspbian, 2013-07-26-wheezy-raspbian + and Occidentalisv01, 2016-02-09 Raspbian Jessie. + CAUTION: it has been observed that when detect enables such as bcm2835_gpio_len() + are used and the pin is pulled LOW + it can cause temporary hangs on 2012-07-15-wheezy-raspbian, 2013-07-26-wheezy-raspbian + and Occidentalisv01. + Reason for this is not yet determined, but we suspect that an interrupt handler is + hitting a hard loop on those OSs. + If you must use bcm2835_gpio_len() and friends, make sure you disable the pins with + bcm2835_gpio_clr_len() and friends after use. + + \par Running as root + + Prior to the release of Raspbian Jessie in Feb 2016, access to any + peripheral device via /dev/mem on the RPi required the process to + run as root. Raspbian Jessie permits non-root users to access the + GPIO peripheral (only) via /dev/gpiomem, and this library supports + that limited mode of operation. + + If the library runs with effective UID of 0 (ie root), then + bcm2835_init() will attempt to open /dev/mem, and, if successful, it + will permit use of all peripherals and library functions. + + If the library runs with any other effective UID (ie not root), then + bcm2835_init() will attempt to open /dev/gpiomem, and, if + successful, will only permit GPIO operations. In particular, + bcm2835_spi_begin() and bcm2835_i2c_begin() will return false and all + other non-gpio operations may fail silently or crash. + + \par Installation + + This library consists of a single non-shared library and header file, which will be + installed in the usual places by make install + + \code + # download the latest version of the library, say bcm2835-1.xx.tar.gz, then: + tar zxvf bcm2835-1.xx.tar.gz + cd bcm2835-1.xx + ./configure + make + sudo make check + sudo make install + \endcode + + \par Physical Addresses + + The functions bcm2835_peri_read(), bcm2835_peri_write() and bcm2835_peri_set_bits() + are low level peripheral register access functions. They are designed to use + physical addresses as described in section 1.2.3 ARM physical addresses + of the BCM2835 ARM Peripherals manual. + Physical addresses range from 0x20000000 to 0x20FFFFFF for peripherals. The bus + addresses for peripherals are set up to map onto the peripheral bus address range starting at + 0x7E000000. Thus a peripheral advertised in the manual at bus address 0x7Ennnnnn is available at + physical address 0x20nnnnnn. + + On RPI 2, the peripheral addresses are different and the bcm2835 library gets them + from reading /proc/device-tree/soc/ranges. This is only availble with recent versions of the kernel on RPI 2. + + After initialisation, the base address of the various peripheral + registers are available with the following + externals: + bcm2835_gpio + bcm2835_pwm + bcm2835_clk + bcm2835_pads + bcm2835_spio0 + bcm2835_st + bcm2835_bsc0 + bcm2835_bsc1 + + \par Raspberry Pi 2 (RPI2) + + For this library to work correctly on RPI2, you MUST have the device tree support enabled in the kernel. + You should also ensure you are using the latest version of Linux. The library has been tested on RPI2 + with 2015-02-16-raspbian-wheezy and ArchLinuxARM-rpi-2 as of 2015-03-29. + + When device tree suport is enabled, the file /proc/device-tree/soc/ranges will appear in the file system, + and the bcm2835 module relies on its presence to correctly run on RPI2 (it is optional for RPI1). + Without device tree support enabled and the presence of this file, it will not work on RPI2. + + To enable device tree support: + + \code + sudo raspi-config + under Advanced Options - enable Device Tree + Reboot. + \endcode + + \par Pin Numbering + + The GPIO pin numbering as used by RPi is different to and inconsistent with the underlying + BCM 2835 chip pin numbering. http://elinux.org/RPi_BCM2835_GPIOs + + RPi has a 26 pin IDE header that provides access to some of the GPIO pins on the BCM 2835, + as well as power and ground pins. Not all GPIO pins on the BCM 2835 are available on the + IDE header. + + RPi Version 2 also has a P5 connector with 4 GPIO pins, 5V, 3.3V and Gnd. + + The functions in this library are designed to be passed the BCM 2835 GPIO pin number and _not_ + the RPi pin number. There are symbolic definitions for each of the available pins + that you should use for convenience. See \ref RPiGPIOPin. + + \par SPI Pins + + The bcm2835_spi_* functions allow you to control the BCM 2835 SPI0 interface, + allowing you to send and received data by SPI (Serial Peripheral Interface). + For more information about SPI, see http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus + + When bcm2835_spi_begin() is called it changes the bahaviour of the SPI interface pins from their + default GPIO behaviour in order to support SPI. While SPI is in use, you will not be able + to control the state of the SPI pins through the usual bcm2835_spi_gpio_write(). + When bcm2835_spi_end() is called, the SPI pins will all revert to inputs, and can then be + configured and controled with the usual bcm2835_gpio_* calls. + + The Raspberry Pi GPIO pins used for SPI are: + + - P1-19 (MOSI) + - P1-21 (MISO) + - P1-23 (CLK) + - P1-24 (CE0) + - P1-26 (CE1) + + \par I2C Pins + + The bcm2835_i2c_* functions allow you to control the BCM 2835 BSC interface, + allowing you to send and received data by I2C ("eye-squared cee"; generically referred to as "two-wire interface") . + For more information about I?C, see http://en.wikipedia.org/wiki/I%C2%B2C + + The Raspberry Pi V2 GPIO pins used for I2C are: + + - P1-03 (SDA) + - P1-05 (SLC) + + \par PWM + + The BCM2835 supports hardware PWM on a limited subset of GPIO pins. This bcm2835 library provides + functions for configuring and controlling PWM output on these pins. + + The BCM2835 contains 2 independent PWM channels (0 and 1), each of which be connnected to a limited subset of + GPIO pins. The following GPIO pins may be connected to the following PWM channels (from section 9.5): + \code + GPIO PIN RPi pin PWM Channel ALT FUN + 12 0 0 + 13 1 0 + 18 1-12 0 5 + 19 1 5 + 40 0 0 + 41 1 0 + 45 1 0 + 52 0 1 + 53 1 1 + \endcode + In order for a GPIO pin to emit output from its PWM channel, it must be set to the Alt Function given above. + Note carefully that current versions of the Raspberry Pi only expose one of these pins (GPIO 18 = RPi Pin 1-12) + on the IO headers, and therefore this is the only IO pin on the RPi that can be used for PWM. + Further it must be set to ALT FUN 5 to get PWM output. + + Both PWM channels are driven by the same PWM clock, whose clock dvider can be varied using + bcm2835_pwm_set_clock(). Each channel can be separately enabled with bcm2835_pwm_set_mode(). + The average output of the PWM channel is determined by the ratio of DATA/RANGE for that channel. + Use bcm2835_pwm_set_range() to set the range and bcm2835_pwm_set_data() to set the data in that ratio + + Each PWM channel can run in either Balanced or Mark-Space mode. In Balanced mode, the hardware + sends a combination of clock pulses that results in an overall DATA pulses per RANGE pulses. + In Mark-Space mode, the hardware sets the output HIGH for DATA clock pulses wide, followed by + LOW for RANGE-DATA clock pulses. + + The PWM clock can be set to control the PWM pulse widths. The PWM clock is derived from + a 19.2MHz clock. You can set any divider, but some common ones are provided by the BCM2835_PWM_CLOCK_DIVIDER_* + values of \ref bcm2835PWMClockDivider. + + For example, say you wanted to drive a DC motor with PWM at about 1kHz, + and control the speed in 1/1024 increments from + 0/1024 (stopped) through to 1024/1024 (full on). In that case you might set the + clock divider to be 16, and the RANGE to 1024. The pulse repetition frequency will be + 1.2MHz/1024 = 1171.875Hz. + + \par SPI + + In order for bcm2835 library SPI to work, you may need to disable the SPI kernel module using: + + \code + sudo raspi-config + under Advanced Options - enable Device Tree + under Advanced Options - disable SPI + Reboot. + \endcode + + \par Real Time performance constraints + + The bcm2835 is a library for user programs (i.e. they run in 'userland'). + Such programs are not part of the kernel and are usually + subject to paging and swapping by the kernel while it does other things besides running your program. + This means that you should not expect to get real-time performance or + real-time timing constraints from such programs. In particular, there is no guarantee that the + bcm2835_delay() and bcm2835_delayMicroseconds() will return after exactly the time requested. + In fact, depending on other activity on the host, IO etc, you might get significantly longer delay times + than the one you asked for. So please dont expect to get exactly the time delay you request. + + Arjan reports that you can prevent swapping on Linux with the following code fragment: + + \code + struct sched_param sp; + memset(&sp, 0, sizeof(sp)); + sp.sched_priority = sched_get_priority_max(SCHED_FIFO); + sched_setscheduler(0, SCHED_FIFO, &sp); + mlockall(MCL_CURRENT | MCL_FUTURE); + \endcode + + \par Bindings to other languages + + mikem has made Perl bindings available at CPAN: + http://search.cpan.org/~mikem/Device-BCM2835-1.9/lib/Device/BCM2835.pm + Matthew Baker has kindly made Python bindings available at: + https: github.com/mubeta06/py-libbcm2835 + Gary Marks has created a Serial Peripheral Interface (SPI) command-line utility + for Raspberry Pi, based on the bcm2835 library. The + utility, spincl, is licensed under Open Source GNU GPLv3 by iP Solutions (http://ipsolutionscorp.com), as a + free download with source included: http://ipsolutionscorp.com/raspberry-pi-spi-utility/ + + \par Open Source Licensing GPL V2 + + This is the appropriate option if you want to share the source code of your + application with everyone you distribute it to, and you also want to give them + the right to share who uses it. If you wish to use this software under Open + Source Licensing, you must contribute all your source code to the open source + community in accordance with the GPL Version 2 when your application is + distributed. See http://www.gnu.org/copyleft/gpl.html and COPYING + + \par Acknowledgements + + Some of this code has been inspired by Dom and Gert. + The I2C code has been inspired by Alan Barr. + + \par Revision History + + \version 1.0 Initial release + + \version 1.1 Minor bug fixes + + \version 1.2 Added support for SPI + + \version 1.3 Added bcm2835_spi_transfern() + + \version 1.4 Fixed a problem that prevented SPI CE1 being used. Reported by David Robinson. + + \version 1.5 Added bcm2835_close() to deinit the library. Suggested by C?sar Ortiz + + \version 1.6 Document testing on 2012-07-15-wheezy-raspbian and Occidentalisv01 + Functions bcm2835_gpio_ren(), bcm2835_gpio_fen(), bcm2835_gpio_hen() + bcm2835_gpio_len(), bcm2835_gpio_aren() and bcm2835_gpio_afen() now + changes only the pin specified. Other pins that were already previously + enabled stay enabled. + Added bcm2835_gpio_clr_ren(), bcm2835_gpio_clr_fen(), bcm2835_gpio_clr_hen() + bcm2835_gpio_clr_len(), bcm2835_gpio_clr_aren(), bcm2835_gpio_clr_afen() + to clear the enable for individual pins, suggested by Andreas Sundstrom. + + \version 1.7 Added bcm2835_spi_transfernb to support different buffers for read and write. + + \version 1.8 Improvements to read barrier, as suggested by maddin. + + \version 1.9 Improvements contributed by mikew: + I noticed that it was mallocing memory for the mmaps on /dev/mem. + It's not necessary to do that, you can just mmap the file directly, + so I've removed the mallocs (and frees). + I've also modified delayMicroseconds() to use nanosleep() for long waits, + and a busy wait on a high resolution timer for the rest. This is because + I've found that calling nanosleep() takes at least 100-200 us. + You need to link using '-lrt' using this version. + I've added some unsigned casts to the debug prints to silence compiler + warnings I was getting, fixed some typos, and changed the value of + BCM2835_PAD_HYSTERESIS_ENABLED to 0x08 as per Gert van Loo's doc at + http://www.scribd.com/doc/101830961/GPIO-Pads-Control2 + Also added a define for the passwrd value that Gert says is needed to + change pad control settings. + + \version 1.10 Changed the names of the delay functions to bcm2835_delay() + and bcm2835_delayMicroseconds() to prevent collisions with wiringPi. + Macros to map delay()-> bcm2835_delay() and + Macros to map delayMicroseconds()-> bcm2835_delayMicroseconds(), which + can be disabled by defining BCM2835_NO_DELAY_COMPATIBILITY + + \version 1.11 Fixed incorrect link to download file + + \version 1.12 New GPIO pin definitions for RPi version 2 (which has a different GPIO mapping) + + \version 1.13 New GPIO pin definitions for RPi version 2 plug P5 + Hardware base pointers are now available (after initialisation) externally as bcm2835_gpio + bcm2835_pwm bcm2835_clk bcm2835_pads bcm2835_spi0. + + \version 1.14 Now compiles even if CLOCK_MONOTONIC_RAW is not available, uses CLOCK_MONOTONIC instead. + Fixed errors in documentation of SPI divider frequencies based on 250MHz clock. + Reported by Ben Simpson. + + \version 1.15 Added bcm2835_close() to end of examples as suggested by Mark Wolfe. + + \version 1.16 Added bcm2835_gpio_set_multi, bcm2835_gpio_clr_multi and bcm2835_gpio_write_multi + to allow a mask of pins to be set all at once. Requested by Sebastian Loncar. + + \version 1.17 Added bcm2835_gpio_write_mask. Requested by Sebastian Loncar. + + \version 1.18 Added bcm2835_i2c_* functions. Changes to bcm2835_delayMicroseconds: + now uses the RPi system timer counter, instead of clock_gettime, for improved accuracy. + No need to link with -lrt now. Contributed by Arjan van Vught. + \version 1.19 Removed inlines added by previous patch since they don't seem to work everywhere. + Reported by olly. + + \version 1.20 Patch from Mark Dootson to close /dev/mem after access to the peripherals has been granted. + + \version 1.21 delayMicroseconds is now not susceptible to 32 bit timer overruns. + Patch courtesy Jeremy Mortis. + + \version 1.22 Fixed incorrect definition of BCM2835_GPFEN0 which broke the ability to set + falling edge events. Reported by Mark Dootson. + + \version 1.23 Added bcm2835_i2c_set_baudrate and bcm2835_i2c_read_register_rs. + Improvements to bcm2835_i2c_read and bcm2835_i2c_write functions + to fix ocasional reads not completing. Patched by Mark Dootson. + + \version 1.24 Mark Dootson p[atched a problem with his previously submitted code + under high load from other processes. + + \version 1.25 Updated author and distribution location details to airspayce.com + + \version 1.26 Added missing unmapmem for pads in bcm2835_close to prevent a memory leak. + Reported by Hartmut Henkel. + + \version 1.27 bcm2835_gpio_set_pad() no longer needs BCM2835_PAD_PASSWRD: it is + now automatically included. + Added suport for PWM mode with bcm2835_pwm_* functions. + + \version 1.28 Fixed a problem where bcm2835_spi_writenb() would have problems with transfers of more than + 64 bytes dues to read buffer filling. Patched by Peter Würtz. + + \version 1.29 Further fix to SPI from Peter Würtz. + + \version 1.30 10 microsecond delays from bcm2835_spi_transfer and bcm2835_spi_transfern for + significant performance improvements, Patch by Alan Watson. + + \version 1.31 Fix a GCC warning about dummy variable, patched by Alan Watson. Thanks. + + \version 1.32 Added option I2C_V1 definition to compile for version 1 RPi. + By default I2C code is generated for the V2 RPi which has SDA1 and SCL1 connected. + Contributed by Malcolm Wiles based on work by Arvi Govindaraj. + + \version 1.33 Added command line utilities i2c and gpio to examples. Contributed by Shahrooz Shahparnia. + + \version 1.34 Added bcm2835_i2c_write_read_rs() which writes an arbitrary number of bytes, + sends a repeat start, and reads from the device. Contributed by Eduardo Steinhorst. + + \version 1.35 Fix build errors when compiled under Qt. Also performance improvements with SPI transfers. Contributed b Udo Klaas. + + \version 1.36 Make automake's test runner detect that we're skipping tests when not root, the second + one makes us skip the test when using fakeroot (as used when building + Debian packages). Contributed by Guido Günther. + + \version 1.37 Moved confiure.in to configure.ac as receommnded by autoreconf.
+ Improvements to bcm2835_st_read to account for possible timer overflow, contributed by 'Ed'.
+ Added definitions for Raspberry Pi B+ J8 header GPIO pins.
+ + \version 1.38 Added bcm2835_regbase for the benefit of C# wrappers, patch by Frank Hommers
+ + \version 1.39 Beta version of RPi2 compatibility. Not tested here on RPi2 hardware. + Testers please confirm correct operation on RPi2.
+ Unneccessary 'volatile' qualifiers removed from all variables and signatures.
+ Removed unsupportable PWM dividers, based on a report from Christophe Cecillon.
+ Minor improvements to spi.c example.
+ + \version 1.40 Correct operation on RPi2 has been confirmed.
+ Fixed a number of compiler errors and warnings that occur when bcm2835.h is included + in code compiled with -Wall -Woverflow -Wstrict-overflow -Wshadow -Wextra -pedantic. + Reported by tlhackque.
+ Fixed a problem where calling bcm2835_delayMicroseconds loops forever when debug is set. Reported by tlhackque.
+ Reinstated use of volatile in 2 functions where there was a danger of lost reads or writes. Reported by tlhackque.
+ + \version 1.41 Added BCM2835_VERSION macro and new function bcm2835_version(); Requested by tlhackque.
+ Improvements to peripheral memory barriers as suggested by tlhackque.
+ Reinstated some necessary volatile declarations as requested by tlhackque.
+ + \version 1.42 Further improvements to memory barriers with the patient assistance and patches of tlhackque.
+ + \version 1.43 Fixed problems with compiling barriers on RPI 2 with Arch Linux and gcc 4.9.2. + Reported and patched by Lars Christensen.
+ Testing on RPI 2, with ArchLinuxARM-rpi-2-latest and 2015-02-16-raspbian-wheezy.
+ + \version 1.44 Added documention about the need for device tree to be enabled on RPI2.
+ Improvements to detection of availablity of DMB instruction based on value of __ARM_ARCH macro.
+ + \version 1.45 Fixed an error in the pad group offsets that would prevent bcm2835_gpio_set_pad() + and bcm2835_gpio_pad() working correctly with non-0 pad groups. Reported by Guido. + + \version 1.46 2015-09-18 + Added symbolic definitions for remaining pins on 40 pin GPIO header on RPi 2.
+ + \version 1.47 2015-11-18 + Fixed possibly incorrect reads in bcm2835_i2c_read_register_rs, patch from Eckhardt Ulrich.
+ + \version 1.48 2015-12-08 + Added patch from Eckhardt Ulrich that fixed problems that could cause hanging with bcm2835_i2c_read_register_rs + and others. + + \version 1.49 2016-01-05 + Added patch from Jonathan Perkin with new functions bcm2835_gpio_eds_multi() and bcm2835_gpio_set_eds_multi(). + + \version 1.50 2016-02-28 + Added support for running as non-root, permitting access to GPIO only. Functions + bcm2835_spi_begin() and bcm2835_i2c_begin() will now return 0 if not running as root + (which prevents access to the SPI and I2C peripherals, amongst others). + Testing on Raspbian Jessie. + + \author Mike McCauley (mikem@airspayce.com) DO NOT CONTACT THE AUTHOR DIRECTLY: USE THE LISTS + + + Modified September 2016 by Marcelo Aquino +*/ + + +/* Defines for BCM2835 */ +#ifndef BCM2835_H +#define BCM2835_H + +#include + +#define BCM2835_VERSION 10050 /* Version 1.50 */ + +/* RPi 2 is ARM v7, and has DMB instruction for memory barriers. + Older RPis are ARM v6 and don't, so a coprocessor instruction must be used instead. + However, not all versions of gcc in all distros support the dmb assembler instruction even on conmpatible processors. + This test is so any ARMv7 or higher processors with suitable GCC will use DMB. +*/ +#if __ARM_ARCH >= 7 +#define BCM2835_HAVE_DMB +#endif + +/*! \defgroup BCM2835constantsgrp Constants for passing to and from library functions + \ingroup BCM2835grp + The values here are designed to be passed to various functions in the bcm2835 library. + @{ +*/ + +/*! This means pin HIGH, true, 3.3volts on a pin. */ +#define HIGH 0x1 +/*! This means pin LOW, false, 0volts on a pin. */ +#define LOW 0x0 + +/*! Speed of the core clock core_clk */ +#define BCM2835_CORE_CLK_HZ 250000000 /*!< 250 MHz */ + +/*! On RPi2 with BCM2836, and all recent OSs, the base of the peripherals is read from a /proc file */ +#define BMC2835_RPI2_DT_FILENAME "/proc/device-tree/soc/ranges" +/*! Offset into BMC2835_RPI2_DT_FILENAME for the peripherals base address */ +#define BMC2835_RPI2_DT_PERI_BASE_ADDRESS_OFFSET 4 +/*! Offset into BMC2835_RPI2_DT_FILENAME for the peripherals size address */ +#define BMC2835_RPI2_DT_PERI_SIZE_OFFSET 8 + +/*! Physical addresses for various peripheral register sets + Base Physical Address of the BCM 2835 peripheral registers + Note this is different for the RPi2 BCM2836, where this is derived from /proc/device-tree/soc/ranges + If /proc/device-tree/soc/ranges exists on a RPi 1 OS, it would be expected to contain the + following numbers: +*/ +/*! Peripherals block base address on RPi 1 */ +#define BCM2835_PERI_BASE 0x20000000 +/*! Size of the peripherals block on RPi 1 */ +#define BCM2835_PERI_SIZE 0x01000000 + +/*! Offsets for the bases of various peripherals within the peripherals block + / Base Address of the System Timer registers +*/ +#define BCM2835_ST_BASE 0x3000 +/*! Base Address of the Pads registers */ +#define BCM2835_GPIO_PADS 0x100000 +/*! Base Address of the Clock/timer registers */ +#define BCM2835_CLOCK_BASE 0x101000 +/*! Base Address of the GPIO registers */ +#define BCM2835_GPIO_BASE 0x200000 +/*! Base Address of the SPI0 registers */ +#define BCM2835_SPI0_BASE 0x204000 +/*! Base Address of the BSC0 registers */ +#define BCM2835_BSC0_BASE 0x205000 +/*! Base Address of the PWM registers */ +#define BCM2835_GPIO_PWM 0x20C000 +/*! Base Address of the BSC1 registers */ +#define BCM2835_BSC1_BASE 0x804000 + +/*! Physical address and size of the peripherals block + May be overridden on RPi2 +*/ +extern uint32_t *bcm2835_peripherals_base; +/*! Size of the peripherals block to be mapped */ +extern uint32_t bcm2835_peripherals_size; + +/*! Virtual memory address of the mapped peripherals block */ +extern uint32_t *bcm2835_peripherals; + +/*! Base of the ST (System Timer) registers. + Available after bcm2835_init has been called (as root) +*/ +extern volatile uint32_t *bcm2835_st; + +/*! Base of the GPIO registers. + Available after bcm2835_init has been called +*/ +extern volatile uint32_t *bcm2835_gpio; + +/*! Base of the PWM registers. + Available after bcm2835_init has been called (as root) +*/ +extern volatile uint32_t *bcm2835_pwm; + +/*! Base of the CLK registers. + Available after bcm2835_init has been called (as root) +*/ +extern volatile uint32_t *bcm2835_clk; + +/*! Base of the PADS registers. + Available after bcm2835_init has been called (as root) +*/ +extern volatile uint32_t *bcm2835_pads; + +/*! Base of the SPI0 registers. + Available after bcm2835_init has been called (as root) +*/ +extern volatile uint32_t *bcm2835_spi0; + +/*! Base of the BSC0 registers. + Available after bcm2835_init has been called (as root) +*/ +extern volatile uint32_t *bcm2835_bsc0; + +/*! Base of the BSC1 registers. + Available after bcm2835_init has been called (as root) +*/ +extern volatile uint32_t *bcm2835_bsc1; + +/*! \brief bcm2835RegisterBase + Register bases for bcm2835_regbase() +*/ +typedef enum +{ + BCM2835_REGBASE_ST = 1, /*!< Base of the ST (System Timer) registers. */ + BCM2835_REGBASE_GPIO = 2, /*!< Base of the GPIO registers. */ + BCM2835_REGBASE_PWM = 3, /*!< Base of the PWM registers. */ + BCM2835_REGBASE_CLK = 4, /*!< Base of the CLK registers. */ + BCM2835_REGBASE_PADS = 5, /*!< Base of the PADS registers. */ + BCM2835_REGBASE_SPI0 = 6, /*!< Base of the SPI0 registers. */ + BCM2835_REGBASE_BSC0 = 7, /*!< Base of the BSC0 registers. */ + BCM2835_REGBASE_BSC1 = 8 /*!< Base of the BSC1 registers. */ +} bcm2835RegisterBase; + +/*! Size of memory page on RPi */ +#define BCM2835_PAGE_SIZE (4*1024) +/*! Size of memory block on RPi */ +#define BCM2835_BLOCK_SIZE (4*1024) + + +/* Defines for GPIO + The BCM2835 has 54 GPIO pins. + BCM2835 data sheet, Page 90 onwards. +*/ +/*! GPIO register offsets from BCM2835_GPIO_BASE. + Offsets into the GPIO Peripheral block in bytes per 6.1 Register View +*/ +#define BCM2835_GPFSEL0 0x0000 /*!< GPIO Function Select 0 */ +#define BCM2835_GPFSEL1 0x0004 /*!< GPIO Function Select 1 */ +#define BCM2835_GPFSEL2 0x0008 /*!< GPIO Function Select 2 */ +#define BCM2835_GPFSEL3 0x000c /*!< GPIO Function Select 3 */ +#define BCM2835_GPFSEL4 0x0010 /*!< GPIO Function Select 4 */ +#define BCM2835_GPFSEL5 0x0014 /*!< GPIO Function Select 5 */ +#define BCM2835_GPSET0 0x001c /*!< GPIO Pin Output Set 0 */ +#define BCM2835_GPSET1 0x0020 /*!< GPIO Pin Output Set 1 */ +#define BCM2835_GPCLR0 0x0028 /*!< GPIO Pin Output Clear 0 */ +#define BCM2835_GPCLR1 0x002c /*!< GPIO Pin Output Clear 1 */ +#define BCM2835_GPLEV0 0x0034 /*!< GPIO Pin Level 0 */ +#define BCM2835_GPLEV1 0x0038 /*!< GPIO Pin Level 1 */ +#define BCM2835_GPEDS0 0x0040 /*!< GPIO Pin Event Detect Status 0 */ +#define BCM2835_GPEDS1 0x0044 /*!< GPIO Pin Event Detect Status 1 */ +#define BCM2835_GPREN0 0x004c /*!< GPIO Pin Rising Edge Detect Enable 0 */ +#define BCM2835_GPREN1 0x0050 /*!< GPIO Pin Rising Edge Detect Enable 1 */ +#define BCM2835_GPFEN0 0x0058 /*!< GPIO Pin Falling Edge Detect Enable 0 */ +#define BCM2835_GPFEN1 0x005c /*!< GPIO Pin Falling Edge Detect Enable 1 */ +#define BCM2835_GPHEN0 0x0064 /*!< GPIO Pin High Detect Enable 0 */ +#define BCM2835_GPHEN1 0x0068 /*!< GPIO Pin High Detect Enable 1 */ +#define BCM2835_GPLEN0 0x0070 /*!< GPIO Pin Low Detect Enable 0 */ +#define BCM2835_GPLEN1 0x0074 /*!< GPIO Pin Low Detect Enable 1 */ +#define BCM2835_GPAREN0 0x007c /*!< GPIO Pin Async. Rising Edge Detect 0 */ +#define BCM2835_GPAREN1 0x0080 /*!< GPIO Pin Async. Rising Edge Detect 1 */ +#define BCM2835_GPAFEN0 0x0088 /*!< GPIO Pin Async. Falling Edge Detect 0 */ +#define BCM2835_GPAFEN1 0x008c /*!< GPIO Pin Async. Falling Edge Detect 1 */ +#define BCM2835_GPPUD 0x0094 /*!< GPIO Pin Pull-up/down Enable */ +#define BCM2835_GPPUDCLK0 0x0098 /*!< GPIO Pin Pull-up/down Enable Clock 0 */ +#define BCM2835_GPPUDCLK1 0x009c /*!< GPIO Pin Pull-up/down Enable Clock 1 */ + +/*! \brief bcm2835PortFunction + Port function select modes for bcm2835_gpio_fsel() +*/ +typedef enum +{ + BCM2835_GPIO_FSEL_INPT = 0x00, /*!< Input 0b000 */ + BCM2835_GPIO_FSEL_OUTP = 0x01, /*!< Output 0b001 */ + BCM2835_GPIO_FSEL_ALT0 = 0x04, /*!< Alternate function 0 0b100 */ + BCM2835_GPIO_FSEL_ALT1 = 0x05, /*!< Alternate function 1 0b101 */ + BCM2835_GPIO_FSEL_ALT2 = 0x06, /*!< Alternate function 2 0b110, */ + BCM2835_GPIO_FSEL_ALT3 = 0x07, /*!< Alternate function 3 0b111 */ + BCM2835_GPIO_FSEL_ALT4 = 0x03, /*!< Alternate function 4 0b011 */ + BCM2835_GPIO_FSEL_ALT5 = 0x02, /*!< Alternate function 5 0b010 */ + BCM2835_GPIO_FSEL_MASK = 0x07 /*!< Function select bits mask 0b111 */ +} bcm2835FunctionSelect; + +/*! \brief bcm2835PUDControl + Pullup/Pulldown defines for bcm2835_gpio_pud() +*/ +typedef enum +{ + BCM2835_GPIO_PUD_OFF = 0x00, /*!< Off ? disable pull-up/down 0b00 */ + BCM2835_GPIO_PUD_DOWN = 0x01, /*!< Enable Pull Down control 0b01 */ + BCM2835_GPIO_PUD_UP = 0x02 /*!< Enable Pull Up control 0b10 */ +} bcm2835PUDControl; + +/*! Pad control register offsets from BCM2835_GPIO_PADS */ +#define BCM2835_PADS_GPIO_0_27 0x002c /*!< Pad control register for pads 0 to 27 */ +#define BCM2835_PADS_GPIO_28_45 0x0030 /*!< Pad control register for pads 28 to 45 */ +#define BCM2835_PADS_GPIO_46_53 0x0034 /*!< Pad control register for pads 46 to 53 */ + +/*! Pad Control masks */ +#define BCM2835_PAD_PASSWRD (0x5A << 24) /*!< Password to enable setting pad mask */ +#define BCM2835_PAD_SLEW_RATE_UNLIMITED 0x10 /*!< Slew rate unlimited */ +#define BCM2835_PAD_HYSTERESIS_ENABLED 0x08 /*!< Hysteresis enabled */ +#define BCM2835_PAD_DRIVE_2mA 0x00 /*!< 2mA drive current */ +#define BCM2835_PAD_DRIVE_4mA 0x01 /*!< 4mA drive current */ +#define BCM2835_PAD_DRIVE_6mA 0x02 /*!< 6mA drive current */ +#define BCM2835_PAD_DRIVE_8mA 0x03 /*!< 8mA drive current */ +#define BCM2835_PAD_DRIVE_10mA 0x04 /*!< 10mA drive current */ +#define BCM2835_PAD_DRIVE_12mA 0x05 /*!< 12mA drive current */ +#define BCM2835_PAD_DRIVE_14mA 0x06 /*!< 14mA drive current */ +#define BCM2835_PAD_DRIVE_16mA 0x07 /*!< 16mA drive current */ + +/*! \brief bcm2835PadGroup + Pad group specification for bcm2835_gpio_pad() +*/ +typedef enum +{ + BCM2835_PAD_GROUP_GPIO_0_27 = 0, /*!< Pad group for GPIO pads 0 to 27 */ + BCM2835_PAD_GROUP_GPIO_28_45 = 1, /*!< Pad group for GPIO pads 28 to 45 */ + BCM2835_PAD_GROUP_GPIO_46_53 = 2 /*!< Pad group for GPIO pads 46 to 53 */ +} bcm2835PadGroup; + +/*! \brief GPIO Pin Numbers + + Here we define Raspberry Pin GPIO pins on P1 in terms of the underlying BCM GPIO pin numbers. + These can be passed as a pin number to any function requiring a pin. + Not all pins on the RPi 26 bin IDE plug are connected to GPIO pins + and some can adopt an alternate function. + RPi version 2 has some slightly different pinouts, and these are values RPI_V2_*. + RPi B+ has yet differnet pinouts and these are defined in RPI_BPLUS_*. + At bootup, pins 8 and 10 are set to UART0_TXD, UART0_RXD (ie the alt0 function) respectively + When SPI0 is in use (ie after bcm2835_spi_begin()), SPI0 pins are dedicated to SPI + and cant be controlled independently. + If you are using the RPi Compute Module, just use the GPIO number: there is no need to use one of these + symbolic names +*/ +typedef enum +{ + RPI_GPIO_P1_03 = 0, /*!< Version 1, Pin P1-03 */ + RPI_GPIO_P1_05 = 1, /*!< Version 1, Pin P1-05 */ + RPI_GPIO_P1_07 = 4, /*!< Version 1, Pin P1-07 */ + RPI_GPIO_P1_08 = 14, /*!< Version 1, Pin P1-08, defaults to alt function 0 UART0_TXD */ + RPI_GPIO_P1_10 = 15, /*!< Version 1, Pin P1-10, defaults to alt function 0 UART0_RXD */ + RPI_GPIO_P1_11 = 17, /*!< Version 1, Pin P1-11 */ + RPI_GPIO_P1_12 = 18, /*!< Version 1, Pin P1-12, can be PWM channel 0 in ALT FUN 5 */ + RPI_GPIO_P1_13 = 21, /*!< Version 1, Pin P1-13 */ + RPI_GPIO_P1_15 = 22, /*!< Version 1, Pin P1-15 */ + RPI_GPIO_P1_16 = 23, /*!< Version 1, Pin P1-16 */ + RPI_GPIO_P1_18 = 24, /*!< Version 1, Pin P1-18 */ + RPI_GPIO_P1_19 = 10, /*!< Version 1, Pin P1-19, MOSI when SPI0 in use */ + RPI_GPIO_P1_21 = 9, /*!< Version 1, Pin P1-21, MISO when SPI0 in use */ + RPI_GPIO_P1_22 = 25, /*!< Version 1, Pin P1-22 */ + RPI_GPIO_P1_23 = 11, /*!< Version 1, Pin P1-23, CLK when SPI0 in use */ + RPI_GPIO_P1_24 = 8, /*!< Version 1, Pin P1-24, CE0 when SPI0 in use */ + RPI_GPIO_P1_26 = 7, /*!< Version 1, Pin P1-26, CE1 when SPI0 in use */ + + /* RPi Version 2 */ + RPI_V2_GPIO_P1_03 = 2, /*!< Version 2, Pin P1-03 */ + RPI_V2_GPIO_P1_05 = 3, /*!< Version 2, Pin P1-05 */ + RPI_V2_GPIO_P1_07 = 4, /*!< Version 2, Pin P1-07 */ + RPI_V2_GPIO_P1_08 = 14, /*!< Version 2, Pin P1-08, defaults to alt function 0 UART0_TXD */ + RPI_V2_GPIO_P1_10 = 15, /*!< Version 2, Pin P1-10, defaults to alt function 0 UART0_RXD */ + RPI_V2_GPIO_P1_11 = 17, /*!< Version 2, Pin P1-11 */ + RPI_V2_GPIO_P1_12 = 18, /*!< Version 2, Pin P1-12, can be PWM channel 0 in ALT FUN 5 */ + RPI_V2_GPIO_P1_13 = 27, /*!< Version 2, Pin P1-13 */ + RPI_V2_GPIO_P1_15 = 22, /*!< Version 2, Pin P1-15 */ + RPI_V2_GPIO_P1_16 = 23, /*!< Version 2, Pin P1-16 */ + RPI_V2_GPIO_P1_18 = 24, /*!< Version 2, Pin P1-18 */ + RPI_V2_GPIO_P1_19 = 10, /*!< Version 2, Pin P1-19, MOSI when SPI0 in use */ + RPI_V2_GPIO_P1_21 = 9, /*!< Version 2, Pin P1-21, MISO when SPI0 in use */ + RPI_V2_GPIO_P1_22 = 25, /*!< Version 2, Pin P1-22 */ + RPI_V2_GPIO_P1_23 = 11, /*!< Version 2, Pin P1-23, CLK when SPI0 in use */ + RPI_V2_GPIO_P1_24 = 8, /*!< Version 2, Pin P1-24, CE0 when SPI0 in use */ + RPI_V2_GPIO_P1_26 = 7, /*!< Version 2, Pin P1-26, CE1 when SPI0 in use */ + RPI_V2_GPIO_P1_29 = 5, /*!< Version 2, Pin P1-29 */ + RPI_V2_GPIO_P1_31 = 6, /*!< Version 2, Pin P1-31 */ + RPI_V2_GPIO_P1_32 = 12, /*!< Version 2, Pin P1-32 */ + RPI_V2_GPIO_P1_33 = 13, /*!< Version 2, Pin P1-33 */ + RPI_V2_GPIO_P1_35 = 19, /*!< Version 2, Pin P1-35 */ + RPI_V2_GPIO_P1_36 = 16, /*!< Version 2, Pin P1-36 */ + RPI_V2_GPIO_P1_37 = 26, /*!< Version 2, Pin P1-37 */ + RPI_V2_GPIO_P1_38 = 20, /*!< Version 2, Pin P1-38 */ + RPI_V2_GPIO_P1_40 = 21, /*!< Version 2, Pin P1-40 */ + + /* RPi Version 2, new plug P5 */ + RPI_V2_GPIO_P5_03 = 28, /*!< Version 2, Pin P5-03 */ + RPI_V2_GPIO_P5_04 = 29, /*!< Version 2, Pin P5-04 */ + RPI_V2_GPIO_P5_05 = 30, /*!< Version 2, Pin P5-05 */ + RPI_V2_GPIO_P5_06 = 31, /*!< Version 2, Pin P5-06 */ + + /* RPi B+ J8 header, also RPi 2 40 pin GPIO header */ + RPI_BPLUS_GPIO_J8_03 = 2, /*!< B+, Pin J8-03 */ + RPI_BPLUS_GPIO_J8_05 = 3, /*!< B+, Pin J8-05 */ + RPI_BPLUS_GPIO_J8_07 = 4, /*!< B+, Pin J8-07 */ + RPI_BPLUS_GPIO_J8_08 = 14, /*!< B+, Pin J8-08, defaults to alt function 0 UART0_TXD */ + RPI_BPLUS_GPIO_J8_10 = 15, /*!< B+, Pin J8-10, defaults to alt function 0 UART0_RXD */ + RPI_BPLUS_GPIO_J8_11 = 17, /*!< B+, Pin J8-11 */ + RPI_BPLUS_GPIO_J8_12 = 18, /*!< B+, Pin J8-12, can be PWM channel 0 in ALT FUN 5 */ + RPI_BPLUS_GPIO_J8_13 = 27, /*!< B+, Pin J8-13 */ + RPI_BPLUS_GPIO_J8_15 = 22, /*!< B+, Pin J8-15 */ + RPI_BPLUS_GPIO_J8_16 = 23, /*!< B+, Pin J8-16 */ + RPI_BPLUS_GPIO_J8_18 = 24, /*!< B+, Pin J8-18 */ + RPI_BPLUS_GPIO_J8_19 = 10, /*!< B+, Pin J8-19, MOSI when SPI0 in use */ + RPI_BPLUS_GPIO_J8_21 = 9, /*!< B+, Pin J8-21, MISO when SPI0 in use */ + RPI_BPLUS_GPIO_J8_22 = 25, /*!< B+, Pin J8-22 */ + RPI_BPLUS_GPIO_J8_23 = 11, /*!< B+, Pin J8-23, CLK when SPI0 in use */ + RPI_BPLUS_GPIO_J8_24 = 8, /*!< B+, Pin J8-24, CE0 when SPI0 in use */ + RPI_BPLUS_GPIO_J8_26 = 7, /*!< B+, Pin J8-26, CE1 when SPI0 in use */ + RPI_BPLUS_GPIO_J8_29 = 5, /*!< B+, Pin J8-29, */ + RPI_BPLUS_GPIO_J8_31 = 6, /*!< B+, Pin J8-31, */ + RPI_BPLUS_GPIO_J8_32 = 12, /*!< B+, Pin J8-32, */ + RPI_BPLUS_GPIO_J8_33 = 13, /*!< B+, Pin J8-33, */ + RPI_BPLUS_GPIO_J8_35 = 19, /*!< B+, Pin J8-35, */ + RPI_BPLUS_GPIO_J8_36 = 16, /*!< B+, Pin J8-36, */ + RPI_BPLUS_GPIO_J8_37 = 26, /*!< B+, Pin J8-37, */ + RPI_BPLUS_GPIO_J8_38 = 20, /*!< B+, Pin J8-38, */ + RPI_BPLUS_GPIO_J8_40 = 21 /*!< B+, Pin J8-40, */ +} RPiGPIOPin; + +/* Defines for SPI + GPIO register offsets from BCM2835_SPI0_BASE. + Offsets into the SPI Peripheral block in bytes per 10.5 SPI Register Map +*/ +#define BCM2835_SPI0_CS 0x0000 /*!< SPI Master Control and Status */ +#define BCM2835_SPI0_FIFO 0x0004 /*!< SPI Master TX and RX FIFOs */ +#define BCM2835_SPI0_CLK 0x0008 /*!< SPI Master Clock Divider */ +#define BCM2835_SPI0_DLEN 0x000c /*!< SPI Master Data Length */ +#define BCM2835_SPI0_LTOH 0x0010 /*!< SPI LOSSI mode TOH */ +#define BCM2835_SPI0_DC 0x0014 /*!< SPI DMA DREQ Controls */ + +/* Register masks for SPI0_CS */ +#define BCM2835_SPI0_CS_LEN_LONG 0x02000000 /*!< Enable Long data word in Lossi mode if DMA_LEN is set */ +#define BCM2835_SPI0_CS_DMA_LEN 0x01000000 /*!< Enable DMA mode in Lossi mode */ +#define BCM2835_SPI0_CS_CSPOL2 0x00800000 /*!< Chip Select 2 Polarity */ +#define BCM2835_SPI0_CS_CSPOL1 0x00400000 /*!< Chip Select 1 Polarity */ +#define BCM2835_SPI0_CS_CSPOL0 0x00200000 /*!< Chip Select 0 Polarity */ +#define BCM2835_SPI0_CS_RXF 0x00100000 /*!< RXF - RX FIFO Full */ +#define BCM2835_SPI0_CS_RXR 0x00080000 /*!< RXR RX FIFO needs Reading (full) */ +#define BCM2835_SPI0_CS_TXD 0x00040000 /*!< TXD TX FIFO can accept Data */ +#define BCM2835_SPI0_CS_RXD 0x00020000 /*!< RXD RX FIFO contains Data */ +#define BCM2835_SPI0_CS_DONE 0x00010000 /*!< Done transfer Done */ +#define BCM2835_SPI0_CS_TE_EN 0x00008000 /*!< Unused */ +#define BCM2835_SPI0_CS_LMONO 0x00004000 /*!< Unused */ +#define BCM2835_SPI0_CS_LEN 0x00002000 /*!< LEN LoSSI enable */ +#define BCM2835_SPI0_CS_REN 0x00001000 /*!< REN Read Enable */ +#define BCM2835_SPI0_CS_ADCS 0x00000800 /*!< ADCS Automatically Deassert Chip Select */ +#define BCM2835_SPI0_CS_INTR 0x00000400 /*!< INTR Interrupt on RXR */ +#define BCM2835_SPI0_CS_INTD 0x00000200 /*!< INTD Interrupt on Done */ +#define BCM2835_SPI0_CS_DMAEN 0x00000100 /*!< DMAEN DMA Enable */ +#define BCM2835_SPI0_CS_TA 0x00000080 /*!< Transfer Active */ +#define BCM2835_SPI0_CS_CSPOL 0x00000040 /*!< Chip Select Polarity */ +#define BCM2835_SPI0_CS_CLEAR 0x00000030 /*!< Clear FIFO Clear RX and TX */ +#define BCM2835_SPI0_CS_CLEAR_RX 0x00000020 /*!< Clear FIFO Clear RX */ +#define BCM2835_SPI0_CS_CLEAR_TX 0x00000010 /*!< Clear FIFO Clear TX */ +#define BCM2835_SPI0_CS_CPOL 0x00000008 /*!< Clock Polarity */ +#define BCM2835_SPI0_CS_CPHA 0x00000004 /*!< Clock Phase */ +#define BCM2835_SPI0_CS_CS 0x00000003 /*!< Chip Select */ + +/*! \brief bcm2835SPIBitOrder SPI Bit order + Specifies the SPI data bit ordering for bcm2835_spi_setBitOrder() +*/ +typedef enum +{ + BCM2835_SPI_BIT_ORDER_LSBFIRST = 0, /*!< LSB First */ + BCM2835_SPI_BIT_ORDER_MSBFIRST = 1 /*!< MSB First */ +}bcm2835SPIBitOrder; + +/*! \brief SPI Data mode + Specify the SPI data mode to be passed to bcm2835_spi_setDataMode() +*/ +typedef enum +{ + BCM2835_SPI_MODE0 = 0, /*!< CPOL = 0, CPHA = 0 */ + BCM2835_SPI_MODE1 = 1, /*!< CPOL = 0, CPHA = 1 */ + BCM2835_SPI_MODE2 = 2, /*!< CPOL = 1, CPHA = 0 */ + BCM2835_SPI_MODE3 = 3 /*!< CPOL = 1, CPHA = 1 */ +}bcm2835SPIMode; + +/*! \brief bcm2835SPIChipSelect + Specify the SPI chip select pin(s) +*/ +typedef enum +{ + BCM2835_SPI_CS0 = 0, /*!< Chip Select 0 */ + BCM2835_SPI_CS1 = 1, /*!< Chip Select 1 */ + BCM2835_SPI_CS2 = 2, /*!< Chip Select 2 (ie pins CS1 and CS2 are asserted) */ + BCM2835_SPI_CS_NONE = 3 /*!< No CS, control it yourself */ +} bcm2835SPIChipSelect; + +/*! \brief bcm2835SPIClockDivider + Specifies the divider used to generate the SPI clock from the system clock. + Figures below give the divider, clock period and clock frequency. + Clock divided is based on nominal base clock rate of 250MHz + It is reported that (contrary to the documentation) any even divider may used. + The frequencies shown for each divider have been confirmed by measurement. +*/ +typedef enum +{ + BCM2835_SPI_CLOCK_DIVIDER_65536 = 0, /*!< 65536 = 262.144us = 3.814697260kHz */ + BCM2835_SPI_CLOCK_DIVIDER_32768 = 32768, /*!< 32768 = 131.072us = 7.629394531kHz */ + BCM2835_SPI_CLOCK_DIVIDER_16384 = 16384, /*!< 16384 = 65.536us = 15.25878906kHz */ + BCM2835_SPI_CLOCK_DIVIDER_8192 = 8192, /*!< 8192 = 32.768us = 30/51757813kHz */ + BCM2835_SPI_CLOCK_DIVIDER_4096 = 4096, /*!< 4096 = 16.384us = 61.03515625kHz */ + BCM2835_SPI_CLOCK_DIVIDER_2048 = 2048, /*!< 2048 = 8.192us = 122.0703125kHz */ + BCM2835_SPI_CLOCK_DIVIDER_1024 = 1024, /*!< 1024 = 4.096us = 244.140625kHz */ + BCM2835_SPI_CLOCK_DIVIDER_512 = 512, /*!< 512 = 2.048us = 488.28125kHz */ + BCM2835_SPI_CLOCK_DIVIDER_256 = 256, /*!< 256 = 1.024us = 976.5625kHz */ + BCM2835_SPI_CLOCK_DIVIDER_128 = 128, /*!< 128 = 512ns = = 1.953125MHz */ + BCM2835_SPI_CLOCK_DIVIDER_64 = 64, /*!< 64 = 256ns = 3.90625MHz */ + BCM2835_SPI_CLOCK_DIVIDER_32 = 32, /*!< 32 = 128ns = 7.8125MHz */ + BCM2835_SPI_CLOCK_DIVIDER_16 = 16, /*!< 16 = 64ns = 15.625MHz */ + BCM2835_SPI_CLOCK_DIVIDER_8 = 8, /*!< 8 = 32ns = 31.25MHz */ + BCM2835_SPI_CLOCK_DIVIDER_4 = 4, /*!< 4 = 16ns = 62.5MHz */ + BCM2835_SPI_CLOCK_DIVIDER_2 = 2, /*!< 2 = 8ns = 125MHz, fastest you can get */ + BCM2835_SPI_CLOCK_DIVIDER_1 = 1 /*!< 1 = 262.144us = 3.814697260kHz, same as 0/65536 */ +} bcm2835SPIClockDivider; + +/* Defines for I2C + GPIO register offsets from BCM2835_BSC*_BASE. + Offsets into the BSC Peripheral block in bytes per 3.1 BSC Register Map +*/ +#define BCM2835_BSC_C 0x0000 /*!< BSC Master Control */ +#define BCM2835_BSC_S 0x0004 /*!< BSC Master Status */ +#define BCM2835_BSC_DLEN 0x0008 /*!< BSC Master Data Length */ +#define BCM2835_BSC_A 0x000c /*!< BSC Master Slave Address */ +#define BCM2835_BSC_FIFO 0x0010 /*!< BSC Master Data FIFO */ +#define BCM2835_BSC_DIV 0x0014 /*!< BSC Master Clock Divider */ +#define BCM2835_BSC_DEL 0x0018 /*!< BSC Master Data Delay */ +#define BCM2835_BSC_CLKT 0x001c /*!< BSC Master Clock Stretch Timeout */ + +/* Register masks for BSC_C */ +#define BCM2835_BSC_C_I2CEN 0x00008000 /*!< I2C Enable, 0 = disabled, 1 = enabled */ +#define BCM2835_BSC_C_INTR 0x00000400 /*!< Interrupt on RX */ +#define BCM2835_BSC_C_INTT 0x00000200 /*!< Interrupt on TX */ +#define BCM2835_BSC_C_INTD 0x00000100 /*!< Interrupt on DONE */ +#define BCM2835_BSC_C_ST 0x00000080 /*!< Start transfer, 1 = Start a new transfer */ +#define BCM2835_BSC_C_CLEAR_1 0x00000020 /*!< Clear FIFO Clear */ +#define BCM2835_BSC_C_CLEAR_2 0x00000010 /*!< Clear FIFO Clear */ +#define BCM2835_BSC_C_READ 0x00000001 /*!< Read transfer */ + +/* Register masks for BSC_S */ +#define BCM2835_BSC_S_CLKT 0x00000200 /*!< Clock stretch timeout */ +#define BCM2835_BSC_S_ERR 0x00000100 /*!< ACK error */ +#define BCM2835_BSC_S_RXF 0x00000080 /*!< RXF FIFO full, 0 = FIFO is not full, 1 = FIFO is full */ +#define BCM2835_BSC_S_TXE 0x00000040 /*!< TXE FIFO full, 0 = FIFO is not full, 1 = FIFO is full */ +#define BCM2835_BSC_S_RXD 0x00000020 /*!< RXD FIFO contains data */ +#define BCM2835_BSC_S_TXD 0x00000010 /*!< TXD FIFO can accept data */ +#define BCM2835_BSC_S_RXR 0x00000008 /*!< RXR FIFO needs reading (full) */ +#define BCM2835_BSC_S_TXW 0x00000004 /*!< TXW FIFO needs writing (full) */ +#define BCM2835_BSC_S_DONE 0x00000002 /*!< Transfer DONE */ +#define BCM2835_BSC_S_TA 0x00000001 /*!< Transfer Active */ + +#define BCM2835_BSC_FIFO_SIZE 16 /*!< BSC FIFO size */ + +/*! \brief bcm2835I2CClockDivider + Specifies the divider used to generate the I2C clock from the system clock. + Clock divided is based on nominal base clock rate of 250MHz +*/ +typedef enum +{ + BCM2835_I2C_CLOCK_DIVIDER_2500 = 2500, /*!< 2500 = 10us = 100 kHz */ + BCM2835_I2C_CLOCK_DIVIDER_626 = 626, /*!< 622 = 2.504us = 399.3610 kHz */ + BCM2835_I2C_CLOCK_DIVIDER_150 = 150, /*!< 150 = 60ns = 1.666 MHz (default at reset) */ + BCM2835_I2C_CLOCK_DIVIDER_148 = 148 /*!< 148 = 59ns = 1.689 MHz */ +} bcm2835I2CClockDivider; + +/*! \brief bcm2835I2CReasonCodes + Specifies the reason codes for the bcm2835_i2c_write and bcm2835_i2c_read functions. +*/ +typedef enum +{ + BCM2835_I2C_REASON_OK = 0x00, /*!< Success */ + BCM2835_I2C_REASON_ERROR_NACK = 0x01, /*!< Received a NACK */ + BCM2835_I2C_REASON_ERROR_CLKT = 0x02, /*!< Received Clock Stretch Timeout */ + BCM2835_I2C_REASON_ERROR_DATA = 0x04 /*!< Not all data is sent / received */ +} bcm2835I2CReasonCodes; + +/* Defines for ST + GPIO register offsets from BCM2835_ST_BASE. + Offsets into the ST Peripheral block in bytes per 12.1 System Timer Registers + The System Timer peripheral provides four 32-bit timer channels and a single 64-bit free running counter. + BCM2835_ST_CLO is the System Timer Counter Lower bits register. + The system timer free-running counter lower register is a read-only register that returns the current value + of the lower 32-bits of the free running counter. + BCM2835_ST_CHI is the System Timer Counter Upper bits register. + The system timer free-running counter upper register is a read-only register that returns the current value + of the upper 32-bits of the free running counter. +*/ +#define BCM2835_ST_CS 0x0000 /*!< System Timer Control/Status */ +#define BCM2835_ST_CLO 0x0004 /*!< System Timer Counter Lower 32 bits */ +#define BCM2835_ST_CHI 0x0008 /*!< System Timer Counter Upper 32 bits */ + +/* Defines for PWM, word offsets (ie 4 byte multiples) */ +#define BCM2835_PWM_CONTROL 0 /*!< PWM Control register */ +#define BCM2835_PWM_STATUS 1 /*!< PWM Status register */ +#define BCM2835_PWM_DMAC 2 /*!< PWM DMA Configuration */ +#define BCM2835_PWM0_RANGE 4 /*!< PWM Channel 0 Range */ +#define BCM2835_PWM0_DATA 5 /*!< PWM Channel 0 Data */ +#define BCM2835_PWM_FIF1 6 /*!< PWM FIFO Input */ +#define BCM2835_PWM1_RANGE 8 /*!< PWM Channel 1 Range */ +#define BCM2835_PWM1_DATA 9 /*!< PWM Channel 1 Data */ + +/* Defines for PWM Clock, word offsets (ie 4 byte multiples) */ +#define BCM2835_PWMCLK_CNTL 40 /*!< PWM Clock Control */ +#define BCM2835_PWMCLK_DIV 41 /*!< PWM Clock Divider */ +#define BCM2835_PWM_PASSWRD (0x5A << 24) /*!< Password to enable setting PWM clock */ + +#define BCM2835_PWM1_MS_MODE 0x8000 /*!< Run in Mark/Space mode */ +#define BCM2835_PWM1_USEFIFO 0x2000 /*!< Data from FIFO */ +#define BCM2835_PWM1_REVPOLAR 0x1000 /*!< Reverse polarity */ +#define BCM2835_PWM1_OFFSTATE 0x0800 /*!< Ouput Off state */ +#define BCM2835_PWM1_REPEATFF 0x0400 /*!< Repeat last value if FIFO empty */ +#define BCM2835_PWM1_SERIAL 0x0200 /*!< Run in serial mode */ +#define BCM2835_PWM1_ENABLE 0x0100 /*!< Channel Enable */ + +#define BCM2835_PWM0_MS_MODE 0x0080 /*!< Run in Mark/Space mode */ +#define BCM2835_PWM_CLEAR_FIFO 0x0040 /*!< Clear FIFO */ +#define BCM2835_PWM0_USEFIFO 0x0020 /*!< Data from FIFO */ +#define BCM2835_PWM0_REVPOLAR 0x0010 /*!< Reverse polarity */ +#define BCM2835_PWM0_OFFSTATE 0x0008 /*!< Ouput Off state */ +#define BCM2835_PWM0_REPEATFF 0x0004 /*!< Repeat last value if FIFO empty */ +#define BCM2835_PWM0_SERIAL 0x0002 /*!< Run in serial mode */ +#define BCM2835_PWM0_ENABLE 0x0001 /*!< Channel Enable */ + +/*! \brief bcm2835PWMClockDivider + Specifies the divider used to generate the PWM clock from the system clock. + Figures below give the divider, clock period and clock frequency. + Clock divided is based on nominal PWM base clock rate of 19.2MHz + The frequencies shown for each divider have been confirmed by measurement +*/ +typedef enum +{ + BCM2835_PWM_CLOCK_DIVIDER_2048 = 2048, /*!< 2048 = 9.375kHz */ + BCM2835_PWM_CLOCK_DIVIDER_1024 = 1024, /*!< 1024 = 18.75kHz */ + BCM2835_PWM_CLOCK_DIVIDER_512 = 512, /*!< 512 = 37.5kHz */ + BCM2835_PWM_CLOCK_DIVIDER_256 = 256, /*!< 256 = 75kHz */ + BCM2835_PWM_CLOCK_DIVIDER_128 = 128, /*!< 128 = 150kHz */ + BCM2835_PWM_CLOCK_DIVIDER_64 = 64, /*!< 64 = 300kHz */ + BCM2835_PWM_CLOCK_DIVIDER_32 = 32, /*!< 32 = 600.0kHz */ + BCM2835_PWM_CLOCK_DIVIDER_16 = 16, /*!< 16 = 1.2MHz */ + BCM2835_PWM_CLOCK_DIVIDER_8 = 8, /*!< 8 = 2.4MHz */ + BCM2835_PWM_CLOCK_DIVIDER_4 = 4, /*!< 4 = 4.8MHz */ + BCM2835_PWM_CLOCK_DIVIDER_2 = 2, /*!< 2 = 9.6MHz, fastest you can get */ + BCM2835_PWM_CLOCK_DIVIDER_1 = 1 /*!< 1 = 4.6875kHz, same as divider 4096 */ +} bcm2835PWMClockDivider; + +/*! @} */ + +/* Historical name compatibility */ +#ifndef BCM2835_NO_DELAY_COMPATIBILITY +#define delay(x) bcm2835_delay(x) +#define delayMicroseconds(x) bcm2835_delayMicroseconds(x) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + /*! \defgroup BCM2835initgrp Library initialisation and management + \ingroup BCM2835grp + These functions allow you to intialise and control the bcm2835 library + @{ + */ + + /*! Initialise the library by opening /dev/mem (if you are root) + or /dev/gpiomem (if you are not) + and getting pointers to the + internal memory for BCM 2835 device registers. You must call this (successfully) + before calling any other + functions in this library (except bcm2835_set_debug). + If bcm2835_init() fails by returning 0, + calling any other function may result in crashes or other failures. + If bcm2835_init() succeeds but you are not running as root, then only gpio operations + are permitted, and calling any other functions may result in crashes or other failures. . + Prints messages to stderr in case of errors. + \return 1 if successful else 0 + */ + extern int bcm2835_init(void); + + /*! Close the library, deallocating any allocated memory and closing /dev/mem + \return 1 if successful else 0 + */ + extern int bcm2835_close(void); + + /*! Sets the debug level of the library. + A value of 1 prevents mapping to /dev/mem, and makes the library print out + what it would do, rather than accessing the GPIO registers. + A value of 0, the default, causes normal operation. + Call this before calling bcm2835_init(); + \param[in] debug The new debug level. 1 means debug + */ + extern void bcm2835_set_debug(uint8_t debug); + + /*! Returns the version number of the library, same as BCM2835_VERSION + \return the current library version number + */ + extern unsigned int bcm2835_version(void); + + /*! @} */ + + /*! \defgroup BCM2835lowlevelgrp Low level register access + \ingroup BCM2835grp + These functions provide low level register access, and should not generally + need to be used + + @{ + */ + + /*! Gets the base of a register + \param[in] regbase You can use one of the common values BCM2835_REGBASE_* + in \ref bcm2835RegisterBase + \return the register base + \sa Physical Addresses + */ + extern uint32_t* bcm2835_regbase(uint8_t regbase); + + /*! Reads 32 bit value from a peripheral address WITH a memory barrier before and after each read. + This is safe, but slow. The MB before protects this read from any in-flight reads that didn't + use a MB. The MB after protects subsequent reads from another peripheral. + + \param[in] paddr Physical address to read from. See BCM2835_GPIO_BASE etc. + \return the value read from the 32 bit register + \sa Physical Addresses + */ + extern uint32_t bcm2835_peri_read(volatile uint32_t* paddr); + + /*! Reads 32 bit value from a peripheral address WITHOUT the read barriers + You should only use this when: + o your code has previously called bcm2835_peri_read() for a register + within the same peripheral, and no read or write to another peripheral has occurred since. + o your code has called bcm2835_memory_barrier() since the last access to ANOTHER peripheral. + + \param[in] paddr Physical address to read from. See BCM2835_GPIO_BASE etc. + \return the value read from the 32 bit register + \sa Physical Addresses + */ + extern uint32_t bcm2835_peri_read_nb(volatile uint32_t* paddr); + + + /*! Writes 32 bit value from a peripheral address WITH a memory barrier before and after each write + This is safe, but slow. The MB before ensures that any in-flight write to another peripheral + completes before this write is issued. The MB after ensures that subsequent reads and writes + to another peripheral will see the effect of this write. + + This is a tricky optimization; if you aren't sure, use the barrier version. + + \param[in] paddr Physical address to read from. See BCM2835_GPIO_BASE etc. + \param[in] value The 32 bit value to write + \sa Physical Addresses + */ + extern void bcm2835_peri_write(volatile uint32_t* paddr, uint32_t value); + + /*! Writes 32 bit value from a peripheral address without the write barrier + You should only use this when: + o your code has previously called bcm2835_peri_write() for a register + within the same peripheral, and no other peripheral access has occurred since. + o your code has called bcm2835_memory_barrier() since the last access to ANOTHER peripheral. + + This is a tricky optimization; if you aren't sure, use the barrier version. + + \param[in] paddr Physical address to read from. See BCM2835_GPIO_BASE etc. + \param[in] value The 32 bit value to write + \sa Physical Addresses + */ + extern void bcm2835_peri_write_nb(volatile uint32_t* paddr, uint32_t value); + + /*! Alters a number of bits in a 32 peripheral regsiter. + It reads the current valu and then alters the bits defines as 1 in mask, + according to the bit value in value. + All other bits that are 0 in the mask are unaffected. + Use this to alter a subset of the bits in a register. + Memory barriers are used. Note that this is not atomic; an interrupt + routine can cause unexpected results. + \param[in] paddr Physical address to read from. See BCM2835_GPIO_BASE etc. + \param[in] value The 32 bit value to write, masked in by mask. + \param[in] mask Bitmask that defines the bits that will be altered in the register. + \sa Physical Addresses + */ + extern void bcm2835_peri_set_bits(volatile uint32_t* paddr, uint32_t value, uint32_t mask); + /*! @} end of lowlevel */ + + /*! \defgroup BCM2835gpiogrp GPIO register access + \ingroup BCM2835grp + These functions allow you to control the GPIO interface. You can set the + function of each GPIO pin, read the input state and set the output state. + @{ + */ + + /*! Sets the Function Select register for the given pin, which configures + the pin as Input, Output or one of the 6 alternate functions. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + \param[in] mode Mode to set the pin to, one of BCM2835_GPIO_FSEL_* from \ref bcm2835FunctionSelect + */ + extern void bcm2835_gpio_fsel(uint8_t pin, uint8_t mode); + + /*! Sets the specified pin output to + HIGH. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + \sa bcm2835_gpio_write() + */ + extern void bcm2835_gpio_set(uint8_t pin); + + /*! Sets the specified pin output to + LOW. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + \sa bcm2835_gpio_write() + */ + extern void bcm2835_gpio_clr(uint8_t pin); + + /*! Sets any of the first 32 GPIO output pins specified in the mask to + HIGH. + \param[in] mask Mask of pins to affect. Use eg: (1 << RPI_GPIO_P1_03) | (1 << RPI_GPIO_P1_05) + \sa bcm2835_gpio_write_multi() + */ + extern void bcm2835_gpio_set_multi(uint32_t mask); + + /*! Sets any of the first 32 GPIO output pins specified in the mask to + LOW. + \param[in] mask Mask of pins to affect. Use eg: (1 << RPI_GPIO_P1_03) | (1 << RPI_GPIO_P1_05) + \sa bcm2835_gpio_write_multi() + */ + extern void bcm2835_gpio_clr_multi(uint32_t mask); + + /*! Reads the current level on the specified + pin and returns either HIGH or LOW. Works whether or not the pin + is an input or an output. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + \return the current level either HIGH or LOW + */ + extern uint8_t bcm2835_gpio_lev(uint8_t pin); + + /*! Event Detect Status. + Tests whether the specified pin has detected a level or edge + as requested by bcm2835_gpio_ren(), bcm2835_gpio_fen(), bcm2835_gpio_hen(), + bcm2835_gpio_len(), bcm2835_gpio_aren(), bcm2835_gpio_afen(). + Clear the flag for a given pin by calling bcm2835_gpio_set_eds(pin); + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + \return HIGH if the event detect status for the given pin is true. + */ + extern uint8_t bcm2835_gpio_eds(uint8_t pin); + + /*! Same as bcm2835_gpio_eds() but checks if any of the pins specified in + the mask have detected a level or edge. + \param[in] mask Mask of pins to check. Use eg: (1 << RPI_GPIO_P1_03) | (1 << RPI_GPIO_P1_05) + \return Mask of pins HIGH if the event detect status for the given pin is true. + */ + extern uint32_t bcm2835_gpio_eds_multi(uint32_t mask); + + /*! Sets the Event Detect Status register for a given pin to 1, + which has the effect of clearing the flag. Use this afer seeing + an Event Detect Status on the pin. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + */ + extern void bcm2835_gpio_set_eds(uint8_t pin); + + /*! Same as bcm2835_gpio_set_eds() but clears the flag for any pin which + is set in the mask. + \param[in] mask Mask of pins to clear. Use eg: (1 << RPI_GPIO_P1_03) | (1 << RPI_GPIO_P1_05) + */ + extern void bcm2835_gpio_set_eds_multi(uint32_t mask); + + /*! Enable Rising Edge Detect Enable for the specified pin. + When a rising edge is detected, sets the appropriate pin in Event Detect Status. + The GPRENn registers use + synchronous edge detection. This means the input signal is sampled using the + system clock and then it is looking for a ?011? pattern on the sampled signal. This + has the effect of suppressing glitches. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + */ + extern void bcm2835_gpio_ren(uint8_t pin); + + /*! Disable Rising Edge Detect Enable for the specified pin. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + */ + extern void bcm2835_gpio_clr_ren(uint8_t pin); + + /*! Enable Falling Edge Detect Enable for the specified pin. + When a falling edge is detected, sets the appropriate pin in Event Detect Status. + The GPRENn registers use + synchronous edge detection. This means the input signal is sampled using the + system clock and then it is looking for a ?100? pattern on the sampled signal. This + has the effect of suppressing glitches. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + */ + extern void bcm2835_gpio_fen(uint8_t pin); + + /*! Disable Falling Edge Detect Enable for the specified pin. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + */ + extern void bcm2835_gpio_clr_fen(uint8_t pin); + + /*! Enable High Detect Enable for the specified pin. + When a HIGH level is detected on the pin, sets the appropriate pin in Event Detect Status. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + */ + extern void bcm2835_gpio_hen(uint8_t pin); + + /*! Disable High Detect Enable for the specified pin. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + */ + extern void bcm2835_gpio_clr_hen(uint8_t pin); + + /*! Enable Low Detect Enable for the specified pin. + When a LOW level is detected on the pin, sets the appropriate pin in Event Detect Status. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + */ + extern void bcm2835_gpio_len(uint8_t pin); + + /*! Disable Low Detect Enable for the specified pin. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + */ + extern void bcm2835_gpio_clr_len(uint8_t pin); + + /*! Enable Asynchronous Rising Edge Detect Enable for the specified pin. + When a rising edge is detected, sets the appropriate pin in Event Detect Status. + Asynchronous means the incoming signal is not sampled by the system clock. As such + rising edges of very short duration can be detected. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + */ + extern void bcm2835_gpio_aren(uint8_t pin); + + /*! Disable Asynchronous Rising Edge Detect Enable for the specified pin. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + */ + extern void bcm2835_gpio_clr_aren(uint8_t pin); + + /*! Enable Asynchronous Falling Edge Detect Enable for the specified pin. + When a falling edge is detected, sets the appropriate pin in Event Detect Status. + Asynchronous means the incoming signal is not sampled by the system clock. As such + falling edges of very short duration can be detected. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + */ + extern void bcm2835_gpio_afen(uint8_t pin); + + /*! Disable Asynchronous Falling Edge Detect Enable for the specified pin. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + */ + extern void bcm2835_gpio_clr_afen(uint8_t pin); + + /*! Sets the Pull-up/down register for the given pin. This is + used with bcm2835_gpio_pudclk() to set the Pull-up/down resistor for the given pin. + However, it is usually more convenient to use bcm2835_gpio_set_pud(). + \param[in] pud The desired Pull-up/down mode. One of BCM2835_GPIO_PUD_* from bcm2835PUDControl + \sa bcm2835_gpio_set_pud() + */ + extern void bcm2835_gpio_pud(uint8_t pud); + + /*! Clocks the Pull-up/down value set earlier by bcm2835_gpio_pud() into the pin. + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + \param[in] on HIGH to clock the value from bcm2835_gpio_pud() into the pin. + LOW to remove the clock. + \sa bcm2835_gpio_set_pud() + */ + extern void bcm2835_gpio_pudclk(uint8_t pin, uint8_t on); + + /*! Reads and returns the Pad Control for the given GPIO group. + \param[in] group The GPIO pad group number, one of BCM2835_PAD_GROUP_GPIO_* + \return Mask of bits from BCM2835_PAD_* from \ref bcm2835PadGroup + */ + extern uint32_t bcm2835_gpio_pad(uint8_t group); + + /*! Sets the Pad Control for the given GPIO group. + \param[in] group The GPIO pad group number, one of BCM2835_PAD_GROUP_GPIO_* + \param[in] control Mask of bits from BCM2835_PAD_* from \ref bcm2835PadGroup. Note + that it is not necessary to include BCM2835_PAD_PASSWRD in the mask as this + is automatically included. + */ + extern void bcm2835_gpio_set_pad(uint8_t group, uint32_t control); + + /*! Delays for the specified number of milliseconds. + Uses nanosleep(), and therefore does not use CPU until the time is up. + However, you are at the mercy of nanosleep(). From the manual for nanosleep(): + If the interval specified in req is not an exact multiple of the granularity + underlying clock (see time(7)), then the interval will be + rounded up to the next multiple. Furthermore, after the sleep completes, + there may still be a delay before the CPU becomes free to once + again execute the calling thread. + \param[in] millis Delay in milliseconds + */ + extern void bcm2835_delay (unsigned int millis); + + /*! Delays for the specified number of microseconds. + Uses a combination of nanosleep() and a busy wait loop on the BCM2835 system timers, + However, you are at the mercy of nanosleep(). From the manual for nanosleep(): + If the interval specified in req is not an exact multiple of the granularity + underlying clock (see time(7)), then the interval will be + rounded up to the next multiple. Furthermore, after the sleep completes, + there may still be a delay before the CPU becomes free to once + again execute the calling thread. + For times less than about 450 microseconds, uses a busy wait on the System Timer. + It is reported that a delay of 0 microseconds on RaspberryPi will in fact + result in a delay of about 80 microseconds. Your mileage may vary. + \param[in] micros Delay in microseconds + */ + extern void bcm2835_delayMicroseconds (uint64_t micros); + + /*! Sets the output state of the specified pin + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + \param[in] on HIGH sets the output to HIGH and LOW to LOW. + */ + extern void bcm2835_gpio_write(uint8_t pin, uint8_t on); + + /*! Sets any of the first 32 GPIO output pins specified in the mask to the state given by on + \param[in] mask Mask of pins to affect. Use eg: (1 << RPI_GPIO_P1_03) | (1 << RPI_GPIO_P1_05) + \param[in] on HIGH sets the output to HIGH and LOW to LOW. + */ + extern void bcm2835_gpio_write_multi(uint32_t mask, uint8_t on); + + /*! Sets the first 32 GPIO output pins specified in the mask to the value given by value + \param[in] value values required for each bit masked in by mask, eg: (1 << RPI_GPIO_P1_03) | (1 << RPI_GPIO_P1_05) + \param[in] mask Mask of pins to affect. Use eg: (1 << RPI_GPIO_P1_03) | (1 << RPI_GPIO_P1_05) + */ + extern void bcm2835_gpio_write_mask(uint32_t value, uint32_t mask); + + /*! Sets the Pull-up/down mode for the specified pin. This is more convenient than + clocking the mode in with bcm2835_gpio_pud() and bcm2835_gpio_pudclk(). + \param[in] pin GPIO number, or one of RPI_GPIO_P1_* from \ref RPiGPIOPin. + \param[in] pud The desired Pull-up/down mode. One of BCM2835_GPIO_PUD_* from bcm2835PUDControl + */ + extern void bcm2835_gpio_set_pud(uint8_t pin, uint8_t pud); + + /*! @} */ + + /*! \defgroup BCM2835spigrp SPI access + \ingroup BCM2835grp + These functions let you use SPI0 (Serial Peripheral Interface) to + interface with an external SPI device. + @{ + */ + + /*! Start SPI operations. + Forces RPi SPI0 pins P1-19 (MOSI), P1-21 (MISO), P1-23 (CLK), P1-24 (CE0) and P1-26 (CE1) + to alternate function ALT0, which enables those pins for SPI interface. + You should call bcm2835_spi_end() when all SPI funcitons are complete to return the pins to + their default functions. + \sa bcm2835_spi_end() + \return 1 if successful, 0 otherwise (perhaps because you are not running as root) + */ + extern int bcm2835_spi_begin(void); + + /*! End SPI operations. + SPI0 pins P1-19 (MOSI), P1-21 (MISO), P1-23 (CLK), P1-24 (CE0) and P1-26 (CE1) + are returned to their default INPUT behaviour. + */ + extern void bcm2835_spi_end(void); + + /*! Sets the SPI bit order + NOTE: has no effect. Not supported by SPI0. + Defaults to + \param[in] order The desired bit order, one of BCM2835_SPI_BIT_ORDER_*, + see \ref bcm2835SPIBitOrder + */ + extern void bcm2835_spi_setBitOrder(uint8_t order); + + /*! Sets the SPI clock divider and therefore the + SPI clock speed. + \param[in] divider The desired SPI clock divider, one of BCM2835_SPI_CLOCK_DIVIDER_*, + see \ref bcm2835SPIClockDivider + */ + extern void bcm2835_spi_setClockDivider(uint16_t divider); + + /*! Sets the SPI data mode + Sets the clock polariy and phase + \param[in] mode The desired data mode, one of BCM2835_SPI_MODE*, + see \ref bcm2835SPIMode + */ + extern void bcm2835_spi_setDataMode(uint8_t mode); + + /*! Sets the chip select pin(s) + When an bcm2835_spi_transfer() is made, the selected pin(s) will be asserted during the + transfer. + \param[in] cs Specifies the CS pins(s) that are used to activate the desired slave. + One of BCM2835_SPI_CS*, see \ref bcm2835SPIChipSelect + */ + extern void bcm2835_spi_chipSelect(uint8_t cs); + + /*! Sets the chip select pin polarity for a given pin + When an bcm2835_spi_transfer() occurs, the currently selected chip select pin(s) + will be asserted to the + value given by active. When transfers are not happening, the chip select pin(s) + return to the complement (inactive) value. + \param[in] cs The chip select pin to affect + \param[in] active Whether the chip select pin is to be active HIGH + */ + extern void bcm2835_spi_setChipSelectPolarity(uint8_t cs, uint8_t active); + + /*! Transfers one byte to and from the currently selected SPI slave. + Asserts the currently selected CS pins (as previously set by bcm2835_spi_chipSelect) + during the transfer. + Clocks the 8 bit value out on MOSI, and simultaneously clocks in data from MISO. + Returns the read data byte from the slave. + Uses polled transfer as per section 10.6.1 of the BCM 2835 ARM Peripherls manual + \param[in] value The 8 bit data byte to write to MOSI + \return The 8 bit byte simultaneously read from MISO + \sa bcm2835_spi_transfern() + */ + extern uint8_t bcm2835_spi_transfer(uint8_t value); + + /*! Transfers any number of bytes to and from the currently selected SPI slave. + Asserts the currently selected CS pins (as previously set by bcm2835_spi_chipSelect) + during the transfer. + Clocks the len 8 bit bytes out on MOSI, and simultaneously clocks in data from MISO. + The data read read from the slave is placed into rbuf. rbuf must be at least len bytes long + Uses polled transfer as per section 10.6.1 of the BCM 2835 ARM Peripherls manual + \param[in] tbuf Buffer of bytes to send. + \param[out] rbuf Received bytes will by put in this buffer + \param[in] len Number of bytes in the tbuf buffer, and the number of bytes to send/received + \sa bcm2835_spi_transfer() + */ + extern void bcm2835_spi_transfernb(char* tbuf, char* rbuf, uint32_t len); + + /*! Transfers any number of bytes to and from the currently selected SPI slave + using bcm2835_spi_transfernb. + The returned data from the slave replaces the transmitted data in the buffer. + \param[in,out] buf Buffer of bytes to send. Received bytes will replace the contents + \param[in] len Number of bytes int eh buffer, and the number of bytes to send/received + \sa bcm2835_spi_transfer() + */ + extern void bcm2835_spi_transfern(char* buf, uint32_t len); + + /*! Transfers any number of bytes to the currently selected SPI slave. + Asserts the currently selected CS pins (as previously set by bcm2835_spi_chipSelect) + during the transfer. + \param[in] buf Buffer of bytes to send. + \param[in] len Number of bytes in the tbuf buffer, and the number of bytes to send + */ + extern void bcm2835_spi_writenb(char* buf, uint32_t len); + + /*! @} */ + + /*! \defgroup BCM2835i2cgrp I2C access + \ingroup BCM2835grp + These functions let you use I2C (The Broadcom Serial Control bus with the Philips + I2C bus/interface version 2.1 January 2000.) to interface with an external I2C device. + @{ + */ + + /*! Start I2C operations. + Forces RPi I2C pins P1-03 (SDA) and P1-05 (SCL) + to alternate function ALT0, which enables those pins for I2C interface. + You should call bcm2835_i2c_end() when all I2C functions are complete to return the pins to + their default functions + \return 1 if successful, 0 otherwise (perhaps because you are not running as root) + \sa bcm2835_i2c_end() + */ + extern int bcm2835_i2c_begin(void); + + /*! End I2C operations. + I2C pins P1-03 (SDA) and P1-05 (SCL) + are returned to their default INPUT behaviour. + */ + extern void bcm2835_i2c_end(void); + + /*! Sets the I2C slave address. + \param[in] addr The I2C slave address. + */ + extern void bcm2835_i2c_setSlaveAddress(uint8_t addr); + + /*! Sets the I2C clock divider and therefore the I2C clock speed. + \param[in] divider The desired I2C clock divider, one of BCM2835_I2C_CLOCK_DIVIDER_*, + see \ref bcm2835I2CClockDivider + */ + extern void bcm2835_i2c_setClockDivider(uint16_t divider); + + /*! Sets the I2C clock divider by converting the baudrate parameter to + the equivalent I2C clock divider. ( see \sa bcm2835_i2c_setClockDivider) + For the I2C standard 100khz you would set baudrate to 100000 + The use of baudrate corresponds to its use in the I2C kernel device + driver. (Of course, bcm2835 has nothing to do with the kernel driver) + */ + extern void bcm2835_i2c_set_baudrate(uint32_t baudrate); + + /*! Transfers any number of bytes to the currently selected I2C slave. + (as previously set by \sa bcm2835_i2c_setSlaveAddress) + \param[in] buf Buffer of bytes to send. + \param[in] len Number of bytes in the buf buffer, and the number of bytes to send. + \return reason see \ref bcm2835I2CReasonCodes + */ + extern uint8_t bcm2835_i2c_write(const char * buf, uint32_t len); + + /*! Transfers any number of bytes from the currently selected I2C slave. + (as previously set by \sa bcm2835_i2c_setSlaveAddress) + \param[in] buf Buffer of bytes to receive. + \param[in] len Number of bytes in the buf buffer, and the number of bytes to received. + \return reason see \ref bcm2835I2CReasonCodes + */ + extern uint8_t bcm2835_i2c_read(char* buf, uint32_t len); + + /*! Allows reading from I2C slaves that require a repeated start (without any prior stop) + to read after the required slave register has been set. For example, the popular + MPL3115A2 pressure and temperature sensor. Note that your device must support or + require this mode. If your device does not require this mode then the standard + combined: + \sa bcm2835_i2c_write + \sa bcm2835_i2c_read + are a better choice. + Will read from the slave previously set by \sa bcm2835_i2c_setSlaveAddress + \param[in] regaddr Buffer containing the slave register you wish to read from. + \param[in] buf Buffer of bytes to receive. + \param[in] len Number of bytes in the buf buffer, and the number of bytes to received. + \return reason see \ref bcm2835I2CReasonCodes + */ + extern uint8_t bcm2835_i2c_read_register_rs(char* regaddr, char* buf, uint32_t len); + + /*! Allows sending an arbitrary number of bytes to I2C slaves before issuing a repeated + start (with no prior stop) and reading a response. + Necessary for devices that require such behavior, such as the MLX90620. + Will write to and read from the slave previously set by \sa bcm2835_i2c_setSlaveAddress + \param[in] cmds Buffer containing the bytes to send before the repeated start condition. + \param[in] cmds_len Number of bytes to send from cmds buffer + \param[in] buf Buffer of bytes to receive. + \param[in] buf_len Number of bytes to receive in the buf buffer. + \return reason see \ref bcm2835I2CReasonCodes + */ + extern uint8_t bcm2835_i2c_write_read_rs(char* cmds, uint32_t cmds_len, char* buf, uint32_t buf_len); + + /*! @} */ + + /*! \defgroup BCM2835stgrp System Timer access + \ingroup BCM2835grp + Allows access to and delays using the System Timer Counter. + @{ + */ + + /*! Read the System Timer Counter register. + \return the value read from the System Timer Counter Lower 32 bits register + */ + extern uint64_t bcm2835_st_read(void); + + /*! Delays for the specified number of microseconds with offset. + \param[in] offset_micros Offset in microseconds + \param[in] micros Delay in microseconds + */ + extern void bcm2835_st_delay(uint64_t offset_micros, uint64_t micros); + + /*! @} */ + + /*! \defgroup BCM2835pwmgrp Pulse Width Modulation + \ingroup BCM2835grp + Allows control of 2 independent PWM channels. A limited subset of GPIO pins + can be connected to one of these 2 channels, allowing PWM control of GPIO pins. + You have to set the desired pin into a particular Alt Fun to PWM output. See the PWM + documentation on the Main Page. + @{ + */ + + /*! Sets the PWM clock divisor, + to control the basic PWM pulse widths. + \param[in] divisor Divides the basic 19.2MHz PWM clock. You can use one of the common + values BCM2835_PWM_CLOCK_DIVIDER_* in \ref bcm2835PWMClockDivider + */ + extern void bcm2835_pwm_set_clock(uint32_t divisor); + + /*! Sets the mode of the given PWM channel, + allowing you to control the PWM mode and enable/disable that channel + \param[in] channel The PWM channel. 0 or 1. + \param[in] markspace Set true if you want Mark-Space mode. 0 for Balanced mode. + \param[in] enabled Set true to enable this channel and produce PWM pulses. + */ + extern void bcm2835_pwm_set_mode(uint8_t channel, uint8_t markspace, uint8_t enabled); + + /*! Sets the maximum range of the PWM output. + The data value can vary between 0 and this range to control PWM output + \param[in] channel The PWM channel. 0 or 1. + \param[in] range The maximum value permitted for DATA. + */ + extern void bcm2835_pwm_set_range(uint8_t channel, uint32_t range); + + /*! Sets the PWM pulse ratio to emit to DATA/RANGE, + where RANGE is set by bcm2835_pwm_set_range(). + \param[in] channel The PWM channel. 0 or 1. + \param[in] data Controls the PWM output ratio as a fraction of the range. + Can vary from 0 to RANGE. + */ + extern void bcm2835_pwm_set_data(uint8_t channel, uint32_t data); + + /*! @} */ +#ifdef __cplusplus +} +#endif + +#endif /* BCM2835_H */ diff --git a/drivers/RPi/piHiPri.c b/drivers/RPi/piHiPri.c new file mode 100644 index 0000000..63c0216 --- /dev/null +++ b/drivers/RPi/piHiPri.c @@ -0,0 +1,48 @@ +/* + * piHiPri: + * Simple way to get your program running at high priority + * with realtime schedulling. + * + * Copyright (c) 2012 Gordon Henderson + *********************************************************************** + * This file is part of wiringPi: + * https://projects.drogon.net/raspberry-pi/wiringpi/ + * + * wiringPi 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. + * + * wiringPi 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with wiringPi. + * If not, see . + *********************************************************************** + */ + +#include +#include + +/* + * piHiPri: + * Attempt to set a high priority schedulling for the running program + ********************************************************************************* + */ + +int piHiPri (const int pri) +{ + struct sched_param sched ; + + memset (&sched, 0, sizeof(sched)) ; + + if (pri > sched_get_priority_max (SCHED_RR)) + sched.sched_priority = sched_get_priority_max (SCHED_RR) ; + else + sched.sched_priority = pri ; + + return sched_setscheduler (0, SCHED_RR, &sched) ; +} diff --git a/drivers/RPi/rpi_util.cpp b/drivers/RPi/rpi_util.cpp new file mode 100644 index 0000000..5954fcc --- /dev/null +++ b/drivers/RPi/rpi_util.cpp @@ -0,0 +1,330 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Marcelo Aquino + * Copyright (C) 2016 Marcelo Aquino + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * Based on wiringPi Copyright (c) 2012 Gordon Henderson. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rpi_util.h" +#include "SPI.h" +#include "log.h" + +extern "C" { + int piHiPri(const int pri); +} + +struct ThreadArgs { + void (*func)(); + int gpioPin; +}; + +static pthread_mutex_t intMutex = PTHREAD_MUTEX_INITIALIZER; + +static pthread_t *threadIds[64] = {NULL}; + +// sysFds: +// Map a file descriptor from the /sys/class/gpio/gpioX/value +static int sysFds[64] = +{ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + +#ifdef __RPI_BPLUS +static uint8_t physToGpio[64] = +{ + 255, // 0 + 255, 255, // 1, 2 + 2, 255, + 3, 255, + 4, 14, + 255, 15, + 17, 18, + 27, 255, + 22, 23, + 255, 24, + 10, 255, + 9, 25, + 11, 8, + 255, 7, // 25, 26 +// B+ + 0, 1, + 5, 255, + 6, 12, + 13, 255, + 19, 16, + 26, 20, + 255, 21, +// the P5 connector on the Rev 2 boards: + 255, 255, + 255, 255, + 255, 255, + 255, 255, + 255, 255, + 28, 29, + 30, 31, + 255, 255, + 255, 255, + 255, 255, + 255, 255, +}; +#else +static uint8_t physToGpio[64] = +{ + 255, // 0 + 255, 255, // 1, 2 + 0, 255, + 1, 255, + 4, 14, + 255, 15, + 17, 18, + 21, 255, + 22, 23, + 255, 24, + 10, 255, + 9, 25, + 11, 8, + 255, 7, // 25, 26 + 255, 255, 255, 255, 255, // ... 31 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // ... 47 + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // ... 63 +}; +#endif + +void *interruptHandler(void *args) { + int fd, ret; + struct pollfd polls; + char c; + struct ThreadArgs *arguments = (struct ThreadArgs *)args; + int gpioPin = arguments->gpioPin; + void (*func)() = arguments->func; + delete arguments; + + (void)piHiPri(55); // Only effective if we run as root + + if ((fd = sysFds[gpioPin]) == -1) { + mys_log(LOG_ERR, "Failed to attach interrupt for pin %d\n", gpioPin); + return NULL; + } + + // Setup poll structure + polls.fd = fd; + polls.events = POLLPRI; + + while (1) { + // Wait for it ... + ret = poll(&polls, 1, -1); + if (ret < 0) { + mys_log(LOG_ERR, "Error waiting for interrupt: %s\n", strerror(errno)); + break; + } + // Do a dummy read to clear the interrupt + // A one character read appars to be enough. + // Followed by a seek to reset it. + (void)read (fd, &c, 1) ; + lseek (fd, 0, SEEK_SET) ; + // Call user function. + func(); + } + + close(fd); + + return NULL; +} + +void rpi_util::pinMode(uint8_t physPin, uint8_t mode) { + uint8_t gpioPin = (physPin > 63)? 255 : physToGpio[physPin]; + if (gpioPin == 255) { + mys_log(LOG_ERR, "pinMode: invalid pin: %d\n", physPin); + return; + } + // Check if SPI is in use and target pin is related to SPI + if (SPIClass::isInitialized() && gpioPin >= RPI_GPIO_P1_26 && gpioPin <= RPI_GPIO_P1_23) { + return; + } else { + bcm2835_gpio_fsel(gpioPin, mode); + } +} + +void rpi_util::digitalWrite(uint8_t physPin, uint8_t value) { + uint8_t gpioPin = (physPin > 63)? 255 : physToGpio[physPin]; + if (gpioPin == 255) { + mys_log(LOG_ERR, "digitalWrite: invalid pin: %d\n", physPin); + return; + } + // Check if SPI is in use and target pin is related to SPI + if (SPIClass::isInitialized() && gpioPin >= RPI_GPIO_P1_26 && gpioPin <= RPI_GPIO_P1_23) { + if (value == LOW && (gpioPin == RPI_GPIO_P1_24 || gpioPin == RPI_GPIO_P1_26)) { + SPI.chipSelect(gpioPin); + } + } else { + bcm2835_gpio_write(gpioPin, value); + // Delay to allow any change in state to be reflected in the LEVn, register bit. + delayMicroseconds(1); + } +} + +uint8_t rpi_util::digitalRead(uint8_t physPin) { + uint8_t gpioPin = (physPin > 63)? 255 : physToGpio[physPin]; + if (gpioPin == 255) { + mys_log(LOG_ERR, "digitalRead: invalid pin: %d\n", physPin); + return 0; + } + // Check if SPI is in use and target pin is related to SPI + if (SPIClass::isInitialized() && gpioPin >= RPI_GPIO_P1_26 && gpioPin <= RPI_GPIO_P1_23) { + return 0; + } else { + return bcm2835_gpio_lev(gpioPin); + } +} + +void rpi_util::attachInterrupt(uint8_t physPin, void (*func)(), uint8_t mode) { + FILE *fd; + char fName[40]; + char c; + int count, i; + + uint8_t gpioPin = (physPin > 63)? 255 : physToGpio[physPin]; + if (gpioPin == 255) { + mys_log(LOG_ERR, "attachInterrupt: invalid pin: %d\n", physPin); + return; + } + + if (threadIds[gpioPin] == NULL) { + threadIds[gpioPin] = new pthread_t; + } else { + // Cancel the existing thread for that pin + pthread_cancel(*threadIds[gpioPin]); + // Wait a bit + delay(1L); + } + + // Export pin for interrupt + if ((fd = fopen("/sys/class/gpio/export", "w")) == NULL) { + mys_log(LOG_ERR, "attachInterrupt: Unable to export pin %d for interrupt: %s\n", physPin, strerror(errno)); + exit(1); + } + fprintf(fd, "%d\n", gpioPin); + fclose(fd); + + // Wait a bit the system to create /sys/class/gpio/gpio + delay(1L); + + snprintf(fName, sizeof(fName), "/sys/class/gpio/gpio%d/direction", gpioPin) ; + if ((fd = fopen (fName, "w")) == NULL) { + fprintf (stderr, "attachInterrupt: Unable to open GPIO direction interface for pin %d: %s\n", physPin, strerror(errno)); + exit(1) ; + } + fprintf(fd, "in\n") ; + fclose(fd) ; + + snprintf(fName, sizeof(fName), "/sys/class/gpio/gpio%d/edge", gpioPin) ; + if ((fd = fopen(fName, "w")) == NULL) { + fprintf (stderr, "attachInterrupt: Unable to open GPIO edge interface for pin %d: %s\n", physPin, strerror(errno)); + exit(1) ; + } + switch (mode) { + case CHANGE: fprintf(fd, "both\n"); break; + case FALLING: fprintf(fd, "falling\n"); break; + case RISING: fprintf(fd, "rising\n"); break; + case NONE: fprintf(fd, "none\n"); break; + default: + mys_log(LOG_ERR, "attachInterrupt: Invalid mode\n"); + fclose(fd); + return; + } + fclose(fd); + + if (sysFds[gpioPin] == -1) { + snprintf(fName, sizeof(fName), "/sys/class/gpio/gpio%d/value", gpioPin); + if ((sysFds[gpioPin] = open(fName, O_RDWR)) < 0) { + fprintf (stderr, "Error reading pin %d: %s\n", physPin, strerror(errno)); + exit(1); + } + } + + // Clear any initial pending interrupt + ioctl(sysFds[gpioPin], FIONREAD, &count); + for (i = 0; i < count; ++i) { + if (read(sysFds[gpioPin], &c, 1) == -1) { + mys_log(LOG_ERR, "attachInterrupt: failed to read pin status: %s\n", strerror(errno)); + } + } + + struct ThreadArgs *threadArgs = new struct ThreadArgs; + threadArgs->func = func; + threadArgs->gpioPin = gpioPin; + + // Create a thread passing the pin and function + pthread_create(threadIds[gpioPin], NULL, interruptHandler, (void *)threadArgs); +} + +void rpi_util::detachInterrupt(uint8_t physPin) { + uint8_t gpioPin = (physPin > 63)? 255 : physToGpio[physPin]; + if (gpioPin == 255) { + mys_log(LOG_ERR, "detachInterrupt: invalid pin: %d\n", physPin); + return; + } + + // Cancel the thread + if (threadIds[gpioPin] != NULL) { + pthread_cancel(*threadIds[gpioPin]); + delete threadIds[gpioPin]; + threadIds[gpioPin] = NULL; + } + + // Close filehandle + if (sysFds[gpioPin] != -1) { + close(sysFds[gpioPin]); + sysFds[gpioPin] = -1; + } + + FILE *fp = fopen("/sys/class/gpio/unexport", "w"); + if (fp == NULL) { + mys_log(LOG_ERR, "Unable to unexport pin %d for interrupt\n", gpioPin); + exit(1); + } + fprintf(fp, "%d", gpioPin); + fclose(fp); +} + +uint8_t rpi_util::digitalPinToInterrupt(uint8_t physPin) { + // No need to convert the pin to gpio, we do it in attachInterrupt(). + return physPin; +} + +void rpi_util::interrupts() { + pthread_mutex_unlock(&intMutex); +} + +void rpi_util::noInterrupts() { + pthread_mutex_lock(&intMutex); +} diff --git a/drivers/RPi/rpi_util.h b/drivers/RPi/rpi_util.h new file mode 100644 index 0000000..3d31eb9 --- /dev/null +++ b/drivers/RPi/rpi_util.h @@ -0,0 +1,71 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Marcelo Aquino + * Copyright (C) 2016 Marcelo Aquino + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * Based on wiringPi Copyright (c) 2012 Gordon Henderson. + */ + +#ifndef pins_io_h +#define pins_io_h + +#include +#include "bcm2835.h" + +namespace rpi_util { + +typedef enum { + LSBFIRST = BCM2835_SPI_BIT_ORDER_LSBFIRST, + MSBFIRST = BCM2835_SPI_BIT_ORDER_MSBFIRST +} rpi_bitorder; + +typedef enum { + INPUT = BCM2835_GPIO_FSEL_INPT, + OUTPUT = BCM2835_GPIO_FSEL_OUTP +} rpi_pinmode; + +typedef enum { + CHANGE = 1, + FALLING = 2, + RISING = 3, + NONE = 4 +} rpi_pinedge; + +typedef enum { + PIN_SPI_SS = RPI_GPIO_P1_24, + PIN_SPI_MOSI = RPI_GPIO_P1_19, + PIN_SPI_MISO = RPI_GPIO_P1_21, + PIN_SPI_SCK = RPI_GPIO_P1_23 +} rpi_spipins; + +const uint8_t SS = PIN_SPI_SS; +const uint8_t MOSI = PIN_SPI_MOSI; +const uint8_t MISO = PIN_SPI_MISO; +const uint8_t SCK = PIN_SPI_SCK; + +/* Some useful arduino functions */ +void pinMode(uint8_t physPin, uint8_t mode); +void digitalWrite(uint8_t physPin, uint8_t value); +uint8_t digitalRead(uint8_t physPin); +void attachInterrupt(uint8_t physPin,void (*func)(), uint8_t mode); +void detachInterrupt(uint8_t physPin); +uint8_t digitalPinToInterrupt(uint8_t physPin); +void interrupts(); +void noInterrupts(); + +} + +#endif diff --git a/examples_linux/mysGateway.cpp b/examples_linux/mysGateway.cpp new file mode 100644 index 0000000..c0c69c2 --- /dev/null +++ b/examples_linux/mysGateway.cpp @@ -0,0 +1,71 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Marcelo Aquino + * Copyleft (c) 2016, Marcelo Aquino + * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include +#include +#include + +// For more options run ./configure --help + +// Config file +//#define MY_LINUX_CONFIG_FILE "/etc/mysensors.dat" + +// How many clients should be able to connect to this gateway (default 1) +#define MY_GATEWAY_MAX_CLIENTS 10 + +// Serial config +// Enable this if you are using an Arduino connected to the USB +//#define MY_LINUX_SERIAL_PORT "/dev/ttyACM0" +// Enable this if you need to connect to a controller running on the same device +//#define MY_IS_SERIAL_PTY +// Choose a symlink name for the PTY device +//#define MY_LINUX_SERIAL_PTY "/dev/ttyMySensorsGateway" +// Grant access to the specified system group for the serial device +//#define MY_LINUX_SERIAL_GROUPNAME "tty" + +// MQTT options +//#define MY_CONTROLLER_IP_ADDRESS 192, 168, 178, 68 +//#define MY_PORT 1883 +//#define MY_MQTT_CLIENT_ID "mysensors-1" +//#define MY_MQTT_PUBLISH_TOPIC_PREFIX "mygateway1-out" +//#define MY_MQTT_SUBSCRIBE_TOPIC_PREFIX "mygateway1-in" + +// Enable these if your MQTT broker requires usenrame/password +//#define MY_MQTT_USER "username" +//#define MY_MQTT_PASSWORD "password" + +// Flash leds on rx/tx/err +//#define MY_DEFAULT_ERR_LED_PIN 12 // Error LED pin +//#define MY_DEFAULT_RX_LED_PIN 16 // Receive LED pin +//#define MY_DEFAULT_TX_LED_PIN 18 // Transmit LED pin +// Inverse the blinking feature +//#define MY_WITH_LEDS_BLINKING_INVERSE + +#include + +void setup() { +} + +void presentation() { + // Present locally attached sensors here +} + +void loop() { + // Send locally attached sensors data here +} diff --git a/initscripts/mysgateway.systemd b/initscripts/mysgateway.systemd new file mode 100644 index 0000000..5b707c6 --- /dev/null +++ b/initscripts/mysgateway.systemd @@ -0,0 +1,9 @@ +[Unit] +Description=MySensors Gateway daemon +Requires=network.target + +[Service] +ExecStart=%gateway_dir%/mysGateway + +[Install] +WantedBy=multi-user.target diff --git a/initscripts/mysgateway.sysvinit b/initscripts/mysgateway.sysvinit new file mode 100644 index 0000000..5ca7e12 --- /dev/null +++ b/initscripts/mysgateway.sysvinit @@ -0,0 +1,154 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: mysGateway +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: MySensors Gateway +# Description: This file should be used to construct scripts to be +# placed in /etc/init.d. +### END INIT INFO + +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin +DESC="MySensors Gateway" +NAME=mysGateway +DAEMON=%gateway_dir%/$NAME +DAEMON_ARGS="-b" +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/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 + +# 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 --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --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. +} + +# +# 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 --name $NAME + 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 --name $NAME + 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 + +: