Conveyor Belt in Cellular Automata

c

var canvas = document.getElementById("canvas");
var canvas = document.getElementById("canvas"); 
var ctx = canvas.getContext("2d"); 
var img; var current, prev, imageData;

function RunOnce()  
{ 
   for (var y = 1; y < img.height - 1; ++y) 
   { 
      for (var x = 1; x < img.width - 1; ++x)
      { 
          var n = (y * img.width + x) * 4; // pixel address 
          var c = prev[n]; // color var neighbours = 0; 
          for (var a = -1; a < 2; ++a) 
              for (var b = -1; b < 2; ++b) 
                 if (!(a == 0 && b == 0) && prev[((y + b) * img.width + (x + a)) * 4] == 240) 
                     neighbours++; 

          if (c == 240 && neighbours >= 3 && neighbours <= 5) 
              continue; 

          if (c >= 80) 
              current[n] = c - 80; 
          else 
              current[n] = neighbours == 2 ? 240 : 0; 
       } 
    } 

    ctx.putImageData(imageData, 0, 0); 
    prev.set(current);  
    setTimeout(function() { RunOnce(); }, 10); 
}

function loadImageFromFile(event)
{
    document.getElementById("label").style.visibility = "hidden";
    var reader = new FileReader();
    img = document.getElementById("world");
    reader.onload = function(event) 
    {
       img.onload = function() 
       { 
           ctx.drawImage(img, 0, 0); 
           imageData = ctx.getImageData(0, 0, img.width, img.height);
           current = imageData.data;
           prev = new Uint8ClampedArray(imageData.data);
           RunOnce();
       };
       img.src = event.target.result;
    } 
    reader.readAsDataURL(event.target.files[0]);
}
conveyor

Source image for conveyor belt example. Should be in 24bit bmp format

glider_gun

Source image for glider gun

burning_arrow

Source image for burning arow

 

PIC 12F675 based Solar Battery Charger

This circuit was build to control charging/discharging of a solar powered battery. A 12F675 monitors the battery voltage through an ADC channel and connects/disconnects solar panel and the load to avoid over charging and over discharging the battery.

An LED connected to the micro controller indicates the state of the battery. When the battery is fully charged (>13v) it blinks in long pulses (2s) and the solar panel is disconnected. When the battery is charging i.e. 12.3v to 13v short pulses about 200ms in each 2s. When the voltage drops below 12.3v the load is disconnected from the battery and LED is blinked  two 25ms pulses in each 2s interval.

But there is an issue with this circuit. When the load is connected while the battery is charged to say 12.5v, battery voltage suddenly drops below 12.3v due to current demand from the load and the internal resistance. Open circuit voltage of a battery is not what you get while running. When the load is disconnected, voltage goes back to the original value. This causes the circuit to take false decision and disconnect the load, then voltage restores and the load is connected back… endless loop. This can be avoided if we can detect when the load (say home lighting system) is taking current. A current sense resistor can be used for this. At that time program should use somewhat lower voltage to determine the ‘low battery’ state.

Image

 

1.1

Image

Note that the solar panel is connected through a  normally closed relay pins and the load connected through normally open pins.

 

Program (HiTec-C)

#include <htc.h>
#include <pic.h>

__CONFIG(UNPROTECT & WDTDIS & BORDIS & MCLRDIS & PWRTDIS & WDTDIS & INTIO); 

#define _XTAL_FREQ 4000000

void init()
{
    TRISIO = 0b00000001; 
    CMCON = 0b00000111; // Comparator Off
    ADCON0 = 0b10000001; // Ref = Vdd, Chan = 0, AD = On
    ANSEL = 0b01010001; // FOSC/16
    __delay_us(30); // Acquisition Delay Min 20 uSec
}

int read_adc()
{
    unsigned int n;
    GODONE = 1; // Start conversion
    while(GODONE); // wait for done
    n = (unsigned int)ADRESH << 8 | ADRESL;
    return n;
}

void delay_ms(int n)
{
    while (n-- > 0)
        __delay_ms(1);
}

void FlashLED(int delay, int count)
{
    while (count-- > 0)
    {
        GPIO2 = 1;
        delay_ms(delay);
        GPIO2 = 0;
        delay_ms(delay);
    }
}

