SOPC之NIOS Ⅱ实现电机转速PID控制

        通过FPGA开发板上的NIOS Ⅱ搭建电机控制的硬件平台,包括电机正反转、编码器的读取,再通过软件部分实现PID算法对电机速度进行控制,使其能够渐近设定的编码器目标值。

一、PID算法

        PID算法(Proportional-Integral-Derivative Algorithm)是一种经典的控制算法,用于实现闭环控制系统中的自动控制,旨在使被控制系统的输出尽可能接近期望值。

        PID算法由三个部分组成:比例(Proportional)、积分(Integral)和微分(Derivative)。这三个部分分别对应了误差的当前值、累积值和变化率。PID算法根据这些部分的加权和来计算控制输出,以实现对系统的稳定、快速、精确的控制。

 U(t)=kp(err(t)+\frac{1}{T_{I}}\int err(t)dt+\frac{T_{D}derr(t)}{dt}

  • 比例(P)部分:比例控制是根据当前的误差值来调整控制输出。如果误差较大,比例部分的输出也会较大,从而更快地减小误差。这有助于系统快速接近期望值,但可能导致震荡和过冲。
  • 积分(I)部分:积分控制用于消除系统存在的稳态误差。它考虑误差的累积值,如果误差持续存在,积分部分的输出会逐渐增加,从而逐步减小稳态误差。然而,过大的积分作用可能导致系统响应过于缓慢或产生震荡。
  • 微分(D)部分:微分控制用于预测误差的未来变化趋势。通过考虑误差的变化率,微分部分可以抑制系统的过冲和震荡。但过大的微分作用可能引起噪音的放大。

        PID算法的参数调节是一个复杂的过程,需要根据被控制系统的特性和性能要求进行调试。不同的应用和系统可能需要不同的PID参数配置,以达到最佳的控制效果。

PID算法详细内容和调试方法可参考:
PID参数解析+调参经验笔记(经验法)_pid调参_Xuan-ZY的博客-CSDN博客

二、硬件设计

硬件部分主要由两部分组成,卡尔曼滤波和NIOS Ⅱ系统

2.1 卡尔曼滤波

卡尔曼滤波主要是为了对霍尔传感器输出的方波进行滤波操作

reg     [15:0]          filterClockDivider;  // 过滤器时钟分频器
reg                     filterClock;         // 过滤器时钟信号// 时钟设置
parameter               ClockFrequency  = 50000000;   // 时钟频率50MHz
parameter               FilterFrequency = 15000;      // 滤波器频率15KHz/   过滤器时钟   
always @(posedge Clock or negedge Reset)
beginif (!Reset)beginfilterClock        <= 0;     // 复位时,过滤器时钟为低电平filterClockDivider <= 0;     // 复位时,分频器清零endelsebeginif (filterClockDivider < (ClockFrequency / FilterFrequency / 8))filterClockDivider <= filterClockDivider + 1;  // 分频计数增加elsebeginfilterClockDivider <= 0;    			  // 分频计数清零filterClock        <= ~filterClock;   // 过滤器时钟翻转endend
endalways @(posedge filterClock or negedge Reset)
beginif (!Reset)beginOutput_A <= 0;     // 复位时,输出信号 A 为低电平endelsebeginif (Input_A)Output_A <= 1;  // 如果输入信号 A 为高,输出信号 A 为高elseOutput_A <= 0;  // 否则输出信号 A 为低end
endalways @(posedge filterClock or negedge Reset)
beginif (!Reset)beginOutput_B <= 0;     // 复位时,输出信号 B 为低电平endelsebeginif (Input_B)Output_B <= 1;  // 如果输入信号 B 为高,输出信号 B 为高elseOutput_B <= 0;  // 否则输出信号 B 为低end
endendmodule

3.1 NIOS Ⅱ系统

NIOS Ⅱ中包含时钟CLK、锁相环PLL、NIOS Ⅱ软核处理器、片上存储onchip_ram、System ID、串行通信jtag_uart,以及自定义组件电机控制Motor_PWM、编码器测量Motor_measure

3.1.1 电机控制

一般的FPGA是无法驱动电机的,因此需要电机驱动芯片控制DC电机,与FPGA相连接的控制信号有IN1/IN2/PWM,通过IN1/IN2去控制电机的方向与停止,通过PWM去控制电机的转速

`define REGISTER_TOTAL_DUR     2'd0
`define REGISTER_HIGH_DUR      2'd1
`define REGISTER_CONTROL       2'd2reg motor_movement;         // 电机运动,1为开始、0为停止
reg motor_direction;        // 电机转向,1为向前、0为向后
reg motor_fast_decay;       // 电机减速,1为快制动、0为慢制动always @(posedge clock or negedge reset_n)
beginif (~reset_n)begin// PWMhigh_dur <= 0;total_dur <= 0;// MOTORmotor_movement <= 1'b0;motor_direction <= 1'b1;motor_fast_decay <= 1'b1;endelse if (select_cs && (select_address == `REGISTER_CONTROL))beginif (select_write){motor_fast_decay, motor_direction, motor_movement} <= select_writedata[2:0];else if (select_read)select_readdata <= {29'b0, motor_fast_decay, motor_direction, motor_movement};endelse if (select_cs & select_write)beginif (select_address == `REGISTER_TOTAL_DUR)total_dur <= select_writedata;else if (select_address == `REGISTER_HIGH_DUR)high_dur <= select_writedata;endelse if (select_cs & select_read)beginif (select_address == `REGISTER_TOTAL_DUR)select_readdata <= total_dur;else if (select_address == `REGISTER_HIGH_DUR)select_readdata <= high_dur;end    
end// 方向控制
always @(*)
beginif (motor_fast_decay)begin  // 急刹车if (motor_movement)beginif (motor_direction){DC_Motor_IN2, DC_Motor_IN1, PWM} <= {1'b1, 1'b0, PWM_OUT};else{DC_Motor_IN2, DC_Motor_IN1, PWM} <= {1'b0, 1'b1, PWM_OUT};endelse{DC_Motor_IN2, DC_Motor_IN1, PWM} <= {1'b1, 1'b1, 1'b0};endelsebegin // 慢刹车if (motor_movement)beginif (motor_direction){DC_Motor_IN2, DC_Motor_IN1, PWM} <= {1'b1, 1'b0, PWM_OUT};else{DC_Motor_IN2, DC_Motor_IN1, PWM} <= {1'b0, 1'b1, PWM_OUT};endelse{DC_Motor_IN2, DC_Motor_IN1, PWM} <= {1'b0, 1'b0, 1'b0};end
end// PWM 转速控制
reg             PWM_OUT;
reg     [31:0] total_dur;       // 总持续时间
reg     [31:0] high_dur;        // 高位时间,决定电机转速,控制 PWM 占空比,值越高,占空比越大,转速越快
reg     [31:0] tick;            // 计数器always @(posedge clock or negedge reset_n)
beginif (~reset_n)begintick <= 1;endelse if (tick >= total_dur)begintick <= 1;endelsetick <= tick + 1;
endalways @(posedge clock)
beginPWM_OUT <= (tick <= high_dur) ? 1'b1 : 1'b0;
endendmodule

3.1.2 电机监测

      电机转动带动磁盘经过霍尔传感器,磁力的变化让霍尔效应传感器产生霍尔效应电压,经过数字电路处理产生方波,两个位置不同的霍尔效应传感器输出两个相位不同的方波(PhaseA和Phase B)。磁盘在转动时,先被感应的传感器会先输出方波,另一个传感器输出会有延迟,所以两个方波的相位有所不同。由此可以通过方波相位领先计算电机的方向。根据输出的脉冲数,可以计算电机转速。

首先计算电机的方向

reg  DO_PULSE;                      //用于存储输出的电机脉冲信号
wire PULSE_XOR;                     //用于存储PHASE_A和PHASE_B进行异或结果
reg  PULSE_XOR_PREVIOUS;            //上一次的PULSE_XOR值
reg  DIRECTION;                     //用于存储电机方向信号
reg  DIRECT_PATCH;                  //用于存储DIRECT异或PHASE_A后取反的结果//解码方向信号
always @(posedge DI_PHASE_A) DIRECTION <= DI_PHASE_B;                    //当有DI_PHASE_A的上升沿,将DI_PHASE_B的值赋给DIRECTION  
always @(posedge DI_PHASE_B) DIRECT_PATCH <= ~(DIRECTION ^ DI_PHASE_A);  //当有DI_PHASE_B的上升沿,将DIRECT和DI_PHASE_A进行异或后取反赋值给DIRECT_PATCH 
assign DO_DIRECT = DIRECTION | DIRECT_PATCH;                             //将DIRECTION和DIRECT_PATCH进行与运算 //解码脉冲信号
assign PULSE_XOR = DI_PHASE_A ^ DI_PHASE_B;                         
always @(posedge DI_SYSCLK) 
beginif(PULSE_XOR != PULSE_XOR_PREVIOUS)                             begin                                                              DO_PULSE <= 1'b1;                                              PULSE_XOR_PREVIOUS <= PULSE_XOR;endelse begin                                                         DO_PULSE <= 1'b0;endend

检测编码器的值

always @(posedge clock or negedge reset_n)
beginif(~reset_n)                                          //当复位有效将counter_threshold和counter_enable置为0begincounter_threshold <= 0;counter_enable <= 0;endelse if (select_chip_enable && select_write)         //当select_chip_enable和select_write有效,即写有效beginif(select_register_address == `COUNTER_ENABLE)counter_enable <= select_write_data;end  else if(select_chip_enable && select_read)begin                                                //当读有效就读取当前counter数值if(select_register_address == `COUNTER_READ)select_read_data <= pulse_counter;end
endalways @(posedge clock)
beginif(select_chip_enable && select_write && select_register_address == `COUNTER_WRITE)pulse_counter <= select_write_data[15:0];else if(counter_enable && motor_pulse)               //当计数使能和电机脉冲同时有效beginif(motor_direction)                              //如果电机正转  beginif(pulse_counter < 16'hffff)pulse_counter <= pulse_counter + 1;      //counter随电机传回的脉冲数累加   endelse if(!motor_direction)                        //如果电机反转beginif(pulse_counter > 0)pulse_counter <= pulse_counter - 1;      //counter随着电机传回的脉冲数递减    endelsepulse_counter <= 0;                                end
end  

三、软件设计

3.1 Motor控制

电机控制部分由Motor.h和Motor.cpp组成,对相关函数进行声明和定义

首先在构造函数中传入电机的地址和测量寄存器地址

Motor::Motor(int Add,int MeasureAdd):motor_BaseAddress(Add),measure_Address(MeasureAdd),CycleWidthMini(CYCLE_WIDTH_MINI),    //电机的最小PWM周期宽度值CycleWidthMaxi(CYCLE_WIDTH_MAX)      //电机的最大PWM周期宽度值{//将REG_TOTAL_DUR寄存器(0)设置为常量值CYCLE_WIDTH,设置电机的初始PWM周期宽度IOWR(motor_BaseAddress, REG_TOTAL_DUR, CYCLE_WIDTH);
}

控制电机的启动、停止以及方向

//启动电机
void Motor::StartMotor(void){int currentStatus, updatedControl;currentStatus = IORD(motor_BaseAddress, STATUS_REG);    //从STATUS_REG寄存器读取当前状态updatedControl = currentStatus | MOTOR_RUN_FLAG;        //设置MOTOR_RUN_FLAGIOWR(motor_BaseAddress, STATUS_REG, updatedControl);    //然后将更新后的控制值写回寄存器IOWR(measure_Address, measure_count_enable_reg, 0x01);  //将0x01写入测量设备的measure_cnt_enable_reg 寄存器来启用测量计数器
}//停止电机
void Motor::StopMotor(void){int currentStatus, updatedControl;currentStatus = IORD(motor_BaseAddress, STATUS_REG);           //从STATUS_REG寄存器读取当前状态updatedControl = currentStatus & (~MOTOR_RUN_FLAG);            //清除MOTOR_RUN_FLAGIOWR(motor_BaseAddress, STATUS_REG, updatedControl);           //将更新后的控制值写回寄存器IOWR(measure_Address, measure_count_enable_reg, 0x00);         //通过将0写入测量设备的 measure_count_enable_reg 寄存器来禁用测量计数器
}//设置电机方向
void Motor::SetMotorDirection(bool forwardDirection){               int currentStatus, updatedControl;currentStatus = IORD(motor_BaseAddress, STATUS_REG);            //从STATUS_REG寄存器中读取当前状态if (forwardDirection)                                           //根据forwardDirection参数修改方向控制updatedControl = currentStatus | MOTOR_FORWARD_FLAG;elseupdatedControl = currentStatus & ~MOTOR_FORWARD_FLAG;IOWR(motor_BaseAddress, STATUS_REG, updatedControl);            //将更新后的值写回STATUS_REG寄存器中
}

设置电机转动的速度

//设置电机速度
void Motor::SetMotorSpeed(float desiredSpeed) {int speedParameter = 0;if (desiredSpeed < -100.0)                  //确保输入速度在有效范围内desiredSpeed = -100;else if (desiredSpeed > 100.0)desiredSpeed = 100.0;if (desiredSpeed != 0.0) {                  //根据输入速度计算适当的PWM值speedParameter = motor_CycleWidth_Min + (int)(fabs(desiredSpeed) * (float)(motor_CycleWidth_Max - motor_CycleWidth_Min) / 100.0);}IOWR(motor_BaseAddress, REG_HIGH_DURATION, speedParameter);   //将计算得到的PWM值写入REG_HIGH_DURATION寄存器以控制电机速度。SetMotorDirection((desiredSpeed >= 0.0) ? true : false);       //使用SetMotorDirection()函数设置电机的方向
}

从寄存器中读取电机编码器的值

//获取编码器计数值
signed short Motor::GetMotorCount(void) {signed short motorCount;                                   //带符号的16位整数motorCount = IORD(motor_MeasureAddress, motor_measure_count_read_reg);  //从内存映射寄存器读取速度计数motorCount = motorCount - 0x8000;                                       //减去0x8000清除count最高位(即最高位为1的标志位)以获取实际计数值return motorCount;
}

3.2 主程序

在主程序中实现PID算法

float kp = 0.02;        //比例增益
float ki = 0.015;       //积分增益
float kd = 0.35;        //微分增益float calculatePID(float error, float integral, float prev_error) {float p = kp * error;        //偏差error = 目标值 - 当前值float i = ki * integral;     //误差和float d = kd * (error - prev_error); return p + i + d;
}

在主函数中实现对电机的控制,使其能够渐近设定的编码器目标值

int main()
{int targetDistance;                             // 将此值更改为所需的目标距离scanf("%d", &targetDistance);printf("Hello BAL-Car,I'll keep moving\r\n");Motor.StopMotor();Motor.StartMotor();float initialSpeed = 0;  // 初始速度Motor.SetSpeed(initialSpeed);// 初始化PID变量float integral = 0;float prev_error = 0;bool reach = false;while(!reach){// 测量当前编码器计数int currentCounts = Motor.GetMotorCount();printf("编码器:%d\n", currentCounts);// 计算误差,即目标距离与当前距离的差值int error = targetDistance - currentCounts;// 计算 PID 控制输出float controlOutput = calculatePID(error, integral, prev_error);// 将控制输出限制在电机速度范围内float speed = initialSpeed + controlOutput;speed = fmaxf(-100, fminf(speed, 100));// 更新下次迭代的前一次误差和积分prev_error = error;integral += error;if(integral>10000)  	integral= 10000;    //避免累积误差过大if(integral<-10000)	    integral=-10000;Motor.SetMotorSpeed(speed);// 打印当前误差和当前速度printf("误差:%d,当前速度:%.2f\n", error, speed);usleep(1000*100);if(error==0) {printf("Enter next targetDistance");scanf("%d", &targetDistance);}//reach = true;}return 0;
}

四、实验结果

电机首先快速向目标编码值转动,最后逐渐收敛,但PID太难调了,调了好久还是会出现震荡

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/100407.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

还不知道怎么提示LLM?ChatGPT提示入门

文章目录 简介&#xff1a;什么是人工智能&#xff1f;什么是提示过程&#xff1f;为什么会出现这样的差异&#xff1f; 为什么需要提示过程&#xff1f;1) 文章摘要2) 数学问题求解 如何进行提示过程&#xff1f;角色提示&#xff1a;多范例提示&#xff1a;无范例提示单范例提…

龙讯旷腾PWmat已部署至曙光智算平台

编者荐语&#xff1a; 近期&#xff0c;龙讯旷腾核心产品PWmat已成功部署至曙光智算AC.sugon.com平台&#xff0c;可为用户提供包括分子建模、第一性原理计算、数据可视化等在内的完备的超级计算云服务&#xff0c;让大家能够轻松上手具有完全自主知识产权的大尺度高性能材料计…

常见前端面试之VUE面试题汇总一

1. Vue 的基本原理 当 一 个 Vue 实 例 创 建 时 &#xff0c; Vue 会 遍 历 data 中 的 属 性 &#xff0c; 用 Object.defineProperty &#xff08; vue3.0 使 用 proxy&#xff09; 将 它 们 转 为 getter/setter&#xff0c;并且在内部追踪相关依赖&#xff0c;在属性被访…

【CSS】CSS 布局——常规流布局

<h1>基础文档流</h1><p>我是一个基本的块级元素。我的相邻块级元素在我的下方另起一行。</p><p>默认情况下&#xff0c;我们会占据父元素 100%的宽度&#xff0c;并且我们的高度与我们的子元素内容一样高。我们的总宽度和高度是我们的内容 内边距…

fastapi发布web配置页面

fastapi发布web配置页面 FastAPI 是一个基于 Python 的快速 Web 开发框架&#xff0c;它提供了许多功能来简化 Web 开发过程。其中一个重要的功能是能够轻松地创建 API 文档页面。 在 FastAPI 中&#xff0c;可以使用 OpenAPI 和 Swagger 来创建 API 文档页面。下面是一个简单…

图像降采样的计算原理:F.interpolate INTER_AREA

一、F.interpolate——数组采样操作 torch.nn.functional.interpolate(input, size=None, scale_factor=None, mode=nearest, align_corners=None, recompute_scale_factor=None) 功能:利用插值方法,对输入的张量数组进行上\下采样操作,换句话说就是科学合理地改变数组的尺…

一套基于C#语言开发的LIMS实验室信息管理系统源码

实验室信息管理系统&#xff08;LIMS)是指帮助实验室组织和管理实验数据的计算机软件系统&#xff0c;它将实验室操作有机地组织在一起&#xff0c;以满足实验室工作流程的所有要求。它能以不同的方式支持实验室的工作&#xff0c;从简单的过程(如样品采集和入库)到复杂的流程(…

微信小程序使用云存储和Markdown开发页面

最近想在一个小程序里加入一个使用指南的页面&#xff0c;考虑到数据存储和减少页面的开发工作量&#xff0c;决定尝试在云存储里上传Markdown文件&#xff0c;微信小程序端负责解析和渲染。小程序端使用到一个库Towxml。 Towxml Towxml是一个可将HTML、Markdown转为微信小程…

ESB是什么?传统ESB升级该怎么选?

ESB的由来 下面这张图&#xff0c;稍微了解些IT集成的朋友应该不陌生。 随着信息化发展不断深入&#xff0c;企业在不同的阶段引入了不同的应用、系统和软件。这些原始的应用系统互不连通&#xff0c;如同一根根独立的烟囱。 但是企业业务是流程化的&#xff0c;这就需要业务…

2023 网络建设与运维 X86架构计算机操作系统安装与管理题解

任务描述: 随着信息技术的快速发展,集团计划2023年把部分业务由原有的X86架构服务器上迁移到ARM架构服务器上,同时根据目前的部分业务需求进行了部分调整和优化。 一、X86架构计算机操作系统安装与管理 1.PC1系统为ubuntu-desktop-amd64系统(已安装,语言为英文),登录用户…

记一次布尔盲注漏洞的挖掘与分析

在上篇文章记一次由于整型参数错误导致的任意文件上传的漏洞成因的分析过程中&#xff0c;发现menu_id貌似是存在注入的。 public function upload() {$menu_id $this->post(menu_id);if ($id) {$where "id {$id}";if ($menu_id) {$where . " and menu_id…

「我的编程笔记」——记录学习中的代码、函数、概念等

文章目录 每日一句正能量前言常用的代码登录存储 特定函数MD5加密 复杂概念1. 多线程2. 集合类3. 异常处理4 泛型5 反射 特定功能1. 文件操作2. 网络通信3. 图形绘制4. 数据库操作5. 多媒体处理 后记 每日一句正能量 不管昨天、今天、明天&#xff0c;能豁然开朗就是最美好的一…

5.8.webrtc事件处理基础知识

在之前的课程中呢&#xff0c;我向你介绍了大量web rtc线程相关内容&#xff0c;今天呢&#xff0c;我们来看一下线程事件处理的基本知识。首先&#xff0c;我们要清楚啊&#xff0c;不同的平台处理事件的API是不一样的&#xff0c;这就如同我们当时创建线程是类似的&#xff0…

C#-Tolewer和ToUpper的使用

目录 简介: 好处:​ 过程: 总结&#xff1a; 简介: 字符串是不可变的&#xff0c;所以这些函数都不会直接改变字符串的内容&#xff0c;而是把修改后的字符串的值通过函数返回值的形式返回。 ToLower和ToUpper是字符串处理函数&#xff0c;用于将字符中的英文字母转换为小…

并查集 size 的优化(并查集 size 的优化)

目录 并查集 size 的优化 Java 实例代码 UnionFind3.java 文件代码&#xff1a; 并查集 size 的优化 按照上一小节的思路&#xff0c;我们把如下图所示的并查集&#xff0c;进行 union(4,9) 操作。 合并操作后的结构为&#xff1a; 可以发现&#xff0c;这个结构的树的层相对…

Spring练习---28 (用户表和角色表分析,角色列表展示,角色层和Dao层的设置,页面展示操作)

84、下面进入我们的业务层面&#xff0c;进入我们的业务层面我们先分析一个东西&#xff0c;我们要分析用户和角色的关系&#xff0c;因为我们只有在分析完用户和角色之间的关系后&#xff0c;我们才知道表的关系&#xff0c;实体的关系 85、现在我们先画一张表&#xff0c;分析…

嵌入式设备应用开发(qt界面开发)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 linux界面开发有很多的方案可以选。比如说lvgl、minigui、ftk之类的。但是,这么多年来,一直屹立不倒的还是qt。相比较其他几种方案,qt支持多个平台,这里面就包括了linux平台。此…

《Linux从练气到飞升》No.16 Linux 进程地址空间

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…

node没有自动安装npm时,如何手动安装 npm

之前写过一篇使用 nvm 管理 node 版本的文章&#xff0c;node版本管理&#xff08;Windows&#xff09; 有时候&#xff0c;我们使用 nvm 下载 node 时&#xff0c;node 没有自动下载 npm &#xff0c;此时就需要我们自己手动下载 npm 1、下载 npm下载地址&#xff1a;&…

Docker创建 LNMP 服务+Wordpress 网站平台

Docker创建 LNMP 服务Wordpress 网站平台 一.环境及准备工作 1.项目环境 公司在实际的生产环境中&#xff0c;需要使用 Docker 技术在一台主机上创建 LNMP 服务并运行 Wordpress 网站平台。然后对此服务进行相关的性能调优和管理工作。 容器 系统 IP地址 软件 nginx centos…