wavegen diagrams and text, comm port text, more of fw structure, starting wireless

master
Ondřej Hruška 6 years ago
parent ee0595414d
commit 3dacea9390
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 147
      ch.fw_structure.tex
  2. 49
      ch.hw_functions.tex
  3. 222
      ch.tinyframe.tex
  4. 41
      ch.wireless.tex
  5. 17
      document_config.tex
  6. 2
      fig.gex-descriptors.tex
  7. BIN
      img/resource-repository.pdf
  8. BIN
      img/settings-storage.pdf
  9. BIN
      img/wavegen-dds.pdf
  10. BIN
      img/wavegen-naive.pdf
  11. BIN
      references/AD9834.pdf
  12. BIN
      references/all-about-direct-digital-synthesis.pdf
  13. BIN
      thesis.pdf
  14. 18
      thesis.tex

@ -1,10 +1,10 @@
\chapter{Application Structure}
GEX was designed to be modular and easy to extend. At its core lies a general framework that provides services to the functional blocks configured and used by the user script running on the host PC. Functional blocks, or internally called \textit{units}, implement functions like SPI, I2C, GPIO access etc.
GEX is designed to be modular and easy to extend. It's composed of a set of functional blocks, sometimes available in more than one instance, which can be configured by the user to fit their application needs. The firmware is built around a \textit{core framework} which provides services to the functional blocks, such as a settings storage, resource allocation, message delivery and periodic updates.
In this chapter we'll focus on the general function of the GEX module, look at implementation details of the core framework, and in the next chapter some space will be given to each of the functional blocks.
In this chapter, we will focus on the general function of the GEX module and will look at the services provided by the core framework. Individual functional blocks and the control API will be described in the following chapters.\todo{references}
\textit{A writing style note:} This and the following parts were written after implementing and evaluating the first hardware prototype and its firmware, therefore rather than describing the development process, it tends to talk about the completed solution. All design choices will nevertheless be explained as well as possible.
\textit{A writing style note:} This and the following parts were written after implementing and evaluating the first hardware prototype and its firmware, therefore rather than describing the development process, it tends to talk about the completed solution and the decisions taken.
\section{User's View of GEX}
@ -20,51 +20,114 @@ In the case when a wireless communication module is installed on the PCB and GEX
Now that GEX is connected and configured, the user can start using it. This involves writing a program in C or Python that uses the GEX client library, using the Python library from MATLAB, or controlling GEX using a GUI front-end built on those libraries. The configuration can be stored in the module, but it's also possible to temporarily (or permanently) replace it using the communication API. This way the settings can be loaded automatically when the user's program starts.
\section{The Core Framework Functions}
The core framework forms the skeleton of the firmware and usually doesn't need any changes when new user-facing features are added. It provides:
\section{Functions of the Core Framework}
The core framework forms the skeleton of the firmware and usually doesn't need any changes when new user-facing features are added. It provides the following services:
\begin{itemize}
\item Hardware resource allocation
\item Settings storage and loading
\item Functional block initialization
\item Communication port with different back-ends (USB, UART, wireless)
\item Message sending and delivery
\item Interrupt management and routing to functional blocks
\item Virtual mass storage to allow editing of the configuration files
\item Hardware resource allocation (\ref{sec:res-allocation})
\item Settings storage and loading (\ref{sec:settings-storage})
\item Functional block (\textit{units}) initialization (\ref{sec:units-function})
\item The communication port with different back-ends: USB, UART, wireless (\ref{sec:com-ports})
\item Message sending and delivery (\ref{sec:message_passing})
\item Interrupt management and routing to functional blocks (\ref{sec:irq-routing})
\item Virtual mass storage for configuration file editing
\end{itemize}
When the firmware needs to be ported to a different STM32 microcontroller, the core framework is relatively straightforward to adapt and the whole process can be accomplished in a few hours. The time consuming part is modifying the functional blocks to work correctly with the new device's hardware.
\subsection{Source Code Layout}
Looking at the source code repository, at the root we'll find device specific driver libraries and files provided by ST Microelectronics, the FreeRTOS middleware, and a folder \verb|User| containing the GEX firmware. This folder is a git submodule. The GEX core framework consists of everything in \verb|User|, excluding the \verb|units| folder. The USB Device library, which had to be modified to support a composite class, is stored inside the \verb|User| folder as well. Hardware configuration, such as the status LED position or clock settings, are defined using compile flags set in the top level Makefile.
\section{Functional Blocks} \label{sec:units-function}
\section{Resource Allocation} \label{sec:res-allocation}
GEX's functional blocks, internally called \textit{units}, have been mentioned a few times but until now haven't been properly explained. GEX's user-facing functions are implemented in \textit{unit drivers}. Those are stand-alone modules that the user can enable and configure using the configuration file. In principle, there can be multiple instances of each unit type. However, in practice, we have to work with hardware constraints: there is only one ADC peripheral, two SPI ports and so on. This limitation is handled using resource allocation, as explained below.
\begin{figure}[h]
\centering
\includegraphics[scale=1] {img/resource-repository.pdf}
\caption{\label{fig:resource-repository}An example allocation in the resource registry}
\end{figure}
Each unit is defined by a section in the configuration file \verb|UNITS.INI|. It is given a name and a \textit{callsign}, a number which serves as an address for messages from the host PC, or, conversely, to indicate which unit sent an event report (such as a pin change interrupt). A unit is internally represented by an object that holds its configuration, internal state, and a link to the unit driver. The driver handles commands sent from the host PC, initializes and de-initializes the unit based on its settings and implements other aspects of a unit's function, such as periodic updates and interrupt handling.
The microcontroller provides a number of hardware resources that require exclusive access: GPIO pins, peripheral blocks (SPI, I2C, UART\textellipsis), DMA channels. If two units tried to control the same pin, the results would be unpredictable; similarly, with a multiple access to a serial port, the output would be a mix of the data streams and completely useless.
\section{Resource Allocation}
To prevent a multiple access, the firmware includes a \textit{resource registry} (fig. \ref{fig:resource-repository}). Each individual resource is represented by a field in a resource table together with its owner's callsign. Initially all resources are free, except for those not available on the particular platform (i.e. a GPIO pin PD1 may be disabled if not present on the microcontroller's package).
The microcontroller provides a number of hardware resources that require exclusive access: GPIO pins, peripheral blocks (SPI, I2C, UART\textellipsis), and DMA channels. When two units tried to control the same pin, the results would be unpredictable; worse, with a multiple access to a serial port, the output would be a mix of the data streams and completely useless.
The resources used by the core framework are taken by a virtual unit \verb|SYSTEM| on start-up to prevent conflicts with the user's units. This is the case of the status LED, the LOCK button, USB pins, the communication UART, the pins and an SPI peripheral connecting the wireless module, pins used for the crystal oscillator, and the timer/counter which provides the system timebase.
To prevent multiple access, the firmware includes a \textit{resource registry}. Each individual resource is represented by a field in a resource table together with its owner. Initially, all resources are free, except those not available on the particular platform (i.e. a GPIO port E may be disabled if not present on the microcontroller's package). On start-up, the resources used by the core framework are taken by a virtual unit \verb|SYSTEM| to prevent interference by user's units. This is the case of the status LED, the LOCK button, GPIO pins used for connecting USB, communication UART, the wireless module and the crystal oscillator, as well as the timer/counter which keeps the system timebase.
\todo[inline]{resources diagram}
\section{Settings Storage} \label{sec:settings-storage}
\section{Settings Storage}
\begin{figure}[h]
\centering
\includegraphics[scale=1] {img/settings-storage.pdf}
\caption{\label{fig:settings-storage}Structure of the settings subsystem}
\end{figure}
The system and unit settings are written, in a binary form, into designated pages of the microcontroller's Flash memory. The unit settings serialization and parsing is implemented by the respective unit drivers.
As the settings persist after a firmware update, it's important to maintain backwards compatibility. This is achieved by prefixing the unit's settings by a version number. When the settings are loaded by a new version of the firmware, it first checks the version and decides whether to use the old or new format. When the settings are next changed, the new format will be used.
The mentioned INI files that can be edited through the communication API or using a text editor with the virtual mass storage, are parsed and generated on demand and are never stored in the Flash or RAM, other than in short temporary buffers. The INI parser processes the byte stream on-the-fly as it is received, and a similar method is used to build a INI file from the configured units and system settings.
The INI files, which can be edited through the communication API or using a text editor with the virtual mass storage, are parsed and generated on demand and are never stored in the Flash or RAM, other than in short temporary buffers. The INI parser processes the byte stream on-the-fly as it is received, and a similar method is used to build a INI file from the configured units and system settings.
\todo[inline]{add a sample large INI file as an attachment}
\section{Functional Blocks} \label{sec:units-function}
GEX's user-facing functions, also called functional blocks or \textit{units}, are implemented in \textit{unit drivers}. Those are independent modules in the firmware that the user can enable and configure using the GEX configuration files. In principle, there can be multiple instances of each unit type. However, we are limited by hardware constraints: there may be only one ADC peripheral, two SPI ports and so on. The mutually exclusive assignment of resources to units is handled by the \textit{resource registry} (\ref{sec:res-allocation}).
Each unit is defined by a section in the configuration file \verb|UNITS.INI|. It is given a name and a \textit{callsign}, which is a number that serves as an address for message delivery. A unit is internally represented by a data object with the following structure:
\todo[inline]{a diagram}
\begin{itemize}
\item Name
\item Callsign
\item Configuration parameters loaded from the unit settings
\item State variables updated at run-time by user commands or internal functions
\item A reference to the unit driver
\end{itemize}
\section{Communication Ports}
The unit driver handles commands sent from the host PC, initializes and de-initializes the unit based on its settings, and implements other aspects of the unit's function, such as periodic updates and interrupt handling. Unit drivers may expose public API functions to make it possible to control the unit from a different driver, allowing the creation of "macro units".
\section{Source Code Layout}
\begin{wrapfigure}[20]{r}{0.4\textwidth}
\scriptsize\vspace{-3em}
\begin{verbatim}
├── build
   ├── firmware.bin
   └── firmware.dfu
├── Drivers
   ├── CMSIS
     └── Device / ST / STM32F0xx
   └── STM32F0xx_HAL_Driver
├── Middlewares / Third_Party / FreeRTOS
├── Src
│ └── main.c
├── User
│ ├── USB / STM32_USB_Device_Library
│ │   ├── Class
│ │     ├── CDC
│ │     ├── MSC
│ │     └── MSC_CDC
│ │   └── Core
│ ├── platform
│ │   ├── plat_compat.h
│ │   └── platform.c
│ ├── units
│ │   ├── adc
│ │   ├── digital_out
│ │   ...
│ ├── FreeRTOSConfig.h
│ └── gex.mk
└── Makefile
\end{verbatim}
\vspace{-1em}
\cprotect\caption{\label{fig:repo-structure} The general structure of the source code repository}
\end{wrapfigure}
Looking at the source code repository (fig. \ref{fig:repo-structure}), at the root we'll find device specific driver libraries and files provided by ST Microelectronics, the FreeRTOS middleware, and a folder called \verb|User| containing the GEX application code. This division is useful when porting the firmware to a different microcontroller, as the GEX folder is mostly platform-independent and can be simply copied (of course, adjustments are needed to accompany different hardware peripheral versions etc.). The GEX core framework consists of everything in the \verb|User| folder, excluding the \verb|units| directory in which the individual units are implemented. Each unit driver must be registered in the file \verb|platform.c| to be available for the user to select. The file \verb|plat_compat.c| includes platform-specific headers and defines e.g. which pin to use for a status LED or the LOCK button.
The USB Device library, which had to be modified to support a composite class, is stored inside the \verb|User| folder too, as it is compatible with all STM32 microcontrollers that support USB.
\section{Communication Ports} \label{sec:com-ports}
The firmware supports three different communication ports: hardware UART, USB (virtual serial port), and a wireless connection. Each interface is configured and accessed in a different way, but for the rest of the firmware (and for the PC-side application) they all appear as a full duplex serial port. To use interfaces other than USB, the user must configure those in the system settings (a file \verb|SYSTEM.INI| on the configuration disk).
@ -84,16 +147,42 @@ This is identical to the USB connection from the PC application's side, except a
The wireless connection uses an on-board communication module and a separate device, a wireless gateway, that connects to the PC. The wireless gateway is interfaced differently from the GEX board itself, but it also shows as a virtual serial port on the host PC. This is required to allow communicating with the gateway itself through the CDC/ACM interface in addition to addressing the end devices.
This interface will be explained in more detail in chapter XX\todo{LINK}.
This interface will be explained in more detail in chapter XX \todo{Link to tinyframe description}.
\section{Message Passing}
\section{Message Passing} \label{sec:message_passing}
One of the key functions of the core framework is to deliver messages from the host PC to the right units. This functionality resides above the framing protocol, which will be described in chapter XX \todo{Link to tinyframe description}.
One of the key functions of the core framework is to deliver messages from the host PC to the right units. This functionality resides above the framing protocol, which will be described in chapter \ref{sec:tinyframe}.
A message that is not a response in a multi-part session (this is handled by the framing library) is identified by its Type field. Two main groups of messages exist: \textit{system messages} and \textit{unit messages}. System messages can access the INI files, query a list of the available units, restart the module etc. Unit messages are addressed to a particular unit by their callsign (see \ref{sec:units-function}), and their payload format is defined by the unit driver. The framework reads the message type, then the callsign byte, and tries to find a matching unit in the unit list. If no unit with the callsign is found, an error response is sent back, otherwise the unit driver is given the message to handle it as required.
The framework provides one more messaging service to the units: event reporting. An asynchronous event, such as an external interrupt, an ADC trigger or an UART data reception needs to be reported to the host. This message is annotated by the unit callsign so the user application knows it's origin.
\section{Interrupt Routing} \label{sec:irq-routing}
Interrupts are an important part of almost any embedded application. They provide a way to rapidly react to asynchronous external or internal events, temporarily leaving the main program, jumping to an interrupt handler routine, and then returning back after the event is handled. Interrupts are also the way FreeRTOS implements multitasking without a multi-core processor.
In the Cortex-M0-based STM32F072, used in the initial GEX prototypes, the interrupt handlers table, defining which routine is called for which interrupt, is stored in the program memory and can't be changed at run-time. This is a complication for the modular structure of GEX where different unit drivers may use the same peripheral, and we would want to dynamically assign the interrupt handlers based on the active configuration. Let's have a look at an interrupt handler, in this case handling four different DMA channels, as is common in STM32 microcontrollers:
\begin{minted}{c}
void DMA1_Channel4_5_6_7_IRQHandler(void)
{
if (LL_DMA_IsActiveFlag_GI4(DMA1)) { /* handle DMA1 channel 4 */ }
if (LL_DMA_IsActiveFlag_GI5(DMA1)) { /* handle DMA1 channel 5 */ }
if (LL_DMA_IsActiveFlag_GI6(DMA1)) { /* handle DMA1 channel 6 */ }
if (LL_DMA_IsActiveFlag_GI7(DMA1)) { /* handle DMA1 channel 7 */ }
}
\end{minted}
It is evident that multiple units might need to use the same interrupt handler, even at the same time, since each DMA channel is configured, and works, independently. GEX implements a redirection scheme to accomplish such interrupt sharing: All interrupt handlers are defined in one place, accompanied by a table of function pointers. When a unit driver wants to register an interrupt handler, it stores a pointer to it in this redirection table. Then, once an interrupt is invoked, the common handler checks the corresponding entry in the table and calls the referenced routine, if any. Conversely, when a unit driver deinitializes a unit, it removes all interrupt handlers it used, freeing the redirection table slots for other use.

@ -48,7 +48,52 @@ A good approach to a universal measurement, when we don't know the expected freq
The system clock's frequency, which we use to measure pulse lengths and to gate the pulse counter, will be affected by tolerances of the used components, the layout of the PCB, temperature effects etc., causing measurement errors. A higher accuracy could be achieved using a temperature-compensated oscillator (TCO), or, in the direct method, by using the synchronization pulse provided by a GPS receiver to time the measurement interval.
\section{Waveform Generation with Direct Digital Synthesis}
\section{Waveform Generation}
A waveform generator is a useful tool in many experiments and measurements. A sine stimulus is the basis of a lock-in amplifier; it can be used to measure impedance; with a frequency sweep, we can obtain the frequency response of an analog filter, etc. We can, of course, generate other waveforms, such as a triangle, ramp, or rectangle wave.
The DAC peripheral can produce a DC level on the output pin based on a control word. When we periodically change its digital input, it produces an analog waveform.
\subsection{Waveform Generation with DMA and a Timer}
A straightforward implementation of the waveform generator is illustrated in figure \ref{fig:wavegen-naive}. This approach has its advantages: it's simple and works entirely in the background, with no interrupt handling required. It could even be implemented entirely in software, using a loop periodically updating the DAC values, of course such approach is less flexible and we would run into problems with asynchronous interrupts.
\begin{figure}
\centering
\includegraphics[scale=1] {img/wavegen-naive.pdf}
\caption{\label{fig:wavegen-naive}A simple implementation of the waveform generator}
\end{figure}
The highest achievable output frequency largely depends on the size of our look-up table. For instance, assuming a timer frequency of 48\,MHz and a 8192-word table, holding one period of the waveform, the maximum frequency would be short of 6\,kHz, whereas if we shorten the table to just 1024 words, we can get almost 47\,kHz on the analog output. The downside of a shorter table is a lower resolution, which will appear as DC plateaus or steps when observed with an oscilloscope, producing harmonic components similar to those of a square wave.
A major disadvantage of this simple generation method is given by the limitations of the used timer, which defines the output frequency. Its output trigger fires when the internal counter reaches a pre-defined value, after which the counting register is reset. The counting speed is derived from the system clock frequency $f_\mathrm{c}$ using a prescaller $P$ and the set maximum value $N$. Only output frequencies that can be exactly expressed as $f=f_\mathrm{c}/(P\cdot N \cdot \mathrm{TableSize})$ can be accurately produced. Still, this simple and efficient method may be used where fine tuning is not required to take advantage of its fully asynchronous operation.
\subsection{Direct Digital Synthesis}
There are situations where the simple waveform generation method is not sufficient, particularly when a fine tuning or on-line frequency and phase changes are required. Those are the strengths of a signal generation method called \textit{Direct Digital Synthesis} (DDS).
\begin{figure}[h]
\centering
\includegraphics[scale=1] {img/wavegen-dds.pdf}
\caption{\label{fig:wavegen-dds}A block diagram of a direct digital synthesis waveform generator}
\end{figure}
A diagram of a possible DDS implementation in the STM32 firmware is shown in figure \ref{fig:wavegen-dds}. It is based on a \textit{numerically controlled oscillator} (NCO). NCO consists of a \textit{phase accumulator} register and a \textit{tuning word} which is periodically added to it at a constant rate in a timer interrupt handler. The value of the tuning word determines the output waveform frequency. The look-up table must have a power-of-two length so that it can be addressed by the \textit{n} most significant bits of the phase accumulator. An additional control word could be added to this address to implement a phase offset for applications like a phase-shift modulation.
The output frequency is calculated as \(f_\mathrm{out} = \dfrac{M\cdot f_\mathrm{c}}{2^n}\), where $M$ is the tuning word, $n$ is the bit length of the phase accumulator, and $f_c$ is the frequency of the phase-updating interrupt. The number of bits used to address the look-up table does not affect the output frequency; the table can be as large as the storage space allows. A tuning word value exceeding the lower part of the phase accumulator (including bits which directly enter the look-up address) will cause some values from the table to be skipped. A smaller tuning word, conversely, makes some values appear on the output more than once. This can be observed as steps or flat areas on the output. When the tuning word does not evenly divide $2^n$, that is, the modulo is non-zero, we can also observe jitter.
\subsubsection{DDS Implemented in Hardware}
DDS may be implemented in hardware, including the look-up table, often together with the DAC itself, which is then called a \textit{Complete DDS}. That is the case of e.g. AD9833 from Analog Devices. As the software implementation depends on a periodic interrupt, it's often advantageous to use a component like this when we need higher output frequencies where the use of an interrupt is not possible. GEX can control an external waveform generator like the AD9833 using an SPI port.
\todo[inline]{screenshots of a real demo, also reference to all-about-direct-digital-synthesis.pdf}
\todo[inline]{todo}

@ -0,0 +1,222 @@
\chapter{Communication Protocol} \label{sec:tinyframe}
GEX can be controlled through a hardware UART, the USB or over a wireless link. To minimize the firmware complexity, all the three connection methods are handled by the same protocol stack and are interchangeable.
The communication is organized in transactions. A transaction consists of one or more messages going in either direction. Messages can be stand-alone, or chained with a response or a follow-up message using the transaction ID. Both peers, GEX and the client application running on the PC, are equal in the communication: either side can independently initiate a transaction at any time.
GEX uses a framing library \textit{TinyFrame}, developed likewise by the author, but kept as a separate project for easier re-use in different applications. The library implements frame building and parsing, checksum calculation and a system of message listeners. An interested reader may find more technical details and the API in its documentation.
\section{Frame Structure}
Message frames have the following structure:
\begin{table}[h]
\centering
%\hspace{-1.5em}
\begin{tabular}{rccccccc}
\toprule
\multicolumn{1}{c|}{} &
\multicolumn{5}{c}{Header}&
\multicolumn{2}{|c}{Body} \\
\midrule
%
\textit{Field} &
\textbf{SOF} &
\textbf{Frame ID} &
\makecell{ \Gape{\textbf{Payload}} \\ \Gape{\textbf{Length}} } &
\makecell{ \textbf{Frame} \\ \textbf{type} } &
\makecell{ \textbf{Header} \\ \textbf{checksum} } &
\textbf{Payload} &
\makecell{ \textbf{Payload} \\ \textbf{checksum} } \\
%
\midrule
\textit{Bytes} &
1 &
2 &
2 &
1 &
1 &
... &
1 \\
%
\bottomrule
\end{tabular}
\end{table}
The field widths shown here are those used in GEX; TinyFrame is flexible and the data type of all fields can be customized, as well as the checksum type. The SOF byte is always 0x01.
\textit{Frame ID}, which could be better described as \textit{Transaction ID}, uniquely identifies each transaction. The most significant bit is set to a different value in each peer to avoid ID conflicts, and the rest of the ID field is incremented with each initiated transaction.
\section{Message Listeners}
After sending a message that should receive a response, the peer registers an \textit{ID listener} with the ID of the sent message. A response reuses the original frame ID and when it is received, this listener is called to process it. ID listeners can also be used to receive multi-part messages re-using the original ID.
\textit{Frame type} describes the payload and does not have any prescribed format; the values are defined by application (here, GEX). A \textit{type listener} may be registered to handle all incoming messages with a given frame type. It works in a similar way to an ID listener and has a lower priority.
Each message can be handled by only one listener, unless it explicitly requests the message to be passed on to a lower priority one. Messages unhandled by any listener are given to a default listener, which can e.g. write an error to a debug log.
\section{Designated Frame Types in GEX}
The following table lists all frame types used by GEX. It is divided into four logical sections: General, Bulk Read/Write, Unit Access, and Settings.
\begin{table*}[h]
\centering
\begin{tabular}{clll}
\toprule
\textbf{Frame type} & \textbf{Function} & \textbf{Note} \\
\midrule
0x00 & Success & \textit{Payload depends on context} \\
0x01 & Ping & \textit{GEX responds with Success and its version string} \\
0x02 & Error & \textit{Payload contains the error message} \\
\midrule
0x03 & Bulk Read Offer & \textit{An offer of data to read using }0x04 \\
0x04 & Bulk Read Poll & \textit{Requesting to read a block of data} \\
0x05 & Bulk Write Offer & \textit{An offer to receive a bulk write transaction} \\
0x06 & Bulk Data & \textit{Used for both reading and writing} \\
0x07 & Bulk End & \textit{Marks the last "Bulk Data" frame} \\
0x08 & Bulk Abort & \textit{} \\
\midrule
0x10 & Unit Request & \textit{Request from PC to a unit} \\
0x11 & Unit Report & \textit{Spontaneous event generated by a unit} \\
\midrule
0x20 & List Units & \textit{Read a list of all instantiated units} \\
0x21 & INI Read & \textit{Request a bulk read transaction of an INI file} \\
0x22 & INI Write & \textit{Request a bulk write transaction of an INI file} \\
0x23 & Persist Config & \textit{Write updated configuration to Flash} \\
\bottomrule
\end{tabular}
\end{table*}
\section{Bulk Read and Write Transactions}
The bulk read and write transactions are generic, multi-message exchanges which are used to transfer the INI configuration files. They could possibly be used by some future unit requiring to transfer a large amount of data (e.g. to read image data from a camera).
The reason for splitting a long file into multiple messages, rather than sending it all in one, lies in the hardware limitations of the platform, specifically its small amount of RAM (the STM32F072 has only 16\,kB). A message cannot be processed until its payload checksum is received and verified; however, the configuration file can have several kilobytes, owning to the numerous explanatory comments, which would require a prohibitively large buffer. Further, the GEX module may need some time to process a part of the message before it can receive more data, which is easily achieved by this multi-part transport where each chunk must be confirmed before proceeding to the next.
A read or write transaction can be aborted by a frame 0x08 (Bulk Abort) at any time, though aborting a write transaction may leave the configuration in a corrupt state. As hinted in the introduction of this chapter, a transaction is defined by sharing a common frame ID. Thus, all frames in a bulk transaction must have the same ID, otherwise the ID listeners won't be called and the transaction will fail.
\subsection{Bulk Read}
To read an INI file, we first send a frame 0x21 (INI Read), specifying the target file in the payload:
\begin{minted}{c}
struct Payload_INI_Read {
uint8_t filenum; // 0 - UNITS.INI, 1 - SYSTEM.INI
};
\end{minted}
What follows is a standard bulk read transaction with the requested file.
GEX offers the file for reading with a frame 0x03 (Bulk Read Offer):
\begin{minted}{c}
struct Payload_BulkReadOffer {
uint32_t total_length; // full size of the file in bytes
uint32_t max_chunk_size; // largest chunk that can be read at once
};
\end{minted}
Now we can proceed to read the file using 0x04 (Bulk Read Poll), which is always responded to with 0x06 (Bulk Data), or 0x07 (Bulk End) if this was the last frame. Data frames have only the useful data as their payload.
The 0x04 (Bulk Read Poll) payload specifies how many bytes we want to read:
\begin{minted}{c}
struct Payload_BulkReadPoll {
uint32_t max_chunk_size; // how many bytes to read
};
\end{minted}
\subsection{Bulk Write}
To overwrite an INI file, we first send a frame 0x22 (INI Write), specifying its size in the payload. Which file is written is detected automatically from the first INI section.
\begin{minted}{c}
struct Payload_INI_Write {
uint32_t total_length; // file size in bytes
};
\end{minted}
The write request is confirmed by a frame 0x05 (Bulk Write Offer):
\begin{minted}{c}
struct Payload_BulkWriteOffer {
uint32_t total_length; // the expected file size in bytes
uint32_t max_chunk_size; // largest chunk that can be written at once
};
\end{minted}
We can now send the file as a series of frames 0x06 (Bulk Data), or 0x07 (Bulk End) in the last frame. Each written chunk is confirmed by 0x00 (Success).
\subsection{Persisting the Changed Configuration to Flash}
The written INI file is immediately parsed and the settings are applied. However, those changes are not persistent: they exist only in RAM and will be lost when the module restarts. To save the current state to Flash, issue a frame 0x23 (Persist Config). This has the same effect as pressing the LOCK button (or replacing the LOCK jumper) when the INI files are edited using the virtual mass storage.
It should be noted that after flashing a firmware, the Flash control registers may remain in an unexpected state and the module must first be manually restarted before attempting to persist settings. Otherwise an assertion will fail and the module is restarted by a watchdog, losing the temporary changes.
% TODO there must be a workaround, and then this paragraph can be removed.
\section{Reading a List of Units}
The frame 0x20 (List Units) requests a list of all available units in the GEX module. The list includes all units' callsigns, names and types. The response payload has the following format (in pseudocode, as it can't be expressed as a C struct like the previous examples):
\begin{minted}{c}
struct {
uint8_t count;
for all units {
uint8_t callsign;
cstring unit_name; // 0-terminated char array
cstring driver_name;
}
}
\end{minted}
\section{Unit Requests and Reports}
Frame types 0x10 (Unit Request) and 0x11 (Unit Report) are dedicated to messages sent to and by unit instances. Each has a fixed header (\textit{inside the payload}) followed by unit-specific data.
\subsection{Unit Requests}
Unit requests deliver a message from the host to a unit instance. Unit drivers implements different commands, each with its own payload structure. The frame 0x10 (Unit Request) has the following structure:
\begin{minted}{c}
struct Payload_UnitRequest {
uint8_t callsign;
uint8_t command; // handled by the unit driver
uint8_t payload[]; // size and content depend on the command
};
\end{minted}
The most significant bit of the command byte (0x80) has a special meaning: when set, the message delivering routine responds with 0x00 (Success) after the command completes, unless an error occurred. That is used to get a confirmation that the message was delivered and the module operates correctly (as opposed to e.g. a lock-up resulting in a watchdog reset). Requests which normally generate a response (e.g. reading a value from the unit) should not be sent with this bit set. As a result of this special treatment of the highest bit, there can be only 127 different commands per unit.
\subsection{Unit Reports}
Several unit types can produce asynchronous events, such as reporting a pin change or a triggering condition. The event is timestamped and sent with a frame type 0x11 (Unit Report):
\begin{minted}{c}
struct Payload_UnitRequest {
uint8_t callsign;
uint8_t report_type; // defines the payload structure
uint64_t timestamp; // microseconds since power-on
uint8_t payload[]; // size and content depend on the report type
};
\end{minted}

@ -0,0 +1,41 @@
\chapter{Wireless Interface}
Four methods of a wireless connection have been considered: bluetooth (e.g. CC2541), WiFi with ESP8266, LoRA or GFSK with SX1276, and a 2.4\,GHz radio link with NRF24L01+. Bluetooth was dismissed early for its complexity and ESP8266 for its high consumption in continuous reception mode, although both solutions might be viable for certain applications and with more time for evaluation. The SX1276 and NRF24L01+ have both been tested using the first GEX prototype, confirming its usefulness as a hardware development tool.
\section{Comparing SX1276 vs. NRF24L01+}

@ -20,6 +20,8 @@
\usepackage{bigfoot} % verbatin in footnote
\usepackage{makecell}
\newcommand{\uF}{\micro\farad}
\newcommand{\nF}{\nano\farad}
\newcommand{\cm}{\centi\metre}
@ -43,7 +45,8 @@
% Fix overful hbox
\setlength{\emergencystretch}{.5em}
\setsvg{svgpath=img/}
%\setsvg{svgpath=img/}
\svgpath{img/}
% Nastavení ctuthesis
@ -178,3 +181,15 @@
\newcommand\nobr[1]{\mbox{#1}}
\usepackage{pmboxdraw}
% ---- Booktabs config ----
%\setlength{\heavyrulewidth}{0.11em}
%\setlength{\lightrulewidth}{0.05em}
%\setlength{\cmidrulewidth}{0.03em}
\setlength{\heavyrulewidth}{0.5mm}
\setlength{\lightrulewidth}{0.25mm}
\setlength{\cmidrulewidth}{0.25mm}

@ -1,4 +1,4 @@
\hspace*{-1.5em}
\hspace*{-3em}
\begin{minipage}[t]{0.5\textwidth}\scriptsize
\begin{verbatim}
Device Descriptor:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -20,6 +20,16 @@
% abstract
% TOC
%TODO FAT 16 and its emulation
%TODO touch sensing -> hw functions
%TODO wireless
%TODO schematics, pcb, photos
%TODO add references (datasheet, specs)
%TODO look for liberty free photos
%TODO add image source references
%TODO retrace diagrams?
\part{Introduction}
\input{ch.introduction}
\input{ch.requirement_analysis}
@ -29,13 +39,15 @@
\input{ch.usb}
\input{ch.freertos}
\input{ch.fat16} % TODO
\input{ch.hw_buses} % TODO
\input{ch.hw_functions} % TODO
\input{ch.fat16}
\input{ch.hw_buses}
\input{ch.hw_functions}
\part{Firmware Implementation}
\input{ch.fw_structure}
\input{ch.tinyframe}
\input{ch.wireless}
\input{ch.gex_units}
\part{Hardware Design}

Loading…
Cancel
Save