|
| 1 | +/* |
| 2 | + * The MySensors Arduino library handles the wireless radio link and protocol |
| 3 | + * between your home built sensors/actuators and HA controller of choice. |
| 4 | + * The sensors forms a self healing radio network with optional repeaters. Each |
| 5 | + * repeater and gateway builds a routing tables in EEPROM which keeps track of the |
| 6 | + * network topology allowing messages to be routed to nodes. |
| 7 | + * |
| 8 | + * Created by Henrik Ekblad <[email protected]> |
| 9 | + * Copyright (C) 2013-2025 Sensnology AB |
| 10 | + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors |
| 11 | + * |
| 12 | + * Documentation: http://www.mysensors.org |
| 13 | + * Support Forum: http://forum.mysensors.org |
| 14 | + * |
| 15 | + * This program is free software; you can redistribute it and/or |
| 16 | + * modify it under the terms of the GNU General Public License |
| 17 | + * version 2 as published by the Free Software Foundation. |
| 18 | + */ |
| 19 | + |
| 20 | +/** |
| 21 | + * @file MyHwSTM32.cpp |
| 22 | + * @brief Hardware abstraction layer for STM32 microcontrollers using STM32duino core |
| 23 | + * |
| 24 | + * This implementation uses the official STM32duino Arduino core which provides |
| 25 | + * STM32Cube HAL underneath. It supports a wide range of STM32 families including |
| 26 | + * F0, F1, F4, L0, L4, G0, G4, H7, and more. |
| 27 | + * |
| 28 | + * Tested on: |
| 29 | + * - STM32F401CC/CE Black Pill |
| 30 | + * - STM32F411CE Black Pill |
| 31 | + * |
| 32 | + * Pin Mapping Example (STM32F4 Black Pill): |
| 33 | + * |
| 34 | + * nRF24L01+ Radio (SPI1): |
| 35 | + * - SCK: PA5 |
| 36 | + * - MISO: PA6 |
| 37 | + * - MOSI: PA7 |
| 38 | + * - CSN: PA4 |
| 39 | + * - CE: PB0 (configurable via MY_RF24_CE_PIN) |
| 40 | + * |
| 41 | + * RFM69/RFM95 Radio (SPI1): |
| 42 | + * - SCK: PA5 |
| 43 | + * - MISO: PA6 |
| 44 | + * - MOSI: PA7 |
| 45 | + * - CS: PA4 |
| 46 | + * - IRQ: PA3 (configurable) |
| 47 | + * - RST: PA2 (configurable) |
| 48 | + */ |
| 49 | + |
| 50 | +#include "MyHwSTM32.h" |
| 51 | + |
| 52 | +bool hwInit(void) |
| 53 | +{ |
| 54 | +#if !defined(MY_DISABLED_SERIAL) |
| 55 | + MY_SERIALDEVICE.begin(MY_BAUD_RATE); |
| 56 | +#if defined(MY_GATEWAY_SERIAL) |
| 57 | + // Wait for serial port to connect (needed for native USB) |
| 58 | + while (!MY_SERIALDEVICE) { |
| 59 | + ; // Wait for serial port connection |
| 60 | + } |
| 61 | +#endif |
| 62 | +#endif |
| 63 | + |
| 64 | + // STM32duino EEPROM library auto-initializes on first use |
| 65 | + // No explicit initialization required |
| 66 | + return true; |
| 67 | +} |
| 68 | + |
| 69 | +void hwReadConfigBlock(void *buf, void *addr, size_t length) |
| 70 | +{ |
| 71 | + uint8_t *dst = static_cast<uint8_t *>(buf); |
| 72 | + int pos = reinterpret_cast<int>(addr); |
| 73 | + |
| 74 | + for (size_t i = 0; i < length; i++) { |
| 75 | + dst[i] = EEPROM.read(pos + i); |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +void hwWriteConfigBlock(void *buf, void *addr, size_t length) |
| 80 | +{ |
| 81 | + uint8_t *src = static_cast<uint8_t *>(buf); |
| 82 | + int pos = reinterpret_cast<int>(addr); |
| 83 | + |
| 84 | + for (size_t i = 0; i < length; i++) { |
| 85 | + EEPROM.update(pos + i, src[i]); |
| 86 | + } |
| 87 | + |
| 88 | + // Commit changes to flash (STM32duino EEPROM emulation) |
| 89 | + // Note: This happens automatically on next read or explicit commit |
| 90 | +} |
| 91 | + |
| 92 | +uint8_t hwReadConfig(const int addr) |
| 93 | +{ |
| 94 | + return EEPROM.read(addr); |
| 95 | +} |
| 96 | + |
| 97 | +void hwWriteConfig(const int addr, uint8_t value) |
| 98 | +{ |
| 99 | + EEPROM.update(addr, value); |
| 100 | +} |
| 101 | + |
| 102 | +void hwWatchdogReset(void) |
| 103 | +{ |
| 104 | +#ifdef IWDG |
| 105 | + // Reset independent watchdog if enabled |
| 106 | + // Note: Watchdog must be configured separately in sketch if needed |
| 107 | + #if defined(HAL_IWDG_MODULE_ENABLED) |
| 108 | + // Using STM32 HAL |
| 109 | + // Implementation depends on whether user has initialized IWDG |
| 110 | + // For safety, we only reset if it's running |
| 111 | + #endif |
| 112 | +#endif |
| 113 | + // No-op if watchdog not enabled - safer default |
| 114 | +} |
| 115 | + |
| 116 | +void hwReboot(void) |
| 117 | +{ |
| 118 | + NVIC_SystemReset(); |
| 119 | +} |
| 120 | + |
| 121 | +void hwRandomNumberInit(void) |
| 122 | +{ |
| 123 | + // Use internal temperature sensor and ADC noise as entropy source |
| 124 | + // This provides reasonably good random seed values |
| 125 | + |
| 126 | +#ifdef ADC1 |
| 127 | + uint32_t seed = 0; |
| 128 | + |
| 129 | + // Read multiple samples from different sources for entropy |
| 130 | + for (uint8_t i = 0; i < 32; i++) { |
| 131 | + uint32_t value = 0; |
| 132 | + |
| 133 | + #ifdef TEMP_SENSOR_AVAILABLE |
| 134 | + // Try to read internal temperature sensor if available |
| 135 | + value ^= analogRead(ATEMP); |
| 136 | + #endif |
| 137 | + |
| 138 | + #ifdef VREF_AVAILABLE |
| 139 | + // Mix in internal voltage reference reading |
| 140 | + value ^= analogRead(AVREF); |
| 141 | + #endif |
| 142 | + |
| 143 | + // Mix in current time |
| 144 | + value ^= hwMillis(); |
| 145 | + |
| 146 | + // Mix in system tick |
| 147 | + value ^= micros(); |
| 148 | + |
| 149 | + // Accumulate into seed |
| 150 | + seed ^= (value & 0x7) << (i % 29); |
| 151 | + |
| 152 | + // Small delay to ensure values change |
| 153 | + delayMicroseconds(100); |
| 154 | + } |
| 155 | + |
| 156 | + randomSeed(seed); |
| 157 | +#else |
| 158 | + // Fallback: use millis as weak entropy source |
| 159 | + randomSeed(hwMillis()); |
| 160 | +#endif |
| 161 | +} |
| 162 | + |
| 163 | +bool hwUniqueID(unique_id_t *uniqueID) |
| 164 | +{ |
| 165 | +#ifdef UID_BASE |
| 166 | + // STM32 unique device ID is stored at a fixed address |
| 167 | + // Length is 96 bits (12 bytes) but we store 16 bytes for compatibility |
| 168 | + |
| 169 | + uint32_t *id = (uint32_t *)UID_BASE; |
| 170 | + uint8_t *dst = (uint8_t *)uniqueID; |
| 171 | + |
| 172 | + // Copy 12 bytes of unique ID |
| 173 | + for (uint8_t i = 0; i < 12; i++) { |
| 174 | + dst[i] = ((uint8_t *)id)[i]; |
| 175 | + } |
| 176 | + |
| 177 | + // Pad remaining bytes with zeros |
| 178 | + for (uint8_t i = 12; i < 16; i++) { |
| 179 | + dst[i] = 0; |
| 180 | + } |
| 181 | + |
| 182 | + return true; |
| 183 | +#else |
| 184 | + // Unique ID not available on this variant |
| 185 | + return false; |
| 186 | +#endif |
| 187 | +} |
| 188 | + |
| 189 | +uint16_t hwCPUVoltage(void) |
| 190 | +{ |
| 191 | +#if defined(AVREF) && defined(__HAL_RCC_ADC1_CLK_ENABLE) |
| 192 | + // Read internal voltage reference to calculate VDD |
| 193 | + // VREFINT is typically 1.2V (varies by STM32 family) |
| 194 | + |
| 195 | + uint32_t vrefint = analogRead(AVREF); |
| 196 | + |
| 197 | + if (vrefint > 0) { |
| 198 | + // Calculate VDD in millivolts |
| 199 | + // Formula: VDD = 3.3V * 4096 / ADC_reading |
| 200 | + // Adjusted: VDD = 1200mV * 4096 / vrefint_reading |
| 201 | + return (uint16_t)((1200UL * 4096UL) / vrefint); |
| 202 | + } |
| 203 | +#endif |
| 204 | + |
| 205 | + // Return typical 3.3V if measurement not available |
| 206 | + return 3300; |
| 207 | +} |
| 208 | + |
| 209 | +uint16_t hwCPUFrequency(void) |
| 210 | +{ |
| 211 | + // Return CPU frequency in 0.1 MHz units |
| 212 | + // F_CPU is defined by the build system (e.g., 84000000 for 84 MHz) |
| 213 | + return F_CPU / 100000UL; |
| 214 | +} |
| 215 | + |
| 216 | +int8_t hwCPUTemperature(void) |
| 217 | +{ |
| 218 | +#if defined(ATEMP) && defined(__HAL_RCC_ADC1_CLK_ENABLE) |
| 219 | + // Read internal temperature sensor |
| 220 | + // Note: Requires calibration values for accurate results |
| 221 | + |
| 222 | + int32_t temp_raw = analogRead(ATEMP); |
| 223 | + |
| 224 | + #ifdef TEMP110_CAL_ADDR |
| 225 | + // Use factory calibration if available (STM32F4, L4, etc.) |
| 226 | + uint16_t *temp30_cal = (uint16_t *)TEMP30_CAL_ADDR; |
| 227 | + uint16_t *temp110_cal = (uint16_t *)TEMP110_CAL_ADDR; |
| 228 | + |
| 229 | + if (temp30_cal && temp110_cal && *temp110_cal != *temp30_cal) { |
| 230 | + // Calculate temperature using two-point calibration |
| 231 | + // Formula: T = ((110-30) / (CAL_110 - CAL_30)) * (raw - CAL_30) + 30 |
| 232 | + int32_t temp = 30 + ((110 - 30) * (temp_raw - *temp30_cal)) / |
| 233 | + (*temp110_cal - *temp30_cal); |
| 234 | + |
| 235 | + // Apply user calibration |
| 236 | + temp = (temp - MY_STM32_TEMPERATURE_OFFSET) / MY_STM32_TEMPERATURE_GAIN; |
| 237 | + |
| 238 | + return (int8_t)temp; |
| 239 | + } |
| 240 | + #endif |
| 241 | + |
| 242 | + // Fallback: use typical values (less accurate) |
| 243 | + // Typical slope: 2.5 mV/°C, V25 = 0.76V for STM32F4 |
| 244 | + // This is a rough approximation |
| 245 | + float voltage = (temp_raw * 3.3f) / 4096.0f; |
| 246 | + int32_t temp = 25 + (int32_t)((voltage - 0.76f) / 0.0025f); |
| 247 | + |
| 248 | + return (int8_t)((temp - MY_STM32_TEMPERATURE_OFFSET) / MY_STM32_TEMPERATURE_GAIN); |
| 249 | +#else |
| 250 | + // Temperature sensor not available |
| 251 | + return FUNCTION_NOT_SUPPORTED; |
| 252 | +#endif |
| 253 | +} |
| 254 | + |
| 255 | +uint16_t hwFreeMem(void) |
| 256 | +{ |
| 257 | + // Calculate free heap memory |
| 258 | + // This uses newlib's mallinfo if available |
| 259 | + |
| 260 | +#ifdef STACK_TOP |
| 261 | + extern char *__brkval; |
| 262 | + extern char __heap_start; |
| 263 | + |
| 264 | + char *heap_end = __brkval ? __brkval : &__heap_start; |
| 265 | + char stack_var; |
| 266 | + |
| 267 | + // Calculate space between heap and stack |
| 268 | + return (uint16_t)(&stack_var - heap_end); |
| 269 | +#else |
| 270 | + // Alternative method: try to allocate and measure |
| 271 | + // Not implemented to avoid fragmentation |
| 272 | + return FUNCTION_NOT_SUPPORTED; |
| 273 | +#endif |
| 274 | +} |
| 275 | + |
| 276 | +int8_t hwSleep(uint32_t ms) |
| 277 | +{ |
| 278 | + // TODO: Implement low-power sleep mode |
| 279 | + // For now, use simple delay |
| 280 | + // Future: Use STM32 STOP or STANDBY mode with RTC wakeup |
| 281 | + |
| 282 | + (void)ms; |
| 283 | + return MY_SLEEP_NOT_POSSIBLE; |
| 284 | +} |
| 285 | + |
| 286 | +int8_t hwSleep(const uint8_t interrupt, const uint8_t mode, uint32_t ms) |
| 287 | +{ |
| 288 | + // TODO: Implement interrupt-based sleep |
| 289 | + // Future: Configure EXTI and enter STOP mode |
| 290 | + |
| 291 | + (void)interrupt; |
| 292 | + (void)mode; |
| 293 | + (void)ms; |
| 294 | + return MY_SLEEP_NOT_POSSIBLE; |
| 295 | +} |
| 296 | + |
| 297 | +int8_t hwSleep(const uint8_t interrupt1, const uint8_t mode1, |
| 298 | + const uint8_t interrupt2, const uint8_t mode2, uint32_t ms) |
| 299 | +{ |
| 300 | + // TODO: Implement dual-interrupt sleep |
| 301 | + |
| 302 | + (void)interrupt1; |
| 303 | + (void)mode1; |
| 304 | + (void)interrupt2; |
| 305 | + (void)mode2; |
| 306 | + (void)ms; |
| 307 | + return MY_SLEEP_NOT_POSSIBLE; |
| 308 | +} |
0 commit comments