Исправлена обработка фронтов START/STOP в 166

Добавлены локальные копии команд и обновлены файлы прошивки. Сохранены анализ слабых мест START/STOP и тесты воспроизведения ошибки ограничения ЦАП проекта 167.
This commit is contained in:
2026-09-22 12:37:24 +03:00
parent 23a4157f1f
commit 085b587167
8 changed files with 753 additions and 391 deletions

105
tests/test_dac167_limit.py Normal file
View File

@@ -0,0 +1,105 @@
"""Host-side reproduction of 167 DAC endpoint logic; no firmware changes.
Run: py -3 -m unittest discover -s tests -p test_dac167_limit.py -v
The source guard ties the model to the inspected C block. These tests reproduce
the bug (passing means reproduced), not execution on a C2000 or physical DAC.
"""
import re
import unittest
from pathlib import Path
SOURCE = Path(__file__).resolve().parents[2] / '167/Source/Internal/DAC.c'
def endpoints(start, stop, corrected=False):
# Signed C2000 int subtraction must remain representable. Deliberately
# exclude inputs -32768..-32765, where subtracting 4 overflows signed int.
if not all(-32764 <= value <= 32767 for value in (start, stop)):
raise ValueError('Outside modeled, non-overflowing input range')
go = float(start - 4)
end = float(stop - 4)
if go < 0:
go = 0.0
if go > 5:
go = 5.0
if end > 16:
if corrected:
end = 16.0
else:
go = 16.0
if end <= go:
end = go + 1
return go, end
def ramp_code(go, end, count, duration=6000):
# Source: LOAD_TIME=30, DAC_FREQ=200. int conversion truncates.
# Only selected exactly representable / far-from-boundary cases tested.
value = (end - go) / 16.0 * count + go / 16.0 * duration
if not -32768 <= value < 32768:
raise ValueError('C2000 int conversion out of range')
return int(value)
class Dac167LimitTests(unittest.TestCase):
def test_source_matches_reproduced_block(self):
code = re.sub(r'\s+', '', SOURCE.read_bytes().decode('latin1'))
expected = '''dac_go=DAC_go-4; dac_stop=DAC_stop-4;
if(dac_go<0) dac_go=0;
if(dac_go>5) dac_go=5;
if(dac_stop>16) dac_go=16;
if(dac_stop<=dac_go) dac_stop=dac_go+1;'''
self.assertIn(re.sub(r'\s+', '', expected), code)
def test_defaults_and_threshold(self):
for stop, actual, fixed in [
(16, (2, 12), (2, 12)),
(19, (2, 15), (2, 15)),
(20, (2, 16), (2, 16)),
(21, (16, 17), (2, 16)),
(22, (16, 18), (2, 16)),
(30, (16, 26), (2, 16)),
]:
with self.subTest(stop=stop):
self.assertEqual(endpoints(6, stop), actual)
self.assertEqual(endpoints(6, stop, True), fixed)
def test_start_limit_is_overwritten(self):
for start in (-100, 0, 4, 6, 9, 10, 20, 100, 32767):
with self.subTest(start=start):
self.assertEqual(endpoints(start, 21), (16, 17))
self.assertLessEqual(endpoints(start, 21, True)[0], 5)
def test_full_nonnegative_stop_range(self):
# 327,680 input pairs. Negative stop values do not trigger end > 16.
for start in (-100, 0, 4, 6, 8, 9, 10, 20, 100, 32767):
for stop in range(32768):
actual = endpoints(start, stop)
fixed = endpoints(start, stop, True)
self.assertEqual(actual != fixed, stop > 20,
(start, stop, actual, fixed))
def test_first_and_last_ramp_codes(self):
actual = endpoints(6, 21)
fixed = endpoints(6, 21, True)
self.assertEqual(ramp_code(*actual, 1), 6000)
self.assertEqual(ramp_code(*fixed, 1), 750)
self.assertEqual(ramp_code(*actual, 6000), 6375)
self.assertEqual(ramp_code(*fixed, 6000), 6000)
def test_low_stop_guard_is_unchanged(self):
self.assertEqual(endpoints(6, 4), (2, 3))
self.assertEqual(endpoints(6, 4), endpoints(6, 4, True))
self.assertEqual(endpoints(20, 0), (5, 6))
def test_model_rejects_signed_subtraction_overflow(self):
for value in range(-32768, -32764):
with self.assertRaises(ValueError):
endpoints(value, 21)
with self.assertRaises(ValueError):
endpoints(6, value)
if __name__ == '__main__':
unittest.main(verbosity=2)