About Crazyflie

The Crazyflie Nano is a small quadcopter that you can purchase from Bitcraze.

http://www.bitcraze.se/

It's great fun to fly!

The mess...

The problem I had with the Crazyflie is that if you want to fly it, you need to install the PC client on a laptop, tether a joystick, have Crazyradio sticking out from a USB port ready to be broken off, set the whole damn lot up, and then go fly.

It's not really a great solution, so I decided that I would write my own remote control software using CrossWorks and off-the-shelf hardware, along with the extensive support we've added in the Platform Library, the Graphics Library, and the Device Library.

Core hardware

I chose an Olimex STM32-103STK as this has an nRF24L01 built in, the same type of device you need to communicate with the Crazyflie.

http://www.olimex.com/Products/ARM/ST/STM32-103STK/

What's more, it has a built-in LCD, is battery powered, and has a UEXT socket that you can connect a MOD-WII and a Wii Classic Controller to. In short, this is the perfect platform to build a no-nonsense stand-along Crazyflie remote control!

This is what I ended up with:

Olimex have also introduced the pretty-much identical STM32-405STK. This has an updated processor on it, but the increase in compute power doesn't bring anything to the remote control. It's a bit more expensive than the STM32-103STK, but clearly there is an insatiable demand for low-cost powerful processors! Needless to say, the Crazyflie remote software I wrote works on this board too.

http://www.olimex.com/Products/ARM/ST/STM32-405STK/

Setup

In the back of the STM32-103STK you'll need to plug a MOD-WII and a Nintendo Classic Controller. Install a battery and move the EXT/BAT jumper to power the device from the battery. Program the board with the firmware, and get ready to fly!

User interface

The LCD displays the current state of the software. After reset, devices are initialized and the software searches to find the channel to communicate with the Crazyflie. If it can't find it, just reset the board using the RESET button near the battery and try again. Once the Crazyflie is found, the red LED on the faceplate illuminates to show a good link—if the link drops, the LED goes out.

The left thumbstick controls thrust:

The right thumbstick controls pitch and roll:

The buttons control trim:

That's it! Up, up, and away: happy flying!

The code
/* Copyright (c) 2004-2013 Rowley Associates Limited.
*/

#include "libplatform/platform.h"
#include "libplatform/platform_graphics.h"
#include "libugfx/ctl_gfx.h"
#include "libdevice/wii_controller.h"
#include "example_support.h"
#include <cross_studio_io.h>
#include <string.h>

// This will compile "out of the box" on the Olimex STM32-405STK
// and Olimex STM32-103STK.

#if defined(Olimex_STM32_405STK)

#define NRF24L01_SS      STM32_405STK_NRF24L01_SS
#define NRF24L01_CE      STM32_405STK_NRF24L01_CE

#elif defined(Olimex_STM32_103STK)

#define NRF24L01_SS      STM32_103STK_NRF24L01_SS
#define NRF24L01_CE      STM32_103STK_NRF24L01_CE

#else

#error unupported platform

#endif

typedef struct {
  unsigned char c;
  float roll;
  float pitch;
  float yaw;
  unsigned short thrust;
} __attribute__((packed)) CRAZYFLIE_COMMANDER_SETPOINT_t;

typedef struct {
  unsigned char c;
  union {
    unsigned char data[32];  // one larger, to insert a null.
  } console;
} __attribute__((packed)) CRAZYFLIE_CONSOLE_t;

// NRF24 variants:
typedef enum {
  NRF24L01,     // nRF24L01
  NRF24L01P,    // nRF24L01+
} NRF24_VARIANT_t;


// NRF24 registers
typedef enum {
  NRF24_CONFIG       = 0x00,
  NRF24_EN_AA,
  NRF24_EN_RXADDR,
  NRF24_SETUP_AW,
  NRF24_SETUP_RETR,
  NRF24_RF_CH,
  NRF24_RF_SETUP,
  NRF24_STATUS,
  NRF24_OBSERVE_TX,  // 0x08
  NRF24_CD,
  NRF24_RX_ADDR_P0,
  NRF24_RX_ADDR_P1,
  NRF24_RX_ADDR_P2,
  NRF24_RX_ADDR_P3,
  NRF24_RX_ADDR_P4,
  NRF24_RX_ADDR_P5,
  NRF24_TX_ADDR,     // 0x10
  NRF24_RX_PW_P0,
  NRF24_RX_PW_P1,
  NRF24_RX_PW_P2,
  NRF24_RX_PW_P3,
  NRF24_RX_PW_P4,
  NRF24_RX_PW_P5,
  NRF24_FIFO_STATUS,
  NRF24_DYNPD        = 0x1c,
  NRF24_FEATURE
} NRF24_REGISTER_t;

// NRF24 CONFIG register bits.
#define NRF24_CONFIG_MASK_RX_DR      0x40
#define NRF24_CONFIG_MASK_TX_DS      0x20     // Asserted when ACK packet received
#define NRF24_CONFIG_MASK_MAX_RT     0x10
#define NRF24_CONFIG_EN_CRC          0x08
#define NRF24_CONFIG_CRCO            0x04
#define NRF24_CONFIG_PWR_UP          0x02
#define NRF24_CONFIG_PRIM_RX         0x01

// NRF24 EN_AA register bits.
#define NRF24_EN_AA_ENAA_P5          0x20
#define NRF24_EN_AA_ENAA_P4          0x10
#define NRF24_EN_AA_ENAA_P3          0x08
#define NRF24_EN_AA_ENAA_P2          0x04
#define NRF24_EN_AA_ENAA_P1          0x02
#define NRF24_EN_AA_ENAA_P0          0x01

// NRF24 EN_AA register bits.
#define NRF24_EN_RXADDR_ERX_P5       0x20
#define NRF24_EN_RXADDR_ERX_P4       0x10
#define NRF24_EN_RXADDR_ERX_P3       0x08
#define NRF24_EN_RXADDR_ERX_P2       0x04
#define NRF24_EN_RXADDR_ERX_P1       0x02
#define NRF24_EN_RXADDR_ERX_P0       0x08

// NRF24 RF_SETUP bits
#define NRF24_RF_SETUP_CONT_WAVE     0x80     // NRF24L01+ only: continuous carrier
#define NRF24_RF_SETUP_RF_DR_LOW     0x20     // low data rate, 250 kHz, need RF_DR_HIGH=0 if set
#define NRF24_RF_SETUP_PLL_LOCK      0x10
#define NRF24_RF_SETUP_RF_DR_HIGH    0x08
#define NRF24_RF_SETUP_RF_PWR        0x02
#define NRF24_RF_SETUP_RF_LNA_HCURR  0x01

// NRF24 data rates
#define NRF24_RF_SETUP_RF_DR_250kbps (1*NRF24_RF_SETUP_RF_DR_LOW + 0*NRF24_RF_SETUP_RF_DR_HIGH)
#define NRF24_RF_SETUP_RF_DR_1Mbps   (0*NRF24_RF_SETUP_RF_DR_LOW + 0*NRF24_RF_SETUP_RF_DR_HIGH)
#define NRF24_RF_SETUP_RF_DR_2Mbps   (0*NRF24_RF_SETUP_RF_DR_LOW + 1*NRF24_RF_SETUP_RF_DR_HIGH)
#define NRF24_RF_SETUP_RF_DR_MASK    (1*NRF24_RF_SETUP_RF_DR_LOW + 1*NRF24_RF_SETUP_RF_DR_HIGH)

// NRF24 STATUS bits
#define NRF24_STATUS_RX_DR           0x40     // Rx data ready interrupt
#define NRF24_STATUS_TX_DS           0x20     // Tx data sent interrupt
#define NRF24_STATUS_MAX_RT          0x10     // Maximum number of transmit interrupt

typedef enum {
  NRF24_CMD_R_REGISTER          = 0x00,
  NRF24_CMD_W_REGISTER          = 0x20,
  NRF24_CMD_RX_PAYLOAD          = 0x61,
  NRF24_CMD_TX_PAYLOAD          = 0xA0,
  NRF24_CMD_FLUSH_TX            = 0xE1,
  NRF24_CMD_FLUSH_RX            = 0xE2,
  NRF24_CMD_TX_PL               = 0xE3,
  NRF24_CMD_ACTIVATE            = 0x50,
  NRF24_CMD_R_RX_PL_WID         = 0x60,
  NRF24_CMD_W_ACK_PAYLOAD       = 0xA8,
  NRF24_CMD_W_TX_PAYLOAD_NO_ACK = 0xB0,
  NRF24_CMD_NOP                 = 0xFF
} NRF24_CMD_t;

typedef struct NRF24_RADIO_s {
  CTL_SPI_DEVICE_t dev;
  void (*write_ce)(struct NRF24_RADIO_s *, int state);

  // Shadow registers.
  unsigned short setup;
  unsigned short retr;

  // Operating mode.
  NRF24_VARIANT_t variant;
  unsigned short ack_payload_length;
} NRF24_RADIO_t;

static const PLATFORM_UEXT_CONFIGURATION_t *uext;
static float trim_pitch;
static float trim_roll;

static void
nrf24_write_register(NRF24_RADIO_t *self, int reg, const void *addr, size_t len)
{
  ctl_spi_select_device(&self->dev);
  ctl_spi_put(&self->dev, NRF24_CMD_W_REGISTER | reg);
  ctl_spi_write(&self->dev, addr, len);
  ctl_spi_deselect_device(&self->dev);
}

static void
nrf24_read_register(NRF24_RADIO_t *self, int reg, void *addr, size_t len)
{
  ctl_spi_select_device(&self->dev);
  ctl_spi_put(&self->dev, NRF24_CMD_R_REGISTER | reg);
  ctl_spi_read(&self->dev, addr, len);
  ctl_spi_deselect_device(&self->dev);
}

static void
nrf24_write_8b_register(NRF24_RADIO_t *self, int reg, int value)
{
  ctl_spi_select_device(&self->dev);
  ctl_spi_put(&self->dev, NRF24_CMD_W_REGISTER | reg);
  ctl_spi_put(&self->dev, value);
  ctl_spi_deselect_device(&self->dev);
}

static CTL_STATUS_t
nrf24_read_8b_register(NRF24_RADIO_t *self, int reg)
{
  CTL_STATUS_t stat;
  ctl_spi_select_device(&self->dev);
  ctl_spi_put(&self->dev, NRF24_CMD_R_REGISTER | reg);
  stat = ctl_spi_get(&self->dev);
  ctl_spi_deselect_device(&self->dev);
  return stat;
}

static void
uext_nrf24_write_cs(CTL_SPI_DEVICE_t *dev, int state)
{
  platform_write_digital_pin(uext->pin10_ssel, state);
}

static void
uext_nrf24_write_ce(NRF24_RADIO_t *self, int state)
{
  platform_write_digital_pin(uext->pin4_rxd, state);
}

static void
builtin_nrf24_write_cs(CTL_SPI_DEVICE_t *dev, int state)
{
  platform_write_digital_pin(NRF24L01_SS, state);
}

static void
builtin_nrf24_write_ce(NRF24_RADIO_t *self, int state)
{
  platform_write_digital_pin(NRF24L01_CE, state);
}

static CTL_STATUS_t
nrf24_cmd(NRF24_RADIO_t *self, unsigned char cmd)
{
  unsigned char status;
  ctl_spi_select_device(&self->dev);
  ctl_spi_exchange(&self->dev, &cmd, &status, 1);
  ctl_spi_deselect_device(&self->dev);
  return status;
}

