Merge branch 'develop' of JF/PineTime into master

This commit is contained in:
JF 2020-06-07 14:17:45 +02:00 committed by Gitea
commit a0e73f5c1a
56 changed files with 10780 additions and 307 deletions

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(pinetime VERSION 0.5.0 LANGUAGES C CXX ASM) project(pinetime VERSION 0.6.0 LANGUAGES C CXX ASM)
set(NRF_TARGET "nrf52") set(NRF_TARGET "nrf52")
@ -66,5 +66,4 @@ endif()
set(VERSION_EDIT_WARNING "// Do not edit this file, it is automatically generated by CMAKE!") set(VERSION_EDIT_WARNING "// Do not edit this file, it is automatically generated by CMAKE!")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/Version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/src/Version.h) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/Version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/src/Version.h)
include("cmake-nRF5x/CMake_nRF5x.cmake")
add_subdirectory(src) add_subdirectory(src)

View file

@ -37,10 +37,12 @@ I've tested this project on the actual PineTime hardware.
* Watchdog (automatic reset in case of firmware crash) and reset support (push and hold the button for 7 - 10s); * 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); * 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). * Supported by companion app [Amazfish](https://openrepos.net/content/piggz/amazfish) (time synchronization and notifications are integrated).
* **[EXPERIMENTAL]** Firmware update (OTA) via BLE.
## Documentation ## Documentation
* [BLE implementation and API](./doc/ble.md) * [BLE implementation and API](./doc/ble.md)
* [Bootloader and DFU](./bootloader/README.md)
## Stub using NRF52-DK ## Stub using NRF52-DK
![Pinetime stub](./images/pinetimestub1.jpg "PinetimeStub") ![Pinetime stub](./images/pinetimestub1.jpg "PinetimeStub")
@ -114,6 +116,11 @@ $ make -j pinetime-app
$ make FLASH_ERASE $ make FLASH_ERASE
``` ```
* Flash application
```
$ make FLASH_pinetime-app
```
* For your information : list make targets : * For your information : list make targets :

67
bootloader/README.md Normal file
View file

@ -0,0 +1,67 @@
# Bootloader
## Bootloader graphic
The bootloader loads a graphic (Pinetime logo) from the SPI Flash memory. If this graphic is not loaded in the memory, the LCD will display garbage (the content of the SPI flash memory).
The SPI Flash memory is not accessible via the SWD debugger. Use the firmware 'pinetime-graphics' to load the graphic into memory. All you have to do is build it and program it at address 0x00 :
- Build:
```
$ make pinetime-graphics
```
- Program (using OpenOCD for example) :
```
program pinetime-graphics.bin 0
```
- Let it run for ~10s (it does nothing for 5 seconds, then write the logo into the SPI memory, then (slowly) displays it on the LCD).
## Bootloader binary
The binary comes from https://github.com/lupyuen/pinetime-rust-mynewt/releases/tag/v4.1.7
It must be flash at address **0x00** in the internal flash memory.
Using OpenOCD:
`
program mynewt_nosemi.elf_4.1.7.bin 0
`
## Application firmware image
Build the binary compatible with the booloader:
`
make pinetime-mcuboot-app
`
The binary is located in *<build directory>/src/pinetime-mcuboot-app.bin*.
It must me converted into a MCUBoot image using *imgtool.py* from [MCUBoot](https://github.com/JuulLabs-OSS/mcuboot/tree/master/scripts). Simply checkout the project and run the script <mcuboot root>/scripts/imgtool.py with the following command line:
`
imgtool.py create --align 4 --version 1.0.0 --header-size 32 --slot-size 475136 --pad-header <build directory>/src/pinetime-mcuboot-app.bin image.bin
`
The image must be then flashed at address **0x8000** in the internal flash memory.
Using OpenOCD:
`
program image.bin 0x8000
`
## OTA and DFU
Pack the image into a .zip file for the NRF DFU protocol:
`
adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application image.bin dfu.zip
`
Use NRFConnect or dfu.py (in <project root>/bootloader/ota-dfu-python) to upload the zip file to the device:
`
sudo dfu.py -z /home/jf/nrf52/bootloader/dfu.zip -a <pinetime MAC address> --legacy
`
**Note** : dfu.py is a slightly modified version of [this repo](https://github.com/daniel-thompson/ota-dfu-python).

View file

@ -0,0 +1,34 @@
# This script programs the bootloader and the firmware application using J-Link debugger.
gdb_flash_program enable
gdb_breakpoint_override hard
$_TARGETNAME configure -event reset-init {
# Arm Semihosting is used to show debug console output and may only be enabled after init event. We wait for the event and enable Arm Semihosting.
echo "Enabled ARM Semihosting to show debug output"
arm semihosting enable
}
# Connect to the device.
init
echo "Stopping..."
reset
halt
echo ""
# Flashing Bootloader
echo "Flashing bootloader..."
program ./mynewt_nosemi_4.1.7.elf verify 0x00000000
# Flashing Application
echo "Flashing application..."
program ./image.bin verify 0x00008000
echo ""
# Restart the device and start the bootloader.
echo "Restarting..."
reset
echo ""
echo "**** Done! Press Ctrl-C to exit..."

7206
bootloader/boot_graphics.h Normal file

File diff suppressed because it is too large Load diff

3
bootloader/create_dfu.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/bash
adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application image.bin dfu.zip

3
bootloader/create_image.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/bash
/home/jf/nrf52/mcuboot/scripts/imgtool.py create --align 4 --version 1.0.0 --header-size 32 --slot-size 475136 --pad-header /home/jf/nrf52/Pinetime/cmake-build-release/src/pinetime-mcuboot-app.bin image.bin

View file

@ -0,0 +1,3 @@
#!/bin/bash
/home/jf/nrf52/openocd-code/src/openocd -s /home/jf/nrf52/openocd-code/tcl/ -c "tcl_port disabled" -c "gdb_port 3333" -c "telnet_port 4444" -f /home/jf/nrf52/openocd-code/tcl/interface/jlink.cfg -c "transport select swd" -f /home/jf/nrf52/openocd-code/tcl/target/nrf52.cfg -f booloader_app_jlink.ocd

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1 @@
This directory contains source forked from https://github.com/daniel-thompson/ota-dfu-python.

View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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
http://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.

View file

@ -0,0 +1,118 @@
# Python nRF5 OTA DFU Controller
So... this is my fork of dingara's fork of astronomer80's fork of
foldedtoad's Python OTA DFU utility.
My own contribution is little more than a brute force conversion to
python3. It is sparsely tested so there are likely to be a few
remaining bytes versus string bugs remaining in the places I didn't test
. I used it primarily as part of
[wasp-os](https://github.com/daniel-thompson/wasp-os) as a way to
deliver OTA updates to nRF52-based smart watches, especially the
[Pine64 PineTime](https://www.pine64.org/pinetime/).
## What does it do?
This is a Python program that uses `gatttool` (provided with the Linux BlueZ driver) to achieve Over The Air (OTA) Device Firmware Updates (DFU) to a Nordic Semiconductor nRF5 (either nRF51 or nRF52) device via Bluetooth Low Energy (BLE).
### Main features:
* Perform OTA DFU to an nRF5 peripheral without an external USB BLE dongle.
* Ability to detect if the peripheral is running in application mode or bootloader, and automatically switch if needed (buttonless).
* Support for both Legacy (SDK <= 11) and Secure (SDK >= 12) bootloader.
Before using this utility the nRF5 peripheral device needs to be programmed with a DFU bootloader (see Nordic Semiconductor documentation/examples for instructions on that).
## Prerequisites
* BlueZ 5.4 or above
* Python 3.6
* Python `pexpect` module (available via pip)
* Python `intelhex` module (available via pip)
## Firmware Build Requirement
* Your nRF5 peripheral firmware build method will produce a firmware file ending with either `*.hex` or `*.bin`.
* Your nRF5 firmware build method will produce an Init file ending with `.dat`.
* The typical naming convention is `application.bin` and `application.dat`, but this utility will accept other names.
## Generating init files
### Legacy bootloader
Use the `gen_dat` application (you need to compile it with `gcc gen_dat.c -o gen_dat` on first run) to generate a `.dat` file from your `.bin` file. Example:
./gen_dat application.bin application.dat
Note: The `gen_dat` utility expects a `.bin` file input, so you'll get Cyclic Redundancy Check (CRC) errors during DFU using a `.dat` file generated from a `.hex` file.
An alternative is to use `nrfutil` from Nordic Semiconductor, but I've found this method to be easier. You may need to edit the `gen_dat` source to fit your specific application.
### Secure bootloader
You need to use `nrfutil` to generate firmware packages for the new secure bootloader (SDK > 12) as the package needs to be signed with a private/public key pair. Note that the bootloader will need to be programmed with the corresponding public key. See the [nrfutil repo](https://github.com/NordicSemiconductor/pc-nrfutil) for details.
Note: I've had problems with the pip version of `nrfutil`. I recommend [installing from source](https://github.com/NordicSemiconductor/pc-nrfutil#running-and-installing-from-source) instead.
## Usage
There are two ways to specify firmware files for this utility. Either by specifying both the `.hex` or `.bin` file with the `.dat` file, or more easily by the `.zip` file, which contains both the hex and dat files.
The new `.zip` file form is encouraged by Nordic, but the older hex/bin + dat file methods should still work.
## Usage Examples
> sudo ./dfu.py -f ~/application.hex -d ~/application.dat -a CD:E3:4A:47:1C:E4
or:
> sudo ./dfu.py -z ~/application.zip -a CD:E3:4A:47:1C:E4
You can use the `hcitool lescan` to figure out the address of a DFU target, for example:
$ sudo hcitool -i hci0 lescan
LE Scan ...
CD:E3:4A:47:1C:E4 <TARGET_NAME>
CD:E3:4A:47:1C:E4 (unknown)
## Example Output
================================
== ==
== DFU Server ==
== ==
================================
Sending file application.bin to CD:E3:4A:47:1C:E4
bin array size: 60788
Checking DFU State...
Board needs to switch in DFU mode
Switching to DFU mode
Enable Notifications in DFU mode
Sending hex file size
Waiting for Image Size notification
Waiting for INIT DFU notification
Begin DFU
Progress: |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 100.0% Complete (60788 of 60788 bytes)
Upload complete in 0 minutes and 14 seconds
segments sent: 3040
Waiting for DFU complete notification
Waiting for Firmware Validation notification
Activate and reset
DFU Server done
## TODO:
* Implement link-loss procedure for Legacy Controller.
* Update example output in readme.
* Add makefile examples.
* More code cleanup.
## Info & References
* [Nordic Legacy DFU Service](http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v11.0.0/bledfu_transport_bleservice.html?cp=4_0_3_4_3_1_4_1)
* [Nordic Legacy DFU sequence diagrams](http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v11.0.0/bledfu_transport_bleprofile.html?cp=4_0_3_4_3_1_4_0_1_6#ota_profile_pkt_rcpt_notif)
* [Nordic Secure DFU bootloader](http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v12.2.0/lib_dfu_transport_ble.html?cp=4_0_1_3_5_2_2)
* [nrfutil](https://github.com/NordicSemiconductor/pc-nrfutil)

View file

@ -0,0 +1,291 @@
import math
import pexpect
import time
from array import array
from util import *
from nrf_ble_dfu_controller import NrfBleDfuController
verbose = False
class Procedures:
START_DFU = 1
INITIALIZE_DFU = 2
RECEIVE_FIRMWARE_IMAGE = 3
VALIDATE_FIRMWARE = 4
ACTIVATE_IMAGE_AND_RESET = 5
RESET_SYSTEM = 6
REPORT_RECEIVED_IMAGE_SIZE = 7
PRN_REQUEST = 8
RESPONSE = 16
PACKET_RECEIPT_NOTIFICATION = 17
string_map = {
START_DFU : "START_DFU",
INITIALIZE_DFU : "INITIALIZE_DFU",
RECEIVE_FIRMWARE_IMAGE : "RECEIVE_FIRMWARE_IMAGE",
VALIDATE_FIRMWARE : "VALIDATE_FIRMWARE",
ACTIVATE_IMAGE_AND_RESET : "ACTIVATE_IMAGE_AND_RESET",
RESET_SYSTEM : "RESET_SYSTEM",
REPORT_RECEIVED_IMAGE_SIZE : "REPORT_RECEIVED_IMAGE_SIZE",
PRN_REQUEST : "PACKET_RECEIPT_NOTIFICATION_REQUEST",
RESPONSE : "RESPONSE",
PACKET_RECEIPT_NOTIFICATION : "PACKET_RECEIPT_NOTIFICATION",
}
@staticmethod
def to_string(proc):
return Procedures.string_map[proc]
@staticmethod
def from_string(proc_str):
return int(proc_str, 16)
class Responses:
SUCCESS = 1
INVALID_STATE = 2
NOT_SUPPORTED = 3
DATA_SIZE_EXCEEDS_LIMITS = 4
CRC_ERROR = 5
OPERATION_FAILED = 6
string_map = {
SUCCESS : "SUCCESS",
INVALID_STATE : "INVALID_STATE",
NOT_SUPPORTED : "NOT_SUPPORTED",
DATA_SIZE_EXCEEDS_LIMITS : "DATA_SIZE_EXCEEDS_LIMITS",
CRC_ERROR : "CRC_ERROR",
OPERATION_FAILED : "OPERATION_FAILED",
}
@staticmethod
def to_string(res):
return Responses.string_map[res]
@staticmethod
def from_string(res_str):
return int(res_str, 16)
class BleDfuControllerLegacy(NrfBleDfuController):
# Class constants
UUID_CONTROL_POINT = "00001531-1212-efde-1523-785feabcd123"
UUID_PACKET = "00001532-1212-efde-1523-785feabcd123"
UUID_VERSION = "00001534-1212-efde-1523-785feabcd123"
# Constructor inherited from abstract base class
# --------------------------------------------------------------------------
# Start the firmware update process
# --------------------------------------------------------------------------
def start(self, verbose=False):
(_, self.ctrlpt_handle, self.ctrlpt_cccd_handle) = self._get_handles(self.UUID_CONTROL_POINT)
(_, self.data_handle, _) = self._get_handles(self.UUID_PACKET)
self.pkt_receipt_interval = 10
if verbose:
print('Control Point Handle: 0x%04x, CCCD: 0x%04x' % (self.ctrlpt_handle, self.ctrlpt_cccd_handle))
print('Packet handle: 0x%04x' % (self.data_handle))
# Subscribe to notifications from Control Point characteristic
if verbose: print("Enabling notifications")
self._enable_notifications(self.ctrlpt_cccd_handle)
# Send 'START DFU' + Application Command
if verbose: print("Sending START_DFU")
self._dfu_send_command(Procedures.START_DFU, [0x04])
# Transmit binary image size
# Need to pad the byte array with eight zero bytes
# (because that's what the bootloader is expecting...)
hex_size_array_lsb = uint32_to_bytes_le(len(self.bin_array))
zero_pad_array_le(hex_size_array_lsb, 8)
self._dfu_send_data(hex_size_array_lsb)
# Wait for response to Image Size
print("Waiting for Image Size notification")
self._wait_and_parse_notify()
# Send 'INIT DFU' + Init Packet Command
self._dfu_send_command(Procedures.INITIALIZE_DFU, [0x00])
# Transmit the Init image (DAT).
self._dfu_send_init()
# Send 'INIT DFU' + Init Packet Complete Command
self._dfu_send_command(Procedures.INITIALIZE_DFU, [0x01])
print("Waiting for INIT DFU notification")
# Wait for INIT DFU notification (indicates flash erase completed)
self._wait_and_parse_notify()
# Set the Packet Receipt Notification interval
if verbose: print("Setting pkt receipt notification interval")
prn = uint16_to_bytes_le(self.pkt_receipt_interval)
self._dfu_send_command(Procedures.PRN_REQUEST, prn)
# Send 'RECEIVE FIRMWARE IMAGE' command to set DFU in firmware receive state.
self._dfu_send_command(Procedures.RECEIVE_FIRMWARE_IMAGE)
# Send bin_array contents as as series of packets (burst mode).
# Each segment is pkt_payload_size bytes long.
# For every pkt_receipt_interval sends, wait for notification.
segment_count = 0
segment_total = int(math.ceil(self.image_size/float(self.pkt_payload_size)))
time_start = time.time()
last_send_time = time.time()
print("Begin DFU")
for i in range(0, self.image_size, self.pkt_payload_size):
segment = self.bin_array[i:i + self.pkt_payload_size]
self._dfu_send_data(segment)
segment_count += 1
# print "segment #{} of {}, dt = {}".format(segment_count, segment_total, time.time() - last_send_time)
# last_send_time = time.time()
if (segment_count == segment_total):
print_progress(self.image_size, self.image_size, prefix = 'Progress:', suffix = 'Complete', barLength = 50)
duration = time.time() - time_start
print("\nUpload complete in {} minutes and {} seconds".format(int(duration / 60), int(duration % 60)))
if verbose: print("segments sent: {}".format(segment_count))
print("Waiting for DFU complete notification")
# Wait for DFU complete notification
self._wait_and_parse_notify()
elif (segment_count % self.pkt_receipt_interval) == 0:
(proc, res, pkts) = self._wait_and_parse_notify()
# TODO: Check pkts == segment_count * pkt_payload_size
if res != Responses.SUCCESS:
raise Exception("bad notification status: {}".format(Responses.to_string(res)))
print_progress(pkts, self.image_size, prefix = 'Progress:', suffix = 'Complete', barLength = 50)
# Send Validate Command
self._dfu_send_command(Procedures.VALIDATE_FIRMWARE)
print("Waiting for Firmware Validation notification")
# Wait for Firmware Validation notification
self._wait_and_parse_notify()
# Wait a bit for copy on the peer to be finished
time.sleep(1)
# Send Activate and Reset Command
print("Activate and reset")
self._dfu_send_command(Procedures.ACTIVATE_IMAGE_AND_RESET)
# --------------------------------------------------------------------------
# Check if the peripheral is running in bootloader (DFU) or application mode
# Returns True if the peripheral is in DFU mode
# --------------------------------------------------------------------------
def check_DFU_mode(self):
if verbose: print("Checking DFU State...")
cmd = 'char-read-uuid %s' % self.UUID_VERSION
if verbose: print(cmd)
self.ble_conn.sendline(cmd)
# Skip two rows
try:
res = self.ble_conn.expect('handle:.*', timeout=10)
# res = self.ble_conn.expect('handle:', timeout=10)
except pexpect.TIMEOUT as e:
print("State timeout")
except:
pass
return self.ble_conn.after.find(b'value: 08 00')!=-1
def switch_to_dfu_mode(self):
(_, bl_value_handle, bl_cccd_handle) = self._get_handles(self.UUID_CONTROL_POINT)
# Enable notifications
cmd = 'char-write-req 0x%02x %02x' % (bl_cccd_handle, 1)
if verbose: print(cmd)
self.ble_conn.sendline(cmd)
# Reset the board in DFU mode. After reset the board will be disconnected
cmd = 'char-write-req 0x%02x 0104' % (bl_value_handle)
if verbose: print(cmd)
self.ble_conn.sendline(cmd)
time.sleep(0.5)
#print "Send 'START DFU' + Application Command"
#self._dfu_state_set(0x0104)
# Reconnect the board.
#ret = self.scan_and_connect()
#if verbose: print("Connected " + str(ret))
#return ret
return 1
# --------------------------------------------------------------------------
# Parse notification status results
# --------------------------------------------------------------------------
def _dfu_parse_notify(self, notify):
if len(notify) < 3:
print("notify data length error")
return None
if verbose: print(notify)
dfu_notify_opcode = Procedures.from_string(notify[0])
if dfu_notify_opcode == Procedures.RESPONSE:
dfu_procedure = Procedures.from_string(notify[1])
dfu_response = Responses.from_string(notify[2])
procedure_str = Procedures.to_string(dfu_procedure)
response_str = Responses.to_string(dfu_response)
if verbose: print("opcode: 0x%02x, proc: %s, res: %s" % (dfu_notify_opcode, procedure_str, response_str))
return (dfu_procedure, dfu_response)
if dfu_notify_opcode == Procedures.PACKET_RECEIPT_NOTIFICATION:
receipt = bytes_to_uint32_le(notify[1:5])
return (dfu_notify_opcode, Responses.SUCCESS, receipt)
# --------------------------------------------------------------------------
# Wait for a notification and parse the response
# --------------------------------------------------------------------------
def _wait_and_parse_notify(self):
if verbose: print("Waiting for notification")
notify = self._dfu_wait_for_notify()
if notify is None:
raise Exception("No notification received")
if verbose: print("Parsing notification")
result = self._dfu_parse_notify(notify)
if result[1] != Responses.SUCCESS:
raise Exception("Error in {} procedure, reason: {}".format(
Procedures.to_string(result[0]),
Responses.to_string(result[1])))
return result
#--------------------------------------------------------------------------
# Send the Init info (*.dat file contents) to peripheral device.
#--------------------------------------------------------------------------
def _dfu_send_init(self):
if verbose: print("dfu_send_init")
# Open the DAT file and create array of its contents
init_bin_array = array('B', open(self.datfile_path, 'rb').read())
# Transmit Init info
self._dfu_send_data(init_bin_array)

View file

@ -0,0 +1,323 @@
import math
import pexpect
import time
from array import array
from util import *
from nrf_ble_dfu_controller import NrfBleDfuController
verbose = False
class Procedures:
CREATE = 0x01
SET_PRN = 0x02
CALC_CHECKSUM = 0x03
EXECUTE = 0x04
SELECT = 0x06
RESPONSE = 0x60
PARAM_COMMAND = 0x01
PARAM_DATA = 0x02
string_map = {
CREATE : "CREATE",
SET_PRN : "SET_PRN",
CALC_CHECKSUM : "CALC_CHECKSUM",
EXECUTE : "EXECUTE",
SELECT : "SELECT",
RESPONSE : "RESPONSE",
}
@staticmethod
def to_string(proc):
return Procedures.string_map[proc]
@staticmethod
def from_string(proc_str):
return int(proc_str, 16)
class Results:
INVALID_CODE = 0x00
SUCCESS = 0x01
OPCODE_NOT_SUPPORTED = 0x02
INVALID_PARAMETER = 0x03
INSUFF_RESOURCES = 0x04
INVALID_OBJECT = 0x05
UNSUPPORTED_TYPE = 0x07
OPERATION_NOT_PERMITTED = 0x08
OPERATION_FAILED = 0x0A
string_map = {
INVALID_CODE : "INVALID_CODE",
SUCCESS : "SUCCESS",
OPCODE_NOT_SUPPORTED : "OPCODE_NOT_SUPPORTED",
INVALID_PARAMETER : "INVALID_PARAMETER",
INSUFF_RESOURCES : "INSUFFICIENT_RESOURCES",
INVALID_OBJECT : "INVALID_OBJECT",
UNSUPPORTED_TYPE : "UNSUPPORTED_TYPE",
OPERATION_NOT_PERMITTED : "OPERATION_NOT_PERMITTED",
OPERATION_FAILED : "OPERATION_FAILED",
}
@staticmethod
def to_string(res):
return Results.string_map[res]
@staticmethod
def from_string(res_str):
return int(res_str, 16)
class BleDfuControllerSecure(NrfBleDfuController):
# Class constants
UUID_BUTTONLESS = '8e400001-f315-4f60-9fb8-838830daea50'
UUID_CONTROL_POINT = '8ec90001-f315-4f60-9fb8-838830daea50'
UUID_PACKET = '8ec90002-f315-4f60-9fb8-838830daea50'
# Constructor inherited from abstract base class
# --------------------------------------------------------------------------
# Start the firmware update process
# --------------------------------------------------------------------------
def start(self):
(_, self.ctrlpt_handle, self.ctrlpt_cccd_handle) = self._get_handles(self.UUID_CONTROL_POINT)
(_, self.data_handle, _) = self._get_handles(self.UUID_PACKET)
if verbose:
print('Control Point Handle: 0x%04x, CCCD: 0x%04x' % (self.ctrlpt_handle, self.ctrlpt_cccd_handle))
print('Packet handle: 0x%04x' % (self.data_handle))
# Subscribe to notifications from Control Point characteristic
self._enable_notifications(self.ctrlpt_cccd_handle)
# Set the Packet Receipt Notification interval
prn = uint16_to_bytes_le(self.pkt_receipt_interval)
self._dfu_send_command(Procedures.SET_PRN, prn)
self._dfu_send_init()
self._dfu_send_image()
# --------------------------------------------------------------------------
# Check if the peripheral is running in bootloader (DFU) or application mode
# Returns True if the peripheral is in DFU mode
# --------------------------------------------------------------------------
def check_DFU_mode(self):
print("Checking DFU State...")
self.ble_conn.sendline('characteristics')
dfu_mode = False
try:
self.ble_conn.expect([self.UUID_BUTTONLESS], timeout=2)
except pexpect.TIMEOUT as e:
dfu_mode = True
return dfu_mode
def switch_to_dfu_mode(self):
(_, bl_value_handle, bl_cccd_handle) = self._get_handles(self.UUID_BUTTONLESS)
self._enable_notifications(bl_cccd_handle)
# Reset the board in DFU mode. After reset the board will be disconnected
cmd = 'char-write-req 0x%04x 01' % (bl_value_handle)
self.ble_conn.sendline(cmd)
# Wait some time for board to reboot
time.sleep(0.5)
# Increase the mac address by one and reconnect
self.target_mac_increase(1)
return self.scan_and_connect()
# --------------------------------------------------------------------------
# Parse notification status results
# --------------------------------------------------------------------------
def _dfu_parse_notify(self, notify):
if len(notify) < 3:
print("notify data length error")
return None
if verbose: print(notify)
dfu_notify_opcode = Procedures.from_string(notify[0])
if dfu_notify_opcode == Procedures.RESPONSE:
dfu_procedure = Procedures.from_string(notify[1])
dfu_result = Results.from_string(notify[2])
procedure_str = Procedures.to_string(dfu_procedure)
result_str = Results.to_string(dfu_result)
# if verbose: print "opcode: {0}, proc: {1}, res: {2}".format(dfu_notify_opcode, procedure_str, result_str)
if verbose: print("opcode: 0x%02x, proc: %s, res: %s" % (dfu_notify_opcode, procedure_str, result_str))
# Packet Receipt notifications are sent in the exact same format
# as responses to the CALC_CHECKSUM procedure.
if(dfu_procedure == Procedures.CALC_CHECKSUM and dfu_result == Results.SUCCESS):
offset = bytes_to_uint32_le(notify[3:7])
crc32 = bytes_to_uint32_le(notify[7:11])
return (dfu_procedure, dfu_result, offset, crc32)
elif(dfu_procedure == Procedures.SELECT and dfu_result == Results.SUCCESS):
max_size = bytes_to_uint32_le(notify[3:7])
offset = bytes_to_uint32_le(notify[7:11])
crc32 = bytes_to_uint32_le(notify[11:15])
return (dfu_procedure, dfu_result, max_size, offset, crc32)
else:
return (dfu_procedure, dfu_result)
# --------------------------------------------------------------------------
# Wait for a notification and parse the response
# --------------------------------------------------------------------------
def _wait_and_parse_notify(self):
if verbose: print("Waiting for notification")
notify = self._dfu_wait_for_notify()
if notify is None:
raise Exception("No notification received")
if verbose: print("Parsing notification")
result = self._dfu_parse_notify(notify)
if result[1] != Results.SUCCESS:
raise Exception("Error in {} procedure, reason: {}".format(
Procedures.to_string(result[0]),
Results.to_string(result[1])))
return result
# --------------------------------------------------------------------------
# Send the Init info (*.dat file contents) to peripheral device.
# --------------------------------------------------------------------------
def _dfu_send_init(self):
if verbose: print("dfu_send_init")
# Open the DAT file and create array of its contents
init_bin_array = array('B', open(self.datfile_path, 'rb').read())
init_size = len(init_bin_array)
init_crc = 0;
# Select command
self._dfu_send_command(Procedures.SELECT, [Procedures.PARAM_COMMAND]);
(proc, res, max_size, offset, crc32) = self._wait_and_parse_notify()
if offset != init_size or crc32 != init_crc:
if offset == 0 or offset > init_size:
# Create command
self._dfu_send_command(Procedures.CREATE, [Procedures.PARAM_COMMAND] + uint32_to_bytes_le(init_size))
res = self._wait_and_parse_notify()
segment_count = 0
segment_total = int(math.ceil(init_size/float(self.pkt_payload_size)))
for i in range(0, init_size, self.pkt_payload_size):
segment = init_bin_array[i:i + self.pkt_payload_size]
self._dfu_send_data(segment)
segment_count += 1
if (segment_count % self.pkt_receipt_interval) == 0:
(proc, res, offset, crc32) = self._wait_and_parse_notify()
if res != Results.SUCCESS:
raise Exception("bad notification status: {}".format(Results.to_string(res)))
# Calculate CRC
self._dfu_send_command(Procedures.CALC_CHECKSUM)
self._wait_and_parse_notify()
# Execute command
self._dfu_send_command(Procedures.EXECUTE)
self._wait_and_parse_notify()
print("Init packet successfully transfered")
# --------------------------------------------------------------------------
# Send the Firmware image to peripheral device.
# --------------------------------------------------------------------------
def _dfu_send_image(self):
if verbose: print("dfu_send_image")
# Select Data Object
self._dfu_send_command(Procedures.SELECT, [Procedures.PARAM_DATA])
(proc, res, max_size, offset, crc32) = self._wait_and_parse_notify()
# Split the firmware into multiple objects
num_objects = int(math.ceil(self.image_size / float(max_size)))
print("Max object size: %d, num objects: %d, offset: %d, total size: %d" % (max_size, num_objects, offset, self.image_size))
time_start = time.time()
last_send_time = time.time()
obj_offset = (offset/max_size)*max_size
while(obj_offset < self.image_size):
# print "\nSending object {} of {}".format(obj_offset/max_size+1, num_objects)
obj_offset += self._dfu_send_object(obj_offset, max_size)
# Image uploaded successfully, update the progress bar
print_progress(self.image_size, self.image_size, prefix = 'Progress:', suffix = 'Complete', barLength = 50)
duration = time.time() - time_start
print("\nUpload complete in {} minutes and {} seconds".format(int(duration / 60), int(duration % 60)))
# --------------------------------------------------------------------------
# Send a single data object of given size and offset.
# --------------------------------------------------------------------------
def _dfu_send_object(self, offset, obj_max_size):
if offset != self.image_size:
if offset == 0 or offset >= obj_max_size or crc32 != crc32_unsigned(self.bin_array[0:offset]):
# Create Data Object
size = min(obj_max_size, self.image_size - offset)
self._dfu_send_command(Procedures.CREATE, [Procedures.PARAM_DATA] + uint32_to_bytes_le(size))
self._wait_and_parse_notify()
segment_count = 0
segment_total = int(math.ceil(min(obj_max_size, self.image_size-offset)/float(self.pkt_payload_size)))
segment_begin = offset
segment_end = min(offset+obj_max_size, self.image_size)
for i in range(segment_begin, segment_end, self.pkt_payload_size):
num_bytes = min(self.pkt_payload_size, segment_end - i)
segment = self.bin_array[i:i + num_bytes]
self._dfu_send_data(segment)
segment_count += 1
# print "j: {} i: {}, end: {}, bytes: {}, size: {} segment #{} of {}".format(
# offset, i, segment_end, num_bytes, self.image_size, segment_count, segment_total)
if (segment_count % self.pkt_receipt_interval) == 0:
try:
(proc, res, offset, crc32) = self._wait_and_parse_notify()
except e:
# Likely no notification received, need to re-transmit object
return 0
if res != Results.SUCCESS:
raise Exception("bad notification status: {}".format(Results.to_string(res)))
if crc32 != crc32_unsigned(self.bin_array[0:offset]):
# Something went wrong, need to re-transmit this object
return 0
print_progress(offset, self.image_size, prefix = 'Progress:', suffix = 'Complete', barLength = 50)
# Calculate CRC
self._dfu_send_command(Procedures.CALC_CHECKSUM)
(proc, res, offset, crc32) = self._wait_and_parse_notify()
if(crc32 != crc32_unsigned(self.bin_array[0:offset])):
# Need to re-transmit object
return 0
# Execute command
self._dfu_send_command(Procedures.EXECUTE)
self._wait_and_parse_notify()
# If everything executed correctly, return amount of bytes transfered
return obj_max_size

188
bootloader/ota-dfu-python/dfu.py Executable file
View file

@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""
------------------------------------------------------------------------------
DFU Server for Nordic nRF51 based systems.
Conforms to nRF51_SDK 11.0 BLE_DFU requirements.
------------------------------------------------------------------------------
"""
import os, re
import sys
import optparse
import time
import math
import traceback
from unpacker import Unpacker
from ble_secure_dfu_controller import BleDfuControllerSecure
from ble_legacy_dfu_controller import BleDfuControllerLegacy
def main():
init_msg = """
================================
== ==
== DFU Server ==
== ==
================================
"""
# print "DFU Server start"
print(init_msg)
try:
parser = optparse.OptionParser(usage='%prog -f <hex_file> -a <dfu_target_address>\n\nExample:\n\tdfu.py -f application.hex -d application.dat -a cd:e3:4a:47:1c:e4',
version='0.5')
parser.add_option('-a', '--address',
action='store',
dest="address",
type="string",
default=None,
help='DFU target address.'
)
parser.add_option('-f', '--file',
action='store',
dest="hexfile",
type="string",
default=None,
help='hex file to be uploaded.'
)
parser.add_option('-d', '--dat',
action='store',
dest="datfile",
type="string",
default=None,
help='dat file to be uploaded.'
)
parser.add_option('-z', '--zip',
action='store',
dest="zipfile",
type="string",
default=None,
help='zip file to be used.'
)
parser.add_option('--secure',
action='store_true',
dest='secure_dfu',
default=True,
help='Use secure bootloader (Nordic SDK > 12)'
)
parser.add_option('--legacy',
action='store_false',
dest='secure_dfu',
help='Use secure bootloader (Nordic SDK < 12)'
)
options, args = parser.parse_args()
except Exception as e:
print(e)
print("For help use --help")
sys.exit(2)
try:
''' Validate input parameters '''
if not options.address:
parser.print_help()
exit(2)
unpacker = None
hexfile = None
datfile = None
if options.zipfile != None:
if (options.hexfile != None) or (options.datfile != None):
print("Conflicting input directives")
exit(2)
unpacker = Unpacker()
#print options.zipfile
try:
hexfile, datfile = unpacker.unpack_zipfile(options.zipfile)
except Exception as e:
print("ERR")
print(e)
pass
else:
if (not options.hexfile) or (not options.datfile):
parser.print_help()
exit(2)
if not os.path.isfile(options.hexfile):
print("Error: Hex file doesn't exist")
exit(2)
if not os.path.isfile(options.datfile):
print("Error: DAT file doesn't exist")
exit(2)
hexfile = options.hexfile
datfile = options.datfile
''' Start of Device Firmware Update processing '''
if options.secure_dfu:
ble_dfu = BleDfuControllerSecure(options.address.upper(), hexfile, datfile)
else:
ble_dfu = BleDfuControllerLegacy(options.address.upper(), hexfile, datfile)
# Initialize inputs
ble_dfu.input_setup()
# Connect to peer device. Assume application mode.
if ble_dfu.scan_and_connect():
if not ble_dfu.check_DFU_mode():
print("Need to switch to DFU mode")
success = ble_dfu.switch_to_dfu_mode()
if not success:
print("Couldn't reconnect")
else:
# The device might already be in DFU mode (MAC + 1)
ble_dfu.target_mac_increase(1)
# Try connection with new address
print("Couldn't connect, will try DFU MAC")
if not ble_dfu.scan_and_connect():
raise Exception("Can't connect to device")
ble_dfu.start()
# Disconnect from peer device if not done already and clean up.
ble_dfu.disconnect()
except Exception as e:
# print traceback.format_exc()
print("Exception at line {}: {}".format(sys.exc_info()[2].tb_lineno, e))
pass
except:
pass
# If Unpacker for zipfile used then delete Unpacker
if unpacker != None:
unpacker.delete()
print("DFU Server done")
"""
------------------------------------------------------------------------------
------------------------------------------------------------------------------
"""
if __name__ == '__main__':
# Do not litter the world with broken .pyc files.
sys.dont_write_bytecode = True
main()

View file

@ -0,0 +1,263 @@
import os
import pexpect
import re
from abc import ABCMeta, abstractmethod
from array import array
from util import *
verbose = False
class NrfBleDfuController(object, metaclass=ABCMeta):
ctrlpt_handle = 0
ctrlpt_cccd_handle = 0
data_handle = 0
pkt_receipt_interval = 10
pkt_payload_size = 20
# --------------------------------------------------------------------------
# Start the firmware update process
# --------------------------------------------------------------------------
@abstractmethod
def start(self):
pass
# --------------------------------------------------------------------------
# Check if the peripheral is running in bootloader (DFU) or application mode
# Returns True if the peripheral is in DFU mode
# --------------------------------------------------------------------------
@abstractmethod
def check_DFU_mode(self):
pass
@abstractmethod
# --------------------------------------------------------------------------
# Switch from application to bootloader (DFU)
# --------------------------------------------------------------------------
def switch_to_dfu_mode(self):
pass
# --------------------------------------------------------------------------
# Parse notification status results
# --------------------------------------------------------------------------
@abstractmethod
def _dfu_parse_notify(self, notify):
pass
# --------------------------------------------------------------------------
# Wait for a notification and parse the response
# --------------------------------------------------------------------------
@abstractmethod
def _wait_and_parse_notify(self):
pass
def __init__(self, target_mac, firmware_path, datfile_path):
self.target_mac = target_mac
self.firmware_path = firmware_path
self.datfile_path = datfile_path
self.ble_conn = pexpect.spawn("gatttool -b '%s' -t random --interactive" % target_mac)
self.ble_conn.delaybeforesend = 0
# --------------------------------------------------------------------------
# Start the firmware update process
# --------------------------------------------------------------------------
def start(self):
(_, self.ctrlpt_handle, self.ctrlpt_cccd_handle) = self._get_handles(self.UUID_CONTROL_POINT)
(_, self.data_handle, _) = self._get_handles(self.UUID_PACKET)
if verbose:
print('Control Point Handle: 0x%04x, CCCD: 0x%04x' % (self.ctrlpt_handle, self.ctrlpt_cccd_handle))
print('Packet handle: 0x%04x' % (self.data_handle))
# Subscribe to notifications from Control Point characteristic
self._enable_notifications(self.ctrlpt_cccd_handle)
# Set the Packet Receipt Notification interval
prn = uint16_to_bytes_le(self.pkt_receipt_interval)
self._dfu_send_command(Procedures.SET_PRN, prn)
self._dfu_send_init()
self._dfu_send_image()
# --------------------------------------------------------------------------
# Initialize:
# Hex: read and convert hexfile into bin_array
# Bin: read binfile into bin_array
# --------------------------------------------------------------------------
def input_setup(self):
print("Sending file " + os.path.split(self.firmware_path)[1] + " to " + self.target_mac)
if self.firmware_path == None:
raise Exception("input invalid")
name, extent = os.path.splitext(self.firmware_path)
if extent == ".bin":
self.bin_array = array('B', open(self.firmware_path, 'rb').read())
self.image_size = len(self.bin_array)
print("Binary imge size: %d" % self.image_size)
print("Binary CRC32: %d" % crc32_unsigned(array_to_hex_string(self.bin_array)))
return
if extent == ".hex":
intelhex = IntelHex(self.firmware_path)
self.bin_array = intelhex.tobinarray()
self.image_size = len(self.bin_array)
print("bin array size: ", self.image_size)
return
raise Exception("input invalid")
# --------------------------------------------------------------------------
# Perform a scan and connect via gatttool.
# Will return True if a connection was established, False otherwise
# --------------------------------------------------------------------------
def scan_and_connect(self, timeout=2):
if verbose: print("scan_and_connect")
print("Connecting to %s" % (self.target_mac))
try:
self.ble_conn.expect('\[LE\]>', timeout=timeout)
except pexpect.TIMEOUT as e:
return False
self.ble_conn.sendline('connect')
try:
res = self.ble_conn.expect('.*Connection successful.*', timeout=timeout)
except pexpect.TIMEOUT as e:
return False
return True
# --------------------------------------------------------------------------
# Disconnect from the peripheral and close the gatttool connection
# --------------------------------------------------------------------------
def disconnect(self):
self.ble_conn.sendline('exit')
self.ble_conn.close()
def target_mac_increase(self, inc):
self.target_mac = uint_to_mac_string(mac_string_to_uint(self.target_mac) + inc)
# Re-start gatttool with the new address
self.disconnect()
self.ble_conn = pexpect.spawn("gatttool -b '%s' -t random --interactive" % self.target_mac)
self.ble_conn.delaybeforesend = 0
# --------------------------------------------------------------------------
# Fetch handles for a given UUID.
# Will return a three-tuple: (char handle, value handle, CCCD handle)
# Will raise an exception if the UUID is not found
# --------------------------------------------------------------------------
def _get_handles(self, uuid):
self.ble_conn.before = ""
self.ble_conn.sendline('characteristics')
try:
self.ble_conn.expect([uuid], timeout=2)
handles = re.findall(b'.*handle: (0x....),.*char value handle: (0x....)', self.ble_conn.before)
(handle, value_handle) = handles[-1]
except pexpect.TIMEOUT as e:
raise Exception("UUID not found: {}".format(uuid))
return (int(handle, 16), int(value_handle, 16), int(value_handle, 16)+1)
# --------------------------------------------------------------------------
# Wait for notification to arrive.
# Example format: "Notification handle = 0x0019 value: 10 01 01"
# --------------------------------------------------------------------------
def _dfu_wait_for_notify(self):
while True:
if verbose: print("dfu_wait_for_notify")
if not self.ble_conn.isalive():
print("connection not alive")
return None
try:
index = self.ble_conn.expect('Notification handle = .*? \r\n', timeout=30)
except pexpect.TIMEOUT:
#
# The gatttool does not report link-lost directly.
# The only way found to detect it is monitoring the prompt '[CON]'
# and if it goes to '[ ]' this indicates the connection has
# been broken.
# In order to get a updated prompt string, issue an empty
# sendline(''). If it contains the '[ ]' string, then
# raise an exception. Otherwise, if not a link-lost condition,
# continue to wait.
#
self.ble_conn.sendline('')
string = self.ble_conn.before
if '[ ]' in string:
print('Connection lost! ')
raise Exception('Connection Lost')
return None
if index == 0:
after = self.ble_conn.after
hxstr = after.split()[3:]
handle = int(float.fromhex(hxstr[0].decode('UTF-8')))
return hxstr[2:]
else:
print("unexpeced index: {0}".format(index))
return None
# --------------------------------------------------------------------------
# Send a procedure + any parameters required
# --------------------------------------------------------------------------
def _dfu_send_command(self, procedure, params=[]):
if verbose: print('_dfu_send_command')
cmd = 'char-write-req 0x%04x %02x' % (self.ctrlpt_handle, procedure)
cmd += array_to_hex_string(params)
if verbose: print(cmd)
self.ble_conn.sendline(cmd)
# Verify that command was successfully written
try:
res = self.ble_conn.expect('Characteristic value was written successfully.*', timeout=10)
except pexpect.TIMEOUT as e:
print("State timeout")
# --------------------------------------------------------------------------
# Send an array of bytes
# --------------------------------------------------------------------------
def _dfu_send_data(self, data):
cmd = 'char-write-cmd 0x%04x' % (self.data_handle)
cmd += ' '
cmd += array_to_hex_string(data)
if verbose: print(cmd)
self.ble_conn.sendline(cmd)
# --------------------------------------------------------------------------
# Enable notifications from the Control Point Handle
# --------------------------------------------------------------------------
def _enable_notifications(self, cccd_handle):
if verbose: print('_enable_notifications')
cmd = 'char-write-req 0x%04x %s' % (cccd_handle, '0100')
if verbose: print(cmd)
self.ble_conn.sendline(cmd)
# Verify that command was successfully written
try:
res = self.ble_conn.expect('Characteristic value was written successfully.*', timeout=10)
except pexpect.TIMEOUT as e:
print("State timeout")

View file

@ -0,0 +1,52 @@
import os.path
import zipfile
import tempfile
import random
import string
import shutil
import re
from os.path import basename
class Unpacker(object):
#--------------------------------------------------------------------------
#
#--------------------------------------------------------------------------
def entropy(self, length):
return ''.join(random.choice('abcdefghijklmnopqrstuvwxyz') for i in range (length))
#--------------------------------------------------------------------------
#
#--------------------------------------------------------------------------
def unpack_zipfile(self, file):
if not os.path.isfile(file):
raise Exception("Error: file, not found!")
# Create unique working direction into which the zip file is expanded
self.unzip_dir = "{0}/{1}_{2}".format(tempfile.gettempdir(), os.path.splitext(basename(file))[0], self.entropy(6))
datfilename = ""
binfilename = ""
with zipfile.ZipFile(file, 'r') as zip:
files = [item.filename for item in zip.infolist()]
datfilename = [m.group(0) for f in files for m in [re.search('.*\.dat', f)] if m].pop()
binfilename = [m.group(0) for f in files for m in [re.search('.*\.bin', f)] if m].pop()
zip.extractall(r'{0}'.format(self.unzip_dir))
datfile = "{0}/{1}".format(self.unzip_dir, datfilename)
binfile = "{0}/{1}".format(self.unzip_dir, binfilename)
# print "DAT file: " + datfile
# print "BIN file: " + binfile
return binfile, datfile
#--------------------------------------------------------------------------
#
#--------------------------------------------------------------------------
def delete(self):
# delete self.unzip_dir and its contents
shutil.rmtree(self.unzip_dir)

View file

@ -0,0 +1,70 @@
import sys
import binascii
import re
def bytes_to_uint32_le(bytes):
return (int(bytes[3], 16) << 24) | (int(bytes[2], 16) << 16) | (int(bytes[1], 16) << 8) | (int(bytes[0], 16) << 0)
def uint32_to_bytes_le(uint32):
return [(uint32 >> 0) & 0xff,
(uint32 >> 8) & 0xff,
(uint32 >> 16) & 0xff,
(uint32 >> 24) & 0xff]
def uint16_to_bytes_le(value):
return [(value >> 0 & 0xFF),
(value >> 8 & 0xFF)]
def zero_pad_array_le(data, padsize):
for i in range(0, padsize):
data.insert(0, 0)
def array_to_hex_string(arr):
hex_str = ""
for val in arr:
if val > 255:
raise Exception("Value is greater than it is possible to represent with one byte")
hex_str += "%02x" % val
return hex_str
def crc32_unsigned(bytestring):
return binascii.crc32(bytestring.encode('UTF-8')) % (1 << 32)
def mac_string_to_uint(mac):
parts = list(re.match('(..):(..):(..):(..):(..):(..)', mac).groups())
ints = [int(x, 16) for x in parts]
res = 0
for i in range(0, len(ints)):
res += (ints[len(ints)-1 - i] << 8*i)
return res
def uint_to_mac_string(mac):
ints = [0, 0, 0, 0, 0, 0]
for i in range(0, len(ints)):
ints[len(ints)-1 - i] = (mac >> 8*i) & 0xff
return ':'.join(['{:02x}'.format(x).upper() for x in ints])
# Print a nice console progress bar
def print_progress(iteration, total, prefix = '', suffix = '', decimals = 1, barLength = 100):
"""
Call in a loop to create terminal progress bar
@params:
iteration - Required : current iteration (Int)
total - Required : total iterations (Int)
prefix - Optional : prefix string (Str)
suffix - Optional : suffix string (Str)
decimals - Optional : positive number of decimals in percent complete (Int)
barLength - Optional : character length of bar (Int)
"""
formatStr = "{0:." + str(decimals) + "f}"
percents = formatStr.format(100 * (iteration / float(total)))
filledLength = int(round(barLength * iteration / float(total)))
bar = 'x' * filledLength + '-' * (barLength - filledLength)
sys.stdout.write('\r%s |%s| %s%s %s (%d of %d bytes)' % (prefix, bar, percents, '%', suffix, iteration, total)),
if iteration == total:
sys.stdout.write('\n')
sys.stdout.flush()

136
gcc_nrf52-mcuboot.ld Normal file
View file

@ -0,0 +1,136 @@
/* Linker script to configure memory regions. */
SEARCH_DIR(.)
GROUP(-lgcc -lc -lnosys)
MEMORY
{
FLASH (rx) : ORIGIN = 0x08020, LENGTH = 0x78000
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x10000
}
SECTIONS
{
}
SECTIONS
{
. = ALIGN(4);
.mem_section_dummy_ram :
{
}
.cli_sorted_cmd_ptrs :
{
PROVIDE(__start_cli_sorted_cmd_ptrs = .);
KEEP(*(.cli_sorted_cmd_ptrs))
PROVIDE(__stop_cli_sorted_cmd_ptrs = .);
} > RAM
.fs_data :
{
PROVIDE(__start_fs_data = .);
KEEP(*(.fs_data))
PROVIDE(__stop_fs_data = .);
} > RAM
.log_dynamic_data :
{
PROVIDE(__start_log_dynamic_data = .);
KEEP(*(SORT(.log_dynamic_data*)))
PROVIDE(__stop_log_dynamic_data = .);
} > RAM
.log_filter_data :
{
PROVIDE(__start_log_filter_data = .);
KEEP(*(SORT(.log_filter_data*)))
PROVIDE(__stop_log_filter_data = .);
} > RAM
} INSERT AFTER .data;
SECTIONS
{
.mem_section_dummy_rom :
{
}
.sdh_soc_observers :
{
PROVIDE(__start_sdh_soc_observers = .);
KEEP(*(SORT(.sdh_soc_observers*)))
PROVIDE(__stop_sdh_soc_observers = .);
} > FLASH
.sdh_ble_observers :
{
PROVIDE(__start_sdh_ble_observers = .);
KEEP(*(SORT(.sdh_ble_observers*)))
PROVIDE(__stop_sdh_ble_observers = .);
} > FLASH
.sdh_req_observers :
{
PROVIDE(__start_sdh_req_observers = .);
KEEP(*(SORT(.sdh_req_observers*)))
PROVIDE(__stop_sdh_req_observers = .);
} > FLASH
.sdh_state_observers :
{
PROVIDE(__start_sdh_state_observers = .);
KEEP(*(SORT(.sdh_state_observers*)))
PROVIDE(__stop_sdh_state_observers = .);
} > FLASH
.sdh_stack_observers :
{
PROVIDE(__start_sdh_stack_observers = .);
KEEP(*(SORT(.sdh_stack_observers*)))
PROVIDE(__stop_sdh_stack_observers = .);
} > FLASH
.nrf_queue :
{
PROVIDE(__start_nrf_queue = .);
KEEP(*(.nrf_queue))
PROVIDE(__stop_nrf_queue = .);
} > FLASH
.nrf_balloc :
{
PROVIDE(__start_nrf_balloc = .);
KEEP(*(.nrf_balloc))
PROVIDE(__stop_nrf_balloc = .);
} > FLASH
.cli_command :
{
PROVIDE(__start_cli_command = .);
KEEP(*(.cli_command))
PROVIDE(__stop_cli_command = .);
} > FLASH
.crypto_data :
{
PROVIDE(__start_crypto_data = .);
KEEP(*(SORT(.crypto_data*)))
PROVIDE(__stop_crypto_data = .);
} > FLASH
.pwr_mgmt_data :
{
PROVIDE(__start_pwr_mgmt_data = .);
KEEP(*(SORT(.pwr_mgmt_data*)))
PROVIDE(__stop_pwr_mgmt_data = .);
} > FLASH
.log_const_data :
{
PROVIDE(__start_log_const_data = .);
KEEP(*(SORT(.log_const_data*)))
PROVIDE(__stop_log_const_data = .);
} > FLASH
.log_backends :
{
PROVIDE(__start_log_backends = .);
KEEP(*(SORT(.log_backends*)))
PROVIDE(__stop_log_backends = .);
} > FLASH
.nrf_balloc :
{
PROVIDE(__start_nrf_balloc = .);
KEEP(*(.nrf_balloc))
PROVIDE(__stop_nrf_balloc = .);
} > FLASH
} INSERT AFTER .text
INCLUDE "./nrf_common.ld"

View file

@ -5,7 +5,7 @@ GROUP(-lgcc -lc -lnosys)
MEMORY MEMORY
{ {
FLASH (rx) : ORIGIN = 0x00000, LENGTH = 0x80000 FLASH (rx) : ORIGIN = 0x00000, LENGTH = 0x78000
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x10000 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x10000
} }

View file

@ -5,29 +5,86 @@ project(pinetime-app C CXX ASM)
# define some variables just for this example to determine file locations # define some variables just for this example to determine file locations
set(NRF_PROJECT_NAME pinetime-app) set(NRF_PROJECT_NAME pinetime-app)
set(NRF_BOARD pca10040) set(NRF_BOARD pca10040)
#set(NRF_SOFTDEVICE s132)
nRF5x_toolchainSetup() # check if all the necessary tools paths have been provided.
nRF5x_setup() if (NOT NRF5_SDK_PATH)
message(FATAL_ERROR "The path to the nRF5 SDK (NRF5_SDK_PATH) must be set.")
endif ()
if(DEFINED ARM_NONE_EABI_TOOLCHAIN_PATH)
set(ARM_NONE_EABI_TOOLCHAIN_BIN_PATH ${ARM_NONE_EABI_TOOLCHAIN_PATH}/bin)
endif()
#nRF5x_addAppScheduler() if (NOT NRF_TARGET MATCHES "nrf52")
#nRF5x_addAppFIFO() message(FATAL_ERROR "Only rRF52 boards are supported right now")
#nRF5x_addAppTimer() endif()
#nRF5x_addAppUART()
nRF5x_addAppButton()
nRF5x_addBSP(FALSE FALSE FALSE)
nRF5x_addAppGpiote()
#nRF5x_addBLEGATT()
#
#nRF5x_addBLEService(ble_lbs)
add_definitions(-DCONFIG_GPIO_AS_PINRESET) # Setup toolchain
add_definitions(-DDEBUG) include(${CMAKE_SOURCE_DIR}/cmake-nRF5x/arm-gcc-toolchain.cmake)
add_definitions(-DNIMBLE_CFG_CONTROLLER)
add_definitions(-DOS_CPUTIME_FREQ)
include_directories(.) if(NOT DEFINED ARM_GCC_TOOLCHAIN)
include_directories(libs/) message(FATAL_ERROR "The toolchain must be set up before calling this macro")
endif()
set(CMAKE_OSX_SYSROOT "/")
set(CMAKE_OSX_DEPLOYMENT_TARGET "")
set(SDK_SOURCE_FILES
# Startup
"${NRF5_SDK_PATH}/modules/nrfx/mdk/system_nrf52.c"
"${NRF5_SDK_PATH}/modules/nrfx/mdk/gcc_startup_nrf52.S"
# Base SDK
"${NRF5_SDK_PATH}/components/boards/boards.c"
"${NRF5_SDK_PATH}/integration/nrfx/legacy/nrf_drv_clock.c"
"${NRF5_SDK_PATH}/modules/nrfx/drivers/src/nrfx_clock.c"
"${NRF5_SDK_PATH}/modules/nrfx/drivers/src/nrfx_gpiote.c"
"${NRF5_SDK_PATH}/modules/nrfx/soc/nrfx_atomic.c"
"${NRF5_SDK_PATH}/modules/nrfx/drivers/src/nrfx_saadc.c"
# FreeRTOS
${NRF5_SDK_PATH}/external/freertos/source/croutine.c
${NRF5_SDK_PATH}/external/freertos/source/event_groups.c
${NRF5_SDK_PATH}/external/freertos/source/portable/MemMang/heap_1.c
${NRF5_SDK_PATH}/external/freertos/source/list.c
${NRF5_SDK_PATH}/external/freertos/source/queue.c
${NRF5_SDK_PATH}/external/freertos/source/stream_buffer.c
${NRF5_SDK_PATH}/external/freertos/source/tasks.c
${NRF5_SDK_PATH}/external/freertos/source/timers.c
${NRF5_SDK_PATH}/components/libraries/timer/app_timer_freertos.c
# Libs
"${NRF5_SDK_PATH}/components/libraries/atomic/nrf_atomic.c"
"${NRF5_SDK_PATH}/components/libraries/balloc/nrf_balloc.c"
"${NRF5_SDK_PATH}/components/libraries/util/nrf_assert.c"
"${NRF5_SDK_PATH}/components/libraries/util/app_error.c"
"${NRF5_SDK_PATH}/components/libraries/util/app_error_weak.c"
"${NRF5_SDK_PATH}/components/libraries/util/app_error_handler_gcc.c"
"${NRF5_SDK_PATH}/components/libraries/util/app_util_platform.c"
"${NRF5_SDK_PATH}/components/libraries/log/src/nrf_log_backend_rtt.c"
"${NRF5_SDK_PATH}/components/libraries/log/src/nrf_log_backend_serial.c"
"${NRF5_SDK_PATH}/components/libraries/log/src/nrf_log_default_backends.c"
"${NRF5_SDK_PATH}/components/libraries/log/src/nrf_log_frontend.c"
"${NRF5_SDK_PATH}/components/libraries/log/src/nrf_log_str_formatter.c"
"${NRF5_SDK_PATH}/components/libraries/memobj/nrf_memobj.c"
"${NRF5_SDK_PATH}/components/libraries/ringbuf/nrf_ringbuf.c"
"${NRF5_SDK_PATH}/components/libraries/strerror/nrf_strerror.c"
# Segger RTT
"${NRF5_SDK_PATH}/external/segger_rtt/SEGGER_RTT_Syscalls_GCC.c"
"${NRF5_SDK_PATH}/external/segger_rtt/SEGGER_RTT.c"
"${NRF5_SDK_PATH}/external/segger_rtt/SEGGER_RTT_printf.c"
# Other
"${NRF5_SDK_PATH}/external/utf_converter/utf.c"
"${NRF5_SDK_PATH}/external/fprintf/nrf_fprintf.c"
"${NRF5_SDK_PATH}/external/fprintf/nrf_fprintf_format.c"
# TWI
"${NRF5_SDK_PATH}/modules/nrfx/drivers/src/nrfx_twi.c"
# GPIOTE
"${NRF5_SDK_PATH}/components/libraries/gpiote/app_gpiote.c"
)
set(TINYCRYPT_SRC set(TINYCRYPT_SRC
libs/mynewt-nimble/ext/tinycrypt/src/aes_encrypt.c libs/mynewt-nimble/ext/tinycrypt/src/aes_encrypt.c
@ -37,9 +94,6 @@ set(TINYCRYPT_SRC
set(NIMBLE_SRC set(NIMBLE_SRC
libs/mynewt-nimble/porting/npl/freertos/src/nimble_port_freertos.c libs/mynewt-nimble/porting/npl/freertos/src/nimble_port_freertos.c
libs/mynewt-nimble/porting/npl/freertos/src/npl_os_freertos.c libs/mynewt-nimble/porting/npl/freertos/src/npl_os_freertos.c
libs/mynewt-nimble/nimble/host/src/ble_hs.c libs/mynewt-nimble/nimble/host/src/ble_hs.c
libs/mynewt-nimble/nimble/host/src/ble_hs_hci_evt.c libs/mynewt-nimble/nimble/host/src/ble_hs_hci_evt.c
libs/mynewt-nimble/nimble/host/src/ble_l2cap_sig_cmd.c libs/mynewt-nimble/nimble/host/src/ble_l2cap_sig_cmd.c
@ -78,11 +132,7 @@ set(NIMBLE_SRC
libs/mynewt-nimble/nimble/host/src/ble_hs_stop.c libs/mynewt-nimble/nimble/host/src/ble_hs_stop.c
libs/mynewt-nimble/nimble/host/src/ble_hs_startup.c libs/mynewt-nimble/nimble/host/src/ble_hs_startup.c
libs/mynewt-nimble/nimble/host/store/ram/src/ble_store_ram.c libs/mynewt-nimble/nimble/host/store/ram/src/ble_store_ram.c
libs/mynewt-nimble/nimble/transport/ram/src/ble_hci_ram.c libs/mynewt-nimble/nimble/transport/ram/src/ble_hci_ram.c
libs/mynewt-nimble/nimble/controller/src/ble_ll.c libs/mynewt-nimble/nimble/controller/src/ble_ll.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_rand.c libs/mynewt-nimble/nimble/controller/src/ble_ll_rand.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_conn.c libs/mynewt-nimble/nimble/controller/src/ble_ll_conn.c
@ -97,9 +147,6 @@ set(NIMBLE_SRC
libs/mynewt-nimble/nimble/controller/src/ble_ll_supp_cmd.c libs/mynewt-nimble/nimble/controller/src/ble_ll_supp_cmd.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_hci_ev.c libs/mynewt-nimble/nimble/controller/src/ble_ll_hci_ev.c
libs/mynewt-nimble/nimble/controller/src/ble_ll_rfmgmt.c libs/mynewt-nimble/nimble/controller/src/ble_ll_rfmgmt.c
libs/mynewt-nimble/porting/nimble/src/os_cputime.c libs/mynewt-nimble/porting/nimble/src/os_cputime.c
libs/mynewt-nimble/porting/nimble/src/os_cputime_pwr2.c libs/mynewt-nimble/porting/nimble/src/os_cputime_pwr2.c
libs/mynewt-nimble/porting/nimble/src/os_mbuf.c libs/mynewt-nimble/porting/nimble/src/os_mbuf.c
@ -108,13 +155,10 @@ set(NIMBLE_SRC
libs/mynewt-nimble/porting/nimble/src/mem.c libs/mynewt-nimble/porting/nimble/src/mem.c
libs/mynewt-nimble/porting/nimble/src/endian.c libs/mynewt-nimble/porting/nimble/src/endian.c
libs/mynewt-nimble/porting/nimble/src/os_msys_init.c libs/mynewt-nimble/porting/nimble/src/os_msys_init.c
libs/mynewt-nimble/nimble/drivers/nrf52/src/ble_hw.c libs/mynewt-nimble/nimble/drivers/nrf52/src/ble_hw.c
libs/mynewt-nimble/nimble/drivers/nrf52/src/ble_phy.c libs/mynewt-nimble/nimble/drivers/nrf52/src/ble_phy.c
libs/mynewt-nimble/nimble/host/services/gap/src/ble_svc_gap.c libs/mynewt-nimble/nimble/host/services/gap/src/ble_svc_gap.c
libs/mynewt-nimble/nimble/host/services/gatt/src/ble_svc_gatt.c libs/mynewt-nimble/nimble/host/services/gatt/src/ble_svc_gatt.c
libs/mynewt-nimble/nimble/host/util/src/addr.c libs/mynewt-nimble/nimble/host/util/src/addr.c
) )
@ -135,7 +179,6 @@ set(LVGL_SRC
libs/lvgl/src/lv_core/lv_refr.h libs/lvgl/src/lv_core/lv_refr.h
libs/lvgl/src/lv_core/lv_style.c libs/lvgl/src/lv_core/lv_style.c
libs/lvgl/src/lv_core/lv_style.h libs/lvgl/src/lv_core/lv_style.h
libs/lvgl/src/lv_misc/lv_anim.c libs/lvgl/src/lv_misc/lv_anim.c
libs/lvgl/src/lv_misc/lv_anim.h libs/lvgl/src/lv_misc/lv_anim.h
libs/lvgl/src/lv_misc/lv_async.h libs/lvgl/src/lv_misc/lv_async.h
@ -175,7 +218,6 @@ set(LVGL_SRC
libs/lvgl/src/lv_misc/lv_types.h libs/lvgl/src/lv_misc/lv_types.h
libs/lvgl/src/lv_misc/lv_utils.c libs/lvgl/src/lv_misc/lv_utils.c
libs/lvgl/src/lv_misc/lv_utils.h libs/lvgl/src/lv_misc/lv_utils.h
libs/lvgl/src/lv_draw/lv_draw.c libs/lvgl/src/lv_draw/lv_draw.c
libs/lvgl/src/lv_draw/lv_draw.h libs/lvgl/src/lv_draw/lv_draw.h
libs/lvgl/src/lv_draw/lv_draw_arc.c libs/lvgl/src/lv_draw/lv_draw_arc.c
@ -196,7 +238,6 @@ set(LVGL_SRC
libs/lvgl/src/lv_draw/lv_img_cache.h libs/lvgl/src/lv_draw/lv_img_cache.h
libs/lvgl/src/lv_draw/lv_img_decoder.c libs/lvgl/src/lv_draw/lv_img_decoder.c
libs/lvgl/src/lv_draw/lv_img_decoder.h libs/lvgl/src/lv_draw/lv_img_decoder.h
libs/lvgl/src/lv_hal/lv_hal.h libs/lvgl/src/lv_hal/lv_hal.h
libs/lvgl/src/lv_hal/lv_hal_disp.c libs/lvgl/src/lv_hal/lv_hal_disp.c
libs/lvgl/src/lv_hal/lv_hal_disp.h libs/lvgl/src/lv_hal/lv_hal_disp.h
@ -204,31 +245,23 @@ set(LVGL_SRC
libs/lvgl/src/lv_hal/lv_hal_indev.h libs/lvgl/src/lv_hal/lv_hal_indev.h
libs/lvgl/src/lv_hal/lv_hal_tick.c libs/lvgl/src/lv_hal/lv_hal_tick.c
libs/lvgl/src/lv_hal/lv_hal_tick.h libs/lvgl/src/lv_hal/lv_hal_tick.h
libs/lvgl/src/lv_font/lv_font.c libs/lvgl/src/lv_font/lv_font.c
libs/lvgl/src/lv_font/lv_font.h libs/lvgl/src/lv_font/lv_font.h
libs/lvgl/src/lv_font/lv_font_fmt_txt.c libs/lvgl/src/lv_font/lv_font_fmt_txt.c
libs/lvgl/src/lv_font/lv_font_fmt_txt.h libs/lvgl/src/lv_font/lv_font_fmt_txt.h
# libs/lvgl/src/lv_font/lv_font_roboto_16.c
libs/lvgl/src/lv_font/lv_symbol_def.h libs/lvgl/src/lv_font/lv_symbol_def.h
libs/lvgl/src/lv_themes/lv_theme.c libs/lvgl/src/lv_themes/lv_theme.c
libs/lvgl/src/lv_themes/lv_theme.h libs/lvgl/src/lv_themes/lv_theme.h
libs/lvgl/src/lv_objx/lv_btn.h libs/lvgl/src/lv_objx/lv_btn.h
libs/lvgl/src/lv_objx/lv_btn.c libs/lvgl/src/lv_objx/lv_btn.c
libs/lvgl/src/lv_objx/lv_cont.h libs/lvgl/src/lv_objx/lv_cont.h
libs/lvgl/src/lv_objx/lv_cont.c libs/lvgl/src/lv_objx/lv_cont.c
libs/lvgl/src/lv_objx/lv_label.h libs/lvgl/src/lv_objx/lv_label.h
libs/lvgl/src/lv_objx/lv_label.c libs/lvgl/src/lv_objx/lv_label.c
libs/lvgl/src/lv_themes/lv_theme.c libs/lvgl/src/lv_themes/lv_theme.c
libs/lvgl/src/lv_themes/lv_theme.h libs/lvgl/src/lv_themes/lv_theme.h
libs/lvgl/src/lv_themes/lv_theme_night.h libs/lvgl/src/lv_themes/lv_theme_night.h
libs/lvgl/src/lv_themes/lv_theme_night.c libs/lvgl/src/lv_themes/lv_theme_night.c
libs/lvgl/src/lv_objx/lv_list.c libs/lvgl/src/lv_objx/lv_list.c
libs/lvgl/src/lv_objx/lv_list.h libs/lvgl/src/lv_objx/lv_list.h
libs/lvgl/src/lv_objx/lv_tileview.c libs/lvgl/src/lv_objx/lv_tileview.c
@ -247,20 +280,16 @@ set(LVGL_SRC
libs/lvgl/src/lv_objx/lv_arc.h libs/lvgl/src/lv_objx/lv_arc.h
libs/lvgl/src/lv_objx/lv_gauge.c libs/lvgl/src/lv_objx/lv_gauge.c
libs/lvgl/src/lv_objx/lv_gauge.h libs/lvgl/src/lv_objx/lv_gauge.h
libs/lvgl/src/lv_objx/lv_mbox.c libs/lvgl/src/lv_objx/lv_mbox.c
libs/lvgl/src/lv_objx/lv_mbox.h libs/lvgl/src/lv_objx/lv_mbox.h
libs/lvgl/src/lv_objx/lv_bar.c libs/lvgl/src/lv_objx/lv_bar.c
libs/lvgl/src/lv_objx/lv_bar.h libs/lvgl/src/lv_objx/lv_bar.h
libs/lvgl/src/lv_objx/lv_slider.h libs/lvgl/src/lv_objx/lv_slider.h
libs/lvgl/src/lv_objx/lv_slider.c libs/lvgl/src/lv_objx/lv_slider.c
) )
list(APPEND IMAGE_FILES list(APPEND IMAGE_FILES
DisplayApp/Icons/battery/os_battery_error.c DisplayApp/Icons/battery/os_battery_error.c
DisplayApp/Icons/battery/os_battery_100.c DisplayApp/Icons/battery/os_battery_100.c
DisplayApp/Icons/battery/os_battery_090.c DisplayApp/Icons/battery/os_battery_090.c
DisplayApp/Icons/battery/os_battery_080.c DisplayApp/Icons/battery/os_battery_080.c
@ -305,11 +334,15 @@ list(APPEND SOURCE_FILES
DisplayApp/Screens/Brightness.cpp DisplayApp/Screens/Brightness.cpp
DisplayApp/Screens/ScreenList.cpp DisplayApp/Screens/ScreenList.cpp
DisplayApp/Screens/Label.cpp DisplayApp/Screens/Label.cpp
DisplayApp/Screens/FirmwareUpdate.cpp
main.cpp main.cpp
drivers/St7789.cpp drivers/St7789.cpp
drivers/SpiNorFlash.cpp
drivers/SpiMaster.cpp drivers/SpiMaster.cpp
drivers/Spi.cpp
drivers/Watchdog.cpp drivers/Watchdog.cpp
drivers/DebugPins.cpp drivers/DebugPins.cpp
drivers/InternalFlash.cpp
Components/Battery/BatteryController.cpp Components/Battery/BatteryController.cpp
Components/Ble/BleController.cpp Components/Ble/BleController.cpp
Components/Ble/NotificationManager.cpp Components/Ble/NotificationManager.cpp
@ -319,6 +352,7 @@ list(APPEND SOURCE_FILES
Components/Ble/DeviceInformationService.cpp Components/Ble/DeviceInformationService.cpp
Components/Ble/CurrentTimeClient.cpp Components/Ble/CurrentTimeClient.cpp
Components/Ble/AlertNotificationClient.cpp Components/Ble/AlertNotificationClient.cpp
Components/Ble/DfuService.cpp
Components/Ble/CurrentTimeService.cpp Components/Ble/CurrentTimeService.cpp
Components/Ble/AlertNotificationService.cpp Components/Ble/AlertNotificationService.cpp
drivers/Cst816s.cpp drivers/Cst816s.cpp
@ -329,6 +363,7 @@ list(APPEND SOURCE_FILES
${NIMBLE_SRC} ${NIMBLE_SRC}
${LVGL_SRC} ${LVGL_SRC}
${IMAGE_FILES} ${IMAGE_FILES}
${SDK_SOURCE_FILES}
DisplayApp/LittleVgl.cpp DisplayApp/LittleVgl.cpp
DisplayApp/Fonts/jetbrains_mono_extrabold_compressed.c DisplayApp/Fonts/jetbrains_mono_extrabold_compressed.c
@ -337,6 +372,26 @@ list(APPEND SOURCE_FILES
SystemTask/SystemTask.cpp SystemTask/SystemTask.cpp
) )
list(APPEND GRAPHICS_SOURCE_FILES
${SDK_SOURCE_FILES}
# FreeRTOS
FreeRTOS/port.c
FreeRTOS/port_cmsis_systick.c
FreeRTOS/port_cmsis.c
drivers/SpiNorFlash.cpp
drivers/SpiMaster.cpp
drivers/Spi.cpp
Logging/NrfLogger.cpp
Components/Gfx/Gfx.cpp
drivers/St7789.cpp
Components/Brightness/BrightnessController.cpp
graphics.cpp
)
set(INCLUDE_FILES set(INCLUDE_FILES
Logging/Logger.h Logging/Logger.h
Logging/NrfLogger.h Logging/NrfLogger.h
@ -355,10 +410,14 @@ set(INCLUDE_FILES
DisplayApp/Screens/Brightness.h DisplayApp/Screens/Brightness.h
DisplayApp/Screens/ScreenList.h DisplayApp/Screens/ScreenList.h
DisplayApp/Screens/Label.h DisplayApp/Screens/Label.h
DisplayApp/Screens/FirmwareUpdate.h
drivers/St7789.h drivers/St7789.h
drivers/SpiNorFlash.h
drivers/SpiMaster.h drivers/SpiMaster.h
drivers/Spi.h
drivers/Watchdog.h drivers/Watchdog.h
drivers/DebugPins.h drivers/DebugPins.h
drivers/InternalFlash.h
Components/Battery/BatteryController.h Components/Battery/BatteryController.h
Components/Ble/BleController.h Components/Ble/BleController.h
Components/Ble/NotificationManager.h Components/Ble/NotificationManager.h
@ -368,6 +427,7 @@ set(INCLUDE_FILES
Components/Ble/DeviceInformationService.h Components/Ble/DeviceInformationService.h
Components/Ble/CurrentTimeClient.h Components/Ble/CurrentTimeClient.h
Components/Ble/AlertNotificationClient.h Components/Ble/AlertNotificationClient.h
Components/Ble/DfuService.h
drivers/Cst816s.h drivers/Cst816s.h
FreeRTOS/portmacro.h FreeRTOS/portmacro.h
FreeRTOS/portmacro_cmsis.h FreeRTOS/portmacro_cmsis.h
@ -379,13 +439,14 @@ set(INCLUDE_FILES
libs/date/includes/date/julian.h libs/date/includes/date/julian.h
libs/date/includes/date/ptz.h libs/date/includes/date/ptz.h
libs/date/includes/date/tz_private.h libs/date/includes/date/tz_private.h
DisplayApp/LittleVgl.h DisplayApp/LittleVgl.h
SystemTask/SystemTask.h SystemTask/SystemTask.h
) )
include_directories( include_directories(
.
../
libs/
FreeRTOS/ FreeRTOS/
libs/date/includes libs/date/includes
libs/mynewt-nimble/porting/npl/freertos/include libs/mynewt-nimble/porting/npl/freertos/include
@ -400,10 +461,205 @@ include_directories(
libs/mynewt-nimble/nimble/host/services/gatt/include libs/mynewt-nimble/nimble/host/services/gatt/include
libs/mynewt-nimble/nimble/host/util/include libs/mynewt-nimble/nimble/host/util/include
libs/mynewt-nimble/nimble/host/store/ram/include libs/mynewt-nimble/nimble/host/store/ram/include
"${NRF5_SDK_PATH}/components/drivers_nrf/nrf_soc_nosd"
"${NRF5_SDK_PATH}/components"
"${NRF5_SDK_PATH}/components/boards"
"${NRF5_SDK_PATH}/components/softdevice/common"
"${NRF5_SDK_PATH}/integration/nrfx"
"${NRF5_SDK_PATH}/integration/nrfx/legacy"
"${NRF5_SDK_PATH}/modules/nrfx"
"${NRF5_SDK_PATH}/modules/nrfx/drivers/include"
"${NRF5_SDK_PATH}/modules/nrfx/hal"
"${NRF5_SDK_PATH}/modules/nrfx/mdk"
${NRF5_SDK_PATH}/external/freertos/source/include
"${NRF5_SDK_PATH}/components/toolchain/cmsis/include"
"${NRF5_SDK_PATH}/components/libraries/atomic"
"${NRF5_SDK_PATH}/components/libraries/atomic_fifo"
"${NRF5_SDK_PATH}/components/libraries/atomic_flags"
"${NRF5_SDK_PATH}/components/libraries/balloc"
"${NRF5_SDK_PATH}/components/libraries/bootloader/ble_dfu"
"${NRF5_SDK_PATH}/components/libraries/cli"
"${NRF5_SDK_PATH}/components/libraries/crc16"
"${NRF5_SDK_PATH}/components/libraries/crc32"
"${NRF5_SDK_PATH}/components/libraries/crypto"
"${NRF5_SDK_PATH}/components/libraries/csense"
"${NRF5_SDK_PATH}/components/libraries/csense_drv"
"${NRF5_SDK_PATH}/components/libraries/delay"
"${NRF5_SDK_PATH}/components/libraries/ecc"
"${NRF5_SDK_PATH}/components/libraries/experimental_section_vars"
"${NRF5_SDK_PATH}/components/libraries/experimental_task_manager"
"${NRF5_SDK_PATH}/components/libraries/fds"
"${NRF5_SDK_PATH}/components/libraries/fstorage"
"${NRF5_SDK_PATH}/components/libraries/gfx"
"${NRF5_SDK_PATH}/components/libraries/gpiote"
"${NRF5_SDK_PATH}/components/libraries/hardfault"
"${NRF5_SDK_PATH}/components/libraries/hci"
"${NRF5_SDK_PATH}/components/libraries/led_softblink"
"${NRF5_SDK_PATH}/components/libraries/log"
"${NRF5_SDK_PATH}/components/libraries/log/src"
"${NRF5_SDK_PATH}/components/libraries/low_power_pwm"
"${NRF5_SDK_PATH}/components/libraries/mem_manager"
"${NRF5_SDK_PATH}/components/libraries/memobj"
"${NRF5_SDK_PATH}/components/libraries/mpu"
"${NRF5_SDK_PATH}/components/libraries/mutex"
"${NRF5_SDK_PATH}/components/libraries/pwm"
"${NRF5_SDK_PATH}/components/libraries/pwr_mgmt"
"${NRF5_SDK_PATH}/components/libraries/queue"
"${NRF5_SDK_PATH}/components/libraries/ringbuf"
"${NRF5_SDK_PATH}/components/libraries/scheduler"
"${NRF5_SDK_PATH}/components/libraries/sdcard"
"${NRF5_SDK_PATH}/components/libraries/slip"
"${NRF5_SDK_PATH}/components/libraries/sortlist"
"${NRF5_SDK_PATH}/components/libraries/spi_mngr"
"${NRF5_SDK_PATH}/components/libraries/stack_guard"
"${NRF5_SDK_PATH}/components/libraries/strerror"
"${NRF5_SDK_PATH}/components/libraries/svc"
"${NRF5_SDK_PATH}/components/libraries/timer"
"${NRF5_SDK_PATH}/components/libraries/usbd"
"${NRF5_SDK_PATH}/components/libraries/usbd/class/audio"
"${NRF5_SDK_PATH}/components/libraries/usbd/class/cdc"
"${NRF5_SDK_PATH}/components/libraries/usbd/class/cdc/acm"
"${NRF5_SDK_PATH}/components/libraries/usbd/class/hid"
"${NRF5_SDK_PATH}/components/libraries/usbd/class/hid/generic"
"${NRF5_SDK_PATH}/components/libraries/usbd/class/hid/kbd"
"${NRF5_SDK_PATH}/components/libraries/usbd/class/hid/mouse"
"${NRF5_SDK_PATH}/components/libraries/usbd/class/msc"
"${NRF5_SDK_PATH}/components/libraries/util"
"${NRF5_SDK_PATH}/external/segger_rtt/"
"${NRF5_SDK_PATH}/external/fprintf/"
"${NRF5_SDK_PATH}/external/thedotfactory_fonts"
"${NRF5_SDK_PATH}/components/libraries/gpiote"
) )
link_directories( link_directories(
../
) )
nRF5x_addExecutable(pinetime-app "${SOURCE_FILES}" ${INCLUDE_FILES})
set(COMMON_FLAGS -MP -MD -mthumb -mabi=aapcs -Wall -g3 -ffunction-sections -fdata-sections -fno-strict-aliasing -fno-builtin --short-enums -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wreturn-type -Werror=return-type)
add_definitions(-DCONFIG_GPIO_AS_PINRESET)
add_definitions(-DDEBUG)
add_definitions(-DNIMBLE_CFG_CONTROLLER)
add_definitions(-DOS_CPUTIME_FREQ)
add_definitions(-DNRF52 -DNRF52832 -DNRF52832_XXAA -DNRF52_PAN_74 -DNRF52_PAN_64 -DNRF52_PAN_12 -DNRF52_PAN_58 -DNRF52_PAN_54 -DNRF52_PAN_31 -DNRF52_PAN_51 -DNRF52_PAN_36 -DNRF52_PAN_15 -DNRF52_PAN_20 -DNRF52_PAN_55 -DBOARD_PCA10040)
add_definitions(-DFREERTOS)
add_definitions(-DDEBUG_NRF_USER)
# Build autonomous binary (without support for bootloader)
set(EXECUTABLE_NAME "pinetime-app")
set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld")
add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES})
target_compile_options(${EXECUTABLE_NAME} PUBLIC
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -O0 -g3>
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -O3>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -O0 -g3>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -O3>
$<$<COMPILE_LANGUAGE:ASM>: -MP -MD -std=c99 -x assembler-with-cpp>
)
set_target_properties(${EXECUTABLE_NAME} PROPERTIES
SUFFIX ".out"
LINK_FLAGS "-mthumb -mabi=aapcs -std=gnu++98 -std=c99 -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_NAME}.map"
CXX_STANDARD 11
C_STANDARD 99
)
add_custom_command(TARGET ${EXECUTABLE_NAME}
POST_BUILD
COMMAND ${CMAKE_SIZE_UTIL} ${EXECUTABLE_NAME}.out
COMMAND ${CMAKE_OBJCOPY} -O binary ${EXECUTABLE_NAME}.out "${EXECUTABLE_NAME}.bin"
COMMAND ${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_NAME}.out "${EXECUTABLE_NAME}.hex"
COMMENT "post build steps for ${EXECUTABLE_NAME}")
# Build binary intended to be used by bootloader
set(EXECUTABLE_MCUBOOT_NAME "pinetime-mcuboot-app")
set(EXECUTABLE_MCUBOOT_WITH_BOOTLOADER_NAME "pinetime-mcuboot-app-wth-bootloader")
set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -O0 -g3>
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -O3>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -O0 -g3>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -O3>
$<$<COMPILE_LANGUAGE:ASM>: -MP -MD -std=c99 -x assembler-with-cpp>
)
set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES
SUFFIX ".out"
LINK_FLAGS "-mthumb -mabi=aapcs -std=gnu++98 -std=c99 -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT_MCUBOOT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_MCUBOOT_NAME}.map"
CXX_STANDARD 11
C_STANDARD 99
)
add_custom_command(TARGET ${EXECUTABLE_MCUBOOT_NAME}
POST_BUILD
COMMAND ${CMAKE_SIZE_UTIL} ${EXECUTABLE_MCUBOOT_NAME}.out
COMMAND ${CMAKE_OBJCOPY} -O binary ${EXECUTABLE_MCUBOOT_NAME}.out "${EXECUTABLE_MCUBOOT_NAME}.bin"
COMMAND ${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_MCUBOOT_NAME}.out "${EXECUTABLE_MCUBOOT_NAME}.hex"
COMMENT "post build steps for ${EXECUTABLE_MCUBOOT_NAME}"
)
# Build binary that writes the graphic assets for the bootloader
set(EXECUTABLE_GRAPHICS_NAME "pinetime-graphics")
add_executable(${EXECUTABLE_GRAPHICS_NAME} ${GRAPHICS_SOURCE_FILES})
target_compile_options(${EXECUTABLE_GRAPHICS_NAME} PUBLIC
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -O0 -g3>
$<$<AND:$<COMPILE_LANGUAGE:C>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -O3>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:DEBUG>>: ${COMMON_FLAGS} -O0 -g3>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CONFIG:RELEASE>>: ${COMMON_FLAGS} -O3>
$<$<COMPILE_LANGUAGE:ASM>: -MP -MD -std=c99 -x assembler-with-cpp>
)
set_target_properties(${EXECUTABLE_GRAPHICS_NAME} PROPERTIES
SUFFIX ".out"
LINK_FLAGS "-mthumb -mabi=aapcs -std=gnu++98 -std=c99 -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_GRAPHICS_NAME}.map"
CXX_STANDARD 11
C_STANDARD 99
)
add_custom_command(TARGET ${EXECUTABLE_GRAPHICS_NAME}
POST_BUILD
COMMAND ${CMAKE_SIZE_UTIL} ${EXECUTABLE_GRAPHICS_NAME}.out
COMMAND ${CMAKE_OBJCOPY} -O binary ${EXECUTABLE_GRAPHICS_NAME}.out "${EXECUTABLE_GRAPHICS_NAME}.bin"
COMMAND ${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_GRAPHICS_NAME}.out "${EXECUTABLE_GRAPHICS_NAME}.hex"
COMMENT "post build steps for ${EXECUTABLE_GRAPHICS_NAME}"
)
# FLASH
if(USE_JLINK)
add_custom_target(FLASH_ERASE
COMMAND ${NRFJPROG} --eraseall -f ${NRF_TARGET}
COMMENT "erasing flashing"
)
add_custom_target("FLASH_${EXECUTABLE_NAME}"
DEPENDS ${EXECUTABLE_NAME}
COMMAND ${NRFJPROG} --program ${EXECUTABLE_NAME}.hex -f ${NRF_TARGET} --sectorerase
COMMAND sleep 0.5s
COMMAND ${NRFJPROG} --reset -f ${NRF_TARGET}
COMMENT "flashing ${EXECUTABLE_NAME}.hex"
)
elseif(USE_GDB_CLIENT)
add_custom_target(FLASH_ERASE
COMMAND ${GDB_CLIENT_BIN_PATH} -nx --batch -ex 'target extended-remote ${GDB_CLIENT_TARGET_REMOTE}' -ex 'monitor swdp_scan' -ex 'attach 1' -ex 'mon erase_mass'
COMMENT "erasing flashing"
)
add_custom_target("FLASH_${EXECUTABLE_NAME}"
DEPENDS ${EXECUTABLE_NAME}
COMMAND ${GDB_CLIENT_BIN_PATH} -nx --batch -ex 'target extended-remote ${GDB_CLIENT_TARGET_REMOTE}' -ex 'monitor swdp_scan' -ex 'attach 1' -ex 'load' -ex 'kill' ${EXECUTABLE_NAME}.hex
COMMENT "flashing ${EXECUTABLE_NAME}.hex"
)
elseif(USE_OPENOCD)
add_custom_target(FLASH_ERASE
COMMAND ${OPENOCD_BIN_PATH} -f interface/stlink.cfg -c 'transport select hla_swd' -f target/nrf52.cfg -c init -c halt -c 'nrf5 mass_erase' -c reset -c shutdown
COMMENT "erasing flashing"
)
add_custom_target("FLASH_${EXECUTABLE_NAME}"
DEPENDS ${EXECUTABLE_NAME}
COMMAND ${OPENOCD_BIN_PATH} -c "tcl_port disabled" -c "gdb_port 3333" -c "telnet_port 4444" -f interface/stlink.cfg -c 'transport select hla_swd' -f target/nrf52.cfg -c "program \"${EXECUTABLE_NAME}.hex\"" -c reset -c shutdown
COMMENT "flashing ${EXECUTABLE_NAME}.hex"
)
endif()

View file

@ -42,10 +42,6 @@ bool AlertNotificationClient::OnDiscoveryEvent(uint16_t connectionHandle, const
return false; return false;
} }
void AlertNotificationClient::Init() {
}
int AlertNotificationClient::OnCharacteristicsDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, int AlertNotificationClient::OnCharacteristicsDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,
const ble_gatt_chr *characteristic) { const ble_gatt_chr *characteristic) {
if(error->status != 0 && error->status != BLE_HS_EDONE) { if(error->status != 0 && error->status != BLE_HS_EDONE) {

View file

@ -16,7 +16,6 @@ namespace Pinetime {
public: public:
explicit AlertNotificationClient(Pinetime::System::SystemTask &systemTask, explicit AlertNotificationClient(Pinetime::System::SystemTask &systemTask,
Pinetime::Controllers::NotificationManager &notificationManager); Pinetime::Controllers::NotificationManager &notificationManager);
void Init();
bool OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service); bool OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, const ble_gatt_svc *service);
int OnCharacteristicsDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error, int OnCharacteristicsDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error *error,

View file

@ -12,4 +12,20 @@ void Ble::Disconnect() {
isConnected = false; isConnected = false;
} }
void Ble::StartFirmwareUpdate() {
isFirmwareUpdating = true;
}
void Ble::StopFirmwareUpdate() {
isFirmwareUpdating = false;
}
void Ble::FirmwareUpdateTotalBytes(uint32_t totalBytes) {
firmwareUpdateTotalBytes = totalBytes;
}
void Ble::FirmwareUpdateCurrentBytes(uint32_t currentBytes) {
firmwareUpdateCurrentBytes = currentBytes;
}

View file

@ -7,13 +7,29 @@ namespace Pinetime {
namespace Controllers { namespace Controllers {
class Ble { class Ble {
public: public:
enum class FirmwareUpdateStates {Idle, Running, Validated, Error};
Ble() = default; Ble() = default;
bool IsConnected() const {return isConnected;} bool IsConnected() const {return isConnected;}
void Connect(); void Connect();
void Disconnect(); void Disconnect();
void StartFirmwareUpdate();
void StopFirmwareUpdate();
void FirmwareUpdateTotalBytes(uint32_t totalBytes);
void FirmwareUpdateCurrentBytes(uint32_t currentBytes);
void State(FirmwareUpdateStates state) { firmwareUpdateState = state; }
bool IsFirmwareUpdating() const { return isFirmwareUpdating; }
uint32_t FirmwareUpdateTotalBytes() const { return firmwareUpdateTotalBytes; }
uint32_t FirmwareUpdateCurrentBytes() const { return firmwareUpdateCurrentBytes; }
FirmwareUpdateStates State() const { return firmwareUpdateState; }
private: private:
bool isConnected = false; bool isConnected = false;
bool isFirmwareUpdating = false;
uint32_t firmwareUpdateTotalBytes = 0;
uint32_t firmwareUpdateCurrentBytes = 0;
FirmwareUpdateStates firmwareUpdateState = FirmwareUpdateStates::Idle;
}; };
} }

View file

@ -1,101 +1,436 @@
#include "DeviceInformationService.h" #include <Components/Ble/BleController.h>
#include <SystemTask/SystemTask.h>
#include <cstring>
#include "DfuService.h"
using namespace Pinetime::Controllers; using namespace Pinetime::Controllers;
constexpr ble_uuid16_t DeviceInformationService::manufacturerNameUuid; constexpr ble_uuid128_t DfuService::serviceUuid;
constexpr ble_uuid16_t DeviceInformationService::modelNumberUuid; constexpr ble_uuid128_t DfuService::controlPointCharacteristicUuid;
constexpr ble_uuid16_t DeviceInformationService::serialNumberUuid; constexpr ble_uuid128_t DfuService::revisionCharacteristicUuid;
constexpr ble_uuid16_t DeviceInformationService::fwRevisionUuid; constexpr ble_uuid128_t DfuService::packetCharacteristicUuid;
constexpr ble_uuid16_t DeviceInformationService::deviceInfoUuid;
constexpr ble_uuid16_t DeviceInformationService::hwRevisionUuid;
int DeviceInformationCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { int DfuServiceCallback(uint16_t conn_handle, uint16_t attr_handle,
auto deviceInformationService = static_cast<DeviceInformationService*>(arg); struct ble_gatt_access_ctxt *ctxt, void *arg) {
return deviceInformationService->OnDeviceInfoRequested(conn_handle, attr_handle, ctxt); auto dfuService = static_cast<DfuService *>(arg);
return dfuService->OnServiceData(conn_handle, attr_handle, ctxt);
} }
void DeviceInformationService::Init() { void NotificationTimerCallback(TimerHandle_t xTimer) {
ble_gatts_count_cfg(serviceDefinition); auto notificationManager = static_cast<DfuService::NotificationManager *>(pvTimerGetTimerID(xTimer));
ble_gatts_add_svcs(serviceDefinition); notificationManager->OnNotificationTimer();
} }
void TimeoutTimerCallback(TimerHandle_t xTimer) {
int DeviceInformationService::OnDeviceInfoRequested(uint16_t conn_handle, uint16_t attr_handle, auto dfuService = static_cast<DfuService *>(pvTimerGetTimerID(xTimer));
struct ble_gatt_access_ctxt *ctxt) { dfuService->OnTimeout();
const char *str;
switch (ble_uuid_u16(ctxt->chr->uuid)) {
case manufacturerNameId:
str = manufacturerName;
break;
case modelNumberId:
str = modelNumber;
break;
case serialNumberId:
str = serialNumber;
break;
case fwRevisionId:
str = fwRevision;
break;
case hwRevisionId:
str = hwRevision;
break;
default:
return BLE_ATT_ERR_UNLIKELY;
}
int res = os_mbuf_append(ctxt->om, str, strlen(str));
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
} }
DeviceInformationService::DeviceInformationService() : DfuService::DfuService(Pinetime::System::SystemTask &systemTask, Pinetime::Controllers::Ble &bleController,
Pinetime::Drivers::SpiNorFlash &spiNorFlash) :
systemTask{systemTask},
bleController{bleController},
dfuImage{spiNorFlash},
characteristicDefinition{ characteristicDefinition{
{ {
.uuid = (ble_uuid_t *) &manufacturerNameUuid, .uuid = (ble_uuid_t *) &packetCharacteristicUuid,
.access_cb = DeviceInformationCallback, .access_cb = DfuServiceCallback,
.arg = this, .arg = this,
.flags = BLE_GATT_CHR_F_READ, .flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
.val_handle = nullptr,
}, },
{ {
.uuid = (ble_uuid_t *) &modelNumberUuid, .uuid = (ble_uuid_t *) &controlPointCharacteristicUuid,
.access_cb = DeviceInformationCallback, .access_cb = DfuServiceCallback,
.arg = this, .arg = this,
.flags = BLE_GATT_CHR_F_READ, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY,
.val_handle = nullptr,
}, },
{ {
.uuid = (ble_uuid_t *) &serialNumberUuid, .uuid = (ble_uuid_t *) &revisionCharacteristicUuid,
.access_cb = DeviceInformationCallback, .access_cb = DfuServiceCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_READ,
},
{
.uuid = (ble_uuid_t *) &fwRevisionUuid,
.access_cb = DeviceInformationCallback,
.arg = this,
.flags = BLE_GATT_CHR_F_READ,
},
{
.uuid = (ble_uuid_t *) &hwRevisionUuid,
.access_cb = DeviceInformationCallback,
.arg = this, .arg = this,
.flags = BLE_GATT_CHR_F_READ, .flags = BLE_GATT_CHR_F_READ,
.val_handle = &revision,
}, },
{ {
0 0
} }
}, },
serviceDefinition{ serviceDefinition{
{ {
/* Device Information Service */ /* Device Information Service */
.type = BLE_GATT_SVC_TYPE_PRIMARY, .type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = (ble_uuid_t *) &deviceInfoUuid, .uuid = (ble_uuid_t *) &serviceUuid,
.characteristics = characteristicDefinition .characteristics = characteristicDefinition
}, },
{ {
0 0
}, },
} } {
{ timeoutTimer = xTimerCreate ("notificationTimer", 10000, pdFALSE, this, TimeoutTimerCallback);
} }
void DfuService::Init() {
ble_gatts_count_cfg(serviceDefinition);
ble_gatts_add_svcs(serviceDefinition);
}
int DfuService::OnServiceData(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context) {
if(bleController.IsFirmwareUpdating()){
xTimerStart(timeoutTimer, 0);
}
ble_gatts_find_chr((ble_uuid_t *) &serviceUuid, (ble_uuid_t *) &packetCharacteristicUuid, nullptr,
&packetCharacteristicHandle);
ble_gatts_find_chr((ble_uuid_t *) &serviceUuid, (ble_uuid_t *) &controlPointCharacteristicUuid, nullptr,
&controlPointCharacteristicHandle);
ble_gatts_find_chr((ble_uuid_t *) &serviceUuid, (ble_uuid_t *) &revisionCharacteristicUuid, nullptr,
&revisionCharacteristicHandle);
if (attributeHandle == packetCharacteristicHandle) {
if (context->op == BLE_GATT_ACCESS_OP_WRITE_CHR)
return WritePacketHandler(connectionHandle, context->om);
else return 0;
} else if (attributeHandle == controlPointCharacteristicHandle) {
if (context->op == BLE_GATT_ACCESS_OP_WRITE_CHR)
return ControlPointHandler(connectionHandle, context->om);
else return 0;
} else if (attributeHandle == revisionCharacteristicHandle) {
if (context->op == BLE_GATT_ACCESS_OP_READ_CHR)
return SendDfuRevision(context->om);
else return 0;
} else {
NRF_LOG_INFO("[DFU] Unknown Characteristic : %d", attributeHandle);
return 0;
}
}
int DfuService::SendDfuRevision(os_mbuf *om) const {
int res = os_mbuf_append(om, &revision, sizeof(revision));
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
int DfuService::WritePacketHandler(uint16_t connectionHandle, os_mbuf *om) {
switch (state) {
case States::Start: {
softdeviceSize = om->om_data[0] + (om->om_data[1] << 8) + (om->om_data[2] << 16) + (om->om_data[3] << 24);
bootloaderSize = om->om_data[4] + (om->om_data[5] << 8) + (om->om_data[6] << 16) + (om->om_data[7] << 24);
applicationSize = om->om_data[8] + (om->om_data[9] << 8) + (om->om_data[10] << 16) + (om->om_data[11] << 24);
bleController.FirmwareUpdateTotalBytes(applicationSize);
NRF_LOG_INFO("[DFU] -> Start data received : SD size : %d, BT size : %d, app size : %d", softdeviceSize,
bootloaderSize, applicationSize);
dfuImage.Erase();
uint8_t data[]{16, 1, 1};
notificationManager.Send(connectionHandle, controlPointCharacteristicHandle, data, 3);
state = States::Init;
}
return 0;
case States::Init: {
uint16_t deviceType = om->om_data[0] + (om->om_data[1] << 8);
uint16_t deviceRevision = om->om_data[2] + (om->om_data[3] << 8);
uint32_t applicationVersion =
om->om_data[4] + (om->om_data[5] << 8) + (om->om_data[6] << 16) + (om->om_data[7] << 24);
uint16_t softdeviceArrayLength = om->om_data[8] + (om->om_data[9] << 8);
uint16_t sd[softdeviceArrayLength];
for (int i = 0; i < softdeviceArrayLength; i++) {
sd[i] = om->om_data[10 + (i * 2)] + (om->om_data[10 + (i * 2) + 1] << 8);
}
expectedCrc =
om->om_data[10 + (softdeviceArrayLength * 2)] + (om->om_data[10 + (softdeviceArrayLength * 2) + 1] << 8);
NRF_LOG_INFO(
"[DFU] -> Init data received : deviceType = %d, deviceRevision = %d, applicationVersion = %d, nb SD = %d, First SD = %d, CRC = %u",
deviceType, deviceRevision, applicationVersion, softdeviceArrayLength, sd[0], expectedCrc);
return 0;
}
case States::Data: {
nbPacketReceived++;
dfuImage.Append(om->om_data, om->om_len);
bytesReceived += om->om_len;
bleController.FirmwareUpdateCurrentBytes(bytesReceived);
if ((nbPacketReceived % nbPacketsToNotify) == 0 && bytesReceived != applicationSize) {
uint8_t data[5]{static_cast<uint8_t>(Opcodes::PacketReceiptNotification),
(uint8_t) (bytesReceived & 0x000000FFu), (uint8_t) (bytesReceived >> 8u),
(uint8_t) (bytesReceived >> 16u), (uint8_t) (bytesReceived >> 24u)};
NRF_LOG_INFO("[DFU] -> Send packet notification: %d bytes received", bytesReceived);
notificationManager.Send(connectionHandle, controlPointCharacteristicHandle, data, 5);
}
if (dfuImage.IsComplete()) {
uint8_t data[3]{static_cast<uint8_t>(Opcodes::Response),
static_cast<uint8_t>(Opcodes::ReceiveFirmwareImage),
static_cast<uint8_t>(ErrorCodes::NoError)};
NRF_LOG_INFO("[DFU] -> Send packet notification : all bytes received!");
notificationManager.Send(connectionHandle, controlPointCharacteristicHandle, data, 3);
state = States::Validate;
}
}
return 0;
default:
// Invalid state
return 0;
}
return 0;
}
int DfuService::ControlPointHandler(uint16_t connectionHandle, os_mbuf *om) {
auto opcode = static_cast<Opcodes>(om->om_data[0]);
NRF_LOG_INFO("[DFU] -> ControlPointHandler");
switch (opcode) {
case Opcodes::StartDFU: {
if (state != States::Idle && state != States::Start) {
NRF_LOG_INFO("[DFU] -> Start DFU requested, but we are not in Idle state");
return 0;
}
if (state == States::Start) {
NRF_LOG_INFO("[DFU] -> Start DFU requested, but we are already in Start state");
return 0;
}
auto imageType = static_cast<ImageTypes>(om->om_data[1]);
if (imageType == ImageTypes::Application) {
NRF_LOG_INFO("[DFU] -> Start DFU, mode = Application");
state = States::Start;
bleController.StartFirmwareUpdate();
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Running);
bleController.FirmwareUpdateTotalBytes(0xffffffffu);
bleController.FirmwareUpdateCurrentBytes(0);
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleFirmwareUpdateStarted);
return 0;
} else {
NRF_LOG_INFO("[DFU] -> Start DFU, mode %d not supported!", imageType);
return 0;
}
}
break;
case Opcodes::InitDFUParameters: {
if (state != States::Init) {
NRF_LOG_INFO("[DFU] -> Init DFU requested, but we are not in Init state");
return 0;
}
bool isInitComplete = (om->om_data[1] != 0);
NRF_LOG_INFO("[DFU] -> Init DFU parameters %s", isInitComplete ? " complete" : " not complete");
if (isInitComplete) {
uint8_t data[3] {
static_cast<uint8_t>(Opcodes::Response),
static_cast<uint8_t>(Opcodes::InitDFUParameters),
(isInitComplete ? uint8_t{1} : uint8_t{0})
};
notificationManager.AsyncSend(connectionHandle, controlPointCharacteristicHandle, data, 3);
return 0;
}
}
return 0;
case Opcodes::PacketReceiptNotificationRequest:
nbPacketsToNotify = om->om_data[1];
NRF_LOG_INFO("[DFU] -> Receive Packet Notification Request, nb packet = %d", nbPacketsToNotify);
return 0;
case Opcodes::ReceiveFirmwareImage:
if (state != States::Init) {
NRF_LOG_INFO("[DFU] -> Receive firmware image requested, but we are not in Start Init");
return 0;
}
// TODO the chunk size is dependant of the implementation of the host application...
dfuImage.Init(20, applicationSize, expectedCrc);
NRF_LOG_INFO("[DFU] -> Starting receive firmware");
state = States::Data;
return 0;
case Opcodes::ValidateFirmware: {
if (state != States::Validate) {
NRF_LOG_INFO("[DFU] -> Validate firmware image requested, but we are not in Data state %d", state);
return 0;
}
NRF_LOG_INFO("[DFU] -> Validate firmware image requested -- %d", connectionHandle);
if(dfuImage.Validate()){
state = States::Validated;
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated);
NRF_LOG_INFO("Image OK");
uint8_t data[3] {
static_cast<uint8_t>(Opcodes::Response),
static_cast<uint8_t>(Opcodes::ValidateFirmware),
static_cast<uint8_t>(ErrorCodes::NoError)
};
notificationManager.AsyncSend(connectionHandle, controlPointCharacteristicHandle, data, 3);
} else {
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Error);
NRF_LOG_INFO("Image Error : bad CRC");
uint8_t data[3] {
static_cast<uint8_t>(Opcodes::Response),
static_cast<uint8_t>(Opcodes::ValidateFirmware),
static_cast<uint8_t>(ErrorCodes::CrcError)
};
notificationManager.AsyncSend(connectionHandle, controlPointCharacteristicHandle, data, 3);
}
return 0;
}
case Opcodes::ActivateImageAndReset:
if (state != States::Validated) {
NRF_LOG_INFO("[DFU] -> Activate image and reset requested, but we are not in Validated state");
return 0;
}
NRF_LOG_INFO("[DFU] -> Activate image and reset!");
bleController.StopFirmwareUpdate();
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleFirmwareUpdateFinished);
Reset();
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated);
return 0;
default:
return 0;
}
}
void DfuService::OnTimeout() {
Reset();
}
void DfuService::Reset() {
state = States::Idle;
nbPacketsToNotify = 0;
nbPacketReceived = 0;
bytesReceived = 0;
softdeviceSize = 0;
bootloaderSize = 0;
applicationSize = 0;
expectedCrc = 0;
notificationManager.Reset();
bleController.State(Pinetime::Controllers::Ble::FirmwareUpdateStates::Error);
bleController.StopFirmwareUpdate();
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleFirmwareUpdateFinished);
}
DfuService::NotificationManager::NotificationManager() {
timer = xTimerCreate ("notificationTimer", 1000, pdFALSE, this, NotificationTimerCallback);
}
bool DfuService::NotificationManager::AsyncSend(uint16_t connection, uint16_t charactHandle, uint8_t *data, size_t s) {
if(size != 0 || s > 10)
return false;
connectionHandle = connection;
characteristicHandle = charactHandle;
size = s;
std::memcpy(buffer, data, size);
xTimerStart(timer, 0);
return true;
}
void DfuService::NotificationManager::OnNotificationTimer() {
if(size > 0) {
Send(connectionHandle, characteristicHandle, buffer, size);
size = 0;
}
}
void DfuService::NotificationManager::Send(uint16_t connection, uint16_t charactHandle, const uint8_t *data, const size_t s) {
auto *om = ble_hs_mbuf_from_flat(data, s);
auto ret = ble_gattc_notify_custom(connection, charactHandle, om);
ASSERT(ret == 0);
}
void DfuService::NotificationManager::Reset() {
connectionHandle = 0;
characteristicHandle = 0;
size = 0;
xTimerStop(timer, 0);
}
void DfuService::DfuImage::Init(size_t chunkSize, size_t totalSize, uint16_t expectedCrc) {
if(chunkSize != 20) return;
this->chunkSize = chunkSize;
this->totalSize = totalSize;
this->expectedCrc = expectedCrc;
this->ready = true;
}
void DfuService::DfuImage::Append(uint8_t *data, size_t size) {
if(!ready) return;
ASSERT(size <= 20);
std::memcpy(tempBuffer + bufferWriteIndex, data, size);
bufferWriteIndex += size;
if(bufferWriteIndex == bufferSize) {
spiNorFlash.Write(writeOffset + totalWriteIndex, tempBuffer, bufferWriteIndex);
totalWriteIndex += bufferWriteIndex;
bufferWriteIndex = 0;
}
if(bufferWriteIndex > 0 && totalWriteIndex + bufferWriteIndex == totalSize) {
spiNorFlash.Write(writeOffset + totalWriteIndex, tempBuffer, bufferWriteIndex);
totalWriteIndex += bufferWriteIndex;
if (totalSize < maxSize)
WriteMagicNumber();
}
}
void DfuService::DfuImage::WriteMagicNumber() {
uint32_t magic[4] = { // TODO When this variable is a static constexpr, the values written to the memory are not correct. Why?
0xf395c277,
0x7fefd260,
0x0f505235,
0x8079b62c,
};
uint32_t offset = writeOffset + (maxSize - (4 * sizeof(uint32_t)));
spiNorFlash.Write(offset, reinterpret_cast<const uint8_t *>(magic), 4 * sizeof(uint32_t));
}
void DfuService::DfuImage::Erase() {
for (int erased = 0; erased < maxSize; erased += 0x1000) {
spiNorFlash.SectorErase(writeOffset + erased);
}
}
bool DfuService::DfuImage::Validate() {
uint32_t chunkSize = 200;
int currentOffset = 0;
uint16_t crc = 0;
bool first = true;
while (currentOffset < totalSize) {
uint32_t readSize = (totalSize - currentOffset) > chunkSize ? chunkSize : (totalSize - currentOffset);
spiNorFlash.Read(writeOffset + currentOffset, tempBuffer, readSize);
if (first) {
crc = ComputeCrc(tempBuffer, readSize, NULL);
first = false;
} else
crc = ComputeCrc(tempBuffer, readSize, &crc);
currentOffset += readSize;
}
return (crc == expectedCrc);
}
uint16_t DfuService::DfuImage::ComputeCrc(uint8_t const *p_data, uint32_t size, uint16_t const *p_crc) {
uint16_t crc = (p_crc == NULL) ? 0xFFFF : *p_crc;
for (uint32_t i = 0; i < size; i++) {
crc = (uint8_t) (crc >> 8) | (crc << 8);
crc ^= p_data[i];
crc ^= (uint8_t) (crc & 0xFF) >> 4;
crc ^= (crc << 8) << 4;
crc ^= ((crc & 0xFF) << 4) << 1;
}
return crc;
}
bool DfuService::DfuImage::IsComplete() {
if(!ready) return false;
return totalWriteIndex == totalSize;
}

View file

@ -1,67 +1,161 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <array> #include <array>
#include <host/ble_gap.h> #include <host/ble_gap.h>
namespace Pinetime { namespace Pinetime {
namespace System {
class SystemTask;
}
namespace Drivers {
class SpiNorFlash;
}
namespace Controllers { namespace Controllers {
class DeviceInformationService { class Ble;
public:
DeviceInformationService();
void Init();
int OnDeviceInfoRequested(uint16_t conn_handle, uint16_t attr_handle, class DfuService {
struct ble_gatt_access_ctxt *ctxt); public:
DfuService(Pinetime::System::SystemTask &systemTask, Pinetime::Controllers::Ble &bleController,
Pinetime::Drivers::SpiNorFlash &spiNorFlash);
void Init();
int OnServiceData(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context);
void OnTimeout();
void Reset();
class NotificationManager {
public:
NotificationManager();
bool AsyncSend(uint16_t connection, uint16_t charactHandle, uint8_t *data, size_t size);
void Send(uint16_t connection, uint16_t characteristicHandle, const uint8_t *data, const size_t s);
private:
TimerHandle_t timer;
uint16_t connectionHandle = 0;
uint16_t characteristicHandle = 0;
size_t size = 0;
uint8_t buffer[10];
public:
void OnNotificationTimer();
void Reset();
};
class DfuImage {
public:
DfuImage(Pinetime::Drivers::SpiNorFlash& spiNorFlash) : spiNorFlash{spiNorFlash} {}
void Init(size_t chunkSize, size_t totalSize, uint16_t expectedCrc);
void Erase();
void Append(uint8_t* data, size_t size);
bool Validate();
bool IsComplete();
private: private:
static constexpr uint16_t deviceInfoId {0x180a}; Pinetime::Drivers::SpiNorFlash& spiNorFlash;
static constexpr uint16_t manufacturerNameId {0x2a29}; static constexpr size_t bufferSize = 200;
static constexpr uint16_t modelNumberId {0x2a24}; bool ready = false;
static constexpr uint16_t serialNumberId {0x2a25}; size_t chunkSize = 0;
static constexpr uint16_t fwRevisionId {0x2a26}; size_t totalSize = 0;
static constexpr uint16_t hwRevisionId {0x2a27}; size_t maxSize = 475136;
size_t bufferWriteIndex = 0;
size_t totalWriteIndex = 0;
static constexpr size_t writeOffset = 0x40000;
uint8_t tempBuffer[bufferSize];
uint16_t expectedCrc = 0;
static constexpr char* manufacturerName = "Codingfield"; void WriteMagicNumber();
static constexpr char* modelNumber = "1"; uint16_t ComputeCrc(uint8_t const *p_data, uint32_t size, uint16_t const *p_crc);
static constexpr char* serialNumber = "9.8.7.6.5.4";
static constexpr char* fwRevision = "0.5.0";
static constexpr char* hwRevision = "1.0.0";
static constexpr ble_uuid16_t deviceInfoUuid {
.u { .type = BLE_UUID_TYPE_16 },
.value = deviceInfoId
}; };
static constexpr ble_uuid16_t manufacturerNameUuid { private:
.u { .type = BLE_UUID_TYPE_16 }, Pinetime::System::SystemTask &systemTask;
.value = manufacturerNameId Pinetime::Controllers::Ble &bleController;
DfuImage dfuImage;
NotificationManager notificationManager;
static constexpr uint16_t dfuServiceId{0x1530};
static constexpr uint16_t packetCharacteristicId{0x1532};
static constexpr uint16_t controlPointCharacteristicId{0x1531};
static constexpr uint16_t revisionCharacteristicId{0x1534};
uint16_t revision{0x0008};
static constexpr ble_uuid128_t serviceUuid{
.u {.type = BLE_UUID_TYPE_128},
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15,
0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}
}; };
static constexpr ble_uuid16_t modelNumberUuid { static constexpr ble_uuid128_t packetCharacteristicUuid{
.u { .type = BLE_UUID_TYPE_16 }, .u {.type = BLE_UUID_TYPE_128},
.value = modelNumberId .value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15,
0xDE, 0xEF, 0x12, 0x12, 0x32, 0x15, 0x00, 0x00}
}; };
static constexpr ble_uuid16_t serialNumberUuid { static constexpr ble_uuid128_t controlPointCharacteristicUuid{
.u { .type = BLE_UUID_TYPE_16 }, .u {.type = BLE_UUID_TYPE_128},
.value = serialNumberId .value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15,
0xDE, 0xEF, 0x12, 0x12, 0x31, 0x15, 0x00, 0x00}
}; };
static constexpr ble_uuid16_t fwRevisionUuid { static constexpr ble_uuid128_t revisionCharacteristicUuid{
.u { .type = BLE_UUID_TYPE_16 }, .u {.type = BLE_UUID_TYPE_128},
.value = fwRevisionId .value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15,
0xDE, 0xEF, 0x12, 0x12, 0x34, 0x15, 0x00, 0x00}
}; };
static constexpr ble_uuid16_t hwRevisionUuid { struct ble_gatt_chr_def characteristicDefinition[4];
.u {.type = BLE_UUID_TYPE_16},
.value = hwRevisionId
};
struct ble_gatt_chr_def characteristicDefinition[6];
struct ble_gatt_svc_def serviceDefinition[2]; struct ble_gatt_svc_def serviceDefinition[2];
uint16_t packetCharacteristicHandle;
uint16_t controlPointCharacteristicHandle;
uint16_t revisionCharacteristicHandle;
enum class States : uint8_t {
Idle, Init, Start, Data, Validate, Validated
};
States state = States::Idle;
enum class ImageTypes : uint8_t {
NoImage = 0x00,
SoftDevice = 0x01,
Bootloader = 0x02,
SoftDeviceAndBootloader = 0x03,
Application = 0x04
};
enum class Opcodes : uint8_t {
StartDFU = 0x01,
InitDFUParameters = 0x02,
ReceiveFirmwareImage = 0x03,
ValidateFirmware = 0x04,
ActivateImageAndReset = 0x05,
PacketReceiptNotificationRequest = 0x08,
Response = 0x10,
PacketReceiptNotification = 0x11
};
enum class ErrorCodes {
NoError = 0x01,
InvalidState = 0x02,
NotSupported = 0x03,
DataSizeExceedsLimits = 0x04,
CrcError = 0x05,
OperationFailed = 0x06
};
uint8_t nbPacketsToNotify = 0;
uint32_t nbPacketReceived = 0;
uint32_t bytesReceived = 0;
uint32_t softdeviceSize = 0;
uint32_t bootloaderSize = 0;
uint32_t applicationSize = 0;
uint16_t expectedCrc = 0;
int SendDfuRevision(os_mbuf *om) const;
int WritePacketHandler(uint16_t connectionHandle, os_mbuf *om);
int ControlPointHandler(uint16_t connectionHandle, os_mbuf *om);
TimerHandle_t timeoutTimer;
}; };
} }
} }

View file

@ -24,14 +24,17 @@ using namespace Pinetime::Controllers;
NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask, NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
Pinetime::Controllers::Ble& bleController, Pinetime::Controllers::Ble& bleController,
DateTime& dateTimeController, DateTime& dateTimeController,
Pinetime::Controllers::NotificationManager& notificationManager) : Pinetime::Controllers::NotificationManager& notificationManager,
Pinetime::Drivers::SpiNorFlash& spiNorFlash) :
systemTask{systemTask}, systemTask{systemTask},
bleController{bleController}, bleController{bleController},
dateTimeController{dateTimeController}, dateTimeController{dateTimeController},
notificationManager{notificationManager}, notificationManager{notificationManager},
spiNorFlash{spiNorFlash},
dfuService{systemTask, bleController, spiNorFlash},
currentTimeClient{dateTimeController}, currentTimeClient{dateTimeController},
alertNotificationClient{systemTask, notificationManager},
anService{systemTask, notificationManager}, anService{systemTask, notificationManager},
alertNotificationClient{systemTask, notificationManager},
currentTimeService{dateTimeController} { currentTimeService{dateTimeController} {
} }
@ -80,6 +83,7 @@ void NimbleController::Init() {
anService.Init(); anService.Init();
dfuService.Init();
int res; int res;
res = ble_hs_util_ensure_addr(0); res = ble_hs_util_ensure_addr(0);
ASSERT(res == 0); ASSERT(res == 0);
@ -93,6 +97,8 @@ void NimbleController::Init() {
} }
void NimbleController::StartAdvertising() { void NimbleController::StartAdvertising() {
if(ble_gap_adv_active()) return;
ble_svc_gap_device_name_set("Pinetime-JF"); ble_svc_gap_device_name_set("Pinetime-JF");
/* set adv parameters */ /* set adv parameters */
@ -116,8 +122,9 @@ void NimbleController::StartAdvertising() {
// fields.uuids128 = BLE_UUID128(BLE_UUID128_DECLARE( // fields.uuids128 = BLE_UUID128(BLE_UUID128_DECLARE(
// 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, // 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
// 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff)); // 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff));
fields.num_uuids128 = 0; fields.uuids128 = &dfuServiceUuid;
fields.uuids128_is_complete = 0;; fields.num_uuids128 = 1;
fields.uuids128_is_complete = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
rsp_fields.name = (uint8_t *)"Pinetime-JF"; rsp_fields.name = (uint8_t *)"Pinetime-JF";
@ -126,16 +133,14 @@ void NimbleController::StartAdvertising() {
int res; int res;
res = ble_gap_adv_set_fields(&fields); res = ble_gap_adv_set_fields(&fields);
//ASSERT(res == 0); // ASSERT(res == 0); // TODO this one sometimes fails with error 22 (notsync)
res = ble_gap_adv_rsp_set_fields(&rsp_fields); res = ble_gap_adv_rsp_set_fields(&rsp_fields);
//ASSERT(res == 0); // ASSERT(res == 0);
res = ble_gap_adv_start(addrType, NULL, 10000, res = ble_gap_adv_start(addrType, NULL, 180000,
&adv_params, GAPEventCallback, this); &adv_params, GAPEventCallback, this);
//ASSERT(res == 0); // ASSERT(res == 0);// TODO I've disabled these ASSERT as they sometime asserts and reset the mcu.
// TODO I've disabled these ASSERT as they sometime asserts and reset the mcu.
// For now, the advertising is restarted as soon as it ends. There may be a race condition // For now, the advertising is restarted as soon as it ends. There may be a race condition
// that prevent the advertising from restarting reliably. // that prevent the advertising from restarting reliably.
// I remove the assert to prevent this uncesseray crash, but in the long term, the management of // I remove the assert to prevent this uncesseray crash, but in the long term, the management of
@ -157,7 +162,6 @@ int NimbleController::OnGAPEvent(ble_gap_event *event) {
case BLE_GAP_EVENT_ADV_COMPLETE: case BLE_GAP_EVENT_ADV_COMPLETE:
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_ADV_COMPLETE"); NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_ADV_COMPLETE");
NRF_LOG_INFO("advertise complete; reason=%dn status=%d", event->adv_complete.reason, event->connect.status); NRF_LOG_INFO("advertise complete; reason=%dn status=%d", event->adv_complete.reason, event->connect.status);
StartAdvertising();
break; break;
case BLE_GAP_EVENT_CONNECT: { case BLE_GAP_EVENT_CONNECT: {
NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_CONNECT"); NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_CONNECT");
@ -172,8 +176,9 @@ int NimbleController::OnGAPEvent(ble_gap_event *event) {
bleController.Disconnect(); bleController.Disconnect();
} else { } else {
bleController.Connect(); bleController.Connect();
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::BleConnected);
connectionHandle = event->connect.conn_handle; connectionHandle = event->connect.conn_handle;
ble_gattc_disc_all_svcs(connectionHandle, OnAllSvrDisco, this); // Service discovery is deffered via systemtask
} }
} }
break; break;
@ -182,6 +187,7 @@ int NimbleController::OnGAPEvent(ble_gap_event *event) {
NRF_LOG_INFO("disconnect; reason=%d", event->disconnect.reason); NRF_LOG_INFO("disconnect; reason=%d", event->disconnect.reason);
/* Connection terminated; resume advertising. */ /* Connection terminated; resume advertising. */
connectionHandle = BLE_HS_CONN_HANDLE_NONE;
bleController.Disconnect(); bleController.Disconnect();
StartAdvertising(); StartAdvertising();
break; break;
@ -247,7 +253,7 @@ int NimbleController::OnGAPEvent(ble_gap_event *event) {
/* Attribute data is contained in event->notify_rx.attr_data. */ /* Attribute data is contained in event->notify_rx.attr_data. */
default: default:
NRF_LOG_INFO("Advertising event : %d", event->type); // NRF_LOG_INFO("Advertising event : %d", event->type);
break; break;
} }
return 0; return 0;
@ -264,7 +270,6 @@ int NimbleController::OnDiscoveryEvent(uint16_t i, const ble_gatt_error *error,
ble_gattc_disc_all_chrs(connectionHandle, alertNotificationClient.StartHandle(), alertNotificationClient.EndHandle(), ble_gattc_disc_all_chrs(connectionHandle, alertNotificationClient.StartHandle(), alertNotificationClient.EndHandle(),
AlertNotificationCharacteristicDiscoveredCallback, this); AlertNotificationCharacteristicDiscoveredCallback, this);
} }
return 0;
} }
alertNotificationClient.OnDiscoveryEvent(i, error, service); alertNotificationClient.OnDiscoveryEvent(i, error, service);
@ -311,6 +316,10 @@ int NimbleController::OnANSDescriptorDiscoveryEventCallback(uint16_t connectionH
return alertNotificationClient.OnDescriptorDiscoveryEventCallback(connectionHandle, error, characteristicValueHandle, descriptor); return alertNotificationClient.OnDescriptorDiscoveryEventCallback(connectionHandle, error, characteristicValueHandle, descriptor);
} }
void NimbleController::StartDiscovery() {
ble_gattc_disc_all_svcs(connectionHandle, OnAllSvrDisco, this);
}

View file

@ -5,16 +5,22 @@
#include "AlertNotificationClient.h" #include "AlertNotificationClient.h"
#include "DeviceInformationService.h" #include "DeviceInformationService.h"
#include "CurrentTimeClient.h" #include "CurrentTimeClient.h"
#include "DfuService.h"
#include "CurrentTimeService.h" #include "CurrentTimeService.h"
#include <host/ble_gap.h> #include <host/ble_gap.h>
namespace Pinetime { namespace Pinetime {
namespace Drivers {
class SpiNorFlash;
}
namespace Controllers { namespace Controllers {
class DateTime; class DateTime;
class NimbleController { class NimbleController {
public: public:
NimbleController(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::Ble& bleController, DateTime& dateTimeController, Pinetime::Controllers::NotificationManager& notificationManager); NimbleController(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::Ble& bleController,
DateTime& dateTimeController, Pinetime::Controllers::NotificationManager& notificationManager,
Pinetime::Drivers::SpiNorFlash& spiNorFlash);
void Init(); void Init();
void StartAdvertising(); void StartAdvertising();
int OnGAPEvent(ble_gap_event *event); int OnGAPEvent(ble_gap_event *event);
@ -27,12 +33,16 @@ namespace Pinetime {
int OnCurrentTimeReadResult(uint16_t connectionHandle, const ble_gatt_error *error, ble_gatt_attr *attribute); int OnCurrentTimeReadResult(uint16_t connectionHandle, const ble_gatt_error *error, ble_gatt_attr *attribute);
int OnANSDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error, int OnANSDescriptorDiscoveryEventCallback(uint16_t connectionHandle, const ble_gatt_error *error,
uint16_t characteristicValueHandle, const ble_gatt_dsc *descriptor); uint16_t characteristicValueHandle, const ble_gatt_dsc *descriptor);
void StartDiscovery();
private: private:
static constexpr char* deviceName = "Pinetime-JF"; static constexpr char* deviceName = "Pinetime-JF";
Pinetime::System::SystemTask& systemTask; Pinetime::System::SystemTask& systemTask;
Pinetime::Controllers::Ble& bleController; Pinetime::Controllers::Ble& bleController;
DateTime& dateTimeController; DateTime& dateTimeController;
Pinetime::Controllers::NotificationManager& notificationManager; Pinetime::Controllers::NotificationManager& notificationManager;
Pinetime::Drivers::SpiNorFlash& spiNorFlash;
Pinetime::Controllers::DfuService dfuService;
DeviceInformationService deviceInformationService; DeviceInformationService deviceInformationService;
CurrentTimeClient currentTimeClient; CurrentTimeClient currentTimeClient;
@ -42,6 +52,12 @@ namespace Pinetime {
uint8_t addrType; uint8_t addrType;
uint16_t connectionHandle; uint16_t connectionHandle;
ble_uuid128_t dfuServiceUuid {
.u { .type = BLE_UUID_TYPE_128},
.value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15,
0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}
};
}; };
} }
} }

View file

@ -43,6 +43,20 @@ void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t col
WaitTransfertFinished(); WaitTransfertFinished();
} }
void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b) {
state.remainingIterations = h;
state.currentIteration = 0;
state.busy = true;
state.action = Action::FillRectangle;
state.color = 0x00;
state.taskToNotify = xTaskGetCurrentTaskHandle();
lcd.BeginDrawBuffer(x, y, w, h);
lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(b), width * 2);
WaitTransfertFinished();
}
void Gfx::DrawString(uint8_t x, uint8_t y, uint16_t color, const char *text, const FONT_INFO *p_font, bool wrap) { void Gfx::DrawString(uint8_t x, uint8_t y, uint16_t color, const char *text, const FONT_INFO *p_font, bool wrap) {
if (y > (height - p_font->height)) { if (y > (height - p_font->height)) {
// Not enough space to write even single char. // Not enough space to write even single char.

View file

@ -19,6 +19,7 @@ namespace Pinetime {
void DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO *p_font, bool wrap); void DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO *p_font, bool wrap);
void DrawChar(const FONT_INFO *font, uint8_t c, uint8_t *x, uint8_t y, uint16_t color); void DrawChar(const FONT_INFO *font, uint8_t c, uint8_t *x, uint8_t y, uint16_t color);
void FillRectangle(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color); void FillRectangle(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color);
void FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b);
void SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines); void SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
void SetScrollStartLine(uint16_t line); void SetScrollStartLine(uint16_t line);
@ -26,6 +27,8 @@ namespace Pinetime {
void Sleep(); void Sleep();
void Wakeup(); void Wakeup();
bool GetNextBuffer(uint8_t **buffer, size_t &size) override; bool GetNextBuffer(uint8_t **buffer, size_t &size) override;
void pixel_draw(uint8_t x, uint8_t y, uint16_t color);
private: private:
static constexpr uint8_t width = 240; static constexpr uint8_t width = 240;
@ -49,7 +52,6 @@ namespace Pinetime {
uint16_t buffer[width]; // 1 line buffer uint16_t buffer[width]; // 1 line buffer
Drivers::St7789& lcd; Drivers::St7789& lcd;
void pixel_draw(uint8_t x, uint8_t y, uint16_t color);
void SetBackgroundColor(uint16_t color); void SetBackgroundColor(uint16_t color);
void WaitTransfertFinished() const; void WaitTransfertFinished() const;
void NotifyEndOfTransfert(TaskHandle_t task); void NotifyEndOfTransfert(TaskHandle_t task);

View file

@ -16,6 +16,7 @@
#include <DisplayApp/Screens/Brightness.h> #include <DisplayApp/Screens/Brightness.h>
#include <DisplayApp/Screens/ScreenList.h> #include <DisplayApp/Screens/ScreenList.h>
#include <Components/Ble/NotificationManager.h> #include <Components/Ble/NotificationManager.h>
#include <DisplayApp/Screens/FirmwareUpdate.h>
#include "../SystemTask/SystemTask.h" #include "../SystemTask/SystemTask.h"
using namespace Pinetime::Applications; using namespace Pinetime::Applications;
@ -157,6 +158,13 @@ void DisplayApp::Refresh() {
// toggle = true; // toggle = true;
// } // }
break;
case Messages::BleFirmwareUpdateStarted:
lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::Down);
currentScreen.reset(nullptr);
currentScreen.reset(new Screens::FirmwareUpdate(this, bleController));
onClockApp = false;
break; break;
} }
} }

View file

@ -30,7 +30,7 @@ namespace Pinetime {
public: public:
enum class States {Idle, Running}; enum class States {Idle, Running};
enum class Messages : uint8_t {GoToSleep, GoToRunning, UpdateDateTime, UpdateBleConnection, UpdateBatteryLevel, TouchEvent, SwitchScreen,ButtonPushed, enum class Messages : uint8_t {GoToSleep, GoToRunning, UpdateDateTime, UpdateBleConnection, UpdateBatteryLevel, TouchEvent, SwitchScreen,ButtonPushed,
NewNotification NewNotification, BleFirmwareUpdateStarted, BleFirmwareUpdateFinished
}; };
enum class FullRefreshDirections { None, Up, Down }; enum class FullRefreshDirections { None, Up, Down };

View file

@ -74,6 +74,9 @@ void LittleVgl::SetFullRefresh(FullRefreshDirections direction) {
void LittleVgl::FlushDisplay(const lv_area_t *area, lv_color_t *color_p) { void LittleVgl::FlushDisplay(const lv_area_t *area, lv_color_t *color_p) {
ulTaskNotifyTake(pdTRUE, 500); ulTaskNotifyTake(pdTRUE, 500);
// NOtification is still needed (even if there is a mutex on SPI) because of the DataCommand pin
// which cannot be set/clear during a transfert.
// TODO refactore and remove duplicated code // TODO refactore and remove duplicated code

View file

@ -0,0 +1,82 @@
#include <libs/lvgl/lvgl.h>
#include "FirmwareUpdate.h"
#include "../DisplayApp.h"
using namespace Pinetime::Applications::Screens;
extern lv_font_t jetbrains_mono_extrabold_compressed;
extern lv_font_t jetbrains_mono_bold_20;
FirmwareUpdate::FirmwareUpdate(Pinetime::Applications::DisplayApp *app, Pinetime::Controllers::Ble& bleController) :
Screen(app), bleController{bleController} {
titleLabel = lv_label_create(lv_scr_act(), NULL);
lv_label_set_text(titleLabel, "Firmware update");
lv_obj_set_auto_realign(titleLabel, true);
lv_obj_align(titleLabel, NULL, LV_ALIGN_IN_TOP_MID, 0, 50);
bar1 = lv_bar_create(lv_scr_act(), NULL);
lv_obj_set_size(bar1, 200, 30);
lv_obj_align(bar1, NULL, LV_ALIGN_CENTER, 0, 0);
lv_bar_set_anim_time(bar1, 10);
lv_bar_set_range(bar1, 0, 100);
lv_bar_set_value(bar1, 0, LV_ANIM_OFF);
percentLabel = lv_label_create(lv_scr_act(), NULL);
lv_label_set_text(percentLabel, "");
lv_obj_set_auto_realign(percentLabel, true);
lv_obj_align(percentLabel, bar1, LV_ALIGN_OUT_TOP_MID, 0, 60);
}
FirmwareUpdate::~FirmwareUpdate() {
lv_obj_clean(lv_scr_act());
}
bool FirmwareUpdate::Refresh() {
switch(bleController.State()) {
default:
case Pinetime::Controllers::Ble::FirmwareUpdateStates::Idle:
case Pinetime::Controllers::Ble::FirmwareUpdateStates::Running:
if(state != States::Running)
state = States::Running;
return DisplayProgression();
case Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated:
if(state != States::Validated) {
UpdateValidated();
state = States::Validated;
}
return running;
case Pinetime::Controllers::Ble::FirmwareUpdateStates::Error:
if(state != States::Error) {
UpdateError();
state = States::Error;
}
return running;
}
}
bool FirmwareUpdate::DisplayProgression() const {
float current = bleController.FirmwareUpdateCurrentBytes() / 1024.0f;
float total = bleController.FirmwareUpdateTotalBytes() / 1024.0f;
int16_t pc = (current / total) * 100.0f;
sprintf(percentStr, "%d %%", pc);
lv_label_set_text(percentLabel, percentStr);
lv_bar_set_value(bar1, pc, LV_ANIM_OFF);
return running;
}
bool FirmwareUpdate::OnButtonPushed() {
running = false;
return true;
}
void FirmwareUpdate::UpdateValidated() {
lv_label_set_recolor(percentLabel, true);
lv_label_set_text(percentLabel, "#00ff00 Image Ok!#");
}
void FirmwareUpdate::UpdateError() {
lv_label_set_recolor(percentLabel, true);
lv_label_set_text(percentLabel, "#ff0000 Error!#");
}

View file

@ -0,0 +1,46 @@
#pragma once
#include <cstdint>
#include <chrono>
#include <Components/Gfx/Gfx.h>
#include "Screen.h"
#include <bits/unique_ptr.h>
#include <libs/lvgl/src/lv_core/lv_style.h>
#include <libs/lvgl/src/lv_core/lv_obj.h>
#include <Components/Battery/BatteryController.h>
#include <Components/Ble/BleController.h>
#include "../Fonts/lcdfont14.h"
#include "../Fonts/lcdfont70.h"
#include "../../Version.h"
namespace Pinetime {
namespace Applications {
namespace Screens {
class FirmwareUpdate : public Screen{
public:
FirmwareUpdate(DisplayApp* app, Pinetime::Controllers::Ble& bleController);
~FirmwareUpdate() override;
bool Refresh() override;
bool OnButtonPushed() override;
private:
enum class States { Idle, Running, Validated, Error };
Pinetime::Controllers::Ble& bleController;
lv_obj_t* bar1;
lv_obj_t* percentLabel;
lv_obj_t* titleLabel;
mutable char percentStr[10];
bool running = true;
States state;
bool DisplayProgression() const;
void UpdateValidated();
void UpdateError();
};
}
}
}

View file

@ -96,7 +96,7 @@
#define configUSE_TIMERS 1 #define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( 0 ) #define configTIMER_TASK_PRIORITY ( 0 )
#define configTIMER_QUEUE_LENGTH 32 #define configTIMER_QUEUE_LENGTH 32
#define configTIMER_TASK_STACK_DEPTH ( 120 ) #define configTIMER_TASK_STACK_DEPTH ( 240 )
/* Tickless Idle configuration. */ /* Tickless Idle configuration. */
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2 #define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2

View file

@ -10,19 +10,27 @@
#include <nimble/hci_common.h> #include <nimble/hci_common.h>
#include <host/ble_gap.h> #include <host/ble_gap.h>
#include <host/util/util.h> #include <host/util/util.h>
#include <drivers/InternalFlash.h>
#include "../main.h" #include "../main.h"
using namespace Pinetime::System; using namespace Pinetime::System;
SystemTask::SystemTask(Drivers::SpiMaster &spi, Drivers::St7789 &lcd, Drivers::Cst816S &touchPanel, void IdleTimerCallback(TimerHandle_t xTimer) {
auto sysTask = static_cast<SystemTask *>(pvTimerGetTimerID(xTimer));
sysTask->OnIdle();
}
SystemTask::SystemTask(Drivers::SpiMaster &spi, Drivers::St7789 &lcd,
Pinetime::Drivers::SpiNorFlash& spiNorFlash, Drivers::Cst816S &touchPanel,
Components::LittleVgl &lvgl, Components::LittleVgl &lvgl,
Controllers::Battery &batteryController, Controllers::Ble &bleController, Controllers::Battery &batteryController, Controllers::Ble &bleController,
Controllers::DateTime &dateTimeController, Controllers::DateTime &dateTimeController,
Pinetime::Controllers::NotificationManager& notificationManager) : Pinetime::Controllers::NotificationManager& notificationManager) :
spi{spi}, lcd{lcd}, touchPanel{touchPanel}, lvgl{lvgl}, batteryController{batteryController}, spi{spi}, lcd{lcd}, spiNorFlash{spiNorFlash}, touchPanel{touchPanel}, lvgl{lvgl}, batteryController{batteryController},
bleController{bleController}, dateTimeController{dateTimeController}, bleController{bleController}, dateTimeController{dateTimeController},
watchdog{}, watchdogView{watchdog}, notificationManager{notificationManager}, watchdog{}, watchdogView{watchdog}, notificationManager{notificationManager},
nimbleController(*this, bleController,dateTimeController, notificationManager) { nimbleController(*this, bleController,dateTimeController, notificationManager, spiNorFlash) {
systemTaksMsgQueue = xQueueCreate(10, 1); systemTaksMsgQueue = xQueueCreate(10, 1);
} }
@ -43,13 +51,20 @@ void SystemTask::Work() {
NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::Watchdog::ResetReasonToString(watchdog.ResetReason())); NRF_LOG_INFO("Last reset reason : %s", Pinetime::Drivers::Watchdog::ResetReasonToString(watchdog.ResetReason()));
APP_GPIOTE_INIT(2); APP_GPIOTE_INIT(2);
/* BLE */ spi.Init();
spiNorFlash.Init();
// Write the 'image OK' flag if it's not already done
// TODO implement a better verification mecanism for the image (ask for user confirmation via UI/BLE ?)
uint32_t* imageOkPtr = reinterpret_cast<uint32_t *>(0x7BFE8);
uint32_t imageOk = *imageOkPtr;
if(imageOk != 1)
Pinetime::Drivers::InternalFlash::WriteWord(0x7BFE8, 1);
nimbleController.Init(); nimbleController.Init();
nimbleController.StartAdvertising(); nimbleController.StartAdvertising();
/* /BLE*/
spi.Init();
lcd.Init(); lcd.Init();
touchPanel.Init(); touchPanel.Init();
batteryController.Init(); batteryController.Init();
@ -83,26 +98,70 @@ void SystemTask::Work() {
nrfx_gpiote_in_init(pinTouchIrq, &pinConfig, nrfx_gpiote_evt_handler); nrfx_gpiote_in_init(pinTouchIrq, &pinConfig, nrfx_gpiote_evt_handler);
idleTimer = xTimerCreate ("idleTimer", idleTime, pdFALSE, this, IdleTimerCallback);
xTimerStart(idleTimer, 0);
while(true) { while(true) {
uint8_t msg; uint8_t msg;
if (xQueueReceive(systemTaksMsgQueue, &msg, isSleeping?2500 : 1000)) { if (xQueueReceive(systemTaksMsgQueue, &msg, isSleeping?2500 : 1000)) {
Messages message = static_cast<Messages >(msg); Messages message = static_cast<Messages >(msg);
switch(message) { switch(message) {
case Messages::GoToRunning: isSleeping = false; break; case Messages::GoToRunning:
isSleeping = false;
xTimerStart(idleTimer, 0);
nimbleController.StartAdvertising();
break;
case Messages::GoToSleep: case Messages::GoToSleep:
NRF_LOG_INFO("[SystemTask] Going to sleep"); NRF_LOG_INFO("[SystemTask] Going to sleep");
displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::GoToSleep); displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::GoToSleep);
isSleeping = true; break; isSleeping = true;
break;
case Messages::OnNewTime: case Messages::OnNewTime:
xTimerReset(idleTimer, 0);
displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::UpdateDateTime); displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::UpdateDateTime);
break; break;
case Messages::OnNewNotification: case Messages::OnNewNotification:
xTimerReset(idleTimer, 0);
displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::NewNotification); displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::NewNotification);
break; break;
case Messages::BleConnected:
xTimerReset(idleTimer, 0);
isBleDiscoveryTimerRunning = true;
bleDiscoveryTimer = 5;
break;
case Messages::BleFirmwareUpdateStarted:
doNotGoToSleep = true;
if(isSleeping) GoToRunning();
displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::BleFirmwareUpdateStarted);
break;
case Messages::BleFirmwareUpdateFinished:
doNotGoToSleep = false;
xTimerStart(idleTimer, 0);
displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::BleFirmwareUpdateFinished);
if(bleController.State() == Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated)
NVIC_SystemReset();
break;
case Messages::OnTouchEvent:
xTimerReset(idleTimer, 0);
break;
case Messages::OnButtonEvent:
xTimerReset(idleTimer, 0);
break;
default: break; default: break;
} }
} }
if(isBleDiscoveryTimerRunning) {
if(bleDiscoveryTimer == 0) {
isBleDiscoveryTimerRunning = false;
// Services discovery is deffered from 3 seconds to avoid the conflicts between the host communicating with the
// tharget and vice-versa. I'm not sure if this is the right way to handle this...
nimbleController.StartDiscovery();
} else {
bleDiscoveryTimer--;
}
}
uint32_t systick_counter = nrf_rtc_counter_get(portNRF_RTC_REG); uint32_t systick_counter = nrf_rtc_counter_get(portNRF_RTC_REG);
dateTimeController.UpdateTime(systick_counter); dateTimeController.UpdateTime(systick_counter);
batteryController.Update(); batteryController.Update();
@ -113,22 +172,29 @@ void SystemTask::Work() {
} }
void SystemTask::OnButtonPushed() { void SystemTask::OnButtonPushed() {
if(!isSleeping) { if(!isSleeping) {
NRF_LOG_INFO("[SystemTask] Button pushed"); NRF_LOG_INFO("[SystemTask] Button pushed");
PushMessage(Messages::OnButtonEvent);
displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::ButtonPushed); displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::ButtonPushed);
} }
else { else {
NRF_LOG_INFO("[SystemTask] Button pushed, waking up"); NRF_LOG_INFO("[SystemTask] Button pushed, waking up");
displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::GoToRunning); GoToRunning();
isSleeping = false;
displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::UpdateBatteryLevel);
} }
} }
void SystemTask::GoToRunning() {
PushMessage(Messages::GoToRunning);
displayApp->PushMessage(Applications::DisplayApp::Messages::GoToRunning);
displayApp->PushMessage(Applications::DisplayApp::Messages::UpdateBatteryLevel);
}
void SystemTask::OnTouchEvent() { void SystemTask::OnTouchEvent() {
NRF_LOG_INFO("[SystemTask] Touch event"); NRF_LOG_INFO("[SystemTask] Touch event");
if(!isSleeping) {
PushMessage(Messages::OnTouchEvent);
displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::TouchEvent); displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::TouchEvent);
}
} }
void SystemTask::PushMessage(SystemTask::Messages msg) { void SystemTask::PushMessage(SystemTask::Messages msg) {
@ -140,3 +206,9 @@ void SystemTask::PushMessage(SystemTask::Messages msg) {
// TODO : should I do something here? // TODO : should I do something here?
} }
} }
void SystemTask::OnIdle() {
if(doNotGoToSleep) return;
NRF_LOG_INFO("Idle timeout -> Going to sleep")
PushMessage(Messages::GoToSleep);
}

View file

@ -9,15 +9,18 @@
#include <DisplayApp/DisplayApp.h> #include <DisplayApp/DisplayApp.h>
#include <drivers/Watchdog.h> #include <drivers/Watchdog.h>
#include <Components/Ble/NimbleController.h> #include <Components/Ble/NimbleController.h>
#include <drivers/SpiNorFlash.h>
namespace Pinetime { namespace Pinetime {
namespace System { namespace System {
class SystemTask { class SystemTask {
public: public:
enum class Messages {GoToSleep, GoToRunning, OnNewTime, OnNewNotification enum class Messages {GoToSleep, GoToRunning, OnNewTime, OnNewNotification, BleConnected,
BleFirmwareUpdateStarted, BleFirmwareUpdateFinished, OnTouchEvent, OnButtonEvent
}; };
SystemTask(Drivers::SpiMaster &spi, Drivers::St7789 &lcd, Drivers::Cst816S &touchPanel, SystemTask(Drivers::SpiMaster &spi, Drivers::St7789 &lcd,
Pinetime::Drivers::SpiNorFlash& spiNorFlash, Drivers::Cst816S &touchPanel,
Components::LittleVgl &lvgl, Components::LittleVgl &lvgl,
Controllers::Battery &batteryController, Controllers::Ble &bleController, Controllers::Battery &batteryController, Controllers::Ble &bleController,
Controllers::DateTime &dateTimeController, Controllers::DateTime &dateTimeController,
@ -29,11 +32,15 @@ namespace Pinetime {
void OnButtonPushed(); void OnButtonPushed();
void OnTouchEvent(); void OnTouchEvent();
void OnIdle();
private: private:
TaskHandle_t taskHandle; TaskHandle_t taskHandle;
Pinetime::Drivers::SpiMaster& spi; Pinetime::Drivers::SpiMaster& spi;
Pinetime::Drivers::St7789& lcd; Pinetime::Drivers::St7789& lcd;
Pinetime::Drivers::SpiNorFlash& spiNorFlash;
Pinetime::Drivers::Cst816S& touchPanel; Pinetime::Drivers::Cst816S& touchPanel;
Pinetime::Components::LittleVgl& lvgl; Pinetime::Components::LittleVgl& lvgl;
Pinetime::Controllers::Battery& batteryController; Pinetime::Controllers::Battery& batteryController;
@ -58,8 +65,13 @@ namespace Pinetime {
static void Process(void* instance); static void Process(void* instance);
void Work(); void Work();
bool isBleDiscoveryTimerRunning = false;
uint8_t bleDiscoveryTimer = 0;
static constexpr uint32_t idleTime = 5000;
TimerHandle_t idleTimer;
bool doNotGoToSleep = false;
void GoToRunning();
}; };
} }
} }

View file

@ -0,0 +1,39 @@
#include <sdk/modules/nrfx/mdk/nrf.h>
#include "InternalFlash.h"
using namespace Pinetime::Drivers;
void InternalFlash::ErasePage(uint32_t address) {
// Enable erase.
NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Een;
__ISB();
__DSB();
// Erase the page
NRF_NVMC->ERASEPAGE = address;
Wait();
// Disable erase
NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren;
__ISB();
__DSB();
}
void InternalFlash::WriteWord(uint32_t address, uint32_t value) {
// Enable write.
NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen;
__ISB();
__DSB();
// Write word
*(uint32_t*)address = value;
Wait();
// Disable write
NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren;
__ISB();
__DSB();
}
void InternalFlash::Wait() {
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {;}
}

View file

@ -0,0 +1,15 @@
#pragma once
#include <cstdint>
namespace Pinetime {
namespace Drivers {
class InternalFlash {
public:
static void ErasePage(uint32_t address);
static void WriteWord(uint32_t address, uint32_t value);
private:
static inline void Wait();
};
}
}

34
src/drivers/Spi.cpp Normal file
View file

@ -0,0 +1,34 @@
#include <hal/nrf_gpio.h>
#include "Spi.h"
using namespace Pinetime::Drivers;
Spi::Spi(SpiMaster& spiMaster, uint8_t pinCsn) :
spiMaster{spiMaster}, pinCsn{pinCsn} {
nrf_gpio_cfg_output(pinCsn);
nrf_gpio_pin_set(pinCsn);
}
bool Spi::Write(const uint8_t *data, size_t size) {
return spiMaster.Write(pinCsn, data, size);
}
bool Spi::Read(uint8_t* cmd, size_t cmdSize, uint8_t *data, size_t dataSize) {
return spiMaster.Read(pinCsn, cmd, cmdSize, data, dataSize);
}
void Spi::Sleep() {
// TODO sleep spi
nrf_gpio_cfg_default(pinCsn);
}
bool Spi::Init() {
nrf_gpio_pin_set(pinCsn); /* disable Set slave select (inactive high) */
return true;
}
bool Spi::WriteCmdAndBuffer(const uint8_t *cmd, size_t cmdSize, const uint8_t *data, size_t dataSize) {
return spiMaster.WriteCmdAndBuffer(pinCsn, cmd, cmdSize, data, dataSize);
}

34
src/drivers/Spi.h Normal file
View file

@ -0,0 +1,34 @@
#pragma once
#include <FreeRTOS.h>
#include <cstdint>
#include <cstddef>
#include <array>
#include <atomic>
#include <task.h>
#include "BufferProvider.h"
#include "SpiMaster.h"
namespace Pinetime {
namespace Drivers {
class Spi {
public:
Spi(SpiMaster& spiMaster, uint8_t pinCsn);
Spi(const Spi&) = delete;
Spi& operator=(const Spi&) = delete;
Spi(Spi&&) = delete;
Spi& operator=(Spi&&) = delete;
bool Init();
bool Write(const uint8_t* data, size_t size);
bool Read(uint8_t* cmd, size_t cmdSize, uint8_t *data, size_t dataSize);
bool WriteCmdAndBuffer(const uint8_t* cmd, size_t cmdSize, const uint8_t *data, size_t dataSize);
void Sleep();
void Wakeup();
private:
SpiMaster& spiMaster;
uint8_t pinCsn;
};
}
}

View file

@ -20,8 +20,8 @@ bool SpiMaster::Init() {
nrf_gpio_pin_clear(params.pinMOSI); nrf_gpio_pin_clear(params.pinMOSI);
nrf_gpio_cfg_output(params.pinMOSI); nrf_gpio_cfg_output(params.pinMOSI);
nrf_gpio_cfg_input(params.pinMISO, NRF_GPIO_PIN_NOPULL); nrf_gpio_cfg_input(params.pinMISO, NRF_GPIO_PIN_NOPULL);
nrf_gpio_cfg_output(params.pinCSN); // nrf_gpio_cfg_output(params.pinCSN);
pinCsn = params.pinCSN; // pinCsn = params.pinCSN;
switch(spi) { switch(spi) {
case SpiModule::SPI0: spiBaseAddress = NRF_SPIM0; break; case SpiModule::SPI0: spiBaseAddress = NRF_SPIM0; break;
@ -33,7 +33,6 @@ bool SpiMaster::Init() {
spiBaseAddress->PSELSCK = params.pinSCK; spiBaseAddress->PSELSCK = params.pinSCK;
spiBaseAddress->PSELMOSI = params.pinMOSI; spiBaseAddress->PSELMOSI = params.pinMOSI;
spiBaseAddress->PSELMISO = params.pinMISO; spiBaseAddress->PSELMISO = params.pinMISO;
nrf_gpio_pin_set(pinCsn); /* disable Set slave select (inactive high) */
uint32_t frequency; uint32_t frequency;
switch(params.Frequency) { switch(params.Frequency) {
@ -147,19 +146,33 @@ void SpiMaster::PrepareTx(const volatile uint32_t bufferAddress, const volatile
spiBaseAddress->EVENTS_END = 0; spiBaseAddress->EVENTS_END = 0;
} }
bool SpiMaster::Write(const uint8_t *data, size_t size) { void SpiMaster::PrepareRx(const volatile uint32_t cmdAddress, const volatile size_t cmdSize, const volatile uint32_t bufferAddress, const volatile size_t size) {
spiBaseAddress->TXD.PTR = 0;
spiBaseAddress->TXD.MAXCNT = 0;
spiBaseAddress->TXD.LIST = 0;
spiBaseAddress->RXD.PTR = bufferAddress;
spiBaseAddress->RXD.MAXCNT = size;
spiBaseAddress->RXD.LIST = 0;
spiBaseAddress->EVENTS_END = 0;
}
bool SpiMaster::Write(uint8_t pinCsn, const uint8_t *data, size_t size) {
if(data == nullptr) return false; if(data == nullptr) return false;
auto ok = xSemaphoreTake(mutex, portMAX_DELAY); auto ok = xSemaphoreTake(mutex, portMAX_DELAY);
ASSERT(ok == true); ASSERT(ok == true);
taskToNotify = xTaskGetCurrentTaskHandle(); taskToNotify = xTaskGetCurrentTaskHandle();
this->pinCsn = pinCsn;
if(size == 1) { if(size == 1) {
SetupWorkaroundForFtpan58(spiBaseAddress, 0,0); SetupWorkaroundForFtpan58(spiBaseAddress, 0,0);
} else { } else {
DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0); DisableWorkaroundForFtpan58(spiBaseAddress, 0, 0);
} }
nrf_gpio_pin_clear(pinCsn); nrf_gpio_pin_clear(this->pinCsn);
currentBufferAddr = (uint32_t)data; currentBufferAddr = (uint32_t)data;
currentBufferSize = size; currentBufferSize = size;
@ -172,12 +185,47 @@ bool SpiMaster::Write(const uint8_t *data, size_t size) {
if(size == 1) { if(size == 1) {
while (spiBaseAddress->EVENTS_END == 0); while (spiBaseAddress->EVENTS_END == 0);
nrf_gpio_pin_set(this->pinCsn);
currentBufferAddr = 0;
xSemaphoreGive(mutex); xSemaphoreGive(mutex);
} }
return true; return true;
} }
bool SpiMaster::Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t *data, size_t dataSize) {
xSemaphoreTake(mutex, portMAX_DELAY);
taskToNotify = nullptr;
this->pinCsn = pinCsn;
DisableWorkaroundForFtpan58(spiBaseAddress, 0,0);
spiBaseAddress->INTENCLR = (1<<6);
spiBaseAddress->INTENCLR = (1<<1);
spiBaseAddress->INTENCLR = (1<<19);
nrf_gpio_pin_clear(this->pinCsn);
currentBufferAddr = 0;
currentBufferSize = 0;
PrepareTx((uint32_t)cmd, cmdSize);
spiBaseAddress->TASKS_START = 1;
while (spiBaseAddress->EVENTS_END == 0);
PrepareRx((uint32_t)cmd, cmdSize, (uint32_t)data, dataSize);
spiBaseAddress->TASKS_START = 1;
while (spiBaseAddress->EVENTS_END == 0);
nrf_gpio_pin_set(this->pinCsn);
xSemaphoreGive(mutex);
return true;
}
void SpiMaster::Sleep() { void SpiMaster::Sleep() {
while(spiBaseAddress->ENABLE != 0) { while(spiBaseAddress->ENABLE != 0) {
spiBaseAddress->ENABLE = (SPIM_ENABLE_ENABLE_Disabled << SPIM_ENABLE_ENABLE_Pos); spiBaseAddress->ENABLE = (SPIM_ENABLE_ENABLE_Disabled << SPIM_ENABLE_ENABLE_Pos);
@ -185,11 +233,43 @@ void SpiMaster::Sleep() {
nrf_gpio_cfg_default(params.pinSCK); nrf_gpio_cfg_default(params.pinSCK);
nrf_gpio_cfg_default(params.pinMOSI); nrf_gpio_cfg_default(params.pinMOSI);
nrf_gpio_cfg_default(params.pinMISO); nrf_gpio_cfg_default(params.pinMISO);
nrf_gpio_cfg_default(params.pinCSN);
} }
void SpiMaster::Wakeup() { void SpiMaster::Wakeup() {
Init(); Init();
} }
bool SpiMaster::WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t *cmd, size_t cmdSize, const uint8_t *data, size_t dataSize) {
xSemaphoreTake(mutex, portMAX_DELAY);
taskToNotify = nullptr;
this->pinCsn = pinCsn;
DisableWorkaroundForFtpan58(spiBaseAddress, 0,0);
spiBaseAddress->INTENCLR = (1<<6);
spiBaseAddress->INTENCLR = (1<<1);
spiBaseAddress->INTENCLR = (1<<19);
nrf_gpio_pin_clear(this->pinCsn);
currentBufferAddr = 0;
currentBufferSize = 0;
PrepareTx((uint32_t)cmd, cmdSize);
spiBaseAddress->TASKS_START = 1;
while (spiBaseAddress->EVENTS_END == 0);
PrepareTx((uint32_t)data, dataSize);
spiBaseAddress->TASKS_START = 1;
while (spiBaseAddress->EVENTS_END == 0);
nrf_gpio_pin_set(this->pinCsn);
xSemaphoreGive(mutex);
return true;
}

View file

@ -5,6 +5,7 @@
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <task.h> #include <task.h>
#include <semphr.h>
#include "BufferProvider.h" #include "BufferProvider.h"
#include <semphr.h> #include <semphr.h>
@ -24,7 +25,6 @@ namespace Pinetime {
uint8_t pinSCK; uint8_t pinSCK;
uint8_t pinMOSI; uint8_t pinMOSI;
uint8_t pinMISO; uint8_t pinMISO;
uint8_t pinCSN;
}; };
SpiMaster(const SpiModule spi, const Parameters& params); SpiMaster(const SpiModule spi, const Parameters& params);
@ -34,7 +34,10 @@ namespace Pinetime {
SpiMaster& operator=(SpiMaster&&) = delete; SpiMaster& operator=(SpiMaster&&) = delete;
bool Init(); bool Init();
bool Write(const uint8_t* data, size_t size); bool Write(uint8_t pinCsn, const uint8_t* data, size_t size);
bool Read(uint8_t pinCsn, uint8_t* cmd, size_t cmdSize, uint8_t *data, size_t dataSize);
bool WriteCmdAndBuffer(uint8_t pinCsn, const uint8_t* cmd, size_t cmdSize, const uint8_t *data, size_t dataSize);
void OnStartedEvent(); void OnStartedEvent();
void OnEndEvent(); void OnEndEvent();
@ -46,6 +49,7 @@ namespace Pinetime {
void SetupWorkaroundForFtpan58(NRF_SPIM_Type *spim, uint32_t ppi_channel, uint32_t gpiote_channel); void SetupWorkaroundForFtpan58(NRF_SPIM_Type *spim, uint32_t ppi_channel, uint32_t gpiote_channel);
void DisableWorkaroundForFtpan58(NRF_SPIM_Type *spim, uint32_t ppi_channel, uint32_t gpiote_channel); void DisableWorkaroundForFtpan58(NRF_SPIM_Type *spim, uint32_t ppi_channel, uint32_t gpiote_channel);
void PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size); void PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size);
void PrepareRx(const volatile uint32_t cmdAddress, const volatile size_t cmdSize, const volatile uint32_t bufferAddress, const volatile size_t size);
NRF_SPIM_Type * spiBaseAddress; NRF_SPIM_Type * spiBaseAddress;
uint8_t pinCsn; uint8_t pinCsn;

124
src/drivers/SpiNorFlash.cpp Normal file
View file

@ -0,0 +1,124 @@
#include <hal/nrf_gpio.h>
#include <libraries/delay/nrf_delay.h>
#include <libraries/log/nrf_log.h>
#include "SpiNorFlash.h"
#include "Spi.h"
using namespace Pinetime::Drivers;
SpiNorFlash::SpiNorFlash(Spi& spi) : spi{spi} {
}
void SpiNorFlash::Init() {
auto id = ReadIdentificaion();
NRF_LOG_INFO("[SPI FLASH] Manufacturer : %d, Memory type : %d, memory density : %d", id.manufacturer, id.type, id.density);
}
void SpiNorFlash::Uninit() {
}
void SpiNorFlash::Sleep() {
}
void SpiNorFlash::Wakeup() {
}
SpiNorFlash::Identification SpiNorFlash::ReadIdentificaion() {
auto cmd = static_cast<uint8_t>(Commands::ReadIdentification);
Identification identification;
spi.Read(&cmd, 1, reinterpret_cast<uint8_t *>(&identification), sizeof(Identification));
return identification;
}
uint8_t SpiNorFlash::ReadStatusRegister() {
auto cmd = static_cast<uint8_t>(Commands::ReadStatusRegister);
uint8_t status;
spi.Read(&cmd, sizeof(cmd), &status, sizeof(uint8_t));
return status;
}
bool SpiNorFlash::WriteInProgress() {
return (ReadStatusRegister() & 0x01u) == 0x01u;
}
bool SpiNorFlash::WriteEnabled() {
return (ReadStatusRegister() & 0x02u) == 0x02u;
}
uint8_t SpiNorFlash::ReadConfigurationRegister() {
auto cmd = static_cast<uint8_t>(Commands::ReadConfigurationRegister);
uint8_t status;
spi.Read(&cmd, sizeof(cmd), &status, sizeof(uint8_t));
return status;
}
void SpiNorFlash::Read(uint32_t address, uint8_t *buffer, size_t size) {
static constexpr uint8_t cmdSize = 4;
uint8_t cmd[cmdSize] = { static_cast<uint8_t>(Commands::Read), (uint8_t)(address >> 16U), (uint8_t)(address >> 8U),
(uint8_t)address };
spi.Read(reinterpret_cast<uint8_t *>(&cmd), cmdSize, buffer, size);
}
void SpiNorFlash::WriteEnable() {
auto cmd = static_cast<uint8_t>(Commands::WriteEnable);
spi.Read(&cmd, sizeof(cmd), nullptr, 0);
}
void SpiNorFlash::SectorErase(uint32_t sectorAddress) {
static constexpr uint8_t cmdSize = 4;
uint8_t cmd[cmdSize] = { static_cast<uint8_t>(Commands::SectorErase), (uint8_t)(sectorAddress >> 16U), (uint8_t)(sectorAddress >> 8U),
(uint8_t)sectorAddress };
WriteEnable();
while(!WriteEnabled()) vTaskDelay(1);
spi.Read(reinterpret_cast<uint8_t *>(&cmd), cmdSize, nullptr, 0);
while(WriteInProgress()) vTaskDelay(1);
}
uint8_t SpiNorFlash::ReadSecurityRegister() {
auto cmd = static_cast<uint8_t>(Commands::ReadSecurityRegister);
uint8_t status;
spi.Read(&cmd, sizeof(cmd), &status, sizeof(uint8_t));
return status;
}
bool SpiNorFlash::ProgramFailed() {
return (ReadSecurityRegister() & 0x20u) == 0x20u;
}
bool SpiNorFlash::EraseFailed() {
return (ReadSecurityRegister() & 0x40u) == 0x40u;
}
void SpiNorFlash::Write(uint32_t address, const uint8_t *buffer, size_t size) {
static constexpr uint8_t cmdSize = 4;
size_t len = size;
uint32_t addr = address;
const uint8_t* b = buffer;
while(len > 0) {
uint32_t pageLimit = (addr & ~(pageSize - 1u)) + pageSize;
uint32_t toWrite = pageLimit - addr > len ? len : pageLimit - addr;
uint8_t cmd[cmdSize] = { static_cast<uint8_t>(Commands::PageProgram), (uint8_t)(addr >> 16U), (uint8_t)(addr >> 8U),
(uint8_t)addr };
WriteEnable();
while(!WriteEnabled()) vTaskDelay(1);
spi.WriteCmdAndBuffer(cmd, cmdSize, b, toWrite);
while(WriteInProgress()) vTaskDelay(1);
addr += toWrite;
b += toWrite;
len -= toWrite;
}
}

60
src/drivers/SpiNorFlash.h Normal file
View file

@ -0,0 +1,60 @@
#pragma once
#include <cstddef>
namespace Pinetime {
namespace Drivers {
class Spi;
class SpiNorFlash {
public:
explicit SpiNorFlash(Spi& spi);
SpiNorFlash(const SpiNorFlash&) = delete;
SpiNorFlash& operator=(const SpiNorFlash&) = delete;
SpiNorFlash(SpiNorFlash&&) = delete;
SpiNorFlash& operator=(SpiNorFlash&&) = delete;
typedef struct __attribute__((packed)) {
uint8_t manufacturer = 0;
uint8_t type = 0;
uint8_t density = 0;
} Identification;
Identification ReadIdentificaion();
uint8_t ReadStatusRegister();
bool WriteInProgress();
bool WriteEnabled();
uint8_t ReadConfigurationRegister();
void Read(uint32_t address, uint8_t* buffer, size_t size);
void Write(uint32_t address, const uint8_t *buffer, size_t size);
void WriteEnable();
void SectorErase(uint32_t sectorAddress);
uint8_t ReadSecurityRegister();
bool ProgramFailed();
bool EraseFailed();
void Init();
void Uninit();
void Sleep();
void Wakeup();
private:
enum class Commands : uint8_t {
PageProgram = 0x02,
Read = 0x03,
ReadStatusRegister = 0x05,
WriteEnable = 0x06,
ReadConfigurationRegister = 0x15,
SectorErase = 0x20,
ReadSecurityRegister = 0x2B,
ReadIdentification = 0x9F,
};
static constexpr uint16_t pageSize = 256;
Spi& spi;
};
}
}

View file

@ -1,16 +1,17 @@
#include <hal/nrf_gpio.h> #include <hal/nrf_gpio.h>
#include <libraries/delay/nrf_delay.h> #include <libraries/delay/nrf_delay.h>
#include "St7789.h" #include "St7789.h"
#include "SpiMaster.h" #include "Spi.h"
using namespace Pinetime::Drivers; using namespace Pinetime::Drivers;
St7789::St7789(SpiMaster &spiMaster, uint8_t pinDataCommand) : spi{spiMaster}, pinDataCommand{pinDataCommand} { St7789::St7789(Spi &spi, uint8_t pinDataCommand) : spi{spi}, pinDataCommand{pinDataCommand} {
} }
void St7789::Init() { void St7789::Init() {
spi.Init();
nrf_gpio_cfg_output(pinDataCommand); nrf_gpio_cfg_output(pinDataCommand);
nrf_gpio_cfg_output(26); nrf_gpio_cfg_output(26);
nrf_gpio_pin_set(26); nrf_gpio_pin_set(26);
@ -173,11 +174,11 @@ void St7789::HardwareReset() {
void St7789::Sleep() { void St7789::Sleep() {
SleepIn(); SleepIn();
nrf_gpio_cfg_default(pinDataCommand); nrf_gpio_cfg_default(pinDataCommand);
spi.Sleep(); // spi.Sleep(); // TODO sleep SPI
} }
void St7789::Wakeup() { void St7789::Wakeup() {
spi.Wakeup(); // spi.Wakeup(); // TODO wake up SPI
nrf_gpio_cfg_output(pinDataCommand); nrf_gpio_cfg_output(pinDataCommand);
// TODO why do we need to reset the controller? // TODO why do we need to reset the controller?

View file

@ -3,10 +3,10 @@
namespace Pinetime { namespace Pinetime {
namespace Drivers { namespace Drivers {
class SpiMaster; class Spi;
class St7789 { class St7789 {
public: public:
explicit St7789(SpiMaster& spiMaster, uint8_t pinDataCommand); explicit St7789(Spi& spi, uint8_t pinDataCommand);
St7789(const St7789&) = delete; St7789(const St7789&) = delete;
St7789& operator=(const St7789&) = delete; St7789& operator=(const St7789&) = delete;
St7789(St7789&&) = delete; St7789(St7789&&) = delete;
@ -29,7 +29,7 @@ namespace Pinetime {
void Sleep(); void Sleep();
void Wakeup(); void Wakeup();
private: private:
SpiMaster& spi; Spi& spi;
uint8_t pinDataCommand; uint8_t pinDataCommand;
uint8_t verticalScrollingStartAddress = 0; uint8_t verticalScrollingStartAddress = 0;

135
src/graphics.cpp Normal file
View file

@ -0,0 +1,135 @@
#include <legacy/nrf_drv_clock.h>
#include <softdevice/common/nrf_sdh.h>
#include <drivers/SpiMaster.h>
#include <drivers/Spi.h>
#include <drivers/SpiNorFlash.h>
#include <sdk/components/libraries/log/nrf_log.h>
#include "bootloader/boot_graphics.h"
#include <FreeRTOS.h>
#include <task.h>
#include <sdk/integration/nrfx/legacy/nrf_drv_gpiote.h>
#include <libraries/gpiote/app_gpiote.h>
#include <sdk/modules/nrfx/hal/nrf_wdt.h>
#include <cstring>
#include <Components/Gfx/Gfx.h>
#include <drivers/St7789.h>
#include <Components/Brightness/BrightnessController.h>
#if NRF_LOG_ENABLED
#include "Logging/NrfLogger.h"
Pinetime::Logging::NrfLogger logger;
#else
#include "Logging/DummyLogger.h"
Pinetime::Logging::DummyLogger logger;
#endif
static constexpr uint8_t pinSpiSck = 2;
static constexpr uint8_t pinSpiMosi = 3;
static constexpr uint8_t pinSpiMiso = 4;
static constexpr uint8_t pinSpiFlashCsn = 5;
static constexpr uint8_t pinLcdCsn = 25;
static constexpr uint8_t pinLcdDataCommand = 18;
Pinetime::Drivers::SpiMaster spi{Pinetime::Drivers::SpiMaster::SpiModule::SPI0, {
Pinetime::Drivers::SpiMaster::BitOrder::Msb_Lsb,
Pinetime::Drivers::SpiMaster::Modes::Mode3,
Pinetime::Drivers::SpiMaster::Frequencies::Freq8Mhz,
pinSpiSck,
pinSpiMosi,
pinSpiMiso
}
};
Pinetime::Drivers::Spi flashSpi{spi, pinSpiFlashCsn};
Pinetime::Drivers::SpiNorFlash spiNorFlash{flashSpi};
Pinetime::Drivers::Spi lcdSpi {spi, pinLcdCsn};
Pinetime::Drivers::St7789 lcd {lcdSpi, pinLcdDataCommand};
Pinetime::Components::Gfx gfx{lcd};
Pinetime::Controllers::BrightnessController brightnessController;
extern "C" {
void vApplicationIdleHook(void) {
}
void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void) {
if(((NRF_SPIM0->INTENSET & (1<<6)) != 0) && NRF_SPIM0->EVENTS_END == 1) {
NRF_SPIM0->EVENTS_END = 0;
spi.OnEndEvent();
}
if(((NRF_SPIM0->INTENSET & (1<<19)) != 0) && NRF_SPIM0->EVENTS_STARTED == 1) {
NRF_SPIM0->EVENTS_STARTED = 0;
spi.OnStartedEvent();
}
if(((NRF_SPIM0->INTENSET & (1<<1)) != 0) && NRF_SPIM0->EVENTS_STOPPED == 1) {
NRF_SPIM0->EVENTS_STOPPED = 0;
}
}
}
void Process(void* instance) {
// Wait before erasing the memory to let the time to the SWD debugger to flash a new firmware before running this one.
vTaskDelay(5000);
APP_GPIOTE_INIT(2);
NRF_LOG_INFO("Init...");
spi.Init();
spiNorFlash.Init();
brightnessController.Init();
lcd.Init();
gfx.Init();
NRF_LOG_INFO("Init Done!")
NRF_LOG_INFO("Erasing...");
for (uint32_t erased = 0; erased < graphicSize; erased += 0x1000) {
spiNorFlash.SectorErase(erased);
}
NRF_LOG_INFO("Erase done!");
NRF_LOG_INFO("Writing graphic...");
static constexpr uint32_t memoryChunkSize = 200;
uint8_t writeBuffer[memoryChunkSize];
for(int offset = 0; offset < 115200; offset+=memoryChunkSize) {
std::memcpy(writeBuffer, &graphicBuffer[offset], memoryChunkSize);
spiNorFlash.Write(offset, writeBuffer, memoryChunkSize);
}
NRF_LOG_INFO("Writing graphic done!");
NRF_LOG_INFO("Read memory and display the graphic...");
static constexpr uint32_t screenWidth = 240;
static constexpr uint32_t screenWidthInBytes = screenWidth*2; // LCD display 16bits color (1 pixel = 2 bytes)
uint16_t displayLineBuffer[screenWidth];
for(int line = 0; line < screenWidth; line++) {
spiNorFlash.Read(line*screenWidthInBytes, reinterpret_cast<uint8_t *>(displayLineBuffer), screenWidth);
spiNorFlash.Read((line*screenWidthInBytes)+screenWidth, reinterpret_cast<uint8_t *>(displayLineBuffer) + screenWidth, screenWidth);
for(int col = 0; col < screenWidth; col++) {
gfx.pixel_draw(col, line, displayLineBuffer[col]);
}
}
NRF_LOG_INFO("Done!");
while(1) {
asm("nop" );
}
}
int main(void) {
TaskHandle_t taskHandle;
logger.Init();
nrf_drv_clock_init();
if (pdPASS != xTaskCreate(Process, "MAIN", 512, nullptr, 0, &taskHandle))
APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
vTaskStartScheduler();
for (;;) {
APP_ERROR_HANDLER(NRF_ERROR_FORBIDDEN);
}
}

View file

@ -14,7 +14,7 @@
#define BLE_HS_LOG_INFO(...) NRF_LOG_INFO(__VA_ARGS__) #define BLE_HS_LOG_INFO(...) NRF_LOG_INFO(__VA_ARGS__)
#define BLE_HS_LOG_WARN(...) NRF_LOG_WARNING( __VA_ARGS__) #define BLE_HS_LOG_WARN(...) NRF_LOG_WARNING( __VA_ARGS__)
#define BLE_HS_LOG_ERROR(...) NRF_LOG_ERROR(__VA_ARGS__) #define BLE_HS_LOG_ERROR(...) NRF_LOG_ERROR(__VA_ARGS__)
#define BLE_HS_LOG_CRITICAL(...) MODLOG_CRITICAL(4, __VA_ARGS__) #define BLE_HS_LOG_CRITICAL(...) NRF_LOG_ERROR(__VA_ARGS__)
#define BLE_HS_LOG_DISABLED(...) MODLOG_DISABLED(4, __VA_ARGS__) #define BLE_HS_LOG_DISABLED(...) MODLOG_DISABLED(4, __VA_ARGS__)
#endif #endif
#if 0 #if 0

View file

@ -460,7 +460,7 @@
/* Overridden by @apache-mynewt-nimble/targets/riot (defined by @apache-mynewt-core/kernel/os) */ /* Overridden by @apache-mynewt-nimble/targets/riot (defined by @apache-mynewt-core/kernel/os) */
#ifndef MYNEWT_VAL_MSYS_1_BLOCK_COUNT #ifndef MYNEWT_VAL_MSYS_1_BLOCK_COUNT
#define MYNEWT_VAL_MSYS_1_BLOCK_COUNT (5) #define MYNEWT_VAL_MSYS_1_BLOCK_COUNT (12)
#endif #endif
/* Overridden by @apache-mynewt-nimble/targets/riot (defined by @apache-mynewt-core/kernel/os) */ /* Overridden by @apache-mynewt-nimble/targets/riot (defined by @apache-mynewt-core/kernel/os) */

View file

@ -12,6 +12,7 @@
#include "Components/Ble/BleController.h" #include "Components/Ble/BleController.h"
#include <drivers/St7789.h> #include <drivers/St7789.h>
#include <drivers/SpiMaster.h> #include <drivers/SpiMaster.h>
#include <drivers/Spi.h>
#include <DisplayApp/LittleVgl.h> #include <DisplayApp/LittleVgl.h>
#include <SystemTask/SystemTask.h> #include <SystemTask/SystemTask.h>
#include <Components/Ble/NotificationManager.h> #include <Components/Ble/NotificationManager.h>
@ -38,7 +39,8 @@ Pinetime::Logging::DummyLogger logger;
static constexpr uint8_t pinSpiSck = 2; static constexpr uint8_t pinSpiSck = 2;
static constexpr uint8_t pinSpiMosi = 3; static constexpr uint8_t pinSpiMosi = 3;
static constexpr uint8_t pinSpiMiso = 4; static constexpr uint8_t pinSpiMiso = 4;
static constexpr uint8_t pinSpiCsn = 25; static constexpr uint8_t pinSpiFlashCsn = 5;
static constexpr uint8_t pinLcdCsn = 25;
static constexpr uint8_t pinLcdDataCommand = 18; static constexpr uint8_t pinLcdDataCommand = 18;
Pinetime::Drivers::SpiMaster spi{Pinetime::Drivers::SpiMaster::SpiModule::SPI0, { Pinetime::Drivers::SpiMaster spi{Pinetime::Drivers::SpiMaster::SpiModule::SPI0, {
@ -47,11 +49,15 @@ Pinetime::Drivers::SpiMaster spi{Pinetime::Drivers::SpiMaster::SpiModule::SPI0,
Pinetime::Drivers::SpiMaster::Frequencies::Freq8Mhz, Pinetime::Drivers::SpiMaster::Frequencies::Freq8Mhz,
pinSpiSck, pinSpiSck,
pinSpiMosi, pinSpiMosi,
pinSpiMiso, pinSpiMiso
pinSpiCsn
} }
}; };
Pinetime::Drivers::St7789 lcd {spi, pinLcdDataCommand};
Pinetime::Drivers::Spi lcdSpi {spi, pinLcdCsn};
Pinetime::Drivers::St7789 lcd {lcdSpi, pinLcdDataCommand};
Pinetime::Drivers::Spi flashSpi {spi, pinSpiFlashCsn};
Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi};
Pinetime::Drivers::Cst816S touchPanel {}; Pinetime::Drivers::Cst816S touchPanel {};
Pinetime::Components::LittleVgl lvgl {lcd, touchPanel}; Pinetime::Components::LittleVgl lvgl {lcd, touchPanel};
@ -206,7 +212,7 @@ int main(void) {
debounceTimer = xTimerCreate ("debounceTimer", 200, pdFALSE, (void *) 0, DebounceTimerCallback); debounceTimer = xTimerCreate ("debounceTimer", 200, pdFALSE, (void *) 0, DebounceTimerCallback);
systemTask.reset(new Pinetime::System::SystemTask(spi, lcd, touchPanel, lvgl, batteryController, bleController, systemTask.reset(new Pinetime::System::SystemTask(spi, lcd, spiNorFlash, touchPanel, lvgl, batteryController, bleController,
dateTimeController, notificationManager)); dateTimeController, notificationManager));
systemTask->Start(); systemTask->Start();
nimble_port_init(); nimble_port_init();