这是最近做竞赛的时候加的一个小装置,还有不完善的地方大家多多包涵。
首先肯定至少要用到四个光敏电阻模块,我这里的分布是左上左下右上右下。然后通过四路ADC采集四个模块返回的值。考虑到采集速率的问题,这里直接采用DMA的方式。
多路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;
}
得到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,所以就打算用它啦。当然你直接通过PWM输出给舵机也是完全没问题的。
PCA9685采用I2C通信,可以控制16路PWM。绿色部分接电源,VCC、GND、SDA、SCL接单片机,下面16组排针接舵机,排针上方数字对应控制的通道。
这里展示一部分驱动代码:
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