Introduction
A dedicated I2C module of a micro-controller including the dsPIC30F2010 is fixed. For instance the I2C1 port pins of the dsPIC30F2010 are RF2(SDA) and RF3(SCL). These pins share with other communication module such as ICSP and UART1. So we need to disconnect them first before setting up a proper communication port connection. However, some modern micro-controllers have a port remapping for communication interface pins.
![]() |
| PCF8574T Software I2C Character LCD |
On the other hand, the I2C communication protocol is very easy to emulate via software bit-banging method using Assembly, C, or even other high level programming languages. Mikro C offers both hardware and software I2C library routines. We can check its user's manual for details. Here I cut only some key-points:
External dependencies of Software I²C Library
| The following variables must be defined in all projects using Software I²C Library: | Description : | Example : |
|---|---|---|
extern sbit Soft_I2C_Scl; |
Soft I²C Clock line. | sbit Soft_I2C_Scl at
RB11_bit; |
extern sbit Soft_I2C_Sda; |
Soft I²C Data line. | sbit Soft_I2C_Sda at
RB12_bit; |
extern sbit Soft_I2C_Scl_Direction; |
Direction of the Soft I²C Clock pin. | sbit Soft_I2C_Scl_Direction at
TRISB11_bit; |
extern sbit Soft_I2C_Sda_Direction; |
Direction of the Soft I²C Data pin. | sbit Soft_I2C_Sda_Direction at
TRISB12_bit; |
Library Routines
The Soft_I2C_Scl and Soft_I2C_Sda must be declared first before we can use the Soft_I2C_Init function. The Software I2C operates at lower clock speed and need more program space (ROM) since it's a purely software communication protocol.
dsPIC30F2010 Interfacing and Programming using MikroC
We can select any pins of the dsPIC30F2010 to operates a software I2C communication protocol. Here I use RD0 for SCL and RD1 for SDA. I'm not sure about its SCL clock frequency since I don't have an oscilloscope.
Schematic:
![]() |
| Schematic |
In this example a PCF8574T character LCD and a DS3231 Real Time Clock (RTC) chip use a single I2C bus to communication with the master MCU. The MCU continuous read the RTC data from DS3231 and shows it on the I2C character LCD. The DS3231 timing is very precise since it uses its internal calibrated RC oscillator.
![]() |
| PCF8574T Arduino TWI HD44780 LCD Module |
![]() |
| Arduino DS3231 High Precision Real Time Clock Module |
![]() |
| Arduino DS3231 High Precision Real Time Clock Module |
The PCF8574T and DS3231 Arduino module are widely used among students and hobbyists due to its low cost and less complexity.
MikroC Source Code:
- // Software I2C connections
- sbit Soft_I2C_Scl at RD0_bit;
- sbit Soft_I2C_Sda at RD1_bit;
- sbit Soft_I2C_Scl_Direction at TRISD0_bit;
- sbit Soft_I2C_Sda_Direction at TRISD1_bit;
- // End Software I2C connections
- const char PCF8574_W=0x40;
- const char PCF8574_R=0x41;
- const char PCF8574T_LCD_W=0x4E;
- const char PCF8574T_LCD_R=0x4F;
- const char PCF8574A_W=0x70;
- const char PCF8574A_R=0x71;
- void pcf8574_write(char _data){
- Soft_I2C_Start(); // issue I2C start signal
- Soft_I2C_Write(PCF8574T_LCD_W); // send byte via I2C (device address + W)
- Soft_I2C_Write(_data); // send byte (data address)
- Soft_I2C_Stop(); // issue I2C stop signal
- }
- /*TWI LCD Driver*/
- #define RS 0
- #define RW 1
- #define EN 2
- #define BL 3
- #define i2c_lcd_cursor_off() i2c_lcd_command(0x0C)
- #define i2c_lcd_line_1() i2c_lcd_command(0x80)
- #define i2c_lcd_line_2() i2c_lcd_command(0xC0)
- char backLight=1;
- void i2c_lcd_command(char command){
- char _data;
- _data=command&0xF0;
- pcf8574_write(_data|(backLight<<BL)|(1<<EN));
- //_delay_us(10);
- pcf8574_write(_data|(backLight<<BL));
- //_delay_us(50);
- _data=command<<4;
- pcf8574_write(_data|(backLight<<BL)|(1<<EN));
- //_delay_us(10);
- pcf8574_write(_data|(backLight<<BL));
- //_delay_us(50);
- }
- void i2c_lcd__data(char command){
- char _data;
- _data=command&0xF0;
- pcf8574_write(_data|(backLight<<BL)|(1<<EN)|(1<<RS));
- Delay_us(10);
- pcf8574_write(_data|(backLight<<BL)|(1<<RS));
- Delay_us(50);
- _data=command<<4;
- pcf8574_write(_data|(backLight<<BL)|(1<<EN)|(1<<RS));
- Delay_us(10);
- pcf8574_write(_data|(backLight<<BL)|(1<<RS));
- Delay_us(50);
- }
- void i2c_lcd_xy(char x, char y){
- char addr[]={0x80,0xC0};
- i2c_lcd_command(addr[y-1]+x-1);
- }
- void i2c_lcd_text(char *txt){
- while(*txt) i2c_lcd__data(*txt++);
- }
- void i2c_lcd_clear(void){
- i2c_lcd_command(0x01);
- Delay_ms(5);
- }
- void i2c_lcd_init(void){
- pcf8574_write(0);
- Delay_ms(10);
- i2c_lcd_command(0x33);
- Delay_us(10);
- i2c_lcd_command(0x32);
- Delay_us(10);
- i2c_lcd_command(0x28);
- Delay_us(10);
- i2c_lcd_command(0x0F);
- Delay_us(10);
- i2c_lcd_command(0x01);
- Delay_ms(10);
- i2c_lcd_command(0x06);
- Delay_us(10);
- }
- void rtc_set(void){
- char rtc[7]={0x35,0x12,0x17,0x05,0x15,0x10,0x26};
- char i=0;
- Soft_I2C_Start(); // issue I2C start signal
- Soft_I2C_Write(0xD0); // send byte via I2C (device address + W)
- Soft_I2C_Write(0); // send byte (data address)
- while(i<7){
- Soft_I2C_Write(rtc[i]);
- i++;
- }
- Soft_I2C_Stop();
- }
- void main(){
- char temp[7], i = 0, text[16];
- Soft_I2C_Init(); // Initialize Soft I2C communication
- Delay_ms(10);
- //rtc_set();
- i2c_lcd_init();
- Delay_ms(10);
- i2c_lcd_clear();
- i2c_lcd_text("Software I2C");
- i2c_lcd_line_2();
- i2c_lcd_text("PCF8574T DS3231");
- i2c_lcd_cursor_off();
- Delay_ms(5000);
- i2c_lcd_clear();
- while(1){
- for(i = 0; i < 7; i++){
- Soft_I2C_Start(); // issue I2C start signal
- Soft_I2C_Write(0xD0); // send byte via I2C (device address + W)
- Soft_I2C_Write(i); // send byte (data address)
- Soft_I2C_Stop();
- Soft_I2C_Start();
- Soft_I2C_Write(0xD1); // send byte (device address + R)
- temp[i] = Soft_I2C_Read(0u); // Read the data (NO acknowledge)
- Soft_I2C_Stop(); // issue I2C stop signal
- }
- sprintf(text,"Date: %2X/%2X/20%2X",temp[4],temp[5],temp[6]);
- i2c_lcd_line_1();
- i2c_lcd_text(text);
- sprintf(text,"Time: %2X/%2X/%2X",temp[2],temp[1],temp[0]);
- i2c_lcd_line_2();
- i2c_lcd_text(text);
- Delay_ms(800);
- }
- }
The sprintf function uses a lot of memory space. We can process the ASCII data one-by-one to save memory space of the MCU.
![]() |
| DS3231 and PCF8574T Software I2C Programming |
![]() |
| DS3231 and PCF8574T Software I2C Programming |
I tested this program on my DIY dsPIC30F2010 Prototype Board.
For a full software TWI programming examples please see this post.


.jpg)













.jpg)











