通用输入输出 (GPIO) 是单片机中非常基础且重要的外设功能之一。GPIO 通常用于控制外部设备或从外部设备读取数据。在 SAM D 系列中,GPIO 通过特定的寄存器和配置来实现。每个 GPIO 引脚都可以配置为输入或输出模式,并且可以设置不同的中断和触发条件。
SAM D 系列的 GPIO 控制主要通过以下寄存器实现:
DIRSET 和 DIRCLR:用于设置和清除引脚的方向(输入或输出)。
OUTSET 和 OUTCLR:用于设置和清除输出引脚的电平。
IN:用于读取输入引脚的电平。
PINCFG:用于配置引脚的其他特性,如上拉/下拉电阻、驱动能力等。
配置 GPIO 引脚的基本步骤如下:
选择端口:SAM D 系列通常有多个 GPIO 端口,例如 PORTA
、PORTB
等。
设置方向:使用 DIRSET
和 DIRCLR
寄存器来设置引脚的方向。
配置引脚:使用 PINCFG
寄存器来配置引脚的其他特性。
读写引脚:使用 IN
和 OUTSET
/OUTCLR
寄存器来读取和设置引脚的电平。
以下是一个示例代码,展示如何配置和使用 GPIO 引脚。我们将配置 PORTA
的第 0 引脚为输出模式,并设置其电平为高。
#include "sam.h"
void configure_gpio_output(void) {
// 选择端口 A
PORTGroup *porta = PORT->Group[0];
// 设置引脚 0 为输出模式
porta->DIRSET.reg = PORT_DIRSET_PIN0;
// 配置引脚 0
porta->PINCFG[0].reg |= PORT_PINCFG_DRVSTR_Msk; // 设置驱动强度
porta->PINCFG[0].reg &= ~PORT_PINCFG_PULLEN_Msk; // 禁用上拉/下拉电阻
// 设置引脚 0 的电平为高
porta->OUTSET.reg = PORT_OUTSET_PIN0;
}
int main(void) {
// 配置 GPIO 引脚
configure_gpio_output();
// 主循环
while (1) {
// 保持运行
}
}
GPIO 中断允许单片机在引脚状态发生变化时触发中断处理程序。通过配置中断,可以实现更高效的事件响应。
选择中断源:确定哪个引脚需要配置中断。
配置中断类型:选择中断触发类型,如上升沿、下降沿或双边沿。
启用中断:在相应的寄存器中启用中断。
编写中断处理函数:定义中断处理函数,并在初始化时注册该函数。
以下是一个示例代码,展示如何配置 PORTA
的第 1 引脚为中断输入,并在引脚状态变化时触发中断处理函数。
#include "sam.h"
#include "system_samd21.h"
#include "port.h"
// 中断处理函数
void PORTA_Handler(void) {
// 清除中断标志
PORTGroup *porta = PORT->Group[0];
porta->INTFLAG.reg = PORT_INTFLAG_PIN1;
// 处理中断
if (porta->IN.reg & PORT_PIN1) {
// 引脚电平为高
PORT->Group[0].OUTSET.reg = PORT_OUTSET_PIN0; // 设置 Pin0 为高
} else {
// 引脚电平为低
PORT->Group[0].OUTCLR.reg = PORT_OUTCLR_PIN0; // 设置 Pin0 为低
}
}
void configure_gpio_interrupt(void) {
// 选择端口 A
PORTGroup *porta = PORT->Group[0];
// 设置引脚 1 为输入模式
porta->DIRCLR.reg = PORT_DIRCLR_PIN1;
// 配置引脚 1
porta->PINCFG[1].reg |= PORT_PINCFG_PULLEN_Msk; // 启用上拉电阻
porta->PINCFG[1].reg &= ~PORT_PINCFG_INEN_Msk; // 启用输入
// 配置中断类型
porta->INTENSET.reg = PORT_INTENSET_PIN1; // 启用 Pin1 中断
porta->EVCTRL.reg |= PORT_EVCTRL_PIN1; // 选择中断触发类型(例如双边沿)
// 注册中断处理函数
NVIC_SetPriority(PORTA_IRQn, 0); // 设置中断优先级
NVIC_EnableIRQ(PORTA_IRQn); // 启用中断
}
int main(void) {
// 配置 GPIO 引脚
configure_gpio_output();
configure_gpio_interrupt();
// 主循环
while (1) {
// 保持运行
}
}
SAM D 系列的 GPIO 引脚通常可以复用为多种功能,例如定时器、ADC 输入、PWM 输出等。通过配置引脚的多功能复用,可以实现更灵活的外设控制。
选择复用功能:确定引脚需要复用的功能。
配置引脚功能:使用 PINCFG
寄存器中的 PMUX
字段来选择复用功能。
启用复用功能:在相应的外设寄存器中启用复用功能。
以下是一个示例代码,展示如何配置 PORTA
的第 2 引脚为 ADC 输入。
#include "sam.h"
#include "system_samd21.h"
#include "adc.h"
void configure_gpio_adc_input(void) {
// 选择端口 A
PORTGroup *porta = PORT->Group[0];
// 设置引脚 2 为输入模式
porta->DIRCLR.reg = PORT_DIRCLR_PIN2;
// 配置引脚 2
porta->PINCFG[2].reg |= PORT_PINCFG_PULLEN_Msk; // 启用上拉电阻
porta->PINCFG[2].reg |= PORT_PINCFG_INEN_Msk; // 启用输入
// 配置多功能复用
porta->PMUX[1].reg = PORT_PMUX_PMUXE_A; // 选择 ADC 功能
porta->PMUX[1].reg |= PORT_PMUX_PMUXO_A; // 选择 ADC 功能
// 启用 ADC 功能
ADC *adc = ADC0;
adc->CTRLA.reg = ADC_CTRLA_SWRST; // 复位 ADC
while (adc->CTRLA.reg & ADC_CTRLA_SWRST) {
// 等待复位完成
}
adc->CTRLA.reg = ADC_CTRLA_ENABLE; // 启用 ADC
adc->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV32; // 设置预分频器
adc->REFCTRL.reg = ADC_REFCTRL_REFSEL_VREFPU; // 选择参考电压
adc->EVTCTRL.reg = 0; // 配置事件控制
adc->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN2; // 选择输入通道
adc->AVGCTRL.reg = ADC_AVGCTRL_SAMPNUM_1 | ADC_AVGCTRL_ADJRES_12; // 设置采样次数和分辨率
adc->SAMPCTRL.reg = ADC_SAMPCTRL_SAMPLEN(1); // 设置采样时间
adc->CTRLA.reg |= ADC_CTRLA_ENABLE; // 重新启用 ADC
}
int main(void) {
// 配置 GPIO 引脚
configure_gpio_adc_input();
// 主循环
while (1) {
// 读取 ADC 值
ADC *adc = ADC0;
adc->SWTRIG.reg = ADC_SWTRIG_START; // 触发一次转换
while (!(adc->INTFLAG.reg & ADC_INTFLAG_RESRDY)) {
// 等待转换完成
}
uint16_t adc_value = adc->RESULT.reg & ADC_RESULT_RESULT_Msk; // 读取结果
// 处理 ADC 值
}
}
串行通信接口 (SERCOM) 是 SAM D 系列中用于实现多种串行通信协议的外设。SERCOM 可以配置为 SPI、UART、I2C 和 I2S 等多种通信模式。每个 SERCOM 模块通常包含多个通信通道,可以根据需要灵活配置。SERCOM 的多功能性和灵活性使得它在嵌入式系统中非常有用,可以轻松实现与外部设备的通信。
SERCOM 的配置主要通过以下寄存器实现:
CTRLA:用于启用和复位 SERCOM 模块。
CTRLB:用于配置通信模式和数据格式。
BAUD:用于设置波特率。
INTENSET 和 INTENCLR:用于设置和清除中断使能。
STATUS:用于读取通信状态。
DATA:用于发送和接收数据。
配置 SERCOM 模块的基本步骤如下:
选择通信模式:确定使用哪种通信协议(SPI、UART、I2C、I2S)。
设置波特率:对于 UART 和 SPI,需要设置波特率。
配置数据格式:设置数据位、停止位、校验位等。
启用通信:在 CTRLA
寄存器中启用 SERCOM 模块。
以下是一个示例代码,展示如何配置 SERCOM 模块为 UART 通信模式,并实现简单的串行通信。
#include "sam.h"
#include "system_samd21.h"
#include "sercom.h"
#include "timer.h"
void configure_uart(void) {
// 选择 SERCOM 模块
SERCOM *sercom = SERCOM0;
// 选择 UART 模式
sercom->USART.CTRLA.reg = SERCOM_USART_CTRLA_SWRST; // 复位 USART
while (sercom->USART.CTRLA.reg & SERCOM_USART_CTRLA_SWRST) {
// 等待复位完成
}
// 配置波特率
uint32_t baud_rate = 9600;
uint32_t baud_rate_divisor = (SystemCoreClock / (16 * baud_rate)) - 1;
sercom->USART.BAUD.reg = SERCOM_USART_BAUD_BAUD(baud_rate_divisor);
// 配置数据格式
sercom->USART.CTRLB.reg = SERCOM_USART_CTRLB_RXEN | SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_CHSIZE_8BIT;
// 启用 USART
sercom->USART.CTRLA.reg |= SERCOM_USART_CTRLA_ENABLE;
}
void send_uart_data(char *data) {
SERCOM *sercom = SERCOM0;
while (*data) {
while (!(sercom->USART.STATUS.reg & SERCOM_USART_STATUS_DRE)) {
// 等待数据寄存器为空
}
sercom->USART.DATA.reg = *data++;
}
}
char receive_uart_data(void) {
SERCOM *sercom = SERCOM0;
while (!(sercom->USART.INTFLAG.reg & SERCOM_USART_INTFLAG_RXC)) {
// 等待接收数据
}
return sercom->USART.DATA.reg;
}
int main(void) {
// 配置 UART
configure_uart();
// 主循环
while (1) {
// 发送数据
send_uart_data("Hello, UART!\r\n");
// 接收数据
char received_data = receive_uart_data();
// 处理接收到的数据
}
}
SPI (Serial Peripheral Interface) 是一种同步串行通信协议,常用于连接微控制器和外部设备。SAM D 系列的 SERCOM 模块可以配置为 SPI 模式。
配置 SPI 模块的基本步骤如下:
选择 SPI 模式:确定 SPI 是主模式还是从模式。
设置波特率:配置 SPI 的通信速度。
配置数据格式:设置数据位、时钟极性和相位等。
启用 SPI:在 CTRLA
寄存器中启用 SPI 模块。
以下是一个示例代码,展示如何配置 SERCOM 模块为 SPI 主模式,并实现简单的 SPI 通信。
#include "sam.h"
#include "system_samd21.h"
#include "sercom.h"
#include "timer.h"
void configure_spi_master(void) {
// 选择 SERCOM 模块
SERCOM *sercom = SERCOM0;
// 选择 SPI 模式
sercom->SPI.CTRLA.reg = SERCOM_SPI_CTRLA_SWRST; // 复位 SPI
while (sercom->SPI.CTRLA.reg & SERCOM_SPI_CTRLA_SWRST) {
// 等待复位完成
}
// 配置波特率
uint32_t baud_rate = 1000000;
uint32_t baud_rate_divisor = (SystemCoreClock / (2 * baud_rate)) - 1;
sercom->SPI.BAUD.reg = SERCOM_SPI_BAUD_BAUD(baud_rate_divisor);
// 配置数据格式
sercom->SPI.CTRLB.reg = SERCOM_SPI_CTRLB_CHSIZE_8BIT | SERCOM_SPI_CTRLB_CPOL | SERCOM_SPI_CTRLB_CPHA;
// 配置主模式
sercom->SPI.CTRLA.reg |= SERCOM_SPI_CTRLA_MODE(3); // 主模式
sercom->SPI.CTRLA.reg |= SERCOM_SPI_CTRLA_ENABLE;
}
void send_spi_data(uint8_t *data, uint8_t length) {
SERCOM *sercom = SERCOM0;
while (length--) {
while (!(sercom->SPI.INTFLAG.reg & SERCOM_SPI_INTFLAG_DRE)) {
// 等待数据寄存器为空
}
sercom->SPI.DATA.reg = *data++;
}
}
uint8_t receive_spi_data(uint8_t length) {
SERCOM *sercom = SERCOM0;
uint8_t received_data = 0;
while (length--) {
while (!(sercom->SPI.INTFLAG.reg & SERCOM_SPI_INTFLAG_RXC)) {
// 等待接收数据
}
received_data = sercom->SPI.DATA.reg;
}
return received_data;
}
int main(void) {
// 配置 SPI
configure_spi_master();
// 主循环
while (1) {
// 发送数据
uint8_t tx_data[] = {0x55, 0xAA, 0x66};
send_spi_data(tx_data, 3);
// 接收数据
uint8_t rx_data = receive_spi_data(1);
// 处理接收到的数据
}
}
I2C (Inter-Integrated Circuit) 是一种用于连接微控制器和外部设备的同步串行通信协议。SAM D 系列的 SERCOM 模块可以配置为 I2C 模式。
配置 I2C 模块的基本步骤如下:
选择 I2C 模式:确定 I2C 是主模式还是从模式。
设置波特率:配置 I2C 的通信速度。
配置地址:设置 I2C 的设备地址。
启用 I2C:在 CTRLA
寄存器中启用 I2C 模块。
以下是一个示例代码,展示如何配置 SERCOM 模块为 I2C 主模式,并实现简单的 I2C 通信。
#include "sam.h"
#include "system_samd21.h"
#include "sercom.h"
#include "timer.h"
void configure_i2c_master(void) {
// 选择 SERCOM 模块
SERCOM *sercom = SERCOM0;
// 选择 I2C 模式
sercom->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_SWRST; // 复位 I2C
while (sercom->I2CM.CTRLA.reg & SERCOM_I2CM_CTRLA_SWRST) {
// 等待复位完成
}
// 配置波特率
uint32_t baud_rate = 100000;
uint32_t baud_rate_divisor = (SystemCoreClock / (2 * baud_rate)) - 1;
sercom->I2CM.BAUD.reg = SERCOM_I2CM_BAUD_BAUD(baud_rate_divisor);
// 配置主模式
sercom->I2CM.CTRLA.reg |= SERCOM_I2CM_CTRLA_MODE(0); // 主模式
sercom->I2CM.CTRLA.reg |= SERCOM_I2CM_CTRLA_ENABLE;
// 配置 I2C 地址
sercom->I2CM.ADDR.reg = SERCOM_I2CM_ADDR_ADDR(0x3C) | SERCOM_I2CM_ADDR_HSA; // 设置目标设备地址
}
void send_i2c_data(uint8_t address, uint8_t *data, uint8_t length) {
SERCOM *sercom = SERCOM0;
// 发送起始信号
sercom->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_CMD(1); // 发送起始信号
while (sercom->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_MB) {
// 等待总线空闲
}
// 发送目标地址
sercom->I2CM.ADDR.reg = SERCOM_I2CM_ADDR_ADDR(address) | SERCOM_I2CM_ADDR_HSA;
while (!(sercom->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_AMATCH)) {
// 等待地址匹配
}
// 发送数据
while (length--) {
while (!(sercom->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_DRE)) {
// 等待数据寄存器为空
}
sercom->I2CM.DATA.reg = *data++;
}
// 发送停止信号
sercom->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_CMD(3); // 发送停止信号
}
uint8_t receive_i2c_data(uint8_t address) {
SERCOM *sercom = SERCOM0;
// 发送起始信号
sercom->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_CMD(1); // 发送起始信号
while (sercom->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_MB) {
// 等待总线空闲
}
// 发送目标地址(读模式)
sercom->I2CM.ADDR.reg = SERCOM_I2CM_ADDR_ADDR(address) | SERCOM_I2CM_ADDR_HSA | SERCOM_I2CM_ADDR_READ;
while (!(sercom->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_AMATCH)) {
// 等待地址匹配
}
// 接收数据
while (!(sercom->I2CM.INTFLAG.reg & SERCOM_I2CM_INTFLAG_RXC)) {
// 等待接收数据
}
uint8_t received_data = sercom->I2CM.DATA.reg;
// 发送停止信号
sercom->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_CMD(3); // 发送停止信号
return received_data;
}
int main(void) {
// 配置 I2C
configure_i2c_master();
// 主循环
while (1) {
// 发送数据
uint8_t tx_data[] = {0x01, 0x02, 0x03};
send_i2c_data(0x3C, tx_data, 3);
// 接收数据
uint8_t rx_data = receive_i2c_data(0x3C);
// 处理接收到的数据
}
}
通过上述示例代码,我们可以看到 SAM D 系列的 SERCOM 模块在配置和使用不同通信协议时的基本步骤和寄存器配置。无论是 UART、SPI 还是 I2C,配置过程都遵循类似的步骤:选择通信模式、设置波特率、配置数据格式和启用通信。这些配置使得 SAM D 系列微控制器能够灵活地与各种外部设备进行通信,满足不同应用场景的需求。