jars121
Posts: 123
Joined: Tue Jun 25, 2013 8:35 pm

Increase GPIO toggle frequency

Fri May 18, 2018 11:31 pm

GPIO toggle frequency has covered before, I know, and I've read through most of those topics. Having not delved into lower level programming (e.g. mmap) before, I want to see whether I have any options available to me other than interfacing directly with registers.

A bit of context: I'm writing data to a device register, and am toggling the CLK pin HIGH/LOW after setting each DATA bit, which then sends the DATA bit to the device. This is all working quite nicely, but I'm right on the limit in terms of frequency. I'd like to write to the device register 60+ times per second, which corresponds to a period of 16.67ms. At present, the writing of the register (writeRegister() in the code below) takes approximately 10ms, but can spike up to around 16ms.

I've optimised as much as I can (as far as I can tell), by externalising the open() and close() calls, so the only operation taking place is to calculate the DATA bit, write it to the DATA pin, then write the CLK pin HIGH, then LOW. This is my current code:

Code: Select all


int register::writeRegister(int registerNumber)
{
    //Loop through all 84 bytes
    for (int i(0); i < this->packetBytes; i++) {
    
        //Loop through 8 bits per byte
        for (int j(0); j < 8; j++) {
        
            //Pull the correct bit from the current byte
            int bitData = !!(this->packet[registerNumber][i] & (1 << (7 - j)));
            
            //Write the correct bit to the DATA GPIO pin sysfs file
            write(this->dataDirectory, std::to_string(bitData).c_str(), std::to_string(bitData).length());
            
            //Write the CLK GPIO pin sysfs file HIGH
            write(this->clockDirectory, "1", 1);
            
            //Write the CLK GPIO pin sysfs file LOW
            write(this->clockDirectory, "0", 1);
        }
    }
}

Is there anything obvious I can do reduce the execution time of writeRegister()? If not using sysfs, is there a simple way to incorporate a lower level GPIO toggle? Bear in mind that both the DATA and CLK pins are static, so I'd imagine it should be a relatively straight forward mapping.

Cheers!

EDIT: Ok so it turns out the current frequency level (~10ms) is nowhere near adequate. The writeRegister() function is writing to 3 RGB LED drivers (TLC5971), and at the current ~10-16ms period I'm experiencing horrendous LED flicker. As such, I want to turn the GPIO toggle frequency up as high as possible to remove the LED flicker.

With that in mind, I'd like to explore some potential mmap options. I'd like to be able to use the solution on other embedded Linux platforms, so RPi-specific solutions aren't suitable I'm afraid (e.g. /dev/gpiomem).

jars121
Posts: 123
Joined: Tue Jun 25, 2013 8:35 pm

Re: Increase GPIO toggle frequency

Sat May 19, 2018 4:38 am

Ok this is where I'm up to. I've read through this post, and have tried to implement something similar into my existing application and class structure. My current .h and .cpp files are as follows (trimmed down for relevancy).

register.h:

Code: Select all


class register : public QObject
{
    Q_OBJECT
public:
    explicit register(QObject *parent = nullptr);
    
    int dataPin = 5;
    int clockPin = 6;
    
    int packetBytes = 28;
    int registerCount = 3;
    
private:

    char BCM2708_PERI_BASE = 0x3F000000;
    char GPIO_BASE = (BCM2708_PERI_BASE + 0x3F200000);
    int BLOCK_SIZE = (4 * 1024);
    
    void INP_GPIO(int g);
    void OUT_GPIO(int g);
    void GPIO_SET(int g);
    void GPIO_CLR(int g);
    
    struct bcm2835_peripheral {
        unsigned long addr_p;
        int mem_fd;
        void *map;
        volatile unsigned int *addr;
    };
    
    struct bcm2835_peripheral gpio = { GPIO_BASE };
    
    int map_peripheral(struct bcm2835_peripheral *p);
    
    void unmap_peripheral(struct bcm2835_peripheral *p);
    
    int writeRegister(int registerNumber);
}
register.cpp:

Code: Select all


