Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save blinkinglight/9b03a61d8f8fb877200f58a1f78728ad to your computer and use it in GitHub Desktop.
Save blinkinglight/9b03a61d8f8fb877200f58a1f78728ad to your computer and use it in GitHub Desktop.
Wokwi Custom Chips C API

This document describes the work-in-progress C API for writing custom chips for the Wokwi simulator.

Using the API

First, make sure to include wokwi-api.h. Every external method you declare should be wrapped with the EXPORT macro (e.g. void EXPORT(my_method_name) (uint32_t arg) { ... }). The chip should declare a chip_init method. This method will be called for every new instance of the chip. If the chip has some internal state, chip_init should allocate memory for the internal state and return a pointer to this memory. This pointer will be passed in the first argument for any listener that you declare (e.g. chip_pin_change). For chip without any internal state, simply return NULL.

Here's an example of a minimal chip file:

#include "wokwi-api.h"

void* EXPORT(chip_init)(void) {
  /* This method runs when the simulation starts. It's called once for each instance of the chip. */
  /* It should return a pointer to a structure with chip instance specific data, or NULL if the chip has no internal state. */
  return NULL;
}

GPIO pins API

Chips interacts with the simulation using digital pins. Use the DECLARE_PIN macro to define your pins, e.g.:

DECLARE_PIN(VCC);
DECLARE_PIN(GND);
DECLARE_PIN(SCL);
DECLARE_PIN(SDA);
DECLARE_PIN(INT);

Then use the pins in the code by calling the PIN macro. For instance, the following code will configure the INT pin as a digital output pin:

pin_mode(PIN(INT), OUTPUT);

The following API methods interact with GPIO pins:

void pin_mode(uint32_t pin, int mode)

Configures the given pin as digital input or output. The valid values for mode are: INPUT, OUTPUT, INPUT_PULLUP and INPUT_PULLDOWN.

void digital_write(uint32_t pin, int value)

Set the output value for a digital pin. Use the LOW and HIGH constants for value.

uint32_t digital_read(uint32_t pin)

Reads the current digital value of the pin, returns either LOW or HIGH.

void digital_watch(uint32_t pin, uint32_t edge)

Listens for changes in the digital value of the given pin. The valid values for edge are:

  • BOTH - Listen for any value change
  • FALLING - Listen for HIGH to LOW changes
  • RISING - Listen for LOW to high changes
  • NONE - Stop listening for changes

You also need to declare a listener with the following signature:

void EXPORT(chip_pin_change)(void *chip, uint32_t pin, uint32_t value) {
  // ...
}

This listener will be called for changes on any pin you watched with digital_watch(). The chip argument is a pointer to the chip state, as returned from chip_init(). The pin parameter is the index of the pin, and value is the new digital value of the pin, LOW or HIGH.

Simulation time

uint64_t get_sim_nanos(void)

Returns the current simulator (virtual) time in nanoseconds.

I2C Device

To create an I2C device, you need to declare four callbacks: chip_i2c_connect, chip_i2c_read, chip_i2c_write, and chip_i2c_disconnect. The first two arguments for all these callbacks are a pointer to the chip state, and the index of the i2c interface.

i2c_init(uint32_t address, uint32_t pin_scl, uint32_t pin_sda)

Initializes an I2C device. The device will attach to the I2C bus on pins pin_scl and pin_sda, and will listen for requests matching the given address.

Note: i2c_init can only be called from chip_init(). Do not call it at a later time.

bool EXPORT(chip_i2c_connect)(chip_state_t *chip, uint32_t i2c_index, uint32_t address)

Called whenever your chip is addressed on the I2C bus. The address parameter contains the address of your device. Return true to acknowledge the connection, or false to discard it.

uint8_t EXPORT(chip_i2c_read)(chip_state_t *chip, uint32_t i2c_index)

Called when the microcontroller wants to read a byte of data from your chip.

bool EXPORT(chip_i2c_write)(chip_state_t *chip, uint32_t i2c_index, uint8_t data)

Called when the microcontroller writes a byte to your chip. data will contain the incoming byte. Return true to acknowledge the transaction, or false to terminate the connection.

void EXPORT(chip_i2c_disconnect)(chip_state_t *chip, uint32_t i2c_index)

Called when the microcontroller disconnects from your chip.

Attributes

Attributes are input parameters that the user can set in diagram.json. You can also define a "controls" section in the "wokwi-custom-chip.json" file to let the user edit these parameters interactively during the simulation. This is particularly useful for sensor inputs (e.g. temperature, humidity, etc).

uint32_t attr_init(const char *name, uint32_t default_value)

Defines a new integer attribute with the given name. The default_value will be used when the user does not define a value for the attribute in diagram.json (under the "attrs" section of the "wokwi-custom-chip").

The function returns an handle to the attribute, which can be used by attr_read().

Note: attr_init can only be called from chip_init(). Do not call it at a later time.

uint32_t attr_read(uint32_t attr)

Returns the current value of the attribute. attr should be a valid attribute handle, previously returned by attr_init().

uint32_t attr_init_float(const char *name, float default_value)

Defines a new floating point attribute with the given name. See attr_init() for more info.

Note: attr_init_float can only be called from chip_init(). Do not call it at a later time.

float attr_float_read(uint32_t attr)

Returns the current value of the attribute. attr should be a valid attribute handle, previously returned by attr_init_float().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment