From 84f508c56b509d0893953992d396af99a821dbb7 Mon Sep 17 00:00:00 2001 From: adam_cummick Date: Mon, 27 Nov 2023 16:25:06 -0500 Subject: [PATCH] Add support for P1AM-200, P1-08ND-TTL, and P1-08TD-TTL --- LICENSE | 2 +- README.md | 105 +- examples/Basic/AnalogInput/AnalogInput.ino | 8 +- examples/Basic/AnalogOutput/AnalogOutput.ino | 8 +- .../Basic/ChannelLabels/ChannelLabels.ino | 8 +- .../Basic/DiscreteInput/DiscreteInput.ino | 8 +- .../Basic/DiscreteOutput/DiscreteOutput.ino | 8 +- .../DiscretesBitmapped/DiscretesBitmapped.ino | 8 +- examples/Basic/PrintModules/PrintModules.ino | 8 +- .../Basic/ReadTemperature/ReadTemperature.ino | 8 +- .../Implementation/START1_LED/START1_LED.ino | 8 +- .../START1_LOOPBACK/START1_LOOPBACK.ino | 8 +- .../Implementation/START2_IO/START2_IO.ino | 8 +- .../START2_PUZZLE/START2_PUZZLE.ino | 8 +- .../HSC/CheckForRollover/CheckForRollover.ino | 8 +- .../CustomChannelNames/CustomChannelNames.ino | 8 +- .../HSC/ReadInputs/ReadInputs.ino | 8 +- .../HSC/ReadPosition/ReadPosition.ino | 8 +- .../SpecialtyModules/HSC/Z_Reset/Z_Reset.ino | 8 +- .../SpecialtyModules/PWM/dirPWM/dirPWM.ino | 8 +- .../PWM/pwmOutput/pwmOutput.ino | 8 +- examples/ToggleSwitch/ToggleSwitch.ino | 8 +- .../BlockDataTransfer/BlockDataTransfer.ino | 8 +- .../BlockTransferAnalog.ino | 8 +- .../Utility/IsBaseActive/IsBaseActive.ino | 12 +- .../ModuleConfiguration.ino | 8 +- examples/Utility/ReadStatus/ReadStatus.ino | 8 +- examples/Utility/RollCall/RollCall.ino | 10 +- .../Utility/slotProperties/slotProperties.ino | 6 +- .../Watchdog/basicWatchdog/basicWatchdog.ino | 12 +- .../Watchdog/stopWatchdog/stopWatchdog.ino | 10 +- library.properties | 20 +- src/Module_List.h | 324 +- src/P1AM.cpp | 3146 ++++++++--------- src/P1AM.h | 233 +- src/P1_HSC.cpp | 3 +- src/defines.h | 28 +- 37 files changed, 2046 insertions(+), 2057 deletions(-) diff --git a/LICENSE b/LICENSE index bb90cce..7dffb04 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 FACTS Engineering, LLC +Copyright (c) 2023 FACTS Engineering, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 8091d1d..181b655 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -ProductivityOpen P1AM-100 [![Build Status](https://github.com/facts-engineering/P1AM/actions/workflows/main.yml/badge.svg)](https://github.com/facts-engineering/P1AM/actions) +ProductivityOpen P1AM [![Build Status](https://github.com/facts-engineering/P1AM/actions/workflows/main.yml/badge.svg)](https://github.com/facts-engineering/P1AM/actions) ============================ -The ProductivityOpen P1AM-100 is an automation platform compatible with Productivity 1000 Series I/O modules, P1AM Series shields, and Arduino MKR format shields. It can be programmed using the Arduino IDE. The board uses the SAMD21G18 Microcontroller like the Arduino MKRZERO and other similar boards. +### Product Description +The ProductivityOpen P1AM is an automation platform compatible with Productivity 1000 Series I/O modules, P1AM Series shields, and Arduino MKR format shields. + - P1AM-100: The board uses the SAMD21G18 Microcontroller like the Arduino MKRZERO and other similar boards. + - P1AM-200: The board uses the SAMD51P20 Microcontroller like the Adafruit Grand Central and other similar boards. -In depth information on the P1AM family and Productivity 1000 Modules can be found on the reference page here: -[P1AM-100 Documentation](https://facts-engineering.github.io/) +### Hardware - P1000 I/O Modules Productivity Series modules offer several types of industrial grade I/O - Analog and Temperature Inputs @@ -13,60 +15,17 @@ Productivity Series modules offer several types of industrial grade I/O - Discrete Outputs and Relays - Specialty Modules -The P1AM Library provides a simple interface for controlling P1000 Modules -```C++ -P1.writeDiscrete(HIGH, 1, 2); //Turn slot 1 channel 2 on - -float temperature = P1.readTemperature(2, 3); //Return temperature read from slot 2 channel 3 -``` - -The P1AM-100 can be purchased on the [Automation Direct Webstore](https://www.automationdirect.com/adc/shopping/catalog/programmable_controllers/open_source_controllers_(arduino-compatible)/productivityopen_(arduino-compatible)/controllers_-a-_shields/p1am-100) - -Productivity 1000 Series Modules can be purchased on the [Automation Direct Webstore](https://www.automationdirect.com/adc/shopping/catalog/programmable_controllers/productivity_series_controllers/productivity1000_(stackable_micro_plc)) - -# P1AM-100 Installation # ->Install the Arduino IDE version 1.8.7 or later. We recommend the current version on the [Arduino website](https://www.arduino.cc/en/main/software). -After you have installed the Arduino IDE you will need to follow one of the library install methods and the boards manager install instructions to get started with the P1AM-100 - -### P1AM-100 Library Install ### - -**Install From Library Manager** -- In the Arduino IDE go to `Sketch > Include Library > Manage Libraries` -- Type **P1AM** into the search box -- Click the install button in the P1AM library box - - ***OR*** - -**Install From Zip** -- Click the green button that says **Clone or download** on this repository page -- Select **Download ZIP** -- In the Arduino IDE go to `Sketch > Include Library > Add .ZIP Library` -- Navigate to the ZIP file that you downloaded in the window that opens - -### Boards Manager Install ### -- Start Arduino and select `File > Preferences` -- Enter `https://raw.githubusercontent.com/facts-engineering/facts-engineering.github.io/master/package_productivity-P1AM-boardmanagermodule_index.json` into the *Additional Board Manager URLs* field. You can add multiple URLs, separating them with commas. -- Open the Boards Manager from `Tools > Board > Boards Manager` -- Type **P1AM** into the search box and install the **P1AM-100** platform -- Select `Tools > Board > P1AM-100` -- Go to `Tools > Port` and select the COM Port your P1AM-100 is plugged into - -Boards manager link: `https://raw.githubusercontent.com/facts-engineering/facts-engineering.github.io/master/package_productivity-P1AM-boardmanagermodule_index.json` +### Hardware - P1AM Shields +ProductivityOpen offers 3 types of industrial shields and a prototyping kit. + - P1AM-ETH + - P1AM-SERIAL + - P1AM-GPIO + - P1AM-PROTO -### Driver Installation ### -For Windows users it is required that you install a device driver for the P1AM-100 -[Download driver installer here](https://github.com/facts-engineering/P1AMCore/raw/master/drivers.zip) +### Base Controller +The P1AM Base Controller is the chip that directs communcations between the microcontroller and the P1000 Modules. A P1000 Series power supply or external 24V supply is required to power the Base Controller and modules. - -# Getting Started # -The P1AM-100 library comes packed with examples to help you get started. -The examples can be found in the Arduino IDE under `File > Examples > P1AM-100` - -The **Basic** category contains the best examples to get started with the library and P1000 Modules. -The other categories contain examples that further explore the functionailty of the P1AM-100. - -# Base Controller # -The P1AM Base Controller is the chip that directs communcations between the SAMD21 and the P1000 Modules. A P1000 Series power supply or external 24V supply is required to power the Base Controller and modules. The communications with the SAMD21 are SPI based and use 5 total pins. Pins 8, 9 and 10 can be shared with other SPI devices. **A3 and A4 must not be used on any shield if using the base controller functionality of the P1AM-100** +The **P1AM-100** communications with the SAMD21 are SPI based and use 5 total pins. Pins 8, 9 and 10 can be shared with other SPI devices. **A3 and A4 must not be used on any shield if using the base controller functionality of the P1AM-100** | Pin | Function | |:---:|:--------:| @@ -76,18 +35,40 @@ The P1AM Base Controller is the chip that directs communcations between the SAMD | A3 | CS | | A4 | ACK | +The **P1AM-200** Base Controller pins are all internal. No header pins are used for IO module communications. -# Shields # +In depth information on the P1AM family hardware can be found on the reference page here: [ProductivityOpen Documentation](https://facts-engineering.github.io/) -The P1AM-100 uses the Arduino MKR format header. The pins are 3.3V tolerant only, so do not apply any 5V signals. +Hardware can be purchased on the [Automation Direct Webstore](https://www.automationdirect.com/adc/shopping/catalog/programmable_controllers/productivity_open_(arduino-compatible)) -Industrial rated shields for the P1AM-100 can be purchased from [Automation Direct here](https://www.automationdirect.com/adc/shopping/catalog/programmable_controllers/open_source_controllers_(arduino-compatible)/productivityopen_(arduino-compatible)/controllers_-a-_shields/). +## P1AM Library -Please read through the Base Controller section above to determine if your shield pinout will work properly. For example, the Arduino MKR RGB Shield is not compatible with the P1AM-100 as it uses pins A3 and A4. +### Description +The P1AM Library provides a simple interface for controlling P1000 Modules. +```C++ +P1.writeDiscrete(HIGH, 1, 2); //Turn slot 1 channel 2 on +float temperature = P1.readTemperature(2, 3); //Return temperature read from slot 2 channel 3 +``` +### Installing the Library -# Additional Resources # +>Install the Arduino IDE version 2.0.0 or later. We recommend the current version on the [Arduino website](https://www.arduino.cc/en/main/software). + +- Use Arduino's Library Manager to install the library. `Tools > Manage Libraries` +- Type **P1AM** into the search box and click the install button + +### Installing the Board + +- Use Arduino's Boards Manager to install the board. `Tools > Board > Boards Manager` +- Type **P1AM** into the search box and click the install button + +### Getting Started +Documentation can be found on the [ProductivityOpen Documentation](https://facts-engineering.github.io/). The website has information that includes the hardware documentation, library API, and code snippets. + +The P1AM library also comes packed with examples that can be found in the Arduino IDE under `File > Examples > P1AM`. + +## Additional Resources [ProductivityBlocks Graphical Programming from Automation Direct](https://github.com/AutomationDirect/ProductivityBlocks) [Automation Direct P1AM Customer Forum](http://go2adc.com/p1am) @@ -98,4 +79,4 @@ Please read through the Base Controller section above to determine if your shiel [Arduino Forum](https://forum.arduino.cc/index.php) - +[Arduino Support Discord](https://discord.com/invite/jQJFwW7) diff --git a/examples/Basic/AnalogInput/AnalogInput.ino b/examples/Basic/AnalogInput/AnalogInput.ino index 082d22f..68c944e 100644 --- a/examples/Basic/AnalogInput/AnalogInput.ino +++ b/examples/Basic/AnalogInput/AnalogInput.ino @@ -21,12 +21,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Basic/AnalogOutput/AnalogOutput.ino b/examples/Basic/AnalogOutput/AnalogOutput.ino index 95c94c6..c4e988a 100644 --- a/examples/Basic/AnalogOutput/AnalogOutput.ino +++ b/examples/Basic/AnalogOutput/AnalogOutput.ino @@ -17,15 +17,15 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ To confirm the analog output value you can use either a P1 analog input module or a multimeter. Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Basic/ChannelLabels/ChannelLabels.ino b/examples/Basic/ChannelLabels/ChannelLabels.ino index e822401..a9c33cc 100644 --- a/examples/Basic/ChannelLabels/ChannelLabels.ino +++ b/examples/Basic/ChannelLabels/ChannelLabels.ino @@ -13,12 +13,12 @@ | A || O || O | | M || T || T | | - || || | - | 1 || 0 || 0 | - | 0 || 1 || 2 | - | 0 || || | + | C || 0 || 0 | + | P || 1 || 2 | + | U || || | ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Basic/DiscreteInput/DiscreteInput.ino b/examples/Basic/DiscreteInput/DiscreteInput.ino index 89ecac4..c99f5c6 100644 --- a/examples/Basic/DiscreteInput/DiscreteInput.ino +++ b/examples/Basic/DiscreteInput/DiscreteInput.ino @@ -15,12 +15,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Basic/DiscreteOutput/DiscreteOutput.ino b/examples/Basic/DiscreteOutput/DiscreteOutput.ino index e84b467..bced380 100644 --- a/examples/Basic/DiscreteOutput/DiscreteOutput.ino +++ b/examples/Basic/DiscreteOutput/DiscreteOutput.ino @@ -15,12 +15,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Basic/DiscretesBitmapped/DiscretesBitmapped.ino b/examples/Basic/DiscretesBitmapped/DiscretesBitmapped.ino index ebda1ca..a85ab98 100644 --- a/examples/Basic/DiscretesBitmapped/DiscretesBitmapped.ino +++ b/examples/Basic/DiscretesBitmapped/DiscretesBitmapped.ino @@ -20,9 +20,9 @@ | A || O || O | | M || T || T | | - || || | - | 1 || 0 || 0 | - | 0 || 1 || 2 | - | 0 || || | + | C || 0 || 0 | + | P || 1 || 2 | + | U || || | ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ Note: This example uses hexadecimal formatted numbers, though any number format @@ -52,7 +52,7 @@ readDiscrete function. Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Basic/PrintModules/PrintModules.ino b/examples/Basic/PrintModules/PrintModules.ino index 2dac691..5966a96 100644 --- a/examples/Basic/PrintModules/PrintModules.ino +++ b/examples/Basic/PrintModules/PrintModules.ino @@ -13,12 +13,12 @@ | A || O || O || O || O || O || O || O || O || O || O || O || O || O || O || O | | M || T || T || T || T || T || T || T || T || T || T || T || T || T || T || T | | - || || || || || || || || || || || || || || || | - | 1 || 0 || 0 || 0 || 0 || 0 || 0 || 0 || 0 || 0 || 1 || 1 || 1 || 1 || 1 || 1 | - | 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 0 || 1 || 2 || 3 || 4 || 5 | - | 0 || || || || || || || || || || || || || || || | + | C || 0 || 0 || 0 || 0 || 0 || 0 || 0 || 0 || 0 || 1 || 1 || 1 || 1 || 1 || 1 | + | P || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 0 || 1 || 2 || 3 || 4 || 5 | + | U || || || || || || || || || || || || || || || | ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Basic/ReadTemperature/ReadTemperature.ino b/examples/Basic/ReadTemperature/ReadTemperature.ino index f01dfbe..5e0c7b4 100644 --- a/examples/Basic/ReadTemperature/ReadTemperature.ino +++ b/examples/Basic/ReadTemperature/ReadTemperature.ino @@ -31,12 +31,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Implementation/START1_LED/START1_LED.ino b/examples/Implementation/START1_LED/START1_LED.ino index 02db729..aadc0f9 100644 --- a/examples/Implementation/START1_LED/START1_LED.ino +++ b/examples/Implementation/START1_LED/START1_LED.ino @@ -14,15 +14,15 @@ | A || A || - | | M || M || C | | - || - || O | - | G || 1 || M | - | P || 0 || B | - | I || 0 || O | + | G || C || M | + | P || P || B | + | I || U || O | | O || || | ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ This example requires an LED (not provided in the start kit) attached to pin 6 of the P1AM-GPIO terminal block. Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Implementation/START1_LOOPBACK/START1_LOOPBACK.ino b/examples/Implementation/START1_LOOPBACK/START1_LOOPBACK.ino index 023a779..a6f5975 100644 --- a/examples/Implementation/START1_LOOPBACK/START1_LOOPBACK.ino +++ b/examples/Implementation/START1_LOOPBACK/START1_LOOPBACK.ino @@ -17,14 +17,14 @@ | A || - | | M || C | | - || O | - | 1 || M | - | 0 || B | - | 0 || O | + | C || M | + | P || B | + | U || O | | || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Implementation/START2_IO/START2_IO.ino b/examples/Implementation/START2_IO/START2_IO.ino index ed60809..39ed44b 100644 --- a/examples/Implementation/START2_IO/START2_IO.ino +++ b/examples/Implementation/START2_IO/START2_IO.ino @@ -14,13 +14,13 @@ | A || - || - | | M || 0 || 0 | | - || 8 || 8 | - | 1 || T || S | - | 0 || R || I | - | 0 || S || M | + | C || T || S | + | P || R || I | + | U || S || M | ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ #include diff --git a/examples/Implementation/START2_PUZZLE/START2_PUZZLE.ino b/examples/Implementation/START2_PUZZLE/START2_PUZZLE.ino index 1549617..7039e55 100644 --- a/examples/Implementation/START2_PUZZLE/START2_PUZZLE.ino +++ b/examples/Implementation/START2_PUZZLE/START2_PUZZLE.ino @@ -17,13 +17,13 @@ | A || - || - | | M || 0 || 0 | | - || 8 || 8 | - | 1 || T || S | - | 0 || R || I | - | 0 || S || M | + | C || T || S | + | P || R || I | + | U || S || M | ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/SpecialtyModules/HSC/CheckForRollover/CheckForRollover.ino b/examples/SpecialtyModules/HSC/CheckForRollover/CheckForRollover.ino index d7c2c0e..fedb800 100644 --- a/examples/SpecialtyModules/HSC/CheckForRollover/CheckForRollover.ino +++ b/examples/SpecialtyModules/HSC/CheckForRollover/CheckForRollover.ino @@ -15,12 +15,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/SpecialtyModules/HSC/CustomChannelNames/CustomChannelNames.ino b/examples/SpecialtyModules/HSC/CustomChannelNames/CustomChannelNames.ino index 6d0aa76..ff44078 100644 --- a/examples/SpecialtyModules/HSC/CustomChannelNames/CustomChannelNames.ino +++ b/examples/SpecialtyModules/HSC/CustomChannelNames/CustomChannelNames.ino @@ -15,12 +15,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/SpecialtyModules/HSC/ReadInputs/ReadInputs.ino b/examples/SpecialtyModules/HSC/ReadInputs/ReadInputs.ino index a7ad2b8..8d12901 100644 --- a/examples/SpecialtyModules/HSC/ReadInputs/ReadInputs.ino +++ b/examples/SpecialtyModules/HSC/ReadInputs/ReadInputs.ino @@ -13,12 +13,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/SpecialtyModules/HSC/ReadPosition/ReadPosition.ino b/examples/SpecialtyModules/HSC/ReadPosition/ReadPosition.ino index a527bb6..7cc0c68 100644 --- a/examples/SpecialtyModules/HSC/ReadPosition/ReadPosition.ino +++ b/examples/SpecialtyModules/HSC/ReadPosition/ReadPosition.ino @@ -14,12 +14,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/SpecialtyModules/HSC/Z_Reset/Z_Reset.ino b/examples/SpecialtyModules/HSC/Z_Reset/Z_Reset.ino index a9cd569..e7e7796 100644 --- a/examples/SpecialtyModules/HSC/Z_Reset/Z_Reset.ino +++ b/examples/SpecialtyModules/HSC/Z_Reset/Z_Reset.ino @@ -15,12 +15,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/SpecialtyModules/PWM/dirPWM/dirPWM.ino b/examples/SpecialtyModules/PWM/dirPWM/dirPWM.ino index 51ebfc1..dbf4643 100644 --- a/examples/SpecialtyModules/PWM/dirPWM/dirPWM.ino +++ b/examples/SpecialtyModules/PWM/dirPWM/dirPWM.ino @@ -10,12 +10,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/SpecialtyModules/PWM/pwmOutput/pwmOutput.ino b/examples/SpecialtyModules/PWM/pwmOutput/pwmOutput.ino index dfdde36..824be2b 100644 --- a/examples/SpecialtyModules/PWM/pwmOutput/pwmOutput.ino +++ b/examples/SpecialtyModules/PWM/pwmOutput/pwmOutput.ino @@ -16,12 +16,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/ToggleSwitch/ToggleSwitch.ino b/examples/ToggleSwitch/ToggleSwitch.ino index c3490ed..b66120d 100644 --- a/examples/ToggleSwitch/ToggleSwitch.ino +++ b/examples/ToggleSwitch/ToggleSwitch.ino @@ -13,12 +13,12 @@ | A | | M | | - | - | 1 | - | 0 | - | 0 | + | C | + | P | + | U | ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Utility/BlockDataTransfer/BlockDataTransfer.ino b/examples/Utility/BlockDataTransfer/BlockDataTransfer.ino index b0d245f..427d0c8 100644 --- a/examples/Utility/BlockDataTransfer/BlockDataTransfer.ino +++ b/examples/Utility/BlockDataTransfer/BlockDataTransfer.ino @@ -34,9 +34,9 @@ | P1 | | 0 | | 0 | | 1 | | 0 | | 0 | | 0 | | 1 | | 0 | | AM | | 8 | | 8 | | 6 | | 8 | | 8 | | 8 | | 6 | | 8 | | - | | T | | T | | T | | T | | N | | T | | N | | S | - | 1 | | R | | R | | R | | R | | D | | R | | D | | I | - | 0 | | S | | S | | | | S | | 3 | | S | | 3 | | M | - | 0 | |DO - 0 | |DO - 1 | |DO - 2 | |DO - 4 | |DI - 0 | |DO - 5 | |DI - 1 | |DI - 3 | + | C | | R | | R | | R | | R | | D | | R | | D | | I | + | P | | S | | S | | | | S | | 3 | | S | | 3 | | M | + | U | |DO - 0 | |DO - 1 | |DO - 2 | |DO - 4 | |DI - 0 | |DO - 5 | |DI - 1 | |DI - 3 | ¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ Each analog channel uses 4 bytes of data, regardless of the resolution of the module. For Voltage/Current analog @@ -56,7 +56,7 @@ This example works with all P1000 Series Discrete Output Modules, but can be adapted to any type of module. Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ #include diff --git a/examples/Utility/BlockTransferAnalog/BlockTransferAnalog.ino b/examples/Utility/BlockTransferAnalog/BlockTransferAnalog.ino index 1aefc77..f78de76 100644 --- a/examples/Utility/BlockTransferAnalog/BlockTransferAnalog.ino +++ b/examples/Utility/BlockTransferAnalog/BlockTransferAnalog.ino @@ -22,9 +22,9 @@ | P1 | | 0 | | 0 | | 0 | | 0 || 0 | | AM | | 4 | | 8 | | 4 | | 4 || 4 | | - | | A | | D | | A | | T || D | - | 1 | | D | | A | | D | | H || A | - | 0 | | L | | L | | | | M || L | - | 0 | |AI - 0 | |AO - 0 | |AI - 16| |AI -32 ||AO - 32| + | C | | D | | A | | D | | H || A | + | P | | L | | L | | | | M || L | + | U | |AI - 0 | |AO - 0 | |AI - 16| |AI -32 ||AO - 32| ¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯ @@ -37,7 +37,7 @@ This example works with all P1000 Series Analog Modules, but can be adapted to any type of module. Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ #include diff --git a/examples/Utility/IsBaseActive/IsBaseActive.ino b/examples/Utility/IsBaseActive/IsBaseActive.ino index 7d497ca..5127dcd 100644 --- a/examples/Utility/IsBaseActive/IsBaseActive.ino +++ b/examples/Utility/IsBaseActive/IsBaseActive.ino @@ -13,13 +13,13 @@ //To detect a base failure and re-init Step 1: Load program in current form. - Step 2: Connect USB cable from PC to P1AM-100. + Step 2: Connect USB cable from PC to P1AM-CPU. Step 3: Remove 24Vdc power and reapply. *Note: Open serial Monitor. You should have no errors. Step 4: Verify Slot 1 Channel 2 output is blinking. // To see a failure Step 5: Comment out lines 55-62. - Step 6: Upload to the P1AM-100. + Step 6: Upload to the P1AM-CPU. Step 7: Remove 24Vdc power & reapply.*Note: Open serial Monitor you will get a "Base Sync Timeout" error. Step 8: The output on Slot 1 Channel 2 should not be blinking. @@ -31,13 +31,13 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering and AutomationDirect. - Copyright (c) 2020 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Utility/ModuleConfiguration/ModuleConfiguration.ino b/examples/Utility/ModuleConfiguration/ModuleConfiguration.ino index 103701b..7383f9f 100644 --- a/examples/Utility/ModuleConfiguration/ModuleConfiguration.ino +++ b/examples/Utility/ModuleConfiguration/ModuleConfiguration.ino @@ -22,12 +22,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Utility/ReadStatus/ReadStatus.ino b/examples/Utility/ReadStatus/ReadStatus.ino index 6c9c3e7..4b3bcfd 100644 --- a/examples/Utility/ReadStatus/ReadStatus.ino +++ b/examples/Utility/ReadStatus/ReadStatus.ino @@ -26,12 +26,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Utility/RollCall/RollCall.ino b/examples/Utility/RollCall/RollCall.ino index d037889..46f9de9 100644 --- a/examples/Utility/RollCall/RollCall.ino +++ b/examples/Utility/RollCall/RollCall.ino @@ -9,7 +9,7 @@ This example will check to see if Slot 1 is a P1-08TRS and Slot 2 - is a P1-08SIM. If either module is missing or incorrect the P1AM-100 will: + is a P1-08SIM. If either module is missing or incorrect the P1AM-CPU will: 1. Report the error to the serial monitor. 2. Disable any further writes or reads to or from the module. 3. Return a bitmapped representation of any module in error @@ -23,13 +23,13 @@ | A || O || O | | M || T || T | | - || || | - | 1 || 0 || 0 | - | 0 || 1 || 2 | - | 0 || || | + | C || 0 || 0 | + | P || 1 || 2 | + | U || || | ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Utility/slotProperties/slotProperties.ino b/examples/Utility/slotProperties/slotProperties.ino index 3dd81e4..754f51d 100644 --- a/examples/Utility/slotProperties/slotProperties.ino +++ b/examples/Utility/slotProperties/slotProperties.ino @@ -16,9 +16,9 @@ | A || O || O | | M || T || T | | - || || | - | 1 || 0 || 0 | - | 0 || 1 || 2 | - | 0 || || | etc... + | C || 0 || 0 | + | P || 1 || 2 | + | U || || | etc... ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ Written by Brett Bowden and Adam Cummick Licensed under the MIT license. diff --git a/examples/Watchdog/basicWatchdog/basicWatchdog.ino b/examples/Watchdog/basicWatchdog/basicWatchdog.ino index 6a89721..134d2bb 100644 --- a/examples/Watchdog/basicWatchdog/basicWatchdog.ino +++ b/examples/Watchdog/basicWatchdog/basicWatchdog.ino @@ -1,12 +1,12 @@ /* basicWatchdog Example The watchdog timer is a safety feature that will stop code from - executing if the P1AM-100 is not operating properly. It is used to detect + executing if the P1AM-CPU is not operating properly. It is used to detect and recover from potential hardware faults or programming mistakes. This example shows how to initialize and then use the watchdog. There are 2 modes for the watchdog: Toggle and Hold. - - Toggle will reset the P1AM-100 CPU and then start code at the beginning. + - Toggle will reset the P1AM-CPU and then start code at the beginning. - Hold will hold the arduino in reset and shut off all modules. This example requires any P1000 series module. @@ -16,12 +16,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/examples/Watchdog/stopWatchdog/stopWatchdog.ino b/examples/Watchdog/stopWatchdog/stopWatchdog.ino index a772387..1de0da4 100644 --- a/examples/Watchdog/stopWatchdog/stopWatchdog.ino +++ b/examples/Watchdog/stopWatchdog/stopWatchdog.ino @@ -2,7 +2,7 @@ stopWatchdog example The watchdog timer is a safety feature that will stop code from - executing if the P1AM-100 is not operating properly. It is used to detect + executing if the P1AM-CPU is not operating properly. It is used to detect and recover from potential hardware or programming faults. This example shows how to initialize and then stop the watchdog timer. @@ -16,12 +16,12 @@ | A || O | | M || T | | - || | - | 1 || 0 | - | 0 || 1 | - | 0 || | + | C || 0 | + | P || 1 | + | U || | ¯¯¯¯¯ ¯¯¯¯¯ Written by FACTS Engineering - Copyright (c) 2019 FACTS Engineering, LLC + Copyright (c) 2023 FACTS Engineering, LLC Licensed under the MIT license. */ diff --git a/library.properties b/library.properties index 8920465..d6a015c 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ -name=P1AM -version=1.0.6 -author=FACTS Engineering -maintainer=Adam Cummick -sentence=P1AM-100 CPU library -paragraph=A library that has all the functions needed to interface a P1AM-100 CPU to P1 I/O modules. -category=Signal Input/Output -url=https://github.com/facts-engineering/P1AM -architectures=samd -includes=P1AM.h +name=P1AM +version=1.0.7 +author=FACTS Engineering +maintainer=Adam Cummick +sentence=P1AM CPU library +paragraph=A library that has all the functions needed to interface the P1AM-100 and P1AM-200 CPUs to P1000 I/O modules. +category=Signal Input/Output +url=https://github.com/facts-engineering/P1AM +architectures=samd +includes=P1AM.h diff --git a/src/Module_List.h b/src/Module_List.h index 1b9903f..32da388 100644 --- a/src/Module_List.h +++ b/src/Module_List.h @@ -1,160 +1,164 @@ -/* -MIT License - -Copyright (c) 2019 FACTS Engineering, LLC - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#ifndef Module_List_h -#define Module_List_h - -const struct moduleProps -{ - unsigned int moduleID; - char diBytes; //Number of bytes used by all Discrete Input channels - char doBytes; //Number of bytes used by all Discrete Output channels - char aiBytes; //Number of bytes used by all Analog Input channels - char aoBytes; //Number of bytes used by all Analog Output channels - char statusBytes; //Number of status bytes. Things like overrange errors or missing 24V. - char configBytes; //Number of bytes used to configure module. Things like ranges and enabled channels. - char dataSize; //Resolution or Specialty info - const char* moduleName; //Text name of module -}mdb[] = { - - //{0x000000ID,di,do,ai,ao,st,cf,ds} - {0x00000000, 0, 0, 0, 0, 0, 0, 0, "Empty"}, //Empty first entry for defaults - - {0x04A00081, 1, 0, 0, 0, 0, 0, 1, "P1-08ND3"}, //P1-08ND3 - - {0x04A00085, 1, 0, 0, 0, 0, 0, 1, "P1-08NA"}, //P1-08NA - - {0x04A00087, 1, 0, 0, 0, 0, 0, 1, "P1-08SIM"}, //P1-08SIM - - {0x04A00088, 1, 0, 0, 0, 0, 0, 1, "P1-08NE3"}, //P1-08NE3 - - {0x05200082, 2, 0, 0, 0, 0, 0, 1, "P1-16ND3"}, //P1-16ND3 - - {0x05200089, 2, 0, 0, 0, 0, 0, 1, "P1-16NE3"}, //P1-16NE3 - - {0x1403F481, 0, 0, 0, 32, 4, 4, 0xA0, "P1-04PWM"}, //P1-04PWM - - {0x1404008D, 0, 1, 0, 0, 0, 0, 1, "P1-08TA"}, //P1-08TA - - {0x1404008F, 0, 1, 0, 0, 0, 0, 1, "P1-08TRS"}, //P1-08TRS - - {0x14040091, 0, 2, 0, 0, 0, 0, 1, "P1-16TR"}, //P1-16TR - - {0x14050081, 0, 1, 0, 0, 0, 0, 1, "P1-08TD1"}, //P1-08TD1 - - {0x14050082, 0, 1, 0, 0, 0, 0, 1, "P1-08TD2"}, //P1-08TD2 - - {0x14080085, 0, 2, 0, 0, 0, 0, 1, "P1-15TD1"}, //P1-15TD1 - - {0x14080086, 0, 2, 0, 0, 0, 0, 1, "P1-15TD2"}, //P1-15TD2 - - {0x24A50081, 1, 1, 0, 0, 0, 0, 1, "P1-16CDR"}, //P1-16CDR - - {0x24A50082, 1, 1, 0, 0, 0, 0, 1, "P1-15CDD1"}, //P1-15CDD1 - - {0x24A50083, 1, 1, 0, 0, 0, 0, 1, "P1-15CDD2"}, //P1-15CDD2 - - {0x34605581, 0, 0, 16, 0, 12, 18, 16, "P1-04AD"}, //P1-04AD - - {0x34605582, 0, 0, 16, 0, 12, 2, 16, "P1-04AD-1"}, //P1-04AD-1 - - {0x34605583, 0, 0, 16, 0, 12, 2, 16, "P1-04AD-2"}, //P1-04AD-2 - - {0x34605588, 0, 0, 16, 0, 12, 8, 16, "P1-04RTD"}, //P1-04RTD - - {0x3460558F, 0, 0, 16, 0, 12, 2, 12, "P1-04ADL-1"}, //P1-04ADL-1 - - {0x34605590, 0, 0, 16, 0, 12, 2, 12, "P1-04ADL-2"}, //P1-04ADL-2 - - {0x34608C81, 0, 0, 16, 0, 12, 20, 32, "P1-04THM"}, //P1-04THM - - {0x34608C8E, 0, 0, 16, 0, 12, 8, 32, "P1-04NTC"}, //P1-04NTC - - {0x34A0558A, 0, 0, 32, 0, 12, 2, 12, "P1-08ADL-1"}, //P1-08ADL-1 - - {0x34A0558B, 0, 0, 32, 0, 12, 2, 12, "P1-08ADL-2"}, //P1-08ADL-2 - - {0x34A5A481, 2, 0, 36, 36, 4, 12, 0xC0, "P1-02HSC"}, //P1-02HSC - - {0x44035583, 0, 0, 0, 16, 4, 0, 12, "P1-04DAL-1"}, //P1-04DAL-1 - - {0x44035584, 0, 0, 0, 16, 4, 0, 12, "P1-04DAL-2"}, //P1-04DAL-2 - - {0x44055588, 0, 0, 0, 32, 4, 0, 12, "P1-08DAL-1"}, //P1-08DAL-1 - - {0x44055589, 0, 0, 0, 32, 4, 0, 12, "P1-08DAL-2"}, //P1-08DAL-2 - - {0x5461A783, 0, 0, 16, 8, 12, 2, 12, "P1-4ADL2DAL-1"}, //P1-4ADL2DAL-1 - - {0x5461A784, 0, 0, 16, 8, 12, 2, 12, "P1-4ADL2DAL-2"}, //P1-4ADL2DAL-2 - - {0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, "BAD SLOT"}, //empty in case no modules are defined. - - {0x00000000, 0, 0, 0, 0, 0, 0, 0, "BAD SLOT"} //empty in case no modules are defined. -}; - -const char P1_04AD_1_DEFAULT_CONFIG[] = {0x40,0x03}; - -const char P1_04AD_2_DEFAULT_CONFIG[] = {0x40,0x03}; - -const char P1_04ADL_1_DEFAULT_CONFIG[] = {0x40,0x03}; - -const char P1_04ADL_2_DEFAULT_CONFIG[] = {0x40,0x03}; - -const char P1_08ADL_1_DEFAULT_CONFIG[] = {0x40,0x07}; - -const char P1_08ADL_2_DEFAULT_CONFIG[] = {0x40,0x07}; - -const char P1_04PWM_DEFAULT_CONFIG[] = {0x02,0x02,0x02,0x02}; - -const char P1_04ADL2DAL_1_DEFAULT_CONFIG[] = {0x40,0x03}; - -const char P1_04ADL2DAL_2_DEFAULT_CONFIG[] = {0x40,0x03}; - -const char P1_04NTC_DEFAULT_CONFIG[] = {0x40,0x03,0x60,0x05, - 0x20,0x00,0x80,0x02}; - -const char P1_04THM_DEFAULT_CONFIG[] = {0x40,0x03,0x60,0x05, - 0x21,0x00,0x22,0x00, - 0x23,0x00,0x24,0x00, - 0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00}; - -const char P1_04RTD_DEFAULT_CONFIG[] = {0x40,0x03,0x60,0x05, - 0x20,0x01,0x80,0x00}; - -const char P1_04AD_DEFAULT_CONFIG[] = {0x40,0x03,0x00,0x00, - 0x20,0x03,0x00,0x00, - 0x21,0x03,0x00,0x00, - 0x22,0x03,0x00,0x00, - 0x23,0x03}; - - -const char P1_02HSC_DEFAULT_CONFIG[] = {0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x01, - 0x00,0x00,0x00,0x01}; - - - -#endif +/* +MIT License + +Copyright (c) 2023 FACTS Engineering, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef Module_List_h +#define Module_List_h + +const struct moduleProps +{ + unsigned int moduleID; + char diBytes; //Number of bytes used by all Discrete Input channels + char doBytes; //Number of bytes used by all Discrete Output channels + char aiBytes; //Number of bytes used by all Analog Input channels + char aoBytes; //Number of bytes used by all Analog Output channels + char statusBytes; //Number of status bytes. Things like overrange errors or missing 24V. + char configBytes; //Number of bytes used to configure module. Things like ranges and enabled channels. + char dataSize; //Resolution or Specialty info + const char* moduleName; //Text name of module +}mdb[] = { + + //{0x000000ID,di,do,ai,ao,st,cf,ds} + {0x00000000, 0, 0, 0, 0, 0, 0, 0, "Empty"}, //Empty first entry for defaults + + {0x04A00042, 1, 0, 0, 0, 0, 0, 1, "P1-08ND-TTL"}, //P1-08ND-TTL + + {0x04A00081, 1, 0, 0, 0, 0, 0, 1, "P1-08ND3"}, //P1-08ND3 + + {0x04A00085, 1, 0, 0, 0, 0, 0, 1, "P1-08NA"}, //P1-08NA + + {0x04A00087, 1, 0, 0, 0, 0, 0, 1, "P1-08SIM"}, //P1-08SIM + + {0x04A00088, 1, 0, 0, 0, 0, 0, 1, "P1-08NE3"}, //P1-08NE3 + + {0x05200082, 2, 0, 0, 0, 0, 0, 1, "P1-16ND3"}, //P1-16ND3 + + {0x05200089, 2, 0, 0, 0, 0, 0, 1, "P1-16NE3"}, //P1-16NE3 + + {0x1403F481, 0, 0, 0, 32, 4, 4, 0xA0, "P1-04PWM"}, //P1-04PWM + + {0x1404008D, 0, 1, 0, 0, 0, 0, 1, "P1-08TA"}, //P1-08TA + + {0x1404008F, 0, 1, 0, 0, 0, 0, 1, "P1-08TRS"}, //P1-08TRS + + {0x14040091, 0, 2, 0, 0, 0, 0, 1, "P1-16TR"}, //P1-16TR + + {0x14050046, 0, 1, 0, 0, 0, 0, 1, "P1-08TD-TTL"}, //P1-08TD-TTL + + {0x14050081, 0, 1, 0, 0, 0, 0, 1, "P1-08TD1"}, //P1-08TD1 + + {0x14050082, 0, 1, 0, 0, 0, 0, 1, "P1-08TD2"}, //P1-08TD2 + + {0x14080085, 0, 2, 0, 0, 0, 0, 1, "P1-15TD1"}, //P1-15TD1 + + {0x14080086, 0, 2, 0, 0, 0, 0, 1, "P1-15TD2"}, //P1-15TD2 + + {0x24A50081, 1, 1, 0, 0, 0, 0, 1, "P1-16CDR"}, //P1-16CDR + + {0x24A50082, 1, 1, 0, 0, 0, 0, 1, "P1-15CDD1"}, //P1-15CDD1 + + {0x24A50083, 1, 1, 0, 0, 0, 0, 1, "P1-15CDD2"}, //P1-15CDD2 + + {0x34605581, 0, 0, 16, 0, 12, 18, 16, "P1-04AD"}, //P1-04AD + + {0x34605582, 0, 0, 16, 0, 12, 2, 16, "P1-04AD-1"}, //P1-04AD-1 + + {0x34605583, 0, 0, 16, 0, 12, 2, 16, "P1-04AD-2"}, //P1-04AD-2 + + {0x34605588, 0, 0, 16, 0, 12, 8, 16, "P1-04RTD"}, //P1-04RTD + + {0x3460558F, 0, 0, 16, 0, 12, 2, 12, "P1-04ADL-1"}, //P1-04ADL-1 + + {0x34605590, 0, 0, 16, 0, 12, 2, 12, "P1-04ADL-2"}, //P1-04ADL-2 + + {0x34608C81, 0, 0, 16, 0, 12, 20, 32, "P1-04THM"}, //P1-04THM + + {0x34608C8E, 0, 0, 16, 0, 12, 8, 32, "P1-04NTC"}, //P1-04NTC + + {0x34A0558A, 0, 0, 32, 0, 12, 2, 12, "P1-08ADL-1"}, //P1-08ADL-1 + + {0x34A0558B, 0, 0, 32, 0, 12, 2, 12, "P1-08ADL-2"}, //P1-08ADL-2 + + {0x34A5A481, 2, 0, 36, 36, 4, 12, 0xC0, "P1-02HSC"}, //P1-02HSC + + {0x44035583, 0, 0, 0, 16, 4, 0, 12, "P1-04DAL-1"}, //P1-04DAL-1 + + {0x44035584, 0, 0, 0, 16, 4, 0, 12, "P1-04DAL-2"}, //P1-04DAL-2 + + {0x44055588, 0, 0, 0, 32, 4, 0, 12, "P1-08DAL-1"}, //P1-08DAL-1 + + {0x44055589, 0, 0, 0, 32, 4, 0, 12, "P1-08DAL-2"}, //P1-08DAL-2 + + {0x5461A783, 0, 0, 16, 8, 12, 2, 12, "P1-4ADL2DAL-1"}, //P1-4ADL2DAL-1 + + {0x5461A784, 0, 0, 16, 8, 12, 2, 12, "P1-4ADL2DAL-2"}, //P1-4ADL2DAL-2 + + {0xFFFFFFFF, 0, 0, 0, 0, 0, 0, 0, "BAD SLOT"}, //empty in case no modules are defined. + + {0x00000000, 0, 0, 0, 0, 0, 0, 0, "BAD SLOT"} //empty in case no modules are defined. +}; + +const char P1_04AD_1_DEFAULT_CONFIG[] = {0x40,0x03}; + +const char P1_04AD_2_DEFAULT_CONFIG[] = {0x40,0x03}; + +const char P1_04ADL_1_DEFAULT_CONFIG[] = {0x40,0x03}; + +const char P1_04ADL_2_DEFAULT_CONFIG[] = {0x40,0x03}; + +const char P1_08ADL_1_DEFAULT_CONFIG[] = {0x40,0x07}; + +const char P1_08ADL_2_DEFAULT_CONFIG[] = {0x40,0x07}; + +const char P1_04PWM_DEFAULT_CONFIG[] = {0x02,0x02,0x02,0x02}; + +const char P1_04ADL2DAL_1_DEFAULT_CONFIG[] = {0x40,0x03}; + +const char P1_04ADL2DAL_2_DEFAULT_CONFIG[] = {0x40,0x03}; + +const char P1_04NTC_DEFAULT_CONFIG[] = {0x40,0x03,0x60,0x05, + 0x20,0x00,0x80,0x02}; + +const char P1_04THM_DEFAULT_CONFIG[] = {0x40,0x03,0x60,0x05, + 0x21,0x00,0x22,0x00, + 0x23,0x00,0x24,0x00, + 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00}; + +const char P1_04RTD_DEFAULT_CONFIG[] = {0x40,0x03,0x60,0x05, + 0x20,0x01,0x80,0x00}; + +const char P1_04AD_DEFAULT_CONFIG[] = {0x40,0x03,0x00,0x00, + 0x20,0x03,0x00,0x00, + 0x21,0x03,0x00,0x00, + 0x22,0x03,0x00,0x00, + 0x23,0x03}; + + +const char P1_02HSC_DEFAULT_CONFIG[] = {0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x01, + 0x00,0x00,0x00,0x01}; + + + +#endif diff --git a/src/P1AM.cpp b/src/P1AM.cpp index 90bee9e..2632e20 100644 --- a/src/P1AM.cpp +++ b/src/P1AM.cpp @@ -1,1575 +1,1571 @@ -/* -MIT License - -Copyright (c) 2019 FACTS Engineering, LLC - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#include "P1AM.h" - -SPISettings P100_SPI_SETTINGS(1000000, MSBFIRST, SPI_MODE2); - -P1AM::P1AM(){ - pinMode(slaveSelectPin, OUTPUT); //Define Slave select pin for Base Controller - pinMode(slaveAckPin, INPUT); //Define Ack pin for Base Controller - pinMode(baseEnable, OUTPUT); //Define baseEnable pin used by Arduino to enable Base Controller -} - -P1AM P1; //Create Class instance - -/******************************************************************************* -Description: Initialise the P1AM-100 Base Controller. It automatically configures each module - with default settings and returns the number of modules that have signed on. - -Parameters: -None - -Returns: -uint8_t - Number of good modules that signed on. -*******************************************************************************/ -uint8_t P1AM::init() { - uint32_t slots = 0; - int dbLoc = 0; - char *cfgArray; - uint8_t di_bytes = 0; // Number of DI Bytes - uint8_t do_bytes = 0; // Number of DO Bytes - uint8_t ai_bytes = 0; // Number of AI Bytes - uint8_t ao_bytes = 0; // Number of AO Bytes - uint8_t st_bytes = 0; // Number of Status Bytes - uint8_t cfg_bytes; // Number of CFG Bytes - union moduleIDs{ // Use a union as a quick convert between byte arrays and ints - uint32_t *IDs; - uint8_t *byteArray; - }modules; - uint8_t retry = 0; - - - memset(baseSlot,0,NUMBER_OF_MODULES); //Clear base constants array - enableBaseController(HIGH); //Start base controller - delay(100); - - if(spiTimeout(1000*5000) == false){ - Serial.println("No Base Controller Activity"); - Serial.println("Check External Supply Connection"); - delay(1000); - return 0; - } - - while(((slots == 0) || (slots > 15)) && (retry < 5)){ //Begin sign-on until number of slots are valid. Only retry 5 times. - if(handleHDR(MOD_HDR)){ - delay(5); - slots = spiSendRecvByte(DUMMY); //Get number of modules in base - if(slots == 0 || slots > 15){ - if(retry > 2){ - enableBaseController(LOW); //Disable base controller - delay(10); - enableBaseController(HIGH); //Start base controller - delay(10); - } - retry++; //Let Base Controller retry - } - } - } - - if(retry >= 5){ //Zero module in the base. Quit sign-on routine and let the user know - Serial.println("Zero modules in the base"); - delay(500); - return 0; - } - - modules.IDs = (uint32_t *)malloc(4*slots); //reserve memory for IDs - modules.byteArray = (uint8_t *)malloc(4*slots); - - spiTimeout(1000*200); - spiSendRecvBuf(modules.byteArray,slots*4,1); //slots * 4 bytes per ID code - - uint8_t *baseControllerConstants = (uint8_t *)malloc(1 * slots * 7);//seven elements in module sign on - for(int i=0;i 0){ //Modules with config Bytes need to havea config loaded - cfgArray = loadConfigBuf(mdb[dbLoc].moduleID); //Get pointer to default config for this module - while(!configureModule(cfgArray, i + 1)){ //configure module - debugPrintln("Working"); - } - } - } - #endif - - delay(50); //Let the Base Controller complete its end of the sign-on - #ifdef DEBUG_PRINT_ON - slots = printModules(); //Returns the number of good modules. Prints IDs to serial monitor - #endif - return slots; -} - -/******************************************************************************* -Description: Enables or disables base controller during normal operation. - -Parameters: -bool state - On or off control - -Returns: -none -*******************************************************************************/ -void P1AM::enableBaseController(bool state){ - - digitalWrite(baseEnable, state); - -} - -/******************************************************************************* -Description: Checks to see if modules in base appear as expected. Disables any modules - that are found not to comply. This function only works after init. - -Parameters: -char* moduleNames[] - List of Module names in order they appear in base - -Returns: -uint16_t - Bitmapped representation of errors. A one in any position - represents a error in configuration. E.g. an error in - slot 1 and 3 would return 0x05. -*******************************************************************************/ -uint16_t P1AM::rollCall(const char* moduleNames[], uint8_t numberOfModules){ - uint8_t numberGoodPresent = 0; - uint16_t slotError = 0; - uint8_t dbLoc = 0; - bool singleError; - - - for(int i = 0;i < numberOfModules;i++){ - dbLoc = baseSlot[i].dbLoc; - - singleError = (moduleNames[i] != mdb[dbLoc].moduleName);//Set to true if there is an error - slotError = slotError | (singleError << i); //Shift bit of error check and add to our return - if(singleError == true){ - debugPrint("Slot "); - debugPrint(i+1); - debugPrintln(" Module Mismatch"); - - debugPrint("Expected: "); - debugPrintln(moduleNames[i]); - - debugPrint("Found: "); - debugPrintln(mdb[dbLoc].moduleName); - - debugPrintln(); //CRLF for next message - - baseSlot[i].dbLoc = 0; //Clear out slot so it no longer functions - } - } - - if(slotError == 0){ - debugPrintln("All Modules Good"); - } - - return slotError; - -} - - -/******************************************************************************* -Description: Read a single discrete input module - -Parameters: -uint8_t slot - Slot to read from. Slots start at 1. - -(Optional) uint8_t channel = 0. If specified reads from "channel". - If left out or 0, reads "data" from all channels where - least Signficant bit is channel 1. - -Returns: -uint32_t data read from the module -*******************************************************************************/ -uint32_t P1AM::readDiscrete(uint8_t slot, uint8_t channel){ - uint32_t data = 0; - uint8_t len = 0; - uint8_t mdbLoc = 0; - char rData[4] = {0,0,0,0}; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].diBytes; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return 0; - } - - if((len <= 0)){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no Discrete Input bytes"); - return 0; - } - - if(channel > (len * 8)){ //8 channels per byte - debugPrintln("This channel is not valid"); - return 0; - } - - rData[0] = READ_DISCRETE_HDR; - rData[1] = slot; - spiSendRecvBuf((uint8_t *)rData,2); - memset(rData,0,4); //clear buffer - if(spiTimeout(1000*200) == true){ - spiSendRecvBuf((uint8_t *)rData,len,true); - data = (rData[3]<<24); - data += (rData[2]<<16); - data += (rData[1]<<8); - data += (rData[0]<<0); - - if(channel != 0){ - data = (data>>(channel-1)) & 1; // shift and mask - } - dataSync(); - return data; - } - else{ - debugPrintln("Slow read"); - delay(100); - return 0; - } -} - -/******************************************************************************* -Description: Write to single discrete output module - -Parameters: -uint32_t data - Data to write to module - -uint8_t slot - Slot to write to. Slots start at 1. - -(Optional) uint8_t channel = 0. If specified writes to "channel". - If left out or 0, writes "data" to all channels where - least Signficant bit is channel 1. - -Returns: -None -*******************************************************************************/ -void P1AM::writeDiscrete(uint32_t data,uint8_t slot, uint8_t channel){ - uint8_t tData[7]; - uint8_t mdbLoc = 0; - uint8_t len = 0; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].doBytes; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if((len <= 0)){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no Discrete Output bytes"); - return; - } - - if(channel > (len * 8)){ //8 channels per byte - debugPrintln("This channel is not valid"); - return; - } - - tData[0] = WRITE_DISCRETE_HDR; - tData[1] = slot; - tData[2] = channel; - - if(channel == 0){ - for(int i=0;i>(8*i) & 0xFF; //Shift and mask - } - } - else{ - tData[3] = data & 0b1; //mask bit - len = 1; //only send 1 byte - } - - - spiSendRecvBuf(tData,len+3); //3 Header bytes plus data length - - dataSync(); - return; -} - -/******************************************************************************* -Description: Read a single analog input module channel. - -Parameters: -uint8_t slot - Slot to read from. Slots start at 1. - -uint8_t channel - Channel to read from. Channels start at 1. - -Returns: -uint32_t - Value of channel in counts. 12-bit returns 0-4095, - 16-bit returns between 0-65535, etc. -*******************************************************************************/ -int P1AM::readAnalog(uint8_t slot, uint8_t channel){ - int data = 0; - uint8_t len = 0; - uint8_t mdbLoc = 0; - char rData[4] = {0,0,0,0}; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].aiBytes; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return 0; - } - - if((len <= 0)){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no Analog Input bytes"); - return 0; - } - - if((channel <= 0) || (channel > (len / 4))){ //4 bytes per channel - debugPrintln("This channel is not valid"); - return 0; - } - - rData[0] = READ_ANALOG_HDR; - rData[1] = slot; - rData[2] = channel; - spiSendRecvBuf((uint8_t *)rData,3); - - if(spiTimeout(1000*200) == true){ - data = spiSendRecvInt(DUMMY); - dataSync(); - return data; - } - else{ - debugPrintln("Slow read"); - delay(100); - return 0; - } -} - -/******************************************************************************* -Description: Read a single temperature input module channel. - -Parameters: -uint8_t slot - Slot to read from. Slots start at 1. - -uint8_t channel - Channel to read from. Channels start at 1. -Returns: -Value of channel in degrees for temperature. mV for voltages. -*******************************************************************************/ -float P1AM::readTemperature(uint8_t slot, uint8_t channel){ - union int2float{ - int data; //We get an int back no matter what analog module. Unions set variables as the same/ - float temperature; //Floats and Ints are both 32 bits, so this lets quickly convert our int to a float. - }ourValue; - - ourValue.data = readAnalog(slot,channel); //Use the same analog read function to get int - - return ourValue.temperature; //return the float -} - -/******************************************************************************* -Description: Write to a single analog output module channel. - -Parameters: -uint32_t data - Value of channel in counts. 12-bit in range 0-4095, - 16-bit in range 0-65535, etc. - -uint8_t slot - Slot to write to. Slots start at 1. - -uint8_t channel - Channel to write to. Channels start at 1. -Returns: -None -*******************************************************************************/ -void P1AM::writeAnalog(uint32_t data,uint8_t slot, uint8_t channel){ - uint8_t tData[7]; - uint8_t mdbLoc = 0; - uint8_t len = 0; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].aoBytes; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if((len <= 0)){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no Analog Output bytes"); - return; - } - - if((channel <= 0) || (channel > (len / 4))){ //4 bytes per channel - debugPrintln("This channel is not valid"); - return; - } - - tData[0] = WRITE_ANALOG_HDR; - tData[1] = slot; - tData[2] = channel; - tData[3] = (data>>0) & 0xFF; - tData[4] = (data>>8) & 0xFF; - tData[5] = (data>>16) & 0xFF; - tData[6] = (data>>24) & 0xFF; - spiSendRecvBuf(tData,7); - - dataSync(); - return; -} - -/******************************************************************************* -Description: Read a block of data stored in Base Controller. This allows you to read data - from many modules in one command, but requires you to calculate the - offset and lengths and later parse out the data. You can also read - the state of output data through this for debugging purposes. This - function is more advanced and is not recommended for new programmers. - -Parameters: -char buf[] - Pointer to an array that will hold the values read. This - function does not return a value, but stores in the *buf passed in. - -uint16_t len - Number of bytes to read - -uint8_t offset - Starting byte in array to read. - -uint8_t type - Specifies which Base Controller data block to read from. - 0 is Discrete Input, 1 is Analog Input, 2 is Discrete Output, 3 is Analog Output,4 is Status. - -Returns: -None -*******************************************************************************/ -void P1AM::readBlockData(char buf[], uint16_t len,uint16_t offset, uint8_t type){ - uint8_t readParams[6]; - - if((len+offset) > 1200){ //max of data array is 1200, so we can't read past that - len = 1200-offset; //adjust len in case we're trying to read too far - } - - readParams[0] = READ_BLOCK_HDR; - readParams[1] = type; // 0 is Discrete In, 1 is Analog In, 2 is Discrete Out, 3 is Analog Out,4 is Status - readParams[2] = len >> 8; - readParams[3] = len & 0xFF; - readParams[4] = offset >> 8; - readParams[5] = offset & 0xFF; - spiSendRecvBuf(readParams,6); //Send paramters for block read - - if(spiTimeout(1000*200) == true){ - spiSendRecvBuf((uint8_t *)buf,len,true); //data is stored in buffer passed in - dataSync(); - return; - } - else{ - debugPrintln("Slow block read"); - delay(100); - return; - } -} - -/******************************************************************************* -Description: Write to a block of data stored in Base Controller. This allows you to write data - to many modules in one command, but requires you to calculate the - offset and lengths and parse together the data. You can write to input data, - but be aware the Base Controller may overwrite this data in normal operations. - This function is more advanced than others and is not recommended - for new programmers. - -Parameters: -char buf[] - Pointer to an array that holds the values to write. This - array is not overwritten. - -uint16_t len - Number of bytes to write - -uint8_t offset - Starting byte in array to write. - -uint8_t type - Specifies which Base Controller data block to write to. - 0 is Discrete Input, 1 is Analog Input, 2 is Discrete Output, 3 is Analog Output,4 is Status. - -Returns: -None -*******************************************************************************/ -void P1AM::writeBlockData(char buf[], uint16_t len,uint16_t offset, uint8_t type){ - - if((len+offset) > 1200){ //max of data array is 1200, so we can't read past that - len = 1200-offset; //adjust len in case we're trying to read too far - } - - uint8_t *tData = (uint8_t *)malloc(len+6); - tData[0] = WRITE_BLOCK_HDR; - tData[1] = type; - tData[2] = len >> 8; - tData[3] = len & 0xFF; - tData[4] = offset >> 8; - tData[5] = offset & 0xFF; - - memcpy(tData+6,buf,len); - spiSendRecvBuf(tData,len+6,0); //data is not stored in passed buffer - free(tData); - - dataSync(); - return; -} - -/******************************************************************************* -Description: Set both duty cycle and frequency of a PWM output module channel. - -Parameters: -float duty - Duty cycle. Range 0.00-100.00. You can include up to 2 decimal places - -uint32_t freq - Frequency. See module data sheet for range. P1-04PWM is 0-20kHz. - -uint8_t slot - Slot to write to. Slots start at 1. - -uint8_t channel - Channel to write to. Channels start at 1. - -Returns: -None -*******************************************************************************/ -void P1AM::writePWM(float duty,uint32_t freq,uint8_t slot,uint8_t channel){ - uint8_t mdbLoc = 0; - uint8_t tempLoc = 0; - uint8_t offset = 0; - uint32_t dutyInt = 0; - char tData[8]; - - mdbLoc = baseSlot[slot-1].dbLoc; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if((channel <= 0) || (channel > 4)){ - debugPrintln("This channel is not valid"); - return; - } - - if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module is not a PWM module"); - return; //Not PWM - } - - for(int i=0;i 0){ - offset += mdb[tempLoc].aoBytes; //get offset of analog bytes - } - } - - offset += (channel - 1) * 8; //Each channel uses 8 bytes - dutyInt = (uint32_t)(duty * 100);// shift decimal over 2 places and cast off remainder. e.g. 12.3456 turns into 1234 - - tData[3] = (dutyInt>>0) & 0xFF; //shift and mask to bytes - tData[2] = (dutyInt>>8) & 0xFF; - tData[1] = (dutyInt>>16) & 0xFF; - tData[0] = (dutyInt>>24) & 0xFF; - tData[7] = (freq>>0) & 0xFF; - tData[6] = (freq>>8) & 0xFF; - tData[5] = (freq>>16) & 0xFF; - tData[4] = (freq>>24) & 0xFF; - - writeBlockData(tData, 8, offset, ANALOG_OUT_BLOCK); //Use block data to ensure duty/freq are entered at the same time. - dataSync(); - - return; -} - -/******************************************************************************* -Description: Set duty cycle of a PWM output module channel. - -Parameters: -float duty - Duty cycle. Range 0.00-100.00. You can include up to 2 decimal places - -uint8_t slot - Slot to write to. Slots start at 1. - -uint8_t channel - Channel to write to. Channels start at 1. - -Returns: -None -*******************************************************************************/ -void P1AM::writePWMDuty(float duty,uint8_t slot,uint8_t channel){ - uint8_t mdbLoc = 0; - uint32_t dutyInt = 0; - - mdbLoc = baseSlot[slot-1].dbLoc; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if((channel <= 0) || (channel > 4)){ - debugPrintln("This channel is not valid"); - return; - } - - if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module is not a PWM module"); - return; //Not PWM - } - - dutyInt = (uint32_t)(duty * 100);// shift decimal over 2 places and cast off remainder. e.g. 12.3456 turns into 1234 - channel = 1 + ((channel-1) * 2); - P1.writeAnalog(dutyInt,slot,channel); - - dataSync(); - return; -} - -/******************************************************************************* -Description: Set frequency of a PWM output module channel. - -Parameters: -uint32_t freq - Frequency. See module data sheet for range. P1-04PWM is 0-20kHz. - -uint8_t slot - Slot to write to. Slots start at 1. - -uint8_t channel - Channel to write to. Channels start at 1. - -Returns: -None -*******************************************************************************/ -void P1AM::writePWMFreq(uint32_t freq,uint8_t slot,uint8_t channel){ - uint8_t mdbLoc = 0; - - mdbLoc = baseSlot[slot-1].dbLoc; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if((channel <= 0) || (channel > 4)){ - debugPrintln("This channel is not valid"); - return; - } - - if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM - debugPrint("Slot "); - debugPrint(slot); - debugPrintln("This module is not a PWM module"); - return; //Not PWM - } - - channel = 2 + ((channel-1) * 2); - P1.writeAnalog(freq,slot,channel); - - dataSync(); - return; -} - - /******************************************************************************* -Description: Set direction of a PWM module channel when set to DIR mode. - See configuration document to see how to enable this feature. - -Parameters: -bool data - Set channel on or off - -uint8_t slot - Slot to write to. Slots start at 1. - -uint8_t channel - Channel to write to. Channels start at 1. - -Returns: -None -*******************************************************************************/ -void P1AM::writePWMDir(bool data,uint8_t slot, uint8_t channel){ - uint8_t mdbLoc = 0; - - mdbLoc = baseSlot[slot-1].dbLoc; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if((channel <= 0) || (channel > 4)){ - debugPrintln("This channel is not valid"); - return; - } - - if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module is not a PWM module"); - return; //Not PWM - } - - channel = 1 + ((channel-1) * 2); - P1.writeAnalog(data,slot,channel); - - dataSync(); - return; -} - - -/******************************************************************************* -Description: Print the names of all the modules in the base to the serial monitor - -Parameters: -None - -Returns: -Number of good sign-ons -*******************************************************************************/ -uint8_t P1AM::printModules(){ - uint32_t slot = 0; - uint8_t dbLoc = 0; - uint8_t goodSlots = 0; - - while(baseSlot[slot].dbLoc != 0){ - dbLoc = baseSlot[slot].dbLoc; - - if(mdb[dbLoc].moduleID == 0 || mdb[dbLoc].moduleID == 0xFFFFFFFF){ - Serial.print("Slot "); - Serial.print(slot + 1); - Serial.println(": Sign On Error"); - } - else{ - Serial.print("Slot "); - Serial.print(slot + 1); - Serial.print(": "); - Serial.println(mdb[dbLoc].moduleName); - goodSlots++; - } - slot++; - } - dataSync(); - Serial.println(""); //Print an empty line to give us some breathing room later - return goodSlots; -} - - -/******************************************************************************* -Description: Return the module properties for a given slot - -Parameters: -uint8_t slot - Slot of which you want the module parameters. - Slot 1 is module closest to processor. - -Returns: -moduleProps - moduleProps structure containing the specific paramters - at the give slot. -*******************************************************************************/ -moduleProps P1AM::readSlotProps(uint8_t slot){ - - uint8_t dbLoc = 0; - moduleProps _slotProps; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - _slotProps = mdb[0]; - return _slotProps; - } - - if (baseSlot[slot-1].dbLoc != 0) { - dbLoc = baseSlot[slot-1].dbLoc; - _slotProps = mdb[dbLoc]; - } - else { - _slotProps = mdb[0]; - } - return _slotProps; -} - -/******************************************************************************* -Description: Read a single status byte from a single module - -Parameters: -int byteNum - Which status byte to read in. Byte numbering starts at 0. - -int slot - Which slot to read from - -Returns: -char - The status byte. -*******************************************************************************/ -char P1AM::readStatus(int byteNum,int slot){ - uint8_t len = 0; - uint8_t mdbLoc = 0; - uint8_t buf[1]; - uint8_t rData[4]; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = 1; //only need 1 byte - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return 0; - } - - if(mdb[mdbLoc].statusBytes <= 0){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no Status bytes"); - return 0; //No status Bytes - } - - rData[0] = READ_STATUS_HDR; - rData[1] = slot; - rData[2] = len; - rData[3] = byteNum;//offset - spiSendRecvBuf((uint8_t *)rData,4); - - if(spiTimeout(1000*200) == true){ - spiSendRecvBuf((uint8_t *)buf,len,true); //data is stored in buffer passed in - dataSync(); - return buf[0]; - } - else{ - debugPrintln("Slow status read"); - delay(100); - return 0; - } -} - -/******************************************************************************* -Description: Read all status bytes from a single module - -Parameters: -char buf[] - Array to store the read status bytes in. This function - does not return a value, but places it in the array passed in - -uint8_t slot - Slot that you want to read status bytes from - -Returns: -None -*******************************************************************************/ -void P1AM::readStatus(char buf[], uint8_t slot){ - uint8_t len = 0; - uint8_t mdbLoc = 0; - uint8_t rData[4]; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].statusBytes; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if(len <= 0){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no Status bytes"); - return; //No status Bytes - } - - rData[0] = READ_STATUS_HDR; - rData[1] = slot; - rData[2] = len; - rData[3] = 0;//offset - spiSendRecvBuf((uint8_t *)rData,4); - - if(spiTimeout(1000*200) == true){ - spiSendRecvBuf((uint8_t *)buf,len,true); //data is stored in buffer passed in - dataSync(); - return; - } - else{ - debugPrintln("Slow status read"); - delay(100); - return; - } -} - -/******************************************************************************* -Description: Checks the under range error on select P1000 modules. Returns if channel - is under range. This function does not check if the module supports - this feature. - -Parameters: -uint8_t slot - Which slot to read from. - -(Optional) uint8_t channel = 0. If value is non-zero checks the - specified channel. If left out or 0, checks all channels where - least Signficant bit is the status of channel 1. - - -Returns: -uint8_t - returns under range status -*******************************************************************************/ -uint8_t P1AM::checkUnderRange(uint8_t slot, uint8_t channel){ - uint8_t statusByte = 0; - - statusByte = readStatus(UNDER_RANGE_STATUS,slot); - if(channel){ - statusByte = (statusByte >> (channel - 1)) & 1; //shift and mask bits - } - - return statusByte; -} - -/******************************************************************************* -Description: Checks the over range error on select P1000 modules. Returns if channel - is over range. This function does not check if the module supports - this feature. - -Parameters: -uint8_t slot - Which slot to read from. - -(Optional) uint8_t channel = 0. If value is non-zero checks the - specified channel. If left out or 0, checks all channels where - least Signficant bit is the status of channel 1. - - -Returns: -uint8_t - returns over range status -*******************************************************************************/ -uint8_t P1AM::checkOverRange(uint8_t slot, uint8_t channel){ - uint8_t statusByte = 0; - - statusByte = readStatus(OVER_RANGE_STATUS,slot); - if(channel){ - statusByte = (statusByte >> (channel - 1)) & 1; //shift and mask bits. - } - - return statusByte; -} - -/******************************************************************************* -Description: Checks the burnout error on select P1000 modules. Returns if channel - is over range. This function does not check if the module supports - this feature. - -Parameters: -uint8_t slot - Which slot to read from. - -(Optional) uint8_t channel = 0. If value is non-zero checks the - specified channel. If left out or 0, checks all channels where - least Signficant bit is the status of channel 1. - - -Returns: -uint8_t - returns burnout status -*******************************************************************************/ -uint8_t P1AM::checkBurnout(uint8_t slot, uint8_t channel){ - uint8_t statusByte = 0; - - statusByte = readStatus(BURNOUT_STATUS,slot); - if(channel){ - statusByte = (statusByte >> (channel - 1)) & 1; //shift and mask bits - } - - return statusByte; -} - -/******************************************************************************* -Description: Checks the lost 24V error on select P1000 modules. Returns 1 if slot is missing - 24V. This function does not check if the module supports this feature. - -Parameters: -uint8_t slot - Which slot to read from. - -Returns: -uint8_t - 1 if 24V is missing. 0 if 24V is present -*******************************************************************************/ -uint8_t P1AM::check24V(uint8_t slot){ - uint8_t statusByte = 0; - - statusByte = readStatus(MISSING24V_STATUS,slot); - statusByte = (statusByte >> 1) & 1; //Mask and shift for 24V bit - - return statusByte; -} - -/******************************************************************************* -Description: Manually configure a module. Use this if you want to use a setting - that is not the default for a module. - Visit https://facts-engineering.github.io/config.html for more information - -Parameters: -uint8_t slot - Slot you want to configure. - -char *cfgData - Pointer to array that contains the configuration settings - -Returns: -bool - Configuration successfully written to Base Controller. -*******************************************************************************/ -bool P1AM::configureModule(char cfgData[], uint8_t slot){ - uint8_t len = 0; - uint8_t mdbLoc = 0; - uint8_t cfgForSpi[66]; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].configBytes + 2; - cfgForSpi[0] = CFG_HDR; - cfgForSpi[1] = slot; - - memcpy(cfgForSpi+2,cfgData,len); - delay(1); - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return 0; - } - - if(len <= 0){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no config Bytes"); - return 0; - } - - spiSendRecvBuf(cfgForSpi,len); - delay(100); //Additional time for Config to written - dataSync(); - dataSync(); - return 1; -} - -/******************************************************************************* -Overload of above function for const arrays -*******************************************************************************/ -bool P1AM::configureModule(const char cfgData[], uint8_t slot){ - - return configureModule((char*)cfgData, slot); - -} - -/******************************************************************************* -Description: Read current configuration of a module. Data is stored in the passed - in array pointer. - - -Parameters: -char *cfgData - Pointer to array that will receive the current configuration - -uint8_t slot - Slot you want to configure. - -Returns: -none -*******************************************************************************/ -void P1AM::readModuleConfig(char cfgData[], uint8_t slot){ - uint8_t len = 0; - uint8_t mdbLoc = 0; - uint8_t cfgForSpi[2]; - - mdbLoc = baseSlot[slot-1].dbLoc; - len = mdb[mdbLoc].configBytes; - - if((slot < 1) || (slot > 15)){ - debugPrintln("Slots must be between 1 and 15"); - return; - } - - if(len <= 0){ - debugPrint("Slot "); - debugPrint(slot); - debugPrintln(": This module has no config Bytes"); - return; - } - - cfgForSpi[0] = READ_CFG_HDR; - cfgForSpi[1] = slot; - spiSendRecvBuf((uint8_t *)cfgForSpi,2); - - if(spiTimeout(1000*200) == true){ - spiSendRecvBuf((uint8_t *)cfgData,len,true); //data is stored in buffer passed in - dataSync(); - return; - } - else{ - debugPrintln("Slow config read"); - delay(100); - return; - } -} - -/******************************************************************************* -Description: Configures the behaviour of the watchdog timer. - -Parameters: -uint16_t milliseconds - Time in milliseconds in range 0-65535. - If any P1AM-100 function is not called before the time elapses, - the Base Controller will toggle the reset pin on the P1AM-100. - -Returns: -None -*******************************************************************************/ -void P1AM::configWD(uint16_t milliseconds, uint8_t toggle) { - uint8_t lowByte; - uint8_t highByte; - uint8_t lowToggle; - uint8_t highToggle; - uint16_t toggleTime = 100; - uint8_t wdData[6]; - - lowByte = (uint8_t) milliseconds; - highByte = (uint8_t) (milliseconds >> 8); - - lowToggle = (uint8_t) toggleTime; - highToggle = (uint8_t) (toggleTime >> 8); - - wdData[0] = CONFIGWD_HDR; - wdData[1] = lowByte; - wdData[2] = highByte; - wdData[3] = lowToggle; - wdData[4] = highToggle; - wdData[5] = toggle & 0x01; - - spiSendRecvBuf(wdData,6); - dataSync(); - return; -} - -/******************************************************************************* -Description: Begin the watchdog timer. If the watchdog is not configured beforehand, - Base Controller WD will default to fullscale (65535) timer. If the Base Controller is not - written to within the time period, the Base Controller will reset the P1AM-100. - -Parameters: -None - -Returns: -None -*******************************************************************************/ -void P1AM::startWD() { - - spiSendRecvByte(STARTWD_HDR); //Send "StartWD" header to Base Controller - if(spiTimeout(1000*200) == true){ - spiSendRecvByte(DUMMY); //Send "PetWD" header to Base Controller - dataSync(); - } - return; -} - -/******************************************************************************* -Description: Stops the watchdog timer from running. Use this is you have code - that takes a long time to execute and you are unable to add petWD - calls to. - -Parameters: -None - -Returns: -None -*******************************************************************************/ -void P1AM::stopWD() { - - spiSendRecvByte(STOPWD_HDR); //Send "StopWD" header to Base Controller - if(spiTimeout(1000*200) == true){ - spiSendRecvByte(DUMMY); //Send "PetWD" header to Base Controller - dataSync(); - } - return; -} - -/******************************************************************************* -Description: Pets the watchdog i.e. resets the timer so it does not trigger. Any - P1AM-100 function will pet the watchdog, but this function can be - used in long sections of code or slow loops where the other P1AM-100 - functions are not called. - -Parameters: -None - -Returns: -None -*******************************************************************************/ -void P1AM::petWD() { - - spiSendRecvByte(PETWD_HDR); //Send "PetWD" header to Base Controller - if(spiTimeout(1000*200) == true){ - spiSendRecvByte(DUMMY); //Send "PetWD" header to Base Controller - dataSync(); - } - return; -} - -/******************************************************************************* -Description: Print out current Base Controller firmware version to serial monitor - -Parameters: -None - -Returns: -uint32_t - Firmware Version byte format X.Y.ZZ -*******************************************************************************/ -uint32_t P1AM::getFwVersion(){ - uint32_t version = 0; - - if(handleHDR(VERSION_HDR)){ - version = spiSendRecvInt(DUMMY); - debugPrint("FW V"); - debugPrint(version>>12); - debugPrint("."); - debugPrint(version>>8 & 0xF); - debugPrint("."); - debugPrintln(version & 0xFF); - dataSync(); - return version; - } - else{ - delay(100); - return 0; - } -} - -/******************************************************************************* -Description: Flag to see if the Base Controller has been initialised and is running - -Parameters: -None - -Returns: -Bool - Returns true if Base Controller has been intialised and is active -*******************************************************************************/ -bool P1AM::isBaseActive(){ - bool isActive = false; - - spiSendRecvByte(ACTIVE_HDR); - if(spiTimeout(1000*200) == true){ - isActive = spiSendRecvByte(DUMMY); - } - dataSync(); - return isActive; -} - -/******************************************************************************* -Description: Check to see if any modules that signed on during init have since - dropped out. - -Parameters: -uint8_t numberOfModules - Number of modules expected. Useful if a module - never signed on to begin with. If ommitted this function will only - check for the number of modules that signed on during the last init - - -Returns: -uint8_t - Returns the slot number of the leftmost module in the base - that is no longer functioning. -*******************************************************************************/ -uint8_t P1AM::checkConnection(uint8_t numberOfModules){ - uint8_t expectedSlots = 0; - uint16_t activeSlots = 0; - uint8_t badSlot = 0; - - if(!numberOfModules){ - while(baseSlot[expectedSlots].dbLoc != 0){ - expectedSlots++; //Count number of slots that signed on during init - } - } - else{ - expectedSlots = numberOfModules; - } - - spiSendRecvByte(DROPOUT_HDR); - if(spiTimeout(1000*200) == true){ - activeSlots = spiSendRecvByte(DUMMY); //Get value of active slots. Bitmapped representation. - activeSlots |= spiSendRecvByte(DUMMY) << 8; - } - dataSync(); - - for(int i=0;i> i) == 0){ - return i+1; //Leftmost slot with numbering starting at 1 - } - } - - return 0; //No bad slots found -} - -/******************************************************************************* -Label Functions - These use the channelLabel type defined in P1AM.h instead of -passing slot and channel individually. These Turn P1.readDiscrete(1,1) into -P1.readDiscrete(sens_1). They are functionally the same, so if you want to gain -a better understanding of the inner workings, please check out the actual -function code in the above portion of this library. -*******************************************************************************/ -uint32_t P1AM::readDiscrete(channelLabel label){ - return readDiscrete(label.slot,label.channel); -} - -void P1AM::writeDiscrete(uint32_t data, channelLabel label){ - writeDiscrete(data,label.slot,label.channel); - return; -} - -int P1AM::readAnalog(channelLabel label){ - return readAnalog(label.slot,label.channel); -} - -float P1AM::readTemperature(channelLabel label){ - return readTemperature(label.slot,label.channel); -} - -void P1AM::writeAnalog(uint32_t data, channelLabel label){ - writeAnalog(data,label.slot,label.channel); - return; -} - -void P1AM::writePWM(float duty, uint32_t freq, channelLabel label){ - writePWM(duty,freq,label.slot,label.channel); - return; -} - -void P1AM::writePWMDuty(float duty, channelLabel label){ - writePWMDuty(duty,label.slot,label.channel); - return; -} -void P1AM::writePWMFreq(uint32_t freq, channelLabel label){ - writePWMFreq(freq,label.slot,label.channel); - return; -} - -void P1AM::writePWMDir(bool data, channelLabel label){ - writePWMDir(data,label.slot,label.channel); - return; -} - -uint8_t P1AM::checkUnderRange(channelLabel label){ - return checkUnderRange(label.slot,label.channel); -} - -uint8_t P1AM::checkOverRange(channelLabel label){ - return checkOverRange(label.slot,label.channel); -} - -uint8_t P1AM::checkBurnout(channelLabel label){ - return checkBurnout(label.slot,label.channel); -} - -/******************************************************************************* -PRIVATE FUNCTIONS FOR P1AM.h -*******************************************************************************/ -void P1AM::dataSync(){ - uint32_t currentMillis = 0; - uint32_t startMillis = 0; - - - startMillis = millis(); - while(!digitalRead(slaveAckPin)){ - currentMillis = millis(); - if(currentMillis - startMillis >= 200){ - debugPrintln("Base Sync Timeout"); - break; - } - } - delayMicroseconds(1); - - startMillis = millis(); - while(digitalRead(slaveAckPin)){ - currentMillis = millis(); - if(currentMillis - startMillis >= 200){ - debugPrintln("Base Sync Timeout"); - break; - } - } - delayMicroseconds(1); - - startMillis = millis(); - while(!digitalRead(slaveAckPin)){ - currentMillis = millis(); - if(currentMillis - startMillis >= 200){ - debugPrintln("Base Sync Timeout"); - break; - } - } - delayMicroseconds(1); - - return; -} - -char *P1AM::loadConfigBuf(int moduleID){ - char *defaultCfg; - - switch(moduleID){ - case 0x34605582: - return (char*)P1_04AD_1_DEFAULT_CONFIG; - case 0x34605583: - return (char*)P1_04AD_2_DEFAULT_CONFIG; - case 0x34605590: - return (char*)P1_04ADL_2_DEFAULT_CONFIG; - case 0x34608C8E: - return (char*)P1_04NTC_DEFAULT_CONFIG; - case 0x34608C81: - return (char*)P1_04THM_DEFAULT_CONFIG; - case 0x34605588: - return (char*)P1_04RTD_DEFAULT_CONFIG; - case 0x34605581: - return (char*)P1_04AD_DEFAULT_CONFIG; - case 0x3460558F: - return (char*)P1_04ADL_1_DEFAULT_CONFIG; - case 0x34A0558A: - return (char*)P1_08ADL_1_DEFAULT_CONFIG; - case 0x34A0558B: - return (char*)P1_08ADL_2_DEFAULT_CONFIG; - case 0x5461A783: - return (char*)P1_04ADL2DAL_1_DEFAULT_CONFIG; - case 0x5461A784: - return (char*)P1_04ADL2DAL_2_DEFAULT_CONFIG; - case 0x1403F481: - return (char*)P1_04PWM_DEFAULT_CONFIG; - case 0x34A5A481: - return (char*)P1_02HSC_DEFAULT_CONFIG; - default: - break; - } -} - -bool P1AM::handleHDR(uint8_t HDR){ - - while(!digitalRead(slaveAckPin)); //Wait for Base Controller to be out of base scanning - spiSendRecvByte(HDR); //Send intital Header to ping DMA - - return spiTimeout(MAX_TIMEOUT,HDR,2000); //1 if we got Base Controller ack, 0 if we took too long -} - -uint8_t P1AM::spiSendRecvByte(uint8_t data){ - - SPI.begin(); - SPI.beginTransaction(P100_SPI_SETTINGS); - digitalWrite(slaveSelectPin, LOW); - data = SPI.transfer(data); - digitalWrite(slaveSelectPin, HIGH); - SPI.endTransaction(); - SPI.end(); - - return data; -} - -uint32_t P1AM::spiSendRecvInt(uint32_t data){ - uint8_t rData[4]; - uint8_t tData[4]; - uint32_t returnInt = 0; - - tData[0] = (data>>0) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[1] = (data>>8) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[2] = (data>>16) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[3] = (data>>24) & 0xFF; // data to write to the Base Controller - 1 byte long - - SPI.begin(); - SPI.beginTransaction(P100_SPI_SETTINGS); - digitalWrite(slaveSelectPin, LOW); - rData[0] = SPI.transfer(tData[0]); - rData[1] = SPI.transfer(tData[1]); - rData[2] = SPI.transfer(tData[2]); - rData[3] = SPI.transfer(tData[3]); - digitalWrite(slaveSelectPin, HIGH); - SPI.endTransaction(); - SPI.end(); - - returnInt = (rData[3]<<24); - returnInt += (rData[2]<<16); - returnInt += (rData[1]<<8); - returnInt += (rData[0]<<0); - - return returnInt; -} - -void P1AM::spiSendRecvBuf(uint8_t *buf, int len, bool returnData){ - - SPI.begin(); - SPI.beginTransaction(P100_SPI_SETTINGS); - digitalWrite(slaveSelectPin, LOW); - - if(returnData){ - for(int i = 0; i < len; ++i){ - buf[i] = SPI.transfer(buf[i]); //return what we get into our buffer - } - } - else{ - for(int i = 0; i < len; ++i){ - SPI.transfer(buf[i]); //or don't return what we get - } - } - - digitalWrite(slaveSelectPin, HIGH); - SPI.endTransaction(); - SPI.end(); - return; -} - -bool P1AM::spiTimeout(uint32_t uS,uint8_t resendMsg,uint16_t retryPeriod){ - uint16_t retryTime = 0; - - while((!digitalRead(slaveAckPin)) && (uS != 0)){ - delayMicroseconds(1); - uS--; - - retryTime++; - if((retryPeriod) && (retryTime == retryPeriod)){ // if we specified a retry period and it equals the time we've waited - - spiSendRecvByte(resendMsg); - retryTime = 0; - } - } - delayMicroseconds(50); //small delay to let Base Controller load next msg in buf - - if(uS > 0){ - return 1; - } - else{ - debugPrintln("Timeout"); - return 0; - } -} - -bool P1AM::Base_Controller_FW_UPDATE(unsigned int fwLen){ - extern unsigned char FW_IMG_Base_Controller[]; - //int fwLen = sizeof(FW_IMG_Base_Controller); - int chunkSize = 1000; - uint8_t status = 0; - uint8_t tData[9]; - - uint8_t *chunkBuffer = (uint8_t *)malloc(chunkSize); //Make spacs for FW chunks - - SPI.begin(); //Start SPI - Serial.println("Establishing Communication"); - - tData[0] = FW_UPDATE_HDR; - - tData[1] = (fwLen>>0) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[2] = (fwLen>>8) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[3] = (fwLen>>16) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[4] = (fwLen>>24) & 0xFF; // data to write to the Base Controller - 1 byte long - - tData[5] = (chunkSize>>0) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[6] = (chunkSize>>8) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[7] = (chunkSize>>16) & 0xFF; // data to write to the Base Controller - 1 byte long - tData[8] = (chunkSize>>24) & 0xFF; // data to write to the Base Controller - 1 byte long - - spiSendRecvBuf(tData,9); //send chunk - - - if(!spiTimeout(1000*200)){ - delay(100); - return 0; - } - if(spiSendRecvByte(DUMMY) != FW_UPDATE_HDR){ - delay(100); - return 0; - } - - delay(10); - Serial.println("FW Transfer Started"); - while(!digitalRead(slaveAckPin)); //wait for Base Controller to be ready - int fullLoops = fwLen/chunkSize; //How many full chunks - - int offset; - for ( offset = 0; offset <= fullLoops; offset++) - { - - Serial.print((float)100*offset/fullLoops,0);//load percentage - Serial.println("%"); - - memcpy(chunkBuffer,(FW_IMG_Base_Controller + offset*chunkSize),chunkSize); //copy image into buffer to send - while(!digitalRead(slaveAckPin)); //wait for Base Controller to be ready - spiSendRecvBuf(chunkBuffer,chunkSize); //send chunk - - - } - - //last chunk - chunkSize = fwLen % chunkSize; //get the remaining bytes smaller than 1 chunk - if(chunkSize != 0) - { - memcpy(chunkBuffer,(FW_IMG_Base_Controller + offset*chunkSize),chunkSize); //copy image into buffer to send - while(!digitalRead(slaveAckPin)); //wait for Base Controller to be ready - spiSendRecvBuf(chunkBuffer,chunkSize); - } - - Serial.println("100%"); - Serial.println("Hang on a sec"); - - while(!digitalRead(slaveAckPin)); - status = spiSendRecvByte(DUMMY); //CRC check on the image - - - if(status == 1) - { - delay(3000); //Good, give the Base Controller a few seconds to reboot - Serial.print("Update Complete! We're now at "); - getFwVersion(); //Print to confirm new FW is correct version - return 1; - } - - else if(status == 4) - { - Serial.println("Bad FW Checksum "); - Serial.println("FW Update FAIL :c"); - return 0; - } - - else if(status == 5) - { - Serial.println("Bad FW Hash "); - Serial.println("FW Update FAIL :c"); - return 0; - } - - else if(status == 9) - { - Serial.println("Bad FW Hash and CRC"); - Serial.println("FW Update FAIL :c"); - return 0; - } -} +/* +MIT License + +Copyright (c) 2023 FACTS Engineering, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "P1AM.h" + +SPISettings P100_SPI_SETTINGS(1000000, MSBFIRST, SPI_MODE2); + +P1AM::P1AM(){ + pinMode(slaveSelectPin, OUTPUT); //Define Slave select pin for Base Controller + pinMode(slaveAckPin, INPUT); //Define Ack pin for Base Controller + pinMode(baseEnable, OUTPUT); //Define baseEnable pin used by Arduino to enable Base Controller +} + +P1AM P1; //Create Class instance + +/******************************************************************************* +Description: Initialise the P1AM-100 Base Controller. It automatically configures each module + with default settings and returns the number of modules that have signed on. + +Parameters: -None + +Returns: -uint8_t - Number of good modules that signed on. +*******************************************************************************/ +uint8_t P1AM::init() { + uint32_t slots = 0; + int dbLoc = 0; + char *cfgArray; + const uint8_t max_p1_slots = 15; + + union moduleIDs{ // Use a union as a quick convert between byte arrays and ints + uint32_t IDs[max_p1_slots]; + uint8_t byteArray[max_p1_slots * 4]; + }modules; + uint8_t retry = 0; + + + memset(baseSlot,0,sizeof(baseSlot)); //Clear base constants array + enableBaseController(HIGH); //Start base controller + delay(100); + + if(spiTimeout(1000*5000) == false){ + Serial.println("No Base Controller Activity"); + Serial.println("Check External Supply Connection"); + delay(1000); + return 0; + } + + while(((slots == 0) || (slots > 15)) && (retry < 5)){ //Begin sign-on until number of slots are valid. Only retry 5 times. + if(handleHDR(MOD_HDR)){ + delay(5); + slots = spiSendRecvByte(DUMMY); //Get number of modules in base + if(slots == 0 || slots > 15){ + if(retry > 2){ + enableBaseController(LOW); //Disable base controller + delay(10); + enableBaseController(HIGH); //Start base controller + delay(10); + } + retry++; //Let Base Controller retry + } + } + } + + if(retry >= 5){ //Zero module in the base. Quit sign-on routine and let the user know + Serial.println("Zero modules in the base"); + delay(500); + return 0; + } + + if(slots > NUMBER_OF_MODULES){ + Serial.println("\nToo many modules in Base"); + Serial.print("Device only supports "); + Serial.println(NUMBER_OF_MODULES); + memset(baseSlot, 0, sizeof(baseSlot)); //Clear base constants array + return 0; + } + + spiTimeout(1000*200); + spiSendRecvBuf(modules.byteArray,slots*4,1); //slots * 4 bytes per ID code + + uint8_t baseControllerConstants[1 * max_p1_slots * 7]; //seven elements in module sign on + for(uint32_t i=0;i 0){ //Modules with config Bytes need to havea config loaded + cfgArray = loadConfigBuf(mdb[dbLoc].moduleID); //Get pointer to default config for this module + while(!configureModule(cfgArray, i + 1)){ //configure module + debugPrintln("Working"); + } + } + } + #endif + + delay(50); //Let the Base Controller complete its end of the sign-on + #ifdef DEBUG_PRINT_ON + slots = printModules(); //Returns the number of good modules. Prints IDs to serial monitor + #endif + return slots; +} + +/******************************************************************************* +Description: Enables or disables base controller during normal operation. + +Parameters: -bool state - On or off control + +Returns: -none +*******************************************************************************/ +void P1AM::enableBaseController(bool state){ + + digitalWrite(baseEnable, state); + +} + +/******************************************************************************* +Description: Checks to see if modules in base appear as expected. Disables any modules + that are found not to comply. This function only works after init. + +Parameters: -char* moduleNames[] - List of Module names in order they appear in base + +Returns: -uint16_t - Bitmapped representation of errors. A one in any position + represents a error in configuration. E.g. an error in + slot 1 and 3 would return 0x05. +*******************************************************************************/ +uint16_t P1AM::rollCall(const char* moduleNames[], uint8_t numberOfModules){ + uint16_t slotError = 0; + uint8_t dbLoc = 0; + bool singleError; + + + for(int i = 0;i < numberOfModules;i++){ + dbLoc = baseSlot[i].dbLoc; + + singleError = (moduleNames[i] != mdb[dbLoc].moduleName);//Set to true if there is an error + slotError = slotError | (singleError << i); //Shift bit of error check and add to our return + if(singleError == true){ + debugPrint("Slot "); + debugPrint(i+1); + debugPrintln(" Module Mismatch"); + + debugPrint("Expected: "); + debugPrintln(moduleNames[i]); + + debugPrint("Found: "); + debugPrintln(mdb[dbLoc].moduleName); + + debugPrintln(); //CRLF for next message + + baseSlot[i].dbLoc = 0; //Clear out slot so it no longer functions + } + } + + if(slotError == 0){ + debugPrintln("All Modules Good"); + } + + return slotError; + +} + + +/******************************************************************************* +Description: Read a single discrete input module + +Parameters: -uint8_t slot - Slot to read from. Slots start at 1. + -(Optional) uint8_t channel = 0. If specified reads from "channel". + If left out or 0, reads "data" from all channels where + least Signficant bit is channel 1. + +Returns: -uint32_t data read from the module +*******************************************************************************/ +uint32_t P1AM::readDiscrete(uint8_t slot, uint8_t channel){ + uint32_t data = 0; + uint8_t len = 0; + uint8_t mdbLoc = 0; + char rData[4] = {0,0,0,0}; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].diBytes; + + if((slot < 1) || (slot > NUMBER_OF_MODULES)){ + debugPrint("Slots must be between 1 and "); + debugPrintln(NUMBER_OF_MODULES); + return 0; + } + + if((len <= 0)){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no Discrete Input bytes"); + return 0; + } + + if(channel > (len * 8)){ //8 channels per byte + debugPrintln("This channel is not valid"); + return 0; + } + + rData[0] = READ_DISCRETE_HDR; + rData[1] = slot; + spiSendRecvBuf((uint8_t *)rData,2); + memset(rData,0,4); //clear buffer + if(spiTimeout(1000*200) == true){ + spiSendRecvBuf((uint8_t *)rData,len,true); + data = (rData[3]<<24); + data += (rData[2]<<16); + data += (rData[1]<<8); + data += (rData[0]<<0); + + if(channel != 0){ + data = (data>>(channel-1)) & 1; // shift and mask + } + dataSync(); + return data; + } + else{ + debugPrintln("Slow read"); + delay(100); + return 0; + } +} + +/******************************************************************************* +Description: Write to single discrete output module + +Parameters: -uint32_t data - Data to write to module + -uint8_t slot - Slot to write to. Slots start at 1. + -(Optional) uint8_t channel = 0. If specified writes to "channel". + If left out or 0, writes "data" to all channels where + least Signficant bit is channel 1. + +Returns: -None +*******************************************************************************/ +void P1AM::writeDiscrete(uint32_t data,uint8_t slot, uint8_t channel){ + uint8_t tData[7]; + uint8_t mdbLoc = 0; + uint8_t len = 0; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].doBytes; + + if((slot < 1) || (slot > NUMBER_OF_MODULES)){ + debugPrint("Slots must be between 1 and "); + debugPrintln(NUMBER_OF_MODULES); + return; + } + + if((len <= 0)){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no Discrete Output bytes"); + return; + } + + if(channel > (len * 8)){ //8 channels per byte + debugPrintln("This channel is not valid"); + return; + } + + tData[0] = WRITE_DISCRETE_HDR; + tData[1] = slot; + tData[2] = channel; + + if(channel == 0){ + for(int i=0;i>(8*i) & 0xFF; //Shift and mask + } + } + else{ + tData[3] = data & 0b1; //mask bit + len = 1; //only send 1 byte + } + + + spiSendRecvBuf(tData,len+3); //3 Header bytes plus data length + + dataSync(); + return; +} + +/******************************************************************************* +Description: Read a single analog input module channel. + +Parameters: -uint8_t slot - Slot to read from. Slots start at 1. + -uint8_t channel - Channel to read from. Channels start at 1. + +Returns: -uint32_t - Value of channel in counts. 12-bit returns 0-4095, + 16-bit returns between 0-65535, etc. +*******************************************************************************/ +int P1AM::readAnalog(uint8_t slot, uint8_t channel){ + int data = 0; + uint8_t len = 0; + uint8_t mdbLoc = 0; + char rData[4] = {0,0,0,0}; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].aiBytes; + + if((slot < 1) || (slot > NUMBER_OF_MODULES)){ + debugPrint("Slots must be between 1 and "); + debugPrintln(NUMBER_OF_MODULES); + return 0; + } + + if((len <= 0)){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no Analog Input bytes"); + return 0; + } + + if((channel <= 0) || (channel > (len / 4))){ //4 bytes per channel + debugPrintln("This channel is not valid"); + return 0; + } + + rData[0] = READ_ANALOG_HDR; + rData[1] = slot; + rData[2] = channel; + spiSendRecvBuf((uint8_t *)rData,3); + + if(spiTimeout(1000*200) == true){ + data = spiSendRecvInt(DUMMY); + dataSync(); + return data; + } + else{ + debugPrintln("Slow read"); + delay(100); + return 0; + } +} + +/******************************************************************************* +Description: Read a single temperature input module channel. + +Parameters: -uint8_t slot - Slot to read from. Slots start at 1. + -uint8_t channel - Channel to read from. Channels start at 1. +Returns: -Value of channel in degrees for temperature. mV for voltages. +*******************************************************************************/ +float P1AM::readTemperature(uint8_t slot, uint8_t channel){ + union int2float{ + int data; //We get an int back no matter what analog module. Unions set variables as the same/ + float temperature; //Floats and Ints are both 32 bits, so this lets quickly convert our int to a float. + }ourValue; + + ourValue.data = readAnalog(slot,channel); //Use the same analog read function to get int + + return ourValue.temperature; //return the float +} + +/******************************************************************************* +Description: Write to a single analog output module channel. + +Parameters: -uint32_t data - Value of channel in counts. 12-bit in range 0-4095, + 16-bit in range 0-65535, etc. + -uint8_t slot - Slot to write to. Slots start at 1. + -uint8_t channel - Channel to write to. Channels start at 1. +Returns: -None +*******************************************************************************/ +void P1AM::writeAnalog(uint32_t data,uint8_t slot, uint8_t channel){ + uint8_t tData[7]; + uint8_t mdbLoc = 0; + uint8_t len = 0; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].aoBytes; + + if((slot < 1) || (slot > NUMBER_OF_MODULES)){ + debugPrint("Slots must be between 1 and "); + debugPrintln(NUMBER_OF_MODULES); + return; + } + + if((len <= 0)){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no Analog Output bytes"); + return; + } + + if((channel <= 0) || (channel > (len / 4))){ //4 bytes per channel + debugPrintln("This channel is not valid"); + return; + } + + tData[0] = WRITE_ANALOG_HDR; + tData[1] = slot; + tData[2] = channel; + tData[3] = (data>>0) & 0xFF; + tData[4] = (data>>8) & 0xFF; + tData[5] = (data>>16) & 0xFF; + tData[6] = (data>>24) & 0xFF; + spiSendRecvBuf(tData,7); + + dataSync(); + return; +} + +/******************************************************************************* +Description: Read a block of data stored in Base Controller. This allows you to read data + from many modules in one command, but requires you to calculate the + offset and lengths and later parse out the data. You can also read + the state of output data through this for debugging purposes. This + function is more advanced and is not recommended for new programmers. + +Parameters: -char buf[] - Pointer to an array that will hold the values read. This + function does not return a value, but stores in the *buf passed in. + -uint16_t len - Number of bytes to read + -uint8_t offset - Starting byte in array to read. + -uint8_t type - Specifies which Base Controller data block to read from. + 0 is Discrete Input, 1 is Analog Input, 2 is Discrete Output, 3 is Analog Output,4 is Status. + +Returns: -None +*******************************************************************************/ +void P1AM::readBlockData(char buf[], uint16_t len,uint16_t offset, uint8_t type){ + uint8_t readParams[6]; + + if((len+offset) > 1200){ //max of data array is 1200, so we can't read past that + len = 1200-offset; //adjust len in case we're trying to read too far + } + + readParams[0] = READ_BLOCK_HDR; + readParams[1] = type; // 0 is Discrete In, 1 is Analog In, 2 is Discrete Out, 3 is Analog Out,4 is Status + readParams[2] = len >> 8; + readParams[3] = len & 0xFF; + readParams[4] = offset >> 8; + readParams[5] = offset & 0xFF; + spiSendRecvBuf(readParams,6); //Send paramters for block read + + if(spiTimeout(1000*200) == true){ + spiSendRecvBuf((uint8_t *)buf,len,true); //data is stored in buffer passed in + dataSync(); + return; + } + else{ + debugPrintln("Slow block read"); + delay(100); + return; + } +} + +/******************************************************************************* +Description: Write to a block of data stored in Base Controller. This allows you to write data + to many modules in one command, but requires you to calculate the + offset and lengths and parse together the data. You can write to input data, + but be aware the Base Controller may overwrite this data in normal operations. + This function is more advanced than others and is not recommended + for new programmers. + +Parameters: -char buf[] - Pointer to an array that holds the values to write. This + array is not overwritten. + -uint16_t len - Number of bytes to write + -uint8_t offset - Starting byte in array to write. + -uint8_t type - Specifies which Base Controller data block to write to. + 0 is Discrete Input, 1 is Analog Input, 2 is Discrete Output, 3 is Analog Output,4 is Status. + +Returns: -None +*******************************************************************************/ +void P1AM::writeBlockData(char buf[], uint16_t len,uint16_t offset, uint8_t type){ + + if((len+offset) > 1200){ //max of data array is 1200, so we can't read past that + len = 1200-offset; //adjust len in case we're trying to read too far + } + uint8_t *tData = (uint8_t *)malloc(len+6); + tData[0] = WRITE_BLOCK_HDR; + tData[1] = type; + tData[2] = len >> 8; + tData[3] = len & 0xFF; + tData[4] = offset >> 8; + tData[5] = offset & 0xFF; + memcpy(tData+6,buf,len); + spiSendRecvBuf(tData,len+6,0); //data is not stored in passed buffer + free(tData); + dataSync(); + return; +} + +/******************************************************************************* +Description: Set both duty cycle and frequency of a PWM output module channel. + +Parameters: -float duty - Duty cycle. Range 0.00-100.00. You can include up to 2 decimal places + -uint32_t freq - Frequency. See module data sheet for range. P1-04PWM is 0-20kHz. + -uint8_t slot - Slot to write to. Slots start at 1. + -uint8_t channel - Channel to write to. Channels start at 1. + +Returns: -None +*******************************************************************************/ +void P1AM::writePWM(float duty,uint32_t freq,uint8_t slot,uint8_t channel){ + uint8_t mdbLoc = 0; + uint8_t tempLoc = 0; + uint8_t offset = 0; + uint32_t dutyInt = 0; + char tData[8]; + + mdbLoc = baseSlot[slot-1].dbLoc; + + if((slot < 1) || (slot > NUMBER_OF_MODULES)){ + debugPrint("Slots must be between 1 and "); + debugPrintln(NUMBER_OF_MODULES); + return; + } + + if((channel <= 0) || (channel > 4)){ + debugPrintln("This channel is not valid"); + return; + } + + if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module is not a PWM module"); + return; //Not PWM + } + + for(int i=0;i 0){ + offset += mdb[tempLoc].aoBytes; //get offset of analog bytes + } + } + + offset += (channel - 1) * 8; //Each channel uses 8 bytes + dutyInt = (uint32_t)(duty * 100);// shift decimal over 2 places and cast off remainder. e.g. 12.3456 turns into 1234 + + tData[3] = (dutyInt>>0) & 0xFF; //shift and mask to bytes + tData[2] = (dutyInt>>8) & 0xFF; + tData[1] = (dutyInt>>16) & 0xFF; + tData[0] = (dutyInt>>24) & 0xFF; + tData[7] = (freq>>0) & 0xFF; + tData[6] = (freq>>8) & 0xFF; + tData[5] = (freq>>16) & 0xFF; + tData[4] = (freq>>24) & 0xFF; + + writeBlockData(tData, 8, offset, ANALOG_OUT_BLOCK); //Use block data to ensure duty/freq are entered at the same time. + dataSync(); + + return; +} + +/******************************************************************************* +Description: Set duty cycle of a PWM output module channel. + +Parameters: -float duty - Duty cycle. Range 0.00-100.00. You can include up to 2 decimal places + -uint8_t slot - Slot to write to. Slots start at 1. + -uint8_t channel - Channel to write to. Channels start at 1. + +Returns: -None +*******************************************************************************/ +void P1AM::writePWMDuty(float duty,uint8_t slot,uint8_t channel){ + uint8_t mdbLoc = 0; + uint32_t dutyInt = 0; + + mdbLoc = baseSlot[slot-1].dbLoc; + + if((slot < 1) || (slot > NUMBER_OF_MODULES)){ + debugPrint("Slots must be between 1 and "); + debugPrintln(NUMBER_OF_MODULES); + return; + } + + if((channel <= 0) || (channel > 4)){ + debugPrintln("This channel is not valid"); + return; + } + + if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module is not a PWM module"); + return; //Not PWM + } + + dutyInt = (uint32_t)(duty * 100);// shift decimal over 2 places and cast off remainder. e.g. 12.3456 turns into 1234 + channel = 1 + ((channel-1) * 2); + P1.writeAnalog(dutyInt,slot,channel); + + dataSync(); + return; +} + +/******************************************************************************* +Description: Set frequency of a PWM output module channel. + +Parameters: -uint32_t freq - Frequency. See module data sheet for range. P1-04PWM is 0-20kHz. + -uint8_t slot - Slot to write to. Slots start at 1. + -uint8_t channel - Channel to write to. Channels start at 1. + +Returns: -None +*******************************************************************************/ +void P1AM::writePWMFreq(uint32_t freq,uint8_t slot,uint8_t channel){ + uint8_t mdbLoc = 0; + + mdbLoc = baseSlot[slot-1].dbLoc; + + if((slot < 1) || (slot > NUMBER_OF_MODULES)){ + debugPrint("Slots must be between 1 and "); + debugPrintln(NUMBER_OF_MODULES); + return; + } + + if((channel <= 0) || (channel > 4)){ + debugPrintln("This channel is not valid"); + return; + } + + if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM + debugPrint("Slot "); + debugPrint(slot); + debugPrintln("This module is not a PWM module"); + return; //Not PWM + } + + channel = 2 + ((channel-1) * 2); + P1.writeAnalog(freq,slot,channel); + + dataSync(); + return; +} + + /******************************************************************************* +Description: Set direction of a PWM module channel when set to DIR mode. + See configuration document to see how to enable this feature. + +Parameters: -bool data - Set channel on or off + -uint8_t slot - Slot to write to. Slots start at 1. + -uint8_t channel - Channel to write to. Channels start at 1. + +Returns: -None +*******************************************************************************/ +void P1AM::writePWMDir(bool data,uint8_t slot, uint8_t channel){ + uint8_t mdbLoc = 0; + + mdbLoc = baseSlot[slot-1].dbLoc; + + if((slot < 1) || (slot > NUMBER_OF_MODULES)){ + debugPrint("Slots must be between 1 and "); + debugPrintln(NUMBER_OF_MODULES); + return; + } + + if((channel <= 0) || (channel > 4)){ + debugPrintln("This channel is not valid"); + return; + } + + if((mdb[mdbLoc].dataSize & 0xF0) != 0xA0){ //Is this PWM + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module is not a PWM module"); + return; //Not PWM + } + + channel = 1 + ((channel-1) * 2); + P1.writeAnalog(data,slot,channel); + + dataSync(); + return; +} + + +/******************************************************************************* +Description: Print the names of all the modules in the base to the serial monitor + +Parameters: -None + +Returns: -Number of good sign-ons +*******************************************************************************/ +uint8_t P1AM::printModules(){ + uint32_t slot = 0; + uint8_t dbLoc = 0; + uint8_t goodSlots = 0; + + while(baseSlot[slot].dbLoc != 0 && slot < NUMBER_OF_MODULES){ + dbLoc = baseSlot[slot].dbLoc; + if(mdb[dbLoc].moduleID == 0 || mdb[dbLoc].moduleID == 0xFFFFFFFF){ + Serial.print("Slot "); + Serial.print(slot + 1); + Serial.println(": Sign On Error"); + } + else{ + Serial.print("Slot "); + Serial.print(slot + 1); + Serial.print(": "); + Serial.println(mdb[dbLoc].moduleName); + goodSlots++; + } + slot++; + } + dataSync(); + Serial.println(""); //Print an empty line to give us some breathing room later + return goodSlots; +} + +/******************************************************************************* +Description: Return the module properties for a given slot + +Parameters: -uint8_t slot - Slot of which you want the module parameters. + Slot 1 is module closest to processor. + +Returns: -moduleProps - moduleProps structure containing the specific paramters + at the give slot. +*******************************************************************************/ +moduleProps P1AM::readSlotProps(uint8_t slot){ + + uint8_t dbLoc = 0; + moduleProps _slotProps; + + if((slot < 1) || (slot > 15)){ + debugPrintln("Slots must be between 1 and 15"); + _slotProps = mdb[0]; + return _slotProps; + } + + if (baseSlot[slot-1].dbLoc != 0) { + dbLoc = baseSlot[slot-1].dbLoc; + _slotProps = mdb[dbLoc]; + } + else { + _slotProps = mdb[0]; + } + return _slotProps; +} + +/******************************************************************************* +Description: Read a single status byte from a single module + +Parameters: -int byteNum - Which status byte to read in. Byte numbering starts at 0. + -int slot - Which slot to read from + +Returns: -char - The status byte. +*******************************************************************************/ +char P1AM::readStatus(int byteNum,int slot){ + uint8_t len = 0; + uint8_t mdbLoc = 0; + uint8_t buf[1]; + uint8_t rData[4]; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = 1; //only need 1 byte + + if((slot < 1) || (slot > NUMBER_OF_MODULES)){ + debugPrint("Slots must be between 1 and "); + debugPrintln(NUMBER_OF_MODULES); + return 0; + } + + if(mdb[mdbLoc].statusBytes <= 0){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no Status bytes"); + return 0; //No status Bytes + } + + rData[0] = READ_STATUS_HDR; + rData[1] = slot; + rData[2] = len; + rData[3] = byteNum;//offset + spiSendRecvBuf((uint8_t *)rData,4); + + if(spiTimeout(1000*200) == true){ + spiSendRecvBuf((uint8_t *)buf,len,true); //data is stored in buffer passed in + dataSync(); + return buf[0]; + } + else{ + debugPrintln("Slow status read"); + delay(100); + return 0; + } +} + +/******************************************************************************* +Description: Read all status bytes from a single module + +Parameters: -char buf[] - Array to store the read status bytes in. This function + does not return a value, but places it in the array passed in + -uint8_t slot - Slot that you want to read status bytes from + +Returns: -None +*******************************************************************************/ +void P1AM::readStatus(char buf[], uint8_t slot){ + uint8_t len = 0; + uint8_t mdbLoc = 0; + uint8_t rData[4]; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].statusBytes; + + if((slot < 1) || (slot > NUMBER_OF_MODULES)){ + debugPrint("Slots must be between 1 and "); + debugPrintln(NUMBER_OF_MODULES); + return; + } + + if(len <= 0){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no Status bytes"); + return; //No status Bytes + } + + rData[0] = READ_STATUS_HDR; + rData[1] = slot; + rData[2] = len; + rData[3] = 0;//offset + spiSendRecvBuf((uint8_t *)rData,4); + + if(spiTimeout(1000*200) == true){ + spiSendRecvBuf((uint8_t *)buf,len,true); //data is stored in buffer passed in + dataSync(); + return; + } + else{ + debugPrintln("Slow status read"); + delay(100); + return; + } +} + +/******************************************************************************* +Description: Checks the under range error on select P1000 modules. Returns if channel + is under range. This function does not check if the module supports + this feature. + +Parameters: -uint8_t slot - Which slot to read from. + -(Optional) uint8_t channel = 0. If value is non-zero checks the + specified channel. If left out or 0, checks all channels where + least Signficant bit is the status of channel 1. + + +Returns: -uint8_t - returns under range status +*******************************************************************************/ +uint8_t P1AM::checkUnderRange(uint8_t slot, uint8_t channel){ + uint8_t statusByte = 0; + + statusByte = readStatus(UNDER_RANGE_STATUS,slot); + if(channel){ + statusByte = (statusByte >> (channel - 1)) & 1; //shift and mask bits + } + + return statusByte; +} + +/******************************************************************************* +Description: Checks the over range error on select P1000 modules. Returns if channel + is over range. This function does not check if the module supports + this feature. + +Parameters: -uint8_t slot - Which slot to read from. + -(Optional) uint8_t channel = 0. If value is non-zero checks the + specified channel. If left out or 0, checks all channels where + least Signficant bit is the status of channel 1. + + +Returns: -uint8_t - returns over range status +*******************************************************************************/ +uint8_t P1AM::checkOverRange(uint8_t slot, uint8_t channel){ + uint8_t statusByte = 0; + + statusByte = readStatus(OVER_RANGE_STATUS,slot); + if(channel){ + statusByte = (statusByte >> (channel - 1)) & 1; //shift and mask bits. + } + + return statusByte; +} + +/******************************************************************************* +Description: Checks the burnout error on select P1000 modules. Returns if channel + is over range. This function does not check if the module supports + this feature. + +Parameters: -uint8_t slot - Which slot to read from. + -(Optional) uint8_t channel = 0. If value is non-zero checks the + specified channel. If left out or 0, checks all channels where + least Signficant bit is the status of channel 1. + + +Returns: -uint8_t - returns burnout status +*******************************************************************************/ +uint8_t P1AM::checkBurnout(uint8_t slot, uint8_t channel){ + uint8_t statusByte = 0; + + statusByte = readStatus(BURNOUT_STATUS,slot); + if(channel){ + statusByte = (statusByte >> (channel - 1)) & 1; //shift and mask bits + } + + return statusByte; +} + +/******************************************************************************* +Description: Checks the lost 24V error on select P1000 modules. Returns 1 if slot is missing + 24V. This function does not check if the module supports this feature. + +Parameters: -uint8_t slot - Which slot to read from. + +Returns: -uint8_t - 1 if 24V is missing. 0 if 24V is present +*******************************************************************************/ +uint8_t P1AM::check24V(uint8_t slot){ + uint8_t statusByte = 0; + + statusByte = readStatus(MISSING24V_STATUS,slot); + statusByte = (statusByte >> 1) & 1; //Mask and shift for 24V bit + + return statusByte; +} + +/******************************************************************************* +Description: Manually configure a module. Use this if you want to use a setting + that is not the default for a module. + Visit https://facts-engineering.github.io/config.html for more information + +Parameters: -uint8_t slot - Slot you want to configure. + -char *cfgData - Pointer to array that contains the configuration settings + +Returns: -bool - Configuration successfully written to Base Controller. +*******************************************************************************/ +bool P1AM::configureModule(char cfgData[], uint8_t slot){ + uint8_t len = 0; + uint8_t mdbLoc = 0; + uint8_t cfgForSpi[66]; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].configBytes + 2; + cfgForSpi[0] = CFG_HDR; + cfgForSpi[1] = slot; + + memcpy(cfgForSpi+2,cfgData,len); + delay(1); + if((slot < 1) || (slot > NUMBER_OF_MODULES)){ + debugPrint("Slots must be between 1 and "); + debugPrintln(NUMBER_OF_MODULES); + return 0; + } + + if(len <= 0){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no config Bytes"); + return 0; + } + + spiSendRecvBuf(cfgForSpi,len); + delay(100); //Additional time for Config to written + dataSync(); + dataSync(); + return 1; +} + +/******************************************************************************* +Overload of above function for const arrays +*******************************************************************************/ +bool P1AM::configureModule(const char cfgData[], uint8_t slot){ + + return configureModule((char*)cfgData, slot); + +} + +/******************************************************************************* +Description: Read current configuration of a module. Data is stored in the passed + in array pointer. + + +Parameters: -char *cfgData - Pointer to array that will receive the current configuration + -uint8_t slot - Slot you want to configure. + +Returns: -none +*******************************************************************************/ +void P1AM::readModuleConfig(char cfgData[], uint8_t slot){ + uint8_t len = 0; + uint8_t mdbLoc = 0; + uint8_t cfgForSpi[2]; + + mdbLoc = baseSlot[slot-1].dbLoc; + len = mdb[mdbLoc].configBytes; + + if((slot < 1) || (slot > NUMBER_OF_MODULES)){ + debugPrint("Slots must be between 1 and "); + debugPrintln(NUMBER_OF_MODULES); + return; + } + + if(len <= 0){ + debugPrint("Slot "); + debugPrint(slot); + debugPrintln(": This module has no config Bytes"); + return; + } + + cfgForSpi[0] = READ_CFG_HDR; + cfgForSpi[1] = slot; + spiSendRecvBuf((uint8_t *)cfgForSpi,2); + + if(spiTimeout(1000*200) == true){ + spiSendRecvBuf((uint8_t *)cfgData,len,true); //data is stored in buffer passed in + dataSync(); + return; + } + else{ + debugPrintln("Slow config read"); + delay(100); + return; + } +} + +/******************************************************************************* +Description: Configures the behaviour of the watchdog timer. + +Parameters: -uint16_t milliseconds - Time in milliseconds in range 0-65535. + If any P1AM-100 function is not called before the time elapses, + the Base Controller will toggle the reset pin on the P1AM-100. + +Returns: -None +*******************************************************************************/ +void P1AM::configWD(uint16_t milliseconds, uint8_t toggle) { + uint8_t lowByte; + uint8_t highByte; + uint8_t lowToggle; + uint8_t highToggle; + uint16_t toggleTime = 100; + uint8_t wdData[6]; + + lowByte = (uint8_t) milliseconds; + highByte = (uint8_t) (milliseconds >> 8); + + lowToggle = (uint8_t) toggleTime; + highToggle = (uint8_t) (toggleTime >> 8); + + wdData[0] = CONFIGWD_HDR; + wdData[1] = lowByte; + wdData[2] = highByte; + wdData[3] = lowToggle; + wdData[4] = highToggle; + wdData[5] = toggle & 0x01; + + spiSendRecvBuf(wdData,6); + dataSync(); + return; +} + +/******************************************************************************* +Description: Begin the watchdog timer. If the watchdog is not configured beforehand, + Base Controller WD will default to fullscale (65535) timer. If the Base Controller is not + written to within the time period, the Base Controller will reset the P1AM-100. + +Parameters: -None + +Returns: -None +*******************************************************************************/ +void P1AM::startWD() { + + spiSendRecvByte(STARTWD_HDR); //Send "StartWD" header to Base Controller + if(spiTimeout(1000*200) == true){ + spiSendRecvByte(DUMMY); //Send "PetWD" header to Base Controller + dataSync(); + } + return; +} + +/******************************************************************************* +Description: Stops the watchdog timer from running. Use this is you have code + that takes a long time to execute and you are unable to add petWD + calls to. + +Parameters: -None + +Returns: -None +*******************************************************************************/ +void P1AM::stopWD() { + + spiSendRecvByte(STOPWD_HDR); //Send "StopWD" header to Base Controller + if(spiTimeout(1000*200) == true){ + spiSendRecvByte(DUMMY); //Send "PetWD" header to Base Controller + dataSync(); + } + return; +} + +/******************************************************************************* +Description: Pets the watchdog i.e. resets the timer so it does not trigger. Any + P1AM-100 function will pet the watchdog, but this function can be + used in long sections of code or slow loops where the other P1AM-100 + functions are not called. + +Parameters: -None + +Returns: -None +*******************************************************************************/ +void P1AM::petWD() { + + spiSendRecvByte(PETWD_HDR); //Send "PetWD" header to Base Controller + if(spiTimeout(1000*200) == true){ + spiSendRecvByte(DUMMY); //Send "PetWD" header to Base Controller + dataSync(); + } + return; +} + +/******************************************************************************* +Description: Print out current Base Controller firmware version to serial monitor + +Parameters: -None + +Returns: -uint32_t - Firmware Version byte format X.Y.ZZ +*******************************************************************************/ +uint32_t P1AM::getFwVersion(){ + uint32_t version = 0; + + if(handleHDR(VERSION_HDR)){ + version = spiSendRecvInt(DUMMY); + debugPrint("FW V"); + debugPrint(version>>12); + debugPrint("."); + debugPrint(version>>8 & 0xF); + debugPrint("."); + debugPrintln(version & 0xFF); + dataSync(); + return version; + } + else{ + delay(100); + return 0; + } +} + +/******************************************************************************* +Description: Flag to see if the Base Controller has been initialised and is running + +Parameters: -None + +Returns: -Bool - Returns true if Base Controller has been intialised and is active +*******************************************************************************/ +bool P1AM::isBaseActive(){ + bool isActive = false; + + spiSendRecvByte(ACTIVE_HDR); + if(spiTimeout(1000*200) == true){ + isActive = spiSendRecvByte(DUMMY); + } + dataSync(); + return isActive; +} + +/******************************************************************************* +Description: Check to see if any modules that signed on during init have since + dropped out. + +Parameters: -uint8_t numberOfModules - Number of modules expected. Useful if a module + never signed on to begin with. If ommitted this function will only + check for the number of modules that signed on during the last init + + +Returns: -uint8_t - Returns the slot number of the leftmost module in the base + that is no longer functioning. +*******************************************************************************/ +uint8_t P1AM::checkConnection(uint8_t numberOfModules){ + uint8_t expectedSlots = 0; + uint16_t activeSlots = 0; + + if(!numberOfModules){ + while(baseSlot[expectedSlots].dbLoc != 0){ + expectedSlots++; //Count number of slots that signed on during init + } + } + else{ + expectedSlots = numberOfModules; + } + + spiSendRecvByte(DROPOUT_HDR); + if(spiTimeout(1000*200) == true){ + activeSlots = spiSendRecvByte(DUMMY); //Get value of active slots. Bitmapped representation. + activeSlots |= spiSendRecvByte(DUMMY) << 8; + } + dataSync(); + + for(int i=0;i> i) == 0){ + return i+1; //Leftmost slot with numbering starting at 1 + } + } + + return 0; //No bad slots found +} + +/******************************************************************************* +Label Functions - These use the channelLabel type defined in P1AM.h instead of +passing slot and channel individually. These Turn P1.readDiscrete(1,1) into +P1.readDiscrete(sens_1). They are functionally the same, so if you want to gain +a better understanding of the inner workings, please check out the actual +function code in the above portion of this library. +*******************************************************************************/ +uint32_t P1AM::readDiscrete(channelLabel label){ + return readDiscrete(label.slot,label.channel); +} + +void P1AM::writeDiscrete(uint32_t data, channelLabel label){ + writeDiscrete(data,label.slot,label.channel); + return; +} + +int P1AM::readAnalog(channelLabel label){ + return readAnalog(label.slot,label.channel); +} + +float P1AM::readTemperature(channelLabel label){ + return readTemperature(label.slot,label.channel); +} + +void P1AM::writeAnalog(uint32_t data, channelLabel label){ + writeAnalog(data,label.slot,label.channel); + return; +} + +void P1AM::writePWM(float duty, uint32_t freq, channelLabel label){ + writePWM(duty,freq,label.slot,label.channel); + return; +} + +void P1AM::writePWMDuty(float duty, channelLabel label){ + writePWMDuty(duty,label.slot,label.channel); + return; +} +void P1AM::writePWMFreq(uint32_t freq, channelLabel label){ + writePWMFreq(freq,label.slot,label.channel); + return; +} + +void P1AM::writePWMDir(bool data, channelLabel label){ + writePWMDir(data,label.slot,label.channel); + return; +} + +uint8_t P1AM::checkUnderRange(channelLabel label){ + return checkUnderRange(label.slot,label.channel); +} + +uint8_t P1AM::checkOverRange(channelLabel label){ + return checkOverRange(label.slot,label.channel); +} + +uint8_t P1AM::checkBurnout(channelLabel label){ + return checkBurnout(label.slot,label.channel); +} + +/******************************************************************************* +PRIVATE FUNCTIONS FOR P1AM.h +*******************************************************************************/ +void P1AM::dataSync(){ + uint32_t currentMillis = 0; + uint32_t startMillis = 0; + + startMillis = millis(); + while(!digitalRead(slaveAckPin)){ + currentMillis = millis(); + if(currentMillis - startMillis >= 200){ + debugPrintln("Base Sync Timeout"); + break; + } + } + delayMicroseconds(1); + + startMillis = millis(); + while(digitalRead(slaveAckPin)){ + currentMillis = millis(); + if(currentMillis - startMillis >= 200){ + debugPrintln("Base Sync Timeout"); + break; + } + } + delayMicroseconds(1); + + startMillis = millis(); + while(!digitalRead(slaveAckPin)){ + currentMillis = millis(); + if(currentMillis - startMillis >= 200){ + debugPrintln("Base Sync Timeout"); + break; + } + } + delayMicroseconds(1); + + return; +} + +char *P1AM::loadConfigBuf(int moduleID){ + + switch(moduleID){ + case 0x34605582: + return (char*)P1_04AD_1_DEFAULT_CONFIG; + break; + case 0x34605583: + return (char*)P1_04AD_2_DEFAULT_CONFIG; + break; + case 0x34605590: + return (char*)P1_04ADL_2_DEFAULT_CONFIG; + case 0x34608C8E: + return (char*)P1_04NTC_DEFAULT_CONFIG; + case 0x34608C81: + return (char*)P1_04THM_DEFAULT_CONFIG; + case 0x34605588: + return (char*)P1_04RTD_DEFAULT_CONFIG; + case 0x34605581: + return (char*)P1_04AD_DEFAULT_CONFIG; + case 0x3460558F: + return (char*)P1_04ADL_1_DEFAULT_CONFIG; + case 0x34A0558A: + return (char*)P1_08ADL_1_DEFAULT_CONFIG; + case 0x34A0558B: + return (char*)P1_08ADL_2_DEFAULT_CONFIG; + case 0x5461A783: + return (char*)P1_04ADL2DAL_1_DEFAULT_CONFIG; + case 0x5461A784: + return (char*)P1_04ADL2DAL_2_DEFAULT_CONFIG; + case 0x1403F481: + return (char*)P1_04PWM_DEFAULT_CONFIG; + case 0x34A5A481: + return (char*)P1_02HSC_DEFAULT_CONFIG; + default: + break; + } + return (char*)P1_04ADL_1_DEFAULT_CONFIG; +} + +bool P1AM::handleHDR(uint8_t HDR){ + + while(!digitalRead(slaveAckPin)); //Wait for Base Controller to be out of base scanning + spiSendRecvByte(HDR); //Send intital Header to ping DMA + + return spiTimeout(MAX_TIMEOUT,HDR,2000); //1 if we got Base Controller ack, 0 if we took too long +} + +uint8_t P1AM::spiSendRecvByte(uint8_t data){ + + _P1AM_SPI.begin(); + _P1AM_SPI.beginTransaction(P100_SPI_SETTINGS); + digitalWrite(slaveSelectPin, LOW); + data = _P1AM_SPI.transfer(data); + digitalWrite(slaveSelectPin, HIGH); + _P1AM_SPI.endTransaction(); + _P1AM_SPI.end(); + + return data; +} + +uint32_t P1AM::spiSendRecvInt(uint32_t data){ + uint8_t rData[4]; + uint8_t tData[4]; + uint32_t returnInt = 0; + + tData[0] = (data>>0) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[1] = (data>>8) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[2] = (data>>16) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[3] = (data>>24) & 0xFF; // data to write to the Base Controller - 1 byte long + + _P1AM_SPI.begin(); + _P1AM_SPI.beginTransaction(P100_SPI_SETTINGS); + digitalWrite(slaveSelectPin, LOW); + rData[0] = _P1AM_SPI.transfer(tData[0]); + rData[1] = _P1AM_SPI.transfer(tData[1]); + rData[2] = _P1AM_SPI.transfer(tData[2]); + rData[3] = _P1AM_SPI.transfer(tData[3]); + digitalWrite(slaveSelectPin, HIGH); + _P1AM_SPI.endTransaction(); + _P1AM_SPI.end(); + + returnInt = (rData[3]<<24); + returnInt += (rData[2]<<16); + returnInt += (rData[1]<<8); + returnInt += (rData[0]<<0); + + return returnInt; +} + +void P1AM::spiSendRecvBuf(uint8_t *buf, int len, bool returnData){ + + _P1AM_SPI.begin(); + _P1AM_SPI.beginTransaction(P100_SPI_SETTINGS); + digitalWrite(slaveSelectPin, LOW); + + if(returnData){ + for(int i = 0; i < len; ++i){ + buf[i] = _P1AM_SPI.transfer(buf[i]); //return what we get into our buffer + } + } + else{ + for(int i = 0; i < len; ++i){ + _P1AM_SPI.transfer(buf[i]); //or don't return what we get + } + } + + digitalWrite(slaveSelectPin, HIGH); + _P1AM_SPI.endTransaction(); + _P1AM_SPI.end(); + return; +} + +bool P1AM::spiTimeout(uint32_t uS,uint8_t resendMsg,uint16_t retryPeriod){ + uint16_t retryTime = 0; + + while((!digitalRead(slaveAckPin)) && (uS != 0)){ + delayMicroseconds(1); + uS--; + + retryTime++; + if((retryPeriod) && (retryTime == retryPeriod)){ // if we specified a retry period and it equals the time we've waited + + spiSendRecvByte(resendMsg); + retryTime = 0; + } + } + delayMicroseconds(50); //small delay to let Base Controller load next msg in buf + + if(uS > 0){ + return 1; + } + else{ + debugPrintln("Timeout"); + return 0; + } +} + +bool P1AM::Base_Controller_FW_UPDATE(unsigned int fwLen){ + extern unsigned char FW_IMG_Base_Controller[]; + //int fwLen = sizeof(FW_IMG_Base_Controller); + int chunkSize = 1000; + uint8_t status = 0; + uint8_t tData[9]; + + uint8_t *chunkBuffer = (uint8_t *)malloc(chunkSize); //Make spacs for FW chunks + + _P1AM_SPI.begin(); //Start SPI + Serial.println("Establishing Communication"); + + tData[0] = FW_UPDATE_HDR; + + tData[1] = (fwLen>>0) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[2] = (fwLen>>8) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[3] = (fwLen>>16) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[4] = (fwLen>>24) & 0xFF; // data to write to the Base Controller - 1 byte long + + tData[5] = (chunkSize>>0) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[6] = (chunkSize>>8) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[7] = (chunkSize>>16) & 0xFF; // data to write to the Base Controller - 1 byte long + tData[8] = (chunkSize>>24) & 0xFF; // data to write to the Base Controller - 1 byte long + + spiSendRecvBuf(tData,9); //send chunk + + + if(!spiTimeout(1000*200)){ + delay(100); + return 0; + } + if(spiSendRecvByte(DUMMY) != FW_UPDATE_HDR){ + delay(100); + return 0; + } + + delay(10); + Serial.println("FW Transfer Started"); + while(!digitalRead(slaveAckPin)); //wait for Base Controller to be ready + int fullLoops = fwLen/chunkSize; //How many full chunks + + int offset; + for ( offset = 0; offset <= fullLoops; offset++) + { + + Serial.print((float)100*offset/fullLoops,0);//load percentage + Serial.println("%"); + + memcpy(chunkBuffer,(FW_IMG_Base_Controller + offset*chunkSize),chunkSize); //copy image into buffer to send + while(!digitalRead(slaveAckPin)); //wait for Base Controller to be ready + spiSendRecvBuf(chunkBuffer,chunkSize); //send chunk + + + } + + //last chunk + chunkSize = fwLen % chunkSize; //get the remaining bytes smaller than 1 chunk + if(chunkSize != 0) + { + memcpy(chunkBuffer,(FW_IMG_Base_Controller + offset*chunkSize),chunkSize); //copy image into buffer to send + while(!digitalRead(slaveAckPin)); //wait for Base Controller to be ready + spiSendRecvBuf(chunkBuffer,chunkSize); + } + + Serial.println("100%"); + Serial.println("Hang on a sec"); + + while(!digitalRead(slaveAckPin)); + status = spiSendRecvByte(DUMMY); //CRC check on the image + + + if(status == 1) + { + delay(3000); //Good, give the Base Controller a few seconds to reboot + Serial.print("Update Complete! We're now at "); + getFwVersion(); //Print to confirm new FW is correct version + return 1; + } + + else if(status == 4) + { + Serial.println("Bad FW Checksum "); + Serial.println("FW Update FAIL :c"); + return 0; + } + + else if(status == 5) + { + Serial.println("Bad FW Hash "); + Serial.println("FW Update FAIL :c"); + return 0; + } + + else if(status == 9) + { + Serial.println("Bad FW Hash and CRC"); + Serial.println("FW Update FAIL :c"); + return 0; + } + Serial.println("Unknown Error"); + return 0; +} diff --git a/src/P1AM.h b/src/P1AM.h index 8d16607..ac9da76 100644 --- a/src/P1AM.h +++ b/src/P1AM.h @@ -1,117 +1,116 @@ -/* -MIT License - -Copyright (c) 2019 FACTS Engineering, LLC - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - P1AM.h - Library for Communicating with P1000 Series Modules - Library V1.0.6 -*/ - -#ifndef P1AM_h -#define P1AM_h - -#include "Arduino.h" -#include "SPI.h" -#include "Module_List.h" -#include "defines.h" - -struct channelLabel{ //Used to call functions through names rather than slot/channel numbers. - uint8_t slot; - uint8_t channel; -}; - -class P1AM{ - public: - P1AM(); - - //Init - uint8_t init(); //Initialise modules in the base. Returns the number of slots that have signed on. - void enableBaseController(bool state); //Enable or Disable base controller. Automatically called in init. - uint16_t rollCall(const char* moduleNames[], uint8_t numberOfModules); //Pass in an array of module names to check if current modules in base match. - - //Data IO Functions - For more info see function headers in P1AM.cpp - uint32_t readDiscrete(uint8_t slot, uint8_t channel = 0); //Read Discrete Module. Passing 0 instead of a channel will return data from all of the channels at once. - void writeDiscrete(uint32_t data,uint8_t slot, uint8_t channel = 0); //Write Discrete Module. Passing 0 instead of a channel will write data for all of the channels at once. - int readAnalog(uint8_t slot, uint8_t channel); //Read Analog Module. Returns 32 bits of data. 16/14/12/etc bit modules are not scaled and will return a bit appropriate value. - float readTemperature(uint8_t slot, uint8_t channel); //Read Temperature Module. Returns float. - void writeAnalog(uint32_t data,uint8_t slot, uint8_t channel); //Write Analog Module. Send up to 32 bits of data. 16/14/12/etc bit modules are masked on Base Controller - void readBlockData(char *buf, uint16_t len,uint16_t offset, uint8_t type); //Read raw data buffers. Allows for data updates for large numbers of points. - void writeBlockData(char *buf, uint16_t len,uint16_t offset, uint8_t type); //Write to raw data buffers. Allows for data updates for large numbers of points. - - //PWM Module functions - For more info see function headers in P1AM.cpp - void writePWM(float duty,uint32_t freq,uint8_t slot,uint8_t channel); //Set duty cycle and frequency of a PWM module channel - void writePWMDuty(float duty,uint8_t slot,uint8_t channel); //Set duty cycle of a PWM module channel without changing its frequency - void writePWMFreq(uint32_t freq,uint8_t slot,uint8_t channel); //Set frequency of a PWM module channel without changing its duty cycle - void writePWMDir(bool data,uint8_t slot, uint8_t channel); //Set state of PWM channel when configured for DIR mode - - //Utility Functions - For more info see function headers in P1AM.cpp - uint8_t printModules(); //Print list of all signed on modules to the Serial monitor - uint8_t checkUnderRange(uint8_t slot, uint8_t channel = 0);//Returns 1 if slot channel is under range and supports this function - uint8_t checkOverRange(uint8_t slot, uint8_t channel = 0);//Returns 1 if slot channel is over range and supports this function - uint8_t checkBurnout(uint8_t slot, uint8_t channel = 0);//Returns 1 if slot channel is in burnout and supports this function - uint8_t check24V(uint8_t slot); //Returns 1 if slot is missing 24V and supports this status - char readStatus(int byteNum,int slot); //Return 1 status byte for a module in a slot. See !!!!! THIS DOC REFERENCED !!!!!! for details - void readStatus(char buf[], uint8_t slot); //Return all status bytes for a module in a slot. See !!!!! THIS DOC REFERENCED !!!!!! for details - bool configureModule(char cfgData[],uint8_t slot); //Select slot and pass in buffer that contains configuration data see !!!!! THIS DOC REFERENCED !!!!!! for details - bool configureModule(const char cfgData[], uint8_t slot); - void readModuleConfig(char cfgData[], uint8_t slot); //Select slot and pass in buffer to return configurationd data to. Does not indicate config is live - void configWD(uint16_t milliseconds, uint8_t toggle); //Configure Watchdog Behaviour - void startWD(); //Start Watchdog Functionality - void stopWD(); //Stop Watchdog Functionality - void petWD(); //Pet WD in case you aren't talking to Base Controller often - uint32_t getFwVersion(); //Returns the Base Controller FW version - bool isBaseActive(); //Check if Base Controller is currently configured. If not, call init. - uint8_t checkConnection(uint8_t numberOfModules = 0); //Checks the modules to see if a connection has been lost. Returns first missing module. - bool Base_Controller_FW_UPDATE(unsigned int fwLen); //For FW update of Base Controller - moduleProps readSlotProps(uint8_t slot); //Returns the module properties at the given slot location. - - //Label functions - functionally the same as the above Data IO but use the channelLabel datatype for easier to read code. - uint32_t readDiscrete(channelLabel label); - void writeDiscrete(uint32_t data, channelLabel label); - int readAnalog(channelLabel label); - float readTemperature(channelLabel label); - void writeAnalog(uint32_t data, channelLabel label); - void writePWM(float duty,uint32_t freq, channelLabel label); - void writePWMDuty(float duty, channelLabel label); - void writePWMFreq(uint32_t freq, channelLabel label); - void writePWMDir(bool data, channelLabel label); - uint8_t checkUnderRange(channelLabel label); - uint8_t checkOverRange(channelLabel label); - uint8_t checkBurnout(channelLabel label); - - //Private functions for Base Controller communication. - private: - uint8_t spiSendRecvByte(uint8_t data); - uint32_t spiSendRecvInt(uint32_t data); - void spiSendRecvBuf(uint8_t *buf, int len, bool returnData = 0); - bool spiTimeout(uint32_t uS, uint8_t resendMsg = 0,uint16_t retryPeriod = 0); - char *loadConfigBuf(int moduleID); - bool handleHDR(uint8_t HDR); - void dataSync(); - struct moduleInfo{ - uint8_t dbLoc; //mdb location - }baseSlot[NUMBER_OF_MODULES]; - -}; - -extern P1AM P1; //Default P1AM class instance - -#endif +/* +MIT License + +Copyright (c) 2023 FACTS Engineering, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + P1AM.h - Library for Communicating with P1000 Series Modules +*/ + +#ifndef P1AM_h +#define P1AM_h + +#include "Arduino.h" +#include "SPI.h" +#include "Module_List.h" +#include "defines.h" + +struct channelLabel{ //Used to call functions through names rather than slot/channel numbers. + uint8_t slot; + uint8_t channel; +}; + +class P1AM{ + public: + P1AM(); + + //Init + uint8_t init(); //Initialise modules in the base. Returns the number of slots that have signed on. + void enableBaseController(bool state); //Enable or Disable base controller. Automatically called in init. + uint16_t rollCall(const char* moduleNames[], uint8_t numberOfModules); //Pass in an array of module names to check if current modules in base match. + + //Data IO Functions - For more info see function headers in P1AM.cpp + uint32_t readDiscrete(uint8_t slot, uint8_t channel = 0); //Read Discrete Module. Passing 0 instead of a channel will return data from all of the channels at once. + void writeDiscrete(uint32_t data,uint8_t slot, uint8_t channel = 0); //Write Discrete Module. Passing 0 instead of a channel will write data for all of the channels at once. + int readAnalog(uint8_t slot, uint8_t channel); //Read Analog Module. Returns 32 bits of data. 16/14/12/etc bit modules are not scaled and will return a bit appropriate value. + float readTemperature(uint8_t slot, uint8_t channel); //Read Temperature Module. Returns float. + void writeAnalog(uint32_t data,uint8_t slot, uint8_t channel); //Write Analog Module. Send up to 32 bits of data. 16/14/12/etc bit modules are masked on Base Controller + void readBlockData(char *buf, uint16_t len,uint16_t offset, uint8_t type); //Read raw data buffers. Allows for data updates for large numbers of points. + void writeBlockData(char *buf, uint16_t len,uint16_t offset, uint8_t type); //Write to raw data buffers. Allows for data updates for large numbers of points. + + //PWM Module functions - For more info see function headers in P1AM.cpp + void writePWM(float duty,uint32_t freq,uint8_t slot,uint8_t channel); //Set duty cycle and frequency of a PWM module channel + void writePWMDuty(float duty,uint8_t slot,uint8_t channel); //Set duty cycle of a PWM module channel without changing its frequency + void writePWMFreq(uint32_t freq,uint8_t slot,uint8_t channel); //Set frequency of a PWM module channel without changing its duty cycle + void writePWMDir(bool data,uint8_t slot, uint8_t channel); //Set state of PWM channel when configured for DIR mode + + //Utility Functions - For more info see function headers in P1AM.cpp + uint8_t printModules(); //Print list of all signed on modules to the Serial monitor + uint8_t checkUnderRange(uint8_t slot, uint8_t channel = 0);//Returns 1 if slot channel is under range and supports this function + uint8_t checkOverRange(uint8_t slot, uint8_t channel = 0);//Returns 1 if slot channel is over range and supports this function + uint8_t checkBurnout(uint8_t slot, uint8_t channel = 0);//Returns 1 if slot channel is in burnout and supports this function + uint8_t check24V(uint8_t slot); //Returns 1 if slot is missing 24V and supports this status + char readStatus(int byteNum,int slot); //Return 1 status byte for a module in a slot. See !!!!! THIS DOC REFERENCED !!!!!! for details + void readStatus(char buf[], uint8_t slot); //Return all status bytes for a module in a slot. See !!!!! THIS DOC REFERENCED !!!!!! for details + bool configureModule(char cfgData[],uint8_t slot); //Select slot and pass in buffer that contains configuration data see !!!!! THIS DOC REFERENCED !!!!!! for details + bool configureModule(const char cfgData[], uint8_t slot); + void readModuleConfig(char cfgData[], uint8_t slot); //Select slot and pass in buffer to return configurationd data to. Does not indicate config is live + void configWD(uint16_t milliseconds, uint8_t toggle); //Configure Watchdog Behaviour + void startWD(); //Start Watchdog Functionality + void stopWD(); //Stop Watchdog Functionality + void petWD(); //Pet WD in case you aren't talking to Base Controller often + uint32_t getFwVersion(); //Returns the Base Controller FW version + bool isBaseActive(); //Check if Base Controller is currently configured. If not, call init. + uint8_t checkConnection(uint8_t numberOfModules = 0); //Checks the modules to see if a connection has been lost. Returns first missing module. + bool Base_Controller_FW_UPDATE(unsigned int fwLen); //For FW update of Base Controller + moduleProps readSlotProps(uint8_t slot); //Returns the module properties at the given slot location. + + //Label functions - functionally the same as the above Data IO but use the channelLabel datatype for easier to read code. + uint32_t readDiscrete(channelLabel label); + void writeDiscrete(uint32_t data, channelLabel label); + int readAnalog(channelLabel label); + float readTemperature(channelLabel label); + void writeAnalog(uint32_t data, channelLabel label); + void writePWM(float duty,uint32_t freq, channelLabel label); + void writePWMDuty(float duty, channelLabel label); + void writePWMFreq(uint32_t freq, channelLabel label); + void writePWMDir(bool data, channelLabel label); + uint8_t checkUnderRange(channelLabel label); + uint8_t checkOverRange(channelLabel label); + uint8_t checkBurnout(channelLabel label); + + //Private functions for Base Controller communication. + private: + uint8_t spiSendRecvByte(uint8_t data); + uint32_t spiSendRecvInt(uint32_t data); + void spiSendRecvBuf(uint8_t *buf, int len, bool returnData = 0); + bool spiTimeout(uint32_t uS, uint8_t resendMsg = 0,uint16_t retryPeriod = 0); + char *loadConfigBuf(int moduleID); + bool handleHDR(uint8_t HDR); + void dataSync(); + struct moduleInfo{ + uint8_t dbLoc; //mdb location + }baseSlot[NUMBER_OF_MODULES]; + +}; + +extern P1AM P1; //Default P1AM class instance + +#endif diff --git a/src/P1_HSC.cpp b/src/P1_HSC.cpp index 0e458be..b5cf388 100644 --- a/src/P1_HSC.cpp +++ b/src/P1_HSC.cpp @@ -1,7 +1,7 @@ /* MIT License -Copyright (c) 2019 FACTS Engineering, LLC +Copyright (c) 2023 FACTS Engineering, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -198,7 +198,6 @@ Example Code: void P1_HSC_Channel::setPosition(int counts){ uint8_t registerOffset = 0; uint32_t bitPosition = 0; - uint32_t checkCommand = 0; if(channelNumber == 1){ registerOffset = 5; diff --git a/src/defines.h b/src/defines.h index d1d765e..d1e988a 100644 --- a/src/defines.h +++ b/src/defines.h @@ -1,6 +1,6 @@ /*MIT License -Copyright (c) 2019 FACTS Engineering, LLC +Copyright (c) 2023 FACTS Engineering, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -24,10 +24,6 @@ SOFTWARE. #ifndef defines_h #define defines_h -#ifndef NUMBER_OF_MODULES - #define NUMBER_OF_MODULES 15 //Current max 15 Modules -#endif - #define DEBUG_PRINT_ON //Comment this out to stop debug messages from being printed to the Serial console. This only will affect messages from this library. #ifdef DEBUG_PRINT_ON @@ -38,10 +34,24 @@ SOFTWARE. #define debugPrint(a) //nothing, disables printing from this function #endif -#define slaveSelectPin A3 -#define slaveAckPin A4 -#define SWITCH_BUILTIN 31 -#define baseEnable 33 + +#ifdef _VARIANT_P1AM_200 + #define NUMBER_OF_MODULES 8 + #define slaveSelectPin 44 + #define slaveAckPin 47 + #define SWITCH_BUILTIN 31 + #define baseEnable 46 + #define _P1AM_SPI SPI2 +#else + #define NUMBER_OF_MODULES 15 + #define slaveSelectPin A3 + #define slaveAckPin A4 + #define SWITCH_BUILTIN 31 + #define baseEnable 33 + #define _P1AM_SPI SPI +#endif + + #define MOD_HDR 0x02 #define VERSION_HDR 0x03