int register::map_peripheral(struct bcm2835_peripheral *p)
{
    if ((p->mem_fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
        qDebug() << "Error opening /dev/mem.";
        return -1;
    }
    
    p->map = mmap(
                               NULL,
                               BLOCK_SIZE,
                               PROT_READ | PROT_WRITE,
                               MAP_SHARED,
                               p->mem_fd,
                               p->addr_p,
                               );
                               
    if (p->map == MAP_FAILED) {
        qDebug() << "mmap failed.";
        return -1;
    }
    
    p->addr = (volatile unsigned int *)p->map;
    
    return 0;
}

void register::unmap_peripheral(struct bcm2835_peripheral *p)
{
    munmap(p_.map, BLOCK_SIZE);
    close(p->mem_fd);
}

void register::INP_GPIO(int g)
{
    *(gpio.addr + ((g) / 10)) &= ~(7 << (((g) % 10) * 3));
}

void register::OUT_GPIO(int g)
{
    *(gpio.addr + ((g) / 10)) |= (1 << (((g) % 10) * 3));
}

void register::GPIO_SET(int g)
{
    auto gpioSet = *(gpio.addr + 7);
    gpioSet = 1 << g;
}

void register::GPIO_CLR(int g)
{
    auto gpioClear = *(gpio.addr + 10);
    gpioClr = 1 << g;
}

int register::writeRegister(int registerNumber)
{
    if (map_peripheral(&gpio) == -1) {
        qDebug() << "Failed to map the physical GPIO registers into memory.";
        return -1;
    }

    for (int i(0); i < this->packetBytes; i++) {
    
        for (int j(0); j < 8; j++) {
        
            int bitData = !!(this->packet[registerNumber][i] & (1 << (7 - j)));
            
            //DATA pin is BCM GPIO pin 5
            INP_GPIO(5);
            OUT_GPIO(5);
            
            if (bitData > 0) {
                GPIO_SET(5);
            } else {
                GPIO_CLR(5);
            }
            
            //CLK pin is BCM GPIO pin 6
            INP_GPIO(6);
            OUT_GPIO(6);
            
            //Toggle CLK HIGH/LOW as fast as possible
            GPIO_SET(6);
            GPIO_CLR(6);
        }
    }
    return 0;
}

If I run this with normal user permissions, I get all the /dev/mem permission errors you'd expect. However, when I run as root, I don't receive any errors, the application seems to run perfectly (and the measured execution time of the above amended code is down from ~10ms to ~0ms), but the LEDs don't turn on. As such, I think I've got some of the header declarations wrong, and really don't know about my interpretation of the INP_GPIO, OUT_GPIO, GPIO_SET, GPIO_CLR, etc. functions; I wouldn't be surprised if they were entirely wrong.

Does anyone have any ideas?

User avatar
joan
Posts: 13364
Joined: Thu Jul 05, 2012 5:09 pm
Location: UK

Re: Increase GPIO toggle frequency

Sat May 19, 2018 8:06 am

There must be dozens of examples of interfacing with those registers.

Here is a small example of mine.

http://abyz.me.uk/rpi/pigpio/examples.h ... nimal_gpio

jars121
Posts: 123
Joined: Tue Jun 25, 2013 8:35 pm

Re: Increase GPIO toggle frequency

Sun May 20, 2018 12:27 am

joan wrote:
Sat May 19, 2018 8:06 am
There must be dozens of examples of interfacing with those registers.

Here is a small example of mine.

http://abyz.me.uk/rpi/pigpio/examples.h ... nimal_gpio
Thanks joan, much appreciated as always. I had sifted through the Tiny GPIO Access example, but somehow missed this one. I've stripped out the relevant pieces (anything GPIO-related), and incorporated it into my existing register class. Data is definitely being sent to the registers, but something still isn't quite right, as the LEDs are flickering on/off and in odd colours sporadically. I have a feeling my gpioReg isn't setup correctly, as my compiler doesn't like the '= MAP_FAILED' initialisation (currently commented out in the code below).


Code: Select all


#define GPIO_LEN 0xB4
#define GPSET0 7
#define GPSET1 8
#define GPCLR0 10
#define GPCLR1 11

static volatile uint32_t *gpioReg;// = MAP_FAILED;

register::register(QObject *parent) : QObject(parent)
{

}

register::setup()
{
    int fd;
    
    //RPi2 is the model in use
    static volatile uint32_t piPeriphBase = 0x3F000000;
    //I can't see where piBusAddr is actually used?
    //static volatile uint32_t piBusAddr = 0xC0000000;
    
    fd = open("/dev/mem", O_RDWR | O_SYNC);
    
    if (fd < 0)
    {
       fprintf(stderr,
          "This program needs root privileges.  Try using sudo\n");
       return -1;
    }

    gpioReg = (uint32_t *) mmap(0, GPIO_LEN, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_LOCKED, fd, (piPeriphBase + 0x200000));

    close(fd);

    if ((gpioReg == MAP_FAILED)) {
       fprintf(stderr,
          "Bad, mmap failed\n");
       return -1;
    }

    //Set GPIO mode
    int reg, shift, mode;

    //Pin 5 DATA set as output
    reg = 5/10;
    shift = (5%10) * 3;
    mode = 1;

    gpioReg[reg] = (gpioReg[reg] & ~(7 << shift)) | (mode << shift);

    //Pin 6 CLK set as output
    reg = 6/10;
    shift = (6%10) * 3;
    mode = 1;

    gpioReg[reg] = (gpioReg[reg] & ~(7 << shift)) | (mode << shift);

    return 0;

}

int register::writeRegister(int registerNumber)
{
    for (int i(0); i < this->packetBytes; i++) {
    
        for (int j(0); j < 8; j++) {
        
            int bitData = !!(this->packet[registerNumber][i] & (1 << (7 - j)));
            unsigned gpio = 5;
            
            //DATA pin is BCM GPIO pin 5
            if (bitData == 0) {
                *(gpioReg + GPCLR0 + (gpio >> 5)) = (1 << (gpio & 0x1F));
            } else {
                *(gpioReg + GPSET0+ (gpio >> 5)) = (1 << (gpio & 0x1F));
            }
            
            //CLK pin is BCM GPIO pin 6
            gpio = 6;
            
            //Toggle CLK HIGH/LOW as fast as possible
            *(gpioReg + GPSET0 + (gpio >> 5)) = (1 << (gpio & 0x1F));
            *(gpioReg + GPCLR0 + (gpio >> 5)) = (1 << (gpio & 0x1F));
        }
    }
    return 0;
}
As I said above, if I run my application as sudo, I don't receive any errors, but the LEDs flicker sporadically, so there's something not quite right. Any input would be very much appreciated as always!

jars121
Posts: 123
Joined: Tue Jun 25, 2013 8:35 pm

Re: Increase GPIO toggle frequency

Sun May 20, 2018 4:33 am

I've spent most of today trying to get this to work to no avail unfortunately. What I have done however, is use joan's excellent c file as the basis for a bare minimum example, where I simply build each of the 3 LED driver registers, setting each LED to the colour red (RGB(65535, 0, 0)), and then writing out the three registers.

I've added a printf component for each of the 28 bytes of data for each of the 3 registers, so ensure the register data I build is correct. So far I've been through each bit against the TLC5971 LED driver datasheet and it's correct. Bear in mind the exact same register values were working successfully using sysfs yesterday, so I'd have been quite surprised if they weren't correct.

This is the c file I'm currently running on my RPi2:

Code: Select all


/*
   minimal_gpio.c
   2016-04-30
   Public Domain
*/

/*
   gcc -o minimal_gpio minimal_gpio.c
   sudo ./minimal_gpio
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

#define GPIO_BASE  (piPeriphBase + 0x200000)

#define GPIO_LEN  0xB4

#define GPSET0 7
#define GPSET1 8

#define GPCLR0 10
#define GPCLR1 11

#define GPLEV0 13
#define GPLEV1 14

static volatile uint32_t  *gpioReg = MAP_FAILED;
char packets[3][28];

#define PI_BANK (gpio>>5)
#define PI_BIT  (1<<(gpio&0x1F))

/* gpio modes. */

//#define PI_INPUT  0
//#define PI_OUTPUT 1

void gpioSetMode(unsigned gpio, unsigned mode)
{
   int reg, shift;

   reg   =  gpio/10;
   shift = (gpio%10) * 3;

   gpioReg[reg] = (gpioReg[reg] & ~(7<<shift)) | (mode<<shift);
}

void gpioWrite()
{
   //char packets[3][28];
   
   int p;
   
   for (p = 0; p < 3; p = p + 1) {
      long packetHeader = 0b10010111000 << (32-6-5);
   
      unsigned char ledBrightnessWorking = 0xFF;
      ledBrightnessWorking >>= 1;
      packetHeader |= ledBrightnessWorking;
      packetHeader |= (long)ledBrightnessWorking << 7;
      packetHeader |= (long)ledBrightnessWorking << 7*2;
   
      packets[p][0] = packetHeader >> 8*3;
      packets[p][1] = packetHeader >> 8*2;
      packets[p][2] = packetHeader >> 8;
      packets[p][3] = packetHeader;
   }
   
   int m;
   int i;
   int value;
   
   for (m = 0; m < 3; m = m + 1) {
      for (i = 0; i < 12; i = i + 1) {
         if (i == 2 || i == 5 || i == 8 || i == 11) {
	        value = 65535;
         } else {
	        value = 0;
	     }
	  
	     packets[m][i*2 + 4] = value >> 8;
	     packets[m][i*2 + 5] = value;
      }
   
      int j;
      int k;
      int bitData;
      unsigned gpio;
   
      for (j = 0; j < 28; j = j + 1) {
         for (k = 0; k < 8; k = k + 1) {
	        bitData = !!(packets[m][j] & (1 << (7 - k)));
	        if (bitData == 0) {
		       *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT;
	        } else {
		       *(gpioReg + GPSET0 + PI_BANK) = PI_BIT;
	        }
	     
	        gpio = 6;
	        *(gpioReg + GPSET0 + PI_BANK) = PI_BIT;
	        *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT;
	     }
      }
      
      int x;
      for (x = 0; x < 28; x = x + 1) {
          printf("%d: %02X \n", x, packets[m][x]);
      }
   }
}

static uint32_t * initMapMem(int fd, uint32_t addr, uint32_t len)
{
    return (uint32_t *) mmap(0, len,
       PROT_READ|PROT_WRITE|PROT_EXEC,
       MAP_SHARED|MAP_LOCKED,
       fd, addr);
}

int gpioInitialise(void)
{
   int fd;

   static volatile uint32_t piModel = 2;
   int chars = 6;
   static volatile uint32_t piPeriphBase = 0x3F000000;
   static volatile uint32_t piBusAddr = 0xC0000000;

   fd = open("/dev/mem", O_RDWR | O_SYNC) ;

   if (fd < 0)
   {
      fprintf(stderr,
         "This program needs root privileges.  Try using sudo\n");
      return -1;
   }

   gpioReg  = initMapMem(fd, GPIO_BASE,  GPIO_LEN);

   close(fd);

   if ((gpioReg == MAP_FAILED)) {
      fprintf(stderr,
         "Bad, mmap failed\n");
      return -1;
   }
   return 0;
}

main()
{
   int i;

   if (gpioInitialise() < 0) return 1;
   
   gpioSetMode(5, 1);
   gpioSetMode(6, 1);
   
   //while(1) {
   //   gpioWrite();
      //usleep(10000);
   //}
   
   gpioWrite();
}

For context, the packet structure for each of the 3 LED drivers is as follows:

6 bits Write Command (100101) - 5 bits Function Command (11000) - 21 bits Brightness Control (1111111 1111111 1111111) - 192 bits GS Data (B3 (16 bits), G3 (16 bits), R3 (16 bits), ... , B0 (16 bits), G0 (16 bits), R0 (16 bits)).

When running this example, the first 4 LEDs (all on LED driver 1) turn on with the colour Green. It doesn't matter what I set the register values to (colour or brightness), only these 4 LEDs turn on, and they are always green.

The printf statements in the code above align with the datasheet structure exactly, so at this point I can only think that I'm using the mmap process incorrectly.

User avatar
karrika
Posts: 1037
Joined: Mon Oct 19, 2015 6:21 am
Location: Finland

Re: Increase GPIO toggle frequency

Sun May 20, 2018 4:55 am

I am a bit puzzled about why you want to bit-bang the serial data instead of just using MOSI and SCLK from the SPI interface? It does exactly the same thing. Just a bit faster. I could be wrong here. This is the way we control rgb snakes and rgb panels in QLCplus

jars121
Posts: 123
Joined: Tue Jun 25, 2013 8:35 pm

Re: Increase GPIO toggle frequency

Sun May 20, 2018 5:03 am

That's a very fair point. I will have numerous other SPI devices in use in the very near future, and would prefer to keep these drivers separate if at all possible. Another consideration is the fact that I'll be moving to a non-RPi Linux device in the near future as well, the SPI details of which I haven't yet finalised, so despite bit-banging being a little more tedious to establish, it should provide reasonable portability in the future.

PiGraham
Posts: 3217
Joined: Fri Jun 07, 2013 12:37 pm
Location: Waterlooville

Re: Increase GPIO toggle frequency

Sun May 20, 2018 6:31 am

jars121 wrote:
Sun May 20, 2018 5:03 am
That's a very fair point. I will have numerous other SPI devices in use in the very near future, and would prefer to keep these drivers separate if at all possible. Another consideration is the fact that I'll be moving to a non-RPi Linux device in the near future as well, the SPI details of which I haven't yet finalised, so despite bit-banging being a little more tedious to establish, it should provide reasonable portability in the future.
The question to ponder is do those other uses require timing as fast and critical as the RGB LEDs? Many SPI peripherals are not critical on timing and it might be easier to bit-bang those and use the hardware SPI to control the LEDs.

It seems to me that you are facing the same problems that others have met with WS2812 type LEDs and the solutions I know of have all used hardware to produce the necessary reliable timing

jars121
Posts: 123
Joined: Tue Jun 25, 2013 8:35 pm

Re: Increase GPIO toggle frequency

Sun May 20, 2018 6:57 am

If I were to look at using hardware SPI instead, how would that work given that these devices don't have a CS pin? Would I manually pull down one of the RPi CS pins, write the data (using the existing MOSI and CLK channels), and then pull the 'fake' CS pin HIGH? I'd really like to avoid hardware SPI if at all possible, as the other SPI devices are somewhat timing-critical as well.

PiGraham
Posts: 3217
Joined: Fri Jun 07, 2013 12:37 pm
Location: Waterlooville

Re: Increase GPIO toggle frequency

Sun May 20, 2018 7:18 am

jars121 wrote:
Sun May 20, 2018 6:57 am
If I were to look at using hardware SPI instead, how would that work given that these devices don't have a CS pin? Would I manually pull down one of the RPi CS pins, write the data (using the existing MOSI and CLK channels), and then pull the 'fake' CS pin HIGH? I'd really like to avoid hardware SPI if at all possible, as the other SPI devices are somewhat timing-critical as well.
If everything has critical timing you have problems. The solution to those problems is probably not to do it with bit-banging in Linux userspace.

jars121
Posts: 123
Joined: Tue Jun 25, 2013 8:35 pm

Re: Increase GPIO toggle frequency

Sun May 20, 2018 7:35 am

I made it sound more dire than it is to be honest. I could use SPI for these LEDs, but I'd really rather not. I only have 12 of them, so really find it hard to believe that I can't reliably bit bang 84 bytes of data at sufficient frequency to drive them.

I've gone back to the sysfs method just to confirm that the register values and circuit are indeed correct, and they definitely are. The colours are correct, and they're turning on in sequence as desired, albeit at far too low frequency resulting in considerable flickering. Reading through my modified version of joan's c file, I really can't see why the output isn't as expected.

StuartF
Posts: 27
Joined: Sun Feb 02, 2014 5:41 pm

Re: Increase GPIO toggle frequency

Sun May 20, 2018 3:13 pm

There is a problem in your initMapMem function.
The second mmap() parameter is the page size for the map.
You are passing 0x84 (132 ) as the value. This should be 4096.

This is a fixed value, so you could use a constant, but you don't need to pass it.
e.g.

Code: Select all

static uint32_t * initMapMem(int fd, uint32_t addr )
{
    return (uint32_t *) mmap(0, 4096,
       PROT_READ|PROT_WRITE,
       MAP_SHARED,
       fd, addr);
}

jars121
Posts: 123
Joined: Tue Jun 25, 2013 8:35 pm

Re: Increase GPIO toggle frequency

Mon May 21, 2018 7:08 am

StuartF wrote:
Sun May 20, 2018 3:13 pm
There is a problem in your initMapMem function.
The second mmap() parameter is the page size for the map.
You are passing 0x84 (132 ) as the value. This should be 4096.

This is a fixed value, so you could use a constant, but you don't need to pass it.
e.g.

Code: Select all

static uint32_t * initMapMem(int fd, uint32_t addr )
{
    return (uint32_t *) mmap(0, 4096,
       PROT_READ|PROT_WRITE,
       MAP_SHARED,
       fd, addr);
}
Stuart, you are an absolute life saver! Thank you so much for picking that up for me! I've changed the initMapMem to include a constant page size (4096) as suggested, and the previously post c code now works perfectly. For future reference, how did you know that 4096 is the correct value? Is that something I'd find deep inside the Broadcom datasheet?

Thanks again!

jahboater
Posts: 2635
Joined: Wed Feb 04, 2015 6:38 pm

Re: Increase GPIO toggle frequency

Mon May 21, 2018 8:35 am

<speculation>
It may be that 0x84 is wrong (too small)?
The second argument is the length of the mapping. When mapping a file for example, this argument is usually passed the result of lseek() or stat.st_size - the exact file size.
4096 is the page size, and mmap() maps in multiples of the page size.
reads from the remaining memory in the last page are zeroed and writes have no effect.
So any writes your program is doing beyond 0x84 will be ignored I think.
Increasing the length to 4096 allows it to work by chance, but if it were my code I would get the length right.
</speculation>
For future reference, how did you know that 4096 is the correct value? Is that something I'd find deep inside the Broadcom datasheet?
you normally get the page size with sysconf(_SC_PAGESIZE)

jars121
Posts: 123
Joined: Tue Jun 25, 2013 8:35 pm

Re: Increase GPIO toggle frequency

Mon May 21, 2018 12:39 pm

Thanks jahboater, I've done some reading now on sysconf, and it does provide 4096 as the page size :)

Despite resolving the issues I was having with bit banging, I'm afraid my problem isn't yet solved. I've measured the frequency of execution of my c code and it's running at around 12.5k executions/second, which is still too low for running the LEDs (I'd expect flicker-free operation from around ~20kHz).

Unless I'm missing something (given the comments above I imagine this particular application of bit banging isn't ideally suited), it looks like a hardware SPI solution may be the only way.

If that's the case, and given I'm currently using spidev for an SPI ADC, how do I go about writing with MOSI and using the SPI CLK without a dedicated SPI CS pin? Do I use the same SPI channel as the ADC (/dev/spidev0.0) or the second channel (/dev/spidev0.1)? I imagine I'll need to ensure that synchronisation is managed between the two SPI devices.

Given that these registers don't have an SPI CS pin, do I need to toggle the ADC SPI CS HIGH and a second CS pin LOW to simulate a CS pin for spidev?

Heater
Posts: 9245
Joined: Tue Jul 17, 2012 3:02 pm

Re: Increase GPIO toggle frequency

Mon May 21, 2018 2:17 pm

I have not looked at the code you have presented but if it is running so slowly something is up.

In my experiments I could toggle a GPIO pin from C code at 50 or 60MHz ! There is example code and a scope trace of it toggling at 1MHz here:
viewtopic.php?f=63&t=200793&start=25#p1252388

This does require a few things though:

1) Write to the GPIO registers directly

2) Which requires they be mapped into user memory space.

3) Linux is terrible for fast real-time code, you process can be descheduled and not running for milliseconds at a time, or more. For best timing determinism instruct the Linux kernel to not schedule tasks on a particular core, then run your program on that free core.

For 1) and 2) see the example code I linked to.

For 3) you will need to add an "isolcpu" parameter to the kernel command line in -boot. Say "isolcpus=3". Then you will need to run your program on core 3 which can be done with the "taskset" command. Something like:
$ taskset -c 3 nice -n -20 ./yourProgram
With that in place your program has a whole core to itself and can scream along. Do not make any long winded calls into the kernel whilst doing all this.

Do read the thread I linked to. It's only two pages. User doublehp had great success running his ws2812 LED strip, eliminating all strange flashing and such using isolcpus and taskset. Even if his code was in the slowest of all languages, Python. He was using pigpio though to push bits out through SPI I believe.

Contrary to statements above it is perfectly possible to run ws2812 LED strips from Linux without hardware support. As long as you have a multicore processor. As that thread shows.
Note: There is a difficulty driving ws2812 LEDS from the 3.3v GPIO pins but that is another story.

Heater
Posts: 9245
Joined: Tue Jul 17, 2012 3:02 pm

Re: Increase GPIO toggle frequency

