概述
目录:
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波:
#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:
#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发过来的信息:
#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波的占空比,此时通过改变结构体的数据就能控制输出信号的占空比
_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);
}
}
我们再声明一个结构体指针:
_CTRL *CTR=&CTRL;
在头文件中将指针extern,
extern _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部分可以参考我引用的网页,这里就不再赘述)
<!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,在接收函数里解码并存入结构体:
void 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发送数据的函数,改而在主循环中以一定频率发送,避免串口过多次发送而阻塞,同时可以节省计算能力,提高流畅度。
void 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核心板开发的99A静改动1.从零开始的静改动工程:底盘设计:通过esp32cam向c8t6发送串口信息来进行控制:所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复