Skip to content

Conversation

@jaguilar
Copy link
Contributor

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:

  • In the uart block implementation, I am relying on the pbio uart interface rather than manipulating the uart directly as in the stm32 block. I felt this simplified the code quite a bit, and seems less error prone -- we've already written the uart manipulation code once, why write it again?
  • I noticed that the run loop for the existing btstack implementation polls data sources, but that seems unnecessary now that we have an easy-to-use RTOS. I was considering only copying the timer-handling portion of the runloop for the classic stack. Are y'all in agreement that that is the correct thing to do?
  • I have read the data sheets for the ti am1808 and the cc2560 as carefully as I know how, but I am not by training or experience an embedded programmer. Do the changes in platform.c look correct? I've tried to leave comments to explain why I'm doing what I'm doing.

I'll be on vacation from tomorrow to Monday, so I probably won't make any further progress until Tuesday next week.

@dlech
Copy link
Member

dlech commented Oct 23, 2025

at work, we are told to make small, self-contained changes

Keeping pull request under around 500 lines changed or so definitely makes them easier to review. 😄

  • I am relying on the pbio uart interface

You can thank Laurens for making it easy to use. So yes, use it, that is what it is there for.

  • I noticed that the run loop for the existing btstack implementation polls data sources,

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.

  • but I am not by training or experience an embedded programmer.

Could have fooled me. 😁

@jaguilar jaguilar force-pushed the ev3-bluetooth-platform-data branch from ba10bc8 to f0565c5 Compare October 25, 2025 17:15
@coveralls
Copy link

coveralls commented Oct 25, 2025

Coverage Status

coverage: 56.673%. remained the same
when pulling b5a83f4 on jaguilar:ev3-bluetooth-platform-data
into 7c5f361 on pybricks:master.

@jaguilar
Copy link
Contributor Author

at work, we are told to make small, self-contained changes

Keeping pull request under around 500 lines changed or so definitely makes them easier to review. 😄

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 noticed that the run loop for the existing btstack implementation polls data sources,

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.

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 do_write_process and do_read_process.)

  • but I am not by training or experience an embedded programmer.

Could have fooled me. 😁

Very kind of you to say.

@jaguilar
Copy link
Contributor Author

jaguilar commented Oct 25, 2025 via email

@dlech
Copy link
Member

dlech commented Oct 25, 2025

But I don’t know which pins are 51,78 and 87.

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.

@dlech
Copy link
Member

dlech commented Oct 25, 2025

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 do_write_process and do_read_process.)

If it works, then it sounds like a nice simplification. 👍

@jaguilar jaguilar marked this pull request as ready for review October 26, 2025 22:36
@jaguilar
Copy link
Contributor Author

jaguilar commented Oct 26, 2025

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.

@laurensvalk
Copy link
Member

Would it make sense to save this one for when we're closer to the end goal here? We can keep it open for now.

I'm not opposed to saving it until we're closer. That being said, the very next PR (the btstack uart block impl for ev3) uses this change.

I don't see a reason to delay the change.

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!

Should I wait until I have something demonstrably working on an end-to-end basis before I send the pull requests for prerequisites?

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.

@jaguilar
Copy link
Contributor Author

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.

@laurensvalk
Copy link
Member

I feel I’m pretty close to having a working demo

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.

@jaguilar
Copy link
Contributor Author

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 😄

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.

@jaguilar
Copy link
Contributor Author

jaguilar commented Oct 29, 2025

Status report:

  • Fixed a bunch of busted things about the commits in this PR which will have to be separated out into clean deltas and added.
  • Got the debug uart stuff working and added a blocking mode to it because I was seeing some crashes and wanted to printf debug them without other stuff happening.
  • Where I'm currently stuck is that the stuff coming out of UART2 doesn't seem to make much sense to the btstack HCI subsystem. See log at the bottom of this comment.

What seems to be the problem is that UART2 is reporting buffers containing zero in response to pbdrv_uart_read. From what I can research online, this probably means the module isn't starting up properly or there's something wrong with the pin configuration or UART configuration. Could also be that the sequence for reading the UART's FIFO isn't being followed correctly w.r.t. flow control, I'll have to double check that as well.

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:

IMG_1565

Logfile:

SF: Detected n25q128a13 with page size 256 Bytes, erase size 4 KiB, total 16 MiB
device 0 offset 0x50000, size 0x100000
SF: 1048576 bytes @ 0x50000 Read: OK
Starting btstack classic ...
Starting btstack classic HCI...
btstack_chipset_cc256x.c.167: cc256x: using default init script
HCI process started.
Reset BT controller
hci.c.5055: hci_power_control: 0, current mode 0
hci.c.7436: BTSTACK_EVENT_STATE 0
HCI in packet type: 04, len: 3
Initialize BT controller
hci.c.5055: hci_power_control: 1, current mode 0
btstack_chipset_cc256x.c.167: cc256x: using default init script
UART CONFIG: baudrate=115200, flowcontrol=1, device_name=NULL, parity=0
Starting UART read/write processes
UART read 1 bytes started
hci.c.7436: BTSTACK_EVENT_STATE 1
HCI in packet type: 04, len: 3
hci.c.1777: hci_initializing_run: substate 0, can send 1
HCI out packet type: 01, len: 3
UART write 4 bytes started
UART write thread
UART write 4 bytes
UART read thread
UART read 1 bytes
UART read OK
UART write done
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 1, can send 0
hci_transport_h4.c.263: hci_transport_h4: invalid packet type 0x00
UART read 1 bytes started
UART read 1 bytes
UART read OK
hci_transport_h4.c.263: hci_transport_h4: invalid packet type 0x00
UART read 1 bytes started
UART read 1 bytes
UART read OK
hci_transport_h4.c.263: hci_transport_h4: invalid packet type 0x00
UART read 1 bytes started
UART read 1 bytes
UART read OK
hci.c.1718: Resend HCI Reset
hci.c.1777: hci_initializing_run: substate 0, can send 1
HCI out packet type: 01, len: 3
UART write 4 bytes started
UART write 4 bytes
hci_transport_h4.c.263: hci_transport_h4: invalid packet type 0x00
UART read 1 bytes started
UART read 1 bytes
UART read OK
UART write done
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 1, can send 0
hci_transport_h4.c.263: hci_transport_h4: invalid packet type 0x00
UART read 1 bytes started
UART read 1 bytes
UART read OK
hci_transport_h4.c.263: hci_transport_h4: invalid packet type 0x00
UART read 1 bytes started
UART read 1 bytes
UART read OK
hci_transport_h4.c.263: hci_transport_h4: invalid packet type 0x00
UART read 1 bytes started
UART read 1 bytes
UART read OK
hci_transport_h4.c.263: hci_transport_h4: invalid packet type 0x00
UART read 1 bytes started
UART read 1 bytes
UART read OK
hci_transport_h4.c.263: hci_transport_h4: invalid packet type 0x00
UART read 1 bytes started
UART read 1 bytes
UART read OK
hci.c.1718: Resend HCI Reset
hci.c.1777: hci_initializing_run: substate 0, can send 1
HCI out packet type: 01, len: 3
UART write 4 bytes started
UART write 4 bytes
UART write done
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 1, can send 0
hci_transport_h4.c.263: hci_transport_h4: invalid packet type 0x00
UART read 1 bytes started
UART read 1 bytes
UART read OK
...

@laurensvalk
Copy link
Member

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.

My janky UART->PC setup:

If it works, it works! But if you want to buy this one, we would gladly refund the expense.

@jaguilar
Copy link
Contributor Author

Like some HCI command to read a vendor string. That might help test the basics before hooking it all up to btstack.

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.

we would gladly refund the expense

Hah, thank you, but there's no need now that I have this thing working.

@dlech
Copy link
Member

dlech commented Oct 29, 2025

Did you enable the Bluetooth chip via gpio? I didn't see that in the code that has been pushed out so far.

@jaguilar
Copy link
Contributor Author

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.

@dlech
Copy link
Member

dlech commented Oct 29, 2025

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.

@jaguilar
Copy link
Contributor Author

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:

hci.c.5055: hci_power_control: 0, current mode 0
hci.c.7436: BTSTACK_EVENT_STATE 0
HCI in packet type: 04, len: 3
Initialize BT controller
hci.c.5055: hci_power_control: 1, current mode 0
btstack_chipset_cc256x.c.167: cc256x: using default init script
UART CONFIG: baudrate=115200, flowcontrol=1, device_name=NULL, parity=0
Starting UART read/write processes
hci.c.7436: BTSTACK_EVENT_STATE 1
HCI in packet type: 04, len: 3
hci.c.1777: hci_initializing_run: substate 0, can send 1
HCI out packet type: 01, len: 3
UART write thread
UART write 4 bytes
UART read thread for uart 0
UART write done
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 1, can send 0
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode 0c03 at substate 1
hci.c.1777: hci_initializing_run: substate 2, can send 1
HCI out packet type: 01, len: 3
UART write 4 bytes
UART write done
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 3, can send 0
HCI in packet type: 04, len: 14
hci.c.2770: Manufacturer: 0x000d
hci.c.2226: Command complete for expected opcode 1001 at substate 3
hci.c.1777: hci_initializing_run: substate 4, can send 1
HCI out packet type: 01, len: 3
UART write 4 bytes
UART write done
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 5, can send 0
HCI in packet type: 04, len: 254
hci.c.2625: local name: 
hci.c.2226: Command complete for expected opcode 0c14 at substate 5
hci.c.1777: hci_initializing_run: substate 6, can send 1
HCI out packet type: 01, len: 7
UART write 8 bytes
UART write done
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 7, can send 0
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode ff36 at substate 7
hci.c.2364: Local baud rate change to 3000000(w4_send_baud_change)
hci_transport_h4.c.178: hci_transport_h4_set_baudrate 3000000
hci.c.1777: hci_initializing_run: substate 8, can send 1
HCI out packet type: 01, len: 5
UART write 6 bytes
UART write done
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 9, can send 1
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode fe37 at substate 9
hci.c.1777: hci_initializing_run: substate 8, can send 1
HCI out packet type: 01, len: 258
UART write 259 bytes
HCI in packet type: 04, len: 3
hci.c.3915: Hardware Error: 0x0b
hci.c.4823: hci_power_control_off
UART write done
hci.c.4828: hci_power_control_off - hci_transport closed
hci.c.4835: hci_power_control_off - control closed

@jaguilar
Copy link
Contributor Author

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.

@jaguilar jaguilar marked this pull request as draft October 30, 2025 02:30
@jaguilar
Copy link
Contributor Author

hci.c.1777: hci_initializing_run: substate 40, can send 0
HCI in packet type: 04, len: 6
HCI in packet type: 04, len: 4
hci.c.2226: Command complete for expected opcode 0c1a at substate 40
hci.c.1777: hci_initializing_run: substate 40, can send 1
hci.c.1770: hci_init_done -> HCI_STATE_WORKING
hci.c.7436: BTSTACK_EVENT_STATE 2
HCI in packet type: 04, len: 3
BT controller initialized. Awaiting shutdown.

Cleared the final bug to get the HCI interface up and running. The baudrate was too high!

@jaguilar jaguilar force-pushed the ev3-bluetooth-platform-data branch 2 times, most recently from fac4394 to 98c13bf Compare October 30, 2025 06:07
@laurensvalk
Copy link
Member

laurensvalk commented Oct 30, 2025

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 run_loop_toppers.c in totally different places, for example. It looks like there are also two versions of the SPP code.

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!

@laurensvalk
Copy link
Member

laurensvalk commented Oct 30, 2025

Possibly worth spinning off to a separate discussion... But there are some chips CC2560 chips out there that can run CC2564 firmware, which is BLE enabled. Now there's a challenge for @dlech 😄 Looks like we have a different PAN version though. But to try it, I guess we could just use the 2564 bts file?

This happened with the Pebble smartwatch:

The Bluetooth chips TI sent to Panasonic were labeled CC2560 but have been flashed with the firmware (and BT LE support) of a CC2564. That's why the module was labeled PAN1316.

Many chip vendors make silicon consistent between product lines but simply flash different firmware to enable features. Our chips were labeled CC2560 because TI asked us if we wouldn't mind using them with CC2564 firmware to speed up our order. Pebble most definitely has Bluetooth LE support, though it has not yet been enabled in our operating system.

@jaguilar jaguilar force-pushed the ev3-bluetooth-platform-data branch from 4d75a3c to bb9f788 Compare November 20, 2025 15:41
@jaguilar
Copy link
Contributor Author

jaguilar commented Nov 20, 2025

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.

@jaguilar jaguilar marked this pull request as ready for review November 20, 2025 15:43
@jaguilar jaguilar force-pushed the ev3-bluetooth-platform-data branch 2 times, most recently from 69d893a to 4104f73 Compare November 20, 2025 22:04
@jaguilar
Copy link
Contributor Author

jaguilar commented Nov 21, 2025

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:

(.venv) PS C:\Users\James\projects\bt_test> python spp_test.py
Opening COM3 and waiting for EV3 to connect...
(Note: The script might block here until the EV3 actually connects)
Connected on COM3!
Received: Ping
Sent: Pong

Logs from the EV3:

[btc] Starting classic BTStack ...
[btc] Reset controller
[btc] Initialize controller
[btc] Powering on the chip to read version info...
[btc] Chip is on, reading version info...
Starting Bluetooth connection test process...
Storage data loaded from firmware version 38 62 31 39 61 39 35 32 00.
Storage data loaded.
Delay for bluetooth bringup...
[btc] Detected LMP Subversion: 0x1b0f
Power is off
Loaded 1 link keys from settings
[btc] Controller initialized. Awaiting shutdown.
Starting connection test...
Waiting until the Bluetooth controller is working...
Starting SDP query...
Found RFCOMM channel: 4
SDP query complete.
Found RFCOMM channel 4 for device.
CAN_SEND_NOW event for socket with no pending data
Connected to RFCOMM channel with MTU 1011
  Server channel: 4
  CID: 1
Sending 'Ping
' to RFCOMM channel.
Received data: 'Pong
'

Here's the C test code to give you an idea what the API looks like:

pbio_error_t test_btc_connect(pbio_os_state_t *state, void *context) {
    static pbio_os_timer_t timer;
    static pbio_os_state_t sub;
    //static bd_addr_t target_addr = { 0xAA, 0x8F, 0x96, 0x70, 0xF3, 0x5C };
    static bd_addr_t target_addr = { 0x5C, 0xF3, 0x70, 0x96, 0x8F, 0xAA };

    static pbdrv_bluetooth_rfcomm_conn_t conn;
    pbio_error_t err;

    PBIO_OS_ASYNC_BEGIN(state);
    pbdrv_uart_debug_printf("Delay for bluetooth bringup...\n");
    PBIO_OS_AWAIT_MS(state, &timer, 5000);
    pbdrv_uart_debug_printf("Starting connection test...\n");
    PBIO_OS_AWAIT(state, &sub, (err = pbdrv_bluetooth_rfcomm_connect(&sub, target_addr, 1, &conn)));
    pbdrv_uart_debug_printf("Connection test completed with error code: %d, conn: %d\n", err, conn.conn_id);
    if (err != PBIO_SUCCESS) {
        pbdrv_uart_debug_printf("Connection test failed.\n");
        return err;
    }

    // Try to send something!
    const char send_msg[] = "Ping\n";
    PBIO_OS_AWAIT(state, &sub, (err = pbdrv_bluetooth_rfcomm_send(&sub, &conn, (uint8_t *)send_msg, strlen(send_msg), 5000)));
    if (err != PBIO_SUCCESS) {
        pbdrv_uart_debug_printf("Failed to send data: %d\n", err);
        return err;
    }

    // Try to read something!
    char recv_msg[64];
    size_t recv_len;
    PBIO_OS_AWAIT(state, &sub, (err = pbdrv_bluetooth_rfcomm_recv(&sub, &conn, (uint8_t *)recv_msg, sizeof(recv_msg) - 1, &recv_len, 5000)));
    if (err != PBIO_SUCCESS) {
        pbdrv_uart_debug_printf("Failed to receive data: %d\n", err);
        return err;
    }

    recv_msg[recv_len] = '\0';
    pbdrv_uart_debug_printf("Received data: '%s'\n", recv_msg);

    PBIO_OS_ASYNC_END(PBIO_SUCCESS);
}

@laurensvalk
Copy link
Member

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.
@laurensvalk
Copy link
Member

