Added V1.0 Node firmware

This commit is contained in:
adrcs 2025-03-28 12:54:00 -06:00 committed by GitHub
parent 510f6c6bd7
commit 10ddfa65ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 5317 additions and 0 deletions

View file

@ -0,0 +1,61 @@
/*---------------------------------------------------------------------------
Project: WL33_NUCLEO_UART
Module: <module description here>
File Name: queue.h
Date Created: Jan 9, 2025
Author: MartinA
Description: <what it does>
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_ */

View file

@ -0,0 +1,208 @@
/*---------------------------------------------------------------------------
Project: WL33_NUCLEO_UART
Module: <module description here>
File Name: frame.h
Date Created: Jan 8, 2025
Author: MartinA
Description: <what it does>
Copyright © 2024-25, Alberta Digital Radio Communications Society,
All rights reserved
Revision History:
---------------------------------------------------------------------------*/
#ifndef FRAME_H_
#define FRAME_H_
#include <stdint.h>
#include "types.h"
// frame defines
#define N_CALL 4 // octets in the excess-40 compressed callsign
#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;
// callsign field
typedef struct callsign_t {
union {
uint8_t bytes[N_CALL]; // compressed callsign
uint32_t encoded;
} callbytes;
uint16_t port; // port information
} IP400_CALL;
// hop table
typedef struct hoptable_t {
union {
uint8_t bytes[N_CALL];
uint32_t encoded;
} hopAddr;
} HOPTABLE;
// complete frame
typedef struct ip400_frame_t {
IP400_CALL source; // source call sign
IP400_CALL 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_PORT_SIZE sizeof(uint16_t)
#define IP_400_CALL_SIZE (N_CALL + IP_400_PORT_SIZE)
#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_CALL *encCall, char *callsign, uint16_t *port);
// 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_ */

View file

@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------
Project: WL33_NUCLEO_UART
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 <stdint.h>
#include "types.h"
#include "config.h"
/* 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_CALL *fr, SOCKADDR_IN *ipaddr);
void GetIP172Addr(IP400_CALL *fr, SOCKADDR_IN *ipaddr);
#if __IP_GROUP == IP_10_GROUP
#define GetIPAddr GetIP10Add
#else
#define GetIPAddr GetIP172Addr
#endif
#endif /* INC_IP_H_ */

View file

@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------
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_
// 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
};
void SetLEDMode(uint8_t mode);
#endif /* INC_LED_H_ */

View file

@ -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 <reent.h>
#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 */

View file

@ -0,0 +1,135 @@
/*---------------------------------------------------------------------------
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 <stm32wl3x_hal_mrsubg.h>
#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);
BOOL WriteSetup(void);
void SetDefSetup(void);
BOOL UpdateSetup(void);
#endif /* INC_SETUP_H_ */

View file

@ -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 fromPort[IP_400_PORT_SIZE]; // from port
uint8_t toCall[N_CALL]; // to callsign
uint8_t toPort[IP_400_PORT_SIZE]; // 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_ */

View file

@ -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_ */

View file

@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------
Project: WL33_NUCLEO_UART
File Name: tod.h
Author: MartinA
Creation Date: Jan 23, 2025
Description: <decription here?
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_TOD_H_
#define INC_TOD_H_
#include <stdint.h>
// 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_ */

View file

@ -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 <stdint.h>
#include <stddef.h>
// 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_ */

View file

@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------
Project: WL33_NUCLEO_UART
File Name: usart.h
Author: MartinA
Creation Date: Jan 12, 2025
Description: <decription here?
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_USART_H_
#define INC_USART_H_
#include "types.h"
#include "main.h"
// definitions
typedef uint32_t UART_TIMEOUT_T; // uart timer type
typedef uint16_t BUFFER_SIZE_T; // buffer size type
typedef uint8_t DATA_ELEMENT; // buffer data element
// uart HAL handle
extern UART_HandleTypeDef huart1; // console UART
extern UART_HandleTypeDef hlpuart1 ; // GPS UART
// logger defines
#define LOG_NOTICE 0 // notice
#define LOG_ERROR 1 // error
#define LOG_SEVERE 2 // severe error
// logger
void logger(int severity, char *format, ...);
// API calls
void USART_RxBuffer_reset(void);
size_t databuffer_bytesInBuffer(void);
DATA_ELEMENT databuffer_get(UART_TIMEOUT_T timeout);
BOOL databuffer_contains(const char *tag, UART_TIMEOUT_T rx_timeout, BOOL saveData, char *SaveBuffer);
BOOL USART_Send_String(const char *string, size_t len);
BOOL USART_Send_Char(const char c);
void USART_Print_string(char *format, ...);
// same as USART but for LPUART: no Tx functions
void LPUART_RxBuffer_reset(void);
size_t gpsbuffer_bytesInBuffer(void);
DATA_ELEMENT gpsbuffer_get(UART_TIMEOUT_T timeout);
void LPUART_Send_String(char *str, uint16_t len);
#endif /* INC_USART_H_ */

View file

@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------
Project: WL33_NUCLEO_UART
File Name: utils.h
Author: MartinA
Creation Date: Jan 20, 2025
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) 2024-25 Alberta Digital Radio Communications Society
Revision History:
---------------------------------------------------------------------------*/
#ifndef INC_UTILS_H_
#define INC_UTILS_H_
// replacement for missing itoa
int ascii2Dec(char *dec);
// and its double counterpart...
double ascii2double(char *val);
// hex to ascii
void hex2ascii(uint8_t hex, char *buf);
// check an entry for floating point
BOOL isfloat(char *val);
// useful in parsing NMEA sentences
int explode_string(char *str, char *strp[], int limit, char delim, char quote);
#endif /* INC_UTILS_H_ */

View file

@ -0,0 +1,402 @@
/*---------------------------------------------------------------------------
Project: WL33_NUCLEO_UART
File Name: beacon.c
Author: MartinA
Description: Beacon task. Sends a beacon frame periodically, based on timer in the setup
structure. The beacon include position data in a readable format, which
can come from a GPS receiver or the setup struct. Lat/Long data
is sent in DDMM.MMMMM format.
Define __ENABLE_GPS to enable the code.
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 <math.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <cmsis_os2.h>
#include <FreeRTOS.h>
#include <stm32wl3x_hal.h>
#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<nBytesinBuff;i++) {
c = (char)gpsbuffer_get(0);
switch(NMEAState) {
// waiting for a start of message
case NMEA_STATE_SOM:
if(c != '$')
break;
GPSBufPtr = GPSMsgBuf;
NMEAState=NMEA_STATE_MSG;
break;
// collecting chars in a message
case NMEA_STATE_MSG:
*GPSBufPtr++ = c;
GPSMsgSize = GPSBufPtr - GPSMsgBuf;
if((c == '*') || (GPSMsgSize >= 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<strlen(nmeaCmd);j++)
cksum ^= nmeaCmd[j];
for(int i=0;i<N_NMEA_SEN;i++) {
*buf = ',';
cksum ^= *buf++;
*buf = senCmds[i] + '0';
cksum ^= *buf++;
}
*buf++ = '*';
hex2ascii(cksum, buf);
buf += 2;
*buf++ = '\r';
*buf++ = '\n';
*buf = '\0';
uint16_t buflen = buf - cmdBuf;
LPUART_Send_String(cmdBuf, buflen);
}
/*
* Process an inbound GPS message
*/
BOOL processGPSMessage(uint8_t *GPSdata, uint8_t bufferSize)
{
char *msg = (char *)GPSdata;
uint8_t nParams = explode_string(msg, gpsFlds, bufferSize, ',', '"');
if ((nParams == 0) || (nParams < NMEA_MIN_FLDS))
return FALSE;
// brute force position fix
// check for message with GGA at the end
char *gpsTag = gpsFlds[NMEA_TAG];
gpsTag += strlen(gpsTag)-strlen(nmeaMsgTag);
if(strcmp(gpsTag, nmeaMsgTag))
return FALSE;
strcpy(GPSFixTime, gpsFlds[NMEA_TIMESTAMP]);
strcpy(GPSLat, gpsFlds[NMEA_LATITUDE]);
strcat(GPSLat, gpsFlds[NMEA_NS_HEMI]);
strcpy(GPSLong, gpsFlds[NMEA_LONGITUDE]);
strcat(GPSLong, gpsFlds[NMEA_EW_HEMI]);
return TRUE;
}
/*
* echo the last GPS message on the console
*/
void GPSEcho(void)
{
if(!gpsEchoReady)
return;
USART_Print_string("%s\r\n", (char *)GPSEchoBuf);
gpsEchoReady=FALSE;
}
#endif

View file

