From 97828812920eca36d0f362d92cc46bebc6107df8 Mon Sep 17 00:00:00 2001 From: CritBit Date: Sun, 26 May 2024 16:47:32 +0200 Subject: [PATCH] working PoC I2C slave example --- Makefile | 4 +- User/do_not_merge_yet.md | 84 ++++++++++++++++++++ User/main.c | 164 +++++++++++++++++++++++++++++++++------ User/py32f0xx_hal_conf.h | 2 +- User/py32f0xx_hal_msp.c | 25 ++++++ User/py32f0xx_it.c | 8 +- 6 files changed, 261 insertions(+), 26 deletions(-) create mode 100644 User/do_not_merge_yet.md diff --git a/Makefile b/Makefile index 144bdd9..14fbfbe 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ BUILD_DIR = Build # PY32F003x4, PY32F003x6, PY32F003x8, # PY32F030x6, PY32F030x8, # PY32F072xB -MCU_TYPE = PY32F003x4 +MCU_TYPE = PY32F002Ax5 ##### Options ##### @@ -31,7 +31,7 @@ FLASH_PROGRM ?= pyocd #ARM_TOOCHAIN ?= /opt/gcc-arm/gcc-arm-11.2-2022.02-x86_64-arm-none-eabi/bin #ARM_TOOCHAIN ?= /opt/gcc-arm/arm-gnu-toolchain-11.3.rel1-x86_64-arm-none-eabi/bin -ARM_TOOCHAIN ?= /opt/gcc-arm/arm-gnu-toolchain-13.2.Rel1-x86_64-arm-none-eabi/bin +ARM_TOOCHAIN ?= /usr/bin # path to JLinkExe JLINKEXE ?= /opt/SEGGER/JLink/JLinkExe diff --git a/User/do_not_merge_yet.md b/User/do_not_merge_yet.md new file mode 100644 index 0000000..9cbc443 --- /dev/null +++ b/User/do_not_merge_yet.md @@ -0,0 +1,84 @@ +This change is not meant to be merged in at this point. See comments on top of `User/main.c` + + +Pasting testing master I2C code for ESP32 + + +``` C +// ESP32 I2C Scanner +// Based on code of Nick Gammon http://www.gammon.com.au/forum/?id=10896 +// ESP32 DevKit - Arduino IDE 1.8.5 +// Device tested PCF8574 - Use pullup resistors 3K3 ohms ! +// PCF8574 Default Freq 100 KHz + +#include +#include +#include + +Adafruit_PCF8574 pcf8574; + +void Scanner () +{ + Serial.println (); + Serial.println ("I2C scanner. Scanning ..."); + uint8_t count = 0; + + Wire.begin(); + for (uint8_t i = 8; i < 120; i++) + { + //save start time + unsigned long startTime = micros(); + Wire.beginTransmission (i); // Begin I2C transmission Address (i) + if (Wire.endTransmission () == 0) // Receive 0 = success (ACK response) + { + Serial.print ("Found address: "); + Serial.print ("0x"); + Serial.print (i, HEX); // PCF8574 7 bit address + Serial.print (" time: "); + Serial.println (micros() - startTime); + count++; + } + } + Serial.print ("Found "); + Serial.print (count, DEC); // numbers of devices + Serial.println (" device(s)."); +} + +void setup() +{ + Serial.begin (115200); + Wire.begin (26, 27); // sda= GPIO_26 /scl= GPIO_27 + pcf8574.begin(0x50, &Wire); + Scanner (); + pinMode(15, INPUT); +} + +uint8_t loopCount = 0; +void loop() +{ + unsigned long startTime = micros(); + Serial.print("Loop: "); + Serial.print(loopCount); + + if (digitalRead(15) == HIGH) + { + pcf8574.digitalWriteByte(loopCount); + Serial.print(" Write: "); + Serial.print(loopCount); + } + else + { + uint8_t ret = pcf8574.digitalReadByte(); + Serial.print(" Read: "); + Serial.print(ret); + } + + Serial.print(" Time: "); + Serial.println (micros() - startTime); + delay (1000); + loopCount++; +} + + + +``` \ No newline at end of file diff --git a/User/main.c b/User/main.c index 8f7d867..84d3120 100644 --- a/User/main.c +++ b/User/main.c @@ -1,44 +1,164 @@ /*** - * Demo: LED Toggle + * Demo: PoC I2C slave * - * PA0 ------> LED+ - * GND ------> LED- + * This firmware serves as an Proof-of-concept PCF8574 emulation. + * Looking at jlc parts inventory, the cheapest available I2C io expander is atleast + * 2-3x more expensive (CH423S, 28c@25u), compared to cheapest PY32F002 (10c@50u) + * + * Remember, PY32 is an powerful machine, it can also serve as and I2C ADC, charlieplexing + * controller, or could even serve as I2C-controlled programmable DC-DC converter. + * + * The code is very messy for now, I2C slave operation is apparently super-hard, and not + * well documented. You can have a look, how much developers are struggling with this, by + * googling "AddrMatchCode" (which I still have no idea what is it for). + * + * The biggest breakthrtough came from a series of articles below. They're + * targeting STM32, but Puya apparently stole all of ST's HAL code, so it seems compatible. + * https://controllerstech.com/stm32-as-i2c-slave-part-1/ + * + * In summary - the purpose of this code is to handle both transmit and receive operations + * without completely freezing the I2C rail. Everything else is a bonus feature from my + * perspective :D + * + * I'm going to continue working on this, I have a lot of plans for this little smart IC. + PF1 ------> I2C1_SCL + PF0 ------> I2C1_SDA + PA2 ------> USART1_TX (logging) */ + +#include "py32f0xx_hal_dma.h" +#include "py32f0xx_hal_i2c.h" #include "py32f0xx_bsp_printf.h" +// this print will slow the I2C read operation by a lot, but ESP32 should +// handle this well. +#define DEBUG_ISR_PRINT(...) printf(__VA_ARGS__); fflush(stdout); +// #define DEBUG_ISR_PRINT(...) -static void APP_GPIO_Config(void); +I2C_HandleTypeDef I2cHandle; +void APP_ErrorHandler(void); +static void APP_I2C_Config(void); -int main(void) +#define I2C_ADDRESS 0xA0 //0x50 +uint8_t aRxBuffer[2] = {0, 0}; + +uint8_t main_opCpltFlag = 0; +uint8_t main_receivedFlag = 0; + +void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode) { - HAL_Init(); - APP_GPIO_Config(); - BSP_USART_Config(); - printf("PY32F0xx LED Toggle Demo\r\nSystem Clock: %ld\r\n", SystemCoreClock); + UNUSED(AddrMatchCode);// I have no idea what this does, maybe it works with some other I2C setup - while (1) + if(TransferDirection == I2C_DIRECTION_TRANSMIT){ + HAL_I2C_Slave_Seq_Receive_IT(hi2c, &aRxBuffer[0], 2, I2C_FIRST_AND_LAST_FRAME); + main_receivedFlag = 1; + } else { + HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &aRxBuffer[0], 2, I2C_FIRST_AND_LAST_FRAME); + } + + DEBUG_ISR_PRINT("\nmaster %s \n\r", + ( TransferDirection == I2C_DIRECTION_TRANSMIT )? "write" : "read"); +} + +void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c) +{ + uint32_t errorcode = HAL_I2C_GetError(hi2c); + + if (errorcode == HAL_I2C_ERROR_AF) + { + // triggered when master tries to read/write more bytes than expected. + // for now this must be triggered for each transaction, as otherwise + // the I2C periph hangs. I'll deal with this at some point. + DEBUG_ISR_PRINT("Error callback: AF\n\r"); + } else if (errorcode == HAL_I2C_ERROR_BERR) { - HAL_Delay(1000); - HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0); - printf("echo\r\n"); + DEBUG_ISR_PRINT("Error callback: BERR\n\r"); } + + HAL_I2C_EnableListen_IT(hi2c); +} + +void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) +{ + // This callback is actually never called + // leaving some spam if someone wants to experiment + // memset( aRxBuffer, 0, RXBUFFERSIZE); + // Rxcounter++; + // HAL_I2C_Slave_Seq_Receive_IT(hi2c, &aRxBuffer[0], 1, I2C_LAST_FRAME); + // HAL_I2C_EnableListen_IT(hi2c); + DEBUG_ISR_PRINT("rx complete %d\n\r", aRxBuffer[0]); } -static void APP_GPIO_Config(void) +void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c) { - GPIO_InitTypeDef GPIO_InitStruct; + // TODO: for some reason, I have to try sending another byte, or the whole + // i2c periph hangs. This call will result int HAL_I2C_ERROR_AF triggered + // in HAL_I2C_ErrorCallback, which is kinda fine. - __HAL_RCC_GPIOA_CLK_ENABLE(); - // PA0 - GPIO_InitStruct.Pin = GPIO_PIN_0; - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; - GPIO_InitStruct.Pull = GPIO_PULLUP; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; - HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + // there are 3 variants: + // 1. try send another byte - works and results in HAL_I2C_ERROR_AF + HAL_I2C_Slave_Seq_Transmit_IT(hi2c, &aRxBuffer[0], 1, I2C_LAST_FRAME); + + // 2. enable listen - hangs + // HAL_I2C_EnableListen_IT(hi2c); // maybe some day + + // 3. do nothing - hangs + + DEBUG_ISR_PRINT("tx complete\n\r"); +} + +void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c) +{ + DEBUG_ISR_PRINT("listen callback\n\r"); + main_opCpltFlag = 1; + HAL_I2C_EnableListen_IT(hi2c); +} + +int main(void) +{ + HAL_Init(); + + BSP_USART_Config(); + printf("SystemClk is:%ld\r\n", SystemCoreClock); + + APP_I2C_Config(); + + HAL_I2C_EnableListen_IT(&I2cHandle); + printf("listen enabled\n\r"); + while(1){ + if(main_opCpltFlag){ + if (main_receivedFlag) + { + printf("got data %02x\n\r", aRxBuffer[0]); + fflush(stdout); + main_receivedFlag = 0; + } else { + printf("transmit complete\n\r"); + fflush(stdout); + } + main_opCpltFlag = 0; + } + } +} + +static void APP_I2C_Config(void) +{ + I2cHandle.Instance = I2C; + I2cHandle.Init.ClockSpeed = 400000; // 100KHz ~ 400KHz + I2cHandle.Init.DutyCycle = I2C_DUTYCYCLE_16_9; + I2cHandle.Init.OwnAddress1 = I2C_ADDRESS; + I2cHandle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; + I2cHandle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; + if (HAL_I2C_Init(&I2cHandle) != HAL_OK) + { + APP_ErrorHandler(); + } } void APP_ErrorHandler(void) { + printf("errord\n"); + fflush(stdout); while (1); } diff --git a/User/py32f0xx_hal_conf.h b/User/py32f0xx_hal_conf.h index b900c04..bc2e46d 100644 --- a/User/py32f0xx_hal_conf.h +++ b/User/py32f0xx_hal_conf.h @@ -48,7 +48,7 @@ #define HAL_DMA_MODULE_ENABLED /* #define HAL_LPTIM_MODULE_ENABLED */ #define HAL_PWR_MODULE_ENABLED -/* #define HAL_I2C_MODULE_ENABLED */ +#define HAL_I2C_MODULE_ENABLED #define HAL_UART_MODULE_ENABLED /* #define HAL_SPI_MODULE_ENABLED */ /* #define HAL_RTC_MODULE_ENABLED */ diff --git a/User/py32f0xx_hal_msp.c b/User/py32f0xx_hal_msp.c index ec5bf34..f483cbd 100644 --- a/User/py32f0xx_hal_msp.c +++ b/User/py32f0xx_hal_msp.c @@ -41,4 +41,29 @@ void HAL_MspInit(void) { } +void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c) +{ + GPIO_InitTypeDef GPIO_InitStruct; + + __HAL_RCC_I2C_CLK_ENABLE(); + __HAL_RCC_GPIOF_CLK_ENABLE(); + + /** + PF1 ------> I2C1_SCL + PF0 ------> I2C1_SDA + */ + GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_0; + GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; + GPIO_InitStruct.Pull = GPIO_PULLUP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF12_I2C; + HAL_GPIO_Init(GPIOF, &GPIO_InitStruct); + + __HAL_RCC_I2C_FORCE_RESET(); + __HAL_RCC_I2C_RELEASE_RESET(); + + HAL_NVIC_SetPriority(I2C1_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(I2C1_IRQn); +} + /************************ (C) COPYRIGHT Puya *****END OF FILE******************/ diff --git a/User/py32f0xx_it.c b/User/py32f0xx_it.c index 0eac39d..ebdbe2e 100644 --- a/User/py32f0xx_it.c +++ b/User/py32f0xx_it.c @@ -32,7 +32,7 @@ /* Private function prototypes -----------------------------------------------*/ /* Private user code ---------------------------------------------------------*/ /* External variables --------------------------------------------------------*/ - +extern I2C_HandleTypeDef I2cHandle; /******************************************************************************/ /* Cortex-M0+ Processor Interruption and Exception Handlers */ /******************************************************************************/ @@ -75,6 +75,12 @@ void SysTick_Handler(void) HAL_IncTick(); } +void I2C1_IRQHandler(void) +{ + HAL_I2C_EV_IRQHandler(&I2cHandle); + HAL_I2C_ER_IRQHandler(&I2cHandle); +} + /******************************************************************************/ /* PY32F0xx Peripheral Interrupt Handlers */ /* Add here the Interrupt Handlers for the used peripherals. */