diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..095bf4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +mbed-os +.boardserial diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..089e145 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.25) + +# Initialize Mbed OS build system +set(MBED_TARGET "DISCO_F769NI") +set(MBED_APP_JSON_PATH mbed_app.json) +include(mbed-os/tools/cmake/app.cmake) +add_subdirectory(mbed-os) + +# Globally enable some compiler sanity +add_compile_options(-fwrapv -fno-strict-aliasing -fsigned-char) + +# Create Tardis project +project(Tardis) +file(GLOB SRC_FILES "${PROJECT_SOURCE_DIR}/src/*.cpp") +add_executable(Tardis ${SRC_FILES}) +target_include_directories(Tardis PRIVATE "${PROJECT_SOURCE_DIR}/include") +target_link_libraries(Tardis PRIVATE mbed-os mbed-storage-qspif mbed-usb-msd mbed-storage-littlefs-v2) +target_compile_options(Tardis PRIVATE -Wall -Wextra -Wpedantic -Werror) +mbed_set_post_build(Tardis) + +mbed_finalize_build() diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..ca7c0f0 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2023 LuminaSensum project contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..bae534f --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# Tardis prototype project + +This project (codename Tardis) is a research project with the goal to create a programming environment suited towards blind people and people with a cognitive impairment that prevents learning most programming languages. + +Planned features: + +- Simple programming language +- Audio playing +- Speech synthesis + +## Hardware + +Development is based around ST's [32F769IDISCOVERY](https://www.st.com/en/evaluation-tools/32f769idiscovery.html) evaluation board. These boards cost around $90 USD and provide an easy to use development board that can be debugged and flashed over USB. No soldering or assembly is required to have a complete development environment. + +## How to use + +Install the following packages on your Linux system: ```git cmake ninja arm-none-eabi-gcc stlink python3 screen``` + +Follow these steps: + +``` +git clone https://git.lumina-sensum.com/git/LuminaSensum/Tardis.git +cd Tardis +git clone https://git.lumina-sensum.com/git/LuminaSensum/mbed-os.git +sudo ./scripts/setup-udev.sh +./scripts/findboard.sh +./scripts/build.sh +./scripts/serial.sh +``` + +Now hit some buttons on the board and see what happens. + +Note 1: If flashing fails and complains about resetting, hold the reset button on the board while flashing. + +Note 2: Hit 'ctrl-a q' to quit the serial screen. + +## License + +All documentation and software for this project is under the MIT license unless otherwise specified. diff --git a/include/ButtonThread.h b/include/ButtonThread.h new file mode 100644 index 0000000..bb50412 --- /dev/null +++ b/include/ButtonThread.h @@ -0,0 +1,17 @@ +#ifndef BUTTONTHREAD_H +#define BUTTONTHREAD_H + +#include "mbed.h" +#include + +int readButtonPresses(void); + +void button_onrise(void); + +void buttonTask(void); + +bool waitPressCondition(Kernel::Clock::time_point timeout); + +int waitForPresses(std::chrono::milliseconds wait_time); + +#endif // BUTTONTHREAD_H diff --git a/include/Filesystem.h b/include/Filesystem.h new file mode 100644 index 0000000..b71d972 --- /dev/null +++ b/include/Filesystem.h @@ -0,0 +1,9 @@ +#ifndef FILESYSTEMS_H +#define FILESYSTEM_H + +#include "MainFilesystem.h" + +void mountFilesystem(mbed::BlockDevice& bd, LittleFileSystem2& fs); +void unmountFilesystem(mbed::BlockDevice& bd, LittleFileSystem2& fs); + +#endif diff --git a/include/FlashErase.h b/include/FlashErase.h new file mode 100644 index 0000000..529ade9 --- /dev/null +++ b/include/FlashErase.h @@ -0,0 +1,9 @@ +#ifndef FLASHERASE_H +#define FLASHERASE_H + +#include "Filesystem.h" +#include "MainBD.h" + +void doErase(mbed::BlockDevice& bd, LittleFileSystem2& fs); + +#endif diff --git a/include/MainBD.h b/include/MainBD.h new file mode 100644 index 0000000..bd55a9a --- /dev/null +++ b/include/MainBD.h @@ -0,0 +1,9 @@ +#ifndef MAINBD_H +#define MAINBD_H + +#include "mbed.h" +#include "QSPIFBlockDevice.h" + +extern QSPIFBlockDevice mainBD; + +#endif diff --git a/include/MainFilesystem.h b/include/MainFilesystem.h new file mode 100644 index 0000000..3782e01 --- /dev/null +++ b/include/MainFilesystem.h @@ -0,0 +1,9 @@ +#ifndef MAINFS_H +#define MAINFS_H + +#include "mbed.h" +#include "LittleFileSystem2.h" + +extern LittleFileSystem2 mainFS; + +#endif diff --git a/include/MyUSBMSD.h b/include/MyUSBMSD.h new file mode 100644 index 0000000..62aff60 --- /dev/null +++ b/include/MyUSBMSD.h @@ -0,0 +1,16 @@ +#ifndef MYUSBMSD_H +#define MYUSBMSD_H + +#include "mbed.h" +#include "USBMSD.h" + +class MyUSBMSD : public USBMSD { +public: + MyUSBMSD(mbed::BlockDevice *bd, bool connect_blocking=true, uint16_t vendor_id=0x0703, uint16_t product_id=0x0104, uint16_t product_release=0x0001); + +private: + virtual const uint8_t *string_iproduct_desc(); +}; + +void doUSBMSD(mbed::BlockDevice& bd, LittleFileSystem2& fs); +#endif diff --git a/mbed_app.json b/mbed_app.json new file mode 100644 index 0000000..5b9c18d --- /dev/null +++ b/mbed_app.json @@ -0,0 +1,7 @@ +{ + "target_overrides": { + "*": { + "platform.stdio-baud-rate": 115200 + } + } +} diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..05ae53d --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e +if ! test -e .boardserial; then + echo "Please run scripts/findboard.sh!" + exit 1 +fi +if ! test -e mbed-os; then + echo "Please provide mbed-os directory!" + exit 1 +fi +SERIAL="$(cat .boardserial)" +mkdir -p build +test -f build/venv/.created || python3 -m venv build/venv +touch build/venv/.created +source build/venv/bin/activate +test -f build/venv/.installed || pip install -r mbed-os/tools/requirements.txt +touch build/venv/.installed +test -f build/build.ninja || cmake -Bbuild -GNinja +ninja -C build --quiet +st-flash --serial "$SERIAL" --connect-under-reset write build/Tardis.bin 0x8000000 diff --git a/scripts/findboard.sh b/scripts/findboard.sh new file mode 100755 index 0000000..f8321a5 --- /dev/null +++ b/scripts/findboard.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -e +SERIALPATH="$PWD/.boardserial" +USBDIR=/sys/bus/usb/devices +cd $USBDIR +for DEV in *; do + cd "$USBDIR/$DEV" + test -e idVendor -a -e idProduct || continue + ID="$(cat idVendor):$(cat idProduct)" + test "$ID" = "0483:374b" || continue + cp serial "$SERIALPATH" + echo "Found board!" + exit 0 +done +echo "Couldn't find board, please re-connect and run again." +echo "Make sure you have run 'sudo scripts/setup-udev.sh'" +exit 1 diff --git a/scripts/serial.sh b/scripts/serial.sh new file mode 100755 index 0000000..b742650 --- /dev/null +++ b/scripts/serial.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e +if ! test -e .boardserial; then + echo "Please run scripts/findboard.sh!" + exit 1 +fi +SERIAL="$(cat .boardserial)" +DEVICE="/dev/serial/by-id/usb-STMicroelectronics_STM32_STLink_${SERIAL}-if02" +exec screen "$DEVICE" 115200 diff --git a/scripts/setup-udev.sh b/scripts/setup-udev.sh new file mode 100755 index 0000000..aac5997 --- /dev/null +++ b/scripts/setup-udev.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e +RULES=/etc/udev/rules.d/60-stlink.rules +test -e /etc/udev/rules.d/60-stlink.rules && exit 0 +cat <$RULES +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", TAG+="uaccess", TAG+="seat" +EOF +udevadm trigger +echo "Please re-connect your ST development board if it's already connected." diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp new file mode 100644 index 0000000..a314d09 --- /dev/null +++ b/src/ButtonThread.cpp @@ -0,0 +1,74 @@ +#include "mbed.h" +#include "ButtonThread.h" + +EventFlags buttonFlags; + +#define PUSH_FLAG 0b01 +#define LIFT_FLAG 0b10 + +void button_onrise(void) { + buttonFlags.set(PUSH_FLAG); +} + +int readButtonPresses(void) { + int pressed_count = 0; + + // Wait and track an initial press without timing out + buttonFlags.wait_any(PUSH_FLAG); + pressed_count++; + + while(true) { + uint32_t flags = buttonFlags.wait_any(PUSH_FLAG, 1000); + bool timed_out = flags == osFlagsErrorTimeout; + bool had_any_error = flags & osFlagsError; + if(timed_out) { + break; + } else if(had_any_error) { + error("Uhh what the f?\n"); + } else { + pressed_count++; + } + } + + return pressed_count; +} + +int buttonPressCount; +Mutex buttonPressMutex; +ConditionVariable cv(buttonPressMutex); + +void buttonTask(void) { + InterruptIn buttonIRQ(BUTTON1); + buttonIRQ.rise(button_onrise); + + while(true) { + int presses = readButtonPresses(); + buttonPressMutex.lock(); + buttonPressCount = presses; + cv.notify_one(); + buttonPressMutex.unlock(); + } +} + +bool waitPressCondition(Kernel::Clock::time_point timeout) { + // buttonPressCount is 0 if we have already read the current value + while(buttonPressCount == 0) { + if(cv.wait_until(timeout) == cv_status::timeout) { + return false; + } + } + return true; +} + +// returns presses or -1 on timeout +int waitForPresses(std::chrono::milliseconds wait_time) { + Kernel::Clock::time_point timeout = Kernel::Clock::now() + wait_time; + int presses = -1; + buttonPressMutex.lock(); + if(waitPressCondition(timeout)) { + presses = buttonPressCount; + buttonPressCount = 0; + } + buttonPressMutex.unlock(); + return presses; +} diff --git a/src/Filesystem.cpp b/src/Filesystem.cpp new file mode 100644 index 0000000..ded610d --- /dev/null +++ b/src/Filesystem.cpp @@ -0,0 +1,50 @@ +#include "mbed.h" +#include +#include +#include "Filesystem.h" +#include "MainBD.h" + +// Maximum number of elements in buffer +#define BUFFER_MAX_LEN 10 +#define FORCE_REFORMAT false + +// function to attempt to mount the filesystem and +// reformat and mount it again should it fail +void mountFilesystem(mbed::BlockDevice& bd, LittleFileSystem2& fs) { + int err; + // Try to mount the filesystem + printf("Mounting the filesystem... "); + fflush(stdout); + err = fs.mount(&bd); + printf("%s\n", (err ? "Fail :(" : "OK")); + if (err || FORCE_REFORMAT) { + // Reformat if we can't mount the filesystem + printf("formatting... "); + fflush(stdout); + err = fs.reformat(&bd); + printf("%s\n", (err ? "Fail :(" : "OK")); + if (err) { + error("error: %s (%d)\n", strerror(-err), err); + } + // Try to mount the filesystem again + printf("Mounting the filesystem... "); + fflush(stdout); + err = fs.mount(&bd); + printf("%s\n", (err ? "Fail :(" : "OK")); + if (err) { + error("error: %s (%d)\n", strerror(-err), err); + } + } +} + +// function to unmount the filesystem +void unmountFilesystem(mbed::BlockDevice& bd, LittleFileSystem2& fs) { + // Try to unmount the filesystem + printf("Unmounting the filesystem... "); + fflush(stdout); + int err = fs.unmount(); + printf("%s\n", (err ? "Fail :(" : "OK")); + if (err) { + error("error: %s (%d)\n", strerror(-err), err); + } +} diff --git a/src/FlashErase.cpp b/src/FlashErase.cpp new file mode 100644 index 0000000..613f5a2 --- /dev/null +++ b/src/FlashErase.cpp @@ -0,0 +1,33 @@ +#include "mbed.h" +#include +#include +#include "FlashErase.h" + +// function to erase the flash +void doErase(mbed::BlockDevice& bd, LittleFileSystem2& fs) { + // Try to unmount the filesystem + unmountFilesystem(bd, fs); + printf("Initializing the block device... "); + fflush(stdout); + int err = bd.init(); + printf("%s\n", (err ? "Fail :(" : "OK")); + if (err) { + error("error: %s (%d)\n", strerror(-err), err); + } + + printf("Erasing the block device... "); + fflush(stdout); + err = bd.erase(0, bd.size()); + printf("%s\n", (err ? "Fail :(" : "OK")); + if (err) { + error("error: %s (%d)\n", strerror(-err), err); + } + + printf("Deinitializing the block device... "); + fflush(stdout); + err = bd.deinit(); + printf("%s\n", (err ? "Fail :(" : "OK")); + if (err) { + error("error: %s (%d)\n", strerror(-err), err); + } +} diff --git a/src/MainBD.cpp b/src/MainBD.cpp new file mode 100644 index 0000000..cee3c60 --- /dev/null +++ b/src/MainBD.cpp @@ -0,0 +1,10 @@ +#if !DEVICE_QSPI +#error [NOT_SUPPORTED] QSPI not supported for this target +#endif + +#include "mbed.h" +#include "MainBD.h" + +// set up the QSPI flash +QSPIFBlockDevice mainBD(QSPI_FLASH1_IO0, QSPI_FLASH1_IO1, QSPI_FLASH1_IO2, QSPI_FLASH1_IO3, + QSPI_FLASH1_SCK, QSPI_FLASH1_CSN, QSPIF_POLARITY_MODE_0, MBED_CONF_QSPIF_QSPI_FREQ); diff --git a/src/MainFilesystem.cpp b/src/MainFilesystem.cpp new file mode 100644 index 0000000..c6021a9 --- /dev/null +++ b/src/MainFilesystem.cpp @@ -0,0 +1,5 @@ +#include "mbed.h" +#include "MainFilesystem.h" + +// setup the filesystem +LittleFileSystem2 mainFS("fs"); diff --git a/src/MyUSBMSD.cpp b/src/MyUSBMSD.cpp new file mode 100644 index 0000000..b53e901 --- /dev/null +++ b/src/MyUSBMSD.cpp @@ -0,0 +1,39 @@ +#include "ButtonThread.h" +#include "Filesystem.h" +#include "mbed.h" +#include "MyUSBMSD.h" + +// create a custom usb mass storage subclass to override +// the product description + +MyUSBMSD::MyUSBMSD(mbed::BlockDevice *bd, bool connect_blocking, uint16_t vendor_id, uint16_t product_id, uint16_t product_release) + : USBMSD(bd, connect_blocking, vendor_id, product_id, product_release) { +} + +const uint8_t *MyUSBMSD::string_iproduct_desc() { + static const uint8_t string_iproduct_descriptor[] = { + 0x28, //bLength + STRING_DESCRIPTOR, //bDescriptorType + 'L', 0, 'u', 0, 'm', 0, 'i', 0, 'n', 0, 'a', 0, 'S', 0, 'e', 0, 'n', 0, 's', 0, 'u', 0, 'm', 0, ' ', 0, 'T', 0, 'a', 0, 'r', 0, 'd', 0, 'i', 0, 's', 0 //bString iProduct - usb mass storage + }; + return string_iproduct_descriptor; +} + +void doUSBMSD(mbed::BlockDevice& bd, LittleFileSystem2& fs) { + // Try to unmount the filesystem +unmountFilesystem(bd, fs); + printf("Switching to the usb mass storage mode...\n"); + MyUSBMSD usb(&bd, true, 0x1209, 0x10); + + while (true) { + int presses = waitForPresses(0s); + usb.process(); + if(presses == 1) { + printf("Disconnecting the usb mass storage device...\n"); + usb.disconnect(); + printf("Switched usb mass storage mode off.\n"); + mountFilesystem(bd, fs); + break; + } + } +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..485099d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,32 @@ +#include "mbed.h" +#include "ButtonThread.h" +#include "Filesystem.h" +#include "FlashErase.h" +#include "MainBD.h" +#include "MainFilesystem.h" +#include "MyUSBMSD.h" + +int main() +{ + printf("Project Tardis\n"); + + mountFilesystem(mainBD, mainFS); + + Thread buttonThread; + buttonThread.start(buttonTask); + + while(true) { + int presses = waitForPresses(600s); + if(presses == 1) { + doUSBMSD(mainBD, mainFS); + } + + if(presses == 2) { + doErase(mainBD, mainFS); + } + } + + buttonThread.terminate(); + + return 0; +}