همه نمونه کدهای این سند مطابق استاندارد محیط MounRiver Studio II (نسخه ۲، رایگان و رسمی) نوشته شده اند؛ این IDE مبتنی بر Eclipse با کامپایلر GCC عرضه میشود و پس از انتخاب قالب پروژه CH32X035، کتابخانه استاندارد پریفرال WCH و فایل پیکربندی ch32x035_conf.h از پیش به پروژه متصل است. هر تابع کتابخانه با همان الگوی رسمی بسته EVT کار میکند: روشن کردن کلاک پریفرال روی باس مربوطه، پرکردن ساختار xxx_InitTypeDef و فراخوانی تابع.xxx_Init ساختار استاندارد پروژه:
قالب پروژه MounRiver Studio II برای CH32X035
| 1 2 3 4 5 6 7 | MyProject/ ├── User/ main.c , ch32x035_it.c (IRQ handlers) , system_ch32x035.c ├── Core/ core_riscv.h and RISC-V core files ├── Debug/ debug.c/h : Delay_Init , Delay_Ms , USART_Printf_Init ├── Peripheral/ WCH std library : ch32x035_gpio.c , ch32x035_adc.c , ... ├── Ld/ Link.ld : 62KB Flash / 20KB SRAM memory map └── ch32x035_conf.h library module selector (all peripherals by default) |
IDE — MounRiver Studio II رسمی و رایگان؛ ساخت پروژه، کامپایل و فلش از همان محیط انجام میشود؛ WCH-Link / WCH-LinkE — پروگرامر و دیباگر دو سیمه SDI؛ متصل به هدر J2؛ بسته EVT — کتابخانه استاندارد پریفرال + ده ها مثال آماده،GPIO،EXTI،USART،ADC،TIM،SPI،I2C (،DMA،OPA/CMP،TOUCHKEY،USBFS،USBPD،PIOC،PWR IWDG و…) که همه کدهای این سند با همان سبک و همان توابع نوشته شده اند؛ WCHispTool / wchisp — برنامه ریزی از طریق بوت لودر USB داخلی بدون پروگرامر؛ پشتیبانی جامعه: هسته Arduino برای CH32، پیوند Rust (ch32-hal) و PlatformIO نیز موجود است.
مطابق مثال رسمی GPIO_Toggle در LED EVT؛ روی PA0 (پایه ۱۰ مدار حداقلی). ساختار همه برنامه های WCH یکسان است: گروه بندی وقفه، به روزرسانی کلاک، راه اندازی Delay و در صورت نیاز پورت سریال دیباگ.
فایل main.c — خروجی پوش پول و تاگل هر ۵۰۰ میلی ثانیه
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /* CH32X035 - LED blink on PA0 (WCH EVT style) */ #include "debug.h" void LED_Init(void) { GPIO_InitTypeDef GPIO_InitStructure = {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); } int main(void) { u8 i = 0; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); SystemCoreClockUpdate(); Delay_Init(); LED_Init(); while (1) { Delay_Ms(500); GPIO_WriteBit(GPIOA, GPIO_Pin_0, (i == 0) ? (i = Bit_SET) : (i = Bit_RESET)); } } |
سریع ترین مسیر دیباگ، تابع آماده USART_Printf_Init است که USART1 را روی TX1=PB10 راه میاندازد و printf را به آن متصل میکند. سپس یک پیکربندی کامل USART2 روی PA2/PA3 میبینیم که الگوی استاندارد همه پریفرال های WCH را نشان میدهد: کلاک APB، پیکربندی (TX GPIO به صورت AF_PP و RX شناور)، ساختار USART_InitTypeDef و فعال سازی.
printf روی + USART1 پیکربندی USART2،TX=PA2) (RX=PA3 و ارسال/دریافت Polling
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #include "debug.h" void USART2_CFG(void) { GPIO_InitTypeDef GPIO_InitStructure = {0}; USART_InitTypeDef USART_InitStructure = {0}; RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); /* TX2 = PA2 (AF push-pull) , RX2 = PA3 (floating in) */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 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); USART_InitStructure.USART_BaudRate = 115200; 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_Tx | USART_Mode_Rx; USART_Init(USART2, &USART_InitStructure); USART_Cmd(USART2, ENABLE); } void USART2_SendString(char *s) { while (*s) { USART_SendData(USART2, *s++); while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET); } } int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); SystemCoreClockUpdate(); Delay_Init(); USART_Printf_Init(115200); /* printf via USART1 @ PB10 */ printf("SystemClk:%d\r\n", SystemCoreClock); printf("ChipID:%08x\r\n", DBGMCU_GetCHIPID()); USART2_CFG(); USART2_SendString("USART2 is alive\r\n"); while (1); } |
به جای printf از طریق UART، تابع SDI_Printf_Init هم در کتابخانه هست که خروجی printf را از رابط دیباگ SWD (بدون اشغال هیچ UART) میفرستد — انتخاب مناسب بردهای نهایی که پایه UART آزاد میخواهند.
ستون فقرات اطمینان پذیری در بردهای صنعتی، Watchdog است. کلاک IWDG از RC داخلی ۴۷kHz تأمین میشود که کاملا از کلاک اصلی مستقل است. با Prescaler=32 و Reload=4000، بازه ریست حدود ۲٫۷ ثانیه میشود (4000 ÷.)46.875kHz/32
راه اندازی IWDG و الگوی تغذیه سگ در حلقه اصلی
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include "debug.h" void IWDG_Feed_Init(u16 prer, u16 rlr) { IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); IWDG_SetPrescaler(prer); IWDG_SetReload(rlr); IWDG_ReloadCounter(); IWDG_Enable(); } int main(void) { SystemCoreClockUpdate(); Delay_Init(); USART_Printf_Init(115200); printf("IWDG test...\r\n"); IWDG_Feed_Init(IWDG_Prescaler_32, 4000); /* ~2.7s timeout */ while (1) { /* do work ... then feed the dog */ IWDG_ReloadCounter(); Delay_Ms(100); } } |
قاعده طلایی Watchdog
سگ را فقط از یک نقطه و پس از اتمام موفق کامل یک سیکل کنترلی تغذیه کنید، نه داخل تایمرهای عمومی یا وقفه ها. اگر بخشی از سیستم گیر کند ولی وقفه ها همچنان اجرا شوند، Watchdog درست کار نمیکند.
PVD (کاشف ولتاژ تغذیه) چهار آستانه دارد:،2.1V،2.3V 3.0V و.4.0V با وصل آن به خط وقفه EXTI، هر زمان ولتاژ VDD از آستانه عبور کند وقفه صادر میشود — پایۀ اصلی حفاظت در برابر افت ناگهانی تغذیه (بخش ۷.۳).
راه اندازی PVD با آستانه 3.0V و خط وقفه (مطابق مثال رسمی PVD_VoltageJudger)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #include "debug.h" volatile u8 Vdrop = 0; void PVD_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); void PVD_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line26) != RESET) { Vdrop = 1; /* VDD crossed the threshold */ EXTI_ClearITPendingBit(EXTI_Line26); } } void PVD_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); EXTI_InitTypeDef EXTI_InitStructure = {0}; NVIC_InitTypeDef NVIC_InitStructure = {0}; EXTI_InitStructure.EXTI_Line = EXTI_Line26; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = PVD_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); PWR_PVDLevelConfig(PWR_PVDLevel_3V0); /* 2.1 / 2.3 / 3.0 / 4.0 V */ } |
در کاربردهای باتری خور یا تجهیزات Always-On، بعد از هر سیکل کاری میتوان با فراخوانی PWR_EnterSTOPMode مصرف را تا حدود Aμ۷۵ کاهش داد؛ بیدارباش با یکی از خطوط EXTI انجام میشود و چون بعد از بیداری کلاک سیستم به HSI بازمی گردد، باید SystemInit دوباره فراخوانی شود (الگوی مثال رسمی Stop_Mode):
مثال MounRiver Studio II — حالت Stop و بیدارباش با EXTI روی PA0 (مبنای:EVT) Stop_Mode
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | /* CH32X035 - Stop mode (75uA) + wake-up by EXTI0 (PA0) * MounRiver Studio II - based on EVT example "Stop_Mode" */ #include "debug.h" void EXTI0_INT_INIT( void ) /* wake-up source: falling edge on PA0 */ { GPIO_InitTypeDef GPIO_InitStructure = {0}; EXTI_InitTypeDef EXTI_InitStructure = {0}; NVIC_InitTypeDef NVIC_InitStructure = {0}; RCC_APB2PeriphClockCmd( RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA, ENABLE ); RCC_APB1PeriphClockCmd( RCC_APB1Periph_PWR, ENABLE ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init( GPIOA, &GPIO_InitStructure ); GPIO_EXTILineConfig( GPIO_PortSourceGPIOA, GPIO_PinSource0 ); EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init( &EXTI_InitStructure ); NVIC_InitStructure.NVIC_IRQChannel = EXTI7_0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init( &NVIC_InitStructure ); } int main( void ) { NVIC_PriorityGroupConfig( NVIC_PriorityGroup_1 ); SystemCoreClockUpdate(); Delay_Init(); USART_Printf_Init( 115200 ); EXTI0_INT_INIT( ); printf( "Entering STOP mode...\r\n" ); PWR_EnterSTOPMode( PWR_STOPEntry_WFI ); /* STOP: ~75uA, wake by EXTI */ SystemInit( ); /* wake-up: rebuild clocks */ SystemCoreClockUpdate( ); printf( "Wake-up! SystemClk:%d\r\n", SystemCoreClock ); while( 1 ); } |
بیشتر خرابی های میدانی بردهای میکروکنترلری ریشه در تغذیه و لبه های ورودی دارند، نه در خود چیپ. این بخش تجربه های عملی طراحی X035 برای محیط صنعتی را با تمرکز بر همان دو نقطه مرور میکند. اعداد مرجع، مستقیما از جداول دیتاشیت استخراج شده اند.
دو سناریوی کلاسیک دارید: تغذیه مستقیم ۳٫۳ ولت، یا ۵ ولت با رگولاتور خطی. در سناریوی ۵ ولت، تراز منطقی پایه ها هم ۵ ولت میشود که اتصال به شناورهای صنعتی کلاسیک (حسگرهای ۴–۲۰mA با مقاومت بار، خروجی اپن کالکتور ۲۴V با مبدل سطح) را ساده تر میکند؛ در سناریوی ۳٫۳ ولت، مصرف کمتر و سازگاری با ماژول های مدرن،RF (CAN ترانسیور ۳٫۳V) بهتر است. بودجه جریان را جدی بگیرید: چیپ در ۴۸MHz با همه پریفرال ها فقط ~۴٫۲mA مصرف میکند، اما هر پایه تا ۳۰mA منبع ۴۰mA / سینک (مقادیر مطلق) و مجموع جریان VDD حداکثر ۱۵۰mA است؛ LEDها و درایو رله را با ترانسیور جدا کنید و از پایه MCU فقط سیگنال بگیرید.
جدول — حدود مطلق که نباید شکسته شوند (از دیتاشیت)

