diff --git a/examples/i2c_slave/README.md b/examples/i2c_slave/README.md index cfdbd1b46fb2f58e9bea2c7b4ce8d2aa0e4ee367..55fc9b0c0b49f4fcd6cf530378923392da48fd2b 100644 --- a/examples/i2c_slave/README.md +++ b/examples/i2c_slave/README.md @@ -6,4 +6,50 @@ The library uses a one-byte address, allowing for up to 256 registers to be defi The first byte written to the device within a transaction determines the offset for following reads and writes, emulating a simple EEPROM. -The example will turn on a LED connected to PD0 when the LSB of register 0 is set to 1 and off when it's set to 0. +The example will turn on a LED connected to PA2 when the LSB of register 0 is set to 1 and off when it's set to 0. + +## Usage + +Initialize the I2C1 SDA and SCL pins you want to use as open-drain outputs: +``` +funPinMode(PC1, GPIO_CFGLR_OUT_10Mhz_AF_OD); // SDA +funPinMode(PC2, GPIO_CFGLR_OUT_10Mhz_AF_OD); // SCL +``` + +For chips other than the CH32V003 you will need to change the pin numbers to the pins corresponding with the I2C1 peripheral. If you want to use the alternative pins for the I2C periperal in addition to configuring the pins you have to configure the chip to use the the alternative pins using the `I2C1_RM` and `I2C1REMAP1` fields of the `AFIO_PCFR1` register. + +Then initialize the I2C1 peripheral in slave mode using: + +``` +SetupI2CSlave(0x09, i2c_registers, sizeof(i2c_registers), onWrite, onRead, false); +``` + +In which `0x09` is the I2C address to listen on and i2c_registers is a pointer to a volitile uint8_t array. + +The `onWrite` and `onRead` functions are optional callbacks used to react to the registers being written to or read from. + +``` +void onWrite(uint8_t reg, uint8_t length) {} +void onRead(uint8_t reg) {} +``` + +The last boolean argument is for making the registers read only via I2C. + +You can also enable and disable writing using the functions + +``` +void SetI2CSlaveReadOnly(bool read_only); +void SetSecondaryI2CSlaveReadOnly(bool read_only); +``` + +The I2C1 peripheral can also listen on a secondary address. To enable this feature call the following function: + +``` +SetupSecondaryI2CSlave(0x42, i2c_registers2, sizeof(i2c_registers2), onWrite2, onRead2, false); +``` + +The function arguments are identical to the normal `SetupI2CSlave` function. The secondary I2C address acts like a completely separate I2C device with it's own registers. + +Calling `SetupSecondaryI2CSlave` with the I2C address set to 0 disables listening on the secondary address. + +It is recommended to react to register writes using the `onWrite` callback and not by reading the registers array from main(). There is a chance the compiler will optimize away your code if you do that. diff --git a/examples/i2c_slave/i2c_slave.c b/examples/i2c_slave/i2c_slave.c index 3f793628685160a60112e71f7eb0587872bd598a..84d81cf6d668df96692cafb6def0d9e3c0e7b68e 100644 --- a/examples/i2c_slave/i2c_slave.c +++ b/examples/i2c_slave/i2c_slave.c @@ -8,21 +8,21 @@ volatile uint8_t i2c_registers[32] = {0x00}; +void onWrite(uint8_t reg, uint8_t length) { + funDigitalWrite(PA2, i2c_registers[0] & 1); +} + int main() { SystemInit(); + funGpioInitAll(); - SetupI2CSlave(0x9, i2c_registers, sizeof(i2c_registers)); + // Initialize I2C slave + funPinMode(PC1, GPIO_CFGLR_OUT_10Mhz_AF_OD); // SDA + funPinMode(PC2, GPIO_CFGLR_OUT_10Mhz_AF_OD); // SCL + SetupI2CSlave(0x9, i2c_registers, sizeof(i2c_registers), onWrite, NULL, false); - // Enable GPIOD and set pin 0 to output - RCC->APB2PCENR |= RCC_APB2Periph_GPIOD; - GPIOD->CFGLR &= ~(0xf<<(4*0)); - GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*0); + // Initialize LED + funPinMode(PA2, GPIO_CFGLR_OUT_10Mhz_PP); // LED - while (1) { - if (i2c_registers[0] & 1) { // Turn on LED (PD0) if bit 1 of register 0 is set - GPIOD-> BSHR |= 1 << 16; - } else { - GPIOD-> BSHR |= 1; - } - } + while (1) {} // Do not let main exit, you can do other things here } diff --git a/examples/i2c_slave/i2c_slave.h b/examples/i2c_slave/i2c_slave.h index 9b5f3f93a713e45c6bc8f4bd80be51dbd31d1ca3..b229808eb22f71ea5b9e534c6e25bbbc8203d4d5 100644 --- a/examples/i2c_slave/i2c_slave.h +++ b/examples/i2c_slave/i2c_slave.h @@ -3,7 +3,7 @@ * * MIT License * - * Copyright (c) 2023 Renze Nicolai + * Copyright (c) 2024 Renze Nicolai * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,37 +27,50 @@ #ifndef __I2C_SLAVE_H #define __I2C_SLAVE_H +#include "ch32v003fun.h" #include <stdint.h> +#include <stdio.h> +#include <stdbool.h> -#define APB_CLOCK FUNCONF_SYSTEM_CORE_CLOCK +typedef void (*i2c_write_callback_t)(uint8_t reg, uint8_t length); +typedef void (*i2c_read_callback_t)(uint8_t reg); struct _i2c_slave_state { uint8_t first_write; uint8_t offset; uint8_t position; - volatile uint8_t* volatile registers; - uint8_t size; + volatile uint8_t* volatile registers1; + uint8_t size1; + volatile uint8_t* volatile registers2; + uint8_t size2; + i2c_write_callback_t write_callback1; + i2c_read_callback_t read_callback1; + bool read_only1; + i2c_write_callback_t write_callback2; + i2c_read_callback_t read_callback2; + bool read_only2; + bool writing; + bool address2matched; } i2c_slave_state; -void SetupI2CSlave(uint8_t address, volatile uint8_t* registers, uint8_t size) { +void SetupI2CSlave(uint8_t address, volatile uint8_t* registers, uint8_t size, i2c_write_callback_t write_callback, i2c_read_callback_t read_callback, bool read_only) { i2c_slave_state.first_write = 1; i2c_slave_state.offset = 0; i2c_slave_state.position = 0; - i2c_slave_state.registers = registers; - i2c_slave_state.size = size; - - // Enable GPIOC and I2C - RCC->APB2PCENR |= RCC_APB2Periph_GPIOC; + i2c_slave_state.registers1 = registers; + i2c_slave_state.size1 = size; + i2c_slave_state.registers2 = NULL; + i2c_slave_state.size2 = 0; + i2c_slave_state.write_callback1 = write_callback; + i2c_slave_state.read_callback1 = read_callback; + i2c_slave_state.read_only1 = read_only; + i2c_slave_state.write_callback2 = NULL; + i2c_slave_state.read_callback2 = NULL; + i2c_slave_state.read_only2 = false; + + // Enable I2C1 RCC->APB1PCENR |= RCC_APB1Periph_I2C1; - // PC1 is SDA, 10MHz Output, alt func, open-drain - GPIOC->CFGLR &= ~(0xf<<(4*1)); - GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*1); - - // PC2 is SCL, 10MHz Output, alt func, open-drain - GPIOC->CFGLR &= ~(0xf<<(4*2)); - GPIOC->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_OD_AF)<<(4*2); - // Reset I2C1 to init all regs RCC->APB1PRSTR |= RCC_APB1Periph_I2C1; RCC->APB1PRSTR &= ~RCC_APB1Periph_I2C1; @@ -67,44 +80,64 @@ void SetupI2CSlave(uint8_t address, volatile uint8_t* registers, uint8_t size) { // Set module clock frequency uint32_t prerate = 2000000; // I2C Logic clock rate, must be higher than the bus clock rate - I2C1->CTLR2 |= (APB_CLOCK/prerate) & I2C_CTLR2_FREQ; + I2C1->CTLR2 |= (FUNCONF_SYSTEM_CORE_CLOCK/prerate) & I2C_CTLR2_FREQ; // Enable interrupts - I2C1->CTLR2 |= I2C_CTLR2_ITBUFEN; - I2C1->CTLR2 |= I2C_CTLR2_ITEVTEN; // Event interrupt - I2C1->CTLR2 |= I2C_CTLR2_ITERREN; // Error interrupt + I2C1->CTLR2 |= I2C_CTLR2_ITBUFEN | I2C_CTLR2_ITEVTEN | I2C_CTLR2_ITERREN; NVIC_EnableIRQ(I2C1_EV_IRQn); // Event interrupt NVIC_SetPriority(I2C1_EV_IRQn, 2 << 4); NVIC_EnableIRQ(I2C1_ER_IRQn); // Error interrupt + NVIC_SetPriority(I2C1_ER_IRQn, 2 << 4); // Set clock configuration uint32_t clockrate = 1000000; // I2C Bus clock rate, must be lower than the logic clock rate - I2C1->CKCFGR = ((APB_CLOCK/(3*clockrate))&I2C_CKCFGR_CCR) | I2C_CKCFGR_FS; // Fast mode 33% duty cycle - //I2C1->CKCFGR = ((APB_CLOCK/(25*clockrate))&I2C_CKCFGR_CCR) | I2C_CKCFGR_DUTY | I2C_CKCFGR_FS; // Fast mode 36% duty cycle - //I2C1->CKCFGR = (APB_CLOCK/(2*clockrate))&I2C_CKCFGR_CCR; // Standard mode good to 100kHz + I2C1->CKCFGR = ((FUNCONF_SYSTEM_CORE_CLOCK/(3*clockrate))&I2C_CKCFGR_CCR) | I2C_CKCFGR_FS; // Fast mode 33% duty cycle + //I2C1->CKCFGR = ((FUNCONF_SYSTEM_CORE_CLOCK/(25*clockrate))&I2C_CKCFGR_CCR) | I2C_CKCFGR_DUTY | I2C_CKCFGR_FS; // Fast mode 36% duty cycle + //I2C1->CKCFGR = (FUNCONF_SYSTEM_CORE_CLOCK/(2*clockrate))&I2C_CKCFGR_CCR; // Standard mode good to 100kHz // Set I2C address I2C1->OADDR1 = address << 1; + I2C1->OADDR2 = 0; // Enable I2C I2C1->CTLR1 |= I2C_CTLR1_PE; - // Acknowledge the first address match event when it happens + // Acknowledge bytes when they are received I2C1->CTLR1 |= I2C_CTLR1_ACK; } +void SetupSecondaryI2CSlave(uint8_t address, volatile uint8_t* registers, uint8_t size, i2c_write_callback_t write_callback, i2c_read_callback_t read_callback, bool read_only) { + if (address > 0) { + I2C1->OADDR2 = (address << 1) | 1; + i2c_slave_state.registers2 = registers; + i2c_slave_state.size2 = size; + i2c_slave_state.write_callback2 = write_callback; + i2c_slave_state.read_callback2 = read_callback; + i2c_slave_state.read_only2 = read_only; + } else { + I2C1->OADDR2 = 0; + } +} + +void SetI2CSlaveReadOnly(bool read_only) { + i2c_slave_state.read_only1 = read_only; +} + +void SetSecondaryI2CSlaveReadOnly(bool read_only) { + i2c_slave_state.read_only2 = read_only; +} + void I2C1_EV_IRQHandler(void) __attribute__((interrupt)); void I2C1_EV_IRQHandler(void) { uint16_t STAR1, STAR2 __attribute__((unused)); STAR1 = I2C1->STAR1; STAR2 = I2C1->STAR2; - I2C1->CTLR1 |= I2C_CTLR1_ACK; - if (STAR1 & I2C_STAR1_ADDR) { // Start event i2c_slave_state.first_write = 1; // Next write will be the offset i2c_slave_state.position = i2c_slave_state.offset; // Reset position + i2c_slave_state.address2matched = !!(STAR2 & I2C_STAR2_DUALF); } if (STAR1 & I2C_STAR1_RXNE) { // Write event @@ -112,20 +145,58 @@ void I2C1_EV_IRQHandler(void) { i2c_slave_state.offset = I2C1->DATAR; i2c_slave_state.position = i2c_slave_state.offset; i2c_slave_state.first_write = 0; + i2c_slave_state.writing = false; } else { // Normal register write - if (i2c_slave_state.position < i2c_slave_state.size) { - i2c_slave_state.registers[i2c_slave_state.position] = I2C1->DATAR; - i2c_slave_state.position++; + i2c_slave_state.writing = true; + if (i2c_slave_state.address2matched) { + if (i2c_slave_state.position < i2c_slave_state.size2 && !i2c_slave_state.read_only2) { + i2c_slave_state.registers2[i2c_slave_state.position] = I2C1->DATAR; + i2c_slave_state.position++; + } + } else { + if (i2c_slave_state.position < i2c_slave_state.size1 && !i2c_slave_state.read_only1) { + i2c_slave_state.registers1[i2c_slave_state.position] = I2C1->DATAR; + i2c_slave_state.position++; + } } } } if (STAR1 & I2C_STAR1_TXE) { // Read event - if (i2c_slave_state.position < i2c_slave_state.size) { - I2C1->DATAR = i2c_slave_state.registers[i2c_slave_state.position]; - i2c_slave_state.position++; + i2c_slave_state.writing = false; + if (i2c_slave_state.address2matched) { + if (i2c_slave_state.position < i2c_slave_state.size2) { + I2C1->DATAR = i2c_slave_state.registers2[i2c_slave_state.position]; + if (i2c_slave_state.read_callback2 != NULL) { + i2c_slave_state.read_callback2(i2c_slave_state.position); + } + i2c_slave_state.position++; + } else { + I2C1->DATAR = 0x00; + } } else { - I2C1->DATAR = 0x00; + if (i2c_slave_state.position < i2c_slave_state.size1) { + I2C1->DATAR = i2c_slave_state.registers1[i2c_slave_state.position]; + if (i2c_slave_state.read_callback1 != NULL) { + i2c_slave_state.read_callback1(i2c_slave_state.position); + } + i2c_slave_state.position++; + } else { + I2C1->DATAR = 0x00; + } + } + } + + if (STAR1 & I2C_STAR1_STOPF) { // Stop event + I2C1->CTLR1 &= ~(I2C_CTLR1_STOP); // Clear stop + if (i2c_slave_state.address2matched) { + if (i2c_slave_state.write_callback2 != NULL) { + i2c_slave_state.write_callback2(i2c_slave_state.offset, i2c_slave_state.position - i2c_slave_state.offset); + } + } else { + if (i2c_slave_state.write_callback1 != NULL) { + i2c_slave_state.write_callback1(i2c_slave_state.offset, i2c_slave_state.position - i2c_slave_state.offset); + } } } }