项目描述:
该项目是一个基于STM32+iCE40的电赛训练平台制作的频率计,利用板载的STM32G031G8U6作为主控,通过板载的高速比较器作为波形输入的整形,通过STM32捕获信号,进行波形测量并在OLED上进行显示。
任务要求:
利用STM32+iCE40的电赛训练平台制作的一款频率计,实现对输入波的频率的测量。
设计思路:
该频率计通过STM32的TIM3 CH1输出一段pwm波给高速比较器,与输入的波形进行比较,整形,输出一段方波给PA1引脚。波形的捕获采用上升沿中断触发,以1s作为周期进行计数。计数后将结果在OLED屏幕上呈现。OLED使用软件模拟SPI进行驱动。
设计框图:
程序流程图:
MCU配置展示:
由于OLED的驱动采用软件模拟SPI,故在工程文件的配置中只需开四个GPIO口即可
PA0 -- SCL
PA4 -- SDA
PA5 -- RES
PA8 -- DC
主函数代码:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM3_Init();
/* Initialize interrupts */
MX_NVIC_Init();
/* USER CODE BEGIN 2 */
OLED_Init();
OLED_ColorTurn(1);
OLED_DisplayTurn(0);
OLED_ShowString(0,0,"Freq Counter/Hz",16);
OLED_ShowString(0,16,"Made by AlexZ",16);
OLED_ShowString(0,32,"freq:",16);
OLED_Refresh();
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, 50);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
pwm_value = 0; // pwm_value置0,这个是必须要做的,数据要刷新
HAL_Delay(1000); // 延时1s
OLED_ShowNum(75,32,pwm_value,6,16);
OLED_Refresh();
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
简单的进行一个OLED屏幕的显示
在while循环中通过hal_delay进行一个1s的延时,在延时过程中每个输入PA1脚的上升沿都会触发中断服务,以下为中断服务函数
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
{
pwm_value++;
}
延时结束后,pwm_value值即为输入波的频率,将pwm_value的值显示在OLED上。
软件模拟的SPI的驱动会相对容易移植,只需要在oled.h文件中配置好对应引脚即可,如下:
//PA0
#define SCL_GPIO_Port GPIOA
#define SCL_Pin GPIO_PIN_0
#define OLED_SCL_Clr() HAL_GPIO_WritePin(SCL_GPIO_Port,SCL_Pin,GPIO_PIN_RESET)
#define OLED_SCL_Set() HAL_GPIO_WritePin(SCL_GPIO_Port,SCL_Pin,GPIO_PIN_SET)
//PA4
#define SDA_GPIO_Port GPIOA
#define SDA_Pin GPIO_PIN_4
#define OLED_SDA_Clr() HAL_GPIO_WritePin(SDA_GPIO_Port,SDA_Pin,GPIO_PIN_RESET)
#define OLED_SDA_Set() HAL_GPIO_WritePin(SDA_GPIO_Port,SDA_Pin,GPIO_PIN_SET)
//PA5
#define RES_GPIO_Port GPIOA
#define RES_Pin GPIO_PIN_5
#define OLED_RES_Clr() HAL_GPIO_WritePin(RES_GPIO_Port,RES_Pin,GPIO_PIN_RESET)
#define OLED_RES_Set() HAL_GPIO_WritePin(RES_GPIO_Port,RES_Pin,GPIO_PIN_SET)
//PA8
#define DC_GPIO_Port GPIOA
#define DC_Pin GPIO_PIN_8
#define OLED_DC_Clr() HAL_GPIO_WritePin(DC_GPIO_Port,DC_Pin,GPIO_PIN_RESET)
#define OLED_DC_Set() HAL_GPIO_WritePin(DC_GPIO_Port,DC_Pin,GPIO_PIN_SET)
PS.本项目未使用FPGA,故无FPGA资源占用报告
实物功能展示:
可以看到在频率达到60.0000Hz左右,频率计依然能够相对比较准确地显示方波的频率。
理论上最大可以检测的频率应该在639999左右。
后记:
遇到的主要难题:
六脚SPI的OLED驱动的移植
0.96寸的OLED显示屏还是很常见的,国内开源平台上有很多可供下载的OLED驱动,适配了多种通信方式,如SPI和IIC。常见的SPI驱动又有软件SPI和硬件SPI。相较之下软件SPI虽然速度比硬件SPI更慢,但是移植起来更加快捷,使用也更加稳定,只要在头文件修改对应的GPIO_Port和GPIO_PIN即可,具体直接参考原理图即可。至于说问题(也不能说是问题),到底就是在OLED屏init和showstring后忘了加refresh,导致OLED屏幕移植不能正常显示,就这样干耗了半天,误以为是软硬件或者各种杂七杂八的问题,我是憨批。总之记得移植驱动前仔细看例程和注释。
软件的debug
问题就是不能debug。串口烧录程序,好处就是方便,不需要使用STLINK,直接使用STM32CubeProgrammer进行串口烧录即可。坏处就是不能在上位机上实时跑程序进行监控以及debug。但是考虑到此次制作频率计的任务相对简单,所以不能debug没有造成太大的问题。
没有外部晶振
本次使用的小脚丫基于STM32G031 + iCE40UP5K的MCU+FPGA核心板上的12MHz晶振接在了ICE40UP5k上,所以STM32没有提供外部晶振,只能使用内部时钟。使用内部时钟的问题是随着板子的使用,可能造成发热影响内部时钟的准确性,对于检测信号频率的准确性大概是有影响的。
后续优化:
考虑到在下此次任务完成时间紧迫,所以只是最底线的实现了信号频率捕获以及显示的功能。还有很多功能没有用上,比如旋转编码器、按键还有板子上那块牛逼哄哄的ICE40等等。
对于具体的优化,理论上,首先可以实现添加按键及旋转编码器的功能,譬如通过按键实现屏幕颜色的正反转,通过编码器调整PWM输出的频率等等。至于ICE40,可以实现的功能就更多了,比如通过高速DAC实现输出一个初始波形,在没有外部输入的时候可以有一个初始波形,进行校验、检视等等。同时在程序的逻辑上,还有很多可以进行优化的。比如程序采用的阻塞式延时,这在大项目的开发中是比较忌讳的。考虑到是小项目所以图方便直接使用网上找的方案直接hal_delay,也不会造成太大的影响,后续优化可以使用定时器中断。其次示数的刷新率为1Hz,还是挺慢的,可以通过减短采样时间来提高刷新率。再有就是freq的示数存在浮动,可以考虑再额外输出一个平均值。
未来的计划及建议
该项目已经成功实现了简易频率计的功能,并达到了预期指标。但是通过硬件的优化,还有许多提升和拓展的空间。
首当其冲就是把SWDIO和SWCLK引出来,能够实现debug,能够很大程度提高开发的便捷性。
还有就是可以再加个按键作为stm32的reset。不然每次重置都需要下电还是不太优雅。可以考虑把reset、boot0、boot1通过开关进行控制。(就像我手头这块f401一样:
工程文件如下
链接:https://pan.baidu.com/s/1_sdcJ-g1IazkYd_gFTd9hg?pwd=alex
提取码:alex