Skip to content

Commit 1f082f7

Browse files
committed
Add nice!nano v2 board support
1 parent 470a97a commit 1f082f7

6 files changed

Lines changed: 381 additions & 1 deletion

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ This Arduino Core does **not** contain any BLE functionality. It has been design
3434
* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516)
3535
* [Adafruit ItsyBitsy nRF52840 Express](https://www.adafruit.com/product/4481)
3636
* [Ebyte E104-BT5040U](https://www.ebyte.com/en/product-view-news.html?id=1185)
37+
* [nice!nano v2](https://nicekeyboards.com/docs/nice-nano/)
3738
* [XIAO nRF52840 Sense](https://wiki.seeedstudio.com/XIAO_BLE/)
3839

3940
### nRF52833
@@ -123,6 +124,12 @@ board = ...
123124
1. Install adafruit-nrfutil if not already installed `pip install adafruit-nrfutil`
124125
2. Select adafruit-nrfutil as the firmware uploader in the tools menu.
125126

127+
### Uploading via UF2
128+
Boards with an Adafruit UF2 bootloader, such as the nice!nano v2, can be reset
129+
into the bootloader and programmed by copying the exported `.uf2` file to the
130+
mounted bootloader drive. UF2 export requires Python 3 to be available on the
131+
system path.
132+
126133
### Uploading via (Nordic) nrfutil (for Nordic bootloader devices only)
127134
1. Install nrfutil if not already installed `pip install nrfutil`
128135
2. Select nrfutil as the firmware uploader in the tools menu.

boards.txt

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,92 @@ BT5040.menu.bootloader.none.bootloader.flags=-DUSE_NORDIC_BL
989989
BT5040.menu.bootloader.none.build.bootloader.file=
990990

991991

992+
# Nice Keyboards
993+
994+
nicenano_v2.name=nice!nano v2
995+
996+
nicenano_v2.vid.0=0x239A
997+
nicenano_v2.pid.0=0x80B3
998+
nicenano_v2.vid.1=0x239A
999+
nicenano_v2.pid.1=0x00B3
1000+
1001+
nicenano_v2.upload.tool=openocd
1002+
nicenano_v2.upload.target=nrf52
1003+
nicenano_v2.upload.jlink_device=nRF52840_xxAA
1004+
nicenano_v2.upload.use_1200bps_touch=true
1005+
nicenano_v2.upload.wait_for_upload_port=true
1006+
nicenano_v2.upload.maximum_size=1032192
1007+
nicenano_v2.upload.maximum_data_size=262144
1008+
nicenano_v2.bootloader.tool=openocd
1009+
1010+
nicenano_v2.build.dfu_pattern={adafruit-nrfutil.dfu_pattern}
1011+
nicenano_v2.build.uf2_family=0xADA52840
1012+
nicenano_v2.build.mcu=cortex-m4
1013+
nicenano_v2.build.f_cpu=64000000
1014+
nicenano_v2.build.board=NICE_NANO_V2
1015+
nicenano_v2.build.core=nRF5
1016+
nicenano_v2.build.variant=nice_nano_v2
1017+
nicenano_v2.build.variant_system_lib=
1018+
nicenano_v2.build.usb_manufacturer="Nice Keyboards"
1019+
nicenano_v2.build.usb_product="nice!nano v2"
1020+
nicenano_v2.build.extra_flags=-DNRF52_SERIES -DNRF52840_XXAA {bootloader.flags} {build.flags.usb} -DCONFIG_NIMBLE_CPP_LOG_LEVEL={build.cpp_debug} {build.nimble_flags}
1021+
nicenano_v2.build.ldscript=nrf52840_s140_v6_adabl.ld
1022+
nicenano_v2.build.lfclk_flags=-DUSE_LFXO
1023+
nicenano_v2.build.vid=0x239A
1024+
nicenano_v2.build.pid=0x80B3
1025+
nicenano_v2.build.float_flags=-mfloat-abi=hard -mfpu=fpv4-sp-d16
1026+
nicenano_v2.recipe.objcopy.uf2.pattern="{tools.uf2conv.cmd}" "{runtime.platform.path}/tools/uf2conv.py" -f {build.uf2_family} -o "{build.path}/{build.project_name}.uf2" "{build.path}/{build.project_name}.hex"
1027+
nicenano_v2.recipe.output.tmp_file={build.project_name}.uf2
1028+
nicenano_v2.recipe.output.save_file={build.project_name}.{build.variant}.uf2
1029+
1030+
nicenano_v2.menu.role.all=All
1031+
nicenano_v2.menu.role.all.build.nimble_flags=
1032+
nicenano_v2.menu.role.periph=Peripheral
1033+
nicenano_v2.menu.role.periph.build.nimble_flags=-DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED -DCONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED
1034+
nicenano_v2.menu.role.adv=Advertiser
1035+
nicenano_v2.menu.role.adv.build.nimble_flags=-DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED -DCONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED -DCONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED
1036+
nicenano_v2.menu.role.scan=Scanner
1037+
nicenano_v2.menu.role.scan.build.nimble_flags=-DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED -DCONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED -DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED
1038+
nicenano_v2.menu.role.central=Client
1039+
nicenano_v2.menu.role.central.build.nimble_flags=-DCONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED -DCONFIG_BT_NIMBLE_ROLE_BROADCASTER_DISABLED
1040+
1041+
nicenano_v2.menu.cppDbgLvl.none=None
1042+
nicenano_v2.menu.cppDbgLvl.none.build.cpp_debug=0
1043+
nicenano_v2.menu.cppDbgLvl.error=Error
1044+
nicenano_v2.menu.cppDbgLvl.error.build.cpp_debug=1
1045+
nicenano_v2.menu.cppDbgLvl.warn=Warn
1046+
nicenano_v2.menu.cppDbgLvl.warn.build.cpp_debug=2
1047+
nicenano_v2.menu.cppDbgLvl.info=Info
1048+
nicenano_v2.menu.cppDbgLvl.info.build.cpp_debug=3
1049+
nicenano_v2.menu.cppDbgLvl.debug=Debug
1050+
nicenano_v2.menu.cppDbgLvl.debug.build.cpp_debug=4
1051+
1052+
nicenano_v2.menu.bootloader.adafruit_ble=Adafruit BLE (s140 v6 softdevice)
1053+
nicenano_v2.menu.bootloader.adafruit_ble.bootloader.flags=-DUSE_ADA_BL -DADA_SD_BL
1054+
nicenano_v2.menu.bootloader.adafruit_ble.upload.tool=adafruit-nrfutil
1055+
nicenano_v2.menu.bootloader.adafruit_ble.upload.protocol=adafruit-nrfutil
1056+
nicenano_v2.menu.bootloader.adafruit_ble.upload.maximum_size=827392
1057+
nicenano_v2.menu.bootloader.adafruit_ble.upload.maximum_data_size=237568
1058+
nicenano_v2.menu.bootloader.adafruit_ble.build.dfu_pattern={adafruit-nrfutil.dfu_pattern}
1059+
nicenano_v2.menu.bootloader.adafruit_ble.build.bootloader.file=tools/nrf52840_s140_v6_adabl.hex
1060+
nicenano_v2.menu.bootloader.adafruit_ble.build.ldscript=nrf52840_s140_v6_adabl.ld
1061+
1062+
nicenano_v2.menu.bootloader.adafruit_serial=Adafruit Serial (no softdevice)
1063+
nicenano_v2.menu.bootloader.adafruit_serial.bootloader.flags=-DUSE_ADA_BL
1064+
nicenano_v2.menu.bootloader.adafruit_serial.upload.tool=adafruit-nrfutil
1065+
nicenano_v2.menu.bootloader.adafruit_serial.upload.protocol=adafruit-nrfutil
1066+
nicenano_v2.menu.bootloader.adafruit_serial.upload.maximum_size=978944
1067+
nicenano_v2.menu.bootloader.adafruit_serial.upload.maximum_data_size=262136
1068+
nicenano_v2.menu.bootloader.adafruit_serial.build.dfu_pattern={adafruit-nrfutil.dfu_pattern}
1069+
nicenano_v2.menu.bootloader.adafruit_serial.build.bootloader.file=tools/nrf52840_serial_adabl.hex
1070+
nicenano_v2.menu.bootloader.adafruit_serial.build.ldscript=nrf52840_serial_adabl.ld
1071+
1072+
nicenano_v2.menu.bootloader.none=No bootloader
1073+
nicenano_v2.menu.bootloader.none.bootloader.flags=
1074+
nicenano_v2.menu.bootloader.none.build.bootloader.file=tools/none_bootloader.hex
1075+
nicenano_v2.menu.bootloader.none.build.ldscript=nrf52840_xxaa.ld
1076+
1077+
9921078
# Seeedstudio
9931079

9941080
seeed52840sense.name=Seeed XIAO nRF52840 Sense

platform.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ adafruit-nrfutil.dfu_pattern="{tools.adafruit-nrfutil.cmd}" dfu genpkg --dev-typ
6161
none.dfu_pattern.cmd=no_dfu
6262
none.dfu_pattern.cmd.windows=no_dfu.bat
6363
none.dfu_pattern.path={runtime.platform.path}/tools
64+
tools.uf2conv.cmd=python3
65+
tools.uf2conv.cmd.windows=python
6466

6567
nordic.path={build.core.path}/nordic
6668

@@ -229,4 +231,4 @@ tools.adafruit-nrfutil.upload.pattern="{cmd}" {upload.verbose} dfu serial -pkg "
229231

230232
tools.adafruit-nrfutil.program.params.verbose=--verbose
231233
tools.adafruit-nrfutil.program.params.quiet=
232-
tools.adafruit-nrfutil.program.pattern="{cmd}" {program.verbose} dfu serial -pkg "{build.path}/{build.project_name}.zip" -p {serial.port} -b 115200 --singlebank
234+
tools.adafruit-nrfutil.program.pattern="{cmd}" {program.verbose} dfu serial -pkg "{build.path}/{build.project_name}.zip" -p {serial.port} -b 115200 --singlebank

tools/uf2conv.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import struct
5+
import sys
6+
7+
8+
UF2_MAGIC_START0 = 0x0A324655
9+
UF2_MAGIC_START1 = 0x9E5D5157
10+
UF2_MAGIC_END = 0x0AB16F30
11+
UF2_FLAG_FAMILY_ID_PRESENT = 0x00002000
12+
UF2_PAYLOAD_SIZE = 256
13+
14+
15+
def parse_int(value):
16+
return int(value, 0)
17+
18+
19+
def parse_hex_line(line, line_number):
20+
line = line.strip()
21+
if not line:
22+
return None
23+
if not line.startswith(":"):
24+
raise ValueError(f"line {line_number}: missing ':'")
25+
26+
try:
27+
raw = bytes.fromhex(line[1:])
28+
except ValueError as exc:
29+
raise ValueError(f"line {line_number}: invalid hex") from exc
30+
31+
if len(raw) < 5:
32+
raise ValueError(f"line {line_number}: record is too short")
33+
34+
count = raw[0]
35+
if len(raw) != count + 5:
36+
raise ValueError(f"line {line_number}: record length mismatch")
37+
38+
if (sum(raw) & 0xFF) != 0:
39+
raise ValueError(f"line {line_number}: checksum mismatch")
40+
41+
address = (raw[1] << 8) | raw[2]
42+
record_type = raw[3]
43+
payload = raw[4:4 + count]
44+
return record_type, address, payload
45+
46+
47+
def read_ihex(filename):
48+
data = {}
49+
upper_address = 0
50+
51+
with open(filename, "r", encoding="ascii") as hex_file:
52+
for line_number, line in enumerate(hex_file, 1):
53+
record = parse_hex_line(line, line_number)
54+
if record is None:
55+
continue
56+
57+
record_type, address, payload = record
58+
if record_type == 0x00:
59+
absolute = upper_address + address
60+
for offset, value in enumerate(payload):
61+
data[absolute + offset] = value
62+
elif record_type == 0x01:
63+
break
64+
elif record_type == 0x02:
65+
upper_address = int.from_bytes(payload, "big") << 4
66+
elif record_type == 0x04:
67+
upper_address = int.from_bytes(payload, "big") << 16
68+
elif record_type in (0x03, 0x05):
69+
continue
70+
else:
71+
raise ValueError(f"line {line_number}: unsupported record type {record_type:#x}")
72+
73+
if not data:
74+
raise ValueError("input HEX contains no data records")
75+
76+
return data
77+
78+
79+
def build_blocks(data):
80+
blocks = {}
81+
for address, value in data.items():
82+
block_address = address & ~(UF2_PAYLOAD_SIZE - 1)
83+
if block_address not in blocks:
84+
blocks[block_address] = bytearray([0xFF] * UF2_PAYLOAD_SIZE)
85+
blocks[block_address][address - block_address] = value
86+
87+
return sorted(blocks.items())
88+
89+
90+
def write_uf2(blocks, family_id, output):
91+
total = len(blocks)
92+
with open(output, "wb") as uf2_file:
93+
for index, (address, payload) in enumerate(blocks):
94+
header = struct.pack(
95+
"<IIIIIIII",
96+
UF2_MAGIC_START0,
97+
UF2_MAGIC_START1,
98+
UF2_FLAG_FAMILY_ID_PRESENT,
99+
address,
100+
UF2_PAYLOAD_SIZE,
101+
index,
102+
total,
103+
family_id,
104+
)
105+
block = header + bytes(payload)
106+
block += bytes(512 - len(block) - 4)
107+
block += struct.pack("<I", UF2_MAGIC_END)
108+
uf2_file.write(block)
109+
110+
111+
def main(argv):
112+
parser = argparse.ArgumentParser(description="Convert an Intel HEX file to UF2.")
113+
parser.add_argument("-f", "--family", type=parse_int, required=True, help="UF2 family ID")
114+
parser.add_argument("-o", "--output", required=True, help="output UF2 file")
115+
parser.add_argument("input", help="input Intel HEX file")
116+
args = parser.parse_args(argv)
117+
118+
data = read_ihex(args.input)
119+
blocks = build_blocks(data)
120+
write_uf2(blocks, args.family, args.output)
121+
print(f"Wrote {args.output} ({len(blocks)} UF2 blocks)")
122+
123+
124+
if __name__ == "__main__":
125+
main(sys.argv[1:])

variants/nice_nano_v2/variant.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include "variant.h"
2+
#include "wiring_constants.h"
3+
#include "wiring_digital.h"
4+
#include "nrf.h"
5+
6+
const uint32_t g_ADigitalPinMap[] =
7+
{
8+
// nice!nano v2 edge castellations
9+
8, // D0 is P0.08
10+
6, // D1 is P0.06
11+
17, // D2 is P0.17
12+
20, // D3 is P0.20
13+
22, // D4 is P0.22
14+
24, // D5 is P0.24
15+
32, // D6 is P1.00
16+
11, // D7 is P0.11
17+
36, // D8 is P1.04
18+
38, // D9 is P1.06
19+
9, // D10 is P0.09
20+
21+
// Exposed bottom pads
22+
33, // D11 is P1.01
23+
34, // D12 is P1.02
24+
39, // D13 is P1.07
25+
26+
// nice!nano v2 edge castellations
27+
43, // D14 is P1.11
28+
45, // D15 is P1.13
29+
10, // D16 is P0.10
30+
42, // D17 is P1.10
31+
47, // D18 is P1.15
32+
2, // D19 is P0.02 (AIN0)
33+
29, // D20 is P0.29 (AIN5)
34+
31, // D21 is P0.31 (AIN7)
35+
36+
15, // D22 is P0.15 (status LED)
37+
4, // D23 is P0.04 (battery voltage)
38+
13, // D24 is P0.13 (VCC cutoff)
39+
};
40+
41+
void initVariant()
42+
{
43+
pinMode(PIN_LED, OUTPUT);
44+
ledOff(PIN_LED);
45+
}

0 commit comments

Comments
 (0)