static CTL_STATUS_t
nrf24_nop(NRF24_RADIO_t *self)
{
  return nrf24_cmd(self, NRF24_CMD_NOP);
}

static CTL_STATUS_t
nrf24_flush_tx(NRF24_RADIO_t *self)
{
  return nrf24_cmd(self, NRF24_CMD_FLUSH_TX);
}

static CTL_STATUS_t
nrf24_flush_rx(NRF24_RADIO_t *self)
{
  return nrf24_cmd(self, NRF24_CMD_FLUSH_RX);
}

static CTL_STATUS_t
nrf24_set_channel(NRF24_RADIO_t *self, int channel)
{
  // Validate channel.
  if (channel < 0 || 125 < channel)
    return CTL_PARAMETER_ERROR;
   
  // Change the channel.
//  self->write_ce(self, 0);
  nrf24_write_8b_register(self, NRF24_RF_CH, channel);
//  self->write_ce(self, 1);

  // All done.
  return CTL_NO_ERROR;
}

static void
nrf24_recompute_retr(NRF24_RADIO_t *self)
{
  int ard = 0;

  // Retry timeouts depend upon link speed and payload length.
  switch (self->setup & NRF24_RF_SETUP_RF_DR_MASK)
    {
    case NRF24_RF_SETUP_RF_DR_2Mbps:
      if (self->ack_payload_length <= 15)      ard = 0;  // 250 us
      else                                     ard = 1;  // 500 us
      break;

    case NRF24_RF_SETUP_RF_DR_1Mbps:
      if (self->ack_payload_length <= 5)       ard = 0;  // 250 us
      else                                     ard = 1;  // 500 us
      break;

    case NRF24_RF_SETUP_RF_DR_250kbps:  // table 18
      if      (self->ack_payload_length <=  0) ard = 1;  //  500 us
      else if (self->ack_payload_length <=  8) ard = 2;  //  750 us
      else if (self->ack_payload_length <= 16) ard = 3;  // 1000 us
      else if (self->ack_payload_length <= 24) ard = 4;  // 1250 us
      else                                     ard = 5;  // 1500 us

    default:
      break;
    }

  // Integrate ARD into shadow register.
  self->retr &= ~(0xf << 4);
  self->retr |=  (ard << 4);

  // Synchronize hardware and shadow registers.
  nrf24_write_8b_register(self, NRF24_SETUP_RETR, self->retr);
}

static CTL_STATUS_t
nrf24_set_data_rate(NRF24_RADIO_t *self, int kHz)
{
  // Clear data rate bits in shadow register.
  self->setup &= ~NRF24_RF_SETUP_RF_DR_MASK;

  // nRF24L01 does not support 250 kHz low data rate...
  if (kHz <= 250 && self->variant == NRF24L01P)
    {
      self->setup |= NRF24_RF_SETUP_RF_DR_250kbps;\
    }
  else if (kHz <= 1000) 
    {
      self->setup |= NRF24_RF_SETUP_RF_DR_1Mbps;
    }
  else
    {
      self->setup |= NRF24_RF_SETUP_RF_DR_2Mbps;
    }

  // Synchronize hardware and shadow registers.
  nrf24_write_8b_register(self, NRF24_RF_SETUP, self->setup);

  // Synchronize RETR.
  nrf24_recompute_retr(self);

  // All done.
  return CTL_NO_ERROR;
}

static CTL_STATUS_t
nrf24_set_auto_retransmit_count(NRF24_RADIO_t *self, int count)
{
  // Clamp.
  if      (count <  0) count = 0;
  else if (count > 15) count = 15;

  // Update.
  self->retr &= ~0xf;
  self->retr |= count;

  // Synchronize hardware and shadow registers.
  nrf24_write_8b_register(self, NRF24_SETUP_RETR, self->retr);

  // All done.
  return CTL_NO_ERROR;
}

static CTL_STATUS_t
nrf24_set_transmit_power(NRF24_RADIO_t *self, int dBm)
{
  // Clear power bits in shadow register.
  self->setup &= ~(3 * NRF24_RF_SETUP_RF_PWR);

  // Encode power in shadow register.
  if      (dBm <= -18) { self->setup |= 0*NRF24_RF_SETUP_RF_PWR; }
  else if (dBm <= -12) { self->setup |= 1*NRF24_RF_SETUP_RF_PWR; }
  else if (dBm <= -6)  { self->setup |= 2*NRF24_RF_SETUP_RF_PWR; }
  else                 { self->setup |= 3*NRF24_RF_SETUP_RF_PWR; }

  // Synchronize hardware and shadow registers.
  nrf24_write_8b_register(self, NRF24_RF_SETUP, self->setup);

  // All done.
  return CTL_NO_ERROR;
}

static CTL_STATUS_t
nrf24_set_ack_payload_length(NRF24_RADIO_t *self, int bytes)
{
  // Update ack payload length.
  self->ack_payload_length = bytes;

  // Update ARD based on payload length and data rate.
  nrf24_recompute_retr(self);
}

static void
nrf24_set_address(NRF24_RADIO_t *self, const unsigned char *address)
{
  int i;

  nrf24_write_register(self, NRF24_TX_ADDR, address, 5);
  for (i = 0; i < 5; ++i)
    nrf24_write_8b_register(self, NRF24_RX_ADDR_P0+i, address[i]);
}

static void
nrf24_pulse_ce(NRF24_RADIO_t *self)
{
  self->write_ce(self, 1);
  platform_spin_delay_us(20);  // From datasheet, must be > 10us
  self->write_ce(self, 0);
}

static void
nrf24_transmit_packet(NRF24_RADIO_t *self,
                      const void *payload, size_t payload_size)
{
  // Write payload to the buffer.
  ctl_spi_select_device(&self->dev);
  ctl_spi_put(&self->dev, NRF24_CMD_TX_PAYLOAD);
  ctl_spi_write(&self->dev, payload, payload_size);
  ctl_spi_deselect_device(&self->dev);
  
  // Pulse CE to start transmit.
  nrf24_pulse_ce(self);
}

static CTL_STATUS_t
nrf24_read_receive_packet(NRF24_RADIO_t *self, void *payload)
{
  CTL_STATUS_t stat;

  // Get the packet length.
  ctl_spi_select_device(&self->dev);
  ctl_spi_put(&self->dev, NRF24_CMD_R_RX_PL_WID);
  stat = ctl_spi_get(&self->dev);
  ctl_spi_deselect_device(&self->dev);
  
  // Validate response.
  if (0 < stat && stat <= 32)
    {
      ctl_spi_select_device(&self->dev);
      ctl_spi_put(&self->dev, NRF24_CMD_RX_PAYLOAD);
      ctl_spi_read(&self->dev, payload, stat);
      ctl_spi_deselect_device(&self->dev);
      return stat;
    }
  else
    return 0;
}

static CTL_STATUS_t
nrf24_wait_for_interrupt_and_acknowledge(NRF24_RADIO_t *self)
{
  unsigned interrupts;
  CTL_STATUS_t stat;

  // Interrupts to wait for.
  interrupts = NRF24_STATUS_RX_DR |
               NRF24_STATUS_TX_DS |
               NRF24_STATUS_MAX_RT;

  // FIXME: add timeout
  for (;;)
    {
      if ((stat = nrf24_nop(self)) & interrupts)
        break;
    }

  // Acknowledge interrupt.
  nrf24_write_8b_register(self, NRF24_STATUS, interrupts & stat);

  // Done.
  return stat;
}

static CTL_STATUS_t
nrf24_send_packet(NRF24_RADIO_t *self,
                  const void *payload, size_t payload_size,
                  void *ack, size_t *ack_size)
{
  CTL_STATUS_t stat;

  // Transmit packet.
  nrf24_transmit_packet(self, payload, payload_size);

  // Wait for interrupt.
  stat = nrf24_wait_for_interrupt_and_acknowledge(self);
  
  // Did we manage to send this packet?  If we took the maximum number of
  // transmits, just drop it.
  if (stat & NRF24_STATUS_MAX_RT)
    {
      nrf24_flush_tx(self);
      return CTL_DEVICE_NOT_RESPONDING;
    }

  // Did we receive an acknowledgment?  If so, accept it.
  if (stat & NRF24_STATUS_RX_DR)
    *ack_size = nrf24_read_receive_packet(self, ack);
  else 
    *ack_size = 0;

  // Done with the acknowledge.  
  nrf24_flush_rx(self);

  // Return "data sent" status.
  return stat & NRF24_STATUS_TX_DS;
}

static int crazyflie_channel;

static void
channel_scan(NRF24_RADIO_t *self)
{
  int separation, channel;
  CTL_STATUS_t stat;
  unsigned char ack[33];
  size_t ack_size;
  CRAZYFLIE_COMMANDER_SETPOINT_t setpoint;

  // Level, no thrust.
  setpoint.c = 3 << 4;
  setpoint.roll = 0;
  setpoint.pitch = 0;
  setpoint.yaw = 0;
  setpoint.thrust = 0;

  // Channel separation is different for 2Mbps link.
  separation = 1;
  if ((self->setup & NRF24_RF_SETUP_RF_DR_MASK) == NRF24_RF_SETUP_RF_DR_2Mbps)
    separation = 2;

  // When scanning, don't use lots of retransmits.
  nrf24_set_auto_retransmit_count(self, 5);

  // Scan channels...
  for (channel = 0; channel <= 125; channel += separation)
    {
      nrf24_set_channel(self, channel);
      stat = nrf24_send_packet(self, &setpoint, sizeof(setpoint), ack, &ack_size);
      if (stat > 0)
        {
//          debug_printf("Response on channel %d\n", channel);
          crazyflie_channel = channel;
          break;
        }
    }
}

static void
app_show_title(const char *text)
{
  const char *space;

  // Select font.
  ctl_gfx_context.current_font = &fixed_6x10;

  // Find delimiter.
  space = strchr(text, ' ');

  // Clear down.
  ctl_gfx_set_pen_color(ctl_gfx_driver->default_background);
  ctl_gfx_fill_rectangle_wh(0, 0, ctl_gfx_screen_width(), ctl_gfx_screen_height());

  // Draw 1st message.
  ctl_gfx_set_text_color(ctl_gfx_driver->default_foreground);
  ctl_gfx_set_pen_color(ctl_gfx_driver->default_foreground);
  ctl_gfx_draw_stringn((ctl_gfx_screen_width() - ctl_gfx_context.current_font->width * (space-text))/2,
                       ctl_gfx_screen_height()/2 - ctl_gfx_context.current_font->height,
                       text,
                       space-text);

  // Draw 2nd message.
  ctl_gfx_draw_string((ctl_gfx_screen_width() - ctl_gfx_context.current_font->width * strlen(space+1))/2,
                      ctl_gfx_screen_height()/2,
                      space+1);

  // Frame.
  ctl_gfx_draw_rectangle_wh(0, 0,
                            ctl_gfx_screen_width(), ctl_gfx_screen_height());
}

int
main(void)
{
  int i;
  CTL_STATUS_t stat;
  CTL_SPI_BUS_t *spi;
  CTL_I2C_BUS_t *bus;
  NRF24_RADIO_t radio;
  char data[5];
  unsigned old_press, new_press;

  // Initialize platform.
  platform_initialize();

  // Configure the built-in LCD.
  example_check_status(platform_configure_builtin_graphics());

  // Turn link LED off.
  platform_write_led(0, 0);

  // Show title and wait for user to read it.
  app_show_title("Crazyflie Remote");
  ctl_delay(2000);

  app_show_title("Initialize Controller");
  ctl_delay(1000);

  // Get UEXT descriptor.
  uext = platform_uext_configuration(0);

  // Configure platform pins for UEXT I2C.
  example_check_status(platform_configure_i2c_bus(uext->i2c_bus_index));

  // Get the I2C bus that is routed to the UEXT connector.
  bus = platform_i2c_bus(uext->i2c_bus_index);

  // Initialize sensor.
  example_check_status(wii_extension_controller_initialize(bus));

  app_show_title("Initialize Radio");
  ctl_delay(1000);

#if 0
  // This code is for a MOD-nRF24L01 on UEXT #0.

  // Get UEXT configuration.
  uext = platform_uext_configuration(0);

  // Configure platform pins for SPI on UEXT socket.
  example_check_status(platform_configure_spi_bus(uext->spi_bus_index));

  // Configure UEXT CS and CE.
  example_check_status(platform_configure_pin(uext->pin10_ssel, PIN_FUNCTION_DIGITAL_OUTPUT | PIN_CLAIM_EXCLUSIVE));  // CS
  example_check_status(platform_configure_pin(uext->pin4_rxd,   PIN_FUNCTION_DIGITAL_OUTPUT | PIN_CLAIM_EXCLUSIVE));  // CE
  platform_write_digital_pin(uext->pin10_ssel, 1);
  platform_write_digital_pin(uext->pin4_rxd, 1);

  // Get the SPI bus routed to the UEXT socket.
  spi = platform_spi_bus(uext->spi_bus_index);

  // Initialize SPI device associated with NRF24L01 and attach it to the bus.
  memset(&dev, 0, sizeof(dev));
  dev.select = uext_write_cs;
  ctl_spi_attach_device(spi, &dev);

#else

  // STM32-103STK has nRF2401 on internal SPI bus.
  example_check_status(platform_configure_spi_bus(1, 0));

  // Configure UEXT CS and CE.
  example_check_status(platform_claim_pin(NRF24L01_SS, PIN_FUNCTION_DIGITAL_OUTPUT | PIN_CLAIM_EXCLUSIVE));  // CS
  example_check_status(platform_claim_pin(NRF24L01_CE, PIN_FUNCTION_DIGITAL_OUTPUT | PIN_CLAIM_EXCLUSIVE));  // CE
  platform_write_digital_pin(NRF24L01_SS, 1);
  platform_write_digital_pin(NRF24L01_CE, 0);

  // Get the SPI bus routed to the UEXT socket.
  spi = platform_spi_bus(1);

  // Initialize SPI device associated with NRF24L01 and attach it to the bus.
  memset(&radio, 0, sizeof(radio));
  radio.variant = NRF24L01;
  radio.dev.select = builtin_nrf24_write_cs;
  radio.write_ce = builtin_nrf24_write_ce;
  ctl_spi_attach_device(spi, &radio.dev);

#endif

  // See if we can read a register.
  ctl_spi_set_protocol(&radio.dev, CTL_SPI_MODE1, 8, 1000000, 0xff);

  // Select device.
  nrf24_read_register(&radio, 0x0a, data, 5);

  // Start radio in PTX mode.  Clear down interrupts.
  nrf24_write_8b_register(&radio, NRF24_CONFIG, NRF24_CONFIG_MASK_RX_DR |
                                                NRF24_CONFIG_MASK_TX_DS |
                                                NRF24_CONFIG_MASK_MAX_RT |
                                                NRF24_CONFIG_EN_CRC |
                                                NRF24_CONFIG_CRCO |
                                                NRF24_CONFIG_PWR_UP);

  // Wait for radio up.
  platform_spin_delay_ms(1);

  // Enable dynamic packet size and ACK payload features.
  nrf24_write_8b_register(&radio, NRF24_FEATURE, 0x06);
  nrf24_write_8b_register(&radio, NRF24_DYNPD, 0x01);

  // Configure fast data rate, high power, 32-byte ACK payload.
  nrf24_set_data_rate(&radio, 2000);
  nrf24_set_transmit_power(&radio, 0);
  nrf24_set_ack_payload_length(&radio, 32);
  nrf24_set_auto_retransmit_count(&radio, 10);

  // Zap any outstanding packets.
  for (i = 0; i < 10; ++i)
    {
      nrf24_flush_tx(&radio);
      nrf24_flush_rx(&radio);
    }

  // Show title and wait for user to read it.
  app_show_title("Scanning... Please wait");
  ctl_delay(1000);

  // Channel scan.
  channel_scan(&radio);

  // If we didn't detect it, drop out.
  if (crazyflie_channel == 0)
    {
      app_show_title("Crazyflie Not Detected");
      ctl_delay(1000);
      example_finish();
    }

  // Control copter.
  app_show_title("Crazyflie Go!");
  ctl_delay(1000);

  // Turn link LED on.
  platform_write_led(0, 1);

  // Send command packets.
  nrf24_set_channel(&radio, crazyflie_channel);

  // Say no trim buttons pressed.
  old_press = 0;

  // Into the blue!
  for (;;)
    {
      CRAZYFLIE_COMMANDER_SETPOINT_t setpoint;
      CRAZYFLIE_CONSOLE_t ack;
      WII_CLASSIC_REPORT_t report;
      unsigned ack_size;
      static int last_stat;

      // Sample.
      stat = wii_classic_sample(bus, &report);

      // If there's an error reading the controller, ignore reading.
      if (stat < CTL_NO_ERROR)
        continue;

      // Figure out if buttons have been pressed.
      new_press = ~old_press & report.buttons_pressed;
      old_press = report.buttons_pressed;

      // Adjust any trim values.
      if (new_press & WII_BUTTON_B)
        trim_pitch -= 1;
      if (new_press & WII_BUTTON_X)
        trim_pitch += 1;
      if (new_press & WII_BUTTON_A)
        trim_roll += 1;
      if (new_press & WII_BUTTON_Y)
        trim_roll -= 1;

      // Setpoint command.
      setpoint.c = 3 << 4;
      setpoint.roll = 0;
      setpoint.pitch = 0;
      setpoint.yaw = 0;

      // Left thumbstick Y controls thrust.
      if (report.left_joystick_y < 0x1f)
        setpoint.thrust = 0;
      else
        setpoint.thrust = (report.left_joystick_y - 0x1f) * 1875;

      // Right thumbstick controls roll and pitch.
      setpoint.pitch = (float)(16 - report.right_joystick_y) / 16 * 50;
      setpoint.roll  = (float)(report.right_joystick_x - 16) / 16 * 50;

      // Apply trims.
      setpoint.pitch += trim_pitch;
      setpoint.roll  += trim_roll;

#if 0
      // ATTENTION -- this is just to see if we're making sense...
      debug_printf("thrust: %6.2f   pitch: %6.2f\n",
                   setpoint.thrust, setpoint.pitch);
#endif

      // Send control packet.
      stat = nrf24_send_packet(&radio,
                               &setpoint, sizeof(setpoint),
                               &ack, &ack_size);

      // Reflect status on link LED: on when good link, off when lost.
      platform_write_led(0, stat > 0);

      // Dump console data coming back.
      if (stat >= 0 && ack_size > 0)
        {
          if ((ack.c >> 4) == 0)
            {
              // Console data.
              char *p = strnchr(ack.console.data, '\n', ack_size-1);
              if (p)
                p[1] = 0;
              else
                ack.console.data[ack_size-1] = 0;

#if 0
              // ATTENTION -- enable this if you want to see the messages
              // returned by the Crazyflie, but you need to run this under
              // the debugger!
              debug_printf("%s", ack.console.data);
#endif
            }
        }
    }

  // Done.
  return example_finish();
}