基于STM32的太阳能板追光系统(采用PCA9685)

这是最近做竞赛的时候加的一个小装置,还有不完善的地方大家多多包涵。

目录

    • ADC采集光敏电阻值(DMA方式)
    • 计算输出的PWM
    • 通过PCA9685舵机驱动板驱动舵机


ADC采集光敏电阻值(DMA方式)

首先肯定至少要用到四个光敏电阻模块,我这里的分布是左上左下右上右下。然后通过四路ADC采集四个模块返回的值。考虑到采集速率的问题,这里直接采用DMA的方式。

基于STM32的太阳能板追光系统(采用PCA9685)_第1张图片

多路ADC参考博客:https://blog.csdn.net/wqx521/article/details/69466440
我的:

 #include "adc.h"
 #include "delay.h"

static uint16_t ad_value[4] = {0};

static void adc_dma_init(void)
{
  DMA_InitTypeDef DMA_InitStructure;
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
 
  DMA_DeInit(DMA1_Channel1);
 
  DMA_InitStructure.DMA_PeripheralBaseAddr  = (u32) & (ADC1->DR);
  DMA_InitStructure.DMA_MemoryBaseAddr      = (u32)&ad_value;
  DMA_InitStructure.DMA_DIR                 = DMA_DIR_PeripheralSRC;
  DMA_InitStructure.DMA_M2M                 = DMA_M2M_Disable;
  DMA_InitStructure.DMA_PeripheralDataSize  = DMA_PeripheralDataSize_HalfWord;
  DMA_InitStructure.DMA_MemoryDataSize      = DMA_MemoryDataSize_HalfWord;
  DMA_InitStructure.DMA_BufferSize          = 4;
  DMA_InitStructure.DMA_MemoryInc           = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralInc       = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_Mode                = DMA_Mode_Circular;
  DMA_InitStructure.DMA_Priority            = DMA_Priority_High;
  DMA_Init(DMA1_Channel1, &DMA_InitStructure);
}
															   
void  Adc_Init(void)
{ 	
	ADC_InitTypeDef ADC_InitStructure; 
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_GPIOC |RCC_APB2Periph_ADC1	, ENABLE );	  //使能ADC1通道时钟
 

	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M

	//PA6 作为模拟通道输入引脚                         
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚6
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
	//PA7 作为模拟通道输入引脚    
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚7
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	//PC4 作为模拟通道输入引脚                         
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚14
	GPIO_Init(GPIOC, &GPIO_InitStructure);	
	//PC5 作为模拟通道输入引脚    
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚15
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	
	ADC_DeInit(ADC1);  //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值

	ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult;	
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;	
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;	
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
	ADC_InitStructure.ADC_NbrOfChannel = 4;	//顺序进行规则转换的ADC通道的数目
	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   

    ADC_RegularChannelConfig(ADC1,ADC_Channel_6,1,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_7,2,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_14,3,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_15,4,ADC_SampleTime_55Cycles5);
	
	ADC_DMACmd(ADC1, ENABLE);
	
	ADC_Cmd(ADC1, ENABLE);	//使能指定的ADC1
	
	ADC_ResetCalibration(ADC1);	//使能复位校准  
	 
	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
	
	ADC_StartCalibration(ADC1);	 //开启AD校准
 
	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
 
//	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能

}				  


void  ADCInit(void)
{
  Adc_Init();
  adc_dma_init();
  ADC_SoftwareStartConvCmd(ADC1, ENABLE); // start convert
  DMA_Cmd(DMA1_Channel1, ENABLE);
}


uint16_t ADCGetData(uint8_t channel)
{
  uint16_t ret = 0;
  switch(channel)
  {
  case ADC_Channel_6:
    ret = ad_value[0];
    break;
  case ADC_Channel_7:
    ret = ad_value[1];
    break;
  case ADC_Channel_14:
    ret = ad_value[2];
    break;
  case ADC_Channel_15:
    ret = ad_value[3];
    break;
  }
 
  return ret;
}

计算输出的PWM

得到ADC的值后,接下来就是计算了。光敏电阻模块受到的光照越强,返回的电压就越低,ad采集到的值就越小;反之越大。比较左右平均值来确定水平方向转动方向,比较上下平均值来确定竖直方向转动方向,那边小就往哪边转,从而找到光源。
程序是从适用于Arduino的代码移植过来的。
Arduino源码:https://create.arduino.cc/editor/wjd76/e225717a-ae15-4282-98bd-f9cf09400bee/preview
移植到32上:(我的板子是STM32RCT6,程序是基于正点原子的改的)

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "lcd.h"
#include "adc.h"
#include "pca9685.h"


int dtime=10;  //延时参数,数值越小相应速度越快,反之相应慢   单位毫秒 一般取值(10~100) 
int tol=100;     //照度的相应范围,越小越敏感,反之迟缓  (取值10~100 根据环境光强度不同敏感度也不同,室内光源变化幅度大,阳光下变化小)
/*以上2参数太小,会对光线的细微变化极其敏感,进而产生抖动,
  为消除抖动,可以使用滤波处理,或者调节参数,减慢反应时间或敏感度来应对。 */

// 水平舵机的设置
int servoh = 80;   // 默认角度

int servohLimitHigh = 175;  //限幅
int servohLimitLow = 5;     

// 垂直舵机的设置
int servov = 110;     // 默认角度

int servovLimitHigh = 175;  //
int servovLimitLow = 100;    //最大仰角 不易过大,传感器可能顶住机架

u16 avt,avb,avl,avr,dvert,dhoriz;
u16 lt,rt,lb,rb;


 int main(void)
 { 
	u16 adcx;
	float temp;
	delay_init();	    	 //延时函数初始化	  
	uart_init(9600);	 	//串口初始化为9600
	LED_Init();		  		//初始化与LED连接的硬件接口
 	LCD_Init();
	 
	Servo_Init(50,90);//初始化舵机驱动
	Servo_angle(0,90);
	
 	ADCInit();		  		//ADC初始化	        
	
		//pca_setpwm(0,0,180); 
		Servo_angle(0,90);//0到180
		//pca_setpwm(0,0,320);
		Servo_angle(2,110);//100到175

	while(1)
	{

				
	lt = ADCGetData(ADC_Channel_6); //左上 
	rt = ADCGetData(ADC_Channel_7); //右上
	lb = ADCGetData(ADC_Channel_14); //左下
	rb = ADCGetData(ADC_Channel_15); //右下
		
	avt = (lt + rt) / 2; // 
	avb = (lb + rb) / 2; // 
	avl = (lt + lb) / 2; // 
	avr = (rt + rb) / 2; //将邻近的读数平均
  
	dvert = avt - avb; // 上下差值
	dhoriz = avl - avr;//左右差值
		

	if (-1*tol > dvert || dvert > tol) 
	 {
	  if (avt > avb)
	  {
	    servov--;
			if (servov < servovLimitLow)
			{
				servov = servovLimitLow;
			}
	  }
	  else if (avt < avb)
	  {
	    servov++;
			if (servov > servovLimitHigh) 
	    	{ 
				servov = servovLimitHigh;
	 	   	}
	  }
	  Servo_angle(2,servov);
	 }

	  //检查差异是否在公差范围内,否则改变水平角度  
	 if (-1*tol > dhoriz || dhoriz > tol) 
	  {
			if (avl > avr)
			{
				servoh++;
				if (servoh > servohLimitHigh)
				{
				servoh = servohLimitHigh;
				}
			}
			else if (avl < avr)
			{
				servoh--;
				if (servoh < servohLimitLow)
				{
					servoh = servohLimitLow;
				}
			}
 	 Servo_angle(0,servoh); 
	  }
/
		LED0=!LED0;
	}
	delay(dtime);											    
}	

通过PCA9685舵机驱动板驱动舵机

因为手头上刚好有一个PCA9685,所以就打算用它啦。当然你直接通过PWM输出给舵机也是完全没问题的。
PCA9685采用I2C通信,可以控制16路PWM。绿色部分接电源,VCC、GND、SDA、SCL接单片机,下面16组排针接舵机,排针上方数字对应控制的通道。
基于STM32的太阳能板追光系统(采用PCA9685)_第2张图片
这里展示一部分驱动代码:

void pca_setfreq(float freq)//设置PWM频率
{
		u8 prescale,oldmode,newmode;
		double prescaleval;
		freq *= 0.92; 
		prescaleval = 25000000;
		prescaleval /= 4096;
		prescaleval /= freq;
		prescaleval -= 1;
		prescale =floor(prescaleval + 0.5f);

		oldmode = pca_read(pca_mode1);
	
		newmode = (oldmode&0x7F) | 0x10; // sleep
	
		pca_write(pca_mode1, newmode); // go to sleep
	
		pca_write(pca_pre, prescale); // set the prescaler
	
		pca_write(pca_mode1, oldmode);
		delay_ms(2);
	
		pca_write(pca_mode1, oldmode | 0xa1); 
}

void pca_setpwm(u8 num, u32 on, u32 off)
{
		pca_write(LED0_ON_L+4*num,on);
		pca_write(LED0_ON_H+4*num,on>>8);
		pca_write(LED0_OFF_L+4*num,off);
		pca_write(LED0_OFF_H+4*num,off>>8);
}

void Servo_angle(u8 num,u8 angle)
{
	u32 off = 0;
    off = (u32)(158+angle*2.2);
	pca_setpwm(num,0,off);
}

有关PCA9685可以参考这篇博客:https://blog.csdn.net/czhzasui/article/details/80332474
驱动PCA9685的I2C代码是参考这一篇博客:https://blog.csdn.net/richardgann/article/details/119243529
有关STM32通过PWM信号驱动舵机可以参考这一篇:https://blog.csdn.net/qq_42866708/article/details/113355329

另外,我在树莓派上也接了一块PCA9685,用的是python的Adafruit_Python_PCA9685库。
详细的可以参考https://www.jianshu.com/p/b95e5a90175a

完整的工程我放在这里啦,有需要的可以取走。
链接:https://pan.baidu.com/s/1C46vDTC7RozeD9FFoMm_0w
提取码:pbaw

你可能感兴趣的:(stm32)