Thursday, May 21, 2015

BLE application development : Custom Hardware

This is the third post of a series on how to develop an application with Bluetooth Low Energy (BLE). If you haven't already, go back to the first post in the series for information on how to get started. I'm working on a light bulb where the intensity and color temperature can be set via BLE. The application does a ton of stuff, and I'm grafting BLE support into the rest of the application. For projects like this, the Anaren module approach is great.

A Hardware Abstraction Layer is a programming pattern whereby you encapsulate all the functions that talk to the bare hardware into one file. This makes it easier to port the application to different hardware platforms.

To complete this step, you'll need:
a) have completed the previous two steps
b) finished most of your schema with FirstApp, and made stubs for the callbacks
c) the schematic
d) hopefully a reference design close to your application. I'm using Tiva.

Because this is my first project using the Anaren BLE module, I routed all my module connections to the same signals as those used on the Anaren example that uses the TI TM4C123GXL LaunchPad and BoosterPack. This makes porting a little easier.

I recommend getting your schema and callback functions written while in the Em-Builder environment. Changing the schema after you move stuff over is a pain.

I like to start with the Hardware Abstraction Layer because that's the lowest level, and once it is done the rest should be fairly straightforward. Start by creating a new directory called Hal. In it create two blank files, Hal.h and Hal.c. First, copy and paste over the contents of the FirstApp Hal.h file. Save it and compile. Be sure that you don't have errors. My general approach is to go step by step, copying over stuff and compiling as we go. That way we can solve problems early instead of getting to the end and discovering a huge pile of problems to wade through.

Porting Hal.c

I have Hal.c open in Em-Builder, and an empty file in my IDE named Hal.c. I've already copied over Hal.h. Now, to port Hal.c we're going to go step by step: start from the top of the file, copying over a little, modifying it as appropriate, and compiling.

#includes

First, copy over all the includes. The original Hal.c had two files:
#include "lm4f120h5qr_pin_map.h"
#include "inc/lm4f120h5qr.h"
... that can be replaced with the simpler driverlib one:
#include "driverlib/pin_map.h"
Now it should work better.

#defines

There are a couple of #defines that are rather perplexing. To clarify:
EAP_RX_ACK_PIN - this is an input to the module, and an output from the MCU
EAP_TX_ACK_PIN - this is an output from the module, and an input to the MCU
Next you'll see the LED stuff. I ported over the LED_PIN and CONNECTED_LED stuff but I commented out the BLUE_LED stuff. I also left out the BUTTON_PIN and DEBUG1_PIN and DEBUG2_PIN stuff. One thing that isn't self-explanatory: the #define MSEC_CYCLES is the number of cycles in a MSEC, so if your clock frequency is 50MHz, this would be equal to 50000.

Functions

I did not port over the debug functions Hal_debugOn, Hal_debugOff, Hal_debugPulse.

The Hal_Init() function is a big chunk of code, and should be done very carefully. Since I already have a hal_init function for the rest of the application, I renamed this BLE_Hal_init() so that it's clearer. I commented out a lot of the initialization stuff, but be sure to leave in the code that initializes the EAP TX & RX pins:
   SysCtlPeripheralEnable(SYSCTL_PERIPH_UART1);
   /* These are the module's special flow-control functions */

   GPIOPinTypeGPIOOutput(EAP_RX_ACK_PORT, EAP_RX_ACK_PIN);
   GPIOPinTypeGPIOInput(EAP_TX_ACK_PORT, EAP_TX_ACK_PIN);
    IntEnable(INT_GPIOA);
    GPIOIntEnable(EAP_TX_ACK_PORT, EAP_TX_ACK_PIN);

Also include the UART1 initialization, or whatever UART initialization you need. Be sure that it's configured properly.

The last line of their Hal_init function won't allow us to compile. Temporarily comment it out; we'll come back to that later.
//FIXME: update this once the rest is working
//handlerTab[DISPATCH_HANDLER_ID] = Em_Message_dispatch;
The FIXME tag will get automatically added to our list of tasks. This can be seen by adding the Tasks view. To do so navigate to: View : Other : General : Tasks and it should appear next to your problems tab.

With that you should be able to compile the entire Hal.c file.

Event Handling

We're handling event handling a little differently. The Hal.c file has a function:
void Hal_idleLoop(void)
But we're doing it differently, so temporarily we skip over this.

LED Functions

Port the Hal_ledOn, off, read, toggle functions next. If you set up your #defines then you should just be able to copy/paste these functions.

