Skip to content

Commit

Permalink
Adds STM32 RailcomDriver (#702)
Browse files Browse the repository at this point in the history
This driver is based on a fork of the TivaRailcomDriver; the hardware interaction is rewritten to use the STM32 HAL libraries and in part direct access to the USART peripheral.

A major difference is that unlike the Tiva, the STM32 devices do not have a FIFO on the UART hardware. This makes it impossible to only perform reads from the UART data registers when the cutout is done.
To work around this issue, the STM32 Railcom Driver uses DMA to copy the data from the UART hardware register into the buffer in the driver. This requires a free DMA channel for each railcom channel that is in use.

Misc changes:
- Implements toggle() in STM32 GPIO driver.
- Adds a limit to how many buffers the RailcomToOpenlcbDebugProxy is willing to allocate. When we run out of buffers, we drop the packet to the floor.

===

* Rename includes and guard.

* Fixes incorrect include for railcom implementation header.

* Adds another instruction to libatomic.

* Implements stm32 railcom driver (forked from the basis of tivarailcom).

* Implements toggle() in stm32gpio.

* Limits the number of buffers that the railcom debug port is willing to allocate.

* Implements the STM32 railcom driver
(this is a diff from the forked tiva railcom driver)

* Support nullptr parent when using the constructor with more arguments.

* Makes detecting a locomotive asymmetric, with fewer positive confirmations needed
for reporting the address than how many empty packets are needed to report
block empty.

* Reserves 1 kbytes of space at the top of RAM for the interrupt stack.
Clears this RAM to all zeros at the beginning of the run, so that we
can verify that the heap did not grow into the interrupt stack.

* Fix review comments including whitespace and hanging braces.
switch minmax repeat counts to more intuitive definitions without double counting.
  • Loading branch information
balazsracz authored Apr 25, 2023
1 parent b9207af commit 3f6afa0
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 178 deletions.
4 changes: 3 additions & 1 deletion boards/armv7m/target.ld
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ INCLUDE memory_map.ld

__top_RAM = ORIGIN(RAM) + LENGTH(RAM);

__cs3_heap_end = __top_RAM;
__cs3_heap_end = __top_RAM - 1024;

__start_ram = ORIGIN(RAM);
__end_ram = __top_RAM;
Expand Down Expand Up @@ -41,6 +41,8 @@ SECTIONS
__bss_section_table = .;
LONG( ADDR(.bss));
LONG( SIZEOF(.bss));
LONG(__cs3_heap_end);
LONG(__top_RAM - __cs3_heap_end - 64);
__bss_section_table_end = .;
__section_table_end = . ;
/* End of Global Section Table */
Expand Down
75 changes: 56 additions & 19 deletions src/dcc/RailcomBroadcastDecoder.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,18 @@ bool RailcomBroadcastDecoder::process_packet(const dcc::Feedback &packet)
{
if (packet.ch1Size)
{
return process_data(packet.ch1Data, packet.ch1Size);
}
else if (packet.ch2Size)
{
return process_data(packet.ch2Data, packet.ch2Size);
return process_data(packet.ch1Data, packet.ch1Size) &&
(packet.ch2Size == 0);
}
else
{
return true; // empty packet.
// No channel1 data.
notify_empty();
if (!packet.ch2Size)
{
return true; // empty packet.
}
return false;
}
}

Expand All @@ -65,13 +68,17 @@ bool RailcomBroadcastDecoder::process_data(const uint8_t *data, unsigned size)
for (unsigned i = 0; i < size; ++i)
{
if (railcom_decode[data[i]] == RailcomDefs::INV)
{
return true; // garbage.
}
}
/// TODO(balazs.racz) if we have only one byte in ch1 but we have a second
/// byte in ch2, we should still process those because it might be a
/// misaligned window.
if (size < 2)
return true; // Dunno what this is.a
{
return true; // Dunno what this is.
}
uint8_t type = (dcc::railcom_decode[data[0]] >> 2);
if (size == 2)
{
Expand All @@ -81,25 +88,39 @@ bool RailcomBroadcastDecoder::process_data(const uint8_t *data, unsigned size)
switch (type)
{
case dcc::RMOB_ADRLOW:
if (currentL_ == payload) {
if (countL_ < REPEAT_COUNT) ++countL_;
} else {
if (currentL_ == payload)
{
if (countL_ < MIN_EMPTY_COUNT)
{
countL_ += 2;
}
}
else
{
currentL_ = payload;
countL_ = 0;
}
break;
case dcc::RMOB_ADRHIGH:
if (currentH_ == payload) {
if (countH_ < REPEAT_COUNT) ++countH_;
} else {
if (currentH_ == payload)
{
if (countH_ < MIN_EMPTY_COUNT)
{
countH_ += 2;
}
}
else
{
currentH_ = payload;
countH_ = 0;
}
break;
default:
return false; // This is something we don't know about.
}
if (countL_ >= REPEAT_COUNT && countH_ >= REPEAT_COUNT) {
if (countL_ >= (MIN_REPEAT_COUNT * 2) &&
countH_ >= (MIN_REPEAT_COUNT * 2))
{
currentAddress_ = (uint16_t(currentH_) << 8) | currentL_;
}
return true;
Expand All @@ -110,11 +131,27 @@ bool RailcomBroadcastDecoder::process_data(const uint8_t *data, unsigned size)
}
}

void RailcomBroadcastDecoder::set_occupancy(bool value) {
if (value) return;
if (countH_) --countH_;
if (countL_) --countL_;
if ((!countH_) || (!countL_)) {
void RailcomBroadcastDecoder::set_occupancy(bool value)
{
if (value)
{
return;
}
notify_empty();
}

void RailcomBroadcastDecoder::notify_empty()
{
if (countH_)
{
--countH_;
}
if (countL_)
{
--countL_;
}
if ((!countH_) || (!countL_))
{
currentAddress_ = 0;
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/dcc/RailcomBroadcastDecoder.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,15 @@ private:
/// bytes arethere to decode. @return dunno.
bool process_data(const uint8_t *data, unsigned size);

/// Notifies the state machine that there is no occupancy detected.
void notify_empty();

/// How many times we shall get the same data out of railcom before we
/// believe it and report to the bus.
static const uint8_t REPEAT_COUNT = 3;
static const uint8_t MIN_REPEAT_COUNT = 3;
/// This is how many empty packets we need to forget the current address
/// when we're getting empty packets.
static const uint8_t MIN_EMPTY_COUNT = 8;

uint8_t currentH_; ///< last received high address bits
uint8_t currentL_; ///< last received low address bits
Expand Down
18 changes: 14 additions & 4 deletions src/dcc/RailcomPortDebug.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#define _DCC_RAILCOMPORTDEBUG_HXX

#include "dcc/RailcomHub.hxx"
#include "utils/LimitedPool.hxx"

namespace dcc
{
Expand Down Expand Up @@ -213,14 +214,17 @@ public:
RailcomToOpenLCBDebugProxy(dcc::RailcomHubFlow *parent, Node *node,
dcc::RailcomHubPort *occupancy_port, bool ch1_enabled = true,
bool ack_enabled = true)
: dcc::RailcomHubPort(parent->service())
: dcc::RailcomHubPort(node->iface())
, parent_(parent)
, node_(node)
, occupancyPort_(occupancy_port)
, ch1Enabled_(ch1_enabled)
, ackEnabled_(ack_enabled)
{
parent_->register_port(this);
if (parent_)
{
parent_->register_port(this);
}
}

RailcomToOpenLCBDebugProxy(Node *node)
Expand Down Expand Up @@ -254,11 +258,15 @@ public:
{
return release_and_exit();
}
if (outputPool_.free_items() == 0)
{
return release_and_exit();
}
if (message()->data()->ch1Size && ch1Enabled_)
{
return allocate_and_call(
node_->iface()->global_message_write_flow(),
STATE(ch1_msg_allocated));
STATE(ch1_msg_allocated), &outputPool_);
}
else
{
Expand All @@ -285,13 +293,14 @@ public:
Action maybe_send_ch2()
{
if (message()->data()->ch2Size &&
outputPool_.free_items() != 0 &&
(ackEnabled_ ||
dcc::railcom_decode[message()->data()->ch2Data[0]] !=
dcc::RailcomDefs::ACK))
{
return allocate_and_call(
node_->iface()->global_message_write_flow(),
STATE(ch2_msg_allocated));
STATE(ch2_msg_allocated), &outputPool_);
}
else
{
Expand All @@ -318,6 +327,7 @@ public:
dcc::RailcomHubFlow *parent_{nullptr};
Node *node_;
dcc::RailcomHubPort *occupancyPort_;
LimitedPool outputPool_{sizeof(Buffer<openlcb::GenMessage>), 20};
/// True if we should transmit channel1 data.
uint8_t ch1Enabled_ : 1;
/// True if we should transmit data that starts with an ACK.
Expand Down
3 changes: 1 addition & 2 deletions src/freertos_drivers/common/RailcomImpl.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@
#ifndef _FREERTOS_DRIVERS_COMMON_RAILCOMIMPL_HXX_
#define _FREERTOS_DRIVERS_COMMON_RAILCOMIMPL_HXX_

#include "TivaDCC.hxx" // for FixedQueue

#include "freertos_drivers/common/RailcomDriver.hxx"
#include "freertos_drivers/common/FixedQueue.hxx"
#include "dcc/RailCom.hxx"

/*
Expand Down
12 changes: 12 additions & 0 deletions src/freertos_drivers/common/libatomic.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@
/// Restores the interrupte enable flag from a register.
#define REL_LOCK() __asm volatile(" msr PRIMASK, %0\n " : : "r"(_pastlock));

/// __atomic_fetch_add_1
///
/// This function is needed for GCC-generated code.
uint8_t __atomic_fetch_add_1(uint8_t *ptr, uint8_t val, int memorder)
{
ACQ_LOCK();
uint16_t ret = *ptr;
*ptr += val;
REL_LOCK();
return ret;
}

/// __atomic_fetch_sub_2
///
/// This function is needed for GCC-generated code.
Expand Down
6 changes: 6 additions & 0 deletions src/freertos_drivers/st/Stm32Gpio.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ template <uint32_t GPIOx, uint16_t PIN, uint8_t PIN_NUM> struct Stm32GpioDefs
return port()->IDR & pin();
}

/// Changes the output pin level.
static void toggle()
{
set(!get());
}

/// @return a os-indepentent Gpio abstraction instance for use in
/// libraries.
static constexpr const Gpio *instance()
Expand Down
Loading

0 comments on commit 3f6afa0

Please sign in to comment.