【转载】STM32F103C8T6实现CAN通讯与直流编码电机转速闭环控制

本次实验目的是通过CAN发送目标转速与转向信息,接收方在接到CAN数据流后开始对直流编码电机进行转速闭环控制。我会尽量说清每个函数,注释每一句代码,希望能对大家有所帮助。

CAN通讯基于STM32自带CAN通讯模块,配合库函数使用十分方便。关于CAN通讯可以参考站内大佬的文章,讲解的十分透彻,末尾会提供链接。 

电机驱动基于定时器1和TB6612,转速测量基于定时器2和直流电机自带编码器。

另外,可通过三个LED来显示电机状态(正转,反转和停止);通过OLED来显示转速和其他信息(如PI输出)。

目录

1.CAN通讯驱动

2.直流电机驱动(PWM)

3.直流电机驱动(转向和转速控制)

4.编码器驱动

5.PI转速闭环控制

1.CAN通讯驱动

因为目前手上只有一个STM32的最小系统板,所以采用CAN通讯的回传模式。这部分的函数包括:CAN配置函数,CAN接收中断服务函数和CAN发送函数。为了简便,我将CAN接收的数据设为外部可调用变量。具体代码如下:


   
   
  1. #include "stm32f10x.h" // Device header
  2. uint32_t get_STID; //存储标准ID
  3. uint32_t get_EXID; //存储拓展ID
  4. uint8_t get_IDE; //标准/拓展ID识别
  5. uint8_t get_RTR; //数据/遥控帧识别
  6. uint8_t get_DLC; //数据长度识别
  7. uint8_t get_DATA[ 8]; //存储数据
  8. uint8_t get_FMI; //识别所经过的筛选器
  9. void CAN_INIT(void)//CAN初始化函数
  10. {
  11. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开GPIOA时钟
  12. GPIO_InitTypeDef GPIO_InitStructure; //GPIO配置
  13. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //配置为复用推挽
  14. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11|GPIO_Pin_12; //打开11,12引脚
  15. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  16. GPIO_Init(GPIOA,&GPIO_InitStructure);
  17. RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); //开CAN时钟
  18. CAN_InitTypeDef CAN_InitStructure; //配置CAN
  19. CAN_InitStructure.CAN_ABOM = DISABLE; //关动休眠
  20. CAN_InitStructure.CAN_AWUM = DISABLE; //关动唤醒
  21. CAN_InitStructure.CAN_BS1 = CAN_BS1_5tq; //TBS1长度5Tq
  22. CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq; //TBS2长度5Tq
  23. CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack; //工作模式选择(单机实验下位回传模式)
  24. CAN_InitStructure.CAN_NART = ENABLE; //禁止重发
  25. CAN_InitStructure.CAN_Prescaler = 80; //80分频(最终得到10kB速率)
  26. CAN_InitStructure.CAN_RFLM = DISABLE; //不锁定FIFO
  27. CAN_InitStructure.CAN_SJW = CAN_SJW_2tq; //最大调整步长2Tq
  28. CAN_InitStructure.CAN_TTCM = DISABLE; //关闭时间触发
  29. CAN_InitStructure.CAN_TXFP = DISABLE; //发送按ID优先级,不按邮箱顺序
  30. CAN_Init(CAN1, &CAN_InitStructure);
  31. CAN_FilterInitTypeDef CAN_FilterInitStructure; //CAN筛选器配置
  32. CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; //使能筛选器
  33. CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; //启用FIFO0
  34. CAN_FilterInitStructure.CAN_FilterIdHigh = 0x00;
  35. CAN_FilterInitStructure.CAN_FilterIdLow = 0x00;
  36. CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x00;
  37. CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x00; //实际上可通过任意ID
  38. CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; //掩码模式
  39. CAN_FilterInitStructure.CAN_FilterNumber = 0; //选择筛选器组0
  40. CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; //32位长度
  41. CAN_FilterInit(&CAN_FilterInitStructure);
  42. // NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//2个抢占2个响应,这句代码已在定时器中短通道配置部分给出,这里不再需要
  43. NVIC_InitTypeDef NVIC_InitStructure;
  44. NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn; //指向CAN接收中断,定义在中容量量产非互联型,需要注意一下
  45. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //通道使能
  46. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //最高抢占等级
  47. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //第2响应等级
  48. NVIC_Init(&NVIC_InitStructure);
  49. CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE); //使能接收中断
  50. }
  51. void USB_LP_CAN1_RX0_IRQHandler()//CAN接收中断服务函数。非互联型使用PA11、12引脚时,使用该中断函数名
  52. {
  53. CanRxMsg RxMessage; //接收数据结构体
  54. CAN_Receive(CAN1, CAN_FIFO0, &RxMessage); //接收函数
  55. get_DLC = RxMessage.DLC; //接下数据长度
  56. get_EXID = RxMessage.ExtId; //接下拓展ID
  57. get_FMI = RxMessage.FMI; //接下所经过的筛选器
  58. get_IDE = RxMessage.IDE; //标准/拓展ID识别
  59. get_RTR = RxMessage.RTR; //数据/遥控帧识别
  60. get_STID = RxMessage.StdId; //接下标准ID
  61. get_DATA[ 0] = RxMessage.Data[ 0];
  62. get_DATA[ 1] = RxMessage.Data[ 1];
  63. get_DATA[ 2] = RxMessage.Data[ 2];
  64. get_DATA[ 3] = RxMessage.Data[ 3];
  65. get_DATA[ 4] = RxMessage.Data[ 4];
  66. get_DATA[ 5] = RxMessage.Data[ 5];
  67. get_DATA[ 6] = RxMessage.Data[ 6];
  68. get_DATA[ 7] = RxMessage.Data[ 7];
  69. }
  70. void send_CAN(uint32_t STID, uint32_t EXID, uint8_t IDE, uint8_t RTR, uint8_t DLC, uint8_t DATA[8])//CAN发送函数
  71. {
  72. CanTxMsg TxMessage;
  73. TxMessage.DLC = DLC;
  74. TxMessage.StdId = STID;
  75. TxMessage.ExtId = EXID;
  76. TxMessage.IDE = IDE;
  77. TxMessage.RTR = RTR;
  78. TxMessage.Data[ 0] = DATA[ 0];
  79. TxMessage.Data[ 1] = DATA[ 1];
  80. TxMessage.Data[ 2] = DATA[ 2];
  81. TxMessage.Data[ 3] = DATA[ 3];
  82. TxMessage.Data[ 4] = DATA[ 4];
  83. TxMessage.Data[ 5] = DATA[ 5];
  84. TxMessage.Data[ 6] = DATA[ 6];
  85. TxMessage.Data[ 7] = DATA[ 7];
  86. CAN_Transmit(CAN1, &TxMessage);
  87. }

再将各个函数以及变量在头文件中声明一下:


   
   
  1. #ifndef __CAN_H
  2. #define __CAN_H
  3. extern uint32_t get_STID; //存储标准ID
  4. extern uint32_t get_EXID; //存储拓展ID
  5. extern uint8_t get_IDE; //标准/拓展ID识别
  6. extern uint8_t get_RTR; //数据/遥控帧识别
  7. extern uint8_t get_DLC; //数据长度识别
  8. extern uint8_t get_DATA[ 8]; //存储数据
  9. extern uint8_t get_FMI; //识别所经过的筛选器
  10. void CAN_INIT(void);
  11. void send_CAN(uint32_t STID,
  12. uint32_t EXID,
  13. uint8_t IDE,
  14. uint8_t RTR,
  15. uint8_t DLC,
  16. uint8_t DATA[8]); //CAN发送函数
  17. #endif

以上为CAN驱动部分,CAN发送函数可以在主函数中直接调用,通过CAN接收中断服务函数来读数据,并转移至内存。

2.直流电机驱动(PWM)

这部分包括PWM配置函数和PWM占空比调节函数。PWM基于定时器1,具体代码如下:


   
   
  1. #include "stm32f10x.h" // Device header
  2. void TIMER1_INIT(void)//配置带有中断和PWM功能的定时器
  3. {
  4. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开GPIOA时钟
  5. GPIO_InitTypeDef GPIO_InitStructure;
  6. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽
  7. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //10引脚
  8. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  9. GPIO_Init(GPIOA,&GPIO_InitStructure);
  10. RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //开定时器1时钟
  11. TIM_InternalClockConfig(TIM1); //定时器1采用内部时钟
  12. TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //时基单元配置
  13. TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //不分频
  14. TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //上升计数模式
  15. TIM_TimeBaseInitStructure.TIM_Period = 100 -1;
  16. TIM_TimeBaseInitStructure.TIM_Prescaler = 72 -1; //72分频配合100的计数周期,则每秒进10k次中断,即载频为10kHz
  17. TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
  18. TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
  19. TIM_OCInitTypeDef TIM_OCInitStructure; //比较器配置
  20. TIM_OCStructInit(&TIM_OCInitStructure); //初始化其他未设置的变量
  21. TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //高电平有效
  22. TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //极性不翻转
  23. TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能比较输出
  24. TIM_OCInitStructure.TIM_Pulse = 0; //比较器装载值
  25. TIM_OC3Init(TIM1, &TIM_OCInitStructure); //PA10引脚对应第三通道(OC3)
  26. TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE); //开定时器中断
  27. // TIM_BDTRInitTypeDef TIM_BDTRInitStructure;//配置死区,直流电机不需要
  28. // TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
  29. // TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable;
  30. // TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
  31. // TIM_BDTRInitStructure.TIM_DeadTime = 11;
  32. // TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
  33. // TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
  34. // TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
  35. // TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);
  36. TIM_ClearITPendingBit(TIM1, TIM_IT_Update);清中断标志位,防止上电/复位进中断
  37. TIM_Cmd(TIM1, ENABLE); //开定时器
  38. TIM_CtrlPWMOutputs(TIM1, ENABLE); //pwm主使能(高级定时器独有)
  39. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级配置
  40. NVIC_InitTypeDef NVIC_InitStructure;
  41. NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn; //指定到定时器1更新中断
  42. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //通道使能
  43. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //最高抢占优先级
  44. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //最高响应优先级
  45. NVIC_Init(&NVIC_InitStructure); //配置中断通道
  46. }
  47. void set_speed(uint8_t speed)//通过调用比较器赋值函数实现调节PWM占空比,从而实现调速
  48. {
  49. TIM_SetCompare3(TIM1, speed);
  50. }

对于定时器1(高级定时器),别忘了“TIM_CtrlPWMOutputs(TIM1, ENABLE);”。需要将PWM信号送入6612的PWMA引脚。最后将函数和变量在头文件中声明一下:


   
   
  1. #ifndef __TIMER1_H
  2. #define __TIMER1_H
  3. void TIMER1_INIT(void);
  4. void set_speed(uint8_t speed);
  5. #endif

3.直流电机驱动(转向和转速控制)

直流电机的转向控制是通过STM32的两个引脚对6612的AIN1和AIN2引脚发送高低电平来控制,转速是通过STM32的PA10引脚送入6612的PWMA引脚的PWM信号来控制。接线如下图所示:

 AIN1和AIN2真值表如下,这里的正反转是相对的,大家可根据需要搭配。

 这部分包括PA6,PA7(转向控制)引脚配置函数,CAN接收数据流解码函数和转速转速转向指定控制。特别的,CAN发送和接收最多8个元素的8位无符号整形数组,本实验用数组的第一个元素指定正反转和停止,0代表停止,1代表正转,2代表反转;用数组的第二个元素来表示转速大小。大家也可采用其他方法,我这里只是为了简单。

在开环状态下,PWM输出饱和时(输出100),直流电机转速rpm=126,所以在转速控制部分需要进行转换:spd=spd*100/126。具体代码如下:


   
   
  1. #include "stm32f10x.h" // Device header
  2. #include "Timer1.h"
  3. #include "CAN.h"
  4. void DCmotor_INIT(void)//转向控制引脚配置
  5. {
  6. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //配置H桥控制引脚(旋转方向)
  7. GPIO_InitTypeDef GPIO_InitStructure;
  8. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
  9. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  10. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_6| GPIO_Pin_7; //2,3,4脚控制三个LED,6,7脚进6612
  11. GPIO_Init(GPIOA, &GPIO_InitStructure);
  12. }
  13. int8_t speed_trans(uint8_t direction, uint8_t speed)//CAN接收数据流解码
  14. {
  15. int8_t spd;
  16. direction = get_DATA[ 0]; //转向/停止
  17. speed = get_DATA[ 1]; //转速大小
  18. if(direction== 1)
  19. {
  20. spd = speed;
  21. }
  22. else if(direction== 2)
  23. {
  24. spd = -speed;
  25. }
  26. else
  27. {
  28. spd = 0;
  29. }
  30. return spd;
  31. }
  32. void speed_CTL(int8_t spd)//调速/转向
  33. {
  34. spd = spd* 100/ 126;
  35. if(spd> 0) //正向旋转
  36. {
  37. GPIO_WriteBit(GPIOA, GPIO_Pin_6, Bit_SET); //转向控制
  38. GPIO_WriteBit(GPIOA, GPIO_Pin_7, Bit_RESET);
  39. set_speed(spd);
  40. GPIO_SetBits(GPIOA, GPIO_Pin_2); //LED指示灯
  41. GPIO_ResetBits(GPIOA, GPIO_Pin_3);
  42. GPIO_ResetBits(GPIOA, GPIO_Pin_4);
  43. }
  44. else if(spd< 0) //反向旋转
  45. {
  46. GPIO_WriteBit(GPIOA, GPIO_Pin_7, Bit_SET);
  47. GPIO_WriteBit(GPIOA, GPIO_Pin_6, Bit_RESET);
  48. set_speed(-spd);
  49. GPIO_SetBits(GPIOA, GPIO_Pin_4);
  50. GPIO_ResetBits(GPIOA, GPIO_Pin_3);
  51. GPIO_ResetBits(GPIOA, GPIO_Pin_2);
  52. }
  53. else //停
  54. {
  55. GPIO_WriteBit(GPIOA, GPIO_Pin_6, Bit_SET);
  56. GPIO_WriteBit(GPIOA, GPIO_Pin_7, Bit_SET);
  57. GPIO_SetBits(GPIOA, GPIO_Pin_3);
  58. GPIO_ResetBits(GPIOA, GPIO_Pin_4);
  59. GPIO_ResetBits(GPIOA, GPIO_Pin_2);
  60. }
  61. }

 最后将函数与变量在头文件声明一下:


   
   
  1. #ifndef __DCMOTOR_H
  2. #define __DCMOTOR_H
  3. void DCmotor_INIT(void);
  4. void speed_CTL(int8_t spd);
  5. int8_t speed_trans(uint8_t direction, uint8_t speed);
  6. #endif

4.编码器驱动

本次实验直流电机转速测量基于电机自带两相霍尔编码器和定时器2。AB两相编码器脉冲信号需接入定时器2的1,2通道(PA0和PA1)。特别的,编码器电源需接3.3V。

直流电机减速比位1:48,输出轴旋转1周,编码器AB两相一共输出2496个上升和下降沿,因为载频较高,所以每进100次中断(10ms)读取一次定时器2的计数值。当电机正向旋转(从零上升计数)时,输出轴的转速为rpm=计数值*100*60/2496。

当电机反向旋转(从5000向下计数)时,因为电机最高转速为rpm=126,所以计数器在10ms内,得到的数值不会小于4947(正转时不会大于53),所以可以通过判断计数值确定是否反转,反转时,转速为rpm=(计数值-5000)*100*60/2496。具体代码如下:


   
   
  1. #include "stm32f10x.h" // Device header
  2. #include "CAN.h"
  3. void encoder1_INIT(void)
  4. {
  5. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开GPIOA时钟
  6. GPIO_InitTypeDef GPIO_InitStructure;
  7. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
  8. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1; //开0,1引脚(定时器2的1,2通道)
  9. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  10. GPIO_Init(GPIOA,&GPIO_InitStructure); //GPIOA初始化
  11. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //开定时器2时钟
  12. TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  13. TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //不分频
  14. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
  15. TIM_TimeBaseStructure.TIM_Period = 5000; //计数到5000
  16. TIM_TimeBaseStructure.TIM_Prescaler = 0;
  17. TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
  18. TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
  19. TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12,
  20. TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); //定时器2编码计数配置,1,2通道同时计数
  21. TIM_ICInitTypeDef TIM_ICInitStructure;
  22. TIM_ICStructInit(&TIM_ICInitStructure);
  23. TIM_ICInitStructure.TIM_ICFilter = 0x00; //不使用滤波
  24. TIM_ICInit(TIM2, &TIM_ICInitStructure);
  25. TIM_SetCounter(TIM2, 0); //清零定时器计数值
  26. }
  27. int16_t get_rpm(void)//转速计算
  28. {
  29. int16_t rpm;
  30. uint16_t count;
  31. count = TIM_GetCounter(TIM2); //接定时器2编码计数值
  32. if(count> 2500) //如果反转
  33. {
  34. rpm = (count -5000)* 100* 60/ 2496;
  35. }
  36. else
  37. {
  38. rpm = (count)* 100* 60/ 2496;
  39. }
  40. return rpm;
  41. }

 最后将函数与变量在头文件声明一下:


   
   
  1. #ifndef __ENCODER_H
  2. #define __ENCODER_H
  3. void encoder1_INIT(void);
  4. int16_t get_rpm(void);
  5. #endif

5.PI转速闭环控制

