基于STM32电赛训练平台的频率计设计
图1
1 项目简介
按照项目要求,本项目实现了利用STM32板上的高速比较器实现频率计的功能,并在OLED屏幕上显示出被测信号的频率。本项目使用C语言,通过对STM32处理器进行编程,使用固定的闸门时间对脉冲进行计数,就可以测量到实际信号的频率。获取到频率数值之后,通过对OLED屏幕编制驱动程序,将频率数值显示至屏幕上。即可基本完成项目需求了。
2 整体设计思路
通过对项目需求的分析,计划采用以下的方案完成频率计项目
2.1 使用STM32内部的定时器,产生一个固定的时间间隔,作为时钟“闸门”信号。本项目中使用了一个1秒的定时器间隔,作为闸门时间。
2.2 在闸门开启的这相对准确的1秒时间内,通过外部中断计数,来计算输入进来的脉冲个数。1秒内的脉冲个数就可以被定义为信号频率。
2.3 查找OLED屏幕规格书,了解相关OLED驱动芯片的资料、控制方式,编写对应的显示驱动程序。
2.4 编制OLED显示部分的应用层程序,完成字符、数字的显示功能。
2.5 在每秒闸门时间采样完成之后,将频率数值调用显示驱动显示至OLED屏幕上。
3 硬件资源介绍
本设计项目硬件上使用了STM32+iCE40核心板+电赛训练板底板两部分。
3.1 核心板硬件介绍
核心板包含两部分,基于Lattice的ICE40UP5K FPGA和STM32G031 MCU,除此之外,在核心板上面还板载了一颗LPC11U35处理器,来完成程序下载器的功能。我们可以通过单独一个USB-C接口进行FPGA的配置,并通过另一个虚拟串口与STM32G031完成通信。在烧录的时候,亦可以按下BOOT0所连接的按钮开关,进行STM32G031的下载。除此之外,板上还有RGB三色LED灯,四个单色LED,四个开关(以上都连接在FPGA上)在DIP40插针上,总计包含了36个IO用于扩展使用,其中14个连接STM32G031 芯片,另外的22根连接ICE40UP5K FPGA芯片。
3.2 底板硬件介绍
底板上为电赛扩展板,可以完成信号源、仪器仪表、控制以及信号处理类题目的训练。板上有通过两个16Pin的插座可以安装有高速ADC(左侧)、高速DAC(右侧)。除此之外板上安装了OLED显示屏、高速比较器、姿态传感器、旋转编码器以及按键等。
本次项目我的选题是频率计。需要使用到板子上的高速比较器,OLED显示屏这两部分外设。
图2
3.3 需要使用到的管脚
根据电路原理图可以看到有以下的管脚对应关系
红色为外界输入的频率信号。连接到核心板38号,连接到到MCU的PA1 管脚
蓝色为PWM输出信号,用来设置高速比较器的门限电压,连接到核心板35脚,MCU的PA6
绿色四个引脚连接到OLED,分别对应核心模块4/36/37/39,对应MCU的PA8/PA5/PA4/PA0
以上对应关系可以在STM32 CubeMX 程序内进行管脚分配。由CubeMX生成代码可以很方便的完成对应的管脚定义、功能设定,
图3
3.4 分配管脚思路
分配完毕管脚时候,需要预先定义频率计所使用的思路方法。本项目使用Systick作为系统计时的基准,在生成了1秒闸门时间之后,在这1秒内,使用外部中断方式对PA1外接引脚上面的脉冲进行计数。1秒内计算到的脉冲数量就是输入信号频率了。所以这里我们需要将PA1设置为外部中断输入。除此之外没有什么别的特殊需要考虑的地方。系统时钟设置为40M,外设时钟设置为20M。
4 STM32软件设计
在设计软件之前,需要对整个频率计项目有一个整体的规划。
4.1 闸门时钟设计
作为频率计,首先需要一个基准时间间隔,也叫做闸门时间。在成品频率计中,这个闸门时间一般是可以调节的。根据分辨率不同,闸门时间可以设置为0.1秒、1秒、10秒等数值。根据在这个闸门时间段之类接收到的脉冲数量不同,可以计算出不同精度的频率数值。本项目没有设计可变闸门时间,所以此处闸门时间设置为一个固定时间1秒。STM32产生固定时间的方案也有很多,设计本项目时间有点紧张了,所以我选择了比较简单的一个Systick作为产生1秒时钟的方法。Systick 是STM32内一个系统定时器,又名系统嘀嗒定时器,是一个 24 位的倒计数定时器,当计数到 0 时,将从 RELOAD 寄存器中自动重装载定时初值,开始新一轮计数。在CubeMX生成的代码中,它会被初始化为一个定时周期为1ms的定时器。可以使用HAL_GetTicks()函数很方便的获取当前Tick数值,并且将其与一个全局变量进行比较,即可判断时间流逝了多少ms. 本项目为了做1秒的延迟,这里使用了定时周期为1000作为对比,每当Systick计数达到1000个,时间流逝1秒。
4.2 脉冲计数设计
频率计的核心,是脉冲计数模块。这里也是我比较遗憾的地方,没能够使用通用定时器去完成这个功能。目前实现的方式,是使用外部中断的方式去实现了脉冲计数。具体地说,是设置了一个全局变量bCnt,在每次外部中断时候对其加一。这样就可以实现计算外部脉冲数量的功能了。对了,此处的脉冲数值需要在每秒闸门时间后进行清零。这样才能在下一次计算过程中被正确引用。
4.3 OLED驱动以及字符显示设计
最后简单说下OLED,作为显示模块,需要引入OLED驱动部分。本项目底板上集成了一块SSD1306 128*64 OLED 模块,SSD1306属于比较成熟的OLED模块了,通过浏览硬禾学堂之前的一些项目,我从中找到了对应的驱动部分, 并且成功合并入本项目中使用。包括IIC驱动以及字符转显示缓冲,还有字库部分。详细源码在附件压缩包内。
5 程序流程以及代码介绍
5.1 程序流程图分析
通过上面的对于频率计的功能分析,结合STM32 CubeMX生成的程序框架内,绘制程序流程图如下:
其中左侧为主程序流程图。右侧为中断服务程序内计数子程序流程图。
图4
5.2 各部分代码介绍
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM3_Init();
/* Initialize interrupts */
MX_NVIC_Init();
/* USER CODE BEGIN 2 */
OLED_Init();
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, 50);
Timer_Glb = HAL_GetTick();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if(HAL_GetTick() - Timer_Glb >1000)
{
Timer_Glb = HAL_GetTick();
OLED_Clear();
OLED_ShowString(20, 0 , "Frequency:");
OLED_ShowNum(20,2, bCnt,6,16);
OLED_ShowString(70, 2 , "Hz");
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
#if 0
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
HAL_Delay(500);
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
HAL_Delay(500);
#endif
bCnt = 0;
}
/* USER CODE END WHILE */
图五
首先程序初始化之后。使用这两句启动PWM发生器,产生一个可调的门限电压 HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, 50);
Timer_Glb是全局定时变量,用来存储systick数值,并且在对比数值大于1000时候进入判断。完成每秒一次的显示过程。
程序中OLED开头的四条语句是在主循环内完成OLED上频率显示的功能。
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_1)
{
bCnt++;
}
}
图6
这段代码内,是外部中断程序的回调函数,用来完成bCnt全局变量+1的任务。这代表每次外部脉冲到来,触发外部中断后,会有一个数值上的增加的动作。
5.3 程序占用空间以及资源使用
因为本项目全部使用STM32处理器来完成,并未使用到FPGA,所以此处无法贴图FPGA资源占用情况。这里截图了编译器在对STM32程序完成编译之后生成的资源占用情况。如下图
图7
6 遇到的问题与未来展望
通过完成这个项目,让我对于STM处理器的理解更深了一步。在开发过程中最开始是计划使用FPGA完成精确的定时,遇到的第一个问题是自己对于FPGA的开发设计也并不是很熟悉。因为时间有限,决定使用STM32处理器完成所有的功能模块。遇到的第二个问题是在STM32采集输入信号的脉冲的任务,使用了外部中断的方式去做,实际上我觉得采用计数器的方法去做是更好的,但是对于定时器的理解和使用还是有很多不足。未能实现。我相信再有一些时间的话,我应该能够更好的设计出一个使用硬件计数器来完成频率计任务的项目。
针对未来的项目进展,我需要进一步深入的学习FPGA的知识,下一步使用FPGA来完成频率计数等任务,使用STM32完成OLED显示任务。这样应该能获得更为精确的频率。
期待下一次能够完成的更好!