打印
[活动专区]

【AT-START-F425测评】IO模拟IIC驱动OLED

[复制链接]
1232|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 zhouminjie 于 2022-5-23 23:40 编辑

1、关于IIC协议的分析介绍,可参考https://bbs.21ic.com/icview-2969754-1-1.html,此处不再赘述。
2、本次使用AT32F425 I/O模拟IIC来实现驱动0.96OLED液晶屏,接线方式如下:PF4连接OLED屏SDA引脚、PF5连接OLED屏SCL引脚。
3、软件设计在之前工程上增加hal_i2c.c、hal_i2c.h、hal_0.96oled.c、hal_0.96oled.h等.c.h文件,具体如下:
hal_i2c.c代码:
#include "hal_i2c.h"
#include "delay.h"

//设置SDA输入模式
void SDA_IN(void)
{
        gpio_init_type gpio_init_struct;
        
        //根据GPIO组初始化GPIO时钟
        crm_periph_clock_enable(SDA_GPIO_CRM_CLK, TRUE); //使能GPIOF时钟
        gpio_init_struct.gpio_pins = SDA_PIN;
        gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
        gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
        gpio_init_struct.gpio_pull = GPIO_PULL_UP;
        gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
        gpio_init(SDA_GPIO, &gpio_init_struct);
}

//设置SDA为输出模式
void SDA_OUT(void)
{
        gpio_init_type gpio_init_struct;
        
        //根据GPIO组初始化GPIO时钟
        crm_periph_clock_enable(SDA_GPIO_CRM_CLK, TRUE); //使能GPIOF时钟
        gpio_init_struct.gpio_pins = SDA_PIN;
        gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
        gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
        gpio_init_struct.gpio_pull = GPIO_PULL_UP;
        gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
        gpio_init(SDA_GPIO, &gpio_init_struct);
}

//设置SCL电平
void I2C_SCL(int n)
{
        if(n == 1)
        {
                gpio_bits_write(SCL_GPIO, SCL_PIN, TRUE); //设置SCL为高电平
        }
        else
        {
                gpio_bits_write(SCL_GPIO, SCL_PIN, FALSE); //设置SCL为低电平
        }
}

//设置SDA电平
void I2C_SDA(int n)
{
        if(n == 1)
        {
                gpio_bits_write(SDA_GPIO, SDA_PIN, TRUE); //设置SDA为高电平
        }
        else
        {
                gpio_bits_write(SDA_GPIO, SDA_PIN, FALSE); //设置SDA为低电平
        }
}

//读取SDA电平
unsigned char READ_SDA(void)
{
        return gpio_input_data_bit_read(SDA_GPIO, SDA_PIN); //读取SDA电平
}

//I2C初始化
void I2C_Init(void)
{
        gpio_init_type gpio_init_struct;
        
        //根据GPIO组初始化GPIO时钟
        crm_periph_clock_enable(SCL_GPIO_CRM_CLK, TRUE); //使能GPIOF时钟
        crm_periph_clock_enable(SDA_GPIO_CRM_CLK, TRUE); //使能GPIOF时钟
        
        //GPIO_SCL初始化设置
        gpio_init_struct.gpio_pins = SCL_PIN;
        gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT; //gpio output mode
        gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL; //output push-pull
        gpio_init_struct.gpio_pull = GPIO_PULL_UP; //pull-up
        gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER; //stronger sourcing/sinking strength
        gpio_init(SCL_GPIO, &gpio_init_struct);
        //GPIO_SDA初始化设置
        gpio_init_struct.gpio_pins = SDA_PIN;
        gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
        gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
        gpio_init_struct.gpio_pull = GPIO_PULL_UP;
        gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
        gpio_init(SDA_GPIO, &gpio_init_struct);
        //SCL、SDA的初始化均为高电平
        I2C_SCL(1);
        I2C_SDA(1);
}

//I2C Start
void I2C_Start(void)
{
        SDA_OUT();
        I2C_SDA(1);
        I2C_SCL(1);
        Delay_Us(4);
        I2C_SDA(0); //START:when CLK is high,DATA change form high to low
        Delay_Us(4);
        I2C_SCL(0); //钳住I2C总线,准备发送或接收数据
}