Mon May 21, 2018 2:22 pm

Do see the super simple to use GPIO bit banging code example by Gert van Loo & Dom here:

https://elinux.org/RPi_GPIO_Code_Sample ... ter_access

PiGraham
Posts: 3217
Joined: Fri Jun 07, 2013 12:37 pm
Location: Waterlooville

Re: Increase GPIO toggle frequency

Tue May 22, 2018 8:51 am

Hrater,
Thanks for those posts. They are very useful.

jars121
Posts: 123
Joined: Tue Jun 25, 2013 8:35 pm

Re: Increase GPIO toggle frequency

Tue May 22, 2018 9:24 pm

Yes thank you Heater, I read through your links thoroughly yesterday and had a bit more of a play around.

I've decided to go with hardware SPI in the end, as I should have enough dedicated hardware SPI channels to accommodate. I've currently got the chain of TLC5971 drivers connected to spidev0.1 alongside an ADC on spidev0.0. This works if I send a single colour and brightness command for all 12 LEDs, but isn't working if I try and send multiple colours, and update the colours over time (e.g. rainbow effect).

The reason for this (I believe) is outlined in the datasheet:

Image

The datasheet specifies that the SCKI must be tied HIGH or LOW for 8x the period between the last and second last rising edges before sending the next N x 224 bits of data (N in my case is 3). At the moment my code runs continuously, so doesn't allow for this 8 x period SCKI pause.

