Минимальная системная плата 沁恒CH32V307VCT6 с открытым исходным кодом

Минимальная системная плата на базе CH32V307VCT6 выводит все порты ввода-вывода. Один разъём Type-C подключён к полноскоростному OTG USB 2.0, микросхема Flash W25Q64 (64 Мбит) подключена к интерфейсу SPI2. На плате также установлен опорный источник напряжения TL432 на 1,25 В (измерено ≈ 1,246 В), который через перемычку можно подключить к входу ADC13 порта PC3. Опора 3,29 В через резистор 0 Ом может быть подключена к опорному входу АЦП VREF.

Чат по электронике/микроконтроллерам: 2169025065

Краткое описание CH32V307VCT6

Семейство CH32V — промышленные 32-битные микроконтроллеры на ядре RISC-V «QingKe». Все чипы содержат аппаратный стек и ускоренный вход в прерывания, что заметно повышает скорость отклика по сравнению со стандартом. Серия CH32V303/305/307 использует ядро V4F с поддержкой одинарной точности и обеспечивает более высокую вычислительную производительность. Частота — до 144 МГц без вставки циклов ожидания. Среди особенностей: 8 каналов USART/UART, 4 таймера для управления двигателями, встроенный USB 2.0 High-Speed (480 Мбит/с) с PHY, гигабитный Ethernet-MAC и др.

Краткое описание на сайте WCH: https://url.zeruns.com/Hcd8p
Бесплатные образцы (доставка за счёт получателя): https://url.zeruns.com/x67TF
Datasheet: https://url.zeruns.com/hDzqw (пароль: cn7c)

Фотографии





Принципиальная схема

На изображении разъём Type-C показан с ошибкой: линии CC1 и CC2 нужно по отдельности подтянуть к земле резисторами 5,1 кОм. У меня они объединены, поэтому при использовании E-mark кабеля питание может не подаваться. В проекте на платформе LCSC ошибка уже исправлена.

PCB

Верхний слой:

Опора TL432 1,25 В (≈ 1,246 В) через перемычку H8 может быть подключена к ADC13 порта PC3.
SW3 задаёт уровни BOOT0/BOOT1; оба перевести в LOW (выключено) — обычный запуск.
H9 — разъём для загрузчика WCH-Link.
H5 выбирает питание VBAT: без батареи перемычка на 3,3 В, с батареей — на BAT.

Нижний слой:

Если внешние VDDA и VREF не требуются, поставьте 0-Ом резисторы R11 и R7. Через R8/R9 можно выбрать опору VREF+: либо 3,3 В с LDO, либо 3,29 В с TL432; обычно ставят R9, второй не паяют.

Где купить компоненты

Рекомендуемый магазин компонентов — LCSC, регистрация со скидкой: https://activity.szlcsc.com/invite/D03E5B9CEAAE70A4.html

Все элементы платы есть в наличии у LCSC; в BOM-файле на платформе нажмите «Добавить всё в корзину LCSC» — компоненты автоматически окажутся в корзине.

Пример кода

Скачать полный проект: https://url.zeruns.com/2q4tX (пароль: 9527)
Открытый проект на LCSC: https://url.zeruns.com/Avda8

Демо использует Harmony LiteOS-M. Создано три задачи: LED2 мигает с периодом 500 мс, LED3 — 1 с, LED4 — 2 с (на гифке скорость увеличена в 1,5 раза).
Проект открывается в MounRiver Studio.

main.c:

/*
 * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved.
 * Copyright (c) 2020-2021 Huawei Device Co., Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 *    of conditions and the following disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "debug.h"
#include "los_tick.h"
#include "los_task.h"
#include "los_config.h"
#include "los_interrupt.h"
#include "los_debug.h"
#include "los_compiler.h"

/* Global define */
``````c
/* Глобальная переменная */
__attribute__((aligned (8))) UINT8 g_memStart[LOSCFG_SYS_HEAP_SIZE];
UINT32 g_VlaueSp=0;

u8 i = 0,j=0,k=0;

/*********************************************************************
 * @fn      taskSampleEntry3
 *
 * @brief   программа taskSampleEntry3.
 *
 * @return  none
 */
VOID taskSampleEntry3(VOID)
{
    while(1) {
      LOS_TaskDelay(2000);
      printf("taskSampleEntry3 running,task3 SP:%08x\n",__get_SP());
      GPIO_WriteBit(GPIOD, GPIO_Pin_13, (k == 0) ? (k = Bit_SET) : (k = Bit_RESET));
    }
}

/*********************************************************************
 * @fn      taskSampleEntry2
 *
 * @brief   программа taskSampleEntry2.
 *
 * @return  none
 */
VOID taskSampleEntry2(VOID)
{
    while(1) {
      LOS_TaskDelay(1000);
      printf("taskSampleEntry2 running,task2 SP:%08x\n",__get_SP());
      GPIO_WriteBit(GPIOD, GPIO_Pin_12, (j == 0) ? (j = Bit_SET) : (j = Bit_RESET));
    }
}

/*********************************************************************
 * @fn      taskSampleEntry1
 *
 * @brief   программа taskSampleEntry1.
 *
 * @return  none
 */
VOID taskSampleEntry1(VOID)
{
    while(1) {
      LOS_TaskDelay(500);
      printf("taskSampleEntry1 running,task1 SP:%08x\n",__get_SP());
      GPIO_WriteBit(GPIOD, GPIO_Pin_11, (i == 0) ? (i = Bit_SET) : (i = Bit_RESET));
    }

}
// https://blog.zeruns.com
/*********************************************************************
 * @fn      EXTI0_INT_INIT
 *
 * @brief   Инициализация EXTI0.
 *
 * @return  none
 */
void EXTI0_INT_INIT(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure={0};
  EXTI_InitTypeDef EXTI_InitStructure={0};
  NVIC_InitTypeDef NVIC_InitStructure={0};

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOA,ENABLE);

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  GPIO_Init(GPIOA, &GPIO_InitStructure);

   /* GPIOA ----> EXTI_Line0 */
  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 = EXTI0_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}

void GPIO_INIT(void) {
    GPIO_InitTypeDef GPIO_InitStructure = { 0 };

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_11|GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOD, &GPIO_InitStructure);

}

/*********************************************************************
 * @fn      taskSample
 *
 * @brief   программа taskSample.
 *
 * @return  none
 */
UINT32 taskSample(VOID) {
    UINT32 uwRet;
    UINT32 taskID1, taskID2, taskID3;
    TSK_INIT_PARAM_S stTask = { 0 };
    stTask.pfnTaskEntry = (TSK_ENTRY_FUNC) taskSampleEntry1;
    stTask.uwStackSize = 0X500;
    stTask.pcName = "taskSampleEntry1";
    stTask.usTaskPrio = 6;/* высокий приоритет */
    uwRet = LOS_TaskCreate(&taskID1, &stTask);
    if (uwRet != LOS_OK) {
        printf("create task1 failed\n");
    }

    stTask.pfnTaskEntry = (TSK_ENTRY_FUNC) taskSampleEntry2;
    stTask.uwStackSize = 0X500;
    stTask.pcName = "taskSampleEntry2";
    stTask.usTaskPrio = 7;/* низкий приоритет */
    uwRet = LOS_TaskCreate(&taskID2, &stTask);
    if (uwRet != LOS_OK) {
        printf("create task2 failed\n");
    }

    stTask.pfnTaskEntry = (TSK_ENTRY_FUNC) taskSampleEntry3;
        stTask.uwStackSize = 0X500;
        stTask.pcName = "taskSampleEntry3";
        stTask.usTaskPrio = 7;/* низкий приоритет */
        uwRet = LOS_TaskCreate(&taskID3, &stTask);
        if (uwRet != LOS_OK) {
            printf("create task3 failed\n");
        }
// https://blog.vpszj.cn
    EXTI0_INT_INIT();
    return LOS_OK;
}

/*********************************************************************
 * @fn      main
 *
 * @brief   Главная программа.
 *
 * @return  none
 */
LITE_OS_SEC_TEXT_INIT int main(void)
{
    unsigned int ret;

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
    SystemCoreClockUpdate();
    Delay_Init();
    GPIO_INIT();
	USART_Printf_Init(115200);
		
	printf("SystemClk:%d\r\n",SystemCoreClock);
	printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );

    ret = LOS_KernelInit();
    taskSample();
    if (ret == LOS_OK)
    {
        LOS_Start();
    }

    GPIO_WriteBit(GPIOC, GPIO_Pin_1,RESET);

    while (1) {
        __asm volatile("nop");
    }

}

/*********************************************************************
 * @fn      EXTI0_IRQHandler
 *
 * @brief   Обработчик прерывания EXTI0.
 *
 * @return  none
 */
void EXTI0_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void EXTI0_IRQHandler(void)
{
  /* Стек прерываний использует значение, установленное при вызове main. Разделение стека прерываний и стека потоков позволяет,
   * при переходе потока в прерывание и большой вложенности функций прерывания, избежать переполнения стека потока.
   * Однако при текущем подходе 16 регистров caller, сохраняемых компилятором, всё равно помещаются в стек потока.
   * Если требуется, чтобы регистры caller сохранялись в стеке прерываний, вход и выход прерывающей функции должны быть
   * реализованы на ассемблере, а в середине вызываться пользовательская функция обработки прерывания. Пример см. в los_exc.S
   * — функция ipq_entry.
   *  */
  GET_INT_SP();
  HalIntEnter();
  if(EXTI_GetITStatus(EXTI_Line0)!=RESET)
  {
    g_VlaueSp= __get_SP();
    printf("Run at EXTI:");
    printf("interruption sp:%08x\r\n",g_VlaueSp);
    HalDisplayTaskInfo();
    EXTI_ClearITPendingBit(EXTI_Line0);     /* Clear Flag */
  }
  HalIntExit();
  FREE_INT_SP();
}

Другие рекомендуемые open-source проекты

Рекомендуем к прочтению