本次实验目的是通过CAN发送目标转速与转向信息,接收方在接到CAN数据流后开始对直流编码电机进行转速闭环控制。我会尽量说清每个函数,注释每一句代码,希望能对大家有所帮助。
CAN通讯基于STM32自带CAN通讯模块,配合库函数使用十分方便。关于CAN通讯可以参考站内大佬的文章,讲解的十分透彻,末尾会提供链接。
电机驱动基于定时器1和TB6612,转速测量基于定时器2和直流电机自带编码器。
另外,可通过三个LED来显示电机状态(正转,反转和停止);通过OLED来显示转速和其他信息(如PI输出)。
目录
1.CAN通讯驱动
因为目前手上只有一个STM32的最小系统板,所以采用CAN通讯的回传模式。这部分的函数包括:CAN配置函数,CAN接收中断服务函数和CAN发送函数。为了简便,我将CAN接收的数据设为外部可调用变量。具体代码如下:
-
#include "stm32f10x.h" // Device header
-
-
uint32_t get_STID;
//存储标准ID
-
uint32_t get_EXID;
//存储拓展ID
-
uint8_t get_IDE;
//标准/拓展ID识别
-
uint8_t get_RTR;
//数据/遥控帧识别
-
uint8_t get_DLC;
//数据长度识别
-
uint8_t get_DATA[
8];
//存储数据
-
uint8_t get_FMI;
//识别所经过的筛选器
-
-
void CAN_INIT(void)//CAN初始化函数
-
{
-
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//开GPIOA时钟
-
GPIO_InitTypeDef GPIO_InitStructure;
//GPIO配置
-
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//配置为复用推挽
-
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11|GPIO_Pin_12;
//打开11,12引脚
-
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
-
GPIO_Init(GPIOA,&GPIO_InitStructure);
-
-
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
//开CAN时钟
-
CAN_InitTypeDef CAN_InitStructure;
//配置CAN
-
CAN_InitStructure.CAN_ABOM = DISABLE;
//关动休眠
-
CAN_InitStructure.CAN_AWUM = DISABLE;
//关动唤醒
-
CAN_InitStructure.CAN_BS1 = CAN_BS1_5tq;
//TBS1长度5Tq
-
CAN_InitStructure.CAN_BS2 = CAN_BS2_3tq;
//TBS2长度5Tq
-
CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;
//工作模式选择(单机实验下位回传模式)
-
CAN_InitStructure.CAN_NART = ENABLE;
//禁止重发
-
CAN_InitStructure.CAN_Prescaler =
80;
//80分频(最终得到10kB速率)
-
CAN_InitStructure.CAN_RFLM = DISABLE;
//不锁定FIFO
-
CAN_InitStructure.CAN_SJW = CAN_SJW_2tq;
//最大调整步长2Tq
-
CAN_InitStructure.CAN_TTCM = DISABLE;
//关闭时间触发
-
CAN_InitStructure.CAN_TXFP = DISABLE;
//发送按ID优先级,不按邮箱顺序
-
CAN_Init(CAN1, &CAN_InitStructure);
-
-
CAN_FilterInitTypeDef CAN_FilterInitStructure;
//CAN筛选器配置
-
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
//使能筛选器
-
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
//启用FIFO0
-
CAN_FilterInitStructure.CAN_FilterIdHigh =
0x00;
-
CAN_FilterInitStructure.CAN_FilterIdLow =
0x00;
-
CAN_FilterInitStructure.CAN_FilterMaskIdHigh =
0x00;
-
CAN_FilterInitStructure.CAN_FilterMaskIdLow =
0x00;
//实际上可通过任意ID
-
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
//掩码模式
-
CAN_FilterInitStructure.CAN_FilterNumber =
0;
//选择筛选器组0
-
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
//32位长度
-
CAN_FilterInit(&CAN_FilterInitStructure);
-
-
// NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//2个抢占2个响应,这句代码已在定时器中短通道配置部分给出,这里不再需要
-
-
NVIC_InitTypeDef NVIC_InitStructure;
-
NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
//指向CAN接收中断,定义在中容量量产非互联型,需要注意一下
-
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//通道使能
-
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =
0;
//最高抢占等级
-
NVIC_InitStructure.NVIC_IRQChannelSubPriority =
1;
//第2响应等级
-
NVIC_Init(&NVIC_InitStructure);
-
-
CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
//使能接收中断
-
}
-
-
void USB_LP_CAN1_RX0_IRQHandler()//CAN接收中断服务函数。非互联型使用PA11、12引脚时,使用该中断函数名
-
{
-
CanRxMsg RxMessage;
//接收数据结构体
-
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
//接收函数
-
-
get_DLC = RxMessage.DLC;
//接下数据长度
-
get_EXID = RxMessage.ExtId;
//接下拓展ID
-
get_FMI = RxMessage.FMI;
//接下所经过的筛选器
-
get_IDE = RxMessage.IDE;
//标准/拓展ID识别
-
get_RTR = RxMessage.RTR;
//数据/遥控帧识别
-
get_STID = RxMessage.StdId;
//接下标准ID
-
-
get_DATA[
0] = RxMessage.Data[
0];
-
get_DATA[
1] = RxMessage.Data[
1];
-
get_DATA[
2] = RxMessage.Data[
2];
-
get_DATA[
3] = RxMessage.Data[
3];
-
get_DATA[
4] = RxMessage.Data[
4];
-
get_DATA[
5] = RxMessage.Data[
5];
-
get_DATA[
6] = RxMessage.Data[
6];
-
get_DATA[
7] = RxMessage.Data[
7];
-
}
-
-
void send_CAN(uint32_t STID, uint32_t EXID, uint8_t IDE, uint8_t RTR, uint8_t DLC, uint8_t DATA[8])//CAN发送函数
-
{
-
CanTxMsg TxMessage;
-
TxMessage.DLC = DLC;
-
TxMessage.StdId = STID;
-
TxMessage.ExtId = EXID;
-
TxMessage.IDE = IDE;
-
TxMessage.RTR = RTR;
-
-
TxMessage.Data[
0] = DATA[
0];
-
TxMessage.Data[
1] = DATA[
1];
-
TxMessage.Data[
2] = DATA[
2];
-
TxMessage.Data[
3] = DATA[
3];
-
TxMessage.Data[
4] = DATA[
4];
-
TxMessage.Data[
5] = DATA[
5];
-
TxMessage.Data[
6] = DATA[
6];
-
TxMessage.Data[
7] = DATA[
7];
-
-
CAN_Transmit(CAN1, &TxMessage);
-
}
再将各个函数以及变量在头文件中声明一下:
-
#ifndef __CAN_H
-
#define __CAN_H
-
-
extern uint32_t get_STID;
//存储标准ID
-
extern uint32_t get_EXID;
//存储拓展ID
-
extern uint8_t get_IDE;
//标准/拓展ID识别
-
extern uint8_t get_RTR;
//数据/遥控帧识别
-
extern uint8_t get_DLC;
//数据长度识别
-
extern uint8_t get_DATA[
8];
//存储数据
-
extern uint8_t get_FMI;
//识别所经过的筛选器
-
-
void CAN_INIT(void);
-
void send_CAN(uint32_t STID,
-
uint32_t EXID,
-
uint8_t IDE,
-
uint8_t RTR,
-
uint8_t DLC,
-
uint8_t DATA[8]);
//CAN发送函数
-
-
#endif
以上为CAN驱动部分,CAN发送函数可以在主函数中直接调用,通过CAN接收中断服务函数来读数据,并转移至内存。
2.直流电机驱动(PWM)
这部分包括PWM配置函数和PWM占空比调节函数。PWM基于定时器1,具体代码如下:
-
#include "stm32f10x.h" // Device header
-
-
-
void TIMER1_INIT(void)//配置带有中断和PWM功能的定时器
-
{
-
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//开GPIOA时钟
-
GPIO_InitTypeDef GPIO_InitStructure;
-
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//复用推挽
-
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
//10引脚
-
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
-
GPIO_Init(GPIOA,&GPIO_InitStructure);
-
-
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
//开定时器1时钟
-
TIM_InternalClockConfig(TIM1);
//定时器1采用内部时钟
-
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
//时基单元配置
-
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//不分频
-
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
//上升计数模式
-
TIM_TimeBaseInitStructure.TIM_Period =
100
-1;
-
TIM_TimeBaseInitStructure.TIM_Prescaler =
72
-1;
//72分频配合100的计数周期,则每秒进10k次中断,即载频为10kHz
-
TIM_TimeBaseInitStructure.TIM_RepetitionCounter =
0;
-
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure);
-
-
TIM_OCInitTypeDef TIM_OCInitStructure;
//比较器配置
-
TIM_OCStructInit(&TIM_OCInitStructure);
//初始化其他未设置的变量
-
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
//高电平有效
-
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//极性不翻转
-
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//使能比较输出
-
TIM_OCInitStructure.TIM_Pulse =
0;
//比较器装载值
-
TIM_OC3Init(TIM1, &TIM_OCInitStructure);
//PA10引脚对应第三通道(OC3)
-
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
//开定时器中断
-
-
// TIM_BDTRInitTypeDef TIM_BDTRInitStructure;//配置死区,直流电机不需要
-
// TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
-
// TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable;
-
// TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
-
// TIM_BDTRInitStructure.TIM_DeadTime = 11;
-
// TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
-
// TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
-
// TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
-
// TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);
-
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);清中断标志位,防止上电/复位进中断
-
TIM_Cmd(TIM1, ENABLE);
//开定时器
-
TIM_CtrlPWMOutputs(TIM1, ENABLE);
//pwm主使能(高级定时器独有)
-
-
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//中断优先级配置
-
-
NVIC_InitTypeDef NVIC_InitStructure;
-
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
//指定到定时器1更新中断
-
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//通道使能
-
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =
0;
//最高抢占优先级
-
NVIC_InitStructure.NVIC_IRQChannelSubPriority =
0;
//最高响应优先级
-
NVIC_Init(&NVIC_InitStructure);
//配置中断通道
-
-
-
}
-
-
void set_speed(uint8_t speed)//通过调用比较器赋值函数实现调节PWM占空比,从而实现调速
-
{
-
TIM_SetCompare3(TIM1, speed);
-
}
对于定时器1(高级定时器),别忘了“TIM_CtrlPWMOutputs(TIM1, ENABLE);”。需要将PWM信号送入6612的PWMA引脚。最后将函数和变量在头文件中声明一下:
-
#ifndef __TIMER1_H
-
#define __TIMER1_H
-
-
void TIMER1_INIT(void);
-
void set_speed(uint8_t speed);
-
-
#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。具体代码如下:
-
#include "stm32f10x.h" // Device header
-
#include "Timer1.h"
-
#include "CAN.h"
-
-
void DCmotor_INIT(void)//转向控制引脚配置
-
{
-
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//配置H桥控制引脚(旋转方向)
-
-
GPIO_InitTypeDef GPIO_InitStructure;
-
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
//推挽输出
-
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
-
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
-
GPIO_Init(GPIOA, &GPIO_InitStructure);
-
}
-
-
int8_t speed_trans(uint8_t direction, uint8_t speed)//CAN接收数据流解码
-
{
-
int8_t spd;
-
direction = get_DATA[
0];
//转向/停止
-
speed = get_DATA[
1];
//转速大小
-
if(direction==
1)
-
{
-
spd = speed;
-
}
-
else
if(direction==
2)
-
{
-
spd = -speed;
-
}
-
else
-
{
-
spd =
0;
-
}
-
return spd;
-
}
-
-
void speed_CTL(int8_t spd)//调速/转向
-
{
-
spd = spd*
100/
126;
-
if(spd>
0)
//正向旋转
-
{
-
GPIO_WriteBit(GPIOA, GPIO_Pin_6, Bit_SET);
//转向控制
-
GPIO_WriteBit(GPIOA, GPIO_Pin_7, Bit_RESET);
-
set_speed(spd);
-
GPIO_SetBits(GPIOA, GPIO_Pin_2);
//LED指示灯
-
GPIO_ResetBits(GPIOA, GPIO_Pin_3);
-
GPIO_ResetBits(GPIOA, GPIO_Pin_4);
-
}
-
else
if(spd<
0)
//反向旋转
-
{
-
GPIO_WriteBit(GPIOA, GPIO_Pin_7, Bit_SET);
-
GPIO_WriteBit(GPIOA, GPIO_Pin_6, Bit_RESET);
-
set_speed(-spd);
-
GPIO_SetBits(GPIOA, GPIO_Pin_4);
-
GPIO_ResetBits(GPIOA, GPIO_Pin_3);
-
GPIO_ResetBits(GPIOA, GPIO_Pin_2);
-
}
-
else
//停
-
{
-
GPIO_WriteBit(GPIOA, GPIO_Pin_6, Bit_SET);
-
GPIO_WriteBit(GPIOA, GPIO_Pin_7, Bit_SET);
-
GPIO_SetBits(GPIOA, GPIO_Pin_3);
-
GPIO_ResetBits(GPIOA, GPIO_Pin_4);
-
GPIO_ResetBits(GPIOA, GPIO_Pin_2);
-
}
-
}
最后将函数与变量在头文件声明一下:
-
#ifndef __DCMOTOR_H
-
#define __DCMOTOR_H
-
-
void DCmotor_INIT(void);
-
void speed_CTL(int8_t spd);
-
int8_t speed_trans(uint8_t direction, uint8_t speed);
-
-
#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。具体代码如下:
-
#include "stm32f10x.h" // Device header
-
#include "CAN.h"
-
-
void encoder1_INIT(void)
-
{
-
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//开GPIOA时钟
-
-
GPIO_InitTypeDef GPIO_InitStructure;
-
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
//浮空输入
-
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
//开0,1引脚(定时器2的1,2通道)
-
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
-
GPIO_Init(GPIOA,&GPIO_InitStructure);
//GPIOA初始化
-
-
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//开定时器2时钟
-
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
-
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//不分频
-
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
//向上计数
-
TIM_TimeBaseStructure.TIM_Period =
5000;
//计数到5000
-
TIM_TimeBaseStructure.TIM_Prescaler =
0;
-
TIM_TimeBaseStructure.TIM_RepetitionCounter =
0;
-
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
-
-
TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12,
-
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
//定时器2编码计数配置,1,2通道同时计数
-
TIM_ICInitTypeDef TIM_ICInitStructure;
-
TIM_ICStructInit(&TIM_ICInitStructure);
-
TIM_ICInitStructure.TIM_ICFilter =
0x00;
//不使用滤波
-
TIM_ICInit(TIM2, &TIM_ICInitStructure);
-
TIM_SetCounter(TIM2,
0);
//清零定时器计数值
-
}
-
-
int16_t get_rpm(void)//转速计算
-
{
-
int16_t rpm;
-
uint16_t count;
-
count = TIM_GetCounter(TIM2);
//接定时器2编码计数值
-
if(count>
2500)
//如果反转
-
{
-
rpm = (count
-5000)*
100*
60/
2496;
-
}
-
else
-
{
-
rpm = (count)*
100*
60/
2496;
-
}
-
-
return rpm;
-
}
最后将函数与变量在头文件声明一下:
-
#ifndef __ENCODER_H
-
#define __ENCODER_H
-
-
void encoder1_INIT(void);
-
int16_t get_rpm(void);
-
-
#endif
5.PI转速闭环控制
PI转速闭环控制可表示为:PI_out=Kp*err+Ki*err_integral,PI调节的输出量直接送给转速控制。这部分与定时器1中断服务函数以及主函数放在了一页,具体代码如下:
-
#include "stm32f10x.h" // Device header
-
#include "Delay.h"
-
#include "OLED.h"
-
#include "CAN.h"
-
#include "encoder.h"
-
#include "DCmotor.h"
-
#include "Timer1.h"
-
-
uint16_t i;
-
int16_t rpm;
-
uint16_t count;
-
int8_t spd_tgt,PI_out;
-
float RPM,err,err_old_intg,err_intg;
//PI调节相关参数
-
float kp =
0.3;
-
float ki =
0.015;
-
float kp_out =
0;
-
float PI_value =
0;
-
-
int main()
-
{
-
uint8_t DATA[
8] = {
1,
55,
2,
3,
4,
5,
6,
7};
//第一个参数控制转向/停止,第二个参数指定转速,限制100
-
-
OLED_Init();
-
CAN_INIT();
-
ADC_INIT();
-
TIMER1_INIT();
-
DCmotor_INIT();
-
encoder1_INIT();
-
-
OLED_ShowString(
1,
1,
"CAN Data:");
-
OLED_ShowString(
3,
1,
"Speed rpm:");
-
-
send_CAN(
0x00,
//标准帧ID(uint32)
-
0xFE,
//扩展帧ID(uint32,但只有29位,0 to 0x1FFFFFFF)
-
CAN_Id_Extended,
//标准/拓展ID识别
-
CAN_RTR_Data,
//数据/遥控帧识别
-
8,
//数据长度识别
-
DATA);
//8个字节数据
-
-
while(
1)
-
{
-
OLED_ShowSignedNum(
2,
1, PI_out,
5);
-
OLED_ShowSignedNum(
4,
1, rpm,
5);
-
}
-
}
-
-
int8_t PI(int8_t target_value)//PI调节函数
-
{
-
RPM = get_rpm();
//获取测量转速
-
err = target_value - RPM;
//得到转速偏差
-
kp_out = err*kp;
-
err_intg = err_old_intg + err;
//计算偏差的积累量
-
err_old_intg = err_intg;
-
PI_value = kp_out + ki*err_intg;
//PI输出
-
-
if(PI_value>
125)
//限幅
-
{
-
PI_value =
125;
-
}
-
if(PI_value< (
-125))
-
{
-
PI_value=
-125;
-
}
-
PI_out = PI_value;
-
return PI_out;
-
}
-
-
void TIM1_UP_IRQHandler(void)//定时器1中断服务函数,执行转速闭环控制
-
{
-
if(TIM_GetITStatus(TIM1, TIM_IT_Update)==SET)
//查定时器1中断标志位
-
{
-
i++;
-
if(i>=
100)
//每进100次中断,计算一次转速
-
{
-
rpm = get_rpm();
//调用转速计算
-
spd_tgt = speed_trans(get_DATA[
0], get_DATA[
1]);
//CAN数据解码为转速(大小,方向)
-
PI_out = PI(spd_tgt);
//调用PI调节
-
speed_CTL(PI_out);
//赋予转向/转速
-
TIM_SetCounter(TIM2,
0);
//定时器重载
-
i =
0;
-
}
-
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
//清定时器1中断标志位
-
}
-
}
实验效果如下图所示,中间的6050模块大家请忽略,与本实验无关。
目标转速为0
目标转速为36
目标转速为-36
介于水平有限,文中的错误和不足还望大家批评指正,谢谢!
CAN通讯参考: