使用国产单片机AT32的硬件I2C的一些心得分享

写在前面的话

我以前很少写过博客,很少提笔创作,包括其他公众平台和短视频平台都很少发表过个人作品和文章。我心中很想把技术探索、项目实操中的那些心得包括自己其他的看法和感悟记录下来,可无奈由于种种原因,计划一次次落空,至今还没写下一个字。
最近,契机源于我着手实操的一个小小的实验 —— 基于 AT32F423 芯片,利用硬件 I2C 点亮了 OLED-I2C-1.3 寸屏幕。过程中收获颇丰,便琢磨着围绕这次经历写上几篇文章,既是复盘总结,也算抛砖引玉。
我知道我的文笔还有很大的进步空间,技术解读上或许也不够深入,所以衷心地希望广大网友、业内前辈以及工程师朋友们留下宝贵的批评与建议。
注:本篇设计到的一些MCU编程基础和硬件基础需要自行去查阅、学习。

想法萌生与遇见的问题

在我以前的开发中,包括我相信很多网友要用I2C的时候,大部分情况还是会选择软件I2C,方便、资料多。但是遇见一些大工程,还有部分高速的场景下,还是会选择硬件I2C,就是调试起来麻烦,基于这些观点我就有了尝试使用硬件I2C的想法,也决定支持国产单片机,于是就拿手上有的AT32F423-START板以及常用的中景园OLED模块尝试了一波。

硬件I2C的配置

使用雅特力AT32他们的配置工具进行辅助配置,可以提高效率,不过仅仅只能作为辅助,具体使用还是要自己配置、修改,包括工程模板强烈建议自己建立。具体的参数和一些配置方法也强烈建议阅读他们的手册。
AT32_WorkBench
我个人只是拿配置工具配置好时钟后,copy到建立的at32f423_clock.c中。
时钟配置
然后进行gpio的配置。此处使用的I2C1,PB6和PB7,根据雅特力官方的I2C应用手册,它们是在MUX4复用器上。
gpio的配置由于I2C使用的总线结构,特别是硬件I2C强烈建议使用复用开漏输出,以免产生线与短路,该模块已经使用了外部的4.7kΩ的上拉电阻,以后遇见其他没有上拉电阻或者外部上拉电阻不合适的模块可以配置内部上拉OLED_GPIO_InitStruce.gpio_mode = GPIO_PULL_UP;
I2C的配置,I2C的速率不建议太快。

(此处踩了坑,后面再讲。)
OLED模块在网上有很多开源的,教程也很多,学了OLED模块后(当然强烈建议自己写一个程序),下载一套OLED的驱动,适当修改后直接调用api函数,根据自己想要的图片文字取模后在main.c里面测试。
注:以下是正确的代码!

void oled_writecommand(uint8_t command)
{
	i2c_status_type I2C_Status;
#ifdef OLED_USE_SW_I2C
	oled_i2c_start();    //I2C initiation,start
	oled_i2c_sendbyte(0x3C);    //The address of the I2C slave sending the OLED
	oled_i2c_sendbyte(0x00);    //The 0x00 indicates a write command
	oled_i2c_sendbyte(command);    //Write the specified command
	oled_i2c_stop();    //I2C termination,stop
#elif defined OLED_USE_HW_I2C
	uint8_t tx_data[2] = {0};
	tx_data[0] = 0x00;
	tx_data[1] = command;
	I2C_Status = i2c_master_transmit(&OLED_I2C, OLED_ADDRESS, tx_data, 2, OLED_I2C_TIMEOUT);
//	if(I2C_Status = I2C_ERR_STEP_2)
//	{
//		led_on();
//	}
#endif
}

