lib/spi_flash: New library for SPI flash commands
Change-Id: I81fb53eef259aa67fe27e96aef603933f04b060c
Reviewed-on: https://pigweed-review.googlesource.com/c/gonk/+/183214
Reviewed-by: Eric Holland <hollande@google.com>
Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
diff --git a/applications/spi_flash_test/BUILD.gn b/applications/spi_flash_test/BUILD.gn
index dd3cd63..6714ab6 100644
--- a/applications/spi_flash_test/BUILD.gn
+++ b/applications/spi_flash_test/BUILD.gn
@@ -31,6 +31,7 @@
"$dir_pw_string",
"$dir_pw_third_party/arduino:arduino_core_sources",
"//lib/pin_config",
+ "//lib/spi_flash",
]
ldflags = [ "-Wl,--print-memory-usage" ]
diff --git a/applications/spi_flash_test/main.cc b/applications/spi_flash_test/main.cc
index 514817c..daaaa04 100644
--- a/applications/spi_flash_test/main.cc
+++ b/applications/spi_flash_test/main.cc
@@ -12,23 +12,28 @@
// License for the specific language governing permissions and limitations under
// the License.
+#include <bitset>
#include <cstdint>
#include <Arduino.h>
#include <SPI.h>
#include "pw_log/log.h"
+#include "pw_result/result.h"
#include "pw_string/format.h"
#include "gonk/pin_config.h"
+#include "gonk/spi_flash.h"
using gonk::pin_config::FlashCS;
using gonk::pin_config::PinConfig;
using gonk::pin_config::StatusLed;
+using gonk::spi_flash::SpiFlash;
namespace {
PinConfig pin_config = PinConfig();
+SpiFlash spi_flash = SpiFlash(FlashCS, /*baudrate=*/1000000);
} // namespace
@@ -44,25 +49,17 @@
char buffer[32];
uint16_t update_count = 0;
- uint8_t manufacturer_id[4];
while (true) {
+ PW_LOG_INFO("Update Count: %d", update_count);
- // Read the SPI Flash JEDEC ID.
- manufacturer_id[0] = 0x9f;
- manufacturer_id[1] = 0;
- manufacturer_id[2] = 0;
- manufacturer_id[3] = 0;
- SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
- digitalWrite(FlashCS, LOW);
- SPI.transfer(manufacturer_id, 4);
- digitalWrite(FlashCS, HIGH);
- SPI.endTransaction();
+ pw::Result<SpiFlash::DeviceId> result = spi_flash.GetDeviceIds();
+ if (result.ok()) {
+ PW_LOG_INFO("SPI Flash JEDEC ID: %x %x %x",
+ result.value().manufacturer_id, result.value().family_code,
+ result.value().product_version);
+ }
- pw::string::Format(buffer, "SPI Flash JEDEC ID: %x %x %x",
- manufacturer_id[1], manufacturer_id[2],
- manufacturer_id[3]);
- PW_LOG_INFO("%s", buffer);
delay(1000);
update_count = (update_count + 1) % UINT16_MAX;
diff --git a/lib/spi_flash/BUILD.gn b/lib/spi_flash/BUILD.gn
new file mode 100644
index 0000000..902e66e
--- /dev/null
+++ b/lib/spi_flash/BUILD.gn
@@ -0,0 +1,38 @@
+# Copyright 2023 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+
+config("default_config") {
+ include_dirs = [ "public" ]
+}
+
+pw_source_set("spi_flash") {
+ public_configs = [ ":default_config" ]
+ public = [ "public/gonk/spi_flash.h" ]
+
+ include_dirs = [ "//third_party/stm32duino/arduino-core/libraries/SPI/src" ]
+
+ sources = [ "spi_flash.cc" ]
+
+ deps = [
+ "$dir_pw_log",
+ "$dir_pw_result",
+ "$dir_pw_span",
+ "$dir_pw_status",
+ "$dir_pw_third_party/arduino:arduino_core_sources",
+ ]
+}
diff --git a/lib/spi_flash/public/gonk/spi_flash.h b/lib/spi_flash/public/gonk/spi_flash.h
new file mode 100644
index 0000000..86a0fc5
--- /dev/null
+++ b/lib/spi_flash/public/gonk/spi_flash.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <Arduino.h>
+#include <stdint.h>
+
+#include "pw_result/result.h"
+#include "pw_span/span.h"
+#include "pw_status/status.h"
+
+using pw::span;
+using pw::Status;
+
+namespace gonk::spi_flash {
+
+class SpiFlash {
+public:
+ struct DeviceId {
+ uint8_t manufacturer_id;
+ uint8_t family_code;
+ uint8_t product_version;
+ };
+
+ SpiFlash(uint16_t flash_cs, int baudrate);
+
+ Status WriteEnable();
+ Status WriteDisable();
+ pw::Result<DeviceId> GetDeviceIds();
+ std::bitset<8> StatusRegister();
+ bool IsBusy();
+ bool WritingIsEnabled();
+ Status Erase();
+
+private:
+ uint16_t flash_cs_;
+ SPISettings spi_settings_;
+};
+
+} // namespace gonk::spi_flash
diff --git a/lib/spi_flash/spi_flash.cc b/lib/spi_flash/spi_flash.cc
new file mode 100644
index 0000000..d518211
--- /dev/null
+++ b/lib/spi_flash/spi_flash.cc
@@ -0,0 +1,125 @@
+#include <Arduino.h>
+#include <SPI.h>
+#include <bitset>
+#include <cstddef>
+#include <stdint.h>
+
+#include "gonk/spi_flash.h"
+
+#define PW_LOG_LEVEL PW_LOG_LEVEL_DEBUG
+#define PW_LOG_MODULE_NAME "SpiFlash"
+
+#include "pw_log/log.h"
+#include "pw_result/result.h"
+#include "pw_span/span.h"
+#include "pw_status/status.h"
+
+namespace gonk::spi_flash {
+
+SpiFlash::SpiFlash(uint16_t flash_cs, int baudrate)
+ : flash_cs_(flash_cs), spi_settings_(baudrate, MSBFIRST, SPI_MODE0) {}
+
+Status SpiFlash::WriteEnable() {
+ SPI.beginTransaction(spi_settings_);
+ digitalWrite(flash_cs_, LOW);
+ SPI.transfer(0x06);
+ digitalWrite(flash_cs_, HIGH);
+ SPI.endTransaction();
+
+ return pw::OkStatus();
+}
+
+Status SpiFlash::WriteDisable() {
+ SPI.beginTransaction(spi_settings_);
+ digitalWrite(flash_cs_, LOW);
+ SPI.transfer(0x04);
+ digitalWrite(flash_cs_, HIGH);
+ SPI.endTransaction();
+
+ return pw::OkStatus();
+}
+
+std::bitset<8> SpiFlash::StatusRegister() {
+ uint8_t status_register[2] = {
+ 0x05, // Read Status Register
+ 0, // Result value
+ };
+
+ SPI.beginTransaction(spi_settings_);
+ digitalWrite(flash_cs_, LOW);
+ SPI.transfer(status_register, 2);
+ digitalWrite(flash_cs_, HIGH);
+ SPI.endTransaction();
+
+ return (std::bitset<8>)status_register[1];
+}
+
+bool SpiFlash::IsBusy() {
+ // 1 in the first bit of the status register indicates the device is busy.
+ return StatusRegister()[0];
+}
+
+bool SpiFlash::WritingIsEnabled() {
+ // 1 in the second bit of the status register indicates the device is busy.
+ return StatusRegister()[1];
+}
+
+Status SpiFlash::Erase() {
+ if (!WritingIsEnabled()) {
+ return pw::Status::FailedPrecondition();
+ }
+
+ PW_LOG_DEBUG("Start Flash Erase");
+ // Begin chip erase.
+ SPI.beginTransaction(spi_settings_);
+ digitalWrite(flash_cs_, LOW);
+ SPI.transfer(0x60);
+ digitalWrite(flash_cs_, HIGH);
+ SPI.endTransaction();
+
+ int wait_time = 100; // Wait at most 1 second.
+ while (IsBusy() && wait_time > 0) {
+ PW_LOG_DEBUG("Device is busy");
+ delay(10);
+ wait_time--;
+ }
+
+ if (wait_time < 0) {
+ return pw::Status::DeadlineExceeded();
+ }
+
+ return pw::OkStatus();
+}
+
+pw::Result<SpiFlash::DeviceId> SpiFlash::GetDeviceIds() {
+ uint8_t manufacturer_id[4] = {
+ 0x9f, // Read Manufacturer and Device ID Command
+ 0, 0, 0, // Result values
+ };
+
+ // Read the SPI Flash JEDEC ID.
+ SPI.beginTransaction(spi_settings_);
+ digitalWrite(flash_cs_, LOW);
+ SPI.transfer(manufacturer_id, 4);
+ digitalWrite(flash_cs_, HIGH);
+ SPI.endTransaction();
+
+ PW_LOG_DEBUG("JEDEC ID: %x %x %x", manufacturer_id[1], manufacturer_id[2],
+ manufacturer_id[3]);
+
+ // Check for expected values
+ if (manufacturer_id[1] == 0xff && manufacturer_id[2] == 0xff &&
+ manufacturer_id[3] == 0xff) {
+ PW_LOG_ERROR("Error: Unexpected device ID values. SPI bus may not be "
+ "released by the FPGA.");
+ return pw::Status::NotFound();
+ }
+
+ return SpiFlash::DeviceId{
+ .manufacturer_id = manufacturer_id[1],
+ .family_code = manufacturer_id[2],
+ .product_version = manufacturer_id[3],
+ };
+}
+
+} // namespace gonk::spi_flash