在外设驱动开发中,我们将详细介绍如何使用 Microchip 的 SAM L 系列单片机(基于 ARM Cortex-M0+)来开发各种外设驱动程序。这部分内容将涵盖常见的外设,如 GPIO、UART、SPI、I2C、ADC 和 DAC 等,并提供具体的代码示例和操作步骤。
GPIO(General Purpose Input/Output)是单片机中最基本的外设之一。通过配置 GPIO,可以实现对外部设备的控制和数据采集。SAM L 系列单片机的 GPIO 配置通常通过寄存器操作来完成。
选择引脚功能:确定要使用的引脚及其功能(输入或输出)。
配置引脚方向:设置引脚为输入或输出。
配置引脚输出电平:对于输出引脚,设置其初始电平。
配置引脚输入状态:对于输入引脚,读取其状态。
以下是一个简单的例子,配置一个 GPIO 引脚为输出,并控制其电平。
#include "sam.h"
// 初始化 GPIO 引脚
void gpio_init_output(uint8_t port, uint8_t pin) {
// 选择引脚功能
PORT->Group[port].DIRSET.reg = (1 << pin); // 设置为输出
PORT->Group[port].OUTCLR.reg = (1 << pin); // 初始化为低电平
}
// 设置 GPIO 引脚电平
void gpio_set_output(uint8_t port, uint8_t pin, uint8_t level) {
if (level == 1) {
PORT->Group[port].OUTSET.reg = (1 << pin); // 设置为高电平
} else {
PORT->Group[port].OUTCLR.reg = (1 << pin); // 设置为低电平
}
}
// 读取 GPIO 引脚状态
uint8_t gpio_read_input(uint8_t port, uint8_t pin) {
return (PORT->Group[port].IN.reg & (1 << pin)) ? 1 : 0; // 读取引脚状态
}
int main(void) {
// 初始化 GPIO 引脚
gpio_init_output(0, 5); // 配置 PORT0 的 PIN5 为输出
while (1) {
gpio_set_output(0, 5, 1); // 设置 PIN5 为高电平
for (volatile uint32_t i = 0; i < 1000000; i++); // 延时
gpio_set_output(0, 5, 0); // 设置 PIN5 为低电平
for (volatile uint32_t i = 0; i < 1000000; i++); // 延时
}
}
GPIO 引脚可以通过配置中断来响应外部事件。中断配置涉及中断使能、中断处理函数的设置等步骤。
选择引脚功能:确定要使用的引脚及其功能(输入或输出)。
配置引脚方向:设置引脚为输入。
配置中断使能:使能引脚的中断。
设置中断触发条件:选择中断触发条件(上升沿、下降沿、双边沿)。
注册中断处理函数:注册中断处理函数,并配置中断优先级。
以下是一个配置 GPIO 中断的例子,当 GPIO 引脚发生上升沿触发时,中断处理函数会被调用。
#include "sam.h"
#include "system_sam_l21.h"
#include "clock_sam_l21.h"
#include "osc32kcal.h"
#include "delay.h"
// 中断处理函数
void GPIO_Interrupt_Handler(void) {
// 清除中断标志
PORT->Group[0].INTFLAG.reg = (1 << 5); // 清除 PORT0 PIN5 中断标志
// 处理中断事件
while (1) {
// 延时
delay_us(1000);
// 切换 LED 状态
PORT->Group[0].OUTTGL.reg = (1 << 6); // 切换 PORT0 PIN6 电平
}
}
// 初始化 GPIO 中断
void gpio_init_interrupt(uint8_t port, uint8_t pin) {
// 选择引脚功能
PORT->Group[port].DIRCLR.reg = (1 << pin); // 设置为输入
// 配置中断使能
PORT->Group[port].INTENSET.reg = (1 << pin); // 使能中断
// 设置中断触发条件
PORT->Group[port].PMUX[5 >> 1].reg = PORT_PMUX_PMUXO_A; // 设置 PIN5 为 PMUXA
PORT->Group[port].PINCFG[pin].reg = PORT_PINCFG_INEN | PORT_PINCFG_PULLEN; // 启用输入和内部上拉
PORT->Group[port].PINCFG[pin].reg |= PORT_PINCFG_PHEN; // 启用外设事件
PORT->Group[port].EVCTRL.reg |= PORT_EVCTRL_PIN0EVEN_RISE; // 设置上升沿触发
// 注册中断处理函数
NVIC_SetPriority(PORT_IRQn, 0); // 设置中断优先级
NVIC_EnableIRQ(PORT_IRQn); // 使能中断
}
int main(void) {
// 初始化系统时钟
SystemInit();
// 初始化 GPIO 引脚
gpio_init_output(0, 6); // 配置 PORT0 PIN6 为输出
gpio_init_interrupt(0, 5); // 配置 PORT0 PIN5 为中断输入
while (1) {
// 主循环
}
}
// 中断向量表
void PORT_Interrupt_Handler(void) {
GPIO_Interrupt_Handler();
}
// 中断向量表映射
void PORT_IRQHandler(void) {
PORT_Interrupt_Handler();
}
UART(Universal Asynchronous Receiver-Transmitter)是用于异步串行通信的外设。通过配置 UART,可以实现与其他设备的数据通信。
选择 UART 外设:确定要使用的 UART 外设。
配置波特率:设置通信的波特率。
配置数据格式:设置数据位、停止位和校验位。
使能 UART 外设:使能 UART 外设。
以下是一个简单的例子,初始化 UART 并发送和接收数据。
#include "sam.h"
#include "system_sam_l21.h"
#include "clock_sam_l21.h"
#include "osc32kcal.h"
#include "delay.h"
// UART 初始化
void uart_init(uint32_t baudrate) {
// 使能 GCLK
GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_SERCOM2_CORE);
while (GCLK->STATUS.bit.SYNCBUSY);
// 使能 SERCOM2
SERCOM2->USART.CTRLA.reg = SERCOM_USART_CTRLA_SWRST; // 软件复位
while (SERCOM2->USART.SYNCBUSY.bit.SWRST);
// 配置波特率
SERCOM2->USART.BAUD.reg = SERCOM_USART_BAUD_BAUD((uint32_t)(48000000 / (16 * baudrate))); // 设置波特率
// 配置数据格式
SERCOM2->USART.CTRLB.reg = SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN | SERCOM_USART_CTRLB_CHSIZE_8_BIT; // 使能发送和接收,8位数据
// 使能 USART
SERCOM2->USART.CTRLA.reg = SERCOM_USART_CTRLA_ENABLE;
while (SERCOM2->USART.SYNCBUSY.bit.ENABLE);
}
// 发送数据
void uart_send_char(char data) {
while (!(SERCOM2->USART.INTFLAG.bit.DRE)); // 等待数据寄存器为空
SERCOM2->USART.DATA.reg = data; // 发送数据
}
// 接收数据
char uart_receive_char(void) {
while (!(SERCOM2->USART.INTFLAG.bit.RXC)); // 等待数据接收
return (char)SERCOM2->USART.DATA.reg; // 返回接收到的数据
}
int main(void) {
// 初始化系统时钟
SystemInit();
// 初始化 UART
uart_init(9600);
while (1) {
char data = 'A';
uart_send_char(data); // 发送字符 'A'
delay_us(1000000); // 延时 1 秒
char received_data = uart_receive_char(); // 接收数据
if (received_data == 'B') {
PORT->Group[0].OUTSET.reg = (1 << 6); // 设置 LED 高电平
} else {
PORT->Group[0].OUTCLR.reg = (1 << 6); // 设置 LED 低电平
}
}
}
UART 也可以通过配置中断来实现数据的发送和接收。中断配置涉及中断使能、中断处理函数的设置等步骤。
选择 UART 外设:确定要使用的 UART 外设。
配置波特率:设置通信的波特率。
配置数据格式:设置数据位、停止位和校验位。
使能中断:使能发送和接收中断。
注册中断处理函数:注册中断处理函数,并配置中断优先级。
以下是一个配置 UART 中断的例子,当 UART 接收到数据时,中断处理函数会被调用。
#include "sam.h"
#include "system_sam_l21.h"
#include "clock_sam_l21.h"
#include "osc32kcal.h"
#include "delay.h"
// 中断处理函数
void UART_Interrupt_Handler(void) {
if (SERCOM2->USART.INTFLAG.bit.RXC) {
char received_data = (char)SERCOM2->USART.DATA.reg; // 读取接收到的数据
if (received_data == 'B') {
PORT->Group[0].OUTSET.reg = (1 << 6); // 设置 LED 高电平
} else {
PORT->Group[0].OUTCLR.reg = (1 << 6); // 设置 LED 低电平
}
}
if (SERCOM2->USART.INTFLAG.bit.DRE) {
char data = 'A';
SERCOM2->USART.DATA.reg = data; // 发送数据
}
}
// UART 初始化
void uart_init(uint32_t baudrate) {
// 使能 GCLK
GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_SERCOM2_CORE);
while (GCLK->STATUS.bit.SYNCBUSY);
// 使能 SERCOM2
SERCOM2->USART.CTRLA.reg = SERCOM_USART_CTRLA_SWRST; // 软件复位
while (SERCOM2->USART.SYNCBUSY.bit.SWRST);
// 配置波特率
SERCOM2->USART.BAUD.reg = SERCOM_USART_BAUD_BAUD((uint32_t)(48000000 / (16 * baudrate))); // 设置波特率
// 配置数据格式
SERCOM2->USART.CTRLB.reg = SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN | SERCOM_USART_CTRLB_CHSIZE_8_BIT; // 使能发送和接收,8位数据
// 使能 USART
SERCOM2->USART.CTRLA.reg = SERCOM_USART_CTRLA_ENABLE;
while (SERCOM2->USART.SYNCBUSY.bit.ENABLE);
// 使能中断
SERCOM2->USART.INTENSET.bit.RXC = 1; // 使能接收中断
SERCOM2->USART.INTENSET.bit.DRE = 1; // 使能发送中断
// 注册中断处理函数
NVIC_SetPriority(SERCOM2_IRQn, 0); // 设置中断优先级
NVIC_EnableIRQ(SERCOM2_IRQn); // 使能中断
}
int main(void) {
// 初始化系统时钟
SystemInit();
// 初始化 GPIO 引脚
gpio_init_output(0, 6); // 配置 PORT0 PIN6 为输出
// 初始化 UART
uart_init(9600);
while (1) {
// 主循环
}
}
// 中断向量表
void SERCOM2_Interrupt_Handler(void) {
UART_Interrupt_Handler();
}
// 中断向量表映射
void SERCOM2_IRQHandler(void) {
SERCOM2_Interrupt_Handler();
}
SPI(Serial Peripheral Interface)是一种同步串行通信接口,常用于主从设备之间的数据传输。通过配置 SPI,可以实现与外部 SPI 设备的数据通信。
选择 SPI 外设:确定要使用的 SPI 外设。
配置通信模式:设置 SPI 的通信模式(主模式或从模式)。
配置波特率:设置通信的波特率。
配置数据格式:设置数据位和时钟极性。
使能 SPI 外设:使能 SPI 外设。
以下是一个简单的例子,初始化 SPI 并发送和接收数据。
#include "sam.h"
#include "system_sam_l21.h"
#include "clock_sam_l21.h"
#include "osc32kcal.h"
#include "delay.h"
// 中断处理函数
void SPI_Interrupt_Handler(void) {
if (SERCOM4->SPI.INTFLAG.bit.RXC) {
char received_data = (char)SERCOM4->SPI.DATA.reg; // 读取接收到的数据
if (received_data == 'B') {
PORT->Group[0].OUTSET.reg = (1 << 6); // 设置 LED 高电平
} else {
PORT->Group[0].OUTCLR.reg = (1 << 6); // 设置 LED 低电平
}
SERCOM4->SPI.INTFLAG.bit.RXC = 1; // 清除接收中断标志
}
if (SERCOM4->SPI.INTFLAG.bit.DRE) {
char data = 'A';
SERCOM4->SPI.DATA.reg = data; // 发送数据
SERCOM4->SPI.INTFLAG.bit.DRE = 1; // 清除发送中断标志
}
}
// SPI 初始化
void spi_init_master(uint32_t baudrate) {
// 使能 GCLK
GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_SERCOM4_CORE);
while (GCLK->STATUS.bit.SYNCBUSY);
// 使能 SERCOM4
SERCOM4->SPI.CTRLA.reg = SERCOM_SPI_CTRLA_SWRST; // 软件复位
while (SERCOM4->SPI.SYNCBUSY.bit.SWRST);
// 配置 SPI 为主模式
SERCOM4->SPI.CTRLA.reg = SERCOM_SPI_CTRLA_MODE_MASTER | SERCOM_SPI_CTRLA_CPOL | SERCOM_SPI_CTRLA_CPHA; // 设置为主模式,时钟极性为高,时钟相位为0
// 配置波特率
SERCOM4->SPI.BAUD.reg = SERCOM_SPI_BAUD_BAUD(baudrate); // 设置波特率
// 配置数据格式
SERCOM4->SPI.CTRLB.reg = SERCOM_SPI_CTRLB_CHSIZE_8_BIT; // 设置数据位为8位
// 使能 SPI
SERCOM4->SPI.CTRLA.reg |= SERCOM_SPI_CTRLA_ENABLE;
while (SERCOM4->SPI.SYNCBUSY.bit.ENABLE);
// 使能中断
SERCOM4->SPI.INTENSET.bit.RXC = 1; // 使能接收中断
SERCOM4->SPI.INTENSET.bit.DRE = 1; // 使能发送中断
// 注册中断处理函数
NVIC_SetPriority(SERCOM4_IRQn, 0); // 设置中断优先级
NVIC_EnableIRQ(SERCOM4_IRQn); // 使能中断
}
// 发送数据
void spi_send_char(char data) {
while (!(SERCOM4->SPI.INTFLAG.bit.DRE)); // 等待数据寄存器为空
SERCOM4->SPI.DATA.reg = data; // 发送数据
}
// 接收数据
char spi_receive_char(void) {
while (!(SERCOM4->SPI.INTFLAG.bit.RXC)); // 等待数据接收
return (char)SERCOM4->SPI.DATA.reg; // 返回接收到的数据
}
int main(void) {
// 初始化系统时钟
SystemInit();
// 初始化 GPIO 引脚
gpio_init_output(0, 6); // 配置 PORT0 PIN6 为输出
// 初始化 SPI
spi_init_master(1000000);
while (1) {
char data = 'A';
spi_send_char(data); // 发送字符 'A'
delay_us(1000000); // 延时 1 秒
char received_data = spi_receive_char(); // 接收数据
if (received_data == 'B') {
PORT->Group[0].OUTSET.reg = (1 << 6); // 设置 LED 高电平
} else {
PORT->Group[0].OUTCLR.reg = (1 << 6); // 设置 LED 低电平
}
}
}
// 中断向量表
void SERCOM4_Interrupt_Handler(void) {
SPI_Interrupt_Handler();
}
// 中断向量表映射
void SERCOM4_IRQHandler(void) {
SERCOM4_Interrupt_Handler();
}
I2C(Inter-Integrated Circuit)是一种同步串行通信接口,常用于连接低速设备。通过配置 I2C,可以实现与外部 I2C 设备的数据通信。
选择 I2C 外设:确定要使用的 I2C 外设。
配置通信模式:设置 I2C 的通信模式(主模式或从模式)。
配置波特率:设置通信的波特率。
使能 I2C 外设:使能 I2C 外设。
以下是一个简单的例子,初始化 I2C 并发送和接收数据。
#include "sam.h"
#include "system_sam_l21.h"
#include "clock_sam_l21.h"
#include "osc32kcal.h"
#include "delay.h"
// I2C 初始化
void i2c_init_master(uint32_t baudrate) {
// 使能 GCLK
GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_SERCOM0_CORE);
while (GCLK->STATUS.bit.SYNCBUSY);
// 使能 SERCOM0
SERCOM0->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_SWRST; // 软件复位
while (SERCOM0->I2CM.SYNCBUSY.bit.SWRST);
// 配置 I2C 为主模式
SERCOM0->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE_MASTER | SERCOM_I2CM_CTRLA_SDAHOLD(3); // 设置为主模式,SDA 保持时间为 3 个 SCL 周期
// 配置波特率
SERCOM0->I2CM.BAUD.reg = SERCOM_I2CM_BAUD_BAUD((48000000 / (2 * baudrate)) - 1); // 设置波特率
// 使能 I2C
SERCOM0->I2CM.CTRLA.reg |= SERCOM_I2CM_CTRLA_ENABLE;
while (SERCOM0->I2CM.SYNCBUSY.bit.ENABLE);
}
// 发送数据
void i2c_send_char(uint8_t slave_address, char data) {
// 启动传输
SERCOM0->I2CM.ADDR.reg = (slave_address << 1) | 0; // 设置从设备地址并启动传输
while (SERCOM0->I2CM.INTFLAG.bit.MB); // 等待地址发送完成
// 发送数据
SERCOM0->I2CM.DATA.reg = data; // 发送数据
while (SERCOM0->I2CM.INTFLAG.bit.TB); // 等待数据发送完成
// 停止传输
SERCOM0->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); // 发送 STOP 条件
while (SERCOM0->I2CM.INTFLAG.bit.MB); // 等待 STOP 条件发送完成
}
// 接收数据
char i2c_receive_char(uint8_t slave_address) {
// 启动传输
SERCOM0->I2CM.ADDR.reg = (slave_address << 1) | 1; // 设置从设备地址并启动传输
while (SERCOM0->I2CM.INTFLAG.bit.MB); // 等待地址发送完成
// 读取数据
while (!SERCOM0->I2CM.INTFLAG.bit.RB); // 等待数据接收完成
char received_data = (char)SERCOM0->I2CM.DATA.reg; // 读取数据
// 停止传输
SERCOM0->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); // 发送 STOP 条件
while (SERCOM0->I2CM.INTFLAG.bit.MB); // 等待 STOP 条件发送完成
return received_data;
}
int main(void) {
// 初始化系统时钟
SystemInit();
// 初始化 I2C
i2c_init_master(100000); // 设置波特率为 100 kbps
while (1) {
char data = 'A';
i2c_send_char(0x50, data); // 发送字符 'A' 到从设备地址 0x50
delay_us(1000000); // 延时 1 秒
char received_data = i2c_receive_char(0x50); // 从从设备地址 0x50 接收数据
if (received_data == 'B') {
PORT->Group[0].OUTSET.reg = (1 << 6); // 设置 LED 高电平
} else {
PORT->Group[0].OUTCLR.reg = (1 << 6); // 设置 LED 低电平
}
}
}
I2C 也可以通过配置中断来实现数据的发送和接收。中断配置涉及中断使能、中断处理函数的设置等步骤。
选择 I2C 外设:确定要使用的 I2C 外设。
配置通信模式:设置 I2C 的通信模式(主模式或从模式)。
配置波特率:设置通信的波特率。
配置数据格式:设置数据位和时钟极性。
使能中断:使能发送和接收中断。
注册中断处理函数:注册中断处理函数,并配置中断优先级。
以下是一个配置 I2C 中断的例子,当 I2C 接收到数据时,中断处理函数会被调用。
#include "sam.h"
#include "system_sam_l21.h"
#include "clock_sam_l21.h"
#include "osc32kcal.h"
#include "delay.h"
// 中断处理函数
void I2C_Interrupt_Handler(void) {
if (SERCOM0->I2CM.INTFLAG.bit.RB) {
char received_data = (char)SERCOM0->I2CM.DATA.reg; // 读取接收到的数据
if (received_data == 'B') {
PORT->Group[0].OUTSET.reg = (1 << 6); // 设置 LED 高电平
} else {
PORT->Group[0].OUTCLR.reg = (1 << 6); // 设置 LED 低电平
}
SERCOM0->I2CM.INTFLAG.bit.RB = 1; // 清除接收中断标志
}
if (SERCOM0->I2CM.INTFLAG.bit.TB) {
char data = 'A';
SERCOM0->I2CM.DATA.reg = data; // 发送数据
SERCOM0->I2CM.INTFLAG.bit.TB = 1; // 清除发送中断标志
}
}
// I2C 初始化
void i2c_init_master(uint32_t baudrate) {
// 使能 GCLK
GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_SERCOM0_CORE);
while (GCLK->STATUS.bit.SYNCBUSY);
// 使能 SERCOM0
SERCOM0->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_SWRST; // 软件复位
while (SERCOM0->I2CM.SYNCBUSY.bit.SWRST);
// 配置 I2C 为主模式
SERCOM0->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE_MASTER | SERCOM_I2CM_CTRLA_SDAHOLD(3); // 设置为主模式,SDA 保持时间为 3 个 SCL 周期
// 配置波特率
SERCOM0->I2CM.BAUD.reg = SERCOM_I2CM_BAUD_BAUD((48000000 / (2 * baudrate)) - 1); // 设置波特率
// 使能 I2C
SERCOM0->I2CM.CTRLA.reg |= SERCOM_I2CM_CTRLA_ENABLE;
while (SERCOM0->I2CM.SYNCBUSY.bit.ENABLE);
// 使能中断
SERCOM0->I2CM.INTENSET.bit.RB = 1; // 使能接收中断
SERCOM0->I2CM.INTENSET.bit.TB = 1; // 使能发送中断
// 注册中断处理函数
NVIC_SetPriority(SERCOM0_IRQn, 0); // 设置中断优先级
NVIC_EnableIRQ(SERCOM0_IRQn); // 使能中断
}
// 发送数据
void i2c_send_char(uint8_t slave_address, char data) {
// 启动传输
SERCOM0->I2CM.ADDR.reg = (slave_address << 1) | 0; // 设置从设备地址并启动传输
while (SERCOM0->I2CM.INTFLAG.bit.MB); // 等待地址发送完成
// 发送数据
SERCOM0->I2CM.DATA.reg = data; // 发送数据
while (SERCOM0->I2CM.INTFLAG.bit.TB); // 等待数据发送完成
// 停止传输
SERCOM0->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); // 发送 STOP 条件
while (SERCOM0->I2CM.INTFLAG.bit.MB); // 等待 STOP 条件发送完成
}
// 接收数据
char i2c_receive_char(uint8_t slave_address) {
// 启动传输
SERCOM0->I2CM.ADDR.reg = (slave_address << 1) | 1; // 设置从设备地址并启动传输
while (SERCOM0->I2CM.INTFLAG.bit.MB); // 等待地址发送完成
// 读取数据
while (!SERCOM0->I2CM.INTFLAG.bit.RB); // 等待数据接收完成
char received_data = (char)SERCOM0->I2CM.DATA.reg; // 读取数据
// 停止传输
SERCOM0->I2CM.CTRLB.reg |= SERCOM_I2CM_CTRLB_CMD(3); // 发送 STOP 条件
while (SERCOM0->I2CM.INTFLAG.bit.MB); // 等待 STOP 条件发送完成
return received_data;
}
int main(void) {
// 初始化系统时钟
SystemInit();
// 初始化 GPIO 引脚
gpio_init_output(0, 6); // 配置 PORT0 PIN6 为输出
// 初始化 I2C
i2c_init_master(100000); // 设置波特率为 100 kbps
while (1) {
char data = 'A';
i2c_send_char(0x50, data); // 发送字符 'A' 到从设备地址 0x50
delay_us(1000000); // 延时 1 秒
char received_data = i2c_receive_char(0x50); // 从从设备地址 0x50 接收数据
if (received_data == 'B') {
PORT->Group[0].OUTSET.reg = (1 << 6); // 设置 LED 高电平
} else {
PORT->Group[0].OUTCLR.reg = (1 << 6); // 设置 LED 低电平
}
}
}
// 中断向量表
void SERCOM0_Interrupt_Handler(void) {
I2C_Interrupt_Handler();
}
// 中断向量表映射
void SERCOM0_IRQHandler(void) {
SERCOM0_Interrupt_Handler();
}
ADC(Analog-to-Digital Converter)是将模拟信号转换为数字信号的外设。通过配置 ADC,可以实现对模拟输入信号的采样和转换。
选择 ADC 外设:确定要使用的 ADC 外设。
配置参考电压:设置 ADC 的参考电压。
配置采样时间:设置 ADC 的采样时间。
配置通道:选择要采样的通道。
使能 ADC 外设:使能 ADC 外设。
以下是一个简单的例子,初始化 ADC 并读取模拟输入信号。
#include "sam.h"
#include "system_sam_l21.h"
#include "clock_sam_l21.h"
#include "osc32kcal.h"
#include "delay.h"
// ADC 初始化
void adc_init(void) {
// 使能 GCLK
GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_ADC);
while (GCLK->STATUS.bit.SYNCBUSY);
// 使能 ADC
ADC->CTRLA.reg = ADC_CTRLA_SWRST; // 软件复位
while (ADC->STATUS.bit.SYNCBUSY);
// 配置 ADC
ADC->REFCTRL.reg = ADC_REFCTRL_REFSEL_INTVCC1; // 使用内部 1.1V 参考电压
ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV16 | ADC_CTRLB_RESSEL_12BIT; // 设置预分频器为 16,12 位分辨率
ADC->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN0; // 选择负输入为 GND,正输入为 PIN0
ADC->CTRLA.reg |= ADC_CTRLA_ENABLE; // 使能 ADC
while (ADC->STATUS.bit.SYNCBUSY); // 等待同步
}
// 读取 ADC 值
uint16_t adc_read(uint8_t channel) {
// 配置通道
ADC->INPUTCTRL.reg = (ADC->INPUTCTRL.reg & ~ADC_INPUTCTRL_MUXPOS_Msk) | (channel << ADC_INPUTCTRL_MUXPOS_Pos); // 选择正输入通道
// 开始转换
ADC->SWTRIG.reg = ADC_SWTRIG_START; // 软件触发转换
while (!ADC->INTFLAG.bit RESRDY); // 等待转换完成
// 读取结果
return ADC->RESULT.reg;
}
int main(void) {
// 初始化系统时钟
SystemInit();
// 初始化 GPIO 引脚
gpio_init_output(0, 6); // 配置 PORT0 PIN6 为输出
// 初始化 ADC
adc_init();
while (1) {
uint16_t adc_value = adc_read(0); // 读取通道 0 的 ADC 值
if (adc_value > 2048) {
PORT->Group[0].OUTSET.reg = (1 << 6); // 设置 LED 高电平
} else {
PORT->Group[0].OUTCLR.reg = (1 << 6); // 设置 LED 低电平
}
delay_us(1000000); // 延时 1 秒
}
}
ADC 也可以通过配置中断来实现数据的读取。中断配置涉及中断使能、中断处理函数的设置等步骤。
选择 ADC 外设:确定要使用的 ADC 外设。
配置参考电压:设置 ADC 的参考电压。
配置采样时间:设置 ADC 的采样时间。
配置通道:选择要采样的通道。
使能 ADC 外设:使能 ADC 外设。
使能中断:使能转换完成中断。
注册中断处理函数:注册中断处理函数,并配置中断优先级。
以下是一个配置 ADC 中断的例子,当 ADC 转换完成时,中断处理函数会被调用。
#include "sam.h"
#include "system_sam_l21.h"
#include "clock_sam_l21.h"
#include "osc32kcal.h"
#include "delay.h"
// 中断处理函数
void ADC_Interrupt_Handler(void) {
if (ADC->INTFLAG.bit.RESRDY) {
uint16_t adc_value = ADC->RESULT.reg; // 读取 ADC 值
if (adc_value > 2048) {
PORT->Group[0].OUTSET.reg = (1 << 6); // 设置 LED 高电平
} else {
PORT->Group[0].OUTCLR.reg = (1 << 6); // 设置 LED 低电平
}
ADC->INTFLAG.bit.RESRDY = 1; // 清除中断标志
}
}
// ADC 初始化
void adc_init(void) {
// 使能 GCLK
GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_ADC);
while (GCLK->STATUS.bit.SYNCBUSY);
// 使能 ADC
ADC->CTRLA.reg = ADC_CTRLA_SWRST; // 软件复位
while (ADC->STATUS.bit.SYNCBUSY);
// 配置 ADC
ADC->REFCTRL.reg = ADC_REFCTRL_REFSEL_INTVCC1; // 使用内部 1.1V 参考电压
ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV16 | ADC_CTRLB_RESSEL_12BIT; // 设置预分频器为 16,12 位分辨率
ADC->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN0; // 选择负输入为 GND,正输入为 PIN0
// 使能中断
ADC->INTENSET.bit.RESRDY = 1; // 使能转换完成中断
// 注册中断处理函数
NVIC_SetPriority(ADC_IRQn, 0); // 设置中断优先级
NVIC_EnableIRQ(ADC_IRQn); // 使能中断
// 使能 ADC
ADC->CTRLA.reg |= ADC_CTRLA_ENABLE;
while (ADC->STATUS.bit.SYNCBUSY); // 等待同步
}
// 开始 ADC 转换
void adc_start_conversion(uint8_t channel) {
// 配置通道
ADC->INPUTCTRL.reg = (ADC->INPUTCTRL.reg & ~ADC_INPUTCTRL_MUXPOS_Msk) | (channel << ADC_INPUTCTRL_MUXPOS_Pos); // 选择正输入通道
// 开始转换
ADC->SWTRIG.reg = ADC_SWTRIG_START; // 软件触发转换
}
int main(void) {
// 初始化系统时钟
SystemInit();
// 初始化 GPIO 引脚
gpio_init_output(0, 6); // 配置 PORT0 PIN6 为输出
// 初始化 ADC
adc_init();
while (1) {
adc_start_conversion(0); // 开始通道 0 的 ADC 转换
delay_us(1000000); // 延时 1 秒
}
}
// 中断向量表
void ADC_Interrupt_Handler(void) {
ADC_Interrupt_Handler();
}
// 中断向量表映射
void ADC_IRQHandler(void) {
ADC_Interrupt_Handler();
}
DAC(Digital-to-Analog Converter)是将数字信号转换为模拟信号的外设。通过配置 DAC,可以实现对模拟输出信号的生成。
选择 DAC 外设:确定要使用的 DAC 外设。
配置参考电压:设置 DAC 的参考电压。
配置输出通道:选择要使用的输出通道。
使能 DAC 外设:使能 DAC 外设。
以下是一个简单的例子,初始化 DAC 并生成模拟输出信号。
#include "sam.h"
#include "system_sam_l21.h"
#include "clock_sam_l21.h"
#include "osc32kcal.h"
#include "delay.h"
// DAC 初始化
void dac_init(void) {
// 使能 GCLK
GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_DAC);
while (GCLK->STATUS.bit.SYNCBUSY);
// 使能 DAC
DAC->CTRLA.reg = DAC_CTRLA_SWRST; // 软件复位
while (DAC->STATUS.bit.SYNCBUSY);
// 配置 DAC
DAC->CTRLB.reg = DAC_CTRLB_REFSEL_VREFA; // 使用外部参考电压 VREFA
DAC->CTRLA.reg = DAC_CTRLA_ENABLE | DAC_CTRLA_RESSEL_12BIT; // 使能 DAC,12 位分辨率
while (DAC->STATUS.bit.SYNCBUSY); // 等待同步
}
// 设置 DAC 输出
void dac_set_value(uint16_t value) {
DAC->DATA.reg = value; // 设置 DAC 输出值
}
int main(void) {
// 初始化系统时钟
SystemInit();
// 初始化 GPIO 引脚
gpio_init_output(0, 6); // 配置 PORT0 PIN6 为输出
// 初始化 DAC
dac_init();
while (1) {
dac_set_value(2048); // 设置 DAC 输出为 2048(1/2 Vref)
delay_us(1000000); // 延时 1 秒
dac_set_value(0); // 设置 DAC 输出为 0
delay_us(1000000); // 延时 1 秒
}
}
DAC 也可以通过配置中断来实现数据的更新。中断配置涉及中断使能、中断处理函数的设置等步骤。
选择 DAC 外设:确定要使用的 DAC 外设。
配置参考电压:设置 DAC 的参考电压。
配置输出通道:选择要使用的输出通道。
使能 DAC 外设:使能 DAC 外设。
使能中断:使能数据更新中断。
注册中断处理函数:注册中断处理函数,并配置中断优先级。
以下是一个配置 DAC 中断的例子,当 DAC 数据更新完成时,中断处理函数会被调用。
#include "sam.h"
#include "system_sam_l21.h"
#include "clock_sam_l21.h"
#include "osc32kcal.h"
#include "delay.h"
// 中断处理函数
void DAC_Interrupt_Handler(void) {
if (DAC->INTFLAG.bit.UNDERRUN) {
DAC->INTFLAG.bit.UNDERRUN = 1; // 清除下溢中断标志
}
if (DAC->INTFLAG.bit.EMPTY) {
DAC->DATA.reg = 2048; // 设置 DAC 输出值
DAC->INTFLAG.bit.EMPTY = 1; // 清除数据更新中断标志
}
}
// DAC 初始化
void dac_init(void) {
// 使能 GCLK
GCLK->CLKCTRL.reg = (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_DAC);
while (GCLK->STATUS.bit.SYNCBUSY);
// 使能 DAC
DAC->CTRLA.reg = DAC_CTRLA_SWRST; // 软件复位
while (DAC->STATUS.bit.SYNCBUSY);
// 配置 DAC
DAC->CTRLB.reg = DAC_CTRLB_REFSEL_VREFA | DAC_CTRLB_LEFTADJ; // 使用外部参考电压 VREFA,左对齐
DAC->CTRLA.reg = DAC_CTRLA_ENABLE | DAC_CTRLA_RESSEL_12BIT; // 使能 DAC,12 位分辨率
while (DAC->STATUS.bit.SYNCBUSY); // 等待同步
// 使能中断
DAC->INTENSET.bit.EMPTY = 1; // 使能数据更新中断
// 注册中断处理函数
NVIC_SetPriority(DAC_IRQn, 0); // 设置中断优先级
NVIC_EnableIRQ(DAC_IRQn); // 使能中断
}
// 设置 DAC 输出
void dac_set_value(uint16_t value) {
DAC->DATA.reg = value; // 设置 DAC 输出值
}
int main(void) {
// 初始化系统时钟
SystemInit();
// 初始化 GPIO 引脚
gpio_init_output(0, 6); // 配置 PORT0 PIN6 为输出
// 初始化 DAC
dac_init();
while (1) {
dac_set_value(2048); // 设置 DAC 输出为 2048(1/2 Vref)
delay_us(1000000); // 延时 1 秒
dac_set_value(0); // 设置 DAC 输出为 0
delay_us(1000000); // 延时 1 秒
}
}
// 中断向量表
void DAC_Interrupt_Handler(void) {
DAC_Interrupt_Handler();
}
// 中断向量表映射
void DAC_IRQHandler(void) {
DAC_Interrupt_Handler();
}
以上内容详细介绍了如何使用 Microchip 的 SAM L 系列单片机(基于 ARM Cortex-M0+)来开发 GPIO、UART、SPI、I2C、ADC 和 DAC 的外设驱动程序,并提供了具体的代码示例和操作步骤。希望这些内容对你的开发工作有所帮助。