X035 فقط یک جفت VDD/GND دارد و همین، خازن گذاری را حساس میکند: تمام جریان پالسی پریفرال ها (به ویژه USB) از یک مسیر میگذرد. ترکیب پیشنهادی 100nF X7R + Fμ4.7 تا Fμ10 است که باید در فاصله کمتر از ۵ میلی متری پایه ۴۸ و با مسیر بازگشت کوتاه به صفحه زمین قرار گیرد. قواعد چیدمان:
POR/PDR داخلی فقط راه اندازی تمیز را تضمین میکند؛ اما خطر اصلی صنعتی «افت تدریجی یا نوسانی تغذیه» است که در آن چیپ روشن میماند ولی فلش یا SRAM درست کار نمیکند. راه حل داخلی همین PVD است که چهار آستانه / ۲٫۳ / ۲٫۱ ۴٫۰ / ۳٫۰ ولت دارد. توصیه عملی:
این مهم ترین تفاوت X035 با خیلی از MCUهای قدیمی تر است: ولتاژ مجاز هر پایه حداکثر VDD + 0.3V است. اگر برد با VDD=3.3V کار کند، اتصال مستقیم سیگنال ۵ ولت (حتی از طریق مقاومت سری) به پایه، دیود ESD داخلی را به صورت دائمی هدایت میکند و خرابی زودهنگام میسازد. راهکارهای عملی:
کانکتور USB نقطه اصلی ورود ESD و نوسان ولتاژ است. چهار لایه حفاظتی را همیشه کنار هم ببینید:
برای ورودی دیجیتال از کنتاکتور و سنسورهای صنعتی، زنجیره استاندارد «مقاومت سری + فیلتر RC + Schmitt داخلی» کافی است: مقاومت سری ۱ تا ۱۰k همراه خازن ۱۰ تا ۱۰۰nF هم نویز را میگیرد هم انرژی ESD محدود را تخلیه میکند. برای ورودی های آنالوگ، دیتاشیت فرمول مقاومت مجاز منبع را میدهد؛ با کلاک ۸MHz و زمان نمونه برداری ۱ سیکل، مقاومت منبع فقط تا ~ 4.3k مجاز است و با افزایش زمان نمونه برداری (تا ۲۴۰ سیکل) به ده ها کیلواهم میرسد — پس یا زمان نمونه برداری را بلند کنید یا با بافر اپ امپ داخلی (OPA) امپدانس را پایین بیاورید.
کانال داخلی (~۱٫۲V) Vref به شما اجازه میدهد بدون قطعه اضافه، ولتاژ واقعی VDD را به صورت نسبی اندازه بگیرید: ADC آن را میخواند و چون Vref تقریبا ثابت است، نسبت خوانده به ۴۰۹۶ مستقیما VDD را میدهد (4096 × 1.2 ≈ V ÷ کد خوانده شده). این روش ارزان ترین «نظارت سلامت تغذیه» برای ثبت لاگ و هشدار میدانی است.
خواندن کانال مرجع داخلی و محاسبه VDD
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include "debug.h" void ADC_Init_Vref(void) { ADC_InitTypeDef ADC_InitStructure = {0}; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); ADC_DeInit(ADC1); ADC_CLKConfig(ADC1, ADC_CLK_Div6); /* 48/6 = 8MHz */ 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_Cmd(ADC1, ENABLE); } u16 ADC_ReadVref(void) { ADC_RegularChannelConfig(ADC1, ADC_Channel_Vrefint, 1, ADC_SampleTime_11Cycles); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); return ADC_GetConversionValue(ADC1); } /* VDD(mV) ~= 1200 * 4096 / ADC_ReadVref() — use in main loop */ |
وقتی PVD اعلام افت ولتاژ کرد، دو راه دارید: ذخیره وضعیت و رفتن به Stop Mode (Aμ۷۵) تا با بازگشت تغذیه از همان جا ادامه دهید، یا ذخیره وضعیت در فلش و ریست تمیز. حالت Stop در X035 با فراخوانی (PWR_STOPEntry_WFI)PWR_EnterSTOPMode شروع میشود و با وقفه AWU یا خط EXTI بیدار میشود؛ نکته مهم اینکه بعد از بیداری باید SystemInit دوباره فراخوانی شود تا کلاک ها بازسازی شوند. برای داده هایی که در هر قطع برق ذخیره میشوند (شمارنده کاری، وضعیت رله)، یک ساختار نسخه دار دوتایی (A/B) در دو صفحه فلش با CRC بنویسید تا یک نوشتن نیمه کاره هرگز داده را خراب نکند و فرسایش هم بین دو صفحه پخش شود (عمر ۳۰۰K سیکل در C°۲۵).
این چک لیست را برای هر برد جدید، به ترتیب و بدون پرش اجرا کنید؛ هر گام، فرضیات گام بعد را تأیید میکند:
جدول — منابع رسمی WCH

جدول — عیب یابی سریع


جمع بندی — نقشه راه پیشنهادی برای ورود به طراحی صنعتی
مسیر توصیه شده این است: ابتدا مدار حداقلی بخش ۵ را روی یک برد آزمایشی دو لایه با هدر SWD کامل بسازید و هر چهار نمونه کد بخش ۶ را روی همان برد به اجرا درآورید؛ سپس چک لیست بخش ۸ را کامل تیک بزنید و نتایج اندازه گیری (مصرف، نویز تغذیه، رفتار PVD و IWDG) را مستند کنید. در طراحی برد اصلی، قواعد چیدمان بخش ۷.۲ را به صورت چک لیست CAD اجرا کنید و مسیر CC و زوج USB را قبل از سفارش PCB با تور بسنجید. برای پروژه های PD، ابتدا سناریوی Sink با مقاومت های 5.1k را مسلط شوید و بعد به سراغ DRP و CH211 بروید؛ و در نهایت، برای هر برد تولیدی یک نسخه به روزرسانی شده از همین سند به عنوان «مستند پایه طراحی» نگه دارید تا تیم و نسخه های بعدی برد از تصمیم های شما شروع کنند نه از صفر.
سخن پایانی
این سند مرور کاملی بر معماری، امکانات و نقشه ۴۸ پایه میکروکنترلر CH32X035C8T6 ارائه میدهد و مدار حداقلی، کدهای شروع کار با کتابخانه استاندارد WCH و مجموعه ای از نکات کلیدی طراحی صنعتی با تمرکز بر تغذیه و حفاظت را پوشش میدهد. تمام مشخصات بر پایه دیتاشیت رسمی نسخه ۲.۳ شرکت WCH استخراج و بازبینی شده است.
من حسن معماری , طراح محصول یا Product Designer هستم. و محصولات یا دستگاه های مختلف سخت افزاری بر پایه الکترونیک , کامپیوتر و هوش مصنوعی طراحی میکنم. شرکت معمار الکترونیک صنعت
سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.