-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
I use an Arduino Pro Mini 3.3V 8MHz (328P) here, with a nRF24L01+ module attached to pins 7(CE), 10(CS) and 11-13 (SPI). There is a led with resistor attached to pin 9 which should be PWM-capable. I set the brightness of the led using analogWrite(), which works fine as long as there's no startListening() or stopListening() in my sketch. When I add one of those to the sketch the led does not blink anymore and the serial port transmits gibberish. If I use a digitalWrite() instead of the analogWrite() it works fine, so the electrical circuit shouldn't be the culprit.
My Arduino IDE version is 1.6.12 and the AVR board library 1.6.17 is installed. RF24 is installed in the library manager and is version 1.2.0.
Here is the (rather long) complete code. You can try the effect by uncommenting line 263 or 264:
//
// Arduino gamepads using the nRF24L01+ transceiver chips. In my example the NRF24duino board is used.
// The power draw of an Arduino Pro Mini 3.3V plus a nRF24L01+ transceiver is ~8-10mA.
// So theoretically, it should run ~300-400h from four AAA batteries (not counting self-discharge ;).
// At the moment this can read ~10 buttons, which is about the limit of pins available
// on the Arduino. This could be expanded with a shift register or MCP23017 or the like...
// Pins have pull-ups activated and buttons are LOW active. One pin acts as an "auto fire" toggle.
// When LOW, some buttons (A, B, X, Y) will toggle their on/off state with ~10Hz,
// as long as they're pressed down.
// The board connects A7 to a 6.8k / 10k voltage divider to measure battery voltage.
// An LED on pin 9 controlled via PWM will show different states of the board. The LED will:
// Pulse slowly: When the gamepad is looking for a receiver.
// Be steady on: When the gamepad has connected to a receiver.
// Blink rapidly: The gamepad battery is running low.
#include <SPI.h>
#include <RF24.h>
//-----serial------------------------------------------------------------
#define SERIAL_OUTPUT
#ifdef SERIAL_OUTPUT
// this is needed for radio.printDetails()
// don't forget call printf_begin() after Serial.begin()...
#include <printf.h>
void printBits(uint16_t myByte) {
for(uint16_t mask = 0x8000; mask; mask >>= 1) {
Serial.print(mask & myByte ? '1' : '0');
}
}
#endif
//-----power------------------------------------------------------------
// The ADC pin we measure battery power on
#define PIN_POWER_MEASURE A7
// Battery voltage should be 4-5V for alkaline batteries and 4-4.8V for rechargable batteries.
// The voltage divider will bring this into a range of 2.4-3V resp. ADC values of ~745-930.
#define BATTERY_LOW 745
bool isPowerOk()
{
// we measure the voltage on PIN_POWER_MEASURE.
return analogRead(PIN_POWER_MEASURE) > BATTERY_LOW;
}
void setupPower()
{
pinMode(PIN_POWER_MEASURE, INPUT);
}
//-----gamepad state------------------------------------------------------------
bool isConnected = false;
bool isBatteryLow = false;
#define PIN_STATE_LED 9
// we update the LED state at ~20Hz
#define LED_UPDATE_INTERVAL (1000/20)
#define LED_BRIGHTNESS 128
#define LED_PULSE_INTERVAL (1000*1)
#define LED_BLINK_INTERVAL (1000/6)
uint32_t lastLedUpdateMillis = 0;
uint32_t ledUpdateRemainingMillis = LED_PULSE_INTERVAL;
int8_t stateLedFadeDirection = 1;
uint8_t stateLedBrightness = 0;
void updateGamepadState()
{
// check if we need to update the state
const int32_t millisPassed = millis() - lastLedUpdateMillis;
if (millisPassed >= ledUpdateRemainingMillis) {
//Serial.print(String(millisPassed, DEC)); Serial.print(", ");
ledUpdateRemainingMillis = LED_UPDATE_INTERVAL - (millisPassed - ledUpdateRemainingMillis);
//Serial.println(String(ledUpdateRemainingMillis, DEC));
lastLedUpdateMillis = millis();
const uint8_t currentBrightness = stateLedBrightness;
// check battery power
isBatteryLow = !isPowerOk();
//Serial.print("Connected: "); Serial.print(isConnected); Serial.print(", battery low: "); Serial.println(isBatteryLow);
// update state LED
if (isBatteryLow) {
// battery is low. blink. calculate LED brightness inc-/decrease
const int32_t incValue = stateLedFadeDirection * (LED_BRIGHTNESS * millisPassed) / LED_BLINK_INTERVAL;
// check if the value would over-/underlow the range
int32_t newValue = ((int32_t)stateLedBrightness) + incValue;
if (newValue < 0 || newValue > LED_BRIGHTNESS) {
// yes. swap direction of the inc-/decrease
stateLedFadeDirection = -stateLedFadeDirection;
}
stateLedBrightness = constrain(newValue, 0, LED_BRIGHTNESS);
}
else {
if (isConnected) {
// battery ok and connected. steady on
stateLedBrightness = LED_BRIGHTNESS;
}
else {
// battery is ok, but no connection yet. pulse. calculate LED brightness inc-/decrease
const int32_t incValue = stateLedFadeDirection * (LED_BRIGHTNESS * millisPassed) / LED_PULSE_INTERVAL;
// check if the value would over-/underlow the range
int32_t newValue = ((int32_t)stateLedBrightness) + incValue;
if (newValue < 0 || newValue > LED_BRIGHTNESS) {
// yes. swap direction of the inc-/decrease
stateLedFadeDirection = -stateLedFadeDirection;
}
stateLedBrightness = constrain(newValue, 0, LED_BRIGHTNESS);
}
}
//update PWM value if the brightness changed
if (currentBrightness != stateLedBrightness) {
//Serial.println(String(stateLedBrightness, DEC));
analogWrite(PIN_STATE_LED, stateLedBrightness);
}
}
}
void setupGamepadState()
{
pinMode(PIN_STATE_LED, OUTPUT);
analogWrite(PIN_STATE_LED, 0);
}
//-----buttons------------------------------------------------------------
// Pins for the specific keys
#define PIN_LEFT 2
#define PIN_RIGHT 3
#define PIN_UP 4
#define PIN_DOWN 5
#define PIN_AUTOFIRE 6
#define PIN_A A0
#define PIN_B A1
//#define PIN_X A2
//#define PIN_Y A3
#define PIN_START A4
#define PIN_SELECT A5
#define PIN_PLAYER12 A6
//#define PIN_PLAYER34 A7
// Button state and pin layout
// Pin | Bit | Size | Description
// A6 | 0 | 1 | Player # (1-4)
// A7 | 1 | 1 | Player # (1-4)
// 2 | 2 | 1 | Left
// 3 | 3 | 1 | Right
// 4 | 4 | 1 | Up
// 5 | 5 | 1 | Down
// A0 | 6 | 1 | A
// A1 | 7 | 1 | B
// A2 | 8 | 1 | X
// A3 | 9 | 1 | Y
// A4 | 10 | 1 | Start
// A5 | 11 | 1 | Select
// | 12 | 4 | reserved
// == 16 bits
uint16_t buttonState = 0;
// here pins are mapped to bits in the button state variable
struct PinToStateBit {
uint8_t pinNo;
uint8_t bitIndex;
bool hasAutofire;
};
const PinToStateBit pinStateMap[] = {
{PIN_PLAYER12, 0, false}, /*{PIN_PLAYER34, 1, false}*/
{PIN_LEFT, 2, false}, {PIN_RIGHT, 3, false}, {PIN_UP, 4, false}, {PIN_DOWN, 5, false},
{PIN_A, 6, true}, {PIN_B, 7, true}, /*{PIN_X, 8, true}, {PIN_Y, 9, true},*/
{PIN_START, 10, false}, {PIN_SELECT, 11, false},
};
// The interval in ms that the button state will be updated (100 Hz)
#define BUTTON_UPDATE_INTERVAL (1000/100)
uint32_t lastButtonUpdateMillis = 0;
// The autofire interval is about 10Hz
#define AUTOFIRE_UPDATE_INTERVAL (1000/10)
bool autofireButtonValue = true;
uint32_t lastAutofireMillis = 0;
uint32_t autofireRemainingMillis = AUTOFIRE_UPDATE_INTERVAL;
void setupButtons()
{
// set up the pins. we want a pullup on every pin because they are LOW active
for (uint8_t i = 0; i < sizeof(pinStateMap) / sizeof(PinToStateBit); ++i) {
pinMode(pinStateMap[i].pinNo, INPUT_PULLUP);
}
pinMode(PIN_AUTOFIRE, INPUT_PULLUP);
}
uint16_t getButtonState()
{
// check if autofire is enabled
const bool autofireEnabled = digitalRead(PIN_AUTOFIRE) == LOW;
if (autofireEnabled)
{
// yes. check if we need to toggle the autofire button value
if ((millis() - lastAutofireMillis) >= autofireRemainingMillis) {
autofireRemainingMillis = AUTOFIRE_UPDATE_INTERVAL - (millis() - lastAutofireMillis) - autofireRemainingMillis;
lastAutofireMillis = millis();
autofireButtonValue = !autofireButtonValue;
}
}
else {
// no. reset to default values
lastAutofireMillis = millis();
autofireRemainingMillis = AUTOFIRE_UPDATE_INTERVAL;
autofireButtonValue = true;
}
// get new button state from pins
uint16_t newState = 0;
for (uint8_t i = 0; i < sizeof(pinStateMap) / sizeof(PinToStateBit); ++i) {
// read button pin state
bool pinState = digitalRead(pinStateMap[i].pinNo) == LOW;
// check if autofire is on, the button has autofire and is pressed in "real life"
if (autofireEnabled && pinStateMap[i].hasAutofire && pinState) {
// yes. overwrite pin state with the current autofire toggle value
pinState = autofireButtonValue;
}
newState |= ((uint16_t)pinState) << pinStateMap[i].bitIndex;
}
return newState;
}
//-----radio------------------------------------------------------------
// nRF24L01 radio on SPI bus (pins 11, 12, 13) plus pins 7 (CE) + 10 (CS)
RF24 radio(7, 10);
// the channel we're sending on. this should be well above WiFi channels (>100)
const uint8_t channel = 123;
// the gamepad send adresses. all gamepads send on the same adress
const uint64_t writePipe = 0xF0F0F0F0D2LL;
const uint64_t readPipe = 0x3A3A3A3AD2LL;
// we keep the time we last sent a message and send messages at least every second
#define HEARTBEAT_INTERVAL 1000
uint32_t lastMessageSentMillis = 0;
void setupRadio()
{
radio.begin();
// set the radio a low power level. RF24_PA_MIN < RF24_PA_LOW < RF24_PA_MID < RF24_PA_MAX.
radio.setPALevel(RF24_PA_LOW);
// set the channel
radio.setChannel(channel);
// set a low, but reliable data rate
radio.setDataRate(RF24_250KBPS);
// turn on auto acknowledgement
radio.setAutoAck(true);
// set the number of retries. we retry up to 8 times, waiting 2*250us = 0.5ms between each retry
// this amounts to a total wait time of 4ms until a transmission has failed
radio.setRetries(2, 8);
// set CRC to 8bit. this should be enough, as we're only sending some bytes per message
radio.setCRCLength(RF24_CRC_8);
// set the payload size to the size of our messages
radio.setPayloadSize(sizeof(buttonState));
// open a writing and reading pipe
radio.openWritingPipe(writePipe);
radio.openReadingPipe(1, readPipe);
#ifdef SERIAL_OUTPUT
// print radio state
printf_begin();
radio.printDetails();
#endif
//radio.startListening(); <<<<< uncomment one of these
//radio.stopListening(); <<<<< uncomment one of these
}
void sendState(uint16_t state)
{
// stop listening, start shouting
//radio.stopListening(); <<<<< uncomment one of these
isConnected = radio.write(&state, sizeof(state));
radio.txStandBy();
#ifdef SERIAL_OUTPUT
Serial.print(F("Button state: ")); printBits(state); Serial.println();
if (!isConnected) {
Serial.println(F("Sending failed!"));
}
#endif
//radio.startListening(); <<<<< uncomment one of these
}
//-----main------------------------------------------------------------
void setup() {
setupPower();
setupGamepadState();
setupButtons();
#ifdef SERIAL_OUTPUT
// set up serial port and print radio and button state
Serial.begin(115200);
Serial.println(F("Started"));
Serial.println(F("Radio state: "));
#endif
setupRadio();
}
void loop() {
updateGamepadState();
// check if we need to check the button state again
if ((millis() - lastButtonUpdateMillis) >= BUTTON_UPDATE_INTERVAL) {
lastButtonUpdateMillis = millis();
// yes. check buttons
const uint16_t newState = getButtonState();
// check if the state changed or we need to send a heartbeat
if (newState != buttonState || (millis() - lastMessageSentMillis) >= HEARTBEAT_INTERVAL) {
lastMessageSentMillis = millis();
// replace button state with new one and send it
buttonState = newState;
sendState(buttonState);
}
}
}