一、项目介绍
使用MAX32650FTHR评估板,利用板载主控MAX32650及板载ADC MAX11261,额外搭配两轴摇杆和AHT20传感器,实现采集多路数据并记录、上传的功能。
所选任务:任务三-数据采集,入门级题目1-多通道数据记录仪
采集4路以上模拟或数字传感器数据(如电压、电流、温度等),采样率1kHz以上,实时显示测量结果,并记录至SD卡(或者Flash里,支持连续记录, 需自行评估数据容量),支持USB导出CSV数据供上位机分析。
推荐开发板:MAX32650FTHR(本项目实际使用开发板)、MAX32690EVKIT
二、硬件介绍
MAX32650是一款面向低功耗、高性能嵌入式应用的微控制器(MCU),最初由Maxim Integrated推出,现已归属于Analog Devices(ADI)产品体系。该芯片采用Arm Cortex-M4内核,并集成浮点运算单元(FPU),主频最高可达120MHz,能够满足复杂算法处理、数据采集以及实时控制等需求。MAX32650最显著的特点之一是拥有较大的片上存储资源,内置3MB Flash和1MB SRAM,相较于许多同级别微控制器具有更强的程序存储和数据缓存能力。同时,它还支持HyperBus、SPI扩展存储接口以及SDHC、SDIO和microSD等外部存储方式,从而使系统能够适应更大规模的数据处理任务。为了满足电池供电设备对续航能力的要求,MAX32650设计了多种低功耗工作模式,并采用SmartDMA技术,在CPU休眠期间仍可执行后台数据处理,有效降低整体功耗。丰富的外设资源也是其重要优势,包括SPI、UART、I2C、I2S、DMA、定时器以及USB等接口,为工业控制、医疗设备、可穿戴产品和物联网终端提供了良好的开发基础。
MAX32650所属的MAX3265x系列还包括MAX32651和MAX32652等型号。其中MAX32651在MAX32650基础上增加了安全功能模块,包括安全启动、AES加密引擎、SHA-256哈希运算、随机数发生器以及椭圆曲线加密加速器等,适用于对信息安全要求较高的应用场景;MAX32652则采用更高密度封装,适合空间受限但需要较高I/O数量的产品设计。整个系列具有统一的软件生态和开发框架,开发者能够在不同型号之间较为方便地进行产品升级和平台迁移。
产品系列最早由美国模拟与混合信号半导体公司Maxim Integrated研发。Maxim Integrated成立于1983年,长期专注于模拟电路、数据转换、电源管理、传感器接口以及嵌入式解决方案,在工业、医疗、汽车和消费电子领域拥有广泛影响力。随着物联网和可穿戴设备市场的发展,Maxim推出了DARWIN系列超低功耗微控制器,而MAX32650正是这一系列的重要成员。2021年,全球知名模拟半导体厂商Analog Devices(简称ADI)正式完成对Maxim的收购。收购完成后,MAX32650及相关产品线被纳入ADI产品组合,并继续获得技术支持与市场推广。
本次项目使用的MAX32650FTHR是MAXIM(现ADI)专为MAX32650设计制造的快速验证板。其特点为板载MAX11261 ADC和MAX77818电池管理芯片,预留了一个TF卡卡槽,同时PCB外形沿用Adafruit Feather规范,兼容大量现有的外设模块,以便使用者可以快速进行数据采集或低功耗领域的测试。用户交互方面,板上有2颗LED和2个轻触按键,另外还有一颗轻触按键用于复位MAX32650。调试方面,引出了SWD和UART0到2*5P排针,可以方便地连接到配套的MAX32625Pico调试器上。
板载的MAX11261芯片是一款24位精度,6通道,16k采样率的Sigma-Delta ADC。其供电范围为2.7V至3.6V,同时支持1.8V低电压IO,可以方便地配合MAX32650使用。MAX11261提供了1,2,4,8,16,32,64,128共8个增益倍数,以8.192MHz频率工作,可达到低至6.2nV/sqrt(Hz)的噪声精度。在本次项目使用的套件中,板载的MAX11261连接至MCU的I2C1外设引脚。
AHT20是一款由奥松电子推出的新一代嵌入式数字温湿度传感器。其内部配备了经过改进的MEMS半导体电容式湿度测湿元件以及ASIC控制器,可以以有竞争力的成本提供高精度温湿度测量。同时,出厂校准和指令自校准也免除了用户自行寻找校准参考与设备的烦恼,做到开箱即用。AHT20使用3.3V供电,兼容5V,通过标准I2C接口连接主控设备。测量范围温度-50°C~150°C,湿度0%RH~100%RH,精度分别为±0.3°C与±2%RH。
另外,为了配合MAX11261 ADC,本次项目还使用了一个双轴摇杆来模拟电压信号。这个模块的结构很简单,可以等效看作两个滑动变阻器连接到供电正负极之间,抽头则引出为电压量。因此根据摇杆位置,3.3V供电下可以输出0~3.3V的电压。不施加外力时,两个抽头均位于正中央,因而缺省输出为1.65V。
三、系统架构
使用C语言编程,通过ARM官方规范CMSIS以及MAXIM(现ADI)开发的MicroSDK外设库操作外设,控制定时器产生1MHz时基,并以此为基础进行1kHz的数据采集。空闲时,处理USB信号,实现与上位机的通信。同时,控制屏幕以30Hz刷新率显示采集到的数据。

四、软件架构
主函数main中,首先进行外设初始化操作,复位并配置ADC、屏幕和AHT20的状态。然后启用USB相关功能,并配置中断。之后进入主循环,采集数据,完成后检查USB是否有指令输入,最后控制采集频率至1000Hz。另外,还需要在主循环中判断距离上次屏幕刷新是否已经超过33ms,如果是则刷新屏幕数据,实现30Hz的数据显示。
上位机采用USB连接,发送单字母指令。下位机根据收到的指令执行操作,并返回状态。如果是数据上传指令,则还需要读取flash中对应位置的数据并发送至上位机。

五、硬件连接
MAX32650FTHR的板载ADC引出的AIN0、AIN1引脚连接双轴摇杆的X、Y轴。P1.29、P1.28两个GPIO引脚连接AHT20的SCL、SDA引脚。P2.14、P2.16配置为UART1,连接屏幕的TX、RX。另外需按需连接3V3与5V供电。

实际连接如下图所示。红色部分为主要部件:
- MAX32650FTHR+黑色调试器子板(MAX32625PICO)
- AHT20 I2C温湿度传感器模块(紫色)。红、黑、黄、白依次为VCC、GND、SDA、SCL。其右下方的蓝色为其他传感器,本次项目未使用,未接线。
- 双轴摇杆模块,输出两路电压模拟量。红、绿线为供电与地线,黄、白线为两路电压输出。
- 显示屏
蓝色部分为数据和电源连接:
- MAX32650 UART1,连接至显示屏
- MAX32650 P1.28与P1.29,连接至AHT20模块
- AIN0与AIN1引脚,连接于板上的MAX11261ADC。连接至双轴摇杆模块的两路电压输出引脚。
- MAX32650FTHR板载3.3V LDO输出与地线,通过右侧分线后给AHT20模块与双轴摇杆模块供电。
此外,显示屏的供电由外部5V提供(串口模块,从USB取电)。同时其地线也连接到了右侧两个模块使用的供电地线,以均衡两侧供电的电势。

六、软件代码和结构
MAXIM(先为ADI)为旗下产品提供了CodeFusion Studio集成开发环境,属于逐步迁移到VSCode平台的浪潮中的一员。安装时也会先提示要安装VSCode。同时,本次活动要求使用CFS开发。
安装CFS后,还需安装MSDK,后者包含了板级支持和编译器等工具链。
CFS提供了图形界面用于配置外设和时钟状态并生成相关初始化代码。相关数据保存在.cfsconfig中,文件内容为json格式,除图形界面外,也可以方便地查看与修改。
压缩包中,src目录下的main.c与i2c4.c是程序源代码。.cfs目录下的.cfsconfig是IO、片上外设及时钟树的配置信息。
main.c包含了主要逻辑,包括外设配置、ADC配置、USB逻辑等等,也包含主循环和对应的数据采集功能。i2c4.c中包含了AHT20通信相关的逻辑。
工程编译方法如下:
- 安装VSCode,安装VSCode CFS扩展,安装MSDK。
- 解压zip文件到一个文件夹。
- 打开VSCode,打开CFS主页,点击新建工作区,选择硬件型号为MAX32650,并选择新建空白项目。
- 关闭VSCode,覆盖.cfsconfig和两个.c文件至对应位置。
- 重新打开VSCode,打开.cfsconfig,检查配置无误后生成soc_init.c/h及其他项目文件。
- 进入CFS侧栏,选择Build,如无意外应该可以成功编译。
使用CFS配置外设、引脚和时钟树时,可参考下图,检查是否有误。



主要代码如下。
第一部分:main函数前半,作为程序入口点,在CFS产生的soc_init.c的逻辑后运行,负责初始化CFS没有配置的各项外设,以及AHT20。
int main(void) {
int error = 0;
int count = 0;
printf("123 Enter main()\n");
max11261_adc_result_t adcRes;
PB_IntEnable(0);
PB_IntEnable(1);
PB_RegisterCallback(0, pb_irq_handler);
PB_RegisterCallback(1, pb_irq_handler);
MXC_NVIC_SetVector(TMR0_IRQn, sys_timer_handler);
NVIC_EnableIRQ(TMR0_IRQn);
MXC_TMR_Start(MXC_TMR0);
NVIC_ClearPendingIRQ(UART1_IRQn);
NVIC_DisableIRQ(UART1_IRQn);
MXC_NVIC_SetVector(UART1_IRQn, UART1_Handler);
NVIC_EnableIRQ(UART1_IRQn);
mxc_uart_req_t uart_write_req;
uart_write_req.uart = MXC_UART1;
uart_write_req.txData = 0;
uart_write_req.txLen = 0;
uart_write_req.rxLen = 0;
uart_write_req.callback = NULL;
error = max11261_adc_config_init(3000, 2500, MAX11261_SIF_FREQ_3001_5000);
if (error != E_NO_ERROR) {
printf("ADC init failed.\n");
for (;;)
;
}
error = max11261_adc_reset();
if (error != E_NO_ERROR) {
printf("ADC reset failed.\n");
while (true)
;
}
max11261_adc_set_rate_single(MAX11261_SINGLE_RATE_12800);
max11261_adc_set_channel(MAX11261_ADC_CHANNEL_0);
max11261_adc_convert_prepare();
printf("Hello World!\n");
printf("\n\n\n\n\n\n\n");
uint32_t tickStart = 0;
uint32_t loopStart;
uint32_t screenUpdate = 0;
int last_led_state = 0;
init_usb();
aht20_init();
init_flash();
MXC_Delay(1000000);
uart_write_req.txData = (uint8_t *)"\r\n\r\nJUMP(1);\r\nJUMP(1);\r\n\r\n";
uart_write_req.txLen = strlen((char *)uart_write_req.txData);
MXC_UART_TransactionAsync(&uart_write_req);
MXC_Delay(2000000);
uart_write_req.txData = (uint8_t *)"\r\n\r\nJUMP(0);\r\nJUMP(0);\r\n\r\n";
uart_write_req.txLen = strlen((char *)uart_write_req.txData);
MXC_UART_TransactionAsync(&uart_write_req);
MXC_Delay(2000000);
...
第二部分:main函数余下部分,主循环。单次循环流程为,根据通道采集对应数据,记录数据到flash,刷新数据到屏幕,处理USB上位机命令。最后延时,控制采样率为1kHz。
int main(void) {
...
while (1) {
loopStart = ticksUs;
switch (last_led_state) {
case 0:
LED_Off(0);
LED_Off(1);
break;
case 1:
LED_On(0);
LED_Off(1);
break;
case 2:
LED_Off(0);
LED_On(1);
break;
case 3:
LED_On(1);
LED_On(1);
break;
}
last_led_state += 1;
if (last_led_state >= 4) {
last_led_state = 0;
}
// read channel and save to val
int val = -1;
switch (cur_channel) {
case 0:
case 1:
if ((error = max11261_adc_convert()) < 0) {
printf("Failed to start conversion\n");
while (true)
;
}
error = max11261_adc_result(&adcRes, 1);
if (error <= 0) {
printf("Error obtaining result: %d (%u us)\n", error, ticksUs - tickStart);
while (true)
;
}
val = adcRes.val;
break;
case 2:
case 3:
val = aht20_read(cur_channel & 1, ticksUs);
break;
}
if (flash_operating > 0) {
if (flash_operating_cnt <= 0) {
write_flash((uint32_t)val | ((uint32_t)cur_channel << 20) | 0xA5000000);
flash_operating -= 1;
flash_operating_cnt = flash_operating_step - 1;
} else {
flash_operating_cnt -= 1;
}
}
if (ticksUs - screenUpdate > 50000) {
screenUpdate = ticksUs;
char *buf = uartbuf;
switch (cur_channel) {
case 0:
case 1:
snprintf(buf, sizeof(uartbuf) - 2, "SET_PROG(0,%d);SET_TXT(2,%d);SET_TXT(4,%5d mV);\r\n", val * 100 / 3300, cur_channel + 1, val);
break;
case 2:
int aa = ((val * 3125 + (1 << 13)) >> 14) - 50000;
snprintf(buf, sizeof(uartbuf) - 2, "SET_PROG(0,%d);SET_TXT(2,%d);SET_TXT(4,%3d.%03d `C);\r\n", ((val * 500 + (1 << 17)) >> 18) - 700, cur_channel + 1, aa / 1000, aa % 1000);
break;
case 3:
int bb = (val * 100) >> 20;
snprintf(buf, sizeof(uartbuf) - 2, "SET_PROG(0,%d);SET_TXT(2,%d);SET_TXT(4,%3d% RH);\r\n", bb, cur_channel + 1, bb);
break;
}
uart_write_req.txData = (uint8_t *)uartbuf;
uart_write_req.txLen = strlen(uartbuf);
// printf("Sending %d bytes:\n %s\n", uart_write_req.txLen, uart_write_req.txData);
MXC_UART_TransactionAsync(&uart_write_req);
// printf("result %d: %d : %d used %d us\n", count, cur_channel + 1, val, ticksUs - loopStart);
// fflush(stdout);
}
count += 1;
while (ticksUs - loopStart < 990) {
usb_cycle();
break;
}
while (ticksUs - loopStart < 1000) {
}
}
}
第三部分:USB逻辑,包含USB外设中断与其依赖的SYSTick,USB描述符定义,USB状态机以及总线存在性检测(是否连接到USB主机,否则休眠以降低资源占用),以及USB上位机命令处理和分发。
/* usb descriptor */
#ifdef __cplusplus
extern "C" {
#endif
/* **** Definitions **** */
MXC_USB_device_descriptor_t __attribute__((aligned(4))) device_descriptor = {
0x12, /* bLength = 18 */
0x01, /* bDescriptorType = Device */
0x0200, /* bcdUSB USB spec rev (BCD) */
0x02, /* bDeviceClass = comm class (2) */
0x00, /* bDeviceSubClass */
0x00, /* bDeviceProtocol */
0x40, /* bMaxPacketSize0 is 64 bytes */
0x0B6A, /* idVendor (Maxim Integrated) */
0x003C, /* idProduct */
0x0100, /* bcdDevice */
0x01, /* iManufacturer Descriptor ID */
0x02, /* iProduct Descriptor ID */
0x00, /* iSerialNumber = (0) No string */
0x01 /* bNumConfigurations */
};
/* Device qualifier needed for high-speed operation */
MXC_USB_device_qualifier_descriptor_t __attribute__((aligned(4))) device_qualifier_descriptor = {
0x0A, /* bLength = 10 */
0x06, /* bDescriptorType = Device Qualifier */
0x0200, /* bcdUSB USB spec rev (BCD) */
0x02, /* bDeviceClass = Unspecified */
0x00, /* bDeviceSubClass */
0x00, /* bDeviceProtocol */
0x40, /* bMaxPacketSize0 is 64 bytes */
0x01, /* bNumConfigurations */
0x00 /* Reserved, must be 0 */
};
__attribute__((aligned(4))) struct __attribute__((packed)) {
MXC_USB_configuration_descriptor_t config_descriptor;
/* Interface #1 CDCACM Device */
MXC_USB_interface_descriptor_t comm_interface_descriptor;
uint8_t header_functional_descriptor[5];
uint8_t call_management_descriptor[5];
uint8_t acm_functional_descriptor[4];
uint8_t union_functional_descriptor[5];
/* Interface #2 CDC Data*/
MXC_USB_endpoint_descriptor_t endpoint_descriptor_3;
MXC_USB_interface_descriptor_t data_interface_descriptor;
MXC_USB_endpoint_descriptor_t endpoint_descriptor_4;
MXC_USB_endpoint_descriptor_t endpoint_descriptor_5;
}
config_descriptor = {
{
0x09, /* bLength = 9 */
0x02, /* bDescriptorType = Config (2) */
0x0043, /* wTotalLength(L/H) */
0x02, /* bNumInterfaces */
0x01, /* bConfigValue */
0x02, /* iConfiguration */
0xE0, /* bmAttributes (self-powered, remote wakeup) */
0x01, /* MaxPower is 2ma (units are 2ma/bit) */
},
/********** Interface #1 : COMM Interface **********/
{
/* First Interface Descriptor For Comm Class Interface */
0x09, /* bLength = 9 */
0x04, /* bDescriptorType = Interface (4) */
0x00, /* bInterfaceNumber */
0x00, /* bAlternateSetting */
0x01, /* bNumEndpoints (one for OUT) */
0x02, /* bInterfaceClass = Communications Interface Class (2) */
0x02, /* bInterfaceSubClass = Abstract Control Model (2) */
0x01, /* bInterfaceProtocol = Common "AT" commands (1), no class specific protocol (0) */
0x04, /* iInterface */
},
{
/* Header Functional Descriptor */
0x05, /* bFunctionalLength = 5 */
0x24, /* bDescriptorType */
0x00, /* bDescriptorSubtype */
0x10, 0x01, /* bcdCDC */
},
{
/* Call Management Descriptor */
0x05, /* bFunctionalLength = 5 */
0x24, /* bDescriptorType */
0x01, /* bDescriptorSubtype */
0x03, /* bmCapabilities = Device handles call management itself (0x01), management over data class (0x02) */
0x01, /* bmDataInterface */
},
{
/* Abstract Control Management Functional Descriptor */
0x04, /* bFunctionalLength = 4 */
0x24, /* bDescriptorType */
0x02, /* bDescriptorSubtype */
0x02, /* bmCapabilities */
},
{
/* Union Functional Descriptor */
0x05, /* bFunctionalLength = 5 */
0x24, /* bDescriptorType */
0x06, /* bDescriptorSubtype */
0x00, /* bmMasterInterface */
0x01, /* bmSlaveInterface0 */
},
{
/* IN Endpoint 3 (Descriptor #1) */
0x07, /* bLength */
0x05, /* bDescriptorType (Endpoint) */
0x84, /* bEndpointAddress (EP3-IN) */
0x03, /* bmAttributes (interrupt) */
0x0040, /* wMaxPacketSize */
0xff, /* bInterval (milliseconds) */
},
{
/* Second Interface Descriptor For Data Interface */
0x09, /* bLength */
0x04, /* bDescriptorType (Interface) */
0x01, /* bInterfaceNumber */
0x00, /* bAlternateSetting */
0x02, /* bNumEndpoints */
0x0a, /* bInterfaceClass = Data Interface (10) */
0x00, /* bInterfaceSubClass = none (0) */
0x00, /* bInterfaceProtocol = No class specific protocol (0) */
0x04, /* biInterface = No Text String (0) */
},
{
/* OUT Endpoint 1 (Descriptor #2) */
0x07, /* bLength */
0x05, /* bDescriptorType (Endpoint) */
0x05, /* bEndpointAddress (EP1-OUT) */
0x02, /* bmAttributes (bulk) */
0x0040, /* wMaxPacketSize */
0x00, /* bInterval (N/A) */
},
{
/* IN Endpoint 2 (Descriptor #3) */
0x07, /* bLength */
0x05, /* bDescriptorType (Endpoint) */
0x83, /* bEndpointAddress (EP2-IN) */
0x02, /* bmAttributes (bulk) */
0x0040, /* wMaxPacketSize */
0x00 /* bInterval (N/A) */
}
};
__attribute__((aligned(4))) struct __attribute__((packed)) {
MXC_USB_configuration_descriptor_t config_descriptor;
/* Interface #1 CDCACM Device */
MXC_USB_interface_descriptor_t comm_interface_descriptor;
uint8_t header_functional_descriptor[5];
uint8_t call_management_descriptor[5];
uint8_t acm_functional_descriptor[4];
uint8_t union_functional_descriptor[5];
MXC_USB_endpoint_descriptor_t endpoint_descriptor_3;
/* Interface #2 CDCACM Data */
MXC_USB_interface_descriptor_t data_interface_descriptor;
MXC_USB_endpoint_descriptor_t endpoint_descriptor_4;
MXC_USB_endpoint_descriptor_t endpoint_descriptor_5;
}
config_descriptor_hs = {
{
0x09, /* bLength */
0x02, /* bDescriptorType = Config */
0x0043, /* wTotalLength(L/H) */
0x02, /* bNumInterfaces */
0x01, /* bConfigurationValue */
0x02, /* iConfiguration */
0xE0, /* bmAttributes (bus-powered, remote wakeup) */
0x32, /* MaxPower is 100ma (units are 2ma/bit) */
},
/********** Interface #1 : COMM Interface **********/
{
/* First Interface Descriptor For Comm Class Interface */
0x09, /* bLength = 9 */
0x04, /* bDescriptorType = Interface (4) */
0x00, /* bInterfaceNumber */
0x00, /* bAlternateSetting */
0x01, /* bNumEndpoints (one for OUT) */
0x02, /* bInterfaceClass = Communications Interface Class (2) */
0x02, /* bInterfaceSubClass = Abstract Control Model (2) */
0x01, /* bInterfaceProtocol = Common "AT" commands (1), no class specific protocol (0) */
0x04, /* iInterface */
},
{
/* Header Functional Descriptor */
0x05, /* bFunctionalLength = 5 */
0x24, /* bDescriptorType */
0x00, /* bDescriptorSubtype */
0x10, 0x01, /* bcdCDC */
},
{
/* Call Management Descriptor */
0x05, /* bFunctionalLength = 5 */
0x24, /* bDescriptorType */
0x01, /* bDescriptorSubtype */
0x03, /* bmCapabilities = Device handles call management itself (0x01), management over data class (0x02) */
0x01, /* bmDataInterface */
},
{
/* Abstract Control Management Functional Descriptor */
0x04, /* bFunctionalLength = 4 */
0x24, /* bDescriptorType */
0x02, /* bDescriptorSubtype */
0x02, /* bmCapabilities */
},
{
/* Union Functional Descriptor */
0x05, /* bFunctionalLength = 5 */
0x24, /* bDescriptorType */
0x06, /* bDescriptorSubtype */
0x00, /* bmMasterInterface */
0x01, /* bmSlaveInterface0 */
},
{
/* IN Endpoint 3 (Descriptor #1) */
0x07, /* bLength */
0x05, /* bDescriptorType (Endpoint) */
0x84, /* bEndpointAddress (EP3-IN) */
0x03, /* bmAttributes (interrupt) */
0x0200, /* wMaxPacketSize */
0xff, /* bInterval (milliseconds) */
},
{
/* Second Interface Descriptor For Data Interface */
0x09, /* bLength */
0x04, /* bDescriptorType (Interface) */
0x01, /* bInterfaceNumber */
0x00, /* bAlternateSetting */
0x02, /* bNumEndpoints */
0x0a, /* bInterfaceClass = Data Interface (10) */
0x00, /* bInterfaceSubClass = none (0) */
0x00, /* bInterfaceProtocol = No class specific protocol (0) */
0x04, /* biInterface = No Text String (0) */
},
{
/* OUT Endpoint 1 (Descriptor #2) */
0x07, /* bLength */
0x05, /* bDescriptorType (Endpoint) */
0x05, /* bEndpointAddress (EP1-OUT) */
0x02, /* bmAttributes (bulk) */
0x0200, /* wMaxPacketSize */
0x00, /* bInterval (N/A) */
},
{
/* IN Endpoint 2 (Descriptor #3) */
0x07, /* bLength */
0x05, /* bDescriptorType (Endpoint) */
0x83, /* bEndpointAddress (EP2-IN) */
0x02, /* bmAttributes (bulk) */
0x0200, /* wMaxPacketSize */
0x00, /* bInterval (N/A) */
},
};
__attribute__((aligned(4))) uint8_t lang_id_desc[] = {
0x04, /* bLength */
0x03, /* bDescriptorType */
0x09, 0x04 /* bString = wLANGID (see usb_20.pdf 9.6.7 String) */
};
__attribute__((aligned(4))) uint8_t mfg_id_desc[] = {
0x22, /* bLength */
0x03, /* bDescriptorType */
'M', 0, 'a', 0, 'x', 0, 'i', 0, 'm', 0, ' ', 0, 'I', 0, 'n', 0, 't', 0, 'e', 0, 'g', 0, 'r', 0, 'a', 0, 't', 0, 'e', 0, 'd', 0,
};
__attribute__((aligned(4))) uint8_t prod_id_desc[] = {
0x22, /* bLength */
0x03, /* bDescriptorType */
'M', 0, 'A', 0, 'X', 0, '3', 0, '2', 0, '6', 0, '5', 0, '0', 0, ' ', 0, 'C', 0, 'D', 0, 'C', 0, '-', 0, 'A', 0, 'C', 0, 'M', 0,
};
/* Not currently used (see device descriptor), but could be enabled if desired */
__attribute__((aligned(4))) uint8_t serial_id_desc[] = {0x14, /* bLength */
0x03, /* bDescriptorType */
'0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '1', 0};
__attribute__((aligned(4))) uint8_t cdcacm_func_desc[] = {
0x20, /* bLength */
0x03, /* bDescriptorType */
'M', 0, 'A', 0, 'X', 0, '3', 0, '2', 0, '6', 0, '5', 0, '0', 0, ' ', 0, 'C', 0, 'D', 0, 'C', 0, 'A', 0, 'C', 0, 'M', 0,
};
#ifdef __cplusplus
}
#endif
/* **** Definitions **** */
#define EVENT_ENUM_COMP MAXUSB_NUM_EVENTS
#define EVENT_REMOTE_WAKE (EVENT_ENUM_COMP + 1)
#define BUFFER_SIZE 64
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
/* **** Global Data **** */
volatile int configured;
volatile int suspended;
volatile unsigned int usb_event_flags;
int remote_wake_en;
/* **** Function Prototypes **** */
static int setconfig_callback(MXC_USB_SetupPkt *sud, void *cbdata);
static int setfeatureCallback(MXC_USB_SetupPkt *sud, void *cbdata);
static int clrfeatureCallback(MXC_USB_SetupPkt *sud, void *cbdata);
static int event_callback(maxusb_event_t evt, void *data);
static void usbAppSleep(void);
static void usbAppWakeup(void);
static int usb_read_callback(void);
int usbStartupCallback(void);
int usbShutdownCallback(void);
/* **** File Scope Variables **** */
/* This EP assignment must match the Configuration Descriptor */
static acm_cfg_t acm_cfg = {
2, /* EP OUT */
MXC_USBHS_MAX_PACKET, /* OUT max packet size */
3, /* EP IN */
MXC_USBHS_MAX_PACKET, /* IN max packet size */
4, /* EP Notify */
MXC_USBHS_MAX_PACKET, /* Notify max packet size */
};
static volatile int usb_read_complete;
/* User-supplied function to delay usec micro-seconds */
void usb_delay_us(unsigned int usec) {
/* mxc_delay() takes unsigned long, so can't use it directly */
MXC_Delay(usec);
}
/* ************************************************************************** */
int init_usb(void) {
maxusb_cfg_options_t usb_opts;
printf("\n\n***** USB CDC-ACM Example *****\n");
printf("Waiting for VBUS...\n");
/* Initialize state */
configured = 0;
suspended = 0;
usb_event_flags = 0;
remote_wake_en = 0;
/* Start out in full speed */
usb_opts.enable_hs = 1;
usb_opts.delay_us = usb_delay_us; /* Function which will be used for delays */
usb_opts.init_callback = usbStartupCallback;
usb_opts.shutdown_callback = usbShutdownCallback;
/* Initialize the usb module */
if (MXC_USB_Init(&usb_opts) != 0) {
printf("usb_init() failed\n");
while (1) {
}
}
/* Initialize the enumeration module */
if (enum_init() != 0) {
printf("enum_init() failed\n");
while (1) {
}
}
/* Register enumeration data */
enum_register_descriptor(ENUM_DESC_DEVICE, (uint8_t *)&device_descriptor, 0);
enum_register_descriptor(ENUM_DESC_CONFIG, (uint8_t *)&config_descriptor, 0);
if (usb_opts.enable_hs) {
/* Two additional descriptors needed for high-speed operation */
enum_register_descriptor(ENUM_DESC_OTHER, (uint8_t *)&config_descriptor_hs, 0);
enum_register_descriptor(ENUM_DESC_QUAL, (uint8_t *)&device_qualifier_descriptor, 0);
}
enum_register_descriptor(ENUM_DESC_STRING, lang_id_desc, 0);
enum_register_descriptor(ENUM_DESC_STRING, mfg_id_desc, 1);
enum_register_descriptor(ENUM_DESC_STRING, prod_id_desc, 2);
enum_register_descriptor(ENUM_DESC_STRING, serial_id_desc, 3);
enum_register_descriptor(ENUM_DESC_STRING, cdcacm_func_desc, 4);
/* Handle configuration */
enum_register_callback(ENUM_SETCONFIG, setconfig_callback, NULL);
/* Handle feature set/clear */
enum_register_callback(ENUM_SETFEATURE, setfeatureCallback, NULL);
enum_register_callback(ENUM_CLRFEATURE, clrfeatureCallback, NULL);
/* Initialize the class driver */
if (acm_init(&config_descriptor.comm_interface_descriptor) != 0) {
printf("acm_init() failed\n");
while (1) {
}
}
/* Register callbacks */
MXC_USB_EventEnable(MAXUSB_EVENT_NOVBUS, event_callback, NULL);
MXC_USB_EventEnable(MAXUSB_EVENT_VBUS, event_callback, NULL);
acm_register_callback(ACM_CB_READ_READY, usb_read_callback);
usb_read_complete = 0;
/* Start with USB in low power mode */
usbAppSleep();
NVIC_EnableIRQ(USB_IRQn);
return 0;
}
void acm_writef(const char *str) {
int len = strnlen(str, 1024);
if (len >= 1020) {
printf("Warning Non string found in acm_write!\n");
char buf[1024];
memcpy(buf, str, 1024);
buf[1023] = 0;
printf("The string is %s\n", buf);
return;
}
acm_write((uint8_t *)str, len);
}
char usb_buf[1024];
int usb_pos = 0;
void do_cmd(char *cmd) {
char buf[1024];
snprintf(buf, sizeof(buf) - 2, "Got cmd: %s\r\n", cmd);
acm_writef(buf);
switch (cmd[0]) {
case 'h':
acm_writef(
"Commands:\r\n"
" 1-4: switch channel\r\n"
" f: show free flash size\r\n"
" e: erase flash\r\n"
" u[number]-[number]: upload flash data in select range\r\n"
" e.g. u1500-2500 to upload 1000 data points from 1500\r\n"
" s[number]: start to record into flash, max [number] items\r\n"
" e.g. s4000 to record for 4s (1khz sps)\r\n\r\n"
);
break;
case '1':
cur_channel = 0;
max11261_adc_set_channel(MAX11261_ADC_CHANNEL_0);
max11261_adc_convert_prepare();
printf("Switched to channel 1\n");
acm_writef("Switched to channel 1\r\n");
break;
case '2':
cur_channel = 1;
max11261_adc_set_channel(MAX11261_ADC_CHANNEL_1);
max11261_adc_convert_prepare();
printf("Switched to channel 2\n");
acm_writef("Switched to channel 2\r\n");
break;
case '3':
cur_channel = 2;
printf("Switched to channel 3\n");
acm_writef("Switched to channel 3\r\n");
break;
case '4':
cur_channel = 3;
printf("Switched to channel 4\n");
acm_writef("Switched to channel 4\r\n");
break;
case 'f':
snprintf(buf, sizeof(buf) - 2, "Flash Allocated: %d\r\nFree: %d\r\n", flash_pointer - flash_base, flash_size - (flash_pointer - flash_base));
printf("%s", buf);
acm_writef(buf);
break;
case 'e':
if (flash_operating) {
printf("Flash busy\n");
return;
}
printf("Erase flash\n");
acm_writef("Erasing...\r\n");
erase_flash();
printf("Erase flash done\n");
acm_writef("Erase done.\r\n");
break;
case 'u': {
if (flash_operating) {
printf("Flash busy\n");
return;
}
const char *p = cmd + 1;
while (*p >= '0' && *p <= '9') {
p++;
}
if (*p != '-') {
acm_writef("wrong syntax\r\n");
return;
}
const char *q = p + 1;
while (*q >= '0' && *q <= '9') {
q++;
}
int start = atoi(cmd + 1);
int end = atoi(p + 1);
if (start >= flash_size || end <= start || end >= flash_size) {
acm_writef("range overflow\n");
return;
}
printf("Start dump data: %d to %d, %08x to %08x, size %d\n", start, end, (uint32_t)(flash_base + start), (uint32_t)(flash_base + end), (end - start) * sizeof(uint32_t));
acm_writef("Start dump data:\r\n\r\n");
acm_write((uint8_t *)(flash_base + start), (end - start) * sizeof(uint32_t));
acm_writef("\r\n\r\n\r\n\r\n\r\nDone uploading.\r\n");
} break;
case 's': {
if (flash_operating) {
printf("Flash busy\n");
return;
}
const char *p = cmd + 1;
while (*p >= '0' && *p <= '9') {
p++;
}
if (*p != ',') {
acm_writef("wrong syntax\r\n");
return;
}
const char *q = p + 1;
while (*q >= '0' && *q <= '9') {
q++;
}
int number = atoi(cmd + 1);
int step = atoi(p + 1);
flash_operating_step = step;
flash_operating = number;
snprintf(buf, sizeof(buf) - 2, "Start recording %d samples with step %d...\r\n", number, step);
printf("%s", buf);
acm_writef(buf);
} break;
}
}
void usb_cycle(void) {
if (usb_event_flags) {
/* Display events */
if (MXC_GETBIT(&usb_event_flags, MAXUSB_EVENT_NOVBUS)) {
MXC_CLRBIT(&usb_event_flags, MAXUSB_EVENT_NOVBUS);
printf("VBUS Disconnect\n");
} else if (MXC_GETBIT(&usb_event_flags, MAXUSB_EVENT_VBUS)) {
MXC_CLRBIT(&usb_event_flags, MAXUSB_EVENT_VBUS);
printf("VBUS Connect\n");
} else if (MXC_GETBIT(&usb_event_flags, MAXUSB_EVENT_BRST)) {
MXC_CLRBIT(&usb_event_flags, MAXUSB_EVENT_BRST);
printf("Bus Reset\n");
} else if (MXC_GETBIT(&usb_event_flags, MAXUSB_EVENT_BRSTDN)) { ///
MXC_CLRBIT(&usb_event_flags, MAXUSB_EVENT_BRSTDN);
printf("Bus Reset Done: %s speed\n", (MXC_USB_GetStatus() & MAXUSB_STATUS_HIGH_SPEED) ? "High" : "Full");
} else if (MXC_GETBIT(&usb_event_flags, MAXUSB_EVENT_SUSP)) {
MXC_CLRBIT(&usb_event_flags, MAXUSB_EVENT_SUSP);
printf("Suspended\n");
} else if (MXC_GETBIT(&usb_event_flags, MAXUSB_EVENT_DPACT)) {
MXC_CLRBIT(&usb_event_flags, MAXUSB_EVENT_DPACT);
printf("Resume\n");
} else if (MXC_GETBIT(&usb_event_flags, EVENT_ENUM_COMP)) {
MXC_CLRBIT(&usb_event_flags, EVENT_ENUM_COMP);
printf("Enumeration complete...\n");
} else if (MXC_GETBIT(&usb_event_flags, EVENT_REMOTE_WAKE)) {
MXC_CLRBIT(&usb_event_flags, EVENT_REMOTE_WAKE);
printf("Remote Wakeup\n");
}
}
int chars;
const int max_can_read = sizeof(usb_buf) - usb_pos - 10;
if ((chars = acm_canread()) > 0) {
if (chars > max_can_read) {
chars = max_can_read;
}
char *p = usb_buf + usb_pos;
/* Read the data from USB */
if (acm_read((uint8_t *)p, chars) != chars) {
printf("acm_read() failed\n");
return;
}
*(p + chars) = '\0';
/* Echo it back */
if (acm_present()) {
if (acm_write((uint8_t *)p, chars) != chars) {
printf("acm_write() failed\n");
}
}
int i = 0;
char *q = usb_buf;
for (; i < chars; i++, p++) {
if (*p == '\n' || *p == '\r') {
*p = 0;
if (p > q) {
do_cmd(q);
}
q = p + 1;
}
}
usb_pos = p - q;
if (q != usb_buf) {
p--;
for (char *p2 = usb_buf + usb_pos - 1; p >= q; p--, p2--) {
*p2 = *p;
}
}
}
}
/******************************************************************************/
int usbStartupCallback(void) {
// Startup the HIRC96M clock if it's not on already
if (!(MXC_GCR->clk_ctrl & MXC_F_GCR_CLK_CTRL_HIRC96_EN)) {
MXC_GCR->clk_ctrl |= MXC_F_GCR_CLK_CTRL_HIRC96_EN;
if (MXC_SYS_Clock_Timeout(MXC_F_GCR_CLK_CTRL_HIRC96_RDY) != E_NO_ERROR) {
return E_TIME_OUT;
}
}
MXC_SYS_ClockEnable(MXC_SYS_PERIPH_CLOCK_USB);
return E_NO_ERROR;
}
/******************************************************************************/
int usbShutdownCallback(void) { return MXC_SYS_USBHS_Shutdown(); }
/* ************************************************************************** */
static int setconfig_callback(MXC_USB_SetupPkt *sud, void *cbdata) {
/* Confirm the configuration value */
if (sud->wValue == config_descriptor.config_descriptor.bConfigurationValue) {
configured = 1;
MXC_SETBIT(&usb_event_flags, EVENT_ENUM_COMP);
acm_cfg.out_ep = config_descriptor.endpoint_descriptor_4.bEndpointAddress & 0x7;
acm_cfg.out_maxpacket = config_descriptor.endpoint_descriptor_4.wMaxPacketSize;
acm_cfg.in_ep = config_descriptor.endpoint_descriptor_5.bEndpointAddress & 0x7;
acm_cfg.in_maxpacket = config_descriptor.endpoint_descriptor_5.wMaxPacketSize;
acm_cfg.notify_ep = config_descriptor.endpoint_descriptor_3.bEndpointAddress & 0x7;
acm_cfg.notify_maxpacket = config_descriptor.endpoint_descriptor_3.wMaxPacketSize;
return acm_configure(&acm_cfg); /* Configure the device class */
} else if (sud->wValue == 0) {
configured = 0;
return acm_deconfigure();
}
return -1;
}
/* ************************************************************************** */
static int setfeatureCallback(MXC_USB_SetupPkt *sud, void *cbdata) {
if (sud->wValue == FEAT_REMOTE_WAKE) {
remote_wake_en = 1;
} else {
/* Unknown callback */
return -1;
}
return 0;
}
/* ************************************************************************** */
static int clrfeatureCallback(MXC_USB_SetupPkt *sud, void *cbdata) {
if (sud->wValue == FEAT_REMOTE_WAKE) {
remote_wake_en = 0;
} else {
/* Unknown callback */
return -1;
}
return 0;
}
/* ************************************************************************** */
static void usbAppSleep(void) { suspended = 1; }
/* ************************************************************************** */
static void usbAppWakeup(void) { suspended = 0; }
/* ************************************************************************** */
static int event_callback(maxusb_event_t evt, void *data) {
/* Set event flag */
MXC_SETBIT(&usb_event_flags, evt);
switch (evt) {
case MAXUSB_EVENT_NOVBUS:
MXC_USB_EventDisable(MAXUSB_EVENT_BRST);
MXC_USB_EventDisable(MAXUSB_EVENT_SUSP);
MXC_USB_EventDisable(MAXUSB_EVENT_DPACT);
MXC_USB_Disconnect();
configured = 0;
enum_clearconfig();
acm_deconfigure();
usbAppSleep();
break;
case MAXUSB_EVENT_VBUS:
MXC_USB_EventClear(MAXUSB_EVENT_BRST);
MXC_USB_EventEnable(MAXUSB_EVENT_BRST, event_callback, NULL);
MXC_USB_EventClear(MAXUSB_EVENT_BRSTDN); ///
MXC_USB_EventEnable(MAXUSB_EVENT_BRSTDN, event_callback, NULL); ///
MXC_USB_EventClear(MAXUSB_EVENT_SUSP);
MXC_USB_EventEnable(MAXUSB_EVENT_SUSP, event_callback, NULL);
MXC_USB_Connect();
usbAppSleep();
break;
case MAXUSB_EVENT_BRST:
usbAppWakeup();
enum_clearconfig();
acm_deconfigure();
configured = 0;
suspended = 0;
break;
case MAXUSB_EVENT_BRSTDN: ///
if (MXC_USB_GetStatus() & MAXUSB_STATUS_HIGH_SPEED) {
enum_register_descriptor(ENUM_DESC_CONFIG, (uint8_t *)&config_descriptor_hs, 0);
enum_register_descriptor(ENUM_DESC_OTHER, (uint8_t *)&config_descriptor, 0);
} else {
enum_register_descriptor(ENUM_DESC_CONFIG, (uint8_t *)&config_descriptor, 0);
enum_register_descriptor(ENUM_DESC_OTHER, (uint8_t *)&config_descriptor_hs, 0);
}
break;
case MAXUSB_EVENT_SUSP:
usbAppSleep();
break;
case MAXUSB_EVENT_DPACT:
usbAppWakeup();
break;
default:
break;
}
return 0;
}
static int usb_read_callback(void) {
usb_read_complete = 1;
return 0;
}
void USB_IRQHandler(void) { MXC_USB_EventHandler(); }
void SysTick_Handler(void) { MXC_DelayHandler(); }
第四部分:flash控制器相关逻辑,以及简单的可用空间检测。负责为数据记录功能服务。
void init_flash(void) {
printf("Init flash\n");
MXC_FLC_Init();
volatile const uint32_t *pnt = flash_base;
while (1) {
if (*pnt == 0xffffffff) {
printf("Flash have data: %d items\n", pnt - flash_base);
break;
}
pnt += 1;
if (pnt - flash_base >= flash_size) {
pnt = 0;
printf("Flash full.\n");
break;
}
}
flash_pointer = (uint32_t *)pnt;
}
void erase_flash(void) {
for (const uint32_t *pos = (const uint32_t *)flash_base; pos - flash_base < flash_size; pos += 4096) {
MXC_SYS_Crit_Enter();
MXC_FLC_PageErase((uint32_t)pos);
MXC_SYS_Crit_Exit();
MXC_Delay(10);
}
flash_pointer = flash_base;
}
int write_flash(uint32_t data) {
if (flash_pointer == NULL) {
return 1;
}
if (flash_pointer - flash_base >= flash_size) {
return 2;
}
MXC_SYS_Crit_Enter();
MXC_FLC_Write32((uint32_t)flash_pointer, data);
flash_pointer += 1;
MXC_SYS_Crit_Exit();
return 0;
}
另外,main.c中还有一些中断服务函数的定义,略去不表。
最后一部分是i2c4.c,其中包含了AHT20通信的I2C逻辑,以及AHT20校准与测量的命令发送、接收和数据处理。
#include <gpio.h>
#include <mxc_delay.h>
#include <stdio.h>
#define SET_BOTH_LOW() MXC_GPIO_OutClr(MXC_GPIO0, (1 << 28) | (1 << 29))
#define CLKH (MXC_GPIO1->out_en_clr = 1UL << 29)
#define CLKL (MXC_GPIO1->out_en_set = 1UL << 29)
#define SDAH (MXC_GPIO1->out_en_clr = 1UL << 28)
#define SDAL (MXC_GPIO1->out_en_set = 1UL << 28)
#define RSDA (MXC_GPIO_InGet(MXC_GPIO1, 1UL << 28) != 0)
#define DL MXC_Delay(1)
#define DL3 MXC_Delay(3)
#define DL5 MXC_Delay(5)
#define DL8 MXC_Delay(8)
#define DL13 MXC_Delay(13)
#define DL20 MXC_Delay(20)
#define DLMS MXC_Delay(1000)
/// @brief After: CLK is low
void i2c4_start(void) {
CLKH;
SDAH;
DL20;
SDAL;
DL13;
CLKL;
DL5;
}
/// @brief After: both high. Stop. needs delay.
void i2c4_stop(void) {
DL13;
SDAL;
DL5;
CLKH;
DL13;
SDAH;
}
/// @brief before: CLK low after: CLK low
/// @param ackok : bool, 1 to send ACK and 0 to send NAK
void i2c4_ack(char ackok) {
if (ackok) {
SDAL;
} else {
SDAH;
}
DL5;
CLKH;
DL13;
CLKL;
DL5;
}
/// @brief before: CLK low after: CLK low
/// @return : bool, 1 for ACK and 0 for NAK
char i2c4_readack(void) {
char result = 0;
DL3;
SDAH;
DL3;
CLKH;
DL8;
if (!RSDA) result = 1;
DL5;
CLKL;
DL5;
return result;
}
/// @brief before: CLK low after: CLK low
/// @param d byte to send
/// @return 1=ACK 0=NAK
char i2c4_wbyte(uint8_t d) {
for (int i = 0; i < 8; i++) {
if (d & 0x80)
SDAH;
else
SDAL;
DL5;
CLKH;
DL8;
CLKL;
DL3;
d <<= 1;
}
DL3;
return i2c4_readack();
}
uint8_t i2c4_rbyte(char ack) {
uint8_t ret = 0;
DL5;
SDAH;
for (int i = 0; i < 8; i++) {
ret <<= 1;
DL3;
CLKH;
DL5;
if (RSDA) ret |= 1;
DL3;
CLKL;
DL8;
}
i2c4_ack(ack);
return ret;
}
void aht20_init(void) {
uint8_t ret;
int count = 0;
SET_BOTH_LOW();
MXC_Delay(40000);
while (1) {
i2c4_start();
i2c4_wbyte(0x71);
ret = i2c4_rbyte(0);
i2c4_stop();
printf("AHT20 init readback %d: %d\n", count, ret);
count += 1;
if (ret & 0x08) {
break;
} else {
MXC_Delay(10000);
i2c4_start();
i2c4_wbyte(0x70);
i2c4_wbyte(0xbe);
i2c4_wbyte(0x08);
i2c4_wbyte(0x00);
i2c4_stop();
MXC_Delay(10000);
}
}
MXC_Delay(100000);
}
uint32_t last_temp = 0, last_humi = 0;
uint32_t last_tick = 0;
uint32_t last_run = 0;
extern uint32_t ticksUs;
/// @brief async read, auto start
/// @param is_humi 1 to read humidity, 0 to read temperature
/// @param tick must pass 1us tick!
/// @return
uint32_t aht20_read(char is_humi, uint32_t tick) {
if (last_tick == 0) {
if (tick - last_run > 2000000) {
printf("AHT20 2s rereading...\n");
i2c4_start();
i2c4_wbyte(0x70);
i2c4_wbyte(0xac);
i2c4_wbyte(0x33);
i2c4_wbyte(0x00);
i2c4_stop();
last_run = tick;
last_tick = tick;
printf("AHT20 2s rereading done in %dus\n", ticksUs - tick);
}
} else {
if (tick - last_tick >= 80000) {
printf("AHT20 80ms Check if result is ready...\n");
uint8_t ret;
i2c4_start();
i2c4_wbyte(0x71);
ret = i2c4_rbyte(1);
if ((ret & 0x80) == 0) {
printf("AHT20 80ms result ready!\n");
last_humi = 0;
last_temp = 0;
ret = i2c4_rbyte(1);
last_humi = ret << 12;
ret = i2c4_rbyte(1);
last_humi |= (ret << 4);
ret = i2c4_rbyte(1);
last_humi |= (ret >> 4);
last_temp = ((ret & 0x0f) << 16);
ret = i2c4_rbyte(1);
last_temp |= ret << 8;
ret = i2c4_rbyte(0);
last_temp |= ret;
last_tick = 0;
}
i2c4_stop();
printf("AHT20 80ms rereading done in %dus\n", ticksUs - tick);
}
}
return is_humi ? last_humi : last_temp;
}
static uint8_t calc_crc(uint8_t *data, uint8_t len) {
uint8_t i;
uint8_t byte;
uint8_t crc = 0xFF;
for (byte = 0; byte < len; byte++) /* len times */
{
crc ^= data[byte]; /* xor byte */
for (i = 8; i > 0; --i) /* one byte */
{
if ((crc & 0x80) != 0) /* if high*/
{
crc = (crc << 1) ^ 0x31; /* xor 0x31 */
} else {
crc = crc << 1; /* skip */
}
}
}
return crc; /* return crc */
}
七、测试运行
上电后调试器先初始化,而后主控芯片MAX32650初始化并启动,执行完CFS生成的外设基本初始化操作后,进入main函数。
初始化过程中,会设置屏幕显示电子森林的logo。初始化完成后,进入数据显示页面,并进入主循环。
在主循环中正常运行时,屏幕上会实时显示采集通道和采集到的数据,以及模拟条。


同时,可以连接usb,从上位机发送指令,进行通道切换、flash擦除、数据记录、数据导出等操作。


动态演示请参见项目展示视频。
八、总结与展望
本次项目探索了实验平台的开发与调试流程,熟悉了CodeFusion Studio新建项目、配置、编译、烧录运行的完整流程,特别是图形化方式配置MAX系列芯片的外设功能,并初步掌握MAX32系列的开发与外设控制。