forked from espressif/esp-idf
CAN Driver
The following commit contains the first version of the ESP32 CAN Driver. closes #544
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := can_alert_and_recovery_example
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# CAN Alert and Recovery Example
|
||||
|
||||
## Overview
|
||||
The CAN Alert and Recovery Example demonstrates the usage of alerts and bus
|
||||
recovery in the CAN driver. This example **requires only a single ESP32 module
|
||||
to run**.
|
||||
|
||||
The CAN Alert and Recovery Example will do the following...
|
||||
|
||||
1. Initialize and start the CAN driver on the ESP32 module
|
||||
2. Repeatedly transmit messages (no acknowledgement required)
|
||||
3. Reconfigure alerts to detect bus-off state
|
||||
4. Purposely trigger errors on transmissions
|
||||
5. Detect Bus Off condition
|
||||
6. Initiate bus recovery
|
||||
7. Deinitialize CAN driver on ESP32 module
|
||||
|
||||
## External Transceiver and Pin Assignment
|
||||
The CAN controller in the ESP32 **does not contain an internal transceiver**.
|
||||
Therefore users are responsible for providing an external transceiver compatible
|
||||
with the physical layer specifications of their target ISO standard (such as
|
||||
SN65HVD23X transceivers for ISO 11898-2 compatibility)
|
||||
|
||||
The CAN controller in the ESP32 represents dominant bits to the transceiver as
|
||||
logic low, and recessive bits as logic high. The Alert and Recovery Example
|
||||
utilizes the following default pin assignments
|
||||
|
||||
* TX Pin is routed to GPIO21
|
||||
* RX Pin is routed to GPIO22
|
||||
@@ -0,0 +1,29 @@
|
||||
#Need Python 3 string formatting functions
|
||||
from __future__ import print_function
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
# The test cause is dependent on the Tiny Test Framework. Ensure the
|
||||
# `TEST_FW_PATH` environment variable is set to `$IDF_PATH/tools/tiny-test-fw`
|
||||
test_fw_path = os.getenv("TEST_FW_PATH")
|
||||
if test_fw_path and test_fw_path not in sys.path:
|
||||
sys.path.insert(0, test_fw_path)
|
||||
import TinyFW
|
||||
import IDF
|
||||
|
||||
# CAN Self Test Example constants
|
||||
STR_EXPECT = ("CAN Alert and Recovery: Driver installed", "CAN Alert and Recovery: Driver uninstalled")
|
||||
EXPECT_TIMEOUT = 20
|
||||
|
||||
@IDF.idf_example_test(env_tag='Example_CAN')
|
||||
def test_can_alert_and_recovery_example(env, extra_data):
|
||||
#Get device under test, flash and start example. "dut4" must be defined in EnvConfig
|
||||
dut = env.get_dut('dut4', 'examples/peripherals/can/can_alert_and_recovery')
|
||||
dut.start_app()
|
||||
|
||||
for string in STR_EXPECT:
|
||||
dut.expect(string, timeout = EXPECT_TIMEOUT)
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_can_alert_and_recovery_example()
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/* CAN Alert and Recovery Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The following example demonstrates how to use the alert and bus recovery
|
||||
* features of the CAN driver. The example will do the following:
|
||||
* 1) Install and start the CAN driver
|
||||
* 2) Have the TX task periodically broadcast messages expecting no ACK
|
||||
* 3) Reconfigure alerts to detect bus-off state
|
||||
* 4) Trigger bus errors by inverting TX GPIO
|
||||
* 5) Initiate bus-off recovery and wait for completion
|
||||
* 6) Uninstall CAN driver
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/can.h"
|
||||
|
||||
/* --------------------- Definitions and static variables ------------------ */
|
||||
//Example Configuration
|
||||
#define TX_GPIO_NUM 21
|
||||
#define RX_GPIO_NUM 22
|
||||
#define TX_TASK_PRIO 9
|
||||
#define CTRL_TASK_PRIO 10
|
||||
#define ERR_DELAY_US 800 //Approximate time for arbitration phase at 25KBPS
|
||||
#define ERR_PERIOD_US 80 //Approximate time for two bits at 25KBPS
|
||||
#define EXAMPLE_TAG "CAN Alert and Recovery"
|
||||
|
||||
static const can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL();
|
||||
static const can_timing_config_t t_config = CAN_TIMING_CONFIG_25KBITS();
|
||||
static const can_general_config_t g_config = CAN_GENERAL_CONFIG_DEFAULT(TX_GPIO_NUM, RX_GPIO_NUM, CAN_MODE_NO_ACK);
|
||||
static const can_message_t tx_msg = {.identifier = 0, .data_length_code = 0, .flags = CAN_MSG_FLAG_NONE};
|
||||
|
||||
static SemaphoreHandle_t tx_task_sem;
|
||||
static SemaphoreHandle_t ctrl_task_sem;
|
||||
static bool trigger_tx_error = false;
|
||||
|
||||
/* --------------------------- Tasks and Functions -------------------------- */
|
||||
|
||||
static void invert_tx_bits(bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
//Inverts output of TX to trigger errors
|
||||
gpio_matrix_out(TX_GPIO_NUM, CAN_TX_IDX, true, false);
|
||||
} else {
|
||||
//Returns TX to default settings
|
||||
gpio_matrix_out(TX_GPIO_NUM, CAN_TX_IDX, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
static void tx_task(void *arg)
|
||||
{
|
||||
xSemaphoreTake(tx_task_sem, portMAX_DELAY);
|
||||
while (1) {
|
||||
if (can_transmit(&tx_msg, 0) == ESP_ERR_INVALID_STATE) {
|
||||
break; //Exit TX task when bus-off state is reached
|
||||
}
|
||||
if (trigger_tx_error) {
|
||||
//Trigger a bit error in transmission by inverting GPIO
|
||||
ets_delay_us(ERR_DELAY_US); //Wait until arbitration phase is over
|
||||
invert_tx_bits(true); //Trigger bit error for a few bits
|
||||
ets_delay_us(ERR_PERIOD_US);
|
||||
invert_tx_bits(false);
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void ctrl_task(void *arg)
|
||||
{
|
||||
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
|
||||
ESP_ERROR_CHECK(can_start());
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver started");
|
||||
ESP_LOGI(EXAMPLE_TAG, "Starting transmissions");
|
||||
xSemaphoreGive(tx_task_sem); //Start transmit task
|
||||
|
||||
//Prepare to trigger errors, reconfigure alerts to detect change in error state
|
||||
can_reconfigure_alerts(CAN_ALERT_ABOVE_ERR_WARN | CAN_ALERT_ERR_PASS | CAN_ALERT_BUS_OFF, NULL);
|
||||
for (int i = 3; i > 0; i--) {
|
||||
ESP_LOGW(EXAMPLE_TAG, "Trigger TX errors in %d", i);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
ESP_LOGI(EXAMPLE_TAG, "Trigger errors");
|
||||
trigger_tx_error = true;
|
||||
|
||||
while (1) {
|
||||
uint32_t alerts;
|
||||
can_read_alerts(&alerts, portMAX_DELAY);
|
||||
if (alerts & CAN_ALERT_ABOVE_ERR_WARN) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "Surpassed Error Warning Limit");
|
||||
}
|
||||
if (alerts & CAN_ALERT_ERR_PASS) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "Entered Error Passive state");
|
||||
}
|
||||
if (alerts & CAN_ALERT_BUS_OFF) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "Bus Off state");
|
||||
//Prepare to initiate bus recovery, reconfigure alerts to detect bus recovery completion
|
||||
can_reconfigure_alerts(CAN_ALERT_BUS_RECOVERED, NULL);
|
||||
for (int i = 3; i > 0; i--) {
|
||||
ESP_LOGW(EXAMPLE_TAG, "Initiate bus recovery in %d", i);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
can_initiate_recovery(); //Needs 128 occurrences of bus free signal
|
||||
ESP_LOGI(EXAMPLE_TAG, "Initiate bus recovery");
|
||||
}
|
||||
if (alerts & CAN_ALERT_BUS_RECOVERED) {
|
||||
//Bus recovery was successful, exit control task to uninstall driver
|
||||
ESP_LOGI(EXAMPLE_TAG, "Bus Recovered");
|
||||
break;
|
||||
}
|
||||
}
|
||||
//No need call can_stop(), bus recovery will return to stopped state
|
||||
xSemaphoreGive(ctrl_task_sem);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
tx_task_sem = xSemaphoreCreateBinary();
|
||||
ctrl_task_sem = xSemaphoreCreateBinary();
|
||||
|
||||
xTaskCreatePinnedToCore(tx_task, "CAN_tx", 4096, NULL, TX_TASK_PRIO, NULL, tskNO_AFFINITY);
|
||||
xTaskCreatePinnedToCore(ctrl_task, "CAN_ctrl", 4096, NULL, CTRL_TASK_PRIO, NULL, tskNO_AFFINITY);
|
||||
|
||||
//Install CAN driver
|
||||
ESP_ERROR_CHECK(can_driver_install(&g_config, &t_config, & f_config));
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver installed");
|
||||
|
||||
xSemaphoreGive(ctrl_task_sem); //Start control task
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); //Wait for completion
|
||||
|
||||
//Uninstall CAN driver
|
||||
ESP_ERROR_CHECK(can_driver_uninstall());
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled");
|
||||
|
||||
//Cleanup
|
||||
vSemaphoreDelete(tx_task_sem);
|
||||
vSemaphoreDelete(ctrl_task_sem);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,67 @@
|
||||
# CAN Network Example
|
||||
|
||||
## Overview
|
||||
The CAN Network Example demonstrates communication between two ESP32 modules (master
|
||||
and slave) using the CAN2.0B protocol. CAN is a multi-master protocol, therefore
|
||||
the concept of master/slave in this example refers to which node initiates
|
||||
and stops the transfer of a stream of data messages. The example also includes
|
||||
an optional **Listen Only module** which can passively receive the CAN messages
|
||||
sent between the master and slave module without participating in any CAN bus activity.
|
||||
|
||||
The CAN Network Example will execute the following steps over multiple iterations:
|
||||
|
||||
1. Both master and slave go through initialization process
|
||||
2. The master repeatedly sends **PING** messages until it receives a **PING_RESP**
|
||||
from the slave. The slave will only send a **PING_RESP** message when it receives
|
||||
a **PING** message from the master.
|
||||
3. Once the master has received the **PING_RESP** from the slave, it will send a
|
||||
**START_CMD** message to the slave.
|
||||
4. Upon receiving the **START_CMD** message, the slave will start transmitting
|
||||
**DATA** messages until the master sends a **STOP_CMD**. The master will send
|
||||
the **STOP_CMD** after receiving N **DATA** messages from the slave (N = 50 by
|
||||
default).
|
||||
5. When the slave receives the **STOP_CMD**, it will confirm that it has stopped
|
||||
by sending a **STOP_RESP** message to the master.
|
||||
|
||||
## External Transceiver and Pin Assignment
|
||||
The CAN controller in the ESP32 **does not contain an internal transceiver**.
|
||||
Therefore users are responsible for providing an external transceiver compatible
|
||||
with the physical layer specifications of their target ISO standard (such as
|
||||
SN65HVD23X transceivers for ISO 11898-2 compatibility)
|
||||
|
||||
The CAN controller in the ESP32 represents dominant bits to the transceiver as
|
||||
logic low, and recessive bits as logic high. The Network Example utilizes the
|
||||
following default pin assignments
|
||||
|
||||
* TX Pin is routed to GPIO21
|
||||
* RX Pin is routed to GPIO22
|
||||
|
||||
The following diagram illustrates an example network
|
||||
|
||||
~~~~
|
||||
---------- ---------- --------------
|
||||
| Master | | Slave | | Listen Only |
|
||||
| | | | | |
|
||||
| 21 22 | | 21 22 | | 21 22 |
|
||||
---------- ---------- --------------
|
||||
| | | | | |
|
||||
| | | | | |
|
||||
---------- ---------- ----------
|
||||
| D R | | D R | | D R |
|
||||
| | | | | |
|
||||
| VP230 | | VP230 | | VP230 |
|
||||
| | | | | |
|
||||
| H L | | H L | | H L |
|
||||
---------- ---------- ----------
|
||||
| | | | | |
|
||||
| | | | | |
|
||||
|--x------|-----x------|-----x------|--| H
|
||||
| | |
|
||||
|---------x------------x------------x--| L
|
||||
|
||||
~~~~
|
||||
|
||||
## Note
|
||||
If there appears to be no activity on the CAN bus when running the example, users
|
||||
can try running the `can_self_test` example to verify if their transceivers are
|
||||
wired properly.
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := can_network_listen_only
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/* CAN Network Listen Only Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The following example demonstrates a Listen Only node in a CAN network. The
|
||||
* Listen Only node will not take part in any CAN bus activity (no acknowledgments
|
||||
* and no error frames). This example will execute multiple iterations, with each
|
||||
* iteration the Listen Only node will do the following:
|
||||
* 1) Listen for ping and ping response
|
||||
* 2) Listen for start command
|
||||
* 3) Listen for data messages
|
||||
* 4) Listen for stop and stop response
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/can.h"
|
||||
|
||||
/* --------------------- Definitions and static variables ------------------ */
|
||||
//Example Configuration
|
||||
#define NO_OF_ITERS 3
|
||||
#define RX_TASK_PRIO 9
|
||||
#define TX_GPIO_NUM 21
|
||||
#define RX_GPIO_NUM 22
|
||||
#define EXAMPLE_TAG "CAN Listen Only"
|
||||
|
||||
#define ID_MASTER_STOP_CMD 0x0A0
|
||||
#define ID_MASTER_START_CMD 0x0A1
|
||||
#define ID_MASTER_PING 0x0A2
|
||||
#define ID_SLAVE_STOP_RESP 0x0B0
|
||||
#define ID_SLAVE_DATA 0x0B1
|
||||
#define ID_SLAVE_PING_RESP 0x0B2
|
||||
|
||||
static const can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL();
|
||||
static const can_timing_config_t t_config = CAN_TIMING_CONFIG_25KBITS();
|
||||
//Set TX queue length to 0 due to listen only mode
|
||||
static const can_general_config_t g_config = {.mode = CAN_MODE_LISTEN_ONLY,
|
||||
.tx_io = TX_GPIO_NUM, .rx_io = RX_GPIO_NUM,
|
||||
.clkout_io = CAN_IO_UNUSED, .bus_off_io = CAN_IO_UNUSED,
|
||||
.tx_queue_len = 0, .rx_queue_len = 5,
|
||||
.alerts_enabled = CAN_ALERT_NONE,
|
||||
.clkout_divider = 0};
|
||||
|
||||
static SemaphoreHandle_t rx_sem;
|
||||
|
||||
/* --------------------------- Tasks and Functions -------------------------- */
|
||||
|
||||
static void can_receive_task(void *arg)
|
||||
{
|
||||
xSemaphoreTake(rx_sem, portMAX_DELAY);
|
||||
bool start_cmd = false;
|
||||
bool stop_resp = false;
|
||||
uint32_t iterations = 0;
|
||||
|
||||
while (iterations < NO_OF_ITERS) {
|
||||
can_message_t rx_msg;
|
||||
can_receive(&rx_msg, portMAX_DELAY);
|
||||
if (rx_msg.identifier == ID_MASTER_PING) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "Received master ping");
|
||||
} else if (rx_msg.identifier == ID_SLAVE_PING_RESP) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "Received slave ping response");
|
||||
} else if (rx_msg.identifier == ID_MASTER_START_CMD) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "Received master start command");
|
||||
start_cmd = true;
|
||||
} else if (rx_msg.identifier == ID_SLAVE_DATA) {
|
||||
uint32_t data = 0;
|
||||
for (int i = 0; i < rx_msg.data_length_code; i++) {
|
||||
data |= (rx_msg.data[i] << (i * 8));
|
||||
}
|
||||
ESP_LOGI(EXAMPLE_TAG, "Received data value %d", data);
|
||||
} else if (rx_msg.identifier == ID_MASTER_STOP_CMD) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "Received master stop command");
|
||||
} else if (rx_msg.identifier == ID_SLAVE_STOP_RESP) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "Received slave stop response");
|
||||
stop_resp = true;
|
||||
}
|
||||
if (start_cmd && stop_resp) {
|
||||
//Each iteration is complete after a start command and stop response is received
|
||||
iterations++;
|
||||
start_cmd = 0;
|
||||
stop_resp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
xSemaphoreGive(rx_sem);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
rx_sem = xSemaphoreCreateBinary();
|
||||
xTaskCreatePinnedToCore(can_receive_task, "CAN_rx", 4096, NULL, RX_TASK_PRIO, NULL, tskNO_AFFINITY);
|
||||
|
||||
//Install and start CAN driver
|
||||
ESP_ERROR_CHECK(can_driver_install(&g_config, &t_config, &f_config));
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver installed");
|
||||
ESP_ERROR_CHECK(can_start());
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver started");
|
||||
|
||||
xSemaphoreGive(rx_sem); //Start RX task
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
xSemaphoreTake(rx_sem, portMAX_DELAY); //Wait for RX task to complete
|
||||
|
||||
//Stop and uninstall CAN driver
|
||||
ESP_ERROR_CHECK(can_stop());
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver stopped");
|
||||
ESP_ERROR_CHECK(can_driver_uninstall());
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled");
|
||||
|
||||
//Cleanup
|
||||
vSemaphoreDelete(rx_sem);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := can_network_master
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
/* CAN Network Master Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The following example demonstrates a master node in a CAN network. The master
|
||||
* node is responsible for initiating and stopping the transfer of data messages.
|
||||
* The example will execute multiple iterations, with each iteration the master
|
||||
* node will do the following:
|
||||
* 1) Start the CAN driver
|
||||
* 2) Repeatedly send ping messages until a ping response from slave is received
|
||||
* 3) Send start command to slave and receive data messages from slave
|
||||
* 4) Send stop command to slave and wait for stop response from slave
|
||||
* 5) Stop the CAN driver
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/can.h"
|
||||
|
||||
/* --------------------- Definitions and static variables ------------------ */
|
||||
//Example Configuration
|
||||
#define PING_PERIOD_MS 250
|
||||
#define NO_OF_DATA_MSGS 50
|
||||
#define NO_OF_ITERS 3
|
||||
#define ITER_DELAY_MS 1000
|
||||
#define RX_TASK_PRIO 8
|
||||
#define TX_TASK_PRIO 9
|
||||
#define CTRL_TSK_PRIO 10
|
||||
#define TX_GPIO_NUM 21
|
||||
#define RX_GPIO_NUM 22
|
||||
#define EXAMPLE_TAG "CAN Master"
|
||||
|
||||
#define ID_MASTER_STOP_CMD 0x0A0
|
||||
#define ID_MASTER_START_CMD 0x0A1
|
||||
#define ID_MASTER_PING 0x0A2
|
||||
#define ID_SLAVE_STOP_RESP 0x0B0
|
||||
#define ID_SLAVE_DATA 0x0B1
|
||||
#define ID_SLAVE_PING_RESP 0x0B2
|
||||
|
||||
typedef enum {
|
||||
TX_SEND_PINGS,
|
||||
TX_SEND_START_CMD,
|
||||
TX_SEND_STOP_CMD,
|
||||
TX_TASK_EXIT,
|
||||
} tx_task_action_t;
|
||||
|
||||
typedef enum {
|
||||
RX_RECEIVE_PING_RESP,
|
||||
RX_RECEIVE_DATA,
|
||||
RX_RECEIVE_STOP_RESP,
|
||||
RX_TASK_EXIT,
|
||||
} rx_task_action_t;
|
||||
|
||||
static const can_timing_config_t t_config = CAN_TIMING_CONFIG_25KBITS();
|
||||
static const can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL();
|
||||
static const can_general_config_t g_config = CAN_GENERAL_CONFIG_DEFAULT(TX_GPIO_NUM, RX_GPIO_NUM, CAN_MODE_NORMAL);
|
||||
|
||||
static const can_message_t ping_message = {.identifier = ID_MASTER_PING, .data_length_code = 0,
|
||||
.flags = CAN_MSG_FLAG_SS, .data = {0, 0 , 0 , 0 ,0 ,0 ,0 ,0}};
|
||||
static const can_message_t start_message = {.identifier = ID_MASTER_START_CMD, .data_length_code = 0,
|
||||
.flags = CAN_MSG_FLAG_NONE, .data = {0, 0 , 0 , 0 ,0 ,0 ,0 ,0}};
|
||||
static const can_message_t stop_message = {.identifier = ID_MASTER_STOP_CMD, .data_length_code = 0,
|
||||
.flags = CAN_MSG_FLAG_NONE, .data = {0, 0 , 0 , 0 ,0 ,0 ,0 ,0}};
|
||||
|
||||
static QueueHandle_t tx_task_queue;
|
||||
static QueueHandle_t rx_task_queue;
|
||||
static SemaphoreHandle_t stop_ping_sem;
|
||||
static SemaphoreHandle_t ctrl_task_sem;
|
||||
static SemaphoreHandle_t done_sem;
|
||||
|
||||
/* --------------------------- Tasks and Functions -------------------------- */
|
||||
|
||||
static void can_receive_task(void *arg)
|
||||
{
|
||||
while (1) {
|
||||
rx_task_action_t action;
|
||||
xQueueReceive(rx_task_queue, &action, portMAX_DELAY);
|
||||
|
||||
if (action == RX_RECEIVE_PING_RESP) {
|
||||
//Listen for ping response from slave
|
||||
while (1) {
|
||||
can_message_t rx_msg;
|
||||
can_receive(&rx_msg, portMAX_DELAY);
|
||||
if (rx_msg.identifier == ID_SLAVE_PING_RESP) {
|
||||
xSemaphoreGive(stop_ping_sem);
|
||||
xSemaphoreGive(ctrl_task_sem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (action == RX_RECEIVE_DATA) {
|
||||
//Receive data messages from slave
|
||||
uint32_t data_msgs_rec = 0;
|
||||
while (data_msgs_rec < NO_OF_DATA_MSGS) {
|
||||
can_message_t rx_msg;
|
||||
can_receive(&rx_msg, portMAX_DELAY);
|
||||
if (rx_msg.identifier == ID_SLAVE_DATA) {
|
||||
uint32_t data = 0;
|
||||
for (int i = 0; i < rx_msg.data_length_code; i++) {
|
||||
data |= (rx_msg.data[i] << (i * 8));
|
||||
}
|
||||
ESP_LOGI(EXAMPLE_TAG, "Received data value %d", data);
|
||||
data_msgs_rec ++;
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(ctrl_task_sem);
|
||||
} else if (action == RX_RECEIVE_STOP_RESP) {
|
||||
//Listen for stop response from slave
|
||||
while (1) {
|
||||
can_message_t rx_msg;
|
||||
can_receive(&rx_msg, portMAX_DELAY);
|
||||
if (rx_msg.identifier == ID_SLAVE_STOP_RESP) {
|
||||
xSemaphoreGive(ctrl_task_sem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (action == RX_TASK_EXIT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void can_transmit_task(void *arg) {
|
||||
while (1) {
|
||||
tx_task_action_t action;
|
||||
xQueueReceive(tx_task_queue, &action, portMAX_DELAY);
|
||||
|
||||
if (action == TX_SEND_PINGS) {
|
||||
//Repeatedly transmit pings
|
||||
ESP_LOGI(EXAMPLE_TAG, "Transmitting ping");
|
||||
while (xSemaphoreTake(stop_ping_sem, 0) != pdTRUE) {
|
||||
can_transmit(&ping_message, portMAX_DELAY);
|
||||
vTaskDelay(pdMS_TO_TICKS(PING_PERIOD_MS));
|
||||
}
|
||||
} else if (action == TX_SEND_START_CMD) {
|
||||
//Transmit start command to slave
|
||||
can_transmit(&start_message, portMAX_DELAY);
|
||||
ESP_LOGI(EXAMPLE_TAG, "Transmitted start command");
|
||||
} else if (action == TX_SEND_STOP_CMD) {
|
||||
//Transmit stop command to slave
|
||||
can_transmit(&stop_message, portMAX_DELAY);
|
||||
ESP_LOGI(EXAMPLE_TAG, "Transmitted stop command");
|
||||
} else if (action == TX_TASK_EXIT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void can_control_task(void *arg) {
|
||||
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
|
||||
tx_task_action_t tx_action;
|
||||
rx_task_action_t rx_action;
|
||||
|
||||
for (int iter = 0; iter < NO_OF_ITERS; iter++) {
|
||||
ESP_ERROR_CHECK(can_start());
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver started");
|
||||
|
||||
//Start transmitting pings, and listen for ping response
|
||||
tx_action = TX_SEND_PINGS;
|
||||
rx_action = RX_RECEIVE_PING_RESP;
|
||||
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
|
||||
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
|
||||
|
||||
//Send Start command to slave, and receive data messages
|
||||
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
|
||||
tx_action = TX_SEND_START_CMD;
|
||||
rx_action = RX_RECEIVE_DATA;
|
||||
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
|
||||
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
|
||||
|
||||
//Send Stop command to slave when enough data messages have been received. Wait for stop response
|
||||
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
|
||||
tx_action = TX_SEND_STOP_CMD;
|
||||
rx_action = RX_RECEIVE_STOP_RESP;
|
||||
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
|
||||
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
|
||||
|
||||
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
|
||||
ESP_ERROR_CHECK(can_stop());
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver stopped");
|
||||
vTaskDelay(pdMS_TO_TICKS(ITER_DELAY_MS));
|
||||
}
|
||||
//Stop TX and RX tasks
|
||||
tx_action = TX_TASK_EXIT;
|
||||
rx_action = RX_TASK_EXIT;
|
||||
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
|
||||
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
|
||||
|
||||
//Delete Control task
|
||||
xSemaphoreGive(done_sem);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
//Create tasks, queues, and semaphores
|
||||
rx_task_queue = xQueueCreate(1, sizeof(rx_task_action_t));
|
||||
tx_task_queue = xQueueCreate(1, sizeof(tx_task_action_t));
|
||||
ctrl_task_sem = xSemaphoreCreateBinary();
|
||||
stop_ping_sem = xSemaphoreCreateBinary();
|
||||
done_sem = xSemaphoreCreateBinary();
|
||||
xTaskCreatePinnedToCore(can_receive_task, "CAN_rx", 4096, NULL, RX_TASK_PRIO, NULL, tskNO_AFFINITY);
|
||||
xTaskCreatePinnedToCore(can_transmit_task, "CAN_tx", 4096, NULL, TX_TASK_PRIO, NULL, tskNO_AFFINITY);
|
||||
xTaskCreatePinnedToCore(can_control_task, "CAN_ctrl", 4096, NULL, CTRL_TSK_PRIO, NULL, tskNO_AFFINITY);
|
||||
|
||||
//Install CAN driver
|
||||
ESP_ERROR_CHECK(can_driver_install(&g_config, &t_config, &f_config));
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver installed");
|
||||
|
||||
xSemaphoreGive(ctrl_task_sem); //Start control task
|
||||
xSemaphoreTake(done_sem, portMAX_DELAY); //Wait for completion
|
||||
|
||||
//Uninstall CAN driver
|
||||
ESP_ERROR_CHECK(can_driver_uninstall());
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled");
|
||||
|
||||
//Cleanup
|
||||
vQueueDelete(rx_task_queue);
|
||||
vQueueDelete(tx_task_queue);
|
||||
vSemaphoreDelete(ctrl_task_sem);
|
||||
vSemaphoreDelete(stop_ping_sem);
|
||||
vSemaphoreDelete(done_sem);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := can_network_slave
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
/* CAN Network Slave Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The following example demonstrates a slave node in a CAN network. The slave
|
||||
* node is responsible for sending data messages to the master. The example will
|
||||
* execute multiple iterations, with each iteration the slave node will do the
|
||||
* following:
|
||||
* 1) Start the CAN driver
|
||||
* 2) Listen for ping messages from master, and send ping response
|
||||
* 3) Listen for start command from master
|
||||
* 4) Send data messages to master and listen for stop command
|
||||
* 5) Send stop response to master
|
||||
* 6) Stop the CAN driver
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/can.h"
|
||||
|
||||
/* --------------------- Definitions and static variables ------------------ */
|
||||
//Example Configuration
|
||||
#define DATA_PERIOD_MS 50
|
||||
#define NO_OF_ITERS 3
|
||||
#define ITER_DELAY_MS 1000
|
||||
#define RX_TASK_PRIO 8 //Receiving task priority
|
||||
#define TX_TASK_PRIO 9 //Sending task priority
|
||||
#define CTRL_TSK_PRIO 10 //Control task priority
|
||||
#define TX_GPIO_NUM 21
|
||||
#define RX_GPIO_NUM 22
|
||||
#define EXAMPLE_TAG "CAN Slave"
|
||||
|
||||
#define ID_MASTER_STOP_CMD 0x0A0
|
||||
#define ID_MASTER_START_CMD 0x0A1
|
||||
#define ID_MASTER_PING 0x0A2
|
||||
#define ID_SLAVE_STOP_RESP 0x0B0
|
||||
#define ID_SLAVE_DATA 0x0B1
|
||||
#define ID_SLAVE_PING_RESP 0x0B2
|
||||
|
||||
typedef enum {
|
||||
TX_SEND_PING_RESP,
|
||||
TX_SEND_DATA,
|
||||
TX_SEND_STOP_RESP,
|
||||
TX_TASK_EXIT,
|
||||
} tx_task_action_t;
|
||||
|
||||
typedef enum {
|
||||
RX_RECEIVE_PING,
|
||||
RX_RECEIVE_START_CMD,
|
||||
RX_RECEIVE_STOP_CMD,
|
||||
RX_TASK_EXIT,
|
||||
} rx_task_action_t;
|
||||
|
||||
static const can_general_config_t g_config = CAN_GENERAL_CONFIG_DEFAULT(TX_GPIO_NUM, RX_GPIO_NUM, CAN_MODE_NORMAL);
|
||||
static const can_timing_config_t t_config = CAN_TIMING_CONFIG_25KBITS();
|
||||
static const can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL();
|
||||
static const can_message_t ping_resp = {.identifier = ID_SLAVE_PING_RESP, .data_length_code = 0,
|
||||
.flags = CAN_MSG_FLAG_NONE, .data = {0, 0 , 0 , 0 ,0 ,0 ,0 ,0}};
|
||||
static const can_message_t stop_resp = {.identifier = ID_SLAVE_STOP_RESP, .data_length_code = 0,
|
||||
.flags = CAN_MSG_FLAG_NONE, .data = {0, 0 , 0 , 0 ,0 ,0 ,0 ,0}};
|
||||
//Data bytes of data message will be initialized in the transmit task
|
||||
static can_message_t data_message = {.identifier = ID_SLAVE_DATA, .data_length_code = 4,
|
||||
.flags = CAN_MSG_FLAG_NONE, .data = {0, 0 , 0 , 0 ,0 ,0 ,0 ,0}};
|
||||
|
||||
static QueueHandle_t tx_task_queue;
|
||||
static QueueHandle_t rx_task_queue;
|
||||
static SemaphoreHandle_t ctrl_task_sem;
|
||||
static SemaphoreHandle_t stop_data_sem;
|
||||
static SemaphoreHandle_t done_sem;
|
||||
|
||||
/* --------------------------- Tasks and Functions -------------------------- */
|
||||
|
||||
static void can_receive_task(void *arg)
|
||||
{
|
||||
while (1) {
|
||||
rx_task_action_t action;
|
||||
xQueueReceive(rx_task_queue, &action, portMAX_DELAY);
|
||||
if (action == RX_RECEIVE_PING) {
|
||||
//Listen for pings from master
|
||||
can_message_t rx_msg;
|
||||
while (1) {
|
||||
can_receive(&rx_msg, portMAX_DELAY);
|
||||
if (rx_msg.identifier == ID_MASTER_PING) {
|
||||
xSemaphoreGive(ctrl_task_sem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (action == RX_RECEIVE_START_CMD) {
|
||||
//Listen for start command from master
|
||||
can_message_t rx_msg;
|
||||
while (1) {
|
||||
can_receive(&rx_msg, portMAX_DELAY);
|
||||
if (rx_msg.identifier == ID_MASTER_START_CMD) {
|
||||
xSemaphoreGive(ctrl_task_sem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (action == RX_RECEIVE_STOP_CMD) {
|
||||
//Listen for stop command from master
|
||||
can_message_t rx_msg;
|
||||
while (1) {
|
||||
can_receive(&rx_msg, portMAX_DELAY);
|
||||
if (rx_msg.identifier == ID_MASTER_STOP_CMD) {
|
||||
xSemaphoreGive(stop_data_sem);
|
||||
xSemaphoreGive(ctrl_task_sem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (action == RX_TASK_EXIT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void can_transmit_task(void *arg)
|
||||
{
|
||||
while (1) {
|
||||
tx_task_action_t action;
|
||||
xQueueReceive(tx_task_queue, &action, portMAX_DELAY);
|
||||
|
||||
if (action == TX_SEND_PING_RESP) {
|
||||
//Transmit ping response to master
|
||||
can_transmit(&ping_resp, portMAX_DELAY);
|
||||
ESP_LOGI(EXAMPLE_TAG, "Transmitted ping response");
|
||||
xSemaphoreGive(ctrl_task_sem);
|
||||
} else if (action == TX_SEND_DATA) {
|
||||
//Transmit data messages until stop command is received
|
||||
ESP_LOGI(EXAMPLE_TAG, "Start transmitting data");
|
||||
while (1) {
|
||||
//FreeRTOS tick count used to simulate sensor data
|
||||
uint32_t sensor_data = xTaskGetTickCount();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
data_message.data[i] = (sensor_data >> (i * 8)) & 0xFF;
|
||||
}
|
||||
can_transmit(&data_message, portMAX_DELAY);
|
||||
ESP_LOGI(EXAMPLE_TAG, "Transmitted data value %d", sensor_data);
|
||||
vTaskDelay(pdMS_TO_TICKS(DATA_PERIOD_MS));
|
||||
if (xSemaphoreTake(stop_data_sem, 0) == pdTRUE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (action == TX_SEND_STOP_RESP) {
|
||||
//Transmit stop response to master
|
||||
can_transmit(&stop_resp, portMAX_DELAY);
|
||||
ESP_LOGI(EXAMPLE_TAG, "Transmitted stop response");
|
||||
xSemaphoreGive(ctrl_task_sem);
|
||||
} else if (action == TX_TASK_EXIT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void can_control_task(void *arg)
|
||||
{
|
||||
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
|
||||
tx_task_action_t tx_action;
|
||||
rx_task_action_t rx_action;
|
||||
|
||||
for (int iter = 0; iter < NO_OF_ITERS; iter++) {
|
||||
ESP_ERROR_CHECK(can_start());
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver started");
|
||||
|
||||
//Listen of pings from master
|
||||
rx_action = RX_RECEIVE_PING;
|
||||
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
|
||||
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
|
||||
|
||||
//Send ping response
|
||||
tx_action = TX_SEND_PING_RESP;
|
||||
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
|
||||
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
|
||||
|
||||
//Listen for start command
|
||||
rx_action = RX_RECEIVE_START_CMD;
|
||||
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
|
||||
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
|
||||
|
||||
//Start sending data messages and listen for stop command
|
||||
tx_action = TX_SEND_DATA;
|
||||
rx_action = RX_RECEIVE_STOP_CMD;
|
||||
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
|
||||
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
|
||||
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
|
||||
|
||||
//Send stop response
|
||||
tx_action = TX_SEND_STOP_RESP;
|
||||
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
|
||||
xSemaphoreTake(ctrl_task_sem, portMAX_DELAY);
|
||||
|
||||
//Wait for bus to become free
|
||||
can_status_info_t status_info;
|
||||
can_get_status_info(&status_info);
|
||||
while (status_info.msgs_to_tx > 0) {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
can_get_status_info(&status_info);
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(can_stop());
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver stopped");
|
||||
vTaskDelay(pdMS_TO_TICKS(ITER_DELAY_MS));
|
||||
}
|
||||
|
||||
//Stop TX and RX tasks
|
||||
tx_action = TX_TASK_EXIT;
|
||||
rx_action = RX_TASK_EXIT;
|
||||
xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY);
|
||||
xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY);
|
||||
|
||||
//Delete Control task
|
||||
xSemaphoreGive(done_sem);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
//Add short delay to allow master it to initialize first
|
||||
for (int i = 3; i > 0; i--) {
|
||||
printf("Slave starting in %d\n", i);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
|
||||
//Create semaphores and tasks
|
||||
tx_task_queue = xQueueCreate(1, sizeof(tx_task_action_t));
|
||||
rx_task_queue = xQueueCreate(1, sizeof(rx_task_action_t));
|
||||
ctrl_task_sem = xSemaphoreCreateBinary();
|
||||
stop_data_sem = xSemaphoreCreateBinary();;
|
||||
done_sem = xSemaphoreCreateBinary();;
|
||||
xTaskCreatePinnedToCore(can_receive_task, "CAN_rx", 4096, NULL, RX_TASK_PRIO, NULL, tskNO_AFFINITY);
|
||||
xTaskCreatePinnedToCore(can_transmit_task, "CAN_tx", 4096, NULL, TX_TASK_PRIO, NULL, tskNO_AFFINITY);
|
||||
xTaskCreatePinnedToCore(can_control_task, "CAN_ctrl", 4096, NULL, CTRL_TSK_PRIO, NULL, tskNO_AFFINITY);
|
||||
|
||||
//Install CAN driver, trigger tasks to start
|
||||
ESP_ERROR_CHECK(can_driver_install(&g_config, &t_config, &f_config));
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver installed");
|
||||
|
||||
xSemaphoreGive(ctrl_task_sem); //Start Control task
|
||||
xSemaphoreTake(done_sem, portMAX_DELAY); //Wait for tasks to complete
|
||||
|
||||
//Uninstall CAN driver
|
||||
ESP_ERROR_CHECK(can_driver_uninstall());
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled");
|
||||
|
||||
//Cleanup
|
||||
vSemaphoreDelete(ctrl_task_sem);
|
||||
vSemaphoreDelete(stop_data_sem);
|
||||
vSemaphoreDelete(done_sem);
|
||||
vQueueDelete(tx_task_queue);
|
||||
vQueueDelete(rx_task_queue);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,77 @@
|
||||
#Need Python 3 string formatting functions
|
||||
from __future__ import print_function
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from threading import Thread
|
||||
# The test cause is dependent on the Tiny Test Framework. Ensure the
|
||||
# `TEST_FW_PATH` environment variable is set to `$IDF_PATH/tools/tiny-test-fw`
|
||||
test_fw_path = os.getenv("TEST_FW_PATH")
|
||||
if test_fw_path and test_fw_path not in sys.path:
|
||||
sys.path.insert(0, test_fw_path)
|
||||
import TinyFW
|
||||
import IDF
|
||||
|
||||
#Define tuple of strings to expect for each DUT.
|
||||
master_expect = ("CAN Master: Driver installed", "CAN Master: Driver uninstalled")
|
||||
slave_expect = ("CAN Slave: Driver installed", "CAN Slave: Driver uninstalled")
|
||||
listen_only_expect = ("CAN Listen Only: Driver installed", "Listen Only: Driver uninstalled")
|
||||
|
||||
def dut_thread_callback(**kwargs):
|
||||
#Parse keyword arguments
|
||||
dut = kwargs['dut'] #Get DUT from kwargs
|
||||
expected = kwargs['expected']
|
||||
result = kwargs['result'] #Get result[out] from kwargs. MUST be of mutable type e.g. list
|
||||
|
||||
#Must reset again as flashing during start_app will reset multiple times, causing unexpected results
|
||||
dut.reset()
|
||||
|
||||
for string in expected:
|
||||
dut.expect(string, 20)
|
||||
|
||||
#Mark thread has run to completion without any exceptions
|
||||
result[0] = True
|
||||
|
||||
@IDF.idf_example_test(env_tag='Example_CAN')
|
||||
def test_can_network_example(env, extra_data):
|
||||
|
||||
#Get device under test. "dut1", "dut2", and "dut3" must be properly defined in EnvConfig
|
||||
dut_master = env.get_dut("dut1", "examples/peripherals/can/can_network/can_network_master")
|
||||
dut_slave = env.get_dut("dut2", "examples/peripherals/can/can_network/can_network_slave")
|
||||
dut_listen_only = env.get_dut("dut3", "examples/peripherals/can/can_network/can_network_listen_only")
|
||||
|
||||
#Flash app onto each DUT, each DUT is reset again at the start of each thread
|
||||
dut_master.start_app()
|
||||
dut_slave.start_app()
|
||||
dut_listen_only.start_app()
|
||||
|
||||
#Create dict of keyword arguments for each dut
|
||||
results = [[False], [False], [False]]
|
||||
master_kwargs = {"dut" : dut_master, "result" : results[0], "expected" : master_expect}
|
||||
slave_kwargs = {"dut" : dut_slave, "result" : results[1], "expected" : slave_expect}
|
||||
listen_only_kwargs = {"dut" : dut_listen_only, "result" : results[2], "expected" : listen_only_expect}
|
||||
|
||||
#Create thread for each dut
|
||||
dut_master_thread = Thread(target = dut_thread_callback, name = "Master Thread", kwargs = master_kwargs)
|
||||
dut_slave_thread = Thread(target = dut_thread_callback, name = "Slave Thread", kwargs = slave_kwargs)
|
||||
dut_listen_only_thread = Thread(target = dut_thread_callback, name = "Listen Only Thread", kwargs = listen_only_kwargs)
|
||||
|
||||
#Start each thread
|
||||
dut_listen_only_thread.start()
|
||||
dut_master_thread.start()
|
||||
dut_slave_thread.start()
|
||||
|
||||
#Wait for threads to complete
|
||||
dut_listen_only_thread.join()
|
||||
dut_master_thread.join()
|
||||
dut_slave_thread.join()
|
||||
|
||||
#check each thread ran to completion
|
||||
for result in results:
|
||||
if result[0] != True:
|
||||
raise Exception("One or more threads did not run successfully")
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_can_network_example()
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := can_self_test_example
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# CAN Self Test Example
|
||||
|
||||
## Overview
|
||||
The CAN Self Test Example demonstrates the self testing capabilities of the
|
||||
ESP32 CAN peripheral and **only requires a single ESP32 module to run**.
|
||||
The Self Test Example can be used to verify that the wiring between the ESP32
|
||||
and an external transceiver operates correctly.
|
||||
|
||||
The CAN Self Test Example will do the following over multiple iterations:
|
||||
|
||||
1. Start the CAN driver
|
||||
2. Simultaneously transmit and receive messages using the self reception request.
|
||||
3. Stop the CAN driver
|
||||
|
||||
## External Transceiver and Pin Assignment
|
||||
The CAN controller in the ESP32 **does not contain an internal transceiver**.
|
||||
Therefore users are responsible for providing an external transceiver compatible
|
||||
with the physical layer specifications of their target ISO standard (such as
|
||||
SN65HVD23X transceivers for ISO 11898-2 compatibility)
|
||||
|
||||
The CAN controller in the ESP32 represents dominant bits to the transceiver as
|
||||
logic low, and recessive bits as logic high. The Self Test Example utilizes the
|
||||
following default pin assignments
|
||||
|
||||
* TX Pin is routed to GPIO21
|
||||
* RX Pin is routed to GPIO22
|
||||
|
||||
## Note
|
||||
If the Self Test Example does not receive any messages, it is likely that the
|
||||
wiring between the ESP32 and the external transceiver is incorrect. To verify
|
||||
that the CAN controller in the ESP32 is operating correctly, users can bypass
|
||||
the external transceiver by connecting the TX Pin directly to the RX Pin when
|
||||
running the Self Test Example.
|
||||
@@ -0,0 +1,29 @@
|
||||
#Need Python 3 string formatting functions
|
||||
from __future__ import print_function
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
# The test cause is dependent on the Tiny Test Framework. Ensure the
|
||||
# `TEST_FW_PATH` environment variable is set to `$IDF_PATH/tools/tiny-test-fw`
|
||||
test_fw_path = os.getenv("TEST_FW_PATH")
|
||||
if test_fw_path and test_fw_path not in sys.path:
|
||||
sys.path.insert(0, test_fw_path)
|
||||
import TinyFW
|
||||
import IDF
|
||||
|
||||
# CAN Self Test Example constants
|
||||
STR_EXPECT = ("CAN Self Test: Driver installed", "CAN Self Test: Driver uninstalled")
|
||||
EXPECT_TIMEOUT = 20
|
||||
|
||||
@IDF.idf_example_test(env_tag='Example_CAN')
|
||||
def test_can_self_test_example(env, extra_data):
|
||||
#Get device under test, flash and start example. "dut4" must be defined in EnvConfig
|
||||
dut = env.get_dut('dut4', 'examples/peripherals/can/can_self_test')
|
||||
dut.start_app()
|
||||
|
||||
for string in STR_EXPECT:
|
||||
dut.expect(string, timeout = EXPECT_TIMEOUT)
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_can_self_test_example()
|
||||
@@ -0,0 +1,141 @@
|
||||
/* CAN Self Test Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The following example demonstrates the self testing capabilities of the CAN
|
||||
* peripheral by utilizing the No Acknowledgment Mode and Self Reception Request
|
||||
* capabilities. This example can be used to verify that the CAN peripheral and
|
||||
* its connections to the external transceiver operates without issue. The example
|
||||
* will execute multiple iterations, each iteration will do the following:
|
||||
* 1) Start the CAN driver
|
||||
* 2) Transmit and receive 100 messages using self reception request
|
||||
* 3) Stop the CAN driver
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/can.h"
|
||||
|
||||
/* --------------------- Definitions and static variables ------------------ */
|
||||
|
||||
//Example Configurations
|
||||
#define NO_OF_MSGS 100
|
||||
#define NO_OF_ITERS 3
|
||||
#define TX_GPIO_NUM 21
|
||||
#define RX_GPIO_NUM 22
|
||||
#define TX_TASK_PRIO 8 //Sending task priority
|
||||
#define RX_TASK_PRIO 9 //Receiving task priority
|
||||
#define CTRL_TSK_PRIO 10 //Control task priority
|
||||
#define MSG_ID 0x555 //11 bit standard format ID
|
||||
#define EXAMPLE_TAG "CAN Self Test"
|
||||
|
||||
static const can_timing_config_t t_config = CAN_TIMING_CONFIG_25KBITS();
|
||||
//Filter all other IDs except MSG_ID
|
||||
static const can_filter_config_t f_config = {.acceptance_code = (MSG_ID << 21),
|
||||
.acceptance_mask = ~(CAN_STD_ID_MASK << 21),
|
||||
.single_filter = true};
|
||||
//Set to NO_ACK mode due to self testing with single module
|
||||
static const can_general_config_t g_config = CAN_GENERAL_CONFIG_DEFAULT(TX_GPIO_NUM, RX_GPIO_NUM, CAN_MODE_NO_ACK);
|
||||
|
||||
static SemaphoreHandle_t tx_sem;
|
||||
static SemaphoreHandle_t rx_sem;
|
||||
static SemaphoreHandle_t ctrl_sem;
|
||||
static SemaphoreHandle_t done_sem;
|
||||
|
||||
/* --------------------------- Tasks and Functions -------------------------- */
|
||||
|
||||
static void can_transmit_task(void *arg)
|
||||
{
|
||||
can_message_t tx_msg = {.data_length_code = 1, .identifier = MSG_ID, .flags = CAN_MSG_FLAG_SELF};
|
||||
for (int iter = 0; iter < NO_OF_ITERS; iter++) {
|
||||
xSemaphoreTake(tx_sem, portMAX_DELAY);
|
||||
for (int i = 0; i < NO_OF_MSGS; i++) {
|
||||
//Transmit messages using self reception request
|
||||
tx_msg.data[0] = i;
|
||||
ESP_ERROR_CHECK(can_transmit(&tx_msg, portMAX_DELAY));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void can_receive_task(void *arg)
|
||||
{
|
||||
can_message_t rx_message;
|
||||
for (int iter = 0; iter < NO_OF_ITERS; iter++) {
|
||||
xSemaphoreTake(rx_sem, portMAX_DELAY);
|
||||
for (int i = 0; i < NO_OF_MSGS; i++) {
|
||||
//Receive message and print message data
|
||||
ESP_ERROR_CHECK(can_receive(&rx_message, portMAX_DELAY))
|
||||
ESP_LOGI(EXAMPLE_TAG, "Msg received - Data = %d", rx_message.data[0]);
|
||||
}
|
||||
//Indicate to control task all messages received for this iteration
|
||||
xSemaphoreGive(ctrl_sem);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void can_control_task(void *arg)
|
||||
{
|
||||
xSemaphoreTake(ctrl_sem, portMAX_DELAY);
|
||||
for (int iter = 0; iter < NO_OF_ITERS; iter++) {
|
||||
//Start CAN Driver for this iteration
|
||||
ESP_ERROR_CHECK(can_start());
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver started");
|
||||
|
||||
//Trigger TX and RX tasks to start transmitting/receiving
|
||||
xSemaphoreGive(rx_sem);
|
||||
xSemaphoreGive(tx_sem);
|
||||
xSemaphoreTake(ctrl_sem, portMAX_DELAY); //Wait for TX and RX tasks to finish iteration
|
||||
|
||||
ESP_ERROR_CHECK(can_stop()); //Stop the CAN Driver
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver stopped");
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); //Delay then start next iteration
|
||||
}
|
||||
xSemaphoreGive(done_sem);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
//Create tasks and synchronization primitives
|
||||
tx_sem = xSemaphoreCreateBinary();
|
||||
rx_sem = xSemaphoreCreateBinary();
|
||||
ctrl_sem = xSemaphoreCreateBinary();
|
||||
done_sem = xSemaphoreCreateBinary();
|
||||
|
||||
xTaskCreatePinnedToCore(can_control_task, "CAN_ctrl", 4096, NULL, CTRL_TSK_PRIO, NULL, tskNO_AFFINITY);
|
||||
xTaskCreatePinnedToCore(can_receive_task, "CAN_rx", 4096, NULL, RX_TASK_PRIO, NULL, tskNO_AFFINITY);
|
||||
xTaskCreatePinnedToCore(can_transmit_task, "CAN_tx", 4096, NULL, TX_TASK_PRIO, NULL, tskNO_AFFINITY);
|
||||
|
||||
//Install CAN driver
|
||||
ESP_ERROR_CHECK(can_driver_install(&g_config, & t_config, &f_config));
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver installed");
|
||||
|
||||
//Start control task
|
||||
xSemaphoreGive(ctrl_sem);
|
||||
//Wait for all iterations and tasks to complete running
|
||||
xSemaphoreTake(done_sem, portMAX_DELAY);
|
||||
|
||||
//Uninstall CAN driver
|
||||
ESP_ERROR_CHECK(can_driver_uninstall());
|
||||
ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled");
|
||||
|
||||
//Cleanup
|
||||
vSemaphoreDelete(tx_sem);
|
||||
vSemaphoreDelete(rx_sem);
|
||||
vSemaphoreDelete(ctrl_sem);
|
||||
vQueueDelete(done_sem);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
Reference in New Issue
Block a user