void main(void)
{
    init();

    GPIO1 = 0;
    GPIO5 = 0;

    FlashLED(1000, 1);

    while (1)
    {
        unsigned int t;
        int bCharging;
        int bReady;

        t = read_adc();
 
        // Note: adjust following magic numbers according to your setup.
 
        // Battery Level High
        if (t > 900)
        {
            GPIO5 = 1; // stop charging
            bCharging = 0;
        }
        else if (t < 860)
        {
            GPIO5 = 0; // charging
            bCharging = 1;
        }
 
        // Battery Level Low
        if (t < 750)
        {
            bReady = 0; // battery is discharged a lot
            GPIO1 = 0; // disconnect the load
            delay_ms(30000);
        }
        else if (t > 770)
        {
            GPIO1 = 1; // battery ready for working
            bReady = 1;
        }

        if (bCharging == 0) // > 13v
            FlashLED(2000, 1);
        else if (bReady == 1) // > 12.3v 
            FlashLED(200, 1);
        else if (bReady == 0) // < 12.3v
            FlashLED(25, 2);

        delay_ms(2000);
    }
}

My Homemade CNC version 2 with USB Driven PIC16F877A Driver

This is the second version of my CNC machine that is capable of cutting plywood. (see version 1)

DSCF1099

DSCF1590

Skate bearings on L-Iron bars

333

Home made mechanical encoders. I later removed these and made the system open loop.

555

The controller software.

DSCF1589

PSU, Stepper Drivers and PIC

DSCF1588 DSCF1587 DSCF1586

Sketchup Models

1

X-Axis (bed)

2 3

Y-axis with Z-axis motor attached

4

Z-axis and router

Components

* 3A Stepper motors (280 oz-in I guess)
* Cheap TB6560 stepper drivers 24V-3A
* Thread bars and double nuts
* Skate bearings on rails
* 24V 10A SMPS
* CP210x USB-TTL module.
* Homemade Firmware (CCS-C) and driver program (VC++)
* PIC16F877A (more than enough)
* 1/4 inch Trim router + 2mm HSS end mill + V-bit

PIC Program (CCS-C)

core of the code

