Decode channels.

This commit is contained in:
Serge Vakulenko 2018-08-21 22:04:45 -07:00
parent abebad7dbd
commit 6118b0519b
3 changed files with 229 additions and 230 deletions

18
main.c
View File

@ -62,7 +62,6 @@ void usage()
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
int write_flag = 0, config_flag = 0; int write_flag = 0, config_flag = 0;
const char *type = 0;
// Set locale and message catalogs. // Set locale and message catalogs.
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
@ -78,11 +77,10 @@ int main(int argc, char **argv)
copyright = _("Copyright (C) 2018 Serge Vakulenko KK6ABQ"); copyright = _("Copyright (C) 2018 Serge Vakulenko KK6ABQ");
serial_verbose = 0; serial_verbose = 0;
for (;;) { for (;;) {
switch (getopt(argc, argv, "vcwt:")) { switch (getopt(argc, argv, "vcw")) {
case 'v': ++serial_verbose; continue; case 'v': ++serial_verbose; continue;
case 'w': ++write_flag; continue; case 'w': ++write_flag; continue;
case 'c': ++config_flag; continue; case 'c': ++config_flag; continue;
case 't': type = optarg; continue;
default: default:
usage(); usage();
case EOF: case EOF:
@ -101,10 +99,10 @@ int main(int argc, char **argv)
if (write_flag) { if (write_flag) {
// Restore image file to device. // Restore image file to device.
if (argc != 2 || !type) if (argc != 2)
usage(); usage();
radio_connect(argv[0], type); radio_connect();
radio_read_image(argv[1]); radio_read_image(argv[1]);
radio_print_version(stdout); radio_print_version(stdout);
radio_upload(0); radio_upload(0);
@ -122,11 +120,8 @@ int main(int argc, char **argv)
radio_save_image("device.img"); radio_save_image("device.img");
} else { } else {
if (!type)
usage();
// Update device from text config file. // Update device from text config file.
radio_connect(argv[0], type); radio_connect();
radio_download(); radio_download();
radio_print_version(stdout); radio_print_version(stdout);
radio_save_image("backup.img"); radio_save_image("backup.img");
@ -147,11 +142,8 @@ int main(int argc, char **argv)
radio_print_config(stdout, ! isatty(1)); radio_print_config(stdout, ! isatty(1));
} else { } else {
if (!type)
usage();
// Dump device to image file. // Dump device to image file.
radio_connect(argv[0], type); radio_connect();
radio_download(); radio_download();
radio_print_version(stdout); radio_print_version(stdout);
radio_disconnect(); radio_disconnect();

View File

@ -30,7 +30,7 @@
// Connect to the radio via the serial port. // Connect to the radio via the serial port.
// Identify the type of device. // Identify the type of device.
// //
void radio_connect(const char *port_name, const char *type); void radio_connect(void);
// //
// Close the serial port. // Close the serial port.

437
uv380.c
View File

@ -35,84 +35,12 @@
#define NCHAN 1000 #define NCHAN 1000
#define NZONES 10 #define NZONES 10
#define NPMS 50
#define MEMSZ 0x6fc8
#define OFFSET_VFO 0x0048 #define MEMSZ 0xd0000
#define OFFSET_HOME 0x01c8 #define OFFSET_CHANNELS 0x40000
#define OFFSET_CHANNELS 0x0248 #define OFFSET_ZONES 0x149e0
#define OFFSET_PMS 0x40c8
#define OFFSET_NAMES 0x4708
#define OFFSET_ZONES 0x69c8
#define OFFSET_SCAN 0x6ec8
static const char CHARSET[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ !`o$%&'()*+,-./|;/=>?@[~]^__"; static const char *POWER_NAME[] = { "Low", "High" };
#define NCHARS 65
#define SPACE 36
#define OPENBOX 64
static const char *POWER_NAME[] = { "High", "Med", "Low", "??" };
static const char *SCAN_NAME[] = { "+", "-", "Only", "??" };
enum {
STEP_5 = 0,
STEP_10,
STEP_12_5,
STEP_15,
STEP_20,
STEP_25,
STEP_50,
STEP_100,
};
//
// Data structure for a memory channel.
//
typedef struct {
uint8_t duplex : 4, // Repeater mode
#define D_SIMPLEX 0
#define D_NEG_OFFSET 2
#define D_POS_OFFSET 3
#define D_CROSS_BAND 4
isam : 1, // Amplitude modulation
isnarrow : 1, // Narrow FM modulation
_u1 : 1,
used : 1; // Channel is used
uint8_t rxfreq [3]; // Receive frequency
uint8_t tmode : 3, // CTCSS/DCS mode
#define T_OFF 0
#define T_TONE 1
#define T_TSQL 2
#define T_TSQL_REV 3
#define T_DTCS 4
#define T_D 5
#define T_T_DCS 6
#define T_D_TSQL 7
step : 3, // Frequency step
_u2 : 2;
uint8_t txfreq [3]; // Transmit frequency when cross-band
uint8_t tone : 6, // CTCSS tone select
#define TONE_DEFAULT 12
power : 2; // Transmit power level
uint8_t dtcs : 7, // DCS code select
_u3 : 1;
uint8_t _u4 [2];
uint8_t offset; // TX offset, in 50kHz steps
uint8_t _u5 [3];
} memory_channel_t;
//
// Data structure for a channel name.
//
typedef struct {
uint8_t name[6];
uint8_t _u1 : 7,
used : 1;
uint8_t _u2 : 7,
valid : 1;
} memory_name_t;
// //
// Print a generic information about the device. // Print a generic information about the device.
@ -257,37 +185,7 @@ static int uv380_is_compatible()
return strncmp("AH017$", (char*)&radio_mem[0], 6) == 0; return strncmp("AH017$", (char*)&radio_mem[0], 6) == 0;
} }
// #if 0
// Convert a 3-byte frequency value from binary coded decimal
// to integer format (in Hertz).
//
static int freq_to_hz(uint8_t *bcd)
{
int hz;
hz = (bcd[0] & 15) * 100000000 +
((bcd[1] >> 4) & 15) * 10000000 +
(bcd[1] & 15) * 1000000 +
((bcd[2] >> 4) & 15) * 100000 +
(bcd[2] & 15) * 10000;
hz += (bcd[0] >> 6) * 2500;
return hz;
}
//
// Convert an integet frequency value (in Hertz)
// to a 3-byte binary coded decimal format.
//
static void hz_to_freq(int hz, uint8_t *bcd)
{
bcd[0] = (hz / 2500 % 4) << 6 |
(hz / 100000000 % 10);
bcd[1] = (hz / 10000000 % 10) << 4 |
(hz / 1000000 % 10);
bcd[2] = (hz / 100000 % 10) << 4 |
(hz / 10000 % 10);
}
// //
// Is this zone non-empty? // Is this zone non-empty?
// //
@ -338,6 +236,7 @@ static void print_zone(FILE *out, int i)
fprintf(out, "-%d", last); fprintf(out, "-%d", last);
fprintf(out, "\n"); fprintf(out, "\n");
} }
#endif
// //
// Set the bitmask of zones for a given channel. // Set the bitmask of zones for a given channel.
@ -351,123 +250,196 @@ static void setup_zone(int zone_index, int chan_index)
} }
// //
// Extract channel name. // Data structure for a channel.
// //
static void decode_name(int i, char *name) typedef struct {
uint8_t lone_worker; // 1 bit
uint8_t squelch; // 1 bit
uint8_t autoscan; // 1 bit
uint8_t bandwidth; // 1 bit
uint8_t channel_mode; // 2 bits
uint8_t colorcode; // 4 bits
uint8_t repeater_slot; // 2 bits
uint8_t rx_only; // 1 bit
uint8_t allow_talkaround; // 1 bit - disabled
uint8_t data_call_conf; // 1 bit
uint8_t private_call_conf; // 1 bit
uint8_t privacy; // 2 bits
uint8_t privacy_no; // 4 bits
uint8_t display_pttid; // 1 bit
uint8_t compressed_udp_hdr; // 1 bit
uint8_t emergency_alarm_ack; // 1 bit
uint8_t rx_ref_frequency; // 2 bits
uint8_t admit_criteria; // 2 bits
uint8_t power; // 1 bit
uint8_t vox; // 1 bit
uint8_t qt_reverse; // 1 bit
uint8_t reverse_burst; // 1 bit
uint8_t tx_ref_frequency; // 2 bits
uint16_t contact_name_index; // 16 bits
uint8_t tot; // 6 bits
uint8_t tot_rekey_delay; // 8 bits
uint8_t emergency_system; // 6 bits
uint8_t scan_list_index; // 8 bits
uint8_t group_list_index; // 8 bits
uint8_t decode_18; // 8 bits
uint32_t rx_frequency; // 32 bits
uint32_t tx_frequency; // 32 bits
uint16_t ctcss_dcs_decode; // 16 bits
uint16_t ctcss_dcs_encode; // 16 bits
uint8_t tx_signaling_syst; // 3 bits
uint8_t rx_signaling_syst; // 3 bits
uint16_t name [17];
} channel_t;
//
// Read nbits from source buffer with given bit offset.
//
static unsigned decode_bits(const unsigned char *source, unsigned offset, unsigned nbits)
{ {
memory_name_t *nm = i + (memory_name_t*) &radio_mem[OFFSET_NAMES]; unsigned i, result = 0;
if (nm->valid && nm->used) { for (i=0; i<nbits; i++) {
int n, c; unsigned mask = 1 << (7 - (offset & 7));
for (n=0; n<6; n++) {
c = nm->name[n];
name[n] = (c < NCHARS) ? CHARSET[c] : ' ';
// Replace spaces by underscore. if (source[offset >> 3] & mask)
if (name[n] == ' ') result |= 1 << (nbits - i - 1);
name[n] = '_'; offset++;
} }
// Strip trailing spaces. return result;
for (n=5; n>=0 && name[n]=='_'; n--) }
name[n] = 0;
name[6] = 0; //
// Read nbytes from source buffer with given byte offset.
//
static unsigned decode_bytes(const unsigned char *source, unsigned offset, unsigned nbytes)
{
unsigned i, result = 0;
for (i=nbytes; i>0; i--) {
result <<= 8;
result |= source[i + offset - 1];
}
return result;
}
//
// Read BCD value of nbytes from source buffer with given byte offset.
//
static unsigned decode_bcd(const unsigned char *source, unsigned offset, unsigned nbytes)
{
unsigned i, result = 0;
for (i=nbytes; i>0; i--) {
int b = source[i + offset - 1];
int a = b >> 4;
b &= 0xf;
if (a > 9 || b > 9)
return 0;
result *= 100;
result += a*10 + b;
}
return result;
}
//
// Decode CTCSS/DCS tones from source buffer with given byte offset.
//
static unsigned decode_tones(const unsigned char *source, unsigned offset)
{
unsigned char ch[2];
unsigned hi, lo;
ch[0] = source[offset];
ch[1] = source[offset + 1];
hi = (ch[1] & 0xc0) << 10;
ch[1] &= ~0xc0;
lo = decode_bcd(ch, 0, 16/8);
if (lo == 0)
return 0;
return hi | lo;
}
//
// Read unicode text.
//
static void decode_text(const unsigned char *source, unsigned offset, uint16_t *target, unsigned nbytes)
{
unsigned i;
for (i=0; i<nbytes; i++) {
target[i] = source[offset] | (source[offset+1] << 8);
offset += 2;
} }
} }
// //
// Encode a character from ASCII to internal index. // Encode utf16 text to utf8.
// Replace underscores by spaces. // Return a pointer to a static buffer.
// Make all letters uppercase.
// //
static int encode_char(int c) static char *utf8(const uint16_t *text, unsigned nchars)
{ {
int i; static char buf[256];
unsigned i;
// Replace underscore by space. for (i=0; i<nchars; i++) {
if (c == '_') //TODO: convert to utf8
c = ' '; buf[i] = text[i];
if (c >= 'a' && c <= 'z')
c += 'A' - 'a';
for (i=0; i<NCHARS; i++)
if (c == CHARSET[i])
return i;
return OPENBOX;
}
//
// Set a name for the channel.
//
static void encode_name(int i, char *name)
{
memory_name_t *nm = i + (memory_name_t*) &radio_mem[OFFSET_NAMES];
int n;
if (name && *name && *name != '-') {
// Setup channel name.
nm->valid = 1;
nm->used = 1;
for (n=0; n<6 && name[n]; n++) {
nm->name[n] = encode_char(name[n]);
}
for (; n<6; n++)
nm->name[n] = SPACE;
} else {
// Clear name.
nm->valid = 0;
nm->used = 0;
for (n=0; n<6; n++)
nm->name[n] = 0xff;
} }
buf[i] = 0;
return buf;
} }
// //
// Get all parameters for a given channel. // Get all parameters for a given channel.
// Seek selects the type of channel:
// OFFSET_VFO - VFO channel, 0..4
// OFFSET_HOME - home channel, 0..4
// OFFSET_CHANNELS - memory channel, 0..999
// OFFSET_PMS - programmable memory scan, i=0..99
// //
static void decode_channel(int i, int seek, char *name, static void decode_channel(int i, channel_t *ch)
int *rx_hz, int *tx_hz, int *power, int *wide,
int *scan, int *isam, int *step)
{ {
memory_channel_t *ch = i + (memory_channel_t*) &radio_mem[seek]; unsigned char *buf = &radio_mem[OFFSET_CHANNELS + i*64];
int scan_data = radio_mem[OFFSET_SCAN + i/4];
*rx_hz = *tx_hz = 0; memset(ch, 0, sizeof(*ch));
*power = *wide = *scan = *isam = *step = 0; ch->lone_worker = decode_bits(buf, 0, 1);
if (name) ch->squelch = decode_bits(buf, 2, 1);
*name = 0; ch->autoscan = decode_bits(buf, 3, 1);
if (! ch->used && (seek == OFFSET_CHANNELS || seek == OFFSET_PMS)) ch->bandwidth = decode_bits(buf, 4, 1);
return; ch->channel_mode = decode_bits(buf, 6, 2);
ch->colorcode = decode_bits(buf, 8, 4);
// Extract channel name. ch->repeater_slot = decode_bits(buf, 12, 2);
if (name && seek == OFFSET_CHANNELS) ch->rx_only = decode_bits(buf, 14, 1);
decode_name(i, name); ch->allow_talkaround = decode_bits(buf, 15, 1);
ch->data_call_conf = decode_bits(buf, 16, 1);
// Decode channel frequencies. ch->private_call_conf = decode_bits(buf, 17, 1);
*rx_hz = freq_to_hz(ch->rxfreq); ch->privacy = decode_bits(buf, 18, 2);
ch->privacy_no = decode_bits(buf, 20, 4);
*tx_hz = *rx_hz; ch->display_pttid = decode_bits(buf, 24, 1);
switch (ch->duplex) { ch->compressed_udp_hdr = decode_bits(buf, 25, 1);
case D_NEG_OFFSET: ch->emergency_alarm_ack = decode_bits(buf, 28, 1);
*tx_hz -= ch->offset * 50000; ch->rx_ref_frequency = decode_bits(buf, 30, 2);
break; ch->admit_criteria = decode_bits(buf, 32, 2);
case D_POS_OFFSET: ch->power = decode_bits(buf, 34, 1);
*tx_hz += ch->offset * 50000; ch->vox = decode_bits(buf, 35, 1);
break; ch->qt_reverse = decode_bits(buf, 36, 1);
case D_CROSS_BAND: ch->reverse_burst = decode_bits(buf, 37, 1);
*tx_hz = freq_to_hz(ch->txfreq); ch->tx_ref_frequency = decode_bits(buf, 38, 2);
break; ch->contact_name_index = decode_bytes(buf, 48/8, 16/8);
} ch->tot = decode_bits(buf, 66, 6);
ch->tot_rekey_delay = decode_bits(buf, 72, 8);
// Other parameters. ch->emergency_system = decode_bits(buf, 82, 6);
*power = ch->power; ch->scan_list_index = decode_bits(buf, 88, 8);
*wide = ! ch->isnarrow; ch->group_list_index = decode_bits(buf, 96, 8);
*scan = (scan_data << ((i & 3) * 2) >> 6) & 3; ch->decode_18 = decode_bits(buf, 112, 8);
*isam = ch->isam; ch->rx_frequency = decode_bcd(buf, 128/8, 32/8);
*step = ch->step; ch->tx_frequency = decode_bcd(buf, 160/8, 32/8);
ch->ctcss_dcs_decode = decode_tones(buf, 192/8);
ch->ctcss_dcs_encode = decode_tones(buf, 208/8);
ch->tx_signaling_syst = decode_bits(buf, 237, 3);
ch->rx_signaling_syst = decode_bits(buf, 229, 3);
decode_text(buf, 256, ch->name, 256);
} }
// //
@ -476,6 +448,7 @@ static void decode_channel(int i, int seek, char *name,
static void setup_channel(int i, char *name, double rx_mhz, double tx_mhz, static void setup_channel(int i, char *name, double rx_mhz, double tx_mhz,
int tmode, int power, int wide, int scan, int isam) int tmode, int power, int wide, int scan, int isam)
{ {
#if 0
memory_channel_t *ch = i + (memory_channel_t*) &radio_mem[OFFSET_CHANNELS]; memory_channel_t *ch = i + (memory_channel_t*) &radio_mem[OFFSET_CHANNELS];
hz_to_freq((int) (rx_mhz * 1000000.0), ch->rxfreq); hz_to_freq((int) (rx_mhz * 1000000.0), ch->rxfreq);
@ -515,6 +488,7 @@ static void setup_channel(int i, char *name, double rx_mhz, double tx_mhz,
*scan_data |= scan << scan_shift; *scan_data |= scan << scan_shift;
encode_name(i, name); encode_name(i, name);
#endif
} }
// //
@ -569,24 +543,56 @@ static void uv380_print_config(FILE *out, int verbose)
fprintf(out, "# 9) Scan mode: +, -, Only\n"); fprintf(out, "# 9) Scan mode: +, -, Only\n");
fprintf(out, "#\n"); fprintf(out, "#\n");
} }
fprintf(out, "Channel Name Receive Transmit Power Modulation Scan\n"); fprintf(out, "Channel Name Receive Transmit Power Width Scan\n");
for (i=0; i<NCHAN; i++) { for (i=0; i<NCHAN; i++) {
int rx_hz, tx_hz, power, wide, scan, isam, step; channel_t ch;
char name[17];
decode_channel(i, OFFSET_CHANNELS, name, &rx_hz, &tx_hz, &power, &wide, &scan, &isam, &step); decode_channel(i, &ch);
if (rx_hz == 0) { if (ch.rx_frequency == 0) {
// Channel is disabled // Channel is disabled
continue; continue;
} }
fprintf(out, "%5d %-7s %8.4f ", i+1, name[0] ? name : "-", rx_hz / 1000000.0); fprintf(out, "%5d %-7s %8.4f ", i+1, utf8(ch.name, 16), ch.rx_frequency / 100000.0);
print_offset(out, rx_hz, tx_hz); print_offset(out, ch.rx_frequency, ch.tx_frequency);
fprintf(out, " %-4s %-10s %s\n", POWER_NAME[power], fprintf(out, " %-4s %-6s %d\n", POWER_NAME[ch.power],
isam ? "AM" : wide ? "Wide" : "Narrow", SCAN_NAME[scan]); ch.bandwidth ? "Wide" : "Normal", ch.scan_list_index);
#if 0
ch.lone_worker 1
ch.squelch 1
ch.autoscan 1
ch.channel_mode 2
ch.colorcode 4
ch.repeater_slot 2
ch.rx_only 1
ch.allow_talkaround 1
ch.data_call_conf 1
ch.private_call_conf 1
ch.privacy 2
ch.privacy_no 4
ch.display_pttid 1
ch.compressed_udp_hdr 1
ch.emergency_alarm_ack 1
ch.rx_ref_frequency 2
ch.admit_criteria 2
ch.vox 1
ch.qt_reverse 1
ch.reverse_burst 1
ch.tx_ref_frequency 2
ch.contact_name_index 16/8
ch.tot 6
ch.tot_rekey_delay 8
ch.emergency_system 6
ch.group_list_index 8
ch.decode_18 8
ch.ctcss_dcs_decode
ch.ctcss_dcs_encode
ch.tx_signaling_syst 3
ch.rx_signaling_syst 3
#endif
} }
#if 0
// //
// Zones. // Zones.
// //
@ -602,6 +608,7 @@ static void uv380_print_config(FILE *out, int verbose)
if (have_zone(i)) if (have_zone(i))
print_zone(out, i); print_zone(out, i);
} }
#endif
} }
// //
@ -731,7 +738,7 @@ badtx: fprintf(stderr, "Bad transmit frequency.\n");
// On first entry, erase the channel table. // On first entry, erase the channel table.
int i; int i;
for (i=0; i<NCHAN; i++) { for (i=0; i<NCHAN; i++) {
setup_channel(i, 0, 0, 0, 0, TONE_DEFAULT, 1, 0, 0); setup_channel(i, 0, 0, 0, 0, 12, 1, 0, 0);
} }
} }