CH32V003
Чем программировать CH32V
Смешной вопрос!
Чем вообще программируются STM32? У кого достаточно финансовых возможностей, тот покупает дорогие фирменные программаторы. Я, как и многие другие разработчики, использую китайские «свистки» по 150 рублей. (Это они раньше столько стоили. Сколько стоят сейчас — я не знаю. Уже давно не покупал. Для работы с STM32 я всё ещё пользуюсь свистком, купленным лет пять назад.) Беда, однако, в том, что ST-Link не подходит для работы с CH32V.
Ну, хорошо. А чем же тогда программировать эти китайские штучки?
Чем-чем — дак, почти таким же китайским «свистком», название которому WCH-LinkE.
Но я не стал заморачиваться только на «свисток», а прикупил полный набор — «свисток», отладочные платы и сами микросхемы.
В «суповой» набор за полторы тысячи рублей (если быть более точным — за 1515,69) входят — собственно, сам «свисток», две отладочных платы (с CH32V003 и с CH32V203) и по пять штук тех и других микросхем.
Причём, что интересно, всё это «бохацтво» пришло в довольно-таки приличной упаковке, а образцы микросхем — так вообще были упакованы в пластиковые коробочки на подобие из-под ювелирных изделий или из-под наручных часов. В общем, мелочь — а приятно!
Программатор собран на базе микроконтроллера тоже RISC-V — CH32V305F8. (Характеристики этого микроконтроллера не смотрел, но думаю, что они будут повкуснее CH32V203.)
У меня есть предположение, что такой программатор можно повторить. Но… какой в этом смысл? Дешевле китайского-то сделать всё равно не получится. Быстрее и проще купить ещё один, если этот сдохнет.
<---
Я пока не совсем разобрался со спецификой блога. Я так и не понял, как публиковать в блоге другие свои статьи.
Видимо, их нужно размещать здесь же. Это немного странно. Но по мере освоения, я приведу блог в нормальное состояние.
Несколько последовательных по смыслу статей будут размещены на этом месте.
<---
CH32V003. Формирование временнЫх интервалов
Делать так, как описано в этой короткой статье, я бы не рекомендовал. Эта статья предназначена не ради готового примера для применения в каких-либо коммерческих программах, а ради "первой ступеньки" в освоении модуля таймера.
Таймеры в STM32 и в CH32V по сравнению с другими микроконтроллерами (например, MSP430, ATMEGA и другими) сильно навороченные, и разобраться сходу, как с ними работать, -- довольно-таки трудно. По себе сужу.
Документация в интернете в основном представлена на английском языке. На русском тоже есть, но есть один момент. Документации по таймерам конкретно для CH32V нет. Хотя таймеры в CH32V и STM32 очень похоже, но состав, названия регистров, названия битов по отношению к STM32 несколько различаются. Поэтому у разработчиков возникают определённые трудности, которые выливаются в затягивание сроков разработки программ.
Представленный в статье пример помогает быстрее начать с таймером работать.
В микроконтроллерах CH32V реализованы два таймера -- таймер общего назначения (General Purpose) TIM2 и продвинутый таймер (Advanced) TIM1. В примере используется таймер общего назначения TIM2, но представленный код пригоден и для продвинутого таймера TIM1.
У таймера много функций, которые он может выполнять. Начать освоения таймера лучше с самой простой функции -- формирование временнЫх промежутков. Что это значит?
Допустим, мы пишем программу, которая выполняет какие-то действия (например, измеряет температуру). Поскольку программа измеряет температуру значительно быстрее, чем температура обычно меняется, то измерять температуру каждую миллисекунду нет смысла. Допустим, что нам нужно измерять температуру с периодом один раз в секунду. Само же время измерения и время передачи полученного значения температуры во внешний мир (на LCD или по последовательному каналу в компьютер) составляет 10 мс.
В этом случае главный цикл программы будет выглядеть как-то так:
int main(void)
{
...
tim_init(); // Настраиваю таймер
// Главный цикл программы
while (1)
{
temperature = get_temperature(); // Измеряю температуру
send_value(temperature); // Передаю показания
wait(); // Жду секунду
}
}
В функции tim_init() производится настройка таймера на формирование секундных промежутков времени, а функция wait() тупо останавливявает выполнение программы до начала следующего промежутка времени.
Вот, эти-то функции мы сейчас и рассмотрим более подробно. Начнём с функции tim_init().
Перед тем как взаимодействовать с таймером нужно разрешить его работу, или другими словами подать на него тактирование. Это делается одной командой:
RCC->APB1PCENR |= RCC_TIM2EN; // Включаю таймер
Следующим шагом нужно настроить работу таймера. Допустим, что тактовая частота SysClock, на которой работает ядро микроконтроллера, равно 24 МГц, а предделитель для шины мы не используем (то есть тактовая частота шины APB1 тоже 24 МГц). Тогда оставшийся код инициализации таймера будет выглядеть так:
TIM2->INTFR = 0; // Предочистка
TIM2->PSC = 24000 - 1; // Входная частота таймера = 1 кГц
TIM2->ATRLR = 1000; // Соответствует одной секунде
TIM2->CTLR1 = TIM_CEN; // Запускаю таймер в работу
У каждого таймера есть свой предделитель. Он делить входную частоту на заданное значение и потом подает её на счетчик таймера. В нашем случае мы записываем коэффициент деления 24000 в регистр предделителя (PSC). Это значит, что после предделителя частота, котораяубдет поступать на счётчик таймера, будет равна 1 кГц.
У каждого счётчика так же имеется регистр автозагрузки. Работа этого регистра зависит от направления счёта счетчика -- увеличивает ли счетчик свое значение или же уменьшает. Значение из этого регистра либо загружается в счётчик каждый раз при достижении счётчиком нулевого значения, либо наоборот -- при достижении счётчиком значения, равного записанному в регистре PSC, счётчик обнуляется. В обоих случая счётчик формирует событие UIF, которое мы и будем отслеживать в функции wait().
Код функции wait() ещё проще:
void wait(void)
{
while (!(TIM2->INTFR & TIM_UIF))
; // Ожидаю поднятия флага UIF
TIM2->INTFR = 0; // Сбрасываю флаг
}
Как можно понять из приведённого кода, функция тормозит выполнение программы до тех пор, пока не сработает таймер и не будет взведён флаг UIF. После этого происходит очистка этого флага и программа может продолжить своё выполнение.
Еще раз отмечу, что не смотря на то, что программа, построенная по предложенному способу, будет вполне рабочей, делать так не надо. Код программы был приведён только в учебных целях. С чего-то же нужно начинать?
CH32V003. Генератор временнЫх интервалов
По жизни намного чаще, чем описанный в предыдущей статье формирователь задержки, требуется формирователь временнЫх отметок, или другими словами -- таймер. Таймер, в нашем контексте, -- это такая бестия, которая периодически с заданным интервалом прерывает выполнение основной программы.
Будем работать с таймером TIM2. Ранее я уже говорил, что периферия CH32V и периферия STM32 сильно совпадают. И это есть хорошо! Но, как обычно, есть нюансы!
Количество и состав регистров таймера TIM2 у CH32V и STM32 совпадает на 100 %, хотя названия регистров сильно расходятся. Например, регистр управления у STM32 называется CR1, в то время как у CH32V он носит имя CTLR1. Или, вот, ещё например, регистр генерации событий -- у STM32 он называется EGR, а у CH32V -- SWEVGR. Не знаю, зачем это было нужно делать китайцам, но по моему мнению они сделали неверный шаг.
Что же касается названий битов в регистрах, то в документации (на STM32 и на CH32V) они совпадают полностью. Но одно дело pdf-ка и совсем другое дело хэдерные файлы. Не знаю, насколько это оказалось дурным, но в данном случае ход китайцев мне понравился. Например, бит разрешения работы таймера у STM32 называется длинно -- TIM_CR1_CEN, а у китайцев этот же бит называется проще -- TIM_CEN. Во всяком случае мне показалось, что писать наименования "китайских" битов легче.
Сравните:
TIM2->CR1 = TIM_CR1_CEN; // STM32
и
TIM2->CTLR1 = TIM_CEN; // CH32V
Однако, давайте вернёмся к теме нарезки времени на кусочки определённой длительности.
Обработчик прерывания от таймера TIM2 должен выглядеть следующим образом:
__attribute__((interrupt("WCH-Interrupt-fast")))
void TIM2_Handler(void)
{
TIM2->INTFR &= ~TIM_UIF; // Очистить флаг прерывания
... // Выполнить какую-то полезную работу
}
В отличие от программного кода для STM32 в программах для CH32V нужно обязательно перед объявлением обработчика прерывания добавлять строку:
__attribute__((interrupt("WCH-Interrupt-fast")))
Ну и не забывайте, что один обработчик может обслуживать целую кучу родственных прерываний. Например, в этом обработчике обслуживается прерывание, которое возбуждается в следствие События Подновления таймера. Кроме этого прерывания таймер может возбуждать также прерывание при возникновении условия захвата, при возникновении условия сравнении.
У каждого прерывания свой флаг, который нужно сбросить при входе в обработчик прерывания. Поэтому чтобы в обработчике прерывания реагировать на своё прерывание, а не пытаться обслужить родственное, нужно по правильному писать так:
__attribute__((interrupt("WCH-Interrupt-fast")))
void TIM2_Handler(void)
{
if (((TIM2->DMAINTENR & TIM_UIE) != 0) && ((TIM2->INTFR & TIM_UIF) != 0))
{ // Это прерываение разрешено и флаг прерывания поднят
TIM2->INTFR &= ~TIM_UIF; // Очистить флаг прерывания
... // Выполнить какую-то полезную работу
}
... // Обслужить другие прерывания от таймера
}
Но если в вашей программе используется только одно прерывание (как в нашем примере), то такую проверку можно и не выполнять.
С прерыванием разобрались. Теперь нужно написать код, который правильно настроит (инициализирует) таймер TIM2. Этот код выглядит так:
void init_tim2(void)
{
RCC->APB1PCENR |= RCC_TIM2EN; // Включаю тактирование модуля таймера
NVIC_EnableIRQ(TIM2_IRQn); // Разрешаю прерывания от таймера TIM2
TIM2->PSC = 24000 - 1; // Предделитель, тактовая частота счётчика 1 кГц
TIM2->ATRLR = 50; // Интервал перываний 50 мс
TIM2->INTFR = 0; // Сбрасываю все флаги
TIM2->DMAINTENR = TIM_UIE; // Разрешаю прерывание по событию "Подновление таймера"
TIM2->CTLR1 = TIM_CEN; // И наконец разрешаю работу таймера TIM2
}
В результате мы получили программу, которая 20 раз в секунду прерывает работу основного цикла программы.
Как видите, писать программу для CH32V не сложнее, чем для STM32.
Изменено пользователем zhevak
3 Комментария
Рекомендуемые комментарии
Присоединяйтесь к обсуждению
Вы публикуете как гость. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.