ATK-MB013 模块使用说明
这里以正点原子M48Z-M3最小系统板STM32F103版为例,给大家介绍一下模块和板卡的连接方法。其它板卡与模块的硬件连接方法,请大家在“ATK-MB013 EC11编码器模块\3,程序源码\相应板卡例程文件夹\readme.txt”路径下查看。
ATK-MB013 EC11编码器模块可通过杜邦线与正点原子M48Z-M3最小系统板STM32F103版进行连接,具体的连接关系,如下表所示:

表1.1 EC11编码器模块与M48Z-M3最小系统板STM32F103版连接关系
在本实验中,当编码器被旋转后,串口会打印编码器的累计计数值和累计旋转圈数;当编码器按键被按下(按压旋钮),串口会打印按键状态。需要查看这部分实验信息的用户,可用杜邦线将最小系统板STM32F103的PA9引脚和GND连接至外部的USB转串口设备,这样就可以通过XCOM上位机查看串口打印的信息了。
用户可以通过旋转编码器的旋钮,改变编码器累计计数值和累计旋转圈数。
开发板的LED0闪烁,提示程序运行。
打开本实验的工程文件夹,能够在./Drivers/BSP目录下看到ATK_ENCODER文件夹,其包含了ATK-MB013 EC11编码器模块的驱动文件,如下图所示:

