Расширить общий протокол CAN_Bal_2812
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* @file balsam_can.h
|
* @file balsam_can.h
|
||||||
* @brief Legacy Balsam 167 extended-CAN register frames.
|
* @brief CAN_Bal_2812 extended-CAN register space.
|
||||||
*
|
*
|
||||||
* The wire layout is taken from Balsam_167_periph/Source/Internal/ecan.c:
|
* The wire layout is used by BALZAM Src/balzam_7/CanSetupBalzam7.c:
|
||||||
* one big-endian address/mask word followed by three big-endian register words.
|
* one big-endian address/mask word followed by three big-endian register words.
|
||||||
*/
|
*/
|
||||||
#ifndef BALSAM_CAN_H
|
#ifndef BALSAM_CAN_H
|
||||||
@@ -34,7 +34,7 @@ typedef struct {
|
|||||||
uint16_t values[BALSAM_CAN_REGISTER_COUNT];
|
uint16_t values[BALSAM_CAN_REGISTER_COUNT];
|
||||||
} balsam_can_frame_t;
|
} balsam_can_frame_t;
|
||||||
|
|
||||||
/** Return non-zero for the command/data/terminal IDs used by Balsam 167. */
|
/** Return non-zero for the command/data/terminal IDs used by CAN_Bal_2812. */
|
||||||
int balsam_can_is_id(uint32_t can_id);
|
int balsam_can_is_id(uint32_t can_id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,11 +44,11 @@ int balsam_can_is_id(uint32_t can_id);
|
|||||||
int balsam_can_decode(uint32_t can_id, const uint8_t *data, size_t size,
|
int balsam_can_decode(uint32_t can_id, const uint8_t *data, size_t size,
|
||||||
balsam_can_frame_t *output);
|
balsam_can_frame_t *output);
|
||||||
|
|
||||||
/** Human-readable Russian name of a Balsam device. */
|
/** Human-readable Russian name of a CAN_Bal_2812 device. */
|
||||||
const char *balsam_can_device_name(uint8_t device);
|
const char *balsam_can_device_name(uint8_t device);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format the register name from the Balsam 167 data table into output.
|
* Format the register name from the CAN_Bal_2812 data table into output.
|
||||||
* Returns the required length (excluding NUL); an empty string means unknown.
|
* Returns the required length (excluding NUL); an empty string means unknown.
|
||||||
*/
|
*/
|
||||||
size_t balsam_can_register_name(uint8_t device, uint16_t address,
|
size_t balsam_can_register_name(uint8_t device, uint16_t address,
|
||||||
|
|||||||
@@ -7,6 +7,15 @@ data class BalsamRegister(val address: Int, val value: Int, val name: String) {
|
|||||||
val signedValue: Int get() = if (value < 0x8000) value else value - 0x10000
|
val signedValue: Int get() = if (value < 0x8000) value else value - 0x10000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class BalsamCanNode(
|
||||||
|
val device: Int,
|
||||||
|
val name: String,
|
||||||
|
val commandId: Long,
|
||||||
|
val dataId: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class BalsamCanWireFrame(val canId: Long, val data: ByteArray)
|
||||||
|
|
||||||
data class BalsamFrame(
|
data class BalsamFrame(
|
||||||
val canId: Long,
|
val canId: Long,
|
||||||
val device: Int,
|
val device: Int,
|
||||||
@@ -20,18 +29,31 @@ data class BalsamFrame(
|
|||||||
val direction = if (fromDevice) "данные" else "команда"
|
val direction = if (fromDevice) "данные" else "команда"
|
||||||
val values = registers.joinToString { "${it.displayName}=0x%04X (%d)".format(it.value, it.signedValue) }
|
val values = registers.joinToString { "${it.displayName}=0x%04X (%d)".format(it.value, it.signedValue) }
|
||||||
.ifEmpty { "нет отмеченных регистров" }
|
.ifEmpty { "нет отмеченных регистров" }
|
||||||
return "BALZAM · $deviceName · $direction · $values"
|
return "CAN_Bal_2812 · $deviceName · $direction · $values"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Shared parser for Balsam_167_periph eCAN frames. */
|
/** Shared CAN_Bal_2812 register protocol used by the BALZAM F2812 firmware. */
|
||||||
object BalsamCanProtocol {
|
object BalsamCanProtocol {
|
||||||
const val BASE_ID = 0x00BA_0000L
|
const val BASE_ID = 0x00BA_0000L
|
||||||
|
const val NODE_COUNT = 13
|
||||||
|
const val REGISTER_BANK_SIZE = 128
|
||||||
const val TERMINAL_REQUEST_ID = 0x00BA_001CL
|
const val TERMINAL_REQUEST_ID = 0x00BA_001CL
|
||||||
const val TERMINAL_RESPONSE_ID = 0x00BA_000CL
|
const val TERMINAL_RESPONSE_ID = 0x00BA_000CL
|
||||||
const val PULT_REQUEST_ID = 0x0074_5019L
|
const val PULT_REQUEST_ID = 0x0074_5019L
|
||||||
const val PULT_RESPONSE_ID = 0x0074_5009L
|
const val PULT_RESPONSE_ID = 0x0074_5009L
|
||||||
|
|
||||||
|
val nodes: List<BalsamCanNode> by lazy {
|
||||||
|
(1..NODE_COUNT).map { device ->
|
||||||
|
BalsamCanNode(
|
||||||
|
device = device,
|
||||||
|
name = deviceName(device),
|
||||||
|
commandId = BASE_ID + device - 1L,
|
||||||
|
dataId = BASE_ID + 0x0FL + device,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun isLegacyId(canId: Long): Boolean {
|
fun isLegacyId(canId: Long): Boolean {
|
||||||
val relative = (canId and 0x1FFF_FFFFL) - BASE_ID
|
val relative = (canId and 0x1FFF_FFFFL) - BASE_ID
|
||||||
return relative in 0L..12L || relative in 0x10L..0x1CL ||
|
return relative in 0L..12L || relative in 0x10L..0x1CL ||
|
||||||
@@ -62,17 +84,44 @@ object BalsamCanProtocol {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Encode one CAN_Bal_2812 register write/data frame with one to three words. */
|
||||||
|
fun encodeWrite(
|
||||||
|
device: Int,
|
||||||
|
fromDevice: Boolean,
|
||||||
|
startAddress: Int,
|
||||||
|
values: List<Int>,
|
||||||
|
): BalsamCanWireFrame {
|
||||||
|
require(device in 1..NODE_COUNT) { "Номер узла должен быть в диапазоне 1..$NODE_COUNT" }
|
||||||
|
require(startAddress in 0 until REGISTER_BANK_SIZE) { "Адрес регистра должен быть в диапазоне 0..127" }
|
||||||
|
require(values.size in 1..3) { "Нужно от 1 до 3 слов данных" }
|
||||||
|
require(startAddress + values.size <= REGISTER_BANK_SIZE) { "Запись выходит за границу регистрового пространства" }
|
||||||
|
values.forEach { require(it in 0..0xFFFF) { "Значение должно быть в диапазоне 0..65535" } }
|
||||||
|
|
||||||
|
val mask = when (values.size) {
|
||||||
|
1 -> 4
|
||||||
|
2 -> 6
|
||||||
|
else -> 7
|
||||||
|
}
|
||||||
|
val padded = values + List(3 - values.size) { 0 }
|
||||||
|
val header = (mask shl 13) or startAddress
|
||||||
|
val data = listOf(header, padded[0], padded[1], padded[2])
|
||||||
|
.flatMap { listOf((it ushr 8).toByte(), it.toByte()) }
|
||||||
|
.toByteArray()
|
||||||
|
val node = nodes[device - 1]
|
||||||
|
return BalsamCanWireFrame(if (fromDevice) node.dataId else node.commandId, data)
|
||||||
|
}
|
||||||
|
|
||||||
fun summary(canId: Long, data: ByteArray? = null): String =
|
fun summary(canId: Long, data: ByteArray? = null): String =
|
||||||
data?.let { decode(canId, it)?.summary() } ?: when (canId) {
|
data?.let { decode(canId, it)?.summary() } ?: when (canId) {
|
||||||
TERMINAL_REQUEST_ID -> "BALZAM legacy · запрос терминала"
|
TERMINAL_REQUEST_ID -> "CAN_Bal_2812 · запрос терминала"
|
||||||
TERMINAL_RESPONSE_ID -> "BALZAM legacy · ответ терминалу"
|
TERMINAL_RESPONSE_ID -> "CAN_Bal_2812 · ответ терминалу"
|
||||||
PULT_REQUEST_ID -> "BALZAM legacy · данные пульта"
|
PULT_REQUEST_ID -> "CAN_Bal_2812 · данные пульта"
|
||||||
PULT_RESPONSE_ID -> "BALZAM legacy · команда пульту"
|
PULT_RESPONSE_ID -> "CAN_Bal_2812 · команда пульту"
|
||||||
in (BASE_ID + 0x10L)..(BASE_ID + 0x1BL) ->
|
in (BASE_ID + 0x10L)..(BASE_ID + 0x1BL) ->
|
||||||
"BALZAM legacy · данные · ${deviceName((canId - BASE_ID - 0x0FL).toInt())}"
|
"CAN_Bal_2812 · данные · ${deviceName((canId - BASE_ID - 0x0FL).toInt())}"
|
||||||
in BASE_ID..(BASE_ID + 0x0BL) ->
|
in BASE_ID..(BASE_ID + 0x0BL) ->
|
||||||
"BALZAM legacy · команда · ${deviceName((canId - BASE_ID + 1L).toInt())}"
|
"CAN_Bal_2812 · команда · ${deviceName((canId - BASE_ID + 1L).toInt())}"
|
||||||
else -> "BALZAM legacy · неизвестный ID"
|
else -> "CAN_Bal_2812 · неизвестный ID"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isRegisterId(canId: Long): Boolean {
|
private fun isRegisterId(canId: Long): Boolean {
|
||||||
@@ -95,7 +144,7 @@ object BalsamCanProtocol {
|
|||||||
private fun u16be(data: ByteArray, offset: Int): Int =
|
private fun u16be(data: ByteArray, offset: Int): Int =
|
||||||
((data[offset].toInt() and 0xFF) shl 8) or (data[offset + 1].toInt() and 0xFF)
|
((data[offset].toInt() and 0xFF) shl 8) or (data[offset + 1].toInt() and 0xFF)
|
||||||
|
|
||||||
private fun deviceName(device: Int): String = if (NativeSetProtocol.available) {
|
fun deviceName(device: Int): String = if (NativeSetProtocol.available) {
|
||||||
NativeSetProtocol.nativeBalsamDeviceName(device)
|
NativeSetProtocol.nativeBalsamDeviceName(device)
|
||||||
} else listOf(
|
} else listOf(
|
||||||
"Трансформатор 1", "Трансформатор 2", "Силовой блок 1", "Силовой блок 2",
|
"Трансформатор 1", "Трансформатор 2", "Силовой блок 1", "Силовой блок 2",
|
||||||
@@ -103,7 +152,7 @@ object BalsamCanProtocol {
|
|||||||
"Узел 12", "Терминал",
|
"Узел 12", "Терминал",
|
||||||
).getOrElse(device - 1) { "Неизвестный узел" }
|
).getOrElse(device - 1) { "Неизвестный узел" }
|
||||||
|
|
||||||
private fun registerName(device: Int, address: Int): String =
|
fun registerName(device: Int, address: Int): String =
|
||||||
if (NativeSetProtocol.available) NativeSetProtocol.nativeBalsamRegisterName(device, address)
|
if (NativeSetProtocol.available) NativeSetProtocol.nativeBalsamRegisterName(device, address)
|
||||||
else when {
|
else when {
|
||||||
device in 1..2 && address in 0x18..0x2B -> "Показания T° ${address - 0x17}"
|
device in 1..2 && address in 0x18..0x2B -> "Показания T° ${address - 0x17}"
|
||||||
|
|||||||
@@ -17,4 +17,22 @@ class BalsamCanProtocolTest {
|
|||||||
assertEquals("Показания T° 1", frame.registers.first().displayName)
|
assertEquals("Показания T° 1", frame.registers.first().displayName)
|
||||||
assertTrue(frame.summary().contains("Трансформатор 1"))
|
assertTrue(frame.summary().contains("Трансформатор 1"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun encodesControllerWriteForBal2812RegisterSpace() {
|
||||||
|
val wire = BalsamCanProtocol.encodeWrite(
|
||||||
|
device = 3,
|
||||||
|
fromDevice = false,
|
||||||
|
startAddress = 0x28,
|
||||||
|
values = listOf(0x1234, 0xFEDC),
|
||||||
|
)
|
||||||
|
assertEquals(0x00BA_0002L, wire.canId)
|
||||||
|
assertEquals(
|
||||||
|
listOf(0xC0, 0x28, 0x12, 0x34, 0xFE, 0xDC, 0x00, 0x00),
|
||||||
|
wire.data.map { it.toInt() and 0xFF },
|
||||||
|
)
|
||||||
|
val decoded = requireNotNull(BalsamCanProtocol.decode(wire.canId, wire.data))
|
||||||
|
assertEquals(false, decoded.fromDevice)
|
||||||
|
assertEquals(listOf(0x1234, 0xFEDC), decoded.registers.map { it.value })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user