本帖最后由 lulugl 于 2023-11-2 21:33 编辑
#申请原创# @21小跑堂
【前言】
OLED为常用的显示外设之一,他可以使用spi或者i2c接口来实现驱动。而以I2C为常见,它只需要两个IO就可以实现驱动,对于IO紧的MCU来说更为方便,而且可以与其它的I2C设备共同一个总线。AT32F423拥用3个I2C总线,可以搭载多个i2c设备。同时AT32F423也可以通过IO的复用,更换不同的IO来实现i2c。I2c驱动也可以用普通GPIO,软件摸拟时序来实现,这样使得可以接入更多的I2C的总线设备。本次实验使用i2c硬件、阻塞式的实现驱动。AT32F423的总线也可以用中断、DMA的方式来驱动,这样占用CPU的资源更小,传输速度更快。
【接线图】
这次i2c我选择i2c1,IO选择PB6为SCL,PB7为SDA。与OLED的接线图如下表:
OLED 开发板
SCL—————PB6
SDA—————PB7
GND-----------GND
VCC------------3.3V
【I2C总线初始化】
根据数据手册PB6、PB7的引脚复用GPIO_MUX_4,详见下图:
在IO初始化中,我们使用i2c的复用配置函数gpio_pin_mux_config来定义:
/* gpio configuration */
gpio_pin_mux_config(I2Cx_SCL_GPIO_PORT, I2Cx_SCL_GPIO_PinsSource, I2Cx_SCL_GPIO_MUX);
gpio_pin_mux_config(I2Cx_SDA_GPIO_PORT, I2Cx_SDA_GPIO_PinsSource, I2Cx_SDA_GPIO_MUX);
然后开启i2c的时序,以及gpio的时钟:
/* i2c periph clock enable */
crm_periph_clock_enable(I2Cx_CLK, TRUE);
crm_periph_clock_enable(I2Cx_SCL_GPIO_CLK, TRUE);
crm_periph_clock_enable(I2Cx_SDA_GPIO_CLK, TRUE);
初始化i2c的引脚,由于i2c需要上拉电平,我们这里配置其为GPIO_PULL_UP模式。
最后我们配置i2c的总线频率为200K
具体的代码见void II2CGpioInit(void)函数。
void II2CGpioInit(void)
{
#if 0
gpio_init_type gpio_init_struct;
/* enable gpioc periph clock */
crm_periph_clock_enable(CRM_GPIOC_PERIPH_CLOCK, TRUE);
gpio_default_para_init(&gpio_init_struct);
/* gpio output config */
gpio_bits_write(GPIOC, IO_SCL_PIN | IO_SDA_PIN, TRUE);
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_OPEN_DRAIN;
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins = IO_SCL_PIN | IO_SDA_PIN;
gpio_init_struct.gpio_pull = GPIO_PULL_UP;
gpio_init(GPIOC, &gpio_init_struct);
#else
gpio_init_type gpio_init_structure;
crm_periph_clock_enable(I2Cx_CLK, TRUE);
crm_periph_clock_enable(I2Cx_SCL_GPIO_CLK, TRUE);
crm_periph_clock_enable(I2Cx_SDA_GPIO_CLK, TRUE);
/* gpio configuration */
gpio_pin_mux_config(I2Cx_SCL_GPIO_PORT, I2Cx_SCL_GPIO_PinsSource, I2Cx_SCL_GPIO_MUX);
gpio_pin_mux_config(I2Cx_SDA_GPIO_PORT, I2Cx_SDA_GPIO_PinsSource, I2Cx_SDA_GPIO_MUX);
/* configure i2c pins: scl */
gpio_init_structure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_structure.gpio_mode = GPIO_MODE_MUX;
gpio_init_structure.gpio_out_type = GPIO_OUTPUT_OPEN_DRAIN;
gpio_init_structure.gpio_pull = GPIO_PULL_UP;
gpio_init_structure.gpio_pins = I2Cx_SCL_GPIO_PIN;
gpio_init(I2Cx_SCL_GPIO_PORT, &gpio_init_structure);
/* configure i2c pins: sda */
gpio_init_structure.gpio_pins = I2Cx_SDA_GPIO_PIN;
gpio_init(I2Cx_SDA_GPIO_PORT, &gpio_init_structure);
hi2cx.i2cx = I2Cx_PORT;
/* i2c config */
i2c_config(&hi2cx);
#endif
}
【i2c发送数据】
在AT32F423中的固件库中,官方给我们提供了一个i2c_application函数,函数封装了常用的数据发送函数。这个函数位于\middlewares\i2c_application_library目录下面。
这个函数中定义了i2c事件,传输数据的宽度,i2c的传输模式,i2c的传输反馈状态,i2c的事件状态等的枚举。
文件中还封装了i2c所有的发送、状态获取、接收、DMA发送、中断发送、阻寒式发送的函数,大家可以详细的去阅读他的源码,对掌握i2c非常有用:
我这里也是采用了官方封装的发送函数来传输一个byte的函数:i2c_master_transmit。用这个函数,实现了向oled写一个字节,写命令、写数据的三个功能函数:
/*****************************************************************************
* [url=home.php?mod=space&uid=139335]@name[/url] :void Write_IIC_Byte(uint8_t IIC_Byte)
* [url=home.php?mod=space&uid=212281]@date[/url] :2018-09-13
* [url=home.php?mod=space&uid=42490]@function[/url] :Write a byte of content with iic bus
* [url=home.php?mod=space&uid=2814924]@parameters[/url] :IIC_Byte
* @retvalue :None
******************************************************************************/
void Write_IIC_Byte(uint8_t IIC_Byte) {
i2c_status_type i2c_status;
#if 0
uint8_t i;
uint8_t m,da;
da=IIC_Byte;
OLED_SCL_CLR();
for(i=0;i<8;i++) {
m=da;
m=m&0x80;
if(m==0x80) {
OLED_SDA_SET();
} else {
OLED_SDA_CLR();
}
da=da<<1;
OLED_SCL_SET();
OLED_SCL_CLR();
}
#else
uint8_t buff[2] = {0};
buff[0] = IIC_Byte;
i2c_status = i2c_master_transmit(&hi2cx, I2Cx_ADDRESS, buff, 1, 1000);
if(i2c_status != I2C_OK)
{
printf("erro send %d",i2c_status);
}
#endif
}
/*****************************************************************************
* [url=home.php?mod=space&uid=139335]@name[/url] :void Write_IIC_Command(uint8_t IIC_Command)
* [url=home.php?mod=space&uid=212281]@date[/url] :2018-09-13
* [url=home.php?mod=space&uid=42490]@function[/url] :Write a byte of command to oled screen
* [url=home.php?mod=space&uid=2814924]@parameters[/url] :IIC_Command:command to be written
* @retvalue :None
******************************************************************************/
void Write_IIC_Command(uint8_t IIC_Command) {
i2c_status_type i2c_status;
#if 0
IIC_Start();
Write_IIC_Byte(IIC_SLAVE_ADDR); //Slave address,SA0=0
IIC_Wait_Ack();
Write_IIC_Byte(0x00); //write command
IIC_Wait_Ack();
Write_IIC_Byte(IIC_Command);
IIC_Wait_Ack();
IIC_Stop();
#else
uint8_t buff[2] = {0};
buff[0] = 0x00;
buff[1] = IIC_Command;
i2c_status = i2c_master_transmit(&hi2cx, I2Cx_ADDRESS, buff, 2, 1000);
if(i2c_status != I2C_OK)
{
printf("erro send %d",i2c_status);
}
#endif
}
/*****************************************************************************
* @name :void Write_IIC_Data(uint8_t IIC_Data)
* @date :2018-09-13
* @function :Write a byte of data to oled screen
* @parameters :IIC_Data:data to be written
* @retvalue :None
******************************************************************************/
void Write_IIC_Data(uint8_t IIC_Data) {
i2c_status_type i2c_status;
#if 0
IIC_Start();
Write_IIC_Byte(IIC_SLAVE_ADDR); //D/C#=0; R/W#=0
IIC_Wait_Ack();
Write_IIC_Byte(0x40); //write data
IIC_Wait_Ack();
Write_IIC_Byte(IIC_Data);
IIC_Wait_Ack();
IIC_Stop();
#else
uint8_t buff[2] = {0};
buff[0] = 0x40;
buff[1] = IIC_Data;
i2c_status = i2c_master_transmit(&hi2cx, I2Cx_ADDRESS, buff, 2, 1000);
if(i2c_status != I2C_OK)
{
printf("erro send %d",i2c_status);
}
#endif
}
【OLED初始化】
我的oled的芯片是ssd1306,其他的oled大多数是通用的初始化函数,我这里采用的页写入模式为分页式,配置函数如下:
/*******************************************************************
* @name :void OLED_Init(void)
* @date :2018-08-27
* @function :initialise OLED SSD1306 control IC
* @parameters :None
* @retvalue :None
********************************************************************/
void OLED_Init(void) {
II2CGpioInit();
delay_ms(200);
/**************初始化SSD1306*****************/
// OLED_Display_Off(); //power off
OLED_WR_Byte(0xAE,OLED_CMD);//--display off
OLED_WR_Byte(0x20,OLED_CMD);//---set low column address
OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
OLED_WR_Byte(0xb0,OLED_CMD);
OLED_WR_Byte(0xC8,OLED_CMD);//-not offset
OLED_WR_Byte(0x00,OLED_CMD);// contract control
OLED_WR_Byte(0x10,OLED_CMD);
OLED_WR_Byte(0x40,OLED_CMD);//--128
OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
OLED_WR_Byte(0xFF,OLED_CMD);
OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap
OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse
OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
OLED_WR_Byte(0x3F,OLED_CMD);
OLED_WR_Byte(0xa4,OLED_CMD);//-set display offset
OLED_WR_Byte(0xd3,OLED_CMD);
OLED_WR_Byte(0x00,OLED_CMD);
OLED_WR_Byte(0xD5,OLED_CMD);//set osc division
OLED_WR_Byte(0xF0,OLED_CMD);
OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period
OLED_WR_Byte(0x22,OLED_CMD);
OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion
OLED_WR_Byte(0x12,OLED_CMD);
OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh
OLED_WR_Byte(0x20,OLED_CMD);
OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable
OLED_WR_Byte(0x14,OLED_CMD);
OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
//OLED_Display_On(); // power on
}
【字库】
字库我这里采用的是网友提供的,如有侵权请及时通知我,其中的我这里特别要说明一下如何生成我的汉字字库,我这里使用的工具是PCtolCD2002。汉字取模采用阴码、逐行式,顺向、C51格式,配置如下图:
字宽与高采用16*16,宋体:
生成字模后加入到字库函数中:
【测试代码】
在主函数中,我先初始化oled,清屏,然后编写测试函数:
【实现的效果】
【试用心得】
1、网上驱动OLED的例程非常多,网友@suncat0504,也提前写了oled的软件驱动例程: https://bbs.21ic.com/icview-3336944-1-1.html。我所用的库也是在他的提供的例程上修改过来的。但是他的驱动例程好象在我的这里运行不了。我在他的基础上修改了一些参数,比如页的大小、修改为硬件驱动等。"
2、在测试中需要用逻辑分析仪来查看一下时序。
3、其实要驱动AT32F423的I2C,最好是去看看官方的一些例程,例程中详细的列出了多种方式的i2c的通信方式。
4、当然这次的驱动只是实现驱动而已,速度只用到了200K。如果需要用到高的刷新率,最好使用dma的方式来实现数据的传输,同时采用双缓存来实时更新数据,以实现高频的数据刷新。当然也可以移植u8g2等第三方库来实现更多的显示功能等。
5、同时也**有其他的大佬们能把dma等高效率的驱动奉献出来。
【附件】:OLED驱动包,如需要移植,可以把包下载后,导入工程就可以了。
ssd1306.zip
(19.16 KB)
|
此文章已获得独家原创/原创奖标签,著作权归21ic所有,未经允许禁止转载。
|