- 2024-09-29
-
回复了主题帖:
昊芯HXS320F28034数字信号处理器项目中电机相位控制与检测调试
后续更新测量与FPGA监测方法
- 2024-09-11
-
发表了主题帖:
昊芯HXS320F28034数字信号处理器项目中电机相位控制与检测调试
本帖最后由 leekom 于 2024-9-11 16:29 编辑
昊芯HXS320F28034数字信号处理器DSP,对电机使用差分编码器,也称为增量式编码器,其工作原理是将位移转换成周期性的电信号,再将这
个电信号转变成计数脉冲,用脉冲的个数表示位移的大小。
其QEP测速的实现方式如下:
采用GPIO MUX定义引脚:其引脚对应在码盘的边缘上开有相等角度的缝隙(分为透明和不透明部分),在开缝码盘两边分别安装光源及光敏元
件。当码盘随工作轴一起转动时,每转过一个缝隙就产生一次光线的明暗变化,从而获取差分编码器正交脉冲QEPA与QEPB、索引QEPI信号;
通过QDECCTL[XCR]选择,上升沿、下降沿或双边沿捕获计数方式,得到时钟信号xCLK,变化的信号经过整形放大,可以得到一定幅值和功率的
电脉冲输出信号。脉冲数就等于转过的缝隙数。将该脉冲信号送到计数器中去进行计数,从测得的数码数就能知道码盘转过的角度;
经解码QDU逻辑处理,得到方向的高低电平信号iDIR;
通过QDECCTL[QSRC]选择引脚模式:正交、方向、增或减计数,读取方向QDIR、时钟QCLK信号;
通过QEPCTL[PCRM]选择,位置计数控制PCCU模式:索引位置事件、最大位置锁存、单位超时锁存;使能QEPCTL[QPEN],捕获N线光电编码器
输出双边沿脉冲,使位置计数器产生计数,从而计算电机转速。
其A\B\C对应差分编码器,通过检测码盘上的缝隙引起的光线明暗变化,将其转换为电脉冲信号,并通过计数器计算脉冲数量来测量位移大小。
同时,通过两套光电转换装置产生的相位差信号来判断旋转方向,常用方法如下:
(1)T法:一个脉冲周期fclk内,捕获脉冲计数,载入16位定时器QCTMR。单位事件发生时,更新载入QCPRDLAT,电机转速n=60*fclk/(4N*QCPRDLAT)。QCTMR上溢时,位置方向QDIR将变化,使捕获单元QEPSTS[COEF]发生错误标志,故仅适用于低速测量。
(2)M法:规定检测事件Tc内,捕获脉冲计数。单位事件发生时,锁存到32位QPOSILAT和QPOSSLAT,故电机转速n=60*QPOSSLAT/(4N*Tc),适用于高速测量。
由此设计QEP测速实例:EPWM配置1KHz PWM波,模拟1000线编码器旋转,EQEP采用M法与T法测量60rpm与6000rpm下旋转的转速,单位周期为 SysFreq*100,采用上升沿计数,捕获得到脉冲计数,实现转速测量,采用定时器中断实现三种不同功能切换,故硬件连接如下:GPIO0-GPIO20,GPIO1-GPIO21。基于以上分析,在CDK上开发QEP脉冲捕获输出,代码包括:EPWM与EQEP的GPIO引脚配置,EPWM 1KHz两路互差90°的PWM波输出配置,EQEP单位时间锁存计数捕获功能配置,定时器Timer0配置,计数捕获、M法与T法测速程序及其功能切换的定时器中断服务程序,主程序调用执行。
int main(void)
{
/*初始化系统控制*/
InitSysCtrl();
/*初始EPWM GPIO*/
InitEPwm1Gpio();
/*EQEP1引脚配置*/
InitEQep1Gpio();
/*初始化LED配置,用于指示实际转速是否到达给定转速*/
InitLED();
/*关中断*/
InitPieCtrl();
/*清中断*/
IER = 0x0000;
IFR = 0x0000;
/*初始化PIE中断向量表*/
InitPieVectTable();
/*定时器配置*/
Timer0_init();
EALLOW;
/*将timer0_ISR入口地址赋给TINT0,执行M法、T法测速切换中断服务程序*/
PieVectTable.TINT0 = &timer0_ISR;
EDIS;
/*电机转速初始化*/
MotorSpeed_init();
EALLOW;
/*禁用TBCLK同步,允许EPWM初始化配置*/
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0;
EDIS;
/*配置epwm1,用于模拟编码器旋转*/
EPWM1_Config();
EALLOW;
/*使能TBCLK同步,EPWM配置功能起作用*/
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1;
EDIS;
/*EQEP脉冲捕获配置*/
EQEP_pulseCap();
/*使能打开相应的CPU IER中断*/
IER |= M_INT1;
/*使能打开相应的PIE IER中断*/
PieCtrlRegs.PIEIER1.bit.INTx7 = 1;
/*使能打开全局中断*/
EINT;
while(1)
{}
return 0;
}
A\B\C三个相位如下图:
CDK上开发QEP测速程序,其编译结果为:
编译通过后,就可以开始调试了,其中M法测速调试结果如下:
T法测速调试结果如下:
所捕获的光电编码器QEP正交脉冲波形为
- 2024-08-28
-
发表了主题帖:
【中科昊芯HXS320F28025C】昊芯HXS320F28034数字信号处理器CAN调试
昊芯HXS320F28034数字信号处理器DSP,其CAN模块以Mailbox信箱MBOXn控制与传输数据,实现DSP之间的控制指令高效收发,可更有效助力于工程师实现高效的多DSP控制指令收发,广泛应用于电动汽车、风力发电、轨道交通、机器人等高复杂度控制领域。
本章节以HX2000系列芯片调试 USB_CAN通信收发实例讲解CAN网络通信功能。
HX2000系列CAN模块原理如下图,通过CANMC[CCR]使能进入初始化配置模式,等待CANMC[CCE]置高写入CANBTC位配置波特率;等待CANMC[CCE]拉低,完成初始化;
int main(void)
{
InitSysCtrl();//系统时钟初始化
CAN_Init();//CAN的初始化参数配置
InitECanaGpio();//CAN的Gpio引脚配置
EALLOW;
ECanaRegs.CANMIM.bit.MIM0=1;//打开接收掩码,接收到数据触发中断
ECanaRegs.CANMIL.bit.MIL0 = 0; // 选择EcanA中断0
ECanaRegs.CANGIM.bit.I0EN = 1; // 使能中断0
PieVectTable.ECAN0INTA = &eCanRxIsr; // CANA 0接收中断入口
EDIS;
PieCtrlRegs.PIEIER9.bit.INTx5 = 1; // 使能ECAN1中断
IER |= M_INT9; // Enable CPU INT9
EINT;
while(1){
CAN_Tx();//发送数据
}
return 0;
}
其中,CAN的初始化参数配置代码为:
void CAN_Init()
{
volatile struct ECAN_REGS ECanaShadow;
EALLOW;
/* 配置RX与TX引脚*/
ECanaShadow.CANTIOC.all = P_ECanaRegs->CANTIOC.all;
ECanaShadow.CANTIOC.bit.TXFUNC = 1;
P_ECanaRegs->CANTIOC.all = ECanaShadow.CANTIOC.all;
ECanaShadow.CANRIOC.all = P_ECanaRegs->CANRIOC.all;
ECanaShadow.CANRIOC.bit.RXFUNC = 1;
P_ECanaRegs->CANRIOC.all = ECanaShadow.CANRIOC.all;
/* 清RMPn, GIFn 位 */
P_ECanaRegs->CANRMP.all = 0xFFFFFFFF;
/* 清中断标志 */
P_ECanaRegs->CANGIF0.all = 0xFFFFFFFF;
P_ECanaRegs->CANGIF1.all = 0xFFFFFFFF;
/* 初始化配置,写入CAN波特率*/
ECanaShadow.CANMC.all = P_ECanaRegs->CANMC.all;
ECanaShadow.CANMC.bit.CCR = 1 ; // Set CCR = 1
P_ECanaRegs->CANMC.all = ECanaShadow.CANMC.all;
ECanaShadow.CANES.all = P_ECanaRegs->CANES.all;
do
{
ECanaShadow.CANES.all = P_ECanaRegs->CANES.all;
} while(ECanaShadow.CANES.bit.CCE != 1 ); // Wait for CCE bit to be set..
//Bit rate=SYSCLKOUT/2/{(BRP+1)*[(TSEG1+1)+(TSEG2+1)+1,
//TSEG1≥3,TSEG2≥2
ECanaShadow.CANBTC.bit.BRPREG = 2;
ECanaShadow.CANBTC.bit.TSEG2REG = 4;
ECanaShadow.CANBTC.bit.TSEG1REG = 13;
P_ECanaRegs->CANBTC.all = ECanaShadow.CANBTC.all;
ECanaShadow.CANMC.all = P_ECanaRegs->CANMC.all;
ECanaShadow.CANMC.bit.CCR = 0 ; // Set CCR = 0
P_ECanaRegs->CANMC.all = ECanaShadow.CANMC.all;
ECanaShadow.CANES.all = P_ECanaRegs->CANES.all;
do
{
ECanaShadow.CANES.all = P_ECanaRegs->CANES.all;
} while(ECanaShadow.CANES.bit.CCE != 0 ); // Wait for CCE bit to be cleared..
/* 屏蔽所有信箱寄存器*/
// Required before writing the MSGIDs
P_ECanaRegs->CANME.all = 0;
/* 配置MBOX1的MSGID*/
P_ECanaMboxes->MBOX1.MSGID.all = 0x00040000;
P_ECanaMboxes->MBOX0.MSGID.all = 0x00040000;
/* 配置 MBOX1/MBOX0的传输字节为2 */
ECanaMboxes.MBOX1.MSGCTRL.bit.DLC = 0x2;
ECanaMboxes.MBOX0.MSGCTRL.bit.DLC = 0x2;
/* 配置 MBOX0 为接收,MBOX1为发送 */
ECanaRegs.CANMD.bit.MD0=1;
ECanaRegs.CANMD.bit.MD1=0;
/*使能 MBOX0 and MBOX1 */
ECanaRegs.CANME.bit.ME0=1;
ECanaRegs.CANME.bit.ME1=1;
//挂起接收邮箱,以触发接收中断
if(ECanaRegs.CANRMP.bit.RMP0==0)
{
ECanaRegs.CANRMP.bit.RMP0=1;
}
EDIS;
return;
}
通过Mailbox邮箱MBOXn.MSGCTRL的RTR位配置远程传输请求,TPL位配置MBOXn传输优先级,DLC位配置传输0~8个字节,传输过程如下:
1.CAN总线通过CAN传输芯片向CAN发送数据,使能CANRIOC[RXFUNC]打开接收线,接收来自GPIOMUX所定义CANRX接收引脚上的数据,装到缓冲区Receive Buffer;
2.通过CANMD[MDn]配置Mailbox邮箱MBOXn为接收,使能CANME[MEn]打开邮箱MBOXn,接收来自缓冲区中的数据;
全局中断标志的设置取决于CANGIM寄存器中GIL位的设置。如果设置了该位,则全局中断在CANGIF1寄存器中设置位;否则,在CANGIF0寄存器中设置。通过CANMIM[MIMn]配置接收掩码中断使能;通过CANMIL[MILn]配置,将MBOXn接收中断,映射到中断ECAN0INTA或ECAN1INTA;使能CANGIM[I0EN/I1EN]打开中断信号,MBOXn接收到数据时,将产生一个接收中断,使CANGIF0/1[MIV0/1]中相应接收邮箱MBOXn的邮箱号置位;通过PIE响应CPU执行接收中断程序:
EEWORLDIMGTK0
(1)置位CANRMP[RMPn]挂起MBOXn邮箱,以防止瞬间接收到多组数据;
(2)通过CANGIF0/1[MIV0/1],确认接收到数据邮箱为MBOXn,并读取接收的数据;
置位CANRMP[RMPn]清除邮箱MBOXn挂起状态,准备接收下一组数据。
CAN的发送程序代码为:
void CAN_Tx(void)
{
//等待MBOX0接收成功,读取消息
while(ECanaRegs.CANRMP.bit.RMP0!=1){}
ECanaMboxes.MBOX1.MDL.byte.BYTE0 = (receive_data&0xff);
ECanaMboxes.MBOX1.MDL.byte.BYTE1 = ((receive_data>>8)&0xff);
ECanaRegs.CANTRS.bit.TRS1 = 1;//发送MBOX1数据到MBOX0
} 作者:中科昊芯 https://www.bilibili.com/read/cv18742715/?spm_id_from=333.999.0.0 出处:bilibili
EEWORLDIMGTK1
4.通过CANMD[MDn]配置另一Mailbox邮箱MBOXn为发送,使能CANME[MEn]打开发送邮箱,使能CANTRS[TRSn]启动发送邮箱,发送数据到缓冲区 Transmit Buffer;
5.使能CANTIOC[TXFUNC]打开发送线,通过GPIOMUX所定义CANTX发送引脚将缓冲区Transmit Buffer数据发送到CAN总线。
- 2024-08-14
-
发表了主题帖:
HXX320中pwm的占空比设置
HXX320和STM32有不同,要配置可定义的PWM,其完成是内部分硬件方式。只需对相关的硬件设定,其DSP将协同处理。
首先,初始化完成后,确定相关的设定库。
TIM_TimeBaseStructure.TIM_Period设为TIMx的时钟,除非APB1的时钟分频数设置为1,否则通用定时器TIMx的时钟是APB1时钟的2倍,这时的TIMx时钟为72MHz,用这个TIMx时钟72MHz除以(PSC+1),得到定时器每隔多少秒涨一次,这里给PSC赋7199,计算得定时器每隔0.0001秒涨一次,即此时频率为10KHz,再把这个值乘以(ARR+1)得出PWM频率。
此例,假如ARR值为0,即0.0001*(0+1),则输出PWM频率为10KHz,再假如输出频率为100Hz的PWM,则将ARR寄存器设置为99即可。如果想调整PWM占空比精度,则只需降低PSC寄存器的值即可。设为100TIM_SetCompare2(20),就是20%占空比了。
相关代码下:
void TIM_Configuration(void)//TIM初始化函数
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//定时器初始化结构
TIM_OCInitTypeDef TIM_OCInitStructure;//通道输出初始化结构
//TIM1初始化
TIM_DeInit(TIM2);
TIM_TimeBaseStructure.TIM_Period = 719; //周期719
TIM_TimeBaseStructure.TIM_Prescaler = 999; //时钟分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //时钟分割点
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//PWM模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //基本初始化
// TIM_ITConfig(TIM2, TIM_IT_CC4, ENABLE); //打开中断,中断需要这行代码
//TIM1通道初始化
TIM_OCStructInit(& TIM_OCInitStructure); //默认参数
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //工作状态,;//配置为PWM1模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //设定为输出,需要PWM输出才需要这行代码
TIM_OCInitStructure.TIM_Pulse = 800; //占空长度
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //高电平
TIM_OC4Init(TIM2, &TIM_OCInitStructure); //使用P4脚
TIM_Cmd(TIM2, ENABLE); //启动TIM1
}
输出的信号周期为5ms,单个周期内高电平持续时间为0.7ms; 见图
- 2024-07-24
-
发表了主题帖:
【中科昊芯】国产RISCV+DSP开发之准备
最近申请到中科昊芯RISCV DSP测试样板,正好手头在做关于电机位置精准备控制相关项目。本想用RSIV片上系统与FPGA结合,做下高精控制,看来这颗片子可以一刀切。
先看看主要性能参数:
和STM相比,看到差别了,有一些增强型外设,这些也就是为了区分普通MCU方式,毕竟内嵌DSP,高速的高阶数学算法应该有极大提升。
相关资料网站 www.haawking.cn/280025
准备下资料,开始读文档了解,希望资料准确易懂。
- 2024-07-03
-
回复了主题帖:
测评入围名单: 中科昊芯HXS320F28025C,基于RISC-V的DSP
个人信息无误,确认可以完成测评分享计划
- 2024-05-29
-
回复了主题帖:
《Python编程快速上手》Python中 if-elif-else与switch-case的执行
-
发表了主题帖:
《Python编程快速上手》Python中 if-elif-else与switch-case的执行
在Python中,没有内置的switch-case语句,但你可以使用字典映射或者if-elif-else链来模拟switch-case的行为。
关于执行速度,通常来说,两者的性能差异非常小,不太可能成为程序执行的瓶颈。实际的性能差异更多地依赖于具体的使用场景、条件分支的数量、以及每个分支内的代码复杂度。
1、 IF-ELSE对于简单的条件判断,if-elif-else结构直观且易于理解。
Python解释器对这类基本结构做了优化,因此在大多数情况下性能是足够的。
value = 2
if value == 1:
print("Case 1")
elif value == 2:
print("Case 2")
elif value == 3:
print("Case 3")
else:
print("Default case")
2、 字典映射(模拟switch-case)使用字典映射可以提供一种更“Pythonic”的方式来处理多分支选择,这种方式在某些情况下可能更简洁,尤其是在处理大量分支时。
def case_1():
return "Case 1"
def case_2():
return "Case 2"
def case_3():
return "Case 3"
def default_case():
return "Default case"
switch = {
1: case_1,
2: case_2,
3: case_3,
}
value = 2
result = switch.get(value, default_case)()
print(result)
执行速度比较理论上,字典查找(模拟switch-case)的平均时间复杂度为O(1),而if-elif-else链的效率会随着条件数量的增加而稍微降低,尽管这种差异在实际应用中往往微乎其微,不足以成为决定使用哪种结构的主要因素。实际上,选择if-elif-else还是字典映射主要应基于代码的可读性、可维护性和具体需求。对于简单的逻辑和较少的分支,if-elif-else可能更直接;而对于复杂的多分支逻辑,尤其是当分支条件是固定值集合时,字典映射可能提供更清晰、更高效的实现方式。在绝大多数日常开发中,这两种方式的性能差异并不显著到需要特别考虑的程度。
-
回复了主题帖:
Python宏定义实现
-
发表了主题帖:
Python宏定义实现
虽然Python本身不支持C/C++风格的预处理器宏定义,但可以通过一些第三方库或技巧来模拟宏的功能,尤其是在处理一些元编程或者代码生成的场景。以下是一些方法,可以用来实现类似“宏”的效果:
1. 使用functools.wraps和装饰器
装饰器可以用于在不修改原有函数定义的情况下,给函数添加额外的功能,这在某种程度上类似于宏的功能。
from functools import wraps
def log_function_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling function: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_function_call
def example_function(x):
return x * x
print(example_function(5))
2. 使用元类(Metaclass)
元类允许你控制类的创建过程,从而在类定义时插入代码或修改行为,类似于在编译时期进行的宏替换或扩展。
class Meta(type):
def __new__(cls, name, bases, dct):
print(f"Creating class: {name}")
# 在这里可以修改dct来添加、删除或修改类的属性和方法
dct['created_at'] = 'now'
return super().__new__(cls, name, bases, dct)
class MyClass(metaclass=Meta):
pass
print(MyClass.created_at)
3. 通过字符串操作和exec模拟宏
虽然不推荐频繁使用,但在某些特定场景下,如动态生成代码,可以使用exec函数执行字符串形式的Python代码。
code = "PI = 3.14159"
exec(code)
print(PI)
通过以上三个方法可以灵活实现很多类似的需求。
- 2024-05-11
-
发表了日志:
Python位运算
-
发表了主题帖:
Python位运算
读到Python位运算这部分,将我理解与大家分享,Python中的位运算允许你在二进制层面操作数字,相较关系运算,理为接近某些底层编程、算法优化或是处理位级别数据时使用。
位运算符主要有以下几种:
按位与运算符 &
这个运算符对两个数字的每一位进行逻辑与操作。如果两个相应的位都是1,则结果位上就是1;否则为0。
a = 5 # 二进制表示为 0101
b = 3 # 二进制表示为 0011
c = a & b # 结果为 1,二进制表示为 0001
按位或运算符 |
对于两个数字的每一位,如果至少有一个位是1,那么结果位上就是1;如果都是0,则结果为0。
a = 5 # 二进制表示为 0101
b = 3 # 二进制表示为 0011
c = a | b # 结果为 7,二进制表示为 0111
按位异或运算符 ^
当两个位不同时,结果位为1;如果相同,则为0。
a = 5 # 二进制表示为 0101
b = 3 # 二进制表示为 0011
c = a ^ b # 结果为 6,二进制表示为 0110
按位取反运算符 ~
对数据的每个二进制位取反,即0变成1,1变成0。需要注意的是,这个运算符通常会得到一个负数,因为Python中整数是以补码形式存储的。
a = 5 # 二进制表示为 0101,取反后为 ...11111010,补码表示为 -6
b = ~a # 结果为 -6
左移运算符 <<
将数字的二进制表示向左移动指定位数,右边空出来的位用0填充。
a = 5 # 二进制表示为 0101
b = a << 2 # 结果为 20,二进制表示为 10100
右移运算符 >>
将数字的二进制表示向右移动指定位数,左边空出来的位根据原数字的符号位决定,正数补0,负数补1(算术右移)。
a = 20 # 二进制表示为 10100
b = a >> 2 # 结果为 5,二进制表示为 0101
在某些需要基于机器层面的数椐处理时用到,以上为部分总结与理解,不足之处欢迎大佬指点。
- 2024-04-16
-
回复了主题帖:
读书入围名单:《Python编程快速上手 让繁琐工作自动化 第2版》
个人信息无误,确认可以完成阅读分享计划。
-
回复了主题帖:
读书入围名单:《Python编程快速上手 让繁琐工作自动化 第2版》
幸运入围,希望学习有惊喜,期待ing......