dipl/sections/DP/textadv/main.tex

374 lines
16 KiB
TeX

\section{Textadventure}
To illustrate how the components work together and can be used in various
different applications, a small text-adventure with audio effects was written in
C. The main goal was to show the capabilities of even small systems like the
one developed.
\subsection{General Implementation details}
\subsubsection{General definitions and pinout of the AVR}
Like the examples seen before, the textadventure was implemented on an
ATMega2560
and uses 3 different Registers for transmission: PORTF, PORTK and PORTL for
address bus, data bus and control bus respectively, as can be seen in listing
\ref{lst:textadv-avr.h}
\lstinputlisting[language=C,frame=trBL,
breaklines=true, breakautoindent=true, formfeed=\newpage,
label={lst:textadv-avr.h}, caption={The avr.h header file},
columns=flexible, style=cstyle]{./code/textadv/include/avr.h}
The in listing \ref{lst:textadv-avr.h} shown preprocessor macros
MR_SHIFT, WR_SHIFT,
RD_SHIFT, CS_UART_SHIFT and CS_DAC_SHIFT are used to indicate the position of
the corresponding control lines inside the control bus register. All other
shift values are the same bitordering in input as in output.
The macro BUS_HOLD_US is used to tell the AVR how many microseconds it takes for
the
data bus to be latched into input register of the devices on write, or how long
it takes for the data bus to become stable on read. A delay of less than 1
microsecond is not possible due to limitations of the AVR and the bus capacity,
which increases the BER\footnote{BER...Bit Error Ratio} to a level which effects
regular operation.
\subsubsection{Read and Write routines}
The set_addr function is the same as in the UART example code in listing
\ref{lst:16550-general} and has therefore been omitted, execept for its definiton
in the avr.h file in listing \ref{lst:textadv-avr.h}. The read and write
functions for the UART module and the DAC module are the same as in the example
code for the modules and have been ommited therefore as well.
\subsubsection{UART and DAC update polling}
The AVR constantly polls the DAC and UART modules for updates as can be seen in
listing \ref{lst:textadv-routine}. The routine\_MODULE functions poll their
respective modules for updates as can be seen in listings
\ref{lst:textadv-routine-uart} and \ref{lst:textadv-routine-dac}. When a
character is received, it is stored inside a bufer array and regular operation
continues. If the $\lnot EF$ status bit is set in a read from the dac, the
feed\_dac function is called, which stores 256 bytes into the DAC, and regular
operation continues.
\lstinputlisting[language=C,frame=trBL,
breaklines=true, breakautoindent=true, formfeed=\newpage,
label={lst:textadv-routine}, caption={The routine function looped by the main},
columns=flexible, style=cstyle, firstline=105, lastline=110]
{code/textadv/src/main.c}
\lstinputlisting[language=C,frame=trBL,
breaklines=true, breakautoindent=true, formfeed=\newpage,
label={lst:textadv-routine-uart}, caption={The routine function for the UART},
columns=flexible, style=cstyle, firstline=112, lastline=125]
{code/textadv/src/16550.c}
\lstinputlisting[language=C,frame=trBL,
breaklines=true, breakautoindent=true, formfeed=\newpage,
label={lst:textadv-routine-dac}, caption={The routine function for the DAC},
columns=flexible, style=cstyle, firstline=200, lastline=207]
{code/textadv/src/dac.c}
\subsubsection{Program execution path}
On microprocessors it is required to not leave a return path for programs, as
a return path would lead to the microcontroller either resetting or seicing to
work until the next power cut. Therefore the program performs all it's tasks in
an infinite loop. This loop can be seen in listing \ref{lst:textadv-routine} and
in figure \ref{fig:textadv_pexfl}.
\begin{figure}[H]
\centering
\input{charts/flowchart_textadv.tex}
\caption{A Flow-Chart of the program execution path}
\label{fig:textadv_pexfl}
\end{figure}
\subsection{DAC sound generation}
\subsubsection{DAC modes}
The DAC can produce any waveform described by 8 bit unsigned PCM code. Though
possible to feed predefined waveforms into the DAC, the AVR doesn't have enough
onboard memory to store more than a few seconds of these waveforms.
For exampl: To store one second of 8 bit unsigned PCM Code at 2 times 44.1KHz
sampling rate of the DAC the AVR would have to store
$s = 2 \times 44100\frac{Bytes}{s}*1s = 2\times 44100 Bytes = 88.2KB$, but it
has only a total of 256KB of onboard flash\cite{atmega2560} which results in a
total track lengh of $ t = \frac{256KB}{88.2\frac{KB}{s}} = 2.9s$ with only
one track.
Therefore the AVR generates the audio during runtime. In order to do that it has
6 modes in which it can operate, as can be seen in Listing
\ref{lst:textadv-dac-modes}:
\begin{enumerate}
\item{silent mode:}
The DAC produces no output at all and is completely silent.
\item{sine mode:}
The DAC produces a sine with a specific frequency and an
amplitude of 255.
\item{square mode:}
The DAC produces a square wave with a specific frequency and an
amplitude of 255.
\item{saw mode:}
The DAC produces a saw wave with a specific frequency and an
amplitude of 255.
\item{noise mode:}
The DAC produces a pseudo-random white-noise with a maximum
amplitude of 255.
\item{triangle mode:}
The DAC produces a triangle wave with a specific frequency and
an amplitude of 255.
\end{enumerate}
To perform these tasks the DAC takes two parameters, again seen in listing
\ref{lst:textadv-dac-modes}:
\begin{itemize}
\item{A frequency deviation:}
Used to tell the DAC how much the desired frequency deviates
from the base frequency of each waveform.
\item{A mode:}
Used to tell it which waveform to generate
\end{itemize}
\lstinputlisting[language=C,frame=trBL,
breaklines=true, breakautoindent=true, formfeed=\newpage,
label={lst:textadv-dac-modes}, caption={The DAC operation modes},
columns=flexible, style=cstyle, firstline=25, lastline=38]
{code/textadv/include/dac.h}
\lstinputlisting[language=C,frame=trBL,
breaklines=true, breakautoindent=true, formfeed=\newpage,
label={lst:textadv-dac-gen}, caption={The DAC waveform generation code},
columns=flexible, style=cstyle, firstline=61, lastline=198]
{code/textadv/src/dac.c}
\subsubsection{Tones and Tracks}
A sound track inside the textadventure consists of independent tones. A tone is
a waveform at a specific frequency played for a specific time. To perform the
specific time functionality independant of DAC speed, an ISR
\footnote{ISR...Interrupt Service Routine} on the AVR was used to change to
the next tone every millisecond. A track is an array of tones with an end marker
tone at the end, which is a tone with a length of 0ms. The end marker tone tells
the ISR to reset to the initial tone. The ISR can be seen in Listing
\ref{lst:textadv-isr}, and the sound update function, which actually updates the
current tone and is responsible for playing a track in listing
\ref{lst:textadv-upsnd}. The output of an example track can be seen in
figures \ref{fig:textadv_track_ex1} and \ref{fig:textadv_track_ex2}.
\lstinputlisting[language=C,frame=trBL,
breaklines=true, breakautoindent=true, formfeed=\newpage,
label={lst:textadv-isr}, caption={The ISR which fires every millisecond},
columns=flexible, style=cstyle, firstline=31, lastline=34]
{code/textadv/src/interrupt.c}
\lstinputlisting[language=C,frame=trBL,
breaklines=true, breakautoindent=true, formfeed=\newpage,
label={lst:textadv-upsnd}, caption={The sound update function},
columns=flexible, style=cstyle, firstline=219, lastline=261]
{code/textadv/src/sound.c}
\newpage
\begin{figure}[H]
\begin{tikzpicture}
\begin{axis}[
ymin=-0.5025374538865546,
ymax=-.0580652077731092,
ylabel=Time,
xlabel=Track output,
ymajorgrids=true,
width=\textwidth,
height=0.98\textheight]
\addplot table [x=c1, y=t, col sep=comma, mark=none] {meas/20200315audio_multiple_voices.csv};
\addplot table [x=c2, y=t, col sep=comma, mark=none] {meas/20200315audio_multiple_voices.csv};
\legend{DACA,DACB}
\end{axis}
\end{tikzpicture}
\caption{The output of an example track part 1}
\label{fig:textadv_track_ex1}
\end{figure}
\newpage
\begin{figure}[H]
\begin{tikzpicture}
\begin{axis}[
ymin=-.0580652077731092,
ymax=0.4444722461134454,
ylabel=Time,
xlabel=Track output,
ymajorgrids=true,
width=\textwidth,
height=0.98\textheight]
\addplot table [x=c1, y=t, col sep=comma, mark=none] {meas/20200315audio_multiple_voices.csv};
\addplot table [x=c2, y=t, col sep=comma, mark=none] {meas/20200315audio_multiple_voices.csv};
\legend{DACA,DACB}
\end{axis}
\end{tikzpicture}
\caption{The output of an example track part 2}
\label{fig:textadv_track_ex2}
\end{figure}
\subsubsection{Track switching}
To switch tracks on different actions, there is a map of tracks associated with
rooms. Every room has an associated track, where the association can change on
actions performed, which allows for a game atmosphere change. Track changes are
performed outside the ISR, which could theoretically result in a race condition,
where the ISR would load a faulty track for 1ms, if the track change was not
performed fast enough, but this is prevented by disabling global interrupts
during a track change.
\subsection{User command interpretation}
\subsubsection{Command structure and parsing}
As in other text adventures \cite{dunnet} a command consits of one line of
input terminated by a newline or line feed character \textbackslash n.
The carriage return character, which is sometimes transmitted with a line
feed character, is not parsed in this text adventure. Incoming character
parsing can be seen in Listings \ref{lst:textadv-routine-uart} and
\ref{lst:textadv-ingest}.
As one command is parsed, each part is required to be separated by an empty
space character, which is ascii code 32 \cite{ascii}. The first part of the
given
input is then compared to an array of actions a user can perform, for example
use or search, as can be seen in Listing \ref{lst:textadv-parsecmd}
In listing \ref{lst:textadv-routine-uart} the comment echo back can be seen. The
write\_char function, writes it's parameter to the user., in this case the
input sent by the user.
This is done to write what the user typed out to the
terminal as otherwise one would not be able to see what has been typed on any
VT100 compatiable terminal\cite{vt100} or terminal emulator.
\lstinputlisting[language=C,frame=trBL,
breaklines=true, breakautoindent=true, formfeed=\newpage,
label={lst:textadv-ingest}, caption={The character ingest function},
columns=flexible, style=cstyle, firstline=73, lastline=81]
{code/textadv/src/game.c}
The in Listing \ref{lst:textadv-ingest} shown branch overrides the last received
character with 0x00, which is ascii NUL, and decrements the buffer pointer by
one if the received character was 0x7F. 0x7F is the ADCII DELETE character
\cite{ascii} which instructs the receiving end, that the last received character
was a mistake and should be purged. This is also what a vt100 compiant terminal
emulator sends, when the backspace or delete key is pressed \cite{vt100}.
\lstinputlisting[language=C,frame=trBL,
breaklines=true, breakautoindent=true, formfeed=\newpage,
label={lst:textadv-parsecmd}, caption={The command parsing function},
columns=flexible, style=cstyle, firstline=33, lastline=71]
{code/textadv/src/game.c}
\subsubsection{Command parameters}
Command paramters are interpreted as the string, that follows the action
and the space behind it. As can be seen in the case for ACTION\_USE in
Listing \ref{lst:textadv-perfact}, the use item function is passed the
command buffer\footnote{which is an address in memory} plus the length of the
entered command plus one for the space. So the string starting at the passed
address should match the start address of the parameter. If no parameter is
supplied, the address should point to a character containing ASCII NUL, which
marks the end of a string, because after command parsing, the string is
overwritten with zeros as seen in Listing \ref{lst:textadv-parsecmd}.
\lstinputlisting[language=C,frame=trBL,
breaklines=true, breakautoindent=true, formfeed=\newpage,
label={lst:textadv-perfact}, caption={The command execution routine},
columns=flexible, style=cstyle, firstline=83, lastline=123]
{code/textadv/src/game.c}
\subsection{Gameplay}
The game itself plays like a regular game with limtations set in direction.
Players can search for items in each room and grab the found items as can be
seen in figure \ref{fig:tetadv_gameplay}. The general gamplay is perfomred via
altering the map data and the strings output to the user.
\begin{figure}[H]
\centering
\includegraphics[width=\textwidth, angle=0]{pics/gameplay.png}
\caption{A regular beginning of the game}
\label{fig:tetadv_gameplay}
\end{figure}
\subsubsection{Memory constraints}
The AVR has 8kB of internal SRAM, which are used for stack and heap
\cite{atmega2560}. During the build of the program an ELF file can be obtained,
which contains infromation on the programs structure and memory usage on boot.
Strings and variables are contained within the .data section of the elf file
and loaded into memory during boot\cite{elf}. This is done for integer
variables as well as for strings, which makes the use of strings limited not
to the flash size but to the RAM size of the AVR. To save memory, sound tracks
as well as the sine and noise table have been put into program space with the
PROGMEM attribute as described by the avr-libc documentation\cite{progmem}.
In listing \ref{lst:textadv-dac-gen} a read from program memory can be seen in
the noise and sine modes. Strings have not been put into programmspace, as this
would require each string to be declared independantly and then be put into
arrays\cite{progmem} as is done now. Which would make the code much less
readable and increase overhead as well as make the usage of buffers nescessary
in order for the override of the printf function to work.
\subsubsection{Story}
The basics of the storyline are, that you wake up in the middle of a forest and
don't remember anything. You have to get through the forest to an old house,
while having to get rid of a bear, which is blocking the way. Inside the house
you have to get a computer to start. The game then proceeds to get recursive,
and your goal is to break out of the recursion.
\subsubsection{Recursion}
The game, when performing the recursion, resets your inventory and internal
state machines, before putting you back to the starting point. However, by
altering the orientation of rooms, altering the list of items found inside rooms
and by altering the texts output by the game, the atmosphere and
the outcome changed.
\subsubsection{Computer State Machine}
One example of a state machine inside the game is the computer inside the
old-house. The computer needs three items: A keyboard to type on, something to
boot from, for example a floppy disk, and a screwdriver to start it. The state
machine implementation can be seen in Listing \ref{lst:textadv-fsm} and the
state diagram in Figure \ref{fig:textadv_compfsm}.
\lstinputlisting[language=C,frame=trBL,
breaklines=true, breakautoindent=true, formfeed=\newpage,
label={lst:textadv-fsm}, caption={The computer FSM},
columns=flexible, style=cstyle, firstline=288, lastline=327]
{code/textadv/src/game.c}
\begin{figure}[H]
\centering
\begin{tikzpicture}
\node[state, initial] (c1) {Nothing};
\node[state, right of=c1] (c2) {Keyboard};
\node[state, right of=c2] (c3) {Bootable};
\draw (c1) edge[loop above] node{else} (c1)
(c1) edge[above] node {keyboard} (c2)
(c2) edge[loop above] node{else} (c2)
(c2) edge[above] node{boot medium} (c3)
(c3) edge[loop above] node {else} (c3)
(c3) edge[bend left, below] node {screwdriver} (c1);
\end{tikzpicture}
\caption{A state diagram of the computer state machine}
\label{fig:textadv_compfsm}
\end{figure}