AT32F421F8P7国产MCU开发实战:从环境搭建到外设测试全解析
1. 项目缘起:为什么选择AT32F421F8P7这颗“小钢炮”?
最近在规划一个对成本极其敏感的小型控制项目,需要一颗引脚不多、性能够用、价格有优势的ARM Cortex-M内核单片机。在众多国产MCU品牌中,雅特力的AT32系列以其出色的性价比和丰富的产品线进入了我的视野。经过一番筛选,AT32F421F8P7这颗芯片成为了我的首选目标。它属于AT32F421系列,采用Cortex-M4F内核,主频高达96MHz,自带硬件浮点单元(FPU),这在同价位、同封装的竞品中并不多见。封装是TSSOP20,只有20个引脚,非常适合空间受限的应用。最关键的是,它的价格非常有竞争力,对于大批量生产来说,能省下不少成本。
但“纸上得来终觉浅”,芯片好不好用,还得实际上手“盘一盘”。官方的数据手册和参考手册写得再漂亮,也不如自己亲手搭建环境、点个灯、调个串口来得实在。这次初步测试的目的很明确:验证这颗芯片的基础开发环境是否顺畅,核心外设(如GPIO、定时器、串口)是否稳定可靠,以及其宣称的性能(如96MHz主频、FPU)在实际代码中是否有直观体现。这就像买车前的试驾,参数表是冰冷的,只有亲手开起来,才能感受到它的“脾气”和“脚感”。接下来,我就把这次从零开始的“试驾”全过程记录下来,希望能给同样对这颗芯片感兴趣的朋友们一个清晰的参考。
2. 开箱第一步:搭建你的专属“工作台”(Workbench)
工欲善其事,必先利其器。对于单片机开发来说,这个“器”就是集成开发环境(IDE)和调试下载工具。雅特力官方提供了基于Eclipse的AT32 Workbench,这是一个一站式的解决方案,集成了编译器、调试器和芯片支持包,对于新手非常友好。我的测试就基于此展开。
2.1 软件环境部署:AT32 Workbench的安装与初体验
首先,需要从雅特力官方网站下载AT32 Workbench安装包。安装过程比较常规,一路“Next”即可,注意安装路径不要包含中文和空格,这是开发工具的通用准则,能避免很多稀奇古怪的问题。安装完成后,启动AT32 Workbench,界面风格和STM32CubeIDE非常相似,这降低了STM32用户的迁移成本。
第一次启动,我们需要创建一个新项目。点击File -> New -> AT32 MCU Project,会弹出一个芯片选择窗口。在这里,输入或找到“AT32F421F8P7”,确认后,Workbench会自动加载该芯片的默认启动文件、链接脚本和系统初始化代码。项目创建向导的最后一步是选择调试接口,这里我们选择“SWD”,这是最常用且占用引脚最少的调试方式。
项目创建成功后,你会看到一个标准的工程结构:Src文件夹放用户源代码,Inc放头文件,Startup放启动汇编文件,Drivers里则是雅特力官方提供的标准外设库(类似于STM32的HAL库或标准库)。我个人习惯先编译一下这个空工程,确保基础环境没有问题。点击工具栏上的“Build”按钮(小锤子图标),如果一切顺利,在底部的“Console”窗口会看到“Build Finished”的提示,没有错误和警告。这第一步的绿灯,意味着你的软件“工作台”已经搭建完毕。
注意:AT32 Workbench内置的编译器是GCC ARM。如果你之前习惯使用Keil MDK或IAR,可能需要一点时间来适应GCC的编译选项和工程管理方式。不过对于新项目,直接使用官方的Workbench能获得最好的兼容性和官方支持。
2.2 硬件连接与调试器配置:认识AT-LINK与SWD接口
软件准备好了,接下来是硬件连接。我使用的调试器是官方的AT-LINK,它本质上是一个基于CMSIS-DAP协议的调试探头,兼容SWD和JTAG接口。对于AT32F421F8P7,我们只需要连接SWD接口的两根线(SWDIO和SWCLK)以及电源和地线即可。
连接方式如下:
- AT-LINK
SWDIO->MCUPA13(这是AT32F421的默认SWDIO引脚) - AT-LINK
SWCLK->MCUPA14(这是AT32F421的默认SWCLK引脚) - AT-LINK
3.3V->MCUVDD(为芯片供电,注意核对电压) - AT-LINK
GND->MCUGND
连接好硬件后,回到AT32 Workbench,我们需要配置调试器。右键点击工程,选择“Debug As -> Debug Configurations...”。在左侧找到你的工程名,在右侧的“Debugger”选项卡中,确保“AT-LINK (CMSIS-DAP)”被选中。在“Interface”下拉菜单中,选择“SWD”。其他参数通常保持默认即可。
点击“Apply”,然后点击“Debug”,如果硬件连接和配置正确,IDE会切换到调试视角,程序指针会停在main函数的开始处。此时,你可以尝试进行单步运行、设置断点等基本调试操作。能成功进入调试模式,就证明你的硬件连接、调试器驱动和IDE配置这三者之间的通路已经完全打通,这是后续所有开发的基础。
实操心得:有时候可能会遇到连接失败的问题,提示“No Debug Unit Found”之类的错误。别慌,按以下顺序排查:1. 检查硬件连线是否松动,特别是电源和地线;2. 检查AT-LINK的驱动是否安装正确(设备管理器中应识别为CMSIS-DAP设备);3. 尝试降低SWD时钟频率,在Debug配置的“Adapter”设置里,将“Clock”从默认的1MHz或4MHz降到500kHz或更低,这在长线或干扰环境下很有效;4. 确认芯片没有进入低功耗模式或复位状态,可以尝试给芯片断电再上电。
3. 点亮“Hello World”:GPIO驱动LED闪烁测试
对于嵌入式开发,点灯是仪式,更是基本功。它验证了最基本的时钟系统、GPIO配置和你的代码下载功能。AT32F421F8P7的GPIO用法和STM32非常相似,这得益于ARM Cortex-M生态的统一性。
3.1 时钟系统初始化:让芯片“心跳”起来
任何外设工作前,必须先有时钟。AT32F421的时钟树比STM32F1系列要灵活一些,但官方库函数已经做了很好的封装。在main函数的最开始,我们调用系统时钟初始化函数。在Workbench生成的项目中,通常会在main.c里自动调用system_clock_config()函数,它将系统时钟配置为使用内部高速RC振荡器(HSI)经过PLL倍频到96MHz。
int main(void) { /* 系统时钟、中断等初始化 */ system_clock_config(); // 初始化系统时钟到96MHz at32_board_init(); // 官方板级支持包初始化,如果自定义硬件,可能需要修改或不用 /* 用户代码开始 */ ... }对于初学者,可以暂时不用深究时钟树的细节,知道系统现在以最高性能96MHz运行即可。如果你想确认,可以查看system_clock_config()函数的实现,或者通过读取crm_clk_freq_get(CRM_SCLK)函数来获取系统时钟频率。
3.2 GPIO配置与LED闪烁代码实现
假设我们使用PA1引脚连接了一个LED(阴极接地,阳极通过限流电阻接PA1)。我们需要将PA1配置为推挽输出模式。
首先,在main.c的头部包含必要的头文件:#include "at32f421.h"和#include "at32f421_clock.h"。然后,在main函数的用户代码区添加以下内容:
// 1. 开启GPIOA的时钟 crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE); // 2. 初始化GPIO结构体 gpio_init_type gpio_init_struct; gpio_default_para_init(&gpio_init_struct); // 将结构体参数恢复为默认值,这是个好习惯 // 3. 配置PA1为推挽输出,高速模式 gpio_init_struct.gpio_pins = GPIO_PINS_1; gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT; gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL; gpio_init_struct.gpio_pull = GPIO_PULL_NONE; gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER; gpio_init(GPIOA, &gpio_init_struct); // 4. 主循环中实现LED闪烁 while(1) { gpio_bits_set(GPIOA, GPIO_PINS_1); // PA1输出高电平,LED亮 delay_ms(500); // 延时500ms gpio_bits_reset(GPIOA, GPIO_PINS_1); // PA1输出低电平,LED灭 delay_ms(500); // 延时500ms }注意,这里的delay_ms函数需要自己实现。一种简单的方式是利用系统滴答定时器(SysTick),AT32 Workbench生成的项目通常已经初始化了SysTick。你可以使用delay_cycle()函数进行微秒级延时,或者自己封装一个毫秒延时函数。更规范的做法是使用官方BSP中的delay库(如果已包含)。
编译并下载程序到芯片中。如果一切正常,你应该能看到LED以1秒的周期稳定闪烁。这个简单的测试成功,意味着:1. 芯片的时钟系统工作正常;2. GPIO外设驱动正确;3. 代码编译、下载、运行流程完全打通。这是一个非常重要的里程碑。
避坑提示:如果LED不亮,首先用万用表测量PA1引脚的电平是否在高低之间变化。如果没有变化,检查代码是否真的下载进去了(可以尝试在
while(1)最开始设置一个断点看能否停下)。如果电平有变化但LED不亮,检查硬件连接,LED方向是否接反,限流电阻是否合适(通常1kΩ-5kΩ)。
4. 性能初探:验证96MHz主频与硬件FPU
参数上写着96MHz Cortex-M4F,带硬件FPU,这听起来很美好,但实际性能如何?我们需要一些更直观的测试。单纯的LED闪烁无法体现性能差异,我设计两个小测试来感受一下。
4.1 软件延时精度测试:感知时钟频率
首先,我们可以用一个简单的软件循环延时来粗略感知主频。虽然不精确,但能看出大概。我们写一个函数,执行固定次数的空循环,并用逻辑分析仪或示波器测量一个GPIO引脚翻转的时间。
void test_delay_loop(void) { gpio_bits_set(GPIOB, GPIO_PINS_0); // 假设PB0接示波器 for(uint32_t i=0; i<100000; i++) { __NOP(); // 执行空操作,避免编译器优化掉循环 } gpio_bits_reset(GPIOB, GPIO_PINS_0); }在96MHz下,执行10万次__NOP()(每个NOP大概1个时钟周期)的时间大约是1.04ms。你可以用示波器测量PB0高电平的脉宽。如果测出来的时间远大于这个值(比如好几毫秒),那就要检查编译器优化等级(建议在Debug配置下测试,优化等级-O0),或者系统时钟是否真的配置到了96MHz。这个方法很土,但能快速验证芯片是否在以预期的速度“奔跑”。
4.2 硬件FPU性能对比测试:浮点运算的飞跃
硬件FPU(浮点单元)是Cortex-M4F相对于M0/M3的核心优势之一。我们通过一个密集的浮点运算来对比开启和关闭FPU时的速度差异。
首先,确保在工程设置中启用了FPU。在AT32 Workbench中,右键工程 -> Properties -> C/C++ Build -> Settings -> Tool Settings -> MCU Settings,检查“Floating-point hardware”是否设置为“fpv4-sp-d16”(单精度FPU)。同时,在代码中,需要在系统初始化时启用FPU,通常启动文件startup_at32f421.s里已经做了,我们只需确认。
然后编写测试代码:
#include <math.h> // 使用sinf函数 void test_fpu_performance(void) { float a = 0.0f; uint32_t start_tick, end_tick; start_tick = get_tick(); // 获取当前系统tick,需要实现此函数或使用SysTick for(int i=0; i<10000; i++) { a += sinf(i * 0.001f); // 进行一万次浮点正弦计算 } end_tick = get_tick(); printf("FPU ON: Time used: %lu ticks, result: %f\r\n", (end_tick-start_tick), a); // 为了对比,我们可以尝试编译时关闭FPU选项,用软件库模拟浮点运算 // 重新编译运行,时间会显著增加。 }在我的测试中,开启硬件FPU执行一万次sinf计算,耗时在几十个毫秒量级。而如果修改工程设置,将浮点硬件选项改为“Software”(即使用编译器软件库模拟),同样的代码耗时可能会增加数十倍甚至上百倍,达到几百毫秒甚至秒级。这个对比非常震撼,直观地展示了硬件FPU在处理复杂算法(如PID控制、数字滤波、坐标变换)时的巨大优势。对于AT32F421F8P7来说,这个FPU是它区别于许多同价位M0/M3芯片的“杀手锏”。
性能测试心得:在进行此类性能测试时,务必关闭所有中断,并将编译器优化等级固定(如-O0),以确保测试结果的可比性。另外,
printf输出本身比较耗时,会影响绝对时间测量,更精确的方法是用一个GPIO引脚在测试开始和结束时翻转,用示波器测量高电平脉宽。
5. 核心外设尝鲜:USART通信与定时器PWM输出
点亮了灯,验证了性能,接下来试试两个最常用的外设:串口(USART)和定时器(TIMER)。它们是项目中最常见的“左膀右臂”。
5.1 USART串口通信:打通调试与数据通道
串口是调试和信息输出的生命线。AT32F421F8P7有多个USART,我们以USART1为例,将其配置为115200波特率,8位数据位,1位停止位,无校验位。使用PA9作为TX,PA10作为RX。
void uart1_init(uint32_t baudrate) { gpio_init_type gpio_init_struct; // 1. 开启时钟 crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_USART1_PERIPH_CLOCK, TRUE); // 2. 配置PA9为复用推挽输出(TX) gpio_init_struct.gpio_pins = GPIO_PINS_9; gpio_init_struct.gpio_mode = GPIO_MODE_MUX; gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL; gpio_init_struct.gpio_pull = GPIO_PULL_NONE; gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER; gpio_init_struct.gpio_mux = GPIO_MUX_1; // USART1 TX复用功能 gpio_init(GPIOA, &gpio_init_struct); // 3. 配置PA10为浮空输入(RX) gpio_init_struct.gpio_pins = GPIO_PINS_10; gpio_init_struct.gpio_mode = GPIO_MODE_INPUT; gpio_init_struct.gpio_pull = GPIO_PULL_NONE; gpio_init_struct.gpio_mux = GPIO_MUX_1; // USART1 RX复用功能 gpio_init(GPIOA, &gpio_init_struct); // 4. 配置USART参数 usart_init(USART1, baudrate, USART_DATA_8BITS, USART_STOP_1_BIT); usart_parity_selection_config(USART1, USART_PARITY_NONE); usart_hardware_flow_control_set(USART1, USART_HARDWARE_FLOW_NONE); usart_transmitter_enable(USART1, TRUE); usart_receiver_enable(USART1, TRUE); usart_enable(USART1, TRUE); } // 重定向printf到串口1(可选,方便调试) int _write(int file, char *ptr, int len) { for(int i=0; i<len; i++) { usart_data_transmit(USART1, (uint8_t)ptr[i]); while(usart_flag_get(USART1, USART_TDBE_FLAG) == RESET); // 等待发送完成 } return len; }初始化后,就可以使用printf函数或者直接调用usart_data_transmit函数发送数据了。连接一个USB转TTL模块到PA9和PA10,打开串口助手(如Putty、SecureCRT),设置正确的波特率,应该能看到单片机发送的数据。这个测试验证了芯片的USART外设工作正常,也为后续更复杂的调试打下了基础。
5.2 定时器产生PWM信号:驱动舵机与LED调光
定时器是单片机的“心脏”,PWM输出更是控制电机、舵机、LED亮度的必备功能。AT32F421F8P7拥有多个高级/通用定时器,我们以通用定时器TIM2的通道1(PA0)为例,产生一个频率1kHz,占空比50%的PWM波。
void pwm_init(void) { gpio_init_type gpio_init_struct; // 1. 开启时钟 crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_TMR2_PERIPH_CLOCK, TRUE); // 2. 配置PA0为复用推挽输出(TMR2_CH1) gpio_init_struct.gpio_pins = GPIO_PINS_0; gpio_init_struct.gpio_mode = GPIO_MODE_MUX; gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL; gpio_init_struct.gpio_pull = GPIO_PULL_NONE; gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER; gpio_init_struct.gpio_mux = GPIO_MUX_2; // TMR2_CH1复用功能,需查数据手册确认 gpio_init(GPIOA, &gpio_init_struct); // 3. 配置定时器基础参数 tmr_base_init(TMR2, 959, 0); // 预分频器(PSC)=959,自动重载值(ARR)=0(先设0,后面计算) tmr_cnt_dir_set(TMR2, TMR_COUNT_UP); // 向上计数模式 // 4. 计算PWM频率和占空比 // 系统时钟96MHz,经过(PSC+1)=960分频,得到计数器时钟 = 100kHz // 要产生1kHz PWM,则ARR值应为 (100kHz / 1kHz) - 1 = 99 // 占空比50%,则比较捕获寄存器(CCR)值应为 (ARR+1) * 50% = 50 tmr_period_value_set(TMR2, 99); // 设置ARR tmr_channel_value_set(TMR2, TMR_SELECT_CHANNEL_1, 50); // 设置CCR1 // 5. 配置PWM模式 tmr_output_config_type tmr_oc_init_struct; tmr_output_default_para_init(&tmr_oc_init_struct); tmr_oc_init_struct.oc_mode = TMR_OUTPUT_CONTROL_PWM_MODE_A; // PWM模式1 tmr_oc_init_struct.oc_output_state = TRUE; tmr_oc_init_struct.oc_polarity = TMR_OUTPUT_ACTIVE_HIGH; tmr_oc_init_struct.oc_idle_state = FALSE; tmr_output_channel_config(TMR2, TMR_SELECT_CHANNEL_1, &tmr_oc_init_struct); // 6. 使能预装载寄存器(使修改CCR立即生效的模式) tmr_channel_value_preload_config(TMR2, TMR_SELECT_CHANNEL_1, TRUE); // 7. 启动定时器 tmr_counter_enable(TMR2, TRUE); tmr_output_enable(TMR2, TRUE); }这段代码配置完成后,用示波器或逻辑分析仪测量PA0引脚,应该能看到一个标准的1kHz、3.3V、占空比50%的方波。你可以通过动态修改tmr_channel_value_set(TMR2, TMR_SELECT_CHANNEL_1, new_ccr_value)来实时改变占空比,实现LED呼吸灯或者舵机角度控制。这个测试成功,意味着定时器外设工作正常,PWM功能可用,为控制类应用扫清了障碍。
外设配置核心:配置复用功能引脚时,
gpio_mux的值一定要查芯片数据手册(Datasheet)中的“Alternate function mapping”表格,而不是想当然。AT32的复用功能编号可能与STM32不同,这是移植代码时最容易出错的地方之一。
6. 功耗与稳定性初窥:睡眠模式与简单抗干扰
对于小型电池供电设备,功耗是关键;对于工业环境,稳定性是生命。我们做两个简单的初步观察。
6.1 进入睡眠模式测量电流
AT32F421支持多种低功耗模式,如睡眠(Sleep)、停止(Stop)、待机(Standby)。我们可以写一段简单的代码,让芯片在完成初始化后直接进入睡眠模式。
#include “at32f421_pwr.h” ... int main(void) { system_clock_config(); // 关闭所有不必要的外设时钟 // 配置所有未使用的GPIO为模拟输入模式以降低功耗 // ... printf("Entering Sleep Mode...\r\n"); delay_ms(100); // 等待串口发送完成 // 进入睡眠模式(通过WFI指令) pwr_enter_sleep_mode(PWR_SLEEP_ENTRY_WFI); // 程序执行将在此暂停,直到发生中断唤醒 while(1) { // 被唤醒后的代码 } }使用万用表电流档串联到芯片的供电回路中(注意安全),测量进入睡眠模式后的静态电流。根据数据手册,AT32F421在睡眠模式下的典型电流值在mA级别(具体取决于开启的外设)。这个测试可以让你对芯片的功耗特性有一个基本的量化认识。在实际项目中,需要精细管理外设时钟和IO状态,才能达到数据手册上宣称的微安级低功耗。
6.2 简单IO翻转压力测试
稳定性测试需要更长时间和复杂场景,但我们可以做一个简单的“压力测试”:让一个GPIO引脚以最高频率翻转,并长时间运行(比如24小时),同时运行一些简单的计算任务。观察芯片是否会死机、复位或者IO输出异常。
void stress_test(void) { // 配置一个IO为最高速输出 gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER; gpio_init(GPIOB, &gpio_init_struct); while(1) { gpio_bits_toggle(GPIOB, GPIO_PINS_0); // 在循环内不停翻转 // 可以同时加入一些浮点运算或内存操作,增加内核压力 float dummy = 0; for(int i=0; i<100; i++) dummy += sqrtf((float)i); } }用示波器观察这个引脚,波形应该干净、频率稳定。长时间运行后,触摸芯片表面,温度应该只是温热,不会烫手。这个测试虽然基础,但能快速排除芯片是否存在明显的硬件缺陷或散热问题。
经过以上六个步骤的初步测试,我对AT32F421F8P7这颗芯片有了比较立体的认识。从开发环境搭建、基础外设驱动到性能与稳定性摸底,整个流程走下来,感觉它确实是一颗性价比很高的“小钢炮”。其软件生态(库函数、IDE)对STM32用户非常友好,降低了迁移成本。96MHz M4F内核加上硬件FPU,为它处理稍复杂的算法提供了底气。当然,初步测试只是开始,后续还需要根据具体项目需求,深入测试它的ADC精度、通信接口(I2C、SPI)的稳定性、中断响应时间等。但就入门和大多数控制应用而言,AT32F421F8P7已经展现出了足够的实力和诚意。如果你正在寻找一颗引脚少、性能强、价格优的国产MCU,它绝对值得你花时间深入了解一下。
