Friday 13 September 2013

An AVR-Based Microstepping Bipolar Chopper Stepper Motor Driver (STMD)

Features

  • Open Source - The schematic, parts list, and software are all freely downloadable!
  • Hobbiest-friendly - No surface mount parts means allows this drive to be easily repaired!
  • DMOS driver chips rated at 55V and 3 Amps.
  • Easy parts availability - Electronic parts are all available at Digikey. Just add your own heat sink and mounting hardware.
  • DIP Switch selectable modes of stepping:
    1. Full Step, Both Phases On
    2. Full Step, Wave Drive
    3. Half Step
    4. Quarter Step
    5. Sixth Step
    6. Eighth Step
    7. Tenth Step
    8. Twelfth Step
    9. Sixteenth Step (available on special request in place of Twelfth Step mode with a special version of firmware)

  • Optically isolated step, direction, & enable signals.
    These signals are ground referenced so no +5VDC is required - hook directly to your parallel port.

  • Selectable automatic idle current reduction to reduce motor heating.
    Motor current can be reduced if no steps are received for 3 seconds. Oscilloscope Image, 202 kb
  • DIP switch selectable motor test mode.
    Allows for testing of motor/drive/wiring without an externally generated step signal.
  • Motor braking can be activated when drive is disabled.
    Alternatively, motor braking can also be disabled such that the motor will freewheel when drive is disabled.
  • Motor current is adjustable by two potentiometers. (One for each motor phase)
    Fixed resistors may be substituted in place of the potentiometers for cost savings or increased reliability.
  • Removable screw terminals for all wiring connections.
  • Status LED flashes during normal operation to let the user know the microcontroller is running.
    Illuminates solid during a fault condition.
  • Quality double-sided printed circuit board with ground plane and silkscreen legend.
    Board size is 3.25" x 4.30" with mounting holes for #6 screws in each corner.

Design & Configuration

  • An Atmel Mega48 running on its internal 8 MHz oscillator is the brain of the system. A 10 pin ISP connector is provided in the event the user would like to modify the open source firmware. The program memory is only about 40% full so there is plenty of room for an enterprising user to add to the custom features and functions. Normal operation of the microcontroller is indicated by flashing of the 'alive' LED once per second. If a fault condition is detected, the LED will be latched on until power is cycled to the board.
  • For power switching, National Semiconductor�s LMD18245T 3A, 55V DMOS Full-Bridge Motor driver chips are used. These driver chips are the most expensive part of the STMD as two of them are required per motor. Although these driver chips are rated for +55 VDC, allowance should be made for back emf from the motor. As such, I recommend a maximum of +45 VDC.
  • For flexiblity, DIP switches are used to select the mode of stepping. As described below, eight modes of stepping are available.
    SW3   SW2   SW1      Stepping Mode          Steps/Revolution
    ---   ---   ---   --------------------     ------------------
    Off   Off   Off   Full, Both Phases On           200
    Off   Off   On   Full, One Phase On           200
    Off   On    Off   Half                            400
    Off   On    On   Quarter                         800
    On    Off   Off   Sixth                          1200
    On    Off   On   Eighth                         1600
    On    On    Off   Tenth                          2000
    On    On    On   Twelfth/Sixteenth*             2400/3200 
    
    *Sixteenth step mode requires a special firmware version - available upon special request.
    

  • SW4 in conjunction with JP1 and two pull-up/down resistors selects the method of motor braking.
    • SW4 off, JP1 on, pull-up resistor R10 populated - Motor braking active when drive is disabled.
    • SW4 on, JP1 on, pull-up resistor R10 populated - Motor is allowed to freewheel when drive is disabled.
    • JP1 off and pull-down resistor R11 populated - Motor braking always disabled.
    • All other combinations of SW4, JP1 and pull-up/down resistors are invalid and drive operation is undefined. DO NOT populate both R10 and R11!

  • SW5 is used to enable test mode. This allows the user to test their wiring and drive/motor operation with no external step signal. During test mode, the drive will internally generate a step signal to rotate the motor at approximately 1 revolution per second. The drive must be enabled and will respond to the direction pin. To enable the drive with no external enable signal, you can temporarily connect J1-3 (Com) to J3-1 (Opto Com) and J1-1 (+5Vout) to J3-4 (Enable). The motor will change directions if J1-1 (+5Vout) is connected to J3-2 (Direction).
  • JP2 is used to enable/disable automatic idle current reduction. When JP2 is on, idle current is reducted when no step signal has been received for 3 seconds. Full motor current is restored upon the next step signal. The amount of idle current reduction can be manipulated by changing R9 in the R8/R9 voltage divider. The default value of 1k allows for a 50% reduction. Using a 2k in place of R9 will allow for a 33% reduction.
  • Current Limiting is set by adjusting potentiometers R4 and R5. The resistance as seen from pin 13 to ground on each of the LMD18245T driver chips (test points 6 & 7) should be set to 18750/I where I is the maximum desired winding current. (Where this comes from?) R2 and R3 (6.2 kohm resistors) are in series with the potentiometer to prevent the motor current from being set higher the driver chips's maximum rate load current. Potentiometers R4 and R5 should be set to identical values. Here is a picture (246 kb) showing how to adjust R4 when measuring from TP6.
        Maximum
         Motor
        Current    Resistance
        --------   ----------
        0.6 Amps   31.3  kohm
        0.8 Amps   23.5  kohm
        1.0 Amps   18.8  kohm
        1.2 Amps   15.7  kohm
        1.4 Amps   13.4  kohm
        1.6 Amps   11.8  kohm
        1.8 Amps   10.5  kohm
        2.0 Amps    9.38 kohm
        2.2 Amps    8.53 kohm
        2.4 Amps    7.82 kohm
        2.6 Amps    7.22 kohm
        2.8 Amps    6.70 kohm
        3.0 Amps    6.25 kohm
    
  • Heat sinking is required for the driver chips and voltage regulator. I have had good success using this heat sink, made from a piece aluminum, with a small fan blowing on it. Heat sink sizing depends on motor current, ambient temperature, airflow, etc. Enabling automatic idle current reduction helps reduce unnecessary heating. The temperature of the driver chips and voltage regulators must not exceed 110°C. Be sure to use a thermally conductive grease between the heat sink and the driver chips/voltage regulator.

Wiring Connections

  • Motor connections (including motor voltage) are made on the removeable screw terminals on right side of the board.
    1. V+ is the Motor Power and must be externally fused. (TP4) The driver chip can is rated up to +55 VDC, but allowance should be made for back emf from the motor. As such, I recommend a maximum of +45 VDC.
    2. V- is the Motor Power common (TP5)
    3. Motor Winding, B+
    4. Motor Winding, B-
    5. Motor Winding, A+
    6. Motor Winding, A-

  • Logic power connections are also made on removeable screw terminals on the right side of the board.
    1. Vout is the +5 VDC output from the on-board LM7805 voltage regulator. (TP1)
    2. Vin is the +8-20 VDC input to the on-board voltage regulator. At higher input voltages, the LM7805, heat sinking will be required. (TP2)
    3. Com is the DC Common for the two previously mentioned signals. (TP3)

  • Connections for the optically isolated signals are made on the removeable screw terminals on the bottom edge of the board.
    1. Opto Com is the DC Common for the optically isolated signals. This is not connected to the the logic DC common. (TP8)
    2. Direction - The motor will revese directions when a +5 VDC signal is applied. This signal must be set 2 us prior to the step signal.
    3. Step - The motor will take a step on the rising edge of a +5 VDC signal when it is applied to this terminal. Maximum step frequency is 100 kHz. The Direction signal must be set for 2 us prior to the step signal becoming active.
    4. Enable - The motor will be disabled unless a +5 VDC signal is applied to this terminal. If you do not have an enable signal and want your drive to always be enabled... do not populate OP1 and where OP1 would have been installed, short the pads corresponding to pins 4 and 5. A photo of this modification can be found here. (206 kb) Please note that the motor shaft may 'jerk' when the drive is enabled/disabled; exact position is not maintained.

    Be sure to double and triple check all connections before power is applied!!


Schematics


AVR thermometer

Build a simple digital thermometer using AT90S2313 and DS1820. Download the hex code directly to the chip with ISP cable. The thermometer is capable of measuring temperature from -55C to +125C with 1 degreeC resolution.


Introduction

I bought the LED module from BanMor' last week, just 30Baht. The module provide a prewired multiplex of 4-digit common anode LED, that's great. See the soldering pad of these signal in the 1st picture below. I thought, my friend gave me the AT90S2313 chip, and with a simple homemade ISP cable and a free software AVR ISP downloader, so we don't need the device programmer. Also I got the DS1820 from my friend. So I spent my freetime weekend putting them together, "the AVR Thermometer".  The 2nd picture shows a sample circuit using universal PCB, see the upper right,  it was 10-pin header for STK200 compatible downloader. Of course the LED module was designed for CLOCK display, so you may interested in writing C program for both Clock and Temperature display after built this project.


