В предыдущей части нашего урока мы познакомились поближе с умными светодиодами RGB WS2812B, а также приготовили схему для практического закрепления материала.
Думаю, хватит нам на сегодняшний день теории, пора плавно переходить к проекту.
Проект мы создадим новый. Для этого запустим проектогенератов Cube MX и выберем наш контроллер
Подключим кварцевый резонатор и отладчик
Включим таймер в режиме PWM на 2 канал, так как первый у нас подключен к ножке PA0, которая всегда может пригодиться и лучше её не занимать
Как видим, у нас включилась ножка PA1
Вот её мы и подключим к сигнальному проводу ленты.
Переходим в Clock Configuration и настроим там всё следующим образом (нажмите на картинку для увеличения изображения)
Переходим в Configuration и настроим таймер
Почему именно 89? А это легко объяснимо. Во-первых к значению предделителя и периода мы всегда прибавляем 1. Поэтому предделитель у нас 1, то есть он не учитывается. А 89 + 1 = 90. Делим частоту тактирования шины таймера 72 на 90 и получаем ровно 0,8 МГц, что составляет 800 килогерц. Это то, что нам и надо!
Прерывания на таймер не включаем, они нам не потребуются. Переходим в закладку DMA и добавим канал DMA, настройки оставляем все по умолчанию (проверьте, что они именно такие). Переключаем только направление работы DMA — Memory To Peripheral
А прерывания на DMA включатся сами.
Вроде пока всё. Заполним настройки проекта
Сохраним настройки. сгенерируем проект для Keil, откроем его, настроим программатор на авторезет и настроим уровень оптимизации 1.
Первым делом давайте попробуем зажечь хотя бы один светодиод. Так как мы знаем, как работает PWM, нам это будет несложно сделать. Правда мы не знаем, как он работает с применением DMA, ну ничего, разберёмся.
Чтобы применить DMA в PWM, да и вообще в любых режимах таймера, мы должны будем при старте таймера передавать некие данные, причём с применением DMA мы можем передавать целые массивы.
В случае с PWM в массиве, указатель на который мы передадим функции старта, будут находиться данные со скважностью, а точнее с периодом, в течение которого ножка, управляемая таймером, будет находиться в высоком состоянии. В нашем случае у нас общий период 90 (в настройках 89). Если мы передадим таймеру число 44 (что составляет 45 — 1, а это ровно половина периода), то мы получим период нахождения ножки в высоком состоянии — 45, следовательно, остальную часть периода, что будет тоже 45, ножка будет в низком состоянии. Таким образом мы получим ровные импульсы со скважностью 50 процентов. Но нам нужны не такие импульсы. Поэтому рассчитаем сначала значение нашего периода для передачи бита 0 в нашу шину. Оно по технической документации составляет 0,35 микросекунды. Общий период бита у нас составляет 1,25 микросекунды. Поэтому мы получим пропорцию: при периоде 1,25 — 0,35 мкс, а при 90 — X. Отсюда X = 90 * 0.35 / 1.25, что составит примерно 25. Но мы всегда вычитаем единицу, поэтому в массив для передачи бита 0 мы будем в соответствующую ячейку заносить число 24.
Давайте сразу создадим библиотеку под наши умные светодиоды. Состоять она будет из пары файлов ws2812.c и ws2812.h следующего содержания
ws2812.c:
#include "ws2812.h"
//----------------------------------------------------------------------------
extern TIM_HandleTypeDef htim2;
//----------------------------------------------------------------------------
ws2812.h:
#ifndef WS2812_H_
#define WS2812_H_
//--------------------------------------------------
#include "stm32f1xx_hal.h"
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
//--------------------------------------------------
//--------------------------------------------------
#endif /* WS2812_H_ */
Подключим к нашей информационной ножке логический анализатор, чтобы следить, что мы всё делаем правильно, ну или неправильно (нажмите на картинку для увеличения изображения)
Давайте сразу создадим буфер для хранения периодов PWM. Буфер потребуется немаленький. Сейчас посчитаем. Во-первых, лучше сразу переждать больше 50 микросекунд, чтобы лента поняла, что это уже новые данные. Это можно сделать с помощью серии нулей. Период таймера у нас 1,25 микросекунд, я думаю 48 таких периодов будет вполне предостаточно. Поэтому, прежде чем организовывать буфер, мы в файле ws2812.h это пропишем в качестве макроса
#include <stdint.h>
//--------------------------------------------------
#define DELAY_LEN 48
Также добавим макрос количества управляемых светодиодов. У нас их 144
#define DELAY_LEN 48
#define LED_COUNT 144
А теперь макрос длины нашего буфера, так как мы помним, что для каждого светодиода требуется по 24 бита, следовательно, в буфере должно быть 24 полуслова, так как одно полуслово — это настройки для передачи одного бита. А полуслово потому, что у нас 16-битный таймер
#define LED_COUNT 144
#define ARRAY_LEN DELAY_LEN + LED_COUNT*24
Перейдём в файл ws2812.c и добавим там глобальный массив для нашего буфера, который мы будем передавать в функцию запуска таймера
extern TIM_HandleTypeDef htim2;
//------------------------------------------------------------------
uint16_t BUF_DMA [ARRAY_LEN] = {0};
//------------------------------------------------------------------
Перейдём в файл main.c и подключим там нашу библиотеку
/* USER CODE BEGIN Includes */
#include "ws2812.h"
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
/* USER CODE END Includes */
Также подключим здесь наш глобальный буфер
/* Private variables ---------------------------------------------------------*/
extern uint16_t BUF_DMA [ARRAY_LEN];
/* USER CODE END PV */
Буфер подготовлен.
Только весь он нам сейчас не потребуется. Мы пока хотим понять, как вообще работает PWM с применением DMA. Поэтому давайте пока передадим только один элемент буфера.
Для этого мы в самое начало буфера запишем какое-нибудь число. А давайте не какое-нибудь, а сразу число 26. Хотя мы для нуля рассчитали и 24, но правильнее работает именно 26.
В функции main() запишем это число в нулевой элемент буфера и вызовем функцию старта таймера, в которую в качестве входного параметра мы и добавим адрес нашего буфера, а также в качестве следующего параметра — количество элементов. Пока отправим один
/* USER CODE BEGIN 2 */
BUF_DMA[0] = 26;
HAL_TIM_PWM_Start_DMA(&htim2,TIM_CHANNEL_2,(uint32_t*)&BUF_DMA,1);
Соберём код, прошьём контроллер. На ленту питание пока не подаём во избежание случайного зажигания всех светодиодов полной яркостью. Посмотрим результат в программе логического анализа
Мы видим, что у нас всё идеально. Только также мы видим, что импульсы следуют бесконечно, нам такого не надо. Впрочем, оно так и должно быть, так как мы таймер запустили и его не останавливали.
Нам надо найти место, где его остановить. Таким местом будет обработчик прерывания от периферии DMA. Поэтому перейдём в файл stm32f1xx_it.c и подключим хендл нашего таймера
/* USER CODE BEGIN 0 */
extern TIM_HandleTypeDef htim2;
/* USER CODE END 0 */
А затем в соответствующем месте в обработчике прерывания от DMA остановим таймер
void DMA1_Channel7_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Channel7_IRQn 0 */
HAL_TIM_PWM_Stop_DMA(&htim2,TIM_CHANNEL_2);
Вернёмся в файл main.c и в функции main() изменим количество элементов на 2. С одним не сработает
HAL_TIM_PWM_Start_DMA(&htim2,TIM_CHANNEL_2,(uint32_t*)&BUF_DMA,2);
Прежде чем прошивать контроллер, настроим программу логического анализа. Установим триггер на восходящий фронт
Соберём код, прошьём контроллер, и увидим, что у нас теперь уходит на информационный контакт только один импульс
Удалим строку с инициализацией первого элемента буфера
BUF_DMA[0] = 26;
Теперь перейдём опять в заголовочный файл ws2812.h и напишем там макросы для настроек PWM для передачи нуля и единицы. Для единицы экспериментальным путём получено число 65
#define ARRAY_LEN DELAY_LEN + LED_COUNT*24
#define HIGH 65
#define LOW 26
Перейдём в файл ws2812.c и напишем функцию инициализации нашей ленты. Мы здесь просто забьём все байты, кроме отведённых под задержку числами для передачи нуля на наш сигнальный провод
//------------------------------------------------------------------
void ws2812_init(void)
{
int i;
for(i=DELAY_LEN;i<ARRAY_LEN;i++) BUF_DMA[i] = LOW;
}
//------------------------------------------------------------------
Напишем для данной функции прототип и вызовем её в файле main.c в функции main()
/* USER CODE BEGIN 2 */
ws2812_init();
В файле ws2812.h добавим макрос установки бита в каком-либо числе
#define LOW 26
//--------------------------------------------------
#define BitIsSet(reg, bit) ((reg & (1<<bit)) != 0)
//--------------------------------------------------
Вернёмся в файл ws2812.c и добавим функцию, которая будет заполнять буфер в позиции, соответствующей определённому светодиоду значениями его цветов. Соответственно, во входящих параметрах будут значения каждого цвета, а также номер светодиода в ленте
//------------------------------------------------------------------
void ws2812_pixel_rgb_to_buf_dma(uint8_t Rpixel , uint8_t Gpixel, uint8_t Bpixel, uint16_t posX)
{
volatile uint16_t i;
for(i=0;i<8;i++)
{
if (BitIsSet(Rpixel,(7-i)) == 1)
{
BUF_DMA[DELAY_LEN+posX*24+i+8] = HIGH;
}else
{
BUF_DMA[DELAY_LEN+posX*24+i+8] = LOW;
}
if (BitIsSet(Gpixel,(7-i)) == 1)
{
BUF_DMA[DELAY_LEN+posX*24+i+0] = HIGH;
}else
{
BUF_DMA[DELAY_LEN+posX*24+i+0] = LOW;
}
if (BitIsSet(Bpixel,(7-i)) == 1)
{
BUF_DMA[DELAY_LEN+posX*24+i+16] = HIGH;
}else
{
BUF_DMA[DELAY_LEN+posX*24+i+16] = LOW;
}
}
}
//------------------------------------------------------------------
С данной функцией, думаю всё понятно. Мы распределяем значения элементов в зависимости от цветов и зная то, что сначала передаётся зелёный, потом красный, а потом голубой цвета.
Напишем для данной функции прототип и вызовем её в файле main.c в функции main(), тем самым попробовав окрасить в какой-нибудь цвет самый первый светодиод нашей ленты, например, в зелёный. В вызове функции старта таймера, мы можем уже передавать в качестве аргумента весь наш буфер, так как он забит уровнями нуля
ws2812_pixel_rgb_to_buf_dma(0, 128, 0, 0);
HAL_TIM_PWM_Start_DMA(&htim2,TIM_CHANNEL_2,(uint32_t*)&BUF_DMA,ARRAY_LEN);
Подключим питание ленты, соберём код, прошьём контроллер и посмотрим результат
Давайте зажжём данный светодиод другим цветом, например красным (опять используем только половину свечения — 128)
ws2812_pixel_rgb_to_buf_dma(128, 0, 0, 0);
Посмотрим результат
Теперь попробуем окрасить синим цветом, и причём не этот, а например четвёртый светодиод
ws2812_pixel_rgb_to_buf_dma(0, 0, 128, 3);
Посмотрим результат
Давайте зажжём теперь разными цветами первые 4 светодиода, не забывая о нашем золотом правиле — общая сумма единиц свечения всех цветов светодиода не должна превышать 128, что составит 1/6 от интенсивности свечения полным белым цветом
ws2812_pixel_rgb_to_buf_dma(128, 0, 0, 0);
ws2812_pixel_rgb_to_buf_dma(0, 128, 0, 1);
ws2812_pixel_rgb_to_buf_dma(0, 0, 128, 2);
ws2812_pixel_rgb_to_buf_dma(64, 64, 0, 3);
Посмотрим результат
Отлично! Значит мы движемся в правильном направлении.
Инициализировать каждый раз каждый светодиод не очень удобно. Поэтому давайте расширять функционал нашей библиотеки.
Также в этих же целях мы не будем инициализировать все светодиоды, а будем только 12, остальные 12 будут повторять по цвету эти 12 и так далее. Поэтому нам надо будет написать соответствующую функцию, которая наши 12 цветов будет распределять и по оставшимся участкам ленты по 12 светодиодов. Таких функций будет даже две. Первая из них будет сначала заносить значения битов только 12 светодиодов в соответствующий временный буфер, а вторая — распределять его по основному буферу, который затем пойдёт в работу.
В файле ws2812.c добавим такой временный буфер, который будет двумерным, первым его размером будет количество светодиодов, а вторым — количество цветов
uint16_t BUF_DMA [ARRAY_LEN] = {0};
uint8_t rgb_temp[
12][3];
После функции ws2812_pixel_rgb_to_buf_dma напишем функцию подготовки временного буфера
//------------------------------------------------------------------
void ws2812_prepareValue (uint8_t r00, uint8_t g00, uint8_t b00,
uint8_t r01, uint8_t g01, uint8_t b01,
uint8_t r02, uint8_t g02, uint8_t b02,
uint8_t r03, uint8_t g03, uint8_t b03,
uint8_t r04, uint8_t g04, uint8_t b04,
uint8_t r05, uint8_t g05, uint8_t b05,
uint8_t r06, uint8_t g06, uint8_t b06,
uint8_t r07, uint8_t g07, uint8_t b07,
uint8_t r08, uint8_t g08, uint8_t b08,
uint8_t r09, uint8_t g09, uint8_t b09,
uint8_t r10, uint8_t g10, uint8_t b10,
uint8_t r11, uint8_t g11, uint8_t b11)
{
rgb_temp[0][0]=r00; rgb_temp[0][1]=g00; rgb_temp[0][2]=b00;
rgb_temp[1][0]=r01; rgb_temp[1][1]=g01; rgb_temp[1][2]=b01;
rgb_temp[2][0]=r02; rgb_temp[2][1]=g02; rgb_temp[2][2]=b02;
rgb_temp[3][0]=r03; rgb_temp[3][1]=g03; rgb_temp[3][2]=b03;
rgb_temp[4][0]=r04; rgb_temp[4][1]=g04; rgb_temp[4][2]=b04;
rgb_temp[5][0]=r05; rgb_temp[5][1]=g05; rgb_temp[5][2]=b05;
rgb_temp[6][0]=r06; rgb_temp[6][1]=g06; rgb_temp[6][2]=b06;
rgb_temp[7][0]=r07; rgb_temp[7][1]=g07; rgb_temp[7][2]=b07;
rgb_temp[8][0]=r08; rgb_temp[8][1]=g08; rgb_temp[8][2]=b08;
rgb_temp[9][0]=r09; rgb_temp[9][1]=g09; rgb_temp[9][2]=b09;
rgb_temp[10][0]=r10;rgb_temp[10][1]=g10;rgb_temp[10][2]=b10;
rgb_temp[11][0]=r11;rgb_temp[11][1]=g11;rgb_temp[11][2]=b11;
}
//------------------------------------------------------------------
Теперь следующая функция, которая из временного буфера переместит данные в наш постоянный, заодно и размножит участки
//------------------------------------------------------------------
void ws2812_setValue(void)
{
uint8_t n=0;
for(n=0;n<12;n++)
{
ws2812_pixel_rgb_to_buf_dma( rgb_temp[0][0], rgb_temp[0][1], rgb_temp[0][2], n*12);
ws2812_pixel_rgb_to_buf_dma( rgb_temp[1][0], rgb_temp[1][1], rgb_temp[1][2], n*12+1);
ws2812_pixel_rgb_to_buf_dma( rgb_temp[2][0], rgb_temp[2][1], rgb_temp[2][2], n*12+2);
ws2812_pixel_rgb_to_buf_dma( rgb_temp[3][0], rgb_temp[3][1], rgb_temp[3][2], n*12+3);
ws2812_pixel_rgb_to_buf_dma( rgb_temp[4][0], rgb_temp[4][1], rgb_temp[4][2], n*12+4);
ws2812_pixel_rgb_to_buf_dma( rgb_temp[5][0], rgb_temp[5][1], rgb_temp[5][2], n*12+5);
ws2812_pixel_rgb_to_buf_dma( rgb_temp[6][0], rgb_temp[6][1], rgb_temp[6][2], n*12+6);
ws2812_pixel_rgb_to_buf_dma( rgb_temp[7][0], rgb_temp[7][1], rgb_temp[7][2], n*12+7);
ws2812_pixel_rgb_to_buf_dma( rgb_temp[8][0], rgb_temp[8][1], rgb_temp[8][2], n*12+8);
ws2812_pixel_rgb_to_buf_dma( rgb_temp[9][0], rgb_temp[9][1], rgb_temp[9][2], n*12+9);
ws2812_pixel_rgb_to_buf_dma(rgb_temp[10][0],rgb_temp[10][1],rgb_temp[10][2],n*12+10);
ws2812_pixel_rgb_to_buf_dma(rgb_temp[11][0],rgb_temp[11][1],rgb_temp[11][2],n*12+11);
}
}
//------------------------------------------------------------------
А следующая функция вызовет функцию старта таймера. Смысл этой функции — простота, так как официальная слишком велика, а у нас всегда будут одни и те же аргументы
//------------------------------------------------------------------
void ws2812_light(void)
{
HAL_TIM_PWM_Start_DMA(&htim2,TIM_CHANNEL_2,(uint32_t*)&BUF_DMA,ARRAY_LEN);
}
//------------------------------------------------------------------
Добавим на эти 3 функции прототипы и попробуем их применить в функции main() файла main.c.
Старый код, кроме инициализации, во второй пользовательской секции удалим и добавим новый, останется там теперь вот это
/* USER CODE BEGIN 2 */
ws2812_init();
ws2812_prepareValue(128, 0, 0,
0, 128, 0,
0, 0, 128,
64, 64, 0,
0, 64, 64,
64, 0, 64,
96, 32, 0,
96, 0, 32,
32, 96, 0,
0, 96, 32,
0, 32, 96,
32, 0, 96);
ws2812_setValue();
ws2812_light();
/* USER CODE END 2 */
В принципе, логический анализатор мы можем отключить. У нас всё правильно работает.
Соберём код, прошьём контроллер и получим следующий результат (нажмите на картинку для увеличения изображения)
Всё отлично работает и все блоки правильно повторяются.
Только вот смотреть картинку — это всё же не то. Поэтому в конце урока есть ссылка на видеоурок на Youtube, там уже будет гораздо красивее.
Но, конечно, ещё красивее это будет выглядеть тогда, когда вы сами соберёте эту схему, прицепите ленточку и увидите всё это вживую.
В следующей части нашего урока мы напишем несколько интересных тестов, чтобы увидеть, как красиво ведут себя умные светодиоды, если уметь управлять их свечением.
Предыдущая часть Программирование МК STM32 Следующая часть
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Ленты светодиодные WS2812B разные можно приобрести здесь WS2812B
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК (нажмите на картинку)
В самом начале опечатка
WS2712B
Очень нравится сайт и видео на канале.
Спасибо, поправил
Добрый день. Я вот все никак не могу понять как настроить TIM2 в STM32WB55 и как посчитать значения HIGH и LOW.
В этом микропроцессоре Counter period (Autoreload Register) 32 битный, и по сему не могу разобраться какие должны быть отличия от кода в вашем уроке.
Помогите, пожалуйста 🙂
Добрый вечер, крутая статья. Возникла проблема при использования более нового STM32CubeMX, отображаются не все светодиоды. Чем может быть связана проблема?
Спасибо за уроки.
Странная проблема возникла в этом. Горит всего 6 светодиодов. Или иногда 7 (в зависимости от комбинации цветов (яркость не зависит). Переделал на ленту в 15 светодиодов, но такая-же фигня.
DMA может вызывать три вида прерываний: Первый — завершена передача половины данных. Второй — завершена передача всех данных. Третий вид прерывания, это какая-либо ошибка.
Eсть особенность, если включён режим Mode ⇨ Normal, то сработает только колбек по половинке, а если Mode ⇨ Circular, то будут срабатывать оба. отсюда и ошибка в отсутствии прерывания если указать 1 светодиод в LED_COUNT, или нечетное число.
Решение, установить Mode ⇨ Circular и в колбеке:
void HAL_TIM_PWM_PulseFinishedHalfCpltCallback(TIM_HandleTypeDef *htim)
{
// коллбек по половине массива буфера
}
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
// коллбек по окончанию массива буфера, сюда же пихать Стоп ДМА и Стоп Таймер
}
спасибо, эта информация очень помогла
еле понял почему только половина ленты светится, но мое решение увеличить в два раза размер буфера передаваемого в ДМА, не знаю как лучше
void ws2812_light(void)
{
HAL_TIM_PWM_Start_DMA(&htim2,TIM_CHANNEL_2,(uint32_t*)&BUF_DMA,ARRAY_LEN*2);
}
подкорректировал
еле понял почему только половина ленты светится, но мое решение увеличить в два раза размер буфера передаваемого в ДМА, не знаю как лучше
void ws2812_light(void)
{
HAL_TIM_PWM_Start_DMA(&htim2,TIM_CHANNEL_2,(uint32_t*)&BUF_DMA,ARRAY_LEN*2+24);
}
разобрался
что бы вся лента светилась
void DMA1_Channel4_5_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Channel4_5_IRQn 0 */
/* USER CODE END DMA1_Channel4_5_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_tim1_ch3_up);
if (HAL_DMA_GetState(&hdma_tim1_ch3_up) == HAL_DMA_STATE_READY)
{
HAL_TIM_PWM_Stop_DMA(&htim1,TIM_CHANNEL_3);
}
/* USER CODE BEGIN DMA1_Channel4_5_IRQn 1 */
/* USER CODE END DMA1_Channel4_5_IRQn 1 */
}
Спасибо! Золотой комментарий. У меня была такая-же проблема в STM32WB55 и эти строчки исправили проблему
Не дергается нога таймера(( инициализировал без DMA и просто HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2); после main() перед while().
при переходе на следующий светодиод не нужна пауза? Если нужна то как она реализуется при использовании функции ws2812_pixel_rgb_to_buf_dma ?
Не учитывается что кол-во светодиодов у всех разное — если уменьшить буфер под светодиды — часто ничего не работает.
Например void ws2812_setValue(void) — расчитано что буфер 12*12 а не Led_count
В регистре DMA_SxCR есть бит CIRC:Circularmode. При его установке DMA каждый раз начинает проходить массив с начала, т.е. циклично. При этом ну нужно останавливать ШИМ внутри прерывания, Reset-пауза перестаёт зависеть от выклюючения/включения ШИМа. А еще если помимо ленты у вас в суперцикле будет другой другой код, который занимает время, то пауза Reset-пауза вообще выйдет за все разумные максимумы.
И еще совет: скачайте свежий даташит с сайта WorldSemi. В свежей версии тайминги вообще другие. Я два дня мучался не понимая, что я делаю не так.
Плюсую! Не было проблем до этого, и тут стало работать криво. Выставил на ощупь период 150 (вместо 89). Стало работать отлично.
У меня таймер с дма отрабатывает ровно один раз в режиме normal и передаёт нужное количество байт, но происходит так только если в функцию передаю длину требуемого массива+1. При таком подходе мне не приходится в прерывании останавливать дма модуль с таймером, оно и логично, я же выбираю нормальный режим, а не циркулярный. А для проверки именно окончания передачи советую проверять в прерывании флаг… _TC для полной передачи и… _HT для половины буфера.
Доброго времени суток! Работаю со светодиодной лентой SK6812MINI(аналог ws2812). Проблема заключается в том, что вывод сигнала DIN подключен к ножке STM32, которая не может, генерировать ШИМ, если я правильно понимаю. Подключить вывод DIN к ноге с возможностью генерации ШИМ проблематично. Придется вносить изменения в разводку платы. Можно ли каким-либо образом GPIOшной ногой генерировать сигналы для управления лентой?
Дай Бог тебе здоровья, милый человек, счастья, денег и удачи. Два дня бьюсь и не понимаю. Глючит, мерцает. Поставил 150 и все стало нормально.
Данные из буфера DMA записываються одновременно в нижнее и верхнее слова регистра, почему это может быть?
Сделал все по примеру, на выходе постоянно единица.
Запустил отладку и вижу что данные из буфера попадают одновременно в TIM2->CCR2_L и CCR2_H, вместо 26 (0x1A) в регистр сравнения пишеться 0x_001A_001A и PWM Не работает.
Если меняю значение верхнего регистра на 0, PWM отрабатывает один раз и снова высокий уровень на выходе.