基于RT-Thread的CAN电机驱动板设计 (三)CAN驱动配置与实现

引言

上一篇文章实现了基于rtthread的uart 的DMA接收驱动的配置与使用。下面重点需要完成rtthread对stm32的CAN驱动的配置,由于rtthread默认工程中没有对特定芯片如stm32的CAN驱动的芯片级驱动代码,但是在rtthread的github仓库中有相关的代码,需要我们自己完成配置。因此本文的重点工作是完成stm32的CAN驱动的配置,通过上述流程达到一以贯之的效果,以后再遇到其他协议也可以如此处理。

资料连接

本项目的所有资料全部开源:

硬件工程https://lceda.cn/FranHawk/485tocan_motor_controller
软件工程https://github.com/FranHawk/RT-Thread-485toCAN

前期准备

1
2
#define BSP_USING_CAN
#define BSP_USING_CAN1


最后还需要CAN的两个引脚初始化的代码,我们打开cubemx完成CAN配置

选中master mode就行了,我们只用到cubemx生成的引脚初始化代码,其余的包括波特率不用管,这个后面用rtthread的设备驱动设置,最后打开生成的MDK5工程,把stm32f1xx_hal_msp.c中的如下代码复制到rtthread studio的board.c中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
* @brief CAN MSP Initialization
* This function configures the hardware resources used in this example
* @param hcan: CAN handle pointer
* @retval None
*/
void HAL_CAN_MspInit(CAN_HandleTypeDef* hcan)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(hcan->Instance==CAN1)
{
/* USER CODE BEGIN CAN1_MspInit 0 */

/* USER CODE END CAN1_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_CAN1_CLK_ENABLE();

__HAL_RCC_GPIOA_CLK_ENABLE();
/**CAN GPIO Configuration
PA11 ------> CAN_RX
PA12 ------> CAN_TX
*/
GPIO_InitStruct.Pin = GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

/* USER CODE BEGIN CAN1_MspInit 1 */

/* USER CODE END CAN1_MspInit 1 */
}

}

/**
* @brief CAN MSP De-Initialization
* This function freeze the hardware resources used in this example
* @param hcan: CAN handle pointer
* @retval None
*/
void HAL_CAN_MspDeInit(CAN_HandleTypeDef* hcan)
{
if(hcan->Instance==CAN1)
{
/* USER CODE BEGIN CAN1_MspDeInit 0 */

/* USER CODE END CAN1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_CAN1_CLK_DISABLE();

/**CAN GPIO Configuration
PA11 ------> CAN_RX
PA12 ------> CAN_TX
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_11|GPIO_PIN_12);

/* USER CODE BEGIN CAN1_MspDeInit 1 */

/* USER CODE END CAN1_MspDeInit 1 */
}

}


至此完成了CAN驱动配置

参考rtthread官网的CAN设备的示例程序编写代码

https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/can/can

根据电机数据手册,设置波特率是1M,8个电机的ID号从0x141到0x148

1
2
3
4
5
6
7
8
9
/*各电机ID定义*/
#define MOTOR1_ID 0x141
#define MOTOR2_ID 0x142
#define MOTOR3_ID 0x143
#define MOTOR4_ID 0x144
#define MOTOR5_ID 0x145
#define MOTOR6_ID 0x146
#define MOTOR7_ID 0x147
#define MOTOR8_ID 0x148




根据数据手册,定义电机命令

1
2
3
4
5
/*电机控制指令定义*/
#define TORQUE_CURRENT_CMD 0xA1
#define STATE_QUEST_CMD 0x9C
#define MOTOR_ON_CMD 0x81
#define MOTOR_OFF_CMD 0x88
1
2
3
4
5
6
/* CAN 设备名称定义 */
#define CAN_DEV_NAME "can1"
/* 用于CAN接收消息的信号量 */
static struct rt_semaphore can_rx_sem;
/* CAN设备句柄 */
static rt_device_t can_dev;

CAN采用中断的方式接收数据,还是分为顶半处理和底半处理,顶半处理就是CAN接收中断函数,里面只负责释放信号量,底半处理是CAN数据处理线程,负责对CAN接收的数据进行解码,并且把有效信息存放在数组中等待被电机驱动板发送至上位机。
下面是CAN接收中断函数

