目录
更新 :
创作原因:
本文章将分别从STM32CubeMX、STM32F103c8t6(Hal库)和Arduino两个方向进行记录,顺便也记录一下制作过程。本设备经过一系列改进原是全国赛比赛作品中的一部分,作为供电系统使用,本条内容仅为升级版前代,但经过测试可以作为太阳能追光系统使用,也可以根据自己的实际情况进行升级改造。该文章带有程序、硬件及板图,看明白,可以直做出成品!!!
废话不多说直接进入主题。
太阳能追光系统
所需硬件
1.光敏传感器*4
因为四个传感器需要接的线太多,可以自己打个小板子,可以省去不少线,下面是我画的PCB板将4个光敏传感器跟x轴舵机接在同一块板子上
2.太阳能充电模块*1
我用的是CN3795,其他太阳能充电模块也都可以,只要与自己选择的太阳能板功率相匹配即可。
3.单片机
本文以stm32f103c8t6最小系统板和Arduino Uno R3+Uno R3拓展板(有拓展板好插线)为例,其他型号也同理。
4.电池
储存太阳能板发的电,根据实际需要选择即可
5.舵机*2
根据太阳能板大小选择合适的舵机,本文结尾带有太阳能追光系统支架的AutoCAD板图,较为小巧所以使用SG90(0-180°)的即可,想跟换大型太阳能板的根据需要自行放大即可,根据重量更换大号舵机如MG996(0-180°)
6.杜邦线若干
STM32CubeMX+STM32F103c8t6
我是用的是Hal库,用STM32CubeMX进行端口的配置,具体如下图所示:使用了四个如图所示的光敏传感器,分别接入ADC1的0-3号口,TIM1的CH1和CH2使用PWM模式分别控制X轴转动跟Y轴转动,并开了USART3串口作为调试口。
ADC1使用了DMA采集模式,加快了数据的采集速度
STM32F103c8t6main.c
以下为Keil uVision5中的主程序,看到本条文章的人肯定都是带着想要程序的目的来的,我把主程序挂在下面供大家参考:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2022 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* 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 ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#define tol 60 //照度的相应范围,越小越敏感,反之迟缓,如果需要修改灵敏度,改这里的数值即可 (取值10~100 根据环境光强度不同敏感度也不同,室内光源变化幅度大,阳光下变化小)
int servoh = 1500; //默认角度(为设备的初始角度,1500对应的为90°)
int servohLimitHigh = 2500; //水平转动最大角度,对应为180°(使用的为0-180°舵机)
int servohLimitLow = 500; //水平转动最小角度,对应为0°(使用的为0-180°舵机)
int servov = 1500; //默认角度(为设备的初始角度,1500对应的为90°)
int servovLimitHigh = 2500; //垂直转动最大角度,对应为180°(使用的为0-180°舵机)
int servovLimitLow = 1500; //垂直转动最小角度,对应为90°(使用的为0-180°舵机)
#include "stdio.h"
int fputc(int ch, FILE *f) // 串口重定向(测试使用)
{
HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
uint16_t ADCvalue[4]={0};
void ADC_DMA_caiji()
{
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)ADCvalue,4);//启动DMA
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)//DMA采集完成中断服务函数
{
// printf("左上%d",ADCvalue[0]);
// printf("右上%d",ADCvalue[1]);
// printf("左下%d",ADCvalue[2]);
// printf("右下%d\r\n",ADCvalue[3]);
HAL_ADC_Stop_DMA(&hadc1);//关闭DMA的ADC采集
int lt = ADCvalue[0]; //左上
int rt = ADCvalue[1]; //右上
int ld = ADCvalue[2]; //左下
int rd = ADCvalue[3]; //右下
int avt = (lt + rt) / 2;
int avd = (ld + rd) / 2;
int avl = (lt + ld) / 2;
int avr = (rt + rd) / 2;
int dvert = avt - avd; //上下行
int dhoriz = avl - avr;
//检查差异是否在公差范围内,否则改变垂直角度
if (-1*tol > dvert || dvert > tol)
{
if (avt < avd)
{
servov = servov+11;
if (servov > servovLimitHigh)
{
servov = servovLimitHigh;
}
}
else if (avt > avd)
{
servov= servov-11;
if (servov < servovLimitLow)
{
servov = servovLimitLow;
}
}
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_2,servov);
}
//检查差异是否在公差范围内,否则改变水平角度
if (-1*tol > dhoriz || dhoriz > tol)
{
if (avl < avr)
{
servoh = servoh-11;
if (servoh < servohLimitLow)
{
servoh = servohLimitLow;
}
}
else if (avl > avr)
{
servoh = servoh+11;
if (servoh > servohLimitHigh)
{
servoh = servohLimitHigh;
}
}
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,servoh);
}
}
/* 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 */
/* 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_DMA_Init();
MX_ADC1_Init();
MX_USART3_UART_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1);//校准ADC
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
ADC_DMA_caiji();
HAL_Delay(50);//设备追光的间歇时间,数值增大,间歇时间延长,但不可省去!!!
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
Arduino Uno代码
接下来为arduino 的代码,该代码经过测试可以直接使用,且相对于STM32来说更为简单,
#include <Servo.h>
#define SERVOPINH 5 //水平舵机
#define SERVOPINV 6 //垂直舵机
#define dtime 30 //延时参数,数值越小相应速度越快,反之相应慢 单位毫秒 一般取值(10~100)
#define tol 40 //照度的相应范围,越小越敏感,反之迟缓 (取值10~100 根据环境光强度不同敏感度也不同,室内光源变化幅度大,阳光下变化小)
/*以上2参数太小,会对光线的细微变化极其敏感,进而产生抖动,
为消除抖动,可以使用滤波处理,或者调节参数,减慢反应时间或敏感度来应对。 */
// 水平舵机的设置
Servo horizontal; //水平舵机
int servoh = 90; //默认角度
int servohLimitHigh = 175; //水平转动最大角度
int servohLimitLow = 5; //水平转动最小角度
// 垂直舵机的设置
Servo vertical; //垂直舵机
int servov = 90; //默认角度
int servovLimitHigh = 180; //垂直转动最大角度
int servovLimitLow = 90; //垂直转动最小角度
// 4个光敏电阻模块的接线口
const int ldrlt = A0; //左上
const int ldrrt = A1; //右上
const int ldrld = A2; //左下
const int ldrrd = A3; //右下
void setup()
{
horizontal.attach(SERVOPINH);
vertical.attach(SERVOPINV);
delay(100);
}
void loop()
{
//分别读取4个光敏电阻模块的照度值
int lt = analogRead(ldrlt); //左上
int rt = analogRead(ldrrt); //右上
int ld = analogRead(ldrld); //左下
int rd = analogRead(ldrrd); //右下
//将邻近光敏电阻模块的读数平均
int avt = (lt + rt) / 2;
int avd = (ld + rd) / 2;
int avl = (lt + ld) / 2;
int avr = (rt + rd) / 2;
//再计算上下行和左右排平均值的差值
int dvert = avt - avd; //上下行
int dhoriz = avl - avr;
//检查差异是否在公差范围内,否则改变垂直角度
if (-1*tol > dvert || dvert > tol)
{
if (avt < avd)
{
servov = ++servov;
if (servov > servovLimitHigh)
{
servov = servovLimitHigh;
}
}
else if (avt > avd)
{
servov= --servov;
if (servov < servovLimitLow)
{
servov = servovLimitLow;
}
}
vertical.write(servov); //舵机旋转角度和光线相反的话 用(180- servov)或 (servov) 调换方向即可
}
//检查差异是否在公差范围内,否则改变水平角度
if (-1*tol > dhoriz || dhoriz > tol)
{
if (avl < avr)
{
servoh = --servoh;
if (servoh < servohLimitLow)
{
servoh = servohLimitLow;
}
}
else if (avl > avr)
{
servoh = ++servoh;
if (servoh > servohLimitHigh)
{
servoh = servohLimitHigh;
}
}
horizontal.write(servoh); //舵机旋转角度和光线相反的话 用(180- servoh) 或 (servoh) 调换方向即可
}
delay(dtime);
}
成品
以下为制作过程中的效果图
相关资料:
看到这之前其实可以根据自己需要做出来了,想偷懒的我也把相关文件放在下面了,可自取~
太阳能追光支架AutoCAD板图:
https://download.csdn.net/download/qq_33288274/86624896?spm=1001.2014.3001.5503
STM32和Arduino的太阳能追光系统(所有程序文件):
https://download.csdn.net/download/qq_33288274/86625023?spm=1001.2014.3001.5503
更新 :
基于STM32F4P6的太阳能追光系统板,可独立完成追光动作,并可通过串口与主控板通讯。