mirror of
https://github.com/lowobservable/coax.git
synced 2026-03-01 09:51:30 +00:00
Add interface2
This commit is contained in:
469
interface2/firmware/src/coax.cpp
Normal file
469
interface2/firmware/src/coax.cpp
Normal file
@@ -0,0 +1,469 @@
|
||||
// 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, CoaxParity parity,
|
||||
volatile uint16_t *buffer, size_t bufferSize) :
|
||||
_spiCoaxTransceiver(spiCoaxTransceiver),
|
||||
_parity(parity),
|
||||
_buffer(buffer),
|
||||
_bufferSize(bufferSize)
|
||||
{
|
||||
_isInitialized = false;
|
||||
}
|
||||
|
||||
bool Coax::init()
|
||||
{
|
||||
_isInitialized = false;
|
||||
|
||||
_interruptState = COAX_INTERRUPT_STATE_DISABLED;
|
||||
|
||||
if (!_spiCoaxTransceiver.init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_spiCoaxTransceiver.setTXParity(_parity);
|
||||
_spiCoaxTransceiver.setRXParity(_parity);
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
return _isInitialized;
|
||||
}
|
||||
|
||||
void Coax::reset()
|
||||
{
|
||||
if (!_isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
_interruptState = COAX_INTERRUPT_STATE_DISABLED;
|
||||
|
||||
_spiCoaxTransceiver.reset();
|
||||
|
||||
_spiCoaxTransceiver.setTXParity(_parity);
|
||||
_spiCoaxTransceiver.setRXParity(_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;
|
||||
}
|
||||
}
|
||||
|
||||
#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_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::setTXParity(CoaxParity parity)
|
||||
{
|
||||
writeRegister(COAX_REGISTER_CONTROL, parity == CoaxParity::Even ? COAX_REGISTER_CONTROL_TX_PARITY : 0, COAX_REGISTER_CONTROL_TX_PARITY);
|
||||
}
|
||||
|
||||
void SPICoaxTransceiver::setRXParity(CoaxParity parity)
|
||||
{
|
||||
writeRegister(COAX_REGISTER_CONTROL, parity == CoaxParity::Even ? COAX_REGISTER_CONTROL_RX_PARITY : 0, COAX_REGISTER_CONTROL_RX_PARITY);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user