JoyStick Shield with the FRDM Board

The latest addition to my set of Arduino shields is a true fun thing: The ElecFreaks.com JoyStick Shield 🙂

ElecFreaks.com Joystick Board with FRDM-KL25Z and nRF24L01+

ElecFreaks.com Joystick Board with FRDM-KL25Z and nRF24L01+

The board costs less than US$10 (see http://www.elecfreaks.com) and enables any Arduino board to be a gaming board 🙂

❗ I had to heat up and re-solder the Arduino headers, as the soldering was not very well done (contacts where not solid). After fixing this, I had no problems.

Joystick Board

Joystick Board

Joystick Board Bottom Side

Joystick Board Bottom Side

The board has

  • 3.3V and 5V supply voltage and logic choice
  • Socket for nRF24L01+ transceiver
  • All Arduino connectors are routed through, and one connector can be used for a Nokia 5110 display.
  • 4 big (red and blue) push buttons: A, B, C an D
  • Two small push buttons (E and F)
  • Joystick with X and Y potentiometer and extra push button (KEY)
Joystick Board Top Side

Joystick Board Top Side

❗ While all Arduino headers are available on the top side of the board, it is not possible to stack another board on top of the Joystick board.

The X/Y joystick has an extra push button (KEY) for pressing the stick down:

KEY Button

KEY Button

Demo Application

I have created a demo application for the shield (see the link to the project and sources on GitHub at the end of this article).

FRDM-KL25Z_Jostick Example

FRDM-KL25Z_Jostick Example

The example runs with FreeRTOS, handles all buttons and the x/y joystick, features a command line shell and sends commands with the nRF24L01+ transceiver.

nRF24L01+ Module

I don’t have that Nokia 5110, but I’m using the nRF24L01+ transceiver with the board. Because the interrupt line of the nRF24L01+ module is not present on this board, I had to extend RNet stack so it works without interrupts in polling mode. To use the module in polling mode, I disable the interrupt pin:

IRQ Pin of nRF24L01+ disabled

IRQ Pin of nRF24L01+ disabled

In the RNet stack, I enable polling:

Polling Enabled in the RNet Stack

Polling Enabled in the RNet Stack

With this enabled, the transceiver part of the stack polls the status byte in the transceiver automatically:

/*
** ===================================================================
**     Method      :  RF1_PollInterrupt (component nRF24L01)
**     Description :
**         If there is no interrupt line available, this method polls
**         the device to check if there is an interrupt. If
**     Parameters  : None
**     Returns     : Nothing
** ===================================================================
*/
void RF1_PollInterrupt(void)
{
  uint8_t status;
  void RADIO_OnInterrupt(void); /* prototype */

  status = RF1_GetStatus();
  if (status&(RF1_STATUS_RX_DR|RF1_STATUS_TX_DS|RF1_STATUS_MAX_RT)) {
    RF1_CE_LOW(); /* pull CE Low to disable transceiver */
    RADIO_OnInterrupt();
    RF1_OnInterrupt(); /* call user event (if enabled)... */
  }
}

If you do not have the nRF24L01+ module, it can be disabled with setting the macro

PL_HAS_NRF24

in platform.h to 0.

Push Buttons

I’m using the Processor Expert ‘Key’ component for the push buttons:

Push Button Handling

Push Button Handling

This components will do all the debouncing and generates the events for the application.

Command Line Shell

The shell offers these commands:

--------------------------------------------------------------
Joystick
--------------------------------------------------------------
CLS1                      ; Group of CLS1 commands
  help|status             ; Print help or status information
FRTOS1                    ; Group of FRTOS1 commands
  help|status             ; Print help or status information
radio                     ; Group of radio commands
  help|status             ; Shows radio help or status
  channel         ; Switches to the given channel. Channel must be in the range 0..127
  power           ; Changes output power to 0, -10, -12, -18
  sniff on|off            ; Turns sniffing on or off
rnwk                      ; Group of rnwk commands
  help|status             ; Shows help or status
app                       ; Group of app commands
  help|status             ; print help or status information

The ‘status’ command shows the system and joystick status:

--------------------------------------------------------------
SYSTEM STATUS
--------------------------------------------------------------
Firmware     : Apr 26 2014 17:29:21
FRTOS1       : 
TASK LIST:
Name        Status    Prio    Stack    TCB#
--------------------------------------------------------------
Shell       R    1    320    1
IDLE        R    0    181    4
App         B    2    98     3
RNet        B    3    120    2
  RTOS ticks : 1000 Hz, 1 ms
  Free heap  : 3728 bytes
Radio        : 
  state      : READY_TX_RX
  sniff      : no
  channel    : 0
  power      : 0 dBm
  OBSERVE_TX : 2 lost, 15 retry
rnwk         : 
  addr       : 0xFF
app          : 
  Buttons    : A(off) B(off) C(off) D(off) E(off) F(off) KEY(off)
  Analog     : X: 0x7F9C(0) Y: 0x7FE6(0)

The application is using events, and when a button is pressed, this is sent as a message with the RNet stack:

void APP_HandleEvent(uint8_t event) {
#if PL_HAS_NRF24
  uint8_t data;
#endif

  switch(event) {
  case EVNT1_A_PRESSED:
    CLS1_SendStr((unsigned char*)"A pressed!\r\n", CLS1_GetStdio()->stdOut);
#if PL_HAS_NRF24
    data = 'A';
    (void)RAPP_SendPayloadDataBlock(&data, sizeof(data), RAPP_MSG_TYPE_JOYSTICK_BTN, RNWK_ADDR_BROADCAST, RPHY_PACKET_FLAGS_NONE);
#endif
    break;
  case EVNT1_B_PRESSED:
    CLS1_SendStr((unsigned char*)"B pressed!\r\n", CLS1_GetStdio()->stdOut);
#if PL_HAS_NRF24
    data = 'B';
    (void)RAPP_SendPayloadDataBlock(&data, sizeof(data), RAPP_MSG_TYPE_JOYSTICK_BTN, RNWK_ADDR_BROADCAST, RPHY_PACKET_FLAGS_NONE);
#endif
    break;
  case EVNT1_C_PRESSED:
    CLS1_SendStr((unsigned char*)"C pressed!\r\n", CLS1_GetStdio()->stdOut);
#if PL_HAS_NRF24
    data = 'C';
    (void)RAPP_SendPayloadDataBlock(&data, sizeof(data), RAPP_MSG_TYPE_JOYSTICK_BTN, RNWK_ADDR_BROADCAST, RPHY_PACKET_FLAGS_NONE);
#endif
    break;
  case EVNT1_D_PRESSED:
    CLS1_SendStr((unsigned char*)"D pressed!\r\n", CLS1_GetStdio()->stdOut);
#if PL_HAS_NRF24
    data = 'D';
    (void)RAPP_SendPayloadDataBlock(&data, sizeof(data), RAPP_MSG_TYPE_JOYSTICK_BTN, RNWK_ADDR_BROADCAST, RPHY_PACKET_FLAGS_NONE);
#endif
    break;
  case EVNT1_E_PRESSED:
    CLS1_SendStr((unsigned char*)"E pressed!\r\n", CLS1_GetStdio()->stdOut);
#if PL_HAS_NRF24
    data = 'E';
    (void)RAPP_SendPayloadDataBlock(&data, sizeof(data), RAPP_MSG_TYPE_JOYSTICK_BTN, RNWK_ADDR_BROADCAST, RPHY_PACKET_FLAGS_NONE);
#endif
    break;
  case EVNT1_F_PRESSED:
    CLS1_SendStr((unsigned char*)"F pressed!\r\n", CLS1_GetStdio()->stdOut);
#if PL_HAS_NRF24
    data = 'F';
    (void)RAPP_SendPayloadDataBlock(&data, sizeof(data), RAPP_MSG_TYPE_JOYSTICK_BTN, RNWK_ADDR_BROADCAST, RPHY_PACKET_FLAGS_NONE);
#endif
    break;
  case EVNT1_KEY_PRESSED:
    CLS1_SendStr((unsigned char*)"KEY pressed!\r\n", CLS1_GetStdio()->stdOut);
#if PL_HAS_NRF24
    data = 'K';
    (void)RAPP_SendPayloadDataBlock(&data, sizeof(data), RAPP_MSG_TYPE_JOYSTICK_BTN, RNWK_ADDR_BROADCAST, RPHY_PACKET_FLAGS_NONE);
#endif
    break;
  default:
    break;
  } /* switch */
}

The application main task performs all the periodic work, and sends joystick x/y data:

static void AppTask(void *pvParameters) {
  uint16_t cntMs;
  uint16_t x, y;
  int8_t x8, y8, x8prev, y8prev;
#if PL_HAS_NRF24
  uint8_t data[2];
#endif
  uint8_t buf[24];

  CLS1_SendStr((unsigned char*)"Hello from the Joystick App!\r\n", CLS1_GetStdio()->stdOut);
  cntMs = 0;
  x8prev = 127; y8prev = 127; /* should be different from center position */
  for(;;) {
    if (APP_GetXY(&x, &y, &x8, &y8)!=ERR_OK) {
      CLS1_SendStr((unsigned char*)"Failed to get x/y!\r\n", CLS1_GetStdio()->stdErr);
    } else {
      if ((x8!=x8prev) || (y8!=y8prev)) { /* send only changing data, and only if not zero/midpoint */
        UTIL1_strcpy(buf, sizeof(buf), (unsigned char*)"xy: ");
        UTIL1_strcatNum8s(buf, sizeof(buf), x8);
        UTIL1_chcat(buf, sizeof(buf), ',');
        UTIL1_strcatNum8s(buf, sizeof(buf), y8);
        UTIL1_strcat(buf, sizeof(buf), (unsigned char*)"\r\n");
        CLS1_SendStr(buf, CLS1_GetStdio()->stdOut);
#if PL_HAS_NRF24
        data[0] = (uint8_t)x8;
        data[1] = (uint8_t)y8;
        (void)RAPP_SendPayloadDataBlock(&data[0], sizeof(data), RAPP_MSG_TYPE_JOYSTICK_XY, RNWK_ADDR_BROADCAST, RPHY_PACKET_FLAGS_NONE);
#endif
        x8prev = x8;
        y8prev = y8;
      }
    }
    if (cntMs>500) {
      LED1_Neg();
      cntMs = 0;
    }
    KEY1_ScanKeys();
    EVNT1_HandleEvent();
    FRTOS1_vTaskDelay(100/portTICK_RATE_MS);
    cntMs += 100;
  }
}

Summary

With the joystick board, I have now a nice remote controller for my Zumo bot, and I have a wireless link with the nRF24L01+ module. I need to add batteries, and then I have a cool remote controller I can use for pretty much anything. I have some other Nokia displays available, so maybe I should spend some time implementing a game (PacMan?) :-).

The project and sources are available on GitHub.

Happy Gaming 🙂

35 thoughts on “JoyStick Shield with the FRDM Board

  1. Pingback: Joystick Shield with nRF24L01 driving a Zumo Robot | MCU on Eclipse

  2. Pingback: Snake Game on the FRDM-KL25Z with Nokia 5110 Display | MCU on Eclipse

  3. Pingback: User Interrupt on NMI Pin with Kinetis and ExtInt Component | MCU on Eclipse

  4. I am getting a totally different view of the Key component in Processor expert components view. How do you add multiple keys, for example. mine says referenced component disabled or not installed. may be there are some components I don’t have, I will try to update from source forge repo. I am using KDS 3.0.

    Like

        • TickPeriodMs, in the component settings/properties. By default 10 ms. You need to call AddTick() with that frequency (period).
          If you select ‘RTOS’, then it is expected that you use the RTOS (FreeRTOS) tick hook to call AddTick().

          Like

      • OK, so I have a timer with a period of 10ms calling TRG1_AddTick(); on its TI1_OnInterrupt event. Still nothing. I can set a breakpoint into the timer events but not the keypressed even, debugger shuts down. I am doing this with interrupts disabled for the Key component BTW.

        Like

      • I was suspecting it was because of removed code as well, for if you remove a component, the events in events.c don’t get deleted automatically even after generating code. But then its not the case, the components are there, I even generated code again. Just can’t set breakpoints in the KEY1_OnKeyPressed event.

        Like

      • For the trigger component, apart from calling the addTick() periodically from a timer event, am I also supposed to call AddTrigger() somewhere ? I dont seem to understand this trigger mechanism. Coz I have a trigger defined by default for the KEY1 component as “#define TRG1_KEY1_PRESS 0” but where is this used? I think there is a connection missing.

        Like

      • Checking settings with what? I’m sorry, but I’m somewhat lost with your problem :-(.
        Are you using the FRDM-KL25Z with that Joystick shield? Do you have proper pull-ups for your push buttons installed if using your own buttons?

        Like

      • Ok, so I looked at your code, a bit complex, am not using RTOS, I can see that somewhere in application.c you are repeatedly calling KEY1_ScanKeys(); through CTRL_ScanKeys();, the KEY1_OnKeyPressed is firing when a key is pressed and CTRL_OnKeyPressed() is setting events and then in Application.c you call APP_HandleEvent(); which handles those events set by OnKeyPressed().
        All this while the timer event is calling AddTick(). I was simply not scanning the keys in main!! Thats it. Now the KeyPressed event is firing. Thanks.

        Like

  5. Hello, Erich

    I’m trying to help a customer who is using your Component “KEY” in a project with K22 in KDS. I order to analyze this issue separately, I created a new project in KDS, and set PTB17 (which in FRDM-K22F is connected to SW3 button) as a button on your component “KEY” and enabled the Port interrupt for detecting falling edges on this pin. Then I set PTD5, which is connected to a led, as a GPIO output and toggle this port every time the Port interrupt happens in ISR (in Events.c).

    The first time I press SW3, the interrupt happens and the led is toggled. But the next times, it doesn’t work anymore. I realized that, after the event occurs, the interrupt is disabled in PCR register. More specifically, after it runs “Scan” function in “keyPin1_OnInterrupt” (in KEY1.c file).
    How can I avoid this? Is there a specific setting to do in “KEY” component for that?

    Thanks!

    Like

  6. Hi Erich.
    I found a bug in the component.
    In the Component inspector, you can select number of Button and number of Hat, but the USB_Descriptor, don’t generate correct descriptor for selected parameters.
    I supose you create a basic and fixed descriptor for this component.
    But for me is OK (I modify the descriptor for my application).
    Thank’s

    Like

    • Hi Sergi,
      I have now implemented settings to configure the number of controls. That works fine for number of buttons (I tested up to 24 buttons). For the analog, throttle and hat switch I have a setting to have one or zero (disabled), as this would require larger changes (duplications in the USB descriptor). Are you actually using multiple hat switches? If so, I could continue looking into this, but in my case I only have a hat switch with 4 positions, so I’m fine 🙂

      Like

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.