diff --git a/Documentation/Diagrams/UARTFrameFormat-1.png b/Documentation/Diagrams/UARTFrameFormat-1.png index baae96e..32c196b 100644 Binary files a/Documentation/Diagrams/UARTFrameFormat-1.png and b/Documentation/Diagrams/UARTFrameFormat-1.png differ diff --git a/Documentation/Diagrams/UARTFrameFormat.svg b/Documentation/Diagrams/UARTFrameFormat.svg index ef06659..e0483dd 100644 --- a/Documentation/Diagrams/UARTFrameFormat.svg +++ b/Documentation/Diagrams/UARTFrameFormat.svg @@ -1,5 +1,5 @@ - + @@ -15,95 +15,101 @@ - + - + - + - + - + - - - - - - - - - - + - - - - + - + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - - - + + + - - + + + + + + + + @@ -112,123 +118,144 @@ - + - + - + - - - + + + - - - - - - - - - - - - + + + + + + + + + - - - + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - + + + - - - + + + - + - - + + - - + + + - - - - - - - - - + + + - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - - + + + + + + + + diff --git a/Documentation/Diagrams/UARTFrameFormat.tex b/Documentation/Diagrams/UARTFrameFormat.tex index cc8020c..c7c9164 100644 --- a/Documentation/Diagrams/UARTFrameFormat.tex +++ b/Documentation/Diagrams/UARTFrameFormat.tex @@ -25,20 +25,21 @@ long/.style={short,text width=60pt} \node (StartPoint) [text depth=13pt, text height=6pt]{}; -\shnode{StartPoint}{MsgId}{msg id}; -\shnode{MsgId}{MsgLen}{msg length}; -\lnode{MsgLen}{Configuration}{ADC configuration}; +\shnode{StartPoint}{MsgType}{msg type}; +\shnode{MsgType}{MsgLen}{msg length}; +\lnode{MsgLen}{MsgCounter}{msg \\ counter}; +\lnode{MsgCounter}{Configuration}{ADC configuration}; \lnode{Configuration}{MSBADC}{MSB ADC value}; \lnode{MSBADC}{LSBADC}{LSB ADC value}; \lnode{LSBADC}{CRC}{CRC}; -\draw[decorate,decoration={brace,raise=3pt}] (MsgId.north west) -- node[above=4pt] {Preamble} (MsgLen.north east); +\draw[decorate,decoration={brace,raise=3pt}] (MsgType.north west) -- node[above=4pt] {Preamble} (MsgCounter.north east); \draw[decorate,decoration={brace,raise=3pt}] (Configuration.north west) -- node[above=4pt] {Data} (LSBADC.north east); \draw[decorate,decoration={brace,raise=3pt}] (CRC.north west) -- node[above=4pt] {CRC} (CRC.north east); -\draw (MsgId.south west) -- +(0,-1.2cm); +\draw (MsgType.south west) -- +(0,-1.2cm); \draw (MsgLen.south east) -- +(0,-1.2cm); -\draw[<->] ( $ (MsgId.south west) +(0,-0.6cm) $ ) -- node[fill=white] {1 Byte} ( $ (MsgLen.south east) +(0,-0.6cm) $ ); +\draw[<->] ( $ (MsgType.south west) +(0,-0.6cm) $ ) -- node[fill=white] {1 byte} ( $ (MsgLen.south east) +(0,-0.6cm) $ ); \end{tikzpicture} diff --git a/Simulation/hardware_connection.py b/Simulation/hardware_connection.py deleted file mode 100644 index 831cc2d..0000000 --- a/Simulation/hardware_connection.py +++ /dev/null @@ -1,11 +0,0 @@ - - -class HardwareConnection: - def __init__(self, config): - pass - - def connect(self): - pass - - def getMeasurement(self): - return [1, 2, 3, 4, 5] diff --git a/Simulation/main.py b/Simulation/main.py index 78518df..0e34d44 100644 --- a/Simulation/main.py +++ b/Simulation/main.py @@ -1,13 +1,13 @@ import sys sys.path.append('../Software/DataAcquisition') -from hardware_connection import HardwareConnection # noqa: E402 +from physical_layer import PhysicalLayer # noqa: E402 from state_machine import IonizationChamberStateMachine # noqa: E402 import config # noqa: E402 if __name__ == "__main__": - hardware = HardwareConnection(config) - machine = IonizationChamberStateMachine(hardware) + physicalLayer = PhysicalLayer(config) + machine = IonizationChamberStateMachine(physicalLayer) while True: machine.tick() diff --git a/Simulation/physical_layer.py b/Simulation/physical_layer.py new file mode 100644 index 0000000..d6f8f13 --- /dev/null +++ b/Simulation/physical_layer.py @@ -0,0 +1,11 @@ + + +class PhysicalLayer: + def __init__(self, config): + pass + + def connect(self): + pass + + def getData(self): + return [1, 2, 3, 4, 5, 9] diff --git a/Software/DataAcquisition/README.md b/Software/DataAcquisition/README.md index 8331e43..585c16f 100644 --- a/Software/DataAcquisition/README.md +++ b/Software/DataAcquisition/README.md @@ -1,15 +1,41 @@ -# Firmware +## Setting up development environment on Linux -## Setup +1. Install tools via apt-get: -* [Setting up development environment on Linux -](https://github.com/RobertGawron/IonizationChamber/wiki/Setting-up-development-environment-on-Linux) +```console +apt-get install screen git sdcc doxygen uncrustify libusb-1.0-0-dev python3-pip texlive-latex-base texlive-latex-extra texlive-extra-utils poppler-utils cmake cppcheck r-base-core shellcheck +``` +3. Install python libraries. +```console +pip3 install pyserial +pip3 install flake8 flake8-html +``` -## Architecture +4. Install R libraries, run R in command line: +```console +pi@raspberrypi:~/IonizationChamber/software/DataAcquisitionFromDevice $ R + +R version 3.5.2 (2018-12-20) -- "Eggshell Igloo" +Copyright (C) 2018 The R Foundation for Statistical Computing +Platform: arm-unknown-linux-gnueabihf (32-bit) +``` + +Type (write yes on next prompts): + +``` +install.packages('latticeExtra') +``` + +``` +install.packages('gridExtra') +``` + +``` +install.packages('Hmisc') +``` - ## Collecting measurements @@ -17,9 +43,9 @@ 2. **Run data acquisition script**, it will log Ionization Chamber output on the screen and also it will save it to data.csv for further processing. -```python main.py``` - -3. When all data is logged, terminate ```python main.py``. +``` +python main.py +``` ## Plotting signal value in domain time + plotting histogram @@ -27,7 +53,9 @@ This mode is useful to look on measurement changes over time. After collecting data run script to post-process it and generate diagrams: -```Rscript main.R``` +``` +Rscript main.R +``` A new .png image with timestamp in its name will be created in directory where script is. @@ -42,7 +70,9 @@ Below is example of such generated plot. 2. Edit ```boxplot.R```, to match filenames of .cvs files and labels of measurements. 3. Run: -```Rscript boxplot.R``` +``` +Rscript boxplot.R +``` A new .png image with timestamp in its name will be created in directory where script is. diff --git a/Software/DataAcquisition/application_layer.py b/Software/DataAcquisition/application_layer.py new file mode 100644 index 0000000..13e8a1d --- /dev/null +++ b/Software/DataAcquisition/application_layer.py @@ -0,0 +1,45 @@ +from transport_layer import TransportLayer +from enum import Enum + + +class ADC_RESOLUTION(Enum): + R12 = 1 + R13 = 2 + R14 = 3 + + +def convert(upperByte, lowerByte, resolution): + """ + based on https://ww1.microchip.com/downloads/en/DeviceDoc/22072b.pdf + assumed that gain = 1 + """ + def digitalToAnalog(value, lsb, pga): + return (value * (lsb / pga)) + + digitalOutput = (upperByte << 8) | lowerByte + + if resolution == ADC_RESOLUTION.R12: + return digitalToAnalog(digitalOutput, (1 * 0.01), 1) + + if resolution == ADC_RESOLUTION.R13: + return digitalToAnalog(digitalOutput, (250 * 0.0000001), 1) + + if resolution == ADC_RESOLUTION.R14: + return digitalToAnalog(digitalOutput, (62.5 * 0.000001), 1) + + +class ApplicationLayer: + def __init__(self, physicalLayer): + self.transportLayer = TransportLayer(physicalLayer) + + def connect(self): + self.transportLayer.connect() + + def getMeasurement(self): + dataIn = self.transportLayer.getFrame() + + (msb, lsb) = (dataIn[2], dataIn[3]) + deviceMeasurement = convert( + msb, lsb, ADC_RESOLUTION.R14) + + return deviceMeasurement diff --git a/Software/DataAcquisition/ionization_chamber.py b/Software/DataAcquisition/ionization_chamber.py deleted file mode 100644 index dc97eca..0000000 --- a/Software/DataAcquisition/ionization_chamber.py +++ /dev/null @@ -1,19 +0,0 @@ - -import mcp3425 - - -class IonizationChamber: - def __init__(self, ionizationSensor): - self.ionizationSensor = ionizationSensor - - def connect(self): - self.ionizationSensor.connect() - - def getMeasurement(self): - dataIn = self.ionizationSensor.getMeasurement() - - (msb, lsb) = (dataIn[2], dataIn[3]) - deviceMeasurement = mcp3425.convert( - msb, lsb, mcp3425.MCP3425_RESOLUTION.R14) - - return deviceMeasurement diff --git a/Software/DataAcquisition/main.py b/Software/DataAcquisition/main.py index 93ad836..6e9ea7f 100644 --- a/Software/DataAcquisition/main.py +++ b/Software/DataAcquisition/main.py @@ -1,11 +1,11 @@ -from hardware_connection import HardwareConnection +from physical_layer import PhysicalLayer from state_machine import IonizationChamberStateMachine import config if __name__ == "__main__": - hardware = HardwareConnection(config) - machine = IonizationChamberStateMachine(hardware) + physicalLayer = PhysicalLayer(config) + machine = IonizationChamberStateMachine(physicalLayer) while True: machine.tick() diff --git a/Software/DataAcquisition/mcp3425.py b/Software/DataAcquisition/mcp3425.py deleted file mode 100644 index 5c43c54..0000000 --- a/Software/DataAcquisition/mcp3425.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -based on https://ww1.microchip.com/downloads/en/DeviceDoc/22072b.pdf -assumed that gain = 1 -""" -from enum import Enum - - -class MCP3425_RESOLUTION(Enum): - R12 = 1 - R13 = 2 - R14 = 3 - - -def convert(upperByte, lowerByte, resolution): - def digitalToAnalog(value, lsb, pga): - return (value * (lsb / pga)) - - digitalOutput = (upperByte << 8) | lowerByte - - if resolution == MCP3425_RESOLUTION.R12: - return digitalToAnalog(digitalOutput, (1 * 0.01), 1) - - if resolution == MCP3425_RESOLUTION.R13: - return digitalToAnalog(digitalOutput, (250 * 0.0000001), 1) - - if resolution == MCP3425_RESOLUTION.R14: - return digitalToAnalog(digitalOutput, (62.5 * 0.000001), 1) diff --git a/Software/DataAcquisition/measurement_storage.py b/Software/DataAcquisition/measurement_storage.py new file mode 100644 index 0000000..cb2d4d1 --- /dev/null +++ b/Software/DataAcquisition/measurement_storage.py @@ -0,0 +1,15 @@ +import csv + + +class MeasurementStorage: + def __init__(self): + self.CVS_FileName = "data.csv" + self.CVS_Header = ['Time', 'Counter'] + + def connect(self): + CVS_Handler = open(self.CVS_FileName, 'w', encoding='UTF8', newline='') + self.CVS_Writter = csv.writer(CVS_Handler) + self.CVS_Writter.writerow(self.CVS_Header) + + def saveMeasurement(self, date, measurement): + self.CVS_Writter.writerow([date, measurement]) diff --git a/Software/DataAcquisition/hardware_connection.py b/Software/DataAcquisition/physical_layer.py similarity index 75% rename from Software/DataAcquisition/hardware_connection.py rename to Software/DataAcquisition/physical_layer.py index 32d4cff..e389792 100644 --- a/Software/DataAcquisition/hardware_connection.py +++ b/Software/DataAcquisition/physical_layer.py @@ -1,7 +1,7 @@ from serial import Serial -class HardwareConnection: +class PhysicalLayer: def __init__(self, config): self.config = config @@ -14,6 +14,7 @@ class HardwareConnection: self.serialPort.isOpen() self.serialPort.flushInput() - def getMeasurement(self): - dataIn = self.serialPort.read(5) + def getData(self): + msgLength = 6 + dataIn = self.serialPort.read(msgLength) return dataIn diff --git a/Software/DataAcquisition/state_machine.py b/Software/DataAcquisition/state_machine.py index c01e635..08d8062 100644 --- a/Software/DataAcquisition/state_machine.py +++ b/Software/DataAcquisition/state_machine.py @@ -1,39 +1,40 @@ import datetime -from ionization_chamber import IonizationChamber +from application_layer import ApplicationLayer +from measurement_storage import MeasurementStorage class IonizationChamberStateMachine: - def __init__(self, hardware): - self.hardware = hardware + def __init__(self, physicalLayer): + self.applicationLayer = ApplicationLayer(physicalLayer) self.deviceMeasurement = 0.0 + self.measurementStorage = MeasurementStorage() + self.nextState = self.initIonizationChamber def tick(self): self.nextState() def initIonizationChamber(self): - self.chamber = IonizationChamber(self.hardware) - self.chamber.connect() - self.nextState = self.initOutputFile + self.applicationLayer.connect() + + self.nextState = self.initMeasurementStorage + + def initMeasurementStorage(self): + self.measurementStorage.connect() - def initOutputFile(self): - self.logFile = open('data.csv', 'w') - self.logFile.write("Time,Counter,DMM\n") self.nextState = self.getMeasurementFromIonizationChamber def getMeasurementFromIonizationChamber(self): - self.deviceMeasurement = self.chamber.getMeasurement() + self.deviceMeasurement = self.applicationLayer.getMeasurement() + self.nextState = self.saveMeasurement def saveMeasurement(self): now = datetime.datetime.now() - self.logFile.write("{0},{1}\n".format( - now, self.deviceMeasurement)) - - self.logFile.flush() - + self.measurementStorage.saveMeasurement(now, self.deviceMeasurement) self.nextState = self.showMeasurementToUser def showMeasurementToUser(self): print("{0}".format(self.deviceMeasurement)) + self.nextState = self.getMeasurementFromIonizationChamber diff --git a/Software/DataAcquisition/transport_layer.py b/Software/DataAcquisition/transport_layer.py new file mode 100644 index 0000000..3a770e2 --- /dev/null +++ b/Software/DataAcquisition/transport_layer.py @@ -0,0 +1,23 @@ + + +class InvalidCRC(Exception): + pass + + +class MissingFrame(Exception): + pass + + +class InvalidMsgLength(Exception): + pass + + +class TransportLayer: + def __init__(self, physicalLayer): + self.physicalLayer = physicalLayer + + def connect(self): + self.physicalLayer.connect() + + def getFrame(self): + return self.physicalLayer.getData() diff --git a/Software/Firmware/README.md b/Software/Firmware/README.md index 1042de0..0198b90 100644 --- a/Software/Firmware/README.md +++ b/Software/Firmware/README.md @@ -1,9 +1,60 @@ -# Firmware +# Setting up development environment on Linux -* ["Data processing and firmware flashing" node architecture -](https://github.com/RobertGawron/IonizationChamber/wiki/%22Data-processing-and-firmware-flashing%22-node-architecture -) -* [Setting up development environment on Linux -](https://github.com/RobertGawron/IonizationChamber/wiki/Setting-up-development-environment-on-Linux) -* [Firmware compilation and hardware flashing -](https://github.com/RobertGawron/IonizationChamber/wiki/Firmware-compilation-and-hardware-flashing) \ No newline at end of file +1. Install tools via apt-get: + +```console +apt-get install screen git sdcc doxygen uncrustify libusb-1.0-0-dev python3-pip texlive-latex-base texlive-latex-extra texlive-extra-utils poppler-utils cmake cppcheck r-base-core shellcheck pdf2svg +``` +Note: libusb is needed by stm8flash. + +2. Clone, build and install stm8flash: + +```console +git clone https://github.com/vdudouyt/stm8flash.git +cd stm8flash/ +make +make install +``` + +3. Stm8flash enumerates as USB device, add access to this device for non-root users (so that its possible to flash the chip without being root): + +```console +chmod o+w /dev/bus/usb/001/004 +``` + +In above example, _/dev/bus/usb/001/004_ is where the stm8flash enumerates, you will get id of your stm8flash from error message when trying to flash the board. + +# Firmware compilation and hardware flashing + +## Initial configuration + +Configure project using cmake, this step is needed only once. + +In Software directory create build directory and go into it: + +``` +mkdir build +cd build +``` + +Run cmake: + +``` +cmake -DCMAKE_SYSTEM_NAME=Generic -DCMAKE_C_COMPILER=sdcc ../ +``` + +## Compilation + +Compilation is done using make in the directory where cmake files were generated (Software/Build) + +``` +make +``` + +Binary will be stored directory where makefile is. + +# Hardware flashing + +``` +stm8flash -c stlink -p stm8s003f3 -w IonizationChamber.ihx +``` diff --git a/Software/README.md b/Software/README.md index 52a2cbb..384d359 100644 --- a/Software/README.md +++ b/Software/README.md @@ -1,5 +1,34 @@ -# Software +## Architecture -* ["Data processing and firmware flashing" node architecture -](https://github.com/RobertGawron/IonizationChamber/wiki/%22Data-processing-and-firmware-flashing%22-node-architecture -) +Measurements are collected from COM port (tunneled over USB) by a python script and stored in .cvs file. This file can be later parsed using R scripts to to collect various diagrams (radioactivity changes over time, radioactivity histogram, box-plots for various samples). + + + + +## Protocol for sending measurements via UART + +## Purpose + +To ensure data integrity sent from the ionization chamber device to computer, a simple protocol was added to encapsulate each data/command send. + +## Protocol details + +The field “msg length” describes amount of bytes the “data” section has. + +Definition of the message: + + + +Content of data section in above message is an example and can be different depends on message id, but now only one message is supported and it is as shown above. + + +### Defined message ids and message content + +* Msg id: 1 - data from last analog measurement of actual value. +* Msg data section: +* ADC configuration +* MSB (most significant byte) from ADC +* LSB (least significant byte) from ADC + +### CRC +CRC is calculated xor’ing all bytes in "preamble" and "data" section.