При настройке таймеров на STM32F4 , внезапно на ровном месте столкнулся со странным глюком - вроде меняешь код на пару строк, никак не связанных с настройкой ШИМ - а фаза ШИМ одного из каналов таймера инвертируется. Еще пару строк поменял - инвертировалось обратно… Что за фигня?
Долго вылавливал часть кода, которая дает этот эффект, пока не сократил код до чуть более 70 строк:
Смотреть код
const timer_dev* MAIN_TIMER = TIMER4;
const timer_dev* OE_TIMER = TIMER3;
uint8_t ADD_NUM = 228;
uint8_t TIM3_PERIOD = 8;
uint8_t oe_channel;
uint8_t pin_DMD_nOE = PB0;
uint8_t pin_CLK = PA6;
// ===============================================
void initialize_timers() {
// setup MAIN TIMER (Master)
timer_init(MAIN_TIMER);
timer_pause(MAIN_TIMER);
timer_set_prescaler(MAIN_TIMER, 0);
timer_set_reload(MAIN_TIMER, (TIM3_PERIOD * ADD_NUM + 4));
// Set master mode
(MAIN_TIMER->regs).gen->SMCR = TIMER_SMCR_MSM;
// use CC1 as trigger output to control slave
(MAIN_TIMER->regs).gen->CR2 = TIMER_CR2_MMS_COMPARE_OC1REF;
timer_oc_set_mode(MAIN_TIMER, 1, TIMER_OC_MODE_PWM_1, 0);
// CC1 to control slave on/off
timer_set_compare(MAIN_TIMER, 1, TIM3_PERIOD);
// Setup OE Timer (slave)
timer_init(OE_TIMER);
timer_pause(OE_TIMER);
timer_set_prescaler(OE_TIMER, 0);
timer_set_reload(OE_TIMER, TIM3_PERIOD - 1);
// connect slave OE_TIMER to Master
(OE_TIMER->regs).gen->SMCR = TIMER_SMCR_TS_ITR3 | TIMER_SMCR_SMS_GATED;
// set PWM mode for output
timer_oc_set_mode(OE_TIMER, oe_channel, TIMER_OC_MODE_PWM_1, 0);
timer_cc_enable(OE_TIMER, oe_channel);
timer_cc_set_pol(OE_TIMER, oe_channel, 0);
timer_set_compare(OE_TIMER, oe_channel, TIM3_PERIOD / 2);
// ==== Start timers
timer_set_count(OE_TIMER, 0);
timer_set_count(MAIN_TIMER, 0);
timer_resume(OE_TIMER);
timer_resume(MAIN_TIMER);
}
// ===============================================
void setup() {
// define timer CH for nOE pin
oe_channel = timer_map[pin_DMD_nOE].channel;
pinMode(pin_DMD_nOE, PWM);
// setup pin for time marks
pinMode(pin_CLK, OUTPUT);
// ==== 1st time mark
digitalWrite(pin_CLK, HIGH);
digitalWrite(pin_CLK, LOW);
// setup and start the timers
initialize_timers();
delayMicroseconds(200);
// stop OE generation by set CC1 of Main timer = 0
timer_set_compare(MAIN_TIMER, 1, 0);
delayMicroseconds(20);
// ==== 2nd time mark
digitalWrite(pin_CLK, HIGH);
digitalWrite(pin_CLK, LOW);
// ++++++ problem lines +++++
// timer_resume(MAIN_TIMER);
// timer_resume(OE_TIMER);
}
// ===============================================
void loop() { }
В детали настройки таймеров в процедуре initialize_timers()
вникать не обязательно. Кратко суть в том, что мы настраиваем два таймера - Master (MAIN_TIMER) и slave (OE_TIMER) таким образом, чтобы высокий уровень сигнала на ШИМ выходе Мастер таймера разрешал работу слейва.
Все нехитрое действие происходит в setup(). В строке 58 мы запускаем таймеры и разрешаем работу слейв таймера, потом ждем 200 мкс и в строке 64 запрещаем генерацию сигнала для слейва.
Еще я добавил вывод коротких импульсов на свободный пин, чтобы они служили временными метками на осцилограмме и показывали начало и конец процесса.
Правильная картинка логик-анализаторе выглядит так:
Красные пики - сигналы слейв-таймера, зеленые - отметки начала и конца.
Но стоит раскомментировать строчки 72 и 73, картина меняется:
Обратите внимание, что в программе “нехорошие” строки стоят только ПОСЛЕ второй временной отметки. А влияют на фазу ШИМ с самого начала!
Причем по хорошему эти команды, вообще-то, никак на ситуацию влиять не должны. Функция timer_resume()
в ядре STM32F4 всего лишь выставляет в контрольном регистре таймера бит, разрещающий его работу. Но к моменту 72 строки этот бит в обоих таймерах и так выставлен, поскольку я их не останавливал.
Честно говоря, я потратил много вечеров, вылавливая эту бяку. Причин я ее так и не знаю, но заметил, что этот баг наблюдается только при сборке с опцией оптимизации -Os (то есть при оптимизации по размеру). Если включить оптимизацию -O3 - баг пропадает и код одинаково работает и со строками 72-73 и без них, выдавая на ЛА первую картинку.
Подозреваю, это как-то связано с тем, что функция timer_resume()
определена как inline:
/**
* @brief Start a timer's counter.
*
* Does not affect the timer's mode or other settings.
*/
static inline void timer_resume(const timer_dev *dev) {
*bb_perip(&(dev->regs).bas->CR1, TIMER_CR1_CEN_BIT) = 1;
}
Смотреть файл Arduino_STM32/blob/master/STM32F4/cores/maple/libmaple/timer.h
Стараясь не допустить дублирования кода, оптимизатор, похоже, связывает вызовы этой функции в строчках 72-73 с таким же вызовом в момент инициализации таймеров в строках 43-44 - поэтому мы и видим, что добавление строк в конце программы портит работу таймеров с самого старта.
Но это только предположение. Так ли это и что именно там портится - может кто подскажет?