# HG changeset patch # User Paul Boddie # Date 1452382241 -3600 # Node ID a9ad2b31a8ffb29b384ea47b55c809a0463e8984 A test of the ElecFreaks alphanumeric display brick with an Arduino Duemilanove. diff -r 000000000000 -r a9ad2b31a8ff Alphanumeric.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Alphanumeric.cpp Sun Jan 10 00:30:41 2016 +0100 @@ -0,0 +1,163 @@ +/* +Interfacing the Arduino Duemilanove to the ElecFreaks alphanumeric display +brick. + +Copyright (C) 2016 Paul Boddie + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see . +*/ + +#include + +const int BUFSIZE = 17; +char inbuffer[BUFSIZE]; +uint8_t nread = 0; + +uint8_t data[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + +void enable_clock() +{ + Wire.beginTransmission(0x70); + Wire.write(0x21); + Wire.endTransmission(); +} + +void set_row_output() +{ + Wire.beginTransmission(0x70); + Wire.write(0xa0); + Wire.endTransmission(); +} + +void set_dimming() +{ + Wire.beginTransmission(0x70); + Wire.write(0xe3); // pulse_width = 4/16 + Wire.endTransmission(); +} + +void enable_display() +{ + Wire.beginTransmission(0x70); + Wire.write(0x81); // no blinking + Wire.endTransmission(); +} + +void disable_display() +{ + Wire.beginTransmission(0x70); + Wire.write(0x80); + Wire.endTransmission(); +} + +void init_alphanumeric() +{ + enable_clock(); + set_row_output(); + set_dimming(); +} + +void write_digits(uint8_t data[], uint8_t len) +{ + uint8_t i; + + Wire.beginTransmission(0x70); + Wire.write(0x00); // address = 0 + + for (i = 0; i < len; i++) + { + Wire.write(data[i]); + } + + Wire.endTransmission(); +} + +/* User interface functions. */ + +uint8_t fromHex(char c) +{ + if ((c >= 48) && (c <= 57)) + return c - 48; + if ((c >= 65) && (c <= 70)) + return c - 65 + 10; + if ((c >= 97) && (c <= 102)) + return c - 97 + 10; + return 0; +} + +void to_digits(char buffer[], uint8_t data[], uint8_t len) +{ + uint8_t i, j, p, high, low; + + for (i = 0, j = 0; j < len; j += 2) + { + for (p = 2; p > 0; p--, i += 2) + { + high = fromHex(buffer[i]); + if (high == 17) + return; + low = fromHex(buffer[i+1]); + if (low == 17) + return; + + /* Switch to little-endian. */ + + data[j+p-1] = (high << 4) + low; + } + } +} + +void setup() +{ + Wire.begin(); + Serial.begin(115200); + + init_alphanumeric(); + write_digits(data, 8); + enable_display(); + + // Interface loop. + + Serial.println("?"); +} + +void loop() +{ + /* Read bytes, obtaining the number read excluding any newline terminator. */ + + if (nread += Serial.readBytesUntil('\n', inbuffer + nread, BUFSIZE - nread)) + { + /* Handle each command, waiting for the newline. */ + + if (nread >= 16) + { + to_digits(inbuffer, data, 8); + write_digits(data, 8); + nread = 0; + Serial.println("OK"); + } + else + { + Serial.print(nread); + Serial.println("..."); + } + + Serial.flush(); + } +} + +extern "C" void __cxa_pure_virtual(void) { + while(1); +} + +// tabstop=4 expandtab shiftwidth=4 diff -r 000000000000 -r a9ad2b31a8ff Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Sun Jan 10 00:30:41 2016 +0100 @@ -0,0 +1,146 @@ +TARGET = $(notdir $(CURDIR)) +INSTALL_DIR = ../arduino-1.0.5 +PORT = /dev/ttyUSB0 +UPLOAD_RATE = 57600 # 19200 +AVRDUDE_PROGRAMMER = stk500v1 +MCU = atmega328p # atmega168 +F_CPU = 16000000 + +LIBRARIES = $(INSTALL_DIR)/libraries + +EXTRA_SRC = $(LIBRARIES)/Wire/utility/twi.c +EXTRA_CXXSRC = $(LIBRARIES)/Wire/Wire.cpp +EXTRA_CINCS = +EXTRA_CXXINCS = -I$(LIBRARIES)/Wire -I$(LIBRARIES)/Wire/utility + + +### Internal definitions. + + +ARDUINO = $(INSTALL_DIR)/hardware/arduino/cores/arduino +VARIANT = $(INSTALL_DIR)/hardware/arduino/variants/standard + +SRC = $(ARDUINO)/wiring.c $(ARDUINO)/wiring_digital.c \ + $(EXTRA_SRC) +CXXSRC = $(ARDUINO)/HardwareSerial.cpp \ + $(ARDUINO)/Print.cpp \ + $(ARDUINO)/WString.cpp \ + $(ARDUINO)/Stream.cpp \ + $(EXTRA_CXXSRC) +FORMAT = ihex + +# Name of this Makefile (used for "make depend"). +MAKEFILE = Makefile + +# Debugging format. +# Native formats for AVR-GCC's -g are stabs [default], or dwarf-2. +# AVR (extended) COFF requires stabs, plus an avr-objcopy run. +DEBUG = stabs + +# Place -D or -U options here +CDEFS = -DF_CPU=$(F_CPU) +CXXDEFS = -DF_CPU=$(F_CPU) + +# Place -I options here +CINCS = -I$(ARDUINO) -I$(VARIANT) $(EXTRA_CINCS) +CXXINCS = -I$(ARDUINO) -I$(VARIANT) $(EXTRA_CXXINCS) + +# Compiler flag to set the C Standard level. +# c89 - "ANSI" C +# gnu89 - c89 plus GCC extensions +# c99 - ISO C99 standard (not yet fully implemented) +# gnu99 - c99 plus GCC extensions +CSTANDARD = -std=gnu99 +CDEBUG = -g$(DEBUG) +CWARN = -Wall -Wstrict-prototypes + +OPT = s + +CFLAGS = $(CDEBUG) $(CDEFS) $(CINCS) -O$(OPT) $(CWARN) $(CSTANDARD) $(CEXTRA) +CXXFLAGS = $(CXXDEFS) $(CXXINCS) -O$(OPT) -ffunction-sections -fdata-sections +LDFLAGS = -lm -Wl,--gc-sections + +# Combine all necessary flags and optional flags. +# Add target processor to flags. +ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) +ALL_CXXFLAGS = -mmcu=$(MCU) -I. $(CXXFLAGS) + +# Programming support using avrdude. Settings and variables. +AVRDUDE_PORT = $(PORT) +AVRDUDE_WRITE_FLASH = -U flash:w:applet/$(TARGET).hex +AVRDUDE_FLAGS = -V -F -C /etc/avrdude.conf \ + -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) \ + -b $(UPLOAD_RATE) + +# Program settings +CC = avr-gcc +CXX = avr-g++ +LD = avr-ld +OBJCOPY = avr-objcopy +OBJDUMP = avr-objdump +AR = avr-ar +SIZE = avr-size +NM = avr-nm +AVRDUDE = avrdude +REMOVE = rm -f +MV = mv -f + +# Define all object files. +OBJ = $(SRC:.c=.o) $(CXXSRC:.cpp=.o) + +# Default target. +all: applet_files build sizeafter + +build: elf hex + +applet_files: $(TARGET).cpp + test -d applet || mkdir applet + cat $(ARDUINO)/main.cpp > applet/$(TARGET).cpp + cat $(TARGET).cpp >> applet/$(TARGET).cpp + +elf: applet/$(TARGET).elf +hex: applet/$(TARGET).hex + +# Program the device. +upload: applet/$(TARGET).hex + $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) + +# Display size of file. +HEXSIZE = $(SIZE) --target=$(FORMAT) applet/$(TARGET).hex +ELFSIZE = $(SIZE) applet/$(TARGET).elf + +sizebefore: + @if [ -f applet/$(TARGET).elf ]; then echo; echo $(MSG_SIZE_BEFORE); $(HEXSIZE); echo; fi + +sizeafter: + @if [ -f applet/$(TARGET).elf ]; then echo; echo $(MSG_SIZE_AFTER); $(HEXSIZE); echo; fi + + +### File-specific processing. + + +.SUFFIXES: .elf .hex .eep .lss .sym + +.elf.hex: + $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@ + +applet/$(TARGET).elf: applet/$(TARGET).cpp applet/core.a + $(CXX) $(ALL_CXXFLAGS) -o $@ applet/$(TARGET).cpp -L. applet/core.a $(LDFLAGS) + +applet/core.a: $(OBJ) + @for i in $(OBJ); do echo $(AR) rcs applet/core.a $$i; $(AR) rcs applet/core.a $$i; done + +# Compile: create object files from C++ source files. +.cpp.o: + $(CXX) -c $(ALL_CXXFLAGS) $< -o $@ + +# Compile: create object files from C source files. +.c.o: + $(CC) -c $(ALL_CFLAGS) $< -o $@ + +# Target: clean project. +clean: + $(REMOVE) applet/$(TARGET).hex applet/$(TARGET).elf applet/$(TARGET).cpp \ + applet/$(TARGET).map applet/core.a $(OBJ) + +.PHONY: all build elf hex eep lss sym program coff extcoff clean applet_files sizebefore sizeafter diff -r 000000000000 -r a9ad2b31a8ff README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.txt Sun Jan 10 00:30:41 2016 +0100 @@ -0,0 +1,136 @@ +From the HT16K33 datasheet [1] and EF4058 schematic [2]. + +Addressing +---------- + + RAM addresses + ROW0..7 ROW8..15 +COM0 -> digit 1, tube 1 00 01 +COM1 -> digit 2, tube 1 02 03 +COM2 -> digit 1, tube 2 04 05 +COM3 -> digit 2, tube 2 06 07 + +ROW0/A2 |-> connected to both tubes +ROW1/A1 | +ROW2/A0 | +ROW3..14 | + +Tube and Digit Layout +--------------------- + +By observing the behaviour, the tubes and digits have the following layout: + +tubes +2 1 +digits +1 2 1 2 + +Communications +-------------- + +I2C commands: + + D15 D14 D13 D12 D11 D10 D9 D8 +setup 0 0 1 0 X X X S S=0 => standby; S=1 => operational +ROW/INT 1 0 1 0 X X A I I=0 => ROW driver output; + I=1 => INT output + A=0 => active low + A=1 => active high +display 1 0 0 0 X B1 B0 D D=0 => display off; D=1 => display on + B1..B0 = 0 => blinking off + B1..B0 = 2 - log2 (blinking frequency) + (1 => 2Hz; 2 => 1Hz; 3 => 0.5Hz) +address 0 0 0 0 A3 A2 A1 A0 A3..A0 => set row address pointer +dimming 1 1 1 0 P3 P2 P1 P0 P3..P0 = 16 * pulse_width - 1 + (0 => 1/16; 15 => 1) + +I2C data format: + + D7 D6 D5 D4 D3 D2 D1 D0 +ROW 7 6 5 4 3 2 1 0 +ROW 15 14 13 12 11 10 9 8 + +When converting values to the I2C format, the most significant byte is sent +after the least significant byte. For example, the following hexadecimal +representation is converted as follows: + + 8000400020001000 +-> 80 00 40 00 20 00 10 00 (bytes in MSB, LSB order) +-> 00 80 00 40 00 20 00 10 (bytes in LSB, MSB order) + +LED Digit Layout +---------------- + +The digits each have the following layout: + +LED ROWx data value remark + +--- ROW0 1 upper horizontal + + + + | ROW1 2 upper-right vertical + + + + ROW2 4 lower-right vertical + + | + + ROW3 8 lower horizontal + +--- + + ROW4 10 lower-left vertical + +| + +| ROW5 20 upper-left vertical + + + + ROW6 40 left-middle horizontal +- + + + ROW7 80 right-middle horizontal + - + + +\ ROW8 100 top-left diagonal + + + + | ROW9 200 upper-middle vertical + + + + / ROW10 400 top-right diagonal + + + + ROW11 800 lower-right diagonal + + \ + + ROW12 1000 lower-middle vertical + + | + + ROW13 2000 lower-left diagonal + +/ + + ROW14 4000 decimal point + + . + + ROW15 8000 (not connected) + + + +References +---------- + +[1] EF4058-OCTOPUS_Alphanumeric_V1.0_SCH.pdf +[2] EF4058-ht16K33v110.pdf diff -r 000000000000 -r a9ad2b31a8ff client.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client.py Sun Jan 10 00:30:41 2016 +0100 @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +""" +A client for updating the display of the ElecFreaks alphanumeric display brick. + +Copyright (C) 2015, 2016 Paul Boddie + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see . +""" + +from serial import Serial +from time import sleep +import sys + +port = "/dev/ttyUSB0" +rate = 115200 + +def openPort(): + global s + s = Serial(port, rate) + s.setTimeout(5) + s.readline() + +def closePort(): + global s + s.close() + s = None + +def writeToPort(data): + s.write(data) + s.flush() + print >>sys.stderr, data.rstrip() + +def readFromPort(): + resp = s.readline().rstrip("\r\n") + if resp == "?": + resp = s.readline().rstrip("\r\n") + return resp + +def console(): + try: + while 1: + cmd = raw_input("> ") + if cmd: + s.write(cmd + "\n") + s.flush() + print s.readline().rstrip("\r\n") + except EOFError: + print "Session closed." + +if __name__ == "__main__": + openPort() + + try: + console() + finally: + closePort() + +# vim: tabstop=4 expandtab shiftwidth=4