Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added CAN Equation #328

Merged
merged 11 commits into from
Dec 26, 2024
28 changes: 16 additions & 12 deletions docs/docs/firmware/can-traffic/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Each CAN frame consists of several fields with fixed bit lengths, plus a variabl
|Identifier (ID)| 11 |
|Remote Transmission Request (RTR)| 1|
| Control (DLC)| 6|
|Data| 8 bits per byte with 20% bit stuffing = 9.6x|
|Data| 8 bits per byte = 8x|
|Cyclic Redundancy Check (CRC) | 16 |
|Acknowledgement (ACK)| 2 |
|End of Frame (EOF)| 7|
Expand All @@ -38,22 +38,24 @@ Each CAN frame consists of several fields with fixed bit lengths, plus a variabl

The maximum total frame length is:

\[ 1 + 11 + 1 + 6 + 16 + 2 + 7 + 9.6x = 44 + 9.6x \text{ bits}\]
\[ 1 + 11 + 1 + 6 + 16 + 2 + 7 + 8x = 44 + 8x \text{ bits}\]

where \(x\) represents the number of bytes in the variable data field.

--------------------------------------

## Total CAN Traffic

We must multiply total bits per second by 1.2 to account for bit stuffing
ManushPatell marked this conversation as resolved.
Show resolved Hide resolved

\begin{align}
\text{Total Bits Per Second} &= \sum_{i=1}^n \left(\text{Frequency}_i \times \text{Message Length}_i \right)\\
\text{Total Bits Per Second} &= 1.2 \times \sum_{i=1}^n \left(\text{Frequency}_i \times \text{Message Length}_i \right)
\end{align}

## Total Bus Load

\begin{align}
\text{Bus Load}\%= \frac{\text{Total Bits per Second}}{\text{Baud Rate}} \times 100\%
\text{Bus Load}\% = \frac{\text{Total Bits per Second}}{\text{Baud Rate}} \times 100\%
\end{align}

--------------------------------------
Expand All @@ -66,22 +68,24 @@ Baud Rate: 500 kbaud (500,000 bits transferred per second)

|Message Type | Data Length| Frequency (Hz)| Message Length (Bits)|
|--------------|------------|---------------|---------------------|
|Battery Status| 8 | 100 | 44 + 9.6 x 8 = 121|
|Motor Control| 5 | 50 | 44 + 9.6 x 5 = 92|
|Battery Status| 8 | 100 | 44 + 8 x 8 = 108|
|Motor Control| 5 | 50 | 44 + 8 x 5 = 84|

\begin{align}
\text{Total Bits Per Second} &= \sum_{i=1}^n \left(\text{Frequency}_i \times \text{Message Length}_i \right)\\
\text{Total Bits Per Second} &= (100\text{ Hz} \times 121\text{ bits}) + (50\text{ Hz}\times 92\text{ bits})\\
&=(12100 + 4600)\text{ bits per second}\\
&= 16700\text{ bits per second}
\text{Total Bits Per Second} &= 1.2 \times \sum_{i=1}^n \left(\text{Frequency}_i \times \text{Message Length}_i \right)\\
\text{Total Bits Per Second} &= 1.2 \times \left((100\text{ Hz} \times 108\text{ bits}) + (50\text{ Hz} \times 84\text{ bits})\right)\\
&= 1.2 \times \left(10800 + 4200\right)\text{ bits per second}\\
&= 1.2 \times 15000\text{ bits per second}\\
&= 18000\text{ bits per second}
\end{align}


The bus load is the previous example can be calculated as:

$$
\text{Bus Load}\%= \frac{\text{Total Bits per Second}}{\text{Baud Rate}} \times 100\%
\text{Bus Load}\% = \frac{\text{Total Bits per Second}}{\text{Baud Rate}} \times 100\%
$$

