Initial I2C support (#26)

* build.zig: Trivial rename around UART test

* mmio: Add writeRaw() to set a full register

* UART: Add TODO for auto baud rate detection

* STM32F30x Initial USART1 output/transmit support

All code assumes default chip clock configuration.
Code assumes STM32F303xB / STM32F3030xC.
Code supports only 8 data bits, 1 stop bit.

* stm32f3discovery @panic() to UART1

This is done by implementing `debugWrite()` for the board,
which only initializes UART1 if that was not yet done,
and flushes afterwards to make sure the host receives all.

* stm32f303: Support UART1 reader

This is done by implementing `rx()` and `canRead()`.

* stm32f303 UART1 correctly support 7 and 8 bits

This includes correctly masking the parity bit
on reads.

* stm32f3 UART1 support 0.5/1.5/2 stop bits

* stm32f303 UART1 simplify parity code

* stm32f303 I2C rough initial code

Allows only writing and reading single bytes.

* stm32f3 i2c: enable debug 'logging'

* Add a few comments

* I2C API changes, STM32F303 I2C multi-byte transfers

Now using controller/device terminology, instead of master/slave.

Now using 'transfer objects' to make STOPs and re-STARTs explicit,
and allow using Writer and Reader APIs.

Added 'register' abstraction.

STM32F303 I2C now supports this new API, and multi-byte transfers.

Now waiting for I2C_ISR.BUSY == 0, after setting I2C_CR2.STOP == 1.
Without this, the sequence write-stop-write caused an additional STOP
to be sent immediately the START and address of the second write.

* Make work with regz-generated registers.zig change

* Updated to match regz-generated update

* After #23 repair Reset on stm32, lpc1768

* Clean-up I2C `readRegisters()`.

* Refactor to separate read/write states

* On STM32F303, make second read call fail

Also doc comments to clarify the new API.

* STM32 I2C: Properly support multiple write calls

* I2C STM32: Fix release mode compile error

...on top of an earlier commit on this branch.

* I2C Add 'write register' shorthand functions
wch-ch32v003
Marnix Klooster 3 years ago committed by GitHub
parent ba8feaed74
commit 04bf2c4d4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,150 @@
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);
const I2CDevice = struct {
const Device = @This();
internal: SystemI2CController,
address: u7,
const Direction = enum { read, write };
fn Transfer(comptime direction: Direction) type {
return switch (direction) {
.read => struct {
const Self = @This();
state: SystemI2CController.ReadState,
pub const Reader = std.io.Reader(*Self, ReadError, readSome);
/// NOTE that some platforms, notably most (all?) STM32 microcontrollers,
/// allow only a single read call per transfer.
pub fn reader(self: *Self) Reader {
return Reader{ .context = self };
}
fn readSome(self: *Self, buffer: []u8) ReadError!usize {
try self.state.readNoEof(buffer);
return buffer.len;
}
/// STOP the transfer, invalidating this object.
pub fn stop(self: *Self) !void {
try self.state.stop();
}
/// RESTART to a new transfer, invalidating this object.
/// Note that some platforms set the repeated START condition
/// on the first read or write call.
pub fn restartTransfer(self: *Self, comptime new_direction: Direction) !Transfer(new_direction) {
return Transfer(direction){ .state = try self.state.restartTransfer(new_direction) };
}
},
.write => struct {
const Self = @This();
state: SystemI2CController.WriteState,
pub const Writer = std.io.Writer(*Self, WriteError, writeSome);
/// NOTE that some platforms, notably most (all?) STM32 microcontrollers,
/// will not immediately write all bytes, but postpone that
/// to the next write call, or to the stop() call.
pub fn writer(self: *Self) Writer {
return Writer{ .context = self };
}
fn writeSome(self: *Self, buffer: []const u8) WriteError!usize {
try self.state.writeAll(buffer);
return buffer.len;
}
/// STOP the transfer, invalidating this object.
pub fn stop(self: *Self) !void {
try self.state.stop();
}
/// RESTART to a new transfer, invalidating this object.
/// Note that some platforms set the repeated START condition
/// on the first read or write call.
pub fn restartTransfer(self: *Self, comptime new_direction: Direction) !Transfer(new_direction) {
return switch (new_direction) {
.read => Transfer(new_direction){ .state = try self.state.restartRead() },
.write => Transfer(new_direction){ .state = try self.state.restartWrite() },
};
}
},
};
}
/// START a new transfer.
/// Note that some platforms set the START condition
/// on the first read or write call.
pub fn startTransfer(self: Device, comptime direction: Direction) !Transfer(direction) {
return switch (direction) {
.read => Transfer(direction){ .state = try SystemI2CController.ReadState.start(self.address) },
.write => Transfer(direction){ .state = try SystemI2CController.WriteState.start(self.address) },
};
}
/// Shorthand for 'register-based' devices
pub fn writeRegister(self: Device, register_address: u8, byte: u8) ReadError!void {
try self.writeRegisters(register_address, &.{byte});
}
/// Shorthand for 'register-based' devices
pub fn writeRegisters(self: Device, register_address: u8, buffer: []u8) ReadError!void {
var wt = try self.startTransfer(.write);
defer wt.stop() catch {};
try wt.writer().writeByte(register_address);
try wt.writer().writeAll(buffer);
}
/// Shorthand for 'register-based' devices
pub fn readRegister(self: Device, register_address: u8) ReadError!u8 {
var buffer: [1]u8 = undefined;
try self.readRegisters(register_address, &buffer);
return buffer[0];
}
/// Shorthand for 'register-based' devices
pub fn readRegisters(self: Device, register_address: u8, buffer: []u8) ReadError!void {
var rt = write_and_restart: {
var wt = try self.startTransfer(.write);
errdefer wt.stop() catch {};
try wt.writer().writeByte(1 << 7 | register_address); // MSB == 'keep sending until I STOP'
break :write_and_restart try wt.restartTransfer(.read);
};
defer rt.stop() catch {};
try rt.reader().readNoEof(buffer);
}
};
return struct {
const Self = @This();
internal: SystemI2CController,
pub fn init() InitError!Self {
return Self{
.internal = try SystemI2CController.init(),
};
}
pub fn device(self: Self, address: u7) I2CDevice {
return I2CDevice{ .internal = self.internal, .address = address };
}
};
}
pub const InitError = error{};
pub const WriteError = error{};
pub const ReadError = error{
EndOfStream,
TooFewBytesReceived,
TooManyBytesReceived,
};

