首页
📁归档
⏳时光机
🚩友链
📫留言
📧订阅本站
推荐
📕考研课程
🏜️ 免费壁纸
❤ 捐助本站
💰资助名单
🎵音乐实验
Search
1
【NPN/PNP三极管】放大电路饱和失真和截止失真的区别
20,095 阅读
2
论文写作中如何把word里面所有数字和字母替换为新罗马字体
10,653 阅读
3
【高数】形心计算公式讲解大全
9,050 阅读
4
Vivado-FPGA Verilog烧写固化教程
7,948 阅读
5
【概论】一阶矩、二阶矩原点矩,中心矩区别与概念
7,821 阅读
🪶微语&随笔
励志美文
我的随笔
工作办公
📡电子&通信
嵌入式&系统
通信&信息处理
编程&脚本笔记
⌨️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
登录
/
注册
软硬件算法(共12篇)
找到
12
篇与
软硬件算法
相关的结果
Python与Verilog实现卡尔曼滤波器 — 机器人位置估计
目录 一、引言:为什么机器人需要卡尔曼滤波? 二、卡尔曼滤波的五大核心公式 三、以机器人为例卡尔曼公式实现和说明设计运动 传感器的观测值 卡尔曼滤波 四、Verilog实现:定点运算与硬件加速4.1 系统架构 4.2 核心代码解析(1)Kalman_1:状态预测(公式1) (2)Kalman_2_3:协方差预测与增益计算(公式2、3) (3)Kalman_4_5:状态与协方差更新(公式4、5) (4)Kalman_ctrl:数据源控制 4.3 10级流水线时序 五、Python vs Verilog:对比分析 六、总结 工程参考代码清单Python仿真代码 Verilog完整工程文件 一、引言:为什么机器人需要卡尔曼滤波? 想象你开发了一个可以在树林里自主导航的机器人。为了知道它的实时位置,你在机器人上安装了GPS传感器,但GPS的精度大约为10米——在布满沟壑和悬崖的树林里,10米的误差足以让机器人坠入深渊。 此时,你还可以获取一些额外的运动信息:里程计可以记录机器人的行走距离,惯性测量单元(IMU)可以感知姿态变化。但这些传感器同样存在噪声和漂移。GPS告诉了你"大概在哪",里程计告诉了你"走了多远",但两者都不完美。 卡尔曼滤波(Kalman Filter) 的核心思想正是:综合利用所有可用的信息,根据其本身的噪声特性分配权重,得到一个比任何单一估计都更准确的结果。 本文将从原理出发,分别用 Python(浮点运算,算法验证) 和 Verilog(定点运算,硬件加速) 实现一维卡尔曼滤波器,用于机器人位置估计。 二、卡尔曼滤波的五大核心公式 卡尔曼滤波是一种最优线性递归估计算法,由预测(Prediction)和更新(Update)两个阶段组成,共五个核心公式: 对于线性离散系统,卡尔曼滤波通过以下五个公式递归运行: 阶段公式含义预测$\hat{X}_k^- = A\hat{X}_{k-1} + BU_{k-1}$先验状态估计:用上一时刻的最优估计预测当前状态预测$P_k^- = AP_{k-1}A^T + Q$先验误差协方差:预测的不确定性会累积更新$K_k = \frac{P_k^-H^T}{HP_k^-H^T + R}$卡尔曼增益:决定"更信预测还是更信测量"更新$\hat{X}_k = \hat{X}_k^- + K_k(Z_k - H\hat{X}_k^-)$后验状态估计:用测量残差修正预测更新$P_k = (I - K_kH)P_k^-$后验误差协方差:更新后的不确定性其中: $A$ 为状态转移矩阵,$B$ 为控制矩阵,$H$ 为观测矩阵 $Q$ 为过程噪声协方差,$R$ 为测量噪声协方差 $K_k$ 是卡尔曼增益,它是整个算法的"灵魂":$P_k^-$ 越大(预测越不靠谱),$K_k$ 越大,越重视测量反馈。 三、以机器人为例卡尔曼公式实现和说明 下面我们用一个例子来说明上述卡尔曼滤波公式的内在逻辑。 假如我们有一台轮式机器人 Robo 如图所示,它只能够进行一维直线运动。 pmqvRRH.png图片 我们用状态向量 $\vec{x}$ 来表示机器人的当前状态: $\vec{x}=\left[ \begin{array}{c}{p} \\ {v}\end{array}\right] \\$ 其中 $p$ 和 $v$ 分别为相对于绝对坐标系 $O_1$ 的位移。 那么我们要做的是,已知某时刻的最佳状态估计 $\hat{\mathbf{x}}_{k}$ ,预测下一时刻的最佳估计 $\hat{\mathbf{x}}_{k+1}$ 。 设计运动 我们为 Robo 设计一个匀加速直线运动。设初始状态向量为 $\vec{x_0}=\left[ \begin{array}{c}{0} &{1}\end{array}\right]^T $ ,并有一个加速指令性,使其以 $a = 0.1$ 的加速度做匀加速直线运动。那么根据匀加速直线运动的位移计算公式,任意时刻 $k$ 的位移 $p$ 与速度 $v$ 都可以由位移、速度、加速度递推得到: $\begin{aligned} p_{k} &=p_{k-1}+v_{k-1} \times \Delta k+a\times \frac{\Delta k^{2}}{2} \\ v_{k} &=v_{k-1}+a \times \Delta k \end{aligned} \tag{3} $ 将其转化为矩阵形式: $\left[ \begin{array}{c}{p_{k}} \\ {v_{k}}\end{array}\right]=\left[ \begin{array}{cc}{1} & {\Delta k} \\ {0} & {1}\end{array}\right] \left[ \begin{array}{c}{p_{k-1}} \\ {v_{k-1}}\end{array}\right]+\left[ \begin{array}{c}{\frac{\Delta k^{2}}{2}} \\ {\Delta k}\end{array}\right] a \tag{4}$ $x_{t}=F_{t} x_{t-1}+B_{t} a \\$ 我们取时间间隔 $\Delta k = 1$ ,由上式编程计算得到机器人的真实状态向量轨迹: import numpy as np import math import matplotlib.pyplot as plt if __name__=="__main__": ## 1.设计一个匀加速直线运动,以观测此运动 X_real = np.mat(np.zeros((2, 100))) # 空矩阵,用于存放真实状态向量 X_real[:, 0] = np.mat([[0.0], # 初始状态向量 [1.0]]) a_real = 0.1# 真实加速度 F = np.mat([[1.0, 1.0], # 状态转移矩阵 [0.0, 1.0]]) Q = np.mat([[0.0001, 0.0], # 状态转移协方差矩阵,我们假设外部干扰很小 [0.0, 0.0001]]) B = np.mat([[0.5], # 控制矩阵 [1.0]]) for i in range(99): X_real[:, i + 1] = F * X_real[:, i] + B * a_real # 计算真实状态向量 X_real = np.array(X_real) fig = plt.figure(1) plt.grid() plt.title('real displacement') plt.xlabel('k (s)') plt.ylabel('x (m)') plt.plot(X_real[0, :]) plt.show() fig = plt.figure(2) plt.grid() plt.title('real velocity') plt.xlabel('k (s)') plt.ylabel('v (m/s)') plt.plot(X_real[1, :]) plt.show() X_real = np.mat(X_real)pasted_1786518907327_xixy8e.png图片 位移真实值 pasted_1786518920293_nxitzh.png图片 速度真实值 传感器的观测值 我们有一台传感器 $O_2$ 放置于机器人的前方,它可以测得机器人位移 $s$ 和速度 $w$ 。当然,传感器自身本身有一定的噪声 $\xi$ ,其协方差为 $R$ 。由于传感器的信息是相对于自身坐标系 $O_2$ 的,而我们的状态向量 $\vec{x}$ 是相对于绝对坐标系 $O_1$ 的,二者的方向刚好相反,我们使用观测矩阵 $\mathbf{H}$ 来转换二者的关系。因此,相对于真实状态向量,传感器的观测向量可以表示如下: $\left[ \begin{array}{c}{s} \\ {w}\end{array}\right]=\left[ \begin{array}{cc}{-1} & 0\\ {0} & {-1}\end{array}\right] \left[ \begin{array}{c}{p} \\ {v}\end{array}\right]+\left[ \begin{array}{c}{\xi_p} \\ {\xi_v}\end{array}\right] \tag{5}$ $z_{t}=H x_{t}+\xi \\$ 我们设传感器噪声的协方差 $R$ 为单位阵(噪声方差为1)。根据上式编写程序,得到的传感器的观测值如下: ## 2.建立传感器观测值 z_t = np.mat(np.zeros((2, 100))) # 空矩阵,用于存放传感器观测值 H = np.mat(np.zeros((2, 2))) H[0, 0], H[1, 1] = -1.0, -1.0 noise = np.mat(np.random.randn(2,100)) # 加入位移方差为1,速度方差为1的传感器噪声 R = np.mat([[1.0, 0.0], # 观测噪声的协方差矩阵 [0.0, 1.0]]) for i in range(100): z_t[:, i] = H * X_real[:, i] + noise[:, i] z_t = np.array(z_t) fig = plt.figure(3) plt.grid() plt.title('sensor displacement') plt.xlabel('k (s)') plt.ylabel('x (m)') plt.plot(z_t[0, :]) plt.show() fig = plt.figure(4) plt.grid() plt.title('sensor velocity') plt.xlabel('k (s)') plt.ylabel('v (m/s)') plt.plot(z_t[1, :]) plt.show() z_t = np.mat(z_t)pasted_1786518974937_94intu.png图片 传感器观测到的位移,与真实值相反且有噪声 pasted_1786519009388_5uhe6m.png图片 传感器观测到的速度,与真实值相反且有噪声 卡尔曼滤波 前两步的操作主要是为了获取数据,现在我们利用预测和更新公式对以上数据进行卡尔曼滤波: ## 3.执行线性卡尔曼滤波 Q = np.mat([[1.0, 0.0], # 状态转移协方差矩阵,我们假设外部干扰很小, [0.0, 1.0]])# 转移矩阵可信度很高 # 建立一系列空序列用于储存结果 X_update = np.mat(np.zeros((2, 100))) P_update = np.zeros((100, 2, 2)) X_predict = np.mat(np.zeros((2, 100))) P_predict = np.zeros((100, 2, 2)) P_update[0, :, :] = np.mat([[1.0, 0.0], # 状态向量协方差矩阵初值 [0.0, 1.0]]) P_predict[0, :, :] = np.mat([[1.0, 0.0], # 状态向量协方差矩阵初值 [0.0, 1.0]]) for i in range(99): # 预测 X_predict[:, i + 1] = F * X_update[:, i] + B * a_real P_p = F * np.mat(P_update[i, :, :]) * F.T + Q P_predict[i + 1, :, :] = P_p # 更新 K = P_p * H.T * np.linalg.inv(H * P_p * H.T + R) # 卡尔曼增益 P_u = P_p - K * H * P_p P_update[i + 1, :, :] = P_u X_update[:, i + 1] = X_predict[:, i + 1] + K * (z_t[:, i + 1] - H * X_predict[:, i + 1]) X_update = np.array(X_update) X_real = np.array(X_real) fig = plt.figure(5) plt.grid() plt.title('Kalman predict displacement') plt.xlabel('k (s)') plt.ylabel('x (m)') plt.plot(X_real[0, :], label='real', color='b') plt.plot(X_update[0, :], label='predict', color='r') plt.legend() plt.show() fig = plt.figure(6) plt.grid() plt.title('Kalman predict velocity') plt.xlabel('k (s)') plt.ylabel('v (m/s)') plt.plot(X_real[1, :], label='real', color='b') plt.plot(X_update[1, :], label='predict', color='r') plt.legend() plt.show() X_update = np.mat(X_update) X_real = np.mat(X_real)pasted_1786519044132_v2numq.png图片 位移卡尔曼滤波结果 pasted_1786519056398_qpvdv1.png图片 速度卡尔曼滤波结果 可以看出,卡尔曼滤波将存在误差的传感器信号和状态向量很好的融合,较好地预测出了真实地机器人运动状态。 四、Verilog实现:定点运算与硬件加速 当我们需要将卡尔曼滤波部署到FPGA或ASIC上时,Python的浮点运算就不再适用。硬件实现需要考虑:定点数表示、流水线设计、时序约束和资源复用。附件中的Verilog代码展示了一个轻量级、10级流水线的卡尔曼滤波器实现。 4.1 系统架构 pasted_1786519272107_29m7o0.png图片 整个系统分为5个模块: 模块功能对应公式Kalman顶层模块,实例化子模块并连接信号—Kalman_1预测状态 X^-公式1Kalman_2_3预测协方差 P^- 并计算增益 Kg公式2、3Kalman_4_5更新状态 X 和协方差 P公式4、5Kalman_ctrl数据控制器,从DPRAM读取测量值—以及3个共享运算器:add(加减法)、mux(乘法)、div(除法)。 4.2 核心代码解析 (1)Kalman_1:状态预测(公式1) module Kalman_1( input clk_50M, input Rst_n, input [15:0] X_last, // t-1时刻的后验估计 input [15:0] B_X_last, // 控制输入(预留,当前为0) output reg [15:0] X_ // t时刻的先验估计 ); // 流水线计数 0~9 always@(posedge clk_50M or negedge Rst_n) begin if(!Rst_n) X_ <= 16'd0; else begin case (Count) 4'd1: begin X_ <= X_last; // X^- = X_last(简化模型) end // ... 其他时钟周期可用于更复杂的预测 endcase end end endmodule设计要点:当前实现为简化模型,直接令 X^- = X_last。B_X_last 预留了控制输入接口,可扩展为带速度项的运动模型。 (2)Kalman_2_3:协方差预测与增益计算(公式2、3) module Kalman_2_3( input clk_50M, input Rst_n, input [15:0] P_last, output reg [15:0] P_, output reg [15:0] Kg ); reg [15:0] Q_error = 0; // 系统过程协方差 reg [15:0] R_error = 3; // 测量噪声协方差 always@(posedge clk_50M or negedge Rst_n) begin case (Count) 4'd1: begin ADD_sub <= 1'd1; // 加法 ADD_dataa <= P_last * 'd100; // P_last 放大100倍 ADD_datab <= Q_error; // + Q end 4'd2: begin P_ <= ADD_result; // P^-(仍放大100倍) ADD_dataa <= ADD_result / 'd10; // 缩小10倍 ADD_datab <= R_error * 'd10; // R 放大10倍 end 4'd3: begin DIV_dataa <= P_; // 被除数 = P^-(×100) DIV_datab <= ADD_result; // 除数 = P^-/10 + R×10 end 4'd4: begin Kg <= DIV_result; // Kg = P^- / (P^- + R) P_ <= P_ / 'd100; // P^- 恢复原始量级 end endcase end endmodule定点数与缩放处理: 这是硬件实现中最关键的部分。由于16位定点数无法直接表示小数,代码采用了缩放因子(Scaling Factor)策略: P_last * 100:将协方差放大100倍,避免小数被截断 R_error * 10:测量噪声放大10倍 最终 Kg 的计算结果也需要在后续模块中相应缩放 // 在Kalman_4_5中处理缩放还原 MULT_datab <= ADD_result / 'd10; // 乘法结果缩小10倍 ADD_datab <= Kg / 10; // Kg缩小10倍用于1-Kg计算(3)Kalman_4_5:状态与协方差更新(公式4、5) module Kalman_4_5( input clk_50M, input Rst_n, input [15:0] in_data, // 测量值 Z input [15:0] X_, // 先验估计 input [15:0] P_, // 先验协方差 input [15:0] Kg, // 卡尔曼增益 output reg [15:0] X, // 后验估计 output reg [15:0] P // 后验协方差 ); always@(posedge clk_50M or negedge Rst_n) begin case (Count) 4'd2: begin // 计算 |Z - X^-|,通过比较大小确定减法顺序 if(in_data > X_) begin ADD_dataa <= in_data; ADD_datab <= X_; end else begin ADD_dataa <= X_; ADD_datab <= in_data; end end 4'd5: begin MULT_dataa <= Kg; MULT_datab <= ADD_result; // Kg * |Z - X^-| end 4'd6: begin // 根据in_data和X_的大小关系,决定加或减 if(in_data > X_) ADD_sub <= 1'd1; // 加法:X = X^- + Kg*(Z-X^-) else ADD_sub <= 1'd0; // 减法:X = X^- - Kg*(X^- - Z) ADD_dataa <= X_; ADD_datab <= MULT_result / 'd10; end 4'd7: begin X <= ADD_result; // 输出更新后的状态 ADD_dataa <= 16'd1; ADD_datab <= Kg / 10; // 1 - Kg/10 end 4'd8: begin MULT_dataa <= P_; MULT_datab <= ADD_result; // P = P^- * (1 - Kg) end 4'd9: begin P <= MULT_result; // 输出更新后的协方差 end endcase end endmodule有符号数处理技巧:由于Verilog中 [15:0] 默认是无符号数,代码巧妙地通过先取绝对值做减法,再根据大小关系决定加减,避免了有符号数运算的复杂性。 (4)Kalman_ctrl:数据源控制 该模块模拟了传感器数据源。实际应用中,这里可以替换为ADC接口、串口接收或传感器总线(如I2C/SPI)。 4.3 10级流水线时序 pasted_1786519515333_7gcyvi.png图片 每个滤波周期 = 10个时钟周期 @ 50MHz = 200ns,即每秒可处理500万次滤波迭代,这是硬件加速相比软件的巨大优势。 五、Python vs Verilog:对比分析 对比维度Python实现Verilog实现数据表示IEEE 754 双精度浮点(64位)16位定点整数 + 缩放因子运算精度高(~15位有效数字)有限(受限于位宽和缩放策略)执行速度受限于CPU,~μs级50MHz时钟,200ns/次迭代资源消耗内存(MB级)LUT/FF/DSP(硬件资源)开发难度低,代码简洁高,需处理时序、缩放、位宽适用场景算法验证、仿真、离线分析实时嵌入式、FPGA加速、ASIC可移植性跨平台依赖具体FPGA/ASIC工艺关键差异——定点数缩放: Python中可以直接写 Kg = P_pred / (P_pred + R),而Verilog中必须手动管理数值范围: # Python:自然优雅 Kg = P_pred / (P_pred + R) # 0.09... X = X_pred + Kg * (z - X_pred)// Verilog:需要精心设计的缩放 // P_last * 100 -> ADD -> /100 -> DIV -> Kg // Kg / 10 -> MULT -> /10 -> ADD -> X这种"不优雅"是硬件实现的代价,但也是达到实时性能的必要手段。 六、总结 卡尔曼滤波是机器人状态估计的基石算法。本文从机器人位置估计这一经典场景出发: 原理层面:梳理了卡尔曼滤波的五大核心公式,阐明了预测与更新的闭环逻辑。 Python层面:利用浮点运算实现了算法原型,验证了滤波器对噪声的抑制效果和增益收敛特性。 Verilog层面:设计了10级流水线的定点数实现,通过缩放因子策略在16位位宽下完成了滤波运算,达到了200ns/次迭代的实时性能。 两种实现方式各有其用:Python用于"想清楚",在算法层面验证正确性;Verilog用于"跑得快",在硬件层面实现实时处理。在实际工程中,通常先用Python/MATLAB进行算法仿真和参数整定,再将验证通过的算法移植到Verilog/VHDL进行硬件部署——这正是从算法到芯片的完整开发流程。 工程参考代码清单 Python仿真代码 见上文第三节完整代码。 Verilog完整工程文件
FPGA&ASIC
通信&信息处理
VLSI&IC验证
软硬件算法
# ASIC/FPGA
# 信号处理
# Verilog
# 软件算法
刘航宇
2天前
1
22
2
在FPGA上部署轻量BP神经网络-Python与Verilog:信号检测实战
目录 储备知识: 一、背景:为什么要在FPGA上跑神经网络? 二、系统整体架构 三、神经网络模型设计:极致轻量 四、FPGA硬件实现详解4.1 串并转换模块(Serial-to-Parallel 4.2 数据归一化模块 4.3 隐藏层神经元模块(核心计算单元) 4.4 ReLU激活函数模块 4.5 输出层神经元模块 4.6 阈值判断模块 五、关键优化技巧5.1 归一化的"逆向"处理 5.2 权值二值化(BWN)降低功耗 5.3 亚稳态消除 六、实验结果:功耗与性能 七、总结与思考 python和verilog源程序下载 储备知识: 我们在前文讲了BP神经网络算法和激活函数 BP神经网络算法 https://ee.ac.cn/index.php/archives/612.html 激活函数讲解 https://ee.ac.cn/index.php/archives/617.html 一、背景:为什么要在FPGA上跑神经网络? 在无源物联网(Battery-free IoT)和反向散射通信(Backscatter)场景中,传统的信号检测方法(如能量检测)虽然电路简单,但无法区分同频段的多种信号,且受噪声影响大;而基于PC或GPU的深度学习方案虽然精度高,但功耗动辄数十瓦,显然无法部署在依赖能量采集的微瓦级标签上。 核心矛盾在于:缺乏一种检测精度高、功耗极低(毫瓦级)、且能实时处理的解决方案。 答案是将轻量级的BP神经网络部署到低功耗FPGA上,在边缘端完成信号推理。 二、系统整体架构 整个检测系统的链路如下: 目标信号 → 天线 → 阻抗匹配 → LNA放大 → 包络检波(ENV) → ADC采样(1MSPS, 12bit) → FPGA(BP神经网络推理) → 调制控制信号在FPGA内部,BP神经网络负责识别当前输入的512点采样数据是否为目标信号的前导码(Preamble)。一旦识别成功,输出控制信号,开启后端的Backscatter调制模块。 FPGA选型:Actel IGLOO/e系列 AGLE600V5-FG484。选择它的原因是: Flash架构:上电即运行,无需外部配置芯片,适合无源系统; 超低功耗:静态功耗仅0.046mW,适合能量采集场景; 资源适中:13824个逻辑单元,足以容纳轻量级网络。 三、神经网络模型设计:极致轻量 为了在FPGA上高效部署,网络结构必须足够"轻"。本文设计的网络结构如下: pasted_1785558315326_lc6zyp.png图片 层级神经元数说明输入层512对应ADC采集的一条信号的512个采样点隐藏层2仅2个神经元,大幅降低计算量输出层1二分类:是目标信号(1) / 非目标信号(0)激活函数:隐藏层和输出层均采用 ReLU。 $$f(x) = \max(0, x)$$ 选择ReLU的原因不仅是训练效果好,更重要的是硬件实现极其简单——只需一个比较器判断正负,无需计算指数或乘法,这对FPGA非常友好。 pasted_1785561816152_bsny25.png图片 训练环境:TensorFlow/Keras,使用Adam优化器和MSE损失函数。在真实环境数据集上训练后,测试集识别准确率可达 95.76%。 pasted_1785561967835_yy5zgm.png图片 四、FPGA硬件实现详解 FPGA内部的神经网络是一个纯推理引擎。本节按数据流方向,逐个拆解每个模块的设计思路和Verilog实现要点。 pasted_1785562098406_gf64mu.png图片 整体RTL结构如下: pasted_1785563631508_f74j4w.png图片 4.1 串并转换模块(Serial-to-Parallel pasted_1785563649652_chqrj8.png图片 ADC(ADS7042)在片选信号CS拉低后,先输出2个前导0,再输出12位有效数据(D11→D0),每个数据位占用一个CLK周期。 设计要点: CS和CLK由FPGA内部产生,CS每16个CLK周期拉低一次; 在CS下降沿开始,用计数器对串行数据移位寄存; 凑齐12位有效数据后,输出一组并行数据 adc_data[11:0]。 波形图 pmhbofe.png图片 module serial_p(input data0_in,input cs,input clk,output[12:0] data0_out,input rst); reg[11:0] temp; reg[12:0] save; integer count; integer cnt; always @(negedge clk or negedge rst) begin if(!rst) begin//reset temp<=12'b000000000000; count<=-1; save<=12'b111111111111; cnt<=-1; end else if(cs&&count%16==0) begin save<={1'b0,temp}; count<=1; cnt<=cnt+1; if(cnt==512) begin cnt<=1; end end else if(cs) begin temp<=12'b0; count<=1; end else if(count<3) count<=count+1; else begin temp<={temp,data0_in}; count<=count+1; end end assign data0_out=save; endmodule 4.2 数据归一化模块 问题:训练时,输入数据被归一化到 [0, 1.8](对应ADC参考电压1.8V)。如果在FPGA中直接用除法做 Vin = adc_data / 4095 * 1.8,会消耗大量逻辑资源(FPGA做除法器代价极高)。 解决方案:在软件端完成归一化,FPGA端只做整数运算。 具体做法: 在Python/Keras训练时,将输入数据 x 归一化为 x_norm = x / 4095 * 1.8; 在提取权值时,将第一层权值 w 除以 1.8 并放大 1024 倍取整; 这样FPGA输入的原始ADC值 adc_data(范围0~4095)直接与预处理后的权值相乘,等效于完成了归一化+加权。 # 创建一个自定义的回调对象 cb = MyCallback() # 以只读方式打开训练数据文件 with open('/content/work/data/data.txt') as traindata: # 读取文件内容 data = traindata.read() # 将文件内容转换为numpy数组,并将数据类型转换为float,并将数组形状调整为(47100, 2) data = np.array(list(data.split()), dtype=float).reshape((47100, 2)) # 创建一个最大最小归一化对象 min_max_scaler = preprocessing.MinMaxScaler() # 归一化 data = data * (1/65535) # 以只读方式打开训练标签文件 with open('/content/work/data/label.txt') as labda: # 读取文件内容 labels = labda.read() # 将文件内容转换为numpy数组,并将数据类型转换为float,并将数组形状调整为(47100, 2) labels = np.array(list(labels.split()), dtype=float).reshape((47100, 2)) # 对data和labels进行乱序,保持它们的对应关系 # 为了保持对应关系,需要先将data和labels沿着第二个维度拼接起来,形成一个(100, 4)的数组 # 然后对这个数组进行乱序,再将它们沿着第二个维度分开,还原成data和labels data_labels = np.concatenate((data, labels), axis=1) np.random.shuffle(data_labels) data = data_labels[:, :2] labels = data_labels[:, 2:]FPGA端实现: 实际上,归一化模块在FPGA中被合并到了权值预处理阶段,硬件上不需要额外的归一化电路。但为了模块化清晰,也可以显式设计一个"电压映射"模块,将12bit ADC码值转换为定点数格式: 工程建议:永远不要在FPGA里做除法。所有线性变换(归一化、缩放)都应在软件端合并到权值中,FPGA只做整数乘加。 4.3 隐藏层神经元模块(核心计算单元) 隐藏层的数学表达式: $$y = \text{ReLU}\left( \sum_{i=1}^{512} x_i \cdot w_i + b \right)$$ , i = 1..512 工程难点: 512次乘累加如果在一个时钟周期完成,需要512个乘法器,资源爆炸; 如果纯串行完成,需要512个时钟周期,一条chirp要512×512=262144个周期,太慢。 解决方案:采用 串行MAC流水线 + 双缓冲FIFO 架构。 设计思路: 权值存储:512个权值和1个偏置在编译时以 reg [31:0] w[0:511] 形式固化在FPGA中; FIFO缓存:ADC每输出一个采样点,立即写入FIFO。FIFO深度设为16即可(只要保证不溢出),因为读出速度(MAC运算)和写入速度(ADC采样)基本匹配; 串行MAC:每个时钟周期从FIFO读出一个数据 xi,与对应的 wi 相乘,累加到累加器 acc; 512个周期后:累加器结果加上偏置 b,送入ReLU模块。 pasted_1785563798085_32317i.png图片 FIFO波形图 pmhqDBt.png图片 关键设计细节: 位宽设计:输入22bit × 权值32bit = 乘法结果54bit,累加512次需要额外9bit,因此累加器至少需要 63bit。如果位宽不够,累加会溢出,导致结果完全错误。 时序约束:乘法器和加法器要满足16MHz时钟的建立保持时间。如果组合逻辑延迟太大,需要插入流水线寄存器(将乘法器和加法器分两级完成)。 双神经元并行:隐藏层有2个神经元,需要例化两个上述模块,各自独立的权值存储和累加器,但共享同一个FIFO输入。 4.4 ReLU激活函数模块 ReLU的硬件实现是整个网络中最简单的部分: pmhqWcj.png图片 module relu ( input wire clk, input wire rst_n, input wire [31:0] x_in, // MAC+bias结果 input wire x_valid, output reg [31:0] y_out, output reg y_valid ); always @(posedge clk or negedge rst_n) begin if (!rst_n) begin y_out <= 32'd0; y_valid <= 1'b0; end else begin y_valid <= x_valid; if ($signed(x_in) >= 0) y_out <= x_in; else y_out <= 32'd0; end end endmodule4.5 输出层神经元模块 输出层只有1个神经元,输入是隐藏层2个神经元的输出: $$\text{output} = \text{ReLU}\left( h_1 \cdot w'_1 + h_2 \cdot w'_2 + b' \right)$$ 这个模块与隐藏层类似,但逻辑资源消耗极小。 pmhq7NT.png图片 module nerual_out( clk, rst,x1,x2,data_out ); input clk, rst; input signed[0:49] x1,x2; output signed[79:0] data_out; //<statements> //reg signed[0:28] weight[0:1]; reg signed[0:7] save,save1,save2; localparam bais=-50'd214979034352654; //localparam bais = -26'd6406874; localparam weight0 = 29'd22506522; localparam weight1 =-29'd341584992; //integer bais; integer count; always @(negedge clk or negedge rst) begin if(!rst) //reset,set values and bias begin // bais<=-50'd214979034352654; // weight[0]<=29'd22506522; // weight[1]<=-29'd341584992; save<=0; save1<=0; save2<=0; count<=0; end else begin save1<=x1*weight0; save2<=x2*weight1; count<=count+1; if(count>518) save<=save1+save2+bais;//Multiply the input by the weights and add up the results end end assign data_out=save; endmodule4.6 阈值判断模块 为什么需要阈值判断? 前面提到,我们在保存权值时将所有参数扩大了 1024 倍(即 $2^{10}$)。这意味着: 隐藏层的输出值也被放大了约1024倍; 输出层的计算结果同样被放大; 原本训练时,输出层ReLU后大于0即判定为目标信号; 现在由于数值被放大,不能简单以0为界,需要设定一个等效的阈值。 阈值推导: 原始判定边界:输出 > 0.5(二分类常用阈值); 放大后等效边界:threshold = 0.5 × 1024 × 1024 = 524288; 实际上,由于隐藏层和输出层都放大了1024倍,总放大倍数为 $1024^2 = 1048576$,因此: threshold = 0.5 × 1048576 = 524288。 波形图 pmhqXv9.png图片 五、关键优化技巧 5.1 归一化的"逆向"处理 再次强调这个最重要的优化: 处理方式FPGA资源消耗推荐度FPGA内做除法(adc/4095)极高,需除法器IP❌FPGA内用移位近似除法中等,精度损失大△权值预处理(本文方法)零额外消耗✅具体做法: 训练时归一化:$V_{\text{in}} = \frac{\text{adc_data}}{4095} \times 1.8$; 提取权值时:$w' = \frac{w}{1.8} \times 1024$,取整; FPGA输入:直接使用原始ADC值 $x$(0~4095); 等效运算。 5.2 权值二值化(BWN)降低功耗 为了进一步压缩模型、降低功耗,本文采用了 Binary-Weight-Networks (BWN) 对权值进行二值化: 权值只取 +1 或 -1; 引入缩放因子 α 保证量化后的权值尽可能接近原始浮点权值。 二值化后,乘法运算退化为加减法或符号位判断,FPGA资源占用和动态功耗大幅下降。虽然精度略有损失,但在本场景中仍能保持97%以上的识别率。 二值化乘法器的Verilog实现: // 二值化权值乘法:只需判断符号 wire signed [31:0] product; assign product = bin_weight ? x_in : -x_in; // bin_weight=1为正,0为负相比32bit×32bit的硬件乘法器,这个逻辑几乎不消耗DSP资源。 5.3 亚稳态消除 ADC数据输入和CS信号是异步信号(相对于16MHz系统时钟),必须做双级触发器同步,防止亚稳态传播: reg adc_dat_d1, adc_dat_d2; always @(posedge clk) begin adc_dat_d1 <= adc_dat; // 第一级 adc_dat_d2 <= adc_dat_d1; // 第二级(稳定后使用) end wire adc_dat_sync = adc_dat_d2;六、实验结果:功耗与性能 通过 Actel Libero SOC 对FPGA各模块进行功耗仿真,结果如下: 工作模式静态功耗动态功耗总功耗空闲态0.046 mW00.046 mW正常工作0.046 mW2.79 mW2.835 mWFlash*Freeze模式0.115 mW00.115 mW2.835 mW 的总功耗,对于低功耗设备来说是完全可以接受的。 各子模块动态功耗分解: 串并转换:0.01 mW 隐藏层神经元(含FIFO):0.083 mW ReLU激活:0.067 mW 输出层神经元:0.067 mW 七、总结与思考 本文介绍了一套完整的从算法到硬件的轻量化神经网络部署方案: 算法层:针对低功耗约束,设计了512-2-1极简BP网络,避免使用CNN/RNN等重算力模型; 数据层:在真实环境中采集目标信号,建立含噪样本库,并通过数据增强提升泛化能力; 硬件层:基于Actel低功耗FPGA,用FIFO流水、整数运算、ReLU硬判决、权值二值化等手段,实现了毫瓦级推理; 系统层:与射频前端(LNA、包络检波、ADC)紧密配合,形成完整的标签信号检测链路。 可扩展性:该FPGA神经网络框架不仅可用于目标信号检测,只需更换训练数据集和权值参数,即可用于FMCW、RFID等其他射频信号的识别,具备良好的通用性。 python和verilog源程序下载
FPGA&ASIC
通信&信息处理
机器学习
软硬件算法
# ASIC/FPGA
# 信号处理
# 机器学习
# 软件算法
# 物联网
# Python
刘航宇
8月1日
0
39
6
基于51/52单片机电容、电感、电阻参数测量系统设计与Proteus仿真
目录 一、设计任务和要求 二、方案设计与论证2.1 系统总设计框图 2.2 方案一:电桥平衡原理① 电阻的测量:直流电桥平衡原理 ② 电容的测量:交流电桥平衡原理 ③ 电感的测量:交流电桥平衡原理 2.3 方案二:频率转换法(本设计采用)① 电阻的测量:555 构成单稳态 ② 电容的测量:555 构成单稳态 ③ 电感的测量:电容三点式正弦波振荡 2.4 方案对比与选择 三、单元电路设计和参数计算3.1 电阻的检测单元电路设计 3.2 电容的检测单元电路设计 3.3 电感的检测单元电路设计 四、总原理图及元器件清单4.1 总原理图 4.2 元器件清单 五、程序设计5.1 程序流程图 5.2 核心代码中断函数(频率测量) 主函数 显示函数 键盘检测函数 5.3 完整程序清单及protues仿真文件(附录) 六、安装与调试6.1 仿真调试(1)电阻测量电路仿真 (2)电容测量电路仿真 (3)电感测量电路仿真 调试要点 液晶显示电路调试 仿真整体显示 6.2 实物调试电阻检测 电容检测 电感检测 八、参考文献 九、附录-完整软件程序与protues仿真文件 完整程序与仿真文件见文章末尾 一、设计任务和要求 设计并制作一个元器件参数测量仪 电阻阻值测量,范围:100Ω ~ 1MΩ 电容容值测量,范围:100pF ~ 10000pF 测量精度:±5% 电感参数的测量 扩大量程 提高测量精度 二、方案设计与论证 2.1 系统总设计框图 pasted_1784990858481_sxzptf.png图片 图1 系统总设计框图在测量各元件参数时,采用首先把电感、电容、电阻的参数转化为频率[1],再送至单片机检测,检测结束后,送至 LCD1602 进行显示。 2.2 方案一:电桥平衡原理 ① 电阻的测量:直流电桥平衡原理 pasted_1784990980814_5yavx3.png图片 图2 直流电桥平衡电路图根据电路平衡原理,不断调节电位器 R3,使得电表指针指向正中间,再测量电位器电阻值。 ② 电容的测量:交流电桥平衡原理 pasted_1784991076614_jito75.png图片 图3 交流电桥平衡电路图通过调节 Z1、Z2 使电桥平衡。这时电表的读数为零。通过读取 Z1、Z2、Zn 的值,即可得到被测电容的值。 ③ 电感的测量:交流电桥平衡原理 pasted_1784991402619_84r7tl.png图片 图4 交流电桥平衡电路图2.3 方案二:频率转换法(本设计采用) ① 电阻的测量:555 构成单稳态 pasted_1784991953296_lg44ir.png图片 图5 555 定时器构成单稳态电路图根据 555 定时器构成单稳态,产生脉冲波形,通过单片机读取高低电平得出频率,通过公式换算得到电阻阻值。 ② 电容的测量:555 构成单稳态 pasted_1785027612555_za97df.png图片 图6 555 定时器构成单稳态电路图** 根据 555 定时器构成单稳态,产生脉冲波形,通过单片机读取高低电平得出频率,通过公式换算得到电容值。 ③ 电感的测量:电容三点式正弦波振荡 pasted_1785027682239_4u3sw9.png图片 图7 电容三点式正弦波振荡电路图由电容三点式正弦波振荡电路得出频率,通过单片机读取高低电平得出频率,通过公式换算得到电感值。 2.4 方案对比与选择 对比项方案一(电桥法)方案二(频率法)测量精度受人为调节影响大单片机数字化,精度高操作难度调节麻烦,不易操作自动测量,一键显示电感测量不易测得准确值振荡电路,相对稳定数字化不易数字化便于单片机处理结论: 方案二通过单片机读取转化,精确度会明显提高,且便于数字化显示。故本设计选择方案二。 三、单元电路设计和参数计算 3.1 电阻的检测单元电路设计 pasted_1785027698225_mjicut.png图片 图8 555 定时器构成单稳态电路图由 555 单稳态触发公式: $$f=\frac{1}{\ln2(R_{1}+2R_x)C}$$ 得: $$R_x=\frac{1}{2}(\frac{1}{\ln2fC}-R_{1})$$ 从而,可以计算得出电阻值的大小。 3.2 电容的检测单元电路设计 pasted_1785028035648_x380cp.png图片 图9 555 定时器构成单稳态电路图根据 555 定时器构成单稳态,产生脉冲波形,通过单片机读取高低电平得出频率,通过公式换算得到电容值[2]。 由: $$f=\frac{1}{\ln2^*(R_1+2R_2)^*C_x}$$ 若 R1 = R2,得: $$C_x=\frac{1}{3\ln2^*f^*R_1}$$ 从而,可以计算得出电容值的大小。 3.3 电感的检测单元电路设计 pasted_1785028091408_lymeeb.png图片 图10 电容三点式正弦波振荡电路图由 LC 振荡频率公式: $$f=\frac{1}{2\pi\sqrt{\frac{C_1*C_2}{C_1+C_2}*L_x}}$$ 得: $$L_x=\frac{1}{\left(2\pi f\right)^2\frac{C_1*C_2}{C_1+C_2}}$$ 从而,可以计算得出电感值的大小。 四、总原理图及元器件清单 4.1 总原理图 pasted_1785028273962_o8rzuv.png图片 图11 电阻、电容、电感参数测试总原理图 pasted_1785028294514_u0wku4.png图片 图12 测量电阻、电容部分的原理图 pasted_1785028310991_mmmde2.png图片 图13 测量电感以及 ±5V 发生原理图4.2 元器件清单 元件序号型号数量备注U1STC89C52RC1单片机R3A103J1排阻R2, R3, R17, R1810KΩ4电位器X111.0592M1晶振C325V, 10uF1电解电容C1, C2, C11103 (0.01uF)3瓷片电容R1, R13, R1810KΩ3电阻J1, J2, J3, J4—4按键U216021液晶A1, A2NE5552555 定时器R4300Ω1电阻R5, R6510KΩ2电阻C4~C9104 (0.1uF)6瓷片电容R7, R9100KΩ2电阻C1050V, 22uF1电解电容Q1~Q3S9108 (NPN)3三极管R102KΩ1电阻R8, R11, R141KΩ3电阻C12, C1350V, 47uF2电解电容R12, R1939KΩ2电阻R15, R1651Ω2电阻C1425V, 470uF1电解电容U3NE555321运放五、程序设计 5.1 程序流程图 图14 中断程序流程图 pasted_1785028347463_kh1ui9.png图片 图15 主程序流程图 pasted_1785028417008_m02be6.png图片 初始化 (led_init, t_init, keyscan) ↓ 清屏 ↓ 循环: ├── 显示频率 display_f(f) └── 根据 a 值分支: ├── case 1: 计算电阻 R,显示 display_R(R) ├── case 2: 计算电容 C,显示 display_C(C) └── case 3: 计算电感 L,显示 display_L(L)5.2 核心代码 中断函数(频率测量) void T0_count() interrupt 1 { switch(a) { case 1: // 电阻测量 while(R_out); while(!R_out); TH0 = 0; TL0 = 0; while(R_out); while(!R_out); th0 = TH0; tl0 = TL0; TR0 = 0; break; case 2: // 电容测量 while(C_out); while(!C_out); TH0 = 0; TL0 = 0; while(C_out); while(!C_out); th0 = TH0; tl0 = TL0; TR0 = 0; break; case 3: // 电感测量 while(L_out); while(!L_out); TH0 = 0; TL0 = 0; while(L_out); while(!L_out); th0 = TH0; tl0 = TL0; TR0 = 0; break; } f = 1000000.0 / 1.085069 / (th0 * 256 + tl0) + 0.5; }主函数 void main() { led_init(); // 液晶初始化 t_init(); // 定时器初始化 keyscan(); // 按键检测 write_com(0x01); // 清屏 while(1) { display_f(f); switch(a) { case 1: // 电阻: R = (T/0.693 - 150) 换算 R = (ulong)(5000000.0 / 0.6931472 / f - 150 + 0.5); display_R(R); break; case 2: // 电容: C = 换算公式 C = (int)(100000000.0 / 153 / 0.6931472 / f + 0.5); display_C(C); break; case 3: // 电感: L = 1/(4π²f²C) 换算 L = (int)(1000000000000.0 / 0.1 / PI / PI / f / f + 0.5); display_L(L); break; } } }显示函数 // 频率显示 void display_f(ulong f) { uchar count = 0; ulong f0 = f; while(f) { f = f / 10; count++; } for(num = 5 + count; num > 5; num--) { table2[num] = f0 % 10 + 48; f0 = f0 / 10; } write_com(0x80); for(num = 0; num < 6 + count; num++) { write_data(table2[num]); delayms(5); } } // 电阻显示 void display_R(ulong R) { uchar count = 0; ulong R0 = R; while(R) { R = R / 10; count++; } for(num = 6 + count; num > 6; num--) { table3[num] = R0 % 10 + 48; R0 = R0 / 10; } write_com(0x80 + 0x40); for(num = 0; num < 7 + count; num++) { write_data(table3[num]); delayms(5); } } // 电容显示 void display_C(uint C) { uchar count = 0; uint C0 = C; while(C) { C = C / 10; count++; } for(num = 5 + count; num > 5; num--) { table4[num] = C0 % 10 + 48; C0 = C0 / 10; } write_com(0x80 + 0x40); for(num = 0; num < 6 + count; num++) { write_data(table4[num]); delayms(5); } } // 电感显示 void display_L(uint L) { uchar count = 0; uint L0 = L; while(L) { L = L / 10; count++; } for(num = 5 + count; num > 5; num--) { table5[num] = L0 % 10 + 48; L0 = L0 / 10; } write_com(0x80 + 0x40); for(num = 0; num < 6 + count; num++) { write_data(table5[num]); delayms(5); } }键盘检测函数 void keyscan() { if(key_R == 0) // 检测电阻按键 { delayms(10); // 消抖 if(key_R == 0) a = 1; } else if(key_C == 0) // 检测电容按键 { delayms(10); if(key_C == 0) a = 2; } else if(key_L == 0) // 检测电感按键 { delayms(10); if(key_L == 0) a = 3; } else while(key_R && key_C && key_L); // 等待按键按下 }5.3 完整程序清单及protues仿真文件(附录) 见文末附录部分。 六、安装与调试 6.1 仿真调试 (1)电阻测量电路仿真 图16 电阻测量的仿真电路图 pasted_1785028525830_e8xagf.png图片 图17 电阻测量的仿真输出波形 pasted_1785028553380_77yjdf.png图片(2)电容测量电路仿真 图18 电容测量的仿真电路图 pasted_1785028566386_213kgj.png图片 图19 电容测量的仿真输出波形 pasted_1785028577785_fn2fqc.png图片(3)电感测量电路仿真 图20 电感测量的仿真电路图 pasted_1785028588732_9bvoyx.png图片 图21 电感测量的仿真输出波形 pasted_1785028599631_39ilf2.png图片调试要点 接通电源,用示波器观察输出波形,若为方波,则电路焊接无误,否则检查电路[3] 在调试过程中发现,若改变电源电压,输出方波的频率会发生变化,计算出的数值存在一定误差 当 VCC 为 3.25V 左右时误差较小 液晶显示电路调试 将测量电路的输出分别与单片机的 P1^5、P1^6、P1^7 相接,观察液晶是否显示测量结果 在调试过程中发现,电阻、电容的测量误差较小 由于电容三点式震荡电路的频率不稳定,电感测量的误差较大 仿真整体显示 图22 电阻测量显示 pasted_1785028620741_10mwwy.png图片 图23 电容测量显示 pasted_1785028631102_motyub.png图片 图24 电感测量显示 pasted_1785028644198_twwyn4.png图片 图25 电源生成模块显示 pm2vEdK.png图片6.2 实物调试 实物按照仿真电路搭建,通电显示 "Welcome! RCL detector"。 图26 实物开机界面 pm2vmJe.png图片电阻检测 插入 2.7KΩ 电阻 若显示不准确,调节仿真中 R2 对应的电位器即可改变输出频率,使得检测精准 检测显示 2735Ω,可见调试准确 图27 电阻检测显示 pm2vnRH.png图片电容检测 插入 0.1uF 电容 若显示不准确,调节仿真中 R3 对应的电位器即可改变输出频率,使得检测精准 检测显示 101pF,可见调试准确[4] 图28 电容检测显示 pm2vuzd.png图片电感检测 插入 65mH 电感 若显示不准确,调节程序计算公式5,即可改显示结果,使得检测精准 检测显示 65535uH,可见调试准确 图29 电感检测显示 pm2vMQA.png图片八、参考文献 [1]杨沁佳.浅谈电感传感器微位移检测仪的原理与设计[J].数字通信世界,2019(09):143+159. [2]张璐雅,阮景.一种基于555时基电路的振荡器设计[J].集成电路应用,2019,36(12):12-13. [3]谢琪林.浅谈STC89S51单片机时序[J].电子测试,2020(24):139-140. [4]黄江.基于STM32的高精度电容测量仪设计[J].信息技术与信息化,2020(11):140-141. [5]王晓辉,王智永,韩智伟,胡帅可,谢印庆.基于三极管放大原理的电容三点式LC振荡器的设计[J].电子世界,2020(03):206. [6] E. L. Pankratov. An approach to model manufacturing of an enhanced swing differential Colpitts oscillator based on heterostructures to increase density of their elements with account mismatch-induced stress: on optimization of annealing. 2020, :1-19. 九、附录-完整软件程序与protues仿真文件 // ============================================ // RCL 测量仪程序 // 基于 STC89C52RC + NE555 + LCD1602 // ============================================ #include <reg52.h> #define uint unsigned int #define uchar unsigned char #define ulong unsigned long #define PI 3.1415926 // ========== 字符串常量 ========== uchar code table1[8] = "Welcome! RCL detector"; uchar table2[16] = "f(Hz)="; uchar table3[16] = "R(Ohm)="; uchar table4[16] = "C(pF)="; uchar table5[16] = "L(uH)=";
嵌入式&系统
通信&信息处理
软硬件算法
# 嵌入式
# 信号处理
# 软件算法
# 物联网
刘航宇
7月26日
0
34
5
2026-07-24
利用图像处理技术mfc设计细胞识别程序-C++
目录 一、图像处理设计目标1.1 课题与技术指标 1.2 任务点 二、设计思路 三、创建工程及算法分析3.1 预备工作及工程创建 3.2 打开 BMP 图片与显示 HSI 值代码分析 主要代码 3.3 Mark - 细胞标记代码分析 主要代码 3.4 OnTwoValue() - 二值化代码分析 状态标记说明 主要代码 3.5 OnFillHoles() - 填洞代码分析 主要代码 3.6 shrink() - 收缩代码分析 主要代码 3.7 findcenter() - 中心点获取代码分析 合并中心点 去除多余圆或者错误圆 主要代码 3.8 count() - 统计代码分析 3.9 OnCellprgAll() - 按序执行全部 四、错误情况及分析4.1 未声明全局变量信息头 4.2 错把指针当作 int 型 4.3 软件菜单栏突然丢失 4.4 矩形框在鼠标未按下就出现了 五、总结 参考文献 一、图像处理设计目标 1.1 课题与技术指标 课题: 利用图像处理技术设计细胞识别程序。 设计指标: 实验 VS2019 MFC 开发平台 待识别图像为 24bit 的真彩色细胞图像进行处理 要求识别出细胞,并且保证准确度情况下,统计出细胞的个数和大小 1.2 任务点 完成图像标注:添加 cdib.h、cdib.cpp、添加函数、添加消息响应函数、添加菜单、打开图像 完成细胞的判别:持续判断 Maybe 点邻域有无 Mark 点、Sobel 计算边缘、删除孤立边缘、生成黑白图像 细胞收缩:利用 stack、vector 获取孔洞坐标、填充孔洞、Edge 处置 0、4 方向、8 方向交替生成边缘 细胞中心点信息获取:递归算法、判别局部是否全是边缘、储存全部中心位置 错误信息的删除:计算中心点均值、半径、去除半径过小的点、包含圆的剔除、相交过大圆的剔除、信息统计与显示 二、设计思路 图像处理之细胞识别的主要工作就是识别细胞数目以及选择出指定大小细胞。设计思路如下图所示。同时我们要去除不符合要求的中心点,来确定最终的细胞数目,去除太小和太大的细胞1,并结合实际来修改各种参数,设计合理。 pasted_1784860654942_5veaow.png图片 图1 系统设计思路 (流程图:打开图像 → HSI 转换 → 细胞标记 → 二值化 → 填洞 → 收缩 → 中心点获取 → 错误删除 → 统计输出)三、创建工程及算法分析 3.1 预备工作及工程创建 (1)VS2019 安装 MFC 与 C++ 开发环境 (2)创建 MFC 工程,配置 MFC 应用程序向导 pasted_1784860679771_dmm4ha.png图片 pasted_1784860708914_t33nmc.png图片 图2 创建工程 / 图3 MFC 配置3.2 打开 BMP 图片与显示 HSI 值 代码分析 (1)在 OnDraw 函数实现显示图片;在 messages 中添加 display() 实现打开一幅图像的功能。在 LoadBmp() 函数中利用 fopen 实现自动打开图片,利用 fread 读取图片信息。 (2)通过:查看 → 建立类向导 → 添加 OnMouseMove() 函数,添加代码实现获取所要信息;实现鼠标移动显示 HSI。 (3)通过函数 RgbtoHsi 实现 RGB 向 HSI 的转化。 RGB → HSI 转换原理: RGB 向 HSI 模型的转换是由一个基于笛卡尔直角坐标系的单位立方体向基于圆柱极坐标的双锥体的转换。基本要求是将 RGB 中的亮度因素分离,将色度分解为色调和饱和度,并用角向量表示色调2。如果直接对 R、G、B 处理,其处理过程中很可能会引起三个量不同程度的变化,这样就会产生色差问题,甚至带来颜色上的失真。HSI 模型的出现,使得在保持色彩无失真的情况下实现图像处理成为可能。 pasted_1784860731276_p9qcks.png图片 图4 HSI 显示效果主要代码 // HSI 主要代码示意 void RgbtoHsi(RGB* rgb, HSI* hsi) { // 将 RGB 转换为 HSI 色彩空间 // H: 色调, S: 饱和度, I: 亮度 // 分离亮度因素,保持色彩无失真 }pasted_1784860760614_roywnx.png图片 图5 HSI 主要代码3.3 Mark - 细胞标记 代码分析 确定 Mark 与 mbMark 是通过半径来的,通过计算图像上的某个像素点与细胞内部像素点的欧几里得距离与归一化门限值分别为 0.09 和 0.15 比较: r < 0.09 → Mark(红色,细胞内部) 0.09 < r < 0.15 → mbMark(蓝色,可能边界) OnMouseMove 函数里调用 RgbtoHsi(&rgb, &Hsi) 函数,可以在屏幕上显示鼠标所指点的坐标以及 RGB、HSI 和灰度值,通过 HSI 的可以选取合适的阈值来找到细胞以及边界3。 边缘提取的方法: 先滤波,去除噪声的影响,防止细胞内的噪声的影响 之后用 Sobel 算子 求出边界 之后画一个 5×5 的矩形,如果没有遇到边界,是噪声,去除 边界点是绿色。细胞边界分别用红色、暗红、蓝色和绿色标记出来 主要代码 // Mark 主要代码示意 // 根据 HSI 距离判断像素属性 // Sobel 边缘检测 + 噪声去除 // 多色标记不同区域pasted_1784860785613_yfx4ad.png图片 pasted_1784860803522_x9441d.png图片 pasted_1784860810461_y7dtuy.png图片 图6 Mark 主要代码 / 图7 标记后情况3.4 OnTwoValue() - 二值化 代码分析 背景灰度值为 128,细胞灰度值为 240,边缘为 255。二值化便于轮廓提取。 均值滤波的基本原理是用均值代替原图像中的各个像素值,即对待处理的当前像素点 (x, y),选择一个模板,该模板由其近邻的若干像素组成,求模板中所有像素的均值,再把该均值赋予当前像素点 (x, y),作为处理后图像在该点上的灰度 g(x, y),即 g(x, y) = 1/m ∑f(x, y);m 为该模板中包含当前像素在内的像素总个数。 状态标记说明 标记值含义0x7Xedge 边界(表示边界没 Mark,存在为四边)0x8XMark -- not edge(是细胞)0xfXMark -- edge0xX1visited(已访问)0x2CENTERED(中心点) m_vCenterPoints.at(j) —— 存储最后中心点的地方 主要代码 // 二值化主要代码示意 // 灰度阈值分割 + 均值滤波 // 状态标记管理pasted_1784860833275_8scd1m.png图片 pasted_1784860839706_6y6bwf.png图片 图8 二值化主要代码 / 图9 二值化现象3.5 OnFillHoles() - 填洞 代码分析 在阈值处理时,如果像素在阈值范围内,则像素将被标志。孔洞填充将先统计所有连通的非标志区域面积,总会有一个或者几个面积特别大的区域,其它的都是面积相对较小的区域。较小或者很小的往往就是系统所要填充的孔洞了。 填洞方法: 用栈(stack)、向量(vector)处理 从四个方向遍历,没访问过的黑点进栈,直到边和走完 之后读值到 xt, yt 并去值,v 放的是点的位置 没有碰到边界和小于 100,则填洞,即令洞的各点值为 Mark 值 将细胞中灰度值为 128 的部分的灰度值设置为 2404 填完细胞中的洞(即为 0 的值)后,处理非 Mark 的边界值(目的是打开边缘),设为 0。 主要代码 // 填洞主要代码示意 // stack + vector 获取孔洞坐标 // 连通区域面积统计 // 小区域填充为 Mark 值pasted_1784860863112_8iao9n.png图片 pasted_1784860868652_0m8z7m.png图片 图10 填洞主要代码 / 图11 填洞现象3.6 shrink() - 收缩 代码分析 收缩的目的是为了方便计数。扫描图像,对图像进行预先的 3 次腐蚀,判断所生成边界点,然后根据原理判定是否标注该点,存放所标志的中心点,便于统计细胞个数及计算细胞半径。 算法: 由 Mark 生成边界,我们有四邻域生成边界和八邻域生成边界 判断该点是否为 Mark 点,如果是 Mark 点的话,判断 i, j 是否是我们选取图片的边界,如果是的话,将该点变成边缘点 否则判断它的上下左右(周围八个点)是否有非 Mark 点,如果有,则将这边变成边缘点,反之不变 最后去掉边界则完成收缩 主要代码 // 收缩主要代码示意 // 3次腐蚀预处理 // 4/8邻域边界生成 // 边界点判定与标记pasted_1784860892612_9svgpz.png图片 图12 收缩主要代码及现象3.7 findcenter() - 中心点获取 代码分析 通过判断 MARK 点上下左右四个方向或者八个方向是否有非 Mark 点,如果有的话即认为是边界,将该点加上边界标志。 在收缩的过程(直到没有边界可去)中: 清除所有标志点 在不超过边下,没有被访问过的边缘,四个方向全是非 Mark 的话(收缩 k < 2 时)则是孤立点 k >= 2,是中心点,保存 x, y, 半径 还有除了边界就是背景,这也要保存(就是消失前保存) Mark(i, j): 八个方向,没有访问过的 Mark,且是边界点,则保存 SaveIT(i, j): 这点不是中心点(相当于没有访问过)则设为中心点(并保存);已经是中心点则去除标志,八方向寻找中心点 值得注意的是我们也要去除半径不大于 2 的孤立点,因为我们认为它的半径太小,是噪声。如果是半径大于 2 的孤立点,我们对他进行标记成中心点,对半径做一点补偿(pt.radius = k + pre_shrink_count + 4,4 为补偿),然后入队。合并中心点 本点是中心点,则八个方向看都不是中心点,则保存。有相连的话,多点求中心与半径(用到了 CalcCenterArea(i, j))。 CalcCenterArea(i, j): 八方向求中心点后去除标志,循环统计。半径取最大,而 x, y 则是取平均。 去除多余圆或者错误圆 相近圆: 圆心距离小于 10,则取半径小的中心点为中心点,取半径最大值 + 2 去除潜在的错误: 即半径小于 9 的去掉。但四个方向,边部补偿,防止边部的细胞被去除 多圆相交时,不相交部分小于则去除: 首先,相交部分先取出来保存 之后,单个圆内的交上了被标志(画一个方框然后统计之内的点数) 之后单个圆不相交的进行统计 不相交的占 50% 以下则清除本圆 主要代码 // 中心点获取主要代码示意 // 递归算法判别局部边缘 // 孤立点过滤与半径补偿 // 中心点合并与错误圆剔除pasted_1784860913700_o2gfhm.png图片 pasted_1784860921869_zuiuiy.png图片 图13 中心点获取主要代码及现象3.8 count() - 统计 代码分析 首先图像重新打开,然后获取细胞内部的 HSI 的最大值和最小值,并且计算细胞、平均半径、平均面积等值。 Count() 函数中首先调用了重载函数 reload(),将图片还原,随后将之前细胞识别所保留下来的信息(细胞个数),从 view.h 中提取信息,将细胞数、半径、面积显示在对话框中。 利用前面修正的结果,在 Count 模块进行总的计数,计算出平均半径和平均面积并输出 文件重新加载模块: 如果 noclick 置为 true,则进行重新加载图片 pasted_1784860943157_r86ld7.png图片 pasted_1784860947860_2i9gn5.png图片 pasted_1784860960332_53obi4.png图片 图14 重新加载图片 / 图15 最终标记现象 3.9 OnCellprgAll() - 按序执行全部 void OnCellprgAll() { // 依次调用前面各个函数 // 实现细胞识别的所有操作步骤 }pasted_1784860975641_sjtz6y.png图片 pasted_1784860983894_4m8nzp.png图片 图16 OnCellprgAll()代码分析: 不难看出调用了前面各个函数可以依次实现细胞识别的所有操作步骤。 四、错误情况及分析 4.1 未声明全局变量信息头 pasted_1784861010264_hf1vnq.png图片 图17 错误案例1原因: 缺少必要的头文件包含或全局变量声明 修正: 在适当位置添加全局变量声明 4.2 错把指针当作 int 型 pasted_1784861026701_fl51zf.png图片 图18 错误案例2修正: 应将 int m_pImage; 改为 BYTE* m_pImage; 4.3 软件菜单栏突然丢失 pasted_1784861045886_ob0u8q.png图片 图19 错误案例3原因: 误碰软件,把菜单移出去了 修正: 可以移动菜单栏使其恢复正常 pasted_1784861066297_b3vczn.png图片 图20 修正错误4.4 矩形框在鼠标未按下就出现了 原因: OnMouseMove 中判断条件不对 修正: 检查鼠标按下状态标志位的逻辑判断 五、总结 本项目基于 VS2019 MFC 平台,利用图像处理技术实现了细胞识别程序。主要技术路线包括: HSI 色彩空间转换 —— 分离亮度与色度,保持色彩无失真处理 Sobel 边缘检测 + 噪声过滤 —— 提取细胞边界 二值化与形态学处理 —— 填洞、腐蚀收缩,简化细胞轮廓 递归中心点检测 —— 通过迭代收缩获取细胞中心 后处理优化 —— 去除噪声点、合并相近圆、剔除错误检测 统计输出 —— 细胞个数、平均半径、平均面积 参考文献 1-2 ↩ 朱会平, 陈志远. 基于细胞显微图像的数量统计应用[J]. 实验室科学, 2011, 14(05): 73-75. ↩ 蔡朋杞. 红细胞识别系统的设计与实现[D]. 电子科技大学, 2011. ↩ 赵秋影. 人体细胞识别技术研究[D]. 长春理工大学, 2007. ↩
通信&信息处理
软硬件算法
# 信号处理
# 图像处理
# 通信&射频
刘航宇
7月24日
0
28
2
通讯等不确定性条件下设计无人机之间的安全距离
引用文章: 简介 1. 研究问题 2. 分离原理介绍 3. 安全半径设计 4. 仿真及实验验证 引用文章: Q. Quan, R. Fu and K. -Y. Cai, "How Far Two UAVs Should Be subject to Communication Uncertainties," in IEEE Transactions on Intelligent Transportation Systems, doi: 10.1109/TITS.2022.3213555. 简介 近年来,无人机技术的快速发展使得低空空域中无人机的数量爆炸增长。碰撞避免作为无人机应用的关键技术,近年来人们已经进行了大量研究,设计出各种方法使得无人机在飞行过程中与障碍物保持一定安全距离(本文将该距离称为无人机的 安全半径)。然而, 在碰撞避免相关研究中,不确定性往往是难以处理的问题。针对不确定性,主流的方法包括对不确定性进行预测,以及在闭环控制中给予补偿。然而,以上方法很依赖于预测及补偿的精准设计,设计不当将有可能导致控制器失效。本研究受地面交通中车辆间安全距离启发,基于对通信不确定性和无人机控制器性能的假设,提出了 安全半径设计和控制器设计的原则 (本文称为 分离原理 )。进一步,安全半径具体通常通过经验预先设计,如果设计得过大或过小将分别影响无人机飞行的高效性及安全性。 如何根据无人机模型及通信不确定性确定其安全半径下界仍然是悬而未决的问题 。利用分离原理,本文研究了 设计阶段(无不确定性)和飞行阶段(受不确定性影响)的无人机安全半径设计 。最后,通过仿真和实验表明了所提方法的有效性。 图片 1. 研究问题 本研究中,无人机与障碍物的运动模型均建模为多旋翼模型,换句话说,我们考虑的障碍物可以等效为另一架多旋翼飞行器。对于多旋翼飞行器而言,其可以获得自身以及障碍物的实时位置和速度。为简单描述起见,本文建模均为二维,类似的建模及分析方法可以扩展到三维情况。 我们首先建立多旋翼的具体控制模型如下。 其中多旋翼的位置和速度分别表示为 ,速度控制模型建立为一阶惯性环节,其时间常数 与无人机的机动性能相关,控制输入 为期望速度。在此基础上,本文定义一种新的滤波位置模型如下。 多旋翼滤波位置的物理意义是根据多旋翼的当前位置、速度及机动能力,对其运动趋势预测。在此基础上,定义多旋翼的安全区域为以滤波位置为圆心的圆形区域,其半径称为安全半径 。对于障碍物,我们类似定义其滤波位置,以及以障碍物滤波位置为圆心的圆形障碍物区域,半径称为障碍物半径 。 图片 进一步,我们针对不确定性做了以下2点假设:(1)无人机和障碍物对自身的三维位置及三维速度估计均存在噪声;(2)无人机在获取障碍物状态信息时,存在通信延迟及丢包。该通信网络模型如下图所示。 图片 本研究中,丢包模型进一步建模为均值模型,且假设估计噪声、通信延迟、丢包均值模型误差均存在上界且上界已知。其数学定义如下: 图片 进一步,本文对无人机的控制器性能做出如下假设:(1)在无不确定性的理想状态下,无人机的控制器可以使无人机和障碍物之间的 真实距离 始终大于给定安全距离;(2)在存在以上不确定性的实际情况中,无人机的控制器可以使无人机和障碍物之间的 估计距离 始终保持给定安全距离。本研究的具体目标是在不确定性上界已知的条件下计算出无人机安全半径的下界。 图片 2. 分离原理介绍 无人机在理想状态下设计的控制器中给定的安全半径值 ,在实际含有不确定性的环境中将转化为估计安全半径 ;直观上理解,将含有不确定性的无人机与障碍物之间的估计位置误差代替真实位置误差作为反馈时,控制器将难以维持给定的安全半径。本研究中, 分离原理具体研究的是不确定性在满足什么条件时无人机的控制器设计和安全半径设计过程可分离 ,也就是估计安全半径和安全半径相等, 成立。分离原理具体数学描述如下: 图片 分离原理中提出的三个条件包括一个充分必要条件(i)以及两个充分条件(ii)和(iii)。其中,条件(i)为理论推导,在实际中难以得到验证,通常可通过条件(ii)和(iii)进行验证。条件(ii)对无人机速度的限制较宽,该式在障碍物主动躲避无人机的条件下容易达成。反之,条件(iii)对无人机速度的限制较苛刻,要求无人机速度严格大于障碍物速度,适用于在障碍物不主动躲避无人机的情况下。以上两种情况的区别如下图所示。 图片 3. 安全半径设计 根据上述分离原理的提出,在分离原理满足的前提下,基于无人机的安全半径模型和通信网络模型,可以进一步设计 合适大小的无人机安全半径 。如前文所述,安全半径设计过大会增加环境的冗余度,设计过小则无法保证飞行过程的安全。在本研究中,给出了在理想情况下以及实际情况下安全半径的下界,分别如下: 图片 4. 仿真及实验验证 仿真及实验验证在仿真中,我们针对单障碍物、多非合作障碍物、多合作障碍物设计了三种场景来验证我们方法的有效性。在实验中,我们使用DJI tello无人机对以上情况进行测试,结果与我们的理论一致。实验视频: https://youtu.be/LkSDPFGa_1E 论文地址 https://rfly.buaa.edu.cn/pdfs/2022/How_far_two_UAVs_should_be_subject_to_communication_uncertainties.pdf
软硬件算法
# 软件算法
刘航宇
2年前
0
1,040
2
2023-12-11
华为C++算法-识别有效的IP地址和掩码并进行分类统计
问题 注意: 输入描述: 输出描述: 示例 需要注意的细节 思路 具体实现 代码 问题 请解析IP地址和对应的掩码,进行分类识别。要求按照A/B/C/D/E类地址归类,不合法的地址和掩码单独归类。 所有的IP地址划分为 A,B,C,D,E五类 A类地址从1.0.0.0到126.255.255.255; B类地址从128.0.0.0到191.255.255.255; C类地址从192.0.0.0到223.255.255.255; D类地址从224.0.0.0到239.255.255.255; E类地址从240.0.0.0到255.255.255.255 私网IP范围是: 从10.0.0.0到10.255.255.255 从172.16.0.0到172.31.255.255 从192.168.0.0到192.168.255.255 子网掩码为二进制下前面是连续的1,然后全是0。(例如:255.255.255.32就是一个非法的掩码) (注意二进制下全是1或者全是0均为非法子网掩码) 注意: 类似于【0...】和【127...】的IP地址不属于上述输入的任意一类,也不属于不合法ip地址,计数时请忽略 私有IP地址和A,B,C,D,E类地址是不冲突的 输入描述: 多行字符串。每行一个IP地址和掩码,用~隔开。 输出描述: 统计A、B、C、D、E、错误IP地址或错误掩码、私有IP的个数,之间以空格隔开。 示例 输入: 10.70.44.68~255.254.255.0 1.0.0.1~255.0.0.0 192.168.0.2~255.255.255.0 19..0.~255.255.255.0 输出: 1 0 1 0 0 2 1 说明: 10.70.44.68~255.254.255.0的子网掩码非法,19..0.~255.255.255.0的IP地址非法,所以错误IP地址或错误掩码的计数为2; 1.0.0.1~255.0.0.0是无误的A类地址; 192.168.0.2~255.255.255.0是无误的C类地址且是私有IP; 所以最终的结果为1 0 1 0 0 2 1 示例2 输入: 0.201.56.50~255.255.111.255 127.201.56.50~255.255.111.255 输出: 0 0 0 0 0 0 0 说明: 类似于【0...】和【127...】的IP地址不属于上述输入的任意一类,也不属于不合法ip地址,计数时请忽略 需要注意的细节 类似于【0...】和【127...】的IP地址不属于上述输入的任意一类,也不属于不合法ip地址,计数时可以忽略 私有IP地址和A,B,C,D,E类地址是不冲突的,也就是说需要同时+1 如果子网掩码是非法的,则不再需要查看IP地址 全零【0.0.0.0】或者全一【255.255.255.255】的子网掩码也是非法的 思路 按行读取输入,根据字符‘~’ 将IP地址与子网掩码分开 查看子网掩码是否合法。 合法,则继续检查IP地址 非法,则相应统计项+1,继续下一行的读入 查看IP地址是否合法 合法,查看IP地址属于哪一类,是否是私有ip地址;相应统计项+1 非法,相应统计项+1 具体实现 判断IP地址是否合法,如果满足下列条件之一即为非法地址 数字段数不为4 存在空段,即【192..1.0】这种 某个段的数字大于255 判断子网掩码是否合法,如果满足下列条件之一即为非法掩码 不是一个合格的IP地址 在二进制下,不满足前面连续是1,然后全是0 在二进制下,全为0或全为1 如何判断一个掩码地址是不是满足前面连续是1,然后全是0? 将掩码地址转换为32位无符号整型,假设这个数为b。如果此时b为0,则为非法掩码 将b按位取反后+1。如果此时b为1,则b原来是二进制全1,非法掩码 如果b和b-1做按位与运算后为0,则说明是合法掩码,否则为非法掩码 代码 注意getline函数可以指定分割字符串的字符 // 引入输入输出流、字符串、字符串流和向量等头文件 #include<iostream> #include<string> #include<sstream> #include<vector> // 使用标准命名空间 using namespace std; // 定义一个函数,判断一个字符串是否是合法的IP地址 bool judge_ip(string ip){ // 定义一个整数变量,记录IP地址的段数 int j = 0; // 定义一个字符串流对象,用于分割IP地址 istringstream iss(ip); // 定义一个字符串变量,用于存储IP地址的每一段 string seg; // 使用循环,以'.'为分隔符,获取IP地址的每一段 while(getline(iss,seg,'.')) // 如果段数加一大于4,或者该段为空,或者该段的数值大于255,说明不是合法的IP地址,返回false if(++j > 4 || seg.empty() || stoi(seg) > 255) return false; // 如果循环结束后,段数等于4,说明是合法的IP地址,返回true return j == 4; } // 定义一个函数,判断一个字符串是否是私有的IP地址 bool is_private(string ip){ // 定义一个字符串流对象,用于分割IP地址 istringstream iss(ip); // 定义一个字符串变量,用于存储IP地址的每一段 string seg; // 定义一个整数向量,用于存储IP地址的每一段的数值 vector<int> v; // 使用循环,以'.'为分隔符,获取IP地址的每一段,并将其转换为整数,存入向量中 while(getline(iss,seg,'.')) v.push_back(stoi(seg)); // 如果IP地址的第一段等于10,说明是私有的IP地址,返回true if(v[0] == 10) return true; // 如果IP地址的第一段等于172,并且第二段在16到31之间,说明是私有的IP地址,返回true if(v[0] == 172 && (v[1] >= 16 && v[1] <= 31)) return true; // 如果IP地址的第一段等于192,并且第二段等于168,说明是私有的IP地址,返回true if(v[0] == 192 && v[1] == 168) return true; // 如果以上条件都不满足,说明不是私有的IP地址,返回false return false; } // 定义一个函数,判断一个字符串是否是合法的子网掩码 bool is_mask(string ip){ // 定义一个字符串流对象,用于分割IP地址 istringstream iss(ip); // 定义一个字符串变量,用于存储IP地址的每一段 string seg; // 定义一个无符号整数变量,用于存储IP地址的二进制表示 unsigned b = 0; // 使用循环,以'.'为分隔符,获取IP地址的每一段,并将其转换为整数,左移8位后与b进行按位或运算,得到IP地址的二进制表示 while(getline(iss,seg,'.')) b = (b << 8) + stoi(seg); // 如果b等于0,说明不是合法的子网掩码,返回false if(!b) return false; // 将b按位取反后加一,得到b的补码 b = ~b + 1; // 如果b等于1,说明不是合法的子网掩码,返回false if(b == 1) return false; // 如果b与b减一进行按位与运算,结果等于0,说明b只有一个1,说明是合法的子网掩码,返回true if((b & (b-1)) == 0) return true; // 如果以上条件都不满足,说明不是合法的子网掩码,返回false return false; } // 定义主函数 int main(){ // 定义一个字符串变量,用于存储输入的IP地址和子网掩码 string input; // 定义七个整数变量,用于统计A、B、C、D、E类地址、错误地址和私有地址的个数 int a = 0,b = 0,c = 0,d = 0,e = 0,err = 0,p = 0; // 使用循环,读取输入的IP地址和子网掩码,直到输入结束 while(cin >> input){ // 定义一个字符串流对象,用于分割IP地址和子网掩码 istringstream is(input); // 定义一个字符串变量,用于存储IP地址或子网掩码 string add; // 定义一个字符串向量,用于存储IP地址和子网掩码 vector<string> v; // 使用循环,以'~'为分隔符,获取IP地址和子网掩码,并存入向量中 while(getline(is,add,'~')) v.push_back(add); // 如果IP地址或子网掩码不合法,错误地址的个数加一 if(!judge_ip(v[1]) || !is_mask(v[1])) err++; else{ // 如果IP地址不合法,错误地址的个数加一 if(!judge_ip(v[0])) err++; else{ // 获取IP地址的第一段的数值 int first = stoi(v[0].substr(0,v[0].find_first_of('.'))); // 如果IP地址是私有的,私有地址的个数加一 if(is_private(v[0])) p++; // 根据IP地址的第一段的数值,判断IP地址的类别,并相应的类别的个数加一 if(first > 0 && first <127) a++; else if(first > 127 && first <192) b++; else if(first > 191 && first <224) c++; else if(first > 223 && first <240) d++; else if(first > 239 && first <256) e++; } } } // 输出A、B、C、D、E类地址、错误地址和私有地址的个数 cout << a << " " << b << " " << c << " " << d << " " << e << " " << err << " " << p << endl; // 返回0,表示程序正常结束 return 0; }
嵌入式&系统
编程&脚本笔记
软硬件算法
# 嵌入式
# 笔试面试
# C/C++
刘航宇
3年前
0
585
1
算法-反转链表C&Python实现
描述 基础数据结构知识回顾 题解C++篇 题解Python篇 描述 给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。 数据范围: 0≤n≤1000 要求:空间复杂度 O(1) ,时间复杂度 O(n) 。 如当输入链表{1,2,3}时, 经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。 以上转换过程如下图所示: pCqibqO.png图片 基础数据结构知识回顾 空间复杂度 O (1) 表示算法执行所需要的临时空间不随着某个变量 n 的大小而变化,即此算法空间复杂度为一个常量,可表示为 O (1)。例如,下面的代码中,变量 i、j、m 所分配的空间都不随着 n 的变化而变化,因此它的空间复杂度是 O (1)。 int i = 1; int j = 2; ++i; j++; int m = i + j;时间复杂度 O (n) 表示算法执行的时间与 n 成正比,即此算法时间复杂度为线性阶,可表示为 O (n)。例如,下面的代码中,for 循环里面的代码会执行 n 遍,因此它消耗的时间是随着 n 的变化而变化的,因此这类代码都可以用 O (n) 来表示它的时间复杂度。 for (i=1; i<=n; ++i) { j = i; j++; }题解C++篇 可以先用一个vector将单链表的指针都存起来,然后再构造链表。 此方法简单易懂,代码好些。 // 定义一个Solution类 class Solution { public: // 定义一个函数,接收一个链表的头节点指针,返回一个反转后的链表的头节点指针 ListNode* ReverseList(ListNode* pHead) { // 如果头节点指针为空,直接返回空指针 if (!pHead) return nullptr; // 定义一个vector,用于存储链表中的每个节点指针 vector<ListNode*> v; // 遍历链表,将每个节点指针放入vector中 while (pHead) { v.push_back(pHead); pHead = pHead->next; } // 反转vector,也可以逆向遍历 reverse(v.begin(), v.end()); // 取出vector中的第一个元素,作为反转后的链表的头节点指针 ListNode *head = v[0]; // 定义一个当前节点指针,初始化为头节点指针 ListNode *cur = head; // 从第二个元素开始遍历vector,构造反转后的链表 for (int i=1; i<v.size(); ++i) { // 当前节点的下一个指针指向下一个节点 cur->next = v[i]; // 当前节点后移 cur = cur->next; } // 切记最后一个节点的下一个指针指向nullptr cur->next = nullptr; // 返回反转后的链表的头节点指针 return head; } };初始化:3个指针 1)pre指针指向已经反转好的链表的最后一个节点,最开始没有反转,所以指向nullptr 2)cur指针指向待反转链表的第一个节点,最开始第一个节点待反转,所以指向head 3)nex指针指向待反转链表的第二个节点,目的是保存链表,因为cur改变指向后,后面的链表则失效了,所以需要保存 接下来,循环执行以下三个操作 1)nex = cur->next, 保存作用 2)cur->next = pre 未反转链表的第一个节点的下个指针指向已反转链表的最后一个节点 3)pre = cur, cur = nex; 指针后移,操作下一个未反转链表的第一个节点 循环条件,当然是cur != nullptr 循环结束后,cur当然为nullptr,所以返回pre,即为反转后的头结点 这里以1->2->3->4->5 举例: pCqAcsP.png图片 pCqAgqf.png图片 pCqAWdS.png图片 pCqA4iQ.png图片 pCqAIRs.png图片 // 定义一个Solution类 class Solution { public: // 定义一个函数,接收一个链表的头节点指针,返回一个反转后的链表的头节点指针 ListNode* ReverseList(ListNode* pHead) { // 定义一个前驱节点指针,初始化为nullptr ListNode *pre = nullptr; // 定义一个当前节点指针,初始化为头节点指针 ListNode *cur = pHead; // 定义一个后继节点指针,初始化为nullptr ListNode *nex = nullptr; // 遍历链表,反转每个节点的指向 while (cur) { // 记录当前节点的下一个节点 nex = cur->next; // 将当前节点的下一个指针指向前驱节点 cur->next = pre; // 将前驱节点更新为当前节点 pre = cur; // 将当前节点更新为后继节点 cur = nex; } // 返回反转后的链表的头节点指针,即原链表的尾节点指针 return pre; } };题解Python篇 假设 链表为 1->2->3->4->null 空就是链表的尾 obj: 4->3->2->1->null 那么逻辑是 首先设定待反转链表的尾 pre = none head 代表一个动态的表头 逐步取下一次链表的值 然后利用temp保存 head.next 第一次迭代head为1 temp 为2 原始链表中是1->2 现在我们需要翻转 即 令head.next = pre 实现 1->none 但此时链表切断了 变成了 1->none 2->3->4 所以我们要移动指针,另pre = head 也就是pre从none 变成1 下一次即可完成2->1的链接 此外另head = next 也就是说 把指针移动到后面仍然链接的链表上 这样执行下一次循环 则实现 把2->3 转变为 2->1->none 然后再次迭代 直到最后一次 head 变成了none 而pre变成了4 则pre是新的链表的表头 完成翻转 # -*- coding:utf-8 -*- # 定义一个ListNode类,表示链表中的节点 # class ListNode: # def __init__(self, x): # self.val = x # 节点的值 # self.next = None # 节点的下一个指针 # 定义一个Solution类,用于解决问题 class Solution: # 定义一个函数,接收一个链表的头节点,返回一个反转后的链表的头节点 def ReverseList(self, pHead): # write code here pre = None # 定义一个前驱节点,初始化为None head = pHead # 定义一个当前节点,初始化为头节点 while head: # 遍历链表,反转每个节点的指向 temp = head.next # 记录当前节点的下一个节点 head.next = pre # 将当前节点的下一个指针指向前驱节点 pre = head # 将前驱节点更新为当前节点 head = temp # 将当前节点更新为下一个节点 return pre # 返回反转后的链表的头节点,即原链表的尾节点
编程&脚本笔记
软硬件算法
# 软件算法
# C/C++
# Python
刘航宇
3年前
0
382
2
FPGA&Matlab联合开发之滤波器模块(带通滤波器为例)
在通信或者信号处理中,数字滤波器是非常重要的模块,前面有关博文中提到FIR滤波器的一步步Verilog设计,如https://ee.ac.cn/index.php/archives/511.html 本文以带通滤波器为例,利用Matlab进行高效开发 目录 MATLAB生成低通滤波器设计步骤: Modelsim仿真上述文件 MATLAB生成低通滤波器设计步骤: (1)在MATLAB命令窗口中输入“filterDesigner”或“fdatool”出现如下对话框 image.png图片 设置FIR滤波器为和需要的阶数滤波器,选择窗函数的类型为海明窗函数,海明窗函数可以得到旁瓣更小的效果,能量更加集中在主瓣中 设置带通滤波器的上下截至频率分别为4MHz 和 5MHz (2)量化输入输出,点击工作栏左边的量化选项,即“set quantization parameters”选项,选择定点,设置输入字长为8,其他选择默认,如下图示: image.png图片 (3)根据自己需求,细化一些配置。这里不难探索 设置完成后,点击Targets中Generate HDL,选择生成Verilog 代码,设置路径,MATLAB即可生成设计好的滤波器Verilog HDL 代码以及测试文件: image.png图片 (4)根据需求,配置输出.v文件的全局信号、测试文件,点击生成,生成后,Matlab主页面会提示.v生成的文件路径 Modelsim仿真上述文件 image.png图片 可以看到输入信号在4MHZ~5MHZ备保留,设计无误。需要注意一点,一般Modelsim仿真输出波形都是离散的01信号,这里需要配置一下,在上图被选中的信号中,在左侧右键鼠标。 右击,format,analog(automatic); 右击,radix,decimal; 这两个步骤完成之后,就出现上图模拟信号的效果
嵌入式&系统
FPGA&ASIC
通信&信息处理
软硬件算法
# ASIC/FPGA
# 信号处理
# 硬件算法
刘航宇
3年前
0
688
2
2023-05-15
机器学习代码实现:线性回归与岭回归
1.线性回归模型 线性回归模型是最简单的一种线性模型,模型的形式就是: $y=W^T x+b$ 我们可以通过对原本的输入向量x扩增一个为1的维度将参数W和b统一成一个参数W,即模型变成了 $y=W^T x$ 这里的W是原本两个参数合并之后的而其损失函数的形式是残差平方损失RSS $L=\frac{1}{2 m} \sum_{i=1}^m\left(W^T x_i-y_i\right)^2=\frac{1}{2 m}\left(W^T X-y\right)^T\left(W^T X-y\right)$ 我们很容易就可以通过求导得到线性回归模型的关于W的梯度 $\nabla_W L=\frac{1}{m} \sum_{i=1}^m\left(W^T x_i-y_i\right) x_i=\frac{1}{m} X^T\left(W^T X-y\right)$ 这样一来我们就可以通过梯度下降的方式来训练参数W,可以用下面的公式表示 $W:=W-\alpha \frac{1}{m} X^T\left(W^T X-y\right)$ 但实际上线性模型的参数W可以直接求解出,即: $W=\left(X^T X\right)^{-1} X^T y$ 2.线性回归的编程实现 具体代码中的参数的形式可能和上面的公式推导略有区别,我们实现了一个LinearRegression的类,包含fit,predict和loss三个主要的方法,fit方法就是求解线性模型的过程,这里我们直接使用了正规方程来解 class LinearRegression: def fit(self, X: np.ndarray, y: np.ndarray) -> float: N, D = X.shape # 将每个样本的特征增加一个维度,用1表示,使得bias和weight可以一起计算 # 这里在输入的样本矩阵X末尾增加一列来给每个样本的特征向量增加一个维度 # 现在X变成了N*(D+1)维的矩阵了 expand_X = np.column_stack((X, np.ones((N, 1)))) self.w = np.matmul(np.matmul(np.linalg.inv(np.matmul(expand_X.T, expand_X)), expand_X.T), y) return self.loss(X, y)predict实际上就是将输入的矩阵X放到模型中进行计算得到对应的结果,loss给出了损失函数的计算方式: def loss(self, X: np.ndarray, y: np.ndarray): """ 线性回归模型使用的是RSS损失函数 :param X:需要预测的特征矩阵X,维度是N*D :param y:标签label :return: """ delta = y - self.predict(X) total_loss = np.sum(delta ** 2) / X.shape[0] return total_loss3.岭回归Ridge Regression与代码实现 岭回归实际上就是一种使用了正则项的线性回归模型,也就是在损失函数上加上了正则项来控制参数的规模,即: $L=\frac{1}{2 m} \sum_{i=1}^m\left(W^T x_i-y_i\right)^2+\lambda\|W\|_2=\frac{1}{2 m}\left(W^T X-y\right)^T\left(W^T X-y\right)+\lambda W^T W$ 因此最终的模型的正规方程就变成了: $W=\left(X^T X+\lambda I\right)^{-1} X^T y$ 这里的\lambda是待定的正则项参数,可以根据情况选定,岭回归模型训练的具体代码如下 class RidgeRegression: def fit(self, X: np.ndarray, y: np.ndarray): N, D = X.shape I = np.identity(D + 1) I[D][D] = 0 expand_X = np.column_stack((X, np.ones((N, 1)))) self.W = np.matmul(np.matmul(np.linalg.inv(np.matmul(expand_X.T, expand_X) + self.reg * I), expand_X.T), y) return self.loss(X, y)4.数据集实验 这里使用了随机生成的二位数据点来对线性模型进行测试,测试结果如下: image.png图片 线性模型测试结果 岭回归也使用同样的代码进行测试。
机器学习
软硬件算法
# 机器学习
# 软件算法
刘航宇
3年前
0
375
1
联发科2024年数字IC设计验证实习生考题解析
总体而言难度中等偏上,如有错误欢迎指正,考察感觉更像是考察嵌入式工程师(有STM32、FPGA基础就很轻松):有数电、模电、python、verilog、C语言、SOC系统等基础知识,可以看到其实很对口电子信息类专业如电子信息、微电子、通信工程、电子科学、集成电路等专业,没有考察模集如果考模集大部分人要G,数集也没考,可能太底层了与工业界需求有偏差,数集在笔试面试中我认为如果考,可能考时序与功耗部分。这里的Round-Robin算法很值得学习。 目录 1、(20分)逻辑化简: 2、(5分)ASIC flow 中综合工具的作用是什么?综合的时候需要SDC文件进行约束,请列举3条SDC的语法。 3、(10分)智力题 4、(10分)选择参与过的任一个项目,简述项目内容以及流程,讲述您在项目中承担的任务,挑一项你认为难的地方并阐述解决方案。 5、(5分)用python写一个冒泡排序的函数以及测试程序。 6、(15分)用Verilog 写一个 Round Robin 仲裁器。模块端口如下: 7、(15分)关于DMA寄存器配置,DMA寄存器(地址 0x81050010)表: 8、(20分)二阶带通滤波器,利用RC组件搭建,通带范围 1kHz~30kHz ,两个电阻 R 均为10kΩ ,问两个电容容值多少? 1、(20分)逻辑化简: 图片 (1)列出真值表 (2)列出其卡诺图 (3)写出Z的最简表达式 答:卡诺图:卡诺图画完后勾1就完事了 提示:约束项的一般形式为:与或式 = 0 (如果不是此种形式,化为此种形式);如此题的BC = 0;或者AB +CD = 0;ABC + CD = 0;等等。BC=0(即B=1,且C=1)对应的格子画X。 图片 图片 2、(5分)ASIC flow 中综合工具的作用是什么?综合的时候需要SDC文件进行约束,请列举3条SDC的语法。 答:ASIC flow 中综合工具的作用是将RTL级的硬件描述语言转换为与特定工艺库相匹配的门级网表,同时进行优化以满足时序、面积和功耗等约束。 综合的时候需要SDC文件进行约束,SDC文件是一种基于Tcl的格式,用于指定设计的时序约束34。SDC文件中的常用时序约束语法有: create_clock -name <clock_name> -period <clock_period> [get_ports <clock_port>] 用于创建时钟源并指定时钟周期。 set_input_delay -clock <clock_name> <delay_value> [get_ports <input_port>] 用于指定输入端口相对于时钟源的延迟。 set_output_delay -clock <clock_name> <delay_value> [get_ports <output_port>] 用于指定输出端口相对于时钟源的延迟。 set_clock_uncertainty -setup <setup_value> -hold <hold_value> <clock_name> 用于指定时钟源的不确定性,包括建立时间和保持时间。 set_false_path -from [get_ports <source_port>] -to [get_ports <destination_port>] 用于指定不需要进行时序分析的路径。 set_multicycle_path -setup -from [get_clocks <source_clock>] -to [get_clocks <destination_clock>] <cycle_number> 用于指定多周期路径,即源时钟和目标时钟之间有多个周期的时间差。3、(10分)智力题 (1)2 12 1112 3112 132112 ,下一个数?给理由; 答:第一个数是2,第二个数是12,表示前一个数有1个2;第三个数是1112,表示前一个数有1个1和1个2;以此类推。所以,下一个数是1113122112,表示前一个数有1个1,1个3,2个1和2个2 (2)有一个小偷费劲力气进入到了银行的金库里。在金库里他找到了一百个箱子,每一个箱子里都装满了金币。不过,只有一个箱子里装的是真的金币,剩下的99个箱子里都是假的。真假金币的外形和质感完全一样,任何人都无法通过肉眼分辨出来。它们只有一个区别:真金币每一个重量为101克,而假金币的重量是100克。在金库里有一个电子秤,它可以准确地测量出任何物品的重量,精确到克。但很不幸的是,这个电子秤和银行的报警系统相连接,只要被使用一次就会立刻失效。请问,小偷怎么做才能只使用一次电子秤就找到装着真金币的箱子呢? 答:小偷可以这样做:从第一个箱子里拿出1个金币,从第二个箱子里拿出2个金币,从第三个箱子里拿出3个金币,以此类推,直到从第一百个箱子里拿出100个金币。然后,把所有拿出来的金币放在电子秤上,测量它们的总重量。如果所有的金币都是假的,那么总重量应该是5050克(等于1+2+3+…+100)。如果有一个箱子里是真的金币,那么总重量会比5050克多出一些。这个多出来的部分就是真金币的数量乘以1克。例如,如果第十一个箱子里是真的金币,那么总重量会比5050克多出11克,因为从第十一个箱子里拿出了11个真金币。所以,小偷只要看电子秤上显示的数字减去5050,就能知道哪个箱子里是真的金币了。 4、(10分)选择参与过的任一个项目,简述项目内容以及流程,讲述您在项目中承担的任务,挑一项你认为难的地方并阐述解决方案。 答:优先答ASIC的设计与验证项目,其次是FPGA项目(如基于FPGA的图像处理、天线阵、雷达、加速器等等),其它项目不要答。 5、(5分)用python写一个冒泡排序的函数以及测试程序。 # 定义冒泡排序函数 def bubble_sort(lst): # 获取列表长度 n = len(lst) # 遍历列表n-1次 for i in range(n-1): # 设置一个标志,用于判断是否发生交换 swapped = False # 遍历未排序的部分 for j in range(n-1-i): # 如果前一个元素大于后一个元素,交换位置 if lst[j] > lst[j+1]: lst[j], lst[j+1] = lst[j+1], lst[j] # 标志设为True,表示发生了交换 swapped = True # 如果没有发生交换,说明列表已经有序,提前结束循环 if not swapped: break # 返回排序后的列表 return lst # 定义测试程序 # 创建一个乱序的列表 lst = [5, 3, 8, 2, 9, 1, 4, 7, 6] # 打印原始列表 print("Original list:", lst) # 调用冒泡排序函数,对列表进行排序 lst = bubble_sort(lst) # 打印排序后的列表 print("Sorted list:", lst)结果图 image.png图片 6、(15分)用Verilog 写一个 Round Robin 仲裁器。模块端口如下: input clock; input reset_b; input [N-1:0] request; input [N-1] lock; output [N-1] grant; //one-hot此处的 lock 输入信号,表示请求方收到了仲裁许可,在对应的lock拉低之前,仲裁器不可以开启新的仲裁。(可简单理解为仲裁器占用) 该题要求参数化编程,在模块例化时可调整参数。也即是说你不能写一个固定参数,比如N=8的模块。 参考波形图: image.png图片 答: Round-Robin算法:当有多个设备同时想占用同一个资源时,需要仲裁器通过某种调度算法决定不同设备使用资源的先后顺序。 Round Robin算法就是其中一种调度算法,其思路是,当多个仲裁请求(request)送给仲裁器时,仲裁器通过轮询的方式分时给不同的设备返回许可(grant),当一个requestor 得到了grant许可之后,它的优先级在接下来的仲裁中就变成了最低,当同时有多个requestor的时候,grant可以依次给到每个requestor,即使之前高优先级的requestor再次有新的request,也会等前面的requestor都grant之后再轮到它。由此看出,Round Robin算法是一种公平的算法,它避免了当最高优先级的requestor不断有新的request时,具有最高优先级的requestor一直占用资源,导致其他requestor无法占用资源的阻塞现象。 在verilog设计中,如何实现呢?假设request是位宽是6,最高位是第5位,最低位是第0位,默认低比特位具有高优先级。 1.首先需要找到request中优先级最高的比特位,对优先级最高的比特位给出许可信号。这一步可以通过request和它的2的补码按位与。这是因为一个数和它的补码相与,得到的结果是一个独热码,独热码为1的那一位是这个数最低的1。 2.在下一轮仲裁中,已经被仲裁许可的比特位变成了最低优先级,而未被仲裁许可的比特位将会被仲裁。因此对第一步中给出许可的比特位(假设是第2位)以及它的低比特位进行屏蔽,对request中的第5位到第3位进行保持,这个操作可以利用掩码111000和request相与实现得到。 得到掩码的方法是,对第一步的许可信号grant-1,再与grant本身相或,相或的结果再取反。 3.通过第二步得到第2位到第0位被屏蔽的request_new信号,判断request_new是否为全0信号,如果是全0信号,代表此时不存在需要被仲裁的比特位,则返回第一步:找到request中优先级最高的比特位,对优先级最高的比特位给出许可信号,然后进行第二步。如果request_new不是全0信号,代表存在未被仲裁的比特位,则找到request_new中优先级最高的比特位,对优先级最高的比特位给出许可信号,然后进行第二步。 // 功能: // -1- Round Robin 仲裁器 // -2- 仲裁请求个数N可变 // -3- 加入lock机制(类似握手) // -4- 复位时的最高优先级定为 0 ,次优先级:1 -> 2 …… -> N-2 -> N-1 `timescale 1ns / 1ps module RoundRobinArbiter #( parameter N = 4 //仲裁请求个数 )( input clock, input reset_b, input [N-1:0] request, input [N-1:0] lock, output reg [N-1:0] grant//one-hot ); // 模块内部参数 localparam IDLE = 3'b001;// 复位进入空闲状态,接收并处理系统的初次仲裁请求 localparam WAIT_REQ_GRANT = 3'b010;// 等待后续仲裁请求到来,并进行仲裁 localparam WAIT_LOCK = 3'b100;// 等待LOCK拉低 // 模块内部信号 reg [2:0] R_STATUS; //请求状态 reg [N-1:0] R_MASK; //掩码 wire [N-1:0] W_REQ_MASKED; assign W_REQ_MASKED = request & R_MASK; //屏蔽低位 always @ (posedge clock) begin if(~reset_b) begin R_STATUS <= IDLE; R_MASK <= 0; grant <= 0; end else begin case(R_STATUS) IDLE: begin if(|request) //首次仲裁请求,不全为0 begin R_STATUS <= WAIT_LOCK; //首先需要找到request中优先级最高的比特位,对优先级最高的比特位给出许可信号。 //这一步可以通过request和它的2的补码按位与。这是因为一个数和它的补码相与,得到的结果是一个独热码,独热码为1的那一位是这个数最低的1 grant <= request & ((~request)+1); R_MASK <= ~((request & ((~request)+1))-1 | (request & ((~request)+1))); //得到掩码的方法是,对第一步的许可信号grant-1,再与grant本身相或,相或的结果再取反。 end else begin R_STATUS <= IDLE; end end WAIT_REQ_GRANT://处理后续的仲裁请求 begin if(|request) begin R_STATUS <= WAIT_LOCK; //在下一轮仲裁中,已经被仲裁许可的比特位变成了最低优先级,而未被仲裁许可的比特位将会被仲裁。 //因此对第一步中给出许可的比特位(假设是第2位)以及它的低比特位进行屏蔽,对request中的第5位到第3位进行保持 //这个操作可以利用掩码111000和request相与实现得到。 if(|(request & R_MASK))//不全为零 begin grant <= W_REQ_MASKED & ((~W_REQ_MASKED)+1); R_MASK <= ~((W_REQ_MASKED & ((~W_REQ_MASKED)+1))-1 | (W_REQ_MASKED & ((~W_REQ_MASKED)+1))); end else begin grant <= request & ((~request)+1); R_MASK <= ~((request & ((~request)+1))-1 | (request & ((~request)+1))); end end else begin R_STATUS <= WAIT_REQ_GRANT; grant <= 0; R_MASK <= 0; end end //通过第二步得到第2位到第0位被屏蔽的request_new信号, //判断request_new是否为全0信号,如果是全0信号,代表此时不存在需要被仲裁的比特位,则返回第一步:找到request中优先级最高的比特位, //对优先级最高的比特位给出许可信号,然后进行第二步。如果request_new不是全0信号,代表存在未被仲裁的比特位, //则找到request_new中优先级最高的比特位,对优先级最高的比特位给出许可信号,然后进行第二步。 WAIT_LOCK: begin if(|(lock & grant)) //未释放仲裁器 begin R_STATUS <= WAIT_LOCK; end else if(|request) //释放的同时存在仲裁请求 begin R_STATUS <= WAIT_LOCK; if(|(request & R_MASK))//不全为零 begin grant <= W_REQ_MASKED & ((~W_REQ_MASKED)+1); R_MASK <= ~((W_REQ_MASKED & ((~W_REQ_MASKED)+1))-1 | (W_REQ_MASKED & ((~W_REQ_MASKED)+1))); end else begin grant <= request & ((~request)+1); R_MASK <= ~((request & ((~request)+1))-1 | (request & ((~request)+1))); end end else begin R_STATUS <= WAIT_REQ_GRANT; grant <= 0; R_MASK <= 0; end end default: begin R_STATUS <= IDLE; R_MASK <= 0; grant <= 0; end endcase end end endmodule测试代码 `timescale 1ns / 1ps module RoundRobinArbiter_tb; parameter N = 4; // 可以在测试时调整参数 // 定义测试信号 reg clock; reg reset_b; reg [N-1:0] request; reg [N-1:0] lock; wire [N-1:0] grant; // 定义时钟信号 initial clock = 0; always #10 clock = ~clock; // 实例化仲裁器模块 RoundRobinArbiter #( .N(N) ) inst_RoundRobinArbiter ( .clock (clock), .reset_b (reset_b), .request (request), .lock (lock), .grant (grant) ); // 定义时钟周期和初始值 initial begin reset_b <= 1'b0; request <= 0; lock <= 0; end // 定义请求和锁定信号的变化 initial begin #20; reset_b <= 1'b1; @(posedge clock) request <= 2; lock <= 2; @(posedge clock) request <= 0; @(posedge clock) request <= 5; lock <= 7; @(posedge clock) lock <= 5; @(posedge clock) request <= 1; @(posedge clock) lock <= 1; @(posedge clock) request <= 0; @(posedge clock) lock <= 0; #1000 $stop; // 测试结束 end // 显示测试结果和波形图 initial begin $monitor("Time=%t, clock=%b, reset_b=%b, request=%b, lock=%b, grant=%b", $time, clock, reset_b, request, lock, grant); $dumpfile("RoundRobinArbiter_tb.vcd"); $dumpvars(0,RoundRobinArbiter_tb); end endmodule结果: image.png图片 如果对波形图无法理解可以看此博文 https://blog.csdn.net/m0_49540263/article/details/114967443 7、(15分)关于DMA寄存器配置,DMA寄存器(地址 0x81050010)表: image.png图片 image.png图片 Type 表示读写类型。Reset 表示复位值。 写一个C函数 void dma_driver(void),按步骤完成以下需求: 分配DMA所需的源地址(0x30) 分配DMA所需的目的地址(0x300) 设置传输128 Byte 数据 开始DMA传输 等待DMA传输结束 答: // 假设有以下宏定义 #define DMA_REG 0x81050010 // DMA控制寄存器的地址 #define DMA_SRC_ADDR 0x30 // DMA源地址 #define DMA_DST_ADDR 0x300 // DMA目的地址 #define DMA_SIZE 128 // DMA传输大小 #define DMA_START 1 // DMA开始传输的标志位 // 定义C函数 void dma_driver(void) void dma_driver(void) { // 定义一个指向DMA控制寄存器的指针 volatile uint32_t *dma_reg = (volatile uint32_t *)DMA_REG; // 清空DMA控制寄存器的值 *dma_reg = 0; // 设置DMA源地址,目的地址和传输大小 *dma_reg |= (DMA_SRC_ADDR << 2) | (DMA_DST_ADDR << 13) | (DMA_SIZE << 24); // 开始DMA传输 *dma_reg |= DMA_START; // 等待DMA传输结束 while (*dma_reg & DMA_START) { // 可以在这里做一些其他的事情,比如打印日志或者检查错误 // printf("Waiting for DMA to finish...\n"); // check_error(); } }官方一点的表达:DMA,全称为:Direct Memory Access,即直接存储器访问。直接存储器存取( DMA )用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须 CPU 干预,数据可以通过 DMA 快速地移动,这就节省了 CPU 的资源来做其他操作。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA 传输对于高效能嵌入式系统算法和网络是很重要的。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。 8、(20分)二阶带通滤波器,利用RC组件搭建,通带范围 1kHz~30kHz ,两个电阻 R 均为10kΩ ,问两个电容容值多少? 答:第一步首得知道二阶带通(RC)滤波器的电路长啥样,高、低通组合一下就是带通,自己思考一下高、低通组合:如串联或并联,会得到带通还是带组? 电路图: H___H21L__E34_WC@43F1_8.jpg图片 这个一看就是总传递函数=A1*A2(模电二阶有源或无源滤波器绝对有) _LIYXIHR_08YNK__EV8SXDH.jpg图片 然后化简 X25LO__~TXMGO59LTLV@9S9.jpg图片 根据推导得到的表达式,对于 jwRC2 ,这一项,当 w 趋于无穷大时,uo/ui 趋于零。那么高频的临界点就是 wRC2 = 1+2C2/C1;(此时忽略低频项1/jwRC1) 同理,对于低频项 1 /jwRC1, w 趋于无穷小时,uo/ui 趋于零 ,那么低频的临界点就是 1/wRC1 = 1+2C2/C1;然后解二元一次方程两个电容就被解出来了 这里提供一种更简单方法: 二阶带通滤波器的中心频率 f0 和品质因数 Q 可以用下面的公式计算: image.png图片 已知 R1 = R2 = 10kΩ,f0 = (1kHz + 30kHz) / 2 = 15.5kHz,Q = f0 / (30kHz - 1kHz) = 0.54,代入上面的公式,可以求得: image.png图片 这是一个二元一次方程组,可以用任意方法求解,例如消元法或代入法。为了方便起见,我们假设 C1 和 C2 的值相近,那么可以近似地认为 C1 = C2 = 3.45nF。这样就得到了两个电容的容值。当然,也可以选择其他的电容值,只要满足上面的方程组即可。
FPGA&ASIC
软硬件算法
# 笔试面试
刘航宇
3年前
0
2,191
9
【硬件算法进阶】Verilog实现802.3 CRC-32校验运算电路
循环冗余校验(Cyclic Redundancy Check,CRC)是通信中常用的差错检测编码方式,其基本工作原理是根据输入的信息位(信息码元),按照给定的生成多项式产生校验位(校验码元),并一起传送到接收端。在接收端,接收电路按照相同的规则对接收数据进行计算并生成本地的校验位,然后与收到的校验位进行对比,如果二者不同,则说明传输过程中发生了错误,否则说明传输是正确的。带有CRC校验结果的数据帧结构如表1-2所示。 image.png图片 CRC检验位生成与检测工作包括以下基本步骤。 image.png图片 图1-6是一个并行CRC-32校验运算电路。图中的d[7:0]是输入的用户数据,它是按照字节的方式输入的。load_ini是在对一个新的数据包开始校验计算之前对电路进行初始化的控制信号,经过初始化后,电路内部32比特寄存器的值改变为全1。calc是电路运算指示信号,在整个数据帧输入和CRC校验结果输出的过程中其都应该保持有效(高电平有效)。d_valid为1时表示当前输入的是需要进行校验运算的有效数据。crc[7:0]是电路输出的CRC校验运算结果,它是按照字节方式,在有效数据输入完成后开始输出的,一共有4个有效字节。crc_reg[31:0]是内部寄存器的值,具体使用时不需要该输出。 image.png图片 并行计算的思想,输入数据S要并行输入到G(x)系数为1的支路中,输入数据从输入端按高到低逐bit输入,就可以实现。 假如被除数是2位的数据S[1:0]=01,多项式是10011,x4 +x+1。在CRC校验里面,习惯省略最高位的1,多项式用0011表示。那么S除以0011的模二运算数字电路结构为: image.png图片 其中d1~ d4是寄存器输入;q1~q4是寄存器输出。寄存器需要赋初值,一般赋全1或全0。 d1=S[1]^q4; d2= S[1]^ q1^q4; d3=q2; d4=q3。 经过一次移位后: q1=d1= S[1]^q4; q2= d2= S[1]^ q1^q4; q3= d3=q2; q4= d4=q3。 此时有: d1=S[0]^q3; d2= S[0]^ S[1]^ q4^q3; d3= S[1]^ q1^q4; d4= q2。 令c[3:0]={q4,q3,q2,q1},d[3:0]={d4,d3,d2,d1},那么d就是最终的运算结果表达式,如下 d[3]=c[1]; d[2]= S[1]^ c[0]^c[3]; d[1]= S[0]^ S[1]^ c[3]^ c[2]; d[0]= S[0]^ c[2]。 令c的初值为0,则01对0011的模二除法的余数为0011。 再比如多项式为x5 +x3 +x+1,简记式为01011,其数字电路结构为: image.png图片 输入数据S要全部输入完,寄存器得到的结果才是最后的结果。同理可推导出其他多项式和输入数据的情况。 对于循环检验,这里举个例子,如果数据是10bit*100个包,则每次输入10bit得到校验码后,该检验码为下次数据计算时寄存器D的初值,如此反复计算得到最后的检验码添加到整个数据后面即可,而不需要每个数据包后面都添加检验码。 下面是以太网循环冗余校验电路的设计代码: module crc32_8023( clk, reset, d, load_init, calc, d_valid, crc_reg, crc ); input clk; input reset; input [7:0] d; input load_init; input calc; input d_valid; output reg [31:0] crc_reg; output reg [7:0] crc; wire [2:0] ctl; wire [31:0] next_crc; wire [31:0] i; assign i = crc_reg; assign ctl = {load_init,calc,d_valid}; always @(posedge clk or posedge reset) begin if(reset) crc_reg <= 32'hffffffff; else begin case (ctl) //{load_init,calc,d_vaild} 3'b000,3'b010: begin crc_reg <= crc_reg; crc <= crc;end 3'b001: begin crc_reg <= {crc_reg[23:0],8'hff}; crc <= ~{crc_reg[16],crc_reg[17],crc_reg[18],crc_reg[19],crc_reg[20],crc_reg[21],crc_reg[22],crc_reg[23]}; //crc <= ~ crc_reg[16:23]; end 3'b011: begin crc_reg <= next_crc[31:0]; crc <= ~{next_crc[24],next_crc[25],next_crc[26],next_crc[27],next_crc[28],next_crc[29],next_crc[30],next_crc[31]}; //crc <= ~ next_crc[24:31]; end 3'b100,3'b110: begin crc_reg <= 32'hffffffff; crc <= crc; end 3'b101: begin crc_reg <= 32'hffffffff; crc <= ~{crc_reg[16],crc_reg[17],crc_reg[18],crc_reg[19],crc_reg[20],crc_reg[21],crc_reg[22],crc_reg[23]}; //crc <= ~ crc_reg[16:23]; end 3'b111: begin crc_reg <= 32'hffffffff; crc <= ~{next_crc[24],next_crc[25],next_crc[26],next_crc[27],next_crc[28],next_crc[29],next_crc[30],next_crc[31]}; //crc <= ~ next_crc[24:31]; end endcase end end assign next_crc[0] = d[7]^i[24]^d[1]^i[30]; //d+i=31 assign next_crc[1] = d[6]^d[0]^d[7]^d[1]^i[24]^i[25]^i[30]^i[31]; assign next_crc[2] = d[5]^d[6]^d[0]^d[7]^d[1]^i[24]^i[25]^i[26]^i[30]^i[31]; assign next_crc[3] = d[4]^d[5]^d[6]^d[0]^i[25]^i[26]^i[27]^i[31]; assign next_crc[4] = d[3]^d[4]^d[5]^d[7]^d[1]^i[24]^i[26]^i[27]^i[28]^i[30]; assign next_crc[5] = d[0]^d[1]^d[2]^d[3]^d[4]^d[6]^d[7]^i[24]^i[25]^i[27]^i[28]^i[29]^i[30]^i[31]; assign next_crc[6] = d[0]^d[1]^d[2]^d[3]^d[5]^d[6]^i[25]^i[26]^i[28]^i[29]^i[30]^i[31]; assign next_crc[7] = d[0]^d[2]^d[4]^d[5]^d[7]^i[24]^i[26]^i[27]^i[29]^i[31]; assign next_crc[8] = d[3]^d[4]^d[6]^d[7]^i[24]^i[25]^i[27]^i[28]^i[0]; //每项多出i[i],i=0、1、2...23 assign next_crc[9] = d[2]^d[3]^d[5]^d[6]^i[1]^i[25]^i[26]^i[28]^i[29]; assign next_crc[10] =d[2]^d[4]^d[5]^d[7]^i[2]^i[24]^i[26]^ i[27]^i[29]; assign next_crc[11] =i[3]^d[3]^i[28]^d[4]^i[27]^d[6]^i[25]^d[7]^i[24]; assign next_crc[12] =d[1]^d[2]^d[3]^d[5]^d[6]^d[7]^i[4]^i[24]^i[25]^i[26]^i[28]^i[29]^i[30]; assign next_crc[13] =d[0]^d[1]^d[2]^d[4]^d[5]^d[6]^i[5]^i[25]^i[26]^i[27]^i[29]^i[30]^i[31]; assign next_crc[14] =d[0]^d[1]^d[3]^d[4]^d[5]^i[6]^i[26]^i[27]^i[28]^i[30]^i[31]; assign next_crc[15] =d[0]^d[2]^d[3]^d[4]^i[7]^i[27]^i[28]^i[29]^i[31]; assign next_crc[16] =d[2]^d[3]^d[7]^i[8]^i[24]^i[28]^i[29]; assign next_crc[17] =d[1]^d[2]^d[6]^i[9]^i[25]^i[29]^i[30]; assign next_crc[18] =d[0]^d[1]^d[5]^i[10]^i[26]^i[30]^i[31]; assign next_crc[19] =d[0]^d[4]^i[11]^i[27]^i[31]; assign next_crc[20] =d[3]^i[12]^i[28]; assign next_crc[21] =d[2]^i[13]^i[29]; assign next_crc[22] =d[7]^i[14]^i[24]; assign next_crc[23] =d[1]^d[6]^d[7]^i[15]^i[24]^i[25]^i[30]; assign next_crc[24] =d[0]^d[5]^d[6]^i[16]^i[25]^i[26]^i[31]; assign next_crc[25] =d[4]^d[5]^i[17]^i[26]^i[27]; assign next_crc[26] =d[1]^d[3]^d[4]^d[7]^i[18]^i[28]^i[27]^i[24]^i[30]; assign next_crc[27] =d[0]^d[2]^d[3]^d[6]^i[19]^i[29]^i[28]^i[25]^i[31]; assign next_crc[28] =d[1]^d[2]^d[5]^i[20]^i[30]^i[29]^i[26]; assign next_crc[29] =d[0]^d[1]^d[4]^i[21]^i[31]^i[30]^i[27]; assign next_crc[30] =d[0]^d[3]^i[22]^i[31]^i[28]; assign next_crc[31] =d[2]^i[23]^i[29]; endmodule测试代码 `timescale 1ns/1ns module crc_test(); reg clk, reset; reg [7:0] d; reg load_init; reg calc; reg data_valid; wire [31:0] crc_reg; wire [7:0] crc; initial begin clk=0; reset=0; load_init=0; calc=0; data_valid=0; d=0; end always begin #10 clk=1; #10 clk=0; end always begin crc_reset; crc_cal; end task crc_reset; begin reset=1; repeat(2)@(posedge clk); #5; reset=0; repeat(2)@(posedge clk); end endtask task crc_cal; begin repeat(5) @ (posedge clk); //通过losd_init=1 对CRC计算电路进行初始化 #5; load_init= 1; repeat(1)@ (posedge clk); //设置1oad_init=0,data_valid= 1,calc=1 //开始对输人数据进行CRC校验运算 #5; load_init= 0; data_valid=1; calc=1; d=8'haa; repeat(1)@ (posedge clk); #5; data_valid=1; calc=1; d=8'hbb; repeat(1)@ (posedge clk); #5; data_valid=1; calc=1; d=8'hcc; repeat(1)@ (posedge clk); #5; data_valid=1; calc=1; d=8'hdd; repeat(1)@ (posedge clk); //设置load_init=0,data_valid=1,calc=0 //停止对数据进行CRC校验运算,开始输出 //计算结果 #5; data_valid=1; calc=0; d=8'haa; repeat(1)@ (posedge clk); #5; data_valid=1; calc=0; d=8'hbb; repeat(1)@ (posedge clk); #5; data_valid=1; calc=0; d=8'hee; repeat(1)@ (posedge clk); #5; data_valid=1; calc=0; d=8'hdd; repeat(1)@ (posedge clk); #5; data_valid=0; repeat(10)@ (posedge clk); end endtask crc32_8023 my_crc_test(.clk(clk),.reset(reset),.d(d),.load_init(load_init),.calc(calc),.d_valid(data_valid),.crc_reg(crc_reg),.crc(crc)); endmodule图1-7是电路的仿真结果。图中①是电路进行CRC校验计算之前对电路进行初始化操作的过程,经过初始化之后,crc_reg内部数值为全1。②是对输入数据aa-> bb-> cc-> dd进行运算操作的过程,此时calc和data_valid均为1。③是输出计算结果的过程,CRC校验运算结果a7、01、b4和55先后被输出。 图片 图片 在接收方向上,可以采用相同的电路进行校验检查,判断是否在传输过程中发生了差错。具体工作时,可以边接收用户数据边进行校验运算,当一个完整的MAC帧接收完成后(此时接收数据帧中的校验结果也参加了校验运算),如果当前校验电路的crc_reg值为0xC704DD7B(对于以太网中使用的CRC-32校验,无论原始数据是什么,正确接收时校验和都是此固定数值),说明没有发生错误,否则说明MAC帧有错。 CRC-32校验值的作用是用于检测数据传输或存储中的错误。发送数据时,会根据数据内容生成简短的校验和,并将其与数据一起发送。接收数据时,将再次生成校验和并将其与发送的校验和进行比较。如果两者相等,则没有数据损坏。如果两者不相等,则说明数据在传输或存储过程中发生了改变,可能是由于噪声、干扰、故障或恶意篡改等原因造成的。 CRC-32校验值可以有效地检测出数据中的随机错误,但是不能保证检测出所有的错误。例如,如果数据中有偶数个比特发生了翻转,那么CRC-32校验值可能不会改变,从而无法发现错误。因此,CRC-32校验值只能作为一种辅助的错误检测手段,不能完全依赖它来保证数据的正确性和完整性。 相关工具 如果不理解推导过程的话,可以由相关工具帮忙计算出结果和得到Verilog代码: CRC校验Verilog代码生成链接:http://outputlogic.com/?page_id=321 CRC校验计算工具链接:http://www.ip33.com/crc.html,这个工具只能计算16bit为一个数据包的数据,如果数据包为10bit等之类的就不太适用 在线计算器使用举例 报文 : 1011001 (0x59) 生成多项式 : g(x) = x^4 + x^3 + 1 CRC : 1010 ( 0xa) CRC计算结果截图: image.png图片 参考文献 Verilog HDL算法与电路设计-乔庐峰
FPGA&ASIC
软硬件算法
# ASIC/FPGA
# 硬件算法
刘航宇
3年前
0
2,315
4
2023-04-06
【硬件算法】Verilog之FPGA实现信号移相法
实现信号移相可以用FPGA控制信号0-360度连续可调,对于高频信号(1GHZ以上)超出了FPGA工作频率还有一种办法是FPGA-》DA-》射频前端-》移相器-》阻抗匹配-》天线。本案例直接采用FPGA对数字中频信号处理(kHZ、MHZ) 本质是边沿检测与分频 现象: 图片 代码: /* Function : Phase Shift Interface : clk_fre---unit(MHZ) din_fre---unit(KHz) phase_angle---unit(0-360 Angle) Date: 2023/04/05 Description: Phase shift is carried out on the input square wave. The phase Angle unit is Angle (0-360), or the input can be greater than 360. The system clock unit is MHz and the input signal clock is KHz. */ module PhaseShift( input clk, //clk input rst_n, //rest input [7:0] clk_fre, //system clock frequency,MHZ input [15:0] din_fre, //input signal clock frequency,KHZ input [8:0] phase_angle, //phase shift angle input din, //input signal output reg dout //output signal ); reg [31:0] posedge_counter; reg [31:0] negedge_counter; reg [31:0] delay_counter; reg in_posedge_flg; reg in_negedge_flg; reg out_posedge_flg; reg out_negedge_flg; reg old_din; reg init_data=1'b1; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin posedge_counter <= 1'b0; negedge_counter <= 1'b0; in_posedge_flg <= 1'b0; in_negedge_flg <= 1'b0; out_posedge_flg <= 1'b0; out_negedge_flg <= 1'b0; end else begin if(~old_din & din) in_posedge_flg = 1'b1; if(old_din & ~din) in_negedge_flg = 1'b1; old_din <= din; if(init_data) begin delay_counter <= ((1000000000)/din_fre*clk_fre)/360*(phase_angle%360)/1000000; dout <= din ; init_data <= 1'b0; end if(in_posedge_flg && posedge_counter <= delay_counter) begin posedge_counter <= posedge_counter + 1'b1 ; out_posedge_flg <= 1'b0; end else begin posedge_counter <= 1'b0 ; in_posedge_flg <= 1'b0 ; if(~out_posedge_flg) begin dout <= 1'b1 ; out_posedge_flg <= 1'b1 ; end end if (in_negedge_flg && negedge_counter <= delay_counter) begin negedge_counter <= negedge_counter + 1'b1 ; out_negedge_flg <= 1'b0 ; end else begin negedge_counter <= 1'b0 ; in_negedge_flg <= 1'b0 ; if(~out_negedge_flg) begin dout <= 1'b0 ; out_negedge_flg <= 1'b1; end end end end endmodule测试代码: `timescale 100ps/10ps // module test_PhaseShift; reg clk; reg rst_n; reg [7:0] clk_fre; //system clock frequency,MHZ reg [15:0] din_fre ; //input signal clock frequency,KHZ reg [8:0] phase_angle; //phase shift angle reg din ; //input signal wire dout ; //output signal initial begin clk = 0; din = 0; din_fre = 4000; phase_angle = 90; clk_fre = 100; rst_n = 1; #12 rst_n = 0; #4 rst_n = 1; #10000 $stop; //end end always #5 clk = ~clk; always #125 din = ~din; PhaseShift u1( .clk(clk), .rst_n(rst_n), .clk_fre(clk_fre), .din_fre(din_fre), .phase_angle(phase_angle), .din(din), .dout(dout) ); endmodule
FPGA&ASIC
软硬件算法
# ASIC/FPGA
# 硬件算法
刘航宇
3年前
0
1,624
3