|
| 1 | +//NOTE: this is an advanced example and is recommended for those who already have experience with the STM32F1 |
| 2 | +//Example to demonstrate the use of input capture on STM32F1xx |
| 3 | +//in this example an operation called PWM input is performed |
| 4 | +//where two channels read the same input but are triggered on different edges |
| 5 | +//thus measuring the frequency and duty cycle of a PWM signal |
| 6 | + |
| 7 | +const std = @import("std"); |
| 8 | +const microzig = @import("microzig"); |
| 9 | + |
| 10 | +//example usage |
| 11 | +const stm32 = microzig.hal; |
| 12 | +const gpio = stm32.gpio; |
| 13 | +const GPTimer = stm32.timer.GPTimer; |
| 14 | + |
| 15 | +//gpios |
| 16 | +const ch1 = gpio.Pin.from_port(.A, 0); |
| 17 | + |
| 18 | +const uart = stm32.uart.UART.init(.USART1); |
| 19 | +const TX = gpio.Pin.from_port(.A, 9); |
| 20 | + |
| 21 | +pub const microzig_options = microzig.Options{ |
| 22 | + .logFn = stm32.uart.log, |
| 23 | + .interrupts = .{ .TIM2 = .{ .c = isr_tim2 } }, |
| 24 | +}; |
| 25 | + |
| 26 | +const comp = GPTimer.init(.TIM2); |
| 27 | +const counter = GPTimer.init(.TIM3).into_counter_mode(); |
| 28 | + |
| 29 | +var global_uptime: i32 = 0; |
| 30 | +var global_downtime: i32 = 0; |
| 31 | +var global_dif: u32 = 0; |
| 32 | + |
| 33 | +var freq: i32 = 0; |
| 34 | +var duty_cycle: f32 = 0; |
| 35 | + |
| 36 | +//function that handles timer interrupts |
| 37 | +//since the timer is reset at each rising edge, |
| 38 | +//the value of ch2 marks the moment when the signal went from high to low, indicating the duty cycle. |
| 39 | +//the value of ch1 marks the point when the signal goes back to high before the timer resets, indicating the period. |
| 40 | +fn isr_tim2() callconv(.C) void { |
| 41 | + const flags = comp.get_interrupt_flags(); |
| 42 | + if (flags.channel2) { |
| 43 | + const rev_ch1 = comp.read_ccr(0); |
| 44 | + const rev_fch1: f32 = @floatFromInt(rev_ch1); |
| 45 | + const rev_ch2 = comp.read_ccr(1); |
| 46 | + const rev_fch2: f32 = @floatFromInt(rev_ch2); |
| 47 | + |
| 48 | + global_uptime = rev_ch1; |
| 49 | + global_downtime = rev_ch2; |
| 50 | + |
| 51 | + global_dif = @intCast(@abs(global_uptime - global_downtime)); |
| 52 | + freq = @divFloor(1_000_000, global_uptime); |
| 53 | + |
| 54 | + duty_cycle = (rev_fch2 / rev_fch1) * 100; |
| 55 | + } |
| 56 | + |
| 57 | + comp.clear_interrupts(); |
| 58 | +} |
| 59 | + |
| 60 | +//timers |
| 61 | +pub fn main() !void { |
| 62 | + |
| 63 | + //first we need to enable the clocks for the GPIO and TIM peripherals |
| 64 | + |
| 65 | + //use HSE as system clock source, more stable than HSI |
| 66 | + try stm32.rcc.clock_init(.{ .SysClkSource = .RCC_SYSCLKSOURCE_HSE }); |
| 67 | + |
| 68 | + //enable GPIOA and TIM2, TIM3, AFIO clocks |
| 69 | + //AFIO is needed for alternate function remapping, not used in this example but eneble for easy remapping |
| 70 | + //if needed |
| 71 | + stm32.rcc.enable_clock(.GPIOA); |
| 72 | + stm32.rcc.enable_clock(.TIM2); |
| 73 | + stm32.rcc.enable_clock(.TIM3); |
| 74 | + stm32.rcc.enable_clock(.AFIO); |
| 75 | + stm32.rcc.enable_clock(.USART1); |
| 76 | + |
| 77 | + TX.set_output_mode(.alternate_function_push_pull, .max_50MHz); |
| 78 | + |
| 79 | + uart.apply(.{ |
| 80 | + .baud_rate = 115200, |
| 81 | + .clock_speed = 8_000_000, |
| 82 | + }); |
| 83 | + |
| 84 | + stm32.uart.init_logger(&uart); |
| 85 | + |
| 86 | + //counter device to genereate delays |
| 87 | + const cd = counter.counter_device(8_000_000); //8MHz clock |
| 88 | + |
| 89 | + //set PA0 (TI1) to input |
| 90 | + ch1.set_input_mode(.floating); |
| 91 | + |
| 92 | + //configure the timer for a frequency of 1MHz counting upwards |
| 93 | + //enable slave mode to reset the counter on the rising edge of TI1 (after filtering) |
| 94 | + //filter only can be configured by channel 1 (index 0) |
| 95 | + comp.timer_general_config(.{ |
| 96 | + .prescaler = 7, |
| 97 | + .counter_mode = .{ .up = {} }, |
| 98 | + .slave_config = .{ |
| 99 | + .trigger_source = .TI1FP1, |
| 100 | + .mode = .ResetMode, |
| 101 | + }, |
| 102 | + }); |
| 103 | + comp.start(); |
| 104 | + |
| 105 | + //configure channel 1 and 2 for the same input (TI1) with inverted edges between them |
| 106 | + comp.configure_ccr(0, .{ |
| 107 | + .ch_mode = .{ .capture = .{} }, |
| 108 | + }); |
| 109 | + |
| 110 | + comp.configure_ccr(1, .{ |
| 111 | + .ch_mode = .{ .capture = .{ .mode = .input_alternate } }, |
| 112 | + .polarity = .low, |
| 113 | + .channel_interrupt_enable = true, |
| 114 | + }); |
| 115 | + |
| 116 | + //enable timer channels and interrupts |
| 117 | + comp.set_channel(0, true); |
| 118 | + comp.set_channel(1, true); |
| 119 | + comp.set_interrupt(true); |
| 120 | + microzig.interrupt.enable_interrupts(); |
| 121 | + microzig.interrupt.enable(.TIM2); |
| 122 | + |
| 123 | + while (true) { |
| 124 | + //values are received in the timer interrupt |
| 125 | + std.log.info("uptime: {d}", .{global_uptime}); |
| 126 | + std.log.info("downtime: {d}", .{global_downtime}); |
| 127 | + std.log.info("freq: {d}HZ", .{freq}); |
| 128 | + std.log.info("duty: {d:.2}%", .{duty_cycle}); |
| 129 | + |
| 130 | + cd.sleep_ms(1350); |
| 131 | + } |
| 132 | +} |
0 commit comments