1
|
|
2
|
/*
|
3
|
|
4
|
Quansheng TG-UV2 Utility by Mike Nix <mnix@wanm.com.au>
|
5
|
|
6
|
Currently only reads the device memory, and displays the contents nicely formatted.
|
7
|
|
8
|
|
9
|
Supports:
|
10
|
All channel config, except CTSS/DCS settings (we know where they are, just haven't
|
11
|
bothered to interpret them yet)
|
12
|
|
13
|
All the settings configurable via Options in the original windows utility.
|
14
|
|
15
|
Both of the known keypad dances for enabling/disabling bands are detected/displayed.
|
16
|
and yes, we can do the same thing by updating the config.
|
17
|
|
18
|
Saves the config read from the device to a file (tguv2.dat)
|
19
|
**** THIS IS NOT THE SAME FORMAT USED BY THE WINDOWS UTILITY ****
|
20
|
The windows utility saves in some proprietry format... we just dump
|
21
|
a copy of the radio memory to disk :)
|
22
|
|
23
|
Reads a file saved above, and uploads it to the radio.
|
24
|
|
25
|
TODO:
|
26
|
There are at least 5 memory locations that do stuff I can't figure out (config->unk*)
|
27
|
|
28
|
I never found a way to enable transmit on the 470-520 band
|
29
|
(Australian UHF CB is at 476-477 Mhz). Probably need a firmware hack for this :(
|
30
|
|
31
|
Command-line options for settings so we can change the config easily.
|
32
|
|
33
|
Find a copy of the firmware, and the update utility so we can really open it up.
|
34
|
|
35
|
|
36
|
There is a command line option to manually set any byte in the config area for
|
37
|
testing purposes, but this is not the way to configure your radio normally :)
|
38
|
|
39
|
There is another command line option for sending arbitrary data to the radio in
|
40
|
programming mode...
|
41
|
|
42
|
|
43
|
Other Info:
|
44
|
-----------
|
45
|
There are positive responses (ACK) to the following commands if followed
|
46
|
by any other byte:
|
47
|
G, O, P, Q but have no idea what they do....
|
48
|
|
49
|
The known commands for programming and getting the model number are:
|
50
|
\002PnOGdAM Enter Programming Mode
|
51
|
M\002 Get Model Number? (Returns 'P5555' followed by F4 00 00)
|
52
|
R Read config memory
|
53
|
W Write config memory
|
54
|
|
55
|
Any 1-byte after entering program mode followed by 0x02 will return the model number
|
56
|
|
57
|
Other commands will not work until after you get the model number.
|
58
|
|
59
|
R command is followed by addr_hi, addr_lo, length
|
60
|
W command is followed by addr_hi, addr_lo, length then data bytes
|
61
|
|
62
|
A null byte is sent by the radio every time the display changes... no idea what use that is.
|
63
|
|
64
|
Further testing reveals that any 8 bytes written will do to start programming mode.
|
65
|
** maybe there is a special sequence for firmware mode?
|
66
|
Actually, any sequence of more than 1 byte containing "M" seems to hit program mode.
|
67
|
|
68
|
*/
|
69
|
|
70
|
|
71
|
#include <string.h>
|
72
|
#include <stdio.h>
|
73
|
#include <stdlib.h>
|
74
|
#include <stdint.h>
|
75
|
#include <sys/types.h>
|
76
|
#include <sys/stat.h>
|
77
|
#include <sys/select.h>
|
78
|
#include <fcntl.h>
|
79
|
#include <termios.h>
|
80
|
#include <sys/ioctl.h>
|
81
|
#include <unistd.h>
|
82
|
#include <errno.h>
|
83
|
|
84
|
// Quansheng TG-UV2 uses 9600,N,8,2
|
85
|
#define RADIO_BAUD B9600
|
86
|
|
87
|
#define ACK 0x06
|
88
|
|
89
|
#define TGUV2_MEMSIZE 0x2000
|
90
|
#define TGUV2_CHANNELS 200
|
91
|
#define TGUV2_BANDS 8
|
92
|
|
93
|
// shorthand forms of integer types for convenience
|
94
|
typedef uint8_t u8;
|
95
|
typedef uint16_t u16;
|
96
|
typedef uint32_t u32;
|
97
|
typedef uint64_t u64;
|
98
|
|
99
|
#pragma pack(1)
|
100
|
struct tguv2_channel {
|
101
|
u32 freq; // frequency in 10s of Hz, stored in BCD
|
102
|
u32 txofs;// offset to tx frequency, 10s of Hz, stored in BCD
|
103
|
u8 rc_lo;
|
104
|
u8 tc_lo;
|
105
|
u8 code_type; // low nibble is rc_hi, high nibble is tc_hi
|
106
|
// or could be type number (0-3)
|
107
|
u8 flags1;
|
108
|
u8 flags2;
|
109
|
u8 flags3; // always 0xFF ??
|
110
|
u8 bandwidth; // channel step / bandwidth - 6=25, 9=100
|
111
|
u8 tx_power;
|
112
|
};
|
113
|
|
114
|
struct tguv2_channel2 { // extra channel data
|
115
|
u8 name[6];
|
116
|
u8 data[10];
|
117
|
};
|
118
|
|
119
|
#define TXPOWER_LOW 2
|
120
|
#define TXPOWER_MID 1
|
121
|
#define TXPOWER_HI 0
|
122
|
|
123
|
#define CF1_SHIFT_POS 0x01
|
124
|
#define CF1_SHIFT_NEG 0x02
|
125
|
#define CF2_REVERSE_ON 0x01
|
126
|
#define CF2_SCRAMBLE_ON 0x02
|
127
|
#define CF2_WideFM 0x10
|
128
|
|
129
|
struct vfo {
|
130
|
u8 current; // memory slot for this vfo: 0-199 = channel mode, 200-204=vfo band
|
131
|
u8 chan; // last used channel no
|
132
|
u8 memno; // last used band (as mem slot number 200-204)
|
133
|
};
|
134
|
|
135
|
struct tguv2_config {
|
136
|
struct tguv2_channel channels[TGUV2_CHANNELS]; // the channel memories
|
137
|
struct tguv2_channel bands[TGUV2_BANDS]; // current / most recent settings for each band
|
138
|
u8 cflags[TGUV2_CHANNELS]; // scan flags and band numbers
|
139
|
u8 bandid[TGUV2_BANDS]; // band ids (F0 - F4)
|
140
|
u8 reserved1[0x30]; // unused?, all 0xFF
|
141
|
|
142
|
u8 unk1; // 00
|
143
|
u8 squelch; // 0 - 9
|
144
|
u8 time_out_timer; // 01 (minutes)
|
145
|
u8 priority_channel;
|
146
|
|
147
|
u8 keylock; // 01=off, 00=on
|
148
|
u8 busy_lockout; // 01=off, 00=on
|
149
|
u8 vox; // 00=off, 1-9
|
150
|
u8 unk2; // FF
|
151
|
|
152
|
u8 keybeep; // 00=on, 01=off
|
153
|
u8 display; // 00=Freq, 01=Channel, 02=Name
|
154
|
u8 step; // FF=5, ??
|
155
|
u8 unk3; // FF
|
156
|
|
157
|
u8 unk4; // 00
|
158
|
u8 mode; // 00=DualWatch, 01=cross-band, other=off
|
159
|
u8 end_tone; // 0=on, 1=off
|
160
|
u8 vfo_model; // 0=VFO Disabled, other=VFO Enabled
|
161
|
|
162
|
struct vfo vfo[2];
|
163
|
|
164
|
u8 unk5; // 00
|
165
|
u8 reserved2[9]; // Unused?, ALL 0xFF
|
166
|
|
167
|
// band_restrict can be toggled by holding PTT+MON at Power on, until you hear a second beep
|
168
|
u8 band_restrict; // !=01 = all bands enabled
|
169
|
|
170
|
// this can be toggled by holding BAND during power on until the display is ******
|
171
|
// then enter 350390 on the keypad.
|
172
|
u8 txen350390; // FF=unset, 00=DIS, 01=EN (!=01 == DIS)
|
173
|
|
174
|
u8 reserved3[0xDE]; // Unused?, ALL 0xFF
|
175
|
|
176
|
struct tguv2_channel2 channels2[TGUV2_CHANNELS]; // channel names etc
|
177
|
};
|
178
|
|
179
|
#define CFLAG_SCAN 0x80
|
180
|
#define CFLAG_BANDMASK 0x07
|
181
|
|
182
|
#pragma pack()
|
183
|
|
184
|
#define PMODE_NONE -1
|
185
|
#define PMODE_PROGRAM 2
|
186
|
#define PMODE_FIRMWARE 1
|
187
|
|
188
|
double bandwidthmap[]={ 5.0, 6.25, 10.0, 12.5, 15, 20, 25, 30, 50, 100, 101, 102, 103, 104, 105, 5.0 };
|
189
|
|
190
|
// current radio state
|
191
|
int radio_mode=PMODE_NONE;
|
192
|
|
193
|
// Serial port options.
|
194
|
int debug=3;
|
195
|
int TX_enabled=0;
|
196
|
|
197
|
int TX_auto_on=1;
|
198
|
int RX_auto_on=1;
|
199
|
|
200
|
|
201
|
void hexdump(unsigned char *data, int len)
|
202
|
{
|
203
|
int i;
|
204
|
for (i=0; i<len; i++)
|
205
|
printf(" %02x", data[i]);
|
206
|
printf(" ");
|
207
|
for (i=0; i<len; i++)
|
208
|
printf("%c", isprint(data[i]) ? data[i] : '.');
|
209
|
}
|
210
|
|
211
|
void TX_on(int fd)
|
212
|
{
|
213
|
// raise RTS
|
214
|
if (TX_auto_on) {
|
215
|
// hardware works this out automatically so don't do anything
|
216
|
}
|
217
|
else {
|
218
|
int status;
|
219
|
if (debug>2) printf("RTS ON\r\n");
|
220
|
ioctl(fd, TIOCMGET, &status);
|
221
|
status |= TIOCM_RTS;
|
222
|
if (ioctl(fd, TIOCMSET, &status)) {
|
223
|
perror("ioctl");
|
224
|
}
|
225
|
}
|
226
|
TX_enabled=1;
|
227
|
}
|
228
|
|
229
|
void TX_off(int fd)
|
230
|
{
|
231
|
// lower RTS
|
232
|
if (TX_auto_on) {
|
233
|
// hardware does this automatically so don't do anything
|
234
|
}
|
235
|
else {
|
236
|
int status;
|
237
|
if (debug>2) printf("RTS OFF\r\n");
|
238
|
tcdrain(fd);
|
239
|
ioctl(fd, TIOCMGET, &status);
|
240
|
status &= ~TIOCM_RTS;
|
241
|
ioctl(fd, TIOCMSET, &status);
|
242
|
}
|
243
|
TX_enabled=0;
|
244
|
}
|
245
|
|
246
|
void RX_on(int fd)
|
247
|
{
|
248
|
// raise DTR
|
249
|
if (RX_auto_on) {
|
250
|
}
|
251
|
else {
|
252
|
int status;
|
253
|
if (debug>2) printf("DTR ON\r\n");
|
254
|
ioctl(fd, TIOCMGET, &status);
|
255
|
status |= TIOCM_DTR;
|
256
|
if (ioctl(fd, TIOCMSET, &status)) {
|
257
|
perror("ioctl");
|
258
|
}
|
259
|
}
|
260
|
}
|
261
|
|
262
|
void RX_off(int fd)
|
263
|
{
|
264
|
// lower DTR
|
265
|
if (RX_auto_on) {
|
266
|
}
|
267
|
else {
|
268
|
int status;
|
269
|
if (debug>2) printf("DTR OFF\r\n");
|
270
|
ioctl(fd, TIOCMGET, &status);
|
271
|
status &= ~TIOCM_DTR;
|
272
|
ioctl(fd, TIOCMSET, &status);
|
273
|
}
|
274
|
}
|
275
|
|
276
|
|
277
|
void TX(int fd, unsigned char *data, int len)
|
278
|
{
|
279
|
if (!TX_enabled) TX_on(fd);
|
280
|
|
281
|
if (debug>3) {
|
282
|
printf("TX:");
|
283
|
hexdump(data,len);
|
284
|
printf("\r\n");
|
285
|
}
|
286
|
write(fd, data, len);
|
287
|
}
|
288
|
|
289
|
int RX(int fd, unsigned char *data, int len)
|
290
|
{
|
291
|
int rc = read(fd, data, len);
|
292
|
|
293
|
if (debug>3 && rc>0) {
|
294
|
printf("RX:");
|
295
|
hexdump(data,rc);
|
296
|
printf("\r\n");
|
297
|
}
|
298
|
return rc;
|
299
|
}
|
300
|
|
301
|
|
302
|
#define TEST1
|
303
|
|
304
|
int start_pgm_mode(int fd, int pmode)
|
305
|
{
|
306
|
u8 buf[8];
|
307
|
|
308
|
if (pmode==PMODE_FIRMWARE) {
|
309
|
buf[0]=0x02;
|
310
|
buf[1]='F';
|
311
|
buf[2]='i';
|
312
|
buf[3]='r';
|
313
|
buf[4]='m';
|
314
|
buf[5]='w';
|
315
|
buf[6]='a';
|
316
|
buf[7]='r';
|
317
|
}
|
318
|
else {
|
319
|
#ifndef TEST1
|
320
|
buf[0]=0x02;
|
321
|
buf[1]=0x50; // "PnOGdAM"
|
322
|
buf[2]=0x6E;
|
323
|
buf[3]=0x4F;
|
324
|
buf[4]=0x47;
|
325
|
buf[5]=0x64;
|
326
|
buf[6]=0x41;
|
327
|
buf[7]=0x4D;
|
328
|
#else
|
329
|
buf[0]=0x02;
|
330
|
buf[1]='P'; // "PnOGdAM"
|
331
|
buf[2]='n';
|
332
|
buf[3]='O';
|
333
|
buf[4]='G';
|
334
|
buf[5]='d';
|
335
|
buf[6]='A';
|
336
|
buf[7]='M';
|
337
|
#endif
|
338
|
}
|
339
|
|
340
|
TX(fd, buf, 8);
|
341
|
|
342
|
if (RX(fd, buf, 1)==1) {
|
343
|
if (buf[0]==ACK) {
|
344
|
printf("Radio detected\n");
|
345
|
radio_mode=pmode;
|
346
|
return 0;
|
347
|
}
|
348
|
printf("Bad response from radio: 0x%02x\n", buf[0]);
|
349
|
return -ENODEV;
|
350
|
}
|
351
|
printf("No response from radio\n");
|
352
|
return -ENODEV;
|
353
|
}
|
354
|
|
355
|
int read_model(int fd, u8 *model) {
|
356
|
u8 buf[8];
|
357
|
int rc;
|
358
|
#ifndef TEST1
|
359
|
buf[0]=0x4D; // 'M'
|
360
|
buf[1]=0x02;
|
361
|
#else
|
362
|
buf[0]='M'; // 'M'
|
363
|
buf[1]=0x02;
|
364
|
#endif
|
365
|
TX(fd, buf, 2);
|
366
|
rc=RX(fd, model, 8);
|
367
|
if (rc!=8) {
|
368
|
printf("Error reading model data, got %d bytes:", rc);
|
369
|
if (rc>0) hexdump(model,rc);
|
370
|
printf("\n");
|
371
|
return -1;
|
372
|
}
|
373
|
buf[0]=ACK;
|
374
|
TX(fd, buf, 1);
|
375
|
if (RX(fd, buf, 1)!=1) {
|
376
|
printf("Didn't get expected ACK response\n");
|
377
|
return -1;
|
378
|
}
|
379
|
if (buf[0]!=ACK) {
|
380
|
printf("Expected ACK, got 0x%02x\n", buf[0]);
|
381
|
}
|
382
|
printf("Model: ");
|
383
|
hexdump(model, 8);
|
384
|
printf("\r\n");
|
385
|
return 0;
|
386
|
}
|
387
|
|
388
|
int read_block(int fd, void *dest, u8 *buf, u16 addr, u8 len) {
|
389
|
|
390
|
printf("Reading memory 0x%04x\r", addr);
|
391
|
memset(buf, 0, 12);
|
392
|
buf[0]='R';
|
393
|
buf[1]=(addr >> 8) & 0xFF;
|
394
|
buf[2]=addr & 0xFF;
|
395
|
buf[3]=len;
|
396
|
TX(fd, buf, 4);
|
397
|
if (RX(fd, buf, len+4)==len+4) {
|
398
|
if (buf[0]==0x57 && buf[3]==len &&
|
399
|
(buf[1]<<8 | buf[2]) == addr
|
400
|
) {
|
401
|
memcpy(dest, buf+4, len);
|
402
|
return 1;
|
403
|
}
|
404
|
else {
|
405
|
printf("\nunexpected data for address 0x%04x\n", addr);
|
406
|
}
|
407
|
}
|
408
|
else {
|
409
|
printf("\nFailed read for address 0x%04x\n", addr);
|
410
|
}
|
411
|
return 0;
|
412
|
}
|
413
|
|
414
|
int read_block2(int fd, void *dest, u8 *buf, u32 addr, u8 len) {
|
415
|
|
416
|
printf("Reading memory 0x%04x\r", addr);
|
417
|
memset(buf, 0, 12);
|
418
|
buf[0]='R';
|
419
|
buf[1]=(addr >> 24) & 0xFF;
|
420
|
buf[2]=(addr >> 16) & 0xFF;
|
421
|
buf[3]=(addr >> 8) & 0xFF;
|
422
|
buf[4]=addr & 0xFF;
|
423
|
buf[5]=len;
|
424
|
TX(fd, buf, 6);
|
425
|
if (RX(fd, buf, len+4)==len+4) {
|
426
|
if (buf[0]==0x57 && buf[5]==len &&
|
427
|
(buf[3]<<8 | buf[4]) == addr
|
428
|
) {
|
429
|
memcpy(dest, buf+4, len);
|
430
|
return 1;
|
431
|
}
|
432
|
else {
|
433
|
printf("\nunexpected data for address 0x%04x\n", addr);
|
434
|
}
|
435
|
}
|
436
|
else {
|
437
|
printf("\nFailed read for address 0x%04x\n", addr);
|
438
|
}
|
439
|
return 0;
|
440
|
}
|
441
|
|
442
|
int write_block(int fd, u8 *src, u16 addr, u8 len) {
|
443
|
|
444
|
u8 buf[4];
|
445
|
|
446
|
printf("Writing memory 0x%04x\r", addr);
|
447
|
buf[0]='W';
|
448
|
buf[1]=(addr >> 8) & 0xFF;
|
449
|
buf[2]=addr & 0xFF;
|
450
|
buf[3]=len;
|
451
|
TX(fd, buf, 4);
|
452
|
TX(fd, src, len);
|
453
|
if (RX(fd, buf, 1)==1) {
|
454
|
if (buf[0]!=ACK)
|
455
|
printf("Expected ACK, got 0x%02x for address 0x%04d\n", buf[0], addr);
|
456
|
}
|
457
|
else {
|
458
|
printf("\nDid not receive ACK for address 0x%04x\n", addr);
|
459
|
}
|
460
|
}
|
461
|
|
462
|
|
463
|
int test_cmd(int fd, unsigned char *cmd, u8 sendlen, u8 expectlen) {
|
464
|
|
465
|
printf("Radio Mode: ");
|
466
|
switch (radio_mode) {
|
467
|
case PMODE_NONE: printf("NONE"); break;
|
468
|
case PMODE_PROGRAM: printf("PROGRAM"); break;
|
469
|
case PMODE_FIRMWARE: printf("FIRMWARE"); break;
|
470
|
default : printf("Unknown: %d", radio_mode); break;
|
471
|
}
|
472
|
printf("\n");
|
473
|
|
474
|
printf("Test Command: (%d bytes) ", sendlen);
|
475
|
hexdump(cmd, sendlen);
|
476
|
printf("\n");
|
477
|
TX(fd, cmd, sendlen);
|
478
|
|
479
|
int dbg=debug;
|
480
|
debug=4;
|
481
|
int rc=RX(fd, cmd, expectlen);
|
482
|
debug=dbg;
|
483
|
printf("Test Command: RX returned %d (expected %d bytes)\n", rc, expectlen);
|
484
|
return 0;
|
485
|
}
|
486
|
|
487
|
|
488
|
int read_memory(int fd, struct tguv2_config *config, int setuponly) {
|
489
|
int addr, end;
|
490
|
u8 buf[12];
|
491
|
u8 *data=(u8*)config;
|
492
|
|
493
|
addr=0;
|
494
|
end=TGUV2_MEMSIZE;
|
495
|
if (setuponly) {
|
496
|
addr=0x0C80;
|
497
|
end =0x0F00;
|
498
|
}
|
499
|
|
500
|
for (; addr < end; addr += 8) {
|
501
|
read_block(fd, data+addr, buf, addr, 8);
|
502
|
}
|
503
|
return TGUV2_MEMSIZE;
|
504
|
}
|
505
|
|
506
|
|
507
|
int write_memory(int fd, struct tguv2_config *config, int setuponly) {
|
508
|
int addr, end;
|
509
|
u8 *data=(u8*)config;
|
510
|
|
511
|
addr=0;
|
512
|
end=TGUV2_MEMSIZE;
|
513
|
if (setuponly) {
|
514
|
addr=0x0C80;
|
515
|
end =0x0F00;
|
516
|
}
|
517
|
|
518
|
for (; addr < end; addr += 8) {
|
519
|
write_block(fd, data+addr, addr, 8);
|
520
|
}
|
521
|
return TGUV2_MEMSIZE;
|
522
|
}
|
523
|
|
524
|
int read_channel(int fd, struct tguv2_config *config, u8 channel) {\
|
525
|
int addr, end;
|
526
|
u8 buf[12];
|
527
|
u8 *data;
|
528
|
|
529
|
if (radio_mode != PMODE_PROGRAM) {
|
530
|
printf("read_channel called with radio not in PROGRAM mode\n");
|
531
|
return;
|
532
|
}
|
533
|
|
534
|
// read the main info
|
535
|
data=(u8*)(&config->channels[channel]);
|
536
|
addr=data - (u8*)config;
|
537
|
read_block(fd, data, buf, addr, 8);
|
538
|
read_block(fd, data+8, buf, addr+8, 8);
|
539
|
|
540
|
// read the extra info (name)
|
541
|
data=(u8*)(&config->channels2[channel]);
|
542
|
addr=data - (u8*)config;
|
543
|
read_block(fd, data, buf, addr, 8);
|
544
|
read_block(fd, data+8, buf, addr+8, 8);
|
545
|
|
546
|
}
|
547
|
|
548
|
int write_channel(int fd, struct tguv2_config *config, u8 channel) {\
|
549
|
int addr, end;
|
550
|
u8 *data;
|
551
|
|
552
|
if (radio_mode != PMODE_PROGRAM) {
|
553
|
printf("write_channel called with radio not in PROGRAM mode\n");
|
554
|
return;
|
555
|
}
|
556
|
|
557
|
// read the main info
|
558
|
data=(u8*)(&config->channels[channel]);
|
559
|
addr=data - (u8*)config;
|
560
|
write_block(fd, data, addr, 8);
|
561
|
write_block(fd, data+8, addr+8, 8);
|
562
|
|
563
|
// read the extra info (name)
|
564
|
data=(u8*)(&config->channels2[channel]);
|
565
|
addr=data - (u8*)config;
|
566
|
write_block(fd, data, addr, 8);
|
567
|
write_block(fd, data+8, addr+8, 8);
|
568
|
|
569
|
}
|
570
|
|
571
|
void save_memory(char *fname, struct tguv2_config *data)
|
572
|
{
|
573
|
int fd=open(fname, O_CREAT | O_WRONLY, 0660);
|
574
|
write(fd, data, TGUV2_MEMSIZE);
|
575
|
close(fd);
|
576
|
}
|
577
|
|
578
|
void load_memory(char *fname, struct tguv2_config *data)
|
579
|
{
|
580
|
int fd=open(fname, O_RDONLY, 0660);
|
581
|
read(fd, data, TGUV2_MEMSIZE);
|
582
|
close(fd);
|
583
|
}
|
584
|
|
585
|
u32 bcdtoint(u32 bcd)
|
586
|
{
|
587
|
u32 i=0;
|
588
|
int j;
|
589
|
bcd = be32toh(bcd);
|
590
|
for (j=7; j>=0; j--) {
|
591
|
i+=(bcd >> (4*j)) & 0x0F;
|
592
|
i*=10;
|
593
|
}
|
594
|
return i;
|
595
|
}
|
596
|
|
597
|
const char charmap[]="0123456789ABCDEFGHIJKLMNOPWRSTUVWXYZ |* +-\0";
|
598
|
|
599
|
char *channel_name(u8 *enc, u8 len)
|
600
|
{
|
601
|
u8 i;
|
602
|
static char buf[40];
|
603
|
for (i=0; i<len; i++) {
|
604
|
if (enc[i] <= 43)
|
605
|
buf[i]=charmap[enc[i]];
|
606
|
else buf[i]='.';
|
607
|
}
|
608
|
buf[i++]=0;
|
609
|
return buf;
|
610
|
}
|
611
|
|
612
|
void name_channel(u8 *enc, char *name, u8 len)
|
613
|
{
|
614
|
int i,j;
|
615
|
for (i=0; i<len && *name; i++, name++) {
|
616
|
for (j=0; charmap[j] && charmap[j]!=*name; j++);
|
617
|
if (charmap[j]) *enc++=j;
|
618
|
}
|
619
|
}
|
620
|
|
621
|
void print_channel(struct tguv2_channel *chan)
|
622
|
{
|
623
|
u32 rxf, txf;
|
624
|
|
625
|
rxf = bcdtoint(chan->freq);
|
626
|
txf = rxf;
|
627
|
if (chan->flags1 & CF1_SHIFT_POS)
|
628
|
txf += bcdtoint(chan->txofs);
|
629
|
if (chan->flags1 & CF1_SHIFT_NEG)
|
630
|
txf -= bcdtoint(chan->txofs);
|
631
|
|
632
|
printf("%10.6f %10.6f %6.2f ",
|
633
|
rxf / 1000000.0,
|
634
|
txf / 1000000.0,
|
635
|
bandwidthmap[chan->bandwidth & 0x0F]);
|
636
|
|
637
|
printf("%-3s %-3s %-4s ",
|
638
|
(chan->flags2 & CF2_SCRAMBLE_ON ? "OFF" : "ON"),
|
639
|
(chan->flags2 & CF2_REVERSE_ON ? "OFF" : "ON"),
|
640
|
(chan->flags2 & CF2_WideFM ? "Wide" : "Narr"));
|
641
|
|
642
|
switch (chan->tx_power) {
|
643
|
case TXPOWER_LOW: printf("LOW"); break;
|
644
|
case TXPOWER_MID: printf("MID"); break;
|
645
|
case TXPOWER_HI : printf("HI "); break;
|
646
|
default: printf("???");
|
647
|
}
|
648
|
|
649
|
}
|
650
|
|
651
|
void list_channels(struct tguv2_config *conf)
|
652
|
{
|
653
|
int c;
|
654
|
|
655
|
printf(" CH RX TX Step Scr REV Wide Pow SCN BAND CH_Name\n");
|
656
|
for (c=0; c<TGUV2_CHANNELS; c++) {
|
657
|
if (conf->channels[c].freq != 0xFFFFFFFF) {
|
658
|
printf("%3d: ", c);
|
659
|
print_channel(&(conf->channels[c]));
|
660
|
|
661
|
printf(" %-3s %02X",
|
662
|
conf->cflags[c] & CFLAG_SCAN ? "Yes" : "No",
|
663
|
conf->bandid[conf->cflags[c] & CFLAG_BANDMASK]);
|
664
|
|
665
|
printf(" %s\n", channel_name(conf->channels2[c].name, 6));
|
666
|
|
667
|
}
|
668
|
}
|
669
|
|
670
|
}
|
671
|
|
672
|
|
673
|
void list_bands(struct tguv2_config *conf)
|
674
|
{
|
675
|
int b;
|
676
|
printf(" RX TX Step Scr REV Wide Pow\n");
|
677
|
for (b=0; b<TGUV2_BANDS; b++) {
|
678
|
if (conf->bands[b].freq != 0xFFFFFFFF) {
|
679
|
printf("Band %02X: ", conf->bandid[b]);
|
680
|
print_channel(&(conf->bands[b]));
|
681
|
printf("\n");
|
682
|
}
|
683
|
}
|
684
|
}
|
685
|
|
686
|
|
687
|
void list_config(struct tguv2_config *conf)
|
688
|
{
|
689
|
printf("Squelch : %d\n", conf->squelch);
|
690
|
printf("Time-Out-Timer : %d seconds\n", conf->time_out_timer*60);
|
691
|
printf("Priority Channel: ");
|
692
|
if (conf->priority_channel < 200) printf("%d\n", conf->priority_channel);
|
693
|
else printf("OFF\n");
|
694
|
printf("Busy Lockout : %s (%02x)\n", conf->busy_lockout==0x01 ? "OFF" : "ON", conf->busy_lockout);
|
695
|
printf("VOX : %d\n", conf->vox);
|
696
|
printf("Key Lock : %s\n", conf->keylock==0x01 ? "OFF" : "ON");
|
697
|
printf("Key Beep : %s\n", conf->keybeep==0x01 ? "OFF" : "ON");
|
698
|
printf("Freq Step : %1.2f (%02x)\n", bandwidthmap[conf->step & 0x0F], conf->step & 0x0F, conf->step);
|
699
|
printf("End Tone : %s\n", conf->end_tone ? "OFF" : "ON");
|
700
|
printf("VFO Model : %s (%02x)\n", conf->vfo_model==0x00 ? "No" : "Yes", conf->vfo_model);
|
701
|
|
702
|
printf("Mode : ");
|
703
|
switch (conf->mode) {
|
704
|
case 0x00: printf("Dual Watch"); break;
|
705
|
case 0x01: printf("Cross-Band"); break;
|
706
|
default: printf("Normal (Channel/VFO) (%02x)", conf->mode);
|
707
|
}
|
708
|
printf("\n");
|
709
|
|
710
|
printf("Channel Display : ");
|
711
|
switch (conf->display) {
|
712
|
case 0x00: printf("Ch/Frequency"); break;
|
713
|
case 0x01: printf("Channel"); break;
|
714
|
case 0x02: printf("Ch/Name"); break;
|
715
|
default: printf("??? (%02x)", conf->display);
|
716
|
}
|
717
|
printf("\n");
|
718
|
|
719
|
int v;
|
720
|
for (v=0; v<2; v++) {
|
721
|
if (conf->vfo[v].current < 200) {
|
722
|
printf("VFO%d : Channel %d\n", v, conf->vfo[v].chan);
|
723
|
}
|
724
|
else {
|
725
|
printf("VFO%d : Band %02X, Freq %10.6f\n",
|
726
|
v,
|
727
|
conf->bandid[conf->vfo[v].memno - 200],
|
728
|
bcdtoint(conf->bands[conf->vfo[v].memno - 200].freq)/1000000.0);
|
729
|
}
|
730
|
}
|
731
|
|
732
|
printf("Band Restrict : %s\n", conf->band_restrict == 0x01 ? "Enabled" : "Disabled");
|
733
|
printf("350-390 Mhz TX : %s\n", conf->txen350390 == 0x01 ? "Enabled" : "Disabled");
|
734
|
printf("test: %02x %02x\n", conf->band_restrict, conf->txen350390);
|
735
|
|
736
|
printf("Unknown Bytes : %02X %02X %02X %02X %02X\n",
|
737
|
conf->unk1, conf->unk2, conf->unk3, conf->unk4, conf->unk5);
|
738
|
|
739
|
printf("Reserved Bytes :\n1: ");
|
740
|
hexdump(conf->reserved1, sizeof(conf->reserved1));
|
741
|
printf("\n2: ");
|
742
|
hexdump(conf->reserved2, sizeof(conf->reserved2));
|
743
|
printf("\n3: ");
|
744
|
hexdump(conf->reserved3, sizeof(conf->reserved3));
|
745
|
printf("\n");
|
746
|
}
|
747
|
|
748
|
int openserial(char *port)
|
749
|
{
|
750
|
struct termios sp;
|
751
|
if (debug>1) printf("Opening %s\r\n", port);
|
752
|
|
753
|
// open non-blocking otherwise open can block until carrier appears
|
754
|
int fd = open(port, O_RDWR | O_NONBLOCK);
|
755
|
if (fd<0) return -ENODEV;
|
756
|
|
757
|
memset(&sp,0,sizeof(sp));
|
758
|
sp.c_iflag=0;
|
759
|
sp.c_oflag=0;
|
760
|
sp.c_cflag=CS8|CREAD|CLOCAL|CSTOPB;
|
761
|
sp.c_lflag=0;
|
762
|
sp.c_cc[VMIN]=100;
|
763
|
sp.c_cc[VTIME]=5;
|
764
|
cfsetispeed(&sp,RADIO_BAUD);
|
765
|
cfsetospeed(&sp,RADIO_BAUD);
|
766
|
|
767
|
tcsetattr(fd, TCSANOW, &sp);
|
768
|
if (debug>3) printf("tcflush()\r\n");
|
769
|
|
770
|
// now switch to blocking IO because that's what we want
|
771
|
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK);
|
772
|
|
773
|
tcflush(fd, TCIOFLUSH);
|
774
|
return fd;
|
775
|
}
|
776
|
|
777
|
|
778
|
void help()
|
779
|
{
|
780
|
printf("Quansheng TG-UV2 Radio control tool 0.1\n");
|
781
|
printf("Usage: radio <options>\n");
|
782
|
printf(" -r f read config from file f\n");
|
783
|
printf(" -w f write config to file f\n");
|
784
|
printf(" -g get config from radio\n");
|
785
|
printf(" -p put config to radio\n");
|
786
|
printf(" -l list current config\n");
|
787
|
printf(" -c list current channels\n");
|
788
|
printf("\n");
|
789
|
printf("To select a port other than /dev/ttyUSB0 specify the name as the first option\n");
|
790
|
|
791
|
printf("\n\n");
|
792
|
printf("Debugging Tools: (DO NOT USE)\n");
|
793
|
printf(" -s addr value - set config byte at addr to value\n");
|
794
|
printf(" -f addr value len - fill len bytes from addr with value\n");
|
795
|
printf(" -e len - expect len byte reply to test command\n");
|
796
|
printf(" -T <data> - Test command in program mode\n");
|
797
|
printf(" -t <data> - Test command.\n");
|
798
|
printf(" -F <data> - Test command in firmware mode ** DANGEROUS **\n");
|
799
|
printf(" <data> consists of a length byte, followed by length characters\n");
|
800
|
printf(" or numeric values (escape digits with \\)\n");
|
801
|
printf(" -D addr len - dump a chunk of memory\n");
|
802
|
}
|
803
|
|
804
|
|
805
|
int init_radio(int *serial, char *port, u8 *model, int pmode) {
|
806
|
if (*serial<0) {
|
807
|
*serial=openserial(port);
|
808
|
if (*serial < 0) {
|
809
|
perror("Error opening port");
|
810
|
return 0;
|
811
|
}
|
812
|
RX_on(*serial);
|
813
|
|
814
|
if (start_pgm_mode(*serial, pmode)==0 && read_model(*serial, model)==0) {
|
815
|
return 1;
|
816
|
}
|
817
|
close(*serial);
|
818
|
*serial=-1;
|
819
|
return 0;
|
820
|
}
|
821
|
if (radio_mode != pmode) {
|
822
|
printf("Can't change programming mode during a run.\n");
|
823
|
printf("You can not mix and match firmware updates and programming updates\n");
|
824
|
return 0;
|
825
|
}
|
826
|
return 1;
|
827
|
}
|
828
|
|
829
|
main (int argc, char *argv[])
|
830
|
{
|
831
|
struct timeval tv;
|
832
|
fd_set rfds, tfds;
|
833
|
int retval;
|
834
|
int quit=0;
|
835
|
int i;
|
836
|
int setaddr=-1;
|
837
|
int setvalue=0x00;
|
838
|
int setlen=0;
|
839
|
|
840
|
unsigned char cmd_buf[20];
|
841
|
int cmd_len=0;
|
842
|
int cmd_expect=8;
|
843
|
|
844
|
char *port="/dev/ttyUSB0";
|
845
|
int serial = -1;
|
846
|
|
847
|
u8 model[20];
|
848
|
int config_valid=0, channels_valid=0;
|
849
|
struct tguv2_config *config;
|
850
|
|
851
|
if (sizeof(*config) > TGUV2_MEMSIZE) {
|
852
|
printf("Program Error: tguv2_config structure is too big for device memory!\n");
|
853
|
printf(" sizeof(config) = %d, TGUV2_MEMSIZE=%d\n", sizeof(*config), TGUV2_MEMSIZE);
|
854
|
exit(0);
|
855
|
}
|
856
|
|
857
|
config = malloc(TGUV2_MEMSIZE);
|
858
|
|
859
|
i=1;
|
860
|
while (i<argc) {
|
861
|
if (strncmp(argv[i],"/dev/",5)==0) {
|
862
|
port=argv[i];
|
863
|
}
|
864
|
else if (strcmp(argv[i],"-h")==0) {
|
865
|
help();
|
866
|
quit=1;
|
867
|
}
|
868
|
else if (strcmp(argv[i],"-q")==0) {
|
869
|
quit=1;
|
870
|
}
|
871
|
else if (strcmp(argv[i],"-r")==0) { // read config from file
|
872
|
i++;
|
873
|
load_memory(argv[i], config);
|
874
|
config_valid=1;
|
875
|
channels_valid=1;
|
876
|
}
|
877
|
else if (strcmp(argv[i],"-w")==0) { // write config to file
|
878
|
i++;
|
879
|
if (!config_valid && channels_valid) {
|
880
|
printf("Write config to file: load a config with -r or -g first\n");
|
881
|
}
|
882
|
else {
|
883
|
save_memory(argv[i], config);
|
884
|
}
|
885
|
}
|
886
|
else if (strcmp(argv[i],"-g")==0) { // get config from radio
|
887
|
if (init_radio(&serial, port, model, PMODE_PROGRAM)) {
|
888
|
read_memory(serial, config, 0);
|
889
|
config_valid=1;
|
890
|
channels_valid=1;
|
891
|
}
|
892
|
}
|
893
|
else if (strcmp(argv[i],"-p")==0) { // put config to radio
|
894
|
if (!config_valid && channels_valid) {
|
895
|
printf("Put Config: load a config with -r or -g first\n");
|
896
|
}
|
897
|
else if (init_radio(&serial, port, model, PMODE_PROGRAM)) {
|
898
|
write_memory(serial, config, 0);
|
899
|
}
|
900
|
}
|
901
|
else if (strcmp(argv[i], "-c")==0) { // list channels in loaded config
|
902
|
if (config_valid) {
|
903
|
list_channels(config);
|
904
|
}
|
905
|
else {
|
906
|
printf("List Channels: load a config with -r or -g first\n");
|
907
|
}
|
908
|
}
|
909
|
else if (strcmp(argv[i], "-l")==0) { // list loaded config
|
910
|
if (config_valid) {
|
911
|
list_bands(config);
|
912
|
list_config(config);
|
913
|
}
|
914
|
else {
|
915
|
printf("List Config: load a config with -r or -g first\n");
|
916
|
}
|
917
|
}
|
918
|
else if (strcmp(argv[i],"-s")==0) { // set config bytes
|
919
|
i++;
|
920
|
setaddr=strtol(argv[i], NULL, 0);
|
921
|
i++;
|
922
|
setvalue=strtol(argv[i], NULL, 0);
|
923
|
setlen=1;
|
924
|
|
925
|
if (setlen>0 && init_radio(&serial, port, model, PMODE_PROGRAM)) {
|
926
|
if (!config_valid) {
|
927
|
read_memory(serial, config, 1); // only read the config area
|
928
|
config_valid=1;
|
929
|
}
|
930
|
|
931
|
memset(((void *)config + setaddr), setvalue & 0xFF, setlen & 0xFF);
|
932
|
|
933
|
write_memory(serial, config, 1); // only write the config area
|
934
|
}
|
935
|
}
|
936
|
else if (strcmp(argv[i],"-f")==0) { // fill config bytes
|
937
|
i++;
|
938
|
setaddr=strtol(argv[i], NULL, 0);
|
939
|
i++;
|
940
|
setvalue=strtol(argv[i], NULL, 0);
|
941
|
i++;
|
942
|
setlen=strtol(argv[i], NULL, 0);
|
943
|
|
944
|
if (setlen>0 && init_radio(&serial, port, model, PMODE_PROGRAM)) {
|
945
|
if (!config_valid) {
|
946
|
read_memory(serial, config, 1); // only read the config area
|
947
|
config_valid=1;
|
948
|
}
|
949
|
|
950
|
memset(((void *)config + setaddr), setvalue & 0xFF, setlen & 0xFF);
|
951
|
|
952
|
write_memory(serial, config, 1); // only write the config area
|
953
|
}
|
954
|
}
|
955
|
else if (strcmp(argv[i],"-D")==0) { // dump a section of memory
|
956
|
i++;
|
957
|
setaddr=strtol(argv[i], NULL, 0);
|
958
|
i++;
|
959
|
setlen=strtol(argv[i], NULL, 0);
|
960
|
if (setlen>0 && init_radio(&serial, port, model, radio_mode == PMODE_NONE ? PMODE_PROGRAM : radio_mode)) {
|
961
|
char *data=malloc(setlen + 16);
|
962
|
char buf[12];
|
963
|
int p=0;
|
964
|
|
965
|
// read_block = 16bit address, read_block2 = 32bit address
|
966
|
while (p<setlen) {
|
967
|
if (read_block(serial, data+p, buf, setaddr+p, 8) ||
|
968
|
read_block(serial, data+p+8, buf, setaddr+p+8, 8)
|
969
|
) {
|
970
|
hexdump(data+p, 16);
|
971
|
printf("\n");
|
972
|
p+=16;
|
973
|
}
|
974
|
else {
|
975
|
p=setlen;
|
976
|
}
|
977
|
}
|
978
|
free(data);
|
979
|
}
|
980
|
}
|
981
|
else if (strcmp(argv[i],"-e")==0) { // set expected response
|
982
|
i++;
|
983
|
cmd_expect=strtol(argv[i],NULL,0);
|
984
|
}
|
985
|
else if ((strcmp(argv[i],"-t")==0) ||
|
986
|
(strcmp(argv[i],"-T")==0) ||
|
987
|
(strcmp(argv[i],"-F")==0)
|
988
|
)
|
989
|
{ // test commands
|
990
|
int from_program_mode=0;
|
991
|
int j;
|
992
|
char *ep;
|
993
|
if (strcmp(argv[i],"-T")==0) from_program_mode=PMODE_PROGRAM;
|
994
|
if (strcmp(argv[i],"-F")==0) from_program_mode=PMODE_FIRMWARE;
|
995
|
|
996
|
i++;
|
997
|
cmd_len=strtol(argv[i],NULL,0);
|
998
|
if (cmd_len) {
|
999
|
printf("%d byte command\n", cmd_len);
|
1000
|
for (j=0; j<cmd_len; j++) {
|
1001
|
i++;
|
1002
|
ep=NULL;
|
1003
|
cmd_buf[j] = strtol(argv[i], &ep, 0);
|
1004
|
if (ep && !*ep) {
|
1005
|
printf(" 0x%02x", cmd_buf[j]);
|
1006
|
}
|
1007
|
else {
|
1008
|
if (*argv[i] == '\\')
|
1009
|
cmd_buf[j]=argv[i][1];
|
1010
|
else cmd_buf[j]=argv[i][0];
|
1011
|
printf(" %c (0x%02x)", cmd_buf[j], cmd_buf[j]);
|
1012
|
}
|
1013
|
}
|
1014
|
printf("\n");
|
1015
|
}
|
1016
|
else {
|
1017
|
i++;
|
1018
|
printf("string command: '%s'\n", argv[i]);
|
1019
|
strcpy(cmd_buf, argv[i]);
|
1020
|
cmd_len=strlen(argv[i]);
|
1021
|
}
|
1022
|
|
1023
|
if (cmd_len) {
|
1024
|
if (from_program_mode) {
|
1025
|
if (init_radio(&serial, port, model, from_program_mode)) {
|
1026
|
test_cmd(serial, cmd_buf, cmd_len, cmd_expect);
|
1027
|
}
|
1028
|
else {
|
1029
|
printf("Unable to enter program mode\n");
|
1030
|
}
|
1031
|
}
|
1032
|
else {
|
1033
|
if (serial<0) {
|
1034
|
serial=openserial(port);
|
1035
|
if (serial < 0) {
|
1036
|
perror("Error opening port");
|
1037
|
}
|
1038
|
RX_on(serial);
|
1039
|
}
|
1040
|
test_cmd(serial, cmd_buf, cmd_len, cmd_expect);
|
1041
|
}
|
1042
|
}
|
1043
|
}
|
1044
|
i++;
|
1045
|
}
|
1046
|
|
1047
|
#if 0
|
1048
|
// update a couple of channels - if you want to do more than a few
|
1049
|
// it's probably easier to read/write the whole memory (but slower).
|
1050
|
read_channel(serial, config, 1);
|
1051
|
read_channel(serial, config, 2);
|
1052
|
config->channels[1].tx_power=TXPOWER_HI;
|
1053
|
config->channels[2].tx_power=TXPOWER_HI;
|
1054
|
write_channel(serial, config, 1);
|
1055
|
write_channel(serial, config, 2);
|
1056
|
#endif
|
1057
|
|
1058
|
if (serial>0) {
|
1059
|
tcdrain(serial);
|
1060
|
usleep(200000);
|
1061
|
close(serial);
|
1062
|
serial=-1;
|
1063
|
}
|
1064
|
}
|