Circuit
The circuit diagram is straight forward, similar to 89C2051 Clock Circuit. PORTB sinks forward current of each LED segment through a 220 Ohms resistor. The common anode pin for each digit is controlled by PORTD, i.e., PD2-PD5. The PNP transistor can be any types of small signal transistor. S1-S4 are optional keypad. Also PD6 is optional output. All of LEDs are driven with sink current.
The temperature sensor chip, DS1820 is connected to PD1 with a 4.7k pull-up resistor. The example of circuit uses only one sensor, you may tie multiple sensors on the same line and modify the program to read it with the help of internal chip ID.
J3 is AVR ISP/STK200 compatible downloader. See the pin designation and build the cable from Experimenting the AT90S8535 with CodeVisionAVR C Compiler
The software for download the hex code, thermo.hex to the 2313 chip, can get from ATMEL FTP website here ATMEL AVR ISP.
Software
The source porgram was written in C language and compiled with AVR CodeVision C compiler. Actually my first try used the AT90S8535, then I moved the C code from 8535 to 2313 with a bit modification.
The program was foreground and background running, or interrupt driven. The background task is forever loop scanning 4-digit LED.
main()
{
while (1)
      {
       scanLED();  //  run background task forever
       }
}
The function scan display was borrowed from 89C2051 Clock controller. (Actually no need any modifications if we use a preprocessor to define such I/O port.)
#define segment PORTB
#define LED_digit PORTD
void scanLED() /* scan 4-digit LED and 4-key switch, if key pressed key = 0-3
else key = -1 */
// adapted from 89C2051 project  if needs scan key, find one bit input port
{
    char i;
    digit = 0x20;
    key = -1;
    for( i = 0; i < 4; i++)  /* 4-DIGIT scanning */
    {
        LED_digit = ~digit;  /* send complement[digit] */
        segment = ~heat[i];  /* send complement[segment] */
        delay_ms(1);         /* delay a while */
        segment = 0xff;        /* off LED */
     //   if ((PORTD & 0x10) == 0) /* if key pressed P3.4 became low */
     //      key = i;       /* save key position to key variable */
        digit>>=1;        /* next digit */
    }
}
for-loop statement provides 4-cycle scanning from digit0 to digit3. First we activate digit control line by sending complement of digit variable. Follow with segment data from heat[i] array. The delay 1ms provides enough time for electron to jump back to ground state converting to electromagnetic radiation. Then turn off before changing digit. The digit data is then shifted to the right one bit. Similarly for the next digit.
The foreground task is entered by timer0 interrupt every 1/15 s. Timer0 get the internal clock from 4MHz/1024 or 3906Hz. So with an 8-bit timer0 counting up from 0 to 255 and set interrupt flag from 255 to 0 transition, the rate of interrupt will be 3906Hz/256 or 15Hz.
I used a global variable "tick" for counting a number of interrupt entering. See below, when tick equal to 15 or one second has elapsed then function read_temp() will be executed.
 // Timer 0 overflow interrupt service routine
 // timer interrupt every 1/15 sec provides foreground task to be run periodically.
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
      switch (++tick){
        case 15: tick = 0;
        read_temp();
         }
}
When read_temp() function has entered, I used another global variable for couting up task that needs time
more than a second. The xtimer variable will count up until reachs 5, or 5s, it will read data from DS1820, performs 5-point moving average and convert the value to 7-segment pattern then.
read_temp()
{
    if(++xtimer1 >=5)
    {
     xtimer1 = 0;
     segment = 0xff; // turn segment off
       T =  ds1820_temperature_10(0)/10;     // read DS1820 every 5 sec.
       LPF();    // enter digital filter providing slowly change of temperature.
       heatToBuffer();         // convert it
     }
}
The function ds1820_temperature_10(0) was provided by CodeVision library.
See below what function of LPF() is;
LPF()      // performs five-point moving average
{
    X5=X4;
    X4=X3;
    X3=X2;
    X2=X1;
    X1= T;
    T = (X1+X2+X3+X4+X5)/5;
}
T is computed from 5-point consecutive data.
The heatToBuffer() is the function that converts signed number T to 7-segment LED pattern.
void heatToBuffer()
{
   if(T<0){
   heat[3] = 0x40;  // yes, negative, put -
   heat[0] = 0x39; // C
   T = abs(T);   // get only amplitude
   heat[1] =  convert[T%10];
   heat[2] = convert[T/10];
   if (heat[2] == 0x3f)
       heat[2] = 0; // off msd
                }
    else
    {
     heat[0] = 0x39; // C
     heat[3] = convert[T/100];
     temp = T%100;
     heat[1] = convert[temp%10];
     heat[2] = convert[temp/10];
    // off msd
     if (heat[3] == 0x3f)
         {
            heat[3] = 0;
            if(heat[2] == 0x3f)
             heat[2] = 0;
          }
      }
}

Reading data from 1-wire interface device, DS1820, takes about 0.2s, so when the program enter 1-wire connecting, the display will turn off, say 0.2s. Also the function that reads DS1820 will disable any interrupt, so we then loss such period of time.
Sample display shows 34 Celsius. DS1820 is located at bottom right.

Download
 
Schematic schematic.pdf
Source code thermo.c
Intel Hex file  thermo.hex
ATMEL generic ROM file thermo.rom


A HiFi Power Amp

A HiFi Power Amp



The recent aquisition of a pair of vintage Infinity RS-5b speakers prompted a search for an amplifier to drive them. According to the documentation that came with my speakers, an amplifier between 35W and 135W is recommended (not my 10W at 10% THD piece of garbage Sharp 3-in-1). Initially, I looked at commercial amplifiers (Yamaha, NAD, and Rotel), but was disappointed at their fairly pedestrian distortion figures. Thus a new hobby project was born :)
  • Stereo. Whilst I'll use it for watching movies, my flat just doesn't have the room for a full set of surround sound speakers. My main motivation is listening to stereo music sources, so a stereo amplifier is appropriate.
  • Low THD and IMD. In audio-speak, this translates either as clinical or as accurate. I'm an engineer by trade, so prefer terms like THD over "warm" or "cold" which could mean anything. Given that most of the commercial amps offer 0.02% THD or thereabouts, I figured a good target was 0.001%. This means the harmonics and intermods will be 100 dB below the fundamental, and will thus mean that the system performance will be dictated by the source (CD) material, rather than the amplifier.
  • Ample power. 100W seems a reasonable amount. I currently live in a two bedroom flat, and don't want my neighbours to kill me :) However seriously, I figured 100W gives me a clear 20dB headroom at a nominal listening level of 1W.
  • Low noise. I usually listen to my music at reasonably low level, so it's important for the amplifier input to have a low noise contribution.
  • Moderate cost. I'm happy to blow around $1000, as long as I get plenty of enjoyable hobby hours (and listening hours) as payback.
  • Good looks. This thing will (along with my preamp) live in my loungeroom. That means it needs to fit in. I don't want something that looks like an escapee from my shed, so a significant part of the design is involved with building a nice case.
  • Useability. This pertains more to the preamp, but I wanted the whole thing to be completely remote controllable.
Design Background
When I was a kid, one of my favorite monthly reads was ETI magazine. In January 1981, they published a series of articles describing the ETI477 MOSFET power amplifier, designed by David Tilbrook. This monoblock formed the basis of the "series 5000" HiFi amp. I desperately wanted to build one, but being all of 9 at the time, it wasn't going to happen.
Optimisation
There were a couple of drawbacks with the design. Firstly, it was based on obsolete TO-3 packaged lateral MOSFETs, and secondly the PCBs (like many kitset boards) were a pretty poor design anyway. I set to work redesigning the PCBs around modern flatpack equivalents (Hitachi 2SK1058 and 2SJ162). While I was at it, I swapped many of the remaining transistors for modern (faster) equivalents.
  • JFET input diffamp: SST404 (SO-8). Was ECG461.
  • Low power bipolars: MMBTA06/56 (SOT-23). Were BC547/557 and BC639/640.
  • Medium power bipolars: MJE340/350 (TO-126). Unchanged.
  • Power MOSFET drivers: 2SK1058/2SJ162 (TO-3P). Were 2SK176/2SJ56 (AEM6000) or 2SK134/2SJ49 (AEM6005 and ETI5000).
In order to dissipate 100W in an 8 Ohm speaker, one needs to put 28V RMS across the load. That's 40V peak. At the peak (assuming a resistive load) the amplifier needs supply 5A. Doing the SOA sums (more later) means that 2 pairs of drivers are needed. Further, the Vgs for the MOSFET can be around 10V at high current. This means the supply must be at least 10V greater than the peak output voltage. A twin 40V transformer is appropriate, with a peak secondary voltage of +/-56V.
  • Use the transistor over a very small operating range.
  • Use feedforward to cancel distortion (symmetry).
  • Use feedback to cancel distortion.
Pretty much all amplifiers use a combination of the three. Feedback has a bad name amongst "audiophile" types. Poorly thought out feedback (especially across multiple stages) can result in oscillation (usually at very high frequency, which isn't audible in itself, but destroys the performance of the amplifier. Feedback needn't all be global though. A robust scheme involves linearising each stage of an amplifier independently (for example with emitter degeneration), then using overall feedback (with appropriate compensation) to set the gain.

I came up with some nominal specifications:
  • The neat thing about the series 5000 is that it was built around new (at the time) Hitachi lateral power MOSFETs. Most power MOSFETs (VMOS, trenchFETs, HexFETs etc) use a vertical structure, where the current flows vertically. This has the advantage of stunningly low Rds and hence high efficiency, but does nothing for linearity or capacitance. Lateral MOSFETs are a much simpler structure, where the gate oxide is formed on a flat substrate, and the current flows across the substrate. This results in well defined, controllable device parameters, good linearity, and relatively low gate capacitance. However, the Rds of lateral MOSFETs is nothing to write home about. Most amplifiers at the time (and now as well) used bipolar output drivers. Bipolar transistors are cheap and plentiful. They have relatively high transconductance, and can operate reasonably fast. However they have some drawbacks when used at high power. The main one is thermal runaway. The gain of a bipolar transistor increases as it gets hotter. That means that if there's any imbalance between output transistors, the hottest one will pass most of the current, getting hotter until it ultimately expires. MOSFETs don't have this problem. Their gain decreases with temperature, so they share the load well. MOSFETs also have a high input impedance at low frequencies, and are capable (when driven by a suitable source) of extremely high slew-rates. Of course this very attribute makes them rather prone to HF oscillation when improperly compensated, but with careful design they're capable of impressive intermod performance. So having decided that now was the time to build a MOSFET amp, I wandered into the library at work and dug out the old ETI series 5000 amp articles, and had a read. I subsequently found that the series 5000 wasn't Tilbrook's final word on MOSFET amps. In 1987 he revisited the topic for a new magazine, Australian Electronics Monthly. This time (with the AEM6000 amplifier) he went all-out, with a matched-JFET differential input stage, and a complementary symmetrical voltage amplifier stage. A quest for super low distortion figures, with heaps of available gain. This looks like a good place to start.


I made the following active device substitutions:
  • Now that I'd changed the transistors, I had to play with the values of most of the other components as well, in order to get reasonable performance while maintaining stability. Firstly, I decided that rather than the usual 1V RMS full-power input, I'd increase this a bit, to 4V RMS. This allows me to use more of the available dynamic range of my preamp, and requires a gain of 7.2, or 17dB.Transistors (and valves) are inherently non-linear devices. They must be linearised, or else they'll distort the sound. There are three ways to achieve this goal:

  • I used Linear-Tech's free spice simulator to redesign the circuit around the newer parts. My main changes were to increase the emitter degeneration in each stage, to improve the linearity of each stage, at the expense of available overall open-loop gain. This is an approach that makes for an easily stabilised amplifier.A somewhat simplified schematic is shown below. Yes, it's a wonderfully complex beast of an amplifier. Heaps of symmetry, and plenty of stages, for ample open-loop gain. The schematic doesn't show the AEM6000 amp. It's my take on Tilbrook's design. The topology is the same, but the component values are different. For schematics of the AEM6000, you'll have to visit the library. Click on the schematic shown for a .pdf version of the real thing, including power supply decoupling and gate protection zeners etc.:





















Please note that the schematic shown is that of the physical amplifier, after some tweaking on the bench to improve stability. For reference, the amplifier developed through simulation used 220 Ohm MOSFET gate resistors, a 6.2K feedback resistor, and 15p miller compensation capacitors on the voltage amplifier. The results of optimisation are fairly pleasing. Firstly, with the input filter and feedback resistor removed, there's plenty of open-loop gain (>90dB). Even at 10KHz, we still have 60dB gain. If we set our gain to 7.2 (17dB) the phase margin is around 70 degrees.


When the feedback loop is closed, the gain curve is pleasantly flat out to 2MHz or so, with very little peaking.


Adding a 1nF cap to the input defines the -3dB point at 160KHz. The gain is flat within 0.1dB to 24KHz.


Using +/-56V supplies, A full-power (4V RMS input) 1KHz sine-wave comes through nice and cleanly, with 0.0010% THD.



A full-power (4V RMS) 10KHz sine-wave is still clean, with 0.0011% THD. This promising result indicates that the amp isn't suffering from slew-rate limiting.



The intermod performance is also fairly good. Feeding a full power 10 KHz and 11 KHz sinewave, the IMD products are 100dB down:


The simulated amp behaves nicely under overload. Here's the output with an 8V RMS 1KHz sinewave input:


PCB Layout

In order to be faithful to the simulations, I took great care with the physical layout of the amplifier. The PCB is double sided, 6.2" by 3.6", and is designed for manufacture using most through-hole plated processes. Minimum track and space is 15 thou. I've used 4oz plated boards to minimise wiring resistance. It uses mainly SMD (low lead inductance) parts, with the exception of the higher power resistors, capacitors, and transistors. I used metal film mini-melf resistors, as I've had good results with these at RF frequencies, and poly and mica capacitors to ensure minimal distortion creeps in through capacitor nonlinearities.


Thermal Design

The thermal design also required some attention. A 100W class A-B amplifier needs to dissipate significant power. It'd be a terrible pity to design an electrically good amp, only to have it blow up the first time it's cranked up. The power supplies are chosen to deliver 100 W into an 8 Ohm speaker. However, considerably more power can be delivered into a 4 Ohm speaker. Using LTspice, I was able to calculate the power dissipated in the output transistors simply by measuring their Id and Vds. For this amplifier, with +/-56V supplies, worst case dissipation occurs at 150 W into 4 Ohms, of 155 W. This is shared equally across the four output transistors.
The transistor's maximum temperature is 150 degrees C. The transistors I used have a 1.25 K/W thermal resistance between the die and surface of the case. Shared across all transistors, this equates to a die-to-heatsink thermal resistance of 0.3125 K/W. Assuming a perfect heatsink, we could dissipate 400 W. The maximum heatsink thermal resistance to dissipate 155 W is given by:


I chose a 75mm x 300mm x 48mm Conrad cast heatsink, with Rth of 0.37 K/W for each channel. These will form the sides of the case, and result in a relatively compact amplifier. The thermal resistance is adequate (just!) when used with 4 Ohm speakers, but is plenty low enough for use with 8 Ohm speakers. Due to the restricted height, I've mounted the four MOSFETS to a 40mm L shaped aluminium extrusion, which is then bolted to the heatsink.
The PCB is designed with all the power transistors mounted underneath. Bolts pass through the PCB, then the transistors, then into a heatsink, as shown in the diagram:


Tweaking

After building the amplifier, some tweaking was necessary to ensure stability. Clearly the models aren't perfect. Occasionally power-up transients would cause the amplifier to oscillate at 1.1 MHz. I found that adding an extra 15p (to 30p) of Miller capacitance on the voltage amp stage greatly improved stability. At the suggestion of some of the experts on the DIYAudio forum, I also increased the MOSFET gate resistors to 680 Ohm and 470 Ohm for the N and P channel devices respectively. I also reduced the feedback a little, by substituting a 15K resistor (24dB gain) rather than the 6.2K one.
The results of tweaking are an amplifier that's stable driving an 8 Ohm load with a 2.2uF MKT capacitor across it, clipping hard (with the capacitor making some cool noises).

Measuring the Beast

Measuring the amplifier's distortion levels were (and are still) challenging. I borrowed a Tektronix SG505 oscillator, and AA501A distortion analyser. This combination is able to measure some pretty low levels.
First, here are plots of THD+N at 1 KHz and 100 KHz, while varying the power from 10 mW to 140 W (into 8 Ohms). The increase below 1 W or so is the noise floor of the distortion analyser.



Maximum power is 120W (0.0015% at 1KHz).
Repeating the exercise at 100 W into 8 Ohms, and varying the frequency gives us the following plot.



THD is about 0.0013% at 1KHz, and rises to 0.0048% at 10KHz. The increase below 100Hz is due to a high pass in the distortion analyser increasing its noise floor. I didn't quite hit my target of 0.001%, but I'm happy with the performance nonetheless.
With no input, I measure 52uV RMS on the output (80KHz bandwidth). That's 12nV/sqrtHz voltage noise at the input. Given 120W maximum power (31V RMS into 8 Ohms), that implies 115dB SNR.

In the flesh, the monoblocks look reasonably impressive. The heatsinks are certainly hefty:




Here's a photo of the assembled amp, with rear panel and bottom made from steel sheet. Unlike a lot of stereo gear, there's not a lot of empty space in the box. It's surprisingly heavy, too.

Listening

Now for the fun part. After setting the bias and offset trimpots with a dummy load (and testing the THD specs), I hooked up my speakers, connected my (as yet unfinished) preamp and a CD player, and stuck a CD in. I'm not really into audiophile adjectives, but suffice it to say I'm pretty damned pleased. It sounds every bit as transparent and clear as I hoped. It's stunningly quiet. I can't hear so much as a glimmer between tracks. I was pleasantly surprised to hear really low frequency detail, especially in movies. Explosions rock the room with wonderful rumbles.
One of the unanticipated gotchas is that I tend towards listening at rather higher levels than I should. Unlike my old amp, which made it quite clear when the volume was excessive, this one just keeps on trucking. No distortion, no mud, just clear (loud) music.
One small problem. When I turn the power off, I get a really impressive thump as the power supplies collapse. I've got some space in the box to add a pair of relays, and a circuit to disconnect my speakers when the power is shut off.
Here's some design files:
A quick word to the reader. I'm presenting this design as a blog. It's something I've done for myself, and I thought it would be nice to share. I'm not interested in selling kits, or boards for the project, so don't bother asking me. Further, this is a very complex amplifier. It's got lots of gain, many stages, and a huge number of components. It's not suited to building on veroboard, tagboard, breadboard, or any other crappy DC prototyping-on-the-cheap construction method. It has gain out to HF frequencies, so requires construction methods that match. That means a real PCB. So if you email me with questions about where to get a kit, how to build it with sticky tape and rubber bands, or other inane topics, please don't be offended when you don't get a reply.

2 X 16 LCD display driver.

A serial interface for the Truly MTC-C162DPLY-2N
2 line X 16 char LCD display
9600 baud RS-232 using AT90S2313 or equivalent.

DOWNLOADS
AT90S2313 AVRStudio assembler source, LDCbutons040904Ca.htm
Truly LCD driver include file, 2x16lcdinc.htm
tlcd309Ba.htm AT90S8515/ATMega8515 AVRStudio assembler source by Rafael A. Alcocer.

Original version of posted AT90S2313 code in zip archive tlcd030929A.zip
The 040904C version of the code has a larger UART receive buffer and supports two pushbuttons that send either and ASCII "R" ($52) or an ASCII carriage return ($0D) via the UART when pressed.

Note: A potential bug that prevents the code from working on AVR controllers that support 16 bit addressing(specifically, the AT90S2313 only has an 8 bit stack pointer, while some others have 16 bit stack pointers) found by Rafael A. Alcocer has been corrected in version AT90S2313 version 030929A as well as being incorporated into the AT90S8515 version. .
The Truly MTC-C162DPLY-2N-LCD is an inexpensive two line LCD display and seems to be ideal as a display for some of my projects that have serial data outputs. Before building this interface, I used to either have a computer on the workbench to serve as a terminal or run an RS-232 cable from the desk with the computer on it to the workbench. When I saw the backlit version of displays being offered for less than US$6.00, I thought "Why not?".

The Truly display uses the Samsung KS0070B controller, which is (reportedly) compatible with the Hitachi HD44780 controller. I found a set of initialization and control routines that were written and placed on the on the web by Richard Hosking which only required a minor adjustment to get the Truly display working. Mr. Hosking's code was used in the assembler code that was originally posted and is in the ATMega8515 version. Mr. Hosking's code served as a model for the .include file driver that is used in the current AT90S2313 version.

The main minor adjustment I mentioned in the paragraph above was the addition of a second "Function Set" command that was needed to get the Truly displays to work. Mr. Peter Coppens has reported that he had to remove the second "Function Set" operation in the initialization routine in the Truly LCD Include file. This is noted in the include file by labeling the two lines with the comment "(OMIT FOR HITACHI)". the affected code fragment is shown immediately below (Some white space removed for to better fit on this page)..

ldi     temp,0b00101000 ;Function set 4 bit mode, 2 lines 5X7 pixels.
rcall   SendCommand ;Write to display -first write sets 4 bit mode. 
ldi     temp,0b00101000 ;Function set 4 bit mode, 2 lines 5X7 pixels (OMIT FOR HITACHI)
rcall   SendCommand ;write to display -second write to set N and F (OMIT FOR HITACHI) 

With this arrangement, the Truly display has a 4 bit parallel interface. The AT90S2313 controller converts incoming serial data to the appropriate 4 bit parallel commands and data. Incoming characters are written to the bottom line of the display and when a displayable character, that is a character that is not a control character, is received after a linefeed is received, the bottom line is copied to the top line, leaving a blank line on the bottom with the cursor set to the first column of the bottom line.

This delayed implementation of the linefeed until after a displayable character is received keeps the display from scrolling at the end of a line until there is something to display on the next line, thus two lines of information are always displayed, even when using it with hardware that automatically sends a carriage return and linefeed at the end of each line. This is important when you only have two lines on the display

It should be noted that the LCD display interface code is copyrighted by Mr. Hoskin and on his web page, we are admonished to use the code for personal use only. The rest of the code is hereby placed in the public domain.

When using the circuit (below) with 0 to 5V level serial connections, doide D1 and capacitor C1 may be omitted and the lower end of R1 connected directly to ground (see the schematic, below).

Behavior of the receive and display code:

Upon the application of power, communications protocol and code date and revision letter are displayed. "9600" Refers to 9600 baud, "1" refers to 1 stop bit being required, and "N" means no parity bits are expected. The next 7 characters uniquely identify the firmware revision level.

Incoming characters are display on the LCD module on the lower of the two lines on the display, referred to here as line two. The upper line is referred to as line 1. the first 16 characters are displayed and any additional characters are not displayed. Control characters, defined as those represented by ASCII values below $1F are not displayed.

Linefeed characters cause the display to wait for the first non-control character following the linefeed before copying line 2 to line 1 (scrolling the display), clearing line 2 and positioning the cursor at the start of line 2.

The display responds to carriage return characters ($0D) by placing the cursor to the fist (leftmost) position of line two.

The display cursor is on.

Note that unused I/O pins are pulled up with the weak pull-up (direction bits set to inputs, data bits written with a logic "1") to keep unused pins from floating.

A note about how this code works

The display is two lines of 16 characters.

The maximum data rate for allowable operation is 9600 baud with 1 stop bit and no parity.

All display write operations are "open loop" timing. That is to say that delay loops are used to assure that display write operations don't proceed faster than the display can handle. Therefore, if the controller's clock rate is changed, the timing routines will have to be modified accordingly. Mr. Hosking has thoughtfully indicated the delay time expected for each routine.

Upon coming out of reset, the display is initialized and the data format and firmware revision are displayed.

Incoming characters cause an interrupt and each character is stored in an 8 character circular buffer. A 16 character buffer is used to store a copy of the line two (lower line) of the display so it can be copied to line one during scrolling. The circular buffer is necessary because scrolling of the display takes 1.8 character times at 9600 baud, and without the buffer, a character would be lost each the time display content is scrolled.

The main loop of the program, named "forever" continuously checks to see if new characters have been written to the circular received character buffer and processes new characters. Displayable characters, defined as those with ASCII codes above $1F, are written to display line 2 and to a 16 character buffer in RAM. Linefeed and carriage return are the only control characters that are recognized. Carriage returns cause the line buffer to be erased and its pointer set to the start of the buffer. Linefeeds cause a flag to be set, which will cause display scrolling when the first displayable character after the linefeed is read from the circular buffer

Display scrolling, in response to the first character read from the circular buffer after a linefeed calls the routine "linefeed". Linefeed scrolls the display upward one line, leaving line two clear with the cursor positioned at the start of the line. In scrolling the display linefeed copies the line buffer, which contains a copy of the contents of line two of the display to line one, resets the line buffer by clearing it and setting the pointer to the start, then it clears line two and sets the cursor to the start of line two.


The second one worked the same as the first. This was not a surprise, but it was reassuring.

Thursday 5 September 2013

Propeller clock Analog and digital.

Propeller Analog/Digital Clock

History
I got this idea by browsing the web. I found by hazard  Mr. Bob Blick's page who make the first propeller clock.  I began to check how can I build one myself.  Base on AVR AT90S2313 I saw rapidly that my MCU must be clocked very fast to make all the calculations needed to light the leds at the right place.  The speed is at 16Mhz,  4 times faster than Atmel's specifications. It's working without any problems.
Each time the PCB passed at 12h00 an hall effect sensor sensed the magnet and generated an interruption. At this time, we have to check the counter to se how many ticks had been passed from this rotation since  the last rotation tooks this value and divide it by 360. This calculation will gives you the time between each degree.  Now, you have to set an interruption to occur at each degree and after you will be able to light leds whenever you want at any of 360 degrees.  At this MCU speed, there is no jitter everything is just FIXED!!!
A simple IR sensor is used to take the signal from the remote control (set to a Sony protocol).
Don't forget to use HIGH BRIGHTNESS LEDS 1600mcd.  Leds are not lighted very long so the mcd's power must be high.
A testing video can be found here.

Features
puce
Analog clock mode
puce
Digital clock mode
puce
Infrared clock setting with a standard universal remote control

Sources codes & Shematics





//**************************************
// Clock 16Mhz
//**************************************

//**************************************
//            I N C L U D E
//**************************************
#include <io2313v.h>
#include <macros.h>

//**************************************
//            D E F I N E
//**************************************
#define TRUE      0x01
#define FALSE     0x00
#define ANALOG    0x01
#define DIGITAL   0x02

//**************************************
//  I N T E R R U P T   H A N D L E R
//**************************************
#pragma interrupt_handler Crossing_interrupt:2
#pragma interrupt_handler IR_interrupt:4
#pragma interrupt_handler Degre_interrupt:5
#pragma interrupt_handler Ticker_interrupt:7

//**************************************
//          P R O T O T Y P E
//**************************************

void Crossing_interrupt(void);
void Degre_interrupt(void);
void Time(unsigned char);
void IR_interrupt(void);
void Ticker_interrupt(void);
void Display(void);
void CopyData(int Value);
void CopyDot(void);

//**************************************
//   G L O B A L   V A R I A B L E
//**************************************

int WeelPosition;
unsigned char Pos;
unsigned int Adder;

unsigned char LatchedIrData;

unsigned char Sec;
unsigned char Min;
unsigned char Hrs;

int SecComp;
int MinComp;
int HrsComp;

unsigned char ClockStyle;

unsigned char TimeString[50];
unsigned char *TimeStringPtr;


unsigned char i;

//**************************************
//           C O N S T A N T
//**************************************
const unsigned char table[12][6] = {{ 0x3e, 0x41, 0x41, 0x41, 0x3e, 0x00 }, // 0
                                    { 0x00, 0x21, 0x7f, 0x01, 0x00, 0x00  }, // 1
                                    { 0x21, 0x43, 0x45, 0x49, 0x31, 0x00  }, // 2
                                   { 0x42, 0x41, 0x51, 0x69, 0x46, 0x00  }, // 3
                                    { 0x0c, 0x14, 0x24, 0x5f, 0x04, 0x00  }, // 4
                                    { 0x72, 0x51, 0x51, 0x51, 0x4e, 0x00  }, // 5
                                    { 0x1e, 0x29, 0x49, 0x49, 0x06, 0x00  }, // 6
                                    { 0x40, 0x47, 0x48, 0x50, 0x60, 0x00  }, // 7
                                    { 0x36, 0x49, 0x49, 0x49, 0x36, 0x00  }, // 8
                                    { 0x30, 0x49, 0x49, 0x4a, 0x3c, 0x00  }, // 9
                                    { 0x00, 0x36, 0x36, 0x00, 0x00, 0x00  }, // :
                                    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  }};// space


//**************************************
//            M A I N
//**************************************
void main()
{
WDTCR = 0x0e;        // Enable WatchDog at 0.97 sec

PORTD = 0x0d;        // Pull up on PD2&PD3 & Led White ON
DDRD = 0x03;         // PD0-O PD1-O PD2-I PD3-I PD4-I PD5-I PD6-I PD7-I

//INT 0
MCUCR = 0x02;        // Int0 generate int on falling eadge
GIMSK = 0x40;        // Int0 enable

//Timer0
TCCR0 = 0x05;        // Timer0 / 1024

//Timer1
TCCR1B = 0x42;       // Timer1 / 8 & Input Capture on Rising eadge
TIMSK = 0x4a;        // int enable on Timer1 Compare Match
                  // int enable on Timer 1 Input Capture
                  // int enable on Timer0 Overflow
PORTB = 0x00;
DDRB = 0xff;         // PB0-7 as output

Hrs = 0;
Min = 0;
Sec = 0;
ClockStyle = ANALOG;

SEI();

while(1)
   {
   asm("WDR");
   for (i=0;i<200;i++);
   if ((LatchedIrData == 0xbb) || (LatchedIrData == 0x92)) Time(TRUE);
   if ((LatchedIrData == 0xb3) || (LatchedIrData == 0xb0)) ClockStyle = DIGITAL;
   if ((LatchedIrData == 0xb4) || (LatchedIrData == 0xb1)) ClockStyle = ANALOG;

   LatchedIrData = 0;
   }
}

/**********************************************************

Name:       void Time(void)

Description:  

Input:         none

Output:        none

Misc:      

**********************************************************/
void Time(unsigned char Fast)
{
if (Fast == FALSE) Sec++;
else Sec += 60;
if (Sec > 59)
   {
   Sec = 0;
   Min++;
   if (Min > 59)
        {
     Min = 0;
     Hrs++;
     if (Hrs > 11)
       {
       Hrs = 0;
       }
     }
   }

if (ClockStyle == ANALOG)
   {
   SecComp = Sec*6;
   MinComp = Min*6;
   HrsComp = (Hrs*30)+(Min/2);
   }
else
   {
   TimeStringPtr = &TimeString[0];
   CopyData(Hrs);
   CopyDot();
   CopyData(Min);
   CopyDot();
   CopyData(Sec);
   }
}

/**********************************************************

Name:       void CopyData(int Value)

Description:  

Input:         none

Output:        none

Misc:      

**********************************************************/

void CopyData(int Value)
{
if (Value < 10)
   {
   for (i=0;i<6;i++) *TimeStringPtr++ = table[0][i];
   for (i=0;i<6;i++) *TimeStringPtr++ = table[Value][i];
   }
else
   {
   for (i=0;i<6;i++) *TimeStringPtr++ = table[Value/10][i];
   for (i=0;i<6;i++) *TimeStringPtr++ = table[Value-((Value/10)*10)][i];
   }
}

/**********************************************************

Name:       void CopySpace(void)

Description:  

Input:         none

Output:        none

Misc:      

**********************************************************/

void CopyDot(void)
{
for (i=0;i<6;i++) *TimeStringPtr++ = table[10][i];
}

/**********************************************************

Name:       void Crossing_interrupt(void

Description:  

Input:         none

Output:        none

Misc:      

**********************************************************/
void Crossing_interrupt(void)
{
static unsigned int LastCount;
static unsigned int TotalCount;
static int Latch;
static unsigned char Lap;

Latch = TCNT1;
TotalCount = Latch - LastCount;
LastCount = Latch;
Lap++;
if (Lap > 250)
   {
   Adder = TotalCount / 378;
   Lap = 0;
   }

WeelPosition = 0;
OCR1 = Latch + Adder;
TIFR |= 0x80;
Display();
}

/**********************************************************

Name:       void Degre_interrupt(void)

Description:  

Input:         none

Output:        none

Misc:      

**********************************************************/
void Degre_interrupt(void)
{
OCR1 = TCNT1 + Adder;
Display();
}

/**********************************************************

Name:       void Display(void)

Description:  

Input:         none

Output:        none

Misc:      

**********************************************************/

void Display(void)
{
PORTB = 0x00;

if (ClockStyle == ANALOG)
   {
   if (WeelPosition == HrsComp) PORTB = 0x80;
   if (WeelPosition == MinComp) PORTB = 0xff;
   if (WeelPosition == SecComp) PORTB |= 0x03;

   if ((WeelPosition == 0) ||
      (WeelPosition == 30) ||
     (WeelPosition == 60) ||
     (WeelPosition == 90) ||
     (WeelPosition == 120) ||
     (WeelPosition == 150) ||
     (WeelPosition == 180) ||
     (WeelPosition == 210) ||
     (WeelPosition == 240) ||
     (WeelPosition == 270) ||
     (WeelPosition == 300) ||
     (WeelPosition == 330)) PORTB |= 0x01;
   }
else
   {
   Pos = ((WeelPosition-100) / 3);
   if (Pos < 49)
        {
     PORTB = TimeString[48-Pos];
     }
   }
WeelPosition++;
}

/**********************************************************

Name:       void IR_interrupt(void)

Description:   This routine is called whenever a rising edge (beginning
                of valid IR signal) is received.

                - The data codes are sent using pulse coding.
                - Each packet has 12 bits and a header.
                - The basic time period T = 550us.
                - The header length = 4T (2.2ms)
                - 0 = pulse with length T followed by space of length T.
                - 1 = pulse with length 2T followed by space of length T.
                - The last 5 bits represent the Addess.
                - The first 7 bits represent the command.
                - A packet is transmitted every 25ms while a button is down.

Input:         none     

Output:        Global variable LatchedIrData

Misc:       Sony VCR protocol

**********************************************************/
void IR_interrupt(void)
{
static unsigned int LastCapture;
unsigned int PulseWidth;
static unsigned int IrPulseCount;
static unsigned int IrData;

PulseWidth = ICR1 - LastCapture;
LastCapture = ICR1;


if (PulseWidth > 4000)
   {
   IrPulseCount = 0;
   IrData = 0;
   }
else
   {
   IrData = IrData >> 1;
   if (PulseWidth > 2800) IrData = IrData | 0x8000;
   IrPulseCount++;
   if (IrPulseCount == 12) LatchedIrData = ((IrData >> 4) & 0x00ff);
   }
}

/**********************************************************

Name:       void Ticker_interrupt(void)

Description:  

Input:         none

Output:        none

Misc:      

**********************************************************/
void Ticker_interrupt(void)
{
static unsigned char Tick;
Tick++;
if (Tick > 62)
   {
   Time(FALSE);
   Tick = 0;
   }
TCNT0 = 0x04; // reload counter
}