Skip to content

This document provides example code and wiring instructions for each peripheral on the 9Mod MCP Verification Board. All examples are built on the emMCP v1.0.1 framework.

The general workflow is:

  1. Create an emMCP_tool_t struct and fill in its fields (name, description, callback handlers)
  2. Call emMCP_AddToolToToolList() for each tool
  3. Call emMCP_RegistrationTools() to sync all tool definitions to the AI platform

Once registered, the AI can directly control peripherals or read sensor data through voice commands.

TIP

For the complete project source code, refer to the example/9Mod_MCPBoard/ directory in the emMCP repository.


1. Relay Control

Wiring

Relay TerminalDescription
NO (Normally Open)Open by default, closes when GPIO is high
COM (Common)Connect to the load line (e.g., light bulb, fan)
GND / VCCOnboard, no external wiring needed

STM32 control pin: PB5 (active high, optocoupler-isolated).

MCP Tool Registration

c
// Tool callback - control
static void emMCP_SetRelayHandler(void *arg)
{
    cJSON *param = (cJSON *)arg;
    cJSON *enable = cJSON_GetObjectItem(param, "enable");
    if (enable != NULL) {
        if (enable->valueint == 1) {
            HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_SET);
        } else {
            HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_RESET);
        }
        emMCP_ResponseValue(emMCP_CTRL_OK);
    } else {
        emMCP_ResponseValue(emMCP_CTRL_ERROR);
    }
}

// Tool callback - query
static void emMCP_GetRelayHandler(void *arg)
{
    emMCP_ResponseValue(emMCP_CTRL_OK);
}

// Register in main()
emMCP_tool_t relay;
relay.name = "relay";
relay.description = "Control the relay switch";
relay.inputSchema.properties[0].name = "enable";
relay.inputSchema.properties[0].description = "Whether to turn on the relay, true=on, false=off, null for query";
relay.inputSchema.properties[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN;
relay.setRequestHandler = emMCP_SetRelayHandler;
relay.checkRequestHandler = emMCP_GetRelayHandler;
emMCP_AddToolToToolList(&relay);
emMCP_RegistrationTools();

Voice Commands

"Turn on the relay" "Turn off the relay"


2. WS2812 LED Strip Control

Wiring

The WS2812 LED strip connects via the onboard 4-pin header:

WS2812 PinDescription
5VPower (onboard 5V output, external power recommended for long strips)
GNDGround
DINData input — PA11 (TIM1_CH4 PWM+DMA)
DOUTData output (cascade to next LED)

Supports RGB color and brightness control. Each LED is individually addressable.

MCP Tool Registration

c
// Tool callback - control
static void emMCP_SetWS2812Handler(void *arg)
{
    cJSON *param = (cJSON *)arg;

    cJSON *red = cJSON_GetObjectItem(param, "red");
    cJSON *green = cJSON_GetObjectItem(param, "green");
    cJSON *blue = cJSON_GetObjectItem(param, "blue");
    cJSON *brightness = cJSON_GetObjectItem(param, "brightness");

    uint8_t r = red ? red->valueint : 0;
    uint8_t g = green ? green->valueint : 0;
    uint8_t b = blue ? blue->valueint : 0;
    uint8_t bri = brightness ? brightness->valueint : 255;

    WS2812_SetColor(r, g, b, bri);
    emMCP_ResponseValue(emMCP_CTRL_OK);
}

// Tool callback - query
static void emMCP_GetWS2812Handler(void *arg)
{
    emMCP_ResponseValue(emMCP_CTRL_OK);
}

// Register in main()
emMCP_tool_t ws2812;
ws2812.name = "ws2812";
ws2812.description = "Control WS2812 LED strip color and brightness";
ws2812.inputSchema.properties[0].name = "red";
ws2812.inputSchema.properties[0].description = "Red value 0-255, null for query";
ws2812.inputSchema.properties[0].type = MCP_SERVER_TOOL_TYPE_NUMBLE;
ws2812.inputSchema.properties[1].name = "green";
ws2812.inputSchema.properties[1].description = "Green value 0-255, null for query";
ws2812.inputSchema.properties[1].type = MCP_SERVER_TOOL_TYPE_NUMBLE;
ws2812.inputSchema.properties[2].name = "blue";
ws2812.inputSchema.properties[2].description = "Blue value 0-255, null for query";
ws2812.inputSchema.properties[2].type = MCP_SERVER_TOOL_TYPE_NUMBLE;
ws2812.inputSchema.properties[3].name = "brightness";
ws2812.inputSchema.properties[3].description = "Brightness 0-255, null for query";
ws2812.inputSchema.properties[3].type = MCP_SERVER_TOOL_TYPE_NUMBLE;
ws2812.setRequestHandler = emMCP_SetWS2812Handler;
ws2812.checkRequestHandler = emMCP_GetWS2812Handler;
emMCP_AddToolToToolList(&ws2812);
emMCP_RegistrationTools();

Voice Commands

"Set the LED strip to red" "Set brightness to 50%" "Turn off the LED strip"


3. SHT30 Temperature & Humidity Sensor

Wiring

SHT30 shares the GPIO bit-banged I²C bus (PB6(SDA) / PB7(SCL)) with the PD decoy, using separate device addresses:

DeviceI²C Address
SHT30 Temp/Humidity0x44
CH224K PD Decoy0x40

OLED uses SPI (dedicated chip select) and does not share the I²C bus.

MCP Tool Registration

c
// Tool callback - control (read-only sensor, returns error on control attempt)
static void emMCP_SetSHT30Handler(void *arg)
{
    emMCP_ResponseValue(emMCP_CTRL_ERROR);
}

// Tool callback - query
static void emMCP_GetSHT30Handler(void *arg)
{
    float temp = 0, humi = 0;
    SHT30_ReadData(&temp, &humi);

    cJSON *result = cJSON_CreateObject();
    cJSON_AddNumberToObject(result, "temperature", temp);
    cJSON_AddNumberToObject(result, "humidity", humi);
    cJSON_AddStringToObject(result, "unit_temp", "°C");
    cJSON_AddStringToObject(result, "unit_humi", "%");
    char *json_str = cJSON_PrintUnformatted(result);
    emMCP_ResponseValue(json_str);
    cJSON_Delete(result);
    cJSON_free(json_str);
}

// Register in main()
emMCP_tool_t sht30;
sht30.name = "sht30";
sht30.description = "Get ambient temperature and humidity";
sht30.inputSchema.properties[0].name = "temperature";
sht30.inputSchema.properties[0].description = "Temperature in °C, null for control";
sht30.inputSchema.properties[0].type = MCP_SERVER_TOOL_TYPE_NUMBLE;
sht30.inputSchema.properties[1].name = "humidity";
sht30.inputSchema.properties[1].description = "Humidity in %, null for control";
sht30.inputSchema.properties[1].type = MCP_SERVER_TOOL_TYPE_NUMBLE;
sht30.setRequestHandler = emMCP_SetSHT30Handler;
sht30.checkRequestHandler = emMCP_GetSHT30Handler;
emMCP_AddToolToToolList(&sht30);
emMCP_RegistrationTools();

Voice Commands

"What's the temperature and humidity?" "Check the current temperature"


4. PD Decoy Power Control

Wiring

PD decoy uses a CH224K chip configured via I²C. Connect a PD charger to the dedicated Type-C port.

⚠️ High Voltage Warning: PD decoy output up to 20V/3A (60W). Verify the rated voltage of the external load before connecting. Do not short-circuit. Do not plug/unplug high-voltage loads while powered. For first use, start testing at 5V.

PD DecoySTM32 Pin
SDAPB6
SCLPB7
I²C Address0x40

MCP Tool Registration

c
// Tool callback - control
static void emMCP_SetPDHandler(void *arg)
{
    cJSON *param = (cJSON *)arg;
    cJSON *voltage = cJSON_GetObjectItem(param, "voltage");

    if (voltage) {
        CH224K_SetVoltage(voltage->valueint);
        emMCP_ResponseValue(emMCP_CTRL_OK);
    } else {
        emMCP_ResponseValue(emMCP_CTRL_ERROR);
    }
}

// Tool callback - query
static void emMCP_GetPDHandler(void *arg)
{
    emMCP_ResponseValue(emMCP_CTRL_OK);
}

// Register in main()
emMCP_tool_t pd;
pd.name = "pd_decoy";
pd.description = "Set PD decoy output voltage";
pd.inputSchema.properties[0].name = "voltage";
pd.inputSchema.properties[0].description = "Target voltage options: 5/9/12/15/20, null for query";
pd.inputSchema.properties[0].type = MCP_SERVER_TOOL_TYPE_NUMBLE;
pd.setRequestHandler = emMCP_SetPDHandler;
pd.checkRequestHandler = emMCP_GetPDHandler;
emMCP_AddToolToToolList(&pd);
emMCP_RegistrationTools();

Voice Commands

"Set PD output to 12V" "Boost PD to 20V"


5. OLED Display Control

Wiring

0.96" OLED (SSD1306, 128×64) uses SPI1. Onboard GT20L61S Chinese font chip shares the same SPI bus via separate chip select.

OLED PinSTM32 PinDescription
OLED_DCPA1Data/Command select
OLED_CS1PA4OLED chip select
CS2PB0GT20L61S font chip select
SCLKPA5 (SPI1_SCK)SPI clock
MISOPA6 (SPI1_MISO)SPI data input (font chip read)
MOSIPA7 (SPI1_MOSI)SPI data output

MCP Tool Registration

c
// Tool callback - control
static void emMCP_SetOLEDHandler(void *arg)
{
    cJSON *param = (cJSON *)arg;
    cJSON *text = cJSON_GetObjectItem(param, "text");

    if (text) {
        OLED_Clear();
        OLED_ShowString(0, 0, text->valuestring);
        OLED_Refresh();
        emMCP_ResponseValue(emMCP_CTRL_OK);
    } else {
        emMCP_ResponseValue(emMCP_CTRL_ERROR);
    }
}

// Tool callback - query
static void emMCP_GetOLEDHandler(void *arg)
{
    emMCP_ResponseValue(emMCP_CTRL_OK);
}

// Register in main()
emMCP_tool_t oled;
oled.name = "oled_display";
oled.description = "Display text on the OLED screen";
oled.inputSchema.properties[0].name = "text";
oled.inputSchema.properties[0].description = "Text to display, null for query";
oled.inputSchema.properties[0].type = MCP_SERVER_TOOL_TYPE_STRING;
oled.setRequestHandler = emMCP_SetOLEDHandler;
oled.checkRequestHandler = emMCP_GetOLEDHandler;
emMCP_AddToolToToolList(&oled);
emMCP_RegistrationTools();

Voice Commands

"Display 'Hello' on the OLED" "Show the current temperature on the screen"


6. Full Example: Register All Peripherals

c
void RegisterAllTools(void)
{
    // LED
    emMCP_AddToolToToolList(&led);
    // Relay
    emMCP_AddToolToToolList(&relay);
    // WS2812
    emMCP_AddToolToToolList(&ws2812);
    // Temperature/Humidity
    emMCP_AddToolToToolList(&sht30);
    // PD Decoy
    emMCP_AddToolToToolList(&pd);
    // OLED Display
    emMCP_AddToolToToolList(&oled);

    // Register all tools to XiaoZhi AI at once
    emMCP_RegistrationTools();
}

After registering all tools, the single call to emMCP_RegistrationTools() syncs all tool definitions to the AI platform. The AI can then recognize voice commands, match them to the appropriate tool, and send an mcp_set or mcp_check command over UART.

Verification

  1. Compile check: Ensure the project compiles without errors
  2. Serial log: After flashing, connect the debug serial port (USART1, 1500000 baud). Say "Hello XiaoZhi" to the module. You should see [DEBUG] emMCP_EventCallback: event:8 in the log
  3. Tool sync: When tool_list transmission success appears in the serial log, the tools have been registered with the AI platform
  4. Functional test: Speak the corresponding voice commands from the examples above and observe the peripheral response

Further Reading

Released under the MIT License.