Close
0%
0%

1K Challange Laser

Draw the hackaday logo with a laser with less than 1K of data.

Similar projects worth following
We create the Hackaday logo using a laser, MSP430, DAC module, and a laser galvo kit. And all of it, just under 1K of code!.

You can find how we accomplished this in the logs.
The source files can be found here:
https://github.com/cyrillegin/1KLaser.git

Creating the hackaday logo with a MSP430 and a laser.

Project files can be found here:

https://github.com/cyrillegin/1KLaser.git

  • <1K Complete!

    Cyrille Gindreau12/09/2016 at 22:34 0 comments

    At this point, we decided to abandon the concept of mirroring. If we had had a much more complex design with many more points and more than 1kb, mirroring would have saved us space. Due to having so little space however, the algorithms power wasn't able to give us the results we were looking for.

    We had by this point made so many optimizations to our code that when we reverted to a single array, the total size was 902 bytes! The final program:

    #include <msp430.h> 
    
    #define uint8_t unsigned char
    #define uint16_t unsigned int
    
    #define LASER BIT0
    #define SSOUT P1OUT
    #define SSX BIT6
    #define SSY BIT7
    
    #define length 234
    
    void writeMCP492x(uint16_t data,uint8_t ss);
    void drawLine(uint16_t, uint16_t, uint16_t, uint16_t);
    
    int main(void) {
         WDTCTL = WDTPW | WDTHOLD;	// Stop watchdog timer
         PM5CTL0 &= ~LOCKLPM5;//reset fram
    
         CSCTL1 |= DCORSEL_6;//sets clock speed (16mz)
    
         P1DIR |= BIT0 + SSX + SSY;//p1 output
         P1OUT |= BIT0;//set bit 0
    
         ///////////////
         ///SPI Int//////
         ////////////////
         P5SEL0 |= BIT1 + BIT2;
         UCB0CTLW0 |= UCSWRST;                     // **Put state machine in reset**
         UCB0CTLW0 |= UCMST+UCSYNC+UCCKPL+UCMSB;   // 3-pin, 8-bit SPI master MSB
         UCB0CTLW0 |= UCSSEL_2;                    // SMCLK
         UCB0BR0 |= 0x01;						  // CLK / 1
         UCB0BR1 = 0;
         UCB0CTL1 &= ~UCSWRST;
    
         //The coordinates for the laser to traverse. Even index are X values, odd index are Y values.
         uint8_t logo[] = {66, 158, 47, 177, 37, 176, 27, 177, 16, 186, 8, 195, 8, 212, 27, 195, 47, 220, 27, 236, 40, 238, 56, 233, 66, 220, 68, 212, 68, 203, 86, 186, 75, 175, 66, 158, //lower left wrench:36
             	66, 98, 47, 79, 37, 80, 27, 79, 16, 70, 8, 61, 8, 44, 27, 61, 47, 36, 27, 20, 40, 18, 56, 23, 66, 36, 68, 44, 68, 53, 86, 70, 75, 81, 66, 98, //upper left wrench:36
         		118, 125, 110, 136, 102, 138, 86, 120, 95, 105, 97, 108, 102, 114, 110, 118, 118, 125, //left eye:18
    			190, 158, 209, 177, 219, 176, 229, 177, 240, 186, 248, 195, 248, 212, 229, 195, 209, 220, 229, 236, 216, 238, 200, 233, 190, 220, 188, 212, 188, 203, 170, 186, 181, 175, 190, 158,//lower right wrench:36
    			190, 98, 209, 79, 219, 80, 229, 79, 240, 70, 248, 61, 248, 44, 229, 61, 209, 36, 229, 20, 216, 18, 200, 23, 190, 36, 188, 44, 188, 53, 170, 70, 181, 81, 190, 98, //upper right wrench:36
    			138, 125, 146, 136, 154, 138, 170, 120, 161, 105, 159, 108, 154, 114, 146, 118, 138, 125,//right eye: 18
    			80, 159, 127, 180, 175, 159, 187, 120, 175, 84, 162, 70, 155, 55, 146, 55, 140, 70, 137, 70, 131, 55, 121, 55, 117, 70, 115, 70, 108, 55, 98, 55, 93, 74, 80, 84, 67, 120, 80, 159,//face: 40
    			127, 103, 130, 91, 133, 83, 127, 91, 121, 83, 125, 91, 127, 103//nose:14
         };
         //keeps track of where in the array we are.
         uint8_t myIndex = 0;
         //used for creating delays due to the difference in speed between processing and moving a mirror.
         uint16_t counter;
         /*
          * main program loop. Iterates logo array two at a time and calls drawline function
          * with either the next four array points or the next two from the index and the
          * beginning two entries creating a loop.
          * Also checks if the laser should be turned off while traversing between
          * two points.
         */
         while(1){
        	 if(myIndex < length-3){
        	 	//Check if laser should be turned off.
        	 	if(myIndex == 34 || myIndex == 70 || myIndex == 88 || myIndex == 124 || myIndex ==160 || myIndex == 178 || myIndex == 218 || myIndex == 232){
        	 	 	for(counter = 1200; counter > 0; counter--){
        	 	 		counter = counter- 1;
        	 	 	}
        	 	 	P1OUT &= ~LASER;
        	 	 }
        	 	 //Draw a line from X,Y to X', Y'
        	 	 drawLine(logo[myIndex], logo[myIndex+1], logo[myIndex+2], logo[myIndex+3]);
        	 	 //Check if laser should be turned back on.
        	 	 if(myIndex == 34 || myIndex == 70 || myIndex == 88 || myIndex == 124 || myIndex ==160 || myIndex == 178 || myIndex == 218 || myIndex == 232){
        	 	    	for(counter = 800; counter > 0; counter--){
        	 	    		counter = counter- 1;
        	 	    	}
        	 	        P1OUT |= LASER;
        	 	 }
        	 	 myIndex = myIndex +2;
        	 //Loop back to beginning of array.
        	 } else {
        	 	 for(counter = 1200; counter > 0; counter--){
     	 	 counter...
    Read more »

  • Optimization: Round 2

    Cyrille Gindreau12/09/2016 at 22:23 0 comments

    We were close, about 200 bytes away, it was time to get creative. The first thing we did was to flip our coordinates so that we were right side up. Second, we added the coordinates for where the laser should turn off (which actually brought our size count up to about 1500 bytes). Then we started experimenting a little bit. We broke up the logo into different sections: A wrench, the skull, one eye and the nose. The idea was that we could mirror the different pieces to save space. Our first take on the mirroring algorithm:

    
    //lower left wrench
    uint16_t wrench[] = {265, 635, 190, 710, 150, 705, 110, 710, 65, 745, 35, 780, 35, 850, 110, 780, 190, 880, 110, 945, 160, 955, 225, 935, 265, 880, 275, 850, 275, 815, 345, 745, 300, 700, 265, 635};
    uint16_t wrenchLength = 36;
    
    //Skull outer
    uint16_t face[] = {320, 390, 510, 305, 700, 390, 750, 545, 700, 690, 650, 745, 620, 805, 585, 805, 560, 745, 550, 745, 525, 805, 485, 805, 470, 745, 460, 745, 435, 805, 395, 805, 375, 730, 320, 690, 270, 545, 320, 390};
    uint16_t faceLength = 40;
    
    //Left eye
    uint16_t leftEye[] = {475, 525, 440, 480, 410, 475, 345, 545, 380, 605, 390, 595, 410, 570, 440, 555, 475, 525};
    uint16_t eyeLength = 18;
    
    //Nose
    uint16_t nose[] = {510, 615, 520, 660, 535, 695, 510, 660, 485, 695, 500, 660, 510, 615};
    uint16_t noseLength = 14;
    
    uint16_t myIndex = 0;
    uint16_t logoPart = 0;
    uint16_t mirrorCount = 4;
    uint16_t laserDelay;
    uint16_t direction = 0;
    while(1){
        switch(logoPart){
        //Draws the wrenches
        	case 0:
        	    if(mirrorCount > 0){
    		if(myIndex < wrenchLength-3){
    		    drawLine(wrench[myIndex], wrench[myIndex+1], wrench[myIndex+2], wrench[myIndex+3]);
    		    myIndex = myIndex +2;
    		} else {
    		    for(laserDelay = 0; laserDelay < 400; laserDelay++)
    		        continue;
    		    P1OUT &= ~LASER;
    		    if(mirrorCount > 1){
    		        uint16_t oldX = wrench[myIndex];
    			uint16_t oldY = wrench[myIndex+1];
    			if(direction == 0){
    			    mirrorX(wrench, wrenchLength, 256);
    			    direction = 1;
    			} else {
    			    mirrorY(wrench, wrenchLength, 256);
    			    direction = 0;
    			}
    			drawLine(oldX, oldY, wrench[0], wrench[1]);
    		    }
    		    for(laserDelay = 0; laserDelay < 250; laserDelay++)
    		        continue;
    		    P1OUT |= LASER;
    		    myIndex = 0;
    		    mirrorCount = mirrorCount - 1;
    		}
        	     } else {
        	         for(laserDelay = 0; laserDelay < 400; laserDelay++)
        	 	     continue;
        	 	 P1OUT &= ~LASER;
        	 	 mirrorCount = 2;
        	 	 drawLine(wrench[myIndex], wrench[myIndex+1], leftEye[0], leftEye[1]);
        	 	 logoPart = 1;
        	 	 for(laserDelay = 0; laserDelay < 250; laserDelay++)
        	 	     continue;
        	 	 P1OUT |= LASER;
        	     }
    	     break;
    	//draws the eyes
        	case 1:
        	    if(myIndex < eyeLength-3){
        	        drawLine(leftEye[myIndex], leftEye[myIndex+1], leftEye[myIndex+2], leftEye[myIndex+3]);
        	        myIndex = myIndex +2;
        	    } else if(mirrorCount > 1) {
        	 	for(laserDelay = 0; laserDelay < 400; laserDelay++)
        	 	    continue;
        	 	P1OUT &= ~LASER;
        	 	uint16_t oldX = leftEye[myIndex];
        	 	uint16_t oldY = leftEye[myIndex+1];
        	 	mirrorX(leftEye, eyeLength, 256);
        	 	drawLine(oldX, oldY, leftEye[0], leftEye[1]);
        	 	myIndex = 0;
        	 	for(laserDelay = 0; laserDelay < 250; laserDelay++)
        	 	    continue;
        	 	    P1OUT |= LASER;
        	 	mirrorCount = mirrorCount - 1;
        	     } else {
        	 	for(laserDelay = 0; laserDelay < 400; laserDelay++)
        	 	    continue;
        	 	P1OUT &= ~LASER;
        	 	mirrorCount = 4;
        	 	drawLine(leftEye[myIndex], leftEye[myIndex+1], nose[0], nose[1]);
        	 	myIndex = 0;
        	 	logoPart = 2;
        	 	for(laserDelay = 0; laserDelay < 250; laserDelay++)
        	 	    continue;
        	 	P1OUT |= LASER;
        	     }
        	     break;
        	 //draws the nose
        	 case 2:
        	     if(myIndex < noseLength-3){
        	 	drawLine(nose[myIndex], nose[myIndex+1], nose[myIndex+2], nose[myIndex+3]);
        	 	myIndex = myIndex +2;
        	     } else {
        	 	for(laserDelay = 0; laserDelay < 400; laserDelay++)
        	 	    continue;
        	 	P1OUT &= ~LASER;
        	 	drawLine(nose[myIndex], nose[myIndex+1], face[0], face[1]);
        	 	logoPart = 3;
        	 	myIndex = 0;
        	 	for(laserDelay = 0; laserDelay < 250; laserDelay++)
        	 	    continue;
        	 	P1OUT |= LASER;
        	     }
        	     break;
        	 //draws the face
        	 case 3:
        	     if(myIndex < faceLength-...
    Read more »

  • Creating the Hackaday logo

    Cyrille Gindreau12/09/2016 at 20:48 0 comments

    Our goal for this project is to display the hackaday logo by shining a laser at some mirrors and moving the mirrors in such way that it would draw the logo. We are under the 1K limit, we have a function that will take an array of coordinates and display the laser, we have ways of turning on/off the laser during certain times, now its time to see if we can draw the whole logo.

    The first thing we did was plot out where we would want our coordinates to be. Using photoshop and a sketch pad, we created our array.

    *The 1's in circles dictated which point the shape would start at and in which direction the laser would go.

    We plug in the number for our array and:

    Okay! Not bad, we've got a little work to do but we're getting close. Unfortunately though, we're back over our 1K limit at around 1150 bytes. So a little more optimization, add the 'turn off laser' array, and flip it right side up, should be a breeze right?

  • Optimize: Round 1

    Cyrille Gindreau12/09/2016 at 20:17 0 comments

    How did we go from around 300 bytes to 1500 bytes when all we did was try to modularize our code? This is where the disassembly window started really becoming the star of the show.

    Going through each assembly instruction, we quickly realized that instructions like multiply and divide and data structures like floats were the culprit. Looking at the memory map, we saw that all kinds of libraries were being brought in.

    We got rid of floats and started using unsigned chars and ints

    void drawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2){
    
    	uint16_t dx1 = x1 > x2 ? x1 - x2 : x2 - x1;
    	uint16_t dy1 = y1 > y2 ? y1 - y2 : y2 - y1;
    
    	uint16_t steps1 = dx1 > dy1 ? dx1/4 : dy1/4;
    
    	uint16_t Xincrement1 = (dx1*100) / steps1;
    	uint16_t Yincrement1 = (dy1*100) / steps1;
    
    	int x11 = x1*100;
    	int y11 = y1*100;
    	int i;
    
    	for(i = 0; i < steps1; i++){
    
    		x11 = x1 < x2 ? x11+Xincrement1 : x11 - Xincrement1;
    		y11 = y1 < y2 ? y11+Yincrement1 : y11 - Yincrement1;
    
    	    	writeMCP492x((int)((x11/100)*16), SSX);
    	    	writeMCP492x((int)((y11/100)*16), SSY);
    	}
    }

    With just a few little changes, this brought our size back down to 464 bytes.

    Another optimization we made was that rather than calling this function with the coordinates directly, we modified the program a little bit to accept arrays of coordinates. The even numbers in the array would be X values while the odd would be Y values.

    We also created an array of when the laser should be turned off. Turning off the laser would allow us to jump from one coordinate to another without being seen and without over cranking the galvos.

    uint16_t myPoly[] = {230, 220, 245, 185, 150, 5, 65, 155, 150, 155, 130, 110, 150, 75, 230, 220, 25, 220, 5, 185, 175, 185, 150, 155, 130, 110, 110, 155, 5, 185, 110, 5, 150, 5};
    uint16_t polyLength = 34;
    uint16_t offIndices[] = {22, 26, 32};
    uint16_t offLength = 3;
    uint16_t offIter = 0;
    uint16_t myIndex = 0;
    
    while(1){
    	 for(offIter = 0; offIter < offLength; offIter++){
    		 if(myIndex == offIndices[offIter]){
    			P1OUT &= ~LASER;
    		 }
     	 }
     	 if(myIndex < polyLength-3){
     		 drawLine(myPoly[myIndex], myPoly[myIndex+1], myPoly[myIndex+2], myPoly[myIndex+3]);
        	 	myIndex = myIndex +2;
        	  } else {
        	 	drawLine(myPoly[myIndex], myPoly[myIndex+1], myPoly[0], myPoly[1]);
        	 	myIndex = 0;
        	  }
        	 P1OUT |= LASER;
    }

    This allowed for the creation of some more complex patterns while still saving as much space as possible.

  • How to draw a line

    Cyrille Gindreau12/09/2016 at 19:53 0 comments

    Once we had the laser at the origin, we wanted to draw a square to find the borders of the area we could project in. Using for loops, we iterated through each side.

    uint16_t i;
    while(1){
    	for(i = 0; i <= 4095; i++){
    		writeMCP492x(i, SSX);
    		writeMCP492x(0, SSY);
    	}
    	for(i = 0; i <= 4095; i++){
    		writeMCP492x(4095, SSX);
    		writeMCP492x(i, SSY);
    	}
    	for(i = 0; i <= 4095; i++){
    		writeMCP492x(4095-i, SSX);
    		writeMCP492x(4095, SSY);
    	}
    	for(i = 0; i <= 4095; i++){
    		writeMCP492x(0, SSX);
    		writeMCP492x(4095-i, SSY);
    	}
    }

    This was the result.

    So far, we were only using about 310 bytes of our 1K, smooth sailing!

    Now that we knew how to draw lines, we thought that the easiest way to implement the drawing of any shape would be to create a function that took in four numbers: an X and Y coordinate from where a line would start and an X and Y coordinate for where it would end. The function would then find every location between these points and move the mirrors to their corresponding location. This is what we came up with.

    void drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2){
    
    	int dx = (int)x2 - (int)x1;
    	int dy = (int)y2 - (int)y1;
    	int steps;
    	if (abs(dx) > abs(dy)){
    	    steps = abs(dx/4);
    	} else{
    	    steps = abs(dy/4);
    	}
    
    	float Xincrement = (float) dx / (float) steps;
    	float Yincrement = (float) dy / (float) steps;
    
    	float x = (float) x1;
    	float y = (float) y1;
    	int i;
    	for(i = 0; i < steps; i++){
    	    x = x + Xincrement;
    	    y = y + Yincrement;
    	    writeMCP492x( ((int)(x*16)),SSX);
    	    writeMCP492x( ((int)(y*16)),SSY);
    	}
    }
    
    int abs(int val){
    	return (val<0 ? (-val) : val);
    }

    With this, we could feed it the coordinates to any kind of polygon, for example, a hexagon.

    while(1){
        	 drawLine(192, 128, 160, 183);
        	 drawLine(160, 183, 96, 183);
        	 drawLine(96, 183, 64, 128);
        	 drawLine(64, 128, 95, 72);
        	 drawLine(95, 72, 160, 72);
        	 drawLine(160, 72, 192, 128);
    }

    After compiling, our size was almost 1500 bytes! But it worked, so we just had to figure out what was taking up all that space.

  • Setting up the SPI

    Cyrille Gindreau12/09/2016 at 19:23 0 comments

    In order to use the DAC, we needed to initialize the SPI on this device as well as create a method to write the DAC values. The module uses an MCP4921 DAC IC which is at 12bit resolution. The voltage conditioners in the modules will scale the min input voltage as zero and the max output voltage as 4095. In this case 0 is -12V and 4095 is 12V making 2048 0V.

    Being on the SPI Bus each DAC module is activated using the slave select PIN. We Defined these as SSX and SSY

    MSP430 SPI Initialization

    WDTCTL = WDTPW | WDTHOLD;	// Stop watchdog timer
    	PM5CTL0 &= ~LOCKLPM5;//sets to 12 mh
    
    	CSCTL1 |= DCORSEL_6;//sets clockspeed
    
    	P1DIR |= BIT0 + SSX + SSY;//p1 output
    	P1OUT |= BIT0;//set bit 0
    
    	///////////////
    	///SPI Int//////
    	////////////////
    	P5SEL0 |= BIT1 + BIT2;
    	UCB0CTLW0 |= UCSWRST;                     // **Put state machine in reset**
    	UCB0CTLW0 |= UCMST+UCSYNC+UCCKPL+UCMSB;   // 3-pin, 8-bit SPI master MSB
    	UCB0CTLW0 |= UCSSEL_2;                    // SMCLK
    	UCB0BR0 |= 0x01;						  // CLK / 1
    	UCB0BR1 = 0;
    	UCB0CTL1 &= ~UCSWRST;

    WriteMCP Routine

    void writeMCP492x(uint16_t data,uint8_t ss) {
      // Take the top 4 bits of config and the top 4 valid bits (data is actually a 12 bit number)
      //and OR them together
      uint8_t top_msg = (0x30 & 0xF0) | (0x0F & (data >> 8));
    
      // Take the bottom octet of data
      uint8_t lower_msg = (data & 0x00FF);
    
      // Select our DAC, Active LOW
      SSOUT &= ~ss;
    
      // Send first 8 bits
      UCB0TXBUF = top_msg;
      while (UCBUSY & UCB0STAT);
    
      // Send second 8 bits
      UCB0TXBUF = lower_msg;
      while (UCBUSY & UCB0STAT);
      //Deselect DAC
      SSOUT |= ss;
    }

    To run faster we also set the internal clock to run at 16Mhz.

    [clock lines]

    CSCTL1 |= DCORSEL_6; //sets clockspeed

    In the main loop after initilization, we create another loop to shine the laser directly at it's origin.
    while(1){
        writeMCP492x(2048, SSX);
        writeMCP492x(2048, SSY);
    }

  • Setting up the environment

    Cyrille Gindreau12/09/2016 at 17:17 0 comments

    The first step is to install TI Code Composer studio and then start a new CCS project for this Microcontroller

    Once created Code Composer studio started off with a real basic main.c file

    #include <msp430.h> 
    
    /*
     * main.c
     */
    int main(void) {
        WDTCTL = WDTPW | WDTHOLD;	// Stop watchdog timer
    	
    	return 0;
    }

    Just using main.c we compiled and ran to see where we were in terms of memory

    MSP430: There were 46 (code) and 38 (data) bytes written to FLASH/FRAM. 
    The expected RAM usage is 160 (uninitialized data + stack) bytes.
    To fully understand what the code was being used for, we wanted to see the Assembly along with the map file. We enabled these features by clicking Project -> Show build settings.

    In the settings, we applied some basic optimization. The first was to tell the compiler to optimize for space instead of speed.

    Then under Processor options we set the code memory model to be small. This just tells the processor to use the first 64k of memory. We are unsure if this will help with space but it may make looking at the disassembly a bit easier.

    We wanted to see the Assembly files generated by the compiler. So we clicked the option to keep the assembly file as well as add the source interlist so we can see the C code in the comments with the assembly.

    And finally we set up our view to show the assembly side by side our C code.

  • The Microcontroller and the 1k Challenge

    Evan Salazar12/09/2016 at 17:13 0 comments

    Originally the microcontroller for this project was a Teensy 3.1 which was picked due to its high speed, however once hackaday announced their 1k challenge we figured maybe we can make this laser project do something interesting with only 1k. At the time the boot-loader memory was still being debated on the forms and in a such a space constrained device it is nice to have full hardware debugging where memory/registers can be viewed and instructions can be stepped through. So the Teensy was out and the only microcontroller we had that met these requirements is a TI MSP430FR133 Launchpad kit. As per rules you can use TI Studio on this device for free and get full hardware programming and debugging. This was also a great opportunity/excuse to explore this architecture and learn something about compiler optimization.

  • The Mirror Galvanometer

    Evan Salazar12/09/2016 at 17:03 0 comments

    This project was originally trying to use a couple SPI DAC modules to control a mirror galvanometer kit that was purchased from eBay. The basic kit included two galvanometers, a stand, two voltage feedback units and a +/- 15V switching power supply. The controller simply takes an analog voltage between -12 and 12v to position the mirror. Our intent was to use our DAC module which is capable of producing that voltage directly via an SPI signal. All components were mounted to a MDF and the laser was held in using a custom 3d printed holder.

View all 9 project logs

Enjoy this project?

Share

Discussions

Ahron Wayne wrote 02/07/2021 at 04:36 point

I appreciate your comments in the code! hope you're still making stuff with lasers.

  Are you sure? yes | no

Frank van der Hulst wrote 03/08/2018 at 21:20 point

Hi,
Can you share a bit about how the hardware is wired up? I have a 20Kpps scanner like yours, and want to control it from an Arduino (or even better ESP32) rather than your MSP430.
Frank

  Are you sure? yes | no

Cyrille Gindreau wrote 02/23/2017 at 04:54 point

Thanks! It was a ton of fun making this!

  Are you sure? yes | no

Grzegorz Pietrusiak wrote 01/17/2017 at 19:33 point

Congratulations!!!

  Are you sure? yes | no

SarahC wrote 12/25/2016 at 04:18 point

Awesome result! I love it.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates