Showing posts with label ATMega328. Show all posts
Showing posts with label ATMega328. Show all posts

Sunday, October 11, 2015

Postage Stamp Micro-controller

I have updated my ATMega328 controller board with a new layout to fix the previously posted deficits and have sent the gerbers off to OshPark for production.  Hopefully I will have the set back in a week or so.  After validation, I will make the design public at OshPark so anyone interested can purchase the boards.  This is a one inch square PCB with an ATMega328 processor, full set of IO pins and programmable via an ISP connector.


See my previous postings for the schematic and further details.  This was originally a project done for purposes of learning how to use KiCAD.

Sunday, September 13, 2015

Arduino and Servos

This little project has been lying around waiting to be done for far to long, so I have resurrected it on a rainy Sunday and hope to make something useful out of it.

I have this little AZ/EL camera mount pair of servos that are quite stout.  They have external feedback potentiometers for the servo position, so even though they are geared down, they can be accurately positioned.


Coupling these up to an Arduino UNO is trivial.  I need one signal pin, power and ground.  Do not try to power servos from the USB power on the Arduino, use an external power supply.

The first task at hand was to decide on the range of pulse widths that would be allowed for the servo.  The defaults are 544 us to 2400 us.  The APIs decide that if you set a pulse width less than the minimum, then it must be an angle in degrees 0-359.  I didn't take the time to sort this out.  Instead, I decided arbitrarily that angle zero is 500 us. and everything is counterclockwise from there.

I placed a mark on the Azimuth gear right at the servo gear and through experimentation determined that a change of 768 microseconds would rotate the gear 360 degrees.



From there, it was simple to create a function that would convert an angle in degrees to the required microseconds pulse width to position the servo as desired.

// For the azimuth servo, angle is mapped 0-360 degrees to 500-128 us
int deg2usAZ(int angle)
{
  return map(angle, 0, 360, 500, 1268);

}

Now in the case of the elevation servo, we have some limitations to the range of motion due to the physical characteristics of the assembly.  I decided first to figure out where level was and call this zero degrees of elevation.



Next I determined the setting for negative 45 degrees of elevation (pointing down).



And finally, I determined the setting for pointing straight up.  Now it was trivial to create a function to map degrees to these settings.

// For elevation servo, angle is mapped from -45-90 to 1112 to 2220 us
int deg2usEL(int angle)
{
  return map(angle, -45, 90, 1112, 2220);

}

Now with the addition of a button, I have a simple test application that will postion to zero degrees azimuth and elevation when started and then move to 90 degrees elevation and 45 degrees of azimuth when the button is pressed..

// Servo test code

#include <Servo.h>

int button1 = 4; //button pin, connect to ground to move servo
int press1 = 0;

Servo servoEL;
Servo servoAZ;

void setup()
{
  pinMode(button1, INPUT);
  servoEL.attach(7, 500, 2400);
  servoAZ.attach(9, 500, 2400); 

  servoEL.writeMicroseconds(deg2usEL(0));
  servoAZ.writeMicroseconds(deg2usAZ(0));

  digitalWrite(4, HIGH);
}

// For the azimuth servo, angle is mapped 0-360 degrees to 500-128 us
int deg2usAZ(int angle)
{
  return map(angle, 0, 360, 500, 1268);
}

// For elevation servo, angle is mapped from -45-90 to 1112 to 2220 us
int deg2usEL(int angle)
{
  return map(angle, -45, 90, 1112, 2220);
}

void loop()
{
  press1 = digitalRead(button1);
  if (press1 == LOW)
  {
    servoEL.writeMicroseconds(deg2usEL(90));
    servoAZ.writeMicroseconds(deg2usAZ(45));
  }
  else 
  {
    servoEL.writeMicroseconds(deg2usEL(0));
    servoAZ.writeMicroseconds(deg2usAZ(0));
  }

}

So, next I obtained the Arduino port of Plan13 which is a port of some very old basic code for determining a satellite location given the latitude/longitude/elevation of an observer, the Keplerian Elements for the satellite of interest and the date/time of interest. Keplerian Elements (or just Keps), named after Johann Kepler [1571-1630] are a set of seven numbers called satellite orbital elements that define an ellipse oriented about the earth and place the satellite on the ellipse at a particular time.

I have the Arduino port of Plan13 up and running and calculating azimuth and elevation values.  The precision is really insufficient on the Arduino to have very accurate calculations due to the fact that double precision floating point is required, but the Arduino defines double precision as a single precision value.  So the calculations do not have the precision they might otherwise have, but I find it is quite sufficient for simple experiments like this.

The remaining work is to provide a little more glue logic to have what amounts to a desktop pointer to the satellite as it passes overhead.

More to come.
.

Sunday, July 26, 2015

Controller check-out

I have FINALLY found my ISP programmer and been able to check out the postage stamp controller and made it to the typical embedded "Hello World" application of a blinking LED.



The small board in the upper left is an ESP8622 wifi module that is hanging out from a previous project.  I have flashed the ATMega328 part with the following typical example code:

void setup()
{
  pinMode(13, OUTPUT);
}

void loop() 
{
  digitalWrite(13, HIGH);
  delay(1000);
  digitalWrite(13, LOW);
  delay(1000);

}

The USBTinyISP programmer had no problem flashing the code to this part, but at initial blush, it appeared that nothing was working.  Then after a very long delay, I saw the LED turn on.  Reducing the delay values to 100 resulted in approximatly 1 second timing of the LED flashes.  So, it appears that the clock was not running at 16 MHz.

On a whim, I decided to flash the optiboot boot loader to the device and let the device reset and then erase the chip and reflash the test application above with no boot loader present.  This in fact fixed the problem.  

In looking at the datasheet, I see the following:



So, it appears that out of the box, a new ATMega328 is going to default to a 1 MHz system clock.  I will have to look at the optiboot code, but I am sure it is fiddling with the clock select bits which would explain the behavior I have seen.

In any event, I now have a tiny little microcontroller that needs no boot loader (saving that space in flash) and can be used for my projects that can use such a controller.  I have a couple updates that I need do to the PCB and will publish my updates on OSHPark where anyone can order the boards as desired.  $5 for 3 boards is a bargain.  More to the point is that I got something useful out of an attempt to learn a little more about KiCAD.

Saturday, July 18, 2015

Postage Stamp Micro-controller

In a previous post I described a little one inch square micro-controller based on the ATMega328 which I decided to build in order to learn KiCAD.  I have the boards back from OshPark.



To my way of thinking, it is a pretty good bargain to be able to obtain this lot of boards for the grand total of $10 plus shipping.  In any event, I have built up one of the units as seen below.


I have a couple of errors I will need to fix.  The first is that the row of header pins on the left and right side are not quite on 0.1 inch centers.  I plugged the headers into a prototype board while soldering and they both bend towards the centre of the board, so I need to space them a little closer together.  Oops...  The top and bottom headers are mounted on the top side of the board to allow plugging the board into a prototype board and still having all pins available.  I would expect to typically hard-wire the board into whatever circuit it will be employed in and then mount it down with double sided tape.  This one however is for bread-boarding.

The second error is that I used the wrong footprint for the thru-hole crystals that I have on hand and so it cannot sit completely down on the board.  Oh sigh...  Easy enough to fix however and I will make those updates before I publish the board at OshPark.com.

The ATMega328 chips are raw ones with no boot loader, so I will have to program them with the ISP header.  This can be done with another Arduino or any of the ISP programmers such as the USBTinyISP.  No boot loader is required on this device as there is no USB or other programming interface other than the ISP connector so therefore the entire flash is available for your code.




Thursday, July 2, 2015

KiCAD

Since being laid off at Dynon Avionics, I have been looking around at schematic capture and PCB CAD packages.  Chris at Contextual Electronics has encouraged me to try out KiCAD.  I decided to give it a go and design a small 1 square inch microcontroller board with a full-on ATMega 328 processor and the minimum circuitry necessary to build an (almost) UNO clone.  The "almost" part is the fact that there will be no USB port on the board and it will need be programmed using the ISP connector.

The circuit diagramme I decided on looks like this from KiCAD:



Nothing here that doesn't need to be here for a minimal controller.  I have the entire I/O pin set that can be found on an UNO, plus since I use the TQFP package, I have two additional analogue input pins.

The board layout is pretty dense at one inch square.  Here is the board layout showing all of the layers from KiCAD.  I am not terribly impressed with KiCAD's copper pour capabilities, at least as far as I have explored them, but what do you want for free, eh?



Producing the Gerber files was trivial and I zipped up the lot of them and shipped them off to OSHPark to be manufactured.  Here is the screen shot of their rendering of the gerber files for the front and back of the board.



So, now we wait for the little purple envelope to arrive.  Meanwhile I will collect up the set of parts to build a few of these up.  This was an experiment to enable me to learn KiCAD and at the same time to produce a useful gadget for other projects.  Once I have verified the functionality is correct, I will publish the OSHPark boards so others can order them directly if desired.  At $5 for 3 of them, they are pretty cheap.

Overall, I found KiCAD relatively easy to learn, once I got a couple of key concepts down.  The main one was that hot keys apply to whatever your mouse is hovering over.  I found myself wanting to click to select something before hitting a hot key and this lead to all sorts of grief.  Moving objects once they are placed also needs a lot of work.  Rubber-banding of traces pretty much sucks.  Once I figured out how to drag an object, the best that KiCAD could do is straight line connect all the existing traces.  This ends up being pretty much worthless for anything other than adjustments of a few centimetres and you end up deleting all the traces and redrawing them anyway.

Selection of wires in schematics needs an easy way to select only a line segment or the entire line without having to draw box around it.  When you move the line, it needs to rubber-band the end points.  Straightening out a series of segments needs to collapse them into one segment and it needs to be able to easily break a line segment in two without disconnecting them.  All of these comments also apply to traces in the PCB layout editor.

I did like that KiCAD included the ability to view gerber files, a comprehensive footprint editor as well as building the notion of hierarchical schematics into the product as a key feature.  I cannot however get the bill of materials functionality to work, so I must be doing something wrong.  Always more to learn. 

For a free, open-source product, it is quite compehensive and there are a lot of part footprints available out-of-box and from 3rd parties.  Overall, I would continue to use it for hobby projects.  I would need to try and build at least a four layer board to have an opinion about suitability for commercial projects.

Monday, March 2, 2015

Generating Audio PSK31 with an Arduino (Part 3) UPDATE

UPDATED: Complete code listing added to end of posting.  Apologies for previously posting an incorrect (and non-functional) version of the code.  The corrected code is now included at the end of this posting.

In three previous posts (part 1, update, part 2), I have described a working PSK31 encoder implemented on an Arduino ATMega328 or ATMega2560 device.  While this implementation worked for the most part, it was based on some incorrect PSK32 bit timing.  Most PSK31 decoders are sufficiently robust to allow successful decoding of my generated signal, but I wanted to write a follow-up posting to describe the error and propose a fix.

By way of review, PSK31 is a communication protocol with a transmission rate of 31.25 baud.  So, what does this mean?  From google, we see the following definition of "baud".



So, a 31.25 baud transmission is 31.25 bits per second or a bit time of 32 ms.  The technique described in my previous postings (above) results in a bit time of 32.768 ms which apparently is still decode-able, but I would still like to get this right.

My error is in how I constructed the waveform of the PSK31 signal.  I used a 1 kHz tone which of course has a 1 ms period (1/1000 = .001 seconds).  To construct this 1 kHz tone, I generated 32 phase points per cycle.  This means that I needed to generate a new phase point every 31.25 us.  (.001 / 32 = .00003125 seconds = 31.25 us).  To do this, I used a timer driven at 16 MHz in phase correct PWM mode.  This means that the timer counts from zero to 255 and then back down to zero at which time it generates an interrupt.  Therefore, the interrupt would happen every 512 ticks of the 16 MHz clock which is every 32 us.  (16000000 / 512 = 31250 = .000032 seconds = 32 us)

So, instead of generating a phase point every 31.25 us, I was generating one every 32 us. (Hey, what's 0.75 us amongst friends?)  This in and of itself is not really a problem, but the error I made was that I was using a model where I generated 1024 phase points per PSK31 bit time.  Therefore, my bit time ended up being 32.768 ms rather than the desired 32 ms.  (1024 * .000032 = .032768 seconds = 32.768 ms).

In an ideal world, I would run the timer at 16 MHz / 500 = 32 kHz instead of 16 MHz / 512 = 31.25 kHz and my current scheme would work fine.  The counter would run from zero to 250 and then back down to zero instead of counting up to 255.  However, there are a limited number of prescaler divisors available on Arduino timers and they are all powers of two, so this is not an option.  Additionally, I need phase correct PWM mode, so the counter has to run from minimum (zero) to max (255) and back down for each phase point.  The 1 kHz tone output goes high at the current phase amplitude value when the counter gets to it on the way up to 255 and sets the output low when the counter gets to the phase amplitude value on the way back down to zero.  This allows the generation of phase correct PWM output.  Once the PWM output is integrated, a nice phase continuous sine wave output is produced.

So, the fix appears to be to use 1000 phase points per PSK31 bit time rather than 1024.  (1000 * .000032 = .032 = 32 ms)  I will need to re-generate the sine tables to use possibly 20 phase points per cycle rather than 32 and make sure the state machine knows the new number of phase points per cycle, so the code changes should be pretty trivial (famous last words...)

The implementation does not separate the bit timing from the timing of the generation of the 1 kHz audio tone.  We count cycles to know when the bit has ended.  If these timing tasks were separated, then we could set the bit time independently of the frequency of the tone being generated.  But, since I coupled these two things together, I have to change the number of phase points per cycle in order to generate the correct character timing.

Here is the updated code listing with the changes discussed above.  I have verified the bit timing is now correct.

As always, your mileage may vary.  I am happy to try and help you if you have questions or comments about anything you read here.  Post a reply here or drop me a note at ko7m at arrl dot net.

// PSK31 audio generation
// Jeff Whitlatch - ko7m

// We are going to generate a 1 kHz centre frequency tone.  
// Each 1 kHz cycle of the sinusoid will be generated from
// 32 eight bit amplitude samples.  The period of a 1 kHz tone
// is 1 ms.  Each of the 32 samples per cycle has a period
// of 31.25 us.  We will construct each sinusoid from a 32 byte
// per cycle lookup table of amplitude values ranging from
// 0x00 to 0xff where the zero crossing value is 0x80.

// The PSK31 character bit time is 31.25 ms constructed of 1024
// samples.  A binary zero is represented by a phase reversal
// while a binary 1 is represented by the lack of a phase reversal.

// Characters are encoded with a variable bit length code (varicode)
// where the length of each character is inversely
// proportional to the frequency of use in the english language 
// of that character.  Characters are encoded with a bit
// pattern where there are no sequential zero bits.  Two zero bits
// in a row signify the end of a character.

// Varicode lookup table
//
// This table defines the PKS31 varicode.  There are 128 entries,
// corresponding to ASCII characters 0-127 with two bytes for each entry.
// The bits for the varicode are to be shifted out LSB-first.
//
// More than one zero in sequence signifies the end of the character.
// For modulation, a 0 represents a phase reversal while a 1 
// represents a steady-state carrier.

uint16_t varicode[] = {
  0x0355,  // 0 NUL
  0x036d,  // 1 SOH
  0x02dd,  // 2 STX
  0x03bb,  // 3 ETX
  0x035d,  // 4 EOT
  0x03eb,  // 5 ENQ
  0x03dd,  // 6 ACK
  0x02fd,  // 7 BEL
  0x03fd,  // 8 BS
  0x00f7,  // 9 HT
  0x0017,  // 10 LF
  0x03db,  // 11 VT
  0x02ed,  // 12 FF
  0x001f,  // 13 CR
  0x02bb,  // 14 SO
  0x0357,  // 15 SI
  0x03bd,  // 16 DLE
  0x02bd,  // 17 DC1
  0x02d7,  // 18 DC2
  0x03d7,  // 19 DC3
  0x036b,  // 20 DC4
  0x035b,  // 21 NAK
  0x02db,  // 22 SYN
  0x03ab,  // 23 ETB
  0x037b,  // 24 CAN
  0x02fb,  // 25 EM
  0x03b7,  // 26 SUB
  0x02ab,  // 27 ESC
  0x02eb,  // 28 FS
  0x0377,  // 29 GS
  0x037d,  // 30 RS
  0x03fb,  // 31 US
  0x0001,  // 32 SP
  0x01ff,  // 33 !
  0x01f5,  // 34 @
  0x015f,  // 35 #
  0x01b7,  // 36 $
  0x02ad,  // 37 %
  0x0375,  // 38 &
  0x01fd,  // 39 '
  0x00df,  // 40 (
  0x00ef,  // 41 )
  0x01ed,  // 42 *
  0x01f7,  // 43 +
  0x0057,  // 44 ,
  0x002b,  // 45 -
  0x0075,  // 46 .
  0x01eb,  // 47 /
  0x00ed,  // 48 0
  0x00bd,  // 49 1
  0x00b7,  // 50 2
  0x00ff,  // 51 3
  0x01dd,  // 52 4
  0x01b5,  // 53 5
  0x01ad,  // 54 6
  0x016b,  // 55 7
  0x01ab,  // 56 8
  0x01db,  // 57 9
  0x00af,  // 58 :
  0x017b,  // 59 ;
  0x016f,  // 60 <
  0x0055,  // 61 =
  0x01d7,  // 62 >
  0x03d5,  // 63 ?
  0x02f5,  // 64 @
  0x005f,  // 65 A
  0x00d7,  // 66 B
  0x00b5,  // 67 C
  0x00ad,  // 68 D
  0x0077,  // 69 E
  0x00db,  // 70 F
  0x00bf,  // 71 G
  0x0155,  // 72 H
  0x007f,  // 73 I
  0x017f,  // 74 J
  0x017d,  // 75 K
  0x00eb,  // 76 L
  0x00dd,  // 77 M
  0x00bb,  // 78 N
  0x00d5,  // 79 O
  0x00ab,  // 80 P
  0x0177,  // 81 Q
  0x00f5,  // 82 R
  0x007b,  // 83 S
  0x005b,  // 84 T
  0x01d5,  // 85 U
  0x015b,  // 86 V
  0x0175,  // 87 W
  0x015d,  // 88 X
  0x01bd,  // 89 Y
  0x02d5,  // 90 Z
  0x01df,  // 91 [
  0x01ef,  // 92 
  0x01bf,  // 93 ]
  0x03f5,  // 94 ^
  0x016d,  // 95 _
  0x03ed,  // 96 `
  0x000d,  // 97 a
  0x007d,  // 98 b
  0x003d,  // 99 c
  0x002d,  // 100 d
  0x0003,  // 101 e
  0x002f,  // 102 f
  0x006d,  // 103 g
  0x0035,  // 104 h
  0x000b,  // 105 i
  0x01af,  // 106 j
  0x00fd,  // 107 k
  0x001b,  // 108 l
  0x0037,  // 109 m
  0x000f,  // 110 n
  0x0007,  // 111 o
  0x003f,  // 112 p
  0x01fb,  // 113 q
  0x0015,  // 114 r
  0x001d,  // 115 s
  0x0005,  // 116 t
  0x003b,  // 117 u
  0x006f,  // 118 v
  0x006b,  // 119 w
  0x00fb,  // 120 x
  0x005d,  // 121 y
  0x0157,  // 122 z
  0x03b5,  // 123 {
  0x01bb,  // 124 |
  0x02b5,  // 125 }
  0x03ad,  // 126 ~
  0x02b7   // 127 (del)
};

// 25 cycles of 20 samples each (500 bytes) of ramp-up
// sinusoid information.  There is an extra byte at the
// end of the table with the value 0x80 which allows the
// first byte to always be at the zero crossing point
// whether ramping up or down.
//
char data[] =
{
  0x80,0x82,0x85,0x86,0x88,0x88,0x88,0x86,0x85,0x82,
  0x80,0x7E,0x7B,0x7A,0x78,0x78,0x78,0x7A,0x7B,0x7E,
  0x80,0x85,0x89,0x8D,0x8F,0x90,0x8F,0x8D,0x89,0x85,
  0x80,0x7B,0x77,0x73,0x71,0x70,0x71,0x73,0x77,0x7B,
  0x80,0x87,0x8E,0x93,0x97,0x98,0x97,0x93,0x8E,0x87,
  0x80,0x79,0x72,0x6D,0x69,0x68,0x69,0x6D,0x72,0x79,
  0x80,0x8A,0x92,0x99,0x9D,0x9F,0x9D,0x99,0x92,0x8A,
  0x80,0x76,0x6E,0x67,0x63,0x61,0x63,0x67,0x6E,0x76,
  0x80,0x8C,0x97,0xA0,0xA5,0xA7,0xA5,0xA0,0x97,0x8C,
  0x80,0x74,0x69,0x60,0x5B,0x59,0x5B,0x60,0x69,0x74,
  0x80,0x8F,0x9C,0xA6,0xAD,0xAF,0xAD,0xA6,0x9C,0x8F,
  0x80,0x71,0x64,0x5A,0x53,0x51,0x53,0x5A,0x64,0x71,
  0x80,0x91,0xA0,0xAC,0xB3,0xB6,0xB3,0xAC,0xA0,0x91,
  0x80,0x6F,0x60,0x54,0x4D,0x4A,0x4D,0x54,0x60,0x6F,
  0x80,0x93,0xA4,0xB1,0xBA,0xBD,0xBA,0xB1,0xA4,0x93,
  0x80,0x6D,0x5C,0x4F,0x46,0x43,0x46,0x4F,0x5C,0x6D,
  0x80,0x95,0xA8,0xB7,0xC1,0xC4,0xC1,0xB7,0xA8,0x95,
  0x80,0x6B,0x58,0x49,0x3F,0x3C,0x3F,0x49,0x58,0x6B,
  0x80,0x97,0xAC,0xBD,0xC7,0xCB,0xC7,0xBD,0xAC,0x97,
  0x80,0x69,0x54,0x43,0x39,0x35,0x39,0x43,0x54,0x69,
  0x80,0x99,0xB0,0xC2,0xCD,0xD1,0xCD,0xC2,0xB0,0x99,
  0x80,0x67,0x50,0x3E,0x33,0x2F,0x33,0x3E,0x50,0x67,
  0x80,0x9B,0xB3,0xC6,0xD3,0xD7,0xD3,0xC6,0xB3,0x9B,
  0x80,0x65,0x4D,0x3A,0x2D,0x29,0x2D,0x3A,0x4D,0x65,
  0x80,0x9D,0xB7,0xCB,0xD8,0xDD,0xD8,0xCB,0xB7,0x9D,
  0x80,0x63,0x49,0x35,0x28,0x23,0x28,0x35,0x49,0x63,
  0x80,0x9E,0xBA,0xCF,0xDD,0xE2,0xDD,0xCF,0xBA,0x9E,
  0x80,0x62,0x46,0x31,0x23,0x1E,0x23,0x31,0x46,0x62,
  0x80,0xA0,0xBD,0xD3,0xE2,0xE7,0xE2,0xD3,0xBD,0xA0,
  0x80,0x60,0x43,0x2D,0x1E,0x19,0x1E,0x2D,0x43,0x60,
  0x80,0xA1,0xBF,0xD7,0xE6,0xEB,0xE6,0xD7,0xBF,0xA1,
  0x80,0x5F,0x41,0x29,0x1A,0x15,0x1A,0x29,0x41,0x5F,
  0x80,0xA2,0xC1,0xDA,0xEA,0xEF,0xEA,0xDA,0xC1,0xA2,
  0x80,0x5E,0x3F,0x26,0x16,0x11,0x16,0x26,0x3F,0x5E,
  0x80,0xA4,0xC4,0xDD,0xED,0xF3,0xED,0xDD,0xC4,0xA4,
  0x80,0x5C,0x3C,0x23,0x13,0x0D,0x13,0x23,0x3C,0x5C,
  0x80,0xA4,0xC5,0xDF,0xF0,0xF6,0xF0,0xDF,0xC5,0xA4,
  0x80,0x5C,0x3B,0x21,0x10,0x0A,0x10,0x21,0x3B,0x5C,
  0x80,0xA5,0xC7,0xE2,0xF3,0xF9,0xF3,0xE2,0xC7,0xA5,
  0x80,0x5B,0x39,0x1E,0x0D,0x07,0x0D,0x1E,0x39,0x5B,
  0x80,0xA6,0xC8,0xE4,0xF5,0xFB,0xF5,0xE4,0xC8,0xA6,
  0x80,0x5A,0x38,0x1C,0x0B,0x05,0x0B,0x1C,0x38,0x5A,
  0x80,0xA7,0xC9,0xE5,0xF7,0xFD,0xF7,0xE5,0xC9,0xA7,
  0x80,0x59,0x37,0x1B,0x09,0x03,0x09,0x1B,0x37,0x59,
  0x80,0xA7,0xCA,0xE6,0xF8,0xFE,0xF8,0xE6,0xCA,0xA7,
  0x80,0x59,0x36,0x1A,0x08,0x02,0x08,0x1A,0x36,0x59,
  0x80,0xA7,0xCB,0xE7,0xF9,0xFF,0xF9,0xE7,0xCB,0xA7,
  0x80,0x59,0x35,0x19,0x07,0x01,0x07,0x19,0x35,0x59,
  0x80,0xA7,0xCB,0xE7,0xF9,0xFF,0xF9,0xE7,0xCB,0xA7,
  0x80,0x59,0x35,0x19,0x07,0x01,0x07,0x19,0x35,0x59,
  0x80
  };

// The last 20 bytes (21 with the extra on the end)
// define a single cycle of full amplitude sinusoid.
#define one  (&data[24*20])      // Sine table pointer for a one bit
#define zero (&data[25*20])      // Sine table pointer for a zero bit

// Useful macros for setting and resetting bits
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

// Variables used by the timer ISR to generate sinusoidal information.
volatile char    rgchBuf[256];    // Buffer of text to send
volatile uint8_t head = 0;        // Buffer head (next character to send)
volatile uint8_t tail = 0;        // Buffer tail (next insert point)

volatile uint16_t vcChar = 0;     // Current varicode char being sent

volatile int   cbHalfBit = 500;   // 500 phase points required for PSK 1/2 bit time

volatile char *pbSine = zero;
volatile int   cbDirection = 500;
volatile char  fSendOne = false;

//volatile char *pbSine = one;
//volatile int   cbDirection = 32;
//volatile char  fSendOne = true;

volatile char ix       = -1;
volatile char phase    = 1;
volatile char fFullBit = 0;

volatile char cZeroBits = 0;
volatile char maxZeroBits = 2;

// Setup timer2 with prescaler = 1, PWM mode to phase correct PWM
// See the ATMega datasheet for all the gory details

void timer2Setup()
{
  // Clock prescaler = 1
  sbi (TCCR2B, CS20);    // 001 = no prescaling
  cbi (TCCR2B, CS21);
  cbi (TCCR2B, CS22);

  // Phase Correct PWM
  cbi (TCCR2A, COM2A0);  // 10 = clear OC2A on compare match when up counting
  sbi (TCCR2A, COM2A1);  //      set OC2A on compare match when down counting

  // Mode 1
  sbi (TCCR2A, WGM20);   // 01 = Mode 1 uses 0xff as TOP value
  cbi (TCCR2A, WGM21);
}

// Timer 2 interrupt service routine (ISR).
//
// Grab the next phase point from the table and 
// set the amplitude value of the sinusoid being
// constructed.  For a one bit, set 500 phase points
// (20 amplitudes of 25 samples each) to ramp
// down to zero and then immediately back up to full
// amplitude for a total of 1024 phase points.
//
// For a zero bit, there is not amplitude or phase
// change, so we just play 32 phase points of
// full amplitude data 32 times for a total of 1024
// phase points.
//
// Each end of the ramp-up table starts with a zero 
// crossing byte, so there is one extra byte in
// the table (501 entries).  Ramping up plays bytes
// 0 -> 499 and ramping down plays bytes 500 -> 1
// allowing each direction to start at the zero
// crossing point.
ISR(TIMER2_OVF_vect)
{
  // Set current amplitude value for the sine wave 
  // being constructed taking care to invert the
  // phase when processing the table in reverse order.
  OCR2A = *pbSine * ix * phase;
  pbSine += ix;
  
  // At the half bit time, we need to change phase
  // if generating a zero bit
  if (0 == --cbHalfBit)
  {
    cbHalfBit = 500;  // Reset 1/2 PSK bit time phase counter
    
    // Get the next varichar bit to send
    if (fFullBit)
    {
      // Count the number of sequential zero bits
      if (fSendOne = vcChar & 1) cZeroBits = 0; else cZeroBits++;

      // Shift off the most least significant bit.
      vcChar >>= 1;
      
      // If we have sent two zero bits, end of character has occurred
      if (cZeroBits > maxZeroBits)
      {
        cZeroBits = 0;
        
        // If send buffer not empty, get next varicode character
        if (head != tail)
        {
          // Assumes a 256 byte buffer as index increments modulo 256
          vcChar = varicode[rgchBuf[head++]];
        }
        else
          if (maxZeroBits > 2) cbi (TIMSK2,TOIE2); else maxZeroBits = 75;
      }
    }
    
    fFullBit = !fFullBit;  // Toggle end of full bit flag
    
    // When we get done ramping down, phase needs to
    // change unless we are sending a one bit
    if (ix < 0 &&!fSendOne) phase = -phase;
  }
  
  // At the end of the table for the bit being
  // generated, we need to change direction
  // and process the table in the other direction.
  if (0 == --cbDirection)
  {
    cbDirection = fSendOne ? 20 : 500;
    ix = -ix;
  }
}

void setup() 
{
  // PWM output for timer2 is pin 10 on the ATMega2560
  // If you use an ATMega328 (such as the UNO) you need
  // to make this pin 11
  // See https://spreadsheets.google.com/pub?key=rtHw_R6eVL140KS9_G8GPkA&gid=0
  pinMode(10, OUTPUT);   // Timer 2 PWM output on mega256 is pin 10

  // Set up timer2 to a phase correct 32kHz clock
  timer2Setup();

  // Put something in the buffer to be sent
  strcpy((char *) &rgchBuf[0], "\nCQ CQ CQ de ko7m ko7m ko7m"
                               "\nCQ CQ CQ de ko7m ko7m ko7m"
                               "\nCQ CQ CQ de ko7m ko7m ko7m CN87xp pse k\n");
  tail = strlen((const char *) rgchBuf);
  head = 0;
  
  sbi (TIMSK2,TOIE2);    // Enable timer 2.
}

void loop() 
{
}

Tuesday, July 15, 2014

Minima hardware build

I have spent some time today organizing my Minima hardware so that it is not quite so fragile and breadboard-ish in preparation for starting to put together my own rig.

I am comfortable that I have the software in good shape and it is time to think about pulling together my own build.

Here is what I have for a front panel.  There is a 20 column by 4 line display, three push-buttons and a rotary encoder.  Readers of my blog may recognize this as the panel for my beacon project which is being re-purposed for this project.


I have mounted an I2C daughter board on the LCD in order to reduce the number of pins required to support the LCD.  I am not going to use the plethora of buttons I have seen on other designs.  I am also using a commercially available Arduino Uno board rather than build a controller board.  I have mounted it on the back of the LCD.  The remainder of the electronics of the radio will be in the bottom of the box.


I am replacing the potentiometer tuning with a rotary encoder and adding my iambic keyer code to the main Minima sketch.  If sufficient flash is available, I will also add my Arduino beacon code to the mix.  This may require an ATMega2560 device with its larger flash and RAM.  There may be sufficient space, but RAM in particular is getting a bit tight.