Если вы оказались здесь, то скорее всего помните как в еще в 2022 году одним из самых важных событий в мире (DIY) была новость про микроконтроллер за 10 центов от уже известной всему миру благодаря своему USB-UART свистку CH340 компании Nanjing Qinheng Microelectronics Co., Ltd, далее WCH.

Отладку от самой WCH, плату от WeAct и даже сами камни я заказал на Али, потыкал в пару примеров и забыл. Для DIY-проектов мне гораздо больше понравились платы от WeAct с ch32x035 и ch32v203, по стоимости примерно такие же, а функционала сильно больше, но в этом году на просторах китайского маркетплейса мне стала попадаться плата с героем статьи, да еще и с USB-C на ней.

Она стоит заметно дешевле своих собратьев и на момент заказа мне обошлась за 90 рублей в сумме с доставкой, а значит, новому королю DIY - быть.

Так и родилась идея сделать свой sdk.

дешевая платка с ch32v003f4p4
дешевая платка с ch32v003f4p4

Первые проблемы

Если открыть любой мануал по работе с платой, то выяснится, что для работы с ней нужен целый отладчик, а это уже дополнительные затраты и неудобное подключение. И казалось бы, даже в Arduino IDE добавили поддержку этого микроконтроллера, но вот даже там нужно использовать программатор. Ардуино великолепна своей простотой, ведь всё, что нужно, чтобы ее программировать, — сама платка, ну, может, еще и кабель.

При этом сам по себе камень ATmega328 не имеет возможности прошивать его с завода, в него на этапе производства или уже на этапе выпуска платы зашивается бутлодер, который и позволяет загружать во внутреннюю память контроллера любую программу без необходимости использования отладчика. Чаще всего используется как раз USB-UART от компании WCH, он дает USB-интерфейс плате с контроллером, которая изначально лишена поддержки USB.

Плата у меня на руках, и основной интерес вызывает USB-порт. Для чего же он?

Например, плата от WeAct позволяет использовать его как настоящее USB-device устройство, хотя сам МК не поддерживает протокол, умельцы научились эмулировать USB-накопитель при помощи GPIO, обычных пинов или в простонародье — ногодрыгом. Это и было моей основной теорией.

Мы все заметили, на плате нет LDO на 3,3 В, есть только защитный диод по входу питания с Type-C, а для работы USB необходимо напряжение 3,3 В, получается не оно. Пока я шел домой с покупкой, думал, что, может, все-таки удастся завести USB на 5 В, ну, может, на 4,5 В за счет диода, ну или, может, в даташите описано, что внутри платы есть встроенная понижайка, ведь по картинке видно, что резисторов маловато, чтобы на них сделать два делителя.

Оказалось, что на выводы DP и DM у этого USB выведен UART. Просто пины PA5 и PA6 без какой-либо обвязки. Идея до сих пор кажется мне странной, но определенная красота в ней есть.

Для работы с этой платой нам в любом случае недостаточно обычного кабеля, но можно один раз собрать себе такой кабель, думаю, любители DIY справятся, ну или на крайний случай заказать уже готовый (с UART-ом в USB-C), чтобы прошивать им платы без возни с разъемами.

УК РФ не обязывает волка соблюдать логические уровни TTL, всё и так работает, а потому встает вопрос, какой же будем использовать загрузчик АУФ.

Загрузчик (bootloader)

Официальный репозиторий openwch примеров к контроллеру показывает пример загрузчика и софта для работы с ним. Это уже лучше, чем ничего.

загрузчик от openwch
загрузчик от openwch

Собираем, пробуем, загружаем и сразу находим минусы:

  • требуется постоянно переключаться между окнами разработки

  • нужно повторно подключать кабель и удерживать кнопку, чтобы попасть в бут

  • работает только под Windows

К моей удаче, нашелся добрый человек со знанием GO, и именно на этом языке он написал свой загрузчик, поддерживающий протокол из примера WCH, только при помощи командной строки. Он решает две проблемы из трех, что уже здорово, остальное не лень сделать и самому.

А раз уж делаем, перечислим наши пожелания ко всему проекту:

  1. Удобная среда разработки.

  2. Возможность компиляции и загрузки кода одним нажатием кнопки.

  3. Управляемый светодиод прямо на плате.

  4. Небольшой объем памяти нужен под настройки, что-то вроде EEPROM.

  5. Удобный терминал, как в Arduino IDE.

  6. Удобный plotter, как в Arduino IDE.

  7. Возможность отладки через дебаггер.

Ну и дальше по плану:

1 - Visual Studio Code

Здесь всё просто: мы выбираем самое гибкое и современное решение. К тому же, оболочку с cmake для наших микроконтроллеров уже собрали до нас, чем и пользуемся.

Однако у меня возникли проблемы с GCC от xPack: он некорректно отображает объём используемой памяти. Это некритично, но всё же неприятно. Потому я решил вытащить GCC-12 из MounRiver Studio, он работает, но чутка не open source. Постараюсь держать руку на пульсе, если удастся исправить и эту проблему, то обновлю комплект в репозитории на открытый GCC.

Настройку cmake файлов я счёл неинтересной, а единственные сложности, которые встретил, — нужно было выкинуть из startup_ch32v00x.S лишний мусор, который съедал память, а также подправить флаги сборки также для минимизации используемой flash.

