Tuesday, January 27, 2026

0

AIROC Bluetooth LE evaluation Kit: I2C Peripheral example with ESP32

Introduction:

In the last blog post, I introduced Infineon CYW920835M2EVB evaluation board and managed to run the simple blinky demo to control one of the onboard user LEDs. Next, I want to explore I2C bus communication between the CYW20835 and the famous ESP32 Dev Kit MCU. This will showcase how interfacing capabilites can be added to our board so it can exchange data with other subsystems / sensors. Typical I2C applications includes reading data from temperature sensor, driving an LED display or storing configurations in EEPORM.

I2C Protocol:

A quick background / recap on I2C (inter- integrated - circuit) bus: It is a 2-wire, one to many digital communication .It only requires two lines, a serial data line (SDA) and serial clock line (SCL). It follows a master/slave hierarchy wherein the master is the device that clocks the bus, intiate data transfers with the slaves (read / write messages). The slaves are devices that respond only when approached by the master, through their unique address. Hence it is imperative to avoid duplication of addresses among slaves.

  

 
The data packets are arranged in 8-bit bytes comprising slave address, register number, and data to be transferred. Example Read/Write data transfer packets are illustrated below. 

  • In case of I2C-Write: the master initiates communication on the bus by sending the START bit. Then the target slave address follows on subsequent clock pulses.When the target slave device then reads the WRITE bit it understands that the master wants to write to it. The targer slave device responds with an acknowledgement bit "ACK" as a feedback to the master to proceed with the data transfer. After each byte transfer from the master to the slave, the slave responds by sending an ACK. Once the master is done with transferring all the data it sends a STOP condition to end the transmission.
  • In case of I2C-Read: the master triggers a START condition after which the target slave address is transmitted on subsequent clock pulses followed by READ bit. The addressed slave device then responds with an acknowledgement bit "ACK". Afterwards the slave takes control of SDA line and sends data to the master. For each transmitted byte, the master sends an acknowledgment bit "ACK". The master responds with a NACK after the last byte it wants to receive, resumes control of the bus and sends a STOP condition to end the transmission.
Example I2C Write Transanction

Example I2C Read Transanction

I2C Example with ESP32:

I created a simple example to demonstrate how two MCUs can exchange data via I2C. The example invloves writing a simple counter application where the slave (ESP32) would increment a counter starting zero every time it recieves an I2C write request from the master (CYW20835). After each write request, the master will make an I2C read request to read the current counter state. The write request will be triggered on the master side via the USER button on the CYW920835M2EVB. We will add UART print statements to the application so we can verify the counter state via PuTTY serial monitor.

Regarding the I2C connections, we only need to wire the SDA, SCLK and GND pins on both controllers as shown below. They both use 3.3V logic GPIOs and pull-up resistors are already available on the CYW920835M2EVB so I do not expect any hardware issues.

Hardware setup with I2C wiring 

ESP32 I2C Slave Code:

I created the simple sketch below in Arduino IDE to program the ESP32 as an I2C peripheral that can be accessed on address 0x25 on the I2C bus. I initalized an 8 bit unsigned integer counter (one byte) to be incremented when the I2C master sends the specific OPCODE 0x01.

The "setup" function (called on ESP32 startup) registers two callback functions to handle read/write requests from master. It also intialized the I2C peripheral and enables serial terminal output so we can track the I2C transmissions during run time.

The "onReceive" callback function (called upon write requests) checks for data trnasfers from the master. It reads one byte (the expected OPCODE). if the OPCDODE matches INCREMENT_OP_CODE it proceeds with incrementing the counter. Then the slave write to master the update counter value

#include "Wire.h"

// ESP32 address on I2C bus
#define I2C_SLAVE_ADDR 0x25

// Increment Opcode to be recieved to increment the counter
#define INCREMENT_OP_CODE 0x01

// ESP32 counter to be incremented
uint8_t counter = 0;

/*
Callback to handle data received from the master device
*/
void onReceive(int len) {
  while (Wire.available()) {
    u_int8_t op_code = Wire.read();
    Serial.printf("Write request - Received OPCODE: 0x%02X", op_code);
    Serial.println();
    if(op_code == INCREMENT_OP_CODE){
      counter++;
      Wire.slaveWrite(&counter, 1);
      Serial.printf("Counter incremented to: %d", counter);
      Serial.println();
    }
  }
}

void setup() {
  // Initialize serial terminal debugging messages
  Serial.begin(115200);
  Serial.setDebugOutput(true);

  // Register callbacks
  Wire.onReceive(onReceive);

  // Start I2C preipheral on specified address
  Wire.begin((uint8_t)I2C_SLAVE_ADDR);
  Serial.printf("Started I2C peripheral on address: 0x%02X", I2C_SLAVE_ADDR);
  Serial.println();
}

void loop() {}

CYW20835 I2C Master Code:

With the help of HAL_I2C_MASTER example from Infenion btsdk, I created the small application code below to make the master code. As the application starts, it registers a button callback function to be triggered when the "USER" button is pressed.  The callback function "i2c_interrupt_callback" performs a write request to the ESP32 with the target OPCODE 0x01. Then it makes a read request to fetch the ESP32 counter value. 

#include "wiced.h"
#include "wiced_platform.h"
#include "sparcommon.h"
#include "wiced_bt_stack.h"
#include "wiced_hal_i2c.h"
#include "wiced_bt_trace.h"
#include "wiced_timer.h"
#include "cycfg_pins.h"

/*****************************    Constants   *****************************/
#define I2C_SLAVE_ADDRESS 0x25
/*****************************    Variables   *****************************/
uint8_t read_counter;
uint8_t increment_op_code = 0x01;
/*****************************    Function Prototypes   *******************/
static void i2c_interrupt_callback(void* user_data, uint8_t value);
/******************************************************************************
 *                              Function Definitions
 ******************************************************************************/

/*
 Entry point to the application.
 */
void application_start(void)
{
    // enable serial debugging messages
    wiced_set_debug_uart(WICED_ROUTE_DEBUG_TO_PUART);
    // register callback function when user button is pressed to trigger I2C read / write  
    wiced_platform_register_button_callback(WICED_PLATFORM_BUTTON_1, i2c_interrupt_callback, NULL, WICED_PLATFORM_BUTTON_RISING_EDGE);
    // start I2C on dedicated SCL, SDA pins
    WICED_BT_TRACE("************Starting I2C Master Application**********\n");
    wiced_hal_i2c_init();
    wiced_hal_i2c_select_pads(I2C_SCL, I2C_SDA);
}

/*
callback function called when user button is pressed
*/
void i2c_interrupt_callback(void* user_data, uint8_t value)
{
    // write the op code to slave to increment its counter (1 byte)
    wiced_hal_i2c_write(&increment_op_code, 1, I2C_SLAVE_ADDRESS);
    WICED_BT_TRACE("I2C Master Write OPCODE = %0x\n", increment_op_code);

    // read the counter state (1 byte)
    wiced_hal_i2c_read(&read_counter, 1, I2C_SLAVE_ADDRESS);
    WICED_BT_TRACE("I2C Master Reads Counter Value =  %6d\n", read_counter);
}

Testing:

After flashing the above code pieces to the ESP32 (I2C Slave) and CYW20835 (I2C Master), I started the serial monitor to read the UART traces of both micro-controllers as I press the USER button on the CYW20835 evalboard. In the screenshoot below, you can see the counter being incremented internally on the ESP32 as it recieves a write request witht the expected OPCODE. On the CYW20835 side we are also able to read the latest status of the counter over I2C.

 
 

Tuesday, January 13, 2026

0

AIROC Bluetooth LE evaluation Kit: Getting started with Blinky demo

 

Introduction and Motivation:

I want to start this series of Bluetooth development on AIROC Bluetooth LE SoC. They feature Arm Cortex-M4 & M33 MCUs with rich set of peripherals and support of latest Bluetooth core specs. My goal from the series is to get familiar with embedded systems architecutre, firmware development and Bluetooth low energy profiles. I will make use of code examples published by Infenion to demonstrate the process of building Bluetooth applications using BTSTACK (Infineon Host Bluetooth® stack) API

AIROC CYW20835 Bluetooth LE Evaluation Board: 

 

