PIC24 Bluetooth Low Energy DK

Thread Starter


Joined Aug 27, 2009
Here is a quick (work in progress) demo of the BLECM board being used for a quick 'remote relay' application from a cell phone app.

I only needed to modify the stock firmware to add (piggyback on the led control code) relay outputs on the mikrobus port pins and quickly design/build a 5vdc relay board with a DIP socket interface.

Port D on the PIC24 allows for 5 volt open-drain operation so these pins were used to drive an ILQ2 optocoupler for the relay coils.

The next step is to customize the generic Android app.

PIC24 xc16 C code change fragments

//Enable / Disable the MCP1642B 5V boost power supply for 5V power pin on MikroBUS header
//Set to 0 to disable; 1 to enable (Enable this for 5V Click Boards)
//#define MCP1642B_EN    0
#define MCP1642B_EN    1


// RELAY outputs
#define RELAY1    LATDbits.LATD3 // output 0 (low) turns on relay
#define RELAY2    LATDbits.LATD9
#define RELAY3    LATDbits.LATD10
#define RELAY4    LATDbits.LATD4

    // RELAYs are outputs and open-drain
    // to drive ILQ2 opto
    // setup in Mikrobus header
    ODCDbits.ODD3 = 1; // pin 16
    ODCDbits.ODD9 = 1; // pin 11
    ODCDbits.ODD10 = 1; // pin 12
    ODCDbits.ODD4 = 1; // pin 2

        LED1 = appData.led1; // logic high turns on led
        RELAY1 = !appData.led1; // logic low turns on relay
        LED2 = appData.led2;
        RELAY2 = !appData.led2;
        LED3 = appData.led3;
        RELAY3 = !appData.led3;
        LED4 = appData.led4;
        RELAY4 = !appData.led4;
        LED5 = 1;
        LED6 = 0;
Project directory
Last edited:

Thread Starter


Joined Aug 27, 2009
The original project with the dev board is done and is in the hands of the requester. I've created a more DIY version of the BLECM board using the PIC24FV16KM202 chip. My vectorboard prototype used the BLE2 click board from Mikro. This was a poor choice at first. The first problem is their RN4020 model is one of the originals with very old firmware (1.10), the second is the designer of the board was too lazy to connect all of the most important signals from the module to the pin header. The header problem was easily fixed with fine wire, fine solder and steady hands.:(

The problem of upgrading the firmware to at least 1.23 needed a little more work. There is a PC program from Microchip to upgrade firmware but it needs a direct 3.3v TTL connection to the module with hardware RTS/CTS flow control, plus it's super easy to brick the module if the upgrade fails with this method. The RN4020 has a DFU-OTA firmware upgrade option via Bluetooth but it also needs a hardware terminal emulator connection to configure the device. The solution was to add an option to the PIC24 driver to send the correct ASCII commands and bit toggles to put the RN4020 in the correct mode for the OTA update. The circuit diagram below shows the jumper connections (SV1 pins 2&3) that the program reads for a 'low' on boot to run the OTA code function. When the status led blinks in fast mode the module is ready for programming using an IOS application from Microchip.

The Apple app downloads 1.23 by default but there is a later firmware 1.33.

I used a IPAD mini 2 for the upgrade process (IMPORTANT, be sure to reboot the Apple device while the RN4020 is in upgrade mode so it can update the Bluetooth service entry to MLPD mode).

After the upgrade it was possible to correctly configure the old module for BLE use. The 28 pin device has limited pins so there are 4 control outputs from the PIC plus the RN4020 gpio pins (analog and digital) can be used for control options.

RN4020 analog commands
Description These commands set the analog port output (O) and get the input (I) voltage. The first parameter can be 0, 1, or 2, which specifies the analog port number. The second parameter is only for analog output, which sets the output voltage in mV. The range of output/input voltage is 0V to 1.3V (valid range is 0x0000 to 0x0514). When outputting the analog signal, the RN4020 module cannot operate in Deep Sleep mode. Instead, the firmware will automatically adjust the operation mode to Shallow Sleep. Once the analog output is turned off by issuing the command @O,<0-2>,0000, the firmware will again automatically adjust the operation mode back to Deep Sleep mode, when available. Default Not applicable. Example @O,1,03E8 // Set AIO1 output voltage to be 1000 mV
Example |O,07,05 // Set PIO1 and PIO3 output to be high and PIO2 // output to be low |I,06 // Read states of PIO2 and PIO3. The result is a one // byte bitmap. If the result is 04, PIO2 is low // and PIO3 is high.
The second I/O option is a SPI Master BLE service that can be used to control just about any SPI connected device for sending or receiving data via Bluetooth. The basic I/O driver is complete with plans to add the MCP3208 ADC and a general output shift-register Bluetooth characteristic for examples.

Software/Hardware (work in progress).


Last edited:

Thread Starter


Joined Aug 27, 2009
Last edited:

Thread Starter


Joined Aug 27, 2009
Version 1.33 now available. The custom advertising packets are incredibly useful.
I updated the DEV board RN4020 to 1.33 and promptly downgraded because the read/write LED code for the demo app stopped working while all other functions were good. I suspect it's a change in the public services with the SS command in 1.33 that's the root cause and is a easy software fix in the original demo code that's locked to version 1.23.
1.2 Known Issues • Compatibility with previous 1.2x or earlier is not guaranteed, especially if the application relies on SS (set local services) command to create public services. In firmware 1.33BEC, the SS command only supports battery service and device information. Firmware 1.33BEC supports creating public services (16-bit UUID) using PS and PC command, in the same manner as defining private services.
The extracted binaries (from the DFU utility program) for the 1.23/1.33 upgrades are in the github repo.

Bluetooth sniffer info after upgrade.


Joined Jun 6, 2011
Fyi: 1.33 is much faster. It will fill a small receive buffer very quickly. Make sure your buffer is large enough to capture everything you expect to receive, or implement hardware flow control.

Thread Starter


Joined Aug 27, 2009
Fyi: 1.33 is much faster. It will fill a small receive buffer very quickly. Make sure your buffer is large enough to capture everything you expect to receive, or implement hardware flow control.
The PIC24 usart is using the hw rts/cts lines with module flow control enabled to drive interrupt driven ring buffers on the uC for receive and transmit data. Proper hw rts/cts is one reasons I had to solder the little wires on the Mikro module.:( I hate it when an otherwise nice product like the BLE2 click board is handicapped by such lazy engineering of something so simple. :(

Thread Starter


Joined Aug 27, 2009
1.33.4 version query.

A few data points of what I'm seeing between 1.33 and 1.23 firmware versions for a write command.
4 led on command, phone

1.33.4, RN4020 tx pin, not working

1.23.5, RN4020 tx pin, working

The hex value of the 16-bit handle of the server characteristic has changed. The handle of the (private) characteristic will change when you add a characteristic. It's the same code in the pic so the firmware must be creating the handle slightly differently.

The code in the original PIC24 BLECM demo app looks for: 001E
        //Process any new messages received from RN module
        appData.got_packet = BT_ReceivePacket(appData.receive_packet); //Get new message if one has been received from the RN4020
        if (appData.got_packet == true) { //true if new packet received
            if (strstr(appData.receive_packet, "WV,001E,")) { //Check for LED update message
                GetNewLEDs(); //Latch new LED values
            //Other message handling can be added here
The fix is simple, add an additional check for the new code.
Last edited:

Thread Starter


Joined Aug 27, 2009
Mounted the RN4020 on the PROTO adapter for another project. The target for the BLE unit is the new PIC32MZ WiFi board so it's on another DIY adapter for the Mikro bus socket on that board. A very Nice adapter!


Thread Starter


Joined Aug 27, 2009
Analog Channel three [0..3] selected.

Completed the basic RN4020 PIC24FV SPI ADC device interface for the 12-bit MCP3208. The output is to the original POT value bar on the BLECM app with channel switching using the D3 and D4 app switches to encode a binary [0..3] channel number for the ADC. There are several new BLE characteristics for analog channel and slave devices but that needs the custom phone app that still being hacked on.

Create a generic buffered byte addressable SPI pipe stream. https://github.com/nsaspook/fac_relay_clone/blob/master/spi.c

MCP3208 command/data byte addressable structure (ADC_DATA). https://github.com/nsaspook/fac_relay_clone/blob/master/app.h
24-bit spi mode 0,0 transmit sequence in the C structure with union overlay to long and 4 byte array.
/* for 24-bit transmit and extra status data */
typedef struct A_data {
uint32_t dummy12 : 12; // dummy space for adc data
uint32_t nullbits : 2;
uint32_t index : 3; //adc channel select
uint32_t single_diff : 1;
uint32_t start_bit : 1;
uint32_t dummy8 : 8; // move past 24 bits
uint32_t finish : 1;
uint32_t in_progress : 1;
} A_data;

/* used to hold 24-bit adc buffer, index and control bits */
union adc_buf_type {
uint32_t ld;
uint8_t bd[4];
struct A_data map;
typedef struct {
union adc_buf_type mcp3208_cmd;
uint16_t potValue;
uint8_t chan;

variable in app.c
ADC_DATA adcData;
ADC command/data task for application state machine. https://github.com/nsaspook/fac_relay_clone/blob/master/adc.c

void ADC_Init()
adcData.mcp3208_cmd.ld = 0; // clear the command word
adcData.chan = 0;
adcData.mcp3208_cmd.map.start_bit = 1;
adcData.mcp3208_cmd.map.single_diff = 1;
adcData.mcp3208_cmd.map.index = 0; // channel
appData.ADCcalFlag = true;
SPI_CS0 = 1;
SPI_CS1 = 1;
... write to spi to request data
adcData.mcp3208_cmd.map.single_diff = 1;
adcData.mcp3208_cmd.map.index = adcData.chan;
SPI_ClearBufs(); // dump the spi buffers
SPI_CS0 = 0; // select the ADC
... read from spi for ADC data
while (SPI_IsNewRxData()) {
switch (count) {
case 1:
adcData.potValue = (SPI_ReadRxBuffer()&0x0f) << 8;
case 2:
adcData.potValue += SPI_ReadRxBuffer();
adcData.mcp3208_cmd.map.finish = true;
SPI_ReadRxBuffer(); // eat extra bytes
... set data flag to main task
if (adcData.mcp3208_cmd.map.finish) {
adcData.mcp3208_cmd.map.in_progress = false;
appData.accumReady = true;
return true;
SDO: adc channel 3 selected

SDI: digital value of current analog input

The controllers app state machine will then send the data via the RN4020 as needed.
Last edited: