mirror of
https://github.com/lowobservable/coax.git
synced 2026-02-28 17:39:38 +00:00
574 lines
14 KiB
C++
574 lines
14 KiB
C++
// Copyright (c) 2020, Andrew Kay
|
|
//
|
|
// Permission to use, copy, modify, and/or distribute this software for any
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
// copyright notice and this permission notice appear in all copies.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
#include <cstring>
|
|
|
|
#include "stm32l4xx_ll_spi.h"
|
|
|
|
#include "pins.h"
|
|
#include "debug.h"
|
|
|
|
#include "coax.h"
|
|
|
|
Coax::Coax(SPICoaxTransceiver &spiCoaxTransceiver, volatile uint16_t *buffer,
|
|
size_t bufferSize) :
|
|
_spiCoaxTransceiver(spiCoaxTransceiver),
|
|
_txProtocol(CoaxProtocol::_3270),
|
|
_rxProtocol(CoaxProtocol::_3270),
|
|
_parity(CoaxParity::Even),
|
|
_buffer(buffer),
|
|
_bufferSize(bufferSize)
|
|
{
|
|
_isInitialized = false;
|
|
}
|
|
|
|
bool Coax::init()
|
|
{
|
|
_isInitialized = false;
|
|
|
|
_interruptState = COAX_INTERRUPT_STATE_DISABLED;
|
|
|
|
if (!_spiCoaxTransceiver.init()) {
|
|
return false;
|
|
}
|
|
|
|
_spiCoaxTransceiver.setTXProtocol(_txProtocol);
|
|
_spiCoaxTransceiver.setTXParity(_parity);
|
|
|
|
_spiCoaxTransceiver.setRXProtocol(_rxProtocol);
|
|
_spiCoaxTransceiver.setRXParity(_parity);
|
|
|
|
_isInitialized = true;
|
|
|
|
return _isInitialized;
|
|
}
|
|
|
|
void Coax::reset()
|
|
{
|
|
if (!_isInitialized) {
|
|
return;
|
|
}
|
|
|
|
_interruptState = COAX_INTERRUPT_STATE_DISABLED;
|
|
|
|
_spiCoaxTransceiver.reset();
|
|
|
|
_spiCoaxTransceiver.setTXProtocol(_txProtocol);
|
|
_spiCoaxTransceiver.setTXParity(_parity);
|
|
|
|
_spiCoaxTransceiver.setRXProtocol(_rxProtocol);
|
|
_spiCoaxTransceiver.setRXParity(_parity);
|
|
}
|
|
|
|
void Coax::setTXProtocol(CoaxProtocol protocol)
|
|
{
|
|
if (!_isInitialized) {
|
|
_txProtocol = protocol;
|
|
return;
|
|
}
|
|
|
|
if (_txProtocol == protocol) {
|
|
return;
|
|
}
|
|
|
|
_spiCoaxTransceiver.setTXProtocol(protocol);
|
|
|
|
_txProtocol = protocol;
|
|
}
|
|
|
|
void Coax::setRXProtocol(CoaxProtocol protocol)
|
|
{
|
|
if (!_isInitialized) {
|
|
_rxProtocol = protocol;
|
|
return;
|
|
}
|
|
|
|
if (_rxProtocol == protocol) {
|
|
return;
|
|
}
|
|
|
|
_spiCoaxTransceiver.setRXProtocol(protocol);
|
|
|
|
_rxProtocol = protocol;
|
|
}
|
|
|
|
void Coax::setParity(CoaxParity parity)
|
|
{
|
|
if (!_isInitialized) {
|
|
_parity = parity;
|
|
return;
|
|
}
|
|
|
|
if (_parity == parity) {
|
|
return;
|
|
}
|
|
|
|
_spiCoaxTransceiver.setTXParity(parity);
|
|
_spiCoaxTransceiver.setRXParity(parity);
|
|
|
|
_parity = parity;
|
|
}
|
|
|
|
int Coax::transmit(const uint16_t *buffer, size_t bufferCount)
|
|
{
|
|
if (!_isInitialized) {
|
|
return COAX_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
uint8_t status = _spiCoaxTransceiver.readRegister(COAX_REGISTER_STATUS);
|
|
|
|
if (status & COAX_REGISTER_STATUS_RX_ACTIVE) {
|
|
return COAX_ERROR_TX_RECEIVER_ACTIVE;
|
|
}
|
|
|
|
if (status & COAX_REGISTER_STATUS_RX_ERROR) {
|
|
Debug::trap(101);
|
|
|
|
_spiCoaxTransceiver.reset();
|
|
}
|
|
|
|
_interruptState = COAX_INTERRUPT_STATE_IDLE;
|
|
|
|
return _spiCoaxTransceiver.transmit(buffer, bufferCount);
|
|
}
|
|
|
|
int Coax::receive(uint16_t *buffer, size_t bufferSize, uint16_t timeout)
|
|
{
|
|
if (!_isInitialized) {
|
|
return COAX_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
if (timeout > 0) {
|
|
uint32_t startTime = HAL_GetTick();
|
|
|
|
while (_interruptState == COAX_INTERRUPT_STATE_IDLE) {
|
|
// https://www.forward.com.au/pfod/ArduinoProgramming/TimingDelaysInArduino.html#unsigned
|
|
if ((HAL_GetTick() - startTime) > timeout) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t startTime = HAL_GetTick();
|
|
|
|
while (!(_interruptState == COAX_INTERRUPT_STATE_RECEIVED || _interruptState == COAX_INTERRUPT_STATE_ERROR)) {
|
|
if ((HAL_GetTick() - startTime) > 1000) {
|
|
Debug::trap(102);
|
|
}
|
|
}
|
|
|
|
int count = 0;
|
|
|
|
if (_interruptState == COAX_INTERRUPT_STATE_RECEIVED) {
|
|
count = _bufferCount > bufferSize ? bufferSize : _bufferCount;
|
|
|
|
memcpy(buffer, const_cast<uint16_t *>(_buffer), count * sizeof(uint16_t));
|
|
|
|
// TODO: we should be able to manipulate _buffer and not change the state
|
|
// to allow multiple calls to this method to read a large message... but
|
|
// that is not necessary right now
|
|
} else if (_interruptState == COAX_INTERRUPT_STATE_ERROR) {
|
|
Debug::trap(103, "error = %d", _error);
|
|
|
|
count = (-1) * _error;
|
|
}
|
|
|
|
_interruptState = COAX_INTERRUPT_STATE_IDLE;
|
|
|
|
return count;
|
|
}
|
|
|
|
void Coax::handleInterrupt()
|
|
{
|
|
if (!_isInitialized) {
|
|
Debug::trap(104);
|
|
return;
|
|
}
|
|
|
|
if (_interruptState != COAX_INTERRUPT_STATE_IDLE) {
|
|
Debug::trap(105, "state = %d", _interruptState);
|
|
return;
|
|
}
|
|
|
|
_interruptState = COAX_INTERRUPT_STATE_RECEIVING;
|
|
|
|
int error = 0;
|
|
|
|
uint16_t *buffer = const_cast<uint16_t *>(_buffer);
|
|
size_t bufferSize = _bufferSize;
|
|
int bufferCount = 0;
|
|
|
|
bool isActive;
|
|
int count;
|
|
|
|
do {
|
|
// Determine if the receiver is active before reading from the FIFO to
|
|
// avoid a race condition where the FIFO is empty, the receiver is
|
|
// active but is inactive by the time we check the status.
|
|
isActive = _spiCoaxTransceiver.isRXActive();
|
|
|
|
// TODO: need to somehow handle an overflow detection here...
|
|
|
|
count = _spiCoaxTransceiver.receive(buffer, bufferSize);
|
|
|
|
if (count < 0) {
|
|
error = (-1) * count;
|
|
break;
|
|
}
|
|
|
|
bufferCount += count;
|
|
|
|
buffer += count;
|
|
bufferSize -= count;
|
|
} while (isActive || count == static_cast<int>(bufferSize));
|
|
|
|
if (error != 0) {
|
|
_bufferCount = 0;
|
|
_error = error;
|
|
|
|
_interruptState = COAX_INTERRUPT_STATE_ERROR;
|
|
} else {
|
|
_bufferCount = bufferCount;
|
|
_error = 0;
|
|
|
|
_interruptState = COAX_INTERRUPT_STATE_RECEIVED;
|
|
}
|
|
}
|
|
|
|
int Coax::snoopie(uint16_t *buffer, size_t bufferSize, uint8_t *writeIndex)
|
|
{
|
|
return _spiCoaxTransceiver.snoopie(buffer, bufferSize, writeIndex);
|
|
}
|
|
|
|
#define COAX_COMMAND_READ_REGISTER 0x2
|
|
#define COAX_COMMAND_WRITE_REGISTER 0x3
|
|
#define COAX_COMMAND_TX 0x4
|
|
#define COAX_COMMAND_RX 0x5
|
|
#define COAX_COMMAND_SNOOPIE 0x6
|
|
#define COAX_COMMAND_RESET 0xff
|
|
|
|
#define NOP asm volatile("nop\n\t")
|
|
#define ATOMIC_BLOCK_START __disable_irq()
|
|
#define ATOMIC_BLOCK_END __enable_irq()
|
|
|
|
SPICoaxTransceiver::SPICoaxTransceiver()
|
|
{
|
|
}
|
|
|
|
bool SPICoaxTransceiver::init()
|
|
{
|
|
// Configure GPIO.
|
|
__HAL_RCC_GPIOA_CLK_ENABLE();
|
|
__HAL_RCC_GPIOB_CLK_ENABLE();
|
|
|
|
HAL_GPIO_WritePin(GPIOA, ICE40_CS_Pin | COAX_RESET_Pin, GPIO_PIN_RESET);
|
|
|
|
GPIO_InitTypeDef gpioInit = { 0 };
|
|
|
|
gpioInit.Pin = ICE40_CS_Pin | COAX_RESET_Pin;
|
|
gpioInit.Mode = GPIO_MODE_OUTPUT_PP;
|
|
gpioInit.Pull = GPIO_NOPULL;
|
|
gpioInit.Speed = GPIO_SPEED_FREQ_LOW;
|
|
|
|
HAL_GPIO_Init(GPIOA, &gpioInit);
|
|
|
|
gpioInit.Pin = COAX_IRQ_Pin;
|
|
gpioInit.Mode = GPIO_MODE_IT_RISING;
|
|
gpioInit.Pull = GPIO_NOPULL;
|
|
|
|
HAL_GPIO_Init(GPIOB, &gpioInit);
|
|
|
|
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
|
|
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
|
|
|
|
// Set initial GPIO state.
|
|
HAL_GPIO_WritePin(GPIOA, ICE40_CS_Pin, GPIO_PIN_SET);
|
|
HAL_GPIO_WritePin(GPIOA, COAX_RESET_Pin, GPIO_PIN_RESET);
|
|
|
|
// Configure SPI.
|
|
__HAL_RCC_SPI1_CLK_ENABLE();
|
|
|
|
gpioInit.Pin = ICE40_SCK_Pin | ICE40_SDI_Pin | ICE40_SDO_Pin;
|
|
gpioInit.Mode = GPIO_MODE_AF_PP;
|
|
gpioInit.Alternate = GPIO_AF5_SPI1;
|
|
gpioInit.Pull = GPIO_NOPULL;
|
|
gpioInit.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
|
|
|
|
HAL_GPIO_Init(GPIOA, &gpioInit);
|
|
|
|
LL_SPI_InitTypeDef spiInit = { 0 };
|
|
|
|
spiInit.TransferDirection = LL_SPI_FULL_DUPLEX;
|
|
spiInit.Mode = LL_SPI_MODE_MASTER;
|
|
spiInit.DataWidth = LL_SPI_DATAWIDTH_8BIT;
|
|
spiInit.ClockPolarity = LL_SPI_POLARITY_LOW;
|
|
spiInit.ClockPhase = LL_SPI_PHASE_1EDGE;
|
|
spiInit.NSS = LL_SPI_NSS_SOFT;
|
|
spiInit.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV8;
|
|
spiInit.BitOrder = LL_SPI_MSB_FIRST;
|
|
spiInit.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;
|
|
spiInit.CRCPoly = 7;
|
|
|
|
LL_SPI_Init(SPI1, &spiInit);
|
|
|
|
LL_SPI_SetStandard(SPI1, LL_SPI_PROTOCOL_MOTOROLA);
|
|
LL_SPI_DisableNSSPulseMgt(SPI1);
|
|
LL_SPI_SetRxFIFOThreshold(SPI1, LL_SPI_RX_FIFO_TH_QUARTER);
|
|
|
|
LL_SPI_Enable(SPI1);
|
|
|
|
// Release hardware reset.
|
|
HAL_GPIO_WritePin(GPIOA, COAX_RESET_Pin, GPIO_PIN_SET);
|
|
|
|
uint8_t deviceId = readRegister(COAX_REGISTER_DEVICE_ID);
|
|
|
|
if (deviceId != 0xa5) {
|
|
return false;
|
|
}
|
|
|
|
reset();
|
|
|
|
setLoopback(false);
|
|
|
|
setTXParity(CoaxParity::Even);
|
|
setRXParity(CoaxParity::Even);
|
|
|
|
return true;
|
|
}
|
|
|
|
void SPICoaxTransceiver::reset()
|
|
{
|
|
uint8_t transmitBuffer[1] = { COAX_COMMAND_RESET };
|
|
|
|
ATOMIC_BLOCK_START;
|
|
LL_GPIO_ResetOutputPin(ICE40_CS_GPIO_Port, ICE40_CS_Pin);
|
|
|
|
spiTransfer(transmitBuffer, NULL, 1);
|
|
|
|
LL_GPIO_SetOutputPin(ICE40_CS_GPIO_Port, ICE40_CS_Pin);
|
|
ATOMIC_BLOCK_END;
|
|
}
|
|
|
|
uint8_t SPICoaxTransceiver::readRegister(uint8_t index)
|
|
{
|
|
uint8_t transmitBuffer[2] = { (uint8_t) (COAX_COMMAND_READ_REGISTER | (index << 4)), 0x00 };
|
|
uint8_t receiveBuffer[2];
|
|
|
|
ATOMIC_BLOCK_START;
|
|
LL_GPIO_ResetOutputPin(ICE40_CS_GPIO_Port, ICE40_CS_Pin);
|
|
|
|
spiTransfer(transmitBuffer, receiveBuffer, 2);
|
|
|
|
LL_GPIO_SetOutputPin(ICE40_CS_GPIO_Port, ICE40_CS_Pin);
|
|
ATOMIC_BLOCK_END;
|
|
|
|
return receiveBuffer[1];
|
|
}
|
|
|
|
void SPICoaxTransceiver::writeRegister(uint8_t index, uint8_t value, uint8_t mask)
|
|
{
|
|
uint8_t transmitBuffer[3] = { (uint8_t) (COAX_COMMAND_WRITE_REGISTER | (index << 4)), mask, value };
|
|
|
|
ATOMIC_BLOCK_START;
|
|
LL_GPIO_ResetOutputPin(ICE40_CS_GPIO_Port, ICE40_CS_Pin);
|
|
|
|
spiTransfer(transmitBuffer, NULL, 3);
|
|
|
|
LL_GPIO_SetOutputPin(ICE40_CS_GPIO_Port, ICE40_CS_Pin);
|
|
ATOMIC_BLOCK_END;
|
|
}
|
|
|
|
int SPICoaxTransceiver::transmit(const uint16_t *buffer, size_t bufferCount)
|
|
{
|
|
uint8_t transmitBuffer[2] = { COAX_COMMAND_TX };
|
|
uint8_t receiveBuffer[2];
|
|
|
|
size_t count = 0;
|
|
int error = 0;
|
|
|
|
ATOMIC_BLOCK_START;
|
|
LL_GPIO_ResetOutputPin(ICE40_CS_GPIO_Port, ICE40_CS_Pin);
|
|
|
|
spiTransfer(transmitBuffer, NULL, 1);
|
|
|
|
do {
|
|
transmitBuffer[0] = (buffer[count] >> 8) & 0x03;
|
|
transmitBuffer[1] = buffer[count] & 0xff;
|
|
|
|
spiTransfer(transmitBuffer, receiveBuffer, 2);
|
|
|
|
uint8_t value = receiveBuffer[1];
|
|
|
|
if (value == 0) {
|
|
count++;
|
|
} else if (value == 0x81) {
|
|
// Overflow... we'll just try again.
|
|
continue;
|
|
} else if (value == 0x82) {
|
|
error = COAX_ERROR_TX_UNDERFLOW;
|
|
break;
|
|
}
|
|
} while (count < bufferCount);
|
|
|
|
LL_GPIO_SetOutputPin(ICE40_CS_GPIO_Port, ICE40_CS_Pin);
|
|
ATOMIC_BLOCK_END;
|
|
|
|
if (error != 0) {
|
|
return error;
|
|
}
|
|
|
|
while (!isTXComplete()) {
|
|
NOP;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
int SPICoaxTransceiver::receive(uint16_t *buffer, size_t bufferSize)
|
|
{
|
|
uint8_t transmitBuffer[2] = { COAX_COMMAND_RX };
|
|
uint8_t receiveBuffer[2];
|
|
|
|
size_t count = 0;
|
|
int error = 0;
|
|
|
|
ATOMIC_BLOCK_START;
|
|
LL_GPIO_ResetOutputPin(ICE40_CS_GPIO_Port, ICE40_CS_Pin);
|
|
|
|
spiTransfer(transmitBuffer, NULL, 1);
|
|
|
|
transmitBuffer[0] = 0x00;
|
|
transmitBuffer[1] = 0x00;
|
|
|
|
do {
|
|
spiTransfer(transmitBuffer, receiveBuffer, 2);
|
|
|
|
uint16_t value = (receiveBuffer[0] << 8) | receiveBuffer[1];
|
|
|
|
if (value & 0x8000) {
|
|
error = (-1) * (value & 0x03ff);
|
|
|
|
if (error == 0) {
|
|
Debug::trap(106);
|
|
|
|
error = COAX_ERROR_RX_UNKNOWN;
|
|
}
|
|
|
|
break;
|
|
} else if (value & 0x4000) {
|
|
break;
|
|
}
|
|
|
|
// TODO: can this ever happen... I don't think so now the loop has
|
|
// been rewritten.
|
|
if (count >= bufferSize) {
|
|
Debug::trap(107);
|
|
break;
|
|
}
|
|
|
|
buffer[count] = value & 0x03ff;
|
|
|
|
count++;
|
|
} while (count < bufferSize);
|
|
|
|
LL_GPIO_SetOutputPin(ICE40_CS_GPIO_Port, ICE40_CS_Pin);
|
|
ATOMIC_BLOCK_END;
|
|
|
|
if (error != 0) {
|
|
return error;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
void SPICoaxTransceiver::setLoopback(bool loopback)
|
|
{
|
|
writeRegister(COAX_REGISTER_CONTROL, loopback ? COAX_REGISTER_CONTROL_LOOPBACK : 0, COAX_REGISTER_CONTROL_LOOPBACK);
|
|
}
|
|
|
|
void SPICoaxTransceiver::setTXProtocol(CoaxProtocol protocol)
|
|
{
|
|
writeRegister(COAX_REGISTER_CONTROL, protocol == CoaxProtocol::_3299 ? COAX_REGISTER_CONTROL_TX_PROTOCOL : 0, COAX_REGISTER_CONTROL_TX_PROTOCOL);
|
|
}
|
|
|
|
void SPICoaxTransceiver::setTXParity(CoaxParity parity)
|
|
{
|
|
writeRegister(COAX_REGISTER_CONTROL, parity == CoaxParity::Even ? COAX_REGISTER_CONTROL_TX_PARITY : 0, COAX_REGISTER_CONTROL_TX_PARITY);
|
|
}
|
|
|
|
void SPICoaxTransceiver::setRXProtocol(CoaxProtocol protocol)
|
|
{
|
|
writeRegister(COAX_REGISTER_CONTROL, protocol == CoaxProtocol::_3299 ? COAX_REGISTER_CONTROL_RX_PROTOCOL : 0, COAX_REGISTER_CONTROL_RX_PROTOCOL);
|
|
}
|
|
|
|
void SPICoaxTransceiver::setRXParity(CoaxParity parity)
|
|
{
|
|
writeRegister(COAX_REGISTER_CONTROL, parity == CoaxParity::Even ? COAX_REGISTER_CONTROL_RX_PARITY : 0, COAX_REGISTER_CONTROL_RX_PARITY);
|
|
}
|
|
|
|
int SPICoaxTransceiver::snoopie(uint16_t *buffer, size_t bufferSize, uint8_t *writeIndex)
|
|
{
|
|
uint8_t transmitBuffer[2] = { COAX_COMMAND_SNOOPIE };
|
|
uint8_t receiveBuffer[2];
|
|
|
|
ATOMIC_BLOCK_START;
|
|
LL_GPIO_ResetOutputPin(ICE40_CS_GPIO_Port, ICE40_CS_Pin);
|
|
|
|
spiTransfer(transmitBuffer, NULL, 1);
|
|
|
|
transmitBuffer[0] = 0x00;
|
|
transmitBuffer[1] = 0x00;
|
|
|
|
spiTransfer(transmitBuffer, receiveBuffer, 1);
|
|
|
|
*writeIndex = receiveBuffer[0];
|
|
|
|
for (size_t index = 0; index < bufferSize; index++) {
|
|
spiTransfer(transmitBuffer, receiveBuffer, 2);
|
|
|
|
uint16_t value = (receiveBuffer[0] << 8) | receiveBuffer[1];
|
|
|
|
buffer[index] = value;
|
|
}
|
|
|
|
LL_GPIO_SetOutputPin(ICE40_CS_GPIO_Port, ICE40_CS_Pin);
|
|
ATOMIC_BLOCK_END;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void SPICoaxTransceiver::spiTransfer(const uint8_t *transmitBuffer,
|
|
uint8_t *receiveBuffer, size_t count)
|
|
{
|
|
// TODO: flush, or confirm TX ready?
|
|
|
|
for (size_t index = 0; index < count; index++) {
|
|
while (!(SPI1->SR & SPI_SR_TXE)) {
|
|
NOP;
|
|
}
|
|
|
|
LL_SPI_TransmitData8(SPI1, transmitBuffer[index]);
|
|
|
|
while (!LL_SPI_IsActiveFlag_RXNE(SPI1)) {
|
|
NOP;
|
|
}
|
|
|
|
uint8_t value = LL_SPI_ReceiveData8(SPI1);
|
|
|
|
if (receiveBuffer != NULL) {
|
|
receiveBuffer[index] = value;
|
|
}
|
|
}
|
|
}
|