I2C improvements (#58)

* i2c: pass a config to init

For now, allow setting the i2c target speed

* i2c: allow selecting the scl/sda pins

Similar to the previous commit for uart, this allows specifying the pins for
microcontrollers that support multiple pins for a single i2c peripheral

* stm32f407: add support for i2c

Supports using all I2C peripherals with all their pins in standard mode (fast
mode support will be added in the future).
wch-ch32v003
Riccardo Binetti 2 years ago committed by GitHub
parent 7cf623aaf2
commit a400e36058
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,8 +2,8 @@ const std = @import("std");
const micro = @import("microzig.zig");
const chip = @import("chip");
pub fn I2CController(comptime index: usize) type {
const SystemI2CController = chip.I2CController(index);
pub fn I2CController(comptime index: usize, comptime pins: Pins) type {
const SystemI2CController = chip.I2CController(index, pins);
const I2CDevice = struct {
const Device = @This();
@ -129,9 +129,9 @@ pub fn I2CController(comptime index: usize) type {
internal: SystemI2CController,
pub fn init() InitError!Self {
pub fn init(config: Config) InitError!Self {
return Self{
.internal = try SystemI2CController.init(),
.internal = try SystemI2CController.init(config),
};
}
@ -141,7 +141,24 @@ pub fn I2CController(comptime index: usize) type {
};
}
pub const InitError = error{};
/// The pin configuration. This is used to optionally configure specific pins to be used with the chosen I2C.
/// This makes sense only with microcontrollers supporting multiple pins for an I2C peripheral.
pub const Pins = struct {
scl: ?type = null,
sda: ?type = null,
};
/// A UART configuration. The config defaults to the *8N1* setting, so "8 data bits, no parity, 1 stop bit" which is the
/// most common serial format.
pub const Config = struct {
/// The target speed in bit/s. Note that the actual speed can differ from this, due to prescaler rounding.
target_speed: u32,
};
pub const InitError = error{
InvalidBusFrequency,
InvalidSpeed,
};
pub const WriteError = error{};
pub const ReadError = error{
EndOfStream,

@ -265,13 +265,17 @@ fn debugPrint(comptime format: []const u8, args: anytype) void {
}
/// This implementation does not use AUTOEND=1
pub fn I2CController(comptime index: usize) type {
pub fn I2CController(comptime index: usize, comptime pins: micro.i2c.Pins) type {
if (!(index == 1)) @compileError("TODO: only I2C1 is currently supported");
if (pins.scl != null or pins.sda != null)
@compileError("TODO: custom pins are not currently supported");
return struct {
const Self = @This();
pub fn init() !Self {
pub fn init(config: micro.i2c.Config) !Self {
// TODO: use config
_ = config;
// CONFIGURE I2C1
// connected to APB1, MCU pins PB6 + PB7 = I2C1_SCL + I2C1_SDA,
// if GPIO port B is configured for alternate function 4 for these PB pins.

@ -362,3 +362,265 @@ pub fn Uart(comptime index: usize, comptime pins: micro.uart.Pins) type {
}
};
}
pub const i2c = struct {
const PinLine = std.meta.FieldEnum(micro.i2c.Pins);
/// Checks if a pin is valid for a given i2c index and line
pub fn isValidPin(comptime pin: type, comptime index: usize, comptime line: PinLine) bool {
const pin_name = pin.name;
return switch (line) {
.scl => switch (index) {
1 => std.mem.eql(u8, pin_name, "PB6") or std.mem.eql(u8, pin_name, "PB8"),
2 => std.mem.eql(u8, pin_name, "PB10") or std.mem.eql(u8, pin_name, "PF1") or std.mem.eql(u8, pin_name, "PH4"),
3 => std.mem.eql(u8, pin_name, "PA8") or std.mem.eql(u8, pin_name, "PH7"),
else => unreachable,
},
// Valid RX pins for the UARTs
.sda => switch (index) {
1 => std.mem.eql(u8, pin_name, "PB7") or std.mem.eql(u8, pin_name, "PB9"),
2 => std.mem.eql(u8, pin_name, "PB11") or std.mem.eql(u8, pin_name, "PF0") or std.mem.eql(u8, pin_name, "PH5"),
3 => std.mem.eql(u8, pin_name, "PC9") or std.mem.eql(u8, pin_name, "PH8"),
else => unreachable,
},
};
}
};
pub fn I2CController(comptime index: usize, comptime pins: micro.i2c.Pins) type {
if (index < 1 or index > 3) @compileError("Valid I2C index are 1..3");
const i2c_name = std.fmt.comptimePrint("I2C{d}", .{index});
const scl_pin =
if (pins.scl) |scl|
if (uart.isValidPin(scl, index, .scl))
scl
else
@compileError(std.fmt.comptimePrint("SCL pin {s} is not valid for I2C{}", .{ scl.name, index }))
else switch (index) {
// Provide default scl pins if no pin is specified
1 => micro.Pin("PB6"),
2 => micro.Pin("PB10"),
3 => micro.Pin("PA8"),
else => unreachable,
};
const sda_pin =
if (pins.sda) |sda|
if (uart.isValidPin(sda, index, .sda))
sda
else
@compileError(std.fmt.comptimePrint("SDA pin {s} is not valid for UART{}", .{ sda.name, index }))
else switch (index) {
// Provide default sda pins if no pin is specified
1 => micro.Pin("PB7"),
2 => micro.Pin("PB11"),
3 => micro.Pin("PC9"),
else => unreachable,
};
const scl_gpio = micro.Gpio(scl_pin, .{
.mode = .alternate_function,
.alternate_function = .af4,
});
const sda_gpio = micro.Gpio(sda_pin, .{
.mode = .alternate_function,
.alternate_function = .af4,
});
// Base field of the specific I2C peripheral
const i2c_base = @field(regs, i2c_name);
return struct {
const Self = @This();
pub fn init(config: micro.i2c.Config) !Self {
// Configure I2C
// 1. Enable the I2C CLOCK and GPIO CLOCK
regs.RCC.APB1ENR.modify(.{ .I2C1EN = 1 });
regs.RCC.AHB1ENR.modify(.{ .GPIOBEN = 1 });
// 2. Configure the I2C PINs
// This takes care of setting them alternate function mode with the correct AF
scl_gpio.init();
sda_gpio.init();
// TODO: the stuff below will probably use the microzig gpio API in the future
const scl = scl_pin.source_pin;
const sda = sda_pin.source_pin;
// Select Open Drain Output
setRegField(@field(scl.gpio_port, "OTYPER"), "OT" ++ scl.suffix, 1);
setRegField(@field(sda.gpio_port, "OTYPER"), "OT" ++ sda.suffix, 1);
// Select High Speed
setRegField(@field(scl.gpio_port, "OSPEEDR"), "OSPEEDR" ++ scl.suffix, 0b10);
setRegField(@field(sda.gpio_port, "OSPEEDR"), "OSPEEDR" ++ sda.suffix, 0b10);
// Activate Pull-up
setRegField(@field(scl.gpio_port, "PUPDR"), "PUPDR" ++ scl.suffix, 0b01);
setRegField(@field(sda.gpio_port, "PUPDR"), "PUPDR" ++ sda.suffix, 0b01);
// 3. Reset the I2C
i2c_base.CR1.modify(.{ .PE = 0 });
while (i2c_base.CR1.read().PE == 1) {}
// 4. Configure I2C timing
const bus_frequency_hz = micro.clock.get().apb1;
const bus_frequency_mhz: u6 = @intCast(u6, @divExact(bus_frequency_hz, 1_000_000));
if (bus_frequency_mhz < 2 or bus_frequency_mhz > 50) {
return error.InvalidBusFrequency;
}
// .FREQ is set to the bus frequency in Mhz
i2c_base.CR2.modify(.{ .FREQ = bus_frequency_mhz });
switch (config.target_speed) {
10_000...100_000 => {
// CCR is bus_freq / (target_speed * 2). We use floor to avoid exceeding the target speed.
const ccr = @intCast(u12, @divFloor(bus_frequency_hz, config.target_speed * 2));
i2c_base.CCR.modify(.{ .CCR = ccr });
// Trise is bus frequency in Mhz + 1
i2c_base.TRISE.modify(bus_frequency_mhz + 1);
},
100_001...400_000 => {
// TODO: handle fast mode
return error.InvalidSpeed;
},
else => return error.InvalidSpeed,
}
// 5. Program the I2C_CR1 register to enable the peripheral
i2c_base.CR1.modify(.{ .PE = 1 });
return Self{};
}
pub const WriteState = struct {
address: u7,
buffer: [255]u8 = undefined,
buffer_size: u8 = 0,
pub fn start(address: u7) !WriteState {
return WriteState{ .address = address };
}
pub fn writeAll(self: *WriteState, bytes: []const u8) !void {
std.debug.assert(self.buffer_size < 255);
for (bytes) |b| {
self.buffer[self.buffer_size] = b;
self.buffer_size += 1;
if (self.buffer_size == 255) {
try self.sendBuffer();
}
}
}
fn sendBuffer(self: *WriteState) !void {
if (self.buffer_size == 0) @panic("write of 0 bytes not supported");
// Wait for the bus to be free
while (i2c_base.SR2.read().BUSY == 1) {}
// Send start
i2c_base.CR1.modify(.{ .START = 1 });
// Wait for the end of the start condition, master mode selected, and BUSY bit set
while ((i2c_base.SR1.read().SB == 0 or
i2c_base.SR2.read().MSL == 0 or
i2c_base.SR2.read().BUSY == 0))
{}
// Write the address to bits 7..1, bit 0 stays at 0 to indicate write operation
i2c_base.DR.modify(@intCast(u8, self.address) << 1);
// Wait for address confirmation
while (i2c_base.SR1.read().ADDR == 0) {}
// Read SR2 to clear address condition
_ = i2c_base.SR2.read();
for (self.buffer[0..self.buffer_size]) |b| {
// Write data byte
i2c_base.DR.modify(b);
// Wait for transfer finished
while (i2c_base.SR1.read().BTF == 0) {}
}
self.buffer_size = 0;
}
pub fn stop(self: *WriteState) !void {
try self.sendBuffer();
// Communication STOP
i2c_base.CR1.modify(.{ .STOP = 1 });
while (i2c_base.SR2.read().BUSY == 1) {}
}
pub fn restartRead(self: *WriteState) !ReadState {
try self.sendBuffer();
return ReadState{ .address = self.address };
}
pub fn restartWrite(self: *WriteState) !WriteState {
try self.sendBuffer();
return WriteState{ .address = self.address };
}
};
pub const ReadState = struct {
address: u7,
pub fn start(address: u7) !ReadState {
return ReadState{ .address = address };
}
/// Fails with ReadError if incorrect number of bytes is received.
pub fn readNoEof(self: *ReadState, buffer: []u8) !void {
std.debug.assert(buffer.len < 256);
// Send start and enable ACK
i2c_base.CR1.modify(.{ .START = 1, .ACK = 1 });
// Wait for the end of the start condition, master mode selected, and BUSY bit set
while ((i2c_base.SR1.read().SB == 0 or
i2c_base.SR2.read().MSL == 0 or
i2c_base.SR2.read().BUSY == 0))
{}
// Write the address to bits 7..1, bit 0 set to 1 to indicate read operation
i2c_base.DR.modify((@intCast(u8, self.address) << 1) | 1);
// Wait for address confirmation
while (i2c_base.SR1.read().ADDR == 0) {}
// Read SR2 to clear address condition
_ = i2c_base.SR2.read();
for (buffer) |_, i| {
if (i == buffer.len - 1) {
// Disable ACK
i2c_base.CR1.modify(.{ .ACK = 0 });
}
// Wait for data to be received
while (i2c_base.SR1.read().RxNE == 0) {}
// Read data byte
buffer[i] = i2c_base.DR.read();
}
}
pub fn stop(_: *ReadState) !void {
// Communication STOP
i2c_base.CR1.modify(.{ .STOP = 1 });
while (i2c_base.SR2.read().BUSY == 1) {}
}
pub fn restartRead(self: *ReadState) !ReadState {
return ReadState{ .address = self.address };
}
pub fn restartWrite(self: *ReadState) !WriteState {
return WriteState{ .address = self.address };
}
};
};
}

Loading…
Cancel
Save