PI转速闭环控制可表示为:PI_out=Kp*err+Ki*err_integral,PI调节的输出量直接送给转速控制。这部分与定时器1中断服务函数以及主函数放在了一页,具体代码如下:


   
   
  1. #include "stm32f10x.h" // Device header
  2. #include "Delay.h"
  3. #include "OLED.h"
  4. #include "CAN.h"
  5. #include "encoder.h"
  6. #include "DCmotor.h"
  7. #include "Timer1.h"
  8. uint16_t i;
  9. int16_t rpm;
  10. uint16_t count;
  11. int8_t spd_tgt,PI_out;
  12. float RPM,err,err_old_intg,err_intg; //PI调节相关参数
  13. float kp = 0.3;
  14. float ki = 0.015;
  15. float kp_out = 0;
  16. float PI_value = 0;
  17. int main()
  18. {
  19. uint8_t DATA[ 8] = { 1, 55, 2, 3, 4, 5, 6, 7}; //第一个参数控制转向/停止,第二个参数指定转速,限制100
  20. OLED_Init();
  21. CAN_INIT();
  22. ADC_INIT();
  23. TIMER1_INIT();
  24. DCmotor_INIT();
  25. encoder1_INIT();
  26. OLED_ShowString( 1, 1, "CAN Data:");
  27. OLED_ShowString( 3, 1, "Speed rpm:");
  28. send_CAN( 0x00, //标准帧ID(uint32)
  29. 0xFE, //扩展帧ID(uint32,但只有29位,0 to 0x1FFFFFFF)
  30. CAN_Id_Extended, //标准/拓展ID识别
  31. CAN_RTR_Data, //数据/遥控帧识别
  32. 8, //数据长度识别
  33. DATA); //8个字节数据
  34. while( 1)
  35. {
  36. OLED_ShowSignedNum( 2, 1, PI_out, 5);
  37. OLED_ShowSignedNum( 4, 1, rpm, 5);
  38. }
  39. }
  40. int8_t PI(int8_t target_value)//PI调节函数
  41. {
  42. RPM = get_rpm(); //获取测量转速
  43. err = target_value - RPM; //得到转速偏差
  44. kp_out = err*kp;
  45. err_intg = err_old_intg + err; //计算偏差的积累量
  46. err_old_intg = err_intg;
  47. PI_value = kp_out + ki*err_intg; //PI输出
  48. if(PI_value> 125) //限幅
  49. {
  50. PI_value = 125;
  51. }
  52. if(PI_value< ( -125))
  53. {
  54. PI_value= -125;
  55. }
  56. PI_out = PI_value;
  57. return PI_out;
  58. }
  59. void TIM1_UP_IRQHandler(void)//定时器1中断服务函数,执行转速闭环控制
  60. {
  61. if(TIM_GetITStatus(TIM1, TIM_IT_Update)==SET) //查定时器1中断标志位
  62. {
  63. i++;
  64. if(i>= 100) //每进100次中断,计算一次转速
  65. {
  66. rpm = get_rpm(); //调用转速计算
  67. spd_tgt = speed_trans(get_DATA[ 0], get_DATA[ 1]); //CAN数据解码为转速(大小,方向)
  68. PI_out = PI(spd_tgt); //调用PI调节
  69. speed_CTL(PI_out); //赋予转向/转速
  70. TIM_SetCounter(TIM2, 0); //定时器重载
  71. i = 0;
  72. }
  73. TIM_ClearITPendingBit(TIM1, TIM_IT_Update); //清定时器1中断标志位
  74. }
  75. }

实验效果如下图所示,中间的6050模块大家请忽略,与本实验无关。

 目标转速为0

 

  目标转速为36

 

   目标转速为-36

介于水平有限,文中的错误和不足还望大家批评指正,谢谢!

CAN通讯参考:

STM32 CAN通信详解_sgh0609的博客-CSDN博客_stm32can通信转:https://blog.csdn.net/CSDN_Yoa/article/details/81384924 并结合自己项目上CAN的配置觉得该文章很好希望帮助想了解CAN的网友。首先是自己工程中自己的东西分享给大家,后面内筒是转载他人的优秀文档。由于STM32中我使用的扩展标识符(ID)是29位(28~0),STM32的过滤器和掩码是32位分别映射到10~0、28~0、IDE、RTR、0,上;那么我们就可以根据这些内容建立自己的过滤和掩码。其中不建议使用CAN接收中的EXtID,因为掩码中为零.https://blog.csdn.net/sgh69/article/details/121243632?spm=1001.2014.3001.5506

## 标题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值