STM32F103 S/W IIC (2)

投稿者: | 2019/11/01

複数のデバイスを接続して使いやすように、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を返します。ただし、バスリセットなどの処理は実装してません。
実際の使用例は次回以降に紹介したいと思います。

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。