Here’s the code for JT65 with OLED display and text input via a USB connected terminal program, such as MacOS iSerialTerm or the Arduino IDE Serial Monitor window.
Code
// JT65_ADS_TEXT_OLED // V2.5 9-4-17 serial input/output, update to U8g2lib // Code based on Feld Hell beacon for Arduino by K6HX // Timer setup code by LA3PNA. // TX 7060000, Dial 7074830 // AD9850 // W_CLK 8 // FQ_UD 9 // DATA 10 // RESET 11 // OLED 128x64 // SDA = A4 // SCL = A5 // RTC I2C bus // SDA = A4 // SCL = A5 #include "ADS9850.h" #include "U8g2lib.h" #include "Wire.h" // RTC address #define RTCADDR 0x68 // Stuff specific to the general (integer) version of the Reed-Solomon codecs #define MODNN(x) modnn(rs,x) #define MM (rs->mm) #define NN (rs->nn) #define ALPHA_TO (rs->alpha_to) #define INDEX_OF (rs->index_of) #define GENPOLY (rs->genpoly) #define NROOTS (rs->nroots) #define FCR (rs->fcr) #define PRIM (rs->prim) #define IPRIM (rs->iprim) #define PAD (rs->pad) #define A0 (NN) #define TONE_SPACING 269 // ~2.6917 Hz #define SUBMODE_A 5812 // CTC value for JT65A #define SYMBOL_COUNT 126 // AD9850 pins #define W_CLK 8 #define FQ_UD 9 #define DATA 10 #define RESET 11 typedef unsigned int data_t; /* Reed-Solomon codec control block */ struct rs { int mm; /* Bits per symbol */ int nn; /* Symbols per block (= (1<<mm)-1) */ data_t *alpha_to; /* log lookup table */ data_t *index_of; /* Antilog lookup table */ data_t *genpoly; /* Generator polynomial */ int nroots; /* Number of generator roots = number of parity symbols */ int fcr; /* First consecutive root, index form */ int prim; /* Primitive element, index form */ int iprim; /* prim-th root of 1, index form */ int pad; /* Padding bytes in shortened block */ }; // RTC time byte sec, mns, hrs; // trigger TX (sec) uint8_t trigger; bool tx; // Frequency variables double freqHz = 7076000; // nominal dial is 1270Hz lower double freqChz = 0; uint8_t phase = 0; // message to send char message[20] = ""; // encode variables uint8_t tx_buffer[SYMBOL_COUNT]; static void *rs; // timer flag volatile bool proceed = false; // AD9850 object ADS9850 ads; // oled object U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE, SCL, SDA); void setup() { Serial.begin(9600); // init I2C Wire.begin(); // oled init, sets I2C addr to 0x3C oled.begin(); // init AD9850, init freq and off to start ads.begin(W_CLK, FQ_UD, DATA, RESET); ads.setFreq(freqHz, freqChz, phase); ads.down(); // Set up Timer1 for interrupts every symbol period. noInterrupts(); // Turn off interrupts. TCCR1A = 0; // Set entire TCCR1A register to 0; disconnects // interrupt output pins, sets normal waveform // mode. We're just using Timer1 as a counter. TCNT1 = 0; // Initialize counter value to 0. TCCR1B = (1 << CS12) | // Set CS12 and CS10 bit to set prescale (1 << CS10) | // to /1024 (1 << WGM12); // turn on CTC // which gives, 64us ticks TIMSK1 = (1 << OCIE1A); // Enable timer compare interrupt. OCR1A = SUBMODE_A; // Set up interrupt trigger count; interrupts(); // Re-enable interrupts. trigger = 1; // trigger on each minute tx = false; // Initialize the Reed-Solomon encoder rs = (struct rs *)(intptr_t)init_rs_int(6, 0x43, 3, 1, 51, 0); } void loop() { // get time getRTC(); dispUpdate(); // message? if (getMsg(message) == true) { // echo message Serial.println(message); // display message & time, trigger time? do { getRTC(); dispUpdate(); } while (mns % trigger != 0 || sec != 0); // send tx = true; // indicate TX on display encode(message); tx = false; // clear message & buffer clearBuf(message); } } // picture loop, display init data void dispUpdate() { oled.firstPage(); do { dispMsg(15, 0, "JT65_ADS_TEXT"); dispMsg(15, 15, message); dispFreq(15, 30, freqHz, freqChz, 2); if (tx == false) dispTime(30, 50); else dispMsg(40, 50, "TX"); } while ( oled.nextPage() ); } // Loop through the string, transmitting one character at a time. void encode(char * tx_string) { uint8_t i; // encode the message jt65_encode(tx_string, tx_buffer); // Now do the rest of the message for (i = 0; i < SYMBOL_COUNT; i++) { ads.setFreq(freqHz, freqChz + (tx_buffer[i] * TONE_SPACING), phase); proceed = false; while (!proceed); } // Turn off the output ads.down(); } static inline int modnn(struct rs *rs, int x) { while (x >= rs->nn) { x -= rs->nn; x = (x >> rs->mm) + (x & rs->nn); } return x; } uint8_t jt_code(char c) { /* Validate the input then return the proper integer code */ // Return 255 as an error code if the char is not allowed if (isdigit(c)) { return (uint8_t)(c - 48); } else if (c >= 'A' && c <= 'Z') { return (uint8_t)(c - 55); } else if (c == ' ') { return 36; } else if (c == '+') { return 37; } else if (c == '-') { return 38; } else if (c == '.') { return 39; } else if (c == '/') { return 40; } else if (c == '?') { return 41; } else { return 255; } } // timer interrupt veector ISR(TIMER1_COMPA_vect) { proceed = true; } // Reed Soloman encoder by KA9Q void encode_rs_int(void *p, data_t *data, data_t *parity) { struct rs *rs = (struct rs *)p; #undef A0 #define A0 (NN) /* Special reserved value encoding zero in index form */ int i, j; data_t feedback; memset(parity, 0, NROOTS * sizeof(data_t)); for (i = 0; i < NN - NROOTS - PAD; i++) { feedback = INDEX_OF[data[i] ^ parity[0]]; if (feedback != A0) { /* feedback term is non-zero */ #ifdef UNNORMALIZED /* This line is unnecessary when GENPOLY[NROOTS] is unity, as it must always be for the polynomials constructed by init_rs() */ feedback = MODNN(NN - GENPOLY[NROOTS] + feedback); #endif for (j = 1; j < NROOTS; j++) parity[j] ^= ALPHA_TO[MODNN(feedback + GENPOLY[NROOTS - j])]; } /* Shift */ memmove(&parity[0], &parity[1], sizeof(data_t) * (NROOTS - 1)); if (feedback != A0) parity[NROOTS - 1] = ALPHA_TO[MODNN(feedback + GENPOLY[0])]; else parity[NROOTS - 1] = 0; } } void free_rs_int(void *p) { struct rs *rs = (struct rs *)p; free(rs->alpha_to); free(rs->index_of); free(rs->genpoly); free(rs); } // init RS enc, symb size, poly coeff, first root, primative, no roots, padding void *init_rs_int(int symsize, int gfpoly, int fcr, int prim, int nroots, int pad) { struct rs *rs; //#undef NULL //#define NULL ((void *)0) int i, j, sr, root, iprim; rs = ((struct rs *)0); /* Check parameter ranges */ if (symsize < 0 || symsize > 8 * sizeof(data_t)) { goto done; } if (fcr < 0 || fcr >= (1 << symsize)) goto done; if (prim <= 0 || prim >= (1 << symsize)) goto done; if (nroots < 0 || nroots >= (1 << symsize)) goto done; /* Can't have more roots than symbol values! */ if (pad < 0 || pad >= ((1 << symsize) - 1 - nroots)) goto done; /* Too much padding */ rs = (struct rs *)calloc(1, sizeof(struct rs)); if (rs == NULL) goto done; rs->mm = symsize; rs->nn = (1 << symsize) - 1; rs->pad = pad; rs->alpha_to = (data_t *)malloc(sizeof(data_t) * (rs->nn + 1)); if (rs->alpha_to == NULL) { free(rs); rs = ((struct rs *)0); goto done; } rs->index_of = (data_t *)malloc(sizeof(data_t) * (rs->nn + 1)); if (rs->index_of == NULL) { free(rs->alpha_to); free(rs); rs = ((struct rs *)0); goto done; } /* Generate Galois field lookup tables */ rs->index_of[0] = A0; /* log(zero) = -inf */ rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */ sr = 1; for (i = 0; i < rs->nn; i++) { rs->index_of[sr] = i; rs->alpha_to[i] = sr; sr <<= 1; if (sr & (1 << symsize)) sr ^= gfpoly; sr &= rs->nn; } if (sr != 1) { /* field generator polynomial is not primitive! */ free(rs->alpha_to); free(rs->index_of); free(rs); rs = ((struct rs *)0); goto done; } /* Form RS code generator polynomial from its roots */ rs->genpoly = (data_t *)malloc(sizeof(data_t) * (nroots + 1)); if (rs->genpoly == NULL) { free(rs->alpha_to); free(rs->index_of); free(rs); rs = ((struct rs *)0); goto done; } rs->fcr = fcr; rs->prim = prim; rs->nroots = nroots; /* Find prim-th root of 1, used in decoding */ for (iprim = 1; (iprim % prim) != 0; iprim += rs->nn) ; rs->iprim = iprim / prim; rs->genpoly[0] = 1; for (i = 0, root = fcr * prim; i < nroots; i++, root += prim) { rs->genpoly[i + 1] = 1; /* Multiply rs->genpoly[] by @**(root + x) */ for (j = i; j > 0; j--) { if (rs->genpoly[j] != 0) rs->genpoly[j] = rs->genpoly[j - 1] ^ rs->alpha_to[modnn(rs, rs->index_of[rs->genpoly[j]] + root)]; else rs->genpoly[j] = rs->genpoly[j - 1]; } /* rs->genpoly[0] can never be zero */ rs->genpoly[0] = rs->alpha_to[modnn(rs, rs->index_of[rs->genpoly[0]] + root)]; } /* convert rs->genpoly[] to index form for quicker encoding */ for (i = 0; i <= nroots; i++) rs->genpoly[i] = rs->index_of[rs->genpoly[i]]; done:; return rs; } uint8_t gray_code(uint8_t c) { return (c >> 1) ^ c; } void rs_encode(uint8_t * data, uint8_t * symbols) { unsigned int dat1[12]; unsigned int b[51]; unsigned int i; // Reverse data order for the Karn codec. for (i = 0; i < 12; i++) { dat1[i] = data[11 - i]; } // Compute the parity symbols encode_rs_int(rs, dat1, b); // Move parity symbols and data into symbols array, in reverse order. for (i = 0; i < 51; i++) { symbols[50 - i] = b[i]; } for (i = 0; i < 12; i++) { symbols[i + 51] = dat1[11 - i]; } } void jt65_encode(char * message, uint8_t symbols[SYMBOL_COUNT]) { uint8_t i, j, k; // Convert all chars to uppercase for (i = 0; i < 13; i++) { if (islower(message[i])) { message[i] = toupper(message[i]); } } // Collapse multiple spaces down to one // Pad the message with trailing spaces uint8_t len = strlen(message); if (len < 13) { for (i = len; i < 13; i++) { message[i] = ' '; } } // Bit packing // ----------- uint8_t c[12]; uint32_t n1, n2, n3; // Find the N values n1 = jt_code(message[0]); n1 = n1 * 42 + jt_code(message[1]); n1 = n1 * 42 + jt_code(message[2]); n1 = n1 * 42 + jt_code(message[3]); n1 = n1 * 42 + jt_code(message[4]); n2 = jt_code(message[5]); n2 = n2 * 42 + jt_code(message[6]); n2 = n2 * 42 + jt_code(message[7]); n2 = n2 * 42 + jt_code(message[8]); n2 = n2 * 42 + jt_code(message[9]); n3 = jt_code(message[10]); n3 = n3 * 42 + jt_code(message[11]); n3 = n3 * 42 + jt_code(message[12]); // Pack bits 15 and 16 of N3 into N1 and N2, // then mask reset of N3 bits n1 = (n1 << 1) + ((n3 >> 15) & 1); n2 = (n2 << 1) + ((n3 >> 16) & 1); n3 = n3 & 0x7fff; // Set the freeform message flag n3 += 32768; c[0] = (n1 >> 22) & 0x003f; c[1] = (n1 >> 16) & 0x003f; c[2] = (n1 >> 10) & 0x003f; c[3] = (n1 >> 4) & 0x003f; c[4] = ((n1 & 0x000f) << 2) + ((n2 >> 26) & 0x0003); c[5] = (n2 >> 20) & 0x003f; c[6] = (n2 >> 14) & 0x003f; c[7] = (n2 >> 8) & 0x003f; c[8] = (n2 >> 2) & 0x003f; c[9] = ((n2 & 0x0003) << 4) + ((n3 >> 12) & 0x000f); c[10] = (n3 >> 6) & 0x003f; c[11] = n3 & 0x003f; // Reed-Solomon encoding // --------------------- uint8_t s[63]; k = 0; rs_encode(c, s); // Interleaving // ------------ uint8_t d[63]; uint8_t d1[7][9]; // Fill temp d1 array for (i = 0; i < 9; i++) { for (j = 0; j < 7; j++) { d1[i][j] = s[(i * 7) + j]; } } // Interleave and translate back to 1D destination array for (i = 0; i < 7; i++) { for (j = 0; j < 9; j++) { d[(i * 9) + j] = d1[j][i]; } } // Gray Code // --------- uint8_t g[63]; for (i = 0; i < 63; i++) { g[i] = gray_code(d[i]); } // Merge with sync vector // ---------------------- const uint8_t sync_vector[126] = { 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 }; j = 0; for (i = 0; i < 126; i++) { if (sync_vector[i]) { symbols[i] = 0; } else { symbols[i] = g[j] + 2; j++; } } } // get time from RTC, convert bcd to decimal void getRTC() { // Reset the register pointer Wire.beginTransmission(RTCADDR); byte zero = 0x00; Wire.write(zero); Wire.endTransmission(); // request 1st 3 bytes from the RTC address Wire.requestFrom(RTCADDR, 3); // get the s/m/h time data sec = bcdToDec(Wire.read()); mns = bcdToDec(Wire.read()); hrs = bcdToDec(Wire.read() & 0b111111); // mask 24 hour time bit } // Convert binary coded decimal to normal decimal numbers byte bcdToDec(byte val) { return ( (val / 16 * 10) + (val % 16) ); } // get input message[] U.C. bool getMsg(char *m) { char ch; int n; n = 0; if (Serial.available() > 0) { // if input while (Serial.available() > 0) { // get input ch = Serial.read(); // use upper case as input if (ch == '\n') ch = '\0'; // end of text m[n++] = ch; delay(20); // let USB catch up } return true; // got input } return false; // no input } // clear msg and buffer void clearBuf(char *m) { m[0] = '\0'; while (Serial.available() > 0) Serial.read(); } // display freq at x, y, f (Hz), cf (cHz), d)ecimal places void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, uint8_t d) { double fd; char buf[100]; // sets font, cursor position and displays freq oled.setFont(u8g2_font_10x20_tf); // font oled.setFontPosTop(); // origin top fd = f + cf / 100; // calc freq oled.setCursor(x, y); oled.print(fd / 1000, d); oled.print("kHz"); } // display message at at x), y), *m)essage void dispMsg(u8g2_uint_t x, u8g2_uint_t y, char *m) { // sets font, cursor position and displays message oled.setFont(u8g2_font_7x13_tf); // font oled.setFontPosTop(); oled.setCursor(x, y); oled.print(m); } // display time HH:MM:SS at x), y) void dispTime(u8g2_uint_t x, u8g2_uint_t y) { // sets font, cursor position and displays message oled.setFont(u8g_font_7x14); // fix font for now oled.setFontPosTop(); oled.setCursor(x, y); if (hrs < 10) oled.print("0"); oled.print(hrs); oled.print(":"); if (mns < 10) oled.print("0"); oled.print(mns); oled.print(":"); if (sec < 10) oled.print("0"); oled.print(sec); }
No comments:
Post a Comment