Blink Example using Timer on a Tiva Launchpad

Working with the powerful Tiva Launchpads using Energia is awesome! However… things start to get ugly when trying to achieve things not officially supported by the IDE and its libraries, such is the case when trying to use hardware Timers.

Tiva Launchpad’s micro controller offers several 16-32 bit hardware Timers with interesting capabilities. I won’t dig deeply with the use of timers, but I’ll show how to setup a timer to trigger an interrupt to handle led blinking.

The following code contains what we need to setup Timer 1A (these Timer provide two channels A and B, each one of 16 bits and they can be combined if using channel A on 32 bit mode), trigger an interrupt and perform led blinking every one second. The code has been tested on a Tiva Launchpad TM4C1294XL but it’s compatible with the TM4C123GXL board (they have different micro controllers but they are based on the same technology by Texas Instruments, that makes code recyclable).


#include "wiring_private.h"
#include "inc/hw_ints.h"
#include "driverlib/interrupt.h"
#include "driverlib/rom.h"
#include "driverlib/timer.h"
#include "driverlib/sysctl.h"

#define LED RED_LED

volatile uint8_t state = 0;

void setup(){
  pinMode(LED, OUTPUT);   
  
  configureTimer1A();
}

void loop(){
  
}

void configureTimer1A(){
  ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER1); // Enable Timer 1 Clock
  ROM_IntMasterEnable(); // Enable Interrupts
  ROM_TimerConfigure(TIMER1_BASE, TIMER_CFG_PERIODIC); // Configure Timer Operation as Periodic
  
  // Configure Timer Frequency
  // Frequency is given by MasterClock / CustomValue
  // Examples: 120MHz / 120k = 1000 kHz ; 120MHz / 120M = 1 Hz
  ROM_TimerLoadSet(TIMER1_BASE, TIMER_A, 120000000); 
  
  ROM_IntEnable(INT_TIMER1A);  // Enable Timer 1A Interrupt
  ROM_TimerIntEnable(TIMER1_BASE, TIMER_TIMA_TIMEOUT); // Timer 1A Interrupt when Timeout
  ROM_TimerEnable(TIMER1_BASE, TIMER_A); // Start Timer 1A
}

void Timer1AHandler(void){
  //Required to launch next interrupt
  ROM_TimerIntClear(TIMER1_BASE, TIMER_A);
  
  state ^= 0x02; // Toggle led state
  digitalWrite(LED, state); // Blink
}

We’re not done yet. There is one important thing missing… We need the compiler to know that our function ‘Timer1AHandler’ is the function that will handle Timer 1A interrupts. We need to hack a little bit with Energia’s libraries. By default most interrupt handlers are not defined by Energia, the following steps helps us to set up not only a handler for the Timer 1A interrupt but for other timers and components of the micro controller as well.

We need to locate the folder where Energia’s libraries are located. They are on your installation folder. Once on the installation folder we must find the following route: Energia/Java/hardware/lm4f/cores/lm4f/. Energia and Arduino core libraries are found in this folder. We need to open and edit two files: wiring_private.h and startup_gcc.c.

On wiring_private.h we need only to add one line of code between/above/down ToneIntHandler and GPIOIntHandler:


void Timer1AHandler(void); //Custom Timer1A Interrupt Handler

On startup_gcc.c we need to insert one line of code and modify two lines. First, we need to add the function prototype as an attribute (copy and paste the following line where the attributes are found):


__attribute__((weak)) void Timer1AHandler(void) {} //Custom Timer1A Interrupt Handler

Second, we need to modify the default interruption handler for Timer 1A. You’ll need to change two lines of code. You’ll find a section similar to the following inside the array __attribute__ ((section(“.isr_vector”)))
void (* const g_pfnVectors[])(void) = { … };


IntDefaultHandler,                      // Timer 0 subtimer B
IntDefaultHandler,                      // Timer 1 subtimer A
IntDefaultHandler,                      // Timer 1 subtimer B

Change it to become this:


IntDefaultHandler,                      // Timer 0 subtimer B
Timer1AHandler,                      	// Timer 1 subtimer A - Custom Timer 1A Interrupt Handler
IntDefaultHandler,                      // Timer 1 subtimer B

IMPORTANT: You’ll have to make the second step twice! You’ll find a similar part of code which is practically the same, it repeats because of a #ifdef statement. Using the ‘find’ command on a text editor helps.

Setting up the custom interrupt handler was tricky, but it’s really important to understand how to do it. I created a git repository containing the Energia sketch and the two modified files (in the case you got confused on how to modify the files). You can find the repository here.

Posted in Uncategorized | 2 Comments

Designing a Communication Protocol using Arduino’s Serial Library

Sending and receiving multiple bytes of information efficiently between microcontrollers, sensors or computers requires the design of a communication protocol. We have by example TCP and UDP protocols, which are the basis of internet data exchange.

A communication protocol consists of a well designed data pattern. We can’t just send data bytes and hope someone understand them. Some protocols define fixed data length, others more complicated are designed for variable data length. For example, the TCP protocol is defined as:

It becomes clear that if we want to transmit complex data, we have to conform with a protocol or create one. Creating one it’s not complicated, the easiest protocol is just a fixed length data sequence that provides a known header.

Let’s create a protocol to send color information to a microcontroller in RGB format, with the goal of controlling a RGB Led. First we need to understand the Red Green Blue color format, for each component we have a possible value between 0 – 255, meaning that each component has a resolution of 8 bits. So… the RGB format defines a 24 bit color.

Our protocol needs to implement a minimum of 3 bytes (1 byte equals 8 bits) to contain color data in RGB format. We also require a header to identify the protocol, and optional but extremely suggested is the use of a checksum byte as a validation method, this gives us a protocol with a fixed value of 5. Every package we send/receive will have a length of 5 bytes. Validation gives us assurance that the data we receive is not corrupted (sometimes by unknown causes we might have data corruption, for example: a bit value changes by electromagnetic effects in the transmission line, the color might change dramatically by this cause). The following table illustrates the protocol we’re going to use:

protocolTable

We are going to employ two Arduinos for testing. One will have 3 potentiometers, one for each color component (Red, Green, Blue), the other one will have an RGB Led connected to it. They will be connected through their UART pins (Arduino Serial Library employs UART).

Arduinos_RGB

The following code will be downloaded to the first Arduino (the one with the potentiometers):


const uint8_t bufferSize = 5;

uint8_t buffer[bufferSize];

void setup(){
  while(!Serial);
  Serial.begin(115200);
  
  //we define our header byte only once, we're not going to change it
  buffer[0] = 0x7E;
}

void loop(){
  //Read potentiometer values
  int r = analogRead(0);
  int g = analogRead(1);
  int b = analogRead(2);
  
  //analogRead() returns a 10 bit value, we need to scale it to a 8 bit value.
  //10 bit max value is 1023, 8 bit max value is 255. 
  //We have two options:
  //1 - Divide our 10 bit value by 4, to obtain and effective 8 bit value.
  //2 - Bitshift Right by 2, to obtain and effective 8 bit value.
  
  //Dividing is easier to understand, but computes slowly
  buffer[1] = r / 4;
  buffer[2] = g / 4;
  buffer[3] = b / 4;
  
  /*
  //Bitshifting is faster, but a little harder to understand.
  //After the shifting we AND the result because the value is a 16 bit value
  //int is a signed 16 bit value in Arduino 8 bit boards.
  buffer[1] = (r >> 2) & 0xFF;
  buffer[2] = (g >> 2) & 0xFF;
  buffer[3] = (b >> 2) & 0xFF;
  */
  
  buffer[4] = checksum();
  
  //We send all bytes stored in the buffer
  Serial.write(buffer, bufferSize);
  
  delay(100);
}

//We perform a sum of all bytes, except the one that corresponds to the original
//checksum value. After summing we need to AND the result to a byte value.
uint8_t checksum(){
  uint8_t result = 0;
  uint16_t sum = 0;
  
  for(uint8_t i = 0; i < (bufferSize - 1); i++){
    sum += buffer[i];
  }
  result = sum & 0xFF;

  return result;
}

Reading a byte stream (a buffer) is tricky. We need to consider several factors. So we have to design a flowchart for this endeavour:

protocol

The following code will be downloaded to the second Arduino (the RGB Led Controller):


const uint8_t header = 0x7E;
const uint8_t bufferSize = 5;

uint8_t buffer[bufferSize];
uint8_t readCounter;
uint8_t isHeader;

//Flag that helps us restart counter when we first find header byte
uint8_t firstTimeHeader; 

void setup(){
  while(!Serial);
  Serial.begin(115200);
  
  readCounter = 0;
  isHeader = 0;
  firstTimeHeader = 0;
  
  pinMode(11, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(9, OUTPUT);
}

void loop(){
  //Check if there is any data available to read
  if(Serial.available() > 0){
    //read only one byte at a time
    uint8_t c = Serial.read();
    
    //Check if header is found
    if(c == header){
      //We must consider that we may sometimes receive unformatted data, and
      //given the case we must ignore it and restart our reading code.
      //If it's the first time we find the header, we restart readCounter
      //indicating that data is coming.
      //It's possible the header appears again as a data byte. That's why
      //this conditional is implemented, so that we don't restart readCounter
      //and corrupt the data. 
      if(!firstTimeHeader){
        isHeader = 1;
        readCounter = 0;
        firstTimeHeader = 1;
      }
    }
    
    //store received byte, increase readCounter
    buffer[readCounter] = c;
    readCounter++;
    
    //prior overflow, we have to restart readCounter
    if(readCounter >= bufferSize){
      readCounter = 0;
      
      //if header was found
      if(isHeader){
        //get checksum value from buffer's last value, according to defined protocol
        uint8_t checksumValue = buffer[4];
        
        //perform checksum validation, it's optional but really suggested
        if(verifyChecksum(checksumValue)){
          //We'll employ PWM to control each RGB Component in the Led
          analogWrite(11, buffer[1]);
          analogWrite(10, buffer[2]);
          analogWrite(9, buffer[3]);
        }
        
        //restart header flag
        isHeader = 0;
        firstTimeHeader = 0;
      }
    }
  }
}

//This a common checksum validation method
//We perform a sum of all bytes, except the one that corresponds to the original
//checksum value. After summing we need to AND the result to a byte value.
uint8_t verifyChecksum(uint8_t originalResult){
  uint8_t result = 0;
  uint16_t sum = 0;
  
  for(uint8_t i = 0; i < (bufferSize - 1); i++){
    sum += buffer[i];
  }
  result = sum & 0xFF;
  
  if(originalResult == result){
     return 1;
  }else{
     return 0;
  }
}

Test the code, and watch as you move the potentiometers how the RGB Led changes its color!

By creating this protocol, we can now easily send and receive data that corresponds to the RGB Led Color. Let’s say we need to control other things… well, we can modify our protocol to handle more bytes.

Some ideas to explore: Connecting these Arduinos wirelessly using two Xbees. Connecting one Arduino with a Raspberry Pi to transmit analog sensor data (Raspberry doesn’t have ADC pins, modifying this protocol we could send all arduino’s analog pins values in one package!). With some modifications you could employ this protocol to read several messages from Arduino Ide’s serial console and perform actions, you could recycle some of the code to parse strings.

This example doesn’t just apply for communication between Arduinos, it actually applies to communicate with everything! Say for example you want to control the same RGB Led wirelessly, you have several options here: bluetooth, zigbee, wifi. Actually… many of the current Smart Led Bulbs in the market employ a similar protocol (like the one we described here) to manipulate its color, they map the RGB Color Components in bytes and send them in a defined sequence (they send a little more data of course). These product’s manufacturers depend heavily on a communication protocol.

Posted in Uncategorized | 2 Comments

Creating an iOS app to talk Bluetooth 4.0 using a Bluno board

Bluetooth 4.0 (Bluetooth Low Energy, BLE) is quite popular in Internet of Things devices. iOS and OSX have official support for it, embedded in the CoreBluetooth framework. I will not delve into the full specs of Bluetooth 4.0, but I encourage the reader to find more here.

In this post we’re going to create a simple iOS app that communicates to a bluetooth 4.0 development board called Bluno. The following code can also be applied to communicate with other Bluetooth 4.0 development boards (with small code changes). If you want to skip the tutorial you can download the code from here.

The Bluno Board is actually an Arduino Board with a Bluetooth 4.0 IC (TI CC2540). The Bluetooth IC reads and writes data to the Atmega328p (the Arduino Core)through UART. The Bluetooth IC also performs the function of USB-UART in the board, meaning that this IC can also use Arduino Ide’s serial console to send/receive data to the Bluetooth 4.0 Central, a “Serial-BLE Adapter”.

In order to test the app, it’s absolutely required a Bluetooth 4.0 enabled iOS device (iPhone 4S, iPhone5, iPhone5C, iPhone5s, iPhone6, iPhone6+, iPod 5th Generation, iPad Mini 1/2/3, iPad Retina, iPad Air 1/2) and a developer account to download code on it, and of course, a Bluno board (you can get one here).

It’s not complicated to create an iOS app that supports Bluetooth 4.0 communication. CoreBluetooth provides delegate methods that handle a lot of the job. Full documentation for CoreBluetooth can be found here. In order to communicate with a BLE device we need to configure our iOS device as a Central, the following picture illustrate the communication.

First, we’ll create a new Xcode Project, it’s the ideal thing to use the Single-View Template. We’ll be using Objective-C, targeting iPad or iPhone. Xcode 6.2 and iOS 7.1+ is recommended.

Your UI should look something like this:

screenshot3

We need to add the CoreBluetooth framework, and we also need to define the main service and characteristics of the BLE Device we need to connect to. For the Bluno board we have as Service UUID “dfb0”, and as Characteristic UUID “dfb1”, here we can change these values if your using other board than Bluno, refer to the manufacturer information. Remember that services contain characteristics, characteristics are the actual source of data and we can subscribe to receive notifications whenever the value of a characteristic is updated. Add the following code to MasterViewController.h:


#import <CoreBluetooth/CoreBluetooth.h>

#define BLEService @"dfb0"
#define BLECharacteristic @"dfb1"

In the MasterViewController.h we need to create a NSMutableArray that stores a reference of all found BLE Devices, a CBPeripheral to reference the device to connect to, we also need to set this class as a CBCentralManagerDelegate and a CBPeripheralDelegate. The UI requires a tableview to display BLE Devices nearby, one textfield and a button to send data to the Bluno, and one UILabel to display received data. The UITableView needs that our class implements UITableViewDataSource and UITableViewDelegate methods. Don’t forget to add the IBAction sendData method in MasterViewController.m


@interface MasterViewController : UIViewController<UITableViewDataSource, UITableViewDelegate, CBCentralManagerDelegate, CBPeripheralDelegate, UITextFieldDelegate>{
    CBCentralManager *manager;
    CBPeripheral *mainPeripheral;
    CBCharacteristic *mainCharacteristic;
    
    NSMutableArray *peripherals;
}

@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet UITextField *dataField;
@property (weak, nonatomic) IBOutlet UILabel *receiveText;

- (IBAction)sendData:(id)sender;

We need to initialise the CBCentralManager to handle BLE connections. Modify the viewDidLoad function, and make it match the following code:


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    //init CBCentralManager and its delegate
    manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
    
    peripherals = [[NSMutableArray alloc] init];
}

We’ll implement the CBCentralManagerDelegate methods in order to start scanning for BLE Devices. Add the following lines of code to MasterViewController.m:


#pragma mark CBCentralManagerDelegate

//Every time we successfully connect to a peripheral this function will be called
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    NSLog(@"Connected to %@", peripheral.name);
}

//This function is invoked after a connected device is disconnected, we also remove reference of its delegate
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"%@ disconnected...", peripheral.name);
    [peripheral setDelegate:nil];
}

//If any error ocurrs it will be notified in this function
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"%@", error);
}

//Add any discovered peripheral to the peripherals array
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
    if(![peripherals containsObject:peripheral]){
        [peripherals addObject:peripheral];
    }
    
    [self.tableView reloadData];
}

//The manager detects the bluetooth state from the iOS device and notifies
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    char* managerStrings[] = {
        "Unknown", "Resetting", "Unsupported",
        "Unauthorized", "PoweredOff", "PoweredOn"
    };
    
    NSString *auxString = [NSString stringWithFormat:@"Manager State: %s", managerStrings[central.state]];
    NSLog(@"%@", auxString);
}

Add/modify the following UITableView methods:


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [peripherals count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

    //We set the cell title according to the peripheral's name
    CBPeripheral *peripheral = [peripherals objectAtIndex:indexPath.row];
    cell.textLabel.text = peripheral.name;
    
    return cell;
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    CBPeripheral *peripheral = [peripherals objectAtIndex:indexPath.row];
    
    [manager connectPeripheral:peripheral options:nil];
}

