TM1638 Seven Segment Display Driver with Key Scan Interface

While looking for a new display on eBay recently I stumbled across a seven segment display module. The module (shown below) features 8 seven segment displays, 8 push button switches as well as 8 LEDs. All of which are controlled by one single driver IC the TM1638. I have never come across the TM1638 before, I have used similar display driver ICs like the MAX7219, but never the TM1638.

led_key

After some googling I discovered the TM1638 is manufactured by a Chinese outfit known as Titan Micro Electronics. After downloading the data sheet I begun looking to see how this driver is used. At this point I must state the data sheet, which appears to have been translated from chinese to english, badly, leaves a lot to be desired. Fortunately for me the Arduino fanboys seem to love these devices and there is no shortage of information scattered across the internet. So armed with this information I decided to purchase one.

Once it had arrived getting it working was fairly straight forward. The module does not come with schematic and I wasn’t going to bother reverse engineering the board to create one (not unless I ran into any issues I could not resolve) so it was a bit trial and error in the early stages. In the rest of the post I will demonstrate how to use this device in more detail.

Background

The TM1638 is a LED driver controller with integrated key-scan interface. Communication with the device is via a 3 wire serial interface. The device is capable to of driving up to 8 x 10 LED segments. As well as reading 24 individual key inputs. The display intensity of the LED segments can also be dynamically configured.

Connections

The supply voltage for the device is quoted as 5V ±10% however have seen evidence of people using a 3V3 supply and not having issues.

The LED segments are connected to the device via the segment output pins SEG1-SEG10 and grid output pins GRID1-GRID8. The module I purchased has 8 seven segment displays and a further 8 LEDs. These seven segment displays connect via segment output pins SEG1-SEG8 and grid output pins GRID1-GRID8. The 8 LEDs connect via segment output pin SEG9 and grid output pins GRID1-GRID8.

Image17

The push button switches are multiplexed together and read via key state output pins KS1-KS8 and key scan data input pins K1-K3. Note key state output pins KS1-KS8 share the same physical pins as the segment output pins SEG1-SEG8. The device simply alternates between driving the segment outputs and scanning the key input states.

Communication with the device is via a 3 wire serial interface comprising of a strobe (STB), data input/output (DIO) and a clock (CLK) pin. The strobe pin is used to initialise the serial interface prior to sending data. The state of the DIO pin is clocked into the device on the rising edge of the clock signal. Similarly when reading from the device the state of DIO pin may be read on the rising edge of the clock signal.

Image31

Operation

The protocol used to communicate with the TM1638 is a fairly simple one. The transfer is initialised by first pulling the strobe line low, a command byte is then sent followed by a number of optional arguments. The transfer is then terminated by returning the strobe pin high. Each command sent must begin with the strobe pin being pulled low and finish with the strobe pin being pulled high.

The device supports three command types. Determined by bits B7 and B6 of the command byte. Data instruction set 0b01 (0x4X) writes or reads data to or from the device. Display control instruction set 0b10 (0x8X) configures the device and Address instruction set 0b11 (0xCX) sets the display register address.

Image23

The first thing we want to do post power up is initialise the display. To do so we need to send the display control instruction set command 0x8X.

Image13

The table above shows the individual bit settings for this command and their relevant function. Bits B7 and B6 set the command type, fixed at 0b10 (0x8X). Bits B5 and B4 are irrelevant and set to 0b00. Bit B3 is used to turn the display ON (1) or OFF (0). The remaining bits B2, B1 and B0 configure the display intensity. They actually set the pulse width which determines the display intensity. 1/16 (0x00) being the dullest and 14/16 (0x07) being the brightest.

For instance if we wanted to turn the display ON with the intensity set to maximum we would send the command 0b10001111 (0x8F). For minimum intensity we would send the command 0b10001000 (0x88).

The data instruction set command can perform a number of functions. We can set the data write mode to either write data to a data register or read the key scan data. We can set the address add mode to either automatically increment the destination address while writing data or write to a fixed destination address. There is also a test mode set function which we will not cover here.

Image24

Bits B7 and B6 set the command type and are fixed at 0b01 (0x4X). Bits B5 and B4 are irrelevant and set to 0b00. Bit B3 is used to set the test mode which we will keep set to 0 normal mode. Bit B2 sets the address add mode (0 automatically increments the address and 1 sets a fixed address). Bits B1 and B0 set the data write mode to be either a write to a data register (0b00) or read from the key scan data register (0b10).

The address instruction set command configures the destination address/register we wish to write. There are 16 display registers in total. Bits B7 and B6 set the command type and are fixed at 0b11 (0xCX). Bits B5 and B4 are irrelevant and set to 0b00. The remaining bits B3, B2, B1 and B0 set the display address (0x00 through 0x0F). The data stored in these registers is used to drive the display segments. Which we will cover in more detail soon.

Image27

This may all look very confusing so let’s cover a couple of examples which should make things a little clearer. Let’s say we want to send the value 0x45 to display register 0x02. The address mode will be fixed since we are only writing one register. So we send the command 0x44 (0b01000100) followed by command 0xC2 (0b11000010) followed by 0x45. If we wanted to send the values 0x01, 0x02 and 0x03 to display registers 0x00, 0x01 and 0x02. The address mode will be set to auto increment since we are writing consecutive registers. We would then send the command 0x40 (0b01000000) followed by command 0xC0 followed by 0x01, 0x02 and 0x03.

To read the state of the keys we need to send the command 0x42 (0b01000010) before reading back 4 bytes containing the key states. The details of which we will cover in more detail further on.

As already mentioned the display registers allow us to set all of the individual segment states. The mapping of each segment in the display registers can be seen in the table below.

Image6

For instance segment output pins SEG1-SEG8 which control the seven segments (A-G) plus the decimal point (DP) on grid output pin GRID1 are mapped to display register 00HL (the lower 4 bits of the display register 0) and 00HU (the upper 4 bits of the display register 0). The remaining two segment output pins SEG9 and SEG10 are mapped to the lower two bits of display register 01HL. The remaining bits of display register 1 have no function. This is then repeated for digit 2 using registers 02HL/02HU and 03HL/03HU etc.

The module I have has single LEDs all connected to segment output pin SEG9 and no connections to segment output pin SEG10. Some of the modules available on eBay have bi-colour LEDs on which I assume SEG9 is used to drive one colour and SEG10 to drive the other.

Again this may appear somewhat confusing so let’s have another example. Let’s say we wanted to show ‘0’ on display digit 1. In order to show ‘0’ we need to set segments SEG1 (a), SEG2 (b), SEG3 (c), SEG4 (d), SEG5 (e) and SEG6 (f) while segments SEG7 (g) and SEG8 (DP) remain unset. We do this by loading display register 0 with the value 0b00111111 (0x3F). For display digit 2 we load display register 2 with 0b00111111 (0x3F) etc.

digits

Similarly if we wanted to energise the LED1 we would need to set SEG9 by loading display register 1 with 0b0000001 (0x01). For the LED2 we would load display register 3 with 0b0000001 (0x01) etc.

When it comes to reading the input keys. A maximum of 24 keys may be read by the device. These are all arranged in a 3×8 matrix format. The key states returned from the read key scan data command as four bytes encoded as follows.

Image10

BYTE1 contains the key input states K3 (B0), K2 (B1) and K1 (B2) for key state output KS1 and K3 (B4), K2 (B5) and K1 (B6) for key state output KS2. BYTE2 corresponds to key state outputs KS3 and KS4. BYTE 3 corresponds to key state outputs KS5 and KS6 and BYTE4 corresponds to key state outputs KS7 and KS8. Bits B3 and B7 in all four bytes are irrelevant and are ignored.

For example to read the input state of the key corresponding to input state K2 and key state output KS8 we read bit B5 of BYTE4. To read the input state of the key corresponding to input state K3 and key state output KS1 we read bit B0 of BYTE1.

After a bit of trial and error I discovered the eight keys on my board are all mapped to input state K3. So by checking bits B0 and B4 of each of the 4 bytes read I was able to determine the state of all of the 8 keys.

I have created a basic driver for the TM1638 (written in C) using Atmel Studio. The driver has been tested on a ATMega328 (Arduino Nano) development board. However the code could easily be ported to any other platform if required. All of the project files and an example implementation are all available on my GitHub account.

Advertisements

Binary Clock

For a while now I have fancied building a binary clock. A clock capable of showing the current time in binary format. Not only do they look great but you have the added bonus that most people probably wont have any idea what an earth they are looking at. For those who have no idea what a binary clock is I would highly recommend this Wikipedia page for more information.

To keep things simple I decided on a Binary Coded Decimal (BCD) format rather than pure binary or grey scale. With BCD the hours, minutes and seconds are all represented by individual binary digits. That way you simply add each digit together to determine the number of hours followed by the number of minutes and finally the number seconds.

I wanted something fairly portable, preferably USB powered and reasonably cheap. I ended up using an Arduino Nano clone, a 0.96″ 128×64 OLED display module and a DS3231 real time clock module. This configuration fitted the bill perfectly.

I love these little OLED displays. They are extremely bright and easy to use. The majority if not all of the ones I found on eBay use the SSD1306 display driver chip. The SSD1306 supports a number of interfaces formats including parallel, SPI  and I²C. The module I purchased uses an I²C bus.

The DS3231 I have used on lots of projects before. These devices are extremely accurate with minimal drift over time. Not a lot to say about these really the device maintains seconds, minutes, hours, day, date, month, and year information. Has two configurable alarms and even has an internal temperature sensor (which is used for temperature compensation of the oscillator) but can also be read via one of the internal registers. Data is transferred serially through an I²C bus.

The OLED display supports two colours. The very top of the display uses yellow on black while the remainder of the display uses blue on black. I decided to take advantage of this and alternate between the current time, date and temperature along the top while showing the current time in binary format below using rectangles to represent each binary bit. I am really pleased with the way it turned out.

binary_clock

The design was implemented on breadboard and all of the code written in C using the Atmel Studio development environment. Rather than continually reading the time from the DS3231 its square wave output pin was configured to output a 1Hz square wave which in turn was connected to one of the external interrupts pins on the ATMega328. Upon interrupt the interrupt handler simply sets a flag to indicate to the main exec to read the time and refresh the display. The clock has been running fine now for around four months with no issues at all. All of the project files and source code will be available in my GitHub account.

Stellaris Mod Player V2

After having issues with the latest version of Code Composer Studio from TI I decided to port this project over to Keil uVision instead. While doing so I took the opportunity to tidy up the code and add a few new features.

One such feature is auto play next module. If enabled next module in the list will play automatically. Auto play is enabled by simply placing a empty file named “autoplay.dat” in the mods folder of the SD card which is detected during power up.

Auto play WILL NOT work with all modules. To detect the end of the module has been reached the code looks for the point where the playback index equals the total song length which would normally roll back to the start of the module. If the composer has used JUMPTOORDER the module may never reach the end. There is no way to determine if the jump is legitimate or the end of the module has been reached.

Source and project files compatible with Keil uVsion V5 are all available in my GitHub account along with pre built images in binary and hex format.

 

Udemy Learning and FPGA Development

I recently signed up with Udemy which claims to be the world’s largest destination for on-line courses. A rather tall order and time will tell. However they do have a huge selection of courses available covering everything from programming to break dancing!! Some of courses are rather expensive but they regularly offer special promotional rates on a lot of these courses that does make them more attractive.

425522_6fbe_12

I have just completed my first course a course created by a reader of this blog by the name of Jordan Christman. Jordan’s course entitled “VHDL and FPGA Development for Beginners and Intermediates” is an excellent introduction to VHDL and FPGA development. Jordan introduces the basics of FPGAs and VHDL before moving onto the development tools used and then takes a hands on approach developing, simulating and running simple designs on target hardware platforms. I can thoroughly recommended this course to anyone wanting to get into FPGA development. Well done Jordan.

Arcade Controller Conversion…Part 2

arcade2With the case now modified to fit the new joystick and buttons I moved onto the interface board. Rather than buying one I decided from the onset that I was going to design my own. Those you buy of the shelf in my opinion are just way overpriced for what you get. The majority of which are keyboard encoders. All I needed after all was a simple joystick to USB converter with support for up to eight buttons. The design I came up with encompasses a Minimus AVR development board I already had lying around. These boards are ideal for USB projects. They feature an Atmel  AT90USB162 micro controller with full speed USB controller which makes implementing USB applications a breeze. For the firmware I opted to use the LUFA (Lightweight USB Framework for AVRs, formerly known as MyUSB). I have used LUFA a lot in recent years because I find it extremely easy to implement as well as being extremely well documented.

arcade_controllerWith the design laid out on strip board I started on the firmware. Rather than starting from scratch I took the joystick class driver example provided with the LUFA and begun modifying it to suit my needs. The example project implements a Human Interface Device (HID) class joystick driver. Since most operating systems support the USB HID classes out of the box there is no need for any third party drivers. Within the project is a call back function used to create the HID report to be transferred to the host. All that was needed was, during the call back function, to read the joystick and button states and add them to the generated report. The main USB call would then take care of transferring this report to the host.

 bool CALLBACK_HID_Device_CreateHIDReport( USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo,  
                                           uint8_t* const ReportID,  
                                           const uint8_t ReportType,  
                                           void* ReportData,  
                                           uint16_t* const ReportSize)  
 {  
      /* New joystick report */  
      USB_JoystickReport_Data_t* JoystickReport = (USB_JoystickReport_Data_t*)ReportData;  
      /* Get joystick states */  
      uint8_t JoystickStatus_LCL = ( JOYSTICK_PORT_PIN & JOYSTICK_ALL_MASK ) ^ JOYSTICK_ALL_MASK;  
      /* Get button states */  
      uint8_t ButtonStatus_LCL = ( BUTTONS_PORT_PIN & BUTTONS_ALL_MASK ) ^ BUTTONS_ALL_MASK;  
      /* Check joystick Y axis */  
      if( JoystickStatus_LCL & JOYSTICK_UP_MASK ) JoystickReport->Y = -100;  
      else if( JoystickStatus_LCL & JOYSTICK_DOWN_MASK ) JoystickReport->Y = 100;  
      /* Check joystick X axis */  
      if( JoystickStatus_LCL & JOYSTICK_LEFT_MASK ) JoystickReport->X = -100;  
      else if( JoystickStatus_LCL & JOYSTICK_RIGHT_MASK ) JoystickReport->X = 100;  
      /* Check joystick buttons */  
      if( ButtonStatus_LCL & BUTTONS_BUTTON1_MASK ) JoystickReport->Button |= (1 << 0);  
      if( ButtonStatus_LCL & BUTTONS_BUTTON2_MASK ) JoystickReport->Button |= (1 << 1);  
      if( ButtonStatus_LCL & BUTTONS_BUTTON3_MASK ) JoystickReport->Button |= (1 << 2);  
      if( ButtonStatus_LCL & BUTTONS_BUTTON4_MASK ) JoystickReport->Button |= (1 << 3);  
      if( ButtonStatus_LCL & BUTTONS_BUTTON5_MASK ) JoystickReport->Button |= (1 << 4);  
      if( ButtonStatus_LCL & BUTTONS_BUTTON6_MASK ) JoystickReport->Button |= (1 << 5);  
      if( ButtonStatus_LCL & BUTTONS_BUTTON7_MASK ) JoystickReport->Button |= (1 << 6);  
      if( ButtonStatus_LCL & BUTTONS_BUTTON8_MASK ) JoystickReport->Button |= (1 << 7);  
      /* Set report size */  
      *ReportSize = sizeof(USB_JoystickReport_Data_t);  
      return false;  
 }  

