Search Microcontrollers

Showing posts with label Radio. Show all posts
Showing posts with label Radio. Show all posts

Friday, July 31, 2015

Internet of Things, sub 1GHz radio and more

If you follow my blog, you probably realized that I am a supporter of technology company that engage in quality education activities.

Texas Instruments is one of them, so I used some of their products to learn and to share my experiments in this blog.

Not long ago (July 16th 2015) TI, together with Element14, offered an interesting free webminar with the captivating title : "From Start to Finish: Creating a Multi-Node Cloud-Connected Sensor Network with Texas Instruments LaunchPad Development Kits"

I know, now you are expecting a "...but...".
Nope, no "but", it was simply great, I strongly suggest you check it :)

IoT is becoming easier and easier, both from software and hardware point of view.
Software is kind of my thing, so, that was never really a main issue for me, or better it was an issue I knew how to deal with, but hardware used to be either hard or expensive (or both).
Gone are those days and there are plenty of products (from various vendors) that really speed up your prototyping.

TI definitely supprots them with great learning resources, this webinar, presented by Adrian Fernandez , TI Microcontroller Development Experience Manager was one of them.

You can see his video here:


Now, the IoT part is cool, but combining it with sub1GHz radio is even better (and as Adrian shows, quite easy).

So, IoT means you have some device that can communicate with the internet, usually acting as a node connected to an ADSL router.
If you have 10 sensors spread around your house, you can definitely have 10 nodes, all connecting to your router and doing their stuff.
It works, possibly, but it's not the best solution.
Back in the days we used to add a RS485 interface, run a few cables here and there and generate a wired network of sensors to the "managing" node, the one that eventually communicates with the internet.
Also works.
Then the nRF24L01 radio came out, working at 2.4GHz.
That one is cool, low cost, not too difficult to use.

Can we do even better?
Turns out we can.
The TI CC110L chip allows Radio communication at 433MHz (and other sub 1GHz frequencies), for wireless connectivity in a low power package and low frequencies give you more "bang" for your milliAmps.
TI provided a boosterpack for the launchpads featuring this chip, that makes it easier to implement a prototype, particularly because being part of their standard ecosystem, it comes with software libraries.
This is the boosterpack FedEX delivered few minutes ago :


The Kit gives you two CC110 (@433MHz) nodes on a shield.
Now, why did I receive the boosterpack?

Well, in my personal opinion this story redefines the concept of "cool".
So, I joined this free webinar, which was extremely informative and fun, extremely "hands-on".
That, in my book, is "cool".
TI delivers free software tools and rather cheap hardware to play with and practice what you learnt, that's also cool, no?

But what goes behind the concept of cool is that I got the boosterpack, plus a MSP430FR5969 launchpad AND a CC3200 Wi-Fi launchpad FOR FREE, delivered from Texas to the old continent.

Apparently I got lucky (not sure how many winners were there), but simply answering a very short quiz after the webinar I won all those toys.
It's Christmas in July! :)

I am currently extremely busy with a couple of projects, but as soon as I can spare some time (hopefully really soon), I plan to test the new toys in an industrial automation project, collecting production data from machines and pushing them to the web.
I am currently using a BBB for that, but there is a case for a different architecture, possibly on top of the existing one.

Until then, well, thanks Adrian and TI!



Monday, April 6, 2015

Stellaris Launchpad - NRF24L01 radio - Part 2

Ok, I had this one in draft for long enough now.
I still need to figure out a few things, so a part 3 will be needed in the future, also I initially had issues with Dynamic Payloads as I forgot to issue the FEATURE = EN_DPL command, hopefully shoudl work better now.


In a previous post we saw some basic concepts and functionality of the NRF24L01 chip.

We managed to establish a SPI communication between the Stellaris and the module, but we did not perform any data transmission across two nodes.
to achieve that we need to add a few functions to our library and decide some basic defaults for it.

I think this automatic ACK system (if you don't know what I am talking about, you really need to read my previous post) is pretty cool and I think it would be fair to use it by default, so the library will assume we are going to use it, should we have any issues with it later on, we might eventually change approach.

While supporting multiple RX pipes is indeed interesting, so I would rather keep that functionality in the library, we can assume that, by default, communication will be through pipe0.

The function nrf_enablePipes accepts a byte (bits 0:5 represent the 6 pipes) does 3 things :

1) Enables the selected pipes
2) Activates the AUTO ACK magic for them
3) configures them for dynamic payload

void nrf_enablePipes(unsigned char pipes)
{
  nrf_writeReg(EN_AA, pipes);     
         // enable AUTO ACK on pipes
  nrf_writeReg(EN_RXADDR, pipes); // enable pipes
  nrf_writeReg( FEATURE, 1<<EN_DPL ); //enable DPL
  nrf_writeReg(DYNPD, pipes); // dynamic payload for pipes
}

About dynamic payload (DPL), the Datasheet says :

(@Nordic)


Another default I picked is to use 5 byte addressing.

Now we need to be able to set the TX address and the various RX addresses for the RX pipes.

 /*
#define RX_ADDR_P0  0x0A
#define RX_ADDR_P1  0x0B
#define RX_ADDR_P2  0x0C
#define RX_ADDR_P3  0x0D
#define RX_ADDR_P4  0x0E
#define RX_ADDR_P5  0x0F
pipe : 0..5
 */
void nrf_setRXAddress(unsigned char pipe, unsigned char *addr)
{
 nrf_writeRegMulti(RX_ADDR_P0+pipe, addr, 5);
}

void nrf_setTXAddress(unsigned char *addr)
{
 nrf_writeRegMulti(TX_ADDR, addr, 5);
}

the writeRegMulti function works like the writeReg, but accepts a buffer and sends n bytes instead of a single one, we need it for setting the addresses since they are 5 bytes long.

As we discussed in the previous post, a node can be set up as PRX or PTX, this is done setting the lowest bit in the CONFIG register : 1 means PRX, 0 means PTX.
The same register is used to specifcy if a CRC is used, int hat case wether is it 1 or 2 byte long,m plus there are 3 bits used to mask the interrupts.

You can find more info on the CONFIG register here.

/**
 * Set crc = 0 to disable crc, 1 for 1 byte ,2 for 2 bytes
 */
void nrf_config(unsigned char maskRXirq, 
                unsigned char maskTXirq,
                unsigned char maskMAXirq,
unsigned char crc,
                unsigned char pwUp,
                unsigned char prx)
{
   unsigned char val = maskRXirq<<6 | 
                       maskTXirq<<5 | 
                       maskMAXirq <<4;
   if (crc>0) val |= 8; else crc--;
   val |= crc <<2 | pwUp<<1 | prx;
   nrf_writeReg(CONFIG,val);
}

When we need to initiate a transmission, we need to set up the node as PTX, to set the TX address to the address of the receiving module, plus the Pipe0 address equal to the TX one.

Additionally we might want to configure the number of transmission attempts (if Auto ACK fails) and the delay between them

/**
 * delay : 0 = 250us, 1=500us ... 15=4000us
 * count : 0..15 auto retransmit if AA fails
 */
void nrf_setRetransmit(unsigned char count, 
                       unsigned char rDelay)
{
 unsigned char val = ((rDelay&0x0f)<<4) | (count&0x0f);
 nrf_writeReg(SETUP_RETR,val);
}

In our experiment we will use node 1 with address "node1" (in ASCII) and node 2 with address "node2" and we will try to have them communicate  @2MBps on the channel 0x10.
Node 1 will try to send a payload containing the data "TEST1234" every 2 seconds and we will use the RGB leds on the two Launchpads to provide some feedback.

For this initial test we will do something simple, however I believe we still need to use two pipes.
The reason for this is that when the PTX sends data, it has to configure the RX pipe0 with the same address of the receiver, therefore it needs to be able to listen on a different pipe, with a unique address, in order to be contacted when it switches back to PRX.

Initially I was thinking to have one of the two nodes running on Arduino, to use a know library such as Mirf to minimize the debug, unfortunately I am not really sure if Mirf allows to be configured properly and if that is the case, I don't know how, while with my library at least I know all the settings.