Timer Function

The next function is a little funky:
void Hal_tickStart(uint16_t msecs, void (*handler)(void))
The two parameters are:
  • msecs - the number of milliseconds before the timer expires
  • handler - the function that will be called when the timer expires.
This works in conjunction with Hal_timerIsr(void) which is called by the timer when it expires. The first line in the function:
handlerTab[TICK_HANDLER_ID] = handler;
adds an entry to the event handler table that should be called when the timer expiry event is processed. For now I'm commenting it out so that I can complete the port of the rest of the file.

SRT-HAL Interface

I think the "SRT" refers to the module. I had to do a little bit of googling to find that it means "Schema Runtime" which is as self-explanatory as "SRT". Or not. At any rate, these functions are the ones that handle the serial communication. Here you can see the funky communications protocol that the module uses. When I copied these functions over, the compiler didn't like that the Em_Hal_reset function used Em_Hal_unlock before it was declared, so I moved Em_Hal_unlock above the Em_Hal_reset function. Then Em_Hal_startSend didn't like how Em_Message_startTx was used because I haven't yet included the other Emmoco files.

Interrupt Service Routines

The ISRs are short and to the point which is nice. I did not copy over the Hal_buttonIsr function since I have my own. The rest of these functions form the real meat and potatoes of the module interface. This includes the serial receive ISR as well as the ISR for the handshake from the module, Hal_txAckIsr. When I added these, it became clear that I needed to import the Emmoco files now.

Adding the Emmoco Files

Now that we have a compiling Hal file, lets import the Emmoco files. I simply copied over the Em directory from my FirstApp to my project directory. The first thing that I did was add in the line at the top of my Hal.c file:
#include "Em_Message.h"
and tried to compile. Unfortunately it didn't work; the error
cannot open source file "Em_Message.h"
was displayed. Temporarily I fixed this by changing that line to:
#include "../Em/Em_Message.h"
But this is a hack.

Fixing the Interrupt Service Routines

When I ported the ISRs, there were a number of errors; some from the event handling, and one from an old Stellaris function that was used:
GPIOPinIntClear(EAP_TX_ACK_PORT, EAP_TX_ACK_PIN);
This has been replaced with:
GPIOIntClear(EAP_TX_ACK_PORT, EAP_TX_ACK_PIN);

Should be able to compile now

Following the steps outlined above (and commenting out the few pieces of code) you should be able to compile now.

Copy over the Application Logic

Now, in your main.c file (or whatever file holds your application), add the folowing line:
#include "Em/FirstApp.h"
And compile again. You shouldn't have any errors.

Over in Em-Browser, open your FirstApp-Prog.c file. We will be copying over some variables and functions from here. First, copy over the variables that are used:
static FirstApp_psuTemperatureC_t psuTempC = 10;
static FirstApp_pcbTemperatureC_t pcbTempC = 10;
static FirstApp_level_t intensity = 10;
static FirstApp_colorTemperature_t colorTemperature = 5600;
static FirstApp_command_t powerState = FirstApp_TURN_OFF;

Also, be sure to copy over the connectHandler and disconnectHandler functions; it turns out that the schema requires them.

Fix the Issues from Before

 Previously we commented out some code. Now, go back in and uncomment each part, and be sure it compiles. Add the start() method - the last three lines of your main application should be something like below:
/* The Hal_init() function for BLE stuff */
    BLE_Hal_init();
/* Initialize the BLE module */
    FirstApp_start();

/* The event handler that never exits */
    eventHandler();

Add Interrupt Vectors

The Tiva line of microcontrollers uses a very cool interrupt vector table to tell the processor which function to call for a given interrupt. Open the startup code file that you're using (mine is startup_ccs.c) and add the following interrupt functions, replacing IntDefaultHandler with the name of the function.
    Hal_txAckIsr,                           // GPIO Port A
    Hal_rxIsr,                              // UART1 Rx and Tx
    Hal_timerIsr,                           // Timer 0 subtimer A

Troubleshooting

After I was able to compile the code it didn't work. So, to troubleshooting we go. First, double check your schematic connections, as far as what is an input and what is an output. Next hook up a logic analyzer and observe, and compare to the reference traces:
http://wiki.em-hub.com/display/ED/Sample+Logic+Analyzer+Trace
When I started, I wasn't seeing the RX_ACK pulses after each byte received from the module. Doing a bit of troubleshooting, I found that the uart receive interrupt was not properly configured.