laurensvalk commented Nov 24, 2025

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 pbdrv/uart, we should try and keep things together. As an example, see lib/pbio/drv/display/display_ev3.c, which manages its own pins since they aren't needed anywhere else.

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 setup_bluetooth_pins() or driver-specific quirks such as defining the cc256x_init symbols. UART2 can be removed from pbdrv/uart's platform data so we don't need workarounds like:

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 pbdrv_display_ev3_spi_init could work, or otherwise it looks like the btstack_uart_ev3 and gpio abstractions have dedicated init functions. We already call e.g. UARTEnable and UARTConfigSetExpClk there.

What do you think? EDIT: While at it, I'll give a go at drafting an overview 😄

@jaguilar
Copy link
Contributor Author

jaguilar commented Nov 24, 2025

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.

@laurensvalk
Copy link
Member

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)

@jaguilar
Copy link
Contributor Author

jaguilar commented Nov 24, 2025

Okay, I stared at this for a little while. I think if we want to isolate the bluetooth pin configuration away from platform.c, we can do it this way:

  • Non-uart pin configs move into a new instance of btstack_control_t which is ev3-specific. The on function would configure the pic pins, the slow clock, etc. Anything not UART related.
  • UART-related configuration moves into bluetooth_btstack_uart_block_ev3.c.

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.

@dlech
Copy link
Member

dlech commented Nov 24, 2025

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.

@jaguilar jaguilar force-pushed the ev3-bluetooth-platform-data branch from 4104f73 to 1bfa411 Compare November 25, 2025 02:09
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.
@jaguilar jaguilar force-pushed the ev3-bluetooth-platform-data branch 2 times, most recently from c98936f to b5a83f4 Compare November 25, 2025 02:19
@jaguilar
Copy link
Contributor Author

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.

@laurensvalk
Copy link
Member

Thanks for the updates. If I run this with debugging enabled, I was curious for the output of:

    if (lmp_subversion == 0) {
        static pbdrv_bluetooth_local_version_info_t version_info;
        // We must power-on the bluetooth chip before
        DEBUG_PRINT("[btc] Powering on the chip to read version info...\n");
        PBIO_OS_AWAIT(state, &sub, bluetooth_btstack_classic_handle_power_control(&sub, HCI_POWER_ON, HCI_STATE_INITIALIZING));
        DEBUG_PRINT("[btc] Chip is on, reading version info...\n");
        PBIO_OS_AWAIT(state, &sub, pbdrv_bluetooth_read_local_version_information(&sub, &version_info));
        lmp_subversion = version_info.lmp_pal_subversion;
        DEBUG_PRINT("[btc] Detected LMP Subversion: 0x%04x\n", lmp_subversion);
        // Power down the chip -- we'll power it up again later with the correct init script.
        PBIO_OS_AWAIT(state, &sub, bluetooth_btstack_classic_handle_power_control(&sub, HCI_POWER_OFF, HCI_STATE_OFF));
        DEBUG_PRINT("Power is off\n", lmp_subversion);
    }

It seems to get to [btc] Chip is on, reading version info... but doesn't seem to get to the result. It always ends as follows, and runs no further.

I think I have both a CC22560 and CC22560A here.

