Skip to content

startListening or stopListening breaks analogWrite on Arduino #325

@HorstBaerbel

Description

@HorstBaerbel

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);
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions