关键词:磁编码器AS5040,、ARM通用定时器、SSI接口
本篇文稿主要是探讨一下用ARM上的通用定时器,也就是俗称的GPIO口,读取SSI串行信号的问题。
串行数据信号传输有很多协议,常见的包括RS232,RS485,SPI等,ARM板一般都会有USART串口、SPI接口,使用时直接可通过软件实现。但ARM芯片暂时还没有SSI接口,通过查阅资料,可设置SPI相应模式实现SSI信号数据的接收【详见正点原子编写的STM32F4开发指南-库函数版本_V1.1-第23章】。
当前接触到的旋转绝对编码器大多是SSI接口,碰到ARM板上的SPI接口被占用或未引出,着实是一件麻烦的事。但既然是串行数据传输,必然是可以通过ARM芯片上普通的GPIO口读取的。本次实验所用到的ARM芯片型号为STM32F415ZGT6,三斯12位绝对型旋转编码器HAE18U5V12A1(原理同磁编码器AS5040),本次实验根据板子的实际情况选用的GPIO口为PC6、PC7、PE5(也可选用其他空闲的端口).
先看一下12位绝对编码器的SSI串行信号:
图1. 12位编码器SSI串行信号
信号包括三个:使能CSn,时钟CLK,编码器数据DO。一个完整的输出数据DO包括前12位有效数据和后6位校验数据,共18位。如此以来便不难实现信号的读取,只需将三个GPIO口分别设置为使能端口、时钟端口和数据接收端口即可。
步骤一:首先将PC7设置为复用功能PWM输出。查阅STM32F407ZGT6芯片手册(P57,stm32f4xx的芯片手册都差不多)可得PC7复用TIM3的第二个通道。相关代码见【3】pwm.c。输出的PWM作为时钟信号用,本实验设置的时钟频率(也即PWM频率)为2kHz。
图2. PC7复用功能
图3. PWM输出波形(用作时钟信号)
步骤二:其次,将PC6用作使能端口,将其配置为TTL电平开关量输出。相关代码见【4】switch.c。需要注意的是,SSI协议规定,使能信号是下降沿触发,而且必须满足下降沿之前的高电平长度大于时钟周期的二分之一,如此才能实现传感器数据输出。
解决方法为在PC7设置为PWM输出功能的同时,设定定时器内部中断,中断类型为TIM_IT_Update(即更新中断,计数器每溢出1次出发一下中断,也即每个时钟周期触发一次中断)。PWM每输出18个时钟周期,将CSn电平拉高2个时钟周期,再将CSn电平拉低,持续18个时钟周期再拉高,如此反复。CSnx信号波形如图4。每当CSn电平拉低时,DO口开始传输编码器数据。此时每个时钟周期会产生一位编码器数据信息,只需在中断程序中将其记录即可。也就是进入到步骤三。
图4. CSn波形图
步骤三:将PE5设置为普通输入模式,相关代码见【5】read.c。当CSn下降沿触发使能后,通过中断程序采集19个数据,每个数据(0或者1)代表图中DO输出的一位信息。传感器每次输出的一个完整数据是18位,为什么要采集19个数据呢?这是因为每次的时钟信号上升沿到来后就触发了中断,编码器输出的新的一位数据有一定延时,此时PE5采集到的数据信息属于上升沿之前的一位数据,因此采集19个数据,然后把第一个数据抛弃。
最后用剩下的18位数据的后六位检测前12位数据的有效性,即可得成功捕获SSI信号。数据在示波器中的波形图见图5(看波形也没啥意义)。
图5. DO输出数据波形
在ARM调试软件Keil中通过Debug可以看出,输出数据误差为1(即360/4095度),在误差范围内。最大数据值为0x0fff,符合12位编码器数据量程。
图6. 输出数据
[2] pwm.c:
/**
******************************************************************************
* @file pwm.c
* @author **
* @version V1.0.0
* @date 20170404
*****************************************************************************
**/
#include "pwm.h"
#include "stm32f4xx_tim.h"
#include "dac8564.h"
#include "SlaveTask.h"
#include "read.h"
extern int ii;
extern u8 GetData[35],GetFlag;
extern u16 SSIData,SSIData2;
//TIM3 PWM部分初始化
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u32 arr,u32 psc)
{
//此部分需手动修改IO口设置
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //TIM3时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); //使能PORTC时钟
GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_TIM3); //GPIOC7复用为定时器3
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //GPIOC7
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOC,&GPIO_InitStructure); //初始化PC7
TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);//初始化定时器3
//初始化TIM9 Channel1 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM1 4OC2
/* Enable the TIM9 global Interrupt */
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR1上的预装载寄存器
TIM_ARRPreloadConfig(TIM3,ENABLE);//ARPE使能
TIM_Cmd(TIM3, ENABLE); //使能TIM3
}
void TIM3_IRQHandler(void)
{
u8 Sum15=0;
u8 j=0;
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)
{
if(ii<18)
{
GetData[ii]=GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_5);
}
if(ii==18)
{
SWITCH_OUT((OUT_TypeDef) 0,Bit_SET);
}
if(ii==20)
{
SWITCH_OUT((OUT_TypeDef) 0,Bit_RESET);
for(ii=13;ii<14;ii++)
{
Sum15=GetData[ii-12]+GetData[ii-11]+GetData[ii-10]+GetData[ii-9]+GetData[ii-8]+GetData[ii-7]+GetData[ii-6]+GetData[ii-5]+GetData[ii-4]+GetData[ii-3]+GetData[ii-2]+GetData[ii-1]+GetData[ii]+GetData[ii+1]+GetData[ii+2];
if(GetData[ii]==1&&GetData[ii+1]==0&&GetData[ii+2]==0&&(GetData[ii+3]+GetData[ii+4]!=2))//&&GetData[ii+5]==(Sum15%2))
{
GetFlag++;
for(j=0;j<12;j++)
{
SSIData=SSIData << 1;
SSIData&=~0x01;
SSIData|=GetData[j+1];
}
SSIData2=SSIData;
}
}
for(ii=0;ii<18;ii++)
{
GetData[ii]=0;
}
ii=0;SSIData=0;
// GetFlag=0;
}
ii++;
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
[4] switch.c:
/**
******************************************************************************
* @file switch.c
* @author **
* @version V1.0.0
* @date 20141009
*****************************************************************************
**/
#include "switch.h"
uint8 fg=0;
GPIO_TypeDef* GPIO_PORT_OUT[SWITCH_OUTn] = {OUT1_GPIO_PORT,OUT2_GPIO_PORT,OUT3_GPIO_PORT,OUT4_GPIO_PORT,\
OUT5_GPIO_PORT,OUT6_GPIO_PORT,OUT7_GPIO_PORT,OUT8_GPIO_PORT,OUT9_GPIO_PORT,OUT10_GPIO_PORT};
const uint16_t GPIO_PIN_OUT[SWITCH_OUTn] = {OUT1_PIN,OUT2_PIN,OUT3_PIN,OUT4_PIN,OUT5_PIN,OUT6_PIN,\
OUT7_PIN,OUT8_PIN,OUT9_PIN,OUT10_PIN};
const uint32_t GPIO_CLK_OUT[SWITCH_OUTn] = {OUT1_GPIO_CLK,OUT2_GPIO_CLK,OUT3_GPIO_CLK,OUT4_GPIO_CLK,\
OUT5_GPIO_CLK,OUT6_GPIO_CLK,OUT7_GPIO_CLK,OUT8_GPIO_CLK,OUT9_GPIO_CLK,OUT10_GPIO_CLK};
static void _SWITCHInit_OUT(OUT_TypeDef Out)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(GPIO_CLK_OUT[Out], ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_PIN_OUT[Out];
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//GPIO_Mode_OUT
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIO_PORT_OUT[Out], &GPIO_InitStructure);
}
void SWITCHInit(void)
{
uint8 Index;
for(Index = 0; Index < SWITCH_OUTn; Index++)
{
_SWITCHInit_OUT((OUT_TypeDef)Index);
}
}
void SWITCH_OUT(OUT_TypeDef Out,BitAction d)
{
GPIO_WriteBit(GPIO_PORT_OUT[Out],GPIO_PIN_OUT[Out],d);
}
void cc1_handle(void)
{
if(fg == 0)
{
fg = 1;
SWITCH_OUT(OUT1,Bit_RESET);
}
else if(fg == 1)
{
fg = 0;
SWITCH_OUT(OUT1,Bit_SET);
}
}
/************************ (C) COPYRIGHT AnHui KeLi *****END OF FILE****/
[5] read.c:
#include "read.h"
void READ_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);//使能GPIOE时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//普通输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100M
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE5
}