remove contractions in some places

master
Ondřej Hruška 7 years ago
parent 5bff138bed
commit 188091c772
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 28
      cast.sh
  2. 6
      ch.existing_solutions.tex
  3. 10
      ch.fat16.tex
  4. 12
      ch.freertos.tex
  5. 18
      ch.fw_structure.tex
  6. 4
      ch.gex_units.tex
  7. 8
      ch.hw_buses.tex
  8. 10
      ch.hw_functions.tex
  9. 4
      ch.introduction.tex
  10. 4
      ch.pc_software.tex
  11. 12
      ch.requirement_analysis.tex
  12. 2
      ch.tinyframe.tex
  13. 6
      ch.usb.tex
  14. 4
      ch.wireless.tex
  15. 584
      chrisper.py
  16. 30
      formalize.sh
  17. BIN
      thesis.pdf

@ -1,28 +0,0 @@
#/bin/bash
if [[ "$1" == "" ]]; then
echo "missing arg"
exit
fi
echo "Processing $1"
sed -i "s/low cost/low-cost/g"
sed -i "s/general purpose/general-purpose/g"
sed -i "s/high level/high-level/g"
sed -i "s/low level/low-level/g"
sed -i "s/don't/do not/g"
sed -i "s/Don't/Do not/g"
sed -i "s/it's/it is/g"
sed -i "s/It's/It is/g"
sed -i "s/isn't/is not/g"
sed -i "s/aren't/are not/g"
sed -i "s/Aren't/Are not/g"
sed -i "s/wouldn't/would not/g"
sed -i "s/Wouldn't/Would not/g"
sed -i "s/couldn't/could not/g"
sed -i "s/Couldn't/Could not/g"
sed -i "s/shouldn't/should not/g"
sed -i "s/Shouldn't/Should not/g"
sed -i "s/can't/cannot/g"
sed -i "s/in recent years/recently/g"
sed -i "s/In recent years/Recently/g"

@ -1,6 +1,6 @@
\chapter{\label{sec:prior-art}Existing Solutions}
The idea of making it easier to interact with low level hardware from a \gls{PC} is not new. Several solutions to this problem have been developed, each with its own advantages and drawbacks. Some examples will be presented in this chapter.
The idea of making it easier to interact with low-level hardware from a \gls{PC} is not new. Several solutions to this problem have been developed, each with its own advantages and drawbacks. Some examples will be presented in this chapter.
\section{Raspberry Pi}
@ -75,8 +75,8 @@ The performance GEX can provide may not always match that of those professional
\todo[inline]{links}
Firmata is a communication protocol based on MIDI (\textit{Musical Instrument Digital Interface}) for passing data to and from embedded microcontrollers. MIDI is mainly used for attaching electronic musical instruments, such as synthesizers, keyboards, mixers etc., to each other or to a PC. Firmata was designed for Arduino as a high level abstraction for its connection to the PC, typically using a FTDI chip or equivalent.
Firmata is a communication protocol based on MIDI (\textit{Musical Instrument Digital Interface}) for passing data to and from embedded microcontrollers. MIDI is mainly used for attaching electronic musical instruments, such as synthesizers, keyboards, mixers etc., to each other or to a PC. Firmata was designed for Arduino as a high-level abstraction for its connection to the PC, typically using a FTDI chip or equivalent.
Implementing Firmata in the GEX firmware would make it possible to use existing Firmata libraries on the PC side. However, the protocol is limited by the encompassing MIDI format and isn't very flexible.
Implementing Firmata in the GEX firmware would make it possible to use existing Firmata libraries on the PC side. However, the protocol is limited by the encompassing MIDI format and is not very flexible.
\fi

@ -2,7 +2,7 @@
A \gls{FS} is used by GEX to provide a comfortable access to the configuration files. By emulating a Mass Storage \gls{USB} device, the module appears as a thumb drive on the host \gls{PC}, and the user can edit its configuration using their preferred text editor. The FAT16 file system was selected for its simplicity and a good cross-platform support \cite{os-support-table}.
Three variants of the \gls{FAT} file system exist: FAT12, FAT16, and FAT32. FAT12 was used on floppy disks and it is similar to FAT16, except for additional size constraints and a \gls{FAT} entry packing scheme. FAT16 and FAT32 are FAT12's later developments from the time when hard disks became more common and the old addressing scheme couldn't support their larger capacity.
Three variants of the \gls{FAT} file system exist: FAT12, FAT16, and FAT32. FAT12 was used on floppy disks and it is similar to FAT16, except for additional size constraints and a \gls{FAT} entry packing scheme. FAT16 and FAT32 are FAT12's later developments from the time when hard disks became more common and the old addressing scheme could not support their larger capacity.
This chapter will explain the structure of FAT16 and the challenges faced when trying to emulate it without a physical data storage.
@ -109,9 +109,9 @@ The GEX firmware adapts several parts of the DAPLink code, optimizing its \gls{R
As shown in table \ref{tab:fat16-disk-areas}, the disk consists of several areas. The boot sector is immutable and can be stored in and read from the Flash memory. The handling of the other disk areas (\gls{FAT}, data area) depends on the type of access: read or write.
The user can only read files that already exist on the disk, in our case, \verb|UNITS.INI| and \verb|SYSTEM.INI|. Those files are generated from the binary settings storage, and conversely, parsed, line-by-line, without ever existing in their full form. This fact makes our task more challenging, as the files can't be easily measured and there's no obvious way to read a sector from the middle of a longer file. We solve this by implementing two additional functions in the INI file writer: a \textit{read window} and a \textit{dummy read mode}.
The user can only read files that already exist on the disk, in our case, \verb|UNITS.INI| and \verb|SYSTEM.INI|. Those files are generated from the binary settings storage, and conversely, parsed, line-by-line, without ever existing in their full form. This fact makes our task more challenging, as the files cannot be easily measured and there's no obvious way to read a sector from the middle of a longer file. We solve this by implementing two additional functions in the INI file writer: a \textit{read window} and a \textit{dummy read mode}.
A read window is a byte range which we wish to generate. The INI writer discards bytes before the start of the read window, writes those inside the window to our data buffer, and stops when its end is reached. This lets us extract a sector from anywhere in a file. The second function, dummy read, is tied to the window function: we set the start index so high that it's never reached (e.g. 0xFFFFFFFF), and have the writer count discarded characters. When the dummy file generation ends, this character counter holds its size.
A read window is a byte range which we wish to generate. The INI writer discards bytes before the start of the read window, writes those inside the window to our data buffer, and stops when its end is reached. This lets us extract a sector from anywhere in a file. The second function, dummy read, is tied to the window function: we set the start index so high that it is never reached (e.g. 0xFFFFFFFF), and have the writer count discarded characters. When the dummy file generation ends, this character counter holds its size.
Now, just one problem remains: how to tell which sectors contain which part of our files? This is straightforward when we realize that the files change only when the user modifies the settings. After each such change, an algorithm is run which allocates clusters to the files and preserves this information in a holding structure. A subsequent read access simply requires a look into this structure and the corresponding chunk of a file may be served using the read window function. The \gls{FAT} can be dynamically generated from this information as well.
@ -119,7 +119,7 @@ Now, just one problem remains: how to tell which sectors contain which part of o
A write access to the disk is more challenging to emulate than a read access, as the host OS tends to be somewhat unpredictable. In GEX's case we are interested only in the action of overwriting an already existing file, but it is interesting to also analyze other actions the host may perform.
It must be noted that due to the nonexistence of a physical storage medium, it's not possible to read back a file the host has previously written, unless we store or re-generate its content when such a read access occurs. The \gls{OS} may show the written file on the disk, but when the user tried to read it, the action either fails, or shows a cached copy. The emulator woulds around this problem by temporarily reporting that the storage medium has been removed, forcing the host to re-load its contents.
It must be noted that due to the nonexistence of a physical storage medium, it is not possible to read back a file the host has previously written, unless we store or re-generate its content when such a read access occurs. The \gls{OS} may show the written file on the disk, but when the user tried to read it, the action either fails, or shows a cached copy. The emulator woulds around this problem by temporarily reporting that the storage medium has been removed, forcing the host to re-load its contents.
\subsubsection{File Deletion}
@ -150,7 +150,7 @@ It can be expected the host \gls{OS} first finds the free sectors and a free fil
To properly handle such a file by the emulator, we could, in theory, find its name from the directory table, which has been updated, and then collect the data written to the corresponding clusters. In practice, confirmed by experiments with a real Linux host, those three steps may happen in any order, and often the content is written before the directory table is updated.
The uncertain order of the written disk areas poses a problem when the file name has any significance, as we can't store the received file data while waiting for the name to be written. The DAPLink mbed flashing firmware solves this by analyzing the content of the first written sector of the file, which may contain the binary \gls{NVIC} table, or a character pattern typical for Intel hex files.
The uncertain order of the written disk areas poses a problem when the file name has any significance, as we cannot store the received file data while waiting for the name to be written. The DAPLink mbed flashing firmware solves this by analyzing the content of the first written sector of the file, which may contain the binary \gls{NVIC} table, or a character pattern typical for Intel hex files.
\subsection{File Content Change}

@ -1,6 +1,6 @@
\chapter{FreeRTOS} \label{sec:freertos}
FreeRTOS is a free, open-source real time operating system kernel targeted at embedded systems and ported to many different microcontroller architectures \cite{freertos-ports-list}. FreeRTOS provides the task scheduler, forming the central part of the RTOS, and implements queues, semaphores, and mutexes for message passing and synchronization between concurrent tasks (summarily called synchronization objects\footnote{In this chapter sometimes simply called ``objects''.}). FreeRTOS is compact and designed to be easy to understand; it's written in C, with the exception of some architecture-specific routines which use assembly. A complete overview of the system is available in the FreeRTOS reference manual \cite{freertos-rm} and its guide book \cite{freertos-book}.
FreeRTOS is a free, open-source real time operating system kernel targeted at embedded systems and ported to many different microcontroller architectures \cite{freertos-ports-list}. FreeRTOS provides the task scheduler, forming the central part of the RTOS, and implements queues, semaphores, and mutexes for message passing and synchronization between concurrent tasks (summarily called synchronization objects\footnote{In this chapter sometimes simply called ``objects''.}). FreeRTOS is compact and designed to be easy to understand; it is written in C, with the exception of some architecture-specific routines which use assembly. A complete overview of the system is available in the FreeRTOS reference manual \cite{freertos-rm} and its guide book \cite{freertos-book}.
FreeRTOS is used in GEX for its synchronization objects that make it easy to safely pass messages between interrupts and working threads, without deadlocks or race conditions. The built-in stack overflow protection helps with a more efficient memory allocation, and the heap allocator provided by FreeRTOS enables a thread-safe dynamic allocation with a shared heap.
@ -14,7 +14,7 @@ At start-up the firmware initializes the kernel, registers tasks to run and star
\subsubsection{Task Run States}
Tasks can be in one of four states: Suspended, Ready, Blocked, Running. The Suspended state does not normally occur in a task's life cycle, it's entered and left using API calls from the application. A task is in the Ready state when it can run, but is currently paused because a higher priority task is running. It enters the Running state when the scheduler switches to it. A Running task can wait for a synchronization object (e.g. a mutex) to be available; at this point it enters a Blocked state and the scheduler runs the next Ready task. When no tasks can run, the Idle Task takes control; it can either enter a sleep state to save power, or wait in a loop until another task is available. The Idle task is always either Ready or Running and has the lowest priority of all tasks.
Tasks can be in one of four states: Suspended, Ready, Blocked, Running. The Suspended state does not normally occur in a task's life cycle, it is entered and left using API calls from the application. A task is in the Ready state when it can run, but is currently paused because a higher priority task is running. It enters the Running state when the scheduler switches to it. A Running task can wait for a synchronization object (e.g. a mutex) to be available; at this point it enters a Blocked state and the scheduler runs the next Ready task. When no tasks can run, the Idle Task takes control; it can either enter a sleep state to save power, or wait in a loop until another task is available. The Idle task is always either Ready or Running and has the lowest priority of all tasks.
\subsubsection{Task Switching and Interrupts}
@ -22,7 +22,7 @@ Task switching occurs periodically in a timer interrupt, usually every 1\,ms; in
After one tick of run time, the Running task is paused (enters Ready state), or continues to run if no higher priority task is available. If a higher priority task waits for an object and this is made available in an interrupt, the previously running task is paused and the waiting task is resumed immediately (enters the Running state). FreeRTOS defines an interrupt-friendly variant of some of the \gls{API} functions intended for this purpose.
Only a subset of the FreeRTOS \gls{API} can be accessed from interrupt routines this way, for example it's not possible to use the delay function or wait for an object with a timeout, because the SysTick interrupt, which increments the tick counter, has the lowest priority and couldn't run. This is by design, to prevent unexpected context switching in application interrupts.
Only a subset of the FreeRTOS \gls{API} can be accessed from interrupt routines this way, for example it is not possible to use the delay function or wait for an object with a timeout, because the SysTick interrupt, which increments the tick counter, has the lowest priority and could not run. This is by design, to prevent unexpected context switching in application interrupts.
FreeRTOS uses a \textit{priority inheritance} mechanism to prevent situations where a high priority task waits for an object held by a lower priority task (called \textit{priority inversion}). The blocking task's priority is temporarily raised to the level of the blocked high priority task so it can finish faster and release the held object. Its priority is then degraded back to the original value. When the lower priority task itself is blocked, the same process can be repeated.
@ -31,7 +31,7 @@ FreeRTOS uses a \textit{priority inheritance} mechanism to prevent situations wh
FreeRTOS provides binary and counting semaphores, mutexes and queues. Each of those objects will now be briefly introduced.
\begin{itemize}
\item \textbf{Binary semaphores} can be used for task notifications, e.g. a task waits for a semaphore to be set by an interrupt when a byte is received on the serial port. This makes the task Ready and if it has a higher priority than the previously running task, it's immediately resumed to process the event.
\item \textbf{Binary semaphores} can be used for task notifications, e.g. a task waits for a semaphore to be set by an interrupt when a byte is received on the serial port. This makes the task Ready and if it has a higher priority than the previously running task, it is immediately resumed to process the event.
\item \textbf{Counting semaphores} are used to represent available resources. A pool of software or hardware resources is accompanied by a counting semaphore, so that tasks can wait for a resource to become available in the pool and then subtract the semaphore value. After finishing with a resource, the semaphore is incremented again and another task can use it.
@ -40,11 +40,11 @@ FreeRTOS provides binary and counting semaphores, mutexes and queues. Each of th
\item \textbf{Queues} are used for passing messages between tasks, or from interrupts to tasks. Both sending and receiving of queue messages can block the task until the operation becomes possible. A queue handing task is often simply a loop which tries to read from the queue with an infinite timeout and processes the received data once the reading succeeds.
\end{itemize}
It must be noted that synchronization objects like mutexes and semaphores can help combat concurrent access only when used consistently and correctly. A locked mutex can't guard against a rogue task accessing the protected resource without checking.
It must be noted that synchronization objects like mutexes and semaphores can help combat concurrent access only when used consistently and correctly. A locked mutex cannot guard against a rogue task accessing the protected resource without checking.
\section{Stack Overflow Protection}
Each task in FreeRTOS is assigned a block of \gls{RAM} to use as its stack when it runs. This is where the stack pointer is restored to in the context switch. It can happen that a insufficient stack is exceeded and the stack pointer moves outside the designated area. Without countermeasures this would mean that we are overwriting bytes in some unrelated memory structure, perhaps another task's control block or a stack.
A stack overflow protection can be enabled by a flag in the FreeRTOS configuration file (more details in \cite{freertos-stackov}). This function works in two ways: the more obvious is a simple check that the stack pointer remains in the designated area; however, as the check may be performed only in the scheduler interrupt, it can happen that the stack pointer exceeds the bounds only temporarily and returns back before the stack can be checked for overflow. The stack is thus filled with a known filler value before starting the task, and the last few bytes are then tested to match this value. Not only can we detect a stack overflow more reliably, this feature also makes it possible to estimate the peak stack usage by counting the remaining filler values in the stack area. Naturally, we can't distinguish between the original filler values and the same value stored on the stack; still, this method proves remarkably successful in the detection of misconfigured stack size.
A stack overflow protection can be enabled by a flag in the FreeRTOS configuration file (more details in \cite{freertos-stackov}). This function works in two ways: the more obvious is a simple check that the stack pointer remains in the designated area; however, as the check may be performed only in the scheduler interrupt, it can happen that the stack pointer exceeds the bounds only temporarily and returns back before the stack can be checked for overflow. The stack is thus filled with a known filler value before starting the task, and the last few bytes are then tested to match this value. Not only can we detect a stack overflow more reliably, this feature also makes it possible to estimate the peak stack usage by counting the remaining filler values in the stack area. Naturally, we cannot distinguish between the original filler values and the same value stored on the stack; still, this method proves remarkably successful in the detection of misconfigured stack size.

@ -1,6 +1,6 @@
\chapter{Application Structure}
GEX is designed to be modular and easy to extend. It's composed of a set of functional blocks (also called \textit{units}), 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.
GEX is designed to be modular and easy to extend. It is composed of a set of functional blocks (also called \textit{units}), 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 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 communication protocol will be described in chapters \ref{sec:tinyframe} and \ref{sec:units-overview}.
@ -18,7 +18,9 @@ Before going into implementation details, we'll have a look at GEX from the outs
\caption{\label{fig:users-view-of-gex}Physical user interface of a GEX module}
\end{figure}
The GEX firmware can be flashed to a STM32 Nucleo or Discovery board or a custom \gls{PCB}. It's equipped with a \gls{USB} connector to connect to the host \gls{PC}. GEX loads its configuration from the Flash memory, configures its peripherals, sets up the function blocks and enables the selected communication interface(s).
The GEX firmware can be flashed to a STM32 Nucleo or Discovery board or a custom \gls{PCB}. Discovery boards are equipped with a ``user \gls{USB}'' connector routed to the application \gls{MCU}; Nucleo boards have only the ST-Link \gls{USB} connector and, since the ST-Link version 2.1, offer a built-in USB-serial converter leading to one of the \gls{MCU}'s hardware \glspl{UART}.
After powering on, GEX loads its configuration from the Flash memory, configures its peripherals, sets up the function blocks and enables the selected communication interface(s). From this point, the module is operational and can be configured or used as needed by the user.
The physical user interface of the module is shown in figure \ref{fig:users-view-of-gex}. When a \gls{USB} cable connects the board to a \gls{PC}, the \gls{PC} \gls{OS} enumerates it and either recognizes the communication interface as \gls{CDCACM} (Virtual serial port), or leaves it without a software driver attached, to be accessed directly as raw \gls{USB} endpoints, based on GEX settings.
The connection using a physical UART and a USB/UART adapter works in a very similar way, as does the wireless connection (described in more detail below).
@ -39,7 +41,7 @@ To use it, the user needs to connect a wireless gateway module to their host \gl
\subsection{Using the Control Interface}
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 \gls{GUI} front-end built on those libraries. The configuration can be stored in the module, but it's also possible to temporarily replace it using the control \gls{API}. This way, the settings are loaded automatically when the user's program starts and may differ for different programs.
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 \gls{GUI} front-end built on those libraries. The configuration can be stored in the module, but it is also possible to temporarily replace it using the control \gls{API}. This way, the settings are loaded automatically when the user's program starts and may differ for different programs.
\section{Internal Structure Block Diagram}
@ -163,7 +165,7 @@ The resources used by the core framework are taken by a virtual unit \verb|SYSTE
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.
As the settings persist after a firmware update, it is important to maintain backwards compatibility. This is achieved by prefixing the settings block of each unit with 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 INI files, which can be edited through the communication \gls{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 \gls{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.
@ -171,7 +173,7 @@ The INI files, which can be edited through the communication \gls{API} or using
The firmware supports three different communication ports: hardware \gls{UART}, \gls{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 \gls{PC}-side application) they all appear as a full duplex serial port. To use interfaces other than \gls{USB}, the user must configure those in the system settings (a file \verb|SYSTEM.INI| on the configuration disk).
At start-up, the firmware enables the \gls{USB} peripheral, configures the device library and waits for enumeration by the host \gls{PC}. When not enumerated, it concludes the \gls{USB} cable is not connected, and tries some other interface. The \gls{UART} interface can't be tested as reliably, but it's possible to measure the voltage on the Rx pin. When idle, a \gls{UART} Rx line should be high (here 3.3\,V). The wireless module, when connected using \gls{SPI}, can be detected by reading a register with a known value and comparing those.
At start-up, the firmware enables the \gls{USB} peripheral, configures the device library and waits for enumeration by the host \gls{PC}. When not enumerated, it concludes the \gls{USB} cable is not connected, and tries some other interface. The \gls{UART} interface cannot be tested as reliably, but it is possible to measure the voltage on the Rx pin. When idle, a \gls{UART} Rx line should be high (here 3.3\,V). The wireless module, when connected using \gls{SPI}, can be detected by reading a register with a known value and comparing those.
\subsubsection{USB Connection}
@ -179,7 +181,7 @@ GEX uses vid:pid \verb|1209:4c60| and the wireless gateway \verb|1209:4c61|. The
\subsubsection{Communication UART}
The parameters of the communication \gls{UART} (such as the baud rate) are defined in \verb|SYSTEM.INI|. It's mapped to pins PA2 and PA3; this is useful with STM32 Nucleo boards that don't include a User \gls{USB} connector, but provide a \gls{USB}-serial bridge using the on-board ST-Link programmer, connected to those pins.
The parameters of the communication \gls{UART} (such as the baud rate) are defined in \verb|SYSTEM.INI|. It is mapped to pins PA2 and PA3; this is useful with STM32 Nucleo boards that do not include a User \gls{USB} connector, but provide a \gls{USB}-serial bridge using the on-board ST-Link programmer, connected to those pins.
This is identical to the \gls{USB} connection from the \gls{PC} application's side, except a physical \gls{UART} is necessarily slower and does not natively support flow control. The use of the Xon and Xoff software flow control is not practical with binary messages that could include those bytes by accident, and the ST-Link \gls{USB}-serial adapter does not implement hadware flow control.
@ -195,14 +197,14 @@ One of the key functions of the core framework is to deliver messages from the h
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 \gls{ADC} trigger or an \gls{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.
The framework provides one more messaging service to the units: event reporting. An asynchronous event, such as an external interrupt, an \gls{ADC} trigger or an \gls{UART} data reception needs to be reported to the host. This message is annotated by the unit callsign so the user application knows its origin.
\subsection{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 \gls{DMA} channels, as is common in STM32 microcontrollers:
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 cannot 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 \gls{DMA} channels, as is common in STM32 microcontrollers:
\begin{minted}{c}
void DMA1_Channel4_5_6_7_IRQHandler(void)

@ -4,7 +4,7 @@ This chapter describes all functional blocks (units) implemented in GEX, version
Each unit's description will be accompanied by a corresponding snippet from the configuration file, and a list of supported commands and events. The commands and events described here form the payload of TinyFrame messages 0x10 (Unit Request) and 0x11 (Unit Report), as described in section \ref{sec:unit_requests_reports}.
The number in the first column of the command (or event) tables, marked as ``Code'', is the command number (or report type) used in the payload to identify how the message data should be treated. When the request or response payload is empty, it's omitted from the table. The same applies to commands with no response, in which case adding 0x80 to the command number triggers a SUCCESS response after the command is finished.
The number in the first column of the command (or event) tables, marked as ``Code'', is the command number (or report type) used in the payload to identify how the message data should be treated. When the request or response payload is empty, it is omitted from the table. The same applies to commands with no response, in which case adding 0x80 to the command number triggers a SUCCESS response after the command is finished.
\subsection{Unit Naming}
@ -12,7 +12,7 @@ Unit types are named in uppercase (e.g. SPI, 1WIRE, NPX) in the INI file and in
\subsection{Packed Pin Access} \label{sec:packedpins}
Several units facilitate an access to a group of GPIO pins, such as the digital input and output units, or the SPI unit's slave select pins. The STM32 microcontroller's ports have 16 pins each, most of which can be configured to one of several alternate functions (e.g. SPI, PWM outputs, ADC input). As a consequence, it's common to be left with a discontiguous group of pins after assigning all the alternate functions needed by an application.
Several units facilitate an access to a group of GPIO pins, such as the digital input and output units, or the SPI unit's slave select pins. The STM32 microcontroller's ports have 16 pins each, most of which can be configured to one of several alternate functions (e.g. SPI, PWM outputs, ADC input). As a consequence, it is common to be left with a discontiguous group of pins after assigning all the alternate functions needed by an application.
\begin{figure}[h]
\centering

@ -29,7 +29,7 @@ RS-232 uses the \gls{UART} framing, but its levels are different: logical 1 is r
\section{SPI} \label{sec:theory-spi}
\acrfull{SPI} is a point-to-point or multi-drop master-slave interface based on shift registers. The \gls{SPI} connection with multiple slave devices is depicted in figure \ref{fig:spi-multislave}. It uses at least 4 wires: \gls{SCK}, \gls{MOSI}, \gls{MISO} and \gls{SS}. \gls{SS} is often marked \gls{CSB} or \gls{NSS} to indicate it's active low. Slave devices are addressed using their \gls{SS} input while the data connections are shared. A slave that's not addressed releases the \gls{MISO} line to a high impedance state so it doesn't interfere in ongoing communication.
\acrfull{SPI} is a point-to-point or multi-drop master-slave interface based on shift registers. The \gls{SPI} connection with multiple slave devices is depicted in figure \ref{fig:spi-multislave}. It uses at least 4 wires: \gls{SCK}, \gls{MOSI}, \gls{MISO} and \gls{SS}. \gls{SS} is often marked \gls{CSB} or \gls{NSS} to indicate that its active state is 0. Slave devices are addressed using their \gls{SS} input while the data connections are shared. A slave that's not addressed releases the \gls{MISO} line to a high impedance state so it doesn't interfere in ongoing communication.
\begin{figure}[h]
\centering
@ -45,7 +45,7 @@ RS-232 uses the \gls{UART} framing, but its levels are different: logical 1 is r
Transmission and reception on the \gls{SPI} bus happen simultaneously. A bus master asserts the \gls{SS} pin of a slave it wishes to address and then sends data on the \gls{MOSI} line while receiving a response on \gls{MISO}. The slave normally responds with 0x00 or a status register as the first byte of the response, before it can process the received command. A timing diagram is shown in figure \ref{fig:spi-timing}, including two configurable parameters: \gls{CPOL} and \gls{CPHA}.
\gls{SPI} devices often provide a number of control, configuration and status registers that can be read and written by the bus master. The first byte of a command usually contains one bit that determines if it's a read or write access, and an address field selecting the target register. The slave then either stores the following \gls{MOSI} byte(s) into the register, or sends its content back on \gls{MISO} (or both simultaneously).
\gls{SPI} devices often provide a number of control, configuration and status registers that can be read and written by the bus master. The first byte of a command usually contains one bit that determines if it is a read or write access, and an address field selecting the target register. The slave then either stores the following \gls{MOSI} byte(s) into the register, or sends its content back on \gls{MISO} (or both simultaneously).
\pagebreak[1] % TODO
\subsection{Examples of Devices Using SPI}
@ -71,7 +71,7 @@ It uses two connections (plus \gls{GND}): \gls{SDA} and \gls{SCL}, both open-dra
The protocol was developed by Philips Semiconductor (now NXP Semiconductors) and its implementors were required to pay licensing fees, until 2006, leading to the development of compatible implementations with different names, such as Atmel's \gls{TWI} or Dallas Semiconductor's ``Serial 2-wire Interface'' (e.g. used in the DS1307 \gls{RTC} chip). \gls{I2C} is a basis of the \gls{SMBus} and \gls{PMBus}, which add additional constraints and rules for a more robust operation.
The frame format is shown and explained in figure \ref{fig:i2c-frame}; more details may be found in the specification \cite{i2c-spec} or application notes and datasheets offered by chip vendors, such as the white paper from Texas Instruments \cite{understanding-i2c}. A frame starts with a start condition and stops with a stop condition, defined by an \gls{SDA} edge while the \gls{SCL} is high. The address and data bytes are acknowledged by the slave by sending a 0 on the open-drain \gls{SDA} line in the following clock cycle. A slave can terminate the transaction by sending 1 in place of the acknowledge bit. Slow slave devices may stop the master from sending more data by holding the SCL line low at the end of a byte, a feature called \textit{Clock Stretching}. As the bus is open-drain, the line can't go high until all participants release it.
The frame format is shown and explained in figure \ref{fig:i2c-frame}; more details may be found in the specification \cite{i2c-spec} or application notes and datasheets offered by chip vendors, such as the white paper from Texas Instruments \cite{understanding-i2c}. A frame starts with a start condition and stops with a stop condition, defined by an \gls{SDA} edge while the \gls{SCL} is high. The address and data bytes are acknowledged by the slave by sending a 0 on the open-drain \gls{SDA} line in the following clock cycle. A slave can terminate the transaction by sending 1 in place of the acknowledge bit. Slow slave devices may stop the master from sending more data by holding the SCL line low at the end of a byte, a feature called \textit{Clock Stretching}. As the bus is open-drain, the line cannot go high until all participants release it.
Two addressing modes are defined: 7-bit and 10-bit. Due to the small address space, exacerbated by many devices implementing only the 7-bit addressing, collisions between different chips on a shared bus are common; many devices thus offer several pins to let the board designer choose a few bits of the address by connecting them to different logic levels.
@ -95,7 +95,7 @@ The bus supports multi-master operation, which leads to the problem of collision
\section{1-Wire} \label{sec:theory-1wire}
The 1-Wire bus, developed by Dallas Semiconductor (acquired by Maxim Integrated), uses a single, bi-directional data line, which can also power the slave devices in a \textit{parasitic mode}, reducing the number of required wires to just two (compare with 3 in \gls{I2C} and 5 in \gls{SPI}, all including \gls{GND}). The parasitic operation is possible thanks to the data line resting at a logic high level most of the time, charging an internal capacitor.
The 1-Wire bus, developed by Dallas Semiconductor (acquired by Maxim Integrated), uses a single, bi-directional data line, which can also power the slave devices in a \textit{parasitic mode}, reducing the number of required wires to just two (compare with 3 in \gls{I2C} and 5 in \gls{SPI}, all including \gls{GND}). The parasitic operation is possible thanks to the data line resting at a high logic level most of the time, charging an internal capacitor.
1-Wire uses an open-drain connection for the data line, similar to \gls{I2C}, though the protocol demands it to be connected directly to V$_dd$ in some places when the parasitic mode is used; this is accomplished using an external transistor, or by reconfiguring the GPIO pin as output and setting it to 1, provided the microcontroller is able to supply a sufficient current.

@ -4,10 +4,10 @@ In addition to communication buses, described in chapter \ref{ch:hw_buses}, GEX
\section{Frequency Measurement} \label{sec:theory-fcap}
Applications like motor speed measurement and the reading of a \gls{VCO} or \gls{VCO}-based sensor's output demand a tool capable of measuring frequency. This can be done using a laboratory instrument such as the Agilent 53131A. A low cost solution is to use a timer/counter peripheral of a microcontroller.
Applications like motor speed measurement and the reading of a \gls{VCO} or \gls{VCO}-based sensor's output demand a tool capable of measuring frequency. This can be done using a laboratory instrument such as the Agilent 53131A. A low-cost solution can be realized using a timer/counter peripheral of a microcontroller.
\noindent
Two basic methods to measure frequency exist \cite{fcap-twotypes}, each with it's advantages and drawbacks:
Two basic methods to measure frequency exist \cite{fcap-twotypes}, each with its advantages and drawbacks:
\begin{itemize}
\item The \textit{direct method} (fig. \ref{fig:fcap-direct-dia}) is based on the definition of frequency as a number of cycles $n$ in a fixed-length time window $\tau$ (usually 1\,s); the frequency is then calculated as $f=n/\tau$.
@ -46,7 +46,7 @@ Which method to use depends on the frequency we want to measure; the worst-case
\caption[Frequency measurement methods comparison]{\label{fig:freqmethods-graph}Worst-case error using the two frequency measurement methods with an ideal 48\,MHz timer clock. The crossing lies at 7\,kHz with an error of 0.015\,\%, or 1.05\,Hz.}
\end{figure}
A good approach to a universal measurement, when we don't know the expected frequency beforehand, could be to first obtain an estimate using the direct method, and if the frequency is below the worst-case error crossing point (here 7\,kHz), to take a more precise measurement using the reciprocal method.
A good approach to a universal measurement, for cases where we do not know the expected frequency beforehand, could be to obtain an estimate using the direct method first, and if the frequency is below the worst-case error crossing point (here 7\,kHz, according to figure \ref{fig:freqmethods-graph}), to take a more precise measurement using the reciprocal method.
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 \gls{PCB}, temperature effects etc., causing measurement errors. A higher accuracy could be achieved using a \gls{TCO}, or, in the direct method, with the synchronization pulse provided by a \gls{GPS} receiver to time the measurement interval.
@ -84,7 +84,7 @@ The \gls{DAC} peripheral can produce a \gls{DC} level on the output pin based on
\subsection{Waveform Generation with DMA and a Timer} \label{sec:theory-dac-simple}
A straightforward, intuitive implementation of the waveform generator is illustrated in figure \ref{fig:wavegen-naive}. This approach has its advantages: it's simple and works autonomously, with no interrupt handling or interventions from the program. It could be implemented without the use of \gls{DMA} as well, using a loop periodically updating the \gls{DAC} values; of course, such approach is less flexible and we would run into problems with interrupt handling affecting the timing accuracy.
A straightforward, intuitive implementation of the waveform generator is illustrated in figure \ref{fig:wavegen-naive}. This approach has its advantages: it is simple and works autonomously, with no interrupt handling or interventions from the program. It could be implemented without the use of \gls{DMA} as well, using a loop periodically updating the \gls{DAC} values; of course, such approach is less flexible and we would run into problems with interrupt handling affecting the timing accuracy.
\begin{figure}[h]
\centering
@ -112,7 +112,7 @@ The output frequency is calculated as \(f_\mathrm{out} = \dfrac{M\cdot f_\mathrm
\subsubsection{DDS Implemented in Hardware}
DDS may be implemented in hardware, including the look-up table, often together with the \gls{DAC} itself, which is then called a \textit{Complete \gls{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 \gls{SPI} port.
DDS may be implemented in hardware, including the look-up table, often together with the \gls{DAC} itself, which is then called a \textit{Complete \gls{DDS}}. That is the case of e.g. AD9833 from Analog Devices. As the software implementation depends on a periodic interrupt, it is 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 \gls{SPI} port.
\section{Touch Sensing} \label{sec:theory-touch}

@ -10,7 +10,7 @@ In the recent years, a wide range of intelligent sensors became available thanks
\caption[A collection of intelligent sensors and devices]{A collection of intelligent sensors and devices, most on breadboard adapters: (from top left) a waveform generator, a gesture detector, a LoRa and two Bluetooth modules, an air quality and pressure sensor, a CO$_2$ sensor, a digital compass, an accelerometer, a GPS module, a camera, an ultrasonic range finder, a humidity sensor, a 1-Wire thermometer, a color detector and an RGB LED strip.}
\end{figure}
To conduct experiments with those integrated modules, or just familiarize ourselves with a device before using it in a project, we need an easy way to interact with them. It's also convenient to have direct access to hardware, be it analog signal sampling, generation, or even just logic level inputs and outputs. However, the drive for miniaturization and the advent of \gls{USB} lead to the disappearance of low-level computer ports, such as the printer port (LPT), that would provide an easy way of doing so.
To conduct experiments with those integrated modules, or just familiarize ourselves with a device before using it in a project, we need an easy way to interact with them. It is also convenient to have direct access to hardware, be it analog signal sampling, generation, or even just logic level inputs and outputs. However, the drive for miniaturization and the advent of \gls{USB} lead to the disappearance of low-level computer ports, such as the printer port (LPT), that would provide an easy way of doing so.
Today, when one wants to perform measurements using a digital sensor, the usual route is to implement an embedded firmware for a microcontroller that connects to the \gls{PC} through \gls{USB}, or perhaps shows the results on a display. This approach has its advantages, but is time-consuming and requires knowledge entirely unrelated to the measurements we wish to perform. It would be advantageous to have a way to interface hardware without having to burden ourselves with the technicalities of the connection, even at the cost of lower performance compared to a specialized device or a professional tool.
@ -18,7 +18,7 @@ The design and implementation of such a universal instrument is the object of th
\section{The Project's Expected Outcome}\label{sec:expected-outcome}
It's been a desire of the author for many years to create a universal instrument connecting low-level hardware to a computer, and, with this project, it is finally being realized. Several related projects approaching this problem from different angles can be found on the internet; those will be presented in chapter \ref{sec:prior-art}. This project should not end with yet another tinkering tool that will be produced in a few prototypes and then forgotten. By building an extensible, open-source platform, GEX can become the foundation for future projects which others can expand, re-use and adapt to their specific needs.
It has been a desire of the author for many years to create a universal instrument connecting low-level hardware to a computer, and, with this project, it is finally being realized. Several related projects approaching this problem from different angles can be found on the internet; those will be presented in chapter \ref{sec:prior-art}. This project should not end with yet another tinkering tool that will be produced in a few prototypes and then forgotten. By building an extensible, open-source platform, GEX can become the foundation for future projects which others can expand, re-use and adapt to their specific needs.
\iffalse
\begin{figure}[H]

@ -26,7 +26,7 @@ The structure of a GEX support library is in all cases similar:
\section{Python Library}
The Python GEX library it implements both serial port and raw USB endpoint access, and includes support classes for each unit type. Its development has been proritized over the C library because of it's potential to integrate with MATLAB, and the general ease-of-use that comes with the Python syntax.
The Python GEX library it implements both serial port and raw USB endpoint access, and includes support classes for each unit type. Its development has been proritized over the C library because of its potential to integrate with MATLAB, and the general ease-of-use that comes with the Python syntax.
The library is composed of a \textit{transport}, the core class called \textit{client}, and unit classes. Three transport implementations have been developed; the gateway is accessed by wrapping either of the transports in an instance of \mono{DongleAdapter}.
@ -82,7 +82,7 @@ The Python library can be accessed from MATLAB scripts thanks to the MATLAB's tw
The C library is more simplistic than the Python one; it supports only the serial port transport (\gls{UART} or \gls{CDCACM}) and does not implement asynchronous polling or the unit support drivers. What \textit{is} implement---the transport, a basic protocol handler, and payload building and parsing utilities---is sufficient for most applications, though less convenient than the Python library.
This low level library is intended for applications where the performance of the Python implementation is insufficient, or where an integration with existing C code is required. The full \gls{API} can be found in the library header files. A C version of the example Python script controlling a \gls{LED} matrix driver follows:
This low-level library is intended for applications where the performance of the Python implementation is insufficient, or where an integration with existing C code is required. The full \gls{API} can be found in the library header files. A C version of the example Python script controlling a \gls{LED} matrix driver follows:
\todo[inline]{add the example}

@ -16,19 +16,19 @@ Moving to industrial and automotive environments, we can encounter various field
\subsection{Analog Signal Acquisition}
Sometimes it's necessary to use a traditional analog sensor, capture a transient waveform or to just measure a voltage. GEX was meant to focus on digital interfaces, however giving it this capability makes it much more versatile. Nearly all microcontrollers include an \gls{ADC} which we can use to measure input voltages and, paired with a timer, to records signals varying in time.
Sometimes it is necessary to use a traditional analog sensor, capture a transient waveform or to just measure a voltage. GEX was meant to focus on digital interfaces, however giving it this capability makes it much more versatile. Nearly all microcontrollers include an \gls{ADC} which we can use to measure input voltages and, paired with a timer, to records signals varying in time.
Certain tasks, such as capturing transient effects on a thermocouple when inserted into a flame (an example from developing fire-proof materials) demand level triggering similar to that of oscilloscopes. The converter continuously measures the input voltage and a timed capture starts only after a set threshold is exceeded. This can be accompanied by a pre-trigger feature where the timed capture is continuously running and the last sample is always compared with the threshold, recording a portion of the historic records together with the following samples.
\subsection{Analog Signal Output}
An analog signal can not only be measured, but it's often necessary to also generate it. This could serve as an excitation signal for an experiment, for instance to measure the characteristic curves of a diode or a transistor. Conveniently, we can at the same time use GEX's analog input to record the output.
An analog signal can not only be measured, but it is often necessary to also generate it. This could serve as an excitation signal for an experiment, for instance to measure the characteristic curves of a diode or a transistor. Conveniently, we can at the same time use GEX's analog input to record the output.
Generating an analog signal is possible using a \gls{PWM} or by a dedicated digital-analog converter included in many microcontrollers. Higher frequencies or resolution can be achieved with a dedicated external \gls{IC}.
\subsection{Logic Level Input and Output}
We've covered some more advanced features, but skipped the simplest feature: a direct access to \gls{GPIO} pins. Considering the latencies of \gls{USB} and the \gls{PC}'s operating system, this can't be reliably used for "bit banging", however we can still accomplish a lot with just changing logic levels - e.g. to control character \glspl{LCD}, or emulate some interfaces that include a clock line, like \gls{SPI}. As mentioned in \ref{sec:uses-digital-ifaces}, many digital sensors and modules use plain \glspl{GPIO} in addition to the communication bus for out-of-band signaling or features like chip selection or reset.
We've covered some more advanced features, but skipped the simplest feature: a direct access to \gls{GPIO} pins. Considering the latencies of \gls{USB} and the \gls{PC}'s operating system, this cannot be used reliably for ``bit banging'', however we can still accomplish a lot with just changing logic levels - e.g. to control character \glspl{LCD}, or emulate some interfaces that include a clock line, like \gls{SPI}. As mentioned in \ref{sec:uses-digital-ifaces}, many digital sensors and modules use plain \glspl{GPIO} in addition to the communication bus for out-of-band signaling or features like chip selection or reset.
\subsection{Pulse Generation and Measurement}
@ -83,9 +83,9 @@ Let's now summarize the features we wish to support in the GEX firmware, based o
\section{Microcontroller Selection}
As discussed in section \ref{sec:expected-outcome}, this project will be based on microcontrollers from the STM32 family. The STM32F072 model was selected for the initial hardware and firmware design due to it's low cost, advanced peripherals and the availability of development boards. The firmware can be ported to other \glspl{MCU} later (e.g. to STM32L072, STM32F103 or STM32F303).
As discussed in section \ref{sec:expected-outcome}, this project will be based on microcontrollers from the STM32 family. The STM32F072 model was selected for the initial hardware and firmware design due to its low cost, advanced peripherals, and the availability of development boards. The firmware can be ported to other \glspl{MCU} later (e.g. to STM32L072, STM32F103 or STM32F303).
The STM32F072 is a Cortex M0 device with 128\,KiB of flash memory, 16\,KiB of \gls{RAM} and running at 48\,MHz. It is equipped with a \gls{USB} Full Speed peripheral block, a 12-bit \gls{ADC} and \gls{DAC}, a number of general purpose timers/counters, SPI, I$^2$C, and USART peripherals, among others. It supports crystal-less \gls{USB}, using the USB SOF packet for synchronization of the internal 48\,MHz RC oscillator; naturally, a real crystal resonator will provide better timing accuracy.
The STM32F072 is a Cortex M0 device with 128\,KiB of flash memory, 16\,KiB of \gls{RAM} and running at 48\,MHz. It is equipped with a \gls{USB} Full Speed peripheral block, a 12-bit \gls{ADC} and \gls{DAC}, a number of general-purpose timers/counters, SPI, I$^2$C, and USART peripherals, among others. It supports crystal-less \gls{USB}, using the USB SOF packet for synchronization of the internal 48\,MHz RC oscillator; naturally, a real crystal resonator will provide better timing accuracy.
To effectively utilize the time available for this work, only the STM32F072 firmware will be developed while making sure the planned expansion is as straightforward as possible.
@ -104,7 +104,7 @@ Three possible form factors are drawn in figure \ref{fig:ff-sketches}. The use o
\begin{figure}[h]
\centering
\includegraphics[width=\textwidth] {img/gex-ff-sketches.png}
\caption[Form factor sketches]{\label{fig:ff-sketches}A sketch of three possible form factors for the GEX hardware prototype. Note the ESP8266 module which was considered as an option for wireless access but was eventually not used due to it's high current usage, unsuitable for battery operation.}
\caption[Form factor sketches]{\label{fig:ff-sketches}A sketch of three possible form factors for a GEX hardware realization}
\end{figure}

@ -144,7 +144,7 @@ The 0x04 (Bulk Read Poll) payload specifies how many bytes we want to read:
\subsection{Bulk Write}
To overwrite an INI file, we first send a frame 0x22 (INI Write) with the file size as its payload. The name of the file is irrelevant, as it's detected automatically by inspecting the content.
To overwrite an INI file, we first send a frame 0x22 (INI Write) with the file size as its payload. The name of the file is irrelevant, as it is detected automatically by inspecting the content.
\begin{boxedpayload}[INI Write frame structure]
\cfield{u32} size of the written file, in bytes

@ -58,7 +58,7 @@ The descriptor table used by GEX is captured in figure \ref{fig:gex-descriptors}
\section{USB Physical Layer}
\gls{USB} uses differential signaling with \gls{NRZI} encoding and bit stuffing (the insertion of dummy bits to prevent long intervals in the same \gls{DC} level). The encoding, together with frame formatting, checksum verification, retransmission, and other low level aspects of the \gls{USB} connection are entirely handled by the \gls{USB} physical interface block in the microcontroller's silicon. Normally we do not need to worry about those details; nonetheless, a curious reader may find more information in chapters 7 and 8 of \cite{usbif-spec}. What needs our attention are the electrical characteristics of the physical connection, which need to be understood correctly for a successful schematic and \gls{PCB} design.
\gls{USB} uses differential signaling with \gls{NRZI} encoding and bit stuffing (the insertion of dummy bits to prevent long intervals in the same \gls{DC} level). The encoding, together with frame formatting, checksum verification, retransmission, and other low-level aspects of the \gls{USB} connection are entirely handled by the \gls{USB} physical interface block in the microcontroller's silicon. Normally we do not need to worry about those details; nonetheless, a curious reader may find more information in chapters 7 and 8 of \cite{usbif-spec}. What needs our attention are the electrical characteristics of the physical connection, which need to be understood correctly for a successful schematic and \gls{PCB} design.
The \gls{USB} cable contains 4 conductors: V$_\mathrm{BUS}$ (+5\,V), D+, D--, and \gls{GND}. The data lines, D+ and D--, are also commonly labeled DP and DM. This differential pair should be routed in parallel on the \gls{PCB} and kept at the same length.
@ -87,7 +87,7 @@ The \gls{MSC} is supported by all modern operating systems (MS Windows, MacOS, G
%http://www.usb.org/developers/docs/devclass_docs/Mass_Storage_Specification_Overview_v1.4_2-19-2010.pdf
%http://www.usb.org/developers/docs/devclass_docs/usbmassbulk_10.pdf
The \gls{MSC} specification \cite{usbif-msco} defines multiple \textit{transport protocols} that can be selected using the descriptors. For it's simplicity, the \gls{BOT} \cite{usbif-bot} will be used. \gls{BOT} uses two bulk endpoints for reading and writing blocks of data and for the exchange of control commands and status messages.
The \gls{MSC} specification \cite{usbif-msco} defines multiple \textit{transport protocols} that can be selected using the descriptors. The \gls{BOT} \cite{usbif-bot} will be used for its simplicity. \gls{BOT} uses two bulk endpoints for reading and writing blocks of data and for the exchange of control commands and status messages.
For the mass storage device to be recognized by the host operating system, it must also implement a \textit{command set}. Most mass storage devices use the \textit{\gls{SCSI} Transparent command set}
\footnote{To confirm this assertion, the descriptors of five thumb drives and an external hard disk were analyzed using \verb|lsusb|. All but one device used the SCSI command set, one (the oldest thumb drive) used \textit{SFF-8070i}. A list of possible command sets can be found in \cite{usbif-msco}}.
@ -110,7 +110,7 @@ An interesting property of the \gls{CDC} class is that the bulk endpoints transp
\subsection{Interface Association: Composite Class}
Since it's creation, the \gls{USB} specification expected that each function will have only one interface enabled at a time. After it became apparent that there is a need for having multiple unrelated interfaces work in parallel, the \gls{IAD} \cite{usbif-iad} was introduced as a workaround.
The original \gls{USB} specification expected that each function will have only one interface enabled at a time. After it became apparent that there is a need to have multiple unrelated interfaces working in parallel, the \gls{IAD} \cite{usbif-iad} was introduced as a workaround.
The \gls{IAD} is an entry in the descriptor table that defines which interfaces belong together and should be handled by the same software driver. To use the \gls{IAD}, the function's class must be set to 239 (0xEF), subclass 2 and protocol 1 in the top level descriptor, so that the \gls{OS} knows to look for this descriptor before binding drivers to any interfaces.

@ -40,7 +40,7 @@ LoRa is a patented proprietary modulation developed by Semtech. It uses a direct
\section{Comparing SX1276 and nRF24L01+}
The two transceivers are compared in table \ref{fig:nrf-sx-comparison}. It's apparent that each has its strengths and weaknesses, discussed below.
The two transceivers are compared in table \ref{fig:nrf-sx-comparison}. It is apparent that each of them has its strengths and weaknesses, which will be discussed below.
\begin{table}[h]
\centering
@ -77,7 +77,7 @@ Both devices implement some form of a packet engine with error checking; that of
The nRF24L01+ was selected to be integrated into GEX thanks to its inclusion of the ShockBurst engine, higher possible data rates and significantly lower price. The SX1276, nonetheless, remains an interesting option that could be used as an alternative in the future, should the need for a long range communication arise.
A separate device, the \textit{GEX wireless gateway}, was developed to provide the PC connection to a nRF24L01+ module. It is based on the STM32F103 microcontroller in its smallest package (LQFP48), selected for it's low cost and good availability.
A separate device, the \textit{GEX wireless gateway}, was developed to provide the PC connection to a nRF24L01+ module. It is based on the STM32F103 microcontroller in its smallest package (LQFP48), selected for its low cost and good availability.
\todo[inline]{more about the hardware}

@ -0,0 +1,584 @@
#!/usr/bin/env python
import subprocess
import sys
import re
import inspect
import datetime
import string
from collections import defaultdict, Counter
import nltk
try:
nltk.pos_tag('Just trying to see if the NLTK dataset is installed')
except LookupError:
nltk.download('maxent_treebank_pos_tagger')
try:
nltk.word_tokenize('test')
except LookupError:
nltk.download('punkt')
try:
from clint.textui import colored
except:
class Passthrough(object):
def __getattr__(self, name):
return lambda x: x
colored = Passthrough()
print "=== For colored output, install clint (via 'sudo pip install clint') ==="
PREPOSITIONS = ["a", "abaft", "aboard", "about", "above", "absent",
"across", "afore", "after", "against", "along", "alongside", "amid",
"amidst", "among", "amongst", "an", "apropos", "around", "as", "aside",
"astride", "at", "athwart", "atop", "barring", "before", "behind", "below",
"beneath", "beside", "besides", "between", "betwixt", "beyond", "but", "by",
"circa", "concerning", "despite", "down", "during", "except", "excluding",
"failing", "following", "for", "from", "given", "in", "including", "inside",
"into", "lest", "like", "mid", "midst", "minus", "modulo", "near", "next",
"notwithstanding", "of", "off", "on", "onto", "opposite", "out", "outside",
"over", "pace", "past", "per", "plus", "pro", "qua", "regarding", "round",
"sans", "save", "since", "than", "through,", "throughout,", "till", "times",
"to", "toward", "towards", "under", "underneath", "unlike", "until", "unto",
"up", "upon", "versus", "via", "vice", "with", "within", "without",
"worth", "through"]
# Obtained with (avoiding the dependency):
# from nltk.corpus import stopwords
# stopwords.words("english")
STOPWORDS = ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves',
'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his',
'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself', 'they',
'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom',
'this', 'that', 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be',
'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing',
'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while',
'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into',
'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up',
'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then',
'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both',
'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not',
'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will',
'just', 'don', 'should', 'now']
CONJUNCTIONS = ["and", "but", "or", "yet", "for", "nor", "so"]
class Paper(object):
##########################################################################
# INTERNAL STUFF (you can ignore these functions)
##########################################################################
def __init__(self, filenames):
self.__filenames = filenames
self.errors = 0
self.__clear_caches()
def __clear_caches(self):
self.__text = {}
self.__latex_text = None
@staticmethod
def __flatten_paragraphs(text):
'''Given a text where paragraphs are separated by one or more
empty lines, it puts every paragraph in a single, separate line.
Example:
I like sushi
and pie.
I ride
horses.
Becomes:
I like sushi and pie.
I ride horses '''
return '\n'.join(paragraph.replace('\n', ' ')
for paragraph in re.split("\n(\s*\n)+", text)
if paragraph.strip())
def __run_all_with_prefix(self, prefix):
# Switch filename and clear caches
for filename in self.__filenames:
self.filename = filename
self.__clear_caches()
# Call all functions
for name in filter(lambda n: n.startswith(prefix), dir(self)):
attribute = getattr(self, name)
if inspect.ismethod(attribute):
attribute()
def _run_all_checks(self):
self.__run_all_with_prefix('check_')
def _run_all_tests(self):
self.__filenames = ["TEST"]
self.__run_all_with_prefix('test_')
def _format_re_match(self, m, text):
start_of_sentence = max(text.rfind('\n', 0, m.start()) + 1, 0)
end_of_sentence = text.find('\n', m.end()), len(text)
if end_of_sentence == -1:
end_of_sentence = len(text)
a_string_start = max(start_of_sentence, m.start() - 10)
a_string_end = min(end_of_sentence, m.end() + 10)
a_string = text[a_string_start : m.start()]
a_string += colored.yellow(text[m.start() : m.end()])
a_string += text[m.end() : a_string_end]
to_return = a_string.split('\n', 1)[0]
return to_return.replace('\r',' ').replace('\n',' ')
##########################################################################
# FUCTIONS THAT ARE RELEVANT FOR CHECKS WRITERS
##########################################################################
def get_latex_text(self):
''' Returns the complete paper, with each paragraph on a single
line. No latex/tex command is stripped '''
if self.__latex_text != None:
return self.__latex_text
else:
with open(self.filename) as f:
text = f.read()
self.__latex_text = self.__flatten_paragraphs(text)
return self.__latex_text
_IGNORED_ENVIRONMENTS = ("array",
"eqnarray",
"equation",
"figure",
"mathmatica",
"picture",
"table",
"verbatim",
"lstlisting")
def get_text(self, ignored_environments=None):
''' Returns the textual content of the tex files, with latex/tex
enviroments stripped. You can control the enviroments to strip via
the 'ignored_environments' argument: if you don't, the default ones
will be stripped'''
if ignored_environments == None:
ignored_environments = Paper._IGNORED_ENVIRONMENTS
try:
return self.__text[ignored_environments]
except:
# Cleanup annoying things
text = self.get_latex_text()
text = re.sub(r'\\cite{[^}]*}', '', text)
text = re.sub(r'\\-', '', text)
# Run it through detex
p = subprocess.Popen(["detex",
"-l",
"-n",
"-e",
','.join(ignored_environments)],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
p.stdin.write(text)
text = p.communicate()[0]
p.stdin.close()
self.__text[ignored_environments] = self.__flatten_paragraphs(
text)
return self.__text[ignored_environments]
def perform_test(self, function, expected_errors_num):
self.errors = 0
function()
assert expected_errors_num == self.errors
def print_issue(self, message, match=None, text=None):
if text == None:
text = self.get_text()
message = colored.red(message)
if match and text:
example = self._format_re_match(match, text)
else:
example = ''
print "%30s - %s: %s" % (colored.green(self.filename),
colored.red(message),
example)
##########################################################################
# CHECKS
##########################################################################
def check_exempli_gratia_without_comma(self):
for m in re.finditer(r'e\.g\.[^,]',
self.get_text(),
re.MULTILINE):
self.print_issue("E.G. without comma", m)
self.errors += 1
def test__check_exempli_gratia_without_comma(self):
self.get_text = lambda: "e.g. a pony \n e.g. what?, e.g., cool!"
self.perform_test(self.check_exempli_gratia_without_comma, 2)
##########################################################################
def check_id_est_without_comma(self):
for m in re.finditer(r'i\.e\.[^,]',
self.get_text(),
re.MULTILINE):
self.print_issue("I.E. without comma", m)
self.errors += 1
def test__check_id_est_without_comma(self):
self.get_text = lambda: "i.e. a pony \n i.e. what?, i.e., cool!"
self.perform_test(self.check_id_est_without_comma, 2)
##########################################################################
def check_quotes(self):
for m in re.finditer(r'"',
self.get_text(),
re.MULTILINE):
self.print_issue('"hello" should be ``hello\'\'', m)
self.errors += 1
def test__check_quotes(self):
self.get_text = lambda: '"this is not ok" ``but this is\'\''
self.perform_test(self.check_quotes, 2)
##########################################################################
def check_citing_with_unbreakeable_spaces(self):
for keyword in ["cite", "ref"]:
for m in re.finditer(r'(\W?)(^|\s)+\\' + keyword + r'\s*{',
self.get_latex_text(),
re.MULTILINE):
if m.group(1) in [',', '&']:
continue
self.print_issue('use hello~\%s{ instead of hello \%s{' % (keyword,
keyword),
m, self.get_latex_text())
self.errors += 1
def test__check_citing_with_unbreakeable_spaces(self):
self.get_latex_text = lambda: r'citing: wrong \cite{ciao} - right~\cite{ciao}'
self.perform_test(self.check_citing_with_unbreakeable_spaces, 1)
self.get_latex_text = lambda: r'refs done wrong \ref{ciao} - right~\ref{ciao}'
self.perform_test(self.check_citing_with_unbreakeable_spaces, 1)
self.get_latex_text = lambda: r'hello& \cite{ciao}'
self.perform_test(self.check_citing_with_unbreakeable_spaces, 0)
self.get_latex_text = lambda: r', \ref{ciao}'
self.perform_test(self.check_citing_with_unbreakeable_spaces, 0)
##########################################################################
def check_variations_of_word_spellings(self):
words = defaultdict(Counter)
for word in re.findall(r'\b\S+\b', self.get_text(), re.MULTILINE):
word_alphanum = re.sub("[^a-zA-Z0-9_']+", '', word).lower()
words[word_alphanum].update([word])
for _, spellings_counter in words.iteritems():
variations = len(spellings_counter.keys())
total_appereances = sum(spellings_counter.values())
if variations > 1:
if len(set(w[1:] for w in spellings_counter.keys())) == 1:
# FIXME: for now, if it's just a case mismatch on the first
# letter, skip
continue
normalized_word = spellings_counter.keys()[0].lower()
if normalized_word in STOPWORDS + PREPOSITIONS + CONJUNCTIONS:
# Ignore common words
continue
# Ignore numbers
try:
float(normalized_word)
continue
except ValueError:
pass
self.print_issue('This word has multiple spellings: %s' % (
dict(spellings_counter)), None)
self.errors += 1
def test__check_variations_of_word_spellings(self):
self.get_text = lambda: (r'I has a cybercriminal. I had a cyber-criminal. '
r'or was it a CyberCriminal?')
self.perform_test(self.check_variations_of_word_spellings, 1)
self.get_text = lambda: (r'no strange words here, however put. '
r'However, is that true?')
self.perform_test(self.check_variations_of_word_spellings, 0)
self.get_text = lambda: (r'It matters the it factor ')
self.perform_test(self.check_variations_of_word_spellings, 0)
self.get_text = lambda: (r'1.6 16 and other fancy numbers.')
self.perform_test(self.check_variations_of_word_spellings, 0)
##########################################################################
def check_commas_in_numbers(self):
# We also check in tables
text = self.get_text(ignored_environments=tuple(
set(Paper._IGNORED_ENVIRONMENTS) - set('table')))
for m in re.finditer('(^|[^\w\-])\d{4}', text, re.MULTILINE):
if text[m.start():m.start() + 1] in string.punctuation:
continue
try:
number = int(text[m.start():m.end()])
except:
number = 0
if number not in range(1990, datetime.date.today().year + 2):
self.errors += 1
self.print_issue('Put commas in numbers over 1,000', m)
# This is the correct rule, but nobody follows it
# for m in re.finditer('(^|[^\w\-])\d{5}', text, re.MULTILINE):
# self.print_issue('Put commas in numbers over 10,000', m)
# self.errors += 1
# for m in re.finditer('[^\d]\d,\d{3}[^,]', text, re.MULTILINE):
# self.print_issue("Don't put commas in numbers under 10,000", m)
# self.errors += 1
def test__check_commas_in_numbers(self):
def get_text(*args, **kwargs): return text
self.get_text = get_text
text = r'10000 cats eat 10,000 mice'
self.perform_test(self.check_commas_in_numbers, 1)
text = r'9999 cats eat 9,999 mice'
self.perform_test(self.check_commas_in_numbers, 1)
text = r'1000 cats eat 999,999 mice'
self.perform_test(self.check_commas_in_numbers, 1)
text = r'project N10000, grant CNS-20000'
self.perform_test(self.check_commas_in_numbers, 0)
text = r'In 2001, we ate spaghetti'
self.perform_test(self.check_commas_in_numbers, 0)
##########################################################################
def check_commas_after_quotes(self):
for m in re.finditer("''\s*,",
self.get_text(),
re.MULTILINE):
self.print_issue("Convert ``hello'', => ``hello,''", m)
self.errors += 1
def test__check_commas_after_quotes(self):
self.get_text = lambda: r"``flower'', should be ``flower,''"
self.perform_test(self.check_commas_after_quotes, 1)
##########################################################################
def check_always_capitalize(self):
for reg in ["internet", "javascript"]:
for m in re.finditer(r"\b{0}".format(reg),
self.get_text(),
re.MULTILINE):
self.print_issue("Always capitalize", m)
self.errors += 1
def test__check_always_capitalize(self):
self.get_text = lambda: r"internet"
self.perform_test(self.check_always_capitalize, 1)
self.get_text = lambda: r"testinternet"
self.perform_test(self.check_always_capitalize, 0)
##########################################################################
#
# def check_comma_before_that(self):
# for m in re.finditer(",\s+that",
# self.get_text(),
# re.MULTILINE):
# phrase_start = max([
# self.get_text().rfind(c, 0, m.start())
# for c in ['\n', '.', ':', ';']
# ] + [0])
# phrase = self.get_text()[phrase_start:m.start() + 1]
# if len([c for c in phrase if c == ',']) % 2 == 0:
# # An even number of commas found, skipping
# continue
# self.print_issue("Do not put a comma before 'that'", m)
# self.errors += 1
#
# def test__check_comma_before_that(self):
# self.get_text = lambda: r"I like cats, that eat mice"
# self.perform_test(self.check_comma_before_that, 1)
# self.get_text = lambda: r"I like cats that eat mice"
# self.perform_test(self.check_comma_before_that, 0)
#
# ##########################################################################
#
# def check_comma_before_which(self):
# for m in re.finditer("[^,'*\s]\s+which",
# self.get_text(),
# re.MULTILINE):
# word_before_start = self.get_text().rfind(' ', 0, m.start())
# word_before = re.search("\w+", self.get_text()[
# word_before_start + 1:
# m.start() + 1]).group()
# if word_before in PREPOSITIONS + CONJUNCTIONS:
# continue
# if word_before.endswith('ing') or word_before.endswith('ly'):
# continue
# # More expensive analysis: is the word before a verb?
# phrase_start = max([
# self.get_text().rfind(c, 0, m.start())
# for c in ['\n', '.', ':', ';']
# ] + [0])
# phrase = self.get_text()[phrase_start:m.start() + 1]
# word_before_kind = filter(lambda x: x[0] == word_before, nltk.pos_tag(
# nltk.word_tokenize(phrase)))[0][1]
# if word_before_kind.startswith('VB'):
# continue
# self.print_issue("Put a comma before 'which'", m)
# self.errors += 1
#
# def test__check_comma_before_which(self):
# self.get_text = lambda: r"I like that cat, which eat mice"
# self.perform_test(self.check_comma_before_which, 0)
# self.get_text = lambda: r"I like that cat which eat mice"
# self.perform_test(self.check_comma_before_which, 1)
# self.get_text = lambda: r"I know which cat eat mice"
# self.perform_test(self.check_comma_before_which, 0)
##########################################################################
def check_word_before_ref_is_capitalized(self):
text = self.get_latex_text()
for m in re.finditer('\\\\ref', text, re.MULTILINE):
word_before_start = max(text.rfind(' ', 0, m.start() - 2),
text.rfind('~', 0, m.start() - 2))
word_before = re.findall("\w+", text[word_before_start + 1:
m.start() + 1])[-1]
if not word_before in ["and"] and not word_before[0].isupper():
self.print_issue(r'Capitalize the word before \ref', m, text)
self.errors += 1
def test__check_word_before_ref_is_capitalized(self):
self.get_latex_text = lambda: "in Section \\ref{sec}, see Figure \\ref{fig}"
self.perform_test(self.check_word_before_ref_is_capitalized, 0)
self.get_latex_text = lambda: "in section \\ref{sec}, see figure \\ref{fig}"
self.perform_test(self.check_word_before_ref_is_capitalized, 2)
self.get_latex_text = lambda: "section \\ref{sec}"
self.perform_test(self.check_word_before_ref_is_capitalized, 1)
##########################################################################
def check_british_spelling(self):
british_spellings = {"acknowledgement": "acknowledgment", "afterwards": "afterward", "arse": "ass", "backwards": "backward",
"cancelling": "canceling", "catalogue": "catalog", "centre": "center", "cheque": "check", "colour": "color", "dialogue": "dialog",
"favour": "favor", "flavour": "flavor", "forwards": "forward", "grey": "gray", "judgement": "judgment", "labour": "labor",
"lustre": "luster", "modelled": "modeled", "revelled": "raveled", "shovelled": "shoveled", "snivelled": "sniveled",
"theatre": "theater", "towards": "toward", "travelling": "traveling", "yodelling": "yodeling"}
for british, american in british_spellings.iteritems():
for m in re.finditer("[^\w]+" + british + "[^\w]+", self.get_text()):
self.print_issue("Don't spell like a bugger (that's british english) -" \
" it's ' "+ american + " ' , not", m)
self.errors += 1
def test__check_british_spelling(self):
self.get_text = lambda: r"Go to the (centre) of town to pick up the best flavour colour."
self.perform_test(self.check_british_spelling, 3)
self.get_text = lambda: r"I am an American, therefore I am"
self.perform_test(self.check_british_spelling, 0)
self.get_text = lambda: r"This cheque, right here, is unacceptable. I'll have to cheque with my manager."
self.perform_test(self.check_british_spelling, 2)
self.get_text = lambda: r"It is hard to parse this sentence."
self.perform_test(self.check_british_spelling, 0)
##########################################################################
def check_slang_and_gergal_terms(self):
gergal = ['basically']
for w in gergal:
for m in re.finditer(w, self.get_text(),
re.IGNORECASE):
self.print_issue(
"This word doesn't sound like it should be in a paper: "
+ w, m)
self.errors += 1
def test__check_slang_and_gergal_terms(self):
self.get_text = lambda: r"Basically, this is wat we do"
self.perform_test(self.check_slang_and_gergal_terms, 1)
##########################################################################
def check_misplelled_standard_phrases(self):
mispellings = {"in more details": "in more detail"}
for wrong, right in mispellings.iteritems():
for m in re.finditer("[^\w]+" + wrong + "[^\w]+", self.get_text()):
self.print_issue("Mispelled standard phrase - ' %s ' "
"should be ' %s' in" % (wrong, right), m)
self.errors += 1
def test__check_misplelled_standard_phrases(self):
self.get_text = lambda: r"I'll discuss this in more details in section"
self.perform_test(self.check_misplelled_standard_phrases, 1)
##########################################################################
def check_banned_words(self):
banned_words = ["is[\s]+comprised[\s]+of",
"doesn't",
"beside",
"won't",
"can't"]
for banned_word in banned_words:
for m in re.finditer("([^\w]+|^)" + banned_word + "[^\w]+",
self.get_text(), flags=re.IGNORECASE):
self.print_issue("Don't use %s" % banned_word, m)
self.errors += 1
def test__check_banned_words(self):
self.get_text = lambda: r"Adam is comprised of a brain and a stomach."
self.perform_test(self.check_banned_words, 1)
self.get_text = lambda: r"Adam comprises a brain and a stomach."
self.perform_test(self.check_banned_words, 0)
self.get_text = lambda: r"You don't know what that is. Comprised. Of."
self.perform_test(self.check_banned_words, 0)
self.get_text = lambda: r"Is comprised of blah and bloop."
self.perform_test(self.check_banned_words, 1)
self.get_text = lambda: r"Adam is awesome. Is comprised of blah and bloop."
self.perform_test(self.check_banned_words, 1)
self.get_text = lambda: r"Don't do this. I won't, tell anybody."
self.perform_test(self.check_banned_words, 1)
##########################################################################
def check_repeated_words(self):
for m in re.finditer(r"\b(\w+)\W+\1\b",
self.get_text(), flags=re.IGNORECASE):
if m.group(1).isdigit():
continue
self.print_issue("Repeated word '%s'" % m.group(1), m)
self.errors += 1
def test__check_repeated_words(self):
self.get_text = lambda: r"We use this this and that."
self.perform_test(self.check_repeated_words, 1)
self.get_text = lambda: r"We use this and that, and this and that too."
self.perform_test(self.check_repeated_words, 0)
self.get_text = lambda: r"This. This is a sentence Sentence."
self.perform_test(self.check_repeated_words, 2)
self.get_text = lambda: r"Version 4.4."
self.perform_test(self.check_repeated_words, 0)
if __name__ == '__main__':
if len(sys.argv) < 2:
print """Usage:
- chrisper *.tex
- chrisper test
Runs the test suite.
"""
sys.exit(0)
if sys.argv[1] == "test":
Paper(sys.argv[2:])._run_all_tests()
print colored.green("\n=== ALL TESTS PASSED ===")
else:
paper = Paper(sys.argv[1:])
paper._run_all_checks()
if paper.errors == 0:
print colored.green('=== IT LOOKS GOOD TO ME. CONGRATS! ===')
else:
print colored.yellow("\n=== I'VE FOUND %d ISSUES ===" %
paper.errors)
sys.exit(1)

@ -0,0 +1,30 @@
#/bin/bash
if [[ "$1" == "" ]]; then
echo "missing arg"
exit
fi
echo "Processing $1"
sed -i "s/\blow cost\b/low-cost/g" "$1"
sed -i "s/\bgeneral purpose\b/general-purpose/g" "$1"
sed -i "s/\bhigh level\b/high-level/g" "$1"
sed -i "s/\blow level\b/low-level/g" "$1"
sed -i "s/\bdon't\b/do not/g" "$1"
sed -i "s/\bDon't\b/Do not/g" "$1"
sed -i "s/\bit's\b/it is/g" "$1"
sed -i "s/\bIt's\b/It is/g" "$1"
sed -i "s/\bisn't\b/is not/g" "$1"
sed -i "s/\baren't\b/are not/g" "$1"
sed -i "s/\bAren't\b/Are not/g" "$1"
sed -i "s/\bwouldn't\b/would not/g" "$1"
sed -i "s/\bWouldn't\b/Would not/g" "$1"
sed -i "s/\bcouldn't\b/could not/g" "$1"
sed -i "s/\bCouldn't\b/Could not/g" "$1"
sed -i "s/\bshouldn't\b/should not/g" "$1"
sed -i "s/\bShouldn't\b/Should not/g" "$1"
sed -i "s/\bcan't\b/cannot/g" "$1"
sed -i "s/\bin recent years\b/recently/g" "$1"
sed -i "s/\bIn recent years\b/Recently/g" "$1"
git diff --word-diff "$1"

Binary file not shown.
Loading…
Cancel
Save