当前位置:   article > 正文

基于MCU和FPGA的DDS信号发生器——MCU与FPGA通信部分_mcu dds

mcu dds

前言

由于项目制作时间有限,考虑到改变方案的风险,我们在遇到许多问题时并没有选择改变路线,而是在现有成果上缝缝补补,造就了现在看来十分笨重的通信模块,不过错误也是宝贵的学习经验,对于电子领域的工作者更是如此,因而笔者保留了我们制作时的失误和思考历程,供广大读者参考借鉴。

总体思路

一般FPGA不适合作为一个完整系统,因为FPGA更擅长流水处理,而不擅长控制,并且资源有限,像DDS信号发生器这种需要多个IP核的项目,on chip memory很容易写满。因而我们选择使用MCU作为控制端,一方面减轻FPGA负担,另一方面可以利用MCU的OLED外设提供用户交互界面。

FPGA需要从MCU接收10k到10M的载波信号频率,1k到5k调制信号频率,20到100调幅系数,10到20调频系数,五种波形选择,总计五个数据。分别对应24位、13位,8位,7位,3位二进制数。我们选用的UART配置为9600的波特率,一次发送一个起始位,八个数据位,一个终止位,无校验位。

我们在初步测试时,利用singal tap发现接收FPGA接收到许多杂乱无章的数据(后来发现时MCU发送端接错了管脚。。。),当时初步判断是噪声干扰,因而之后的绝大部分工作都花在了排除噪声上。当时已经写好的FPGA接收代码和MCU发送代码都没有加校验位,因而我们提出的排除噪声的方案是:FPGA添加一个FIFO模块用来暂时储存MCU发送过来的数据,MCU在用户输入了所需的所有数据后,将所有数据打包成十个八位码元连续不间断发送,这十个码元中只有中间八个是数据位(24位的载波信号频率需要三个八位码元发送,以此类推,五种数据共需要八个码元),前后两位都固定发送0xff,当且仅当FPGA接收到首尾都为0xff的数据时,才进行拆包,一点出现噪声,首尾的数据将不再是0xff,如此就有效避免了噪声的干扰。

FPGA接收部分

UART接收部分

Verilog代码

端口及变量定义

  1. module uart1(clk,rst,uart_rx,r_rx_data,rx_done);
  2. input clk;
  3. input rst;
  4. input uart_rx;//输入信号
  5. output reg rx_done;//接收完成标志
  6. output reg [7:0]r_rx_data;//接收到的数据
  7. parameter clk_fre = 50000000;
  8. parameter baud = 9600;//接受信号波特率
  9. parameter MCNT_BAUD = clk_fre / baud - 1;//波特率计数最大值
  10. reg [29:0]baud_div_cnt;//波特率计数
  11. reg en_baud_cnt;//波特率计数使能
  12. reg [3:0]bit_cnt;//位计数
  13. reg [7:0]rx_data;//接收数据暂存
  14. reg r_uart_rx;//最终接收数据
  15. reg dff0_uart_rx;
  16. reg dff1_uart_rx;//打拍
  17. wire negedge_uart_rx;//下降沿标志,用于检测数据起始位
  18. wire w_rx_done;//接收完成标志

波特率计数模块

  1. always @(posedge clk)
  2. dff0_uart_rx <= uart_rx;
  3. always @(posedge clk)
  4. dff1_uart_rx <= dff0_uart_rx;//若在时钟上升沿附近uart_rx触发则会出现亚稳态问题,故进行两次同步,以将uart_rx同步到clk时钟域上,俗称打拍
  5. always @(posedge clk)
  6. r_uart_rx <= dff1_uart_rx;//相当于一个D触发器,暂存当前状态
  7. assign negedge_uart_rx = ((dff1_uart_rx == 0) && (r_uart_rx == 1));

 打拍及下降沿判断

  1. always @(posedge clk or negedge rst)//波特率计数模块
  2. if(!rst)
  3. baud_div_cnt <= 0;
  4. else if(en_baud_cnt)
  5. begin
  6. if(baud_div_cnt == MCNT_BAUD)
  7. baud_div_cnt <= 0;
  8. else
  9. baud_div_cnt <= baud_div_cnt + 1'd1;
  10. end
  11. else
  12. baud_div_cnt <= 0;

波特率计数使能模块

  1. always @(posedge clk or negedge rst)
  2. if(!rst)
  3. en_baud_cnt <= 0;
  4. else if(negedge_uart_rx)
  5. en_baud_cnt <= 1;
  6. else if((baud_div_cnt == MCNT_BAUD/2) && (bit_cnt == 0) && (dff1_uart_rx == 1))//如果是毛刺则停止计数
  7. en_baud_cnt <= 0;
  8. else if((baud_div_cnt == MCNT_BAUD) && (bit_cnt == 9))//计数完成清零

位计数模块

  1. always @(posedge clk or negedge rst)//位计数器模块
  2. if(!rst)
  3. bit_cnt <= 0;
  4. else if(baud_div_cnt == MCNT_BAUD)
  5. begin
  6. if(bit_cnt == 9)
  7. bit_cnt <= 0;
  8. else
  9. bit_cnt <= bit_cnt + 1'd1;
  10. end

位接收模块 

  1. always @(posedge clk or negedge rst)//位接受逻辑
  2. if(!rst)
  3. rx_data <= 8'd0;
  4. else if(baud_div_cnt == MCNT_BAUD/2)
  5. begin
  6. case(bit_cnt)
  7. 1:rx_data[0] <= dff1_uart_rx;
  8. 2:rx_data[1] <= dff1_uart_rx;
  9. 3:rx_data[2] <= dff1_uart_rx;
  10. 4:rx_data[3] <= dff1_uart_rx;
  11. 5:rx_data[4] <= dff1_uart_rx;
  12. 6:rx_data[5] <= dff1_uart_rx;
  13. 7:rx_data[6] <= dff1_uart_rx;
  14. 8:rx_data[7] <= dff1_uart_rx;
  15. //8:rx_data[8] <= dff1_uart_rx;
  16. default:rx_data<=rx_data;
  17. endcase
  18. end

接收完成逻辑

  1. assign w_rx_done = (baud_div_cnt == MCNT_BAUD) && (bit_cnt == 9);
  2. always @(posedge clk)//接受完成标志信号
  3. rx_done <= w_rx_done;
  4. always @(posedge clk)
  5. if(w_rx_done)
  6. r_rx_data<=rx_data;
串口仿真

用test bench写的,大概修改了一下

  1. timescale 1 ns/ 1 ns
  2. module uart1_vlg_tst();
  3. // constants
  4. // general purpose registers
  5. reg eachvec;
  6. // test vector input registers
  7. reg clk;
  8. reg rst;
  9. reg uart_rx;
  10. // wires
  11. wire [7:0] rx_data;
  12. wire rx_done;
  13. // assign statements (if any)
  14. uart1 i1 (
  15. // port map - connection between master ports and signals/registers
  16. .clk(clk),
  17. .rst(rst),
  18. .rx_data(rx_data),
  19. .rx_done(rx_done),
  20. .uart_rx(uart_rx)
  21. );
  22. initial clk = 1;
  23. always #10 clk = ~clk;
  24. initial
  25. begin
  26. clk = 1;
  27. rst = 0;
  28. uart_rx = 1;
  29. #201;
  30. rst = 1;
  31. #200;
  32. //8'b01010101
  33. uart_rx = 0; #(5208*20);
  34. uart_rx = 1; #(5208*20);
  35. uart_rx = 0; #(5208*20);
  36. uart_rx = 1; #(5208*20);
  37. uart_rx = 0; #(5208*20);
  38. uart_rx = 1; #(5208*20);
  39. uart_rx = 0; #(5208*20);
  40. uart_rx = 1; #(5208*20);
  41. uart_rx = 1; #(5208*20);
  42. #(5208*20*10);
  43. uart_rx = 0; #(5208*20);
  44. uart_rx = 1; #(5208*20);
  45. uart_rx = 1; #(5208*20);
  46. uart_rx = 1; #(5208*20);
  47. uart_rx = 1; #(5208*20);
  48. uart_rx = 1; #(5208*20);
  49. uart_rx = 0; #(5208*20);
  50. uart_rx = 1; #(5208*20);
  51. uart_rx = 1; #(5208*20);
  52. #(5208*20*10);
  53. uart_rx = 0; #(5208*20);
  54. uart_rx = 1; #(5208*20);
  55. uart_rx = 1; #(5208*20);
  56. uart_rx = 1; #(5208*20);
  57. uart_rx = 0; #(5208*20);
  58. uart_rx = 1; #(5208*20);
  59. uart_rx = 0; #(5208*20);
  60. uart_rx = 1; #(5208*20);
  61. uart_rx = 1; #(5208*20);
  62. #(5208*20*10);
  63. uart_rx = 0; #(5208*20);
  64. uart_rx = 1; #(5208*20);
  65. uart_rx = 1; #(5208*20);
  66. uart_rx = 0; #(5208*20);
  67. uart_rx = 0; #(5208*20);
  68. uart_rx = 1; #(5208*20);
  69. uart_rx = 0; #(5208*20);
  70. uart_rx = 1; #(5208*20);
  71. uart_rx = 1; #(5208*20);
  72. #(5208*20*10);
  73. uart_rx = 0; #(5208*20);
  74. uart_rx = 0; #(5208*20);
  75. uart_rx = 1; #(5208*20);
  76. uart_rx = 1; #(5208*20);
  77. uart_rx = 0; #(5208*20);
  78. uart_rx = 1; #(5208*20);
  79. uart_rx = 0; #(5208*20);
  80. uart_rx = 1; #(5208*20);
  81. uart_rx = 1; #(5208*20);
  82. #(5208*20*10);
  83. // code that executes only once
  84. // insert code here --> begin
  85. // --> end
  86. $display("Running testbench");
  87. end
  88. /*always
  89. // optional sensitivity list
  90. // @(event1 or event2 or .... eventn)
  91. begin
  92. // code executes for every event on sensitivity list
  93. // insert code here --> begin
  94. @eachvec;
  95. // --> end
  96. end */
  97. endmodule

仿真波形

FIFO IP核

http://t.csdnimg.cn/g1y0F

这篇文章中有非常详细的IP核配置教程,我们选用普通单时钟模式

端口定义以及变量使用

  1. module decide
  2. (
  3. input clk,
  4. input rst,
  5. input uart_tx, // 输入信号
  6. input data_valid,
  7. output reg [23:0] car_wave_fre, // 10k-10M载波信号频率
  8. output reg [12:0] mod_wave_fre, // 1k-5k调制信号频率
  9. output reg [7:0] ma, // 20-100调幅系数
  10. output reg [6:0] kf, // 10-50调频系数
  11. output reg [2:0] wave_select // 信号选择
  12. );
  13. uart_rx uart_rx_inst
  14. (
  15. .clk(clk),
  16. rst_n(rst),
  17. .TX(data),
  18. .RX(uart_rx),
  19. .done(data_valid),
  20. );
  21. // FIFO实例化
  22. wire rdreq, wrreq, empty, full;
  23. wire [7:0] q;
  24. wire [4:0] usedw;
  25. wire [7:0] data;//输入FPGA的数据
  26. FIFO FIFO_normal_inst (
  27. .clock(clk),
  28. .data(uart_tx),
  29. .rdreq(rdreq), //读使能
  30. .wrreq(!empty && !full), // 写入使能
  31. .empty(empty), //读空标志
  32. .full(full), //写满标志
  33. .q(q),
  34. .usedw(usedw) //当前存储了多少字符
  35. );
  36. reg [7:0] buffer[9:0]; // 用于储存数据
  37. reg [1:0] index; // 用于追踪当前储存的数据在数组中的位置
  38. reg packet_start; // 检测到0xff,开始
  39. reg valid_packet;

 具体代码

  1. always @(posedge clk or posedge rst)
  2. begin
  3. if (rst)
  4. begin
  5. index <= 0;
  6. packet_start <= 0;
  7. valid_packet <= 0;
  8. end else
  9. begin
  10. // 接收了十个数据时进行检测
  11. if (index == 9)
  12. begin
  13. if (buffer[0] == 8'hff && buffer[9] == 8'hff)
  14. begin
  15. valid_packet <= 1; // Valid packet detected
  16. car_wave_fre <= {buffer[1],buffer[2],buffer[3]};
  17. mod_wave_fre <= {buffer[4],buffer[5]};
  18. ma <= buffer[6];
  19. kf <= buffer[7];
  20. wave_select <= buffer[8];
  21. end else
  22. begin
  23. valid_packet <= 0; // 如果不满足规定的首位都为0xff的条件,停止传输
  24. end
  25. index <= 0;
  26. packet_start <= 0;
  27. end else if (data_valid)
  28. begin
  29. if (!packet_start && uart_tx == 8'hff)
  30. begin
  31. packet_start <= 1; // 有效数据检测到
  32. index <= 0;
  33. end
  34. if (packet_start)
  35. begin
  36. buffer[index] <= q; // 将数据存在数组中
  37. index <= index + 1; // 当前数组位数
  38. end
  39. end
  40. end
  41. end
  42. endmodule

 MCU发送部分

本项目使用的是MSPM01306单片机

sysconfig配置

需要注意的一点,在Advanced Configuration中Oversampling选择16x保证传输精度,并且选择PA11,PA10这一组管脚

 

发送代码

由于主程序中涉及到OLED显示等库函数,非笔者所写,故在此仅分享发送函数和矩阵键盘函数

发送函数。值得注意的是,UART只能发送二进制数,故我们需要将用户输入的十进制数先转化为二进制数,并且将其分为若干段八位二进制数再进行发送,为了节省CPU资源(曾经我也认为MCU不需要节省CPU资源,直到有一次我在代码里写了指针,编译花了整整一分钟),我们使用逻辑右移运算符,并且与0xff进行位与运算,由此得到我们想要的八位数据

  1. void transmit()
  2. {
  3. unsigned char byte1 = (csfre >> 16) & 0xFF; // 次高8位
  4. unsigned char byte2 = (csfre >> 8) & 0xFF; // 次低8位
  5. unsigned char byte3 = csfre & 0xFF; // 最低8
  6. unsigned char byte4 = (msfre >> 8) & 0xFF; // 次低8位
  7. unsigned char byte5 = msfre & 0xFF; // 最低8
  8. unsigned char byte6 = ma & 0xFF; // 最低8
  9. unsigned char byte7 = kf & 0xFF; // 最低8
  10. unsigned char byte8 = wave_select & 0xFF;
  11. // 最低8
  12. DL_UART_Main_transmitData(UART1,0xff);
  13. delay(1);
  14. DL_UART_Main_transmitData(UART1,byte1);
  15. delay(1);
  16. DL_UART_Main_transmitData(UART1,byte2);
  17. delay(1);
  18. DL_UART_Main_transmitData(UART1,byte3);
  19. delay(1);
  20. DL_UART_Main_transmitData(UART1,byte4);
  21. delay(1);
  22. DL_UART_Main_transmitData(UART1,byte5);
  23. delay(1);
  24. DL_UART_Main_transmitData(UART1,byte6);
  25. delay(1);
  26. DL_UART_Main_transmitData(UART1,byte7);
  27. delay(1);
  28. DL_UART_Main_transmitData(UART1,byte8);
  29. delay(1);
  30. DL_UART_Main_transmitData(UART1,0xff);
  31. delay(1);
  32. }

delay函数。时钟频率为32M

  1. void delay(int x)
  2. {
  3. delay_cycles(CLK_HZ / 1000 * x);
  4. }

矩阵键盘函数

  1. uint32_t Key()
  2. {
  3. uint8_t a =15;
  4. static uint8_t flag = 0;
  5. if (flag)
  6. {
  7. delay(300);
  8. flag = 0;
  9. }
  10. DL_GPIO_clearPins(MAT_PORT, MAT_ROW1_PIN);
  11. DL_GPIO_setPins(MAT_PORT, MAT_ROW2_PIN |MAT_ROW3_PIN | MAT_ROW4_PIN);
  12. delay(10);
  13. if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL1_PIN)))
  14. {
  15. a = 1;
  16. flag = 1;
  17. return a;
  18. }
  19. else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL2_PIN)))
  20. {
  21. a = 2;
  22. flag = 1;
  23. return a;
  24. }
  25. else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL3_PIN)))
  26. {
  27. a = 3;
  28. flag = 1;
  29. return a;
  30. }
  31. else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL4_PIN)))
  32. {
  33. a = 4;
  34. flag = 1;
  35. return a;
  36. }
  37. DL_GPIO_clearPins(MAT_PORT, MAT_ROW2_PIN);
  38. DL_GPIO_setPins(MAT_PORT, MAT_ROW1_PIN |MAT_ROW3_PIN | MAT_ROW4_PIN);
  39. delay(10);
  40. if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL1_PIN)))
  41. {
  42. a = 5;
  43. flag = 1;
  44. return a;
  45. }
  46. else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL2_PIN)))
  47. {
  48. a = 6;
  49. flag = 1;
  50. return a;
  51. }
  52. else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL3_PIN)))
  53. {
  54. a = 7;
  55. flag = 1;
  56. return a;
  57. }
  58. else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL4_PIN)))
  59. {
  60. a = 8;
  61. flag = 1;
  62. return a;
  63. }
  64. // Row 4
  65. DL_GPIO_clearPins(MAT_PORT, MAT_ROW3_PIN);
  66. DL_GPIO_setPins(MAT_PORT, MAT_ROW1_PIN |MAT_ROW2_PIN | MAT_ROW4_PIN);
  67. delay(10);
  68. if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL1_PIN)))
  69. {
  70. a = 9;
  71. flag = 1;
  72. return a;
  73. }
  74. else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL2_PIN)))
  75. {
  76. a = 10;
  77. flag = 1;
  78. return a;
  79. }
  80. else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL3_PIN)))
  81. {
  82. a = 11;
  83. flag = 1;
  84. return a;
  85. }
  86. else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL4_PIN)))
  87. {
  88. a = 12;
  89. flag = 1;
  90. return a;
  91. }
  92. DL_GPIO_clearPins(MAT_PORT, MAT_ROW4_PIN);
  93. DL_GPIO_setPins(MAT_PORT, MAT_ROW1_PIN |MAT_ROW2_PIN | MAT_ROW3_PIN);
  94. delay(10);
  95. if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL1_PIN)))
  96. {
  97. a = 13;
  98. flag = 1;
  99. return a;
  100. }
  101. else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL2_PIN)))
  102. {
  103. a = 14;
  104. flag = 1;
  105. return a;
  106. }
  107. else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL3_PIN)))
  108. {
  109. a = 15;
  110. flag = 1;
  111. return a;
  112. }
  113. else if (!(DL_GPIO_readPins(MAT_PORT, MAT_COL4_PIN)))
  114. {
  115. a = 0;
  116. flag = 1;
  117. return a;
  118. }
  119. return a;
  120. }

矩阵键盘sysconfig配置

行设置,我们只需要将PORT设置为PORTA,Direction设为output,initial value设为set,并在Assigned Pin中配置相应管脚

列设置,仅需要将Direction改为intput,并配置相应管脚

结语

 现在看来,UART所使用的TTL电平已经可以有效的排除噪声的干扰,也就是说,将所有数据收集好后再进行发送是十分多余的,这使得用户想改变一种数据时都要重新输入所有数据。希望读者阅读完这篇文章后能够有所启发,设计出更为简单高效的通信系统

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/841060
推荐阅读
相关标签
  

闽ICP备14008679号