2023寒假在家练(2)基于STM32+iCE40的电赛训练平台制作频率计
利用STM32单片机和外部高速比较器制作频率计(测试信号FPGA产生的DDS信号)
标签
嵌入式系统
FPGA
DDS
2023寒假在家练
野生阿波
更新2023-03-28
河南城建学院
1029

一、项目要求

利用板上的高速比较器和STM32代码实现频率计的功能,并在OLED屏幕上显示出被测信号的频率。

二、设计思路

本次设计采用了硬禾学堂推出的STM32+iCE40的电赛训练平台。板载有一块STM32G031的芯片和外部的高速比较器,这两个模块是制作本次频率计的核心,单片机可以利用板载的捕获计数器来捕获边缘变化从而计算信号频率,但是直接测量频率仅限于有明确边缘变化的矩形波,对于正弦波、三角波等等边缘变化不明显的信号就不适用了,这就用到了我们板载的外部高速比较器,利用单片机输出的PWM波作为比较信号,将各种波形统一转化为矩形波信号,如此一来就可以用来检测任意信号的频率了。当然既然是要测频率,那么就必须要有信号。为了形成对比本次测试信号采用两个信号,分别是板载FPGA生成的DDS正弦波信号和函数信号发生器所产生的正弦波信号。

wps

三、程序设计(STM32、FPGA)

(一)、STM32部分

   1、参数配置:本次设计使用的设计软件为STM32CubeIDE,采用的配置方式为图形化配置。所使用的的功能有硬件SPI及OLED、UART、捕获计时器、PWM输出等功能。其配置页面如下图所示。

A+WtQqWdKpNkAAAAAElFTkSuQmCC

2、时钟树配置:本次设计需要产生高频的PWM波信号,所以主频采用STM32G031所能达到的最大值64MHz,配置页面比较简单就不在此展示了。

3、捕获计数器配置及计算程序:将TIM2的通道1配置为input capture direct mode打开其中断,根据需要配置分频倍数。计算原理是利用两个上升沿之间的时间差t和计数器的计数周期来计算频率,如下图所示。

clmOgoAAAAASUVORK5CYII=

在回调函数中计算频率数据,t为两上升沿间时间差f为计算出的频率值,代码如下所示:

void HAL_TIM_IC_CaptureCallback (TIM_HandleTypeDef *htim)
{
	if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_2)
	{
	t=HAL_TIM_ReadCapturedValue (&htim2, TIM_CHANNEL_2);
	if(t!=0)
	{
			__HAL_TIM_SetCounter(&htim2,0);
		f=1000000/t;

	}
  }

4、PWM波输出配置

由于PWM信号的用处是设置比较信号,当一个信号频率足够大通过低通滤波器就可以输出一个模拟量,因此需要频率远大于截止频率,占空比用来设置比较电压。我采用的频率为最大芯片的最大频率64MHz。本来想采用重装载值为2,比较值为1,即32MHz且占空比为50%的PWM信号,但是发现无法驱动,相对过高的频率也会出现幅值的严重失真,最后选择重装载值为7,比较值为4,即8MHz的PWM信号,虽说也还是有些失真但是还能用。PWM配置代码及波形图如下。

void MX_TIM3_Init(void)
{

  /* USER CODE BEGIN TIM3_Init 0 */

  /* USER CODE END TIM3_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};

  /* USER CODE BEGIN TIM3_Init 1 */

  /* USER CODE END TIM3_Init 1 */
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 0;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 7;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 4;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM3_Init 2 */

  /* USER CODE END TIM3_Init 2 */
  HAL_TIM_MspPostInit(&htim3);

}

LDZ3%90PGO5(9T6G@}QOHZ4

5、OLED配置代码

OLED部分主要是移植比较复杂,而且为了达到最好的传输效果,我们还需要将软件SPI通信改为硬件SPI通信。由于我们所传输数据的对象为OLED,所以选择主机仅传输模式,如下图所示。而且需要将各IO口速率调为High,不然传输会出现一些问题。移植灵感来源于咱硬禾学堂的一场直播课程。链接我就放这里了希望对大家能有一些帮助https://class.eetree.cn/detail/l_61cea60ee4b01af4136f8138/4?from=p_61cea3f4e4b0a91144b07841&content_app_id=&type=6

j+AIx7L42bqrQAAAABJRU5ErkJggg==

OLED的主要修改代码就是这个OLED_WR_Byte函数,当我们仔细观察下面的各种功能性函数就会发现,所有的代码和函数都是由这个函数串起来的。当我们能对这个函数进行修改与操作时,就可以在任意开发板上移植这块SPI驱动的OLED屏幕了。

void OLED_WR_Byte(u8 dat, u8 cmd) {
   // u8 i;
    if (cmd)
        OLED_DC_Set();
    else
        OLED_DC_Clr();
    HAL_SPI_Transmit(&hspi2, &dat, 1, HAL_MAX_DELAY);

    OLED_DC_Set();
}

(二)、FPGA部分

1、DDS原理:DDS的原理还是比较好理解的,就是把函数表的数据存入ROM,然后利用DAC将这些数据输出,例如本次的的10位ADC,0-1023就对应着0-3.3V。只要能控制这些数据的大小和输出顺序,就能实现任意波形的输出。不仅仅限于正弦波、三角波、矩形波,你甚至可以输出你想输出的任何图案。当然DDS还有一个特点就是频率可调,说白了频率可调的原理就是“间隔输出”,假设这个信号有10个点1、2、3、4、5、6、7、8、9、10,当我们逐个输出时,输出一整个周期就需要10个时钟周期,就姑且先定义这个信号的频率为X吧。那么当我们间隔一位输出就会出现1、3、5、7、9这样输出一个周期就仅需要5个时钟周期,这样频率也就提升了一倍即为2X,当然也不用担心信号输出会不会失真,因为我们采样的时候一个周期肯定有成千上万个点。因而当我们选择不同的“间隔”时就能实现任意频率信号的输出了,我第一次理解这个原理的时候可以说是相当兴奋的。

2、函数表的获取:在这里我们采用MATLAB来生成采样文件,但是注意这个软件所需的是hex文件,说白了也就是要一个16进制的数值表,当我们使用10进制输出的文件是也就是mif文件,输出的函数就会凌乱不堪,接下来是MATLAB生成该文件的代码:

clc;
clear all;
F1=1;
Fs=2^11;
P1=0;
N=2^11;
t=[0:1/Fs:(N-1)/Fs];
ADC=2^9-1;
A=2^9;
%正弦信号
s=A*sin(2*pi*F1*t + pi*P1/180)+ADC;
%创建hex文件
fild=fopen('sin_wave_2048x10.hex','wt');

for i=1:N
    s0(i)=round(s(i));
    if s0(i)<0
        s0(i)=0
    end
   fprintf(fild,'%X\n',s0(i));
end
  fclose(fild);
  plot(s);  

3、IP核配置,这里对我来说是最困难的了,因为这个软件也就是Lattice Radiant是我第一次接触,可以说是举步维艰。但是我也知道学习软件绝对是将来最重要的技能之一,配置页面如下

wPaXUd8tKXKawAAAABJRU5ErkJggg==

4.为了测试频率计的准确性,我选择采用按键来控制频率,也就是通过按键来控制频率参数代码如下。

always @(posedge clk or negedge rst)
	begin
		if(!rst)
		   begin
		    F1<=32'd178956;
		   end
		else if(key1[0]==1)
		   begin
		   F1<=F1+32'd17895;
		   end
		else if(key1[1]==1)
			begin
			F1<=F1-32'd17895;
			end
		else if(key1[2]==1)
			begin
			F1<=32'd178956;
			end
		else if(F1<1000)
			begin
			F1<=32'd178956;	
		    end
				
		else
			begin
			F1<=F1 ;
			end
	end

四、所遇问题与解决方法

(一)、误差部分

在演示视频中可以看到在使用PWM波驱动高速比较器时产生了较大的误差达到了7-8%,我感觉代码方面应该没有问题,于是我开始一点一点寻找原因。我一点一点排除问题,直到突然有一个想法,是不是比较信号不太稳定导致的,于是引入一个外部的比较信号(1.5V直流信号)。然后误差骤降到达了1-2%,当我发现了问题所在我就将输入比较器前的信号与比较器接入MCU的信号用示波器进行检测,效果图如下。

VY$30FZS$TYAFUB$_C}{X6O

我就发现在比较值附近出现了一个抖动,也就是这个抖动导致了信号出现巨大的误差。继续追溯原因,寻找这个抖动的来源,我发现引入的直流信号波动在10mV以内,PWM波驱动的模拟信号并非一个很稳定的直流信号,其波动达到了40mV-50mV之间。所以这个误差出现的原因我认为是PWM频率输出不够,虽然超过了截止频率但是交流信号并未衰减为0。

(二)、其余部分

本次设计遇到的问题可谓是相当的多,在软件下载、选题、原理理解、程序书写、调试等等各方面都给我了我一个全方面打击。首先就从软件下载开始说吧,我下载软件后按照视频上申请证书,结果怎么申请都无法使用,我把能用的网络地址都用了,我都以为我要出师未捷先倒在软件上了。然后在我走投无路的时候选择在群里进行提问,结果出乎所料很多大佬来给我解答,当时我的心情相当温暖与感激。最后发现我申请错了,Diamond软件和Radiant软件的申请页面非常相近,并且一模一样。我申请了Diamond证书自然也就没法在Radiant上使用。虽然这个错误很生草且荒谬,但是实实在在发生了而且对我来说是个莫大的难题。这也让我认识到细心的重要性。然后是原理的理解,就是哪个PWM波和高速比较器的结合,我并不知道PWM改怎么比较去比较,一般的比较都是一个固定的模拟量。然后我就去群里提问,再次感谢大佬们帮忙,我了解到当PWM波的频率足够大时,当其通过低通滤波器的时候就会输出一个模拟量,其占空比就是模拟量大小,可以说是增长了我的知识面了。最后就是Radiant中IP核程序的调用了,这个可谓是我遇到的最大困难没有之一,近乎让我的项目就此夭折。由于没有教程不知道怎么下手,真的可以说是走投无路了,这个框是干嘛的,哪个符号是干嘛的可以说是一窍不通,我之前学习的FPGA是利用的quartus软件,这俩软件的IP核配置完全不一样,内部参数也不一样可以说是让我对照都没得对照。我真的没办法了差一点就放弃了。最后我决定拼一把,我开始看说明书,由于英语不好所以只能一点一点翻译,但是最后翻译出来的过程相当简略对于我来说完全没用,可以说是让我失望至极,于是一切回到原点。然后我就开始疯狂尝试,真的就一点一点试,一个参数一个参数的调,由于还不会仿真只能用我之前制作的示波器进行实机检测。每个参数、每个文件、每个选项我都进行了一遍尝试。也好在花了4个通宵,下载了成百上千次终于出波形了。当看到效果出来时我都从床上跳起来。也好在除了效果不然我感觉我都快崩溃了。可以说这次对我最大的帮助就是让我知道了尝试与坚持的重要性,如果害怕困难,浅尝辄止那么动不动就放弃那么将一事无成。

五、设计心得

这次是我第二次参加寒假一起练这个项目了,第一次是大一那时候我甚至连C语言都不会,就在老师的鼓励下开始学习FPGA。可以说就是在这里开始了我的启蒙第一课,至今我都记忆犹新。那次我花了整整一个半月才堪堪制作出了一个勉勉强强的项目,但是我仍然自豪万分,因为我做出来了,我做出了我人生的第一个项目,同时也开启了我的竞赛之路。这次这个项目前前后后一共做了9天,而且是32和FPGA的联合项目可以说是难度较上次来说有了很大的提升,对我来说也是一个很大的挑战。我其实一直都想将FPGA和STM32结合起来,但是不知道怎么办,网上也没有相关教程,可以说本次项目对我来说恰好开拓了我想开拓的视野,虽说我这次制作的是频率计,剩下的几个项目我也会自己尝试去做出来,特别是SPI通信的部分。这两次项目都给了我莫大的提升,再此对咱平台表达感谢!希望咱平台能越做越好!

 

附件下载
PLJ.hex
STM32可下载文件
led2_impl_1.rbt
FPGA可下载文件
丁文波2023寒假在家练源码.zip
源码
团队介绍
丁文波 河南城建学院
团队成员
野生阿波
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号