void main()
{
    int8 c;

    Setup();

    output_high(RED_LED);

    output_low(PIN_X_EN); // disable
    output_high(PIN_Y_EN);
    output_high(PIN_Z_EN);

    delay_ms(1000);
    output_low(RED_LED);
    output_low(GREEN_LED);

    while (TRUE)
    {
       output_high(LED_READY); // RX indicator
       c = my_getc();
       output_low(LED_READY); // RX indicator
       if (c == 0xff) // reset comm
       {
          gi_CmdCount = 0;
          gi_NextCmd = 0;
          continue;
       }
       else if (c == 0xfe) // end of transmission 
       {
          while (gi_CmdCount > 0)
          {
              output_high(LED_WORKING);
              DoNext();
              output_low(LED_WORKING);
          } 
      }

      if (gi_CmdCount >= MAX_CMDS)
      {
          SendNack(ERR_MSG_LIMIT);
          gi_CmdCount = 0;
          gi_NextCmd = 0;
          continue;
      }
      set_cmd(gi_CmdCount, c);
      gi_CmdCount++;
   }
}
//------------------------------------------------------------------------------
void ConfigureDevice(int8 iScale, int8 iActive, int8 iMaxActive)
{
   gi_Active = iActive;
   gi_Active *= 100;
   gi_MaxActive = iMaxActive;
   gi_MaxActive *= 100;
   gi_Scale = iScale;

   output_low(PIN_X_EN); // disable
   output_high(PIN_Y_EN);
   output_high(PIN_Z_EN);
}
//------------------------------------------------------------------------------
void UpdateDelay()
{
   if (gi_CurActive > gi_Active)
   {
      if (gi_CurActive > 3000) 
         gi_CurActive -= 1000;
      else if (gi_CurActive > 1000) 
        gi_CurActive -= 200;
      else
        gi_CurActive -= 50;
   }
}
//------------------------------------------------------------------------------
void Move(signed int iDir, int iAxis)
{
   // Y dir: Reversed to match hardware

   switch (iAxis)
   {
   case AXIS_X:
   {
     output_bit(PIN_X_DIR, iDir == FWD ? 0 : 1);
     output_high(PIN_X_CLK);
     output_high(LED_STEPPING);
     UpdateDelay();
     delay_us(gi_CurActive);
     output_low(PIN_X_CLK);
     output_low(LED_STEPPING);
     break;
   }
   case AXIS_Y:
   {
     output_bit(PIN_Y_DIR, iDir == FWD ? 0 : 1);
     output_high(PIN_Y_CLK);
     output_high(LED_STEPPING);
     UpdateDelay();
     delay_us(gi_CurActive);
     output_low(PIN_Y_CLK);
     output_low(LED_STEPPING);

     break;
  }
  case AXIS_Z:
  {
     output_bit(PIN_Z_DIR, iDir == FWD ? 1 : 0);
     output_high(PIN_Z_CLK);
     output_high(LED_STEPPING);
     UpdateDelay();
     delay_us(gi_CurActive);
     output_low(PIN_Z_CLK);
     output_low(LED_STEPPING);

     break;
  }
 }
}
//------------------------------------------------------------------------------
void RunManual()
{
 int16 i;

 output_low(LED_READY); 

 while (input_state(PIN_MANUAL) == 1)
 {
   if (input_state(PIN_Z_UP) == 1)
   {
      delay_ms(1000);
      gi_CurActive = gi_MaxActive;

      for (i = 0; i < 32000; i++)
         Move(FWD, AXIS_Z);
   }
   else if (input_state(PIN_Z_DOWN) == 1)
   {
       delay_ms(1000);
       gi_CurActive = gi_MaxActive;

       for (i = 0; i < 32000; i++)
           Move(BWD, AXIS_Z);
   }

   output_high(LED_BUSY);
   delay_ms(50);
   output_low(LED_BUSY);
   delay_ms(50);
 }

 delay_ms(1000);
}
//------------------------------------------------------------------------------
void DoNext()
{
   int8 cmd;
   sint16 a;
   int8 i;
   signed int m;
   signed int n;
   signed int k;

   cmd = get_cmd(gi_NextCmd);
   gi_NextCmd++;
   gi_CmdCount--;

   if (cmd == MOVE_XYZ)
   {
      if (gi_CmdCount < 2)
      {
         SendNack(ERR_ARGC);
         gi_CmdCount = 0;
         gi_NextCmd = 0;
         return;
      }
      m = get_cmd(gi_NextCmd) - 125; // distance
      gi_NextCmd++;
      gi_CmdCount--;
      n = get_cmd(gi_NextCmd) - 125; // axis
      gi_NextCmd++;
      gi_CmdCount--;
      gi_CurActive = gi_MaxActive;
      k = (m < 0 ? BWD : FWD);
      a = gi_Scale * abs(m);
     output_high(PIN_X_EN); // enable
     output_low(PIN_Y_EN);
     output_low(PIN_Z_EN);

     while (a > 0)
     {
        for (i = 0; i < 16; i++)
           Move(k, n);
        a--;

        /*if (input_state(PIN_MANUAL) == 1)
          {
              delay_ms(1000);
              RunManual();
              gi_CurActive = gi_MaxActive;
           }*/
      }

       output_low(PIN_X_EN); // disable
       output_high(PIN_Y_EN);
       output_high(PIN_Z_EN);

        gi_CurActive = gi_MaxActive;
    }
    else if (cmd == CONFIGURE_DEVICE)
    {
        if (gi_CmdCount < 3)
        {
             SendNack(ERR_ARGC);
             return;
        }
        m = get_cmd(gi_NextCmd); // scale
        gi_NextCmd++;
        gi_CmdCount--;
        k = get_cmd(gi_NextCmd); // active 
        gi_NextCmd++;
        gi_CmdCount--;

        i = get_cmd(gi_NextCmd); // max active 
        gi_NextCmd++;
        gi_CmdCount--;

        ConfigureDevice(m, k, i);
   }
   else
   {
      SendNack(ERR_NO_SUCH_CMD);
      gi_CmdCount = 0;
      gi_NextCmd = 0;
      return;
   }
   if (gi_CmdCount == 0) // All cmds executed 
   {
       gi_NextCmd = 0;
       SendAck();
   }
}

Tracing Voronoi Diagrams – The Greedy Approach

Voronoi Diagrams are pretty and useful. I use them for PCB isolation milling. This piece of code (C++/MFC) traces the voronoi diagrom of some random points. The algorithm is the most simple, lowest performing greedy method.

void TraceVoronoi(CDC* pDC, std::vector<CPoint>& vPoints, std::vector<COLORREF>& vColors)
{
    for (int x = 0; x < 600; ++x)
    {
        for (int y = 0; y < 400; ++y)
        {
            double dMinDist = 100000000;
            CPoint pt;
            int n = 0;
            for (int i = 0; i < vPoints.size(); ++i)
            {
                CPoint a = vPoints[i];
                double d = (x - a.x) * (x - a.x) + (y - a.y) * (y - a.y);
                //double d = abs(x - a.x) + abs(y - a.y); // Manhattan distance
                //double d = max(abs(x - a.x), abs(y - a.y)); // ?? distance
                if (d < dMinDist)
                {
                    pt = a;
                    n = i;
                    dMinDist = d;
                }
           }
           pDC->SetPixelV(x, y, vColors[n]);
       }
   }
   // Points
   for (int i = 0; i < vPoints.size(); ++i)
   {
       CPoint a = vPoints[i];
       pDC->FillSolidRect(a.x - 1, a.y - 1, 3, 3, 0x0);
   }
}

