IP400/Node Firmware/IP400/Src/spi.c
2025-06-30 20:37:17 -06:00

387 lines
11 KiB
C

/*---------------------------------------------------------------------------
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 <config.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
HAL_StatusTypeDef spiXfer; // last transfer status
BOOL spiExchangeComplete; // spi exchange has been completed
BOOL spiErrorOccurred;
BOOL spiActive;
uint16_t spiActivityTimer; // no activity timer
// select the correct SPI
#if _BOARD_TYPE == NUCLEO_BOARD
#define hSPI hspi1
extern SPI_HandleTypeDef hspi1; // spi handle
#else
#if __INCLUDE_SPI
#define hSPI hspi3
extern SPI_HandleTypeDef hspi3; // spi handle
#else
#define hSPI NULL // not implemented
#endif
#endif
// validate an inbound frame
BOOL isIP400Frame(uint8_t *eye);
/*
* place a frame on the queue frame content is copied
* to alloc'd memory
*/
BOOL EnqueSPIFrame(void *ip400frame)
{
IP400_FRAME *qFrame, *SrcFrame = (IP400_FRAME *)ip400frame;
uint8_t *frameBuffer;
// spi is not running...
if(!spiActive)
return FALSE;
// allocate an IP400 frame
if((qFrame=malloc(sizeof(IP400_FRAME)))== NULL)
return FALSE;
memcpy(qFrame, SrcFrame, sizeof(IP400_FRAME));
int16_t hopCount = qFrame->flagfld.flags.hop_count;
int16_t hTblSize = hopCount * sizeof(HOPTABLE);
// hop table is in the first part of the payload
// alloc enough for both
if((frameBuffer=malloc(SrcFrame->length+hTblSize)) == NULL) {
free(qFrame);
return FALSE;
}
if(hopCount) {
memcpy(frameBuffer, qFrame->hopTable, hTblSize);
frameBuffer += hTblSize;
}
memcpy(frameBuffer, (uint8_t *)SrcFrame->buf, SrcFrame->length);
qFrame->buf = frameBuffer;
qFrame->length = SrcFrame->length + hTblSize;
if(!enqueFrame(&spiTxQueue, qFrame))
return FALSE;
return TRUE;
}
/*
* clean up any queued frames
*/
void EmptySPIFrameQ(void)
{
while(dequeFrame(&spiTxQueue) != NULL);
return;
}
/*
* Initialize the task
*/
void SPI_Task_init(void)
{
// create the rx completed semaphore
spiExchangeComplete = FALSE;
spiErrorOccurred = FALSE;
spiTxBuffer.spiData.hdr.eye[0] = 'I';
spiTxBuffer.spiData.hdr.eye[1] = 'P';
spiTxBuffer.spiData.hdr.eye[2] = '4';
spiTxBuffer.spiData.hdr.eye[3] = 'C';
spiTxBuffer.spiData.hdr.status = NO_DATA;
spiRxBuffer.spiData.hdr.status = NO_DATA;
SPITxState = SPITXIDLE;
SPIRxState = SPIRXIDLE;
// tx (outbound) frame queue
spiTxQueue.q_forw = &spiTxQueue;
spiTxQueue.q_back = &spiTxQueue;
spiActive = FALSE; // no activity yet
spiActivityTimer = 0;
#if __INCLUDE_SPI
// start the ball rolling..
if((spiXfer = HAL_SPI_TransmitReceive_DMA(&hSPI, spiTxBuffer.rawData, spiRxBuffer.rawData, SPI_RAW_LEN)) != HAL_OK)
spiErrorOccurred = TRUE;
#endif
}
// execute the task
void SPI_Task_Exec(void)
{
static IP400_FRAME *txFrame;
static uint16_t txSegLength, txPayloadRoom, hopSize;
static uint8_t *prxData, *ptxData, nHops;
static uint16_t rxSegLen;
#if __INCLUDE_SPI
// check the status first: repost Rx if an error occurred and it is now ready
if(spiErrorOccurred) {
if(hSPI.State == HAL_SPI_STATE_READY) {
if((spiXfer = HAL_SPI_TransmitReceive_DMA(&hSPI, spiTxBuffer.rawData, spiRxBuffer.rawData, SPI_RAW_LEN)) == HAL_OK) {
spiErrorOccurred = FALSE;
}
}
}
#endif
/*
* Here we wait for an exchange to be completed
* If there is no activity for NO_SPI_TIMEOUT, then
* the other end is probably dead, so clean up
* any pending frames. If SPI is not enabled,
* the queue will also be cleaned up
*/
if(!spiExchangeComplete) {
spiActivityTimer += 1;
if(spiActivityTimer >= NO_SPI_TIMEOUT) {
EmptySPIFrameQ();
spiActive = FALSE;
spiActivityTimer = 0;
}
return;
}
spiExchangeComplete = FALSE; // reset exchange done
spiActive = TRUE; // indicate that the SPI is active..
spiActivityTimer = 0; // reset no activity timer
// scope trigger on nucleo board
#if _BOARD_TYPE==NUCLEO_BOARD // board type in use
HAL_GPIO_TogglePin(SCOPE_GPIO_Port, SCOPE_Pin);
#endif
/*
* process an outbound frame
* Fragment it if longer that 500 bytes
*/
switch(SPITxState) {
case SPITXIDLE:
if((txFrame=dequeFrame(&spiTxQueue)) == NULL) {
spiTxBuffer.spiData.hdr.status = NO_DATA;
break;
}
txSegLength = txFrame->length;
nHops = txFrame->flagfld.flags.hop_count;
hopSize = (uint16_t)nHops * sizeof(HOPTABLE);
txPayloadRoom = SPI_BUFFER_LEN - hopSize;
spiTxBuffer.spiData.hdr.status = SINGLE_FRAME;
// fragment the frame if needed
if(txFrame->length > txPayloadRoom) {
txSegLength = txPayloadRoom;
spiTxBuffer.spiData.hdr.status = FRAGMENT;
SPITxState = SPITXFRAG;
}
// copy the address fields
memcpy(&spiTxBuffer.spiData.hdr.fromCall, txFrame->source.callbytes.bytes, N_CALL);
memcpy(&spiTxBuffer.spiData.hdr.fromIP, txFrame->source.vpnBytes.vpn, N_IPBYTES);
memcpy(spiTxBuffer.spiData.hdr.toCall, txFrame->dest.callbytes.bytes, N_CALL);
memcpy(&spiTxBuffer.spiData.hdr.toIP, txFrame->dest.vpnBytes.vpn, N_IPBYTES);
// flag fields
spiTxBuffer.spiData.hdr.coding = txFrame->flagfld.flags.coding;
spiTxBuffer.spiData.hdr.hopCount = txFrame->flagfld.flags.hop_count;
spiTxBuffer.spiData.hdr.flags = (uint8_t)((txFrame->flagfld.allflags >> 8) & 0xFF);
ptxData = spiTxBuffer.spiData.buffer;
// hop table
if(txFrame->flagfld.flags.hoptable) {
memcpy(ptxData, txFrame->hopTable, hopSize);
ptxData += hopSize;
free(txFrame->hopTable);
}
// and now the data
memcpy(ptxData, txFrame->buf, txSegLength);
spiTxBuffer.spiData.hdr.offset_hi = spiTxBuffer.spiData.hdr.offset_lo = 0;
spiTxBuffer.spiData.hdr.length_hi = ((txSegLength + hopSize) >> 8);
spiTxBuffer.spiData.hdr.length_lo = ((txSegLength + hopSize) & 0xFF);
// release memory if only a single frame
if(spiTxBuffer.spiData.hdr.status == SINGLE_FRAME)
{
free(txFrame->buf);
free(txFrame);
}
break;
case SPITXFRAG:
uint16_t offset = ((uint16_t)(spiTxBuffer.spiData.hdr.offset_hi) << 8) + (uint16_t)spiTxBuffer.spiData.hdr.offset_lo;
uint16_t prevLen = ((uint16_t)(spiTxBuffer.spiData.hdr.length_hi) << 8) + (uint16_t)spiTxBuffer.spiData.hdr.length_lo;
offset += prevLen;
txFrame->length -= prevLen;
txSegLength = txFrame->length;
if(txFrame->length > SPI_BUFFER_LEN) {
spiTxBuffer.spiData.hdr.status = FRAGMENT;
txSegLength = SPI_BUFFER_LEN;
} else {
spiTxBuffer.spiData.hdr.status = LAST_FRAGMENT;
SPITxState = SPITXIDLE;
}
// send the next fragment
void *fragAddr = txFrame->buf + offset;
memcpy(spiTxBuffer.spiData.buffer, fragAddr, txSegLength);
spiTxBuffer.spiData.hdr.offset_hi = (offset >> 8);
spiTxBuffer.spiData.hdr.offset_lo = (offset & 0xff);
spiTxBuffer.spiData.hdr.length_hi = (txSegLength >> 8);
spiTxBuffer.spiData.hdr.length_lo = (txSegLength & 0xFF);
// done with frame
if(spiTxBuffer.spiData.hdr.status == LAST_FRAGMENT)
{
free(txFrame->buf);
free(txFrame);
}
break;
}
/*
* Inbound frame. Reassemble fragments if needed...
*/
switch(SPIRxState) {
case SPIRXIDLE:
spiFrameStatus rstat = spiRxBuffer.spiData.hdr.status;
if((rstat == NO_DATA) || (rstat >= NUM_STATS))
break;
if(!isIP400Frame(spiRxBuffer.spiData.hdr.eye))
break;
rxSegLen = (spiRxBuffer.spiData.hdr.length_hi << 8) + spiRxBuffer.spiData.hdr.length_lo;
if(rstat != SINGLE_FRAME) {
prxData = rxFrameBuffer + (spiRxBuffer.spiData.hdr.offset_hi << 8) + spiRxBuffer.spiData.hdr.offset_lo;
memcpy(prxData, spiRxBuffer.spiData.buffer, rxSegLen);
SPIRxState = SPIRXFRAG;
} else {
SendSPIFrame(&spiRxBuffer.spiData.hdr, spiRxBuffer.spiData.buffer, rxSegLen);
}
break;
case SPIRXFRAG:
uint8_t fragStat = spiRxBuffer.spiData.hdr.status;
uint16_t offset = (spiTxBuffer.spiData.hdr.offset_hi << 8) + spiTxBuffer.spiData.hdr.offset_lo;
prxData = rxFrameBuffer + offset;
rxSegLen = (spiRxBuffer.spiData.hdr.length_hi << 8) + spiRxBuffer.spiData.hdr.length_lo;
memcpy(prxData, spiRxBuffer.spiData.buffer, rxSegLen);
if(fragStat == LAST_FRAGMENT) {
uint16_t frameLen = offset + rxSegLen;
spiRxBuffer.spiData.hdr.length_hi = (frameLen >> 8);
spiRxBuffer.spiData.hdr.length_lo = (frameLen & 0xFF);
// process frame for tx here...
SPIRxState = SPIRXIDLE; // placeholder
}
break;
}
}
// test an incoming frame
BOOL isIP400Frame(uint8_t *eye)
{
if((eye[0] != 'I') || (eye[1] != 'P'))
return FALSE;
if((eye[2] != '4') || (eye[3] != 'C'))
return FALSE;
return TRUE;
}
#if __INCLUDE_SPI
// rx done callback
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef * hspi)
{
// set data valid and start a new transfer
if(hspi->State == HAL_SPI_STATE_READY) {
spiExchangeComplete = TRUE;
spiXfer = HAL_SPI_TransmitReceive_DMA(&hSPI, spiTxBuffer.rawData, spiRxBuffer.rawData, SPI_RAW_LEN);
if(spiXfer != HAL_OK)
spiErrorOccurred = TRUE;
return;
}
spiErrorOccurred = TRUE; // spi not ready
}
#endif