複数のデバイスを接続して使いやすように、C++でプログラムする事にします。
SW_I2C Classを定義します。
/*
* sw_i2c.h
*
* Created on: Sep 18, 2019
* Author: https://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ポートを指定します。
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用のプログラムですが、
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のメンバー関数です。関数名、変数名を見れば大体何をやっているかの想像はつくと思います。
/*
* sw_i2c.cpp
*
* Created on: Sep 18, 2019
* Author: https://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を返します。ただし、バスリセットなどの処理は実装してません。
実際の使用例は次回以降に紹介したいと思います。