Monday, June 27, 2016

LED control using UART on the C2000 LaunchPad

Continuing from where we left off in the previous article, we're going to make use of the SCI (UART) peripheral on the C2000 in order to toggle the on-board LEDs using PuTTY.


First of all, we need to extend our code skeleton with GPIO and SCI handlers for use in the article later:
#include "f2802x_common/include/gpio.h"
#include "f2802x_common/include/sci.h"

GPIO_Handle myGpio;
SCI_Handle mySci;

void setup_handles() {
    ...
    myGpio = GPIO_init((void *) GPIO_BASE_ADDR, sizeof(GPIO_Obj));
    mySci = SCI_init((void *) SCIA_BASE_ADDR, sizeof(SCI_Obj));
}

Now, let's see how can we use SCI on the C2000 LaunchPad - according to the C2000 LaunchPad User's Guide, the (big) S4 switch connects/disconnects the on-board FTDI module to GPIO28 (RX) and GPIO29 (TX). While the System Control and Interrupts Reference Guide has a very detailed explanation about GPIO muxing, I'm just going to use the short, but informative table in the LaunchPad User's Guide.


According to the table, GPIO28 and GPIO29 are indeed muxable to SCI-A (the one and only SCI peripheral on the F28027) - luckily, since we're using the software driver model, we don't actually need to remember the mux value and register. Instead, we can simply do the following (thanks to the F2802x Peripheral Driver Library User's Guide indicating so):
GPIO_setMode(myGpio, GPIO_Number_28, GPIO_28_Mode_SCIRXDA);
GPIO_setMode(myGpio, GPIO_Number_29, GPIO_29_Mode_SCITXDA);

While at it, let's follow the way TI set up the SCI GPIOs. Enable the RX pull-up to avoid a floating input, causing noise, and disable the TX pull-up, since it's an output driven by the SCI peripheral.
GPIO_setPullUp(myGpio, GPIO_Number_28, GPIO_PullUp_Enable);
GPIO_setPullUp(myGpio, GPIO_Number_29, GPIO_PullUp_Disable);

And finally, qualify the RX signal to remove unwanted noise. Again, detailed information about this can be found in the System Control and Interrupts Reference Guide. To put it simply, this is used to filter input glitches (ie. above logic high noises present for a few clock cycles) by creating a user-configurable sampling window and sampling period. Qualified signals need to be stable for several (again, user configurable) clock cycles for the detection to occur. According to the reference guide, SCI input ports should use asynchronous qualification (the input signal doesn't need to be synchronized to the system clock).
GPIO_setQualification(myGpio, GPIO_Number_28, GPIO_Qual_ASync);

Now that the GPIOs are correctly set up, let's configure the SCI-A peripheral.

Firstly, we need to enable the SCI-A clock to, well, make the SCI-A peripheral work at all :)
CLK_enableSciaClock(myClk);

Next on, we configure the serial line... this part should be quite obvious. As a side note, constants like SCI_BaudRate_9_6_kBaud are pre-calculated for the 60 MHz configuration. If you decide to use another frequency, you need to replace this with the BRR value you can calculate as specified in the Serial Communications Interface (SCI) Reference Guide.
// disable parity
SCI_disableParity(mySci);

// 1 stop bit
SCI_setNumStopBits(mySci, SCI_NumStopBits_One);

// 8 data bits
SCI_setCharLength(mySci, SCI_CharLength_8_Bits);

// 9600 baud rate
SCI_setBaudRate(mySci, SCI_BaudRate_9_6_kBaud);

We can also set a priority for the SCI operations - again, this is described pretty well in the reference guide. We use free run priority, which means that the peripheral continues whatever it is doing in case of an emulation suspend (ie. breakpoint).
SCI_setPriority(mySci, SCI_Priority_FreeRun);

Finally, we enable both the TX and RX lines, and the interface itself:
// enable TX and RX
SCI_enableTx(mySci);
SCI_enableRx(mySci);

// enable the SCI interface
SCI_enable(mySci);

At this point, the SCI-A peripheral is ready to receive and transmit data, as soon as we tell it to do so. Initially, I planned on using interrupts in this guide, but later on I decided to cover them in a followup article instead, to keep this one cleaner. Instead, I'm going to use blocking calls.

So let's say that we want to control the on-board LED0, which is connected to GPIO0 on the LaunchPad, using negative logic (the LED lits when the GPIO is low). Let's suppose that we want to turn LED0 off by sending a '0' character over the serial connection, and turn it on by sending a '1' character. Let's acknowledge the command by sending confirmation messages back as well.

Begin by initializing GPIO0 before our main loop:
GPIO_setMode(myGpio, GPIO_Number_0, GPIO_0_Mode_GeneralPurpose);
GPIO_setDirection(myGpio, GPIO_Number_0, GPIO_Direction_Output);
To find out how to set a specific mode, refer to the F2802x Peripheral Driver Library User's Guide.

Now, let's wait for and read the data over the serial interface in out main loop. This is quite easy using the blocking call. If the received data is '0' or '1', toggle the LED:
char data = SCI_getDataBlocking(mySci);

if (data == '0')
    GPIO_setHigh(myGpio, GPIO_Number_0);
else if (data == '1')
    GPIO_setLow(myGpio, GPIO_Number_0);

As a side note, the peripheral driver library reveals that we could as well use the following (more complex) code instead of getDataBlocking:
while (SCI_isRxDataReady(mySci) != true) {
}

char data = SCI_getData(mySci);
Basically, this is what getDataBlocking does for us.

We're almost finished - we just want to echo back a confirmation message now. Again, the peripheral driver library user guide reveals that we can use SCI_putData or SCI_putDataBlocking (same story) to send back a single character. However, we want to send back a whole sentence.
This should be trivial for most people who worked with such low-level IDEs - I ran into this some time ago when I wrote my first AVR program using Atmel Studio; there is no function to send back multiple characters (sentences) at once, we need to write our own.
This is pretty easy; we simply loop through a string and transmit the characters one by one until we reach the termination character:
void scia_msg(char *msg) {
    while (*msg != '\0') {
        SCI_putDataBlocking(mySci, *msg);
        ++msg;
    }
}

We can now finish our application using this function by modifying the blocks that turn the LED on and off:
if (data == '0') {
    GPIO_setHigh(myGpio, GPIO_Number_0);
    scia_msg("LED turned OFF\r\n");
} else if (data == '1') {
    GPIO_setLow(myGpio, GPIO_Number_0);
    scia_msg("LED turned ON\r\n");
}

That's it! You can now fire up PuTTY (make sure the S4 switch is in the UP position) and try it out. It's not hard at all after-all, right? I realized that the examples TI provided (loopback and echoback) are great, but they don't provide any sort of explanation for them; it's rather hard for someone new to the C2000 (like me) to find out what's going on in them, and the lack of online guides doesn't help (pros doesn't need help or guides, I guess).

The greatest side of the software driver model is that if we decide to buy another C2000 MCU, it will be extremely easy to adapt the code for it, thanks to the great API.

Now, some closing words for this article before you can see the completed code - it's easy to notice that in this example, I didn't use the full potential of the SCI peripheral: for example, I didn't use FIFO or interrupts at all. However, I'm planning to talk about them in a followup article sometime later. But for now, I'm going to try to get I2C working, which won't be easy at all, as for some reason, there are no APIs for it (only for SPI)... and again, the lack of any guides doesn't help. Neither the fact that I don't have an oscilloscope :(.

#include "f2802x_headers/include/F2802x_Device.h"

#include "f2802x_common/include/f2802x_examples.h"

#include "f2802x_common/include/clk.h"
#include "f2802x_common/include/cpu.h"
#include "f2802x_common/include/gpio.h"
#include "f2802x_common/include/pie.h"
#include "f2802x_common/include/pll.h"
#include "f2802x_common/include/sci.h"
#include "f2802x_common/include/wdog.h"

CPU_Handle myCpu;
CLK_Handle myClk;
GPIO_Handle myGpio;
PIE_Handle myPie;
PLL_Handle myPll;
SCI_Handle mySci;
WDOG_Handle myWDog;

void setup_handles() {
    myClk = CLK_init((void *) CLK_BASE_ADDR, sizeof(CLK_Obj));
    myCpu = CPU_init((void *) NULL, sizeof(CPU_Obj));
    myGpio = GPIO_init((void *) GPIO_BASE_ADDR, sizeof(GPIO_Obj));
    myPie = PIE_init((void *) PIE_BASE_ADDR, sizeof(PIE_Obj));
    myPll = PLL_init((void *) PLL_BASE_ADDR, sizeof(PLL_Obj));
    mySci = SCI_init((void *) SCIA_BASE_ADDR, sizeof(SCI_Obj));
    myWDog = WDOG_init((void *) WDOG_BASE_ADDR, sizeof(WDOG_Obj));
}

void init_system() {
    // running from flash -  copy RAM based functions to RAM
    memcpy(&RamfuncsRunStart, &RamfuncsLoadStart, (size_t) &RamfuncsLoadSize);

    // disable watchdog
    WDOG_disable(myWDog);

    // load factory calibration
    CLK_enableAdcClock(myClk);
    (*Device_cal )();
    CLK_disableAdcClock(myClk);

    // select the internal oscillator 1 (10 MHz) as the clock source
    CLK_setOscSrc(myClk, CLK_OscSrc_Internal);

    // setup the PLL for 10 MHz * 12 / 2 = 60 MHz
    PLL_setup(myPll, PLL_Multiplier_12, PLL_DivideSelect_ClkIn_by_2);

    // disable PIE and all interrupts
    PIE_disable(myPie);
    PIE_disableAllInts(myPie);
    CPU_disableGlobalInts(myCpu);
    CPU_clearIntFlags(myCpu);
}

void init_gpio_scia() {
    // set SCI pull-ups
    GPIO_setPullUp(myGpio, GPIO_Number_28, GPIO_PullUp_Enable);
    GPIO_setPullUp(myGpio, GPIO_Number_29, GPIO_PullUp_Disable);

    // use asynchronous qualification for the SCI input signal
    GPIO_setQualification(myGpio, GPIO_Number_28, GPIO_Qual_ASync);

    // mux gpio28/29 to SCI-A
    GPIO_setMode(myGpio, GPIO_Number_28, GPIO_28_Mode_SCIRXDA);
    GPIO_setMode(myGpio, GPIO_Number_29, GPIO_29_Mode_SCITXDA);
}

void scia_init() {
    // enable the SCI-A clock
    CLK_enableSciaClock(myClk);

    // disable parity
    SCI_disableParity(mySci);

    // 1 stop bit
    SCI_setNumStopBits(mySci, SCI_NumStopBits_One);

    // 8 data bits
    SCI_setCharLength(mySci, SCI_CharLength_8_Bits);

    // 9600 baud rate
    SCI_setBaudRate(mySci, SCI_BaudRate_9_6_kBaud);

    // enable free run - continue SCI operation regardless of emulation suspend
    SCI_setPriority(mySci, SCI_Priority_FreeRun);

    // enable TX and RX
    SCI_enableTx(mySci);
    SCI_enableRx(mySci);

    // enable the SCI interface
    SCI_enable(mySci);
}

void init_gpio_led0() {
    GPIO_setMode(myGpio, GPIO_Number_0, GPIO_0_Mode_GeneralPurpose);
    GPIO_setDirection(myGpio, GPIO_Number_0, GPIO_Direction_Output);
}

void scia_msg(char *msg) {
    while (*msg != '\0') {
        SCI_putDataBlocking(mySci, *msg);
        ++msg;
    }
}

void main() {
    setup_handles();
    init_system();

    init_gpio_scia();
    scia_init();

    init_gpio_led0();

    while (1) {
        // read RX data
        char data = SCI_getDataBlocking(mySci);

        // toggle LED and print confirmation message
        if (data == '0') {
            GPIO_setHigh(myGpio, GPIO_Number_0);
            scia_msg("LED turned OFF\r\n");
        } else if (data == '1') {
            GPIO_setLow(myGpio, GPIO_Number_0);
            scia_msg("LED turned ON\r\n");
        }
    }
}

2 comments:

  1. I keep getting the message 'could not open source file stdbool.h', what could it be?

    ReplyDelete
  2. Bonjour les gars, si vous avez besoin d'embaucher un vrai hacker pour surveiller / pirater le téléphone de votre partenaire à distance, échanger et doubler votre argent en quelques jours / semaines, ou pirater une base de données, le tout avec confidentialité garantie, contactez easybinarysolutions@gmail.com, ou whatsapp: +1 3478577580, ils sont efficaces et confidentiels.

    ReplyDelete