vedderb / bldc

The VESC motor control firmware
2.1k stars 1.32k forks source link

Buzzer/beeper controlled via the servo/pwm pin #555

Closed surfdado closed 1 year ago

surfdado commented 1 year ago

Comes with a new app config (general) field: buzzer_enable. We will need access from app packages to the buzzer APIs too, but I left that for a separate commit.

Signed-off-by: Dado Mista dadomista@gmail.com

vedderb commented 1 year ago

This looks like code to toggle an io-pin at relatively low rate. Couldn't that be done from the package directly?

surfdado commented 1 year ago

I would think controlling a buzzer via the servo pin would be useful regardless of package.

But if you're saying that I can configure and control the servo pin directly from the package already then I don't need to push for this commit to be included. Doing it directly from the package will work.

vedderb commented 1 year ago

You can do it from the package. A buzzer can be useful in general, but it is so trivial to enable and disable an io-pin at some rate that I don't think it is worth making a library for that. If we add a library for all possible useful but simple tasks we will end up with so many libraries that it will be more work to look through all libraries and understanding how they work than just writing the code directly.

vedderb commented 1 year ago

The native library actually didn't have the ppm-pin, I added it just now.

vedderb commented 1 year ago

Here is a buzzer-example in lisp that uses the ppm-pin and does the beeping in a new thread to not block the calling thread:

; Choose buzzer pin and configure it as output
(def pin-buzzer 'pin-ppm)
(gpio-configure pin-buzzer 'pin-mode-out)

; Spawn thread that beeps so that the current thread is not blocked
(defun buzzer-beep (num-beeps beep-time)
    (spawn 50 (fn ()
            (looprange i 0 num-beeps
                (progn
                    (gpio-write pin-buzzer 1)
                    (sleep (/ beep-time 2))
                    (gpio-write pin-buzzer 0)
                    (sleep (/ beep-time 2))
)))))

; Example
; Beep 5 times with 0.5 seconds from beep to beep
(buzzer-beep 5 0.5)
surfdado commented 1 year ago

Awesome response, as always - now one last question, how would I trigger the buzzer from the C part of my code? Can I call "buzzer-beep" from C somehow? sorry for the Lisp noob question

Mitchlol commented 1 year ago

My planned implementation was to have the lisp code monitor the real time data, and it would do all the buzzer and led stuff. This way the c code does balancing, and lisp does accessories. Also lisp is easier for users to modify, so you don't have to go crazy with a ui for buzzer options.

surfdado commented 1 year ago

I don't have a problem with having general beeper/monitoring/warning logic in the UI side and Lisp, but I must have a way to trigger the beeper from the balance control code, this is critical for me for new feature development - placebo is an extremely strong force when evaluating onewheel behavior, I must have a way of identifying whether a certain feature I'm working on is being triggered or not, so far the beeper has been the only way I'd know for sure that what I'm feeling isn't just my imagination but truly the new block of code I just added... Tbh I'd be surprised if there wasn't a way to call Lisp functions from C, right?

Mitchlol commented 1 year ago

Well I'm not sure you can call a lisp function from C, but you can set a flag in C which lisp can monitor which should achieve your goals. Look at the lisp script included with the balance package and the ext-balance-dbg function. You could output a debug variable as a flag for beeper on/off.

surfdado commented 1 year ago

okay, that should work - at what frequency does the Lisp script run?

surfdado commented 1 year ago

assuming I use flags in C-code that Lisp acts on, how do I make it bidirectional, essentially how do I implement a protocol? If I set a flag in C, can the Lisp code set a separate flag to let the C-code know that it's been handled so the C-code can clear the flag?

Mitchlol commented 1 year ago

LBM script frequency is roughly determined by the sleep time, same as anything else. 100hz will work fine, if the script is not super complex. You can push it a little harder, but you wont get the same level of performance as native c code.

using VESC_IF->lbm_add_extension("ext-balance-dbg", ext_bal_dbg) this method, you should be able to add multiple extensions.

vedderb commented 1 year ago

It is also possible to implement the buzzer in C and have a lisp-extension to call it. To control the pin from C you can use:

// Configure the pin as output
VESC_IF->io_set_mode(VESC_PIN_PPM, VESC_PIN_MODE_OUTPUT);

// Set pin to 1
VESC_IF->io_write(VESC_PIN_PPM, 1);

// Set pin to 0
VESC_IF->io_write(VESC_PIN_PPM, 0);

You also have to do the buzzer logic and you can do it with a thread from the C side too, but you have to take a bit more care.

As @Mitchlol wrote you can also have lisp poll something in C to determine if the buzzer should be on. Polling at 100Hz is no problem, but I think 20Hz is more than enough too.

Additionally you can send messages from C to lisp and have a thread in lisp that waits for the event. This is a bit more tricky, but certainly possible.

vedderb commented 1 year ago

About the lisp frequency, the balancing robot code that runs 3 nested PID-controllers and some pitch feed forward runs at about 450 Hz. Simpler code can run at 1 kHz easily. Since the first versions the frequency has doubled, so Joel has done a nice job with optimizations the past months.

vedderb commented 1 year ago

I think you can do something like this in C

typedef struct {
    int beeps;
    float time;
} buzzer_data;

static void thd_buzzer(void *arg) {
    buzzer_data *d = (buzzer_data*)arg;

    VESC_PIN pin = VESC_PIN_PPM;

    VESC_IF->io_set_mode(pin, VESC_PIN_MODE_OUTPUT);

    for (int i = 0;i < d->beeps;i++) {
        VESC_IF->io_write(pin, 1);
        VESC_IF->sleep_ms((uint32_t)(d->time * 500.0));
        VESC_IF->io_write(pin, 0); 
        VESC_IF->sleep_ms((uint32_t)(d->time * 500.0));
    }

    VESC_IF->free(d);
}

static void buzzer_beep(int beeps, float time) {
    buzzer_data *d = VESC_IF->malloc(sizeof(buzzer_data));
    d->beeps = beeps;
    d->time = time;
    VESC_IF->spawn(thd_buzzer, 100, "Buzzer", d);
}

Another approach is to make something similar to this pull request and make a thread in C that keeps polling the state machine.