Hi3061M开发板初测2——IIC通信-OLED(两种实现方法)
目录
- 前言
- 案例源码
- 我的实现
- 测试
- OLED点亮
- 踩坑与调试
- 吐槽下
- 测试问题
- 接线错误
- 从地址错误
- 另一种方法---原始库函数
前言
测试过环境的搭建,IO口的基本配置和使用后,开始测试IIC通信。
案例源码
IDE中提供IIC的通信案例,将在此基础上进行修改。
下面是代码主要功能注释,案例是阻塞通信的,基于24c64芯片,eeprom,存储芯片
宏定义了设备的读写地址,用到我们的IIC通信模块需要进行对应的修改
#define DEV_24C64_ADDRESS_WRITE 0xA0
#define DEV_24C64_ADDRESS_READ 0xA1
static BASE_StatusType Sample24c64ReadData(unsigned int addr, unsigned char *buffer, unsigned int len)
{BASE_StatusType ret = BASE_STATUS_OK;/* Define variables for internal use. */unsigned char tempAddr[I2C_SAMPLE_24C64_ADDR_SIZE];unsigned int currentLen = len;unsigned int currentAddr = addr;unsigned int tempReadLen;unsigned char *tempBuffer = buffer;/* Start read data from the 24c64 eeprom. */while (1) {if (currentLen == 0) {break;}/* Set the memory address of eeprom. */tempAddr[0] = (currentAddr >> I2C_SAMPLE_24C64_ADDRESS_POS) & I2C_SAMPLE_24C64_ADDRESS_MASK;tempAddr[1] = currentAddr & I2C_SAMPLE_24C64_ADDRESS_MASK;//这里就是将16位的地址分为两个8位,用于后面发送,而我们一般的其他IIC往往是8位的寄存器地址tempReadLen = currentLen;if (currentLen > I2C_SAMPLE_24C64_OPT_ONCE_LEN) {tempReadLen = I2C_SAMPLE_24C64_OPT_ONCE_LEN;}//这里控制一次读取的长度,限制最多255/* Send the memory address of eeprom. */ret = HAL_I2C_MasterWriteBlocking(&g_i2c0, DEV_24C64_ADDRESS_WRITE, tempAddr,I2C_SAMPLE_24C64_ADDR_SIZE, I2C_SAMPLE_MAX_TIMEOUT);//调用hal库的主机阻塞写函数,输入参数位,IIC接口,从设备地址,写数据(这里是地址),写数据长度(多少字节),超时设定,,默认即可//这里是一个读函数,所以需要告诉从设备读的哪个地址,需要写操作if (ret != BASE_STATUS_OK) {DBG_PRINTF("LINE:%d,Read Data Fail,ret:%d\r\n", __LINE__, ret);return ret;}/* Read data from eeprom. */ret = HAL_I2C_MasterReadBlocking(&g_i2c0, DEV_24C64_ADDRESS_READ, tempBuffer,tempReadLen, I2C_SAMPLE_MAX_TIMEOUT);//具体的读实现函数,参数,IIC接口,从设备地址读地址,读存储数组、读长度,超时if (ret != BASE_STATUS_OK) {DBG_PRINTF("LINE:%d,Read Data Fail,ret:%d\r\n", __LINE__, ret);return ret;}/* Updata the destAddress, srcAddress and len. */currentAddr += tempReadLen;currentLen -= tempReadLen;tempBuffer += tempReadLen;//超过了一次读取长度255,分多次读取进行拼接}return ret;
}
另外介绍的是读写函数的具体实现,原理步骤都是一致的,看写函数
ret = HAL_I2C_MasterWriteBlocking(&g_i2c0, DEV_24C64_ADDRESS_WRITE, tempAddr,I2C_SAMPLE_24C64_ADDR_SIZE, I2C_SAMPLE_MAX_TIMEOUT);
ret = HAL_I2C_MasterReadBlocking(&g_i2c0, DEV_24C64_ADDRESS_READ, tempBuffer,tempReadLen, I2C_SAMPLE_MAX_TIMEOUT);
这个里面的实现代码就不贴了,就是按IIC时序进行,不过是多加了一些处理,比如判断总线是否占用,然后就是先发送从设备地址,起始信号,读写数据, 终止信号,都封装好了的,不过有些参数确实有时候看不明白,功力太浅,能用就行吧。好像最后的实现是将数据传入读写FIFO,一次一字节。
static BASE_StatusType SetTxFIFODataAndCmd(I2C_Handle *handle, I2C_CmdType cmd, unsigned char data)
{BASE_StatusType ret;unsigned int temp;ret = WaitStatusReady(handle, TX_FIFO_NOT_FULL, I2C_OPERATION_WRITE);if (ret != BASE_STATUS_OK) {return ret;}/* The 8 to 11 bits are the Timing Commands, and the 0 to 7 bits are the write data. */temp = (((unsigned int)cmd << I2C_TXFIFO_CMD_POS) & I2C_TXFIFO_CMD_MASK);temp |= (((unsigned int)data << I2C_TXFIFO_WDATA_POS) & I2C_TXFIFO_WDATA_MASK);handle->baseAddress->I2C_TX_FIFO.reg = temp; /* Sets the data and commands to be sent. */return BASE_STATUS_OK;
}
我的实现
源码是基于24c64芯片芯片的,虽然都是IIC通信,但是没法直接一起来用,毕竟寄存器地址范围不同,一般的IIC传感器之类的设备,就8位地址,尤其是命令类的,一次一字节传输,所以需要进行一些简单的修改简化。
BASE_StatusType IIC_my_ReadData(unsigned int addr, unsigned char *buffer, unsigned int len)
{BASE_StatusType ret = BASE_STATUS_OK;unsigned char tempAddr[I2C_SAMPLE_OLED_ADDR_SIZE];/* Set the memory address of eeprom. */tempAddr[0] = addr;ret = HAL_I2C_MasterWriteBlocking(&g_i2c0, IIC_WRITE, tempAddr,I2C_SAMPLE_OLED_ADDR_SIZE, I2C_SAMPLE_MAX_TIMEOUT);if (ret != BASE_STATUS_OK) {DBG_PRINTF("LINE:%d,Read Data Fail,ret:%d\r\n", __LINE__, ret);return ret;}/* Read data from eeprom. */ret = HAL_I2C_MasterReadBlocking(&g_i2c0, IIC_READ, buffer,len, I2C_SAMPLE_MAX_TIMEOUT);if (ret != BASE_STATUS_OK) {DBG_PRINTF("LINE:%d,Read Data Fail,ret:%d\r\n", __LINE__, ret);return ret;}return ret;
}// SetTxFIFODataAndCmd 一次8位
BASE_StatusType IIC_my_WriteData(unsigned int addr, unsigned char dat, unsigned int len)
{unsigned char tempWrite[I2C_SAMPLE_OLED_PAGE_SIZE + I2C_SAMPLE_OLED_ADDR_SIZE];//写寄存器地址和存储发送数据 通常1+1 2个字节unsigned int tempWriteLen;BASE_StatusType ret = BASE_STATUS_OK;/* Set the memory address of eeprom. */tempWrite[0] = addr;tempWrite[1] = dat;ret = HAL_I2C_MasterWriteBlocking(&g_i2c0, IIC_WRITE, tempWrite,len + I2C_SAMPLE_OLED_ADDR_SIZE, I2C_SAMPLE_MAX_TIMEOUT);//len + I2C_SAMPLE_OLED_ADDR_SIZE ,写的长度加上写的寄存器地址长度,通常1+1 2个字节if (ret != BASE_STATUS_OK) {DBG_PRINTF("LINE:%d,Write Data Fail!,ret:%d\r\n", __LINE__, ret);} return ret;
}
测试
涉及到IIC的读写,而OLED只有写,所以用一个光强度模块进行了测试,芯片好像是AP3216。
IIC_my_WriteData(0x00,0x03,1);DBG_PRINTF("write ok! \r\n");BASE_FUNC_DELAY_MS(1000);IIC_my_ReadData(0x00,s,1);DBG_PRINTF("read ok! read value 0x0%d\r\n",(int)s[0]);
输出结果
OLED点亮
使用OLED需要进行一系列初始化,还有将原来stm32的代码移植过来使用。其实也简单,就是包装换一下就好。
显示展示:
踩坑与调试
吐槽下
只能说这个东西真的弄了我好久,差点整吐了,一开始代码改好了很快,想测试一下,结果就是超时。
准确来说一开始是找IO口,从提供的文档中翻找,但是他这个开发板为了适应兼容,就文档把IO写的很。。。。。
反正是看的很难受,一方面是mcu板的功能,还有扩展板电机驱动板,还有能接ARDUINO UNO。
虽然但是他这很好,但是文档有点难受,主要还有另一个为了兼容ARDUINO UNO的口还是电机的,IO口做了扩展,就是连接,反正就是电路设计是哪些电阻焊上了,就是对应哪个端口是和这个IO连接的,然后有些没焊的
但是他文档前面的功能引脚的时候写的又不管是焊上没焊上,是直接都写有这个功能的,但事实是 有时候那个引脚可能没接上mcu对上的io口,就比如我找IIC口的时候,这个I2C0_SCL说是j2.4,IO口是GPIO2_0。
但是实际上,默认却是没接的,但是他也写,就这么告诉你,然后你去接那个脚发现更本不行
然后写的不全,这个有那个功能,我使用的,且可以连接的反而不写,这个4_2是IIC0 的SCL口,他不写,巨难受。
最后还是得看原理图,结合开发的IDE的芯片配置器来看
顺便解释下原理图
焊接的是一个0欧的电阻,我也是才知道哈,见识少谅解。主要就是用来连接,NC标号的表示对应的电阻没有焊,当时没有仔细看原理图和实际板子结合去理解其意思,后知后觉。自己的问题。
测试问题
在调试过程中遇到了两个问题,如果有人遇到类似的可以参考。
接线错误
第一个是接线接错了,前面找对应的接线柱找错了,scl接线接错了。
然后软件调试的结果是总线占用,也就是iic还没有开始,起始信号都没有发送就报错结束了。
在BASE_StatusType HAL_I2C_MasterWriteBlocking函数中的下面这个函数中
/* The I2C bus is free. */
ret = handle->baseAddress->I2C_FSM_STAT.BIT.i2c_bus_free;
这个标志会为0,表示总线占用,然后下面函数就报超时
/* Waiting for the i2c bus to be idle. */ret = WaitStatusReady(handle, I2C_BUS_IS_FREE, I2C_SEND_ADDR_STATUS_READ);
从地址错误
前面提到了,每个IIC设备的地址可能是不一样的,比如我这的oled是0x78,AP3216是0x3c。
这个要根据实际地址进行更改。
如果这个地址错误,前面的总线空闲检查、起始信号、从地址发送都会进行且不报错,但是在数据发送的时候会报错。同样是上面函数BASE_StatusType HAL_I2C_MasterWriteBlocking
/* step3 : Send to slave data */ret = BlockingMasterTxDataOptStepNormal(handle);
具体是BlockingMasterTxDataOptStepNormal()–>>SetTxFIFODataAndCmd–>>WaitStatusReady中的下面部分
if (handle->baseAddress->I2C_INTR_RAW.reg & I2C_ERROR_BIT_MASK) {SetErrorHandling(handle);return BASE_STATUS_ERROR;}
返回的报错结果BASE_STATUS_ERROR。
另一种方法—原始库函数
另外一种不用案例中提供的接口hal库,可以直接用标准库,或者说就是把io口设置为正常的IO使用,调节其高低电平,按IIC时序进行通信,就和原来的stm32进行通信时用库函数一样的,把原来的文件复制过来,修改一下,改下电平变化的函数,也可以点亮OLED。
不过这个的io口初始化,可能就要自己定义一下了,通过软件生成的可能不太行,因为要分开定义,自动化生成的还是有点呆。
static void GPIO_Init(void)
{HAL_CRG_IpEnableSet(GPIO4_BASE, IP_CLK_ENABLE);g_gpio4_2.baseAddress = GPIO4;g_gpio4_3.baseAddress = GPIO4;g_gpio4_2.pins = GPIO_PIN_2;HAL_GPIO_Init(&g_gpio4_2);HAL_GPIO_SetDirection(&g_gpio4_2, g_gpio4_2.pins, GPIO_OUTPUT_MODE);HAL_GPIO_SetValue(&g_gpio4_2, g_gpio4_2.pins, GPIO_HIGH_LEVEL);HAL_GPIO_SetIrqType(&g_gpio4_2, g_gpio4_2.pins, GPIO_INT_TYPE_NONE);g_gpio4_3.pins = GPIO_PIN_3;HAL_GPIO_Init(&g_gpio4_3);HAL_GPIO_SetDirection(&g_gpio4_3, g_gpio4_3.pins, GPIO_OUTPUT_MODE);HAL_GPIO_SetValue(&g_gpio4_3, g_gpio4_3.pins, GPIO_HIGH_LEVEL);HAL_GPIO_SetIrqType(&g_gpio4_3, g_gpio4_3.pins, GPIO_INT_TYPE_NONE);return;
}
此外oled.h要进行修改,还有相应的头文件要进行删除,对应的头文件也要加入。主要用到的就是io口高低电平设置变化的函数。
#define OLED_SCLK_Clr() HAL_GPIO_SetValue(&g_gpio4_2, g_gpio4_2.pins, GPIO_LOW_LEVEL)//SCL
#define OLED_SCLK_Set() HAL_GPIO_SetValue(&g_gpio4_2, g_gpio4_2.pins, GPIO_HIGH_LEVEL)#define OLED_SDIN_Clr() HAL_GPIO_SetValue(&g_gpio4_3, g_gpio4_3.pins, GPIO_LOW_LEVEL)//SDA
#define OLED_SDIN_Set() HAL_GPIO_SetValue(&g_gpio4_3, g_gpio4_3.pins, GPIO_HIGH_LEVEL)
以上就是全部内容了,欢迎批评指正,有什么问题欢迎留言讨论。