From 0dbf9bb7c18227860da563ec87bde230f819dceb Mon Sep 17 00:00:00 2001 From: adrcs Date: Mon, 14 Apr 2025 19:29:49 -0600 Subject: [PATCH] Add files via upload --- Node Firmware/IP400/Inc/config.h | 41 ++ Node Firmware/IP400/Inc/dataq.h | 61 ++ Node Firmware/IP400/Inc/frame.h | 210 ++++++ Node Firmware/IP400/Inc/ip.h | 97 +++ Node Firmware/IP400/Inc/led.h | 72 ++ Node Firmware/IP400/Inc/newlib-freertos.h | 62 ++ Node Firmware/IP400/Inc/setup.h | 139 ++++ Node Firmware/IP400/Inc/spi.h | 71 ++ Node Firmware/IP400/Inc/streambuffer.h | 18 + Node Firmware/IP400/Inc/tod.h | 40 + Node Firmware/IP400/Inc/types.h | 54 ++ Node Firmware/IP400/Inc/usart.h | 62 ++ Node Firmware/IP400/Inc/utils.h | 42 ++ Node Firmware/IP400/Src/beacon.c | 402 ++++++++++ Node Firmware/IP400/Src/callsign.c | 195 +++++ Node Firmware/IP400/Src/chat.c | 306 ++++++++ Node Firmware/IP400/Src/dataq.c | 66 ++ Node Firmware/IP400/Src/frame.c | 647 +++++++++++++++++ Node Firmware/IP400/Src/insque.c | 48 ++ Node Firmware/IP400/Src/ip.c | 163 +++++ Node Firmware/IP400/Src/led.c | 277 +++++++ Node Firmware/IP400/Src/logger.c | 63 ++ Node Firmware/IP400/Src/menu.c | 845 ++++++++++++++++++++++ Node Firmware/IP400/Src/mesh.c | 301 ++++++++ Node Firmware/IP400/Src/setup.c | 356 +++++++++ Node Firmware/IP400/Src/spi.c | 395 ++++++++++ Node Firmware/IP400/Src/tod.c | 80 ++ Node Firmware/IP400/Src/usart.c | 300 ++++++++ Node Firmware/IP400/Src/utils.c | 146 ++++ 29 files changed, 5559 insertions(+) create mode 100644 Node Firmware/IP400/Inc/config.h create mode 100644 Node Firmware/IP400/Inc/dataq.h create mode 100644 Node Firmware/IP400/Inc/frame.h create mode 100644 Node Firmware/IP400/Inc/ip.h create mode 100644 Node Firmware/IP400/Inc/led.h create mode 100644 Node Firmware/IP400/Inc/newlib-freertos.h create mode 100644 Node Firmware/IP400/Inc/setup.h create mode 100644 Node Firmware/IP400/Inc/spi.h create mode 100644 Node Firmware/IP400/Inc/streambuffer.h create mode 100644 Node Firmware/IP400/Inc/tod.h create mode 100644 Node Firmware/IP400/Inc/types.h create mode 100644 Node Firmware/IP400/Inc/usart.h create mode 100644 Node Firmware/IP400/Inc/utils.h create mode 100644 Node Firmware/IP400/Src/beacon.c create mode 100644 Node Firmware/IP400/Src/callsign.c create mode 100644 Node Firmware/IP400/Src/chat.c create mode 100644 Node Firmware/IP400/Src/dataq.c create mode 100644 Node Firmware/IP400/Src/frame.c create mode 100644 Node Firmware/IP400/Src/insque.c create mode 100644 Node Firmware/IP400/Src/ip.c create mode 100644 Node Firmware/IP400/Src/led.c create mode 100644 Node Firmware/IP400/Src/logger.c create mode 100644 Node Firmware/IP400/Src/menu.c create mode 100644 Node Firmware/IP400/Src/mesh.c create mode 100644 Node Firmware/IP400/Src/setup.c create mode 100644 Node Firmware/IP400/Src/spi.c create mode 100644 Node Firmware/IP400/Src/tod.c create mode 100644 Node Firmware/IP400/Src/usart.c create mode 100644 Node Firmware/IP400/Src/utils.c diff --git a/Node Firmware/IP400/Inc/config.h b/Node Firmware/IP400/Inc/config.h new file mode 100644 index 0000000..cbfcdd1 --- /dev/null +++ b/Node Firmware/IP400/Inc/config.h @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------- + Project: WL33_E04_RPI + + File Name: config.h + + Author: MartinA + + Creation Date: Jan 27, 2025 + + Description: Configuration parameters + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) 2024-25 Alberta Digital Radio Communications Society + + Revision History: + +---------------------------------------------------------------------------*/ +#ifndef INC_CONFIG_H_ +#define INC_CONFIG_H_ + +// the board type is selected here... +#define PI_BOARD 0 // pi board +#define NUCLEO_BOARD 1 // Nucleo board +#define _BOARD_TYPE PI_BOARD // board type in use + +// gps receiver enabled +#if _BOARD_TYPE == PI_BOARD +#define __ENABLE_GPS 0 // PI board does not have GPS +#endif + +#if _BOARD_TYPE == NUCLEO_BOARD +#define __ENABLE_GPS 0 // set to 1 if you have GPS attached to Nucleo +#endif + +#endif /* INC_CONFIG_H_ */ + diff --git a/Node Firmware/IP400/Inc/dataq.h b/Node Firmware/IP400/Inc/dataq.h new file mode 100644 index 0000000..eec2c2f --- /dev/null +++ b/Node Firmware/IP400/Inc/dataq.h @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + Module: + + File Name: queue.h + + Date Created: Jan 9, 2025 + + Author: MartinA + + Description: + + Copyright © 2024-25, Alberta Digital Radio Communications Society, + All rights reserved + + + Revision History: + +---------------------------------------------------------------------------*/ + +#ifndef INC_DATAQ_H_ +#define INC_DATAQ_H_ + +#ifndef __CCALL +#ifdef __cplusplus +#define __CCALL extern "C" +#else +#define __CCALL +#endif +#endif + +#define Q_TYPECAST (struct qelem *) + +// queue structure +typedef struct qelem { + struct qelem *q_forw; + struct qelem *q_back; +} QUEUE_ELEM; + +// frame data queue +typedef struct frame_data_queue_t { + struct frame_data_queue_t *q_forw; // forwared pointer + struct frame_data_queue_t *q_back; // backward pointer + void *frame; // IP400 Frame + uint16_t length; // length (in some cases) +} FRAME_QUEUE; + +// queue functions +BOOL enqueFrame(FRAME_QUEUE *que, IP400_FRAME *fr); +IP400_FRAME *dequeFrame(FRAME_QUEUE *que); + +// queue management +void insque (struct qelem *elem, struct qelem *pred); +void remque (struct qelem *elem); + +// print memory stats +void Print_Memory_Stats(void); + + +#endif /* INC_DATAQ_H_ */ diff --git a/Node Firmware/IP400/Inc/frame.h b/Node Firmware/IP400/Inc/frame.h new file mode 100644 index 0000000..7686818 --- /dev/null +++ b/Node Firmware/IP400/Inc/frame.h @@ -0,0 +1,210 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + Module: + + File Name: frame.h + + Date Created: Jan 8, 2025 + + Author: MartinA + + Description: + + Copyright © 2024-25, Alberta Digital Radio Communications Society, + All rights reserved + + + Revision History: + +---------------------------------------------------------------------------*/ + +#ifndef FRAME_H_ +#define FRAME_H_ + +#include +#include "types.h" + +// frame defines +#define N_CALL 4 // octets in the excess-40 compressed callsign +#define N_IPBYTES 2 // number of IP address bytes +#define MAX_CALL 6 // max callsign size +#define EXT_CALL 4*MAX_CALL // extended call sign field +#define PAYLOAD_MIN 56 // min octets in payload +#define PAYLOAD_MAX 1053 // max octets in payload +#define MAX_CALL_BUFFER 20 // max buffering for callsign in payload +#define N_FEC 4 // number of bytes in the FEC +#define BROADCAST_ADDR 0xFF // broadcast address + +#define MAX_HOP_COUNT 15 // max hop count + +// transmit states +enum { + TX_IDLE=0, // idle - waiting for work + TX_SENDING, // sending a frame + TX_DONE // done +}; + +// radio error register +#define SEQ_COMPLETE_ERR 0x8000 // Sequencer error +#define SEQ_ACT_TIMEOUT 0x4000 // Sequencer action timeout +#define PLL_CALAMP_ERR 0x0800 // VCO amplitude calibration error +#define PLL_CALFREQ_ERR 0x0400 // VCO frequency calibration error +#define PLL_UNLOCK_ERR 0x0200 // PLL is unlocked +#define PLL_LOCK_FAIL 0x0100 // PLL lock failure +#define DBM_FIFO_ERR 0x0020 // Data buffer failure +#define N_RADIO_ERRS 7 // number of the above + +// radio FSM states +enum fsm_states_e { + FSM_IDLE=0, // idle + FSM_ENA_RF_REG, // enable RF registers + FSM_WAIT_ACTIVE2, // wait for active 2 + FSM_ACTIVE2, // active 2 + FSM_ENA_CURR, // enable current + FSM_SYNTH_SETUP, // synth setup + FSM_CALIB_VCO, // VCO calibration + FSM_LOCKRXTX, // lock Rx and Rx + FSM_LOCKONTX, // lock on Rx + FSM_EN_PA, // enable PA + FSM_TX, // transmit + FSM_PA_DWN_ANA, // Analog power down + FSM_END_TX, // end transmit + FSM_LOCKONRX, // lock on Rx + FSM_EN_RX, // Enable Rx + FSM_EN_LNA, // enable LNA + FSM_RX, // recieve + FSM_END_RX, // end rx + FSM_SYNTH_PWDN, // synth power down + FSM_N_FSM_STATES +}; + +// header flags +typedef struct frame_flags_t { + unsigned hop_count: 4; // hop count + unsigned coding: 4; // coding method + unsigned compression: 2; // compression method + unsigned hoptable: 1; // hop table is included + unsigned srcExt: 1; // source call is extended + unsigned destExt: 1; // dest call is extended + unsigned command: 1; // command packet + unsigned noconnect: 1; // connectionless + unsigned repeat: 1; // repeat flag +} IP400_FLAGS; + +// IP 400 MAC address +typedef struct ip400_mac_t { + union { + uint8_t bytes[N_CALL]; // compressed callsign + uint32_t encoded; + } callbytes; + union { + uint8_t ip[N_IPBYTES]; + uint16_t encip; // encoded IP data + } ipbytes; +} IP400_MAC; + +// hop table +typedef struct hoptable_t { + IP400_MAC hopAddr; +} HOPTABLE; + +// complete frame +typedef struct ip400_frame_t { + IP400_MAC source; // source call sign + IP400_MAC dest; // destination call sign + union { + IP400_FLAGS flags; // flag bit field + uint16_t allflags; // all flags + } flagfld; + uint32_t seqNum; // packet sequence number + void *buf; // data to send + uint16_t length; // data length + void *hopTable; // hop table address +} IP400_FRAME; + +// min/max payload sizes +#define IP_400_CALL_SIZE (N_CALL + N_IPBYTES) +#define IP_400_FLAG_SIZE sizeof(uint16_t) +#define IP_400_HDR_SIZE (2*IP_400_CALL_SIZE + IP_400_FLAG_SIZE) +#define IP_400_LEN_SIZE sizeof(uint16_t) + +#define MIN_FRAME_SIZE sizeof(IP400_FRAME) + PAYLOAD_MIN + N_FEC +#define MAX_FRAME_SIZE MIN_FRAME_SIZE - PAYLOAD_MIN + PAYLOAD_MAX + +// packet coding (type) +typedef enum { + UTF8_TEXT_PACKET=0, // Text packet (chat application) + COMPRESSED_AUDIO, // compressed audio packet + COMPREESSD_VIDEO, // compressed video packet + DATA_PACKET, // data packet + BEACON_PACKET, // beacon packet + IP_ENCAPSULATED, // IP encapsulated packet + AX_25_PACKET, // AX.25 encapsulated packet + RFC4733_DTMF, // DTMF packet + DMR_FRAME, // DMR Frame + DSTAR_FRAME, // Dstar Frame + P25_FRAME, // TIA project 25 + NXDN_FRAME, // NXDN + M17_FRAME, // M17 + TBD_1, + TBD_2, + LOCAL_COMMAND // local command frame +} IP400FrameType; + +// audio compression types +enum { + AUDIO_RAW, // raw 16-bit PCM + AUDIO_ULAW, // mu law compression + AUDIO_CODEC2, // codec 2 encoded + AUDIO_AMBE // AMBE encoded +}; + +// H.246 Video compression +enum { + H264_240_180_24, // H.264: 240x180, 24FPS + H264_320_240_24, // H.264: 320x240, 24FPS + H264_480_360_12, // H.264: 480x360, 12FPS + H264_640_480_6 // H.264: 640x480, 6FPS +}; + +// callsign fields +enum { + SRC_CALLSIGN=0, // dest for encode is source callsign + DEST_CALLSIGN // dest for encode is dest callsign +}; + +// frame stats +typedef struct frame_stats_t { + uint32_t TxFrameCnt; // transmit frame count + uint32_t RxFrameCnt; // good receive frame count + uint32_t CRCErrors; // CRC Errors + uint32_t TimeOuts; // Timeouts + uint32_t lastRSSI; // last RSSI reading + uint32_t framesOK; // processed frames + uint32_t dropped; // rejected frames + uint32_t duplicates; // duplicates + uint32_t nBeacons; // number of beacons processed + uint32_t nRepeated; // repeated frames +} FRAME_STATS; + +uint8_t getFrameStatus(void); + +// references +uint8_t callEncode(char *callsign, uint16_t port, IP400_FRAME *frame, uint8_t dest, uint8_t offset); +BOOL callDecode(IP400_MAC *encCall, char *callsign, uint16_t *port); +void EncodeChunk(char *src, int len, uint32_t *enc); + +// frame senders +BOOL SendTextFrame(char *srcCall, uint16_t srcPort, char *destCall, uint16_t dstPort, char *buf, uint16_t length, BOOL repeat); +void SendBeaconFrame(char *srcCall, uint8_t *payload, int bcnlen); +void SendSPIFrame(void *spiHdr, uint8_t *payload, int len); +// +BOOL EnqueChatFrame(void *Frame); // queue a chat frame +FRAME_STATS *GetFrameStats(void); // return the frame stats +uint32_t GetRadioStatus(void); // get the radio status +uint8_t GetFSMState(void); // get FSM state +// +uint8_t getFrameStatus(void); // get the frame status + +#endif /* FRAME_H_ */ diff --git a/Node Firmware/IP400/Inc/ip.h b/Node Firmware/IP400/Inc/ip.h new file mode 100644 index 0000000..2c1a63f --- /dev/null +++ b/Node Firmware/IP400/Inc/ip.h @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------- + Project: IP400 + + File Name: ip.h + + Author: MartinA + + Creation Date: Jan 26, 2025 + + Description: Definitions for IP addressing + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) 2024-25 Alberta Digital Radio Communications Society + + Revision History: + +---------------------------------------------------------------------------*/ + +#ifndef INC_IP_H_ +#define INC_IP_H_ + +#include + +#include "types.h" +#include "frame.h" + +// IP Generation Algorithm +//IP Addresses +#define IP_10_GROUP 0 // use IP 10 +#define IP_172_GROUP 1 // use ip 172 +#define IP_172_ID 2 // use ip 172 from ID +#define __IP_GROUP IP_172_ID + + +/* If your port already typedef's sa_family_t, define SA_FAMILY_T_DEFINED + to prevent this code from redefining it. */ +#if !defined(sa_family_t) && !defined(SA_FAMILY_T_DEFINED) +typedef uint8_t sa_family_t; +#endif +/* If your port already typedef's in_port_t, define IN_PORT_T_DEFINED + to prevent this code from redefining it. */ +#if !defined(in_port_t) && !defined(IN_PORT_T_DEFINED) +typedef uint16_t in_port_t; +#endif + +// in_addr struct +struct in_addr_t { + union { + struct { uint8_t s_b1,s_b2,s_b3,s_b4; } S_un_b; + struct { uint16_t s_w1,s_w2; } S_un_w; + uint32_t S_addr; + } S_un; +}; +typedef struct in_addr_t IN_ADDR; + +/* members are in network byte order */ +struct sockaddr_in { + uint8_t sin_len; + sa_family_t sin_family; + in_port_t sin_port; + IN_ADDR sin_addr; +#define SIN_ZERO_LEN 8 + char sin_zero[SIN_ZERO_LEN]; +}; + +typedef struct sockaddr_in SOCKADDR_IN; + +// convert packed frame to IP address +void GetIP10Addr(IP400_MAC *fr, SOCKADDR_IN *ipaddr); +void GetIP172Addr(IP400_MAC *fr, SOCKADDR_IN *ipaddr); +void Get172AddrFromID(IP400_MAC *fr, SOCKADDR_IN *ipaddr); + +void GetIPAddrFromMAC(IP400_MAC *fr, SOCKADDR_IN *ipAddr); + +#if __IP_GROUP == IP_172_ID +#define GetIPAddr Get172AddrFromID +#else + +#if __IP_GROUP == IP_10_GROUP +#define GetIPAddr GetIP10Add +#else +#define GetIPAddr GetIP172Addr +#endif + +#endif + +// Get IP address from setup data +void GetMyIP(SOCKADDR_IN **ipAddr); +void GetMyMAC(IP400_MAC **mac); +uint16_t GetIPLowerWord(void); + +#endif /* INC_IP_H_ */ diff --git a/Node Firmware/IP400/Inc/led.h b/Node Firmware/IP400/Inc/led.h new file mode 100644 index 0000000..db31764 --- /dev/null +++ b/Node Firmware/IP400/Inc/led.h @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + File Name: led.h + + Author: MartinA + + Creation Date: Jan 26, 2025 + + Description: Definitions for the LED module + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) 2024-25 Alberta Digital Radio Communications Society + + Revision History: + +---------------------------------------------------------------------------*/ +#ifndef INC_LED_H_ +#define INC_LED_H_ + +// config +#define REVERSE_LEADS 1 // reverse Bi-color LED leads on some boards + +// LED functions +enum { + + BICOLOR_OFF=0, // turn LED off + BICOLOR_RED, // red mode steady + BICOLOR_RED_FLASH, // red mode flashing + BICOLOR_GREEN, // green mode steady + BICOLOR_GREEN_FLASH, // green mode flashing + BICOLOR_RED_GREEN, // alternate RED/GREEN + TX_LED_ON, // tx LED on + TX_LED_OFF, // tx LED OFF + N_LED_MODE // modes1 +}; + +#if _BOARD_TYPE == PI_BOARD +// define the LED ports +#define LED_Pin GPIO_PIN_15 +#define LED_GPIO_Port GPIOB +#if REVERSE_LEADS +// green on PA9, red on PB0 +#define LED_Green_Pin GPIO_PIN_9 +#define LED_Green_GPIO_Port GPIOA +#define LED_Red_Pin GPIO_PIN_0 +#define LED_Red_GPIO_Port GPIOB +#else +// green on PB0, red on PA9 +#define LED_Green_Pin GPIO_PIN_0 +#define LED_Green_GPIO_Port GPIOB +#define LED_Red_Pin GPIO_PIN_9 +#define LED_Red_GPIO_Port GPIOA +#endif + +// +#define TXLED_Pin GPIO_PIN_15 +#define TXLED_GPIO_Port GPIOB +// +#define PA_ENA_Pin GPIO_PIN_0 +#define PA_ENA_GPIO_Port GPIOA +#endif + +void SetLEDMode(uint8_t mode); + + +#endif /* INC_LED_H_ */ diff --git a/Node Firmware/IP400/Inc/newlib-freertos.h b/Node Firmware/IP400/Inc/newlib-freertos.h new file mode 100644 index 0000000..b6911c9 --- /dev/null +++ b/Node Firmware/IP400/Inc/newlib-freertos.h @@ -0,0 +1,62 @@ +/* + * FreeRTOS Kernel V10.6.2 + * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-License-Identifier: MIT + * + * 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. + * + * https://www.FreeRTOS.org + * https://github.com/FreeRTOS + * + */ + +#ifndef INC_NEWLIB_FREERTOS_H +#define INC_NEWLIB_FREERTOS_H + +/* Note Newlib support has been included by popular demand, but is not + * used by the FreeRTOS maintainers themselves. FreeRTOS is not + * responsible for resulting newlib operation. User must be familiar with + * newlib and must provide system-wide implementations of the necessary + * stubs. Be warned that (at the time of writing) the current newlib design + * implements a system-wide malloc() that must be provided with locks. + * + * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html + * for additional information. */ + +#include + +#define configUSE_C_RUNTIME_TLS_SUPPORT 1 + +#ifndef configTLS_BLOCK_TYPE + #define configTLS_BLOCK_TYPE struct _reent +#endif + +#ifndef configINIT_TLS_BLOCK + #define configINIT_TLS_BLOCK( xTLSBlock, pxTopOfStack ) _REENT_INIT_PTR( &( xTLSBlock ) ) +#endif + +#ifndef configSET_TLS_BLOCK + #define configSET_TLS_BLOCK( xTLSBlock ) ( _impure_ptr = &( xTLSBlock ) ) +#endif + +#ifndef configDEINIT_TLS_BLOCK + #define configDEINIT_TLS_BLOCK( xTLSBlock ) _reclaim_reent( &( xTLSBlock ) ) +#endif + +#endif /* INC_NEWLIB_FREERTOS_H */ diff --git a/Node Firmware/IP400/Inc/setup.h b/Node Firmware/IP400/Inc/setup.h new file mode 100644 index 0000000..f1b328c --- /dev/null +++ b/Node Firmware/IP400/Inc/setup.h @@ -0,0 +1,139 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + File Name: setup.h + + Author: MartinA + + Creation Date: Jan 13, 2025 + + Description: Definitions for setup data + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) 2024-25 Alberta Digital Radio Communications Society + + Revision History: + +---------------------------------------------------------------------------*/ +#ifndef INC_SETUP_H_ +#define INC_SETUP_H_ + +#include + +#include "frame.h" +#include "usart.h" + +#define __USE_SETUP_PARAMS 1 // set to 1 to use setup parameters +#define US 0 // station is in the US + +// defined elsewhere +extern char *modTypes[]; +extern char *paModes[]; + +typedef struct setup_flags_t { + unsigned fsk: 1; // can run FSK + unsigned ofdm: 1; // can run OFDM + unsigned aredn: 1; // is an AREDN node + unsigned repeat: 1; // repeat mode default + unsigned ext: 1; // use extended callsign + unsigned rate: 3; // data rate in use +} SETUP_FLAGS; + +// data rates in the flag field (FSK mode) +enum { + FSK_1200, + FSK_9600, + FSK_56K, + FSK_100K, + FSK_200K, + FSK_400K, + FSK_600K +}; + +// setup data struct +typedef struct setup_data_t { + SETUP_FLAGS flags; // flags + char stnCall[MAX_CALL]; // station call sign + char extCall[EXT_CALL]; // extended call sign + char latitude[10]; // latititude + char longitude[10]; // longitude + char gridSq[10]; // grid square + uint16_t beaconInt; // beacon interval +} SETUP_DATA; + +// Radio setup struct +typedef struct radio_setup_t { + uint32_t lFrequencyBase; /*!< Specifies the base carrier frequency (in Hz) */ + MRSubGModSelect xModulationSelect; /*!< Specifies the modulation @ref MRSubGModSelect */ + uint32_t lDatarate; /*!< Specifies the datarate expressed in sps.*/ + uint32_t lFreqDev; /*!< Specifies the frequency deviation expressed in Hz. */ + uint32_t lBandwidth; /*!< Specifies the channel filter bandwidth expressed in Hz. */ + uint8_t dsssExp; /*!< Specifies the DSSS spreading exponent. Use 0 to disable DSSS. */ + uint8_t outputPower; /*!< PA value to write expressed in dBm. */ + MRSubG_PA_DRVMode PADrvMode; /*!< PA drive mode. */ + int16_t rxSquelch; // rx squelch level +} RADIO_SETUP; + +// setup struct +typedef struct stn_params_t { + SETUP_DATA setup_data; // basic setup data + RADIO_SETUP radio_setup; // radio setup + uint8_t FirmwareVerMajor; // firmware major rev + uint8_t FirmwareVerMinor; // firmware minor vers + uint32_t Magic; // magic number: "DEBEADEF" + uint32_t SetupCRC; // CRC +} STN_PARAMS; + +// beacon header +typedef union { + struct beacon_hdr_t { + SETUP_FLAGS flags; + uint8_t txPower; + uint8_t FirmwareMajor; + uint8_t FirmwareMinor; + } setup; + uint8_t hdrBytes[sizeof(struct beacon_hdr_t)]; +} BEACON_HEADER; + +#define SETUP_MAGIC 0xDEBEADEF // magic number + +typedef union { + STN_PARAMS params; + uint8_t bytes[sizeof(STN_PARAMS)]; + uint32_t flashwords[sizeof(STN_PARAMS)/sizeof(uint32_t)]; +} SETUP_MEMORY; + +extern SETUP_MEMORY setup_memory; +extern SETUP_MEMORY def_params; +extern CRC_HandleTypeDef hcrc; + +// validations used by key entry +#if US +#define MIN_FREQ 420000000 // min freq (US only) +#else +#define MIN_FREQ 430000000 // min freq (CAN only) +#endif + +// links in +void printStationSetup(void); // print setup struct +void printRadioSetup(void); // print radio setup +char *GetMyCall(void); // return the station's callsign +STN_PARAMS *GetStationParams(void); // get the station params +BOOL CompareToMyCall(char *call); +// +BOOL VerifySetup(void); +BOOL ReadSetup(void); +HAL_StatusTypeDef WriteSetup(void); +void SetDefSetup(void); +BOOL UpdateSetup(void); + +// device ID +uint32_t GetDevID0(void); +uint32_t GetDevID1(void); + +#endif /* INC_SETUP_H_ */ diff --git a/Node Firmware/IP400/Inc/spi.h b/Node Firmware/IP400/Inc/spi.h new file mode 100644 index 0000000..e44b05e --- /dev/null +++ b/Node Firmware/IP400/Inc/spi.h @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + File Name: spi.h + + Author: MartinA + + Creation Date: Jan 26, 2025 + + Description: Definitions for SPI mod + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) 2024-25 Alberta Digital Radio Communications Society + + Revision History: + +---------------------------------------------------------------------------*/ + +#ifndef INC_SPI_H_ +#define INC_SPI_H_ + +#include "frame.h" + +// SPI frame header +typedef struct spi_hdr_t { + uint8_t eye[4]; // 'IP4X' + uint8_t status; // status byte + uint8_t offset_hi; // high offset + uint8_t offset_lo; // low offset + uint8_t length_hi; // length hi + uint8_t length_lo; // length lo + uint8_t fromCall[N_CALL]; // from callsign + uint8_t fromIP[N_IPBYTES]; // from port + uint8_t toCall[N_CALL]; // to callsign + uint8_t toIP[N_IPBYTES]; // to port + uint8_t coding; // packet coding + uint8_t hopCount; // hop count + uint8_t flags; // remaining flags +} SPI_HEADER; + +#define SPI_BUFFER_LEN 400 // 400 bytes/transfer +#define SPI_RAW_LEN SPI_BUFFER_LEN + sizeof(struct spi_hdr_t) + +// status values +typedef enum { + NO_DATA=0, // no data available + SINGLE_FRAME, // single frame + FRAGMENT, // fragment + LAST_FRAGMENT, // last fragment + NUM_STATS // number of stats +} spiFrameStatus; + +// data buffer struct +typedef union { + struct { + SPI_HEADER hdr; // header + uint8_t buffer[SPI_BUFFER_LEN]; + } spiData; + uint8_t rawData[SPI_RAW_LEN]; +} SPI_BUFFER; + +#define SPI_TIMEOUT 100 // SPI timeout + +BOOL EnqueSPIFrame(void *raw); + +#endif /* INC_SPI_H_ */ diff --git a/Node Firmware/IP400/Inc/streambuffer.h b/Node Firmware/IP400/Inc/streambuffer.h new file mode 100644 index 0000000..bcad0da --- /dev/null +++ b/Node Firmware/IP400/Inc/streambuffer.h @@ -0,0 +1,18 @@ +/* + * streambuffer.h + * + * Created on: Jan 11, 2025 + * Author: MartinA + */ + +#ifndef INC_STREAMBUFFER_H_ +#define INC_STREAMBUFFER_H_ + +// data buffer defs +typedef uint8_t DATA_ELEMENT; // data element +#define BUFFER_EMPTY(x) (x.nDataBytes == 0) +#define BUFFER_NO_DATA 0 +#define bufferSIZE 512 // large buffer + + +#endif /* INC_STREAMBUFFER_H_ */ diff --git a/Node Firmware/IP400/Inc/tod.h b/Node Firmware/IP400/Inc/tod.h new file mode 100644 index 0000000..2867da6 --- /dev/null +++ b/Node Firmware/IP400/Inc/tod.h @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + File Name: tod.h + + Author: MartinA + + Creation Date: Jan 23, 2025 + + Description: + +// TOD struct +typedef struct tod_t { + uint8_t Seconds; + uint8_t Minutes; + uint8_t Hours; +} TIMEOFDAY; + +// links in +void TOD_10SecTimer(void); // 10 second timer +void getTOD(TIMEOFDAY *time); +BOOL setTOD(char *todString); + +#endif /* INC_TOD_H_ */ diff --git a/Node Firmware/IP400/Inc/types.h b/Node Firmware/IP400/Inc/types.h new file mode 100644 index 0000000..92bf64f --- /dev/null +++ b/Node Firmware/IP400/Inc/types.h @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + Module: Type definitions + + File Name: types.h + + Date Created: Jan 9, 2025 + + Author: MartinA + + Description: Useful data types + + Copyright © 2024-25, Alberta Digital Radio Communications Society, + All rights reserved + + + Revision History: + +---------------------------------------------------------------------------*/ + +#ifndef INC_TYPES_H_ +#define INC_TYPES_H_ + +#include +#include + +// boolean type +#ifndef BOOL +typedef uint8_t BOOL; // boolean +typedef uint8_t BOOLEAN; // alternate +#endif + +#ifndef TRUE +#define TRUE ((BOOL)1U) +#endif + +#ifndef FALSE +#define FALSE ((BOOL)0U) +#endif + +#ifndef __CCALL +#ifdef __cplusplus +#define __CCALL extern "C" +#else +#define __CCALL +#endif +#endif + +// macros +#define islower(c) ((c >= 'a') && (c <= 'z')) +#define toupper(c) (c-0x20) + +#endif /* INC_TYPES_H_ */ diff --git a/Node Firmware/IP400/Inc/usart.h b/Node Firmware/IP400/Inc/usart.h new file mode 100644 index 0000000..a823ff7 --- /dev/null +++ b/Node Firmware/IP400/Inc/usart.h @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + File Name: usart.h + + Author: MartinA + + Creation Date: Jan 12, 2025 + + Description: +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "frame.h" +#include "tasks.h" +#include "setup.h" +#include "utils.h" +#include "tod.h" +#include "usart.h" + +// config +#define __SPEED_DAEMON 0 // send beacon every 5 seconds for testing purposes + +// local defines +#define MAX_BEACON 80 // max beacon string +#define GPS_FIX_LEN 20 // gps fix length +#define GPS_BFR_SIZE 140 // GPS buffer size + +#if __ENABLE_GPS +// NMEA GGA Sentence fields +char *nmeaMsgTag = "RMC"; // sentence we are processing +enum { + NMEA_TAG=0, // message tag + NMEA_TIMESTAMP, // time of fix' + NMEA_STATUS, // status + NMEA_LATITUDE, // latitude + NMEA_NS_HEMI, // latitude hemisphere + NMEA_LONGITUDE, // longitude + NMEA_EW_HEMI, // longitude hemisphere + NMEA_MIN_FLDS // minimum fields in GPS message +}; + +// NMEA processing states +enum { + NMEA_STATE_SOM=0, // start of message + NMEA_STATE_MSG, // in the message +}; + +#define N_NMEA_SEN 14 // number of NMEA sentences +enum { + NMEA_CMD_DISABLED=0, // disabled + NMEA_CMD_ONCE, // once per fix + NMEA_CMD_2FIX, // once every 2 fixes + NMEA_CMD_3FIX, // once every 3 fixes + NMEA_CMD_4FIX, // once every 4 fixes + NMEA_CMD_5FIX, // once every 5 fixes +}; + +char *nmeaCmd = "$PMTK314"; // command +uint8_t senCmds[N_NMEA_SEN] = { + NMEA_CMD_DISABLED, // 0:GLL disabled + NMEA_CMD_ONCE, // 1: RMC once + NMEA_CMD_DISABLED, // 2: VTG disabled + NMEA_CMD_DISABLED, // 3: GGA disabled + NMEA_CMD_DISABLED, // 4: GSA disabled + NMEA_CMD_DISABLED, // 5: GSV disabled + NMEA_CMD_DISABLED, // 6 not used + NMEA_CMD_DISABLED, // 7 + NMEA_CMD_DISABLED, // 13 + NMEA_CMD_DISABLED, // 14 + NMEA_CMD_DISABLED, // 15 + NMEA_CMD_DISABLED, // 16 + NMEA_CMD_DISABLED, // 17 + NMEA_CMD_DISABLED // 18 +}; +#endif + +// hemispheres +enum { + N_HEMI=0, // North + S_HEMI, // South + E_HEMI, // East + W_HEMI, // West + N_HEMIS +}; + +uint8_t hemispheres[N_HEMIS] = { + 'N', 'S', 'E', 'W' +}; + +uint32_t timerInitValue; // value to initialize timer +uint32_t timerCtrValue; // current counter +BEACON_HEADER beacon_hdr; // beacon header +uint8_t bcnPayload[MAX_BEACON]; // beacon payload +BOOL gpsMessageRx = FALSE; +BOOL GPSBusy=FALSE; +TIMEOFDAY wallClockTime; + +#if __ENABLE_GPS +// data for GPS +uint8_t GPSMsgBuf[GPS_BFR_SIZE]; +uint8_t GPSEchoBuf[GPS_BFR_SIZE]; +uint8_t GPSProcBuf[GPS_BFR_SIZE]; +uint8_t *GPSBufPtr; +uint8_t GPSMsgSize; +uint8_t NMEAState; +char cmdBuf[MAX_BEACON]; +// +BOOL haveGPSFix = FALSE; +BOOL gpsEchoReady=FALSE; +// +char GPSLat[GPS_FIX_LEN]; +char GPSLong[GPS_FIX_LEN]; +char GPSFixTime[GPS_FIX_LEN]; +char *gpsFlds[GPS_BFR_SIZE]; + +// fwd Refs in this module in GPS mode +BOOL processGPSMessage(uint8_t *GPSMsgBuf, uint8_t bufferSize); +void sendGPSCmd(void); +#endif + +//fwd refs w/o GPS +void GPSFormat(char *buffer, double value, uint8_t hePos, uint8_t heNeg); + +// initialization: calculate the init value in +// quanta of MAIN_TASK_SCHED +void Beacon_Task_init(void) +{ +// higher speed for testing... +#if __SPEED_DAEMON + timerInitValue = 5 *1000/MAIN_TASK_SCHED; // every 5 seconds for testing + timerCtrValue = 0; +#else + uint32_t timerTick = 60 * 1000/MAIN_TASK_SCHED; + timerInitValue = setup_memory.params.setup_data.beaconInt * timerTick; + timerCtrValue = 0; // set to timerInitValue to wait +#endif + + // GPS Init +#if __ENABLE_GPS + gpsMessageRx = FALSE; + haveGPSFix = FALSE; + gpsEchoReady = FALSE; + GPSBufPtr = GPSMsgBuf; + GPSBusy = FALSE; + NMEAState=NMEA_STATE_SOM; +#endif + +} + +// this runs every MAIN_TASK_SCHED ms +void Beacon_Task_exec(void) +{ +#if __ENABLE_GPS + if(gpsMessageRx) { + // process the message + if(processGPSMessage(GPSProcBuf, (uint8_t)strlen((char *)GPSProcBuf))) { + haveGPSFix = TRUE; + } + gpsMessageRx = FALSE; + } +#endif + + if(timerCtrValue > 0) { + timerCtrValue--; + return; + } + timerCtrValue = timerInitValue; + +#if __ENABLE_GPS + // send a command to the GPS every beacon interval + sendGPSCmd(); +#endif + + // start with the header + beacon_hdr.setup.flags = setup_memory.params.setup_data.flags; + beacon_hdr.setup.txPower = setup_memory.params.radio_setup.outputPower; + uint8_t *buf = bcnPayload; + + // beacon header: flags, txpower and firmware version + // brute force copy: compiler rounds SETUP_FLAGS to 32 bits + *buf++ = beacon_hdr.hdrBytes[0]; + *buf++ = beacon_hdr.hdrBytes[4]; + + // firmware version + *buf++ = def_params.params.FirmwareVerMajor + '0'; + *buf++ = def_params.params.FirmwareVerMinor + '0'; + + char *pPayload = (char *)buf; + char *p2 = pPayload; + +#if __ENABLE_GPS + //GPS generated payload + if(haveGPSFix) { + strcpy(pPayload, "GPS,"); + strcat(pPayload, GPSLat); + strcat(pPayload, ","); + strcat(pPayload, GPSLong); + strcat(pPayload, ","); + strcat(pPayload, GPSFixTime); + strcat(pPayload, ","); + } else { +#endif + // setup struct generated payload + strcpy(pPayload, "FXD,"); + pPayload += strlen(pPayload); + double dlat = ascii2double(setup_memory.params.setup_data.latitude); + GPSFormat(pPayload, dlat, N_HEMI, S_HEMI); + strcat(pPayload, ","); + pPayload += strlen(pPayload); + double dlong = ascii2double(setup_memory.params.setup_data.longitude); + GPSFormat(pPayload, dlong, E_HEMI, W_HEMI); + strcat(pPayload, ",,"); +#if __ENABLE_GPS + } +#endif + pPayload += strlen(pPayload); + + // Use RTC for time + getTOD(&wallClockTime); + sprintf(pPayload, "%02d%02d%02d", wallClockTime.Hours, wallClockTime.Minutes, wallClockTime.Seconds); + strcat(pPayload, ","); + + // home grid square + strcat(pPayload, setup_memory.params.setup_data.gridSq); + + int pos = strlen((char *)p2); + buf[pos++] = '\0'; // null terminated + + pos += 2*sizeof(uint8_t); // account for firmware field + + // time to send a beacon frame.. + SendBeaconFrame(setup_memory.params.setup_data.stnCall, bcnPayload, pos+1); +} + +/* + * Format the +/-ddd.dddd lat/long format into DDMM.MMMMM format + */ +void GPSFormat(char *buffer, double value, uint8_t hePos, uint8_t heNeg) +{ + // Set hemi, get abs value + uint8_t hemi = value > 0.00 ? hePos : heNeg; + value = fabs(value); + + // separate whole and fractional + int whole = (int)value; + double fract = value - (double)whole; + + // calculate minutes and fraction + double dmin = 60.0 * fract; + dmin = round(dmin * 100.0)/100.0; + + int min = floor(dmin); + int ifract = (int)ceil((dmin-min) * 100000); + + sprintf(buffer, "%d%02d.%05d%c", whole, min, ifract, hemispheres[hemi]); +} + + + +/* + * Process GPS data from LPUART: runs at a higher priority + */ +void GPS_Task_exec(void) +{ +#if __ENABLE_GPS + char c; + int nBytesinBuff; + + if((nBytesinBuff=gpsbuffer_bytesInBuffer()) == 0) + return; + + for(int i=0;i= GPS_BFR_SIZE)) { + *GPSBufPtr = '\0'; + GPSMsgSize++; + memcpy(GPSEchoBuf, GPSMsgBuf, GPSMsgSize); + gpsMessageRx = TRUE; + memcpy(GPSProcBuf, GPSMsgBuf, GPSMsgSize); + memcpy(GPSEchoBuf, GPSMsgBuf, GPSMsgSize); + gpsEchoReady = TRUE; + NMEAState=NMEA_STATE_SOM; + } + break; + } + } +#else + return; // unused code +#endif +} + +#if __ENABLE_GPS +/* + * Send a command to the GPS device to limit traffic + */ +void sendGPSCmd(void) +{ + uint8_t cksum = 0; + char *buf = cmdBuf; + strcpy(buf, nmeaCmd); + buf += strlen(nmeaCmd); + + for(int j=1;j + +#include "types.h" +#include "frame.h" + +#define RADIX_40 40 // alphabet radix + +// Radix 40 callsign alphabet +char alphabet[RADIX_40] = { +// 0 1 2 3 4 5 6 7 8 9 + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + +// 10 11 12 13 14 15 16 17 18 19 + ' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', + +// 20 21 22 23 24 25 26 27 28 29 + 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + +// 30 31 32 33 34 35 36 37 38 39 + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '(', ')', '-' +}; + +// encode a char into the alphabet +uint32_t alphaEncode(char byte) +{ + byte = islower(byte) ? toupper(byte) : byte; + + for(uint32_t i=0;i= 0) && (alpha <= 9)) + return '0' + alpha; + + // special cases: see alphabet table + + switch(alpha) { + + case 10: + return ' '; + + case 37: + return '('; + + case 38: + return ')'; + + case 39: + return '@'; + + default: + return 'A' + (alpha - 11); + } + return ' '; +} + +void EncodeChunk(char *src, int len, uint32_t *enc) +{ + uint32_t chunk=alphaEncode(src[0]); + + // less than 2 characters + if(len < 2) { + *enc = chunk; + return; + } + + // 2 or more + for(int i=1;ibuf; + p += offset; + + if(dest == DEST_CALLSIGN) + frame->dest.ipbytes.encip = ipAddr; + else + frame->source.ipbytes.encip = ipAddr; + + // broadcast address + if(!strcmp(callsign, "FFFF")) { + if(dest == DEST_CALLSIGN) + frame->dest.callbytes.encoded = 0xFFFFFFFF; + else + frame->source.callbytes.encoded = 0xFFFFFFFF; + return 0; + } + + // ensure the callsign is padded out to at least 6 characters + char paddedCall[50]; + strcpy(paddedCall, callsign); + strcat(paddedCall, " "); + + // non-broadcast: break it up into chunks of 6 characters + int nChunks = len/MAX_CALL; + if ((len % MAX_CALL) > 0) + nChunks++; + + char *cll = paddedCall; + + for(int k=0;kdest.callbytes.encoded = encChunk; + else + frame->source.callbytes.encoded = encChunk; + // callsign less than or equal to MAX_CALL + if(nChunks == 1) { + return 0; + } + } else { + *p++ = encChunk; + cll += MAX_CALL; + if(dest == DEST_CALLSIGN) + frame->flagfld.flags.destExt = TRUE; + else + frame->flagfld.flags.srcExt = TRUE; + } + } + *p++ = 0xFF000000; + // return the offset in the buffer + return (p-(uint32_t *)frame->buf) + sizeof(uint32_t); +} + +// decode a callsign +BOOL callDecode(IP400_MAC *encCall, char *callsign, uint16_t *ipAddr) +{ + char tmpBuf[10], *p = tmpBuf; + int i; + + uint32_t encoded = encCall->callbytes.encoded; + for(i=0;i=0;i--) + *callsign++ = tmpBuf[i]; + + *callsign = '\0'; + if(ipAddr != NULL) + *ipAddr = encCall->ipbytes.encip; + + return TRUE; +} diff --git a/Node Firmware/IP400/Src/chat.c b/Node Firmware/IP400/Src/chat.c new file mode 100644 index 0000000..f646649 --- /dev/null +++ b/Node Firmware/IP400/Src/chat.c @@ -0,0 +1,306 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + File Name: chat.c + + Author: MartinA + + Description: This code contains the chat application. It gathers key until the CR + is hit to send a packet. Two internal operations are exit and set the + destination. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) Alberta Digital Radio Communications Society + All rights reserved. + + Revision History: + +---------------------------------------------------------------------------*/ +#include +#include +#include + +#include "setup.h" +#include "frame.h" +#include "types.h" +#include "usart.h" +#include "streambuffer.h" +#include "dataq.h" +#include "ip.h" + +#define MAX_KEY 140 // max keys in a buffer +#define MAX_DEST 20 // max chars in dest callsign +#define BROADCAST "FFFF" // broadcast address + +static char keyBuffer[MAX_KEY]; // buffer for keystrokes +uint8_t keyPos; // current position + +#define KEY_EOL 0x0D // carriage return +#define KEY_RPT 0x12 // change repeat status +#define KEY_EXIT 0x1A // exit key +#define KEY_ESC 0x1B // escape key +#define KEY_DEL 0x7F // delete key +#define KEY_BKSP 0x08 // backspace key +#define KEY_DUMP 0x04 // toggle dump mode + +char dest_call[MAX_DEST]; // broadcast destination +char *dp = dest_call; // pointer to dest call characters +char *entCall = "Enter Destination Callsign"; +char *rptMode[] = { + "Repeat mode->off", + "Repeat mode->on" +}; + +char *dumpStrings[] = { + "Dump mode->off", + "Dump mode->on" +}; + +char *welcome = "Welcome to chat. ESC to set destination, CTRL/R to toggle repeat, CTRL/Z to exit"; + +BOOL destEnt = FALSE; // not entering destination +BOOL repeat = TRUE; // repeating by default +BOOL deleteMode = FALSE; // delete mode +BOOL welcomed = FALSE; // welcome mat is out +BOOL dumpMode = FALSE; + +FRAME_QUEUE chatQueue; // queue for inbound frames + +// fwd refs +void sendLine(char *buffer, int len); +void PrintFrame(IP400_FRAME *FrameBytes); + +// init entry +void Chat_Task_init(void) +{ + strcpy(dest_call, BROADCAST); + destEnt = FALSE; + chatQueue.q_forw = &chatQueue; + chatQueue.q_back = &chatQueue; + keyPos = 0; +} + +void Chat_Task_welcome(void) +{ + // start with welcome message + USART_Print_string("%s\r\n", welcome); + USART_Print_string("%s; ", rptMode[repeat]); + + if(!strcmp(dest_call, BROADCAST)) + USART_Print_string("%s; Destination callsign->(Broadcast)\r\n\n"); + else + USART_Print_string("%s; Destination callsign->%s\r\n\n", dest_call); + + welcomed = TRUE; +} + +/* + * place a frame on the queue frame content is copied + * to alloc'd memory + */ +BOOL EnqueChatFrame(void *raw) +{ + IP400_FRAME *qFrame, *SrcFrame = (IP400_FRAME *)raw; + uint8_t *frameBuffer; + + // if the welcome mat is not out, then discard any pending frames + if(!welcomed) + return FALSE; + + // allocate an IP400 frame + if((qFrame=malloc(sizeof(IP400_FRAME)))== NULL) + return FALSE; + memcpy(qFrame, SrcFrame, sizeof(IP400_FRAME)); + + // alloc the data portion of the frame + if((frameBuffer=malloc(SrcFrame->length)) == NULL) { + return FALSE; + } + + memcpy(frameBuffer, (uint8_t *)SrcFrame->buf, SrcFrame->length); + qFrame->buf = frameBuffer; + qFrame->length = SrcFrame->length; + + if(!enqueFrame(&chatQueue, qFrame)) + return FALSE; + + return TRUE; +} + +/* + * chat task main entry + * return: TRUE if return to main menu is required + * FALSE: more to go + */ +BOOL Chat_Task_exec(void) +{ + if(!welcomed) + Chat_Task_welcome(); + + char c; + int nBytesinBuff; + IP400_FRAME *fr; + + // process any inbound frames first.. + if((fr=dequeFrame(&chatQueue)) != NULL) { + PrintFrame(fr); + free(fr->buf); + free(fr); + } + + if((nBytesinBuff=databuffer_bytesInBuffer()) == 0) + return FALSE; + + for(int i=0;i 0) { + USART_Print_string("%c", keyBuffer[--keyPos]); + } else { + USART_Print_string("\\\r\n"); + deleteMode = FALSE; + } + } + continue; + } else { + // processing a key + + switch (c) { + + // CTRL/R: change repeat flag + case KEY_RPT: + repeat = repeat ? FALSE : TRUE; + char *r = rptMode[repeat]; + USART_Print_string("%s\r\n",r); + break; + + // CTRL/D: change dump mode + case KEY_DUMP: + dumpMode = dumpMode ? FALSE : TRUE; + char *d = dumpStrings[dumpMode]; + USART_Print_string("%s\r\n",d); + break; + + // escape key: get a destination call sign + case KEY_ESC: + if(destEnt) { + strcpy(dest_call, BROADCAST); + USART_Print_string("Destination set to broadcast\r\n"); + destEnt=FALSE; + break; + } + if(keyPos == 0) { + USART_Print_string("%s->", entCall); + dp = dest_call; + destEnt = TRUE; + } + break; + + // EOL key: sent the packet + case KEY_EOL: + USART_Print_string("\r\n"); + if(destEnt) { + int cpyLen = keyPos > MAX_CALL ? MAX_CALL : keyPos; + strncpy(dest_call, keyBuffer, cpyLen); + destEnt = FALSE; + } else { + if(keyPos != 0) { + keyBuffer[keyPos++] = '\0'; + sendLine(keyBuffer, keyPos); + } else { + USART_Print_string(">>>not sent\r\n"); + } + } + keyPos = 0; + break; + + case KEY_EXIT: + welcomed = FALSE; + return TRUE; + + case KEY_DEL: + case KEY_BKSP: + if(keyPos > 0) { + USART_Print_string("\\%c", keyBuffer[--keyPos]); + deleteMode = TRUE; + } + break; + + default: + USART_Send_Char(c); + if(keyPos < MAX_KEY) + keyBuffer[keyPos++] = c; + break; + } + } + } + return FALSE; +} + +// send a line of text +void sendLine(char *buffer, int len) +{ + SendTextFrame(setup_memory.params.setup_data.stnCall, GetIPLowerWord(), dest_call, 0xFFFF, buffer, len, repeat); +} + +/* + * Print a received frame on the console + */ +void PrintFrame(IP400_FRAME *FrameBytes) +{ + char printBuf[250]; + char decCall[100]; + uint16_t dataLen = FrameBytes->length; + SOCKADDR_IN fromIP; + + // dump mode for header debugging + if(dumpMode) { + /* print the received data */ + USART_Print_string("RX - Data received: [ "); + + for(uint8_t i=0; isource, decCall, NULL); + GetIPAddrFromMAC(&FrameBytes->source, &fromIP); + + USART_Print_string("%s(%d.%d.%d.%d) ", decCall, + fromIP.sin_addr.S_un.S_un_b.s_b1, fromIP.sin_addr.S_un.S_un_b.s_b2, + fromIP.sin_addr.S_un.S_un_b.s_b3, fromIP.sin_addr.S_un.S_un_b.s_b4); + + // dest call + if((FrameBytes->dest.callbytes.bytes[0] == BROADCAST_ADDR) && + (FrameBytes->dest.callbytes.bytes[1] == BROADCAST_ADDR)) { + USART_Print_string("BROADCAST"); + } else { + callDecode(&FrameBytes->dest, decCall, NULL); + USART_Print_string("%s", decCall);; + } + + // flags + USART_Print_string("[%d:%04d]:", FrameBytes->flagfld.flags.hop_count, FrameBytes->seqNum); + + // now dump the data + memcpy(printBuf, FrameBytes->buf, dataLen); + printBuf[dataLen] = '\0'; + USART_Print_string("%s\r\n", printBuf); + +} diff --git a/Node Firmware/IP400/Src/dataq.c b/Node Firmware/IP400/Src/dataq.c new file mode 100644 index 0000000..7ff73c9 --- /dev/null +++ b/Node Firmware/IP400/Src/dataq.c @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + File Name: dataq.c + + Author: MartinA + + Description: This module enques and deques an IP400 Frame type on a queue + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) Alberta Digital Radio Communications Society + All rights reserved. + + Revision History: + +---------------------------------------------------------------------------*/ +#include + +#include "types.h" +#include "frame.h" +#include "dataq.h" + +/* + * Enque a frame. The frame data buffer must have + * alredy been allocated + */ +BOOL enqueFrame(FRAME_QUEUE *que, IP400_FRAME *fr) +{ + FRAME_QUEUE *f; + if((f = malloc(sizeof(FRAME_QUEUE))) == NULL) + return FALSE; + + // set the frame buffer + f->frame = fr; + f->length = fr->length + sizeof(IP400_FRAME); + + insque((QUEUE_ELEM *)f, (QUEUE_ELEM *)que->q_back); + return TRUE; + +} + +/* + * Deqeue a frame + * Returns null if no frame is one the queue + * Does NOT dealloc data or frame or q + */ +IP400_FRAME *dequeFrame(FRAME_QUEUE *que) +{ + IP400_FRAME *ipFrame; + + if(que->q_back == que) + return NULL; + + FRAME_QUEUE *f = que->q_forw; + remque((struct qelem *)f); + + ipFrame = f->frame; + + free(f); + return ipFrame; +} diff --git a/Node Firmware/IP400/Src/frame.c b/Node Firmware/IP400/Src/frame.c new file mode 100644 index 0000000..785aabc --- /dev/null +++ b/Node Firmware/IP400/Src/frame.c @@ -0,0 +1,647 @@ +/*--------------------------------------------------------------------------- + Project: IP400 + + Module: Frame transmit and receive tasks + + File Name: frame.c + + Author: MartinA + + Creation Date: Jan 8, 2025 + + Description: Handle the transmission and reception of frames + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) Alberta Digital Radio Communications Society + All rights reserved. + + + Revision History: + +---------------------------------------------------------------------------*/ +#include +#include +#include +#include + +#include +#if _BOARD_TYPE==NUCLEO_BOARD +#include +#endif + +#include +#include +#include + +#include "frame.h" +#include "dataq.h" +#include "setup.h" +#include "tasks.h" +#include "led.h" +#include "spi.h" +#include "usart.h" +#include "ip.h" + +// local defines +#define RX_TIMEOUT 2 // return to OS after 5 ms timeout + +// conditionals +#define __DUMP_BEACON 0 // dump a beacon frame to console using chat + +// locals +uint8_t txState; // transmitter state +uint8_t radioCmd; // current radio command +uint8_t prevCmd; // previous state +FRAME_QUEUE txQueue; // transmitter frame queue +uint32_t subgIRQStatus; // interrupt status +BOOL txDone; // transmitter is done +BOOL rxDone; // rx is done... +BOOL rxReady; // frame ready for further processing +FRAME_STATS Stats; // collected stats +uint32_t nextSeq; // next frame sequence number + +// from setup... +int16_t rxSquelch; // rx squlech + + +// internal fwd references +void QueueTxFrame(IP400_FRAME *txframe); // send a frame +void EnableRx(void); // enable the rx + +// processed frames +static IP400_FRAME rFrame; +static HOPTABLE rxHopTable[MAX_HOP_COUNT]; + +/* + * These are in dedicated buffer memory + */ +#if USE_BUFFER_RAM +static uint8_t rawTxFrame[MAX_FRAME_SIZE] __attribute__((section("BUFFERS"), aligned(4))); +static uint8_t rawRxFrame[MAX_FRAME_SIZE] __attribute__((section("BUFFERS"), aligned(4))); +#else +static uint8_t rawTxFrame[MAX_FRAME_SIZE]; +static uint8_t rawRxFrame[MAX_FRAME_SIZE]; +#endif + +// intialize the transmit task +void Frame_task_init(void) +{ + txState = TX_IDLE; + txQueue.q_forw = &txQueue; + txQueue.q_back = &txQueue; + + // init stats and counters + memset(&Stats, 0, sizeof(FRAME_STATS)); + nextSeq = 0xFFFFFFFF; + + // set Rx threshold + STN_PARAMS *params = GetStationParams(); + rxSquelch = params->radio_setup.rxSquelch; + HAL_MRSubG_SetRSSIThreshold(rxSquelch); + + // enable the interrupt + __HAL_MRSUBG_SET_RFSEQ_IRQ_ENABLE( + MR_SUBG_GLOB_DYNAMIC_RFSEQ_IRQ_ENABLE_RX_OK_E + | MR_SUBG_GLOB_DYNAMIC_RFSEQ_IRQ_ENABLE_TX_DONE_E + | MR_SUBG_GLOB_DYNAMIC_RFSEQ_IRQ_ENABLE_RX_TIMEOUT_E + | MR_SUBG_GLOB_DYNAMIC_RFSEQ_IRQ_ENABLE_RX_CRC_ERROR_E + ); + HAL_NVIC_EnableIRQ(MR_SUBG_IRQn); + + // enable the Rx + rxDone = FALSE; + rxReady = FALSE; + EnableRx(); +} + +/* + * return the stats + */ +FRAME_STATS *GetFrameStats(void) +{ + return &Stats; +} +// return the radio status +uint32_t GetRadioStatus(void) +{ + uint32_t radioStats = READ_REG(MR_SUBG_GLOB_STATUS->RFSEQ_STATUS_DETAIL); + return radioStats; +} +// get the FSM state +uint8_t GetFSMState(void) +{ + uint32_t fsmState = READ_REG(MR_SUBG_GLOB_STATUS->RADIO_FSM_INFO); + return (uint8_t)(fsmState & MR_SUBG_GLOB_STATUS_RADIO_FSM_INFO_RADIO_FSM_STATE_Msk); +} + +/* + * Send a text frame. Buf is not malloc'ed, can be static in ram + */ +BOOL SendTextFrame(char *srcCall, uint16_t srcIPAddr, char *destCall, uint16_t dstIPAddr, char *buf, uint16_t length, BOOL repeat) +{ + IP400_FRAME *txFrame; + + if((txFrame=malloc(sizeof(IP400_FRAME))) == NULL) + return FALSE; + + if((txFrame->buf=malloc(length + MAX_CALL_BUFFER)) == NULL) + return FALSE; + + txFrame->flagfld.allflags = 0; // start with all flags cleared + + // format the header + uint8_t offset = callEncode(srcCall, srcIPAddr, txFrame, SRC_CALLSIGN, 0); + offset = callEncode(destCall, dstIPAddr, txFrame, DEST_CALLSIGN, offset); + + // copy the payload in + uint8_t *f = (uint8_t *)txFrame->buf; + f += offset*sizeof(uint32_t); + memcpy(f, (const uint8_t *)buf, length); + + txFrame->length = length + offset; + txFrame->flagfld.flags.hop_count = 0; + txFrame->flagfld.flags.coding |= UTF8_TEXT_PACKET; + txFrame->flagfld.flags.repeat = repeat; + txFrame->flagfld.flags.hoptable = 0; + txFrame->hopTable = NULL; + txFrame->seqNum = nextSeq++; + + QueueTxFrame(txFrame); + + return TRUE; +} + +/* + * compose and send a beacon frame (ping frame with broadcast destination + */ +void SendBeaconFrame(char *srcCall, uint8_t *payload, int bcnlen) +{ + IP400_FRAME *bcnFrame; + + if((bcnFrame=malloc(sizeof(IP400_FRAME))) == NULL) + return; + + if((bcnFrame->buf=malloc(bcnlen + MAX_CALL_BUFFER)) == NULL) + return; + + bcnFrame->flagfld.allflags = 0; // start with all flags cleared + + // broadcast frame + uint16_t ipLower = GetIPLowerWord(); + uint8_t offset = callEncode(srcCall, ipLower, bcnFrame, SRC_CALLSIGN, 0); + callEncode("FFFF", 0xFFFF, bcnFrame, DEST_CALLSIGN, 0); + + // adjust starting data point, add payload + uint8_t *f = (uint8_t *)bcnFrame->buf; + f += offset*sizeof(uint32_t); + memcpy(f, payload, bcnlen); + + bcnFrame->length = bcnlen + offset; + bcnFrame->flagfld.flags.hop_count = 0; + bcnFrame->flagfld.flags.coding |= BEACON_PACKET; + bcnFrame->flagfld.flags.repeat = TRUE; + bcnFrame->flagfld.flags.hoptable = 0; + bcnFrame->hopTable = NULL; + bcnFrame->seqNum = nextSeq++; + + QueueTxFrame(bcnFrame); +} + +void SendSPIFrame(void *spi, uint8_t *payload, int len) +{ + IP400_FRAME *spiFrame; + + SPI_HEADER *spiHdr = (SPI_HEADER *)spi; + + // validate the frame type + switch(spiHdr->coding) { + + // beacon packets are not sent + case BEACON_PACKET: + return; + + // all others are + } + + // allocate memory for the SPI frame and buffer + if((spiFrame=malloc(sizeof(IP400_FRAME))) == NULL) + return; + if((spiFrame->buf=malloc(len)) == NULL) { + free(spiFrame); + return; + } + + // hop table is in the first part of the payload buffer + uint16_t nHops = spiHdr->hopCount; + uint16_t htblSize = nHops * sizeof(HOPTABLE); + HOPTABLE *hTable; + if(nHops) { + if((hTable=malloc(htblSize)) == NULL) { + free(spiFrame->buf); + free(spiFrame); + return; + } + memcpy(hTable, payload, htblSize); + payload += htblSize; + len -= htblSize; + spiFrame->hopTable = (void *)hTable; + } + + // add in source and destination + uint8_t *srcCall; + IP400_MAC *myMac; + GetMyMAC(&myMac); + srcCall = myMac->callbytes.bytes; + + memcpy(spiFrame->source.callbytes.bytes, srcCall, N_CALL); + memcpy(spiFrame->source.ipbytes.ip, spiHdr->fromIP, N_IPBYTES); + + memcpy(spiFrame->dest.callbytes.bytes, spiHdr->toCall, N_CALL); + memcpy(spiFrame->source.ipbytes.ip, spiHdr->toIP, N_IPBYTES); + + spiFrame->flagfld.allflags = (uint16_t)(spiHdr->hopCount) + (uint16_t)(spiHdr->coding<<4) + (uint16_t)(spiHdr->flags<<8); + if(spiFrame->flagfld.flags.hop_count) + spiFrame->flagfld.flags.hoptable = TRUE; + spiFrame->seqNum = nextSeq++; + + memcpy(spiFrame->buf, payload, len); + + QueueTxFrame(spiFrame); + +} + +// check to see if a frame came from me +// returns true if I originated the frame +BOOL FrameisMine(IP400_FRAME *frame) +{ + char decCall[30]; + + // check if I am the originator call sign + callDecode(&frame->source, decCall, NULL); + if(CompareToMyCall(decCall)) + return TRUE; + + // now check to see if I repeated this frame + if(frame->flagfld.flags.hoptable == 0) + return FALSE; + + // check my call is in the table + HOPTABLE *htable = (HOPTABLE *)frame->hopTable; + IP400_MAC *myMac; + GetMyMAC(&myMac); + for(int i=0;iflagfld.flags.hop_count; i++) + if(htable[i].hopAddr.callbytes.encoded == myMac->callbytes.encoded) + return TRUE; + + return FALSE; +} + +/* + * Repeat a frame. Do not send it if we were the originator + * Inbound frame is static, not malloc'd + */ +void RepeatFrame(IP400_FRAME *frame) +{ + IP400_FRAME *rptFrame; + + // copy the frame + if((rptFrame=malloc(sizeof(IP400_FRAME))) == NULL) + return; + memcpy(rptFrame, frame, sizeof(IP400_FRAME)); + + // copy the data + rptFrame->length = frame->length; + if((rptFrame->buf=malloc(frame->length)) == NULL) { + free(rptFrame); + return; + } + memcpy(rptFrame->buf, frame->buf, frame->length); + + // allocate a new hop table + uint8_t hopCount= frame->flagfld.flags.hop_count; + if((rptFrame->hopTable=malloc(sizeof(HOPTABLE)*(hopCount+1))) == NULL) { + free(rptFrame->buf); + free(rptFrame); + } + + // copy the existing one and add me to the end of it + if(frame->flagfld.flags.hoptable) { + memcpy(rptFrame->hopTable, frame->hopTable, sizeof(HOPTABLE)*hopCount); + } + + // build a new entry + HOPTABLE *table = (HOPTABLE *)rptFrame->hopTable; + IP400_MAC *myMac; + GetMyMAC(&myMac); + table[hopCount].hopAddr.callbytes.encoded = myMac->callbytes.encoded; + table[hopCount].hopAddr.ipbytes.encip = myMac->ipbytes.encip; + rptFrame->flagfld.flags.hoptable = TRUE; + rptFrame->flagfld.flags.hop_count = hopCount + 1; + + Stats.nRepeated++; + QueueTxFrame(rptFrame); +} + +/* + * queue a frame for transmission by the tx task + */ +void QueueTxFrame(IP400_FRAME *txframe) +{ + enqueFrame(&txQueue, txframe); +} + +/* + * Enable the receiver + */ +void EnableRx(void) +{ + __HAL_MRSUBG_SET_RX_MODE(RX_NORMAL); + __HAL_MRSUBG_SET_DATABUFFER_SIZE(MAX_FRAME_SIZE); + MR_SUBG_GLOB_STATIC->DATABUFFER0_PTR = (uint32_t)&rawRxFrame; + + // set the command + radioCmd = CMD_RX; + __HAL_MRSUBG_STROBE_CMD(radioCmd); + + SetLEDMode(BICOLOR_GREEN); +} + +/* + * main entry for tx task. Pick frames from the transmit queue + */ +void Frame_Txtask_exec(void) +{ + static IP400_FRAME *tFrame; + int frameLen = 0; + + switch(txState) { + + // idle: waiting for work + case TX_IDLE: + + tFrame = dequeFrame(&txQueue); + uint8_t *rawFrame = (uint8_t *)rawTxFrame; + + if(tFrame == NULL) + return; + + frameLen = tFrame->length; + + /* + * Build the raw frame bytes: see IP400_FRAME struct + */ + // Source call + port (6 bytes) + memcpy(rawFrame, (uint8_t *)&tFrame->source, IP_400_CALL_SIZE); + rawFrame += IP_400_CALL_SIZE; + // Dest call + port (6 bytes) + memcpy(rawFrame, (uint8_t *)&tFrame->dest, IP_400_CALL_SIZE); + rawFrame += IP_400_CALL_SIZE; + // flag byte (2 byte) + memcpy(rawFrame, (uint8_t *)&tFrame->flagfld, IP_400_FLAG_SIZE); + rawFrame += IP_400_FLAG_SIZE; + // frame sequence number (4 bytes) + memcpy(rawFrame, (uint32_t *)&tFrame->seqNum, sizeof(uint32_t)); + rawFrame += sizeof(uint32_t); + // frame length (2 bytes) + memcpy(rawFrame, (uint8_t *)&tFrame->length, sizeof(uint16_t)); + rawFrame += IP_400_LEN_SIZE; + + // add in the hop table + if(tFrame->flagfld.flags.hoptable) { + uint16_t hopLen = (uint16_t)(tFrame->flagfld.flags.hop_count) * sizeof(HOPTABLE); + memcpy(rawFrame, (uint8_t *)(tFrame->hopTable), hopLen); + rawFrame += hopLen; + free(tFrame->hopTable); + } + + // and now the data... + if((tFrame->buf != NULL) && (tFrame->length != 0)) + memcpy(rawFrame, tFrame->buf, tFrame->length); + + // free the allocations in the reverse order... + if(tFrame->buf != NULL) + free(tFrame->buf); + + free(tFrame); + + // ensure packet length is a multiple of 4 bytes + int pktLen = (rawFrame - rawTxFrame) + frameLen; + pktLen += (pktLen % 4); + + HAL_MRSubG_PktBasicSetPayloadLength(frameLen + pktLen); + + // abort the current rx operation + if(radioCmd == CMD_RX) { + __HAL_MRSUBG_STROBE_CMD(CMD_SABORT); + uint32_t reject=0, abortDone=0; + do { + subgIRQStatus = READ_REG(MR_SUBG_GLOB_STATUS->RFSEQ_IRQ_STATUS); + reject = subgIRQStatus & MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_COMMAND_REJECTED_F; + abortDone = subgIRQStatus & MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_SABORT_DONE_F; + } while ((abortDone == 0) && (reject == 0)); + if(abortDone) + __HAL_MRSUBG_CLEAR_RFSEQ_IRQ_FLAG(MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_SABORT_DONE_F); + if(reject) + __HAL_MRSUBG_CLEAR_RFSEQ_IRQ_FLAG(MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_COMMAND_REJECTED_F); + } + + __HAL_MRSUBG_SET_DATABUFFER0_POINTER((uint32_t)rawTxFrame); + __HAL_MRSUBG_SET_TX_MODE(TX_NORMAL); + + txDone = FALSE; + prevCmd = radioCmd; + radioCmd = CMD_TX; + __HAL_MRSUBG_STROBE_CMD(radioCmd); + + // set tx indication: bicolor off and Tx on + SetLEDMode(BICOLOR_OFF); + SetLEDMode(TX_LED_ON); + + txState = TX_SENDING; + break; + + // sending a frame + case TX_SENDING: + // still busy sending + if(!txDone) + return; + + txState = TX_DONE; + break; + + // done + case TX_DONE: + // restart the receiver, if needed + EnableRx(); + + SetLEDMode(TX_LED_OFF); + txState = TX_IDLE; + break; + } +} + +/* + * Main entry of the rx task + */ +void Frame_Rxtask_exec(void) +{ + // wait for completion.. + if(!rxDone) + return; + rxDone = FALSE; + + uint8_t *RxRaw = rawRxFrame; + uint8_t *cpyDest; + uint32_t rawLength = __HAL_MRSUBG_GET_DATABUFFER_SIZE(); + + /* + * Do the opposite of the transmitter... + */ + // Source call + port (6 bytes) + cpyDest = (uint8_t *)&rFrame.source.callbytes.bytes; + memcpy(cpyDest, RxRaw, IP_400_CALL_SIZE); + RxRaw += IP_400_CALL_SIZE; + + // Dest call + port (6 bytes) + cpyDest = (uint8_t *)&rFrame.dest.callbytes.bytes; + memcpy(cpyDest, RxRaw, IP_400_CALL_SIZE); + RxRaw += IP_400_CALL_SIZE; + + // flag byte (2 byte) + cpyDest = (uint8_t *)&rFrame.flagfld.allflags; + memcpy(cpyDest, RxRaw, IP_400_FLAG_SIZE); + RxRaw += IP_400_FLAG_SIZE; + + // frame sequence number (4 bytes) + cpyDest = (uint8_t *)&rFrame.seqNum; + memcpy(cpyDest, RxRaw, sizeof(uint32_t)); + RxRaw += sizeof(uint32_t); + + // frame length (2 bytes) + cpyDest = (uint8_t *)&rFrame.length; + memcpy(cpyDest, RxRaw, sizeof(uint16_t)); + RxRaw += IP_400_LEN_SIZE; + + // copy the hop table + uint8_t nHops = rFrame.flagfld.flags.hop_count; + if(nHops != 0) { + uint16_t hopLen = (uint16_t)nHops*sizeof(HOPTABLE); + memcpy(rxHopTable, RxRaw, hopLen); + RxRaw += hopLen; + rFrame.hopTable = rxHopTable; + } else { + rFrame.hopTable = NULL; + } + + rFrame.buf = RxRaw; + + // find a reason to reject a frame... + BOOL isMine = FrameisMine(&rFrame); + + // process the frame if it is not mine and unique + // do a sanity check on the length + if(!isMine && (rFrame.length < rawLength)) { + + IP400FrameType frameType = rFrame.flagfld.flags.coding; + + switch(frameType) { + + // process a beacon frame + case BEACON_PACKET: + if(Mesh_Accept_Frame((void *)&rFrame, Stats.lastRSSI)) { + Mesh_ProcessBeacon((void *)&rFrame, Stats.lastRSSI); +#if __DUMP_BEACON + EnqueChatFrame((void *)&rFrame); +#endif + EnqueSPIFrame(&rFrame); + Stats.nBeacons++; + } + break; + + // process a local chat frame + case UTF8_TEXT_PACKET: + if(Mesh_Accept_Frame((void *)&rFrame, Stats.lastRSSI)) { + EnqueChatFrame((void *)&rFrame); + Stats.framesOK++; + } + break; + + // frames passed on to the host + case COMPRESSED_AUDIO: // compressed audio packet + case COMPREESSD_VIDEO: // compressed video packet + case DATA_PACKET: // data packet + case IP_ENCAPSULATED: // IP encapsulated packet + case AX_25_PACKET: // AX.25 encapsulated packet + case RFC4733_DTMF: // DTMF packet + case DMR_FRAME: // DMR Frame + case DSTAR_FRAME: // Dstar Frame + case P25_FRAME: // TIA project 25 + case NXDN_FRAME: // NXDN + case M17_FRAME: // M17 + if(Mesh_Accept_Frame((void *)&rFrame, Stats.lastRSSI)) { + EnqueSPIFrame((void *)&rFrame); + Stats.framesOK++; + } + break; + + //reserved for future use + case LOCAL_COMMAND: // local command frame + break; + + default: + Stats.dropped++; + logger(LOG_ERROR, "Frame Received with unknown coding: %d\r\n", rFrame.flagfld.flags.coding); + break; + } + } + + // repeat the frame if the repeat flag is set and the hop count is not exhausted + if(!isMine) { + if(rFrame.flagfld.flags.repeat && (rFrame.flagfld.flags.hop_count < MAX_HOP_COUNT)) + RepeatFrame(&rFrame); + } + + if(isMine) + Stats.dropped++; + + // restart the receiver + EnableRx(); + +} +// frame interrupt callback +void HAL_MRSubG_IRQ_Callback(void) +{ + subgIRQStatus = READ_REG(MR_SUBG_GLOB_STATUS->RFSEQ_IRQ_STATUS); + + // Process transmitter interrupts + if(subgIRQStatus & MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_TX_DONE_F) { + __HAL_MRSUBG_CLEAR_RFSEQ_IRQ_FLAG(MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_TX_DONE_F); + Stats.TxFrameCnt++; + txDone = TRUE; + } + + // process receiver interrupts + if(subgIRQStatus & MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_RX_CRC_ERROR_F) { + Stats.CRCErrors++; + __HAL_MRSUBG_CLEAR_RFSEQ_IRQ_FLAG(MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_RX_CRC_ERROR_F); + } + + if (subgIRQStatus & MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_RX_TIMEOUT_F) { + Stats.TimeOuts++; + __HAL_MRSUBG_CLEAR_RFSEQ_IRQ_FLAG(MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_RX_TIMEOUT_F); + } + + if (subgIRQStatus & MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_RX_OK_F ) { + Stats.RxFrameCnt++; + __HAL_MRSUBG_CLEAR_RFSEQ_IRQ_FLAG(MR_SUBG_GLOB_STATUS_RFSEQ_IRQ_STATUS_RX_OK_F); + Stats.lastRSSI = READ_REG_FIELD(MR_SUBG_GLOB_STATUS->RX_INDICATOR, MR_SUBG_GLOB_STATUS_RX_INDICATOR_RSSI_LEVEL_ON_SYNC); + rxDone = TRUE; + } + + EnableRx(); +} diff --git a/Node Firmware/IP400/Src/insque.c b/Node Firmware/IP400/Src/insque.c new file mode 100644 index 0000000..814df9a --- /dev/null +++ b/Node Firmware/IP400/Src/insque.c @@ -0,0 +1,48 @@ +/* insque(3C) routines + This file is in the public domain. */ + +/* + +@deftypefn Supplemental void insque (struct qelem *@var{elem}, @ + struct qelem *@var{pred}) +@deftypefnx Supplemental void remque (struct qelem *@var{elem}) + +Routines to manipulate queues built from doubly linked lists. The +@code{insque} routine inserts @var{elem} in the queue immediately +after @var{pred}. The @code{remque} routine removes @var{elem} from +its containing queue. These routines expect to be passed pointers to +structures which have as their first members a forward pointer and a +back pointer, like this prototype (although no prototype is provided): + +@example +struct qelem @{ + struct qelem *q_forw; + struct qelem *q_back; + char q_data[]; +@}; +@end example + +@end deftypefn + +*/ + +struct qelem { + struct qelem *q_forw; + struct qelem *q_back; +}; + + +void insque (struct qelem *elem, struct qelem *pred) +{ + elem -> q_forw = pred -> q_forw; + pred -> q_forw -> q_back = elem; + elem -> q_back = pred; + pred -> q_forw = elem; +} + + +void remque (struct qelem *elem) +{ + elem -> q_forw -> q_back = elem -> q_back; + elem -> q_back -> q_forw = elem -> q_forw; +} diff --git a/Node Firmware/IP400/Src/ip.c b/Node Firmware/IP400/Src/ip.c new file mode 100644 index 0000000..ba72e06 --- /dev/null +++ b/Node Firmware/IP400/Src/ip.c @@ -0,0 +1,163 @@ +/*--------------------------------------------------------------------------- + Project: IP400 + + Module: Calculate IP address for a node + + File Name: ip.c + + Author: MartinA + + Creation Date: Mar 3, 2025 + + Description: Calculate the IP address from an IP400 Frame structure + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) Alberta Digital Radio Communications Society + All rights reserved. + + + Revision History: + +---------------------------------------------------------------------------*/ +#include "types.h" +#include "frame.h" +#include "ip.h" +#include "setup.h" + +#define AF_INET 2 // internet address family +#define IP_10_NETWORK 10 // ip network address +#define IP_172_NETWORK 172 // alt network address +#define IP_172_START 16 // start of 172 range + +uint8_t netmask10[] = { + 0xFF, 0xFF, 0xFF, 0xF8 +}; + +uint8_t netmask172[] = { + 0xFF, 0x30, 0xFF, 0xFF +}; + +// unique ID +union { + uint8_t bytes[4]; + uint16_t halfwords[2]; + uint32_t word; +} uniqueID; + +#define NETMASK 0xF8 // mask for last byte + +/* + * Create an IP 10.x.x.x from the compressed callsign field + */ +void GetIP10Addr(IP400_MAC *fr, SOCKADDR_IN *ipaddr) +{ + ipaddr->sin_family = AF_INET; + + // first byte is fixed + ipaddr->sin_addr.S_un.S_un_b.s_b1 = IP_10_NETWORK & netmask10[0]; + + // next two are sums of callsign bytes + ipaddr->sin_addr.S_un.S_un_b.s_b2 = (fr->callbytes.bytes[0] ^ fr->callbytes.bytes[2]) & netmask10[1]; + ipaddr->sin_addr.S_un.S_un_b.s_b3 = (fr->callbytes.bytes[1] ^ fr->callbytes.bytes[3]) & netmask10[2]; + + // last digit from sum + int macsum = 0; + for (int i = 0; i < N_CALL; i++) + macsum += fr->callbytes.bytes[i]; + ipaddr->sin_addr.S_un.S_un_b.s_b4 = (uint8_t)(macsum & netmask10[3]); + + // port number from source + ipaddr->sin_port = 0; +} + + +void GetIP172Addr(IP400_MAC *fr, SOCKADDR_IN *ipaddr) +{ + ipaddr->sin_family = AF_INET; + ipaddr->sin_addr.S_un.S_addr = 0; + + // first byte is fixed + ipaddr->sin_addr.S_un.S_un_b.s_b1 = IP_172_NETWORK & netmask172[0]; + + // next two are sums of callsign bytes + uint8_t b3 = (fr->callbytes.bytes[0] ^ fr->callbytes.bytes[2]) & netmask172[2]; + uint8_t b4 = (fr->callbytes.bytes[1] ^ fr->callbytes.bytes[3]) & netmask172[3]; + uint8_t b2 = (b3 + b4) & 0xf; + + // compose the address + ipaddr->sin_addr.S_un.S_un_b.s_b2 = b2 + IP_172_START; + ipaddr->sin_addr.S_un.S_un_b.s_b3 = b3; + ipaddr->sin_addr.S_un.S_un_b.s_b4 = b4; + + // port number from source + ipaddr->sin_port = 0; +} + +/* + * Create an IP 172.16.x.x from the compressed callsign field and unique ID + */ +void Get172AddrFromID(IP400_MAC *fr, SOCKADDR_IN *ipaddr) +{ + ipaddr->sin_family = AF_INET; + ipaddr->sin_addr.S_un.S_addr = 0; + + uniqueID.word = GetDevID0() ^ GetDevID1(); + + // first is fixed + ipaddr->sin_addr.S_un.S_un_b.s_b1 = IP_172_NETWORK & netmask172[0]; + + // second byte from the callsign data + uint8_t b3 = (fr->callbytes.bytes[0] ^ fr->callbytes.bytes[2]) & netmask172[2]; + uint8_t b4 = (fr->callbytes.bytes[1] ^ fr->callbytes.bytes[3]) & netmask172[3]; + uint8_t b2 = (b3 + b4) & 0xf; + ipaddr->sin_addr.S_un.S_un_b.s_b2 = b2 + IP_172_START; + + + // remainder from the unique ID + ipaddr->sin_addr.S_un.S_un_b.s_b3 = uniqueID.bytes[1]; + ipaddr->sin_addr.S_un.S_un_b.s_b4 = uniqueID.bytes[0]; + + // port number from source + ipaddr->sin_port = 0; +} + +/* + * return the IP Address in an IP_400 MAC + */ +void GetIPAddrFromMAC(IP400_MAC *fr, SOCKADDR_IN *ipAddr) +{ + ipAddr->sin_family = AF_INET; + ipAddr->sin_addr.S_un.S_addr = 0; + + // first is fixed + ipAddr->sin_addr.S_un.S_un_b.s_b1 = IP_172_NETWORK & netmask172[0]; + + // second byte from the callsign data + uint8_t b3 = (fr->callbytes.bytes[0] ^ fr->callbytes.bytes[2]) & netmask172[2]; + uint8_t b4 = (fr->callbytes.bytes[1] ^ fr->callbytes.bytes[3]) & netmask172[3]; + uint8_t b2 = (b3 + b4) & 0xf; + ipAddr->sin_addr.S_un.S_un_b.s_b2 = b2 + IP_172_START; + + // remainder from the ip Address field + ipAddr->sin_addr.S_un.S_un_b.s_b3 = fr->ipbytes.ip[1]; + ipAddr->sin_addr.S_un.S_un_b.s_b4 = fr->ipbytes.ip[0]; + + // port number from source + ipAddr->sin_port = 0; + + +} + +uint16_t GetIPLowerWord(void) +{ + uniqueID.word = GetDevID0() ^ GetDevID1(); + + return uniqueID.halfwords[0]; +} + + diff --git a/Node Firmware/IP400/Src/led.c b/Node Firmware/IP400/Src/led.c new file mode 100644 index 0000000..a6b6cdf --- /dev/null +++ b/Node Firmware/IP400/Src/led.c @@ -0,0 +1,277 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + File Name: led.c + + Author: MartinA + + Description: Handler for the LED's on nucleo or PI boards + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) Alberta Digital Radio Communications Society + All rights reserved. + + Revision History: + +---------------------------------------------------------------------------*/ +/* + * notes for different implementations. + * On the Pi there are two LED's a dedicated red and a bicolor red-green. + * TX: On when transmitting + * BICOLOR: Green solid when Rx is enabled + * Red solid when HAL error or NMI occurred + * + * On the NUCLEO board, there are three, red, green and blue + * BLUE (LD1) Duplicats the TX LED + * GREEN (LD2) Duplicates the GREEN bi-color function + * RED (LD3) Duplicates the RED Bi-color function + */ + +#include "types.h" +#include "led.h" +#include "usart.h" +#include + +// include LED defs from the right place +#if _BOARD_TYPE == NUCLEO_BOARD +#include +#endif + + +// local defines +#define FLASH_OFF 0 // LED flashing: off state +#define FLASH_ON 1 // LED flashing: on state + +// timer for 1/2 second when flashing +#define LED_TIMER 5 // timer +#define TEST_TIMER 20 // test timer + +// internals +void SetLEDMode(uint8_t mode); +void LED_SetOff(void); +void LED_SetRed(void); +void LED_SetGreen(void); +void LED_SetError(void); +void setTxLED(BOOL state); + +// vars +BOOL ToggleEnable; // led toggling enabled +uint8_t ledColour; // direction (colour) +uint8_t ledMode; // led mode +uint8_t ledState; // state on/off +uint8_t ledTimer; // timer for led test +uint8_t testNum; // test number +uint8_t testTimer; // test timer +uint8_t saveMode; // saved mode + +// +// Notes on timer setup for STM32H732 +// Prescaler value of 0 allows 64MHz clock to drive timer +// 1 reduces it to 32MHz +// Period divider of 6400 yeilds 100uSec time base +// +#define N_LED 5 +struct led_tests_t { + char *testName; + uint8_t testMode; +} LEDTests[N_LED] = { + {"Bicolor RED On", BICOLOR_RED }, + {"Bicolor GREEN On", BICOLOR_GREEN }, + {"Bicolor off", BICOLOR_OFF }, + {"Tx LED On", TX_LED_ON }, + {"Tx LED Off", TX_LED_OFF } +}; +// Initialization +void Led_Task_Init(void) +{ + ToggleEnable = FALSE; + ledColour = BICOLOR_RED; + ledState = BICOLOR_OFF; + ledTimer = LED_TIMER; + LED_SetOff(); + setTxLED(FALSE); + testTimer = 0; + testNum = 0; +} + +// We handle the flashing here... +// for the bicolor led, we can be solid red, solid green, +// or red-green flashing +void Led_Task_Exec(void) +{ + + if(ToggleEnable) { + + if(ledTimer != 0) { + ledTimer--; + return; + } + + ledTimer--; + + // blink LED if enabled + switch(ledMode) { + + case BICOLOR_RED_FLASH: + case BICOLOR_GREEN_FLASH: + ledState = (ledState == FLASH_ON) ? FLASH_OFF : FLASH_ON; + break; + + case BICOLOR_RED_GREEN: + ledState = (ledState == FLASH_ON) ? FLASH_OFF : FLASH_ON; + ledColour = (ledColour == BICOLOR_RED) ? BICOLOR_GREEN : BICOLOR_RED; + break; + } + + // update the LED state + if(ledState == FLASH_OFF) { + LED_SetOff(); + } else { + if(ledColour == BICOLOR_RED) + LED_SetRed(); + else + LED_SetGreen(); + } + } +} + +// led test mode +BOOL LedTest(void) +{ + + if(testTimer == 0) { + if(testNum == 0) + saveMode = ledMode; + if(testNum == N_LED) { + testNum = 0; + SetLEDMode(saveMode); + return TRUE; + } + + USART_Print_string("%s\r\n", LEDTests[testNum].testName); + SetLEDMode(LEDTests[testNum].testMode); + testTimer = TEST_TIMER; + testNum++; + return FALSE; + } + + testTimer--; + return FALSE; +} + +// API routines: set the LED mode +void SetLEDMode(uint8_t mode) +{ + ledMode = mode; + + switch(ledMode) { + + case BICOLOR_OFF: + LED_SetOff(); + ToggleEnable = FALSE; + ledState = FLASH_OFF; + break; + + case BICOLOR_RED: + LED_SetRed(); + ToggleEnable = FALSE; + ledColour = BICOLOR_RED; + ledState = FLASH_ON; + break; + + case BICOLOR_RED_FLASH: + LED_SetRed(); + ToggleEnable = TRUE; + ledColour = BICOLOR_RED; + ledState = FLASH_ON; + break; + + case BICOLOR_GREEN: + LED_SetGreen(); + ToggleEnable = FALSE; + ledColour = BICOLOR_GREEN; + ledState = FLASH_ON; + break; + + case BICOLOR_GREEN_FLASH: + LED_SetGreen(); + ToggleEnable = TRUE; + ledColour = BICOLOR_GREEN; + ledState = FLASH_ON; + break; + + case BICOLOR_RED_GREEN: + LED_SetRed(); + ToggleEnable = TRUE; + ledColour = BICOLOR_RED; + ledState = FLASH_ON; + break; + + case TX_LED_ON: + setTxLED(TRUE); + break; + + case TX_LED_OFF: + setTxLED(FALSE); + break; + + } +} + +// do the actual updates +void LED_SetOff(void) +{ +#if _BOARD_TYPE == NUCLEO_BOARD + BSP_LED_Off(LED_RED); + BSP_LED_Off(LED_GREEN); + BSP_LED_Off(LED_BLUE); +#else + HAL_GPIO_WritePin(TXLED_GPIO_Port, TXLED_Pin, GPIO_PIN_RESET); + HAL_GPIO_WritePin(LED_Green_GPIO_Port, LED_Green_Pin, GPIO_PIN_RESET); + HAL_GPIO_WritePin(LED_Red_GPIO_Port, LED_Red_Pin, GPIO_PIN_RESET); +#endif +} + +void setTxLED(BOOL state) +{ +#if _BOARD_TYPE == NUCLEO_BOARD + if(state) + BSP_LED_On(LED_BLUE); + else + BSP_LED_Off(LED_BLUE); +#else + if(state) + HAL_GPIO_WritePin(TXLED_GPIO_Port, TXLED_Pin, GPIO_PIN_SET); + else + HAL_GPIO_WritePin(TXLED_GPIO_Port, TXLED_Pin, GPIO_PIN_RESET); +#endif +} +// set green direction for bidir LED +void LED_SetGreen(void) +{ +#if _BOARD_TYPE == NUCLEO_BOARD + BSP_LED_On(LED_GREEN); +#else + HAL_GPIO_WritePin(LED_Green_GPIO_Port, LED_Green_Pin, GPIO_PIN_SET); + HAL_GPIO_WritePin(LED_Red_GPIO_Port, LED_Red_Pin, GPIO_PIN_RESET); +#endif +} + +// set red direction for bidir LED +void LED_SetRed(void) +{ +#if _BOARD_TYPE == NUCLEO_BOARD + BSP_LED_On(LED_RED); +#else + HAL_GPIO_WritePin(LED_Green_GPIO_Port, LED_Green_Pin, GPIO_PIN_RESET); + HAL_GPIO_WritePin(LED_Red_GPIO_Port, LED_Red_Pin, GPIO_PIN_SET); +#endif +} + + + diff --git a/Node Firmware/IP400/Src/logger.c b/Node Firmware/IP400/Src/logger.c new file mode 100644 index 0000000..ad6bbde --- /dev/null +++ b/Node Firmware/IP400/Src/logger.c @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------- + Project: MMDLTX + + Module: Logger + + File Name: audio.c + + Author: MartinA + + Revision: 1.00 + + Description: Log an error message. On the nucleo, we send it out to the + console UAR/T, on the E04, it goes out to the LPUART which + is connected to the VCOM port. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) Alberta Digital Radio Communications Society + All rights reserved. + + + Revision History: + +---------------------------------------------------------------------------*/ +#include +#include + +#include "usart.h" + +char errmsg[200]; + +// need to figure out how to talk to UART on board... +void logger(int severity, char *format, ...) +{ + + // process the arg list + va_list argptr; + va_start(argptr, format); + vsprintf(errmsg,format, argptr); + va_end(argptr); + + switch(severity) { + + case LOG_NOTICE: + USART_Print_string("Notice: %s\r\n", errmsg); + break; + + case LOG_ERROR: + USART_Print_string("Notice: %s\r\n", errmsg); + break; + + case LOG_SEVERE: + USART_Print_string("Notice: %s\r\n", errmsg); + break; + } + +} + + diff --git a/Node Firmware/IP400/Src/menu.c b/Node Firmware/IP400/Src/menu.c new file mode 100644 index 0000000..8429eff --- /dev/null +++ b/Node Firmware/IP400/Src/menu.c @@ -0,0 +1,845 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + File Name: menu.c + + Author: MartinA + + Description: Menu handler + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) Alberta Digital Radio Communications Society + All rights reserved. + + Revision History: + +---------------------------------------------------------------------------*/ +#include +#include + +#include "types.h" +#include "usart.h" +#include "streambuffer.h" +#include "setup.h" +#include "tasks.h" +#include "utils.h" +#include "tod.h" +#include "config.h" +#include "ip.h" + +// menu state +uint8_t menuState; // menu state +enum { + MENU_OFF, // off + MENU_SHOWING, // showing the menu + MENU_SELECTING, // getting a selection + MENU_SELECTED, // item has been selected + MENU_PAUSED // pausing a while... +}; + +// key entry state +uint8_t entryState; // state of keyboard entry +enum { + NO_ENTRY=0, // no entry yet.. + ENTERING, // entering a value + VALIDATING, // validating the entry +}; + +// menu items +#define NO_ITEM -1 // no item selected +#define __MEM_DEBUG 1 // memory debug + +// DEC VT100 Escape sequences +#define ASCII_TAB 0x09 +#define ASCII_RET 0x0D +#define ASCII_ESC 0x1B // escape char +#define DEC_LEADIN '[' // lead-in character +#define MAX_SEQ 6 // max sequence length + +// control codes.. +enum { + CONTROL_CLEAR=0, // clear screen + CONTROL_HOME, // cursor home + CONTROL_DOUBLE_TOP, // double wide and height (top) + CONTROL_DOUBLE_BOTTOM, // double wide and height (bottom) + CONTROL_SINGLE, // single wide and height + NCONTROL_CODES // number of conrol codes +}; + +struct controlCodes_t { + char sequence[MAX_SEQ]; // sequence + uint8_t len; // length +} controlCodes[NCONTROL_CODES] = { + {{ASCII_ESC, DEC_LEADIN, '2', 'J'}, 4}, + {{ASCII_ESC, DEC_LEADIN, 'H'}, 3}, + {{ASCII_ESC, '#', '3'}, 3}, + {{ASCII_ESC, '#', '4'}, 3}, + {{ASCII_ESC, '#', '5'}, 3}, +}; + +// strings common to all menu types +char *whatItem = "??Try again->"; +char *menuSpacer = "\r\n\n"; +char *pauseString = "Hit enter to continue->"; +char *selectItem = "Select an item->"; + +static char menu[100]; // Buffer for file items +int sel_item = 0; // selected item +uint8_t activeMenu; // active menu + +// forward refs in this module +void printMenu(void); // print the menu +int getMenuItem(void); // get a menu item +void sendControlCode(uint8_t code); +BOOL pause(void); +void Print_Frame_stats(FRAME_STATS *stats); +void Print_Memory_Stats(void); +void Print_Radio_errors(uint32_t errs); +void Print_FSM_state(uint8_t state); +uint8_t getEntry(int activeMenu, int item); +uint8_t getKeyEntry(void); + +// list of menus +enum { + MAIN_MENU=0, // main menu + RADIO_MENU, // radio params menu + STATION_MENU, // Station params menu + N_MENUS // number of menus +}; + +// menu return functions +enum { + RET_MORE=0, // more to do + RET_DONE, // done + RET_PAUSE // pause before leaving +}; + +/* + * The first part of this code contains the menus and processing routines + * handle the various menu options + * return TRUE if done, else FALSE + */ +uint8_t printAllSetup(void) +{ + TIMEOFDAY tod; + getTOD(&tod); + SOCKADDR_IN *ipAddr; + + USART_Print_string("Current firmware is at %d.%d\r\n",def_params.params.FirmwareVerMajor, + def_params.params.FirmwareVerMinor); + USART_Print_string("System time is %02d:%02d:%02d\r\n", tod.Hours, tod.Minutes, tod.Seconds); + USART_Print_string("Radio ID is %08x%08x\r\n", GetDevID0(), GetDevID1()); + + GetMyIP(&ipAddr); + USART_Print_string("IP Address %d.%d.%d.%d\r\n\n",ipAddr->sin_addr.S_un.S_un_b.s_b1, ipAddr->sin_addr.S_un.S_un_b.s_b2, + ipAddr->sin_addr.S_un.S_un_b.s_b3, ipAddr->sin_addr.S_un.S_un_b.s_b4); + printStationSetup(); + printRadioSetup(); + return RET_PAUSE; +} +// +uint8_t printStnSetup(void) +{ + printStationSetup(); + return RET_PAUSE; +} +// +uint8_t printRadSetup(void) +{ + printRadioSetup(); + return RET_PAUSE; +} +// list mesh status +uint8_t listMesh(void) +{ + Mesh_ListStatus(); + return RET_PAUSE; +} + +// enter chat mode +uint8_t chatMode(void) +{ + if(Chat_Task_exec()) + return RET_PAUSE; + return RET_MORE; +} + +// dump the rx stats +uint8_t showstats(void) +{ + Print_Frame_stats(GetFrameStats()); + Print_Radio_errors(GetRadioStatus()); + Print_FSM_state(GetFSMState()); + return RET_PAUSE; +} + +// set the radio entry mode +uint8_t setRadio(void) +{ + activeMenu = RADIO_MENU; + menuState = MENU_OFF; + return RET_DONE; +} + +// set the station entry mode +uint8_t setStation(void) +{ + activeMenu = STATION_MENU; + menuState = MENU_OFF; + return RET_DONE; +} +// enter chat mode +uint8_t ledTest(void) +{ + if(LedTest()) + return RET_PAUSE; + return RET_MORE; +} +// write the flash memory +uint8_t writeSetup(void) +{ + if(!UpdateSetup()) { + USART_Print_string("Error in writing flash: setup may be corrupt\r\n"); + } else { + USART_Print_string("Flash written successfully\r\n"); + } + menuState = MENU_OFF; + return RET_PAUSE; +} +// exit the current menu +uint8_t exitMenu(void) +{ + activeMenu = MAIN_MENU; + menuState = MENU_OFF; + return RET_DONE; +} +// set a parameter value +uint8_t setParam(void) +{ + return(getEntry(activeMenu, sel_item)); +} +/* + * These are conditional + */ +#if __ENABLE_GPS +uint8_t gpsEcho(void) +{ + GPSEcho(); + return(getKeyEntry()); +} +#endif +#if __MEM_DEBUG +// memory statistics +uint8_t memStats(void) +{ + Print_Memory_Stats(); + return RET_PAUSE; +} +#endif + + +/* + * main menu definition + * 1) define the number of line items + * 2) create menuitms struct + * 3) add to menucontents struct + */ +struct menuItems_t { + char *menuLine; // text of menu line + char selChar; // character to select it + uint8_t (*func)(void); // processing function +}; + +// main menu +#if __ENABLE_GPS +#define N_GPS 1 // additional menu item for GPS +#else +#define N_GPS 0 +#endif + +#if __MEM_DEBUG +#define N_MEM 1 // additional menu item for memory +#else +#define N_MEM 0 +#endif + +#define N_MAINMENU (11+N_GPS+N_MEM) // additional menu item for GPS + +struct menuItems_t mainMenu[N_MAINMENU] = { + { "List setup parameters\r\n", 'A', printAllSetup }, + { "Mesh Status\r\n", 'B', listMesh }, + { "Chat Mode\r\n", 'C', chatMode }, + { "Dump Frame stats\r\n", 'D', showstats }, +#if __ENABLE_GPS + { "GPS Echo mode\r\n", 'G', gpsEcho }, +#endif + { "LED test\r\n", 'L', ledTest }, +#if __MEM_DEBUG + { "Memory Status\r\n", 'M', memStats }, +#endif + { "Set Radio Parameters\r\n", 'R', setRadio }, + { "Set Station Parameters\r\n", 'S', setStation }, + { "Set clock (HH:MM)\r\n\n", 'T', setParam }, + { "Write Setup Values\r\n", 'W', writeSetup }, + { "Exit\r\n\n", 'X', exitMenu } +}; + +// radio menu +#define N_RADIOMENU 8 +struct menuItems_t radioMenu[N_RADIOMENU] = { + { "RF Frequency\r\n", 'A', setParam }, + { "Data Rate\r\n", 'B', setParam }, + { "Peak Deviation\r\n", 'C', setParam }, + { "Channel Filter BW\r\n", 'D', setParam }, + { "Output Power (dBm)\r\n", 'E', setParam }, + { "Rx Squelch (dBm)\r\n\n", 'F', setParam }, + { "List Settings\r\n", 'L', printRadSetup }, + { "Return to main menu\r\n\n", 'X', exitMenu } +}; + +// these need to correspond to the items above + +// station menu +#define N_STATIONMENU 8 +struct menuItems_t stationMenu[N_STATIONMENU] = { + { "Callsign\r\n", 'A', setParam }, + { "Latitude\r\n", 'B', setParam }, + { "Longitude\r\n", 'C', setParam }, + { "Grid Square\r\n", 'D', setParam }, + { "Repeat Default\r\n", 'E', setParam }, + { "Beacon Interval\r\n\n", 'F', setParam }, + { "List Settings\r\n", 'L', printStnSetup }, + { "Return to main menu\r\n\n", 'X', exitMenu } +}; + +// menu contents +struct menuContents_t { + char *title; // title of the menu + int nMenuLines; // number of lines + struct menuItems_t *menus; // menu items +} menuContents[N_MENUS] = { +#if _BOARD_TYPE==NUCLEO_BOARD + { " IP400 Nucleo Main menu\r\n", N_MAINMENU, mainMenu }, +#else + { " IP400 Pi Zero HAT menu\r\n", N_MAINMENU, mainMenu }, +#endif + { " Radio setup menu\r\n", N_RADIOMENU, radioMenu }, + { " Station Setup menu\r\n", N_STATIONMENU, stationMenu } +}; + +// menu (main) task +void Menu_Task_Init(void) +{ + // start off with the menu showing + activeMenu = MAIN_MENU; + menuState = MENU_SHOWING; + entryState = NO_ENTRY; +} + +// send a control code +void sendControlCode(uint8_t code) +{ + USART_Send_String(controlCodes[code].sequence, controlCodes[code].len); +} + +// send a control code +void sendTextString(char *string) +{ + strcpy(menu, string); + USART_Send_String(menu, strlen(string)); +} + +// process our time slot +void Menu_Task_Exec(void) +{ + int nBytesinBuff = 0; + char c; + struct menuItems_t *m; + + switch(menuState) { + + // if there is a return in the console buffer, + // bring up the menu + case MENU_OFF: + if((nBytesinBuff=databuffer_bytesInBuffer()) == 0) + return; + for(int i=0;ifunc)()) { + + case RET_MORE: + break; + + case RET_PAUSE: + sendTextString(pauseString); + menuState = MENU_PAUSED; + break; + + case RET_DONE: + menuState = MENU_SHOWING; + break; + } + break; + + case MENU_PAUSED: + if(pause()) + menuState = MENU_SHOWING; + break; + + } +} + +// print the main menu +void printMenu(void) +{ + struct menuItems_t *m; + + // title + sendControlCode(CONTROL_CLEAR); + sendControlCode(CONTROL_HOME); + +#if _BOARD_TYPE==NUCLEO_BOARD + // only use double wide mode with Nucleo + sendControlCode(CONTROL_DOUBLE_TOP); + sendTextString(menuContents[activeMenu].title); + sendControlCode(CONTROL_DOUBLE_BOTTOM); + sendTextString(menuContents[activeMenu].title); + sendControlCode(CONTROL_SINGLE); + sendTextString(menuSpacer); +#else + sendTextString(menuContents[activeMenu].title); + sendTextString(menuSpacer); +#endif + + // lines + int nMenuLines = menuContents[activeMenu].nMenuLines; + for(int i=0;iselChar; + strcpy(&menu[1], ") "); + strcat(menu, m->menuLine); + USART_Send_String(menu, strlen(menu)); + } + + // selection + sendTextString(selectItem); +} + +// get a menu item and dispatch the correct processing routine +int getMenuItem(void) +{ + int nBytesinBuff =0; + struct menuItems_t *m; + char c; + + if((nBytesinBuff=databuffer_bytesInBuffer()) == 0) + return NO_ITEM; + + for(int i=0;iselChar == c) { + sendTextString(menuSpacer); + return j; + } + } + } + } + sendTextString(whatItem); + return NO_ITEM; +} + +BOOL pause(void) +{ + if(databuffer_bytesInBuffer() == 0) + return FALSE; + + if(databuffer_get(0) == ASCII_RET) + return TRUE; + + return FALSE; + +} + +/* + * This part pertains to getting an entry and validating it.. + */ + +// keys in entry mode +#define KEY_EOL 0x0D // carriage return +#define KEY_ESC 0x1B // escape key +#define KEY_DEL 0x7F // delete key +#define KEY_BKSP 0x08 // backspace key + +#define MAX_ENTRY 40 // max entry chars + +BOOL delMode = FALSE; +int pos = 0; +char keyBuffer[MAX_ENTRY]; + +// forward refs +uint8_t validateEntry(int activeMenu, int item, char *keyBuffer); + +/* + * Get a key entry: basically stolen from chat.c + */ +uint8_t getKeyEntry(void) +{ + + char c; + int nBytesinBuff; + + if((nBytesinBuff=databuffer_bytesInBuffer()) == 0) + return RET_MORE; + + for(int i=0;i 0) { + USART_Print_string("%c", keyBuffer[--pos]); + } else { + USART_Print_string("\\\r\n"); + delMode = FALSE; + } + } + continue; + } else { + // processing a key + + switch (c) { + + // EOL key: sent the packet + case KEY_EOL: + USART_Print_string("\r\n"); + keyBuffer[pos++] = '\0'; + return RET_DONE; + break; + + // escape key: abort the entry + case KEY_ESC: + return RET_PAUSE; + break; + + case KEY_DEL: + case KEY_BKSP: + USART_Print_string("\\%c", keyBuffer[--pos]); + delMode = TRUE; + break; + + default: + USART_Send_Char(c); + if(pos < MAX_ENTRY) + keyBuffer[pos++] = c; + break; + } + } + } + return RET_MORE; +} + +/* + * Get an entry and validate it + */ +uint8_t getEntry(int activeMenu, int item) +{ + struct menuItems_t *m; + + switch(entryState) { + + case NO_ENTRY: + delMode = FALSE; + pos = 0; + entryState = ENTERING; + m=menuContents[activeMenu].menus; + m += item; + USART_Print_string("%s->", m->menuLine); + return RET_MORE; + + case ENTERING: + int keyStat = getKeyEntry(); + if(keyStat == RET_DONE) { + entryState = VALIDATING; + return RET_MORE; + } + return keyStat; + + case VALIDATING: + entryState = NO_ENTRY; + return validateEntry(activeMenu, item, keyBuffer); + } + + return RET_MORE; +} + +// data types we are updating +enum { + uint8_type, // uint8 field + int16_type, // int16 field + uint32_type, // uint32 field + float_type, // floating point + char_type, // character type + field_type // field type +}; + +// struct to hold validation values +typedef struct field_validator_t { + int MinVal; // minimum value + int MaxVal; // maximum value + void *setupVal; // pointer to setup value + int type; // type of entry + uint32_t scalar; // scalar to convert to decimal +} FIELD_VALIDATOR; + +// validators for radio mmenu +FIELD_VALIDATOR radioValidators[] = { + { MIN_FREQ, 450000000, &setup_memory.params.radio_setup.lFrequencyBase, uint32_type, 1000000 }, + { 9600, 600000, &setup_memory.params.radio_setup.lDatarate, uint32_type, 1000 }, + { 12500, 150000, &setup_memory.params.radio_setup.lFreqDev, uint32_type, 1000 }, + { 2600, 1600000, &setup_memory.params.radio_setup.lBandwidth, uint32_type, 1000 }, + { 0, 20, &setup_memory.params.radio_setup.outputPower, uint8_type, 1 }, + { -115, 0, &setup_memory.params.radio_setup.rxSquelch, int16_type, 1 }, +}; + +FIELD_VALIDATOR stationValidators[] = { + { 4, 6, &setup_memory.params.setup_data.stnCall, char_type, 0 }, + { 2, 14, &setup_memory.params.setup_data.latitude, char_type, 0 }, + { 2, 14, &setup_memory.params.setup_data.longitude, char_type, 0 }, + { 6, 6, &setup_memory.params.setup_data.gridSq, char_type, 0 }, + { 0, 0x8, &setup_memory.params.setup_data.flags, field_type, 0 }, + { 1, 100, &setup_memory.params.setup_data.beaconInt, int16_type, 0 } +}; + +uint8_t validateEntry(int activeMenu, int item, char *keyBuffer) +{ + + int newValue, min, max; + + //NB: the cases here must jive with the menu items + switch(activeMenu) { + + case MAIN_MENU: // the only menu item here is the clock + setTOD(keyBuffer); + break; + + case RADIO_MENU: + // convert a floating point entry to the required decimal + if(isfloat(keyBuffer)) + newValue = (int)(ascii2double(keyBuffer)*radioValidators[item].scalar); + else newValue = ascii2Dec(keyBuffer); + max = radioValidators[item].MaxVal; + min = radioValidators[item].MinVal; + if((newValuemax)) { + USART_Print_string("Must be in the range of %d to %d\r\n", min, max); + return RET_PAUSE; + } + switch(radioValidators[item].type){ + + case uint8_type: + uint8_t *v8 = (uint8_t *)radioValidators[item].setupVal; + *v8 = (uint8_t)newValue; + break; + + case int16_type: + int16_t *v16 = (int16_t *)radioValidators[item].setupVal; + *v16 = (int16_t)newValue; + break; + + case uint32_type: + uint32_t *v32 = (uint32_t *)radioValidators[item].setupVal; + *v32 = (uint32_t)newValue; + break; + } + + break; + + case STATION_MENU: + switch(stationValidators[item].type){ + + case int16_type: + newValue = ascii2Dec(keyBuffer); + max = stationValidators[item].MaxVal; + min = stationValidators[item].MinVal; + if((newValuemax)) { + USART_Print_string("Must be in the range of %d to %d\r\n", min, max); + return RET_PAUSE; + } + uint16_t *v8 = (uint16_t *)stationValidators[item].setupVal; + *v8 = (uint16_t)newValue; + break; + + case char_type: + size_t len = strlen(keyBuffer); + max = stationValidators[item].MaxVal; + min = stationValidators[item].MinVal; + if((lenmax)) { + USART_Print_string("String must be %d to %d in length\r\n", min, max); + return RET_PAUSE; + } + strcpy((char *)stationValidators[item].setupVal, keyBuffer); + break; + + case field_type: + uint8_t val = keyBuffer[0] == 'T' ? 1 : 0; + uint8_t *f8= (uint8_t *)stationValidators[item].setupVal; + if(val) + *f8 |= (uint8_t)stationValidators[item].MinVal; + else + *f8 &= (uint8_t)stationValidators[item].MinVal; + break; + } + break; + + } + + // falls to here when done... + return RET_DONE; +} + + +/* + * Print the frame stats + */ +void Print_Frame_stats(FRAME_STATS *stats) +{ + USART_Print_string("Frame Statistics\r\n\n"); + + USART_Print_string("Transmitted frames->%d\r\n", stats->TxFrameCnt); + USART_Print_string("CRC Errors->%d\r\n", stats->CRCErrors); + USART_Print_string("Rx Timeouts->%d\r\n", stats->TimeOuts); + USART_Print_string("Frames with good CRC->%d\r\n", stats->RxFrameCnt); + USART_Print_string("Beacon frames->%d\r\n", stats->nBeacons); + USART_Print_string("Repeated frames->%d\r\n", stats->nRepeated); + + USART_Print_string("Processed Frames->%d\r\n", stats->RxFrameCnt); + USART_Print_string("Dropped frames->%d\r\n", stats->dropped); + USART_Print_string("Duplicate frames->%d\r\n", stats->duplicates); + USART_Print_string("Repeated frames->%d\r\n", stats->duplicates); + +} + +/* + * Print the radio error stats + */ +struct radio_errs_t { + uint32_t mask; + char *errmsg; +} radio_errs[N_RADIO_ERRS] = { + { SEQ_COMPLETE_ERR, "Sequencer Error" }, + { SEQ_ACT_TIMEOUT, "Sequencer action timeout" }, + { PLL_CALAMP_ERR, "VCO Amplitude calibration error" }, + { PLL_CALFREQ_ERR, "VCO Frequency calibration error" }, + { PLL_UNLOCK_ERR, "PLL Unlocked" }, + { PLL_LOCK_FAIL, "PLL Lock failure" }, + { DBM_FIFO_ERR, "Data buffer FIFO error" } +}; +// +void Print_Radio_errors(uint32_t errs) +{ + if(errs == 0){ + USART_Print_string("No radio errors\r\n"); + return; + } + for(int i=0;i FSM_N_FSM_STATES) { + USART_Print_string("\r\n?Unknown FSM state\r\n"); + return; + } + USART_Print_string("\r\nRadio FSM State: %d: %s\r\n", state, fsm_states[state]); +} + +#if __MEM_DEBUG +/* + * dump memory statistics + * uses the mallinfo structure + */ +void Print_Memory_Stats(void) +{ + + struct mallinfo memStats; + + memStats = mallinfo(); + + USART_Print_string("Memory Statistics\r\n\n"); + + USART_Print_string("Total non-mapped bytes (arena)->%ld\r\n", memStats.arena); + USART_Print_string("Chunks not in use->%ld\r\n", memStats.ordblks); + USART_Print_string("Free fast bin blocks->%ld\r\n", memStats.smblks); + USART_Print_string("Mapped Regions->%ld\r\n", memStats.hblks); + USART_Print_string("Bytes in mapped regions->%ld\r\n\n", memStats.hblkhd); + + USART_Print_string("Free bytes in fast bins->%ld\r\n", memStats.fsmblks); + USART_Print_string("Total Allocated Space->%ld\r\n", memStats.uordblks); + USART_Print_string("Total Space not in use->%ld\r\n", memStats.fordblks); + USART_Print_string("Topmost releasable block->%ld\r\n", memStats.keepcost); + +} +#endif diff --git a/Node Firmware/IP400/Src/mesh.c b/Node Firmware/IP400/Src/mesh.c new file mode 100644 index 0000000..2218ec8 --- /dev/null +++ b/Node Firmware/IP400/Src/mesh.c @@ -0,0 +1,301 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + File Name: mesh.c + + Author: MartinA + + Description: This code builds and lists the mesh status + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) Alberta Digital Radio Communications Society + All rights reserved. + + Revision History: + +---------------------------------------------------------------------------*/ +#include +#include +#include +#include +#include +#include +#include + +#include "tasks.h" +#include "frame.h" +#include "usart.h" +#include "dataq.h" +#include "setup.h" +#include "tod.h" +#include "ip.h" + +#define EXPIRY_TIME 100 // seconds since last heard +#define RSSI_SCALAR 161 // 95-Gain(rx), appros 65 + +char *FSKSpeeds[] = { + "FSK 1200", + "FSK 9600", + "FSK_56K", + "FSK_100K", + "FSK_200K", + "FSK_300K", + "FSK_400K", + "FSK_600K" +}; + +// table entry status +typedef enum { + MESHTBL_UNUSED=0, // unused entry + MESHTBL_VALID, // valid entry + MESHTBL_LOST // lost in space +} MeshTableStatus; + +// mesh entry struct +typedef struct mesh_entry_t { + MeshTableStatus status; // status + IP400_MAC macAddr; // MAC address/IP Address + uint32_t nextSeq; // next sequence number + int16_t lastRssi; // signal strength + uint8_t txPower; // transmit power + SETUP_FLAGS capabilities; // capabilities + TIMEOFDAY lastHeard; // last heard + uint8_t hopCount; // hop count +} MESH_ENTRY; + +// mesh table definitions +#define MAX_MESH_MEMORY 2048 // max amount of mesh memory used +#define MAX_MESH_ENTRIES (MAX_MESH_MEMORY/sizeof(MESH_ENTRY)) +#define ENTRY_NOTFOUND -1 // not found in the table + +// table size is limited to by memory defintion +static MESH_ENTRY MeshTable[MAX_MESH_ENTRIES] __attribute__((section("MESHTABLE"))); +int nMeshEntries = 0; + +BEACON_HEADER mesh_bcn_hdr; // beacon header +uint32_t seqNum; // sequence number received + +// forward refs in this module +int findCall(IP400_MAC *macAddr); +void AddMeshEntry(IP400_FRAME *frameData, int16_t rssi, BOOL isBeacon); + +// task initialization +void Mesh_Task_Init(void) +{ + for(int i=0;isource)) != ENTRY_NOTFOUND) { + + // if it is repeated frame with a higher hop count, ignore it + if(MeshTable[entryNum].hopCount < frameData->flagfld.flags.hop_count) + return; + + MeshTable[entryNum].lastHeard.Hours = current.Hours; + MeshTable[entryNum].lastHeard.Minutes = current.Minutes; + MeshTable[entryNum].lastHeard.Seconds = current.Seconds; + MeshTable[entryNum].lastRssi = actRSSI; + MeshTable[entryNum].nextSeq = frameData->seqNum == 0xFFFFFFFF ? 0 : ++frameData->seqNum; + + //ugly, but necessary + memset(mesh_bcn_hdr.hdrBytes, 0, sizeof(BEACON_HEADER)); + uint8_t *p = (uint8_t *)frameData->buf; + mesh_bcn_hdr.hdrBytes[0] = *p++; + mesh_bcn_hdr.hdrBytes[4] = *p; + + MeshTable[entryNum].capabilities = mesh_bcn_hdr.setup.flags; + MeshTable[entryNum].txPower = mesh_bcn_hdr.setup.txPower; + + // all done + return; + } + + // beacon from a new station + AddMeshEntry(frameData, actRSSI, TRUE); +} + +/* + * add a new station to the mesh table + */ +void AddMeshEntry(IP400_FRAME *frameData, int16_t actRSSI, BOOL isBeacon) +{ + // check for room first + if(nMeshEntries >= MAX_MESH_ENTRIES) + return; + + MESH_ENTRY newEntry; + + TIMEOFDAY current; + getTOD(¤t); + SOCKADDR_IN ipAddr; + + // grab call and capabilities + newEntry.macAddr = frameData->source; + newEntry.lastHeard.Hours = current.Hours; + newEntry.lastHeard.Minutes = current.Minutes; + newEntry.lastHeard.Seconds = current.Seconds; + newEntry.nextSeq = frameData->seqNum == 0xFFFFFFFF ? 0 : ++frameData->seqNum; + newEntry.lastRssi = actRSSI; + newEntry.txPower = 0; + newEntry.hopCount = frameData->flagfld.flags.hop_count; + + GetIPAddrFromMAC(&newEntry.macAddr, &ipAddr); + + if(isBeacon) { + memcpy(mesh_bcn_hdr.hdrBytes, (struct beacon_hdr_t *)frameData->buf, sizeof(BEACON_HEADER)); + newEntry.capabilities = mesh_bcn_hdr.setup.flags; + newEntry.txPower = mesh_bcn_hdr.setup.txPower; + } else { + memset(&newEntry.capabilities, 0, sizeof(SETUP_FLAGS)); + } + + // insert this at the end of the queue + newEntry.status = MESHTBL_VALID; + memcpy(&MeshTable[nMeshEntries++], &newEntry, sizeof(MESH_ENTRY)); +} + +/* + * reject a duplicate frame that may have been repeated + * TRUE: keep it, FALSE: reject it + */ +BOOL Check_Sender_Address(void *rxFrame, uint32_t rssi) +{ + IP400_FRAME *frameData = (IP400_FRAME *)rxFrame; + int entryNum; + + if((entryNum = findCall(&frameData->source)) != ENTRY_NOTFOUND) { + // rebooted + if(frameData->seqNum == 0xFFFFFFFF) + MeshTable[entryNum].nextSeq = 0; + // reject if the seq is lower + if(frameData->seqNum < MeshTable[entryNum].nextSeq) + return FALSE; + MeshTable[entryNum].nextSeq = frameData->seqNum + 1; + return TRUE; + } + + // sender is unknown: add him for now.. + int16_t actRSSI = rssi/2 - RSSI_SCALAR; + AddMeshEntry(frameData, actRSSI, FALSE); + return TRUE; +} + +/* + * Check if the frame can be accepted. + * Accept a broadcast or my address if: + * -the sender is not in the mesh table + * -the sequence is not out of order + * Else reject it + */ +BOOL Mesh_Accept_Frame(void *rxFrame, uint32_t rssi) +{ + IP400_FRAME *frameData = (IP400_FRAME *)rxFrame; + char decCall[30]; + uint16_t port;; + + // frame is sent a broadcast address + if((frameData->dest.callbytes.bytes[0] == BROADCAST_ADDR) + && (frameData->dest.callbytes.bytes[0] == BROADCAST_ADDR)) + return Check_Sender_Address(rxFrame, rssi); + + callDecode(&frameData->dest, decCall, &port); + if(CompareToMyCall(decCall)) + return Check_Sender_Address(rxFrame, rssi); + + // not for me + return FALSE; +} + +// find a callsign in the list +int findCall(IP400_MAC *call) +{ + for(int i=0;icallbytes.encoded) + return i; + } + return ENTRY_NOTFOUND; +} + +char capabilties[50]; +// return the capabilities of a node +char *GetCapabilities(SETUP_FLAGS cap) +{ + mesh_bcn_hdr.setup.flags = cap; + + if(mesh_bcn_hdr.hdrBytes[0] == 0) { + strcpy(capabilties, "Unknown"); + } + + // modes + if(cap.fsk) { + sprintf(capabilties, "%s", FSKSpeeds[cap.rate]); + } + else if(cap.ofdm) { + strcat(capabilties, " OFDM"); + } + else if(cap.aredn) { + strcat(capabilties, "AREDN"); + } + + // repeat mode is on.. + if(cap.repeat) { + strcat(capabilties, " RPT"); + } + + return capabilties; +} + +// list the mesh status: walk the mesh entries +void Mesh_ListStatus(void) +{ + USART_Print_string("Stations Heard: %d\r\n", nMeshEntries); + if(nMeshEntries == 0) + return; + + // process the list + char decodedCall[20]; + SOCKADDR_IN ipAddr; + + USART_Print_string("Call\tIP Addr\t\tRSSI\tSeq\tLast Heard\tHops\tCapabilities\r\n"); + + for(int i=0;i +#include +#include +#include + +#include "types.h" +#include "setup.h" +#include "usart.h" +#include "ip.h" +#include "frame.h" + +#define USE_HAL + +// flash stuff +#define FLASH_PAGE_ADDR ((uint32_t)0x1007F800) +#define FLASH_PAGE_NUM 127 +static FLASH_EraseInitTypeDef EraseInitStruct; + +/* + * Default setup parameters + */ +SETUP_MEMORY setup_memory; + +SETUP_MEMORY def_params = { + .params.setup_data.flags = { 1, 0, 0 , 1, 0, FSK_100K }, // LSB specified first + .params.setup_data.stnCall ="NOCALL", + .params.setup_data.gridSq = "DO21vd", + .params.setup_data.latitude = "51.08", + .params.setup_data.longitude = "-114.10", + .params.setup_data.beaconInt = 5, + // + .params.radio_setup.lFrequencyBase = 445750000, + .params.radio_setup.xModulationSelect = MOD_4FSK, + .params.radio_setup.lDatarate = 100000, + .params.radio_setup.lFreqDev = 25000, + .params.radio_setup.lBandwidth = 200000, + .params.radio_setup.dsssExp = 0, + .params.radio_setup.outputPower = 14, + .params.radio_setup.PADrvMode = PA_DRV_TX_HP, + .params.radio_setup.rxSquelch = -95, + // + .params.FirmwareVerMajor = 1, // current rev is 1.1 + .params.FirmwareVerMinor = 1, + .params.Magic = SETUP_MAGIC, + .params.SetupCRC = 0 +}; + +/* + * IP Address information + */ +IP400_MAC myMAC; // my callsign encoded +SOCKADDR_IN myIP; // my IP address + +// enum fields in setup struct +// modulation type +char *modTypes[] = { + "2FSK", + "4FSK", + "2GFSK05", + "2GFSK1", + "4GFSK05", + "4GFSK1", + "ASK", + "OOK", + "POLAR", + "CW" +}; + +// PA modes +char *paModes[] = { + "TX 10dBm Max", + "HP 14dBm Max", + "TX_HP 20dBm Max" +}; + +// return the setup struct +STN_PARAMS *GetStationParams(void) // get the station params +{ + return &setup_memory.params; +} + +// compare call to station callsign +// returns true if callsigns match +BOOL CompareToMyCall(char *call) +{ + char expCall[20]; + + // make sure call sign is padded out to 6 characters b4 comparison + strcpy(expCall, setup_memory.params.setup_data.stnCall); + strcat(expCall, " "); + + if(!strncmp(call, expCall, MAX_CALL)) + return TRUE; + return FALSE; +} + +/* + * Print the setup struct + */ +void printStationSetup(void) +{ + // station callsign first + USART_Print_string("Station Callsign->%s\r\n", setup_memory.params.setup_data.stnCall); + if(setup_memory.params.setup_data.flags.ext) + USART_Print_string("Extended Callsign->%s\r\n"); + + USART_Print_string("Latitude->%s\r\n", setup_memory.params.setup_data.latitude); + USART_Print_string("Longitude->%s\r\n", setup_memory.params.setup_data.longitude); + USART_Print_string("Grid Square->%s\r\n", setup_memory.params.setup_data.gridSq); + + USART_Print_string("Capabilities->"); + if(setup_memory.params.setup_data.flags.fsk) + USART_Print_string("FSK "); + if(setup_memory.params.setup_data.flags.ofdm) + USART_Print_string("OFDM "); + if(setup_memory.params.setup_data.flags.aredn) + USART_Print_string("AREDN "); + if(setup_memory.params.setup_data.flags.repeat) + USART_Print_string("\r\nRepeat mode on by default\r\n"); + else + USART_Print_string("\r\nRepeat mode off by default\r\n"); + USART_Print_string("Beacon Interval->%d mins\r\n\n", setup_memory.params.setup_data.beaconInt); +} + +void printRadioSetup(void) +{ + // dump the radio init struct + uint16_t fWhole = setup_memory.params.radio_setup.lFrequencyBase/1e6; + uint16_t fFract = setup_memory.params.radio_setup.lFrequencyBase/1e3 - fWhole*1e3; + USART_Print_string("RF Frequency->%d.%d MHz\r\n", fWhole, fFract); + + USART_Print_string("Modulation method->%s\r\n", modTypes[setup_memory.params.radio_setup.xModulationSelect]); + + uint16_t dWhole = setup_memory.params.radio_setup.lDatarate/1000; + uint16_t dFract = setup_memory.params.radio_setup.lDatarate - dWhole*1000; + USART_Print_string("Data Rate->%d.%d Kbps\r\n", dWhole, dFract); + + uint16_t pWhole = setup_memory.params.radio_setup.lFreqDev/1000; + uint16_t pFract = setup_memory.params.radio_setup.lFreqDev - pWhole*1000; + USART_Print_string("Peak Deviation->%d.%d KHz\r\n", pWhole, pFract); + + uint16_t bWhole = setup_memory.params.radio_setup.lBandwidth/1000; + uint16_t bFract = setup_memory.params.radio_setup.lBandwidth - bWhole*1000; + USART_Print_string("Channel Filter Bandwidth->%d.%d KHz\r\n", bWhole, bFract); + + USART_Print_string("Output Power->%d dBm\r\n", setup_memory.params.radio_setup.outputPower); + USART_Print_string("PA Mode->%s\r\n", paModes[setup_memory.params.radio_setup.PADrvMode]); + USART_Print_string("Rx Squelch->%d\r\n\n\n", setup_memory.params.radio_setup.rxSquelch); +} + +/* + * Manage the IP address + */ + void GetMyIP(SOCKADDR_IN **ipAddr) + { + *ipAddr = &myIP; + } + + void GetMyMAC(IP400_MAC **mac) + { + *mac = &myMAC; + } + + // set my IP Address + void SetMyIP(void) + { + char paddedCall[20]; + strncpy(paddedCall, setup_memory.params.setup_data.stnCall, MAX_CALL); + strcat(paddedCall, " "); + + // encode my callsign + EncodeChunk(paddedCall, MAX_CALL, &myMAC.callbytes.encoded); + + myMAC.ipbytes.encip = GetIPLowerWord(); + GetIPAddrFromMAC(&myMAC, &myIP); + } + +/* + * This code manages saving and reading the setup params + */ +// internals +uint32_t CalcSetupCRC(void); + +BOOL UpdateSetup(void) +{ + // update CRC before writing + setup_memory.params.SetupCRC = CalcSetupCRC(); + + if(WriteSetup() != HAL_OK) { + return FALSE; + } + + if(!ReadSetup()) { + return FALSE; + } + + if(!VerifySetup()) { + return FALSE; + } + + return TRUE; // only one iteration required +} + +// verify that the current setup record is valid +BOOL VerifySetup(void) +{ + // if the magic number matches, then all is well + if(setup_memory.params.Magic != SETUP_MAGIC) + return FALSE; + + uint32_t SetupCRC = CalcSetupCRC(); + + if(SetupCRC == setup_memory.params.SetupCRC) + return TRUE; + + return FALSE; +} + +// set the default setup +void SetDefSetup(void) +{ + memcpy((void *)&setup_memory.bytes, (const void *)&def_params.bytes, sizeof(SETUP_MEMORY)); +} + +// Read setup from Flash memory +// data is stored in the last flash page +BOOL ReadSetup(void) +{ + __IO uint32_t data32 = 0;; + + uint32_t memAddr = FLASH_PAGE_ADDR; + + uint32_t *dst_addr = setup_memory.flashwords; + uint16_t nwords = sizeof(STN_PARAMS)/sizeof(uint32_t); + + while(nwords--){ + data32 = *(__IO uint32_t *)memAddr; + *dst_addr++ = data32; + memAddr += sizeof(uint32_t); + } + + SetMyIP(); + + return TRUE; +} + +// write the setup to OTP memory +HAL_StatusTypeDef WriteSetup(void) +{ + HAL_StatusTypeDef status=0; + uint32_t PageError; + + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS); + + // erase it first + EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; + EraseInitStruct.Page = FLASH_PAGE_NUM; + EraseInitStruct.NbPages = 1; + if ((status=HAL_FLASHEx_Erase(&EraseInitStruct, &PageError)) != HAL_OK) + return status; + + // waste some time... + for(int i=0;i<1000;i++); + + // now write + uint32_t memAddr = FLASH_PAGE_ADDR; + uint32_t *src_addr = setup_memory.flashwords; + uint16_t nwords = sizeof(SETUP_MEMORY)/sizeof(uint32_t); + + while(nwords--) + { + uint32_t data32 = *(__IO uint32_t *)src_addr++; + + if ((status=HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, memAddr, data32)) != HAL_OK) + return status; + memAddr += sizeof(uint32_t); + } + return status; +} + + +/* + * HAL related functions + */ +uint32_t GetDevID0(void) +{ + return HAL_GetUIDw0(); +} + +uint32_t GetDevID1(void) +{ + return HAL_GetUIDw1(); +} + +// calcuate the setup CRC +uint32_t CalcSetupCRC(void) +{ + uint32_t CRCValue; +#ifdef USE_HAL + CRCValue = HAL_CRC_Calculate(&hcrc, (uint32_t *)&setup_memory.bytes, sizeof(setup_memory) - sizeof(uint32_t)); +#else + + uint32_t cnt, count = sizeof(setup_memory) - sizeof(uint32_t); + uint8_t *arr = setup_memory.bytes; + + /* Reset CRC data register if necessary */ + CRC->CR = CRC_CR_RESET; + + + /* Calculate number of 32-bit blocks */ + cnt = count >> 2; + + /* Calculate */ + while (cnt--) { + /* Set new value */ + CRC->DR = *(uint32_t *)arr; + + /* Increase by 4 */ + arr += 4; + } + + /* Calculate remaining data as 8-bit */ + cnt = count % 4; + + /* Calculate */ + while (cnt--) { + /* Set new value */ + *((uint8_t *)&CRC->DR) = *arr++; + } + + /* Return data */ + CRCValue = CRC->DR; +#endif + return(CRCValue); +} diff --git a/Node Firmware/IP400/Src/spi.c b/Node Firmware/IP400/Src/spi.c new file mode 100644 index 0000000..5df354a --- /dev/null +++ b/Node Firmware/IP400/Src/spi.c @@ -0,0 +1,395 @@ +/*--------------------------------------------------------------------------- + Project: NucleoCC2 + + File Name: spi.c + + Author: Martin, VE6VH + + Description: SPI task. responds to commands from the host, implementing a quasi- + memory device + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) Alberta Digital Radio Communications Society + All rights reserved. + + Revision History: + +---------------------------------------------------------------------------*/ +#include +#include +#include +#include +#include +#include + +#include +#include "types.h" +#include "main.h" +#include "spi.h" +#include "dataq.h" +#include "tasks.h" +#include "led.h" + +// conditionals +#define __ENABLE_SPI_ON_TXENA 0 // enable spi sync output on TX enable for E04 modules +#define __ENABLE_SPI_ON_PB1 0 // enable SPI sync output on PB0 for IP400 module + +#define SPI_MAX_TIME 200 // 200 ms max no activity timeout +#define NO_SPI_TIMEOUT (SPI_MAX_TIME/SPI_TASK_SCHED) // timeout in schedule ticks + +// transmitter states +enum { + SPITXIDLE=0, // idle + SPITXFRAG // next fragment +}; + +// receiver states +enum { + SPIRXIDLE=0, // idle + SPIRXFRAG // next fragment +}; + +uint8_t SPITxState; // transmitter state +uint8_t SPIRxState; // receiver state + +// frame queue +FRAME_QUEUE spiTxQueue; // queue for outbound + +// frame buffers +#if USE_BUFFER_RAM +static SPI_BUFFER spiTxBuffer __attribute__((section("BUFFERS"), aligned(4))); +static SPI_BUFFER spiRxBuffer __attribute__((section("BUFFERS"), aligned(4))); +static uint8_t rxFrameBuffer[PAYLOAD_MAX] __attribute__((section("BUFFERS"), aligned(4))); +#else +static SPI_BUFFER spiTxBuffer; +static SPI_BUFFER spiRxBuffer; +static uint8_t rxFrameBuffer[PAYLOAD_MAX]; +#endif + +uint8_t SPI_State; // current state +HAL_StatusTypeDef spiXfer; // last transfer status +BOOL spiExchangeComplete; // spi exchange has been completed +BOOL spiErrorOccurred; +BOOL spiActive; +uint16_t spiActivityTimer; // no activity timer + +// select the correct SPI +#if _BOARD_TYPE == NUCLEO_BOARD +#define hSPI hspi1 +extern SPI_HandleTypeDef hspi1; // spi handle +#else + #if __INCLUDE_SPI + #define hSPI hspi3 + extern SPI_HandleTypeDef hspi3; // spi handle + #else + #define hSPI NULL // not implemented +#endif + +#endif + + +// validate an inbound frame +BOOL isIP400Frame(uint8_t *eye); + +/* + * place a frame on the queue frame content is copied + * to alloc'd memory + */ +BOOL EnqueSPIFrame(void *ip400frame) +{ + IP400_FRAME *qFrame, *SrcFrame = (IP400_FRAME *)ip400frame; + uint8_t *frameBuffer; + + // spi is not running... + if(!spiActive) + return FALSE; + + // allocate an IP400 frame + if((qFrame=malloc(sizeof(IP400_FRAME)))== NULL) + return FALSE; + memcpy(qFrame, SrcFrame, sizeof(IP400_FRAME)); + + int16_t hopCount = qFrame->flagfld.flags.hop_count; + int16_t hTblSize = hopCount * sizeof(HOPTABLE); + + // hop table is in the first part of the payload + // alloc enough for both + if((frameBuffer=malloc(SrcFrame->length+hTblSize)) == NULL) { + free(qFrame); + return FALSE; + } + + if(hopCount) { + memcpy(frameBuffer, qFrame->hopTable, hTblSize); + frameBuffer += hTblSize; + } + + memcpy(frameBuffer, (uint8_t *)SrcFrame->buf, SrcFrame->length); + qFrame->buf = frameBuffer; + qFrame->length = SrcFrame->length + hTblSize; + + if(!enqueFrame(&spiTxQueue, qFrame)) + return FALSE; + + return TRUE; +} + +/* + * clean up any queued frames + */ +void EmptySPIFrameQ(void) +{ + while(dequeFrame(&spiTxQueue) != NULL); + return; +} + +/* + * Initialize the task + */ +void SPI_Task_init(void) +{ + // create the rx completed semaphore + spiExchangeComplete = FALSE; + spiErrorOccurred = FALSE; + + spiTxBuffer.spiData.hdr.eye[0] = 'I'; + spiTxBuffer.spiData.hdr.eye[1] = 'P'; + spiTxBuffer.spiData.hdr.eye[2] = '4'; + spiTxBuffer.spiData.hdr.eye[3] = 'C'; + + spiTxBuffer.spiData.hdr.status = NO_DATA; + spiRxBuffer.spiData.hdr.status = NO_DATA; + + SPITxState = SPITXIDLE; + SPIRxState = SPIRXIDLE; + + // tx (outbound) frame queue + spiTxQueue.q_forw = &spiTxQueue; + spiTxQueue.q_back = &spiTxQueue; + + spiActive = FALSE; // no activity yet + spiActivityTimer = 0; + +#if __INCLUDE_SPI + // start the ball rolling.. + if((spiXfer = HAL_SPI_TransmitReceive_DMA(&hSPI, spiTxBuffer.rawData, spiRxBuffer.rawData, SPI_RAW_LEN)) != HAL_OK) + spiErrorOccurred = TRUE; +#endif + +} + +// execute the task +void SPI_Task_Exec(void) +{ + static IP400_FRAME *txFrame; + static uint16_t txSegLength, txPayloadRoom, hopSize; + static uint8_t *prxData, *ptxData, nHops; + static uint16_t rxSegLen; + +#if __INCLUDE_SPI + // check the status first: repost Rx if an error occurred and it is now ready + if(spiErrorOccurred) { + if(hSPI.State == HAL_SPI_STATE_READY) { + if((spiXfer = HAL_SPI_TransmitReceive_DMA(&hSPI, spiTxBuffer.rawData, spiRxBuffer.rawData, SPI_RAW_LEN)) == HAL_OK) { + spiErrorOccurred = FALSE; + } + } + } +#endif + + /* + * Here we wait for an exchange to be completed + * If there is no activity for NO_SPI_TIMEOUT, then + * the other end is probably dead, so clean up + * any pending frames. If SPI is not enabled, + * the queue will also be cleaned up + */ + if(!spiExchangeComplete) { + spiActivityTimer += 1; + if(spiActivityTimer >= NO_SPI_TIMEOUT) { + EmptySPIFrameQ(); + spiActive = FALSE; + spiActivityTimer = 0; + } + return; + } + + spiExchangeComplete = FALSE; // reset exchange done + spiActive = TRUE; // indicate that the SPI is active.. + spiActivityTimer = 0; // reset no activity timer + +// scope trigger on nucleo board +#if _BOARD_TYPE==NUCLEO_BOARD // board type in use + HAL_GPIO_TogglePin(SCOPE_GPIO_Port, SCOPE_Pin); +#else +#if __ENABLE_SPI_ON_TXENA + HAL_GPIO_TogglePin(PA_ENA_GPIO_Port, PA_ENA_Pin); +#endif +#endif + + /* + * process an outbound frame + * Fragment it if longer that 500 bytes + */ + switch(SPITxState) { + + case SPITXIDLE: + if((txFrame=dequeFrame(&spiTxQueue)) == NULL) { + spiTxBuffer.spiData.hdr.status = NO_DATA; + break; + } + txSegLength = txFrame->length; + nHops = txFrame->flagfld.flags.hop_count; + hopSize = (uint16_t)nHops * sizeof(HOPTABLE); + txPayloadRoom = SPI_BUFFER_LEN - hopSize; + spiTxBuffer.spiData.hdr.status = SINGLE_FRAME; + + // fragment the frame if needed + if(txFrame->length > txPayloadRoom) { + txSegLength = txPayloadRoom; + spiTxBuffer.spiData.hdr.status = FRAGMENT; + SPITxState = SPITXFRAG; + } + + // copy the address fields + memcpy(&spiTxBuffer.spiData.hdr.fromCall, txFrame->source.callbytes.bytes, N_CALL); + memcpy(&spiTxBuffer.spiData.hdr.fromIP, txFrame->source.ipbytes.ip, N_IPBYTES); + + memcpy(spiTxBuffer.spiData.hdr.toCall, txFrame->dest.callbytes.bytes, N_CALL); + memcpy(&spiTxBuffer.spiData.hdr.toIP, txFrame->dest.ipbytes.ip, N_IPBYTES); + + // flag fields + spiTxBuffer.spiData.hdr.coding = txFrame->flagfld.flags.coding; + spiTxBuffer.spiData.hdr.hopCount = txFrame->flagfld.flags.hop_count; + spiTxBuffer.spiData.hdr.flags = (uint8_t)((txFrame->flagfld.allflags >> 8) & 0xFF); + + ptxData = spiTxBuffer.spiData.buffer; + + // hop table + if(txFrame->flagfld.flags.hoptable) { + memcpy(ptxData, txFrame->hopTable, hopSize); + ptxData += hopSize; + free(txFrame->hopTable); + } + + // and now the data + memcpy(ptxData, txFrame->buf, txSegLength); + spiTxBuffer.spiData.hdr.offset_hi = spiTxBuffer.spiData.hdr.offset_lo = 0; + spiTxBuffer.spiData.hdr.length_hi = ((txSegLength + hopSize) >> 8); + spiTxBuffer.spiData.hdr.length_lo = ((txSegLength + hopSize) & 0xFF); + + // release memory if only a single frame + if(spiTxBuffer.spiData.hdr.status == SINGLE_FRAME) + { + free(txFrame->buf); + free(txFrame); + } + break; + + case SPITXFRAG: + uint16_t offset = ((uint16_t)(spiTxBuffer.spiData.hdr.offset_hi) << 8) + (uint16_t)spiTxBuffer.spiData.hdr.offset_lo; + uint16_t prevLen = ((uint16_t)(spiTxBuffer.spiData.hdr.length_hi) << 8) + (uint16_t)spiTxBuffer.spiData.hdr.length_lo; + offset += prevLen; + txFrame->length -= prevLen; + txSegLength = txFrame->length; + if(txFrame->length > SPI_BUFFER_LEN) { + spiTxBuffer.spiData.hdr.status = FRAGMENT; + txSegLength = SPI_BUFFER_LEN; + } else { + spiTxBuffer.spiData.hdr.status = LAST_FRAGMENT; + SPITxState = SPITXIDLE; + } + + // send the next fragment + void *fragAddr = txFrame->buf + offset; + memcpy(spiTxBuffer.spiData.buffer, fragAddr, txSegLength); + spiTxBuffer.spiData.hdr.offset_hi = (offset >> 8); + spiTxBuffer.spiData.hdr.offset_lo = (offset & 0xff); + spiTxBuffer.spiData.hdr.length_hi = (txSegLength >> 8); + spiTxBuffer.spiData.hdr.length_lo = (txSegLength & 0xFF); + + // done with frame + if(spiTxBuffer.spiData.hdr.status == LAST_FRAGMENT) + { + free(txFrame->buf); + free(txFrame); + } + break; + } + + /* + * Inbound frame. Reassemble fragments if needed... + */ + + switch(SPIRxState) { + + case SPIRXIDLE: + spiFrameStatus rstat = spiRxBuffer.spiData.hdr.status; + if((rstat == NO_DATA) || (rstat >= NUM_STATS)) + break; + + if(!isIP400Frame(spiRxBuffer.spiData.hdr.eye)) + break; + + rxSegLen = (spiRxBuffer.spiData.hdr.length_hi << 8) + spiRxBuffer.spiData.hdr.length_lo; + if(rstat != SINGLE_FRAME) { + prxData = rxFrameBuffer + (spiRxBuffer.spiData.hdr.offset_hi << 8) + spiRxBuffer.spiData.hdr.offset_lo; + memcpy(prxData, spiRxBuffer.spiData.buffer, rxSegLen); + SPIRxState = SPIRXFRAG; + } else { + SendSPIFrame(&spiRxBuffer.spiData.hdr, spiRxBuffer.spiData.buffer, rxSegLen); + } + break; + + case SPIRXFRAG: + uint8_t fragStat = spiRxBuffer.spiData.hdr.status; + uint16_t offset = (spiTxBuffer.spiData.hdr.offset_hi << 8) + spiTxBuffer.spiData.hdr.offset_lo; + prxData = rxFrameBuffer + offset; + rxSegLen = (spiRxBuffer.spiData.hdr.length_hi << 8) + spiRxBuffer.spiData.hdr.length_lo; + memcpy(prxData, spiRxBuffer.spiData.buffer, rxSegLen); + + if(fragStat == LAST_FRAGMENT) { + uint16_t frameLen = offset + rxSegLen; + spiRxBuffer.spiData.hdr.length_hi = (frameLen >> 8); + spiRxBuffer.spiData.hdr.length_lo = (frameLen & 0xFF); + // process frame for tx here... + SPIRxState = SPIRXIDLE; // placeholder + } + break; + } + +} + + +// test an incoming frame +BOOL isIP400Frame(uint8_t *eye) +{ + if((eye[0] != 'I') || (eye[1] != 'P')) + return FALSE; + + if((eye[2] != '4') || (eye[3] != 'C')) + return FALSE; + + return TRUE; +} + +#if __INCLUDE_SPI +// rx done callback +void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef * hspi) +{ + // set data valid and start a new transfer + if(hspi->State == HAL_SPI_STATE_READY) { + spiExchangeComplete = TRUE; + spiXfer = HAL_SPI_TransmitReceive_DMA(&hSPI, spiTxBuffer.rawData, spiRxBuffer.rawData, SPI_RAW_LEN); + if(spiXfer != HAL_OK) + spiErrorOccurred = TRUE; + return; + } + spiErrorOccurred = TRUE; // spi not ready +} +#endif diff --git a/Node Firmware/IP400/Src/tod.c b/Node Firmware/IP400/Src/tod.c new file mode 100644 index 0000000..eebc124 --- /dev/null +++ b/Node Firmware/IP400/Src/tod.c @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + File Name: tod.c + + Author: MartinA + + Description: Time of Day clock + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) Alberta Digital Radio Communications Society + All rights reserved. + + Revision History: + +---------------------------------------------------------------------------*/ +#include "types.h" +#include "tod.h" +#include "utils.h" + +TIMEOFDAY tod = { 0, 0, 0 }; // where we hold the TOD + +// routines +void TOD_10SecTimer(void) // 10 second timer +{ + // handle seconds + tod.Seconds += 10; + if(tod.Seconds >= 60) { + tod.Minutes += tod.Seconds/60; + tod.Seconds %= 60; + } + + // handle minutes + if(tod.Minutes >= 60) { + tod.Hours += tod.Minutes/60; + tod.Minutes %= 60; + } + + // handle hours + if(tod.Hours > 24) + tod.Hours %= 24; +} + +// return pointer to Time of Day +void getTOD(TIMEOFDAY *time) +{ + time->Hours = tod.Hours; + time->Minutes = tod.Minutes; + time->Seconds = tod.Seconds; +} + +// set the TOD from HH:MM string +// somewhat brute force parser +BOOL setTOD(char *todString) +{ + char *todValues[5]; + + int nParams = explode_string(todString, todValues, 5, ':', '"'); + if(nParams != 2) + return FALSE; + + int nHours = ascii2Dec(todValues[0]); + if((nHours < 0) || (nHours > 24)) + return FALSE; + + int nMins = ascii2Dec(todValues[1]); + if((nHours < 0) || (nHours > 60)) + return FALSE; + + tod.Hours = nHours; + tod.Minutes = nMins; + tod.Seconds = 0; + + return TRUE; +} diff --git a/Node Firmware/IP400/Src/usart.c b/Node Firmware/IP400/Src/usart.c new file mode 100644 index 0000000..b4f0d73 --- /dev/null +++ b/Node Firmware/IP400/Src/usart.c @@ -0,0 +1,300 @@ +/*--------------------------------------------------------------------------- + Project: IP400 Modem + + File Name: usart.c + + Author: Martin C. Alcock, VE6VH + + Revision: 1.05 + + Description: API for USART handling + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) Alberta Digital Radio Communications Society + All rights reserved. + + Revision History: + +---------------------------------------------------------------------------*/ +#include +#include +#include + +#include +#include +#include + +#include "config.h" +#include "stream_buffer.h" +#include "types.h" +#include "streambuffer.h" +#include "usart.h" + +// local defines +#define RX_TIMEOUT 10000 // 10 second rx timeout +#define TX_TIMEOUT 1000 // 1s transmit timeout + +// stream buffer +StreamBufferHandle_t USART_RxBuffer; // handle to buffer +StaticStreamBuffer_t USART_StreamBuffer; + +// UART/T Support +SemaphoreHandle_t txCompleted; // tx completed semaphore +static DATA_ELEMENT USARTRxChar[10]; // last received character + +uint8_t usart_data[bufferSIZE]; +char usartPrintBuffer[200]; + +// fwd refs here... +void USART_Receive_char(void); + +#if __ENABLE_GPS +#define LPUART_DMA_SIZE 32 +static DATA_ELEMENT LPUARTRxChar[LPUART_DMA_SIZE]; + +void LPUART_Receive_char(void); + +StreamBufferHandle_t LPUART_RxBuffer; // handle to buffer +StaticStreamBuffer_t LPUART_StreamBuffer; +uint8_t gps_data[bufferSIZE]; +#endif + +/* + * API for the rx data buffer + */ +void USART_API_init(void) +{ + // create the tx completed semaphore + txCompleted = xSemaphoreCreateBinary(); + + // start USART + USART_RxBuffer = xStreamBufferCreateStatic(bufferSIZE, 1, usart_data, &USART_StreamBuffer); + USART_RxBuffer_reset(); + USART_Receive_char(); + +#if __ENABLE_GPS + LPUART_RxBuffer = xStreamBufferCreateStatic(bufferSIZE, 1, gps_data, &LPUART_StreamBuffer); + LPUART_RxBuffer_reset(); + LPUART_Receive_char(); +#endif +} + +// reset the data buffer +void USART_RxBuffer_reset(void) +{ + xStreamBufferReset(USART_RxBuffer); + return; +} + +#if __ENABLE_GPS +// reset the LPUART Buffer +void LPUART_RxBuffer_reset(void) +{ + xStreamBufferReset(LPUART_RxBuffer); + return; +} +#endif + +// return the number of byte in the buffer +size_t databuffer_bytesInBuffer(void) +{ + size_t nBytes = xStreamBufferBytesAvailable(USART_RxBuffer); + return nBytes; +} + +// get a byte from the buffer: blocks if timeout is zero, +// else returns BUFFER_NO_DATA if timeout exceeded +DATA_ELEMENT databuffer_get(UART_TIMEOUT_T timeout) +{ + DATA_ELEMENT retval; + + TickType_t tickTimeout; + + if(timeout == 0) + tickTimeout = portMAX_DELAY; + else + tickTimeout = pdMS_TO_TICKS(timeout); + + if(xStreamBufferReceive(USART_RxBuffer, &retval, 1, tickTimeout) == 0) + return BUFFER_NO_DATA; + + return retval; +} + +/* + * Similar but from GPS buffer + */ +#if __ENABLE_GPS +// return the number of byte in the buffer +size_t gpsbuffer_bytesInBuffer(void) +{ + size_t nBytes = xStreamBufferBytesAvailable(LPUART_RxBuffer); + return nBytes; +} +// get a bytes +DATA_ELEMENT gpsbuffer_get(UART_TIMEOUT_T timeout) +{ + DATA_ELEMENT retval; + + TickType_t tickTimeout; + + if(timeout == 0) + tickTimeout = portMAX_DELAY; + else + tickTimeout = pdMS_TO_TICKS(timeout); + + if(xStreamBufferReceive(LPUART_RxBuffer, &retval, 1, tickTimeout) == 0) + return BUFFER_NO_DATA; + + return retval; +} +#endif + +// see if the buffer contains a keyword, save data up to it if needed +BOOL databuffer_contains(const char *tag, UART_TIMEOUT_T rx_timeout, BOOL saveData, char *SaveBuffer) +{ + DATA_ELEMENT c; + + uint8_t tagSize = (uint8_t)(strlen(tag) & 0xff); + uint8_t tagLen = tagSize; + + const char *tagAddr = tag; + + while ((c = databuffer_get(rx_timeout)) != BUFFER_NO_DATA) { + if (c == *tagAddr) { + if (--tagLen == 0) { + if(saveData) { + *SaveBuffer++ = c; + *SaveBuffer = '\0'; + } + return TRUE; + } + tagAddr++; + } + else { + tagAddr = tag; + tagLen = tagSize; + } + if(saveData) + *SaveBuffer++ = c; + } + if(saveData) + *SaveBuffer = '\0'; + return FALSE; +} + +/* + * Low level HAL interaction functions + */ + +// send a packet (with timeout) +BOOL USART_Send_String(const char *string, size_t len) +{ + // send using DMA + uint16_t dataLen = (uint16_t)len; + if((HAL_UART_Transmit_DMA(&huart1, (const uint8_t *)string, dataLen)) != HAL_OK) + return FALSE; + + // wait for completion.. + if((xSemaphoreTake(txCompleted, pdMS_TO_TICKS(TX_TIMEOUT))) == pdTRUE) + return TRUE; + + return FALSE; +} + +// send a packet (with timeout) +BOOL USART_Send_Char(const char c) +{ + + // send using interrupt + HAL_UART_Transmit_IT(&huart1, (const uint8_t *)&c, 1); + + // wait for completion.. + if((xSemaphoreTake(txCompleted, pdMS_TO_TICKS(TX_TIMEOUT))) == pdTRUE) + return TRUE; + + return FALSE; +} + +// print a string to the UAR/T +void USART_Print_string(char *format, ...) +{ + // process the arg list + va_list argptr; + va_start(argptr, format); + vsprintf(usartPrintBuffer,format, argptr); + va_end(argptr); + + USART_Send_String(usartPrintBuffer, strlen(usartPrintBuffer)); +} + + +// Transmit completed: trigger semaphore +void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) +{ +#if __ENABLE_GPS + // Not interested in LPUART + if(huart->Instance == LPUART1) + return; +#endif + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR(txCompleted, &xHigherPriorityTaskWoken); + +} + +// receive a byte with DMA, wait for DMA completed interrupt +void USART_Receive_char(void) +{ + HAL_UART_Receive_IT(&huart1,(uint8_t *)USARTRxChar,1); +} + + +// callback function when rx is completed: overrides previous +// __weak definition +// NB: Console USART and GPS LPUART share the same HAL routine +void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) +{ +#if __ENABLE_GPS + // service the LPUART + if(huart->Instance == LPUART1) { + xStreamBufferSendFromISR(LPUART_RxBuffer, LPUARTRxChar, 1, NULL); + HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)LPUARTRxChar,1); + return; + } +#endif + // service the USART + xStreamBufferSendFromISR(USART_RxBuffer, USARTRxChar, 1, NULL); + HAL_UART_Receive_IT(&huart1,(uint8_t *)USARTRxChar,1); +} + +#if __ENABLE_GPS +// send a string to the LPUART +void LPUART_Send_String(char *str, uint16_t len) +{ + // send using interrupt + HAL_UART_Transmit_IT(&hlpuart1, (const uint8_t *)str, len); +} + + +// receive a byte with DMA, wait for DMA completed interrupt +void LPUART_Receive_char(void) +{ + HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)LPUARTRxChar,1); + // HAL_UARTEx_ReceiveToIdle_DMA(&hlpuart1,(uint8_t *)LPUARTRxChar,LPUART_DMA_SIZE); + //__HAL_DMA_DISABLE_IT(&hlpuart1, DMA_IT_HT); +} + + +// callback from GPS Rx +void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) +{ + xStreamBufferSendFromISR(LPUART_RxBuffer, LPUARTRxChar, Size, NULL); + HAL_UARTEx_ReceiveToIdle_DMA(&hlpuart1,(uint8_t *)LPUARTRxChar,LPUART_DMA_SIZE); +} + +#endif diff --git a/Node Firmware/IP400/Src/utils.c b/Node Firmware/IP400/Src/utils.c new file mode 100644 index 0000000..6d5e370 --- /dev/null +++ b/Node Firmware/IP400/Src/utils.c @@ -0,0 +1,146 @@ +/*--------------------------------------------------------------------------- + Project: WL33_NUCLEO_UART + + File Name: utils.c + + Author: MartinA + + Description: utility routines + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version, provided this copyright notice + is included. + + Copyright (c) Alberta Digital Radio Communications Society + All rights reserved. + + Revision History: + +---------------------------------------------------------------------------*/ +#include + +#include "types.h" +#include "utils.h" + +// ascii to decimal +int ascii2Dec(char *dec) +{ + int retval = 0; + int sgn = 1; + while (*dec) { + if(*dec == '-') + sgn = -1; + else retval = retval*10 + (*dec - '0'); + dec++; + } + + return retval*sgn; +} + +// nibble to ascii +char nib2ascii(uint8_t nib) +{ + char anib = (char)(nib + '0'); + anib = (anib > '9') ? anib+('A'-'9') : anib; + return anib; +} + +// hex to ascii +void hex2ascii(uint8_t hex, char *buf) +{ + *buf++= nib2ascii(hex>>4); + *buf = nib2ascii(hex & 0xf); +} + +// see if an entry is floating point +BOOL isfloat(char *val) +{ + while(*val) + if(*val++ == '.') + return 1U; + return 0U; +} + +// convert an ascii string to a double +// until we encounter the decimal place, treat it the same as an integer. +// after that, scale each digit down appropriately +// +double ascii2double(char *val) +{ + double retval = 0; + int power = -1, inc = 1; + int sgn = 1; + + while(*val) { + + switch(*val) { + + case '-': + sgn = -1; + break; + + case '.': + power = -1; + inc = -1; + break; + + default: + if(inc > 0) { + retval = retval*10 + (*val - '0'); + } else { + retval += pow(10.0, power) * (*val - '0'); + power += inc; + } + break; + } + val++; + } + return retval * sgn; +} + +// Your basic linux-stle (argv, argc) parser based on delimiters +// string is destroyed +int explode_string(char *str, char *strp[], int limit, char delim, char quote) +{ +int i,l,inquo; + + inquo = 0; + i = 0; + strp[i++] = str; + if (!*str) + { + strp[0] = 0; + return(0); + } + for(l = 0; *str && (l < limit) ; str++) + { + if(quote) + { + if (*str == quote) + { + if (inquo) + { + *str = 0; + inquo = 0; + } + else + { + strp[i - 1] = str + 1; + inquo = 1; + } + } + } + if ((*str == delim) && (!inquo)) + { + *str = 0; + l++; + strp[i++] = str + 1; + } + } + strp[i] = 0; + return(i); + +} +