ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于CH32V003的PWM风扇控制器:RISC-V嵌入式开发实战

基于CH32V003的PWM风扇控制器:RISC-V嵌入式开发实战 最近在折腾一个需要精确温控的小项目发现市面上的风扇调速模块要么功能单一要么价格感人。尤其是想实现根据温度自动调节风扇转速PWM控制时常见的方案要么依赖复杂的MCU开发板要么就是简单的模拟电路调速精度和稳定性都难以保证。直到我遇到了这颗国产的“性价比怪兽”——CH32V003一款基于RISC-V内核的32位单片机售价仅2元左右却拥有令人惊喜的外设资源。本文就将手把手带你用这颗芯片打造一个功能齐全的PWM风扇控制器从原理、硬件设计、软件编程到最终调试实现完整的闭环。无论你是嵌入式新手想入门RISC-V还是老手在寻找低成本高性能的解决方案这篇文章都能让你直接复现把每一分成本都花在刀刃上。1. 项目背景与核心概念解析1.1 为什么需要PWM风扇控制器在电子设备散热中风扇是最常见的主动散热元件。然而让风扇始终全速运转不仅噪音大、耗电多而且风扇寿命也会缩短。PWMPulse Width Modulation脉冲宽度调制技术通过快速开关来控制风扇的供电从而精确调节其平均功率和转速。一个典型的4线PWM风扇除了电源和地线还有一根测速线TACH和一根PWM控制线。通过改变PWM信号的占空比高电平时间占整个周期的比例就可以线性地控制风扇转速。例如25%的占空比可能对应最低转速100%的占空比则对应最高转速。一个智能的风扇控制器可以根据温度传感器的读数动态调整PWM占空比实现“热则快转凉则慢转”的静音高效散热。这正是本项目要实现的核心目标。1.2 主角登场CH32V003J4M6CH32V003是南京沁恒微电子推出的一款超值RISC-V MCU。我们以常见的SOP8封装CH32V003J4M6为例它虽然只有8个引脚但“麻雀虽小五脏俱全”内核32位RISC-V青稞V4C最高48MHz主频。存储16KB Flash2KB SRAM。外设1个16位高级定时器支持4路PWM输出、1个16位通用定时器、1个看门狗、5通道12位ADC、I2C、SPI、USART等。价格约2元人民币极致性价比。我们的目标就是“榨干”这颗芯片的每一个引脚实现一个包含温度采集、PWM输出、转速检测、状态指示等全功能的控制器。1.3 开源硬件与RISC-V生态本项目秉承开源精神所有硬件设计原理图、PCB和软件代码都将开源。选择RISC-V架构的CH32V003也是参与和支持开源指令集生态的一种方式。与传统的ARM Cortex-M0系列相比RISC-V架构免授权费发展迅猛社区活跃是未来嵌入式开发的一个重要方向。通过这个实战项目你可以无缝入门RISC-V开发。2. 系统设计与硬件准备2.1 功能需求与引脚分配我们的全功能风扇控制器需要实现以下功能并据此分配CH32V003J4M6的8个引脚温度采集使用NTC热敏电阻或DS18B20等传感器需要1个ADC通道或1个数字IO。PWM输出驱动风扇的PWM信号线需要1个支持PWM输出的定时器通道。转速检测读取风扇的TACH测速信号需要1个支持输入捕获的定时器通道或外部中断引脚。控制接口用于设置参数或读取状态如USB、串口或按键。这里选择简单的USART串口。状态指示1个LED用于指示工作状态或故障。电源VCC和GND。引脚分配方案SOP8封装PA1 (Pin1): ADC通道1连接温度传感器如NTC分压电路。PA2 (Pin2): USART1_TX串口发送用于调试输出。PA3 (Pin3): USART1_RX串口接收用于接收命令。PD4 (Pin4): 定时器1通道3 (TIM1_CH3)作为PWM输出连接风扇PWM线。PD3 (Pin5): 定时器1通道2 (TIM1_CH2)配置为输入捕获连接风扇TACH测速线。PD2 (Pin6): 通用IO驱动一个状态LED。VCC (Pin7): 供电3.3V。GND (Pin8): 接地。这个分配方案充分利用了高级定时器TIM1产生PWM和捕获转速和USART1几乎用尽了所有引脚。2.2 硬件电路设计要点电源电路风扇通常需要5V或12V供电而CH32V003需要3.3V。因此需要设计一个电源模块将输入电压如12V降压为5V给风扇供电和3.3V给MCU供电。可以使用AMS1117-3.3等LDO。风扇接口需要一个4Pin风扇插座。注意将风扇的PWM线通过一个上拉电阻如1kΩ连接到MCU的PWM输出引脚因为开漏输出可能需要上拉。TACH线通常也是开漏输出需要上拉到3.3V。温度传感电路如果使用NTC热敏电阻需要与一个固定电阻如10kΩ组成分压电路中点接入MCU的ADC引脚。最好在ADC引脚增加一个0.1uF的滤波电容。串口电平转换如果要用电脑USB调试需要CH340等USB转TTL模块连接PA2和PA3。LED限流电阻LED需串联一个220Ω-1kΩ的限流电阻。2.3 所需材料清单CH32V003J4M6 SOP8 单片机 x1SOP8转DIP8适配板可选方便焊接 x14线PWM风扇12V或5V x1NTC热敏电阻如10kΩ, B值3950 x110kΩ 精度1% 电阻 x1AMS1117-3.3 LDO x110uF、0.1uF电容若干LED及220Ω电阻 x1USB转TTL串口模块如CH340 x1万用板或定制PCB导线若干3. 开发环境搭建与项目创建3.1 软件环境准备CH32V003可以使用沁恒官方的MounRiver Studio基于Eclipse或更轻量的VS Code PlatformIO进行开发。本文以**MounRiver Studio (MRS)**为例因为它对沁恒芯片支持最直接。下载安装MounRiver Studio从沁恒官网下载并安装。安装芯片支持包启动MRS它通常会自动管理芯片支持包。确保已安装WCH RISC-V MCU的相关支持。准备编程工具CH32V003支持串口ISP下载。你需要一个USB转TTL模块如CH340连接方式为TTL模块的TX - MCU的PD6 (Pin1? 注意需要查阅手册确认ISP引脚通常是PD6/RX)TTL模块的RX - MCU的PD5 (Pin2? 通常是PD5/TX)TTL模块的3.3V - MCU的VCCTTL模块的GND - MCU的GND关键MCU的BOOT0引脚在SOP8上是Pin4/PD4不需要查证。对于CH32V003ISP模式通常需要将BOOT0可能是PD0拉高。请务必查阅最新数据手册在上电前拉高进入下载模式。下载完成后再将BOOT0拉低从用户Flash启动。3.2 创建新工程打开MRS点击File - New - WCH RISC-V Project。选择芯片型号CH32V003和具体的子型号CH32V003J4M6。输入项目名称例如PWM_Fan_Controller。选择工程路径点击Finish。工程创建后你会看到熟悉的目录结构User文件夹下的main.c、debug.c/h等。外设库文件已包含在工程中。4. 核心代码实现与解析我们将分模块实现功能。首先在main.c中集中进行外设初始化。4.1 GPIO与时钟初始化任何操作前必须先配置系统时钟和GPIO。// File: User/main.c #include debug.h #include ch32v00x.h void GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOA, ENABLE); // PD4 as PWM Output (TIM1_CH3), Push-Pull, 10MHz GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_10MHz; GPIO_Init(GPIOD, GPIO_InitStructure); // PD3 as Input Capture for TACH (TIM1_CH2), Floating Input GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOD, GPIO_InitStructure); // PD2 as Status LED Output GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_10MHz; GPIO_Init(GPIOD, GPIO_InitStructure); // PA1 as ADC Input (Temperature) GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; // Analog Input GPIO_Init(GPIOA, GPIO_InitStructure); // PA2/PA3 as USART1 TX/RX GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_10MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); } void Clock_Init(void) { // 使用HSI 8MHz通过PLL倍频到48MHz RCC_HSICmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY) RESET); RCC_HCLKConfig(RCC_SYSCLK_Div1); RCC_PCLK1Config(RCC_HCLK_Div2); RCC_PCLK2Config(RCC_HCLK_Div1); RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_12); // 8/24, 4*1248MHz RCC_PLLCmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while(RCC_GetSYSCLKSource() ! 0x08); }4.2 PWM输出配置TIM1我们将使用TIM1的通道3产生一个频率为25kHz风扇常用PWM频率的PWM信号。void PWM_Init(uint16_t duty) { TIM_OCInitTypeDef TIM_OCInitStructure {0}; TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); // 时基单元配置 // 定时器时钟 48MHz // 目标PWM频率 25kHz 48MHz / (PSC1) / (ARR1) // 取 PSC0, ARR1919则频率 48M / (01) / (19191) ≈ 25kHz TIM_TimeBaseInitStructure.TIM_Period 1919; // ARR TIM_TimeBaseInitStructure.TIM_Prescaler 0; // PSC TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, TIM_TimeBaseInitStructure); // 输出比较配置 (PWM模式1) TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OCInitStructure.TIM_Pulse duty; // 初始占空比CCR3值 TIM_OC3Init(TIM1, TIM_OCInitStructure); // 通道3 TIM_CtrlPWMOutputs(TIM1, ENABLE); // 高级定时器必须使能主输出 TIM_Cmd(TIM1, ENABLE); } // 动态改变PWM占空比的函数 void PWM_SetDuty(uint16_t duty) { if(duty 1919) duty 1919; // 限制在ARR范围内 TIM1-CCR3 duty; // 直接操作寄存器快速更新占空比 }代码解释TIM_Period(ARR) 和TIM_Prescaler(PSC) 共同决定PWM频率。25kHz是4线PWM风扇的标准控制频率。TIM_Pulse即捕获/比较寄存器CCR的值它决定了PWM的占空比。占空比 CCR / (ARR1)。通过PWM_SetDuty函数可以实时改变占空比从而控制风扇转速。4.3 风扇转速检测TIM1输入捕获利用TIM1的通道2来捕获风扇TACH引脚输出的脉冲通常每转产生2个脉冲计算转速。volatile uint32_t capture_count 0; volatile uint32_t rpm 0; void TACH_Init(void) { TIM_ICInitTypeDef TIM_ICInitStructure {0}; // 时基配置可以与PWM共用但为简化这里单独为输入捕获配置一个时基测量周期 // 使用TIM1的通道2作为输入 TIM_ICInitStructure.TIM_Channel TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; // 上升沿捕获 TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; // 直接映射到TI2 TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; // 每个事件都捕获 TIM_ICInitStructure.TIM_ICFilter 0x0; // 不滤波 TIM_ICInit(TIM1, TIM_ICInitStructure); // 使能捕获中断 TIM_ITConfig(TIM1, TIM_IT_CC2, ENABLE); NVIC_EnableIRQ(TIM1_CC_IRQn); } // TIM1捕获比较中断服务函数 void TIM1_CC_IRQHandler(void) __attribute__((interrupt(WCH-Interrupt-fast))); void TIM1_CC_IRQHandler(void) { static uint32_t last_capture 0; uint32_t current_capture; if(TIM_GetITStatus(TIM1, TIM_IT_CC2) ! RESET) { current_capture TIM_GetCapture2(TIM1); // 获取当前的捕获值 if(last_capture ! 0) { uint32_t period 0; if(current_capture last_capture) { period current_capture - last_capture; } else { period (1919 - last_capture) current_capture; // 处理计数器溢出 } // 计算RPM: 定时器时钟48MHz每计数一次的时间 1/48M 秒 // 两个上升沿之间的时间 period * (1/48M) 秒 // 风扇每转产生2个脉冲所以转一圈的时间 2 * period * (1/48M) 秒 // RPM 60秒 / (转一圈的时间) 60 / (2 * period / 48e6) (60 * 48e6) / (2 * period) if(period 0) { rpm (60 * 48000000) / (2 * period); } } last_capture current_capture; TIM_ClearITPendingBit(TIM1, TIM_IT_CC2); } }代码解释配置TIM1通道2为输入捕获模式捕获上升沿。在中断服务程序中计算连续两次捕获之间的计数器差值这个差值代表了脉冲周期。根据定时器时钟频率和风扇每转脉冲数假设为2计算出转速RPM。这是一个简单的测速方法更稳健的做法是使用定时器的编码器接口模式或进行多次测量取平均。4.4 ADC温度采集使用ADC1的通道1对应PA1来读取NTC热敏电阻的分压值。void ADC_Init(void) { ADC_InitTypeDef ADC_InitStructure {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div8); // ADC时钟 48/8 6MHz ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; // 单通道 ADC_InitStructure.ADC_ContinuousConvMode DISABLE; // 单次转换 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStructure); // 配置ADC通道1采样时间239.5周期 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5); ADC_Cmd(ADC1, ENABLE); // ADC校准 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); } uint16_t ADC_GetValue(void) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); return ADC_GetConversionValue(ADC1); } // 将ADC值转换为温度简化版需根据NTC特性曲线校准 float ADC_To_Temperature(uint16_t adc_value) { float voltage (adc_value / 4095.0) * 3.3; // 12位ADC参考电压3.3V // 这里需要根据你的NTC分压电路和电阻-温度表进行计算 // 例如使用Steinhart-Hart方程或查表法 // 此处返回一个示例值 return 25.0 (adc_value - 2048) * 0.01; // 伪公式请替换 }4.5 USART串口通信用于输出调试信息和接收简单命令。void USART1_Init(uint32_t baudrate) { USART_InitTypeDef USART_InitStructure {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); USART_InitStructure.USART_BaudRate baudrate; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, USART_InitStructure); USART_Cmd(USART1, ENABLE); } void USART1_SendByte(uint8_t byte) { USART_SendData(USART1, byte); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); } void USART1_SendString(char *str) { while(*str) { USART1_SendByte(*str); } } // 简单的printf重定向需要启用MicroLIB或实现_write int fputc(int ch, FILE *f) { USART1_SendByte((uint8_t)ch); return ch; }4.6 主程序逻辑与温控算法将以上模块整合实现一个简单的闭环温控系统。int main(void) { uint16_t adc_val; float temperature; uint16_t target_pwm_duty 960; // 初始占空比50% (960/1920) uint16_t current_rpm 0; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); Delay_Init(); Clock_Init(); GPIO_Config(); USART1_Init(115200); ADC_Init(); PWM_Init(target_pwm_duty); TACH_Init(); USART1_SendString(\r\nCH32V003 PWM Fan Controller Started.\r\n); while(1) { // 1. 读取温度 adc_val ADC_GetValue(); temperature ADC_To_Temperature(adc_val); // 2. 简单的温控算法比例控制 // 假设目标温度是40°C #define TARGET_TEMP 40.0f #define KP 50.0f // 比例系数 float error temperature - TARGET_TEMP; int16_t duty_adjust (int16_t)(error * KP); // 3. 更新PWM占空比并限制范围 (例如 10% - 100%) target_pwm_duty duty_adjust; if(target_pwm_duty 1919) target_pwm_duty 1919; // 100% if(target_pwm_duty 192) target_pwm_duty 192; // 10% (防止风扇停转) PWM_SetDuty(target_pwm_duty); // 4. 读取当前转速 (来自全局变量由中断更新) current_rpm rpm; // 注意这是一个volatile变量中断中修改 // 5. 通过串口输出状态信息 printf(Temp: %.2f C, PWM Duty: %d, RPM: %d\r\n, temperature, target_pwm_duty, current_rpm); // 6. 根据状态控制LED (例如温度过高时闪烁) if(temperature 50.0f) { GPIO_WriteBit(GPIOD, GPIO_Pin_2, (i%105)? Bit_SET : Bit_RESET); // 快速闪烁 } else { GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_SET); // 常亮 } Delay_Ms(1000); // 每秒更新一次 } }5. 编译、下载与调试编译工程在MRS中点击编译按钮确保0错误0警告。连接硬件按照前面“开发环境搭建”部分的说明连接USB转TTL工具到MCU的ISP引脚注意BOOT0引脚状态并将风扇、温度传感器等外围电路接好。下载程序在MRS中选择Flash - Download将程序下载到芯片。下载成功后将BOOT0拉低接地复位或重新上电程序将从用户Flash启动。串口调试将USB转TTL工具的RX/TX连接到MCU的PA2(TX)/PA3(RX)。打开串口助手如MRS内置的或Putty、Xshell等设置波特率115200即可看到每秒打印的温度、PWM占空比和转速信息。功能验证用手触摸或用电吹风加热NTC电阻观察串口输出的温度值是否变化。观察PWM占空比是否随温度升高而增加。观察风扇转速是否随PWM占空比变化。观察LED状态是否符合预期。6. 常见问题与排查思路问题现象可能原因排查步骤与解决方案程序无法下载1. BOOT0引脚未正确拉高。2. 串口工具驱动问题或线接反。3. 芯片电源不正常。1. 确认上电前BOOT0为高电平通过跳线或开关控制。2. 检查设备管理器中串口COM号确认TX/RX交叉连接。3. 用万用表测量芯片VCC与GND之间是否为3.3V。风扇不转1. 风扇电源未接或接错。2. PWM信号频率不对或幅值不够。3. PWM初始占空比设置过低有些风扇有启动占空比阈值。1. 检查风扇的电源12V/5V和地线。2. 用示波器或逻辑分析仪测量PD4引脚是否有25kHz的PWM波形幅值是否为3.3V。3. 尝试将初始占空比调高如50%。串口无输出1. 串口引脚配置错误模式应为复用推挽输出和浮空输入。2. 波特率不匹配。3. USB转TTL模块故障。1. 检查GPIO_Init中PA2和PA3的配置模式。2. 确保代码中设置的波特率与串口助手设置的完全一致。3. 短接模块的TX和RX看是否能自发自收以测试模块。转速读数不准或为01. TACH引脚上拉电阻未接或阻值不对。2. 输入捕获配置错误通道、极性。3. 中断未正确启用或服务函数未实现。4. 风扇TACH信号格式不标准如开源集电极输出。1. 确保TACH线通过一个1k-10k电阻上拉到3.3V。2. 用示波器看TACH引脚是否有脉冲信号。3. 检查TIM1_CC_IRQHandler函数是否正确实现NVIC是否使能。4. 查阅风扇数据手册确认其TACH信号特性。ADC读数不稳定1. ADC引脚未配置为模拟输入(GPIO_Mode_AIN)。2. 模拟电源噪声大滤波电容不足。3. 采样时间太短。1. 确认PA1的GPIO模式。2. 在ADC引脚到地之间加一个0.1uF的滤波电容。3. 尝试增加ADC_SampleTime例如使用ADC_SampleTime_239Cycles5。温控响应不灵敏或震荡1. 比例系数KP设置不当。2. 温度采样和控制的周期太快或太慢。3. 算法过于简单没有积分和微分项。1. 调整KP值太小响应慢太大易震荡。2. 调整主循环中的Delay_Ms值如改为500ms或200ms。3. 考虑实现更成熟的PID控制算法。7. 优化与进阶玩法一个基础版本完成后可以考虑以下方向进行优化和功能增强这正是开源项目的魅力所在PID温控算法替换简单的比例控制实现更稳定、无静差的PID控制让温度控制更加精准和平滑。多种控制模式除了温控可以增加手动模式固定PWM、静音模式限制最高转速、性能模式温度墙更高等。参数存储将PID参数、温度目标值等保存到芯片内部的Flash中实现掉电保存。注意Flash的擦写寿命。更丰富的通信接口除了串口可以尝试使用I2C或SPI连接一个小型OLED屏幕实时显示温度、转速等信息。或者使用USBCH32V003支持USB FS Device与电脑通信。多路风扇控制虽然CH32V003引脚有限但它的高级定时器TIM1可以产生4路互补PWM。通过巧妙的电路设计如使用模拟开关或三极管理论上可以控制多于一路的风扇。故障保护增加风扇堵转检测长时间转速为0、温度过高报警并全速运转等保护逻辑。使用PlatformIO开发对于喜欢VS Code的开发者可以尝试配置PlatformIO开发环境利用其强大的库管理和构建系统。设计PCB将万用板上的电路优化绘制成专业的PCB可以大大提升项目的稳定性和美观度并方便分享给其他爱好者。通过这个项目你不仅得到了一个实用的风扇控制器更深入理解了RISC-V MCU的开发流程、PWM生成、输入捕获、ADC采样、串口通信等嵌入式核心技能。最重要的是你体验了如何用极低的成本核心MCU仅2元实现一个功能完备的嵌入式系统这正是开源硬件和国产芯片带给开发者的巨大红利。希望你能在此基础上继续探索创造出更多有趣的应用。
返回列表