0

My question is similar to this: convert a pic to bit stream and write it to gpio.
I want to expose printed circuit boards line by line with a laser diode. That means, I have to switch the laser diode 10000 times on and off in 1/10th of a second. The binary pattern I want to pick from an uncompressed bitmap (or an array), which contents the whole print. So, my idea is, that I want to read the bytes that represent one line of my bitmap, push it through some kind of software shift register and output the "zeros" and "ones" to a GPIO pin to drive my laser diode. If this can be done with the help of the build in UART, SPI or I2C, I would be lucky, but I don't want any protocol with start- and stop bits. I want to program in "C", because, if it works, I want to do it in "Bare Metal", to avoid any Multi Task OS.

I would be lucky, if anybody turns me in the right direction and gives me a push.

1
  • 1
    How many bits do you want to send at most in a single atomic stream?
    – joan
    Commented Mar 28, 2016 at 20:13

2 Answers 2

2

You can't do it with a UART because of the start and stop bits.

You can't do it with I2C because of the 9th bit of each byte being an acknowledge bit.

You can't do it with SPI (on the Pi) because the SPI hardware introduces a 1.5 bit time gap between each byte.

You might be able to do it with the Pi PWM or PCM peripheral as they can serialise data. I think they would be the place to start.

See BCM2835 ARM Peripherals

As a matter of interest what is the longest bit stream you want to send?

8
  • The longest bit stream will be about 10000 bits. But that does not mean to toggle the GPIO pin 10000 times on and off. Imagine, I want to exposure a PCB of a width of 100 mm and I want a resolution of 1/100 of a millimeter. I move my laser beam with a polygon mirror from a leaser printer. So one sweep of the laser beam takes maybe 1/10 or 1/100 of a second (I don't know for now). So the laser toggles on and off only in the milliseconds range but I need the precision in microseconds.
    – Wilfried
    Commented Mar 30, 2016 at 15:14
  • The information about the build in serial hardware is helpful. So I guess, I have to learn Bare Metal Assembly programming to get the timing precision, that I need. Right?
    – Wilfried
    Commented Mar 30, 2016 at 15:29
  • Not necessarily. The key bit of information is how many bits you have to transmit before you can pause. For instance if you can pause at the end of each line for a variable number of milliseconds (1-10 say) then there is possibly a solution from userland.
    – joan
    Commented Mar 30, 2016 at 15:45
  • It's only one complete line (say 10000 bits) that I have to write with high precision timing. At the end I can pause as long as I want. But the next written line gets its start pulse by the rotating laserbeam itself (by photosensor) and the bitstream must start at a certain number of microseconds after that pulse. In Assembler without an Operating System I can poll an input pin 5 million times a second to detect the start pulse and then output the bitstream of the next line.
    – Wilfried
    Commented Mar 30, 2016 at 21:22
  • pigpio using waves can generate the pulses down to microsecond precision. What would be difficult would be starting the wave at a set number of microseconds from an external event (unless the number of microseconds was in the region of ten thousand or so).
    – joan
    Commented Mar 30, 2016 at 22:06
0

A lot of online sources suggest that the system sleep commands have overhead in the tens to hundreds of microsecond range, which won't work for your needs (question suggests a need for 10us timing accuracy).

The WiringPi site has a lightweight solution for timing needs at the microsecond level:

Microsecond timing on the pi

Note also Gordon's direction in the comments to use piHiPri() to mitigate issues with process scheduling at the OS level.

Leveraging wiringPi's delayMicroseconds() call (which uses low-level ARM system timer) could allow you to get the timing you're looking for along the lines of the following example (which just flips bits off and on at the specified rate - you would of course replace that logic to modulate the bits per your input data).

(This was adapted from some PWM code that I wrote for motor control, but you could streamline this by axing gpio.h and gpio.c and instead just using wiringPi's digitalWrite(pin, value) calls).

gpio.h:

// File:      gpio.h
// Date:      March 29, 2014
//
// Header file containing definitions and includes needed for GPIO control.
//
#ifndef GPIO_INCLUDE
#define GPIO_INCLUDE

// Need PI2 defined for BCM2709 - won't work with old base address
#define PI2

#define BCM2708_PERI_BASE        0x20000000
#define BCM2709_PERI_BASE        0x3F000000
#ifdef PI2
#define GPIO_BASE                (BCM2709_PERI_BASE + 0x200000)
#else
#define GPIO_BASE                (BCM2708_PERI_BASE + 0x200000)
#endif

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

#define PAGE_SIZE (4*1024)
#define BLOCK_SIZE (4*1024)

extern int  mem_fd;
extern char *gpio_mem, *gpio_map;
extern char *spi0_mem, *spi0_map;

// I/O access
extern volatile unsigned *gpio;

// GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x) or SET_GPIO_ALT(x,y)
#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
#define OUT_GPIO(g) *(gpio+((g)/10)) |=  (1<<(((g)%10)*3))
#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))

#define GPIO_SET *(gpio+7)  // sets   bits which are 1 ignores bits which are 0
#define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0

#define SET_PIN(g) GPIO_SET = 1 << g
#define CLR_PIN(g) GPIO_CLR = 1 << g

#define INIT_OUT(g) INP_GPIO(g); OUT_GPIO(g)

void setup_io();
void init_output(int g);
void init_input(int g);

#endif

gpio.c:

// File:      gpio.c
// Date:      March 29, 2014
//
// Setup for gpio controls
#include "gpio.h"

int  mem_fd;
char *gpio_mem, *gpio_map;
char *spi0_mem, *spi0_map;

// I/O access
volatile unsigned *gpio;
//
// Set up a memory regions to access GPIO
//
void setup_io()
{

   /* open /dev/mem */
   if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
      printf("can't open /dev/mem \n");
      exit (-1);
   }

   /* mmap GPIO */

   // Allocate MAP block
   if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) {
      printf("allocation error \n");
      exit (-1);
   }

   // Make sure pointer is on 4K boundary
   if ((unsigned long)gpio_mem % PAGE_SIZE)
     gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE);

   // Now map it
   gpio_map = (unsigned char *)mmap(
      (caddr_t)gpio_mem,
      BLOCK_SIZE,
      PROT_READ|PROT_WRITE,
      MAP_SHARED|MAP_FIXED,
      mem_fd,
      GPIO_BASE
   );

   if ((long)gpio_map < 0) {
      printf("mmap error %d\n", (int)gpio_map);
      exit (-1);
   }

   // Always use volatile pointer!
   gpio = (volatile unsigned *)gpio_map;

} // setup_io


void init_output(int g)
{
    INP_GPIO(g);
    OUT_GPIO(g);
}

void init_input(int g)
{
    INP_GPIO(g);
}

gpio_pwm.c:

// File:      gpio_pwm.c
// Author:    T3am5hark
// Date:      May 29, 2014
//
// This program pulses a gpio pin with a PWM signal
//
// gpio_pwm [pin] [pulseDuration] [repCount] [dutyCycle]
//
// pulseDuration is full-duty pulse length specified in microseconds (T_cycle)
// repCount is a positive integer (number of pulses to send)
// dutyCycle is an integer percentage from 0 to 100 (on pulse width)
//

#include "gpio.h"
#include "stdio.h"
//  Make use of wiringPi's delayMicroseconds() call
#include "wiringPi.h"

void showHelp();

int main(int argc, char **argv)
{
  int g;
  int repCount = 10;
  int rep = 0;
  double dutyCycle = 0.50;
  unsigned int waitTimeUs = 10; // Default at 10us

  if (argc == 1) showHelp();

  if (argc > 1) g = atoi( argv[1] );
  // Wait time in us
  if (argc > 2) waitTimeUs=atoi( argv[2] );
  // number of reps
  if (argc > 3) repCount=atoi( argv[3] );
  // pulse width duty cycle
  if (argc > 4) dutyCycle=(double)(atoi(argv[4])/100.0);

  if (dutyCycle > 1.0) dutyCycle = 1.0;
  if (dutyCycle < 0.0) dutyCycle = 0.0;

  // Set up gpio pointer for direct register access
  setup_io();

  init_output(g);

  for (rep=0; rep<repCount; rep++)
  {
    SET_PIN( g );
    delayMicroseconds(waitTimeUs);
    CLR_PIN( g );
    delayMicroseconds(waitTimeUs);
  }

  CLR_PIN( g );
  return 0;

} // main

void showHelp()
{
  printf("gpio_pwm utility for soft-PWM generation \n");
  printf("---------------------------------------- \n");
  printf("Usage: \n");
  printf("gpio_pwm pin pw pulses duty \n");
  printf("\n");
  printf("pin    = GPIO pin number \n");
  printf("pw     = pulse width, in usec \n");
  printf("pulses = pulse count\n");
  printf("\n\n");
  printf("Example: \n");
  printf("gpio_pwm 20 10000 25 15\n");
  printf("Create 25 pulses of 15pct duty (1500usec from T=10000usec) on pin 20\n");
  exit(0);
}
6
  • "10000 times on and off in 1/10th of a second" equates to 5 µs toggles.
    – joan
    Commented Mar 29, 2016 at 8:33
  • Or maybe 10us if it's effectively 10k bits per 100ms - the real question is how precise nanosleep timing is going to be at that level. It works great at the 1500us scale needed for motor control. Only way I'd know to test it would be to try it out at 5-10us scale and throw it on an oscilloscope to see what it looks like.
    – T3am5hark
    Commented Mar 29, 2016 at 16:09
  • Pretty bad, I expect. From a timer expiring to a process being awoken is about 50 µs on the Pi, potentially tens of milliseconds on occasion. The best userland solution is likely to be pigpio waves, but that depends on the maximum number of atomic bits.
    – joan
    Commented Mar 29, 2016 at 17:28
  • Actually, Gordon's got a good solution using the hardware timer... I think if I modify answer to use this instead of nano sleep it ought to work for microsecond resolution: projects.drogon.net/accurate-delays-on-the-raspberry-pi
    – T3am5hark
    Commented Mar 29, 2016 at 17:36
  • A busy spin (which is what you are talking about) is fine until the process is rescheduled.
    – joan
    Commented Mar 29, 2016 at 19:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.