Commit 84f4590d authored by TMRh20's avatar TMRh20

Update for generic spidev/Linux support

- Update main Makefile (CCFLAGS)
- Update examples_RPi Makefile (CCFLAGS)
- Update examples_RPi/gettingstarted.cpp with BBB/spidev pins example
parent 2ca76c6b
...@@ -27,9 +27,6 @@ HEADER_DIR=${PREFIX}/include/RF24 ...@@ -27,9 +27,6 @@ HEADER_DIR=${PREFIX}/include/RF24
# The base location of support files for different devices # The base location of support files for different devices
ARCH_DIR=arch ARCH_DIR=arch
# The recommended compiler flags for the Raspberry Pi
CCFLAGS=-Ofast -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s
# The default objects to compile # The default objects to compile
OBJECTS=RF24.o spi.o OBJECTS=RF24.o spi.o
...@@ -44,6 +41,8 @@ endif ...@@ -44,6 +41,8 @@ endif
ifneq ("$(wildcard $(BCMLOC))","") ifneq ("$(wildcard $(BCMLOC))","")
DRIVER_DIR=$(ARCH_DIR)/RPi DRIVER_DIR=$(ARCH_DIR)/RPi
OBJECTS+=bcm2835.o OBJECTS+=bcm2835.o
# The recommended compiler flags for the Raspberry Pi
CCFLAGS=-Ofast -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s
else else
DRIVER_DIR=$(ARCH_DIR)/BBB DRIVER_DIR=$(ARCH_DIR)/BBB
OBJECTS+=gpio.o compatibility.o OBJECTS+=gpio.o compatibility.o
...@@ -69,7 +68,7 @@ bcm2835.o: $(DRIVER_DIR)/bcm2835.c ...@@ -69,7 +68,7 @@ bcm2835.o: $(DRIVER_DIR)/bcm2835.c
gcc -Wall -fPIC ${CCFLAGS} -c $^ gcc -Wall -fPIC ${CCFLAGS} -c $^
spi.o: $(DRIVER_DIR)/spi.cpp spi.o: $(DRIVER_DIR)/spi.cpp
gcc -Wall -fPIC ${CCFLAGS} -c $^ g++ -Wall -fPIC ${CCFLAGS} -c $^
compatibility.o: $(DRIVER_DIR)/compatibility.c compatibility.o: $(DRIVER_DIR)/compatibility.c
gcc -Wall -fPIC ${CCFLAGS} -c $(DRIVER_DIR)/compatibility.c gcc -Wall -fPIC ${CCFLAGS} -c $(DRIVER_DIR)/compatibility.c
......
#############################################################################
#
# Makefile for librf24 examples on Raspberry Pi
#
# License: GPL (General Public License)
# Author: gnulnulf <arco@appeltaart.mine.nu>
# Date: 2013/02/07 (version 1.0)
#
# Description:
# ------------
# use make all and make install to install the examples
# You can change the install directory by editing the prefix line
#
prefix := /usr/local
# The recommended compiler flags for the Raspberry Pi
CCFLAGS=-Ofast -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s
#CCFLAGS=
# define all programs
#PROGRAMS = scanner pingtest gettingstarted
PROGRAMS = gettingstarted
SOURCES = ${PROGRAMS:=.cpp}
all: ${PROGRAMS}
${PROGRAMS}: ${SOURCES}
g++ ${CCFLAGS} -Wall -I../ -lrf24 $@.cpp -o $@
clean:
rm -rf $(PROGRAMS)
install: all
test -d $(prefix) || mkdir $(prefix)
test -d $(prefix)/bin || mkdir $(prefix)/bin
for prog in $(PROGRAMS); do \
install -m 0755 $$prog $(prefix)/bin; \
done
.PHONY: install
#############################################################################
#
# Makefile for librf24 examples on Raspberry Pi
#
# License: GPL (General Public License)
# Author: gnulnulf <arco@appeltaart.mine.nu>
# Date: 2013/02/07 (version 1.0)
#
# Description:
# ------------
# use make all and make install to install the examples
# You can change the install directory by editing the prefix line
#
prefix := /opt/librf24-examples
# define all programs
PROGRAMS = scanner pingtest pongtest
SOURCES = ${PROGRAMS:=.cpp}
all: ${PROGRAMS}
${PROGRAMS}: ${SOURCES}
# g++ ${CCFLAGS} -Wall -L../librf24/ -lrf24 $@.cpp -o $@
g++ ${CCFLAGS} -L../librf24/ -lrf24 $@.cpp -o $@
clean:
rm -rf $(PROGRAMS)
install: all
test -d $(prefix) || mkdir $(prefix)
test -d $(prefix)/bin || mkdir $(prefix)/bin
for prog in $(PROGRAMS); do \
install -m 0755 $$prog $(prefix)/bin; \
done
.PHONY: install
/*
Copyright (C) 2011 J. Coliz <maniacbug@ymail.com>
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.
*/
/**
* Example RF Radio Ping Pair
*
* This is an example of how to use the RF24 class. Write this sketch to two different nodes,
* connect the role_pin to ground on one. The ping node sends the current time to the pong node,
* which responds by sending the value back. The ping node can then see how long the whole cycle
* took.
*/
#include <cstdlib>
#include <iostream>
#include "RF24.h"
//
// Hardware configuration
//
RF24 radio(115, 117);
// sets the role of this unit in hardware. Connect to GND to be the 'pong' receiver
// Leave open to be the 'ping' transmitter
const int role_pin = 7;
//
// Topology
//
// Radio pipe addresses for the 2 nodes to communicate.
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };
//
// Role management
//
// Set up role. This sketch uses the same software for all the nodes
// in this system. Doing so greatly simplifies testing. The hardware itself specifies
// which node it is.
//
// This is done through the role_pin
//
// The various roles supported by this sketch
typedef enum { role_ping_out = 1, role_pong_back } role_e;
// The debug-friendly names of those roles
const char* role_friendly_name[] = { "invalid", "Ping out", "Pong back"};
// The role of the current running sketch
role_e role;
void setup(void)
{
//
// Role
//
// set up the role pin
// pinMode(role_pin, INPUT);
//digitalWrite(role_pin,HIGH);
// delay(20); // Just to get a solid reading on the role pin
// read the address pin, establish our role
//if ( ! digitalRead(role_pin) )
role = role_ping_out;
//else
// role = role_pong_back;
//
// Print preamble:
//
//Serial.begin(115200);
//printf_begin();
printf("\n\rRF24/examples/pingpair/\n\r");
printf("ROLE: %s\n\r",role_friendly_name[role]);
//
// Setup and configure rf radio
//
radio.begin();
// optionally, increase the delay between retries & # of retries
radio.setRetries(15,15);
// optionally, reduce the payload size. seems to
// improve reliability
// radio.setPayloadSize(8);
radio.setChannel(0x4c);
radio.setPALevel(RF24_PA_MAX);
//
// Open pipes to other nodes for communication
//
// This simple sketch opens two pipes for these two nodes to communicate
// back and forth.
// Open 'our' pipe for writing
// Open the 'other' pipe for reading, in position #1 (we can have up to 5 pipes open for reading)
if ( role == role_ping_out )
{
radio.openWritingPipe(pipes[0]);
radio.openReadingPipe(1,pipes[1]);
}
else
{
radio.openWritingPipe(pipes[1]);
radio.openReadingPipe(1,pipes[0]);
}
//
// Start listening
//
radio.startListening();
//
// Dump the configuration of the rf unit for debugging
//
radio.printDetails();
}
void loop(void)
{
//
// Ping out role. Repeatedly send the current time
//
if (role == role_ping_out)
{
// First, stop listening so we can talk.
radio.stopListening();
// Take the time, and send it. This will block until complete
unsigned long time = __millis();
printf("Now sending %lu...",time);
bool ok = radio.write( &time, sizeof(unsigned long) );
if (ok)
printf("ok...");
else
printf("failed.\n\r");
// Now, continue listening
radio.startListening();
// Wait here until we get a response, or timeout (250ms)
unsigned long started_waiting_at = __millis();
bool timeout = false;
while ( ! radio.available() && ! timeout ) {
// by bcatalin » Thu Feb 14, 2013 11:26 am
__msleep(5); //add a small delay to let radio.available to check payload
if (__millis() - started_waiting_at > 200 )
timeout = true;
}
// Describe the results
if ( timeout )
{
printf("Failed, response timed out.\n\r");
}
else
{
// Grab the response, compare, and send to debugging spew
unsigned long got_time;
radio.read( &got_time, sizeof(unsigned long) );
// Spew it
printf("Got response %lu, round-trip delay: %lu\n\r",got_time,__millis()-got_time);
}
// Try again 1s later
// delay(1000);
sleep(1);
}
//
// Pong back role. Receive each packet, dump it out, and send it back
//
if ( role == role_pong_back )
{
// if there is data ready
if ( radio.available() )
{
// Dump the payloads until we've gotten everything
unsigned long got_time;
bool done = false;
while (!done)
{
// Fetch the payload, and see if this was the last one.
done = radio.read( &got_time, sizeof(unsigned long) );
// Spew it
printf("Got payload %lu...",got_time);
// Delay just a little bit to let the other unit
// make the transition to receiver
delay(20);
}
// First, stop listening so we can talk
radio.stopListening();
// Send the final one back.
printf("Sent response.\n\r");
radio.write( &got_time, sizeof(unsigned long) );
// Now, resume listening so we catch the next packets.
radio.startListening();
}
}
}
int main(int argc, char** argv)
{
setup();
while(1)
loop();
return 0;
}
// vim:cin:ai:sts=2 sw=2 ft=cpp
/*
Copyright (C) 2011 J. Coliz <maniacbug@ymail.com>
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.
*/
/**
* Example RF Radio Ping Pair
*
* This is an example of how to use the RF24 class. Write this sketch to two different nodes,
* connect the role_pin to ground on one. The ping node sends the current time to the pong node,
* which responds by sending the value back. The ping node can then see how long the whole cycle
* took.
*/
#include <cstdlib>
#include <iostream>
#include "RF24.h"
//
// Hardware configuration
//
RF24 radio(115, 117);
// sets the role of this unit in hardware. Connect to GND to be the 'pong' receiver
// Leave open to be the 'ping' transmitter
const int role_pin = 7;
//
// Topology
//
// Radio pipe addresses for the 2 nodes to communicate.
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };
//
// Role management
//
// Set up role. This sketch uses the same software for all the nodes
// in this system. Doing so greatly simplifies testing. The hardware itself specifies
// which node it is.
//
// This is done through the role_pin
//
// The various roles supported by this sketch
typedef enum { role_ping_out = 1, role_pong_back } role_e;
// The debug-friendly names of those roles
const char* role_friendly_name[] = { "invalid", "Ping out", "Pong back"};
// The role of the current running sketch
role_e role;
void setup(void)
{
//
// Role
//
// set up the role pin
// pinMode(role_pin, INPUT);
//digitalWrite(role_pin,HIGH);
// delay(20); // Just to get a solid reading on the role pin
// read the address pin, establish our role
//if ( ! digitalRead(role_pin) )
// role = role_ping_out;
//else
role = role_pong_back;
//
// Print preamble:
//
//Serial.begin(115200);
//printf_begin();
printf("\n\rRF24/examples/pingpair/\n\r");
printf("ROLE: %s\n\r",role_friendly_name[role]);
//
// Setup and configure rf radio
//
radio.begin();
// optionally, increase the delay between retries & # of retries
radio.setRetries(15,15);
// optionally, reduce the payload size. seems to
// improve reliability
// radio.setPayloadSize(8);
radio.setChannel(0x4c);
radio.setPALevel(RF24_PA_LOW);
//
// Open pipes to other nodes for communication
//
// This simple sketch opens two pipes for these two nodes to communicate
// back and forth.
// Open 'our' pipe for writing
// Open the 'other' pipe for reading, in position #1 (we can have up to 5 pipes open for reading)
if ( role == role_ping_out )
{
radio.openWritingPipe(pipes[0]);
radio.openReadingPipe(1,pipes[1]);
}
else
{
radio.openWritingPipe(pipes[1]);
radio.openReadingPipe(1,pipes[0]);
}
//
// Start listening
//
radio.startListening();
//
// Dump the configuration of the rf unit for debugging
//
radio.printDetails();
}
void loop(void)
{
//
// Ping out role. Repeatedly send the current time
//
if (role == role_ping_out)
{
// First, stop listening so we can talk.
radio.stopListening();
// Take the time, and send it. This will block until complete
unsigned long time = __millis();
printf("Now sending %lu...",time);
bool ok = radio.write( &time, sizeof(unsigned long) );
if (ok)
printf("ok...");
else
printf("failed.\n\r");
// Now, continue listening
radio.startListening();
// Wait here until we get a response, or timeout (250ms)
unsigned long started_waiting_at = __millis();
bool timeout = false;
while ( ! radio.available() && ! timeout ) {
// by bcatalin » Thu Feb 14, 2013 11:26 am
__msleep(5); //add a small delay to let radio.available to check payload
if (__millis() - started_waiting_at > 200 )
timeout = true;
}
// Describe the results
if ( timeout )
{
printf("Failed, response timed out.\n\r");
}
else
{
// Grab the response, compare, and send to debugging spew
unsigned long got_time;
radio.read( &got_time, sizeof(unsigned long) );
// Spew it
printf("Got response %lu, round-trip delay: %lu\n\r",got_time,__millis()-got_time);
}
// Try again 1s later
// delay(1000);
sleep(1);
}
//
// Pong back role. Receive each packet, dump it out, and send it back
//
if ( role == role_pong_back )
{
// if there is data ready
if ( radio.available() )
{
// Dump the payloads until we've gotten everything
unsigned long got_time;
bool done = false;
while (!done)
{
// Fetch the payload, and see if this was the last one.
done = radio.read( &got_time, sizeof(unsigned long) );
// Spew it
printf("Got payload %lu...",got_time);
// Delay just a little bit to let the other unit
// make the transition to receiver
delay(20);
}
// First, stop listening so we can talk
radio.stopListening();
// Send the final one back.
printf("Sent response.\n\r");
radio.write( &got_time, sizeof(unsigned long) );
// Now, resume listening so we catch the next packets.
radio.startListening();
}
}
}
int main(int argc, char** argv)
{
setup();
while(1)
loop();
return 0;
}
// vim:cin:ai:sts=2 sw=2 ft=cpp
/*
Copyright (C) 2011 J. Coliz <maniacbug@ymail.com>
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.
*/
/**
* Channel scanner
*
* Example to detect interference on the various channels available.
* This is a good diagnostic tool to check whether you're picking a
* good channel for your application.
*
* Inspired by cpixip.
* See http://arduino.cc/forum/index.php/topic,54795.0.html
*/
#include <cstdlib>
#include <iostream>
#include "RF24.h"
using namespace std;
//
// Hardware configuration
//
// CE and CSN pins
RF24 radio(115, 117);
//
// Channel info
//
//const uint8_t num_channels = 128;
const uint8_t num_channels = 120;
uint8_t values[num_channels];
//
// Setup
//
void setup(void)
{
//
// Print preamble
//
//Serial.begin(57600);
//printf_begin();
printf("\n\rRF24/examples/scanner/\n\r");
//
// Setup and configure rf radio
//
radio.begin();
radio.setAutoAck(false);
// Get into standby mode
radio.startListening();
radio.stopListening();
radio.printDetails();
// Print out header, high then low digit
int i = 0;
while ( i < num_channels )
{
printf("%x",i>>4);
++i;
}
printf("\n\r");
i = 0;
while ( i < num_channels )
{
printf("%x",i&0xf);
++i;
}
printf("\n\r");
}
//
// Loop
//
/*
const int num_reps = 100;
void loop2(void)
{
// Clear measurement values
memset(values,0,sizeof(values));
// Scan all channels num_reps times
int rep_counter = num_reps;
while (rep_counter--)
{
int i = num_channels;
while (i--)
{
// Select this channel
radio.setChannel(i);
// Listen for a little
radio.startListening();
delayMicroseconds(128);
radio.stopListening();
// Did we get a carrier?
if ( radio.testCarrier() )
++values[i];
}
}
// Print out channel measurements, clamped to a single hex digit
int i = 0;
while ( i < num_channels )
{
printf("%x",min(0xf,values[i]&0xf));
++i;
}
printf("\n\r");
}
*/
//
// Loop
//
const int num_reps = 100;
int reset_array=0;
void loop(void)
{
if ( reset_array == 1 ) {
// Clear measurement values
memset(values,0,sizeof(values));
printf("\n\r");
}
// Scan all channels num_reps times
int i = num_channels;
while (i--)
{
// Select this channel
radio.setChannel(i);
// Listen for a little
radio.startListening();
delayMicroseconds(128);
radio.stopListening();
// Did we get a carrier?
if ( radio.testCarrier() )
++values[i];
if ( values[i] == 0xf ) {
reset_array = 2;
}
}
// Print out channel measurements, clamped to a single hex digit
i = 0;
while ( i < num_channels )
{
printf("%x",min(0xf,values[i]&0xf));
++i;
}
printf("\n\r");
}
int main(int argc, char** argv)
{
setup();
while(1)
loop();
return 0;
}
// vim:ai:cin:sts=2 sw=2 ft=cpp
/*
Copyright (C) 2011 J. Coliz <maniacbug@ymail.com>
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.
03/17/2013 : Charles-Henri Hallard (http://hallard.me)
Modified to use with Arduipi board http://hallard.me/arduipi
Changed to use modified bcm2835 and RF24 library
TMRh20 2014 - Updated to work with optimized RF24 Arduino library
*/
/**
* Example RF Radio Ping Pair
*
* This is an example of how to use the RF24 class on RPi, communicating to an Arduino running
* the GettingStarted sketch.
*/
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <string>
#include <RF24/RF24.h>
using namespace std;
//
// Hardware configuration
// Configure the appropriate pins for your connections
// Radio CE Pin, CSN Pin, SPI Speed
// Setup for GPIO 22 CE and CE1 CSN with SPI Speed @ 1Mhz
//RF24 radio(RPI_V2_GPIO_P1_22, RPI_V2_GPIO_P1_26, BCM2835_SPI_SPEED_1MHZ);
// Setup for GPIO 22 CE and CE0 CSN with SPI Speed @ 4Mhz
//RF24 radio(RPI_V2_GPIO_P1_22, BCM2835_SPI_CS0, BCM2835_SPI_SPEED_4MHZ);
// NEW: Setup for RPi B+
//RF24 radio(RPI_BPLUS_GPIO_J8_15,RPI_BPLUS_GPIO_J8_24, BCM2835_SPI_SPEED_8MHZ);
// Setup for GPIO 15 CE and CE0 CSN with SPI Speed @ 8Mhz
//RF24 radio(RPI_V2_GPIO_P1_15, RPI_V2_GPIO_P1_24, BCM2835_SPI_SPEED_8MHZ);
// Pin connections as per above for RPi. Needs to be modified for BBB
RF24 radio(22,8);
/********** User Config *********/
// Assign a unique identifier for this node, 0 or 1
bool radioNumber = 0;
/********************************/
// Radio pipe addresses for the 2 nodes to communicate.
const uint8_t pipes[][6] = {"1Node","2Node"};
int main(int argc, char** argv){
bool role_ping_out = true, role_pong_back = false;
bool role = role_pong_back;
printf("RF24/examples/GettingStarted/\n");
// Setup and configure rf radio
radio.begin();
radio.setDataRate(RF24_1MBPS);
// optionally, increase the delay between retries & # of retries
radio.setRetries(1,2);
// Dump the configuration of the rf unit for debugging
radio.printDetails();
/********* Role chooser ***********/
printf("\n ************ Role Setup ***********\n");
string input = "";
char myChar = {0};
cout << "Choose a role: Enter 0 for pong_back, 1 for ping_out (CTRL+C to exit) \n>";
getline(cin,input);
if(input.length() == 1) {
myChar = input[0];
if(myChar == '0'){
cout << "Role: Pong Back, awaiting transmission " << endl << endl;
}else{ cout << "Role: Ping Out, starting transmission " << endl << endl;
role = role_ping_out;
}
}
/***********************************/
// This simple sketch opens two pipes for these two nodes to communicate
// back and forth.
if ( !radioNumber ) {
radio.openWritingPipe(pipes[0]);
radio.openReadingPipe(1,pipes[1]);
} else {
radio.openWritingPipe(pipes[1]);
radio.openReadingPipe(1,pipes[0]);
}
radio.startListening();
// forever loop
while (1)
{
if (role == role_ping_out)
{
// First, stop listening so we can talk.
//radio.stopListening();
// Take the time, and send it. This will block until complete
printf("Now sending...\n");
unsigned long time = millis();
radio.startFastWrite( &time, sizeof(unsigned long),0,1 );
bool ok = radio.txStandBy(100,true);
if (!ok){
printf("failed.\n");
}
// Now, continue listening
radio.startListening();
// Wait here until we get a response, or timeout (250ms)
unsigned long started_waiting_at = millis();
bool timeout = false;
while ( ! radio.available() && ! timeout ) {
if (millis() - started_waiting_at > 200 )
timeout = true;
}
// Describe the results
if ( timeout )
{
printf("Failed, response timed out.\n");
}
else
{
// Grab the response, compare, and send to debugging spew
unsigned long got_time;
radio.read( &got_time, sizeof(unsigned long) );
// Spew it
printf("Got response %lu, round-trip delay: %lu\n",got_time,millis()-got_time);
}
sleep(1);
}
//
// Pong back role. Receive each packet, dump it out, and send it back
//
if ( role == role_pong_back )
{
// if there is data ready
if ( radio.available() )
{
// Dump the payloads until we've gotten everything
unsigned long got_time;
// Fetch the payload, and see if this was the last one.
while(radio.available()){
radio.read( &got_time, sizeof(unsigned long) );
}
//delay(1);
radio.stopListening();
// delay(1);
radio.write( &got_time, sizeof(unsigned long) );
// Now, resume listening so we catch the next packets.
radio.startListening();
// Spew it
printf("Got payload(%d) %lu...\n",sizeof(unsigned long), got_time);
delay(925); //Delay after payload responded to, minimize RPi CPU time
}
}
} // forever loop
return 0;
}
...@@ -13,9 +13,13 @@ ...@@ -13,9 +13,13 @@
# #
prefix := /usr/local prefix := /usr/local
# Detect the Raspberry Pi by the existence of the bcm_host.h file
BCMLOC=/opt/vc/include/bcm_host.h
ifneq ("$(wildcard $(BCMLOC))","")
# The recommended compiler flags for the Raspberry Pi # The recommended compiler flags for the Raspberry Pi
CCFLAGS=-Ofast -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s CCFLAGS=-Ofast -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s
#CCFLAGS= endif
# define all programs # define all programs
#PROGRAMS = scanner pingtest gettingstarted #PROGRAMS = scanner pingtest gettingstarted
......
...@@ -44,6 +44,9 @@ using namespace std; ...@@ -44,6 +44,9 @@ using namespace std;
// Setup for GPIO 15 CE and CE0 CSN with SPI Speed @ 8Mhz // Setup for GPIO 15 CE and CE0 CSN with SPI Speed @ 8Mhz
RF24 radio(RPI_V2_GPIO_P1_15, RPI_V2_GPIO_P1_24, BCM2835_SPI_SPEED_8MHZ); RF24 radio(RPI_V2_GPIO_P1_15, RPI_V2_GPIO_P1_24, BCM2835_SPI_SPEED_8MHZ);
// Setup for ARM(Linux) devices like BBB using spidev
//RF24 radio(115,117);
/********** User Config *********/ /********** User Config *********/
// Assign a unique identifier for this node, 0 or 1 // Assign a unique identifier for this node, 0 or 1
bool radioNumber = 1; bool radioNumber = 1;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment