|
|
|
@ -1,46 +1,38 @@ |
|
|
|
|
#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" |
|
|
|
|
|
|
|
|
|
#define PCA8575_ADDRESS (0x20) |
|
|
|
|
#define PCA8575_TIMEOUT (5) |
|
|
|
|
#include "freertos/FreeRTOS.h" |
|
|
|
|
|
|
|
|
|
namespace gay_ipod { |
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* PCA8575 |
|
|
|
|
* 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(); |
|
|
|
|
|
|
|
|
|
esp_err_t Write(void); |
|
|
|
|
esp_err_t Read(void); |
|
|
|
|
|
|
|
|
|
bool charge_power_ok(void) const; |
|
|
|
|
bool headphone_detect(void) const; |
|
|
|
|
uint8_t key_states(void) const; |
|
|
|
|
|
|
|
|
|
enum SdMuxController { |
|
|
|
|
ESP, |
|
|
|
|
USB |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
void set_sd_mux(SdMuxController controller); |
|
|
|
|
void set_sd_cs(bool high); |
|
|
|
|
void set_display_cs(bool high); |
|
|
|
|
static const uint8_t kPca8575Address = 0x20; |
|
|
|
|
static const uint8_t kPca8575Timeout = 100 / portTICK_RATE_MS; |
|
|
|
|
|
|
|
|
|
// Not copyable or movable.
|
|
|
|
|
// TODO: maybe this could be movable?
|
|
|
|
|
GpioExpander(const GpioExpander&) = delete; |
|
|
|
|
GpioExpander& operator=(const GpioExpander&) = delete; |
|
|
|
|
|
|
|
|
|
private: |
|
|
|
|
// Port A:
|
|
|
|
|
// 0 - audio power enable
|
|
|
|
|
// 1 - usb interface power enable
|
|
|
|
@ -51,7 +43,7 @@ class GpioExpander { |
|
|
|
|
// 6 - sd chip select
|
|
|
|
|
// 7 - display chip select
|
|
|
|
|
// All power switches low, chip selects high, active-low charge power high
|
|
|
|
|
uint8_t port_a_ = 0b11010001; |
|
|
|
|
static const uint8_t kPortADefault = 0b11010001; |
|
|
|
|
|
|
|
|
|
// Port B:
|
|
|
|
|
// 0 - 3.5mm jack detect (active low)
|
|
|
|
@ -63,10 +55,155 @@ class GpioExpander { |
|
|
|
|
// 6 - GPIO
|
|
|
|
|
// 7 - GPIO
|
|
|
|
|
// DAC mute output low, everything else is active-low inputs.
|
|
|
|
|
uint8_t port_b_ = 0b11111101; |
|
|
|
|
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, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
uint8_t input_a_ = 0; |
|
|
|
|
uint8_t input_b_ = 0; |
|
|
|
|
/* 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
|
|
|
|
|