2 - Task Runner

Упомянутая IDE предоставляет возможность устанавливать расширения, и в дальнейшем мы будем активно их использовать. Первым рассмотрим расширение Task Runner, которое позволяет запускать скрипты через GUI кнопочки, настраиваемые в файле tasks.json, для начала можно настроить сборку:

{
    "label": "[build]...............cmake build",
    "type": "shell",
    "command": "cmake --build ${command:cmake.buildDirectory} --target all",
    "problemMatcher": "$gcc"
}

Просто вставлю красивую картинку

красивая картинка
красивая картинка

2.1.1 - Изменение бутлодера

В текущей реализации для перепрошивки необходимо извлечь провод, нажать кнопку и вставить его обратно. Это довольно сложная процедура. Для начала попробуем упростить её.

Нам доступен только UART, поэтому нужно научиться принимать команды через него. Вариант приема сообщением в блокирующем режиме отметаем как некрутой. Остается два варианта: использовать прерывания при получении каждого байта или настроить DMA.

Первый способ предполагает использование второго потока. Следует помнить, что при длительном отключении прерываний мы можем потерять часть или всю команду. Тем не менее, этот способ рабочий, проблемы если и будут, то у людей, которые знают как их решать. Кроме того, он позволяет использовать немного меньше памяти, учитывая ограниченный объём программного пространства в 16 килобайт.

Второй вариант позволяет реализовать всю бизнес-логику обработки в main-процессе, а DMA был создан именно для этого.

Логика на базе Arduino стремится к максимальной прозрачности и простоте в работе. Именно поэтому я выбрал второй вариант.

2.1.2 - Настройка DMA

Настраиваем циклический буфер на 5 канале DMA:

DMA каналы (скрин из RM)
DMA каналы (скрин из RM)

Создаём переменную для хранения принятых байт и настраиваем регистры DMA, используя аналог HAL от WCH:

static u8 RxDmaBuffer[100] = {0};

void _usart_dma_init(void)
{
    DMA_InitTypeDef DMA_InitStructure = {0};
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    DMA_DeInit(DMA1_Channel5);
    DMA_StructInit(&DMA_InitStructure);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DATAR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)RxDmaBuffer;
    
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_BufferSize = sizeof(RxDmaBuffer);

    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;

    DMA_Init(DMA1_Channel5, &DMA_InitStructure);
    DMA_Cmd(DMA1_Channel5, ENABLE); /* USART1 Rx */
}

2.1.3 - Настройка таймера

При пустом loop() проверка происходит либо слишком часто, либо слишком медленно.Предполагаемый пользователь фреймворка должен иметь возможность использовать его без лишних сложностей. Дополнительно добавим таймер, который позволит отслеживать, сколько времени прошло с момента последнего изменения счетчика принятых байтов.

void _usart_tim_init(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure={0};

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_TIM1, ENABLE );

    TIM_TimeBaseInitStructure.TIM_Period = 60000;
    TIM_TimeBaseInitStructure.TIM_Prescaler = 48000-1;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit( TIM1, &TIM_TimeBaseInitStructure);

    TIM_CtrlPWMOutputs(TIM1, ENABLE );
    TIM_ARRPreloadConfig( TIM1, ENABLE );
    
    TIM_Cmd( TIM1, ENABLE );   
}

При такой настройке счётчик таймера будет увеличиваться каждую миллисекунду до тех пор, пока не достигнет 60 секунд. Для базового функционала приёма целых команд этого более чем достаточно.

Для фиксации команды достаточно проверить, не менялся ли счётчик байтов DMA, например, в течение 5 миллисекунд. Напомню, что мы разрабатываем простой интерфейс для отправки элементарных команд.

Код может показаться не очень красивым и интересным, но я рекомендую не обращать на это внимания и двигаться дальше. Если у вас есть идеи, как сделать его более привлекательным, вы всегда можете оставить комментарий или отправить запрос на GitHub.

static void _uart_commands_loop() {
    static uint8_t last_dma_count = 0;
    static uint8_t last_dma_check = 0;
    const uint8_t timer_check_count = 5;

    uint8_t current_dma_count = _get_dma_count();
    if (current_dma_count != last_dma_count) {
        _uart_timer_start();
    }
    last_dma_count = current_dma_count;

    if (TIM_GetCounter(TIM1) > timer_check_count) {
        _uart_timer_stop();
        char command[sizeof(RxDmaBuffer) / 2] = {0};
        if (_get_dma_string(last_dma_check, current_dma_count, sizeof(command) - 1, command) > 0) {   
            printf("try_run_uart_command [%s]\r\n", command);
            _try_run_uart_command(command);
        }
        last_dma_check = current_dma_count; 
    }
}

Для перевода микроконтроллера в режим бутлодера нужно отправить в консоль строку: "command: reboot bootloader". 

Недостатков у получившегося метода более, чем достаточно, я перечислю их за вас:

  • Если долго висеть в loop(), то мы не увидим ответа.

  • Если долго висеть в loop() в консоль могут попасть данные, не относящиеся к команде.

  • Минимальная задержка составляет 5 миллисекунд + время выполнения одного loop() после полной отправки команды.

Преимущества очевидны и перевешивают:

  • Простота.

  • Предсказуемость.

  • Проверенная эффективность.

2.1.4 Команды

Идея с командами мне показалась настолько интересной, что я решил, что неплохо было бы добавить их в юзерспейс. Если команда отличается от системной, то можно ее перехватить в методе command_callback.

void command_callback(const char* cmd)
{
	const char prefix[] = "mode ";
	if (strlen(cmd) < sizeof(prefix)) {
		printf("argument error");
		return;
	}
	if (strncmp(cmd, prefix, sizeof(prefix) - 1) == 0) {
		switch (cmd[sizeof(prefix) - 1]) {
			case '0': config.mode = 0; break;
			case '1': config.mode = 1; break;
			default: printf("argument error"); return;
		}
		save_config(&config.raw);
		printf("mode changed to %d\r\n", config.mode);
	}
}

Обратите внимание на save_config и config.mode, вероятно вы уже поняли к чему они тут.

Чтобы попасть в метод, нужно отправить строку "command: mode 1" в консоль с чипом. Я добавил пример в TaskRunner для mode 0 и 1, вам же предлагается поменять и коллбек и команды по собственному усмотрению.

Пример из файла tasks.json:

{
    "label": "⚙️ Example cmd (mode 0)",
    "type": "shell",
    "command": "python",
    "args": [
        "..\\tools\\serialsend.py",
        "COM18",
        "460800",
        "command: mode 0"
    ]
}

3.0 Светодиод

Что-то мигает
Что-то мигает

На плате имеется два светодиода: один из них служит индикатором питания, а второй может быть использован по вашему усмотрению. Светодиод подключен к порту PD1, который, в свою очередь, служит для программирования чипа через дебаггер.

Прежде чем приступить к программированию, необходимо отключить режим прошивки. Для этого следует изменить конфигурацию порта и настроить альтернативные функции.

Port Configuration Register Low (GPIOx_CFGLR)
Port Configuration Register Low (GPIOx_CFGLR)
Remap Register 1 (AFIO_PCFR1)
Remap Register 1 (AFIO_PCFR1)

И теперь в виде кода:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure = {0};
GPIOD->CFGLR &= ~( 0b11 << 6 );
u32 tmp = AFIO->PCFR1 & (~(0b111 << 24));
tmp |= 0b100 << 24;AFIO->PCFR1 |= tmp;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);

Светодиод завелся и работает, а результат есть на гифке из начала статьи.

3.1 Светодиод - минусы

Признаться, ровно с этого и началась моя работа над подобным подходом к чипу, то есть ардуино-формат. Я хотел дешевую плату со светодиодом, да и отладчик у меня есть, но так вышло, что я могу использовать либо одно, либо другое.

Пользоваться дебаггером, бегать по регистрам, выставлять брейкпоинты всё так же можно, и даже не выходя из VS Code, теряется лишь работа с портом как с GPIO. А для удобства все спорные моменты вынесены в корневой CMakeLists.txt.

CMakeLists.txt
CMakeLists.txt

Так, для подключения дебаггера нужно будет убрать дефайн USE_PROGRAMMING_PIN_AS_GPIO, закомментировав строку 11.

Если я хочу вернуться к MRS и дебаггеру мне нужно перешивать твою плату?

К счастью, не нужно. В режиме бутлодера пин PD1 не задействован, поэтому вы можете свободно подключать плату через WCH-LinkE. Чтобы перейти в этот режим, достаточно нажать единственную кнопку на плате, даже во время её работы.

4. EEPROM

Не совсем EEPROM, и даже не совсем флеш, но всё же. У ATMEGA из Arduino есть специальная область памяти, которая прекрасно подходит для хранения небольших данных.

Мне нужно примерно 10 байт для хранения таких настроек, как режим работы, частота передачи и локальные параметры. Плюс ещё около 30 байт для хранения имени пользователя и пароля.

Заявленная ёмкость памяти чипа составляет 10 000 циклов, что должно быть достаточно для изменения настроек только пользователем.

Размер страницы в ch32v003 — 64 байта (стереть меньше этого объема нельзя), а значит на все про все хватит и одной странички, и еще останется место на CRC.

Мы ограничиваем размер прошивки двумя страницами. Вторая страница нужна на случай, если во время записи в память произойдёт обрыв питания. В таком случае мы, возможно, не запишем новые настройки, но когда питание будет восстановлено, в памяти останутся хотя бы старые параметры.

Link.ld
Link.ld

И делаем обвязку вокруг этих страниц флеш в виде понятных методов, которые принимают на вход ссылку на config_t, который под капотом просто массив на 62 байта (два под флаг и crc):

read_config(&config.raw);
save_config(&config.raw);

Вот пример, который поможет понять, как это работает. Мы создаём структуру с необходимыми параметрами и объединяем её с config_t через оператор union. Это позволяет объекту raw находиться в тех же местах оперативной памяти, что и переменные, что делает его использование очень интуитивным.

union config_u {
    config_t raw;
    struct {
        uint8_t mode;
        uint8_t mac[6];
        uint8_t ipv4[4];
        char password[32];
    };
} config;

...
  read_config(&config.raw);
...

...  
  if (config.mode == 0) {		
    delay(100);	
  } else if (config.mode == 1) {		
    delay(500);	
  }
...

Также пришлось поработать над бутлодером, так как оригинальный вариант полностью удаляет флеш-память, а нам нужно было оставить часть. Этот кусок я решил оставить за кадром.

Если вам интересно, вы можете изучить код бутлодера, он находится в папке tools в виде архива. Я не стал детально разбирать его, так как сам код вышел довольно колхозный, но рабочий, и тратить время на его прилизывание я не стал. Тем не менее, оригинальный загрузчик в формате exe, взятый с GitHub-репозитория openwch, корректно работает с моим бутлодером.

5. Монитор порта

Я не стал заниматься созданием велосипедов, так как VS Code предоставляет возможность добавлять любой функционал с помощью расширений. Я изучил самые популярные из них и выбрал то, что показалось мне наиболее удобным. Кстати, в этом расширении есть функция сохранения консоли в файл.

так выглядит терминал
так выглядит терминал

6. Плоттер

Тут, на удивление, аналогично. У получившегося решения гораздо больше возможностей, чем у плоттера Arduino. Оно отображает текущее значение и позволяет просматривать логи, исключая из них ненужные данные. Есть возможность разделить графики и настроить масштаб по своему усмотрению.

Для вывода значения необходимо начать строку с символа ">", передать название переменной и через двоеточие значение. В одной строке можно передавать несколько значений, разделяя их запятой.

В примере ниже я подключил потенциометр к пину D4, вывел значение с него, с Vref (на котором постоянное напряжение 1,2 В) и простейший счетчик, который увеличивается от 300 до 1000 и сбрасывается.

printf(">D4_voltage:%d\r\n", analogRead(D4));
printf(">Vref_voltage:%d\r\n", analogRead(Aref));
printf(">counter:%d\r\n", counter);
так выглядит плоттер
так выглядит плоттер

7. Отладка с дебаггером

Как уже упоминалось в пункте 3.1, идем в CMakeLists.txt и выставляем там дебаг (обратите внимание на гиф). ВАЖНО!!! для отладки нужен специальный программатор wch-linkE, обычного usb-uart недостаточно.

так выглядит дебаг
так выглядит дебаг

Я не буду углубляться в детали отладки, но вы можете убедиться, что OpenOCD работает, посмотрев значение переменной counter

Итог пессимиста

Вынужден заметить, что есть отличия от Arduino IDE, которые я так и не смог убрать:

  • Бутлодер нужно загружать вручную (или покупать плату с уже установленным бутлодером).

  • Кабель также необходимо изготавливать самостоятельно (или приобретать уже готовый).

  • Для перепрошивки требуется отключать монитор и плоттер.

  • Использование библиотеки printf и string занимает значительную часть памяти.

Последний пункт можно отключить с помощью CMake, и тогда минимальная прошивка с blink-ом будет занимать менее 2 кБайт. Все результаты далее для конфига типа Debug.

Минимальная прошивка
Минимальная прошивка

Но для ее использования придется передергивать питание и зажимать бут каждый раз при прошивке.

Можно немного изменить ситуацию и разрешить использование кнопки на плате и во время прошивки. Это может занять ещё один килобайт памяти, но зато пользоваться функцией станет удобнее. Однако это всё равно неудобно.

Кнопка бута во время работы программы
Кнопка бута во время работы программы

Ну и минимальный приятный конфиг, с которым не нужно вставать с дивана. Он занимает всего 4 килобайта, что составляет четверть доступной памяти. В этом конфиге не используется printf.

Прошивка без нажатия на кнопку
Прошивка без нажатия на кнопку

Режим сборки Release ужимает последний до 3700, потому отдельно его не привожу, чудес не будет.

Итог оптимиста

Работа с платой стала настолько простой, что мне захотелось использовать её во всех своих проектах. Или, скорее, придумывать проекты, которые можно было бы реализовать на этой плате.

Я всегда мечтал иметь возможность измерять потребление тока в диапазоне от микроампер до единиц миллиампер. Для этого я заменил шунт на модуле с INA219, расширив диапазон измерений до 1 мкА — 30 мА. На мой взгляд, это идеальный вариант для измерения потребления платы с CH582M, но вот с ESP32 такой подход уже не сработает. На гиф ниже, измеряется потребление резистора 300к.

У Алекса Гувера вышел видос про его новые умные часы и мне захотелось поиграться с лентой на ws2812 с координатами hex.

Удовольствие получено, плата оправдала все свои запросы, а проект отправляется покорять OpenSource.

Комментарии (31)


  1. dlinyj
    23.10.2024 15:23

    Очень круто, спасибо за отличную статью. Великолепно, что на хабре пишут такие классные хабратортные статьи про контроллеры.


  1. zexma
    23.10.2024 15:23

    Спасибо за статью. Укажите какие именно расширения для vscode использовались, для монитора COM порта и режима плоттера.


    1. ECRV Автор
      23.10.2024 15:23

      Они указаны в репозитории, конкретно для монитора и плоттера

      code --install-extension awsxxf.serialterminal

      code --install-extension badlogicgames.serial-plotter


  1. man55
    23.10.2024 15:23

    Что только люди не придумают, чтобы не покупать копеечный LinkE, который по одному проводу умеет одновременно прошивать, отлаживаться под openocd и еще и отладочную консоль трассировки иметь )))

    https://aliexpress.ru/item/1005005180653105.html

    Все это прикручивается в пару кликов к Eclipse, кроссплатформенно и на вкус моих фломастеров явно удобнее чем VSCode


    1. man55
      23.10.2024 15:23

      UPD: для v003 надо именно LinkE, не перепутайте с более дешевым но без поддержки v003 1-wire debug


    1. ECRV Автор
      23.10.2024 15:23

      В планах дорабатывать идею дальше, с прицелом на ch32v006, добавить камень в Arduino IDE, поддержать хотя бы Wire.h

      То есть упор делается на людей, которые хотят простого старта, отлаживаются printf-ами. Люди что работают в MRS или знают как настроить Eclipse (а MRS это разве не настроенный эклипс) знают как работать с чипом и без меня


      1. man55
        23.10.2024 15:23

        Видимо я слишком старый.
        Не понять мне, для чего надо учить людей не сказать что плохому, но реально далекому от разработки на микроконтроллерах Arduino-like подходу, хотя Arduino как явление безусловно было прорывом. Без понимания того, что внутри, в embedded делать нечего. И чем раньше новоявленный микроконтроллерщик это поймет, тем ему же и легче будет.

        У WCH весьма неплохая документация, куча примеров, порог вхождения минимальный для старта с того же HAL. Их MRS можно конечно назвать перенастроенным Eclipse, только вот реально удобнее иметь один инструмент вместо зоопарка от вендоров. И это я конечно не про Arduino IDE, а про Eclipse или VS.

        А про отладку по printf() совсем не понял. Это вообще не отладка, а трассировка, и для этого есть родной SDI_printf() для WCH - работает из коробки. А отладка для микроконтроллера - это остановиться в прерывании, вручную дернуть ножками, глянуть осфиллографом, продолжить выполнение.


        1. ECRV Автор
          23.10.2024 15:23

          Arduino хорош тем, что можно легко собрать любой пример и он заработает из коробки. И вы правы, что это не то что нужно для embedded-разработки.

          Даная статья это hello world проект. Далее я планирую сделать пару простых устройств на основе этого sdk с максимально простым повторением.

          Почти уверен, что если идея людям понравится, то сначала на авито, озоне и вб, появятся прошитые бутлодером платы, а затем и на Али их можно будет приобрести за бесценок. У школьников, самодельщиков и других людей появится возможность купить что-то, что в два раза дешевле текущей платы ардуино нано, и повторить кучу проектов из интернета на ней без особых усилий. Можно даже не знать что такое программатор


        1. Matshishkapeu
          23.10.2024 15:23

          На Ардуине много всяких примеров и проектов, от мигания светодиодом до машинного обучения. Можно сравнительно небольшими усилиями сделать проект который реально заинтересует школьников/студентов. Ардуина типа не промышленная в плане подходов, надёжности и так далее. Но проектом сделать управление для лифта или счётчика электричества на трушной элементной базе труднее заинтересовать начинающего. Мало кто хочет делать управление лифтом, все хотят робота.

          К слову, какую бы вы рекомендовали не ардуиновую платформу именно с этой точки зрения - чтобы много примеров, не конский ценник на платы, желательно существенное их разнообразие, чтоб и побольше, и поменьше, и с блютусом, и под носимые устройства и так далее.


          1. man55
            23.10.2024 15:23

            какую бы вы рекомендовали не ардуиновую платформу

            Я далек от обучения школьников и от подхода шилд-на шилд-на шилд-через переходнушку. Из микроконтроллерного пожалуй Bluepill, из wireless - наверное ESP какую-то.

            Но мне ближе подход подбора оборудования под конкретную прикладную задачу, а не наоборот.


            1. man55
              23.10.2024 15:23

              WCH кстати для старта тоже вполне себе, я взял поиграться и меня прямо зацепило. Много возможностей за копейки. Даже в сраном чипидипе они по 17 рублей.

              А вот оказывается уже братья китайцы и bluepill на WCH сварганили


              1. ECRV Автор
                23.10.2024 15:23

                Теперь вы тоже получаете удовольствие от дешёвых китайский микроконтроллеров

                Кстати и для вам текущий проект тоже полезен, можете заимствовать реализацию хранения конфига. Она простая, и ее всегда можно написать самому, а можно взять код уже готовой и продолжать получать удовольствие


                1. man55
                  23.10.2024 15:23

                  Да, Вы абсолютно правы. Но только я получаю удовольствие от контроллера, его архитектуры, особенностей периферии, а не от Вашей обертки над оберткой из HAL, от которой тянет к нехорошему. И я совершенно точно при необходимости сделать что-то, сделаю это на регистрах, прочитав даташит и одним глазком подглядев в HAL, в чем не вижу ничего зазорного.

                  Но я реально наверное старой школы. Вот мой код для CH32V003 - управление вентилятором охлаждения одной хитрой железки, к которой уже нет родных запчастей. Все на регистрах, написан и отлажен на реальном железе за полдня субботы вместе с подбором констант под железку (правда до этого вечер пятницы ушел на чтение макулатуры и примеров)

                  Собственно трэш и угар
                  #define SYSCLK 24000000L
                  #define TIMCLK 100000L
                  #define AVG_NUM 10
                  
                  #define RPM_MIN 600
                  #define RPM_MAX 1500
                  #define RPM_COEFF 30 // RPM = RPM_COEFF / period in seconds from sensor
                  
                  volatile uint32_t avg = 0, avgnum = 0;
                  
                  void TIM2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
                  
                  void TIM2_IRQHandler(void)
                  {
                  	TIM2->CNT = 0;
                  	uint16_t rpm;
                  
                  	if(TIM2->INTFR)
                  	{
                  		avg += TIM2->CH3CVR;
                  		if(++avgnum == 10)
                  		{
                  			rpm = (TIMCLK * RPM_COEFF * AVG_NUM) / avg;
                  			avgnum = 0;
                  			avg = 0;
                  
                  			if(rpm > RPM_MAX) rpm = RPM_MAX;
                  			if(rpm < RPM_MIN) rpm = RPM_MIN;
                  
                  			TIM1->ATRLR = (TIMCLK * RPM_COEFF) / rpm;
                  			TIM1->CH2CVR = (TIM1->ATRLR) >> 1;
                  		}
                  		TIM2->INTFR = 0;
                  	}
                  }
                  
                  int main(void)
                  {
                  	RCC->APB1PCENR |= RCC_TIM2EN;
                  	RCC->APB2PCENR |= RCC_IOPDEN | RCC_IOPCEN | RCC_TIM1EN;
                  
                  	TIM1->PSC = (SYSCLK / TIMCLK) - 1;
                  	TIM1->ATRLR = (TIMCLK * RPM_COEFF / RPM_MAX);
                  	TIM1->CH3CVR = (TIM1->ATRLR) >> 1;
                  	TIM1->CTLR1 |= TIM_ARPE;
                  	TIM1->CHCTLR2 &= ~TIM_OC3M_0; // CH2 PWM mode 110
                  	TIM1->CHCTLR2 |= TIM_OC3M_2 | TIM_OC3M_1 | TIM_OC3PE; // CH3 PWM mode 110, CH3 preload
                  	TIM1->CCER |= TIM_CC3E; // CH3 enable
                  	TIM1->BDTR |= TIM_MOE;
                  	TIM1->CTLR1 |= TIM_CEN; // TIM1 enable
                  
                  	TIM2->PSC = (SYSCLK / TIMCLK) - 1;
                  	TIM2->ATRLR = 0xFFFF;
                  	TIM2->CHCTLR2 |= (TIM_IC3F_3 | TIM_IC3F_2 | TIM_IC3F_1 | TIM_IC3F_0) | TIM_CC3S_0;
                  	TIM2->CTLR2 |= TIM_TI1S;
                  	TIM2->SMCFGR |= TIM_TS_TI1FP1;
                  	TIM2->INTFR = 0 ;
                  	TIM2->CCER |= TIM_CC3E;
                  	TIM2->DMAINTENR |= TIM_CC3IE;
                  	TIM2->CTLR1 |= TIM_CEN; // TIM2 enable
                  
                  	GPIOD->CFGLR &= ~(GPIO_CFGLR_CNF4_1 | GPIO_CFGLR_CNF4_0 | GPIO_CFGLR_CNF2_1 | GPIO_CFGLR_CNF2_0); // PD4 LED -> pushpull, PD2 -> pullup for extfan
                  	GPIOD->CFGLR |= (GPIO_CFGLR_MODE4_1 | GPIO_CFGLR_MODE4_0 | GPIO_CFGLR_MODE2_1 | GPIO_CFGLR_MODE2_0); // output Fmax
                  	GPIOD->OUTDR |= (GPIO_OUTDR_ODR2); // PD2 -> 1
                  
                  	GPIOC->CFGLR &= ~(GPIO_CFGLR_CNF3_0 | GPIO_CFGLR_CNF0_0); // PC0 -> input + pullup/pulldown, PC3 -> output PWM
                  	GPIOC->CFGLR |= (GPIO_CFGLR_CNF3_1 | GPIO_CFGLR_MODE3_1 | GPIO_CFGLR_MODE3_0 | GPIO_CFGLR_CNF0_1); // PC0 -> input + pullup/pulldown, PC3 -> output PWM
                  	GPIOC->OUTDR |= (GPIO_OUTDR_ODR0); // PC0 -> pullup
                  
                  	NVIC_EnableIRQ(TIM2_IRQn);
                  	__enable_irq();
                  
                  	while(1)
                  	{
                  	}
                  }
                  


                  1. sami777
                    23.10.2024 15:23

                    Прикольно! Весь код как классический SPL под STM32! Если бы не увидел уточнение, что это под CH32V003, то так бы и подумал.


                  1. COKPOWEHEU
                    23.10.2024 15:23

                    void TIM2_IRQHandler(void) attribute((interrupt("WCH-Interrupt-fast")));

                    Если нет объективной необходимости, лучше использовать обычные прерывания, чтобы можно было собрать ваш код обычным gcc, не патченным wch-ами.

                    GPIOD->CFGLR &= ~(GPIO_CFGLR_CNF4_1 | GPIO_CFGLR_CNF4_0 | GPIO_CFGLR_CNF2_1 | GPIO_CFGLR_CNF2_0); // PD4 LED -> pushpull, PD2 -> pullup for extfan

                    Лучше что-то вроде

                    #define GPIO_PP50 0b0011
                    #define GPIO_PULL 0b1000
                    GPIOD->CFGLR = (GPIOD->CFGLR &~ (0b1111<<(4*4) | 0b1111<<(2*4))) | (GPIO_PP50<<(4*4)) | (GPIO_PULL<<(4*2));
                    

                    По крайней мере, сразу видно и номер ножки, и режим. А магическое чиселко GPIO_CFGLR_CNF4_0 ничуть не лучше, чем 0x00010000, абсолютно ни о чем не говорит. Ну и само собой, шаблонную операцию x = (x &~mask) | val можно завернуть в макрос. Лично у меня это бы выглядело как-то так:

                    #define LED D,4,1,GPIO_PP50
                    #define BTN D,2,0,GPIO_PULL
                    ...
                    GPIO_config(LED);
                    GPIO_config(BTN);
                    

                    __enable_irq();

                    Это не обязательно. Стартап от wch сам включает прерывания.


          1. N-Cube
            23.10.2024 15:23

            BBC MicroBit и RPI Pico классический набор, во множестве школ по миру их используют.


        1. N-Cube
          23.10.2024 15:23

          А отладка для микроконтроллера - это остановиться в прерывании, вручную дернуть ножками, глянуть осфиллографом, продолжить выполнение.

          Ардуинщики с осциллографом в таких же отношениях, как библейский черт с ладаном. Кстати, зачем осциллограф для дискретных сигналов? Логический анализатор восьмиканальный за 3-5 баксов купить можно на али, или самому собрать на esp32 или rpi pico (осциллограф тоже можно, кстати, и даже по вайфаю работает - с мобильным приложением на телефоне или в браузере гугл хром, где вебусб поддерживается).


      1. man55
        23.10.2024 15:23

        дубль


      1. fivlabor
        23.10.2024 15:23

        Лучше уж тогда целиться на ch32v203. Стоит ненамного дороже, но там уже и периферия привычная, памяти/частоты больше, да и сам процессор следующей ступень risc-v (набор команд v4 против v2)

        В ch32v003 хорошо бы подошел как менеджер питания, но там нет RTC.

        Вообще, он pin2pin замена некогда популярного stm8s003 (речь про 20-выводной корпус). Т.е. прямой конкурент. Пусть и с заметными улучшениями, но его ниша - самые примитивные схемы.


        1. ECRV Автор
          23.10.2024 15:23

          Есть такой и в статье упониминал его же, он занимает нишу дешёвой замены bluepill и платка такого же цвета и usb аж две штуки. Я выбрал для первого проекта что попроще и самое дешевое. Все же, 10 центов есть 10 центов


        1. LAutour
          23.10.2024 15:23

          ch32v003  интересен легкопаяемыми вручную корпусами SO-8 и SO-16

          А по ch32v203, там есть один странный микроконтроллер в подходящем для самоделок корпусе TSSOP-20: CH32V203F8P6. Вот чем думали его разработчики, когда совместили на одних и тех же ножках: интерфейс программирования/отладки, единственный аппартный i2c и USB интерфейс?


          1. COKPOWEHEU
            23.10.2024 15:23

            Да с v203 они вообще упоролись. Я нашел чуть ли не единственный камень с какой-никакой периферией и удобным корпусом (лучше бы, конечно, DIP, но это уже из области фантастики) - ch32v203g8. Но и тут не без фокусов. Китайцы умудрились не вывести ножки кварца. А что еще веселее, отдали под бутлоадер UART2. Вот у всех остальных камней на UART1, а тут на UART2. И в документации об этом ни слова.

            Кстати к автору. Если хотите, чтобы вашей разработкой пользовались и для v003 и для более мощных контроллеров, и раз все равно приходится подменять загрузчик своим, стоит его сделать совместимым с более старшими. Благо протокол не слишком сложный, я его где-то год назад реверсил.


            1. Anton1906
              23.10.2024 15:23

              У CH32V203G8 шаг между ножками 0.635мм, что менее удобно, чем у CH32V203K8 с шагом 0.8мм. Адаптеры под корпус LQFP32 с шагом 0.8мм можно легко купить на алиэкспрессе. А вот для корпусов LQFP48 и LQFP64 у данной серии микроконтроллеров шаг между ножками уже всего 0.5мм.


              1. COKPOWEHEU
                23.10.2024 15:23

                CH32V203K8 с шагом 0.8мм

                А еще, смотрю, там ножки кварца выведены. И boot0 не забыли. Вы правы, надо будет посмотреть этот камень более подробно. Не знаете, у него с бутлоадером не накосячено как у g8?
                Но на счет шага зря вы так: 0.635мм развести и запаять намного проще, чем 0.5.


                1. Anton1906
                  23.10.2024 15:23

                  Про шаг 0.5 я написал потому, что хотел обратить внимание на то, что хотя тип корпуса одинаковый (LQFP), шаг между ножками бывает разный и можно случайно неправильно развести плату или купить неправильный адаптер (как я, например).
                  Бутлоадер использовать не пробовал, но в даташите про него написано : "The bootloader is stored in the system memory, and the contents of the program Flash memory storage can be reprogrammed through the USART1 and USB interface." Я предполагаю, что для этой цели должны использоваться ножки PB6 и PB7, на которых как раз есть и USB и USART1. У CH32V203G8 это должны быть 2 и 3 ножки, а у CH32V203K8 - 29 и 30. Ну и можно предположить, что в документации просто допущена ошибка, которые там периодически встречаются.


                  1. COKPOWEHEU
                    23.10.2024 15:23

                    шаг между ножками бывает разный и можно случайно неправильно развести плату или купить неправильный адаптер (как я, например).

                    Ага, мне тоже это удалось. Оказывается, шаг 0.635 это не 0.65. Хорошо, что это обнаружилось всего лишь на макетке.

                    USART1. У CH32V203G8 это должны быть 2 и 3 ножки

                    Нет. Там другая распиновка. UART1 на PA9,PA10 (25, 26), а прошивается через PA2,PA3 (11,12, которые на UART2). Ни с чем интересным они не конфликтуют.

                    Ну и можно предположить, что в документации просто допущена ошибка, которые там периодически встречаются.

                    Похоже, не в документации, а в кристалле. Уж написать код для проверки на какие ножки выведен UART1, а на какие UART2 несложно. Собственно, я бы предположил, что умудрился купить поддельный кристалл, но так и не смог представить как же именно надо было накосячить вот для такого эффекта.


                  1. COKPOWEHEU
                    23.10.2024 15:23

                    Я предполагаю, что для этой цели должны использоваться ножки PB6 и PB7, на которых как раз есть и USB и USART1. У CH32V203K8 - 29 и 30.

                    Заинтересовали вы меня этим корпусом и вот что получилось. UART1 выведен на PA9,PA10 (19,20, как почти во всех остальных камнях). SWD на PA13,PA14(23,24, ни с чем не конфликтует), USB выведены на PA11,PA12 и PB6,PB7 (21,22 и 29,30, причем USBD конфликтует с CAN, а USBFS - с I2C). Boot0 совмещен с PB8 (31). Ну и выводы кварца в наличии - PD0,PD1 (2,3).
                    На всякий случай, если там бутлоадер тоже кривой, UART2 это PA2,PA3 (8,9).
                    Что еще интересно, там упомянут UART4, но нет UART3. Но, похоже, реально и 4-го там нет, он только в C8, RB. И что-то есть подозрение, что USBFS (который на PB6,PB7) там не выведен...
                    Интересная штука, наверное, стоит купить.

                    Anton1906, вы не могли бы проверить через какой UART он реально прошивается?


                    1. Anton1906
                      23.10.2024 15:23

                      Получилось прошить микроконтроллер через 19 и 20 ножки с помощью утилиты WCHISPTool под Windows.

                      По повожу неправильного USART для прошивки у CH32V203G8 могу предположить, что производитель во время отладки новой версии бутлоадера по каким-то причинам использовал USART2, а при релизе забыл поменять. Ну или просто случайно не тот дефайн для сборки прошивки указал.


    1. LAutour
      23.10.2024 15:23

      Все это прикручивается в пару кликов к Eclipse

      Так у WCH вроде есть уже настроенная Eclipse: MounRiverStudio.


  1. Hoksmur
    23.10.2024 15:23

    Читаю:
    нашелся добрый человек со знанием GO, и именно на этом языке он написал свой загрузчик, 
    Что?! Bootloader на GOLang? Неужели настолько доработали? Перехожу по ссылке, а там
    CH32V003 UART Programmer
    Нет, всё по прежнему.


  1. fearpro13
    23.10.2024 15:23

    Для работы с ch32v003 рекомендую покупать платы разработки без USB, USB там бесполезен(могу быть не прав)
    https://aliexpress.ru/item/1005006413780514.html

    Брал такие + припаивал 3 контакта сбоку(3v3, GND, SWD/SWDIO) + 1 контакт на uart_tx

    Обязательно приобретение программатора WCH Link-E(!ОБЯЗАТЕЛЬНО С БУКОВКОЙ E!) для облегчения вашей разработки
    Там есть всё, что требуется для разработки: выводы на 3v3, uart_tx, uart_rx
    https://aliexpress.ru/item/1005005180653105.html

    Микроконтроллер имеет ограниченную поддержку в arduino IDE: на текущий момент некорректно работает навигация по коду и подсказки(насколько я понял это из-за проблем с clangd)
    Также не все библиотеки собираются под этот микроконтроллер: конкретно у меня не заработала сборка Radiohead RF_ASK и OneWire

    Для разработки можно использовать 3 разных библиотеки:

    1)Тулчейн и библиотеки от WCH
    Подгружается из mounriver IDE вроде как
    Не использовал, но знаю, что там есть "улучшенная" обработка прерываний(WCH-fast-interrupt)

    2)openwch
    https://github.com/openwch/ch32v003
    https://github.com/openwch/arduino_core_ch32

    Openwch оказался более громоздким, иногда итоговая прошивка не влезала в 16КБ флешки
    Сначала пытался использовать его, затем перешёл к ch32v003fun

    3)ch32v003fun
    https://github.com/cnlohr/ch32v003fun

    Прошивка с использованием библиотек ch32v003fun занимает значительно меньше места, после тщательной подкрутки библиотеки и мейкфайлов настроил её для работы в CLion(jetbrains)

    Я не использую Eclipse поэтому настроил всё в CLion jetbrains IDE для работы с этим контроллером

    Если вдруг кому то интересны мои наработки для работы с этим микроконтроллером в CLion IDE - могу оформить гитхаб репо
    )Поддержка c++
    )Рабочий RH_ASK(c++) библиотеки Radiohead
    )Рабочий OneWire(c++)