feat: Squash all min-features into full
This commit is contained in:
parent
0cfd6de2b3
commit
9f073db08f
3
.gitignore
vendored
3
.gitignore
vendored
@ -94,3 +94,6 @@ Pipfile
|
|||||||
# Ignore all local history of files
|
# Ignore all local history of files
|
||||||
.history
|
.history
|
||||||
.ionide
|
.ionide
|
||||||
|
|
||||||
|
# rick - keep panda_tici standalone
|
||||||
|
panda_tici/
|
||||||
|
|||||||
@ -196,6 +196,7 @@ Export('messaging')
|
|||||||
|
|
||||||
# Build other submodules
|
# Build other submodules
|
||||||
SConscript(['panda/SConscript'])
|
SConscript(['panda/SConscript'])
|
||||||
|
SConscript(['panda_tici/SConscript'])
|
||||||
|
|
||||||
# Build rednose library
|
# Build rednose library
|
||||||
SConscript(['rednose/SConscript'])
|
SConscript(['rednose/SConscript'])
|
||||||
|
|||||||
@ -10,10 +10,13 @@ $Cxx.namespace("cereal");
|
|||||||
# DO rename the structs
|
# DO rename the structs
|
||||||
# DON'T change the identifier (e.g. @0x81c2f05a394cf4af)
|
# DON'T change the identifier (e.g. @0x81c2f05a394cf4af)
|
||||||
|
|
||||||
struct CustomReserved0 @0x81c2f05a394cf4af {
|
struct DpControlsState @0x81c2f05a394cf4af {
|
||||||
|
alkaActive @0 :Bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CustomReserved1 @0xaedffd8f31e7b55d {
|
struct ModelExt @0xaedffd8f31e7b55d {
|
||||||
|
leftEdgeDetected @0 :Bool;
|
||||||
|
rightEdgeDetected @1 :Bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CustomReserved2 @0xf35cc4560bbf6ec2 {
|
struct CustomReserved2 @0xf35cc4560bbf6ec2 {
|
||||||
|
|||||||
@ -2622,8 +2622,8 @@ struct Event {
|
|||||||
# DO change the name of the field and struct
|
# DO change the name of the field and struct
|
||||||
# DON'T change the ID (e.g. @107)
|
# DON'T change the ID (e.g. @107)
|
||||||
# DON'T change which struct it points to
|
# DON'T change which struct it points to
|
||||||
customReserved0 @107 :Custom.CustomReserved0;
|
dpControlsState @107 :Custom.DpControlsState;
|
||||||
customReserved1 @108 :Custom.CustomReserved1;
|
modelExt @108 :Custom.ModelExt;
|
||||||
customReserved2 @109 :Custom.CustomReserved2;
|
customReserved2 @109 :Custom.CustomReserved2;
|
||||||
customReserved3 @110 :Custom.CustomReserved3;
|
customReserved3 @110 :Custom.CustomReserved3;
|
||||||
customReserved4 @111 :Custom.CustomReserved4;
|
customReserved4 @111 :Custom.CustomReserved4;
|
||||||
|
|||||||
@ -95,6 +95,8 @@ _services: dict[str, tuple] = {
|
|||||||
"customReservedRawData0": (True, 0.),
|
"customReservedRawData0": (True, 0.),
|
||||||
"customReservedRawData1": (True, 0.),
|
"customReservedRawData1": (True, 0.),
|
||||||
"customReservedRawData2": (True, 0.),
|
"customReservedRawData2": (True, 0.),
|
||||||
|
"dpControlsState": (False, 100., 10),
|
||||||
|
"modelExt": (True, 20.),
|
||||||
}
|
}
|
||||||
SERVICE_LIST = {name: Service(*vals) for
|
SERVICE_LIST = {name: Service(*vals) for
|
||||||
idx, (name, vals) in enumerate(_services.items())}
|
idx, (name, vals) in enumerate(_services.items())}
|
||||||
|
|||||||
@ -130,4 +130,28 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"Version", {PERSISTENT, STRING}},
|
{"Version", {PERSISTENT, STRING}},
|
||||||
{"dp_dev_last_log", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
{"dp_dev_last_log", {CLEAR_ON_ONROAD_TRANSITION, STRING}},
|
||||||
{"dp_dev_reset_conf", {CLEAR_ON_MANAGER_START, BOOL, "0"}},
|
{"dp_dev_reset_conf", {CLEAR_ON_MANAGER_START, BOOL, "0"}},
|
||||||
|
{"dp_dev_is_rhd", {PERSISTENT, BOOL, "0"}},
|
||||||
|
{"dp_dev_monitoring_disabled", {PERSISTENT, BOOL, "0"}},
|
||||||
|
{"dp_dev_beep", {PERSISTENT, BOOL, "0"}},
|
||||||
|
{"dp_lat_alka", {PERSISTENT, BOOL, "0"}},
|
||||||
|
{"dp_ui_display_mode", {PERSISTENT, INT, "0"}},
|
||||||
|
{"dp_dev_model_selected", {PERSISTENT, STRING}},
|
||||||
|
{"dp_dev_model_list", {PERSISTENT, STRING}},
|
||||||
|
{"dp_lat_lca_speed", {PERSISTENT, INT, "20"}},
|
||||||
|
{"dp_lat_lca_auto_sec", {PERSISTENT, FLOAT, "0.0"}},
|
||||||
|
{"dp_dev_go_off_road", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||||
|
{"dp_ui_hide_hud_speed_kph", {PERSISTENT, INT, "0"}},
|
||||||
|
{"dp_lon_ext_radar", {PERSISTENT, BOOL, "0"}},
|
||||||
|
{"dp_lat_road_edge_detection", {PERSISTENT, BOOL, "0"}},
|
||||||
|
{"dp_ui_rainbow", {PERSISTENT, BOOL, "0"}},
|
||||||
|
{"dp_lon_acm", {PERSISTENT, BOOL, "0"}},
|
||||||
|
{"dp_lon_aem", {PERSISTENT, BOOL, "0"}},
|
||||||
|
{"dp_lon_dtsc", {PERSISTENT, BOOL, "0"}},
|
||||||
|
{"dp_dev_audible_alert_mode", {PERSISTENT, INT, "0"}},
|
||||||
|
{"dp_dev_auto_shutdown_in", {PERSISTENT, INT, "-5"}},
|
||||||
|
{"dp_ui_lead", {PERSISTENT, INT, "0"}},
|
||||||
|
{"dp_dev_dashy", {PERSISTENT, INT, "0"}},
|
||||||
|
{"dp_dev_delay_loggerd", {PERSISTENT, INT, "0"}},
|
||||||
|
{"dp_dev_disable_connect", {PERSISTENT, BOOL, "0"}},
|
||||||
|
{"dp_dev_tethering", {PERSISTENT, BOOL, "0"}},
|
||||||
};
|
};
|
||||||
|
|||||||
0
dragonpilot/dashy/.nojekyll
Normal file
0
dragonpilot/dashy/.nojekyll
Normal file
15
dragonpilot/dashy/LICENSE.md
Normal file
15
dragonpilot/dashy/LICENSE.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
Copyright (c) 2025, Rick Lan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, and/or sublicense,
|
||||||
|
for non-commercial purposes only, subject to the following conditions:
|
||||||
|
|
||||||
|
- The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
- Commercial use (e.g. use in a product, service, or activity intended to
|
||||||
|
generate revenue) is prohibited without explicit written permission from
|
||||||
|
the copyright holder.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
46
dragonpilot/dashy/README.md
Normal file
46
dragonpilot/dashy/README.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Dashy Release Branch
|
||||||
|
|
||||||
|
This is the production-ready release branch of Dashy - Dragonpilot's All-in-one System Hub for You.
|
||||||
|
|
||||||
|
## 🚀 Quick Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone -b release https://github.com/efinilan/dashy
|
||||||
|
cd dashy
|
||||||
|
python3 backend/server.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📁 What's Included
|
||||||
|
|
||||||
|
- `backend/` - Python server with all dependencies included
|
||||||
|
- `web/` - Pre-built web interface (minified and optimized)
|
||||||
|
|
||||||
|
## 🌐 Access
|
||||||
|
|
||||||
|
After starting the server, open Chrome browser and navigate to:
|
||||||
|
```
|
||||||
|
http://<device-ip>:5088
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Requirements
|
||||||
|
|
||||||
|
- Network connection
|
||||||
|
- Port 5088 available
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
Copyright (c) 2025, Rick Lan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, and/or sublicense,
|
||||||
|
for non-commercial purposes only, subject to the following conditions:
|
||||||
|
|
||||||
|
- The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
- Commercial use (e.g. use in a product, service, or activity intended to
|
||||||
|
generate revenue) is prohibited without explicit written permission from
|
||||||
|
the copyright holder.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
1
dragonpilot/dashy/backend/.gitignore
vendored
Normal file
1
dragonpilot/dashy/backend/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
__pycache__
|
||||||
194
dragonpilot/dashy/backend/server.py
Executable file
194
dragonpilot/dashy/backend/server.py
Executable file
@ -0,0 +1,194 @@
|
|||||||
|
"""
|
||||||
|
Copyright (c) 2025, Rick Lan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, and/or sublicense,
|
||||||
|
for non-commercial purposes only, subject to the following conditions:
|
||||||
|
|
||||||
|
- The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
- Commercial use (e.g. use in a product, service, or activity intended to
|
||||||
|
generate revenue) is prohibited without explicit written permission from
|
||||||
|
the copyright holder.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
# import asyncio
|
||||||
|
# import json
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from urllib.parse import quote
|
||||||
|
# import socket
|
||||||
|
# import sys
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
from openpilot.common.params import Params
|
||||||
|
from openpilot.system.hardware import PC
|
||||||
|
|
||||||
|
|
||||||
|
# --- File Browser Settings ---
|
||||||
|
DEFAULT_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), '..') if PC else '/data/media/0/realdata')
|
||||||
|
WEB_DIST_PATH = os.path.join(os.path.dirname(__file__), "..", "web", "dist")
|
||||||
|
|
||||||
|
def get_safe_path(requested_path):
|
||||||
|
"""Ensures the requested path is within DEFAULT_DIR, preventing arbitrary file access"""
|
||||||
|
combined_path = os.path.join(DEFAULT_DIR, requested_path.lstrip('/'))
|
||||||
|
safe_path = os.path.realpath(combined_path)
|
||||||
|
if os.path.commonpath((safe_path, DEFAULT_DIR)) == DEFAULT_DIR:
|
||||||
|
return safe_path
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def list_files_api(request):
|
||||||
|
"""API endpoint to list files and folders"""
|
||||||
|
try:
|
||||||
|
path_param = request.query.get('path', '/')
|
||||||
|
safe_path = get_safe_path(path_param)
|
||||||
|
if not safe_path or not os.path.isdir(safe_path):
|
||||||
|
return web.json_response({'error': 'Invalid or Not Found Path'}, status=404)
|
||||||
|
items = []
|
||||||
|
for entry in os.listdir(safe_path):
|
||||||
|
full_path = os.path.join(safe_path, entry)
|
||||||
|
try:
|
||||||
|
stat = os.stat(full_path)
|
||||||
|
is_dir = os.path.isdir(full_path)
|
||||||
|
items.append({
|
||||||
|
'name': entry,
|
||||||
|
'is_dir': is_dir,
|
||||||
|
'mtime': datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M'),
|
||||||
|
'size': stat.st_size if not is_dir else 0
|
||||||
|
})
|
||||||
|
except FileNotFoundError:
|
||||||
|
continue
|
||||||
|
directories = sorted([item for item in items if item['is_dir']], key=lambda x: x['mtime'], reverse=True)
|
||||||
|
files = sorted([item for item in items if not item['is_dir']], key=lambda x: x['mtime'], reverse=True)
|
||||||
|
items = directories + files
|
||||||
|
relative_path = os.path.relpath(safe_path, DEFAULT_DIR)
|
||||||
|
if relative_path == '.':
|
||||||
|
relative_path = ''
|
||||||
|
return web.json_response({'path': relative_path, 'files': items})
|
||||||
|
except Exception as e:
|
||||||
|
return web.json_response({'error': str(e)}, status=500)
|
||||||
|
|
||||||
|
async def serve_player_api(request):
|
||||||
|
"""API endpoint to serve the HLS player page"""
|
||||||
|
file_path = request.query.get('file')
|
||||||
|
if not file_path:
|
||||||
|
return web.Response(text="File parameter is required.", status=400)
|
||||||
|
|
||||||
|
player_html_path = os.path.join(WEB_DIST_PATH, 'pages', 'player.html')
|
||||||
|
try:
|
||||||
|
with open(player_html_path, 'r') as f:
|
||||||
|
html_template = f.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return web.Response(text="Player HTML not found.", status=500)
|
||||||
|
|
||||||
|
encoded_path = quote(file_path)
|
||||||
|
html = html_template.replace('{{FILE_PATH}}', encoded_path)
|
||||||
|
return web.Response(text=html, content_type='text/html')
|
||||||
|
|
||||||
|
async def serve_manifest_api(request):
|
||||||
|
"""API endpoint to dynamically generate m3u8 playlist"""
|
||||||
|
file_path = request.query.get('file').lstrip('/')
|
||||||
|
if not file_path:
|
||||||
|
return web.Response(text="File parameter is required.", status=400)
|
||||||
|
encoded_path = quote(file_path)
|
||||||
|
manifest = f"""#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:60\n#EXT-X-PLAYLIST-TYPE:VOD\n#EXTINF:60.0,\n/media/{encoded_path}\n#EXT-X-ENDLIST\n"""
|
||||||
|
return web.Response(text=manifest, content_type='application/vnd.apple.mpegurl')
|
||||||
|
|
||||||
|
async def save_settings_api(request):
|
||||||
|
"""API endpoint to receive and save settings"""
|
||||||
|
try:
|
||||||
|
data = await request.json()
|
||||||
|
logging.getLogger("web_ui").info(f"Received settings to save: {data}")
|
||||||
|
return web.json_response({'status': 'success', 'message': 'Settings saved successfully!'})
|
||||||
|
except Exception as e:
|
||||||
|
logging.getLogger("web_ui").error(f"Error saving settings: {e}")
|
||||||
|
return web.json_response({'status': 'error', 'message': str(e)}, status=500)
|
||||||
|
|
||||||
|
async def init_api(request):
|
||||||
|
"""API endpoint to provide initial data to the client."""
|
||||||
|
try:
|
||||||
|
params = Params()
|
||||||
|
return web.json_response({
|
||||||
|
'is_metric': params.get_bool("IsMetric"),
|
||||||
|
'dp_dev_dashy': int(params.get("dp_dev_dashy") or 0)
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logging.getLogger("web_ui").error(f"Error fetching initial data: {e}")
|
||||||
|
return web.json_response({'error': f"Error fetching initial data: {e}"}, status=500)
|
||||||
|
|
||||||
|
async def on_startup(app):
|
||||||
|
logging.getLogger("web_ui").info("Web UI application starting up...")
|
||||||
|
|
||||||
|
async def on_cleanup(app):
|
||||||
|
logging.getLogger("web_ui").info("Web UI application shutting down...")
|
||||||
|
|
||||||
|
# --- CORS Middleware ---
|
||||||
|
@web.middleware
|
||||||
|
async def cors_middleware(request, handler):
|
||||||
|
response = await handler(request)
|
||||||
|
response.headers['Access-Control-Allow-Origin'] = '*'
|
||||||
|
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
|
||||||
|
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def handle_cors_preflight(request):
|
||||||
|
if request.method == 'OPTIONS':
|
||||||
|
headers = {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||||
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||||
|
'Access-Control-Max-Age': '86400',
|
||||||
|
}
|
||||||
|
return web.Response(status=200, headers=headers)
|
||||||
|
return await request.app['handler'](request)
|
||||||
|
|
||||||
|
def setup_aiohttp_app(host: str, port: int, debug: bool):
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
logging.getLogger("web_ui").setLevel(logging.DEBUG if debug else logging.INFO)
|
||||||
|
|
||||||
|
app = web.Application(middlewares=[cors_middleware])
|
||||||
|
app['port'] = port
|
||||||
|
|
||||||
|
# Register API endpoints
|
||||||
|
app.router.add_get("/api/init", init_api)
|
||||||
|
app.router.add_get("/api/files", list_files_api)
|
||||||
|
app.router.add_get("/api/play", serve_player_api)
|
||||||
|
app.router.add_get("/api/manifest.m3u8", serve_manifest_api)
|
||||||
|
# app.router.add_post("/api/settings", save_settings_api)
|
||||||
|
|
||||||
|
# Static files
|
||||||
|
app.router.add_static('/media', path=DEFAULT_DIR, name='media', show_index=False, follow_symlinks=False)
|
||||||
|
app.router.add_static('/download', path=DEFAULT_DIR, name='download', show_index=False, follow_symlinks=False)
|
||||||
|
app.router.add_get("/", lambda r: web.FileResponse(os.path.join(WEB_DIST_PATH, "index.html")))
|
||||||
|
app.router.add_static("/", path=WEB_DIST_PATH)
|
||||||
|
|
||||||
|
app.on_startup.append(on_startup)
|
||||||
|
app.on_cleanup.append(on_cleanup)
|
||||||
|
|
||||||
|
# Add CORS preflight handler
|
||||||
|
app.router.add_route('OPTIONS', '/{tail:.*}', handle_cors_preflight)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# rick - may need "sudo ufw allow 5088" to allow port access
|
||||||
|
parser = argparse.ArgumentParser(description="Dashy Server")
|
||||||
|
parser.add_argument("--host", type=str, default="0.0.0.0", help="Host to listen on")
|
||||||
|
parser.add_argument("--port", type=int, default=5088, help="Port to listen on")
|
||||||
|
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
app = setup_aiohttp_app(args.host, args.port, args.debug)
|
||||||
|
web.run_app(app, host=args.host, port=args.port)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
58
dragonpilot/dashy/web/dist/README.md
vendored
Normal file
58
dragonpilot/dashy/web/dist/README.md
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# dashy - dragonpilot's All-in-one System Hub for You
|
||||||
|
|
||||||
|
A modern web-based dashboard for monitoring and controlling your dragonpilot/openpilot remotely.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
These files should be served by the Dashy backend server running on your comma device.
|
||||||
|
|
||||||
|
Access the interface by navigating to `http://<comma-device-ip>:5088` in Chrome browser.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `index.html` - Main application
|
||||||
|
- `js/app.js` - Minified JavaScript bundle
|
||||||
|
- `css/styles.css` - Minified styles
|
||||||
|
- `icons/` - Favicon
|
||||||
|
- `pages/player.html` - HLS video player
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Live Driving View** - Real-time WebRTC video with augmented reality overlay
|
||||||
|
- **File Browser** - Access and stream driving recordings
|
||||||
|
- **Settings Control** - Configure vehicle and display preferences
|
||||||
|
|
||||||
|
## 🎮 Usage
|
||||||
|
|
||||||
|
### Navigation
|
||||||
|
- **Driving View** - Live camera feed with lane lines and path visualization
|
||||||
|
- **Files** - Browse `/data/media/0/realdata` recordings
|
||||||
|
- **Local Settings** - Adjust display preferences
|
||||||
|
|
||||||
|
### Display Options
|
||||||
|
- Metric/Imperial units
|
||||||
|
- HUD mode for windshield projection
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- dragonpilot/openpilot device (comma 3/3X)
|
||||||
|
- Chrome browser (recommended)
|
||||||
|
- Same network connection as vehicle
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
Copyright (c) 2025, Rick Lan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, and/or sublicense,
|
||||||
|
for non-commercial purposes only, subject to the following conditions:
|
||||||
|
|
||||||
|
- The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
- Commercial use (e.g. use in a product, service, or activity intended to
|
||||||
|
generate revenue) is prohibited without explicit written permission from
|
||||||
|
the copyright holder.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
1
dragonpilot/dashy/web/dist/css/styles.css
vendored
Normal file
1
dragonpilot/dashy/web/dist/css/styles.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
dragonpilot/dashy/web/dist/icons/icon-192x192.png
vendored
Normal file
BIN
dragonpilot/dashy/web/dist/icons/icon-192x192.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
1
dragonpilot/dashy/web/dist/index.html
vendored
Normal file
1
dragonpilot/dashy/web/dist/index.html
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!doctypehtml><html lang="en"><meta charset="UTF-8"><meta name="viewport"content="width=device-width,initial-scale=1,user-scalable=no"><title>Dashy by dragonpilot</title><link rel="icon"href="/icons/icon-192x192.png"><link rel="stylesheet"href="/css/styles.css"><div id="app-container"><nav><button id="nav-files"class="active">Files</button> <button id="nav-driving"style="display:none"class="">Driving</button> <button id="nav-local-settings"style="display:none">Local Settings</button></nav><main id="page-content"style="flex-grow:1;min-height:0"><div id="files-page"class="page active"><div id="files-breadcrumbs"></div><table id="files-table"><thead><tr><th><th>Name<th>Last Modified<th style="text-align:right">Size<th><tbody></table></div><div id="driving-page"class="page"><div id="driving-page-content"><video id="videoPlayer"autoplay playsinline muted></video><canvas id="uiCanvas"></canvas></div></div><div id="local-settings-page"class="page"><div class="settings-page-wrapper"><h1>Local Settings</h1><div id="local-settings-content"></div></div></div></main></div><script src="/js/app.js"></script>
|
||||||
1
dragonpilot/dashy/web/dist/js/app.js
vendored
Normal file
1
dragonpilot/dashy/web/dist/js/app.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dragonpilot/dashy/web/dist/lib/hls.js
vendored
Normal file
2
dragonpilot/dashy/web/dist/lib/hls.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dragonpilot/dashy/web/dist/pages/player.html
vendored
Normal file
1
dragonpilot/dashy/web/dist/pages/player.html
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!doctypehtml><title>HLS Player</title><style>body,html{margin:0;padding:0;height:100%;background-color:#000}#video{width:100%;height:100%}</style><script src="/lib/hls.js"></script><video id="video"controls autoplay></video><script>var v=document.getElementById("video"),s="/api/manifest.m3u8?file={{FILE_PATH}}";if(Hls.isSupported()){var h=new Hls;h.loadSource(s),h.attachMedia(v)}else v.canPlayType("application/vnd.apple.mpegurl")&&(v.src=s)</script>
|
||||||
161
dragonpilot/selfdrive/controls/lib/acm.py
Normal file
161
dragonpilot/selfdrive/controls/lib/acm.py
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
"""
|
||||||
|
Copyright (c) 2025, Rick Lan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, and/or sublicense,
|
||||||
|
for non-commercial purposes only, subject to the following conditions:
|
||||||
|
|
||||||
|
- The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
- Commercial use (e.g. use in a product, service, or activity intended to
|
||||||
|
generate revenue) is prohibited without explicit written permission from
|
||||||
|
the copyright holder.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
from openpilot.common.swaglog import cloudlog
|
||||||
|
|
||||||
|
# Configuration parameters
|
||||||
|
SPEED_RATIO = 0.98 # Must be within 2% over cruise speed
|
||||||
|
TTC_THRESHOLD = 3.0 # seconds - disable ACM when lead is within this time
|
||||||
|
|
||||||
|
# Emergency thresholds - IMMEDIATELY disable ACM
|
||||||
|
EMERGENCY_TTC = 2.0 # seconds - emergency situation
|
||||||
|
EMERGENCY_RELATIVE_SPEED = 10.0 # m/s (~36 km/h closing speed - only for rapid closing)
|
||||||
|
EMERGENCY_DECEL_THRESHOLD = -1.5 # m/s² - if MPC wants this much braking, emergency disable
|
||||||
|
|
||||||
|
# Safety cooldown after lead detection
|
||||||
|
LEAD_COOLDOWN_TIME = 0.5 # seconds - brief cooldown to handle sensor glitches
|
||||||
|
|
||||||
|
# Speed-based distance scaling - more practical for real traffic
|
||||||
|
SPEED_BP = [0., 10., 20., 30.] # m/s (0, 36, 72, 108 km/h)
|
||||||
|
MIN_DIST_V = [15., 20., 25., 30.] # meters - closer to original 25m baseline
|
||||||
|
|
||||||
|
|
||||||
|
class ACM:
|
||||||
|
def __init__(self):
|
||||||
|
self.enabled = False
|
||||||
|
self._is_speed_over_cruise = False
|
||||||
|
self._has_lead = False
|
||||||
|
self._active_prev = False
|
||||||
|
self._last_lead_time = 0.0 # Track when we last saw a lead
|
||||||
|
|
||||||
|
self.active = False
|
||||||
|
self.just_disabled = False
|
||||||
|
|
||||||
|
def _check_emergency_conditions(self, lead, v_ego, current_time):
|
||||||
|
"""Check for emergency conditions that require immediate ACM disable."""
|
||||||
|
if not lead or not lead.status:
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.lead_ttc = lead.dRel / max(v_ego, 0.1)
|
||||||
|
relative_speed = v_ego - lead.vLead # Positive = closing
|
||||||
|
|
||||||
|
# Speed-adaptive minimum distance
|
||||||
|
min_dist_for_speed = np.interp(v_ego, SPEED_BP, MIN_DIST_V)
|
||||||
|
|
||||||
|
# Emergency disable conditions - only for truly dangerous situations
|
||||||
|
# Require BOTH close distance AND (fast closing OR very short TTC)
|
||||||
|
if lead.dRel < min_dist_for_speed and (
|
||||||
|
self.lead_ttc < EMERGENCY_TTC or
|
||||||
|
relative_speed > EMERGENCY_RELATIVE_SPEED):
|
||||||
|
|
||||||
|
self._last_lead_time = current_time
|
||||||
|
if self.active: # Only log if we're actually disabling
|
||||||
|
cloudlog.warning(f"ACM emergency disable: dRel={lead.dRel:.1f}m, TTC={self.lead_ttc:.1f}s, relSpeed={relative_speed:.1f}m/s")
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _update_lead_status(self, lead, v_ego, current_time):
|
||||||
|
"""Update lead vehicle detection status."""
|
||||||
|
if lead and lead.status:
|
||||||
|
self.lead_ttc = lead.dRel / max(v_ego, 0.1)
|
||||||
|
|
||||||
|
if self.lead_ttc < TTC_THRESHOLD:
|
||||||
|
self._has_lead = True
|
||||||
|
self._last_lead_time = current_time
|
||||||
|
else:
|
||||||
|
self._has_lead = False
|
||||||
|
else:
|
||||||
|
self._has_lead = False
|
||||||
|
self.lead_ttc = float('inf')
|
||||||
|
|
||||||
|
def _check_cooldown(self, current_time):
|
||||||
|
"""Check if we're still in cooldown period after lead detection."""
|
||||||
|
time_since_lead = current_time - self._last_lead_time
|
||||||
|
return time_since_lead < LEAD_COOLDOWN_TIME
|
||||||
|
|
||||||
|
def _should_activate(self, user_ctrl_lon, v_ego, v_cruise, in_cooldown):
|
||||||
|
"""Determine if ACM should be active based on all conditions."""
|
||||||
|
self._is_speed_over_cruise = v_ego > (v_cruise * SPEED_RATIO)
|
||||||
|
|
||||||
|
return (not user_ctrl_lon and
|
||||||
|
not self._has_lead and
|
||||||
|
not in_cooldown and
|
||||||
|
self._is_speed_over_cruise)
|
||||||
|
|
||||||
|
def update_states(self, cc, rs, user_ctrl_lon, v_ego, v_cruise):
|
||||||
|
"""Update ACM state with multiple safety checks."""
|
||||||
|
# Basic validation
|
||||||
|
if not self.enabled or len(cc.orientationNED) != 3:
|
||||||
|
self.active = False
|
||||||
|
return
|
||||||
|
|
||||||
|
current_time = time.monotonic()
|
||||||
|
lead = rs.leadOne
|
||||||
|
|
||||||
|
# Check emergency conditions first (highest priority)
|
||||||
|
if self._check_emergency_conditions(lead, v_ego, current_time):
|
||||||
|
self.active = False
|
||||||
|
self._active_prev = self.active
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update normal lead status
|
||||||
|
self._update_lead_status(lead, v_ego, current_time)
|
||||||
|
|
||||||
|
# Check cooldown period
|
||||||
|
in_cooldown = self._check_cooldown(current_time)
|
||||||
|
|
||||||
|
# Determine if ACM should be active
|
||||||
|
self.active = self._should_activate(user_ctrl_lon, v_ego, v_cruise, in_cooldown)
|
||||||
|
|
||||||
|
# Track state changes for logging
|
||||||
|
self.just_disabled = self._active_prev and not self.active
|
||||||
|
if self.active and not self._active_prev:
|
||||||
|
cloudlog.info(f"ACM activated: v_ego={v_ego*3.6:.1f} km/h, v_cruise={v_cruise*3.6:.1f} km/h")
|
||||||
|
elif self.just_disabled:
|
||||||
|
cloudlog.info("ACM deactivated")
|
||||||
|
|
||||||
|
self._active_prev = self.active
|
||||||
|
|
||||||
|
def update_a_desired_trajectory(self, a_desired_trajectory):
|
||||||
|
"""
|
||||||
|
Modify acceleration trajectory to allow coasting.
|
||||||
|
SAFETY: Check for any strong braking request and abort.
|
||||||
|
"""
|
||||||
|
if not self.active:
|
||||||
|
return a_desired_trajectory
|
||||||
|
|
||||||
|
# SAFETY CHECK: If MPC wants significant braking, DON'T suppress it
|
||||||
|
min_accel = np.min(a_desired_trajectory)
|
||||||
|
if min_accel < EMERGENCY_DECEL_THRESHOLD:
|
||||||
|
cloudlog.warning(f"ACM aborting: MPC requested {min_accel:.2f} m/s² braking")
|
||||||
|
self.active = False # Immediately deactivate
|
||||||
|
return a_desired_trajectory # Return unmodified trajectory
|
||||||
|
|
||||||
|
# Only suppress very mild braking (> -1.0 m/s²)
|
||||||
|
# This allows coasting but preserves any meaningful braking
|
||||||
|
modified_trajectory = np.copy(a_desired_trajectory)
|
||||||
|
for i in range(len(modified_trajectory)):
|
||||||
|
if -1.0 < modified_trajectory[i] < 0:
|
||||||
|
# Only suppress very gentle braking for cruise control
|
||||||
|
modified_trajectory[i] = 0.0
|
||||||
|
# Any braking stronger than -1.0 m/s² is preserved!
|
||||||
|
|
||||||
|
return modified_trajectory
|
||||||
70
dragonpilot/selfdrive/controls/lib/aem.py
Normal file
70
dragonpilot/selfdrive/controls/lib/aem.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
"""
|
||||||
|
Copyright (c) 2025, Rick Lan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, and/or sublicense,
|
||||||
|
for non-commercial purposes only, subject to the following conditions:
|
||||||
|
|
||||||
|
- The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
- Commercial use (e.g. use in a product, service, or activity intended to
|
||||||
|
generate revenue) is prohibited without explicit written permission from
|
||||||
|
the copyright holder.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||||
|
|
||||||
|
|
||||||
|
AEM_COOLDOWN_TIME = 0.5 # seconds
|
||||||
|
|
||||||
|
SLOW_DOWN_BP = [0., 2.78, 5.56, 8.34, 11.12, 13.89, 15.28]
|
||||||
|
SLOW_DOWN_DIST = [10, 30., 50., 70., 80., 90., 120.]
|
||||||
|
|
||||||
|
# Allow throttle
|
||||||
|
# ALLOW_THROTTLE_THRESHOLD = 0.4
|
||||||
|
# MIN_ALLOW_THROTTLE_SPEED = 2.5
|
||||||
|
|
||||||
|
|
||||||
|
class AEM:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._active = False
|
||||||
|
self._cooldown_end_time = 0.0
|
||||||
|
|
||||||
|
def _perform_experimental_mode(self):
|
||||||
|
self._active = True
|
||||||
|
self._cooldown_end_time = time.monotonic() + AEM_COOLDOWN_TIME
|
||||||
|
|
||||||
|
def get_mode(self, mode):
|
||||||
|
# override mode
|
||||||
|
if time.monotonic() < self._cooldown_end_time:
|
||||||
|
mode = 'blended'
|
||||||
|
else:
|
||||||
|
self._active = False
|
||||||
|
return mode
|
||||||
|
|
||||||
|
def update_states(self, model_msg, radar_msg, v_ego):
|
||||||
|
|
||||||
|
# Stop sign/light detection
|
||||||
|
if not self._active:
|
||||||
|
if not radar_msg.leadOne.status and len(model_msg.orientation.x) == len(model_msg.position.x) == ModelConstants.IDX_N and \
|
||||||
|
model_msg.position.x[ModelConstants.IDX_N - 1] < np.interp(v_ego, SLOW_DOWN_BP, SLOW_DOWN_DIST):
|
||||||
|
self._perform_experimental_mode()
|
||||||
|
|
||||||
|
# throttle prob is low and there is no lead ahead (prep for brake?)
|
||||||
|
# if not self._active:
|
||||||
|
# if len(model_msg.meta.disengagePredictions.gasPressProbs) > 1:
|
||||||
|
# throttle_prob = model_msg.meta.disengagePredictions.gasPressProbs[1]
|
||||||
|
# else:
|
||||||
|
# throttle_prob = 1.0
|
||||||
|
|
||||||
|
# allow_throttle = throttle_prob > ALLOW_THROTTLE_THRESHOLD or v_ego <= MIN_ALLOW_THROTTLE_SPEED
|
||||||
|
# if not allow_throttle and not radar_msg.leadOne.status:
|
||||||
|
# self._perform_experimental_mode()
|
||||||
|
|
||||||
145
dragonpilot/selfdrive/controls/lib/dtsc.py
Normal file
145
dragonpilot/selfdrive/controls/lib/dtsc.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
"""
|
||||||
|
Copyright (c) 2025, Rick Lan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, and/or sublicense,
|
||||||
|
for non-commercial purposes only, subject to the following conditions:
|
||||||
|
|
||||||
|
- The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
- Commercial use (e.g. use in a product, service, or activity intended to
|
||||||
|
generate revenue) is prohibited without explicit written permission from
|
||||||
|
the copyright holder.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||||
|
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
|
||||||
|
from openpilot.common.swaglog import cloudlog
|
||||||
|
|
||||||
|
# Physics constants
|
||||||
|
COMFORT_LAT_G = 0.2 # g units - universal human comfort threshold
|
||||||
|
BASE_LAT_ACC = COMFORT_LAT_G * 9.81 # ~2.0 m/s^2
|
||||||
|
SAFETY_FACTOR = 0.9 # 10% safety margin on calculated speeds
|
||||||
|
MIN_CURVE_DISTANCE = 5.0 # meters - minimum distance to react to curves
|
||||||
|
MAX_DECEL = -2.0 # m/s^2 - maximum comfortable deceleration
|
||||||
|
|
||||||
|
|
||||||
|
class DTSC:
|
||||||
|
"""
|
||||||
|
Dynamic Turn Speed Controller - Predictive curve speed management via MPC constraints.
|
||||||
|
|
||||||
|
Core physics: v_max = sqrt(lateral_acceleration / curvature) * safety_factor
|
||||||
|
|
||||||
|
Operation:
|
||||||
|
1. Scans predicted path for curvature (up to ~10 seconds ahead)
|
||||||
|
2. Calculates safe speed for each point using physics + comfort limits
|
||||||
|
3. Identifies critical points where current speed would exceed safe speed
|
||||||
|
4. Calculates required deceleration to reach safe speed at critical point
|
||||||
|
5. Provides deceleration as MPC constraint for smooth trajectory planning
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, aggressiveness=1.0):
|
||||||
|
"""
|
||||||
|
Initialize DTSC with user-adjustable aggressiveness.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
aggressiveness: Factor to adjust lateral acceleration limit
|
||||||
|
0.7 = 30% more conservative (slower in curves)
|
||||||
|
1.0 = default balanced behavior
|
||||||
|
1.3 = 30% more aggressive (faster in curves)
|
||||||
|
"""
|
||||||
|
self.aggressiveness = np.clip(aggressiveness, 0.5, 1.5)
|
||||||
|
self.active = False
|
||||||
|
self.debug_msg = ""
|
||||||
|
cloudlog.info(f"DTSC: Initialized with aggressiveness {self.aggressiveness:.2f}")
|
||||||
|
|
||||||
|
def set_aggressiveness(self, value):
|
||||||
|
"""Update aggressiveness factor (0.5 to 1.5)."""
|
||||||
|
self.aggressiveness = np.clip(value, 0.5, 1.5)
|
||||||
|
cloudlog.info(f"DTSC: Aggressiveness updated to {self.aggressiveness:.2f}")
|
||||||
|
|
||||||
|
def get_mpc_constraints(self, model_msg, v_ego, base_a_min, base_a_max):
|
||||||
|
"""
|
||||||
|
Calculate MPC acceleration constraints based on predicted path curvature.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model_msg: ModelDataV2 containing predicted path
|
||||||
|
v_ego: Current vehicle speed (m/s)
|
||||||
|
base_a_min: Default minimum acceleration constraint
|
||||||
|
base_a_max: Default maximum acceleration constraint
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(a_min_array, a_max_array): Modified constraints for each MPC timestep
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Initialize with base constraints
|
||||||
|
a_min = np.ones(len(T_IDXS_MPC)) * base_a_min
|
||||||
|
a_max = np.ones(len(T_IDXS_MPC)) * base_a_max
|
||||||
|
|
||||||
|
# Validate model data
|
||||||
|
if not self._is_model_data_valid(model_msg):
|
||||||
|
self.active = False
|
||||||
|
return a_min, a_max
|
||||||
|
|
||||||
|
# Extract predictions for MPC horizon
|
||||||
|
v_pred = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x)
|
||||||
|
turn_rates = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.orientationRate.z)
|
||||||
|
positions = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x)
|
||||||
|
|
||||||
|
# Calculate curvature (turn_rate / velocity)
|
||||||
|
curvatures = np.abs(turn_rates / np.clip(v_pred, 1.0, 100.0))
|
||||||
|
|
||||||
|
# Calculate safe speeds
|
||||||
|
lat_acc_limit = BASE_LAT_ACC * self.aggressiveness
|
||||||
|
safe_speeds = np.sqrt(lat_acc_limit / (curvatures + 1e-6)) * SAFETY_FACTOR
|
||||||
|
|
||||||
|
# Find speed violations
|
||||||
|
speed_excess = v_pred - safe_speeds
|
||||||
|
if np.all(speed_excess <= 0):
|
||||||
|
self._deactivate()
|
||||||
|
return a_min, a_max
|
||||||
|
|
||||||
|
# Find critical point (maximum speed excess)
|
||||||
|
critical_idx = np.argmax(speed_excess)
|
||||||
|
critical_distance = positions[critical_idx]
|
||||||
|
critical_safe_speed = safe_speeds[critical_idx]
|
||||||
|
|
||||||
|
# Only act if we have sufficient distance
|
||||||
|
if critical_distance <= MIN_CURVE_DISTANCE:
|
||||||
|
self._deactivate()
|
||||||
|
return a_min, a_max
|
||||||
|
|
||||||
|
# Calculate required deceleration: a = (v_f^2 - v_i^2) / (2*d)
|
||||||
|
required_decel = (critical_safe_speed**2 - v_ego**2) / (2 * critical_distance)
|
||||||
|
required_decel = max(required_decel, MAX_DECEL)
|
||||||
|
|
||||||
|
# Apply constraint progressively until critical point
|
||||||
|
for i in range(len(T_IDXS_MPC)):
|
||||||
|
t = T_IDXS_MPC[i]
|
||||||
|
distance_at_t = v_ego * t + 0.5 * required_decel * t**2
|
||||||
|
|
||||||
|
if distance_at_t < critical_distance:
|
||||||
|
a_max[i] = min(a_max[i], required_decel)
|
||||||
|
|
||||||
|
# Update status
|
||||||
|
self.active = True
|
||||||
|
self.debug_msg = f"Curve in {critical_distance:.0f}m → {critical_safe_speed*3.6:.0f} km/h"
|
||||||
|
cloudlog.info(f"DTSC: {self.debug_msg} (aggr={self.aggressiveness:.1f})")
|
||||||
|
|
||||||
|
return a_min, a_max
|
||||||
|
|
||||||
|
def _is_model_data_valid(self, model_msg):
|
||||||
|
"""Check if model message contains valid prediction data."""
|
||||||
|
return (len(model_msg.position.x) == ModelConstants.IDX_N and
|
||||||
|
len(model_msg.velocity.x) == ModelConstants.IDX_N and
|
||||||
|
len(model_msg.orientationRate.z) == ModelConstants.IDX_N)
|
||||||
|
|
||||||
|
def _deactivate(self):
|
||||||
|
"""Clear active state and debug message."""
|
||||||
|
self.active = False
|
||||||
|
self.debug_msg = ""
|
||||||
57
dragonpilot/selfdrive/controls/lib/road_edge_detector.py
Normal file
57
dragonpilot/selfdrive/controls/lib/road_edge_detector.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Copyright (c) 2019, rav4kumar, Rick Lan, dragonpilot community, and a number of other of contributors.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, and/or sublicense,
|
||||||
|
for non-commercial purposes only, subject to the following conditions:
|
||||||
|
|
||||||
|
- The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
- Commercial use (e.g. use in a product, service, or activity intended to
|
||||||
|
generate revenue) is prohibited without explicit written permission from
|
||||||
|
the copyright holder.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
NEARSIDE_PROB = 0.2
|
||||||
|
EDGE_PROB = 0.35
|
||||||
|
|
||||||
|
class RoadEdgeDetector:
|
||||||
|
def __init__(self, enabled = False):
|
||||||
|
self._is_enabled = enabled
|
||||||
|
self.left_edge_detected = False
|
||||||
|
self.right_edge_detected = False
|
||||||
|
|
||||||
|
def update(self, road_edge_stds, lane_line_probs):
|
||||||
|
if not self._is_enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
left_road_edge_prob = np.clip(1.0 - road_edge_stds[0], 0.0, 1.0)
|
||||||
|
left_lane_nearside_prob = lane_line_probs[0]
|
||||||
|
|
||||||
|
right_road_edge_prob = np.clip(1.0 - road_edge_stds[1], 0.0, 1.0)
|
||||||
|
right_lane_nearside_prob = lane_line_probs[3]
|
||||||
|
|
||||||
|
self.left_edge_detected = bool(
|
||||||
|
left_road_edge_prob > EDGE_PROB and
|
||||||
|
left_lane_nearside_prob < NEARSIDE_PROB and
|
||||||
|
right_lane_nearside_prob >= left_lane_nearside_prob
|
||||||
|
)
|
||||||
|
|
||||||
|
self.right_edge_detected = bool(
|
||||||
|
right_road_edge_prob > EDGE_PROB and
|
||||||
|
right_lane_nearside_prob < NEARSIDE_PROB and
|
||||||
|
left_lane_nearside_prob >= right_lane_nearside_prob
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_enabled(self, enabled):
|
||||||
|
self._is_enabled = enabled
|
||||||
|
|
||||||
|
def is_enabled(self):
|
||||||
|
return self._is_enabled
|
||||||
139
dragonpilot/selfdrive/ui/beepd.py
Normal file
139
dragonpilot/selfdrive/ui/beepd.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Copyright (c) 2025, Rick Lan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, and/or sublicense,
|
||||||
|
for non-commercial purposes only, subject to the following conditions:
|
||||||
|
|
||||||
|
- The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
- Commercial use (e.g. use in a product, service, or activity intended to
|
||||||
|
generate revenue) is prohibited without explicit written permission from
|
||||||
|
the copyright holder.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from cereal import car, messaging
|
||||||
|
from openpilot.common.realtime import Ratekeeper
|
||||||
|
import threading
|
||||||
|
|
||||||
|
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
|
||||||
|
|
||||||
|
class Beepd:
|
||||||
|
def __init__(self):
|
||||||
|
self.current_alert = AudibleAlert.none
|
||||||
|
self.enable_gpio()
|
||||||
|
|
||||||
|
def enable_gpio(self):
|
||||||
|
try:
|
||||||
|
subprocess.run("echo 42 | sudo tee /sys/class/gpio/export",
|
||||||
|
shell=True,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
encoding='utf8')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
subprocess.run("echo \"out\" | sudo tee /sys/class/gpio/gpio42/direction",
|
||||||
|
shell=True,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
encoding='utf8')
|
||||||
|
|
||||||
|
def _beep(self, on):
|
||||||
|
val = "1" if on else "0"
|
||||||
|
subprocess.run(f"echo \"{val}\" | sudo tee /sys/class/gpio/gpio42/value",
|
||||||
|
shell=True,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
encoding='utf8')
|
||||||
|
|
||||||
|
def engage(self):
|
||||||
|
self._beep(True)
|
||||||
|
time.sleep(0.05)
|
||||||
|
self._beep(False)
|
||||||
|
|
||||||
|
def disengage(self):
|
||||||
|
for _ in range(2):
|
||||||
|
self._beep(True)
|
||||||
|
time.sleep(0.01)
|
||||||
|
self._beep(False)
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
def warning(self):
|
||||||
|
for _ in range(3):
|
||||||
|
self._beep(True)
|
||||||
|
time.sleep(0.01)
|
||||||
|
self._beep(False)
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
def warning_immediate(self):
|
||||||
|
for _ in range(5):
|
||||||
|
self._beep(True)
|
||||||
|
time.sleep(0.01)
|
||||||
|
self._beep(False)
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
def dispatch_beep(self, func):
|
||||||
|
threading.Thread(target=func, daemon=True).start()
|
||||||
|
|
||||||
|
def update_alert(self, new_alert):
|
||||||
|
current_alert_played_once = self.current_alert == AudibleAlert.none
|
||||||
|
if self.current_alert != new_alert and (new_alert != AudibleAlert.none or current_alert_played_once):
|
||||||
|
self.current_alert = new_alert
|
||||||
|
# if new_alert == AudibleAlert.engage:
|
||||||
|
# self.dispatch_beep(self.engage)
|
||||||
|
# if new_alert == AudibleAlert.disengage:
|
||||||
|
# self.dispatch_beep(self.disengage)
|
||||||
|
if new_alert == AudibleAlert.warningImmediate:
|
||||||
|
self.dispatch_beep(self.warning_immediate)
|
||||||
|
|
||||||
|
def get_audible_alert(self, sm):
|
||||||
|
if sm.updated['selfdriveState']:
|
||||||
|
new_alert = sm['selfdriveState'].alertSound.raw
|
||||||
|
self.update_alert(new_alert)
|
||||||
|
|
||||||
|
def test_beepd_thread(self):
|
||||||
|
frame = 0
|
||||||
|
rk = Ratekeeper(20)
|
||||||
|
pm = messaging.PubMaster(['selfdriveState'])
|
||||||
|
while True:
|
||||||
|
cs = messaging.new_message('selfdriveState')
|
||||||
|
if frame == 20:
|
||||||
|
cs.selfdriveState.alertSound = AudibleAlert.engage
|
||||||
|
if frame == 40:
|
||||||
|
cs.selfdriveState.alertSound = AudibleAlert.disengage
|
||||||
|
if frame == 60:
|
||||||
|
cs.selfdriveState.alertSound = AudibleAlert.prompt
|
||||||
|
if frame == 80:
|
||||||
|
cs.selfdriveState.alertSound = AudibleAlert.disengage
|
||||||
|
if frame == 85:
|
||||||
|
cs.selfdriveState.alertSound = AudibleAlert.prompt
|
||||||
|
|
||||||
|
pm.send("selfdriveState", cs)
|
||||||
|
frame += 1
|
||||||
|
rk.keep_time()
|
||||||
|
|
||||||
|
def beepd_thread(self, test=False):
|
||||||
|
if test:
|
||||||
|
threading.Thread(target=self.test_beepd_thread, daemon=True).start()
|
||||||
|
|
||||||
|
sm = messaging.SubMaster(['selfdriveState'])
|
||||||
|
rk = Ratekeeper(20)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
sm.update(0)
|
||||||
|
self.get_audible_alert(sm)
|
||||||
|
rk.keep_time()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
s = Beepd()
|
||||||
|
s.beepd_thread(test=False)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -57,15 +57,187 @@ class DragonpilotLayout(Widget):
|
|||||||
def _lat_toggles(self):
|
def _lat_toggles(self):
|
||||||
self._toggles["title_lat"] = simple_item(title=lambda: tr("### Lateral ###"))
|
self._toggles["title_lat"] = simple_item(title=lambda: tr("### Lateral ###"))
|
||||||
|
|
||||||
|
self._toggles["dp_lat_alka"] = toggle_item(
|
||||||
|
title=lambda: tr("Always-on Lane Keeping Assist (ALKA)"),
|
||||||
|
description=lambda: tr("Allows openpilot to always steer to keep the car in its lane."),
|
||||||
|
initial_state=self._params.get_bool("dp_lat_alka"),
|
||||||
|
callback=lambda val: self._params.put_bool("dp_lat_alka", val),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles['dp_lat_lca_speed'] = spin_button_item(
|
||||||
|
title=lambda: tr('Lane Change Assist At:'),
|
||||||
|
description=lambda: tr("Off = Disable LCA.<br>1 mph = 1.2 km/h."),
|
||||||
|
initial_value=int(self._params.get("dp_lat_lca_speed") or 20),
|
||||||
|
callback=self._on_dp_lat_lca_speed_change,
|
||||||
|
min_val=0,
|
||||||
|
max_val=100,
|
||||||
|
step=5,
|
||||||
|
suffix=tr(" mph"),
|
||||||
|
special_value_text=tr("Off"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles['dp_lat_lca_auto_sec'] = double_spin_button_item(
|
||||||
|
title=lambda: "+ " + tr('Auto Lane Change after:'),
|
||||||
|
description=lambda: tr("Off = Disable Auto Lane Change."),
|
||||||
|
initial_value=int(self._params.get("dp_lat_lca_auto_sec") or 0.0),
|
||||||
|
callback=lambda val: self._params.put("dp_lat_lca_auto_sec", float(val)),
|
||||||
|
min_val=0.0,
|
||||||
|
max_val=5.0,
|
||||||
|
step=0.5,
|
||||||
|
suffix=tr(" sec"),
|
||||||
|
special_value_text=tr("Off"),
|
||||||
|
# enabled=int(self._params.get("dp_lat_lca_speed") or 20) > 0
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles['dp_lat_road_edge_detection'] = toggle_item(
|
||||||
|
title=lambda: tr('Road Edge Detection (RED)'),
|
||||||
|
description=lambda: tr("Block lane change assist when the system detects the road edge.<br>NOTE: This will show 'Car Detected in Blindspot' warning."),
|
||||||
|
initial_state=self._params.get_bool("dp_lat_road_edge_detection"),
|
||||||
|
callback=lambda val: self._params.put_bool("dp_lat_road_edge_detection", val),
|
||||||
|
)
|
||||||
|
|
||||||
def _lon_toggles(self):
|
def _lon_toggles(self):
|
||||||
self._toggles["title_lon"] = simple_item(title=lambda: tr("### Longitudinal ###"))
|
self._toggles["title_lon"] = simple_item(title=lambda: tr("### Longitudinal ###"))
|
||||||
|
|
||||||
|
self._toggles['dp_lon_ext_radar'] = toggle_item(
|
||||||
|
title=lambda: tr("Use External Radar"),
|
||||||
|
description=lambda: tr("See https://github.com/eFiniLan/openpilot-ext-radar-addon for more information."),
|
||||||
|
initial_state=self._params.get_bool("dp_lon_ext_radar"),
|
||||||
|
callback=lambda val: self._params.put_bool("dp_lon_ext_radar", val),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles["dp_lon_acm"] = toggle_item(
|
||||||
|
title=lambda: tr("Enable Adaptive Coasting Mode (ACM)"),
|
||||||
|
description=tr("Adaptive Coasting Mode (ACM) reduces braking to allow smoother coasting when appropriate."),
|
||||||
|
initial_state=self._params.get_bool("dp_lon_acm"),
|
||||||
|
callback=lambda val: self._params.put_bool("dp_lon_acm", val),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles["dp_lon_aem"] = toggle_item(
|
||||||
|
title=lambda: tr("Adaptive Experimental Mode (AEM)"),
|
||||||
|
description=lambda: tr("Adaptive mode switcher between ACC and Blended based on driving context."),
|
||||||
|
initial_state=self._params.get_bool("dp_lon_aem"),
|
||||||
|
callback=lambda val: self._params.put_bool("dp_lon_aem", val),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles["dp_lon_dtsc"] = toggle_item(
|
||||||
|
title=lambda: tr("Dynamic Turn Speed Control (DTSC)"),
|
||||||
|
description=lambda: tr("DTSC automatically adjusts the vehicle's predicted speed based on upcoming road curvature and grip conditions.<br>Originally from the openpilot TACO branch."),
|
||||||
|
initial_state=self._params.get_bool("dp_lon_dtsc"),
|
||||||
|
callback=lambda val: self._params.put_bool("dp_lon_dtsc", val),
|
||||||
|
)
|
||||||
|
|
||||||
def _ui_toggles(self):
|
def _ui_toggles(self):
|
||||||
self._toggles["title_ui"] = simple_item(title=lambda: tr("### UI ###"))
|
self._toggles["title_ui"] = simple_item(title=lambda: tr("### UI ###"))
|
||||||
|
|
||||||
|
self._toggles["dp_ui_hide_hud_speed_kph"] = spin_button_item(
|
||||||
|
title=lambda: tr("Hide HUD When Moves above:"),
|
||||||
|
description=lambda: tr("To prevent screen burn-in, hide Speed, MAX Speed, and Steering/DM Icons when the car moves.<br>Off = Stock Behavior<br>1 km/h = 0.6 mph"),
|
||||||
|
initial_value=int(self._params.get("dp_ui_hide_hud_speed_kph") or 0),
|
||||||
|
callback=lambda val: self._params.put("dp_ui_hide_hud_speed_kph", val),
|
||||||
|
suffix=tr(" km/h"),
|
||||||
|
min_val=0,
|
||||||
|
max_val=120,
|
||||||
|
step=5,
|
||||||
|
special_value_text=tr("Off"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles["dp_ui_rainbow"] = toggle_item(
|
||||||
|
title=lambda: tr("Rainbow Driving Path like Tesla"),
|
||||||
|
description=lambda: tr("Why not?"),
|
||||||
|
initial_state=self._params.get_bool("dp_ui_rainbow"),
|
||||||
|
callback=lambda val: self._params.put_bool("dp_ui_rainbow", val),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles["dp_ui_lead"] = text_spin_button_item(
|
||||||
|
title=lambda: tr("Display Lead Stats"),
|
||||||
|
description=lambda: tr("Display the statistics of lead car and/or radar tracking points.<br>Lead: Lead stats only<br>Radar: Radar tracking point stats only<br>All: Lead and Radar stats<br>NOTE: Radar option only works on certain vehicle models."),
|
||||||
|
initial_index=int(self._params.get("dp_ui_lead") or 0),
|
||||||
|
callback=lambda val: self._params.put("dp_ui_lead", val),
|
||||||
|
options=["Off", "Lead", "Radar", "All"]
|
||||||
|
)
|
||||||
|
|
||||||
def _device_toggles(self):
|
def _device_toggles(self):
|
||||||
self._toggles["title_dev"] = simple_item(title=lambda: tr("### Device ###"))
|
self._toggles["title_dev"] = simple_item(title=lambda: tr("### Device ###"))
|
||||||
|
|
||||||
|
if "LITE" in os.environ:
|
||||||
|
self._toggles["dp_dev_is_rhd"] = toggle_item(
|
||||||
|
title=lambda: tr("Enable Right-Hand Drive Mode"),
|
||||||
|
description=lambda: tr("Allow openpilot to obey right-hand traffic conventions on right driver seat."),
|
||||||
|
initial_state=self._params.get_bool("dp_dev_is_rhd"),
|
||||||
|
callback=lambda val: self._params.put_bool("dp_dev_is_rhd", val),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles["dp_dev_monitoring_disabled"] = toggle_item(
|
||||||
|
title=lambda: tr("Disable Driver Monitoring"),
|
||||||
|
description=lambda: tr("USE AT YOUR OWN RISK."),
|
||||||
|
initial_state=self._params.get_bool("dp_dev_monitoring_disabled"),
|
||||||
|
callback=lambda val: self._params.put_bool("dp_dev_monitoring_disabled", val),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles["dp_dev_beep"] = toggle_item(
|
||||||
|
title=lambda: tr("Enable Beep (Warning)"),
|
||||||
|
description=lambda: tr("Use Buzzer for audiable alerts."),
|
||||||
|
initial_state=self._params.get_bool("dp_dev_beep"),
|
||||||
|
callback=lambda val: self._params.put_bool("dp_dev_beep", val),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles["dp_ui_display_mode"] = text_spin_button_item(
|
||||||
|
title=lambda: tr("Display Mode"),
|
||||||
|
callback=lambda val: self._params.put("dp_ui_display_mode", val),
|
||||||
|
options=["Std.", "MAIN+", "OP+", "MAIN-", "OP-"],
|
||||||
|
initial_index=int(self._params.get("dp_ui_display_mode") or 0),
|
||||||
|
description=lambda: tr("Std.: Stock behavior.<br>MAIN+: ACC MAIN on = Display ON.<br>OP+: OP enabled = Display ON.<br>MAIN-: ACC MAIN on = Display OFF<br>OP-: OP enabled = Display OFF."),
|
||||||
|
)
|
||||||
|
|
||||||
|
if "LITE" not in os.environ:
|
||||||
|
self._toggles["dp_dev_audible_alert_mode"] = text_spin_button_item(
|
||||||
|
title=lambda: tr("Audible Alert"),
|
||||||
|
description=lambda: tr("Std.: Stock behaviour.<br>Warning: Only emits sound when there is a warning.<br>Off: Does not emit any sound at all."),
|
||||||
|
initial_index=int(self._params.get("dp_dev_audible_alert_mode") or 0),
|
||||||
|
callback=lambda val: self._params.put("dp_dev_audible_alert_mode", val),
|
||||||
|
options=["Std.", "Warning", "Off"],
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles["dp_dev_auto_shutdown_in"] = spin_button_item(
|
||||||
|
title=lambda: tr("Auto Shutdown After"),
|
||||||
|
description=lambda: tr("0 mins = Immediately"),
|
||||||
|
initial_value=int(self._params.get("dp_dev_auto_shutdown_in") or -5),
|
||||||
|
callback=lambda val: self._params.put("dp_dev_auto_shutdown_in", val),
|
||||||
|
min_val=-5,
|
||||||
|
max_val=300,
|
||||||
|
step=5,
|
||||||
|
suffix=tr(" mins"),
|
||||||
|
special_value_text=tr("Off"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles["dp_dev_dashy"] = text_spin_button_item(
|
||||||
|
title=lambda: tr("dashy"),
|
||||||
|
description=lambda: tr("dashy - dragonpilot's all-in-one system hub for you.<br><br>Visit http://<device_ip>:5088 to access.<br><br>Off - Turn off dashy completely.<br>File: File Manager only.<br>All: File Manager + Live Stream."),
|
||||||
|
initial_index=int(self._params.get("dp_dev_dashy") or 0),
|
||||||
|
callback=lambda val: self._params.put("dp_dev_dashy", val),
|
||||||
|
options=[tr("Off"), tr("File"), tr("All")],
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles["dp_dev_delay_loggerd"] = spin_button_item(
|
||||||
|
title=lambda: tr("Delay Starting Loggerd for:"),
|
||||||
|
description=lambda: tr("Delays the startup of loggerd and its related processes when the device goes on-road.<br>This prevents the initial moments of a drive from being recorded, protecting location privacy at the start of a trip."),
|
||||||
|
initial_value=int(self._params.get("dp_dev_delay_loggerd") or 0),
|
||||||
|
callback=lambda val: self._params.put("dp_dev_delay_loggerd", val),
|
||||||
|
min_val=0,
|
||||||
|
max_val=300,
|
||||||
|
step=5,
|
||||||
|
suffix=tr(" secs"),
|
||||||
|
special_value_text=tr("Off"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._toggles["dp_dev_disable_connect"] = toggle_item(
|
||||||
|
title=lambda: tr("Disable Comma Connect"),
|
||||||
|
description=lambda: tr("Disable Comma connect service if you do not wish to upload / being tracked by the service."),
|
||||||
|
initial_state=self._params.get_bool("dp_dev_disable_connect"),
|
||||||
|
callback=lambda val: self._params.put_bool("dp_dev_disable_connect", val),
|
||||||
|
)
|
||||||
|
|
||||||
def _reset_dp_conf(self):
|
def _reset_dp_conf(self):
|
||||||
def reset_dp_conf(result: int):
|
def reset_dp_conf(result: int):
|
||||||
# Check engaged again in case it changed while the dialog was open
|
# Check engaged again in case it changed while the dialog was open
|
||||||
@ -85,3 +257,7 @@ class DragonpilotLayout(Widget):
|
|||||||
|
|
||||||
def _render(self, rect):
|
def _render(self, rect):
|
||||||
self._scroller.render(rect)
|
self._scroller.render(rect)
|
||||||
|
|
||||||
|
def _on_dp_lat_lca_speed_change(self, val):
|
||||||
|
self._params.put("dp_lat_lca_speed", int(val))
|
||||||
|
self._toggles['dp_lat_lca_auto_sec'].action_item.set_enabled(val > 0)
|
||||||
|
|||||||
@ -27,6 +27,33 @@ function agnos_init {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_tici_hw() {
|
||||||
|
if grep -q "tici" /sys/firmware/devicetree/base/model 2>/dev/null; then
|
||||||
|
echo "Querying panda MCU type..."
|
||||||
|
MCU_OUTPUT=$(python -c "from panda_tici import Panda; p = Panda(cli=False); print(p.get_mcu_type()); p.close()" 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ "$MCU_OUTPUT" == *"McuType.F4"* ]]; then
|
||||||
|
echo "TICI (DOS) detected"
|
||||||
|
elif [[ "$MCU_OUTPUT" == *"McuType.H7"* ]]; then
|
||||||
|
echo "TICI (TRES) detected"
|
||||||
|
export TICI_TRES=1
|
||||||
|
else
|
||||||
|
echo "TICI (UNKNOWN) detected"
|
||||||
|
fi
|
||||||
|
export TICI_HW=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
set_lite_hw() {
|
||||||
|
output=$(i2cget -y 0 0x10 0x00 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -z "$output" ]; then
|
||||||
|
echo "Lite HW"
|
||||||
|
export LITE=1
|
||||||
|
export DISABLE_DRIVER=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
function launch {
|
function launch {
|
||||||
# Remove orphaned git lock if it exists on boot
|
# Remove orphaned git lock if it exists on boot
|
||||||
[ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock
|
[ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock
|
||||||
@ -71,6 +98,8 @@ function launch {
|
|||||||
|
|
||||||
# hardware specific init
|
# hardware specific init
|
||||||
if [ -f /AGNOS ]; then
|
if [ -f /AGNOS ]; then
|
||||||
|
set_tici_hw
|
||||||
|
set_lite_hw
|
||||||
agnos_init
|
agnos_init
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -82,8 +82,8 @@ def can_fingerprint(can_recv: CanRecvCallable) -> tuple[str | None, dict[int, di
|
|||||||
|
|
||||||
# **** for use live only ****
|
# **** for use live only ****
|
||||||
def fingerprint(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, num_pandas: int,
|
def fingerprint(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, num_pandas: int,
|
||||||
cached_params: CarParamsT | None) -> tuple[str | None, dict, str, list[CarParams.CarFw], CarParams.FingerprintSource, bool]:
|
cached_params: CarParamsT | None, dp_fingerprint: str = "") -> tuple[str | None, dict, str, list[CarParams.CarFw], CarParams.FingerprintSource, bool]:
|
||||||
fixed_fingerprint = os.environ.get('FINGERPRINT', "")
|
fixed_fingerprint = os.environ.get('FINGERPRINT', dp_fingerprint)
|
||||||
skip_fw_query = os.environ.get('SKIP_FW_QUERY', False)
|
skip_fw_query = os.environ.get('SKIP_FW_QUERY', False)
|
||||||
disable_fw_cache = os.environ.get('DISABLE_FW_CACHE', False)
|
disable_fw_cache = os.environ.get('DISABLE_FW_CACHE', False)
|
||||||
ecu_rx_addrs = set()
|
ecu_rx_addrs = set()
|
||||||
@ -149,8 +149,8 @@ def fingerprint(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_mu
|
|||||||
|
|
||||||
|
|
||||||
def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, alpha_long_allowed: bool,
|
def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multiplexing: ObdCallback, alpha_long_allowed: bool,
|
||||||
is_release: bool, num_pandas: int = 1, dp_params: int = 0, cached_params: CarParamsT | None = None):
|
is_release: bool, num_pandas: int = 1, dp_params: int = 0, cached_params: CarParamsT | None = None, dp_fingerprint: str = ""):
|
||||||
candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(can_recv, can_send, set_obd_multiplexing, num_pandas, cached_params)
|
candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(can_recv, can_send, set_obd_multiplexing, num_pandas, cached_params, dp_fingerprint=dp_fingerprint)
|
||||||
|
|
||||||
if candidate is None:
|
if candidate is None:
|
||||||
carlog.error({"event": "car doesn't match any fingerprints", "fingerprints": repr(fingerprints)})
|
carlog.error({"event": "car doesn't match any fingerprints", "fingerprints": repr(fingerprints)})
|
||||||
|
|||||||
@ -61,7 +61,7 @@ class CarController(CarControllerBase):
|
|||||||
# Below are the HUD messages. We copy the stock message and modify
|
# Below are the HUD messages. We copy the stock message and modify
|
||||||
if self.CP.carFingerprint != CAR.NISSAN_ALTIMA:
|
if self.CP.carFingerprint != CAR.NISSAN_ALTIMA:
|
||||||
if self.frame % 2 == 0:
|
if self.frame % 2 == 0:
|
||||||
can_sends.append(nissancan.create_lkas_hud_msg(self.packer, CS.lkas_hud_msg, CC.enabled, hud_control.leftLaneVisible, hud_control.rightLaneVisible,
|
can_sends.append(nissancan.create_lkas_hud_msg(self.packer, CS.lkas_hud_msg, CC.latActive, hud_control.leftLaneVisible, hud_control.rightLaneVisible,
|
||||||
hud_control.leftLaneDepart, hud_control.rightLaneDepart))
|
hud_control.leftLaneDepart, hud_control.rightLaneDepart))
|
||||||
|
|
||||||
if self.frame % 50 == 0:
|
if self.frame % 50 == 0:
|
||||||
|
|||||||
162
opendbc_repo/opendbc/car/radar_interface.py
Normal file
162
opendbc_repo/opendbc/car/radar_interface.py
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
"""
|
||||||
|
Copyright (c) 2025, Rick Lan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, and/or sublicense,
|
||||||
|
for non-commercial purposes only, subject to the following conditions:
|
||||||
|
|
||||||
|
- The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
- Commercial use (e.g. use in a product, service, or activity intended to
|
||||||
|
generate revenue) is prohibited without explicit written permission from
|
||||||
|
the copyright holder.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from opendbc.car.interfaces import RadarInterfaceBase
|
||||||
|
from opendbc.can.parser import CANParser
|
||||||
|
from opendbc.car.structs import RadarData
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
# car head to radar
|
||||||
|
DREL_OFFSET = -1.52
|
||||||
|
|
||||||
|
|
||||||
|
# typically max lane width is 3.7m
|
||||||
|
LANE_WIDTH = 3.8
|
||||||
|
LANE_WIDTH_HALF = LANE_WIDTH/2
|
||||||
|
|
||||||
|
LANE_CENTER_MIN_LAT = 0.
|
||||||
|
LANE_CENTER_MAX_LAT = LANE_WIDTH_HALF
|
||||||
|
LANE_CENTER_MIN_DIST = 5.
|
||||||
|
|
||||||
|
LANE_SIDE_MIN_LAT = LANE_WIDTH_HALF
|
||||||
|
LANE_SIDE_MAX_LAT = LANE_WIDTH_HALF + LANE_WIDTH
|
||||||
|
LANE_SIDE_MIN_DIST = 10.
|
||||||
|
|
||||||
|
|
||||||
|
# lat distance, typically max lane width is 3.7m
|
||||||
|
MAX_LAT_DIST = 6.
|
||||||
|
|
||||||
|
# objects to ignore thats really close to the vehicle (after DREL_OFFSET applied)
|
||||||
|
MIN_DIST = 5.
|
||||||
|
|
||||||
|
# ignore oncoming objects
|
||||||
|
IGNORE_OBJ_STATE = 2
|
||||||
|
|
||||||
|
# ignore objects that we haven't seen for 5 secs
|
||||||
|
NOT_SEEN_INIT = 33
|
||||||
|
|
||||||
|
def _create_radar_parser():
|
||||||
|
return CANParser('u_radar', [("Status", float('nan')), ("ObjectData", float('nan'))], 1)
|
||||||
|
|
||||||
|
class RadarInterface(RadarInterfaceBase):
|
||||||
|
def __init__(self, CP):
|
||||||
|
super().__init__(CP)
|
||||||
|
|
||||||
|
self.updated_messages = set()
|
||||||
|
|
||||||
|
self.rcp = _create_radar_parser()
|
||||||
|
|
||||||
|
self._pts_cache = dict()
|
||||||
|
self._pts_not_seen = {key: 0 for key in range(255)}
|
||||||
|
self._should_clear_cache = False
|
||||||
|
|
||||||
|
# called by card.py, 100hz
|
||||||
|
def update(self, can_strings):
|
||||||
|
vls = self.rcp.update(can_strings)
|
||||||
|
self.updated_messages.update(vls)
|
||||||
|
|
||||||
|
if 1546 in self.updated_messages:
|
||||||
|
self._should_clear_cache = True
|
||||||
|
|
||||||
|
if 1547 in self.updated_messages:
|
||||||
|
all_objects = zip(
|
||||||
|
self.rcp.vl_all['ObjectData']['ID'],
|
||||||
|
self.rcp.vl_all['ObjectData']['DistLong'],
|
||||||
|
self.rcp.vl_all['ObjectData']['DistLat'],
|
||||||
|
self.rcp.vl_all['ObjectData']['VRelLong'],
|
||||||
|
self.rcp.vl_all['ObjectData']['VRelLat'],
|
||||||
|
self.rcp.vl_all['ObjectData']['DynProp'],
|
||||||
|
self.rcp.vl_all['ObjectData']['Class'],
|
||||||
|
self.rcp.vl_all['ObjectData']['RCS'],
|
||||||
|
)
|
||||||
|
|
||||||
|
# clean cache when we see a 0x60a then a 0x60b
|
||||||
|
if self._should_clear_cache:
|
||||||
|
self._pts_cache.clear()
|
||||||
|
self._should_clear_cache = False
|
||||||
|
|
||||||
|
for track_id, dist_long, dist_lat, vrel_long, vrel_lat, dyn_prop, obj_class, rcs in all_objects:
|
||||||
|
|
||||||
|
d_rel = dist_long + DREL_OFFSET
|
||||||
|
y_rel = -dist_lat
|
||||||
|
|
||||||
|
should_ignore = False
|
||||||
|
|
||||||
|
# ignore point (obj_class = 0)
|
||||||
|
if not should_ignore and int(obj_class) == 0:
|
||||||
|
should_ignore = True
|
||||||
|
|
||||||
|
# ignore oncoming objects
|
||||||
|
# @todo remove this because it's always 0 ?
|
||||||
|
if not should_ignore and int(dyn_prop) == IGNORE_OBJ_STATE:
|
||||||
|
should_ignore = True
|
||||||
|
|
||||||
|
# far away lane object, ignore
|
||||||
|
if not should_ignore and abs(y_rel) > LANE_SIDE_MAX_LAT:
|
||||||
|
should_ignore = True
|
||||||
|
|
||||||
|
# close object, ignore, use vision
|
||||||
|
if not should_ignore and LANE_CENTER_MIN_LAT > abs(y_rel) > LANE_CENTER_MAX_LAT and d_rel < LANE_CENTER_MIN_DIST:
|
||||||
|
should_ignore = True
|
||||||
|
|
||||||
|
# close object, ignore, use vision
|
||||||
|
if not should_ignore and LANE_SIDE_MIN_LAT > abs(y_rel) > LANE_SIDE_MAX_LAT and d_rel < LANE_SIDE_MIN_DIST:
|
||||||
|
should_ignore = True
|
||||||
|
|
||||||
|
if not should_ignore and track_id not in self._pts_cache:
|
||||||
|
self._pts_cache[track_id] = RadarData.RadarPoint()
|
||||||
|
self._pts_cache[track_id].trackId = track_id
|
||||||
|
|
||||||
|
if should_ignore:
|
||||||
|
self._pts_not_seen[track_id] = -1
|
||||||
|
else:
|
||||||
|
self._pts_not_seen[track_id] = NOT_SEEN_INIT
|
||||||
|
|
||||||
|
# init cache
|
||||||
|
if track_id not in self._pts_cache:
|
||||||
|
self._pts_cache[track_id] = RadarData.RadarPoint()
|
||||||
|
self._pts_cache[track_id].trackId = track_id
|
||||||
|
|
||||||
|
# add/update to cache
|
||||||
|
self._pts_cache[track_id].dRel = d_rel
|
||||||
|
self._pts_cache[track_id].yRel = y_rel
|
||||||
|
self._pts_cache[track_id].vRel = float(vrel_long)
|
||||||
|
self._pts_cache[track_id].yvRel = float('nan')
|
||||||
|
self._pts_cache[track_id].aRel = float('nan')
|
||||||
|
self._pts_cache[track_id].measured = True
|
||||||
|
|
||||||
|
self.updated_messages.clear()
|
||||||
|
|
||||||
|
# publish to cereal
|
||||||
|
if self.frame % 3 == 0:
|
||||||
|
keys_to_remove = [key for key in self.pts if key not in self._pts_cache]
|
||||||
|
for key in keys_to_remove:
|
||||||
|
self._pts_not_seen[key] -= 1
|
||||||
|
if self._pts_not_seen[key] <= 0:
|
||||||
|
del self.pts[key]
|
||||||
|
|
||||||
|
self.pts.update(self._pts_cache)
|
||||||
|
|
||||||
|
ret = RadarData()
|
||||||
|
if not self.rcp.can_valid:
|
||||||
|
ret.errors.canError = True
|
||||||
|
|
||||||
|
ret.points = list(self.pts.values())
|
||||||
|
return ret
|
||||||
|
|
||||||
|
return None
|
||||||
@ -20,4 +20,6 @@ CarControlT = capnp.lib.capnp._StructModule
|
|||||||
CarParamsT = capnp.lib.capnp._StructModule
|
CarParamsT = capnp.lib.capnp._StructModule
|
||||||
|
|
||||||
class DPFlags:
|
class DPFlags:
|
||||||
|
LateralALKA = 1
|
||||||
|
ExtRadar = 2
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -291,7 +291,7 @@ class CarController(CarControllerBase):
|
|||||||
if self.frame % 20 == 0 or send_ui:
|
if self.frame % 20 == 0 or send_ui:
|
||||||
can_sends.append(toyotacan.create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, hud_control.leftLaneVisible,
|
can_sends.append(toyotacan.create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, hud_control.leftLaneVisible,
|
||||||
hud_control.rightLaneVisible, hud_control.leftLaneDepart,
|
hud_control.rightLaneVisible, hud_control.leftLaneDepart,
|
||||||
hud_control.rightLaneDepart, CC.enabled, CS.lkas_hud))
|
hud_control.rightLaneDepart, lat_active, CS.lkas_hud))
|
||||||
|
|
||||||
if (self.frame % 100 == 0 or send_ui) and (self.CP.enableDsu or self.CP.flags & ToyotaFlags.DISABLE_RADAR.value):
|
if (self.frame % 100 == 0 or send_ui) and (self.CP.enableDsu or self.CP.flags & ToyotaFlags.DISABLE_RADAR.value):
|
||||||
can_sends.append(toyotacan.create_fcw_command(self.packer, fcw_alert))
|
can_sends.append(toyotacan.create_fcw_command(self.packer, fcw_alert))
|
||||||
|
|||||||
@ -77,6 +77,8 @@ class ToyotaFlags(IntFlag):
|
|||||||
RAISED_ACCEL_LIMIT = 1024
|
RAISED_ACCEL_LIMIT = 1024
|
||||||
SECOC = 2048
|
SECOC = 2048
|
||||||
|
|
||||||
|
ALKA = 2 ** 12
|
||||||
|
|
||||||
|
|
||||||
def dbc_dict(pt, radar):
|
def dbc_dict(pt, radar):
|
||||||
return {Bus.pt: pt, Bus.radar: radar}
|
return {Bus.pt: pt, Bus.radar: radar}
|
||||||
|
|||||||
85
opendbc_repo/opendbc/dbc/u_radar.dbc
Normal file
85
opendbc_repo/opendbc/dbc/u_radar.dbc
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
|
||||||
|
|
||||||
|
VERSION ""
|
||||||
|
|
||||||
|
|
||||||
|
NS_ :
|
||||||
|
NS_DESC_
|
||||||
|
CM_
|
||||||
|
BA_DEF_
|
||||||
|
BA_
|
||||||
|
VAL_
|
||||||
|
CAT_DEF_
|
||||||
|
CAT_
|
||||||
|
FILTER
|
||||||
|
BA_DEF_DEF_
|
||||||
|
EV_DATA_
|
||||||
|
ENVVAR_DATA_
|
||||||
|
SGTYPE_
|
||||||
|
SGTYPE_VAL_
|
||||||
|
BA_DEF_SGTYPE_
|
||||||
|
BA_SGTYPE_
|
||||||
|
SIG_TYPE_REF_
|
||||||
|
VAL_TABLE_
|
||||||
|
SIG_GROUP_
|
||||||
|
SIG_VALTYPE_
|
||||||
|
SIGTYPE_VALTYPE_
|
||||||
|
BO_TX_BU_
|
||||||
|
BA_DEF_REL_
|
||||||
|
BA_REL_
|
||||||
|
BA_DEF_DEF_REL_
|
||||||
|
BU_SG_REL_
|
||||||
|
BU_EV_REL_
|
||||||
|
BU_BO_REL_
|
||||||
|
SG_MUL_VAL_
|
||||||
|
|
||||||
|
BS_:
|
||||||
|
|
||||||
|
BU_: RADAR
|
||||||
|
|
||||||
|
BO_ 513 RadarState: 8 XXX
|
||||||
|
SG_ NVMReadStatus : 6|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
SG_ NVMWriteStatus : 7|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
SG_ MaxDistanceCfg : 15|10@0+ (2,0) [0|2046] "m" XXX
|
||||||
|
SG_ RadarPowerCfg : 25|3@0+ (1,0) [0|7] "" XXX
|
||||||
|
SG_ SensorID : 34|3@0+ (1,0) [0|7] "" XXX
|
||||||
|
SG_ SortIndex : 38|3@0+ (1,0) [0|7] "" XXX
|
||||||
|
SG_ CtrlRelayCfg : 41|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
SG_ OutputTypeCfg : 43|2@0+ (1,0) [0|3] "" XXX
|
||||||
|
SG_ QualityInfoCfg : 44|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
SG_ ExtInfoCfg : 45|1@0+ (1,0) [0|1] "" XXX
|
||||||
|
SG_ CANBaudRate : 55|3@0+ (1,0) [0|7] "" XXX
|
||||||
|
SG_ InterfaceType : 57|2@0+ (1,0) [0|3] "" XXX
|
||||||
|
SG_ RCSThreshold : 58|3@1+ (1,0) [0|7] "" XXX
|
||||||
|
SG_ CalibrationEnabled : 63|2@0+ (1,0) [0|3] "" XXX
|
||||||
|
|
||||||
|
VAL_ 513 NVMReadStatus 0 "Failed" 1 "Successful";
|
||||||
|
VAL_ 513 NVMWriteStatus 0 "Failed" 1 "Successful";
|
||||||
|
VAL_ 513 RadarPowerCfg 0 "Standard" 1 "-3dB Gain" 2 "-6dB Gain" 3 "-9dB Gain";
|
||||||
|
VAL_ 513 SortIndex 0 "No Sorting" 1 "Sort By Range" 2 "Sort By RCS";
|
||||||
|
VAL_ 513 CtrlRelayCfg 0 "Off" 1 "On";
|
||||||
|
VAL_ 513 OutputTypeCfg 0 "None" 1 "Objects" 2 "Clusters";
|
||||||
|
VAL_ 513 QualityInfoCfg 0 "Off" 1 "On";
|
||||||
|
VAL_ 513 ExtInfoCfg 0 "Off" 1 "On";
|
||||||
|
VAL_ 513 CANBaudRate 0 "500K" 1 "250K" 2 "1M";
|
||||||
|
VAL_ 513 RCSThreshold 0 "Standard" 1 "High Sensitivity";
|
||||||
|
VAL_ 513 CalibrationEnabled 1 "Enabled" 2 "Initial Recovery";
|
||||||
|
|
||||||
|
BO_ 1546 Status: 8 RADAR
|
||||||
|
SG_ NoOfObjects : 7|8@0+ (1,0) [0|255] "" XXX
|
||||||
|
SG_ MeasCount : 15|16@0+ (1,0) [0|65535] "" XXX
|
||||||
|
SG_ InterfaceVersion : 31|4@0+ (1,0) [0|15] "" XXX
|
||||||
|
|
||||||
|
BO_ 1547 ObjectData: 8 RADAR
|
||||||
|
SG_ ID : 7|8@0+ (1,0) [0|255] "" XXX
|
||||||
|
SG_ DistLong : 15|13@0+ (0.2,-500) [-500|1138.2] "m" XXX
|
||||||
|
SG_ DistLat : 18|11@0+ (0.2,-204.6) [-204.6|204.8] "m" XXX
|
||||||
|
SG_ VRelLong : 39|10@0+ (0.25,-128) [-128|127.75] "m/s" XXX
|
||||||
|
SG_ VRelLat : 45|9@0+ (0.25,-64) [-64|63.75] "m/s" XXX
|
||||||
|
SG_ DynProp : 50|3@0+ (1,0) [0|7] "" XXX
|
||||||
|
SG_ Class : 52|2@0+ (1,0) [0|3] "" XXX
|
||||||
|
SG_ RCS : 63|8@0+ (0.5,-64) [-64|63.75] "dBm2" XXX
|
||||||
|
|
||||||
|
CM_ BO_ 1547 "Object detection and tracking information";
|
||||||
|
VAL_ 1547 DynProp 0 "moving" 1 "stationary" 2 "oncoming" 3 "crossing_left" 4 "crossing_right" 5 "unknown" 6 "stopped";
|
||||||
|
VAL_ 1547 Class 0 "point" 1 "vehicle";
|
||||||
@ -8,3 +8,5 @@ class ALTERNATIVE_EXPERIENCE:
|
|||||||
DISABLE_STOCK_AEB = 2
|
DISABLE_STOCK_AEB = 2
|
||||||
RAISE_LONGITUDINAL_LIMITS_TO_ISO_MAX = 8
|
RAISE_LONGITUDINAL_LIMITS_TO_ISO_MAX = 8
|
||||||
ALLOW_AEB = 16
|
ALLOW_AEB = 16
|
||||||
|
|
||||||
|
ALKA = 2 ** 10
|
||||||
|
|||||||
@ -3,7 +3,12 @@
|
|||||||
static const unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U};
|
static const unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U};
|
||||||
|
|
||||||
#define CANPACKET_HEAD_SIZE 6U // non-data portion of CANPacket_t
|
#define CANPACKET_HEAD_SIZE 6U // non-data portion of CANPacket_t
|
||||||
#define CANPACKET_DATA_SIZE_MAX 64U
|
|
||||||
|
#ifdef CANFD
|
||||||
|
#define CANPACKET_DATA_SIZE_MAX 64U
|
||||||
|
#else
|
||||||
|
#define CANPACKET_DATA_SIZE_MAX 8U
|
||||||
|
#endif
|
||||||
|
|
||||||
// bump this when changing the CAN packet
|
// bump this when changing the CAN packet
|
||||||
#define CAN_PACKET_VERSION 4
|
#define CAN_PACKET_VERSION 4
|
||||||
|
|||||||
@ -230,7 +230,9 @@ void update_sample(struct sample_t *sample, int sample_new);
|
|||||||
bool get_longitudinal_allowed(void);
|
bool get_longitudinal_allowed(void);
|
||||||
int ROUND(float val);
|
int ROUND(float val);
|
||||||
void gen_crc_lookup_table_8(uint8_t poly, uint8_t crc_lut[]);
|
void gen_crc_lookup_table_8(uint8_t poly, uint8_t crc_lut[]);
|
||||||
|
#ifdef CANFD
|
||||||
void gen_crc_lookup_table_16(uint16_t poly, uint16_t crc_lut[]);
|
void gen_crc_lookup_table_16(uint16_t poly, uint16_t crc_lut[]);
|
||||||
|
#endif
|
||||||
bool steer_torque_cmd_checks(int desired_torque, int steer_req, const TorqueSteeringLimits limits);
|
bool steer_torque_cmd_checks(int desired_torque, int steer_req, const TorqueSteeringLimits limits);
|
||||||
bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const AngleSteeringLimits limits);
|
bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const AngleSteeringLimits limits);
|
||||||
bool steer_angle_cmd_checks_vm(int desired_angle, bool steer_control_enabled, const AngleSteeringLimits limits,
|
bool steer_angle_cmd_checks_vm(int desired_angle, bool steer_control_enabled, const AngleSteeringLimits limits,
|
||||||
@ -301,6 +303,8 @@ extern struct sample_t angle_meas; // last 6 steer angles/curvatures
|
|||||||
// This flag allows AEB to be commanded from openpilot.
|
// This flag allows AEB to be commanded from openpilot.
|
||||||
#define ALT_EXP_ALLOW_AEB 16
|
#define ALT_EXP_ALLOW_AEB 16
|
||||||
|
|
||||||
|
#define ALT_EXP_ALKA 1024
|
||||||
|
|
||||||
extern int alternative_experience;
|
extern int alternative_experience;
|
||||||
|
|
||||||
// time since safety mode has been changed
|
// time since safety mode has been changed
|
||||||
|
|||||||
@ -60,8 +60,9 @@ static bool rt_torque_rate_limit_check(int val, int val_last, const int MAX_RT_D
|
|||||||
bool steer_torque_cmd_checks(int desired_torque, int steer_req, const TorqueSteeringLimits limits) {
|
bool steer_torque_cmd_checks(int desired_torque, int steer_req, const TorqueSteeringLimits limits) {
|
||||||
bool violation = false;
|
bool violation = false;
|
||||||
uint32_t ts = microsecond_timer_get();
|
uint32_t ts = microsecond_timer_get();
|
||||||
|
bool alka = (alternative_experience & ALT_EXP_ALKA) != 0;
|
||||||
|
|
||||||
if (controls_allowed) {
|
if (controls_allowed || alka) {
|
||||||
// Some safety models support variable torque limit based on vehicle speed
|
// Some safety models support variable torque limit based on vehicle speed
|
||||||
int max_torque = limits.max_torque;
|
int max_torque = limits.max_torque;
|
||||||
if (limits.dynamic_max_torque) {
|
if (limits.dynamic_max_torque) {
|
||||||
@ -96,7 +97,7 @@ bool steer_torque_cmd_checks(int desired_torque, int steer_req, const TorqueStee
|
|||||||
}
|
}
|
||||||
|
|
||||||
// no torque if controls is not allowed
|
// no torque if controls is not allowed
|
||||||
if (!controls_allowed && (desired_torque != 0)) {
|
if (!(controls_allowed || alka) && (desired_torque != 0)) {
|
||||||
violation = true;
|
violation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +139,7 @@ bool steer_torque_cmd_checks(int desired_torque, int steer_req, const TorqueStee
|
|||||||
}
|
}
|
||||||
|
|
||||||
// reset to 0 if either controls is not allowed or there's a violation
|
// reset to 0 if either controls is not allowed or there's a violation
|
||||||
if (violation || !controls_allowed) {
|
if (violation || !(controls_allowed || alka)) {
|
||||||
valid_steer_req_count = 0;
|
valid_steer_req_count = 0;
|
||||||
invalid_steer_req_count = 0;
|
invalid_steer_req_count = 0;
|
||||||
desired_torque_last = 0;
|
desired_torque_last = 0;
|
||||||
@ -176,7 +177,9 @@ static bool rt_angle_rate_limit_check(AngleSteeringLimits limits) {
|
|||||||
bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const AngleSteeringLimits limits) {
|
bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const AngleSteeringLimits limits) {
|
||||||
bool violation = false;
|
bool violation = false;
|
||||||
|
|
||||||
if (controls_allowed && steer_control_enabled) {
|
bool alka = (alternative_experience & ALT_EXP_ALKA) != 0;
|
||||||
|
|
||||||
|
if ((controls_allowed || alka) && steer_control_enabled) {
|
||||||
// convert floating point angle rate limits to integers in the scale of the desired angle on CAN,
|
// convert floating point angle rate limits to integers in the scale of the desired angle on CAN,
|
||||||
// add 1 to not false trigger the violation. also fudge the speed by 1 m/s so rate limits are
|
// add 1 to not false trigger the violation. also fudge the speed by 1 m/s so rate limits are
|
||||||
// always slightly above openpilot's in case we read an updated speed in between angle commands
|
// always slightly above openpilot's in case we read an updated speed in between angle commands
|
||||||
@ -262,12 +265,12 @@ bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No angle control allowed when controls are not allowed
|
// No angle control allowed when controls are not allowed
|
||||||
if (!controls_allowed) {
|
if (!(controls_allowed || alka)) {
|
||||||
violation |= steer_control_enabled;
|
violation |= steer_control_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset to current angle if either controls is not allowed or there's a violation
|
// reset to current angle if either controls is not allowed or there's a violation
|
||||||
if (violation || !controls_allowed) {
|
if (violation || !(controls_allowed || alka)) {
|
||||||
if (limits.inactive_angle_is_zero) {
|
if (limits.inactive_angle_is_zero) {
|
||||||
desired_angle_last = 0;
|
desired_angle_last = 0;
|
||||||
} else {
|
} else {
|
||||||
@ -304,7 +307,10 @@ bool steer_angle_cmd_checks_vm(int desired_angle, bool steer_control_enabled, co
|
|||||||
|
|
||||||
bool violation = false;
|
bool violation = false;
|
||||||
|
|
||||||
if (controls_allowed && steer_control_enabled) {
|
bool alka = (alternative_experience & ALT_EXP_ALKA) != 0;
|
||||||
|
|
||||||
|
|
||||||
|
if ((controls_allowed || alka) && steer_control_enabled) {
|
||||||
// *** ISO lateral jerk limit ***
|
// *** ISO lateral jerk limit ***
|
||||||
// calculate maximum angle rate per second
|
// calculate maximum angle rate per second
|
||||||
const float max_curvature_rate_sec = MAX_LATERAL_JERK / (fudged_speed * fudged_speed);
|
const float max_curvature_rate_sec = MAX_LATERAL_JERK / (fudged_speed * fudged_speed);
|
||||||
@ -340,12 +346,12 @@ bool steer_angle_cmd_checks_vm(int desired_angle, bool steer_control_enabled, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No angle control allowed when controls are not allowed
|
// No angle control allowed when controls are not allowed
|
||||||
if (!controls_allowed) {
|
if (!(controls_allowed || alka)) {
|
||||||
violation |= steer_control_enabled;
|
violation |= steer_control_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset to current angle if either controls is not allowed or there's a violation
|
// reset to current angle if either controls is not allowed or there's a violation
|
||||||
if (violation || !controls_allowed) {
|
if (violation || !(controls_allowed || alka)) {
|
||||||
desired_angle_last = SAFETY_CLAMP(angle_meas.values[0], -limits.max_angle, limits.max_angle);
|
desired_angle_last = SAFETY_CLAMP(angle_meas.values[0], -limits.max_angle, limits.max_angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -238,7 +238,8 @@ static bool honda_tx_hook(const CANPacket_t *msg) {
|
|||||||
|
|
||||||
// STEER: safety check
|
// STEER: safety check
|
||||||
if ((msg->addr == 0xE4U) || (msg->addr == 0x194U)) {
|
if ((msg->addr == 0xE4U) || (msg->addr == 0x194U)) {
|
||||||
if (!controls_allowed) {
|
bool alka = (alternative_experience & ALT_EXP_ALKA) != 0;
|
||||||
|
if (!(controls_allowed || alka)) {
|
||||||
bool steer_applied = msg->data[0] | msg->data[1];
|
bool steer_applied = msg->data[0] | msg->data[1];
|
||||||
if (steer_applied) {
|
if (steer_applied) {
|
||||||
tx = false;
|
tx = false;
|
||||||
|
|||||||
@ -112,6 +112,7 @@ void hyundai_common_cruise_buttons_check(const int cruise_button, const bool mai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CANFD
|
||||||
uint32_t hyundai_common_canfd_compute_checksum(const CANPacket_t *msg) {
|
uint32_t hyundai_common_canfd_compute_checksum(const CANPacket_t *msg) {
|
||||||
int len = GET_LEN(msg);
|
int len = GET_LEN(msg);
|
||||||
uint32_t address = msg->addr;
|
uint32_t address = msg->addr;
|
||||||
@ -136,3 +137,4 @@ uint32_t hyundai_common_canfd_compute_checksum(const CANPacket_t *msg) {
|
|||||||
|
|
||||||
return crc;
|
return crc;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@ -25,7 +25,10 @@
|
|||||||
#include "opendbc/safety/modes/elm327.h"
|
#include "opendbc/safety/modes/elm327.h"
|
||||||
#include "opendbc/safety/modes/body.h"
|
#include "opendbc/safety/modes/body.h"
|
||||||
#include "opendbc/safety/modes/psa.h"
|
#include "opendbc/safety/modes/psa.h"
|
||||||
|
|
||||||
|
#ifdef CANFD
|
||||||
#include "opendbc/safety/modes/hyundai_canfd.h"
|
#include "opendbc/safety/modes/hyundai_canfd.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
uint32_t GET_BYTES(const CANPacket_t *msg, int start, int len) {
|
uint32_t GET_BYTES(const CANPacket_t *msg, int start, int len) {
|
||||||
uint32_t ret = 0U;
|
uint32_t ret = 0U;
|
||||||
@ -292,6 +295,7 @@ void gen_crc_lookup_table_8(uint8_t poly, uint8_t crc_lut[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CANFD
|
||||||
void gen_crc_lookup_table_16(uint16_t poly, uint16_t crc_lut[]) {
|
void gen_crc_lookup_table_16(uint16_t poly, uint16_t crc_lut[]) {
|
||||||
for (uint16_t i = 0; i < 256U; i++) {
|
for (uint16_t i = 0; i < 256U; i++) {
|
||||||
uint16_t crc = i << 8U;
|
uint16_t crc = i << 8U;
|
||||||
@ -305,6 +309,7 @@ void gen_crc_lookup_table_16(uint16_t poly, uint16_t crc_lut[]) {
|
|||||||
crc_lut[i] = crc;
|
crc_lut[i] = crc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// 1Hz safety function called by main. Now just a check for lagging safety messages
|
// 1Hz safety function called by main. Now just a check for lagging safety messages
|
||||||
void safety_tick(const safety_config *cfg) {
|
void safety_tick(const safety_config *cfg) {
|
||||||
@ -401,7 +406,9 @@ int set_safety_hooks(uint16_t mode, uint16_t param) {
|
|||||||
{SAFETY_FORD, &ford_hooks},
|
{SAFETY_FORD, &ford_hooks},
|
||||||
{SAFETY_RIVIAN, &rivian_hooks},
|
{SAFETY_RIVIAN, &rivian_hooks},
|
||||||
{SAFETY_TESLA, &tesla_hooks},
|
{SAFETY_TESLA, &tesla_hooks},
|
||||||
|
#ifdef CANFD
|
||||||
{SAFETY_HYUNDAI_CANFD, &hyundai_canfd_hooks},
|
{SAFETY_HYUNDAI_CANFD, &hyundai_canfd_hooks},
|
||||||
|
#endif
|
||||||
#ifdef ALLOW_DEBUG
|
#ifdef ALLOW_DEBUG
|
||||||
{SAFETY_PSA, &psa_hooks},
|
{SAFETY_PSA, &psa_hooks},
|
||||||
{SAFETY_SUBARU_PREGLOBAL, &subaru_preglobal_hooks},
|
{SAFETY_SUBARU_PREGLOBAL, &subaru_preglobal_hooks},
|
||||||
|
|||||||
@ -29,6 +29,7 @@ env = Environment(
|
|||||||
'-fprofile-arcs',
|
'-fprofile-arcs',
|
||||||
'-ftest-coverage',
|
'-ftest-coverage',
|
||||||
'-DALLOW_DEBUG',
|
'-DALLOW_DEBUG',
|
||||||
|
'-DCANFD',
|
||||||
],
|
],
|
||||||
LINKFLAGS=[
|
LINKFLAGS=[
|
||||||
'-fprofile-arcs',
|
'-fprofile-arcs',
|
||||||
|
|||||||
@ -55,8 +55,8 @@ cppcheck() {
|
|||||||
|
|
||||||
OPTS=" --enable=all --enable=unusedFunction --addon=misra"
|
OPTS=" --enable=all --enable=unusedFunction --addon=misra"
|
||||||
|
|
||||||
printf "\n${GREEN}** Safety **${NC}\n"
|
printf "\n${GREEN}** Safety with CANFD **${NC}\n"
|
||||||
cppcheck $OPTS $BASEDIR/opendbc/safety/tests/misra/main.c
|
cppcheck $OPTS -DCANFD $BASEDIR/opendbc/safety/tests/misra/main.c
|
||||||
|
|
||||||
printf "\n${GREEN}Success!${NC} took $SECONDS seconds\n"
|
printf "\n${GREEN}Success!${NC} took $SECONDS seconds\n"
|
||||||
|
|
||||||
|
|||||||
38
panda_tici/.gitignore
vendored
Normal file
38
panda_tici/.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
.venv
|
||||||
|
*.tmp
|
||||||
|
*.pyc
|
||||||
|
.*.swp
|
||||||
|
.*.swo
|
||||||
|
*.o
|
||||||
|
*.so
|
||||||
|
*.os
|
||||||
|
*.d
|
||||||
|
*.dump
|
||||||
|
a.out
|
||||||
|
*~
|
||||||
|
.#*
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
pandacan.egg-info/
|
||||||
|
obj/
|
||||||
|
examples/output.csv
|
||||||
|
.DS_Store
|
||||||
|
.vscode*
|
||||||
|
nosetests.xml
|
||||||
|
.mypy_cache/
|
||||||
|
.sconsign.dblite
|
||||||
|
uv.lock
|
||||||
|
compile_commands.json
|
||||||
|
|
||||||
|
# CTU info files generated by Cppcheck
|
||||||
|
*.*.ctu-info
|
||||||
|
|
||||||
|
# safety coverage-related files
|
||||||
|
*.gcda
|
||||||
|
*.gcno
|
||||||
|
tests/safety/coverage-out
|
||||||
|
tests/safety/coverage.info
|
||||||
|
|
||||||
|
*.profraw
|
||||||
|
*.profdata
|
||||||
|
mull.yml
|
||||||
21
panda_tici/Dockerfile
Normal file
21
panda_tici/Dockerfile
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV PYTHONPATH=/tmp/pythonpath
|
||||||
|
|
||||||
|
# deps install
|
||||||
|
COPY pyproject.toml __init__.py setup.sh /tmp/
|
||||||
|
COPY python/__init__.py /tmp/python/
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends sudo && /tmp/setup.sh
|
||||||
|
|
||||||
|
COPY pyproject.toml __init__.py $PYTHONPATH/panda_tici/
|
||||||
|
COPY python/__init__.py $PYTHONPATH/panda_tici/python/
|
||||||
|
RUN pip3 install --break-system-packages --no-cache-dir $PYTHONPATH/panda_tici/[dev]
|
||||||
|
|
||||||
|
RUN git config --global --add safe.directory $PYTHONPATH/panda_tici
|
||||||
|
|
||||||
|
# for Jenkins
|
||||||
|
COPY README.md panda_tici.tar.* /tmp/
|
||||||
|
RUN mkdir -p /tmp/pythonpath/panda_tici && \
|
||||||
|
tar -xvf /tmp/panda_tici.tar.gz -C /tmp/pythonpath/panda_tici/ || true
|
||||||
152
panda_tici/Jenkinsfile
vendored
Normal file
152
panda_tici/Jenkinsfile
vendored
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
def docker_run(String step_label, int timeout_mins, String cmd) {
|
||||||
|
timeout(time: timeout_mins, unit: 'MINUTES') {
|
||||||
|
sh script: "docker run --rm --privileged \
|
||||||
|
--env PYTHONWARNINGS=error \
|
||||||
|
--volume /dev/bus/usb:/dev/bus/usb \
|
||||||
|
--volume /var/run/dbus:/var/run/dbus \
|
||||||
|
--workdir /tmp/pythonpath/panda_tici \
|
||||||
|
--net host \
|
||||||
|
${env.DOCKER_IMAGE_TAG} \
|
||||||
|
bash -c 'scons -j8 && ${cmd}'", \
|
||||||
|
label: step_label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def phone(String ip, String step_label, String cmd) {
|
||||||
|
withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) {
|
||||||
|
def ssh_cmd = """
|
||||||
|
ssh -tt -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' /usr/bin/bash <<'END'
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
|
||||||
|
source ~/.bash_profile
|
||||||
|
if [ -f /etc/profile ]; then
|
||||||
|
source /etc/profile
|
||||||
|
fi
|
||||||
|
|
||||||
|
export CI=1
|
||||||
|
export TEST_DIR=${env.TEST_DIR}
|
||||||
|
export SOURCE_DIR=${env.SOURCE_DIR}
|
||||||
|
export GIT_BRANCH=${env.GIT_BRANCH}
|
||||||
|
export GIT_COMMIT=${env.GIT_COMMIT}
|
||||||
|
export PYTHONPATH=${env.TEST_DIR}/../
|
||||||
|
export PYTHONWARNINGS=error
|
||||||
|
ln -sf /data/openpilot/opendbc_repo/opendbc /data/opendbc
|
||||||
|
|
||||||
|
cd ${env.TEST_DIR} || true
|
||||||
|
${cmd}
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
END"""
|
||||||
|
|
||||||
|
sh script: ssh_cmd, label: step_label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def phone_steps(String device_type, steps) {
|
||||||
|
lock(resource: "", label: device_type, inversePrecedence: true, variable: 'device_ip', quantity: 1) {
|
||||||
|
timeout(time: 20, unit: 'MINUTES') {
|
||||||
|
phone(device_ip, "git checkout", readFile("tests/setup_device_ci.sh"),)
|
||||||
|
steps.each { item ->
|
||||||
|
phone(device_ip, item[0], item[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
environment {
|
||||||
|
CI = "1"
|
||||||
|
PYTHONWARNINGS= "error"
|
||||||
|
DOCKER_IMAGE_TAG = "panda:build-${env.GIT_COMMIT}"
|
||||||
|
|
||||||
|
TEST_DIR = "/data/panda_tici"
|
||||||
|
SOURCE_DIR = "/data/panda_tici_source/"
|
||||||
|
}
|
||||||
|
options {
|
||||||
|
timeout(time: 3, unit: 'HOURS')
|
||||||
|
disableConcurrentBuilds(abortPrevious: env.BRANCH_NAME != 'master')
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage ('Acquire resource locks') {
|
||||||
|
options {
|
||||||
|
lock(resource: "pandas")
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Build Docker Image') {
|
||||||
|
steps {
|
||||||
|
timeout(time: 20, unit: 'MINUTES') {
|
||||||
|
script {
|
||||||
|
sh 'git archive -v -o panda.tar.gz --format=tar.gz HEAD'
|
||||||
|
dockerImage = docker.build("${env.DOCKER_IMAGE_TAG}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('jungle tests') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
retry (3) {
|
||||||
|
docker_run("reset hardware", 3, "python3 ./tests/hitl/reset_jungles.py")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('parallel tests') {
|
||||||
|
parallel {
|
||||||
|
stage('test cuatro') {
|
||||||
|
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
|
||||||
|
steps {
|
||||||
|
phone_steps("panda-cuatro", [
|
||||||
|
["build", "scons -j4"],
|
||||||
|
["flash", "cd scripts/ && ./reflash_internal_panda.py"],
|
||||||
|
["flash jungle", "cd board/jungle && ./flash.py --all"],
|
||||||
|
["test", "cd tests/hitl && HW_TYPES=10 pytest -n0 --durations=0 2*.py [5-9]*.py"],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('test tres') {
|
||||||
|
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
|
||||||
|
steps {
|
||||||
|
phone_steps("panda-tres", [
|
||||||
|
["build", "scons -j4"],
|
||||||
|
["flash", "cd scripts/ && ./reflash_internal_panda.py"],
|
||||||
|
["flash jungle", "cd board/jungle && ./flash.py --all"],
|
||||||
|
["test", "cd tests/hitl && HW_TYPES=9 pytest -n0 --durations=0 2*.py [5-9]*.py"],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('test dos') {
|
||||||
|
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
|
||||||
|
steps {
|
||||||
|
phone_steps("panda-dos", [
|
||||||
|
["build", "scons -j4"],
|
||||||
|
["flash", "cd scripts/ && ./reflash_internal_panda.py"],
|
||||||
|
["flash jungle", "cd board/jungle && ./flash.py --all"],
|
||||||
|
["test", "cd tests/hitl && HW_TYPES=6 pytest -n0 --durations=0 [2-9]*.py -k 'not test_send_recv'"],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('bootkick tests') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
docker_run("test", 10, "pytest -n0 ./tests/som/test_bootkick.py")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
panda_tici/LICENSE
Normal file
7
panda_tici/LICENSE
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Copyright (c) 2016, Comma.ai, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
91
panda_tici/README.md
Normal file
91
panda_tici/README.md
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# Welcome to panda
|
||||||
|
|
||||||
|
panda speaks CAN and CAN FD, and it runs on [STM32F413](https://www.st.com/resource/en/reference_manual/rm0430-stm32f413423-advanced-armbased-32bit-mcus-stmicroelectronics.pdf) and [STM32H725](https://www.st.com/resource/en/reference_manual/rm0468-stm32h723733-stm32h725735-and-stm32h730-value-line-advanced-armbased-32bit-mcus-stmicroelectronics.pdf).
|
||||||
|
|
||||||
|
## Directory structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── board # Code that runs on the STM32
|
||||||
|
├── drivers # Drivers (not needed for use with Python)
|
||||||
|
├── python # Python userspace library for interfacing with the panda
|
||||||
|
├── tests # Tests for panda
|
||||||
|
├── scripts # Miscellaneous used for panda development and debugging
|
||||||
|
├── examples # Example scripts for using a panda in a car
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safety Model
|
||||||
|
|
||||||
|
panda is compiled with safety firmware provided by [opendbc](https://github.com/commaai/opendbc). See details about the car safety models, safety testing, and code rigor in that repository.
|
||||||
|
|
||||||
|
## Code Rigor
|
||||||
|
|
||||||
|
The panda firmware is written for its use in conjunction with [openpilot](https://github.com/commaai/openpilot). The panda firmware, through its safety model, provides and enforces the
|
||||||
|
[openpilot safety](https://github.com/commaai/openpilot/blob/master/docs/SAFETY.md). Due to its critical function, it's important that the application code rigor within the `board` folder is held to high standards.
|
||||||
|
|
||||||
|
These are the [CI regression tests](https://github.com/commaai/panda_tici/actions) we have in place:
|
||||||
|
* A generic static code analysis is performed by [cppcheck](https://github.com/danmar/cppcheck/).
|
||||||
|
* In addition, [cppcheck](https://github.com/danmar/cppcheck/) has a specific addon to check for [MISRA C:2012](https://misra.org.uk/) violations. See [current coverage](https://github.com/commaai/panda_tici/blob/master/tests/misra/coverage_table).
|
||||||
|
* Compiler options are relatively strict: the flags `-Wall -Wextra -Wstrict-prototypes -Werror` are enforced.
|
||||||
|
* The [safety logic](https://github.com/commaai/panda_tici/tree/master/opendbc/safety) is tested and verified by [unit tests](https://github.com/commaai/panda_tici/tree/master/opendbc/safety/tests) for each supported car variant.
|
||||||
|
to ensure that the behavior remains unchanged.
|
||||||
|
* A hardware-in-the-loop test verifies panda's functionalities on all active panda variants, including:
|
||||||
|
* additional safety model checks
|
||||||
|
* compiling and flashing the bootstub and app code
|
||||||
|
* receiving, sending, and forwarding CAN messages on all buses
|
||||||
|
* CAN loopback and latency tests through USB and SPI
|
||||||
|
|
||||||
|
The above tests are themselves tested by:
|
||||||
|
* a [mutation test](tests/misra/test_mutation.py) on the MISRA coverage
|
||||||
|
|
||||||
|
In addition, we run the [ruff linter](https://github.com/astral-sh/ruff) and [mypy](https://mypy-lang.org/) on panda's Python library.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/commaai/panda_tici.git
|
||||||
|
cd panda
|
||||||
|
|
||||||
|
# setup your environment
|
||||||
|
./setup.sh
|
||||||
|
|
||||||
|
# build fw + run the tests
|
||||||
|
./test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
See [the Panda class](https://github.com/commaai/panda_tici/blob/master/python/__init__.py) for how to interact with the panda.
|
||||||
|
|
||||||
|
For example, to receive CAN messages:
|
||||||
|
``` python
|
||||||
|
>>> from panda_tici import Panda
|
||||||
|
>>> panda = Panda()
|
||||||
|
>>> panda.can_recv()
|
||||||
|
```
|
||||||
|
And to send one on bus 0:
|
||||||
|
``` python
|
||||||
|
>>> from opendbc.car.structs import CarParams
|
||||||
|
>>> panda.set_safety_mode(CarParams.SafetyModel.allOutput)
|
||||||
|
>>> panda.can_send(0x1aa, b'message', 0)
|
||||||
|
```
|
||||||
|
Note that you may have to setup [udev rules](https://github.com/commaai/panda_tici/tree/master/drivers/linux) for Linux, such as
|
||||||
|
``` bash
|
||||||
|
sudo tee /etc/udev/rules.d/11-panda.rules <<EOF
|
||||||
|
SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", MODE="0666"
|
||||||
|
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddcc", MODE="0666"
|
||||||
|
SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddee", MODE="0666"
|
||||||
|
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddcc", MODE="0666"
|
||||||
|
SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddee", MODE="0666"
|
||||||
|
EOF
|
||||||
|
sudo udevadm control --reload-rules && sudo udevadm trigger
|
||||||
|
```
|
||||||
|
|
||||||
|
The panda jungle uses different udev rules. See [the repo](https://github.com/commaai/panda_jungle#udev-rules) for instructions.
|
||||||
|
|
||||||
|
## Software interface support
|
||||||
|
|
||||||
|
- [Python library](https://github.com/commaai/panda_tici/tree/master/python)
|
||||||
|
- [C++ library](https://github.com/commaai/openpilot/tree/master/selfdrive/pandad)
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
panda software is released under the MIT license unless otherwise specified.
|
||||||
213
panda_tici/SConscript
Normal file
213
panda_tici/SConscript
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import os
|
||||||
|
import opendbc
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
PREFIX = "arm-none-eabi-"
|
||||||
|
BUILDER = "DEV"
|
||||||
|
|
||||||
|
common_flags = []
|
||||||
|
|
||||||
|
panda_root = Dir('.')
|
||||||
|
|
||||||
|
if os.getenv("RELEASE"):
|
||||||
|
BUILD_TYPE = "RELEASE"
|
||||||
|
cert_fn = os.getenv("CERT")
|
||||||
|
assert cert_fn is not None, 'No certificate file specified. Please set CERT env variable'
|
||||||
|
assert os.path.exists(cert_fn), 'Certificate file not found. Please specify absolute path'
|
||||||
|
else:
|
||||||
|
BUILD_TYPE = "DEBUG"
|
||||||
|
cert_fn = File("./certs/debug").srcnode().relpath
|
||||||
|
common_flags += ["-DALLOW_DEBUG"]
|
||||||
|
|
||||||
|
if os.getenv("DEBUG"):
|
||||||
|
common_flags += ["-DDEBUG"]
|
||||||
|
|
||||||
|
def objcopy(source, target, env, for_signature):
|
||||||
|
return '$OBJCOPY -O binary %s %s' % (source[0], target[0])
|
||||||
|
|
||||||
|
def get_version(builder, build_type):
|
||||||
|
try:
|
||||||
|
git = subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"], encoding='utf8').strip()
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
git = "unknown"
|
||||||
|
return f"{builder}-{git}-{build_type}"
|
||||||
|
|
||||||
|
def get_key_header(name):
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
|
||||||
|
public_fn = File(f'./certs/{name}.pub').srcnode().get_path()
|
||||||
|
with open(public_fn) as f:
|
||||||
|
rsa = RSA.importKey(f.read())
|
||||||
|
assert(rsa.size_in_bits() == 1024)
|
||||||
|
|
||||||
|
rr = pow(2**1024, 2, rsa.n)
|
||||||
|
n0inv = 2**32 - pow(rsa.n, -1, 2**32)
|
||||||
|
|
||||||
|
r = [
|
||||||
|
f"RSAPublicKey {name}_rsa_key = {{",
|
||||||
|
f" .len = 0x20,",
|
||||||
|
f" .n0inv = {n0inv}U,",
|
||||||
|
f" .n = {to_c_uint32(rsa.n)},",
|
||||||
|
f" .rr = {to_c_uint32(rr)},",
|
||||||
|
f" .exponent = {rsa.e},",
|
||||||
|
f"}};",
|
||||||
|
]
|
||||||
|
return r
|
||||||
|
|
||||||
|
def to_c_uint32(x):
|
||||||
|
nums = []
|
||||||
|
for _ in range(0x20):
|
||||||
|
nums.append(x % (2**32))
|
||||||
|
x //= (2**32)
|
||||||
|
return "{" + 'U,'.join(map(str, nums)) + "U}"
|
||||||
|
|
||||||
|
|
||||||
|
def build_project(project_name, project, extra_flags):
|
||||||
|
linkerscript_fn = File(project["LINKER_SCRIPT"]).srcnode().relpath
|
||||||
|
|
||||||
|
flags = project["PROJECT_FLAGS"] + extra_flags + common_flags + [
|
||||||
|
"-Wall",
|
||||||
|
"-Wextra",
|
||||||
|
"-Wstrict-prototypes",
|
||||||
|
"-Werror",
|
||||||
|
"-mlittle-endian",
|
||||||
|
"-mthumb",
|
||||||
|
"-nostdlib",
|
||||||
|
"-fno-builtin",
|
||||||
|
"-std=gnu11",
|
||||||
|
"-fmax-errors=1",
|
||||||
|
f"-T{linkerscript_fn}",
|
||||||
|
]
|
||||||
|
|
||||||
|
includes = [
|
||||||
|
'.',
|
||||||
|
'..',
|
||||||
|
panda_root,
|
||||||
|
f"{panda_root}/board/",
|
||||||
|
opendbc.INCLUDE_PATH,
|
||||||
|
]
|
||||||
|
|
||||||
|
env = Environment(
|
||||||
|
ENV=os.environ,
|
||||||
|
CC=PREFIX + 'gcc',
|
||||||
|
AS=PREFIX + 'gcc',
|
||||||
|
OBJCOPY=PREFIX + 'objcopy',
|
||||||
|
OBJDUMP=PREFIX + 'objdump',
|
||||||
|
CFLAGS=flags,
|
||||||
|
ASFLAGS=flags,
|
||||||
|
LINKFLAGS=flags,
|
||||||
|
CPPPATH=includes,
|
||||||
|
ASCOM="$AS $ASFLAGS -o $TARGET -c $SOURCES",
|
||||||
|
BUILDERS={
|
||||||
|
'Objcopy': Builder(generator=objcopy, suffix='.bin', src_suffix='.elf')
|
||||||
|
},
|
||||||
|
tools=["default", "compilation_db"],
|
||||||
|
)
|
||||||
|
|
||||||
|
startup = env.Object(f"obj/startup_{project_name}", project["STARTUP_FILE"])
|
||||||
|
|
||||||
|
# Bootstub
|
||||||
|
crypto_obj = [
|
||||||
|
env.Object(f"rsa-{project_name}", f"{panda_root}/crypto/rsa.c"),
|
||||||
|
env.Object(f"sha-{project_name}", f"{panda_root}/crypto/sha.c")
|
||||||
|
]
|
||||||
|
bootstub_obj = env.Object(f"bootstub-{project_name}", File(project.get("BOOTSTUB", f"{panda_root}/board/bootstub.c")))
|
||||||
|
bootstub_elf = env.Program(f"obj/bootstub.{project_name}.elf",
|
||||||
|
[startup] + crypto_obj + [bootstub_obj])
|
||||||
|
env.Objcopy(f"obj/bootstub.{project_name}.bin", bootstub_elf)
|
||||||
|
|
||||||
|
# Build main
|
||||||
|
main_obj = env.Object(f"main-{project_name}", project["MAIN"])
|
||||||
|
main_elf = env.Program(f"obj/{project_name}.elf", [startup, main_obj],
|
||||||
|
LINKFLAGS=[f"-Wl,--section-start,.isr_vector={project['APP_START_ADDRESS']}"] + flags)
|
||||||
|
main_bin = env.Objcopy(f"obj/{project_name}.bin", main_elf)
|
||||||
|
|
||||||
|
# Sign main
|
||||||
|
sign_py = File(f"{panda_root}/crypto/sign.py").srcnode().relpath
|
||||||
|
env.Command(f"obj/{project_name}.bin.signed", main_bin, f"SETLEN=1 {sign_py} $SOURCE $TARGET {cert_fn}")
|
||||||
|
|
||||||
|
|
||||||
|
base_project_f4 = {
|
||||||
|
"MAIN": "main.c",
|
||||||
|
"STARTUP_FILE": File("./board/stm32f4/startup_stm32f413xx.s"),
|
||||||
|
"LINKER_SCRIPT": File("./board/stm32f4/stm32f4_flash.ld"),
|
||||||
|
"APP_START_ADDRESS": "0x8004000",
|
||||||
|
"PROJECT_FLAGS": [
|
||||||
|
"-mcpu=cortex-m4",
|
||||||
|
"-mhard-float",
|
||||||
|
"-DSTM32F4",
|
||||||
|
"-DSTM32F413xx",
|
||||||
|
"-Iboard/stm32f4/inc",
|
||||||
|
"-mfpu=fpv4-sp-d16",
|
||||||
|
"-fsingle-precision-constant",
|
||||||
|
"-Os",
|
||||||
|
"-g",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
base_project_h7 = {
|
||||||
|
"MAIN": "main.c",
|
||||||
|
"STARTUP_FILE": File("./board/stm32h7/startup_stm32h7x5xx.s"),
|
||||||
|
"LINKER_SCRIPT": File("./board/stm32h7/stm32h7x5_flash.ld"),
|
||||||
|
"APP_START_ADDRESS": "0x8020000",
|
||||||
|
"PROJECT_FLAGS": [
|
||||||
|
"-mcpu=cortex-m7",
|
||||||
|
"-mhard-float",
|
||||||
|
"-DSTM32H7",
|
||||||
|
"-DSTM32H725xx",
|
||||||
|
"-Iboard/stm32h7/inc",
|
||||||
|
"-mfpu=fpv5-d16",
|
||||||
|
"-fsingle-precision-constant",
|
||||||
|
"-Os",
|
||||||
|
"-g",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
Export('base_project_f4', 'base_project_h7', 'build_project')
|
||||||
|
|
||||||
|
###### rick - get version.h from panda/ #######
|
||||||
|
# instead of generate them, copy from panda/ so we have the same version
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
SOURCE_DIR = os.path.join("..", "panda", "board", "obj")
|
||||||
|
if os.path.exists(SOURCE_DIR):
|
||||||
|
DEST_DIR = os.path.join("board", "obj")
|
||||||
|
FILES_TO_COPY = ["gitversion.h", "version", "cert.h"]
|
||||||
|
|
||||||
|
# Ensure the destination directory exists.
|
||||||
|
os.makedirs(DEST_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
# Loop through the files and copy them.
|
||||||
|
for filename in FILES_TO_COPY:
|
||||||
|
source_path = os.path.join(SOURCE_DIR, filename)
|
||||||
|
dest_path = os.path.join(DEST_DIR, filename)
|
||||||
|
|
||||||
|
if os.path.exists(source_path):
|
||||||
|
shutil.copy(source_path, dest_path)
|
||||||
|
|
||||||
|
##############################################
|
||||||
|
# if we compile directly, then we fallback to original code
|
||||||
|
else:
|
||||||
|
# Common autogenerated includes
|
||||||
|
with open("board/obj/gitversion.h", "w") as f:
|
||||||
|
version = get_version(BUILDER, BUILD_TYPE)
|
||||||
|
f.write(f'extern const uint8_t gitversion[{len(version)}];\n')
|
||||||
|
f.write(f'const uint8_t gitversion[{len(version)}] = "{version}";\n')
|
||||||
|
|
||||||
|
with open("board/obj/version", "w") as f:
|
||||||
|
f.write(f'{get_version(BUILDER, BUILD_TYPE)}')
|
||||||
|
|
||||||
|
certs = [get_key_header(n) for n in ["debug", "release"]]
|
||||||
|
with open("board/obj/cert.h", "w") as f:
|
||||||
|
for cert in certs:
|
||||||
|
f.write("\n".join(cert) + "\n")
|
||||||
|
|
||||||
|
# panda fw
|
||||||
|
SConscript('board/SConscript')
|
||||||
|
|
||||||
|
# panda jungle fw
|
||||||
|
SConscript('board/jungle/SConscript')
|
||||||
|
|
||||||
|
# test files
|
||||||
|
if GetOption('extras'):
|
||||||
|
SConscript('tests/libpanda/SConscript')
|
||||||
19
panda_tici/SConstruct
Normal file
19
panda_tici/SConstruct
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
AddOption('--minimal',
|
||||||
|
action='store_false',
|
||||||
|
dest='extras',
|
||||||
|
default=True,
|
||||||
|
help='the minimum build. no tests, tools, etc.')
|
||||||
|
|
||||||
|
AddOption('--ubsan',
|
||||||
|
action='store_true',
|
||||||
|
help='turn on UBSan')
|
||||||
|
|
||||||
|
env = Environment(
|
||||||
|
COMPILATIONDB_USE_ABSPATH=True,
|
||||||
|
tools=["default", "compilation_db"],
|
||||||
|
)
|
||||||
|
|
||||||
|
env.CompilationDatabase("compile_commands.json")
|
||||||
|
|
||||||
|
# panda fw & test files
|
||||||
|
SConscript('SConscript')
|
||||||
10
panda_tici/__init__.py
Normal file
10
panda_tici/__init__.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from .python.constants import McuType, BASEDIR, FW_PATH, USBPACKET_MAX_SIZE # noqa: F401
|
||||||
|
from .python.spi import PandaSpiException, PandaProtocolMismatch, STBootloaderSPIHandle # noqa: F401
|
||||||
|
from .python.serial import PandaSerial # noqa: F401
|
||||||
|
from .python.utils import logger # noqa: F401
|
||||||
|
from .python import (Panda, PandaDFU, # noqa: F401
|
||||||
|
pack_can_buffer, unpack_can_buffer, calculate_checksum,
|
||||||
|
DLC_TO_LEN, LEN_TO_DLC, CANPACKET_HEAD_SIZE)
|
||||||
|
|
||||||
|
# panda jungle
|
||||||
|
from .board.jungle import PandaJungle, PandaJungleDFU # noqa: F401
|
||||||
20
panda_tici/board/README.md
Normal file
20
panda_tici/board/README.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
## Programming
|
||||||
|
|
||||||
|
```
|
||||||
|
./flash.py # flash application
|
||||||
|
./recover.py # flash bootstub
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
To print out the serial console from the STM32, run `tests/debug_console.py`
|
||||||
|
|
||||||
|
Troubleshooting
|
||||||
|
----
|
||||||
|
|
||||||
|
If your panda will not flash and green LED is on, use `recover.py`.
|
||||||
|
If panda is blinking fast with green LED, use `flash.py`.
|
||||||
|
|
||||||
|
Otherwise if LED is off and panda can't be seen with `lsusb` command, use [panda paw](https://comma.ai/shop/products/panda-paw) to go into DFU mode.
|
||||||
|
|
||||||
|
If your device has an internal panda and none of the above works, try running `../scripts/reflash_internal_panda.py`.
|
||||||
18
panda_tici/board/SConscript
Normal file
18
panda_tici/board/SConscript
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import os
|
||||||
|
import copy
|
||||||
|
|
||||||
|
Import('build_project', 'base_project_f4', 'base_project_h7')
|
||||||
|
|
||||||
|
build_projects = {
|
||||||
|
"panda": base_project_f4,
|
||||||
|
"panda_h7": base_project_h7,
|
||||||
|
}
|
||||||
|
|
||||||
|
for project_name, project in build_projects.items():
|
||||||
|
flags = [
|
||||||
|
"-DPANDA",
|
||||||
|
]
|
||||||
|
if ("ENABLE_SPI" in os.environ or "h7" in project_name):
|
||||||
|
flags.append('-DENABLE_SPI')
|
||||||
|
|
||||||
|
build_project(project_name, project, flags)
|
||||||
0
panda_tici/board/__init__.py
Normal file
0
panda_tici/board/__init__.py
Normal file
81
panda_tici/board/boards/board_declarations.h
Normal file
81
panda_tici/board/boards/board_declarations.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// ******************** Prototypes ********************
|
||||||
|
typedef enum {
|
||||||
|
BOOT_STANDBY,
|
||||||
|
BOOT_BOOTKICK,
|
||||||
|
BOOT_RESET,
|
||||||
|
} BootState;
|
||||||
|
|
||||||
|
typedef void (*board_init)(void);
|
||||||
|
typedef void (*board_init_bootloader)(void);
|
||||||
|
typedef void (*board_enable_can_transceiver)(uint8_t transceiver, bool enabled);
|
||||||
|
typedef void (*board_set_can_mode)(uint8_t mode);
|
||||||
|
typedef bool (*board_check_ignition)(void);
|
||||||
|
typedef uint32_t (*board_read_voltage_mV)(void);
|
||||||
|
typedef uint32_t (*board_read_current_mA)(void);
|
||||||
|
typedef void (*board_set_ir_power)(uint8_t percentage);
|
||||||
|
typedef void (*board_set_fan_enabled)(bool enabled);
|
||||||
|
typedef void (*board_set_siren)(bool enabled);
|
||||||
|
typedef void (*board_set_bootkick)(BootState state);
|
||||||
|
typedef bool (*board_read_som_gpio)(void);
|
||||||
|
typedef void (*board_set_amp_enabled)(bool enabled);
|
||||||
|
|
||||||
|
struct board {
|
||||||
|
harness_configuration *harness_config;
|
||||||
|
GPIO_TypeDef * const led_GPIO[3];
|
||||||
|
const uint8_t led_pin[3];
|
||||||
|
const uint8_t led_pwm_channels[3]; // leave at 0 to disable PWM
|
||||||
|
const bool has_spi;
|
||||||
|
const bool has_canfd;
|
||||||
|
const uint16_t fan_max_rpm;
|
||||||
|
const uint16_t avdd_mV;
|
||||||
|
const bool fan_stall_recovery;
|
||||||
|
const uint8_t fan_enable_cooldown_time;
|
||||||
|
const uint8_t fan_max_pwm;
|
||||||
|
board_init init;
|
||||||
|
board_init_bootloader init_bootloader;
|
||||||
|
board_enable_can_transceiver enable_can_transceiver;
|
||||||
|
board_set_can_mode set_can_mode;
|
||||||
|
board_check_ignition check_ignition;
|
||||||
|
board_read_voltage_mV read_voltage_mV;
|
||||||
|
board_read_current_mA read_current_mA;
|
||||||
|
board_set_ir_power set_ir_power;
|
||||||
|
board_set_fan_enabled set_fan_enabled;
|
||||||
|
board_set_siren set_siren;
|
||||||
|
board_set_bootkick set_bootkick;
|
||||||
|
board_read_som_gpio read_som_gpio;
|
||||||
|
board_set_amp_enabled set_amp_enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ******************* Definitions ********************
|
||||||
|
// These should match the enums in cereal/log.capnp and __init__.py
|
||||||
|
#define HW_TYPE_UNKNOWN 0U
|
||||||
|
//#define HW_TYPE_WHITE_PANDA 1U
|
||||||
|
//#define HW_TYPE_GREY_PANDA 2U
|
||||||
|
//#define HW_TYPE_BLACK_PANDA 3U
|
||||||
|
//#define HW_TYPE_PEDAL 4U
|
||||||
|
//#define HW_TYPE_UNO 5U
|
||||||
|
#define HW_TYPE_DOS 6U
|
||||||
|
#define HW_TYPE_RED_PANDA 7U
|
||||||
|
#define HW_TYPE_RED_PANDA_V2 8U
|
||||||
|
#define HW_TYPE_TRES 9U
|
||||||
|
#define HW_TYPE_CUATRO 10U
|
||||||
|
|
||||||
|
// USB power modes (from cereal.log.health)
|
||||||
|
#define USB_POWER_NONE 0U
|
||||||
|
#define USB_POWER_CLIENT 1U
|
||||||
|
#define USB_POWER_CDP 2U
|
||||||
|
#define USB_POWER_DCP 3U
|
||||||
|
|
||||||
|
// CAN modes
|
||||||
|
#define CAN_MODE_NORMAL 0U
|
||||||
|
#define CAN_MODE_OBD_CAN2 1U
|
||||||
|
|
||||||
|
extern struct board board_dos;
|
||||||
|
extern struct board board_tres;
|
||||||
|
extern struct board board_cuatro;
|
||||||
|
extern struct board board_red;
|
||||||
143
panda_tici/board/boards/cuatro.h
Normal file
143
panda_tici/board/boards/cuatro.h
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "board_declarations.h"
|
||||||
|
|
||||||
|
// ////////////////////////// //
|
||||||
|
// Cuatro (STM32H7) + Harness //
|
||||||
|
// ////////////////////////// //
|
||||||
|
|
||||||
|
static void cuatro_enable_can_transceiver(uint8_t transceiver, bool enabled) {
|
||||||
|
switch (transceiver) {
|
||||||
|
case 1U:
|
||||||
|
set_gpio_output(GPIOB, 7, !enabled);
|
||||||
|
break;
|
||||||
|
case 2U:
|
||||||
|
set_gpio_output(GPIOB, 10, !enabled);
|
||||||
|
break;
|
||||||
|
case 3U:
|
||||||
|
set_gpio_output(GPIOD, 8, !enabled);
|
||||||
|
break;
|
||||||
|
case 4U:
|
||||||
|
set_gpio_output(GPIOB, 11, !enabled);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t cuatro_read_voltage_mV(void) {
|
||||||
|
return adc_get_mV(8) * 11U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t cuatro_read_current_mA(void) {
|
||||||
|
return adc_get_mV(3) * 2U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cuatro_set_fan_enabled(bool enabled) {
|
||||||
|
set_gpio_output(GPIOD, 3, !enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cuatro_set_bootkick(BootState state) {
|
||||||
|
set_gpio_output(GPIOA, 0, state != BOOT_BOOTKICK);
|
||||||
|
// TODO: confirm we need this
|
||||||
|
//set_gpio_output(GPIOC, 12, state != BOOT_RESET);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cuatro_set_amp_enabled(bool enabled){
|
||||||
|
set_gpio_output(GPIOA, 5, enabled);
|
||||||
|
set_gpio_output(GPIOB, 0, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cuatro_init(void) {
|
||||||
|
common_init_gpio();
|
||||||
|
|
||||||
|
// open drain
|
||||||
|
set_gpio_output_type(GPIOD, 3, OUTPUT_TYPE_OPEN_DRAIN); // FAN_EN
|
||||||
|
set_gpio_output_type(GPIOC, 12, OUTPUT_TYPE_OPEN_DRAIN); // VBAT_EN
|
||||||
|
|
||||||
|
// Power readout
|
||||||
|
set_gpio_mode(GPIOC, 5, MODE_ANALOG);
|
||||||
|
set_gpio_mode(GPIOA, 6, MODE_ANALOG);
|
||||||
|
|
||||||
|
// CAN transceiver enables
|
||||||
|
set_gpio_pullup(GPIOB, 7, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOB, 7, MODE_OUTPUT);
|
||||||
|
set_gpio_pullup(GPIOD, 8, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOD, 8, MODE_OUTPUT);
|
||||||
|
|
||||||
|
// FDCAN3, different pins on this package than the rest of the reds
|
||||||
|
set_gpio_pullup(GPIOD, 12, PULL_NONE);
|
||||||
|
set_gpio_alternate(GPIOD, 12, GPIO_AF5_FDCAN3);
|
||||||
|
set_gpio_pullup(GPIOD, 13, PULL_NONE);
|
||||||
|
set_gpio_alternate(GPIOD, 13, GPIO_AF5_FDCAN3);
|
||||||
|
|
||||||
|
// C2: SOM GPIO used as input (fan control at boot)
|
||||||
|
set_gpio_mode(GPIOC, 2, MODE_INPUT);
|
||||||
|
set_gpio_pullup(GPIOC, 2, PULL_DOWN);
|
||||||
|
|
||||||
|
// SOM bootkick + reset lines
|
||||||
|
cuatro_set_bootkick(BOOT_BOOTKICK);
|
||||||
|
|
||||||
|
// SOM debugging UART
|
||||||
|
gpio_uart7_init();
|
||||||
|
uart_init(&uart_ring_som_debug, 115200);
|
||||||
|
|
||||||
|
// fan setup
|
||||||
|
set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3);
|
||||||
|
register_set_bits(&(GPIOC->OTYPER), GPIO_OTYPER_OT8); // open drain
|
||||||
|
|
||||||
|
// Clock source
|
||||||
|
clock_source_init(true);
|
||||||
|
|
||||||
|
// Sound codec
|
||||||
|
cuatro_set_amp_enabled(false);
|
||||||
|
set_gpio_alternate(GPIOA, 2, GPIO_AF8_SAI4); // SAI4_SCK_B
|
||||||
|
set_gpio_alternate(GPIOC, 0, GPIO_AF8_SAI4); // SAI4_FS_B
|
||||||
|
set_gpio_alternate(GPIOD, 11, GPIO_AF10_SAI4); // SAI4_SD_A
|
||||||
|
set_gpio_alternate(GPIOE, 3, GPIO_AF8_SAI4); // SAI4_SD_B
|
||||||
|
set_gpio_alternate(GPIOE, 4, GPIO_AF3_DFSDM1); // DFSDM1_DATIN3
|
||||||
|
set_gpio_alternate(GPIOE, 9, GPIO_AF3_DFSDM1); // DFSDM1_CKOUT
|
||||||
|
set_gpio_alternate(GPIOE, 6, GPIO_AF10_SAI4); // SAI4_MCLK_B
|
||||||
|
sound_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
static harness_configuration cuatro_harness_config = {
|
||||||
|
.has_harness = true,
|
||||||
|
.GPIO_SBU1 = GPIOC,
|
||||||
|
.GPIO_SBU2 = GPIOA,
|
||||||
|
.GPIO_relay_SBU1 = GPIOA,
|
||||||
|
.GPIO_relay_SBU2 = GPIOA,
|
||||||
|
.pin_SBU1 = 4,
|
||||||
|
.pin_SBU2 = 1,
|
||||||
|
.pin_relay_SBU1 = 9,
|
||||||
|
.pin_relay_SBU2 = 3,
|
||||||
|
.adc_channel_SBU1 = 4, // ADC12_INP4
|
||||||
|
.adc_channel_SBU2 = 17 // ADC1_INP17
|
||||||
|
};
|
||||||
|
|
||||||
|
board board_cuatro = {
|
||||||
|
.harness_config = &cuatro_harness_config,
|
||||||
|
.has_spi = true,
|
||||||
|
.has_canfd = true,
|
||||||
|
.fan_max_rpm = 12500U,
|
||||||
|
.fan_max_pwm = 99U, // it can go up to 14k RPM, but 99% -> 100% is very non-linear
|
||||||
|
.avdd_mV = 1800U,
|
||||||
|
.fan_stall_recovery = false,
|
||||||
|
.fan_enable_cooldown_time = 3U,
|
||||||
|
.init = cuatro_init,
|
||||||
|
.init_bootloader = unused_init_bootloader,
|
||||||
|
.enable_can_transceiver = cuatro_enable_can_transceiver,
|
||||||
|
.led_GPIO = {GPIOC, GPIOC, GPIOC},
|
||||||
|
.led_pin = {6, 7, 9},
|
||||||
|
.led_pwm_channels = {1, 2, 4},
|
||||||
|
.set_can_mode = tres_set_can_mode,
|
||||||
|
.check_ignition = red_check_ignition,
|
||||||
|
.read_voltage_mV = cuatro_read_voltage_mV,
|
||||||
|
.read_current_mA = cuatro_read_current_mA,
|
||||||
|
.set_fan_enabled = cuatro_set_fan_enabled,
|
||||||
|
.set_ir_power = unused_set_ir_power,
|
||||||
|
.set_siren = unused_set_siren,
|
||||||
|
.set_bootkick = cuatro_set_bootkick,
|
||||||
|
.read_som_gpio = tres_read_som_gpio,
|
||||||
|
.set_amp_enabled = cuatro_set_amp_enabled
|
||||||
|
};
|
||||||
158
panda_tici/board/boards/dos.h
Normal file
158
panda_tici/board/boards/dos.h
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "board_declarations.h"
|
||||||
|
|
||||||
|
// /////////////////////// //
|
||||||
|
// Dos (STM32F4) + Harness //
|
||||||
|
// /////////////////////// //
|
||||||
|
|
||||||
|
static void dos_enable_can_transceiver(uint8_t transceiver, bool enabled) {
|
||||||
|
switch (transceiver){
|
||||||
|
case 1U:
|
||||||
|
set_gpio_output(GPIOC, 1, !enabled);
|
||||||
|
break;
|
||||||
|
case 2U:
|
||||||
|
set_gpio_output(GPIOC, 13, !enabled);
|
||||||
|
break;
|
||||||
|
case 3U:
|
||||||
|
set_gpio_output(GPIOA, 0, !enabled);
|
||||||
|
break;
|
||||||
|
case 4U:
|
||||||
|
set_gpio_output(GPIOB, 10, !enabled);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dos_set_bootkick(BootState state) {
|
||||||
|
set_gpio_output(GPIOC, 4, state != BOOT_BOOTKICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dos_set_can_mode(uint8_t mode) {
|
||||||
|
dos_enable_can_transceiver(2U, false);
|
||||||
|
dos_enable_can_transceiver(4U, false);
|
||||||
|
switch (mode) {
|
||||||
|
case CAN_MODE_NORMAL:
|
||||||
|
case CAN_MODE_OBD_CAN2:
|
||||||
|
if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) {
|
||||||
|
// B12,B13: disable OBD mode
|
||||||
|
set_gpio_mode(GPIOB, 12, MODE_INPUT);
|
||||||
|
set_gpio_mode(GPIOB, 13, MODE_INPUT);
|
||||||
|
|
||||||
|
// B5,B6: normal CAN2 mode
|
||||||
|
set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2);
|
||||||
|
set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2);
|
||||||
|
dos_enable_can_transceiver(2U, true);
|
||||||
|
} else {
|
||||||
|
// B5,B6: disable normal CAN2 mode
|
||||||
|
set_gpio_mode(GPIOB, 5, MODE_INPUT);
|
||||||
|
set_gpio_mode(GPIOB, 6, MODE_INPUT);
|
||||||
|
|
||||||
|
// B12,B13: OBD mode
|
||||||
|
set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2);
|
||||||
|
set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2);
|
||||||
|
dos_enable_can_transceiver(4U, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
print("Tried to set unsupported CAN mode: "); puth(mode); print("\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dos_check_ignition(void){
|
||||||
|
// ignition is checked through harness
|
||||||
|
return harness_check_ignition();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dos_set_ir_power(uint8_t percentage){
|
||||||
|
pwm_set(TIM4, 2, percentage);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dos_set_fan_enabled(bool enabled){
|
||||||
|
set_gpio_output(GPIOA, 1, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dos_set_siren(bool enabled){
|
||||||
|
set_gpio_output(GPIOC, 12, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t dos_read_voltage_mV(void){
|
||||||
|
return adc_get_mV(12) * 11U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dos_read_som_gpio (void){
|
||||||
|
return (get_gpio_input(GPIOC, 2) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dos_init(void) {
|
||||||
|
common_init_gpio();
|
||||||
|
|
||||||
|
// A8,A15: normal CAN3 mode
|
||||||
|
set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3);
|
||||||
|
set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3);
|
||||||
|
|
||||||
|
// C8: FAN PWM aka TIM3_CH3
|
||||||
|
set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3);
|
||||||
|
|
||||||
|
// C2: SOM GPIO used as input (fan control at boot)
|
||||||
|
set_gpio_mode(GPIOC, 2, MODE_INPUT);
|
||||||
|
set_gpio_pullup(GPIOC, 2, PULL_DOWN);
|
||||||
|
|
||||||
|
// Initialize IR PWM and set to 0%
|
||||||
|
set_gpio_alternate(GPIOB, 7, GPIO_AF2_TIM4);
|
||||||
|
pwm_init(TIM4, 2);
|
||||||
|
dos_set_ir_power(0U);
|
||||||
|
|
||||||
|
// Bootkick
|
||||||
|
dos_set_bootkick(true);
|
||||||
|
|
||||||
|
// Init clock source (camera strobe) using PWM
|
||||||
|
clock_source_init(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static harness_configuration dos_harness_config = {
|
||||||
|
.has_harness = true,
|
||||||
|
.GPIO_SBU1 = GPIOC,
|
||||||
|
.GPIO_SBU2 = GPIOC,
|
||||||
|
.GPIO_relay_SBU1 = GPIOC,
|
||||||
|
.GPIO_relay_SBU2 = GPIOC,
|
||||||
|
.pin_SBU1 = 0,
|
||||||
|
.pin_SBU2 = 3,
|
||||||
|
.pin_relay_SBU1 = 10,
|
||||||
|
.pin_relay_SBU2 = 11,
|
||||||
|
.adc_channel_SBU1 = 10,
|
||||||
|
.adc_channel_SBU2 = 13
|
||||||
|
};
|
||||||
|
|
||||||
|
board board_dos = {
|
||||||
|
.harness_config = &dos_harness_config,
|
||||||
|
#ifdef ENABLE_SPI
|
||||||
|
.has_spi = true,
|
||||||
|
#else
|
||||||
|
.has_spi = false,
|
||||||
|
#endif
|
||||||
|
.has_canfd = false,
|
||||||
|
.fan_max_rpm = 6500U,
|
||||||
|
.fan_max_pwm = 100U,
|
||||||
|
.avdd_mV = 3300U,
|
||||||
|
.fan_stall_recovery = true,
|
||||||
|
.fan_enable_cooldown_time = 3U,
|
||||||
|
.init = dos_init,
|
||||||
|
.init_bootloader = unused_init_bootloader,
|
||||||
|
.enable_can_transceiver = dos_enable_can_transceiver,
|
||||||
|
.led_GPIO = {GPIOC, GPIOC, GPIOC},
|
||||||
|
.led_pin = {9, 7, 6},
|
||||||
|
.set_can_mode = dos_set_can_mode,
|
||||||
|
.check_ignition = dos_check_ignition,
|
||||||
|
.read_voltage_mV = dos_read_voltage_mV,
|
||||||
|
.read_current_mA = unused_read_current,
|
||||||
|
.set_fan_enabled = dos_set_fan_enabled,
|
||||||
|
.set_ir_power = dos_set_ir_power,
|
||||||
|
.set_siren = dos_set_siren,
|
||||||
|
.set_bootkick = dos_set_bootkick,
|
||||||
|
.read_som_gpio = dos_read_som_gpio,
|
||||||
|
.set_amp_enabled = unused_set_amp_enabled
|
||||||
|
};
|
||||||
144
panda_tici/board/boards/red.h
Normal file
144
panda_tici/board/boards/red.h
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "board_declarations.h"
|
||||||
|
|
||||||
|
// ///////////////////////////// //
|
||||||
|
// Red Panda (STM32H7) + Harness //
|
||||||
|
// ///////////////////////////// //
|
||||||
|
|
||||||
|
static void red_enable_can_transceiver(uint8_t transceiver, bool enabled) {
|
||||||
|
switch (transceiver) {
|
||||||
|
case 1U:
|
||||||
|
set_gpio_output(GPIOG, 11, !enabled);
|
||||||
|
break;
|
||||||
|
case 2U:
|
||||||
|
set_gpio_output(GPIOB, 3, !enabled);
|
||||||
|
break;
|
||||||
|
case 3U:
|
||||||
|
set_gpio_output(GPIOD, 7, !enabled);
|
||||||
|
break;
|
||||||
|
case 4U:
|
||||||
|
set_gpio_output(GPIOB, 4, !enabled);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void red_set_can_mode(uint8_t mode) {
|
||||||
|
red_enable_can_transceiver(2U, false);
|
||||||
|
red_enable_can_transceiver(4U, false);
|
||||||
|
switch (mode) {
|
||||||
|
case CAN_MODE_NORMAL:
|
||||||
|
case CAN_MODE_OBD_CAN2:
|
||||||
|
if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) {
|
||||||
|
// B12,B13: disable normal mode
|
||||||
|
set_gpio_pullup(GPIOB, 12, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOB, 12, MODE_ANALOG);
|
||||||
|
|
||||||
|
set_gpio_pullup(GPIOB, 13, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOB, 13, MODE_ANALOG);
|
||||||
|
|
||||||
|
// B5,B6: FDCAN2 mode
|
||||||
|
set_gpio_pullup(GPIOB, 5, PULL_NONE);
|
||||||
|
set_gpio_alternate(GPIOB, 5, GPIO_AF9_FDCAN2);
|
||||||
|
|
||||||
|
set_gpio_pullup(GPIOB, 6, PULL_NONE);
|
||||||
|
set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2);
|
||||||
|
red_enable_can_transceiver(2U, true);
|
||||||
|
} else {
|
||||||
|
// B5,B6: disable normal mode
|
||||||
|
set_gpio_pullup(GPIOB, 5, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOB, 5, MODE_ANALOG);
|
||||||
|
|
||||||
|
set_gpio_pullup(GPIOB, 6, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOB, 6, MODE_ANALOG);
|
||||||
|
// B12,B13: FDCAN2 mode
|
||||||
|
set_gpio_pullup(GPIOB, 12, PULL_NONE);
|
||||||
|
set_gpio_alternate(GPIOB, 12, GPIO_AF9_FDCAN2);
|
||||||
|
|
||||||
|
set_gpio_pullup(GPIOB, 13, PULL_NONE);
|
||||||
|
set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2);
|
||||||
|
red_enable_can_transceiver(4U, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool red_check_ignition(void) {
|
||||||
|
// ignition is checked through harness
|
||||||
|
return harness_check_ignition();
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t red_read_voltage_mV(void){
|
||||||
|
return adc_get_mV(2) * 11U; // TODO: is this correct?
|
||||||
|
}
|
||||||
|
|
||||||
|
static void red_init(void) {
|
||||||
|
common_init_gpio();
|
||||||
|
|
||||||
|
// G11,B3,D7,B4: transceiver enable
|
||||||
|
set_gpio_pullup(GPIOG, 11, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOG, 11, MODE_OUTPUT);
|
||||||
|
|
||||||
|
set_gpio_pullup(GPIOB, 3, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOB, 3, MODE_OUTPUT);
|
||||||
|
|
||||||
|
set_gpio_pullup(GPIOD, 7, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOD, 7, MODE_OUTPUT);
|
||||||
|
|
||||||
|
set_gpio_pullup(GPIOB, 4, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOB, 4, MODE_OUTPUT);
|
||||||
|
|
||||||
|
//B1: 5VOUT_S
|
||||||
|
set_gpio_pullup(GPIOB, 1, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOB, 1, MODE_ANALOG);
|
||||||
|
|
||||||
|
// B14: usb load switch, enabled by pull resistor on board, obsolete for red panda
|
||||||
|
set_gpio_output_type(GPIOB, 14, OUTPUT_TYPE_OPEN_DRAIN);
|
||||||
|
set_gpio_pullup(GPIOB, 14, PULL_UP);
|
||||||
|
set_gpio_mode(GPIOB, 14, MODE_OUTPUT);
|
||||||
|
set_gpio_output(GPIOB, 14, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static harness_configuration red_harness_config = {
|
||||||
|
.has_harness = true,
|
||||||
|
.GPIO_SBU1 = GPIOC,
|
||||||
|
.GPIO_SBU2 = GPIOA,
|
||||||
|
.GPIO_relay_SBU1 = GPIOC,
|
||||||
|
.GPIO_relay_SBU2 = GPIOC,
|
||||||
|
.pin_SBU1 = 4,
|
||||||
|
.pin_SBU2 = 1,
|
||||||
|
.pin_relay_SBU1 = 10,
|
||||||
|
.pin_relay_SBU2 = 11,
|
||||||
|
.adc_channel_SBU1 = 4, //ADC12_INP4
|
||||||
|
.adc_channel_SBU2 = 17 //ADC1_INP17
|
||||||
|
};
|
||||||
|
|
||||||
|
board board_red = {
|
||||||
|
.set_bootkick = unused_set_bootkick,
|
||||||
|
.harness_config = &red_harness_config,
|
||||||
|
.has_spi = false,
|
||||||
|
.has_canfd = true,
|
||||||
|
.fan_max_rpm = 0U,
|
||||||
|
.fan_max_pwm = 100U,
|
||||||
|
.avdd_mV = 3300U,
|
||||||
|
.fan_stall_recovery = false,
|
||||||
|
.fan_enable_cooldown_time = 0U,
|
||||||
|
.init = red_init,
|
||||||
|
.init_bootloader = unused_init_bootloader,
|
||||||
|
.enable_can_transceiver = red_enable_can_transceiver,
|
||||||
|
.led_GPIO = {GPIOE, GPIOE, GPIOE},
|
||||||
|
.led_pin = {4, 3, 2},
|
||||||
|
.set_can_mode = red_set_can_mode,
|
||||||
|
.check_ignition = red_check_ignition,
|
||||||
|
.read_voltage_mV = red_read_voltage_mV,
|
||||||
|
.read_current_mA = unused_read_current,
|
||||||
|
.set_fan_enabled = unused_set_fan_enabled,
|
||||||
|
.set_ir_power = unused_set_ir_power,
|
||||||
|
.set_siren = unused_set_siren,
|
||||||
|
.read_som_gpio = unused_read_som_gpio,
|
||||||
|
.set_amp_enabled = unused_set_amp_enabled
|
||||||
|
};
|
||||||
180
panda_tici/board/boards/tres.h
Normal file
180
panda_tici/board/boards/tres.h
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "board_declarations.h"
|
||||||
|
|
||||||
|
// ///////////////////////////
|
||||||
|
// Tres (STM32H7) + Harness //
|
||||||
|
// ///////////////////////////
|
||||||
|
|
||||||
|
static bool tres_ir_enabled;
|
||||||
|
static bool tres_fan_enabled;
|
||||||
|
static void tres_update_fan_ir_power(void) {
|
||||||
|
set_gpio_output(GPIOD, 3, tres_ir_enabled || tres_fan_enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tres_set_ir_power(uint8_t percentage){
|
||||||
|
tres_ir_enabled = (percentage > 0U);
|
||||||
|
tres_update_fan_ir_power();
|
||||||
|
pwm_set(TIM3, 4, percentage);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tres_set_bootkick(BootState state) {
|
||||||
|
set_gpio_output(GPIOA, 0, state != BOOT_BOOTKICK);
|
||||||
|
set_gpio_output(GPIOC, 12, state != BOOT_RESET);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tres_set_fan_enabled(bool enabled) {
|
||||||
|
// NOTE: fan controller reset doesn't work on a tres if IR is enabled
|
||||||
|
tres_fan_enabled = enabled;
|
||||||
|
tres_update_fan_ir_power();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tres_enable_can_transceiver(uint8_t transceiver, bool enabled) {
|
||||||
|
static bool can0_enabled = false;
|
||||||
|
static bool can2_enabled = false;
|
||||||
|
|
||||||
|
switch (transceiver) {
|
||||||
|
case 1U:
|
||||||
|
can0_enabled = enabled;
|
||||||
|
break;
|
||||||
|
case 2U:
|
||||||
|
set_gpio_output(GPIOB, 10, !enabled);
|
||||||
|
break;
|
||||||
|
case 3U:
|
||||||
|
can2_enabled = enabled;
|
||||||
|
break;
|
||||||
|
case 4U:
|
||||||
|
set_gpio_output(GPIOB, 11, !enabled);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAN0 and 2 are tied, so enable both if either is enabled
|
||||||
|
set_gpio_output(GPIOG, 11, !(can0_enabled || can2_enabled));
|
||||||
|
set_gpio_output(GPIOD, 7, !(can0_enabled || can2_enabled));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tres_set_can_mode(uint8_t mode) {
|
||||||
|
current_board->enable_can_transceiver(2U, false);
|
||||||
|
current_board->enable_can_transceiver(4U, false);
|
||||||
|
switch (mode) {
|
||||||
|
case CAN_MODE_NORMAL:
|
||||||
|
case CAN_MODE_OBD_CAN2:
|
||||||
|
if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(harness.status == HARNESS_STATUS_FLIPPED)) {
|
||||||
|
// B12,B13: disable normal mode
|
||||||
|
set_gpio_pullup(GPIOB, 12, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOB, 12, MODE_ANALOG);
|
||||||
|
|
||||||
|
set_gpio_pullup(GPIOB, 13, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOB, 13, MODE_ANALOG);
|
||||||
|
|
||||||
|
// B5,B6: FDCAN2 mode
|
||||||
|
set_gpio_pullup(GPIOB, 5, PULL_NONE);
|
||||||
|
set_gpio_alternate(GPIOB, 5, GPIO_AF9_FDCAN2);
|
||||||
|
|
||||||
|
set_gpio_pullup(GPIOB, 6, PULL_NONE);
|
||||||
|
set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2);
|
||||||
|
current_board->enable_can_transceiver(2U, true);
|
||||||
|
} else {
|
||||||
|
// B5,B6: disable normal mode
|
||||||
|
set_gpio_pullup(GPIOB, 5, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOB, 5, MODE_ANALOG);
|
||||||
|
|
||||||
|
set_gpio_pullup(GPIOB, 6, PULL_NONE);
|
||||||
|
set_gpio_mode(GPIOB, 6, MODE_ANALOG);
|
||||||
|
// B12,B13: FDCAN2 mode
|
||||||
|
set_gpio_pullup(GPIOB, 12, PULL_NONE);
|
||||||
|
set_gpio_alternate(GPIOB, 12, GPIO_AF9_FDCAN2);
|
||||||
|
|
||||||
|
set_gpio_pullup(GPIOB, 13, PULL_NONE);
|
||||||
|
set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2);
|
||||||
|
current_board->enable_can_transceiver(4U, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tres_read_som_gpio (void) {
|
||||||
|
return (get_gpio_input(GPIOC, 2) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tres_init(void) {
|
||||||
|
// Enable USB 3.3V LDO for USB block
|
||||||
|
register_set_bits(&(PWR->CR3), PWR_CR3_USBREGEN);
|
||||||
|
register_set_bits(&(PWR->CR3), PWR_CR3_USB33DEN);
|
||||||
|
while ((PWR->CR3 & PWR_CR3_USB33RDY) == 0U);
|
||||||
|
|
||||||
|
common_init_gpio();
|
||||||
|
|
||||||
|
// C2: SOM GPIO used as input (fan control at boot)
|
||||||
|
set_gpio_mode(GPIOC, 2, MODE_INPUT);
|
||||||
|
set_gpio_pullup(GPIOC, 2, PULL_DOWN);
|
||||||
|
|
||||||
|
// SOM bootkick + reset lines
|
||||||
|
// WARNING: make sure output state is set before configuring as output
|
||||||
|
tres_set_bootkick(BOOT_BOOTKICK);
|
||||||
|
set_gpio_mode(GPIOC, 12, MODE_OUTPUT);
|
||||||
|
|
||||||
|
// SOM debugging UART
|
||||||
|
gpio_uart7_init();
|
||||||
|
uart_init(&uart_ring_som_debug, 115200);
|
||||||
|
|
||||||
|
// fan setup
|
||||||
|
set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3);
|
||||||
|
|
||||||
|
// Initialize IR PWM and set to 0%
|
||||||
|
set_gpio_alternate(GPIOC, 9, GPIO_AF2_TIM3);
|
||||||
|
pwm_init(TIM3, 4);
|
||||||
|
tres_set_ir_power(0U);
|
||||||
|
|
||||||
|
// Fake siren
|
||||||
|
set_gpio_alternate(GPIOC, 10, GPIO_AF4_I2C5);
|
||||||
|
set_gpio_alternate(GPIOC, 11, GPIO_AF4_I2C5);
|
||||||
|
register_set_bits(&(GPIOC->OTYPER), GPIO_OTYPER_OT10 | GPIO_OTYPER_OT11); // open drain
|
||||||
|
|
||||||
|
// Clock source
|
||||||
|
clock_source_init(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static harness_configuration tres_harness_config = {
|
||||||
|
.has_harness = true,
|
||||||
|
.GPIO_SBU1 = GPIOC,
|
||||||
|
.GPIO_SBU2 = GPIOA,
|
||||||
|
.GPIO_relay_SBU1 = GPIOA,
|
||||||
|
.GPIO_relay_SBU2 = GPIOA,
|
||||||
|
.pin_SBU1 = 4,
|
||||||
|
.pin_SBU2 = 1,
|
||||||
|
.pin_relay_SBU1 = 8,
|
||||||
|
.pin_relay_SBU2 = 3,
|
||||||
|
.adc_channel_SBU1 = 4, // ADC12_INP4
|
||||||
|
.adc_channel_SBU2 = 17 // ADC1_INP17
|
||||||
|
};
|
||||||
|
|
||||||
|
board board_tres = {
|
||||||
|
.harness_config = &tres_harness_config,
|
||||||
|
.has_spi = true,
|
||||||
|
.has_canfd = true,
|
||||||
|
.fan_max_rpm = 6600U,
|
||||||
|
.fan_max_pwm = 100U,
|
||||||
|
.avdd_mV = 1800U,
|
||||||
|
.fan_stall_recovery = false,
|
||||||
|
.fan_enable_cooldown_time = 3U,
|
||||||
|
.init = tres_init,
|
||||||
|
.init_bootloader = unused_init_bootloader,
|
||||||
|
.enable_can_transceiver = tres_enable_can_transceiver,
|
||||||
|
.led_GPIO = {GPIOE, GPIOE, GPIOE},
|
||||||
|
.led_pin = {4, 3, 2},
|
||||||
|
.set_can_mode = tres_set_can_mode,
|
||||||
|
.check_ignition = red_check_ignition,
|
||||||
|
.read_voltage_mV = red_read_voltage_mV,
|
||||||
|
.read_current_mA = unused_read_current,
|
||||||
|
.set_fan_enabled = tres_set_fan_enabled,
|
||||||
|
.set_ir_power = tres_set_ir_power,
|
||||||
|
.set_siren = fake_siren_set,
|
||||||
|
.set_bootkick = tres_set_bootkick,
|
||||||
|
.read_som_gpio = tres_read_som_gpio,
|
||||||
|
.set_amp_enabled = unused_set_amp_enabled
|
||||||
|
};
|
||||||
32
panda_tici/board/boards/unused_funcs.h
Normal file
32
panda_tici/board/boards/unused_funcs.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void unused_init_bootloader(void) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void unused_set_ir_power(uint8_t percentage) {
|
||||||
|
UNUSED(percentage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unused_set_fan_enabled(bool enabled) {
|
||||||
|
UNUSED(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unused_set_siren(bool enabled) {
|
||||||
|
UNUSED(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t unused_read_current(void) {
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unused_set_bootkick(BootState state) {
|
||||||
|
UNUSED(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool unused_read_som_gpio(void) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unused_set_amp_enabled(bool enabled) {
|
||||||
|
UNUSED(enabled);
|
||||||
|
}
|
||||||
90
panda_tici/board/bootstub.c
Normal file
90
panda_tici/board/bootstub.c
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#define BOOTSTUB
|
||||||
|
|
||||||
|
#define VERS_TAG 0x53524556
|
||||||
|
#define MIN_VERSION 2
|
||||||
|
|
||||||
|
// ********************* Includes *********************
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "drivers/led.h"
|
||||||
|
#include "drivers/pwm.h"
|
||||||
|
#include "drivers/usb.h"
|
||||||
|
|
||||||
|
#include "early_init.h"
|
||||||
|
#include "provision.h"
|
||||||
|
|
||||||
|
#include "crypto/rsa.h"
|
||||||
|
#include "crypto/sha.h"
|
||||||
|
|
||||||
|
#include "obj/cert.h"
|
||||||
|
#include "obj/gitversion.h"
|
||||||
|
#include "flasher.h"
|
||||||
|
|
||||||
|
// cppcheck-suppress unusedFunction ; used in headers not included in cppcheck
|
||||||
|
void __initialize_hardware_early(void) {
|
||||||
|
early_initialization();
|
||||||
|
}
|
||||||
|
|
||||||
|
void fail(void) {
|
||||||
|
soft_flasher_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// know where to sig check
|
||||||
|
extern void *_app_start[];
|
||||||
|
|
||||||
|
// FIXME: sometimes your panda will fail flashing and will quickly blink a single Green LED
|
||||||
|
// BOUNTY: $200 coupon on shop.comma.ai or $100 check.
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
// Init interrupt table
|
||||||
|
init_interrupts(true);
|
||||||
|
|
||||||
|
disable_interrupts();
|
||||||
|
clock_init();
|
||||||
|
detect_board_type();
|
||||||
|
|
||||||
|
#ifdef PANDA_JUNGLE
|
||||||
|
current_board->set_panda_power(true);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (enter_bootloader_mode == ENTER_SOFTLOADER_MAGIC) {
|
||||||
|
enter_bootloader_mode = 0;
|
||||||
|
soft_flasher_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate length
|
||||||
|
int len = (int)_app_start[0];
|
||||||
|
if ((len < 8) || (len > (0x1000000 - 0x4000 - 4 - RSANUMBYTES))) goto fail;
|
||||||
|
|
||||||
|
// compute SHA hash
|
||||||
|
uint8_t digest[SHA_DIGEST_SIZE];
|
||||||
|
SHA_hash(&_app_start[1], len-4, digest);
|
||||||
|
|
||||||
|
// verify version, last bytes in the signed area
|
||||||
|
uint32_t vers[2] = {0};
|
||||||
|
memcpy(&vers, ((void*)&_app_start[0]) + len - sizeof(vers), sizeof(vers));
|
||||||
|
if (vers[0] != VERS_TAG || vers[1] < MIN_VERSION) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify RSA signature
|
||||||
|
if (RSA_verify(&release_rsa_key, ((void*)&_app_start[0]) + len, RSANUMBYTES, digest, SHA_DIGEST_SIZE)) {
|
||||||
|
goto good;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow debug if built from source
|
||||||
|
#ifdef ALLOW_DEBUG
|
||||||
|
if (RSA_verify(&debug_rsa_key, ((void*)&_app_start[0]) + len, RSANUMBYTES, digest, SHA_DIGEST_SIZE)) {
|
||||||
|
goto good;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// here is a failure
|
||||||
|
fail:
|
||||||
|
fail();
|
||||||
|
return 0;
|
||||||
|
good:
|
||||||
|
// jump to flash
|
||||||
|
((void(*)(void)) _app_start[1])();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
18
panda_tici/board/bootstub_declarations.h
Normal file
18
panda_tici/board/bootstub_declarations.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// ******************** Prototypes ********************
|
||||||
|
void print(const char *a){ UNUSED(a); }
|
||||||
|
void puth(uint8_t i){ UNUSED(i); }
|
||||||
|
void puth2(uint8_t i){ UNUSED(i); }
|
||||||
|
void puth4(uint8_t i){ UNUSED(i); }
|
||||||
|
void hexdump(const void *a, int l){ UNUSED(a); UNUSED(l); }
|
||||||
|
typedef struct board board;
|
||||||
|
typedef struct harness_configuration harness_configuration;
|
||||||
|
void pwm_init(TIM_TypeDef *TIM, uint8_t channel);
|
||||||
|
void pwm_set(TIM_TypeDef *TIM, uint8_t channel, uint8_t percentage);
|
||||||
|
// No UART support in bootloader
|
||||||
|
typedef struct uart_ring {} uart_ring;
|
||||||
|
uart_ring uart_ring_som_debug;
|
||||||
|
void uart_init(uart_ring *q, int baud) { UNUSED(q); UNUSED(baud); }
|
||||||
|
|
||||||
|
// ********************* Globals **********************
|
||||||
|
uint8_t hw_type = 0;
|
||||||
|
board *current_board;
|
||||||
5
panda_tici/board/can.h
Normal file
5
panda_tici/board/can.h
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define PANDA_CAN_CNT 3U
|
||||||
|
|
||||||
|
#include "opendbc/safety/can.h"
|
||||||
122
panda_tici/board/can_comms.h
Normal file
122
panda_tici/board/can_comms.h
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
CAN transactions to and from the host come in the form of
|
||||||
|
a certain number of CANPacket_t. The transaction is split
|
||||||
|
into multiple transfers or chunks.
|
||||||
|
|
||||||
|
* comms_can_read outputs this buffer in chunks of a specified length.
|
||||||
|
chunks are always the given length, except the last one.
|
||||||
|
* comms_can_write reads in this buffer in chunks.
|
||||||
|
* both functions maintain an overflow buffer for a partial CANPacket_t that
|
||||||
|
spans multiple transfers/chunks.
|
||||||
|
* the overflow buffers are reset by a dedicated control transfer handler,
|
||||||
|
which is sent by the host on each start of a connection.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t ptr;
|
||||||
|
uint32_t tail_size;
|
||||||
|
uint8_t data[72];
|
||||||
|
} asm_buffer;
|
||||||
|
|
||||||
|
static asm_buffer can_read_buffer = {.ptr = 0U, .tail_size = 0U};
|
||||||
|
|
||||||
|
int comms_can_read(uint8_t *data, uint32_t max_len) {
|
||||||
|
uint32_t pos = 0U;
|
||||||
|
|
||||||
|
// Send tail of previous message if it is in buffer
|
||||||
|
if (can_read_buffer.ptr > 0U) {
|
||||||
|
uint32_t overflow_len = MIN(max_len - pos, can_read_buffer.ptr);
|
||||||
|
(void)memcpy(&data[pos], can_read_buffer.data, overflow_len);
|
||||||
|
pos += overflow_len;
|
||||||
|
(void)memcpy(can_read_buffer.data, &can_read_buffer.data[overflow_len], can_read_buffer.ptr - overflow_len);
|
||||||
|
can_read_buffer.ptr -= overflow_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (can_read_buffer.ptr == 0U) {
|
||||||
|
// Fill rest of buffer with new data
|
||||||
|
CANPacket_t can_packet;
|
||||||
|
while ((pos < max_len) && can_pop(&can_rx_q, &can_packet)) {
|
||||||
|
uint32_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[can_packet.data_len_code];
|
||||||
|
if ((pos + pckt_len) <= max_len) {
|
||||||
|
(void)memcpy(&data[pos], (uint8_t*)&can_packet, pckt_len);
|
||||||
|
pos += pckt_len;
|
||||||
|
} else {
|
||||||
|
(void)memcpy(&data[pos], (uint8_t*)&can_packet, max_len - pos);
|
||||||
|
can_read_buffer.ptr += pckt_len - (max_len - pos);
|
||||||
|
// cppcheck-suppress objectIndex
|
||||||
|
(void)memcpy(can_read_buffer.data, &((uint8_t*)&can_packet)[(max_len - pos)], can_read_buffer.ptr);
|
||||||
|
pos = max_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
static asm_buffer can_write_buffer = {.ptr = 0U, .tail_size = 0U};
|
||||||
|
|
||||||
|
// send on CAN
|
||||||
|
void comms_can_write(const uint8_t *data, uint32_t len) {
|
||||||
|
uint32_t pos = 0U;
|
||||||
|
|
||||||
|
// Assembling can message with data from buffer
|
||||||
|
if (can_write_buffer.ptr != 0U) {
|
||||||
|
if (can_write_buffer.tail_size <= (len - pos)) {
|
||||||
|
// we have enough data to complete the buffer
|
||||||
|
CANPacket_t to_push = {0};
|
||||||
|
(void)memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], can_write_buffer.tail_size);
|
||||||
|
can_write_buffer.ptr += can_write_buffer.tail_size;
|
||||||
|
pos += can_write_buffer.tail_size;
|
||||||
|
|
||||||
|
// send out
|
||||||
|
(void)memcpy((uint8_t*)&to_push, can_write_buffer.data, can_write_buffer.ptr);
|
||||||
|
can_send(&to_push, to_push.bus, false);
|
||||||
|
|
||||||
|
// reset overflow buffer
|
||||||
|
can_write_buffer.ptr = 0U;
|
||||||
|
can_write_buffer.tail_size = 0U;
|
||||||
|
} else {
|
||||||
|
// maybe next time
|
||||||
|
uint32_t data_size = len - pos;
|
||||||
|
(void) memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], data_size);
|
||||||
|
can_write_buffer.tail_size -= data_size;
|
||||||
|
can_write_buffer.ptr += data_size;
|
||||||
|
pos += data_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rest of the message
|
||||||
|
while (pos < len) {
|
||||||
|
uint32_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[(data[pos] >> 4U)];
|
||||||
|
if ((pos + pckt_len) <= len) {
|
||||||
|
CANPacket_t to_push = {0};
|
||||||
|
(void)memcpy((uint8_t*)&to_push, &data[pos], pckt_len);
|
||||||
|
can_send(&to_push, to_push.bus, false);
|
||||||
|
pos += pckt_len;
|
||||||
|
} else {
|
||||||
|
(void)memcpy(can_write_buffer.data, &data[pos], len - pos);
|
||||||
|
can_write_buffer.ptr = len - pos;
|
||||||
|
can_write_buffer.tail_size = pckt_len - can_write_buffer.ptr;
|
||||||
|
pos += can_write_buffer.ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_can_tx_slots_available();
|
||||||
|
}
|
||||||
|
|
||||||
|
void comms_can_reset(void) {
|
||||||
|
can_write_buffer.ptr = 0U;
|
||||||
|
can_write_buffer.tail_size = 0U;
|
||||||
|
can_read_buffer.ptr = 0U;
|
||||||
|
can_read_buffer.tail_size = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make this more general!
|
||||||
|
void refresh_can_tx_slots_available(void) {
|
||||||
|
if (can_tx_check_min_slots_free(MAX_CAN_MSGS_PER_USB_BULK_TRANSFER)) {
|
||||||
|
can_tx_comms_resume_usb();
|
||||||
|
}
|
||||||
|
if (can_tx_check_min_slots_free(MAX_CAN_MSGS_PER_SPI_BULK_TRANSFER)) {
|
||||||
|
can_tx_comms_resume_spi();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
panda_tici/board/can_declarations.h
Normal file
29
panda_tici/board/can_declarations.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// bump this when changing the CAN packet
|
||||||
|
#define CAN_PACKET_VERSION 4
|
||||||
|
|
||||||
|
#define CANPACKET_HEAD_SIZE 6U
|
||||||
|
|
||||||
|
#if !defined(STM32F4)
|
||||||
|
#define CANFD
|
||||||
|
#define CANPACKET_DATA_SIZE_MAX 64U
|
||||||
|
#else
|
||||||
|
#define CANPACKET_DATA_SIZE_MAX 8U
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned char fd : 1;
|
||||||
|
unsigned char bus : 3;
|
||||||
|
unsigned char data_len_code : 4; // lookup length with dlc_to_len
|
||||||
|
unsigned char rejected : 1;
|
||||||
|
unsigned char returned : 1;
|
||||||
|
unsigned char extended : 1;
|
||||||
|
unsigned int addr : 29;
|
||||||
|
unsigned char checksum;
|
||||||
|
unsigned char data[CANPACKET_DATA_SIZE_MAX];
|
||||||
|
} __attribute__((packed, aligned(4))) CANPacket_t;
|
||||||
|
|
||||||
|
#define GET_BUS(msg) ((msg)->bus)
|
||||||
|
#define GET_LEN(msg) (dlc_to_len[(msg)->data_len_code])
|
||||||
|
#define GET_ADDR(msg) ((msg)->addr)
|
||||||
12
panda_tici/board/comms_definitions.h
Normal file
12
panda_tici/board/comms_definitions.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
typedef struct {
|
||||||
|
uint8_t request;
|
||||||
|
uint16_t param1;
|
||||||
|
uint16_t param2;
|
||||||
|
uint16_t length;
|
||||||
|
} __attribute__((packed)) ControlPacket_t;
|
||||||
|
|
||||||
|
int comms_control_handler(ControlPacket_t *req, uint8_t *resp);
|
||||||
|
void comms_endpoint2_write(const uint8_t *data, uint32_t len);
|
||||||
|
void comms_can_write(const uint8_t *data, uint32_t len);
|
||||||
|
int comms_can_read(uint8_t *data, uint32_t max_len);
|
||||||
|
void comms_can_reset(void);
|
||||||
44
panda_tici/board/config.h
Normal file
44
panda_tici/board/config.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
//#define DEBUG
|
||||||
|
//#define DEBUG_UART
|
||||||
|
//#define DEBUG_USB
|
||||||
|
//#define DEBUG_SPI
|
||||||
|
//#define DEBUG_FAULTS
|
||||||
|
//#define DEBUG_COMMS
|
||||||
|
//#define DEBUG_FAN
|
||||||
|
|
||||||
|
#define CAN_INIT_TIMEOUT_MS 500U
|
||||||
|
#define USBPACKET_MAX_SIZE 0x40U
|
||||||
|
#define MAX_CAN_MSGS_PER_USB_BULK_TRANSFER 51U
|
||||||
|
#define MAX_CAN_MSGS_PER_SPI_BULK_TRANSFER 170U
|
||||||
|
|
||||||
|
// USB definitions
|
||||||
|
#define USB_VID 0x3801U
|
||||||
|
|
||||||
|
#ifdef PANDA_JUNGLE
|
||||||
|
#ifdef BOOTSTUB
|
||||||
|
#define USB_PID 0xDDEFU
|
||||||
|
#else
|
||||||
|
#define USB_PID 0xDDCFU
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#ifdef BOOTSTUB
|
||||||
|
#define USB_PID 0xDDEEU
|
||||||
|
#else
|
||||||
|
#define USB_PID 0xDDCCU
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// platform includes
|
||||||
|
#ifdef STM32H7
|
||||||
|
#include "stm32h7/stm32h7_config.h"
|
||||||
|
#elif defined(STM32F4)
|
||||||
|
#include "stm32f4/stm32f4_config.h"
|
||||||
|
#else
|
||||||
|
// TODO: uncomment this, cppcheck complains
|
||||||
|
// building for tests
|
||||||
|
//#include "fake_stm.h"
|
||||||
|
#endif
|
||||||
21
panda_tici/board/crc.h
Normal file
21
panda_tici/board/crc.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(ENABLE_SPI) || defined(BOOTSTUB)
|
||||||
|
uint8_t crc_checksum(const uint8_t *dat, int len, const uint8_t poly) {
|
||||||
|
uint8_t crc = 0xFFU;
|
||||||
|
int i;
|
||||||
|
int j;
|
||||||
|
for (i = len - 1; i >= 0; i--) {
|
||||||
|
crc ^= dat[i];
|
||||||
|
for (j = 0; j < 8; j++) {
|
||||||
|
if ((crc & 0x80U) != 0U) {
|
||||||
|
crc = (uint8_t)((crc << 1) ^ poly);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
16
panda_tici/board/critical.h
Normal file
16
panda_tici/board/critical.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#include "critical_declarations.h"
|
||||||
|
|
||||||
|
// ********************* Critical section helpers *********************
|
||||||
|
uint8_t global_critical_depth = 0U;
|
||||||
|
|
||||||
|
static volatile bool interrupts_enabled = false;
|
||||||
|
|
||||||
|
void enable_interrupts(void) {
|
||||||
|
interrupts_enabled = true;
|
||||||
|
__enable_irq();
|
||||||
|
}
|
||||||
|
|
||||||
|
void disable_interrupts(void) {
|
||||||
|
interrupts_enabled = false;
|
||||||
|
__disable_irq();
|
||||||
|
}
|
||||||
17
panda_tici/board/critical_declarations.h
Normal file
17
panda_tici/board/critical_declarations.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// ********************* Critical section helpers *********************
|
||||||
|
void enable_interrupts(void);
|
||||||
|
void disable_interrupts(void);
|
||||||
|
|
||||||
|
extern uint8_t global_critical_depth;
|
||||||
|
|
||||||
|
#define ENTER_CRITICAL() \
|
||||||
|
__disable_irq(); \
|
||||||
|
global_critical_depth += 1U;
|
||||||
|
|
||||||
|
#define EXIT_CRITICAL() \
|
||||||
|
global_critical_depth -= 1U; \
|
||||||
|
if ((global_critical_depth == 0U) && interrupts_enabled) { \
|
||||||
|
__enable_irq(); \
|
||||||
|
}
|
||||||
26
panda_tici/board/debug/README.md
Normal file
26
panda_tici/board/debug/README.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# In-circuit debugging using openocd and gdb
|
||||||
|
|
||||||
|
## Hardware
|
||||||
|
Connect an ST-Link V2 programmer to the SWD pins on the board. The pins that need to be connected are:
|
||||||
|
- GND
|
||||||
|
- VTref
|
||||||
|
- SWDIO
|
||||||
|
- SWCLK
|
||||||
|
- NRST
|
||||||
|
|
||||||
|
Make sure you're using a genuine one for boards that do not have a 3.3V panda power rail. For example, the tres runs at 1.8V, which is not supported by the clones.
|
||||||
|
|
||||||
|
## Openocd
|
||||||
|
Install openocd. For Ubuntu 24.04, the one in the package manager works fine: `sudo apt install openocd`.
|
||||||
|
|
||||||
|
To run, use `./debug_f4.sh (TODO)` or `./debug_h7.sh` depending on the panda.
|
||||||
|
|
||||||
|
## GDB
|
||||||
|
You need `gdb-multiarch`.
|
||||||
|
|
||||||
|
Once openocd is running, you can connect from gdb as follows:
|
||||||
|
```
|
||||||
|
$ gdb-multiarch
|
||||||
|
(gdb) target ext :3333
|
||||||
|
```
|
||||||
|
To reset and break, use `monitor reset halt`.
|
||||||
4
panda_tici/board/debug/debug_h7.sh
Executable file
4
panda_tici/board/debug/debug_h7.sh
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
sudo openocd -f "interface/stlink.cfg" -c "transport select hla_swd" -f "target/stm32h7x.cfg" -c "init"
|
||||||
11
panda_tici/board/debug/dfu_util_f4.sh
Executable file
11
panda_tici/board/debug/dfu_util_f4.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DFU_UTIL="dfu-util"
|
||||||
|
|
||||||
|
scons -u -j$(nproc)
|
||||||
|
|
||||||
|
PYTHONPATH=.. python3 -c "from python import Panda; Panda().reset(enter_bootstub=True); Panda().reset(enter_bootloader=True)" || true
|
||||||
|
sleep 1
|
||||||
|
$DFU_UTIL -d 0483:df11 -a 0 -s 0x08004000 -D obj/panda.bin.signed
|
||||||
|
$DFU_UTIL -d 0483:df11 -a 0 -s 0x08000000:leave -D obj/bootstub.panda.bin
|
||||||
11
panda_tici/board/debug/dfu_util_h7.sh
Executable file
11
panda_tici/board/debug/dfu_util_h7.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DFU_UTIL="dfu-util"
|
||||||
|
|
||||||
|
scons -u -j$(nproc)
|
||||||
|
|
||||||
|
PYTHONPATH=.. python3 -c "from python import Panda; Panda().reset(enter_bootstub=True); Panda().reset(enter_bootloader=True)" || true
|
||||||
|
sleep 1
|
||||||
|
$DFU_UTIL -d 0483:df11 -a 0 -s 0x08020000 -D obj/panda_h7.bin.signed
|
||||||
|
$DFU_UTIL -d 0483:df11 -a 0 -s 0x08000000:leave -D obj/bootstub.panda_h7.bin
|
||||||
3
panda_tici/board/debug/gdb.sh
Executable file
3
panda_tici/board/debug/gdb.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
gdb-multiarch --eval-command="target extended-remote localhost:3333"
|
||||||
11
panda_tici/board/dfu_util_f4.sh
Executable file
11
panda_tici/board/dfu_util_f4.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DFU_UTIL="dfu-util"
|
||||||
|
|
||||||
|
scons -u -j$(nproc)
|
||||||
|
|
||||||
|
PYTHONPATH=.. python3 -c "from python import Panda; Panda().reset(enter_bootstub=True); Panda().reset(enter_bootloader=True)" || true
|
||||||
|
sleep 1
|
||||||
|
$DFU_UTIL -d 0483:df11 -a 0 -s 0x08004000 -D obj/panda.bin.signed
|
||||||
|
$DFU_UTIL -d 0483:df11 -a 0 -s 0x08000000:leave -D obj/bootstub.panda.bin
|
||||||
11
panda_tici/board/dfu_util_h7.sh
Executable file
11
panda_tici/board/dfu_util_h7.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DFU_UTIL="dfu-util"
|
||||||
|
|
||||||
|
scons -u -j$(nproc)
|
||||||
|
|
||||||
|
PYTHONPATH=.. python3 -c "from python import Panda; Panda().reset(enter_bootstub=True); Panda().reset(enter_bootloader=True)" || true
|
||||||
|
sleep 1
|
||||||
|
$DFU_UTIL -d 0483:df11 -a 0 -s 0x08020000 -D obj/panda_h7.bin.signed
|
||||||
|
$DFU_UTIL -d 0483:df11 -a 0 -s 0x08000000:leave -D obj/bootstub.panda_h7.bin
|
||||||
68
panda_tici/board/drivers/bootkick.h
Normal file
68
panda_tici/board/drivers/bootkick.h
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#include "bootkick_declarations.h"
|
||||||
|
|
||||||
|
bool bootkick_reset_triggered = false;
|
||||||
|
|
||||||
|
void bootkick_tick(bool ignition, bool recent_heartbeat) {
|
||||||
|
static uint16_t bootkick_last_serial_ptr = 0;
|
||||||
|
static uint8_t waiting_to_boot_countdown = 0;
|
||||||
|
static uint8_t boot_reset_countdown = 0;
|
||||||
|
static uint8_t bootkick_harness_status_prev = HARNESS_STATUS_NC;
|
||||||
|
static bool bootkick_ign_prev = false;
|
||||||
|
static BootState boot_state = BOOT_BOOTKICK;
|
||||||
|
BootState boot_state_prev = boot_state;
|
||||||
|
const bool harness_inserted = (harness.status != bootkick_harness_status_prev) && (harness.status != HARNESS_STATUS_NC);
|
||||||
|
|
||||||
|
if ((ignition && !bootkick_ign_prev) || harness_inserted) {
|
||||||
|
// bootkick on rising edge of ignition or harness insertion
|
||||||
|
boot_state = BOOT_BOOTKICK;
|
||||||
|
} else if (recent_heartbeat) {
|
||||||
|
// disable bootkick once openpilot is up
|
||||||
|
boot_state = BOOT_STANDBY;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Ensure SOM boots in case it goes into QDL mode. Reset behavior:
|
||||||
|
* shouldn't trigger on the first boot after power-on
|
||||||
|
* only try reset once per bootkick, i.e. don't keep trying until booted
|
||||||
|
* only try once per panda boot, since openpilot will reset panda on startup
|
||||||
|
* once BOOT_RESET is triggered, it stays until countdown is finished
|
||||||
|
*/
|
||||||
|
if (!bootkick_reset_triggered && (boot_state == BOOT_BOOTKICK) && (boot_state_prev == BOOT_STANDBY)) {
|
||||||
|
waiting_to_boot_countdown = 20U;
|
||||||
|
}
|
||||||
|
if (waiting_to_boot_countdown > 0U) {
|
||||||
|
bool serial_activity = uart_ring_som_debug.w_ptr_tx != bootkick_last_serial_ptr;
|
||||||
|
if (serial_activity || current_board->read_som_gpio() || (boot_state != BOOT_BOOTKICK)) {
|
||||||
|
waiting_to_boot_countdown = 0U;
|
||||||
|
} else {
|
||||||
|
// try a reset
|
||||||
|
if (waiting_to_boot_countdown == 1U) {
|
||||||
|
boot_reset_countdown = 5U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle reset state
|
||||||
|
if (boot_reset_countdown > 0U) {
|
||||||
|
boot_state = BOOT_RESET;
|
||||||
|
bootkick_reset_triggered = true;
|
||||||
|
} else {
|
||||||
|
if (boot_state == BOOT_RESET) {
|
||||||
|
boot_state = BOOT_BOOTKICK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update state
|
||||||
|
bootkick_ign_prev = ignition;
|
||||||
|
bootkick_harness_status_prev = harness.status;
|
||||||
|
bootkick_last_serial_ptr = uart_ring_som_debug.w_ptr_tx;
|
||||||
|
if (waiting_to_boot_countdown > 0U) {
|
||||||
|
waiting_to_boot_countdown--;
|
||||||
|
}
|
||||||
|
if (boot_reset_countdown > 0U) {
|
||||||
|
boot_reset_countdown--;
|
||||||
|
}
|
||||||
|
current_board->set_bootkick(boot_state);
|
||||||
|
}
|
||||||
5
panda_tici/board/drivers/bootkick_declarations.h
Normal file
5
panda_tici/board/drivers/bootkick_declarations.h
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
extern bool bootkick_reset_triggered;
|
||||||
|
|
||||||
|
void bootkick_tick(bool ignition, bool recent_heartbeat);
|
||||||
222
panda_tici/board/drivers/bxcan.h
Normal file
222
panda_tici/board/drivers/bxcan.h
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
#include "bxcan_declarations.h"
|
||||||
|
|
||||||
|
// IRQs: CAN1_TX, CAN1_RX0, CAN1_SCE
|
||||||
|
// CAN2_TX, CAN2_RX0, CAN2_SCE
|
||||||
|
// CAN3_TX, CAN3_RX0, CAN3_SCE
|
||||||
|
|
||||||
|
CAN_TypeDef *cans[CAN_ARRAY_SIZE] = {CAN1, CAN2, CAN3};
|
||||||
|
uint8_t can_irq_number[CAN_IRQS_ARRAY_SIZE][CAN_IRQS_ARRAY_SIZE] = {
|
||||||
|
{ CAN1_TX_IRQn, CAN1_RX0_IRQn, CAN1_SCE_IRQn },
|
||||||
|
{ CAN2_TX_IRQn, CAN2_RX0_IRQn, CAN2_SCE_IRQn },
|
||||||
|
{ CAN3_TX_IRQn, CAN3_RX0_IRQn, CAN3_SCE_IRQn },
|
||||||
|
};
|
||||||
|
|
||||||
|
bool can_set_speed(uint8_t can_number) {
|
||||||
|
bool ret = true;
|
||||||
|
CAN_TypeDef *CANx = CANIF_FROM_CAN_NUM(can_number);
|
||||||
|
uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number);
|
||||||
|
|
||||||
|
ret &= llcan_set_speed(
|
||||||
|
CANx,
|
||||||
|
bus_config[bus_number].can_speed,
|
||||||
|
can_loopback,
|
||||||
|
(unsigned int)(can_silent) & (1U << can_number)
|
||||||
|
);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void can_clear_send(CAN_TypeDef *CANx, uint8_t can_number) {
|
||||||
|
can_health[can_number].can_core_reset_cnt += 1U;
|
||||||
|
llcan_clear_send(CANx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) {
|
||||||
|
CAN_TypeDef *CANx = CANIF_FROM_CAN_NUM(can_number);
|
||||||
|
uint32_t esr_reg = CANx->ESR;
|
||||||
|
|
||||||
|
can_health[can_number].bus_off = ((esr_reg & CAN_ESR_BOFF) >> CAN_ESR_BOFF_Pos);
|
||||||
|
can_health[can_number].bus_off_cnt += can_health[can_number].bus_off;
|
||||||
|
can_health[can_number].error_warning = ((esr_reg & CAN_ESR_EWGF) >> CAN_ESR_EWGF_Pos);
|
||||||
|
can_health[can_number].error_passive = ((esr_reg & CAN_ESR_EPVF) >> CAN_ESR_EPVF_Pos);
|
||||||
|
|
||||||
|
can_health[can_number].last_error = ((esr_reg & CAN_ESR_LEC) >> CAN_ESR_LEC_Pos);
|
||||||
|
if ((can_health[can_number].last_error != 0U) && (can_health[can_number].last_error != 7U)) {
|
||||||
|
can_health[can_number].last_stored_error = can_health[can_number].last_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
can_health[can_number].receive_error_cnt = ((esr_reg & CAN_ESR_REC) >> CAN_ESR_REC_Pos);
|
||||||
|
can_health[can_number].transmit_error_cnt = ((esr_reg & CAN_ESR_TEC) >> CAN_ESR_TEC_Pos);
|
||||||
|
|
||||||
|
can_health[can_number].irq0_call_rate = interrupts[can_irq_number[can_number][0]].call_rate;
|
||||||
|
can_health[can_number].irq1_call_rate = interrupts[can_irq_number[can_number][1]].call_rate;
|
||||||
|
can_health[can_number].irq2_call_rate = interrupts[can_irq_number[can_number][2]].call_rate;
|
||||||
|
|
||||||
|
if (ir_reg != 0U) {
|
||||||
|
can_health[can_number].total_error_cnt += 1U;
|
||||||
|
|
||||||
|
// RX message lost due to FIFO overrun
|
||||||
|
if ((CANx->RF0R & (CAN_RF0R_FOVR0)) != 0U) {
|
||||||
|
can_health[can_number].total_rx_lost_cnt += 1U;
|
||||||
|
CANx->RF0R &= ~(CAN_RF0R_FOVR0);
|
||||||
|
}
|
||||||
|
can_clear_send(CANx, can_number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************** CAN *****************************
|
||||||
|
// CANx_SCE IRQ Handler
|
||||||
|
static void can_sce(uint8_t can_number) {
|
||||||
|
update_can_health_pkt(can_number, 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CANx_TX IRQ Handler
|
||||||
|
void process_can(uint8_t can_number) {
|
||||||
|
if (can_number != 0xffU) {
|
||||||
|
|
||||||
|
ENTER_CRITICAL();
|
||||||
|
|
||||||
|
CAN_TypeDef *CANx = CANIF_FROM_CAN_NUM(can_number);
|
||||||
|
uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number);
|
||||||
|
|
||||||
|
// check for empty mailbox
|
||||||
|
CANPacket_t to_send;
|
||||||
|
if ((CANx->TSR & (CAN_TSR_TERR0 | CAN_TSR_ALST0)) != 0U) { // last TX failed due to error arbitration lost
|
||||||
|
can_health[can_number].total_tx_lost_cnt += 1U;
|
||||||
|
CANx->TSR |= (CAN_TSR_TERR0 | CAN_TSR_ALST0);
|
||||||
|
}
|
||||||
|
if ((CANx->TSR & CAN_TSR_TME0) == CAN_TSR_TME0) {
|
||||||
|
// add successfully transmitted message to my fifo
|
||||||
|
if ((CANx->TSR & CAN_TSR_RQCP0) == CAN_TSR_RQCP0) {
|
||||||
|
if ((CANx->TSR & CAN_TSR_TXOK0) == CAN_TSR_TXOK0) {
|
||||||
|
CANPacket_t to_push;
|
||||||
|
to_push.fd = 0U;
|
||||||
|
to_push.returned = 1U;
|
||||||
|
to_push.rejected = 0U;
|
||||||
|
to_push.extended = (CANx->sTxMailBox[0].TIR >> 2) & 0x1U;
|
||||||
|
to_push.addr = (to_push.extended != 0U) ? (CANx->sTxMailBox[0].TIR >> 3) : (CANx->sTxMailBox[0].TIR >> 21);
|
||||||
|
to_push.data_len_code = CANx->sTxMailBox[0].TDTR & 0xFU;
|
||||||
|
to_push.bus = bus_number;
|
||||||
|
WORD_TO_BYTE_ARRAY(&to_push.data[0], CANx->sTxMailBox[0].TDLR);
|
||||||
|
WORD_TO_BYTE_ARRAY(&to_push.data[4], CANx->sTxMailBox[0].TDHR);
|
||||||
|
can_set_checksum(&to_push);
|
||||||
|
|
||||||
|
rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear interrupt
|
||||||
|
// careful, this can also be cleared by requesting a transmission
|
||||||
|
CANx->TSR |= CAN_TSR_RQCP0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (can_pop(can_queues[bus_number], &to_send)) {
|
||||||
|
if (can_check_checksum(&to_send)) {
|
||||||
|
can_health[can_number].total_tx_cnt += 1U;
|
||||||
|
// only send if we have received a packet
|
||||||
|
CANx->sTxMailBox[0].TIR = ((to_send.extended != 0U) ? (to_send.addr << 3) : (to_send.addr << 21)) | (to_send.extended << 2);
|
||||||
|
CANx->sTxMailBox[0].TDTR = to_send.data_len_code;
|
||||||
|
BYTE_ARRAY_TO_WORD(CANx->sTxMailBox[0].TDLR, &to_send.data[0]);
|
||||||
|
BYTE_ARRAY_TO_WORD(CANx->sTxMailBox[0].TDHR, &to_send.data[4]);
|
||||||
|
// Send request TXRQ
|
||||||
|
CANx->sTxMailBox[0].TIR |= 0x1U;
|
||||||
|
} else {
|
||||||
|
can_health[can_number].total_tx_checksum_error_cnt += 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_can_tx_slots_available();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXIT_CRITICAL();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CANx_RX0 IRQ Handler
|
||||||
|
// blink blue when we are receiving CAN messages
|
||||||
|
void can_rx(uint8_t can_number) {
|
||||||
|
CAN_TypeDef *CANx = CANIF_FROM_CAN_NUM(can_number);
|
||||||
|
uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number);
|
||||||
|
|
||||||
|
while ((CANx->RF0R & CAN_RF0R_FMP0) != 0U) {
|
||||||
|
can_health[can_number].total_rx_cnt += 1U;
|
||||||
|
|
||||||
|
// can is live
|
||||||
|
pending_can_live = 1;
|
||||||
|
|
||||||
|
// add to my fifo
|
||||||
|
CANPacket_t to_push;
|
||||||
|
|
||||||
|
to_push.fd = 0U;
|
||||||
|
to_push.returned = 0U;
|
||||||
|
to_push.rejected = 0U;
|
||||||
|
to_push.extended = (CANx->sFIFOMailBox[0].RIR >> 2) & 0x1U;
|
||||||
|
to_push.addr = (to_push.extended != 0U) ? (CANx->sFIFOMailBox[0].RIR >> 3) : (CANx->sFIFOMailBox[0].RIR >> 21);
|
||||||
|
to_push.data_len_code = CANx->sFIFOMailBox[0].RDTR & 0xFU;
|
||||||
|
to_push.bus = bus_number;
|
||||||
|
WORD_TO_BYTE_ARRAY(&to_push.data[0], CANx->sFIFOMailBox[0].RDLR);
|
||||||
|
WORD_TO_BYTE_ARRAY(&to_push.data[4], CANx->sFIFOMailBox[0].RDHR);
|
||||||
|
can_set_checksum(&to_push);
|
||||||
|
|
||||||
|
// forwarding (panda only)
|
||||||
|
int bus_fwd_num = safety_fwd_hook(bus_number, to_push.addr);
|
||||||
|
if (bus_fwd_num != -1) {
|
||||||
|
CANPacket_t to_send;
|
||||||
|
|
||||||
|
to_send.fd = 0U;
|
||||||
|
to_send.returned = 0U;
|
||||||
|
to_send.rejected = 0U;
|
||||||
|
to_send.extended = to_push.extended; // TXRQ
|
||||||
|
to_send.addr = to_push.addr;
|
||||||
|
to_send.bus = to_push.bus;
|
||||||
|
to_send.data_len_code = to_push.data_len_code;
|
||||||
|
(void)memcpy(to_send.data, to_push.data, dlc_to_len[to_push.data_len_code]);
|
||||||
|
can_set_checksum(&to_send);
|
||||||
|
|
||||||
|
can_send(&to_send, bus_fwd_num, true);
|
||||||
|
can_health[can_number].total_fwd_cnt += 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
safety_rx_invalid += safety_rx_hook(&to_push) ? 0U : 1U;
|
||||||
|
ignition_can_hook(&to_push);
|
||||||
|
|
||||||
|
led_set(LED_BLUE, true);
|
||||||
|
rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U;
|
||||||
|
|
||||||
|
// next
|
||||||
|
CANx->RF0R |= CAN_RF0R_RFOM0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CAN1_TX_IRQ_Handler(void) { process_can(0); }
|
||||||
|
static void CAN1_RX0_IRQ_Handler(void) { can_rx(0); }
|
||||||
|
static void CAN1_SCE_IRQ_Handler(void) { can_sce(0); }
|
||||||
|
|
||||||
|
static void CAN2_TX_IRQ_Handler(void) { process_can(1); }
|
||||||
|
static void CAN2_RX0_IRQ_Handler(void) { can_rx(1); }
|
||||||
|
static void CAN2_SCE_IRQ_Handler(void) { can_sce(1); }
|
||||||
|
|
||||||
|
static void CAN3_TX_IRQ_Handler(void) { process_can(2); }
|
||||||
|
static void CAN3_RX0_IRQ_Handler(void) { can_rx(2); }
|
||||||
|
static void CAN3_SCE_IRQ_Handler(void) { can_sce(2); }
|
||||||
|
|
||||||
|
bool can_init(uint8_t can_number) {
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
REGISTER_INTERRUPT(CAN1_TX_IRQn, CAN1_TX_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_1)
|
||||||
|
REGISTER_INTERRUPT(CAN1_RX0_IRQn, CAN1_RX0_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_1)
|
||||||
|
REGISTER_INTERRUPT(CAN1_SCE_IRQn, CAN1_SCE_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_1)
|
||||||
|
REGISTER_INTERRUPT(CAN2_TX_IRQn, CAN2_TX_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_2)
|
||||||
|
REGISTER_INTERRUPT(CAN2_RX0_IRQn, CAN2_RX0_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_2)
|
||||||
|
REGISTER_INTERRUPT(CAN2_SCE_IRQn, CAN2_SCE_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_2)
|
||||||
|
REGISTER_INTERRUPT(CAN3_TX_IRQn, CAN3_TX_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_3)
|
||||||
|
REGISTER_INTERRUPT(CAN3_RX0_IRQn, CAN3_RX0_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_3)
|
||||||
|
REGISTER_INTERRUPT(CAN3_SCE_IRQn, CAN3_SCE_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_3)
|
||||||
|
|
||||||
|
if (can_number != 0xffU) {
|
||||||
|
CAN_TypeDef *CANx = CANIF_FROM_CAN_NUM(can_number);
|
||||||
|
ret &= can_set_speed(can_number);
|
||||||
|
ret &= llcan_init(CANx);
|
||||||
|
// in case there are queued up messages
|
||||||
|
process_can(can_number);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
22
panda_tici/board/drivers/bxcan_declarations.h
Normal file
22
panda_tici/board/drivers/bxcan_declarations.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// IRQs: CAN1_TX, CAN1_RX0, CAN1_SCE
|
||||||
|
// CAN2_TX, CAN2_RX0, CAN2_SCE
|
||||||
|
// CAN3_TX, CAN3_RX0, CAN3_SCE
|
||||||
|
|
||||||
|
#define CAN_ARRAY_SIZE 3
|
||||||
|
#define CAN_IRQS_ARRAY_SIZE 3
|
||||||
|
extern CAN_TypeDef *cans[CAN_ARRAY_SIZE];
|
||||||
|
extern uint8_t can_irq_number[CAN_IRQS_ARRAY_SIZE][CAN_IRQS_ARRAY_SIZE];
|
||||||
|
|
||||||
|
bool can_set_speed(uint8_t can_number);
|
||||||
|
void can_clear_send(CAN_TypeDef *CANx, uint8_t can_number);
|
||||||
|
void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg);
|
||||||
|
|
||||||
|
// ***************************** CAN *****************************
|
||||||
|
// CANx_TX IRQ Handler
|
||||||
|
void process_can(uint8_t can_number);
|
||||||
|
// CANx_RX0 IRQ Handler
|
||||||
|
// blink blue when we are receiving CAN messages
|
||||||
|
void can_rx(uint8_t can_number);
|
||||||
|
bool can_init(uint8_t can_number);
|
||||||
262
panda_tici/board/drivers/can_common.h
Normal file
262
panda_tici/board/drivers/can_common.h
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
#include "can_common_declarations.h"
|
||||||
|
|
||||||
|
uint32_t safety_tx_blocked = 0;
|
||||||
|
uint32_t safety_rx_invalid = 0;
|
||||||
|
uint32_t tx_buffer_overflow = 0;
|
||||||
|
uint32_t rx_buffer_overflow = 0;
|
||||||
|
|
||||||
|
can_health_t can_health[PANDA_CAN_CNT] = {{0}, {0}, {0}};
|
||||||
|
|
||||||
|
// Ignition detected from CAN meessages
|
||||||
|
bool ignition_can = false;
|
||||||
|
uint32_t ignition_can_cnt = 0U;
|
||||||
|
|
||||||
|
int pending_can_live = 0;
|
||||||
|
bool can_silent = true;
|
||||||
|
bool can_loopback = false;
|
||||||
|
|
||||||
|
// ********************* instantiate queues *********************
|
||||||
|
#define can_buffer(x, size) \
|
||||||
|
static CANPacket_t elems_##x[size]; \
|
||||||
|
extern can_ring can_##x; \
|
||||||
|
can_ring can_##x = { .w_ptr = 0, .r_ptr = 0, .fifo_size = (size), .elems = (CANPacket_t *)&(elems_##x) };
|
||||||
|
|
||||||
|
#define CAN_RX_BUFFER_SIZE 4096U
|
||||||
|
#define CAN_TX_BUFFER_SIZE 416U
|
||||||
|
|
||||||
|
#ifdef STM32H7
|
||||||
|
// ITCM RAM and DTCM RAM are the fastest for Cortex-M7 core access
|
||||||
|
__attribute__((section(".axisram"))) can_buffer(rx_q, CAN_RX_BUFFER_SIZE)
|
||||||
|
__attribute__((section(".itcmram"))) can_buffer(tx1_q, CAN_TX_BUFFER_SIZE)
|
||||||
|
__attribute__((section(".itcmram"))) can_buffer(tx2_q, CAN_TX_BUFFER_SIZE)
|
||||||
|
#else
|
||||||
|
can_buffer(rx_q, CAN_RX_BUFFER_SIZE)
|
||||||
|
can_buffer(tx1_q, CAN_TX_BUFFER_SIZE)
|
||||||
|
can_buffer(tx2_q, CAN_TX_BUFFER_SIZE)
|
||||||
|
#endif
|
||||||
|
can_buffer(tx3_q, CAN_TX_BUFFER_SIZE)
|
||||||
|
|
||||||
|
// FIXME:
|
||||||
|
// cppcheck-suppress misra-c2012-9.3
|
||||||
|
can_ring *can_queues[PANDA_CAN_CNT] = {&can_tx1_q, &can_tx2_q, &can_tx3_q};
|
||||||
|
|
||||||
|
// ********************* interrupt safe queue *********************
|
||||||
|
bool can_pop(can_ring *q, CANPacket_t *elem) {
|
||||||
|
bool ret = 0;
|
||||||
|
|
||||||
|
ENTER_CRITICAL();
|
||||||
|
if (q->w_ptr != q->r_ptr) {
|
||||||
|
*elem = q->elems[q->r_ptr];
|
||||||
|
if ((q->r_ptr + 1U) == q->fifo_size) {
|
||||||
|
q->r_ptr = 0;
|
||||||
|
} else {
|
||||||
|
q->r_ptr += 1U;
|
||||||
|
}
|
||||||
|
ret = 1;
|
||||||
|
}
|
||||||
|
EXIT_CRITICAL();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool can_push(can_ring *q, const CANPacket_t *elem) {
|
||||||
|
bool ret = false;
|
||||||
|
uint32_t next_w_ptr;
|
||||||
|
|
||||||
|
ENTER_CRITICAL();
|
||||||
|
if ((q->w_ptr + 1U) == q->fifo_size) {
|
||||||
|
next_w_ptr = 0;
|
||||||
|
} else {
|
||||||
|
next_w_ptr = q->w_ptr + 1U;
|
||||||
|
}
|
||||||
|
if (next_w_ptr != q->r_ptr) {
|
||||||
|
q->elems[q->w_ptr] = *elem;
|
||||||
|
q->w_ptr = next_w_ptr;
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
EXIT_CRITICAL();
|
||||||
|
if (!ret) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
print("can_push to ");
|
||||||
|
if (q == &can_rx_q) {
|
||||||
|
print("can_rx_q");
|
||||||
|
} else if (q == &can_tx1_q) {
|
||||||
|
print("can_tx1_q");
|
||||||
|
} else if (q == &can_tx2_q) {
|
||||||
|
print("can_tx2_q");
|
||||||
|
} else if (q == &can_tx3_q) {
|
||||||
|
print("can_tx3_q");
|
||||||
|
} else {
|
||||||
|
print("unknown");
|
||||||
|
}
|
||||||
|
print(" failed!\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t can_slots_empty(const can_ring *q) {
|
||||||
|
uint32_t ret = 0;
|
||||||
|
|
||||||
|
ENTER_CRITICAL();
|
||||||
|
if (q->w_ptr >= q->r_ptr) {
|
||||||
|
ret = q->fifo_size - 1U - q->w_ptr + q->r_ptr;
|
||||||
|
} else {
|
||||||
|
ret = q->r_ptr - q->w_ptr - 1U;
|
||||||
|
}
|
||||||
|
EXIT_CRITICAL();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void can_clear(can_ring *q) {
|
||||||
|
ENTER_CRITICAL();
|
||||||
|
q->w_ptr = 0;
|
||||||
|
q->r_ptr = 0;
|
||||||
|
EXIT_CRITICAL();
|
||||||
|
// handle TX buffer full with zero ECUs awake on the bus
|
||||||
|
refresh_can_tx_slots_available();
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign CAN numbering
|
||||||
|
// bus num: CAN Bus numbers in panda, sent to/from USB
|
||||||
|
// Min: 0; Max: 127; Bit 7 marks message as receipt (bus 129 is receipt for but 1)
|
||||||
|
// cans: Look up MCU can interface from bus number
|
||||||
|
// can number: numeric lookup for MCU CAN interfaces (0 = CAN1, 1 = CAN2, etc);
|
||||||
|
// bus_lookup: Translates from 'can number' to 'bus number'.
|
||||||
|
// can_num_lookup: Translates from 'bus number' to 'can number'.
|
||||||
|
// forwarding bus: If >= 0, forward all messages from this bus to the specified bus.
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
// Panda: Bus 0=CAN1 Bus 1=CAN2 Bus 2=CAN3
|
||||||
|
bus_config_t bus_config[PANDA_CAN_CNT] = {
|
||||||
|
{ .bus_lookup = 0U, .can_num_lookup = 0U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false },
|
||||||
|
{ .bus_lookup = 1U, .can_num_lookup = 1U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false },
|
||||||
|
{ .bus_lookup = 2U, .can_num_lookup = 2U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false },
|
||||||
|
// { .bus_lookup = 0xFFU, .can_num_lookup = 0xFFU, .forwarding_bus = -1, .can_speed = 333U, .can_data_speed = 333U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false },
|
||||||
|
};
|
||||||
|
|
||||||
|
void can_init_all(void) {
|
||||||
|
for (uint8_t i=0U; i < PANDA_CAN_CNT; i++) {
|
||||||
|
if (!current_board->has_canfd) {
|
||||||
|
bus_config[i].can_data_speed = 0U;
|
||||||
|
bus_config[i].canfd_enabled = false;
|
||||||
|
}
|
||||||
|
can_clear(can_queues[i]);
|
||||||
|
(void)can_init(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void can_set_orientation(bool flipped) {
|
||||||
|
bus_config[0].bus_lookup = flipped ? 2U : 0U;
|
||||||
|
bus_config[0].can_num_lookup = flipped ? 2U : 0U;
|
||||||
|
bus_config[2].bus_lookup = flipped ? 0U : 2U;
|
||||||
|
bus_config[2].can_num_lookup = flipped ? 0U : 2U;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PANDA_JUNGLE
|
||||||
|
void can_set_forwarding(uint8_t from, uint8_t to) {
|
||||||
|
bus_config[from].forwarding_bus = to;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ignition_can_hook(CANPacket_t *msg) {
|
||||||
|
if (msg->bus == 0U) {
|
||||||
|
int len = GET_LEN(msg);
|
||||||
|
|
||||||
|
// GM exception
|
||||||
|
if ((msg->addr == 0x1F1U) && (len == 8)) {
|
||||||
|
// SystemPowerMode (2=Run, 3=Crank Request)
|
||||||
|
ignition_can = (msg->data[0] & 0x2U) != 0U;
|
||||||
|
ignition_can_cnt = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rivian R1S/T GEN1 exception
|
||||||
|
if ((msg->addr == 0x152U) && (len == 8)) {
|
||||||
|
// 0x152 overlaps with Subaru pre-global which has this bit as the high beam
|
||||||
|
int counter = msg->data[1] & 0xFU; // max is only 14
|
||||||
|
|
||||||
|
static int prev_counter_rivian = -1;
|
||||||
|
if ((counter == ((prev_counter_rivian + 1) % 15)) && (prev_counter_rivian != -1)) {
|
||||||
|
// VDM_OutputSignals->VDM_EpasPowerMode
|
||||||
|
ignition_can = ((msg->data[7] >> 4U) & 0x3U) == 1U; // VDM_EpasPowerMode_Drive_On=1
|
||||||
|
ignition_can_cnt = 0U;
|
||||||
|
}
|
||||||
|
prev_counter_rivian = counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tesla Model 3/Y exception
|
||||||
|
if ((msg->addr == 0x221U) && (len == 8)) {
|
||||||
|
// 0x221 overlaps with Rivian which has random data on byte 0
|
||||||
|
int counter = msg->data[6] >> 4;
|
||||||
|
|
||||||
|
static int prev_counter_tesla = -1;
|
||||||
|
if ((counter == ((prev_counter_tesla + 1) % 16)) && (prev_counter_tesla != -1)) {
|
||||||
|
// VCFRONT_LVPowerState->VCFRONT_vehiclePowerState
|
||||||
|
int power_state = (msg->data[0] >> 5U) & 0x3U;
|
||||||
|
ignition_can = power_state == 0x3; // VEHICLE_POWER_STATE_DRIVE=3
|
||||||
|
ignition_can_cnt = 0U;
|
||||||
|
}
|
||||||
|
prev_counter_tesla = counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mazda exception
|
||||||
|
if ((msg->addr == 0x9EU) && (len == 8)) {
|
||||||
|
ignition_can = (msg->data[0] >> 5) == 0x6U;
|
||||||
|
ignition_can_cnt = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool can_tx_check_min_slots_free(uint32_t min) {
|
||||||
|
return
|
||||||
|
(can_slots_empty(&can_tx1_q) >= min) &&
|
||||||
|
(can_slots_empty(&can_tx2_q) >= min) &&
|
||||||
|
(can_slots_empty(&can_tx3_q) >= min);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t calculate_checksum(const uint8_t *dat, uint32_t len) {
|
||||||
|
uint8_t checksum = 0U;
|
||||||
|
for (uint32_t i = 0U; i < len; i++) {
|
||||||
|
checksum ^= dat[i];
|
||||||
|
}
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
void can_set_checksum(CANPacket_t *packet) {
|
||||||
|
packet->checksum = 0U;
|
||||||
|
packet->checksum = calculate_checksum((uint8_t *) packet, CANPACKET_HEAD_SIZE + GET_LEN(packet));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool can_check_checksum(CANPacket_t *packet) {
|
||||||
|
return (calculate_checksum((uint8_t *) packet, CANPACKET_HEAD_SIZE + GET_LEN(packet)) == 0U);
|
||||||
|
}
|
||||||
|
|
||||||
|
void can_send(CANPacket_t *to_push, uint8_t bus_number, bool skip_tx_hook) {
|
||||||
|
if (skip_tx_hook || safety_tx_hook(to_push) != 0) {
|
||||||
|
if (bus_number < PANDA_CAN_CNT) {
|
||||||
|
// add CAN packet to send queue
|
||||||
|
tx_buffer_overflow += can_push(can_queues[bus_number], to_push) ? 0U : 1U;
|
||||||
|
process_can(CAN_NUM_FROM_BUS_NUM(bus_number));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
safety_tx_blocked += 1U;
|
||||||
|
to_push->returned = 0U;
|
||||||
|
to_push->rejected = 1U;
|
||||||
|
|
||||||
|
// data changed
|
||||||
|
can_set_checksum(to_push);
|
||||||
|
rx_buffer_overflow += can_push(&can_rx_q, to_push) ? 0U : 1U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_speed_valid(uint32_t speed, const uint32_t *all_speeds, uint8_t len) {
|
||||||
|
bool ret = false;
|
||||||
|
for (uint8_t i = 0U; i < len; i++) {
|
||||||
|
if (all_speeds[i] == speed) {
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
81
panda_tici/board/drivers/can_common_declarations.h
Normal file
81
panda_tici/board/drivers/can_common_declarations.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
volatile uint32_t w_ptr;
|
||||||
|
volatile uint32_t r_ptr;
|
||||||
|
uint32_t fifo_size;
|
||||||
|
CANPacket_t *elems;
|
||||||
|
} can_ring;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t bus_lookup;
|
||||||
|
uint8_t can_num_lookup;
|
||||||
|
int8_t forwarding_bus;
|
||||||
|
uint32_t can_speed;
|
||||||
|
uint32_t can_data_speed;
|
||||||
|
bool canfd_auto;
|
||||||
|
bool canfd_enabled;
|
||||||
|
bool brs_enabled;
|
||||||
|
bool canfd_non_iso;
|
||||||
|
} bus_config_t;
|
||||||
|
|
||||||
|
extern uint32_t safety_tx_blocked;
|
||||||
|
extern uint32_t safety_rx_invalid;
|
||||||
|
extern uint32_t tx_buffer_overflow;
|
||||||
|
extern uint32_t rx_buffer_overflow;
|
||||||
|
|
||||||
|
extern can_health_t can_health[PANDA_CAN_CNT];
|
||||||
|
|
||||||
|
// Ignition detected from CAN meessages
|
||||||
|
extern bool ignition_can;
|
||||||
|
extern uint32_t ignition_can_cnt;
|
||||||
|
|
||||||
|
extern bool can_silent;
|
||||||
|
|
||||||
|
extern int pending_can_live;
|
||||||
|
|
||||||
|
// ******************* functions prototypes *********************
|
||||||
|
bool can_init(uint8_t can_number);
|
||||||
|
void process_can(uint8_t can_number);
|
||||||
|
|
||||||
|
// ********************* instantiate queues *********************
|
||||||
|
extern can_ring *can_queues[PANDA_CAN_CNT];
|
||||||
|
|
||||||
|
// helpers
|
||||||
|
#define WORD_TO_BYTE_ARRAY(dst8, src32) 0[dst8] = ((src32) & 0xFFU); 1[dst8] = (((src32) >> 8U) & 0xFFU); 2[dst8] = (((src32) >> 16U) & 0xFFU); 3[dst8] = (((src32) >> 24U) & 0xFFU)
|
||||||
|
#define BYTE_ARRAY_TO_WORD(dst32, src8) ((dst32) = 0[src8] | (1[src8] << 8U) | (2[src8] << 16U) | (3[src8] << 24U))
|
||||||
|
|
||||||
|
// ********************* interrupt safe queue *********************
|
||||||
|
bool can_pop(can_ring *q, CANPacket_t *elem);
|
||||||
|
bool can_push(can_ring *q, const CANPacket_t *elem);
|
||||||
|
uint32_t can_slots_empty(const can_ring *q);
|
||||||
|
|
||||||
|
// assign CAN numbering
|
||||||
|
// bus num: CAN Bus numbers in panda, sent to/from USB
|
||||||
|
// Min: 0; Max: 127; Bit 7 marks message as receipt (bus 129 is receipt for but 1)
|
||||||
|
// cans: Look up MCU can interface from bus number
|
||||||
|
// can number: numeric lookup for MCU CAN interfaces (0 = CAN1, 1 = CAN2, etc);
|
||||||
|
// bus_lookup: Translates from 'can number' to 'bus number'.
|
||||||
|
// can_num_lookup: Translates from 'bus number' to 'can number'.
|
||||||
|
// forwarding bus: If >= 0, forward all messages from this bus to the specified bus.
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
// Panda: Bus 0=CAN1 Bus 1=CAN2 Bus 2=CAN3
|
||||||
|
extern bus_config_t bus_config[PANDA_CAN_CNT];
|
||||||
|
|
||||||
|
#define CANIF_FROM_CAN_NUM(num) (cans[num])
|
||||||
|
#define BUS_NUM_FROM_CAN_NUM(num) (bus_config[num].bus_lookup)
|
||||||
|
#define CAN_NUM_FROM_BUS_NUM(num) (bus_config[num].can_num_lookup)
|
||||||
|
|
||||||
|
void can_init_all(void);
|
||||||
|
void can_set_orientation(bool flipped);
|
||||||
|
#ifdef PANDA_JUNGLE
|
||||||
|
void can_set_forwarding(uint8_t from, uint8_t to);
|
||||||
|
#endif
|
||||||
|
void ignition_can_hook(CANPacket_t *to_push);
|
||||||
|
bool can_tx_check_min_slots_free(uint32_t min);
|
||||||
|
uint8_t calculate_checksum(const uint8_t *dat, uint32_t len);
|
||||||
|
void can_set_checksum(CANPacket_t *packet);
|
||||||
|
bool can_check_checksum(CANPacket_t *packet);
|
||||||
|
void can_send(CANPacket_t *to_push, uint8_t bus_number, bool skip_tx_hook);
|
||||||
|
bool is_speed_valid(uint32_t speed, const uint32_t *all_speeds, uint8_t len);
|
||||||
44
panda_tici/board/drivers/clock_source.h
Normal file
44
panda_tici/board/drivers/clock_source.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#include "clock_source_declarations.h"
|
||||||
|
|
||||||
|
void clock_source_set_timer_params(uint16_t param1, uint16_t param2) {
|
||||||
|
// Pulse length of each channel
|
||||||
|
register_set(&(TIM1->CCR1), (((param1 & 0xFF00U) >> 8U)*10U), 0xFFFFU);
|
||||||
|
register_set(&(TIM1->CCR2), ((param1 & 0x00FFU)*10U), 0xFFFFU);
|
||||||
|
register_set(&(TIM1->CCR3), (((param2 & 0xFF00U) >> 8U)*10U), 0xFFFFU);
|
||||||
|
// Timer period
|
||||||
|
register_set(&(TIM1->ARR), (((param2 & 0x00FFU)*10U) - 1U), 0xFFFFU);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clock_source_init(bool enable_channel1) {
|
||||||
|
// Setup timer
|
||||||
|
register_set(&(TIM1->PSC), ((APB2_TIMER_FREQ*100U)-1U), 0xFFFFU); // Tick on 0.1 ms
|
||||||
|
register_set(&(TIM1->ARR), ((CLOCK_SOURCE_PERIOD_MS*10U) - 1U), 0xFFFFU); // Period
|
||||||
|
register_set(&(TIM1->CCMR1), 0U, 0xFFFFU); // No output on compare
|
||||||
|
register_set(&(TIM1->CCER), TIM_CCER_CC1E, 0xFFFFU); // Enable compare 1
|
||||||
|
register_set(&(TIM1->CCR1), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value
|
||||||
|
register_set(&(TIM1->CCR2), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value
|
||||||
|
register_set(&(TIM1->CCR3), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value
|
||||||
|
register_set_bits(&(TIM1->DIER), TIM_DIER_UIE | TIM_DIER_CC1IE); // Enable interrupts
|
||||||
|
register_set(&(TIM1->CR1), TIM_CR1_CEN, 0x3FU); // Enable timer
|
||||||
|
|
||||||
|
// No interrupts
|
||||||
|
NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn);
|
||||||
|
NVIC_DisableIRQ(TIM1_CC_IRQn);
|
||||||
|
|
||||||
|
// Set GPIO as timer channels
|
||||||
|
if (enable_channel1) {
|
||||||
|
set_gpio_alternate(GPIOA, 8, GPIO_AF1_TIM1);
|
||||||
|
}
|
||||||
|
set_gpio_alternate(GPIOB, 14, GPIO_AF1_TIM1);
|
||||||
|
set_gpio_alternate(GPIOB, 15, GPIO_AF1_TIM1);
|
||||||
|
|
||||||
|
// Set PWM mode
|
||||||
|
register_set(&(TIM1->CCMR1), (0b110UL << TIM_CCMR1_OC1M_Pos) | (0b110UL << TIM_CCMR1_OC2M_Pos), 0xFFFFU);
|
||||||
|
register_set(&(TIM1->CCMR2), (0b110UL << TIM_CCMR2_OC3M_Pos), 0xFFFFU);
|
||||||
|
|
||||||
|
// Enable output
|
||||||
|
register_set(&(TIM1->BDTR), TIM_BDTR_MOE, 0xFFFFU);
|
||||||
|
|
||||||
|
// Enable complementary compares
|
||||||
|
register_set_bits(&(TIM1->CCER), TIM_CCER_CC2NE | TIM_CCER_CC3NE);
|
||||||
|
}
|
||||||
7
panda_tici/board/drivers/clock_source_declarations.h
Normal file
7
panda_tici/board/drivers/clock_source_declarations.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define CLOCK_SOURCE_PERIOD_MS 50U
|
||||||
|
#define CLOCK_SOURCE_PULSE_LEN_MS 2U
|
||||||
|
|
||||||
|
void clock_source_set_timer_params(uint16_t param1, uint16_t param2);
|
||||||
|
void clock_source_init(bool enable_channel1);
|
||||||
84
panda_tici/board/drivers/fake_siren.h
Normal file
84
panda_tici/board/drivers/fake_siren.h
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#include "stm32h7/lli2c.h"
|
||||||
|
|
||||||
|
#define CODEC_I2C_ADDR 0x10
|
||||||
|
|
||||||
|
void fake_siren_init(void);
|
||||||
|
|
||||||
|
void fake_siren_codec_enable(bool enabled) {
|
||||||
|
if (enabled) {
|
||||||
|
bool success = true;
|
||||||
|
success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x2B, (1U << 1)); // Left speaker mix from INA1
|
||||||
|
success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x2C, (1U << 1)); // Right speaker mix from INA1
|
||||||
|
success &= i2c_set_reg_mask(I2C5, CODEC_I2C_ADDR, 0x3D, 0x17, 0b11111); // Left speaker volume
|
||||||
|
success &= i2c_set_reg_mask(I2C5, CODEC_I2C_ADDR, 0x3E, 0x17, 0b11111); // Right speaker volume
|
||||||
|
success &= i2c_set_reg_mask(I2C5, CODEC_I2C_ADDR, 0x37, 0b101, 0b111); // INA gain
|
||||||
|
success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x4C, (1U << 7)); // Enable INA
|
||||||
|
success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x51, (1U << 7)); // Disable global shutdown
|
||||||
|
if (!success) {
|
||||||
|
print("Siren codec enable failed\n");
|
||||||
|
fault_occurred(FAULT_SIREN_MALFUNCTION);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Disable INA input. Make sure to retry a few times if the I2C bus is busy.
|
||||||
|
for (uint8_t i=0U; i<10U; i++) {
|
||||||
|
if (i2c_clear_reg_bits(I2C5, CODEC_I2C_ADDR, 0x4C, (1U << 7))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void fake_siren_set(bool enabled) {
|
||||||
|
static bool initialized = false;
|
||||||
|
static bool fake_siren_enabled = false;
|
||||||
|
|
||||||
|
if (!initialized) {
|
||||||
|
fake_siren_init();
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled != fake_siren_enabled) {
|
||||||
|
fake_siren_codec_enable(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
register_set_bits(&DMA1_Stream1->CR, DMA_SxCR_EN);
|
||||||
|
} else {
|
||||||
|
register_clear_bits(&DMA1_Stream1->CR, DMA_SxCR_EN);
|
||||||
|
}
|
||||||
|
fake_siren_enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fake_siren_init(void) {
|
||||||
|
// 1Vpp sine wave with 1V offset
|
||||||
|
static const uint8_t fake_siren_lut[360] = { 134U, 135U, 137U, 138U, 139U, 140U, 141U, 143U, 144U, 145U, 146U, 148U, 149U, 150U, 151U, 152U, 154U, 155U, 156U, 157U, 158U, 159U, 160U, 162U, 163U, 164U, 165U, 166U, 167U, 168U, 169U, 170U, 171U, 172U, 174U, 175U, 176U, 177U, 177U, 178U, 179U, 180U, 181U, 182U, 183U, 184U, 185U, 186U, 186U, 187U, 188U, 189U, 190U, 190U, 191U, 192U, 193U, 193U, 194U, 195U, 195U, 196U, 196U, 197U, 197U, 198U, 199U, 199U, 199U, 200U, 200U, 201U, 201U, 202U, 202U, 202U, 203U, 203U, 203U, 203U, 204U, 204U, 204U, 204U, 204U, 204U, 204U, 205U, 205U, 205U, 205U, 205U, 205U, 205U, 204U, 204U, 204U, 204U, 204U, 204U, 204U, 203U, 203U, 203U, 203U, 202U, 202U, 202U, 201U, 201U, 200U, 200U, 199U, 199U, 199U, 198U, 197U, 197U, 196U, 196U, 195U, 195U, 194U, 193U, 193U, 192U, 191U, 190U, 190U, 189U, 188U, 187U, 186U, 186U, 185U, 184U, 183U, 182U, 181U, 180U, 179U, 178U, 177U, 177U, 176U, 175U, 174U, 172U, 171U, 170U, 169U, 168U, 167U, 166U, 165U, 164U, 163U, 162U, 160U, 159U, 158U, 157U, 156U, 155U, 154U, 152U, 151U, 150U, 149U, 148U, 146U, 145U, 144U, 143U, 141U, 140U, 139U, 138U, 137U, 135U, 134U, 133U, 132U, 130U, 129U, 128U, 127U, 125U, 124U, 123U, 122U, 121U, 119U, 118U, 117U, 116U, 115U, 113U, 112U, 111U, 110U, 109U, 108U, 106U, 105U, 104U, 103U, 102U, 101U, 100U, 99U, 98U, 97U, 96U, 95U, 94U, 93U, 92U, 91U, 90U, 89U, 88U, 87U, 86U, 85U, 84U, 83U, 82U, 82U, 81U, 80U, 79U, 78U, 78U, 77U, 76U, 76U, 75U, 74U, 74U, 73U, 72U, 72U, 71U, 71U, 70U, 70U, 69U, 69U, 68U, 68U, 67U, 67U, 67U, 66U, 66U, 66U, 65U, 65U, 65U, 65U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 63U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 65U, 65U, 65U, 65U, 66U, 66U, 66U, 67U, 67U, 67U, 68U, 68U, 69U, 69U, 70U, 70U, 71U, 71U, 72U, 72U, 73U, 74U, 74U, 75U, 76U, 76U, 77U, 78U, 78U, 79U, 80U, 81U, 82U, 82U, 83U, 84U, 85U, 86U, 87U, 88U, 89U, 90U, 91U, 92U, 93U, 94U, 95U, 96U, 97U, 98U, 99U, 100U, 101U, 102U, 103U, 104U, 105U, 106U, 108U, 109U, 110U, 111U, 112U, 113U, 115U, 116U, 117U, 118U, 119U, 121U, 122U, 123U, 124U, 125U, 127U, 128U, 129U, 130U, 132U, 133U };
|
||||||
|
|
||||||
|
// Init DAC
|
||||||
|
register_set(&DAC1->MCR, 0U, 0xFFFFFFFFU);
|
||||||
|
register_set(&DAC1->CR, DAC_CR_TEN1 | (6U << DAC_CR_TSEL1_Pos) | DAC_CR_DMAEN1, 0xFFFFFFFFU);
|
||||||
|
register_set_bits(&DAC1->CR, DAC_CR_EN1);
|
||||||
|
|
||||||
|
// Setup DMAMUX (DAC_CH1_DMA as input)
|
||||||
|
register_set(&DMAMUX1_Channel1->CCR, 67U, DMAMUX_CxCR_DMAREQ_ID_Msk);
|
||||||
|
|
||||||
|
// Setup DMA
|
||||||
|
register_set(&DMA1_Stream1->M0AR, (uint32_t) fake_siren_lut, 0xFFFFFFFFU);
|
||||||
|
register_set(&DMA1_Stream1->PAR, (uint32_t) &(DAC1->DHR8R1), 0xFFFFFFFFU);
|
||||||
|
DMA1_Stream1->NDTR = sizeof(fake_siren_lut);
|
||||||
|
register_set(&DMA1_Stream1->FCR, 0U, 0x00000083U);
|
||||||
|
DMA1_Stream1->CR = (0b11UL << DMA_SxCR_PL_Pos);
|
||||||
|
DMA1_Stream1->CR |= DMA_SxCR_MINC | DMA_SxCR_CIRC | (1U << DMA_SxCR_DIR_Pos);
|
||||||
|
|
||||||
|
// Init trigger timer (around 2.5kHz)
|
||||||
|
register_set(&TIM7->PSC, 0U, 0xFFFFU);
|
||||||
|
register_set(&TIM7->ARR, 133U, 0xFFFFU);
|
||||||
|
register_set(&TIM7->CR2, (0b10U << TIM_CR2_MMS_Pos), TIM_CR2_MMS_Msk);
|
||||||
|
register_set(&TIM7->CR1, TIM_CR1_ARPE | TIM_CR1_URS, 0x088EU);
|
||||||
|
TIM7->SR = 0U;
|
||||||
|
TIM7->CR1 |= TIM_CR1_CEN;
|
||||||
|
|
||||||
|
// Enable the I2C to the codec
|
||||||
|
i2c_init(I2C5);
|
||||||
|
fake_siren_codec_enable(false);
|
||||||
|
}
|
||||||
87
panda_tici/board/drivers/fan.h
Normal file
87
panda_tici/board/drivers/fan.h
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#include "fan_declarations.h"
|
||||||
|
|
||||||
|
struct fan_state_t fan_state;
|
||||||
|
|
||||||
|
static const uint8_t FAN_TICK_FREQ = 8U;
|
||||||
|
static const uint8_t FAN_STALL_THRESHOLD_MIN = 3U;
|
||||||
|
|
||||||
|
|
||||||
|
void fan_set_power(uint8_t percentage) {
|
||||||
|
fan_state.target_rpm = ((current_board->fan_max_rpm * CLAMP(percentage, 0U, 100U)) / 100U);
|
||||||
|
}
|
||||||
|
|
||||||
|
void llfan_init(void);
|
||||||
|
void fan_init(void) {
|
||||||
|
fan_state.stall_threshold = FAN_STALL_THRESHOLD_MIN;
|
||||||
|
fan_state.cooldown_counter = current_board->fan_enable_cooldown_time * FAN_TICK_FREQ;
|
||||||
|
llfan_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call this at FAN_TICK_FREQ
|
||||||
|
void fan_tick(void) {
|
||||||
|
const float FAN_I = 6.5f;
|
||||||
|
const uint8_t FAN_STALL_THRESHOLD_MAX = 8U;
|
||||||
|
|
||||||
|
if (current_board->fan_max_rpm > 0U) {
|
||||||
|
// Measure fan RPM
|
||||||
|
uint16_t fan_rpm_fast = fan_state.tach_counter * (60U * FAN_TICK_FREQ / 4U); // 4 interrupts per rotation
|
||||||
|
fan_state.tach_counter = 0U;
|
||||||
|
fan_state.rpm = (fan_rpm_fast + (3U * fan_state.rpm)) / 4U;
|
||||||
|
|
||||||
|
// Stall detection
|
||||||
|
bool fan_stalled = false;
|
||||||
|
if (current_board->fan_stall_recovery) {
|
||||||
|
if (fan_state.target_rpm > 0U) {
|
||||||
|
if (fan_rpm_fast == 0U) {
|
||||||
|
fan_state.stall_counter = MIN(fan_state.stall_counter + 1U, 254U);
|
||||||
|
} else {
|
||||||
|
fan_state.stall_counter = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fan_state.stall_counter > (fan_state.stall_threshold*FAN_TICK_FREQ)) {
|
||||||
|
fan_stalled = true;
|
||||||
|
fan_state.stall_counter = 0U;
|
||||||
|
fan_state.stall_threshold = CLAMP(fan_state.stall_threshold + 2U, FAN_STALL_THRESHOLD_MIN, FAN_STALL_THRESHOLD_MAX);
|
||||||
|
fan_state.total_stall_count += 1U;
|
||||||
|
|
||||||
|
// datasheet gives this range as the minimum startup duty
|
||||||
|
fan_state.error_integral = CLAMP(fan_state.error_integral, 20.0f, 45.0f);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fan_state.stall_counter = 0U;
|
||||||
|
fan_state.stall_threshold = FAN_STALL_THRESHOLD_MIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_FAN
|
||||||
|
puth(fan_state.target_rpm);
|
||||||
|
print(" "); puth(fan_rpm_fast);
|
||||||
|
print(" "); puth(fan_state.power);
|
||||||
|
print(" "); puth(fan_state.stall_counter);
|
||||||
|
print("\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Cooldown counter
|
||||||
|
if (fan_state.target_rpm > 0U) {
|
||||||
|
fan_state.cooldown_counter = current_board->fan_enable_cooldown_time * FAN_TICK_FREQ;
|
||||||
|
} else {
|
||||||
|
if (fan_state.cooldown_counter > 0U) {
|
||||||
|
fan_state.cooldown_counter--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update controller
|
||||||
|
if (fan_state.target_rpm == 0U) {
|
||||||
|
fan_state.error_integral = 0.0f;
|
||||||
|
} else {
|
||||||
|
float error = (fan_state.target_rpm - fan_rpm_fast) / ((float) current_board->fan_max_rpm);
|
||||||
|
fan_state.error_integral += FAN_I * error;
|
||||||
|
}
|
||||||
|
fan_state.error_integral = CLAMP(fan_state.error_integral, 0U, current_board->fan_max_pwm);
|
||||||
|
fan_state.power = fan_state.error_integral;
|
||||||
|
|
||||||
|
// Set PWM and enable line
|
||||||
|
pwm_set(TIM3, 3, fan_state.power);
|
||||||
|
current_board->set_fan_enabled(!fan_stalled && ((fan_state.target_rpm > 0U) || (fan_state.cooldown_counter > 0U)));
|
||||||
|
}
|
||||||
|
}
|
||||||
20
panda_tici/board/drivers/fan_declarations.h
Normal file
20
panda_tici/board/drivers/fan_declarations.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct fan_state_t {
|
||||||
|
uint16_t tach_counter;
|
||||||
|
uint16_t rpm;
|
||||||
|
uint16_t target_rpm;
|
||||||
|
uint8_t power;
|
||||||
|
float error_integral;
|
||||||
|
uint8_t stall_counter;
|
||||||
|
uint8_t stall_threshold;
|
||||||
|
uint8_t total_stall_count;
|
||||||
|
uint8_t cooldown_counter;
|
||||||
|
};
|
||||||
|
extern struct fan_state_t fan_state;
|
||||||
|
|
||||||
|
void fan_set_power(uint8_t percentage);
|
||||||
|
void llfan_init(void);
|
||||||
|
void fan_init(void);
|
||||||
|
// Call this at FAN_TICK_FREQ
|
||||||
|
void fan_tick(void);
|
||||||
274
panda_tici/board/drivers/fdcan.h
Normal file
274
panda_tici/board/drivers/fdcan.h
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
#include "fdcan_declarations.h"
|
||||||
|
|
||||||
|
FDCAN_GlobalTypeDef *cans[CANS_ARRAY_SIZE] = {FDCAN1, FDCAN2, FDCAN3};
|
||||||
|
|
||||||
|
static bool can_set_speed(uint8_t can_number) {
|
||||||
|
bool ret = true;
|
||||||
|
FDCAN_GlobalTypeDef *FDCANx = CANIF_FROM_CAN_NUM(can_number);
|
||||||
|
uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number);
|
||||||
|
|
||||||
|
ret &= llcan_set_speed(
|
||||||
|
FDCANx,
|
||||||
|
bus_config[bus_number].can_speed,
|
||||||
|
bus_config[bus_number].can_data_speed,
|
||||||
|
bus_config[bus_number].canfd_non_iso,
|
||||||
|
can_loopback,
|
||||||
|
(unsigned int)(can_silent) & (1U << can_number)
|
||||||
|
);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void can_clear_send(FDCAN_GlobalTypeDef *FDCANx, uint8_t can_number) {
|
||||||
|
static uint32_t last_reset = 0U;
|
||||||
|
uint32_t time = microsecond_timer_get();
|
||||||
|
|
||||||
|
// Resetting CAN core is a slow blocking operation, limit frequency
|
||||||
|
if (get_ts_elapsed(time, last_reset) > 100000U) { // 10 Hz
|
||||||
|
can_health[can_number].can_core_reset_cnt += 1U;
|
||||||
|
can_health[can_number].total_tx_lost_cnt += (FDCAN_TX_FIFO_EL_CNT - (FDCANx->TXFQS & FDCAN_TXFQS_TFFL)); // TX FIFO msgs will be lost after reset
|
||||||
|
llcan_clear_send(FDCANx);
|
||||||
|
last_reset = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) {
|
||||||
|
uint8_t can_irq_number[3][2] = {
|
||||||
|
{ FDCAN1_IT0_IRQn, FDCAN1_IT1_IRQn },
|
||||||
|
{ FDCAN2_IT0_IRQn, FDCAN2_IT1_IRQn },
|
||||||
|
{ FDCAN3_IT0_IRQn, FDCAN3_IT1_IRQn },
|
||||||
|
};
|
||||||
|
|
||||||
|
FDCAN_GlobalTypeDef *FDCANx = CANIF_FROM_CAN_NUM(can_number);
|
||||||
|
uint32_t psr_reg = FDCANx->PSR;
|
||||||
|
uint32_t ecr_reg = FDCANx->ECR;
|
||||||
|
|
||||||
|
can_health[can_number].bus_off = ((psr_reg & FDCAN_PSR_BO) >> FDCAN_PSR_BO_Pos);
|
||||||
|
can_health[can_number].bus_off_cnt += can_health[can_number].bus_off;
|
||||||
|
can_health[can_number].error_warning = ((psr_reg & FDCAN_PSR_EW) >> FDCAN_PSR_EW_Pos);
|
||||||
|
can_health[can_number].error_passive = ((psr_reg & FDCAN_PSR_EP) >> FDCAN_PSR_EP_Pos);
|
||||||
|
|
||||||
|
can_health[can_number].last_error = ((psr_reg & FDCAN_PSR_LEC) >> FDCAN_PSR_LEC_Pos);
|
||||||
|
if ((can_health[can_number].last_error != 0U) && (can_health[can_number].last_error != 7U)) {
|
||||||
|
can_health[can_number].last_stored_error = can_health[can_number].last_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
can_health[can_number].last_data_error = ((psr_reg & FDCAN_PSR_DLEC) >> FDCAN_PSR_DLEC_Pos);
|
||||||
|
if ((can_health[can_number].last_data_error != 0U) && (can_health[can_number].last_data_error != 7U)) {
|
||||||
|
can_health[can_number].last_data_stored_error = can_health[can_number].last_data_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
can_health[can_number].receive_error_cnt = ((ecr_reg & FDCAN_ECR_REC) >> FDCAN_ECR_REC_Pos);
|
||||||
|
can_health[can_number].transmit_error_cnt = ((ecr_reg & FDCAN_ECR_TEC) >> FDCAN_ECR_TEC_Pos);
|
||||||
|
|
||||||
|
can_health[can_number].irq0_call_rate = interrupts[can_irq_number[can_number][0]].call_rate;
|
||||||
|
can_health[can_number].irq1_call_rate = interrupts[can_irq_number[can_number][1]].call_rate;
|
||||||
|
|
||||||
|
|
||||||
|
if (ir_reg != 0U) {
|
||||||
|
// Clear error interrupts
|
||||||
|
FDCANx->IR |= (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L);
|
||||||
|
can_health[can_number].total_error_cnt += 1U;
|
||||||
|
// Check for RX FIFO overflow
|
||||||
|
if ((ir_reg & (FDCAN_IR_RF0L)) != 0U) {
|
||||||
|
can_health[can_number].total_rx_lost_cnt += 1U;
|
||||||
|
}
|
||||||
|
// Cases:
|
||||||
|
// 1. while multiplexing between buses 1 and 3 we are getting ACK errors that overwhelm CAN core, by resetting it recovers faster
|
||||||
|
// 2. H7 gets stuck in bus off recovery state indefinitely
|
||||||
|
if ((((can_health[can_number].last_error == CAN_ACK_ERROR) || (can_health[can_number].last_data_error == CAN_ACK_ERROR)) && (can_health[can_number].transmit_error_cnt > 127U)) ||
|
||||||
|
((ir_reg & FDCAN_IR_BO) != 0U)) {
|
||||||
|
can_clear_send(FDCANx, can_number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************** CAN *****************************
|
||||||
|
// FDFDCANx_IT1 IRQ Handler (TX)
|
||||||
|
void process_can(uint8_t can_number) {
|
||||||
|
if (can_number != 0xffU) {
|
||||||
|
ENTER_CRITICAL();
|
||||||
|
|
||||||
|
FDCAN_GlobalTypeDef *FDCANx = CANIF_FROM_CAN_NUM(can_number);
|
||||||
|
uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number);
|
||||||
|
|
||||||
|
FDCANx->IR |= FDCAN_IR_TFE; // Clear Tx FIFO Empty flag
|
||||||
|
|
||||||
|
if ((FDCANx->TXFQS & FDCAN_TXFQS_TFQF) == 0U) {
|
||||||
|
CANPacket_t to_send;
|
||||||
|
if (can_pop(can_queues[bus_number], &to_send)) {
|
||||||
|
if (can_check_checksum(&to_send)) {
|
||||||
|
can_health[can_number].total_tx_cnt += 1U;
|
||||||
|
|
||||||
|
uint32_t TxFIFOSA = FDCAN_START_ADDRESS + (can_number * FDCAN_OFFSET) + (FDCAN_RX_FIFO_0_EL_CNT * FDCAN_RX_FIFO_0_EL_SIZE);
|
||||||
|
// get the index of the next TX FIFO element (0 to FDCAN_TX_FIFO_EL_CNT - 1)
|
||||||
|
uint32_t tx_index = (FDCANx->TXFQS >> FDCAN_TXFQS_TFQPI_Pos) & 0x1FU;
|
||||||
|
// only send if we have received a packet
|
||||||
|
canfd_fifo *fifo;
|
||||||
|
fifo = (canfd_fifo *)(TxFIFOSA + (tx_index * FDCAN_TX_FIFO_EL_SIZE));
|
||||||
|
|
||||||
|
fifo->header[0] = (to_send.extended << 30) | ((to_send.extended != 0U) ? (to_send.addr) : (to_send.addr << 18));
|
||||||
|
|
||||||
|
// If canfd_auto is set, outgoing packets will be automatically sent as CAN-FD if an incoming CAN-FD packet was seen
|
||||||
|
bool fd = bus_config[can_number].canfd_auto ? bus_config[can_number].canfd_enabled : (bool)(to_send.fd > 0U);
|
||||||
|
uint32_t canfd_enabled_header = fd ? (1UL << 21) : 0UL;
|
||||||
|
|
||||||
|
uint32_t brs_enabled_header = bus_config[can_number].brs_enabled ? (1UL << 20) : 0UL;
|
||||||
|
fifo->header[1] = (to_send.data_len_code << 16) | canfd_enabled_header | brs_enabled_header;
|
||||||
|
|
||||||
|
uint8_t data_len_w = (dlc_to_len[to_send.data_len_code] / 4U);
|
||||||
|
data_len_w += ((dlc_to_len[to_send.data_len_code] % 4U) > 0U) ? 1U : 0U;
|
||||||
|
for (unsigned int i = 0; i < data_len_w; i++) {
|
||||||
|
BYTE_ARRAY_TO_WORD(fifo->data_word[i], &to_send.data[i*4U]);
|
||||||
|
}
|
||||||
|
|
||||||
|
FDCANx->TXBAR = (1UL << tx_index);
|
||||||
|
|
||||||
|
// Send back to USB
|
||||||
|
CANPacket_t to_push;
|
||||||
|
|
||||||
|
to_push.fd = fd;
|
||||||
|
to_push.returned = 1U;
|
||||||
|
to_push.rejected = 0U;
|
||||||
|
to_push.extended = to_send.extended;
|
||||||
|
to_push.addr = to_send.addr;
|
||||||
|
to_push.bus = bus_number;
|
||||||
|
to_push.data_len_code = to_send.data_len_code;
|
||||||
|
(void)memcpy(to_push.data, to_send.data, dlc_to_len[to_push.data_len_code]);
|
||||||
|
can_set_checksum(&to_push);
|
||||||
|
|
||||||
|
rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U;
|
||||||
|
} else {
|
||||||
|
can_health[can_number].total_tx_checksum_error_cnt += 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_can_tx_slots_available();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXIT_CRITICAL();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FDFDCANx_IT0 IRQ Handler (RX and errors)
|
||||||
|
// blink blue when we are receiving CAN messages
|
||||||
|
void can_rx(uint8_t can_number) {
|
||||||
|
FDCAN_GlobalTypeDef *FDCANx = CANIF_FROM_CAN_NUM(can_number);
|
||||||
|
uint8_t bus_number = BUS_NUM_FROM_CAN_NUM(can_number);
|
||||||
|
|
||||||
|
uint32_t ir_reg = FDCANx->IR;
|
||||||
|
|
||||||
|
// Clear all new messages from Rx FIFO 0
|
||||||
|
FDCANx->IR |= FDCAN_IR_RF0N;
|
||||||
|
while((FDCANx->RXF0S & FDCAN_RXF0S_F0FL) != 0U) {
|
||||||
|
can_health[can_number].total_rx_cnt += 1U;
|
||||||
|
|
||||||
|
// can is live
|
||||||
|
pending_can_live = 1;
|
||||||
|
|
||||||
|
// get the index of the next RX FIFO element (0 to FDCAN_RX_FIFO_0_EL_CNT - 1)
|
||||||
|
uint32_t rx_fifo_idx = (uint8_t)((FDCANx->RXF0S >> FDCAN_RXF0S_F0GI_Pos) & 0x3FU);
|
||||||
|
|
||||||
|
// Recommended to offset get index by at least +1 if RX FIFO is in overwrite mode and full (datasheet)
|
||||||
|
if((FDCANx->RXF0S & FDCAN_RXF0S_F0F) == FDCAN_RXF0S_F0F) {
|
||||||
|
rx_fifo_idx = ((rx_fifo_idx + 1U) >= FDCAN_RX_FIFO_0_EL_CNT) ? 0U : (rx_fifo_idx + 1U);
|
||||||
|
can_health[can_number].total_rx_lost_cnt += 1U; // At least one message was lost
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t RxFIFO0SA = FDCAN_START_ADDRESS + (can_number * FDCAN_OFFSET);
|
||||||
|
CANPacket_t to_push;
|
||||||
|
canfd_fifo *fifo;
|
||||||
|
|
||||||
|
// getting address
|
||||||
|
fifo = (canfd_fifo *)(RxFIFO0SA + (rx_fifo_idx * FDCAN_RX_FIFO_0_EL_SIZE));
|
||||||
|
|
||||||
|
bool canfd_frame = ((fifo->header[1] >> 21) & 0x1U);
|
||||||
|
bool brs_frame = ((fifo->header[1] >> 20) & 0x1U);
|
||||||
|
|
||||||
|
to_push.fd = canfd_frame;
|
||||||
|
to_push.returned = 0U;
|
||||||
|
to_push.rejected = 0U;
|
||||||
|
to_push.extended = (fifo->header[0] >> 30) & 0x1U;
|
||||||
|
to_push.addr = ((to_push.extended != 0U) ? (fifo->header[0] & 0x1FFFFFFFU) : ((fifo->header[0] >> 18) & 0x7FFU));
|
||||||
|
to_push.bus = bus_number;
|
||||||
|
to_push.data_len_code = ((fifo->header[1] >> 16) & 0xFU);
|
||||||
|
|
||||||
|
uint8_t data_len_w = (dlc_to_len[to_push.data_len_code] / 4U);
|
||||||
|
data_len_w += ((dlc_to_len[to_push.data_len_code] % 4U) > 0U) ? 1U : 0U;
|
||||||
|
for (unsigned int i = 0; i < data_len_w; i++) {
|
||||||
|
WORD_TO_BYTE_ARRAY(&to_push.data[i*4U], fifo->data_word[i]);
|
||||||
|
}
|
||||||
|
can_set_checksum(&to_push);
|
||||||
|
|
||||||
|
// forwarding (panda only)
|
||||||
|
int bus_fwd_num = safety_fwd_hook(bus_number, to_push.addr);
|
||||||
|
if (bus_fwd_num < 0) {
|
||||||
|
bus_fwd_num = bus_config[can_number].forwarding_bus;
|
||||||
|
}
|
||||||
|
if (bus_fwd_num != -1) {
|
||||||
|
CANPacket_t to_send;
|
||||||
|
|
||||||
|
to_send.fd = to_push.fd;
|
||||||
|
to_send.returned = 0U;
|
||||||
|
to_send.rejected = 0U;
|
||||||
|
to_send.extended = to_push.extended;
|
||||||
|
to_send.addr = to_push.addr;
|
||||||
|
to_send.bus = to_push.bus;
|
||||||
|
to_send.data_len_code = to_push.data_len_code;
|
||||||
|
(void)memcpy(to_send.data, to_push.data, dlc_to_len[to_push.data_len_code]);
|
||||||
|
can_set_checksum(&to_send);
|
||||||
|
|
||||||
|
can_send(&to_send, bus_fwd_num, true);
|
||||||
|
can_health[can_number].total_fwd_cnt += 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
safety_rx_invalid += safety_rx_hook(&to_push) ? 0U : 1U;
|
||||||
|
ignition_can_hook(&to_push);
|
||||||
|
|
||||||
|
led_set(LED_BLUE, true);
|
||||||
|
rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U;
|
||||||
|
|
||||||
|
// Enable CAN FD and BRS if CAN FD message was received
|
||||||
|
if (!(bus_config[can_number].canfd_enabled) && (canfd_frame)) {
|
||||||
|
bus_config[can_number].canfd_enabled = true;
|
||||||
|
}
|
||||||
|
if (!(bus_config[can_number].brs_enabled) && (brs_frame)) {
|
||||||
|
bus_config[can_number].brs_enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update read index
|
||||||
|
FDCANx->RXF0A = rx_fifo_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
if ((ir_reg & (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L)) != 0U) {
|
||||||
|
update_can_health_pkt(can_number, ir_reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FDCAN1_IT0_IRQ_Handler(void) { can_rx(0); }
|
||||||
|
static void FDCAN1_IT1_IRQ_Handler(void) { process_can(0); }
|
||||||
|
|
||||||
|
static void FDCAN2_IT0_IRQ_Handler(void) { can_rx(1); }
|
||||||
|
static void FDCAN2_IT1_IRQ_Handler(void) { process_can(1); }
|
||||||
|
|
||||||
|
static void FDCAN3_IT0_IRQ_Handler(void) { can_rx(2); }
|
||||||
|
static void FDCAN3_IT1_IRQ_Handler(void) { process_can(2); }
|
||||||
|
|
||||||
|
bool can_init(uint8_t can_number) {
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
REGISTER_INTERRUPT(FDCAN1_IT0_IRQn, FDCAN1_IT0_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_1)
|
||||||
|
REGISTER_INTERRUPT(FDCAN1_IT1_IRQn, FDCAN1_IT1_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_1)
|
||||||
|
REGISTER_INTERRUPT(FDCAN2_IT0_IRQn, FDCAN2_IT0_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_2)
|
||||||
|
REGISTER_INTERRUPT(FDCAN2_IT1_IRQn, FDCAN2_IT1_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_2)
|
||||||
|
REGISTER_INTERRUPT(FDCAN3_IT0_IRQn, FDCAN3_IT0_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_3)
|
||||||
|
REGISTER_INTERRUPT(FDCAN3_IT1_IRQn, FDCAN3_IT1_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_3)
|
||||||
|
|
||||||
|
if (can_number != 0xffU) {
|
||||||
|
FDCAN_GlobalTypeDef *FDCANx = CANIF_FROM_CAN_NUM(can_number);
|
||||||
|
ret &= can_set_speed(can_number);
|
||||||
|
ret &= llcan_init(FDCANx);
|
||||||
|
// in case there are queued up messages
|
||||||
|
process_can(can_number);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
28
panda_tici/board/drivers/fdcan_declarations.h
Normal file
28
panda_tici/board/drivers/fdcan_declarations.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// IRQs: FDCAN1_IT0, FDCAN1_IT1
|
||||||
|
// FDCAN2_IT0, FDCAN2_IT1
|
||||||
|
// FDCAN3_IT0, FDCAN3_IT1
|
||||||
|
|
||||||
|
#define CANFD
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
volatile uint32_t header[2];
|
||||||
|
volatile uint32_t data_word[CANPACKET_DATA_SIZE_MAX/4U];
|
||||||
|
} canfd_fifo;
|
||||||
|
|
||||||
|
#define CANS_ARRAY_SIZE 3
|
||||||
|
extern FDCAN_GlobalTypeDef *cans[CANS_ARRAY_SIZE];
|
||||||
|
|
||||||
|
#define CAN_ACK_ERROR 3U
|
||||||
|
|
||||||
|
void can_clear_send(FDCAN_GlobalTypeDef *FDCANx, uint8_t can_number);
|
||||||
|
void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg);
|
||||||
|
|
||||||
|
// ***************************** CAN *****************************
|
||||||
|
// FDFDCANx_IT1 IRQ Handler (TX)
|
||||||
|
void process_can(uint8_t can_number);
|
||||||
|
// FDFDCANx_IT0 IRQ Handler (RX and errors)
|
||||||
|
// blink blue when we are receiving CAN messages
|
||||||
|
void can_rx(uint8_t can_number);
|
||||||
|
bool can_init(uint8_t can_number);
|
||||||
94
panda_tici/board/drivers/gpio.h
Normal file
94
panda_tici/board/drivers/gpio.h
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
#define MODE_INPUT 0
|
||||||
|
#define MODE_OUTPUT 1
|
||||||
|
#define MODE_ALTERNATE 2
|
||||||
|
#define MODE_ANALOG 3
|
||||||
|
|
||||||
|
#define PULL_NONE 0
|
||||||
|
#define PULL_UP 1
|
||||||
|
#define PULL_DOWN 2
|
||||||
|
|
||||||
|
#define OUTPUT_TYPE_PUSH_PULL 0U
|
||||||
|
#define OUTPUT_TYPE_OPEN_DRAIN 1U
|
||||||
|
|
||||||
|
void set_gpio_mode(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int mode) {
|
||||||
|
ENTER_CRITICAL();
|
||||||
|
uint32_t tmp = GPIO->MODER;
|
||||||
|
tmp &= ~(3U << (pin * 2U));
|
||||||
|
tmp |= (mode << (pin * 2U));
|
||||||
|
register_set(&(GPIO->MODER), tmp, 0xFFFFFFFFU);
|
||||||
|
EXIT_CRITICAL();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_gpio_output(GPIO_TypeDef *GPIO, unsigned int pin, bool enabled) {
|
||||||
|
ENTER_CRITICAL();
|
||||||
|
if (enabled) {
|
||||||
|
register_set_bits(&(GPIO->ODR), (1UL << pin));
|
||||||
|
} else {
|
||||||
|
register_clear_bits(&(GPIO->ODR), (1UL << pin));
|
||||||
|
}
|
||||||
|
set_gpio_mode(GPIO, pin, MODE_OUTPUT);
|
||||||
|
EXIT_CRITICAL();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_gpio_output_type(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int output_type){
|
||||||
|
ENTER_CRITICAL();
|
||||||
|
if(output_type == OUTPUT_TYPE_OPEN_DRAIN) {
|
||||||
|
register_set_bits(&(GPIO->OTYPER), (1UL << pin));
|
||||||
|
} else {
|
||||||
|
register_clear_bits(&(GPIO->OTYPER), (1U << pin));
|
||||||
|
}
|
||||||
|
EXIT_CRITICAL();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_gpio_alternate(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int mode) {
|
||||||
|
ENTER_CRITICAL();
|
||||||
|
uint32_t tmp = GPIO->AFR[pin >> 3U];
|
||||||
|
tmp &= ~(0xFU << ((pin & 7U) * 4U));
|
||||||
|
tmp |= mode << ((pin & 7U) * 4U);
|
||||||
|
register_set(&(GPIO->AFR[pin >> 3]), tmp, 0xFFFFFFFFU);
|
||||||
|
set_gpio_mode(GPIO, pin, MODE_ALTERNATE);
|
||||||
|
EXIT_CRITICAL();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_gpio_pullup(GPIO_TypeDef *GPIO, unsigned int pin, unsigned int mode) {
|
||||||
|
ENTER_CRITICAL();
|
||||||
|
uint32_t tmp = GPIO->PUPDR;
|
||||||
|
tmp &= ~(3U << (pin * 2U));
|
||||||
|
tmp |= (mode << (pin * 2U));
|
||||||
|
register_set(&(GPIO->PUPDR), tmp, 0xFFFFFFFFU);
|
||||||
|
EXIT_CRITICAL();
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_gpio_input(const GPIO_TypeDef *GPIO, unsigned int pin) {
|
||||||
|
return (GPIO->IDR & (1UL << pin)) == (1UL << pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PANDA_JUNGLE
|
||||||
|
typedef struct {
|
||||||
|
GPIO_TypeDef * const bank;
|
||||||
|
uint8_t pin;
|
||||||
|
} gpio_t;
|
||||||
|
|
||||||
|
void gpio_set_all_output(gpio_t *pins, uint8_t num_pins, bool enabled) {
|
||||||
|
for (uint8_t i = 0; i < num_pins; i++) {
|
||||||
|
set_gpio_output(pins[i].bank, pins[i].pin, enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void gpio_set_bitmask(gpio_t *pins, uint8_t num_pins, uint32_t bitmask) {
|
||||||
|
for (uint8_t i = 0; i < num_pins; i++) {
|
||||||
|
set_gpio_output(pins[i].bank, pins[i].pin, (bitmask >> i) & 1U);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Detection with internal pullup
|
||||||
|
#define PULL_EFFECTIVE_DELAY 4096
|
||||||
|
bool detect_with_pull(GPIO_TypeDef *GPIO, int pin, int mode) {
|
||||||
|
set_gpio_mode(GPIO, pin, MODE_INPUT);
|
||||||
|
set_gpio_pullup(GPIO, pin, mode);
|
||||||
|
for (volatile int i=0; i<PULL_EFFECTIVE_DELAY; i++);
|
||||||
|
bool ret = get_gpio_input(GPIO, pin);
|
||||||
|
set_gpio_pullup(GPIO, pin, PULL_NONE);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
108
panda_tici/board/drivers/harness.h
Normal file
108
panda_tici/board/drivers/harness.h
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
#include "harness_declarations.h"
|
||||||
|
|
||||||
|
struct harness_t harness;
|
||||||
|
|
||||||
|
// The ignition relay is only used for testing purposes
|
||||||
|
void set_intercept_relay(bool intercept, bool ignition_relay) {
|
||||||
|
if (current_board->harness_config->has_harness) {
|
||||||
|
bool drive_relay = intercept;
|
||||||
|
if (harness.status == HARNESS_STATUS_NC) {
|
||||||
|
// no harness, no relay to drive
|
||||||
|
drive_relay = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drive_relay || ignition_relay) {
|
||||||
|
harness.relay_driven = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait until we're not reading the analog voltages anymore
|
||||||
|
while (harness.sbu_adc_lock) {}
|
||||||
|
|
||||||
|
if (harness.status == HARNESS_STATUS_NORMAL) {
|
||||||
|
set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !ignition_relay);
|
||||||
|
set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, !drive_relay);
|
||||||
|
} else {
|
||||||
|
set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, !drive_relay);
|
||||||
|
set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, !ignition_relay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(drive_relay || ignition_relay)) {
|
||||||
|
harness.relay_driven = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool harness_check_ignition(void) {
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
// wait until we're not reading the analog voltages anymore
|
||||||
|
while (harness.sbu_adc_lock) {}
|
||||||
|
|
||||||
|
switch(harness.status){
|
||||||
|
case HARNESS_STATUS_NORMAL:
|
||||||
|
ret = !get_gpio_input(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1);
|
||||||
|
break;
|
||||||
|
case HARNESS_STATUS_FLIPPED:
|
||||||
|
ret = !get_gpio_input(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t harness_detect_orientation(void) {
|
||||||
|
uint8_t ret = harness.status;
|
||||||
|
|
||||||
|
#ifndef BOOTSTUB
|
||||||
|
// We can't detect orientation if the relay is being driven
|
||||||
|
if (!harness.relay_driven && current_board->harness_config->has_harness) {
|
||||||
|
harness.sbu_adc_lock = true;
|
||||||
|
set_gpio_mode(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1, MODE_ANALOG);
|
||||||
|
set_gpio_mode(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2, MODE_ANALOG);
|
||||||
|
|
||||||
|
harness.sbu1_voltage_mV = adc_get_mV(current_board->harness_config->adc_channel_SBU1);
|
||||||
|
harness.sbu2_voltage_mV = adc_get_mV(current_board->harness_config->adc_channel_SBU2);
|
||||||
|
uint16_t detection_threshold = current_board->avdd_mV / 2U;
|
||||||
|
|
||||||
|
// Detect connection and orientation
|
||||||
|
if((harness.sbu1_voltage_mV < detection_threshold) || (harness.sbu2_voltage_mV < detection_threshold)){
|
||||||
|
if (harness.sbu1_voltage_mV < harness.sbu2_voltage_mV) {
|
||||||
|
// orientation flipped (PANDA_SBU1->HARNESS_SBU1(relay), PANDA_SBU2->HARNESS_SBU2(ign))
|
||||||
|
ret = HARNESS_STATUS_FLIPPED;
|
||||||
|
} else {
|
||||||
|
// orientation normal (PANDA_SBU2->HARNESS_SBU1(relay), PANDA_SBU1->HARNESS_SBU2(ign))
|
||||||
|
// (SBU1->SBU2 is the normal orientation connection per USB-C cable spec)
|
||||||
|
ret = HARNESS_STATUS_NORMAL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret = HARNESS_STATUS_NC;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pins are not 5V tolerant in ADC mode
|
||||||
|
set_gpio_mode(current_board->harness_config->GPIO_SBU1, current_board->harness_config->pin_SBU1, MODE_INPUT);
|
||||||
|
set_gpio_mode(current_board->harness_config->GPIO_SBU2, current_board->harness_config->pin_SBU2, MODE_INPUT);
|
||||||
|
harness.sbu_adc_lock = false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void harness_tick(void) {
|
||||||
|
harness.status = harness_detect_orientation();
|
||||||
|
}
|
||||||
|
|
||||||
|
void harness_init(void) {
|
||||||
|
// init OBD_SBUx_RELAY
|
||||||
|
set_gpio_output_type(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, OUTPUT_TYPE_OPEN_DRAIN);
|
||||||
|
set_gpio_output_type(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, OUTPUT_TYPE_OPEN_DRAIN);
|
||||||
|
set_gpio_output(current_board->harness_config->GPIO_relay_SBU1, current_board->harness_config->pin_relay_SBU1, 1);
|
||||||
|
set_gpio_output(current_board->harness_config->GPIO_relay_SBU2, current_board->harness_config->pin_relay_SBU2, 1);
|
||||||
|
|
||||||
|
// detect initial orientation
|
||||||
|
harness.status = harness_detect_orientation();
|
||||||
|
|
||||||
|
// keep buses connected by default
|
||||||
|
set_intercept_relay(false, false);
|
||||||
|
}
|
||||||
34
panda_tici/board/drivers/harness_declarations.h
Normal file
34
panda_tici/board/drivers/harness_declarations.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define HARNESS_STATUS_NC 0U
|
||||||
|
#define HARNESS_STATUS_NORMAL 1U
|
||||||
|
#define HARNESS_STATUS_FLIPPED 2U
|
||||||
|
|
||||||
|
struct harness_t {
|
||||||
|
uint8_t status;
|
||||||
|
uint16_t sbu1_voltage_mV;
|
||||||
|
uint16_t sbu2_voltage_mV;
|
||||||
|
bool relay_driven;
|
||||||
|
bool sbu_adc_lock;
|
||||||
|
};
|
||||||
|
extern struct harness_t harness;
|
||||||
|
|
||||||
|
struct harness_configuration {
|
||||||
|
const bool has_harness;
|
||||||
|
GPIO_TypeDef * const GPIO_SBU1;
|
||||||
|
GPIO_TypeDef * const GPIO_SBU2;
|
||||||
|
GPIO_TypeDef * const GPIO_relay_SBU1;
|
||||||
|
GPIO_TypeDef * const GPIO_relay_SBU2;
|
||||||
|
const uint8_t pin_SBU1;
|
||||||
|
const uint8_t pin_SBU2;
|
||||||
|
const uint8_t pin_relay_SBU1;
|
||||||
|
const uint8_t pin_relay_SBU2;
|
||||||
|
const uint8_t adc_channel_SBU1;
|
||||||
|
const uint8_t adc_channel_SBU2;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The ignition relay is only used for testing purposes
|
||||||
|
void set_intercept_relay(bool intercept, bool ignition_relay);
|
||||||
|
bool harness_check_ignition(void);
|
||||||
|
void harness_tick(void);
|
||||||
|
void harness_init(void);
|
||||||
81
panda_tici/board/drivers/interrupts.h
Normal file
81
panda_tici/board/drivers/interrupts.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#include "interrupts_declarations.h"
|
||||||
|
|
||||||
|
void unused_interrupt_handler(void) {
|
||||||
|
// Something is wrong if this handler is called!
|
||||||
|
print("Unused interrupt handler called!\n");
|
||||||
|
fault_occurred(FAULT_UNUSED_INTERRUPT_HANDLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
interrupt interrupts[NUM_INTERRUPTS];
|
||||||
|
|
||||||
|
static bool check_interrupt_rate = false;
|
||||||
|
|
||||||
|
static uint32_t idle_time = 0U;
|
||||||
|
static uint32_t busy_time = 0U;
|
||||||
|
float interrupt_load = 0.0f;
|
||||||
|
|
||||||
|
void handle_interrupt(IRQn_Type irq_type){
|
||||||
|
static uint8_t interrupt_depth = 0U;
|
||||||
|
static uint32_t last_time = 0U;
|
||||||
|
ENTER_CRITICAL();
|
||||||
|
if (interrupt_depth == 0U) {
|
||||||
|
uint32_t time = microsecond_timer_get();
|
||||||
|
idle_time += get_ts_elapsed(time, last_time);
|
||||||
|
last_time = time;
|
||||||
|
}
|
||||||
|
interrupt_depth += 1U;
|
||||||
|
EXIT_CRITICAL();
|
||||||
|
|
||||||
|
interrupts[irq_type].call_counter++;
|
||||||
|
interrupts[irq_type].handler();
|
||||||
|
|
||||||
|
// Check that the interrupts don't fire too often
|
||||||
|
if (check_interrupt_rate && (interrupts[irq_type].call_counter > interrupts[irq_type].max_call_rate)) {
|
||||||
|
fault_occurred(interrupts[irq_type].call_rate_fault);
|
||||||
|
}
|
||||||
|
|
||||||
|
ENTER_CRITICAL();
|
||||||
|
interrupt_depth -= 1U;
|
||||||
|
if (interrupt_depth == 0U) {
|
||||||
|
uint32_t time = microsecond_timer_get();
|
||||||
|
busy_time += get_ts_elapsed(time, last_time);
|
||||||
|
last_time = time;
|
||||||
|
}
|
||||||
|
EXIT_CRITICAL();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every second
|
||||||
|
void interrupt_timer_handler(void) {
|
||||||
|
if (INTERRUPT_TIMER->SR != 0U) {
|
||||||
|
for (uint16_t i = 0U; i < NUM_INTERRUPTS; i++) {
|
||||||
|
// Log IRQ call rate faults
|
||||||
|
if (check_interrupt_rate && (interrupts[i].call_counter > interrupts[i].max_call_rate)) {
|
||||||
|
print("Interrupt 0x"); puth(i); print(" fired too often (0x"); puth(interrupts[i].call_counter); print("/s)!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset interrupt counters
|
||||||
|
interrupts[i].call_rate = interrupts[i].call_counter;
|
||||||
|
interrupts[i].call_counter = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate interrupt load
|
||||||
|
// The bootstub does not have the FPU enabled, so can't do float operations.
|
||||||
|
#if !defined(BOOTSTUB)
|
||||||
|
interrupt_load = ((busy_time + idle_time) > 0U) ? ((float) (((float) busy_time) / (busy_time + idle_time))) : 0.0f;
|
||||||
|
#endif
|
||||||
|
idle_time = 0U;
|
||||||
|
busy_time = 0U;
|
||||||
|
}
|
||||||
|
INTERRUPT_TIMER->SR = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_interrupts(bool check_rate_limit){
|
||||||
|
check_interrupt_rate = check_rate_limit;
|
||||||
|
|
||||||
|
for(uint16_t i=0U; i<NUM_INTERRUPTS; i++){
|
||||||
|
interrupts[i].handler = unused_interrupt_handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init interrupt timer for a 1s interval
|
||||||
|
interrupt_timer_init();
|
||||||
|
}
|
||||||
31
panda_tici/board/drivers/interrupts_declarations.h
Normal file
31
panda_tici/board/drivers/interrupts_declarations.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct interrupt {
|
||||||
|
IRQn_Type irq_type;
|
||||||
|
void (*handler)(void);
|
||||||
|
uint32_t call_counter;
|
||||||
|
uint32_t call_rate;
|
||||||
|
uint32_t max_call_rate; // Call rate is defined as the amount of calls each second
|
||||||
|
uint32_t call_rate_fault;
|
||||||
|
} interrupt;
|
||||||
|
|
||||||
|
void interrupt_timer_init(void);
|
||||||
|
uint32_t microsecond_timer_get(void);
|
||||||
|
void unused_interrupt_handler(void);
|
||||||
|
|
||||||
|
extern interrupt interrupts[NUM_INTERRUPTS];
|
||||||
|
|
||||||
|
#define REGISTER_INTERRUPT(irq_num, func_ptr, call_rate_max, rate_fault) \
|
||||||
|
interrupts[irq_num].irq_type = (irq_num); \
|
||||||
|
interrupts[irq_num].handler = (func_ptr); \
|
||||||
|
interrupts[irq_num].call_counter = 0U; \
|
||||||
|
interrupts[irq_num].call_rate = 0U; \
|
||||||
|
interrupts[irq_num].max_call_rate = (call_rate_max); \
|
||||||
|
interrupts[irq_num].call_rate_fault = (rate_fault);
|
||||||
|
|
||||||
|
extern float interrupt_load;
|
||||||
|
|
||||||
|
void handle_interrupt(IRQn_Type irq_type);
|
||||||
|
// Every second
|
||||||
|
void interrupt_timer_handler(void);
|
||||||
|
void init_interrupts(bool check_rate_limit);
|
||||||
32
panda_tici/board/drivers/led.h
Normal file
32
panda_tici/board/drivers/led.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
#define LED_RED 0U
|
||||||
|
#define LED_GREEN 1U
|
||||||
|
#define LED_BLUE 2U
|
||||||
|
|
||||||
|
#define LED_PWM_POWER 5U
|
||||||
|
|
||||||
|
void led_set(uint8_t color, bool enabled) {
|
||||||
|
if (color < 3U) {
|
||||||
|
if (current_board->led_pwm_channels[color] != 0U) {
|
||||||
|
pwm_set(TIM3, current_board->led_pwm_channels[color], 100U - (enabled ? LED_PWM_POWER : 0U));
|
||||||
|
} else {
|
||||||
|
set_gpio_output(current_board->led_GPIO[color], current_board->led_pin[color], !enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_init(void) {
|
||||||
|
for (uint8_t i = 0U; i<3U; i++){
|
||||||
|
set_gpio_pullup(current_board->led_GPIO[i], current_board->led_pin[i], PULL_NONE);
|
||||||
|
set_gpio_output_type(current_board->led_GPIO[i], current_board->led_pin[i], OUTPUT_TYPE_OPEN_DRAIN);
|
||||||
|
|
||||||
|
if (current_board->led_pwm_channels[i] != 0U) {
|
||||||
|
set_gpio_alternate(current_board->led_GPIO[i], current_board->led_pin[i], GPIO_AF2_TIM3);
|
||||||
|
pwm_init(TIM3, current_board->led_pwm_channels[i]);
|
||||||
|
} else {
|
||||||
|
set_gpio_mode(current_board->led_GPIO[i], current_board->led_pin[i], MODE_OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
led_set(i, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
panda_tici/board/drivers/pwm.h
Normal file
56
panda_tici/board/drivers/pwm.h
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#define PWM_COUNTER_OVERFLOW 2000U // To get ~50kHz
|
||||||
|
|
||||||
|
// TODO: Implement for 32-bit timers
|
||||||
|
|
||||||
|
void pwm_init(TIM_TypeDef *TIM, uint8_t channel){
|
||||||
|
// Enable timer and auto-reload
|
||||||
|
register_set(&(TIM->CR1), TIM_CR1_CEN | TIM_CR1_ARPE, 0x3FU);
|
||||||
|
|
||||||
|
// Set channel as PWM mode 1 and enable output
|
||||||
|
switch(channel){
|
||||||
|
case 1U:
|
||||||
|
register_set_bits(&(TIM->CCMR1), (TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE));
|
||||||
|
register_set_bits(&(TIM->CCER), TIM_CCER_CC1E);
|
||||||
|
break;
|
||||||
|
case 2U:
|
||||||
|
register_set_bits(&(TIM->CCMR1), (TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE));
|
||||||
|
register_set_bits(&(TIM->CCER), TIM_CCER_CC2E);
|
||||||
|
break;
|
||||||
|
case 3U:
|
||||||
|
register_set_bits(&(TIM->CCMR2), (TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE));
|
||||||
|
register_set_bits(&(TIM->CCER), TIM_CCER_CC3E);
|
||||||
|
break;
|
||||||
|
case 4U:
|
||||||
|
register_set_bits(&(TIM->CCMR2), (TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4PE));
|
||||||
|
register_set_bits(&(TIM->CCER), TIM_CCER_CC4E);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set max counter value
|
||||||
|
register_set(&(TIM->ARR), PWM_COUNTER_OVERFLOW, 0xFFFFU);
|
||||||
|
|
||||||
|
// Update registers and clear counter
|
||||||
|
TIM->EGR |= TIM_EGR_UG;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pwm_set(TIM_TypeDef *TIM, uint8_t channel, uint8_t percentage){
|
||||||
|
uint16_t comp_value = (((uint16_t) percentage * PWM_COUNTER_OVERFLOW) / 100U);
|
||||||
|
switch(channel){
|
||||||
|
case 1U:
|
||||||
|
register_set(&(TIM->CCR1), comp_value, 0xFFFFU);
|
||||||
|
break;
|
||||||
|
case 2U:
|
||||||
|
register_set(&(TIM->CCR2), comp_value, 0xFFFFU);
|
||||||
|
break;
|
||||||
|
case 3U:
|
||||||
|
register_set(&(TIM->CCR3), comp_value, 0xFFFFU);
|
||||||
|
break;
|
||||||
|
case 4U:
|
||||||
|
register_set(&(TIM->CCR4), comp_value, 0xFFFFU);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
68
panda_tici/board/drivers/registers.h
Normal file
68
panda_tici/board/drivers/registers.h
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#include "registers_declarations.h"
|
||||||
|
|
||||||
|
static reg register_map[REGISTER_MAP_SIZE];
|
||||||
|
|
||||||
|
// Hash spread in first and second iterations seems to be reasonable.
|
||||||
|
// See: tests/development/register_hashmap_spread.py
|
||||||
|
// Also, check the collision warnings in the debug output, and minimize those.
|
||||||
|
static uint16_t hash_addr(uint32_t input){
|
||||||
|
return (((input >> 16U) ^ ((((input + 1U) & 0xFFFFU) * HASHING_PRIME) & 0xFFFFU)) & REGISTER_MAP_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not put bits in the check mask that get changed by the hardware
|
||||||
|
void register_set(volatile uint32_t *addr, uint32_t val, uint32_t mask){
|
||||||
|
ENTER_CRITICAL()
|
||||||
|
// Set bits in register that are also in the mask
|
||||||
|
(*addr) = ((*addr) & (~mask)) | (val & mask);
|
||||||
|
|
||||||
|
// Add these values to the map
|
||||||
|
uint16_t hash = hash_addr((uint32_t) addr);
|
||||||
|
uint16_t tries = REGISTER_MAP_SIZE;
|
||||||
|
while(CHECK_COLLISION(hash, addr) && (tries > 0U)) { hash = hash_addr((uint32_t) hash); tries--;}
|
||||||
|
if (tries != 0U){
|
||||||
|
register_map[hash].address = addr;
|
||||||
|
register_map[hash].value = (register_map[hash].value & (~mask)) | (val & mask);
|
||||||
|
register_map[hash].check_mask |= mask;
|
||||||
|
} else {
|
||||||
|
#ifdef DEBUG_FAULTS
|
||||||
|
print("Hash collision: address 0x"); puth((uint32_t) addr); print("!\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
EXIT_CRITICAL()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set individual bits. Also add them to the check_mask.
|
||||||
|
// Do not use this to change bits that get reset by the hardware
|
||||||
|
void register_set_bits(volatile uint32_t *addr, uint32_t val) {
|
||||||
|
register_set(addr, val, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear individual bits. Also add them to the check_mask.
|
||||||
|
// Do not use this to clear bits that get set by the hardware
|
||||||
|
void register_clear_bits(volatile uint32_t *addr, uint32_t val) {
|
||||||
|
register_set(addr, (~val), val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// To be called periodically
|
||||||
|
void check_registers(void){
|
||||||
|
for(uint16_t i=0U; i<REGISTER_MAP_SIZE; i++){
|
||||||
|
if((uint32_t) register_map[i].address != 0U){
|
||||||
|
ENTER_CRITICAL()
|
||||||
|
if((*(register_map[i].address) & register_map[i].check_mask) != (register_map[i].value & register_map[i].check_mask)){
|
||||||
|
if(!register_map[i].logged_fault){
|
||||||
|
print("Register 0x"); puth((uint32_t) register_map[i].address); print(" divergent! Map: 0x"); puth(register_map[i].value); print(" Reg: 0x"); puth(*(register_map[i].address)); print("\n");
|
||||||
|
register_map[i].logged_fault = true;
|
||||||
|
}
|
||||||
|
fault_occurred(FAULT_REGISTER_DIVERGENT);
|
||||||
|
}
|
||||||
|
EXIT_CRITICAL()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_registers(void) {
|
||||||
|
for(uint16_t i=0U; i<REGISTER_MAP_SIZE; i++){
|
||||||
|
register_map[i].address = (volatile uint32_t *) 0U;
|
||||||
|
register_map[i].check_mask = 0U;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
panda_tici/board/drivers/registers_declarations.h
Normal file
25
panda_tici/board/drivers/registers_declarations.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct reg {
|
||||||
|
volatile uint32_t *address;
|
||||||
|
uint32_t value;
|
||||||
|
uint32_t check_mask;
|
||||||
|
bool logged_fault;
|
||||||
|
} reg;
|
||||||
|
|
||||||
|
// 10 bit hash with 23 as a prime
|
||||||
|
#define REGISTER_MAP_SIZE 0x3FFU
|
||||||
|
#define HASHING_PRIME 23U
|
||||||
|
#define CHECK_COLLISION(hash, addr) (((uint32_t) register_map[hash].address != 0U) && (register_map[hash].address != (addr)))
|
||||||
|
|
||||||
|
// Do not put bits in the check mask that get changed by the hardware
|
||||||
|
void register_set(volatile uint32_t *addr, uint32_t val, uint32_t mask);
|
||||||
|
// Set individual bits. Also add them to the check_mask.
|
||||||
|
// Do not use this to change bits that get reset by the hardware
|
||||||
|
void register_set_bits(volatile uint32_t *addr, uint32_t val);
|
||||||
|
// Clear individual bits. Also add them to the check_mask.
|
||||||
|
// Do not use this to clear bits that get set by the hardware
|
||||||
|
void register_clear_bits(volatile uint32_t *addr, uint32_t val);
|
||||||
|
// To be called periodically
|
||||||
|
void check_registers(void);
|
||||||
|
void init_registers(void);
|
||||||
21
panda_tici/board/drivers/simple_watchdog.h
Normal file
21
panda_tici/board/drivers/simple_watchdog.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#include "simple_watchdog_declarations.h"
|
||||||
|
|
||||||
|
static simple_watchdog_state_t wd_state;
|
||||||
|
|
||||||
|
void simple_watchdog_kick(void) {
|
||||||
|
uint32_t ts = microsecond_timer_get();
|
||||||
|
|
||||||
|
uint32_t et = get_ts_elapsed(ts, wd_state.last_ts);
|
||||||
|
if (et > wd_state.threshold) {
|
||||||
|
print("WD timeout 0x"); puth(et); print("\n");
|
||||||
|
fault_occurred(wd_state.fault);
|
||||||
|
}
|
||||||
|
|
||||||
|
wd_state.last_ts = ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
void simple_watchdog_init(uint32_t fault, uint32_t threshold) {
|
||||||
|
wd_state.fault = fault;
|
||||||
|
wd_state.threshold = threshold;
|
||||||
|
wd_state.last_ts = microsecond_timer_get();
|
||||||
|
}
|
||||||
10
panda_tici/board/drivers/simple_watchdog_declarations.h
Normal file
10
panda_tici/board/drivers/simple_watchdog_declarations.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct simple_watchdog_state_t {
|
||||||
|
uint32_t fault;
|
||||||
|
uint32_t last_ts;
|
||||||
|
uint32_t threshold;
|
||||||
|
} simple_watchdog_state_t;
|
||||||
|
|
||||||
|
void simple_watchdog_kick(void);
|
||||||
|
void simple_watchdog_init(uint32_t fault, uint32_t threshold);
|
||||||
243
panda_tici/board/drivers/spi.h
Normal file
243
panda_tici/board/drivers/spi.h
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "spi_declarations.h"
|
||||||
|
#include "crc.h"
|
||||||
|
|
||||||
|
#ifdef STM32H7
|
||||||
|
#define SPI_BUF_SIZE 2048U
|
||||||
|
// H7 DMA2 located in D2 domain, so we need to use SRAM1/SRAM2
|
||||||
|
__attribute__((section(".sram12"))) uint8_t spi_buf_rx[SPI_BUF_SIZE];
|
||||||
|
__attribute__((section(".sram12"))) uint8_t spi_buf_tx[SPI_BUF_SIZE];
|
||||||
|
#else
|
||||||
|
#define SPI_BUF_SIZE 1024U
|
||||||
|
uint8_t spi_buf_rx[SPI_BUF_SIZE];
|
||||||
|
uint8_t spi_buf_tx[SPI_BUF_SIZE];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint16_t spi_error_count = 0;
|
||||||
|
|
||||||
|
uint16_t spi_checksum_error_count = 0;
|
||||||
|
|
||||||
|
#if defined(ENABLE_SPI) || defined(BOOTSTUB)
|
||||||
|
static uint8_t spi_state = SPI_STATE_HEADER;
|
||||||
|
static uint16_t spi_data_len_mosi;
|
||||||
|
static bool spi_can_tx_ready = false;
|
||||||
|
static const unsigned char version_text[] = "VERSION";
|
||||||
|
|
||||||
|
static uint16_t spi_version_packet(uint8_t *out) {
|
||||||
|
// this protocol version request is a stable portion of
|
||||||
|
// the panda's SPI protocol. its contents match that of the
|
||||||
|
// panda USB descriptors and are sufficent to list/enumerate
|
||||||
|
// a panda, determine panda type, and bootstub status.
|
||||||
|
|
||||||
|
// the response is:
|
||||||
|
// VERSION + 2 byte data length + data + CRC8
|
||||||
|
|
||||||
|
// echo "VERSION"
|
||||||
|
(void)memcpy(out, version_text, 7);
|
||||||
|
|
||||||
|
// write response
|
||||||
|
uint16_t data_len = 0;
|
||||||
|
uint16_t data_pos = 7U + 2U;
|
||||||
|
|
||||||
|
// write serial
|
||||||
|
(void)memcpy(&out[data_pos], ((uint8_t *)UID_BASE), 12);
|
||||||
|
data_len += 12U;
|
||||||
|
|
||||||
|
// HW type
|
||||||
|
out[data_pos + data_len] = hw_type;
|
||||||
|
data_len += 1U;
|
||||||
|
|
||||||
|
// bootstub
|
||||||
|
out[data_pos + data_len] = USB_PID & 0xFFU;
|
||||||
|
data_len += 1U;
|
||||||
|
|
||||||
|
// SPI protocol version
|
||||||
|
out[data_pos + data_len] = 0x2;
|
||||||
|
data_len += 1U;
|
||||||
|
|
||||||
|
// data length
|
||||||
|
out[7] = data_len & 0xFFU;
|
||||||
|
out[8] = (data_len >> 8) & 0xFFU;
|
||||||
|
|
||||||
|
// CRC8
|
||||||
|
uint16_t resp_len = data_pos + data_len;
|
||||||
|
out[resp_len] = crc_checksum(out, resp_len, 0xD5U);
|
||||||
|
resp_len += 1U;
|
||||||
|
|
||||||
|
return resp_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void spi_init(void) {
|
||||||
|
// platform init
|
||||||
|
llspi_init();
|
||||||
|
|
||||||
|
// Start the first packet!
|
||||||
|
spi_state = SPI_STATE_HEADER;
|
||||||
|
llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool validate_checksum(const uint8_t *data, uint16_t len) {
|
||||||
|
// TODO: can speed this up by casting the bulk to uint32_t and xor-ing the bytes afterwards
|
||||||
|
uint8_t checksum = SPI_CHECKSUM_START;
|
||||||
|
for(uint16_t i = 0U; i < len; i++){
|
||||||
|
checksum ^= data[i];
|
||||||
|
}
|
||||||
|
return checksum == 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
void spi_rx_done(void) {
|
||||||
|
uint16_t response_len = 0U;
|
||||||
|
uint8_t next_rx_state = SPI_STATE_HEADER_NACK;
|
||||||
|
bool checksum_valid = false;
|
||||||
|
static uint8_t spi_endpoint;
|
||||||
|
static uint16_t spi_data_len_miso;
|
||||||
|
|
||||||
|
// parse header
|
||||||
|
spi_endpoint = spi_buf_rx[1];
|
||||||
|
spi_data_len_mosi = (spi_buf_rx[3] << 8) | spi_buf_rx[2];
|
||||||
|
spi_data_len_miso = (spi_buf_rx[5] << 8) | spi_buf_rx[4];
|
||||||
|
|
||||||
|
if (memcmp(spi_buf_rx, version_text, 7) == 0) {
|
||||||
|
response_len = spi_version_packet(spi_buf_tx);
|
||||||
|
next_rx_state = SPI_STATE_HEADER_NACK;;
|
||||||
|
} else if (spi_state == SPI_STATE_HEADER) {
|
||||||
|
checksum_valid = validate_checksum(spi_buf_rx, SPI_HEADER_SIZE);
|
||||||
|
if ((spi_buf_rx[0] == SPI_SYNC_BYTE) && checksum_valid) {
|
||||||
|
// response: ACK and start receiving data portion
|
||||||
|
spi_buf_tx[0] = SPI_HACK;
|
||||||
|
next_rx_state = SPI_STATE_HEADER_ACK;
|
||||||
|
response_len = 1U;
|
||||||
|
} else {
|
||||||
|
// response: NACK and reset state machine
|
||||||
|
#ifdef DEBUG_SPI
|
||||||
|
print("- incorrect header sync or checksum "); hexdump(spi_buf_rx, SPI_HEADER_SIZE);
|
||||||
|
#endif
|
||||||
|
spi_buf_tx[0] = SPI_NACK;
|
||||||
|
next_rx_state = SPI_STATE_HEADER_NACK;
|
||||||
|
response_len = 1U;
|
||||||
|
}
|
||||||
|
} else if (spi_state == SPI_STATE_DATA_RX) {
|
||||||
|
// We got everything! Based on the endpoint specified, call the appropriate handler
|
||||||
|
bool response_ack = false;
|
||||||
|
checksum_valid = validate_checksum(&(spi_buf_rx[SPI_HEADER_SIZE]), spi_data_len_mosi + 1U);
|
||||||
|
if (checksum_valid) {
|
||||||
|
if (spi_endpoint == 0U) {
|
||||||
|
if (spi_data_len_mosi >= sizeof(ControlPacket_t)) {
|
||||||
|
ControlPacket_t ctrl = {0};
|
||||||
|
(void)memcpy((uint8_t*)&ctrl, &spi_buf_rx[SPI_HEADER_SIZE], sizeof(ControlPacket_t));
|
||||||
|
response_len = comms_control_handler(&ctrl, &spi_buf_tx[3]);
|
||||||
|
response_ack = true;
|
||||||
|
} else {
|
||||||
|
print("SPI: insufficient data for control handler\n");
|
||||||
|
}
|
||||||
|
} else if ((spi_endpoint == 1U) || (spi_endpoint == 0x81U)) {
|
||||||
|
if (spi_data_len_mosi == 0U) {
|
||||||
|
response_len = comms_can_read(&(spi_buf_tx[3]), spi_data_len_miso);
|
||||||
|
response_ack = true;
|
||||||
|
} else {
|
||||||
|
print("SPI: did not expect data for can_read\n");
|
||||||
|
}
|
||||||
|
} else if (spi_endpoint == 2U) {
|
||||||
|
comms_endpoint2_write(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi);
|
||||||
|
response_ack = true;
|
||||||
|
} else if (spi_endpoint == 3U) {
|
||||||
|
if (spi_data_len_mosi > 0U) {
|
||||||
|
if (spi_can_tx_ready) {
|
||||||
|
spi_can_tx_ready = false;
|
||||||
|
comms_can_write(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi);
|
||||||
|
response_ack = true;
|
||||||
|
} else {
|
||||||
|
response_ack = false;
|
||||||
|
print("SPI: CAN NACK\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("SPI: did expect data for can_write\n");
|
||||||
|
}
|
||||||
|
} else if (spi_endpoint == 0xABU) {
|
||||||
|
// test endpoint, send max response length
|
||||||
|
response_len = spi_data_len_miso;
|
||||||
|
response_ack = true;
|
||||||
|
} else {
|
||||||
|
print("SPI: unexpected endpoint"); puth(spi_endpoint); print("\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Checksum was incorrect
|
||||||
|
response_ack = false;
|
||||||
|
#ifdef DEBUG_SPI
|
||||||
|
print("- incorrect data checksum ");
|
||||||
|
puth4(spi_data_len_mosi);
|
||||||
|
print("\n");
|
||||||
|
hexdump(spi_buf_rx, SPI_HEADER_SIZE);
|
||||||
|
hexdump(&(spi_buf_rx[SPI_HEADER_SIZE]), MIN(spi_data_len_mosi, 64));
|
||||||
|
print("\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response_ack) {
|
||||||
|
spi_buf_tx[0] = SPI_NACK;
|
||||||
|
next_rx_state = SPI_STATE_HEADER_NACK;
|
||||||
|
response_len = 1U;
|
||||||
|
} else {
|
||||||
|
// Setup response header
|
||||||
|
spi_buf_tx[0] = SPI_DACK;
|
||||||
|
spi_buf_tx[1] = response_len & 0xFFU;
|
||||||
|
spi_buf_tx[2] = (response_len >> 8) & 0xFFU;
|
||||||
|
|
||||||
|
// Add checksum
|
||||||
|
uint8_t checksum = SPI_CHECKSUM_START;
|
||||||
|
for(uint16_t i = 0U; i < (response_len + 3U); i++) {
|
||||||
|
checksum ^= spi_buf_tx[i];
|
||||||
|
}
|
||||||
|
spi_buf_tx[response_len + 3U] = checksum;
|
||||||
|
response_len += 4U;
|
||||||
|
|
||||||
|
next_rx_state = SPI_STATE_DATA_TX;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("SPI: RX unexpected state: "); puth(spi_state); print("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// send out response
|
||||||
|
if (response_len == 0U) {
|
||||||
|
print("SPI: no response\n");
|
||||||
|
spi_buf_tx[0] = SPI_NACK;
|
||||||
|
spi_state = SPI_STATE_HEADER_NACK;
|
||||||
|
response_len = 1U;
|
||||||
|
}
|
||||||
|
llspi_miso_dma(spi_buf_tx, response_len);
|
||||||
|
|
||||||
|
spi_state = next_rx_state;
|
||||||
|
if (!checksum_valid) {
|
||||||
|
spi_error_count += 1U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void spi_tx_done(bool reset) {
|
||||||
|
if ((spi_state == SPI_STATE_HEADER_NACK) || reset) {
|
||||||
|
// Reset state
|
||||||
|
spi_state = SPI_STATE_HEADER;
|
||||||
|
llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE);
|
||||||
|
} else if (spi_state == SPI_STATE_HEADER_ACK) {
|
||||||
|
// ACK was sent, queue up the RX buf for the data + checksum
|
||||||
|
spi_state = SPI_STATE_DATA_RX;
|
||||||
|
llspi_mosi_dma(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi + 1U);
|
||||||
|
} else if (spi_state == SPI_STATE_DATA_TX) {
|
||||||
|
// Reset state
|
||||||
|
spi_state = SPI_STATE_HEADER;
|
||||||
|
llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE);
|
||||||
|
} else {
|
||||||
|
spi_state = SPI_STATE_HEADER;
|
||||||
|
llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE);
|
||||||
|
print("SPI: TX unexpected state: "); puth(spi_state); print("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void can_tx_comms_resume_spi(void) {
|
||||||
|
spi_can_tx_ready = true;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void can_tx_comms_resume_spi(void) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user