aboutsummaryrefslogtreecommitdiffstats
path: root/sw/bittiming/bittiming.py
blob: 2f4b733cd3dfcf6ac1d38cf5dfee14e348447f4c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
from argparse import ArgumentParser
from math import floor, ceil

MAX_BRP = 2**6

MIN_TQ_PER_BIT = 8
MAX_TQ_PER_BIT = 25

MIN_PROP_SEG = 1
MAX_PROP_SEG = 8

MIN_PS1 = 1
MAX_PS1 = 8

MIN_PS2 = 2
MAX_PS2 = 8

MIN_SJW = 1
MAX_SJW = 4

CNF2_BTLMODE = 1 << 7

def prescaler(f_osc: int, bitrate: int, tq_per_bit: int) -> int | None:
	brp: float = f_osc / (2.0 * bitrate * tq_per_bit)
	if brp.is_integer():
		return int(brp)
	else:
		return None

def is_valid_tq_per_bit(f_osc: int, bitrate: int, tq_per_bit: int) -> bool:
	brp = prescaler(f_osc, bitrate, tq_per_bit)
	return (brp is not None) and (brp <= MAX_BRP)

def valid_tqs_per_bit(f_osc: int, bitrate: int) -> list[int]:
	return list(filter(
		lambda tq_per_bit: is_valid_tq_per_bit(f_osc, bitrate, tq_per_bit),
		range(MIN_TQ_PER_BIT, MAX_TQ_PER_BIT+1)))

def prop_plus_ps1(tq_per_bit: int, samplepoint: float) -> float:
	sync: int = 1
	return samplepoint * tq_per_bit - sync

def prop_ps1(tq_per_bit, samplepoint: float) -> (int, int):
	sync: int = 1
	prop_plus_ps1: float = samplepoint * tq_per_bit - sync

	prop: int = floor(prop_plus_ps1 / 2)
	ps1: int = ceil(prop_plus_ps1 / 2)
	return (prop, ps1)

def ps2(tq_per_bit: int, prop: int, ps1: int) -> int:
	sync: int = 1
	return tq_per_bit - sync - prop - ps1

def parse_int(s: str) -> int:
	n: float = float(s)
	if n.is_integer():
		return int(n)
	else:
		raise argparse.ArgumentTypeError(f"invalid int: {s}")

def unzip(lst: list[tuple[any, any]]) -> (list[any], list[any]):
	return zip(*lst)

def fmt_freq(f: int) -> str:
	if f > 1e6:
		return f"{f/1e6}MHz"
	elif f > 1e3:
		return f"{f/1e3}kHz"
	else:
		return f"{f}Hz"

def fmt_bitrate(br: int) -> str:
	if br > 1e6:
		return f"{br/1e6}Mbps"
	elif br > 1e3:
		return f"{br/1e3}kbps"
	else:
		return f"{br}bps"

class Bittiming:
	sync: int = 1

	def __init__(self, fosc: int, bitrate: int, prop: int, ps1: int, ps2: int):
		self.fosc = fosc
		self.bitrate = bitrate
		self.prop = prop
		self.ps1 = ps1
		self.ps2 = ps2

	def samplepoint(self) -> float:
		return (self.sync + self.prop + self.ps1) / self.tq_per_bit()

	def tq_per_bit(self) -> int:
		return self.sync + self.prop + self.ps1 + self.ps2

	def prescaler(self) -> int:
		brp = self.fosc / (2 * self.bitrate * self.tq_per_bit())
		assert brp.is_integer()
		return int(brp)

	def max_sjw(self) -> int:
		return min(MAX_SJW, self.ps1, self.ps2)

	def cnf1(self) -> int:
		return (((self.max_sjw()-1) & 0x3) << 6) | ((self.prescaler()-1) & 0x3F)

	def cnf2(self) -> int:
		phseg: int = (self.ps1-1) & 0x7
		prseg: int = (self.prop-1) & 0x7
		return CNF2_BTLMODE | (phseg << 3) | prseg

	def cnf3(self) -> int:
		return (self.ps2-1) & 0x7

	def __str__(self) -> str:
		return f"fosc={fmt_freq(self.fosc)}, bitrate={fmt_bitrate(self.bitrate)}, Tq/bit={self.tq_per_bit()}, BRP={self.prescaler()}, sync={self.sync}, prop={self.prop}, PS1={self.ps1}, PS2={self.ps2}, SP={self.samplepoint()*100.0:.1f}%, SJW_max={self.max_sjw()}, (CNF0, CNF1, CNF2)=(0x{self.cnf1():02X}, 0x{self.cnf2():02X}, 0x{self.cnf3():02X})"

if __name__ == "__main__":
	argparser = ArgumentParser(description="Calculate CAN bit-timing parameters for the MCP2515")
	argparser.add_argument("-fosc", type=parse_int, required=True, help="Oscillator frequency (Hz)")
	argparser.add_argument("-bitrate", type=parse_int, required=True, help="Bitrate (bps)")
	argparser.add_argument("-samplepoint", type=float, required=True, help="Sample point (%)")
	args = argparser.parse_args()

	# Find valid numbers of time quanta per bit
	tqs_per_bit: list[int] = valid_tqs_per_bit(args.fosc, args.bitrate)

	# Calculate Propogation Segment and Phase Segment 1 for each Tq/bit value
	props: list[int]
	ps1s: list[int]
	props, ps1s = unzip(map(
		lambda tq_per_bit: prop_ps1(tq_per_bit, args.samplepoint/100.0),
		tqs_per_bit))

	# Calculate Phase Segment 2 for each Tq/bit value
	ps2s: list[int] = list(map(
		lambda tq_per_bit, prop, ps1: ps2(tq_per_bit, prop, ps1),
		tqs_per_bit, props, ps1s))

	# Print valid bit-timings and MCP2515 config register values
	bittimings: list[Bittiming] = list(map(
		lambda prop, ps1, ps2: Bittiming(args.fosc, args.bitrate, prop, ps1, ps2),
		props, ps1s, ps2s))
	for bt in bittimings:
		print(bt)