UART ISR and custom printf() function with CC2540

I recently got started learning Texas Instruments CC2540 Bluetooth Low Energy chip to write a HID keyboard and mouse bridge to pair up with a MSP430 microcontroller and make my ThinkPad keyboard wireless.

Besides getting started with the CC2540 in general, I found using UART a major hurdle even though it turned out to be quite easy using the HAL driver supplied. When I wrote ‘easy’ I meant to pair it with ‘by using ISR (interrupts)’ instead of ‘DMA’. I have not had a stab at DMA yet and given that my 30-day license is expiring in a few days, I would love to finish writing the firmware as soon as possible.

So may this be a quick trot down of how I went with UART and tiny modified printf() function over UART based on oPossum’s tiny printf for the MSP430 platform.

I found it beneficial to read over Design Note DN112 as it gives a basic understanding of how UART works in the CC2530, which is quite similar to CC2540.

In general, to use UART, it can be as followed:

/*
Setting up UART
- To use UART, HAL_UART=TRUE, preferrably POWER_SAVING is not enabled (xPOWER_SAVING)
- To use interrupts, HAL_UART_ISR = (1 or 2), HAL_UART_DMA=FALSE
- To use DMA, HAL_UART_ISR = 0, HAL_UART_DMA = (1 or 2)

+ HAL_UART_ISR = 1: Use USART 0
+ HAL_UART_ISR = 2: Use USART 1

Check _hal_uart_isr.c for more information.

For keyfob, USART 0 alt. 1 is being used:
- HAL_UART_ISR = 1
For HM-10, USART 1 alt. 2 is being used:
- HAL_UART_ISR = 2

*/

For my project, I am using the CC2540 keyfob and am using USART 0 Alt. 1:

//UART test variable
uint8 *rxBuffer;
uint8 rxBufferIndex = 0;

static void setupUART(void) {
 HalUARTInit();

 halUARTCfg_t uartConfig;

 // configure UART
 uartConfig.configured = TRUE;
 uartConfig.baudRate = HAL_UART_BR_57600;
 uartConfig.flowControl = HAL_UART_FLOW_OFF;
 uartConfig.flowControlThreshold = 0;
 uartConfig.rx.maxBufSize = 128;
 uartConfig.tx.maxBufSize = 128;
 uartConfig.idleTimeout = 1; //1ms timeout
 uartConfig.intEnable = TRUE;
 uartConfig.callBackFunc = (halUARTCBack_t)uartCallback;

 //start UART
 //assumes no issues with starting UART
 (void)HalUARTOpen(HAL_UART_PORT_0, &uartConfig);

 rxBuffer = osal_mem_alloc(128); //assumes there is no problem with getting this block of memory
}

static void uartCallback(uint8 port, uint8 event) {
 uint16 len;
 uint8 buf[8];
 uint8 i;

 switch(event) {
 case HAL_UART_RX_FULL:
 case HAL_UART_RX_ABOUT_FULL:
 case HAL_UART_RX_TIMEOUT:
 len = Hal_UART_RxBufLen(HAL_UART_PORT_0);
 HalUARTRead(HAL_UART_PORT_0, buf, len);
 for(i = 0; i < len; i++) {
 //in this application, all lines sent to CC2540 ends with a carriage return (0x0D)
 if(buf[i] != 0x0D) rxBuffer[rxBufferIndex++] = buf[i];
 else {
 processBuffer(); //do stuff with rxBuffer using rxBufferIndex as the length of the rxBuffer
 break;
 }
 }

 break;
 }
}

setupUART() is placed in the application’s Init() function after all the other initializations. For example:

#if defined( CC2540_MINIDK )
   //some initializations specific to the keyfob
#endif

//UART init
setupUART();

//setup a delayed profile startup...

The callback function is called anytime there is an event related to UART is fired. Through printing text onto UART with HalUARTWrite(), I found that only HAL_UART_RX_TIMEOUT matters for receiving data. I’m not sure if this is the correct approach but so far it seems to work fine.

Next up, based on oPossum’s tiny printf(),  one can be implemeted for CC2540 to send data easily over UART: Printf.c:

#include "stdarg.h"
#include "hal_uart.h"
#include <string.h>
#include "hal_types.h"

static void sendByte(unsigned char byte) {
 HalUARTWrite(HAL_UART_PORT_0, &byte, 1); //change port to suit your needs
}

static void putc(unsigned char c) {
 sendByte(c);
}

static void puts(uint8 *str) {
 HalUARTWrite(HAL_UART_PORT_0, str, strlen((const char*)str)); //change port to suit your needs
}

static const unsigned long dv[] = {
 // 4294967296 // 32 bit unsigned max
 1000000000,// +0
 100000000, // +1
 10000000, // +2
 1000000, // +3
 100000, // +4
 // 65535 // 16 bit unsigned max
 10000, // +5
 1000, // +6
 100, // +7
 10, // +8
 1, // +9
};

static void xtoa(unsigned long x, const unsigned long *dp) {
 char c;
 unsigned long d;
 if (x) {
 while (x < *dp)
 ++dp;
 do {
 d = *dp++;
 c = '0';
 while (x >= d)
 ++c, x -= d;
 putc(c);
 } while (!(d & 1));
 } else
 putc('0');
}

static void puth(unsigned n) {
 static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
 '9', 'A', 'B', 'C', 'D', 'E', 'F' };
 putc(hex[n & 15]);
}

void printf(char *format, ...)
{
 char c;
 int i;
 long n;

 va_list a;
 va_start(a, format);
 while(c = *format++) {
 if(c == '%') {
 switch(c = *format++) {
 case 's': // String
 puts(va_arg(a, char*));
 break;
 case 'c':// Char
 putc(va_arg(a, char));
 break;
 case 'i':// 16 bit Integer
 case 'u':// 16 bit Unsigned
 i = va_arg(a, int);
 if(c == 'i' && i < 0) i = -i, putc('-');
 xtoa((unsigned)i, dv + 5);
 break;
 case 'l':// 32 bit Long
 case 'n':// 32 bit uNsigned loNg
 n = va_arg(a, long);
 if(c == 'l' && n < 0) n = -n, putc('-');
 xtoa((unsigned long)n, dv);
 break;
 case 'x':// 16 bit heXadecimal
 i = va_arg(a, int);
 puth(i >> 12);
 puth(i >> 8);
 puth(i >> 4);
 puth(i);
 break;
 case 0: return;
 default: goto bad_fmt;
 }
 } else
 bad_fmt: putc(c);
 }
 va_end(a);
}

With printf() declared in <Application>.h (so there’s no need to make a Printf.h)

extern void printf(char *format, ...);

Here’s a screenshot of the CC2540 sending a number counting up at 5ms apart (through use of a timer based on SimplePeripheral project):

CC2540 printf()

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s