[National Technology Low Power Series N32L43x Review] 08. Software and Hardware I2C Driver 1.5-inch 16-color grayscale OLED display
[Copy link]
Overview
The National N32L43x series MCU has a 2-way hardware I2C interface, which provides multi-host functionality and controls all I2C bus-specific timing, protocols, arbitration, and timing. It supports multiple communication modes (up to 1MHz), supports DMA operations, and is compatible with SMBus2.0. The main functions are described as follows:
Multi-host function: The module can be used as both a master device and a slave device;
- I2C master function;
- Generate clock;
- Generate start and stop signals;
- I2C Slave Functionality
- Programmable address detection;
- The I2C interface supports 7-bit or 10-bit addressing and dual slave address response capability in 7-bit slave mode;
- Stop position detection;
- Generate and detect 7-bit/10-bit address and general call;
- Support different communication speeds;
- Standard speed (up to 100 kHz);
- Fast (up to 400 kHz);
- Fast+ (up to 1MHz);
- Transmitter/receiver mode flag;
- Byte sending end mark;
- I2C bus busy flag;
- Arbitration lost in master mode;
- Error in acknowledgment (ACK) after address/data transmission;
- An erroneous start or stop condition was detected;
- Overflow or underflow when stretching the clock function is prohibited;
- 1 interrupt for successful address/data communication;
- 1 interrupt for errors;
- Optional stretched clock function
- DMA to single-byte buffer;
- Configurable PEC (Packet Error Check) generation or checking
- In send mode, the PEC value can be transmitted as the last byte
- PEC error checking for the last received byte
- SMBus 2.0 compliant
- 25 ms clock low timeout delay
- 10 ms master cumulative clock low extension time
- 25 ms slave device cumulative clock low extension time
- Hardware PEC generation/verification with ACK control
- Support Address Resolution Protocol (ARP)
16 Gray OLED
Generally, OLEDs are monochrome. For a 0.96-inch 128*64 pixel OLED, one pixel only needs 1 bit to represent, and its video memory is 1024 bytes. It can easily achieve a smooth effect through the 400kbps communication rate of I2C; but for a 16-gray OLED, one pixel needs 4 bits to represent. The 1.5-inch 16-gray OLED used in this article has a pixel of 128*128, so the video memory needs 8KB of space. If the display is refreshed at a rate of 400kbps, it will be obviously stuck/unsmooth. It happens that the hardware I2C of the National N32L43x series MCU supports the fast+ mode, and the communication rate can reach 1MHz, which fully meets the display refresh requirements of this OLED.
Code Implementation
/* Define to prevent recursive inclusion -------------------------------------*/
#define __OLED_C__
/* Includes ------------------------------------------------------------------*/
#include "OLED.h"
#if ENABLE_OLED
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define sI2C_SCL_PORT GPIOB
#define sI2C_SCL_PIN GPIO_PIN_8
#define sI2C_SDA_PORT GPIOB
#define sI2C_SDA_PIN GPIO_PIN_9
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/* Exported variables --------------------------------------------------------*/
/* Exported function prototypes ----------------------------------------------*/
/*******************************************************************************
* [url=home.php?mod=space&uid=159083]@brief[/url] * @param
* @retval
* [url=home.php?mod=space&uid=1020061]@attention[/url] *******************************************************************************/
void sI2C_Delay(uint32_t t)
{
while(t--);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void sI2C_Init(void)
{
GPIO_InitType GPIO_InitStructure;
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB, ENABLE);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_8 | GPIO_PIN_9;
GPIO_InitStructure.GPIO_Current = GPIO_DC_12mA;
GPIO_InitStructure.GPIO_Slew_Rate = GPIO_Slew_Rate_High;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pull = GPIO_Pull_Up;
GPIO_InitPeripheral(GPIOB, &GPIO_InitStructure);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void sI2C_SDA_IN(void)
{
GPIO_InitType GPIO_InitStructure;
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_9;
GPIO_InitStructure.GPIO_Current = GPIO_DC_12mA;
GPIO_InitStructure.GPIO_Slew_Rate = GPIO_Slew_Rate_High;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Input;
GPIO_InitStructure.GPIO_Pull = GPIO_Pull_Up;
GPIO_InitPeripheral(GPIOB, &GPIO_InitStructure);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void sI2C_SDA_OUT(void)
{
GPIO_InitType GPIO_InitStructure;
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_9;
GPIO_InitStructure.GPIO_Current = GPIO_DC_12mA;
GPIO_InitStructure.GPIO_Slew_Rate = GPIO_Slew_Rate_High;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pull = GPIO_Pull_Up;
GPIO_InitPeripheral(GPIOB, &GPIO_InitStructure);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void sI2C_START(void)
{
sI2C_SDA_OUT();
GPIO_WriteBit(sI2C_SDA_PORT, sI2C_SDA_PIN, Bit_SET); sI2C_Delay(10);
GPIO_WriteBit(sI2C_SCL_PORT, sI2C_SCL_PIN, Bit_SET); sI2C_Delay(10);
GPIO_WriteBit(sI2C_SDA_PORT, sI2C_SDA_PIN, Bit_RESET); sI2C_Delay(10);
GPIO_WriteBit(sI2C_SCL_PORT, sI2C_SCL_PIN, Bit_RESET); sI2C_Delay(10);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void sI2C_STOP(void)
{
sI2C_SDA_OUT();
GPIO_WriteBit(sI2C_SDA_PORT, sI2C_SDA_PIN, Bit_RESET); sI2C_Delay(10);
GPIO_WriteBit(sI2C_SCL_PORT, sI2C_SCL_PIN, Bit_SET); sI2C_Delay(10);
GPIO_WriteBit(sI2C_SDA_PORT, sI2C_SDA_PIN, Bit_SET); sI2C_Delay(10);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
uint8_t sI2C_WaitACK(void)
{
uint32_t Timeout = 0;
GPIO_WriteBit(sI2C_SDA_PORT, sI2C_SDA_PIN, Bit_SET); /* 释放总线 */
sI2C_SDA_IN();
sI2C_Delay(5);
GPIO_WriteBit(sI2C_SCL_PORT, sI2C_SCL_PIN, Bit_SET); sI2C_Delay(5);
while(GPIO_ReadInputDataBit(sI2C_SDA_PORT,sI2C_SDA_PIN))
{
if(Timeout++ > 250)
{
sI2C_STOP(); return 1;
}
}
GPIO_WriteBit(sI2C_SCL_PORT, sI2C_SCL_PIN, Bit_RESET); sI2C_Delay(5);
return 0;
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void sI2C_SendData(uint8_t Data)
{
sI2C_SDA_OUT();
GPIO_WriteBit(sI2C_SCL_PORT, sI2C_SCL_PIN, Bit_RESET);
for(uint8_t i = 0; i < 8; i++)
{
if(Data & (0x80 >> i))
{
GPIO_WriteBit(sI2C_SDA_PORT, sI2C_SDA_PIN, Bit_SET);
}
else
{
GPIO_WriteBit(sI2C_SDA_PORT,sI2C_SDA_PIN, Bit_RESET);
}
sI2C_Delay(5);
GPIO_WriteBit(sI2C_SCL_PORT, sI2C_SCL_PIN, Bit_SET); sI2C_Delay(5);
GPIO_WriteBit(sI2C_SCL_PORT, sI2C_SCL_PIN, Bit_RESET); sI2C_Delay(5);
}
}
#define USE_H_I2C 0
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void OLED_InitI2C(void)
{
#if USE_H_I2C
GPIO_InitType GPIO_InitStructure;
I2C_InitType I2C1_InitStructure;
RCC_EnableAPB1PeriphClk(RCC_APB1_PERIPH_I2C1, ENABLE);
I2C_DeInit(I2C1);
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB | RCC_APB2_PERIPH_AFIO, ENABLE);
/*PB8 -- SCL; PB9 -- SDA*/
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_8 | GPIO_PIN_9;
GPIO_InitStructure.GPIO_Slew_Rate = GPIO_Slew_Rate_High;
GPIO_InitStructure.GPIO_Pull = GPIO_Pull_Up;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Alternate = GPIO_AF4_I2C1;
GPIO_InitPeripheral(GPIOB, &GPIO_InitStructure);
I2C_InitStruct(&I2C1_InitStructure);
I2C1_InitStructure.ClkSpeed = 100000;
I2C1_InitStructure.BusMode = I2C_BUSMODE_I2C;
I2C1_InitStructure.FmDutyCycle = I2C_FMDUTYCYCLE_2;
I2C1_InitStructure.OwnAddr1 = 0xFF;
I2C1_InitStructure.AckEnable = I2C_ACKEN;
I2C1_InitStructure.AddrMode = I2C_ADDR_MODE_7BIT;
I2C_Init(I2C1, &I2C1_InitStructure);
I2C_Enable(I2C1, ENABLE);
#else
sI2C_Init();
#endif
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void OLED_WriteCMD(uint8_t Data)
{
#if USE_H_I2C
while(I2C_GetFlag(I2C1, I2C_FLAG_BUSY));
I2C_GenerateStart(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVT_MASTER_MODE_FLAG));
I2C_SendAddr7bit(I2C1, 0x78, I2C_DIRECTION_SEND);
while(!I2C_CheckEvent(I2C1, I2C_EVT_MASTER_TXMODE_FLAG));
I2C_SendData(I2C1, 0x00);
while (!I2C_CheckEvent(I2C1, I2C_EVT_MASTER_DATA_SENDED));
I2C_SendData(I2C1, Data);
while (!I2C_CheckEvent(I2C1, I2C_EVT_MASTER_DATA_SENDED));
I2C_GenerateStop(I2C1, ENABLE);
#else
sI2C_START();
sI2C_SendData(0x78);
sI2C_WaitACK();
sI2C_SendData(0x00);
sI2C_WaitACK();
sI2C_SendData(Data);
sI2C_WaitACK();
sI2C_STOP();
#endif
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void OLED_WriteDAT(uint8_t Data)
{
#if USE_H_I2C
while(I2C_GetFlag(I2C1, I2C_FLAG_BUSY));
I2C_GenerateStart(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVT_MASTER_MODE_FLAG));
I2C_SendAddr7bit(I2C1, 0x78, I2C_DIRECTION_SEND);
while(!I2C_CheckEvent(I2C1, I2C_EVT_MASTER_TXMODE_FLAG));
I2C_SendData(I2C1, 0x40);
while (!I2C_CheckEvent(I2C1, I2C_EVT_MASTER_DATA_SENDED));
I2C_SendData(I2C1, Data);
while (!I2C_CheckEvent(I2C1, I2C_EVT_MASTER_DATA_SENDED));
I2C_GenerateStop(I2C1, ENABLE);
#else
sI2C_START();
sI2C_SendData(0x78);
sI2C_WaitACK();
sI2C_SendData(0x40);
sI2C_WaitACK();
sI2C_SendData(Data);
sI2C_WaitACK();
sI2C_STOP();
#endif
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void OLED_WriteBuffer(const uint8_t *Buffer, uint32_t Length)
{
#if USE_H_I2C
while(I2C_GetFlag(I2C1, I2C_FLAG_BUSY));
I2C_GenerateStart(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVT_MASTER_MODE_FLAG)); // EV5
I2C_SendAddr7bit(I2C1, 0x78, I2C_DIRECTION_SEND);
while(!I2C_CheckEvent(I2C1, I2C_EVT_MASTER_TXMODE_FLAG)); // EV6
I2C_SendData(I2C1, 0x40);
while(!I2C_CheckEvent(I2C1, I2C_EVT_MASTER_DATA_SENDING)); // EV8
while(Length--)
{
I2C_SendData(I2C1, *Buffer++);
while(!I2C_CheckEvent(I2C1, I2C_EVT_MASTER_DATA_SENDING)); // EV8
}
while(!I2C_CheckEvent(I2C1, I2C_EVT_MASTER_DATA_SENDED)); // EV8-2
I2C_GenerateStop(I2C1, ENABLE);
#else
sI2C_START();
sI2C_SendData(0x78);
sI2C_WaitACK();
sI2C_SendData(0x40);
sI2C_WaitACK();
while(Length--)
{
sI2C_SendData(*Buffer++);
sI2C_WaitACK();
}
sI2C_STOP();
#endif
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void OLED_InitCFG(void)
{
OLED_WriteCMD(0xAE);//关闭显示
OLED_WriteCMD(0x15);//设置列地址
OLED_WriteCMD(0x00);//起始地址00
OLED_WriteCMD(0x3F);//结束列地址3F对应127列,每8列一组
OLED_WriteCMD(0x75);//设置行地址
OLED_WriteCMD(0x00); //起始0
OLED_WriteCMD(0x7F); //结束127
OLED_WriteCMD(0x81);//对比度设置
OLED_WriteCMD(0x80);//1~255;默认0x7F (亮度设置,越大越亮)
OLED_WriteCMD(0xA0);//显存映射
OLED_WriteCMD(0x51);
OLED_WriteCMD(0xA1);//显示起始行地址
OLED_WriteCMD(0x00);
OLED_WriteCMD(0xA2);//显示偏移
OLED_WriteCMD(0x00);
OLED_WriteCMD(0xA4);//正常显示模式
OLED_WriteCMD(0xA8);//设置MUX 比率 16-128
OLED_WriteCMD(0x7F);
OLED_WriteCMD(0xB1);// Set phase length
OLED_WriteCMD(0xF1);
OLED_WriteCMD(0xB3); // Set Display Clock Divide Ratio/Oscillator Frequency
OLED_WriteCMD(0x00); // 80Hz:0xc1 90Hz:0xe1 100Hz:0x00 110Hz:0x30 // 120Hz:0x50 130Hz:0x70
OLED_WriteCMD(0xAB);
OLED_WriteCMD(0x01);// set vdd internal
OLED_WriteCMD(0xB6); // Set second pre-charge period
OLED_WriteCMD(0x0F);
OLED_WriteCMD(0xBE);// set VCOMH
OLED_WriteCMD(0x0F);
OLED_WriteCMD(0xBC);// set pre_charge voltage/VCOMH
OLED_WriteCMD(0x08);
OLED_WriteCMD(0xD5);// second precharge and VSL
OLED_WriteCMD(0x62);
OLED_WriteCMD(0xFD);// Unlock/Lock OLED driver IC MCU interface from entering command
OLED_WriteCMD(0x12);
OLED_WriteCMD(0xAF);//开启显示
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void OLED_SetWindow(uint8_t StartX, uint8_t StartY, uint8_t EndX, uint8_t EndY)
{
OLED_WriteCMD(0x15);
OLED_WriteCMD(StartX/2);
OLED_WriteCMD(EndX/2-1);
OLED_WriteCMD(0x75);
OLED_WriteCMD(StartY);
OLED_WriteCMD(EndY-1);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void OLED_Clear(uint8_t Data)
{
OLED_SetWindow(0, 0, 128, 128);
for(uint32_t i = 0; i < OLED_HEIGHT*OLED_WIDTH/2; i++)
{
OLED_WriteDAT(Data);
}
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void OLED_Init(void)
{
OLED_InitI2C();
OLED_InitCFG();
OLED_Clear(0xFF);
OLED_SetWindow(0, 0, 128, 128);
OLED_WriteBuffer(gImage_AAA, sizeof(gImage_AAA));
}
Display Effect
Question Feedback
The above code uses two I2C driving modes, software and hardware, to drive the OLED display. For the software driving mode, the execution consumes a lot more resources than the hardware I2C, but it can successfully drive the OLED display, which shows that there is no problem with the upper-level functional code for the parameter configuration and refresh display of the OLED. However, it was not successful when driving the display through the hardware I2C mode. During this period, I tried to modify the I2C communication rate through software and weld pull-up resistors of different resistance values on the hardware. The hardware I2C has never communicated successfully. Through online single-step DEBUG debugging, it was found that after sending some data, it would be stuck in the waiting state of generating the START signal, as shown in the figure below; if conditions permit, I hope the original manufacturer can debug it together to see what exactly caused this phenomenon.
|