DRAM:  64 MiB
Core:  10 devices, 10 uclasses, devicetree: separate
MMC:   da830-mmc: 0
Loading Environment from nowhere... OK
In:    serial@10c000
Out:   serial@10c000
Err:   serial@10c000
Autoboot in 0 seconds - press 'l' to stop...
Card did not respond to voltage select! : -110
SF: Detected n25q128a13 with page size 256 Bytes, erase size 4 KiB, total 16 MiB
device 0 offset 0x50000, size 0x100000
SF: 1048576 bytes @ 0x50000 Read: OK
[btc] Starting classic BTStack ...
[btc] Reset controller
hci.c.5055: hci_power_control: 0, current mode 0
hci.c.7436: BTSTACK_EVENT_STATE 0
HCI in packet type: 04, len: 3
[btc] Initialize controller
[btc] Powering on the chip to read version info...
hci.c.5055: hci_power_control: 1, current mode 0
hci.c.7436: BTSTACK_EVENT_STATE 1
HCI in packet type: 04, len: 3
hci.c.1777: hci_initializing_run: substate 0, can send 1
HCI out packet type: 01, len: 3
[btc] Chip is on, reading version info...
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 1, can send 0
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode 0c03 at substate 1
hci.c.1777: hci_initializing_run: substate 2, can send 1
HCI out packet type: 01, len: 3
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 3, can send 0
HCI in packet type: 04, len: 14
hci.c.2770: Manufacturer: 0x000d
hci.c.2226: Command complete for expected opcode 1001 at substate 3
hci.c.1777: hci_initializing_run: substate 4, can send 1
HCI out packet type: 01, len: 3
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 5, can send 0
HCI in packet type: 04, len: 254
hci.c.2625: local name: 
hci.c.2226: Command complete for expected opcode 0c14 at substate 5
hci.c.1777: hci_initializing_run: substate 6, can send 1
HCI out packet type: 01, len: 3
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 15, can send 0
HCI in packet type: 04, len: 70
hci.c.2591: Command SUPPORTED_HCI_COMMAND_READ_REMOTE_EXTENDED_FEATURES (0) supported 2/5
hci.c.2591: Command SUPPORTED_HCI_COMMAND_WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE (1) supported 10/4
hci.c.2591: Command SUPPORTED_HCI_COMMAND_READ_BUFFER_SIZE (2) supported 14/7
hci.c.2591: Command SUPPORTED_HCI_COMMAND_WRITE_DEFAULT_ERRONEOUS_DATA_REPORTING (3) suppoHCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode 0c45 at substate 35
hci.c.1777: hci_initializing_run: substate 36, can send 1
HCI out packet type: 01, lHCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 40, can send 0
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode 0c13 at substate 40
hci.c.1777: hci_initializing_run: substate 40, can send 1
HCI out pHCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 40, can send 0
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode 0c52 at substate 40
hci.c.1777: hci_initializing_run: substate 40, can send 1
HCI out phci.c.5097: HCI_STATE_HALTING, substate 32

hci.c.5248: HCI_STATE_HALTING, calling off
hci.c.4823: hci_power_control_off
hci.c.4828: hci_power_control_off - hci_transport closed
hci.c.4835: hci_power_control_off - control closed
hci.c.5253: HCI_STATE_HALTING, emitting state
hci.c.7436: BTSTACK_EVENT_STATE 0
HCI in packet type: 04, len: 3
hci.c.5255: HCI_STATE_HALTING, done
Power is off
btstack_chipset_cc256x.c.163: cc256x: using custom init script
hci.c.5055: hci_power_control: 1, current mode 0
btstack_chiHCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 1, can send 0
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode 0c03 at substate 1
hci.c.1777: hci_initializing_run: substate 2, can send 1
HCI out packet type: 01, len: 3
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 3, can send 0
HCI in packet type: 04, len: 14
hci.c.2770: Manufacturer: 0x000d
hci.c.2226: Command complete for expected opcode 1001 at substate 3
hci.c.1777: hci_initializing_run: substate 4, can send 1
HCI out packet type: 01, len: 3
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 5, can send 0
HCI in packet type: 04, len: 254
hci.c.2625: local name: 
hci.c.2226: Command complete for expected opcode 0c14 at substate 5
hci.c.1777: hci_initializing_run: substate 6, can send 1
HCI out packet type: 01, len: 7
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 7, can send 0
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode ff36 at substate 7
hci.c.2364: Local baud rate change to 921600(w4_send_baud_change)
hci_transport_h4.c.178: hci_transport_h4_set_baudrate 921600
hci.c.1777: hci_initializing_run: substate 8, can send 1
HCI out packet type: 01, len: 5
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 9HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 9, can send 1
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode ff05 at substate 9
hci.c.1777: hci_initializing_run: substate 8, can send 1
HCI out packHCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 9, can send 1
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode ff05 at substate 9
hci.c.1777: hci_initializing_run: substate 8, can send 1
HCI out packHCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 9, can send 1
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode fHCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 9, can send 1
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode ff05 at substate 9
hci.c.1777: hci_initializing_run: substate 8, can send 1
HCI out packHCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 9, can send 1
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode ff05 at substate 9
hci.c.1777: hci_initializing_run: substate 8, can send 1
HCI out packHCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode ff05 at substate 9
hci.c.1777: hci_initializing_run: substate 8, can send 1
HCI out packet type: 01, len: 258
HCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: sHCI in packet type: 04, len: 2
hci.c.1777: hci_initializing_run: substate 9, can send 1
HCI in packet type: 04, len: 6
hci.c.2226: Command complete for expected opcode ff05 at substate 9
hci.c.1777: hci_initializing_run: substate 8, can send 1
HCI out packHCI in packet type: 04, len: 70
hci.c.2591: Command SUPPORTED_HCI_COMMAND_READ_REMOTE_EXTENDED_FEATURES (0) supported 2/5
hci.c.2591: Command SUPPORTED_HCI_COMMAND_WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE (1) supported 10/4
hci.c.2591: Command SUPPORTED_HCI_C

@laurensvalk
Copy link
Member

I think I have both a CC22560 and CC22560A here.

But if I try the earlier PR version here I seem to read Detected LMP Subversion: 0x1b0f for both. What did you find?

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 bricks
diff --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);

@jaguilar
Copy link
Contributor Author

jaguilar commented Nov 27, 2025

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

hci.c.2591: Command SUPPORTED_HCI_COMMAND_READ_REMOTE_EXTENDED_FEATURES (0) supported 2/5
hci.c.2591: Command SUPPORTED_HCI_COMMAND_WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE (1) supported 10/4

when the bluetooth module is up and running.)

To disable the HCI logs, comment out these lines

    hci_dump_init(&bluetooth_btstack_classic_hci_dump);
    hci_dump_enable_log_level(HCI_DUMP_LOG_LEVEL_INFO, true);
    hci_dump_enable_log_level(HCI_DUMP_LOG_LEVEL_ERROR, true);
    hci_dump_enable_log_level(HCI_DUMP_LOG_LEVEL_DEBUG, true);

and add

    (void)bluetooth_btstack_classic_hci_dump;

@laurensvalk
Copy link
Member

I tested that originally, but got the same result with all prints disabled except the handful of [btc] prints.

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 ev3dev tells them apart, since it says it doesn't use the HW ID pins in the docs. It's worth nothing that the HID bootloader reports the same hardware ID, so maybe it didn't actually read those pins.

@jaguilar
Copy link
Contributor Author

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.)

@laurensvalk
Copy link
Member

But if I try the earlier PR version here I seem to read Detected LMP Subversion: 0x1b0f for both.

Reg’d the question of bricks producing different subversions in one build and the same in another. That is indeed a mystery.

Well, it looks like there are at least three versions. I will document this somewhere later.

HWID         # LMP    ~ year
(1, 1, 1, 0) # 0x191f ~ 2013 (release)
(1, 1, 1, 0) # 0x1b0f ~ 2015 (ev3dev docs)
(0, 0, 0, 1) # 0x1b0f ~ 2017 (estimate)

Not sure what the implications are, if any, but I'd definitely like to try the 2564 patch file on the latter two. 😄

@BertLindeman
Copy link
Contributor

Well, it looks like there are at least three versions.

Laurens, (if it only takes a quick answer, just curious)
I have the EV3 (maybe from 2020) running ev3dev and hciconfig shows:

 LMP Version: 4.0 (0x6)  Subversion: 0x1b5d

Where could I see the HWID?

@jaguilar
Copy link
Contributor Author

jaguilar commented Nov 27, 2025

@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.

image

@BertLindeman
Copy link
Contributor

Thanks Laurens,
Probably due to:

ll /lib/firmware/ti-connectivity/*.bts
lrwxrwxrwx 1 root root    17 Oct 30 18:30 /lib/firmware/ti-connectivity/TIInit_6.2.31.bts -> TIInit_7.2.31.bts*
-rwxr-xr-x 1 root root 15614 Dec  7  2018 /lib/firmware/ti-connectivity/TIInit_6.6.15.bts*
-rwxr-xr-x 1 root root 48345 Oct 30 18:26 /lib/firmware/ti-connectivity/TIInit_7.2.31.bts*

I tried to swap the TIInit patches, to no avail.
No change in hciconfig output.

@laurensvalk
Copy link
Member

Well, it looks like there are at least three versions.

Laurens, (if it only takes a quick answer, just curious) I have the EV3 (maybe from 2020) running ev3dev and hciconfig shows:

 LMP Version: 4.0 (0x6)  Subversion: 0x1b5d

Where could I see the HWID?

We'll document it soon enough :)

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

Successfully merging this pull request may close these issues.

5 participants