7. Examples from C2000ware

This section goes through some of the examples available in C2000ware driverlib which demonstrates how the the cores within the device communicate with each and share data using Message RAMs. It also covers some examples which uses shared resources and how the ownership is assigned by the CPU1 master core.

7.1. Exercise 2 - IPC example

In this exercise, we will look into the an IPC example which showcases how to send a message from CPU1 to CPU2 with interrupts.

The example is available in <C2000ware>\driverlib\f2838x\examples\c28x_dual\ipc\CCS\ipc_ex1_basic.

  • CPU1 application is sending a command to CPU2 along with a buffer of data. The buffer data is stored in the CPU1_TO_CPU2_MSGRAM.

    #pragma DATA_SECTION(readData, "MSGRAM_CPU1_TO_CPU2")
    uint32_t readData[10];
    
  • The example uses the Device_bootCPU2 function to boot up the CPU2 code. Note that during debug time, CCS will boot up all the cores and having Device_bootCPU2 may not make much difference. This is needed for standalone (without debugger) execution of application.

    #ifdef _FLASH
       Device_bootCPU2(BOOTMODE_BOOT_TO_FLASH_SECTOR0);
    #else
       Device_bootCPU2(BOOTMODE_BOOT_TO_M0RAM);
    #endif
    
  • It uses the IPC_sync function to synchronize both the cores. This function will be returned only if the other core has also reached the IPC_sync function. This example uses Flag 31 for synchronizing.

    On CPU1 side:

    IPC_sync(IPC_CPU1_L_CPU2_R, IPC_FLAG31);
    

    On CPU2 side:

    IPC_sync(IPC_CPU2_L_CPU1_R, IPC_FLAG31);
    
  • CPU1 uses FLAG0 for sending the command. An IPC command consists of a command, address and data. In this example, we are using a user defined command IPC_CMD_READ_MEM, base address of readData buffer as address and length of the readData as the data. CPU1 waits for acknowledgment from CPU2.

    IPC_sendCommand(IPC_CPU1_L_CPU2_R, IPC_FLAG0, IPC_ADDR_CORRECTION_ENABLE,
                      IPC_CMD_READ_MEM, (uint32_t)readData, 10);
    
    IPC_waitForAck(IPC_CPU1_L_CPU2_R, IPC_FLAG0);
    
  • CPU2 enables the interrupt corresponding to Flag1. On receiving the command from CPU1, IPC_ISR0 ISR is invoked.

    IPC_registerInterrupt(IPC_CPU2_L_CPU1_R, IPC_INT0, IPC_ISR0);
    
  • In the IPC_ISR0 ISR function in CPU2, we read the command and the received memory location. CPU2 sends back a response and acknowledge the IPC Flag 0. The sending of response is optional, but acknowledging the flag is mandatory.

    IPC_readCommand(IPC_CPU2_L_CPU1_R, IPC_FLAG0, IPC_ADDR_CORRECTION_ENABLE,
                      &command, &addr, &data);
    
    ...
    
    IPC_sendResponse(IPC_CPU2_L_CPU1_R, TEST_PASS);
    
    IPC_ackFlagRtoL(IPC_CPU2_L_CPU1_R, IPC_FLAG0);
    
  • CPU1 on receiving the acknowledgement reads the response from CPU2.

    IPC_getResponse(IPC_CPU1_L_CPU2_R);
    

Note that there is only one set of IPC command registers and 32 different flags. Sending another command using a different Flag will overwrite the contents of command registers. Hence it is recommended to send a command only after receiving the acknowledgment of the previous command.

Running the example :

  • Build both the CCS projects and load there .outs in CPU1 and CPU2.

  • Run CPU1 followed by CPU2.

  • If the example runs as expected, the variable pass in CPU1 application will be updated with a value of 1.

The same example is available for CPU1-CM IPC, in <C2000ware>\driverlib\f2838x\examples\c28x_cm\ipc\CCS\ipc_ex1_basic. Note that in this case, we are using the CPU1_TO_CM_MSGRAM to store the readData buffer. The addresses used by CPU1 and CM to access the MSGRAM is different and hence it is very important to enable the address correction feature in IPC_sendCommand and IPC_readCommand function. Note that this should be used only if the addr parameter is an address in the IPC MSGRAM.

On CPU1 side:

IPC_sendCommand(IPC_CPU1_L_CM_R, IPC_FLAG0, IPC_ADDR_CORRECTION_ENABLE,
                  IPC_CMD_READ_MEM, (uint32_t)readData, 10);

On CM side:

IPC_readCommand(IPC_CM_L_CPU1_R, IPC_FLAG0, IPC_ADDR_CORRECTION_ENABLE,
                  &command, &addr, &data);

Note: Instead of using interrupts, the core can wait for a flag from the remote core using the function IPC_waitForFlag.

7.2. Exercise 3 - IPC example with message queues

In this exercise, we will look into the an IPC example which showcases how to send a message from CPU1 to CPU2 with interrupts using message queue.

The example is available in <C2000ware>\driverlib\f2838x\examples\c28x_dual\ipc\CCS\ipc_ex2_msgqueue.

As mentioned in the previous exercise, there is only one set of command registers and hence queuing of messages is not possible with IPC command registers. The IPC driver implements a message queuing mechanism to address this issue. This implements circular buffers in the MSG RAM to store the messages instead of the IPC command registers. For every IPC instance, there are 4 buffers implemented with a size of 4 messages per buffer. These 4 buffers are tied to the IPC flags 0 to 3. In this example CPU1 sends a message to CPU2 and expects a message back.

  • CPU1 application is sending a command to CPU2 along with a buffer of data. The buffer data is stored in the CPU1_TO_CPU2_MSGRAM.

    #pragma DATA_SECTION(readData, "MSGRAM_CPU1_TO_CPU2")
    uint32_t readData[10];
    
  • In this example we are using the buffers tied to IPC FLAG1 for both CPU1 to CPU2 and CPU2 to CPU1 communication.

    CPU1 side :

    IPC_initMessageQueue(IPC_CPU1_L_CPU2_R, &msqQ, IPC_INT1, IPC_INT1);
    

    CPU2 side :

    IPC_initMessageQueue(IPC_CPU2_L_CPU1_R, &msqQ, IPC_INT1, IPC_INT1);
    
  • As mentioned in the previous exercise, this example uses Device_bootCPU2 to boot the CPU2 core and uses IPC_sync function to synchronize both the cores. Note that the example uses the sync function after initializing the message queues on both the cores.

  • CPU1 creates a TX message instance which consists of a command, address and 2 data. In this example, we are using a user defined command IPC_CMD_READ_MEM, base address of readData buffer as address and length of the readData as the data1, a message identifier as data2. After sending the message, it waits for a message from CPU2 in the same message queue.

    TxMsg.command = IPC_CMD_READ_MEM;
    TxMsg.address = (uint32_t)readData;
    TxMsg.dataw1  = 10;  // Using dataw1 as data length
    TxMsg.dataw2  = 1;   // Message identifier
    
    IPC_sendMessageToQueue(IPC_CPU1_L_CPU2_R, &msqQ,
                           IPC_ADDR_CORRECTION_ENABLE,
                           &TxMsg, IPC_BLOCKING_CALL);
    
    IPC_readMessageFromQueue(IPC_CPU1_L_CPU2_R, &msqQ,
                             IPC_ADDR_CORRECTION_DISABLE,
                             &RxMsg, IPC_BLOCKING_CALL);
    
  • CPU2 enables the interrupt corresponding to Flag1. On receiving the message from CPU1, IPC_ISR1 ISR is invoked.

    IPC_registerInterrupt(IPC_CPU2_L_CPU1_R, IPC_INT1, IPC_ISR1);
    
  • In the IPC_ISR1 ISR function in CPU2, we read the message and the received memory location. CPU2 sends back a message using the message queue and acknowledge the IPC Flag 0. The sending of response is optional, but acknowledging the flag is mandatory.

    IPC_readMessageFromQueue(IPC_CPU2_L_CPU1_R, &msqQ,
                             IPC_ADDR_CORRECTION_ENABLE,
                             &RxMsg, IPC_NONBLOCKING_CALL);
    
    IPC_sendMessageToQueue(IPC_CPU2_L_CPU1_R, &msqQ,
                           IPC_ADDR_CORRECTION_DISABLE,
                           &TxMsg, IPC_NONBLOCKING_CALL);
    
    IPC_ackFlagRtoL(IPC_CPU2_L_CPU1_R, IPC_FLAG1);
    

Running the example :

  • Build both the CCS projects and load there .outs in CPU1 and CPU2.

  • Run CPU1 followed by CPU2.

  • If the example runs as expected, the variable pass in CPU1 application will be updated with a value of 1.

The same example is available for CPU1-CM IPC, in <C2000ware>\driverlib\f2838x\examples\c28x_cm\ipc\CCS\ipc_ex2_msgqueue. Note that in this case, we are using the CPU1_TO_CM_MSGRAM to store the readData buffer. The addresses used by CPU1 and CM to access the MSGRAM is different and hence it is very important to enable the address correction feature in IPC_sendMessageToQueue and IPC_readMessageFromQueue function. Note that this should be used only if the addr parameter is an address in the IPC MSGRAM.

IPC_sendMessageToQueue(IPC_CPU1_L_CM_R, &msqQ, IPC_ADDR_CORRECTION_ENABLE,
                        &TxMsg, IPC_BLOCKING_CALL);

IPC_readMessageFromQueue(IPC_CM_L_CPU1_R, &msqQ, IPC_ADDR_CORRECTION_ENABLE,
                     &RxMsg, IPC_NONBLOCKING_CALL);

7.3. Exercise 4 - CLA example

In this exercise, we will look into a CLA example which showcases how to share data between C28x and CLA cores.

The example is available in <C2000ware>\driverlib\f2838x\examples\c28x\cla\CCS\cla_ex1_asin.

In this example, C28x core shares a data to CLA and triggers a CLA task to compute its asin value. The CLA returns the computed value back to C28x core.

  • CPU1 application is sending a data to CLA and receiving back a data from CLA. The data to be sent, fVal variable, is stored in CPU_TO_CLA_MSGRAM and the data to be received, fResult variable is stored in CLA_TO_CPU_MSGRAM. Both these variables are defined in .c file.

    #pragma DATA_SECTION(fVal, "CpuToCla1MsgRAM")
    float fVal;
    #pragma DATA_SECTION(fResult, "Cla1ToCpuMsgRAM")
    float fResult;
    
  • In this example, LS5RAM is used as CLA program memory and LS0RAM and LS1RAM is used as CLA data memory. CPU1 configures these memories accordingly.

    MemCfg_setLSRAMMasterSel(MEMCFG_SECT_LS5, MEMCFG_LSRAMMASTER_CPU_CLA1);
    MemCfg_setCLAMemType(MEMCFG_SECT_LS5, MEMCFG_CLA_MEM_PROGRAM);
    
    MemCfg_setLSRAMMasterSel(MEMCFG_SECT_LS0, MEMCFG_LSRAMMASTER_CPU_CLA1);
    MemCfg_setCLAMemType(MEMCFG_SECT_LS0, MEMCFG_CLA_MEM_DATA);
    
    MemCfg_setLSRAMMasterSel(MEMCFG_SECT_LS1, MEMCFG_LSRAMMASTER_CPU_CLA1);
    MemCfg_setCLAMemType(MEMCFG_SECT_LS1, MEMCFG_CLA_MEM_DATA);
    
  • CLA Task 1 is used to compute the asin value of fVal variable and update fResult variable. CPU1 maps the CLA task function and enables tasks and interrupts. Cla1Task1 is CLA task function which is written in .cla file and cla1Isr1 is the ISR function on the C28x core which gets invoked once the CLA task is completed.

    CLA_mapTaskVector(CLA1_BASE,CLA_MVECT_1, (uint16_t)&Cla1Task1);
    
    CLA_enableIACK(CLA1_BASE);
    CLA_enableTasks(CLA1_BASE, CLA_TASKFLAG_ALL);
    
    Interrupt_register(INT_CLA1_1, &cla1Isr1);
    Interrupt_enable(INT_CLA1_1);
    
  • CPU1 updates the fVal variable an triggers CLA task 1 and reads the fResult variable after a delay. Instead of a constant delay, CPU1 can also wait for the CLA run status using the driverlib function CLA_getTaskRunStatus.

    fVal = (float)(BUFFER_SIZE - i)/(float)BUFFER_SIZE;
    CLA_forceTasks(CLA1_BASE,CLA_TASKFLAG_1);
    WAITSTEP;
    y[i] = fResult;
    
  • CLA can use the variables fVal and fResult like normal global variables.

    extern float fVal;
    extern float fResult;
    ...
    
    __interrupt void Cla1Task1 ( void )
    {
       int xTblIdx;
       float result;
       ...
       xTblIdx = fVal * TABLE_SIZE_M_1;
       ...
       fResult = result;
    }
    

