SPI Library

It is up to the implementation to provide the hardware interfaces used by the prepared Vision SPI library:

  • SPI master interrupt based or using DMA
  • External GPIO IRQ interrupt

A good Baudrate for SPI is around 1.125MBits/s, but not much lower. SPI Mode is zero (CPOL = 0, CPHA = 0).

If you are using dynamic memory allocation mode, please make sure your heap size is big enough. At least 1024 Bytes.

You can find the source code for the library in the Vision External Example or download it from here:

https://kk-t.com/wp-content/uploads/2026/05/VisionSpiLibrary_2026_05_25.zip

The library handles the low-level SPI transaction flow. Your implementation must provide the hardware functions, call the library task/event functions, and implement the RDM, DMX, and DFU callbacks with your fixture behavior.

Include

Just add all the files to your project. Include libVisionSpiInterface.h and call the necessary functions. The current library uses Vision naming. Older packages and examples used iQMesh/iqMesh; if you compare older material, treat those names as the previous name for the same Vision SPI library.

#include "libVisionSpiInterface.h"

Memory Mode

The library supports two memory modes: dynamic allocation and static allocation.

In dynamic allocation mode, memory is allocated at runtime as needed, minimizing overall memory usage.
In static allocation mode, all required memory is defined globally at compile time.

By default, the library uses dynamic allocation.

To enable static allocation, define the compile-time constant VISION_USE_STATIC_ALLOCATION in your project, or uncomment it at the bottom of libVisionSpiInterface.h.

Provide necessary hardware functions

Make sure to provide this external hardware functions:

/* Start SPI transmit and receive interrupt or DMA based */
void HardwareVisionSpiSetTxRx(uint8_t* buf, uint16_t length);
/* Set SPI CS Pin regarding set value */
void HardwareVisionSpiSetCsPin(bool set);
/* Read state of IRQ Gpio Pin */
bool HardwareVisionGpioReadIrqPin(void);

Initialization

For initialization you have to call "vision_init" function. Therefore you have to provide the RDM UID, the necessary callbacks, DMX receive length and if you support device firmware updates.

uint8_t rdmId[6];
uint16_t visionSpecialDmxFootprint= 100; // The footprint used for Vision Control Behavior 255. (App Control)
bool supportDfu = false;
// Register necessary callbacks
vision_callbacks_t callbacks;
// Dfu callback implementation is not needed if dfu is not supported
callbacks.DfuDataReceived = &DfuDataReceived;
callbacks.DfuFlagReceived = &DfuFlagReceived;
// For receiving Dmx frames
callbacks.DmxReceived = &DmxReceived;
// For receiving Rdm frames
callbacks.RdmReceived = &RdmReceived;

// Init Vision SPI library
vision_init(rdmId, visionSpecialDmxFootprint,supportDfu, callbacks);

Change RDM UID

If the serial number is changing and therefore the RDM UID, it is possible to overwrite the RDM UID using:

 vision_setRdmId(rdm_id);

Provide Task event

In order to process the data its necesary to call "vision_task" whenever there is time. For example in the while loop: (Use a lower priority than the interput routine from SPI and IRQ EXTI)

while (1)
{
  uint32_t timems = HAL_GetTick();
  vision_task(timems);
}

Provide SPI completion event

After completion or if an error occured you have to call "vision_extSpiEvent".

For example using STM32 HAL callbacks:

void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
	HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin,GPIO_PIN_SET);
	vision_extSpiEvent(SPI_EVENT_COMPLETION, dmaBufRx, dmaBufLength);
}
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
	HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin,GPIO_PIN_SET);
	vision_extSpiEvent(SPI_EVENT_ERROR, dmaBufRx, dmaBufLength);
}
void HAL_SPI_AbortCpltCallback(SPI_HandleTypeDef *hspi)
{
	HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin,GPIO_PIN_SET);
	vision_extSpiEvent(SPI_EVENT_ERROR, dmaBufRx, dmaBufLength);
}

Provide GPIO edge detection event

In order to detect edges for the "IRQ" pin, HAL EXTI Callback is used. Because of many controller do not have support for complete edge detection we have to check them in the interrupt. Make sure you detect the falling and rising edge.

