Sunday, 9 August 2015

What's the point of Adobe Flash now?

Don't get me wrong. I love Flash. I've used it since about version 4 and have created some pretty crazy stuff with it over the years. Back in version 5 I was re-creating pseudo-3d isometric puzzle games and as soon as xml sockets were supported, realtime multiplayer maze games (like Rise of the Triad and Doom). Flash could do some amazing stuff. And it did it all in tiny web-friendly file sizes.
To top it all, Flash could be embedded into PC desktop applications, for fancy menus in "real" commercial systems - it wasn't just an animated web banner tool (though that's probably what 90% of it's users were building with it).

But I tried using it recently to do some cool stuff I used to use Flash for, and Adobe have butchered it so badly that it's pretty much useless for anything useful at all now.


Until quite recently, I still used it to draw simple shapes and logos to create printable vector artwork for paper documents. I love(d) the drawing tools in Flash - they were so easy and intuitive. And it used to have a massive array of different output formats.

Quite often I'd make a drawing or a diagram for something for the missus, who still uses (MS desktop publishing software) Publisher. She could position it and guarantee it would print in all it's vector-y goodness, scaled and without jagged edges and so on. WMF and EMF were my favourite image formats for exporting.

Now Flash lets you export images in about three different formats, all of them rasterised. The only vector option is SVG. And even then, it rasterises gradients and embeds them as shitty 72DPI images. And, quite often, it doesn't even get that right. So it's rubbish for exporting images.




I used to create web apps using Flash, and all kinds of realtime socket-based clients, as it actively installed on something like 90% of all internet-connected devices (mostly PCs) at one time, across a wide range of browsers.

Then Apple decided that Flash was poo and in the kind of backwards step that we're still reaping the benefits from today (145Mb animated GIFs on the Google homepage over a 3G/mobile connection anyone? Because that's better than a 30kb animated vector file surely?) refused to support it.

There's no point getting into the whys and wherefores about why the Flash player couldn't be updated to fixed whatever Apple had against it - it's a long lost discussion - but hate it they did, and slowly Flash lost favour as a web-based development platform. It's still immensely powerful (or, at least, would be if you could get an older browser plug-in to work today) and still does stuff that todays' web-based technologies can only dream of (without consuming 100% CPU and locking up the entire device for 10-20 seconds at a time!). So Flash is rubbish for web-based apps.



Just as I was about to give up on Flash completely (I remained a faithful supported long after many other people had jumped ship to HTML5/jQuery or - shudder - Python for desktop-based apps) the first glimmer of good news came out, around about when Flash reached version CS5.5 - Flash could compile apps for native Android or iOS. Very exciting!

But Flash doesn't get access to many of the operating system functions and only a subset of them are exposed by installing and hacking "black box" pre-compiled ANEs (modifying is too generous a term, since they're usually badly-written with no support or documentation to speak of).

So if you want to create an animated, interactive advert and call it an app, Flash is great to get your content quickly onto a mobile device. But if you want to create a "serious" app (and access things like peer-to-peer networking, or control the rendering for your creation to use the GPU instead of CPU to avoid draining the battery and causing the entire handset to warm up from chewing up too many CPU cycles) you're out of luck.

In fact, for anything other than pretty basic interactive screens, Flash is rubbish for mobile apps.

One thing is was good at was interactive videos.
We're making one of those (although the interactive interface is a board game, not a series of buttons on the screen). So despite a brief dalliance with Unity, we thought we'd make far faster progress with good old Flash. 

Interactive videos and Flash - like two old friends back together after a ten year break. 
Flash introduced us to .flv video files, which supported background transparency. You could simply drop a movie onto your timeline, stick a bitmap behind it and you had an interactive movie!

Flash could even do "green screening" on-the-fly. In fact, it's almost ten years ago since examples like this (http://www.quasimondo.com/archives/000615.php) were really capturing people's imaginatons:



If your browser will still play the embedded Flash file, have a play with the sliders and see how easy it is to "key out" background colours in videos at runtime.

This is just the thing we were looking for to get our zombie game coded up quickly, to create a working demo for our zombie board game. But it seems that Flash doesn't support transparent videos any more.

In fact, Flash doesn't even support .flv files any more. Adobe's official response to anyone who wants/needs to work with videos with an alpha channel is "use an older version of the software, that's why we made it possible for you to install multiple instances of Flash on your computer".

But the last versions of Flash to properly support alpha-videos/.flv don't compile mobile apps properly any more. So there's no way of creating a mobile/tablet (or even PC) app that can play a video with a transparent background. And since the example (above) was written using a really old version of Flash, you can't even import that and use it to build some runtime-chroma-key routines in the latest version - because later versions of Flash refuse to open anything that has any reference to AS2 in it at all.

So it seems that Flash is rubbish for making interactive movies too.



And given that Adobe has a massive suite of tools for doing things like drawing and animating and web development, it begs the question - "what's the point of Adobe Flash now?"

I think I'm going to cancel my monthly subscription. I've not really used Flash in over 12 months for anything more than quick-and-dirty prototyping, and I can't see it ever being used for a commercial grade project again.

And spend the next few days really getting to understand Unity properly, to see if that can do what I need - namely play a video with a transparent background over a scriptable background. Fingers crossed........

Saturday, 8 August 2015

Creating flat matte videos for chroma keying in Unity with Adobe After Effects

The time seems to be whizzing by lately - it only seems like yesterday we were filming our greenscreen zombie footage and it's already been nearly two weeks! Now we've a load of video footage, and plenty of work to do, to make it "game ready" (i.e. dropping out all the unusable background). We've loads of little clips, so we're starting small - but this is the main technique we used for pretty much all of them (and will continue with for future footage):

Here's a quick clip of Andrea wandering around, getting ready to shoot some zombies with his puny pistol.


As you can see, the background is hardly a perfect flat green (although it didn't quite look that bad when we were actually shooting the footage - the higher contrast of the output video makes the wrinkles in the screen really stand out!)


The footage is also "too big" - it was shot at 1920 x 1088 and included far too much footage, so we've had to crop the video and rescale to a more manageable 720 x 405, as well as trim out the unwanted footage both before and after the action we want to capture


After importing the video into After Effects, the final output render size was massive and there was a lot of unwanted extra footage.

We used the keyboard shortcut to "splice" the video footage at the current keyframe, using Ctrl + Shift + D. This not only creates a razor cut in the video, but splits it out onto two separate frames. Before cutting any footage, we rescaled the actual video and moved it around inside our composition window, so that only the actual footage we wanted was captured.


We made a cut at the start and at the end of the section of footage we wanted to keep. This split the video onto three layers, making it easy to select the bits before and after to remove them completely.

Now, we've had mixed success with the following technique, using the entire footage, so at this point we exported our video selection to a new, separate file, and imported it into a new project. As we've still plenty of work still to do with this footage, we exported it as uncompressed video without using any compression or using a video codec. This makes massive files (100Mb for 5 seconds) but does mean fewer compression artifacts and no blockiness in the new video.

Next we imported the newly rendered file and made sure that it filled our composition size of 720 x 405 (the earlier footage was much larger than this and needed to be cropped). Then, following the instructions at creativecow website (https://library.creativecow.net/articles/rabinowitz_aharon/junk_mattes/video-tutorial) we made the entire background transparent. The basic steps are:

With the video footage selected in After Effects, apply the KeyLight (v1.2) key chroma effect and select a green close to the actor (or one nearest the "average" green of the background).


After applying KeyLight chroma key, we can see that there are lots of imperfections in the footage that didn't get captured by the key colour.

This takes away most of the background, but still leaves semi-transparent shadows in the background. So now, we need to "trace" our actor to completely isolate them from the background. We do this by selecting the video footage and then Layer - AutoTrace and tracing based on the alpha of the channel.


Maybe 70% is a bit much, but it worked for this particular bit of footage, so we're sticking with it - expect to use values 50%-70%.

A short while after applying the trace (it takes a few seconds to go through every frame in the video) a new layer appears with an outline around the main character. We can scrub through the footage to make sure that it captures just our main character on each frame of the video and that nothing is either truncated, or left in when it should be removed.



Next comes the clever bit - we need to expand the outline a few pixels away from the main character, to create a green "halo" around the character. So we select the traced layer and then Effect - Matte - Simple Choker. Setting the choke value to a negative value expands the matte away from the main character. We went with something in the region of -25


Depending on your version of After Effects, you may need to locate and click the "Toggle Switches/Modes" button to enable the "track matte" feature on the original footage. Set this track matte setting to the traced layer (immediately above it).


What's the point of doing all this? Well, what it means is we're only using a traced area of the original footage now, for the chroma key - we've got rid of most of the (wrinkly, unevenly lit) green background, and we're only actually keying the "green halo" around our main character. It basically means we need a much less "harsh" keying setting to completely remove all of the remaining green background (since there is so much less of it to begin with).


What we've done so far is create a "junk mask" around our main character, so remove most of the background, but there are still a few little areas around him that are not quite right. Now we just need to play with the KeyLight Chroma Key settings to get rid of any extra artifacts around the outside of our main actor.

We set the screen gain to 110 and the Matte Black setting to about 15 and all of the artifacts were gone.



One thing we found was that with a slightly more "aggressive" setting to remove the green, we started to lose opacity on our main character. In particular, the body started to disappear, a bit like Marty McFly at the Enchantment Under The Sea Ball. It was quickly corrected by adjusting the Matte White setting down from 100 to about 80.

The final step is to simply export the video footage to a format that supports RGB+ (or RGB+Alpha or 32-bit RGBA colour). For this project, we went with Quicktime as it created the smallest files (about 2.5Mb for our 5 second video). To ensure that the video plays back ok in media players that may not support alpha transparency, we double-checked that our composition settings had a bright green (#00FF00) background. Where alpha channels are supported, the video should play back with a transparent background. Where they are not, a flat, matte, solid green background should appear.



Gone are those nasty greenscreen wrinkles and badly-lit backgrounds! Our video footage is now "game ready".


With our footage now "game-ready" all we need to do is drop it into a game engine that supports alpha videos (Flash has been able to do this since about version 7 or 8) and place the appropriate background bitmap behind it.


The absolute worst case scenario would be to use Flash or Unity (or some other gaming engine) to chroma key out the green at runtime, now that we've got it a nice, solid, uniform bright green throughout the entire video...




Tuesday, 4 August 2015

Windows 10 Upgrade - did I miss something?

I'd read online about being able to install the latest Windows 10 upgrade as either a "proper upgrade" to Windows 8.1 or being able to install it on a new partition, as a fresh install.

As I clicked around this morning, looking for the "fresh install" option, I set the wheels in motion and suddenly found myself facing a black screen with the dreaded "do not turn off your PC" message - Windows was upgrading, whether I liked it or not!

It was a scary couple of hours (it really does take that long) despite Microsoft reassuring me that everything was ok, and I should "sit back and relax".


And when it was finished, it took a scary-long time before the entirely black screen flashed into life with the new Windows 10 logo


A few more reboots, and a few heart-in-mouth moments, while the screen remained absolutely black with no sign of activity for 5-10 minutes at a time, and eventually, Windows 10 was installed.

And the difference?

Well, none really. The task bar looks a little bit shit. It's now black. And semi-transparent. Like the colour scheme Google tried out for their G+ homepage about four years ago and gave up on. And the long-awaited return of the start menu is nothing to write home about - it's like the metro screen, but small, and squashed up into about a quarter of the screen estate.



Even if you make the start menu full screen - to make it more like the Windows 8.1 metro screen (which I've actually go to quite like in recent months) - it just doesn't quite "feel right".


I had to spend ages re-arranging my icons back into the groups I had them on my metro screen (the upgrade had them jumbled about all over the place). And now they scroll vertically instead of horizontally. What an amazing improvement on the Metro screen.... big deal.

Other than a black task bar, and a not-even-as-good-as-Windows-8.1 style metro screen, nothing else looks that much different.

Of course, it performs differently.
As in, it's slow.
Painfully slow.

The amazing new web browser, Edge, has "draw on webpages" as a big selling point. But when you do, it simply creates a bitmap screen grab, super-imposes your text/scribbles as a transparent bitmap, and saves a local copy to your hard drive. The screen grabs are even saved as nasty 80% jpegs, complete with weird-coloured artifacts around text, making it nasty to read back.

After being impressed, comparing IE11 to (the hateful) Google Chrome, I was really looking forward to the new web browser from MS. It's not terrible. It's just not as good as I'd hoped. The extra functionality feels like it would have been cutting edge in the first release of Windows 98, and everything just feels a bit slow and sluggish.

Windows 10 isn't the revolutionary new OS their TV adverts suggest. It's not even an improvement on Windows 8.1. Just as Windows 3.11 was a slight improvement on Windows 3.1, back in the day, I think I'd have referred to this latest "upgrade" as an "update" and called it Windows 8.11

Maybe after some prolonged use, the improvements to the OS will become obvious. But right now, I just don't see them. If you're happy with Windows 7, stick with it. If you hate Windows 8 (as a lot of people did) then this upgrade isn't the magic bullet to make everything cool again. It just makes your PC feel a bit unfamiliar for a while, as you learn where they've tucked away all the settings and apps you normally use, this time!

Sunday, 2 August 2015

Not all zombies are scary

In fact, looking over the video footage from last weekend's quick-and-dirty video shoot, it would seem that not everyone was taking it entirely seriously at all times either!



Thursday, 30 July 2015

Junk CNC controller from flatbed scanner and dvd rom drive - schematics and pcb layout

Earlier last week we put together a CNC from some left over junk. It went together surprisingly easily - since both the flatbed scanner and DVD rom drive had 4-wire (bipolar) stepper motors, all we really needed was to interface with an L293D h-bridge chip to get them moving.

It's taken a little while to get the time to draw up a "proper" schematic and PCB for the controller board, but - after a few requests in recent days - here they are:





(this is a press-n-peel ready version of the controller board; although when transferred the lettering will appear the wrong way around, this tells you that you got it right!)

The PCB is not a perfect final version- as can be seen from the massive number of wire jumpers and zero ohm resistors. Both are essentially the same thing, but 0R resistors are simply "scars" from many, quick changes the design - implemented after the schematic and PCB we originally drawn up - there's nothing like iterative prototyping!

This is how the board looks when it's all made up and plugged in:


(we've created a little "daughter board" with some jog buttons on it, to avoid having to keep poking wires into holes to manually position the xy bed!)


We've already written a little bit about sourcing cheap stepper motors and how to investigate the type of motor and how to drive them, so there's nothing really new about that here. What is slightly different is the way we're handling incoming serial data.

Since we don't want to stop the machine from moving while data is being received, we've re-created the kind of thing you might find in an Arduino serial library (though minus the blocking functions for reading the serial buffer). It's basically an interrupt-driven circular buffer and it works like this;

  • We're using the hardware UART for receiving serial data.
  • We set up a high priority interrupt to fire every time a character is received in via the serial port.
  • The character is added to a buffer (a 34 character byte array)
  • If the byte received has the value zero, set a flag to say "we've had a new message".
  • In the main code, periodically check the data received flag and parse the new message as necessary


Keeping track of positions in the buffer is on the only really tricky thing here.
We need to use two "pointers" to track two position in the array - the first pointer is the place where we want to add the next character that arrives via serial. The second pointer is the place from where we should start reading data when we want to pull the message back from the buffer



  • Whenever a character is received over serial, the interrupt fires which does a few things;
  • Writes the received character/byte into the circular buffer
  • Moves the "write pointer" along one place
  • If the received byte is zero, sets the "message received" flag



When the "message received" flag is set, in our main program code (not in the interrupt - it's important to keep that code as tight as possible) we read the data back out from the buffer/byte array. This approach means that we're not "locking" the serial buffer while we're reading from it - in fact, it's quite possible that we can be reading the serial buffer while appending data to the end of it (via the interrupt routine) at the same time!


When we have read back a zero value byte from the buffer, we know we've reached the end of the message. If we reach the end of the buffer and both the "reading" and "writing" pointers are looking at the same space, we know that the serial buffer is empty so we clear the "message received" flag.

If the either pointer exceeds the size of our buffer, it is re-set to zero - effectively "wrapping round" to the start of the array again (and thus creating a "circular" buffer).


Our buffer size is 34 characters so, of course, if we stream in more than 34 bytes without a zero value (to trigger the "read back" routine in the main code and update the read pointer) there's a good chance that the write pointer will "overtake" the read pointer. When this happens, the data read back from the buffer will be truncated. So it's important to keep serial messages down to less than 34 characters.

We settled on 34 as this allows us to stream an entire message to the 16x2 character display (32 characters) if necessary, and still have a couple of bytes over, in the buffer. Most of our serial messages are much shorter than this (a couple of bytes at most).

Because we're using zero as an end of message marker, it means that we need to use a different method to send actual numerical data (since the numerical value zero might actually be a valid part of that data). We don't want to send the legitimate numerical value zero over serial and have our controller interpret it as an end of message marker! For this reason, we're converting our numerical values into hex, and sending them as ASCII characters.

So to send the value 255, for example, we send the character sequence "FF"
Similarly, to send the value 0, we send the character sequence "00".

So to send a byte value zero, we actually send two bytes, each of value 48 (the number zero is 48 in ascii). At the receiving end we convert the ascii characters into 0-9 or A-F, assign them their correct value (so the character "1" has the value 1, "A" has the value ten and so on) and apply to either the top or bottom "nibble" (4 bits) of the value transmitted, to create the actual numerical value originally sent.

This ensures that all numerical data has the value 48-59 or 65-70, and so we can still use our zero value to indicate the end of a serial message.

Define CONFIG1H = 0x0c
Define CONFIG2L = 0x18
Define CONFIG2H = 0x1e
Define CONFIG3L = 0x00
Define CONFIG3H = 0x01
Define CONFIG4L = 0x80
Define CONFIG4H = 0x00
Define CONFIG5L = 0x0f
Define CONFIG5H = 0xc0
Define CONFIG6L = 0x0f
Define CONFIG6H = 0xe0
Define CONFIG7L = 0x0f
Define CONFIG7H = 0x40

Define CLOCK_FREQUENCY = 20
AllDigital

declarations:
     Dim state As Byte
     Dim serial_read_pos As Byte
     Dim serial_place_pos As Byte
     Dim serial_buffer(35) As Byte
     Dim serial_has_data As Bit
     Dim serial_byte As Byte
     Dim serial_state As Byte
     Dim get_more_data As Bit
     
     Dim lcd_char_count As Byte
     
     Dim target_x As Long
     Dim target_y As Long
     Dim current_x As Long
     Dim current_y As Long
     Dim step_difference_x As Long
     Dim step_difference_y As Long
     Dim step_delay_x As Byte
     Dim step_delay_y As Byte
     Dim tmp_long As Long
     Dim b_long As Long
     
     Dim x_motor_state As Byte
     Dim y_motor_state As Byte
     Dim home_at_start As Bit
     Dim is_moving As Bit
     Dim is_jogging As Bit
     Dim motor_dir_y As Bit
     Dim motor_dir_x As Bit

     Dim servo_dir As Bit
     Dim servo_counter As Byte
     
     Const state_default = 0
     Const state_home = 1
     Const serial_first_character = 0
     Const serial_lcd_msg = 1
     Const serial_get_position = 2
     Const serial_vac_pen = 3
     Const serial_servo = 4
     
     Define LCD_BITS = 4
     Define LCD_DREG = PORTD
     Define LCD_DBIT = 4
     Define LCD_RSREG = PORTD
     Define LCD_RSBIT = 2
     Define LCD_EREG = PORTD
     Define LCD_EBIT = 3
     Define LCD_RWREG = PORTD
     Define LCD_RWBIT = 1
          
     Dim i As Byte
     Dim j As Byte
     Dim k As Byte
     
symbols:
     Symbol x_motor_enable = PORTA.2
     Symbol x_coil_a_1 = PORTE.0
     Symbol x_coil_a_2 = PORTA.5
     Symbol x_coil_b_1 = PORTA.3
     Symbol x_coil_b_2 = PORTA.4
     
     Symbol y_motor_enable = RE.1
     Symbol y_coil_a_1 = PORTC.2
     Symbol y_coil_a_2 = PORTC.1
     Symbol y_coil_b_1 = PORTE.2
     Symbol y_coil_b_2 = PORTC.0
     
     Symbol tx = PORTC.6
     Symbol rx = PORTC.7
          
     Symbol limit_home_x = PORTB.6
     Symbol limit_home_y = PORTB.5
     Symbol limit_extent_x = PORTC.5
     Symbol limit_extent_y = PORTC.4
     
     Symbol jog_left = PORTB.0
     Symbol jog_right = PORTB.1
     Symbol jog_up = PORTB.2
     Symbol jog_down = PORTB.3
     Symbol jog_slow = PORTB.4
     
     Symbol servo_pin = PORTD.0
     Symbol led_pin = PORTA.0
     Symbol vac_pen_relay = PORTA.1
     
init:
     ConfigPin PORTA = Output
     ConfigPin PORTB = Input
     ConfigPin PORTC = Output
     ConfigPin PORTD = Output
     ConfigPin PORTE = Output

     ConfigPin tx = Output
     ConfigPin rx = Input
     ConfigPin servo_pin = Output
     ConfigPin led_pin = Output
     ConfigPin vac_pen_relay = Output
     ConfigPin limit_extent_x = Input
     ConfigPin limit_extent_y = Input
     
     '------------------------------
     'first make sure motors are off
     '------------------------------
     Gosub stop_x_motor
     Gosub stop_y_motor
     is_moving = 0
     is_jogging = 0
               
     '------------------------------------
     'lift the servo up (pen is retracted)
     '------------------------------------
     servo_dir = 0
     servo_counter = 0
     
     '-----------------------------
     'start with the vacuum pen off
     '-----------------------------
     Low vac_pen_relay
     
     '---------------------
     'start the UART/serial
     '---------------------
     Hseropen 9600 '115200
     'create an interrupt on serial input
     PIE1.RCIE = 1
     IPR1.RCIP = 1 'rx is high priority
     
     'set up the serial "cirular buffer"
     serial_read_pos = 0
     serial_place_pos = 0
     For i = 0 To 34
          serial_buffer(i) = 0
     Next i
     
     '-------------------------
     'set up the stepper motors
     '-------------------------
     x_motor_state = 0
     y_motor_state = 0
     home_at_start = 0
     
     target_x = 0
     target_y = 0
     current_x = 0
     current_y = 0
     
     'to use RC4 and RC5 we have to actively disable the USB
     UCON.3 = 0
     UCFG.3 = 1
     'for RA2 we need to disconnect the vref comparitor
     CVRCON.CVROE = 0
     CVRCON.CVREN = 0
     CVRCON.CVRSS = 0
          
     '--------------
     'set up the LCD
     '--------------
     High led_pin
     Lcdinit 0
     Lcdcmdout LcdClear
     Lcdout " El Cheapo "
     Lcdcmdout LcdLine2Home
     WaitMs 1000
     Low led_pin
     Lcdout " Pick and Place "
     Hserout "El Cheapo Ready", CrLf
          
     '------------------------
     'enable pull-ups on PORTB
     '------------------------
     INTCON2.RBPU = 0
     Low led_pin
     
     '---------------------------
     'set up the state machine(s)
     '---------------------------
     state = state_home
     If home_at_start = 0 Then state = state_default
     serial_state = serial_first_character
     Gosub set_servo
     
     '---------------------------------------
     'enable global and peripheral interrupts
     '---------------------------------------
     INTCON.GIE = 1
     INTCON.PEIE = 1
     
loop:
     Select Case state
     
          '--------------
          Case state_home
          '--------------
          target_x = 0
          target_y = 0
          current_x = 0
          current_y = 0
          
          If limit_home_x <> 0 Then current_x = 1
          If limit_home_y <> 0 Then current_y = 1
          
          If current_x = 0 And current_y = 0 Then
               'send a message back to the host to tell them we're at home
               Hserout "HOME", CrLf
               'we're at the home position, so go to default state
               state = state_default
          Else
               'move home as quickly as possible
               step_delay_x = 2
               step_delay_y = 2
          Endif
          
          '-----------------
          Case state_default
          '-----------------
          'here we're waiting for the serial buffer to be flushed
          '(when a zero byte is sent to indicate end of line)
          'and when it is, parse the data out of it (because it's sent
          'via ascii)
          
          If serial_has_data = 1 Then
               'parse the data from out of the buffer
               serial_state = serial_first_character
               get_more_data = 1
               
               While get_more_data = 1
                    serial_byte = serial_buffer(serial_read_pos)
                    serial_read_pos = serial_read_pos + 1
                    If serial_read_pos > 34 Then serial_read_pos = 0
                    
                    If serial_byte = 0 Then
                         'this is the message termination byte
                         If serial_read_pos = serial_place_pos Then
                              'the last character received was this termination character
                              'so we've emptied the serial buffer
                              serial_has_data = 0
                         Else
                              'we've received more data following the termination character
                              'so once we've finished with this message, leave the has_data
                              'flag set and we'll parse the next lot of data
                         Endif
                         get_more_data = 0
                    Else
                         'read the data from the buffer, one byte at a time
                         Select Case serial_state
                              '--------------------------
                              Case serial_first_character
                              '--------------------------
                              'we've just had our first serial character: this should be the
                              'message type marker
                              If serial_byte = "M" Then
                                   'this is a string message
                                   serial_state = serial_lcd_msg
                                   lcd_char_count = 0
                                   Lcdcmdout LcdClear
                              Endif
                              If serial_byte = "P" Then
                                   'this is positional data, sent as two lots of
                                   'four character hex values (16-bit)
                                   lcd_char_count = 0 'use this to count the serial bytes in
                                   tmp_long = 0
                                   is_moving = 1
                                   serial_state = serial_get_position
                              Endif
                              If serial_byte = "V" Then
                                   'this is the vacuum pen command, next byte
                                   'will tell us whether to turn it on or off
                                   serial_state = serial_vac_pen
                              Endif
                              If serial_byte = "S" Then
                                   'this is the servo up/down command
                                   serial_state = serial_servo
                              Endif
                              
                              '------------------
                              Case serial_lcd_msg
                              '------------------
                              'this is a character to display on the LCD
                              Lcdout serial_byte
                              lcd_char_count = lcd_char_count + 1
                              If lcd_char_count = 16 Then Lcdcmdout LcdLine2Home
                              If lcd_char_count = 32 Then
                                   Lcdcmdout LcdHome
                                   lcd_char_count = 0
                              Endif
                              
                              '-----------------------
                              Case serial_get_position
                              '-----------------------
                              If lcd_char_count < 4 Then
                                   'this is another hex-based character for the x position
                                   Gosub hex_to_value
                                   tmp_long = ShiftLeft(tmp_long, 4)
                                   tmp_long = tmp_long Or b_long
                                   If lcd_char_count = 3 Then
                                        target_x = tmp_long
                                        tmp_long = 0
                                   Endif
                              Endif
                              If lcd_char_count >= 4 And lcd_char_count < 8 Then
                                   Gosub hex_to_value
                                   tmp_long = ShiftLeft(tmp_long, 4)
                                   tmp_long = tmp_long Or b_long
                                   If lcd_char_count = 7 Then
                                        target_y = tmp_long
                                        'draw the received values to the LCD
                                        Gosub draw_coords_target
                                   Endif
                              Endif
                              lcd_char_count = lcd_char_count + 1
                              
                              '------------------
                              Case serial_vac_pen
                              '------------------
                              If serial_byte = 49 Or serial_byte = 1 Then
                                   'this is the "on" command (ascii value one)
                                   High vac_pen_relay
                                   Lcdcmdout LcdHome
                                   Lcdout "Vac pen on "
                              Else
                                   Low vac_pen_relay
                                   Lcdcmdout LcdHome
                                   Lcdout "Vac pen off "
                              Endif
                              
                              '----------------
                              Case serial_servo
                              '----------------
                              If serial_byte = 49 Or serial_byte = 1 Then
                                   'this is the "servo down" command (ascii value one)
                                   servo_dir = 1
                                   Lcdcmdout LcdHome
                                   Lcdout "Pen down "
                              Else
                                   'if you've not specifically put the pen down, lift it up
                                   servo_dir = 0
                                   Lcdcmdout LcdHome
                                   Lcdout "Pen up "
                              Endif
                              Gosub set_servo
                         EndSelect
                         
                    Endif
               Wend
          Endif
          
          'as well as waiting for serial data, we can always respond to button presses
          If jog_left = 0 And current_x > 0 Then
               is_jogging = 1
               motor_dir_x = 0
               target_x = current_x - 1
          Endif

          If jog_right = 0 Then
               is_jogging = 1
               motor_dir_x = 1
               target_x = current_x + 1
          Endif
          
          If jog_up = 0 And current_y > 0 Then
               is_jogging = 1
               motor_dir_y = 0
               target_y = current_y - 1
          Endif

          If jog_down = 0 Then
               is_jogging = 1
               motor_dir_y = 1
               target_y = current_y + 1
          Endif

     EndSelect
     
     '------------------------------------------------------------------------
     'if the motors need to be turned, move them in steps.
     'This takes a bit of thinking about: we set our target x and y values
     'as "number of HALF-steps" from current position. So if we want to target
     'a point 5 steps away, we set the target_x to 10.
     'Every half step, reduce the current_x so if we make a whole step, we
     'need to reduce current_x by 2 (not by one)
     '------------------------------------------------------------------------
          
     If target_x < current_x Then
          'we need to step closer to our target in the X axis
          'provided the limit switch hasn't been hit
          If limit_home_x = 0 Then
               'we've hit the home limit switch, so set current and target x to zero
               current_x = 0
               target_x = 0
               is_moving = 0
               Hserout "LIMIT X HOME", CrLf
               Lcdcmdout LcdHome
               Lcdout " "
               Lcdcmdout LcdHome
               Lcdout "Home X reached "
          Else
               motor_dir_x = 0
               step_difference_x = current_x - target_x
               Gosub set_x_stepper_speed
               Gosub move_x_stepper
               current_x = current_x - 1
          Endif
     Endif
     
     If target_x > current_x Then
          'we need to step closer to our target in the X axis
          
          If limit_extent_x = 0 Then
          'k = 1
          'If k = 2 Then
               target_x = current_x
               is_moving = 0
               Hserout "LIMIT X EXTENT", CrLf
               Lcdcmdout LcdHome
               Lcdout " "
               Lcdcmdout LcdHome
               Lcdout "Limit X reached "
          Else
               motor_dir_x = 1
               step_difference_x = target_x - current_x
               Gosub set_x_stepper_speed
               Gosub move_x_stepper
               current_x = current_x + 1
          Endif
     Endif
     
     If target_y < current_y Then
          'we need to step closer to our target in the Y axis
          'provided the limit switch hasn't been hit
          If limit_home_y = 0 Then
               'we've hit the home limit switch, so set current and target x to zero
               current_y = 0
               target_y = 0
               Hserout "LIMIT Y HOME", CrLf
               Lcdcmdout LcdHome
               Lcdout " "
               Lcdcmdout LcdHome
               Lcdout "Home Y reached "
          Else
               motor_dir_y = 0
               step_difference_y = current_y - target_y
               Gosub set_y_stepper_speed
               Gosub move_y_stepper
               current_y = current_y - 1
          Endif
     Endif
     
     If target_y > current_y Then
          'we need to step closer to our target in the Y axis
          
          If limit_extent_y = 0 Then
          'k = 1
          'If k = 2 Then
               target_y = current_y
               Hserout "LIMIT Y EXTENT", CrLf
               Lcdcmdout LcdHome
               Lcdout " "
               Lcdcmdout LcdHome
               Lcdout "Limit Y reached "
          Else
               motor_dir_y = 1
               step_difference_y = target_y - current_y
               Gosub set_y_stepper_speed
               Gosub move_y_stepper
               current_y = current_y + 1
          Endif
     Endif
     
     '--------------------------------------------------------------
     'this just helps stop the motors getting too hot so they're not
     'always powered, when they are stationary
     '--------------------------------------------------------------
     If target_x = current_x And jog_left = 1 And jog_right = 1 Then Gosub stop_x_motor
     If target_y = current_y And jog_up = 1 And jog_down = 1 Then Gosub stop_y_motor
          
     '----------------------------------------------------------------
     'if we've just stopped moving after reaching a target point, send
     'a message back via serial to the host, to say we've arrived
     '----------------------------------------------------------------
     If is_moving = 1 Then
          If target_x = current_x And target_y = current_y Then
               is_moving = 0
               Hserout "READY", CrLf
               Gosub draw_coords_current
          Endif
     Endif
     
     '-----------------------------------------------------------------------
     'if we've just released one of the jog buttons, write the current XY out
     '-----------------------------------------------------------------------
     If is_jogging = 1 Then
          If jog_left = 1 And jog_right = 1 And jog_up = 1 And jog_down = 1 Then
               is_jogging = 0
               Gosub draw_coords_current
          Endif
     Endif
     
Goto loop
End

On Low Interrupt

Resume

On High Interrupt
     If PIR1.RCIF = 1 Then
          'we've just received a byte from the serial port
          High led_pin
          serial_byte = RCREG
          serial_buffer(serial_place_pos) = serial_byte
          serial_place_pos = serial_place_pos + 1
          If serial_place_pos > 34 Then serial_place_pos = 0
          If serial_byte = 0 Then
               'set the flag to say we've a full message in the serial buffer
               serial_has_data = 1
               Low led_pin
          Endif
     Endif
Resume

move_x_stepper:

     High x_motor_enable
     If motor_dir_x = 1 Then
          x_motor_state = x_motor_state + 1
     Else
          If x_motor_state = 0 Then
               x_motor_state = 3
          Else
               x_motor_state = x_motor_state - 1
          Endif
     Endif

     If x_motor_state > 3 Then x_motor_state = 0
     
     'once each step, the polarity of one coil should be reversed
     Select Case x_motor_state

          Case 0
          Low x_coil_a_2
          Low x_coil_b_2
          High x_coil_b_1
          High x_coil_a_1
                              
          Case 1
          Low x_coil_a_1
          Low x_coil_b_2
          High x_coil_b_1
          High x_coil_a_2
                                   
          Case 2
          Low x_coil_a_1
          Low x_coil_b_1
          High x_coil_b_2
          High x_coil_a_2
          
          Case 3
          Low x_coil_a_2
          Low x_coil_b_1
          High x_coil_b_2
          High x_coil_a_1
                         
     EndSelect
     WaitMs step_delay_x
          
Return

stop_x_motor:
     Low x_coil_a_1
     Low x_coil_a_2
     Low x_coil_b_1
     Low x_coil_b_2
     Low x_motor_enable
Return

move_y_stepper:

     High y_motor_enable
     If motor_dir_y = 1 Then
          y_motor_state = y_motor_state + 1
     Else
          If y_motor_state = 0 Then
               y_motor_state = 3
          Else
               y_motor_state = y_motor_state - 1
          Endif
     Endif

     If y_motor_state > 3 Then y_motor_state = 0
     
     'once each step, the polarity of one coil should be reversed
     
     Select Case y_motor_state

          Case 0
          Low y_coil_a_2
          Low y_coil_b_2
          High y_coil_b_1
          High y_coil_a_1
                              
          Case 1
          Low y_coil_a_1
          Low y_coil_b_2
          High y_coil_b_1
          High y_coil_a_2
                                   
          Case 2
          Low y_coil_a_1
          Low y_coil_b_1
          High y_coil_b_2
          High y_coil_a_2
          
          Case 3
          Low y_coil_a_2
          Low y_coil_b_1
          High y_coil_b_2
          High y_coil_a_1
                         
     EndSelect
     WaitMs step_delay_y
          
Return

stop_y_motor:
     Low y_coil_a_1
     Low y_coil_a_2
     Low y_coil_b_1
     Low y_coil_b_2
     Low y_motor_enable
Return

hex_to_value:
     'ascii zero is 48, ascii A is 65
     If serial_byte >= 65 Then
          'if ascii A is 65, we want A=10, B=11 etc.
          b_long = serial_byte - 55
     Else
          b_long = serial_byte - 48
     Endif
Return

set_x_stepper_speed:
     step_delay_x = 2
     If step_difference_x < 20 Then step_delay_x = 8
     If step_difference_x < 10 Then step_delay_x = 14
     If step_difference_x < 4 Then step_delay_x = 25
     If jog_left = 0 Or jog_right = 0 Then step_delay_x = 2
     If jog_slow = 0 Then step_delay_x = 50
Return

set_y_stepper_speed:
     step_delay_y = 2
     If step_difference_y < 20 Then step_delay_y = 8
     If step_difference_y < 10 Then step_delay_y = 14
     If step_difference_y < 4 Then step_delay_y = 25
     If jog_up = 0 Or jog_down = 0 Then step_delay_y = 2
     If jog_slow = 0 Then step_delay_y = 50
Return

draw_coords_target:
     Lcdcmdout LcdClear
     Lcdout "Target position:"
     Lcdcmdout LcdLine2Home
     Lcdout "X:", #target_x, " "
     Lcdout "Y:", #target_y
Return
                                                                           
draw_coords_current:
     Lcdcmdout LcdClear
     Lcdout "Current position"
     Lcdcmdout LcdLine2Home
     Lcdout "X:", #current_x, " "
     Lcdout "Y:", #current_y
Return

set_servo:
     'this is a quick and dirty blocking function
     High led_pin
     For j = 0 To 50
          If servo_dir = 0 Then
               ServoOut servo_pin, 150
          Else
               ServoOut servo_pin, 200
          Endif
          WaitMs 20
     Next j
     Low led_pin
Return

Our junk CNC controller has a few ways of driving it:
  • Jog buttons to move the xy axis up/down/left/right
  • Send serial data to give new destination co-ordinates; the CNC will move to the new target position as soon as possible
  • Serial command to switch on a relay (which is connected to one side of the 240V mains supply of a vacuum pump operated pen, for picking up SMT components)
  • Serial command to move a servo (used to lift the pen up or put it down)

Whenever the machine has received a new positional instruction and has completed the move to that location, a "ready" message is sent back to the host over serial, so that it knows the CNC head has arrived at its destination - this is preferable to using nasty timing loops to determine when the next command should be sent, from a long list of commands for the machine.

[video]

Unfortunately, even though it is highly accurate (to within 0.05mm) the flatbed scanner bed is running a little slow, even when the motor is spinning as quickly as possible (we have to have a 2ms delay between steps, otherwise the motor locks up). This is because of the multi-cog gearing before the belt. We tried driving the belt pulley directly from the motor, but it doesn't have enough torque - the gears not only slow the motor down, to allow for precise positioning while scanning, but also give it more torque than the motor can provide, if driving the belt directly.

So while, in principle, we're calling this a success (it can populate a pcb automatically from a list of co-ordinates) in practice it's actually quicker to place a dozen or so components by hand, using tweezers!

Wednesday, 29 July 2015

Last Night In Zombieville - video production

It's of no surprise to regular readers that we're currently putting together an electronic board game, which includes playing piece tracking and an app for PC, tablets and smartphones. The idea is that we can use the same hardware for multiple games, covering multiple genres, and using lots of different rule sets.

On of our more ambitious ideas is the game "Last Night in Zombieville" - an interactive movie-based board game. Like interactive movies of the early nineties, we'll have a number of clips which are played in sequence, depending on the actions of the players.

So this weekend, a few of us hopped over to meet up with fellow nerds in Berlin for three days greenscreen video filming.



Andrea plays one of the hero characters who has the possibility of being turned into a zombie, so had twice as much work to do as the others! Here he is, playing the hero, waving around a big scary (replica) gun.



Sarah has been a make-up artist on quite a few independent horror/zombie movies (as well as being quite a proficient children's face-painter for festivals and parties) and made a great job of turning Andrea into one of the living dead.


The blood and gore were not only completely edible - being made from little more than summer fruits and food colouring - but, this being Berlin, we also completely organic and ethically sourced!

Right now, we've half-a-hard drive of video footage that needs some serious post-production to get it ready for adding to our game engine. There's enough to keep even a few of us busy for quite a while yet....

Monday, 13 July 2015

Getting jiggy with it (see what I did there?)

Using a jig and a stencil for applying solder paste has been quite a revelation for churning through our PCBs for the electronic board game sections - far more so than getting a rudimentary pick-n-place device working.

After just a few hours this evening, we had soldered and assembled a chain of eighteen boards for testing.


And with just one dry solder joint across all the boards, it was very encouraging indeed, to see each one responding the placement (and removal) of a magnet above each of the hall sensors



There are a few things come up with work today that may take a little time to resolve, so it's likely to be the weekend before we can get to the workshop and rout out some mdf boards to embed these in. But already things are looking very encouraging indeed. Can't wait to get a few sections daisy-chained together and actually see one of our board game apps responding to the  hardware for real!