This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CC2652P: How is the UART configured for a znp project

Part Number: CC2652P
Other Parts Discussed in Thread: SYSCONFIG, TIMAC

Tool/software:

So I've been staring myself blind here, and was hoping someone can shed some light/confirm my suspicions of a potential bug.

When I build a ZNP project, lets say we use the following example,  https://github.com/TexasInstruments/simplelink-zstack-examples/blob/main/examples/rtos/CC1352P_2_LAUNCHXL/zstack/znp/README.html#L72 which is sadly not very readable as it's pure HTML, but bare with me. The linked line speaks of configuring the example using sysconfig. This is important for later.

Also mentioned in the same document at line 100, how to communicate with ZigBee Network Processor, e.g. 115200 no flow-control. This is of course as expected, since this is what the defaults of the syscfg settings (and the hardcoded defaults) are.

Another thing of importance, is to notice that in the projectspec, a define is set to tell the build to use the UART for the (ZigBee) Network Processor Interface https://github.com/TexasInstruments/simplelink-zstack-examples/blob/main/examples/rtos/CC1352P_2_LAUNCHXL/zstack/znp/tirtos7/ticlang/znp_CC1352P_2_LAUNCHXL_tirtos7_ticlang.projectspec#L30

The example is of course based off the SDK, and my first assumption now is that, the 'main' entrypoint of our application is somewhere in the zstack/znp SDK directory. Here is also determined how we communicate to the outside world, with the previously mentioned NPI_USE_UART define. To dumb it extremely (and incorrectly, I am aware) down, I expect something like:

```C

void main(void) {

  do_zigbee_core_stuff();

  NPIUART_writetransport(zigbee_data);

}

```

Looking at the NPIUART three things stand out.

First, we setup the default parameters for the UART2, https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/zstack/npi/npi_tl_uart.c#L139 which is a hardcoded table https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/drivers/UART2.c#L50 and just assigned to params https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/drivers/UART2.c#L105 , but not relevant (at least not the baudrate, it gets overwritten below)

Second, NPIUART calls DISPLAY_UART to send out data, https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/zstack/npi/npi_tl_uart.c#L157 which also makes sense, there's no UART setup or configured here.

The third thing that is odd, is that the baudrate parameters are hard-coded to a define https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/zstack/npi/npi_tl_uart.c#L142 which is set here https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/zstack/npi/npi_tl_uart.h#L66. So if it is not defined (as is our case from our projectspec file, it will always be hardcoded to either a FPGA favorable rate, or the expected 115200. I haven't figoured out what `HOST_CONFIG` is about however, but seems to be irrelevant. We are never using the FPGA define, as stuff would break everywhere, nor is `HOST_CONFIG` relevant for non-FPGA platforms. But it overwrites the previously initialized copied values.

Anyway, so far things are starting to trickle down, from main, to NPI where https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/drivers/uart2/UART2CC26X2.c#L358 UART_Open is called with DISPLAY_UART as argument, just to indicate which of the display uarts to use, this is generated from sysconfig, more on this in a bit, and the params, which where hardcoded to defines (and enums) as part of the NPI_UART_init https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/zstack/npi/npi_tl_uart.c#L131. This is problematic of course, because if we have hardcoded initialization parameters ...

Importantly it is to note, that the display config and its accompaning UART is generated from this template https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/display/.meta/Display.Board.c.xdt which gets its input from sysconfig, and generates HWAttributes structures, for the display uart and it's underyling CC26x2 UART2, which *store* the baudrate and flowcontrol (and pin configuration). This then generates the `ti_drivers_config.c` file to be consumed by UART2_Open.

Within UART2_Open, called by NPIUART_Init(), which passes its own list of parameters, we copy the NPI parameters to the UART2CC26X driver 'object' (they are no longer parameters or hardware attributes :p) https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/drivers/uart2/UART2CC26X2.c#L410 So now, object->baudRate is set to the hardcoded define of NPIUART NPI_UART_BR, e.g. 115200. Funnily enough, some booleans are hardcoded here, maybe the NPIUart doesn't even set them, or these values are ignored for our chip ...

Also our IO pins are setup from here https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/drivers/uart2/UART2CC26X2.c#L447 which does correctly take into account our flow-control pins. https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/drivers/uart2/UART2CC26X2.c#L1149 The variable comes from the previously mentioned Hardware Attributes, as do the pin configurations.

The baudRate is setup in the drivers UART_IinitHW() https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/drivers/uart2/UART2CC26X2.c#L1087 which takes the copied hardcoded NPI values.

So where does the `ti_drivers_config.c` UART config go? I think the culprit is here, https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/drivers/uart2/UART2CC26X2.c#L1089 which takes the 'object' baudrate, ignoring the hwAttrs baudRate. During init, this should have been checked, if setup or not. And only use the znp value if nothing is setup. Alternativly, the ZNP stack could define a MAX baudrate instead, but that would be a bigger change. Using a Define (USE_SYSCONFIG) would also work, but more defines is usually not a great idea :)

Comming back to the flowControl story, I have a rather interesting SonOff dongle, which has a switch for flowControl. The switch either connects the RTS/CTS pins to the CPN2102 UART chip, or, and not 100% sure what happens there yet, pulls them up/down to a preconfigured state. It could be because of that, if the stack is built with flow control enabled, and the switch is set to 'off' (pulling the RTS/CTS to their preconfigured states) that the znp stack always gets a positive result on those pins, and sends out data anyway. For 115200 there should be no difference thus whether flow control is enabled or not. On the other side of the link, it would of course matter what the state of the switch is. This could be the reason that hardware flow control _appears_ to do nothing, when in fact, it's working as expected, just being faked/ignored. I haven't probed the signal with a scope, but that's more to do with the tiny pins and pads ;) But could be that while pins and pads are setup for flow control, the chip is simply not 'enabling' flow-contorl, just connecting the pins. I haven't dug that deep there.

