我是靠谱客的博主 风趣黑夜,最近开发中收集的这篇文章主要介绍使用esp32cam与stm32c8t6核心板开发的99A静改动1.从零开始的静改动工程:底盘设计:通过esp32cam向c8t6发送串口信息来进行控制:,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

目录:

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/pwm0正转
01/pwm反转
00停止(电磁感应刹车)

通过普通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 &#128663; 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发送串口信息来进行控制:所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(59)

评论列表共有 0 条评论

立即
投稿
返回
顶部