-
-
Notifications
You must be signed in to change notification settings - Fork 80
pybricks/bluetooth: Add pybricks.experimental.btc for bluetooth classic functionality. #405
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
base: master
Are you sure you want to change the base?
Conversation
Keeping pull request under around 500 lines changed or so definitely makes them easier to review. 😄
You can thank Laurens for making it easy to use. So yes, use it, that is what it is there for.
Can you give an example of what you mean? We don't really have an RTOS, just cooperative multitasking using coroutines. Usually, the mechanism is that we receive an interrupt letting us know that data is ready. In the interrupt handler, we call a process poll function to let a "process" (just a coroutine) know that something change and that it should run to actually do something with the data that was received.
Could have fooled me. 😁 |
ba10bc8 to
f0565c5
Compare
So, for example, I was originally just posting this as an FYI, with the intention to eventually split it into 3 different requests. So what I'm wondering is whether this pull request already represents the right granularity, or if I should indeed go ahead and split it up. It sounds like you're saying this is already about the right granularity and we can just go forward with this. Anyway, going forward, if I send stuff that's hard for y'all to review please don't hesitate to correct me.
I'm referring to this code. What it does is it adds a hook that the btstack run loop will poll to check if the DMA for the previous write is complete. In this pull request, I instead do what you're describing e.g. in btstack_uart_block_ev3_receive_block, bypassing the data_source_handler mechanism in the btstack runloop. (And the interrupt to hear if the DMA is complete is handled by the pbio uart implementation, in the form of completing the child async op in
Very kind of you to say. |
|
To be clear I know how to set up the pinmux for a given pin if I know which
bank it’s in. But I don’t know which pins are 51,78 and 87. For example,
is 51==3[3] (16*3+3)?
…On Sat, Oct 25, 2025 at 11:43 AM David Lechner ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In lib/pbio/platform/ev3/platform.c
<#405 (comment)>
:
> const pbdrv_gpio_t bluetooth_uart_rx = PBDRV_GPIO_EV3_PIN(4, 19, 16, 1, 3);
const pbdrv_gpio_t bluetooth_uart_tx = PBDRV_GPIO_EV3_PIN(4, 23, 20, 1, 2);
+ const pbdrv_gpio_t bluetooth_uart_cts = PBDRV_GPIO_EV3_PIN(0, 31, 28, 0, 8);
To find the magic numbers to put into the PBDRV_GPIO_EV3_PIN() macro, you
have to find e.g. GP0[5] in the pinmux tables in the technical reference
manual.
image.png (view on web)
<https://github.com/user-attachments/assets/23d63233-e66d-4bcc-9833-015b2e33b891>
This would translate to PBDRV_GPIO_EV3_PIN(1, 11, 8, 0, 5). 1, 11, 8,
comes from PINMUX1_11_8 and 0, 5 comes from GP0[5].
You can also avoid having to look in the TRM by searching for GPIO0_5 in
lib/tiam1808/tiam1808/hw/hw_syscfg0_AM1808.h and you will find
SYSCFG_PINMUX1_PINMUX1_11_8_GPIO0_5 which has the same info.
but according to that device configuration, it's gpio 5 from the
perspective of ev3dev
No, these are two different pins. BTSLOWCLK is not used as a GPIO, but
rather as the ECAP2_PWM2 pulse width modulation output (each pad on the CPU
has a multiplexer to pick one of several pin functions). GP0[5] is BTCLKDIS
which is a different signal. If BTCLKDIS is configured as a GPIO and pulled
low, it will essentially turn off the Bluetooth clock even if the PWM
output is still on.
Technically, we could just not do anything with the BTCLKDIS and CTS_PIC
pins. The gpio-hog in Linux is there to make sure no one can change them
from userspace. But they are set to input and all pins are already high
impedance by default. But we aren't too limited on flash memory on EV3 so
it wouldn't hurt to set them in code anyway just as a matter of
documentation.
—
Reply to this email directly, view it on GitHub
<#405 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAGDGTDDPQAN33UEYWNOOQT3ZPAFFAVCNFSM6AAAAACJ5GFRHOVHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZTGOBQGE4TKMBUHA>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
Oh, right. Yes, just do floor division by 16 to get the bank and the remainder is the pin. You can also verify on the schematic, e.g. PIC_EN is EMA_D11/GP3_3 on the schematic which matches bt-pic-en-hog with gpios = <51 ...> in the devicetree. |
If it works, then it sounds like a nice simplification. 👍 |
|
In terms of verification, all I've done is upload this to my EV3 and ensure it can run "hello, world", which it can. And all the other bricks still build. In my next pull request I will install a classic-only run loop for btstack and ensure that we can do a basic hello-world operation with the bluetooth module. Probably y'all will also want me to fix up the history a bit once the review is done. Let me know if that's desired. |
I just returned from a short trip so probably I haven't followed this closely enough yet. We're very happy with contributions like this, so thanks for working on it!
Making submissions for feedback is fine, and some standalone changes like the UART size argument can be merged if they are tested independently. We may be a bit slower to merge driver code for something that might not get used, so here a demonstration would be great. |
Totally understandable. I feel I’m pretty close to having a working demo, so I will hold off on sending anything else until things are demonstrably working. |
This is awesome. If you'd asked me last year, I would never have imagined that the very first EV3 release might have anything Bluetooth related at all 😄 By the way, if you want some quick hooks from Python into C without learning all the API details just yet, you can modify experimental_hello_world as needed. It is used like this: from pybricks.experimental import hello_world
result = hello_world(foo=5, bar="hi")
print("the square of foo is", result)To add more functions, copy that function below and adjust as needed. Then add it to the table here. |
Hah. Yeah, well, I'm to the point where if the code were all working correctly I would be able to get some basic local address info across the HCI interface all the way up to the Python layer. But, Pybricks is not starting up, so the code isn't all working. So, it's time to do some printf debugging. We'll see where that gets us. Turns out I don't have a USB uart thingy, so I whipped one up with an ESP32 I had lying around. I'll try to hook it up and see what's being printed tonight. |
|
Status report:
What seems to be the problem is that UART2 is reporting buffers containing zero in response to Fortunately, the manual for the CC2560 has a detailed startup sequence and I guess I can use it to figure out if the thing is turned on properly. I'm going to try that tomorrow. Any helpful insights for further debugging would be welcome. My janky UART->PC setup: Logfile: |
|
To start simple, is there some kind of basic command we can send/receive to verify that UART is working at that the Bluetooth chip is up and running? Like some HCI command to read a vendor string. That might help test the basics before hooking it all up to btstack. For additional debugging, we could expose this UART to the user code (just like the UARTs on S1 through S4 can be used) so you can poke at it from Python. Let me know if I should set this up.
If it works, it works! But if you want to buy this one, we would gladly refund the expense. |
Yep, that's ultimately what I'm trying to get set up as my hello world demo for this pull request or the next one. The simplest HCI command is read_local_version_information, I think, and I have that command wired all the way through to Python. However, we don't even get that far. In the log, the problem is more fundamental. We are sending the initial HCI power-on packet, and we can't get back a single valid packet from the bluetooth module. I'm assuming that either I've configured some pin wrong, the UART is configured wrong, or the startup sequence is busted somehow. If you look at the cc2560 manual, section 5.7.1.2, it seems there's a very specific order things need to happen. My next goal is to make sure things are happening in this order and ensure that the RTS wire is being pulled low before continuing with startup.
Hah, thank you, but there's no need now that I have this thing working. |
|
Did you enable the Bluetooth chip via gpio? I didn't see that in the code that has been pushed out so far. |
|
Yes, I have that in my working copy on my local machine. I haven't pushed it out because there's a bunch of other garbage I made for testing, and because if you add it all up it's going to push this pull request well over 500loc. |
That's just a rule of thumb, so don't sweat it if there is good reason that a changeset is bigger than that. |
|
Lots of progress. See log below. I am now to the point where the module appears to be up, but I get a hardware error after sending a 259 byte write to the bluetooth module. I have to investigate further, but I am thinking that this might be caused by using the wrong init script for the module. The one we have is for the cc2564C, which is obviously not going to work on the cc2560. If y'all happen to know where I can find the correct init script for the cc2560 and can send me a pointer, that would be great. In the mean time, I will continue searching. New logfile: |
|
Okay, actually, no worries. I've found the bts files that @dlech originally uploaded to the linux firmware git repo. I have also found the convert_bts_init_scripts.py file in the btstack library. I think I can bump these two things together until something good comes out. |
Cleared the final bug to get the HCI interface up and running. The baudrate was too high! |
fac4394 to
98c13bf
Compare
|
I'm kicking myself for not remembering this sooner, but once upon a time we tried running Pybricks on ev3rt, with this library. We ended up not using it for various reasons but it is still a useful inspiration for some driver configurations. Notably, it uses BTstack. Some pointers include:
A note of caution with this code base: Just because something is there, doesn't mean it is used/included/built. There are two different versions of That said, it did work. So another insightful approach could be to build it and see what the UART is sending and reporting, to verify your own work. Also, please see this article. There are different EV3 brick versions containing different Bluetooth chips. @dlech probably remembers this better, but I thought the EV3 brick hardware version was still the same, so maybe we can determine the variant based on what the chip is saying. EDIT: I hadn't seen your latest replies when I wrote this. Looks like you have found some resources already. Nice work! |
|
Possibly worth spinning off to a separate discussion... But there are some chips This happened with the Pebble smartwatch:
|
4d75a3c to
bb9f788
Compare
|
I'm generally happy to accept edits on my PR. I don't know git very well so I wasn't sure what would happen to my scan-related changes. I went ahead and split the bulk of them into a child branch. Please feel welcome to make whatever remaining edits are necessary to get this into a mergeable shape. I will send the PR for the scan and the experimental module after this is merged. I would also like to get cracking on rfcomm_listen/accept/send/receive if there are no objections. |
69d893a to
4104f73
Compare
|
Good news for whenever we get this done. I have working BTC communication between EV3 and Windows. I'm just so stoked I have to post about it somewhere! From my Windows machine: Logs from the EV3: Here's the C test code to give you an idea what the API looks like: |
|
Woo-hoo! Nice work. Please bear with us as we get some other things pushed/merged first. 😄 |
The driver currently only gets the bluetooth module into a working state. It includes the runloop, some basic HCI interactions, the init scripts, and necessary build changes. All actual functionality will come in future changes.
|
I'm currently looking at this, and there is perhaps some refactoring we could do before we take a deeper dive. Currently, UART2 is configured in at least 3 different places. Since we will be using UART2 exclusively for Bluetooth on EV3 and separate from Setting things up in platform data is only needed if things need to be done very early in boot, or if it is needed for common driver code. This doesn't seem to really apply to if (pdata->base_address == SOC_UART_2_REGS && PBDRV_CONFIG_BLUETOOTH_BTSTACK_EV3_UART) {
// UART2 is set up in the Bluetooth driver, since it is fully managed there.
continue;
}A unified initialization in the driver similar to What do you think? EDIT: While at it, I'll give a go at drafting an overview 😄 |
|
Yeah that sounds reasonable to me. Although I can also see an argument for at least keeping all the pinmux configuration in the same place. But removing uart2 from the pbdrv/uart configs sounds very sensible at a minimum. Since you are drafting something, I'll await further word from you before taking any action. |
|
I think I'll be focusing on getting current EV3 + SPIKE progress available as a beta release in the days to come, so don't let me stop you. The direction I went in first wasn't quite satisfactory so I'd have to restart anyway. (This led me down the rabbithole of pybricks/support#2461) |
|
Okay, I stared at this for a little while. I think if we want to isolate the bluetooth pin configuration away from
A remaining question then becomes whether this should live in the bluetooth directory or in the platform/ev3 directory. IMO it is cleaner to move it all into platform/ev3 since it will not plausibly be useful for other bricks. LMK if this seems sensible to you. I should be able to complete this tonight. |
|
I have a slight preference for keeping all of the Bluetooth code in a single directory rather than putting some of it in the platform directory. It makes it easier for me to find stuff that way. |
4104f73 to
1bfa411
Compare
Moves almost all EV3 bluetooth related configuration out of platform.c. EDMA3 interrupts still need to be routed from platform.c, but that's it. Bluetooth controller still initializes as before.
c98936f to
b5a83f4
Compare
|
As requested: all Bluetooth-specific initialization moved into ev3-specific bluetooth modules. It so happens that these actually do a pretty good job of logically connecting to btstack concepts without much contortion. The btstack_control_t for the ev3 takes up all of the non-UART pins. The UART pins are now configured in the UART block. |
|
Thanks for the updates. If I run this with debugging enabled, I was curious for the output of: It seems to get to I think I have both a |
But if I try the earlier PR version here I seem to read But we can tell these bricks apart from the hardware ID pins, which seem to be inverted for the newer bricks: from pybricks.experimental import hello_world
print(hello_world())
(1, 1, 1, 0) # older bricks
(0, 0, 0, 1) # newer bricksdiff --git a/pybricks/experimental/pb_module_experimental.c b/pybricks/experimental/pb_module_experimental.c
index 6f252a925..44f02a9ee 100644
--- a/pybricks/experimental/pb_module_experimental.c
+++ b/pybricks/experimental/pb_module_experimental.c
@@ -20,6 +20,18 @@
#include <pybricks/robotics.h>
+#include <pbdrv/gpio.h>
+#include <tiam1808/hw/hw_syscfg0_AM1808.h>
+#include <tiam1808/hw/hw_syscfg1_AM1808.h>
+#include "../lib/pbio/drv/gpio/gpio_ev3.h"
+
+static const pbdrv_gpio_t hwid[] = {
+ PBDRV_GPIO_EV3_PIN(8, 11, 8, 3, 5),
+ PBDRV_GPIO_EV3_PIN(8, 15, 12, 3, 4),
+ PBDRV_GPIO_EV3_PIN(8, 23, 20, 3, 2),
+ PBDRV_GPIO_EV3_PIN(9, 23, 20, 4, 10),
+};
+
// pybricks.experimental.hello_world
static mp_obj_t experimental_hello_world(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args,
@@ -32,7 +44,6 @@ static mp_obj_t experimental_hello_world(size_t n_args, const mp_obj_t *pos_args
// PB_ARG_DEFAULT_FALSE(name), Keyword argument with default False value.
// PB_ARG_DEFAULT_TRUE(name), Keyword argument with default True value.
// PB_ARG_DEFAULT_NONE(name), Keyword argument with default None value.
- PB_ARG_REQUIRED(foo),
PB_ARG_DEFAULT_NONE(bar));
// This function can be used in the following ways:
@@ -42,18 +53,15 @@ static mp_obj_t experimental_hello_world(size_t n_args, const mp_obj_t *pos_args
// y = hello_world(5, 6)
// y = hello_world(foo=5, bar=6)
- // All input arguments are available as MicroPython objects with _in post-fixed to the name.
- // Use MicroPython functions or Pybricks helper functions to unpack them into C types:
- mp_int_t foo = pb_obj_get_int(foo_in);
+ (void)bar_in;
- // You can check if an argument is none.
- if (bar_in == mp_const_none) {
- // Example of how to print.
- mp_printf(&mp_plat_print, "Bar was not given. Foo is: %d\n", foo);
+ mp_obj_t ret[MP_ARRAY_SIZE(hwid)];
+ for (uint8_t i = 0; i < MP_ARRAY_SIZE(hwid); i++) {
+ ret[i] = mp_obj_new_int(pbdrv_gpio_input(&hwid[i]));
}
// Example of returning an object. Here we return the square of the input argument.
- return mp_obj_new_int(foo * foo);
+ return mp_obj_new_tuple(MP_ARRAY_SIZE(hwid), ret);
}
// See also experimental_globals_table below. This function object is added there to make it importable.
static MP_DEFINE_CONST_FUN_OBJ_KW(experimental_hello_world_obj, 0, experimental_hello_world); |
|
I'm out of town for Thanksgiving so I don't have a device in front of me to test right now. But I believe what you are seeing is just an artifact of the verbose logs flooding the UART buffer. I think if you disable the HCI log dump, you'll find that it's working. At least, that is what happened with my brick when I was home when I pushed this revision. (Part of the way I can tell is that you only see when the bluetooth module is up and running.) To disable the HCI logs, comment out these lines and add |
|
I tested that originally, but got the same result with all prints disabled except the handful of In any case, it's interesting that the different bricks produced the same LMP subversion (using the older build). I guess I'll have to check how |
|
Okay. I won’t have hardware to test on until Monday but then I can have a look. I’m pretty sure this was working on my hardware before. Reg’d the question of bricks producing different subversions in one build and the same in another. That is indeed a mystery. I will have to try with my other brick and see if it has a different module version. In the case of the subversion, as you can see it comes back in the local version information packet. That comes directly from the Bluetooth module so it is surprising that it could have different values in different builds on the same hardware, since that bit of the code hasn’t really changed much. (FWIW I think having learned more about how the usb code handles dynamic chipset selection, that whole portion of the code can be somewhat adjusted out of existence. We still need to handle the local version information and load the init script dynamically, but the regular startup process does send a local version information event and we can dynamically select the init script at that time without the “turn it off and turn it on again” code currently present in this PR.) |
Well, it looks like there are at least three versions. I will document this somewhere later. Not sure what the implications are, if any, but I'd definitely like to try the 2564 patch file on the latter two. 😄 |
Laurens, (if it only takes a quick answer, just curious) Where could I see the HWID? |
|
@BertLindeman as far as I can tell that is not a rom lmp subversion. At least, my brief searching does not find any matching cc2560x revision. That might be the post-patch lmp subversion of the chip? I don’t know how to read the pre-patch version on ev3dev, but it is probably in some kernel log or another. Probably near any log messages about loading the firmware patch file.
|
|
Thanks Laurens, I tried to swap the TIInit patches, to no avail. |
We'll document it soon enough :) |


This pull request is just a heads up, so that y'all can tell me if I'm barking up the wrong tree. It's my intention to break these commits into separate requests as each parent commit gets approved. (I don't know if this is the style you all prefer to review code -- at work, we are told to make small, self-contained changes, and I intend to proceed in that mode unless told otherwise.)
I have a few questions about what I'm doing here:
I'll be on vacation from tomorrow to Monday, so I probably won't make any further progress until Tuesday next week.