For the purpose of development, prototyping and testing I got myself Infineon CYW920835M2EVB evaluation board which hosts the CYW20835 Bluetooth 5.4 LE SoC. The block diagram below shows the internal archiecture of the chip with the MCU subsystem ruuing of an ARM Cortex-M4 CPU.
 
modalImg
CYW20835 Block Diagram

The above chip is connected to the board in the form of M.2 radio card as shown in the images below. The system can be powered from 5V USB or Coin battery cell based on a jumper selection. The evalboard is equipped with a thermistor, an ambient ligh sensor, 2 User LEDs and 1 User push button. 4 Arduino and 2 Bluetooth I/O headers are available to access the CYW20835 GPIOs. For debugging, Serial Wire Debug (SWD) header is also available.

CYW20835 Evalboard Top View

CYW20835 Evalboard Bottom View


Blinky Example:

One of the simplest examples to get started with embedded systems is a Blinky project. The goal is to program the CYW20835 to drive one of the onboard LEDs on/off. This will also demonstrate the usage of Infenion ModusToolbox frimware development eco system for their micro-controllers.

Installing ModusToolbox:

I will not go through the development environement setup process since it is a straightforward. Ideally you would download the ModusToolbox setup tool from here and launch it to install the essential components (i.e, ARM compiler, SDK, examples, programming and debugging tools) as listed in the image below:
 

Creating Template Project:

I use ModusToolbox project creator to create a template project selecting VScode as the development IDE. The tool provide a set of source templates based on suported list of boards. By typing the product name of the evaluation kit we can select it from the BSP menu as shown below:
 

 Afterwads we get a list of application templates. I selected the Empty BTSDK app as it is the simplest one for getting started
 
 

Cleaning Up The Template:

After hitting create, the the template project files are generated. Now I move to VSCode to start developing the Blinky example, build it and flash it to the evaluation board. Please be aware that you need to install VSCode "C/C++" and "Cortex-Debug" extensions for syntax highlighting and debugging. Looking at the gnerated file strucutre of the project, you notice that it looks quite complex with many directories and various types of files. For this simple demonstration, I only had to worry about the "C" source/header files to write the application logic and the "makefile" to enable debugging flag.
 
Generated template project files
 
After cleaning up the project files, I end up with the structure below. Note that I have renamed "empty_wiced_bt.c" to "main.c" since this file will contain the entry point function for the application. In addition, I have renamed "app_bt_cfg.h" to "blinky.h". The generated "COMPONENT_btstack" folders were removed since I am not going to use any Bluetooth functions in this simple example.
 
Project files after cleanup

 

Coding The Blinky Firmware:

To write this demo code, I have spent sometime to understand the required functions and imports required to control the GPIO states for turning the LED on/off with a defined timer. The BTSDK online documentation along with the SDK examples were quite useful in this regard. Below I share the source code I came up with to accomplish the project goal. In addition to LED controls, the application can print debug messages to UART which can be observed from a serial terminal window (e.g Putty) listening to the respective COM port as you will see later. 
 
The "blinky.h" header file defines the LED2 mapped GPIO number based on our selected chipset platform. 
 
The "main.c" start by importing required headers. I have defined two constants to control the LED On / Off duration to create a blinking effect. In the variables section, I have made use of the SDK time managment service to define an accurate timer that once it times out the LED2 GPIO gets toggled via a callback function. The "APPLICATION_START" function is the entry point of our program (like main in a conventional C/C++ program). The function name is a convention used by the underlying SDK so I had to stick to it. In the main function, I set the UART debug interface to observe printed messages from the application while it is running. Then I print a debug message via UART to indicate that application has started. Finally, I initialize and start the LED timer with the respecitve "led_timeout_func" callback. The  callback function will trigger once the defined ON/OFF duration is elapsed and pull the LED2 GPIO high/low and start the timer again to repeat the process infinitely.
 
blinky.h 
/** blinky.h
 *
 * Header File
 *
 */

#ifndef BLINKY_H_
#define BLINKY_H_

#include "wiced_bt_cfg.h"

/******************************************************************************
 *                                Constants
 ******************************************************************************/