//I2C Stop
void I2C_Stop(void)
{
        SDA_OUT();
        I2C_SCL(0);
        I2C_SDA(0); //STOP:when CLK is high DATA change form low to high
        Delay_Us(4);
        I2C_SCL(1);
        I2C_SDA(1); //发送I2C总线结束信号
        Delay_Us(4);
}

//I2C_Wait_ack 返回HAL_OK表示wait成功,返回HAL_ERROR表示wait失败
unsigned char I2C_Wait_Ack(void) //IIC_Wait_ack,返回wait失败或是成功
{
        unsigned char ucErrTime = 0;
        
        SDA_IN();
        I2C_SDA(1);
        Delay_Us(1);
        I2C_SCL(1);
        Delay_Us(1);
        while(READ_SDA())
        {
                ucErrTime++;
                if(ucErrTime>250)
                {
                        I2C_Stop();
                        return HAL_ERROR;
                }
        }
        I2C_SCL(0);
        return HAL_OK;
}

//产生ACK应答
void I2C_Ack(void)      
{
        I2C_SCL(0);
        SDA_OUT();
        I2C_SDA(0);
        Delay_Us(2);
  I2C_SCL(1);
  Delay_Us(2);
  I2C_SCL(0);
}

//产生NACK应答
void I2C_NAck(void)      
{
        I2C_SCL(0);
        SDA_OUT();
        I2C_SDA(1);
        Delay_Us(2);
  I2C_SCL(1);
  Delay_Us(2);
  I2C_SCL(0);
}

//I2C_Send_Byte,入口参数为要发送的字节
void I2C_Send_Byte(unsigned char txd)     
{
        unsigned char cnt = 0;
        
        SDA_OUT();
        I2C_SCL(0);
        for(cnt = 0; cnt < 8; cnt++)
        {
                I2C_SDA((txd & 0x80) >> 7);
                txd <<= 1;
                Delay_Us(2);
                I2C_SCL(1);
                Delay_Us(2);
                I2C_SCL(0);
                Delay_Us(2);
        }  
}

//I2C_Read_Byte,入口参数为是否要发送ACK信号
unsigned char I2C_Read_Byte(unsigned char ack)     
{
        unsigned char cnt, rec = 0;
        
        SDA_IN();
        for(cnt = 0; cnt < 8; cnt++)
        {
                I2C_SCL(0);
                Delay_Us(2);
                I2C_SCL(1);
                rec <<= 1;
                if(READ_SDA())
                {
                        rec++;
                }
                Delay_Us(1);
        }
        if(!ack)
        {
                I2C_NAck();
        }
        else
        {
                I2C_Ack();
        }
        return rec;
}
hal_0.96oled.c代码:
#include "hal_0.96oled.h"
#include "oledfont.h"
#include "delay.h"

//向设备写控制命令
static void OLED_Write_CMD(unsigned char cmd)
{
        I2C_Start();
        I2C_Send_Byte(0x78);
        I2C_Wait_Ack();
        I2C_Send_Byte(0x00);
        I2C_Wait_Ack();
        I2C_Send_Byte(cmd);
        I2C_Wait_Ack();
        I2C_Stop();
}

//向设备写数据
static void OLED_Write_Date(unsigned char date)
{
        I2C_Start();
        I2C_Send_Byte(0x78);
        I2C_Wait_Ack();
        I2C_Send_Byte(0x40);
        I2C_Wait_Ack();
        I2C_Send_Byte(date);
        I2C_Wait_Ack();
        I2C_Stop();
}

//坐标设置
static void OLED_Set_Pos(unsigned char x, unsigned char y)
{
        OLED_Write_CMD(0xB0 + y);
        OLED_Write_CMD(((x & 0xF0) >> 4) | 0x10);
        OLED_Write_CMD(x & 0x0F);        
}

//开启OLED显示
static void OLED_Display_On(void)
{
        OLED_Write_CMD(0x8D); //SET DCDC命令
        OLED_Write_CMD(0x14); //DCDC ON
        OLED_Write_CMD(0xAF); //DISPLAY ON
}

//关闭OLED显示     
static void OLED_Display_Off(void)
{
        OLED_Write_CMD(0x8D); //SET DCDC命令
        OLED_Write_CMD(0x10); //DCDC OFF
        OLED_Write_CMD(0xAE); //DISPLAY OFF
}