My questions:
  • How can I 'pause' (either setting HIGH or LOW) the SPI CLK line using spidev (linux/spi/spidev.h)?
  • Is my understanding correct that I would need to be on spidev1.x to alter the CLK line and not impact my existing ADC on spidev0.0?

jars121
Posts: 123
Joined: Tue Jun 25, 2013 8:35 pm

Re: Increase GPIO toggle frequency

Tue May 22, 2018 11:13 pm

I've now got the rainbow effect working on spidev0.1, without addressing the 8 * period SCKI pause as specified in the datasheet. How is that? The LEDs still have an occasional, very slight flicker, but at this point I'm inclined to believe it's due to the wiring itself. My DATA and CLK lines for the LED driver breadboard are around ~50cm long, and running over/around all sorts of power wires etc. I'm hopeful that when I clean the wiring up the occasional flickering will disappear. If not, I might need to investigate the SPI speed and execution speed further.

Heater
Posts: 9245
Joined: Tue Jul 17, 2012 3:02 pm

Re: Increase GPIO toggle frequency

Wed May 23, 2018 5:29 am

You can always make a pause the old fashioned way. Just loop doing nothing but counting loops:

Code: Select all

volatile int pauseCounter;
int pauseTime = 234432;         // Whatever value is required for the pause length you need
...
...
// Pause
for (pauseCounter = 0; pauseCouner < pauseTime; pauseCounter++)
{
    // Do nothing.
}
...
...

jars121
Posts: 123
Joined: Tue Jun 25, 2013 8:35 pm

Re: Increase GPIO toggle frequency

Wed May 23, 2018 6:31 am

Thanks Heater.

That approach aligns with my initial thinking. With that said, isn't the SPI CLK line still rising and falling, independent of what my code is doing? I.e. my code might be 'paused', and not producing any output to the device, but the attached CLK line is still toggling between LOW and HIGH. Particularly in this case as my CLK line is shared with the ADC, but even if I were to migrated to the other, unused SPI bus, I assume the SPI CLK is still running?

juj
Posts: 21
Joined: Sat Nov 18, 2017 10:51 pm

Re: Increase GPIO toggle frequency

Wed May 23, 2018 10:23 am

To sleep for a specific period, don't do a volatile variable hammering

Code: Select all

for(int i = 0; i < pauseTime; ++i)
   /*nop*/;
approach. That will not tell you how long you slept, and it will be dependent on your CPU clock frequency and turbo state, and possibly even contention on the memory bus, so it is very brittle and nonportable. Instead, just use usleep: http://man7.org/linux/man-pages/man3/usleep.3.html

Code: Select all


#include <unistd.h>

void foo()
{
   ...
   usleep(100); // sleeps for at least 100 microseconds
   ...
}
[/core]

