Fork of Tangara with customizations
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
tangara-fw/main/gpio-expander.h

209 lines
5.8 KiB

#pragma once
#include <atomic>
#include <functional>
#include <mutex>
#include <stdint.h>
#include <tuple>
#include <utility>
#include "esp_check.h"
#include "esp_log.h"
#include "esp_err.h"
#include "driver/i2c.h"
#include "freertos/FreeRTOS.h"
namespace gay_ipod {
/**
* Wrapper for interfacing with the PCA8575 GPIO expander. Includes basic
* low-level pin setting methods, as well as higher level convenience functions
* for reading, writing, and atomically interacting with the SPI chip select
* pins.
*
* Each method of this class can be called safely from any thread, and all
* updates are guaranteed to be atomic. Any access to chip select related pins
* should be done whilst holding `cs_lock` (preferably via the helper methods).
*/
class GpioExpander {
public:
GpioExpander();
~GpioExpander();
static const uint8_t kPca8575Address = 0x20;
static const uint8_t kPca8575Timeout = 100 / portTICK_RATE_MS;
// Port A:
// 0 - audio power enable
// 1 - usb interface power enable
// 2 - display power enable
// 3 - sd card power enable
// 4 - charge power ok (active low)
// 5 - sd mux switch
// 6 - sd chip select
// 7 - display chip select
// All power switches low, chip selects high, active-low charge power high
static const uint8_t kPortADefault = 0b11010001;
// Port B:
// 0 - 3.5mm jack detect (active low)
// 1 - dac soft mute switch
// 2 - GPIO
// 3 - GPIO
// 4 - GPIO
// 5 - GPIO
// 6 - GPIO
// 7 - GPIO
// DAC mute output low, everything else is active-low inputs.
static const uint8_t kPortBDefault = 0b11111101;
/*
* Convenience mehod for packing the port a and b bytes into a single 16 bit
* value.
*/
static uint16_t pack(uint8_t a, uint8_t b) {
return ((uint16_t) b) << 8 | a;
}
/*
* Convenience mehod for unpacking the result of `pack` back into two single
* byte port datas.
*/
static std::pair<uint8_t, uint8_t> unpack(uint16_t ba) {
return std::pair((uint8_t) ba, (uint8_t) (ba >> 8));
}
/*
* Convenience function for running some arbitrary pin writing code, then
* flushing a `Write()` to the expander. Example usage:
*
* ```
* gpio_.with([&](auto& gpio) {
* gpio.set_pin(AUDIO_POWER_ENABLE, true);
* });
* ```
*/
void with(std::function<void(GpioExpander&)> f);
/**
* Sets the ports on the GPIO expander to the values currently represented
* in `ports`.
*/
esp_err_t Write(void);
/**
* Reads from the GPIO expander, populating `inputs` with the most recent
* values.
*/
esp_err_t Read(void);
/* Maps each pin of the expander to its number in a `pack`ed uint16. */
enum Pin {
// Port A
AUDIO_POWER_ENABLE = 0,
USB_INTERFACE_POWER_ENABLE = 1,
DISPLAY_POWER_ENABLE = 2,
SD_CARD_POWER_ENABLE = 3,
CHARGE_POWER_OK = 4, // Active-low input
SD_MUX_SWITCH = 5,
SD_CHIP_SELECT = 6,
DISPLAY_CHIP_SELECT = 7,
// Port B
PHONE_DETECT = 8, // Active-high input
DAC_MUTE = 9,
GPIO_1 = 10,
GPIO_2 = 11,
GPIO_3 = 12,
GPIO_4 = 13,
GPIO_5 = 14,
GPIO_6 = 15,
};
/* Pins whose access should be guarded by `cs_lock`. */
enum ChipSelect {
SD_CARD = SD_CHIP_SELECT,
DISPLAY = DISPLAY_CHIP_SELECT,
};
/* Nicer value names for use with the SD_MUX_SWITCH pin. */
enum SdController {
SD_MUX_ESP = 0,
SD_MUX_USB = 1,
};
/**
* Returns the current driven status of each of the ports. The first byte is
* port a, and the second byte is port b.
*/
std::atomic<uint16_t>& ports() { return ports_; }
/*
* Sets a single specific pin to the given value. `true` corresponds to
* HIGH, and `false` corresponds to LOW.
*
* Calls to this method will be buffered in memory until a call to `Write()`
* is made.
*/
void set_pin(Pin pin, bool value);
void set_pin(ChipSelect cs, bool value);
/**
* Returns the input status of each of the ports. The first byte is port a,
* and the second byte is port b.
*/
const std::atomic<uint16_t>& inputs() const { return inputs_; }
/* Returns the most recently cached value of the given pin. Only valid for
* pins used as inputs; to check what value we're driving a pin, use
* `ports()`.
*/
bool get_input(Pin pin) const;
/* Returns the mutex that must be held whilst pulling a CS pin low. */
std::mutex& cs_mutex() { return cs_mutex_; }
/*
* Helper class containing an active `cs_mutex` lock. When an instance of
* this class is destroyed (usually by falling out of scope), the associated
* CS pin will be driven high before the lock is released.
*/
class SpiLock {
public:
SpiLock(GpioExpander &gpio, ChipSelect cs);
~SpiLock();
SpiLock(const SpiLock&) = delete;
private:
std::scoped_lock<std::mutex> lock_;
GpioExpander &gpio_;
ChipSelect cs_;
};
/*
* Pulls the given CS pin low to signal that we are about to communicate
* with a particular device, after acquiring a lock on `cs_mutex`. The
* recommended way to safely interact with devices on the SPI bus is to have
* a self-contained block like so:
*
* ```
* {
* auto lock = AcquireSpiBus(WHATEVER);
* // Do some cool things here.
* }
* ```
*/
SpiLock AcquireSpiBus(ChipSelect cs);
// Not copyable or movable. There should usually only ever be once instance
// of this class, and that instance will likely have a static lifetime.
GpioExpander(const GpioExpander&) = delete;
GpioExpander& operator=(const GpioExpander&) = delete;
private:
std::mutex cs_mutex_;
std::atomic<uint16_t> ports_;
std::atomic<uint16_t> inputs_;
};
} // namespace gay_ipod