$$
\text{Bus Load} = \left(\frac{16700}{500,000}\right) \times 100\% = 3.34\%
\text{Bus Load} = \left(\frac{18000}{500000}\right) \times 100\% = 3.60\%
$$
20 changes: 20 additions & 0 deletions scripts/cangen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,23 @@ FrontController
├─ main.cc
└─ README.md
```

## Development

We use Poetry to manage this package. Install the development environment with

```bash
poetry install
```

To run the tests:

```bash
poetry run pytest
```

To format the code using `ruff`:

```bash
poetry run ruff format
```
9 changes: 7 additions & 2 deletions scripts/cangen/cangen/can_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,14 @@ def _camel_to_snake(text):
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()


def _create_output_file_name(output_dir: str, bus_name: str, template_file_name: str) -> str:
def _create_output_file_name(
output_dir: str, bus_name: str, template_file_name: str
) -> str:
return os.path.join(
output_dir, bus_name.lower() + "_" + template_file_name.removesuffix(".jinja2")
)


def _generate_code(bus: Bus, output_dir: str):
"""
Parses DBC files, extracts information, and generates code using Jinja2
Expand Down Expand Up @@ -212,7 +215,9 @@ def _generate_code(bus: Bus, output_dir: str):
template = env.get_template(template_file_name)
rendered_code = template.render(**context)

output_file_name = _create_output_file_name(output_dir, bus.bus_name, template_file_name)
output_file_name = _create_output_file_name(
output_dir, bus.bus_name, template_file_name
)
with open(output_file_name, "w") as output_file:
output_file.write(rendered_code)
logger.info(f"Rendered code written to '{os.path.abspath(output_file_name)}'")
Expand Down
42 changes: 42 additions & 0 deletions scripts/cangen/cangen/traffic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
Author: Manush Patel
Date: 2024-11-24
https://macformula.github.io/racecar/firmware/can-traffic/
"""

from dataclasses import dataclass


@dataclass
class CANMessage:
data_length: int
frequency: int

@property
def bit_count(self):
ManushPatell marked this conversation as resolved.
Show resolved Hide resolved
return 44 + 8 * self.data_length


def calculate_bus_load(messages: list[CANMessage], can_speed: int) -> float:
"""Calculates the bus load percentage based on CAN messages and bus speed

Parameters
----------
messages : list[CANMessage]
can_speed : int

Returns
-------
float
Returns bus load as a percentage
"""
if can_speed <= 0:
raise ValueError(f"Invalid CAN speed {can_speed}. Must be positive.")

total_bits = (
sum(message.bit_count * message.frequency for message in messages) * 1.2
) # 1.2x is for bit stuffing

bus_load = total_bits / can_speed * 100

return bus_load
118 changes: 116 additions & 2 deletions scripts/cangen/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions scripts/cangen/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ cangen = "cangen.main:main"

[tool.poetry.group.dev.dependencies]
ruff = "^0.6.9"
pytest = "^8.3.3"

[build-system]
requires = ["poetry-core"]
Expand Down
60 changes: 60 additions & 0 deletions scripts/cangen/tests/test_traffic.py
ManushPatell marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from cangen.traffic import calculate_bus_load, CANMessage
import pytest


def test_no_messages():
assert (calculate_bus_load([], 500000)) == pytest.approx(0, rel=1e-5)


def test_normal_messages():
assert calculate_bus_load(
[
CANMessage(data_length=8, frequency=100),
CANMessage(data_length=5, frequency=50),
],
500000,
) == pytest.approx(3.60, rel=1e-2)


def test_bus_overload():
assert calculate_bus_load(
[
CANMessage(data_length=64, frequency=100),
CANMessage(data_length=32, frequency=200),
],
100000,
) == pytest.approx(138.72, rel=1e-5)
ManushPatell marked this conversation as resolved.
Show resolved Hide resolved


def test_high_traffic():
assert calculate_bus_load(
[
CANMessage(data_length=8, frequency=1000),
CANMessage(data_length=4, frequency=800),
],
1000000,
) == pytest.approx(20.256, rel=1e-5)


def test_zero_speed():
with pytest.raises(ValueError):
assert calculate_bus_load(
[
CANMessage(data_length=8, frequency=1000),
CANMessage(data_length=4, frequency=800),
],
0,
) == pytest.approx(0, rel=1e-5)


def test_negative_speed():
with pytest.raises(ValueError):
assert (
calculate_bus_load(
[
CANMessage(data_length=8, frequency=1000),
CANMessage(data_length=4, frequency=800),
],
-500000,
)
) == pytest.approx(0, rel=1e-5)
Loading