@ -39,6 +39,9 @@ pub const Pin = pin.Pin;
pub const uart = @import("uart.zig");
pub const Uart = uart.Uart;
pub const i2c = @import("i2c.zig");
pub const I2CController = i2c.I2CController;
pub const debug = @import("debug.zig");
pub const mmio = @import("mmio.zig");

@ -35,6 +35,7 @@
//! and therefore USART1 runs on 8 MHz.
const std = @import("std");
const runtime_safety = std.debug.runtime_safety;
const micro = @import("microzig");
const chip = @import("registers.zig");
const regs = chip.registers;
@ -234,3 +235,215 @@ pub fn Uart(comptime index: usize) type {
}
};
}
const enable_stm32f303_debug = false;
fn debugPrint(comptime format: []const u8, args: anytype) void {
if (enable_stm32f303_debug) {
micro.debug.writer().print(format, args) catch {};
}
}
/// This implementation does not use AUTOEND=1
pub fn I2CController(comptime index: usize) type {
if (!(index == 1)) @compileError("TODO: only I2C1 is currently supported");
return struct {
const Self = @This();
pub fn init() !Self {
// 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.
// 1. Enable the I2C CLOCK and GPIO CLOCK
regs.RCC.APB1ENR.modify(.{ .I2C1EN = 1 });
regs.RCC.AHBENR.modify(.{ .IOPBEN = 1 });
debugPrint("I2C1 configuration step 1 complete\r\n", .{});
// 2. Configure the I2C PINs for ALternate Functions
// a) Select Alternate Function in MODER Register
regs.GPIOB.MODER.modify(.{ .MODER6 = 0b10, .MODER7 = 0b10 });
// b) Select Open Drain Output
regs.GPIOB.OTYPER.modify(.{ .OT6 = 1, .OT7 = 1 });
// c) Select High SPEED for the PINs
regs.GPIOB.OSPEEDR.modify(.{ .OSPEEDR6 = 0b11, .OSPEEDR7 = 0b11 });
// d) Select Pull-up for both the Pins
regs.GPIOB.PUPDR.modify(.{ .PUPDR6 = 0b01, .PUPDR7 = 0b01 });
// e) Configure the Alternate Function in AFR Register
regs.GPIOB.AFRL.modify(.{ .AFRL6 = 4, .AFRL7 = 4 });
debugPrint("I2C1 configuration step 2 complete\r\n", .{});
// 3. Reset the I2C
regs.I2C1.CR1.modify(.{ .PE = 0 });
while (regs.I2C1.CR1.read().PE == 1) {}
// DO NOT regs.RCC.APB1RSTR.modify(.{ .I2C1RST = 1 });
debugPrint("I2C1 configuration step 3 complete\r\n", .{});
// 4-6. Configure I2C1 timing, based on 8 MHz I2C clock, run at 100 kHz
// (Not using https://controllerstech.com/stm32-i2c-configuration-using-registers/
// but copying an example from the reference manual, RM0316 section 28.4.9.)
regs.I2C1.TIMINGR.modify(.{
.PRESC = 1,
.SCLL = 0x13,
.SCLH = 0xF,
.SDADEL = 0x2,
.SCLDEL = 0x4,
});
debugPrint("I2C1 configuration steps 4-6 complete\r\n", .{});
// 7. Program the I2C_CR1 register to enable the peripheral
regs.I2C1.CR1.modify(.{ .PE = 1 });
debugPrint("I2C1 configuration step 7 complete\r\n", .{});
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 {
debugPrint("I2C1 writeAll() with {d} byte(s); buffer={any}\r\n", .{ bytes.len, self.buffer[0..self.buffer_size] });
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(1);
}
}
}
fn sendBuffer(self: *WriteState, reload: u1) !void {
debugPrint("I2C1 sendBuffer() with {d} byte(s); RELOAD={d}; buffer={any}\r\n", .{ self.buffer_size, reload, self.buffer[0..self.buffer_size] });
if (self.buffer_size == 0) @panic("write of 0 bytes not supported");
std.debug.assert(reload == 0 or self.buffer_size == 255); // see TODOs below
// As master, initiate write from address, 7 bit address
regs.I2C1.CR2.modify(.{
.ADD10 = 0,
.SADD1 = self.address,
.RD_WRN = 0, // write
.NBYTES = self.buffer_size,
.RELOAD = reload,
});
if (reload == 0) {
regs.I2C1.CR2.modify(.{ .START = 1 });
} else {
// TODO: The RELOAD=1 path is untested but doesn't seem to work yet,
// even though we make sure that we set NBYTES=255 per the docs.
}
for (self.buffer[0..self.buffer_size]) |b| {
// wait for empty transmit buffer
while (regs.I2C1.ISR.read().TXE == 0) {
debugPrint("I2C1 waiting for ready to send (TXE=0)\r\n", .{});
}
debugPrint("I2C1 ready to send (TXE=1)\r\n", .{});
// Write data byte
regs.I2C1.TXDR.modify(.{ .TXDATA = b });
}
self.buffer_size = 0;
debugPrint("I2C1 data written\r\n", .{});
if (reload == 1) {
// TODO: The RELOAD=1 path is untested but doesn't seem to work yet,
// the following loop never seems to finish.
while (regs.I2C1.ISR.read().TCR == 0) {
debugPrint("I2C1 waiting transmit complete (TCR=0)\r\n", .{});
}
debugPrint("I2C1 transmit complete (TCR=1)\r\n", .{});
} else {
while (regs.I2C1.ISR.read().TC == 0) {
debugPrint("I2C1 waiting for transmit complete (TC=0)\r\n", .{});
}
debugPrint("I2C1 transmit complete (TC=1)\r\n", .{});
}
}
pub fn stop(self: *WriteState) !void {
try self.sendBuffer(0);
// Communication STOP
debugPrint("I2C1 STOPping\r\n", .{});
regs.I2C1.CR2.modify(.{ .STOP = 1 });
while (regs.I2C1.ISR.read().BUSY == 1) {}
debugPrint("I2C1 STOPped\r\n", .{});
}
pub fn restartRead(self: *WriteState) !ReadState {
try self.sendBuffer(0);
return ReadState{ .address = self.address };
}
pub fn restartWrite(self: *WriteState) !WriteState {
try self.sendBuffer(0);
return WriteState{ .address = self.address };
}
};
pub const ReadState = struct {
address: u7,
read_allowed: if (runtime_safety) bool else void = if (runtime_safety) true else {},
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 {
if (runtime_safety and !self.read_allowed) @panic("second read call not allowed");
std.debug.assert(buffer.len < 256); // TODO: use RELOAD to read more data
// As master, initiate read from accelerometer, 7 bit address
regs.I2C1.CR2.modify(.{
.ADD10 = 0,
.SADD1 = self.address,
.RD_WRN = 1, // read
.NBYTES = @intCast(u8, buffer.len),
});
debugPrint("I2C1 prepared for read of {} byte(s) from 0b{b:0<7}\r\n", .{ buffer.len, self.address });
// Communication START
regs.I2C1.CR2.modify(.{ .START = 1 });
debugPrint("I2C1 RXNE={}\r\n", .{regs.I2C1.ISR.read().RXNE});
debugPrint("I2C1 STARTed\r\n", .{});
debugPrint("I2C1 RXNE={}\r\n", .{regs.I2C1.ISR.read().RXNE});
if (runtime_safety) self.read_allowed = false;
for (buffer) |_, i| {
// Wait for data to be received
while (regs.I2C1.ISR.read().RXNE == 0) {
debugPrint("I2C1 waiting for data (RXNE=0)\r\n", .{});
}
debugPrint("I2C1 data ready (RXNE=1)\r\n", .{});
// Read first data byte
buffer[i] = regs.I2C1.RXDR.read().RXDATA;
}
debugPrint("I2C1 data: {any}\r\n", .{buffer});
}
pub fn stop(_: *ReadState) !void {
// Communication STOP
regs.I2C1.CR2.modify(.{ .STOP = 1 });
while (regs.I2C1.ISR.read().BUSY == 1) {}
debugPrint("I2C1 STOPped\r\n", .{});
}
pub fn restartRead(self: *ReadState) !ReadState {
debugPrint("I2C1 no action for restart\r\n", .{});
return ReadState{ .address = self.address };
}
pub fn restartWrite(self: *ReadState) !WriteState {
debugPrint("I2C1 no action for restart\r\n", .{});
return WriteState{ .address = self.address };
}
};
};
}

Loading…
Cancel
Save