If you have specific tests to do with Contiki OS and TSCH maybe this post will interest you. Lately, I wanted to have a specific slotframe with TSCH to have a better understanding of TSCH. For this purpose, I needed some timeslots to have congestion-free communications between specific nodes.
For example, with tree nodes 1, 2 and 3. I wanted two slots to have unidirectional communications between node 2 and 1 and node 1 and 2.
This post explains how to hard code the following slotframe. The slotframe have a length of 5, we have 6 channels and we have 3 timeslots. The first one is shared and is used for advertising and broadcast in TSCH. If we use the “minimal schedule” of Contiki TSCH we will have only this one, then other communications like unicast will also be performed in this timeslot.

The slotframe length is defined with the macro TSCH_SCHEDULE_CONF_DEFAULT_LENGTH in Contiki. The schedule is defined in the file core/net/mac/tsch/tsch-schedule.c. To change the schedule with another static version we will surcharge the function void tsch_schedule_create_minimal(void) This function create a 6TiSCH minimal schedule i.e. a slotframe with only a shared slot at timeslot number 0 and channel offset 0.

/* Create a 6TiSCH minimal schedule */
void tsch_schedule_create_minimal(void)
{
  struct tsch_slotframe *sf_min;
  /* First, empty current schedule */
  tsch_schedule_remove_all_slotframes();
  /* Build 6TiSCH minimal schedule.
   * We pick a slotframe length of TSCH_SCHEDULE_DEFAULT_LENGTH */
  sf_min = tsch_schedule_add_slotframe(0, TSCH_SCHEDULE_DEFAULT_LENGTH);
  /* Add a single Tx|Rx|Shared slot using broadcast address (i.e. usable for unicast and broadcast).
   * We set the link type to advertising, which is not compliant with 6TiSCH minimal schedule
   * but is required according to 802.15.4e if also used for EB transmission.
   * Timeslot: 0, channel offset: 0. */
  tsch_schedule_add_link(sf_min,
      LINK_OPTION_RX | LINK_OPTION_TX | LINK_OPTION_SHARED | LINK_OPTION_TIME_KEEPING,
      LINK_TYPE_ADVERTISING, &tsch_broadcast_address,
      0, 0);
}

An important fact: a slotframe is defined locally. If we take the node 3 it will have only the shared timeslot (0,0) and the timeslot 3 -> 2 (2,0). More in detail, the timeslot (0,0) will be used in reception and transmission and the timeslot (2,0) will be a transmission slot with the destination address 2. To personalize the slotframe we will use the MAC node address.
First we will initialize static node address variables in the schedule file. We will later replace each address by the current node address and replace the last byte with the correct Node ID value. Then we will branch based on the last byte of the node address to add specific timeslot.

/* 802.15.4 broadcast MAC address  */
static linkaddr_t node_1_address = { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } };
static linkaddr_t node_2_address = { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } };
static linkaddr_t node_3_address = { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } };


void
tsch_schedule_create_example(void)
{
  struct tsch_slotframe *sf_custom;
  /* First, empty current schedule */
  tsch_schedule_remove_all_slotframes();
  /* Build 6TiSCH minimal schedule.
   * We pick a slotframe length of TSCH_SCHEDULE_DEFAULT_LENGTH */
  sf_custom = tsch_schedule_add_slotframe(0, TSCH_SCHEDULE_DEFAULT_LENGTH);
  /* Add a single Tx|Rx|Shared slot using broadcast address (i.e. usable for unicast and broadcast).
   * We set the link type to advertising, which is not compliant with 6TiSCH minimal schedule
   * but is required according to 802.15.4e if also used for EB transmission.
   * Timeslot: 0, channel offset: 0. */
  tsch_schedule_add_link(sf_custom,
      LINK_OPTION_RX | LINK_OPTION_TX | LINK_OPTION_SHARED | LINK_OPTION_TIME_KEEPING,
      LINK_TYPE_ADVERTISING, &tsch_broadcast_address,
      0, 0);

  linkaddr_copy(&node_1_address, &linkaddr_node_addr);
  node_1_address.u8[7] = 0x1;
  linkaddr_copy(&node_2_address, &linkaddr_node_addr);
  node_2_address.u8[7] = 0x2;
  linkaddr_copy(&node_3_address, &linkaddr_node_addr);
  node_3_address.u8[7] = 0x3;

  if(linkaddr_node_addr.u8[7] == 0x01){
    tsch_schedule_add_link(sf_custom, LINK_OPTION_RX, LINK_TYPE_NORMAL, &node_2_address, 3, 1);
  }
  else if(linkaddr_node_addr.u8[7] == 0x02){
    tsch_schedule_add_link(sf_custom, LINK_OPTION_TX, LINK_TYPE_NORMAL, &node_1_address, 3, 1);
    tsch_schedule_add_link(sf_custom, LINK_OPTION_RX, LINK_TYPE_NORMAL, &node_3_address, 2, 2);
  }
  else if(linkaddr_node_addr.u8[7] == 0x03){
    tsch_schedule_add_link(sf_custom, LINK_OPTION_TX, LINK_TYPE_NORMAL, &node_2_address, 2, 2);
  }
}

Finally, we have to set the node address of each node. It can be done with the argument NODEID when upload the firmware (in case of the Zolertia Zoul platform).