void oled_writedata(uint8_t *data, uint8_t count)
{
	i2c_status_type I2C_Status;
	uint8_t i;
#ifdef OLED_USE_SW_I2C
	oled_i2c_start();
	oled_i2c_sendbyte(0x78);
	oled_i2c_sendbyte(0x40);
	for(i = 0; i < count; i++)
	{
	  oled_i2c_sendbyte(data[i]);
  }
	oled_i2c_stop();
#elif  defined(OLED_USE_HW_I2C)
	uint8_t tx_data[count+1];
	tx_data[0] = 0x40;
	for(i = 0; i < count; i++)
	{
		tx_data[i+1] = data[i];
	}
	i2c_master_transmit(&OLED_I2C, OLED_ADDRESS, tx_data, count+1, OLED_I2C_TIMEOUT);
//	if(I2C_Status = I2C_ERR_STEP_2)
//	{
//		led_on();
//	}
#endif
}
int main(void)
{
	system_initialization();
	oled_clear();
  while(1)
  {
		oled_show_num(2,2,12,2,OLED_6X8);
		oled_show_chinese(24,24,"你好,枫华。");
		oled_update();
		//hw_delay_ms(1000);
  }
}

void system_initialization(void)
{
	system_clock_config();
	nvic_config();
	timebase_init_timer3();
	all_gpio_init();
	i2c1_init();
	oled_init();
}

void nvic_config(void)
{
	  nvic_irq_enable(TMR3_GLOBAL_IRQn, 0, 0);
}

遇到问题了

首次调试中不出意外的屏幕没有点亮,不过上面粘贴的当然是正确的代码了。调试过程中首先遇到了SCL和SDA两条线一直都是高的问题,调试了两三天,SCL和SDA也一直都是高。时钟频率的配置、从机地址、数据和命令的写入、GPIO的配置、I2C的配置、I2C发送超时时间都检查一遍,没找到问题所在,屏幕还是不亮,SCL和SDA还是一直高电平。
逻辑分析仪的波形如下图(注:后面逻辑分析仪抓波形都是0号通道是SCL,1号通道是SDA)。

使用板载的AT-Link调试,一直触发I2C_ERR_STEP_2事件。

经查阅网上说法还有询问AI,这个事件是发送中断标志(我也知道发送中断)。

直到一个偶然把OLED初始化写命令之前的延时改长一些,SCL和SDA就有正常的起始信号,于是把问题锁定在软件延时不够也不精确上。于是改成了,系统定时延时,时钟源是Timer3,采用定时器中断,考虑到以后会上FreeRTOS,于是没有用SysTick作为系统定时时钟源,部分代码如下图。


然后在oled_init()这个函数里面,在写命令之前添加上适量延时,一般是200到300个毫秒,目的是先让OLED供电稳定,例如:hw_delay_ms(200)。

问题还没完呢

使用逻辑分析仪,可以产生I2C波形了,但是一直是NACK信号,逻辑分析仪的波形如下图所示。增加I2C模拟滤波、数字滤波也均无济于事。

经过仿真后,发现i2c_config(&HI2C1);结构体初始化配置会将配置好的I2C参数全都初始化,相当于复位,或者说得更清楚点是“清零”。所以在配置完结构体HI2C1.i2cx = I2C1;后立马进行结构体初始化,两者必须连上,其实现在看i2_config这个函数原型也不难发现这个问题。部分代码如下所示。

HI2C1.i2cx = I2C1;
//初始化结构体
i2c_config(&HI2C1);

本以为接下来什么问题都没有了,烧录程序后,发现OLED花屏了。

写命令和数据都没问题的话,无非就是频率没有写对,回想之前使用过AT32_WorkBench工具配置过i2c的频率以及上升沿时间和下降沿时间。

频率是没有问题,上升沿时间和下降沿时间太短、太陡峭,导致从机采样不了,也增加了潜在的干扰,于是就在i2c_init这个api函数内的第三个形参上直接写入频率20000。

i2c_init(I2C1, 15, 20000);

最后的效果

经简单测试后。

作者:LyKenny

物联沃分享整理
物联沃-IOTWORD物联网 » 使用国产单片机AT32的硬件I2C的一些心得分享

发表回复