373 lines
16 KiB
TeX
373 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}
|
|
|