// http://lcamtuf.coredump.cx/soft/trash/lame.gif
//
// What is going on here?
//
// The vision module consists of six (duh! I'm a cheap bastard!) 
// phototransistors driven by a low signal from a 1-of-16
// line decoder, 74LS154. This chip is wired to a control bus
// I discussed earlier, to four least significant bits of the second
// memory register. The idea here is to write a number of the sensor
// to be read in the next cycle to this register, 74LS154 triggers
// a current flow to the specific sensor... all sensors are
// wired back to a 8-bit ADC (TLV571). This is much cheaper than getting
// a multi-channel ADC and works surprisingly well.
//
// DAC requires two signals. First line, -CSTART, is high by default.
// When it goes down, the chip starts sampling using its internal
// clock. When the line goes back high, conversion begins. Few clock
// cycles later, the data is ready and EOC (end of conversion) goes up.
// At this point, another signal, -RD, should go down, so the output data
// becomes available on a 8-bit parallel bus. Fortunately, since we're
// sampling at low speeds (compared to the internal 3 MHz clock or such),
// we can simply ignore EOC. To make certain tasks simplier and to save
// few cycles, I wired -RD and -CSTART to LPT control lines, -Strobe and
// -Ack. As you probably remember, I already use them to select the
// register, but this does not matter. You just have to make sure that
// the right value is on the LPT data output when you change those lines,
// and this can be automated.
//
// -Strobe has also a third use. Since we can read just 4 bits at once
// over LPT (there are only four status lines), I use 4-of-8 selector,
// 74LS157, that is driven by -Strobe. When -Strobe is down (on the 
// software side), upper half of the data from DAC can be read. When it is
// up, lower half is available.
// 
// Ok, soo... to read a sensor, you have to:
//
//   1) set control lines to select the second memory register
//   2) write the number of a sensor you want to read
//   3) set control lines to some other register to save your selection
//   4) put -Strobe high (software) to start sampling
//   5) put -Strobe low to stop sampling and start conversion
//   6) put -Ack high (software) to get upper half of the result
//   7) put -Strobe high (software) to get lower half of the result
//      (and start sampling again)
//   8) ...go to 5
//
// As you can see, for constant sampling from a single source (such as
// a microphone), this solution is very fast, it takes three port clock
// cycles to get a sample. But this is not particularly important right now.
//
// This program implements a robo-vision. Six phototransistors in opaque
// tubes are read (this gives a pathetic vertical resolution, but I ordered
// only six sensors without thinking; it would be more reasonable to get 16).
// The main structure where the sensors are installed is rotating at a
// constant speed, so that subsequent reads make columns of the composite
// image. This gives a very good horizontal resolution. The code for starting
// and stopping motors is not included here (it is just extra two lines of
// code, so...). Note that this solution yields a very good tonal range
// - 256 shades of gray, better than a typical computer display that can
// do 64.
//
// This is a very early solution. The final code will do a four-pass scan
// (both directions, step forward, both directions) to get a better
// resolution. With 16 sensors, I guess it is possible to get around 40-50
// pixels vertical, add some software interpolation and you get really
// good results. In my lame setup, I probably won't get more than around 16
// pixels, but... heh ;-) I'll fix it as soon as I get some money.
//
// Currently, the code simply scans the scene and does some pathetic
// software interpolation (averaging) in one axis, so the resolution is
// not impressive. Step forward interpolation is not supported. The robot 
// sees things that have horizontal details much better than ones with
// vertical details (which is perfect for a robot), so if you put a fork
// pointing downwards in the front of a robot, it should be able to see
// all details, but if you put it vertically, it would look no different
// from a spoon...

// So, here's the code.


#include <sys/io.h>
#include <stdio.h>
#include <assert.h>
#include <vga.h>

int _i,_k;


// This constitutes a (discrete) port clock. As a matter of fact,
// this can be much much faster, but this speed is just fine for
// demonstration purposes.

#define WAIT for (_i=0;_i<100000;_i++) _k+=2


// Now, some port management, so we always put the right value when
// tweaking control lines.


unsigned char bkdata[4];	// Saved memory registers
int curblock;			// Current reg. (current control lines state)


// Change the register / control lines:
void setblock(int i) {
  // There are only four registers // possible control line states:
  assert(i>=0);
  assert(i<4);
  // ...avoid nasty bugs...
  assert(curblock!=i);
  // Put out whatever is supposed to be in the current register
  outb(bkdata[curblock],0x378);
  WAIT;
  // Switch to another register (old register will be updated at this point).
  // It is updated with the value that is supposed to be there, so we simply
  // preserved it current state.
  outb(i,0x37a);
  WAIT;
  curblock=i;
}


// Store data for the register. This data will be sent to the robot
// as soon as possible via setblock().

void senddata(int block,unsigned char data) {
  assert(block>=0);
  assert(block<4);
  bkdata[block]=data;
}


// Do byte swapping. Both data read from DAC and going to ADC has
// a reversed bit order because I screwed up ;-)

unsigned char bswapped (unsigned char in) {
  int i;
  int r=0;
  for (i=0;i<8;i++) {
    if (in & (1<<i)) r|=1<<(7-i);
  }
  return r;
}


int i,j,a,b,q,y,m,n,bx;
int prevcol;

main() {

  vga_setmode(G320x200x256);

  iopl(3);

  // Just for sanity.
  outb(0,0x378); WAIT;
  outb(0,0x37a); WAIT;

  // Set b/w palette
  for (bx=1;bx<255;bx++) vga_setpalette(bx,bx/4,bx/4,bx/4);

foo:

//    vga_clear();

  for (y=0;y<40;y++) {

    prevcol=-1;

  for (q=0;q<6;q++) {

    senddata(2,q);	// Select the sensor
    setblock(1);        // Start sampling
    setblock(0);        // Stop sampling, start conversion
    setblock(2);        // Read first block of data
    b=inb(0x379);
    setblock(2|1);      // Read second block of data
    a=inb(0x379);

    a ^= 128;		// Combine the data in a single bit as needed.
    a &= 0xf0;		// Note that one of status lines is inverted,
    b ^= 128;		// so I have to xor both values with 128.
    b >>= 4;

    if (q==0) q=6;	// This is a lame trick because of a sensor
			// ordering glitch, last sensor is actually
			// the first.

    // First, draw a 6x10 box in the color read...

    vga_setcolor(bswapped(a|b));

    for (n=0;n<6;n++) {
      for (m=0;m<10;m++) {
        vga_drawpixel(y*6+n,q*20+10+m);
      }
    }

    // Then, it may be a good idea to add another block in between
    // to make the picture a bit smoother...

    if (prevcol>=0) vga_setcolor((bswapped(a|b)+prevcol)/2);

    for (n=0;n<6;n++) {
      for (m=0;m<10;m++) {
        vga_drawpixel(y*6+n,q*20+m);
      }
    }

    // Go back to the original q...

    if (q==6) q=0;

    prevcol=bswapped(a|b);

  }

  }

  // Picture generation done... wait and try again.

  usleep(100000);

  goto foo;

}