that has the bonus of idling the CPU for other tasks, reducing power consumption. Generally if you want to sleep for less than < 70 usecs on the Pi 3B/Pi Zero, and don't want to overshoot too much, you can instead use the BCM2835 timer register directly. See https://github.com/juj/fbcp-ili9341/commit/d69c1a3305b473c4e247794c91b13c7c51d303c9 for example.

juj
Posts: 21
Joined: Sat Nov 18, 2017 10:51 pm

Re: Increase GPIO toggle frequency

Wed May 23, 2018 10:32 am

jars121 wrote:
Tue May 22, 2018 9:24 pm
  • How can I 'pause' (either setting HIGH or LOW) the SPI CLK line using spidev (linux/spi/spidev.h)?
The hardware BC2835 SPI peripheral automatically stops driving the CLK signal immediately when there are no bytes left in the FIFO (SPI send queue) to send. That is, when you don't transmit, the CLK signal doesn't pump. If you are seeing it does live while you are not transmitting, then there is some other device driver transmitting bytes on the bus.

Whether CLK is idle HIGH or LOW is controlled by the SPI peripheral CPOL (clock polarity) setting. See https://www.raspberrypi.org/app/uploads ... herals.pdf page 155 CPOL register. Though how this is defined when using spidev is a good question, I'm not that familiar with spidev.
jars121 wrote:
Tue May 22, 2018 9:24 pm
[*]Is my understanding correct that I would need to be on spidev1.x to alter the CLK line and not impact my existing ADC on spidev0.0?[/list]
Your devices need to be on separate SPI lines only if one or more of them don't respect chip select lines to control their active enabled/disabled state. If your devices all honor CS lines properly, then you can use those to specify which device to talk to. If on the other hand one or more of the devices don't have a proper CS line that you could use to make the device quiet, then it has to live on its own SPI line.

Again, not sure how the CS lines are controlled exactly by the spidev device files, but hope this was of some help.

jars121
Posts: 123
Joined: Tue Jun 25, 2013 8:35 pm

Re: Increase GPIO toggle frequency

Wed May 23, 2018 8:57 pm

It was most certainly of help, thank you for your thorough explanation.

My understanding is as follows:
  • SPI CLK will settle either HIGH or LOW if SPI data transmission pauses.
  • I'm going to need to put these registers on the second SPI bus as they don't have a CS function.

Return to “C/C++”

Who is online

Users browsing this forum: No registered users and 8 guests