Найти в Дзене
Terrabyte

Китайские микроконтроллеры CH32V003 на практике: особенности, проблемы и находки

Привет, друзья! Прошло почти три месяца с тех пор как я начал использовать китайские микроконтроллеры серии CH32V003 на ядре RISC-V в своих самоделках. За это время микрочипы из страшных и неизведанных зверьков превратились для меня в милых домашних питомцев. Оказалось, что ничего страшного в них нет, архитектура почти полностью копирует STM-овскую, а базового знания языка Си вполне достаточно, чтобы начать прикладное использование микроконтроллеров в различных устройствах. Для любителей среды Ардуино переход тоже вполне ровный. Все необходимое, для того, чтобы продолжить использование любимой IDE имеется. С другой стороны нельзя в очередной раз не отметить цену и мощь микроконтроллеров CH32V003, которые полностью окупают интеллектуальные вложения в освоение возможностей новых кристаллов. Для тех, кто подобно мне до последнего цеплялся за ATtiny13, есть хорошие новости - бросайте это дело, а то разоритесь! Соотношение цены 1 к 5 и явно не в пользу Тинек! Ну а про соотношение возможност
Оглавление

Привет, друзья!

Прошло почти три месяца с тех пор как я начал использовать китайские микроконтроллеры серии CH32V003 на ядре RISC-V в своих самоделках. За это время микрочипы из страшных и неизведанных зверьков превратились для меня в милых домашних питомцев. Оказалось, что ничего страшного в них нет, архитектура почти полностью копирует STM-овскую, а базового знания языка Си вполне достаточно, чтобы начать прикладное использование микроконтроллеров в различных устройствах. Для любителей среды Ардуино переход тоже вполне ровный. Все необходимое, для того, чтобы продолжить использование любимой IDE имеется.

С другой стороны нельзя в очередной раз не отметить цену и мощь микроконтроллеров CH32V003, которые полностью окупают интеллектуальные вложения в освоение возможностей новых кристаллов. Для тех, кто подобно мне до последнего цеплялся за ATtiny13, есть хорошие новости - бросайте это дело, а то разоритесь! Соотношение цены 1 к 5 и явно не в пользу Тинек! Ну а про соотношение возможностей и писать-то неприлично! Оперативной памяти в 30 раз больше, памяти программ в 16, быстродействие, и т.п. И это при том, что CH32V003 есть в таком же 8-пиновом SOP-корпусе, который у меня лично вызывает чувство душевной расположенности к изделию, несмотря на вечную нехватку портов ввода-вывода!

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

Сегодня расскажу о вещах, с коими придется столкнуться любому прикладнику, для которого глубина изучения контроллера определяется уровнем решаемых задач. Это я к тому, что не очень-то я люблю копаться в документации просто так. Обычно мне нужен стимул. То есть, задача, ради которой можно и в datasheet-ы погрузиться. И вот в ходе решения именно таких задач выясняются, как правило, интересные подробности, неочевидные с первого взгляда, но важные с точки зрения сохранения сил и нервов. На их изучение жуть как не хочется тратить время. Время хочется тратить на саму задачу и получать от ее решения удовольствие! А получается, что полдня ворошишь документацию только для того, чтобы выяснить, что в ней чего-то не хватает!

Возможно, те заметки, которые последуют дальше, помогут кому-то сохранить время, а кому-то и нервы!

Чего лишили 8-пиновый чип (CH32V003J4M6)

Я уже говорил, что питаю слабость к контроллерам с небольшим количеством ножек. Повторюсь, что это скорее спортивный интерес, стимулирующий инженерное творческое начало. Тем не менее базовый вариант микроконтроллера серии CH32V003 подразумевает 20-пиновое исполнение. Либо в 2-рядном корпусе TSSOP-20 (версия CH32V003F4P6, шаг между контактами 0,65 мм) либо в 4-стороннем QFN-20 (версия CH32V003F4U6). Для тех, кто не любит паять близко расположенные контакты есть так же 16-пиновый вариант в корпусе SOP-16 (версия CH32V003A4M6). Хотя 4 ножки и были выкинуты на помойку, функциональность не особенно и пострадала. А вот в случае с 8-пиновым исполнением (моя любимая версия CH32V003J4M6) все не так радужно!

Дело в том, что во всех версиях используется один и тот же 20-выводный кристалл. При уменьшении количества ножек часть выводов кристалла остается неподключенной, а часть объединяется и подключается к одной ножке. Для наглядности я решил масштабы "бедствия", то есть схему подключения ножек к кристаллу наглядно представить на схеме.

