linux下对/sys/class/gpio中的gpio的控制 (转)-----ircut

本文详细介绍在Linux环境下如何通过/sys/class/gpio接口控制GPIO,包括GPIO的配置、方向设置、输出电平控制及关闭流程。并提供了C语言实现的LED闪烁和按键输入读取的示例代码。

linux下对/sys/class/gpio中的gpio的控制 (转)

在嵌入式设备中对GPIO的操作是最基本的操作。一般的做法是写一个单独驱动程序,网上大多数的例子都是这样的。其实linux下面有一个通用的GPIO操作接口,那就是我要介绍的 “/sys/class/gpio” 方式。

首先,看看系统中有没有“/sys/class/gpio”这个文件夹。如果没有请在编译内核的时候加入 Device Drivers —> GPIO Support —> /sys/class/gpio/… (sysfs interface)。

/sys/class/gpio 的使用说明:

01 gpio_operation 通过/sys/文件接口操作IO端口 GPIO到文件系统的映射

02 * 控制GPIO的目录位于/sys/class/gpio

03 * /sys/class/gpio/export文件用于通知系统需要导出控制的GPIO引脚编号

04 * /sys/class/gpio/unexport 用于通知系统取消导出

05 * /sys/class/gpio/gpiochipX目录保存系统中GPIO寄存器的信息,包括每个寄存器控制引脚的起始编号 base,寄存器名称,引脚总数 导出一个引脚的操作步骤

06 * 首先计算此引脚编号,引脚编号 = 控制引脚的寄存器基数 + 控制引脚寄存器位数

07 * 向/sys/class/gpio/export写入此编号,比如12号引脚,在shell中可以通过以下命令实现,

echo 12 > /sys/class/gpio/export

命令成功后生成/sys/class/gpio/gpio12目录,如果没有出现相应的目录,说明此引脚不可导出:

08

09 * direction文件,定义输入输入方向,可以通过下面命令定义为输出

10 echo out > /sys/class/gpio/gpio12/direction

11 * direction接受的参数:in, out, high, low。high/low同时设置方向为输出,

并将value设置为相应的1/0。

12 * value文件是端口的数值,为1或0.

13 echo 1 >/sys/class/gpio/gpio12/value

编写控制程序

GPIO的配置文件在/sys/class/gpio目录下,控制程序可以分为四个步骤:

配置GPIO:在/sys/class/gpio目录下可以看到文件export,调用该文件以实现配置。该文件对所有GPIO编号,从0开始。GPIOn_x的编号为32*n+x,例如此处用的GPIO1_6的编号为32*1+6=38。在终端输入:# echo "38" > /sys/class/gpio/export,在此回到目录/sys/class/gpio下,可以看到产生了一个新的目录./gpio38,里面包含了该IO口的输入输出设置等配置文件。注意:export文件只有root写权限,执行上述命令或者以后用C编写的可执行文件要以ROOT身份执行。
设置GPIO的方向(输入输出):在终端输入:# echo "out" > /sys/class/gpio/gpio38/direction,即设置该GPIO为输出。
设置GPIO的输出电平:在终端输入:#echo "1" > /sys/class/gpio/gpio38/value,即设置GPIO输出高电平,输入echo "0" > /sys/class/gpio/gpio38/value设置GPIO输出低电平。
关闭GPIO:在终端输入:#echo "38" > /sys/class/gpio/unexport,即删除GPIO配置文件,可以看到目录gpio38已经被删除。

下面是C语言编写的GPIO控制例程,实现LED的每隔一秒闪烁一次。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
FILE *p=NULL;
int i=0;
p = fopen("/sys/class/gpio/export","w");
fprintf(p,"%d",38);
fclose(p);
p = fopen("/sys/class/gpio/gpio38/direction","w");
fprintf(p,"out");
fclose(p);
for(i=0;i<100;i++)
{
p = fopen("/sys/class/gpio/gpio38/value","w");
fprintf(p,"%d",1);
sleep(1);
fclose(p);
p = fopen("/sys/class/gpio/gpio38/value","w");
fprintf(p,"%d",0);
sleep(1);
fclose(p);
}
p = fopen("/sys/class/gpio/unexport","w");
fprintf(p,"%d",38);
fclose(p);
return 0;
}
 
 
下面实现按键输入的读取操作
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>


int main(void)
{
FILE *p=NULL;
char i[100]={0,};

p = fopen("/sys/class/gpio/export","w");
fprintf(p,"%d",161);
fclose(p);
p = fopen("/sys/class/gpio/gpio161/direction","w");
fprintf(p,"in");  //配置成输入
fclose(p);

while(1)
{
//以只读方式打开
p = fopen("/sys/class/gpio/gpio161/value","r");
//使文件读写定位到0位置
fseek(p , 0 , 0);
#if 0
//将文件内容输出到存储器i中,注意要以字符串的方式,否则会出错
fscanf(p,"%s",i);
#else
//从文件中读出数据到存储器i中
fread(i , 1, 1 ,p);
#endif
//以字符的方式将读到的值打印出
printf("key = %c \r\n",i[0]);
sleep(1);
//注意这里必须要关闭,然后再次读时再重新打开,这样只面的内容才会更新
fclose(p);
}
return 0;
}
 
/*********************************************************************************/
查看当前系统下已使用的GPIO:
# cat /sys/kernel/debug/gpio
 
用户态使用gpio监听中断      

首先需要将该gpio配置为中断

echo  "rising" > /sys/class/gpio/gpio12/edge       

以下是伪代码

int gpio_id;

struct pollfd fds[1];

 

gpio_fd = open("/sys/class/gpio/gpio12/value",O_RDONLY);

if( gpio_fd == -1 )

   err_print("gpio open");

fds[0].fd = gpio_fd;

fds[0].events  = POLLPRI;

ret = read(gpio_fd,buff,10);

if( ret == -1 )

    err_print("read");

while(1){

     ret = poll(fds,1,-1);

     if( ret == -1 )

         err_print("poll");

       if( fds[0].revents & POLLPRI){

           ret = lseek(gpio_fd,0,SEEK_SET);

           if( ret == -1 )

               err_print("lseek");

           ret = read(gpio_fd,buff,10);

           if( ret == -1 )

               err_print("read");

            /*此时表示已经监听到中断触发了,该干事了*/

            ...............

    }

}

记住使用poll()函数,设置事件监听类型为POLLPRI和POLLERR在poll()返回后,使用lseek()移动到文件开头读取新的值或者关闭它再重新打开读取新值。必须这样做否则poll函数会总是返回。
#include "DengFOC.h" int Sensor_DIR=-1; //传感器方向 int Motor_PP=7; //电机极对数 void setup() { Serial.begin(115200); DFOC_Vbus(12.6); //设定驱动器供电电压 DFOC_alignSensor(Motor_PP,Sensor_DIR); } void loop() { //设置速度环PID DFOC_M0_SET_VEL_PID(0.005,0.00,0,0); //设置速度 DFOC_M0_setVelocity(serial_motor_target()); //接收串口 serialReceiveUserCommand(); } #include "AS5600.h" #include "Wire.h" #include <Arduino.h> #define _2PI 6.28318530718f // AS5600 相关 double Sensor_AS5600::getSensorAngle() { uint8_t angle_reg_msb = 0x0C; byte readArray[2]; uint16_t readValue = 0; wire->beginTransmission(0x36); wire->write(angle_reg_msb); wire->endTransmission(false); wire->requestFrom(0x36, (uint8_t)2); for (byte i=0; i < 2; i++) { readArray[i] = wire->read(); } int _bit_resolution=12; int _bits_used_msb=11-7; float cpr = pow(2, _bit_resolution); int lsb_used = _bit_resolution - _bits_used_msb; uint8_t lsb_mask = (uint8_t)( (2 << lsb_used) - 1 ); uint8_t msb_mask = (uint8_t)( (2 << _bits_used_msb) - 1 ); readValue = ( readArray[1] & lsb_mask ); readValue += ( ( readArray[0] & msb_mask ) << lsb_used ); return (readValue/ (float)cpr) * _2PI; } //AS5600 相关 //=========角度处理相关============= Sensor_AS5600::Sensor_AS5600(int Mot_Num) { _Mot_Num=Mot_Num; //使得 Mot_Num 可以统一在该文件调用 } void Sensor_AS5600::Sensor_init(TwoWire* _wire) { wire=_wire; wire->begin(); //电机Sensor delay(500); getSensorAngle(); delayMicroseconds(1); vel_angle_prev = getSensorAngle(); vel_angle_prev_ts = micros(); delay(1); getSensorAngle(); delayMicroseconds(1); angle_prev = getSensorAngle(); angle_prev_ts = micros(); } void Sensor_AS5600::Sensor_update() { float val = getSensorAngle(); angle_prev_ts = micros(); float d_angle = val - angle_prev; // 圈数检测 if(abs(d_angle) > (0.8f*_2PI) ) full_rotations += ( d_angle > 0 ) ? -1 : 1; angle_prev = val; } float Sensor_AS5600::getMechanicalAngle() { return angle_prev; } float Sensor_AS5600::getAngle(){ return (float)full_rotations * _2PI + angle_prev; } float Sensor_AS5600::getVelocity() { // 计算采样时间 float Ts = (angle_prev_ts - vel_angle_prev_ts)*1e-6; // 快速修复奇怪的情况(微溢出) if(Ts <= 0) Ts = 1e-3f; // 速度计算 float vel = ( (float)(full_rotations - vel_full_rotations)*_2PI + (angle_prev - vel_angle_prev) ) / Ts; // 保存变量以待将来使用 vel_angle_prev = angle_prev; vel_full_rotations = full_rotations; vel_angle_prev_ts = angle_prev_ts; return vel; } #include <Arduino.h> #include "Wire.h" class Sensor_AS5600 { public: Sensor_AS5600(int Mot_Num); void Sensor_init(TwoWire* _wire = &Wire); void Sensor_update(); float getAngle(); float getVelocity(); float getMechanicalAngle(); double getSensorAngle(); private: int _Mot_Num; //AS5600 变量定义 //int sensor_direction=1; //编码器旋方向定义 float angle_prev=0; // 最后一次调用 getSensorAngle() 的输出结果,用于得到完整的圈数和速度 long angle_prev_ts=0; // 上次调用 getAngle 的时间戳 float vel_angle_prev=0; // 最后一次调用 getVelocity 时的角度 long vel_angle_prev_ts=0; // 最后速度计算时间戳 int32_t full_rotations=0; // 总圈数计数 int32_t vel_full_rotations=0; //用于速度计算的先前完整旋圈数 TwoWire* wire; }; #include <Arduino.h> #include "AS5600.h" #include "lowpass_filter.h" #include "pid.h" #define _constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt))) float voltage_power_supply; float Ualpha,Ubeta=0,Ua=0,Ub=0,Uc=0; #define _3PI_2 4.71238898038f float zero_electric_angle=0; int PP=1,DIR=1; int pwmA = 32; int pwmB = 33; int pwmC = 25; //低通滤波初始化 LowPassFilter M0_Vel_Flt = LowPassFilter(0.01); // Tf = 10ms //M0速度环 //PID PIDController vel_loop_M0 = PIDController{.P = 2, .I = 0, .D = 0, .ramp = 100000, .limit = voltage_power_supply/2}; PIDController angle_loop_M0 = PIDController{.P = 2, .I = 0, .D = 0, .ramp = 100000, .limit = 100}; //AS5600 Sensor_AS5600 S0=Sensor_AS5600(0); TwoWire S0_I2C = TwoWire(0); //=================PID 设置函数================= //速度PID void DFOC_M0_SET_VEL_PID(float P,float I,float D,float ramp) //M0角度环PID设置 { vel_loop_M0.P=P; vel_loop_M0.I=I; vel_loop_M0.D=D; vel_loop_M0.output_ramp=ramp; } //角度PID void DFOC_M0_SET_ANGLE_PID(float P,float I,float D,float ramp) //M0角度环PID设置 { angle_loop_M0.P=P; angle_loop_M0.I=I; angle_loop_M0.D=D; angle_loop_M0.output_ramp=ramp; } //M0速度PID接口 float DFOC_M0_VEL_PID(float error) //M0速度环 { return vel_loop_M0(error); } //M0角度PID接口 float DFOC_M0_ANGLE_PID(float error) { return angle_loop_M0(error); } //初始变量及函数定义 #define _constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt))) //宏定义实现的一个约束函数,用于限制一个值的范围。 //具体来说,该宏定义的名称为 _constrain,接受三个参数 amt、low 和 high,分别表示要限制的值、最小值和最大值。该宏定义的实现使用了三元运算符,根据 amt 是否小于 low 或大于 high,返回其中的最大或最小值,或者返回原值。 //换句话说,如果 amt 小于 low,则返回 low;如果 amt 大于 high,则返回 high;否则返回 amt。这样,_constrain(amt, low, high) 就会将 amt 约束在 [low, high] 的范围内。1 // 归一化角度到 [0,2PI] float _normalizeAngle(float angle){ float a = fmod(angle, 2*PI); //取余运算可以用于归一化,列出特殊值例子算便知 return a >= 0 ? a : (a + 2*PI); //三目运算符。格式:condition ? expr1 : expr2 //其中,condition 是要求值的条件表达式,如果条件成立,则返回 expr1 的值,否则返回 expr2 的值。可以将三目运算符视为 if-else 语句的简化形式。 //fmod 函数的余数的符号与除数相同。因此,当 angle 的值为负数时,余数的符号将与 _2PI 的符号相反。也就是说,如果 angle 的值小于 0 且 _2PI 的值为正数,则 fmod(angle, _2PI) 的余数将为负数。 //例如,当 angle 的值为 -PI/2,_2PI 的值为 2PI 时,fmod(angle, _2PI) 将返回一个负数。在这种情况下,可以通过将负数的余数加上 _2PI 来将角度归一化到 [0, 2PI] 的范围内,以确保角度的值始终为正数。 } // 设置PWM控制器输出 void setPwm(float Ua, float Ub, float Uc) { // 限制上限 Ua = _constrain(Ua, 0.0f, voltage_power_supply); Ub = _constrain(Ub, 0.0f, voltage_power_supply); Uc = _constrain(Uc, 0.0f, voltage_power_supply); // 计算占空比 // 限制占空比从0到1 float dc_a = _constrain(Ua / voltage_power_supply, 0.0f , 1.0f ); float dc_b = _constrain(Ub / voltage_power_supply, 0.0f , 1.0f ); float dc_c = _constrain(Uc / voltage_power_supply, 0.0f , 1.0f ); //写入PWMPWM 0 1 2 通道 ledcWrite(0, dc_a*255); ledcWrite(1, dc_b*255); ledcWrite(2, dc_c*255); } void setTorque(float Uq,float angle_el) { S0.Sensor_update(); //更新传感器数值 Uq=_constrain(Uq,-(voltage_power_supply)/2,(voltage_power_supply)/2); float Ud=0; angle_el = _normalizeAngle(angle_el); // 帕克逆变换 Ualpha = -Uq*sin(angle_el); Ubeta = Uq*cos(angle_el); // 克拉克逆变换 Ua = Ualpha + voltage_power_supply/2; Ub = (sqrt(3)*Ubeta-Ualpha)/2 + voltage_power_supply/2; Uc = (-Ualpha-sqrt(3)*Ubeta)/2 + voltage_power_supply/2; setPwm(Ua,Ub,Uc); } void DFOC_Vbus(float power_supply) { voltage_power_supply=power_supply; pinMode(pwmA, OUTPUT); pinMode(pwmB, OUTPUT); pinMode(pwmC, OUTPUT); ledcAttachPin(pwmA, 0); ledcAttachPin(pwmB, 1); ledcAttachPin(pwmC, 2); ledcSetup(0, 30000, 8); //pwm频道, 频率, 精度 ledcSetup(1, 30000, 8); //pwm频道, 频率, 精度 ledcSetup(2, 30000, 8); //pwm频道, 频率, 精度 Serial.println("完成PWM初始化设置"); //AS5600 S0_I2C.begin(19,18, 400000UL); S0.Sensor_init(&S0_I2C); //初始化编码器0 Serial.println("编码器加载完毕"); //PID 加载 vel_loop_M0 = PIDController{.P = 2, .I = 0, .D = 0, .ramp = 100000, .limit = voltage_power_supply/2}; } float _electricalAngle(){ return _normalizeAngle((float)(DIR * PP) * S0.getMechanicalAngle()-zero_electric_angle); } void DFOC_alignSensor(int _PP,int _DIR) { PP=_PP; DIR=_DIR; setTorque(3, _3PI_2); //起劲 delay(1000); S0.Sensor_update(); //更新角度,方便下面电角度读取 zero_electric_angle=_electricalAngle(); setTorque(0, _3PI_2); //松劲(解除校准) Serial.print("0电角度:");Serial.println(zero_electric_angle); } float DFOC_M0_Angle() { return DIR*S0.getAngle(); } //无滤波 //float DFOC_M0_Velocity() //{ // return DIR*S0.getVelocity(); //} //有滤波 float DFOC_M0_Velocity() { //获取速度数据并滤波 float vel_M0_ori=S0.getVelocity(); float vel_M0_flit=M0_Vel_Flt(DIR*vel_M0_ori); return vel_M0_flit; //考虑方向 } //==============串口接收============== float motor_target; int commaPosition; String serialReceiveUserCommand() { // a string to hold incoming data static String received_chars; String command = ""; while (Serial.available()) { // get the new byte: char inChar = (char)Serial.read(); // add it to the string buffer: received_chars += inChar; // end of user input if (inChar == '\n') { // execute the user command command = received_chars; commaPosition = command.indexOf('\n');//检测字符串中的逗号 if(commaPosition != -1)//如果有逗号存在就向下执行 { motor_target = command.substring(0,commaPosition).toDouble(); //电机角度 Serial.println(motor_target); } // reset the command buffer received_chars = ""; } } return command; } float serial_motor_target() { return motor_target; } //================简易接口函数================ void DFOC_M0_set_Velocity_Angle(float Target) { setTorque(DFOC_M0_VEL_PID(DFOC_M0_ANGLE_PID((Target-DFOC_M0_Angle())*180/PI)),_electricalAngle()); //角度闭环 } void DFOC_M0_setVelocity(float Target) { setTorque(DFOC_M0_VEL_PID((serial_motor_target()-DFOC_M0_Velocity())*180/PI),_electricalAngle()); //速度闭环 } void DFOC_M0_set_Force_Angle(float Target) //力位 { setTorque(DFOC_M0_ANGLE_PID((Target-DFOC_M0_Angle())*180/PI),_electricalAngle()); } void DFOC_M0_setTorque(float Target) { setTorque(Target,_electricalAngle()); } void setPwm(float Ua, float Ub, float Uc); float setTorque(float Uq,float angle_el); float _normalizeAngle(float angle); void DFOC_Vbus(float power_supply); void DFOC_alignSensor(int _PP,int _DIR); float _electricalAngle(); float serial_motor_target(); String serialReceiveUserCommand(); //传感器读取 float DFOC_M0_Velocity(); float DFOC_M0_Angle(); //PID void DFOC_M0_SET_ANGLE_PID(float P,float I,float D,float ramp); void DFOC_M0_SET_VEL_PID(float P,float I,float D,float ramp); float DFOC_M0_VEL_PID(float error); float DFOC_M0_ANGLE_PID(float error); //接口函数 void DFOC_M0_set_Velocity_Angle(float Target); void DFOC_M0_setVelocity(float Target); void DFOC_M0_set_Force_Angle(float Target); void DFOC_M0_setTorque(float Target); #include "lowpass_filter.h" LowPassFilter::LowPassFilter(float time_constant) : Tf(time_constant) , y_prev(0.0f) { timestamp_prev = micros(); } float LowPassFilter::operator() (float x) { unsigned long timestamp = micros(); float dt = (timestamp - timestamp_prev)*1e-6f; if (dt < 0.0f ) dt = 1e-3f; else if(dt > 0.3f) { y_prev = x; timestamp_prev = timestamp; return x; } float alpha = Tf/(Tf + dt); float y = alpha*y_prev + (1.0f - alpha)*x; y_prev = y; timestamp_prev = timestamp; return y; } #include <Arduino.h> #ifndef LOWPASS_FILTER_H #define LOWPASS_FILTER_H /** * 低通滤波器类 */ class LowPassFilter { public: /** * @Tf - 低通滤波时间常数 */ LowPassFilter(float Tf); ~LowPassFilter() = default; float operator() (float x); float Tf; //!< 低通滤波时间常数 protected: unsigned long timestamp_prev; //!< 最后执行时间戳 float y_prev; //!< 上一个循环中的过滤后的值 }; #endif #include "pid.h" #include <Arduino.h> #define _constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt))) PIDController::PIDController(float P, float I, float D, float ramp, float limit) : P(P) , I(I) , D(D) , output_ramp(ramp) // PID控制器加速度限幅 , limit(limit) // PID控制器输出限幅 , error_prev(0.0f) , output_prev(0.0f) , integral_prev(0.0f) { timestamp_prev = micros(); } // PID 控制器函数 float PIDController::operator() (float error){ // 计算两次循环中间的间隔时间 unsigned long timestamp_now = micros(); float Ts = (timestamp_now - timestamp_prev) * 1e-6f; if(Ts <= 0 || Ts > 0.5f) Ts = 1e-3f; // P环 float proportional = P * error; // Tustin 散点积分(I环) float integral = integral_prev + I*Ts*0.5f*(error + error_prev); integral = _constrain(integral, -limit, limit); // D环(微分环节) float derivative = D*(error - error_prev)/Ts; // 将P,I,D三环的计算值加起来 float output = proportional + integral + derivative; output = _constrain(output, -limit, limit); if(output_ramp > 0){ // 对PID的变化速率进行限制 float output_rate = (output - output_prev)/Ts; if (output_rate > output_ramp) output = output_prev + output_ramp*Ts; else if (output_rate < -output_ramp) output = output_prev - output_ramp*Ts; } // 保存值(为了下一次循环) integral_prev = integral; output_prev = output; error_prev = error; timestamp_prev = timestamp_now; return output; } #ifndef PID_H #define PID_H class PIDController { public: PIDController(float P, float I, float D, float ramp, float limit); ~PIDController() = default; float operator() (float error); float P; //!< 比例增益(P环增益) float I; //!< 积分增益(I环增益) float D; //!< 微分增益(D环增益) float output_ramp; float limit; protected: float error_prev; //!< 最后的跟踪误差值 float output_prev; //!< 最后一个 pid 输出值 float integral_prev; //!< 最后一个积分分量值 unsigned long timestamp_prev; //!< 上次执行时间戳 }; #endif void setPwm(float Ua, float Ub, float Uc); float setTorque(float Uq,float angle_el); float _normalizeAngle(float angle); void DFOC_Vbus(float power_supply); void DFOC_alignSensor(int _PP,int _DIR); float _electricalAngle(); float serial_motor_target(); String serialReceiveUserCommand(); //传感器读取 float DFOC_M0_Velocity(); float DFOC_M0_Angle(); //PID void DFOC_M0_SET_ANGLE_PID(float P,float I,float D,float ramp); void DFOC_M0_SET_VEL_PID(float P,float I,float D,float ramp); float DFOC_M0_VEL_PID(float error); float DFOC_M0_ANGLE_PID(float error); //接口函数 void DFOC_M0_set_Velocity_Angle(float Target); void DFOC_M0_setVelocity(float Target); void DFOC_M0_set_Force_Angle(float Target); void DFOC_M0_setTorque(float Target); 重写下面的代码使它运行后的效果与上方代码一致 /* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "i2c.h" #include "tim.h" #include "usart.h" #include "wwdg.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "BLDC.h" #include "string.h" #include "AS5600.h" #include "stdio.h" #include "stdlib.h" //#include "stdio.h" //#include "string.h" //#include "stdlib.h" //#include "math.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ //char msg [100]; /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ // 速度控制相关变量 float target_speed = 0.0f; // 目标速度 (rad/s) float current_speed = 0.0f; // 实际速度 (rad/s) uint8_t uart_rx_buf[32]; // 串口接收缓冲区 uint8_t uart_rx_len = 0; // 接收数据长度 /* USER CODE END PV */ /* USER CODE BEGIN PFP */ void SpeedControl_Update(void); void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM3_Init(); MX_TIM17_Init(); //MX_WWDG_Init(); MX_I2C1_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ // 启动串口接收中断 HAL_UART_Receive_IT(&huart1, &uart_rx_buf[uart_rx_len], 1); Init(); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ Cycle(); SpeedControl_Update(); // 添加速度控制 HAL_Delay(1); // 1ms控制周期 // int len = sprintf(msg, "Uq=%.2fV, Angle=%.1f°, Speed=%.1frad/s\r\n", // uq_div, // has5600.angle, // has5600.speed); // HAL_UART_Transmit(&huart1, (uint8_t*)tx_buffer, len, HAL_MAX_DELAY); /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Configure the main internal regulator output voltage */ HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV1; RCC_OscInitStruct.PLL.PLLN = 8; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ /* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.h * @brief : Header for main.c file. * This file contains the common defines of the application. ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Define to prevent recursive inclusion -------------------------------------*/ #ifndef __MAIN_H #define __MAIN_H #ifdef __cplusplus extern "C" { #endif /* Includes ------------------------------------------------------------------*/ #include "stm32g0xx_hal.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ /* Exported types ------------------------------------------------------------*/ /* USER CODE BEGIN ET */ /* USER CODE END ET */ /* Exported constants --------------------------------------------------------*/ /* USER CODE BEGIN EC */ /* USER CODE END EC */ /* Exported macro ------------------------------------------------------------*/ /* USER CODE BEGIN EM */ /* USER CODE END EM */ /* Exported functions prototypes ---------------------------------------------*/ void Error_Handler(void); /* USER CODE BEGIN EFP */ /* USER CODE END EFP */ /* Private defines -----------------------------------------------------------*/ #define LED_RUN_Pin GPIO_PIN_14 #define LED_RUN_GPIO_Port GPIOC #define KEY_Pin GPIO_PIN_15 #define KEY_GPIO_Port GPIOC #define KEY_EXTI_IRQn EXTI4_15_IRQn #define SLEEP_Pin GPIO_PIN_0 #define SLEEP_GPIO_Port GPIOA #define RESET_Pin GPIO_PIN_1 #define RESET_GPIO_Port GPIOA #define ERROR_Pin GPIO_PIN_2 #define ERROR_GPIO_Port GPIOA #define ERROR_EXTI_IRQn EXTI2_3_IRQn #define EN1_Pin GPIO_PIN_3 #define EN1_GPIO_Port GPIOA #define EN2_Pin GPIO_PIN_4 #define EN2_GPIO_Port GPIOA #define EN3_Pin GPIO_PIN_5 #define EN3_GPIO_Port GPIOA /* USER CODE BEGIN Private defines */ /* USER CODE END Private defines */ #ifdef __cplusplus } #endif #endif /* __MAIN_H */ #include "i2c.h" #include "math.h" #include "AS5600.h" #include "stdio.h" #include "string.h" #include "usart.h" #include "LPF.h" #define AS5600_PORT &hi2c1 #define AS5600_I2C_TIMEOUT 5 #define AS5600_ADDRESS 0x36 // 7-bit address #define AS5600_RAW_ANGLE_REG 0x0C // Raw angle register (12-bit) #define _2PI 6.28318530718f AS5600_HandleTypeDef has5600; LPF_1Order speed_filter; // 速度低通滤波器 // 初始化AS5600传感器 void AS5600_Init(void) { // 初始化滤波器(截止频率10Hz,假设采样周期1ms) LPF_1Order_Init(&speed_filter, 10.0f, 0.001f); // 读取初始角度(丢弃第一次可能不稳定的读数) AS5600_Read_RawAngle(&has5600); has5600.last_raw_angle = has5600.raw_angle; has5600.full_rotations = 0; } // 读取原始角度(0-4095) void AS5600_Read_RawAngle(AS5600_HandleTypeDef *h) { uint8_t data[2]; HAL_I2C_Mem_Read(AS5600_PORT, AS5600_ADDRESS, AS5600_RAW_ANGLE_REG, I2C_MEMADD_SIZE_8BIT, data, 2, AS5600_I2C_TIMEOUT); h->raw_angle = (data[0] << 8) | data[1]; // 组合高低字节 } // 更新角度和速度计算 void AS5600_Update(AS5600_HandleTypeDef *h) { static uint32_t last_time = 0; uint32_t current_time = HAL_GetTick(); float dt = (current_time - last_time) * 0.001f; // 换为秒 last_time = current_time; // 保存上一次角度 float prev_angle = h->angle_rad; // 读取新角度 AS5600_Read_RawAngle(h); // 处理角度环绕(4095→0跳变) int16_t delta = h->raw_angle - h->last_raw_angle; if (delta < -2048) delta += 4096; else if (delta > 2048) delta -= 4096; // 换为弧度(0-2π) h->angle_rad = (float)h->raw_angle / 4096.0f * _2PI; h->angle_deg = h->angle_rad * 180.0f / 3.14159265f; // 多圈跟踪(角度突变超过0.8圈时调整圈数) float angle_diff = h->angle_rad - prev_angle; if (fabsf(angle_diff) > 0.8f * _2PI) { h->full_rotations += (angle_diff > 0) ? -1 : 1; } // 计算角速度(rad/s) if (dt > 0) { float raw_speed = angle_diff / dt; h->speed_radps = LPF_1Order_Update(&speed_filter, raw_speed); } h->last_raw_angle = h->raw_angle; } // 获取累计多圈角度(弧度) float AS5600_GetFullAngle(AS5600_HandleTypeDef *h) { return h->full_rotations * _2PI + h->angle_rad; } // 通过串口输出数据 void AS5600_Send_Data(AS5600_HandleTypeDef *h) { char buffer[64]; snprintf(buffer, sizeof(buffer), "Angle: %.2f deg, Speed: %.2f rad/s, Turns: %ld\r\n", h->angle_deg, h->speed_radps, h->full_rotations); HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), HAL_MAX_DELAY); } // 主循环调用示例 void AS5600_Cycle(void) { static uint32_t last_update = 0; if (HAL_GetTick() - last_update >= 10) { // 每10ms更新一次 AS5600_Update(&has5600); AS5600_Send_Data(&has5600); last_update = HAL_GetTick(); } } #include "main.h" #define AS5600_D_CW 1 #define AS5600_D_CCW 0 #define AS5600_D_STOP 2 // 传感器数据结构体 typedef struct { uint16_t raw_angle; // 原始角度值 (0-4095) uint16_t last_raw_angle; // 上一次原始角度值 float angle_rad; // 当前角度(弧度) float angle_deg; // 当前角度(度) float speed_radps; // 角速度(rad/s) int32_t full_rotations; // 完整旋圈数 } AS5600_HandleTypeDef; extern uint16_t as5600_read_cnt; extern AS5600_HandleTypeDef has5600; void AS5600_Init(void); void AS5600_Cycle(void); uint16_t AS5600_Read_uint16(uint8_t reg_addr); void AS5600_Refresh_Angle(AS5600_HandleTypeDef *as5600_handle); void AS5600_Send_Data(AS5600_HandleTypeDef *as5600_handle); void AS5600_Read_RawAngle(AS5600_HandleTypeDef *h); #endif /* __A_AS5600_H */ #ifndef __BLDC_H #define __BLDC_H #include "main.h" #define Constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt))) #define POLE_PAIRS 7 extern uint16_t debug_cnt; extern uint16_t oled_cnt; void Init(void); void Cycle(void); void My_BLDC_Init(void); void Set_PWM(float Ua, float Ub, float Uc); float Normalize_Angle(float angle); float Elect_Angle(float shaft_angle, int pole_pairs); void Set_Phase_Voltage(float Uq,float Ud, float angle_el); #endif /* __A_BLDC_MAIN_H */ //===== Sys ===== #include "stdio.h" #include "string.h" #include "stdlib.h" #include "math.h" //===== ST ===== #include "tim.h" #include "usart.h" #include "wwdg.h" //===== Self ===== #include "BLDC.h" #include "BLDC_tim.h" #include "DRV8313.h" #include "AS5600.h" //===== Ext Lib File ===== #include "MyProject.h" //===== IO Config ===== #define RUN_LED_ON() HAL_GPIO_WritePin(LED_RUN_GPIO_Port, LED_RUN_Pin, GPIO_PIN_SET) #define RUN_LED_OFF() HAL_GPIO_WritePin(LED_RUN_GPIO_Port, LED_RUN_Pin, GPIO_PIN_RESET) #define RUN_LED_TOGGLE() HAL_GPIO_TogglePin(LED_RUN_GPIO_Port, LED_RUN_Pin); //===== BLDC Config ===== uint8_t mode = 0; float roll_speed = 6.28; float target; //char tx_buffer [100]; void Init(void){ HAL_Delay(5); DRV8313_Init(); AS5600_Init(); HAL_Delay(50); My_BLDC_Init(); } void Cycle(void){ AS5600_Cycle(); } uint16_t bldc_cnt = 50; float pwm_duty = 0.1f; float shaft_Angle=0, zero_elect_angle=0, Ualpha,Ubeta=0, Ua=0,Ub=0,Uc=0, dc_a=0,dc_b=0,dc_c=0; float target_velocity = 10, vel_lim = 80; float vin = 12.6; float uq_div = 5; //Uq = vin/uq_div, Uq must small than vin/3. unsigned long timeStamp; void My_BLDC_Init(void){ mode=1; RUN_LED_ON(); DRV8313_ENx_On(); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3); // mode=0; // RUN_LED_OFF(); // DRV8313_ENx_Off(); // HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_1); // HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_2); // HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_3); } float Elect_Angle(float shaft_Angle, int pole_pairs){ return (shaft_Angle*pole_pairs); } float Normalize_Angle(float angle){ float ret = fmod(angle, _2PI); if(ret >= 0){ return ret; }else{ return (ret + _2PI); } } void Set_Phase_Voltage(float Uq,float Ud, float angle_el) { angle_el = Normalize_Angle(angle_el + zero_elect_angle); // 帕克逆变换 Ualpha = -Uq*sin(angle_el); Ubeta = Uq*cos(angle_el); // 克拉克逆变换 Ua = Ualpha + vin/2; Ub = (sqrt(3)*Ubeta-Ualpha)/2 + vin/2; Uc = (-Ualpha-sqrt(3)*Ubeta)/2 + vin/2; Set_PWM(Ua,Ub,Uc); } void Set_PWM(float Ua, float Ub, float Uc){ // 计算占空比 // 限制占空比从0到1 dc_a = Constrain(Ua / vin, 0.0f , 1.0f ); dc_b = Constrain(Ub / vin, 0.0f , 1.0f ); dc_c = Constrain(Uc / vin, 0.0f , 1.0f ); SetPWMDutyPersent(&htim3, TIM_CHANNEL_1, dc_a*100); SetPWMDutyPersent(&htim3, TIM_CHANNEL_2, dc_b*100); SetPWMDutyPersent(&htim3, TIM_CHANNEL_3, dc_c*100); } void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == KEY_Pin) { if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_SET) { if(mode == 0){ mode = 1; }else{ mode = 0; } } } } void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == ERROR_Pin){ //BLDC_ERR_Protect DRV8313_ENx_Off(); HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_1); HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_2); HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_3); mode = 0; RUN_LED_OFF(); } } #include "LPF.h" #include "MyProject.h" void LPF_1Order_Init(LPF_1Order *filter, float cutoff_freq, float sample_time) { // 计算滤波系数 α = ΔT / (1/2πfc + ΔT) float rc = 1.0f / (2.0f * _PI * cutoff_freq); filter->alpha = sample_time / (rc + sample_time); filter->prev_output = 0.0f; } float LPF_1Order_Update(LPF_1Order *filter, float input) { filter->prev_output = filter->alpha * input + (1.0f - filter->alpha) * filter->prev_output; return filter->prev_output; } #ifndef __LPF_H #define __LPF_H typedef struct { float alpha; // 滤波系数 (α = ΔT / (RC + ΔT)) float prev_output; // 上一次的输出值 } LPF_1Order; void LPF_1Order_Init(LPF_1Order *filter, float cutoff_freq, float sample_time); float LPF_1Order_Update(LPF_1Order *filter, float input); #endif #include "PID.h" #ifndef __PID_H #define __PID_H #endif #ifndef __A_DRV8313_H #define __A_DRV8313_H #include "main.h" #define DRV8313_RESET_HIGH() HAL_GPIO_WritePin(RESET_GPIO_Port, RESET_Pin, GPIO_PIN_SET) #define DRV8313_RESET_LOW() HAL_GPIO_WritePin(RESET_GPIO_Port, RESET_Pin, GPIO_PIN_RESET) #define DRV8313_SLEEP_HIGH() HAL_GPIO_WritePin(SLEEP_GPIO_Port, SLEEP_Pin, GPIO_PIN_SET) #define DRV8313_SLEEP_LOW() HAL_GPIO_WritePin(SLEEP_GPIO_Port, SLEEP_Pin, GPIO_PIN_RESET) #define DRV8313_EN1_HIGH() HAL_GPIO_WritePin(EN1_GPIO_Port, EN1_Pin, GPIO_PIN_SET) #define DRV8313_EN1_LOW() HAL_GPIO_WritePin(EN1_GPIO_Port, EN1_Pin, GPIO_PIN_RESET) #define DRV8313_EN2_HIGH() HAL_GPIO_WritePin(EN2_GPIO_Port, EN2_Pin, GPIO_PIN_SET) #define DRV8313_EN2_LOW() HAL_GPIO_WritePin(EN2_GPIO_Port, EN2_Pin, GPIO_PIN_RESET) #define DRV8313_EN3_HIGH() HAL_GPIO_WritePin(EN3_GPIO_Port, EN3_Pin, GPIO_PIN_SET) #define DRV8313_EN3_LOW() HAL_GPIO_WritePin(EN3_GPIO_Port, EN3_Pin, GPIO_PIN_RESET) void DRV8313_Init(void); void DRV8313_ENx_On(void); void DRV8313_ENx_Off(void); #endif /* __A_DRV8313_H */ #include "DRV8313.h" void DRV8313_Init(void){ DRV8313_RESET_LOW(); HAL_Delay(1); DRV8313_RESET_HIGH(); HAL_Delay(1); DRV8313_SLEEP_HIGH(); DRV8313_ENx_Off(); } void DRV8313_ENx_On(void){ DRV8313_EN1_HIGH(); DRV8313_EN2_HIGH(); DRV8313_EN3_HIGH(); } void DRV8313_ENx_Off(void){ DRV8313_EN1_LOW(); DRV8313_EN2_LOW(); DRV8313_EN3_LOW(); } //===== STM32 HAL Includes ===== #include "tim.h" //===== Self Includes ===== #include "BLDC_tim.h" #include "BLDC.h" #include "AS5600.h" //#include "a_lsm6ds3tr.h" /** *@brief 微秒阻塞延迟。 *@param delay - 延迟微秒数。 *@retval 无。 */ void HAL_Delay_us(uint32_t delay){ HAL_SYSTICK_Config(64); //改变System tick,使HAL_Delay变成微秒延迟。64MHz for us delay HAL_Delay(delay); HAL_SYSTICK_Config(64000); //改变System tick,使HAL_Delay变成毫秒延迟。64MHz for ms delay } /** *@brief 设定PWM的正占空比。 *@param htim - 定时器句柄;channel - 定时器通道;dutyPersent - PWM正占空比0~100。 *@retval 无。 */ void SetPWMDutyPersent(TIM_HandleTypeDef *htim, uint32_t channel, float dutyPersent){ uint32_t compare, arr; arr = __HAL_TIM_GetAutoreload(htim); //读当前定时器的自动重装载AutoReload(ARR)值 compare = (dutyPersent/100) * (arr+1); //根据当前定时器的ARR值,计算当前定时器的比较compare值 __HAL_TIM_SetCompare(htim, channel, compare); //设定比较compare值 //htim->Instance->CCR1 = compare; } /** *@brief 获取PWM的正占空比。 *@param htim - 定时器句柄;channel - 定时器通道。 *@retval PWM正占空比0~100。 */ float GetPWMDutyPersent(TIM_HandleTypeDef *htim, uint32_t channel){ float dutyPersent; uint32_t compare, arr; arr = __HAL_TIM_GetAutoreload(htim); //读当前定时器的自动重装载AutoReload(ARR)值 compare = __HAL_TIM_GetCompare(htim, channel); //读当前定时器的比较compare值 dutyPersent = 100*((float)compare / (arr+1)); //计算占空比 return dutyPersent; } //internal, only for m_tim.c uint32_t count_100us = 0; uint16_t count_ms = 0; uint8_t count_s = 0; uint8_t count_min = 0; uint32_t count_h = 0; //external, for main.c and other uint32_t count_100us_out = 0; uint32_t count_ms_out = 0; uint32_t count_s_out = 0; uint32_t count_min_out = 0; void Timer_SetCount(void){ if(count_100us%10 == 0){ count_ms++; count_ms_out++; if(count_ms >= 1000){ count_s++; count_s_out++; count_ms = 0; } if(count_s >= 60){ count_min++; count_min_out++; count_s=0; } if(count_min >= 60){ count_h++; count_min=0; } //external start if(count_ms_out >= 0xfffffff0){ count_ms_out = 0; } if(count_s_out >= 0xfffffff0){ count_s_out = 0; } if(count_min_out >= 0xfffffff0){ count_min_out = 0; } as5600_read_cnt++; if(as5600_read_cnt>60000){ as5600_read_cnt=0; } } } //will be called by stm32xnxx_it.c void TIM17_IR(void){ count_100us++; count_100us_out++; if(count_100us >= 0xfffffff0){ count_100us = 0; } if(count_100us_out >= 0xfffffff0){ count_100us_out = 0; } Timer_SetCount(); } uint32_t Timer_GetCount_100us(void){ return count_100us_out; } void Timer_ResetCount_100us(void){ count_100us_out = 0; } uint32_t Timer_GetCount_ms(void){ return count_ms_out; } void Timer_ResetCount_ms(void){ count_ms_out = 0; } uint32_t Timer_GetCount_s(void){ return count_s_out; } void Timer_ResetCount_s(void){ count_s_out = 0; } #ifndef __BLDC_TIM_H #define __BLDC_TIM_H #include "main.h" void TIM17_IR(void); void HAL_Delay_us(uint32_t delay); void SetPWMDutyPersent(TIM_HandleTypeDef *htim, uint32_t channel, float dutyPersent); float GetPWMDutyPersent(TIM_HandleTypeDef *htim, uint32_t channel); typedef struct { uint32_t h; uint8_t min; uint8_t s; uint16_t ms; }Time_HandleTypeDef; extern uint32_t count_100us_out; extern uint32_t count_ms_out; extern uint32_t count_s_out; extern uint32_t count_min_out; void GetRunTime(Time_HandleTypeDef *ret_time); uint32_t Timer_GetCount_100us(void); void Timer_ResetCount_100us(void); uint32_t Timer_GetCount_ms(void); void Timer_ResetCount_ms(void); uint32_t Timer_GetCount_s(void); void Timer_ResetCount_s(void); #endif /* __A_BLDC_TIM_H */
07-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值