Output 1

Image

Output 2 (B/W)

Image

Output 3 (Manhattan Distance)

Image

Output 4 (?? Distance)

Image

Discrete Fourier Transformation (DFT) based Spectrum Tracer

DFT can be used to convert audio samples from time domain to frequency domain. That is the dancing spectrum in graphic equalizers. This piece of code (C++ with MFC) traces the spectrum of a wave file.

const double PI = 3.14159265;
const int N = 1024; // DFT point count
void TraceSpectrum(CDC* pDC)
{
char* pzFile ="D:\\in.wav";
HMMIO h = mmioOpen((LPTSTR)pzFile, NULL, MMIO_READ | MMIO_ALLOCBUF | MMIO_DENYWRITE); for (int i = 0; i < 50000; ++i)
{
double realInput[N] = { 0 }; for (int j = 0; j < N; ++j)
{
SHORT iLeft;
SHORT iRight;
mmioRead(h, (HPSTR)&iLeft, 2); // 16 bit samples
mmioRead(h, (HPSTR)&iRight, 2); // 16 bit samples realInput[j] = iLeft / 1000.0;
}
TraceDFT(realInput, pDC);
Sleep(10);
} mmioClose(h, 0);
}

void TraceDFT(double* realInput, CDC* pDC) {
double realOutput[N] = { 0 };
double imagOutput[N] = { 0 }; for (int k = 0; k < N / 2; ++k) // Output is symmetric, hence N/2
{
for (int n = 0; n < N; ++n)
{
// Theory: F(k) = sigma(T(n) * (cos(x) - j*sin(x))) Where x = 2PIkn/N double x = 2 * PI * k * n / N;
double re = realInput[n] * cos(x);
double im = realInput[n] * -sin(x);
realOutput[k] += re;
imagOutput[k] += im;
}
}

// To Decibels
double spectrum[N / 2];
for (int i = 0; i < N / 2; ++i)
{
double d = sqrt(realOutput[i] * realOutput[i] + imagOutput[i] * imagOutput[i]);
d = d < 1 ? 0 : 20 * log(d);
spectrum[i] = min(d, 200);
}
// Draw the graph
pDC->FillSolidRect(0, 0, 1000, 500, 0xffffff);
for (int i = 0; i < N / 2; ++i)
pDC->FillSolidRect(20 + i, 200 - spectrum[i], 1, spectrum[i], 0x0);
}

Output

Image

Homemade Timer Switch from PIC 12F629

Image

Features

  • Runs a device for a specified time duration.
  • Using the single push button at the bottom left, the duration can be programmed from 1s to ~18hrs and it is saved in EEPROM.
  • If the power is interrupted while the timer is on, the remainder is continued when the power comes back.
  • Can be converted to a conventional switch by holding down the push button when the timer is powered on. Repeating the same procedure converts the switch back to the previous programmable state.

How to Program

  • Hold down the push button to enter setup mode. The green LED turns on when entered.
  • Short pressing the push button starts a counter and blinks the green LED.
  • Count the number of blinks. Then the next short press takes the count as seconds or a long press takes it as minutes and stores in EEPROM.

How to Use

  • Short pressing the push button turns the switch on for the pre-programmed duration.
  • A short press can preempt a running switch.

Schematic

Image

Power supply: 6v-200mA transformer -> Rectifier Bridge -> Filter caps -> 7805

Plug base drilled for LEDs and push button.

Image

The ugly inside.

Image

Program:  (CCS-C)

#include <12F629.h>
#FUSES NOWDT //No Watch Dog Timer
#FUSES INTRC_IO //Internal RC Osc, no CLKOUT
#FUSES NOCPD //No EE protection
#FUSES NOPROTECT //Code not protected from reading
#FUSES MCLR //Master Clear pin enabled
#FUSES NOPUT //No Power Up Timer
#FUSES NOBROWNOUT //No brownout reset
#use delay(int=4000000)

#define PUSH_BTN    PIN_A0
#define SETUP_LED   PIN_A1
#define RUN_LED     PIN_A2
#define RELAY       PIN_A4
#define SPEAKER     PIN_A5
#define DEBOUNCE_DELAY   30 // ms
#define SHORT_PRESS      1
#define LONG_PRESS       2
#define RELEASE_TIME_OUT 3 // took too long to release
int16 gi_TimerDuration = 0; // The switch is kept on this amount of seconds
int16 gi_Remaining = 0; // Number of seconds remaining to switch off
//------------------------------------------------------------------------------
void PerSecond() // This gets called per second
{
    if (gi_Remaining > 0)
    {
        gi_Remaining--;

        // In each ~32 seconds, save remainder to continue automatically after a power outage
        if ((gi_Remaining & 0x1f) == 0) 
        {
             write_eeprom(2, gi_Remaining & 0xff);
             write_eeprom(3, gi_Remaining >> 8);
        }

        // Toggle Running indicator at last 10s
        //if (gi_Remaining < 10)
        // output_bit(RUN_LED, gi_Remaining & 0x1);
    }
    else if (gi_Remaining == 0)
    {
        output_low(RELAY);
        output_low(RUN_LED);
    }
}
//------------------------------------------------------------------------------
int16 time = 0; // driven by timer0
#INT_TIMER0 
void isr_timer0_overflow()
{
    time++;
    if (time < 3840)
       return;

    time = 0;
    PerSecond();
}
//------------------------------------------------------------------------------
void WaitForButtonPushDown()
{
    while (true)
    {
        int n;
        int i;

        while (input_state(PUSH_BTN) == 0)
            continue;

        // Some times this is sensitive to power glitches.
        // Read consecutive 5 HIGHs to verify.
        n = 0;
        for (i = 0; i < 5; ++i)
        {
             n += input_state(PUSH_BTN);
             delay_us(200);
        }

        if (n == 5) // OK. we are satisfied
            break;
    }

    delay_ms(DEBOUNCE_DELAY);
}
//------------------------------------------------------------------------------
int WaitForButtonReleaseOrTimeout()
{
    int16 t;

    // Wait till release or time out
    t = 0;
    while (input_state(PUSH_BTN) == 1)
    {
         output_high(SPEAKER);
         delay_us(300);
         output_low(SPEAKER);
         delay_us(300);

         if (t == 500) // 1500 // ~1s time out
             return RELEASE_TIME_OUT;

         t++;
    }
    delay_ms(DEBOUNCE_DELAY);

    if (t < 300) // less than half a second
        return SHORT_PRESS;
    else
        return LONG_PRESS;
}
//------------------------------------------------------------------------------
int WaitForButtonPress()
{
    WaitForButtonPushDown();
    return WaitForButtonReleaseOrTimeout();
}
//------------------------------------------------------------------------------
void DoTheJob()
{
    if (gi_TimerDuration == 0) // holiday
        return;

    // Start new service cycle
    gi_Remaining = gi_TimerDuration;
    output_high(RELAY);
    output_high(RUN_LED);
}
//------------------------------------------------------------------------------
void StopTheJob()
{
    if (gi_Remaining == 0) // already stoped
        return;

    gi_Remaining = 0;
    output_low(RELAY);
    output_low(RUN_LED);

    write_eeprom(2, 0);
    write_eeprom(3, 0);
}
//------------------------------------------------------------------------------
void RunSetup()
{
    int16 seconds;
    int16 t;
    int a;

    // Enter the setup mode
    output_high(SETUP_LED);

    // If we entered here in case of a timeout, Wait until button release 
    while (input_state(PUSH_BTN) == 1)
        continue;
    delay_ms(DEBOUNCE_DELAY);

    // Wait for a short press to start reading the duration input from user
    a = WaitForButtonPress();
    if (a == LONG_PRESS || a == RELEASE_TIME_OUT)
    {
        // Exit setup mode
        output_low(SETUP_LED);

        // Wait until release in case of a timeout
        while (input_state(PUSH_BTN) == 1)
            continue;
        delay_ms(DEBOUNCE_DELAY);

        return;
    }

    // Short press.
    // Read user input in seconds (i.e. time till next push down)
    seconds = 0;
    t = 0;
    while (input_state(PUSH_BTN) == 0)
    {
        delay_ms(7);// 10
        t++;
        output_bit(SETUP_LED, t > 90 ? 1 : 0);

        if (t == 100) // 1s
        {
            t = 0;
            seconds++;
        }
    }
    delay_ms(DEBOUNCE_DELAY);

    output_high(SETUP_LED); // as this LED is used above for 1Hz blink

    // Wait for a press to complete reading the duration input from user
    a = WaitForButtonReleaseOrTimeout();
    if (a == LONG_PRESS || a == RELEASE_TIME_OUT) // input in minutes
        gi_TimerDuration = seconds * 60; // Read minutes in seconds
    else if (a == SHORT_PRESS) // input is in seconds
        gi_TimerDuration = seconds;

    write_eeprom(0, gi_TimerDuration & 0xff);
    write_eeprom(1, gi_TimerDuration >> 8); 

    // Exit setup mode.
    output_low(SETUP_LED);
    // Wait until release
    while (input_state(PUSH_BTN) == 1)
        continue;
    delay_ms(DEBOUNCE_DELAY);
}
//------------------------------------------------------------------------------
void main()
{
    int switch_mode; // Regular or Timer

    setup_timer_0(RTCC_INTERNAL | RTCC_DIV_1);

    output_high(RUN_LED);
    output_high(SETUP_LED);
    delay_ms(100);

    output_low(RELAY);
    output_low(RUN_LED);
    output_low(SETUP_LED);
    delay_ms(1000); // env stabilization

    // EEPROM
    // Byte [0,1] -> gi_TimerDuration
    // Byte [2,3] -> gi_Remaining
    // Byte [4] -> switch_mode. 1=Regular Switch, else=Timer Switch 

    switch_mode = read_eeprom(4);

    // This is to switch between Regular and Timer modes. Hold down the button
    // when micro is powered on.
    if (input_state(PUSH_BTN) == 1)
    {
        // Wait until release
        while (input_state(PUSH_BTN) == 1)
        {
             output_high(SPEAKER);
             delay_us(300);
             output_low(SPEAKER);
             delay_us(300);
        }
        delay_ms(DEBOUNCE_DELAY);

        // user wants to toggle the switch mode
        if (switch_mode == 0)
            switch_mode = 1;
        else
            switch_mode = 0;

        write_eeprom(4, switch_mode); 
    }

    if (switch_mode == 1)
    {
        // Regular switch

        output_high(RELAY);
        output_high(RUN_LED);

        while (TRUE)
            continue;
    }

    // Starting Timer mode.

    // Read saved configurations
    gi_TimerDuration = read_eeprom(1);
    gi_TimerDuration = gi_TimerDuration << 8 | read_eeprom(0);
    gi_Remaining = read_eeprom(3); 
    gi_Remaining = gi_Remaining << 8 | read_eeprom(2);

    // Continuity after power outage.
    // Note: we save this in each 32s. so there can be a error of 32s.
    if (gi_Remaining >= 32) // not going to continue remainders of less than 32s
    {
        // little compensation (error / 2) may be preferred.
        gi_Remaining -= 16;
        output_high(RELAY);
    }
    else
        gi_Remaining = 0;

    enable_interrupts(GLOBAL);
    enable_interrupts(INT_TIMER0);

    while (TRUE)
    {
        int a;
        a = WaitForButtonPress();
        if (a == SHORT_PRESS)
        {
            if (gi_Remaining > 0)
                StopTheJob();
            else
                DoTheJob();
        }
        else if (a == LONG_PRESS || a == RELEASE_TIME_OUT)
        {
            RunSetup();
        }
    }
}
//------------------------------------------------------------------------------

Troubleshooting

  1. EEPROM of the microcontroller should be cleared when it is first programmed.
  2. MCLR pin should be kept grounded. If this is not done LEDs will flash rapidly.
  3. Verify your circuit using a simple program. Check LEDs are blinking, switch is working, buzzer is working.
  4. If it still does not work, hold the switch down while power is on. This toggles the behaviour of the switch from programmable to non-programmable and vice versa.

How Others Did

Pavel from Bangladesh has made his own version with some extended features.

539815_10152055538180902_1323642183_n 1511030_10152055538110902_1340965153_n

Homemade CNC v1

This is the first version of my homemade CNC machine (see version 2). It does not have enough power to cut anything but has a reasonable precision in drawing.

Image

It does not have any bearings (other than those inside steppers actually), lead screws run each axis and bear the weight at the same time, reasonably noisy and slow. Each axis (X,Y) is driven by two steppers working synchronously.

Image

Z axis is the head moving mechanism of an old CD ROM drive.

Image

In the above picture Y axis steppers are visible at the top. X axis steppers are in the box at the right hand side. Stepper drivers are based on L298 H-bridges and stays at the same box. This was the first time to work with power electronics. Mani chips left their life.

The base is made of MDF. The working area is about 8″ x 8″. Z axis travels about 1″.

Image

Motor couplers are cheap flexible tubes clamped with hose clips.

Image

Image

This machine is driven by a laptop -> USB to serial TTL converter -> PIC micro -> L298 drivers powered by an ATX PC PSU. As there are no opensource or free CAM softwares that support my configuration, I happened to write my own.

Image

The software accepts images, detect edges, generates tool paths and communicates with the PIC.

If you are trying to make your own homemade CNC, create the mechanical implementation your self and buy some stepper drivers. Do not try to build stepper drivers unless you are a skilled electronics guy. Power electronics is a separate subject that can generate lot of headache. Even a cheap poor quality TB6560 based stepper driver easily defeats most of the H-Bridges. Though steppers are supposed to rotate in steps, they do not. In the natural frequency they just vibrate not rotate (happened to me at 200Hz or 5 ms steps). This is a very sad situation. You have to accelerate the stepper rapidly across this area or start at a larger frequency and lose some steps!

Electromagnetic interference generated by steppers and router is non trivial, that can reset a PIC and stop your work unfinished at the middle. Do not forget the filter capacitors at the power supply and PIC MCLR pin. Do not use the USB power to drive signaling circuit. If your laptop is not intelligent enough to shut down the ports at the correct time then you are very likely to have a laptop with 2/3 ports burnt at the end. So use a separate power supply for the signaling circuit.

Ray Tracer: Hello world

Ray tracing is a deep and complex subject. It needs reasonable amount of mathematics to write even a simple one. But that kind of simple one still generates awesome output. The basic theory is to simulate the travel of light rays from one or more light sources to the eye, subjecting to reflections, refraction and deflections by various objects through its way. To reduce the computational complexity a view is assumed in-front of eye (or camera) and calculates the color of each pixel in the view, based on the amount and hue of the light that may arrive there.

Image

A vector class is essential to write a ray tracer.

class vec
{
public:
    vec()                   { x = y = z = 0; }
    vec(double _x, double _y, double _z) { x = _x; y = _y; z = _z; }

    vec operator-(vec& rhs) { return vec(x - rhs.x, y - rhs.y, z - rhs.z); }
    vec operator-()         { return vec(-x, -y, -z); }
    vec operator+(vec& rhs) { return vec(x + rhs.x, y + rhs.y, z + rhs.z); }
    vec operator*(double t) { return vec(x * t, y * t, z * t); }
    double dot(vec& r)      { return x * r.x + y * r.y + z * r.z; }
    vec cross(vec& r)       { return vec(y * r.z - z * r.y, z * r.x - x * r.z
                                         , x * r.y - y * r.x); }
    double length()         { return sqrt(x * x + y * y + z * z); }
    bool is_equal(vec& r, double eps) { return abs(x - r.x) < eps 
                                               && abs(y - r.y) < eps 
                                               && abs(z - r.z) < eps; }

    void normalize()
    {
        double d = length();
        assert(d);
        x /= d;
        y /= d;
        z /= d;
    }

    static double angle(vec v1, vec v2) { return acos(v1.dot(v2) 
                                                / (v1.length() * v2.length())); }

    double x;
    double y;
    double z;
};

Our first attempt is to trace a sphere illuminated by one omnidirectional light source. For that, this handy function is also required.

bool IntersectLineAndSphere(vec a, vec u, vec S, double r, double& t)
{
    // Line R = a + u*t
    // Sphere center S with radius r. x*x + y*y + z*z = r*r
    // To find the intersection point of ray and the sphere substitute ray on 
    // circle and solve 
    // the quadratic equation for minimum positive t.
    double A = 1;
    double B = 2 * (u.x * (a.x - S.x) + u.y * (a.y - S.y) + u.z * (a.z - S.z));
    double C = (a.x - S.x) * (a.x - S.x) + (a.y - S.y) * (a.y - S.y) 
               + (a.z - S.z) * (a.z - S.z) - r * r;
    double D = B * B - 4 * A * C;
    if (D >= 0)
    {
        double t1 = (-B + sqrt(D)) / 2;
        double t2 = (-B - sqrt(D)) / 2;
        // assuming t1 > 0 && t2 > 0 for this specific setup and as t1 >= t2
        t = t2;
        return true;
    }
    return false;
}

Rays (straight line) are represented in parametric form R = a + ut or x = x0 + dx * t, y = y0 + dy * t, z = z0 + dz * t where t is the parameter that vary along the line. Above function returns the parameter value at the nearest intersection point.

Step 1

Lets forget the light source. Just check where the sphere is.

