ExplorerLink is a system I built that modernizes my 2005 Ford Explorer. My foremost goal was to apply and expand on what I learned as an undergraduate, and I specifically wanted to go beyond the scope of traditional academic work by taking a project from start to finish. Through this project, I developed my skills and knowledge of embedded programming, real-time operating systems, and PCB/hardware design.
Background
This is my 2005 Ford Explorer. It has great utility and low mileage for its age, so it should see several more years of usefulness on road trips, snow excursions, and even light off-roading. This makes it a great candidate for customization. Before starting this project, I asked myself how it might be possible to bring modern features to this 13-year-old SUV.
Automakers have introduced countless new convenience and safety features to vehicles over the past decade or two which can be traced back to the introduction of electronic sensing and control where previously there was none. Some examples include blind spot alert systems, app-based remote monitoring and control, and adaptive high beams.
I set about listing as many of these modern features as possible and then evaluated them for feasibility. I selected the following features:
- Remote monitoring and control through a web interface, including:
- Remote start
- Climate control
- CAN bus data logging and viewing
- GPS location
- Automatic climate control
- Park-assist using ultrasonic sensors
Objectives
The primary objective of this project was to build my embedded design and programming skills. This made the process open-ended and experimental. Still, it was important to define what exactly I aimed to achieve and how the system would be used.
Use cases
Below is a tabular breakdown of how I planned each feature of the system would be used:
Feature | Use case(s) |
Remote start | On a hot or cold day, the driver is able to connect to the vehicle remotely, start the engine, and heat or cool the cabin. |
Remote climate control | |
CAN data viewing/logging | The driver can see parameters such as speed, range, and fuel economy while driving. |
The driver is able to check fuel level and range from anywhere. | |
GPS location | The driver is able to locate the vehicle from anywhere. |
Automatic climate control | The driver is able to set a desired cabin temperature using the factory selector knob, and the system will modulate temperature blend to maintain the set temperature. |
Park-assist | The driver is able to see visual feedback indicating the distance to any obstacles while the vehicle is in reverse. |
Requirements
I drew up a document to outline the engineering requirements for the project. Throughout the process, I ensured that these requirements only specified what the system should do, as opposed to how the system should do it.
Design
System architecture
The system has three components: a control unit within the vehicle, a server program, and web client software. The focus of this project was the control unit, but I will touch on the server and client software to illustrate how the functionality comes together. Figure 2 depicts the three components and their roles.
The server and ELCU remain powered on at all times. The web client application loads from the server when the user visits explorerlink.rounds.tech. First we’ll dive into the ELCU’s design. I’ll briefly discuss the design of the server program and web application in later sections.
ExplorerLink Control Unit
The ELCU is an electronic control unit that performs the varied functions described in the Objectives section. I chose to build a single control unit capable of handling all the system’s features rather than build multiple independent devices performing dedicated functions and communicating as needed. Multi-ECU architectures are the norm for automobiles, but because I wanted to fully explore real-time operating systems I did not split the functionality among separate devices.
This section will discuss the ELCU’s hardware and software. To skip to the software part, click here.
External hardware and interfacing
Below is a list of the external interfaces to the ELCU. This is a combination of requirements external to the system and requirements posed by sensors and LEDs that are part of the system design.
- Ignition
- Two switched outputs are required to drive the key position signals that normally originate from the Explorer’s ignition lock cylinder. These must supply vehicle battery voltage and at least 3A each.
- A/C
- One input must measure a voltage from 0V to 90% of vehicle battery voltage from the temperature selection potentiometer.
- One output must supply a voltage from 0V to 90% of vehicle battery voltage to command the temperature blend door actuator (normally controlled by the potentiometer).
- Four inputs must measure 0-3.3V analog from MCP9701A temperature sensors.
- CAN
- Two independent CAN buses exist in the Explorer – one main bus and one for the ABS module (vehicle dynamics). The former is required for some functions of the ELCU, and the latter is just cool, so I deemed it a requirement to interface with both CAN buses.
- Park-assist
- I selected these ultrasonic sensors for rearward distance measurement. One UART at 9600 baud is required to communicate with all four sensors.
- For visual feedback I chose to add 4 LED circuits whose current-limiting resistors would be external to the module. Internally, 4 GPIO-controlled current sinks would be needed.
ELCU hardware
The ELCU consists of a custom PCB with an attached modem board – both residing in a plastic enclosure. A 40-pin board-mounted connector provides an entry point for external circuitry.
In this section, I’ll detail how I selected some of the key components and give an overview of the PCB schematic. To see the full schematic you can view a PDF version here:
Analog
External interfaces require that the ELCU measure at least 6 analog inputs and produce one output. Four of the inputs need only be 0-3.3V, so no extra components are required as they can feed directly to ADC pins on a microcontroller. The others (2 inputs, 1 output) require additional components.
This is an overview of the analog components I selected:
- 2 analog inputs (for measuring vehicle battery voltage and temperature selector knob output)
- Resistive dividers scale the input voltage down by a factor of 4.6.
- Op amp buffers allow the dividers to use high resistance values while still guaranteeing quick charging of ADC input capacitance.
- 1 analog output (for driving the temperature blend door actuator)
- A MAX5815 DAC is included which communicates on an I2C bus.
- A TSX711-based non-inverting amplifier circuit with negative feedback and a gain of 5.85 amplifies the 2.5V-maximum outputs of the DAC to a range from 0 to 14.625V, exceeding the 0-12.7V range necessary.
Looking ahead, the microcontroller I selected would need to support at least six ADC inputs and one I2C master connection to interface with these analog components.
Ignition outputs
I chose to use two Omron G5Q relays for the ignition outputs. Two MOSFETs are used as low-side switches and two flyback diodes handle the coil voltage spikes. On the MCU, two GPIOs would need to be set aside to control the MOSFET gates.
CAN
I selected TI’s SN65HVD235 CAN transceivers for this design. Figure 12 shows 1 of 2 identical transceiver circuits. A design iteration would introduce some ESD protection on the bus pins. Two CAN TX/RX pairs would need to be set aside on the MCU for these transceivers.
Modem
A mobile connection to the internet requires a cellular modem. Here are the key factors I weighed when selecting a modem solution:
- Minimum 3G capable
- GSM
- Available for purchase in low quantities
- Available as breakout module or hand-solderable
- Minimal learning curve
- Solid documentation
Choosing a modem was challenging, particularly due to #1 and #4 above. I examined standalone modules as well as breakouts boards. I found that about 75% of the modules on the market use LGA packages, which make manufacturing and debugging difficult. Most remaining modules were 2G only, as they were generally legacy designs requiring fewer pins. Accounting for those limitations, using a breakout board was the more practical choice.
I selected Adafruit’s FONA 3G. It is a breakout for SimCom’s SIM5320A module with IO level-shifting, U.FL cellular and GPS antenna connectors, and a lithium-ion battery charger onboard. These were solid advantages, as they reduced the number of components I’d need on the ELCU’s main board. Let’s take a look at the relevant sections of the ELCU schematic:
MCU
There are many factors to consider when choosing a microcontroller, but before weighing the pros and cons of different options, I ensured that these hard requirements would be met:
- Available in a hand-solderable package
- Minimum 2 CAN interfaces
- Minimum 3 UART interfaces (debug, modem, ultrasonic sensors)
- Minimum 1 I2C interface (DAC)
- Minimum 6 ADC inputs
- Inexpensive and easy-to-use development tools
- RTOS available
My search for MCUs turned up a few products that met the first five requirements, including Microchip’s PIC32MX and PIC24HJ series, Atmel/Microchip’s AT32UC3C and SAM C21 series, and TI’s Tiva C series. The Tiva series stood out from these because of its development tools. I was able to begin programming the TM4C123GH6PM chip on TI’s EK-TM4C123G evaluation board. The EK also has JTAG and serial breakouts enabling it to be used as a debugger for a custom PCB. The Tiva chip also meets my final requirement, as TI supplies a port of FreeRTOS with their software bundle.
Power
The final aspect of the hardware design to tackle was power. The components in use so far collectively require four different voltage levels: 3.3V, 3.7V, 5V, and 12V. The 3.7V and 12V are battery-sourced while the others are regulated.
The 3.7V battery is connected to the ELCU board so that net current consumption of the modem and all ELCU components can be measured. The 5V regulator is sourced off-board from the vehicle battery, and is controlled by the MCU so that it does not consume power when the vehicle isn’t running.
PCB layout
Software
As the ELCU contains a single processor responsible for multiple functions, an RTOS is well-suited to handle all the varied requirements. I used FreeRTOS for this design, which allowed me to implement lean ISRs with deferred processing, a flexible hierarchy of interrupt and task priorities, and clean communication between tasks that avoids polling for events. More about that later. For now, here’s a link to all the source files:
Most of the links in this section will take you directly to related files or lines of source code on GitHub. Among the files you’ll find:
explorerlink_main | main() function with FreeRTOS task initialization |
*_task | RTOS task functions along with related ISRs and helper functions |
ring_buffer , channel , sample | data structure APIs |
priorities , stack_sizes , FreeRTOSConfig | configuration headers |
debug_helper | debug macros and functions |
Each _task.h
and _task.c
translation unit contains one task and zero or more related ISRs which work in concert. For example, the CAN task awaits notifications from the CAN ISR before retrieving frame data from the peripheral.
Meeting the requirements
Section 3 of the requirements doc outlines what the tasks and ISRs must accomplish. In this section I’ll describe what the code does at a high level. For greater detail, check out the comments.
Data acquisition
The most important source of data is the CAN bus, and the first example of deferred processing is in the interaction between the CAN ISR and the CAN task. The CAN controller is configured to filter for 12 different IDs on the vehicle bus. Since there are 32 message objects available, I set 12 of them to filter for one ID each, as defined in this array. The message objects serve as buffers for the CAN frames. All the ISR must do is notify the CAN task that a particular object has new data. Once the notification is received, the task then performs the more time-consuming job of deciphering the frames and storing data.
Most of the other tasks store data as well (see Figure 22). Data points are stored temporarily using the channel API in channel.h
. Each channel represents one source of data along with metadata such as the sampling rate and any associated CAN IDs.
To facilitate both viewing and logging of vehicle data, it must be sampled at regular intervals. ExplorerLink supports multiple sample rates in order to meet the needs of data sources with low and high frequency components.
The sampling process is performed in the RTC-triggered ISR, HibernateIntHandler
. On each run, it determines which channels need sampling at that point in time and proceeds to enqueue channel data along with a timestamp into a thread-safe circular buffer (also see ring_buffer.c
). Then, the ISR notifies the Modem UART task that there is data available, and the Modem UART task transmits the data when it is ready to. This flow is illustrated in Figure 22.
Remote start
The Remote Start task is in charge of operating the two GPIO outputs that control the ignition and cranking signals. On the surface, this seems simple, but the system risk is elevated for this software component. In other words, there are adverse consequences for mishandling these outputs. Proper control requires that software know the state of the system at all times and feature redundancies that prevent output errors. The table below lists the effects of three such failure modes.
Requirement | Failure effect |
Cranking must not be commanded longer than required to start the vehicle. | Excessive wear on starter solenoid clutch |
Engine must not remain running unattended (>10 minutes) | Fuel waste |
Electrical system must not remain on if client-device connection is broken | Battery drain |
User commands arrive via the modem and are parsed by the Modem UART task. Parsing determines what task must be notified; in this case, the Remote Start task. The Remote Start task awaits notifications in its main loop. In addition to the basic commands (ignition ON, ignition OFF, engine start), there are notification types to handle severe errors and loss or return of client connections. Because multiple notifications may be active, these are prioritized as follows:
- Error handling
- Ignition OFF
- Client loss
- Client returned
- Ignition ON
- Engine start
Only the highest priority notification takes effect. The Remote Start task uses a timer ISR, WTimer1AIntHandler
, to guarantee meeting the requirements in the table above. For example, when starting the engine, although cranking stops when the engine RPM is greater than 1000, the IgnitionStart
function configures a timer countdown prior to cranking. A normal cranking attempt is limited to 5 seconds. Should 7 seconds elapse before the timer is disabled manually, the ISR will trigger, disable the ignition completely, and send an error notification to the task. Check out the source to see how the rest of the functionality comes together.
Climate control
Climate control functionality is housed in analog_task.c
. Currently the task is a work in progress – it supports one temperature sensor (instead of aggregating multiple measurements), and only direct set point adjustment via the knob (no remote adjustment). In the file, you’ll find two ADC sampling ISRs and the task function. ADC0SS0IntHandler
is triggered at 20Hz and averages every five readings to produce a stable temperature measurement. The temperature selector knob voltage is also sampled and stored here.
The Analog task uses a proportional-integral (PI, no derivative) controller to produce output values that will control the temperature blend door. Normally, the door’s actuator is continuously fed an analog voltage from the selector knob. Instead, I translate the knob’s output voltage to a fixed temperature selection and rely on the PI controller to modulate the output as needed. As in any PI-controlled system, the control variable is calculated based on the current and accumulated error of the measured temperature with respect to the desired temperature.
Park-assist
The SRF task operates four rear-facing ultrasonic sensors, which communicate via UART. UART is not designed to support more than two devices, but because each sensor has a unique address, a bus architecture can be imitated. The ELCU’s TX output is wired to all four sensors’ RX inputs. All sensors receive the UART transmissions, but each only takes action if its own address is specified. When receiving, the ELCU must request a distance reading from one sensor at a time to avoid collisions. Figure 23 shows the UART connections.
Tying things together
Using an RTOS allows the software behind each of ExplorerLink’s key features to be modular. Ultimately, though, the various threads of execution must share one processor in an efficient, well-defined manner. In this section I’ll discuss how the threads are synchronized and prioritized.
Where are all the synchronization primitives?
FreeRTOS provides semaphores, mutexes, queues, buffers, and task notifications. I chose to use task notifications for nearly all synchronization among tasks and ISRs because they are lightweight and flexible. Let’s look at the instances in which task notifications are used:
Three of the arrows (purple, green, red) represent basic signals; there’s no data being handled other than the notification value itself. This is the simplest and most ideal use for a task notification. The other four are more interesting. The CAN notification (orange) takes advantage of the CAN peripheral message objects as buffers; it relies on the task receiving the notification quickly enough to retrieve the frame data before another message with the same ID arrives (and overwrites the object). The SRF UART notification (pink) uses the notification value itself to store the distance measurements, again eliminating the need for a queue or separate buffer. The sampling (yellow) and modem UART (blue) notifications are the two that do make use of separate buffers (ring_buffer.c
). These could use FreeRTOS queues or message buffers as an alternative, but I chose to create my own buffer implementation for lower overhead.
Prioritization
Using an RTOS allows for fine control over the prioritization of tasks and ISRs, but careful consideration of possible execution sequencing is required to ensure that timing requirements are met. First, let’s look at the priorities themselves:
Task | Priority |
CAN | 4 |
Remote Start | 3 |
Data | 2 |
Modem Mgmt | 2 |
SRF | 2 |
Analog | 1 |
Modem UART | 1 |
Interrupt | Priority |
Ignition timer | 5 |
CAN RX | 6 |
Data sampling | 7 |
Modem UART | 7 |
SRF UART RX | 7 |
FreeRTOS kernel | 7 |
Note: I’ve omitted “normal” ISRs that don’t contain FreeRTOS API calls. All of the unlisted ISRs have an equal priority of 0 (highest). See here for more.
Each list starts with the highest priority at the top (Cortex-M4 high priorities are numerically low). Let’s take one example. The CAN ISR is short, but it must have high priority to ensure that frames are not missed. Putting it below the sampling ISR would be risky because that ISR is more time-consuming. The CAN task, which processes the frames, is given the highest priority among tasks because it must read every message object (buffer of length 1) before the next frame arrives and overwrites the object.
Server
The server is a DreamCompute instance running a Node.js program. The cool thing about using Node.js is that a single program can service a raw TCP connection to the ELCU, maintain a database connection, and function as a web server. This is all thanks to the ecosystem of packages provided by npm
. The server setup is not a model for efficiency (it would not scale), but its flexibility allowed me to focus on building features rather than writing low-level code.
Web client
The client software consists of JavaScript code supporting user interaction with interface elements and communication with the server. I have a demo of the interface set up here:
Integration
I will include some details about hardware and software testing here in the future; for now, enjoy these pictures:
If you have questions or knowledge to share, please feel free to leave a comment.