AT32学习笔记-I2C_DMA.md
# bsp初始化
```c
// 设置中断优先级组,抢占优先级4响应优先级0
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
// 时钟初始化
system_clock_config();
// bsp初始化(串口打印,延迟)
at32_board_init();
```
# 初始化I2C
## 常量定义
```c
// 超时时间
#define I2C_TIMEOUT 0xFFFFFFFF
// I2C速度,主机地址
#define I2Cx_SPEED 100000
#define I2Cx_ADDRESS 0xA0
// I2C端口(I2C1) ,外设时钟CRM_I2C1_PERIPH_CLOCK
#define I2Cx_PORT I2C1
#define I2Cx_CLK CRM_I2C1_PERIPH_CLOCK
// SCL
#define I2Cx_SCL_PIN GPIO_PINS_6
#define I2Cx_SCL_GPIO_PORT GPIOB
#define I2Cx_SCL_GPIO_CLK CRM_GPIOB_PERIPH_CLOCK
// SDA
#define I2Cx_SDA_PIN GPIO_PINS_7
#define I2Cx_SDA_GPIO_PORT GPIOB
#define I2Cx_SDA_GPIO_CLK CRM_GPIOB_PERIPH_CLOCK
// DMA外设时钟,TX通道,TX中断,详见手册
#define I2Cx_DMA_CLK CRM_DMA1_PERIPH_CLOCK
#define I2Cx_DMA_TX_CHANNEL DMA1_CHANNEL6
#define I2Cx_DMA_TX_IRQn DMA1_Channel6_IRQn
// RX通道,RX中断
#define I2Cx_DMA_RX_CHANNEL DMA1_CHANNEL7
#define I2Cx_DMA_RX_IRQn DMA1_Channel7_IRQn
// I2C中断
#define I2Cx_EVT_IRQn I2C1_EVT_IRQn
#define I2Cx_ERR_IRQn I2C1_ERR_IRQn
```
## 初始化
```c
// 创建结构体
i2c_status_type i2c_status;
// 设置为常量中的I2C1
hi2cx.i2cx = I2Cx_PORT;
// 再去调用库文件中的配置
i2c_config(&hi2cx);
```
### 内部实现
会先重置I2C外设,然后再去调用用户自己定义的i2c_lowlevel_init
```c
/**
* @brief i2c peripheral initialization.
* @param hi2c: the handle points to the operation information.
* @retval none.
*/
void i2c_config(i2c_handle_type* hi2c)
{
/* reset i2c peripheral */
i2c_reset(hi2c->i2cx);
/* i2c peripheral initialization */
i2c_lowlevel_init(hi2c);
/* i2c peripheral enable */
i2c_enable(hi2c->i2cx, TRUE);
}
```
## 定义i2c_lowlevel_init函数,初始化
```c
void i2c_lowlevel_init(i2c_handle_type* hi2c)
{
// 先去初始化GPIO
gpio_init_type gpio_initstructure;
// 这里判断一下是不是要初始化我们的I2C外设,这个函数初始化其他的I2C的时候也会调用,所以要判断,I2Cx_PORT为我们设置的常量I2C1
if(hi2c->i2cx == I2Cx_PORT)
{
// 开启I2C外设时钟
/* i2c periph clock enable */
crm_periph_clock_enable(I2Cx_CLK, TRUE);
// 然后开启两个GPIO的时钟
crm_periph_clock_enable(I2Cx_SCL_GPIO_CLK, TRUE);
crm_periph_clock_enable(I2Cx_SDA_GPIO_CLK, TRUE);
// 开始配置GPIO
/* gpio configuration */
// GPIO输出模式,开漏输出
// 可选 GPIO_OUTPUT_PUSH_PULL GPIO_OUTPUT_OPEN_DRAIN
gpio_initstructure.gpio_out_type = GPIO_OUTPUT_OPEN_DRAIN;
// GPIO上拉,开启内部上拉,这样就不用外部上拉电阻了
gpio_initstructure.gpio_pull = GPIO_PULL_UP;
// GPIO模式改为IOMUX,AT32特有,内部会自动处理输入输出模式
gpio_initstructure.gpio_mode = GPIO_MODE_MUX;
// 驱动强度,选择强驱动就行
gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;
// 设置GPIO PIN
/* configure i2c pins: scl */
gpio_initstructure.gpio_pins = I2Cx_SCL_PIN;
// 开始初始化GPIO
gpio_init(I2Cx_SCL_GPIO_PORT, &gpio_initstructure);
/* configure i2c pins: sda */
gpio_initstructure.gpio_pins = I2Cx_SDA_PIN;
// 开始初始化GPIO
gpio_init(I2Cx_SDA_GPIO_PORT, &gpio_initstructure);
// 这里先开启一下DMA的TX RX中断,后面等待发送完成会用到
/* 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);
// 开启DMA外设时钟
/* i2c dma tx and rx channels configuration */
/* enable the dma clock */
crm_periph_clock_enable(I2Cx_DMA_CLK, TRUE);
// 先给I2C设置好DMA通道
hi2c->dma_tx_channel = I2Cx_DMA_TX_CHANNEL;
hi2c->dma_rx_channel = I2Cx_DMA_RX_CHANNEL;
// 然后重置DMA通道
/* i2c dma channel configuration */
dma_reset(hi2c->dma_tx_channel);
dma_reset(hi2c->dma_rx_channel);
// 给DMA初始化结构体
dma_default_para_init(&hi2c->dma_init_struct);
// 外设地址自动增加,关闭
hi2c->dma_init_struct.peripheral_inc_enable = FALSE;
// 内存地址自动增加,开启
hi2c->dma_init_struct.memory_inc_enable = TRUE;
// 外设数据宽度,I2C每次为8位
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的方向,内存至外设
hi2c->dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL;
// 初始化DMA
dma_init(hi2c->dma_tx_channel, &hi2c->dma_init_struct);
dma_init(hi2c->dma_rx_channel, &hi2c->dma_init_struct);
// 初始化I2C
// 第二个参数用于设置在快速模式下(Fast Speed Mode)的时钟线SCL高低电平宽度比值,当第三个参数speed>100000时,该参数有效,当speed<=100000时,该参数无效,此时可以填任意值
// I2C_FSMODE_DUTY_2_1:低电平宽度:低电平宽度=2:1
// I2C_FSMODE_DUTY_16_9:低电平宽度:低电平宽度=16:9
i2c_init(hi2c->i2cx, I2C_FSMODE_DUTY_2_1, I2Cx_SPEED);
// 这个貌似是ARM系列的一个特性,主机和从机是一份代码,要设置我们自己的地址,做主机的时候随意即可
i2c_own_address1_set(hi2c->i2cx, I2C_ADDRESS_MODE_7BIT, I2Cx_ADDRESS);
}
}
```
# 添加中断处理函数
at32f413_int.c
```c
// 一定要加这一段,要不然不能用
#include "i2c_application.h"
extern i2c_handle_type hi2cx;
// 定义常量
#define I2Cx_DMA_TX_IRQHandler DMA1_Channel6_IRQHandler
#define I2Cx_DMA_RX_IRQHandler DMA1_Channel7_IRQHandler
#define I2Cx_EVT_IRQHandler I2C1_EVT_IRQHandler
#define I2Cx_ERR_IRQHandler I2C1_ERR_IRQHandler
// 这里调用了库文件中的irq处理函数,为了配合后面等待传送结束的等待
/**
* @brief this function handles dma interrupt request.
* @param none
* @retval none
*/
void I2Cx_DMA_RX_IRQHandler(void)
{
i2c_dma_rx_irq_handler(&hi2cx);
}
/**
* @brief this function handles dma interrupt request.
* @param none
* @retval none
*/
void I2Cx_DMA_TX_IRQHandler(void)
{
i2c_dma_tx_irq_handler(&hi2cx);
}
```
# DMA发送
## 函数原型
```c
/**
* @brief the master transmits data through dma mode.
* @param hi2c: the handle points to the operation information.
* @param address: slave address.
* @param pdata: data buffer.
* @param size: data size.
* @param timeout: maximum waiting time.
* @retval i2c status.
*/
i2c_status_type i2c_master_transmit_dma(i2c_handle_type* hi2c, uint16_t address, uint8_t* pdata, uint16_t size, uint32_t timeout)
```
## 使用方法
```c
// 先去定义一个I2C_status 变量,这是一个enum
i2c_status_type i2c_status;
// i2c_master_transmit_dma 发送,同时赋值+判断
// 0x44为从机地址,记得左移一位留给控制位设置发送还是接收
if((i2c_status = i2c_master_transmit_dma(&hi2cx, 0x44 << 1, tx_buf1, 2, I2C_TIMEOUT)) != I2C_OK)
{
error_handler(i2c_status);
}
```
```c
// 让DMA发送后,我们要等待发送完成,要不然不知道从机是不是真的处理完了我们发送的数据
/* wait for the communication to end */
if(i2c_wait_end(&hi2cx, I2C_TIMEOUT) != I2C_OK)
{
error_handler(i2c_status);
}
```
# DMA接收
## 函数原型
```c
/**
* @brief the master receive data through dma mode.
* @param hi2c: the handle points to the operation information.
* @param address: slave address.
* @param pdata: data buffer.
* @param size: data size.
* @param timeout: maximum waiting time.
* @retval i2c status.
*/
i2c_status_type i2c_master_receive_dma(i2c_handle_type* hi2c, uint16_t address, uint8_t* pdata, uint16_t size, uint32_t timeout)
```
## 使用方法
```c
// 开始接收,一般我们要先发送I2C命令才能开始接收
if((i2c_status = i2c_master_receive_dma(&hi2cx, 0x44<<1, rx_buf1, 6, I2C_TIMEOUT)) != I2C_OK)
{
error_handler(i2c_status);
}
// 等待接收完成
/* wait for the communication to end */
if(i2c_wait_end(&hi2cx, I2C_TIMEOUT) != I2C_OK)
{
error_handler(i2c_status);
}
```
# DEMO
## SHT30读取
```c
hi2cx.i2cx = I2Cx_PORT;
i2c_config(&hi2cx);
// 0X30,0XA2 首先对芯片重置
uint8_t tx_buf1[2] = {0x30,0xa2};
/* start the request reception process */
if((i2c_status = i2c_master_transmit_dma(&hi2cx, 0x44 << 1, tx_buf1, 2, I2C_TIMEOUT)) != I2C_OK)
{
error_handler(i2c_status);
}
/* wait for the communication to end */
if(i2c_wait_end(&hi2cx, I2C_TIMEOUT) != I2C_OK)
{
error_handler(i2c_status);
}
// 按照数据手册要求去等待初始化完成
delay_ms(10); // RST delay
while(1)
{
// 开始读取,首先去发送读取命令
tx_buf1[0] = 0x24;
tx_buf1[1] = 0x00;
if((i2c_status = i2c_master_transmit_dma(&hi2cx, 0x44 << 1, tx_buf1, 2, I2C_TIMEOUT)) != I2C_OK)
{
error_handler(i2c_status);
}
/* wait for the communication to end */
if(i2c_wait_end(&hi2cx, I2C_TIMEOUT) != I2C_OK)
{
error_handler(i2c_status);
}
// 等待芯片完成温度读取和转换
delay_ms(20);
/* start the request reception process */
uint8_t rx_buf1[6] = {0};
if((i2c_status = i2c_master_receive_dma(&hi2cx, 0x44<<1, rx_buf1, 6, I2C_TIMEOUT)) != I2C_OK)
{
error_handler(i2c_status);
}
/* wait for the communication to end */
if(i2c_wait_end(&hi2cx, I2C_TIMEOUT) != I2C_OK)
{
error_handler(i2c_status);
}
// 打印一下原始值
printf("%X,%X,%X,%X,%X,%X\r\n",rx_buf1[0],rx_buf1[1],rx_buf1[2],rx_buf1[3],rx_buf1[4],rx_buf1[5]);
uint16_t temperature_raw = rx_buf1[0] << 8 | rx_buf1[1];
uint16_t humidity_raw = rx_buf1[3] << 8 | rx_buf1[4];
// 转换
float temperature = (((float)temperature_raw / 65535.0) * 175) - 45;
float humidity = ((float)humidity_raw / 65535.0) * 100;
// 打印温度
printf("temperature = %.2fC,humidity = %.2f%% \r\n",temperature,humidity);
// 延迟
delay_ms(1000);
}
```