Make sure the interrupt has high priority because the switching edge can be as fast as 30 us

.

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if (GPIO_Pin == SPI_IRQ_Pin)
	{
		if (HAL_GPIO_ReadPin(SPI_IRQ_GPIO_Port, SPI_IRQ_Pin))
		{
			vision_extIrqInterupt(SPIEXT_IRQ_EDGE_RISING);
		} 
		else
		{
			vision_extIrqInterupt(SPIEXT_IRQ_EDGE_FALLING);
		}
	}
}

Use of RdmReceived callback

The RdmReceived callback has the priority from the Task event. Here you have to decode the RDM message.

In most projects, pass the received data into the same RDM decoder used for wired RDM. Vision does not need a separate second RDM stack. Only add the required Vision custom RDM commands to your existing RDM implementation.

static void RdmReceived(const uint8_t* buf, uint16_t length)
{
	// Handle RDM Data
	RdmDecodeMessage(buf, length);
}

If you have to answer this message write a response directly using:

uint8_t msg[length];
vision_WriteRdm(msg,length);

Use of DmxReceived Callback

The DmxReceived callback has the priority from the Task event. The buffer is released after return from this event. So make sure to copy the data.

The first value in the Vision DMX stream is the Vision Control Behavior. Use it only to interpret the current Vision DMX frame. Do not change the fixture's saved DMX personality, display settings, RDM settings, or settings for other protocols.

For Vision Control Behavior 0, pass the DMX data into the same DMX handling used for wired DMX. For Vision Control Behavior 255, apply the defined App Control defaults for the current frame only.

static void DmxReceived(uint8_t personality, const uint8_t* buf, uint16_t length)
{
	// Handle DMX Data
	if(personality == 255)
	{
		// Use App Control behavior with App Control default settings.
	}
	else if(personality > 0)
	{
		// Use DMX Control with this temporary DMX personality override.
	}
	else
	{
		// Use selected personality from the fixture
	}
}

Use of Dfu Callbacks

The Dfu callback has the priority from the Task event.

static void DfuFlagReceived(vision_dfu_flag_t flag)
{
    // Handle DFU data
	if(flag == VISION_DFUFLAG_STOP)
	{
		// Check if dfu has started
		// Check received data for example using a CRC
		// if anything is good answer with success and initiate the update. There is a timeout about of 10 sec for answering
		vision_WriteDfu(DFU_SUCCESS);
	}
	if(flag == VISION_DFUFLAG_START)
	{
		// Check if dfu has not already started
		// Prepare flash, .. for receiving dfu
		// if anything is good answer with success to start the transmission. There is a timeout about of 10 sec for answering
		vision_WriteDfu(DFU_SUCCESS);
	}
}
static void DfuDataReceived(uint16_t packetNr, const uint8_t* buf, uint16_t length)
{
	// Check if packetNr is correct. Store packet.
	// if something is wrong answer with a error reason
	// vision_WriteDfu(DFU_ERROR_WRONG_PACKETNR);
}

Do not miss:

Make sure you call the following library functions:

  • vision_init
  • vision_task
  • vision_extSpiEvent
  • vision_extIrqInterupt

And implement the following callbacks to :

  • void HardwareVisionSpiSetTxRx(uint8_t* buf, uint16_t length);
  • void HardwareVisionSpiSetCsPin(bool set);
  • bool HardwareVisionGpioReadIrqPin(void);

And implement the following callbacks use the data :

  • DfuDataReceived
  • DfuFlagReceived
  • DmxReceived
  • RdmReceived

Debug Library – Find Issues

In order to find issues regarding hardware or firmware implementation there is a step by step guide how to debug the library:

Analyse logs of Vision Wireless Controller

To obtain a special firmware version of the Vision Wireless Controller for log collection, please navigate to CUSTOM IMPLEMENTATION Project Artery IC and refer to the chapter Analyse logs of Vision Wireless Controller at the end of the page. The logs can be very helpful during the implementation process.

© KKT Künzler Technologies GbR.
Developed by KKT - Künzler Technologies GbR