เนื่องจาก CH32V003J4M6 มีจำนวนขาเพียง 8 ขาเท่านั้น เลยมีการใส่ function การทำงานของขาที่ทับซ้อนลงไปในขา ที่ 1 และ 8 เพื่อมี function ต่างๆที่จำเป็นครบถ้วนขายใน 8 ขา

ดังนั้นเวลาที่เราใช้งาน USART ที่เป็นค่าเริ่มต้นของโปรแกรมจะใช้ขา PD5/PD6 เป็นขา TX/RX ทำให้มีการส่งข้อมูลออกมาที่ขาที่ 8 เพราะมีการใช้งานรวมกัน หากเราดูตามเอกสารอ้างอิงที่ WCH ให้มา

สัญลักษณ์ ขาของ CH32V003J4M6

จากภาพและตาราง จะเห็นว่าขาที่ 8 จะมี function ที่ทับซ้อนกัน เช่น GPIO / URX / UTX / SWIO ตามรูปด้านล่าง

ภาพรายละเอียดขาต่างๆของ CH32V003J4M6

หากเราดูที่ Code เริ่มต้นการทำงานอย่างง่ายๆ เช่นตัวอย่าง code ของ GPIO

#include "debug.h"

vu8 val;

/*********************************************************************
 * @fn      main
 *
 * @brief   Main program.
 *
 * @return  none
 */
int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n",SystemCoreClock);

    while(1)
    {

    }
}

ในส่วนของ USART_Printf_Init(115200); ที่ทำหน้าที่เกี่ยวกับ USART ส่งข้อมูลไม่ว่าจะเป็น OUTPUT หรือ DEBUG ออกมา

/*********************************************************************
 * @fn      USART_Printf_Init
 *
 * @brief   Initializes the USARTx peripheral.
 *
 * @param   baudrate - USART communication baud rate.
 *
 * @return  None
 */
void USART_Printf_Init(uint32_t baudrate)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    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_Tx;

    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE);
}

ตรงส่วนที่กำหนดขาของ GPIO ที่กำหนดขา PIN GPIO_Pin_5 ซึ่งปกติถ้าคิดแบบทั่วไปเพียงเปลี่ยน จาก GPIO_Pin_5 เป็น GPIO_Pin_6 ก็ควรจะทำงานแล้ว…

แต่มันไม่สามารถทำงานได้ เพราะว่ามีค่า REGISTER กำหนดการทำงานอยู่ ต้องไปแก้ไขหรือเปิดการทำงานของ function สำรองในการเปลี่ยนขา หรือ สลับขาก่อน ซึ่งการที่จะเปลี่ยนหรือสลับขาได้ต้องไปดูเอกสารการใช้งานของ MCU CH32V003 ก่อนว่าต้องเปิดหรือตั้งค่าอะไรบ้าง

ภาพการทำงานรวมภายในของ MCU
Register ที่เกี่ยวข้องกับการสลับขา AFIO
รายละเอียดแต่ละ BIT สำหรับการตั้งค่า Remap

มีมาดูค่า REGISTER ที่จำเป็นที่จะต้องกำหนดค่า ตามตารางด้านล่าง bit ที่ 21 จะต้องมีการตั้งค่าให้ถูกต้องในการสลับขา

เมื่อไปดูในส่วนของ Code ที่กำหนดไว้จะเป็นค่าที่ define ไว้แล้ว และค่าที่เราต้องใช้ในการ Remap จากภาพด้านบน คือ ค่า 10 ที่ Remap TX/PD6, RX/PD5

เมื่อเทียบ Code ดูแล้วจะพบว่า ค่า จะได้ดังนี้

GPIO_PartialRemap1_USART1 = Remap [01]
GPIO_PartialRemap2_USART1 = Remap [10]
GPIO_FullRemap_USART1 = Remap [11] ค่าที่ define ไว้ใน code

และค่า REGISTER นี้อยู่ในส่วนของ AFIO (alternate functions) หรือ function อื่นๆ ที่รวมของใน ขานั้นๆ จำเป็นที่จะตั้งเปิด clock ให้เดียวเพื่อใช้งาน function รอง เหล่านี้

ดังนั้นในส่วนของ code จะมี function ที่กำหนดค่านี้อยู่คือ GPIO_PinRemapConfig

/*********************************************************************
 * @fn      GPIO_PinRemapConfig
 *
 * @brief   Changes the mapping of the specified pin.
 *
 * @param   GPIO_Remap - selects the pin to remap.
 *            GPIO_Remap_SPI1 - SPI1 Alternate Function mapping
 *            GPIO_PartialRemap_I2C1 - I2C1 Partial Alternate Function mapping
 *            GPIO_PartialRemap_I2C1 - I2C1 Full Alternate Function mapping
 *            GPIO_PartialRemap1_USART1 - USART1 Partial1 Alternate Function mapping
 *            GPIO_PartialRemap2_USART1 - USART1 Partial2 Alternate Function mapping
 *            GPIO_FullRemap_USART1 - USART1 Full Alternate Function mapping
 *            GPIO_PartialRemap1_TIM1 - TIM1 Partial1 Alternate Function mapping
 *            GPIO_PartialRemap2_TIM1 - TIM1 Partial2 Alternate Function mapping
 *            GPIO_FullRemap_TIM1 - TIM1 Full Alternate Function mapping
 *            GPIO_PartialRemap1_TIM2 - TIM2 Partial1 Alternate Function mapping
 *            GPIO_PartialRemap2_TIM2 - TIM2 Partial2 Alternate Function mapping
 *            GPIO_FullRemap_TIM2 - TIM2 Full Alternate Function mapping
 *            GPIO_Remap_PA12 - PA12 Alternate Function mapping
 *            GPIO_Remap_ADC1_ETRGINJ - ADC1 External Trigger Injected Conversion remapping
 *            GPIO_Remap_ADC1_ETRGREG - ADC1 External Trigger Regular Conversion remapping
 *            GPIO_Remap_LSI_CAL - LSI calibration Alternate Function mapping
 *            GPIO_Remap_SDI_Disable - SDI Disabled
 *          NewState - ENABLE or DISABLE.
 *
 * @return  none
 */
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState)
{
    uint32_t tmp = 0x00, tmp1 = 0x00, tmpreg = 0x00, tmpmask = 0x00;

    tmpreg = AFIO->PCFR1;

    tmpmask = (GPIO_Remap & DBGAFR_POSITION_MASK) >> 0x10;
    tmp = GPIO_Remap & LSB_MASK;

    if((GPIO_Remap & 0x10000000) == 0x10000000)
    {
        tmpreg &= ~((1<<1) | (1<<22));
        tmpreg |= ~DBGAFR_SDI_MASK;
        if(NewState != DISABLE)
        {
            tmpreg |= (GPIO_Remap & 0xEFFFFFFF);
        }

    }
    else if((GPIO_Remap & 0x80000000) == 0x80000000)
    {
        tmpreg &= ~((1<<2) | (1<<21));
        tmpreg |= ~DBGAFR_SDI_MASK;
        if(NewState != DISABLE)
        {
            tmpreg |= (GPIO_Remap & 0x7FFFFFFF);
        }

    }
    else if((GPIO_Remap & (DBGAFR_LOCATION_MASK | DBGAFR_NUMBITS_MASK)) == (DBGAFR_LOCATION_MASK | DBGAFR_NUMBITS_MASK))/* SDI */
    {
        tmpreg &= DBGAFR_SDI_MASK;
        AFIO->PCFR1 &= DBGAFR_SDI_MASK;

        if(NewState != DISABLE)
        {
            tmpreg |= (tmp << ((GPIO_Remap >> 0x15) * 0x10));
        }
    }
    else if((GPIO_Remap & DBGAFR_NUMBITS_MASK) == DBGAFR_NUMBITS_MASK)/* [15:0] 2bit */
    {
        tmp1 = ((uint32_t)0x03) << tmpmask;
        tmpreg &= ~tmp1;
        tmpreg |= ~DBGAFR_SDI_MASK;

        if(NewState != DISABLE)
        {
            tmpreg |= (tmp << ((GPIO_Remap >> 0x15) * 0x10));
        }
    }
    else/* [31:0] 1bit */
    {
        tmpreg &= ~(tmp << ((GPIO_Remap >> 0x15) * 0x10));
        tmpreg |= ~DBGAFR_SDI_MASK;

        if(NewState != DISABLE)
        {
            tmpreg |= (tmp << ((GPIO_Remap >> 0x15) * 0x10));
        }
    }


     AFIO->PCFR1 = tmpreg;
}

สามารถแก้ code เดิมได้ดังนี้

/*********************************************************************
 * @fn      USART_Printf_Init
 *
 * @brief   Initializes the USARTx peripheral.
 *
 * @param   baudrate - USART communication baud rate.
 *
 * @return  None
 */
void USART_Printf_Init(uint32_t baudrate)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_USART1 | RCC_APB2Periph_AFIO, ENABLE); // เปิด clock ให้กับ AFIO หากไม่เปิดจะไม่ทำงาน
    GPIO_PinRemapConfig(GPIO_PartialRemap2_USART1, ENABLE); // กำหนดค่า REMAP TX:PD6, RX:PD5

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; // กำหนดขา GPIO
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

    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_Tx;

    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE);
}
CH32V003J4M6 Fix Port TX/RX

ตัว Framework ที่ทาง WCH ให้มาเป็นค่าตั้งต้น จะว่าเขียน code ไม่ง่าย ไม่ยาก ถ้ามาจากสายของ Arduino จำเป็นต้องล้างความคิดเดิมใหม่ทั้งหมด หากมาทางสาย STM32 จะสะดวกมากกว่า

การเขียนแนวนี้มันทำให้เราลงลึกไปถึงระดับ bit และ register กันเลยที่เดียว แต่นี้ก็ถือว่าเป็นโอกาศดีที่เราสามารถเรียนรู้ได้อย่างลึกซึ้ง สามารถนำไปต่อยอดกับ MCU ค่ายอื่นๆ ที่ไม่ได้รองรับ Arduino