Hacker News new | past | comments | ask | show | jobs | submit login
How to blink a light in 10 different programming languages (mcconn.xyz)
94 points by mcconnma on Nov 7, 2015 | hide | past | favorite | 48 comments



You may "How to blink a light extremely inefficiently 10 different ways." ;-)

Seriously, not even one Asm example? If you have a PC that has a parallel port and can boot to DOS, you can stick a LED in series with a 100-ohm resistor across pins 2(+) and 18(-) and do this:

        mov dx, 378h
        xor ax, ax
    blinkloop:
        out dx, al
        mov cx, 36
        call delay
        not ax
        jmp blinkloop

    delay:
        hlt
        loop delay
        ret
I find it somewhat amazing that this program, in Assembly, is actually shorter than the 10 HLL programs in the article.


Here's something more appropriate (air coded, modulo my iPhone's spell corrector)

  section .text
  global _start

  _start:

  ; Write the ‘1’ to a file
  mov eax, 4       ; sys_write
  mov edx, 1       ; # of bytes to write
  mov ecx, ‘1’     ; Means light should be on
  mov ebx, file_open ; File descriptor
  mov eax, 4       ; Call to sys_write 
  int 0x80         ; Call kernel to write file
  
  ; Close file
  move eax, 6
  mov mov ebx, 
  int 0x80         ; Call kernel to close file
  
  ; Quit to OS
  move eax, 1      ; SYS_EXIT
  int 0x80         ; Call kernel to exit to OS
  
  section.data
  
  filename db ‘/sys/class/leds/beaglebone:green:usr0/brightness’,0
  len equ $-filename

  section .bss
  file_open resb 1
  state db ‘1’


That's not a fair comparison in any way. Your assembler program doesn't do half of the original code.


That's the point. It doesn't do all the work of the original code because it doesn't need to. You don't need a whole OS and several layers of abstraction just to blink an LED, not even a dozen instructions (and I notice now, since I was writing it off the top of my head, that the delay subroutine is only called once, so there's another two "wasted" instructions...)

I think it's very important that programmers are exposed to such "absolute minimum simplicity" solutions, as it helps greatly with understanding the essence of the problem and its solution - in this case it's nothing more than toggling a bit in an I/O port. Complexity can be added afterwards as needed, but not gratuitously.

I like how one of the other comments here and in the article mentions that there's no error handling in many of the examples, since it brings up another point about complexity: it can introduce opportunities for errors. If you're working with the GPIO directly, errors either can't occur, or they're hardware-level (e.g. shorted output) and nothing can really be done about it anyway unless the hardware has additional features for signaling such conditions. If you're going through the OS' filesystem, errors can occur at any one of the multiple layers for not-so-related reasons to the actual task. One should then ask the question of whether the code that's added to handle errors is actually doing something useful for the task, or serves no purpose beyond satisfying the other layers of complexity that introduce them.


Good points, and I agree about understanding minimal simplicity and you don't need an OS and several layers of abstractions to blink a light. The post wasn't explicit (I think it is now after updating), but the intent was to demonstrate the variety of languages available on Linux based platforms that have GPIO SysFS, so assembly was never considered. It is more focused on those environments in which these HLL do have a role/place.


userbinator's example does not conform to the API, which writes to a file (and on some platforms, uses best practices to write atomically and check for errors to boot). I took a stab at it elsewhere.


C#

https://dotnetfiddle.net/afyTvh

    using System;
    using System.IO;
    using System.Threading;
					
    public class Blink
    {
    	const string FilePath = @"/sys/class/leds/beaglebone\:green\:usr0/brightness";
    	const int BlinkDelay = 2000;
    	
    	public static void Main()
    	{		
    	    using (var writer = new StreamWriter(FilePath))
    	    {
    	 	for (var state = true; true; state = !state)
    		{
		    writer.Write((state ? 1 : 0).ToString("00"));
                    Thread.Sleep(2000);	
    		}
    	    }
    	}
    }
Reactive Extensions (C#)

https://dotnetfiddle.net/bpOF5e

    Observable
	.Repeat(Observable.Unit)
	.Select((index, _) => index % 2)
        .Select(string.Format({0:00})
	.Delay(TimeSpan.FromSeconds(2))
	.Aggregate(
	    new StreamWriter(@"/sys/class/leds/beaglebone\:green\:usr0/brightness"), 
	    writer => (writer, state) => writer.Write(state)
	)
	.Subscribe(writer => writer.Close());


Both your example and the Java one are not abstract enough (although the added string conversion is nice.) I was expecting more design patterns and layering for a truly enterprise-quality solution, like this:

https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...


This is my favorite comment :-) After years of programming in enterprise Java, I failed to truly make it enterprise worthy! I'm going to see if I can get an assembly example added per your other comment.


I am disappointed. I opened this article expecting a myriad of MOVs and JMPs, PEEKS and POKEs, &s and *s, <s and +s, DOs and GO TOs. These are all languages that borrow a lot from each other, and as such look mostly the same. We need some variety in here, some zing. Give some FORTRAN IV, some COBOL, some BASIC even, and it could be interesting, especially if you add in how to do it with raw io; sysfs is ugly and boring.


It wasn't clear in the original post but this was really meant for embedded Linux platforms that already have GPIO mapped via SysFS so assembly wasn't considered. I've updated the front matter of the post to reflect that. I don't know if Pascal is considered 'zing' these days, but I added that as an example. Thanks for your comment.


The bash example should have set -e or #!/bin/bash -e so that it will exit on error. That or the rest of the languages also shouldn't bother to check for errors. Also rather than invoking another shell use let and do let STATE=1-$STATE just like all the other programs. And why does bash need to have comments. Maybe it was a subtle joke that C doesn't get comments, but bash does :D in which case well played.

  #!/bin/bash -e
  STATE=1
  while : do
   echo $STATE > /sys/class/leds/beaglebone\:green\:usr0/brightness
   let STATE=1-$STATE
   sleep 2
  done
While all of the programs can be made smaller in size the bash script is probably the default way that the script would be tossed together. Bash has a ton of problems, but sadly between it and the standard unix commands you can get the job done too quickly and easily.


> invoking another shell

That wasn't a subshell - $(( )) is Arithmetic Expansion. (you may be thinking of $( ) for Command Substitution)

    $ echo $BASH_SUBSHELL $((BASH_SUBSHELL)) $(echo $BASH_SUBSHELL)
    0 0 1
Now, using the result of a boolean is a bit strange; I would have used your math with the modern expansion style:

    # the new style allows spaces
    STATE=$(( 1 - $STATE ))


Ah, thats great learned a new bash trick


Shorthand for $(( )) is $[ ]

In predicate contexts (which means return code in bash) like if, while or the left hand side of '&&', consider using (( )) - this has an exit code of 0 if the arithmetic expression is true (non-zero) and 1 otherwise. Use it like this:

    let x=3*3
    if (( x > 5 )); then
        echo greater than five
    else
        echo less than or equal to five
    end


With JavaScript (nodejs)

  var lightsOn = true;

  setInterval(blink, 2000);

  function blink() {
    var fs = require("fs");
	
    fs.writeFile("/sys/class/leds/beaglebone:green:usr0/brightness", lightsOn, function (err) {
      if (err) throw err;
    });
	
    lightsOn = lightsOn ? false : true;

  }


It makes no difference but for some reason I feel the urge to shorten the last line to:

lightsOn = !lightsOn;

I'll see myself out.


Every time I write that in JavaScript, I delete it and just use ! As well haha


Don't forget to turn off the light on your way out.

lightsOn = !lightsOn || lightsOn


Writing a boolean type to SysFS/GPIO from NodeJS doesn't quite work so I tweaked your example to following similar pattern of the other languages. Thanks.


I wanted to show the power of JavaScript types, and you got a type bug? What the irony. I'll probably report that as a bug in nodejs or an undocumented "feature".


And in Haskell (feel free to include this):

    import Control.Concurrent (threadDelay)                                         
    import System.IO

    main :: IO ()                                                                   
    main = withFile "/sys/class/leds/beaglebone:green:usr0/brightness" AppendMode go
      where put h s = hPutStrLn h s >> threadDelay 2000000                          
            go  h   = let m = put h "0" >> put h "1" >> m in m


As always, prefer prebuilt recursion scheme combinators instead of making your own. The following, using `forever`, is IMO much more readable because you don't have to mentally resolve the recursion:

  main :: IO ()
  main = withFile ledFile AppendMode $ \h -> do
      let put s = hPutStrLn h s >> threadDelay 2000000                          
      forever $ do
         put "0" 
         put "1"
    where
      ledFile = "/sys/class/leds/beaglebone:green:usr0/brightness"


Why:

    go  h = let m = put h "0" >> put h "1" >> m in m
Rather than:

    go  h = put h "0" >> put h "1"
Oh, recursion...


Thanks, any thoughts on why I get this?

$ ghc -o blink blink.hs [1 of 1] Compiling Main ( blink.hs, blink.o )

blink.hs:7:22: parse error on input `='


Not sure; I copy-pasted it back from hackernews and it works fine for me.


Ruby example is not very idiomatic. Give File.open a block and it'll handle closing it. Instead of flushing the file every time, you could just set file.sync = true once.

Also, if one was going to use the ensure block, use nil? not '== nil'


Similar quirks in the Python example. Opening the file should ideally be done with a "with" statement to close the file automatically at the end. But in the Ruby example the author at least closed the file instead of doing nothing as in the Python example. :)


Dart:

  import 'dart:async';
  import 'dart:io';
  main() {
    var file = new File('/sys/class/leds/beaglebone:green:usr0/brightness');
    var state = 0;
    new Timer.periodic(new Duration(seconds: 2), (_) {
      state = 1 - state;
      file.writeAsStringSync('0$state', flush: true);
    });
  }
While it's not the most concise version, it's certainly very readable. Everyone can clearly see that the duration is 2 seconds and that that boolean flag is for flushing, because that's exactly what the code says.


Yes, I like it! Only thing I don't understand is '0$state'.


That's string interpolation. That line writes either "00" or "01". It's essentially the same as `'0' + state`.

  'x: $x, y: $y'
is a lot easier to type than:

  'x: ' + x + ', y: ' + y
I actually had to triple check that line. Concatenation is very error-prone. I often mess it up.

String interpolation is a fairly popular feature nowadays:

https://en.wikipedia.org/wiki/String_interpolation


A bit more compact go-version:

    package main

    import (
    	"log"
    	"os"
    	"strconv"
    	"time"
    )

    func main() {
    	f, err := os.OpenFile("/sys/class/leds/beaglebone:green:usr0/brightness", os.O_RDWR, 0666)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer f.Close()

    	state := 1
    	for i := 0; i < 3*2; i++ {
    		_, err = f.WriteString(strconv.Itoa(state))
    		if err != nil {
    			log.Fatal(err)
    		}
    		time.Sleep(2 * time.Second)
    		state = 1 - state
    	}
    }


Thanks, yeah, I went crazy and implemented it via go channels, which isn't a fair comparison. I updated with a slightly tweaked version of what you provided.


The Ruby example doesn't handle exceptions with the same granularity as Java example and is therefore much shorter. Please do not compare the examples.


Bonescript is pretty simple as well:

    // npm install bonescript
    var b = require('bonescript');
    b.pinMode('P8_13', b.OUTPUT);
    b.digitalWrite('P8_13', 1);
Pretty sure Bonescript is just doing the filesystem stuff under the hood, though doing memory mapping would be way nicer.


Alternatively one could use the Arduino API on the beaglebone with https://github.com/prpplague/Userspace-Arduino

It would make the whole thing less scary for beginners and there are tons of examples and documentation out there.


that is interesting, not sure if it is any easier than compiling some of the examples I provide, but it does open Linux platforms to those that know wiring, thanks.


I find using name-spaces in imports makes the code a hundred times easier to reason about.


First example seems like it would want >, not <


you're right,fixed.


How to open a file in 10 different programming languages.


It is a little more than that as you also need to write alternating state to the file, but you are right, and that is the point of the post. You can read/write embedded sensors/actuators in whatever language you are comfortable by simply reading/writing from the Linux file system.


.. on a correctly set up beaglebone, not other systems. It would be much better if this were in the title.

On most embedded systems and FPGAs faffing with the toolchain and hunting in the documentation is 99% of the effort of getting an LED blinking, that's why it's used as the embedded 'hello world'.


good point, I updated the front matter of the post to make this more explicit.


...if your Linux base port has the chip-specific GPIO driver wired up correctly and is enabled in the kernel along with the sysfs interface.


Yeah this exactly, I was disappointed reading the article.


On Linux you're allowed to mmap gpio as well if I'm not mistaken.


Yep, mmap is definitely a possibility here, though finding out where everything is is a bit of a pain (Derek Molloy's device tree charts really help here: http://derekmolloy.ie/beaglebone/beaglebone-gpio-programming...).

An example of LED toggling via mmap on the Beaglebone is here: https://github.com/MarkAYoder/BeagleBoard-exercises/blob/mas... (and the associated tutorial: http://elinux.org/EBC_Exercise_11b_gpio_via_mmap)




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: