离线
TA的每日心情 | 奋斗 2022-6-21 08:23 |
---|
签到天数: 2 天 [LV.1]
|
有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
本帖最后由 塞巴斯蒂安 于 2022-6-3 14:39 编辑
一、项目概述
这个项目是制作一辆由PWM波控制轮速,用光电传感器检测信号来实现转弯的四轮驱动小车 :
比赛赛道
如图所示,小车放到中央,当两个光电传感器检测都为白色时,左右两侧都正转,小车直走;当左侧传感器检测到黑线时,即小车右偏或即将左转,左侧两轮反转,右侧两轮正转,达到向左转弯的目的;反之,当右侧传感器检测到黑线时,右轮反转,左轮正转,小车向右转。
https://www.bilibili.com/video/BV1594y1m77G?share_source=copy_web
二、硬件设计
此辆车由两块板子负责运行,一块负责电源输入与电机驱动,另一块为负责控制的CH32V307VCT6型号主板。
主板正:
主板反:
电机驱动板:
此驱动为电机驱动,由于此主板不方便直接给电机驱动,因此我们需要另一块板子用来驱动,将主板中电机驱动管脚与驱动板中与之对应的管脚相连。
将杜邦线焊到电机后,通过参考引线分配手册,我们将每个轮子对应的管脚分为:
左前轮——PD15,PD14(对应驱动板P67 P66)
左后轮——PD13,PD12(对应驱动板P65 P64)
右前轮——PA1 ,PA0(对应驱动板P63 P62)
右后轮——PA3 ,PA2(对应驱动板P61 P60)
传感器为光电传感器,我们用两个光电传感器来接收赛道信息,对应管脚为:
左——PC11
右——PC8
用杜邦线连接:
由于电池只给主板供电,我们从电机驱动板跳线到主板上给主板供电:
整体布局
三、软件设计
本项目主要通过定时器产生PWM输出,根据光电传感器采集数据来控制电机,实现小车循迹。本项目主要由两个配置文件和一个主函数组成,photoelectricity,motor,main。
1.传感器使用
photoelectricity.h文件- #ifndef HARDWARE_PHOTOELECTRICITY_PHOTOELECTRICITY_H_
- #define HARDWARE_PHOTOELECTRICITY_PHOTOELECTRICITY_H_
- #include "ch32v30x_conf.h"
- void PHOTOELECTRICITY_GPIOCInit (void); //初始化光电传感器
- void detect(); //检测
- #endif /* HARDWARE_PHOTOELECTRICITY_PHOTOELECTRICITY_H_ */
复制代码 photoelectricity.h 文件是函数的声明,对光电传感器的初始化与检测的配置函数进行声明
photoelectricity.c文件- #include "photoelectricity.h"
- u16 detectC11 ,detectC8 ,detectend;//用来存放PC11,PC8,以及最后检测出来的结果
- void PHOTOELECTRICITY_GPIOCInit (void) //初始化光电传感器
- {
- GPIO_InitTypeDef GPIO_InitStruct; //定义结构体,光电传感器 PC8、PC11
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//使能PC管脚
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_11; //配置GPIO引脚为8和11
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; //配置GPIO模式为高阻输入
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;/配置GPIO口输出速度为50MHz
- GPIO_Init(GPIOC, &GPIO_InitStruct); //调用库函数,初始化GPIO
- }
- void detect() //检测
- {
- if (GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_11)==Bit_SET) //如果PC11检测到高电平,则detectC11 = 1
- {
- detectC11=1;
- }
- else //如果PC11检测到低电平,则detectC11 = 0
- {
- detectC11=0;
- }
- if (GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_8)==Bit_SET) //如果PC8检测到高电平,则detectC8 = 1
- {
- detectC8=1;
- }
- else//如果PC8检测到低电平,则detectC8 = 0
- {
- detectC8=0;
- }
- detectend=detectC11*10+detectC8; //排列顺序为PC11、PC8 对每个传感器输出信号进行排列
- }
复制代码 2.电机驱动模块
moter.h文件
- #ifndef HARDWARE_MOTOR_MOTER_H_
- #define HARDWARE_MOTOR_MOTER_H_
- #include "ch32v30x_conf.h"
- void Motor_Left_Init(u16 arr,u16 psc);
- void Motor_Right_Init(u16 arr,u16 psc);
- void straight(); //直走
- void stop(); //停止
- void left(); //左转
- void right(); //右转
- #endif /* HARDWARE_MOTOR_MOTER_H_ */
复制代码 moter.c文件
- #include "moter.h"
- void Motor_Left_Init(u16 arr,u16 psc) // PD12 PD13 PD14 PD15
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
- TIM_OCInitTypeDef TIM_OCInitStructure;
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //使能定时器4时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
- GPIO_PinRemapConfig(GPIO_Remap_TIM4,ENABLE); //开重映射功能
- //设置该引脚为复用输出功能,输出TIM4 CH2 \ TIM4 CH4的PWM脉冲波形 GPIOD.13 和 GPIOD.15
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化GPIO
- GPIO_PinRemapConfig(GPIO_Remap_TIM4, ENABLE); //Timer4代替函数映射 TIM4_CH4->PD15
- //初始化TIM4
- TIM_TimeBaseStructure.TIM_Period = arr - 1; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
- TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
- TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
- TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
- //初始化TIM4 Channel4 PWM1模式
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低
- TIM_OC1Init(TIM4, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC1 PD12
- TIM_OC2Init(TIM4, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC2 PD13
- TIM_OC3Init(TIM4, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC3 PD14
- TIM_OC4Init(TIM4, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC4 PD15
- TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能TIM4在CCR1上的预装载寄存器
- TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能TIM4在CCR2上的预装载寄存器
- TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能TIM4在CCR3上的预装载寄存器
- TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能TIM4在CCR4上的预装载寄存器
- TIM_Cmd(TIM4, ENABLE); //使能TIM4
- }
- void Motor_Right_Init(u16 arr,u16 psc) // PA0 PA1 PA2 PA3
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
- TIM_OCInitTypeDef TIM_OCInitStructure;
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能定时器2时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
- //设置该引脚为复用输出功能,输出TIM2 CH2 \ TIM2 CH4的PWM脉冲波形 GPIOA.1 和 GPIOA.3
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
- //初始化TIM2
- TIM_TimeBaseStructure.TIM_Period = arr - 1; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
- TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
- TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
- TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIM2的时间基数单位
- //初始化TIM2 Channel2 Channel4 PWM1模式
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低
- TIM_OC1Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC1 PA0
- TIM_OC2Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC2 PA1
- TIM_OC3Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC3 PA2
- TIM_OC4Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2 OC4 PA3
- TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR1上的预装载寄存器
- TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR2上的预装载寄存器
- TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR3上的预装载寄存器
- TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable); //使能TIM2在CCR4上的预装载寄存器
- TIM_Cmd(TIM2, ENABLE); //使能TIM4
- }
- void straight() //直走
- {
- TIM_SetCompare1(TIM2,0); //将占空比调整至 0/800 PA0
- TIM_SetCompare2(TIM2,600); //将占空比调整至 600/800 PA1
- TIM_SetCompare3(TIM2,0); //将占空比调整至 0/800 PA2
- TIM_SetCompare4(TIM2,600); //将占空比调整至 600/800 PA3
- TIM_SetCompare1(TIM4,0); //将占空比调整至0/800
- TIM_SetCompare2(TIM4,600); //将占空比调整至600/800
- TIM_SetCompare3(TIM4,0); //将占空比调整至600/800
- TIM_SetCompare4(TIM4,600); //将占空比调整至0/800
- }
- void stop() //停止
- {
- TIM_SetCompare1(TIM2,0);
- TIM_SetCompare2(TIM2,0);
- TIM_SetCompare3(TIM2,0);
- TIM_SetCompare4(TIM2,0);
- TIM_SetCompare1(TIM4,0);
- TIM_SetCompare2(TIM4,0);
- TIM_SetCompare3(TIM4,0);
- TIM_SetCompare4(TIM4,0);
- }
- void left() //左转,右边轮正转,左边轮反转
- {
- TIM_SetCompare1(TIM2,0);
- TIM_SetCompare2(TIM2,799);
- TIM_SetCompare3(TIM2,0);
- TIM_SetCompare4(TIM2,799);
- TIM_SetCompare1(TIM4,799);
- TIM_SetCompare2(TIM4,0);
- TIM_SetCompare3(TIM4,799);
- TIM_SetCompare4(TIM4,0);
- }
- void right() //右转,左边轮正转,右边轮反转
- {
- TIM_SetCompare1(TIM2,799);
- TIM_SetCompare2(TIM2,0);
- TIM_SetCompare3(TIM2,799);
- TIM_SetCompare4(TIM2,0);
- TIM_SetCompare1(TIM4,0);
- TIM_SetCompare2(TIM4,799);
- TIM_SetCompare3(TIM4,0);
- TIM_SetCompare4(TIM4,799);
- }
复制代码 3.主函数 main.c
- #include "debug.h"
- #include "ch32v30x_conf.h"
- /* Global typedef */
- extern u16 detectend;//引用其他头文件中的变量需申明
- /* Global Variable */
- u16 detectendold;
- /*********************************************************************
- * @fn main
- *
- * @brief Main program.
- *
- * @return none
- */
- int main(void)
- {
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- Delay_Init(); //延时器初始化
- USART_Printf_Init(115200);
- printf("SystemClk:%d\r\n",SystemCoreClock);
- printf("This is print example\r\n");
- PHOTOELECTRICITY_GPIOCInit ();//初始化光电传感器
- Motor_Left_Init(800, 0); //初始化PWM 不分频 频率为8M/800=10khz
- Motor_Right_Init(800, 0); //初始化PWM 不分频 频率为8M/800=10khz
- while(1)
- {
- detect();
- if (detectend != detectendold) //如果检测到的情况与上一次相同,不进入循环
- {
- switch (detectend)
- {
- case 10:left(); break;//左边传感器检测到黑线,左转
- case 1:right();break;//右边传感器检测到黑线,右转
- case 0:straight();break;//没检测到黑线,直走
- case 11:stop();break;//全检测到黑线,停止
- default:detectend = 11;break; // 凡没考虑到的情况皆STOP
- }
- detectendold = detectend;
- }
- }
- }
复制代码
4、参考资料
芯片主板各程序示例:https://www.RISC-V1.com/thread-1629-1-1.html(CH32V307VCT6).
原理图—最小系统核心板:https://www.risc-v1.com/thread-2552-1-1.html
(CH32V307VCT6)应用手册 数据手册 编辑手册
https://www.risc-v1.com/thread-1890-1-1.html
5、操作步骤
第一步、将四个电机按照合适位置摆放,用螺丝拧紧,焊上杜邦线,将线的另一端接入四个电机的插座。 装好两块板子与电池,按硬件设计部分将杜邦线连接完整。
第二步、用数据连接线连接电脑与主板,在MounRiver Studio软件中写好代码,将其烧入。
第三步、接通电池,通过调试代码中void straight() 、void left()、void right()PWM占空比数值(如 TIM_SetCompare1(TIM2,799),如799表示为(799+1)/800全速100%),调整小车速度,最终能顺利跑完赛道即可。
将小车插上电源,放入赛道,他自个儿会跑。
完
|
|