查看: 1024|回复: 3
收起左侧

「RISC-V单片机中文社区」一辆RISC-V MCU极速四轮驱动智能车诞生了

  [复制链接]

  离线 

  • TA的每日心情
    奋斗
    2022-6-21 08:23
  • 签到天数: 2 天

    [LV.1]

    发表于 2022-6-1 22:20:54 | 显示全部楼层 |阅读模式

    有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    本帖最后由 塞巴斯蒂安 于 2022-6-3 14:39 编辑

    一、项目概述      

    这个项目是制作一辆由PWM波控制轮速,用光电传感器检测信号来实现转弯的四轮驱动小车 :                                

    RISC-Vduino DUE开发板教程-「RISC-V单片机中文社区」一辆RISC-V MCU极速四轮驱动智能车诞生了risc-v单片机中文社区(1)               
    比赛赛道

    如图所示,小车放到中央,当两个光电传感器检测都为白色时,左右两侧都正转,小车直走;当左侧传感器检测到黑线时,即小车右偏或即将左转,左侧两轮反转,右侧两轮正转,达到向左转弯的目的;反之,当右侧传感器检测到黑线时,右轮反转,左轮正转,小车向右转。
    https://www.bilibili.com/video/BV1594y1m77G?share_source=copy_web

    二、硬件设计   

    此辆车由两块板子负责运行,一块负责电源输入与电机驱动,另一块为负责控制的CH32V307VCT6型号主板。

    主板正:   
    RISC-Vduino DUE开发板教程-「RISC-V单片机中文社区」一辆RISC-V MCU极速四轮驱动智能车诞生了risc-v单片机中文社区(2)
    主板反:                   
    RISC-Vduino DUE开发板教程-「RISC-V单片机中文社区」一辆RISC-V MCU极速四轮驱动智能车诞生了risc-v单片机中文社区(3)
    电机驱动板:        
    RISC-Vduino DUE开发板教程-「RISC-V单片机中文社区」一辆RISC-V MCU极速四轮驱动智能车诞生了risc-v单片机中文社区(4)

    此驱动为电机驱动,由于此主板不方便直接给电机驱动,因此我们需要另一块板子用来驱动,将主板中电机驱动管脚与驱动板中与之对应的管脚相连。

    将杜邦线焊到电机后,通过参考引线分配手册,我们将每个轮子对应的管脚分为:
    左前轮——PD15,PD14(对应驱动板P67 P66)
    左后轮——PD13,PD12(对应驱动板P65 P64)
    右前轮——PA1 ,PA0(对应驱动板P63 P62)
    右后轮——PA3 ,PA2(对应驱动板P61 P60)   

    传感器为光电传感器,我们用两个光电传感器来接收赛道信息,对应管脚为:
    左——PC11
    右——PC8
    RISC-Vduino DUE开发板教程-「RISC-V单片机中文社区」一辆RISC-V MCU极速四轮驱动智能车诞生了risc-v单片机中文社区(5)

    用杜邦线连接:
    RISC-Vduino DUE开发板教程-「RISC-V单片机中文社区」一辆RISC-V MCU极速四轮驱动智能车诞生了risc-v单片机中文社区(6)
    由于电池只给主板供电,我们从电机驱动板跳线到主板上给主板供电:                                     
    RISC-Vduino DUE开发板教程-「RISC-V单片机中文社区」一辆RISC-V MCU极速四轮驱动智能车诞生了risc-v单片机中文社区(7)
    整体布局

    三、软件设计

    本项目主要通过定时器产生PWM输出,根据光电传感器采集数据来控制电机,实现小车循迹。本项目主要由两个配置文件和一个主函数组成,photoelectricity,motor,main。

    1.传感器使用
    photoelectricity.h文件
    1. #ifndef HARDWARE_PHOTOELECTRICITY_PHOTOELECTRICITY_H_
    2. #define HARDWARE_PHOTOELECTRICITY_PHOTOELECTRICITY_H_

    3. #include "ch32v30x_conf.h"

    4. void PHOTOELECTRICITY_GPIOCInit (void); //初始化光电传感器
    5. void detect(); //检测

    6. #endif /* HARDWARE_PHOTOELECTRICITY_PHOTOELECTRICITY_H_ */

    复制代码
    photoelectricity.h 文件是函数的声明,对光电传感器的初始化与检测的配置函数进行声明

    photoelectricity.c文件
    1. #include "photoelectricity.h"

    2. u16 detectC11 ,detectC8 ,detectend;//用来存放PC11,PC8,以及最后检测出来的结果

    3. void PHOTOELECTRICITY_GPIOCInit (void)   //初始化光电传感器
    4. {
    5.     GPIO_InitTypeDef GPIO_InitStruct;   //定义结构体,光电传感器 PC8、PC11

    6.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//使能PC管脚

    7.     GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_11; //配置GPIO引脚为8和11
    8.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; //配置GPIO模式为高阻输入
    9.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;/配置GPIO口输出速度为50MHz
    10.     GPIO_Init(GPIOC, &GPIO_InitStruct); //调用库函数,初始化GPIO

    11. }

    12. void detect() //检测
    13. {
    14.     if (GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_11)==Bit_SET) //如果PC11检测到高电平,则detectC11 = 1
    15.      {
    16.          detectC11=1;
    17.      }
    18.      else //如果PC11检测到低电平,则detectC11 = 0
    19.      {
    20.          detectC11=0;
    21.      }

    22.      if (GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_8)==Bit_SET) //如果PC8检测到高电平,则detectC8 = 1
    23.      {
    24.          detectC8=1;
    25.      }
    26.      else//如果PC8检测到低电平,则detectC8 = 0
    27.      {
    28.          detectC8=0;
    29.      }


    30.      detectend=detectC11*10+detectC8; //排列顺序为PC11、PC8 对每个传感器输出信号进行排列
    31. }
    复制代码
    2.电机驱动模块
    moter.h文件
    1. #ifndef HARDWARE_MOTOR_MOTER_H_
    2. #define HARDWARE_MOTOR_MOTER_H_

    3. #include "ch32v30x_conf.h"

    4. void Motor_Left_Init(u16 arr,u16 psc);
    5. void Motor_Right_Init(u16 arr,u16 psc);
    6. void straight();  //直走
    7. void stop();  //停止
    8. void left();  //左转
    9. void right();  //右转

    10. #endif /* HARDWARE_MOTOR_MOTER_H_ */
    复制代码
    moter.c文件

    1. #include "moter.h"

    2. void Motor_Left_Init(u16 arr,u16 psc)  // PD12 PD13 PD14 PD15
    3. {
    4.     GPIO_InitTypeDef GPIO_InitStructure;
    5.     TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    6.     TIM_OCInitTypeDef  TIM_OCInitStructure;

    7.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);    //使能定时器4时钟
    8.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟

    9.     GPIO_PinRemapConfig(GPIO_Remap_TIM4,ENABLE); //开重映射功能
    10.    //设置该引脚为复用输出功能,输出TIM4 CH2 \ TIM4 CH4的PWM脉冲波形    GPIOD.13 和  GPIOD.15
    11.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    12.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
    13.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    14.     GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化GPIO

    15.     GPIO_PinRemapConfig(GPIO_Remap_TIM4, ENABLE); //Timer4代替函数映射   TIM4_CH4->PD15

    16.    //初始化TIM4
    17.     TIM_TimeBaseStructure.TIM_Period = arr - 1; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
    18.     TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
    19.     TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
    20.     TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    21.     TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

    22.     //初始化TIM4 Channel4 PWM1模式
    23.     TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
    24.     TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
    25.     TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低
    26.     TIM_OC1Init(TIM4, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM4 OC1 PD12
    27.     TIM_OC2Init(TIM4, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM4 OC2 PD13
    28.     TIM_OC3Init(TIM4, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM4 OC3 PD14
    29.     TIM_OC4Init(TIM4, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM4 OC4 PD15

    30.     TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);  //使能TIM4在CCR1上的预装载寄存器
    31.     TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);  //使能TIM4在CCR2上的预装载寄存器
    32.     TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);  //使能TIM4在CCR3上的预装载寄存器
    33.     TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);  //使能TIM4在CCR4上的预装载寄存器

    34.     TIM_Cmd(TIM4, ENABLE);  //使能TIM4
    35. }

    36. void Motor_Right_Init(u16 arr,u16 psc)  // PA0 PA1 PA2 PA3
    37. {
    38.     GPIO_InitTypeDef GPIO_InitStructure;
    39.     TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    40.     TIM_OCInitTypeDef  TIM_OCInitStructure;

    41.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);    //使能定时器2时钟
    42.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA  | RCC_APB2Periph_AFIO, ENABLE);  //使能GPIO外设和AFIO复用功能模块时钟

    43.    //设置该引脚为复用输出功能,输出TIM2 CH2 \ TIM2 CH4的PWM脉冲波形    GPIOA.1 和  GPIOA.3
    44.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    45.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
    46.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    47.     GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO


    48.    //初始化TIM2
    49.     TIM_TimeBaseStructure.TIM_Period = arr - 1; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
    50.     TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
    51.     TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
    52.     TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    53.     TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIM2的时间基数单位

    54.     //初始化TIM2 Channel2 Channel4 PWM1模式
    55.     TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
    56.     TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
    57.     TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低
    58.     TIM_OC1Init(TIM2, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM2 OC1 PA0
    59.     TIM_OC2Init(TIM2, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM2 OC2 PA1
    60.     TIM_OC3Init(TIM2, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM2 OC3 PA2
    61.     TIM_OC4Init(TIM2, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM2 OC4 PA3

    62.     TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);  //使能TIM2在CCR1上的预装载寄存器
    63.     TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);  //使能TIM2在CCR2上的预装载寄存器
    64.     TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable);  //使能TIM2在CCR3上的预装载寄存器
    65.     TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable);  //使能TIM2在CCR4上的预装载寄存器

    66.     TIM_Cmd(TIM2, ENABLE);  //使能TIM4
    67. }

    68. void straight()  //直走
    69. {

    70.     TIM_SetCompare1(TIM2,0);    //将占空比调整至     0/800 PA0
    71.     TIM_SetCompare2(TIM2,600);  //将占空比调整至  600/800 PA1
    72.     TIM_SetCompare3(TIM2,0);    //将占空比调整至     0/800 PA2
    73.     TIM_SetCompare4(TIM2,600);  //将占空比调整至  600/800 PA3

    74.     TIM_SetCompare1(TIM4,0);    //将占空比调整至0/800
    75.     TIM_SetCompare2(TIM4,600); //将占空比调整至600/800
    76.     TIM_SetCompare3(TIM4,0);    //将占空比调整至600/800
    77.     TIM_SetCompare4(TIM4,600); //将占空比调整至0/800
    78. }

    79. void stop()  //停止
    80. {
    81.     TIM_SetCompare1(TIM2,0);  
    82.     TIM_SetCompare2(TIM2,0);  
    83. TIM_SetCompare3(TIM2,0);  
    84. TIM_SetCompare4(TIM2,0);  

    85. TIM_SetCompare1(TIM4,0);  
    86. TIM_SetCompare2(TIM4,0);  
    87.     TIM_SetCompare3(TIM4,0);  
    88.     TIM_SetCompare4(TIM4,0);  
    89. }

    90. void left()  //左转,右边轮正转,左边轮反转
    91. {
    92.     TIM_SetCompare1(TIM2,0);  
    93.     TIM_SetCompare2(TIM2,799);  
    94.     TIM_SetCompare3(TIM2,0);  
    95.     TIM_SetCompare4(TIM2,799);  

    96.     TIM_SetCompare1(TIM4,799);  
    97.     TIM_SetCompare2(TIM4,0);  
    98.     TIM_SetCompare3(TIM4,799);  
    99.     TIM_SetCompare4(TIM4,0);  
    100. }

    101. void right()  //右转,左边轮正转,右边轮反转
    102. {
    103.     TIM_SetCompare1(TIM2,799);  
    104.     TIM_SetCompare2(TIM2,0);  
    105.     TIM_SetCompare3(TIM2,799);  
    106.     TIM_SetCompare4(TIM2,0);  

    107.     TIM_SetCompare1(TIM4,0);  
    108.     TIM_SetCompare2(TIM4,799);  
    109.     TIM_SetCompare3(TIM4,0);  
    110.     TIM_SetCompare4(TIM4,799);  
    111. }
    复制代码
    3.主函数 main.c
    1. #include "debug.h"
    2. #include "ch32v30x_conf.h"

    3. /* Global typedef */
    4. extern u16 detectend;//引用其他头文件中的变量需申明
    5. /* Global Variable */
    6. u16 detectendold;
    7. /*********************************************************************
    8. * @fn      main
    9. *
    10. * @brief   Main program.
    11. *
    12. * @return  none
    13. */
    14. int main(void)
    15. {

    16.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    17.         Delay_Init(); //延时器初始化
    18.         USART_Printf_Init(115200);
    19.         printf("SystemClk:%d\r\n",SystemCoreClock);

    20.         printf("This is print example\r\n");

    21.     PHOTOELECTRICITY_GPIOCInit ();//初始化光电传感器

    22.     Motor_Left_Init(800, 0); //初始化PWM 不分频 频率为8M/800=10khz

    23.     Motor_Right_Init(800, 0); //初始化PWM 不分频 频率为8M/800=10khz



    24.         while(1)
    25.     {
    26.             detect();
    27.             if (detectend != detectendold) //如果检测到的情况与上一次相同,不进入循环
    28.             {
    29.                switch (detectend)
    30.                 {
    31.                   case 10:left(); break;//左边传感器检测到黑线,左转
    32.                   case 1:right();break;//右边传感器检测到黑线,右转
    33.                   case 0:straight();break;//没检测到黑线,直走
    34.                   case 11:stop();break;//全检测到黑线,停止
    35.                   default:detectend = 11;break; // 凡没考虑到的情况皆STOP
    36.                  }
    37.               detectendold = detectend;
    38.              }
    39.     }
    40. }
    复制代码

    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%),调整小车速度,最终能顺利跑完赛道即可。

    将小车插上电源,放入赛道,他自个儿会跑。
    RISC-Vduino DUE开发板教程-「RISC-V单片机中文社区」一辆RISC-V MCU极速四轮驱动智能车诞生了risc-v单片机中文社区(8)

    RISCV作者优文
    全球首家只专注于RISC-V单片机行业应用的中文网站
    回复

    使用道具 举报

      离线 

  • TA的每日心情
    飞起
    2022-10-5 00:04
  • 签到天数: 2 天

    [LV.1]

    发表于 2022-6-5 17:53:40 | 显示全部楼层
    年轻有为,来这个跑道溜溜,哈哈哈

    RISC-Vduino DUE开发板教程-「RISC-V单片机中文社区」一辆RISC-V MCU极速四轮驱动智能车诞生了risc-v单片机中文社区(9)



    RISC-Vduino DUE开发板教程-「RISC-V单片机中文社区」一辆RISC-V MCU极速四轮驱动智能车诞生了risc-v单片机中文社区(10)


    RISC-Vduino DUE开发板教程-「RISC-V单片机中文社区」一辆RISC-V MCU极速四轮驱动智能车诞生了risc-v单片机中文社区(11)


    全球首家只专注于RISC-V单片机行业应用的中文网站

      离线 

  • TA的每日心情
    飞起
    2024-7-14 09:44
  • 签到天数: 3 天

    [LV.2]

    发表于 2022-10-22 15:23:18 | 显示全部楼层
    RISC-V Car
    全球首家只专注于RISC-V单片机行业应用的中文网站
    点评回复

    使用道具 举报

      离线 

  • TA的每日心情
    飞起
    2024-7-14 09:44
  • 签到天数: 3 天

    [LV.2]

    发表于 2022-10-22 15:23:19 | 显示全部楼层
    RISC-V Car
    全球首家只专注于RISC-V单片机行业应用的中文网站
    点评回复

    使用道具 举报

    高级模式
    B Color Image Link Quote Code Smilies

    本版积分规则

    关闭

    RISC-V单片机中文网上一条 /2 下一条


    版权及免责声明|RISC-V单片机中文网 |网站地图

    GMT+8, 2024-11-26 11:52 , Processed in 0.825015 second(s), 57 queries .

    快速回复 返回顶部 返回列表