From 0d8d6b8544269fd6ef62bc5fd31e8552c13514f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?UIO=E8=BE=9B=E5=8F=AF?= <2692571183@qq.com> Date: Wed, 31 Jul 2024 14:11:58 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E9=9A=90=E8=97=8F=E9=83=A8=E5=88=86?= =?UTF-8?q?=E7=A7=AF=E6=9C=A8=E5=9D=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dstemp.cpp | 619 +++++++++++++++++++++++++++++++++++++++++++++++++++++ dstemp.ts | 58 +++++ pxt.json | 7 +- 3 files changed, 681 insertions(+), 3 deletions(-) create mode 100644 dstemp.cpp create mode 100644 dstemp.ts diff --git a/dstemp.cpp b/dstemp.cpp new file mode 100644 index 0000000..da5a00c --- /dev/null +++ b/dstemp.cpp @@ -0,0 +1,619 @@ +/** +* Bill Siever +* 2018-10-10 +* 2021-02-27 CODAL and V2 updates +* +* Development environment specifics: +* Written in Microsoft PXT +* Based on SparkFun weather:bit for micro:bit (https://github.com/sparkfun/pxt-weather-bit) +* +* This code is released under the [MIT License](http://opensource.org/licenses/MIT). +* Please review the LICENSE.md file included with this example. If you have any questions +* or concerns with licensing, please contact techsupport@sparkfun.com. +* Distributed as-is; no warranty is given. +*/ + +#include "pxt.h" +#include +#include +#include "app_error.h" +#include "nrf.h" +#include "MicroBitSystemTimer.h" + + +// Enable debugging: Debugging uses #ifdefs, so uncomment or comment out +//#define DEBUG 1 +// DEBUG uses ioPin P1 to indicate sampling of read (for timing calibration) +using namespace pxt; + + +#if MICROBIT_CODAL +// ********************* V2/CODAL Specific Functions *********************** + +// Example of port mapping from V1 codebase +// From: codal-nrf52/source/NRF52Pin.cpp +#ifdef NRF_P1 + #define PORT (pin < 32 ? NRF_P0 : NRF_P1) + #define PIN ((pin) & 31) + #define NUM_PINS 48 +#else + #define PORT (NRF_P0) + #define PIN (pin) + #define NUM_PINS 32 +#endif + + #define _wait_us(us) system_timer_wait_cycles((us)==0? 1: (((10*500*(us))/470))) // Adjusted to 11 + #define _GPIO int + static void setToInput(_GPIO pin) { PORT->PIN_CNF[PIN] &= 0xfffffffc; } + static void setToOutput(_GPIO pin) { PORT->PIN_CNF[PIN] |= 3; } + static void setPinValue(_GPIO pin, int val) { if (val) PORT->OUTSET = 1 << PIN; else PORT->OUTCLR = 1 << PIN;} + static bool getPinValue(_GPIO pin) { return (PORT->IN & (1 << PIN)) ? 1 : 0; } + + static void configTimer() { + // Ensure that the external crystal is being used (higher precision) + // and that the system timer is in 32-bit mode + static NRF_TIMER_Type *timer = NULL; + + // If we haven't gotten the timer yet, do startup tasks, including getting the timer. + if(timer == NULL) { + NVIC_DisableIRQ(TIMER1_IRQn); + // Ensure the HFCLOCK is running + NRF_CLOCK_Type *clock = NRF_CLOCK; + clock->TASKS_HFCLKSTART = 1; + + // Get the timer (ensures this is only done once) + timer = NRF_TIMER1; + // Disable timer + timer->TASKS_STOP = 1; + // Set bit mode + timer->BITMODE = 3; // Ensure 32-bit mode (error in CODAL was resulting in 24-bit mode) + // Restart it + timer->TASKS_START = 1; + NVIC_EnableIRQ(TIMER1_IRQn); + + // Call timer calibration + //system_timer_calibrate_cycles(); + } + } + +#else +// ********************* V1/AL Specific Functions *********************** + // Map to NRF library + #define _wait_us(us) wait_us(((us)>5)?(us)-5:0) + #define _GPIO gpio_t* + #define setToInput(pin) gpio_dir((pin), PIN_INPUT) + #define setToOutput(pin) gpio_dir((pin), PIN_OUTPUT) + #define setPinValue(pin, val) gpio_write((pin), (val)) + #define getPinValue(pin) gpio_read((pin)) +#endif + + +#ifdef DEBUG +// If debugging is enabled create an "indicatePin" to assist timing tests +#if MICROBIT_CODAL + _GPIO indicatePin = uBit.io.P1.name; +#else + gpio_t indicateObj; + _GPIO indicatePin = &indicateObj; +#endif +#endif + +namespace dstemp { + + // ************* Forward Decalarations + void loopUntilSent(ManagedString str); + void loopUntilSent(int str); + void writeBit(_GPIO ioPin, bool one, bool finalYield = true); + void writeByte(_GPIO ioPin, uint8_t b, bool finalYield = true); + bool readBit(_GPIO ioPin); + bool readScratchpad(_GPIO ioPin, float& temp); + bool resetAndCheckPresence(_GPIO ioPin); + bool configure(_GPIO pin); + bool startConversion(_GPIO ioPin); + + // ************* Timing related constants + // Times related to time slots and read/write operations + const int TIME_SLOT = 90; // Time slot length = 60-120uS T_SLOT; Rounded up from min + const int TIME_RECOV = 15; // Recovery time between bits = 1uS T_REC; Rounded up for pull-up time. + const int TIME_ZERO_LOW = TIME_SLOT; // Zero low time = 60-120uS; T_LOW0; Assume 100% of time slot + const int TIME_ONE_LOW = 0; // One Low Time = 1-15uS; T_LOW1; Hand calibrated via scope to be ~11.5uS + + const int TIME_READ_START = 1; // Not used / Read start (>uS); Not used; Hand calibrated via extra call + const int TIME_READ_OFFSET = 0; // Hand Calibrated via scope; 1uS+overhead ==> 15uS from beginning of cycle (no more than 15uS) + const int TIME_SLAVE_WRITE_END = 61; // Time from start of cycle to the end of the slave impacting the bus: 15uS+45uS (rounded up) + + // Times related to reset / presence detection (all in uS) + const int TIME_RESET_LOW = 500; // Reset low = 480uS T_RSTL (rounded up) + const int TIME_RESET_HIGH = 500; // Reset High/Presence detection Time = 480 uS T_RSTH (rounded up) + const int TIME_POWER_UP = 1000; // Time for DS18B20 to power up before reset (not mentioned in Data Sheet; 1ms assumed sufficient) + const int TIME_POST_RESET_TO_DETECT = 10; // Time after reset to wait before checking for presence = 15uS T_PDHIGH (rounded down) + const int TIME_PRESENCE_DETECT = 300; // Max time to wait after releasing reset for potential detect = 60uS+240uS T_PDHIGH+T_PDLOW + + const int TIME_CONVERSION = 760; // Conversion Time = 750mS T_CONV (rounded up) + + const float ERROR_SENTINEL = -INFINITY; // Sentinel value to return for errors. + + const int HIGH_ALARM = 0xFF; // High Alarm Value (both set and confirmed; Should be non-zero) + const int LOW_ALARM = 0x80; // Low Alarm Value (both set and confirmed; Should be non-zero) + + const int MAX_TRIES = 3; // Max tries to attempt conversion before fail + + // ************* State variables + int errorObjectIdx = 0; + int errorPort = 0; + Action errorHandler = NULL; + + + // ************* Blocks + + //% + void setErrorHandler(Action a) { + // Release any prior error handler + if(errorHandler) + pxt::decr(errorHandler); + errorHandler = a; + if(errorHandler) + pxt::incr(errorHandler); + } + + /* + * Helper method to send an actual error code to the registered handler. + * It will set error values and immediately call the handler (i.e., no race condition should occur) + */ + void error(int objIndex, int port) { + errorObjectIdx = objIndex; + errorPort = port - MICROBIT_ID_IO_P0; + if(errorHandler) { + pxt::runAction0(errorHandler); + } + } + + /* Configure the device for 12-bit conversion and set High/Low Alarm Values (Magic numbers too.) + * @param ioPin the IO pin to use + * @returns true on (assumed) success; false on known failure + */ + bool configure(_GPIO ioPin) { + if(resetAndCheckPresence(ioPin) == false) { + return false; + } + // Write ROM command: Skip ROM Command CCh: To address all devices + writeByte(ioPin, 0xCC); + + // Write: Function Command Write Scratchpad 4Eh + writeByte(ioPin, 0x4E); + + // Write: Data for Function command - Alarm High Byte + writeByte(ioPin, HIGH_ALARM); + // Write: Data for Function command - Alarm Low Byte + writeByte(ioPin, LOW_ALARM); + // Write: Data for Function command - Conversion Configuration Byte + writeByte(ioPin, 0x7F); // Write bits for 12-bit conversion. + return true; + } + + /* Start a temperature conversion + * @param ioPin the IO pin to use + * @returns true on success; false on failure + */ + bool startConversion(_GPIO ioPin) { + if(resetAndCheckPresence(ioPin) == false) { + return false; + } + + // Write ROM command: Skip ROM Command CCh: To address all devices + writeByte(ioPin, 0xCC); + + // Write: Function command - Convert 44h + writeByte(ioPin, 0x44, false); + + // Read Time Slot + return readBit(ioPin)==0; + } + + +// Calibration code that can be used to debug timing issues and +// identify values for constants +#ifdef DEBUG + void calibrate(_GPIO gpio) { + // Misc tests. +#if MICROBIT_CODAL + loopUntilSent("CODAL"); +#else + loopUntilSent("DAL"); +#endif + // Setup + setPinValue(gpio, 1); + setToOutput(gpio); + setPinValue(indicatePin, 1); + setToOutput(indicatePin); + + setPinValue(gpio, 1); + _wait_us(100); + setPinValue(gpio, 0); + _wait_us(100); + setPinValue(gpio, 1); + _wait_us(200); + setPinValue(gpio, 0); + _wait_us(1000); + setPinValue(gpio, 1); + + // Calibrate input loop + // v1: 1147 uS for 2000 iterations; 0.5735/iteration + // v2: 127 uS for 2000 iterations ; 0.0635uS/ iteration + setPinValue(indicatePin, 0); + setToInput(gpio); + uint32_t maxCounts = 2000; + bool b = true; + do { + // If the bus goes low, its a 0 + b = b && getPinValue(gpio); + } while(maxCounts-- > 0); + setPinValue(indicatePin, 1); + _wait_us(100); + + + setPinValue(indicatePin, 0); + + // v1: 3551 uS for 2000 iterations; 1.775 uS/Iteration + // v2: 346 for 2000 iterations ; 0.173uS/iter + maxCounts = 2000; + setToInput(gpio); + // Check for presence pulse + bool presence = false; + do { + presence = presence || (getPinValue(gpio) == 0); + } while (maxCounts-- > 0); + setPinValue(indicatePin, 1); + + _wait_us(200); + // v1: Aim for exactly 200uS + //maxCounts = (int)(200/1.775); + // v2: Aim for exactly 200uS + maxCounts = (int)(200/0.173); + setPinValue(indicatePin, 0); + setToInput(gpio); + // Check for presence pulse + presence = false; + do { + presence = presence || (getPinValue(gpio) == 0); + } while (maxCounts-- > 0); + setPinValue(indicatePin, 1); + + setToOutput(gpio); + } +#endif + + + + //% + float celsius(int pin) { + // Only needs to be done once, but done every call... +#if MICROBIT_CODAL + // CODAL may not be using external crystal by default; Update it + // May also be using 24-bit timer +#ifdef SOFTDEVICE_PRESENT + if (!ble_running()) // Only configTimer if either no soft-dev or no ble +#endif + configTimer(); +#endif + // Get corresponding I/O ioPin Object + MicroBitPin *mbp = getPin(pin); // This returns a "uBit.io.P0" type thing +#if MICROBIT_CODAL + _GPIO gpio = mbp->name; +#else + gpio_t gpioObj; + _GPIO gpio = &gpioObj; + gpio_init(gpio, mbp->name); +#endif + + // If debugging, configure the indicate pin +#ifdef DEBUG +#if MICROBIT_CODAL +#else + MicroBitPin* indicate = &uBit.io.P1; + gpio_init(indicatePin, indicate->name); +#endif + + setToOutput(indicatePin); + setPinValue(indicatePin, 0); +#endif + + +#ifdef DEBUG + // Optional calibration (rather than actually running) + // calibrate(gpio); + // return 0; +#endif + + +#ifdef DEBUG + { + char buffer[24]; + sprintf(buffer, "pin: %d\n", mbp->name); + loopUntilSent(buffer); + loopUntilSent("Celsius Block\n"); + } +#endif + bool success = false; + // 1. Check for valid device, configure it for conversion, and start conversion + for(int tries=0;tries0; maxIterations--) { + if(readBit(gpio) == 0) { + success = true; + break; + } else { + // Wait for conversion to complete. + uBit.sleep(0); + } + } + // If not successful, error + if(success==false) { + error(4, pin); + goto return_error; + } + + // 3. Retrieve Data + for(int tries=0;tries>=1) { + writeBit(ioPin, (b & 0x01), i!=7); + } + } + + /* + * Read a single bit + * @param ioPin the MicroBitPin pin to read from + * @return true if the bit is a 1; false otherwise + */ + bool readBit(_GPIO ioPin) { + // Ensure recovery time + setToOutput(ioPin); + setPinValue(ioPin,1); + _wait_us(TIME_RECOV); + + // Start the transaction + setPinValue(ioPin, 0); + _wait_us(1); // Updated to 1 for minimum wait + setPinValue(ioPin, 1); + setToInput(ioPin); + + // Start high (default) + bool b = true; + +#ifdef DEBUG + setPinValue(indicatePin, 1); +#endif + + // Sample for ~70uS after releasing +#if MICROBIT_CODAL + // v2: 156 uS for 2000 iterations ; 0.077uS/ iteration + uint32_t maxCounts = (int)(TIME_SLOT/0.0635); +#else + // v1: 115 uS for 200 iterations; 0.575uS/iteration + _wait_us(0); // Wait for ~6uS + uint32_t maxCounts = (int)(TIME_SLOT/0.57); +#endif + do { + // If the bus goes low, its a 0 + b = b && getPinValue(ioPin); + } while(maxCounts-->0); + + // Switch back to output + setPinValue(ioPin,1); + +#ifdef DEBUG + setPinValue(indicatePin, 0); +#endif + + return b; + } + + /* + * Read the DS18B20 Temperature from Scratch pad and confirm success (via High/Low and CRC). + * @param ioPin the MicroBitPin pin to use + * @param temp the temperature (on success) or + */ + // Assumes configuration and HIGH/LOW set already. + bool readScratchpad(_GPIO ioPin, float& temp) { + uint8_t data[9]; + int16_t value; + uint8_t crc=0; + // Read each byte + for(int j = 0; j<9; j++) { + // read each bit (LSB to MSB) + uint8_t b = 0; + for(int i=0; i<8; i++) { + bool bit = readBit(ioPin); + b |= (bit<>= 1; // Shift CRC to left + if(bit != lsb) // bit xor lsb + crc ^= 0x8C; + } + data[j] = b; + } + value = data[1]; + value <<= 8; + value |= data[0]; + temp = value; + temp /= 16.0; +#ifdef DEBUG + { + char buffer[40]; + sprintf(buffer, "data: %X %X %X %X %X %X %X %X %X\n", data[0],data[1],data[2],data[3],data[4],data[5],data[6],data[7],data[8]); + loopUntilSent(buffer); + sprintf(buffer, "crc: %X\n", crc); + loopUntilSent(buffer); + } +#endif + return crc==0 && data[2]==HIGH_ALARM && data[3]==LOW_ALARM; + } + + /* + * Reset the DS18B20 on the designated pin. + * @param ioPin the pin to use for the bus + * @returns true if a device is detected on the bus following the reset; false otherwise + */ + bool resetAndCheckPresence(_GPIO ioPin) { + // Set pin to High (and get I/O object) + setToOutput(ioPin); + setPinValue(ioPin, 1); + _wait_us(TIME_POWER_UP); // Possible power-up time + + // Set ioPin to output / apply reset signal + setPinValue(ioPin, 0); + _wait_us(TIME_RESET_LOW); // Wait for duration of reset pulse + + // Return pin to input for presence detection + setPinValue(ioPin, 1); + setToInput(ioPin); + _wait_us(TIME_POST_RESET_TO_DETECT); + +#if MICROBIT_CODAL + // v2: 462.5 for 2000 iterations ; 0.231uS/iter + // Padded down (the "release" needs to be complete) + int maxCounts = (int)(TIME_PRESENCE_DETECT/0.1); // Hand tuned values...Full presence period sample +#else + // v1: 1.705 uS/Iteration + int maxCounts = (int)(TIME_PRESENCE_DETECT/1); +#endif + + // Check for presence pulse (pulling line low) +#ifdef DEBUG + setPinValue(indicatePin,1); +#endif + bool presence = false; + do { + presence = presence || (getPinValue(ioPin) == 0); + } while (maxCounts-- > 0); + + // Confirm that it's released +#ifdef DEBUG + setPinValue(indicatePin,0); +#endif + bool release = getPinValue(ioPin)==1; + + // Success if the pin was pulled low and went high again + bool success = presence && release; + +#ifdef DEBUG + loopUntilSent("\npres= "); + loopUntilSent(presence); + loopUntilSent(" relese= "); + loopUntilSent(release); + loopUntilSent("\n"); +#endif + return success; // Return success or failure + } +} \ No newline at end of file diff --git a/dstemp.ts b/dstemp.ts new file mode 100644 index 0000000..6099804 --- /dev/null +++ b/dstemp.ts @@ -0,0 +1,58 @@ + //% color=#0000FF +//% icon="\uf2c8" +//% block="DS Temp" +namespace dstemp { + //% whenUsed + let errorHandler:Action = null; + //% whenUsed + let errorObjectIdx : number = 0; + //% whenUsed + let errorPort : number = -1; + + // TODO: Localization + const errorMsgs = [ "No Error", "Not Connected", "Start Error", "Read Timeout", "Conversion Failure"]; + + //% blockId="celsius" block="temperature (\u00B0\\C) on %pin|" + //% shim=dstemp::celsius + //% parts=dstemp trackArgs=0 + export function celsius(pin: DigitalPin) : number { + return 32.6; + } + + // Helper function + //% shim=dstemp::setErrorHandler + export function setErrorHandler(a: Action) { + errorHandler = a; + } + + // Helper function + //% shim=dstemp::getErrorObjectIdx + export function getErrorObjectIdx() : number { + return errorObjectIdx; + } + + // Helper function + //% shim=dstemp::getErrorPort + export function getErrorPort() : number { + return errorPort; + } + + /** + * Set a handler for errors + * @param errCallback The error handler + */ + //% blockId="error" block="temperature sensor error" + //% draggableParameters="reporter" weight=0 + export function sensorError(errCallback: (errorMessage: string, errorCode: number, port: number) => void) { + if(errCallback) { + errorHandler = () => { + let i = getErrorObjectIdx(); + let p = getErrorPort(); + errCallback(errorMsgs[i], i, p); + }; + } else { + errorHandler = null; + } + setErrorHandler(errorHandler); + }; +} diff --git a/pxt.json b/pxt.json index a3ca6ec..e35314b 100644 --- a/pxt.json +++ b/pxt.json @@ -5,8 +5,7 @@ "dependencies": { "core": "*", "microphone": "*", - "pxt-PlanetX-AI": "github:elecfreaks/pxt-PlanetX-AI#v1.0.2", - "microbit-dstemp": "github:bsiever/microbit-dstemp#v0.1.26" + "pxt-PlanetX-AI": "github:elecfreaks/pxt-PlanetX-AI#v1.0.2" }, "files": [ "README.md", @@ -19,7 +18,9 @@ "_locales/nb/pxt-PlanetX-strings.json", "_locales/el/pxt-PlanetX-strings.json", "_locales/el/pxt-PlanetX-jsdoc-strings.json", - "main.ts" + "main.ts", + "dstemp.ts", + "dstemp.cpp" ], "testFiles": [ "test.ts" From e2ce8b0b9f232ca1f19aa5039e620b5a8d2d574e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?UIO=E8=BE=9B=E5=8F=AF?= <2692571183@qq.com> Date: Wed, 31 Jul 2024 14:15:13 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=B1=8F=E8=94=BD=E5=AF=B9=E5=A4=96?= =?UTF-8?q?=E7=A7=AF=E6=9C=A8=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dstemp.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dstemp.ts b/dstemp.ts index 6099804..4cf9ac7 100644 --- a/dstemp.ts +++ b/dstemp.ts @@ -15,25 +15,25 @@ namespace dstemp { //% blockId="celsius" block="temperature (\u00B0\\C) on %pin|" //% shim=dstemp::celsius //% parts=dstemp trackArgs=0 - export function celsius(pin: DigitalPin) : number { + function celsius(pin: DigitalPin) : number { return 32.6; } // Helper function //% shim=dstemp::setErrorHandler - export function setErrorHandler(a: Action) { + function setErrorHandler(a: Action) { errorHandler = a; } // Helper function //% shim=dstemp::getErrorObjectIdx - export function getErrorObjectIdx() : number { + function getErrorObjectIdx() : number { return errorObjectIdx; } // Helper function //% shim=dstemp::getErrorPort - export function getErrorPort() : number { + function getErrorPort() : number { return errorPort; } @@ -43,7 +43,7 @@ namespace dstemp { */ //% blockId="error" block="temperature sensor error" //% draggableParameters="reporter" weight=0 - export function sensorError(errCallback: (errorMessage: string, errorCode: number, port: number) => void) { + function sensorError(errCallback: (errorMessage: string, errorCode: number, port: number) => void) { if(errCallback) { errorHandler = () => { let i = getErrorObjectIdx();