一、简介
S12ZVML系列是NXP专门为电机控制的设计的一款mcu,常规的用法与st的芯片并没有什么差异。这里主要说的再电机控制中的一些用法。由于要求再70微妙内必须跑完一边FOC算法,所以在各个模块的使用上有所不同。
二、工程的建立
本人用的是官方提供的编译器coder warrior11.1编译器。在官网上基本找不到单一模块的demo,更多的是一套完整的电机代码。完全看不懂底层做了些什么。这里要说的是在电机工程中有两个文件一个是S12ZVM_devconfig.h和vector.c 这两个文件,主要是用来做中断注册的。在往自己建立的工程做移植的时候要在.prm文件里加入 vector INTO OSVECTORS; 如下图:
在移植那两个文件后,使得prm文件与上图保持一致就行(修改不同之处就行)有些地方下文会做说明。
三、IO口的使用
对于这款MCU的io的使用非常简单。比如根据芯片手册,想要使用PP0这个端口,就直接如下:
DDRP_DDRP0 = 1; // 使能PP0端口为输出 默认0为输入
PTP_PTP0 = 1; // 设置端口电平 1:高电平 0 : 低电平
这里要解释一下 , PP0 是手册上的描述, 应为Port P0。 设置的时候是 DDRx_DDRxn, PTx_PTxn, 解释为Port xn。
四、几个模块的关联使用
先上图:
这张图主要展示了几个模块间的配合使用,这就使nxp为电机控制所设计的。这里要解释是的是,在TIM分好时间后,在PMF(可以理解为pwm模块)的一个周期内,通过PTU去触发ADC采样,然后cpu去取AD值做运算。这样就可以在一个pmf中断内做完这些事情。
1、首先、先说一下pmf(带故障保护功能的脉宽调制器),
下面为一个简单的配置:注意的是重载设置,指的是每4个pwm周期进行一次reload。用于 ADC运算。
void PMF_init(void){
PMFCFG2_REV0 = 1; // 01 PWM generator A generates reload event.
PMFCFG2 |= 0x3F;
PMFMODA = PWM_MODULO; // 2775 18 kHz
PMFDTMA = PWM_DEADTIME; // 20 0.25 us dead time
PMFENCA_LDOKA = 1; // apply PMF Modulo value
PMFENCA_PWMENA = 1;
PMFENCA_PWMRIEA = 1; // 开启中断
PMFENCA_GLDOKA = 1; // 0 = Local LDOKA controls buffered registers / 1 = external
Load OK controls buffered registers
PMFFQCA_LDFQA = 3; // Reload every 4 (3+1) PWM, fcore / 1
}
2、ADC设置
这个MCU是基于列表的架构(LBA)提供灵活的转换序列定义以及灵活的过采样。两个ADC 转换命令列表均存储在全局存储器映像内;即以二维字节数组的形式(ADC0CommandList[][], ADC1CommandList[][])存储在系统存储器内。 ADC转换命令在系统存储器内的准确位置由链接 器命令文件指定,并在初始化阶段链接到对应的ADC模块。 ADC结果也采用了相同的策略。转 换结果存储在系统存储器内的short型数组中(ADC0ResultList[], ADC1ResultList[])。代码设置如下:
/****************************************
* ADC0
* ****************************************/
ADC0CTL_0_ACC_CFG = 3; // Dual access mode
ADC0CTL_0_STR_SEQA = 1; // Store result at abort/restart
ADC0TIM = 0; // clock: clk = fbus /
(2x(reg.value + 1)) [0.25 - 8MHz]
ADC0FMT_DJM = 1; // right justified result data
ADC0FMT_SRES = 4; // 12-bit result
ADC0CONIE_1_EOL_IE = 1; // End of list interrupt enable
ADC0CBP = ADC0CommandList;
ADC0RBP = ADC0ResultList;
ADC0CROFF1 = 0;
ADC0CTL_0_ADC_EN = 1; // enable ADC0
//ADC0EIE = 0xEE;
上面主要是ADC的简单的配置,主要关注的还是ADC0CommandList,ADC0ResultList这两个列表。还有就是右对齐,在其他demo中都是左对齐,有于是12位的ADC 所以右移4位就是右对齐的值。
接下来再看一段代码:
PR_SECTION(adcLists)
volatile char ADC0CommandList[4][4]= {
{0x40,0xD0,0x00,0x00}, // current sense channel, end of sequence [D0]
{0xC0,0xCB,0x00,0x00}, // end of list + no int [C0], HD voltage [CB], 4 clock cycles
sample time [00], reserved [00]
{0x00,0x00,0x00,0x00}, // end of list + no int [C0], ADC temperature senser [C9], 4
clock cycles sample time [00], reserved [00]
{0x00,0x00,0x00,0x00}, // end of list + no int [C0], AIN3 [D3], 4 clock cycles sample
time [00], reserved [00]
};
volatile char ADC1CommandList[1][4]={
{0xC0,0xD3,0x00,0x00}, //temperature sense channel, end of sequence [D3]
};
volatile unsigned short ADC0ResultList[4] = {0, 0, 0, 0};
volatile unsigned short ADC1ResultList[1] = {0};
PR_SECTION(DEFAULT_SEC)
上述代码中,ADC0CommandList存放的ADC转换的命令,二维数组的每一个一维数组代编一个完整的命令。这里有4个一维数组代表4个命令,每个命令代表不同的通道。ADC0ResultList中存放的就是4个通道所转换出来的值。下图表明了每条命令的含义:
上述代码段中,一维数组中第一个值 C0代表已到列表末,下面即使有命令也不再继续执行。40指的是队列的末尾,下面的命令依旧执行。第二个值代表的是通道几,第三个值指的是执行一次转换的时间。第四个值保留。这些通道所采样的值会自动保存到ADC0ResultList中。
3、PTU 设置
可编程触发器装置(PTU)的目标是在控制周期内对于与时序有关的状态变量的采集时,可以完全 避免CPU的干预。 PTU模块包含2个触发生成器(TG)。每个TG分别有自己的使能位,因此两个TG可以独立使能。 TG0连接到ADC0,TG1连接到ADC1。PTU模块的触发的产生与到来的重载事件同步。此重载事 件将重置并重启内部时基计数器,并确保载入实际触发器列表中的首个触发值。此外,对应的 ADC将被告知新控制周期已开始。
这个模块的设置,直接决定了在一个pmf周期内,ADC何时采样,采样间隔。看下面代码:
void PTU_init(void){
PTUPTR = ptuTriggerEventList0;
TG0L1IDX = (unsigned char)(((long)&ptuTriggerEventList0[0][0] -
(long)ptuTriggerEventList0) >> 1); // same as TG0L0IDX
TG1L0IDX = (unsigned char)(((long)&ptuTriggerEventList1[0][0] -
(long)ptuTriggerEventList1) >> 1);
PTUE_TG0EN = 1; // Enable
Trigger Generation 0
PTUE_TG1EN = 1; // Enable
Trigger Generation 1
PTUC_PTULDOK = 1; // Switch list
when next reload event
return;
}
上面的代码,就是PTU简单的配置.。
PR_SECTION(ptuTrigE)
volatile short ptuTriggerEventList0[2][4] = {
{0x0020,0x00F4,0x0000,0x0000},
{0x0000,0x0000,0x0000,0x0000} // !_! for 50 MHz bus clock
};
volatile short ptuTriggerEventList1[1][4] = {
{0x0100,0x0000,0x0000,0x0000} // !_! for 50 MHz bus clock
};
PR_SECTION(DEFAULT_SEC)
这一段代码就定义了每个ADC命令在pmf周期内何时开始执行,当然时间的设定要确保上一个命令可被执行完。虽然是二维数组,但是每一个值都对应了adc中的每一个通道。特别要说明的是,ptuTriggerEventList0 是作用域ADC0的,ptuTriggerEventList1是作用于ADC1的。
相信也都看到了PR_SECTION()和PR_SECTION(DEFAULT_SEC) 就是为了将这几个列表放入ROM中从而不受CPU影响。要在prm文件中设置,详情请看第二节内容。
到这里,几个某块的配合使用就基本完成了。一个pmf reload就可以完成这么多的事情。
五、总结
以上内容就是我在用这款MCU所遇到的困惑的地方。当然具体的要去看芯片手册。上面的代码只是一个DEMO,实际操作会遇到各种问题,这就看你自己解决问题的能力了(当然你也可以用常规的方式去设置与ST的一样)。这款MCU还有一个折磨人的模块就是GDU,当然这只在电机控制中才用到,这里就不再赘述了。