From 042d4b6e91f48bc4938168eb207599cdc9eadb8f Mon Sep 17 00:00:00 2001 From: David Brodski Date: Tue, 16 Sep 2014 00:17:23 +0200 Subject: [PATCH 1/9] Added LedDevice for "ws2812s" leds To activate: use led device "ws2812s" in the hyperion configuration Former-commit-id: 0b5ee38679fe353f43bb4a347882d056ca237128 --- libsrc/leddevice/CMakeLists.txt | 9 + libsrc/leddevice/LedDeviceFactory.cpp | 6 + libsrc/leddevice/LedDeviceWS2812s.cpp | 529 ++++++++++++++++++++++++++ libsrc/leddevice/LedDeviceWS2812s.h | 451 ++++++++++++++++++++++ 4 files changed, 995 insertions(+) create mode 100644 libsrc/leddevice/LedDeviceWS2812s.cpp create mode 100644 libsrc/leddevice/LedDeviceWS2812s.h diff --git a/libsrc/leddevice/CMakeLists.txt b/libsrc/leddevice/CMakeLists.txt index 441618d2..0efbd18c 100755 --- a/libsrc/leddevice/CMakeLists.txt +++ b/libsrc/leddevice/CMakeLists.txt @@ -68,6 +68,15 @@ if(ENABLE_SPIDEV) ) endif(ENABLE_SPIDEV) +SET(Leddevice_HEADERS + ${Leddevice_HEADERS} + ${CURRENT_SOURCE_DIR}/LedDeviceWS2812s.h + ) +SET(Leddevice_SOURCES + ${Leddevice_SOURCES} + ${CURRENT_SOURCE_DIR}/LedDeviceWS2812s.cpp +) + if(ENABLE_TINKERFORGE) SET(Leddevice_HEADERS ${Leddevice_HEADERS} diff --git a/libsrc/leddevice/LedDeviceFactory.cpp b/libsrc/leddevice/LedDeviceFactory.cpp index c7797c3d..abc0a2c7 100755 --- a/libsrc/leddevice/LedDeviceFactory.cpp +++ b/libsrc/leddevice/LedDeviceFactory.cpp @@ -31,6 +31,8 @@ #include "LedDevicePhilipsHue.h" #include "LedDeviceTpm2.h" +#include "LedDeviceWS2812s.h" + LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) { std::cout << "Device configuration: " << deviceConfig << std::endl; @@ -181,6 +183,10 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) LedDeviceTpm2* deviceTpm2 = new LedDeviceTpm2(output, rate); deviceTpm2->open(); device = deviceTpm2; + }else if (type == "ws2812s") + { + LedDeviceWS2812s * ledDeviceWS2812s = new LedDeviceWS2812s(); + device = ledDeviceWS2812s; } else { diff --git a/libsrc/leddevice/LedDeviceWS2812s.cpp b/libsrc/leddevice/LedDeviceWS2812s.cpp new file mode 100644 index 00000000..545519b0 --- /dev/null +++ b/libsrc/leddevice/LedDeviceWS2812s.cpp @@ -0,0 +1,529 @@ +// For license and other informations see LedDeviceWS2812s.h +// To activate: use led device "ws2812s" in the hyperion configuration + +// STL includes +#include +#include +#include +#include + +// Linux includes +#include +//#include + +// hyperion local includes +#include "LedDeviceWS2812s.h" + +LedDeviceWS2812s::LedDeviceWS2812s() : + LedDevice(), + mLedCount(0) +{ + // Init PWM generator and clear LED buffer + initHardware(); + //clearLEDBuffer(); +} + + +int LedDeviceWS2812s::write(const std::vector &ledValues) +{ + mLedCount = ledValues.size(); + //printf("Set leds, number: %d\n", mLedCount); + +// const unsigned dataLen = ledValues.size() * sizeof(ColorRgb); +// const uint8_t * dataPtr = reinterpret_cast(ledValues.data()); + + // Clear out the PWM buffer + // Disabled, because we will overwrite the buffer anyway. + + // Read data from LEDBuffer[], translate it into wire format, and write to PWMWaveform +// unsigned int LEDBuffeWordPos = 0; +// unsigned int PWMWaveformBitPos = 0; + unsigned int colorBits = 0; // Holds the GRB color before conversion to wire bit pattern + unsigned char colorBit = 0; // Holds current bit out of colorBits to be processed + unsigned int wireBit = 0; // Holds the current bit we will set in PWMWaveform +// Color_t color; + + for(size_t i=0; i=0; j--) { + colorBit = (colorBits & (1 << j)) ? 1 : 0; + switch(colorBit) { + case 1: + //wireBits = 0b110; // High, High, Low + setPWMBit(wireBit++, 1); + setPWMBit(wireBit++, 1); + setPWMBit(wireBit++, 0); + break; + case 0: + //wireBits = 0b100; // High, Low, Low + setPWMBit(wireBit++, 1); + setPWMBit(wireBit++, 0); + setPWMBit(wireBit++, 0); + break; + } + } + } + + // Copy PWM waveform to DMA's data buffer + //printf("Copying %d words to DMA data buffer\n", NUM_DATA_WORDS); + ctl = (struct control_data_s *)virtbase; + dma_cb_t *cbp = ctl->cb; + + // 72 bits per pixel / 32 bits per word = 2.25 words per pixel + // Add 1 to make sure the PWM FIFO gets the message: "we're sending zeroes" + // Times 4 because DMA works in bytes, not words + cbp->length = ((mLedCount * 2.25) + 1) * 4; + if(cbp->length > NUM_DATA_WORDS * 4) { + cbp->length = NUM_DATA_WORDS * 4; + } + + // This block is a major CPU hog when there are lots of pixels to be transmitted. + // It would go quicker with DMA. + for(unsigned int i = 0; i < (cbp->length / 4); i++) { + ctl->sample[i] = PWMWaveform[i]; + } + + + // Enable DMA and PWM engines, which should now send the data + startTransfer(); + + // Wait long enough for the DMA transfer to finish + // 3 RAM bits per wire bit, so 72 bits to send one color command. + //float bitTimeUSec = (float)(NUM_DATA_WORDS * 32) * 0.4; // Bits sent * time to transmit one bit, which is 0.4μSec + //printf("Delay for %d μSec\n", (int)bitTimeUSec); + //usleep((int)bitTimeUSec); + + return 0; +} + +int LedDeviceWS2812s::switchOff() +{ + return write(std::vector(mLedCount, ColorRgb{0,0,0})); +} + +LedDeviceWS2812s::~LedDeviceWS2812s() +{ + // Exit cleanly, freeing memory and stopping the DMA & PWM engines + // We trap all signals (including Ctrl+C), so even if you don't get here, it terminates correctly + terminate(0); +} + + +// ================================================================================================= +// ________ .__ +// / _____/ ____ ____ ________________ | | +// / \ ____/ __ \ / \_/ __ \_ __ \__ \ | | +// \ \_\ \ ___/| | \ ___/| | \// __ \| |__ +// \______ /\___ >___| /\___ >__| (____ /____/ +// \/ \/ \/ \/ \/ +// ================================================================================================= + +// Convenience functions +// -------------------------------------------------------------------------------------------------- +// Print some bits of a binary number (2nd arg is how many bits) +void LedDeviceWS2812s::printBinary(unsigned int i, unsigned int bits) { + int x; + for(x=bits-1; x>=0; x--) { + printf("%d", (i & (1 << x)) ? 1 : 0); + if(x % 16 == 0 && x > 0) { + printf(" "); + } else if(x % 4 == 0 && x > 0) { + printf(":"); + } + } +} + +// Reverse the bits in a word +unsigned int reverseWord(unsigned int word) { + unsigned int output = 0; + //unsigned char bit; + int i; + for(i=0; i<32; i++) { + //bit = word & (1 << i) ? 1 : 0; + output |= word & (1 << i) ? 1 : 0; + if(i<31) { + output <<= 1; + } + } + return output; +} + +// Not sure how this is better than usleep...? +/* +static void udelay(int us) { + struct timespec ts = { 0, us * 1000 }; + nanosleep(&ts, NULL); +} +*/ + + +// Shutdown functions +// -------------------------------------------------------------------------------------------------- +void LedDeviceWS2812s::terminate(int dummy) { + // Shut down the DMA controller + if(dma_reg) { + CLRBIT(dma_reg[DMA_CS], DMA_CS_ACTIVE); + usleep(100); + SETBIT(dma_reg[DMA_CS], DMA_CS_RESET); + usleep(100); + } + + // Shut down PWM + if(pwm_reg) { + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_PWEN1); + usleep(100); + pwm_reg[PWM_CTL] = (1 << PWM_CTL_CLRF1); + } + + // Free the allocated memory + if(page_map != 0) { + free(page_map); + } + + //exit(1); +} + +void LedDeviceWS2812s::fatal(char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + terminate(0); +} + + +// Memory management +// -------------------------------------------------------------------------------------------------- +// Translate from virtual address to physical +unsigned int LedDeviceWS2812s::mem_virt_to_phys(void *virt) { + unsigned int offset = (uint8_t *)virt - virtbase; + return page_map[offset >> PAGE_SHIFT].physaddr + (offset % PAGE_SIZE); +} + +// Translate from physical address to virtual +unsigned int LedDeviceWS2812s::mem_phys_to_virt(uint32_t phys) { + unsigned int pg_offset = phys & (PAGE_SIZE - 1); + unsigned int pg_addr = phys - pg_offset; + + for (unsigned int i = 0; i < NUM_PAGES; i++) { + if (page_map[i].physaddr == pg_addr) { + return (uint32_t)virtbase + i * PAGE_SIZE + pg_offset; + } + } + fatal("Failed to reverse map phys addr %08x\n", phys); + + return 0; +} + +// Map a peripheral's IO memory into our virtual memory, so we can read/write it directly +void * LedDeviceWS2812s::map_peripheral(uint32_t base, uint32_t len) { + int fd = open("/dev/mem", O_RDWR); + void * vaddr; + + if (fd < 0) + fatal("Failed to open /dev/mem: %m\n"); + vaddr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, base); + if (vaddr == MAP_FAILED) + fatal("Failed to map peripheral at 0x%08x: %m\n", base); + close(fd); + + return vaddr; +} + +// Zero out the PWM waveform buffer +void LedDeviceWS2812s::clearPWMBuffer() { + memset(PWMWaveform, 0, NUM_DATA_WORDS * 4); // Times four because memset deals in bytes. +} + +// Set an individual bit in the PWM output array, accounting for word boundaries +// The (31 - bitIdx) is so that we write the data backwards, correcting its endianness +// This means getPWMBit will return something other than what was written, so it would be nice +// if the logic that calls this function would figure it out instead. (However, that's trickier) +void LedDeviceWS2812s::setPWMBit(unsigned int bitPos, unsigned char bit) { + + // Fetch word the bit is in + unsigned int wordOffset = (int)(bitPos / 32); + unsigned int bitIdx = bitPos - (wordOffset * 32); + + //printf("bitPos=%d wordOffset=%d bitIdx=%d value=%d\n", bitPos, wordOffset, bitIdx, bit); + + switch(bit) { + case 1: + PWMWaveform[wordOffset] |= (1 << (31 - bitIdx)); +// PWMWaveform[wordOffset] |= (1 << bitIdx); + break; + case 0: + PWMWaveform[wordOffset] &= ~(1 << (31 - bitIdx)); +// PWMWaveform[wordOffset] &= ~(1 << bitIdx); + break; + } +} + +// ================================================================================================= +// .___ .__ __ ___ ___ .___ +// | | ____ |__|/ |_ / | \_____ _______ __| _/_ _ _______ _______ ____ +// | |/ \| \ __\ / ~ \__ \\_ __ \/ __ |\ \/ \/ /\__ \\_ __ \_/ __ \ +// | | | \ || | \ Y // __ \| | \/ /_/ | \ / / __ \| | \/\ ___/ +// |___|___| /__||__| \___|_ /(____ /__| \____ | \/\_/ (____ /__| \___ > +// \/ \/ \/ \/ \/ \/ +// ================================================================================================= + +void LedDeviceWS2812s::initHardware() { + int pid; + int fd; + char pagemap_fn[64]; + + // Clear the PWM buffer + // --------------------------------------------------------------- + clearPWMBuffer(); + + // Set up peripheral access + // --------------------------------------------------------------- + dma_reg = (unsigned int *) map_peripheral(DMA_BASE, DMA_LEN); + dma_reg += 0x000; + pwm_reg = (unsigned int *) map_peripheral(PWM_BASE, PWM_LEN); + clk_reg = (unsigned int *) map_peripheral(CLK_BASE, CLK_LEN); + gpio_reg = (unsigned int *) map_peripheral(GPIO_BASE, GPIO_LEN); + + + // Set PWM alternate function for GPIO18 + // --------------------------------------------------------------- + //gpio_reg[1] &= ~(7 << 24); + //usleep(100); + //gpio_reg[1] |= (2 << 24); + //usleep(100); + SET_GPIO_ALT(18, 5); + + + // Allocate memory for the DMA control block & data to be sent + // --------------------------------------------------------------- + virtbase = (uint8_t *) mmap( + NULL, // Address + NUM_PAGES * PAGE_SIZE, // Length + PROT_READ | PROT_WRITE, // Protection + MAP_SHARED | // Shared + MAP_ANONYMOUS | // Not file-based, init contents to 0 + MAP_NORESERVE | // Don't reserve swap space + MAP_LOCKED, // Lock in RAM (don't swap) + -1, // File descriptor + 0); // Offset + + if (virtbase == MAP_FAILED) { + fatal("Failed to mmap physical pages: %m\n"); + } + + if ((unsigned long)virtbase & (PAGE_SIZE-1)) { + fatal("Virtual address is not page aligned\n"); + } + + //printf("virtbase mapped 0x%x bytes at 0x%x\n", NUM_PAGES * PAGE_SIZE, virtbase); + + // Allocate page map (pointers to the control block(s) and data for each CB + page_map = (page_map_t *) malloc(NUM_PAGES * sizeof(*page_map)); + if (page_map == 0) { + fatal("Failed to malloc page_map: %m\n"); + } else { + //printf("Allocated 0x%x bytes for page_map at 0x%x\n", NUM_PAGES * sizeof(*page_map), page_map); + } + + // Use /proc/self/pagemap to figure out the mapping between virtual and physical addresses + pid = getpid(); + sprintf(pagemap_fn, "/proc/%d/pagemap", pid); + fd = open(pagemap_fn, O_RDONLY); + + if (fd < 0) { + fatal("Failed to open %s: %m\n", pagemap_fn); + } + + if (lseek(fd, (unsigned long)virtbase >> 9, SEEK_SET) != (unsigned long)virtbase >> 9) { + fatal("Failed to seek on %s: %m\n", pagemap_fn); + } + + printf("Page map: %d pages\n", NUM_PAGES); + for (unsigned int i = 0; i < NUM_PAGES; i++) { + uint64_t pfn; + page_map[i].virtaddr = virtbase + i * PAGE_SIZE; + + // Following line forces page to be allocated + // (Note: Copied directly from Hirst's code... page_map[i].virtaddr[0] was just set...?) + page_map[i].virtaddr[0] = 0; + + if (read(fd, &pfn, sizeof(pfn)) != sizeof(pfn)) { + fatal("Failed to read %s: %m\n", pagemap_fn); + } + + if (((pfn >> 55) & 0xfbf) != 0x10c) { // pagemap bits: https://www.kernel.org/doc/Documentation/vm/pagemap.txt + fatal("Page %d not present (pfn 0x%016llx)\n", i, pfn); + } + + page_map[i].physaddr = (unsigned int)pfn << PAGE_SHIFT | 0x40000000; + //printf("Page map #%2d: virtual %8p ==> physical 0x%08x [0x%016llx]\n", i, page_map[i].virtaddr, page_map[i].physaddr, pfn); + } + + + // Set up control block + // --------------------------------------------------------------- + ctl = (struct control_data_s *)virtbase; + dma_cb_t *cbp = ctl->cb; + // FIXME: Change this to use DEFINEs + unsigned int phys_pwm_fifo_addr = 0x7e20c000 + 0x18; + + // No wide bursts, source increment, dest DREQ on line 5, wait for response, enable interrupt + cbp->info = DMA_TI_CONFIGWORD; + + // Source is our allocated memory + cbp->src = mem_virt_to_phys(ctl->sample); + + // Destination is the PWM controller + cbp->dst = phys_pwm_fifo_addr; + + // 72 bits per pixel / 32 bits per word = 2.25 words per pixel + // Add 1 to make sure the PWM FIFO gets the message: "we're sending zeroes" + // Times 4 because DMA works in bytes, not words + cbp->length = ((mLedCount * 2.25) + 1) * 4; + if(cbp->length > NUM_DATA_WORDS * 4) { + cbp->length = NUM_DATA_WORDS * 4; + } + + // We don't use striding + cbp->stride = 0; + + // These are reserved + cbp->pad[0] = 0; + cbp->pad[1] = 0; + + // Pointer to next block - 0 shuts down the DMA channel when transfer is complete + cbp->next = 0; + + // Testing + /* + ctl = (struct control_data_s *)virtbase; + ctl->sample[0] = 0x00000000; + ctl->sample[1] = 0x000000FA; + ctl->sample[2] = 0x0000FFFF; + ctl->sample[3] = 0xAAAAAAAA; + ctl->sample[4] = 0xF0F0F0F0; + ctl->sample[5] = 0x0A0A0A0A; + ctl->sample[6] = 0xF00F0000; + */ + + + // Stop any existing DMA transfers + // --------------------------------------------------------------- + dma_reg[DMA_CS] |= (1 << DMA_CS_ABORT); + usleep(100); + dma_reg[DMA_CS] = (1 << DMA_CS_RESET); + usleep(100); + + + // PWM Clock + // --------------------------------------------------------------- + // Kill the clock + // FIXME: Change this to use a DEFINE + clk_reg[PWM_CLK_CNTL] = 0x5A000000 | (1 << 5); + usleep(100); + + // Disable DMA requests + CLRBIT(pwm_reg[PWM_DMAC], PWM_DMAC_ENAB); + usleep(100); + + // The fractional part is quantized to a range of 0-1024, so multiply the decimal part by 1024. + // E.g., 0.25 * 1024 = 256. + // So, if you want a divisor of 400.5, set idiv to 400 and fdiv to 512. + unsigned int idiv = 400; + unsigned short fdiv = 0; // Should be 16 bits, but the value must be <= 1024 + clk_reg[PWM_CLK_DIV] = 0x5A000000 | (idiv << 12) | fdiv; // Set clock multiplier + usleep(100); + + // Enable the clock. Next-to-last digit means "enable clock". Last digit is 1 (oscillator), + // 4 (PLLA), 5 (PLLC), or 6 (PLLD) (according to the docs) although PLLA doesn't seem to work. + // FIXME: Change this to use a DEFINE + clk_reg[PWM_CLK_CNTL] = 0x5A000015; + usleep(100); + + + // PWM + // --------------------------------------------------------------- + // Clear any preexisting crap from the control & status register + pwm_reg[PWM_CTL] = 0; + + // Set transmission range (32 bytes, or 1 word) + // <32: Truncate. >32: Pad with SBIT1. As it happens, 32 is perfect. + pwm_reg[PWM_RNG1] = 32; + usleep(100); + + // Send DMA requests to fill the FIFO + pwm_reg[PWM_DMAC] = + (1 << PWM_DMAC_ENAB) | + (8 << PWM_DMAC_PANIC) | + (8 << PWM_DMAC_DREQ); + usleep(1000); + + // Clear the FIFO + SETBIT(pwm_reg[PWM_CTL], PWM_CTL_CLRF1); + usleep(100); + + // Don't repeat last FIFO contents if it runs dry + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_RPTL1); + usleep(100); + + // Silence (default) bit is 0 + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_SBIT1); + usleep(100); + + // Polarity = default (low = 0, high = 1) + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_POLA1); + usleep(100); + + // Enable serializer mode + SETBIT(pwm_reg[PWM_CTL], PWM_CTL_MODE1); + usleep(100); + + // Use FIFO rather than DAT1 + SETBIT(pwm_reg[PWM_CTL], PWM_CTL_USEF1); + usleep(100); + + // Disable MSEN1 + CLRBIT(pwm_reg[PWM_CTL], PWM_CTL_MSEN1); + usleep(100); + + + // DMA + // --------------------------------------------------------------- + // Raise an interrupt when transfer is complete, which will set the INT flag in the CS register + SETBIT(dma_reg[DMA_CS], DMA_CS_INT); + usleep(100); + + // Clear the END flag (by setting it - this is a "write 1 to clear", or W1C, bit) + SETBIT(dma_reg[DMA_CS], DMA_CS_END); + usleep(100); + + // Send the physical address of the control block into the DMA controller + dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(ctl->cb); + usleep(100); + + // Clear error flags, if any (these are also W1C bits) + // FIXME: Use a define instead of this + dma_reg[DMA_DEBUG] = 7; + usleep(100); +} + +// Begin the transfer +void LedDeviceWS2812s::startTransfer() { + // Enable DMA + dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(ctl->cb); + dma_reg[DMA_CS] = DMA_CS_CONFIGWORD | (1 << DMA_CS_ACTIVE); + usleep(100); + + // Enable PWM + SETBIT(pwm_reg[PWM_CTL], PWM_CTL_PWEN1); + +// dumpPWM(); +// dumpDMA(); +} diff --git a/libsrc/leddevice/LedDeviceWS2812s.h b/libsrc/leddevice/LedDeviceWS2812s.h new file mode 100644 index 00000000..2dca0fb0 --- /dev/null +++ b/libsrc/leddevice/LedDeviceWS2812s.h @@ -0,0 +1,451 @@ +#ifndef LEDDEVICEWS2812S_H_ +#define LEDDEVICEWS2812S_H_ + +#pragma once + + +// Set tabs to 4 spaces. + +// ================================================================================================= +// +// __ __ _________________ ______ ____________ ____________________.__ +// / \ / \/ _____/\_____ \ / __ \/_ \_____ \ \______ \______ \__| +// \ \/\/ /\_____ \ / ____/ > < | |/ ____/ | _/| ___/ | +// \ / / \/ \/ -- \| / \ | | \| | | | +// \__/\ / /_______ /\_______ \______ /|___\_______ \ |____|_ /|____| |__| +// \/ \/ \/ \/ \/ \/ +// +// WS2812 NeoPixel driver +// Based on code by Richard G. Hirst and others +// Adapted for the WS2812 by 626Pilot, April/May 2014 +// See: https://github.com/626Pilot/RaspberryPi-NeoPixel-WS2812 +// Version: https://github.com/626Pilot/RaspberryPi-NeoPixel-WS2812/blob/1d43407d9e6eba19bff24330bc09a27963b55751/ws2812-RPi.c +// Huge ASCII art section labels are from http://patorjk.com/software/taag/ +// +// LED driver adaptation by Kammerjaeger () +// mostly code removed that was not needed +// +// License: GPL +// +// You are using this at your OWN RISK. I believe this software is reasonably safe to use (aside +// from the intrinsic risk to those who are photosensitive - see below), although I can't be certain +// that it won't trash your hardware or cause property damage. +// +// Speaking of risk, WS2812 pixels are bright enough to cause eye pain and (for all I know) possibly +// retina damage when run at full strength. It's a good idea to set the brightness at 0.2 or so for +// direct viewing (whether you're looking directly at the pixels or not), or to put some diffuse +// material between you and the LEDs. +// +// PHOTOSENSITIVITY WARNING: +// Patterns of light and darkness (stationary or moving), flashing lights, patterns and backgrounds +// on screens, and the like, may cause epilleptic seizures in some people. This is a danger EVEN IF +// THE PERSON (WHICH MAY BE *YOU*) HAS NEVER KNOWINGLY HAD A PHOTOSENSITIVE EPISODE BEFORE. It's up +// to you to learn the warning signs, but symptoms may include dizziness, nausea, vision changes, +// convlusions, disorientation, involuntary movements, and eye twitching. (This list is not +// necessarily exhaustive.) +// +// NEOPIXEL BEST PRACTICES: https://learn.adafruit.com/adafruit-neopixel-uberguide/best-practices +// +// Connections: +// Positive to Raspberry Pi's 3.3v, for better separation connect only ground and data directly +// (5v can be used then without a problem, at least it worked for me, Kammerjaeger) +// Negative to Raspberry Pi's ground +// Data to GPIO18 (Pin 12) (through a resistor, which you should know from the Best +// Practices guide!) +// +// Buy WS2812-based stuff from: http://adafruit.com +// +// To activate: use led device "ws2812s" in the hyperion configuration +// (it needs to be root so it can map the peripherals' registers) +// +// ================================================================================================= + +// This is for the WS2812 LEDs. It won't work with the older WS2811s, although it could be modified +// for that without too much trouble. Preliminary driver used Frank Buss' servo driver, but I moved +// to Richard Hirst's memory mapping/access model because his code already works with DMA, and has +// what I think is a slightly cleaner way of accessing the registers: register[name] rather than +// *(register + name). + +// At the time of writing, there's a lot of confusing "PWM DMA" code revolving around simulating +// an FM signal. Usually this is done without properly initializing certain registers, which is +// OK for their purpose, but I needed to be able to transfer actual coherent data and have it wind +// up in a proper state once it was transferred. This has proven to be a somewhat painful task. +// The PWM controller likes to ignore the RPTL1 bit when the data is in a regular, repeating +// pattern. I'M NOT MAKING IT UP! It really does that. It's bizarre. There are lots of other +// strange irregularities as well, which had to be figured out through trial and error. It doesn't +// help that the BCM2835 ARM Peripherals manual contains outright errors and omissions! + +// Many examples of this kind of code have magic numbers in them. If you don't know, a magic number +// is one that either lacks an obvious structure (e.g. 0x2020C000) or purpose. Please don't use +// that stuff in any code you release! All magic numbers found in reference code have been changed +// to DEFINEs. That way, instead of seeing some inscrutable number, you see (e.g.) PWM_CTL. + +// References - BCM2835 ARM Peripherals: +// http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf +// +// Raspberry Pi low-level peripherals: +// http://elinux.org/RPi_Low-level_peripherals +// +// Richard Hirst's nice, clean code: +// https://github.com/richardghirst/PiBits/blob/master/PiFmDma/PiFmDma.c +// +// PWM clock register: +// http://www.raspberrypi.org/forums/viewtopic.php?t=8467&p=124620 +// +// Simple (because it's in assembly) PWM+DMA setup: +// https://github.com/mikedurso/rpi-projects/blob/master/asm-nyancat/rpi-nyancat.s +// +// Adafruit's NeoPixel driver: +// https://github.com/adafruit/Adafruit_NeoPixel/blob/master/Adafruit_NeoPixel.cpp + + +// ================================================================================================= +// .___ .__ .___ +// | | ____ ____ | | __ __ __| _/____ ______ +// | |/ \_/ ___\| | | | \/ __ |/ __ \ / ___/ +// | | | \ \___| |_| | / /_/ \ ___/ \___ \ +// |___|___| /\___ >____/____/\____ |\___ >____ > +// \/ \/ \/ \/ \/ +// ================================================================================================= + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Hyperion includes +#include + + +// ================================================================================================= +// ________ _____.__ ____ ____ ____ +// \______ \ _____/ ____\__| ____ ____ ______ / _ \ \ \ / /____ _______ ______ +// | | \_/ __ \ __\| |/ \_/ __ \ / ___/ > _ __| |__|___| /\___ >____ > \_____\ \ \___/ (____ /__| /____ > +// \/ \/ \/ \/ \/ \/ \/ \/ +// ================================================================================================= + +// Base addresses for GPIO, PWM, PWM clock, and DMA controllers (physical, not bus!) +// These will be "memory mapped" into virtual RAM so that they can be written and read directly. +// ------------------------------------------------------------------------------------------------- +#define DMA_BASE 0x20007000 +#define DMA_LEN 0x24 +#define PWM_BASE 0x2020C000 +#define PWM_LEN 0x28 +#define CLK_BASE 0x20101000 +#define CLK_LEN 0xA8 +#define GPIO_BASE 0x20200000 +#define GPIO_LEN 0xB4 + +// GPIO +// ------------------------------------------------------------------------------------------------- +#define GPFSEL0 0x20200000 // GPIO function select, pins 0-9 (bits 30-31 reserved) +#define GPFSEL1 0x20200004 // Pins 10-19 +#define GPFSEL2 0x20200008 // Pins 20-29 +#define GPFSEL3 0x2020000C // Pins 30-39 +#define GPFSEL4 0x20200010 // Pins 40-49 +#define GPFSEL5 0x20200014 // Pins 50-53 +#define GPSET0 0x2020001C // Set (turn on) pin +#define GPCLR0 0x20200028 // Clear (turn off) pin +#define GPPUD 0x20200094 // Internal pullup/pulldown resistor control +#define GPPUDCLK0 0x20200098 // PUD clock for pins 0-31 +#define GPPUDCLK1 0x2020009C // PUD clock for pins 32-53 + +// Memory offsets for the PWM clock register, which is undocumented! Please fix that, Broadcom! +// ------------------------------------------------------------------------------------------------- +#define PWM_CLK_CNTL 40 // Control (on/off) +#define PWM_CLK_DIV 41 // Divisor (bits 11:0 are *quantized* floating part, 31:12 integer part) + +// PWM Register Addresses (page 141) +// These are divided by 4 because the register offsets in the guide are in bytes (8 bits) but +// the pointers we use in this program are in words (32 bits). Buss' original defines are in +// word offsets, e.g. PWM_RNG1 was 4 and PWM_DAT1 was 5. This is functionally the same, but it +// matches the numbers supplied in the guide. +// ------------------------------------------------------------------------------------------------- +#define PWM_CTL 0x00 // Control Register +#define PWM_STA (0x04 / 4) // Status Register +#define PWM_DMAC (0x08 / 4) // DMA Control Register +#define PWM_RNG1 (0x10 / 4) // Channel 1 Range +#define PWM_DAT1 (0x14 / 4) // Channel 1 Data +#define PWM_FIF1 (0x18 / 4) // FIFO (for both channels - bytes are interleaved if both active) +#define PWM_RNG2 (0x20 / 4) // Channel 2 Range +#define PWM_DAT2 (0x24 / 4) // Channel 2 Data + +// PWM_CTL register bit offsets +// Note: Don't use MSEN1/2 for this purpose. It will screw things up. +// ------------------------------------------------------------------------------------------------- +#define PWM_CTL_MSEN2 15 // Channel 2 - 0: Use PWM algorithm. 1: Use M/S (serial) algorithm. +#define PWM_CTL_USEF2 13 // Channel 2 - 0: Use PWM_DAT2. 1: Use FIFO. +#define PWM_CTL_POLA2 12 // Channel 2 - Invert output polarity (if set, 0=high and 1=low) +#define PWM_CTL_SBIT2 11 // Channel 2 - Silence bit (default line state when not transmitting) +#define PWM_CTL_RPTL2 10 // Channel 2 - Repeat last data in FIFO +#define PWM_CTL_MODE2 9 // Channel 2 - Mode. 0=PWM, 1=Serializer +#define PWM_CTL_PWEN2 8 // Channel 2 - Enable PWM +#define PWM_CTL_CLRF1 6 // Clear FIFO +#define PWM_CTL_MSEN1 7 // Channel 1 - 0: Use PWM algorithm. 1: Use M/S (serial) algorithm. +#define PWM_CTL_USEF1 5 // Channel 1 - 0: Use PWM_DAT1. 1: Use FIFO. +#define PWM_CTL_POLA1 4 // Channel 1 - Invert output polarity (if set, 0=high and 1=low) +#define PWM_CTL_SBIT1 3 // Channel 1 - Silence bit (default line state when not transmitting) +#define PWM_CTL_RPTL1 2 // Channel 1 - Repeat last data in FIFO +#define PWM_CTL_MODE1 1 // Channel 1 - Mode. 0=PWM, 1=Serializer +#define PWM_CTL_PWEN1 0 // Channel 1 - Enable PWM + +// PWM_STA register bit offsets +// ------------------------------------------------------------------------------------------------- +#define PWM_STA_STA4 12 // Channel 4 State +#define PWM_STA_STA3 11 // Channel 3 State +#define PWM_STA_STA2 10 // Channel 2 State +#define PWM_STA_STA1 9 // Channel 1 State +#define PWM_STA_BERR 8 // Bus Error +#define PWM_STA_GAPO4 7 // Gap Occurred on Channel 4 +#define PWM_STA_GAPO3 6 // Gap Occurred on Channel 3 +#define PWM_STA_GAPO2 5 // Gap Occurred on Channel 2 +#define PWM_STA_GAPO1 4 // Gap Occurred on Channel 1 +#define PWM_STA_RERR1 3 // FIFO Read Error +#define PWM_STA_WERR1 2 // FIFO Write Error +#define PWM_STA_EMPT1 1 // FIFO Empty +#define PWM_STA_FULL1 0 // FIFO Full + +// PWM_DMAC bit offsets +// ------------------------------------------------------------------------------------------------- +#define PWM_DMAC_ENAB 31 // 0: DMA Disabled. 1: DMA Enabled. +#define PWM_DMAC_PANIC 8 // Bits 15:8. Threshold for PANIC signal. Default 7. +#define PWM_DMAC_DREQ 0 // Bits 7:0. Threshold for DREQ signal. Default 7. + +// PWM_RNG1, PWM_RNG2 +// -------------------------------------------------------------------------------------------------- +// Defines the transmission range. In PWM mode, evenly spaced pulses are sent within a period +// of length defined in these registers. In serial mode, serialized data is sent within the +// same period. The value is normally 32. If less, data will be truncated. If more, data will +// be padded with zeros. + +// DAT1, DAT2 +// -------------------------------------------------------------------------------------------------- +// NOTE: These registers are not useful for our purposes - we will use the FIFO instead! +// Stores 32 bits of data to be sent when USEF1/USEF2 is 0. In PWM mode, defines how many +// pulses will be sent within the period specified in PWM_RNG1/PWM_RNG2. In serializer mode, +// defines a 32-bit word to be transmitted. + +// FIF1 +// -------------------------------------------------------------------------------------------------- +// 32-bit-wide register used to "stuff" the FIFO, which has 16 32-bit words. (So, if you write +// it 16 times, it will fill the FIFO.) +// See also: PWM_STA_EMPT1 (FIFO empty) +// PWM_STA_FULL1 (FIFO full) +// PWM_CTL_CLRF1 (Clear FIFO) + +// DMA +// -------------------------------------------------------------------------------------------------- +// DMA registers (divided by four to convert form word to byte offsets, as with the PWM registers) +#define DMA_CS (0x00 / 4) // Control & Status register +#define DMA_CONBLK_AD (0x04 / 4) // Address of Control Block (must be 256-BYTE ALIGNED!!!) +#define DMA_TI (0x08 / 4) // Transfer Information (populated from CB) +#define DMA_SOURCE_AD (0x0C / 4) // Source address, populated from CB. Physical address. +#define DMA_DEST_AD (0x10 / 4) // Destination address, populated from CB. Bus address. +#define DMA_TXFR_LEN (0x14 / 4) // Transfer length, populated from CB +#define DMA_STRIDE (0x18 / 4) // Stride, populated from CB +#define DMA_NEXTCONBK (0x1C / 4) // Next control block address, populated from CB +#define DMA_DEBUG (0x20 / 4) // Debug settings + +// DMA Control & Status register bit offsets +#define DMA_CS_RESET 31 // Reset the controller for this channel +#define DMA_CS_ABORT 30 // Set to abort transfer +#define DMA_CS_DISDEBUG 29 // Disable debug pause signal +#define DMA_CS_WAIT_FOR 28 // Wait for outstanding writes +#define DMA_CS_PANIC_PRI 20 // Panic priority (bits 23:20), default 7 +#define DMA_CS_PRIORITY 16 // AXI priority level (bits 19:16), default 7 +#define DMA_CS_ERROR 8 // Set when there's been an error +#define DMA_CS_WAITING_FOR 6 // Set when the channel's waiting for a write to be accepted +#define DMA_CS_DREQ_STOPS_DMA 5 // Set when the DMA is paused because DREQ is inactive +#define DMA_CS_PAUSED 4 // Set when the DMA is paused (active bit cleared, etc.) +#define DMA_CS_DREQ 3 // Set when DREQ line is high +#define DMA_CS_INT 2 // If INTEN is set, this will be set on CB transfer end +#define DMA_CS_END 1 // Set when the current control block is finished +#define DMA_CS_ACTIVE 0 // Enable DMA (CB_ADDR must not be 0) +// Default CS word +#define DMA_CS_CONFIGWORD (8 << DMA_CS_PANIC_PRI) | \ + (8 << DMA_CS_PRIORITY) | \ + (1 << DMA_CS_WAIT_FOR) + +// DREQ lines (page 61, most DREQs omitted) +#define DMA_DREQ_ALWAYS 0 +#define DMA_DREQ_PCM_TX 2 +#define DMA_DREQ_PCM_RX 3 +#define DMA_DREQ_PWM 5 +#define DMA_DREQ_SPI_TX 6 +#define DMA_DREQ_SPI_RX 7 +#define DMA_DREQ_BSC_TX 8 +#define DMA_DREQ_BSC_RX 9 + +// DMA Transfer Information register bit offsets +// We don't write DMA_TI directly. It's populated from the TI field in a control block. +#define DMA_TI_NO_WIDE_BURSTS 26 // Don't do wide writes in 2-beat bursts +#define DMA_TI_WAITS 21 // Wait this many cycles after end of each read/write +#define DMA_TI_PERMAP 16 // Peripheral # whose ready signal controls xfer rate (pwm=5) +#define DMA_TI_BURST_LENGTH 12 // Length of burst in words (bits 15:12) +#define DMA_TI_SRC_IGNORE 11 // Don't perform source reads (for fast cache fill) +#define DMA_TI_SRC_DREQ 10 // Peripheral in PERMAP gates source reads +#define DMA_TI_SRC_WIDTH 9 // Source transfer width - 0=32 bits, 1=128 bits +#define DMA_TI_SRC_INC 8 // Source address += SRC_WITH after each read +#define DMA_TI_DEST_IGNORE 7 // Don't perform destination writes +#define DMA_TI_DEST_DREQ 6 // Peripheral in PERMAP gates destination writes +#define DMA_TI_DEST_WIDTH 5 // Destination transfer width - 0=32 bits, 1=128 bits +#define DMA_TI_DEST_INC 4 // Dest address += DEST_WIDTH after each read +#define DMA_TI_WAIT_RESP 3 // Wait for write response +#define DMA_TI_TDMODE 1 // 2D striding mode +#define DMA_TI_INTEN 0 // Interrupt enable +// Default TI word +#define DMA_TI_CONFIGWORD (1 << DMA_TI_NO_WIDE_BURSTS) | \ + (1 << DMA_TI_SRC_INC) | \ + (1 << DMA_TI_DEST_DREQ) | \ + (1 << DMA_TI_WAIT_RESP) | \ + (1 << DMA_TI_INTEN) | \ + (DMA_DREQ_PWM << DMA_TI_PERMAP) + +// DMA Debug register bit offsets +#define DMA_DEBUG_LITE 28 // Whether the controller is "Lite" +#define DMA_DEBUG_VERSION 25 // DMA Version (bits 27:25) +#define DMA_DEBUG_DMA_STATE 16 // DMA State (bits 24:16) +#define DMA_DEBUG_DMA_ID 8 // DMA controller's AXI bus ID (bits 15:8) +#define DMA_DEBUG_OUTSTANDING_WRITES 4 // Outstanding writes (bits 7:4) +#define DMA_DEBUG_READ_ERROR 2 // Slave read response error (clear by setting) +#define DMA_DEBUG_FIFO_ERROR 1 // Operational read FIFO error (clear by setting) +#define DMA_DEBUG_READ_LAST_NOT_SET 0 // AXI bus read last signal not set (clear by setting) + +// Control Block (CB) - this tells the DMA controller what to do. +typedef struct { + unsigned int + info, // Transfer Information (TI) + src, // Source address (physical) + dst, // Destination address (bus) + length, // Length in bytes (not words!) + stride, // We don't care about this + next, // Pointer to next control block + pad[2]; // These are "reserved" (unused) +} dma_cb_t; + +// The page map contains pointers to memory that we will allocate below. It uses two pointers +// per address. This is because the software (this program) deals only in virtual addresses, +// whereas the DMA controller can only access RAM via physical address. (If that's not confusing +// enough, it writes to peripherals by their bus addresses.) +typedef struct { + uint8_t *virtaddr; + uint32_t physaddr; +} page_map_t; + + +#define PAGE_SIZE 4096 // Size of a RAM page to be allocated +#define PAGE_SHIFT 12 // This is used for address translation +#define NUM_PAGES ((sizeof(struct control_data_s) + PAGE_SIZE - 1) >> PAGE_SHIFT) + +#define SETBIT(word, bit) word |= 1< &ledValues); + + /// Switch the leds off + virtual int switchOff(); + +private: + + /// the number of leds (needed when switching off) + size_t mLedCount; + + page_map_t *page_map; // This will hold the page map, which we'll allocate below + uint8_t *virtbase; // Pointer to some virtual memory that will be allocated + + volatile unsigned int *pwm_reg; // PWM controller register set + volatile unsigned int *clk_reg; // PWM clock manager register set + volatile unsigned int *dma_reg; // DMA controller register set + volatile unsigned int *gpio_reg; // GPIO pin controller register set + + // Contains arrays of control blocks and their related samples. + // One pixel needs 72 bits (24 bits for the color * 3 to represent them on the wire). + // 768 words = 341.3 pixels + // 1024 words = 455.1 pixels + // The highest I can make this number is 1016. Any higher, and it will start copying garbage to the + // PWM controller. I think it might be because of the virtual->physical memory mapping not being + // contiguous, so *pointer+1016 isn't "next door" to *pointer+1017 for some weird reason. + // However, that's still enough for 451.5 color instructions! If someone has more pixels than that + // to control, they can figure it out. I tried Hirst's message of having one CB per word, which + // seems like it might fix that, but I couldn't figure it out. + #define NUM_DATA_WORDS 1016 + struct control_data_s { + dma_cb_t cb[1]; + uint32_t sample[NUM_DATA_WORDS]; + }; + + struct control_data_s *ctl; + + // PWM waveform buffer (in words), 16 32-bit words are enough to hold 170 wire bits. + // That's OK if we only transmit from the FIFO, but for DMA, we will use a much larger size. + // 1024 (4096 bytes) should be enough for over 400 elements. It can be bumped up if you need more! + unsigned int PWMWaveform[NUM_DATA_WORDS]; + + void initHardware(); + void startTransfer(); + + void clearPWMBuffer(); + void setPWMBit(unsigned int bitPos, unsigned char bit); + + unsigned int mem_phys_to_virt(uint32_t phys); + unsigned int mem_virt_to_phys(void *virt); + void terminate(int dummy); + void fatal(char *fmt, ...); + void * map_peripheral(uint32_t base, uint32_t len); + void printBinary(unsigned int i, unsigned int bits); +}; + + + + + + + + + + + + +#endif /* LEDDEVICEWS2812S_H_ */ From 61da05e1081777ae4708f593669968159b0fe8a5 Mon Sep 17 00:00:00 2001 From: David Brodski Date: Wed, 17 Sep 2014 21:12:46 +0200 Subject: [PATCH 2/9] Moved defines into cpp file make include of h file smaller removed not needed includes fixed warnings (removed some ascii art for that) Former-commit-id: 71b16cf7e73a9463462820238d12069e4d1e6d6e --- libsrc/leddevice/LedDeviceWS2812s.cpp | 227 +++++++++++++++++++++- libsrc/leddevice/LedDeviceWS2812s.h | 260 +------------------------- 2 files changed, 226 insertions(+), 261 deletions(-) diff --git a/libsrc/leddevice/LedDeviceWS2812s.cpp b/libsrc/leddevice/LedDeviceWS2812s.cpp index 545519b0..d7fd19b4 100644 --- a/libsrc/leddevice/LedDeviceWS2812s.cpp +++ b/libsrc/leddevice/LedDeviceWS2812s.cpp @@ -9,11 +9,224 @@ // Linux includes #include +#include +#include +//#include //#include // hyperion local includes #include "LedDeviceWS2812s.h" +// ==== Defines and Vars ==== + +// Base addresses for GPIO, PWM, PWM clock, and DMA controllers (physical, not bus!) +// These will be "memory mapped" into virtual RAM so that they can be written and read directly. +// ------------------------------------------------------------------------------------------------- +#define DMA_BASE 0x20007000 +#define DMA_LEN 0x24 +#define PWM_BASE 0x2020C000 +#define PWM_LEN 0x28 +#define CLK_BASE 0x20101000 +#define CLK_LEN 0xA8 +#define GPIO_BASE 0x20200000 +#define GPIO_LEN 0xB4 + +// GPIO +// ------------------------------------------------------------------------------------------------- +#define GPFSEL0 0x20200000 // GPIO function select, pins 0-9 (bits 30-31 reserved) +#define GPFSEL1 0x20200004 // Pins 10-19 +#define GPFSEL2 0x20200008 // Pins 20-29 +#define GPFSEL3 0x2020000C // Pins 30-39 +#define GPFSEL4 0x20200010 // Pins 40-49 +#define GPFSEL5 0x20200014 // Pins 50-53 +#define GPSET0 0x2020001C // Set (turn on) pin +#define GPCLR0 0x20200028 // Clear (turn off) pin +#define GPPUD 0x20200094 // Internal pullup/pulldown resistor control +#define GPPUDCLK0 0x20200098 // PUD clock for pins 0-31 +#define GPPUDCLK1 0x2020009C // PUD clock for pins 32-53 + +// Memory offsets for the PWM clock register, which is undocumented! Please fix that, Broadcom! +// ------------------------------------------------------------------------------------------------- +#define PWM_CLK_CNTL 40 // Control (on/off) +#define PWM_CLK_DIV 41 // Divisor (bits 11:0 are *quantized* floating part, 31:12 integer part) + +// PWM Register Addresses (page 141) +// These are divided by 4 because the register offsets in the guide are in bytes (8 bits) but +// the pointers we use in this program are in words (32 bits). Buss' original defines are in +// word offsets, e.g. PWM_RNG1 was 4 and PWM_DAT1 was 5. This is functionally the same, but it +// matches the numbers supplied in the guide. +// ------------------------------------------------------------------------------------------------- +#define PWM_CTL 0x00 // Control Register +#define PWM_STA (0x04 / 4) // Status Register +#define PWM_DMAC (0x08 / 4) // DMA Control Register +#define PWM_RNG1 (0x10 / 4) // Channel 1 Range +#define PWM_DAT1 (0x14 / 4) // Channel 1 Data +#define PWM_FIF1 (0x18 / 4) // FIFO (for both channels - bytes are interleaved if both active) +#define PWM_RNG2 (0x20 / 4) // Channel 2 Range +#define PWM_DAT2 (0x24 / 4) // Channel 2 Data + +// PWM_CTL register bit offsets +// Note: Don't use MSEN1/2 for this purpose. It will screw things up. +// ------------------------------------------------------------------------------------------------- +#define PWM_CTL_MSEN2 15 // Channel 2 - 0: Use PWM algorithm. 1: Use M/S (serial) algorithm. +#define PWM_CTL_USEF2 13 // Channel 2 - 0: Use PWM_DAT2. 1: Use FIFO. +#define PWM_CTL_POLA2 12 // Channel 2 - Invert output polarity (if set, 0=high and 1=low) +#define PWM_CTL_SBIT2 11 // Channel 2 - Silence bit (default line state when not transmitting) +#define PWM_CTL_RPTL2 10 // Channel 2 - Repeat last data in FIFO +#define PWM_CTL_MODE2 9 // Channel 2 - Mode. 0=PWM, 1=Serializer +#define PWM_CTL_PWEN2 8 // Channel 2 - Enable PWM +#define PWM_CTL_CLRF1 6 // Clear FIFO +#define PWM_CTL_MSEN1 7 // Channel 1 - 0: Use PWM algorithm. 1: Use M/S (serial) algorithm. +#define PWM_CTL_USEF1 5 // Channel 1 - 0: Use PWM_DAT1. 1: Use FIFO. +#define PWM_CTL_POLA1 4 // Channel 1 - Invert output polarity (if set, 0=high and 1=low) +#define PWM_CTL_SBIT1 3 // Channel 1 - Silence bit (default line state when not transmitting) +#define PWM_CTL_RPTL1 2 // Channel 1 - Repeat last data in FIFO +#define PWM_CTL_MODE1 1 // Channel 1 - Mode. 0=PWM, 1=Serializer +#define PWM_CTL_PWEN1 0 // Channel 1 - Enable PWM + +// PWM_STA register bit offsets +// ------------------------------------------------------------------------------------------------- +#define PWM_STA_STA4 12 // Channel 4 State +#define PWM_STA_STA3 11 // Channel 3 State +#define PWM_STA_STA2 10 // Channel 2 State +#define PWM_STA_STA1 9 // Channel 1 State +#define PWM_STA_BERR 8 // Bus Error +#define PWM_STA_GAPO4 7 // Gap Occurred on Channel 4 +#define PWM_STA_GAPO3 6 // Gap Occurred on Channel 3 +#define PWM_STA_GAPO2 5 // Gap Occurred on Channel 2 +#define PWM_STA_GAPO1 4 // Gap Occurred on Channel 1 +#define PWM_STA_RERR1 3 // FIFO Read Error +#define PWM_STA_WERR1 2 // FIFO Write Error +#define PWM_STA_EMPT1 1 // FIFO Empty +#define PWM_STA_FULL1 0 // FIFO Full + +// PWM_DMAC bit offsets +// ------------------------------------------------------------------------------------------------- +#define PWM_DMAC_ENAB 31 // 0: DMA Disabled. 1: DMA Enabled. +#define PWM_DMAC_PANIC 8 // Bits 15:8. Threshold for PANIC signal. Default 7. +#define PWM_DMAC_DREQ 0 // Bits 7:0. Threshold for DREQ signal. Default 7. + +// PWM_RNG1, PWM_RNG2 +// -------------------------------------------------------------------------------------------------- +// Defines the transmission range. In PWM mode, evenly spaced pulses are sent within a period +// of length defined in these registers. In serial mode, serialized data is sent within the +// same period. The value is normally 32. If less, data will be truncated. If more, data will +// be padded with zeros. + +// DAT1, DAT2 +// -------------------------------------------------------------------------------------------------- +// NOTE: These registers are not useful for our purposes - we will use the FIFO instead! +// Stores 32 bits of data to be sent when USEF1/USEF2 is 0. In PWM mode, defines how many +// pulses will be sent within the period specified in PWM_RNG1/PWM_RNG2. In serializer mode, +// defines a 32-bit word to be transmitted. + +// FIF1 +// -------------------------------------------------------------------------------------------------- +// 32-bit-wide register used to "stuff" the FIFO, which has 16 32-bit words. (So, if you write +// it 16 times, it will fill the FIFO.) +// See also: PWM_STA_EMPT1 (FIFO empty) +// PWM_STA_FULL1 (FIFO full) +// PWM_CTL_CLRF1 (Clear FIFO) + +// DMA +// -------------------------------------------------------------------------------------------------- +// DMA registers (divided by four to convert form word to byte offsets, as with the PWM registers) +#define DMA_CS (0x00 / 4) // Control & Status register +#define DMA_CONBLK_AD (0x04 / 4) // Address of Control Block (must be 256-BYTE ALIGNED!!!) +#define DMA_TI (0x08 / 4) // Transfer Information (populated from CB) +#define DMA_SOURCE_AD (0x0C / 4) // Source address, populated from CB. Physical address. +#define DMA_DEST_AD (0x10 / 4) // Destination address, populated from CB. Bus address. +#define DMA_TXFR_LEN (0x14 / 4) // Transfer length, populated from CB +#define DMA_STRIDE (0x18 / 4) // Stride, populated from CB +#define DMA_NEXTCONBK (0x1C / 4) // Next control block address, populated from CB +#define DMA_DEBUG (0x20 / 4) // Debug settings + +// DMA Control & Status register bit offsets +#define DMA_CS_RESET 31 // Reset the controller for this channel +#define DMA_CS_ABORT 30 // Set to abort transfer +#define DMA_CS_DISDEBUG 29 // Disable debug pause signal +#define DMA_CS_WAIT_FOR 28 // Wait for outstanding writes +#define DMA_CS_PANIC_PRI 20 // Panic priority (bits 23:20), default 7 +#define DMA_CS_PRIORITY 16 // AXI priority level (bits 19:16), default 7 +#define DMA_CS_ERROR 8 // Set when there's been an error +#define DMA_CS_WAITING_FOR 6 // Set when the channel's waiting for a write to be accepted +#define DMA_CS_DREQ_STOPS_DMA 5 // Set when the DMA is paused because DREQ is inactive +#define DMA_CS_PAUSED 4 // Set when the DMA is paused (active bit cleared, etc.) +#define DMA_CS_DREQ 3 // Set when DREQ line is high +#define DMA_CS_INT 2 // If INTEN is set, this will be set on CB transfer end +#define DMA_CS_END 1 // Set when the current control block is finished +#define DMA_CS_ACTIVE 0 // Enable DMA (CB_ADDR must not be 0) +// Default CS word +#define DMA_CS_CONFIGWORD (8 << DMA_CS_PANIC_PRI) | \ + (8 << DMA_CS_PRIORITY) | \ + (1 << DMA_CS_WAIT_FOR) + +// DREQ lines (page 61, most DREQs omitted) +#define DMA_DREQ_ALWAYS 0 +#define DMA_DREQ_PCM_TX 2 +#define DMA_DREQ_PCM_RX 3 +#define DMA_DREQ_PWM 5 +#define DMA_DREQ_SPI_TX 6 +#define DMA_DREQ_SPI_RX 7 +#define DMA_DREQ_BSC_TX 8 +#define DMA_DREQ_BSC_RX 9 + +// DMA Transfer Information register bit offsets +// We don't write DMA_TI directly. It's populated from the TI field in a control block. +#define DMA_TI_NO_WIDE_BURSTS 26 // Don't do wide writes in 2-beat bursts +#define DMA_TI_WAITS 21 // Wait this many cycles after end of each read/write +#define DMA_TI_PERMAP 16 // Peripheral # whose ready signal controls xfer rate (pwm=5) +#define DMA_TI_BURST_LENGTH 12 // Length of burst in words (bits 15:12) +#define DMA_TI_SRC_IGNORE 11 // Don't perform source reads (for fast cache fill) +#define DMA_TI_SRC_DREQ 10 // Peripheral in PERMAP gates source reads +#define DMA_TI_SRC_WIDTH 9 // Source transfer width - 0=32 bits, 1=128 bits +#define DMA_TI_SRC_INC 8 // Source address += SRC_WITH after each read +#define DMA_TI_DEST_IGNORE 7 // Don't perform destination writes +#define DMA_TI_DEST_DREQ 6 // Peripheral in PERMAP gates destination writes +#define DMA_TI_DEST_WIDTH 5 // Destination transfer width - 0=32 bits, 1=128 bits +#define DMA_TI_DEST_INC 4 // Dest address += DEST_WIDTH after each read +#define DMA_TI_WAIT_RESP 3 // Wait for write response +#define DMA_TI_TDMODE 1 // 2D striding mode +#define DMA_TI_INTEN 0 // Interrupt enable +// Default TI word +#define DMA_TI_CONFIGWORD (1 << DMA_TI_NO_WIDE_BURSTS) | \ + (1 << DMA_TI_SRC_INC) | \ + (1 << DMA_TI_DEST_DREQ) | \ + (1 << DMA_TI_WAIT_RESP) | \ + (1 << DMA_TI_INTEN) | \ + (DMA_DREQ_PWM << DMA_TI_PERMAP) + +// DMA Debug register bit offsets +#define DMA_DEBUG_LITE 28 // Whether the controller is "Lite" +#define DMA_DEBUG_VERSION 25 // DMA Version (bits 27:25) +#define DMA_DEBUG_DMA_STATE 16 // DMA State (bits 24:16) +#define DMA_DEBUG_DMA_ID 8 // DMA controller's AXI bus ID (bits 15:8) +#define DMA_DEBUG_OUTSTANDING_WRITES 4 // Outstanding writes (bits 7:4) +#define DMA_DEBUG_READ_ERROR 2 // Slave read response error (clear by setting) +#define DMA_DEBUG_FIFO_ERROR 1 // Operational read FIFO error (clear by setting) +#define DMA_DEBUG_READ_LAST_NOT_SET 0 // AXI bus read last signal not set (clear by setting) + + + +#define PAGE_SIZE 4096 // Size of a RAM page to be allocated +#define PAGE_SHIFT 12 // This is used for address translation +#define NUM_PAGES ((sizeof(struct control_data_s) + PAGE_SIZE - 1) >> PAGE_SHIFT) + +#define SETBIT(word, bit) word |= 1< -// \/ \/ \/ \/ \/ \/ -// ================================================================================================= +// ==== Init Hardware ==== void LedDeviceWS2812s::initHardware() { int pid; @@ -341,7 +547,8 @@ void LedDeviceWS2812s::initHardware() { fatal("Failed to open %s: %m\n", pagemap_fn); } - if (lseek(fd, (unsigned long)virtbase >> 9, SEEK_SET) != (unsigned long)virtbase >> 9) { + off_t newOffset = (unsigned long)virtbase >> 9; + if (lseek(fd, newOffset, SEEK_SET) != newOffset) { fatal("Failed to seek on %s: %m\n", pagemap_fn); } diff --git a/libsrc/leddevice/LedDeviceWS2812s.h b/libsrc/leddevice/LedDeviceWS2812s.h index 2dca0fb0..d92ef39f 100644 --- a/libsrc/leddevice/LedDeviceWS2812s.h +++ b/libsrc/leddevice/LedDeviceWS2812s.h @@ -98,233 +98,18 @@ // Adafruit's NeoPixel driver: // https://github.com/adafruit/Adafruit_NeoPixel/blob/master/Adafruit_NeoPixel.cpp - -// ================================================================================================= -// .___ .__ .___ -// | | ____ ____ | | __ __ __| _/____ ______ -// | |/ \_/ ___\| | | | \/ __ |/ __ \ / ___/ -// | | | \ \___| |_| | / /_/ \ ___/ \___ \ -// |___|___| /\___ >____/____/\____ |\___ >____ > -// \/ \/ \/ \/ \/ -// ================================================================================================= - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - // Hyperion includes #include -// ================================================================================================= -// ________ _____.__ ____ ____ ____ -// \______ \ _____/ ____\__| ____ ____ ______ / _ \ \ \ / /____ _______ ______ -// | | \_/ __ \ __\| |/ \_/ __ \ / ___/ > _ __| |__|___| /\___ >____ > \_____\ \ \___/ (____ /__| /____ > -// \/ \/ \/ \/ \/ \/ \/ \/ -// ================================================================================================= - -// Base addresses for GPIO, PWM, PWM clock, and DMA controllers (physical, not bus!) -// These will be "memory mapped" into virtual RAM so that they can be written and read directly. -// ------------------------------------------------------------------------------------------------- -#define DMA_BASE 0x20007000 -#define DMA_LEN 0x24 -#define PWM_BASE 0x2020C000 -#define PWM_LEN 0x28 -#define CLK_BASE 0x20101000 -#define CLK_LEN 0xA8 -#define GPIO_BASE 0x20200000 -#define GPIO_LEN 0xB4 - -// GPIO -// ------------------------------------------------------------------------------------------------- -#define GPFSEL0 0x20200000 // GPIO function select, pins 0-9 (bits 30-31 reserved) -#define GPFSEL1 0x20200004 // Pins 10-19 -#define GPFSEL2 0x20200008 // Pins 20-29 -#define GPFSEL3 0x2020000C // Pins 30-39 -#define GPFSEL4 0x20200010 // Pins 40-49 -#define GPFSEL5 0x20200014 // Pins 50-53 -#define GPSET0 0x2020001C // Set (turn on) pin -#define GPCLR0 0x20200028 // Clear (turn off) pin -#define GPPUD 0x20200094 // Internal pullup/pulldown resistor control -#define GPPUDCLK0 0x20200098 // PUD clock for pins 0-31 -#define GPPUDCLK1 0x2020009C // PUD clock for pins 32-53 - -// Memory offsets for the PWM clock register, which is undocumented! Please fix that, Broadcom! -// ------------------------------------------------------------------------------------------------- -#define PWM_CLK_CNTL 40 // Control (on/off) -#define PWM_CLK_DIV 41 // Divisor (bits 11:0 are *quantized* floating part, 31:12 integer part) - -// PWM Register Addresses (page 141) -// These are divided by 4 because the register offsets in the guide are in bytes (8 bits) but -// the pointers we use in this program are in words (32 bits). Buss' original defines are in -// word offsets, e.g. PWM_RNG1 was 4 and PWM_DAT1 was 5. This is functionally the same, but it -// matches the numbers supplied in the guide. -// ------------------------------------------------------------------------------------------------- -#define PWM_CTL 0x00 // Control Register -#define PWM_STA (0x04 / 4) // Status Register -#define PWM_DMAC (0x08 / 4) // DMA Control Register -#define PWM_RNG1 (0x10 / 4) // Channel 1 Range -#define PWM_DAT1 (0x14 / 4) // Channel 1 Data -#define PWM_FIF1 (0x18 / 4) // FIFO (for both channels - bytes are interleaved if both active) -#define PWM_RNG2 (0x20 / 4) // Channel 2 Range -#define PWM_DAT2 (0x24 / 4) // Channel 2 Data - -// PWM_CTL register bit offsets -// Note: Don't use MSEN1/2 for this purpose. It will screw things up. -// ------------------------------------------------------------------------------------------------- -#define PWM_CTL_MSEN2 15 // Channel 2 - 0: Use PWM algorithm. 1: Use M/S (serial) algorithm. -#define PWM_CTL_USEF2 13 // Channel 2 - 0: Use PWM_DAT2. 1: Use FIFO. -#define PWM_CTL_POLA2 12 // Channel 2 - Invert output polarity (if set, 0=high and 1=low) -#define PWM_CTL_SBIT2 11 // Channel 2 - Silence bit (default line state when not transmitting) -#define PWM_CTL_RPTL2 10 // Channel 2 - Repeat last data in FIFO -#define PWM_CTL_MODE2 9 // Channel 2 - Mode. 0=PWM, 1=Serializer -#define PWM_CTL_PWEN2 8 // Channel 2 - Enable PWM -#define PWM_CTL_CLRF1 6 // Clear FIFO -#define PWM_CTL_MSEN1 7 // Channel 1 - 0: Use PWM algorithm. 1: Use M/S (serial) algorithm. -#define PWM_CTL_USEF1 5 // Channel 1 - 0: Use PWM_DAT1. 1: Use FIFO. -#define PWM_CTL_POLA1 4 // Channel 1 - Invert output polarity (if set, 0=high and 1=low) -#define PWM_CTL_SBIT1 3 // Channel 1 - Silence bit (default line state when not transmitting) -#define PWM_CTL_RPTL1 2 // Channel 1 - Repeat last data in FIFO -#define PWM_CTL_MODE1 1 // Channel 1 - Mode. 0=PWM, 1=Serializer -#define PWM_CTL_PWEN1 0 // Channel 1 - Enable PWM - -// PWM_STA register bit offsets -// ------------------------------------------------------------------------------------------------- -#define PWM_STA_STA4 12 // Channel 4 State -#define PWM_STA_STA3 11 // Channel 3 State -#define PWM_STA_STA2 10 // Channel 2 State -#define PWM_STA_STA1 9 // Channel 1 State -#define PWM_STA_BERR 8 // Bus Error -#define PWM_STA_GAPO4 7 // Gap Occurred on Channel 4 -#define PWM_STA_GAPO3 6 // Gap Occurred on Channel 3 -#define PWM_STA_GAPO2 5 // Gap Occurred on Channel 2 -#define PWM_STA_GAPO1 4 // Gap Occurred on Channel 1 -#define PWM_STA_RERR1 3 // FIFO Read Error -#define PWM_STA_WERR1 2 // FIFO Write Error -#define PWM_STA_EMPT1 1 // FIFO Empty -#define PWM_STA_FULL1 0 // FIFO Full - -// PWM_DMAC bit offsets -// ------------------------------------------------------------------------------------------------- -#define PWM_DMAC_ENAB 31 // 0: DMA Disabled. 1: DMA Enabled. -#define PWM_DMAC_PANIC 8 // Bits 15:8. Threshold for PANIC signal. Default 7. -#define PWM_DMAC_DREQ 0 // Bits 7:0. Threshold for DREQ signal. Default 7. - -// PWM_RNG1, PWM_RNG2 -// -------------------------------------------------------------------------------------------------- -// Defines the transmission range. In PWM mode, evenly spaced pulses are sent within a period -// of length defined in these registers. In serial mode, serialized data is sent within the -// same period. The value is normally 32. If less, data will be truncated. If more, data will -// be padded with zeros. - -// DAT1, DAT2 -// -------------------------------------------------------------------------------------------------- -// NOTE: These registers are not useful for our purposes - we will use the FIFO instead! -// Stores 32 bits of data to be sent when USEF1/USEF2 is 0. In PWM mode, defines how many -// pulses will be sent within the period specified in PWM_RNG1/PWM_RNG2. In serializer mode, -// defines a 32-bit word to be transmitted. - -// FIF1 -// -------------------------------------------------------------------------------------------------- -// 32-bit-wide register used to "stuff" the FIFO, which has 16 32-bit words. (So, if you write -// it 16 times, it will fill the FIFO.) -// See also: PWM_STA_EMPT1 (FIFO empty) -// PWM_STA_FULL1 (FIFO full) -// PWM_CTL_CLRF1 (Clear FIFO) - -// DMA -// -------------------------------------------------------------------------------------------------- -// DMA registers (divided by four to convert form word to byte offsets, as with the PWM registers) -#define DMA_CS (0x00 / 4) // Control & Status register -#define DMA_CONBLK_AD (0x04 / 4) // Address of Control Block (must be 256-BYTE ALIGNED!!!) -#define DMA_TI (0x08 / 4) // Transfer Information (populated from CB) -#define DMA_SOURCE_AD (0x0C / 4) // Source address, populated from CB. Physical address. -#define DMA_DEST_AD (0x10 / 4) // Destination address, populated from CB. Bus address. -#define DMA_TXFR_LEN (0x14 / 4) // Transfer length, populated from CB -#define DMA_STRIDE (0x18 / 4) // Stride, populated from CB -#define DMA_NEXTCONBK (0x1C / 4) // Next control block address, populated from CB -#define DMA_DEBUG (0x20 / 4) // Debug settings - -// DMA Control & Status register bit offsets -#define DMA_CS_RESET 31 // Reset the controller for this channel -#define DMA_CS_ABORT 30 // Set to abort transfer -#define DMA_CS_DISDEBUG 29 // Disable debug pause signal -#define DMA_CS_WAIT_FOR 28 // Wait for outstanding writes -#define DMA_CS_PANIC_PRI 20 // Panic priority (bits 23:20), default 7 -#define DMA_CS_PRIORITY 16 // AXI priority level (bits 19:16), default 7 -#define DMA_CS_ERROR 8 // Set when there's been an error -#define DMA_CS_WAITING_FOR 6 // Set when the channel's waiting for a write to be accepted -#define DMA_CS_DREQ_STOPS_DMA 5 // Set when the DMA is paused because DREQ is inactive -#define DMA_CS_PAUSED 4 // Set when the DMA is paused (active bit cleared, etc.) -#define DMA_CS_DREQ 3 // Set when DREQ line is high -#define DMA_CS_INT 2 // If INTEN is set, this will be set on CB transfer end -#define DMA_CS_END 1 // Set when the current control block is finished -#define DMA_CS_ACTIVE 0 // Enable DMA (CB_ADDR must not be 0) -// Default CS word -#define DMA_CS_CONFIGWORD (8 << DMA_CS_PANIC_PRI) | \ - (8 << DMA_CS_PRIORITY) | \ - (1 << DMA_CS_WAIT_FOR) - -// DREQ lines (page 61, most DREQs omitted) -#define DMA_DREQ_ALWAYS 0 -#define DMA_DREQ_PCM_TX 2 -#define DMA_DREQ_PCM_RX 3 -#define DMA_DREQ_PWM 5 -#define DMA_DREQ_SPI_TX 6 -#define DMA_DREQ_SPI_RX 7 -#define DMA_DREQ_BSC_TX 8 -#define DMA_DREQ_BSC_RX 9 - -// DMA Transfer Information register bit offsets -// We don't write DMA_TI directly. It's populated from the TI field in a control block. -#define DMA_TI_NO_WIDE_BURSTS 26 // Don't do wide writes in 2-beat bursts -#define DMA_TI_WAITS 21 // Wait this many cycles after end of each read/write -#define DMA_TI_PERMAP 16 // Peripheral # whose ready signal controls xfer rate (pwm=5) -#define DMA_TI_BURST_LENGTH 12 // Length of burst in words (bits 15:12) -#define DMA_TI_SRC_IGNORE 11 // Don't perform source reads (for fast cache fill) -#define DMA_TI_SRC_DREQ 10 // Peripheral in PERMAP gates source reads -#define DMA_TI_SRC_WIDTH 9 // Source transfer width - 0=32 bits, 1=128 bits -#define DMA_TI_SRC_INC 8 // Source address += SRC_WITH after each read -#define DMA_TI_DEST_IGNORE 7 // Don't perform destination writes -#define DMA_TI_DEST_DREQ 6 // Peripheral in PERMAP gates destination writes -#define DMA_TI_DEST_WIDTH 5 // Destination transfer width - 0=32 bits, 1=128 bits -#define DMA_TI_DEST_INC 4 // Dest address += DEST_WIDTH after each read -#define DMA_TI_WAIT_RESP 3 // Wait for write response -#define DMA_TI_TDMODE 1 // 2D striding mode -#define DMA_TI_INTEN 0 // Interrupt enable -// Default TI word -#define DMA_TI_CONFIGWORD (1 << DMA_TI_NO_WIDE_BURSTS) | \ - (1 << DMA_TI_SRC_INC) | \ - (1 << DMA_TI_DEST_DREQ) | \ - (1 << DMA_TI_WAIT_RESP) | \ - (1 << DMA_TI_INTEN) | \ - (DMA_DREQ_PWM << DMA_TI_PERMAP) - -// DMA Debug register bit offsets -#define DMA_DEBUG_LITE 28 // Whether the controller is "Lite" -#define DMA_DEBUG_VERSION 25 // DMA Version (bits 27:25) -#define DMA_DEBUG_DMA_STATE 16 // DMA State (bits 24:16) -#define DMA_DEBUG_DMA_ID 8 // DMA controller's AXI bus ID (bits 15:8) -#define DMA_DEBUG_OUTSTANDING_WRITES 4 // Outstanding writes (bits 7:4) -#define DMA_DEBUG_READ_ERROR 2 // Slave read response error (clear by setting) -#define DMA_DEBUG_FIFO_ERROR 1 // Operational read FIFO error (clear by setting) -#define DMA_DEBUG_READ_LAST_NOT_SET 0 // AXI bus read last signal not set (clear by setting) +// The page map contains pointers to memory that we will allocate below. It uses two pointers +// per address. This is because the software (this program) deals only in virtual addresses, +// whereas the DMA controller can only access RAM via physical address. (If that's not confusing +// enough, it writes to peripherals by their bus addresses.) +typedef struct { + uint8_t *virtaddr; + uint32_t physaddr; +} page_map_t; // Control Block (CB) - this tells the DMA controller what to do. typedef struct { @@ -338,33 +123,6 @@ typedef struct { pad[2]; // These are "reserved" (unused) } dma_cb_t; -// The page map contains pointers to memory that we will allocate below. It uses two pointers -// per address. This is because the software (this program) deals only in virtual addresses, -// whereas the DMA controller can only access RAM via physical address. (If that's not confusing -// enough, it writes to peripherals by their bus addresses.) -typedef struct { - uint8_t *virtaddr; - uint32_t physaddr; -} page_map_t; - - -#define PAGE_SIZE 4096 // Size of a RAM page to be allocated -#define PAGE_SHIFT 12 // This is used for address translation -#define NUM_PAGES ((sizeof(struct control_data_s) + PAGE_SIZE - 1) >> PAGE_SHIFT) - -#define SETBIT(word, bit) word |= 1< Date: Wed, 17 Sep 2014 21:20:36 +0200 Subject: [PATCH 3/9] Rename WS2812s to WS2812b (spelling error) Former-commit-id: 83c92c9ef99d45b8683860f199a14952c05d0f5d --- libsrc/leddevice/CMakeLists.txt | 4 +-- libsrc/leddevice/LedDeviceFactory.cpp | 8 ++--- ...DeviceWS2812s.cpp => LedDeviceWS2812b.cpp} | 32 +++++++++---------- ...{LedDeviceWS2812s.h => LedDeviceWS2812b.h} | 12 +++---- 4 files changed, 28 insertions(+), 28 deletions(-) rename libsrc/leddevice/{LedDeviceWS2812s.cpp => LedDeviceWS2812b.cpp} (97%) rename libsrc/leddevice/{LedDeviceWS2812s.h => LedDeviceWS2812b.h} (98%) diff --git a/libsrc/leddevice/CMakeLists.txt b/libsrc/leddevice/CMakeLists.txt index 0efbd18c..abe28bf5 100755 --- a/libsrc/leddevice/CMakeLists.txt +++ b/libsrc/leddevice/CMakeLists.txt @@ -70,11 +70,11 @@ endif(ENABLE_SPIDEV) SET(Leddevice_HEADERS ${Leddevice_HEADERS} - ${CURRENT_SOURCE_DIR}/LedDeviceWS2812s.h + ${CURRENT_SOURCE_DIR}/LedDeviceWS2812b.h ) SET(Leddevice_SOURCES ${Leddevice_SOURCES} - ${CURRENT_SOURCE_DIR}/LedDeviceWS2812s.cpp + ${CURRENT_SOURCE_DIR}/LedDeviceWS2812b.cpp ) if(ENABLE_TINKERFORGE) diff --git a/libsrc/leddevice/LedDeviceFactory.cpp b/libsrc/leddevice/LedDeviceFactory.cpp index abc0a2c7..0915b4fe 100755 --- a/libsrc/leddevice/LedDeviceFactory.cpp +++ b/libsrc/leddevice/LedDeviceFactory.cpp @@ -31,7 +31,7 @@ #include "LedDevicePhilipsHue.h" #include "LedDeviceTpm2.h" -#include "LedDeviceWS2812s.h" +#include "LedDeviceWS2812b.h" LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) { @@ -183,10 +183,10 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) LedDeviceTpm2* deviceTpm2 = new LedDeviceTpm2(output, rate); deviceTpm2->open(); device = deviceTpm2; - }else if (type == "ws2812s") + }else if (type == "ws2812b") { - LedDeviceWS2812s * ledDeviceWS2812s = new LedDeviceWS2812s(); - device = ledDeviceWS2812s; + LedDeviceWS2812b * ledDeviceWS2812b = new LedDeviceWS2812b(); + device = ledDeviceWS2812b; } else { diff --git a/libsrc/leddevice/LedDeviceWS2812s.cpp b/libsrc/leddevice/LedDeviceWS2812b.cpp similarity index 97% rename from libsrc/leddevice/LedDeviceWS2812s.cpp rename to libsrc/leddevice/LedDeviceWS2812b.cpp index d7fd19b4..2abbb1b9 100644 --- a/libsrc/leddevice/LedDeviceWS2812s.cpp +++ b/libsrc/leddevice/LedDeviceWS2812b.cpp @@ -1,4 +1,4 @@ -// For license and other informations see LedDeviceWS2812s.h +// For license and other informations see LedDeviceWS2812b.h // To activate: use led device "ws2812s" in the hyperion configuration // STL includes @@ -15,7 +15,7 @@ //#include // hyperion local includes -#include "LedDeviceWS2812s.h" +#include "LedDeviceWS2812b.h" // ==== Defines and Vars ==== @@ -227,7 +227,7 @@ -LedDeviceWS2812s::LedDeviceWS2812s() : +LedDeviceWS2812b::LedDeviceWS2812b() : LedDevice(), mLedCount(0) { @@ -237,7 +237,7 @@ LedDeviceWS2812s::LedDeviceWS2812s() : } -int LedDeviceWS2812s::write(const std::vector &ledValues) +int LedDeviceWS2812b::write(const std::vector &ledValues) { mLedCount = ledValues.size(); //printf("Set leds, number: %d\n", mLedCount); @@ -315,12 +315,12 @@ int LedDeviceWS2812s::write(const std::vector &ledValues) return 0; } -int LedDeviceWS2812s::switchOff() +int LedDeviceWS2812b::switchOff() { return write(std::vector(mLedCount, ColorRgb{0,0,0})); } -LedDeviceWS2812s::~LedDeviceWS2812s() +LedDeviceWS2812b::~LedDeviceWS2812b() { // Exit cleanly, freeing memory and stopping the DMA & PWM engines // We trap all signals (including Ctrl+C), so even if you don't get here, it terminates correctly @@ -340,7 +340,7 @@ LedDeviceWS2812s::~LedDeviceWS2812s() // Convenience functions // -------------------------------------------------------------------------------------------------- // Print some bits of a binary number (2nd arg is how many bits) -void LedDeviceWS2812s::printBinary(unsigned int i, unsigned int bits) { +void LedDeviceWS2812b::printBinary(unsigned int i, unsigned int bits) { int x; for(x=bits-1; x>=0; x--) { printf("%d", (i & (1 << x)) ? 1 : 0); @@ -378,7 +378,7 @@ static void udelay(int us) { // Shutdown functions // -------------------------------------------------------------------------------------------------- -void LedDeviceWS2812s::terminate(int dummy) { +void LedDeviceWS2812b::terminate(int dummy) { // Shut down the DMA controller if(dma_reg) { CLRBIT(dma_reg[DMA_CS], DMA_CS_ACTIVE); @@ -402,7 +402,7 @@ void LedDeviceWS2812s::terminate(int dummy) { //exit(1); } -void LedDeviceWS2812s::fatal(const char *fmt, ...) { +void LedDeviceWS2812b::fatal(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); @@ -414,13 +414,13 @@ void LedDeviceWS2812s::fatal(const char *fmt, ...) { // Memory management // -------------------------------------------------------------------------------------------------- // Translate from virtual address to physical -unsigned int LedDeviceWS2812s::mem_virt_to_phys(void *virt) { +unsigned int LedDeviceWS2812b::mem_virt_to_phys(void *virt) { unsigned int offset = (uint8_t *)virt - virtbase; return page_map[offset >> PAGE_SHIFT].physaddr + (offset % PAGE_SIZE); } // Translate from physical address to virtual -unsigned int LedDeviceWS2812s::mem_phys_to_virt(uint32_t phys) { +unsigned int LedDeviceWS2812b::mem_phys_to_virt(uint32_t phys) { unsigned int pg_offset = phys & (PAGE_SIZE - 1); unsigned int pg_addr = phys - pg_offset; @@ -435,7 +435,7 @@ unsigned int LedDeviceWS2812s::mem_phys_to_virt(uint32_t phys) { } // Map a peripheral's IO memory into our virtual memory, so we can read/write it directly -void * LedDeviceWS2812s::map_peripheral(uint32_t base, uint32_t len) { +void * LedDeviceWS2812b::map_peripheral(uint32_t base, uint32_t len) { int fd = open("/dev/mem", O_RDWR); void * vaddr; @@ -450,7 +450,7 @@ void * LedDeviceWS2812s::map_peripheral(uint32_t base, uint32_t len) { } // Zero out the PWM waveform buffer -void LedDeviceWS2812s::clearPWMBuffer() { +void LedDeviceWS2812b::clearPWMBuffer() { memset(PWMWaveform, 0, NUM_DATA_WORDS * 4); // Times four because memset deals in bytes. } @@ -458,7 +458,7 @@ void LedDeviceWS2812s::clearPWMBuffer() { // The (31 - bitIdx) is so that we write the data backwards, correcting its endianness // This means getPWMBit will return something other than what was written, so it would be nice // if the logic that calls this function would figure it out instead. (However, that's trickier) -void LedDeviceWS2812s::setPWMBit(unsigned int bitPos, unsigned char bit) { +void LedDeviceWS2812b::setPWMBit(unsigned int bitPos, unsigned char bit) { // Fetch word the bit is in unsigned int wordOffset = (int)(bitPos / 32); @@ -480,7 +480,7 @@ void LedDeviceWS2812s::setPWMBit(unsigned int bitPos, unsigned char bit) { // ==== Init Hardware ==== -void LedDeviceWS2812s::initHardware() { +void LedDeviceWS2812b::initHardware() { int pid; int fd; char pagemap_fn[64]; @@ -722,7 +722,7 @@ void LedDeviceWS2812s::initHardware() { } // Begin the transfer -void LedDeviceWS2812s::startTransfer() { +void LedDeviceWS2812b::startTransfer() { // Enable DMA dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(ctl->cb); dma_reg[DMA_CS] = DMA_CS_CONFIGWORD | (1 << DMA_CS_ACTIVE); diff --git a/libsrc/leddevice/LedDeviceWS2812s.h b/libsrc/leddevice/LedDeviceWS2812b.h similarity index 98% rename from libsrc/leddevice/LedDeviceWS2812s.h rename to libsrc/leddevice/LedDeviceWS2812b.h index d92ef39f..65351056 100644 --- a/libsrc/leddevice/LedDeviceWS2812s.h +++ b/libsrc/leddevice/LedDeviceWS2812b.h @@ -1,5 +1,5 @@ -#ifndef LEDDEVICEWS2812S_H_ -#define LEDDEVICEWS2812S_H_ +#ifndef LEDDEVICEWS2812B_H_ +#define LEDDEVICEWS2812B_H_ #pragma once @@ -126,14 +126,14 @@ typedef struct { /// /// Implementation of the LedDevice interface for writing to Ws2801 led device. /// -class LedDeviceWS2812s : public LedDevice +class LedDeviceWS2812b : public LedDevice { public: /// /// Constructs the LedDevice for a string containing leds of the type WS2812 - LedDeviceWS2812s(); + LedDeviceWS2812b(); - ~LedDeviceWS2812s(); + ~LedDeviceWS2812b(); /// /// Writes the led color values to the led-device /// @@ -206,4 +206,4 @@ private: -#endif /* LEDDEVICEWS2812S_H_ */ +#endif /* LEDDEVICEWS2812B_H_ */ From a92967fa7c345d4e9b0ed7207194fe884c3b31fd Mon Sep 17 00:00:00 2001 From: David Brodski Date: Thu, 18 Sep 2014 10:28:23 +0200 Subject: [PATCH 4/9] Added benchmark define some code cleanup and speedups Former-commit-id: 8254c34e1d10c598e127f46635ae6bafcb97087a --- libsrc/leddevice/LedDeviceWS2812b.cpp | 68 ++++++++++++++++++++------- libsrc/leddevice/LedDeviceWS2812b.h | 11 ++++- 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/libsrc/leddevice/LedDeviceWS2812b.cpp b/libsrc/leddevice/LedDeviceWS2812b.cpp index 2abbb1b9..bcd9f48d 100644 --- a/libsrc/leddevice/LedDeviceWS2812b.cpp +++ b/libsrc/leddevice/LedDeviceWS2812b.cpp @@ -14,6 +14,10 @@ //#include //#include +#ifdef BENCHMARK + #include +#endif + // hyperion local includes #include "LedDeviceWS2812b.h" @@ -230,31 +234,40 @@ LedDeviceWS2812b::LedDeviceWS2812b() : LedDevice(), mLedCount(0) + +#ifdef BENCHMARK + , + runCount(0), + combinedNseconds(0), + shortestNseconds(2147483647) +#endif + { + //shortestNseconds = 2147483647; // Init PWM generator and clear LED buffer initHardware(); //clearLEDBuffer(); + printf("WS2812b init finished \n"); } int LedDeviceWS2812b::write(const std::vector &ledValues) { +#ifdef BENCHMARK + timespec timeStart; + timespec timeEnd; + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &timeStart); +#endif + mLedCount = ledValues.size(); //printf("Set leds, number: %d\n", mLedCount); -// const unsigned dataLen = ledValues.size() * sizeof(ColorRgb); -// const uint8_t * dataPtr = reinterpret_cast(ledValues.data()); - // Clear out the PWM buffer // Disabled, because we will overwrite the buffer anyway. // Read data from LEDBuffer[], translate it into wire format, and write to PWMWaveform -// unsigned int LEDBuffeWordPos = 0; -// unsigned int PWMWaveformBitPos = 0; unsigned int colorBits = 0; // Holds the GRB color before conversion to wire bit pattern - unsigned char colorBit = 0; // Holds current bit out of colorBits to be processed unsigned int wireBit = 0; // Holds the current bit we will set in PWMWaveform -// Color_t color; for(size_t i=0; i &ledValues) // Iterate through color bits to get wire bits for(int j=23; j>=0; j--) { - colorBit = (colorBits & (1 << j)) ? 1 : 0; + unsigned char colorBit = (colorBits & (1 << j)) ? 1 : 0; // Holds current bit out of colorBits to be processed + + setPWMBit(wireBit++, 1); + setPWMBit(wireBit++, colorBit); + setPWMBit(wireBit++, 0); + /* old code for better understanding switch(colorBit) { case 1: //wireBits = 0b110; // High, High, Low @@ -279,13 +297,13 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) setPWMBit(wireBit++, 0); setPWMBit(wireBit++, 0); break; - } + }*/ } } // Copy PWM waveform to DMA's data buffer //printf("Copying %d words to DMA data buffer\n", NUM_DATA_WORDS); - ctl = (struct control_data_s *)virtbase; + struct control_data_s *ctl = (struct control_data_s *)virtbase; dma_cb_t *cbp = ctl->cb; // 72 bits per pixel / 32 bits per word = 2.25 words per pixel @@ -298,10 +316,10 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) // This block is a major CPU hog when there are lots of pixels to be transmitted. // It would go quicker with DMA. - for(unsigned int i = 0; i < (cbp->length / 4); i++) { - ctl->sample[i] = PWMWaveform[i]; - } - +// for(unsigned int i = 0; i < (cbp->length / 4); i++) { +// ctl->sample[i] = PWMWaveform[i]; +// } + memcpy ( ctl->sample, PWMWaveform, cbp->length ); // memcpy does the same and is potentially faster // Enable DMA and PWM engines, which should now send the data startTransfer(); @@ -312,6 +330,20 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) //printf("Delay for %d μSec\n", (int)bitTimeUSec); //usleep((int)bitTimeUSec); +#ifdef BENCHMARK + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &timeEnd); + timespec result; + + result.tv_sec = timeEnd.tv_sec - timeStart.tv_sec; + result.tv_nsec = timeEnd.tv_nsec - timeStart.tv_nsec; + if (result.tv_nsec < 0) { + result.tv_nsec = 1e9 - result.tv_nsec; + result.tv_sec -= 1; + } + runCount ++; + combinedNseconds += result.tv_nsec; + shortestNseconds = result.tv_nsec < shortestNseconds ? result.tv_nsec : shortestNseconds; +#endif return 0; } @@ -325,6 +357,10 @@ LedDeviceWS2812b::~LedDeviceWS2812b() // Exit cleanly, freeing memory and stopping the DMA & PWM engines // We trap all signals (including Ctrl+C), so even if you don't get here, it terminates correctly terminate(0); +#ifdef BENCHMARK + printf("WS2812b Benchmark results: Runs %d - Avarage %lu (n) - Minimum %ld (n)\n", + runCount, (runCount > 0 ? combinedNseconds / runCount : 0), shortestNseconds); +#endif } @@ -576,7 +612,7 @@ void LedDeviceWS2812b::initHardware() { // Set up control block // --------------------------------------------------------------- - ctl = (struct control_data_s *)virtbase; + struct control_data_s *ctl = (struct control_data_s *)virtbase; dma_cb_t *cbp = ctl->cb; // FIXME: Change this to use DEFINEs unsigned int phys_pwm_fifo_addr = 0x7e20c000 + 0x18; @@ -724,7 +760,7 @@ void LedDeviceWS2812b::initHardware() { // Begin the transfer void LedDeviceWS2812b::startTransfer() { // Enable DMA - dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(ctl->cb); + dma_reg[DMA_CONBLK_AD] = mem_virt_to_phys(((struct control_data_s *) virtbase)->cb); dma_reg[DMA_CS] = DMA_CS_CONFIGWORD | (1 << DMA_CS_ACTIVE); usleep(100); diff --git a/libsrc/leddevice/LedDeviceWS2812b.h b/libsrc/leddevice/LedDeviceWS2812b.h index 65351056..79216b9a 100644 --- a/libsrc/leddevice/LedDeviceWS2812b.h +++ b/libsrc/leddevice/LedDeviceWS2812b.h @@ -101,6 +101,7 @@ // Hyperion includes #include +//#define BENCHMARK // The page map contains pointers to memory that we will allocate below. It uses two pointers // per address. This is because the software (this program) deals only in virtual addresses, @@ -150,7 +151,7 @@ private: /// the number of leds (needed when switching off) size_t mLedCount; - page_map_t *page_map; // This will hold the page map, which we'll allocate below + page_map_t *page_map; // This will hold the page map, which we'll allocate uint8_t *virtbase; // Pointer to some virtual memory that will be allocated volatile unsigned int *pwm_reg; // PWM controller register set @@ -174,7 +175,7 @@ private: uint32_t sample[NUM_DATA_WORDS]; }; - struct control_data_s *ctl; + //struct control_data_s *ctl; // PWM waveform buffer (in words), 16 32-bit words are enough to hold 170 wire bits. // That's OK if we only transmit from the FIFO, but for DMA, we will use a much larger size. @@ -193,6 +194,12 @@ private: void fatal(const char *fmt, ...); void * map_peripheral(uint32_t base, uint32_t len); void printBinary(unsigned int i, unsigned int bits); + +#ifdef BENCHMARK + unsigned int runCount; + long combinedNseconds; + long shortestNseconds; +#endif }; From 5ff05d58fdfe86760acf1da764dcada75bc80601 Mon Sep 17 00:00:00 2001 From: David Brodski Date: Thu, 18 Sep 2014 20:56:32 +0200 Subject: [PATCH 5/9] fixed potential buffer overflow Former-commit-id: 2c9ea902fd563b909e6d0457c4957f80be86f93f --- libsrc/leddevice/LedDeviceWS2812b.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/libsrc/leddevice/LedDeviceWS2812b.cpp b/libsrc/leddevice/LedDeviceWS2812b.cpp index bcd9f48d..10df666a 100644 --- a/libsrc/leddevice/LedDeviceWS2812b.cpp +++ b/libsrc/leddevice/LedDeviceWS2812b.cpp @@ -269,6 +269,20 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) unsigned int colorBits = 0; // Holds the GRB color before conversion to wire bit pattern unsigned int wireBit = 0; // Holds the current bit we will set in PWMWaveform + // Copy PWM waveform to DMA's data buffer + //printf("Copying %d words to DMA data buffer\n", NUM_DATA_WORDS); + struct control_data_s *ctl = (struct control_data_s *)virtbase; + dma_cb_t *cbp = ctl->cb; + + // 72 bits per pixel / 32 bits per word = 2.25 words per pixel + // Add 1 to make sure the PWM FIFO gets the message: "we're sending zeroes" + // Times 4 because DMA works in bytes, not words + cbp->length = ((mLedCount * 2.25) + 1) * 4; + if(cbp->length > NUM_DATA_WORDS * 4) { + cbp->length = NUM_DATA_WORDS * 4; + mLedCount = (NUM_DATA_WORDS - 1) / 2.25; + } + for(size_t i=0; i &ledValues) } } - // Copy PWM waveform to DMA's data buffer - //printf("Copying %d words to DMA data buffer\n", NUM_DATA_WORDS); - struct control_data_s *ctl = (struct control_data_s *)virtbase; - dma_cb_t *cbp = ctl->cb; - - // 72 bits per pixel / 32 bits per word = 2.25 words per pixel - // Add 1 to make sure the PWM FIFO gets the message: "we're sending zeroes" - // Times 4 because DMA works in bytes, not words - cbp->length = ((mLedCount * 2.25) + 1) * 4; - if(cbp->length > NUM_DATA_WORDS * 4) { - cbp->length = NUM_DATA_WORDS * 4; - } - // This block is a major CPU hog when there are lots of pixels to be transmitted. // It would go quicker with DMA. // for(unsigned int i = 0; i < (cbp->length / 4); i++) { From d41857e626053de54f4295ab987106ac6fec0fa0 Mon Sep 17 00:00:00 2001 From: David Brodski Date: Thu, 18 Sep 2014 23:41:46 +0200 Subject: [PATCH 6/9] Pre initialized bit pattern to speed things up Former-commit-id: 08dc5ee53854997060af0257b5cff324d29f87b5 --- libsrc/leddevice/LedDeviceWS2812b.cpp | 54 ++++++++++++++++++++++++--- libsrc/leddevice/LedDeviceWS2812b.h | 2 +- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/libsrc/leddevice/LedDeviceWS2812b.cpp b/libsrc/leddevice/LedDeviceWS2812b.cpp index 10df666a..827a7f3d 100644 --- a/libsrc/leddevice/LedDeviceWS2812b.cpp +++ b/libsrc/leddevice/LedDeviceWS2812b.cpp @@ -247,6 +247,16 @@ LedDeviceWS2812b::LedDeviceWS2812b() : // Init PWM generator and clear LED buffer initHardware(); //clearLEDBuffer(); + + // init bit pattern, it is always 1X0 + unsigned int wireBit = 0; + + while ((wireBit + 3) < ((NUM_DATA_WORDS) * 4 * 8)){ + setPWMBit(wireBit++, 1); + setPWMBit(wireBit++, 0); // just init it with 0 + setPWMBit(wireBit++, 0); + } + printf("WS2812b init finished \n"); } @@ -267,7 +277,7 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) // Read data from LEDBuffer[], translate it into wire format, and write to PWMWaveform unsigned int colorBits = 0; // Holds the GRB color before conversion to wire bit pattern - unsigned int wireBit = 0; // Holds the current bit we will set in PWMWaveform + unsigned int wireBit = 1; // Holds the current bit we will set in PWMWaveform, start with 1 and skip the other two for speed // Copy PWM waveform to DMA's data buffer //printf("Copying %d words to DMA data buffer\n", NUM_DATA_WORDS); @@ -277,12 +287,16 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) // 72 bits per pixel / 32 bits per word = 2.25 words per pixel // Add 1 to make sure the PWM FIFO gets the message: "we're sending zeroes" // Times 4 because DMA works in bytes, not words - cbp->length = ((mLedCount * 2.25) + 1) * 4; + cbp->length = (mLedCount * 2.25) * 4; + //cbp->length = ((mLedCount * 2.25) + 1) * 4; if(cbp->length > NUM_DATA_WORDS * 4) { cbp->length = NUM_DATA_WORDS * 4; - mLedCount = (NUM_DATA_WORDS - 1) / 2.25; + mLedCount = NUM_DATA_WORDS / 2.25; + //mLedCount = (NUM_DATA_WORDS - 1) / 2.25; } + + for(size_t i=0; i &ledValues) for(int j=23; j>=0; j--) { unsigned char colorBit = (colorBits & (1 << j)) ? 1 : 0; // Holds current bit out of colorBits to be processed - setPWMBit(wireBit++, 1); - setPWMBit(wireBit++, colorBit); - setPWMBit(wireBit++, 0); + setPWMBit(wireBit, colorBit); + wireBit +=3; /* old code for better understanding switch(colorBit) { case 1: @@ -315,6 +328,25 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) } } + //remove one to undo optimization + wireBit --; + + // fill up the bytes + int rest = 32 - wireBit % 32; + unsigned int oldwireBitValue = wireBit; + +// printBinary(PWMWaveform[(int)(oldwireBitValue / 32)], 32); +// printf(" pre\n"); + + // zero rest of the 4 bytes / int so that output is 0 (no data is send) + for (int i = 0; i < rest; i += 3){ + setPWMBit(wireBit, 0); + wireBit += 3; + } + +// printBinary(PWMWaveform[(int)(oldwireBitValue / 32)], 32); +// printf(" post\n"); + // This block is a major CPU hog when there are lots of pixels to be transmitted. // It would go quicker with DMA. // for(unsigned int i = 0; i < (cbp->length / 4); i++) { @@ -325,6 +357,16 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) // Enable DMA and PWM engines, which should now send the data startTransfer(); + // restore bit pattern + wireBit = oldwireBitValue; + for (int i = 0; i < rest; i += 3){ + setPWMBit(wireBit, 1); + wireBit += 3; + } + +// printBinary(PWMWaveform[(int)(oldwireBitValue / 32)], 32); +// printf(" restored\n"); + // Wait long enough for the DMA transfer to finish // 3 RAM bits per wire bit, so 72 bits to send one color command. //float bitTimeUSec = (float)(NUM_DATA_WORDS * 32) * 0.4; // Bits sent * time to transmit one bit, which is 0.4μSec diff --git a/libsrc/leddevice/LedDeviceWS2812b.h b/libsrc/leddevice/LedDeviceWS2812b.h index 79216b9a..55ce250a 100644 --- a/libsrc/leddevice/LedDeviceWS2812b.h +++ b/libsrc/leddevice/LedDeviceWS2812b.h @@ -101,7 +101,7 @@ // Hyperion includes #include -//#define BENCHMARK +#define BENCHMARK // The page map contains pointers to memory that we will allocate below. It uses two pointers // per address. This is because the software (this program) deals only in virtual addresses, From 961bef22f598c5c2bba00540ac793739470443e5 Mon Sep 17 00:00:00 2001 From: David Brodski Date: Fri, 19 Sep 2014 02:00:32 +0200 Subject: [PATCH 7/9] Assembler version 1: use roll and bit clear instructions Former-commit-id: 4f27d34dd63c635a65ee33f2c368978d5b162974 --- libsrc/leddevice/LedDeviceWS2812b.cpp | 53 ++++++++++++++++++++++++++- libsrc/leddevice/LedDeviceWS2812b.h | 1 + 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/libsrc/leddevice/LedDeviceWS2812b.cpp b/libsrc/leddevice/LedDeviceWS2812b.cpp index 827a7f3d..84b7ac97 100644 --- a/libsrc/leddevice/LedDeviceWS2812b.cpp +++ b/libsrc/leddevice/LedDeviceWS2812b.cpp @@ -260,6 +260,31 @@ LedDeviceWS2812b::LedDeviceWS2812b() : printf("WS2812b init finished \n"); } +#ifdef WS2812_ASM_OPTI + +// rotate register, used to move the 1 around :-) +static inline __attribute__((always_inline)) +uint32_t arm_ror_imm(uint32_t v, uint32_t sh) { + uint32_t d; + asm ("ROR %[Rd], %[Rm], %[Is]" : [Rd] "=r" (d) : [Rm] "r" (v), [Is] "i" (sh)); + return d; +} + +static inline __attribute__((always_inline)) +uint32_t arm_ror(uint32_t v, uint32_t sh) { + uint32_t d; + asm ("ROR %[Rd], %[Rm], %[Rs]" : [Rd] "=r" (d) : [Rm] "r" (v), [Rs] "r" (sh)); + return d; +} + + +static inline __attribute__((always_inline)) +uint32_t arm_Bit_Clear_imm(uint32_t v, uint32_t v2) { + uint32_t d; + asm ("BIC %[Rd], %[Rm], %[Rs]" : [Rd] "=r" (d) : [Rm] "r" (v), [Rs] "r" (v2)); + return d; +} +#endif int LedDeviceWS2812b::write(const std::vector &ledValues) { @@ -295,6 +320,9 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) //mLedCount = (NUM_DATA_WORDS - 1) / 2.25; } +#ifdef WS2812_ASM_OPTI + unsigned int startbitPattern = 0x40000000; // = 0100 0000 0000 0000 0000 0000 0000 0000 pattern +#endif for(size_t i=0; i &ledValues) // Iterate through color bits to get wire bits for(int j=23; j>=0; j--) { - unsigned char colorBit = (colorBits & (1 << j)) ? 1 : 0; // Holds current bit out of colorBits to be processed +#ifdef WS2812_ASM_OPTI + // Fetch word the bit is in + unsigned int wordOffset = (int)(wireBit / 32); + wireBit +=3; +// printBinary(startbitPattern, 32); +// printf(" %d\n", j); + if (colorBits & (1 << j)) { + PWMWaveform[wordOffset] |= startbitPattern; + } else { + PWMWaveform[wordOffset] = arm_Bit_Clear_imm(PWMWaveform[wordOffset], startbitPattern); + } + + startbitPattern = arm_ror_imm(startbitPattern, 3); + +#else + unsigned char colorBit = (colorBits & (1 << j)) ? 1 : 0; // Holds current bit out of colorBits to be processed setPWMBit(wireBit, colorBit); wireBit +=3; +#endif /* old code for better understanding switch(colorBit) { case 1: @@ -328,6 +372,11 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) } } +#ifdef WS2812_ASM_OPTI + // calculate the bits manually since it is not needed with asm + //wireBit += mLedCount * 24 *3; + //printf(" %d\n", wireBit); +#endif //remove one to undo optimization wireBit --; @@ -344,6 +393,8 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) wireBit += 3; } +// printBinary(PWMWaveform[(int)(oldwireBitValue / 32) -1 ], 32); +// printf(" post\n"); // printBinary(PWMWaveform[(int)(oldwireBitValue / 32)], 32); // printf(" post\n"); diff --git a/libsrc/leddevice/LedDeviceWS2812b.h b/libsrc/leddevice/LedDeviceWS2812b.h index 55ce250a..d95b7912 100644 --- a/libsrc/leddevice/LedDeviceWS2812b.h +++ b/libsrc/leddevice/LedDeviceWS2812b.h @@ -102,6 +102,7 @@ #include #define BENCHMARK +#define WS2812_ASM_OPTI // The page map contains pointers to memory that we will allocate below. It uses two pointers // per address. This is because the software (this program) deals only in virtual addresses, From 0e55169f42c5efc8ef3000bf245d3489c5a99c46 Mon Sep 17 00:00:00 2001 From: David Brodski Date: Fri, 19 Sep 2014 03:16:15 +0200 Subject: [PATCH 8/9] fixed strange bug with last led and pwm needs one more int Former-commit-id: c5e20fed3d84c19032afb64e66a5934fc3db701c --- libsrc/leddevice/LedDeviceWS2812b.cpp | 61 ++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/libsrc/leddevice/LedDeviceWS2812b.cpp b/libsrc/leddevice/LedDeviceWS2812b.cpp index 84b7ac97..8a084b9c 100644 --- a/libsrc/leddevice/LedDeviceWS2812b.cpp +++ b/libsrc/leddevice/LedDeviceWS2812b.cpp @@ -270,6 +270,16 @@ uint32_t arm_ror_imm(uint32_t v, uint32_t sh) { return d; } +// rotate register, used to move the 1 around, add 1 to int counter on carry +static inline __attribute__((always_inline)) +uint32_t arm_ror_imm_add_on_carry(uint32_t v, uint32_t sh, uint32_t inc) { + uint32_t d; + asm ("RORS %[Rd], %[Rm], %[Is]\n\t" + "ADDCS %[Rd1], %[Rd1], #1" + : [Rd] "=r" (d), [Rd1] "+r" (inc): [Rm] "r" (v), [Is] "i" (sh)); + return d; +} + static inline __attribute__((always_inline)) uint32_t arm_ror(uint32_t v, uint32_t sh) { uint32_t d; @@ -312,12 +322,12 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) // 72 bits per pixel / 32 bits per word = 2.25 words per pixel // Add 1 to make sure the PWM FIFO gets the message: "we're sending zeroes" // Times 4 because DMA works in bytes, not words - cbp->length = (mLedCount * 2.25) * 4; - //cbp->length = ((mLedCount * 2.25) + 1) * 4; +// cbp->length = (mLedCount * 2.25) * 4; + cbp->length = ((mLedCount * 2.25) + 1) * 4; if(cbp->length > NUM_DATA_WORDS * 4) { cbp->length = NUM_DATA_WORDS * 4; - mLedCount = NUM_DATA_WORDS / 2.25; - //mLedCount = (NUM_DATA_WORDS - 1) / 2.25; +// mLedCount = NUM_DATA_WORDS / 2.25; + mLedCount = (NUM_DATA_WORDS - 1) / 2.25; } #ifdef WS2812_ASM_OPTI @@ -380,19 +390,39 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) //remove one to undo optimization wireBit --; - // fill up the bytes - int rest = 32 - wireBit % 32; - unsigned int oldwireBitValue = wireBit; - -// printBinary(PWMWaveform[(int)(oldwireBitValue / 32)], 32); +// printBinary(PWMWaveform[(int)(wireBit / 32)], 32); // printf(" pre\n"); +#ifdef WS2812_ASM_OPTI + int rest = 32 - wireBit % 32; // 64: 32 - used Bits + startbitPattern = (1 << (rest-1)); // set new bitpattern to start at the benigining of one bit (3 bit in wave form) + rest += 32; // add one int extra for pwm + +// printBinary(startbitPattern, 32); +// printf(" startbit\n"); + + unsigned int oldwireBitValue = wireBit; + unsigned int oldbitPattern = startbitPattern; + + // zero rest of the 4 bytes / int so that output is 0 (no data is send) + for (int i = 0; i < rest; i += 3){ + unsigned int wordOffset = (int)(wireBit / 32); + wireBit += 3; + PWMWaveform[wordOffset] = arm_Bit_Clear_imm(PWMWaveform[wordOffset], startbitPattern); + startbitPattern = arm_ror_imm(startbitPattern, 3); + } + +#else + // fill up the bytes + int rest = 32 - wireBit % 32 + 32; // 64: 32 - used Bits + 32 (one int extra for pwm) + unsigned int oldwireBitValue = wireBit; + // zero rest of the 4 bytes / int so that output is 0 (no data is send) for (int i = 0; i < rest; i += 3){ setPWMBit(wireBit, 0); wireBit += 3; } - +#endif // printBinary(PWMWaveform[(int)(oldwireBitValue / 32) -1 ], 32); // printf(" post\n"); // printBinary(PWMWaveform[(int)(oldwireBitValue / 32)], 32); @@ -410,10 +440,21 @@ int LedDeviceWS2812b::write(const std::vector &ledValues) // restore bit pattern wireBit = oldwireBitValue; + +#ifdef WS2812_ASM_OPTI + startbitPattern = oldbitPattern; + for (int i = 0; i < rest; i += 3){ + unsigned int wordOffset = (int)(wireBit / 32); + wireBit += 3; + PWMWaveform[wordOffset] |= startbitPattern; + startbitPattern = arm_ror_imm(startbitPattern, 3); + } +#else for (int i = 0; i < rest; i += 3){ setPWMBit(wireBit, 1); wireBit += 3; } +#endif // printBinary(PWMWaveform[(int)(oldwireBitValue / 32)], 32); // printf(" restored\n"); From f41a0fc977ecf3275de48ab62fb0149d03258ea7 Mon Sep 17 00:00:00 2001 From: David Brodski Date: Fri, 19 Sep 2014 03:18:26 +0200 Subject: [PATCH 9/9] removed benchmark Former-commit-id: e716fa337c4995a258c34200e14fd56ee8dae05c --- libsrc/leddevice/LedDeviceWS2812b.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsrc/leddevice/LedDeviceWS2812b.h b/libsrc/leddevice/LedDeviceWS2812b.h index d95b7912..dd92065c 100644 --- a/libsrc/leddevice/LedDeviceWS2812b.h +++ b/libsrc/leddevice/LedDeviceWS2812b.h @@ -101,7 +101,7 @@ // Hyperion includes #include -#define BENCHMARK +//#define BENCHMARK #define WS2812_ASM_OPTI // The page map contains pointers to memory that we will allocate below. It uses two pointers