ThinkClamp – short project update

Finally got some spare time yesterday to solder the new PCB revision and it works well, some better than anticipated. I’ve found one or two PCB errors. Nothing major though, it’s all working well enough.

I decided not to solder on the reset buttons as a slide between the ON and OFF positions of the slide switch would effectively reset the entire board.

Soldered board with MSP430F5502 and HM-10

Soldered board with MSP430F5502 and HM-10

Now it’s time for some programming. With a USB-enabled microcontroller, this board would eventually support both USB and Bluetooth functionality, preferably with the same firmware flashed. I’ve decided to use the HM-10 with my custom firmware instead of using a RN42-HID module so I can further improve the firmware.

Update: There might be a minor problem with the LiPo charger in which the battery voltage may jitter when a battery is not connected. This caused a bit of a random enumeration/disconnection problem. However, if a battery is connected to the battery output pins, the board seems to work fine. The USB microcontroller enumerates with no problems at all. For now, I’ve soldered on a single-cell battery pack so that I may get on to write the firmware.

Advertisements

CC254x HID Project – Name changing feature added

After many weeks working on this particular feature, I have finally succeeded. Strangely enough, the solution is so simple, making all the other ways I have thought of seems dumb and unnecessary.

The name is stored in CC2540/1’s non-volatile (NV) memory. Along with the name, its CRC checksum and length are stored in separate NV memory slots as well. Upon start-up, the name and its CRC value is retrieved from the NV memory. A new CRC value is generated from the stored name and compared with the stored CRC value. If the two CRC values matches, the device will go on and set this stored name as the scan response data string and the device’s name. Otherwise, it will use the default names.

Before today, I have been trying the idea of dynamically allocating an array and filling it with appropriate elements to make up a new scan response. This approach has been fruitless. Then I realized, why not use the static scan response variable that is going to be available every time the device starts up and modify its elements instead. This new approach works flawlessly (after only a few tests).

The code should be pushed and available on Git (one of those repositories) soon enough. However, for future debugging, these are the relevant sections to this feature:

0. Local variables and constants

#define SNV_ID_DEVICE_NAME              0x80
#define SNV_ID_DEVICE_NAME_LENGTH       0x81
#define SNV_ID_DEVICE_NAME_CRC          0x82

uint8 *device_name_crc;
uint8 *device_name;
uint8 *device_name_length;
    
// GAP Profile - Name attribute for SCAN RSP data - Name shown up when scanned
static uint8 default_scanData[] =
{
  0x15,                             // length of this data => (name's size = 20 bytes) + 1
  GAP_ADTYPE_LOCAL_NAME_COMPLETE,   // AD Type = Complete local name
  'H',
  'I',
  'D',
  ' ',
  'K',
  'e',
  'y',
  'b',
  'o',
  'a',
  'r',
  'd',
  ' ',
  '&',
  ' ',
  'M',
  'o',
  'u',
  's',
  'e'
};

1. Local functions

//Pololu's CRC functions with minimal changes
uint8 CRCPoly = 0x89;  // the value of our CRC-7 polynomial
uint8 CRCTable[256];

void GenerateCRCTable()
{
    int i, j;
 
    // generate a table value for all 256 possible byte values
    for (i = 0; i < 256; i++)
    {
        CRCTable[i] = (i & 0x80) ? i ^ CRCPoly : i;
        for (j = 1; j < 8; j++)
        {
            CRCTable[i] <<= 1;
            if (CRCTable[i] & 0x80)
                CRCTable[i] ^= CRCPoly;
        }
    }
}
 
 
// adds a message byte to the current CRC-7 to get a the new CRC-7
uint8 CRCAdd(uint8 CRC, uint8 message_byte)
{
    return CRCTable[(CRC << 1) ^ message_byte];
}
 
 
// returns the CRC-7 for a message of "length" bytes
uint8 getCRC(uint8 message[], uint8 length)
{
    uint8 i;
    uint8 CRC = 0;
 
    for (i = 0; i < length; i++)
        CRC = CRCAdd(CRC, message[i]);
 
    return CRC;
}

3. Initialization in hidKbdMouse.c

void HidKbdMouse_Init( uint8 task_id )
{
  setupUART();
  GenerateCRCTable();
//......

4. Name checking during initlization

//about line 354 in source code
if(*device_name_crc != getCRC(device_name, *device_name_length)) {
      printf("Using default scan response name\r\n");
      GAPRole_SetParameter( GAPROLE_SCAN_RSP_DATA, sizeof ( default_scanData ), default_scanData );
    } else {      
      //make changes directly to the default_scanData. Since this variable is set at start-up, it should not matter
      uint8 len = *device_name_length;
      uint8 default_name_length = default_scanData[0];
      default_scanData[0] = len + 1;
      uint8 i;
      for(i = 0; i < len; i++) {
        default_scanData[i+2] = device_name[i];
      }      
      GAPRole_SetParameter( GAPROLE_SCAN_RSP_DATA, sizeof ( default_scanData ), default_scanData );
    }

//...
//about line 377 in source code
 if(*device_name_crc != getCRC(device_name, *device_name_length)) {
    GGS_SetParameter( GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, (void *) attDeviceName );
  } else {
    GGS_SetParameter( GGS_DEVICE_NAME_ATT, *device_name_length + 1, (void *) device_name );
  }

5. UART name changing section

//...
else if((rxBuffer[1] == 'N') && (rxBuffer[2] == ',')) {
      uint8 i;
      uint8 deviceNewName[20];
      uint8 deviceNewNameLength;
      uint8 deviceNewNameCRC;
      
      deviceNewNameLength = rxBufferIndex-3;
      if(deviceNewNameLength > 20) {
        printf("Name exceeds permitted length\r\n");
      } else {
        for(i = 3; i < rxBufferIndex; i++) {
          deviceNewName[i-3] = rxBuffer[i];
        }   
        deviceNewName[deviceNewNameLength] = '\0';
        deviceNewNameCRC = getCRC(deviceNewName, deviceNewNameLength);

        osal_snv_write(SNV_ID_DEVICE_NAME, 20, deviceNewName);
        osal_snv_write(SNV_ID_DEVICE_NAME_LENGTH, 1, &deviceNewNameLength);
        osal_snv_write(SNV_ID_DEVICE_NAME_CRC, 1, &deviceNewNameCRC);
        printf("Name is being set, reset to set new name\r\n");
      }
  • To change the device’s scan response data and device’s name, first it must be put into CMD mode by sending 3’s @, ie. @@@
  • Then, the name can be set to be stored with SN,<value> where is the new name and it must be less than 20 characters long.
  • To see changes, the device needs to be restarted with S,R.

HID firmware on HC-06 Bluetooth modules

371412603_147

TL;DR – HC-05/06 shares the same MCU used in the RN-42HID and RN-41HID. You can dump the firmware from these HID modules and transfer it onto the HC-05/06 modules. Doing so may infringe Roving Network’s intellectual property so it’s best if you don’t distribute their firmware. I post this because I found it interesting.

For educational purposes, I’ve been playing with these HC-05, HC-06 Bluetooth modules. They are cheap, widely available and their firmware can be changed. But the best thing about these modules is the microcontroller BC417 that is used to handle all their logic, the CSR Bluecore4-Ext, which shall be referred to from now as BC4-Ext.

CSR BC4 is available in different flavours, so far I’ve heard of BC4-Ext and BC4-ROM. The ‘Ext’ uses an external ROM to store some data, what, I can’t remember. These guys are the core of many Bluetooth modules, including the ones from Bluegiga and Roving Networks.

I have been learning to program these modules to handle UART communication to send HID key codes but it was difficult. The resources on this is quite limited and since Bluetooth 2.0 was so 2004, CSR Support website wasn’t of much help.

I wrote a custom firmware for the HM-10 CC2540/1 but it requires Bluetooth 4.0. For a keyboard and mouse, what good would Bluetooth 4.0 do. Bluetooth 2.0 is much more suitable as it is compatible with almost all Bluetooth devices that has been around for the last 10 years. In slight frustration, I gave in and bought a BlueSmirf RN-42-HID from Sparkfun. After reading the datasheet, however, it turns out they also use CSR BC4-Ext! This is great news.

The BlueSmirf arrived yesterday. After searching for the pinout of the RN-42, I soldered some jumper cables to its SPI pins and used a USB SPI programmer that is CSR-compatible and dump the firmware with BlueFlash. After flashing this firmware onto a HC-06 module, voilà, I now have an exact copy of a RN-42-HID in firmware.

There is still the issue of conflicting Bluetooth addresses, but may that be resolved by changing the PSR settings. Byron’s blogpost gives some steps on how this can be done. And of course, the firmware of the RN-42-HID will not be shared.

Bluetooth HID Firmware Tested on HM-10

Months ago, I wrote a firmware to convert any HM-10 to a Bluetooth Low Energy HID device. The HM-10 arrived and today I finally have some spare time to test it out. Flashing the HM-10 wasn’t straightforward as I thought it would be, due to the confusing VDD lines from the CC Debugger. So here’s how mine’s connected:

CC Debugger - HM-10

 

The LED on the CC Debugger should lit green, indicating it has found the CC2541 or CC2540 on the HM-10. After that, Texas Instruments SmartRF Flash Programmer was used to program the chip.

A MSP430 Launchpad was used with this test program:

#include <msp430.h>
#include <stdint.h>
#include "KBD_HUT.h"

void printf(char *format, ...);
void pressKey(uint8_t keyCode);
void releaseKey(uint8_t keyCode);
void sendKbdReport(void);
void sendMouseReport(void);
void buildMouseReport(void);

typedef struct {
 uint8_t buttons;
 uint8_t dX;
 uint8_t dY;
 uint8_t dZ;
} MOUSE_REPORT;

uint8_t index = 1;
MOUSE_REPORT mouseReport = { 0, 0, 0, 0 }; // HID report, to be sent to the PC.

const int8_t tableSinCosLookUp[16][2]; 

void main(void) {
 WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer

 BCSCTL1 = CALBC1_1MHZ;
 DCOCTL = CALDCO_1MHZ;

 //setup UART
 P1SEL = BIT1 + BIT2 ; // P1.1 = RXD, P1.2=TXD
 P1SEL2 = BIT1 + BIT2 ; // P1.1 = RXD, P1.2=TXD
 UCA0CTL1 |= UCSSEL_2; // SMCLK
 // 1000000 Hz 57600 bps
 UCA0BR0 = 0x11;
 UCA0BR1 = 0x00;
 UCA0MCTL = UCBRS_3 + UCBRF_0;
 UCA0CTL1 &= ~UCSWRST; // **Initialize USCI state machine**

 //setup LED
 P1OUT = 0;
 P1DIR |= BIT0;
 //setup button
 P1DIR &= ~BIT3;
 P1OUT |= BIT3; //pull-up, active low
 P1REN |= BIT3;

 while(1) {
 if((P1IN & BIT3) == BIT3) {
 releaseKey(hutSpacebar);
 P1OUT &= ~BIT0;
 } else {
 __delay_cycles(1000);
 if((P1IN & BIT3) != BIT3) {
 P1OUT |= BIT0;
 pressKey(hutSpacebar);

 buildMouseReport();
 sendMouseReport();
 }
 }
 sendKbdReport();
 }
}

void buildMouseReport(void) {
 mouseReport.dX =
 (tableSinCosLookUp[index][0] -
 tableSinCosLookUp[index - 1][0]) >> 1;
 mouseReport.dY =
 (tableSinCosLookUp[index][1] -
 tableSinCosLookUp[index - 1][1]) >> 1;

 if (index++ >= 90){
 index = 1;
 }
}

void pressKey(uint8_t keyCode) {
 printf("KD%c\r\n", keyCode); //send keycode as a character
}

void releaseKey(uint8_t keyCode) {
 printf("KU%c\r\n", keyCode); //send keycode as a character
}

void sendKbdReport(void) {
 printf("KUPDATE\r\n");
}

void sendMouseReport(void) {
 printf("M%c%c%c%c\r\n",mouseReport.buttons,mouseReport.dX,mouseReport.dY,mouseReport.dZ);
}

const int8_t tableSinCosLookUp[16][2] = {
 4,0,
 4,0,
 4,0,
 4,0,
 4,0,
 4,0,
 4,0,
 4,0,
 4,0,
 4,0,
 -4,0,
 -4,0,
 -4,0,
 -4,0,
 -4,0,
 -4,0,
 -4,0,
 -4,0
};

The test program demonstrates the Bluetooth HID firmware’s keyboard capability beautifully but there’s still a hiccup with the mouse movement, definitely because of that poor looking look-up table.

I shall have the TrackPoint setup and tested in a few days. Until then, thanks for reading and have fun!

Bluetooth 4 HID Keyboard & Mouse Bridge

Just keeping up to date, I’ve written a firmware for Texas Instruments CC2540 Bluetooth Low Energy IC that sends keyboard & mouse reports to the host using a few UART commands that works similarly to Arduino Keyboard’s press() and release() commands, except it will send raw HID keycodes instead of printable characters. As for the mouse, a UART command with button states, X, Y, Z bytes can be used at the same time as well.
The firmware isn’t as good as if it is programmed by professional software engineers and if IAR Workbench was free so I can spend an infinite amount of time tweaking, but it does the job. The advantage to using a CC2540 with my firmware means you can turn any cheap eBay HM-10 serial Bluetooth module into a HID Bluetooth keyboard & mouse bridge! The disadvantage would be that to write a new firmware, you would need to use IAR Embedded Workbench for 8051 which is free for 30 days or cost $3k if you buy it. However, for just flashing, IAR is not needed. So as an end-user, there are essentially no disadvantages.

The  UART commands are sent to the CC2540 via UART at 57600bps with 8 data bits and no parity. They must end with a carriage return (0x0D, ‘\r’) or a line termination character (0x0A, ‘\n’), or both. The commands include:

  • KU(keycode): a key is released with the keycode (keycode)
  • KD(keycode): a key is pressed with the keycode (keycode)
  • M(status)(X)(Y)(Z): sending buttons and mouse coordinates
  • KUPDATE: send report to host

(___) are 8-byte characters, and you don’t send the brackets, just the value inside the brackets. The (keycode) used are from USB HID usage table 1.12 in the keyboard section. Since I haven’t made a full keyboard board, the module will ask the host for pin, which is 000000. There is also another firmware that advertise the device with containing a keyboard so the user must use the keyboard to enter the pin, but a test board is needed thus this has not been tested.

Here is a pretty 3D render of the board I’ve whipped up.
It uses a MSP430 to handle the keyboard scanning and TrackPoint polling. The entire top left portion of the board is dedicated to power management. Battery used can be of any types in the range of 3V-5V. If the battery is a rechargeable Li-Po or Li-Ion single cell, the Micro USB can be used to recharge it. There are 2 LEDs (Green and Yellow) indicating if external power is present and whether the battery is being charged.
On this board, the battery connector used is a JST-PH 2.0mm 2-pin right angle connector. It is used for many battery packs for Remote Controlled toys though I have found that there is also 1.5mm, 1.25mm and 2.5mm. It just happens that the 2.0mm is cheap and available.
Beep beep boop, forgot to include a link to the released source code and binaries. Here it is: https://github.com/rampadc/cc2540-hidKbdM

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, &amp;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 &lt; 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 &quot;stdarg.h&quot;
#include &quot;hal_uart.h&quot;
#include &lt;string.h&gt;
#include &quot;hal_types.h&quot;

static void sendByte(unsigned char byte) {
 HalUARTWrite(HAL_UART_PORT_0, &amp;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 &lt; *dp)
 ++dp;
 do {
 d = *dp++;
 c = '0';
 while (x &gt;= d)
 ++c, x -= d;
 putc(c);
 } while (!(d &amp; 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 &amp; 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' &amp;&amp; i &lt; 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' &amp;&amp; n &lt; 0) n = -n, putc('-');
 xtoa((unsigned long)n, dv);
 break;
 case 'x':// 16 bit heXadecimal
 i = va_arg(a, int);
 puth(i &gt;&gt; 12);
 puth(i &gt;&gt; 8);
 puth(i &gt;&gt; 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()