Generating 800Khz on MOSI line

Hi,

we have created a led driver for ucs5603.c taking reference of ws2812-spi.c. we are using nrf52840 with NCS v17.0

we have set SPI clock frequency at 1MHz and we are getting 4MHz on clock line. When we check the MOSI line the data is at 500KHz. When I increased the clock frequency to 8MHz the MOSI line frequency is increased to 1MHz.  So I got to know that it is using divider of 8 but I did not find the configuration of it.

we need to have 800KHz on MOSI line for ucs5603 to work so can you please guide us how to achieive this?

Parents
  • NCSv17.0 seems like a wrong SDKk version, did you mean nRF5SDKv17 which is our older deprecated version?

    Even though we do not work with drivers of those sensors ourself, we can guide you with the settings on the SPI to get proper waveforms. It would be nice to show some code snippets on how you configured the SPI so that I understand more on what you are calibrating to get those different clock output frequencies.

  • I mentioned the wrong version. It is NCSv2.7.0

    I have taken reference from ws2812_spi.c driver. So basically I have modified the "ws2812_strip_update_rgb" method for ucs5603. I am sharing the ws2812_spi.c file's code for your reference.

    /*
     * Copyright (c) 2017 Linaro Limited
     * Copyright (c) 2019, Nordic Semiconductor ASA
     * Copyright (c) 2021 Seagate Technology LLC
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #define DT_DRV_COMPAT worldsemi_ws2812_spi
    
    #include <zephyr/drivers/led_strip.h>
    
    #include <string.h>
    
    #define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL
    #include <zephyr/logging/log.h>
    LOG_MODULE_REGISTER(ws2812_spi);
    
    #include <zephyr/kernel.h>
    #include <zephyr/device.h>
    #include <zephyr/drivers/spi.h>
    #include <zephyr/sys/math_extras.h>
    #include <zephyr/sys/util.h>
    #include <zephyr/dt-bindings/led/led.h>
    
    /* spi-one-frame and spi-zero-frame in DT are for 8-bit frames. */
    #define SPI_FRAME_BITS 8
    
    /*
     * SPI master configuration:
     *
     * - mode 0 (the default), 8 bit, MSB first (arbitrary), one-line SPI
     * - no shenanigans (don't hold CS, don't hold the device lock, this
     *   isn't an EEPROM)
     */
    #define SPI_OPER(idx) (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | \
    		  SPI_WORD_SET(SPI_FRAME_BITS))
    
    struct ws2812_spi_cfg {
    	struct spi_dt_spec bus;
    	uint8_t *px_buf;
    	uint8_t one_frame;
    	uint8_t zero_frame;
    	uint8_t num_colors;
    	const uint8_t *color_mapping;
    	size_t length;
    	uint16_t reset_delay;
    };
    
    static const struct ws2812_spi_cfg *dev_cfg(const struct device *dev)
    {
    	return dev->config;
    }
    
    /*
     * Serialize an 8-bit color channel value into an equivalent sequence
     * of SPI frames, MSbit first, where a one bit becomes SPI frame
     * one_frame, and zero bit becomes zero_frame.
     */
    static inline void ws2812_spi_ser(uint8_t buf[8], uint8_t color,
    				  const uint8_t one_frame, const uint8_t zero_frame)
    {
    	int i;
    
    	for (i = 0; i < 8; i++) {
    		buf[i] = color & BIT(7 - i) ? one_frame : zero_frame;
    	}
    }
    
    /*
     * Latch current color values on strip and reset its state machines.
     */
    static inline void ws2812_reset_delay(uint16_t delay)
    {
    	k_usleep(delay);
    }
    
    static int ws2812_strip_update_rgb(const struct device *dev,
    				   struct led_rgb *pixels,
    				   size_t num_pixels)
    {
    	const struct ws2812_spi_cfg *cfg = dev_cfg(dev);
    	const uint8_t one = cfg->one_frame, zero = cfg->zero_frame;
    	struct spi_buf buf = {
    		.buf = cfg->px_buf,
    		.len = (cfg->length * 8 * cfg->num_colors),
    	};
    	const struct spi_buf_set tx = {
    		.buffers = &buf,
    		.count = 1
    	};
    	uint8_t *px_buf = cfg->px_buf;
    	size_t i;
    	int rc;
    
    	/*
    	 * Convert pixel data into SPI frames. Each frame has pixel data
    	 * in color mapping on-wire format (e.g. GRB, GRBW, RGB, etc).
    	 */
    	for (i = 0; i < num_pixels; i++) {
    		uint8_t j;
    
    		for (j = 0; j < cfg->num_colors; j++) {
    			uint8_t pixel;
    
    			switch (cfg->color_mapping[j]) {
    			/* White channel is not supported by LED strip API. */
    			case LED_COLOR_ID_WHITE:
    				pixel = 0;
    				break;
    			case LED_COLOR_ID_RED:
    				pixel = pixels[i].r;
    				break;
    			case LED_COLOR_ID_GREEN:
    				pixel = pixels[i].g;
    				break;
    			case LED_COLOR_ID_BLUE:
    				pixel = pixels[i].b;
    				break;
    			default:
    				return -EINVAL;
    			}
    			ws2812_spi_ser(px_buf, pixel, one, zero);
    			px_buf += 8;
    		}
    	}
    
    	/*
    	 * Display the pixel data.
    	 */
    	rc = spi_write_dt(&cfg->bus, &tx);
    	ws2812_reset_delay(cfg->reset_delay);
    
    	return rc;
    }
    
    static size_t ws2812_strip_length(const struct device *dev)
    {
    	const struct ws2812_spi_cfg *cfg = dev_cfg(dev);
    
    	return cfg->length;
    }
    
    static int ws2812_spi_init(const struct device *dev)
    {
    	const struct ws2812_spi_cfg *cfg = dev_cfg(dev);
    	uint8_t i;
    
    	if (!spi_is_ready_dt(&cfg->bus)) {
    		LOG_ERR("SPI device %s not ready", cfg->bus.bus->name);
    		return -ENODEV;
    	}
    
    	for (i = 0; i < cfg->num_colors; i++) {
    		switch (cfg->color_mapping[i]) {
    		case LED_COLOR_ID_WHITE:
    		case LED_COLOR_ID_RED:
    		case LED_COLOR_ID_GREEN:
    		case LED_COLOR_ID_BLUE:
    			break;
    		default:
    			LOG_ERR("%s: invalid channel to color mapping."
    				"Check the color-mapping DT property",
    				dev->name);
    			return -EINVAL;
    		}
    	}
    
    	return 0;
    }
    
    static const struct led_strip_driver_api ws2812_spi_api = {
    	.update_rgb = ws2812_strip_update_rgb,
    	.length = ws2812_strip_length,
    };
    
    #define WS2812_SPI_NUM_PIXELS(idx) \
    	(DT_INST_PROP(idx, chain_length))
    #define WS2812_SPI_HAS_WHITE(idx) \
    	(DT_INST_PROP(idx, has_white_channel) == 1)
    #define WS2812_SPI_ONE_FRAME(idx) \
    	(DT_INST_PROP(idx, spi_one_frame))
    #define WS2812_SPI_ZERO_FRAME(idx) \
    	(DT_INST_PROP(idx, spi_zero_frame))
    #define WS2812_SPI_BUFSZ(idx) \
    	(WS2812_NUM_COLORS(idx) * 8 * WS2812_SPI_NUM_PIXELS(idx))
    
    /*
     * Retrieve the channel to color mapping (e.g. RGB, BGR, GRB, ...) from the
     * "color-mapping" DT property.
     */
    #define WS2812_COLOR_MAPPING(idx)				  \
    	static const uint8_t ws2812_spi_##idx##_color_mapping[] = \
    		DT_INST_PROP(idx, color_mapping)
    
    #define WS2812_NUM_COLORS(idx) (DT_INST_PROP_LEN(idx, color_mapping))
    
    /* Get the latch/reset delay from the "reset-delay" DT property. */
    #define WS2812_RESET_DELAY(idx) DT_INST_PROP(idx, reset_delay)
    
    #define WS2812_SPI_DEVICE(idx)						 \
    									 \
    	static uint8_t ws2812_spi_##idx##_px_buf[WS2812_SPI_BUFSZ(idx)]; \
    									 \
    	WS2812_COLOR_MAPPING(idx);					 \
    									 \
    	static const struct ws2812_spi_cfg ws2812_spi_##idx##_cfg = {	 \
    		.bus = SPI_DT_SPEC_INST_GET(idx, SPI_OPER(idx), 0),	 \
    		.px_buf = ws2812_spi_##idx##_px_buf,			 \
    		.one_frame = WS2812_SPI_ONE_FRAME(idx),			 \
    		.zero_frame = WS2812_SPI_ZERO_FRAME(idx),		 \
    		.num_colors = WS2812_NUM_COLORS(idx),			 \
    		.color_mapping = ws2812_spi_##idx##_color_mapping,	 \
    		.length = DT_INST_PROP(idx, chain_length),               \
    		.reset_delay = WS2812_RESET_DELAY(idx),			 \
    	};								 \
    									 \
    	DEVICE_DT_INST_DEFINE(idx,					 \
    			      ws2812_spi_init,				 \
    			      NULL,					 \
    			      NULL,					 \
    			      &ws2812_spi_##idx##_cfg,			 \
    			      POST_KERNEL,				 \
    			      CONFIG_LED_STRIP_INIT_PRIORITY,		 \
    			      &ws2812_spi_api);
    
    DT_INST_FOREACH_STATUS_OKAY(WS2812_SPI_DEVICE)

  • I do not want to dive into this specific driver from ws2812 which is outside the scope of our solution but looking at the below code snippet

    struct ws2812_spi_cfg {
    	struct spi_dt_spec bus;
    	uint8_t *px_buf;
    	uint8_t one_frame;
    	uint8_t zero_frame;
    	uint8_t num_colors;
    	const uint8_t *color_mapping;
    	size_t length;
    	uint16_t reset_delay;
    };
    
    static const struct ws2812_spi_cfg *dev_cfg(const struct device *dev)
    {
    	return dev->config;
    }
    

    It seems that you are relying that the driver is setting proper clock based on your device tree setting. So make sure that your device tree has set this correctly for the  clock-frequency element in the spi node. Something like below

    &spi1 {
        compatible = "nordic,nrf-spi";
        reg = <0x40004000 0x1000>;
        status = "okay";
        clock-frequency = <1000000>; // Set to 1 MHz
        cs-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
    };


    Apart from that it seems like there is nothing much to set the clock speed of the SPI in this driver. it just seems to be using the spi speed already set in the spi driver.

  • Hi,

    Yes, we have set the clock frequency to 4MHz and when we measure the clock, it is 4MHz indeed. But when we measure the frequency of MOSI line to check at what speed data is transmitted, it is 500KHz.

    Can you specify reason behind this?

  • Do not know what you mean by that, the data on MOSI line aligns with the SCK that is being used. 
    Can you show me a snapshot of the data on the MOSI pin and SCK side by side?
    How do you know that the data on MOSI is 500KHz, when there is data on it, how are you measuring frequency? 
    Is there some encoding on WS2812 that encodes 1 bit of data into 8 spi bits.

    Doing a google search seems to show that WS2812 have a capability to encode 1-8 bits into frames, please check if that is the thing that you are observing. In your driver code, I see this.

     * Serialize an 8-bit color channel value into an equivalent sequence
     * of SPI frames, MSbit first, where a one bit becomes SPI frame
     * one_frame, and zero bit becomes zero_frame.
     */
    static inline void ws2812_spi_ser(uint8_t buf[8], uint8_t color,
    				  const uint8_t one_frame, const uint8_t zero_frame)
    {
    	int i;
    
    	for (i = 0; i < 8; i++) {
    		buf[i] = color & BIT(7 - i) ? one_frame : zero_frame;
    	}
    }

Reply
  • Do not know what you mean by that, the data on MOSI line aligns with the SCK that is being used. 
    Can you show me a snapshot of the data on the MOSI pin and SCK side by side?
    How do you know that the data on MOSI is 500KHz, when there is data on it, how are you measuring frequency? 
    Is there some encoding on WS2812 that encodes 1 bit of data into 8 spi bits.

    Doing a google search seems to show that WS2812 have a capability to encode 1-8 bits into frames, please check if that is the thing that you are observing. In your driver code, I see this.

     * Serialize an 8-bit color channel value into an equivalent sequence
     * of SPI frames, MSbit first, where a one bit becomes SPI frame
     * one_frame, and zero bit becomes zero_frame.
     */
    static inline void ws2812_spi_ser(uint8_t buf[8], uint8_t color,
    				  const uint8_t one_frame, const uint8_t zero_frame)
    {
    	int i;
    
    	for (i = 0; i < 8; i++) {
    		buf[i] = color & BIT(7 - i) ? one_frame : zero_frame;
    	}
    }

Children
Related