Skip to content

Commit 58ec848

Browse files
committed
Add ESP-IDF native SPI support borrowing a pre-installed device handle
1 parent 1ea7dd9 commit 58ec848

6 files changed

Lines changed: 216 additions & 1 deletion

File tree

src/M5UnitComponent.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,15 @@ bool Component::assign(const uart_port_t uart_num)
244244
}
245245
return false;
246246
}
247+
248+
bool Component::assign(spi_device_handle_t handle, const gpio_num_t cs)
249+
{
250+
if (canAccessSPI()) {
251+
_adapter = std::make_shared<AdapterSPI>(handle, cs);
252+
return static_cast<bool>(_adapter);
253+
}
254+
return false;
255+
}
247256
#endif
248257

249258
bool Component::selectChannel(const uint8_t ch)

src/M5UnitComponent.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
#include "m5_unit_component/types.hpp"
1414
#include "m5_unit_component/adapter.hpp"
1515
#if defined(ESP_PLATFORM)
16-
#include <driver/uart.h> // for uart_port_t
16+
#include <driver/uart.h> // for uart_port_t
17+
#include <driver/spi_master.h> // for spi_device_handle_t
1718
#endif
1819
#if defined(ESP_PLATFORM) && __has_include(<driver/i2c_master.h>)
1920
#include <driver/i2c_master.h> // for i2c_master_bus_handle_t
@@ -344,6 +345,8 @@ class Component {
344345
#if defined(ESP_PLATFORM)
345346
/*! @brief Assign UART (ESP-IDF native driver, pre-installed port) */
346347
virtual bool assign(const uart_port_t uart_num);
348+
//! @brief Assign SPI device handle (ESP-IDF native, borrowed; cs controlled manually)
349+
virtual bool assign(spi_device_handle_t handle, const gpio_num_t cs);
347350
#endif
348351
///@}
349352

src/M5UnitUnified.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,31 @@ bool UnitUnified::add(Component& u, const uart_port_t uart_num)
202202
}
203203
#endif
204204

205+
#if defined(ESP_PLATFORM)
206+
bool UnitUnified::add(Component& u, spi_device_handle_t handle, const gpio_num_t cs)
207+
{
208+
if (u.isRegistered()) {
209+
M5_LIB_LOGW("Already added");
210+
return false;
211+
}
212+
if (!handle) {
213+
M5_LIB_LOGE("SPI handle null");
214+
return false;
215+
}
216+
217+
M5_LIB_LOGD("Add [%s] children:%zu", u.deviceName(), u.childrenSize());
218+
219+
u._manager = this;
220+
if (u.assign(handle, cs)) {
221+
u._order = ++_registerCount;
222+
_units.emplace_back(&u);
223+
return add_children(u);
224+
}
225+
M5_LIB_LOGE("Failed to assign %s:%u", u.deviceName(), u.canAccessSPI());
226+
return false;
227+
}
228+
#endif
229+
205230
// Add children if exists (iterative to avoid stack overflow)
206231
bool UnitUnified::add_children(Component& u)
207232
{

src/M5UnitUnified.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,16 @@ class UnitUnified {
154154
@return True if successful
155155
*/
156156
bool add(Component& u, SPIClass& spi, const SPISettings& settings);
157+
#endif
158+
#if defined(ESP_PLATFORM)
159+
/*!
160+
@brief Adding unit to be managed (SPI, ESP-IDF native driver)
161+
@param u Unit Component
162+
@param handle ESP-IDF SPI device handle (create with spics_io_num = -1; init bus with SPI_DMA_DISABLED)
163+
@param cs CS GPIO controlled manually by this library
164+
@return True if successful
165+
*/
166+
bool add(Component& u, spi_device_handle_t handle, const gpio_num_t cs);
157167
#endif
158168
///@}
159169

src/m5_unit_component/adapter_spi.cpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
@note Currently handles SPI directly, but will handle via M5HAL in the future
1010
*/
1111
#include "adapter_spi.hpp"
12+
#if defined(ESP_PLATFORM)
13+
#include <driver/gpio.h>
14+
#endif
1215
#include <M5HAL.hpp>
1316
#include <M5Utility.hpp>
1417
#include <cassert>
@@ -101,5 +104,140 @@ AdapterSPI::AdapterSPI(SPIClass& spi, const SPISettings& settings, const uint8_t
101104
}
102105
#endif
103106

107+
#if defined(ESP_PLATFORM)
108+
namespace {
109+
m5::hal::error::error_t to_spi_error(const esp_err_t err)
110+
{
111+
switch (err) {
112+
case ESP_OK:
113+
return m5::hal::error::error_t::OK;
114+
case ESP_ERR_INVALID_ARG:
115+
case ESP_ERR_INVALID_STATE:
116+
return m5::hal::error::error_t::INVALID_ARGUMENT;
117+
case ESP_ERR_TIMEOUT:
118+
return m5::hal::error::error_t::TIMEOUT_ERROR;
119+
default:
120+
return m5::hal::error::error_t::UNKNOWN_ERROR;
121+
}
122+
}
123+
} // namespace
124+
125+
AdapterSPI::ESPIDFImpl::ESPIDFImpl(spi_device_handle_t handle, const gpio_num_t cs)
126+
: AdapterSPI::SPIImpl(), _handle(handle), _cs(cs)
127+
{
128+
if (_cs != GPIO_NUM_NC) {
129+
gpio_set_direction(_cs, GPIO_MODE_OUTPUT);
130+
gpio_set_level(_cs, 1); // Idle high
131+
}
132+
}
133+
134+
void AdapterSPI::ESPIDFImpl::beginTransaction()
135+
{
136+
if (_in_transaction) {
137+
M5_LIB_LOGE("Don't nest!");
138+
return;
139+
}
140+
_in_transaction = true;
141+
spi_device_acquire_bus(_handle, portMAX_DELAY);
142+
if (_cs != GPIO_NUM_NC) {
143+
gpio_set_level(_cs, 0);
144+
}
145+
}
146+
147+
void AdapterSPI::ESPIDFImpl::endTransaction()
148+
{
149+
if (!_in_transaction) {
150+
M5_LIB_LOGE("Don't nest!");
151+
return;
152+
}
153+
if (_cs != GPIO_NUM_NC) {
154+
gpio_set_level(_cs, 1);
155+
}
156+
spi_device_release_bus(_handle);
157+
_in_transaction = false;
158+
}
159+
160+
m5::hal::error::error_t AdapterSPI::ESPIDFImpl::do_transmit(const uint8_t* tx, uint8_t* rx, const size_t len)
161+
{
162+
const uint8_t* tp = tx;
163+
uint8_t* rp = rx;
164+
size_t remain = len;
165+
while (remain) {
166+
const size_t n = (remain > 64) ? 64 : remain;
167+
spi_transaction_t t{};
168+
t.length = n * 8; // In bits
169+
if (n <= 4) {
170+
// Inline 4-byte path: no DMA buffer required (always safe)
171+
t.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA;
172+
for (size_t i = 0; i < n; ++i) {
173+
t.tx_data[i] = tp ? tp[i] : 0xFF; // Send 0xFF dummy on read (matches Arduino)
174+
}
175+
} else {
176+
// 4 < n <= 64: FIFO via DMA-disabled bus accepts stack buffers
177+
t.tx_buffer = tp; // nullptr -> driver sends zeros
178+
t.rx_buffer = rp;
179+
}
180+
const esp_err_t err = spi_device_polling_transmit(_handle, &t);
181+
if (err != ESP_OK) {
182+
return to_spi_error(err);
183+
}
184+
if (n <= 4 && rp) {
185+
for (size_t i = 0; i < n; ++i) {
186+
rp[i] = t.rx_data[i];
187+
}
188+
}
189+
if (tp) {
190+
tp += n;
191+
}
192+
if (rp) {
193+
rp += n;
194+
}
195+
remain -= n;
196+
}
197+
return m5::hal::error::error_t::OK;
198+
}
199+
200+
m5::hal::error::error_t AdapterSPI::ESPIDFImpl::readWithTransaction(uint8_t* data, const size_t len)
201+
{
202+
if (!data) {
203+
return m5::hal::error::error_t::INVALID_ARGUMENT;
204+
}
205+
return do_transmit(nullptr, data, len);
206+
}
207+
208+
m5::hal::error::error_t AdapterSPI::ESPIDFImpl::writeWithTransaction(const uint8_t* data, const size_t len,
209+
const uint32_t /* unused */)
210+
{
211+
return do_transmit(data, nullptr, len);
212+
}
213+
214+
m5::hal::error::error_t AdapterSPI::ESPIDFImpl::writeWithTransaction(const uint8_t reg, const uint8_t* data,
215+
const size_t len, const uint32_t /* unused */)
216+
{
217+
auto ret = do_transmit(&reg, nullptr, 1);
218+
if (data && len && ret == m5::hal::error::error_t::OK) {
219+
ret = do_transmit(data, nullptr, len);
220+
}
221+
return ret;
222+
}
223+
224+
m5::hal::error::error_t AdapterSPI::ESPIDFImpl::writeWithTransaction(const uint16_t reg, const uint8_t* data,
225+
const size_t len, const uint32_t /* unused */)
226+
{
227+
m5::types::big_uint16_t r(reg);
228+
auto ret = do_transmit(r.data(), nullptr, r.size());
229+
if (data && len && ret == m5::hal::error::error_t::OK) {
230+
ret = do_transmit(data, nullptr, len);
231+
}
232+
return ret;
233+
}
234+
235+
AdapterSPI::AdapterSPI(spi_device_handle_t handle, const gpio_num_t cs)
236+
: Adapter(Adapter::Type::SPI, new AdapterSPI::ESPIDFImpl(handle, cs))
237+
{
238+
assert(_impl);
239+
}
240+
#endif
241+
104242
} // namespace unit
105243
} // namespace m5

src/m5_unit_component/adapter_spi.hpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
#else
1818
class SPIClass;
1919
#endif
20+
#if defined(ESP_PLATFORM)
21+
#include <driver/spi_master.h>
22+
#endif
2023

2124
namespace m5 {
2225
namespace unit {
@@ -79,9 +82,36 @@ class AdapterSPI : public Adapter {
7982
};
8083
#endif
8184

85+
#if defined(ESP_PLATFORM)
86+
class ESPIDFImpl : public SPIImpl {
87+
public:
88+
ESPIDFImpl(spi_device_handle_t handle, const gpio_num_t cs);
89+
virtual void beginTransaction() override;
90+
virtual void endTransaction() override;
91+
virtual m5::hal::error::error_t readWithTransaction(uint8_t* data, const size_t len) override;
92+
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len,
93+
const uint32_t stop) override;
94+
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t reg, const uint8_t* data, const size_t len,
95+
const uint32_t stop) override;
96+
virtual m5::hal::error::error_t writeWithTransaction(const uint16_t reg, const uint8_t* data, const size_t len,
97+
const uint32_t stop) override;
98+
99+
protected:
100+
m5::hal::error::error_t do_transmit(const uint8_t* tx, uint8_t* rx, const size_t len);
101+
102+
private:
103+
spi_device_handle_t _handle{};
104+
gpio_num_t _cs{GPIO_NUM_NC};
105+
bool _in_transaction{false};
106+
};
107+
#endif
108+
82109
#if defined(ARDUINO)
83110
AdapterSPI(SPIClass& spi, const SPISettings& settings, const uint8_t cs);
84111
#endif
112+
#if defined(ESP_PLATFORM)
113+
AdapterSPI(spi_device_handle_t handle, const gpio_num_t cs);
114+
#endif
85115

86116
inline SPIImpl* impl()
87117
{

0 commit comments

Comments
 (0)