Skip to content

Commit 798a877

Browse files
committed
Add test support and disable analog audio bypass
1 parent 3ee954a commit 798a877

5 files changed

Lines changed: 239 additions & 31 deletions

File tree

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,45 @@ M5.begin();
2626
audio.begin(Wire);
2727
```
2828

29+
## Microphone routing
30+
31+
The LINE/MIC jack and the headset microphone share the ES8388 `LIN1` input. Keep the two STM32-controlled paths
32+
mutually exclusive.
33+
34+
Use the LINE/MIC jack:
35+
36+
```cpp
37+
audio.setHPMICStatus(AUDIO_MIC_CLOSE);
38+
audio.setMICStatus(AUDIO_MIC_OPEN);
39+
audio.setMicInputLine(ADC_INPUT_LINPUT1_RINPUT1);
40+
```
41+
42+
Use the microphone in a connected headset:
43+
44+
```cpp
45+
if (audio.getHPInsertStatus()) {
46+
audio.setMICStatus(AUDIO_MIC_CLOSE);
47+
audio.setHPMICStatus(AUDIO_MIC_OPEN);
48+
audio.setMicInputLine(ADC_INPUT_LINPUT1_RINPUT1);
49+
}
50+
```
51+
52+
Register `0x11` is reset to `AUDIO_MIC_CLOSE` at power-up. Headset insertion or removal does not change it.
53+
54+
## Device UID
55+
56+
Firmware `0xF1` exposes the STM32 96-bit UID in registers `0xE0` through `0xEB`:
57+
58+
```cpp
59+
uint8_t uid[DEVICE_UID_LENGTH];
60+
if (audio.getDeviceUID(uid)) {
61+
for (uint8_t value : uid) {
62+
Serial.printf("%02X", value);
63+
}
64+
Serial.println();
65+
}
66+
```
67+
2968
## Related Link
3069

3170
- [Document & Datasheet](https://docs.m5stack.com/en/module/Module-Audio)

src/M5Module_Audio.cpp

Lines changed: 96 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,12 @@ void M5ModuleAudio::setHPMode(audio_hpmode_t mode)
216216
writeBytes(_addr, reg, (uint8_t *)&mode, 1);
217217
}
218218

219+
void M5ModuleAudio::setHPMICStatus(audio_mic_t status)
220+
{
221+
uint8_t reg = HEADPHONE_MIC_STATUS;
222+
writeBytes(_addr, reg, (uint8_t *)&status, 1);
223+
}
224+
219225
void M5ModuleAudio::setRGBBrightness(uint8_t brightness)
220226
{
221227
if (brightness > 100) brightness = 100;
@@ -226,14 +232,35 @@ void M5ModuleAudio::setRGBBrightness(uint8_t brightness)
226232
void M5ModuleAudio::setRGBLED(uint8_t num, uint32_t color)
227233
{
228234
if (num > 2) num = 2;
229-
color = ((color & 0xFF) << 16) | (color & 0xFF00) | ((color >> 16) & 0xFF);
230-
uint8_t reg = RGB_LED + num * 3;
231-
writeBytes(_addr, reg, (uint8_t *)&color, 3);
235+
uint8_t rgb[3] = {
236+
static_cast<uint8_t>((color >> 16) & 0xFF),
237+
static_cast<uint8_t>((color >> 8) & 0xFF),
238+
static_cast<uint8_t>(color & 0xFF),
239+
};
240+
uint8_t reg = RGB_LED + num * sizeof(rgb);
241+
writeBytes(_addr, reg, rgb, sizeof(rgb));
242+
}
243+
244+
void M5ModuleAudio::setAllRGBLED(uint32_t color)
245+
{
246+
const uint8_t red = static_cast<uint8_t>((color >> 16) & 0xFF);
247+
const uint8_t green = static_cast<uint8_t>((color >> 8) & 0xFF);
248+
const uint8_t blue = static_cast<uint8_t>(color & 0xFF);
249+
uint8_t rgb[9];
250+
for (size_t index = 0; index < sizeof(rgb); index += 3) {
251+
rgb[index] = red;
252+
rgb[index + 1] = green;
253+
rgb[index + 2] = blue;
254+
}
255+
writeBytes(_addr, RGB_LED, rgb, sizeof(rgb));
232256
}
233257

234258
uint8_t M5ModuleAudio::setI2CAddress(uint8_t newAddr)
235259
{
236-
newAddr = constrain(newAddr, I2C_ADDR_MIN, I2C_ADDR_MAX);
260+
if (newAddr < I2C_ADDR_MIN || newAddr > I2C_ADDR_MAX || newAddr == ES8388_ADDR) {
261+
return _addr;
262+
}
263+
237264
uint8_t reg = I2C_ADDRESS;
238265
writeBytes(_addr, reg, (uint8_t *)&newAddr, 1);
239266
_addr = newAddr;
@@ -265,6 +292,14 @@ audio_hpmode_t M5ModuleAudio::getHPMode()
265292
return (audio_hpmode_t)data;
266293
}
267294

295+
audio_mic_t M5ModuleAudio::getHPMICStatus()
296+
{
297+
uint8_t data;
298+
uint8_t reg = HEADPHONE_MIC_STATUS;
299+
readBytes(_addr, reg, (uint8_t *)&data, 1);
300+
return (audio_mic_t)data;
301+
}
302+
268303
uint8_t M5ModuleAudio::getHPInsertStatus()
269304
{
270305
uint8_t data;
@@ -289,6 +324,26 @@ uint32_t M5ModuleAudio::getRGBLED(uint8_t num)
289324
return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
290325
}
291326

327+
bool M5ModuleAudio::getRGBLEDs(uint32_t colors[3])
328+
{
329+
if (colors == nullptr) {
330+
return false;
331+
}
332+
333+
uint8_t rgb[9] = {};
334+
if (!readBytes(_addr, RGB_LED, rgb, sizeof(rgb))) {
335+
colors[0] = colors[1] = colors[2] = 0;
336+
return false;
337+
}
338+
339+
for (size_t index = 0; index < 3; ++index) {
340+
const size_t offset = index * 3;
341+
colors[index] = (static_cast<uint32_t>(rgb[offset]) << 16) | (static_cast<uint32_t>(rgb[offset + 1]) << 8) |
342+
static_cast<uint32_t>(rgb[offset + 2]);
343+
}
344+
return true;
345+
}
346+
292347
uint8_t M5ModuleAudio::getFirmwareVersion()
293348
{
294349
uint8_t version;
@@ -305,6 +360,14 @@ uint8_t M5ModuleAudio::getI2CAddress()
305360
return I2CAddress;
306361
}
307362

363+
bool M5ModuleAudio::getDeviceUID(uint8_t uid[DEVICE_UID_LENGTH])
364+
{
365+
if (uid == nullptr) {
366+
return false;
367+
}
368+
return readBytes(_addr, DEVICE_UID, uid, DEVICE_UID_LENGTH);
369+
}
370+
308371
bool M5ModuleAudio::es8388_codec_init()
309372
{
310373
if (es8388 != nullptr) {
@@ -511,6 +574,20 @@ bool M5ModuleAudio::setMixSourceSelect(es_mixsel_t lmixsel, es_mixsel_t rmixsel)
511574
return true;
512575
}
513576

577+
bool M5ModuleAudio::setLineBypass(bool enabled)
578+
{
579+
if (es8388 == nullptr) {
580+
ESP_LOGI(TAG, "ES8388 codec is not initialized");
581+
return false;
582+
}
583+
584+
if (!es8388->setLineBypass(enabled)) {
585+
ESP_LOGI(TAG, "Failed to configure ES8388 line bypass");
586+
return false;
587+
}
588+
return true;
589+
}
590+
514591
bool M5ModuleAudio::setBitsSample(es_module_t mode, es_bits_length_t bits_len)
515592
{
516593
if (es8388 == nullptr) {
@@ -726,13 +803,13 @@ bool M5ModuleAudio::play(const uint8_t *buffer, int size)
726803
size_t bytes_written = 0;
727804
#if USE_NEW_I2S_API
728805
bytes_written = I2S.write(buffer, size);
729-
if (bytes_written < 0) {
806+
if (bytes_written != static_cast<size_t>(size)) {
730807
ESP_LOGI(TAG, "Playback failed");
731808
return false;
732809
}
733810
#else
734811
esp_err_t err = i2s_write(i2s_num, buffer, size, &bytes_written, portMAX_DELAY);
735-
if (err != ESP_OK) {
812+
if (err != ESP_OK || bytes_written != static_cast<size_t>(size)) {
736813
ESP_LOGI(TAG, "Playback failed");
737814
return false;
738815
}
@@ -774,25 +851,32 @@ void M5ModuleAudio::writeBytes(uint8_t addr, uint8_t reg, uint8_t *buffer, uint8
774851
#endif
775852
}
776853

777-
void M5ModuleAudio::readBytes(uint8_t addr, uint8_t reg, uint8_t *buffer, uint8_t length)
854+
bool M5ModuleAudio::readBytes(uint8_t addr, uint8_t reg, uint8_t *buffer, uint8_t length)
778855
{
856+
if (buffer == nullptr || length == 0) {
857+
return false;
858+
}
859+
779860
if (_m5_i2c != nullptr) {
780861
if (!_m5_i2c->readRegister(addr, reg, buffer, length, _i2c_speed)) {
781862
memset(buffer, 0, length);
863+
return false;
782864
}
783-
return;
865+
return true;
784866
}
785867

786868
if (_wire == nullptr) {
787869
memset(buffer, 0, length);
788-
return;
870+
return false;
789871
}
790872

791873
uint8_t index = 0;
792874
_wire->beginTransmission(addr);
793875
_wire->write(reg);
794-
_wire->endTransmission(false);
795-
_wire->requestFrom(addr, length);
876+
if (_wire->endTransmission(false) != 0 || _wire->requestFrom(addr, length) != length) {
877+
memset(buffer, 0, length);
878+
return false;
879+
}
796880
for (int i = 0; i < length; i++) {
797881
buffer[index++] = _wire->read();
798882
}
@@ -811,4 +895,5 @@ void M5ModuleAudio::readBytes(uint8_t addr, uint8_t reg, uint8_t *buffer, uint8_
811895
Serial.println("]");
812896
#else
813897
#endif
898+
return true;
814899
}

src/M5Module_Audio.h

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@
3939
#define I2C_ADDR_MAX (0x77)
4040
#define MICROPHONE_STATUS (0x00)
4141
#define HEADPHONE_MODE (0x10)
42+
#define HEADPHONE_MIC_STATUS (0x11)
4243
#define HEADPHONE_INSERT_STATUS (0x20)
4344
#define RGB_LED_BRIGHTNESS (0x30)
4445
#define RGB_LED (0x40)
46+
#define DEVICE_UID (0xE0)
47+
#define DEVICE_UID_LENGTH (12)
4548
#define FLASH_WRITE (0xF0)
4649
#define FIRMWARE_VERSION (0xFE)
4750
#define I2C_ADDRESS (0xFF)
@@ -63,12 +66,12 @@ typedef enum { AUDIO_MIC_CLOSE = 0, AUDIO_MIC_OPEN } audio_mic_t;
6366
* @brief Headphone output mode configuration options
6467
* @details Corresponds to register 0x10 (R/W)
6568
*
66-
* @var AUDIO_HPMODE_NATIONAL
67-
* National Standard audio mode (value: 0, default)
6869
* @var AUDIO_HPMODE_AMERICAN
69-
* American Standard audio mode (value: 1)
70+
* American Standard (CTIA) audio mode (value: 0, default)
71+
* @var AUDIO_HPMODE_NATIONAL
72+
* National Standard (OMTP) audio mode (value: 1)
7073
*/
71-
typedef enum { AUDIO_HPMODE_NATIONAL = 0, AUDIO_HPMODE_AMERICAN } audio_hpmode_t;
74+
typedef enum { AUDIO_HPMODE_AMERICAN = 0, AUDIO_HPMODE_NATIONAL = 1 } audio_hpmode_t;
7275

7376
/**
7477
* @brief Class to manage M5ModuleAudio operations including audio input/output and codec control.
@@ -133,6 +136,13 @@ class M5ModuleAudio {
133136
*/
134137
void setHPMode(audio_hpmode_t mode);
135138

139+
/**
140+
* @brief Enable or disable the microphone in the headset jack.
141+
* @param status Headset microphone state (AUDIO_MIC_CLOSE/AUDIO_MIC_OPEN).
142+
* @note Controls register 0x11 (R/W). This setting is not persisted.
143+
*/
144+
void setHPMICStatus(audio_mic_t status);
145+
136146
/**
137147
* @brief Set global brightness for all RGB LEDs
138148
* @param brightness Brightness value (0-100)
@@ -142,13 +152,20 @@ class M5ModuleAudio {
142152

143153
/**
144154
* @brief Set color for specific RGB LED
145-
* @param num LED number (1-3)
155+
* @param num Zero-based LED number (0-2)
146156
* @param color 24-bit color value (0xRRGGBB)
147157
* @note Writes to register 0x40 (R/W). Color components are automatically
148158
* decomposed into 3 bytes (R, G, B)
149159
*/
150160
void setRGBLED(uint8_t num, uint32_t color);
151161

162+
/**
163+
* @brief Set all three RGB LEDs to the same color in one I2C transaction
164+
* @param color 24-bit color value (0xRRGGBB)
165+
* @note Writes 9 bytes to registers 0x40-0x48 in R, G, B order
166+
*/
167+
void setAllRGBLED(uint32_t color);
168+
152169
/**
153170
* @brief Trigger configuration save to internal Flash
154171
* @note Writes 1 to register 0xF0 (Write-Only). Requires 20ms for Flash erase.
@@ -158,9 +175,9 @@ class M5ModuleAudio {
158175

159176
/**
160177
* @brief Set device I2C address
161-
* @param newAddr New I2C address (0x08-0x77)
162-
* @return Actual set address (may differ if invalid input)
163-
* @note Writes to register 0xFF (R/W). Requires reboot to take effect.
178+
* @param newAddr New I2C address (0x08-0x77, excluding 0x10 reserved for ES8388)
179+
* @return Current device address; unchanged if newAddr is invalid
180+
* @note Writes to register 0xFF (R/W). The new address takes effect immediately and is saved automatically.
164181
*/
165182
uint8_t setI2CAddress(uint8_t newAddr);
166183

@@ -178,6 +195,13 @@ class M5ModuleAudio {
178195
*/
179196
audio_hpmode_t getHPMode();
180197

198+
/**
199+
* @brief Get the microphone state of the headset jack.
200+
* @return Current headset microphone state.
201+
* @note Reads register 0x11 (R/W).
202+
*/
203+
audio_mic_t getHPMICStatus();
204+
181205
/**
182206
* @brief Get headphone insertion status
183207
* @return 0=Not inserted, 1=Inserted
@@ -194,12 +218,19 @@ class M5ModuleAudio {
194218

195219
/**
196220
* @brief Get current color for specific RGB LED
197-
* @param num LED number (1-3)
221+
* @param num Zero-based LED number (0-2)
198222
* @return 24-bit color value (0xRRGGBB)
199223
* @note Reads from register 0x40 (R/W). Combines 3 bytes (R, G, B) into color
200224
*/
201225
uint32_t getRGBLED(uint8_t num);
202226

227+
/**
228+
* @brief Read all three RGB LED colors in one I2C transaction
229+
* @param colors Output array of three 0xRRGGBB color values
230+
* @return true when all 9 register bytes were read successfully
231+
*/
232+
bool getRGBLEDs(uint32_t colors[3]);
233+
203234
/**
204235
* @brief Get firmware version
205236
* @return 4-byte version code (e.g., 0x01020304 = v1.2.3.4)
@@ -214,6 +245,14 @@ class M5ModuleAudio {
214245
*/
215246
uint8_t getI2CAddress();
216247

248+
/**
249+
* @brief Read the STM32 96-bit unique device identifier.
250+
* @param uid Destination buffer with room for DEVICE_UID_LENGTH bytes.
251+
* @return True when all 12 bytes were read successfully.
252+
* @note Reads registers 0xE0 through 0xEB in little-endian word order.
253+
*/
254+
bool getDeviceUID(uint8_t uid[DEVICE_UID_LENGTH]);
255+
217256
/**
218257
* @brief Sets the speaker volume.
219258
*
@@ -279,6 +318,14 @@ class M5ModuleAudio {
279318
*/
280319
bool setMixSourceSelect(es_mixsel_t lmixsel, es_mixsel_t rmixsel);
281320

321+
/**
322+
* @brief Enables or disables the ES8388 analog input bypass to the output mixers.
323+
*
324+
* @param enabled True to mix analog input into the outputs, false for DAC-only output.
325+
* @return bool True if both mixer registers were updated successfully.
326+
*/
327+
bool setLineBypass(bool enabled);
328+
282329
/**
283330
* @brief Configures ES8388 sample width.
284331
*
@@ -414,8 +461,9 @@ class M5ModuleAudio {
414461
* @param reg The register address from which the data will be read.
415462
* @param buffer A pointer to the data buffer where the read bytes will be stored.
416463
* @param length The number of bytes to read into the buffer.
464+
* @return True when the complete register range was read successfully.
417465
*/
418-
void readBytes(uint8_t addr, uint8_t reg, uint8_t* buffer, uint8_t length);
466+
bool readBytes(uint8_t addr, uint8_t reg, uint8_t* buffer, uint8_t length);
419467

420468
// Initializes the ES8388 codec
421469
bool es8388_codec_init();

0 commit comments

Comments
 (0)