Microchip 系列:SAM L 系列 (基于 ARM Cortex-M0+)_(7).外设驱动开发

外设驱动开发

在外设驱动开发中,我们将详细介绍如何使用 Microchip 的 SAM L 系列单片机(基于 ARM Cortex-M0+)来开发各种外设驱动程序。这部分内容将涵盖常见的外设,如 GPIO、UART、SPI、I2C、ADC 和 DAC 等,并提供具体的代码示例和操作步骤。

在这里插入图片描述

GPIO 驱动开发

GPIO 引脚配置

GPIO(General Purpose Input/Output)是单片机中最基本的外设之一。通过配置 GPIO,可以实现对外部设备的控制和数据采集。SAM L 系列单片机的 GPIO 配置通常通过寄存器操作来完成。

配置步骤
  1. 选择引脚功能:确定要使用的引脚及其功能(输入或输出)。

  2. 配置引脚方向:设置引脚为输入或输出。

  3. 配置引脚输出电平:对于输出引脚,设置其初始电平。

  4. 配置引脚输入状态:对于输入引脚,读取其状态。

代码示例

以下是一个简单的例子,配置一个 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 引脚可以通过配置中断来响应外部事件。中断配置涉及中断使能、中断处理函数的设置等步骤。

配置步骤
  1. 选择引脚功能:确定要使用的引脚及其功能(输入或输出)。

  2. 配置引脚方向:设置引脚为输入。

  3. 配置中断使能:使能引脚的中断。

  4. 设置中断触发条件:选择中断触发条件(上升沿、下降沿、双边沿)。

  5. 注册中断处理函数:注册中断处理函数,并配置中断优先级。

代码示例

以下是一个配置 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 驱动开发

UART 初始化

UART(Universal Asynchronous Receiver-Transmitter)是用于异步串行通信的外设。通过配置 UART,可以实现与其他设备的数据通信。

配置步骤
  1. 选择 UART 外设:确定要使用的 UART 外设。

  2. 配置波特率:设置通信的波特率。

  3. 配置数据格式:设置数据位、停止位和校验位。

  4. 使能 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 也可以通过配置中断来实现数据的发送和接收。中断配置涉及中断使能、中断处理函数的设置等步骤。

配置步骤
  1. 选择 UART 外设:确定要使用的 UART 外设。

  2. 配置波特率:设置通信的波特率。

  3. 配置数据格式:设置数据位、停止位和校验位。

  4. 使能中断:使能发送和接收中断。

  5. 注册中断处理函数:注册中断处理函数,并配置中断优先级。

代码示例

以下是一个配置 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 驱动开发

SPI 初始化

SPI(Serial Peripheral Interface)是一种同步串行通信接口,常用于主从设备之间的数据传输。通过配置 SPI,可以实现与外部 SPI 设备的数据通信。

配置步骤
  1. 选择 SPI 外设:确定要使用的 SPI 外设。

  2. 配置通信模式:设置 SPI 的通信模式(主模式或从模式)。

  3. 配置波特率:设置通信的波特率。

  4. 配置数据格式:设置数据位和时钟极性。

  5. 使能 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 驱动开发

I2C 初始化

I2C(Inter-Integrated Circuit)是一种同步串行通信接口,常用于连接低速设备。通过配置 I2C,可以实现与外部 I2C 设备的数据通信。

配置步骤
  1. 选择 I2C 外设:确定要使用的 I2C 外设。

  2. 配置通信模式:设置 I2C 的通信模式(主模式或从模式)。

  3. 配置波特率:设置通信的波特率。

  4. 使能 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 也可以通过配置中断来实现数据的发送和接收。中断配置涉及中断使能、中断处理函数的设置等步骤。

配置步骤
  1. 选择 I2C 外设:确定要使用的 I2C 外设。

  2. 配置通信模式:设置 I2C 的通信模式(主模式或从模式)。

  3. 配置波特率:设置通信的波特率。

  4. 配置数据格式:设置数据位和时钟极性。

  5. 使能中断:使能发送和接收中断。

  6. 注册中断处理函数:注册中断处理函数,并配置中断优先级。

代码示例

以下是一个配置 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 驱动开发

ADC 初始化

ADC(Analog-to-Digital Converter)是将模拟信号转换为数字信号的外设。通过配置 ADC,可以实现对模拟输入信号的采样和转换。

配置步骤
  1. 选择 ADC 外设:确定要使用的 ADC 外设。

  2. 配置参考电压:设置 ADC 的参考电压。

  3. 配置采样时间:设置 ADC 的采样时间。

  4. 配置通道:选择要采样的通道。

  5. 使能 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 也可以通过配置中断来实现数据的读取。中断配置涉及中断使能、中断处理函数的设置等步骤。

配置步骤
  1. 选择 ADC 外设:确定要使用的 ADC 外设。

  2. 配置参考电压:设置 ADC 的参考电压。

  3. 配置采样时间:设置 ADC 的采样时间。

  4. 配置通道:选择要采样的通道。

  5. 使能 ADC 外设:使能 ADC 外设。

  6. 使能中断:使能转换完成中断。

  7. 注册中断处理函数:注册中断处理函数,并配置中断优先级。

代码示例

以下是一个配置 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 驱动开发

DAC 初始化

DAC(Digital-to-Analog Converter)是将数字信号转换为模拟信号的外设。通过配置 DAC,可以实现对模拟输出信号的生成。

配置步骤
  1. 选择 DAC 外设:确定要使用的 DAC 外设。

  2. 配置参考电压:设置 DAC 的参考电压。

  3. 配置输出通道:选择要使用的输出通道。

  4. 使能 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 也可以通过配置中断来实现数据的更新。中断配置涉及中断使能、中断处理函数的设置等步骤。

配置步骤
  1. 选择 DAC 外设:确定要使用的 DAC 外设。

  2. 配置参考电压:设置 DAC 的参考电压。

  3. 配置输出通道:选择要使用的输出通道。

  4. 使能 DAC 外设:使能 DAC 外设。

  5. 使能中断:使能数据更新中断。

  6. 注册中断处理函数:注册中断处理函数,并配置中断优先级。

代码示例

以下是一个配置 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 的外设驱动程序,并提供了具体的代码示例和操作步骤。希望这些内容对你的开发工作有所帮助。

你可能感兴趣的:(单片机开发,arm开发,驱动开发,架构,java,数据库,嵌入式硬件)