电磁智能小车基础版HAL库(adc,数据处理,pid,舵机,oled显示,驱动)

声明:本人也是初学者,写这篇博客的目的是梳理一下思路,和分享一下自己的想法,对于一些原理我就不阐述了(网站上已经有许多大佬讲了)如有错误和不足希望大家指出。

1.先是adc,毕竟我们要有数据才能进行接下来的操作(我使用的是六路多通道采集)

因为要不停的采集,所以放在定时器中比较好(我定时器12用来干其它了,所以我用的定时器1)

定时器3就选:internal clock 12就在 clock source 中选internal clock

相关配置

1.定时器

adc配置

然后是代码:这里我用了一个二位数组将每个端口的十组数据存到二位数组ADC_Value[a][i]中,这段从for循环中采集10次,即二位数组中储存了每个端口的十组数据,然后通过定时器中断,实现他的连续采集。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM3) 
    {
       
        pidController();
        Servo_Control(angle);
        MotorControl(0, 5000, 5000); //直行
        HAL_Delay(200);
        for (int a = 0; a < 10; a++)
        {
            for (i = 0; i < 6; i++)
            {
                HAL_ADC_Start(&hadc1);
                HAL_ADC_PollForConversion(&hadc1, 50); //等待转换完成
                ADC_Value[a][i] = HAL_ADC_GetValue(&hadc1);
            }
            HAL_ADC_Stop(&hadc1);
        }
    }
}

2,数据处理

我采用了1.先扫描2.然后限幅3.除去最大值和最小值平均(这三步都是尽量减小误差的影响)方法不是最优,有问题希望大家指出

最后再进行归一化

1.扫描:(其实可以扫描多次更精确,但有点费事)

void saomiao(void)
{   
   int i, b;
 
	 for(i=0;i<6;i++)
	 {
		 for(b=0;b<10;b++)
		{				 
		if(ADC_collect[b][i]>max[i])
		max[i]=ADC_collect[b][i];
		if(ADC_collect[b][i]<min[i])
		min[i]=ADC_collect[b][i];
		}
	 }
      
}

2.限幅滤波(除去偶然出现的极值)

void AD_filter(void)
{    
	int i = 0,b = 0;
	
	for(i=0;i<6;i++)
	{
		for(b=0;b<10;b++)
   {
    if(ADC_collect[b][i]>max[i])
	  	ADC_collect[b][i]=max[i];
    if(ADC_collect[b][i]<min[i])
      ADC_collect[b][i]=min[i];
	 }
	}
}

3.求平均 和归一化(归一化的目的就是使得预处理的数据被限定在一定的范围内(比如[0,1]或者[-1,1]),从而消除奇异样本数据导致的不良影响。)我这里把他放大了一点,以免过小引起的数据失真

归一化的公式:x=(x-Min)/(Max-Min)

void AD_value(void)     
{  
	int i ,b ,c,d,e;
	double sum;
	uint16_t max[6] = {0},min[6] = {0};
	uint16_t Average[6] = {0};
	for(c = 0;c<6;c++)
	 {
		 max[c] = ADC_collect[0][c];
		 min[c] = ADC_collect[0][c];
	 }
  for(i=0;i<6;i++)
	{
		for(b=0;b<10;b++)
   {
		  if(ADC_collect[i][b]>max[b])
			{
				max[b] = ADC_collect[i][b];
			}
			if(ADC_collect[i][b]<min[b])
			{
				min[b] = ADC_collect[i][b];      
			}
	 }
  }
   for (d = 0; d < 6; d++)
	{
        sum = 0;
        for (e = 0; e < 10; e++)
		    {
            if (ADC_collect[e][d] != max[d] && ADC_collect[e][d] != min[d])     //除去最大值和最小值,然后平均
							{
                sum += ADC_collect[e][d];
            }
        }
				Average[d] = sum/8;
		}
	for(i=0;i<6;i++)
 {
  AD[i] =(int)(99*(Average[i]-min[i])/(max[i]-min[i])+1);   //归一化
 }

}

 

 3.舵机(MG995)

舵机的原理无非就是通过占空比来控制他的转动角度(我这里将角度转换成了占空比,大家也可以直接输入相应的占空比,如果我没有记错的话应该是150-250)

计算公式

舵机原理

代码:

1.先配置定时器来生成pwm

 

 2.然后将角度转换成占空比

void Servo_Control(uint16_t angle)
{
	float temp;
	temp = angle / 9 * 10 + 50;
	__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, (uint16_t)temp);
}

 4.pid算法

 对处理过后的数据进行pid算法,来改变舵机的角度,我这里转弯只使用了舵机,有时间的同学可以在加一个差速转弯,这样可可以更丝滑的转弯。

1.先进行比较可以观测小车偏离中心的大小 

void get_channel(void)
{
	float rate[6] = {1,1,1,1,1,1};
	date = (rate[0] *  AD[0]) + (rate[1] *  AD[1]) + (rate[2] *  AD[2]  ) - (rate[3] * AD[3]) - (rate[4] * AD[4])-(rate[5]* AD[5]);
}

2.然后就是pid算法函数(这是还没有调式的代码,大家可以参考一下) 

float calculatePID(float date)
{
	int data = 1000;
	float kp = 0.1, ki = 0.01, kd = 0.05, err = 0;
	float target = 0, result =0,kp1 = 0,ki1 = 0,kd1 = 0;
	static float sum_err = 0,last_err = 0;
  
	err = date - target;
	kp1 = kp*err;  //比例项
	sum_err += err;
  if(sum_err > data)
	{
		sum_err = data;
	}
	else if(sum_err < -data)
	{
		sum_err = -data;
	}
	ki1 = ki*sum_err;    //积分项
	kd1 = kd*(err-last_err);
	last_err = err;
  result = kp1 + ki1 + kd1;
	return result;
}

3.通过pid函数控制舵机转弯 

void pidController(void)
{
	float err = date;
	float adjustment = calculatePID(err);
	int angle = 90;
  angle += adjustment;
}

 

5.驱动(这里只是最简单的驱动,没有使用差速转弯,只是给了个固定格式,后面有时间我会把在弯道和直线的不同速度代码写一下) 

1. tb6612http://t.csdnimg.cn/ziViu

申明一下:我使用的是tb612,emmmm:其实l298n也是相同的,我感觉两个差不多,其实我更推荐大家使用tb612,其要稳定,效率高一点;

功能引脚引脚功能
A控制信号输入pwmavm电机驱动电压输入端(4.5V-13.5V
A电机输入端2ain2vcc逻辑电平输入端(2.7V-5.5V)
A电机输入端1ain1gnd接数字地
B控制信号输入pwmbbo1B电机输出端1
B电机输入端2bin2bo2B电机输出端2
B电机输入端1bin1ao2A电机输出端2
正常工作、待机状态控制端stbyao1A电机输出端1
模拟接地pgnd1pgnd2接模拟地

 

 

2. 正反转和pwm

1.定时器配置

大家可以在gpio中改一下名字好辩认

 

2. 正反转

将4个端口配置为输出模式,然后可以改一下名3.

3.这样所有配置就完成了,接下来是驱动的代码 

正反转:

void LeftMotor_Go() //左电机正转
{
	HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_SET);
	HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_RESET);
}
void LeftMotor_Back()  //左电机反转
{
	HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_SET);
}
void LeftMotor_Stop()  //左电机停止 
{
	HAL_GPIO_WritePin(AIN1_GPIO_Port, AIN1_Pin, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(AIN2_GPIO_Port, AIN2_Pin, GPIO_PIN_RESET);
}
void RightMotor_Go() //右电机正转 
{
	HAL_GPIO_WritePin(BIN1_GPIO_Port, BIN1_Pin, GPIO_PIN_SET);
	HAL_GPIO_WritePin(BIN2_GPIO_Port, BIN2_Pin, GPIO_PIN_RESET);
}
void RightMotor_Back()  //右电机反转
{
	HAL_GPIO_WritePin(BIN1_GPIO_Port, BIN1_Pin, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(BIN2_GPIO_Port, BIN2_Pin, GPIO_PIN_SET);
}
void RightMotor_Stop()  //右电机停止
{
	HAL_GPIO_WritePin(BIN1_GPIO_Port, BIN1_Pin, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(BIN2_GPIO_Port, BIN2_Pin, GPIO_PIN_RESET);
}

pwm(这段是csdn其他大佬写的,我整理了一下,如有侵权,请联系我)

void MotorControl(char motorDirection, int leftMotorPWM, int rightMotorPWM) 
{
	switch (motorDirection) 
	{
	case 0:
		LeftMotor_Go();
		RightMotor_Go();
		__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_4, leftMotorPWM);
		__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, rightMotorPWM);
		break;
	case 1:
		LeftMotor_Back();
		RightMotor_Back();
		__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_4, leftMotorPWM);
		__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, rightMotorPWM);
		break;
	case 2:
		LeftMotor_Stop();
		RightMotor_Stop();
		__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_4, 0);
		__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 0);
		break;
	  default:
		break;
	}
}

6.oled屏幕 

我把oled的标准代码放在百度网盘了,需要的可以自己提取 

链接:https://pan.baidu.com/s/1D3wrXnbXgj8hIUDZAxwVzA 
提取码:x8b8

7.下面是主函数代码 

#include "main.h"
               // Device header

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
#include "turn.h"
#include "motor.h"
#include "PID.h"
#include "oled.h"
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;

TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim2;
TIM_HandleTypeDef htim3;

UART_HandleTypeDef huart1;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);
static void MX_TIM1_Init(void);
static void MX_ADC1_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_TIM3_Init(void);
/* USER CODE BEGIN PFP */
void SystemClock_Config(void);
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint16_t ADC_Value[10][5] = {0};
uint16_t ADC_collect[10][6] = {0};
int angle = 90;
uint16_t AD[6] = {0};
int i = 0;
char ad1,ad2,ad3,ad4,ad5,ad6;

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM3) 
    {
       
        pidController();
        Servo_Control(angle);
        MotorControl(0, 5000, 5000); //直行
        HAL_Delay(200);
        for (int a = 0; a < 10; a++)
        {
            for (i = 0; i < 6; i++)
            {
                HAL_ADC_Start(&hadc1);
                HAL_ADC_PollForConversion(&hadc1, 50); //等待转换完成
                ADC_collect[a][i] = HAL_ADC_GetValue(&hadc1);
            }
            HAL_ADC_Stop(&hadc1);
        }
    }
}



/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
 

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
   HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_TIM1_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
	
  OLED_Init();	
	OLED_Clear();
	
	
	HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
	HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
	HAL_ADCEx_Calibration_Start(&hadc1);   //校准adc
	
		OLED_ShowString(1,1,"ad1:",12);
		OLED_ShowString(1,14,"ad2:",12);
		OLED_ShowString(1,27,"ad3:",12);
		OLED_ShowString(1,40,"ad4:",12);
		OLED_ShowString(1,53,"ad5:",12);
		OLED_ShowString(1,66,"ad6:",12);
		OLED_Refresh();
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		 AD_value();
       
        OLED_ShowNum(13, 1, AD[0], 4, 12);
        OLED_ShowNum(13, 14, AD[1], 4, 12);
        OLED_ShowNum(13, 27, AD[2], 4, 12);
        OLED_ShowNum(13, 40, AD[3], 4, 12);
        OLED_ShowNum(13, 53, AD[4], 4, 12);
        OLED_ShowNum(13, 66, AD[5], 4, 12);

  }
  /* USER CODE END 3 */
}

 8.声明

这只是我的初步思路,如有不正确的地方请指正,另外,在上述代码中,扫描和限幅那一块还可以优化,(学长说是屎山代码55555555),大家看看思路就行,我会在比赛后将我的优化代码和功能性代妈(如环岛,干簧管停车等),发出(其实是还没有写) 

  • 40
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
1.时钟源 外部时钟 2.GPIO (实验:点亮LED灯,获取引脚的电平高低,将对应引脚置高置低)。 3.PWM-脉宽调制 主要是调节占空比(在小车中用来实现小车的加减速)。 4.TIMX定时器 5.红外遥控 主要运用了EXTI(外部中断/事件控制器),系统延时(系统滴答定时器SysTick)。 6.超声波避障 了解了超声波工作的原理,主要运用了TIM2定时器(用来定时测距),GPIO口。 7.红外探测 通过对障碍物和光的感应来返回电平的状态,从而达到避障和巡线效果。 8.测速码盘 (通过检测码盘上的凹槽数来获取脉冲数,通过计算公式得到小车当前速度) TIM3定时器(定时测速)。 9.PID算法 为了使小车的速度更快的达到目标值,获取更稳定的速度。 10.系统的使用 我们在小车内加入系统,对小车的数据获取,状态实现任务化,使小车内的运作更加有序。我们主要使用μC/OS-II系统内核来实现系统。在实现过程中,我们了解了系统的任务调度以及任务运行和处理器之间的关系(多任务同时运行)。 μC/OS-II内核中的任务控制块,任务状态,以及多任务和任务的优先级类似操作系统中的进程操作。 11.任务间的通信 主要用到信号量(主要就是PV操作 P操作:占用资源。V操作:释放资源);邮箱(邮箱可以使一个任务或者中断服务子程序向另一个任务发送指针型的变量,通常该指针指向包含了“消息”特定的数据结构)。
以下是基于 STM32F103 HAL 库的 OLED 显示屏 IIC 驱动代码,供参考: ```c #include "stm32f1xx_hal.h" #include "fonts.h" #define OLED_I2C_ADDR 0x78 // OLED I2C 地址 #define OLED_WIDTH 128 // OLED 宽度 #define OLED_HEIGHT 64 // OLED 高度 I2C_HandleTypeDef hi2c1; void OLED_Init(void) { uint8_t data[] = { 0xAE, // 关闭显示 0xD5, 0x80, // 设置时钟分频因子,震荡频率 0xA8, 0x3F, // 设置驱动路数 0xD3, 0x00, // 设置显示偏移 0x40, // 设置起始行 0x8D, 0x14, // 使能电荷泵 0x20, 0x00, // 水平寻址模式 0xA1, // 设置段重定向 0xC8, // 设置行重定向 0xDA, 0x12, // 设置 COM 硬件引脚配置 0x81, 0xCF, // 设置对比度 0xD9, 0xF1, // 设置预充电周期 0xDB, 0x40, // 设置 VCOMH 电压倍率 0xA4, // 全局显示开启 0xA6, // 设置显示方式,正常显示 0xAF // 开启显示 }; HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x00, 1, data, sizeof(data), 1000); } void OLED_Clear(void) { uint8_t data[OLED_WIDTH * OLED_HEIGHT / 8] = {0}; HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40, 1, data, sizeof(data), 1000); } void OLED_SetPos(uint8_t x, uint8_t y) { uint8_t data[] = { 0x00 + x % 16, // 设置列低位地址 0x10 + x / 16, // 设置列高位地址 0xB0 + y, // 设置页地址 }; HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x00, 1, data, sizeof(data), 1000); } void OLED_DrawPixel(uint8_t x, uint8_t y, uint8_t color) { OLED_SetPos(x, y); uint8_t data[] = {color}; HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40, 1, data, 1, 1000); } void OLED_DrawChar(uint8_t x, uint8_t y, uint8_t ch, uint8_t size, uint8_t color) { uint8_t i, j; uint8_t data[size * size]; for (i = 0; i < size; i++) { for (j = 0; j < size; j++) { if (size == 12) { data[i * size + j] = pgm_read_byte(&font1206[(ch - 32) * 12 + i]) & (1 << (11 - j)) ? color : 0; } else { data[i * size + j] = pgm_read_byte(&font1608[(ch - 32) * 16 + i]) & (1 << (15 - j)) ? color : 0; } } } OLED_SetPos(x, y); HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, 0x40, 1, data, size * size, 1000); } void OLED_DrawString(uint8_t x, uint8_t y, char *str, uint8_t size, uint8_t color) { while (*str) { OLED_DrawChar(x, y, *str++, size, color); x += size / 2; } } int main(void) { HAL_Init(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_I2C1_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 400000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; HAL_I2C_Init(&hi2c1); OLED_Init(); OLED_Clear(); OLED_DrawString(0, 0, "Hello World!", 12, 1); while (1) { HAL_Delay(1000); } } ``` 需要注意的是,上述代码中使用了一个 `fonts.h` 文件,它包含了 OLED 显示屏所需的字体数据。可以根据需要更换字体文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值