编程技术分享

分享编程知识,探讨技术创新

0%

简介:基于STM32+移远EC20的CAN网络车载终端,支持自定义配置CAN数据解码脚本;可通过CAN、OTA 等方式升级固件;具备离线数据存储、GPS车辆定位、MQTT通信功能,并接入阿里云物联网平台。

基于STM32+EC20的CAN网络车载终端代码设计架构及C代码实现

关注微信公众号,提前获取相关推文

项目简介

本项目旨在开发一款基于STM32微控制器和移远EC20 4G模组的车载终端,实现车辆数据的采集、处理、传输和远程管理。该终端通过CAN总线与车辆网络通信,获取车辆行驶数据、故障信息等;利用GPS模块进行车辆定位;通过EC20模组实现4G网络连接,支持MQTT协议接入阿里云物联网平台,进行数据上报和远程控制;同时具备本地数据存储功能,在网络异常时能够缓存数据;支持OTA远程固件升级,方便维护和功能扩展;最重要的是,系统支持自定义配置CAN数据解码脚本,满足不同车辆和应用场景的需求。

系统架构设计

为了构建一个可靠、高效、可扩展的车载终端系统,我们采用分层架构设计,将系统划分为以下几个层次:

  1. **硬件抽象层 (HAL, Hardware Abstraction Layer)**:

    • 目的:屏蔽底层硬件差异,提供统一的硬件访问接口,提高代码的可移植性。
    • 内容:定义底层硬件外设(GPIO、UART、SPI、I2C、CAN、Timer、ADC、Flash、SDIO等)的通用接口函数,例如 HAL_GPIO_Init(), HAL_UART_Send(), HAL_CAN_Receive() 等。
    • 实现:针对STM32的具体硬件平台,实现这些接口函数,例如使用STM32 HAL库或者直接操作寄存器。
  2. **板级支持包 (BSP, Board Support Package)**:

    • 目的:初始化硬件平台,配置时钟、外设、中断等,为上层提供运行环境。
    • 内容:系统时钟初始化、GPIO端口配置、外设模块初始化(CAN、UART、SPI、I2C、GPS、EC20、SD卡等)、中断向量表配置、电源管理等。
    • 实现:基于具体的硬件电路设计,完成硬件初始化配置。
  3. **驱动层 (Drivers)**:

    • 目的:封装具体的硬件模块操作,提供高层次的API接口,简化上层应用开发。
    • 内容
      • CAN驱动:初始化CAN控制器,配置CAN过滤器,实现CAN数据发送和接收功能,处理CAN错误。
      • GPS驱动:初始化GPS模块串口,解析NMEA-0183协议数据,提取经纬度、速度、时间等信息。
      • EC20驱动:初始化EC20模组串口,实现AT指令交互,封装网络连接、数据收发、GPS定位、短信等功能。
      • SD卡驱动:初始化SD卡接口,实现SD卡文件系统的读写操作,用于数据存储和固件升级。
      • Flash驱动:实现内部Flash的读写操作,用于存储配置信息、OTA升级固件。
      • Timer驱动:提供定时器功能,用于系统定时任务、周期性数据采集等。
      • GPIO驱动:控制LED指示灯、电源开关等GPIO设备。
    • 实现:针对每个硬件模块,编写相应的驱动程序,提供易于使用的API接口。
  4. **中间件层 (Middleware)**:

    • 目的:提供通用的软件组件和服务,简化应用开发,提高代码复用率。
    • 内容
      • CAN数据解码模块:加载和解析自定义CAN解码脚本,将原始CAN数据解析为结构化的车辆数据。
      • MQTT客户端:实现MQTT协议,连接阿里云物联网平台,进行数据上报和指令接收。
      • OTA升级模块:实现OTA固件升级功能,支持从服务器下载固件并更新系统。
      • GPS数据处理模块:对GPS数据进行解析和处理,提取关键信息,例如经纬度、速度、方向等。
      • 数据存储模块:实现离线数据存储功能,将采集的数据缓存到SD卡,并在网络恢复后上传。
      • 配置管理模块:加载和管理系统配置信息,例如CAN解码脚本、MQTT连接参数、服务器地址等。
      • 日志管理模块:记录系统运行日志,方便调试和故障排查。
      • 看门狗模块:实现硬件或软件看门狗,提高系统稳定性。
    • 实现:根据具体需求,选择或自行开发相应的中间件模块。
  5. **应用层 (Application Layer)**:

    • 目的:实现系统的核心业务逻辑,协调各个模块,完成最终的应用功能。
    • 内容
      • 主循环:系统的主程序循环,负责任务调度、事件处理、数据采集、数据处理、数据上传等。
      • CAN数据采集任务:周期性读取CAN数据,并调用CAN数据解码模块进行解析。
      • GPS数据采集任务:周期性读取GPS数据,并调用GPS数据处理模块进行处理。
      • 数据上传任务:将采集到的车辆数据、GPS数据等通过MQTT协议上传到阿里云物联网平台。
      • OTA升级任务:接收OTA升级指令,启动OTA升级过程。
      • 配置加载任务:在系统启动时加载配置信息,包括CAN解码脚本等。
      • 指令处理任务:接收阿里云物联网平台下发的指令,并执行相应的操作。
      • 错误处理:处理系统运行过程中出现的错误,例如CAN通信错误、GPS数据解析错误、网络连接错误等。
    • 实现:基于以上各层提供的接口和模块,编写应用程序代码,实现车载终端的各项功能。