@ -0,0 +1,194 @@
/*---------------------------------------------------------------------------
Project: IP400
Module: Compress and expand a callsign
File Name: callsign.c
Author: MartinA
Creation Date: Jan 9, 2025
Description: Comresses an ASCII string of callsign characters. A callsign can
have up to 6 characters to fit into the four byte field, if longer
the rest are placed in the payload of the data frame. Callsigns can
be extended with a '-' character, in this case the call is removed
and padded before conversion, and the remainder is also in the
frame payload.
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 <string.h>
#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<RADIX_40;i++) {
if(alphabet[i] == byte)
return i;
}
return 0;
}
// decode a byte back into ASCII
char alphaDecode(uint32_t alpha)
{
// numeric
if((alpha >= 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;i<len;i++) {
uint32_t current = alphaEncode(src[i]);
chunk = current + chunk*RADIX_40;
}
*enc = chunk;
}
// encode a callsign: if less than max_call, then just encode into the frame,
// else continue in the data portion of the frame
uint8_t callEncode(char *callsign, uint16_t port, IP400_FRAME *frame, uint8_t dest, uint8_t offset)
{
int len = strlen(callsign);
uint32_t encChunk;
uint32_t *p = (uint32_t *)frame->buf;
p += offset;
if(dest == DEST_CALLSIGN)
frame->dest.port = port;
else
frame->source.port = port;
// 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;k<nChunks;k++) {
EncodeChunk(cll, MAX_CALL, &encChunk);
if(k==0) {
if(dest == DEST_CALLSIGN)
frame->dest.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_CALL *encCall, char *callsign, uint16_t *port)
{
char tmpBuf[10], *p = tmpBuf;
int i;
uint32_t encoded = encCall->callbytes.encoded;
for(i=0;i<MAX_CALL;i++) {
*p++ = alphaDecode(encoded % RADIX_40);
encoded /= RADIX_40;
}
*p = '\0';
for(i=strlen(tmpBuf)-1;i>=0;i--)
*callsign++ = tmpBuf[i];
*callsign = '\0';
*port = encCall->port;
return TRUE;
}

View file

@ -0,0 +1,301 @@
/*---------------------------------------------------------------------------
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 <stdint.h>
#include <string.h>
#include <malloc.h>
#include "setup.h"
#include "frame.h"
#include "types.h"
#include "usart.h"
#include "streambuffer.h"
#include "dataq.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<nBytesinBuff;i++) {
c=databuffer_get(0);
if(deleteMode) {
if((c != KEY_DEL) && (c != KEY_BKSP)) {
USART_Print_string("\\%c", c);
if(keyPos < MAX_KEY)
keyBuffer[keyPos++] = c;
deleteMode = FALSE;
} else {
if(keyPos > 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, 0, dest_call, 0, buffer, len, repeat);
}
/*
* Print a received frame on the console
*/
void PrintFrame(IP400_FRAME *FrameBytes)
{
char printBuf[250];
char decCall[100];
uint16_t port;
uint16_t dataLen = FrameBytes->length;
// dump mode for header debugging
if(dumpMode) {
/* print the received data */
USART_Print_string("RX - Data received: [ ");
for(uint8_t i=0; i<sizeof(IP400_FRAME); i++)
USART_Print_string("%02x ", FrameBytes[i]);
USART_Print_string("]\r\n");
return;
}
// source call
callDecode(&FrameBytes->source, decCall, &port);
USART_Print_string("%s(%d)>", decCall, port);
// dest call
if((FrameBytes->dest.callbytes.bytes[0] == BROADCAST_ADDR) &&
(FrameBytes->dest.callbytes.bytes[1] == BROADCAST_ADDR)) {
USART_Print_string("BROADCAST(%d)", FrameBytes->dest.port);
} else {
callDecode(&FrameBytes->dest, decCall, &port);
USART_Print_string("%s(%d)", decCall, port);
}
// 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);
}

View file

@ -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 <malloc.h>
#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;
}

View file

@ -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 <cmsis_os2.h>
#include <stdlib.h>
#include <string.h>
#include <config.h>
#include <stm32wl3x_hal_mrsubg.h>
#if _BOARD_TYPE==NUCLEO_BOARD
#include <stm32wl3x_nucleo.h>
#endif
#include <cmsis_os2.h>
#include <FreeRTOS.h>
#include <semphr.h>
#include "frame.h"
#include "dataq.h"
#include "setup.h"
#include "tasks.h"
#include "led.h"
#include "spi.h"
#include "usart.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
IP400_CALL stnCall; // station callsign
// 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 srcPort, char *destCall, uint16_t dstPort, 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, srcPort, txFrame, SRC_CALLSIGN, 0);
offset = callEncode(destCall, dstPort, 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
uint8_t offset = callEncode(srcCall, 0, bcnFrame, SRC_CALLSIGN, 0);
callEncode("FFFF", 0, bcnFrame, DEST_CALLSIGN, 0);
// since this is the first frame out of the gate,
// record the src callsign
stnCall.callbytes.encoded = bcnFrame->source.callbytes.encoded;
// 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
}
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;
}
// debug option to use a supplied call instead of the station callsign
// NB: use the station one in normal use, as a different call could cause havoc
uint8_t *srcCall;
#if DEBUG_RX_CALL
srcCall = spiHdr->fromCall;
#else
srcCall = stnCall.callbytes.bytes;
#endif
memcpy(spiFrame->source.callbytes.bytes, srcCall, N_CALL);
spiFrame->source.port = (uint16_t)(spiHdr->fromPort[0]<<8) + (uint16_t)(spiHdr->fromPort[1]);
memcpy(spiFrame->dest.callbytes.bytes, spiHdr->toCall, N_CALL);
spiFrame->dest.port = (uint16_t)(spiHdr->toPort[0]<<8) + (uint16_t)(spiHdr->toPort[1]);
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];
uint16_t port;;
// check if I am the originator call sign
callDecode(&frame->source, decCall, &port);
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;
for(int i=0;i<frame->flagfld.flags.hop_count; i++)
if(htable[i].hopAddr.encoded == stnCall.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);
}
HOPTABLE *table = (HOPTABLE *)rptFrame->hopTable;
table[hopCount].hopAddr.encoded = stnCall.callbytes.encoded;
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();
}

View file

@ -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;
}

View file

@ -0,0 +1,88 @@
/*---------------------------------------------------------------------------
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"
#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
};
#define NETMASK 0xF8 // mask for last byte
// return the IP address
void GetIP10Addr(IP400_CALL *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 = fr->port;
}
void GetIP172Addr(IP400_CALL *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 = fr->port;
}

View file

@ -0,0 +1,289 @@
/*---------------------------------------------------------------------------
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 <config.h>
// include LED defs from the right place
#if _BOARD_TYPE==NUCLEO_BOARD
#include <stm32wl3x_nucleo.h>
#else
#include <main.h>
#endif
// config
#define REVERSE_LEADS 1 // reverse Bi-color LED leads
// 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
BSP_LED_Off(LED_RED);
BSP_LED_Off(LED_GREEN);
BSP_LED_Off(LED_BLUE);
#else
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_A_GPIO_Port, LED_A_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_K_GPIO_Port, LED_K_Pin, GPIO_PIN_RESET);
#endif
}
void setTxLED(BOOL state)
{
#if _BOARD_TYPE
if(state)
BSP_LED_On(LED_BLUE);
else
BSP_LED_Off(LED_BLUE);
#else
if(state)
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
else
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
#endif
}
// set fwd direction for bidir LED
#if REVERSE_LEADS
void LED_SetGreen(void)
#else
void LED_SetRed(void)
#endif
{
#if _BOARD_TYPE
BSP_LED_On(LED_GREEN);
#else
HAL_GPIO_WritePin(LED_A_GPIO_Port, LED_A_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LED_K_GPIO_Port, LED_K_Pin, GPIO_PIN_RESET);
#endif
}
// set bwd direction for bidir LED
#if REVERSE_LEADS
void LED_SetRed(void)
#else
void LED_SetGreen(void)
#endif
{
#if _BOARD_TYPE
BSP_LED_On(LED_RED);
#else
HAL_GPIO_WritePin(LED_A_GPIO_Port, LED_A_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_K_GPIO_Port, LED_K_Pin, GPIO_PIN_SET);
#endif
}

View file

@ -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 <stdio.h>
#include <stdarg.h>
#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;
}
}

View file

@ -0,0 +1,839 @@
/*---------------------------------------------------------------------------
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 <string.h>
#include <malloc.h>
#include "types.h"
#include "usart.h"
#include "streambuffer.h"
#include "setup.h"
#include "tasks.h"
#include "utils.h"
#include "tod.h"
#include "config.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);
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\n", tod.Hours, tod.Minutes, tod.Seconds);
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 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;i<nBytesinBuff;i++)
if((c=databuffer_get(0)) == ASCII_RET)
menuState = MENU_SHOWING;
return;
case MENU_SHOWING:
printMenu();
menuState = MENU_SELECTING;
return;
case MENU_SELECTING:
if((sel_item=getMenuItem()) == NO_ITEM)
return;
menuState = MENU_SELECTED;
break;
case MENU_SELECTED:
m=menuContents[activeMenu].menus;
m += sel_item;
switch((*m->func)()) {
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;i<nMenuLines;i++) {
m=menuContents[activeMenu].menus;
m += i;
menu[0] = m->selChar;
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;i<nBytesinBuff;i++) {
if((c=databuffer_get(0)) != BUFFER_NO_DATA) {
// translate and echo
c = islower(c) ? toupper(c) : c;
USART_Send_Char(c);
// find the correct processing routine
int nMenuLines = menuContents[activeMenu].nMenuLines;
for(int j=0;j<nMenuLines;j++) {
m=menuContents[activeMenu].menus;
m += j;
if(m->selChar == 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<nBytesinBuff;i++) {
c=databuffer_get(0);
if(delMode) {
if((c != KEY_DEL) && (c != KEY_BKSP)) {
USART_Print_string("\\%c", c);
if(pos < MAX_ENTRY)
keyBuffer[pos++] = c;
delMode = FALSE;
} else {
if(pos > 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((newValue<min) || (newValue>max)) {
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((newValue<min) || (newValue>max)) {
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((len<min) || (len>max)) {
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<N_RADIO_ERRS;i++)
if(errs & radio_errs[i].mask)
USART_Print_string("%s\r\n", radio_errs[i].errmsg);
}
/*
* Print the FSM state
*/
char *fsm_states[FSM_N_FSM_STATES] = {
"Idle",
"Enable RF registers",
"Wait for active 2",
"Active 2",
"Enable current",
"Synth setup",
"VCO calibration",
"Lock Rx and Rx",
"Llock on Rx",
"Enable PA",
"Transmit",
"Analog power down",
"End transmit",
"lock on Rx",
"Enable Rx",
"Enable LNA",
"Recieve",
"End rx",
"Synth power down"
};
void Print_FSM_state(uint8_t state)
{
if(state > 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

View file

@ -0,0 +1,303 @@
/*---------------------------------------------------------------------------
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 <cmsis_os2.h>
#include <sys/queue.h>
#include <stdint.h>
#include <FreeRTOS.h>
#include <task.h>
#include <string.h>
#include <stdio.h>
#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_CALL encCall; // encoded callsign
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
uint32_t ipAddress; // IP address
} 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_CALL *call);
void AddMeshEntry(IP400_FRAME *frameData, int16_t rssi, BOOL isBeacon);
// task initialization
void Mesh_Task_Init(void)
{
for(int i=0;i<MAX_MESH_ENTRIES;i++)
MeshTable[i].status = MESHTBL_UNUSED;
}
// process a beacon packet: if we don't have it, enter it
// otherwise update the last time heard
void Mesh_ProcessBeacon(void *rxFrame, uint32_t rssi)
{
IP400_FRAME *frameData = (IP400_FRAME *)rxFrame;
TIMEOFDAY current;
getTOD(&current);
int16_t actRSSI = rssi/2 - RSSI_SCALAR;
int entryNum;
// see if we already know about it
// if so, just update last heard, expected sequence and rssi
// NB: firt frame is sent with an all '1's sequence number
if((entryNum = findCall(&frameData->source)) != 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(&current);
SOCKADDR_IN ipAddr;
// grab call and capabilities
newEntry.encCall = 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;
GetIPAddr(&newEntry.encCall, &ipAddr);
newEntry.ipAddress = ipAddr.sin_addr.S_un.S_addr;
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_CALL *call)
{
for(int i=0;i<nMeshEntries;i++) {
if(MeshTable[i].encCall.callbytes.encoded == call->callbytes.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];
uint16_t port;
SOCKADDR_IN ipAddr;
USART_Print_string("Call(Port)\tIP Addr\t\tRSSI\tNext Seq\tLast Heard\tHops\tCapabilities\r\n");
for(int i=0;i<nMeshEntries;i++) {
if(MeshTable[i].status == MESHTBL_VALID) {
callDecode(&MeshTable[i].encCall, decodedCall, &port);
GetIPAddr(&MeshTable[i].encCall, &ipAddr);
USART_Print_string("%s(%d)\t%d.%d.%d.%d\t%-03d\t%04d\t\t%02d:%02d:%02d\t%d\t%s %d dBm\r\n",
decodedCall, port,
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,
MeshTable[i].lastRssi,
MeshTable[i].nextSeq,
MeshTable[i].lastHeard.Hours, MeshTable[i].lastHeard.Minutes, MeshTable[i].lastHeard.Seconds,
MeshTable[i].hopCount,
GetCapabilities(MeshTable[i].capabilities), MeshTable[i].txPower);
}
USART_Print_string("\r\n\n");
}
}

View file

@ -0,0 +1,305 @@
/*---------------------------------------------------------------------------
Project: WL33_NUCLEO_UART
File Name: setup.c
Author: MartinA
Description: Holds and displays the 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) Alberta Digital Radio Communications Society
All rights reserved.
Revision History:
---------------------------------------------------------------------------*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <main.h>
#include "types.h"
#include "setup.h"
#include "usart.h"
#define USE_HAL
// flash stuff
#define FLASH_PAGE_ADDR ((uint32_t)0x1007F800)
#define FLASH_PAGE_NUM 127
static FLASH_EraseInitTypeDef EraseInitStruct;
// Default Setup Data
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.0
.params.FirmwareVerMinor = 0,
.params.Magic = SETUP_MAGIC,
.params.SetupCRC = 0
};
char prtBuf[100];
// 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);
}
/*
* 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);
}
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;
}
// 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);
}

View file

@ -0,0 +1,364 @@
/*---------------------------------------------------------------------------
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 <cmsis_os2.h>
#include <FreeRTOS.h>
#include <semphr.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include "types.h"
#include "main.h"
#include "spi.h"
#include "dataq.h"
#include "tasks.h"
#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
extern SPI_HandleTypeDef hspi1; // spi handle
HAL_StatusTypeDef spiXfer; // last transfer status
BOOL spiExchangeComplete; // spi exchange has been completed
BOOL spiErrorOccurred;
BOOL spiActive;
uint16_t spiActivityTimer; // no activity timer
// 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;
// start the ball rolling..
if((spiXfer = HAL_SPI_TransmitReceive_DMA(&hspi1, spiTxBuffer.rawData, spiRxBuffer.rawData, SPI_RAW_LEN)) != HAL_OK)
spiErrorOccurred = TRUE;
}
// 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;
// check the status first: repost Rx if an error occurred and it is now ready
if(spiErrorOccurred) {
if(hspi1.State == HAL_SPI_STATE_READY) {
if((spiXfer = HAL_SPI_TransmitReceive_DMA(&hspi1, spiTxBuffer.rawData, spiRxBuffer.rawData, SPI_RAW_LEN)) == HAL_OK) {
spiErrorOccurred = FALSE;
}
}
}
/*
* 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(!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);
#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);
spiTxBuffer.spiData.hdr.fromPort[0] = (uint8_t)(txFrame->source.port << 8);
spiTxBuffer.spiData.hdr.fromPort[1] = (uint8_t)(txFrame->source.port & 0xff);
memcpy(spiTxBuffer.spiData.hdr.toCall, txFrame->dest.callbytes.bytes, N_CALL);
spiTxBuffer.spiData.hdr.toPort[0] = (uint8_t)(txFrame->dest.port << 8);
spiTxBuffer.spiData.hdr.toPort[1] = (uint8_t)(txFrame->dest.port & 0xff);
// 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;
}
}
// 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(&hspi1, spiTxBuffer.rawData, spiRxBuffer.rawData, SPI_RAW_LEN);
if(spiXfer != HAL_OK)
spiErrorOccurred = TRUE;
return;
}
spiErrorOccurred = TRUE; // spi not ready
}
// 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;
}

View file

@ -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;
}

View file

@ -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 <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <cmsis_os2.h>
#include <FreeRTOS.h>
#include <semphr.h>
#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

View file

@ -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 <math.h>
#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);
}

Binary file not shown.

11
Node Firmware/Read.me Normal file
View file

@ -0,0 +1,11 @@
This directory contains the code sources. The current source code is at Rev level 1.0.
There are three sub-directories:
Bin - precompiled binaries of the node code
IP400 - current sources for the IP400 code
Platforms - Zipped projects for the CubeIDE for different hardware platforms.
Currently supported platforms:
Nucleo CC Experimenter node
Release notes are in the node software document in the documentation directory.