From 165780bef9bbc45d4c6b700ec8a557def6ac12ed Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Tue, 17 Mar 2026 21:17:45 +0300 Subject: [PATCH] I2C works --- F0:F030,F042,F072/usbcan_gpio/flash.h | 1 + F0:F030,F042,F072/usbcan_gpio/gpio.c | 108 ++++++++- F0:F030,F042,F072/usbcan_gpio/gpio.h | 15 ++ F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp | 191 +++++++++++++++- F0:F030,F042,F072/usbcan_gpio/hardware.c | 2 +- F0:F030,F042,F072/usbcan_gpio/i2c.c | 214 ++++++++++++++++++ F0:F030,F042,F072/usbcan_gpio/i2c.h | 47 ++++ F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin | Bin 25700 -> 46484 bytes .../usbcan_gpio/usbcangpio.creator.user | 2 +- .../usbcan_gpio/usbcangpio.files | 2 + F0:F030,F042,F072/usbcan_gpio/version.inc | 4 +- 11 files changed, 558 insertions(+), 28 deletions(-) create mode 100644 F0:F030,F042,F072/usbcan_gpio/i2c.c create mode 100644 F0:F030,F042,F072/usbcan_gpio/i2c.h diff --git a/F0:F030,F042,F072/usbcan_gpio/flash.h b/F0:F030,F042,F072/usbcan_gpio/flash.h index 439b1e1..3672050 100644 --- a/F0:F030,F042,F072/usbcan_gpio/flash.h +++ b/F0:F030,F042,F072/usbcan_gpio/flash.h @@ -49,6 +49,7 @@ typedef struct __attribute__((packed, aligned(4))){ // gpio settings pinconfig_t pinconfig[2][16]; // GPIOA, GPIOB usartconf_t usartconfig; + uint8_t I2Cspeed; //spiconfig_t spiconfig; } user_conf; diff --git a/F0:F030,F042,F072/usbcan_gpio/gpio.c b/F0:F030,F042,F072/usbcan_gpio/gpio.c index 0dd658f..da2e031 100644 --- a/F0:F030,F042,F072/usbcan_gpio/gpio.c +++ b/F0:F030,F042,F072/usbcan_gpio/gpio.c @@ -22,6 +22,7 @@ #include "adc.h" #include "flash.h" #include "gpio.h" +#include "i2c.h" #include "pwm.h" #include "usart.h" @@ -39,12 +40,6 @@ typedef struct{ uint8_t AF[FUNC_AMOUNT]; // alternate function number for corresponding `FuncNames` number } pinprops_t; -#define CANADC(x) ((x) & (1< 1 || pin > 15 || !CANUSART(pin_props[port][pin].funcs)) return -1; int idx = -1; usart_props_t curprops = {0}; if(port == 0){ // GPIOA @@ -133,6 +139,37 @@ static int get_usart_index(uint8_t port, uint8_t pin, usart_props_t *p){ return idx; } +// return -1 if pin can't I2C, or return 0 and fill `p` +int get_i2c_index(uint8_t port, uint8_t pin, i2c_props_t *p){ + if(port > 1 || pin > 15 || !CANI2C(pin_props[port][pin].funcs)) return -1; + int idx = -1; // I2C1 is alone + i2c_props_t curprops = {0}; + if(port == 1){ // only GPIOB + switch(pin){ + case 6: // PB6 - I2C1_SCL + idx = 0; + curprops.isscl = 1; + break; + case 7: // PB7 - I2C1_SDA + idx = 0; + curprops.issda = 1; + break; + case 10: // PB10 - I2C1_SCL + idx = 0; + curprops.isscl = 1; + break; + case 11: // PB11 - I2C1_SDA + idx = 0; + curprops.issda = 1; + break; + default: + break; + } + } + if(p) *p = curprops; + return idx; +} + // default config static void defconfig(pinconfig_t *cfg){ if(!cfg) return; @@ -158,6 +195,8 @@ int chkpinconf(){ UC.RXen = 0; UC.TXen = 0; UC.monitor = 0; } int active_usart = -1; // number of USART if user selects it (we can't check it by UC->idx) + int active_i2c = -1; + uint8_t i2c_scl_pin = 0xFF, i2c_sda_pin = 0xFF; // to check SCL/SDA collisions and (SCL&SDA) for(int port = 0; port < 2; ++port){ for(int pin = 0; pin < 16; ++pin){ pinconfig_t *cfg = &pinconfig[port][pin]; @@ -210,6 +249,41 @@ int chkpinconf(){ break; } } + break; + case FUNC_I2C:{ + i2c_props_t ip; + int i2c_idx = get_i2c_index(port, pin, &ip); + if(i2c_idx < 0){ + defconfig(cfg); + ret = FALSE; + break; + } + // maybe for 2 I2Cs + if(active_i2c == -1) active_i2c = i2c_idx; + else if(active_i2c != i2c_idx){ + // collision + defconfig(cfg); + ret = FALSE; + break; + } + if(ip.isscl){ + if(i2c_scl_pin != 0xFF){ // two SCLs + defconfig(cfg); + ret = FALSE; + break; + } + i2c_scl_pin = (port << 4) | pin; + } + if(ip.issda){ + if(i2c_sda_pin != 0xFF){ // two SDAs + defconfig(cfg); + ret = FALSE; + break; + } + i2c_sda_pin = (port << 4) | pin; + } + } + break; default: break; // later fill other functions } } @@ -226,11 +300,19 @@ int chkpinconf(){ get_defusartconf(&UC); // clear global configuration the_conf.usartconfig = UC; } + // check active I2C + if(active_i2c != -1){ + if(i2c_scl_pin == 0xFF || i2c_sda_pin == 0xFF){ + DBG("Need two pins for I2C\n"); + ret = FALSE; + haveI2C = 0; + }else haveI2C = 1; + }else i2c_stop(); return ret; } int is_disabled(uint8_t port, uint8_t pin){ - if(port > 1 || pin > 15) return FALSE; + if(port > 1 || pin > 15) return TRUE; if(the_conf.pinconfig[port][pin].enable) return FALSE; return TRUE; } @@ -371,6 +453,8 @@ int gpio_reinit(){ if(!usart_config(NULL)) ret = FALSE; else if(!usart_start()) ret = FALSE; }else usart_stop(); + if(haveI2C) i2c_setup((i2c_speed_t) the_conf.I2Cspeed); + else i2c_stop(); return ret; } diff --git a/F0:F030,F042,F072/usbcan_gpio/gpio.h b/F0:F030,F042,F072/usbcan_gpio/gpio.h index 2893c68..25fd875 100644 --- a/F0:F030,F042,F072/usbcan_gpio/gpio.h +++ b/F0:F030,F042,F072/usbcan_gpio/gpio.h @@ -69,6 +69,7 @@ typedef enum FuncNames{ // shift 1 by this to get "canUSART" etc; not more than FUNC_PWM = 4, FUNC_AMOUNT // just for arrays' sizes } funcnames_t; + /* typedef union{ struct{ @@ -91,6 +92,16 @@ typedef struct{ uint16_t threshold; // threshold for ADC measurement } pinconfig_t; +typedef struct{ + uint8_t isrx : 1; + uint8_t istx : 1; +} usart_props_t; + +typedef struct { + uint8_t isscl : 1; + uint8_t issda : 1; +} i2c_props_t; + /* typedef struct{ uint32_t speed; @@ -102,11 +113,15 @@ typedef struct{ */ int is_disabled(uint8_t port, uint8_t pin); +int pinfuncs(uint8_t port, uint8_t pin); int chkpinconf(); int set_pinfunc(uint8_t port, uint8_t pin, pinconfig_t *pcfg); int get_curpinconf(uint8_t port, uint8_t pin, pinconfig_t *c); +int get_usart_index(uint8_t port, uint8_t pin, usart_props_t *p); +int get_i2c_index(uint8_t port, uint8_t pin, i2c_props_t *p); + int gpio_reinit(); int pin_out(uint8_t port, uint8_t pin, uint8_t newval); diff --git a/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp b/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp index 03d8255..04812e9 100644 --- a/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp +++ b/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp @@ -27,6 +27,7 @@ extern "C"{ #include "flash.h" #include "gpio.h" #include "gpioproto.h" +#include "i2c.h" #include "pwm.h" #include "usart.h" #undef USBIF @@ -52,10 +53,15 @@ static uint8_t hex_input_mode = 0; // ==0 for text input, 1 for HEX + text in qu COMMAND(eraseflash, "erase full flash storage") \ COMMAND(help, "show this help") \ COMMAND(hexinput, "input is text (0) or hex + text in quotes (1)") \ + COMMAND(iic, "write data over I2C: I2C=addr data (hex)") \ + COMMAND(iicread, "I2C read: I2Cread=addr nbytes (hex)") \ + COMMAND(iicreadreg, "I2C read register: I2Creadreg=addr reg nbytes (hex)") \ + COMMAND(iicscan, "Scan I2C bus for devices") \ COMMAND(mcutemp, "get MCU temperature (degC*10)") \ COMMAND(mcureset, "reset MCU") \ COMMAND(PA, "GPIOA setter/getter (type PA0=help for further info)") \ COMMAND(PB, "GPIOB setter/getter") \ + COMMAND(pinout, "list pinout with all available functions (or selected in setter, like pinout=USART,AIN") \ COMMAND(pwmmap, "show pins with PWM ability") \ COMMAND(readconf, "re-read config from flash") \ COMMAND(reinit, "apply pin config") \ @@ -112,7 +118,7 @@ enum MiscValues{ // TODO: add HEX input? #define KEYWORDS \ -KW(AIN) \ + KW(AIN) \ KW(IN) \ KW(OUT) \ KW(AF) \ @@ -131,11 +137,12 @@ KW(AIN) \ KW(HEX) \ KW(PWM) \ - enum{ // indexes of string keywords + +typedef enum{ // indexes of string keywords #define KW(k) STR_ ## k, KEYWORDS #undef KW - }; +} kwindex_t; // strings for keywords static const char *str_keywords[] = { @@ -194,6 +201,9 @@ static const char *pinhelp = ; static const char *EQ = " = "; // equal sign for getters +// token delimeters in setters +static const char *DELIM_ = " ,"; +static const char *COMMA = ", "; // comma before next val in list // send `command = ` #define CMDEQ() do{SEND(cmd); SEND(EQ);}while(0) @@ -326,7 +336,6 @@ static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){ uint32_t *pending_u32 = NULL; // -//- for uint32_t usartconf_t UsartConf; if(!get_curusartconf(&UsartConf)) return ERR_CANTRUN; -#define DELIM_ " ," char *saveptr, *token = strtok_r(setter, DELIM_, &saveptr); while(token){ if(pending_u16){ @@ -381,7 +390,7 @@ static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){ pending_u16 = &curconf.threshold; break; case MISC_SPEED: - pending_u32 = &UsartConf.speed; + pending_u32 = &UsartConf.speed; // also used for I2C speed! break; case MISC_TEXT: // what to do, if textproto is set, but user wants binary? UsartConf.textproto = 1; @@ -403,6 +412,10 @@ static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){ // check periferial settings before refresh pin data // check current USART settings if(func_set == FUNC_USART && !chkusartconf(&UsartConf)) return ERR_BADVAL; + if(func_set == FUNC_I2C){ // check speed + i2c_speed_t s = (UsartConf.speed > I2C_SPEED_1M) ? I2C_SPEED_10K : static_cast (UsartConf.speed); + the_conf.I2Cspeed = static_cast (s); + } if(func_set != 0xFF) mode_set = MODE_AF; if(mode_set == 0xFF) return ERR_BADVAL; // user forgot to set mode // set defaults @@ -579,6 +592,17 @@ static errcodes_t cmd_dumpconf(const char _U_ *cmd, char _U_ *args){ else if(!U.TXen && U.RXen) SEND(" RXONLY"); NL(); } + if(I2C1->CR1 & I2C_CR1_PE){ // I2C active, show its speed + SEND("iicspeed="); + switch(the_conf.I2Cspeed){ + case 0: SEND("10kHz"); break; + case 1: SEND("100kHz"); break; + case 2: SEND("400kHz"); break; + case 3: SEND("1MHz"); break; + default: SEND("unknown"); + } + NL(); + } #undef S #undef SP return ERR_AMOUNT; @@ -748,12 +772,146 @@ static errcodes_t cmd_USART(const char _U_ *cmd, char *args){ return ERR_AMOUNT; } +static errcodes_t cmd_iic(const char _U_ *cmd, char *args){ + if(!(I2C1->CR1 & I2C_CR1_PE)) return ERR_CANTRUN; + if(!args) return ERR_BADVAL; + char *setter = splitargs(args, NULL); + if(!setter) return ERR_BADVAL; + int len = parse_hex_data(setter, curbuf, MAXSTRLEN); + if(len < 1) return ERR_BADVAL; // need at least address + uint8_t addr = curbuf[0]; + if(addr > 0x7F) return ERR_BADVAL; // 7-битный адрес + if(len == 1){ // only address without data + return ERR_BADPAR; + } + addr <<= 1; // roll address to run i2c_write + if(!i2c_write(addr, curbuf + 1, len - 1)){ // len = address + data length + return ERR_CANTRUN; + } + return ERR_OK; +} + +static errcodes_t cmd_iicread(const char *cmd, char *args){ + if(!(I2C1->CR1 & I2C_CR1_PE)) return ERR_CANTRUN; + if(!args) return ERR_BADVAL; + char *setter = splitargs(args, NULL); + if(!setter) return ERR_BADVAL; + int len = parse_hex_data(setter, curbuf, MAXSTRLEN); + if(len != 2) return ERR_BADVAL; // address, amount of bytes + uint8_t addr = curbuf[0]; + uint8_t nbytes = curbuf[1]; + if(addr > 0x7F) return ERR_BADVAL; // allow to "read" 0 bytes (just get ACK) + addr <<= 1; + if(!i2c_read(addr, curbuf, nbytes)) return ERR_CANTRUN; + CMDEQ(); + if(nbytes < 9) NL(); + hexdump(sendfun, curbuf, nbytes); + return ERR_AMOUNT; +} + +static errcodes_t cmd_iicreadreg(const char *cmd, char *args){ + if(!(I2C1->CR1 & I2C_CR1_PE)) return ERR_CANTRUN; + if(!args) return ERR_BADVAL; + char *setter = splitargs(args, NULL); + if(!setter) return ERR_BADVAL; + int len = parse_hex_data(setter, curbuf, MAXSTRLEN); + if(len != 3) return ERR_BADVAL; // address, register, amount of bytes + uint8_t addr = curbuf[0]; + uint8_t nreg = curbuf[1]; + uint8_t nbytes = curbuf[2]; + if(addr > 0x7F || nbytes == 0) return ERR_BADVAL; + addr <<= 1; + if(!i2c_read_reg(addr, nreg, curbuf, nbytes)) return ERR_CANTRUN; + CMDEQ(); + if(nbytes < 9) NL(); + hexdump(sendfun, curbuf, nbytes); + return ERR_AMOUNT; +} + +static errcodes_t cmd_iicscan(const char _U_ *cmd, char _U_ *args){ + if(!(I2C1->CR1 & I2C_CR1_PE)) return ERR_CANTRUN; + i2c_init_scan_mode(); + return ERR_OK; +} + +// array for `cmd_pinout` +static kwindex_t func_array[FUNC_AMOUNT] = { + [FUNC_AIN] = STR_AIN, + [FUNC_USART] = STR_USART, + [FUNC_SPI] = STR_SPI, + [FUNC_I2C] = STR_I2C, + [FUNC_PWM] = STR_PWM, +}; + +static errcodes_t cmd_pinout(const char _U_ *cmd, char *args){ + uint8_t listmask = 0xff; // bitmask for funcnames_t + if(args && *args){ // change listmask by user choise + char *setter = splitargs(args, NULL); + if(!setter) return ERR_BADVAL; + char *saveptr, *token = strtok_r(setter, DELIM_, &saveptr); + listmask = 0; + while(token){ + int i = 0; + for(; i < FUNC_AMOUNT; ++i){ + if(0 == strcmp(token, str_keywords[func_array[i]])){ + listmask |= (1 << i); + break; + } + } + if(i == FUNC_AMOUNT) return ERR_BADVAL; // wrong argument + token = strtok_r(NULL, DELIM_, &saveptr); + } + if(listmask == 0) return ERR_BADVAL; + } + pwmtimer_t tp; // timers' pins + usart_props_t up; // USARTs' pins + i2c_props_t ip; // I2C's pins + + SEND("\nConfigurable pins (check collisions if functions have same name!):\n"); + for(int port = 0; port < 2; ++port){ + for(int pin = 0; pin < 16; ++pin){ + int funcs = pinfuncs(port, pin); + if(funcs == -1) continue; + uint8_t mask = (static_cast (funcs)) & listmask; + if(listmask != 0xff && !mask) continue; // no asked functions + SEND((port == 0) ? "PA" : "PB"); + SEND(u2str(pin)); + SEND(": "); + if(listmask == 0xff) SEND("GPIO"); // don't send "GPIO" for specific choice + if(mask & (1 << FUNC_AIN)){ SEND(COMMA); SEND(str_keywords[STR_AIN]); } + if(mask & (1 << FUNC_USART)){ // USARTn_aX (n - 1/2, a - R/T) + SEND(", "); + int idx = get_usart_index(port, pin, &up); + SEND(str_keywords[STR_USART]); PUTCHAR('1' + idx); + PUTCHAR('_'); PUTCHAR(up.isrx ? 'R' : 'T'); PUTCHAR('X'); + } + if(mask & (1 << FUNC_SPI)){ + SEND(COMMA); SEND(str_keywords[STR_SPI]); + // TODO: MISO/MOSI/SCL + } + if(mask & (1 << FUNC_I2C)){ + int idx = get_i2c_index(port, pin, &ip); + SEND(COMMA); SEND(str_keywords[STR_I2C]); PUTCHAR('1' + idx); + PUTCHAR('_'); + SEND(ip.isscl ? "SCL" : "SDA"); + } + if(mask & (1 << FUNC_PWM)){ + canPWM(port, pin, &tp); + SEND(COMMA); SEND(str_keywords[STR_PWM]); + SEND(u2str(tp.timidx)); // timidx == TIMNO! + PUTCHAR('_'); + PUTCHAR('1' + tp.chidx); + } + NL(); + } + } + return ERR_AMOUNT; +} + constexpr uint32_t hash(const char* str, uint32_t h = 0){ return *str ? hash(str + 1, h + ((h << 7) ^ *str)) : h; } -// TODO: add checking real command length! - static const char *CommandParser(char *str){ char command[CMD_MAXLEN+1]; int i = 0; @@ -787,11 +945,20 @@ void GPIO_process(){ l = usart_process(curbuf, MAXSTRLEN); if(l > 0) sendusartdata(curbuf, l); l = RECV((char*)curbuf, MAXSTRLEN); - if(l == 0) return; - if(l < 0) SEND("ERROR: USB buffer overflow or string was too long\n"); - else{ - const char *ans = CommandParser((char*)curbuf); - if(ans) SENDn(ans); + if(l){ + if(l < 0) SEND("ERROR: USB buffer overflow or string was too long\n"); + else{ + const char *ans = CommandParser((char*)curbuf); + if(ans) SENDn(ans); + } + } + if(I2C_scan_mode){ + uint8_t addr; + if(i2c_scan_next_addr(&addr)){ + SEND("foundaddr = "); + printuhex(addr); + NL(); + } } } diff --git a/F0:F030,F042,F072/usbcan_gpio/hardware.c b/F0:F030,F042,F072/usbcan_gpio/hardware.c index 8728d16..da8c165 100644 --- a/F0:F030,F042,F072/usbcan_gpio/hardware.c +++ b/F0:F030,F042,F072/usbcan_gpio/hardware.c @@ -36,7 +36,7 @@ void hardware_setup(){ // enable all active GPIO clocking RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN | RCC_AHBENR_DMA1EN; RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_SYSCFGEN | RCC_APB2ENR_TIM1EN | RCC_APB2ENR_TIM16EN; - RCC->APB1ENR |= RCC_APB1ENR_USART2EN | RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM14EN; + RCC->APB1ENR |= RCC_APB1ENR_USART2EN | RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM14EN | RCC_APB1ENR_I2C1EN; pins_setup(); adc_setup(); GPIO_init(); diff --git a/F0:F030,F042,F072/usbcan_gpio/i2c.c b/F0:F030,F042,F072/usbcan_gpio/i2c.c new file mode 100644 index 0000000..d2700ed --- /dev/null +++ b/F0:F030,F042,F072/usbcan_gpio/i2c.c @@ -0,0 +1,214 @@ +/* + * This file is part of the usbcangpio project. + * Copyright 2026 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gpio.h" +#include "i2c.h" + +// fields position in I2C1->TIMINGR +#define I2C_TIMINGR_PRESC_Pos 28 +#define I2C_TIMINGR_SCLDEL_Pos 20 +#define I2C_TIMINGR_SDADEL_Pos 16 +#define I2C_TIMINGR_SCLH_Pos 8 +#define I2C_TIMINGR_SCLL_Pos 0 + +i2c_speed_t curI2Cspeed = I2C_SPEED_10K; +extern volatile uint32_t Tms; +static uint32_t cntr; +volatile uint8_t I2C_scan_mode = 0; // == 1 when I2C is in scan mode +static uint8_t i2caddr = I2C_ADDREND; // address for `scan`, not active + +void i2c_setup(i2c_speed_t speed){ + if(speed >= I2C_SPEED_1M) speed = curI2Cspeed; + else curI2Cspeed = speed; + uint8_t PRESC, SCLDEL = 4, SDADEL = 2, SCLH, SCLL; + I2C1->CR1 = 0; + // I2C + RCC->CFGR3 |= RCC_CFGR3_I2C1SW; // use sysclock for timing + switch(curI2Cspeed){ + case I2C_SPEED_10K: + PRESC = 0x0B; + SCLH = 0xC3; + SCLL = 0xC7; + break; + case I2C_SPEED_100K: + PRESC = 0x0B; + SCLH = 0x0F; + SCLL = 0x13; + break; + case I2C_SPEED_400K: + SDADEL = 3; + SCLDEL = 3; + PRESC = 5; + SCLH = 3; + SCLL = 9; + break; + case I2C_SPEED_1M: + default: + SDADEL = 0; + SCLDEL = 1; + PRESC = 5; + SCLH = 1; + SCLL = 3; + break; + } + I2C1->TIMINGR = (PRESC<CFGR1 &= ~SYSCFG_CFGR1_I2C_FMP_I2C1; + }else{ // activate FM+ + SYSCFG->CFGR1 |= SYSCFG_CFGR1_I2C_FMP_I2C1; + } + I2C1->ICR = 0xffff; // clear all errors + I2C1->CR1 = I2C_CR1_PE; +} + +void i2c_stop(){ + I2C1->CR1 = 0; +} + +/** + * write command byte to I2C + * @param addr - device address (TSYS01_ADDR0 or TSYS01_ADDR1) + * @param data - bytes to write + * @param nbytes - amount of bytes to write + * @param stop - to set STOP + * @return 0 if error + */ +static uint8_t i2c_writes(uint8_t addr, uint8_t *data, uint8_t nbytes, uint8_t stop){ + cntr = Tms; + I2C1->CR1 = 0; // clear busy flag + I2C1->ICR = 0x3f38; // clear all errors + I2C1->CR1 = I2C_CR1_PE; + while(I2C1->ISR & I2C_ISR_BUSY){ + IWDG->KR = IWDG_REFRESH; + if(Tms - cntr > I2C_TIMEOUT){ + DBG("Line busy\n"); + return 0; // check busy + }} + cntr = Tms; + while(I2C1->CR2 & I2C_CR2_START){ + IWDG->KR = IWDG_REFRESH; + if(Tms - cntr > I2C_TIMEOUT){ + return 0; // check start + }} + //I2C1->ICR = 0x3f38; // clear all errors + I2C1->CR2 = nbytes << 16 | addr; + if(stop) I2C1->CR2 |= I2C_CR2_AUTOEND; // autoend + // now start transfer + I2C1->CR2 |= I2C_CR2_START; + for(int i = 0; i < nbytes; ++i){ + cntr = Tms; + while(!(I2C1->ISR & I2C_ISR_TXIS)){ // ready to transmit + IWDG->KR = IWDG_REFRESH; + if(I2C1->ISR & I2C_ISR_NACKF){ + I2C1->ICR |= I2C_ICR_NACKCF; + DBG("NAK\n"); + return 0; + } + if(Tms - cntr > I2C_TIMEOUT){ + DBG("Timeout\n"); + return 0; + } + } + I2C1->TXDR = data[i]; // send data + } + // wait for data gone + while(I2C1->ISR & I2C_ISR_BUSY){ + IWDG->KR = IWDG_REFRESH; + if(Tms - cntr > I2C_TIMEOUT){break;} + } + return 1; +} + +uint8_t i2c_write(uint8_t addr, uint8_t *data, uint8_t nbytes){ + return i2c_writes(addr, data, nbytes, 1); +} + +/** + * read nbytes of data from I2C line + * `data` should be an array with at least `nbytes` length + * @return 1 if all OK, 0 if NACK or no device found + */ +static uint8_t i2c_readb(uint8_t addr, uint8_t *data, uint8_t nbytes, uint8_t busychk){ + if(busychk){ + cntr = Tms; + while(I2C1->ISR & I2C_ISR_BUSY){ + IWDG->KR = IWDG_REFRESH; + if(Tms - cntr > I2C_TIMEOUT){ + DBG("Line busy\n"); + return 0; // check busy + }} + } + cntr = Tms; + while(I2C1->CR2 & I2C_CR2_START){ + IWDG->KR = IWDG_REFRESH; + if(Tms - cntr > I2C_TIMEOUT){ + DBG("No start\n"); + return 0; // check start + }} + // read N bytes + I2C1->CR2 = (nbytes<<16) | addr | 1 | I2C_CR2_AUTOEND | I2C_CR2_RD_WRN; + I2C1->CR2 |= I2C_CR2_START; + uint8_t i; + for(i = 0; i < nbytes; ++i){ + cntr = Tms; + while(!(I2C1->ISR & I2C_ISR_RXNE)){ // wait for data + IWDG->KR = IWDG_REFRESH; + if(I2C1->ISR & I2C_ISR_NACKF){ + I2C1->ICR |= I2C_ICR_NACKCF; + DBG("NAK\n"); + return 0; + } + if(Tms - cntr > I2C_TIMEOUT){ + DBG("Timeout\n"); + return 0; + } + } + *data++ = I2C1->RXDR; + } + return 1; + } + +uint8_t i2c_read(uint8_t addr, uint8_t *data, uint8_t nbytes){ + return i2c_readb(addr, data, nbytes, 1); +} + +// read register reg +uint8_t i2c_read_reg(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t nbytes){ + if(!i2c_writes(addr, ®, 1, 0)) return 0; + return i2c_readb(addr, data, nbytes, 0); +} + +void i2c_init_scan_mode(){ + i2caddr = 1; + I2C_scan_mode = 1; +} + +// return 1 if next addr is active & return in as `addr` +// if addresses are over, return 1 and set addr to I2C_NOADDR +// if scan mode inactive, return 0 and set addr to I2C_NOADDR +int i2c_scan_next_addr(uint8_t *addr){ + *addr = i2caddr; + if(i2caddr == I2C_ADDREND){ + *addr = I2C_ADDREND; + I2C_scan_mode = 0; + return 0; + } + if(!i2c_read_reg((i2caddr++)<<1, 0, NULL, 0)) return 0; + return 1; +} diff --git a/F0:F030,F042,F072/usbcan_gpio/i2c.h b/F0:F030,F042,F072/usbcan_gpio/i2c.h new file mode 100644 index 0000000..0c2385e --- /dev/null +++ b/F0:F030,F042,F072/usbcan_gpio/i2c.h @@ -0,0 +1,47 @@ +/* + * This file is part of the usbcangpio project. + * Copyright 2026 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +#define I2C_ADDREND (0x80) + +typedef enum{ + I2C_SPEED_10K, + I2C_SPEED_100K, + I2C_SPEED_400K, + I2C_SPEED_1M, + I2C_SPEED_AMOUNT +} i2c_speed_t; + +extern i2c_speed_t curI2Cspeed; +extern volatile uint8_t I2C_scan_mode; + +// timeout of I2C bus in ms +#define I2C_TIMEOUT (100) + +void i2c_setup(i2c_speed_t speed); +void i2c_stop(); +uint8_t i2c_read(uint8_t addr, uint8_t *data, uint8_t nbytes); +uint8_t i2c_read_reg(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t nbytes); +uint8_t i2c_write(uint8_t addr, uint8_t *data, uint8_t nbytes); + +void i2c_init_scan_mode(); +int i2c_scan_next_addr(uint8_t *addr); + diff --git a/F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin b/F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin index b58d71185b03451f489b2be6a5bb16226abafc5b..e4d6aba153dd34566f4637a884454aa367201824 100755 GIT binary patch literal 46484 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 diff --git a/F0:F030,F042,F072/usbcan_gpio/usbcangpio.creator.user b/F0:F030,F042,F072/usbcan_gpio/usbcangpio.creator.user index efeb3e1..cb78273 100644 --- a/F0:F030,F042,F072/usbcan_gpio/usbcangpio.creator.user +++ b/F0:F030,F042,F072/usbcan_gpio/usbcangpio.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/F0:F030,F042,F072/usbcan_gpio/usbcangpio.files b/F0:F030,F042,F072/usbcan_gpio/usbcangpio.files index 7e44667..05fc5ab 100644 --- a/F0:F030,F042,F072/usbcan_gpio/usbcangpio.files +++ b/F0:F030,F042,F072/usbcan_gpio/usbcangpio.files @@ -15,6 +15,8 @@ hardware.h hashgen/Readme hashgen/hashgen.c hashgen/mktestdic +i2c.c +i2c.h main.c pwm.c pwm.h diff --git a/F0:F030,F042,F072/usbcan_gpio/version.inc b/F0:F030,F042,F072/usbcan_gpio/version.inc index 34e7d71..a7a139e 100644 --- a/F0:F030,F042,F072/usbcan_gpio/version.inc +++ b/F0:F030,F042,F072/usbcan_gpio/version.inc @@ -1,2 +1,2 @@ -#define BUILD_NUMBER "207" -#define BUILD_DATE "2026-03-15" +#define BUILD_NUMBER "217" +#define BUILD_DATE "2026-03-17"