void Trace(CDC* pDC)
{
    const double PI = 3.14159;
    const double eps = 0.001;

    // Eye or camera
    vec e(0, 0, 0);

    // View port
    vec vp0(-200, -200, 200);
    int vpcx = 400;
    int vpcy = 400;

    // Sphere
    vec S(0, 0, 450);
    double r = 300;

    for (int vpy = vp0.y; vpy < vp0.y + vpcy; ++vpy)
    {
        for (int vpx = vp0.x; vpx < vp0.x + vpcx; ++vpx)
        {
            // For each pixel in view port, the unit vector of ray is
            vec u(vpx - e.x, vpy - e.y, vp0.z - e.z);
            u.normalize();

            int color;
            double t;
            if (IntersectLineAndSphere(e, u, S, r, t))
                color = 100;
            else
                color = 0;

            pDC->SetPixel(vpcx / 2 - vpx, vpcy / 2 - vpy
                          , RGB(color, color, color));
        }
    }
}

Image

Step 2

Easily decorate the background with a checker board pattern.

void Trace(CDC* pDC)
{
    ...

    for (int vpy = vp0.y; vpy < vp0.y + vpcy; ++vpy)
    {
        for (int vpx = vp0.x; vpx < vp0.x + vpcx; ++vpx)
        {
            ...

            double t;
            if (IntersectLineAndSphere(e, u, S, r, t))
            {
                 int color = 100;
                 pDC->SetPixel(vpcx / 2 - vpx, vpcy / 2 - vpy
                               , RGB(color, color, color));
            }
            else // no intersection
            {
=>               bool dark = (int)(vpx - vp0.x) / 8 % 2 
                                   != (int)(vpy - vp0.y) / 8 % 2;
=>               pDC->SetPixel(vpcx / 2 - vpx, vpcy / 2 - vpy
                               , dark ? 0x0 : 0x202020); // grid
            }
        }
    }
}

Image

Step 3

The sphere is illuminated by the ambient light only. i.e. each point on sphere has a constant brightness value. That light is emitted radially. So amount of light coming from the points at boundary is less than that near the center.

void Trace(CDC* pDC)
{
    ...

    for (int vpy = vp0.y; vpy < vp0.y + vpcy; ++vpy)
    {
        for (int vpx = vp0.x; vpx < vp0.x + vpcx; ++vpx)
        {
            ...

            double t;
            if (IntersectLineAndSphere(e, u, S, r, t))
            {
                 vec vi = e + u * t; // intersection point
                 vec vn = (S - vi) * (1 / r); // surface normal
                 vn.normalize();

=>               double c = (PI / 2 - abs(vec::angle(vn, u))) / (PI / 2) * 256;
=>               int color = min(c / 3, 255);
                 pDC->SetPixel(vpcx / 2 - vpx, vpcy / 2 - vpy
                               , RGB(color, color, color));
            }
            else // no intersection
            {
                 ...
            }
        }
    }
}

Image

Step 4

Lights ON. Check whether the light hits the point that we are interested on. Calculate the amount of light that is reflected towards the selected pixel in the view port. Ambient light turned off to see the reflected light only. Various magic numbers are required to tune the light.

void Trace(CDC* pDC)
{
    ...
    // Omnidirectional Light
    vec L(-400, 400, -600);
    for (int vpy = vp0.y; vpy < vp0.y + vpcy; ++vpy)
    {
        for (int vpx = vp0.x; vpx < vp0.x + vpcx; ++vpx)
        {
             // For each pixel in view port, the unit vector of ray is
             vec u(vpx - e.x, vpy - e.y, vp0.z - e.z);
             u.normalize();
             double t = 0;
             if (IntersectLineAndSphere(e, u, S, r, t))
             {
                  vec vi = e + u * t; // intersection point
                  vec vn = (S - vi) * (1 / r); // surface normal
                  vn.normalize();
                  int color = 0;
                  // Check if light hits this point.

                  // Unit vector of light
                  vec uL = vi - L;
                  uL.normalize();
                  if (IntersectLineAndSphere(L, uL, S, r, t))
                  {
                      vec vL1 = L + uL * t; // nearest point to the light
                      if (vL1.is_equal(vi, eps)) // light hits here
                      {
                          // reflected light
                          vec vr = vn * uL.dot(vn) * 2 - uL;
                          double c = (PI - abs(vec::angle(u, vr))) / PI * 256;
                          c = c * c / 256 + c * c * c / 256 / 500;
                          color += min(c / 1.5, 255);
                       }
                   }
                   pDC->SetPixel(vpcx / 2 - vpx, vpcy / 2 - vpy
                                 , RGB(color, color, color));
              }
              else // no intersection
              {
                   ...
              }
         }
     }
}

Image

Step 5

Image

Ambient + Reflection  Adding more objects and light sources hugely complicates the algorithm.