代码实现 (C语言)

为了展示清晰的代码结构和关键功能实现,以下代码将以模块化的方式呈现,并附带详细注释。由于代码量庞大,以下代码仅为核心模块的示例代码,并非完整的3000行代码。实际项目中,需要根据需求完善各个模块的功能,并进行详细的测试和优化。

1. HAL层 (示例,基于STM32 HAL库)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// hal_gpio.h
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

#include "stm32f4xx_hal.h" // 假设使用STM32F4系列

typedef enum {
GPIO_PIN_RESET = 0,
GPIO_PIN_SET
} HAL_GPIO_PinStateTypeDef;

typedef enum {
GPIO_MODE_INPUT = 0x00,
GPIO_MODE_OUTPUT_PP,
GPIO_MODE_OUTPUT_OD,
GPIO_MODE_AF_PP,
GPIO_MODE_AF_OD,
GPIO_MODE_ANALOG,
GPIO_MODE_IT_RISING,
GPIO_MODE_IT_FALLING,
GPIO_MODE_IT_RISING_FALLING
} HAL_GPIO_ModeTypeDef;

typedef enum {
GPIO_PULL_NO_PULL = 0x00,
GPIO_PULLUP,
GPIO_PULLDOWN
} HAL_GPIO_PullTypeDef;

typedef struct {
GPIO_TypeDef* port;
uint16_t pin;
HAL_GPIO_ModeTypeDef mode;
HAL_GPIO_PullTypeDef pull;
uint16_t speed; // 可选,例如 GPIO_SPEED_FREQ_LOW, GPIO_SPEED_FREQ_MEDIUM, GPIO_SPEED_FREQ_HIGH, GPIO_SPEED_FREQ_VERY_HIGH
} HAL_GPIO_InitTypeDef;

void HAL_GPIO_Init(HAL_GPIO_InitTypeDef* GPIO_InitStruct);
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, HAL_GPIO_PinStateTypeDef PinState);
HAL_GPIO_PinStateTypeDef HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

#endif // HAL_GPIO_H

// hal_gpio.c
#include "hal_gpio.h"

void HAL_GPIO_Init(HAL_GPIO_InitTypeDef* GPIO_InitStruct) {
GPIO_InitTypeDef GPIO_Init;
GPIO_Init.Pin = GPIO_InitStruct->pin;
GPIO_Init.Mode = GPIO_InitStruct->mode;
GPIO_Init.Pull = GPIO_InitStruct->pull;
// GPIO_Init.Speed = GPIO_InitStruct->speed; // 如果需要配置速度
HAL_GPIO_Init(GPIO_InitStruct->port, &GPIO_Init); // 调用STM32 HAL库函数
}

void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, HAL_GPIO_PinStateTypeDef PinState) {
HAL_GPIO_WritePin(GPIOx, GPIO_Pin, (GPIO_PinState)PinState); // 调用STM32 HAL库函数
}

HAL_GPIO_PinStateTypeDef HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) {
return (HAL_GPIO_PinStateTypeDef)HAL_GPIO_ReadPin(GPIOx, GPIO_Pin); // 调用STM32 HAL库函数
}

2. BSP层 (示例,部分初始化代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// bsp.h
#ifndef BSP_H
#define BSP_H

void BSP_Init(void);
void SystemClock_Config(void);
void Error_Handler(void);

#endif // BSP_H

// bsp.c
#include "bsp.h"
#include "hal_gpio.h" // 使用HAL层GPIO
#include "hal_uart.h" // 使用HAL层UART
#include "hal_can.h" // 使用HAL层CAN
// ... 其他HAL头文件

void BSP_Init(void) {
SystemClock_Config(); // 配置系统时钟
HAL_Init(); // 初始化STM32 HAL库

// 初始化GPIO
HAL_GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.port = LED_GPIO_PORT; // 假设LED连接到 LED_GPIO_PORT
GPIO_InitStruct.pin = LED_PIN; // 假设LED连接到 LED_PIN
GPIO_InitStruct.mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.pull = GPIO_PULL_NO_PULL;
HAL_GPIO_Init(&GPIO_InitStruct);

// 初始化UART (用于GPS和EC20)
HAL_UART_InitTypeDef UART_InitStruct;
UART_InitStruct.baudrate = 9600; // GPS默认波特率
UART_InitStruct.databits = UART_WORDLENGTH_8B;
UART_InitStruct.parity = UART_PARITY_NONE;
UART_InitStruct.stopbits = UART_STOPBITS_1;
HAL_UART_Init(GPS_UART_PORT, &UART_InitStruct); // 假设GPS使用 GPS_UART_PORT

UART_InitStruct.baudrate = 115200; // EC20默认波特率
HAL_UART_Init(EC20_UART_PORT, &UART_InitStruct); // 假设EC20使用 EC20_UART_PORT

// 初始化CAN
HAL_CAN_InitTypeDef CAN_InitStruct;
CAN_InitStruct.baudrate = 500000; // 假设CAN总线波特率 500kbps
CAN_InitStruct.mode = CAN_MODE_NORMAL;
// ... 其他CAN配置,例如过滤器
HAL_CAN_Init(CAN_PORT, &CAN_InitStruct); // 假设CAN使用 CAN_PORT
HAL_CAN_Start(CAN_PORT); // 启动CAN

// ... 初始化其他外设 (SPI, I2C, SD卡, Flash, Timer, GPS, EC20等)
}

// 系统时钟配置函数 (示例,根据实际硬件配置修改)
void SystemClock_Config(void) {
// ... 配置RCC时钟树,例如使用HSI或HSE作为时钟源,配置PLL倍频分频
}

void Error_Handler(void) {
// ... 错误处理函数,例如打印错误信息,重启系统,进入安全模式等
}

3. 驱动层 (示例 - CAN驱动)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// can_driver.h
#ifndef CAN_DRIVER_H
#define CAN_DRIVER_H

#include "stdint.h"
#include "stdbool.h"

#define CAN_MAX_DATA_LEN 8 // CAN数据最大长度

typedef struct {
uint32_t id; // CAN ID
uint8_t data[CAN_MAX_DATA_LEN]; // CAN数据
uint8_t len; // 数据长度
} CAN_FrameTypeDef;

typedef void (*CAN_RxCallback)(CAN_FrameTypeDef *frame); // CAN接收回调函数类型

bool CAN_Driver_Init(uint32_t baudrate);
bool CAN_Driver_SendFrame(CAN_FrameTypeDef *frame);
bool CAN_Driver_RegisterRxCallback(CAN_RxCallback callback);
void CAN_Driver_Process(void); // CAN驱动处理函数,例如轮询接收

#endif // CAN_DRIVER_H

// can_driver.c
#include "can_driver.h"
#include "hal_can.h" // 使用HAL层CAN

static CAN_RxCallback can_rx_callback = NULL; // CAN接收回调函数指针

bool CAN_Driver_Init(uint32_t baudrate) {
HAL_CAN_InitTypeDef CAN_InitStruct;
CAN_InitStruct.baudrate = baudrate;
CAN_InitStruct.mode = CAN_MODE_NORMAL;
// ... 配置CAN过滤器,例如接收所有ID
if (HAL_CAN_Init(CAN_PORT, &CAN_InitStruct) != HAL_OK) {
return false; // 初始化失败
}
HAL_CAN_Start(CAN_PORT); // 启动CAN
return true;
}

bool CAN_Driver_SendFrame(CAN_FrameTypeDef *frame) {
HAL_CAN_TxHeaderTypeDef TxHeader;
TxHeader.StdId = frame->id;
TxHeader.IDE = CAN_ID_STD;
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.DLC = frame->len;

uint32_t TxMailbox;
if (HAL_CAN_AddTxMessage(CAN_PORT, &TxHeader, frame->data, &TxMailbox) != HAL_OK) {
return false; // 发送失败
}
return true;
}

bool CAN_Driver_RegisterRxCallback(CAN_RxCallback callback) {
if (callback != NULL) {
can_rx_callback = callback;
return true;
}
return false;
}

void CAN_Driver_Process(void) {
HAL_CAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[CAN_MAX_DATA_LEN];

if (HAL_CAN_GetRxFifoFillLevel(CAN_PORT, CAN_FIFO0) > 0) { // FIFO0有数据
if (HAL_CAN_GetRxMessage(CAN_PORT, CAN_FIFO0, &RxHeader, RxData) == HAL_OK) {
CAN_FrameTypeDef frame;
frame.id = RxHeader.StdId;
frame.len = RxHeader.DLC;
for (int i = 0; i < frame.len; i++) {
frame.data[i] = RxData[i];
}
if (can_rx_callback != NULL) {
can_rx_callback(&frame); // 调用注册的回调函数
}
}
}
// ... 可以添加FIFO1的处理,或者错误处理
}

4. 中间件层 (示例 - CAN数据解码模块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// can_decoder.h
#ifndef CAN_DECODER_H
#define CAN_DECODER_H

#include "stdint.h"
#include "stdbool.h"
#include "can_driver.h"

// 假设定义车辆数据结构
typedef struct {
float speed; // 车速 (km/h)
float engine_speed; // 发动机转速 (RPM)
float coolant_temp; // 冷却液温度 (°C)
// ... 其他车辆数据
} VehicleDataTypeDef;

bool CAN_Decoder_LoadScript(const char *script_path); // 加载CAN解码脚本
bool CAN_Decoder_ParseFrame(CAN_FrameTypeDef *frame, VehicleDataTypeDef *vehicle_data); // 解析CAN帧

#endif // CAN_DECODER_H

// can_decoder.c
#include "can_decoder.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// 简单的CAN解码脚本格式示例:
// # CAN ID, Start Byte, Bit Length, Scale, Offset, Data Field Name
// 0x100, 0, 16, 0.1, 0, speed
// 0x101, 2, 16, 1, 0, engine_speed
// 0x102, 4, 8, 1, -40, coolant_temp

typedef struct {
uint32_t can_id;
uint8_t start_byte;
uint8_t bit_length;
float scale;
float offset;
char data_field_name[32];
} CAN_DecodeRuleTypeDef;

#define MAX_DECODE_RULES 10 // 最大解码规则数量
static CAN_DecodeRuleTypeDef decode_rules[MAX_DECODE_RULES];
static uint8_t rule_count = 0;

bool CAN_Decoder_LoadScript(const char *script_path) {
FILE *fp = fopen(script_path, "r");
if (fp == NULL) {
perror("Error opening script file");
return false;
}

char line[256];
rule_count = 0; // 重置规则计数

while (fgets(line, sizeof(line), fp) != NULL && rule_count < MAX_DECODE_RULES) {
if (line[0] == '#' || line[0] == '\n') continue; // 跳过注释和空行

CAN_DecodeRuleTypeDef *rule = &decode_rules[rule_count];
if (sscanf(line, "%x,%u,%u,%f,%f,%s",
&rule->can_id,
&rule->start_byte,
&rule->bit_length,
&rule->scale,
&rule->offset,
rule->data_field_name) == 6) {
rule_count++;
} else {
fprintf(stderr, "Error parsing line: %s", line);
}
}
fclose(fp);
printf("Loaded %u CAN decode rules.\n", rule_count);
return true;
}

bool CAN_Decoder_ParseFrame(CAN_FrameTypeDef *frame, VehicleDataTypeDef *vehicle_data) {
for (int i = 0; i < rule_count; i++) {
CAN_DecodeRuleTypeDef *rule = &decode_rules[i];
if (frame->id == rule->can_id) {
// 找到匹配的CAN ID
uint32_t raw_value = 0;
uint8_t byte_index = rule->start_byte;
uint8_t bit_offset = 0; // 假设从字节的最低位开始

for (int bit_index = 0; bit_index < rule->bit_length; bit_index++) {
uint8_t bit = (frame->data[byte_index] >> (bit_offset + bit_index)) & 0x01; // 从CAN帧数据中提取位
raw_value |= (bit << bit_index);
if ((bit_offset + bit_index + 1) % 8 == 0) { // 跨字节
byte_index++;
bit_offset = 0;
}
}

float decoded_value = (float)raw_value * rule->scale + rule->offset;

if (strcmp(rule->data_field_name, "speed") == 0) {
vehicle_data->speed = decoded_value;
} else if (strcmp(rule->data_field_name, "engine_speed") == 0) {
vehicle_data->engine_speed = decoded_value;
} else if (strcmp(rule->data_field_name, "coolant_temp") == 0) {
vehicle_data->coolant_temp = decoded_value;
}
// ... 处理其他数据字段

return true; // 成功解析至少一个数据字段
}
}
return false; // 没有找到匹配的CAN ID
}

5. 中间件层 (示例 - MQTT客户端)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// mqtt_client.h
#ifndef MQTT_CLIENT_H
#define MQTT_CLIENT_H

#include "stdint.h"
#include "stdbool.h"

typedef struct {
char *host;
uint16_t port;
char *client_id;
char *username;
char *password;
} MQTT_ConnectInfoTypeDef;

bool MQTT_Client_Init(MQTT_ConnectInfoTypeDef *connect_info);
bool MQTT_Client_Connect(void);
bool MQTT_Client_Publish(const char *topic, const char *payload);
bool MQTT_Client_Subscribe(const char *topic);
void MQTT_Client_Process(void); // MQTT客户端处理函数,例如处理网络事件

#endif // MQTT_CLIENT_H

// mqtt_client.c
#include "mqtt_client.h"
#include <stdio.h>
#include <string.h>
// ... 需要集成一个MQTT客户端库,例如 Paho MQTT embedded C client 或者自己实现一个简易的MQTT客户端

bool MQTT_Client_Init(MQTT_ConnectInfoTypeDef *connect_info) {
// ... 初始化网络连接,例如socket
// ... 保存连接信息
printf("MQTT Client Initialized.\n");
return true;
}

bool MQTT_Client_Connect(void) {
// ... 建立MQTT连接,发送CONNECT报文
printf("MQTT Client Connected.\n");
return true;
}

bool MQTT_Client_Publish(const char *topic, const char *payload) {
// ... 构建PUBLISH报文,发送到MQTT服务器
printf("MQTT Client Published to topic: %s, payload: %s\n", topic, payload);
return true;
}

bool MQTT_Client_Subscribe(const char *topic) {
// ... 构建SUBSCRIBE报文,订阅主题
printf("MQTT Client Subscribed to topic: %s\n", topic);
return true;
}

void MQTT_Client_Process(void) {
// ... 处理网络事件,例如接收MQTT报文,心跳保活等
// ... 轮询网络状态,处理连接断开重连等
}

6. 应用层 (示例 - 主循环)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// main.c
#include "bsp.h"
#include "can_driver.h"
#include "can_decoder.h"
#include "mqtt_client.h"
#include "gps_driver.h" // 假设有GPS驱动
#include "data_storage.h" // 假设有数据存储模块
#include "ota_update.h" // 假设有OTA升级模块
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "delay.h" // 假设有延时函数

// ... 定义全局变量,例如车辆数据、GPS数据等

VehicleDataTypeDef vehicle_data;
GPS_DataTypeDef gps_data;

void CAN_RxCallback_App(CAN_FrameTypeDef *frame) {
CAN_Decoder_ParseFrame(frame, &vehicle_data); // 解析CAN帧
// ... 可以根据解析后的车辆数据进行其他处理,例如存储、上传
}

int main(void) {
BSP_Init(); // 初始化硬件和外设
delay_init(168); // 初始化延时函数,假设系统时钟为168MHz

// 初始化CAN驱动
if (!CAN_Driver_Init(500000)) {
Error_Handler(); // CAN初始化失败
}
CAN_Driver_RegisterRxCallback(CAN_RxCallback_App); // 注册CAN接收回调函数

// 加载CAN解码脚本
if (!CAN_Decoder_LoadScript("can_decode_script.txt")) {
Error_Handler(); // 加载CAN解码脚本失败
}

// 初始化GPS驱动
if (!GPS_Driver_Init()) {
Error_Handler(); // GPS初始化失败
}

// 初始化MQTT客户端
MQTT_ConnectInfoTypeDef mqtt_info = {
.host = "your_aliyun_iot_host",
.port = 1883,
.client_id = "your_device_id",
.username = "your_device_name",
.password = "your_device_secret"
};
if (!MQTT_Client_Init(&mqtt_info)) {
Error_Handler(); // MQTT客户端初始化失败
}
if (!MQTT_Client_Connect()) {
Error_Handler(); // MQTT连接失败
}

// ... 初始化数据存储模块、OTA升级模块等

printf("System Initialized.\n");

while (1) {
CAN_Driver_Process(); // 处理CAN接收
GPS_Driver_Process(); // 处理GPS数据接收

// 定时任务 (例如每秒执行一次)
static uint32_t last_tick = 0;
if (HAL_GetTick() - last_tick >= 1000) {
last_tick = HAL_GetTick();

GPS_Driver_GetLocation(&gps_data); // 获取GPS位置信息

// 构建JSON payload (示例)
char payload_buffer[256];
snprintf(payload_buffer, sizeof(payload_buffer),
"{\"speed\":%.2f, \"engine_speed\":%.2f, \"coolant_temp\":%.2f, \"latitude\":%.6f, \"longitude\":%.6f}",
vehicle_data.speed, vehicle_data.engine_speed, vehicle_data.coolant_temp,
gps_data.latitude, gps_data.longitude);

MQTT_Client_Publish("/topic/vehicle_data", payload_buffer); // 发布数据到阿里云

// ... 数据存储逻辑 (例如每隔一段时间存储数据)
// ... OTA升级任务检查 (例如检查是否有新的OTA升级指令)

HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_PIN); // LED指示灯闪烁
}

MQTT_Client_Process(); // 处理MQTT客户端事件
// ... 其他系统任务处理
delay_ms(10); // 适当延时,降低CPU占用
}
}

项目中采用的各种技术和方法

  1. 分层架构:采用HAL、BSP、驱动层、中间件层、应用层的分层架构,提高代码的模块化、可维护性和可移植性。
  2. 模块化设计:将系统功能划分为独立的模块,例如CAN驱动、GPS驱动、MQTT客户端、OTA升级模块等,每个模块负责特定的功能,降低系统复杂度,方便开发和测试。
  3. 事件驱动:通过CAN接收回调函数、GPS数据接收事件等方式,实现事件驱动的编程模型,提高系统的实时性和响应速度。
  4. 自定义CAN解码脚本:采用文本文件存储CAN解码规则,支持用户自定义配置,提高了系统的灵活性和适用性。
  5. MQTT协议:使用轻量级的MQTT协议进行数据传输,适用于物联网应用场景,降低网络带宽占用和设备功耗。
  6. 阿里云物联网平台:接入阿里云物联网平台,利用云平台的强大功能,实现设备管理、数据存储、远程监控等功能。
  7. OTA远程固件升级:支持OTA远程固件升级,方便设备维护和功能扩展,降低维护成本。
  8. 离线数据存储:具备本地数据存储功能,在网络异常时能够缓存数据,保证数据的完整性。
  9. GPS车辆定位:集成GPS模块,实现车辆的实时定位,提供位置信息服务。
  10. C语言编程:采用C语言进行开发,充分利用C语言的效率和灵活性,适用于嵌入式系统开发。
  11. STM32 HAL库:使用STM32 HAL库简化硬件操作,提高开发效率,同时保证代码的可移植性。
  12. 版本控制:使用Git等版本控制工具进行代码管理,方便团队协作和代码维护。
  13. 代码注释和文档:编写详细的代码注释和文档,提高代码的可读性和可维护性。
  14. 单元测试和集成测试:进行单元测试和集成测试,保证代码的质量和系统的稳定性。
  15. 实践验证:项目中采用的各种技术和方法都是经过实践验证的,例如STM32 HAL库、MQTT协议、OTA升级技术等,保证系统的可靠性和稳定性。

测试验证和维护升级

  • 测试验证
    • 单元测试:针对每个模块进行单元测试,例如CAN驱动、GPS驱动、MQTT客户端等,验证模块的功能是否正确。
    • 集成测试:将各个模块集成起来进行集成测试,验证模块之间的协同工作是否正常。
    • 系统测试:进行系统级的测试,模拟实际应用场景,验证系统的整体功能和性能是否满足需求。
    • 稳定性测试:进行长时间的稳定性测试,验证系统在长时间运行下的可靠性。
    • CAN总线测试:使用CAN总线分析仪等工具,测试CAN通信的正确性和稳定性。
    • 网络通信测试:测试MQTT通信的连接、数据传输、订阅等功能是否正常。
    • OTA升级测试:模拟OTA升级过程,验证OTA升级功能的正确性和可靠性。
    • GPS定位测试:测试GPS定位的精度和稳定性。
    • 功耗测试:测试系统的功耗,优化功耗设计。
  • 维护升级
    • OTA远程固件升级:通过OTA升级功能,远程更新固件,修复Bug,增加新功能。
    • 日志管理:通过日志管理模块,记录系统运行日志,方便故障排查和维护。
    • 模块化设计:模块化的设计使得系统易于维护和升级,可以单独修改和升级某个模块,而不会影响其他模块。
    • 版本控制:使用版本控制工具,方便代码维护和版本管理。
    • 远程监控:通过阿里云物联网平台,可以远程监控设备状态,及时发现和解决问题。

总结

本方案基于STM32+EC20平台,采用分层架构和模块化设计,构建了一个可靠、高效、可扩展的车载终端系统。通过实践验证的技术和方法,实现了CAN数据采集、GPS定位、MQTT通信、OTA升级、离线数据存储等核心功能,并接入阿里云物联网平台,满足了车载终端的应用需求。 代码示例虽然不完整,但展示了关键模块的设计思路和实现方式,为实际项目开发提供了参考。 实际项目中需要根据具体需求进行代码完善、优化和详细测试,最终打造一个稳定可靠的车载终端产品。

欢迎关注我的其它发布渠道