// STM32C031C6 - PWM trifásico por software (registradores) com novos pinos
// Potenciômetros: PA5, PA6, PA7
// Saídas PWM: PB0 (Fase A), PB1 (Fase B), PB2 (Fase C)
// Frequência de modulação: 60 Hz; PWM: ~500 Hz
#include <Arduino.h>
#include "stm32c0xx.h"
#include <math.h>
#define F_CPU_HZ 16000000UL
// === Mapeamento de pinos ===
#define GPIO_PWM_PORT GPIOB
#define PWM_A_PIN 0U // PB0 (Fase A)
#define PWM_B_PIN 1U // PB1 (Fase B)
#define PWM_C_PIN 2U // PB2 (Fase C)
#define ADC_GPIO_PORT GPIOA
#define ADC_A0_PIN 5U // PA5 -> ADC1_IN5
#define ADC_A1_PIN 6U // PA6 -> ADC1_IN6
#define ADC_A2_PIN 7U // PA7 -> ADC1_IN7
// === Parâmetros da estratégia do guia ===
#define F_SINE_HZ 60.0f
#define TWO_PI 6.28318530718f
#define DT_SINE_MS 1.0f // Atualiza seno a cada 1 ms (~1 kHz)
#define PWM_TOP 200 // Resolução do PWM por software
#define TICK_US 10 // Período do "sub-tick" do PWM -> ~500 Hz
// Defasagens de 120°
#define PHASE_A 0.0f
#define PHASE_B (-2.0f * 3.14159265359f / 3.0f) // -120°
#define PHASE_C (+2.0f * 3.14159265359f / 3.0f) // +120°
static volatile uint16_t pwmCount = 0;
static volatile uint32_t usAccum = 0; // Acumulador de micros (base de tempo)
static volatile uint32_t msAccum = 0; // Acumulador de millis
static int dutyCount[3] = {0,0,0};
static float theta = 0.0f;
// ================== Utilidades de GPIO ==================
static inline void gpio_set_output(GPIO_TypeDef* port, uint32_t pin)
{
// Configura pino como saída
port->MODER &= ~(0x3UL << (pin*2));
port->MODER |= (0x1UL << (pin*2)); // Output
// Configuração push-pull, sem pull-up, com baixa velocidade
port->OTYPER &= ~(1UL << pin);
port->OSPEEDR &= ~(0x3UL << (pin*2));
port->PUPDR &= ~(0x3UL << (pin*2));
}
static inline void gpio_write_high(GPIO_TypeDef* port, uint32_t pin)
{
port->BSRR = (1UL << pin);
}
static inline void gpio_write_low(GPIO_TypeDef* port, uint32_t pin)
{
port->BSRR = (1UL << (pin + 16U));
}
// ================== Função de temporização com micros() ==================
unsigned long lastMicros = 0;
// ================== ADC1: leitura de um canal ==================
static void adc_init(void)
{
// Habilita clock de GPIOA e ADC
RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
RCC->APBENR2 |= RCC_APBENR2_ADCEN;
// Configura pinos PA5, PA6, PA7 como ANALÓGICO
ADC_GPIO_PORT->MODER |= (0x3UL<<(ADC_A0_PIN*2)) | (0x3UL<<(ADC_A1_PIN*2)) | (0x3UL<<(ADC_A2_PIN*2));
ADC_GPIO_PORT->PUPDR &= ~((0x3UL<<(ADC_A0_PIN*2)) | (0x3UL<<(ADC_A1_PIN*2)) | (0x3UL<<(ADC_A2_PIN*2)));
// Seleciona clock do ADC (opcional: RCC->CCIPR se necessário)
// Calibração
if ((ADC1->CR & ADC_CR_ADEN) != 0) {
ADC1->CR |= ADC_CR_ADDIS; // Desabilitar se estava habilitado
}
while (ADC1->CR & ADC_CR_ADEN) { /* Aguarda */ }
ADC1->CR |= ADC_CR_ADCAL; // Iniciar calibração
while (ADC1->CR & ADC_CR_ADCAL) { /* Aguarda */ }
// Habilita ADC
ADC1->CR |= ADC_CR_ADEN;
while ((ADC1->ISR & ADC_ISR_ADRDY) == 0) { /* Aguarda pronto */ }
// Amostragem: SMPR (um valor médio funciona bem p/ potenciômetro)
ADC1->SMPR = 0x2; // Ajuste de sample time (dependente do RM; 0x2 ~ ~19.5 cycles típico)
}
static uint16_t adc_read_channel(uint8_t ch) // ch: 0,1,4...
{
// Seleciona canal (CHSELR)
ADC1->CHSELR = (1UL << ch);
// Inicia conversão
ADC1->CR |= ADC_CR_ADSTART;
// Espera EOC (end of conversion)
while ((ADC1->ISR & ADC_ISR_EOC) == 0) { /* Aguarda */ }
uint16_t val = (uint16_t)ADC1->DR;
// (EOC é limpo ao ler DR)
return val; // 12 bits: 0..4095
}
// ================== RCC/GPIO Init (pinos de saída) ==================
static void gpio_init_pwm_pins(void)
{
RCC->IOPENR |= RCC_IOPENR_GPIOBEN; // Habilita clock GPIOB
gpio_set_output(GPIO_PWM_PORT, PWM_A_PIN);
gpio_set_output(GPIO_PWM_PORT, PWM_B_PIN);
gpio_set_output(GPIO_PWM_PORT, PWM_C_PIN);
// Inicialmente em LOW
gpio_write_low(GPIO_PWM_PORT, PWM_A_PIN);
gpio_write_low(GPIO_PWM_PORT, PWM_B_PIN);
gpio_write_low(GPIO_PWM_PORT, PWM_C_PIN);
}
// ================== Main ==================
void setup()
{
// Habilita GPIO/ADC
gpio_init_pwm_pins();
adc_init();
// Configura o tempo base com micros()
lastMicros = micros();
Serial.begin(115200);
}
void loop()
{
unsigned long currentMicros = micros();
if (currentMicros - lastMicros >= TICK_US) { // Verifica o intervalo de 10 us
lastMicros = currentMicros;
pwmCount++;
if (pwmCount >= PWM_TOP) pwmCount = 0;
// Leitura dos potenciômetros
float A[3];
uint16_t r0 = adc_read_channel(ADC_A0_PIN); // PA5
uint16_t r1 = adc_read_channel(ADC_A1_PIN); // PA6
uint16_t r2 = adc_read_channel(ADC_A2_PIN); // PA7
A[0] = (float)r0 / 4095.0f;
A[1] = (float)r1 / 4095.0f;
A[2] = (float)r2 / 4095.0f;
// Senos defasados
float sA = sinf(theta + PHASE_A);
float sB = sinf(theta + PHASE_B);
float sC = sinf(theta + PHASE_C);
// Mapeamento com offset (duty em [0..1])
float duty[3] = {
0.5f + 0.5f * A[0] * sA,
0.5f + 0.5f * A[1] * sB,
0.5f + 0.5f * A[2] * sC
};
// Saturação e conversão para contagem 0..PWM_TOP
for (int i = 0; i < 3; i++) {
if (duty[i] < 0.0f) duty[i] = 0.0f;
if (duty[i] > 1.0f) duty[i] = 1.0f;
dutyCount[i] = (int)(duty[i] * (float)PWM_TOP);
if (dutyCount[i] > (int)PWM_TOP) dutyCount[i] = PWM_TOP;
}
// Avança ângulo e dobra quando passa 2π
theta += (TWO_PI * F_SINE_HZ * (DT_SINE_MS / 1000.0f));
if (theta >= TWO_PI) theta -= TWO_PI;
// Atualiza saídas (software PWM)
if (pwmCount < dutyCount[0]) gpio_write_high(GPIO_PWM_PORT, PWM_A_PIN);
else gpio_write_low (GPIO_PWM_PORT, PWM_A_PIN);
if (pwmCount < dutyCount[1]) gpio_write_high(GPIO_PWM_PORT, PWM_B_PIN);
else gpio_write_low (GPIO_PWM_PORT, PWM_B_PIN);
if (pwmCount < dutyCount[2]) gpio_write_high(GPIO_PWM_PORT, PWM_C_PIN);
else gpio_write_low (GPIO_PWM_PORT, PWM_C_PIN);
}
}