c0nb0xDeveloper's info
Source Code (v1.01) |
c0nb0x_101_src.zip (118 KB) |
c0nb0x & x0xb0x Serial Protocol
To communicate with the x0xb0x, c0nb0x uses the same protocol which is used originally in c0ntr0l.
However, c0nb0x introduces some additional messages on top of that..
Everything between the app and x0x is sent in the form of packets:
[msgID] [size1] [size2] <data> [CRC]
-
msgID
is the message ID -
size1
andsize2
are used together to form auint16_t data_size
for the data in the packet -
data
is a variable-sized chunk of bytes, if any -
CRC
is always the last byte, it is calculated by including the bytes frommsgID
all the way to the data section (if any)
The smallest possible packet (one which has no data) is 4 bytes long.
The packet total size is thus (4+data_size
).
Note
|
While it’s technically possible to have a very big packet (since the data_size is a uint16_t type)
i would strongly recommend to never use packets with more than 60 bytes of data,
since the firmware accumulates the data for a packet into a buffer which is defined as 64 bytes long
(see the definition of In order to not break compatibility with the stock firmware - keep the This means that we’re stuck to having a useless byte in all packets,
sadly it cannot be changed, since it will break compatibility with the existing x0x OSes. |
Messages
msgID | DESCRIPTION | USED | Direction x0x<->APP |
||
---|---|---|---|---|---|
NAME | DEC | HEX | |||
MSG_UNUSED | 0 | 00 | ? | ? | |
MSG_PING | 1 | 01 | Ping pong? | Y | <-> |
MSG_PATT_WR | 16 | 10 | App sends a pattern to the x0x | Y | <- |
MSG_PATT_RD | 17 | 11 | App requests a pattern from the x0x | Y | <- |
MSG_PATT_LOAD | 18 | 12 | App wants x0x to select pattern X? | - | <- |
MSG_PATT_GET | 19 | 13 | App wants to know the pattern slot? | - | <- |
MSG_PATT_PLAY | 20 | 14 | - | <- | |
MSG_PATT_STOP | 21 | 15 | - | <- | |
MSG_PATT | 25 | 19 | x0x sends a pattern to the App | Y | -> |
MSG_TRACK_WR | 32 | 20 | App sends a track to the x0x? | - | <- |
MSG_TRACK_RD | 33 | 21 | App requests a track from the x0x? | - | <- |
MSG_TRACK_LOAD | 34 | 22 | App wants x0x to select track X? | - | <- |
MSG_TRACK_GET | 35 | 23 | App wants to know the track slot? | - | <- |
MSG_TRACK | 41 | 29 | x0x sends a track to the App? | - | -> |
MSG_SEQ_START | 48 | 30 | App wants to press RUN? | - | <- |
MSG_SEQ_STOP | 49 | 31 | App wants to press STOP? | - | <- |
MSG_SEQ_GET | 50 | 32 | App wants to know if running or not? | - | <- |
MSG_SYNC_SET | 51 | 33 | App wants to change sync mode? | - | <- |
MSG_TEMPO_GET | 64 | 40 | App wants to know the BPM | Y | <- |
MSG_TEMPO_SET | 65 | 41 | App wants to change the BPM | Y | <- |
MSG_TEMPO | 66 | 42 | x0x sends BPM to the App | Y | -> |
MSG_STATUS | 128 | 80 | Status (OK / BAD) | Y | <-> |
EXTENDED MESSAGES | |||||
MSG_FW_VER | 129 | 81 | Firmware Version (OSID) | <-> | |
MSG_ISSUPP | 130 | 82 | Is Supported? | <-> | |
MSG_DIAG | 131 | 83 | DiagOS special message | <-> | |
MSG_TEXTOUT | 132 | 84 | Text Output to App | -> | |
MSG_GET_MEM | 133 | 85 | Get variable-sized chunk from the EEPROM | <-> | |
MSG_SET_MEM | 134 | 86 | Send variable-sized chunk to the EEPROM | <- | |
MSG_PATBUF | 135 | 87 | Send/Get pattern(s) from the pattern buffer | <- | |
MSG_GET_INFO | 136 | 88 | Get info | <-> | |
MSG_GET_PARAM | 137 | 89 | Get parameter | <-> | |
MSG_SET_PARAM | 138 | 8A | Set parameter | <- |
Note
|
The dark-gray messages (marked with a dash under USED) have only been |
c0nb0x implements Extended messages with values above 128.
Note
|
The "bi-directional" messages may have different packet structure/content depending on the direction
(see MSG_FW_VER as an example of this).
|
Extended Messages
MSG_FW_VER
c0nb0x introduces the MSG_FW_VER
message, to make it possible for each firmware to be
identified with it’s unique ID and name (string).
The ID can be used in the app to have custom features or handle different pattern formats.
App-to-x0x:
[MSG_FW_VER] [0] [0] [CRC]
You can see this is just an empty packet (has no data).
x0x-to-App:
This is the reply, it has the following data contents:
[osID] [VERSION] [osSTRING]
-
uint16_t osID
- unique ID of the firmware -
uint16_t VERSION
- this value will be divided by 100 and printed as a fixed-point number in c0nb0x (for example, 123 → "1.23") -
osSTRING
- variable sized C-string with the name of the Firmware which will be printed in c0nb0x.
This must be at least 1 char, but not more than 32 chars, not null-terminated.
So the data_size of this message is then (2+2+length_of_osSTRING
).
Note
|
If the firmware does not support the
In all these cases, that’s "auto-detection" and thus c0nb0x prints "(auto)" in front of the Firmware name. Only if the Firmware replies with |
Implementing MSG_FW_VER
reply in your firmware:
case MSG_FW_VER:
{
// 2bytes 2bytes <variable>
// [ osID ] [ osVER ] [ os string ]
{
uint16_t* ptr = (uint16_t*)(tx_msg_buff+3);
*ptr = 23; // TODO: osID
++ptr;
*ptr = 200; // TODO: version
}
unsigned char* ptr = (tx_msg_buff+(3+4));
// os string should not be longer than 32 chars
#define OSSTRING_LEN 6 // TODO: osSTRING_LENGTH
for (uint8_t i = 0; i < OSSTRING_LEN; i++)
{
ptr[i] = "n0nx0x"[i]; // TODO: osSTRING
}
uint8_t tmp = 2+2+OSSTRING_LEN; // data_size
tx_msg_buff[0] = MSG_FW_VER;
tx_msg_buff[1] = 0;
tx_msg_buff[2] = tmp;
tmp += 3;
tx_msg_buff[tmp] = calc_CRC8(tx_msg_buff, tmp);
++tmp;
send_msg(tx_msg_buff, tmp);
} break;
In the code above, you have to change 4 things (commented with TODO) and you’re done.
MSG_TEXTOUT
MSG_TEXTOUT will be available in c0nb0x v1.01.
Its purpose is to help you "Monitor" or debug the x0x firmware.
The direction is only x0x→App and the message can contain a string, an integer, or binary data.
x0x→App:
[MSG_TEXTOUT] [0] [size] <data> [CRC]
where the data
contains:
[type] [rawdata]
type
can be one of the following:
enum TEXTOUT_TYPE
{
TXTO_STR = 0, // null-terminated string
TXTO_UI8 = 1, // uint8_t
TXTO_SI8 = 2, // int8_t
TXTO_UI16 = 3, // uint16_t
TXTO_SI16 = 4, // int16_t
TXTO_UI32 = 5, // uint32_t
TXTO_SI32 = 6, // int32_t
TXTO_BIN = 7, // <binary data>
};
rawdata
is the actual string of characters, or integer, or array of bytes of binary data.
c0nb0x will convert the integers to text (via ostringstream) and the binary data will be shown as HEX.
Here are functions for textout:
#ifdef XMSG_TEXTOUT
void textout(char* s);
void textout_bin(uint8_t* data, uint8_t len);
void textout_ui8(uint8_t x);
void textout_ui16(uint16_t x);
void textout_ui32(uint32_t x);
void textout_si8(int8_t x);
void textout_si16(int16_t x);
void textout_si32(int32_t x);
#else
#define textout(x)
#define textout_bin(x,y)
#define textout_ui8(x)
#define textout_ui16(x)
#define textout_ui32(x)
#define textout_si8(x)
#define textout_si16(x)
#define textout_si32(x)
#endif//XMSG_TEXTOUT
#ifdef XMSG_TEXTOUT
void textout(char* s)
{
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_STR;
// get length of string
// don't pass long strings please!
uint8_t len = 0;
while (len < 255)
{
if (s[len] == 0x00) { break; }
++len;
}
// we'll slice the string if it's too big to fit into a single packet
uint8_t nump = len/48; // let's use 48 bytes at a time
if ((len-(nump*48))) { ++nump; } // round-up
uint8_t i = 0;
uint8_t i2 = 0;
uint8_t n = 0;
while (n < nump)
{
// for each packet..
i = 0;
while (i < 48)
{
if (i2 >= len) { break; }
tx_msg_buff[4+i] = s[i2];
++i;
++i2;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
++n;
}
return;
}
void textout_bin(uint8_t* data, uint8_t len)
{
// binary!
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_BIN;
// we'll slice the data if it's too big to fit into a single packet
uint8_t nump = len/48; // let's use 48 bytes at a time
if ((len-(nump*48))) { ++nump; } // round-up
uint8_t i = 0;
uint8_t i2 = 0;
uint8_t n = 0;
while (n < nump)
{
// for each packet..
i = 0;
while (i < 48)
{
if (i2 >= len) { break; }
tx_msg_buff[4+i] = data[i2];
++i;
++i2;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
++n;
}
return;
}
void textout_ui8(uint8_t x)
{
uint8_t len = 1;
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_UI8;
uint8_t i = 0;
while (i < len)
{
tx_msg_buff[4+i] = x;
++i;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
return;
}
void textout_ui16(uint16_t x)
{
uint8_t len = 2;
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_UI16;
uint8_t i = 0;
uint8_t *s = (uint8_t*)(&x);
while (i < len)
{
tx_msg_buff[4+i] = s[i];
++i;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
return;
}
void textout_ui32(uint32_t x)
{
uint8_t len = 4;
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_UI32;
uint8_t i = 0;
uint8_t *s = (uint8_t*)(&x);
while (i < len)
{
tx_msg_buff[4+i] = s[i];
++i;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
return;
}
void textout_si8(int8_t x)
{
uint8_t len = 1;
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_SI8;
uint8_t i = 0;
while (i < len)
{
tx_msg_buff[4+i] = x;
++i;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
return;
}
void textout_si16(int16_t x)
{
uint8_t len = 2;
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_SI16;
uint8_t i = 0;
uint8_t *s = (uint8_t*)(&x);
while (i < len)
{
tx_msg_buff[4+i] = s[i];
++i;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
return;
}
void textout_si32(int32_t x)
{
uint8_t len = 4;
tx_msg_buff[0] = MSG_TEXTOUT;
tx_msg_buff[1] = 0;
tx_msg_buff[3] = TXTO_SI32;
uint8_t i = 0;
uint8_t *s = (uint8_t*)(&x);
while (i < len)
{
tx_msg_buff[4+i] = s[i];
++i;
}
++i;
tx_msg_buff[2] = i; // data_size
i += 3;
tx_msg_buff[i] = calc_CRC8(tx_msg_buff, i);
send_msg(tx_msg_buff, 1+i);
return;
}
#endif//XMSG_TEXTOUT
if (tempo_changed)
{
textout("Tempo changed to ");
textout_ui16(tempo);
textout(" bpm.\n");
}
MSG_ISSUP
This message is used to query if some messages/features are supported by the firmware.
c0nb0x will usually send a few such messages on connect to see if the FW supports this and that.
[MSG_ISSUP] [0] [2] <CODE> [CRC]
-
uint16_t CODE
- this value is normally amsgID
, but it’s two-byte for possible future extension.
[MSG_ISSUP] [0] [2] [0, MSG_ISSUP] [CRC]
That’s how c0nb0x will ask if the FW even supports the MSG_ISSUP itself.
The x0x will reply with:
[MSG_ISSUP] [0] [3] <CODE> <ANSWER> [CRC]
-
CODE
must have the same value as the requested one. -
ANSWER
is 0 for "not supported" or 1 for "supported" (higher values may be used in some special cases).
Normally, c0nb0x will not query about the "normal" messages (those below 128)
so they don’t need to return a positive answer.
c0nb0x needs to know the pattern size, and on startup, requests MSG_PATT_RD
just for that reason.
From c0nb0x v1.01, another method is used: MSG_ISSUP
with MSG_PATT
as the argument - the x0x
should reply with PATT_SIZE
as the answer, then c0nb0x will not request MSG_PATT_RD
.
MSG_GET_MEM
Other than MSG_PATT_WR
and MSG_PATT_RD
, there is really no other way to get/set data from/to the EEPROM via the App.
So here it is.
App→x0x:
[MSG_GET_MEM] [0] [3] <ADDRESS> <LENGTH> [CRC]
-
uint16_t ADDRESS
- the address (or offset) in the EEPROM memory. -
uint8_t LENGTH
- the number of bytes the App wants to get.
Warning
|
The App should never try to request big amounts of data at once,
the x0x should refuse such requests. Instead, the app should make multiple requests using smaller packets. |
x0x→App:
[MSG_GET_MEM] [0] [size] <ADDRESS> <DATA> [CRC]
-
uint16_t ADDRESS
must be the same value as in the request. -
DATA
is the actual data. -
size
is the LENGTH of the data + 2.
Note
|
If the x0x refuses to reply to a request - it should send a MSG_STATUS with STAT_BAD .
|
MSG_SET_MEM
App→x0x:
[MSG_SET_MEM] [0] [size] <ADDRESS> <DATA> [CRC]
-
uint16_t ADDRESS
- the address (or offset) in the EEPROM memory. -
DATA
- the actual data to be written. -
size
- the LENGTH of the data + 2.
Note
|
The App should NOT send too much data at once, so the x0x is free to refuse to process such requests. |
The x0x must reply with MSG_STATUS
with either STAT_OK
or STAT_BAD
.
MSG_PATBUF
c0nb0x has a pattern editor.
Up till v1.00, the pattern editor uses MSG_PATT_RD
and MSG_PATT_WR
to load/save the pattern from the x0xb0x.
This means that the patterns were written to EEPROM everytime, especially with the "Auto L/S" option,
that’s quite an abuse for the EEPROM.
So, to end this abuse, the MSG_PATBUF
message was introduced.
Instead of loading/saving (directly from/to the EEPROM) the pattern you edit - this can be done from
the pattern buffer (which is in RAM).
The downsides are:
-
The pattern is not really "saved" unless the firmware saves it
-
The pattern editor in c0nb0x cannot load/save from/to a specific bank & slot
The benefits are:
-
Hear the changes to the pattern as it plays instantly (how this will be implemented is up to the firmware)
-
Use "Auto L/S" without worrying about abusing the EEPROM.
In the stock firmware, the pattern buffer holds only 1 pattern, but in other FWs it might hold more. Thus the message supports that scenario, but c0nb0x will not implement it, as the pattern editor in c0nb0x currently works with one pattern at a time.
To get a pattern from the pattern buffer:
App→x0x:
[MSG_PATBUF] [0] [2] <0> <N> [CRC]
-
byte 4
is 0 for "get" -
N
is the pattern index in the patbuf (if it holds more than one pattern) .. starts from 0.
x0x→App:
The x0x must reply with MSG_PATT
containing the requested pattern,
or MSG_STATUS
with STAT_BAD
if something goes wrong.
To send a pattern to the pattern buffer:
App→x0x:
[MSG_PATBUF] [0] [size] <1> <N> <DATA> [CRC]
-
size
is (2+PATT_SIZE
). -
byte 4
is 1 for "set" -
N
is the pattern index in the patbuf (if it holds more than one pattern) .. starts from 0. -
DATA
is the actual pattern to be written to the pattern buffer.
x0x→App:
The x0x must reply with MSG_STATUS
with either STAT_OK
or STAT_BAD
.
MSG_GET_INFO
This message is used to query some useful information about the firmware.
It will be available in c0nb0x v1.01.
App→x0x:
[MSG_GET_INFO] [0] [1] <CODE> [CRC]
-
uint8_t CODE
- see examples below
[MSG_GET_INFO] [0] [1] <0> [CRC]
The x0x should reply with:
[MSG_GET_INFO] [0] [4] <INTERNAL> <EXTERNAL> [CRC]
-
uint16_t INTERNAL
- size of the internal EEPROM.
The internal EEPROM is not used by c0nb0x yet, but might be used in the future. -
uint16_t EXTERNAL
- size of the external EEPROM (aka pattern memory).
The EXTERNAL eeprom is 4kB by design.
However, the CPUMOD has a slot for an additional 4kB EEPROM which may be present.
In that case, the firmware can return 8kB.
For this purpose, the implementation of MSG_GET_MEM and MSG_SET_MEM (or MSG_PATT_RD and MSG_PATT_WR)
should be changed to operate on the second EEPROM for addresses above 4095.
[MSG_GET_INFO] [0] [1] <1> [CRC]
The x0x should reply with:
[MSG_GET_INFO] [0] [2] <BANKS> <SLOTS> [CRC]
-
uint8_t BANKS
- number of banks (e.g. 16) -
uint8_t SLOTS
- number of pattern slots (per bank)
Thus the number of patterns is (BANKS
*SLOTS
), which will be used for
the "Import/Export Patterns" function in c0nb0x.
[MSG_GET_INFO] [0] [1] <8> [CRC]
If this is a firmware running on the CPUMOD (atmega2561) the x0x should reply with:
[MSG_GET_INFO] [0] [1] <1> [CRC]
Parameters
c0nb0x v1.02 introduces two new messages into the protocol: MSG_GET_PARAM
and MSG_SET_PARAM
.
The firmware can have up to 1024 parameters, which can be changed by the user from a menu in c0nb0x.
Parameters are represented as int16_t
values.
There are 3 parameter types: BOOL
, INT
, ENUM
.
-
BOOL
: an On/Off-type parameter -
INT
: a signed integer -
ENUM
: a parameter where each value has a custom label
c0nb0x uses a special external file (*.prm
) which describes each parameter,
what type it is, what range it has, and what labels/info text to print on screen.
Adding Parameter support to your firmware:
-
Your FW must support at least
MSG_FW_VER
, andMSG_ISSUPP
. -
On connect, c0nb0x would ask if
MSG_GET_PARAM
is supported.-
If it is, c0nb0x will send a
MSG_GET_PARAM
with param index0xFFFF
.
The firmware should reply with aMSG_GET_PARAM
containing:[uint16_t num_params] [uint16_t params_unique_version]
.
-
If all that is successiful, c0nb0x will generate a unique *.prm
filename for the firmware,
and attempt to load that file from the main c0nb0x directory.
The filename will contain the firmware name
string (exotic chars would be stripped, A-Z would be turned into a-z, and 0-9),
the OSID
, the number of params, and the unique params version.
This would make it possible to support multiple versions of the same firmware,
even if those versions have a different set of parameters and/or parameter ordering.
Example filename: n0nx0x_23_0_18.prm
name string
|
"n0nx0x" |
OSID
|
23 |
params_unique_version
|
0 |
num_params
|
18 |
The *.prm
file would have to be distributed together with the firmware,
and "installed" into the c0nb0x main directory.
If c0nb0x doesn’t find the file, or something goes wrong during the parsing (wrong number of params, bad syntax.. etc..), the Parameters menu will be unaccessible to the user.
On CONNECT
Here’s an overview of what happens when c0nb0x connects to the x0xb0x.
Note
|
this is valid for c0nb0x v1.01. |
Note
|
this does not apply for when connecting to upload firmware. The bootloader would freak out with such data. |
- Serial port opened successifully -
→ MSG_PING
→ MSG_FW_VER
→ MSG_ISSUP
: "MSG_ISSUP"
If MSG_ISSUP
is supported:
-
→
MSG_ISSUP
:"MSG_PATT"
(the x0x should return the pattern size) -
→
MSG_ISSUP
:"MSG_GET_MEM"
-
→
MSG_ISSUP
:"MSG_SET_MEM"
-
→
MSG_ISSUP
:"MSG_PATBUF"
-
→
MSG_ISSUP
:"MSG_GET_INFO"
If MSG_GET_INFO
is supported:
-
→
MSG_GET_INFO
: 0 (eeprom info) -
→
MSG_GET_INFO
: 1 (pattern banks/slots info) -
→
MSG_GET_INFO
: 8 (is you is, or is you ain’t a CPUMOD)
If the pattern size was not detected yet:
-
→
MSG_PATT_RD
: 0, 0 (get the pattern at bank 1, slot 1)
If the OSID
wasn’t detected yet - guess it based on the collected info so far.
Guess the pattern format.
→ MSG_TEMPO_GET
- Connected -
CXM memory files
c0nb0x uses .cxm
files for the EEPROM memory and Pattern memory import/export.
CXM is just a raw data file like the .xbp
(used in c0ntr0l), but with a prepended header.
description | offset | type | name | offset | end |
---|---|---|---|---|---|
Magic Word |
0 |
|
"c0nb0x/M" |
8 |
|
9 |
|
|
11 |
|
|
12 |
|
|
14 |
|
|
15 |
|
|
17 |
|
|
Raw data |
18 |
the remaining data is the same as in |
The idea behind this is to store the OS_ID
into the file.
This makes it possible for c0nb0x to figure out what format the patterns in the file are,
for when you try to import them into another firmware which uses a different pattern format.