本页面介绍Atmel ATSAMD20 XPlained Pro开发板及其入门指南。
可用开发板
DigiKey在售的ATSAMD20-XPRO (SAMD20 Xplained Pro)
DigiKey在售的ATPROTO1-XPRO (原型扩展板)
DigiKey在售的ATIO1-XPRO (I/O扩展板)
DigiKey在售的ATOLED1-XPRO (128x32 OLED扩展板)
特性与优势
采用ARM Cortex-M0+内核,SAMD20是Atmel发布的最新闪存微控制器之一。有四个不同的设备系列,D10、D11、D20和D21。结合其广泛的外设集和低功耗工艺,这使其成为产品线的一个很好的补充。
SAMD20能够以最高48MHz的速度运行。这结合了Atmel事件系统、单周期32x32乘法器、2级流水线和单周期I/O访问。所有部件都具有Atmel SERCOM(串行通信接口),这是一个非常灵活的串行接口,可以配置为I2C、SPI或USART。每个接口都可以通过多路复用分配到不同的I/O引脚,增加了部件的灵活性。有多个16位定时器/计数器实例,每个都可以编程以执行频率和波形生成。有两种睡眠模式,空闲和待机,可以通过软件选择。
软件
Atmel Studio 6.2(最新版本)- 如果您有以前版本的Studio 6.x,我建议您在安装最新版本之前卸载这些版本。我发现从6.1(build 2562)升级时,Atmel Software Framework (ASF) 基本上从Studio中被移除。我回去删除了Studio 6.1(2562)、6.0和5.0(以确保安全),然后安装了6.1(2674)的完整版本。然后安装了ASF的3.9.1版本并可用。
硬件
如果您熟悉AVR Xplained套件,那么您知道这些套件是为了让设计者(您)更容易开始使用各种Atmel设备而创建的。自推出以来,可用开发板的数量已经扩展,主要面向高端产品,如ATxmega、UC3和SAM4。利用这些板子,他们创建了一些附加板,以展示如何使用已安装的微控制器实现各种传感器。在从现场和客户那里获得反馈后,Atmel决定推出一款Xplained Pro板,为用户(再次是你)提供更多且更便捷的访问各种GPIO和外设的途径。当然,当你这样做时,你必须有一些可以连接到这些相同外设的板子。因此,Atmel开发了适合Xplained Pro板子的附加板(在上面列出的可用板子下)。为了让你更轻松,Atmel将他们的SHA204产品集成到这些新板子中,以便Atmel Studio可以识别哪些板子已连接。Atmel Studio实际上会告诉你已连接到它的板子以及附加的扩展板。
SAMD20 Xplained Pro 的特性
- Atmel ATSAMD20J18微控制器
- 嵌入式调试器(EDBG)
- USB接口
- 通过串行线调试(SWD)在板子上对SAM D20进行编程和调试
- 通过串行线调试(SWD)对外部目标进行编程和调试
- 数字I/O
- 两个机械按钮(用户按钮和复位按钮)
- 一个用户LED
- 三个扩展头
- 两种可能的电源
- 外部电源
- 嵌入式调试器USB
- 32kHz晶体
示例项目
我将在这里展示的所有示例都是为Atmel Studio编写和编译的。IAR和Keil可能支持也可能不支持这个芯片和板子。截至当前写作时,我还没有看到可用的支持。如果你想获取Atmel软件框架中当前可用应用程序的列表,请访问Atmel SAMD20 Pro ASF。您还可以找到有关支持的Atmel设备和板的ASF的更多信息。
LED 切换
也许最基本且最受欢迎的程序是让LED闪烁。为什么,你问?好吧,让我们思考一下。当你在板上切换LED时,到底发生了什么?首先,您必须初始化系统。这意味着您的时钟已设置并运行。这意味着您已初始化GPIO引脚,以便能够驱动或吸收LED的电流,并且在某些情况下,具有按钮输入。这也意味着您必须设置中断。它可能来自按钮按下或定时器。如果您使用定时器(如下一个示例所示),您还需要初始化它。本质上,您必须让芯片的一些最重要部分运行才能使LED闪烁。那么,让我们开始吧。
我很幸运能够获得SAMD20 Xplained Pro板的预发布版本以及使用它的软件。虽然与上面显示的板没有明显不同,但在引脚位置方面存在一些差异。然而,对于我们的目的来说,这不会产生影响。代码中有一些差异,但不足以产生影响。其中一个在下面的代码块中。我认为下面的版本比他们在更新版本中的内容更容易理解。如果您从未使用过Atmel Studio,我建议您转到帮助菜单下的查看帮助(或按CTRL-F1)并浏览Atmel Studio文件和Atmel Software Framework文件。
因此,使用Studio 6,我从开始页面****(Start Page) 选择了新建示例项目**(New Example Project)** ,从所有项目****(All Projects) 中更改并按套件排序。我选择了正确版本的ASF,向下滚动直到找到SAM D20 Xplained Pro并选择了LED 切换应用程序 (LED Toggle Application) 。这个项目的主要功能是当您按下SW0时,LED0会亮起。此时您会想“这有什么意义?你可以硬编码它来做同样的事情。然而,这正是Atmel使其更容易更改内容并展示部件功能的地方。
在主文件(led_toggle.c)中,靠近顶部有一些定义。通过更改这些#define语句,你可以更改部件对按钮按下的反应方式。它可以通过PORT驱动程序、EIC驱动程序、使用SysTick处理程序或使用EIC处理程序来反应,使用中断或不使用中断。这使你有能力调试使用各种方法来查看引脚变化的代码。
/*
USE_INTERRUPTS USE_EIC Result
-------------- ------- ---------------------------------------------
false false Polled via PORT driver
false true Polled via EIC driver
true false Polled via PORT driver, using SysTick handler
true true Polled via EIC driver, using EIC handler
*/
#define USE_INTERRUPTS true
#define USE_EIC true
那么,让我们分解一下Atmel使这个程序运行的步骤。第一步是系统初始化,system_init()。这个函数依次调用system_clock_init()和system_board_init()函数。
#if CONF_CLOCK_XOSC_ENABLE == true
struct system_clock_source_xosc_config xosc_conf;
system_clock_source_xosc_get_default_config(&xosc_conf);
xosc_conf.external_clock = CONF_CLOCK_XOSC_EXTERNAL_CRYSTAL;
xosc_conf.startup_time = CONF_CLOCK_XOSC_STARTUP_TIME;
xosc_conf.auto_gain_control = CONF_CLOCK_XOSC_AUTO_GAIN_CONTROL;
xosc_conf.frequency = CONF_CLOCK_XOSC_EXTERNAL_FREQUENCY;
xosc_conf.on_demand = CONF_CLOCK_XOSC_ON_DEMAND;
xosc_conf.run_in_standby = CONF_CLOCK_XOSC_RUN_IN_STANDBY;
system_clock_source_xosc_set_config(&xosc_conf);
system_clock_source_enable(SYSTEM_CLOCK_SOURCE_XOSC);
#endif
system_board_init()函数将完全依赖于所使用的开发板。当你制作自己的开发板时,你必须制作自己的board_init函数。在这个函数中,Atmel所做的只是设置LED输出和按钮输入的端口引脚。我不得不承认,他们这样做的方式似乎有点复杂,但考虑到他们所有的开发板,它似乎有效。
void system_board_init(void)
{
struct port_config pin_conf;
port_get_config_defaults(&pin_conf);
/* Configure LEDs as outputs, turn them off */
pin_conf.direction = PORT_PIN_DIR_OUTPUT;
port_pin_set_config(LED_0_PIN, &pin_conf);
port_pin_set_output_level(LED_0_PIN, LED_0_INACTIVE);
/* Set buttons as inputs */
pin_conf.direction = PORT_PIN_DIR_INPUT;
pin_conf.input_pull = PORT_PIN_PULL_UP;
port_pin_set_config(BUTTON_0_PIN, &pin_conf);
}
此时,代码开始进入一系列#if语句,以确定它应该如何对按钮按下做出反应。因此,根据你如何设置USE_INTERRUPTS和USE_EIC,将决定它是否使用EIC、中断和systick。
#if USE_EIC == true
configure_extint();
#endif
#if USE_INTERRUPTS == true
# if USE_EIC == false
configure_systick_handler();
# else
configure_eic_callback();
# endif
system_interrupt_enable_global();
while (true) {
/* Do nothing - use interrupts */
}
#else
# if USE_EIC == false
while (true) {
update_led_state();
}
# else
while (true) {
if (extint_chan_is_detected(BUTTON_0_EIC_LINE)) {
extint_chan_clear_detected(BUTTON_0_EIC_LINE);
update_led_state();
}
}
# endif
#endif
再次,如果你通过Atmel Studio并选择新建示例项目 (New Example Project) ,你可以找到这个项目。
延迟示例
或者像我们喜欢说的,“闪烁”。同样的推理适用于这个示例,就像前一个示例一样。你实际上是在使用芯片的主要部分来实现这一点。所以,就像之前一样,我进入Studio 6.1并选择新建示例项目( New Example Project ) 。在这种情况下,我选择了延迟服务示例 (Delay Service Example) - SAM D20 Xplained Pro。阅读描述,你会看到“初始化系统时钟并以恒定的1Hz频率闪烁LED。”这几乎就是闪烁。这就是我遇到一点小问题的地方。就像任何示例项目一样,你期望看到一个包含 Main 的文件,通常就在你面前。在这种情况下,并非如此。这就是我最终看到的内容。
所以,由于没有一个明显的文件显示 Main,我不得不通过解决方案资源管理器 (Solution Explorer) 来找到它。它最终位于 ASF>common2>services>delay>example 下,标记为 delay_example.c。
所以,就像 LED 切换一样,你有 system_init() 函数,它调用 system_clock_init() 和 system_board_init() 函数。现在,由于这是一个“延迟示例 (Delay Example) ”,你需要设置一些东西来获得那个延迟。这个特定的示例在 system_init() 完成后有一个 delay_init() 调用。
这个函数的第一行实际上是调用另一个返回频率的函数。在这种情况下,system_gclk_gen_get_hz(0) 读取通道 0 的时钟控制寄存器,并以赫兹为单位返回时钟频率。接下来的两行分别获取每毫秒和每微秒的周期数。
这个初始化的最后一行是 SysTick,它是 ARM 核心内系统计时器的实现。启用后,它是一个相当标准的倒计时计时器,一旦它溢出就会设置一个标志。这一行所做的就是加载它使用的时钟源并启用 SysTick。在这种情况下,SysTick_CTRL_CLKSOURCE_Msk 设置为 1,这意味着它使用的是处理器时钟而不是参考时钟。
void delay_init(void)
{
cycles_per_ms = system_gclk_gen_get_hz(0);
cycles_per_ms /= 1000;
cycles_per_us = cycles_per_ms / 1000;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;
}
所以,接下来的几行是设置端口引脚。在 ASF 或代码中未提及的是,这是为与 OLED1 板一起使用而编写的。我添加的内容,如下所示,是设置 PA14,以便也使用 USER LED0。如果你有 OLED1 板,那么 LED0 和 LED2 都将被使用。
所以,在 ASF 中,端口引脚已经有一个默认设置,即作为输入并启用内部上拉。第一行设置了一个名为 pin 的 port_config 结构。下一行获取默认配置,即方向和 input_pull,并将其存储在 pin 结构中。每次要更改端口引脚或组时,都需要在修改之前调用 port_get_config_defaults() 函数,从而为你提供一个设置好的引脚配置。最后一行将引脚方向从输入更改为输出。
port_pin_set_config() 函数将新配置写入相应的端口引脚,在这种情况下是 PA13 和 PA14。他们在该函数中注明,如果引脚方向设置为输出,则忽略上拉/下拉输入配置。然而,这并不完全准确。在查看该函数后,内部上拉仍然启用,并且在调用该函数时仍然写回引脚寄存器。现在,作为输出,上拉是否激活并没有太大区别。当你将输出电平设置为高时,它会变高。当你将其设置为低时,它会变低。
另一方面,这些部件上有一些方便的寄存器,可以更轻松地在输入和输出之间切换。每个端口都有DIRCLR、DIRSET和DIRTGL寄存器。DIRCLR是端口数据方向清除,SET是设置,TGL是切换。因此,当你向CLEAR中的某一位写入1时,它将清除DIR寄存器中的相应位,将该引脚配置为输入。向SET中的某一位写入1,它将设置DIR寄存器中的该位,使其成为输出。当你向TOGGLE中的某一位写入1时,它将在输入/输出和输出/输入之间切换。因此,当你开始使用这些特定寄存器时,需要记住内部上拉可能仍然启用。
回到代码所示,最后两行所做的只是将引脚的输出设置为高电平。此函数使用逻辑电平来设置或清除输出,true = 1,false = 0。
struct port_config pin;
port_get_config_defaults(&pin);
pin.direction = PORT_PIN_DIR_OUTPUT;
port_pin_set_config(PIN_PA13, &pin);
port_pin_set_config(PIN_PA14, &pin);
port_pin_set_output_level(PIN_PA13, true);
port_pin_set_output_level(PIN_PA14, true);
因此,在完成所有这些操作后,你最终可以让LED闪烁。
我还将PA14添加到这些函数中,以使LED0闪烁。因此,在for()循环中,端口引脚的输出电平被切换。这些寄存器与相同引脚的方向寄存器类似,用于设置、清除或切换端口引脚。在这种情况下,该函数将向OUTTGL寄存器中的相应位写入1以切换该引脚。
如你所见,每个for()循环都有自己的延迟函数:delay_s()、delay_ms()和delay_cycles()。函数名称非常直观,delay_s()将延迟若干秒,依此类推。然而,我认为这里他们变得过于复杂了。首先,他们将delay_s(delay)定义为cpu_delay_s(delay),而cpu_delay_s(delay)又定义为delay_cycles_ms(1000 * delay)。此函数是一个while()循环,它是一个倒计时,具有delay_cycles(cycles_per_ms)函数。
所以我在cycle_counter.h文件中找到了这个函数。这是一个静态内联void函数,它设置了SysTick计数器并等待它运行完毕。有趣的是,delay_s、delay_ms和delay_us(这里没有使用)最终都会调用delay_cycles()函数。当然,delay_ms和delay_us被定义为cpu_delay_ms和cpu_delay_us,而它们又被定义为delay_cycles_ms和delay_cycles_us。这些函数最终都会调用相同的静态内联函数来处理SysTick。如果你还记得上面的内容,cycles_per_ms等于系统时钟频率除以1000。函数delay_cycles_ms()使用该值调用delay_cycles()函数来确定要延迟多少个周期。同样,delay_cycles_us()使用上面显示的cycles_per_us值来确定delay_cycles()函数将等待多少个周期。
所以,按照他们设置这个while()循环的方式,如果你直接运行它,最后一个循环将不会被执行。如果你注释掉前两个中的一个,那么最后一个就会运行。你不会真正注意到它们都在运行,因为它只有5000次计数,对应100个时钟周期。如果你把它增加到50000,你会更容易看到它。
while (true) {
for (int i = 0; i < 5; i++) {
port_pin_toggle_output_level(PIN_PA13);
port_pin_toggle_output_level(PIN_PA14);
delay_s(1);
}
for (int i = 0; i < 50; i++) {
port_pin_toggle_output_level(PIN_PA13);
port_pin_toggle_output_level(PIN_PA14);
delay_ms(100);
}
for (int i = 0; i < 5000; i++) {
port_pin_toggle_output_level(PIN_PA13);
port_pin_toggle_output_level(PIN_PA14);
delay_cycles(100);
}
}
所以,为了解决最后一个函数在代码中无法被执行的问题,我做了一些调整。我将i设为main()中的一个volatile变量,这样所有三个for()循环都可以被执行。我还决定改变PA14(LED0)的初始输出电平,以便当PA13(LED2)亮时它熄灭。这样两个LED会在不同的时间亮起。
int main(void)
{
system_init();
delay_init();
volatile int i;
struct port_config pin;
port_get_config_defaults(&pin);
pin.direction = PORT_PIN_DIR_OUTPUT;
port_pin_set_config(PIN_PA13, &pin);
port_pin_set_config(PIN_PA14, &pin);
port_pin_set_output_level(PIN_PA13, true);
port_pin_set_output_level(PIN_PA14, false);
while (true) {
for (i = 0; i < 5; i++) {
port_pin_toggle_output_level(PIN_PA13);
port_pin_toggle_output_level(PIN_PA14);
delay_s(1);
}
for (i = 0; i < 50; i++) {
port_pin_toggle_output_level(PIN_PA13);
port_pin_toggle_output_level(PIN_PA14);
delay_ms(100);
}
for (i = 0; i < 50000; i++) {
port_pin_toggle_output_level(PIN_PA13);
port_pin_toggle_output_level(PIN_PA14);
delay_cycles(100);
}
}
}
结论
这两个示例项目几乎是最基础的。第一个项目中,你按下按钮,LED就会亮起。第二个项目使用定时器来闪烁LED。ASF中列出了更多的项目,我相信会有更多关于ATSAMD20 Xplained Pro板和扩展板的页面。



