feat(tms2812): добавь общий протокол терминала и привязки Python и Android
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
package ru.setcorp.setflash.core
|
||||
|
||||
import ru.setcorp.setprotocol.NativeSetProtocol
|
||||
|
||||
/** Shared templates facade for BALZAM TMS320F2812; framing and CRC use C99. */
|
||||
object Tms2812Protocol {
|
||||
const val CMD_LOAD = 51
|
||||
const val CMD_UPLOAD = 52
|
||||
const val CMD_RUN = 53
|
||||
const val CMD_XFLASH = 54
|
||||
const val CMD_TFLASH = 55
|
||||
const val CMD_PEEK = 56
|
||||
const val CMD_POKE = 57
|
||||
const val CMD_INITLOAD = 58
|
||||
const val CMD_INIT = 59
|
||||
const val CMD_EXTEND = 60
|
||||
const val CMD_VECTOR = 61
|
||||
const val CMD_IMPULSE = 62
|
||||
const val CMD_STD = 65
|
||||
const val CMD_TEST_ALL = 66
|
||||
|
||||
// BALZAM CHIEF bios.c returns 0x0055 at ADR_FOR_SPECIAL without reading
|
||||
// the peripheral bus. Address 0 goes through read_bus_special(), whose
|
||||
// failure suppresses the reply entirely, so it is unsuitable for PING.
|
||||
const val PING_MEMORY_ADDRESS = 0x00000100L
|
||||
|
||||
// extendbios() operation selectors from BALZAM_167/Src/mybalzam_7/bios.c.
|
||||
const val EXTEND_FLASH_REMOTE_EEPROM = 6
|
||||
const val EXTEND_READ_REMOTE_EEPROM = 7
|
||||
const val EXTEND_RESET_TO_INTERNAL_FLASH = 9
|
||||
const val EXTEND_FLASH_SPARTAN6 = 10
|
||||
const val EXTEND_VERIFY_REMOTE_EEPROM = 17
|
||||
|
||||
const val ACK_SIZE = 6
|
||||
const val PEEK_RESPONSE_SIZE = 10
|
||||
const val STD_RESPONSE_SIZE = 112
|
||||
const val UPLOAD_RESPONSE_OVERHEAD = 8
|
||||
const val EXTEND_RESPONSE_SIZE = 18
|
||||
|
||||
data class StandardResponse(
|
||||
val digital: ByteArray,
|
||||
val analog: List<Int>,
|
||||
)
|
||||
|
||||
data class ExtendResponse(
|
||||
val processedWords: Long,
|
||||
val errorCode: Long,
|
||||
val repeatErrors: Long,
|
||||
)
|
||||
|
||||
fun crc16(data: ByteArray, initial: Int = 0xFFFF): Int {
|
||||
require(initial == 0xFFFF) {
|
||||
"Произвольное начальное значение CRC не входит в протокол ПМ67"
|
||||
}
|
||||
requireNative()
|
||||
return NativeSetProtocol.nativeTms2812Crc16(data)
|
||||
}
|
||||
|
||||
fun initLoad(address: Int, ramAddress: Long, length: Int): ByteArray = fixed(
|
||||
address,
|
||||
CMD_INITLOAD,
|
||||
le32(ramAddress) + le32(length.toLong()),
|
||||
12,
|
||||
)
|
||||
|
||||
/**
|
||||
* LOAD is address + command, followed by raw data and CRC. RS485.c consumes
|
||||
* the first two bytes as a normal header, switches RS_FlagBegin off for
|
||||
* CMD_LOAD, and stores the remaining Length + 2 bytes in RAM.
|
||||
*/
|
||||
fun loadData(address: Int, image: ByteArray): ByteArray {
|
||||
return fixed(address, CMD_LOAD, image, image.size + 4)
|
||||
}
|
||||
|
||||
fun tFlash(address: Int, ramAddress: Long, flashAddress: Long, length: Int): ByteArray = fixed(
|
||||
address,
|
||||
CMD_TFLASH,
|
||||
le32(ramAddress) + le32(flashAddress) + le32(length.toLong()),
|
||||
16,
|
||||
)
|
||||
|
||||
fun upload(address: Int, memoryAddress: Long, length: Int): ByteArray {
|
||||
require(address in 0..255) { "Адрес контроллера должен быть 0…255" }
|
||||
require(memoryAddress in 0..0xFFFF_FFFFL) { "Адрес слова вне диапазона uint32" }
|
||||
require(length > 0) { "Размер блока должен быть положительным" }
|
||||
requireNative()
|
||||
return requireNotNull(
|
||||
NativeSetProtocol.nativeTms2812BuildUpload(
|
||||
address, memoryAddress, length.toLong(),
|
||||
),
|
||||
) { "SETProtocol отклонил CMD_UPLOAD ПМ67" }
|
||||
}
|
||||
|
||||
/**
|
||||
* bios.c sends UPLOAD as address, command, packed bytes, CRC16 and four
|
||||
* trailing bytes. Unlike ordinary replies its CRC immediately follows data.
|
||||
*/
|
||||
fun validateUploadResponse(packet: ByteArray, address: Int, length: Int): Boolean {
|
||||
if (length <= 0 || address !in 0..255) return false
|
||||
requireNative()
|
||||
return NativeSetProtocol.nativeTms2812ValidateUpload(packet, address, length)
|
||||
}
|
||||
|
||||
fun uploadData(packet: ByteArray, address: Int, length: Int): ByteArray {
|
||||
requireNative()
|
||||
return requireNotNull(
|
||||
NativeSetProtocol.nativeTms2812DecodeUpload(packet, address, length),
|
||||
) { "Поврежден ответ CMD_UPLOAD" }
|
||||
}
|
||||
|
||||
fun peek(address: Int, memoryAddress: Long): ByteArray =
|
||||
memory(address, CMD_PEEK, memoryAddress, 0)
|
||||
|
||||
fun ping(address: Int): ByteArray = memory(address, 0, 0, 0)
|
||||
|
||||
fun poke(address: Int, memoryAddress: Long, value: Int): ByteArray =
|
||||
memory(address, CMD_POKE, memoryAddress, value)
|
||||
|
||||
private fun memory(address: Int, command: Int, memoryAddress: Long, value: Int): ByteArray {
|
||||
require(address in 0..255 && memoryAddress in 0..0xFFFF_FFFFL && value in 0..65535) {
|
||||
"Адрес или значение TMS2812 вне диапазона"
|
||||
}
|
||||
requireNative()
|
||||
return requireNotNull(NativeSetProtocol.nativeTms2812BuildMemory(address, command, memoryAddress, value))
|
||||
}
|
||||
|
||||
fun testAll(address: Int, duration: Int, pause: Int, digits: ByteArray): ByteArray {
|
||||
require(address in 0..255 && duration in 0..65535 && pause in 0..65535 && digits.size == 12)
|
||||
requireNative()
|
||||
return requireNotNull(NativeSetProtocol.nativeTms2812BuildTest(address, duration, pause, digits))
|
||||
}
|
||||
|
||||
fun extend(
|
||||
address: Int,
|
||||
firstAddress: Long,
|
||||
secondAddress: Long,
|
||||
length: Long,
|
||||
code1: Int,
|
||||
code2: Int,
|
||||
): ByteArray = fixed(
|
||||
address,
|
||||
CMD_EXTEND,
|
||||
le32(firstAddress) + le32(secondAddress) + le32(length) + byteArrayOf(code1.toByte(), code2.toByte()),
|
||||
18,
|
||||
)
|
||||
|
||||
fun parseExtendResponse(packet: ByteArray, address: Int): ExtendResponse {
|
||||
require(packet.size == EXTEND_RESPONSE_SIZE && validateResponse(packet, address, CMD_EXTEND)) {
|
||||
"Поврежден ответ CMD_EXTEND"
|
||||
}
|
||||
return ExtendResponse(
|
||||
processedWords = le32Value(packet, 2),
|
||||
errorCode = le32Value(packet, 6),
|
||||
repeatErrors = le32Value(packet, 10),
|
||||
)
|
||||
}
|
||||
|
||||
fun standard(address: Int, analog: List<Int>, flags: Set<Int>): ByteArray {
|
||||
require(analog.size == 4) { "CMD_STD должен содержать четыре аналоговых значения" }
|
||||
val payload = ByteArray(14)
|
||||
analog.forEachIndexed { index, value ->
|
||||
require(value in Short.MIN_VALUE..Short.MAX_VALUE) { "Аналог ${index + 1} вне диапазона Int16" }
|
||||
payload[index * 2] = value.toByte()
|
||||
payload[index * 2 + 1] = (value ushr 8).toByte()
|
||||
}
|
||||
flags.forEach { oneBased ->
|
||||
require(oneBased in 1..48) { "Номер флага должен быть от 1 до 48" }
|
||||
val bit = oneBased - 1
|
||||
payload[8 + bit / 8] = (payload[8 + bit / 8].toInt() or (1 shl (bit % 8))).toByte()
|
||||
}
|
||||
return fixed(address, CMD_STD, payload, 18)
|
||||
}
|
||||
|
||||
fun validateResponse(packet: ByteArray, address: Int, command: Int): Boolean {
|
||||
requireNative()
|
||||
return NativeSetProtocol.nativeTms2812ValidateResponse(packet, address, command, packet.size, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* BALZAM replies can omit final reserved bytes after CRC (two in ordinary
|
||||
* replies, four in UPLOAD data replies).
|
||||
* Call after the receive settle interval so a delayed tail cannot overlap
|
||||
* the next request. Never pad missing data or CRC bytes. UPLOAD uses its
|
||||
* own validator because its CRC precedes four trailing bytes, not two.
|
||||
*/
|
||||
fun normalizeResponseTail(
|
||||
packet: ByteArray,
|
||||
address: Int,
|
||||
command: Int,
|
||||
fullSize: Int,
|
||||
): ByteArray? {
|
||||
val uploadData = command == CMD_UPLOAD && fullSize >= UPLOAD_RESPONSE_OVERHEAD
|
||||
val reservedBytes = if (uploadData) 4 else 2
|
||||
if (fullSize < ACK_SIZE || packet.size !in (fullSize - reservedBytes)..fullSize) return null
|
||||
val normalized = packet.copyOf(fullSize)
|
||||
val valid = if (uploadData) {
|
||||
validateUploadResponse(normalized, address, fullSize - UPLOAD_RESPONSE_OVERHEAD)
|
||||
} else {
|
||||
requireNative()
|
||||
NativeSetProtocol.nativeTms2812ValidateResponse(packet, address, command, fullSize, true)
|
||||
}
|
||||
return normalized.takeIf { valid }
|
||||
}
|
||||
|
||||
fun peekValue(packet: ByteArray, address: Int): Int {
|
||||
require(packet.size == PEEK_RESPONSE_SIZE && validateResponse(packet, address, CMD_PEEK)) {
|
||||
"Поврежден ответ CMD_PEEK"
|
||||
}
|
||||
return (packet[2].toInt() and 0xFF) or ((packet[3].toInt() and 0xFF) shl 8)
|
||||
}
|
||||
|
||||
fun parseStandard(packet: ByteArray, address: Int): StandardResponse {
|
||||
require(packet.size == STD_RESPONSE_SIZE && validateResponse(packet, address, CMD_STD)) {
|
||||
"Поврежден ответ CMD_STD"
|
||||
}
|
||||
val digital = packet.copyOfRange(2, 40)
|
||||
val analog = (0 until 34).map { index ->
|
||||
val offset = 40 + index * 2
|
||||
(((packet[offset].toInt() and 0xFF) or
|
||||
((packet[offset + 1].toInt() and 0xFF) shl 8)).toShort()).toInt()
|
||||
}
|
||||
return StandardResponse(digital, analog)
|
||||
}
|
||||
|
||||
fun commandName(command: Int): String = when (command) {
|
||||
CMD_LOAD -> "CMD_LOAD"
|
||||
CMD_UPLOAD -> "CMD_UPLOAD"
|
||||
CMD_RUN -> "CMD_RUN"
|
||||
CMD_XFLASH -> "CMD_XFLASH"
|
||||
CMD_TFLASH -> "CMD_TFLASH"
|
||||
CMD_PEEK -> "CMD_PEEK"
|
||||
CMD_POKE -> "CMD_POKE"
|
||||
CMD_INITLOAD -> "CMD_INITLOAD"
|
||||
CMD_INIT -> "CMD_INIT"
|
||||
CMD_EXTEND -> "CMD_EXTEND"
|
||||
CMD_VECTOR -> "CMD_VECTOR"
|
||||
CMD_IMPULSE -> "CMD_IMPULSE"
|
||||
CMD_STD -> "CMD_STD"
|
||||
CMD_TEST_ALL -> "CMD_TEST_ALL"
|
||||
else -> "CMD_$command"
|
||||
}
|
||||
|
||||
private fun fixed(address: Int, command: Int, payload: ByteArray, size: Int): ByteArray {
|
||||
require(address in 0..255) { "Адрес контроллера должен быть 0…255" }
|
||||
require(payload.size + 4 == size) { "Неверная длина пакета" }
|
||||
requireNative()
|
||||
return requireNotNull(NativeSetProtocol.nativeTms2812BuildFixed(address, command, payload))
|
||||
}
|
||||
|
||||
private fun le32(value: Long): ByteArray = ByteArray(4) { index ->
|
||||
(value ushr (index * 8)).toByte()
|
||||
}
|
||||
|
||||
private fun le32Value(data: ByteArray, offset: Int): Long =
|
||||
(data[offset].toLong() and 0xFF) or
|
||||
((data[offset + 1].toLong() and 0xFF) shl 8) or
|
||||
((data[offset + 2].toLong() and 0xFF) shl 16) or
|
||||
((data[offset + 3].toLong() and 0xFF) shl 24)
|
||||
|
||||
private fun requireNative() {
|
||||
check(NativeSetProtocol.available) { "Нативное ядро SETProtocol недоступно" }
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,11 @@ object NativeSetProtocol {
|
||||
external fun nativePeriph28335ProjectName(projectIndex: Int): String?
|
||||
external fun nativePeriph28335CommandName(projectIndex: Int, commandIndex: Int): String?
|
||||
external fun nativeTms2812Crc16(input: ByteArray): Int
|
||||
external fun nativeTms2812BuildFixed(controller: Int, command: Int, payload: ByteArray): ByteArray?
|
||||
external fun nativeTms2812BuildMemory(controller: Int, command: Int, address: Long, value: Int): ByteArray?
|
||||
external fun nativeTms2812BuildTest(controller: Int, duration: Int, pause: Int, digits: ByteArray): ByteArray?
|
||||
external fun nativeTms2812ValidateResponse(input: ByteArray, controller: Int, command: Int,
|
||||
fullSize: Int, allowShortTail: Boolean): Boolean
|
||||
external fun nativeTms2812BuildUpload(controller: Int, wordAddress: Long, byteCount: Long): ByteArray?
|
||||
external fun nativeTms2812ExpectedUploadSize(byteCount: Long): Int
|
||||
external fun nativeTms2812ValidateUpload(input: ByteArray, controller: Int, byteCount: Int): Boolean
|
||||
|
||||
@@ -10,6 +10,79 @@
|
||||
#include "periph28335.h"
|
||||
#include "tms2812.h"
|
||||
|
||||
static jbyteArray tms_result(JNIEnv *env, const uint8_t *data, size_t size)
|
||||
{
|
||||
if (size == 0U || size > INT32_MAX) return NULL;
|
||||
jbyteArray result = (*env)->NewByteArray(env, (jsize)size);
|
||||
if (result != NULL) (*env)->SetByteArrayRegion(env, result, 0, (jsize)size, (const jbyte *)data);
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812BuildFixed(
|
||||
JNIEnv *env, jobject self, jint controller, jint command, jbyteArray input)
|
||||
{
|
||||
(void)self;
|
||||
if (input == NULL || controller < 0 || controller > 255 || command < 0 || command > 255) return NULL;
|
||||
jsize size = (*env)->GetArrayLength(env, input);
|
||||
if (size > INT32_MAX - 4) return NULL;
|
||||
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
|
||||
if (data == NULL) return NULL;
|
||||
uint8_t *output = (uint8_t *)malloc((size_t)size + 4U);
|
||||
if (output == NULL) { (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT); return NULL; }
|
||||
size_t written = tms2812_build_fixed_request((uint8_t)controller, (uint8_t)command,
|
||||
(const uint8_t *)data, (size_t)size, output, (size_t)size + 4U);
|
||||
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||
jbyteArray result = tms_result(env, output, written);
|
||||
free(output);
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812BuildMemory(
|
||||
JNIEnv *env, jobject self, jint controller, jint command, jlong address, jint value)
|
||||
{
|
||||
(void)self;
|
||||
uint8_t output[12];
|
||||
if (controller < 0 || controller > 255 || command < 0 || command > 255 ||
|
||||
address < 0 || (uint64_t)address > UINT32_MAX || value < 0 || value > 65535) return NULL;
|
||||
size_t written = tms2812_build_memory_request((uint8_t)controller, (uint8_t)command,
|
||||
(uint32_t)address, (uint16_t)value, output, sizeof output);
|
||||
return tms_result(env, output, written);
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812BuildTest(
|
||||
JNIEnv *env, jobject self, jint controller, jint duration, jint pause, jbyteArray input)
|
||||
{
|
||||
(void)self;
|
||||
uint8_t output[20];
|
||||
if (input == NULL || controller < 0 || controller > 255 || duration < 0 ||
|
||||
duration > 65535 || pause < 0 || pause > 65535 || (*env)->GetArrayLength(env, input) != 12) return NULL;
|
||||
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
|
||||
if (data == NULL) return NULL;
|
||||
size_t written = tms2812_build_test_request((uint8_t)controller, (uint16_t)duration,
|
||||
(uint16_t)pause, (const uint8_t *)data, 12U, output, sizeof output);
|
||||
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||
return tms_result(env, output, written);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812ValidateResponse(
|
||||
JNIEnv *env, jobject self, jbyteArray input, jint controller, jint command,
|
||||
jint full_size, jboolean allow_short_tail)
|
||||
{
|
||||
(void)self;
|
||||
if (input == NULL || controller < 0 || controller > 255 || command < 0 || command > 255 || full_size < 6) return JNI_FALSE;
|
||||
jsize size = (*env)->GetArrayLength(env, input);
|
||||
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
|
||||
if (data == NULL) return JNI_FALSE;
|
||||
int status = tms2812_validate_response((const uint8_t *)data, (size_t)size,
|
||||
(uint8_t)controller, (uint8_t)command, (size_t)full_size, allow_short_tail != JNI_FALSE);
|
||||
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||
return status == TMS2812_OK ? JNI_TRUE : JNI_FALSE;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812Crc16(
|
||||
JNIEnv *env, jobject self, jbyteArray input)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package ru.setcorp.setflash.core
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
class TmsTerminalSharedTest {
|
||||
@Test fun testAllUsesSharedNativeFraming() {
|
||||
val digits = byteArrayOf(0x81.toByte(), 2, 4, 8, 9, 0x20, 0x10, 0x20, 0x40, 0x80.toByte(), 3, 4)
|
||||
val packet = Tms2812Protocol.testAll(7, 25, 18, digits)
|
||||
assertEquals(20, packet.size)
|
||||
assertArrayEquals(byteArrayOf(7, 66, 25, 0, 18, 0) + digits, packet.copyOf(18))
|
||||
assertEquals(0, Tms2812Protocol.crc16(packet))
|
||||
}
|
||||
|
||||
@Test fun pokeKeepsWordAddressAndZeroPadding() {
|
||||
val packet = Tms2812Protocol.poke(10, 0x12345678L, 0xABCD)
|
||||
assertArrayEquals(byteArrayOf(10, 57, 0x78, 0x56, 0x34, 0x12,
|
||||
0xCD.toByte(), 0xAB.toByte(), 0, 0), packet.copyOf(10))
|
||||
assertEquals(0, Tms2812Protocol.crc16(packet))
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun invalidAddressIsNotTruncated() {
|
||||
Tms2812Protocol.peek(10, 0x1_0000_0000L)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user