#define LED_2_GPIO (uint32_t)*platform_led[WICED_PLATFORM_LED_2].gpio

#endif /* BLINKY_H_ */
 
 
 main.c  
/** @file
*
* Blinky Example
*
*/
#include "wiced_platform.h"
#include "wiced_timer.h"
#include "wiced_hal_gpio.h"
#include "sparcommon.h"
#include "blinky.h"
#ifdef  WICED_BT_TRACE_ENABLE
#include "wiced_bt_trace.h"
#endif

/******************************************************************************
 *                                Constants
 ******************************************************************************/
#define LED_OFF_MS 1000
#define LED_ON_MS 1000
/******************************************************************************
 *                                Variables Definitions
 ******************************************************************************/
wiced_timer_t led_timer;
extern wiced_platform_led_config_t platform_led[];
wiced_bt_gpio_numbers_t led_pin;
static void led_timeout_func( TIMER_PARAM_TYPE count);
/******************************************************************************
 *                          Function Definitions
 ******************************************************************************/
/*
 *  Entry point to the application
 */
APPLICATION_START()
{
    // Set to PUART to see traces on peripheral uart(puart)
    wiced_set_debug_uart(WICED_ROUTE_DEBUG_TO_PUART);
    // Assign target GPIO to LED 2 on the board (The RED LED)
    led_pin = LED_2_GPIO;
    // Print debug message to UART
    WICED_BT_TRACE( "AK-Experiments - Blinky Example");
    // initialize a timer wiht a callback function to turn LED on/off
    wiced_init_timer(&led_timer, led_timeout_func, 0, WICED_MILLI_SECONDS_TIMER);
    // Start the timer
    wiced_start_timer(&led_timer, LED_ON_MS);
}


/*
 * The function invoked on timeout of led timer.
 */
void led_timeout_func( TIMER_PARAM_TYPE count )
{
    static wiced_bool_t led_on = WICED_FALSE;
    if (led_on)
    {
        wiced_hal_gpio_set_pin_output(led_pin, GPIO_PIN_OUTPUT_HIGH);
        led_on = WICED_FALSE;
        wiced_start_timer(&led_timer, LED_OFF_MS);
       
    }
    else
    {
        led_on = WICED_TRUE;
        wiced_hal_gpio_set_pin_output(led_pin, GPIO_PIN_OUTPUT_LOW);
        wiced_start_timer(&led_timer, LED_ON_MS);
    }
}


 

Flashing Blinky Firmware:

Finally we should be ready to build our application and program our board to see if the written code above actually works. The template project will have prepared tasks to build and download the program to the board. So I simply triggered the "Program: build and download app [Release]" after connecting my Evalboard to the PC via USB cable. After few seconds, the project builds and gets falshed successfully to the board as indicated by the below message:
 
 
 
Aferwards, I see LED2 on the board blinking red every 1 second. Great I just created my first firmware application :). To check that the debug message is also printed over UART. I open a serial terminal on the respective COM port (you can find exact one via Device Manager on Windows) and then hit the Reset buton on the board to restart the system. I am able to see the below message as expected (really neat).
 

 

 
 

Monday, December 8, 2025

0

Inter-Process Communication: Playing with COM objects in Python

Introduction and Motivation:

When developing cross-platform modular software applications, interoperability is essential to ensure easy interaction between different components of an application even if they were implemented in different programming languages. This is where inter-process communication technologies come in action to enable parallel processing and share of data between processes running either locally on same machine or distributed across a computing cluster. 

Multiple inter-process communication frameworks / libraries already exist, each with its own architecture, performance and support of  wide range of programming languages. Example libraries that I came across my working journey are: gRPC (an open-source remote procedure call framework developed by Google) and ZeroMQ (a flexible asynchronous messaging library designed for use in distributed or concurrent applications). 

Nevertheless, I was always curios about OS built-in communication mechanisms (i.e, at the end of the day an operating system is a set of components and services that need to interact with each other according to some standard). In addition, I came across certain scenarios where using an external communication library was an 'overkill' for the problem at hand.

In the context of Windows, Component Object Model (COM) is Microsoft inter-process communication component that is built in the native Windows API facilitating interaction between software objects that implements one or more interfaces. These objects can be within a single process, in other processes, even on remote computers. 

In this blog-post, I want to share my initial experience working with COM objects using Python. This can help creating Python applications that can talk and reuse functionality from Windows applications written in .NET / C++ and expose their features via COM interface.

About COM (Component Object Model): 

According to Microsoft, COM is an object-oriented system for creating binary software components that can interact. It is the foundation for Microsoft's OLE (Object Linking and Embedding) technology which allows Microsoft Office applications to communicate and exchange data between documents (e.g, embedding an Excel chart inside a Word document)

Through COM, a client object can call methods of server objects, which can be Dynamic Link Libraries (DLLs) or executables (EXEs). Remote COM execution is possible by using Distributed Component Object Model (DCOM) which exposes application objects via remote procedure calls (RPCs)

In-Process and Out-Of-Process COM:

There are two main types of COM servers: in-process and out-of process servers. In-process servers are implemented in the form of a dynamic linked library (DLL) running in the same process space as the client. In this case, the client communicates with the in-process server using direct calls to the COM interface.
 
In-process server
Credits: docwiki.embarcadero.com
 

Out-of-process servers are implemented in the form of an executable file (EXE) that runs in a different process space as the client. The local server uses COM with a proxy to communicate with the client. In case the COM server resides on a remote machine, distributed COM (DCOM) is utilized where some RPC protocol transfer the calls to the remote COM server. In both cases, all interface calls look alike from the client perspective as the COM proxy resides in the same process as the client.
 
 
Out-of-process and remote servers
Credits: docwiki.embarcadero.com

DLL Surrogates:

COM provides a mechanism that allows an in-process server (a DLL) to run in a surrogate (EXE) process.
This allows a COM server to serve multiple clients simultaneously (i.e. similar to an out-of process sever). The surrogate also provides additional security features and help isolating client side application from faults occurring on the server side. Windows provides a default surrogate process: "dllhost.exe" that can be used to host the COM (DLL) server. In summary, a DLL surrogate is useful to make an in-process component act as a remote service.

COM Server - Client example in Python:

You can write COM based applications in multiple programming languages. The most common combination I have seen is C++ and C# with Visual Studio IDE providing a starting template for development. Here I want to explore however the extension of COM development into Python world. 

To do so I picked up the "comtpyes" library which seems to be an active project on Github. In short, the library is based on "ctypes" python library allowing the creation of pure python wrappers around DLLs or shared libraries functions. This means that one can create, register and talk to COM interfaces using python syntax. Since COM is Microsoft based technology, the library is only meant for Windows.

I created a simple server-client project to demonstrate working with COM objects in Python. 
The following components are needed:
 
  • Install Python 3.9 or higher to work with comtypes 
  • Start a Python virtual environment with: python -m venv venv 
  • Activate the virtual environment: . venv/Scripts/activate 
  • Install comtypes  with: python -m pip install comtypes
  • Windows Kits version 10 and above need to be installed using Visual Studio Installer

Creating and registering COM server in Python:

To implement a COM server object in Python we first need to create a type library file using IDL (Interface Definition Language) syntax. This file provides the COM interface attributes along with the definition of derived classes and their methods. Every type library / interface / class defined needs to be labelled with a unique GUID. An example IDL file syntax is provided below which defines a COM interface named: "IMyCOMInterface" with a derived class: "MyCOMObject" that has the method: "MyCOMMethod"

import "oaidl.idl";
import "ocidl.idl";

[
uuid(FFE9DC12-DF9B-4B3A-896D-14556FFC59E6),
dual,
oleautomation
]
interface IMyCOMInterface : IDispatch {
HRESULT MyCOMMethod([in] INT a, [in] INT b, [out, retval] INT *presult);
}

[
uuid(61CAA54D-F3C7-442D-9827-BEB160D13134)
]
library MyCOMLib
{
importlib("stdole2.tlb");

[uuid(BC2ED72E-ACC6-4A0D-9DC3-C9767658A6CA)]
coclass MyCOMObject {
[default] interface IMyCOMInterface;
};
};

Next we need to compile the IDL file to generate the required .(tlb) type library file. To do so, we trigger the MIDL compiler on the IDL file. The compiler should be automatically installed as part of Widnows Kits (SDKs) installation. The compiler will not be present on system path by default so you need to add it manually or run it from a Visual Studio Developer Command Prompt.

Compiling COMServer.idl using MIDL compiler

Once the compilation process succeeds, the COMServer.tlb binary file is generated along with a couple of C header and source files.

Generated artifacts from compiling COMServer.idl file

Now we can move to Python and start implementing "MyCOMObject" class followed by the registering of the COM object in Windows registry. The code snippet below outlines the implementation:

import comtypes
import comtypes.server.localserver
from comtypes.client import GetModule
import os

# generate wrapper code for the "MyCOMLib" library, this needs
# to be done only once (but also each time the IDL file changes)
GetModule(r"./COMServer.tlb")
from comtypes.gen.MyCOMLib import MyCOMObject

class MyCOMObject(MyCOMObject):
# COM object attributes that will reside in registry entries
_reg_threading_ = "Both" # ThreadingModel for execution: "Both", "Free", or "Apartment"
_reg_progid_ = "MyCOMLib.MyCOMObject.1" # programmatic identifier that can be associated with a CLSID
_reg_novers_progid_ = "MyCOMLib.MyCOMObject"
_reg_desc_ = "AK-Experiments COM server for testing"
_reg_clsctx_ = comtypes.CLSCTX_INPROC_SERVER | comtypes.CLSCTX_LOCAL_SERVER # contexts the COM server can operate within (e.g. inprocess, local server)
_regcls_ = comtypes.server.localserver.REGCLS_MULTIPLEUSE # controls the type of connections to a class object (e.g single, multiple .. etc)
print(f"COM-Server: Creating an instance of MyCOMObject. Server Process ID: {os.getpid()}")

def MyCOMMethod(self, a, b):
# COM method high level implementation in python
print("COM-Server: MyComm Method got Called")
return a + b

if __name__ == "__main__":
from comtypes.server.register import UseCommandLine
# invoke command line interface with a specific sys command to trigger register, unregister of COM server
# NOTE. You need admin access to perform registry operations
UseCommandLine(MyCOMObject)

To register the COM server object, we trigger the above python script with the "/regserver" option from an elevated terminal:

 

To make sure the COM object is correctly registered, we can use tools such as OleView and COMView to find our COM object using its name of defined CLSID and check its properties and available interfaces. As you can see our COM object exposes correctly our defined "IMyCOMInterface" in addtion to other default interfaces. We can instantiate an instance of the COM server as well by the same tool as shown below and view properties and methods of underlying objects.

Finding the COM object by its CLSID in OleView

 

Creating an instance of MyCOMObject and calling MyCOMMethod in ComView  

Talking to the COM server from Python:

To communicate with the previously created COM object, we simply have to create an instance of the COM object with the aid of comtypes as follows:
from comtypes.client import CreateObject
import os

def main():
# instantiate object using its programmtic ID or CLSID
# by default the context enumeration CLSCTX_SERVER : (CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER) is used
com_object = CreateObject("MyCOMLib.MyCOMObject")
print(f"COM-Client: Created COM object: {com_object} from Client, Process ID: {os.getpid()}")
ret = com_object.MyCOMMethod(1,2)
print(f"COM-Client: Calling COM object MyCOMMethod with arguments 1 , 2 returns: {ret}")

if __name__ == "__main__":
main()

Upon running the above code we should be able to create an instance of the com server, get a pointer to the interface instance and then call the server implemented "MyCOMMethod" with two integers and get the correct sum as shown below:

 

You would also notice that I printed the process ID from which the server and client are running and in this case both are running from the same space as the IDs are identical so we can say that our COM server operates in "in-process" mode. If we run the same script above using a 32-bit (x86) Python virtual environment, you will notice that a new window pops up and the server starts in a separate process as the client. This can be confirmed by noting the different process IDs printed from client and server windows.

In this case, the COM server operates in "out-of-process" mode due to the bitness different between the client and the registered COM server. We can also enforce "out-of-process" behavior from the client side by passing the "CLSCTX_LOCAL_SERVER" context enumeration to the "CreateObject" method.