73 lines
3.7 KiB
C++
73 lines
3.7 KiB
C++
#ifndef FIFO_H
|
|
#define FIFO_H
|
|
/** Typ dbus_w_t je podobně definován jako sig_atomic_t v hlavičce signal.h.
|
|
* Je to prostě největší typ, ke kterému je "atomický" přístup. V GCC je definováno
|
|
* __SIG_ATOMIC_TYPE__, šlo by použít, ale je znaménkový.
|
|
* */
|
|
#ifdef __SIG_ATOMIC_TYPE__
|
|
typedef unsigned __SIG_ATOMIC_TYPE__ dbus_w_t;
|
|
#else
|
|
typedef unsigned int dbus_w_t; // pro AVR by to měl být uint8_t (šířka datové sběrnice)
|
|
#endif //__SIG_ATOMIC_TYPE__
|
|
/// Tahle podivná rekurzívní formule je použita pro validaci délky bufferu.
|
|
static constexpr bool isValidM (const int N, const dbus_w_t M) {
|
|
// constexpr má raději rekurzi než cyklus (c++11)
|
|
return (N > 12) ? false : (((1u << N) == M) ? true : isValidM (N+1, M));
|
|
}
|
|
/** @class FIFO
|
|
* @brief Jednoduchá fronta (kruhový buffer).
|
|
*
|
|
* V tomto přikladu je vidět, že synchronizace mezi přerušením a hlavní smyčkou programu
|
|
* může být tak jednoduchá, že je v podstatě neviditelná. Využívá se toho, že pokud
|
|
* do kruhového buferu zapisujeme jen z jednoho bodu a čteme také jen z jednoho bodu
|
|
* (vlákna), zápis probíhá nezávisle pomocí indexu m_head a čtení pomocí m_tail.
|
|
* Délka dat je dána rozdílem tt. indexů, pokud v průběhu výpočtu délky dojde k přerušení,
|
|
* v zásadě se nic špatného neděje, maximálně je délka určena špatně a to tak,
|
|
* že zápis nebo čtení je nutné opakovat. Důležité je, že po výpočtu se nová délka zapíše
|
|
* do paměti "atomicky". Takže např. pro 8-bit procesor musí být indexy jen 8-bitové.
|
|
* To není moc velké omezení, protože tyto procesory obvykle mají dost malou RAM, takže
|
|
* velikost bufferu stejně nebývá být větší než nějakých 64 položek.
|
|
* Opět nijak nevadí že přijde přerušení při zápisu nebo čtení položky - to se provádí
|
|
* dříve než změna indexu, zápis a čtení je vždy na jiném místě RAM. Celé je to uděláno
|
|
* jako šablona, takže je možné řadit do fronty i složitější věci než je pouhý byte.
|
|
* Druhým parametrem šablony je délka bufferu (aby to šlo konstruovat jako statický objekt),
|
|
* musí to být mocnina dvou v rozsahu 8 až 4096, default je 64. Mocnina 2 je zvolena proto,
|
|
* aby se místo zbytku po dělení mohl použít jen bitový and, což je rychlejší.
|
|
* */
|
|
template<typename T, const dbus_w_t M = 64> class FIFO {
|
|
T m_data [M];
|
|
volatile dbus_w_t m_head; //!< index pro zápis (hlava)
|
|
volatile dbus_w_t m_tail; //!< index pro čtení (ocas)
|
|
/// vrací skutečnou délku dostupných dat
|
|
constexpr dbus_w_t lenght () const { return (M + m_head - m_tail) & (M - 1); };
|
|
/// zvětší a saturuje index, takže se tento motá v kruhu @param n index
|
|
void sat_inc (volatile dbus_w_t & n) const { n = (n + 1) & (M - 1); };
|
|
public:
|
|
/// Konstruktor
|
|
explicit constexpr FIFO<T,M> () noexcept {
|
|
// pro 8-bit architekturu může být byte jako index poměrně malý
|
|
static_assert (1ul << (8 * sizeof(dbus_w_t) - 1) >= M, "atomic type too small");
|
|
// a omezíme pro jistotu i delku buferu na nějakou rozumnou delku
|
|
static_assert (isValidM (3, M), "M must be power of two in range <8,4096> or <8,128> for 8-bit data bus (AVR)");
|
|
m_head = 0;
|
|
m_tail = 0;
|
|
}
|
|
/// Čtení položky
|
|
/// @return true, pokud se úspěšně provede
|
|
const bool Read (T & c) {
|
|
if (lenght() == 0) return false;
|
|
c = m_data [m_tail];
|
|
sat_inc (m_tail);
|
|
return true;
|
|
}
|
|
/// Zápis položky
|
|
/// @return true, pokud se úspěšně provede
|
|
const bool Write (const T & c) {
|
|
if (lenght() >= (M - 1)) return false;
|
|
m_data [m_head] = c;
|
|
sat_inc (m_head);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
#endif // FIFO_H
|