/************************************************* * Memory Keyer Program * * Written for issue Aug. 2014 of Monthly FB News * * T.Tsujioka (JH1NRR/JL3YMC) * *************************************************/ // Last updated: 2014/07/30 #include // #include // #include // #include #define _XTAL_FREQ 48000000 // PLL clock of 48MHz // #define _XTAL_FREQ 20000000 // External clock of 20MHz #define OUT_LED LATB7 #define OUT_KEY LATA5 #define KEY_DOT PORTCbits.RC0 #define KEY_DASH PORTCbits.RC5 #define KEY_RB0 PORTBbits.RB0 #define KEY_RB1 PORTBbits.RB1 #define KEY_RB2 PORTBbits.RB2 #define KEY_RB3 PORTBbits.RB3 #define KEY_REC PORTBbits.RB4 #define KEY_REPEAT PORTBbits.RB5 #define KEY_CH_SHIFT 1 // if not implemented // #define KEY_CH_SHIFT PORTAbits.RA4 // ■ トグルスイッチを実装したらこちらを有効にして下さい。 #pragma config PLLDIV = 5 // Divide by 5 (20 MHz oscillator input) #pragma config CPUDIV = OSC1_PLL2 // 96 MHz PLL Src: /2 #pragma config USBDIV = 2 // USB clock source comes from the 96 MHz PLL divided by 2 #pragma config FOSC = HSPLL_HS // HS oscillator, PLL enabled, HS used by USB #pragma config FCMEN = OFF // Fail-Safe Clock Monitor disabled #pragma config IESO = OFF // Oscillator Switchover mode disabled #pragma config PWRT = OFF // PoWeR-up Timer disabled #pragma config BOR = ON // Brown-out Reset enabled in hardware only (SBOREN is disabled) #pragma config BORV = 3 // Minimum setting #pragma config VREGEN = OFF // USB voltage regulator disabled (RC4,RC5) #pragma config WDT = OFF // HW Disabled - SW Controlled #pragma config WDTPS = 32768 // Watch-dog Timer Postscale Select: 1:32768 #pragma config MCLRE = ON // MCLR pin enabled; RE3 input pin disabled #pragma config LPT1OSC = OFF // Timer1 configured for higher power operation #pragma config PBADEN = ON // PORTB<4:0> pins are configured as analog input channels on Reset #pragma config CCP2MX = OFF // CCP2 input/output is multiplexed with RB3 #pragma config STVREN = ON // Stack full/underflow will cause Reset #pragma config LVP = OFF // Single-Supply ICSP disabled #pragma config XINST = OFF // Instruction set extension and Indexed Addressing mode disabled (Legacy mode) #pragma config DEBUG = OFF // Background debugger disabled, RB6 and RB7 configured as GPIO pins // ===== INTERRUPT HANDLER ===== void int_handler(void); void interrupt high_priority isr_high(void) { int_handler(); } void interrupt low_priority isr_low(void) { int_handler(); } int tick = 0; void int_handler(void) { if (TMR0IE && TMR0IF) { TMR0IF = 0; WriteTimer0(0); tick++; } if (TMR1IE && TMR1IF) { // Do nothing TMR1IF = 0; } if (CCP1IE && CCP1IF) { CCP1IF = 0; CCP1 = !CCP1; // WriteTimer1(0); // set to 0 automatically } } // ===== TIMER0 ===== void init_timer0(void) { // Tick timer (unused) OpenTimer0(TIMER_INT_ON & T0_8BIT & T0_SOURCE_INT & T0_PS_1_32); WriteTimer0(0); // 48MHz/4 / 32 / (256 + 1) = 1.459kHz RCONbits.IPEN = 0; TMR0IE = 1; PEIE = 1; GIE = 1; } // ===== COMPARE1 ===== void init_compare(void) { #if 0 // cannot be used // freq. > 700Hz (not valid because of 8-bit counter) // OpenTimer2(TIMER_INT_OFF & T2_PS_1_16 & T2_POST_1_1); // OpenPWM1(446); // T = 3.2us * (445+1) = 1424us // SetDCPWM1(312); // T_high = 0.8us * 892 = 713.6us #endif #if 1 // 600Hz: 12MHz/600Hz/2=10000, T1_CCP1_T3_CCP2 // 700Hz: 12MHz/700Hz/2=8572, T1_CCP1_T3_CCP2 SetTmrCCPSrc(T1_SOURCE_CCP); WriteTimer1(0); OpenTimer1(TIMER_INT_OFF & T1_16BIT_RW & T1_PS_1_1 & T1_OSC1EN_OFF & T1_SYNC_EXT_OFF & T1_SOURCE_INT); OpenCompare1(COM_INT_ON & COM_TRIG_SEVNT, 10000); // ■ トーン周波数の600Hzを変更する場合はこの値を変えて下さい。12MHz÷周波数÷2で計算して下さい。 #endif } // ===== ADC ===== void init_adc(void) { // FOSC_16(<22MHz), FOSC_64(<48MHz) (TAD = 0.7us - 25us, Vref>=3.0) // T_ACQ >= 2.45us OpenADC(ADC_FOSC_64 & ADC_RIGHT_JUST & ADC_16_TAD, ADC_CH0 & ADC_INT_OFF & ADC_REF_VDD_VSS, 0b1001); Delay10TCYx(5); } // ===== TONE / DELAY ===== int count_dash = 210; int count_dot = 70; int count_dot_silence = 70; double dash_dot_ratio = 3.0; void update_speed(void) { int adc_value_speed; int adc_value_ratio; SetChanADC(ADC_CH0); ConvertADC(); while (BusyADC()); adc_value_speed = ReadADC(); Delay10TCYx(10); SetChanADC(ADC_CH1); ConvertADC(); while (BusyADC()); adc_value_ratio = ReadADC(); Delay10TCYx(10); // ratio: 1:2.5 - 1:4.5 dash_dot_ratio = 3.5 + ((adc_value_ratio - 512.0) / 512.0); // count_dash: 100 - 356 count_dash = 100 + (1023 - adc_value_speed) / 4; count_dot = (int)(count_dash / dash_dot_ratio); #if 0 // ■ どちらか選んで下さい。短点スペースも短くするときは1、1:3を維持する場合は0です。 count_dot_silence = count_dot; #else count_dot_silence = count_dash / 3; #endif } void delay_dot(void) { int i; // The dot duration is the basic unit of time measurement in code transmission. for (i = 0; i < count_dot; i++) { __delay_ms(1); } } void delay_dot_silence(void) { int i; // Each dash or dot is followed by a short silence which is equal to a dot duration time. for (i = 0; i < count_dot_silence; i++) { __delay_ms(1); } } void delay_dash(void) { int i; // The duration of a dash is three times the duration of a dot. for (i = 0; i < count_dash; i++) { __delay_ms(1); } } void delay_half_dot(void) { int i; for (i = count_dot / 2; i > 0; i--) { __delay_ms(1); } } void delay_word_space(void) { int i; // The words are separated by a space equal to seven dots; thus a short silence plus two dash spaces. delay_dash(); delay_dash(); } void tone_on(void) { OUT_KEY = 1; CCP1IE = 1; } void tone_off(void) { OUT_KEY = 0; CCP1IE = 0; CCP1 = 0; } void tone_dash(void) { tone_on(); delay_dash(); tone_off(); delay_dot_silence(); // a short silence } void tone_dot(void) { tone_on(); delay_dot(); tone_off(); delay_dot_silence(); // a short silence } // ===== KEY ===== unsigned char is_anykeyon(void) { // Sense any key is on. if (KEY_RB0 == 0 || KEY_RB1 == 0 || KEY_RB2 == 0 || KEY_RB3 == 0 || KEY_DOT == 0 || KEY_DASH == 0 || KEY_REPEAT == 0 || KEY_REC == 0) { return (1); } else { return (0); } } unsigned char is_anykeyon_wo_dotdash(void) { // Sense any key is on. if (KEY_RB0 == 0 || KEY_RB1 == 0 || KEY_RB2 == 0 || KEY_RB3 == 0 || KEY_REPEAT == 0 || KEY_REC == 0) { return (1); } else { return (0); } } void wait_keysoff(void) { // Wait for all keys released. while (is_anykeyon()) { delay_dot(); } } void wait_keysoff_wo_dotdash(void) { // Wait for all keys released. while (is_anykeyon_wo_dotdash()) { delay_dot(); } } // ===== MORSE CONVERSION ===== #define M_DOT 1 #define M_DASH 2 #define M_SPACE 3 #define M_WSPACE 4 #define M_END 0 struct morse_s { char c; char code[8]; }; struct morse_s morse[] = { { 'A', { M_DOT, M_DASH, M_END } }, { 'B', { M_DASH, M_DOT, M_DOT, M_DOT, M_END } }, { 'C', { M_DASH, M_DOT, M_DASH, M_DOT, M_END } }, { 'D', { M_DASH, M_DOT, M_DOT, M_END } }, { 'E', { M_DOT, M_END } }, { 'F', { M_DOT, M_DOT, M_DASH, M_DOT, M_END } }, { 'G', { M_DASH, M_DASH, M_DOT, M_END } }, { 'H', { M_DOT, M_DOT, M_DOT, M_DOT, M_END } }, { 'I', { M_DOT, M_DOT, M_END } }, { 'J', { M_DOT, M_DASH, M_DASH, M_DASH, M_END } }, { 'K', { M_DASH, M_DOT, M_DASH, M_END } }, { 'L', { M_DOT, M_DASH, M_DOT, M_DOT, M_END } }, { 'M', { M_DASH, M_DASH, M_END } }, { 'N', { M_DASH, M_DOT, M_END } }, { 'O', { M_DASH, M_DASH, M_DASH, M_END } }, { 'P', { M_DOT, M_DASH, M_DASH, M_DOT, M_END } }, { 'Q', { M_DASH, M_DASH, M_DOT, M_DASH, M_END } }, { 'R', { M_DOT, M_DASH, M_DOT, M_END } }, { 'S', { M_DOT, M_DOT, M_DOT, M_END } }, { 'T', { M_DASH, M_END } }, { 'U', { M_DOT, M_DOT, M_DASH, M_END } }, { 'V', { M_DOT, M_DOT, M_DOT, M_DASH, M_END } }, { 'W', { M_DOT, M_DASH, M_DASH, M_END } }, { 'X', { M_DASH, M_DOT, M_DOT, M_DASH, M_END } }, { 'Y', { M_DASH, M_DOT, M_DASH, M_DASH, M_END } }, { 'Z', { M_DASH, M_DASH, M_DOT, M_DOT, M_END } }, { '0', { M_DASH, M_DASH, M_DASH, M_DASH, M_DASH, M_END } }, { '1', { M_DOT, M_DASH, M_DASH, M_DASH, M_DASH, M_END } }, { '2', { M_DOT, M_DOT, M_DASH, M_DASH, M_DASH, M_END } }, { '3', { M_DOT, M_DOT, M_DOT, M_DASH, M_DASH, M_END } }, { '4', { M_DOT, M_DOT, M_DOT, M_DOT, M_DASH, M_END } }, { '5', { M_DOT, M_DOT, M_DOT, M_DOT, M_DOT, M_END } }, { '6', { M_DASH, M_DOT, M_DOT, M_DOT, M_DOT, M_END } }, { '7', { M_DASH, M_DASH, M_DOT, M_DOT, M_DOT, M_END } }, { '8', { M_DASH, M_DASH, M_DASH, M_DOT, M_DOT, M_END } }, { '9', { M_DASH, M_DASH, M_DASH, M_DASH, M_DOT, M_END } }, // { '.', { M_DOT, M_DASH, M_DOT, M_DASH, M_DOT, M_DASH, M_END } }, // ~AAA { ',', { M_DASH, M_DASH, M_DOT, M_DOT, M_DASH, M_DASH, M_END } }, // ~MIM { '?', { M_DOT, M_DOT, M_DASH, M_DASH, M_DOT, M_DOT, M_END } }, // ~IMI { '!', { M_DOT, M_DOT, M_DOT, M_DASH, M_DOT, M_END } }, // ~SN { '-', { M_DASH, M_DOT, M_DOT, M_DOT, M_DOT, M_DASH, M_END } }, // ~DU { '/', { M_DASH, M_DOT, M_DOT, M_DASH, M_DOT, M_END } }, // ~DN { '@', { M_DOT, M_DASH, M_DASH, M_DOT, M_DASH, M_DOT, M_END } }, // ~PN ~WR { '(', { M_DASH, M_DOT, M_DASH, M_DASH, M_DOT, M_END } }, // ~KN { ')', { M_DASH, M_DOT, M_DASH, M_DASH, M_DOT, M_DASH, M_END } }, // ~KK // { '+', { M_DOT, M_DASH, M_DOT, M_DASH, M_DOT, M_END } }, // ~AR { '$', { M_DOT, M_DOT, M_DOT, M_DASH, M_DOT, M_DOT, M_DASH, M_END } }, // ~SX { '%', { M_DASH, M_DOT, M_DASH, M_DOT, M_DASH, M_END } }, // ~KA { '&', { M_DOT, M_DASH, M_DOT, M_DOT, M_DOT, M_END } }, // ~AS { '\'',{ M_DOT, M_DASH, M_DASH, M_DASH, M_DASH, M_DOT, M_END } }, // ~WG { ':', { M_DASH, M_DASH, M_DASH, M_DOT, M_DOT, M_DOT, M_END } }, // ~OS { ';', { M_DASH, M_DOT, M_DASH, M_DOT, M_DASH, M_DOT, M_END } }, // ~KR { '=', { M_DASH, M_DOT, M_DOT, M_DOT, M_DASH, M_END } }, // ~BT { '|', { M_DOT, M_DOT, M_DOT, M_DASH, M_DOT, M_DASH, M_END } }, // ~SK { '\\',{ M_DOT, M_DASH, M_DOT, M_DASH, M_DOT, M_DOT, M_END } }, // ~AL { '_', { M_DOT, M_DOT, M_DASH, M_DASH, M_DOT, M_DASH, M_END } }, // ~IQ // // { '\r', { M_DASH, M_DOT, M_DOT, M_DOT, M_DASH, M_END } }, // BT BAR // 前後のSPも指定しなければならないのでこの方法は止めました // { '\n', { M_DASH, M_DOT, M_DOT, M_DOT, M_DASH, M_END } }, // BT BAR // 同上 { '\0', { M_END } }, }; char scan_morse(char c) { int i; for (i = 0; morse[i].c; i++) { if (morse[i].c == c) { return (i); } } return (-1); } void morse_conversion(char *m, const unsigned char *string) { int i, j; unsigned char c; unsigned char *p; unsigned char bar_flag; p = (unsigned char *)string; bar_flag = 0; while (c = *p++) { if (c == '\\' || c == '~') { // BTバーなどは ~BT または \\BT と入力します。 bar_flag = 1; } else if (c == ' ') { // Word space... *m++ = M_WSPACE; bar_flag = 0; } else if (c == '\r') { #if 1 // \rは前後にSPが挿入された~BT(文区切り)に変換して挿入します。 *m++ = M_WSPACE; *m++ = M_DASH; *m++ = M_DOT; *m++ = M_DOT; *m++ = M_DOT; *m++ = M_DASH; *m++ = M_WSPACE; #endif bar_flag = 0; } else { // Roman letters and numbers... i = scan_morse(c); if (i >= 0) { for (j = 0; morse[i].code[j]; j++) { *m++ = morse[i].code[j]; } if (!bar_flag) { *m++ = M_SPACE; // character separator } } } } *m = M_END; } // ===== MORSE MEMORY ===== #define MAXCH 8 #define MAXMEMLEN 1024 #define FLASH_ADDRESS 0x6000 long mem_tblptr[MAXCH] = { FLASH_ADDRESS, FLASH_ADDRESS + MAXMEMLEN, FLASH_ADDRESS + MAXMEMLEN * 2, FLASH_ADDRESS + MAXMEMLEN * 3, FLASH_ADDRESS + MAXMEMLEN * 4, FLASH_ADDRESS + MAXMEMLEN * 5, FLASH_ADDRESS + MAXMEMLEN * 6, FLASH_ADDRESS + MAXMEMLEN * 7 }; char mem[MAXMEMLEN]; void init_mem(unsigned char raw_ch, char *p) { int i; unsigned long address; address = mem_tblptr[raw_ch]; #if 1 EraseFlash(address, address + MAXMEMLEN - 1); WriteBytesFlash(address, MAXMEMLEN, p); #else // If FLASH_WRITE_BLOCK is not equal to FLASH_ERASE_BLOCK, this cannot be used. for (i = 0; i < MAXMEMLEN; i += FLASH_WRITE_BLOCK) { EraseFlash(address, address + FLASH_ERASE_BLOCK); WriteBlockFlash(address, 1, p); address += FLASH_WRITE_BLOCK; p += FLASH_WRITE_BLOCK; } #endif } void init_mem_default(void) { // ■RECスイッチを押しながら電源を入れた時に設定される定型文を、見本に従って設定して下さい。 morse_conversion(mem, "CQ CQ CQ DE JL3YMC JL3YMC JL3YMC PSE ~AR"); init_mem(0, mem); morse_conversion(mem, "DE JL3YMC JL3YMC ~AR"); init_mem(1, mem); morse_conversion(mem, "TNX FB QSO ~BT MY QSL VIA JARL"); init_mem(2, mem); morse_conversion(mem, "CUAGN 73 ~SK E E"); init_mem(3, mem); morse_conversion(mem, ""); init_mem(4, mem); init_mem(5, mem); init_mem(6, mem); init_mem(7, mem); } #define REPEAT_LIMIT 100 // ■この回数以上はリピートしません(送信事故を回避するため) #define REPEAT_BLANK 15 // ■リピート時に間に挿入するブランク長です。ボリュームで調整できるようにすると良いかもしれません。 void playback(unsigned char ch) { int i, j; int repeat; unsigned long address; char c; // Set repeat times. if (KEY_REPEAT == 0) { repeat = REPEAT_LIMIT; } else { repeat = 1; } // Wait until all keys are off. wait_keysoff(); OUT_LED = 1; // Channel bank if (KEY_CH_SHIFT == 0) { ch += 4; } // Read from Flash address = mem_tblptr[ch]; ReadFlash(address, MAXMEMLEN, mem); // Pleyback /w repeat for (i = 0; i < repeat; i++) { // A long silence for repeat transmission if (i > 0) { for (j = 0; j < REPEAT_BLANK; j++) { delay_dash(); #if 1 // Break if any key is on. if (is_anykeyon()) { delay_dot(); // delay_dash(); OUT_LED = 0; return; } #endif } } // Playback from memory for (j = 0; j < MAXMEMLEN; j++) { c = mem[j]; if (c == M_END) break; switch (c) { case M_DOT: tone_dot(); break; case M_DASH: tone_dash(); break; case M_SPACE: delay_dash(); break; case M_WSPACE: delay_word_space(); break; } #if 1 // Break if any key is on. if (is_anykeyon()) { delay_dot(); // delay_dash(); // wait_keysoff(); // comment this out if necessary OUT_LED = 0; return; } #endif #if 1 // UPDATE SPEED & RATIO update_speed(); #endif } } OUT_LED = 0; } void record(unsigned char ch) { int i, j; int count_space; unsigned long address; char c; // Wait until all keys are off. wait_keysoff(); OUT_LED = 1; i = 0; count_space = 0; while (1) { if (KEY_DASH != 0 && KEY_DOT != 0) { delay_half_dot(); count_space++; } else { if (i > 0) { if (count_space <= 1) { // ignored } else if (count_space <= 8) { // Character (Letter or Number) space... mem[i++] = M_SPACE; } else { // Word space... #if 0 // ■どちらか選んで下さい。ブランク長を記録するときは1、固定値挿入の場合は0です。 j = (int)(count_space / (dash_dot_ratio * 2.0) + 0.0); #else j = 1; #endif for (; j > 0; j--) { mem[i++] = M_WSPACE; } } } if (KEY_DASH == 0) { tone_dash(); mem[i++] = M_DASH; count_space = 0; } if (KEY_DOT == 0) { tone_dot(); mem[i++] = M_DOT; count_space = 0; } } if (KEY_REC == 0) break; #if 1 // Cancel if any key is on except REC key. if (is_anykeyon_wo_dotdash()) { delay_dot(); // delay_dash(); // wait_keysoff(); // comment this out if necessary OUT_LED = 0; return; } #endif #if 1 // UPDATE SPEED & RATIO update_speed(); #endif } mem[i] = M_END; // Channel bank if (KEY_CH_SHIFT == 0) { ch += 4; } // Write to Flash address = mem_tblptr[ch]; EraseFlash(address, address + MAXMEMLEN - 1); WriteBytesFlash(address, MAXMEMLEN, mem); OUT_LED = 0; } // ===== MAIN ===== void main(void) { OSCCON = 0b00110000; // Internal 8MHz: unused ADCON1 = 0b00001101; // DDDDDDAA: AN1-0:enable INTCON = 0; INTCON2 = 0b01110101; // RB Pull-Up=on, TMR0IP=high // CMCON = 0x07; // To use pins RC4 and RC5 as digital inputs, the USB module must be disabled (UCON<3> = 0) // and the on-chip USB transceiver must be disabled(UCFG<3> = 1). UCON = 0b00000000; // UCON.USBEN = 0; UCFG = 0b00001000; // UCFG.UTRDIS = 1; TRISA = 0b00000011; // RA1-0=AN1-AN0 TRISB = 0b00111111; // RB7-6=out, RB5-1:inp TRISC = 0b00110001; // RC5,RC0=inp init_timer0(); init_compare(); init_adc(); if (KEY_REC == 0) { init_mem_default(); } // Sound off tone_off(); // LED off OUT_LED = 0; // Main loop while (1) { // DOT if (KEY_DOT == 0) { tone_dot(); } // DASH if (KEY_DASH == 0) { tone_dash(); } // RECORD / PLAYBACK if (KEY_REC == 0) { if (KEY_RB3 == 0) { record(0); } else if (KEY_RB2 == 0) { record(1); } else if (KEY_RB1 == 0) { record(2); } else if (KEY_RB0 == 0) { record(3); } } else { if (KEY_RB3 == 0) { playback(0); } else if (KEY_RB2 == 0) { playback(1); } else if (KEY_RB1 == 0) { playback(2); } else if (KEY_RB0 == 0) { playback(3); } } #if 1 // UPDATE SPEED & RATIO update_speed(); #endif } }