Превращение 20-пинового кристалла в 8-пиновый корпус
Превращение 20-пинового кристалла в 8-пиновый корпус

На рисунке внешний прямоугольник (черного цвета) обозначает 8-пиновый корпус SOP-8. Заметили, что не все выводы кристалла нашли свои ножки корпуса? А это может означать, что часть функциональности также была утрачена. Попробуем разобраться, что именно пропало, помимо портов ввода-вывода. А пропало вот что.

Вывод аппаратного системного сброса NRST, ножка 4. Назначение - привести микроконтроллер в исходное состояние при помощи внешнего сигнала. Может быть не такая и нужная функция в готовом устройстве, а вот для работы с прототипами могла бы и пригодиться. Кроме того, вывод сброса, подключенный к программатору, должен помочь ему в заливке прошивки в случае "окирпичивания" микроконтроллера. Конечно мы знаем о том, как это сделать и без сброса (чуть дальше напомню), но с аппаратным сбросом было бы пожалуй удобнее. Ладно, переживем!

Альтернативные выводы операционного усилителя: OPP1 (ножка 4) и OPN1 (ножка 8). Как в лучших домах ЛондОна и Парижа, в китайском микроконтроллере предусмотрен встроенный операционный усилитель с возможностью выбора входных выводов. Его можно использовать, например, для усиления сигнала перед подачей на АЦП или для фильтрации ШИМ-сигнала. И тот и другой способ я проиллюстрировал соответствующим материалом. В качестве предварительного усилителя операционник был использован в материале про усилитель класса D:

В качестве фильтра восстановления формы сигнала - в материале про генератор:

Я привел ссылку на вторую часть материала из которой есть ссылка и на первую.

Так вот, в старших версиях корпуса есть возможность выбора входных ножек операционного усилителя (при помощи регистра EXTEND_CTR). В 8-пиновой версии такого выбора нет. Положительный вывод (OPP0) выведен только на ножку 3, отрицательный (OPN0) - на ножку 2. То есть, возможности усилителя все же остались доступны.

Кстати сказать, внешний вывод операционного усилителя (OPO) приходится на ножку 8.

Синхронный режим USART. Недоступна главная функция USART - возможность синхронной передачи данных. Без нее он превращается в обычный асинхронный UART, возможностей которого в большинстве случаев достаточно для обмена данными с внешними устройствами. Пины же синхросигнала (UCK) и аппаратного управления потоком синхронного режима (URTS и UCTS) остались неподключенными. Немудрено. Эдак все ножки одним USART-ом занять можно!

В качестве ремарки - выводы приема и передачи асинхронного UART (URX, UTX) находятся на ножках 8 и 1 соответственно.

Интерфейс SPI. А вот это удар посерьезнее! Конечно для подключения устройств по последовательной шине у нас еще остался протокол I2C (я имею ввиду аппаратную поддержку). Но, хотелось бы и SPI иметь в запасе. Ведь по нему так удобно подключать внешние микросхемы памяти большого объема. Нет, в целом с китайскими инженерами можно согласиться. Зачем в 8-пиновом корпусе последовательный интерфейс, занимающий 4 пина. Ведь тогда для всех остальных целей остается лишь два пина (еще два ушли на питание). Но мы же ведь уже знаем как максимально использовать эти два пина! Для тех, кто хочет узнать, как через два пина вводить и выводить звук, а также управлять двумя кнопками, добро пожаловать вот в эту статью.

Да, а еще в этой статье показано, как, за неимением аппаратной реализации SPI сделать ее программную эмуляцию.

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

"Окирпичивание" микроконтроллера и проблемы с драйверами в PlatformIO

Полагаю, что слово "окирпичивание" знакомо большинству микроконтрольщиков, поэтому дальше буду писать его без кавычек. И еще. Все, что дальше будет сказано относится лишь к случаю работы в среде VS-Code с установленной надстройкой PlatformIO. Эта среда позволила мне получить самый компактный код, поэтому ее и использую.

Итак, вы прошили устройство и оно даже может работать, но, повторная прошивка не проходит!

Основная проблема, которая приводит к невозможности повторной прошивки, связана с настройкой вывода 8 в качестве выхода. Дело в том, что вывод 8 используется программатором как единственная линия обмена данными с микроконтроллером. А начинается этот обмен с того, что программатор запрашивает наличие микроконтроллера. Если при этом ножка 8 будет настроена программно как выход, то программатор не сможет передать данные на микроконтроллер. Прошивка вылетает с ошибкой.

Чтобы этого не случилось, рекомендуется в начале кода программы, еще до настройки выводов, сделать небольшую задержку (5...8 секунд). Тогда достаточно будет отключить контроллер от программатора, затем подключить его снова и быстро нажать на кнопку прошивки в среде PlatformIO.

Однако, что делать если вы все же окирпичили контроллер? Тут тоже не беда, поскольку китайские производители софта предусмотрели такую возможность как в оригинальной IDE - MounRiver, так и в утилите настройки программатора - WCH-LinkUtility. В первом случае возможность раскирпичивания вшита непосредственно в оболочку IDE. А вот в случае использования PlatformIO нужно будет воспользоваться WCH-LinkUtility. И именно тут возникает небольшая проблема. Дело в том, что драйверы, через которые работают PlatformIO и WCH-LinkUtility с программатором разные. WCH-LinkUtility использует штатные драйвера программатора, а PlatformIO для загрузки требует драйвера USBserial.

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

Программатор в диспетчере устройств (штатные драйвера)
Программатор в диспетчере устройств (штатные драйвера)

С этими драйверами микроконтроллер легко раскирпичить. Достаточно запустить утилиту WCH-LinkUtility и выбрать пункт меню "Target-Clear All Code Flash - By Power Off".

Раскирпичивание микроконтроллера при помощи WCH-LinkUtility
Раскирпичивание микроконтроллера при помощи WCH-LinkUtility

Однако после этого PlatformIO не увидит программатора. В ходе прошивки будет вылетать вот такая ошибка.

Ошибка прошивки микроконтроллера (внизу, красным)
Ошибка прошивки микроконтроллера (внизу, красным)

Что бы исправить ситуацию нужно заменить драйвера. Для этого специалисты рекомендуют использовать утилиту со странным названием Zadig.

После запуска этой утилиты идем в пункт меню "Options" и выбираем "List All Devices".

Утилита Zadig
Утилита Zadig

Затем в списке выбираем пункт WCH-Link (Interface 0).

Выбор интерфейса WCH-Link
Выбор интерфейса WCH-Link

В левом окошке текущего драйвера появится надпись WCHLink_A64. Теперь в правом нужно выбрать WinUSB и запустить установку (Replace Driver). По окончании установки окно диспетчера устройств будет выглядеть так.

Окно диспетчера устройств после установки WinUSB
Окно диспетчера устройств после установки WinUSB

Как видно, добавилось добавилось устройство USB - WCH-Link. Теперь микроконтроллер можно прошивать в PlatformIO.

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

Использование цифрового анализатора для отладки программы

Разумеется написание сколь-нибудь серьезного кода становится невозможным без его отладки. Обычно для этих целей используется средство внутрисхемной отладки OpenOCD. В интернете я нашел инструкцию о том, как прикрутить отладку к среде VS-Code, однако воспользоваться ей так и не смог. Быстро не получилось, а разбираться... В общем, отложил на потом. Если кто-то разберется или есть другие ссылки - напишите, пожалуйста в комментариях.

Однако эта моя лень связана не столько с нежеланием разобраться, сколько с пониманием - даже внутрисхемная отладка кода может не дать требуемый результат. В чем тут дело? А дело в том, что работаю я, да и многие другие инженеры, с микроконтроллером для того, чтобы получить на его выходах определенные сигналы управления внешней периферией (памятью, ключами, контроллерами и т.д. Так вот, работающая программа еще не гарантирует "правильных" сигналов на выходах микроконтроллера! Выяснить что что-то не так и что не так можно лишь изучая структуру сигналов на выводах микроконтроллера.

Например, в статье про усилитель класса D (ссылка была выше), мне было очень важно получить небольшую, но нормированную (десятки наносекунд) задержку для того, чтобы предотвратить сквозной ток в полумостовом каскаде. Тут без анализа реальных сигналов не обойтись! А значит нужен цифровой анализатор. Устройство это недорогое (несколько сотен рублей) и небольшое по размеру. Вот, например, мой анализатор, подключенный для отладки к светодиодному индикатору уровня звука.

Логический анализатор цифровых сигналов
Логический анализатор цифровых сигналов

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

Задача 1: Прямой контроль структуры сигналов на выводах микроконтроллера. Это самый простой способ использования. Все, что нам нужно от анализатора в этом случае - возможность записи цифрового сигнала в память компьютера. Обычные недорогие анализаторы имеют 8 каналов записи с частотой до 24 Мегагерц. Столько, как правило, не нужно. Достаточно 2...4 канала.

Вот простой пример использования, о котором я уже упоминал.

Сигналы управления полумостовой схемой
Сигналы управления полумостовой схемой

В схеме усилителя класса D нужно было управлять двумя транзисторами, включенными в полумостовом режиме. При этом, когда один транзистор открыт, второй должен быть закрыт. Для этого нужны два инверсных сигнала. Но, если использовать обычные инвертированные сигналы, то в моменты переключения возможны ситуации, когда оба транзистора открыты. Через них начинает идти большой ток, который может вывести их из строя. Надо сделать так, чтобы сначала закрывался один транзистор и только потом открывался второй. Как раз этот случай и показан на скриншоте. Видно, что спадающий фронт импульса на канале D0 опережает восходящий фронт на канале D1 на некоторую величину задержки - защитный интервал. Технически сделано это при помощи работы с таймером TIM2. Подробнее можно посмотреть в статье про усилитель.

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

Задача 2. Контроль сигналов стандартных протоколов. Еще одна важная задача, которую позволяет решить анализатор. Очень часто периферия подключается к микроконтроллеру при помощи стандартных интерфейсов: IIC, SPI, UART и т.п. При этом нередки ситуации, когда программа работает, данные отправляются, а устройство не работает так как нужно. А если еще и интерфейс эмулируется программно, то вообще непонятно, где искать ошибку - в некорректной эмуляции, или не тех отправляемых данных.

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

Анализ сигналов протокола I2C
Анализ сигналов протокола I2C

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

А вот так выглядит обмен по программно-эмулированному протоколу SPI.

Обмен данными по протоколу SPI
Обмен данными по протоколу SPI

Опять же все на виду! Сможет это внутрисхемная отладка? Разумеется нет! Но как быть если все же надо проникнуть вглубь кода программы, например оценить изменение переменной. Ага! И в этом нам может помочь цифровой анализатор!

Задача 3. Получение информации о переменных программы. Собственно идея лежит на виду. Я более чем уверен, что к подобного рода трюкам прибегал любой программист микроконтроллеров. Вы же вешали на ножки контроллера светодиоды и контролировали логику программы при помощи них? Ну? признавайтесь! Было? Конечно было! Только причем здесь переменные? А вот причем. В наших руках ведь цифровой анализатор. Что если мы с вами будем не просто светодиодами мигать, а передавать на выходы значение переменных в последовательном виде? Например, при помощи вот такого кода.

#define PIN_SYNC PA1
#define PIN_INFO PA2
uint32_t some_var;
PIN_high(PIN_SYNC);
for (uint8_t i=32; i; i--) {
PIN_low(PIN_SYNC);
if (some_var & 0x80000000) PIN_high(PIN_INFO);
else PIN_low(PIN_INFO);
some_var <<= 1;
PIN_high(PIN_SYNC);
}

Здесь две константы определяют номера выводов микроконтроллера. PIN_SYNC - вывод передачи сигнала синхронизации. PIN_INFO - вывод информационного сигнала. Анализируемая 16-битная переменная - some_var. На вывод PIN_INFO последовательно передаются все биты этой переменной, начиная со старшего. Для лучшей интерпретации сигнала используется синхросигнал на выводе PIN_SYNC, на котором устанавливается низкий уровень перед каждым выводом информационного сигнала. Вот пример.

Получившееся значение: 00100100101100101110000000000000
Получившееся значение: 00100100101100101110000000000000

На канале D0 отображается информация о значении переменной (PIN_INFO), на канале D1 - синхросигнал (PIN_SYNC). Не помню, откуда сделал скриншот. По-моему я тогда анализировал какой-то алгоритм обработки данных с АЦП. Не суть! А вот что действительно важно, так это то, что данные в этом случае не требуют остановки программы, то есть отражают реальное ее выполнение.

таким же способом можно выводить значения любой переменной или счетчика циклов или значение, получаемое с АЦП. Понятно, что обладая такой отладочной мощью можно особенно не торопиться с освоением OpenOCD. Но, тем не менее это дело недалекого будущего, о котором я обязательно расскажу в грядущих заметках!

Спасибо, что читаете-смотрите Terrabyte! Подписывайтесь, если вам интересна радиолюбительская тематика, микроконтроллеры, мини-ПК, необычные компьютерные решения и инновационные разработки! Спасибо всем, кто поддерживает нас с братом своими комментариями и лайками!

Наша группа ВК: https://vk.com/terrabyte

Наш канал на VK-Video: https://vk.com/video/@terrabyte/all