GD32F427学习记录
layout: post
title: GD32F427学习记录
description: GD32F427学习记录
categories:
- GD32
tags: - GD32
- USART
I2C
GD32F427学习记录
一、时钟控制单元(CCTL)
1.简介
时钟控制单元提供了一系列频率的时钟功能,包括一个内部16M RC振荡器时钟(IRC16M)、一个内部48M RC振荡器时钟(IRC48M)、一个外部高速晶体振荡器时钟(HXTAL)、一个内部32K RC振荡器时钟(IRC32K)、一个外部低速晶体振荡器时钟(LXTAL)、三个锁相环(PLL)、一个HXTAL时钟监视器、时钟预分频器、时钟多路复用器和时钟门控电路。
2.时钟树
AHB: 240 MHz
APB2: 120MHz
APB1: 60MHz
RCU 通过 AHB 时钟(HCLK)8 分频后作为 Cortex 系统定时器(SysTick)的外部时钟
ADC时钟由APB2时钟经2、4、6、8分频或由AHB时钟经5、6、10、20分频获得
TIMER时钟由AHB时钟分频获得,它的频率可以等于CK_APBx、CK_APBx的两倍或CK_APBx的四倍
USBFS/USBHS/TRNG/SDIO的时钟由CK48M时钟提供
可以选择PLLQ时钟、PLLSAIP时钟或IRC48M时钟做为CK48M的时钟源
CTC时钟由IRC48M时钟提供
I2S时钟可以选择由PLLI2SR时钟或外部I2S_CKIN引脚输入时钟提供
TLI时钟可以选择由PLLSAIR时钟的2、4、8、16分频提供
以太网TX/RX时钟可以选择由外部引脚(ENET_TX_CLK / ENET_RX_CLK)输入时钟提供
RTC时钟可以选择由LXTAL时钟、IRC32K时钟或HXTAL时钟的2-31(由RCU_CFG0寄存器的RTCDIV位域值决定)分频提供
当FWDG启动时,FWDG时钟被强制选择由IRC32K时钟做为时钟源
3.时钟源
- 外部高速晶体振荡器
- 内部16 MHz RC振荡器(IRC16M)
- 内部48 MHz RC振荡器(IRC48M)
- 32,768 Hz外部低速晶体振荡器(LXTAL)
- 内部32 KHz RC振荡器(IRC32K)
4.锁相环(PLL)
PLLP:可做为系统时钟
PLLQ:可以做为USBFS/USBHS/TRNG/SDIO模块的时钟源
PLLI2S:可以做为I2S模块的时钟源
PLLSAI:可以做为CK48M或TLI模块的时钟源
5.系统时钟(CK_SYS)选择
系统复位后,IRC16M时钟默认做为CK_SYS的时钟源
二、通用和备用输入/输出接口(GPIO 和 AFIO)
1.简介
最多可支持 140 个通用 I/O 引脚(GPIO),每个 GPIO 引脚可以由软件配置为输出(推挽或开漏)、输入、外设备用功能或者模拟模式。每个 GPIO 引脚都可以配置为上拉、下拉或无上拉/下拉。除模拟模式外,所有的 GPIO 引脚都具备大电流驱动能力。
2.IO端口结构
3.引脚配置
在复位期间或复位之后
PA15:JTDI 为上拉模式
PA14:JTCK / SWCLK 为下拉模式
PA13:JTMS / SWDIO 为上拉模式
PB4:NJTRST 为上拉模式。
PB3:JTDO 为浮空模式。
其他IO口:浮空
IO口配置为输入引脚时,可以选择内部弱上拉和弱下拉
IO口配置位输出引脚时,可以选择配置为开漏或推挽模式
4.中断时间
只有在输入模式下配置,端口才能使用外部中断/事件线
5.备用功能
当端口配置为 AFIO时,每个端口可以配置 16 个备用功能
6.输入结构图
输入时配备了若上拉和下拉,并且有ESD保护
7.输出结构图
8.模拟配置
配置为模拟模式时,弱上拉和下拉电阻禁用
9.GPIO 锁定功能
GPIO 的锁定机制可以保护 I/O 端口的配置
被保护的寄存器有:GPIOx_CTL,GPIOx_OMODE,GPIOx_OSPD,GPIOx_PUD 和GPIOx_AFSELz(z=0,1)。通过配置 32 位锁定寄存器(GPIOx_LOCK)可以锁定 I/O 端口的配置。通过特定的锁定序列配置 GPIOx_LOCK 中的 LKK 位和 LKy 位,相应的端口位被锁定,直到下一个复位前,相应端口位的配置都不能修改。建议在电源驱动模块的配置中使用锁定功能。
10.GPIO 单周期输出翻转功能
GPIO 可以在一个 AHB 时钟周期内翻转 I/O 的输出电平。输出信号的频率可以达到 AHB 时钟的一半
11.GPIO基地址
| IO口 | 基地址 |
| —– | ———– |
| GPIOA | 0x4002 0000 |
| GPIOB | 0x4002 0400 |
| GPIOC | 0x4002 0800 |
| GPIOD | 0x4002 0C00 |
| GPIOE | 0x4002 1000 |
| GPIOF | 0x4002 1400 |
| GPIOG | 0x4002 1800 |
| GPIOH | 0x4002 1C00 |
| GPIOI | 0x4002 2000 |
12.各寄存器及地址偏移
| 寄存器 | 寄存器功能 | 偏移 | 复位值 |
| ———— | ——————– | —- | ———————————————————— |
| GPIOx_CTL | 端口控制寄存器 | 0x00 | 端口 A 0xA800 0000;端口 B 0x0000 0280;其他端口 0x0000 0000 |
| GPIOx_OMODE | 端口输出模式寄存器 | 0x04 | 0x0000 0000 |
| GPIOx_OSPD | 端口输出速度寄存器 | 0x08 | 端口 A 0x0C00 0000;端口 B 0x0000 00C0;其他端口 0x0000 0000 |
| GPIOx_PUD | 端口上拉/下拉寄存器 | 0x0C | 端口 A 0x6400 0000;端口 B 0x0000 0100;其他端口 0x0000 0000 |
| GPIOx_ISTAT | 端口输入状态寄存器 | 0x10 | 0x0000 XXXX |
| GPIOx_OCTL | 端口输出控制寄存器 | 0x14 | 0x0000 0000 |
| GPIOx_BOP | 端口位操作寄存器 | 0x18 | 0x0000 0000 |
| GPIOx_LOCK | 端口配置锁定寄存器 | 0x1C | 0x0000 0000 |
| GPIOx_AFSEL0 | 备用功能选择寄存器 0 | 0x20 | 0x0000 0000 |
| GPIOx_AFSEL1 | 备用功能选择寄存器 1 | 0x24 | 0x0000 0000 |
| GPIOx_BC | 位清除寄存器 | 0x28 | 0x0000 0000 |
| GPIOx_TG | 端口位翻转寄存器 | 0x2C | 0x0000 0000 |
13.初始化例程
13.1 初始化LED输出例程
void led_init(void)
{
rcu_periph_clock_enable(RCU_GPIOE); // 初始化GPIOE时钟
gpio_mode_set(GPIOE, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_2); // 初始化PE2为浮空输出
gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2); // 初始化PE2为推挽输出,速度为50MHz
GPIO_BC(GPIOE) = GPIO_PIN_2; // PE2复位
}
13.2 初始化按键输入例程
void key_init(void)
{
rcu_periph_clock_enable(RCU_GPIOA); // 初始化GPIOA时钟
rcu_periph_clock_enable(RCU_SYSCFG); // 初始化SYSCFG时钟
gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_0); // 设置PA0为浮空输入
nvic_irq_enable(EXTI0_IRQn, 2U, 0U); // 配置外部中断0中断优先级
syscfg_exti_line_config(EXTI_SOURCE_GPIOA, EXTI_SOURCE_PIN0); // 配置GPIO口对应的外部中断口
exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_FALLING); // 配置外部中断0触发方式为下降沿
exti_interrupt_flag_clear(EXTI_0); // 清除EXTI线0中断标志位
}
14.GPIO库函数汇总
| 库函数名称 | 库函数名称 |
| ———————– | ———————- |
| gpio_deinit | 复位外设GPIOx |
| gpio_mode_set | 设置GPIO模式 |
| gpio_output_options_set | 设置GPIO输出模式和速度 |
| gpio_bit_set | 置位引脚值 |
| gpio_bit_reset | 复位引脚值 |
| gpio_bit_write | 将特定的值写入引脚 |
| gpio_port_write | 将特定的值写入一组端口 |
| gpio_input_bit_get | 获取引脚的输入值 |
| gpio_input_port_get | 获取一组端口的输入值 |
| gpio_output_bit_get | 获取引脚的输出值 |
| gpio_output_port_get | 获取一组端口的输出值 |
| gpio_af_set | 设置GPIO复用功能 |
| gpio_pin_lock | 相应的引脚配置被锁定 |
| gpio_bit_toggle | 翻转GPIO引脚状态 |
| gpio_port_toggle | 翻转一组GPIO状态 |
三、通用同步异步收发器(USART)
1.简介
数据帧可以通过全双工或半双工,同步或异步的方式进行传输,可支持红外编码规范,SIR,智能卡协议,LIN,以及同步单双工模式,支持多处理器通信和Modem流控操作(CTS/RTS)。数据帧支持从LSB或者MSB开始传输。支持DMA传输。
多种状态标志
- 接收缓冲区不为空(RBNE),发送缓冲区为空(TBE),传输完成(TC),忙(BSY)
- 过载错误(ORERR),噪声错误(NERR),帧格式错误(FERR),奇偶校验错误(PERR)
- 硬件流控操作标志:CTS变化(CTSF)
- LIN模式标志:LIN断开检测(LBDF)
- 多处理器通信模式标志:IDLE帧检测(IDLEF)
- 智能卡模式标志:块结束(EBF)和接收超时(RTF)
- 若相应的中断使能,这些事件发生将会触发中断
USART0/1/2/5完全实现上述功能,但是UART3/4/6/7只实现了上面所介绍功能的部分
2.内部框图
3.时钟源
USART0/5的系统时钟为PCLK2,USART1/2和UART3/4/6/7的系统时钟为PCLK1。在使能USART之前,必须在时钟控制单元使能系统时钟。
PCLK1的时钟来源于APB1,APB2最高频率为120MHz
PCLK2的时钟来源于APB2,APB2最高频率为60MHz
APB1和APB2时钟均由AHB总线分频获得
4.USART发送步骤
在USART_CTL0寄存器中置位UEN位,使能USART;
通过USART_CTL0寄存器的WL设置字长;
在USART_CTL1寄存器中写STB[1:0]位来设置停止位的长度;
如果选择了多级缓存通信方式,应该在USART_CTL2寄存器中使能DMA (DENT位);
在USART_BAUD寄存器中设置波特率;
在USART_CTL0寄存器中设置TEN位;
等待TBE置位;
向USART_DATA寄存器写数据;
若DMA未使能,每发送一个字节都需重复步骤7-8;
等待TC=1,发送完成
5.USART接收步骤
- 在USART_CTL0寄存器中置位UEN位,使能USART;
- 写USART_CTL0寄存器的WL去设置字长;
- 在USART_CTL1寄存器中写STB[1:0]位来设置停止位的长度;
- 如果选择了多级缓存通信方式,应该在USART_CTL2寄存器中使能DMA(DENR位);
- 在USART_BAUD寄存器中设置波特率;
- 在USART_CTL0中设置REN位
6.DMA 方式访问数据缓冲区
6.1 DMA发送
- 将USART_STAT0中TC清0
- 将 USART_DATA的地址设置为 DMA目的地址
- 将存放数据的片内SRAM地址设置为DMA源地址
- 将要传输的数据字节数设置为DMA 传输字节数
- DMA其他设置,中断使能,校验位设置等
- 使能用于USART的DMA通道
- 等待TC置位
6.2 DMA接收
- 将USART_DATA的地址设置为DMA源地址
- 将存放数据的片内SRAM地址设置为DMA目的地址
- 将要传输的数据字节数设置为DMA 传输字节数
- DMA其他设置,中断使能,校验位设置等
- 使能用于USART的DMA通道
7.硬件控制流
7.1 RTS流控
USART接收器输出nRTS,它用于反映接收缓冲区状态。当一帧数据接收完成,nRTS变成高电平,这样是为了阻止发送器继续发送下一帧数据。当接收缓冲区满时,nRTS保持高电平,可以通过读USART_DATA寄存器来清零。
7.2 CTS流控
USART发送器监视nCTS输入引脚来决定数据帧是否可以发送。如果USART_STAT0寄存器中TBE位是0且nCTS为低电平,发送器发送数据帧。在发送期间,若nCTS信号变为高电平,发送器将会在当前数据帧发送完成后停止发送。
7.3 硬件流控制图
8.多处理器通信
USART可以进入静默模式,通过空闲总线检测和地址掩码检测唤醒。
9.同步通信模式
USART可以使用额外的一根同步线作为时钟频率线,采用同步模式时,接收器在时钟捕获沿采样数据,并无任何过采样。
10.串行红外(IrDA SIR)编解码功能模块
在IrDA模式下,USART数据帧由SIR发送编码器进行调制,调制后的信号经由红外LED进行发送,经解调后将数据发送至USART接收器。对于编码器而言,波特率应小于115200。
11.半双工工作模式
在半双工通信模式下,USART_CTL1寄存器的LMEN,CKEN位和USART_CTL2寄存器的SCEN,IREN位清零。
半双工模式下,TX引脚和RX引脚将从内部连接到一起,RX引脚不再使用。TX引脚应该被配置为开漏输出模式。通信冲突由软件处理。
12.USART 中断
| 中断事件 | 事件标志 | 控制寄存器 | 使能控制位 |
| ————————————————— | ——————— | ———- | ———- |
| 发送数据寄存器空 | TBE | USART_CTL0 | TBEIE |
| CTS 标志 | CTSF | USART_CTL2 | CTSIE |
| 发送结束 | TC | USART_CTL0 | TCIE |
| 接收到的数据可以读取 | RBNE | USART_CTL0 | RBNEIE |
| 检测到过载错误 | ORERR | USART_CTL0 | RBNEIE |
| 检测到线路空闲 | IDLEF | USART_CTL0 | IDLEIE |
| 奇偶校验错误 | PERR | USART_CTL0 | PERRIE |
| LIN模式下,检测到断开标志 | LBDF | USART_CTL1 | LBDIE |
| 接收超时错误 | RTF | USART_CTL3 | RTIE |
| 发现块尾 | EBF | USART_CTL3 | EBIE |
| 接收错误(噪声错误、溢出错误、帧错误)当DMA接收使能时 | NERR or ORERR or FERR | USART_CTL2 | ERRIE |
在任何时候 USART 只能向控制器产生一个中断请求,但是软件可以在一个中断服务程序里处理多个中断事件
13.USART 寄存器
| 寄存器 | 寄存器功能 | 偏移 | 复位值 |
| ———– | ———————— | —- | ————— |
| USART_STAT0 | 状态寄存器 0 | 0x00 | 0x0000 000000C0 |
| USART_DATA | 数据寄存器 | 0x04 | 未定义 |
| USART_BAUD | 波特率寄存器 | 0x08 | 0x0000 0000 |
| USART_CTL0 | 控制寄存器 0 | 0x0C | 0x0000 0000 |
| USART_CTL1 | 控制寄存器 1 | 0x10 | 0x0000 0000 |
| USART_CTL2 | 控制寄存器 2 | 0x14 | 0x0000 0000 |
| USART_GP | 保护时间和预分频器寄存器 | 0x18 | 0x0000 0000 |
| USART_CTL3 | 控制寄存器 3 | 0x80 | 0x0000 0000 |
| USART_RT | 接收超时寄存器 | 0x84 | 0x0000 0000 |
| USART_STAT1 | 状态寄存器 1 | 0x88 | 0x0000 0000 |
| USART_CHC | 兼容性控制寄存器 | 0xC0 | 0x0000 0000 |
14.初始化例程
14.1 串口0初始化例程
void usart0_init(void)
{
rcu_periph_clock_enable(RCU_GPIOA); // 初始化GPIOA时钟
rcu_periph_clock_enable(RCU_USART0); // 初始化USART0时钟
gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_9); // 配置PA9复用为TX
gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_10); // 配置PA10复用为RX
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_9); // 配置PA9为复用输出,上拉
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); // 配置PA9为推挽输出,速度50MHz
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_10); // 配置PA10为复用输出,上拉
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10); // 配置PA10为推挽输出,速度50MHz
usart_deinit(USART0); // 重置USART0
usart_baudrate_set(USART0, 115200U); // 配置USART0 波特率为115200
usart_receive_config(USART0, USART_RECEIVE_ENABLE); // 使能串口接收
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE); // 使能串口发送
usart_enable(USART0); // 使能USART0
}
14.2 串口接收中断例程
void USART0_IRQHandler(void)
{
// 检测串口接收中断
if ((RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)) &&
(RESET != usart_flag_get(USART0, USART_FLAG_RBNE)))
{
rxbuffer[rxcount++] = (usart_data_receive(USART0) & 0x7F); // 接收串口数据
}
// 检测串口空闲中断
if ((RESET != usart_flag_get(USART0, USART_FLAG_TBE)) &&
(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_TBE)))
{
// 将接收的数据发送出去
usart_data_transmit(USART0, txbuffer[txcount++]);
if (txcount >= rxcount)
{
usart_interrupt_disable(USART0, USART_INT_TBE); // 所有数据发送完成后,关闭串口空闲中断
}
}
}
14.3 串口DMA初始化
void usart0_dma_init(void)
{
dma_single_data_parameter_struct dma_init_struct; // 初始化DMA结构体
rcu_periph_clock_enable(RCU_DMA1); // 初始化DMA1时钟
dma_deinit(DMA1, DMA_CH7); // 复位DMA1 通道7
dma_init_struct.direction = DMA_MEMORY_TO_PERIPH; // 选择从内存到外设,用于DMA串口发送
dma_init_struct.memory0_addr = (uint32_t)txbuffer; // 配置发送内存地址
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 打开DMA地址自增
dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; // DMA传输带宽为8位
dma_init_struct.number = buffer_size; // 配置DMA传输数据的大小
dma_init_struct.periph_addr = (uint32_t)&USART_DATA(USART0); // 配置外设地址
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 禁止外设地址自增
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; // 超高优先级
dma_single_data_mode_init(DMA1, DMA_CH7, &dma_init_struct); // 初始化DMA1通道7数据模式
dma_circulation_disable(DMA1, DMA_CH7); // 禁止DMA循环模式
dma_channel_subperipheral_select(DMA1, DMA_CH7, DMA_SUBPERI4); // DMA通道外设选择
dma_channel_enable(DMA1, DMA_CH7); // 使能DMA
}
此处的初始化仅为DMA发送的初始化程序,接收部分选择方向与发送相反,即从外设到内存
14.4 串口DMA发送
void usart0_dma_send(uint8_t *buffer, uint16_t len)
{
dma_channel_disable(DMA1, DMA_CH7);
dma_memory_address_config(DMA1, DMA_CH7, (uint32_t)buffer); // 设置要发送数据的内存地址
dma_transfer_number_config(DMA1, DMA_CH7, len); // 一共发多少个数据
dma_channel_enable(DMA1, DMA_CH7); // 使能DMA通道
usart_dma_transmit_config(USART0, USART_DENT_ENABLE); // 使能串口DMA发送
}
串口DMA接收可通过DMA半满、全满中断或串口空闲中断获取DMA的接收状态信息,然后直接对存储地址进行操作即可
14.5 printf函数重写
int fputc(int ch, FILE *f)
{
while (RESET == usart_flag_get(USART0, USART_FLAG_TBE)); // 等待上一次发送完成
usart_data_transmit(USART0, (uint32_t)ch); // 串口0发送
return ch;
}
14.6 设置串口接收超时
usart_receiver_timeout_enable(USART0); // 使能串口接收超时
usart_receiver_timeout_threshold_config(USART0, 115200*3); // 配置串口接收超时阈值
15.库函数汇总
| 库函数名称 | 库函数描述 |
| —————————————- | ———————————————————— |
| usart_deinit | 复位外设USART |
| usart_baudrate_set | 配置USART波特率 |
| usart_parity_config | 配置USART奇偶校验 |
| usart_word_length_set | 配置USART字长 |
| usart_stop_bit_set | 配置USART停止位 |
| usart_enable | 使能USART |
| usart_disable | 失能USART |
| usart_transmit_config | USART发送配置 |
| usart_receive_config | USART接收配置 |
| usart_data_first_config | 配置数据传输时低位在前或高位在前 |
| usart_invert_config | 配置USART反转功能 |
| usart_overrun_enable | 使能USART溢出禁止功能 |
| usart_overrun_disable | 失能USART溢出禁止功能 |
| usart_oversample_config | 配置USART过采样模式 |
| usart_sample_bit_config | 配置USART单次采样方式 |
| usart_receiver_timeout_enable | 使能USART接收超时 |
| usart_receiver_timeout_disable | 失能USART接收超时 |
| usart_receiver_timeout_threshold_con fig | 设置USART接收超时阈值 |
| usart_data_transmit | USART发送数据功能 |
| usart_data_receive | USART接收数据功能 |
| usart_autobaud_detection_enable | 使能USART自动波特率检测 |
| usart_autobaud_detection_disable | 失能USART自动波特率检测 |
| usart_autobaud_detection_mode_con fig | 配置USART自动波特率检测模式 |
| usart_address_config | 在地址掩码唤醒模式下配置USART地址 |
| usart_address_detection_mode_confi g | 配置USART地址检测模式 |
| usart_mute_mode_enable | 使能USART静默模式 |
| usart_mute_mode_disable | 失能USART静默模式 |
| usart_mute_mode_wakeup_config | 配置USART静默模式唤醒方式 |
| usart_lin_mode_enable | 使能USART LIN模式 |
| usart_lin_mode_disable | 失能USART LIN模式 |
| usart_lin_break_detection_length_con fig | 配置USART LIN模式中断帧长度 |
| usart_halfduplex_enable | 使能USART半双工模式 |
| usart_halfduplex_disable | 失能USART半双工模式 |
| usart_clock_enable | 使能USART CK引脚 |
| usart_clock_disable | 失能USART CK引脚 |
| usart_synchronous_clock_config | 配置USART同步通讯模式参数 |
| usart_guard_time_config | 在USART智能卡模式下配置保护时间值 |
| usart_smartcard_mode_enable | 使能USART智能卡模式 |
| usart_smartcard_mode_disable | 失能USART智能卡模式 |
| usart_smartcard_mode_nack_enable | 在USART智能卡模式下使能NACK |
| usart_smartcard_mode_nack_disable | 在USART智能卡模式下失能NACK |
| usart_smartcard_mode_early_nack_e nable | 使能USART智能卡模式提前NACK |
| usart_smartcard_mode_early_nack_di sable | 失能USART智能卡模式提前NACK |
| usart_smartcard_autoretry_config | 配置智能卡自动重试次数 |
| usart_block_length_config | 配置智能卡T=1的接收时块的长度 |
| usart_irda_mode_enable | 使能USART串行红外编解码功能模块 |
| usart_irda_mode_disable | 失能USART串行红外编解码功能模块 |
| usart_prescaler_config | 在USART IrDA低功耗模式下或者SmartCard模式配置外设时 钟分频系数 |
| usart_irda_lowpower_config | 配置USART IrDA低功耗模式 |
| usart_hardware_flow_rts_config | 配置USART RTS硬件控制流 |
| usart_hardware_flow_cts_config | 配置USART CTS硬件控制流 |
| usart_rs485_driver_enable | 使能USART rs485驱动 |
| usart_rs485_driver_disable | 失能USART rs485驱动 |
| usart_driver_assertime_config | 配置USART驱动使能置位时间 |
| usart_driver_deassertime_config | 配置USART驱动使能置低时间 |
| usart_depolarity_config | 配置USART驱动使能极性模式 |
| usart_dma_receive_config | 配置USART DMA接收 |
| usart_dma_transmit_config | 配置USART DMA发送 |
| usart_reception_error_dma_disable | USART接收错误时禁能DMA |
| usart_reception_error_dma_enable | USART接收错误时使能DMA |
| usart_wakeup_enable | 使能USART唤醒 |
| usart_wakeup_disable | 失能USART唤醒 |
| usart_wakeup_mode_config | 配置USART唤醒模式 |
| usart_command_enable | 使能USART请求 |
| usart_receive_fifo_enable | 使能接收FIFO |
| usart_receive_fifo_disable | 失能接收FIFO |
| usart_receive_fifo_counter_number | 读取接收FIFO计数器的值 |
| usart_flag_get | 得到STAT/RFCS寄存器中的标志 |
| usart_flag_clear | 清除USART状态 |
| usart_interrupt_enable | 使能USART中断 |
| usart_interrupt_disable | 失能USART中断 |
| usart_interrupt_flag_get | 得到USART中断和标志状态 |
| usart_interrupt_flag_clear | 清除USART中断标志位 |
四、内部集成电路总线接口(I2C)
1.简介
I2C(内部集成电路总线)模块提供了符合工业标准的两线串行制接口,使用两条串行线:串行数据线 SDA 和串行时钟线 SCL。
具备CRC校验功能、支持 SMBus(系统管理总线)、PMBus(电源管理总线)和 SAM_V(验证安全控制模块)模式,支持 DMA 模式。
- 同一接口既可实现主机功能又可实现从机功能
- 主从机之间的双向数据传输
- 支持标速模式(最高 100 kHz)和快速模式(最高 400 kHz)
- 支持 DMA 模式
2.I2C程序框图
I2C时钟来自于APB总线
3.SDA和SCL线
SDA:串行数据线
SCL:串行时钟线
当总线空闲时,两条线都是高电平,逻辑‘0’和逻辑‘1’的电平并不是固定的,取决于 VDD 的实际电平。
3.1 开始信号和停止信号
所有的数据传输起始于一个 START 结束于一个 STOP
START 信号定义:在 SCL 为高时,SDA 线上出现一个从高到低的电平转换
STOP 信号定义:在 SCL 为高时,SDA 线上出现一个从低到高的电平转换
3.2 同步时钟
SCL 线的高到低切换会使器件开始计数它们的低电平周期,而且当主机的时钟变低电平时,它会使 SCL 线保持这种状态直到到达时钟的高
电平
但是如果另一个时钟仍处于低电平周期,这个时钟的低到高切换不会改变 SCL 线的状态。因此 SCL 线被有最长低电平周期的器件保持低电平
4.I2C通信流程
每个I2C设备都通过唯一的地址进行识别,根据设备功能,他们既可以是发送器也可作为接收器。
- I2C从机检测到I2C总线上的起始信号
- 接收总线地址并与自身地址比较
- 如果地址相同,则发送一个ACK,并响应总线上的后续命令
主机负责产生起始和终止信号,并负责产生SCL时钟。
4.1 主机发送流程
4.2 主机接收流程
5.软件编程模型
系统复位以后,I2C默认工作在从机模式下。通过软件配置使I2C在总线上发送一个START信号之后,I2C变为主机模式,软件配置在I2C总线上发送STOP信号
后,I2C又变回从机模式。
5.1从机发送模式下的软件流程
使能I2C外设时钟,配置I2C时序,默认I2C处于从机模式,等待总线上的START信号
进入发送状态,写入数据到I2C_DATA寄存器,数据将被移入内部位移寄存器,位移 寄存器清空,I2C开始发送数据到I2C总线
在第一个字节发送期间,可以向I2C_DATA寄存器中写第二个字节
第一个字节发送完成后,TBE再次置起,可以写第三个字节到I2C_DATA,在此之后,任何时候TBE被置1,只要依然有数据待被发送,软件都可以
写入一个字节到I2C_DATA寄存器
倒数第二个字节发送期间,软件写最后一个数据到I2C_DATA寄存器来清除TBE标志位,之后就再不用关心TBE的状态。TBE位会在倒数第二个字节发送完成后置起,直到检测到STOP信号时被清0
在最后一个字节发送结束后,I2C从机的AERR(应答错误)会置起以通知软件发送结束
5.2从机接收模式下的软件流程
- 首先,软件应该使能I2C外设时钟,以及配置I2C_CTL1中时钟相关寄存器来确保正确的I2C时序。使能和配置以后,I2C运行在默认的从机模式状态,等待START信号以及地址
- 在接收到START起始信号和匹配的7位或10地址之后,I2C硬件将I2C状态寄存器0的ADDSEND位置1,此位应该通过软件轮询或者中断来检测,发现置起后,软件通过先读I2C_STAT0寄存器然后读I2C_STAT1寄存器来清除ADDSEND位。当ADDSEND位被清0时,I2C就开始接收来自I2C总线的数据
- 当接收到第一个字节时,RBNE位被硬件置1,软件可以读取I2C_DATA寄存器的第一个字节,此时RBNE位也被清0
- 任何时候RBNE被置1,软件可以从I2C_DATA寄存器读取一个字节
- 接收到最后一个字节后,RBNE被置1,软件可以读取最后的字节
- 当I2C检测到I2C总线上一个STOP信号,STPDET位被置1,软件通过先读I2C_STAT0寄存器再写I2C_CTL0寄存器来清除STPDET位
5.3 主机发送模式下的软件流程
首先,软件应该使能I2C外设时钟,以及配置I2C_CTL1中时钟相关寄存器来确保正确的I2C时序。使能和配置以后,I2C运行在默认的从机模式状态,等待START信号,随后等待I2C总线寻址。
软件将START位置1,在I2C总线上产生一个START信号。
发送一个START信号后,I2C硬件将I2C_STAT0的SBSEND位置1然后进入主机模式。现在软件应该读I2C_STAT0寄存器然后写一个7位地址位或10位地址的地址头到I2C_DATA寄存器来清除SBSEND位。当SBSEND位被清0时,I2C就开始发送地址或者地址头到I2C总线。如果发送的地址是10位地址的地址头,硬件在发送地址头的时候会将ADD10SEND位置1,软件应该通过读I2C_STAT0寄存器然后写10位低地址到I2C_DATA来清除ADD10SEND位
7位或10位的地址位发送出去之后,I2C硬件将ADDSEND位置1,软件通过读I2C_STAT0寄存器然后读I2C_STAT1寄存器清除ADDSEND位。
I2C进入数据发送状态,因为移位寄存器和数据寄存器I2C_DATA都是空的,所以硬件将TBE位置1。此时软件可以写第一个字节数据到I2C_DATA寄存器,但是TBE位此时不会被清零,因为写入I2C_DATA寄存器的字节会被立即移入内部移位寄存器。当移位寄存器非空时,I2C就开始发送数据到总线。
在第一个字节的发送过程中,软件可以写第二个字节到I2C_DATA,此时TBE会被清零,因为I2C_DATA寄存器和移位寄存器都不为空。
任意时刻TBE被置1,软件都可以向I2C_DATA寄存器写入一个字节,只要还有数据待发送。
在倒数第二个字节发送过程中,软件写入最后一个字节数据到I2C_DATA来清除TBE标志位,此后就不用关心TBE位的状态。TBE位会在倒数第二个字节发送完成后被置起,直到发送STOP信号时被清零。
最后一个字节发送结束后,I2C主机将BTC位置起,因为移位寄存器和I2C_DATA寄存器此时都为空。软件此时应该配置STOP来发送一个STOP信号,此后TBE和BTC状态位都将被清0。
5.4 主机接收模式下的软件流程
方案A:
首先,软件应该使能I2C外设时钟,以及配置I2C_CTL1中时钟相关寄存器来确保正确的I2C时序。使能和配置以后,I2C运行在默认的从机模式状态,等待START信号,随后等待I2C总线寻址。
软件将START位置1,从而在I2C总线上产生一个START信号。
发送一个START信号后,I2C硬件将I2C_STAT0寄存器的SBSEND位置1然后进入主机模式。现在软件应该读I2C_STAT0寄存器然后写一个7位地址位或10位地址的地址头到I2C_DATA寄存器来清除SBSEND位。当SBSEND位被清0时,I2C就开始发送地址或者地址头到I2C总线。如果发送的地址是10位地址的地址头,硬件在发送地址头的时候会先将ADD10SEND位置1,软件应该通过读I2C_STAT0寄存器然后写10位低地址到I2C_DATA来清除ADD10SEND位。
7位或10位的地址位发送出去之后,I2C硬件将ADDSEND位置1,软件应该通过读I2C_STAT0寄存器然后读I2C_STAT1寄存器清除ADDSEND位。如果地址是10位格式,软件应该再次将START位置1来重新产生一个START。在START产生后,SBSEND位会被置1。软件应该通过先读I2C_STAT0然后写地址头到I2C_DATA来清除SBSEND位,然后地址头被发到I2C总线,ADDSEND再次被置1。软件应该再次通过先读I2C_STAT0然后读I2C_STAT1来清除ADDSEND位。
当接收到第一个字节时,硬件会将RBNE位置1。此时软件可以从I2C_DATA寄存器读取第一个字节,之后RBNE位被清0。
此后任何时候RBNE被置1,软件就可以从I2C_DATA寄存器读取一个字节。
接收完倒数第二个字节(N-1)数据之后,软件应该立即将ACKEN位清0,并将STOP位置1,这一过程需要在最后一个字节接收完毕之前完成,以确保NACK发送给最后一个字节。
最后一个字节接收完毕后,RBNE位被置1,软件可以读取最后一个字节。由于ACKEN已经在前一步骤中被清0,I2C不再为最后一个字节发送ACK,并在最后一个字节发送完毕后产生一个STOP信号。
以上步骤要求字节数目N>1,如果N=1,步骤7应该在步骤4之后就执行,且需要在字节接收完
成之前完成。
方案B:
首先,软件应该使能I2C外设时钟,配置I2C_CTL1中时钟相关寄存器来确保正确的I2C时序。初始化完成之后,I2C运行在默认的从机模式状态,等待START信号和地址。
软件将START位置1,从而在I2C总线上产生一个START信号。
发送一个START信号后,I2C硬件将I2C_STAT0寄存器的SBSEND位置1然后进入主机模式。现在软件应该读I2C_STAT0寄存器然后写一个7位地址位或10位地址的地址头到I2C_DATA寄存器来清除SBSEND位。当SBSEND位被清0时,I2C就开始发送地址或者地址头到I2C总线。如果发送的地址是10位地址的地址头,硬件在发送地址头之后会将ADD10SEND位置1,软件应该通过读I2C_STAT0寄存器然后写10位低地址到I2C_DATA来清除ADD10SEND位。
7位或10位的地址位发送出去之后,I2C硬件将ADDSEND位置1,软件应该通过读I2C_STAT0寄存器然后读I2C_STAT1寄存器清除ADDSEND位。如果地址是10位格式,软件应该接着将START位再次置1来产生一个开始信号,START被发送出去以后SBSEND位被再次置1。软件应该通过先读I2C_STAT0然后写地址头到I2C_DATA来清除SBSEND位,然后地址头被发到I2C总线,ADDSEND再次被置1。软件应该再次通过先读I2C_STAT0然后读I2C_STAT1来清除ADDSEND位。
当第一个字节被接收时,RBNE位会被硬件置1。此时软件可从I2C_DATA寄存器读取出第一个字节,同时RBNE位被清0。
此后任何时刻,只要RBNE位被置1,软件就可以从I2C_DATA寄存器读取一个字节的数据,直到主机接收了N-3个字节。
第 N-2 个字节还没被软件读出,之后第 N-1 个字节被接收,此时 BTC 和 RBNE 都被置位,总线就会被主机锁死以阻止最后一个字节的接收。然后软件应该清除 ACKEN 位
- 软件从I2C_DATA读出倒数第三个(N-2)字节数据,同时也将BTC位清0。此后第N-1个字节从移位寄存器被移到I2C_DATA,总线得到释放然后开始接收最后一个字节,由于ACKEN已经被清除,因此主机不会给最后一个字节数据发送ACK响应。
- 最后一个字节接收完毕后,硬件再次把BTC位和RBNE置1,并拉低SCL,软件将STOP位置1,主机发出一个STOP信号。
- 软件读取第N-1个字节,清除BTC。此后最后一个字节从移位寄存器被移动到I2C_DATA。
- 软件读取最后一个字节,清除RBNE。以上步骤需要字节数字N>2,N=1和N=2的情况近似:
N=1
在第 4 步,软件应该在清除 ADDSEND 位之前将 ACKEN 位清 0,在清除 ADDSEND 位之后将 STOP 位置 1。当 N=1 时步骤 5 是最后一步。
N=2
在第2步,软件应该在START置1之前将POAP置1。在第4步,软件应该在清除ADDSEND位之前将ACKEN位清0。在第5步,软件应该一直等到BTC位被置1然后将STOP位置1且读取I2C_DATA两次
6.DMA 模式下数据传输
I2C 的 DMA 功能可以在 TBE 或 RBNE 位置 1 时,自动进行一次写或读操作。
7.状态、错误和中断
7.1 事件状态标志位
| 事件标志位名称 | 说明 |
| ————– | ——————————— |
| SBSEND | 主机发送 START 信号 |
| ADDSEND | 地址发送和接收 |
| ADD10SEND | 10 位地址模式中地址头发送 |
| STPDET | 监测到 STOP 信号 |
| BTC | 字节发送结束 |
| TBE | 发送时 I2C_DATA 为空 |
| RBNE | 接收时 I2C_DATA 非空 |
| RFR | SAM_V 模式时检测到 rxframe 上升沿 |
| RFF | SAM_V 模式时检测到 rxframe 下降沿 |
| TFR | SAM_V 模式时检测到 txframe 上升沿 |
| TFF | SAM_V 模式时检测到txframe 下降沿 |
7.2 错误标志位
| 错误名称 | 说明 |
| ——– | ———————————– |
| BERR | 总线错误 |
| LOSTARB | 仲裁丢失 |
| OUERR | 当禁用 SCL 拉低后,发生了上溢或下溢 |
| AERR | 没有接收到应答 |
| PECERR | CRC 值不相同 |
| SMBTO | SMBus 模式下总线超时 |
| SMBALT | SMBus 警报 |
8.I2C寄存器
| 寄存器名称 | 寄存器描述 |
| ———- | ——————– |
| I2C_CTL0 | 控制寄存器0 |
| I2C_CTL1 | 控制寄存器1 |
| I2C_SADDR0 | 从机地址寄存器0 |
| I2C_SADDR1 | 从机地址寄存器1 |
| I2C_DATA | 传输缓冲区寄存器 |
| I2C_STAT0 | 传输状态寄存器0 |
| I2C_STAT1 | 传输状态寄存器1 |
| I2C_CKCFG | 时钟配置寄存器 |
| I2C_RT | 上升时间寄存器 |
| I2C_FMPCFG | 快速+ 模式配置寄存器 |
9.I2C库函数汇总
| 库函数名称 | 库函数描述 |
| ———————————- | ——————————— |
| i2c_deinit | 复位外设I2C |
| i2c_clock_config | 配置I2C时钟 |
| i2c_mode_addr_config | 配置I2C地址 |
| i2c_smbus_type_config | SMBus类型选择 |
| i2c_ack_config | 是否发送ACK |
| i2c_ackpos_config | 配置在接收模式下ACK和PEC的位置 |
| i2c_master_addressing | 主机发送从机地址 |
| i2c_dualaddr_enable | 双地址模式使能 |
| i2c_dualaddr_disable | 双地址模式禁能 |
| i2c_enable | 使能I2C模块 |
| i2c_disable | 关闭I2C模块 |
| i2c_start_on_bus | 在I2C总线上生成起始位 |
| i2c_stop_on_bus | 在I2C总线上生成停止位 |
| i2c_data_transmit | 发送数据 |
| i2c_data_receive | 接收数据 |
| i2c_dma_enable | I2C DMA模式使能 |
| i2c_dma_last_transfer_config | 配置下一个DMA EOT是否最后一次传输 |
| i2c_stretch_scl_low_config | 当从机数据没有准备好时是否拉低SCL |
| i2c_slave_response_to_gcall_config | 从机是否响应广播呼叫 |
| i2c_software_reset_config | 配置I2C软件复位 |
| i2c_pec_enable | 报文错误校验使能 |
| i2c_pec_transfer_enable | 传输PEC值使能 |
| i2c_pec_value_get | 获取报文错误校验值 |
| i2c_smbus_issue_alert | 通过SMBA引脚发送警告 |
| i2c_smbus_arp_enable | SMBus下ARP协议是否开启 |
| i2c_flag_get | 获取I2C标志位 |
| i2c_flag_clear | 清除I2C标志位 |
| i2c_interrupt_enable | 中断使能 |
| i2c_interrupt_disable | 中断除能 |
| i2c_interrupt_flag_get | 中断标志位获取 |
| i2c_interrupt_flag_clear | 中断标志位清除 |
10.硬件I2C例程
10.1 I2C初始化
void i2c_init(void)
{
rcu_periph_clock_enable(RCU_GPIOB); // 使能GPIOB时钟
rcu_periph_clock_enable(RCU_I2C0); // 使能I2C0时钟
gpio_af_set(GPIOB, GPIO_AF_4, GPIO_PIN_6); // 配置I2C0_SCL到PB6
gpio_af_set(GPIOB, GPIO_AF_4, GPIO_PIN_7); // 配置I2C0_SDA到PB7
// 配置I2C所用的IO口
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_6);
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_7);
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
i2c_clock_config(I2C0, 400000, I2C_DTCY_2); // 配置I2C时钟
i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, I2C0_OWN_ADDRESS7); // 配置I2C地址
i2c_enable(I2C0); // 使能I2C0
i2c_ack_config(I2C0, I2C_ACK_ENABLE); // 使能I2C ACK
}
10.2 I2C主机写单字节
void i2c_byte_write(uint8_t p_buffer, uint8_t write_address)
{
while (i2c_flag_get(I2C0, I2C_FLAG_I2CBSY)) {}; // 等待I2C总线空闲
i2c_start_on_bus(I2C0); // 向I2C总线发送启动条件
while (!i2c_flag_get(I2C0, I2C_FLAG_SBSEND)) {}; // 等待SBSEND位设置
i2c_master_addressing(I2C0, 0xA0, I2C_TRANSMITTER); // 发送从地址到I2C总线
while (!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) {}; // 等待ADDSEND位设置
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); // 清除ADDSEND位
while (SET != i2c_flag_get(I2C0, I2C_FLAG_TBE)) {}; // 等待发送寄存器空
i2c_data_transmit(I2C0, write_address); // 发送EEPROM的内部地址写:只有一个字节地址
while (!i2c_flag_get(I2C0, I2C_FLAG_BTC)) {}; // 等待BTC位设置完成
i2c_data_transmit(I2C0, p_buffer); // 发送要写入的字节
while (!i2c_flag_get(I2C0, I2C_FLAG_BTC)) {}; // 等待BTC位设置完成
i2c_stop_on_bus(I2C0); // 向I2C总线发送停止位
while (I2C_CTL0(I2C0) & 0x0200) {}; // 等待停止条件完成
}
10.3 I2C主机写整页
void i2c_page_write(uint8_t *p_buffer, uint8_t write_address, uint8_t number_of_byte)
{
while (i2c_flag_get(I2C0, I2C_FLAG_I2CBSY)) {}; // 等待I2C总线空闲
i2c_start_on_bus(I2C0); // 向I2C总线发送启动条件
while (!i2c_flag_get(I2C0, I2C_FLAG_SBSEND)) {}; // 等待SBSEND位设置
i2c_master_addressing(I2C0, 0xA0, I2C_TRANSMITTER); // 发送从地址到I2C总线
while (!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) {}; // 等待ADDSEND位设置
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); // 清除ADDSEND位
while (SET != i2c_flag_get(I2C0, I2C_FLAG_TBE)) {}; // 等待传输数据缓冲区为空
i2c_data_transmit(I2C0, write_address); // 发送i2c的内部地址写:只有一个字节地址
while (!i2c_flag_get(I2C0, I2C_FLAG_BTC)) {}; // 等待BTC位设置完成
while (number_of_byte--) { // 有数据要写的时候循环
i2c_data_transmit(I2C0, *p_buffer); // 发送i2c的数据
p_buffer++; // 指向要写入的下一个字节
while (!i2c_flag_get(I2C0, I2C_FLAG_BTC)) {}; // 等待BTC位设置完成
}
i2c_stop_on_bus(I2C0); // 向I2C总线发送停止位
while (I2C_CTL0(I2C0) & 0x0200) {}; // 等待停止条件完成
}
10.4 I2C主机读数据
void i2c_buffer_read(uint8_t *p_buffer, uint8_t read_address, uint16_t number_of_byte)
{
while (i2c_flag_get(I2C0, I2C_FLAG_I2CBSY)) {}; // 等待I2C总线空闲
if (2 == number_of_byte) i2c_ackpos_config(I2C0, I2C_ACKPOS_NEXT); // ACK配置
i2c_start_on_bus(I2C0); // 向I2C总线发送启动条件
while (!i2c_flag_get(I2C0, I2C_FLAG_SBSEND)) {}; // 等待SBSEND位设置
i2c_master_addressing(I2C0, 0xA0, I2C_TRANSMITTER); // 发送从地址到I2C总线
while (!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) {}; // 等待ADDSEND位设置
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); // 清除ADDSEND位
while (SET != i2c_flag_get(I2C0, I2C_FLAG_TBE)) {}; // 等待传输数据缓冲区为空
i2c_enable(I2C0); // 使能I2C
i2c_data_transmit(I2C0, read_address); // 发送EEPROM的内部地址
while (!i2c_flag_get(I2C0, I2C_FLAG_BTC)) {}; // 等待BTC位设置完成
i2c_start_on_bus(I2C0); // 向I2C总线发送启动条件
while (!i2c_flag_get(I2C0, I2C_FLAG_SBSEND)) {}; // 等待SBSEND位设置
i2c_master_addressing(I2C0, 0xA0, I2C_RECEIVER); // 发送从地址到I2C总线
if (number_of_byte < 3) i2c_ack_config(I2C0, I2C_ACK_DISABLE); // 禁用ACK
while (!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) {}; // 等待ADDSEND位设置
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); // 清除ADDSEND位
if (1 == number_of_byte) i2c_stop_on_bus(I2C0); // 向I2C总线发送停止条件
while (number_of_byte) { // 当有数据要读取时
if (3 == number_of_byte) {
while (!i2c_flag_get(I2C0, I2C_FLAG_BTC)) {}; // 等待BTC位设置完成
i2c_ack_config(I2C0, I2C_ACK_DISABLE); // 禁用ACK
}
if (2 == number_of_byte) {
while (!i2c_flag_get(I2C0, I2C_FLAG_BTC)) {}; // 等待BTC位设置完成
i2c_stop_on_bus(I2C0); // 向I2C总线发送停止条件
}
// 等待RBNE位设置完毕并清除它
if (i2c_flag_get(I2C0, I2C_FLAG_RBNE)) {
*p_buffer = i2c_data_receive(I2C0); // 从i2c中读取一个字节
p_buffer++; // 指向将保存所读字节的下一个位置
number_of_byte--; // 递减读字节计数器
}
}
while (I2C_CTL0(I2C0) & 0x0200) {}; // 等待停止条件完成
i2c_ack_config(I2C0, I2C_ACK_ENABLE); // 使能ACK
i2c_ackpos_config(I2C0, I2C_ACKPOS_CURRENT); // ACK配置
while (i2c_flag_get(I2C0, I2C_FLAG_I2CBSY)) {}; // 等待I2C总线空闲
}
10.5 I2C从机读数据
void i2c_slave_receive(void)
{
while (!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) {}; // 等待ADDSEND置1
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); // 清除ADDSEND位
for (i = 0; i < 16; i++) {
while (!i2c_flag_get(I2C0, I2C_FLAG_RBNE)) {}; // 等待RBNE置1
i2c_receiver[i] = i2c_data_receive(I2C0); // 从I2C_DATA寄存器读数据
}
while (!i2c_flag_get(I2C0, I2C_FLAG_STPDET)) {}; // 等待STPDET置1
i2c_enable(I2C0); // 清除STPDET位
}
10.6 I2C从机写数据
void i2c_slave_transmitter(uint8_t data)
{
while (!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) {}; // 等待ADDSEND置1
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); // 清除ADDSEND位
while (!i2c_flag_get(I2C0, I2C_FLAG_TBE)) {}; // 等待发送数据寄存器清空
i2c_data_transmit(I2C0, data); // 发送数据
while (!i2c_flag_get(I2C0, I2C_FLAG_TBE)) {}; // 等待发送数据寄存器清空
while (!i2c_flag_get(I2C0, I2C_FLAG_AERR)) {}; // 等待主机不回复ACK
i2c_flag_clear(I2C0, I2C_FLAG_AERR); // 清AERR位
}
11.软件I2C
由于硬件I2C资源有限,在一部分速率要求不高的情况下可以使用软件I2C
11.1 软件I2C初始化
// IO方向设置宏定义
#define SDA_IN() \
{ \
gpio_mode_set(GPIOB, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_PIN_7); \
} // PB7输入模式
#define SDA_OUT() \
{ \
gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_7); \
} // PB7输出模式
#define IIC_SCL PBout(6) // SCL
#define IIC_SDA PBout(7) // SDA
void soft_i2c_init(void)
{
rcu_periph_clock_enable(RCU_GPIOB); // 使能GPIOB时钟
// 配置软件I2C使用的IO口
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_6);
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_7);
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
}
11.2 软件I2C产生起始信号
// 产生起始信号
void i2c_start(void)
{
SDA_OUT(); // sda线输出
IIC_SDA = 1;
IIC_SCL = 1;
__nop();
__nop();
__nop();
__nop();
IIC_SDA = 0; // CLK线拉高时,SDA线拉低
__nop();
__nop();
__nop();
__nop();
IIC_SCL = 0; // 钳住I2C总线,准备发送或接收数据
}
11.3 软件I2C产生终止信号
// 产生终止信号
void i2c_stop(void)
{
SDA_OUT(); // sda线输出
IIC_SCL = 0;
IIC_SDA = 0; // CLK线拉高时,SDA线拉低
__nop();
__nop();
__nop();
__nop();
IIC_SCL = 1;
IIC_SDA = 1; // 发送I2C总线结束信号
__nop();
__nop();
__nop();
__nop();
}
11.4 软件I2C等待应答信号
uint8_t i2c_wait_ack(void)
{
uint8_t err_time = 0;
SDA_IN(); // SDA设置为输入
IIC_SDA = 1;
__nop();
IIC_SCL = 1;
__nop();
while (IIC_SDA) {
err_time++;
if (err_time > 250) {
iic_stop();
return 1;
}
}
IIC_SCL = 0; // 时钟输出0
return 0;
}
11.5 软件I2C产生应答信号
void i2c_ack(void)
{
IIC_SCL = 0;
SDA_OUT();
IIC_SDA = 0;
__nop();
__nop();
IIC_SCL = 1;
__nop();
__nop();
IIC_SCL = 0;
}
11.6 软件I2C不产生应答信号
void i2c_without_ack(void)
{
IIC_SCL = 0;
SDA_OUT();
IIC_SDA = 1;
__nop();
__nop();
IIC_SCL = 1;
__nop();
__nop();
IIC_SCL = 0;
}
11.7 软件I2C发送单字节数据
void i2c_send_byte(uint8_t txd)
{
uint8_t t;
SDA_OUT();
IIC_SCL = 0; // 拉低时钟开始数据传输
for (t = 0; t < 8; t++) {
IIC_SDA = (txd & 0x80) >> 7;
txd <<= 1;
__nop();
__nop(); // 对TEA5767这三个延时都是必须的
IIC_SCL = 1;
__nop();
__nop();
IIC_SCL = 0;
__nop();
__nop();
}
}
11.8 软件IIC读单字节数据
uint8_t i2c_read_byte(uint8_t ack)
{
uint8_t i, receive = 0;
SDA_IN(); // SDA设置为输入
for (i = 0; i < 8; i++) {
IIC_SCL = 0;
__nop();
__nop();
IIC_SCL = 1;
receive <<= 1;
if (IIC_SDA) receive++;
__nop();
}
if (!ack)
i2c_without_ack(); // 发送nACK
else
i2c_ack(); // 发送ACK
return receive;
}