打印
[AT32L021]

【AT-START-L021测评】I2CDMA驱动OLED

[复制链接]
38|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
lulugl|  楼主 | 2024-11-26 18:13 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 lulugl 于 2024-11-26 19:30 编辑

【前言】
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硬件驱动:
/**
  * [url=home.php?mod=space&uid=247401]@brief[/url]  initializes peripherals used by the i2c.
  * @param  none
  * @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[i]);        //依次发送Data的每一个数据
//        }
//        OLED_I2C_Stop();                                //I2C终止
        i2c_memory_write(&hi2cx, I2C_MEM_ADDR_WIDIH_8, I2C_OLED_ADDRESS, 0x40, Data, Count, I2C_TIMEOUT);
}
到此,屏的驱动就移植好了,我们在主函数中添加测试代码,来验证移动是否成功:

/**
  * [url=home.php?mod=space&uid=247401]@brief[/url]  main function.
  * @param  none
  * @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
依次进行试验,都是可以顺利通过的。
【小结】
厂家提供了非常好的i2c初始化的API,使得我们驱动IIC非常的方便,同时也可以随意的修改数据传送的方式。
值得注意的是,我们需要在接IIC从机时,总线需要使用上拉电阻进行拉高。

22701674597bbd9604.png (427.72 KB )

22701674597bbd9604.png

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

151

主题

723

帖子

9

粉丝