查看: 1042|回复: 1

【瑞萨RA4系列开发板体验】CANOPEN协议移植及测试

[复制链接]

116

主题

133

帖子

3768

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
3768
发表于 2023-2-3 14:42:48 | 显示全部楼层 |阅读模式
【瑞萨RA4系列开发板体验】CANOPEN协议移植及测试
作者:我爱下载


Canopen协议移植和设备通讯测试
Canopen设备通讯测试是基于RA4M2产品CAN总线外设功能的一个小项目应用,其主要功能是通过RA4M2评估板采集开关量状态,通过canopen协议将开关量状态实时发送到canopen主机端。
Can网络构成
根据设计目的,通过RA4M2评估板外扩CAN总线接口,和外部两个CAN总线设备构成一个CAN的局域网,其中一个设备用于canopen 主机,一个设备作为CAN总线的监视设备。

其中:
RA4M2为canopen设备,基于canopenNode协议栈
CANOPEN主机采用自制的SLCAN设备,支持python
CAN总线监视设备为自制的CAN总线设备。

软件实现
RA4M2评估板canopen协议栈

1)canopennode协议栈下载
CanopenNode为github上的一个开源项目,在gitee上面也可以找到,大家搜索即可,这里不展开介绍。
2)canopeneds管理关键
基于开源软件自己编译的,和canopenNode配套的软件,可以直接生成对象字典的C文件和H文件,集成进软件中及可以了。
3)canopenNode的移植
协议栈移植时基于前一个试用《CAN总线收发测试》,链接为:https://bbs.elecfans.com/jishu_2319076_1_1.html
将前面下载的canopenNode源码打开,如下图所示,我们在工程文件下,建立canopen虚拟文件夹,并将301目录,example目录下复制到工程中,再将CANopen.c和CANopen.h复制到工程中,将example目录名修改为port。


文件添加完成后的工程如下图所示:

移植过程主要时port目录下面CO_driver.c中相关接口函数的移植:
  • 添加必要的头文件
  • CO_CANModule_Init函数移植

  1. * * * * CO_ReturnError_t  CO_CANmodule_init (CO_CANmodule_t
  2. *CANmodule,
  3. void                   *CANptr,
  4. CO_CANrx_t      rxArray[],
  5. uint16_t             rxSize,
  6. CO_CANtx_t     txArray[],
  7. uint16_t                txSize,
  8. uint16_t                CANbitRate)
  9. {
  10. FSP_PARAMETER_NOT_USED (CANbitRate);
  11. … … 此处省略若干代码
  12. /* configure CAN interrupt registers */
  13. fsp_err_t err =  *FSP_SUCCESS* ;
  14. /* Open the transfer instance with initial configuration. */
  15. err = R_CAN_Open (&g_can0_ctrl, &g_can0_cfg);
  16. assert(*FSP_SUCCESS* == err);
  17. return CO_ERROR_NO ;
  18. }
复制代码
  • CO_CANmodule_disable 函数移植

  1. void  CO_CANmodule_disable (CO_CANmodule_t *CANmodule)

  2. {

  3. FSP_PARAMETER_NOT_USED (CANmodule);
  4. /* turn off the module */

  5. fsp_err_t err =  *FSP_SUCCESS* ;

  6. /* Open the transfer instance with initial configuration.

  7. */

  8. err = R_CAN_Close (&g_can0_ctrl);
  9. assert(*FSP_SUCCESS* == err);
  10. }
复制代码
  • CO_CANtxBufferInit 函数移植

  1. * CO_CANtx_t  CO_CANtxBufferInit (CO_CANmodule_t

  2. *CANmodule,
  3. uint16_t                index,
  4. uint16_t                ident,
  5. bool_t                  rtr,
  6. uint8_t                 noOfBytes,
  7. bool_t                  syncFlag)

  8. {
  9. CO_CANtx_t *buffer = NULL;

  10. if ((CANmodule != NULL) && (index < CANmodule->txSize)){

  11. /* get specific buffer */
  12. buffer = &CANmodule->txArray[index];

  13. /* CAN identifier, DLC and rtr, bit aligned with CAN module transmit buffer.

  14. buffer->ident = ((uint32_t)(ident & 0x07FFU) | (uint32_t)(rtr ? 0x8000U : 0U));
  15. buffer->DLC = (uint32_t)noOfBytes & 0xFU;
  16. buffer->bufferFull = false;
  17. buffer->syncFlag = syncFlag;
  18. return buffer;

  19. }
  20. }
复制代码
l CO_CANinterrupt 函数移植
将原 CO_CANinterrupt 函数调整为CO_CANinterrupt_TX 和CO_CANinterrupt_Rx 两个函数

  1. void  CO_CANinterrupt_Rx (CO_CANmodule_t *CANmodule , _CANMsg_t *rcvMsg)

  2. {

  3. /* receive interrupt */
  4. uint16_t index;             /* index of received message */

  5. uint32_t rcvMsgIdent;       /* identifier of the received message */
  6. CO_CANrx_t *buffer = NULL;  /* receive message buffer from CO_CANmodule_t object. */
  7. bool_t msgMatched = false;
  8. //        rcvMsg = 0; /* get message from module here */
  9. rcvMsgIdent = rcvMsg->ident;

  10. {
  11. /* CAN module filters are not used, message with any standard 11-bit identifier */
  12. /* has been received. Search rxArray form CANmodule for the same CAN-ID. */

  13. buffer = &CANmodule->rxArray[0];
  14. for (index = CANmodule->rxSize; index > 0U; index--){

  15. if (((rcvMsgIdent ^ buffer->ident) & buffer->mask) == 0U){

  16. msgMatched = true;
  17. break ;

  18. }

  19. buffer++;

  20. }

  21. }

  22. /* Call specific function, which will process the message*/

  23. if (msgMatched && (buffer != NULL) && (buffer->CANrx_callback != NULL)){
  24. buffer->CANrx_callback(buffer->object, ( void *) rcvMsg);
  25. }

  26. /* Clear interrupt flag */

  27. }

  28. //Interrupt from Transeiver

  29. void  CO_CANinterrupt_Tx (CO_CANmodule_t *CANmodule) {

  30. /* Clear interrupt flag */
  31. /* First CAN message (bootup) was sent successfully */

  32. CANmodule->firstCANtxMessage = false;

  33. /* clear flag from previous message */
  34. CANmodule->bufferInhibitFlag = false;

  35. /* Are there any new messages waiting to be send */

  36. if (CANmodule->CANtxCount > 0U){

  37. uint16_t i;             /* index of transmitting message */
  38. /* first buffer */

  39. CO_CANtx_t *buffer = &CANmodule->txArray[0];

  40. /* search through whole array of pointers to transmit message buffers. */
  41. for (i = CANmodule->txSize; i > 0U; i--){
  42. /* if message buffer is full, send it. */
  43. if (buffer->bufferFull){
  44. buffer->bufferFull = false;
  45. CANmodule->CANtxCount--;
  46. /* Copy message to CAN buffer */
  47. CANmodule->bufferInhibitFlag = buffer->syncFlag;
  48. /* canSend... */
  49. _can_send(buffer);
  50. break ;                      /* exit for loop */
  51. }
  52. buffer++;
  53. }/* end of for loop */
  54. /* Clear counter if no more messages */
  55. if (i == 0U){
  56. CANmodule->CANtxCount = 0U;
  57. }
  58. }
  59. }
复制代码
  • Can接收线程的移植:

  1. void  can_recv_thread_entry (void *pvParameters)
  2. {
  3. FSP_PARAMETER_NOT_USED (pvParameters);
  4. /*  **TODO** : add your own code here */
  5. _CANMsg_t RxMsg;
  6. while (1)
  7. {
  8. if (xQueueReceive(g_canrecv_queue, &RxMsg,100) == pdTRUE)     {
  9. if (CO != NULL)
  10. CO_CANinterrupt_Rx(CO->CANmodule[0] , &RxMsg);     }
  11. }
  12. }
复制代码
CAN发送线程的移植:
  1. void  can_send_thread_entry (void *pvParameters){
  2. FSP_PARAMETER_NOT_USED (pvParameters);
  3. /*  **TODO** : add your own code here */
  4. can_frame_t TxMsg;
  5. _CANMsg_t msg;
  6. while (1)
  7. {
  8. /* 等待接收有效数据包 */
  9. if (xQueueReceive(g_cansend_queue, &msg,100) == pdTRUE)     {
  10. TxMsg.id = msg.ident;
  11. TxMsg.data_length_code = msg.DLC;
  12. TxMsg.type =  *CAN_FRAME_TYPE_DATA* ;
  13. memcpy (TxMsg.data, msg.data, 8);        can_send(TxMsg);
  14. }
  15. }
  16. }
复制代码
毫秒定时器
Canopen协议的驱动还需要一个1ms定时器,利用系统中的timer0定时器,创建一个1ms定时器,在回调函数中添加canopen的定时处理事务。

  1. /*
  2. timer thread executes in constant intervals
  3. */
  4. void  timer0_callback** (timer_callback_args_t *p_args){
  5. if (p_args->event ==  *TIMER_EVENT_CYCLE_END* )
  6. {
  7. /* sleep for interval */
  8. INCREMENT_1MS(CO_timer1ms);

  9. if (CO->CANmodule[0]->CANnormal) {
  10. bool_t syncWas;
  11. /* Process Sync */
  12. syncWas = CO_process_SYNC(CO,TMR_TASK_INTERVAL, NULL);
  13. /* Read inputs */
  14. CO_process_RPDO(CO, syncWas);
  15. /* Further I/O or nonblocking application code may go here. */
  16. /* Write outputs */
  17. CO_process_TPDO(CO, syncWas,TMR_TASK_INTERVAL, NULL);
  18. /* verify timer overflow */
  19. if (0) {
  20. CO_errorReport(CO->em, CO_EM_ISR_TIMER_OVERFLOW,CO_EMC_SOFTWARE_INTERNAL, 0U);
  21. }
  22. }
  23. }
  24. }
复制代码
3、实测运行
为了完成测试,需要构建一个canopen主机,这里使用python+slcan+canopen搭建一个CANOPEN主机。
基本代码为:

  1. import time

  2. import canopen

  3. // #创建一个网络用来表示CAN总线

  4. network = canopen.Network()

  5. // #添加slave节点,其id是6,对象字典为CANopenSocket.eds

  6. node = canopen.RemoteNode(10, 'c1.eds')

  7. network.add_node(node)

  8. forobjinnode.object_dictionary.values():

  9. print('0x%X: %s' % (obj.index, obj.name))

  10. ifisinstance(obj, canopen.objectdictionary.Record):

  11. forsubobjinobj.values():

  12. print('  %d: %s' % (subobj.subindex, subobj.name))

  13. /# 连接到CAN总线


  14. network.connect(interface='slcan', channel='COM4', bitrate=125000)

  15. time.sleep(1)

  16. stat = node.nmt.state

  17. print(stat)

  18. time.sleep(0.5)

  19. /# Change state

  20. to operational (NMT start)

  21. node.nmt.state = 'PRE-OPERATIONAL'

  22. time.sleep(0.5)

  23. /# Read a

  24. variable using SDO

  25. device_name = node.sdo['Manufacturer
  26. device name'].raw

  27. time.sleep(0.5)

  28. vendor_id = node.sdo[0x1018][1].raw

  29. print(device_name)

  30. print(vendor_id)

  31. time.sleep(0.5)

  32. /# Write a

  33. variable using SDO

  34. node.sdo['Producer
  35. heartbeat time'].raw = 3000

  36. time.sleep(0.5)

  37. /# Read PDO

  38. configuration from node

  39. node.tpdo.read()

  40. node.rpdo.read()

  41. /# Using a

  42. callback to asynchronously receive values

  43. /# Do not do any

  44. blocking operations here!

  45. defprint_speed(message):
复制代码
print('%s received' % message.name)
forvarinmessage:
print('%s = %d' % (var.name, var.raw))

  1. node.tpdo[1].add_callback(print_speed)

  2. /# Change state

  3. to operational (NMT start)

  4. node.nmt.state = 'OPERATIONAL'

  5. time.sleep(0.5)

  6. /# Transmit SYNC

  7. every 100 ms

  8. network.sync.start(0.1)

  9. time.sleep(0.5)

  10. whileTrue:

  11. pass
复制代码
CANOpen设备的配置情况,0x6000开始保存8字节的开关量输入信息,TPDO[1]配置为同步循环发送,同步10次发送1次。TPDO[1]中就包含一个数据字节,为0x6000对应的第一字节的开关量。具体配置信息见下图。
设备地址设置为10.




设备启动时:
如下图所示,首先发送0x70A 1 0x00 表示设备上线,然后循环发送0x70A 1 0x7F 表示CANOpen设备进入到预操作状态,等待CANOpen主机的进一步操作。


主机启动:

主机输出log信息
PS D:\MyProg\python> & "C:/Program
Files/Python38/python.exe" d:/MyProg/python/pcan/testcanopen_m.py
0x1000: Device type
省略部分信息
0x100A: Manufacturer software version
0x1018: Identity
0: max sub-index
1: Vendor-ID
2: Product code
3: Revision number
4: Serial number
0x1800: TPDO communication parameter
0: max sub-index
1: COB-ID used by TPDO
2: transmission type
3: inhibit time
4: compatibility entry
5: event timer
6: SYNC start value
0x1801: TPDO communication parameter
0: max sub-index
1: COB-ID used by TPDO
2: transmission type
3: inhibit time
4: compatibility entry
5: event timer
6: SYNC start value
省略部分信息
0x6000: Read input 8 bit
省略部分信息
PRE-OPERATIONAL(当前设备状态)
CANopenNode(Manufacturer device name)
0
(Vendor id)
(主机读取TPDO和RPDO配置信息)
(以下为设备转如操作状态,同时主机提供同步信号)
TxPDO1_node10 received
Read input 8 bit.Input = 17
TxPDO1_node10 received
Read input 8 bit.Input = 18
TxPDO1_node10 received
Read input 8 bit.Input = 19
主机控制设备进入预操作状态,并读取厂商信息和id


主机读取TPDO和RPDO配置信息
主机启动设备进入操作模式,并且提供同步信号。
主机操作设备进入操作状态,0x000 2 0x01 0x0A
设备进入操作状态: 0x70A 1 0x05
主机启动同步信息 0x080 ,同步信号间隔0.1秒
在同步信号的作用下,从机间隔10个同步信号,发送给意思TPDO[1]信息。TPDO[1] 配置为同步10次,循环发送。从发送的CAN总线监视设备上可以清晰的观察到间隔10个ID = 0x080的同步帧后,设备发送了一个ID = 0x18A的TPDO[1]数据帧。数据帧中包含1个字节的开关量输入数据,这个数据认为处理为每秒加1,所以我们可以看到ID=0x18A的数据在逐次递增。
到此,执行CANOPEN协议的设备已经正确的运行起来了。


回复

使用道具 举报

3

主题

195

帖子

1196

积分

金牌会员

Rank: 6Rank: 6

积分
1196
发表于 2023-3-14 11:06:21 | 显示全部楼层

不错,学习一下
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

用户排行榜

RA助手

主题: 116帖子:133精华:0

RA_Lance

主题: 92帖子:132精华:9

lugl

主题: 38帖子:126精华:0

xujiwei263

主题: 16帖子:73精华:0

books咦

主题: 11帖子:11精华:2

Juggernaut

主题: 9帖子:95精华:0
快速回复 返回顶部 返回列表