Closed Resseguie closed 6 years ago
I've been casually thinking about this for about a year and the basic sketch I always return to is:
Receiver
class
channels
property, that's an array and the values are the most recently received valueTransmitter
class
That's about as far as I usually get. It's critically important that these are designed with the controller pattern.
I'm not sure we need Transmitter
? Seems like that's on "the other end" that we don't care about. At least I can't think of a use case. We just need to know the value the transmitter sends, via the receiver.
I'll need to think through the channels
property, to make sure that makes sense for PPM as well. (I think so, but want to think it through.)
That may very well be the case, I just figured I'd put it there and we can let the junk shake out :)
(There are Spektrum compatible Tx an Rx made by Orange that are much cheaper.)
I just purchased these, should receive them tomorrow
The major issue we face is that pulseIn
is process blocking. Some things to consider:
I like the idea of defining our own I2C backpacked peripheral, because we can control every aspect. I've come to realize that the design decisions made by many (not all) hardware creators is generally not very compatible with our async goals.
Is there a way to handle PPM signal over firmata?
@henricavalcante no, but it can be made available via I2C
@Resseguie
I assembled this gizmo as a basic "backpack" starting point. In the photos below, you'll notice that the intention is to plug this thing directly into the receiver unit's channel pins. The fritzing shows everything facing forward, but that's just only for illustrative purposes (and a limitation of fritzing)
G: Ground V: VCC C: Channel
I haven't added a label for channels, because my actual receiver won't arrive until later today, so I'm not sure which direction the channels are in.
Nice. I'll try to build something similar.
Welllllllll my receiver just arrived and unfortunately the one I ordered has notches
I cut them out and made it fit. Then I made a newer, less messy backpack. Pictures later
That's the same Rx I have. FYI, the typical method for connecting such a receiver to your RC project (drone, truck, etc) is with a breakout cable, such as this Naze32 one: http://www.readymaderc.com/store/index.php?main_page=product_info&products_id=3062
That also gives you more flexibility in positioning components on your build. I'll probably make my backpack to accept something similar to that.
Note that weight is also a factor for many uses of this, so I'd like to investigate the minimum backpack necessary once we get the design working.
The second one I built significantly reduced materials:
FYI, the typical method for connecting such a receiver to your RC project (drone, truck, etc) is with a breakout cable, such as this Naze32 one: http://www.readymaderc.com/store/index.php?main_page=product_info&products_id=3062 That also gives you more flexibility in positioning components on your build. I'll probably make my backpack to accept something similar to that.
But you won't be able to use such a thing if you're trying to read the pulse values from the receiver—right? You're effectively replacing this thing and adding a "middle man" to intercept the pulses to do something else with them.
I'm testing the Pin Change Interrupts to decode pwm signal and it works, but I'm trying to figure out a way to send the information to johnny-five like firmata 'cause I'm using a crius board which uses atmega 2560 and has more sensors and I need them, when I upload firmata to board I cant read pwm signals, when I upload my code with Pin Change Interrupt I only access pwm signals from receiver.
This is the board:
This is the receiver:
I have a quadcopter working well with this firmware and I'm trying to make a car controlled by johnny-five.
There is a way to rewrite firmata with Pin Change Interrupts in some pins? I want still firmata and read pwm signals in same board.
something like:
I'm crazy or there is a way to make it possible?
The reader code will go on a different microprocessor. I have some super basic firmware written that gets us 6 channels of PPM values. How are you using the pin change interrupts? I couldn't get them to do what I wanted. Can you share your work so far? Tomorrow I will push out what I have so far
It's my setup:
It's my code:
#include <PinChangeInt.h> //https://github.com/GreyGnome/PinChangeInt
#define FIRST_PIN 62
volatile int pwm[] = {0,0,0,0,0,0,0,0,0,0};
volatile int last_up[] = {0,0,0,0,0,0,0,0,0,0};
void up()
{
uint8_t pin=PCintPort::arduinoPin;
PCintPort::attachInterrupt(pin, &down, FALLING);
last_up[pin - FIRST_PIN] = micros();
}
void down() {
uint8_t pin=PCintPort::arduinoPin;
PCintPort::attachInterrupt(pin, &up, RISING);
pwm[pin - FIRST_PIN] = micros()-last_up[pin - FIRST_PIN];
Serial.print(pin);
Serial.print(" - ");
Serial.println(pwm[pin - FIRST_PIN]);
}
void setup() {
pinMode(A8, INPUT); digitalWrite(A8, HIGH);
pinMode(A9, INPUT); digitalWrite(A9, HIGH);
pinMode(A10, INPUT); digitalWrite(A10, HIGH);
pinMode(A11, INPUT); digitalWrite(A11, HIGH);
pinMode(A12, INPUT); digitalWrite(A12, HIGH);
pinMode(A13, INPUT); digitalWrite(A13, HIGH);
pinMode(A14, INPUT); digitalWrite(A14, HIGH);
pinMode(A15, INPUT); digitalWrite(A15, HIGH);
Serial.begin(115200);
//start interrupts
PCintPort::attachInterrupt(A8, &up, RISING);
PCintPort::attachInterrupt(A9, &up, RISING);
PCintPort::attachInterrupt(A10, &up, RISING);
PCintPort::attachInterrupt(A11, &up, RISING);
PCintPort::attachInterrupt(A12, &up, RISING);
PCintPort::attachInterrupt(A13, &up, RISING);
PCintPort::attachInterrupt(A14, &up, RISING);
PCintPort::attachInterrupt(A15, &up, RISING);
}
void loop() { }
And this is some serial output:
66 - 1456
62 - 1472
64 - 680
65 - 1156
63 - 1476
68 - 1464
67 - 1084
66 - 1448
62 - 1476
64 - 684
65 - 1156
63 - 1480
68 - 1468
67 - 1080
66 - 1452
62 - 1476
64 - 680
65 - 1156
63 - 1476
68 - 1464
67 - 1080
66 - 1452
62 - 1472
64 - 680
65 - 1156
63 - 1476
68 - 1464
When I move the sticks on controller, values on serial output change between ~600 and ~1500
Are you getting receiver PPM signal by channels output? how? If you get signal from each channel should be a PWM signal. Have you checked it on oscilloscope?
If you get signal from each channel should be a PWM signal
Yes, I was mistaken.
Here is the crude firmware I wrote:
#include <Wire.h>
#define DEBUG_MODE 1
// Address Pins
#define AD0 8
#define AD1 9
// I2C Defaults
#define I2C_DEFAULT_ADDRESS 0x0A
#define I2C_BUFFER_SIZE 12
byte buffer[I2C_BUFFER_SIZE];
int addressPins[] = { AD0, AD1 };
int address = I2C_DEFAULT_ADDRESS;
int first = 2;
int channels = 6;
void resetState() {
for (int i = first; i < first + channels; i++) {
pinMode(i, INPUT);
}
}
void setup() {
int offset = 0;
for (int i = 0; i < 2; i++) {
pinMode(addressPins[i], INPUT);
if (digitalRead(addressPins[i])) {
offset |= 1 << i;
}
}
address += offset;
#if DEBUG_MODE
Serial.begin(9600);
#endif
resetState();
Wire.begin(address);
Wire.onRequest(onRequest);
}
void loop() {
uint16_t pulses[channels];
for (int i = first; i < first + channels; i++) {
pulses[i - first] = pulseIn(i, HIGH, 35000);
}
#if DEBUG_MODE
for (int i = 0; i < channels; i++) {
Serial.print(i);
Serial.print(": ");
Serial.println(pulses[i]);
}
#endif
buffer[0] = pulses[0] >> 8;
buffer[1] = pulses[0] & 0xFF;
buffer[2] = pulses[1] >> 8;
buffer[3] = pulses[1] & 0xFF;
buffer[4] = pulses[2] >> 8;
buffer[5] = pulses[2] & 0xFF;
buffer[6] = pulses[3] >> 8;
buffer[7] = pulses[3] & 0xFF;
buffer[8] = pulses[4] >> 8;
buffer[9] = pulses[4] & 0xFF;
buffer[10] = pulses[5] >> 8;
buffer[11] = pulses[5] & 0xFF;
}
void onRequest() {
Wire.write(buffer, I2C_BUFFER_SIZE);
}
I don't want to put this inside any version of Firmata, because that means it's not available on any other platform that Johnny-Five supports. The benefit of using the I2C slave backpack approach is that all we need is I2C support on other platforms and we're good to go. I'm going to try to combine your interrupt approach with my slave firmware (I will obviously cite you as co-author)
I'm not sure we should use this: https://github.com/GreyGnome/PinChangeInt it's not available via the Arduino IDE "Manage Libraries" interface, which means people have to go through the hassle of adding it via the "Add .ZIP Library". Both are terrible.
Also, there is a notice on the repo:
NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE This library is deprecated as of April 3, 2015. This means that I will be providing bug fixes for some time, but users are encouraged to migrate to the EnableInterrupt library, at https://github.com/GreyGnome/EnableInterrupt . It is faster and easier to use. Thank you.
The version referenced does exist in the Arduino IDE's Library Manager.
The interrupt version is giving me wildly inaccurate results and appears to be slower than the version with pulseIn... which is strange.
What if we use EnableInterrupt instead?
We'll have to either way.
So... this works awesome when channels is set to 1. Once it I set back to 6 or 8, the values are garbage.
#define EI_ARDUINO_INTERRUPTED_PIN
#include <EnableInterrupt.h>
#include <Wire.h>
#define DEBUG_MODE 1
// Address Pins
#define AD0 11
#define AD1 12
// I2C Defaults
#define I2C_DEFAULT_ADDRESS 0x0A
#define I2C_BUFFER_SIZE 16
byte buffer[I2C_BUFFER_SIZE];
int addressPins[] = { AD0, AD1 };
int address = I2C_DEFAULT_ADDRESS;
int first = 2;
int channels = 1;
volatile int pulses[8];
volatile int last_up[8];
void up() {
uint8_t pin = arduinoInterruptedPin;
enableInterrupt(pin, &down, FALLING);
#if DEBUG_MODE
Serial.print("pin up: ");
Serial.println(pin);
#endif
last_up[pin - first] = micros();
}
void down() {
uint8_t pin = arduinoInterruptedPin;
enableInterrupt(pin, &up, RISING);
// #if DEBUG_MODE
// Serial.print("pin down: ");
// Serial.println(pin);
// #endif
pulses[pin - first] = micros() - last_up[pin - first];
#if DEBUG_MODE
Serial.print(pin);
Serial.print(": ");
Serial.println(pulses[pin - first]);
#endif
}
void resetState() {
for (int i = first; i < first + channels; i++) {
pulses[i - first] = 0;
last_up[i - first] = 0;
pinMode(i, INPUT_PULLUP);
enableInterrupt(i, &up, RISING);
}
}
void setup() {
// First check if the address was set via pins 11 or 12
int offset = 0;
for (int i = 0; i < 2; i++) {
pinMode(addressPins[i], INPUT);
if (digitalRead(addressPins[i])) {
offset |= 1 << i;
}
}
address += offset;
resetState();
#if DEBUG_MODE
Serial.begin(9600);
#endif
// Initialize Slave
Wire.begin(address);
Wire.onRequest(onRequest);
Wire.onReceive(onReceive);
}
void loop() {
for (int i = 0; i < channels; i++) {
buffer[i * 2] = pulses[i] >> 8;
buffer[i * 2 + 1] = pulses[i] & 0xFF;
}
}
void onRequest() {
Wire.write(buffer, I2C_BUFFER_SIZE);
}
void onReceive(int count) {
while (Wire.available()) {
// Command 0x01 => Reset.
if (Wire.read() == 0x01) {
Serial.println("RESET");
resetState();
}
}
}
............
Can we discuss the pros and cons of pulseIn vs interrupts, given the context (which is: 100% dedicated microprocessor that must only write to I2C bus)
Im going home now and I will make some tests about differences, interrupts should be better because is non blocking, but I'm thinking, if you want to make a hardware exclusive to convert signals why not convert pwm to analog signal with capacitors and use arduino ADC ports?
Are you building a tessel 2 module?
I'm getting better results using atmega 2560 microprocessor than using atmega 328p, trying to figure out the problem.
Check using without Serial output inside interrupt delegates like this:
#define EI_ARDUINO_INTERRUPTED_PIN
#include <EnableInterrupt.h>
#include <Wire.h>
#define DEBUG_MODE 1
// Address Pins
#define AD0 11
#define AD1 12
// I2C Defaults
#define I2C_DEFAULT_ADDRESS 0x0A
#define I2C_BUFFER_SIZE 16
byte buffer[I2C_BUFFER_SIZE];
int addressPins[] = { AD0, AD1 };
int address = I2C_DEFAULT_ADDRESS;
int first = 2;
int channels = 8;
volatile int pulses[8];
volatile int last_up[8];
void up() {
volatile uint8_t pin = arduinoInterruptedPin;
enableInterrupt(pin, &down, FALLING);
last_up[pin - first] = micros();
}
void down() {
volatile uint8_t pin = arduinoInterruptedPin;
enableInterrupt(pin, &up, RISING);
pulses[pin - first] = micros() - last_up[pin - first];
}
void resetState() {
for (int i = first; i < first + channels; i++) {
pulses[i - first] = 0;
last_up[i - first] = 0;
pinMode(i, INPUT_PULLUP);
enableInterrupt(i, &up, RISING);
}
}
void setup() {
// First check if the address was set via pins 11 or 12
int offset = 0;
for (int i = 0; i < 2; i++) {
pinMode(addressPins[i], INPUT);
if (digitalRead(addressPins[i])) {
offset |= 1 << i;
}
}
address += offset;
resetState();
#if DEBUG_MODE
Serial.begin(9600);
#endif
// Initialize Slave
Wire.begin(address);
Wire.onRequest(onRequest);
Wire.onReceive(onReceive);
}
void loop() {
#if DEBUG_MODE
for (int i = first; i < first + channels - 1; i++) {
Serial.print(pulses[i - first]);
Serial.print(" - ");
}
Serial.println(pulses[channels - 1]);
#endif
for (int i = 0; i < channels; i++) {
buffer[i * 2] = pulses[i] >> 8;
buffer[i * 2 + 1] = pulses[i] & 0xFF;
}
}
void onRequest() {
Wire.write(buffer, I2C_BUFFER_SIZE);
}
void onReceive(int count) {
while (Wire.available()) {
// Command 0x01 => Reset.
if (Wire.read() == 0x01) {
Serial.println("RESET");
resetState();
}
}
}
works like a charm here with arduino pro mini (atmega 328p 16MHZ) ;)
Are you building a tessel 2 module?
Not specifically, but I plan to use this with Tessel 2 :D
Check using without Serial output inside interrupt delegates like this
Brilliant! I guess I didn't realize that Serial output inside the interrupt would cause such issues—great work :D
works like a charm here with arduino pro mini (atmega 328p 16MHZ) ;)
Can't wait to try this!
@henricavalcante :clap: this works perfectly
I guess I didn't realize that Serial output inside the interrupt would cause such issues
Yeah typically you want to do as little as possible inside the interrupt service handler (ISR) - usually just toggle a variable (marked as "volatile") and then based on the state of that variable, do any actual reporting or heavy calculations in the main loop. Some architectures limit the size of the stack for the ISR to just a few bytes so you can't do much and really want to return as quickly as possible.
Some architectures limit the size of the stack for the ISR to just a few bytes so you can't do much and really want to return as quickly as possible.
I feel like I learned a lot today—thanks for providing that additional insight :)
There are also scenarios where you want to disable the interrupts globally while executing a piece of code. You have to approach from the perspective of what happens if an interrupt is triggered while some block of code is executing and if so disable interrupts globally before that block and reenable after that block. You will see a lot of this if you dig into the code in Adafruit libraries and other well-written Arduino libraries that use interrupts. You'll see this in PJRC libs as well.
here's an example where disabling and reenabling global interrupts is useful: https://github.com/PaulStoffregen/CapacitiveSensor/blob/master/CapacitiveSensor.cpp#L154-L183
One scenario where it's necessary to disable interrupts in the above code is when the pulses array is accessed in the loop function.
void loop() {
for (int i = 0; i < channels; i++) {
buffer[i * 2] = pulses[i] >> 8;
buffer[i * 2 + 1] = pulses[i] & 0xFF;
}
}
When the following code is executed
buffer[i * 2] = pulses[i] >> 8;
there is no guarantee that loading the values of the two bytes of the 16 bit integer pulses[i]
into registers is an atomic operation that can't be interrupted. There may be an interrupt after the first byte is loaded and before the second byte is loaded. This can potentially mess things things up.
The same applies to the following code
buffer[i * 2 + 1] = pulses[i] & 0xFF;
There is a similar issue with the following code
buffer[i * 2] = pulses[i] >> 8;
buffer[i * 2 + 1] = pulses[i] & 0xFF;
There may be an interrupt between the first and second line of code that changes the value of pulses[i]
.
@fivdi
This should address that?
int pulse = pulses[i];
buffer[i * 2] = pulse >> 8;
buffer[i * 2 + 1] = pulse & 0xFF;
@soundanalogous
Per your suggestion, I tried using the noInterrupt()/interrupt()
calls (in a few different places), but everywhere I tried just crashed the board :\
This should address that?
int pulse = pulses[i];
buffer[i * 2] = pulse >> 8; buffer[i * 2 + 1] = pulse & 0xFF;
@rwaldron
I'm afraid not. The AVR 8-bit instructions set doesn't have an instruction for loading 16 bit values. This forces to the compiler to generate two instructions each of which loads a single byte. Because there are two instructions there can be an interrupt between the execution of the first and second.
Well, then I guess it's a deal breaker.
Can someone tell me why we need to use interrupts here?
I think because reading pwm values with pulseIn will block everything, with interrupts the microprocessor will be able to work between pwm edges. I will try to figure out a way to make data atomic while i2c buffering.
I think because reading pwm values with pulseIn will block everything, with interrupts the microprocessor will be able to work between pwm edges.
Thanks, I know why we might want to use interrupts in general, but that strategy is generally used in scenarios where the entire program, including all user application code is running on the same processor. We're only using this to read a maximum of 8 channels (maybe someone will want to use a 10 or 12 channel unit, but I'll worry about that when the day actually comes), and write the values to the I2C bus...
Whoops!
@rwaldron have you tried like this:
for (int i = 0; i < channels; i++) {
noInterrupts();
buffer[i * 2] = pulses[i] >> 8;
buffer[i * 2 + 1] = pulses[i] & 0xFF;
interrupts();
}
I thought I did and I thought I saw it crashing. It's not crashing now.
I'm signing off for a bit.
Presently, this is not crashing:
#define EI_ARDUINO_INTERRUPTED_PIN
#include <EnableInterrupt.h>
#include <Wire.h>
#define DEBUG_MODE 1
// Address Pins
#define AD0 11
#define AD1 12
// I2C Defaults
#define I2C_DEFAULT_ADDRESS 0x0A
#define I2C_BUFFER_SIZE 16
byte buffer[I2C_BUFFER_SIZE];
int addressPins[] = { AD0, AD1 };
int address = I2C_DEFAULT_ADDRESS;
int first = 2;
int channels = 8;
volatile int pulses[8];
volatile int last_up[8];
void up() {
volatile uint8_t pin = arduinoInterruptedPin;
enableInterrupt(pin, &down, FALLING);
last_up[pin - first] = micros();
}
void down() {
volatile uint8_t pin = arduinoInterruptedPin;
enableInterrupt(pin, &up, RISING);
pulses[pin - first] = micros() - last_up[pin - first];
}
void resetState() {
for (int i = first; i < first + channels; i++) {
pulses[i - first] = 0;
last_up[i - first] = 0;
pinMode(i, INPUT_PULLUP);
enableInterrupt(i, &up, RISING);
}
}
void setup() {
// First check if the address was set via pins 11 or 12
int offset = 0;
for (int i = 0; i < 2; i++) {
pinMode(addressPins[i], INPUT);
if (digitalRead(addressPins[i])) {
offset |= 1 << i;
}
}
address += offset;
resetState();
#if DEBUG_MODE
Serial.begin(9600);
#endif
// Initialize Slave
Wire.begin(address);
Wire.onRequest(onRequest);
Wire.onReceive(onReceive);
}
void loop() {
#if DEBUG_MODE
for (int i = first; i < first + channels - 1; i++) {
Serial.print(pulses[i - first]);
Serial.print(" - ");
}
Serial.println(pulses[channels - 1]);
#endif
for (int i = 0; i < channels; i++) {
noInterrupts();
buffer[i * 2] = pulses[i] >> 8;
buffer[i * 2 + 1] = pulses[i] & 0xFF;
interrupts();
}
}
void onRequest() {
Wire.write(buffer, I2C_BUFFER_SIZE);
}
void onReceive(int count) {
while (Wire.available()) {
// Command 0x01 => Reset.
if (Wire.read() == 0x01) {
Serial.println("RESET");
resetState();
}
}
}
It's not crashing here too, which node code are you using in tessel 2 to get values from I2C?
I've been working on a plugin component Receiver
class all day, I will push it out tomorrow. I have to leave my computer for the rest of the day.
ok I'll wait until tomorrow ;) I was trying something like this without success:
const five = require('johnny-five');
const board = new five.Board();
board.on('ready', function(){
const opts = {
address: 0x0A
};
this.io.i2cConfig(opts);
this.io.i2cRead(opts.address, 16, function(data) {
console.log(data);
})
});
Looks like I was away for a few days and missed some fun discussion.
Regarding that Naze cable:
But you won't be able to use such a thing if you're trying to read the pulse values from the receiver—right? You're effectively replacing this thing and adding a "middle man" to intercept the pulses to do something else with them.
I really only meant that I'll probably have male pins on my backpack so I could use such a cable since you can get something similar for most receivers. Then you don't have to worry about having female headers shaped a specific way to fit a particular receiver onto a backpack.
I'd like to add a class to support traditional hobby RC receivers to control nodebots. I happen to have a Spektrum radio and compatible receivers, but other popular Tx/Rx (transmitter/receiver - common abbreviations in RC world) combinations should be supported, too.
Details of my exact setup: DX6 Radio: http://amzn.to/1Se2hjd AR610 Rx: http://amzn.to/1UQfqVw (There are Spektrum compatible Tx an Rx made by Orange that are much cheaper.)
It should support individual channels (like the AR610), but also the PPM (Pulse Position Modulation) varieties.
There are a number of examples of using these with Arduino that could get us started. https://www.sparkfun.com/tutorials/348 http://rcarduino.blogspot.com/2012/01/how-to-read-rc-receiver-with.html
How generic should such a component be?