Skip to main content
Visitor II
June 10, 2025
Question

STM32F103 ADC: accidental trigger if CR2 value does not change after a write

  • June 10, 2025
  • 0 replies
  • 329 views

I'm writing a small HAL of my own for the STM32F1 and I noticed that sometimes the DMA would read in the wrong order for seemingly no reason.

After several tests, I came up with this code:

const std = @import("std");
const microzig = @import("microzig");

const RCC = microzig.chip.peripherals.RCC;
const DMA = microzig.chip.peripherals.DMA1;
const DMA_t = microzig.chip.types.peripherals.bdma_v1;
const stm32 = microzig.hal;
const timer = microzig.hal.timer.GPTimer.init(.TIM2);

const uart = stm32.uart.UART.init(.USART1);
const gpio = stm32.gpio;
const TX = gpio.Pin.from_port(.A, 9);

pub const microzig_options = microzig.Options{
 .logFn = stm32.uart.log,
};

const AdvancedADC = microzig.hal.adc.AdvancedADC;
const ADC_pin1 = gpio.Pin.from_port(.A, 1);
const ADC_pin2 = gpio.Pin.from_port(.A, 2);

fn DMA_init(arr_addr: u32, adc_addr: u32) void {
 const CH1: *volatile DMA_t.CH = @ptrCast(&DMA.CH);
 CH1.CR.raw = 0; //disable channel
 CH1.NDTR.raw = 0;
 CH1.CR.modify(.{
 .DIR = DMA_t.DIR.FromPeripheral,
 .CIRC = 0, //disable circular mode
 .PL = DMA_t.PL.High, //high priority
 .MSIZE = DMA_t.SIZE.Bits16,
 .PSIZE = DMA_t.SIZE.Bits16,
 .MINC = 1, //memory increment mode
 .PINC = 0, //peripheral not incremented
 });

 CH1.NDTR.modify(.{ .NDT = 4 }); //number of data to transfer, 4 samples
 CH1.PAR = adc_addr; //peripheral address
 CH1.MAR = arr_addr; //memory address
 CH1.CR.modify(.{ .EN = 1 }); //enable channel
}

pub fn main() !void {
 RCC.AHBENR.modify(.{
 .DMA1EN = 1,
 });
 RCC.APB1ENR.modify(.{
 .TIM2EN = 1,
 });
 RCC.APB2ENR.modify(.{
 .AFIOEN = 1,
 .USART1EN = 1,
 .GPIOAEN = 1,
 .ADC1EN = 1,
 });
 const counter = timer.into_counter(8_000_000);
 const adc = AdvancedADC.init(.ADC1);

 const adc_data_addr: u32 = @intFromPtr(&adc.regs.DR);
 var adc_buf: [10]u16 = .{1234} ** 10;
 const adc_buf_addr: u32 = @intFromPtr(&adc_buf);

 DMA_init(adc_buf_addr, adc_data_addr);

 TX.set_output_mode(.alternate_function_push_pull, .max_50MHz);
 ADC_pin1.set_input_mode(.analog);
 ADC_pin2.set_input_mode(.analog);

 uart.apply(.{
 .baud_rate = 115200,
 .clock_speed = 8_000_000,
 });

 stm32.uart.init_logger(&uart);

 //Force disable ADC before any config
 adc.regs.SR.raw = 0;
 adc.regs.CR1.raw = 0;
 adc.regs.CR2.raw = 0;
 counter.sleep_ms(100);

 //enable ADC and temp sensor
 adc.regs.CR2.raw = 1 | (u32, 1) << 23;
 counter.sleep_ms(100);

 adc.load_sequence(&.{ 16, 17, 1, 2 });

 adc.regs.CR1.raw |= (u32, 1) << 8; //enable SCAN (bit 8)

 adc.regs.CR2.raw |= (@as(u32, 1) << 8 | (u32, 1) << 20); //enable DMA (bit 8) and EXTERNAL trigger (bit20)

 counter.sleep_ms(100); //just a dummy sleep

 adc.regs.CR1.raw |= 0; //load nothing

 counter.sleep_ms(100); //just a dummy sleep

 //=========TEST===============

 //adc.regs.CR2.raw |= 0b10; //enable CONT (bit 2) //register value changes, does not start a conversion
 //adc.regs.CR2.raw |= 0; //load nothing, value is the same, starts a ADC conversion
 //adc.regs.CR2.raw |= (@as(u32, 1) << 8 | (u32, 1) << 20); //load the same value as before, value keep the same, start a ADC conversion

 //just read the CR2, does not start a conversion
 //const foo = adc.regs.CR2.raw;
 //std.mem.doNotOptimizeAway(foo);

 counter.sleep_ms(1000);

 std.log.info("arr1: {d:0>4}", .{adc_buf[0]});
 std.log.info("arr2: {d:0>4}", .{adc_buf[1]});
 std.log.info("arr3: {d:0>4}", .{adc_buf[2]});
 std.log.info("arr4: {d:0>4}", .{adc_buf[3]});

 while (true) {}
}

(it's a Zig code made with microzig, if you want to run it just take this example and replace it with this code)



with this I noticed that if a write happens on CR2, but the value remains the same, this is interpreted as ADON activating 2x starting a conversion

it's a super simple fix in code, but I couldn't find any source citing it directly, which took a good few hours of debugging

is there any other obscure behavior in the ADC in these "old" STM32s?

    This topic has been closed for replies.