Skip to content

Commit 589ce11

Browse files
Merge pull request #1 from lucaguazzaroni/feat-embedded-proto-can-netbooting
Feat: embedded proto can netbooting
2 parents d3df4ff + a9e72b4 commit 589ce11

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+47749
-0
lines changed

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,9 @@
3030
*.exe
3131
*.out
3232
*.app
33+
34+
# Generated by setup.py
35+
desktop/venv
36+
desktop/generated
37+
frdm-ke06z/generated
38+
/.metadata/

.gitmodules

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[submodule "EmbeddedProto"]
2+
path = EmbeddedProto
3+
url = https://github.com/Embedded-AMS/EmbeddedProto
4+
branch = 3.4.1

EmbeddedProto

Submodule EmbeddedProto added at 69f6574

README.md

+66
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,68 @@
11
# EmbeddedProto_Example_FRDM_CAN_Bootloader
22
Example in which Embedded Proto is used to do CAN net booting on a NXP FRDM-KE06Z
3+
4+
5+
![alt text](https://embeddedproto.com/wp-content/uploads/2022/04/Embedded_Proto.png "Embedded Proto Logo")
6+
7+
8+
Embedded Proto is a product of Embedded AMS B.V. For more information about Embedded Proto please visit [EmbeddedProto.com](https://EmbeddedProto.com).
9+
10+
Copyrights 2020-2024 Embedded AMS B.V. Amsterdam, [www.EmbeddedAMS.nl](https://www.EmbeddedAMS.nl), [[email protected]](mailto:[email protected])
11+
12+
13+
# Introduction
14+
15+
This repository hosts example code for Embedded Proto, the embedded implementation of Google Protocol Buffers.
16+
This is an example showing how the NXP FRDM-KE06Z boots with firmware it receives from a CAN network.
17+
18+
Commands are sent from the PC to a USB to CAN converter which is connected to the FDRM-KE06Z.
19+
20+
You can read the full tutorial [here]().
21+
22+
# Requirements
23+
24+
* [FDRM-KE06Z](https://www.nxp.com/design/design-center/development-boards-and-designs/general-purpose-mcus/freedom-development-platform-for-kinetis-ke06-mcus:FRDM-KE06Z)
25+
* MCUExpresso
26+
* USB to CAN adapter, like [this one](https://openlightlabs.com/collections/frontpage/products/canable-pro-isolated-usb-to-can-adapter)
27+
* Dupont cables
28+
* Embedded Proto version 3.4.1 (included as a submodule). The requirements of Embedded Proto are listed [here](https://github.com/Embedded-AMS/EmbeddedProto/blob/master/README.md).
29+
30+
# Installation
31+
32+
1. Install MCUXpresso if you have not already.
33+
2. Install the dependencies required by Embedded Proto. They are listed [here](https://github.com/Embedded-AMS/EmbeddedProto).
34+
3. Clone the repository and auto generate the protobuf files.
35+
36+
```shell
37+
git clone --recursive https://github.com/Embedded-AMS/EmbeddedProto_Example_FRDM_CAN_Bootloader.git
38+
cd EmbeddedProto_Example_FRDM_CAN_Bootloader
39+
python3 setup.py
40+
```
41+
42+
# Running the code
43+
44+
1. Connect the USB to CAN to the FDRM-KE06Z CAN header.
45+
2. Upload the code to the FRDM-KE06Z using MCUExpresso. The board includes the PE-Micro debugger.
46+
3. Run the following instructions
47+
48+
```shell
49+
cd EmbeddedProto_Example_FRDM_CAN_Bootloader
50+
source desktop/venv/bin/activate
51+
python3 desktop/main.py desktop/assets/frdmke06z_led_blinky_RED.bin
52+
```
53+
54+
Debug information will be shown in the terminal. After the booting is done the led will start blinking RED.
55+
56+
There is another example binary to make the led blink GREEN
57+
```shell
58+
python3 desktop/main.py desktop/assets/frdmke06z_led_blinky_RED.bin
59+
```
60+
61+
Have fun!
62+
63+
---
64+
65+
Three simple things you can do to help improve Embedded Proto:
66+
* Give private [feedback](https://EmbeddedProto.com/feedback).
67+
* Report an issue in public on [Github](https://github.com/Embedded-AMS/EmbeddedProto/issues).
68+
* Stay up to date on Embedded Proto via our [User mailing list](https://EmbeddedProto.com/signup).
7.13 KB
Binary file not shown.
7.13 KB
Binary file not shown.

desktop/main.py

+297
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
#
2+
# Copyright (C) 2020-2024 Embedded AMS B.V. - All Rights Reserved
3+
#
4+
# This file is part of Embedded Proto.
5+
#
6+
# Embedded Proto is open source software: you can redistribute it and/or
7+
# modify it under the terms of the GNU General Public License as published
8+
# by the Free Software Foundation, version 3 of the license.
9+
#
10+
# Embedded Proto is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with Embedded Proto. If not, see <https://www.gnu.org/licenses/>.
17+
#
18+
# For commercial and closed source application please visit:
19+
# <https://EmbeddedProto.com/license/>.
20+
#
21+
# Embedded AMS B.V.
22+
# Info:
23+
# info at EmbeddedProto dot com
24+
#
25+
# Postal address:
26+
# Atoomweg 2
27+
# 1627 LE, Hoorn
28+
# the Netherlands
29+
#
30+
31+
import argparse
32+
import can
33+
import subprocess
34+
import time
35+
36+
from generated import netbooting_pb2 as pb
37+
38+
39+
DEF_CAN_INTERFACE = 'can0'
40+
DEF_CAN_BITRATE = 1000000
41+
DEF_TARGET_CAN_ID = 0x801
42+
43+
44+
class RemoteDeviceNotHookedError(Exception):
45+
def __init__(self, msg='Remote device is not hooked', *args, **kwargs):
46+
super().__init__(msg, *args, **kwargs)
47+
48+
49+
class CanNetBooting:
50+
PAYLOAD_SIZE = 8
51+
FLASH_SECTOR_SIZE = 512
52+
FLASH_WRITE_MAX_SIZE = FLASH_SECTOR_SIZE // 2
53+
FLASH_AVAILABLE_SIZE = 0x10000
54+
55+
def __init__(self, interface: str, bitrate: int, remote_id: int):
56+
self._interface = interface
57+
self._bitrate = bitrate
58+
self._remote_id = remote_id
59+
self._bus = None
60+
self._hooked_up = False
61+
62+
def __enter__(self):
63+
if not self._can_interface_is_up():
64+
self._set_can_interface()
65+
66+
self._bus = can.Bus(interface="socketcan", channel=self._interface, bitrate=self._bitrate)
67+
return self
68+
69+
def __exit__(self, exc_type, exc_value, exc_tb):
70+
self._bus.shutdown()
71+
if exc_type is not None:
72+
raise exc_type(exc_value)
73+
return True
74+
75+
def _receive_packet(self, timeout : float = 1.0):
76+
if self._bus is None:
77+
return
78+
79+
msg = self._bus.recv(timeout)
80+
if msg is None:
81+
raise TimeoutError(f"No message received after '{timeout}' secs")
82+
#print(type(msg), msg)
83+
return msg
84+
85+
def _send_packet(self, payload: bytes):
86+
if len(payload) > self.PAYLOAD_SIZE:
87+
raise ValueError(f"CAN can send up to {self.PAYLOAD_SIZE} bytes")
88+
89+
msg = can.Message(
90+
arbitration_id=self._remote_id,
91+
data=payload,
92+
is_extended_id=True,
93+
is_remote_frame=False
94+
)
95+
96+
try:
97+
self._bus.send(msg)
98+
except can.CanError as e:
99+
raise e
100+
101+
def _can_interface_is_up(self):
102+
# Run 'ip -details link show {interface}' to get CAN interface details
103+
result = subprocess.run(['ip', '-details', 'link', 'show', self._interface], capture_output=True, text=True)
104+
105+
if result.returncode != 0:
106+
raise RuntimeError(f"CAN interface '{self._interface}' is not available or not up. Please connect the USB to can device")
107+
108+
# Check if the output contains the bitrate and state
109+
output = result.stdout
110+
111+
if 'state UP' in output and f'bitrate {self._bitrate}' in output:
112+
print(f"CAN self._interface '{self._interface}' is already up with bitrate {self._bitrate}.")
113+
return True
114+
else:
115+
print(f"CAN interface '{self._interface}' is either down or set with a different bitrate.")
116+
return False
117+
118+
def _set_can_interface(self):
119+
try:
120+
print(f"Setting up the '{self._interface}' interface with bitrate '{self._bitrate}'")
121+
# Define the command to set the CAN interface
122+
command = ['sudo', 'ip', 'link', 'set', self._interface, 'up', 'type', 'can', 'bitrate', str(self._bitrate)]
123+
subprocess.run(command, check=True)
124+
print(f"CAN interface '{self._interface}' set up successfully.")
125+
except subprocess.CalledProcessError as e:
126+
print(f"Failed to set up CAN interface: {e}")
127+
raise e
128+
129+
def _receive_reply(self, timeout: float = 1.0):
130+
data = bytes()
131+
start = time.time()
132+
133+
while time.time() - start < timeout:
134+
msg = self._receive_packet()
135+
136+
if msg.arbitration_id != self._remote_id:
137+
continue
138+
139+
# Empty message means EOF
140+
if msg.dlc == 0:
141+
break
142+
143+
data += msg.data
144+
145+
reply = pb.Reply()
146+
reply.ParseFromString(data)
147+
return reply
148+
149+
def _send_command(self, command: pb.Command, timeout: float = 1.0):
150+
serialized_command = command.SerializeToString()
151+
for i in range(0, len(serialized_command), self.PAYLOAD_SIZE):
152+
chunk = serialized_command[i:i + self.PAYLOAD_SIZE]
153+
self._send_packet(chunk)
154+
time.sleep(0.02)
155+
156+
# Send an empty message as EOF
157+
self._send_packet(bytes([]))
158+
time.sleep(0.02)
159+
160+
reply = self._receive_reply(timeout=timeout)
161+
162+
if reply.status != pb.Reply.Status.Succeed:
163+
command_str = str(command).replace("\n", "").replace("\r", "")
164+
raise RuntimeError(f"Command {command_str} failed")
165+
elif reply.action != command.action:
166+
raise RuntimeError(f"Action missmatch, received {reply.action} while expecting {command.action}")
167+
168+
return reply
169+
170+
def hook_up(self, attempts: int = 10, timeout: float = 1.0):
171+
command = pb.Command()
172+
command.action = pb.Action.HookUp
173+
174+
for i in range(attempts):
175+
try:
176+
_ = cnb._send_command(command, timeout=timeout)
177+
except TimeoutError:
178+
print(f"Timeout for hook up in the {i+1} attempt")
179+
continue
180+
except RuntimeError as e:
181+
print(e)
182+
continue
183+
184+
self._hooked_up = True
185+
break
186+
else:
187+
raise TimeoutError(f"Couldn't hook up after {attempts} attempts")
188+
189+
def quit(self, timeout: float = 1.0):
190+
if not self._hooked_up:
191+
raise RemoteDeviceNotHookedError()
192+
193+
command = pb.Command()
194+
command.action = pb.Action.Quit
195+
_ = cnb._send_command(command, timeout=timeout)
196+
self._hooked_up = False
197+
198+
def jump(self, timeout: float = 1.0):
199+
if not self._hooked_up:
200+
raise RemoteDeviceNotHookedError()
201+
202+
command = pb.Command()
203+
command.action = pb.Action.Jump
204+
_ = cnb._send_command(command, timeout=timeout)
205+
self._hooked_up = False
206+
207+
def erase_sector(self, address: int, timeout: float = 1.0):
208+
if not self._hooked_up:
209+
raise RemoteDeviceNotHookedError()
210+
211+
if address % self.FLASH_SECTOR_SIZE:
212+
address = address // self.FLASH_SECTOR_SIZE
213+
214+
print("erasing 0x{}".format(hex(address)))
215+
command = pb.Command()
216+
command.action = pb.Action.Erase
217+
command.address = address
218+
_ = cnb._send_command(command, timeout=timeout)
219+
220+
def write(self, address: int, data: bytes, timeout: float = 1.0):
221+
if not self._hooked_up:
222+
raise RemoteDeviceNotHookedError()
223+
224+
data_len = len(data)
225+
if data_len > self.FLASH_WRITE_MAX_SIZE:
226+
raise ValueError("Data too long ({data_len}), max_size is {self.FLASH_WRITE_MAX_SIZE}")
227+
228+
print("writting 0x{}".format(hex(address)))
229+
command = pb.Command()
230+
command.action = pb.Action.Write
231+
command.address = address
232+
command.data.len = data_len
233+
command.data.buf = data
234+
_ = cnb._send_command(command, timeout=timeout)
235+
236+
def flash_binary(self, binary: bytes):
237+
binary_len = len(binary)
238+
if binary_len > self.FLASH_AVAILABLE_SIZE:
239+
raise ValueError(f"Binary too long ({binary_len}), max size is {self.FLASH_AVAILABLE_SIZE}")
240+
241+
for address in range(0, binary_len, self.FLASH_WRITE_MAX_SIZE):
242+
if address % self.FLASH_SECTOR_SIZE == 0:
243+
self.erase_sector(address)
244+
245+
data = binary[address:address + self.FLASH_WRITE_MAX_SIZE]
246+
self.write(address, data)
247+
248+
249+
def commandline():
250+
parser = argparse.ArgumentParser(description='Set up CAN interface with a specific bitrate.')
251+
252+
parser.add_argument('binary',
253+
type=str,
254+
help="Path to the firmware binary")
255+
256+
parser.add_argument('-i', '--interface',
257+
type=str,
258+
default=DEF_CAN_INTERFACE,
259+
help=f'CAN interface to use (default: {DEF_CAN_INTERFACE})')
260+
261+
parser.add_argument('-b', '--bitrate',
262+
type=str,
263+
default=DEF_CAN_BITRATE,
264+
help=f'Bitrate to set on the CAN interface (default: {DEF_CAN_BITRATE})')
265+
266+
parser.add_argument('-t', '--target-id',
267+
type=str,
268+
default=DEF_TARGET_CAN_ID,
269+
help=f'Target device CAN ID (default: {DEF_TARGET_CAN_ID})')
270+
271+
return parser.parse_args()
272+
273+
274+
if __name__ == '__main__':
275+
args = commandline()
276+
277+
with CanNetBooting(args.interface, args.bitrate, args.target_id) as cnb:
278+
print("Hooking up...")
279+
cnb.hook_up()
280+
print("Device hooked")
281+
282+
try:
283+
with open(args.binary, 'rb') as f:
284+
binary_data = f.read()
285+
286+
print(f"Starting to flash {args.binary} ({len(binary_data)} bytes)...")
287+
cnb.flash_binary(binary_data)
288+
print(f"{args.binary} Flashed")
289+
290+
print("Jumping to the app...")
291+
cnb.jump()
292+
print("Bootloader jumped!")
293+
294+
except KeyboardInterrupt:
295+
print("Quiting the net booting...")
296+
cnb.quit()
297+
print("Quit succeed")

desktop/requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
python-can[gs_usb]
2+
protobuf==4.21.6

0 commit comments

Comments
 (0)