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

这篇具有很好参考价值的文章主要介绍了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接收的数据设为外部可调用变量。具体代码如下:

#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信号来控制。接线如下图所示:

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

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

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

 这部分包括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模块大家请忽略,与本实验无关。

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

 目标转速为0

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

  目标转速为36

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

   目标转速为-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文章来源地址https://www.toymoban.com/news/detail-400125.html

到了这里,关于STM32F103C8T6实现CAN通讯与直流编码电机转速闭环控制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 舵机控制(STM32F103C8T6)

    舵机控制(STM32F103C8T6)

            本文是以 STM32F103C8T6 作为主控芯片,通过PB6端口输出PWM,实现控制180°舵机。 (一)概述         舵机是一种位置伺服驱动器器,是一种带有输出轴的小装置。当我们向伺服器发送一个控制信号时,输出轴就可以转到特定的位置。只在控制信号持续不变,伺服机构就

    2023年04月09日
    浏览(13)
  • STM32F103C8T6串口通信

    STM32F103C8T6串口通信

      首先来看一下需要操作的函数,以及配置的步骤: 图1                                                  图2   Code: usart.c #include \\\"usart.h\\\" void ustart_Init(void ) { GPIO_InitTypeDef GPIO_Init_Ustar ; // 定义输出端口TX的结构体对象 USART_InitTypeDef USTART_Init; // 定义串口初始化结构体对象

    2024年02月16日
    浏览(18)
  • STM32F103C8T6系统板

    STM32F103C8T6系统板

    1.电源部分 2.复位 3.晶振 4. 电源电路——防反接 有关二极管 漫谈二极管防电源反接电路 本次采用上图右下角的NMOS防反接电路。 电源电路——电源芯片 AMS1117是AMS公司的 ,LM1117是NS(美国国家半导体)的,LM1117要贵很多,所以一般的用AMS1117就可以了。 (以下分析参考STM32F10

    2024年02月02日
    浏览(6)
  • STM32F103C8T6板子介绍

    STM32F103C8T6板子介绍

    STM32简介 STM32是ST公司基于ARM Cortex-M内核开发的32位微控制器 STM32常应用在嵌入式领域,如智能车、无人机、机器人、无线通信、物联网、工业控制、娱乐电子产品等 STM32功能强大、性能优异、片上资源丰富、功耗低,是一款经典的嵌入式微控制器。  STM32F103C8T6 F1XX片上资源

    2024年02月11日
    浏览(14)
  • 【STM32 IAP技术实现】适合小白“食用”(以STM32F103C8T6为例)

    【STM32 IAP技术实现】适合小白“食用”(以STM32F103C8T6为例)

      想必大家对 单片机烧录 一词都不陌生,就是将程序下载到我们的板子(MCU)里面。常见的烧录方法有用Keil下载,或者是编译出Hex文件通过烧录软件(上位机例如:muisp、flymcu)、烧录器软件(例如:J-LINK、ST-LINK)烧录,从程序的角度来看通过烧录,它被“更新”了。

    2024年02月03日
    浏览(12)
  • stm32f103c8t6的外部中断

    stm32f103c8t6的外部中断

    在单片机中存在着中断系统,这个系统的逻辑和功能在51单片机中已经有所了解。 1.在32单片机中的内核有一个nvic 里面存放许多优先级的设定,外接许多中断源,比如“exti、tim、adc、usart等”接入之后,nvic再通过中断优先级进行排队,再内接入cpu中进行处理,这样子大大减少

    2024年02月09日
    浏览(16)
  • [STM32F103C8T6]ADC转换

    [STM32F103C8T6]ADC转换

    什么是ADC转换? ADC转换的全称是: Analog-to-Digital Converter ,指模拟 / 数字转换器 ADC的性能指标: ADC分辨率: SSA与VREF-一起接到地,DDA与VREF+接到3.3v,所以ADC转换的范围是0---3.3v 所以最后的ADC转换值应该是我们的测量值*分辨率    分辨率 = 3.3v/2^12 = (3.3/4096)   12位的转换器所

    2024年02月06日
    浏览(14)
  • STM32F103C8T6串口调试篇

    STM32F103C8T6串口调试篇

    项目开发中,当出现bug时,由于不知道某个变量的值,所以很难定位问题,针对此问题,串口调试脱颖而出。通过串口printf()实时将需要显示的信息打印出来,这样就很方便的定位问题。 串口设置方法 1.购买调试器pwlink2。参考STM32F103C8T6程序烧录方法_stm32f103c8t6如何烧录_流

    2024年02月12日
    浏览(14)
  • STM32F103C8T6移植FreeRTOS

    STM32F103C8T6移植FreeRTOS

    根据自己开发板型号下载地址:STM32相关内容下载地址 SDK直接下载地址:STM32直接下载地址 下载参考博客 FreeROTS下载地址 选用V9.0.0 版本 个人创建目录如下,可做参考 keil目录 链接头文件 • 修改堆栈大小 • 修改中断函数名 去掉stm32f10x_it.c终端函数 增加FreeRTOS中断 特别解释

    2024年02月12日
    浏览(14)
  • STM32F103C8T6 按键扫描输入

    STM32F103C8T6 按键扫描输入

    第一章 STM32F103C8T6 点亮LED灯 系列文章目录 前言 一、原理  1.按键类型  2.按键消抖 3.IO口输入配置 1)模拟输出 2)浮空输入模式 3)下拉输入模式(PULL DOWN) 4)上拉输入模式(PULL UP) 二、代码部分 main.c key.c key.h 总结         上一章我们成功入门了STM32F103C8T6,今天我们来

    2023年04月23日
    浏览(15)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包