diff --git a/tests/pyb/accel.py b/tests/pyb/accel.py
new file mode 100644
index 0000000000000000000000000000000000000000..13f53b33c07ea4ee989828bac599992e420ce437
--- /dev/null
+++ b/tests/pyb/accel.py
@@ -0,0 +1,7 @@
+accel = pyb.Accel()
+print(accel)
+accel.x()
+accel.y()
+accel.z()
+accel.tilt()
+accel.filtered_xyz()
diff --git a/tests/pyb/accel.py.exp b/tests/pyb/accel.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..28070be1771bba44b7b33d0b063b4f9f7bd88b5a
--- /dev/null
+++ b/tests/pyb/accel.py.exp
@@ -0,0 +1 @@
+<Accel>
diff --git a/tests/pyb/adc.py b/tests/pyb/adc.py
new file mode 100644
index 0000000000000000000000000000000000000000..7bed54e9f807489f56e2b3fe1fbe57cca5fb1c34
--- /dev/null
+++ b/tests/pyb/adc.py
@@ -0,0 +1,10 @@
+from pyb import ADC
+from pyb import Pin
+
+adc = ADC('X22')
+print(adc)
+
+adc.read()
+
+buf = bytearray(100)
+adc.read_timed(buf, 500)
diff --git a/tests/pyb/adc.py.exp b/tests/pyb/adc.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..bbc6af737943e66e006868d64166fa638b3cfa18
--- /dev/null
+++ b/tests/pyb/adc.py.exp
@@ -0,0 +1 @@
+<ADC on X22 channel=13>
diff --git a/tests/pyb/dac.py b/tests/pyb/dac.py
new file mode 100644
index 0000000000000000000000000000000000000000..61ab7bd6e6b591e4fd6b4117bee99913d004ec61
--- /dev/null
+++ b/tests/pyb/dac.py
@@ -0,0 +1,8 @@
+dac = pyb.DAC(1)
+print(dac)
+dac.noise(100)
+dac.triangle(100)
+dac.write(0)
+dac.write_timed(bytearray(10), 100, mode=pyb.DAC.NORMAL)
+pyb.delay(20)
+dac.write(0)
diff --git a/tests/pyb/dac.py.exp b/tests/pyb/dac.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..ae245f2e6107846511d0aaa8e3a9641a8961415a
--- /dev/null
+++ b/tests/pyb/dac.py.exp
@@ -0,0 +1 @@
+<DAC>
diff --git a/tests/pyb/extint.py b/tests/pyb/extint.py
new file mode 100644
index 0000000000000000000000000000000000000000..20648995bcb94891b01852a974bbc3dad86404ed
--- /dev/null
+++ b/tests/pyb/extint.py
@@ -0,0 +1,6 @@
+ext = pyb.ExtInt('X1', pyb.ExtInt.IRQ_RISING, pyb.Pin.PULL_DOWN, lambda l:print('line:', l))
+ext.disable()
+ext.enable()
+print(ext.line())
+ext.swint()
+ext.disable()
diff --git a/tests/pyb/extint.py.exp b/tests/pyb/extint.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..28019d75c6c8dcec80a85dcfdf030277c8251e86
--- /dev/null
+++ b/tests/pyb/extint.py.exp
@@ -0,0 +1,2 @@
+0
+line: 0
diff --git a/tests/pyb/i2c.py b/tests/pyb/i2c.py
new file mode 100644
index 0000000000000000000000000000000000000000..79169d0553ee8e9ab9f465b46f272f32b8dd72ee
--- /dev/null
+++ b/tests/pyb/i2c.py
@@ -0,0 +1,23 @@
+from pyb import I2C
+
+i2c = I2C(1)
+i2c2 = I2C(2)
+
+i2c.init(I2C.MASTER, baudrate=400000)
+print(i2c.scan())
+i2c.deinit()
+
+# use accelerometer to test i2c bus
+
+accel_addr = 76
+
+pyb.Accel() # this will init the bus for us
+
+print(i2c.scan())
+print(i2c.is_ready(accel_addr))
+
+print(i2c.mem_read(1, accel_addr, 7, timeout=500))
+i2c.mem_write(0, accel_addr, 0, timeout=500)
+
+i2c.send(7, addr=accel_addr)
+i2c.recv(1, addr=accel_addr)
diff --git a/tests/pyb/i2c.py.exp b/tests/pyb/i2c.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..c2b982f0fe7e9c32263743e6b4e7472ce71ec1ac
--- /dev/null
+++ b/tests/pyb/i2c.py.exp
@@ -0,0 +1,4 @@
+[]
+[76]
+True
+b'\x01'
diff --git a/tests/pyb/led.py b/tests/pyb/led.py
new file mode 100644
index 0000000000000000000000000000000000000000..a727c90dfad0edbc01fc52f3a43207a0d5c19c40
--- /dev/null
+++ b/tests/pyb/led.py
@@ -0,0 +1,28 @@
+from pyb import LED
+
+for i in range(4):
+    print(LED(i+1))
+
+for i in range(4):
+    LED(i+1).on()
+pyb.delay(10)
+for i in range(4):
+    LED(i+1).off()
+pyb.delay(10)
+for i in range(4):
+    LED(i+1).toggle()
+pyb.delay(10)
+for i in range(4):
+    LED(i+1).intensity(0)
+
+for i in range(256):
+    LED(4).intensity(i)
+    if LED(4).intensity() != i:
+        print('fail', i)
+    pyb.delay(1)
+for i in range(256):
+    LED(4).intensity(255 - i)
+    pyb.delay(1)
+
+for i in range(4):
+    LED(i+1).off()
diff --git a/tests/pyb/led.py.exp b/tests/pyb/led.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..b528706694fd795c4e89deba19593b3320f724d8
--- /dev/null
+++ b/tests/pyb/led.py.exp
@@ -0,0 +1,4 @@
+<LED 1>
+<LED 2>
+<LED 3>
+<LED 4>
diff --git a/tests/pyb/pin.py b/tests/pyb/pin.py
new file mode 100644
index 0000000000000000000000000000000000000000..448ce539982bcea340d0c1fa11a3c7e098642096
--- /dev/null
+++ b/tests/pyb/pin.py
@@ -0,0 +1,29 @@
+from pyb import Pin
+
+p = Pin('X1')
+print(p)
+print(p.name())
+print(p.pin())
+print(p.port())
+
+p = Pin('X1', Pin.IN, Pin.PULL_UP)
+#p = Pin('X1', Pin.IN, pull=Pin.PULL_UP)
+print(p.value())
+
+p.init(p.IN, p.PULL_DOWN)
+#p.init(p.IN, pull=p.PULL_DOWN)
+print(p.value())
+
+p.init(p.OUT_PP)
+p.low()
+print(p.value())
+p.high()
+print(p.value())
+p.value(0)
+print(p.value())
+p.value(1)
+print(p.value())
+p.value(False)
+print(p.value())
+p.value(True)
+print(p.value())
diff --git a/tests/pyb/pin.py.exp b/tests/pyb/pin.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..4856f5b8e7ae3b88b9fa3022d0724f837ef239a4
--- /dev/null
+++ b/tests/pyb/pin.py.exp
@@ -0,0 +1,12 @@
+<Pin A0>
+A0
+0
+0
+1
+0
+0
+1
+0
+1
+0
+1
diff --git a/tests/pyb/pyb1.py b/tests/pyb/pyb1.py
new file mode 100644
index 0000000000000000000000000000000000000000..0087ec050770c6abbee236cc1465147699342bca
--- /dev/null
+++ b/tests/pyb/pyb1.py
@@ -0,0 +1,42 @@
+# basic tests of pyb module
+
+import pyb
+
+# test delay
+
+pyb.delay(-1)
+pyb.delay(0)
+pyb.delay(1)
+
+start = pyb.millis()
+pyb.delay(17)
+print((pyb.millis() - start) // 5) # should print 3
+
+# test udelay
+
+pyb.udelay(-1)
+pyb.udelay(0)
+pyb.udelay(1)
+
+start = pyb.millis()
+pyb.udelay(17000)
+print((pyb.millis() - start) // 5) # should print 3
+
+# other
+
+pyb.disable_irq()
+pyb.enable_irq()
+
+print(pyb.freq())
+
+print(pyb.have_cdc())
+
+pyb.hid((0, 0, 0, 0)) # won't do anything
+
+pyb.rng()
+
+pyb.sync()
+
+print(len(pyb.unique_id()))
+
+pyb.wfi()
diff --git a/tests/pyb/pyb1.py.exp b/tests/pyb/pyb1.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..84034d683cdbd778bff03464d42b310433a7771a
--- /dev/null
+++ b/tests/pyb/pyb1.py.exp
@@ -0,0 +1,5 @@
+3
+3
+(168000000, 168000000, 42000000, 84000000)
+True
+12
diff --git a/tests/pyb/rtc.py b/tests/pyb/rtc.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac716b27fc5eb0e69733309393f704af4b9ae0c3
--- /dev/null
+++ b/tests/pyb/rtc.py
@@ -0,0 +1,6 @@
+from pyb import RTC
+rtc = RTC()
+print(rtc)
+rtc.datetime((2014, 1, 1, 1, 0, 0, 0, 0))
+pyb.delay(1000)
+print(rtc.datetime()[:7])
diff --git a/tests/pyb/rtc.py.exp b/tests/pyb/rtc.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..d1ea2d9590b0b7e284603a260e962281ad1acff9
--- /dev/null
+++ b/tests/pyb/rtc.py.exp
@@ -0,0 +1,2 @@
+<RTC>
+(2014, 1, 1, 1, 0, 0, 1)
diff --git a/tests/pyb/servo.py b/tests/pyb/servo.py
new file mode 100644
index 0000000000000000000000000000000000000000..d15cafe483e019252258884299bbb4c9b94289b1
--- /dev/null
+++ b/tests/pyb/servo.py
@@ -0,0 +1,16 @@
+from pyb import Servo
+
+servo = Servo(1)
+print(servo)
+
+servo.angle(0)
+servo.angle(10, 100)
+
+servo.speed(-10)
+servo.speed(10, 100)
+
+servo.pulse_width(1500)
+print(servo.pulse_width())
+
+servo.calibration(630, 2410, 1490, 2460, 2190)
+print(servo.calibration())
diff --git a/tests/pyb/servo.py.exp b/tests/pyb/servo.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..ac6032ba5fa0a8e3f06843ac8aa405758810ab87
--- /dev/null
+++ b/tests/pyb/servo.py.exp
@@ -0,0 +1,3 @@
+<Servo 1 at 1500us>
+1500
+(630, 2410, 1490, 2460, 2190)
diff --git a/tests/pyb/spi.py b/tests/pyb/spi.py
new file mode 100644
index 0000000000000000000000000000000000000000..90dfb441a3e686cb0cdc91f8a98673f4eac4ea6c
--- /dev/null
+++ b/tests/pyb/spi.py
@@ -0,0 +1,17 @@
+from pyb import SPI
+
+spi = SPI(1)
+print(spi)
+
+spi = SPI(1, SPI.MASTER)
+spi = SPI(1, SPI.MASTER, baudrate=500000)
+spi = SPI(1, SPI.MASTER, 500000, polarity=1, phase=1, bits=8, firstbit=SPI.MSB, ti=False, crc=None)
+print(spi)
+
+spi.init(SPI.SLAVE)
+print(spi)
+
+spi.init(SPI.MASTER)
+spi.send(1, timeout=100)
+print(spi.recv(1, timeout=100))
+print(spi.send_recv(1, timeout=100))
diff --git a/tests/pyb/spi.py.exp b/tests/pyb/spi.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..030dfe1b34ed841371beab3dba6f201b4a8f1160
--- /dev/null
+++ b/tests/pyb/spi.py.exp
@@ -0,0 +1,5 @@
+SPI(1)
+SPI(1, SPI.MASTER, baudrate=328125, polarity=1, phase=1, bits=8)
+SPI(1, SPI.SLAVE, polarity=1, phase=1, bits=8)
+b'\xff'
+b'\xff'
diff --git a/tests/pyb/switch.py b/tests/pyb/switch.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b74e0fd73ba3470fb2ba6eee1b9b01b29a706cd
--- /dev/null
+++ b/tests/pyb/switch.py
@@ -0,0 +1,6 @@
+from pyb import Switch
+
+sw = pyb.Switch()
+print(sw())
+sw.callback(print)
+sw.callback(None)
diff --git a/tests/pyb/switch.py.exp b/tests/pyb/switch.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..bc59c12aa16bda1942655872af699a2d418c472b
--- /dev/null
+++ b/tests/pyb/switch.py.exp
@@ -0,0 +1 @@
+False
diff --git a/tests/pyb/timer.py b/tests/pyb/timer.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff1bda0f23aa01a0e3db5331b59f6d9bdafeb8a0
--- /dev/null
+++ b/tests/pyb/timer.py
@@ -0,0 +1,28 @@
+from pyb import Timer
+
+tim = Timer(4)
+tim = Timer(4, prescaler=100, period=200)
+print(tim.prescaler())
+print(tim.period())
+tim.prescaler(300)
+print(tim.prescaler())
+tim.period(400)
+print(tim.period())
+
+tim = Timer(4, freq=1)
+tim.init(freq=2000)
+def f(t):
+    print(1)
+    t.callback(None)
+tim.callback(f)
+pyb.delay(10)
+
+# f3 closes over f2.y
+def f2(x):
+    y = x
+    def f3(t):
+        print(2, y)
+        t.callback(None)
+    return f3
+tim.callback(f2(3))
+pyb.delay(10)
diff --git a/tests/pyb/timer.py.exp b/tests/pyb/timer.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..58b81e2af12b828e978f846611afa081c83f7184
--- /dev/null
+++ b/tests/pyb/timer.py.exp
@@ -0,0 +1,6 @@
+100
+200
+300
+400
+1
+2 3
diff --git a/tests/pyb/uart.py b/tests/pyb/uart.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e0118d155409b9bd9cf63ae556ddafea9b72a16
--- /dev/null
+++ b/tests/pyb/uart.py
@@ -0,0 +1,12 @@
+from pyb import UART
+
+uart = UART(1)
+uart = UART(1, 9600)
+uart = UART(1, 9600, bits=8, stop=1, parity=None)
+print(uart)
+
+uart.init(1200)
+print(uart)
+
+uart.any()
+uart.send(1, timeout=500)
diff --git a/tests/pyb/uart.py.exp b/tests/pyb/uart.py.exp
new file mode 100644
index 0000000000000000000000000000000000000000..58ded4d8489aa7305056c6157e0b8ce9b910690b
--- /dev/null
+++ b/tests/pyb/uart.py.exp
@@ -0,0 +1,2 @@
+UART(1, baudrate=9600, bits=8, stop=1, parity=None)
+UART(1, baudrate=1200, bits=8, stop=1, parity=None)
diff --git a/tests/pyboard.py b/tests/pyboard.py
new file mode 120000
index 0000000000000000000000000000000000000000..3a82f6a6a36dbdeb4d3ab500bc85b52db2afa866
--- /dev/null
+++ b/tests/pyboard.py
@@ -0,0 +1 @@
+../tools/pyboard.py
\ No newline at end of file
diff --git a/tests/run-tests b/tests/run-tests
index 9e94026fa23b033de6a9c69ae1538933e913fda4..c1eee59eaa430c91dc873040ce696f813d3ac63d 100755
--- a/tests/run-tests
+++ b/tests/run-tests
@@ -3,6 +3,7 @@
 import os
 import subprocess
 import sys
+import argparse
 from glob import glob
 
 # Tests require at least CPython 3.3. If your default python3 executable
@@ -15,82 +16,113 @@ else:
     CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3')
     MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', '../unix/micropython')
 
-# Set of tests that we shouldn't run under Travis CI
-skip_travis_tests = set(['basics/memoryerror.py'])
-
 def rm_f(fname):
     if os.path.exists(fname):
         os.remove(fname)
 
-test_count = 0
-testcase_count = 0
-passed_count = 0
-failed_tests = []
-tests = []
-
-if not sys.argv[1:]:
-    test_dirs = ('basics', 'float', 'import', 'io', 'misc')
-    tests = sorted(test_file for test_files in (glob('{}/*.py'.format(dir)) for dir in test_dirs) for test_file in test_files)
-else:
-    tests = sys.argv[1:]
-
-test_on_pyboard = False
-if test_on_pyboard:
-    import pyboard
-    pyb = pyboard.Pyboard('/dev/ttyACM0')
-    pyb.enter_raw_repl()
-
-running_under_travis = os.getenv('TRAVIS') == 'true'
-
-for test_file in tests:
-    if running_under_travis and test_file in skip_travis_tests:
-        print("skip ", test_file)
-        continue
-
-    # run CPython
-    try:
-        output_expected = subprocess.check_output([CPYTHON3, '-B', test_file])
-    except subprocess.CalledProcessError:
-        output_expected = b'CPYTHON3 CRASH'
-
-    # run Micro Python
-    if test_on_pyboard:
+def run_tests(pyb, tests):
+    test_count = 0
+    testcase_count = 0
+    passed_count = 0
+    failed_tests = []
+
+    running_under_travis = os.getenv('TRAVIS') == 'true'
+
+    # Set of tests that we shouldn't run under Travis CI
+    skip_travis_tests = set(['basics/memoryerror.py'])
+
+    for test_file in tests:
+        if running_under_travis and test_file in skip_travis_tests:
+            print("skip ", test_file)
+            continue
+
+        # get expected output
+        test_file_expected = test_file + '.exp'
+        if os.path.isfile(test_file_expected):
+            # expected output given by a file, so read that in
+            with open(test_file_expected, 'rb') as f:
+                output_expected = f.read()
+        else:
+            # run CPython to work out expeceted output
+            try:
+                output_expected = subprocess.check_output([CPYTHON3, '-B', test_file])
+            except subprocess.CalledProcessError:
+                output_expected = b'CPYTHON3 CRASH'
+
+        # run Micro Python
+        if pyb is None:
+            # run on PC
+            try:
+                output_mupy = subprocess.check_output([MICROPYTHON, '-X', 'emit=bytecode', test_file])
+            except subprocess.CalledProcessError:
+                output_mupy = b'CRASH'
+        else:
+            # run on pyboard
+            pyb.enter_raw_repl()
+            try:
+                output_mupy = pyb.execfile(test_file).replace(b'\r\n', b'\n')
+            except pyboard.PyboardError:
+                output_mupy = b'CRASH'
+
+        testcase_count += len(output_expected.splitlines())
+
+        test_basename = os.path.basename(test_file)
+        test_name = os.path.splitext(test_basename)[0]
+        filename_expected = test_basename + ".exp"
+        filename_mupy = test_basename + ".out"
+
+        if output_expected == output_mupy:
+            print("pass ", test_file)
+            passed_count += 1
+            rm_f(filename_expected)
+            rm_f(filename_mupy)
+        else:
+            with open(filename_expected, "w") as f:
+                f.write(str(output_expected, "ascii"))
+            with open(filename_mupy, "w") as f:
+                f.write(str(output_mupy, "ascii"))
+            print("FAIL ", test_file)
+            failed_tests.append(test_name)
+
+        test_count += 1
+
+    print("{} tests performed ({} individual testcases)".format(test_count, testcase_count))
+    print("{} tests passed".format(passed_count))
+
+    if len(failed_tests) > 0:
+        print("{} tests failed: {}".format(len(failed_tests), ' '.join(failed_tests)))
+        return False
+
+    # all tests succeeded
+    return True
+
+def main():
+    cmd_parser = argparse.ArgumentParser(description='Run tests for Micro Python.')
+    cmd_parser.add_argument('--pyboard', action='store_true', help='run the tests on the pyboard')
+    cmd_parser.add_argument('files', nargs='*', help='input test files')
+    args = cmd_parser.parse_args()
+
+    if args.pyboard:
+        import pyboard
+        pyb = pyboard.Pyboard('/dev/ttyACM0')
         pyb.enter_raw_repl()
-        try:
-            output_mupy = pyb.execfile(test_file).replace(b'\r\n', b'\n')
-        except pyboard.PyboardError:
-            output_mupy = b'CRASH'
     else:
-        try:
-            output_mupy = subprocess.check_output([MICROPYTHON, '-X', 'emit=bytecode', test_file])
-        except subprocess.CalledProcessError:
-            output_mupy = b'CRASH'
-
-    testcase_count += len(output_expected.splitlines())
-
-    test_basename = os.path.basename(test_file)
-    test_name = os.path.splitext(test_basename)[0]
-    filename_expected = test_basename + ".exp"
-    filename_mupy = test_basename + ".out"
-
-    if output_expected == output_mupy:
-        print("pass ", test_file)
-        passed_count += 1
-        rm_f(filename_expected)
-        rm_f(filename_mupy)
+        pyb = None
+
+    if len(args.files) == 0:
+        if pyb is None:
+            # run PC tests
+            test_dirs = ('basics', 'float', 'import', 'io', 'misc')
+        else:
+            # run pyboard tests
+            test_dirs = ('basics', 'float', 'pyb')
+        tests = sorted(test_file for test_files in (glob('{}/*.py'.format(dir)) for dir in test_dirs) for test_file in test_files)
     else:
-        with open(filename_expected, "w") as f:
-            f.write(str(output_expected, "ascii"))
-        with open(filename_mupy, "w") as f:
-            f.write(str(output_mupy, "ascii"))
-        print("FAIL ", test_file)
-        failed_tests.append(test_name)
-
-    test_count += 1
+        # tests explicitly given
+        tests = args.files
 
-print("{} tests performed ({} individual testcases)".format(test_count, testcase_count))
-print("{} tests passed".format(passed_count))
+    if not run_tests(pyb, tests):
+        sys.exit(1)
 
-if len(failed_tests) > 0:
-    print("{} tests failed: {}".format(len(failed_tests), ' '.join(failed_tests)))
-    sys.exit(1)
+if __name__ == "__main__":
+    main()