| // 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. |
| |
| #include <bitset> |
| #include <cstdint> |
| |
| #include <Arduino.h> |
| #include <SPI.h> |
| |
| #define PW_LOG_LEVEL PW_LOG_LEVEL_INFO |
| #define PW_LOG_MODULE_NAME "main" |
| |
| #include "pw_log/log.h" |
| #include "pw_result/result.h" |
| #include "pw_status/status.h" |
| #include "pw_string/format.h" |
| |
| #include "gonk/adc.h" |
| #include "gonk/fpga_control.h" |
| #include "gonk/pin_config.h" |
| #include "gonk/spi_flash.h" |
| |
| using gonk::adc::Adc; |
| using gonk::fpga_control::FpgaControl; |
| using gonk::pin_config::FlashCS; |
| using gonk::pin_config::FpgaDspiCS; |
| using gonk::pin_config::FpgaIoMode; |
| using gonk::pin_config::FpgaIoReset; |
| using gonk::pin_config::FpgaIoValid; |
| using gonk::pin_config::ICE40Done; |
| using gonk::pin_config::PinConfig; |
| using gonk::pin_config::StatusLed; |
| using gonk::spi_flash::SpiFlash; |
| using pw::Status; |
| |
| namespace { |
| |
| PinConfig pin_config = PinConfig(); |
| FpgaControl fpga_control = FpgaControl(&pin_config); |
| SpiFlash spi_flash = |
| SpiFlash(FlashCS, /*baudrate=*/1000000, pin_config.flash_spi); |
| Adc fpga_adc = Adc(Serial, pin_config.fpga_spi, /*fpga_spi_baudrate=*/7500000, |
| FpgaDspiCS, FpgaIoMode, FpgaIoReset, FpgaIoValid); |
| |
| void (*current_task)(); |
| |
| bool fpga_provisioned = false; |
| |
| } // namespace |
| |
| void CheckFpgaReadyTask(); |
| void FpgaConfigTask(); |
| void ADCIdleTask(); |
| void ADCReadTask(); |
| |
| void StartAdcReads() { |
| PW_LOG_INFO("Starting ADC Continuous Reads."); |
| fpga_adc.SetContinuousReadMode(); |
| current_task = &ADCReadTask; |
| } |
| |
| void StopAdcReads() { |
| PW_LOG_INFO("Stopping ADC Continuous Reads."); |
| fpga_adc.SetReadWriteMode(); |
| current_task = &ADCIdleTask; |
| } |
| |
| // Task that logs once a second and allows toggling continuous read mode. |
| void ADCIdleTask() { |
| static uint32_t last_update = millis(); |
| static uint32_t this_update = millis(); |
| static uint16_t update_count = 0; |
| |
| this_update = millis(); |
| |
| if (Serial.available()) { |
| int data_in = Serial.read(); |
| if (data_in == '\n') { |
| StartAdcReads(); |
| } |
| } |
| |
| // Output an idle state heartbeat message each second. |
| if (this_update > last_update + 1000) { |
| PW_LOG_INFO("ADC Idle; Press Enter to start/stop reading"); |
| |
| last_update = this_update; |
| update_count = (update_count + 1) % UINT16_MAX; |
| |
| // Toggle status LED each loop. |
| if (update_count % 2 == 0) { |
| digitalWrite(StatusLed, HIGH); |
| } else { |
| digitalWrite(StatusLed, LOW); |
| } |
| } |
| } |
| |
| // Continuous measurement task. |
| void ADCReadTask() { |
| // Check for serial input. |
| if (Serial.available()) { |
| int data_in = Serial.read(); |
| // Press enter to stop. |
| if (data_in == '\n') { |
| StopAdcReads(); |
| return; |
| } |
| } |
| |
| Status update_result = fpga_adc.UpdateContinuousMeasurements(); |
| if (!update_result.ok()) { |
| PW_LOG_ERROR("UpdateContinuousMeasurements() failed"); |
| } else { |
| Status write_result = fpga_adc.WriteMeasurementPacket(); |
| if (!write_result.ok()) { |
| PW_LOG_ERROR("WriteMeasurementPacket failed"); |
| } |
| } |
| } |
| |
| Status CheckSpiFlash() { |
| PW_LOG_INFO("Checking SPI Flash"); |
| spi_flash.StartFlashSpi(); |
| |
| pw::Result<SpiFlash::DeviceId> result = spi_flash.GetDeviceIds(); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| PW_LOG_INFO("SPI Flash JEDEC ID: %x %x %x", result.value().manufacturer_id, |
| result.value().family_code, result.value().product_version); |
| return pw::OkStatus(); |
| } |
| |
| Status AdcInit() { |
| // Init ADCs |
| Status init_result = fpga_adc.InitAdcs(); |
| if (!init_result.ok()) { |
| return init_result; |
| } |
| |
| Status check_result = fpga_adc.CheckAllAdcs(); |
| if (!check_result.ok()) { |
| return check_result; |
| } |
| // Select 7 ADC channels. |
| Status select_result = fpga_adc.SelectContinuousReadAdcs(0b011100111001); |
| if (!select_result.ok()) { |
| return select_result; |
| } |
| |
| return pw::OkStatus(); |
| } |
| |
| // Attepmt to init the ADCs through the FPGA. |
| // |
| // Success -> Switch to ADC read mode |
| // Fail -> Switch to FpgaConfigTask |
| void CheckFpgaReadyTask() { |
| Status init_result = AdcInit(); |
| if (init_result.ok()) { |
| StartAdcReads(); |
| return; |
| } |
| |
| PW_LOG_WARN("FPGA not ready"); |
| current_task = &FpgaConfigTask; |
| } |
| |
| // Provision the FPGA with a bitstream over Serial. |
| // |
| // Success -> Switch to ADC read mode |
| // Fail -> Restart FpgaConfigTask |
| void FpgaConfigTask() { |
| auto result = fpga_control.StartConfig(); |
| if (!result.ok()) { |
| PW_LOG_INFO("Restarting FPGA Config."); |
| current_task = &FpgaConfigTask; |
| return; |
| } |
| |
| // FPGA has been configured successfully. |
| fpga_provisioned = true; |
| // Check if SPI flash is readable. |
| CheckSpiFlash(); |
| |
| Status init_result = AdcInit(); |
| if (init_result.ok()) { |
| StartAdcReads(); |
| return; |
| } |
| } |
| |
| int main() { |
| pin_config.Init(); |
| pin_config.InitFpgaPins(); |
| fpga_adc.InitInterrupts(); |
| delay(500); |
| |
| current_task = &CheckFpgaReadyTask; |
| |
| // Start the task loop. |
| while (true) { |
| current_task(); |
| } |
| |
| PW_UNREACHABLE; |
| return 0; |
| } |