Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Could we add feedback to the flight controller through SPI? #6

Open
rmackay9 opened this issue Jul 18, 2012 · 27 comments
Open

Could we add feedback to the flight controller through SPI? #6

rmackay9 opened this issue Jul 18, 2012 · 27 comments

Comments

@rmackay9
Copy link

I was wondering if it would be possible to add feedback to the flight controller perhaps reusing the SPI ports used for programming the AVR (we'd need to leave wires connected to those ports after programming). This would be useful to allow more sophisticated controllers to check the status of the ESC (i.e. temp overload, low voltage, motor RPM, etc).

-Randy (DIYDrones Arducopter dev team)

@sim-
Copy link
Owner

sim- commented Jul 18, 2012

It's possible, sure.. Did you have any protocol in mind? For now, it's not possible to check temperature or voltage without redoing how the commutation timing works, since currently every commutation is based on a zero-cross sense, and many boards require ADMUX and ACME (ADC disabled) in order to do that. It needs to be changed to run from just timers first, which I have been doing, but I'm on vacation at the moment. :)

@rmackay9
Copy link
Author

I was just thinking something very simple like an SPI interface where the ESC responds to a microcontroller which is acting as a "master". The ESC could just have a bunch of "registers" which hold all the relevant info (like rpm, temp, whatever the ESC has). I guess it might also be possible to use the SPI interface as a way for the microcontroller to send the desired motor speed to the ESC by writing to a register. On the microcontroller (at least if it was arducopter) we would wrap it all in a library so that the higher level code wasn't really aware of the underlying protocol anyway.
i guess another alternative would be to support Mavlink but that's a bit wordy for this purpose probably.

@rmackay9
Copy link
Author

rmackay9 commented Sep 8, 2012

Simon,
Could you give me a hint as to how I could get some indication of the motor's rpm? I'm happy for it not to be exactly "RPM" but some indication as to what rate the motor is spinning. Is it held in one of the variables in the code?
By the way, I've made a first attempt at adding feedback to a micro controller using spi. It's in my clone, in the spi_feedback branch. Any feedback or advice would be greatly appreciated! At the moment it just tries to send back the results of a counter (i.e. 8, 9, 10, etc).
https://github.com/rmackay9/tgy/compare/spi_feedback.

 We discussed this in the arducopter dev group, and the feedback doesn't need to be SPI necessarily.  We'd be ok with pwm or uart feedback.  I see there is some support for UART messages in.  I'm personally not a huge fan of UART because many microprocessors have a very limited number of them and coordinating multiple sensors on a single UART line becomes complicated if more than one is talking at a time.
 Unfortunately I've found that the slave-select pin (PB2) is used for a FET on my turnigy ESC so I've ordered a towerPro which apparently has the PB2 pin free.  I will retry my code once that ESC arrives.
my email address is rmackay9 at yahoo.com.

@sim-
Copy link
Owner

sim- commented Sep 10, 2012

Hello! There is actually no variable for RPM, since it has to be calculated as 1/timing, and there is no divide instruction in avr8. Would a timing period be OK instead? Unfortunately, I just kind of ripped that out (still uncommitted) as well, but it is fairly easy to restore or emulate, since the motor timing is always needed. You can use timing_l/h/x for now from the fork you have.

@rmackay9
Copy link
Author

Thanks Simon. I will try using that then in my fork. Returning the motor speed via SPI is still a proof-of-concept so do what you've gotta do but I might ask to have it back in trunk if it all works.
I think it will allow us to land a hexa after a motor failure and even in quads logging a motor failure in the logs will help us get to the bottom of some user crashes more quickly.

@sim-
Copy link
Owner

sim- commented Sep 11, 2012

Yeah, logging and feedback would certainly be nice! I suppose almost all ESCs have free SPI pads or at least pins, too. I'd be happy to merge it back. I just pushed the big change that kills timing_*... You should be able to use last_tcnt1 as a 60 degree period, though.

@rmackay9
Copy link
Author

Hi Simon, I got it working for a hobbyking RedBrick 50a. It's sitting in my clone in the spi_feedback2 branch. I don't expect you to merge it into trunk just yet 'cuz it only works for that one type of ESC and I'm thinking maybe I should use I2C instead to reduce the number of wires from the ESC to the controller.

By the way, if you have any advice on how to turn the timing info into an RPM that'd be great. I'll have a look around in the code as well because I know it does the conversion in some other places.

I've done a blog post about it on diydrones.com as well: http://www.diydrones.com/profiles/blogs/steps-toward-esc-feedback-using-simonk

@sim-
Copy link
Owner

sim- commented Sep 28, 2012

Hello! That looks pretty cool! :)

RPM is a bit tricky because it depends on motor poles. The ESC cannot actually know the RPM without knowing how many poles the motor has, but it can still drive it without knowing this. It's just going to be an inversion (from period to frequency), a division for poles, and a multiply for time-base correction. I think it should be rpm = 16000000 * 60 / timing / poles / 3. F_CPU will be 16MHz on boards with oscillators, or a bit lower on boards without.

@j07rdi
Copy link

j07rdi commented Oct 29, 2012

Hello Simon,

To keep it simple would be possible to use a single pin to output the period (pulse width)? Then mesure the pulse width and do the RPM calculation on the other controller?

Warmly,

@sim-
Copy link
Owner

sim- commented Oct 30, 2012

Hello! You could, but the commutation period's precision requirement is quite exponential, so a straight pulse length may be difficult. If this isn't a problem, there's already code that outputs this on the MOSI and SCK pins if you just turn on MOTOR_DEBUG (see tgy.asm for exact details). Basically, sync_on and flag_on macros show commutation timing, and sync debug output similar to stock Mystery/BlueSeries firmware.

@j07rdi
Copy link

j07rdi commented Nov 6, 2012

Simon,

Thank very much for the explanation, I was able to enable the "MOTOR_DEBUG", I'm using HK BlueSeries 20A. Can you please explain me a little bit more about the "commutation timing" and how should I interpret the data displayed over the MOSI and SCK pins to obtain the RPM?

Warmly,

@j07rdi
Copy link

j07rdi commented Nov 14, 2012

Hello Simon,

Never mind I was able to figure it out thanks! What I can't find out is the UART protocol.

Warmly,

@sim-
Copy link
Owner

sim- commented Nov 19, 2012

You mean the UART input protocol? It's based on the UART support in Quax' tree. Did you read the comments above? The host is expected to broadcast a sequence of motor commands after 0xf5 or higher byte, where MOTOR_ID set in the code is the number of bytes we skip first after receiving the 0xf5 or higher. 0 is stop, 200 is full throttle. So, this is similar to i2c control, but with a serial "broadcast" instead of p2p messaging. There is no talk-back or acknowledgement expected.

I probably will be adding some serial debugging soon when I implement the ADC sampling and want to make sure everything is working properly.

@j07rdi
Copy link

j07rdi commented Nov 19, 2012

Hello Simon,

Thanks for the explanation. Right now I'm testing with one ESC, motor ID 0x01. So I'm pulsing 0xF5,0x00 to 0xF5,0xC8..... Where 2nd byte (the one after 0xF5) is the intended speed for motor 1, but no luck..

Do you see something wrong?

Thanks.

@sim-
Copy link
Owner

sim- commented Nov 19, 2012

Hello! The only oddity with my implementation is probably that it still requires an arming phase, and it works similar to pulses (there is a relatively short timeout and you need to send enough 0 for it to arm). So, maybe try a bunch of zeroes first?

@sim-
Copy link
Owner

sim- commented Nov 19, 2012

Here's a hack I used for testing (on Linux):

#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char *argv[]) {
        struct termios portSettings;
        int option,fd,i;

        fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NONBLOCK);
        if (fd == -1){
                fprintf(stderr,"open() failed: %s\n",strerror(errno));
                exit(-1);
        }
        memset(&portSettings,0,sizeof(portSettings));
        portSettings.c_iflag = IGNBRK | IGNPAR | IGNCR | IXOFF;
        portSettings.c_cflag = B38400 | CS8;
        if (tcsetattr(fd,TCSANOW,&portSettings)){
                perror("tcsetattr()");
                exit(-1);
        }
        fcntl(fd,F_SETFL,O_APPEND); //| O_NONBLOCK);
        for (;;) {
                for (i = 0;i < 2000;i++) {
                        write(fd,"\xf5",1);
                        write(fd,"\x00",1);
                }
                for (i = 0;i < 1000;i++) {
                        write(fd,"\xf5",1);
                        write(fd,"\x0f",1);
                }
        }
}

@j07rdi
Copy link

j07rdi commented Nov 19, 2012

Ohh! I will try that. Thank you very much!

@balrog-kun
Copy link

I had not noticed this discussion and started adding a similar thing myself, https://github.com/balrog-kun/tgy

This uses a serial two-pin protocol for writing the throttle value (8-bit) and reading the number of commutations since last read, which gives me the RPM on the flight computer, which I hope will let me maintain a given exact RPM while the voltage gets lower, which I hope will allow hovering more-or-less still without gps/barometer. It uses the standard ppm input pin as a clock and the MOSI pin as the two-way data pin, so both pins should be available on every ESC out there. First bit sent is 1 for a write and 0 for the read. If first bit was 0, the next bit sent is the 1-bit address: 0 for low byte, 1 for high byte of the commutations counter.

This works with one interrupt per bit sent or received, so the entire throttle write operation steals some 300-400 cycles. I was first thinking, since the ESC runs at 16MHz and the flight controller also runs at 16MHz (or a multiple) lets send/receive one bit every, say, 3 cpu cycles but this was too difficult to implement.

@sim-
Copy link
Owner

sim- commented Jan 3, 2013

Hello! Hmm, interesting work you've done there! My first reaction is: hmm, couldn't you have just used the edge that MOTOR_DEBUG already provides on the MOSI pin and done the actual timing processing on the receiving side? If you don't have any interrupt pins on the host side, I suppose not, but it looks like you would need this anyway to be able to interact with the thing.

My second thought is: could you use SPI instead? Those pins tend to always be free, except on a the MK BL-Ctrl v1 and afro1/2, and on most ESCs, you even have pads for them. You'll need SPI on the host, though. UART rx/tx is also possible, though many layouts use those pins for FET driving -- and the i2c pins.

So, I see how you got there. Definitely useful for testing and measurement...I was hoping to just use a serial link for debugging and testing at some point. The BlueSeries-type boards already leave TX open and RX can unbridged from the PWM input (int0) pin if needed (bridged normally to support 1200bps settings programming cards). I'm interested to see how you are using this, anyway. :)

@balrog-kun
Copy link

Ah, yeah MOTOR_DEBUG would have worked now that I understand what it does.. Only downside is I'd need one pin per motor instead of a single "bus" type of thing. I guess I also want to read the temperature once some else does the hard work of adding ADC support :)

I'd have liked to use I2C or SPI. The problem with SPI that I noticed is the Slave Select pin needed for normal operation (not when reprogramming), not usually broken out and might even be in use already.

@sim-
Copy link
Owner

sim- commented Jan 7, 2013

Ah, I noticed the SS pin issue as well just the other day. It is necessary when not in SPI master mode, and the direction is overridden to input when in slave mode. It's PB2, which is used by tgy.inc. On BlueSeries/Mystery type boards, it seems to often be tied to ground as some sort of hardware indicator pin (not sure why). This appears to force it to select it as the slave if SPI slave mode is enabled, so maybe that would work. Meanwhile, the i2c pins are almost always used for FETs or sense lines.

So, where does the host code live for your work? :)

@balrog-kun
Copy link

So my host code is quite ugly, but anyway: https://gist.github.com/4516478#file-motor-test-serial-c-L24-L264 is for testing on Arduino and it assumes both the ESC and the Arduino run at 16MHz. MOSI & PWM connect to arduino's PD3 & PD2.

The "production" code is slightly more flexible. I run it on an LPC1343 at 12MHz with the "data" (MOSI) line on PIN0_3 and the "clock" (PWM) pins on PIN0_0, PIN0_1 and PIN0_2: https://gist.github.com/4516518#file-actuators-serial-h-L83-L156

@sim-
Copy link
Owner

sim- commented Feb 19, 2013

Hello again! I just checked your latest tree and noticed an issue. You are trying to increment com_count_l/h atomically, but the INT0S interrupt could come anywhere in the read/increment/write instructions. Sadly, you will probably have to cli/sei around this:

  •           lds     temp1, com_count_l
    
  •           lds     temp2, com_count_h
    
  •           adiw    temp1, 1
    
  •           sts     com_count_l, temp1
    
  •           sts     com_count_h, temp2
    

Note also that the in/out of TCNT1L/H and OCR1BL/H is also unsafe if any other interrupt may come along and do anything with a 16-bit timer register, since only one hardware register for buffering the high byte when reading/writing 16-bit timer registers (except ICR1L/H). For example, an interrupt occurring after "out OCR1BH" that reads TCNT1L/H will clobber the register and result in "out OCR1BL" outputting TCNT1H instead of the value expecetd for OCR1BH. I think the only interrupt that does access a 16-bit timer is rcp_int, though, so since you've replaced it, you should be safe. Maybe worth a comment, though. ;)

@balrog-kun
Copy link

Good point about the counter increment, I did the cli/sei thing and moved this operation to some place where it only runs once per the commutation sequence instead of 6 times to reduce overhead. The resolution is 6x lower now.

Not sure I understand about the buffering register. These things happen inside an interrupt handler so no other interrupt can do anything to OCR1B or TCNT1 until "reti", right?

@sim-
Copy link
Owner

sim- commented Mar 5, 2013

"cli" can be later -- before the first "sts" -- if the one instance is the only updater.

Yes, assuming the interrupt itself does not re-enable interrupts, it can be assumed that nothing will interrupt it.until one instruction after "reti". (I had a fatal bug in the past caused by assuming that interrupts would always be serviced until no more interrupts are pending before non-interrupt code was executed, but this is NOT true -- one non-interrupt instruction gets executed between interrupts.)

Anyway, the high byte buffering register is shared between the multiple registers associated with the 16-bit timer, so it can be not obvious if you do things like write to OCR1BL/H in an interrupt while non-interrupt code reads TCNT1L/H, or vice-versa, for example. Your INT0S replaces INT0, and both contain code that can clobber the buffering register. This means that any similar code that might depend on it to make operations atomic outside of interrupts need to be protected by the interrupts. The rest of the code already assumes interrupts may clobber the buffering register. You have some OCR1B initialization after control_start, but before "sei", so it seems safe.

@PyloDEV
Copy link

PyloDEV commented Jan 17, 2015

Simon, is it possible to programm the ESC using SPI after you enable MOTOR_DEBUG as it sends pulses out?

@sim-
Copy link
Owner

sim- commented Oct 7, 2015

Hello! Yes, it is. If you have a USBASP or AVRISP attached or something, it will just ignore these (the pins are tristated until programming is attempted, and they are only driven after RESET is driven low, tristating the MCU).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants