The short version: after the first home-made prototype of the interface board I made a few changes to the circuit, re-designed the board, sent it out to have a small run professionally made, started work on schematic for the main board, ordered the parts for both, and planned to release a quick demo package after building a new interface board (rather than release my mess of spaghetti code and half-arsed functions as a 'demo' that'd need modification to work with the new design).
Most of that went swimmingly. But…
Installing and testing the interface board. |
But now the final version of the interface board is built, tested, and working. The picture above shows just how small the board is - at 50x57mm it's the same size as the original cover plate on the back of the FRG-7700, and even uses the same mounting holes & rivets! The pic to the left shows how the radio's existing memory unit wiring plugs into the board (SV1 & SV2, which connect to the main board, are mounted on the component side).
Just for info, the main board is planned to be only slightly larger - the same width (50mm), and under 100mm long - and will piggyback mount on the interface board. The whole thing will be a fraction of the size of the original...
Interface Board v3 schematic |
"Main" board demo schematic |
(edit, 17/07: ordered the boards on Monday. Haven't heard from iTead since they received the gerbers, which is generally a good sign [means the gerbers passed their factory audit OK]. Hopefully I'll hear from them that the boards are in the mail soon...)
That leaves the software. At the moment it's necessarily in a very bare-bones state, since much of it is dependent on the main board. The basic functions needed to prove that the idea can work have been written, and indeed do work - albeit as a very basic memory unit with no fine tune and a single memory position that is cleared when the radio is turned off…
(Truth be told, it wouldn't be hard to tidy the software up a little more so that the Arduino-and-breadboard circuit here recreates all the functions of the original MU-7700 - except for fine tune and the need for batteries. It's just that I've got some extra features in mind, and how well they work out in practice will determine exactly how the storage and retrieval routines are structured/written.)
Anyway, the current Arduino software - and I emphasise again that it's a very rudimentary, bare-bones, proof-of-concept, collection of functions - can be downloaded here, or you can read through it down below. You'll also need to download and install a couple of libraries - my AD9850_Serial library (Google Drive link) and Martin Nawrath's FreqCounter library (website link).
Note: I use OSX's Xcode IDE with embedXcode for programming, which inserts/requires a load of extraneous stuff at the top of the source. If you're using the Arduino's so-called IDE**, don't worry about all the extraneous code-sense & function declarations in the first 40 lines or so - the compiler ignores them and the source code compiles as-is without any changes necessary.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 | // FRG-7700 Memory Module Demo // // Software for Arduino-based FRG-7700 memory module // Developed with [embedXcode](http://embedXcode.weebly.com) // // Author ThinkingBrainDog // the7700.blogspot.com // // Date 8/07/14 2:00 PM // Version <#version#> // // Copyright © ThinkingBrainDog, 2014 // License <#license#> // // See ReadMe.txt for references // // Core library for code-sense #if defined(WIRING) // Wiring specific #include "Wiring.h" #elif defined(MAPLE_IDE) // Maple specific #include "WProgram.h" #elif defined(MPIDE) // chipKIT specific #include "WProgram.h" #elif defined(DIGISPARK) // Digispark specific #include "Arduino.h" #elif defined(ENERGIA) // LaunchPad MSP430, Stellaris and Tiva, Experimeter Board FR5739 specific #include "Energia.h" #elif defined(CORE_TEENSY) // Teensy specific #include "WProgram.h" #elif defined(ARDUINO) && (ARDUINO >= 100) // Arduino 1.0 and 1.5 specific #include "Arduino.h" #elif defined(ARDUINO) && (ARDUINO < 100) // Arduino 23 specific #include "WProgram.h" #else // error #error Platform not defined #endif // Include application, user and local libraries #include <Wire.h> #include <AD9850_Serial.h> #include <FreqCounter.h> // Function declarations // Guess - or go read the functions themselves - for details ;) void FRG7700_read(); byte FRG7700_read_memsw(); void FRG7700_read_membuttons(); void FRG7700_write(byte band, byte VCO, byte ant); void TCA9535_init(int addr); byte TCA9535_read(int addr, byte port); void TCA9535_write(int addr, byte port0_write, byte port1_write); long Get_VFO_Freq(); // Defines, constants, & variables // // DEFINES // Port expander addresses & I/O config masks // First port expander #define TCA9535_1_Addr 0x20 // Address #define TCA9535_1_Port0_Conf B00000000 // All pins output #define TCA9535_1_Port1_Conf B11100000 // Pins 0-4 output, 5-7 input // Second port expander #define TCA9535_2_Addr 0x21 // Address #define TCA9535_2_Port0_Conf B11111111 // All pins input #define TCA9535_2_Port1_Conf B11111111 // All pins input // Bitmasks // Used to extract relevant bits from the byte read #define AntFilt_bitmask B11100000 // Top 3 bits of port #define Band_bitmask B00111111 // Bottom 6 bits of port #define VCO_bitmask B11000000 // Top 2 bits of port // Anything else // Memory button pins #define MEM 2 // "M" switch connected to pin 2 #define MEM_R 4 // "MR" switch connected to pin 4 // // CONSTANTS // Lookup tables: // Memory # switch (12 position): [switch position - 1] = {read byte} const byte memory_in_map[] = {1,2,4,8,16,32,64,128,129,65,33,17}; const byte memory_in_map_len = 12; // Bandswitch inbound (0-29MHz): [band] = {read byte} const byte band_in_map[] = {4,5,6,7,8,9,16,17,18,19, 20,21,22,23,24,25,32,33,34,35, 36,37,38,39,40,41,48,49,50,51}; const byte band_in_map_len = 30; // Bandswitch outbound (0-29MHz): [band] = {write byte} // Bits 1 & 6 are 0 to allow interleaving/OR'ing with the outbound VCO data const byte band_out_map[] = {B10000000,B10100000,B10000001,B10100001,B00001000,B00101000, B00000100,B00100100,B00000101,B00100101,B10000100,B10100100, B10000101,B10100101,B00001100,B00101100,B00010000,B00110000, B00010001,B00110001,B10010000,B10110000,B10010001,B10110001, B00011000,B00111000,B00010100,B00110100,B00010101,B00110101}; const byte band_out_map_len = 30; // VCO outbound (0-3): [vco setting] = {write byte} // Only bits 1 & 6 are used, OR'd with the bandswitch outbound value const byte VCO_out_map[] = {B00000000,B01000000,B00000010,B01000010}; const byte VCO_out_map_len = 4; // Antenna filter outbound (0-6): [antfilter] = {write bits} // Note: position "5" is not found in the incoming data; B000 used there... const byte antfilt_out_map[] = {B000,B001,B100,B101,B010,B000,B110}; const byte antfilt_out_map_len = 7; // // Miscellaneous constants: // Frequency compensation factor for Get_VFO_Freq (is slightly Arduino/crystal // dependent - final design will allow calibration off 800Hz signal). const byte freq_comp = 3; // // VARIABLES byte read_port0 = 0; // default value of port0 byte read_port1 = 0; // default value of port1 byte read_memsw = 0; // default value of memory switch byte read_bandsw = 0; // default value of bandswitch byte read_VCO = 0; // default value of VCO byte read_antfilter = 0; // default value of antenna filter long read_VFO = 0; // default value of VFO frequency boolean button_m = false; // default "M" button state boolean button_mr = false; // default "MR" button state boolean m_flag = false; // previous button state flag boolean mr_flag = false; // previous button state flag // Temp vars for storing a single setting in memory byte s_mem_band = 0; byte s_mem_vco = 0; byte s_mem_antfilt = 0; long s_mem_vfo = 0; // // ANYTHING ELSE // Create a DDS object (pins are CLK, FQ_UD, DATA, RESET) // Currently, for the demo, we use a cheap AD9850 DDS module. AD9850_Serial dds(8,9,10,11); // ***** Program proper starts here // Setup void setup(){ // Start I2C comms Wire.begin(); // Initialise the TCA9535 port expanders TCA9535_init(TCA9535_1_Addr); TCA9535_init(TCA9535_2_Addr); // Initialise the memory button (M & MR) pins (inputs, internal pullups on) pinMode(MEM, INPUT_PULLUP); pinMode(MEM_R, INPUT_PULLUP); // Prime/clear the freq. counter (discard first reading after start) Get_VFO_Freq(); // Initialise/reset the DDS dds.reset(); // And turn it off (it defaults to on at powerup/reset) dds.off(); // DEBUG // Set up serial port for development / debugging // Serial.begin(9600); Serial.print("FRG-7700 Memory Unit. Waiting..."); } // Main loop void loop(){ // Read the front panel "M" & "MR" buttons FRG7700_read_membuttons(); // Handle "M" button press if (button_m != m_flag){ if (button_m == true){ // Do something if button has been PRESSED. Serial.println("M button pressed. Reading settings..."); // Read & display radio settings FRG7700_read(); Serial.print("Band="); Serial.print(read_bandsw); s_mem_band = read_bandsw; Serial.print(", VCO="); Serial.print(read_VCO); s_mem_vco = read_VCO; Serial.print(", Filter="); Serial.print(read_antfilter); s_mem_antfilt = read_antfilter; Serial.print(", VFO="); s_mem_vfo = Get_VFO_Freq(); Serial.print(s_mem_vfo); Serial.println("Hz"); Serial.println("Settings stored in memory\n"); } else { // Do something if button has been RELEASED. } } m_flag = button_m; // Handle "MR" button press if (button_mr != mr_flag){ if (button_mr == true){ // Do something if button has been PRESSED. Serial.println("MR button pressed. Controlling radio..."); // Write settings to radio FRG7700_write(s_mem_band, s_mem_vco, s_mem_antfilt); // Set memory unit VFO (DDS module) to stored frequency dds.setfreq(s_mem_vfo); // Turn on the DDS output dds.on(); } else { // Do something if button has been RELEASED. Serial.println("MR button released. Returning control...\n"); // Turn off the memory unit VFO dds.off(); // Re-initialise TCA9535s (sets all I/O pins to input/hi-z) TCA9535_init(TCA9535_1_Addr); TCA9535_init(TCA9535_2_Addr); } } mr_flag = button_mr; } // ***** Functions-B-Here void FRG7700_read(){ // Read radio settings - band, VCO, & antenna settings, & // VFO frequency (memory switch is read separately by FRG7700_read_memsw) // Modifies: // read_bandsw : bandswitch position (remapped to 0-29MHz) // read_VCO : VCO setting (shifted to 0-3) // read_antfilter : antenna filter setting (shifted to 0-6) // read_VFO : VFO frequency (approx. 2.5-3.5MHz; 5Hz resolution) // // Locals byte tmp, port; // Get bandswitch & VCO data // Band = TCA9535 #2, port 1 bits 0-5 // VCO = TCA9535 #2, port 1 bits 6-7 port = TCA9535_read(TCA9535_2_Addr,1); // returns byte // Remap bandswitch to 0-29MHz for (tmp=0; tmp<band_in_map_len; tmp++){ if (band_in_map[tmp] == (port & Band_bitmask)){ read_bandsw = tmp; } } // Mask & shift VCO setting to values 0-3 read_VCO = (port & VCO_bitmask) >> 6; // Get antenna filter data // Antenna filter = TCA9535 #1, port1 bits 5-7 port = TCA9535_read(TCA9535_1_Addr,1); // Mask & shift antenna filter setting to values 0-6 read_antfilter = (port & AntFilt_bitmask) >> 5; // Get VFO frequency read_VFO = Get_VFO_Freq(); // Everything's done; go home now... } byte FRG7700_read_memsw(){ // Read memory switch // Returns byte containing remapped memory switch position (0-11) // // Locals byte tmp, port, result; // Get memory switch data port = TCA9535_read(TCA9535_2_Addr,0); // Remap memory switch to 0-11 for (tmp=0; tmp<memory_in_map_len; tmp++){ if (memory_in_map[tmp] == port){ result = tmp; } } return result; } void FRG7700_read_membuttons(){ // Check the M & MR buttons; set Booleans button_m & button_mr // as appropriate. // Note: MR cuts the throat of M - both can't be true at the same time, // and MR always wins. // Modifies: // button_m : front panel M button (TRUE or FALSE) // button_mr : front panel MR button (TRUE or FALSE) if (digitalRead(MEM) == LOW){ button_m = true; } else { button_m = false; } if (digitalRead(MEM_R) == LOW) { button_mr = true; } else { button_mr = false; } } void FRG7700_write(byte band, byte VCO, byte ant){ // Write the info to the FRG-7700 through TCA9535_1 // Port 0 (bits 0-7) = band & VCO, Port 1 (bits 2-4) = filter // // Locals byte band_vco_out = (band_out_map[band] | VCO_out_map[VCO]); byte filter_out = (antfilt_out_map[ant] << 2); // Hmmmm... will have to think about the best way to do this! Wire.beginTransmission(TCA9535_1_Addr); Wire.write(6); // Configure Port0/1 Wire.write(TCA9535_1_Port0_Conf); Wire.write(TCA9535_1_Port1_Conf); Wire.endTransmission(); TCA9535_write(TCA9535_1_Addr, band_vco_out, filter_out); } void TCA9535_init(int addr){ // Initialise a TCA9535 to all pins as input (Hi-Z). // // Note: Wire.begin needs to have been called already e.g. in setup(). // The Wire library doesn't actually send anything until // Wire.endTransmission() is called... Wire.beginTransmission(addr); Wire.write(6); // Port 0 configuration register Wire.write(0xFF); // Configure Port 0, all bits INPUT Wire.write(0xFF); // Configure Port 1, all bits INPUT Wire.endTransmission(); // Write config to wire } byte TCA9535_read(int addr, byte port){ // Read from the specified TCA9535 the specified port (0 or 1) // Returns: byte of data // // A bit of trivia / something to remember: although the I2C address "addr" // is a byte (at least as far as we're concerned), Wire.requestFrom() // expects an int. Strangely, Wire.beginTransmission etc. don't... // // Locals byte retval; port = constrain(port, 0, 1); // Probably overkill... Wire.beginTransmission(addr); // Start talking to device Wire.write(port); // Port to read (0 or 1) Wire.endTransmission(); Wire.requestFrom(addr,1); // Request 1 byte if (Wire.available()){ retval = Wire.read(); // Wait until we have it... } Wire.endTransmission(); // Stop talking to device return retval; // Return byte read } void TCA9535_write(int addr, byte port0_write, byte port1_write){ // Write output pins of the specified TCA9535 Wire.beginTransmission(addr); // Start talking to device Wire.write(2); // Start write @ port 0 Wire.write(port0_write); // Write Wire.write(port1_write); Wire.endTransmission(); // Stop talking to device } long Get_VFO_Freq(){ // Get VFO frequency (in Hz) // Returns: long int containing frequency in Hz (5Hz resolution) // // Colour me surprised - this is actually quite stable/accurate when // calibrated; compared to my Tektronix counter it's within +-7Hz or so! // // Locals long frq; FreqCounter::f_comp = freq_comp; // Compensation = 3 works for me FreqCounter::start(200); // Start count; gatetime of 200ms while (FreqCounter::f_ready == 0){ // Wait until counter ready frq = (FreqCounter::f_freq*5); } return frq; // Return result in Hz } |
A quick run-through:
- Lines 58-66 define the addresses of the two TCA9535 I/O chips on the interface board, and bitmasks used for configuring the pins as inputs or outputs. These are only used when actually driving the radio - to avoid the memory unit intefering during normal operation, when the MR button is out and front panel controls are operational all pins (input and output) are configured as high-impedance inputs.
- Lines 68-71 define bitmasks used for individually extracting the antenna filter, band, and VCO setting data. Only the memory switch occupies a full 8-bit port of one TCA9535 - the band switch (6 bits) and VCO setting (2 bits) are together on one 8-bit port, and the antenna filter occupies 3 bits on another 8-bit port. To separate them, we simply logical-AND the bitmask with the value from the port.
- Lines 78-103 define various lookup tables for converting memory switch, band switch, and VCO data to and from "human logical" values. When reading each setting the bits are in one (mixed up) order; when writing each setting they need to go out in a different (but still mixed-up!) order. To save my sanity, the lookup tables are used to convert them to & from human readable values - e.g 1-12 for the memory switch, 0-29 for the band switch, etc.
- Lines 111-126 sets up some global variables used throughout the program with default values.
- Line 131 is where we create an object, named 'dds', using the AD9850_Serial library. This represents the AD9850 DDS module connected to pins 8-11 that serves as the memory unit's VFO.
- Setup happens in lines 137-157 - I2C communications are started (line 139), the TCA9535s are initialised (lines 141-142), the Arduino pins used to read the "M" and "MR" buttons are configured (lines 144-145), the frequency counter routine is run once to initialise/reset it (line 147), the DDS module is initialised/reset and turned off (lines 149-151), and the serial port (used for debugging) is started (lines 155-156).
- If the "M" button is pressed a message is sent to the serial port (line 167), the 'FRG7700_read' function is called to read the radio settings (line 169), and the 'Get_VFO_Freq' function is called to count the radio's VFO (front panel tuning knob) frequency (line 176). At the same time, the radio settings and VFO frequency are printed to the serial port and stored in variables (s_mem_band, s_mem_vco, etc).
- When the "MR" button is first pressed (it's a latching button), another message is sent to the serial port (line 188). The stored memory settings are then sent to the radio (line 190), and the DDS is programmed to produce the stored VFO frequency (line 192) and its output turned on (line 194).
- When the "MR" button is later pressed to un-latch it, a message indicating this is sent to the serial port (line 197). The DDS is then turned off (line 199), and the TCA9535s re-initialised to set all pins to high impedance inputs (lines 201-202) which returns control to the front panel.
(I'm also sure you can find many improvements. Me too. Remember again that this is a collection of code fragments meant for testing and proving individual functions, not final polished software!)
Now that I've written all that, it brings up the issue of how I want to release the whole project when it's done. To be honest, I'm … unsure. My feelings boil down to three things:
- I want people to learn by doing, not just buy a box and plug it in - though I realise that's not an option for some people.
- I would love to see people take my all my investigation & development work, circuit design, and software, and improve it, but
- I would hate to see my exact design ripped off by someone else just so they could make money from it.
The plan at the moment is to release the circuit schematics, parts list, and base software (with all the functionality of the original MU-7700 memory unit) freely under a BSD licence, but hang on to my board designs and any enhanced software. Anyone is free to modify the design, make their own boards, and improve the software, and I'll possibly have boards/kits/finished units to sell.
So … does that seem fair?
(* Oh, yeah, did I mention that? All the memory unit connectors (and most of the others) in the radio are metric, not imperial. "So what?" you might say, "yeah, imperial is big in the electronics world, but metric is pretty common amongst connectors".
To which I would say "go on, find the right connectors". Whatever the original ones were (the connector part numbers listed in the service manual couldn't be found) - and they're a pretty basic pin-header type, just in metric - they don't seem to be made anymore. The closest I could find was some Multicomp (Element14's in-house brand) headers (e.g.). While not exactly right, those are a near-perfect fit - but not available in all sizes, and are being discontinued. I ended up using mostly Molex 22-04-1xx1 (where 'xx' is the number of pins) connectors, which are a slightly less-perfect fit since the pins are ~2mm too long…)
(** Really, guys - if your aim is to get people interested in programming, then maybe something remotely approaching a decent IDE is in order? That … thing … you provide which claims to be an IDE is terrible. Beginners would almost be better off using Notepad or Textedit…)
No comments:
Post a Comment