blob: 02b91e77b0a32a87ec32af3ca2a84a2d8d4258dd [file] [log] [blame]
// Copyright 2020 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_i2c_baremetal_stm32f429/i2c_baremetal.h"
#include "pw_preprocessor/compiler.h"
#include "pw_preprocessor/util.h"
namespace pw::i2c {
// Reset/clock configuration block (RCC).
// `reserved` fields are unimplemented features, and are present to ensure
// proper alignment of registers that are in use.
PW_PACKED(struct) RccBlock {
uint32_t reserved1[12];
uint32_t ahb1_config;
uint32_t reserved2[3];
uint32_t apb1_config;
uint32_t apb2_config;
};
// I2C register block definition.
// Only the lower 16 bits of each register are used
struct I2CBlock {
uint16_t control1; // I2C_CR1
uint16_t reserved1;
uint16_t control2; // I2C_CR2
uint16_t reserved2;
uint16_t own_address1; // I2C_OAR1
uint16_t reserved3;
uint16_t own_address2; // I2C_OAR2
uint16_t reserved4;
uint16_t data_register; // I2C_DR
uint16_t reserved5;
uint16_t status1; // I2C_SR1
uint16_t reserved6;
uint16_t status2; // I2C_SR2
uint16_t reserved7;
uint16_t clock_control; // I2C_CCR
uint16_t reserved8;
uint16_t TRISE; // I2C_TRISE
uint16_t reserved9;
uint16_t FLTR; // I2C_FLTR
uint16_t reserved10;
};
// GPIO register block definition.
PW_PACKED(struct) GpioBlock {
uint32_t modes;
uint32_t out_type;
uint32_t out_speed;
uint32_t pull_up_down;
uint32_t input_data;
uint32_t output_data;
uint32_t gpio_bit_set;
uint32_t port_config_lock;
uint32_t alt_low;
uint32_t alt_high;
};
volatile RccBlock& platform_rcc =
*reinterpret_cast<volatile RccBlock*>(0x40023800U);
volatile GpioBlock& gpio_a =
*reinterpret_cast<volatile GpioBlock*>(0x40020000U);
volatile GpioBlock& gpio_c =
*reinterpret_cast<volatile GpioBlock*>(0x40020800U);
volatile I2CBlock& i2c3 = *reinterpret_cast<volatile I2CBlock*>(0x40005C00U);
// Masks for ahb1_config (AHB1ENR) to enable the "A" and "C" GPIO pins.
constexpr uint32_t kGpioAEnable = 0x1u;
constexpr uint32_t kGpioCEnable = 0x4u;
// Constants related to GPIO output type open-drain
constexpr uint32_t kGpioOutputTypeOpenDrain = 1;
constexpr uint32_t kGpio9OutputTypePos = 9;
constexpr uint32_t kGpio8OutputTypePos = 8;
// Constants related to GPIO pull up/down resistor type masks
constexpr uint32_t kGpioPullTypeNone = 0;
constexpr uint32_t kGpio9PullTypePos = 18;
constexpr uint32_t kGpio8PullTypePos = 16;
// Constants related to GPIO mode register masks
constexpr uint32_t kGpioPortModeAlternate = 2;
constexpr uint32_t kGpio9PortModePos = 18;
constexpr uint32_t kGpio8PortModePos = 16;
// Constants related to GPIO alternate function mode
constexpr uint32_t kGpioAlternateFunctionI2C3 = 0x4u;
constexpr uint32_t kGpio9AltFuncHighPos = 4;
constexpr uint32_t kGpio8AltFuncHighPos = 0;
// Mask for apb2_config (APB1ENR) to enable I2C3.
constexpr uint32_t kI2C3Enable = 0x00800000u;
// Masks for i2c_cr1 to software reset
constexpr uint16_t kI2CSwReset = 0x8000u;
// Masks for i2c_cr2 frequency
constexpr uint16_t kI2CCr2Freq = 0x003fu;
// Mask for i2c_trise maximum rase time
constexpr uint8_t kI2CTRISE = 0x3fu;
//// Masks for I2C interrupts
// constexpr uint16_t kI2CErrIrq = 0x0100u; // error irq
// constexpr uint16_t kI2CEvtIrq = 0x0200u; // event irq
// constexpr uint16_t kI2CBufIrq = 0x0400u; // buffer irq
// Default core clock and CCR value. These are technically not constants,
// but since this app doesn't change the system clock constants will suffice.
constexpr uint32_t kSystemCoreClock = 16000000;
constexpr uint16_t kCCRStdMode = 0x32u;
// Masks for flags on CR1
constexpr uint16_t kCR1Pe = 0x0001u; // PE
constexpr uint16_t kCR1Start = 0x0100u; // START
constexpr uint16_t kCR1Stop = 0x0200u; // STOP
constexpr uint16_t kCR1Ack = 0x0400u; // ACK
// Masks for the flags on SR1
constexpr uint16_t kSR1Sb = 0x1u; // SB
constexpr uint16_t kSR1Addr = 0x2u; // ADDR
constexpr uint16_t kSR1Btf = 0x4u; // ADDR
constexpr uint16_t kSR1Txe = 0x80u; // TXE
constexpr uint16_t kSR1Rxne = 0x40u; // RXNE
struct SdaScl {
volatile GpioBlock& gpio;
uint32_t kGpioEnable;
uint32_t kGpioOutputTypePos;
uint32_t kGpioPullTypePos;
uint32_t kGpioPortModePos;
uint32_t kGpioAltFuncHighPos;
uint32_t kGpioAlternateFunction;
SdaScl(volatile GpioBlock& gpio,
uint32_t kGpioEnable,
uint32_t kGpioOutputTypePos,
uint32_t kGpioPullTypePos,
uint32_t kGpioPortModePos,
uint32_t kGpioAltFuncHighPos,
uint32_t kGpioAlternateFunction)
: gpio(gpio),
kGpioEnable(kGpioEnable),
kGpioOutputTypePos(kGpioOutputTypePos),
kGpioPullTypePos(kGpioPullTypePos),
kGpioPortModePos(kGpioPortModePos),
kGpioAltFuncHighPos(kGpioAltFuncHighPos),
kGpioAlternateFunction(kGpioAlternateFunction) {}
};
Stm32f429I2c::Stm32f429I2c(SdaScl& sda,
SdaScl& scl,
volatile I2CBlock& i2c_hw,
int32_t kI2cEnable)
: sda_(sda), scl_(scl), i2c_hw_(i2c_hw), kI2cEnable_(kI2cEnable) {}
pw::Status Stm32f429I2c::Enable() {
// Enable 'A' & 'C' GPIO clocks.
platform_rcc.ahb1_config |= sda_.kGpioEnable;
platform_rcc.ahb1_config |= scl_.kGpioEnable;
// Enable the SDA (PC9 for I2C3_SDA):
// open-drain, no pull-up/down ,alternate function mode, alternate function
// i2c
sda_.gpio.out_type |= kGpioOutputTypeOpenDrain << sda_.kGpioOutputTypePos;
sda_.gpio.pull_up_down |= kGpioPullTypeNone << sda_.kGpioPullTypePos;
sda_.gpio.modes |= kGpioPortModeAlternate << sda_.kGpioPortModePos;
sda_.gpio.alt_high |= sda_.kGpioAlternateFunction << sda_.kGpioAltFuncHighPos;
// Enable the SCL (PA8 for I2C3_SCL):
// open-drain, no pull-up/down ,alternate function mode, alternate function
// i2c
scl_.gpio.out_type |= kGpioOutputTypeOpenDrain << scl_.kGpioOutputTypePos;
scl_.gpio.pull_up_down |= kGpioPullTypeNone << scl_.kGpioPullTypePos;
scl_.gpio.modes |= kGpioPortModeAlternate << scl_.kGpioPortModePos;
scl_.gpio.alt_high |= scl_.kGpioAlternateFunction << scl_.kGpioAltFuncHighPos;
// Initialize I2C3. Reset and clear all peripherals
platform_rcc.apb1_config |= kI2C3Enable;
i2c_hw_.control1 = kI2CSwReset;
i2c_hw_.control1 = 0;
// Enable peripheral clock
i2c_hw_.control2 &= ~kI2CCr2Freq;
i2c_hw_.control2 |= kSystemCoreClock / 1000000;
// Initiate speed (standard mode up to 100kHz)
i2c_hw_.clock_control = kCCRStdMode;
// Initiate Max rise time
i2c_hw_.TRISE = (kSystemCoreClock / 1000000 + 1) & kI2CTRISE;
// Enable interrupts
// i2c3.control2 |= (kI2CErrIrq | kI2CEvtIrq | kI2CBufIrq);
// Enable the I2C peripheral
i2c_hw_.control1 = kCR1Pe;
return pw::Status::OK;
}
pw::Status Stm32f429I2c::Disable() {
// Disable the I2C peripheral
i2c3.control1 &= ~kCR1Pe;
return pw::Status::OK;
}
pw::Status Stm32f429I2c::WriteRead(I2cAddress address,
std::span<const std::byte> tx_buffer,
std::span<std::byte> rx_buffer) {
if (tx_buffer.size() == 0 && rx_buffer.size() == 0) {
return pw::Status::OK;
}
// Generate start condition
i2c_hw_.control1 |= kCR1Start;
// Wait until SB is set
while (!(i2c_hw_.status1 & kSR1Sb))
;
if (tx_buffer.size() > 0) {
// Send the slave address
i2c_hw_.data_register = address & 0xff;
// Wait until ADDR is set
while (!(i2c_hw_.status1 & kSR1Addr))
;
// Clear ADDR flag by reading SR1 and SR2
i2c_hw_.status1;
i2c_hw_.status2;
for (size_t i = 0; i < tx_buffer.size(); i++) {
// Wait until TXE is set
while (!(i2c_hw_.status1 & kSR1Txe))
;
// Send the data
i2c_hw_.data_register = (uint16_t)tx_buffer[i];
// Wait until BTF is set
while (!(i2c_hw_.status1 & kSR1Btf))
;
}
}
if (rx_buffer.size() > 0) {
// Generate restart
i2c_hw_.control1 |= kCR1Start;
// Wait until SB is set
while (!(i2c_hw_.status1 & kSR1Sb))
;
// Send the slave address, set the last bit to 1 since it's a READ
i2c_hw_.data_register = (address & 0xff) | 1;
// Wait until ADDR is set
while (!(i2c_hw_.status1 & kSR1Addr))
;
// Clear ADDR flag by reading SR1 and SR2
i2c_hw_.status1;
i2c_hw_.status2;
for (size_t i = 0; i < rx_buffer.size(); i++) {
// Wait until RXNE bit is set
while (!(i2c_hw_.status1 & kSR1Rxne))
;
rx_buffer[i] = (std::byte)(i2c_hw_.data_register & 0xff);
}
}
// Generate stop
i2c_hw_.control1 |= kCR1Stop;
return pw::Status::OK;
}
SdaScl pc9{gpio_c,
kGpioCEnable,
kGpio9OutputTypePos,
kGpio9PullTypePos,
kGpio9PortModePos,
kGpio9AltFuncHighPos,
kGpioAlternateFunctionI2C3};
SdaScl pa8{gpio_a,
kGpioAEnable,
kGpio8OutputTypePos,
kGpio8PullTypePos,
kGpio8PortModePos,
kGpio8AltFuncHighPos,
kGpioAlternateFunctionI2C3};
// The I2C3(use PC9 as SDA, PA8 as SCL) singleton
Stm32f429I2c stm32f429_i2c3{pc9, pa8, i2c3, kI2C3Enable};
} // namespace pw::i2c