本帖最后由 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硬件驱动:
/**
* [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
依次进行试验,都是可以顺利通过的。【移植开源菜单】
在这样资源比较小的MCU上,使用开源的菜单工具来实现与用户的交互,相比LVGL等图形库还是非常有必要的。
1、在CSDN上有个非常优秀的开源菜单:https://blog.csdn.net/m0_69390033/article/details/143793144?[color=var(--bili-rich-text-link-color)]spm=1001.2014.3001.5501
2、下载到源码后,我把菜单源码添加进工程。
对结这个开源框架非常简单,只需要把按键的放入main进行初始化,同时在按键回调中添加向上向下等的调用就行了。如下图所示:
【执行效果】
长按以后进入二级菜单:
这样菜单就移植好了。
【总结】
厂家提供了非常好的i2c初始化的API,使得我们驱动IIC非常的方便,同时也可以随意的修改数据传送的方式。
值得注意的是,我们需要在接IIC从机时,总线需要使用上拉电阻进行拉高。
通过移植开源的OLED驱动以及开源的菜单,可以轻松实现复杂的应用。
|
|