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