Initial partial SPI support (#97)

* 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

* Make sure vector_table is correctly exported

It needs to be a non-`comptime` `var` for `@export` to work properly.

The only 'documentation' for this behavior currently seems GitHub comment
https://github.com/ziglang/zig/issues/5157#issuecomment-618933196 .

This issue was introduced in 1c17304 for PR #27,
which broke at least ARM-based STM32F303.

* fix missing vector table on ARM

* Revert "Merge branch 'fix-vector_table-export' into marnix-master"

This reverts commit 8ea0a74e1031cd0b88abe0283f179f0cf20f450c, reversing
changes made to 355a3618080d28c5da6e044773e6449989355fe5.

* Temp commit for SPI

* Check new I2C target_speed config setting

* Corrected incorrect doc comment

* Initial SPI transfer support for STM32F303

* SPI device CS pin is now used

* Revert accidentally committed debug flag.

* SPI: Add shorthands for 'register-based' devices.

* Additional fix to remove PE3 pin dependency

* SPI: Renames, comments, extracted device-specific code

Specifically,
top-level `Spi` is now `SpiBus`;
and the internal API has an additional `switchToDevice()`,
and receives `DeviceConfig` in more places.

* SPI device: Add `transceive()` method.

---------

Co-authored-by: Matt Knight <mattnite@protonmail.com>
wch-ch32v003
Marnix Klooster 2 years ago committed by GitHub
parent 1c3e04baa1
commit e1c1466d9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -41,6 +41,9 @@ pub const Pin = pin.Pin;
pub const uart = @import("uart.zig"); pub const uart = @import("uart.zig");
pub const Uart = uart.Uart; pub const Uart = uart.Uart;
pub const spi = @import("spi.zig");
pub const SpiBus = spi.SpiBus;
pub const i2c = @import("i2c.zig"); pub const i2c = @import("i2c.zig");
pub const I2CController = i2c.I2CController; pub const I2CController = i2c.I2CController;

@ -0,0 +1,146 @@
const std = @import("std");
const micro = @import("microzig.zig");
const chip = @import("chip");
/// The SPI bus with the given environment-specific number.
/// Only 'master' mode is supported currently.
pub fn SpiBus(comptime index: usize) type {
const SystemSpi = chip.SpiBus(index);
return struct {
/// A SPI 'slave' device, selected via the given CS pin.
/// (Default is CS=low to select.)
pub fn SpiDevice(comptime cs_pin: type, config: DeviceConfig) type {
return struct {
const SelfSpiDevice = @This();
internal: SystemSpi,
/// A 'transfer' is defined as a sequence of reads/writes that require
/// the SPI device to be continuously enabled via its 'chip select' (CS) line.
const Transfer = struct {
const SelfTransfer = @This();
device: SelfSpiDevice,
fn transceiveByte(self: *SelfTransfer, write_byte: u8, read_pointer: *u8) !void {
try self.device.internal.transceiveByte(write_byte, read_pointer);
}
pub const Writer = std.io.Writer(*SelfTransfer, WriteError, writeSome);
/// Return a standard Writer (which ignores the bytes read).
pub fn writer(self: *SelfTransfer) Writer {
return Writer{ .context = self };
}
fn writeSome(self: *SelfTransfer, buffer: []const u8) WriteError!usize {
try self.device.internal.writeAll(buffer);
return buffer.len;
}
pub const Reader = std.io.Reader(*SelfTransfer, ReadError, readSome);
/// Return a standard Reader (which writes arbitrary bytes).
pub fn reader(self: *SelfTransfer) Reader {
return Reader{ .context = self };
}
fn readSome(self: *SelfTransfer, buffer: []u8) ReadError!usize {
try self.device.internal.readInto(buffer);
return buffer.len;
}
/// end the current transfer, releasing via the CS pin
pub fn end(self: *SelfTransfer) void {
self.device.internal.endTransfer(cs_pin, config);
}
};
/// start a new transfer, selecting using the CS pin
pub fn beginTransfer(self: SelfSpiDevice) !Transfer {
self.internal.switchToDevice(cs_pin, config);
self.internal.beginTransfer(cs_pin, config);
return Transfer{ .device = self };
}
pub fn transceive(self: SelfSpiDevice, write_buffer: []const u8, read_buffer: []u8) !void {
std.debug.assert(write_buffer.len == read_buffer.len);
var transfer = try self.beginTransfer();
defer transfer.end();
for (write_buffer) |_, i| {
try transfer.transceiveByte(write_buffer[i], &read_buffer[i]);
}
}
/// Shorthand for 'register-based' devices
pub fn writeRegister(self: SelfSpiDevice, register_address: u8, byte: u8) ReadError!void {
try self.writeRegisters(register_address, &.{byte});
}
/// Shorthand for 'register-based' devices
pub fn writeRegisters(self: SelfSpiDevice, register_address: u8, buffer: []u8) ReadError!void {
var transfer = try self.beginTransfer();
defer transfer.end();
// write auto-increment, starting at given register
try transfer.writer().writeByte(0b01_000000 | register_address);
try transfer.writer().writeAll(buffer);
}
/// Shorthand for 'register-based' devices
pub fn readRegister(self: SelfSpiDevice, 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: SelfSpiDevice, register_address: u8, buffer: []u8) ReadError!void {
var transfer = try self.beginTransfer();
defer transfer.end();
// read auto-increment, starting at given register
try transfer.writer().writeByte(0b11_000000 | register_address);
try transfer.reader().readNoEof(buffer);
}
};
}
const SelfSpiBus = @This();
internal: SystemSpi,
/// Initialize this SPI bus and return a handle to it.
pub fn init(config: BusConfig) InitError!SelfSpiBus {
micro.clock.ensure(); // TODO: Wat?
return SelfSpiBus{
.internal = try SystemSpi.init(config),
};
}
/// Create (a descriptor for) a device on this SPI bus.
pub fn device(self: SelfSpiBus, comptime cs_pin: type, config: DeviceConfig) SpiDevice(cs_pin, config) {
return SpiDevice(cs_pin, config){ .internal = self.internal };
}
};
}
/// A SPI bus configuration.
/// (There are no bus configuration options yet.)
pub const BusConfig = struct {
// Later: add common options
};
/// A SPI device configuration (excluding the CS pin).
/// (There are no device configuration options yet.)
pub const DeviceConfig = struct {
// TODO: add common options, like clock polarity and phase, and CS polarity
};
pub const InitError = error{
// TODO: add common options
};
pub const WriteError = error{};
pub const ReadError = error{
EndOfStream,
};

@ -281,7 +281,6 @@ pub fn I2CController(comptime index: usize, comptime pins: micro.i2c.Pins) type
regs.RCC.APB1ENR.modify(.{ .I2C1EN = 1 }); regs.RCC.APB1ENR.modify(.{ .I2C1EN = 1 });
regs.RCC.AHBENR.modify(.{ .IOPBEN = 1 }); regs.RCC.AHBENR.modify(.{ .IOPBEN = 1 });
debugPrint("I2C1 configuration step 1 complete\r\n", .{}); debugPrint("I2C1 configuration step 1 complete\r\n", .{});
// 2. Configure the I2C PINs for ALternate Functions // 2. Configure the I2C PINs for ALternate Functions
// a) Select Alternate Function in MODER Register // a) Select Alternate Function in MODER Register
regs.GPIOB.MODER.modify(.{ .MODER6 = 0b10, .MODER7 = 0b10 }); regs.GPIOB.MODER.modify(.{ .MODER6 = 0b10, .MODER7 = 0b10 });
@ -469,3 +468,131 @@ pub fn I2CController(comptime index: usize, comptime pins: micro.i2c.Pins) type
}; };
}; };
} }
/// An STM32F303 SPI bus
pub fn SpiBus(comptime index: usize) type {
if (!(index == 1)) @compileError("TODO: only SPI1 is currently supported");
return struct {
const Self = @This();
/// Initialize and enable the bus.
pub fn init(config: micro.spi.BusConfig) !Self {
_ = config; // unused for now
// CONFIGURE SPI1
// connected to APB2, MCU pins PA5 + PA7 + PA6 = SPC + SDI + SDO,
// if GPIO port A is configured for alternate function 5 for these PA pins.
// Enable the GPIO CLOCK
regs.RCC.AHBENR.modify(.{ .IOPAEN = 1 });
// Configure the I2C PINs for ALternate Functions
// - Select Alternate Function in MODER Register
regs.GPIOA.MODER.modify(.{ .MODER5 = 0b10, .MODER6 = 0b10, .MODER7 = 0b10 });
// - Select High SPEED for the PINs
regs.GPIOA.OSPEEDR.modify(.{ .OSPEEDR5 = 0b11, .OSPEEDR6 = 0b11, .OSPEEDR7 = 0b11 });
// - Configure the Alternate Function in AFR Register
regs.GPIOA.AFRL.modify(.{ .AFRL5 = 5, .AFRL6 = 5, .AFRL7 = 5 });
// Enable the SPI1 CLOCK
regs.RCC.APB2ENR.modify(.{ .SPI1EN = 1 });
regs.SPI1.CR1.modify(.{
.MSTR = 1,
.SSM = 1,
.SSI = 1,
.RXONLY = 0,
.SPE = 1,
});
// the following configuration is assumed in `transceiveByte()`
regs.SPI1.CR2.raw = 0;
regs.SPI1.CR2.modify(.{
.DS = 0b0111, // 8-bit data frames, seems default via '0b0000 is interpreted as 0b0111'
.FRXTH = 1, // RXNE event after 1 byte received
});
return Self{};
}
/// Switch this SPI bus to the given device.
pub fn switchToDevice(_: Self, comptime cs_pin: type, config: micro.spi.DeviceConfig) void {
_ = config; // for future use
regs.SPI1.CR1.modify(.{
.CPOL = 1, // TODO: make configurable
.CPHA = 1, // TODO: make configurable
.BR = 0b111, // 1/256 the of PCLK TODO: make configurable
.LSBFIRST = 0, // MSB first TODO: make configurable
});
gpio.setOutput(cs_pin);
}
/// Begin a transfer to the given device. (Assumes `switchToDevice()` was called.)
pub fn beginTransfer(_: Self, comptime cs_pin: type, config: micro.spi.DeviceConfig) void {
_ = config; // for future use
gpio.write(cs_pin, .low); // select the given device, TODO: support inverse CS devices
debugPrint("enabled SPI1\r\n", .{});
}
/// The basic operation in the current simplistic implementation:
/// send+receive a single byte.
/// Writing `null` writes an arbitrary byte (`undefined`), and
/// reading into `null` ignores the value received.
pub fn transceiveByte(_: Self, optional_write_byte: ?u8, optional_read_pointer: ?*u8) !void {
// SPIx_DR's least significant byte is `@bitCast([dr_byte_size]u8, ...)[0]`
const dr_byte_size = @sizeOf(@TypeOf(regs.SPI1.DR.raw));
// wait unril ready for write
while (regs.SPI1.SR.read().TXE == 0) {
debugPrint("SPI1 TXE == 0\r\n", .{});
}
debugPrint("SPI1 TXE == 1\r\n", .{});
// write
const write_byte = if (optional_write_byte) |b| b else undefined; // dummy value
@bitCast([dr_byte_size]u8, regs.SPI1.DR.*)[0] = write_byte;
debugPrint("Sent: {X:2}.\r\n", .{write_byte});
// wait until read processed
while (regs.SPI1.SR.read().RXNE == 0) {
debugPrint("SPI1 RXNE == 0\r\n", .{});
}
debugPrint("SPI1 RXNE == 1\r\n", .{});
// read
var data_read = regs.SPI1.DR.raw;
_ = regs.SPI1.SR.read(); // clear overrun flag
const dr_lsb = @bitCast([dr_byte_size]u8, data_read)[0];
debugPrint("Received: {X:2} (DR = {X:8}).\r\n", .{ dr_lsb, data_read });
if (optional_read_pointer) |read_pointer| read_pointer.* = dr_lsb;
}
/// Write all given bytes on the bus, not reading anything back.
pub fn writeAll(self: Self, bytes: []const u8) !void {
for (bytes) |b| {
try self.transceiveByte(b, null);
}
}
/// Read bytes to fill the given buffer exactly, writing arbitrary bytes (`undefined`).
pub fn readInto(self: Self, buffer: []u8) !void {
for (buffer) |_, i| {
try self.transceiveByte(null, &buffer[i]);
}
}
pub fn endTransfer(_: Self, comptime cs_pin: type, config: micro.spi.DeviceConfig) void {
_ = config; // for future use
// no delay should be needed here, since we know SPIx_SR's TXE is 1
debugPrint("(disabling SPI1)\r\n", .{});
gpio.write(cs_pin, .high); // deselect the given device, TODO: support inverse CS devices
// HACK: wait long enough to make any device end an ongoing transfer
var i: u8 = 255; // with the default clock, this seems to delay ~185 microseconds
while (i > 0) : (i -= 1) {
asm volatile ("nop");
}
}
};
}

Loading…
Cancel
Save