Up to this point we have declared all necessary methods to handle the connection of a BLE Device, but we haven't added something really important: the device scanning. Apple suggests to scan only when necessary, to avoid CPU utilisation and battery draining. So, we will only scan when the user requests and for a short period of time. In this case, we will scan for 2 seconds when the user presses a button. Add the following code to the viewDidLoad function:


UIBarButtonItem *scanButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(scanBLEDevices:)];
self.navigationItem.rightBarButtonItem = scanButton;

Now we need to create the scanBLEDevices function and a stop scanning function, we employ a NSTimer to help us:


- (void)scanBLEDevices:(id)sender {
    //we will search for devices that contain the service that our device is programmed to have
    [manager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:BLEService]] options:nil];
    
    //we are going to trigger a NSTimer to stop scanning after 2 seconds
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(stopScan:) userInfo:nil repeats:NO];
}

- (void) stopScan:(id)sender{
    NSTimer *timer = (NSTimer *)sender;
    [timer invalidate];
    
    [manager stopScan];
}

If we run our code right now, we'll have our app capable of scanning and displaying nearby Bluno boards, and if we click on a table view cell we'll be able to connect to the BLE Device, if connected the console will notify us.

Good enough, now it's time to start discovering services and characteristics of the BLE Device. We need to implement CBPeripheralDelegate methods in order to do this.


#pragma mark CBPeripheral Delegate

- (void) peripheral:(CBPeripheral *)aPeripheral didDiscoverServices:(NSError *)error{
    for (CBService *aService in aPeripheral.services){
        NSLog(@"Service found with UUID: %@", aService.UUID);
        
        /* Device Information Service */
        if ([aService.UUID isEqual:[CBUUID UUIDWithString:@"180A"]]){
            [aPeripheral discoverCharacteristics:nil forService:aService];
        }
        
        /* GAP (Generic Access Profile) for Device Name */
        if ([aService.UUID isEqual:[CBUUID UUIDWithString:CBUUIDGenericAccessProfileString]]){
            [aPeripheral discoverCharacteristics:nil forService:aService];
        }
        
        /* Bluno Service */
        if([aService.UUID isEqual:[CBUUID UUIDWithString:BLEService]]){
            [aPeripheral discoverCharacteristics:nil forService:aService];
        }
    }
}

- (void) peripheral:(CBPeripheral *)aPeripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
    
    if([service.UUID isEqual:[CBUUID UUIDWithString:CBUUIDGenericAccessProfileString]] ){
        for(CBCharacteristic *aChar in service.characteristics){
            /* Read device name */
            if([aChar.UUID isEqual:[CBUUID UUIDWithString:CBUUIDDeviceNameString]]){
                [aPeripheral readValueForCharacteristic:aChar];
                NSLog(@"Found a Device Name Characteristic");
            }
        }
    }
    if([service.UUID isEqual:[CBUUID UUIDWithString:@"180A"]]){
        for(CBCharacteristic *aChar in service.characteristics){
            /* Read manufacturer name */
            if([aChar.UUID isEqual:[CBUUID UUIDWithString:@"2A29"]]){
                [aPeripheral readValueForCharacteristic:aChar];
                NSLog(@"Found a Device Manufacturer Name Characteristic");
            }else if([aChar.UUID isEqual:[CBUUID UUIDWithString:@"2A23"]]){
                [aPeripheral readValueForCharacteristic:aChar];
                NSLog(@"Found System ID");
            }
        }
    }
    
    if ([service.UUID isEqual:[CBUUID UUIDWithString:BLEService]]){
        for (CBCharacteristic *aChar in service.characteristics){
            /* Read DATA Characteristic */
            if ([aChar.UUID isEqual:[CBUUID UUIDWithString:BLECharacteristic]]){
                //we'll save the reference, we need it to write data
                mainCharacteristic = aChar;
                //Set Notify is extremely useful to read incoming data asynchronously
                [aPeripheral setNotifyValue:YES forCharacteristic:aChar];
                NSLog(@"Found Bluno DATA Characteristic");
            }
        }
    }
}

- (void) peripheral:(CBPeripheral *)aPeripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    /* Value for device Name received */
    if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:CBUUIDDeviceNameString]]){
        NSString * deviceName = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
        NSLog(@"Device Name = %@", deviceName);
    }
    /* Value for manufacturer name received */
    else if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2A29"]]){
        NSString *manufacturer = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
        NSLog(@"Manufacturer Name = %@", manufacturer);
    }
    /* Value for manufacturer name received */
    else if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2A23"]]){
        NSString *systemID = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
        NSLog(@"System ID = %@", systemID);
    }
    /* Data received */
    else if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:BLECharacteristic]]){
        NSString *data = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
        NSLog(@"Received Data = %@", data);

        [_receiveText setText:data];
    }
}

Before going further it's important to note that upon discovering a service, we need to discover its characteristics. A characteristic holds a determined value, sometimes it's fixed, like a manufacturer name, but other times it's continually changing (extremely useful for getting sensor data). In the first cases we need to read the characteristic's value once, but for the second cases we need to subscribe to it by using "notifications" (not the ones we are used to), and every time the value is updated the corresponding delegate method will execute, and that's where we have to code and read the updated data.

Before testing the code we need to set the peripheral to respond to the delegate of MasterViewController. So we need to set this just after we succeeded connected to the BLE Device. We need to modify the following method:


//Every time we successfully connect to a peripheral this function will be called
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    NSLog(@"Connected to %@", peripheral.name);
    
    //we'll save the reference, we'll use it when writing data
    mainPeripheral = peripheral;
    
    //set delegate and discover all available services
    [peripheral setDelegate:self];
    [peripheral discoverServices:nil];
}

It's time to test the code so far. We're now able to explore some of the services and characteristics that our BLE Device has. The delegate method didUpdateValueForCharacteristic provides us with a simple way of reading and displaying data sent by the Bluno. Remember that the Bluno Board employs the "dfb1" characteristic to send data transmitted by UART from the Atmega328p in the board. You could bypass programming and only open the Arduino Ide's serial console having the Bluno connected and write text (at a BaudRate of 115200), you'll see this text displayed on the app and the console!

Now we have to send data, it's not really completed. Just put the following code in the IBAction corresponding to the send button.


- (IBAction)sendData:(id)sender{
    //Read data to send, convert it to NSData
    NSString *auxText = [_dataField text];
    NSData *auxData = [auxText dataUsingEncoding:NSUTF8StringEncoding];
    
    //Send the data by using the stored CBCharacteristic and CBPeripheral references
    [mainPeripheral writeValue:auxData forCharacteristic:mainCharacteristic type:CBCharacteristicWriteWithoutResponse];
}

Don't forget to implement UITextFieldDelegate and set the delegate in the UITextField in order to dismiss keyboard!


#pragma mark UITextField Delegate

- (BOOL)textFieldShouldReturn:(UITextField *)aTextField
{
    [aTextField resignFirstResponder];
    return YES;
}

That's it! We are now sending and receiving data from the Bluno! Hopefully you haven't had any problem coming this far. Sometimes the Bluno doesn't display data coming from the iOS device, but you can see the RX (receiving) led flash when sending data from the iOS Device. So we can implement an echo function in the Atmega328p, to send back everything that is sent from the iOS Device.

IMG_1085

If you need the complete iOS code, you can download it from here.

Posted in Uncategorized | Leave a comment