Introduction: The Necessity of Custom CAN in AAOS
The automotive industry widely relies on standard protocols like J1939 for communication between Electronic Control Units (ECUs) on the CAN bus. However, for specialized applications, legacy system integration, or unique OEM requirements within Android Automotive OS (AAOS) environments, a proprietary CAN protocol becomes not just an option, but a necessity. This article delves into the intricacies of designing, implementing, and integrating custom CAN bus protocols with AAOS, empowering developers to unlock new levels of vehicle functionality and HMI.
Why Proprietary CAN Beyond J1939?
While J1939 provides a robust framework, its rigidity and overhead can be a limitation. Proprietary CAN protocols offer:
- Legacy System Integration: Connecting AAOS to older vehicle systems that predate widespread J1939 adoption or use vendor-specific CAN messages.
- Specialized Hardware Control: Interfacing with custom sensors, actuators, or internal ECUs that communicate using unique message formats.
- Performance Optimization: Crafting highly optimized, compact messages for high-frequency data, reducing bus load and latency compared to standard protocols.
- Security and IP Protection: Obscuring internal vehicle communications from easy reverse-engineering, protecting proprietary designs.
- Cost Efficiency: Avoiding licensing fees or complex implementations associated with certain standard protocols for niche applications.
Designing Your Proprietary CAN Protocol
1. Message ID Allocation Strategy
CAN IDs are crucial for arbitration and message identification. A well-structured ID scheme is vital:
- Priority-Based: Lower numerical IDs have higher priority. Assign critical messages (e.g., safety, steering) lower IDs.
- Functional Grouping: Dedicate ID ranges to specific functional domains (e.g., 0x100-0x1FF for powertrain, 0x200-0x2FF for infotainment).
- Source/Destination Addressing: Encode sender/receiver information within the ID, similar to J1939’s PGN and SA, but simplified.
Example ID allocation:
// Proprietary CAN ID Allocation Example
// 11-bit CAN IDs
#define CAN_ID_ENGINE_RPM 0x101 // High priority engine data
#define CAN_ID_VEHICLE_SPEED 0x102 // High priority speed data
#define CAN_ID_HVAC_STATUS 0x205 // Medium priority HVAC status
#define CAN_ID_DOOR_LOCK_CMD 0x30A // Medium priority door command
#define CAN_ID_DIAGNOSTIC_DATA 0x7DF // Low priority diagnostic, often 0x7DF for functional requests
2. Data Payload Definition
CAN data fields are 0-8 bytes. Efficiently pack your data using bitfields and scaling.
- Bitfield Packing: Combine multiple small values (booleans, small enums) into a single byte or word.
- Scaling and Offset: Represent physical values (e.g., temperature, voltage) with appropriate scaling factors to fit within smaller integer types.
- Endianness: Define whether multi-byte values are little-endian or big-endian. Consistency is key.
Example Message Structure for CAN_ID_ENGINE_RPM (2 bytes for RPM, 1 byte for engine load):
// Example data structure for CAN_ID_ENGINE_RPM
struct EngineData {
uint16_t rpm; // 0-8000 RPM, scaled by 1 (raw value)
uint8_t engine_load; // 0-100%, scaled by 1 (raw value)
// Padding or other data if needed
} __attribute__((packed));
// To encode:
EngineData data;
data.rpm = 2500;
data.engine_load = 50;
can_frame frame = { .can_id = CAN_ID_ENGINE_RPM, .can_dlc = 3 };
memcpy(frame.data, &data, sizeof(EngineData));
Interfacing CAN with AAOS: The VHAL Bridge
AAOS abstracts vehicle hardware access through the Vehicle Hardware Abstraction Layer (VHAL). To integrate custom CAN protocols, you’ll extend or implement a custom VHAL service.
1. Hardware Interface and Linux CAN Drivers
AAOS typically runs on Linux. Ensure your CAN hardware (e.g., dedicated CAN controller, USB-CAN adapter, PCIe-CAN card) is recognized by the Linux kernel and uses SocketCAN.
Check if SocketCAN is available:
$ ip link show type can
If not present, you might need to load kernel modules or compile with CAN support. To bring up a CAN interface:
$ sudo ip link set can0 type can bitrate 500000
$ sudo ip link set up can0
You can test with candump:
$ candump can0
2. Extending the VHAL for Custom Properties
The VHAL defines standard vehicle properties (`VehicleProperty`). For custom CAN data, you’ll define new proprietary properties. This involves:
- Defining New Property IDs: In `hardware/interfaces/automotive/vehicle/2.0/types.hal` (or your custom HAL version), add new `VehicleProperty` enums. Proprietary IDs usually start from `0x10000000` to avoid conflicts.
// hardware/interfaces/automotive/vehicle/2.0/types.hal (simplified)
enum VehicleProperty : int32 {
// ... standard properties ...
// Custom proprietary properties start from 0x1000
CUSTOM_ENGINE_RPM = 0x1000,
CUSTOM_HVAC_STATUS = 0x1001,
CUSTOM_DOOR_LOCK_STATE = 0x1002,
// ... more custom properties
};
- Implementing the Custom VHAL Service: You’ll typically fork or extend the reference VHAL (`hardware/automotive/vehicle/VHAL_YOUR_DEVICE`).
Inside your VHAL implementation (e.g., `DefaultVehicleHal.cpp`), you’ll manage the CAN communication and map it to VHAL properties.
3. VHAL Implementation Details (C++ Example)
Your VHAL service will:
- Open a SocketCAN socket.
- Create a dedicated thread to read CAN frames.
- Parse incoming custom CAN messages.
- Map parsed data to corresponding VHAL properties and notify `CarService`.
- Handle `SET` requests from AAOS apps by translating them into custom CAN messages and sending them out.
Snippet: Opening SocketCAN and Reading Data
// In your VHAL implementation (e.g., VehicleHal.cpp or a dedicated CAN driver class)
#include
#include
#include
#include
#include
#include
class CustomCanDriver {
public:
CustomCanDriver(VehicleHal* vhal_instance) : mVhal(vhal_instance) {
mCanFd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (mCanFd < 0) {
// Handle error
return;
}
struct ifreq ifr;
strcpy(ifr.ifr_name, "can0"); // Or your specific CAN interface
ioctl(mCanFd, SIOCGIFINDEX, &ifr);
struct sockaddr_can addr;
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(mCanFd, (struct sockaddr*)&addr, sizeof(addr)) = 0) {
close(mCanFd);
}
}
private:
void canReadLoop() {
can_frame frame;
while (!mStopThread) {
ssize_t nbytes = read(mCanFd, &frame, sizeof(can_frame));
if (nbytes = 3) {
uint16_t rpm = (frame.data[1] <sendPropEvent(VehicleProperty::CUSTOM_ENGINE_RPM, rpm);
}
} else if (frame.can_id == CAN_ID_HVAC_STATUS) {
if (frame.can_dlc >= 1) {
uint8_t status = frame.data[0];
mVhal->sendPropEvent(VehicleProperty::CUSTOM_HVAC_STATUS, status);
}
}
// ... handle other custom CAN IDs
}
VehicleHal* mVhal;
int mCanFd;
std::thread mReadThread;
std::atomic mStopThread{false};
};
In your `DefaultVehicleHal::get, set, subscribe` methods, you would add cases for your `CUSTOM_` properties. For `set` operations, you’d construct a `can_frame` and `write` it to `mCanFd`.
Developing an Android Application for Custom CAN Data
Once your VHAL is integrated, Android applications can access these custom properties using the `CarPropertyManager`.
1. Requesting Permissions
Your app will need specific permissions in `AndroidManifest.xml`.
2. Accessing Custom Properties with CarPropertyManager (Kotlin Example)
import android.car.Car
import android.car.VehiclePropertyIds
import android.car.hardware.CarPropertyConfig
import android.car.hardware.CarPropertyManager
import android.car.hardware.CarPropertyValue
// Assuming you defined CUSTOM_ENGINE_RPM = 0x1000 in types.hal
// In Kotlin, you'd access it via a constant, or define it in your app
const val CUSTOM_ENGINE_RPM_ID = 0x1000
const val CUSTOM_HVAC_STATUS_ID = 0x1001
class CustomVehicleApp : AppCompatActivity() {
private lateinit var car: Car
private lateinit var carPropertyManager: CarPropertyManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
car = Car.createCar(this)
carPropertyManager = car.getCarManager(Car.PROPERTY_SERVICE) as CarPropertyManager
// Register a listener for custom properties
carPropertyManager.registerCallback(
object : CarPropertyManager.CarPropertyEventCallback {
override fun onChangeEvent(value: CarPropertyValue) {
when (value.propertyId) {
CUSTOM_ENGINE_RPM_ID -> {
val rpm = value.value as Int
println("Custom Engine RPM: $rpm")
// Update UI or perform actions
}
CUSTOM_HVAC_STATUS_ID -> {
val status = value.value as Int
println("Custom HVAC Status: $status")
}
}
}
override fun onErrorEvent(propertyId: Int, zone: Int) {
println("Error for property $propertyId")
}
},
// Specify the properties to listen for
CUSTOM_ENGINE_RPM_ID,
CUSTOM_HVAC_STATUS_ID
)
// Read a custom property once
val currentRpm: Int? = carPropertyManager.getProperty(CUSTOM_ENGINE_RPM_ID, 0)?.value
println("Initial Custom Engine RPM: $currentRpm")
// Set a custom property (e.g., for a custom actuator control)
// This assumes CUSTOM_DOOR_LOCK_STATE is a settable property in your VHAL
// carPropertyManager.setProperty(Int::class.java, CUSTOM_DOOR_LOCK_STATE, 0, 1) // 1 for locked
}
override fun onDestroy() {
super.onDestroy()
carPropertyManager.unregisterCallback(customPropertyCallback)
car.disconnect()
}
}
private val customPropertyCallback = object : CarPropertyManager.CarPropertyEventCallback {
override fun onChangeEvent(value: CarPropertyValue) {
// This is a placeholder; actual logic would be in onCreate or a dedicated method
}
override fun onErrorEvent(propertyId: Int, zone: Int) {}
}
```
Deployment and Debugging
- Building AAOS with Custom VHAL: You'll need to build the full AAOS image with your modified VHAL project. This is typically done through the AOSP build system (e.g., `lunch aosp_car_x86_64-userdebug`, `make -j$(nproc)`).
- Flashing to Device: Flash the new image to your AAOS development board.
- Debugging VHAL: Use `logcat` to monitor VHAL logs. Check for errors in your CAN parsing or property updates. You can also use `dumpsys car_service` to inspect registered properties.
- Debugging CAN Bus: Utilize `candump` and `cansend` on the Linux command line to verify raw CAN communication.
# On your AAOS device's shell
$ logcat | grep VehicleHal
$ dumpsys car_service --properties
# To send a custom CAN message from shell (for testing VHAL 'get' functionality)
$ candump can0 # To verify reception
$ cansend can0 101#0009C401 # Example: RPM 2500 (0x09C4), Load 1 (0x01)
Conclusion
Integrating proprietary CAN protocols with AAOS is a powerful way to extend vehicle functionality, leverage existing hardware, and create highly customized automotive user experiences. While it demands a deep understanding of CAN bus fundamentals, Linux kernel interfaces, and the AAOS VHAL architecture, the ability to tailor vehicle communication precisely to your needs offers unparalleled flexibility and innovation opportunities. By carefully designing your protocol, implementing a robust VHAL extension, and testing thoroughly, you can seamlessly bridge the gap between custom automotive hardware and the rich Android ecosystem.
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →