Skip to content

Commit 9a4bc05

Browse files
authored
Merge pull request #15 from newaetech/uf2_all
Uf2 all
2 parents f6a7b48 + a7e7cf6 commit 9a4bc05

32 files changed

+941
-289
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ if (CMAKE_C_COMPILER_ID STREQUAL "GNU")
3030
add_compile_options(-Wno-pointer-sign)
3131
add_compile_options(-Wno-discarded-qualifiers)
3232
add_compile_options(-Wno-missing-braces)
33+
add_compile_options(-Wno-unused-variable)
3334
endif()
3435

3536
# Hardware-specific examples in subdirectories:

Developer Notes.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
## Things This Firmware Does
2+
3+
### Handling Bitstreams/Firmware
4+
5+
This firmware enumerates as a FAT16 USB mass storage device and allows programming of both
6+
bitstreams to the onboard FPGA/SPI flash and UF2 files into the second SPI flash.
7+
8+
The most important function for that is `tud_msc_write10_cb()` in `msc_disk.c`, which is called
9+
when the PC writes data to the disk. Most of the drive is "fake" (i.e. the file can't be read after
10+
being written).
11+
12+
In practice, writes are typically the smaller of `CFG_TUD_MSC_EP_BUFSIZE` and the length of the remaining file.
13+
Writes to folder clusters (the file information), the file table, and the actual file data can be done in any order
14+
and this varies by operation system. For example, Windows will write file information before file data, but this
15+
is the opposite on Linux. Technically, data writes can be out of order, but this doesn't appear to happen in practice.
16+
17+
Various FAT utility functions (such as writing to files, getting file info, etc) are in `fat_util.c`
18+
and flash functions are in `flash_util.c`. A CRC32C is used to verify the data written to flash.
19+
20+
See `FAT Filesystem.md` for more information about the filesystem.
21+
22+
### Config File
23+
24+
This firmware includes a config file (`CONFIG.txt`) that appears in the root directory of the filesystem. Code for parsing
25+
this file is in `config.c`. Current options include SPI frequency and whether or not to save bitstreams to SPI flash.
26+
27+
### Logging
28+
29+
A logging file (`LOG.txt`) is also included.
30+
Different logging levels are available and can be selected with `-DDEBUG_LEVEL=N` where `N` is between 1 and 5. The levels available are:
31+
32+
1. Critical
33+
1. Error
34+
1. Warning
35+
1. Info
36+
1. Debug
37+
38+
with Info (4) being the default. Handling of the log file is done in `error.c` and `error.h`.
39+
40+
### Tests
41+
42+
Some basic tests can be run by creating testing firmware with cmake: `-DTESTING_BUILD=ON`,
43+
then running either `tests/test_linux.py` or `tests/test_windows.py`, depending on your platform.
44+
45+
Tests should be run after a fresh boot and nothing should be uploaded to the Sonata before running the tests.
46+
47+
Some tests are done via functions in `tests.c`, but others are done inline in the code. Tests will be recorded
48+
in `LOG.txt` and in the form `"TEST %lu %4s: " + STR` where `%lu` is an incrementing integer, `%4s` is either
49+
`"PASS"` or `"FAIL"` and `STR` gives additional context for the test.

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,38 @@
11
# RP2040 Firmware for Sonata Board
22

3+
## Usage
4+
5+
### Copying Bitstreams/Firmware
6+
7+
Once plugged in, the Sonata board should enumerate as a USB mass storage device. Bitstreams
8+
and Sonata firmware can both be programmed by copying their respective files to the mass
9+
storage device. Bitstreams and firmware must be in UF2 format with a maximum block size
10+
of 256 bytes and device IDs of `0x6ce29e6b` and `0x6ce29e60`, respectively. Only the data
11+
portion of the UF2 block is written to flash.
12+
13+
Note that the FPGA is currently erased during both firmware and bitstream programming,
14+
then reprogrammed afterwards.
15+
16+
### Flash Slots
17+
18+
Bitstream and firmware both have 3 flash slots, which can be selected via the 3 position
19+
Bitstream select switch by the USB port on the device. This controls both where
20+
bitstream/firmware are written to when programming, as well as where bitstreams
21+
are loaded from on device boot.
22+
23+
### Logging/Options
24+
25+
Important things such as firmware version, which slots have bitstreams/firmware, etc.
26+
is logged in LOG.TXT. Various options, such as programming speed and whether or not
27+
to write bitstreams to flash can be modified in OPTIONS.TXT.
28+
29+
### Reprogramming/Updating Sonata
30+
31+
The Sonata's firmware can be erased by holding down SW9 while plugging in the USB, after
32+
which the board should enumerate as a different USB mass storage device. New firmware is
33+
avilable in UF2 format and can be programmed by copying the UF2 file into the drive, similar
34+
to how bitstream and firmware programming works on the Sonata board.
35+
336
## Getting started
437

538
### Linux Setup
@@ -30,6 +63,18 @@ cd build
3063
PICO_SDK_PATH=/path-to/pico-sdk cmake ..
3164
make # or ninja if on Windows
3265
```
66+
## Logging
67+
68+
Different logging levels are available and can be selected with `-DDEBUG_LEVEL=N` where `N` is
69+
between 1 and 5. The levels available are:
70+
71+
1. Critical
72+
1. Error
73+
1. Warning
74+
1. Info
75+
1. Debug
76+
77+
with Info (4) being the default. Logs are written to `LOG.txt`.
3378

3479
## Debugging
3580

@@ -50,6 +95,13 @@ cd build
5095
arm-none-eabi-gdb usb_msc/usb_msc.elf -ex "target extended-remote localhost:3333" -ex "load" -ex "monitor reset init"
5196
```
5297

98+
## Testing
99+
100+
Some basic tests can be run by creating testing firmware with cmake: `-DTESTING_BUILD=ON`,
101+
then running either `tests/test_linux.py` or `tests/test_windows.py`, depending on your platform.
102+
103+
Tests should be run after a fresh boot and nothing should be uploaded to the Sonata before running the tests.
104+
53105
## Notes
54106

55107
### Windows Quirks

tests/bitstream.bit.uf2

3.06 MB
Binary file not shown.

tests/firmware.elf.uf2

141 KB
Binary file not shown.

tests/generate_crc.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from test_common import xorshift, xor_fill_buf, gen_xorshift_crc32
2+
from crc32c import crc32c
3+
4+
sums = gen_xorshift_crc32()
5+
print("xorshift gen crc32: " + ", ".join("0x{:04X}".format(sum) for sum in sums))
6+
7+
files = ["sonata_top_lrlcd.bit", "sonata-exceptions.bit", "sonata.bit", "usb_msc.uf2"]
8+
for file in files:
9+
with open(file, "rb") as f:
10+
data = f.read()
11+
crc = crc32c(data)
12+
print("Crc for {} is 0x{:04X}".format(file, crc))

tests/test_common.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ def xor_fill_buf(buf, seed):
2525

2626
return buf
2727

28+
def clear_log_file(sonata_path):
29+
with open(sonata_path + "/LOG.TXT", "w") as f:
30+
f.close()
31+
2832
def get_windows_drive_letter():
2933
import wmi
3034
sonata_letter = None
@@ -69,13 +73,17 @@ def gen_xorshift_crc32(seeds=[0xDEADBEEF, 0x11223344, 0xF0F0A7A7]):
6973
"
7074

7175
def copy_sonata_bitstream(sonata_path):
72-
shutil.copyfile("./sonata.bit", sonata_path + "/sonata.bit")
76+
shutil.copyfile("./bitstream.bit.uf2", sonata_path + "/bs.uf2")
77+
78+
def copy_firmware(sonata_path):
79+
shutil.copyfile("./firmware.elf.uf2", sonata_path + "/fw.uf2")
7380

7481
def write_options(sonata_path, string):
75-
option_file = open(sonata_path + "/OPTIONS.txt", "w")
82+
option_file = open(sonata_path + "/OPTIONS.TXT", "w")
7683
option_file.write(string)
7784
logging.debug("Writing")
7885
logging.debug(string)
86+
option_file.flush() # shouldn't be necessary
7987
option_file.close()
8088

8189
def write_all_options(sonata_path):
@@ -88,7 +96,7 @@ def write_all_options(sonata_path):
8896

8997
def get_test_results(sonata_path):
9098
results = []
91-
option_file = open(sonata_path + "/LOG.txt", "r")
99+
option_file = open(sonata_path + "/LOG.TXT", "r")
92100
string = option_file.read()
93101
option_file.close()
94102

@@ -111,4 +119,14 @@ def test_passed(test_string):
111119

112120
def get_test_name(test_string):
113121
strs = test_string.split(':')
114-
return strs[-1]
122+
return strs[-1]
123+
124+
def get_test_names():
125+
names = []
126+
with open("../usb_msc/test_names.h") as name_file:
127+
data = name_file.read()
128+
for line in data.split('\n'):
129+
if "#define" in line:
130+
name = line.split(" ")[-1]
131+
print(name)
132+
pass

tests/test_linux.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ def get_sonata_path():
1212
result = subprocess.run(['blkid', '-l', '-o', 'device', '-t',
1313
'LABEL=SONATA', '-c', '/dev/null'],
1414
capture_output=True)
15-
mnt = result.stdout().decode().strip()
15+
mnt = result.stdout.decode().strip()
1616
result = subprocess.run(['findmnt', mnt, '-no', 'TARGET'], capture_output=True)
17-
return result.stdout().decode().strip()
17+
return result.stdout.decode().strip()
1818

1919

20-
sonata_path = "../../sonata" #TODO: make this better
20+
#sonata_path = "../../sonata" #TODO: make this better
21+
sonata_path = get_sonata_path()
22+
os.sync()
2123
print("Copying sonata.bit...")
2224
copy_sonata_bitstream(sonata_path)
2325
os.sync() #ensure above write finishes
@@ -30,6 +32,11 @@ def get_sonata_path():
3032
print("Done. Ejecting drive...")
3133
sleep(0.25)
3234

35+
print("Copying firmware")
36+
copy_firmware(sonata_path)
37+
os.sync()
38+
39+
3340
results, full_file = get_test_results(sonata_path)
3441
intended_failures = (3, 4) # these ones check to make sure invalid
3542
any_failures = 0
@@ -38,12 +45,16 @@ def get_sonata_path():
3845
if result['passed']:
3946
print("Passed test that should have failed {}".format(str(result)))
4047
any_failures = 1
48+
else:
49+
print("Passed test {}".format(str(result['name'])))
4150
else:
4251
if not result['passed']:
4352
print("Failed test {}".format(str(result)))
4453
any_failures = 1
54+
else:
55+
print("Passed test {}".format(str(result['name'])))
4556

46-
with open('LOG.txt', "w") as f:
57+
with open('LOG.TXT', "w") as f:
4758
f.write(str(full_file))
4859

4960
if not any_failures:

tests/test_windows.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,47 @@
11
import numpy as np
2-
from time import sleep
2+
from time import sleep, perf_counter
33
from test_common import *
44
import logging
55

66
# shutil.copyfile("")
77
# logging.getLogger().setLevel(logging.DEBUG)
88
# ps_run("./eject.ps1")
9+
10+
test_names = get_test_names()
11+
912
logging.basicConfig(filename="log.txt", level=logging.DEBUG, filemode='w',
1013
format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s',
1114
datefmt='%H:%M:%S',)
1215
sonata_letter = get_windows_drive_letter()
1316

17+
1418
if not sonata_letter:
1519
print("Sonata drive not found")
1620
exit(1)
1721

1822
print("Sonata at " + sonata_letter)
23+
print("Clearing log file...")
24+
clear_log_file(sonata_letter)
1925
print("Copying sonata.bit...")
26+
t0 = perf_counter()
2027
copy_sonata_bitstream(sonata_letter)
28+
t1 = perf_counter()
29+
total = t1 - t0
2130

22-
print ("Done copy. Testing config write and parse...")
31+
print ("Done copy, took {}. Testing config write and parse...".format(total))
2332

2433
write_all_options(sonata_letter)
2534

2635
print("Done. Ejecting drive...")
2736
sleep(0.25)
2837
win_eject_drive()
2938

39+
print("Copying firmware")
40+
copy_firmware(sonata_letter)
41+
print("Done. Ejecting drive...")
42+
sleep(0.25)
43+
win_eject_drive()
44+
3045
results, full_file = get_test_results(sonata_letter)
3146
intended_failures = (3, 4) # these ones check to make sure invalid
3247
any_failures = 0
@@ -35,10 +50,14 @@
3550
if result['passed']:
3651
print("Passed test that should have failed {}".format(str(result)))
3752
any_failures = 1
53+
else:
54+
print("Passed test {}".format(str(result['name'])))
3855
else:
3956
if not result['passed']:
4057
print("Failed test {}".format(str(result)))
4158
any_failures = 1
59+
else:
60+
print("Passed test {}".format(str(result['name'])))
4261

4362
with open('LOG.txt', "w") as f:
4463
f.write(str(full_file))

tests/uf2_calc_crc.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from crc32c import crc32c
2+
3+
with open("usb_msc.uf2", "rb") as f:
4+
data = f.read()
5+
# print(data)
6+
crc = crc32c(data)
7+
print(hex(crc))

0 commit comments

Comments
 (0)