diff options
| author | jacqueline <me@jacqueline.id.au> | 2022-11-07 12:01:29 +1100 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2022-11-07 12:01:29 +1100 |
| commit | 28d73ad8660e27f9c7b20b6e978d3d0c412dec00 (patch) | |
| tree | c50b739ae4712f5ddb9fb6e44e39e01e4c20356d /src/drivers/include | |
| parent | b13a9793e17e7e0e52ea08fa5fb69ca9b90ad56d (diff) | |
| download | tangara-fw-28d73ad8660e27f9c7b20b6e978d3d0c412dec00.tar.gz | |
Split driver-y things into a separate component
Diffstat (limited to 'src/drivers/include')
| -rw-r--r-- | src/drivers/include/battery.hpp | 16 | ||||
| -rw-r--r-- | src/drivers/include/dac.hpp | 73 | ||||
| -rw-r--r-- | src/drivers/include/display-init.hpp | 81 | ||||
| -rw-r--r-- | src/drivers/include/display.hpp | 67 | ||||
| -rw-r--r-- | src/drivers/include/gpio-expander.hpp | 208 | ||||
| -rw-r--r-- | src/drivers/include/i2c.hpp | 84 | ||||
| -rw-r--r-- | src/drivers/include/playback.hpp | 67 | ||||
| -rw-r--r-- | src/drivers/include/storage.hpp | 60 |
8 files changed, 656 insertions, 0 deletions
diff --git a/src/drivers/include/battery.hpp b/src/drivers/include/battery.hpp new file mode 100644 index 00000000..399e866f --- /dev/null +++ b/src/drivers/include/battery.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include <stdint.h> + +#include "esp_err.h" + +namespace gay_ipod { + +esp_err_t init_adc(void); + +/** + * Returns the current battery level in millivolts. + */ +uint32_t read_battery_voltage(void); + +} // namespace gay_ipod diff --git a/src/drivers/include/dac.hpp b/src/drivers/include/dac.hpp new file mode 100644 index 00000000..6d025384 --- /dev/null +++ b/src/drivers/include/dac.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "gpio-expander.hpp" + +#include <stdint.h> +#include <functional> + +#include "esp_err.h" +#include "result.hpp" + +namespace gay_ipod { + +/** + * Interface for a PCM5122PWR DAC, configured over I2C. + */ +class AudioDac { + public: + enum Error { + FAILED_TO_BOOT, + FAILED_TO_CONFIGURE, + }; + static auto create(GpioExpander* expander) + -> cpp::result<std::unique_ptr<AudioDac>, Error>; + + AudioDac(GpioExpander* gpio); + ~AudioDac(); + + /** + * Sets the volume on a scale from 0 (loudest) to 254 (quietest). A value of + * 255 engages the soft mute function. + */ + void WriteVolume(uint8_t volume); + + enum PowerState { + POWERDOWN = 0b0, + WAIT_FOR_CP = 0b1, + CALIBRATION_1 = 0b10, + CALIBRATION_2 = 0b11, + RAMP_UP = 0b100, + RUN = 0b101, + SHORT = 0b110, + RAMP_DOWN = 0b111, + STANDBY = 0b1000, + }; + + /* Returns the current boot-up status and internal state of the DAC */ + std::pair<bool, PowerState> ReadPowerState(); + + // Not copyable or movable. + AudioDac(const AudioDac&) = delete; + AudioDac& operator=(const AudioDac&) = delete; + + private: + GpioExpander* gpio_; + + /* + * Pools the power state for up to 10ms, waiting for the given predicate to + * be true. + */ + bool WaitForPowerState(std::function<bool(bool, PowerState)> predicate); + + enum Register { + PAGE_SELECT = 0, + DE_EMPHASIS = 7, + DIGITAL_VOLUME_L = 61, + DIGITAL_VOLUME_R = 62, + DSP_BOOT_POWER_STATE = 118, + }; + + void WriteRegister(Register reg, uint8_t val); +}; + +} // namespace gay_ipod diff --git a/src/drivers/include/display-init.hpp b/src/drivers/include/display-init.hpp new file mode 100644 index 00000000..f11e9b57 --- /dev/null +++ b/src/drivers/include/display-init.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include <cstdint> + +namespace gay_ipod { +namespace displays { + +extern const uint8_t kDelayBit; + +struct InitialisationData { + uint8_t num_sequences; + const uint8_t* sequences[4]; +}; + +extern const InitialisationData kST7735R; + +/* + * Valid command bytes that can be sent to ST77XX displays, as well as commands + * for more specific variants. + */ +enum StCommands { + ST77XX_NOP = 0x00, + ST77XX_SWRESET = 0x01, + ST77XX_RDDID = 0x04, + ST77XX_RDDST = 0x09, + + ST77XX_SLPIN = 0x10, + ST77XX_SLPOUT = 0x11, + ST77XX_PTLON = 0x12, + ST77XX_NORON = 0x13, + + ST77XX_INVOFF = 0x20, + ST77XX_INVON = 0x21, + ST77XX_DISPOFF = 0x28, + ST77XX_DISPON = 0x29, + ST77XX_CASET = 0x2A, + ST77XX_RASET = 0x2B, + ST77XX_RAMWR = 0x2C, + ST77XX_RAMRD = 0x2E, + + ST77XX_PTLAR = 0x30, + ST77XX_TEOFF = 0x34, + ST77XX_TEON = 0x35, + ST77XX_MADCTL = 0x36, + ST77XX_COLMOD = 0x3A, + + ST77XX_MADCTL_MY = 0x80, + ST77XX_MADCTL_MX = 0x40, + ST77XX_MADCTL_MV = 0x20, + ST77XX_MADCTL_ML = 0x10, + ST77XX_MADCTL_RGB = 0x00, + + ST77XX_RDID1 = 0xDA, + ST77XX_RDID2 = 0xDB, + ST77XX_RDID3 = 0xDC, + ST77XX_RDID4 = 0xDD, + + ST7735_MADCTL_BGR = 0x08, + ST7735_MADCTL_MH = 0x04, + + ST7735_FRMCTR1 = 0xB1, + ST7735_FRMCTR2 = 0xB2, + ST7735_FRMCTR3 = 0xB3, + ST7735_INVCTR = 0xB4, + ST7735_DISSET5 = 0xB6, + + ST7735_PWCTR1 = 0xC0, + ST7735_PWCTR2 = 0xC1, + ST7735_PWCTR3 = 0xC2, + ST7735_PWCTR4 = 0xC3, + ST7735_PWCTR5 = 0xC4, + ST7735_VMCTR1 = 0xC5, + + ST7735_PWCTR6 = 0xFC, + + ST7735_GMCTRP1 = 0xE0, + ST7735_GMCTRN1 = 0xE1, +}; + +} // namespace displays +} // namespace gay_ipod diff --git a/src/drivers/include/display.hpp b/src/drivers/include/display.hpp new file mode 100644 index 00000000..2d6e9cd6 --- /dev/null +++ b/src/drivers/include/display.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include <cstdint> +#include "display-init.hpp" +#include "driver/spi_master.h" +#include "gpio-expander.hpp" +#include "lvgl/lvgl.h" +#include "result.hpp" + +namespace gay_ipod { + +/* + * Display driver for LVGL. + */ +class Display { + public: + enum Error {}; + static auto create(GpioExpander* expander, + const displays::InitialisationData& init_data) + -> cpp::result<std::unique_ptr<Display>, Error>; + + Display(GpioExpander* gpio, spi_device_handle_t handle); + ~Display(); + + void WriteData(); + + void Flush(lv_disp_drv_t* disp_drv, + const lv_area_t* area, + lv_color_t* color_map); + + void IRAM_ATTR PostTransaction(const spi_transaction_t& transaction); + + void ServiceTransactions(); + + private: + GpioExpander* gpio_; + spi_device_handle_t handle_; + + lv_disp_draw_buf_t buffers_; + lv_disp_drv_t driver_; + lv_disp_t* display_ = nullptr; + + enum TransactionType { + COMMAND = 0, + DATA = 1, + }; + + enum TransactionFlags { + LVGL_FLUSH = 1, + }; + + void SendInitialisationSequence(const uint8_t* data); + + void SendCommandWithData(uint8_t command, + const uint8_t* data, + size_t length, + uintptr_t flags = 0); + + void SendCmd(const uint8_t* data, size_t length, uintptr_t flags = 0); + void SendData(const uint8_t* data, size_t length, uintptr_t flags = 0); + void SendTransaction(TransactionType type, + const uint8_t* data, + size_t length, + uintptr_t flags = 0); +}; + +} // namespace gay_ipod diff --git a/src/drivers/include/gpio-expander.hpp b/src/drivers/include/gpio-expander.hpp new file mode 100644 index 00000000..2a12fba4 --- /dev/null +++ b/src/drivers/include/gpio-expander.hpp @@ -0,0 +1,208 @@ +#pragma once + +#include <stdint.h> +#include <atomic> +#include <functional> +#include <mutex> +#include <tuple> +#include <utility> + +#include "driver/i2c.h" +#include "esp_check.h" +#include "esp_err.h" +#include "esp_log.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 = 0b11111111; + + /* + * 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 diff --git a/src/drivers/include/i2c.hpp b/src/drivers/include/i2c.hpp new file mode 100644 index 00000000..db554f5d --- /dev/null +++ b/src/drivers/include/i2c.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include <cstdint> + +#include "driver/i2c.h" +#include "hal/i2c_types.h" + +namespace gay_ipod { + +/* + * Convenience wrapper for performing an I2C transaction with a reasonable + * preconfigured timeout, automatic management of a heap-based command buffer, + * and a terser API for enqueuing bytes. + * + * Any error codes from the underlying ESP IDF are treated as fatal, since they + * typically represent invalid arguments or OOMs. + */ +class I2CTransaction { + public: + static const uint8_t kI2CTimeout = 100 / portTICK_RATE_MS; + + I2CTransaction(); + ~I2CTransaction(); + + /* + * Executes all enqueued commands, returning the result code. Possible error + * codes, per the ESP-IDF docs: + * + * ESP_OK Success + * ESP_ERR_INVALID_ARG Parameter error + * ESP_FAIL Sending command error, slave doesn’t ACK the transfer. + * ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode. + * ESP_ERR_TIMEOUT Operation timeout because the bus is busy. + */ + esp_err_t Execute(); + + /* + * Enqueues a start condition. May also be used for repeated start + * conditions. + */ + I2CTransaction& start(); + /* Enqueues a stop condition. */ + I2CTransaction& stop(); + + /* + * Enqueues writing the given 7 bit address, followed by one bit indicating + * whether this is a read or write request. + * + * This command will expect an ACK before continuing. + */ + I2CTransaction& write_addr(uint8_t addr, uint8_t op); + + /* + * Enqueues one or more bytes to be written. The transaction will wait for + * an ACK to be returned before writing the next byte. + */ + I2CTransaction& write_ack(uint8_t data); + template <typename... More> + I2CTransaction& write_ack(uint8_t data, More... more) { + write_ack(data); + write_ack(more...); + return *this; + } + + /* + * Enqueues a read of one byte into the given uint8. Responds with the given + * ACK/NACK type. + */ + I2CTransaction& read(uint8_t* dest, i2c_ack_type_t ack); + + /* Returns the underlying command buffer. */ + i2c_cmd_handle_t handle() { return handle_; } + + // Cannot be moved or copied, since doing so is probably an error. Pass a + // reference instead. + I2CTransaction(const I2CTransaction&) = delete; + I2CTransaction& operator=(const I2CTransaction&) = delete; + + private: + i2c_cmd_handle_t handle_; + uint8_t* buffer_; +}; + +} // namespace gay_ipod diff --git a/src/drivers/include/playback.hpp b/src/drivers/include/playback.hpp new file mode 100644 index 00000000..493dd311 --- /dev/null +++ b/src/drivers/include/playback.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "dac.hpp" +#include "storage.hpp" + +#include <cstdint> +#include <memory> +#include <string> + +#include "audio_common.h" +#include "audio_element.h" +#include "audio_event_iface.h" +#include "audio_pipeline.h" +#include "esp_err.h" +#include "fatfs_stream.h" +#include "i2s_stream.h" +#include "mp3_decoder.h" +#include "result.hpp" + +namespace gay_ipod { + +class DacAudioPlayback { + public: + enum Error { PIPELINE_INIT }; + static auto create(AudioDac* dac) + -> cpp::result<std::unique_ptr<DacAudioPlayback>, Error>; + + DacAudioPlayback(AudioDac* dac, + audio_pipeline_handle_t pipeline, + audio_element_handle_t fatfs_stream_reader, + audio_element_handle_t i2s_stream_writer, + audio_event_iface_handle_t event_interface, + audio_element_handle_t mp3_decoder); + ~DacAudioPlayback(); + + void Play(const std::string& filename); + void Resume(); + void Pause(); + + void ProcessEvents(); + + /* for gapless */ + void set_next_file(const std::string& filename); + + void set_volume(uint8_t volume); + auto volume() -> uint8_t; + + // Not copyable or movable. + DacAudioPlayback(const DacAudioPlayback&) = delete; + DacAudioPlayback& operator=(const DacAudioPlayback&) = delete; + + private: + AudioDac* dac_; + std::mutex playback_lock_; + + std::string next_filename_; + uint8_t volume_; + + audio_pipeline_handle_t pipeline_; + audio_element_handle_t fatfs_stream_reader_; + audio_element_handle_t i2s_stream_writer_; + audio_event_iface_handle_t event_interface_; + + audio_element_handle_t mp3_decoder_; +}; + +} // namespace gay_ipod diff --git a/src/drivers/include/storage.hpp b/src/drivers/include/storage.hpp new file mode 100644 index 00000000..cee49cc5 --- /dev/null +++ b/src/drivers/include/storage.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "gpio-expander.hpp" + +#include <memory> + +#include "driver/sdmmc_types.h" +#include "driver/sdspi_host.h" +#include "esp_err.h" +#include "esp_vfs_fat.h" +#include "result.hpp" + +namespace gay_ipod { + +extern const char* kStoragePath; + +class SdStorage { + public: + enum Error { + FAILED_TO_INIT, + /** We couldn't interact with the SD card at all. Is it missing? */ + FAILED_TO_READ, + /** We couldn't mount the SD card. Is it formatted? */ + FAILED_TO_MOUNT, + }; + + static auto create(GpioExpander* gpio) + -> cpp::result<std::unique_ptr<SdStorage>, Error>; + + SdStorage(GpioExpander* gpio, + esp_err_t (*do_transaction)(sdspi_dev_handle_t, sdmmc_command_t*), + sdspi_dev_handle_t handle_, + std::unique_ptr<sdmmc_host_t>& host_, + std::unique_ptr<sdmmc_card_t>& card_, + FATFS* fs_); + ~SdStorage(); + + auto HandleTransaction(sdspi_dev_handle_t handle, sdmmc_command_t* cmdinfo) + -> esp_err_t; + + // Not copyable or movable. + // TODO: maybe this could be movable? + SdStorage(const SdStorage&) = delete; + SdStorage& operator=(const SdStorage&) = delete; + + private: + GpioExpander* gpio_; + + esp_err_t (*do_transaction_)(sdspi_dev_handle_t, sdmmc_command_t*) = nullptr; + + // SPI and SD driver info + sdspi_dev_handle_t handle_; + std::unique_ptr<sdmmc_host_t> host_; + std::unique_ptr<sdmmc_card_t> card_; + + // Filesystem info + FATFS* fs_ = nullptr; +}; + +} // namespace gay_ipod |
