diff --git a/CMakeLists.txt b/CMakeLists.txt index 51ac2d18..ed8abfc9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(pinetime VERSION 0.7.0 LANGUAGES C CXX ASM) +project(pinetime VERSION 0.7.1 LANGUAGES C CXX ASM) set(NRF_TARGET "nrf52") diff --git a/README.md b/README.md index 42efda20..643c3717 100644 --- a/README.md +++ b/README.md @@ -3,255 +3,87 @@ > We envision the PineTime as a companion for not only your PinePhone but also for your favorite devices — any phone, tablet, or even PC. -*https://www.pine64.org/pinetime/* +*https://www.pine64.org/pinetime/* -The goal of this project is to build a firmware designed for the PineTime around FreeRTOS. -It's coded using a mix of C and C++ : the NRF52-SDK and FreeRTOS are written in C, but I prefer to write my own code in C++. +The **Pinetime** smartwatch is built around the NRF52832 MCU (512KB Flash, 64KB RAM), a 240*240 LCD display driven by the ST7789 controller, an accelerometer, a heartrate sensor and a vibrator. -Please **NOTE : for now, this project is in heavy development state**. Feel free to use it, contribute to it or fork it ! +The goal of this project is to design an open-source firmware for the Pinetime smartwatch : -This project is based on https://github.com/JF002/nrf52-baseproject. -It's a CMake project that configure everything needed to build applications for the PineTime (https://wiki.pine64.org/index.php/PineTime). + - Code written in **modern C++**; + - Build system based on **CMake**; + - Based on **[FreeRTOS 10.0.0](https://freertos.org)** real-time OS. + - Using **[LittleVGL/LVGL 6.1.2](https://lvgl.io/)** as UI library... + - ... and **[NimBLE 1.3.0](https://github.com/apache/mynewt-nimble)** as BLE stack. -I tested this project (compile only) with the following versions: +##Overview - * gcc-arm-none-eabi-8-2019-q3-update (from https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads) - * nRF5_SDK_15.3.0_59ac345 (from https://www.nordicsemi.com/Software-and-Tools/Software/nRF5-SDK) - -I've tested this project on the actual PineTime hardware. +![Pinetime screens](images/0.7.0/montage.jpg "PinetimeScreens") -## Current state - -![Pinetime](./images/v0.3.0.png "Pinetime") - - * Project builds and runs on the Pinetime; - * Logs available via JLink RTT; - * SPI (DMA & IRQ based) LCD driver; - * Open source BLE stack : [NimBLE](https://github.com/apache/mynewt-nimble); - * BLE advertising and connection connection; - * BLE CTS client (retrieves the time from the connected device if it implements a CTS server); - * Push button to go to disable screen (and go to low power mode) / enable screen (and wake-up) and UI navigation - * Touch panel support; - * Rich user interface (using [LittleVGL](https://littlevgl.com/)) via display, touchpanel and push button. - * Digital watch face and 4 demo applications (spinning meter, analog gauche, push button and message box); - * Watchdog (automatic reset in case of firmware crash) and reset support (push and hold the button for 7 - 10s); - * BLE Notification support (still Work-In-Progress, [companion app](https://github.com/JF002/gobbledegook) needed); - * Supported by companion app [Amazfish](https://openrepos.net/content/piggz/amazfish) (time synchronization and notifications are integrated). - * **[EXPERIMENTAL]** Firmware update (OTA) via BLE. +As of now, here is the list of achievements of this project: + - Fast and optimized LCD driver + - BLE communication + - Rich user interface via display, touchpanel and push button + - Time synchronisation via BLE + - Notification via BLE + - Multiple 'apps' : + * Clock (displays the date, time, battery level, ble connection status, heart rate and step count) + * Heart rate + * Motion + * System info (displays various info : BLE MAC, build date/time, uptime, version,...) + * Brightess (allows the user to configure the brightness of the display) + - Supported by 2 companion apps (developpments ongoing): + * [Gadgetbridge](https://codeberg.org/Freeyourgadget/Gadgetbridge/src/branch/pinetime-jf) (on Android) + * [Amazfish](https://openrepos.net/content/piggz/amazfish) (on SailfishOS) + - **[Experimental]** OTA (Over-the-air) update via BLE + - **[Experimental]** Bootloader based on [MCUBoot](https://juullabs-oss.github.io/mcuboot/) + ## Documentation - - * [BLE implementation and API](./doc/ble.md) - * [Bootloader and DFU](./bootloader/README.md) - -## Stub using NRF52-DK -![Pinetime stub](./images/pinetimestub1.jpg "PinetimeStub") - -See [this page](./doc/PinetimeStubWithNrf52DK.md) - -## How to build - - * Download and unzip arm-none-eabi and NRF52 SDK - * Clone this repo - * **[JLINK]** Call CMake with the following command line argument - - - -DARM_NONE_EABI_TOOLCHAIN_PATH=[Path to the toolchain directory] - - -DNRF5_SDK_PATH=[Path to the SDK directory] - - -DUSE_JLINK=1 - - -DNRFJPROG=[Path to NRFJProg executable] - - * OR - * **[GDB CLIENT (if you use a BlackMagicProbe, for example)]** Call CMake with the following command line argument - - - -DARM_NONE_EABI_TOOLCHAIN_PATH=[Path to the toolchain directory] - - -DNRF5_SDK_PATH=[Path to the SDK directory] - - -DUSE_GDB_CLIENT=1 - - -DGDB_CLIENT_BIN_PATH=[Path to arm-none-eabi-gdb executable] - - -DGDB_CLIENT_TARGET_REMOTE=[Target remote connetion string. Ex : /dev/ttyACM0] - - * OR - * **[OPENOCD (if you use a STlink v2 clone, for example)]** Call CMake with the following command line argument - - - -DARM_NONE_EABI_TOOLCHAIN_PATH=[Path to the toolchain directory] - - -DNRF5_SDK_PATH=[Path to the SDK directory] - - -DUSE_OPENOCD=1 - - * Optionally, if you want to use a another version then whats on your path - - - -DOPENOCD_BIN_PATH=[path to openocd] - - -JLINK -``` -$ mkdir build -$ cd build -$ cmake -DCMAKE_BUILD_TYPE=Debug -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... -DUSE_JLINK=1 -DNRFJPROG=... ../ -``` - -GDB (Back Magic Probe) -``` -$ mkdir build -$ cd build -$ cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... -DUSE_GDB_CLIENT=1 -DGDB_CLIENT_BIN_PATH=... -DGDB_CLIENT_TARGET_REMOTE=... ../ -``` - -OpenOCD (STlink v2 clones) -``` -$ mkdir build -$ cd build -$ cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... -DUSE_OPENOCD=1 -DGDB_CLIENT_BIN_PATH=[optional] ../ -``` - - -**Note** : By default, the build configuration *Release* is applied. It means that the code is built with optimisations enabled (-O3). If you wanto to compile in *Debug* mode to ease debugging and step-by-step debugging, specify the CMake variable `CMAKE_BUILD_TYPE` in the CMake command line: - -``` --DCMAKE_BUILD_TYPE=Debug -``` +### Build, flash and debug + - [Build the project](doc/buildAndProgram.md) + - [Bootloader, OTA and DFU](./bootloader/README.md) + - [Stub using NRF52-DK](./doc/PinetimeStubWithNrf52DK.md) + - Logging with JLink RTT. + - Using files from the releases - - * Make -``` -$ make -j pinetime-app -``` - -## How to program -### Using make - - * Erase +### API + - [BLE implementation and API](./doc/ble.md) -``` -$ make FLASH_ERASE -``` - -* Flash application - -``` -$ make FLASH_pinetime-app -``` - -* For your information : list make targets : - -``` -$ make help -``` - -### Using GDB and a Black Magic Probe (BMP) -You can use this procedure if you want to flash the firmware (from a release, for example) without having to build the project locally. - -Enter the following command into GDB: - -``` -target extended-remote /dev/ttyACM0 -monitor swdp_scan -attach 1 -file ./pinetime-app-full.hex -load -run -``` - -*Note* : Sometimes, I need to run this procedure 2 times : the first time, the screen stays blank, and the second time, it works. This is something I'll need to debug... - -Example: -``` -$ /home/jf/nrf52/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-gdb - -(gdb) target extended-remote /dev/ttyACM0 -Remote debugging using /dev/ttyACM0 -(gdb) monitor swdp_scan -Target voltage: ABSENT! -Available Targets: -No. Att Driver - 1 Nordic nRF52 M3/M4 - 2 Nordic nRF52 Access Port - -(gdb) attach 1 -Attaching to Remote target -warning: No executable has been specified and target does not support -determining executable automatically. Try using the "file" command. -0xfffffffe in ?? () -(gdb) file ./pinetime-app-full.hex -A program is being debugged already. -Are you sure you want to change the file? (y or n) y -Reading symbols from ./pinetime-app-full.hex... -(No debugging symbols found in ./pinetime-app-full.hex) -(gdb) load -Loading section .sec1, size 0xb00 lma 0x0 -Loading section .sec2, size 0xf000 lma 0x1000 -Loading section .sec3, size 0x10000 lma 0x10000 -Loading section .sec4, size 0x5150 lma 0x20000 -Loading section .sec5, size 0xa000 lma 0x26000 -Loading section .sec6, size 0x10000 lma 0x30000 -Loading section .sec7, size 0xdf08 lma 0x40000 -Start address 0x0, load size 314200 -Transfer rate: 45 KB/sec, 969 bytes/write. -``` - -## RTT - -RTT is a feature from Segger's JLink devices that allows bidirectionnal communication between the debugger and the target. -This feature can be used to get the logs from the embedded software on the development computer. - - * Program the MCU with the code (see above) - * Start JLinkExe - -``` -$ JLinkExe -device nrf52 -if swd -speed 4000 -autoconnect 1 -``` - - * Start JLinkRTTClient - -``` -$ JLinkRTTClient -``` - -## Tools - - - https://github.com/eliotstock/memory : display the memory usage (FLASH/RAM) using the .map file from GCC. +### Architecture and technical topics + - [Memory analysis](./doc/MemoryAnalysis.md) -## BLE connection and time synchronization -At runtime, BLE advertising is started. You can then use a smartphone or computer to connect and bond to your Pinetime. -As soon as a device is bonded, Pinetime will look for a **CTS** server (**C**urrent **T**ime **S**ervice) on the connected device. - -### Using Android and NRFConnect -Here is how to do it with an Android smartphone running NRFConnect: - -* Build and program the firmware on the Pinetime -* Install NRFConnect (https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Connect-for-desktop) -* Start NRFConnect and create a CTS server : - - Tap the hamburger button on the top left and select "Configure GATT server" - - Tap "Add service" on the bottom - - Select server configuration "Current Time Service" and tap OK -* Go back to the main screen and scan for BLE devices. A device called "PineTime" should appear -* Tap the button "Connect" next to the PineTime device. It should connect to the PineTime and switch to a new tab. -* If a CTS server is found, the Pinetime should update its time with the time provided by the server. - -### Using Linux and bluetoothctl -* Ensure that your bluetooth controller is enabled and working fine. I've tested this on a x86 Debian computer and on a RaspberryPi 3. -* Run bluetoothctl as root : `sudo bluetoothctl` -* Enter the following commands: - * `scan on` and wait for you Pinetime to be detected. Note the BLE MAC address - * `scan off' - * `trust ` - * `pair ` - * Wait for some time, and the connection should be established. +### Using the firmware + - Integration with Gadgetbridge + - Integration with AmazFish + - Integration with NRFConnect + - Firmware update, OTA -**NOTE : ** The commands above establish a BLE connection between your PC, but the time synchronization and notifications won't work because there is not CTS or ANS server running. I'm currently working on an application that'll provide both of these servers. + +## TODO - contribute -### Troubleshooting -If the connection cannot be established, or the time synchronization does not work, try the following steps. +This project is far from beeing finished, and there are still a lot of things to do for this project to become a firmware usable by the general public. -On Android: -* Disable and re-enable your bluetooth device -* In NRFConnect, in the device tab corresponding to your pinetime, tap on the menu on the top left of the screen and select "Delete bond information". -* Then re-try to bond/connect. +Here a quick list out of my head of things to do for this project: -On Linux: -* Reset the bluetooth device : `sudo hciconfig hci0 reset` -* Restart the Bluetooth service : `sudo systemctl restart bluetooth.service` -* In bluetootctl: - * `remove ` - * `trust ` - * `pair ` - -Note that the current firmware only advertise for the first 3 minutes. If you cannot connect after more than 3 minutes, try resetting the device (push the button and hold it for 7-10 seconds). \ No newline at end of file + - Improve BLE communication stability and reliability + - Improve OTA and MCUBoot bootloader + - Add more functionalities : Alarm, chrono, configuration, activities, heart rate logging, games,... + - Add more BLE functionalities : call notifications, agenda, configuration, data logging,... + - Measure power consumption and improve battery life + - Improve documentation, take better pictures and video than mine + - Improve the UI + - Create companion app for multiple OSes (Linux, Android, IoS) and platforms (desktop, ARM, mobile). Do not forget the other devices from Pine64 like [the Pinephone](https://www.pine64.org/pinephone/) and the [Pinebook Pro](https://www.pine64.org/pinebook-pro/). + - Design a simple CI (preferably selfhosted and easy to reproduce). + +Do not hesitate to clone/fork the code, hack it and create pull-requests. I'll do my best to review and merge them :) + +## Credits +I’m not working alone on this project. First, many people create PR for this projects. Then, there is the whole #pinetime community : a lot of people all around the world who are hacking, searching, experimenting and programming the Pinetime. We exchange our ideas, experiments and code in the chat rooms and forums. + +Here are some people I would like to highlight: + + - [Atc1441](https://github.com/atc1441/) : He works on an Arduino based firmware for the Pinetime and many other smartwatches based on similar hardware. He was of great help when I was implementing support for the BMA421 motion sensor and I²C driver. + - [Koen](https://github.com/bosmoment) : He’s working on a firmware based on RiotOS. He integrated similar libs than me : NimBLE, LittleVGL,… His help was invaluable too! + - [Lup Yuen Lee](https://github.com/lupyuen) : He is everywhere : he works on a Rust firmware, buils a MCUBoot based bootloader for the Pinetime, design a Flutter based companion app for smartphones and write a lot of articles about the Pinetime! + +*If you feel like you should appear on this list, just get in touch with me or submit a PR :)* diff --git a/doc/MemoryAnalysis.md b/doc/MemoryAnalysis.md index 2e20cae6..1bf6e24d 100644 --- a/doc/MemoryAnalysis.md +++ b/doc/MemoryAnalysis.md @@ -72,4 +72,7 @@ add_definitions(-D__STACK_SIZE=8192) *TODO* #NimBLE buffers -*TODO* \ No newline at end of file +*TODO* + +#Tools + - https://github.com/eliotstock/memory : display the memory usage (FLASH/RAM) using the .map file from GCC. \ No newline at end of file diff --git a/doc/buildAndProgram.md b/doc/buildAndProgram.md new file mode 100644 index 00000000..77fee33b --- /dev/null +++ b/doc/buildAndProgram.md @@ -0,0 +1,209 @@ +# Build +##Dependencies +To build this project, you'll need: + - A cross-compiler : [gcc-arm-none-eabi-8-2019-q3-update](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads/8-2019q3-update) + - The NRF52 SDK 15.3.0 : [nRF5_SDK_15.3.0_59ac345](https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/nRF5_SDK_15.3.0_59ac345.zip) + - A reasonably recent version of CMake (I use 3.16.5) + +##Build steps +###Clone the repo +``` +git clone https://github.com/JF002/Pinetime.git +cd Pinetime +mkdir build +cd build +``` +###Project generation using CMake +CMake configures the project according to variables you specify the command line. The variables are: + + Variable | Description | Example| +----------|-------------|--------| +**ARM_NONE_EABI_TOOLCHAIN_PATH**|path to the toolchain directory|`-DARM_NONE_EABI_TOOLCHAIN_PATH=/home/jf/nrf52/gcc-arm-none-eabi-9-2019-q4-major/`| +**NRF5_SDK_PATH**|path to the NRF52 SDK|`-DNRF5_SDK_PATH=/home/jf/nrf52/Pinetime/sdk`| +**USE_JLINK, USE_GDB_CLIENT and USE_OPENOCD**|Enable *JLink* mode, *GDB Client* (Black Magic Probe) mode or *OpenOCD* mode (set the one you want to use to `1`)|`-DUSE_JLINK=1` +**CMAKE_BUILD_TYPE**| Build type (Release or Debug). Release is applied by default if this variable is not specified.|`-DCMAKE_BUILD_TYPE=Debug` +**NRFJPROG**|Path to the NRFJProg executable. Used only if `USE_JLINK` is 1.|`-DNRFJPROG=/opt/nrfjprog/nrfjprog` +**GDB_CLIENT_BIN_PATH**|Path to arm-none-eabi-gdb executable. Used only if `USE_GDB_CLIENT` is 1.|`-DGDB_CLIENT_BIN_PATH=/home/jf/nrf52/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-gdb` +**GDB_CLIENT_TARGET_REMOTE**|Target remote connection string. Used only if `USE_GDB_CLIENT` is 1.|`-DGDB_CLIENT_TARGET_REMOTE=/dev/ttyACM0` + + +####CMake command line for JLink +``` +cmake -DCMAKE_BUILD_TYPE=Debug -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... -DUSE_JLINK=1 -DNRFJPROG=... ../ +``` + +####CMake command line for GDB Client (Black Magic Probe) +``` +cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... -DUSE_GDB_CLIENT=1 -DGDB_CLIENT_BIN_PATH=... -DGDB_CLIENT_TARGET_REMOTE=... ../ +``` + +####CMake command line for OpenOCD +``` +cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... -DUSE_OPENOCD=1 -DGDB_CLIENT_BIN_PATH=[optional] ../ +``` + +###Build the project +During the project generation, CMake created the following targets: +- FLASH_ERASE : mass erase the flash memory of the NRF52. +- FLASH_pinetime-app : flash the firmware into the NRF52. +- pinetime-app : build the standalone (without bootloader support) version of the firmware. +- pinetime-mcuboot-app : build the firmware with the support of the bootloader (based on MCUBoot). +- pinetime-graphics : small firmware that writes the boot graphics into the SPI flash. + +If you just want to build the project and run it on the Pinetime, using *pinetime-app* is recommanded. See ???? for more info about bootloader support. + +Build: +``` +make -j pinetime-app +``` + +List of files generated: +Binary files are generated into the folder `src`: + - **pinetime-app.bin, .hex and .out** : standalone firmware in bin, hex and out formats. + - **pinetime-app.map** : map file + - **pinetime-mcuboot-app.bin, .hex and .out** : firmware with bootloader support in bin, hex and out formats. + - **pinetime-mcuboot-app.map** : map file + - **pinetime-graphics.bin, .hex and .out** : firmware for the boot graphic in bin, hex and out formats. + - **pinetime-graphics.map** : map file + +###Program and run +####Using CMake targets +These target have been configured during the project generation by CMake according to the parameters you provided to the command line. + +Mass erase: +``` +make FLASH_ERASE +``` + +Flash the application: +``` +make FLASH_pinetime-app +``` + +###Using JLink +Start JLinkExe: +``` +$ /opt/SEGGER/JLink/JLinkExe -device nrf52 -if swd -speed 4000 -autoconnect 1 +SEGGER J-Link Commander V6.70d (Compiled Apr 16 2020 17:59:37) +DLL version V6.70d, compiled Apr 16 2020 17:59:25 + +Connecting to J-Link via USB...O.K. +Firmware: J-Link OB-SAM3U128-V2-NordicSemi compiled Mar 17 2020 14:43:00 +Hardware version: V1.00 +S/N: 682579153 +License(s): RDI, FlashBP, FlashDL, JFlash, GDB +VTref=3.300V +Device "NRF52" selected. + + +Connecting to target via SWD +InitTarget() start +InitTarget() end +Found SW-DP with ID 0x2BA01477 +DPIDR: 0x2BA01477 +Scanning AP map to find all available APs +AP[2]: Stopped AP scan as end of AP map has been reached +AP[0]: AHB-AP (IDR: 0x24770011) +AP[1]: JTAG-AP (IDR: 0x02880000) +Iterating through AP map to find AHB-AP to use +AP[0]: Core found +AP[0]: AHB-AP ROM base: 0xE00FF000 +CPUID register: 0x410FC241. Implementer code: 0x41 (ARM) +Found Cortex-M4 r0p1, Little endian. +FPUnit: 6 code (BP) slots and 2 literal slots +CoreSight components: +ROMTbl[0] @ E00FF000 +ROMTbl[0][0]: E000E000, CID: B105E00D, PID: 000BB00C SCS-M7 +ROMTbl[0][1]: E0001000, CID: B105E00D, PID: 003BB002 DWT +ROMTbl[0][2]: E0002000, CID: B105E00D, PID: 002BB003 FPB +ROMTbl[0][3]: E0000000, CID: B105E00D, PID: 003BB001 ITM +ROMTbl[0][4]: E0040000, CID: B105900D, PID: 000BB9A1 TPIU +ROMTbl[0][5]: E0041000, CID: B105900D, PID: 000BB925 ETM +Cortex-M4 identified. +J-Link> +``` + +Use the command loadfile to program the .hex file: +``` +J-Link>loadfile pinetime-app.hex +Downloading file [pinetime-app.hex]... +Comparing flash [100%] Done. +Erasing flash [100%] Done. +Programming flash [100%] Done. +Verifying flash [100%] Done. +J-Link: Flash download: Bank 0 @ 0x00000000: 1 range affected (4096 bytes) +J-Link: Flash download: Total time needed: 0.322s (Prepare: 0.043s, Compare: 0.202s, Erase: 0.003s, Program: 0.064s, Verify: 0.000s, Restore: 0.007s) +O.K. +``` + +Then reset (r) and start (g) the CPU: +``` +J-Link>r +Reset delay: 0 ms +Reset type NORMAL: Resets core & peripherals via SYSRESETREQ & VECTRESET bit. +Reset: Halt core after reset via DEMCR.VC_CORERESET. +Reset: Reset device via AIRCR.SYSRESETREQ. +J-Link>g +``` + +####JLink RTT +RTT is a feature from Segger's JLink devices that allows bidirectionnal communication between the debugger and the target. This feature can be used to get the logs from the embedded software on the development computer. + + - Program the MCU with the code (see above) + - Start JLinkExe + +``` +$ JLinkExe -device nrf52 -if swd -speed 4000 -autoconnect 1 +``` + +Start JLinkRTTClient +``` +$ JLinkRTTClient +``` + +###Using GDB and Black Magic Probe (BMP) +Enter the following command into GDB: + +``` +target extended-remote /dev/ttyACM0 +monitor swdp_scan +attach 1 +file ./pinetime-app-full.hex +load +run +``` + +Example : +``` +$ /home/jf/nrf52/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-gdb + +(gdb) target extended-remote /dev/ttyACM0 +Remote debugging using /dev/ttyACM0 +(gdb) monitor swdp_scan +Target voltage: ABSENT! +Available Targets: +No. Att Driver + 1 Nordic nRF52 M3/M4 + 2 Nordic nRF52 Access Port + +(gdb) attach 1 +Attaching to Remote target +warning: No executable has been specified and target does not support +determining executable automatically. Try using the "file" command. +0xfffffffe in ?? () +(gdb) file ./pinetime-app-full.hex +A program is being debugged already. +Are you sure you want to change the file? (y or n) y +Reading symbols from ./pinetime-app-full.hex... +(No debugging symbols found in ./pinetime-app-full.hex) +(gdb) load +Loading section .sec1, size 0xb00 lma 0x0 +Loading section .sec2, size 0xf000 lma 0x1000 +Loading section .sec3, size 0x10000 lma 0x10000 +Loading section .sec4, size 0x5150 lma 0x20000 +Loading section .sec5, size 0xa000 lma 0x26000 +Loading section .sec6, size 0x10000 lma 0x30000 +Loading section .sec7, size 0xdf08 lma 0x40000 +Start address 0x0, load size 314200 +Transfer rate: 45 KB/sec, 969 bytes/write. +``` \ No newline at end of file diff --git a/images/0.7.0/montage.jpg b/images/0.7.0/montage.jpg new file mode 100644 index 00000000..81716cc9 Binary files /dev/null and b/images/0.7.0/montage.jpg differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ff2e1812..e60c2cfe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -371,6 +371,7 @@ list(APPEND SOURCE_FILES DisplayApp/Fonts/jetbrains_mono_bold_20.c SystemTask/SystemTask.cpp + drivers/TwiMaster.cpp ) list(APPEND GRAPHICS_SOURCE_FILES @@ -444,6 +445,7 @@ set(INCLUDE_FILES SystemTask/SystemTask.h SystemTask/SystemMonitor.h DisplayApp/Screens/Symbols.h + drivers/TwiMaster.h ) include_directories( diff --git a/src/SystemTask/SystemTask.cpp b/src/SystemTask/SystemTask.cpp index 39e9751b..8f565860 100644 --- a/src/SystemTask/SystemTask.cpp +++ b/src/SystemTask/SystemTask.cpp @@ -24,12 +24,14 @@ void IdleTimerCallback(TimerHandle_t xTimer) { SystemTask::SystemTask(Drivers::SpiMaster &spi, Drivers::St7789 &lcd, - Pinetime::Drivers::SpiNorFlash& spiNorFlash, Drivers::Cst816S &touchPanel, + Pinetime::Drivers::SpiNorFlash& spiNorFlash, + Drivers::TwiMaster& twiMaster, Drivers::Cst816S &touchPanel, Components::LittleVgl &lvgl, Controllers::Battery &batteryController, Controllers::Ble &bleController, Controllers::DateTime &dateTimeController, Pinetime::Controllers::NotificationManager& notificationManager) : - spi{spi}, lcd{lcd}, spiNorFlash{spiNorFlash}, touchPanel{touchPanel}, lvgl{lvgl}, batteryController{batteryController}, + spi{spi}, lcd{lcd}, spiNorFlash{spiNorFlash}, + twiMaster{twiMaster}, touchPanel{touchPanel}, lvgl{lvgl}, batteryController{batteryController}, bleController{bleController}, dateTimeController{dateTimeController}, watchdog{}, watchdogView{watchdog}, notificationManager{notificationManager}, nimbleController(*this, bleController,dateTimeController, notificationManager, spiNorFlash) { @@ -67,6 +69,7 @@ void SystemTask::Work() { nimbleController.StartAdvertising(); lcd.Init(); + twiMaster.Init(); touchPanel.Init(); batteryController.Init(); diff --git a/src/SystemTask/SystemTask.h b/src/SystemTask/SystemTask.h index 860647a1..8fa7e7d1 100644 --- a/src/SystemTask/SystemTask.h +++ b/src/SystemTask/SystemTask.h @@ -21,7 +21,8 @@ namespace Pinetime { }; SystemTask(Drivers::SpiMaster &spi, Drivers::St7789 &lcd, - Pinetime::Drivers::SpiNorFlash& spiNorFlash, Drivers::Cst816S &touchPanel, + Pinetime::Drivers::SpiNorFlash& spiNorFlash, + Drivers::TwiMaster& twiMaster, Drivers::Cst816S &touchPanel, Components::LittleVgl &lvgl, Controllers::Battery &batteryController, Controllers::Ble &bleController, Controllers::DateTime &dateTimeController, @@ -42,6 +43,7 @@ namespace Pinetime { Pinetime::Drivers::SpiMaster& spi; Pinetime::Drivers::St7789& lcd; Pinetime::Drivers::SpiNorFlash& spiNorFlash; + Pinetime::Drivers::TwiMaster& twiMaster; Pinetime::Drivers::Cst816S& touchPanel; Pinetime::Components::LittleVgl& lvgl; Pinetime::Controllers::Battery& batteryController; diff --git a/src/drivers/Cst816s.cpp b/src/drivers/Cst816s.cpp index 61bce94c..60cd402c 100644 --- a/src/drivers/Cst816s.cpp +++ b/src/drivers/Cst816s.cpp @@ -13,51 +13,42 @@ using namespace Pinetime::Drivers; * TODO : we need a complete datasheet and protocol reference! * */ -void Pinetime::Drivers::Cst816S::Init() { +Cst816S::Cst816S(TwiMaster &twiMaster, uint8_t twiAddress) : twiMaster{twiMaster}, twiAddress{twiAddress} { + +} + +void Cst816S::Init() { nrf_gpio_cfg_output(pinReset); - nrf_gpio_pin_clear(pinReset); - vTaskDelay(20); nrf_gpio_pin_set(pinReset); - vTaskDelay(200); + vTaskDelay(50); + nrf_gpio_pin_clear(pinReset); + vTaskDelay(5); + nrf_gpio_pin_set(pinReset); + vTaskDelay(50); - nrfx_twi_config_t config; - config.frequency = NRF_TWI_FREQ_400K; - config.scl = 7; - config.sda = 6; - config.interrupt_priority = NRFX_TWI_DEFAULT_CONFIG_IRQ_PRIORITY; - config.hold_bus_uninit = NRFX_TWI_DEFAULT_CONFIG_HOLD_BUS_UNINIT; + // Wake the touchpanel up + uint8_t dummy; + twiMaster.Read(twiAddress, 0x15, &dummy, 1); + vTaskDelay(5); + twiMaster.Read(twiAddress, 0xa7, &dummy, 1); - // Configure TWI in blocking mode (event_handler = nullptr) - auto ret = nrfx_twi_init(&twi, &config, nullptr, this); - nrfx_twi_enable(&twi); } -void Cst816S::Probe() { - nrfx_err_t ret; - for(int i = 0; i < 127; i++) { - uint8_t data; - ret = nrfx_twi_rx(&twi, i, &data, 1); - if(ret == NRFX_SUCCESS) { - NRF_LOG_INFO("I2C device detected at address %d", i); - } - } -} - Cst816S::TouchInfos Cst816S::GetTouchInfo() { Cst816S::TouchInfos info; - nrfx_twi_rx(&twi, address, touchData, 63); + twiMaster.Read(twiAddress, 0, touchData, 63); auto nbTouchPoints = touchData[2] & 0x0f; // uint8_t i = 0; // NRF_LOG_INFO("#########################") for(int i = 0; i < 1; i++) { - uint8_t pointId = (touchData[touchIdIndex + (touchStep * i)]) >> 4; - if(nbTouchPoints == 0 && pointId == lastTouchId) return info; + uint8_t pointId = (touchData[touchIdIndex + (touchStep * i)]) >> 4; + if(nbTouchPoints == 0 && pointId == lastTouchId) return info; - // We fetch only the first touch point (the controller seems to handle only one anyway...) - info.isTouch = true; + // We fetch only the first touch point (the controller seems to handle only one anyway...) + info.isTouch = true; auto xHigh = touchData[touchXHighIndex + (touchStep * i)] & 0x0f; @@ -106,11 +97,12 @@ Cst816S::TouchInfos Cst816S::GetTouchInfo() { } void Cst816S::Sleep() { - nrfx_twi_disable(&twi); + // TODO re enable sleep mode + //twiMaster.Sleep(); nrf_gpio_cfg_default(6); nrf_gpio_cfg_default(7); } void Cst816S::Wakeup() { Init(); -} +} \ No newline at end of file diff --git a/src/drivers/Cst816s.h b/src/drivers/Cst816s.h index 4a5dda60..b115a688 100644 --- a/src/drivers/Cst816s.h +++ b/src/drivers/Cst816s.h @@ -1,20 +1,21 @@ #pragma once #include +#include "TwiMaster.h" namespace Pinetime { namespace Drivers { class Cst816S { public : enum class Gestures : uint8_t { - None = 0x00, - SlideDown = 0x01, - SlideUp = 0x02, - SlideLeft = 0x03, - SlideRight = 0x04, - SingleTap = 0x05, - DoubleTap = 0x0B, - LongPress = 0x0C + None = 0x00, + SlideDown = 0x01, + SlideUp = 0x02, + SlideLeft = 0x03, + SlideRight = 0x04, + SingleTap = 0x05, + DoubleTap = 0x0B, + LongPress = 0x0C }; struct TouchInfos { uint16_t x; @@ -27,21 +28,19 @@ namespace Pinetime { bool isTouch = false; }; - Cst816S() = default; + Cst816S(TwiMaster& twiMaster, uint8_t twiAddress); Cst816S(const Cst816S&) = delete; Cst816S& operator=(const Cst816S&) = delete; Cst816S(Cst816S&&) = delete; Cst816S& operator=(Cst816S&&) = delete; void Init(); - void Probe(); TouchInfos GetTouchInfo(); void Sleep(); void Wakeup(); private: static constexpr uint8_t pinIrq = 28; static constexpr uint8_t pinReset = 10; - static constexpr uint8_t address = 0x15; static constexpr uint8_t lastTouchId = 0x0f; static constexpr uint8_t touchPointNumIndex = 2; static constexpr uint8_t touchMiscIndex = 8; @@ -56,12 +55,9 @@ namespace Pinetime { static constexpr uint8_t gestureIndex = 1; uint8_t touchData[63]; - - // TODO TWI (i²C) should be created outside and injected into this class - // It will be needed when implementing other I²C devices - // (0x15 = touch, 0x18 = accelerometer, 0x44 = HR sensor) - nrfx_twi_t twi = NRFX_TWI_INSTANCE(1); // Use instance 1, because instance 0 is already used by SPI + TwiMaster& twiMaster; + uint8_t twiAddress; }; } -} +} \ No newline at end of file diff --git a/src/drivers/TwiMaster.cpp b/src/drivers/TwiMaster.cpp new file mode 100644 index 00000000..5d8fcf6b --- /dev/null +++ b/src/drivers/TwiMaster.cpp @@ -0,0 +1,140 @@ +#include +#include +#include +#include "TwiMaster.h" + +using namespace Pinetime::Drivers; + +// TODO use shortcut to automatically send STOP when receive LastTX, for example +// TODO use DMA/IRQ + +TwiMaster::TwiMaster(const Modules module, const Parameters& params) : module{module}, params{params} { + mutex = xSemaphoreCreateBinary(); +} + +void TwiMaster::Init() { + NRF_GPIO->PIN_CNF[params.pinScl] = ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) + | ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) + | ((uint32_t)GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) + | ((uint32_t)GPIO_PIN_CNF_DRIVE_S0D1 << GPIO_PIN_CNF_DRIVE_Pos) + | ((uint32_t)GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos); + + NRF_GPIO->PIN_CNF[params.pinSda] = ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) + | ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) + | ((uint32_t)GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) + | ((uint32_t)GPIO_PIN_CNF_DRIVE_S0D1 << GPIO_PIN_CNF_DRIVE_Pos) + | ((uint32_t)GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos); + + switch(module) { + case Modules::TWIM1: twiBaseAddress = NRF_TWIM1; break; + default: + return; + } + + switch(static_cast(params.frequency)) { + case Frequencies::Khz100 : twiBaseAddress->FREQUENCY = TWIM_FREQUENCY_FREQUENCY_K100; break; + case Frequencies::Khz250 : twiBaseAddress->FREQUENCY = TWIM_FREQUENCY_FREQUENCY_K250; break; + case Frequencies::Khz400 : twiBaseAddress->FREQUENCY = TWIM_FREQUENCY_FREQUENCY_K400; break; + } + + twiBaseAddress->PSEL.SCL = params.pinScl; + twiBaseAddress->PSEL.SDA = params.pinSda; + twiBaseAddress->EVENTS_LASTRX = 0; + twiBaseAddress->EVENTS_STOPPED = 0; + twiBaseAddress->EVENTS_LASTTX = 0; + twiBaseAddress->EVENTS_ERROR = 0; + twiBaseAddress->EVENTS_RXSTARTED = 0; + twiBaseAddress->EVENTS_SUSPENDED = 0; + twiBaseAddress->EVENTS_TXSTARTED = 0; + + twiBaseAddress->ENABLE = (TWIM_ENABLE_ENABLE_Enabled << TWIM_ENABLE_ENABLE_Pos); + + + /* // IRQ + NVIC_ClearPendingIRQ(_IRQn); + NVIC_SetPriority(_IRQn, 2); + NVIC_EnableIRQ(_IRQn); + */ + + xSemaphoreGive(mutex); + +} + +void TwiMaster::Read(uint8_t deviceAddress, uint8_t registerAddress, uint8_t *data, size_t size) { + xSemaphoreTake(mutex, portMAX_DELAY); + Write(deviceAddress, ®isterAddress, 1, false); + Read(deviceAddress, data, size, true); + xSemaphoreGive(mutex); +} + +void TwiMaster::Write(uint8_t deviceAddress, uint8_t registerAddress, const uint8_t *data, size_t size) { + ASSERT(size <= maxDataSize); + xSemaphoreTake(mutex, portMAX_DELAY); + internalBuffer[0] = registerAddress; + std::memcpy(internalBuffer+1, data, size); + Write(deviceAddress, internalBuffer, size+1, true); + xSemaphoreGive(mutex); +} + + +void TwiMaster::Read(uint8_t deviceAddress, uint8_t *buffer, size_t size, bool stop) { + twiBaseAddress->ADDRESS = deviceAddress; + twiBaseAddress->TASKS_RESUME = 0x1UL; + twiBaseAddress->RXD.PTR = (uint32_t)buffer; + twiBaseAddress->RXD.MAXCNT = size; + + twiBaseAddress->TASKS_STARTRX = 1; + + while(!twiBaseAddress->EVENTS_RXSTARTED && !twiBaseAddress->EVENTS_ERROR); + twiBaseAddress->EVENTS_RXSTARTED = 0x0UL; + + while(!twiBaseAddress->EVENTS_LASTRX && !twiBaseAddress->EVENTS_ERROR); + twiBaseAddress->EVENTS_LASTRX = 0x0UL; + + if (stop || twiBaseAddress->EVENTS_ERROR) { + twiBaseAddress->TASKS_STOP = 0x1UL; + while(!twiBaseAddress->EVENTS_STOPPED); + twiBaseAddress->EVENTS_STOPPED = 0x0UL; + } + else { + twiBaseAddress->TASKS_SUSPEND = 0x1UL; + while(!twiBaseAddress->EVENTS_SUSPENDED); + twiBaseAddress->EVENTS_SUSPENDED = 0x0UL; + } + + if (twiBaseAddress->EVENTS_ERROR) { + twiBaseAddress->EVENTS_ERROR = 0x0UL; + } +} + +void TwiMaster::Write(uint8_t deviceAddress, const uint8_t *data, size_t size, bool stop) { + twiBaseAddress->ADDRESS = deviceAddress; + twiBaseAddress->TASKS_RESUME = 0x1UL; + twiBaseAddress->TXD.PTR = (uint32_t)data; + twiBaseAddress->TXD.MAXCNT = size; + + twiBaseAddress->TASKS_STARTTX = 1; + + while(!twiBaseAddress->EVENTS_TXSTARTED && !twiBaseAddress->EVENTS_ERROR); + twiBaseAddress->EVENTS_TXSTARTED = 0x0UL; + + while(!twiBaseAddress->EVENTS_LASTTX && !twiBaseAddress->EVENTS_ERROR); + twiBaseAddress->EVENTS_LASTTX = 0x0UL; + + if (stop || twiBaseAddress->EVENTS_ERROR) { + twiBaseAddress->TASKS_STOP = 0x1UL; + while(!twiBaseAddress->EVENTS_STOPPED); + twiBaseAddress->EVENTS_STOPPED = 0x0UL; + } + else { + twiBaseAddress->TASKS_SUSPEND = 0x1UL; + while(!twiBaseAddress->EVENTS_SUSPENDED); + twiBaseAddress->EVENTS_SUSPENDED = 0x0UL; + } + + if (twiBaseAddress->EVENTS_ERROR) { + twiBaseAddress->EVENTS_ERROR = 0x0UL; + uint32_t error = twiBaseAddress->ERRORSRC; + twiBaseAddress->ERRORSRC = error; + } +} \ No newline at end of file diff --git a/src/drivers/TwiMaster.h b/src/drivers/TwiMaster.h new file mode 100644 index 00000000..3b7555f5 --- /dev/null +++ b/src/drivers/TwiMaster.h @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include + + +namespace Pinetime { + namespace Drivers { + class TwiMaster { + public: + enum class Modules { TWIM1 }; + enum class Frequencies {Khz100, Khz250, Khz400}; + struct Parameters { + uint32_t frequency; + uint8_t pinSda; + uint8_t pinScl; + }; + + TwiMaster(const Modules module, const Parameters& params); + + void Init(); + void Read(uint8_t deviceAddress, uint8_t registerAddress, uint8_t* buffer, size_t size); + void Write(uint8_t deviceAddress, uint8_t registerAddress, const uint8_t* data, size_t size); + + private: + void Read(uint8_t deviceAddress, uint8_t* buffer, size_t size, bool stop); + void Write(uint8_t deviceAddress, const uint8_t* data, size_t size, bool stop); + NRF_TWIM_Type* twiBaseAddress; + SemaphoreHandle_t mutex; + const Modules module; + const Parameters params; + static constexpr uint8_t maxDataSize{8}; + static constexpr uint8_t registerSize{1}; + uint8_t internalBuffer[maxDataSize + registerSize]; + + }; + } +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 41943892..fe413585 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,6 +42,9 @@ static constexpr uint8_t pinSpiMiso = 4; static constexpr uint8_t pinSpiFlashCsn = 5; static constexpr uint8_t pinLcdCsn = 25; static constexpr uint8_t pinLcdDataCommand = 18; +static constexpr uint8_t pinTwiScl = 7; +static constexpr uint8_t pinTwiSda = 6; +static constexpr uint8_t touchPanelTwiAddress = 0x15; Pinetime::Drivers::SpiMaster spi{Pinetime::Drivers::SpiMaster::SpiModule::SPI0, { Pinetime::Drivers::SpiMaster::BitOrder::Msb_Lsb, @@ -58,7 +61,15 @@ Pinetime::Drivers::St7789 lcd {lcdSpi, pinLcdDataCommand}; Pinetime::Drivers::Spi flashSpi {spi, pinSpiFlashCsn}; Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi}; -Pinetime::Drivers::Cst816S touchPanel {}; + +// The TWI device should work @ up to 400Khz but there is a HW bug which prevent it from +// respecting correct timings. According to erratas heet, this magic value makes it run +// at ~390Khz with correct timings. +static constexpr uint32_t MaxTwiFrequencyWithoutHardwareBug{0x06200000}; +Pinetime::Drivers::TwiMaster twiMaster{Pinetime::Drivers::TwiMaster::Modules::TWIM1, + Pinetime::Drivers::TwiMaster::Parameters { + MaxTwiFrequencyWithoutHardwareBug, pinTwiSda, pinTwiScl}}; +Pinetime::Drivers::Cst816S touchPanel {twiMaster, touchPanelTwiAddress}; Pinetime::Components::LittleVgl lvgl {lcd, touchPanel}; @@ -213,7 +224,7 @@ int main(void) { debounceTimer = xTimerCreate ("debounceTimer", 200, pdFALSE, (void *) 0, DebounceTimerCallback); - systemTask.reset(new Pinetime::System::SystemTask(spi, lcd, spiNorFlash, touchPanel, lvgl, batteryController, bleController, + systemTask.reset(new Pinetime::System::SystemTask(spi, lcd, spiNorFlash, twiMaster, touchPanel, lvgl, batteryController, bleController, dateTimeController, notificationManager)); systemTask->Start(); nimble_port_init(); diff --git a/src/sdk_config.h b/src/sdk_config.h index e83bde4b..aa1bbb3a 100644 --- a/src/sdk_config.h +++ b/src/sdk_config.h @@ -5244,7 +5244,7 @@ // NRFX_TWI_ENABLED - nrfx_twi - TWI peripheral driver //========================================================== #ifndef NRFX_TWI_ENABLED -#define NRFX_TWI_ENABLED 1 +#define NRFX_TWI_ENABLED 0 #endif // NRFX_TWI0_ENABLED - Enable TWI0 instance @@ -5257,7 +5257,7 @@ #ifndef NRFX_TWI1_ENABLED -#define NRFX_TWI1_ENABLED 1 +#define NRFX_TWI1_ENABLED 0 #endif // NRFX_TWI_DEFAULT_CONFIG_FREQUENCY - Frequency