ВходНаше всё Теги codebook 无线电组件 Поиск Опросы Закон Вторник
3 декабря
1401331
klen (08.02.2024 13:44 - 25.02.2024 23:17, просмотров: 13479)
[РЕШЕНО]stm32 ШИМ для полного моста на таймерах tim1/tim8 как? Что бы шимить нужно третье состояние когда два нижних или верхних одновременно открыты. Все фирменные доки в таком случае про HRTIM.. 

тему завел 08 а сегодня 25.... итого на эту простейшую если "знать как" задачку я потратил аж 25-08=17 дней... много. но сделал.

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


итак - задачка описано в видосике - зашимить полный мост.

youtube


однако там это сделано для преобразователя в котором частота фиксированная. в таком случае все относительно просто.

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

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


трахался трахался и наконец сегодня придумал - можно на одном продвинутом таймере сделать оказывается.


очевидно что пришлось частоту пихать в auto_reload таймера а скважность в регистры сравнения сс1 сс2. НО!!! я сразу не отдуплил как это сделать так ловко ловко чтоб оба канала не рассыпались при перестройке по частоте - ибо при изменении частоты нужно пересчитывать сс1 сс2 и записывать их - и очень хотелось одним батчем чтоб не было "пролагов". если это не обеспечить то такой код нельзя замкнуть на контур управления напряжением на выходе - трещать рассыпаться будет когда будем писат в cc1 cc2 по очереди.


поэтому нас спасает dma, но тут есть хитрость. настраиваем таймер на генерацию ДВУХ запросов dma для батча записи сс1 сс2 - в момент перегрузки счетчика - таким образом гарантируется что сравнение и запись сс1 и сс2 всегда будут разнесены по времени и не возникнет косяков. плюс я включил "прелоад" cc1 cc2 на всякий случай. длинна буфера ДВА по ДВА значения, то есть одним батчем актвный один полумост, вторым батчем активен второй - таймер работает на удвоенной частоте шима. та ким изебом решается вопрос с третьм состояние когда два нижних транзистора открыты. также прелоад включен для регистра auto_reload. в такой схеме необходимо и достаточно что бы расчет новых значений и запись в буфер dma был не более периода шим.


теперь возникает вопрос - а как всунуть в буфер dma новые значения cc1 cc2 так чтобы посередине пе произошла выборка свежего значения и предыдущего? - так расположить в памяти два 16 битных значения чтоб они давали 32битный доступ и писать целочисленной 32битной инструкцией. тогда запись будет атомарной и если процессор начнет писать и захватит шину - то dmа уже не сможет начать передачу на запасись в таймер пока процессор не освободит шину. если все это делать записью 16 битных значений два раза - есть вероятность что dma воткнется в середину выполнения двух инструкций и каналы таймера разъедутся.... и последнее чтобб не словить эксепшен от невыровненого доступа нужно хитрожопо разложить поля буффера dma - читатся будет два по 16 бит, писаться один 32 бит. вот то что пишется должно быть выровнено по 32 битам. в ниже приведенном коде это особо расписано камментами св структуре dma_buff


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


в заголовке темы я объявил STM32 а сделал в итоге на ch32v307 - но разницы нет никакой - таймеры одинаковые чуть меньше чем абсолютно.


вот картинки того что я так желал, желтый и фиолетовый - верхние транзисторы моста, голубой и синий - нижние. причем в ТРЕТЬЕМ состоянии моста верхние закрыты а нижние открыты и обеспечиваю резонансный ток через контур и землю.


скважность 0.1



скважность 0.5


скважность 0.9


при управлении частотой пересчитываются cc1 cc2 для сохранения скважности

ниже мой код этого безобразия. не HAL не SPL и не регисты - мой С++ sdk но все понятно и так будет. stm32 или ch32v или gd32 все едино - продвинутые таймеры практически одинаковые.

/*
 * pump.h
 *
 *  Created on: 07 фев. 2024 г.
 *      Author: klen
 */

#pragma once

#include "appdefs.h"

// full bridge PWM. полезный видосик
// https://www.youtube.com/watch?v=Xe6H8RXnmTs

class pump_t
{
   public:
	__ai__ pump_t()
       {

/*
          tim1_ch1  pa8   верхний левый
	  tim1_ch1n pb13  нижный  левый

	  tim1_ch2  pa9   верхний правый
	  tim1_ch2n pb14  нижний  правый

	  tim1_ch4n  pa15 - маркер периода таймера - двойнвая частота ШИМ
*/

		  rcc.apb2_peripheral_clock.modify(
				  rcc_t::apb2_peripheral_clock_t::afio_t::enable,
				  rcc_t::apb2_peripheral_clock_t::gpioa_t::enable,
				  rcc_t::apb2_peripheral_clock_t::gpiob_t::enable,
				  rcc_t::apb2_peripheral_clock_t::gpioc_t::enable,
				  rcc_t::apb2_peripheral_clock_t::tim1_t::enable
				                          ) ;

		  gpioa.config1.modify(
				  gpio_t::config1_t:: pin8_mode_t::output_50mhz, gpio_t::config1_t:: pin8_output_type_t::alternate_push_pull, // tim1_ch1  pa8
				  gpio_t::config1_t:: pin9_mode_t::output_50mhz, gpio_t::config1_t:: pin9_output_type_t::alternate_push_pull  // tim1_ch2  pa9
		  					  ) ;

		  gpiob.config1.modify(
				  gpio_t::config1_t::pin13_mode_t::output_50mhz, gpio_t::config1_t::pin13_output_type_t::alternate_push_pull, // tim1_ch1n pb13
				  gpio_t::config1_t::pin14_mode_t::output_50mhz, gpio_t::config1_t::pin14_output_type_t::alternate_push_pull, // tim1_ch2n pb14

				  gpio_t::config1_t::pin15_mode_t::output_50mhz, gpio_t::config1_t::pin15_output_type_t::alternate_push_pull  // tim1_ch3n pb15
				              ) ;

          // установка tim1 dma
	      dma_channel_tim1_up_t::dma_ref().clock_enable() ;
	      dma_channel_tim1_up.clear();
	      dma_channel_tim1_up.global_interrupt_flag_clear();
	      dma_channel_tim1_up.config.write(
                         dma_channel_t::config_t::state_t::disable,
                         dma_channel_t::config_t::tx_complete_interrupt_t::disable,
                         dma_channel_t::config_t::error_interrupt_t::enable,
                         dma_channel_t::config_t::direction_t::memory_to_peripheral,
                         dma_channel_t::config_t::memory_increment_mode_t::enable,
                         dma_channel_t::config_t::peripheral_data_size_t::half_word,
                         dma_channel_t::config_t::memory_data_size_t::half_word,
                         dma_channel_t::config_t::priority_level_t::very_high,
	                 dma_channel_t::config_t::circular_mode_t::enable
                                              );

		  dma_channel_tim1_up.memory_address     =  &dma_buff.cc1a ;
		  dma_channel_tim1_up.peripheral_address =  &tim1.dma_address ;
		  dma_channel_tim1_up.counter =  4 ;
		  dma_channel_tim1_up.enable();

		  tim1.clock_enable();

		  tim1.control1.write( tim1_t::control1_t::auto_reload_preload_t::enable ) ;
		  tim1.cc_control1.write( 
tim1_t::cc_control1_t::cc1_mode_t::output_compare, tim1_t::cc_control1_t::oc1_mode_t::pwm1, tim1_t::cc_control1_t::oc1_preload_t::enable, tim1_t::cc_control1_t::cc2_mode_t::output_compare, tim1_t::cc_control1_t::oc2_mode_t::pwm1, tim1_t::cc_control1_t::oc2_preload_t::enable ) ; tim1.cc_control2.write( tim1_t::cc_control2_t::cc3_mode_t::output_compare, tim1_t::cc_control2_t::oc3_mode_t::pwm1, tim1_t::cc_control2_t::oc3_preload_t::enable ) ; tim1.cc_enable.write(
tim1_t::cc_enable_t::cc1_state_t::enable, tim1_t::cc_enable_t::oc1_polarity_t::high, tim1_t::cc_enable_t::oc1n_state_t::enable, tim1_t::cc_enable_t::oc1n_polarity_t::high, tim1_t::cc_enable_t::cc2_state_t::enable, tim1_t::cc_enable_t::oc2_polarity_t::high, tim1_t::cc_enable_t::oc2n_state_t::enable, tim1_t::cc_enable_t::oc2n_polarity_t::high, tim1_t::cc_enable_t::oc3n_state_t::enable, tim1_t::cc_enable_t::oc3n_polarity_t::low
)
; tim1.break_dead_time.write( tim1_t::break_dead_time_t::main_output_t::enable); d = pump_d ; tim1.freq( 2 * pump_f0); duty_update(); // маркер tim1.cc3 = 2 ; tim1.counter = 0; tim1.repetition_counter = 0 ; tim1.dma_base_address_cc1(); tim1.dma_burst_xfer2(); tim1.dma_event_update(); tim1.update_dma_request_enable(); tim1.enable(); } __ais__ auto dead_time() { return tim1.dead_time_generator(); } __ais__ auto dead_time(const uint8_t val) { tim1.dead_time_generator(val); } // множитель 2 - вывод импульсов в две ступени по dma 0-d-0-d-... __ais__ auto f0() { return tim1.freq() / 2 ; } __ai__ auto f0(const float val) { tim1.freq( 2 * val); duty_update(); return f0() ; } __ai__ auto f0_up (const float val = pump_freq_up_scale ) { return f0(f0()*val) ; } __ai__ auto f0_down(const float val = pump_freq_up_scale ) { return f0(f0()/val) ; } __ai__ auto duty(const float val) { d = val ; dma_buff(d); } __ai__ auto duty() const { return d ; } __ai__ void duty_update() { dma_buff(d); } __ai__ auto duty_up (const float val = pump_d_up_scale ) { d*=pump_d_up_scale ; dma_buff(d); return d ;} __ai__ auto duty_down(const float val = pump_d_up_scale ) { d/=pump_d_up_scale ; dma_buff(d); return d ;} protected: private: float d ; // скважность struct { uint16_t bummy0 = 0; // выравнивание для обеспечений адреса cc2a по гнанице слова и исключения исключения при записи словом uint16_t cc1a = 0; // <- адрес начала буфера dma (выровнен по 16 битному слову но не выровнен по 32 битному) union { struct { uint16_t cc2a = 0x8f ; // <- адрес позволяющий писать 32 битным словом uint16_t cc1b = 0x8f ; } ; uint32_t cc ; }; uint16_t cc2b = 0; uint16_t bummy1 = 0; // выравнивание для обеспечений адреса cc2a по гнанице слова и исключения исключения при записи словом __ai__ void operator () (const float val)
{
// вычисление значений cc1 сс2
uint32_t tmp = tim1.auto_reload * val ;
tmp |= tmp << 16 ;
// атомарная запись в cc2a + cc1b
cc = tmp ;
} } dma_buff __attribute__ ((aligned(8),packed)); }; #ifdef __APP_WRAPS_IMPL__ pump_t pump __attribute__ ((aligned(4),init_priority (pump_constructor_priority))) ; ; #else extern pump_t pump ; #endif