lulugl 发表于 2024-11-26 18:13

【AT-START-L021测评】I2C驱动OLED并移植开源多级菜单

本帖最后由 lulugl 于 2024-11-27 16:16 编辑

【前言】
AT32L021的外设的拥有2路I2C,2C总线接口处理微控制器和串行I2C总线之间的通信,支持主机和从机模式,最大通信速度为1Mbit/s(增强快速模式)。同时也可以支持DMA高速数据传输模式。
这篇将分享如何使用AT32L021的DMA+IT进行OLED的驱动。
【硬件】
1、AT-START-L021开发板。
2、0.96寸的SSD1306
3、两个4.7K上拉电阻。
【工程搭建】
为了结合前面的评测文章,我这里将采用以前的工程为基础【AT-START-L021测评】GPIO学习以及移植开源按键驱动 - - 21ic电子技术开**坛,继续进行开发。
在BSP库中,官方已经给我们写好了一个i2c的库i2c_application,库文件在\AT32L021_Firmware_Library_V2.0.5\middlewares\i2c_application_library目录下面。
1、把这个库文件添加进工程的bsp分组下面,并把文件路径添加进工程。

2、由于库中的底层硬件初始化是没有写的,是一个weak弱函数,所以需要我们进行硬件的初始化代码实现。
3、移植江科大的OLED驱动,将他的工程也添加进keil工程中

工程中OLED_Data.c是用于OLED显示的字库文件,OLED.c是屏的驱动文件。
4、打开OLED.c,将我们需要对I2C进行配置的代码进行实现,首先定义I2C的驱动GPIO以及复用的配置。
#define I2C_TIMEOUT                      0xFFFFFFF

//#define I2Cx_CLKCTRL                   0xF070F7F7   //10K
//#define I2Cx_CLKCTRL                   0x60F06C6C   //50K
#define I2Cx_CLKCTRL                     0x60F03333   //100K
//#define I2Cx_CLKCTRL                   0x20C02C4F   //200K

#define I2C_OLED_ADDRESS                     0x78

#define I2Cx_PORT                        I2C2
#define I2Cx_CLK                         CRM_I2C2_PERIPH_CLOCK
#define I2Cx_DMA                         DMA1
#define I2Cx_DMA_CLK                     CRM_DMA1_PERIPH_CLOCK

#define I2Cx_SCL_GPIO_CLK                CRM_GPIOB_PERIPH_CLOCK
#define I2Cx_SCL_GPIO_PIN                GPIO_PINS_10
#define I2Cx_SCL_GPIO_PinsSource         GPIO_PINS_SOURCE10
#define I2Cx_SCL_GPIO_PORT               GPIOB
#define I2Cx_SCL_GPIO_MUX                GPIO_MUX_1

#define I2Cx_SDA_GPIO_CLK                CRM_GPIOB_PERIPH_CLOCK
#define I2Cx_SDA_GPIO_PIN                GPIO_PINS_11
#define I2Cx_SDA_GPIO_PinsSource         GPIO_PINS_SOURCE11
#define I2Cx_SDA_GPIO_PORT               GPIOB
#define I2Cx_SDA_GPIO_MUX                GPIO_MUX_1

#define I2Cx_DMA_TX_Channel            DMA1_CHANNEL1
#define I2Cx_DMA_TX_DMAMUX_Channel       FLEX_CHANNEL1
#define I2Cx_DMA_TX_DMAREQ               DMA_FLEXIBLE_I2C2_TX
#define I2Cx_DMA_TX_IRQn               DMA1_Channel1_IRQn

#define I2Cx_DMA_RX_Channel            DMA1_CHANNEL2
#define I2Cx_DMA_RX_DMAMUX_Channel       FLEX_CHANNEL2
#define I2Cx_DMA_RX_DMAREQ               DMA_FLEXIBLE_I2C2_RX
#define I2Cx_DMA_RX_IRQn               DMA1_Channel3_2_IRQn

#define I2Cx_IRQn                        I2C2_IRQn以上宏定义中,首先选择通信速度为100K,定义OLED屏的从机地址为0x78,I2C我们选用I2C,DMA选择DMA1。
I2C SCL选择PB10,SDA选择PB11,从数据手册上查到PB10、PB11的复用为MUX1。

然后透一定义时钟、以及中断等参数。

2、添加底层I2C_DMA硬件驱动:
/**
* @briefinitializes peripherals used by the i2c.
* @paramnone
* @retval none
*/
void i2c_lowlevel_init(i2c_handle_type* hi2c)
{
gpio_init_type gpio_init_structure;

if(hi2c->i2cx == I2Cx_PORT)
{
    /* 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);

    /* 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_NONE;

    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);

    /* configure and enable i2c interrupt */
    nvic_irq_enable(I2Cx_IRQn, 0, 0);

    /* configure and enable i2c dma channel interrupt */
    nvic_irq_enable(I2Cx_DMA_TX_IRQn, 0, 0);
    nvic_irq_enable(I2Cx_DMA_RX_IRQn, 0, 0);

    /* i2c dma tx and rx channels configuration */
    /* enable the dma clock */
    crm_periph_clock_enable(I2Cx_DMA_CLK, TRUE);

    /* i2c dma channel configuration */
    hi2c->dma_tx_channel = I2Cx_DMA_TX_Channel;
    hi2c->dma_rx_channel = I2Cx_DMA_RX_Channel;

    dma_reset(hi2c->dma_tx_channel);
    dma_reset(hi2c->dma_rx_channel);

    hi2c->dma_init_struct.peripheral_base_addr    = (uint32_t)&hi2c->i2cx->txdt;
    hi2c->dma_init_struct.memory_base_addr      = 0;
    hi2c->dma_init_struct.direction               = DMA_DIR_MEMORY_TO_PERIPHERAL;
    hi2c->dma_init_struct.buffer_size             = 0xFFFF;
    hi2c->dma_init_struct.peripheral_inc_enable   = FALSE;
    hi2c->dma_init_struct.memory_inc_enable       = TRUE;
    hi2c->dma_init_struct.peripheral_data_width   = DMA_PERIPHERAL_DATA_WIDTH_BYTE;
    hi2c->dma_init_struct.memory_data_width       = DMA_MEMORY_DATA_WIDTH_BYTE;
    hi2c->dma_init_struct.loop_mode_enable      = FALSE;
    hi2c->dma_init_struct.priority                = DMA_PRIORITY_LOW;

    dma_init(hi2c->dma_tx_channel, &hi2c->dma_init_struct);
    dma_init(hi2c->dma_rx_channel, &hi2c->dma_init_struct);

    dma_flexible_config(DMA1, I2Cx_DMA_TX_DMAMUX_Channel, I2Cx_DMA_TX_DMAREQ);
    dma_flexible_config(DMA1, I2Cx_DMA_RX_DMAMUX_Channel, I2Cx_DMA_RX_DMAREQ);

    /* config i2c */
    i2c_init(hi2c->i2cx, 0x0F, I2Cx_CLKCTRL);

    i2c_own_address1_set(hi2c->i2cx, I2C_ADDRESS_MODE_7BIT, I2C_OLED_ADDRESS);
}
}【OLED驱动修改】
拿到江科大的OLED驱动,他是在STM32下面的驱动,使用的是模拟IIC的,这里我们需要修改的地方,一个是硬件的初始化,另一个是修改OLED发送命令、发送数据两个函数就OK了。
void OLED_GPIO_Init(void)
{
      hi2cx.i2cx = I2Cx_PORT;

/* i2c config */
i2c_config(&hi2cx);
}
1、在初始中,我们只需要先把hi2cx.i2cx定义为I2C2,然后执行i2c_config进行注册就行了。
2、在修改OLED写命令是,我们只需要将原来的函数修改为i2c_memory_write就可以了,
/**
* 函    数:OLED写命令
* 参    数:Command 要写入的命令值,范围:0x00~0xFF
* 返 回 值:无
*/
void OLED_WriteCommand(uint8_t Command)
{
//      OLED_I2C_Start();                              //I2C起始
//      OLED_I2C_SendByte(0x78);                //发送OLED的I2C从机地址
//      OLED_I2C_SendByte(0x00);                //控制字节,给0x00,表示即将写命令
//      OLED_I2C_SendByte(Command);                //写入指定的命令
//      OLED_I2C_Stop();                              //I2C终止
      i2c_memory_write(&hi2cx, I2C_MEM_ADDR_WIDIH_8, I2C_OLED_ADDRESS, 0x00, &Command, 1, I2C_TIMEOUT);
}
在这个函数中,我们写入从机地址后,向OLED写入0x00,然后接着写入Command一个byte,然后数据长度为1。
3、修改OLED写数据,跟上面一们,我们只需要把写入第一个REG写入0x40,然后定入传入数组的地址,以及数据长度就OK
/**
* 函    数:OLED写数据
* 参    数:Data 要写入数据的起始地址
* 参    数:Count 要写入数据的数量
* 返 回 值:无
*/
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
//      uint8_t i;
//      
//      OLED_I2C_Start();                              //I2C起始
//      OLED_I2C_SendByte(0x78);                //发送OLED的I2C从机地址
//      OLED_I2C_SendByte(0x40);                //控制字节,给0x40,表示即将写数量
//      /*循环Count次,进行连续的数据写入*/
//      for (i = 0; i < Count; i ++)
//      {
//                OLED_I2C_SendByte(Data);      //依次发送Data的每一个数据
//      }
//      OLED_I2C_Stop();                              //I2C终止
      i2c_memory_write(&hi2cx, I2C_MEM_ADDR_WIDIH_8, I2C_OLED_ADDRESS, 0x40, Data, Count, I2C_TIMEOUT);
}到此,屏的驱动就移植好了,我们在主函数中添加测试代码,来验证移动是否成功:

/**
* @briefmain function.
* @paramnone
* @retval none
*/
int main(void)
{
      static int tick;
system_clock_config();

at32_board_init();

uart_print_init(115200);
printf("at32 mcu initialize ok.\r\n");
      key_test_sample();
      OLED_Init();
      OLED_ShowString(0, 0, (char *)"AT32L021", OLED_8X16);
      OLED_Update();
while(1)
{
                key_tick();
                delay_ms(1);
                tick++;
                if(tick > g_speed * DELAY)
                {
                        at32_led_toggle(LED2);
                        tick = 0;
                }
}
}【结果验证】
当我把OLED屏接入开发板后,发现屏是驱动不了的,使用逻加分析仪看到只有SDA有波形,但是也不对,SCL是没有形波的。原因是因为我们使用的IIC没有上拉电阻,所以SCL的时钟信号上拉不了,而且SDA的波形也是25M左右的不规则波形。接了使用4.7K的电阻对两个IO进行上拉后,就成功的驱动了。还有就是我们使用的是1.8V的电源供给MCU,所以上拉电阻是缺一不可的。
测试效果如下:

【IIC不同传输方式测试】
由于刚才验证,我们是使用poll的方式,接下来我们修改数据传输为IT,以及DMA模式进行测试。
由于传输命令的字节少,所以我们不需要改变,只修改发送数据的函数:
/**
* 函    数:OLED写数据
* 参    数:Data 要写入数据的起始地址
* 参    数:Count 要写入数据的数量
* 返 回 值:无
*/
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
      i2c_status_type i2c_status;
//      i2c_memory_write(&hi2cx, I2C_MEM_ADDR_WIDIH_8, I2C_OLED_ADDRESS, 0x40, Data, Count, I2C_TIMEOUT);
      i2c_memory_write_int(&hi2cx, I2C_MEM_ADDR_WIDIH_8, I2C_OLED_ADDRESS, 0x40, Data, Count, I2C_TIMEOUT);
      if(i2c_wait_end(&hi2cx, I2C_TIMEOUT) != I2C_OK)
    {
      error_handler(i2c_status);
    }
}下载后,也是可以成功点亮的。
接着把数据发送修改为i2c_memory_write_dma,也是可以成功点亮的:
/**
* 函    数:OLED写数据
* 参    数:Data 要写入数据的起始地址
* 参    数:Count 要写入数据的数量
* 返 回 值:无
*/
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
      i2c_status_type i2c_status;
//      i2c_memory_write(&hi2cx, I2C_MEM_ADDR_WIDIH_8, I2C_OLED_ADDRESS, 0x40, Data, Count, I2C_TIMEOUT);
//      i2c_memory_write_int(&hi2cx, I2C_MEM_ADDR_WIDIH_8, I2C_OLED_ADDRESS, 0x40, Data, Count, I2C_TIMEOUT);
      i2c_memory_write_dma(&hi2cx, I2C_MEM_ADDR_WIDIH_8, I2C_OLED_ADDRESS, 0x40, Data, Count, I2C_TIMEOUT);
      if(i2c_wait_end(&hi2cx, I2C_TIMEOUT) != I2C_OK)
    {
      error_handler(i2c_status);
    }
}
【速率测试】
使用Work Bench修改 400K,以及增强模式的1M:

修改宏定义如下:
//#define I2Cx_CLKCTRL                   0xF070F7F7   //10K
//#define I2Cx_CLKCTRL                   0x60F06C6C   //50K
//#define I2Cx_CLKCTRL                     0x60F03333   //100K
//#define I2Cx_CLKCTRL                   0x20C02C4F   //200K
//#define I2Cx_CLKCTRL                   0x10D01C32   //400K
#define I2Cx_CLKCTRL                     0xD01526       //1M依次进行试验,都是可以顺利通过的。【移植开源菜单】
在这样资源比较小的MCU上,使用开源的菜单工具来实现与用户的交互,相比LVGL等图形库还是非常有必要的。
1、在CSDN上有个非常优秀的开源菜单:https://blog.csdn.net/m0_69390033/article/details/143793144?https://i0.hdslb.com/bfs/reply/9f3ad0659e84c96a711b88dd33f4bc2e945045e0.pngspm=1001.2014.3001.5501
2、下载到源码后,我把菜单源码添加进工程。

对结这个开源框架非常简单,只需要把按键的放入main进行初始化,同时在按键回调中添加向上向下等的调用就行了。如下图所示:

【执行效果】

长按以后进入二级菜单:

这样菜单就移植好了。

【总结】
厂家提供了非常好的i2c初始化的API,使得我们驱动IIC非常的方便,同时也可以随意的修改数据传送的方式。
值得注意的是,我们需要在接IIC从机时,总线需要使用上拉电阻进行拉高。
通过移植开源的OLED驱动以及开源的菜单,可以轻松实现复杂的应用。


bartonalfred 发表于 2024-12-3 07:59

多级菜单提供了直观的用户界面,使得用户可以轻松地导航和选择不同的功能或设置。

uptown 发表于 2024-12-6 19:32

移植开源多级菜单这一策略凸显了卓越的开发效率。

mickit 发表于 2024-12-6 21:04

设计中移植了开源的多级菜单,这增加了系统的交互性和功能性。

dspmana 发表于 2024-12-7 00:05

OLED显示屏相比传统LCD显示屏具有更低的功耗,这使得整个系统更加节能。

mikewalpole 发表于 2024-12-7 03:07

移植开源多级菜单,为用户提供了直观、友好的交互界面,增强了用户体验。

juliestephen 发表于 2024-12-7 06:11

在移植开源多级菜单时,设计者可能需要对菜单的结构、功能、界面等进行定制和优化。

geraldbetty 发表于 2024-12-9 09:39

该设计具有较强的扩展性。通过增加更多的I2C设备或扩展多级菜单的功能,可以轻松地实现更多样化的应用场景。

fengm 发表于 2024-12-9 12:39

开发效率与代码复用性            

lzbf 发表于 2024-12-9 15:40

提供了一个很好的示例,展示了如何在嵌入式系统中实现高效的数据传输和用户交互界面设计。

alvpeg 发表于 2024-12-10 13:01

多级菜单系统,该设计为用户提供了更加直观和友好的操作界面。多级菜单结构使得用户能够方便地浏览和选择各种功能,提高了用户的使用体验。

maudlu 发表于 2024-12-10 16:34

OLED显示屏的刷新率和分辨率可能会影响系统的性能

geraldbetty 发表于 2024-12-10 19:33

I2C协议简单易懂,使得OLED显示屏的集成变得相对容易。同时,开源多级菜单的设计使得用户界面更加友好和易于操作。

belindagraham 发表于 2024-12-10 21:02

在某些情况下,I2C通信可能会受到干扰,这需要在设计时考虑信号完整性,并可能需要添加额外的保护措施。

backlugin 发表于 2024-12-11 17:10

设计易于扩展,可以方便地添加新功能或修改现有功能。

uytyu 发表于 2024-12-11 18:38

设计在硬件和软件方面都表现出了高效和便利性

eefas 发表于 2024-12-11 19:35

将开源多级菜单系统集成到现有的应用程序中可能需要额外的工作

tifmill 发表于 2024-12-11 20:03

多级菜单的应用可以提供更加丰富的用户体验,适合于需要复杂界面和功能控制的场景。

robertesth 发表于 2024-12-11 20:31

可以通过增加上拉电阻、优化布线等方式提高通信稳定性。

10299823 发表于 2024-12-11 20:58

使用I2C接口驱动OLED显示屏,既简化了硬件连接,又提高了数据传输效率。
页: [1] 2
查看完整版本: 【AT-START-L021测评】I2C驱动OLED并移植开源多级菜单