Every USB device must contain an embedded device descriptor which describe to the host information such as what the device is, who makes it, what version of USB it supports etc. This device descriptor contains a vendor ID (a 16-bit number) which is assigned by the USB Implementers Forum to a specific company. Who in turn assign a product ID (again a 16-bit number) to individual products. In the case of the LUFA joystick example the vendor ID and product ID used are 0x03EB and 0x2043 respectively. Refering to the USB ID Repository on the Linux USB home page shows the registered vendor ID as “Atmel Corp.” (no surprise there) and the product ID as “LUFA Joystick Demo Application”. The VID and PID are communicated to the computer when the device is plugged in, along with text strings describing the vendor and product as well as additional descriptors. I choose to modify these vendor and product strings to something more meaningful to me.

output_BGcEfUWith the code compiled and programmed into the AT90USB162 the first test was to check it worked under windows. Once plugged in windows identified the device and installed a suitable driver. Then using the game controllers configuration wizard I was then able to confirm the joystick and buttons were functioning correctly.

Moving on time to start testing on the Raspberry Pi. RetroPie detected the controller and allowed it to be configured for use. This is fine for navigating the RetroPie front end but in order to use it with any of the emulators (apart from MAME who has its own set-up) the controller needs registering for use with RetroArch. So exiting RetroPie and checking available USB devices shows the device has been detected on bus 001 device 005. The vendor ID and product ID clearly shown.

 Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp.  
 Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. 
 Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
 Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. 
 Bus 001 Device 004: ID 7392:7811 Edimax Technology Co., Ltd EW-7811Un 802.11n Wireless Adapter [Realtek RTL8188CUS]
 Bus 001 Device 005: ID 03eb:2043 Atmel Corp. LUFA Joystick Demo Application 

Then registering the controller for use with RetroArch using the “Register RetroArch controller” script. You can see the modified manufacturer and product strings read by the script.

  Using joypad: Mikes Modz Arcade Controller  
 Joypads tend to have stale state after opened.  
 Press some buttons and move some axes around to make sure joypad state is completely neutral before proceeding.  
 When done, press Enter ...   
 Configuring binds for player #1 on joypad #0.  
 B button (down)  
      Joybutton pressed: 0  
 Y button (left)  
      Joybutton pressed: 1  
 Select button  
      Joybutton pressed: 2  
 Start button  
      Joybutton pressed: 3  
 Up D-pad  
      Joyaxis moved: Axis 1, Value -32767  
 Down D-pad  
      Joyaxis moved: Axis 1, Value 32767  
 Left D-pad  
      Joyaxis moved: Axis 0, Value -32767  
 Right D-pad  
      Joyaxis moved: Axis 0, Value 32767  
 A button (right)  
      Joybutton pressed: 4  
 X button (top)  
      Joybutton pressed: 5  
 L button (shoulder)  
      Joybutton pressed: 6  
 R button (shoulder)  
      Joybutton pressed: 7  
 L2 button (trigger)  
      Timed out ...  
 R2 button (trigger)  
      Timed out ...  
 L3 button (thumb)  
      Timed out ...  
 R3 button (thumb)  
      Timed out ...  
 Left analog X+ (right)  
      Timed out ...  
 Left analog X- (left)  
      Timed out ...  
 Left analog Y+ (down)  
      Timed out ...  
 Left analog Y- (up)  
      Timed out ...  
 Right analog X+ (right)  
      Timed out ...  
 Right analog X- (left)  
      Timed out ...  
 Right analog Y+ (down)  
      Timed out ...  
 Right analog Y- (up)  
      Timed out ...  
 Writing autoconfig profile to: /opt/retropie/emulators/retroarch/configs/tempconfig.cfg.  
 input_player1_joypad_index = "0"  
 input_player1_b_btn = "0"  
 input_player1_y_btn = "1"  
 input_player1_select_btn = "2"  
 input_player1_start_btn = "3"  
 input_player1_up_axis = "-1"  
 input_player1_down_axis = "+1"  
 input_player1_left_axis = "-0"  
 input_player1_right_axis = "+0"  
 input_player1_a_btn = "4"  
 input_player1_x_btn = "5"  
 input_player1_l_btn = "6"  
 input_player1_r_btn = "7"  
 = = = = = = = = = = = = = = = = = = = = =  
 Configuring RetroArch-AutoConfigs  
 = = = = = = = = = = = = = = = = = = = = =  
 Remapping controller hotkeys  
 Processing /opt/retropie/emulators/retroarch/configs/2Axes11KeysGamePad.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/ControlBlockArcadeGamepad.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/ControlBlockSNESGamepad.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/DragonRise_Inc.___Generic___USB__Joystick__.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/Generic_X-Box_pad.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/GreenAsia_Inc.____USB_Joystick_____.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/HuiJiaSNEStoUSBConverter.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/JessTechColourRumblePad.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/LogitechGamepadF710.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/LogitechLogitechCordlessRumblePad2.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/Logitech_Logitech_Dual_Action.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/Logitech_Logitech_RumblePad_2_USB.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/Microsoft_Sidewinder_Dual_Strike_USB_version_1.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/Microsoft_X-Box_360_pad.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/MikesModzArcadeController.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/MY-POWER_CO__LTD__2In1_USB_Joystick.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/PS3ControllerBT.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/PS3Controller.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/PS3ControllerUSB.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/RockCandyGamepadforPS3.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/Saitek_P880.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/SNES-to-GamepadDevice.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/Sony_PLAYSTATION(R)3_Controller.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/Thrustmaster_Dual_Trigger_3-in-1.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/THRUSTMASTER_FireStorm_Dual_Analog_2.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/Thrustmaster_T_Mini_Wireless.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/USB_2-axis_8-button_gamepad.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/USB_Gamepad.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/USBGamepad.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/Xbox_360_Wireless_Receiver.cfg  
 Processing /opt/retropie/emulators/retroarch/configs/XboxGamepad(userspacedriver).cfg  

With the controller now registered it was time to start playing. First up Teenage Mutant Ninja Turtles on the SNES which worked great. The Seimitsu joystick has a great feel with lovely movement not too stiff but not too loose either. The buttons respond perfectly with minimal effort, if anything they are little bit to sensitive but that’s fine.

Arcade Controller Conversion…Part 1

I love playing retro arcade games and most recently I have taken to playing these classic games on my Raspberry Pi 2 using the RetroPie. For those who aren’t aware the RetroPie project is a collection of works that all have the overall goal to turn the Raspberry Pi into a dedicated retro-gaming console. Up until now I have been using a couple of cheap USB NES controllers. These controllers are great but nothing compares to the feel of an authentic arcade controller. So I set about building my own controller that could be plugged directly into my Raspberry Pi.

arcade4The first issue would be the enclosure. Do I build something? Or do I buy a flat packed kit from eBay? Neither option was going to be cheap. Then I stumbled on someone selling a second hand PlayStation arcade controller on eBay which at the time was listed at 99p plus postage. I figured that has got to be worth a punt. Even if I can only salvage the case and possibly a few other parts then so be it. So I placed a bid and ended up winning it for just over a pound. Result! I wasn’t expecting a lot but was pleasantly surprised when it arrived. The build quality was a lot better than I had expected. It had obviously seen a fair bit of use and the joystick micro switches were worn out. But still I wasn’t disappointed as the case was in great condition.

arcade5I begun disassembling it as soon as it arrived. I removed the bottom plate, the cable, the joystick and the eight push buttons. I had no plans to use the interface board so I de-soldered all of the connections and left the PCB in situ. Next I measured the holes for the push buttons which turned out to be 30mm which was good news since this a standard size for arcade buttons. Selecting a suitable joystick proved to be more troublesome. There isn’t a lot of clearance for the joystick once mounted so I needed to find one with the lowest possible profile.

In the end I went for a Seimitsu LS-58-01and Seimitsu PS-15 buttons. The LS-58 a great quality small form factor joystick. Not to stiff and with an adequate throw distance for my liking. The micro switches are soldered directly into a PCB. This worked out perfect as I later went on to use the PCB to mount it to the original mounting posts. Being 30mm the PS-15’s fitted perfectly with no modifications required. The joystick however required a few modifications. In order to drill the mounting holes in the PCB I first had to remove the common connections for each micro switch. Rather than completely removing the micro switches I simply cut the tabs and de-soldered. Then after drilling the holes I used Kynar wire to re-connect them.

arcade1The next problem was getting the joystick to fit flush with the case. Because of the way the case had been moulded I needed to remove some of the plastic surrounding the stick. Thankfully this didn’t comprise the rigidity of the joystick. The end result wasn’t pretty but at least it now fitted flush with the case. I finished off by connecting all of the switches to my new interface board which I will move onto in the next post.

TP-Link WDR4300 Router Recovery

My new wireless router decided to stop working at the weekend. It has been behaving rather strange for the past few days. I’ve tried power cycling in the vain hope a reboot may fix the issue. Which it didn’t. And to make things worse now it wont even boot. Pinging it gets no response either. The original firmware was replaced with DD WRT, a great Linux alternative firmware, so the first thing I did was head over to the DD WRT homepage for help. I tried the recommended 30/30/30 reset procedure which didn’t solve the issue.

Luckily there is a ton of resources out there surrounding these routers. Once such resource I found extremely useful was the OpenWRT Wiki page. Apparently the WDR4300 has an Atheros AR9344 SOC running at 560 MHz with 8MB of flash and 128 MB DRAM. It also has both a JTAG and serial programming/debug connection. The flash memory is made up of the boot loader (U-Boot), the operating firmware and the ART (which contains MAC addresses and calibration data for the wifi). It’s also the boot loaders responsibility for configuring the serial interface.

WDR4300_1With nothing to lose I begun disassembling the router to see if I could get access to this serial port and maybe diagnose the problem. After removing the aerials and the screws holding the case together I had the router disassembled. There are two unpopulated headers one 14 pin, which I assume must be the JTAG connection, and one 4 pin which turns out to be the serial connection. The four pins comprising the two supply lines and the TX and RX lines. I soldered in a pin header to the board and connected my USB to serial converter. The connection settings according to the OpenWRT Wiki page are 115200 8N1 with no flow control. Using puTTY I managed to capture the routers output during power up.

 U-Boot 1.1.4 (Apr 25 2012 - 18:29:12)  
 U-boot DB120  
 DRAM: 128 MB  
 id read 0x100000ff  
 flash size 8MB, sector count = 128  
 Flash: 8 MB  
 Using default environment  
 In: serial  
 Out: serial  
 Err: serial  
 Net: ag934x_enet_initialize...  
 No valid address in Flash. Using fixed address  
 wasp reset mask:c03300  
 WASP ----> S17 PHY *  
 : cfg1 0x7 cfg2 0x7114  
 eth0: ba:be:fa:ce:08:41  
 athrs17_reg_init: complete  
 eth0 up  
 eth0  
 Autobooting in 1 seconds  
 ## Booting image at 9f020000 ...  
 Uncompressing Kernel Image ... OK  
 Starting kernel ...  

The output shows the boot loader initialising the serial port. After initialisation the boot loader attempts to auto boot the image at address 0x9F020000. Firstly by uncompressing the kernel image and then starting it. Additional serial output follows before we encounter a raft of file system page read errors before stopping.

 start service  
 starting Architecture code for wasp  
 [ 1.960000] SQUASHFS error: lzma returned unexpected result 0x1  
 [ 1.960000] SQUASHFS error: Unable to read page, block c523, size dd54  
 [ 2.030000] SQUASHFS error: lzma returned unexpected result 0x1  
 [ 2.030000] SQUASHFS error: Unable to read page, block c523, size dd54  
 [ 2.100000] SQUASHFS error: lzma returned unexpected result 0x1  
 [ 2.100000] SQUASHFS error: Unable to read page, block c523, size dd54  
 [ 2.170000] SQUASHFS error: lzma returned unexpected result 0x1  
 [ 2.170000] SQUASHFS error: Unable to read page, block c523, size dd54  
 [ 2.240000] SQUASHFS error: lzma returned unexpected result 0x1  
 [ 2.240000] SQUASHFS error: Unable to read page, block c523, size dd54  
 [ 2.310000] SQUASHFS error: lzma returned unexpected result 0x1  
 [ 2.310000] SQUASHFS error: Unable to read page, block c523, size dd54  
 [ 2.380000] SQUASHFS error: lzma returned unexpected result 0x1  
 [ 2.380000] SQUASHFS error: Unable to read page, block c523, size dd54  
 [ 2.450000] SQUASHFS error: lzma returned unexpected result 0x1  

So it appears the firmware may have become corrupted. The obvious next step then was to try re-flashing it. After some reading around on the subject (being a complete Linux novice) I discovered the boot loader supports network transfer via the Trivial File Transfer Protocol (TFTP). So it is possible to transfer the firmware image via TFTP and re-flash it.

The first step was to download the required firmware images and other supporting software. I downloaded the DD-WRT firmware image files from here and then downloaded and installed the client side TFTP server program TFTPD32 from here.

The next step was to break execution of the boot loader in order to run tftpboot. To do this, within putty after power cycling, simply wait for the message “Autobooting in 1 second” to appear begin typing “tpl” and pressing enter until the sequence stops and the prompt “db12x>” appears. Once the prompt appeared I ran “tftpboot”. I then made a note of the expected server address “192.168.1.100” and load address “0x81000000”. I then exited tftpboot by pressing Ctrl+C.

 db12x> tftpboot  
 dup 1 speed 1000  
  Warning: no boot file name; using '6F01A8C0.img'  
 Using eth0 device  
 TFTP from server 192.168.1.100; our IP address is 192.168.1.111  
 Filename '6F01A8C0.img'.  
 Load address: 0x81000000  
 Log: *  
 TFTP error: 'Access violation' (2)  
 Starting again  

With the router connected directly via an ethernet cable and all wireless adaptors disabled I set the network adaptor to the static IP address “192.168.1.100”. I then ran TFTPD32, browsed for the folder containing the firmware images and set the server address to “192.168.1.100”.
I then ran “tftpboot” again with required load address and source firmware parameters. The new firmware then begun transferring. Upon completion the message “done” appeared and the number of bytes transferred was shown.

 db12x> tftpboot 0x81000000 factory-to-ddwrt.bin  
 Using eth0 device  
 TFTP from server 192.168.1.100; our IP address is 192.168.1.111  
 Filename 'factory-to-ddwrt.bin'.  
 Load address: 0x81000000  
 Lg: #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      #################################################################  
      ############################  
 done  
 Bytes transferred = 8126464 (7c0000 hex)  

Before the new firmware image could now be copied the destination flash the destination flash first needed to be erased. The destination address being 0x9F020000 (which we know from the initial captured output) and destination length being the size of the transferred firmware image 0x7C0000 bytes.

 db12x> erase 0x9f020000 +7c0000  
 First 0x2 last 0x7d sector size 0x10000                                                                                                 125  
 Erased 124 sectors  

Once erased the new firmware could then be copied over from the destination flash. Again the destination address being 0x9F020000, the source address being 0x81000000 and the length 0x7C0000 bytes.

 db12x> cp.b 0x81000000 0x9f020000 0x7c0000  
 Copy to Flash... write addr: 9f020000  
 done  

So that all appeared fine all that all that remained was to reboot and hopefully everything would be working.

 db12x> reset  

Much to my relief that appeared to have fixed the issue. The router now booted fine and I was able to access to the DD-WRT web interface. WDR4300_7I then went ahead and performed a web flash using the web flash firmware image file previously downloaded. I am not entirely sure this stage was necessary but some of the guides I had read previously did and I cant imagine it would do any harm doing so.

So overall a great result. Saved myself some money not having to rush out and buy a new router and learnt a little bit about Linux in the process.