複数のデバイスを接続して使いやすように、C++でプログラムする事にします。
SW_I2C Classを定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
/* * sw_i2c.h * * Created on: Sep 18, 2019 * Author: http://www.e-momonga.com/honkytonk * Copyright 2019 honkytonk. All right reserved. */ #ifndef INC_SW_I2C_H_ #define INC_SW_I2C_H_ #include <stdint.h> #include "stm32f1xx_hal.h" #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus }; #endif typedef struct { GPIO_TypeDef* group; uint16_t port; }gpio; #define BIT_00 0x0001 #define BIT_01 0x0002 #define BIT_02 0x0004 #define BIT_03 0x0008 #define BIT_04 0x0010 #define BIT_05 0x0020 #define BIT_06 0x0040 #define BIT_07 0x0080 #define BIT_08 0x0100 #define BIT_09 0x0200 #define BIT_10 0x0400 #define BIT_11 0x0800 #define BIT_12 0x1000 #define BIT_13 0x2000 #define BIT_14 0x4000 #define BIT_15 0x8000 #define IIC_READ_MASK ~0xfe #define IIC_WRITE_MASK 0xfe #define IIC_STATUS_NACK 0x01 #define IIC_STATUS_OK 0x00 #define IIC_STATUS_NO_ERROR 0x00 #ifdef __cplusplus class SW_I2C { private: uint8_t slave_address; gpio scl; gpio sda; uint8_t status; private: void clearStatus(void){this->status = IIC_STATUS_OK;} void start(void); void stop(void); void sendAck(bool anck); bool checkAck(void); void shiftData8Write(uint8_t data); uint8_t* shiftData8Read(uint8_t *rdata, bool ack); void SCL(bool hl){ HAL_GPIO_WritePin(this->scl.group, this->scl.port, hl ? GPIO_PIN_SET : GPIO_PIN_RESET); } void SDA(bool hl){ HAL_GPIO_WritePin(this->sda.group, this->sda.port, hl ? GPIO_PIN_SET : GPIO_PIN_RESET); } bool readSDA(void){ return HAL_GPIO_ReadPin(this->sda.group, this->sda.port) ? true : false; } public: SW_I2C(uint8_t slave, gpio sda, gpio scl){ this->slave_address = slave; this->sda = sda; this->scl = scl; this->status = IIC_STATUS_OK; } void setSlaveAddress(uint8_t address){this->slave_address = address;} void setSclPort(gpio io){ scl = io; } void setSdaPort(gpio io){ sda = io; } uint8_t* burstReadDataBytes(uint8_t address, uint8_t reg_address, uint8_t *rdata, uint8_t len); void burstWriteDataBytes(uint8_t address, uint8_t reg_address, uint8_t *wdata, uint8_t len); uint8_t* readBytes(uint8_t address, uint8_t *rdata, uint8_t len = 1); void writeBytes(uint8_t address, uint8_t *wdata, uint8_t len); bool testReadByte(uint8_t address, uint8_t reg_address, uint8_t *rdata, uint8_t len); uint8_t getStatus(void){return this->status;} uint8_t getSlaveAddress(void){return this->slave_address;} }; #endif #endif /* INC_SW_I2C_H_ */ |
コンストラクタ SW_I2C()には、スレーブデバイスアドレス、SDA,SCLポートを指定します。
1 |
class SW_I2C sc1602((0x27 << 1), {SDA_GPIO_Port, SDA_Pin}, {SCL_GPIO_Port, SCL_Pin}); |
I2Cスレーブデバイスのアドレスが0x27の場合、1bitシフトしたものをスレーブアドレスとして指定します。各メンバー関数は、引数としてスレーブアドレスを設定することができます。コンストラクタで設定したスレーブアドレスを使う場合は、getSlaveAddress()を呼んでください。
SDA, SCLはCubeMXで設定したポートを指定します。ポートは、Open Drain(OD), No Pull-up, Pull-Down, 初期値 Hに設定します。STM32F103には5Vトレラントのポートがありますが、5Vで使用する場合は内蔵のPull-upは使用できませんので、No Pull-upに設定してください。また、I2Cは通信していないときはラインをHにしなければなりませんので、初期値としてHを設定します。
STM32F103用のプログラムですが、
70 71 72 |
void SCL(bool hl){ HAL_GPIO_WritePin(this->scl.group, this->scl.port, hl ? GPIO_PIN_SET : GPIO_PIN_RESET); } void SDA(bool hl){ HAL_GPIO_WritePin(this->sda.group, this->sda.port, hl ? GPIO_PIN_SET : GPIO_PIN_RESET); } bool readSDA(void){ return HAL_GPIO_ReadPin(this->sda.group, this->sda.port) ? true : false; } |
この部分を修正すれば、他のマイコンにも移植できると思います。
(ESP32でも一部修正して使っています)
SW_I2Cのメンバー関数です。関数名、変数名を見れば大体何をやっているかの想像はつくと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
/* * sw_i2c.cpp * * Created on: Sep 18, 2019 * Author: http://www.e-momonga.com/honkytonk * Copyright 2019, honkytonk. All right reserved. */ #include "sw_i2c.h" static void shortWait(volatile uint16_t wait) { wait *= 1; do{ wait--; }while(wait); } void SW_I2C::start(void) { this->SDA(1); shortWait(1); this->SCL(1); shortWait(1); this->SDA(0); shortWait(1); this->SCL(0); this->SDA(1); } void SW_I2C::stop(void) { this->SDA(0); this->SCL(0); this->SCL(1); shortWait(1); this->SDA(1); } void SW_I2C::sendAck(bool anck) { this->SCL(0); this->SDA(anck ? 0 : 1); // shortWait(1); this->SCL(1); shortWait(2); this->SCL(0); } bool SW_I2C::checkAck(void) { bool ret = false; //nack this->SDA(1); // to set OD Tr OFF shortWait(1); this->SCL(1); shortWait(1); this->readSDA() == false ? ret = true : ret = false; this->SCL(0); return ret; } void SW_I2C::shiftData8Write(uint8_t data) { uint8_t mask = 0x80; for(uint8_t i = 0; i < 8; i++){ this->SCL(0); this->SDA(data & mask ? 1 : 0); mask >>= 1; this->SCL(1); shortWait(1); } this->SCL(0); this->SDA(1); } uint8_t* SW_I2C::shiftData8Read(uint8_t *rdata, bool ack) { this->SDA(1); uint8_t mask = 0x80; for(uint8_t i = 0; i < 8; i++){ this->SCL(0); shortWait(1); this->SCL(1); this->readSDA() ? *rdata |= mask : *rdata &= ~mask; mask >>= 1; } this->SCL(0); this->sendAck(ack); return rdata; } bool SW_I2C::testReadByte(uint8_t address, uint8_t reg_address, uint8_t *rdata, uint8_t len) { bool ret = false; this->start(); this->shiftData8Write(address | IIC_READ_MASK); ret = this->checkAck(); if(ret){ for(uint8_t i = 0; i < len; i++){ bool ack = (i < (len -1) ? true : false); //the last byte is terminated by nack. this->shiftData8Read(rdata +i, ack); } } this->stop(); return ret; } uint8_t* SW_I2C::readBytes(uint8_t address, uint8_t *rdata, uint8_t len) { this->status = IIC_STATUS_NO_ERROR; this->start(); this->shiftData8Write(address | IIC_READ_MASK); if(this->checkAck() == false){this->status |= IIC_STATUS_NACK;} if(this->status == IIC_STATUS_OK){ for(uint8_t i = 0; i < len; i++){ bool ack = (i < (len -1) ? true : false); //the last byte is terminated by nack. this->shiftData8Read(rdata +i, ack); } } this->stop(); return rdata; } uint8_t* SW_I2C::burstReadDataBytes(uint8_t address, uint8_t reg_address, uint8_t *rdata, uint8_t len) { this->status = IIC_STATUS_NO_ERROR; this->start(); this->shiftData8Write(address & IIC_WRITE_MASK); if(this->checkAck() == false){this->status |= IIC_STATUS_NACK;} if(this->status == IIC_STATUS_OK){ this->shiftData8Write(reg_address); if(this->checkAck() == false){this->status |= IIC_STATUS_NACK;} this->start(); //restart this->shiftData8Write(address | IIC_READ_MASK); if(this->checkAck() == false){this->status |= IIC_STATUS_NACK;} for(uint8_t i = 0; i < len; i++){ bool ack = (i < (len -1) ? true : false); //the last byte is terminated by nack. this->shiftData8Read(rdata +i, ack); } } this->stop(); return rdata; } void SW_I2C::burstWriteDataBytes(uint8_t address, uint8_t reg_address, uint8_t *wdata, uint8_t len) { this->status = IIC_STATUS_NO_ERROR; this->start(); this->shiftData8Write(address & IIC_WRITE_MASK); if(this->checkAck() == false){ this->status |= IIC_STATUS_NACK; } else{ this->shiftData8Write(reg_address); if(this->checkAck() == false){this->status |= IIC_STATUS_NACK;} for(uint8_t i = 0; i < len; i++){ this->shiftData8Write(wdata[i]); if(this->checkAck() == false){this->status |= IIC_STATUS_NACK;} } } this->stop(); } void SW_I2C::writeBytes(uint8_t address, uint8_t *wdata, uint8_t len) { this->status = IIC_STATUS_NO_ERROR; this->start(); this->shiftData8Write(address & IIC_WRITE_MASK); if(this->checkAck() == false){ this->status |= IIC_STATUS_NACK; } else{ for(uint8_t i = 0; i < len; i++){ this->shiftData8Write(wdata[i]); if(this->checkAck() == false){this->status |= IIC_STATUS_NACK;} } } this->stop(); } |
クロックSCLのスピードが早い場合は、shortWait( )を調整してください。
実機で確認したところ、クロック周波数72MHzのSTM32F103C8T6では、SCLクロック周波数は 250kHz程度ありましたので、もう少しwaitを増やしてスピードを落とす方が良いと思います。
Open Drainは立ち上がりがプルアップ抵抗とラインの容量で鈍りますので、通信速度は遅い方が安全です。
エラー処理はackの確認のみ実装しています。checkStatus()でnackだった場合はIIC_STATUS_NACKを返します。ただし、バスリセットなどの処理は実装してません。
実際の使用例は次回以降に紹介したいと思います。