Similar example is available for CPU2 in <C2000ware>\driverlib\f2838x\examples\c28x_dual\cla\CCS\cla_ex1_asin.

Running the example :

  • Build the CCS project and load the .out in CPU1.

  • If you need to debug the CLA code or view memories, load symbols of the same .out to CLA core. This step is not mandatory for running the example.

  • If the example runs as expected, the variable pass will be updated with a value of 64. You could also monitor the values of fVal and fResult in each test iteration.

7.4. Exercise 5 - Dual core GPIO example

In this exercise, we will look into the LED blinky example on the CPU1 and CM cores. In this example, 2 GPIOs which are tied to LEDs on the controlcard are used - one is controlled by the CPU1 core and the other is controlled by the CM core.

The example is available in <C2000ware>\driverlib\f2838x\examples\c28x_cm\led\CCS\led_ex1_c28x_cm_blinky.

  • As described in the previous exercises, CPU1 boots up CM.

    #ifdef _FLASH
       Device_bootCM(BOOTMODE_BOOT_TO_FLASH_SECTOR0);
    #else
       Device_bootCM(BOOTMODE_BOOT_TO_S0RAM);
    #endif
    
  • The GPIO configuration registers are only accessible by CPU1. Hence both the pins are configured by CPU1 application.

    Device_initGPIO();
    GPIO_setPadConfig(DEVICE_GPIO_PIN_LED1, GPIO_PIN_TYPE_STD);
    GPIO_setDirectionMode(DEVICE_GPIO_PIN_LED1, GPIO_DIR_MODE_OUT);
    GPIO_setPadConfig(DEVICE_GPIO_PIN_LED2, GPIO_PIN_TYPE_STD);
    GPIO_setDirectionMode(DEVICE_GPIO_PIN_LED2, GPIO_DIR_MODE_OUT);
    
  • By default, all the GPIOs are owned by CPU1. CPU1 assigns the ownership of LED2 pin to CM.

    GPIO_setMasterCore(DEVICE_GPIO_PIN_LED2, GPIO_CORE_CM);
    
  • CPU1 updates the LED1 pin and CM updates the LED2 using the GPIO_writePin or GPIO_togglePin functions.

Running the example :

  • Build both the CCS projects and load the .outs in CPU1 and CM cores.

  • Run CPU1 core followed by CM core.

  • If the example runs as expected, you will see the 2 LEDs on the controlcard blinking.

Similar example is available for CPU1 and CPU2 in <C2000ware>\driverlib\f2838x\examples\c28x_dual\led\CCS\led_ex1_c28x_dual_blinky.

7.5. Exercise 6 - CM example with shared peripheral

In this exercise, we will look into CAN examples on the CM core. As mentioned earlier, CAN is shared across CPU1, CPU2 and CM cores. Even though this is a CM-only example, we need an application running on the CPU1 core (master core) which does the basic initialization of the device.

The example is available in <C2000ware>\driverlib\f2838x\examples\cm\can and the CPU1 application is available in <C2000ware>\driverlib\f2838x\examples\cm\can\CCS\can_config_c28x.

  • CPU1 does the basic initialization of the device such as setting up the clock, GPIO initialization and booting of CM core.

    Device_init();
    Device_bootCM(bootmode);
    Device_initGPIO();
    
  • Configure the GPIOs required for CAN. Since in this example, GPIOs are not controlled by GPIO Data registers, assigning the master core is not necessary.

    GPIO_setPinConfig(GPIO_36_CANA_RX);
    GPIO_setPinConfig(GPIO_37_CANA_TX);
    
  • Assign the ownership of CAN to CM core.

    SysCtl_allocateSharedPeripheral(SYSCTL_PALLOCATE_CAN_A,0x1U);
    

Running the example :

  • Build the can_config_c28x project and load to CPU1.

  • Build the required CAN example and load to CM core.

  • Run CPU1 core followed by CM core.

  • Refer to the selected example in CM for the expected outcome.