1.方面

  • 模块:

    • 2G 通信:A9G 模块

    • 4G 通信:移远 EC20,合宙 Air724UG

    • 主控:勘智 K210,stm32F103C8T6,stm32F413RCT7(后期)

    • 采集:SH367309,GPS 模块

  • 外设:

    • 通信:UART,CAN,RS485,SPI,IIC

    • 传感器:温度传感器,重力传感器

    • 显示:串口屏,SPI 屏

    • 声音:喇叭

  • 协议:

    • 换电柜通讯协议
    • CAN 通讯协议
    • 换电网络通讯协议

2.历程

  • 2019~2020:前期研发

    • 3月~6月:换电柜整体设计,主板硬件设计,控制逻辑,初版定型,软件研发;
    • 6月~9月:BMS 协议制定,协议测试,换电柜协议制定,平台设计与开发;
    • 9月~12月:协议修订测试,OTA 测试,BMS 测试。
  • 2020~2021:产品迭代

    • 1月~3月:座式充电器设计开发,BMS 软件重新开发,BMS 板硬件重新设计(人员变更);
    • 3月~6月:功能设计,业务规划,服务器端软件研发,微信端软件开发,测试,产品发布会;
    • 6月~9月:工厂生产,产品装配,功能测试;
    • 9月~12月:落地银川、运城,解决市场端问题,业务调整,软硬件更新。
  • 2021~至今:产品迭代

    • 1月~3月:市场端问题修复,新版本软硬件设计,寻求融资。

3.图画

项目图.png


4.程序

开发感想:

没有费脑的算法,或者特别难理解的协议或驱动实现;
麻烦的地方在于业务和逻辑的实现,比较绕人,需要清晰的业务设计和流程梳理;
其中最关键的在于前期设计,是否完善、合理,以及可扩展性。
明白自己在做什么很重要。

个人成长:

嵌入式开发 GET!
代码能力得到锻炼,但还需加强。
从前到后。

1.bootloader

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
#define FLASH_SECTOR_NUM    	 (64)
#define FLASH_SECTOR_SIZE (1024)

//loader 起始地址 //loader 大小 4k
#define FLASH_LOADER_BASE ((uint32_t)0x8000000)
#define FLASH_LOADER_SIZE (5 * FLASH_SECTOR_SIZE)

//app 起始地址 //app 大小25K
#define FLASH_APP_BASE (FLASH_LOADER_BASE+FLASH_LOADER_SIZE)
#define FLASH_APP_SIZE (25 * FLASH_SECTOR_SIZE)

//地址跳转
typedef void (*funcptr)();

//funcptr是一个类型转换符,转型为“指向返回值为void的函数的指针”

#define APPADDR (*(uint32_t*))(FLASH_APP_BASE + 4) //用户代码区第二个字为程序开始地址(复位地址)
//flash从地址0开始是一个4字节的向量表,0x04存放复位程序的地址

//使用typedef
void jumpApp(){
(*(funcptr)APPADDR)();
//根据《C陷阱与缺陷》:fp() 是 (*fp)() 的简写
//以上语句可以简写为
//((funcptr)APPADDR)();
}

//不使用typedef
void jumpApp2(){
(*(void(*)())APPADDR)();
}

funcptr jump;
void jumpApp3(){
jump = (funcptr)APPADDR;
jump();
}

2.application

校验

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
//累加和
uint8_t m_crc8(const uint8_t *p, uint16_t len)
{
uint8_t crc = 0;
uint16_t i = 0;

for(i = 0; i < len; i++)
{
crc += p[i];
}

return crc;
}
//crc校验
int16_t factory_crc16 ( uint8_t *bufData, uint16_t buflen)
{
    uint16_t TCPCRC = 0xffff;
    uint16_t POLYNOMIAL = 0xa001;
    uint8_t i, j;
   
    for (i = 0; i < buflen; i++)
    {
        TCPCRC ^= bufData[i];
        for (j = 0; j < 8; j++)
        {
            if ((TCPCRC & 0x0001) != 0)
            {
                TCPCRC >>= 1;
                TCPCRC ^= POLYNOMIAL;
            }
            else
            {
                TCPCRC >>= 1;
            }
        }
    }
    return TCPCRC;
}

OTA 升级

  • 4G 模块从服务器下载升级固件:curl 下载,curl 相关资料:

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
//create an "easy handle", which is the handle to a transfer
CURL *easy_handle = curl_easy_init();
//then set options in that handle to control the upcomming transfer
CURLcode res = curl_easy_setopt(easy_handle, CURLOPT_URL, "http://example.com/");
//if curl_easy_setopt() returns CURL_OK, it stored the option fine

//----------------------Drive transfers
//1. drive with easy
res = curl_easy_perform(easy_handle);

//2. drive with multi
CURLM *multi_handle = curl_multi_init();
//add an easy handle to the multi handle
curl_multi_add_handle(multi_handle, easy_handle);
//remove one
curl_multi_remove_handle(multi_handle, easy_handle);

//a. do the transfer loop with curl_multi_wait()
int transfers_running;
do{
//1000 ms:longest time
curl_multi_wait(multi_handle, NULL, 0, 1000, NULL);
curl_multi_perform(multi_handle, &transfers_running);
}while(transfers_running);

//b. do the transfer loop with select()
int transfers_running;
do{
fd_set fdread;
fd_set fdwrite; //只接收的话不用定义,select参数设为NULL
fd_set fdexcep; //同上
int maxfd = -1;
long timeout;
// extract timeout value
curl_multi_timeout(multi_handle, &timeout);
if(timeout < 0) timeout = 1000;
//convert to struct usable by select
timeout.tv_sec = timeout / 1000;
timeout.tv_usec = (timeout % 1000) * 1000;

FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);

//get file description from the transfer
mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
if(maxfd == -1) SHORT_SLEEP;
else select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);

//timeout or readable/writable sockets
curl_multi_perform(multi_handle, &transfers_running);
}while(transfers_running);

//3. drive with multi_socket
//socket_callback
//libcurl informs the application about socket activity to wait for with a callback called //CURLMOPT_SOCKETFUNCTION.
int socket_callback(CURL *easy,
curl_socket_t s, //socket
int what, //what to wait for
void *userp, //private callback pointer
void *socketp) //private socket pointer
{
//told about the socket 's'
}
//set the callback in the multi handle
curl_multi_setopt(multi_handle, CURLMOPT_SOCKETFUNCTION, socket_callback);

int running_handles;
ret = curl_multi_socket_action(multi_handle,
sockfd, //the socket with activity
ev_bitmask, //the specific activity
&running_handles);
//timer_callback
//libcurl sets the timeout with the timer_callback CURLMOPT_TIMERFUNCTION.
int timer_callback(multi_handle,
timeout_ms, //ms to wait
userp) //private callback pointer
{
//the new time-out value to wait for is in 'timeout-ms'
}
//set the callback in the multi handle
curl_multi_setopt(multi_handle, CURLMOPT_TIMERFUNCTION, timer_callback);

//How to start everything
curl_multi_socket_action(muti, CURL_SOCKET_TIMEOUT, 0, &running);
//looping

//exit
event_base_dispatch(event_base); //libevent2 has this API

//-----------------Callbacks
//1. Write data
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback);
//write_callback function prototype
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
//if this callback is not set, libcurl instead uses 'fwrite' by default
//2. Read data
//...

在 linux 上测试,只需包含 "curl/curl.h" 头文件即可,链接时加入参数 -lcurl,测试代码:

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
#include "stdio.h"
#include "curl/curl.h"

size_t process_data(void *buffer, size_t size, size_t nmemb, void *user_p)
{
FILE *fp = (FILE *)user_p;
size_t return_size = fwrite(buffer, size, nmemb, fp);
return return_size;
}

int main(){
CURL *curl;
CURLcode res;

curl = curl_easy_init();
if(!curl)
{
return -1;
}

char *url = "liyunlong.xdt-iot.com/download/XDT_BMS/V1.20tobms.bin";
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &process_data);
/*
* 'b' shows that the file is a binary file.
* Linux ignore this.
* Some system treat different between text file and binary file.
*/
FILE *fp = fopen("down.bin", "ab+");
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
res = curl_easy_perform(curl);

if (CURLE_OK == res) printf("ok!\n");
else printf("ng!\n");

fclose(fp);
curl_easy_cleanup(curl);

return 0;
}

1
2
3
4
5
6
7
8
9
char p[1024 * 50];
long file_size;
FILE *r_file = fopen("ota.bin","r");
fseek(r_file, 0L, SEEK_END); //移动文件指针到末尾
file_size = ftell(r_file); //返回文件位置指针当前位置相对于文件首的偏移字节
fseek(r_file, 0L, SEEK_SET);
fread(p, sizeof(uint8_t), file_size, r_file);
fclose(r_file);
system("rm -rf ota.bin"); //删除