使用Xilinx FPGA完成CAN总线的收发控制(二)

发布时间:2026/7/3 18:50:55

使用Xilinx FPGA完成CAN总线的收发控制(二) 1-4Verilog代码说明(1)can_rx接收模块[1]状态机的定义.模块中的状态机完全按照CAN总线数据格式定义.//RX模块状态机定义 localparam IDLE 4d0;//RX模块空闲 localparam SOF 4d1;//接收SOF数据 localparam ID 4d2;//接收识别段ID数据 localparam CONTROL 4d3;//接收控制段数据 localparam CRC 4d4;//接收CRC段数据 localparam ACK 4d5;//接收成功发送响应位 localparam EOF 4d6;//接收结束段数据[2]相关参数的定义.本实验使用100Kbps的总线速率,所以设置目标计数值为499,每bit数据为10us.按照参数设计,本实验中每个bit划分为SSPTSPBS1PBS210个tq,所以每个tq时长为1us.//定义FPGA CAN总线ID识别码为11h2aa localparam LOCAL_ID 11h2aa ; //使用100Kps的CAN传输,每位10us localparam CNT_10US 13d499 ; //以下为位同步操作代码 //每一位bit分为10个tq,所以计数目标值为1us localparam CNT_1US 13d49 ;[3]状态机的跳转直接按照远程帧的协议定义设定,数据位计数值到达预定值,即跳转下一个状态.即使发送端发送的设备ID与FPGA端不一致,将寄存器discard置1,但FPGA并不暂停此流程而是继续监控此帧数据直到结束.1.//判断状态转移条件 2. always (*) 3.begin 4. case(canrx_curstate) 5. IDLE: if(can_rx_negedge) canrx_nextstateSOF; else canrx_nextstatecanrx_curstate; 6. SOF: if(bit_cnt2) canrx_nextstateID; else canrx_nextstatecanrx_curstate; 7. ID: if(bit_cnt12) canrx_nextstateCONTROL; else canrx_nextstatecanrx_curstate; 8. CONTROL: if(bit_cnt6) canrx_nextstateCRC; else canrx_nextstatecanrx_curstate; 9. CRC: if(bit_cnt16) canrx_nextstateACK; else canrx_nextstatecanrx_curstate; 10. ACK: if(bit_cnt2) canrx_nextstateEOF; else canrx_nextstatecanrx_curstate; 11. EOF: if(bit_cnt7) canrx_nextstateIDLE; else canrx_nextstatecanrx_curstate; 12. default : canrx_nextstateIDLE; 13. endcase 14. end[4]生成与每个bit同步的clk_bit.定位每个bit的起始是通过对tq进行计数完成的.当tq的计数完成一个循环,那么此时长就是一个bit数据位的时长.clk_cnt_tq是tq时长(1us)的计数器.另外,可以看到,clk_bit的生成时刻并不是tq的0时刻(起始时刻),而设计在了第1个tq的时长中心时刻.这个设计的目的在于位同步设计中,需要判定每个bit是否位于ss段的tq时长内.所以需要将数据位起始定义在ss段tq的中心位置.1. reg clk_bit ; //与每一位数据同步的时钟 2. reg [5:0] tq_cnt ; //位同步操作使用的计数器,tq为位同步操作的最小单位 3. always (posedge clk or negedge rst_n) 4. if(!rst_n) clk_bit1b0; 5. else if((clk_cnt_tq(CNT_1US1))(tq_cnt0)) clk_bit1b1; else clk_bit1b0;[5]在之前的章节中,已经介绍过CAN总线填充位的概念.CAN数据在发送的时候会根据实际连续位的情况添加填充位,那么接收端就需要排除这些填充位.识别的方法为对相邻的两位传输数据进行比对,如果值相同,那么samebit_cnt加1,如果samebit_cnt的值达到4,即相邻五位的数据一致,那么下一位必定是填充位,此时将stuff_valid置1,作为当前bit为填充位的标识.因为填充位不在协议的数据定义之内,所以当stuff_valid有效时,bit_cnt在此位不递增.前后两位比对的方法为先将前一位寄存,然后与当前位比对.1.//以位时钟为参考寄存rx信号,目的在于比较前后两位bit数值是否一致 2. reg can_rx_clkbit; 3. always (posedge clk or negedge rst_n) 4.if(!rst_n) begin can_rx_clkbit1b1;end 5.else if(sample_posedge) can_rx_clkbitcan_rx_b; 6. 7.//如果前后两个bit位数值相同,那么计数值加一 8. reg [2:0] samebit_cnt; 9. always (posedge clk or negedge rst_n) 10.if(!rst_n) samebit_cnt3b0; 11. else if(sample_posedge) 12. begin if(can_rx_b!can_rx_clkbit) samebit_cnt3b0; 13. else samebit_cntsamebit_cnt1b1; end 14. else samebit_cntsamebit_cnt; 15. 16.//侦测到连续5个bit数值相同,按照协议要求,添加一个数值相反的位 17. reg stuff_valid; //填充位有效标识 18. always (posedge clk or negedge rst_n) 19.if(!rst_n) stuff_valid1b0; 20. else if((canrx_curstateSOF) ||(canrx_curstateID)|| 21. (canrx_curstateCONTROL)||(canrx_curstateCRC)) 22. begin if(samebit_cnt3d4) stuff_valid1b1; 23. else stuff_valid1b0; end 24. else stuff_valid1b0; 25. 26.//对每个bit进行计数,来判定当前处于接收的哪个数据段 27.//插入的填充位是每个数据段多余的数据,填充位不计数 28. always (posedge clk or negedge rst_n) 29.if(!rst_n) bit_cnt8b0; 30.else if(canrx_curstateIDLE) bit_cnt8b0; 31. else if(canrx_nextstate!canrx_curstate) bit_cnt8b0; 32. else if(clk_bit) 33. begin if(stuff_valid) bit_cntbit_cnt; 34. else bit_cntbit_cnt1b1; end 35. else bit_cntbit_cnt;[6]数值侦测位置按照协议要求定义在PBS1段的结尾时刻,也就是代码中定义的sample_point时刻.将数据帧中的ID识别出来,与FPGA预设的ID做对比.如果一致,那么按照协议由接收端反馈数据0(显性位)给发送端.如果不一致,则发送数据1(隐性位)给发送端,相当于释放掉总线.//接收11位ID数据 2. always (posedge clk or negedge rst_n) 3. if(!rst_n) local_id 11b0; 4. else if(canrx_curstateID) 5. begin if(sample_point) 6. case(bit_cnt) 7. 8d0:local_id[10]can_rx_b; 8. 8d1 :local_id[9]can_rx_b; 9. 8d2 :local_id[8]can_rx_b; 10. 8d3 :local_id[7]can_rx_b; 11. 8d4 :local_id[6]can_rx_b; 12. 8d5 :local_id[5]can_rx_b; 13. 8d6 :local_id[4]can_rx_b; 14. 8d7 :local_id[3]can_rx_b; 15. 8d8 :local_id[2]can_rx_b; 16. 8d9 :local_id[1]can_rx_b; 17. 8d10:local_id[0]can_rx_b; 18. default:local_idlocal_id; 19. endcase end 20. else local_id 11b0; 21. 22. //如果当前CAN总线上发送的ID不是FPGA预设ID则FPGA不响应ACK字段 23. //否则,在ACK字段发送0响应位 24. always (posedge clk or negedge rst_n) 25. if(!rst_n) can_tx 1b1; 26. else if((canrx_curstateACK)(bit_cnt8d0)) 27. begin if(!discard) can_tx 1b0; 28. else can_tx 1b1; end 29. else can_tx 1b1;[7]位同步代码.在前述章节中已经介绍过位同步的概念,以下是位同步操作实现的代码.1clk_cnt_tq是tq时长1us的时钟计数值.clk_tq是与tq同步的时钟标识.2pbs1_shift和pbs2_shift是当前位同步中PBS1段和PBS2段的移动值.当然,如果当前bit无需调整,则pbs1_shift和pbs2_shift均为0.3bit_time指的是当前bit的计时时长.正常情况下,bit_time是协议规定的SSPTSPBS1PBS210个tq.若需要位同步调整,则需要考虑到pbs1_shift和pbs2_shift的值.所以 bit_time1b1PTSPBS1pbs1_shiftPBS2-pbs2_shift. pbs1_shift和pbs2_shift的值只针对当前bit,当前bit结束后,就认为位同步操作完成, pbs1_shift和pbs2_shift的值回到0.如果后续bit仍然需要位同步操作,那么就再对pbs1_shift和pbs2_shift赋值.4依照协议图示,直接根据数据bit下降沿落在bit_time中哪个时间段来决定是否需要调整pbs1_shift和pbs2_shift的值.比如数据bit下降沿落在SS段内,则认为当前发送端与接收端位同步已经OK,无需调整.若数据bit下降沿落在PTSPBS1段则增加PBS1的值.在本代码中,每次只调整一个tq的时间值,就结束此次位同步调整.如果此刻位偏差比较大,调整一个tq不能完全补偿发送端和接收端的误差,那么在下一次位同步时还会侦测到不同步,此时再次进行调整.1. //tq时钟计数器 2. always (posedge clk or negedge rst_n) 3. if(!rst_n) clk_cnt_tq13b0; 4. else if((canrx_curstateIDLE)(canrx_nextstateIDLE)) clk_cnt_tq13b0; 5. else if((canrx_curstateIDLE)(canrx_nextstateSOF)) clk_cnt_tq(CNT_1US1); 6. else if(clk_cnt_tq(CNT_1US)) clk_cnt_tq13b0; 7. else clk_cnt_tqclk_cnt_tq1; 8. 9. //与每个tq同步的时钟 10. reg clk_tq; 11. always (posedge clk or negedge rst_n) 12. if(!rst_n) clk_tq1b0; 13. else if(clk_cnt_tqCNT_1US) clk_tq1b1; 14. else clk_tq1b0; 15. 16. //pbs1数据段的调整量 17. reg [1:0] pbs1_shift; 18. //pbs2数据段的调整量 19. reg [1:0] pbs2_shift; 20. 21. //经过位同步之后的当前bit的时间间隔, 22. //若无pbs1_shift,pbs2_shift那么bit_time为默认的10个tq时间 23. //若有pbs调整,那么当前的bit_time会有临时的增大或减少 24. reg [5:0] bit_time; 25. always (posedge clk or negedge rst_n) 26. if(!rst_n) bit_time6b0; 27. else if(clk_bit) bit_time1b1PTSPBS1pbs1_shiftPBS2-pbs2_shift; 28. else bit_timebit_time; 29. 30. //对每一个bit中的tq数量进行计数 31. always (posedge clk or negedge rst_n) 32. if(!rst_n) tq_cnt6b0; 33. else if(clk_cnt_tqCNT_1US) 34. begin if(tq_cntbit_time-1) tq_cnt6b0; 35. else tq_cnttq_cnt1b1; end 36. else tq_cnttq_cnt; 37. 38. //pbs1数据段的调整量 39. //按照协议要求,如果每个bit的起始沿(下降沿)没有落在ss区间,而是落在PTS或者PBS区间 40. //那么需要增加pbs1的时间,来补偿(准确定位),每个bit的起始和终止 41. always (posedge clk or negedge rst_n) 42. if(!rst_n) pbs1_shift4b0; 43. else if(can_rx_negedge) 44. begin if(tq_cnt0) pbs1_shiftpbs1_shift; 45. //代码设置每次只调整一个tq 46. else if((tq_cnt1b1)(tq_cnt1b1PTSPBS1)) pbs1_shiftpbs1_shift1b1; 47. else pbs1_shiftpbs1_shift; end 48. else if(clk_bit) pbs1_shift4b0; 49. else pbs1_shiftpbs1_shift; 50. 51. //pbs2数据段的调整量 52. //按照协议要求,如果每个bit的起始沿(下降沿)没有落在ss区间,而是落在PBS2区间 53. //那么需要减少pbs2的时间,来补偿(准确定位),每个bit的起始和终止 54. always (posedge clk or negedge rst_n) 55. if(!rst_n) pbs2_shift4b0; 56. else if(can_rx_negedge) 57. begin if(tq_cnt0) pbs2_shiftpbs2_shift; 58. else if(tq_cnt1b1PTSPBS1pbs1_shift) pbs2_shiftpbs2_shift1b1; 59. else pbs2_shiftpbs2_shift; end 60. else if(clk_bit) pbs2_shift4b0; 61. else pbs2_shiftpbs2_shift;(2)can_tx发送模块发送模块的设计逻辑比较清晰,完全按照协议定义的数据帧格式逐个数据bit发送.无需考虑位同步的[1]状态机的定义和参数定义.状态机按照数据帧的格式定义.1. localparam IDLE 4d0 ; //TX模块空闲 2. localparam SOF 4d1 ; //发送SOF数据 3. localparam ID 4d2 ; //发送识别段ID数据 4. localparam CONTROL 4d3 ; //发送控制段数据 5. localparam DATA 4d4 ; //发送数据段 6. localparam CRC 4d5 ; //发送CRC段数据 7. localparam ACK 4d6 ; //发送成功发送响应位 8. localparam EOF 4d7 ; //发送结束段数据 9. 10. localparam CNT_10US 13d499 ; //使用100Kps的CAN传输,每位10us 11. 12. //tx各数据段参数定义 13. localparam LOCAL_ID 11h2aa; 14. localparam ID_RTR 1b0 ; 15. localparam CONTROL_IDE 1b0 ; 16. localparam CONTROL_r0 1b0 ; 17. localparam CONTROL_DLC 4h1 ; 18. localparam DATA_TX 8h00 ; 19. localparam CRC_IDE 1b1 ;[2]填充位的处理与RX模组类似.比对前后两个bit的值,若相同,则samebit_cnt计数值递增.识别到待发送的数据出现连续5个相同值,意味着需要加入一个填充位,同时将代码中stuff_valid置1.当stuff_valid为1时:1数据bit的计数器对此位不计数.2暂停原本要输出的下一个bit数据,填充一个与前一位准位相反的数据.3对于填充位不进行CRC计算.1. //寄存前一个时刻的数值,目的在于计数数值相同数据位 2. reg can_tx_a; 3. always (posedge clk_bit or negedge rst_n) 4. if(!rst_n) can_tx_a1b1; 5. else can_tx_acan_tx; 6. 7. //按照协议规定,对连续数值相同的数据位计数 8. reg [2:0] samebit_cnt; 9. always (posedge clk or negedge rst_n) 10. if(!rst_n) samebit_cnt3b0; 11. else if(clk_cntCNT_10US) 12. begin if(can_tx!can_tx_a) samebit_cnt3b0; 13. else samebit_cntsamebit_cnt1b1; end 14. else samebit_cntsamebit_cnt; 15. 16. //连续相同的数值位到达5位,则在下一个位插入一个填充位 17. //填充位的数值与之前数值相反. 18. reg stuff_valid; //填充位有效标识 19. always (posedge clk or negedge rst_n) 20. if(!rst_n) stuff_valid1b0; 21. else if((cantx_curstateSOF)||(cantx_curstateID)||(cantx_curstateCONTROL)|| 22. (cantx_curstateDATA)||(cantx_curstateCRC)) 23. begin if(samebit_cnt3d4) stuff_valid1b1; 24. else stuff_valid1b0; end 25. else stuff_valid1b0; 26. 27. //对每个bit进行计数,定位当前发送的数据 28. //插入的填充位是每个数据段多余的数据,填充位不计数 29. reg [7:0] bit_cnt; 30. always (posedge clk or negedge rst_n) 31. if(!rst_n) bit_cnt8b0; 32. else if(cantx_curstateIDLE) bit_cnt8b0; 33. else if(cantx_nextstate!cantx_curstate) bit_cnt8b0; 34. else if(clk_cntCNT_10US) 35. begin if(stuff_valid) bit_cntbit_cnt; 36. else bit_cntbit_cnt1b1; end 37. else bit_cntbit_cnt;[3]状态机的跳转与赋值.状态机的跳转按照协议中定义的数据帧的位数直接跳转.对can_tx赋值时,首先判定当前bit是否为填充位,如果为填充位,就直接输出与前一位相反的数值.如果不是填充位,再根据数据位的定义输出数据.1. always (posedge clk or negedge rst_n) 2. if(!rst_n) cantx_curstateIDLE; else cantx_curstatecantx_nextstate; 3. 4. //判断状态转移条件 5. always (*) 6. begin 7. case(cantx_curstate) 8. IDLE: if(send_en) cantx_nextstateSOF; else cantx_nextstatecantx_curstate; 9. SOF: if(bit_cnt1) cantx_nextstateID; else cantx_nextstatecantx_curstate; 10. ID: if(bit_cnt12) cantx_nextstateCONTROL;else cantx_nextstatecantx_curstate; 11. CONTROL: if(bit_cnt6) cantx_nextstateDATA; else cantx_nextstatecantx_curstate; 12. DATA: if(bit_cnt8) cantx_nextstateCRC; else cantx_nextstatecantx_curstate; 13. CRC: if(bit_cnt16) cantx_nextstateACK; else cantx_nextstatecantx_curstate; 14. ACK: if(bit_cnt2) cantx_nextstateEOF; else cantx_nextstatecantx_curstate; 15. EOF: if(bit_cnt7) cantx_nextstateIDLE; else cantx_nextstatecantx_curstate; 16. default: cantx_nextstateIDLE; 17. endcase 18. end 19. 20. always (posedge clk or negedge rst_n) 21. if(!rst_n) can_tx1b1; 22. //若判定下一个数据位为填充位,则直接赋值填充位数据 23. //若不是填充位,则按照协议要求逐个发送数据 24. else if(stuff_valid) 25. can_tx!can_tx_a; //else can_txcan_tx;end 26. else case(cantx_curstate) 27. IDLE: can_tx1b1; 28. SOF: can_tx1b0; 29. ID: case(bit_cnt) 30. 0:can_txLOCAL_ID[10]; 31. 1:can_txLOCAL_ID[9] ; 32. 2:can_txLOCAL_ID[8] ; 33. 3:can_txLOCAL_ID[7] ; 34. 4:can_txLOCAL_ID[6] ; 35. 5:can_txLOCAL_ID[5] ; 36. 6:can_txLOCAL_ID[4] ; 37. 7:can_txLOCAL_ID[3] ; 38. 8:can_txLOCAL_ID[2] ; 39. 9:can_txLOCAL_ID[1] ; 40. 10:can_txLOCAL_ID[0] ; 41. 11:can_txID_RTR ; 42. default:can_txcan_tx ; 43. endcase 44. CONTROL: case(bit_cnt) 45. 0:can_txCONTROL_IDE; 46. 1:can_txCONTROL_r0 ; 47. 2:can_txCONTROL_DLC[3] ; 48. 3:can_txCONTROL_DLC[2] ; 49. 4:can_txCONTROL_DLC[1] ; 50. 5:can_txCONTROL_DLC[0] ; 51. default:can_txcan_tx ; 52. endcase 53. DATA: case(bit_cnt) 54. 0:can_txDATA_TX[7]; 55. 1:can_txDATA_TX[6]; 56. 2:can_txDATA_TX[5] ; 57. 3:can_txDATA_TX[4] ; 58. 4:can_txDATA_TX[3] ; 59. 5:can_txDATA_TX[2] ; 60. 6:can_txDATA_TX[1] ; 61. 7:can_txDATA_TX[0] ; 62. default: can_txcan_tx ; 63. endcase 64. CRC: case(bit_cnt) 65. 0:can_txcrc_value[14]; 66. 1:can_txcrc_value[13]; 67. 2:can_txcrc_value[12] ; 68. 3:can_txcrc_value[11] ; 69. 4:can_txcrc_value[10] ; 70. 5:can_txcrc_value[9] ; 71. 6:can_txcrc_value[8] ; 72. 7:can_txcrc_value[7] ; 73. 8:can_txcrc_value[6]; 74. 9:can_txcrc_value[5] ; 75. 10:can_txcrc_value[4] ; 76. 11:can_txcrc_value[3] ; 77. 12:can_txcrc_value[2] ; 78. 13:can_txcrc_value[1] ; 79. 14:can_txcrc_value[0] ; 80. 15:can_txCRC_IDE ; 81. default: can_txcan_tx ; 82. endcase 83. ACK: case(bit_cnt) 84. 0:can_tx1b1; 85. 1:can_tx1b1; 86. default: can_txcan_tx ; 87. endcase 88. EOF: can_tx1b1; 89. default: can_txcan_tx ; 90. endcase[4] CRC计算.CRC的计算范围从SOF到数据段.代码中的计算仿照CAN协议中推荐的算法.1. //CRC的计算范围从SOF到数据段,期间的填充位不进入CRC计算 2. reg [14:0] crc_value; 3. wire crc_next ; 4. wire [14:0] crc_temp ; 5. 6. //CRC计算的算法直接仿照CAN官方协议推荐 7. assign crc_nextcan_tx^crc_value[14]; 8. assign crc_temp{crc_value[13:0],1b0}; 9. 10. reg crc_en; 11. always (posedge clk or negedge rst_n) 12. if(!rst_n) crc_value15b0; 13. else if((crc_en)(clk_cnt(CNT_10US1))) 14. begin if(crc_next) crc_valuecrc_temp^15h4599; 15. else crc_valuecrc_temp; end 16. else if(cantx_curstateEOF) crc_value15b0; 17. else crc_valuecrc_value;(3)can_top顶层模块.[1]顶层模块例化了can_rx模块和can_tx模块.并设计了状态机完成先接收一个远程帧再发送一个数据帧的设计目标.[2]顶层模块的can_tx数据在不同的阶段分别有rx模块和tx模块分别控制.rx模块需要使用can_tx数据线发送接收反馈.1. can_rx can_rx_u 2. ( 3. .clk (clk) , //主时钟 4. .rst_n (rst_n) , //异步复位 5. .can_rx (can_rx) , //input数据信号:接收到CAN数据 6. .rx_on (rx_on) , //CAN RX模组正在接受标志 7. .can_rx_done (can_rx_done), //CAN RX模组接收完成 8. .can_tx (can_tx_ack) //CAN RX模组接收成功后响应位 9. ); 10. 11. reg send_en ; //CAN TX模组发送使能 12. wire can_tx_done ; //CAN TX模组发送完成标识 13. 14. can_tx can_tx_u 15. ( 16. .clk (clk) , //主时钟 17. .rst_n (rst_n) , //异步复位 18. .send_en (send_en) , //CAN TX模组发送使能 19. .can_tx (can_tx_send) , //CAN TX模组发送信号 20. .can_tx_done (can_tx_done) //CAN TX模组发送完成标识 21. ); 22. 23. //can_tx 信号在接收阶段和发送阶段分别有RX模组和TX模组的信号控制 24. assign can_tx(cantop_curstateRECEIVE)?can_tx_ack:can_tx_send; 25. 26. //顶层模块状态机定义 27. localparam IDLE 2d0 ; //CAN总线空闲状态 28. localparam RECEIVE 2d1 ; //FPGA正在接收CAN总线数据 29. localparam SEND 2d2 ; //FPGA正在向CAN总线发送数据 30. localparam INTERVAL 2d3 ; //FPGA发送与接收之间加入1ms间隔 31. 32. reg [1:0] cantop_curstate; 33. reg [1:0] cantop_nextstate; 34. 35. //时间间隔计数器,计数目标为设定值100us 36. reg [15:0] clk_cnt; 37. always (posedge clk or negedge rst_n) 38. if(!rst_n) clk_cnt16d0; 39. else if(cantop_curstateINTERVAL) clk_cntclk_cnt1d1; 40. else clk_cnt16d0; 41. 42. always (posedge clk or negedge rst_n) 43. if(!rst_n) cantop_curstateIDLE; else cantop_curstatecantop_nextstate; 44. 45. //判断状态转移条件 46. always (*) 47. begin 48. case(cantop_curstate) 49. //总线空闲状态,一旦RX模块开始接收则进入接收状态 50. IDLE: if(rx_on) cantop_nextstateRECEIVE; else cantop_nextstatecantop_curstate; 51. RECEIVE: if(can_rx_done) cantop_nextstateINTERVAL; else cantop_nextstatecantop_curstate; 52. INTERVAL: if(clk_cnt16d4_999) cantop_nextstateSEND; 53. else cantop_nextstatecantop_curstate; 54. SEND: if(can_tx_done) cantop_nextstateIDLE; else cantop_nextstatecantop_curstate; 55. default: cantop_nextstateIDLE; 56. endcase 57. end 58. 59. //切换到发送状态时,使能send_en 60. always (posedge clk or negedge rst_n) 61. if(!rst_n) send_en1b0; 62. else if((cantop_curstateINTERVAL)(cantop_nextstateSEND)) send_en1b1; 63. else send_en1b0;实例代码配套开发板,新品绝对低价,物超所值,技术客服全程支持,欢迎咨询.

相关新闻