2025-06-09
6:18 PM
- last edited on
2025-06-10
8:31 AM
by
Lina_DABASINSKA
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?