Fix RTC synchronization after sleep, add many more tests
There were two issues with the tests 1. Incorrect print formats causing incorrect output. 2. The RTC driver was not waiting for the shadow registers to be updated after sleeping, their reset values to be read.
This commit is contained in:
25
test/Dockerfile
Normal file
25
test/Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
FROM ubuntu:20.04
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get upgrade -y && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
curl \
|
||||
sigrok-cli \
|
||||
python3 \
|
||||
python3-pip \
|
||||
libncurses5 \
|
||||
&& \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN cd /tmp && \
|
||||
curl 'https://www.segger.com/downloads/jlink/JLink_Linux_x86_64.deb' \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
--data 'accept_license_agreement=accepted&submit=Download+software' \
|
||||
--output JLink_Linux_x86_64.deb && \
|
||||
dpkg -i ./JLink_Linux_x86_64.deb && \
|
||||
rm ./JLink_Linux_x86_64.deb
|
||||
|
||||
RUN pip3 install \
|
||||
pytest \
|
||||
pylink-square \
|
||||
pyserial \
|
||||
33
test/README.md
Normal file
33
test/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
This directory contains the test code and other requirements in order to run the hardware integration tests for the Timely Reference.
|
||||
|
||||
The Docker image uses SEGGER's JLink software tools, and requires accepting their [license](https://www.segger.com/downloads/jlink/JLink_Linux_x86_64.deb). This may be swapped out for [OpenOCD](https://sourceforge.net/p/openocd/code/ci/master/tree/) in the future.
|
||||
|
||||
Of course, the tools can also be installed directly into the host- Docker provides a (more) consistent runtime environment and forces documentation of setup steps.
|
||||
|
||||
### Hardware Requirements
|
||||
|
||||
Current:
|
||||
|
||||
- STMicroelectronics [Nucleo-L412-RB-P](https://www.st.com/en/evaluation-tools/nucleo-l412rb-p.html)
|
||||
- SEGGER [J-Link](https://www.segger.com/products/debug-probes/j-link/) (Any model should do)
|
||||
- FTDI [FT232R USB-Serial Breakout](https://www.ftdichip.com/Products/ICs/FT232R.htm). Tested with [this one from Amazon](https://www.amazon.com/gp/product/B075N82CDL)
|
||||
- Sigrok-supported [FX2 logic analyzer](https://sigrok.org/wiki/Fx2lafw#Cypress_FX2_vs._FX2LP). Tested with [Lcsoft Mini Board](https://sigrok.org/wiki/Lcsoft_Mini_Board) and another no-name board. Other Sigrok-supported could be supported easily.
|
||||
|
||||
The current hardware detection mechanisms may fail if more than one these devices is connected.
|
||||
|
||||
### Execution
|
||||
|
||||
First, build the docker image. Depending on your execution environment, you may need to escalate privileges in order to run `docker`.
|
||||
|
||||
```
|
||||
cd <project-root>
|
||||
docker build -t tr-test test/
|
||||
```
|
||||
|
||||
Then, execute the tests. The tests require access to the USB hardware components, so their files need to be volume-mounted in, and the container must be given privilege in order to access the devices.
|
||||
|
||||
```
|
||||
docker run --privileged -it -v /dev/bus/usb:/dev/bus/usb -v $(pwd):/build-dir tr-test /build-dir/test/src/tr_test/test.py
|
||||
```
|
||||
|
||||
The tests also accept other arguments accepted by [pytest](https://docs.pytest.org/en/latest/usage.html).
|
||||
@@ -20,7 +20,6 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
import pytest
|
||||
import subprocess
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
import logging
|
||||
@@ -29,6 +28,7 @@ import time
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
PYTEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
DEFAULT_FW_DIR = os.path.abspath(PYTEST_DIR + "../../../../firmware/")
|
||||
@@ -49,19 +49,18 @@ def logger():
|
||||
|
||||
@pytest.fixture
|
||||
def context_factory():
|
||||
def create_context(
|
||||
fw_rel_path: str,
|
||||
mcu: str = "STM32L412RB",
|
||||
addr: int = 0x8000000,
|
||||
leave_halted: bool = False,
|
||||
):
|
||||
ports = [
|
||||
p
|
||||
for p in serial.tools.list_ports.comports()
|
||||
if p.product == "FT232R USB UART"
|
||||
]
|
||||
|
||||
def create_context(fw_rel_path: str,
|
||||
mcu: str = "STM32L412RB",
|
||||
addr: int = 0x8000000,
|
||||
leave_halted: bool = False):
|
||||
|
||||
proc = subprocess.run(["make", "BOARD=devboard", fw_rel_path],
|
||||
cwd=DEFAULT_FW_DIR,
|
||||
capture_output=True,
|
||||
check=True)
|
||||
logging.info("Make process stdout: {}".format(proc.stdout))
|
||||
|
||||
ports = serial.tools.list_ports.comports()
|
||||
if len(ports) == 0:
|
||||
raise RuntimeError("No serial devices found")
|
||||
if len(ports) > 1:
|
||||
@@ -82,9 +81,7 @@ def context_factory():
|
||||
assert jlink.halted()
|
||||
jlink.reset(halt=True)
|
||||
|
||||
serial_dev = serial.Serial(port=ports[0].device,
|
||||
baudrate=115200,
|
||||
timeout=1)
|
||||
serial_dev = serial.Serial(port=ports[0].device, baudrate=115200, timeout=2)
|
||||
if leave_halted:
|
||||
return serial_dev, jlink
|
||||
|
||||
@@ -93,15 +90,15 @@ def context_factory():
|
||||
while True:
|
||||
try:
|
||||
logging.info("Waiting for firmware to start...")
|
||||
assert serial_dev \
|
||||
.read_until(TEST_START_TEXT) \
|
||||
.endswith(TEST_START_TEXT), \
|
||||
"Timed out starting test firmware application"
|
||||
assert serial_dev.read_until(TEST_START_TEXT).endswith(
|
||||
TEST_START_TEXT
|
||||
), "Timed out starting test firmware application"
|
||||
logging.debug("Test execution started")
|
||||
except serial.serialutil.SerialException:
|
||||
continue
|
||||
break
|
||||
return serial_dev, jlink
|
||||
|
||||
return create_context
|
||||
|
||||
|
||||
@@ -131,15 +128,28 @@ def test_meta_nostart(context_factory, logger):
|
||||
|
||||
|
||||
def test_watch(context_factory, logger):
|
||||
serial_dev, jlink = context_factory("Application/main.bin",
|
||||
leave_halted=True)
|
||||
serial_dev, jlink = context_factory("Application/main.bin", leave_halted=True)
|
||||
jlink.reset(halt=False)
|
||||
|
||||
|
||||
def test_set_time(context_factory, logger):
|
||||
serial_dev, jlink = context_factory("Test/set_time.bin")
|
||||
text = serial_dev.read_until(TEST_PASS_TEXT)
|
||||
print("Text:", text.decode())
|
||||
assert text.endswith(TEST_PASS_TEXT)
|
||||
|
||||
def test_periodic_alarms(context_factory, logger):
|
||||
serial_dev, jlink = context_factory("Test/periodic_alarms.bin")
|
||||
serial_dev.timeout = 6
|
||||
text = serial_dev.read_until(TEST_PASS_TEXT)
|
||||
print("Text:", text.decode())
|
||||
assert text.endswith(TEST_PASS_TEXT)
|
||||
|
||||
|
||||
def test_clock(context_factory, logger):
|
||||
serial_dev, jlink = context_factory("Test/clock.bin")
|
||||
EXPECTED_RUNTIME = 10
|
||||
TOLERANCE = .1
|
||||
TOLERANCE = 0.1
|
||||
|
||||
serial_dev.timeout = EXPECTED_RUNTIME * 1.2
|
||||
|
||||
@@ -148,23 +158,53 @@ def test_clock(context_factory, logger):
|
||||
|
||||
start_text = serial_dev.read_until(START_MARKER)
|
||||
start = time.monotonic()
|
||||
print("Start text:", start_text)
|
||||
assert start_text.endswith(START_MARKER)
|
||||
|
||||
end_text = serial_dev.read_until(END_MARKER)
|
||||
end = time.monotonic()
|
||||
print("Time:", end - start)
|
||||
assert end_text.endswith(END_MARKER)
|
||||
|
||||
delta = end - start
|
||||
logger.debug("Serial text: {}".format(start_text))
|
||||
logger.debug("Serial text: {}".format(end_text))
|
||||
logger.info("Serial text: {}".format(start_text))
|
||||
logger.info("Serial text: {}".format(end_text))
|
||||
logger.info("Start time: {}".format(start))
|
||||
logger.info("End time: {}".format(end))
|
||||
logger.info("Delta time: {}".format(delta))
|
||||
|
||||
# TODO: Using a single pin, instead of UART, would make this more
|
||||
# accurate. Add support via sigrok.
|
||||
assert start_text.endswith(START_MARKER)
|
||||
assert end_text.endswith(END_MARKER)
|
||||
assert (delta - EXPECTED_RUNTIME) < TOLERANCE
|
||||
|
||||
|
||||
def test_wakeup_irq(context_factory, logger):
|
||||
serial_dev, jlink = context_factory("Test/wakeup_irq.bin")
|
||||
serial_dev.timeout = 65
|
||||
|
||||
pattern = re.compile("Requested=(\\d*) Actual=(\\d*) Wakeups=(\\d*)")
|
||||
while True:
|
||||
line = serial_dev.readline()
|
||||
if line == TEST_PASS_TEXT:
|
||||
break
|
||||
|
||||
line = line.decode("ascii", errors="ignore")
|
||||
print(line.strip())
|
||||
match = pattern.match(line)
|
||||
assert match
|
||||
req = int(match.group(1))
|
||||
actual = int(match.group(2))
|
||||
wakeups = int(match.group(3))
|
||||
delta = req - actual
|
||||
|
||||
assert wakeups == 1, "Expected one wakeup per line of test output"
|
||||
if req < 32000:
|
||||
assert abs(delta < req * (2.0 / 100.0)) or (delta <= 1)
|
||||
else:
|
||||
# Delays > 32sec have reduced resolution (1 sec)
|
||||
assert abs(delta) < 1000
|
||||
|
||||
|
||||
def test_stop(context_factory, logger):
|
||||
serial_dev, jlink = context_factory("Test/stop.bin")
|
||||
serial_dev.timeout = 65
|
||||
@@ -189,6 +229,70 @@ def test_stop(context_factory, logger):
|
||||
# Delays > 32sec have reduced resolution (1 sec)
|
||||
assert abs(delta) < 1000
|
||||
|
||||
"sigrok-cli -C D3 -d fx2lafw -c samplerate=1M --time 1s -P timing:data=D3"
|
||||
|
||||
|
||||
def measure_frequency(
|
||||
period: float,
|
||||
pin_name: str,
|
||||
executable: str = "sigrok-cli",
|
||||
driver_name: str = "fx2lafw",
|
||||
):
|
||||
|
||||
cmd = [
|
||||
executable,
|
||||
"-C",
|
||||
pin_name,
|
||||
"-d",
|
||||
driver_name,
|
||||
"-c",
|
||||
"samplerate=1M",
|
||||
"--time",
|
||||
"{}ms".format(int(period * 1000)),
|
||||
"-P",
|
||||
"timing:data={}".format(pin_name),
|
||||
"-A",
|
||||
"timing=time",
|
||||
]
|
||||
|
||||
print("sigrok-cli cmd {}".format(cmd))
|
||||
proc = subprocess.run(cmd, capture_output=True, check=True)
|
||||
lines = proc.stdout.decode("utf-8").split("\n")
|
||||
reg = re.compile(".*:\\W(\\d+.\\d+)\\W(\\w+)")
|
||||
periods = []
|
||||
for line in lines:
|
||||
m = reg.match(line)
|
||||
if not m:
|
||||
break
|
||||
num = float(m.groups(1)[0])
|
||||
units = m.groups(1)[1]
|
||||
if units == "ms":
|
||||
periods.append(num / 1000)
|
||||
elif units == "μs":
|
||||
periods.append(num / 1000000)
|
||||
else:
|
||||
assert False
|
||||
|
||||
return periods[::2], periods[1:][::2]
|
||||
|
||||
def test_lptim(context_factory, logger):
|
||||
serial_dev, jlink = context_factory("Test/lptim.bin")
|
||||
state0_periods, state1_periods = measure_frequency(1, "D1")
|
||||
num_periods = min(len(state0_periods), len(state1_periods))
|
||||
periods = [state0_periods[i] + state1_periods[i] for i in range(num_periods)]
|
||||
freqs = list(map(lambda x: 1 / x, periods))
|
||||
assert (
|
||||
periods
|
||||
), "No LPTIM changes detected, is the right analyzer being used? Is the device connected?"
|
||||
|
||||
min_f = min(freqs)
|
||||
max_f = max(freqs)
|
||||
avg_f = sum(freqs) / len(freqs)
|
||||
print("min:{}, max:{}, avg:{}".format(min_f, max_f, avg_f))
|
||||
assert abs(avg_f - 50) < 0.25
|
||||
assert min_f > 49
|
||||
assert max_f < 51
|
||||
|
||||
|
||||
def main():
|
||||
pytest.main(sys.argv)
|
||||
|
||||
Reference in New Issue
Block a user