//OLED清屏
void OLED_Clear(void)
{
        unsigned char cnt, count;
        
        for(cnt = 0; cnt < 8; cnt++)
        {
                OLED_Write_CMD(0xB0 + cnt);
                OLED_Write_CMD(0x00);
                OLED_Write_CMD(0x10);
                for(count = 0; count < 128; count++)
                {
                        OLED_Write_Date(0x00);
                }
        }
}

//OLED清行
void OLED_Clear_Row(unsigned char n)
{
        unsigned char count;
        
        OLED_Write_CMD(0xB0 + n);
        OLED_Write_CMD(0x00);
        OLED_Write_CMD(0x10);
        for(count = 0; count < 128; count++)
        {
                OLED_Write_Date(0x00);
        }
}

//OLED填满屏幕
void OLED_Fill(void)
{
        unsigned char cnt, count;
        
        for(cnt = 0; cnt < 8; cnt++)
        {
                OLED_Write_CMD(0xB0 + cnt); //设置页地址(0~7)
                OLED_Write_CMD(0x00); //设置显示位置—列低地址
                OLED_Write_CMD(0x10); //设置显示位置—列高地址
                for(count = 0; count < 128; count++)
                {
                        OLED_Write_Date(0x01);
                }
        }
}

//指定位置显示一个字符
//x:0~127
//y:0~63
//chr:字符
//mode:0,反白显示;1,正常显示                                 
//size:选择字体 16/12
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char chr, unsigned char size)
{
        unsigned char offset = 0, cnt = 0;
        
        offset = chr - ' '; //计算偏移量
        if(x > 128 - 1)
        {
                x = 0;
                y = y + 2;
        }
        if(size == 16)
        {
                OLED_Set_Pos(x, y);
                for(cnt = 0; cnt < 8; cnt++)
                {
                        OLED_Write_Date(F8x16[offset * 16 + cnt]);
                }
                OLED_Set_Pos(x, y + 1);
                for(cnt = 0; cnt < 8; cnt++)
                {
                        OLED_Write_Date(F8x16[offset * 16 + cnt + 8]);
                }
        }
        else
        {
                OLED_Set_Pos(x, y);
                for(cnt = 0; cnt < 6; cnt++)
                {
                        OLED_Write_Date(F6x8[offset][cnt]);
                }
        }
}

unsigned int oled_pow(unsigned char m, unsigned char n)
{
        unsigned int result = 1;
        while(n--)
        {
                result *= m;   
        }
        return result;
}

//指定位置显示一个数字
//x,y:起点坐标         
//num:数值(0~4294967295)
//len:数字的位数
//size:字体大小
//mode:模式        0,填充模式;1,叠加模式
void OLED_ShowNum(unsigned char x, unsigned char y, unsigned int num, unsigned char len, unsigned char size)
{
        unsigned char cnt, temp;
        unsigned char show = 0;
        
        for(cnt = 0; cnt < len; cnt++)
        {
                temp = (num / oled_pow(10, len - cnt - 1)) % 10;
                if(show == 0 && cnt < (len - 1))
                {
                        if(temp == 0)
                        {
                                OLED_ShowChar(x + (size / 2) * cnt, y, ' ', size);
                                continue;
                        }
                        else
                        {
                                show = 1;
                        }
                }
                OLED_ShowChar(x + (size / 2) * cnt, y, temp + '0', size);
        }
}        

//指定位置显示字符串
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *chr, unsigned char size)        
{
        unsigned char cnt = 0;
        
        while(chr[cnt] != '\0')
        {
                OLED_ShowChar(x, y, chr[cnt], size);
                x += 8;
                if(x > 120)
                {
                        x = 0;
                        y += 2;
                }
                cnt++;
        }
}

//显示汉字
void OLED_ShowCHinese(unsigned char x, unsigned char y, unsigned char no)
{
        unsigned char cnt, addr = 0;
        
        OLED_Set_Pos(x, y);
        for(cnt = 0; cnt < 16; cnt++)
        {
                OLED_Write_Date(Hzk[2 * no][cnt]);
                addr++;
        }
        OLED_Set_Pos(x, y + 1);
        for(cnt = 0; cnt < 16; cnt++)
        {
                OLED_Write_Date(Hzk[2 * no + 1][cnt]);
                addr++;
        }
}

//OLED初始化
void OLED_Init(void)
{
        I2C_Init();
        Delay_Ms(200);
        OLED_Write_CMD(0xAE); //display off
        OLED_Write_CMD(0x00); //set low column address
        OLED_Write_CMD(0x10); //set high column address
        OLED_Write_CMD(0x40); //set start line address  
        OLED_Write_CMD(0xB0); //set page address
        OLED_Write_CMD(0x81); //contract control
        OLED_Write_CMD(0xFF); //128
        OLED_Write_CMD(0xA1); //set segment remap
        OLED_Write_CMD(0xA6); //normal / reverse
        OLED_Write_CMD(0xA8); //set multiplex ratio(1 to 64)
        OLED_Write_CMD(0x3F); //1/32 duty
        OLED_Write_CMD(0xC8); //Com scan direction
        OLED_Write_CMD(0xD3); //set display offset
        OLED_Write_CMD(0x00); //
        OLED_Write_CMD(0xD5); //set osc division
        OLED_Write_CMD(0x80); //
        OLED_Write_CMD(0xD8); //set area color mode off
        OLED_Write_CMD(0x05); //
        OLED_Write_CMD(0xD9); //Set Pre-Charge Period
        OLED_Write_CMD(0xF1); //
        OLED_Write_CMD(0xDA); //set com pin configuartion
        OLED_Write_CMD(0x12); //
        OLED_Write_CMD(0xDB); //set Vcomh
        OLED_Write_CMD(0x30); //
        OLED_Write_CMD(0x8D); //set charge pump enable
        OLED_Write_CMD(0x14); //
        OLED_Write_CMD(0xAF); //turn on oled panel
}
模拟i2c时,delay.c代码:
#include "at32f425.h"
#include "hal_timer.h"

static unsigned char fac_us = 0; //us延时倍乘数
static unsigned short fac_ms = 0; //ms延时倍乘数

//初始化延迟函数
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void Delay_Init(void)
{
        crm_clocks_freq_get(&crm_clocks_freq_struct);
        fac_us = crm_clocks_freq_struct.sclk_freq / (8000000U); //systick为HCLK/8,fac_us为systick的1/8/1000000,即每1us systick的VAL减的数目
        fac_ms = (unsigned short)fac_us * (1000U); //每1ms需要的systick时钟数,即每1ms systick的VAL减的数目
}

//延时nus
//nus为要延时的us数
void Delay_Us(unsigned int nus)
{
        unsigned int temp;                     

        SysTick->LOAD = (unsigned int)(nus * fac_us); //时间加载                           
        SysTick->VAL = 0x00; //清空计数器
        SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //开始倒数         
        do
        {
                temp = SysTick->CTRL;
        }
        while((temp & 0x01) && !(temp & (1 << 16))); //等待时间到达   
        SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //关闭计数器
        SysTick->VAL = 0x00; //清空计数器
}

//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对96M条件下,nms<=1398
void Delay_Ms(unsigned short nms)
{
        unsigned int temp;                  

        SysTick->LOAD = (unsigned int)(nms * fac_ms); //时间加载(SysTick->LOAD为24bit)
        SysTick->VAL = 0x00; //清空计数器
        SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //开始倒数
        do
        {
                temp = SysTick->CTRL;
        }
        while((temp & 0x01) && !(temp & (1 << 16))); //等待时间到达,看CTRL的第16位(COUNTFLAG)是否为1,看STRL的第0位(ENABLE)是否为1
        SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //关闭计数器
        SysTick->VAL = 0x00; //清空计数器                     
}
4、显示效果:

5、工程代码:

AT32F425_SW_I2C_0.96OLED.zip

352.86 KB

使用特权

评论回复
沙发
sadicy| | 2022-6-3 08:57 | 只看该作者
这开发板都是白色的么?看着挺清新

使用特权

评论回复
板凳
简单happy| | 2022-6-12 23:33 | 只看该作者
感谢楼主分享,借鉴了一下I2C

使用特权

评论回复
地板
yangxiaor520| | 2022-6-13 19:13 | 只看该作者
很多时候IIC都是用IO模拟的。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

31

主题

135

帖子

3

粉丝