Compare commits

..

9 Commits

Author SHA1 Message Date
Your Name bb1e17b27a Updated project plan 2026-10-04 16:57:41 +03:00
Your Name 36f406e69d Centralized TX messages to command handler to support multiple outputs 2026-10-04 16:44:15 +03:00
Your Name 5d511f3aca Added power monitor its own message buffer 2026-10-04 16:04:05 +03:00
Your Name 9f8b9f7715 Added ADC messages its own buffer 2026-10-04 15:55:09 +03:00
Your Name 2d7ed3b1d7 Small comment fixes 2026-10-04 15:41:12 +03:00
Your Name 5bba799bdd First implementation of INA3221 power monitor 2026-10-03 20:50:51 +03:00
Your Name 3e23ddfa3d Updated MCU software block design 2026-10-03 20:50:03 +03:00
Your Name 48fbeefa4a Another ADC mask mistake fix 2026-10-02 13:15:05 +03:00
Your Name 06f53e4afe First implementation of digital out for leg enable 2026-10-02 13:08:52 +03:00
20 changed files with 1178 additions and 445 deletions
+324 -306
View File
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
&i2c0 {
power_monitor: ina3221@40 {
compatible = "ti,ina3221";
reg = <0x40>;
status = "okay";
shunt-resistors = <100>, <100>, <100>;
};
};
+6 -1
View File
@@ -1,6 +1,11 @@
CONFIG_GPIO=y CONFIG_GPIO=y
CONFIG_PWM=y CONFIG_PWM=y
CONFIG_ADC=y CONFIG_ADC=y
CONFIG_I2C=y
# INA3221
CONFIG_SENSOR=y
CONFIG_INA3221=y
# Mux # Mux
CONFIG_CD74HC4067=y CONFIG_CD74HC4067=y
@@ -33,5 +38,5 @@ CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_DEBUG_THREAD_INFO=y CONFIG_DEBUG_THREAD_INFO=y
# CONFIG_DEBUG=y # CONFIG_DEBUG=y
# CONFIG_DEBUG_OPTIMIZATIONS=y # CONFIG_DEBUG_OPTIMIZATIONS=y
CONFIG_OUTPUT_DISASSEMBLY=n # Create disassmebly files # CONFIG_OUTPUT_DISASSEMBLY=y # Create disassmebly files
#CONFIG_OUTPUT_DISASSEMBLE_ALL=n #CONFIG_OUTPUT_DISASSEMBLE_ALL=n
+317 -86
View File
@@ -1,135 +1,366 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Plot packed 4-byte-integer channel data from a serial_logger.py CSV, dark mode. """Plot decoded command payloads from a serial_logger.py CSV, dark mode.
Expects rows where `data_hex` is N 4-byte little-endian integers packed back Each command gets its own subplot, stacked vertically in one window, in the
to back (default N=18, matching an 18-channel ADC_READ_ALL-style packet). order given by --commands (default: ADC_READ_ALL on top, POWER_MONITOR_READ
X-axis is the device-clock `tick_ms` column by default (or host_time). below it). X-axis is the device-clock `tick_ms` column by default, or
host_time.
--------------------------------------------------------------------------
TO ADD OR CHANGE A DATA FORMAT: edit the FORMATS dict below. Each entry is
a DataFormat(...) describing how to turn that command's raw data_hex bytes
into a list of (label, value) channels for one subplot. See the two
existing entries for examples: a plain packed-int array (ADC_READ_ALL) and
a decoded struct with scaling (POWER_MONITOR_READ, Zephyr sensor_value).
--------------------------------------------------------------------------
""" """
import argparse import argparse
import csv import csv
import struct import struct
import sys import sys
from dataclasses import dataclass, field, replace
from datetime import datetime from datetime import datetime
from typing import Callable, List, Sequence
import matplotlib import matplotlib
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib.dates as mdates import matplotlib.dates as mdates
def build_parser(): # --------------------------------------------------------------------------
p = argparse.ArgumentParser( # Data formats: command name -> how to decode its payload for plotting.
description="Plot packed 4-byte-integer channels from a serial_logger.py CSV (dark mode).", # This is the "field" to edit when a new command/payload format shows up.
formatter_class=argparse.RawDescriptionHelpFormatter, # --------------------------------------------------------------------------
epilog="""\
examples: @dataclass
%(prog)s -i adc_log.csv class DataFormat:
%(prog)s -i adc_log.csv --command ADC_READ_ALL -o plot.png byte_length: int # expected payload length in bytes
%(prog)s -i adc_log.csv --channels 18 --unsigned --x host decode: Callable[[bytes], Sequence[float]] # raw payload -> one float per channel
""", labels: List[str] # one label per decoded value, same order as decode()
ylabel: str = "value"
title: str = "" # subplot title; defaults to the command name
# Optional: if the labels are `group_count` groups of `group_size` each
# (e.g. 3 power channels x 3 metrics, grouped channel-major), the plot
# uses one color per group and a different line style per item within
# a group, instead of a flat colormap over every label.
group_count: int = 0
group_size: int = 0
def decode_int32_array(n: int, signed: bool = True) -> Callable[[bytes], Sequence[float]]:
"""N packed little-endian 4-byte integers, back to back."""
fmt = f"<{n}{'i' if signed else 'I'}"
def _decode(payload: bytes) -> Sequence[float]:
return struct.unpack(fmt, payload)
return _decode
def decode_sensor_value_grid(num_channels: int, metrics: List[str]) -> Callable[[bytes], Sequence[float]]:
"""Decode `struct sensor_value values[num_channels][len(metrics)]`,
memcpy'd channel-major (matches `values[0][0], values[0][1], ...,
values[1][0], ...` in the C code). Each sensor_value is
`{int32_t val1; int32_t val2;}`; real value = val1 + val2 * 1e-6
(val2 is the fractional part, in millionths)."""
n = num_channels * len(metrics)
def _decode(payload: bytes) -> Sequence[float]:
raw = struct.unpack(f"<{n * 2}i", payload)
return [raw[2 * i] + raw[2 * i + 1] * 1e-6 for i in range(n)]
return _decode
def grid_labels(num_channels: int, metrics: List[str]) -> List[str]:
return [f"Ch{ch + 1} {metric}" for ch in range(num_channels) for metric in metrics]
POWER_MONITOR_METRICS = ["Voltage (V)", "Current (A)", "Power (W)"]
POWER_MONITOR_CHANNELS = 3
FORMATS = {
"ADC_READ_ALL": DataFormat(
byte_length=18 * 4,
decode=decode_int32_array(18, signed=True),
labels=[f"angle{i}" for i in range(18)],
ylabel="value (int32)",
title="Angles",
),
"POWER_MONITOR_READ": DataFormat(
# 3 channels x 3 sensor_value structs x 8 bytes each (int32 val1 + int32 val2)
byte_length=POWER_MONITOR_CHANNELS * len(POWER_MONITOR_METRICS) * 8,
decode=decode_sensor_value_grid(POWER_MONITOR_CHANNELS, POWER_MONITOR_METRICS),
labels=grid_labels(POWER_MONITOR_CHANNELS, POWER_MONITOR_METRICS),
ylabel="value",
title="Power Monitor",
group_count=POWER_MONITOR_CHANNELS,
group_size=len(POWER_MONITOR_METRICS),
),
}
DEFAULT_COMMANDS = ["ADC_READ_ALL", "POWER_MONITOR_READ"]
def fallback_format(byte_length: int, signed: bool = True) -> DataFormat:
"""Used for a --commands entry with no FORMATS registry entry: assume a
plain packed int32 array and size it from the data actually seen."""
n = byte_length // 4
return DataFormat(
byte_length=byte_length,
decode=decode_int32_array(n, signed=signed),
labels=[f"ch{i}" for i in range(n)],
ylabel="value (int32)" if signed else "value (uint32)",
) )
p.add_argument("-i", "--input", required=True, help="input CSV file (from serial_logger.py)")
p.add_argument("-o", "--output", help="save the plot to this file instead of showing it interactively")
p.add_argument("-c", "--command", help="only use rows with this command name (default: use whatever " # --------------------------------------------------------------------------
"PACKET rows have the right byte length)") # CSV loading
p.add_argument("-n", "--channels", type=int, default=18, # --------------------------------------------------------------------------
help="number of 4-byte integer channels packed in data_hex (default: %(default)s)")
p.add_argument("--signed", dest="signed", action="store_true", default=True,
help="interpret channels as signed int32 (default: on)")
p.add_argument("--unsigned", dest="signed", action="store_false",
help="interpret channels as unsigned uint32")
p.add_argument("--x", choices=["tick", "host"], default="tick", def load_rows(path: str, command: str):
help="x-axis source: device tick_ms, or host_time (default: %(default)s)")
p.add_argument("--labels", help="comma-separated channel labels (default: ch0..chN-1)")
p.add_argument("--title", default="Channel data", help="plot title (default: %(default)s)")
return p
def load_rows(path, command, channel_bytes):
"""Read the CSV and return rows that look like packed-channel PACKET rows."""
rows = [] rows = []
with open(path, newline="", encoding="utf-8") as fh: with open(path, newline="", encoding="utf-8") as fh:
reader = csv.DictReader(fh) reader = csv.DictReader(fh)
for row in reader: for row in reader:
if row.get("type") != "PACKET": if row.get("type") != "PACKET":
continue continue
if command and row.get("command") != command: if row.get("command") != command:
continue
data_hex = row.get("data_hex", "")
if len(data_hex) != channel_bytes * 2: # 2 hex chars per byte
continue continue
rows.append(row) rows.append(row)
return rows return rows
def build_series(rows, fmt: DataFormat, x_source: str):
xs = []
channels = [[] for _ in fmt.labels]
for row in rows:
data_hex = row.get("data_hex", "")
if len(data_hex) != fmt.byte_length * 2: # 2 hex chars per byte
continue # skip rows that don't match this format's expected size
if x_source == "tick":
xs.append(int(row["tick_ms"]))
else:
xs.append(datetime.fromisoformat(row["host_time"]))
values = fmt.decode(bytes.fromhex(data_hex))
for i, v in enumerate(values):
channels[i].append(v)
if not xs:
return xs, channels
order = sorted(range(len(xs)), key=lambda i: xs[i])
xs = [xs[i] for i in order]
channels = [[ch[i] for i in order] for ch in channels]
return xs, channels
# --------------------------------------------------------------------------
# Plotting
# --------------------------------------------------------------------------
LINESTYLES = ["-", "--", ":", "-."]
def plot_subplot(ax, xs, channels, fmt: DataFormat, command: str, x_source: str):
if fmt.group_count and fmt.group_size and fmt.group_count * fmt.group_size == len(fmt.labels):
# One color per group (e.g. per channel), one line style per item
# within the group (e.g. per metric), so related lines are easy
# to tell apart at a glance instead of 9 near-identical hues.
# Index the discrete tab10 swatches directly (not .resampled(),
# which interpolates between them and can produce near-duplicate
# muddy colors for small group counts).
tab10 = matplotlib.colormaps["tab10"].colors
for i, label in enumerate(fmt.labels):
g, m = divmod(i, fmt.group_size)
ax.plot(xs, channels[i], label=label, color=tab10[g % len(tab10)],
linestyle=LINESTYLES[m % len(LINESTYLES)], linewidth=1.3)
else:
cmap = matplotlib.colormaps["tab20"].resampled(max(len(fmt.labels), 1))
for i, label in enumerate(fmt.labels):
ax.plot(xs, channels[i], label=label, color=cmap(i), linewidth=1.2)
ax.set_title(fmt.title or command, fontsize=12, color="white", loc="left")
ax.set_ylabel(fmt.ylabel)
ax.grid(True, alpha=0.25)
if x_source == "host":
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S"))
if fmt.group_count and fmt.group_size and fmt.group_count * fmt.group_size == len(fmt.labels):
ncol = fmt.group_size # one column per metric, one row per channel/group
else:
ncol = min(6, max(len(fmt.labels), 1))
ax.legend(ncol=ncol, fontsize=8, loc="upper center",
bbox_to_anchor=(0.5, -0.15), frameon=False)
def parse_index_list(text: str) -> List[int]:
"""'0,2,5' or '0-3,7' -> [0,2,5] / [0,1,2,3,7]."""
indices = []
for part in text.split(","):
part = part.strip()
if not part:
continue
if "-" in part:
a, b = part.split("-", 1)
indices.extend(range(int(a), int(b) + 1))
else:
indices.append(int(part))
return indices
def select_channels(fmt: DataFormat, channels, keep_indices: List[int]) -> "tuple[DataFormat, list]":
"""Return a (fmt, channels) pair restricted to keep_indices, preserving
group_count/group_size for the grouped coloring in plot_subplot if the
selection still divides evenly (e.g. picking a subset of metrics but
keeping every channel)."""
labels2 = [fmt.labels[i] for i in keep_indices]
channels2 = [channels[i] for i in keep_indices]
group_count, group_size = fmt.group_count, fmt.group_size
if group_count and group_size:
# If we kept the same subset of positions-within-group for every
# group (e.g. "voltage only" for all 3 channels), grouping still
# applies with a smaller group_size. Otherwise drop grouping.
per_group = [[] for _ in range(group_count)]
ok = True
for i in keep_indices:
g, m = divmod(i, group_size)
if g >= group_count:
ok = False
break
per_group[g].append(m)
if ok and len(set(tuple(p) for p in per_group if p)) <= 1 and all(per_group[0] == p for p in per_group if p):
group_size = len(per_group[0]) if per_group[0] else 0
else:
group_count, group_size = 0, 0
return replace(fmt, labels=labels2, group_count=group_count, group_size=group_size), channels2
def build_parser():
p = argparse.ArgumentParser(
description="Plot decoded command payloads from a serial_logger.py CSV (dark mode).",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""\
To add/change how a command's data is decoded, edit the FORMATS dict at the
top of this script (see decode_int32_array / decode_power_monitor).
examples:
%(prog)s -i log.csv
%(prog)s -i log.csv -o plot.png
%(prog)s -i log.csv --commands ADC_READ_ALL
%(prog)s -i log.csv --commands POWER_MONITOR_READ,ADC_READ_ALL --x host
""",
)
p.add_argument("-i", "--input", required=True, help="input CSV file (from serial_logger.py)")
p.add_argument("-o", "--output", help="save the plot to this file instead of showing it interactively")
p.add_argument("--commands", default=",".join(DEFAULT_COMMANDS),
help="comma-separated command names to plot, top to bottom "
"(default: %(default)s)")
p.add_argument("--x", choices=["tick", "host"], default="tick",
help="x-axis source: device tick_ms, or host_time (default: %(default)s)")
p.add_argument("--title", default="Serial data", help="overall figure title (default: %(default)s)")
p.add_argument("--unsigned", action="store_true",
help="for a command with no FORMATS entry, decode its ints as unsigned "
"(default: signed)")
p.add_argument("--adc-channels",
help="only plot these ADC_READ_ALL channel indices, e.g. '0,2,5' or '0-3,7' "
"(default: all)")
power_group = p.add_argument_group(
"power monitor metrics",
"which POWER_MONITOR_READ metrics to plot, for every channel (default: all three)")
power_group.add_argument("--voltage", action="store_true", help="show voltage")
power_group.add_argument("--current", action="store_true", help="show current")
power_group.add_argument("--power", action="store_true", help="show power")
return p
def main(): def main():
parser = build_parser() parser = build_parser()
args = parser.parse_args() args = parser.parse_args()
channel_bytes = args.channels * 4 commands = [c.strip() for c in args.commands.split(",") if c.strip()]
if not commands:
parser.error("--commands gave no command names")
rows = load_rows(args.input, args.command, channel_bytes) panels = [] # (command, fmt, xs, channels)
if not rows: for command in commands:
hint = f" with command={args.command!r}" if args.command else "" rows = load_rows(args.input, command)
print(f"ERROR: no PACKET rows{hint} with {channel_bytes}-byte payloads " if not rows:
f"({args.channels} x 4-byte ints) found in {args.input}", file=sys.stderr) print(f"WARNING: no PACKET rows for command={command!r} in {args.input}, skipping", file=sys.stderr)
continue
fmt = FORMATS.get(command)
if fmt is None:
# No registry entry: fall back to a plain packed-int32 array,
# sized from whatever payload length actually shows up.
byte_length = len(rows[0]["data_hex"]) // 2
fmt = fallback_format(byte_length, signed=not args.unsigned)
print(f"NOTE: {command!r} has no FORMATS entry, treating its {byte_length}-byte "
f"payload as {byte_length // 4} packed int32 values. Add a FORMATS entry "
f"for proper decoding.", file=sys.stderr)
xs, channels = build_series(rows, fmt, args.x)
if not xs:
print(f"WARNING: rows for command={command!r} didn't match the expected "
f"{fmt.byte_length}-byte payload, skipping", file=sys.stderr)
continue
if command == "ADC_READ_ALL" and args.adc_channels:
try:
keep = parse_index_list(args.adc_channels)
except ValueError:
parser.error(f"--adc-channels: couldn't parse {args.adc_channels!r}")
bad = [i for i in keep if not (0 <= i < len(fmt.labels))]
if bad:
parser.error(f"--adc-channels: index out of range (0-{len(fmt.labels) - 1}): {bad}")
fmt, channels = select_channels(fmt, channels, keep)
if command == "POWER_MONITOR_READ" and fmt.group_count and fmt.group_size:
selected_metrics = [m for flag, m in
[(args.voltage, "Voltage"), (args.current, "Current"), (args.power, "Power")]
if flag]
if selected_metrics:
keep = [i for i, label in enumerate(fmt.labels)
if any(m in label for m in selected_metrics)]
fmt, channels = select_channels(fmt, channels, keep)
panels.append((command, fmt, xs, channels))
if not panels:
print("ERROR: nothing to plot", file=sys.stderr)
return 1 return 1
fmt = f"<{args.channels}{'i' if args.signed else 'I'}"
xs = []
channels = [[] for _ in range(args.channels)]
for row in rows:
if args.x == "tick":
xs.append(int(row["tick_ms"]))
else:
xs.append(datetime.fromisoformat(row["host_time"]))
values = struct.unpack(fmt, bytes.fromhex(row["data_hex"]))
for i, v in enumerate(values):
channels[i].append(v)
# sort chronologically, just in case the file wasn't strictly ordered
order = sorted(range(len(xs)), key=lambda i: xs[i])
xs = [xs[i] for i in order]
channels = [[ch[i] for i in order] for ch in channels]
if args.labels:
labels = [s.strip() for s in args.labels.split(",")]
if len(labels) != args.channels:
parser.error(f"--labels has {len(labels)} entries, expected {args.channels}")
else:
labels = [f"ch{i}" for i in range(args.channels)]
# --- dark mode plot ---
plt.style.use("dark_background") plt.style.use("dark_background")
fig, ax = plt.subplots(figsize=(13, 7)) fig, axes = plt.subplots(len(panels), 1, figsize=(13, 5 * len(panels)), sharex=True)
if len(panels) == 1:
axes = [axes]
cmap = matplotlib.colormaps["tab20"].resampled(args.channels) for ax, (command, fmt, xs, channels) in zip(axes, panels):
for i in range(args.channels): plot_subplot(ax, xs, channels, fmt, command, args.x)
ax.plot(xs, channels[i], label=labels[i], color=cmap(i), linewidth=1.2)
ax.set_title(args.title, fontsize=14, color="white")
ax.set_xlabel("tick (ms)" if args.x == "tick" else "host time")
ax.set_ylabel("value" + (" (int32)" if args.signed else " (uint32)"))
ax.grid(True, alpha=0.25)
axes[-1].set_xlabel("tick (ms)" if args.x == "tick" else "host time")
if args.x == "host": if args.x == "host":
fig.autofmt_xdate() fig.autofmt_xdate()
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S"))
ax.legend(ncol=min(6, args.channels), fontsize=8, loc="upper center", fig.suptitle(args.title, fontsize=15, color="white")
bbox_to_anchor=(0.5, -0.12), frameon=False) fig.tight_layout(rect=(0, 0, 1, 0.97))
fig.tight_layout()
if args.output: if args.output:
fig.savefig(args.output, dpi=150, facecolor=fig.get_facecolor()) fig.savefig(args.output, dpi=150, facecolor=fig.get_facecolor())
print(f"Saved plot to {args.output} ({len(xs)} samples, {args.channels} channels)") summary = ", ".join(f"{c} ({len(xs)} samples)" for c, _, xs, _ in panels)
print(f"Saved plot to {args.output}: {summary}")
else: else:
plt.show() plt.show()
+3
View File
@@ -45,6 +45,9 @@ COMMANDS = {
"ADC_READ_ALL": 9, "ADC_READ_ALL": 9,
"ADC_SET_READ": 10, "ADC_SET_READ": 10,
"DIGITAL_IN": 11, "DIGITAL_IN": 11,
"DIGITAL_LEG_OUT": 12,
"POWER_MONITOR_SET_READ": 13,
"POWER_MONITOR_READ": 14,
} }
COMMAND_NAMES = {v: k for k, v in COMMANDS.items()} COMMAND_NAMES = {v: k for k, v in COMMANDS.items()}
@@ -49,6 +49,9 @@ COMMANDS = {
"ADC_READ_ALL": 9, "ADC_READ_ALL": 9,
"ADC_SET_READ": 10, "ADC_SET_READ": 10,
"DIGITAL_IN": 11, "DIGITAL_IN": 11,
"DIGITAL_LEG_OUT": 12,
"POWER_MONITOR_SET_READ": 13,
"POWER_MONITOR_READ": 14,
} }
COMMAND_NAMES = {v: k for k, v in COMMANDS.items()} COMMAND_NAMES = {v: k for k, v in COMMANDS.items()}
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
import serial
import struct
import threading
import time
PORT = "/dev/ttyACM0"
BAUDRATE = 115200
COMMAND_PREFIX = 0x69
COMMAND_ACK = 0
COMMAND_NACK = 1
LED_TOGGLE = 2
LED_SET = 3
SERVO_SET = 4
SERVO_SET_ALL = 5
DIGITAL_LEG_OUT = 12
DEVICE_ID = 0
# Header layout: prefix, length, id, command, tick(int64 LE), crc
TICK_FORMAT = "<q" # signed 64-bit little-endian, milliseconds
TICK_SIZE = struct.calcsize(TICK_FORMAT) # 8
CRC_OFFSET = 4 + TICK_SIZE # index of the crc byte -> 12
HEADER_SIZE = CRC_OFFSET + 1 # bytes before data -> 13
def calculate_crc(msg: bytes) -> int:
s = sum(msg) & 0xFF
return (-s) & 0xFF
def make_packet(command: int, data: bytes = b"", tick: int = 0) -> bytes:
length = len(data)
pkt = bytearray()
pkt.append(COMMAND_PREFIX)
pkt.append(length)
pkt.append(DEVICE_ID)
pkt.append(command)
pkt.extend(struct.pack(TICK_FORMAT, tick))
pkt.append(0) # CRC placeholder
pkt.extend(data)
pkt[CRC_OFFSET] = calculate_crc(pkt[:CRC_OFFSET] + pkt[CRC_OFFSET + 1:])
return bytes(pkt)
def verify_crc(packet: bytes) -> bool:
crc = packet[CRC_OFFSET]
calc = calculate_crc(packet[:CRC_OFFSET] + packet[CRC_OFFSET + 1:])
return crc == calc
def packet_size(buf: bytes):
if len(buf) < 2:
return None
return HEADER_SIZE + buf[1]
def unpack_tick(packet: bytes) -> int:
return struct.unpack(TICK_FORMAT, packet[4:4 + TICK_SIZE])[0]
def reader(ser):
rx = bytearray()
while True:
data = ser.read(64)
if not data:
continue
rx.extend(data)
while rx:
# Binary packet?
if rx[0] == COMMAND_PREFIX:
size = packet_size(rx)
if size is None or len(rx) < size:
break
pkt = bytes(rx[:size])
del rx[:size]
if not verify_crc(pkt):
print("RX: Bad CRC:", pkt.hex())
continue
length = pkt[1]
dev_id = pkt[2]
cmd = pkt[3]
tick = unpack_tick(pkt)
if cmd == COMMAND_ACK:
print(f"<-- ACK (device={dev_id}, tick={tick}ms)")
elif cmd == COMMAND_NACK:
print(f"<-- NACK (device={dev_id}, tick={tick}ms)")
else:
print(f"<-- Command {cmd} (tick={tick}ms) len={length}")
else:
# ASCII log output
idx = rx.find(b'\n')
if idx == -1:
break
line = rx[:idx + 1]
del rx[:idx + 1]
try:
print("[LOG]", line.decode().rstrip())
except UnicodeDecodeError:
print("[RAW]", line.hex())
def main():
ser = serial.Serial(PORT, BAUDRATE, timeout=0.05)
threading.Thread(target=reader, args=(ser,), daemon=True).start()
for i in range(0x3f):
data = struct.pack("<B", i) # explicitly uint8_t, raises if i is out of 0-255 range
ser.write(make_packet(DIGITAL_LEG_OUT, data, tick=0))
time.sleep(0.1)
print("Done.")
time.sleep(2)
if __name__ == "__main__":
main()
+28 -6
View File
@@ -1,6 +1,5 @@
#include "adc.h" #include "adc.h"
#include "command_message.h" #include "command_handler.h"
#include "usb.h"
#include "mux.h" #include "mux.h"
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
@@ -36,6 +35,11 @@ struct k_msgq adc_mask_msgq;
// ADC TIMER // ADC TIMER
struct k_timer adc_timer; struct k_timer adc_timer;
// ADC BUFFER
#define ADC_MSG_BUFFER_SIZE 10
struct command_message_t adc_msg_buffer[ADC_MSG_BUFFER_SIZE];
struct command_message_t *adc_msg_buffer_ptr;
static void adc_timer_handler(struct k_timer *timer) { static void adc_timer_handler(struct k_timer *timer) {
// Timer trigger // Timer trigger
@@ -68,9 +72,13 @@ static void adc_thread(void *p1, void *p2, void *p3) {
} }
// Send to USB // Send to USB
struct command_message_t *msg = usb_get_next_tx_buf(); command_create_message(adc_msg_buffer_ptr, sizeof(values), ADC_READ_ALL, (uint8_t *)values);
command_create_message(msg, sizeof(values), ADC_READ_ALL, (uint8_t *)values); command_handler_tx(adc_msg_buffer_ptr);
usb_send_command(msg); adc_msg_buffer_ptr++;
if (adc_msg_buffer_ptr > &adc_msg_buffer[ADC_MSG_BUFFER_SIZE-1]) {
adc_msg_buffer_ptr = adc_msg_buffer;
}
} }
} }
@@ -83,6 +91,10 @@ int adc_init_all(void) {
// TIMER // TIMER
k_timer_init(&adc_timer, adc_timer_handler, NULL); k_timer_init(&adc_timer, adc_timer_handler, NULL);
// BUFFER
memset(adc_msg_buffer, 0, sizeof(adc_msg_buffer));
adc_msg_buffer_ptr = adc_msg_buffer;
// MUX init // MUX init
ret = mux_init(); ret = mux_init();
if (ret != 0) { if (ret != 0) {
@@ -138,7 +150,6 @@ int adc_init_all(void) {
return 0; return 0;
} }
int adc_read_id(int id) { int adc_read_id(int id) {
int ret; int ret;
int val_mv = 0; int val_mv = 0;
@@ -211,3 +222,14 @@ int adc_timer_set(int interval_ms) {
return 0; return 0;
} }
void* adc_get_buffer() {
struct command_message_t *ptr = adc_msg_buffer_ptr;
ptr--;
if (ptr < &adc_msg_buffer[0]) {
ptr = &adc_msg_buffer[ADC_MSG_BUFFER_SIZE-1];
}
return (void *)ptr;
}
+1
View File
@@ -12,6 +12,7 @@ int adc_init_all();
int adc_read_id(int id); int adc_read_id(int id);
int adc_add_mask(uint32_t mask); int adc_add_mask(uint32_t mask);
int adc_timer_set(int interval_ms); int adc_timer_set(int interval_ms);
void* adc_get_buffer();
#endif // ADC_H #endif // ADC_H
+67 -6
View File
@@ -4,12 +4,33 @@
#include "servo.h" #include "servo.h"
#include "adc.h" #include "adc.h"
#include "usb.h" #include "usb.h"
#include "digital_out.h"
#include "power_monitor.h"
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(command_handler, LOG_LEVEL_INF); LOG_MODULE_REGISTER(command_handler, LOG_LEVEL_INF);
int command_handler(struct command_message_t *msg) {
// BUFFER (add ack and nack at the and as static)
#define CMD_MSG_BUFFER_SIZE 10
struct command_message_t cmd_msg_buffer[CMD_MSG_BUFFER_SIZE + 2];
struct command_message_t *cmd_msg_buffer_ptr;
int command_handler_init() {
// BUFFER
memset(cmd_msg_buffer, 0, sizeof(cmd_msg_buffer));
cmd_msg_buffer_ptr = cmd_msg_buffer;
// CREATE ACK/NACK
command_create_ack(&cmd_msg_buffer[CMD_MSG_BUFFER_SIZE]);
command_create_nack(&cmd_msg_buffer[CMD_MSG_BUFFER_SIZE + 1]);
return 0;
}
int command_handler_rx(struct command_message_t *msg) {
if (msg == NULL) { if (msg == NULL) {
LOG_ERR("Received NULL message pointer"); LOG_ERR("Received NULL message pointer");
return -EINVAL; return -EINVAL;
@@ -69,9 +90,9 @@ int command_handler(struct command_message_t *msg) {
int channel = msg->data[0]; int channel = msg->data[0];
int value = adc_read_id(channel); int value = adc_read_id(channel);
struct command_message_t *reply = usb_get_next_tx_buf(); struct command_message_t *reply = cmd_get_next_tx_buf();
command_create_message(reply, sizeof(value), msg->command, (uint8_t *)&value); command_create_message(reply, sizeof(value), msg->command, (uint8_t *)&value);
usb_send_command(reply); command_handler_tx(reply);
break; break;
} }
@@ -81,9 +102,9 @@ int command_handler(struct command_message_t *msg) {
values[i] = adc_read_id(i); values[i] = adc_read_id(i);
} }
struct command_message_t *reply = usb_get_next_tx_buf(); struct command_message_t *reply = cmd_get_next_tx_buf();
command_create_message(reply, sizeof(values), msg->command, (uint8_t *)values); command_create_message(reply, sizeof(values), msg->command, (uint8_t *)values);
usb_send_command(reply); command_handler_tx(reply);
break; break;
} }
@@ -98,7 +119,7 @@ int command_handler(struct command_message_t *msg) {
} }
case ADC_READ_ALL: { case ADC_READ_ALL: {
uint32_t mask = 0b1111111111111111; uint32_t mask = 0b111111111111111111;
adc_add_mask(mask); adc_add_mask(mask);
@@ -114,6 +135,23 @@ int command_handler(struct command_message_t *msg) {
break; break;
} }
case DIGITAL_LEG_OUT: {
uint8_t mask = msg->data[0];
digital_out_set_leg(mask);
break;
}
case POWER_MONITOR_SET_READ: {
int interval_ms;
memcpy(&interval_ms, &msg->data[0], sizeof(int));
power_monitor_timer_set(interval_ms);
break;
}
default: { default: {
LOG_WRN("Unknown command received: %d", msg->command); LOG_WRN("Unknown command received: %d", msg->command);
return -EINVAL; return -EINVAL;
@@ -122,3 +160,26 @@ int command_handler(struct command_message_t *msg) {
return 0; return 0;
} }
struct command_message_t* cmd_get_next_tx_buf() {
struct command_message_t *buf = cmd_msg_buffer_ptr;
// Increment the buffer pointer
cmd_msg_buffer_ptr++;
if (cmd_msg_buffer_ptr > &cmd_msg_buffer[CMD_MSG_BUFFER_SIZE-1]) {
cmd_msg_buffer_ptr = cmd_msg_buffer;
}
return buf;
}
int command_handler_tx(struct command_message_t *msg) {
// Send over USB if enabled and ready
if (usb_is_tx_enabled()) {
usb_send_command(msg);
}
return 0;
}
+4 -7
View File
@@ -4,13 +4,10 @@
#include "command_message.h" #include "command_message.h"
/** int command_handler_init();
* @brief Process received command message int command_handler_rx(struct command_message_t *msg);
* struct command_message_t* cmd_get_next_tx_buf();
* @param msg Command message to process int command_handler_tx(struct command_message_t *msg);
* @return 0 on success, negative errno on failure
*/
int command_handler(struct command_message_t *msg);
#endif // COMMAND_HANDLER_H #endif // COMMAND_HANDLER_H
+4 -1
View File
@@ -23,6 +23,9 @@ typedef enum {
ADC_READ_ALL, ADC_READ_ALL,
ADC_SET_READ, ADC_SET_READ,
DIGITAL_IN, DIGITAL_IN,
DIGITAL_LEG_OUT,
POWER_MONITOR_SET_READ,
POWER_MONITOR_READ,
// Keep last // Keep last
NUM_COMMANDS, NUM_COMMANDS,
@@ -80,7 +83,7 @@ void command_create_nack(struct command_message_t *msg);
/** /**
* @brief Print the command with LOG * @brief Print the command with LOG
* *
* @param msg Message to calculate CRC for * @param msg Message to LOG
*/ */
void command_log(struct command_message_t *msg); void command_log(struct command_message_t *msg);
+4 -4
View File
@@ -1,5 +1,5 @@
#include "digital_in.h" #include "digital_in.h"
#include "usb.h" #include "command_handler.h"
#include <zephyr/kernel.h> #include <zephyr/kernel.h>
#include <zephyr/device.h> #include <zephyr/device.h>
@@ -20,9 +20,9 @@ static struct gpio_callback digital_in_cb_data;
void digital_in_irq_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { void digital_in_irq_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins) {
// TODO: what happens // TODO: what happens
struct command_message_t *msg = usb_get_next_tx_buf(); struct command_message_t *msg = cmd_get_next_tx_buf();
command_create_message(msg, sizeof(pins), DIGITAL_IN, (uint8_t *)&pins); command_create_message(msg, sizeof(pins), DIGITAL_IN, (uint8_t *)&pins);
usb_send_command(msg); command_handler_tx(msg);
} }
int digital_in_pin_init(const struct gpio_dt_spec *gpio, uint32_t *bitmask) { int digital_in_pin_init(const struct gpio_dt_spec *gpio, uint32_t *bitmask) {
@@ -30,7 +30,7 @@ int digital_in_pin_init(const struct gpio_dt_spec *gpio, uint32_t *bitmask) {
if (!gpio_is_ready_dt(gpio)) { if (!gpio_is_ready_dt(gpio)) {
LOG_ERR("GPIO device %s is not ready\n", gpio->port->name); LOG_ERR("GPIO device %s is not ready\n", gpio->port->name);
return -EBUSY; return -ENODEV;
} }
ret = gpio_pin_configure_dt(gpio, GPIO_INPUT); ret = gpio_pin_configure_dt(gpio, GPIO_INPUT);
+61
View File
@@ -0,0 +1,61 @@
#include "digital_out.h"
#include <zephyr/devicetree.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/gpio.h>
LOG_MODULE_REGISTER(digital_out, LOG_LEVEL_INF);
#define DIGITAL_OUT_MACRO(node_id) GPIO_DT_SPEC_GET(node_id, gpios),
static const struct gpio_dt_spec digital_leg_outputs[] = {
DT_FOREACH_CHILD(DT_NODELABEL(digital_outputs), DIGITAL_OUT_MACRO)
};
static int digital_out_pin_init(const struct gpio_dt_spec *gpio) {
int ret;
if (!device_is_ready(gpio->port)) {
LOG_ERR("GPIO device %s is not ready\n", gpio->port->name);
return -ENODEV;
}
ret = gpio_pin_configure_dt(gpio, GPIO_OUTPUT_INACTIVE);
if (ret != 0) {
LOG_ERR("Failed to configure %s pin %d\n", gpio->port->name, gpio->pin);
return ret;
}
// Turn it off
ret = gpio_pin_set_dt(gpio, 0);
if (ret != 0) {
LOG_ERR("Failed to initialize %s pin %d\n", gpio->port->name, gpio->pin);
return ret;
}
return 0;
}
int digital_out_init() {
// Initialize the LEG enable pins
for (int i = 0; i < DIGITAL_OUT_LEG_CHANNELS; i++) {
digital_out_pin_init(&digital_leg_outputs[i]);
}
return 0;
}
int digital_out_set_leg(uint8_t mask) {
for (int i = 0; i < DIGITAL_OUT_LEG_CHANNELS; i++) {
if ((mask >> i) & 0x1) {
gpio_pin_set_dt(&digital_leg_outputs[i], 1);
}
else {
gpio_pin_set_dt(&digital_leg_outputs[i], 0);
}
}
return 0;
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef DIGITAL_OUT_H
#define DIGITAL_OUT_H
#include <stdint.h>
#define DIGITAL_OUT_LEG_CHANNELS 6
int digital_out_init();
int digital_out_set_leg(uint8_t mask);
#endif // DIGITAL_OUT_H
+25
View File
@@ -3,6 +3,9 @@
#include "servo.h" #include "servo.h"
#include "adc.h" #include "adc.h"
#include "digital_in.h" #include "digital_in.h"
#include "digital_out.h"
#include "power_monitor.h"
#include "command_handler.h"
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF); LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
@@ -11,12 +14,20 @@ LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
int main(void) { int main(void) {
int ret; int ret;
// COMMAND HANDLER init
ret = command_handler_init();
if (ret != 0) {
LOG_ERR("Failed to enable COMMAND HANDLER");
return 0;
}
// USB init // USB init
ret = usb_init(); ret = usb_init();
if (ret != 0) { if (ret != 0) {
LOG_ERR("Failed to enable USB"); LOG_ERR("Failed to enable USB");
return 0; return 0;
} }
usb_set_tx_state(1);
// LED init // LED init
ret = led_init(); ret = led_init();
@@ -46,5 +57,19 @@ int main(void) {
return 0; return 0;
} }
// DIGITAL OUT init
ret = digital_out_init();
if (ret != 0) {
LOG_ERR("Failed to enable DIGITAL OUT");
return 0;
}
// POWER MONITOR init
ret = power_monitor_init();
if (ret != 0) {
LOG_ERR("Failed to enable POWER MONITOR");
return 0;
}
return 0; return 0;
} }
+133
View File
@@ -0,0 +1,133 @@
#include "power_monitor.h"
#include "command_handler.h"
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/sensor.h>
LOG_MODULE_REGISTER(power_monitor, LOG_LEVEL_INF);
// DEVICE
#define POWER_MONITOR_NODE DT_NODELABEL(power_monitor)
const struct device *power_monitor_dt = DEVICE_DT_GET(POWER_MONITOR_NODE);
// THREAD
static struct k_thread power_monitor_thread_data;
static k_tid_t power_monitor_thread_id = NULL;
#define POWER_MONITOR_THREAD_STACK_SIZE 2048
#define POWER_MONITOR_THREAD_PRIORITY 5
K_THREAD_STACK_DEFINE(power_monitor_thread_stack, POWER_MONITOR_THREAD_STACK_SIZE);
// TIMER
struct k_timer power_monitor_timer;
struct k_sem power_monitor_semaphore;
// BUFFER
#define PWR_MSG_BUFFER_SIZE 10
struct command_message_t pwr_msg_buffer[PWR_MSG_BUFFER_SIZE];
struct command_message_t *pwr_msg_buffer_ptr;
static void power_monitor_timer_handler(struct k_timer *timer) {
// Timer trigger
k_sem_give(&power_monitor_semaphore);
}
static void power_monitor_thread(void *p1, void *p2, void *p3) {
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
while (1) {
k_sem_take(&power_monitor_semaphore, K_FOREVER);
struct sensor_value values[3][3];
sensor_sample_fetch(power_monitor_dt);
// Read channel 1
struct sensor_value value = {1, 0};
sensor_attr_set(power_monitor_dt, SENSOR_CHAN_ALL, (SENSOR_ATTR_PRIV_START+1), &value);
sensor_channel_get(power_monitor_dt, SENSOR_CHAN_VOLTAGE, &values[0][0]);
sensor_channel_get(power_monitor_dt, SENSOR_CHAN_CURRENT, &values[0][1]);
sensor_channel_get(power_monitor_dt, SENSOR_CHAN_POWER, &values[0][2]);
// Read channel 2
value.val1 = 2;
sensor_attr_set(power_monitor_dt, SENSOR_CHAN_ALL, (SENSOR_ATTR_PRIV_START+1), &value);
sensor_channel_get(power_monitor_dt, SENSOR_CHAN_VOLTAGE, &values[1][0]);
sensor_channel_get(power_monitor_dt, SENSOR_CHAN_CURRENT, &values[1][1]);
sensor_channel_get(power_monitor_dt, SENSOR_CHAN_POWER, &values[1][2]);
// Read channel 3
value.val1 = 3;
sensor_attr_set(power_monitor_dt, SENSOR_CHAN_ALL, (SENSOR_ATTR_PRIV_START+1), &value);
sensor_channel_get(power_monitor_dt, SENSOR_CHAN_VOLTAGE, &values[2][0]);
sensor_channel_get(power_monitor_dt, SENSOR_CHAN_CURRENT, &values[2][1]);
sensor_channel_get(power_monitor_dt, SENSOR_CHAN_POWER, &values[2][2]);
// Send to USB
command_create_message(pwr_msg_buffer_ptr, sizeof(values), POWER_MONITOR_READ, (uint8_t *)values);
command_handler_tx(pwr_msg_buffer_ptr);
pwr_msg_buffer_ptr++;
if (pwr_msg_buffer_ptr > &pwr_msg_buffer[PWR_MSG_BUFFER_SIZE-1]) {
pwr_msg_buffer_ptr = pwr_msg_buffer;
}
}
}
int power_monitor_init() {
k_sem_init(&power_monitor_semaphore, 0, 1);
// TIMER
k_timer_init(&power_monitor_timer, power_monitor_timer_handler, NULL);
// BUFFER
memset(pwr_msg_buffer, 0, sizeof(pwr_msg_buffer));
pwr_msg_buffer_ptr = pwr_msg_buffer;
// DEVICE
if (!device_is_ready(power_monitor_dt)) {
LOG_ERR("INA3221 not ready\n");
return 0;
}
// THREAD
power_monitor_thread_id = k_thread_create(
&power_monitor_thread_data,
power_monitor_thread_stack,
K_THREAD_STACK_SIZEOF(power_monitor_thread_stack),
power_monitor_thread,
NULL, NULL, NULL,
POWER_MONITOR_THREAD_PRIORITY,
0,
K_NO_WAIT
);
return 0;
}
int power_monitor_timer_set(int interval_ms) {
if (interval_ms > 0) {
k_timer_start(&power_monitor_timer, K_MSEC(interval_ms), K_MSEC(interval_ms));
}
else {
k_timer_stop(&power_monitor_timer);
}
return 0;
}
void* power_monitor_get_buffer() {
struct command_message_t *ptr = pwr_msg_buffer_ptr;
ptr--;
if (ptr < &pwr_msg_buffer[0]) {
ptr = &pwr_msg_buffer[PWR_MSG_BUFFER_SIZE-1];
}
return (void *)ptr;
}
+10
View File
@@ -0,0 +1,10 @@
#ifndef POWER_MONITOR_H
#define POWER_MONITOR_H
int power_monitor_init();
int power_monitor_timer_set(int interval_ms);
void* power_monitor_get_buffer();
#endif // POWER_MONITOR_H
+26 -27
View File
@@ -16,6 +16,8 @@ LOG_MODULE_REGISTER(usb, LOG_LEVEL_INF);
// DEVICE // DEVICE
const struct device *const uart_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart); const struct device *const uart_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
static struct usbd_context *usb_context; static struct usbd_context *usb_context;
int usb_device_ready = 0;
int usb_tx_enabled = 0;
// RX THREAD // RX THREAD
static struct k_thread usb_rx_thread_data; static struct k_thread usb_rx_thread_data;
@@ -35,12 +37,12 @@ static k_tid_t usb_tx_thread_id = NULL;
#define USB_TX_THREAD_STACK_SIZE 512 #define USB_TX_THREAD_STACK_SIZE 512
K_THREAD_STACK_DEFINE(usb_tx_thread_stack, USB_TX_THREAD_STACK_SIZE); K_THREAD_STACK_DEFINE(usb_tx_thread_stack, USB_TX_THREAD_STACK_SIZE);
// TX BUFFER (add ack and nack at the and as static) // TX MSG BUFFER
#define TX_BUFFER_SIZE 10 #define TX_BUFFER_SIZE 10
struct command_message_t usb_tx_buffer[TX_BUFFER_SIZE + 2]; char usb_tx_ptr_msgq_buffer[(TX_BUFFER_SIZE) * sizeof(struct command_message_t *)];
struct command_message_t *usb_tx_buf_ptr;
char usb_tx_ptr_msgq_buffer[(TX_BUFFER_SIZE + 2) * sizeof(struct command_message_t *)];
struct k_msgq usb_tx_ptr_msgq; struct k_msgq usb_tx_ptr_msgq;
struct command_message_t ack;
struct command_message_t nack;
// ACK / NACK messages // ACK / NACK messages
#define RETURN_ACK true #define RETURN_ACK true
@@ -121,28 +123,28 @@ static void usb_rx_thread(void *p1, void *p2, void *p3) {
if (calculated_crc != msg.crc) { if (calculated_crc != msg.crc) {
if (RETURN_ACK) { if (RETURN_ACK) {
// Send NACK // Send NACK
usb_tx_buffer[TX_BUFFER_SIZE + 1].tick = k_uptime_get(); nack.tick = k_uptime_get();
usb_tx_buffer[TX_BUFFER_SIZE + 1].crc = command_calculate_crc(&usb_tx_buffer[TX_BUFFER_SIZE + 1]); nack.crc = command_calculate_crc(&nack);
usb_send_command(&usb_tx_buffer[TX_BUFFER_SIZE + 1]); usb_send_command(&nack);
} }
continue; continue;
} }
int ret = command_handler(&msg); int ret = command_handler_rx(&msg);
if (ret == 0) { if (ret == 0) {
if (RETURN_ACK) { if (RETURN_ACK) {
// Send ACK // Send ACK
usb_tx_buffer[TX_BUFFER_SIZE].tick = k_uptime_get(); ack.tick = k_uptime_get();
usb_tx_buffer[TX_BUFFER_SIZE].crc = command_calculate_crc(&usb_tx_buffer[TX_BUFFER_SIZE]); ack.crc = command_calculate_crc(&ack);
usb_send_command(&usb_tx_buffer[TX_BUFFER_SIZE]); usb_send_command(&ack);
} }
} }
else { else {
if (RETURN_ACK) { if (RETURN_ACK) {
// Send NACK // Send NACK
usb_tx_buffer[TX_BUFFER_SIZE + 1].tick = k_uptime_get(); nack.tick = k_uptime_get();
usb_tx_buffer[TX_BUFFER_SIZE + 1].crc = command_calculate_crc(&usb_tx_buffer[TX_BUFFER_SIZE + 1]); nack.crc = command_calculate_crc(&nack);
usb_send_command(&usb_tx_buffer[TX_BUFFER_SIZE + 1]); usb_send_command(&nack);
} }
} }
@@ -205,11 +207,11 @@ static void usb_tx_thread(void *p1, void *p2, void *p3) {
int usb_init() { int usb_init() {
ring_buf_init(&ringbuf, sizeof(ring_buffer), ring_buffer); ring_buf_init(&ringbuf, sizeof(ring_buffer), ring_buffer);
k_sem_init(&rx_semaphore, 0, 1); k_sem_init(&rx_semaphore, 0, 1);
usb_tx_buf_ptr = usb_tx_buffer;
k_msgq_init(&usb_tx_ptr_msgq, usb_tx_ptr_msgq_buffer, sizeof(struct command_message_t *), TX_BUFFER_SIZE); k_msgq_init(&usb_tx_ptr_msgq, usb_tx_ptr_msgq_buffer, sizeof(struct command_message_t *), TX_BUFFER_SIZE);
command_create_ack(&usb_tx_buffer[TX_BUFFER_SIZE]); // CREATE ACK/NACK
command_create_nack(&usb_tx_buffer[TX_BUFFER_SIZE + 1]); command_create_ack(&ack);
command_create_nack(&nack);
int ret; int ret;
@@ -270,20 +272,17 @@ int usb_init() {
return -ENOMEM; return -ENOMEM;
} }
usb_device_ready = 1;
return ret; return ret;
} }
struct command_message_t* usb_get_next_tx_buf() { int usb_is_tx_enabled() {
struct command_message_t *buf = usb_tx_buf_ptr; return usb_tx_enabled && usb_device_ready;
}
// Increment the buffer pointer void usb_set_tx_state(int state) {
usb_tx_buf_ptr++; usb_tx_enabled = state ? 1 : 0;
if (usb_tx_buf_ptr > &usb_tx_buffer[TX_BUFFER_SIZE-1]) {
usb_tx_buf_ptr = usb_tx_buffer;
}
return buf;
} }
int usb_send_command(struct command_message_t *msg) { int usb_send_command(struct command_message_t *msg) {
+2 -1
View File
@@ -6,7 +6,8 @@
int usb_init(); int usb_init();
struct command_message_t* usb_get_next_tx_buf(); int usb_is_tx_enabled();
void usb_set_tx_state(int state);
int usb_send_command(struct command_message_t *msg); int usb_send_command(struct command_message_t *msg);