/* * Outputs ax25 to the si_trx * Copyright (C) 2015 Richard Meadows * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include "samd20.h" #include "system/gclk.h" #include "system/pinmux.h" #include "tc/tc_driver.h" #include "hw_config.h" #include "ax25.h" #include "telemetry.h" #include "si_trx.h" enum ax25_symbol_t next_symbol; uint8_t bit_index; struct ax25_byte_t current_byte; uint8_t current_bit; uint8_t one_count; uint32_t byte_index; enum ax25_state_t ax25_state; uint8_t ax25_frame[AX25_MAX_FRAME_LEN]; uint32_t ax25_index, ax25_frame_length; void ax25_gpio1_pwm_init(void); /** * USEFUL RESOURCES * ============================================================================= * * http://en.wikipedia.org/wiki/AX.25 * https://www.tapr.org/pub_ax25.html#2.4.1.2 * http://owenduffy.net/blog/?p=2101 * http://n1vg.net/packet/ */ /** * Frame Check Sequence (FCS) * ============================================================================= */ /** * CRC Function for CCITT-16. Poly = 0x8408 * http://www.nongnu.org/avr-libc/user-manual/group__util__crc.html#ga1c1d3ad875310cbc58000e24d981ad20 */ uint16_t crc_ccitt_update (uint16_t crc, uint8_t data) { data ^= (crc & 0xff); data ^= data << 4; return ((((uint16_t)data << 8) | ((crc >> 8) & 0xff)) ^ (uint8_t)(data >> 4) ^ ((uint16_t)data << 3)); } /** * Calculates the Frame Check Sequence (FCS) using the CRC algorithm */ uint16_t crc_fcs(uint8_t *string, uint32_t length) { size_t i; uint16_t crc; uint8_t c; crc = 0xFFFF; for (i = 0; i < length; i++) { c = string[i]; crc = crc_ccitt_update(crc, c); } return crc ^ 0xFFFF; } /** * Starts the transmission of an ax25 frame */ void ax25_start(char* addresses, uint32_t addresses_len, char* information, uint32_t information_len) { uint32_t i = 0; uint16_t fcs; /* Process addresses */ for (i = 0; i < addresses_len; i++) { if ((i % 7) == 6) { /* Secondary Station ID */ ax25_frame[i] = ((addresses[i] << 1) & 0x1F) | 0x60; } else { ax25_frame[i] = (addresses[i] << 1); } } ax25_frame[i-1] |= 0x1; /* Set HLDC bit */ ax25_frame[i++] = AX25_CONTROL_WORD; ax25_frame[i++] = AX25_PROTOCOL_ID; /* Process information */ memcpy(ax25_frame+i, information, information_len); i += information_len; /* Frame Check Sequence (FCS) */ fcs = crc_fcs(ax25_frame, i); ax25_frame[i++] = (fcs >> 0) & 0xFF; ax25_frame[i++] = (fcs >> 8) & 0xFF; /* Length */ ax25_frame_length = i; /* Init */ next_symbol = AX25_MARK; bit_index = 8; current_bit = 1; one_count = 0; ax25_state = AX25_PREAMBLE; ax25_index = 0; /* Hardware init */ ax25_gpio1_pwm_init(); } /** * Sets up gpio1 for the afsk pwm output. Uses gclk 7 */ void ax25_gpio1_pwm_init(void) { float gclk1_frequency = (float)system_gclk_gen_get_hz(1); float divide_needed = round(gclk1_frequency / 13200); uint32_t top = (uint32_t)divide_needed & ~0x1; uint32_t capture = top >> 1; /* 50% duty cycle */ if (top > 0xFFFF) while (1); // It's only an 16-bit counter /* Setup GCLK genertor 7 */ system_gclk_gen_set_config(GCLK_GENERATOR_7, GCLK_SOURCE_GCLKGEN1, /* Source */ false, /* High When Disabled */ AX25_DIVISION_MARK,/* Division Factor */ false, /* Run in standby */ false); /* Output Pin Enable */ system_gclk_gen_enable(GCLK_GENERATOR_7); /* Configure timer */ bool capture_channel_enables[] = {false, false}; uint32_t compare_channel_values[] = {top, capture}; tc_init(TC5, GCLK_GENERATOR_7, TC_COUNTER_SIZE_16BIT, TC_CLOCK_PRESCALER_DIV1, TC_WAVE_GENERATION_MATCH_PWM, TC_RELOAD_ACTION_GCLK, TC_COUNT_DIRECTION_UP, TC_WAVEFORM_INVERT_OUTPUT_NONE, false, /* Oneshot = false */ false, /* Run in standby = false */ 0x0000, /* Initial value */ top, /* Top value */ capture_channel_enables, /* Capture Channel Enables */ compare_channel_values); /* Compare Channels Values */ /* Enable the output pin */ system_pinmux_pin_set_config(SI4xxx_GPIO1_PINMUX >> 16, /* GPIO Pin */ SI4xxx_GPIO1_PINMUX & 0xFFFF, /* Mux Position */ SYSTEM_PINMUX_PIN_DIR_INPUT, /* Direction */ SYSTEM_PINMUX_PIN_PULL_NONE, /* Pull */ false); /* Powersave */ tc_enable(TC5); tc_start_counter(TC5); } void ax25_gpio1_pwm_deinit(void) { tc_stop_counter(TC5); tc_disable(TC5); } /** * Returns the next byte to transmit */ uint8_t ax25_get_next_byte(struct ax25_byte_t* next) { switch (ax25_state) { case AX25_PREAMBLE: /* Preamble */ /* Return flag */ next->val = AX25_HDLC_FLAG; next->stuff = 0; /* Check for next state */ ax25_index++; if (ax25_index >= AX25_PREAMBLE_FLAGS) { /* Next state */ ax25_state = AX25_FRAME; ax25_index = 0; } break; case AX25_FRAME: /* Frame */ /* Return data */ next->val = ax25_frame[ax25_index]; next->stuff = 1; /* Check for next state */ ax25_index++; if (ax25_index >= ax25_frame_length) { /* Next state */ ax25_state = AX25_POSTAMBLE; ax25_index = 0; } break; case AX25_POSTAMBLE: /* Postamble */ /* Return flag */ next->val = AX25_HDLC_FLAG; next->stuff = 0; /* Check for next state */ ax25_index++; if (ax25_index >= AX25_POSTAMBLE_FLAGS) { /* Next state */ ax25_state = AX25_IDLE; ax25_index = 0; } break; default: return 0; } return 1; } /** * Returns the next symbol to transmit */ enum ax25_symbol_t ax25_get_next_symbol(void) { /* Get next byte if we need to */ if (bit_index >= 8) { /* Attempt to get the next byte */ if (!ax25_get_next_byte(¤t_byte)) { return AX25_NONE; /* We're done */ } bit_index = 0; } /* transmit bits lsb first */ if (current_byte.val & 0x01) { /* One */ one_count++; /* Check if we need to stuff this bit */ if (one_count >= AX25_BITSTUFFINGCOUNT && current_byte.stuff) { current_byte.val &= ~0x01;/* Next bit is zero */ one_count = 0; } else { current_byte.val >>= 1; /* Move along one bit */ bit_index++; } } else { /* Zero */ one_count = 0; /* Clear concecutive ones */ current_byte.val >>= 1; /* Move along one bit */ bit_index++; /* NRZI encoding */ current_bit ^= 0x01; } return current_bit; } /** * Called at our tick rate, controls the pwm gclk * * Returns 1 when more work todo, 0 when finished */ uint8_t ax25_tick(void) { if (next_symbol == AX25_NONE) { ax25_gpio1_pwm_deinit(); return 0; /* We're done */ } if (next_symbol == AX25_SPACE) { /* Space */ system_gclk_gen_set_config(GCLK_GENERATOR_7, GCLK_SOURCE_GCLKGEN1, /* Source */ false, /* High When Disabled */ AX25_DIVISION_SPACE, /* Division Factor */ false, /* Run in standby */ false); /* Output Pin Enable */ } else { /* Mark */ system_gclk_gen_set_config(GCLK_GENERATOR_7, GCLK_SOURCE_GCLKGEN1, /* Source */ false, /* High When Disabled */ AX25_DIVISION_MARK, /* Division Factor */ false, /* Run in standby */ false); /* Output Pin Enable */ } next_symbol = ax25_get_next_symbol(); return 1; }