petzval
Posts: 46
Joined: Sat Aug 10, 2013 12:15 pm

Bluetooth LE code and motor speed control

Thu May 30, 2019 12:08 pm

This post presents C code to communicate with a network of Bluetooth LE receivers set up to control motors. Circuit, component, and LE device programming details are included. This has been done on a Pi3 Model B+ with built-in Bluetooth.
Part of the code is straightforward communication procedures to connect a Bluetooth LE device and send HCI/ACL/ATT command strings. On top of this is specific example code for controlling motor speed by PMW via a Microchip RN4020 Bluetooth LE chip and a motor driver. It is possible to have multiple LE devices connected simultaneously, each controlling a separate motor. The code does not require the installation of the libbluetooth-dev library.
I have used a Microchip RN4020 Bluetooth LE receiver chip that has a PWM generator and four general purpose input/output (GPIO) pins. To program and monitor the RN4020 for development it must be connected to a PC serial port. The simplest option, especially for development, is Microchip's PICTAIL device which is an RN4020 on a carrier board and an inbuilt USB serial connection which also provides power - just plug it into a PC USB port and it is ready to go. The GPIO pins are accessible via convenient headers.

Microchip PICTAIL development board - RS part number 828-2859

Microchip RN4040 chip. Various firmware vintages are available. I have the older RS 828-2856

If you want to deal with the RN4020 chip alone you will need surface mount capabilities, and a USB to 3.3V UART serial cable for programming, such as the following:

FTDI USB-3.3V UART cable - CPC part number SC14084

I see this on the RS website - it will avoid surface mount work:
Mikro-Elektronika BLE2 - RN4020 on carrier board RS 862-4856

The following diagram shows the connections for programming and motor operation.
ble01.gif
ble01.gif (15.22 KiB) Viewed 2510 times
I have used a Pololu MAX14870 board with a Maxim 14870 H-bridge driver chip, but any similar driver with PWM, Enable and Direction inputs will work. In case it isn't obvious, the serial PC connection is not needed for normal operation - it is only for initial programming of the RN4020. For development, the serial link and motor driver can be connected simultaneously.
The instructions below set up the RN4020 with a number of "characteristics" which are simply single byte registers on the chip that can be written to over the Bluetooth link. The only commands that are sent to the LE device are connect, disconnect, and write a characteristic. Writing a characteristic does not in itself perform any operation. The RN4020 must be running a script code that reads the characteristics and then sets the PWM/GPIO outputs accordingly. The instruction set for the RN4020 is very limited, and some cunning is required to do this. The key is that it has an alert service that is activated by writing to its characteristic. Writing a 0,1 or 2 to this ALERT characteristic runs the script code at the following labels, coded as below:

0 runs @ALERTO not coded
1 runs @ALERTL not coded
2 runs @ALERTH set motor enable/direction/speed

Once the ALERT code is completed, the RN4020 does nothing more, and simply waits for another characteristic write.
To program the RN4020 you will need a serial terminal program running on the PC. These instructions are for Realterm.

In the Port tab: set Baud=115200. Port=COM port number - the USB connection should appear as a COM port. Parity=None. Data Bits=8. Stop Bits=1. Hardware flow Control=None. Click Change button

In the Send tab: In the EOL box, tick the top +CR and leave the rest unticked. The \n box Before/After should be unticked. So each command send is followed by a single carriage return.

Once set up, type commands in the text box to the left of the Send Numbers button. Then click Send ASCII.
To start, type d in the text box, then click Send ASCII, you should get the following response from the RN4020:

BTA=112233445566
Name=RN177C
Connected=no
. . . etc

BTA is its Bluetooth board address which you need to enter into the C code before compiling, so make a note of it.
Now send the following commands (without the comments obviously):

Code: Select all

sf,2           ; OPTIONAL factory reset if needed to clear existing settings
r,1            ; reboot to execute above instruction

ss,00080001    ; enable alert service and private characteristics
r,1            ; reboot to execute above instruction

pz             ; zero private services
               ; set private service UUID
               ; choose any 16-byte number
ps,112233445566778899aabbccddeeff00
               ; set 3 characteristics, each
               ; 1 byte, read/write, no acknowledge
pc,112233445566778899aabbccddeeff01,06,01  
pc,112233445566778899aabbccddeeff02,06,01 
pc,112233445566778899aabbccddeeff03,06,01 
r,1            ; reboot to execute above commands

ls             ; print characteristics

The result should be:

1802
  2A06,000B,V
112233445566778899AABBCCDDEEFF00
  112233445566778899AABBCCDDEEFF01,000E,06,01
  112233445566778899AABBCCDDEEFF02,0010,06,01
  112233445566778899AABBCCDDEEFF03,0012,06,01
The important information here is the handles that have been assigned to the four characteristics. Alert has been given 000B, and the three private characteristics are 000E, 0010, 0012. The RN4020 script and the C code assume these handles and GPIO pin use:
Alert = 000B
Control = 000E
PWM hi = 0010
PWM lo = 0012
P1 = PWM
P2 = Direction
P3 = Enable hi=off lo=on
If different, you must modify the codes appropriately.
Now enter the script code from the terminal:

Code: Select all

+   ; toggle echo on
wc  ; clear script
ww  ; start script input mode
    ; in the PC terminal set EOL = +CR and +LF so it
    ; sends CR/LF after each entry
    ; START SCRIPT
@PW_ON                 ; executed at power on
|O,06,04               ; GPIO pins enable/P3=hi dirn/P2=lo
[,1,00,10,FF,00,00,00  ; PWM to P1 = off
SHW,000E,04            ; Control = 04
SHW,0010,00            ; PWM hi = 00
SHW,0012,10            ; PWM lo = 10
@ALERTH                ; executed when write Alert=2
$VAR1=SHR,0010         ; read PWM hi
$VAR2=SHR,0012         ; read PWM lo
[,1,$VAR1,$VAR2,FF,00,00,00  ; set PWM on P1
$VAR1=SHR,000E         ; read Control and . . .
|O,06,$VAR1            ; copy to Enable/Dirn GPIO pins
      ; END SCRIPT
      ; stop script mode by sending ESC character (27)
      ; type 27 in text box and click Send Numbers
      ; in the PC terminal untick LF, to restore
      ; single CR send after each entry

+     ; toggle echo off
lw    ; print script to check
Extract the following C code and save as, for example, btle.c

Code: Select all

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

// Bluetooth development files option
// It is not necessary to:  apt-get install libbluetooth-dev
// uncomment #define BTDEV if libbluetooth-dev is installed
// and you want to add any of its features to the code

// compile instructions
// BTDEV not defined     gcc btle.c -o btle
// BTDEV defined         gcc btle.c -lbluetooth -o btle


// #define BTDEV


#ifdef BTDEV
   // libbluetooth-dev installed
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#else
   // these are the definitions supplied by 
   // the above libbluetooth-dev include files
#define BTPROTO_HCI 1
#define SOL_HCI 0
#define HCI_FILTER 2
#define HCIDEVUP _IOW('H',201,int)
struct sockaddr_hci
  {
  unsigned short hci_family;
  unsigned short hci_dev;      
  unsigned short hci_channel;
  };

struct hci_filter
  {
  unsigned int type_mask;
  unsigned int event_mask[2];
  unsigned short opcode;
  };
#endif

void devinfo(void);
int devconnect(int ndevice);
int devdisconnect(int ndevice);
int motorcontrol(int ndevice,int dirn,int speed,int opflag);
int setdhandle(int ndevice);
int readreply(int replyflag,int numrep,unsigned char *han);
int sendcmd(unsigned char *cmd,int len);
int devsock(void);
int writectic(int ndevice,int cticn,unsigned char *data);
void hexdump(unsigned char *buf, int len);

     // simple user interface functions
void printhelp(void);
int inputdev(void);
int inputctic(int ndevice);
int inputval(void);
int inputdirn(void);
int inputspeed(void);
int inputint(void);

// LE characteristics

struct cticdata 
  {
  int size;     // number of bytes  (0=no entry)
  int ack;      // 0=no acknowledge  1=acknowledge
  char *name;   // name of characteristic - your choice
  int chandle;  // characteristic handle 
  };

#define NUMCTICS 8

struct cticdata motor[NUMCTICS] = {
{1,0,"Alert" ,0x000B},
{1,0,"Control",0x000E},
{1,0,"PWM hi",0x0010},
{1,0,"PWM lo",0x0012},
{2,1,"Test",0x0014},
{0,0,NULL,0},
{0,0,NULL,0},
{0,0,NULL,0},
};

struct devicedata
  {
  int conflag;                // 0=disconnected 1=connected
  int speed;                  // current motor speed
                               // GPIO pin bit masks  P1=1  P2=2  P3=4  P7=8
  unsigned char enmask;        // bit mask of enable pin
  unsigned char dirmask;       //             directionn pin
                 // index of characteristic info in ctic[] 
  int alertn;    // Alert = ctic[alertn]
  int controln;  // Control = ctic[controln]
  int pwmhin;    // PWMhi = ctic[pwmhin] 
  int pwmlon;    // PWMlo = ctic[pwmlon]

  unsigned char dhandle[2];   // LE device handle returned by connect  [0]=lo [1]=hi
  char name[16];              // name of LE device - your choice
  unsigned char baddr[6];     // board address hi byte first
  struct cticdata *ctic;      // characteristic info array
  };

#define NUMDEVICES 4

struct devicedata device[NUMDEVICES] = {
{ 0,0,4,2,0,1,2,3,{0,0},"Pictail", {0x11,0x11,0x11,0x11,0x11,0x11},motor },
{ 0,0,4,2,0,1,2,3,{0,0},"Device B",{0x22,0x22,0x22,0x22,0x22,0x22},motor },
{ 0,0,0,0,0,1,2,3,{0,0},"Device C",{0x33,0x33,0x33,0x33,0x33,0x33},motor },
{ 0,0,0,0,0,1,2,3,{0,0},"Device D",{0x44,0x44,0x44,0x44,0x44,0x44},motor },
};

// GLOBAL CONSTANTS

struct globpar 
  {
  int printflag;       // print BT sends and replies
  int hci;             // PI's BT handle
  int handev;          // command strings set with this device's handle  -1=none
  int replyflag;       // 0=wait for expected number of replies  1=wait for time out                      
  int conrep;          // connect expected replies   
  int timout;          // time out for replies seconds
  };
  
struct globpar gpar;

               // command strings
unsigned char hciopen[32] = {1,0x0D,0x20,0x19,0x60,0,0x60,0,0,0,0x7C,0x17,0x2D,0xC0,0x1E,0,0,0x18,0,0x28,0,0,0,0x2A,0,0,0,0,0}; // len 29
unsigned char hciclose[8] = {1,6,4,3,0x40,0,0x13};  // len 7
unsigned char hciwrite[32] = {2,0x40,0,8,0,4,0,4,0,0x52,0x0B,0,0};  // len 13 if 1 byte
              //  [1][2]=device handle  [9]=opcode  [10][11]=characteristic handle  [12]=data - up to 20 bytes

int main()
  {
  int speed,ndevice,dirn,cticn,cticval;
  unsigned char cmd,cmds[8],dat[8];
  
     // global parameters you choose
  gpar.printflag = 1;   // 0=no traffic prints  1=print bluetooth traffic
  gpar.replyflag = 0;   // 0=wait for specific number of replies  1=time out
  gpar.conrep = 23;     // expected number of replies for connect
  gpar.timout = 1;      // reply wait time out = 1 second

    // global parameters set by code
  gpar.hci = -1;         // hci socket
  gpar.handev = -1;      // handles set for this device number 

  
  if(devsock() == 0)    // open HCI socket gpar.hci 
    {
    printf("Socket open ERROR\n");  
    return(0);
    }
  
  ndevice = 0;  // device index
  speed = 8;    // motor speed
  dirn = 0;     // motor direction
  cticn = 0;    // characteristic index
  cticval = 0;  // characteristic value

  printhelp();
  
  do
    {
    printf("Input command: ");
    scanf("%s",cmds);
    cmd = cmds[0];
 
       // further inputs
          
    if(cmd == 'c' || cmd == 's' || cmd == 'v' || cmd == 'x' || cmd == 'd' || cmd == 'w')
      {
      ndevice = inputdev();
      if(cmd == 's' || cmd == 'v')
        {
        speed = inputspeed();
        if(cmd == 's')
          dirn = inputdirn();
        }
      if(cmd == 'w')
        {
        cticn = inputctic(ndevice);
        cticval = inputval();
        }  
      }
      
      // execute command
      
    if(cmd == 'h')            // help
      printhelp();
    if(cmd == 'c')            // connect device
      devconnect(ndevice);
    else if(cmd == 's')       // start motor
      motorcontrol(ndevice,dirn,speed,1);
    else if(cmd == 'v')       // change speed
      motorcontrol(ndevice,0,speed,2);
    else if(cmd == 'x')       // stop motor
      motorcontrol(ndevice,0,0,0);
    else if(cmd == 'd')       // disconnect device
      devdisconnect(ndevice);
    else if(cmd == 'w')       // write characteristic
      {
      if(setdhandle(ndevice) != 0)  // set device handles in command strings
        {
        dat[0] = (unsigned char)(cticval & 0xFF);         // convert to unsigned char array
        dat[1] = (unsigned char)((cticval >> 8) & 0xFF);  // in case 2 byte
        writectic(ndevice,cticn,dat);  
        }   
      }
    else if(cmd == 'i')      // print all device info
      devinfo();
    else if(cmd != 'q')
      printf("Invalid command\n");
    }
  while(cmd != 'q');
  
  close(gpar.hci);  // close HCI socket
  
  return(0);
  }

/*************** PRINT DEVICE INFO ********************/

void devinfo()
  {
  int n,k;
  struct devicedata *tdp;
  struct cticdata *cp;
  
  for(n = 0 ; n < NUMDEVICES ; ++n)
    {
    tdp = &device[n];
    printf("DEVICE %d  %s  Connect=%d  Speed=%d\n",n,tdp->name,tdp->conflag,tdp->speed);
    printf("  ADDR = ");
    for(k = 0 ; k < 6 ; ++k)
      printf("%02X ",tdp->baddr[k]);
    if(tdp->conflag != 0)
      printf("   Handle=%02X%02X",tdp->dhandle[1],tdp->dhandle[0]);
    printf("\n");
    for(k = 0 ; k < NUMCTICS ; ++k)
      {
      cp = &tdp->ctic[k];
      if(cp->size > 0)
        printf("  %s - Handle %04X Size %d Ack %d\n",cp->name,cp->chandle,cp->size,cp->ack);
      }
    }
  }
  
/***********  CONNECT DEVICE index ndevice *******************/

int devconnect(int ndevice)
  {
  int n,retval;
  struct devicedata *dp;
  
  if(ndevice < 0 || ndevice >= NUMDEVICES)
    {
    printf("Invalid device\n");
    return(0);
    }
    
  dp = &device[ndevice];
  if(dp->conflag != 0)
    {
    printf("Already connected\n");
    return(0);
    }

     // copy board address to command string  low byte first
  for(n = 0 ; n < 6 ; ++n)
    hciopen[n+10] = dp->baddr[5-n];
  
  if(sendcmd(hciopen,29) == 0)
    return(0);
   
   /***** CONNECT REPLIES *****************
   This is set up to exit readreply when it times out:
      readreply(1,  ....second parameter number of expected replies ignored... 
    
   If you see a consistent number of replies (say 23)
   modify replyflag and gpar.conrep to exit on that number:
   
      readreply(0,23,dp->dhandle)
        
   This will eliminate the time out wait and will be faster.
  
   When experimenting with commands, call readreply(1,..
   to see all the replies when the number is uncertain  
  **************************************/
    
  retval = readreply(1,gpar.conrep,dp->dhandle);  // will read device handle
  if(retval == 0)
    {
    printf("Time out - failed\n",retval);
    return(0);
    }
  if(retval == 1)
    {
    printf("Fail - no handle\n");
    return(0);
    }
  printf("Connect OK\n");
    
  dp->conflag = 1;
  
       
  return(1);
  }

/***********  DISCONNECT *******************/

int devdisconnect(int ndevice)
  {
  int n;
  struct devicedata *dp;
  
  dp = &device[ndevice];
  if(dp->conflag == 0)
    {
    printf("Already disconnected\n");
    return(0);
    }
    
  if(setdhandle(ndevice) == 0)   
    return(0);                  
    
  if(sendcmd(hciclose,7) == 0)
    return(0);
    
  if(readreply(gpar.replyflag,2,NULL) == 0)  
    return(0);

  dp->conflag = 0;
  gpar.handev = -1;    

  printf("Disconnected\n");       
  return(1);
  }

/*********** MOTOR CONTROL **************
 opflag  0 = stop motor - needs ndevice
         1 = start motor - needs ndevice, dirn (0/1) ,speed (0-16)
         2 = change motor speed - needs ndevice, speed (0-16)
 **********************************/       
 
int motorcontrol(int ndevice,int dirn,int speed,int opflag)
  {
  unsigned char dat,spd;
  struct devicedata *dp;
    
  if(setdhandle(ndevice) == 0)  // set device handles
    return(0);                
  
  if(speed < 0 || speed > 16)
    {
    printf("Invalid speed\n");
    return(0);
    }

  dp = &device[ndevice];
  spd = (unsigned char)speed;
  
  if(opflag != 2)    // stop/start  
    {                // will change Control characterisctic
    if(opflag == 0)         
       {                    // stop
       dat = dp->enmask;    // enable hi  dirn lo
       spd = 0;             // speed = 0
       }
    else                   
      {                     // start
      if(dirn == 0)
        dat = 0;            // dirn lo  enable lo
      else
        dat = dp->dirmask;  // dirn hi  enable lo
      }
    // set Control characteristic - does not change GPIO pins
    // ALERT and RN4020 script do that
    if(writectic(ndevice,dp->controln,&dat) == 0)
      return(0);
    }
                            // write PWMhi/lo settings   
  dat = spd;
  if(writectic(ndevice,dp->pwmhin,&dat) == 0)
    return(0);
    
  dat = (unsigned char)(16-spd);
  if(writectic(ndevice,dp->pwmlon,&dat) == 0)
    return(0);  
  
  dat = 2;                                     // Alert = 2
  if(writectic(ndevice,dp->alertn,&dat) == 0)  // trigger ALERTH script
    return(0);
         
  device[ndevice].speed = spd;
        
  return(1);    
  }

/************* SET DEVICE HANDLE in command strings *********/

int setdhandle(int ndevice)
  {
  struct devicedata *dp;
 
  if(gpar.handev == ndevice)
    return(1);   // device handles already set
  
  if(ndevice < 0 || ndevice >= NUMDEVICES)
    {
    printf("Invalid device\n");
    return(0);
    }
    
  dp = &device[ndevice];
  if(dp->conflag == 0)
    {
    printf("Not connected\n");
    return(0);
    }
     
  hciwrite[1] = dp->dhandle[0];
  hciwrite[2] = dp->dhandle[1];
  
  hciclose[4] = dp->dhandle[0];
  hciclose[5] = dp->dhandle[1];

  gpar.handev = ndevice;   // device handles set for this device number
  return(1);
  }
  
/***********  WRITE CHARACTERISTIC *****************
must set up hciwrite first with device handle via setdhandle(ndevice)
ndevice = device index in device[ndevice]
cticn = characteristic index in device[ndevice].ctic[cticn]
data = array of bytes to write - low byte first 
*****************/

int writectic(int ndevice,int cticn,unsigned char *data)
  {
  struct cticdata *cp;  // characteristic info structure
  int n,chandle,size,ack;
    
  cp = &device[ndevice].ctic[cticn];
  chandle = cp->chandle;  // characteristic handle
  size = cp->size;        // number of bytes
  ack = cp->ack;          // acknowledge        
           
                          // set characteristic handle
  hciwrite[10] = (unsigned char)(chandle & 0xFF);
  hciwrite[11] = (unsigned char)((chandle >> 8) & 0xFF);
  for(n = 0 ; n < size ; ++n)     // set data
    hciwrite[12+n] = data[n];     // low byte first

  hciwrite[3] = size+7;
  hciwrite[5] = size+3;
  
  if(ack == 0)           // no acknowledge 
    hciwrite[9] = 0x52;  // write command opcode
  else                   // acknowledge
    hciwrite[9] = 0x12;  // write request opcode

  printf("Write %s %s =",device[ndevice].name,cp->name);    
  for(n = 0 ; n < size ; ++n)
    printf(" %02X",hciwrite[n+12]);
  printf("\n");
       
  if(sendcmd(hciwrite,12+size) == 0)   // send 13 for 1 byte
    return(0);
    
  if(readreply(gpar.replyflag,1+ack,NULL) == 0)    // 2 replies if acknowledge  
    return(0);   
 
  return(1);
  }
  
/*************** SEND COMMAND *********
cmd = string to send
len = number of bytes to send
**************************************/

int sendcmd(unsigned char *cmd,int len)
  {
  int nwrit,ntogo;
  unsigned char *s;
  time_t tim0;
    
  if(cmd[0] == 2 && (cmd[9] == 0x12 || cmd[9] == 0x0A))
    printf("*** WARNING *** may disconnect device\n"); 
    
  s = cmd;  
  
  if(gpar.printflag != 0)
    {
    printf("< CMD");
    if(cmd[0] == 2)
      printf(": Opcode %02X\n",cmd[9]);
    else if(cmd[0] == 1)
      printf(": OGF=%02X OCF=%02X\n",(cmd[2] >> 2) & 0x3F,((cmd[2] << 8) & 0x300) + cmd[1]);
    hexdump(s,len); 
    }
    
  ntogo = len;

  tim0 = time(NULL);
  do
    {
    nwrit = write(gpar.hci,s,ntogo);
    if(nwrit > 0)
      {
      ntogo -= nwrit;
      s += nwrit;
      }  
    if(time(NULL) - tim0 > 3)
      {
      printf("Send CMD timeout\n");
      return(0);
      }
    }
  while(ntogo != 0);

  return(1);
  }

/************** READ REPLY ***********************
replyflag = 0  wait for expected number of replies
            1  wait for time out to see all replies when number unknown 
numrep = expected number of replies for replyflag=0 

han = destination for 2 byte device handle from connect reply
      or NULL if not connect reply
          
return 1=OK   2=connect OK   0=time out when waiting for reply count
****************************/

int readreply(int replyflag,int numrep,unsigned char *han)
  {
  unsigned char b0,buf[512];   
  int len,blen,wantlen,retval;
  int n,k;
  time_t tim0;
      
  tim0 = time(NULL);
  n = 0;   // number of reply
   
  blen = 0;         // existing buffer length
  wantlen = 8192;   // expected messaage length - new message flag 
  retval = 1;       // non-connect OK return   
   
  do   
    {
    // next message may loop with data in buf
    do       // wait for complete message
      { 
      if(blen != 0 && wantlen == 8192)   // find expected messaage length
        {
        b0 = buf[0];
        if(!(b0==1 || b0==2 || b0==4))
          {   
          printf("Unknown packet type\n");
          // clear reads and exit
          do
            {
            len = read(gpar.hci,buf,sizeof(buf));
            }
          while(time(NULL) - tim0 <= 3);
          return(0);
          }
        if(b0 == 1 && blen > 3)
          wantlen = buf[3] + 4;
        else if(b0 == 2 && blen > 4)
          wantlen = buf[3] + (buf[4] << 8) + 5;
        else if(b0 == 4 && blen > 2)
          wantlen = buf[2] + 3;
        }
       
      if(blen < wantlen)   // need more        
        {   
        do       // read block of data - may be less than or more than one line
          {
          len = read(gpar.hci,&buf[blen],sizeof(buf)-blen);
        
          if(time(NULL) - tim0 > gpar.timout+1)
            {                                   // timed out
            if(replyflag == 0 && retval != 2)   // counting replies and not connecting 
              {
              printf("Timed out waiting for %d replies\n",numrep);
              return(0);       // time out is error
              }
            if(retval == 2)
              printf("Seen %d connect replies - see notes in devconnect()\n",n);
            return(retval);   //  time out exit - OK return
            }
          }
        while(len < 0);
        blen += len;  // new length of buffer
        }                 
      }    
    while(blen < wantlen);
       
               // got a complete message length = wantlen 
               // buffer length blen may be larger
               
    if(han != NULL)   // reply from connect - looking for device handle 
      {
      if(buf[0] == 4 && buf[1] == 0x3E && buf[3] == 1 && buf[4] == 0)   // event 3E subevent 1  status 0
        {   // Vol 2 Part E 7.7.65
            // connect OK
        han[0] = buf[5];  // read returned device handle
        han[1] = buf[6];
        retval = 2;       // connect OK return even if missed expected reply count
        }
      }  // end connect
      
    ++n;  // reply count  
         
    if(gpar.printflag != 0)
      { 
      b0 = buf[0];
      if(b0 == 4)
        {
        printf("%d > Event: %02X\n",n,buf[1]);
        if(buf[1] == 0x0E && buf[2] == 4 && buf[3] == 1 && buf[6] != 0)
          printf("**** ERROR return **** CMD %02X %02X\n",buf[4],buf[5]);
        }
      else if(b0 == 2)
        printf("%d > ACL Opcode %02X\n",n,buf[9]);
      else
        printf("%d > Unknown\n",n);
      hexdump(buf,wantlen);
      }
    
    if(blen == wantlen)
      {    // have got exact message length
      blen = 0;
      }
    else
      {   // have got part of next message as well
          // wipe last message length wantlen - copy next down
          // starts at buf[wantlen] ends at buf[blen-1]
          // copy to buf[0]             
      for(k = wantlen ; k < blen ; ++k)
        buf[k-wantlen] = buf[k];       
      blen -= wantlen;
      }

    wantlen = 8192;  // new message flag 

    }  // next message
  while(n < numrep || replyflag != 0 || blen != 0);
   
  return(retval);
  } 

/************** OPEN HCI SOCKET ******
return 0=fail
       1= OK and sets gpar.hci= socket handle
*************************************/       

int devsock()
  {
  int dd;
  struct sockaddr_hci sa;
  struct hci_filter flt;

         // AF_BLUETOOTH=31
  dd = socket(31, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_HCI);

  if(dd < 0)
    {
    printf("Socket open error\n");
    return(0);
    }
     
          //  same as hciconfig hci0 up  
  if(ioctl(dd,HCIDEVUP,0) < 0)   // 0=hci0
    {
    if(errno != EALREADY)
      {
      printf("hci0 not up\n");
      close(dd);
      return(0);
      }
    }
   
  sa.hci_family = 31;   // AF_BLUETOOTH;
  sa.hci_dev = 0;
  sa.hci_channel = 0;    
  if(bind(dd,(struct sockaddr *)&sa,sizeof(sa)) < 0)
    {
    printf("Bind failed\n");
    close(dd);
    return(0);
    }
      
  flt.type_mask = 0x14;  
  flt.event_mask[0] = 0xFFFFFFFF;
  flt.event_mask[1] = 0xFFFFFFFF;
  flt.opcode = 0;
  /** same as ****
  hci_filter_clear(&flt);
  hci_filter_set_ptype(HCI_EVENT_PKT, &flt);
  hci_filter_set_ptype(HCI_ACLDATA_PKT, &flt);
  hci_filter_all_events(&flt);
  ***************/
        
  if(setsockopt(dd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0)
    {
    printf("HCI filter setup failed\n");
    close(dd);
    return(0);
    }   
 
  gpar.hci = dd;    
       
  printf("HCI Socket OK\n");
   
  return(1);
  }  

/******** HEX DUMP - print buf in hex format, length = len ********/

void hexdump(unsigned char *buf, int len)
  {
  int i,i0,n;

  if(len <= 0)
    {
    printf("No data\n");
    return;
    }
     
  i = 0;
  do
    {
    i0 = i;
    n = 0;
    printf("  %04X  ",i0);
    do
      {
      if(n == 8)
        printf("- ");
      printf("%02X ",buf[i]);
      ++n;
      ++i;
      }
    while(n < 16 && i < len);
    printf("\n");
    }
  while(i < len);  
  }

/*************** USER INTERFACE *******************/

void printhelp()
  {
  printf("  h = print this help\n");
  printf("  i = print all device info\n");
  printf("  c = connect a device\n");
  printf("  w = write characteristic\n");
  printf("  s = start motor\n");
  printf("  v = change motor speed\n");
  printf("  x = stop motor\n");
  printf("  d = disconnect a device\n");
  printf("  q = quit\n");
  } 

int inputdev()
  {
  int n;
     
  for(n = 0 ; n < NUMDEVICES ; ++n)
    printf("  DEVICE %d = %s\n",n,device[n].name);
  do
    {
    printf("Enter device number 0-%d: ",NUMDEVICES-1);
    n = inputint();   
    }
  while(n < 0 || n >= NUMDEVICES);

  return(n);
  }
  
int inputctic(int ndevice)
  {
  int n,nx;
    
  for(n = 0 ; n < NUMCTICS && device[ndevice].ctic[n].size > 0 ; ++n)
    {
    nx = n;
    printf("  CHARACTERISTIC %d = %s\n",n,device[ndevice].ctic[n].name);
    }
  do
    {
    printf("Enter characteristic number 0-%d :",nx);
    n = inputint();
    }
  while(n < 0 || n > nx);
  return(n);
  } 
  
int inputval()
  {
  int n;
  
  do
    {
    printf("Enter value 0-255: ");
    n = inputint();
    }
  while(n < 0 || n > 255);
  return(n);
  }  
  
int inputdirn()
  {
  int n;
  
  do
    {
    printf("Enter direction 0/1: ");
    n = inputint();
    }
  while(n < 0 || n > 1);
  return(n);
  }      
  
int inputspeed()
  {
  int n;
  
  do
    {
    printf("Enter speed 0-16: ");
    n = inputint();
    }
  while(n < 0 || n > 16);
  return(n);
  }      
  
int inputint()
  {
  int n,flag;
  char s[64];
 
  do
    { 
    scanf("%s",s);
    flag = 0;
    for(n = 0 ; s[n] != 0 ; ++n)
      {
      if(s[n] < '0' || s[n] > '9')
        flag = 1;
      }
    if(flag == 0)
      n = atoi(s);
    }
  while(flag != 0);
  return(n);
  } 


The board addresses of LE devices must be incorporated into the C source code. For the example board address BTA=112233445566, modify the btle.c code as follows:

struct devicedata device[NUMDEVICES] = {
{ 0,. . . "Your choice of name",{0x11,0x22,0x33,0x44,0x55,0x66}. . .

Compile with: gcc btle.c -o btle

Running btle presents a rudimentary user interface, which should be self-explanatory. To start, connect and write a characteristic - you should see the results on the terminal. Without the RN4020 script running, the commands will write characteristics OK, but there will be no effect on the GPIO pins. For the motor control functions to work, the script must be running. Use the following commands from the terminal:

Code: Select all

wr     ; run script - nothing will appear on terminal in response
wp     ; stop script 

Or set up the RN4020 to run the script automatically on startup with:

sr,21000000    ; 2=auto advert  1=run script automatically
r,1            ; reboot

To cancel auto run:
sr,20000000    ; turn off auto script run
r,1            ; reboot
If you are using a Pictail, the LEDs indicate pin activity:

BLUE = Enable (ON=disabled OFF=enabled, motor running)
ORANGE = Direction
YELLOW = PWM signal

The C code allows many devices to be connected and controlled simultaneously.

DOCUMENTATION

Bluetooth specification

https://www.bluetooth.com/specification ... ification/

HCI/ACL string formats Vol 2 Part E - 5.4
HCI commands (starting 01) and events Vol 2 Part E - 7
LE connect command Vol 2 Part E - 7.8.12
HCI events (replies starting 04) Vol 2 Part E - 7.7
LE connection complete reply Vol 2 Part E - 7.7.65.1
Disconnect command Vol 2 Part E - 7.1.6
ATT/ACL commands/replies (starting 02) Vol 3 Part F - 3.4
Write characteristic command Vol 3 Part F - 3.4.5.3
Write response Vol 3 Part F - 3.4.5.2

Microchip RN4020

user's guide
http://ww1.microchip.com/downloads/en/D ... 05191B.pdf
datasheet
http://ww1.microchip.com/downloads/en/D ... 02279B.pdf

Return to “Automation, sensing and robotics”