在前几篇文章中实现了电机驱动板需要实现的大部分功能,本来想进一步加一点锦上添花的小功能即配置按键中断向电机发送CAN指令实现电机的启动与停止,但是在这个过程中也遇到了不少问题,所以记录下来。
本项目的所有资料全部开源:
硬件工程:https://lceda.cn/FranHawk/485tocan_motor_controller
软件工程:https://github.com/FranHawk/RT-Thread-485toCAN
主要参照了rtthread官方的pin设备文档,但是发现根据PIN设备文档来配置仍存在一些问题,这个后面会提到
官方文档链接:
https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/pin/pin
根据官方文档,PIN设备的接口如下
通过PIN接口为按键绑定中断函数,官方文档中给出的示例代码如下:
#define KEY0_PIN_NUM 55 /* PD8 */
/* 中断回调函数 */
void beep_on(void *args)
{
rt_kprintf("turn on beep!\n");
rt_pin_write(BEEP_PIN_NUM, PIN_HIGH);
}
static void pin_beep_sample(void)
{
/* 按键0引脚为输入模式 */
rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP);
/* 绑定中断,上升沿模式,回调函数名为beep_on */
rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL);
}
我刚开始编写代码的时候,按照示例代码编写发现根本进入不了中断服务函数,最后通过在rtthread studio中查找pin设备的API发现下面这个例子。
/*
* Copyright (c) 2006-2018, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2018-08-15 misonyo first implementation.
*/
/*
* 程序清单:这是一个 PIN 设备使用例程
* 例程导出了 pin_beep_sample 命令到控制终端
* 命令调用格式:pin_beep_sample
* 程序功能:通过按键控制蜂鸣器对应引脚的电平状态控制蜂鸣器
*/
#include
#include
/* 引脚编号,通过查看驱动文件drv_gpio.c确定 */
#ifndef BEEP_PIN_NUM
#define BEEP_PIN_NUM 35 /* PB0 */
#endif
#ifndef KEY0_PIN_NUM
#define KEY0_PIN_NUM 55 /* PD8 */
#endif
#ifndef KEY1_PIN_NUM
#define KEY1_PIN_NUM 56 /* PD9 */
#endif
void beep_on(void *args)
{
rt_kprintf("turn on beep!\n");
rt_pin_write(BEEP_PIN_NUM, PIN_HIGH);
}
void beep_off(void *args)
{
rt_kprintf("turn off beep!\n");
rt_pin_write(BEEP_PIN_NUM, PIN_LOW);
}
static void pin_beep_sample(void)
{
/* 蜂鸣器引脚为输出模式 */
rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT);
/* 默认低电平 */
rt_pin_write(BEEP_PIN_NUM, PIN_LOW);
/* 按键0引脚为输入模式 */
rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP);
/* 绑定中断,上升沿模式,回调函数名为beep_on */
rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL);
/* 使能中断 */
rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE);
/* 按键1引脚为输入模式 */
rt_pin_mode(KEY1_PIN_NUM, PIN_MODE_INPUT_PULLUP);
/* 绑定中断,上升沿模式,回调函数名为beep_off */
rt_pin_attach_irq(KEY1_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_off, RT_NULL);
/* 使能中断 */
rt_pin_irq_enable(KEY1_PIN_NUM, PIN_IRQ_ENABLE);
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(pin_beep_sample, pin beep sample);
发现官网文档中的示例程序少了一句
rt_pin_irq_enable(KEY1_PIN_NUM, PIN_IRQ_ENABLE);
手动使能pin中断,这个问题卡了我半天…在这里记录下来。
同样采用顶半处理和底半处理的结构,通过信号量完成同步,代码比较简单,重要的是别忘了rt_pin_irq_enable。
//KEY0电机启动中断函数
void motor_on_irq(void *args)
{
rt_sem_release(&motor_on_sem);
rt_pin_write(LED0_PIN, PIN_HIGH);
}
//KEY1电机关闭中断函数
void motor_off_irq(void *args)
{
rt_sem_release(&motor_off_sem);
rt_pin_write(LED0_PIN, PIN_HIGH);
}
//电机启动底半处理
static void motor_on_thread_entry()
{
rt_size_t size;
struct rt_can_msg can_motor_on_msg;
/* 电机开启指令初始化 */
can_motor_on_msg.ide = RT_CAN_STDID; /* 标准格式 */
can_motor_on_msg.rtr = RT_CAN_DTR; /* 数据帧 */
can_motor_on_msg.len = 8; /* 数据长度为 8 */
can_motor_on_msg.data[0] = MOTOR_ON_CMD;/* 指令类型为状态查询 */
can_motor_on_msg.data[1] = 0x00;
can_motor_on_msg.data[2] = 0x00;
can_motor_on_msg.data[3] = 0x00;
can_motor_on_msg.data[4] = 0x00;
can_motor_on_msg.data[5] = 0x00;
can_motor_on_msg.data[6] = 0x00;
can_motor_on_msg.data[7] = 0x00;
while (1)
{
/* 阻塞等待接收信号量 */
rt_sem_take(&motor_on_sem, RT_WAITING_FOREVER);
//rt_kprintf("power up motor!\n");
/* CAN向电机1发送数据 */
can_motor_on_msg.id = MOTOR1_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_on_msg, sizeof(can_motor_on_msg));
/* CAN向电机2发送数据 */
can_motor_on_msg.id = MOTOR2_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_on_msg, sizeof(can_motor_on_msg));
/* CAN向电机3发送数据 */
can_motor_on_msg.id = MOTOR3_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_on_msg, sizeof(can_motor_on_msg));
/* CAN向电机4发送数据 */
can_motor_on_msg.id = MOTOR4_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_on_msg, sizeof(can_motor_on_msg));
/* CAN向电机5发送数据 */
can_motor_on_msg.id = MOTOR5_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_on_msg, sizeof(can_motor_on_msg));
/* CAN向电机6发送数据 */
can_motor_on_msg.id = MOTOR6_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_on_msg, sizeof(can_motor_on_msg));
/* CAN向电机7发送数据 */
can_motor_on_msg.id = MOTOR7_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_on_msg, sizeof(can_motor_on_msg));
/* CAN向电机8发送数据 */
can_motor_on_msg.id = MOTOR8_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_on_msg, sizeof(can_motor_on_msg));
}
}
//电机停止底半处理
static void motor_off_thread_entry()
{
rt_size_t size;
struct rt_can_msg can_motor_off_msg;
/* 电机关闭指令初始化 */
can_motor_off_msg.ide = RT_CAN_STDID; /* 标准格式 */
can_motor_off_msg.rtr = RT_CAN_DTR; /* 数据帧 */
can_motor_off_msg.len = 8; /* 数据长度为 8 */
can_motor_off_msg.data[0] = MOTOR_OFF_CMD;/* 指令类型为状态查询 */
can_motor_off_msg.data[1] = 0x00;
can_motor_off_msg.data[2] = 0x00;
can_motor_off_msg.data[3] = 0x00;
can_motor_off_msg.data[4] = 0x00;
can_motor_off_msg.data[5] = 0x00;
can_motor_off_msg.data[6] = 0x00;
can_motor_off_msg.data[7] = 0x00;
while (1)
{
/* 阻塞等待接收信号量 */
rt_sem_take(&motor_off_sem, RT_WAITING_FOREVER);
//rt_kprintf("power off motor!\n");
/* CAN向电机1发送数据 */
can_motor_off_msg.id = MOTOR1_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_off_msg, sizeof(can_motor_off_msg));
/* CAN向电机2发送数据 */
can_motor_off_msg.id = MOTOR2_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_off_msg, sizeof(can_motor_off_msg));
/* CAN向电机3发送数据 */
can_motor_off_msg.id = MOTOR3_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_off_msg, sizeof(can_motor_off_msg));
/* CAN向电机4发送数据 */
can_motor_off_msg.id = MOTOR4_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_off_msg, sizeof(can_motor_off_msg));
/* CAN向电机5发送数据 */
can_motor_off_msg.id = MOTOR5_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_off_msg, sizeof(can_motor_off_msg));
/* CAN向电机6发送数据 */
can_motor_off_msg.id = MOTOR6_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_off_msg, sizeof(can_motor_off_msg));
/* CAN向电机7发送数据 */
can_motor_off_msg.id = MOTOR7_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_off_msg, sizeof(can_motor_off_msg));
/* CAN向电机8发送数据 */
can_motor_off_msg.id = MOTOR8_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_motor_off_msg, sizeof(can_motor_off_msg));
}
}
//电机启停线程初始化函数
static rt_err_t motor_on_off_thread_init(void)
{
rt_err_t res_on, res_off, res;
rt_thread_t thread_on, thread_off;
res = RT_EOK;
/* 按键0引脚为输入模式 */
rt_pin_mode(KEY0_PIN, PIN_MODE_INPUT_PULLUP);
/* 绑定中断,上升沿模式,回调函数名为motor_on */
rt_pin_attach_irq(KEY0_PIN, PIN_IRQ_MODE_FALLING, motor_on_irq, RT_NULL);
rt_pin_irq_enable(KEY0_PIN, PIN_IRQ_ENABLE);
/* 按键1引脚为输入模式 */
rt_pin_mode(KEY1_PIN, PIN_MODE_INPUT_PULLUP);
/* 绑定中断,上升沿模式,回调函数名为motor_on */
rt_pin_attach_irq(KEY1_PIN, PIN_IRQ_MODE_FALLING, motor_off_irq, RT_NULL);
rt_pin_irq_enable(KEY1_PIN, PIN_IRQ_ENABLE);
/* 初始化电机启动信号量 */
rt_sem_init(&motor_on_sem, "motor_on_sem", 0, RT_IPC_FLAG_FIFO);
/* 初始化电机启动信号量 */
rt_sem_init(&motor_off_sem, "motor_off_sem", 0, RT_IPC_FLAG_FIFO);
/* 创建电机启动线程,优先级为4 */
thread_on = rt_thread_create("motor_on", motor_on_thread_entry, RT_NULL, 1024, 4, 10);
if (thread_on != RT_NULL)
{
rt_thread_startup(thread_on);
}
else
{
res_on = RT_ERROR;
}
/* 创建电机停止线程,优先级为4 */
thread_off = rt_thread_create("motor_off", motor_off_thread_entry, RT_NULL, 1024, 4, 10);
if (thread_off != RT_NULL)
{
rt_thread_startup(thread_off);
}
else
{
res_off = RT_ERROR;
}
if ((res_on == RT_ERROR) || (res_off == RT_ERROR))
{
res = RT_ERROR;
}
return res;
}
至此,整个基于RT-Thread的CAN电机驱动板项目开发完毕,学到了很多新东西,加深了我对rtthread中驱动还有线程间通信和同步的理解,这个项目的可扩展性比较强,不仅可以用于这款电机,还能用于其他CAN协议的电机,值得大家借鉴。