【ARM】仅用三个GPIO口实现串行SSI信号读取

关键词:磁编码器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串行信号:

【ARM】仅用三个GPIO口实现串行SSI信号读取_第1张图片

图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复用功能

【ARM】仅用三个GPIO口实现串行SSI信号读取_第2张图片

图3. PWM输出波形(用作时钟信号)

       步骤二:其次,将PC6用作使能端口,将其配置为TTL电平开关量输出。相关代码见【4】switch.c。需要注意的是,SSI协议规定,使能信号是下降沿触发,而且必须满足下降沿之前的高电平长度大于时钟周期的二分之一,如此才能实现传感器数据输出。

       解决方法为在PC7设置为PWM输出功能的同时,设定定时器内部中断,中断类型为TIM_IT_Update(即更新中断,计数器每溢出1次出发一下中断,也即每个时钟周期触发一次中断)。PWM每输出18个时钟周期,将CSn电平拉高2个时钟周期,再将CSn电平拉低,持续18个时钟周期再拉高,如此反复。CSnx信号波形如图4。每当CSn电平拉低时,DO口开始传输编码器数据。此时每个时钟周期会产生一位编码器数据信息,只需在中断程序中将其记录即可。也就是进入到步骤三。

【ARM】仅用三个GPIO口实现串行SSI信号读取_第3张图片

图4. CSn波形图

       步骤三:将PE5设置为普通输入模式,相关代码见【5】read.c。当CSn下降沿触发使能后,通过中断程序采集19个数据,每个数据(0或者1)代表图中DO输出的一位信息。传感器每次输出的一个完整数据是18位,为什么要采集19个数据呢?这是因为每次的时钟信号上升沿到来后就触发了中断,编码器输出的新的一位数据有一定延时,此时PE5采集到的数据信息属于上升沿之前的一位数据,因此采集19个数据,然后把第一个数据抛弃。

       最后用剩下的18位数据的后六位检测前12位数据的有效性,即可得成功捕获SSI信号。数据在示波器中的波形图见图5(看波形也没啥意义)。

【ARM】仅用三个GPIO口实现串行SSI信号读取_第4张图片

图5. DO输出数据波形

       在ARM调试软件Keil中通过Debug可以看出,输出数据误差为1(即360/4095度),在误差范围内。最大数据值为0x0fff,符合12位编码器数据量程。

 【ARM】仅用三个GPIO口实现串行SSI信号读取_第5张图片

图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
}

你可能感兴趣的:(ARM)