Ok, I stopped here because I got lost with the DPL feature, as stated at the beginning, should work now, need to experiment a bit more (been doing other stuff for quite some time) and then hopefully a final Part 3 will be posted :) 

Saturday, April 26, 2014

Stellaris Launchpad - NRF24L01 radio - Part 1

I have a couple of digital radio transceivers hanging around my desk since quite some time, so I decided to finally try them out.

These are cheap Nortel NRF24L01 devices, it is common to find them in small breakout modules that communicate via SPI protocol.


Some have an 8 pin connection, some others a 10 pins one, they are just the same in fact since the 10 pins one have 2 VCC and 2 GND pins, normally pins are marked on the silkscreen.

WARNING : The chip works at 3.3V (VCC) which is fine with the Stellaris, but should you use it with an arduino or another 5V MCU make sure you provide a proper VCC. The digital signals are also at 3.3V, but they are 5V tolerant.

There are popular libraries for Arduino / AVR for them and even a Stellaris library for the Energia environment  ( Arduino-like for the Stellaris Launchpad).

I actually prefer to use them in my common environment, plain CCSV5 with driverlib support, so I decided to write my own module.

You can find the datasheet of the module here (Sparkfun site, they sell those little boards too).

I reverse engineered code snippets found on the web, from the various libraries and managed to write my own code.

First off, since we are dealing with a SPI connection we are going to have  a SCLK, MISO , MOSI connection, plus a CE (Chip Enable), a CSN and an IRQ generated by the module itself.
So, it is a sort of " rich" SPI interface.
While the first three (MISO, MOSI and  SCLK) are managed directly by the SSI module of the Cortex M4F, we still need to manage manually the other three signals using 3 GPIO conections, two outputs and 1 input for the IRQ.

I decided to use SPI on port SSI1 (uses pins D.0, D.2, D.3) and the GPIO port E to drive CE, CSN and IRQ -E.1, E.2, E.3- (I saw a library on the web using this setup and found it quite smart, since the connections are all aligned on the launchpad connector).
I stored these values in variables, just to allow for some flexibility in configuration

unsigned long NRF_GPIO = GPIO_PORTE_BASE;
unsigned long NRF_PERIPH_GPIO = SYSCTL_PERIPH_GPIOE;
unsigned char CEPIN    = GPIO_PIN_1;
unsigned char CSNPIN   = GPIO_PIN_2;
unsigned char IRQPIN   = GPIO_PIN_3;

Then I created an init function to enable the needed ports, set up the SPI etc :

void setSPI()
{
  unsigned long dummy = 0;
  SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI1);
  SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD); // SPI
  SysCtlPeripheralEnable(NRF_PERIPH_GPIO); // CS, CE, IRQ
  GPIOPinConfigure(GPIO_PD0_SSI1CLK);
  GPIOPinConfigure(GPIO_PD1_SSI1FSS);
  GPIOPinConfigure(GPIO_PD2_SSI1RX);
  GPIOPinConfigure(GPIO_PD3_SSI1TX);
  GPIOPinTypeSSI(GPIO_PORTD_BASE, GPIO_PIN_3 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0);
  SSIConfigSetExpClk(SSI1_BASE, SysCtlClockGet(),
                 SSI_FRF_MOTO_MODE_0,
         SSI_MODE_MASTER, 1000000, 8);
  SSIEnable(SSI1_BASE);
  GPIOPinTypeGPIOOutput(NRF_GPIO, CEPIN| CSNPIN);
  GPIOPinTypeGPIOInput(NRF_GPIO, IRQPIN);
  while(SSIDataGetNonBlocking(SSI1_BASE, &dummy))
   {}
}

The while cycle at the end is there just to remove from the FIFO whatever garbage data might be eventually present.

The NRF24L01 chip is quite versatile, can operate on various channels and can easily manage communication on a network of addressable devices.
To allow this flexibility, the chip is configured with a set of registers, so the first thing we need to implement are the functions to read from and write to these registers, plus the CE / CSN handling.

The process is quite simple :
Each register has a 5 bit address (documented in the data sheet, but since I am lazy I just imported an existing .h file that defined them all -you can find it here-) and if we combine these 5 bits (r rrrr) with 001 in the 3 MSBs (001r rrrr) then we have a 1 byte command that, when delivered via SPI, tells the chip we want to write the register r rrrr.
The following byte is the value we want to store in the specified register.
Reading is similar , but we will add 000 in the 3 MSBs instead, so the command for reading r rrrr is 000r rrrr .
Details about reading values will be discussed later.

However, for the chip to respond we need to properly manage the CE and CSN signals.

The chip is enabled when CE is asserted LOW and when there is no communication from the master, CSN should be HIGH, so this is our initial setting.
Then, before sending data via SPI, CSN must be transitioned to LOW and finally back HIGH once the communication (writes + reads) is terminated.

void setCE(unsigned char val)
{
  if (val>0)
 GPIOPinWrite(NRF_GPIO, CEPIN ,CEPIN); //CE HIGH
  else
GPIOPinWrite(NRF_GPIO, CEPIN ,0); //CE LOW
}

void setCSN(unsigned char val)
{
  if (val>0)
GPIOPinWrite(NRF_GPIO, CSNPIN ,CSNPIN); //CSN HIGH
 else
  GPIOPinWrite(NRF_GPIO, CSNPIN,0); //CSN LOW
}

The two setCxx functions are quite obvious, not particularly elegant I have to admit, but they get the job done.

void sendChar(unsigned char ch)
{
  SSIDataPut(SSI1_BASE, ch);
  while(SSIBusy(SSI1_BASE)) {}
}

sendChar is a helper function that outputs a byte to the SPI port, I added a while loop to ensure data is flushed out before leaving the function, I am not 100% sure we need that, but it helped me when debugging the line with my DSO.

void _writeReg(unsigned char addr, unsigned char val)
{
  setCSN(0);
  sendChar(W_REGISTER | addr);
  sendChar(val);
  setCSN(1);
}

unsigned long _readReg(unsigned char addr)
{
  unsigned long result = 0xff;
  setCSN(0);
  sendChar(R_REGISTER | addr);
  while(SSIDataGetNonBlocking(SSI1_BASE, &result)){}
  sendChar(0xf0); //whatever value, used 0xf0 because 
      // it is easily visible with the oscilloscope
  SSIDataGet(SSI1_BASE, &result);
  setCSN(1);
  return result;
}

_writeReg is quite a straightforward implementation of what I described before (W_REGISTER = 0010 0000) while _readReg is a bit trickier :
First thing notice the while loop used to throw away data right after the read register command.
This is needed (you can check it with an oscilloscope on the RX (MISO) line of the MCU) because as soon as you start sending data on the TX channel (MOSI), the slave device writes garbage data on the RX (typically a 0x0E value I found in my experiments).

After you sent the command, then, you need to send out a dummy byte (it does not matter the value) per each byte you need to read -in this case 1- and finally read the value and bring CSN high.



[You can see in yellow the MOSI line, roughly between the two purple vertical cursors there is the first byte. In red the MISO line is immediately answering with "garbage" data which I suspect is in fact the status of the module.
It is easy to recognize the second dummy byte being 0xF0 and then we get a (correct) response 0x03 on the MISO]

I then found a sequence of "default" values of the registers used to init the module :

void nrfInit()
{
  unsigned long dummy =0;
  _writeReg(CONFIG, 0x00);  // Deep power-down, everything disabled
  _writeReg(EN_AA, 0x03);
  _writeReg(EN_RXADDR, 0x03);
  _writeReg(RF_SETUP, 0x00);
  _writeReg(STATUS, ENRF24_IRQ_MASK);  // Clear all IRQs
  _writeReg(DYNPD, 0x03);
  _writeReg(FEATURE, EN_DPL);  // Dynamic payloads enabled by default
  SysCtlDelay(SysCtlClockGet() / 100 / 3); // grace time, never hurts 
  while(SSIDataGetNonBlocking(SSI1_BASE, &dummy)) {}
}

So, the init overall sequence is :
setSPI();
setCE(0);
setCSN(1);
nrfInit();

I also found that a way to check if the connection with the module is working is to check the value of the SETUP_AW register, which should return 6 MSBs = 0 and the two LSB with 01 (3byte address) ,10 (4 bytes) or 11 (5 bytes) while 00 means illegal number.


int _isAlive()
{
  unsigned long aw;
  aw = _readReg(SETUP_AW);
  return ((aw & 0xFC) == 0x00 && (aw & 0x03) != 0x00);
}

At this point, we can check if the basic communication via SPI works :

#include <inc/hw_memmap.h>
#include <inc/hw_types.h>
#include <driverlib/gpio.h>
#include <driverlib/sysctl.h>
#include <driverlib/uart.h>
#include <inc/hw_timer.h>
#include <driverlib/timer.h>
#include <driverlib/pin_map.h>
#include <utils/uartstdio.c>
#include "driverlib/ssi.h"
#include "nRF24L01.h"

void setClock()
{
   SysCtlClockSet(  SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL |
        SYSCTL_XTAL_16MHZ  | SYSCTL_OSC_MAIN);
}

....

int main(void) {
 setClock();
 // we  use the led to provide feedback
  SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
  GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_3);
  GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_3, 0); // LED OFF

 setSPI();
 enableNRF();
 SysCtlDelay(SysCtlClockGet() / 10 / 3);
 nrfInit();
 if (_isAlive()!=0)
  GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3, GPIO_PIN_3); // GREEN
 else
  GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3, 0); // OFF
} // main

Ok, so far we implemented the basic communication with the module, now we need to investigate how the radio data transmission happens.

Reading the Nortel datasheet shows some complexity and also allows us to understand that these devices can be operated in quite a few different ways.

Some basic concepts : 
In a network, at a given time, we identify one PTX and one  PRX, these stand for Primary Transmitter and Primary Receiver.
Obviously we are dealing with a Transceiver, so this module can send and receive data.

Table 12 in the Datasheet shows the different states we can set the module


(@Nordic)

For the actual data transmission, a mechanism called "Enhanced ShockBurst"(TM) is used, Nordic defines it as "a packet based data link layer".

What this layer does is to take care of the delivery of payloads (strings of data with 0 to 32 bytes content) from a TX to an RX station.

Enhanced ShockBurst has an automatic handling which automated a few basic actions for us, as an example, after a PTX finished sending out a packed of data, it cnverts it automatically to PRX to be ready to receive an ACK packet.
ACK packets can be also generated automatically if the module is configured to do so.
In fact in the init procedure we have a  _writeReg(EN_AA, 0x03); instruction which enable this Auto ACK feature.
If an ack is not received, then the PTX will retry to send the packet a number  of times, automatically.
To prevent that a packet is read twice (duplicating the data) a PID (Packet Identification) is associated to each packet, a CRC is also used to verify if data is correct.

The packet itself is composed by different sections (Preamble, Address, packet control field, payload and CRC), but it is properly assembled by the device itself using an automatic packet assembly functionality.

Similarly a packet is decoded (disassembled) automatically on the RX side.

An important feature,m on the RX side, is the Multiceiver.
In fact each receiver can use up to 6 data pipes, each one responding on a different address.
There is no magic here, one single channel is used at a time, so you cannot stream different payloads to different pipes at the same time, this functionality is there mainly to allow you to logically separate data streams, for whatever need you might have.

Addresses can be 3, 4 or 5 bytes (can be configured via the SETUP_AW register), so, assuming we are using 5 byte addresses some rules apply: 

1) the lower by is unique across all the pipes
2)  pipes 1 to 5 have the same 4 high bytes

When using the multiceiver feature, the PRX can receive packets from multiple PTXs (on different pipes), it stores on each pipe the "return address" of the TX and uses it to send automated ACK packets.

Specific registers RX_ADDR_Px are used to specify the RX addresses for the 6 pipes, each of these registers is 5 byte wide.

An example of multiceiver configuration (from the nrf24l01 datasheet) is reported here



(@Nordic)

So, our next step will be to write the functions that set up the channel, the TX and RX Addresses and finally that deliver a payload.
While I work on that, you might as well go through the datasheet.

[to be continued]