So does my story make some sense? Is this a bug, feature, expected behavior?

  • So digging a little bit deeper, looks like I was wrong a little.

    The SDK configured DISPLAY_UART, to be used to ... offer ways to display information to the user. But the NPI_UART, also sets up the UART, to communicate with the host. Technically there's now a conflict here. Both call UART2_Open(), and configure the UART/Driver. So if Someone calls one of the display_uart functions, it would 'blurt' data over the communications uart. Whats worse, If we open the display uart with certain parameters, and then later open the NPI uart with different parameters, one overwrites the other. That's probably not quite what was intended. Which is also the reason why I ran into https://e2e.ti.com/support/wireless-connectivity/zigbee-thread-group/zigbee-and-thread/f/zigbee-thread-forum/1401511/cc2652p-cannot-change-baudrate-flowcontrol-or-rather-the-settings-have-no-effect ...

    Thinking more about it, this seems like a race-condition (if it where multi-threaded) where depending on who opens the UART first gets to set the parameters ...

  • Hi Olliver,

    I'm sorry to hear that this has been so confusing.  Thank you for writing such a detailed description, I'm not sure I will be able to help however I will attempt to put forth some of my thoughts.  First, here is a more legible version of the ZNP README.

    • The SysConfig Display module and Display TI Driver is not used by the ZNP (no Display_open API and UART2_write is called).  The name "CONFIG_DISPLAY_UART" solely refers to the UART2 driver.  I do not know why the Display module/driver is enabled inside the ZNP example since it is unused.  This is why the SysConfig baudrate does not sever any purpose for the ZNP.
    • It is always the expectation of the UART2 TI Driver that the baud rate is established during run-time in the application code, not through the SysConfig compile settings.  Hence NPITLUART_initializeTransport establishes the UART2 parameters such as baud rate, data/stop bits, and callback mode. 
    • SysConfig is responsible for creating the UART2's hardware attributes such as PinMux and "Flow Control" mode, which is used to configure the UART2 peripheral's hardware inside the driver APIs such as UART2_open, UART2CC26X2_initIO, and UART2CC26X2_initHw.  I haven't tested the ZNP with flow control enabled, and as it is not the default setting I do not know whether the project will support this configuration.

    There is a long history of device development (hence unused TIMAC_AGAMA_FPGA defines), code shared between other stacks (HOST_CONFIG references BLE), and software development (SysConfig was integrated into the SDK a few years ago).  This is not an excuse for the existence of unused code, misleading pre-definition names, and confusing references, it only serves to somewhat explain the existing project state.

    Bottom line is that the SysConfig UART module will determine the GPIO PinMux.  The params.baudRate (among other UART2 parameters) will determine the baud rate, and if none is explicitly determined by the application code then the UART2 driver uses the value established in UART2_Params_init.

    Regards,
    Ryan

  • First, here is a more legible version of the ZNP README.

    Thank you, While I'm quite versed at reading HTML, I also had the file locally and just opened it in a browser :p but for all googlers this certainly will be useful in the future.

    t is always the expectation of the UART2 TI Driver that the baud rate is established during run-time in the application code, not through the SysConfig compile settings.  Hence NPITLUART_initializeTransport establishes the UART2 parameters such as baud rate, data/stop bits, and callback mode. 

    The SysConfig Display module and Display TI Driver is not used by the ZNP (no Display_open API and UART2_write is called).  The name "CONFIG_DISPLAY_UART" solely refers to the UART2 driver.  I do not know why the Display module/driver is enabled inside the ZNP example since it is unused.  This is why the SysConfig baudrate does not sever any purpose for the ZNP.

    So I think it's enabled, because the SDK requires 'some' output to configure the UART? What's worse, the znp _does_ use it here, https://github.com/TexasInstruments/simplelink-lowpower-f2-sdk/blob/d216a1abc94460e1ed53c2577da0302e93023c48/source/ti/zstack/npi/npi_tl_uart.c#L157

    which you also mention. It's 'abusing' the sysconfig generated display to instantiate the UART.

    The only way to use the GUI to configure the UART, with the current stack-up is to set the baudrate via the Display. Obviously no code is called, so only some bits and pieces are passed through.

    Not sure if the Display relates in any way to the launchpad board, which while obviously has a uart output, for ZNP this is not relevant. Removing Display from syscfg would make sense, but then a few other stars would have to align (calling the UART the proper name) and then again, how would you configure it in the GUI, without writing all the xdt and js boilerplate. By instead re-using the rest of the Display to get the parameters then is the only 'sensible' way (ontop of the already existing `CONFIG_DISPLAY_UART`.

    It is always the expectation of the UART2 TI Driver that the baud rate is established during run-time in the application code, not through the SysConfig compile settings.  Hence NPITLUART_initializeTransport establishes the UART2 parameters such as baud rate, data/stop bits, and callback mode. 

    True, but what is 'user-code'. In the znp case, the 'user code' is `npi_tl_uart.c`, whereas when using  the Display, the same is done in `DisplayUart2Ansi_open`. Both call `Uart2_Open` indeed after configuring things. Coming from 'just the example' and using that to build an application (ideally without any code chances), you would not expect a user to change thing in code (which is dumbing it down, I realize). Instead, you'd expect the user to be able to configure it via sysconfig, which happens for the Display variant, so why not for NPI? I've wrote a patch that does that btw, I'll link it once I have it verified working, uses the Display config to configure the baudrate. Looking at the sonoff "documentation" that's also probably what these kind of users expect, as this is also exactly how it is explained to enable hardware flow control, without any code changes, all through the GUI.

    SysConfig is responsible for creating the UART2's hardware attributes such as PinMux and "Flow Control" mode, which is used to configure the UART2 peripheral's hardware inside the driver APIs such as UART2_open, UART2CC26X2_initIO, and UART2CC26X2_initHw.  I haven't tested the ZNP with flow control enabled, and as it is not the default setting I do not know whether the project will support this configuration.

    I fully understand that, but as an engineer myself, unless there's a) hardware bugs (unlikely) this should work (after some validation/verification) as it's just how data is exchanged between the MCU UART2 hardware peripherial, and (in this case) USB serial chip (CPN2101). The hardware (after proper configuration) and even more so the software, wouldn't know the difference. What hardware flow control should enable is things like not overwhelming the MCU (or even the host CPU, which is much less likely). At 115200 it probably does not matter at all. Going to 1M this is likely to be an issue, at 3M it is probably a hard requirement.

    There is a long history of device development (hence unused TIMAC_AGAMA_FPGA defines), code shared between other stacks (HOST_CONFIG references BLE), and software development (SysConfig was integrated into the SDK a few years ago).  This is not an excuse for the existence of unused code, misleading pre-definition names, and confusing references, it only serves to somewhat explain the existing project state.

    I am fully aware how these things go :) And it is no excuse :p It's usually poor management that does not prioritize clean code and code maintenance, as that's upfront investment in future returns. There's so many things that could use a big brush to clean things up (from whitespace cleanup, to data types (e.g. why is there uint8, when there is uint8_t) etc etc. The longer these things get delayed, the more expensive they become to maintain, the quicker someone says 'just copy/paste it for this new project' instead of having maintainable code :p but I am happy to see this code up on github; if willing, these things can get slowly cleaned up by contributors as well :)

    Bottom line is that the SysConfig UART module will determine the GPIO PinMux.  The params.baudRate (among other UART2 parameters) will determine the baud rate, and if none is explicitly determined by the application code then the UART2 driver uses the value established in UART2_Params_init.

    Yep, It took me some while to figure these things out, abstraction fun :p but I understand it now :)