오디오(Audio)

A HiFi Preamp

arirangled 2010. 1. 31. 00:36

A HiFi Preamp
프리앰프는 초단증폭기이다. 매우 미소한 수밀리볼트의 적은 전압신호를 증폭하는 역할을 한다. CD플레이어, MP3플레이어, 외부입력신호(오디오)를 받아 들이고 증폭하고 필터링하여 후단에 연결될 스피커를 구동할 파워앰프(전력증폭기)의 방패역할을 한다고 생각하면 된다.

Recently I've become a little obsessed about upgrading my stereo. It all started when I was given a gorgeous pair of Infinity RS-5b speakers, of mid '80s vintage, complete with rotted surrounds on the woofers. These were brought up to scratch quite easily by replacing the surrounds with a kit bought from ebay, allowing me to hear the limitations of my Sharp 3-in-1 stereo with utmost clarity.

Rather than go out and buy a Yamaha/NAD/Denon/whatever receiver or amplifier, I figured I'd roll my own. What started out as a simple integrated amp turned out into something a little more. At present it's a pre/power amp pair. The power amp (the really boring box on the bottom)is based on David Tilbrook's AEM6000 Ultra-Fi design, with some newer transistors here and there and a redesigned PCB to allow for flatpack MOSFETs.

Old magazines weren't of much use in providing inspiration for a preamp, as they generally get frightened at the prospect of programming micros. I have no such fear. I sat down for a bit and thought about what I really wanted a preamp to do.

I came up with some nominal specifications:

  • 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.
  • Heaps of inputs. There's nothing worse than not having enough inputs. At the moment we have a CD player, DVD player, tuner, TV set top box, and video recorder. We'll probably get a home theatre PC shortly, and having a tape and line level phono input never hurt anyone. That's eight stereo inputs.
  • Ability to switch video sources as well. No need for all eight, but the DVD, TV, video, and PC inputs will do video as well. I'm not a big video junkie, so S-video is oodles good enough. I can get passive filters to convert from composite to S-video.
  • Very low distortion. This is relatively easy to achieve in a preamp, as the power levels are low. As with the power amp, I decided 0.001% (100dB) THD and intermods was a reasonable target.
  • Moderate cost.
  • Good looks. I'm a bit of a perfectionist in this regard.
  • Useability. I want it to be as simple as possible to use, with a simple volume knob, and a button to select each input. Remote control is a must, so it needs some sort of display (that's viewable from across the room) to show what it's up to.

Research

I started by searching the net for information on IR protocols. Rather than build a remote control, I figured that if it uses standard signals, then I could use a universal remote to control it (as well as my other gear). I found a whole preamp design by Mark Hennessy, which is quite close to what I want to do. Mark's preamp uses a a Burr-Brown PGA2310 volume control IC rather than the more usual potentiometer, with relay's to select the source and tape monitor, and OPA2134 buffers. The front panel features a gorgeous graphical VFD to show you what's happening, and a rotary encoder to control the volume. There's also an IR receiver, to allow the whole thing to be controlled remotely.

Design

This design was pretty much spot on what I wanted to do. All I needed to do was simplify the audio side a little (by removing the surround channels and the independent tape relays), and add some video switches. I also added an NE5532 to buffer the line level outputs, and removed the buffer on the output of the PGA2310, as the input impedance of my power amp is fairly high, so it wasn't needed.

The PGA2310 is a pretty neat chip. It's capable of attenuating a stereo source over a 127dB range (-95.5dB to +31.5dB) with 0.5dB steps. It has a maximum gain error of +/-0.05dB, and THD of 0.0004% at 1KHz. The sweet spot between THD and noise appears to be between 3V RMS and about 8V RMS. I thus put 6dB gain in the input buffer (an OPA2134), so that the full-scale input is around 4V RMS with my CD player (2V RMS full-scale output).

I used signal relays to select the inputs. If I was doing it over, I'd probably use some Analog Devices SSM2404 FET switches. However, at the time I laid it out I didn't know about the SSM2404, so the relays will have to suffice.

The preamp is separated into six PCBs in total. They are:

  • Preamp analogue board. This board contains the input and output phono sockets, source select relays, buffer opamps, and volume control chip.
  • Preamp video board. This board contains the s-video input andoutput sockets, video select switches, and video buffers.
  • Control board. This board attaches to the front panel, and contains the microcontroller to control everything, a graphical vacuum fluorescent display to display source and volume, source select and power pushbuttons, relay drivers, and IR receiver.
  • Power switch board. This board contains a 5V power supply for the control board, plus some power relays to switch the mains under control of the micro. There's also a soft-start circuit.
  • +/-18V supply for analogue board. A simple regulated power supply for the analogue board. The analogue board has its own regulators, but having a second set doubles power supply rejection, ensuring that horrid junk on the mains doesn't make it through to the speakers. It's overkill in the extreme, but then so is most audio design.
  • +/-5V supply for video board. A simple regulated power supply (actually a duplicate of the analogue power supply board with some different components) to supply the video mux board.

The audio signal path is shown below. A set of eight signal relays are used to select the source, which is then buffered by an NE5532 for the line level outputs, and an OPA2134 for the monitor output, with a gain of 2 (6dB), followed by a PGA2310 volume control IC. Gain is adjustable from -89.5dB to +37.5dB. Given the +/-15V supplies, a 1V RMS input will clip with the gain set to about +18dB. In order to ensure that the poweramp doesn't clip with my CD player (2V RMS output), the gain shouldn't be driven beyond +6dB (0dB setting on the PGA2310).

The line level outputs are DC coupled, and have no bandwidth limiting components. The monitor output has a single pole low-pass at 160KHz, and a high-pass at 3.4Hz. The gain is flat within 0.1dB from 20Hz to 20KHz.

The video switch uses a pair of MAX4314 quad video muxes to select luminance and chrominance inputs from one of four s-video cources. The selected luminance and chrominance signals are then buffered using a pair of MAX497 video drivers. The MAX4314 and MAX497 each have 6dB gain. With the impedance matching resistors, the circuit as a whole has unity gain. The circuit should be flat within 0.1dB to 87MHz (dictated by the MAX4314), so won't degrade video signals.

The front panel is controlled by a PIC16F877 microcontroller. Port D is used as a data bus, and is shared by the VFD, source select pushbuttons, volume control (clock and data), and relay driver. A power switch IC is used to switch 5V power for the VFD, source select relays, rotary encoder, and source select pushbuttons. This switched 5V line is used as an enable for the power switch board.

As I mentioned in the intro, this amplifier (and the accompanying preamp) is destined for my loungeroom. It's thus important that it look good with my other stereo gear. I used Autocad to design the enclosures, along with the emachineshop software for the front panels.

The sides are made from thick aluminium sheet. The top, bottom, and rear are simple painted steel sheets, and the front is an (expensive) milled aluminium block, shaped and anodised to look good with my NAD tuner, CD player, and DVD player.

When paired with the matching poweramp, the whole assembly should look quite good.

I actually balked at the cost of the front panel from emachineshop, and got one made by front panel express instead. It was significantly cheaper, but was only 4mm thick. It's also anodised before being milled, which means the edges are raw aluminium. Looks okay though. I cut a piece of red acrylic to go in front of the vacuum fluorescent display, which gives it a lovely red glow.

This is a work-in-progress. I'll post stuff up here as I do it.

Preamp board:

Control board:

Here's a few pictures taken while developing the code for the PIC micro. I really like the display.

Next, some code to control the whole thing. The source code is written in Hitech PICC lite (a free C compiler for PIC micros). I used Microchip MPLAB IDE to manage everything, and did the debugging with a Microchip ICD2 debugger. At the moment (13 May 2006) the following bits work:

  • The power button.
  • The vacuum fluorescent display (see above).
  • The source select buttons and relays.
  • The rotary encoder and serial data outputs to the PGA2310.

Stuff that still needs attention are:

  • IR receiver routines - there's a raw receiver routine but nothing to decode the packet.
  • Adding 6dB to the volume output to account for the gain in the OPA2134.
  • Optimising stuff and cleaning up the code. The code is pretty messy at the moment.

There's a minor error with the board that has to be fixed before the source select relays will work. The output enable line on the 74HC374 is connected to VCC on the board, but should be connected to ground in order to enable the outputs.

Anyway, here's the code, warts and all:

/*------------------------------------------------------------------------
 *HI-TECH C COMPILER
 * File:        vfd.c
 *
 * Author: Suzy Jackson <suzyj@littlefishbicycles.com>
 *
 * Revision: 0.1
 *
 * Commenced: 24 April 2006.
 *
 * Last edit: 24 April 2006.
 *
 * Purpose:     Graphical VFD routines.
 *
 * Description: This file contains a set of useful routines for use with
 *  a Noritake-Itron GU140X16G graphical Vacuum Fluorescent
 *  Display (VFD).
 *
 *------------------------------------------------------------------------*/

#include <pic.h>
#include "vfd.h"
#include "delay.h"
#include "hardware.h"

/* write a byte to the VFD cmd register */

void
vfd_write_cmd(unsigned char c)
{
 unsigned char busy;
 TRISD = 0xff;
 VFD_RW = 1;  // read
 VFD_RS = 0;  
 do {
  VFD_EN = 1;
  busy = DATA & 0x80;
  VFD_EN = 0;
 } while (busy);
 TRISD = 0x00;
 VFD_RW = 0;  // write
 VFD_RS = 0;  
 DATA = c;
 VFD_EN = 1;
 VFD_EN =0;
}

/* write a byte to the VFD data register */

void
vfd_write_data(unsigned char c)
{
 unsigned char busy;
 TRISD = 0xff;
 VFD_RW = 1;  // read
 VFD_RS = 0;  
 do {
  VFD_EN = 1;
  busy = DATA & 0x80;
  VFD_EN = 0;
 } while (busy);
 TRISD = 0x00;
 VFD_RW = 0;  // write
 VFD_RS = 1;  
 DATA = c;
 VFD_EN = 1;
 VFD_EN =0;
}

/*
 *  Clear and home the LCD
 */

void
vfd_clear(void)
{
 vfd_write_cmd(0x01);
}

/* write a string of chars to the VFD */

void
vfd_puts(const char * s)
{
 while(*s)
  vfd_write_data(*s++);
}

/*
 * Go to the specified position
 *
 * xpos = 0-139, ypos = 0-15
 */

void
vfd_goto(unsigned char xpos, unsigned char ypos)
{
 vfd_write_cmd(0xf0);
 vfd_write_data(xpos); 
 vfd_write_data(ypos); 
}

/*
 * Set the VFD font
 *
 * 'A' proportional mini font
 * 'B' 5x7 Katakana font
 * 'C' 10x14 Katakana font
 * 'b' 5x7 European font
 * 'c' 10x14 European font
 * '1' 1 pixel inter-character spacing
 * '2' 2 pixel inter-character spacing
 */

void
vfd_font(unsigned char font)
{
 vfd_write_cmd(0xf2);
 vfd_write_data(font); 

/*
 * Set the VFD area
 *
 * x1, y1 = left top of area
 * x2, y2 = right bottom of area
 *
 * Commands:
 *  'I' Invert area
 *  'F' Fill area
 *  'C' Clear area
 *  'O' Set outline
 *  'o' Clear outline
 *  'H' Write horizontal graphical data with horizontal cursor movement
 *  'V' Write vertical graphical data with vertical cursor movement
 *  'h' Write horizontal graphical data with vertical cursor movement
 *  'v' Write vertical graphical data with horizontal cursor movement
 */

void
vfd_area(unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2, unsigned char cmd)
{
 vfd_write_cmd(0xf1);
 vfd_write_data(x1); 
 vfd_write_data(y1); 
 vfd_write_data(x2); 
 vfd_write_data(y2); 
 vfd_write_data(cmd); 

 
/* initialise the VFD - put into 8 bit mode */
void
vfd_init()
{
 VFD_EN = 0;
 vfd_write_cmd (0x30);
 DelayMs (10);
 vfd_write_cmd (0x30);
 DelayMs (10);
 vfd_write_cmd (0x30);
 DelayMs (10);
 vfd_write_cmd (0x30); // Eight bit mode
 vfd_write_data(0x01); // Set luminance=75%
 vfd_write_cmd(0x06);
 vfd_write_cmd(0x0c);
 vfd_clear();  // Clear screen
}
***********************************************************************************************

/*
 * Delay functions
 * See delay.h for details
 *
 * Make sure this code is compiled with full optimization!!!
 */

#include "delay.h"

void
DelayMs(unsigned char cnt)
{
#if XTAL_FREQ <= 2MHZ
 do {
  DelayUs(996);
 } while(--cnt);
#endif

#if    XTAL_FREQ > 2MHZ 
 unsigned char i;
 do {
  i = 4;
  do {
   DelayUs(250);
  } while(--i);
 } while(--cnt);
#endif
}
****************************************************************************

/*------------------------------------------------------------------------
 *
 * File:        preamp.c
 *
 * Author:  Suzy Jackson <suzyj@littlefishbicycles.com>
 *
 * Revision: 0.1
 *
 * Commenced: 16 April 2006.
 *
 * Last edit: 2 May 2006.
 *
 * Purpose:     Preamp controller.
 *
 * Inputs:      Rotary encoder switch (volume control).
 *
 *              Eight source select pushbuttons.
 *
 *              Power pushbutton.
 *
 *              IR receiver serial stream.
 *
 * Outputs:     VFD for source display and volume level display.
 *
 *              Serial commands to Burr Brown PGA2310 stereo volume
 *              control.
 *
 *              Three address lines for video switch.
 *
 *              Eight outputs for audio source select relays.
 *
 *              Power enable line to allow preamp analogue board,
 *              video switch board, and power amps to be shut down
 *              when not in use.
 *
 * Description: This is the firmware for a stereo preamplifier.
 *              It allows the user to select between eight stereo audio
 *              and S-video sources, using relays to select the audio,
 *              and video muxes to select the video.
 *              The preamp uses a Burr Brown PGA2310 stereo volume
 *              control chip to allow volume control from an infra-red
 *              remote controller, without mucking around with motorised
 *              pots, and indeed without mucking around with pots at all.
 *              The preamp tells the user what source is selected, as
 *              well as the current volume setting using a Noritake-Itron
 *              GU140X16G graphical Vacuum Fluorescent Display (VFD).  The
 *              display pretends it's a character LCD, to simplify control.
 *              The preamp also takes inputs from an IR receiver chip.
 *
 *------------------------------------------------------------------------*/

/*------------------------------------------------------------------------
 *
 *                          I/O Map
 *                          =======
 *
 * Port Bit I/O Pin     to Where        Description
 * ---- --- --- ---     --------        -----------
 *
 *   A   0   i  3       Unused
 *   A   1   i  4       Unused
 *   A   2   i  5       Unused
 *
 *   A   3   o  6       PWR_EN         Low enables power supply
 *                                      for VFD, Preamp, Video Switch,
 *                                      Power amp, Volume rotary encoder.
 *                                      When high, only pushbuttons and
 *                                      IR receiver will work.
 *
 *   A   4   i  7       ENC_A           Rotary encoder A input.
 *
 *   A   5   i  8       ENC_B           Rotary encoder B input.
 *
 *   B   0   i  36      IR              Serial stream from IR receiver.
 *
 *   B   1   o  37      PB_EN           Low gates source select
 *                                      pushbuttons onto port D.
 *
 *   B   2   o  38      RELAY_CLK       Low to high transition clocks
 *                                      Port D onto relay driver.
 *
 *   B   3   i  39      Unused
 *
 *   B   4   i  41      PWRPB           Input from Power pushbutton.
 *                                      Low = button pressed.
 *
 *   B   5   i  42      Unused
 *   B   6   i  43      Unused
 *   B   7   i  44      Unused
 *
 *   C   0   o  16      VOL_CS          Low enables PGA2310
 *                                      Volume control.
 *
 *   C   1   o  18      VOL_MUTE        Low mutes PGA2310.
 *
 *   C   2   o  19      VFD_RS          Register select for VFD.
 *
 *   C   3   o  20      VFD_RW          VFD Read/-Write line.
 *
 *   C   4   o  25      VFD_EN          VFD E Strobe.
 *
 *   C   5   o  26      VID_A0          Video Mux A0.
 *
 *   C   6   o  27      VID_A1          Video Mux A1.
 *
 *   C   7   o  29      VID_A2          Video Mux A2.
 *
 *   D   0   io 21      D0              Data 0.  Used by VFD, Relay
 *                      VOL_D           driver, and pushbuttons.
 *                                      High enables source 1 when
 *                                      RELAY_clk clocked.  Low
 *                                      when PB_en low means source 1
 *                                      pushbutton pressed.
 *                                      PGA2310 data when VOL_cs low.
 *
 *   D   1   io 22      D1              Data 1.  Used by VFD, Relay
 *                      VOL_CLK         driver, and pushbuttons.
 *                                      High enables source 2 when
 *                                      RELAY_clk clocked.  Low
 *                                      when PB_en low means source 2
 *                                      pushbutton pressed.
 *                                      PGA2310 clock when VOL_cs low.
 *
 *   D   2   io 23      D2              Data 2.  Used by VFD, Relay
 *                                      driver, and pushbuttons.
 *                                      High enables source 3 when
 *                                      RELAY_clk clocked.  Low
 *                                      when PB_en low means source 3
 *                                      pushbutton pressed.
 *
 *   D   3   io 24      D3              Data 3.  Used by VFD, Relay
 *                                      driver, and pushbuttons.
 *                                      High enables source 4 when
 *                                      RELAY_clk clocked.  Low
 *                                      when PB_en low means source 4
 *                                      pushbutton pressed.
 *
 *   D   4   io 30      D4              Data 4.  Used by VFD, Relay
 *                                      driver, and pushbuttons.
 *                                      High enables source 5 when
 *                                      RELAY_clk clocked.  Low
 *                                      when PB_en low means source 5
 *                                      pushbutton pressed.
 *
 *   D   5   io 31      D5              Data 5.  Used by VFD, Relay
 *                                      driver, and pushbuttons.
 *                                      High enables source 6 when
 *                                      RELAY_clk clocked.  Low
 *                                      when PB_en low means source 6
 *                                      pushbutton pressed.
 *
 *   D   6   io 32      D6              Data 6.  Used by VFD, Relay
 *                                      driver, and pushbuttons.
 *                                      High enables source 7 when
 *                                      RELAY_clk clocked.  Low
 *                                      when PB_en low means source 7
 *                                      pushbutton pressed.
 *
 *   D   7   io 33      D7              Data 7.  Used by VFD, Relay
 *                                      driver, and pushbuttons.
 *                                      High enables source 8 when
 *                                      RELAY_clk clocked.  Low
 *                                      when PB_en low means source 8
 *                                      pushbutton pressed.
 *
 *   E   0   i  9       Unused
 *   E   1   i  10      Unused
 *   E   2   i  11      Unused
 *
 *------------------------------------------------------------------------*/

#include <pic.h>
#include "hardware.h"
#include "vfd.h"
#include "delay.h"
#include "preamp.h"

/*------------------------------------------------------------------------
 *
 * VOLUME SET - Drives serial data lines on PGA2310 volume control.
 *
 *------------------------------------------------------------------------*/

void
volume_set(unsigned char right, unsigned char left)
{
 unsigned char i, b;
 VOL_CS = 0; // Enable the volume control
 b=right;
 for (i=8;i>0;i--) { // Clock out right data
  unsigned char x= 1 << (i-1);
  if (b>=x) {
   VOL_D=1;
   b-=x;
  }
  else {
   VOL_D=0;
  }
  VOL_CLK = 0;
  VOL_CLK = 1;
 }
 b=left;
 for (i=8;i>0;i--) { // Clock out right data
  unsigned char x= 1 << (i-1);
  if (b>=x) {
   VOL_D=1;
   b-=x;
  }
  else {
   VOL_D=0;
  }
  VOL_CLK = 0;
  VOL_CLK = 1;
 }
 VOL_CS = 1;  // Disable the volume control

/*------------------------------------------------------------------------
 *
 * ENCODER - Checks rotary encoder and returns the following:
 *
 *               0:  no movement
 *              -1:  ccw rotation
 *               1:  cw rotation
 *
 *------------------------------------------------------------------------*/

signed char
encoder(void)
{
 static unsigned char a, b;
 if (!a & !b) {
  if (ENC_A) {
   a = ENC_A;
   return (-1);
  }
  if (ENC_B) {
   b = ENC_B;
   return (1);
  }
 }
 if (a & !b) {
  if (!ENC_A) {
   a = ENC_A;
   return (1);
  }
  if (ENC_B) {
   b = ENC_B;
   return (-1);
  }
 }
 if (a & b) {
  if (!ENC_A) {
   a = ENC_A;
   return (-1);
  }
  if (!ENC_B) {
   b = ENC_B;
   return (1);
  }
 }
 if (!a & b) {
  if (ENC_A) {
   a = ENC_A;
   return (1);
  }
  if (!ENC_B) {
   b = ENC_B;
   return (-1);
  }
 }
 return (0);
}

/*------------------------------------------------------------------------
 *
 * RC5_RECEIVE - Checks IR receiver and returns the following:
 *
 *               0:  no meaningful IR data present
 *              !0:  14 bit RC5 packet
 *
 *------------------------------------------------------------------------*/

unsigned int
rc5_receive(void)
{
 unsigned int message;
 unsigned char i, cnt, IR_old;
 message = 0;
 if (!IR) { // in middle of start bit
  for (i=0;i<14;i++) {
   message = message << 1 + IR;
   cnt = 6; // wait 1500ms (just before middle of next bit at 1778ms)
   do {
    DelayUs(250);
   } while (--cnt);
   IR_old = IR;
   cnt = 50;
   do {
    DelayUs(10);
   } while (IR_old == IR & --cnt);
   if (!cnt) return (0); // cnt = 0 indicates timeout - return 0
  }
 }
 return (message);
}

/*------------------------------------------------------------------------
 *
 * CHECK_SOURCE_PB - Checks source select pushbuttons and returns the following:
 *
 *               0xff:  no pushbutton pressed
 *               0x00:  source 1 pushbutton pressed
 *               ...
 *               0x07:  source 8 pushbutton pressed
 *
 *------------------------------------------------------------------------*/

unsigned char
check_source_pb(void)
{
 unsigned char source, i;
 TRISD = 0xff; // set data port to input
 PB_EN = 0;  // enable pushbutton buffer
 source = DATA;
 PB_EN = 1;  // disable pushbutton buffer
 TRISD = 0x00; // set data port to output
 if (source == 0x00) return (0xff); // no button pressed
 DelayMs (10); // debounce delay
 TRISD = 0xff; // set data port to input
 PB_EN = 0;  // enable pushbutton buffer
 source = DATA;
 PB_EN = 1;  // disable pushbutton buffer
 TRISD = 0x00; // set data port to output
 switch (source) {
  case 0b00000001:
   return (0x00);
   break;
  case 0b00000010:
   return (0x01);
   break;
  case 0b00000100:
   return (0x02);
   break;
  case 0b00001000:
   return (0x03);
   break;
  case 0b00010000:
   return (0x04);
   break;
  case 0b00100000:
   return (0x05);
   break;
  case 0b01000000:
   return (0x06);
   break;
  case 0b10000000:
   return (0x07);
   break;
  default:
   return (0xff);
   break;
 }
 return (0xff); // no button pressed
}

/*------------------------------------------------------------------------
 *
 * RELAY_SET - Outputs data to the relay driver.  Inputs are:
 *
 *               0x00:  source 1 selected
 *               ...
 *               0x07:  source 8 selected
 *
 *------------------------------------------------------------------------*/

void
relay_set (unsigned char source)
{
 switch (source) {
  case 0:
   DATA = 0b00000001;
   break;
  case 1:
   DATA = 0b00000010;
   break;
  case 2:
   DATA = 0b00000100;
   break;
  case 3:
   DATA = 0b00001000;
   break;
  case 4:
   DATA = 0b00010000;
   break;
  case 5:
   DATA = 0b00100000;
   break;
  case 6:
   DATA = 0b01000000;
   break;
  default:
   DATA = 0b10000000;
   break;
 }
 RELAY_CLK = 0;
 RELAY_CLK = 1;
 RELAY_CLK = 0;
}

/*------------------------------------------------------------------------
 *
 * VIDEO_SET - Outputs data to the video select board.  Inputs are:
 *
 *               0x00:  source 1 selected
 *               ...
 *               0x07:  source 8 selected
 *
 *------------------------------------------------------------------------*/

void
video_set (unsigned char source)
{
 VID_A0 = source & 0b00000001;
 VID_A1 = source & 0b00000010;
 VID_A2 = source & 0b00000100;

}

/*------------------------------------------------------------------------
 *
 * UPDATE_LEGENDS - displays the dB symbol and decimal point on the VFD.
 *
 *------------------------------------------------------------------------*/

void
update_legends(void)
{
 const char db_symbol [20] = {           
  0b00000001, 0b00000001, 0b00000001, 0b00000001, 0b00011101,
  0b00100011, 0b00100001, 0b00100001, 0b00100011, 0b00011101, 0b00111110,
  0b00100001, 0b00100001, 0b00100001, 0b00111110, 0b00100001,
  0b00100001, 0b00100001, 0b00100001, 0b00111110};
 unsigned char i;
 vfd_area(110, 7, 111, 8, 'F'); // print decimal point           //  300us
 vfd_area(124, 1, 139, 10, 'h');                                  //  300us
 for (i=0;i<20;i++) vfd_write_data(db_symbol[i]); // print dB symbol   //  880us
}

/*------------------------------------------------------------------------
 *
 * UPDATE_SOURCE_DISPLAY - Displays thesource on the VFD.
 *
 *           Source selection:
 *               0x00:  "Tuner"
 *               0x01:  "TV"
 *               0x02:  "Phono"
 *               0x03:  "DVD"
 *               0x04:  "CD"
 *               0x05:  "VHS"
 *               0x06:  "Tape"
 *               0x07:  "HTPC"
 *
 *------------------------------------------------------------------------*/

void
update_source_display(unsigned char source)
{
 vfd_goto(0,15);
 switch (source) {
  case 0:
   vfd_puts("Tuner");
   break;
  case 1:
   vfd_puts("TV   ");
   break;
  case 2:
   vfd_puts("Phono");
   break;
  case 3:
   vfd_puts("DVD  ");
   break;
  case 4:
   vfd_puts("CD   ");
   break;
  case 5:
   vfd_puts("VHS  ");
   break;
  case 6:
   vfd_puts("Tape ");
   break;
  default:
   vfd_puts("HTPC ");
   break;
 }
}

/*------------------------------------------------------------------------
 *
 * UPDATE_VOLUME_DISPLAY - Displays the volume on the VFD.
 *
 *           Volume encoding:
 *               0x00:  "-96.0dB"
 *               0x01:  "-95.5dB"
 *               ...
 *               0xc0:  "+00.0dB"
 *               ...
 *               0xfe:  "+31.0dB"
 *               0xff:  "+31.5dB"
 *
 *------------------------------------------------------------------------*/

void
update_volume_display(unsigned char volume)
{
 const char plus_symbol [6] = {           
  0b00001100, 0b00001100, 0b00111111, 0b00111111, 0b00001100, 0b00001100};
 const char minus_symbol [6] = {           
  0b00000000, 0b00000000, 0b00111111, 0b00111111, 0b00000000, 0b00000000};
 unsigned char i;
 vfd_area(76, 5, 83, 10, 'h');
 if (volume>=192) {
  for (i=0;i<6;i++) vfd_write_data(plus_symbol[i]); // print + symbol
  vfd_goto(86,15);
  vfd_write_data((volume-192)/20+'0');
  vfd_write_data((volume-192)/2%10+'0');
 }
 else {
  for (i=0;i<6;i++) vfd_write_data(minus_symbol[i]); // print - symbol
  vfd_goto(86,15);
  vfd_write_data((192-volume)/20+'0');
  vfd_write_data((192-volume)/2%10+'0');
 }
 vfd_goto(114,15);
 if (volume%2) {
  vfd_write_data('5');
 }
 else {
  vfd_write_data('0');
 }
}

/*------------------------------------------------------------------------
 *
 * UPDATE_DISPLAY - displays the whole display.
 *
 *------------------------------------------------------------------------*/

void
update_display(unsigned char source, unsigned char volume)
{
 vfd_clear();
 vfd_font('c'); // Set 10x14 font
 vfd_font('2'); // Set 1 pixel inter-char spacing
 update_legends();
 update_source_display(source);
 update_volume_display(volume);
}

/*------------------------------------------------------------------------
 *
 * MAIN - main code.
 *
 *------------------------------------------------------------------------*/

void
main(void)
{
 unsigned char volume = 0;
 unsigned char source = 0;
 signed char enc;
 unsigned char temp;
 unsigned int ir_packet;
 ADCON1 = 0x07;   // disable ADC
 TRISA = 0b11110111;  // set directions for port a
 TRISB = 0b11111001;  // set directions for port b
 TRISC = 0b00000000;  // set directions for port c
 TRISD = 0b00000000;  // set directions for port d
 PWR_EN = 1; // start with everything switched off
 enc = encoder(); // dummy encoder read to initialise statics
 for(;;) {
  if (!PWR_EN) {
   if ((enc = encoder()) != 0) {
    if (volume > 0 & enc < 0) {
     volume--;
    }
    if (volume < 255 & enc > 0) {
     volume++;
    }
    update_volume_display(volume);
    enc = encoder(); // dummy encoder read to account for slow display update
    volume_set(volume, volume);
    VOL_MUTE = 1;
   }
   temp = check_source_pb();
   if (temp <= 0x07) {
    source = temp;
    update_source_display(source);
    relay_set(source);
    video_set(source);
   }
   if (PWR_PB == 0) { // power button pressed
    DelayMs(10); // debounce delay
    if (PWR_PB == 0) {
     VOL_MUTE = 0;
     PWR_EN = 1; // shut down unnecessary stuff
     do {
      DelayMs(10);
     } while (PWR_PB == 0);  // wait for button to be released
     DelayMs(10);
    }
   }
  }
  else {
   if (PWR_PB == 0) { // power button pressed
    DelayMs(10); // debounce delay
    if (PWR_PB == 0) {
     PWR_EN = 0; // power stuff up
     VOL_MUTE = 1;
     DelayMs(10);
     vfd_init();
     update_display(source, volume);
     volume_set(volume, volume);
     relay_set(source);
     video_set(source);
     do {
      DelayMs(10);
     } while (PWR_PB == 0);  // wait for button to be released
     DelayMs(10);
     enc = encoder(); // dummy encoder read to initialise statics
    }
   }
  }
 }
}
 /*
 * Delay functions for HI-TECH C on the PIC
 *
 * Functions available:
 *  DelayUs(x) Delay specified number of microseconds
 *  DelayMs(x) Delay specified number of milliseconds
 *
 * Note that there are range limits: x must not exceed 255 - for xtal
 * frequencies > 12MHz the range for DelayUs is even smaller.
 * To use DelayUs it is only necessary to include this file; to use
 * DelayMs you must include delay.c in your project.
 *
 */

/* Set the crystal frequency in the CPP predefined symbols list in
 HPDPIC, or on the PICC commmand line, e.g.
 picc -DXTAL_FREQ=4MHZ
 
 or
 picc -DXTAL_FREQ=100KHZ
 
 Note that this is the crystal frequency, the CPU clock is
 divided by 4.

 * MAKE SURE this code is compiled with full optimization!!!
 
 */

#ifndef XTAL_FREQ
#define XTAL_FREQ 4MHZ  /* Crystal frequency in MHz */
#endif

#define MHZ *1000L   /* number of kHz in a MHz */
#define KHZ *1   /* number of kHz in a kHz */

#if XTAL_FREQ >= 12MHZ

#define DelayUs(x) { unsigned char _dcnt; \
     _dcnt = (x)*((XTAL_FREQ)/(12MHZ)); \
     while(--_dcnt != 0) \
      continue; }
#else

#define DelayUs(x) { unsigned char _dcnt; \
     _dcnt = (x)/((12MHZ)/(XTAL_FREQ))|1; \
     while(--_dcnt != 0) \
      continue; }
#endif

extern void DelayMs(unsigned char);

/*______________________________________________________________________________-

/*------------------------------------------------------------------------
 *
 * File:        preamp.h
 *
 * Author:  Suzy Jackson <suzyj@littlefishbicycles.com>
 *
 * Revision: 0.1
 *
 * Commenced: 16 April 2006.
 *
 * Last edit: 2 May 2006.
 *
 * Purpose:     Main program header file.

/*------------------------------------------------------------------------
 *
 * VOLUME SET - Drives serial data lines on PGA2310 volume control.
 *
 *------------------------------------------------------------------------*/

void
volume_set(unsigned char right, unsigned char left);

/*------------------------------------------------------------------------
 *
 * ENCODER - Checks rotary encoder and returns the following:
 *
 *               0:  no movement
 *              -1:  ccw rotation
 *               1:  cw rotation
 *
 *------------------------------------------------------------------------*/

signed char
encoder(void);

/*------------------------------------------------------------------------
 *
 * RC5_RECEIVE - Checks IR receiver and returns the following:
 *
 *               0:  no meaningful IR data present
 *              !0:  14 bit RC5 packet
 *
 *------------------------------------------------------------------------*/

unsigned int
rc5_receive(void);

/*------------------------------------------------------------------------
 *
 * CHECK_SOURCE_PB - Checks source select pushbuttons and returns the following:
 *
 *               0xff:  no pushbutton pressed
 *               0x00:  source 1 pushbutton pressed
 *               ...
 *               0x07:  source 8 pushbutton pressed
 *
 *------------------------------------------------------------------------*/

unsigned char
check_source_pb(void);

/*------------------------------------------------------------------------
 *
 * RELAY_SET - Outputs data to the relay driver.  Inputs are:
 *
 *               0x00:  source 1 selected
 *               ...
 *               0x07:  source 8 selected
 *
 *------------------------------------------------------------------------*/

void
relay_set (unsigned char source);

/*------------------------------------------------------------------------
 *
 * VIDEO_SET - Outputs data to the video select board.  Inputs are:
 *
 *               0x00:  source 1 selected
 *               ...
 *               0x07:  source 8 selected
 *
 *------------------------------------------------------------------------*/

void
video_set (unsigned char source);

/*------------------------------------------------------------------------
 *
 * UPDATE_LEGENDS - displays the dB symbol and decimal point on the VFD.
 *
 *------------------------------------------------------------------------*/

void
update_legends(void);

/*------------------------------------------------------------------------
 *
 * UPDATE_SOURCE_DISPLAY - Displays thesource on the VFD.
 *
 *           Source selection:
 *               0x00:  "Tuner"
 *               0x01:  "TV"
 *               0x02:  "Phono"
 *               0x03:  "DVD"
 *               0x04:  "CD"
 *               0x05:  "VHS"
 *               0x06:  "Tape"
 *               0x07:  "HTPC"
 *
 *------------------------------------------------------------------------*/

void
update_source_display(unsigned char source);

/*------------------------------------------------------------------------
 *
 * UPDATE_VOLUME_DISPLAY - Displays the volume on the VFD.
 *
 *           Volume encoding:
 *               0x00:  "-96.0dB"
 *               0x01:  "-95.5dB"
 *               ...
 *               0xc0:  "+00.0dB"
 *               ...
 *               0xfe:  "+31.0dB"
 *               0xff:  "+31.5dB"
 *
 *--------------------------HARDWARE.H------------------------------------------*/

void
update_volume_display(unsigned char volume);

/*------------------------------------------------------------------------
 *
 * UPDATE_DISPLAY - displays the whole display.
 *
 *------------------------------------------------------------------------*/

void
update_display(unsigned char source, unsigned char volume);
/*________________________________________________________________________*/

/*
 * VFD interface header file
 * See vfd.c for more info
 */

/* write a byte to the VFD cmd register */

extern void vfd_write_cmd(unsigned char);

/* write a byte to the VFD data register */

extern void vfd_write_data(unsigned char);

/* Clear and home the VFD */

extern void vfd_clear(void);

/* write a string of characters to the VFD */

extern void vfd_puts(const char * s);

/* Go to the specified position */

extern void vfd_goto(unsigned char xpos, unsigned char ypos);
 
/* Intialize the VFD - call before anything else */

extern void vfd_init(void);

/* Set the cursor position */

#define vfd_cursor(x) vfd_write(((x)&0x7F)|0x80)

/* Set VFD font */

extern void vfd_font (char);

extern void vfd_area(unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2, unsigned char cmd);
/*__________________________________________________________________________*/

#define PWR_EN  RA3
#define ENC_A  RA4
#define ENC_B  RA5

#define IR   RB0
#define PB_EN  RB1
#define RELAY_CLK RB2
#define PWR_PB  RB4

#define VOL_CS  RC0
#define VOL_MUTE RC1
#define VFD_RS  RC2
#define VFD_RW  RC3
#define VFD_EN  RC4
#define VID_A0  RC5
#define VID_A1  RC6
#define VID_A2  RC7

#define VOL_D  RD0
#define VOL_CLK  RD1
#define DATA  PORTD

반응형

'오디오(Audio)' 카테고리의 다른 글

C-MIC두개 적용된 PCB Module  (0) 2019.02.06
SD AUDIO PLAYER  (0) 2010.05.06
A급증폭기  (0) 2010.03.24
프리,파워앰프내부  (0) 2010.01.31
A RANK HiFi Power Amp  (0) 2010.01.30