图2.1.2.1 ATK-MB013 EC11编码器模块驱动代码
下面简要介绍atk_encoder.c中几个重要的API函数。
1. 函数atk_encoder_timx_init()
该函数用于初始化ATK-MB013 EC11编码器模块相关的定时器,具体代码如下所示:
TIM_HandleTypeDef g_timx_encode_chy_handle; /* 定时器x句柄 */
/**
* @brief 编码器定时器初始化
* @param arr: 自动重装值
* @param psc: 时钟预分频数
* @retval 无
*/
void atk_encoder_timx_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct;
TIM_Encoder_InitTypeDef timx_encoder_chy_handle;
/************************** 时钟使能 ***************************/
ATK_ENCODER_TIMX_CH1_GPIO_CLK_ENABLE(); /* 开启通道1的IO时钟 */
ATK_ENCODER_TIMX_CH2_GPIO_CLK_ENABLE(); /* 开启通道2的IO时钟 */
ATK_ENCODER_TIMX_CH1_CLK_ENABLE(); /* 开启通道1定时器时钟 */
ATK_ENCODER_TIMX_CH2_CLK_ENABLE(); /* 开启通道2定时器时钟 */
/**************************** GPIO配置 *******************************/
/* 通道1的IO口,即编码器的A相 */
gpio_init_struct.Pin = ATK_ENCODER_TIMX_CH1_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入模式 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(ATK_ENCODER_TIMX_CH1_GPIO_PORT, &gpio_init_struct);
/* 通道2的IO口,即编码器的B相 */
gpio_init_struct.Pin = ATK_ENCODER_TIMX_CH2_GPIO_PIN;
HAL_GPIO_Init(ATK_ENCODER_TIMX_CH2_GPIO_PORT, &gpio_init_struct);
/************************ 定时器基本配置 **************************/
g_timx_encode_chy_handle.Instance = ATK_ENCODER_TIMX; /* 定时器3 */
g_timx_encode_chy_handle.Init.Prescaler = psc; /* 定时器分频 */
g_timx_encode_chy_handle.Init.Period = arr; /* 自动重装载值 */
/* 递增计数模式 */
g_timx_encode_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
/* 不分频 */
g_timx_encode_chy_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
/************************* 编码器接口配置 ***************************/
/* TI1,TI2都检测 */
timx_encoder_chy_handle.EncoderMode = TIM_ENCODERMODE_TI12;
/* 边沿检测器设置,非反向 */
timx_encoder_chy_handle.IC1Polarity = TIM_ICPOLARITY_RISING;
/* 不分频 */
timx_encoder_chy_handle.IC1Prescaler = TIM_ICPSC_DIV1;
/* 滤波器设置 */
timx_encoder_chy_handle.IC1Filter = 10;
/* 边沿检测器设置,非反向 */
timx_encoder_chy_handle.IC2Polarity = TIM_ICPOLARITY_RISING;
/* 不分频 */
timx_encoder_chy_handle.IC2Prescaler = TIM_ICPSC_DIV1;
/* 滤波器设置 */
timx_encoder_chy_handle.IC2Filter = 10;
HAL_TIM_Encoder_Init(&g_timx_encode_chy_handle, &timx_encoder_chy_handle);
/* 开启编码器通道1 */
HAL_TIM_Encoder_Start(&g_timx_encode_chy_handle, ATK_ENCODER_TIMX_CH1);
/* 开启编码器通道2 */
HAL_TIM_Encoder_Start(&g_timx_encode_chy_handle, ATK_ENCODER_TIMX_CH2);
/************************* 定时器中断配置 ***************************/
/* 抢占优先级2,响应优先级0 */
HAL_NVIC_SetPriority(ATK_ENCODER_TIMX_INT_IRQn, 2, 0);
/* 开启定时器3中断 */
HAL_NVIC_EnableIRQ(ATK_ENCODER_TIMX_INT_IRQn);
/* 清除更新中断 */
__HAL_TIM_CLEAR_FLAG(&g_timx_encode_chy_handle,TIM_IT_UPDATE);
/* 开启更新中断 */
__HAL_TIM_ENABLE_IT(&g_timx_encode_chy_handle,TIM_IT_UPDATE);
/* 清空计数 */
__HAL_TIM_SET_COUNTER(&g_timx_encode_chy_handle, 0);
}
atk_encoder_timx_init()函数主要用于初始化编码器接口相关的IO及定时器,其中A相连接的是PA6(通道1),B相连接的是PA7(通道2)。这里用到的定时器为定时器3,配置为双边沿检测,即4倍频,如果接入的编码器为20线,则旋转一圈会计数80次。
2. 计数值计算相关函数
该系列函数用于获取编码器的计数值,具体代码如下所示:
/**
* @brief 定时器3中断服务函数
* @param 无
* @retval 无
*/
void ATK_ENCODER_TIMX_INT_IRQHandler(void)
{
HAL_TIM_IRQHandler(&g_timx_encode_chy_handle);
}
/* 用于记录溢出次数,向下溢出就-1,向上溢出就+1 */
volatile int g_timx_encode_count = 0;
/**
* @brief 定时器中断回调函数
* @param htim:定时器句柄指针
* @note 此函数会被定时器中断函数共同调用的
* @retval 无
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* 判断CR1的DIR位 */
if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&g_timx_encode_chy_handle))
{
g_timx_encode_count--; /* DIR位为1,也就是递减计数 */
}
else
{
g_timx_encode_count++; /* DIR位为0,也就是递增计数 */
}
}
/**
* @brief 编码器计数
* @param 无
* @retval 当前总计数值
*/
int32_t atk_encoder_get_count(void)
{
/* 计算当前总计数值,当前总计数值 = 计数器当前值 + 溢出次数*65536 */
return ( int32_t )(__HAL_TIM_GET_COUNTER(&g_timx_encode_chy_handle)
+ g_timx_encode_count * 65536);
}
上述代码中存在3个函数,前两者用于记录编码器计数溢出的次数,当定时器计数溢出时,会进入到定时器中断回调函数中,然后根据CR1寄存器的DIR位来判断是向上溢出还是向下溢出,依此来修改g_timx_encode_count变量的值。atk_encoder_get_count()函数则用于计算编码器的当前总计数值,具体计算公式见上述代码注释。
3. 编码器按键相关函数
该系列函数用于初始化编码器的按键以及扫描按键状态,具体代码如下所示:
/**
* @brief 编码器按键初始化函数
* @param 无
* @retval 无
*/
void atk_encoder_key_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
ATK_ENCODER_KEY_GPIO_CLK_ENABLE(); /* 时钟使能 */
gpio_init_struct.Pin = ATK_ENCODER_KEY_GPIO_PIN; /* 编码器引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
/* 编码器引脚模式设置,上拉输入 */
HAL_GPIO_Init(ATK_ENCODER_KEY_GPIO_PORT, &gpio_init_struct);
}
/**
* @brief 编码器按键扫描函数
* @param 无
* @retval 0:无按键按下
* ENCODER_KEY_PRESSED:按键被按下
* ENCODER_KEY_LOOSE:按键被松开
*/
uint8_t atk_encoder_key_scan(void)
{
static uint8_t key_up = 1; /* 按键松开标志 */
uint8_t key_val = 0;
if ((key_up == 1) && (ATK_ENCODER_KEY == 0)) /* 按键松开标志为1,且有按键按下 */
{
key_up = 0; /* 标记按键被按下 */
delay_ms(10); /* 去抖动 */
if (ATK_ENCODER_KEY == 0)
{
key_val = ENCODER_KEY_PRESSED; /* 返回按键被按下状态 */
}
}
else if ((key_up == 0) && (ATK_ENCODER_KEY == 1)) /* 检测到按键被松开 */
{
key_up = 1; /* 标记按键被松开 */
key_val = ENCODER_KEY_LOOSE; /* 返回按键被松开状态 */
}
return key_val; /* 返回键值 */
}
上述代码中,atk_encoder_key_init()函数用于初始化编码器按键相关的IO,这里使用的是PA5,配置为输入上拉模式。atk_encoder_key_scan()函数用于扫描编码器按键的状态,当按键被按下时,会返回ENCODER_KEY_PRESSED;当按键被松开时,会返回ENCODER_KEY_LOOSE。
实验的测试代码在demo.c文件中,该文件在工程根目录下的 User文件夹。测试代码的入口函数为demo_run(),具体的代码,如下所示:
/**
* @brief 例程演示入口函数
* @param 无
* @retval 无
*/
void demo_run(void)
{
uint8_t key, t = 0;
int32_t last_count = 0, new_count = 0;
atk_encoder_timx_init(0XFFFF, 0); /* 初始化编码器相关定时器,不分频 */
atk_encoder_key_init(); /* 初始化编码器按键 */
while (1)
{
key = atk_encoder_key_scan();
if (key == ENCODER_KEY_PRESSED) /* 编码器按键被按下 */
{
printf("按键被按下\r\n");
}
else if(key == ENCODER_KEY_LOOSE) /* 编码器按键被松开 */
{
printf("按键被释放\r\n");
}
new_count = atk_encoder_get_count(); /* 读取编码器计数值 */
if(new_count != last_count) /* 当计数值与上一次不同才打印到串口 */
{
last_count = new_count; /* 记录当前计数值 */
printf("编码器当前的计数值为:%d\r\n", new_count);
printf("编码器当前旋转的圈数为:%0.2f\r\n", (float)new_count / 80);
}
t++;
delay_ms(10);
if (t == 20)
{
LED0_TOGGLE(); /* LED0闪烁, 提示系统正在运行 */
t = 0;
}
}
}
从上面代码可以看出,整个测试代码的逻辑相对简单。初始化编码器模块的相关定时器和按键后,在while循环中调用相关函数不断地扫描按键状态和读取编码器的计数值,并通过串口打印,LED0闪烁表示程序正常运行。
注意:为了更好地观察编码器的计数值与按键状态,仅当编码器的计数值发生变化时才会打印串口。
将ATK-MB013 EC11编码器模块按照第一节“硬件连接”中介绍的连接方式与开发板连接,并将实验代码编译烧录至开发板中。本实验使用串口输出调试信息,因此需将开发板的PA9连接至DAP虚拟串口(或USB转TTL模块)的RX引脚。完成连接后,可通过串口调试助手XCOM查看实验信息输出,如下图所示:

图2.1.3.1串口调试助手显示内容
注:其它现象请看2.1.1功能说明。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3078162/ ,如需转载,请注明出处,否则将追究法律责任。