1
2
3
4
5
6
7
8
/* CAN接收数据回调函数 */
static rt_err_t can_rx_call(rt_device_t dev, rt_size_t size)
{
/* CAN 接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
rt_sem_release(&can_rx_sem);

return RT_EOK;
}

CAN数据处理线程初始化函数,由于CAN发送控制和查询指令至电机的程序在串口数据处理线程中,优先级为5,每发给一个电机指令,电机就会返回一条指令,为了不让CAN数据接收受到影响,将CAN数据接收线程的优先级调整至比CAN数据发送更高。CAN数据处理线程优先级设为6。这个地方要注意的是,在电机驱动板连接电机时,将CAN工作模式设为正常模式,在未连接电机,单独调试时需要设置为LOOPBACK回环模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* CAN接收数据处理初始化 */
static rt_err_t can_rx_thread_init()
{
rt_err_t res;
rt_thread_t thread;

/* 查找 CAN 设备 */
can_dev = rt_device_find(CAN_DEV_NAME);
if (!can_dev)
{
rt_kprintf("find %s failed!\n", CAN_DEV_NAME);
return RT_ERROR;
}

/* 初始化 CAN 接收信号量 */
rt_sem_init(&can_rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);

/* 以中断接收及发送方式打开 CAN 设备 */
res = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX);
res = rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, (void *) CAN1MBaud);
res = rt_device_control(can_dev, RT_CAN_CMD_SET_MODE, (void *) RT_CAN_MODE_NORMAL);
RT_ASSERT(res == RT_EOK);
/* 创建数据接收线程,优先级为5 */
thread = rt_thread_create("can_rx", can_rx_thread_entry, RT_NULL, 2048, 5, 10);
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
res = RT_ERROR;
}
return res;
}

CAN数据处理线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/* CAN接收数据处理线程 */
static void can_rx_thread_entry(void *parameter)
{
struct rt_can_msg rxmsg = { 0 };

serial_tx_buffer[0] = 0x5A;
serial_tx_buffer[34] = 0xA5;
/* 设置接收回调函数 */
rt_device_set_rx_indicate(can_dev, can_rx_call);
/*不使用硬件过滤表*/
#ifdef RT_CAN_USING_HDR
struct rt_can_filter_item items[5] =
{
RT_CAN_FILTER_ITEM_INIT(0x100, 0, 0, 1, 0x700, RT_NULL, RT_NULL), /* std,match ID:0x100~0x1ff,hdr 为 - 1,设置默认过滤表 */
RT_CAN_FILTER_ITEM_INIT(0x300, 0, 0, 1, 0x700, RT_NULL, RT_NULL), /* std,match ID:0x300~0x3ff,hdr 为 - 1 */
RT_CAN_FILTER_ITEM_INIT(0x211, 0, 0, 1, 0x7ff, RT_NULL, RT_NULL), /* std,match ID:0x211,hdr 为 - 1 */
RT_CAN_FILTER_STD_INIT(0x486, RT_NULL, RT_NULL), /* std,match ID:0x486,hdr 为 - 1 */
{ 0x555, 0, 0, 1, 0x7ff, 7,} /* std,match ID:0x555,hdr 为 7,指定设置 7 号过滤表 */
};
struct rt_can_filter_config cfg =
{ 5, 1, items}; /* 一共有 5 个过滤表 */
/* 设置硬件过滤表 */
res = rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &cfg);
RT_ASSERT(res == RT_EOK);
#endif

while (1)
{
/* hdr 值为 - 1,表示直接从 uselist 链表读取数据 */
rxmsg.hdr = -1;
/* 阻塞等待接收信号量 */
rt_sem_take(&can_rx_sem, RT_WAITING_FOREVER);
/* 从 CAN 读取一帧数据 */
rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg));
/* 打印数据 ID 及内容 */
// rt_kprintf("ID:%x", rxmsg.id);
// for (i = 0; i < 8; i++)
// {
// rt_kprintf("%2x", rxmsg.data[i]);
// }
//rt_kprintf("\n");
// rt_kprintf("state_quest\n");
/*根据id号判断是哪个电机,并把相应的值放进数据发送数组中*/
/*由于控制指令和查询状态指令的返回值格式相同,这里不做区分*/
if (rxmsg.data[0] == STATE_QUEST_CMD || rxmsg.data[0] == TORQUE_CURRENT_CMD)/*判断是不是状态查询帧*/
{
switch (rxmsg.id)
{
case MOTOR1_ID:
serial_tx_buffer[1] = rxmsg.data[2];
serial_tx_buffer[2] = rxmsg.data[3];
serial_tx_buffer[17] = rxmsg.data[6];
serial_tx_buffer[18] = rxmsg.data[7];
break;
case MOTOR2_ID:
serial_tx_buffer[3] = rxmsg.data[2];
serial_tx_buffer[4] = rxmsg.data[3];
serial_tx_buffer[19] = rxmsg.data[6];
serial_tx_buffer[20] = rxmsg.data[7];
break;
case MOTOR3_ID:
serial_tx_buffer[5] = rxmsg.data[2];
serial_tx_buffer[6] = rxmsg.data[3];
serial_tx_buffer[21] = rxmsg.data[6];
serial_tx_buffer[22] = rxmsg.data[7];
break;
case MOTOR4_ID:
serial_tx_buffer[7] = rxmsg.data[2];
serial_tx_buffer[8] = rxmsg.data[3];
serial_tx_buffer[23] = rxmsg.data[6];
serial_tx_buffer[24] = rxmsg.data[7];
break;
case MOTOR5_ID:
serial_tx_buffer[9] = rxmsg.data[2];
serial_tx_buffer[10] = rxmsg.data[3];
serial_tx_buffer[25] = rxmsg.data[6];
serial_tx_buffer[26] = rxmsg.data[7];
break;
case MOTOR6_ID:
serial_tx_buffer[11] = rxmsg.data[2];
serial_tx_buffer[12] = rxmsg.data[3];
serial_tx_buffer[27] = rxmsg.data[6];
serial_tx_buffer[28] = rxmsg.data[7];
break;
case MOTOR7_ID:
serial_tx_buffer[13] = rxmsg.data[2];
serial_tx_buffer[14] = rxmsg.data[3];
serial_tx_buffer[29] = rxmsg.data[6];
serial_tx_buffer[30] = rxmsg.data[7];
break;
case MOTOR8_ID:/*收到电机8数据,说明所有数据均发送完成*/
serial_tx_buffer[15] = rxmsg.data[2];
serial_tx_buffer[16] = rxmsg.data[3];
serial_tx_buffer[31] = rxmsg.data[6];
serial_tx_buffer[32] = rxmsg.data[7];
break;
default:
break;
}
}

}
}

