目录:
1.从零开始的静改动工程:
底盘设计:
目标功能:
1.车体基本功能
c8t6输出pwm波:
通过esp32cam向c8t6发送串口信息来进行控制:
1.从零开始的静改动工程:
现阶段完成到底盘部分,由于tb套件的二次开发空间有限,所以开始考虑自己制作一版静改动解决方案。第一次做静改动,对空间使用的把握没有b数,所以就不考虑苏系车的圆脑袋了。综合考虑下选择了叹息之墙99a(小号手)。方形的脑壳便于安装设备,同时小号手内部空间巨大的优势体现得淋漓尽致。
底盘:
内部电路:
底盘采用一块stm32c8t6作为主控,两个100rpm的n20减速电机作为主要动力源,一块DRV8833作为pwm调速芯片。采用7.4v降压6.5v为电机供电。(测试极速大约5cm/s,原地转圈速度约90度/秒。秒爬坡性能还可以,就是履带会打滑,后续考虑打印一些软片制作挂胶履带。 )
底盘设计:
目标功能:
1.车体基本功能
首先对底盘内部空间进行建模,数据可以略微小于底盘内部实际宽度,这一步是为了方便后续设计以及购买模块的时候经行空间管理:
底盘粗略建模:
部分原理图:
本来想找一块与l298n功能相似的模块。奈何实在是找不到,无奈之下采用了DRV8833与两块数字电路芯片自行制作了l2980相关功能。这样就能节省pwm波输出引脚,为后续增加舵机实现前倾,后倾,可调悬挂等动作预留了io接口。
c8t6输出pwm波:
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#include "bsp_Pwm.h" static void PWM_TIM_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; // 输出比较通道1 GPIO初始化 RCC_APB2PeriphClockCmd(PWM_TIM_CH1_GPIO_CLK, ENABLE); GPIO_InitStructure.GPIO_Pin = PWM_TIM_CH1_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(PWM_TIM_CH1_PORT, &GPIO_InitStructure); // 输出比较通道2 GPIO初始化 RCC_APB2PeriphClockCmd(PWM_TIM_CH2_GPIO_CLK, ENABLE); GPIO_InitStructure.GPIO_Pin = PWM_TIM_CH2_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(PWM_TIM_CH2_PORT, &GPIO_InitStructure); // 输出比较通道3 GPIO初始化 RCC_APB2PeriphClockCmd(PWM_TIM_CH3_GPIO_CLK, ENABLE); GPIO_InitStructure.GPIO_Pin = PWM_TIM_CH3_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(PWM_TIM_CH3_PORT, &GPIO_InitStructure); // 输出比较通道4 GPIO初始化 RCC_APB2PeriphClockCmd(PWM_TIM_CH4_GPIO_CLK, ENABLE); GPIO_InitStructure.GPIO_Pin = PWM_TIM_CH4_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(PWM_TIM_CH4_PORT, &GPIO_InitStructure); } static void PWM_TIM_Mode_Config(void) { /*--------------------配置函数-------------------*/ // 输出占空比配置 CCRx_VAL / PWM_TIM_Period * 100% uint16_t CCR1_Val = 100; uint16_t CCR2_Val = 10; uint16_t CCR3_Val = 0; uint16_t CCR4_Val = 0; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; PWM_TIM_APBxClock_FUN(PWM_TIM_CLK, ENABLE); TIM_TimeBaseStructure.TIM_Period=PWM_TIM_Period; TIM_TimeBaseStructure.TIM_Prescaler= PWM_TIM_Prescaler; TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_RepetitionCounter=0; TIM_TimeBaseInit(PWM_TIM, &TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 通道 1 TIM_OCInitStructure.TIM_Pulse = CCR1_Val; TIM_OC1Init(PWM_TIM, &TIM_OCInitStructure); TIM_OC1PreloadConfig(PWM_TIM, TIM_OCPreload_Enable); // 通道 2 TIM_OCInitStructure.TIM_Pulse = CCR2_Val; TIM_OC2Init(PWM_TIM, &TIM_OCInitStructure); TIM_OC2PreloadConfig(PWM_TIM, TIM_OCPreload_Enable); // 通道 3 TIM_OCInitStructure.TIM_Pulse = CCR3_Val; TIM_OC3Init(PWM_TIM, &TIM_OCInitStructure); TIM_OC3PreloadConfig(PWM_TIM, TIM_OCPreload_Enable); // 通道 4 TIM_OCInitStructure.TIM_Pulse = CCR4_Val; TIM_OC4Init(PWM_TIM, &TIM_OCInitStructure); TIM_OC4PreloadConfig(PWM_TIM, TIM_OCPreload_Enable); // 使能 TIM_Cmd(PWM_TIM, ENABLE); } void PWM_TIM_Init(void) { PWM_TIM_GPIO_Config(); PWM_TIM_Mode_Config(); } void SET_SPEED_1(int per){ if(per>199){per=199;} TIM_SetCompare3(PWM_TIM,per); } void SET_SPEED_2(int per){ if(per>199){per=199;} TIM_SetCompare4(PWM_TIM,per); } /*********************************************END OF FILE**********************/
bsp_pwm.h:
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#ifndef __BSP_PWM_H #define __BSP_PWM_H #include "stm32f10x.h" #define PWM_TIM TIM3 #define PWM_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd #define PWM_TIM_CLK RCC_APB1Periph_TIM3 #define PWM_TIM_Period 199 #define PWM_TIM_Prescaler 7199 // TIM3 通道1 #define PWM_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOA #define PWM_TIM_CH1_PORT GPIOA #define PWM_TIM_CH1_PIN GPIO_Pin_6 // TIM3 通道2 #define PWM_TIM_CH2_GPIO_CLK RCC_APB2Periph_GPIOA #define PWM_TIM_CH2_PORT GPIOA #define PWM_TIM_CH2_PIN GPIO_Pin_7 // TIM3 通道3 #define PWM_TIM_CH3_GPIO_CLK RCC_APB2Periph_GPIOB #define PWM_TIM_CH3_PORT GPIOB #define PWM_TIM_CH3_PIN GPIO_Pin_0 // TIM3通道4 #define PWM_TIM_CH4_GPIO_CLK RCC_APB2Periph_GPIOB #define PWM_TIM_CH4_PORT GPIOB #define PWM_TIM_CH4_PIN GPIO_Pin_1 void PWM_TIM_Init(void); void SET_SPEED_1(int per); void SET_SPEED_2(int per); #endif /* __BSP_GENERALTIME_H */
pwm波驱动下的DVR8833芯片输出的电压驱动两组N20电机,全速运转下没有明显发热,组装完成后发现,车体扭矩虽然满足要求,但是极速性能非常差,由于这是一个鱼和熊掌不可兼得的问题,要改进可能要提高整体输入电压。
2.炮塔旋转功能
stm32的一路pwm输出接到了炮塔座圈下的360度舵机上,输出转速0到11度每秒。由于实现的功能中包含火炮的双向稳定功能,炮塔的转速必须与陀螺仪输出的角速度数据形成闭环控制。底盘的主板上预留了一组8pin段子,用于实现与炮塔的信息交互。
利用PID算法,在陀螺仪的加速度数据与炮塔电机转速之间建立闭环控制,就可以实现炮塔水平方向的类云台效果:
底盘添加了灯光功能后,总装效果如图:
通过esp32cam向c8t6发送串口信息来进行控制:
完成pwm波输出后,就可以让电机转起来了,由于DRV8833的真值表是
1/pwm | 0 | 正转 |
0 | 1/pwm | 反转 |
0 | 0 | 停止(电磁感应刹车) |
通过普通io口和cd4066来交换pwm波的输出线路,另一路就会被自动下拉到0。此时方向已经可以控制了,接下来就要将pwm占空比信息与串口输入信息关联起来。
接收端(c8t6):
定义一个结构体变量,用来保存esp32发过来的信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#ifndef __MAIN_H #define __MAIN_H #include "stm32f10x.h" #include <stdio.h> typedef struct { int speed_L; int speed_R; int strait_engine; int if_adjust; int altitude; int turn; int turn_pid; int data; }_CTRL;
在主函数中初始化,并循环写入pwm波的占空比,此时通过改变结构体的数据就能控制输出信号的占空比
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16_CTRL CTRL; void ctrl_init(void){ CTRL.speed_L=199; CTRL.speed_R=199; } int main(void) { PWM_TIM_Init();//使能pwm输出 while (1) { SET_SPEED_1(CTRL.speed_L); SET_SPEED_2(CTRL.speed_R); } }
我们再声明一个结构体指针:
1_CTRL *CTR=&CTRL;
在头文件中将指针extern,
1extern _CTRL *CTR;
此时,就可以在"stm32f10x_it.c"中使用"CTR->xxx"来向结构体写入信息了,当然,使用之前别忘了导入头文件。
发送端(esp32cam):
前端界面直接可以使用html制作,esp32cam的html前端界面有非常多的例子可以参考
这里我直接采用了一位大佬的代码,使用websocket控制esp32cam发送信息,引用这位大佬的文章 :引用:(34条消息) 基于esp32-cam的监控小车_待在图书馆的毛毛虫的博客-CSDN博客_esp32小车
代码功能为使用websocket向串口输出信息,同时向网页输出视频信号。由于按键控制无法实现连续控制,我引用了这位大佬的设计,向网页插入一个摇杆,控制小车移动。
引用:网页摇杆joystick——使用HTML5的canvas实现_eyhxh的博客-CSDN博客_html 摇杆
改写后的代码如下:(js部分可以参考我引用的网页,这里就不再赘述)
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<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <style> .noselect { -webkit-touch-callout: none; /* iOS Safari */ -webkit-user-select: none; /* Safari */ -khtml-user-select: none; /* Konqueror HTML */ -moz-user-select: none; /* Firefox */ -ms-user-select: none; /* Internet Explorer/Edge */ user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */ } .button1 { background-color: #4CAF50; width: 60px; height: 60px; } .button2 { background-color: #4CAF50; width: 60px; height: 60px; } .button3 { background-color: #4CAF50; width: 60px; height: 60px; } .button4 { background-color: #4CAF50; width: 60px; height: 60px; } .button5 { background-color: #4CAF50; width: 60px; height: 60px; } </style> </head> <body class="noselect" align="center" style="background-color:white"> <!--h2 style="color: teal;text-align:center;">Wi-Fi Camera 🚗 Control</h2--> <table id="mainTable" style="table-layout:fixed" CELLSPACING=10> <th> <img id="adjuster" src="" style="width:30%;height:250px"> </th> <th> <div class="leftjoystick"> <canvas id="joystick" width="250px" height="250px"></canvas> </div> </th> <th> <img id="cameraImage" src="" style="width:400px;height:250px"> </th> <th> <table> <tr> <td></td> <td><button type="button" class="button1">Green</button></td> <td></td> </tr> <tr> <td><button type="button" class="button2">Green2</button></td> <td><button type="button" class="button3">Green2</button></td> <td><button type="button" class="button4">Green2</button></td> </tr> <tr> <td></td> <td><button type="button" class="button5">Green3</button></td> <td></td> </tr> </table> </th> </table> <script src="../Prj_GolBB/main.js"></script> </body> </html>
通过摇杆和按钮产生控制信号,编码后通过websocket传输给esp32cam,在接收函数里解码并存入结构体:
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
50void onCarInputWebSocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { switch (type) { case WS_EVT_CONNECT: Serial.printf("WebSocket client #%u connected from %sn", client->id(), client->remoteIP().toString().c_str()); break; case WS_EVT_DISCONNECT: Serial.printf("WebSocket client #%u disconnectedn", client->id()); ledcWrite(PWMLightChannel, 0); break; case WS_EVT_DATA: AwsFrameInfo *info; info = (AwsFrameInfo*)arg; if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) { std::string myData = ""; myData.assign((char *)data, len); std::istringstream ss(myData); std::string key, value; std::getline(ss, key, ','); std::getline(ss, value, ','); int addrInt = atoi(key.c_str()); int valueInt = atoi(value.c_str()); WBC->addr=addrInt; switch (addrInt) { case 1: WBC->speed_L = valueInt; break; case 2: WBC->speed_R = valueInt; break; case 111: WBC->speed_LR = valueInt; break; } } break; case WS_EVT_PONG: case WS_EVT_ERROR: break; default: break; } }
为了避免一直发送数据,所以不在中断函数中放置向c8t6发送数据的函数,改而在主循环中以一定频率发送,避免串口过多次发送而阻塞,同时可以节省计算能力,提高流畅度。
1
2
3
4
5
6
7
8
9
10
11
12
13void loop() { wsCamera.cleanupClients(); wsCarInput.cleanupClients(); sendCameraPicture(); //Serial.printf("DATA1 %d, DATA2 %d, DATA3 %dn", wbcache.speed_L, wbcache.speed_R, wbcache.speed_LR); delay(10); value_80 = (uint8_t)(wbcache.speed_LR >> 8); value_08 = (uint8_t)(wbcache.speed_LR & 0x00FF); uint8_t arry[6]={0xFF,0xAA,value_08,value_80,wbcache.addr,0xDD}; Serial.write(arry,6); delay(40); }
摄像头的安装:
由于原版观察窗无法安装摄像头,我将观察窗后半部分切除,并重新建模:
模型:
安装效果:
说实话对视野的效果不是很满意,理论上炮镜应该是全车视野最开阔的地方,可能是小号手模型比例的问题,居然遮挡了视野的一半。
此处需要注意,制作视窗的时候一定要根据镜头的视角上限来设计镜头位置,否则会遮挡一部分视野,使得效果变差。ov2640镜头的等效像方焦点可以选取镜头平面向内1mm的位置
仅提供学习交流。如果存在侵权,请联系删除。
最后
以上就是风趣黑夜最近收集整理的关于使用esp32cam与stm32c8t6核心板开发的99A静改动1.从零开始的静改动工程:底盘设计:通过esp32cam向c8t6发送串口信息来进行控制:的全部内容,更多相关使用esp32cam与stm32c8t6核心板开发内容请搜索靠谱客的其他文章。
发表评论 取消回复