From 90c9a148a94ed92c93d42110e45d8787f7ac1eff Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Mon, 12 Dec 2011 00:11:34 +0100 Subject: initial import --- Makefile | 15 ++++++ README | 94 ++++++++++++++++++++++++++++++++ assembler.vala | 140 +++++++++++++++++++++++++++++++++++++++++++++++ lp5523.vala | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test.vala | 141 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 558 insertions(+) create mode 100644 Makefile create mode 100644 README create mode 100644 assembler.vala create mode 100644 lp5523.vala create mode 100644 test.vala diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5ff359d --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +all: lp5523-as + +test: lp5523-test + @./$< + +lp5523-as: lp5523.vala assembler.vala + @valac-0.14 --enable-experimental -o $@ $^ + +lp5523-test: lp5523.vala test.vala + @valac-0.14 --enable-experimental -o $@ $^ + +clean: + @rm -f lp5523-as lp5523-test + +.PHONY: all test clean diff --git a/README b/README new file mode 100644 index 0000000..a75e2e3 --- /dev/null +++ b/README @@ -0,0 +1,94 @@ +This is a simple assembler for lp5523 led chips. You +can use it to convert the microcode from lp5523 engines +into human readable code and the other way around. + +------------------------------------------------------------ +The Assembly Language +------------------------------------------------------------ + +The assembly language consists of the following mnemonics: + + * start + This must be specified at the beginning of each program. + + * restart + Jumps back to the start. + + * stop + Stops the engine. + + * stopr + Stops the engine with reset. + + * stopi + Stops the engine with interrupt. + + * stopri + Stops the engine with reset & interrupt. + + * send1, send2, send3 + Sends signal to engine 1, 2 or 3. + + * wait1, wait2, wait3 + Waits for signal from engine 1, 2 or 3. + + * set LEVEL + Sets output to LEVEL (0-255). + + * sleep TIME + - TIME can be between 0 and 61 + Does nothing for the specified amount of time. See + below for TIME value -> time in seconds calculation. + + * fade TIME +STEPS + - TIME can be between 0 and 61 + - STEPS can be between 0 and 255 + - The '+' is important + Slowly increase the output by STEPS in the specified + amount of time. + + * fade TIME -STEPS + - TIME can be between 0 and 61 + - STEPS can be between 0 and 255 + - The '-' is important + Slowly decrease the output by STEPS in the specified + amount of time. + + * branch COUNT PC + - COUNT can be between 0 and 63 + - PC can be between 0 and 95 + Jumps (back) COUNT times to the opcode located at PC. + A COUNT value of 0 is interpreted as infinite. + + +Calculation of time in ms for a given TIME value: + + if(value < 31) + return value * 0.49ms; + else + return (value-31) * 15.6ms; + +-------------------------------------------------------- +Usage +-------------------------------------------------------- + +Assemble lp5523.S into lp5523 machine code: + $ lp5523-as file.S > result.txt + $ echo "code" | lp5523-as > result.txt + +Disassemble lp5523 machine code in assembly code: + $ lp5523-as -d input > result.txt + $ echo "code" | lp5523-as -d > result.txt + +-------------------------------------------------------- +Building & Installation +-------------------------------------------------------- + +Building: + $ make + +Running Unit Tests: + $ make test + +Installation: + $ install -m755 lp5523 /usr/local/bin diff --git a/assembler.vala b/assembler.vala new file mode 100644 index 0000000..e44ceb8 --- /dev/null +++ b/assembler.vala @@ -0,0 +1,140 @@ +/* assembler.vala + * Copyright 2011, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +const OptionEntry[] option_entries = { + {"assemble", 'a', OptionFlags.NO_ARG, OptionArg.CALLBACK, (void*) parse_mode, "use assemble mode", null}, + {"disassemble", 'd', OptionFlags.NO_ARG, OptionArg.CALLBACK, (void*) parse_mode, "use disassemble mode", null}, + { "", 0, 0, OptionArg.FILENAME_ARRAY, ref files, "input file", "FILE" }, + {null} +}; + +enum mode { + ASSEMBLE, + DISASSEMBLE +} + +static string[] files; +static mode m; + +bool parse_mode(string key, string? val) throws OptionError { + switch(key) { + case "--assemble": + case "-a": + m = mode.ASSEMBLE; + return true; + case "--disassemble": + case "-d": + m = mode.DISASSEMBLE; + return true; + default: + throw new OptionError.UNKNOWN_OPTION("Unknown Option " + key); + } +} + +string assemble(FileStream stream) throws lp5523.ParsingError { + string line; + string result = ""; + + /* read until EOF */ + while((line = stream.read_line()) != null) { + /* skip comment lines */ + line = line.strip(); + if(line.has_prefix("#")) + continue; + + /* remove inline comments */ + line = line.split("#", 2)[0]; + line = line.strip(); + + result += "%04hx".printf(lp5523.assemble(line)); + } + + /* size check */ + if(result.length/4 > lp5523.PROGSPACE_SIZE) + warning("this does not fit into lp5523 progspace"); + + result += "\n"; + + return result; +} + +public string disassemble(FileStream stream) throws lp5523.ParsingError { + string line; + string result = ""; + + line = stream.read_line(); + + if(line == null) + return ""; + + /* remove trailing newline */ + if(line.length % 2 == 1 && line.has_suffix("\n")) + line = line.substring(0, line.length-1); + + /* disassemble */ + for(int i=0; i + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +namespace lp5523 { + public errordomain ParsingError { + INVALID_OPCODE, + INVALID_COMMAND, + OUT_OF_RANGE + } + + public uint8 PROGSPACE_SIZE = 96; + + public string disassemble(uint16 opcode) throws ParsingError { + opcode = opcode.to_big_endian(); + uint8 byte1 = (uint8) opcode; + uint8 byte2 = (uint8) (opcode >> 8); + opcode = uint16.from_big_endian(opcode); + + if(byte1 >= 0x02 && byte1 <= 0x3f) { + bool neg = (byte1 & 0x01) == 0x01; + uint8 time = (byte1 >> 1) - 0x01; + uint8 steps = byte2; + if(neg && steps == 0x00) + return "sleep %hhu".printf(time); + else + return "fade %hhu %c%hhu".printf(time, neg ? '-' : '+', steps); + } else if(byte1 == 0x40) { + return "set %hhu".printf(byte2); + } else if(byte1 >= 0x42 && byte1 <= 0x7f) { + bool neg = (byte1 & 0x01) == 0x01; + uint8 time = (byte1 >> 1) - 0x02; + uint8 steps = byte2; + if(neg && steps == 0x00) + return "sleep %hhu".printf(time); + else + return "fade %hhu %c%hhu".printf(time, neg ? '-' : '+', steps); + } else if(byte1 >= 0xa0 && byte1 <= 0xbf) { + uint8 count = (byte1 << 1) & ~0xc1; + if((byte2 & 0x80) == 0x80) count++; + uint8 pc = byte2 & ~0x80; + return "branch %hhu %hhu".printf(count, pc); + } + + switch(opcode) { + case 0x0000: return "restart"; + case 0x9d80: return "start"; + case 0xc000: return "stop"; + case 0xc800: return "stopr"; + case 0xd000: return "stopi"; + case 0xd800: return "stopri"; + case 0xe002: return "send1"; + case 0xe004: return "send2"; + case 0xe008: return "send3"; + case 0xe080: return "wait1"; + case 0xe100: return "wait2"; + case 0xe200: return "wait3"; + default: + throw new ParsingError.INVALID_OPCODE("invalid opcode: %04hx".printf(opcode)); + } + } + + public uint16 assemble(string cmd) throws ParsingError { + MatchInfo m; + + Regex REGEX_SET = /^set\s+(\d+)$/; + Regex REGEX_SLEEP = /^sleep\s+(\d+)$/; + Regex REGEX_FADE = /^fade\s+(\d+)\s+([\+\-])(\d+)$/; + Regex REGEX_BRANCH = /^branch\s+(\d+)\s+(\d+)$/; + + if(REGEX_SLEEP.match(cmd, 0, out m)) { + uint16 time = (uint8) int.parse(m.fetch(1)); + + if(time < 31) + time += 0x01; + else if(time < 62) + time += 0x02; + else + throw new ParsingError.OUT_OF_RANGE("fade time out of range (0-61)"); + + time <<= 1; + time |= 0x01; + time <<= 8; + + return time; + } + + if(REGEX_FADE.match(cmd, 0, out m)) { + uint16 time = (uint8) int.parse(m.fetch(1)); + bool neg = m.fetch(2).get(0) == '-'; + uint8 steps = (uint8) int.parse(m.fetch(3)); + + if(time < 31) + time += 0x01; + else if(time < 62) + time += 0x02; + else + throw new ParsingError.OUT_OF_RANGE("fade time out of range (0-61)"); + + time <<= 1; + + if(neg) + time |= 0x01; + + time <<= 8; + + return time + steps; + } + + if(REGEX_SET.match(cmd, 0, out m)) { + uint16 byte1 = 0x4000; + + uint level = int.parse(m.fetch(1)); + if(level > 0xFF) + throw new ParsingError.OUT_OF_RANGE("brightness level is out of range (0-255)"); + + return byte1 + (uint8) level; + } + + if(REGEX_BRANCH.match(cmd, 0, out m)) { + uint16 byte1 = (uint8) int.parse(m.fetch(1)); + uint8 byte2 = (uint8) int.parse(m.fetch(2)); + + if((byte1 & 0x01) != 0x00) + byte2 |= 0x80; + else + byte2 &= ~0x80; + + byte1 >>= 1; /* shift 1 right */ + byte1 |= 0x80; /* set 1st bit */ + byte1 &= ~0x40; /* clear 2nd bit */ + byte1 |= 0x20; /* set 3rd bit */ + + byte1 <<= 8; + + return byte1 + byte2; + } + + switch(cmd) { + case "start": return 0x9d80; + case "restart": return 0x0000; + case "stop": return 0xc000; + case "stopr": return 0xc800; + case "stopi": return 0xd000; + case "stopri": return 0xd800; + case "send1": return 0xe002; + case "send2": return 0xe004; + case "send3": return 0xe008; + case "wait1": return 0xe080; + case "wait2": return 0xe100; + case "wait3": return 0xe200; + default: + throw new ParsingError.INVALID_COMMAND("invalid command: %s".printf(cmd)); + } + } +} diff --git a/test.vala b/test.vala new file mode 100644 index 0000000..29fd902 --- /dev/null +++ b/test.vala @@ -0,0 +1,141 @@ +/* test.vala + * Copyright 2011, Sebastian Reichel + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +public class AssemblerTestSuite { + public static void add_simple_assembly_tests() { + /* start */ + Test.add_func("/lp5523/reassembly/restart", () => { + simple_assembly_test("restart", 0x0000); + }); + + Test.add_func("/lp5523/reassembly/start", () => { + simple_assembly_test("start", 0x9d80); + }); + + /* stop */ + Test.add_func("/lp5523/reassembly/stop", () => { + simple_assembly_test("stop", 0xc000); + }); + + Test.add_func("/lp5523/reassembly/stopi", () => { + simple_assembly_test("stopr", 0xc800); + }); + + Test.add_func("/lp5523/reassembly/stopr", () => { + simple_assembly_test("stopi", 0xd000); + }); + + Test.add_func("/lp5523/reassembly/stopri", () => { + simple_assembly_test("stopri", 0xd800); + }); + + /* signals */ + Test.add_func("/lp5523/reassembly/send1", () => { + simple_assembly_test("send1", 0xe002); + }); + + Test.add_func("/lp5523/reassembly/send2", () => { + simple_assembly_test("send2", 0xe004); + }); + + Test.add_func("/lp5523/reassembly/send3", () => { + simple_assembly_test("send3", 0xe008); + }); + + Test.add_func("/lp5523/reassembly/wait1", () => { + simple_assembly_test("wait1", 0xe080); + }); + + Test.add_func("/lp5523/reassembly/wait2", () => { + simple_assembly_test("wait2", 0xe100); + }); + + Test.add_func("/lp5523/reassembly/wait3", () => { + simple_assembly_test("wait3", 0xe200); + }); + + /* fading */ + Test.add_func("/lp5523/reassembly/fade 1 +255", () => { + simple_assembly_test("fade 1 +255", 0x04ff); + }); + + Test.add_func("/lp5523/reassembly/fade 0 -100", () => { + simple_assembly_test("fade 0 -100", 0x0364); + }); + + Test.add_func("/lp5523/reassembly/fade 61 -100", () => { + simple_assembly_test("fade 61 -100", 0x7f64); + }); + + Test.add_func("/lp5523/reassembly/fade 61 +100", () => { + simple_assembly_test("fade 61 +100", 0x7e64); + }); + + Test.add_func("/lp5523/reassembly/fade 0 +100", () => { + simple_assembly_test("fade 0 +100", 0x0264); + }); + + Test.add_func("/lp5523/reassembly/fade 1 -255", () => { + simple_assembly_test("fade 1 -255", 0x05ff); + }); + + /* sleeping */ + Test.add_func("/lp5523/reassembly/sleep 61", () => { + simple_assembly_test("sleep 61", 0x7f00); + }); + + /* instant brightness setting */ + Test.add_func("/lp5523/reassembly/set 255", () => { + simple_assembly_test("set 255", 0x40ff); + }); + + Test.add_func("/lp5523/reassembly/set 0", () => { + simple_assembly_test("set 0", 0x4000); + }); + + /* branching */ + Test.add_func("/lp5523/reassembly/branch 0 0", () => { + simple_assembly_test("branch 0 0", 0xa000); + }); + + Test.add_func("/lp5523/reassembly/branch 63 95", () => { + simple_assembly_test("branch 63 95", 0xbfdf); + }); + } + + public static void simple_assembly_test(string cmd, uint16 opcode) { + try { + uint16 assembled_opcode = lp5523.assemble(cmd); + string disassembled_cmd = lp5523.disassemble(opcode); + + if(assembled_opcode != opcode) { + error("\"%04hx\" != \"%04hx\"", opcode, assembled_opcode); + } + + if(disassembled_cmd != cmd) { + error("\"%s\" != \"%s\"", cmd, disassembled_cmd); + } + } catch(lp5523.ParsingError e) { + error("Received Parsing Error: %s", e.message); + } + } +} + +void main(string[] args) { + Test.init(ref args); + AssemblerTestSuite.add_simple_assembly_tests(); + Test.run(); +} -- cgit v1.2.3