上面都是CAN接收数据的部分,下面展示部分CAN发送数据的代码,下面是上一篇所述的串口数据处理函数,对上位机的指令解码,然后通过CAN向电机发送指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/* 串口处理接收数据线程入口 */
static void rs485_serial_thread_entry(void *parameter)
{
rt_size_t size;
struct rs485_serial_rx_msg msg;
struct rt_can_msg can_tx_msg = { 0 };
rt_err_t result;
rt_uint32_t rx_length;
rt_uint8_t check_sum, check_index;
static char rx_buffer[RT_SERIAL_RB_BUFSZ + 1];

while (1)
{
rt_memset(&msg, 0, sizeof(msg));
/* 从消息队列中读取消息*/
result = rt_mq_recv(&rs485_serial_rx_mq, &msg, sizeof(msg), RT_WAITING_FOREVER);
if (result == RT_EOK)
{
//rt_kprintf("%d\n", msg.size);
/* 从串口读取数据*/

rx_length = rt_device_read(msg.dev, 0, rx_buffer, msg.size);
if ((rx_buffer[0] == 0x5A) && (rx_buffer[18] == 0xA5))
{
//rt_kprintf("recevice success\n");
//判断指令类型,若不为0xFF,则是转矩控制指令,否则为状态查询指令
if ((rx_buffer[1] != 0xFF) && (rx_buffer[17] != 0xFF))
{
check_sum = 0;
for (check_index = 1; check_index < 17; check_index++)
{
check_sum += rx_buffer[check_index];
}
if ((check_sum != rx_buffer[17]) && (rx_buffer[17] != 0x00))
{
rt_kprintf("check_sum wrong\n");
rt_kprintf("%02x\n", check_sum);
}
else
{
//rt_kprintf("check_sum right\n");

/* 向电机发送转矩电流控制数据 */
can_tx_msg.ide = RT_CAN_STDID; /* 标准格式 */
can_tx_msg.rtr = RT_CAN_DTR; /* 数据帧 */
can_tx_msg.len = 8; /* 数据长度为 8 */
can_tx_msg.data[0] = TORQUE_CURRENT_CMD;
can_tx_msg.data[1] = 0x00;
can_tx_msg.data[2] = 0x00;
can_tx_msg.data[3] = 0x00;
can_tx_msg.data[6] = 0x00;
can_tx_msg.data[7] = 0x00;

/* CAN向电机1发送数据 */
can_tx_msg.id = MOTOR1_ID;
can_tx_msg.data[4] = rx_buffer[1];
can_tx_msg.data[5] = rx_buffer[2];
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机2发送数据 */
can_tx_msg.id = MOTOR2_ID;
can_tx_msg.data[4] = rx_buffer[3];
can_tx_msg.data[5] = rx_buffer[4];
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机3发送数据 */
can_tx_msg.id = MOTOR3_ID;
can_tx_msg.data[4] = rx_buffer[5];
can_tx_msg.data[5] = rx_buffer[6];
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机4发送数据 */
can_tx_msg.id = MOTOR4_ID;
can_tx_msg.data[4] = rx_buffer[7];
can_tx_msg.data[5] = rx_buffer[8];
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机5发送数据 */
can_tx_msg.id = MOTOR5_ID;
can_tx_msg.data[4] = rx_buffer[9];
can_tx_msg.data[5] = rx_buffer[10];
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机6发送数据 */
can_tx_msg.id = MOTOR6_ID;
can_tx_msg.data[4] = rx_buffer[11];
can_tx_msg.data[5] = rx_buffer[12];
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机7发送数据 */
can_tx_msg.id = MOTOR7_ID;
can_tx_msg.data[4] = rx_buffer[13];
can_tx_msg.data[5] = rx_buffer[14];
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机8发送数据 */
can_tx_msg.id = MOTOR8_ID;
can_tx_msg.data[4] = rx_buffer[15];
can_tx_msg.data[5] = rx_buffer[16];
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));
for (check_index = 1; check_index < 33; check_index++)
{
serial_tx_buffer[33] += serial_tx_buffer[check_index];
}
rt_pin_write(RS485A_RE_PIN, PIN_HIGH);/*使485处于发送模式*/
rt_device_write(rs485_serial_device_handle, 0, serial_tx_buffer, sizeof(serial_tx_buffer));
rt_pin_write(RS485A_RE_PIN, PIN_LOW);/*使485处于接收模式*/
}
}
else
{
/* 向电机发送状态查询指令 */
can_tx_msg.ide = RT_CAN_STDID; /* 标准格式 */
can_tx_msg.rtr = RT_CAN_DTR; /* 数据帧 */
can_tx_msg.len = 8; /* 数据长度为 8 */
can_tx_msg.data[0] = STATE_QUEST_CMD;/* 指令类型为状态查询 */
can_tx_msg.data[1] = 0x00;
can_tx_msg.data[2] = 0x00;
can_tx_msg.data[3] = 0x00;
can_tx_msg.data[4] = 0x00;
can_tx_msg.data[5] = 0x00;
can_tx_msg.data[6] = 0x00;
can_tx_msg.data[7] = 0x00;

/* CAN向电机1发送数据 */
can_tx_msg.id = MOTOR1_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机2发送数据 */
can_tx_msg.id = MOTOR2_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机3发送数据 */
can_tx_msg.id = MOTOR3_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机4发送数据 */
can_tx_msg.id = MOTOR4_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机5发送数据 */
can_tx_msg.id = MOTOR5_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机6发送数据 */
can_tx_msg.id = MOTOR6_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机7发送数据 */
can_tx_msg.id = MOTOR7_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

/* CAN向电机8发送数据 */
can_tx_msg.id = MOTOR8_ID;
/* 发送一帧 CAN 数据 */
size = rt_device_write(can_dev, 0, &can_tx_msg, sizeof(can_tx_msg));

//通过485向上位机发送数据
for (check_index = 1; check_index < 33; check_index++)
{
serial_tx_buffer[33] += serial_tx_buffer[check_index];
}
rt_pin_write(RS485A_RE_PIN, PIN_HIGH);/*使485处于发送模式*/
rt_device_write(rs485_serial_device_handle, 0, serial_tx_buffer, sizeof(serial_tx_buffer));
rt_pin_write(RS485A_RE_PIN, PIN_LOW);/*使485处于接收模式*/
}
}
}
}
}

总结

通过上述代码,完成了针对stm32的CAN驱动的配置与应用,完成了电机驱动板大部分需求,可以通过电机驱动板控制电机转矩电流并查询电机状态了,详细代码我均已开源至github。下一篇准备实现用两个按键中断向电机发送CAN指令控制电机启停的功能。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2021 张竞豪的小岛 All Rights Reserved.

UV : | PV :