Menu

#84 Timers dying out in MS/TP

release
open
nobody
None
1
2024-06-21
2024-06-14
No

Hi Folks,

I am porting the MS/TP BACnet library to a Renesas RX uC, and using 'ports/stm32f4xx' code as a reference. Know there's a RX62n port, but that is for BACnet IP, so decided to stick to the STM32 due to the RS485 code.

This code will run in a slave device with a single RS485 port. I have a FreeRTOS running already, with the bacnet main routine being called from a task. However, I haven't seem anything in the network yet. Investigating the issue, I noticed that timers are dying out. In order to isolate the problem, I left only the bare minimum in place, to the point of having the following:

static struct mstimer test_timer;

void bacnet_init(void) {
mstimer_set(&test_timer, 200); // 200ms
}

void bacnet_task(void) {
uint8_t buffer[2];

buffer[0] = 0xbe;
buffer[1] = 0xef;

rs485_bytes_send(&buffer, 2);

if (mstimer_expired(&test_timer)) {
        led_toggle(STATUSLED_B_RED, 0);// gpio to Saleae
        mstimer_reset(&test_timer);
        led_toggle(STATUSLED_B_RED, 1);
}

}

System has a CMT timer running at 1ms, which increments Millisecond_Counter variable. Everything else is standard.

After power on, I can see activity in the RS485 channel (dummy message 0xbeef), and regular pulses in the Saleae. However, after less than a minute, the signals coming from the 200ms timer stop (figure attached). Possibily, somehow, 'mstimer_expired()' stopped working.

Important to highlight that, initially, I am simply trying to broadcast an I-am telegram. Only after I am confident bases are solid, will start adding more functions and services.

The RS485 from the dev board is connected directly to a USB-RS485 converter.

So, any ideas what could be wrong, and where to start looking?

Best Regards,
Luciano.

1 Attachments

Discussion

  • Luciano Rottava da Silva

    Hi,

    For the record, managed to find the issue. My 'mstimer_now()' function was returning a uint16_t, which is obviously not correct. Just change it to uint32_t and now timers seem to be ok.

    However, I still don't see the I-Am telegram during power up. It never gets to the point of loading the data for it, function 'MSTP_Create_Frame' inside mstp.c file.

    What could I be missing?

    Best Regard,
    Luciano.

     
    • Steve Karg

      Steve Karg - 2024-06-18

      The latest stack code (in master branch) includes a bacnet/datalink/dlmstp.c module that should be able to work with most 'ports', and I have been porting the ports/x/dlmstp.c modules to use the common dataling/dlmstp.c module - see ports/stm32f10x or ports/xplained
      or ports/stm32f4xx for an idea of the changes it required, or look at the current master branch in any of those ports folders for the current state of the code.

      I have been debugging under VSCode and using the Cortex-M extensions to connect to the microcontroller dev kits and have been able to use the Live View variables to monitor and debug the MS/TP and RS485 issues that occur during development.

      I've also used FreeRTOS with MS/TP in a couple products and typically use a 1ms task to update the mstimer, and another task to run the MS/TP state machines. I started to create an example FreeRTOS port but haven't finished or tested it - see feature/freertos-mstp

       
  • Luciano Rottava da Silva

    Hi Steve,
    Thank you for your quick reply.
    I am quite new to BACnet, so I am still struggling to make it work. Code is compiling fine, timers are now ok and I am able to send fakes telegram, but no luck with the I-Am telegram. Was expecting to see it in the rs485 bus right after its call, in the end of the 'bacnet_init()'. I mean, do I need to call 'datalink_receive' and 'npdu_handler' inside my task to allow that? Maybe I am misunderstanding the whole thing but, my initial goal was just send a I-Am telegram, and then move forward from there, taking care of the receiving routines. Isn't that possible?

    My code for the initialization and the task are like this:

    void bacnet_init(void) {
    / initialize MSTP datalink layer /
    MSTP_Port.Nmax_info_frames = DLMSTP_MAX_INFO_FRAMES;
    MSTP_Port.Nmax_master = DLMSTP_MAX_MASTER;
    MSTP_Port.InputBuffer = Input_Buffer;
    MSTP_Port.InputBufferSize = sizeof(Input_Buffer);
    MSTP_Port.OutputBuffer = Output_Buffer;
    MSTP_Port.OutputBufferSize = sizeof(Output_Buffer);
    / user data /
    MSTP_Port.ZeroConfigEnabled = false;
    MSTP_Port.SlaveNodeEnabled = true;
    MSTP_Port.UserData = &MSTP_User_Data;
    MSTP_User_Data.RS485_Driver = &RS485_Driver;

    /* initialize datalink layer */
    dlmstp_init((char *)&MSTP_Port);
    
    if (MSTP_Port.ZeroConfigEnabled) {
        dlmstp_set_mac_address(255);
    } else {
        /* FIXME: get the address from hardware DIP or from EEPROM */
        dlmstp_set_mac_address(5);
    }
    
    /* initialize objects */
    Device_Init(NULL);
    
    /* set up our confirmed service unrecognized service handler - required! */
    apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service);
    
    /* we need to handle who-is to support dynamic device binding */
    apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is);
    apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, handler_who_has);
    
    /* Set the handlers for any confirmed services that we support. */
    /* We must implement read property - it's required! */
    apdu_set_confirmed_handler(SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property);
    
    // apdu_set_confirmed_handler(
    //    SERVICE_CONFIRMED_READ_PROP_MULTIPLE, handler_read_property_multiple);
    // apdu_set_confirmed_handler(
    //    SERVICE_CONFIRMED_REINITIALIZE_DEVICE, handler_reinitialize_device);
    apdu_set_confirmed_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, handler_write_property);
    /* handle communication so we can shutup when asked */
    //apdu_set_confirmed_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, handler_device_communication_control);
    
    mstimer_set(&test_timer, 200);  // 200ms
    
    // Broadcast an I-Am on startup.
    Send_I_Am(&Handler_Transmit_Buffer[0]);
    

    }

    void bacnet_task(void) {
    if (mstimer_expired(&test_timer)) {
    rs485_bytes_send(buffer, 2);
    mstimer_reset(&test_timer);
    }
    }

    Besides, data inside 'Handler_Transmit_Buffer[]' after the call 'Send_I_Am()' array looks rubbish; was expecting to see the traditional 0x55, 0xFF, etc.
    Appreciate any information.
    Best Regards,
    Luciano.

     
    • Steve Karg

      Steve Karg - 2024-06-18

      Yes, you need to call datalink_receive() to make the MS/TP datalink process any outgoing messages that are queued in the transmit buffer, but it will only happen after receiving a Token frame from a peer device.

      The datalink_receive() - for MSTP - runs the MSTP receive and MSTP node FSM (finite state machines) and not only receives bytes and packetizes them, but also checks for any pending outgoing messages and only sends when a Token is passed to it. Before a Token can be passed, peer nodes will discover each other (poll-for-master, reply-to-poll-for-master).

      If NPDU/APDU are received during datalink_receive(), then it returns a non-zero length, and you can call npdu_handler() with the data and length.

      Other datalinks - such as IP - typically have another process that runs the IP stack states, use an address resolution protocol (ARP), and have sockets to queue incoming and outgoing packets, and I suppose I could have made the MSTP state machine more like that (and you could), but MS/TP tends to run on smaller devices and additional queues can be expensive in terms of RAM.

      For BACnet, the datalink is a separate layer from the Network and Application layer, so what you see in Handler_Transmit_Buffer[] starts with the NPDU and APDU. The datalink header (and sometimes footer or checksum) will encapsulate the NPDU and APDU during transport. It becomes quite clear in Wireshark captures when you can see BACnet/IP and BACnet MSTP and BACnet Ethernet and BACnet ARCNET and BACnet/IPv6 and BACnet/SC and all have a datalink layer of some kind - sometimes several layers - and within the packet is the BACnet NPDU and APDU.

      Many intelligent people new to BACnet are initially confused by BACnet MSTP, expecting that it is the ONLY layer, and expecting to see a MODBUS-like master/slave experience and simple read/write data, so you are not alone.

       
  • Luciano Rottava da Silva

    Hi Steve,

    Thank you very much for the explanation! BACnet is indeed different from what I've used so far, so it's take a while to wrap my head around these new concepts. This duality, master and slave in the same equipment, is what was bugging me. In the last days was reading more about the protocol, and your paper titled "Understanding BACnet MS/TP Encoding" clarified several questions I had.

    So, after that, fixed some parts of my code and managed to send the first messages; recognized poll-for-master and then one I-Am telegram, after that only poll-for-master (screenshots attached). I figure this is correct now considering there's no other device in the bus. Reception of telegrams is not working yet, still have to fix the uart in this Renesas RX uC.

    In the coming days will add another real BACnet device and see what happens. But the important thing is that I made some progress.

    Best Regards,
    Luciano.

     

Anonymous
Anonymous

Add attachments
Cancel