Pico PIO状态机实现外设:CAN - 示例1
Pico拥有一组PIO协处理器。它们是实时控制器,能够以确定的时序执行逻辑。非常适合运行严格定时的序列和状态机,以及实现额外的外设(如这里的CAN)。序列和状态机,以及实现额外的外设(如这里的CAN)。
PIO引擎不易编程,也不易学习。但有一些很好的示例可供参考。我正在复习Kevin O'Connor精彩的can2040库。
https://github.com/KevinOConnor/can2040
在这篇文章中:我将创建一个测试平台,并证明我可以接收CAN通信。
这篇博客的目的不是编写PIO(可编程输入输出)开发者教程。我试图回溯开发者如何使用PIO指令实现一个标准协议。我使用的工具包括C语言、CMake、VSCode和Pico C SDK。预期的技能水平是:你能够构建和运行官方的pico-examples。
在通常情况下(这里跳过OSI模型),CAN(控制器局域网)有两层:
- 逻辑层:TTL、5V、3.3V等数字信号,由一些智能元件实现,如外设、控制器位操作,本例中为PIO状态机。
- 总线层:物理层,通常通过驱动器/收发器IC实现。我将在这里使用一个物理驱动器IC,这是我自己设计的一个小东西,我经常使用。你也可以仅使用普通电阻和二极管制作一个简易的CAN驱动器。
作为通信对等体,我使用另外两个具备CAN能力的设备:一个带有CAN外设的衍生设备和一个Microchip CAN总线分析仪。一个带有CAN屏蔽板的Arduino(MKR)同样适用。
这篇文章不深入代码。它只是一个测试平台,让我看看是否能建立一个CAN通信。
获取can2040库
从Kevin的Kevin's github克隆或下载源代码。
https://github.com/KevinOConnor/can2040
设置一个环境变量指向这个位置。这将确保我们可以创建一个不依赖于你存放第三方代码位置的make脚本。我使用的是VSCode,并将在那里定义环境变量。你也可以在你的操作系统设置、shell脚本等中设置。
项目文件夹
我创建了一个包含CMake文件和src文件夹的目录,src文件夹包含一个非常简单的测试文件(从这里获取的,但我在项目中引用了can2040源代码而不是导入它们)。
来源:src/main.c
// source: https://gitea.predevolution-technologies.de/anme/CAN2040_Test
#include
#include
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/irq.h"
#include "can2040.h"
#include "RP2040.h"
static struct can2040 cbus;
static void can2040_cb(struct can2040 *cd, uint32_t notify, struct can2040_msg *msg)
{
// Add message processing code here...
}
static void PIOx_IRQHandler(void)
{
can2040_pio_irq_handler(&cbus);
}
void canbus_setup(void)
{
uint32_t pio_num = 0;
uint32_t sys_clock = 125000000, bitrate = 125000;
uint32_t gpio_rx = 14, gpio_tx = 15;
// Setup canbus
can2040_setup(&cbus, pio_num);
can2040_callback_config(&cbus, can2040_cb);
// Enable irqs
irq_set_exclusive_handler(PIO0_IRQ_0_IRQn, PIOx_IRQHandler);
NVIC_SetPriority(PIO0_IRQ_0_IRQn, 1);
NVIC_EnableIRQ(PIO0_IRQ_0_IRQn);
// Start canbus
can2040_start(&cbus, sys_clock, bitrate, gpio_rx, gpio_tx);
}
int main(void){
const uint LED_PIN = PICO_DEFAULT_LED_PIN;
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
int32_t ledState = 0;
stdio_init_all();
canbus_setup();
while(1){
printf("bla\n");
gpio_put(LED_PIN, ledState);
if (ledState == 0){
ledState = 1;
}
else{
ledState = 0;
}
sleep_ms(1000);
}
}
./CMakeList.txt
cmake_minimum_required(VERSION 3.13)
# Pull in SDK (must be before project)
include(pico_sdk_import.cmake)
project(can2040_project0 C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
#I've set this to allow breakpoints on any source line
set(PICO_DEOPTIMIZED_DEBUG=1)
pico_sdk_init()
add_executable(can2040_project0
source/main.c
$ENV{CAN2040_LIB_PATH}/src/can2040.c
)
target_include_directories(can2040_project0 PRIVATE
${CMAKE_CURRENT_LIST_DIR}/source
$ENV{CAN2040_LIB_PATH}/src
)
target_link_libraries(can2040_project0 pico_stdlib cmsis_core)
pico_add_extra_outputs(can2040_project0)
侧边栏:Pico PIO和其他预测性、时间关键型协处理器
Pico PIO状态机是小型协控制器,它们以可预测的速度执行每条指令。这类控制器从不会被中断,不会监听中断(但可以触发中断)。它们只是可靠地时钟同步并执行它们的小程序。通常,它们可以快速访问一些GIO引脚。
还有其他一些控制器和处理器具有类似的功能:
TI(德州仪器)的Hercules微控制器具有高端定时器。它与Pico PIO引擎非常相似,但Hercules指令额外支持角度、相位等(这些是用于多相电源和电机驱动的功能)。(同样来自TI的)BeagleBone具有PRU(可编程实时单元),这也与Pico PIO引擎的功能非常接近。PRU可以直接访问内存和DMA引擎。
这三者共同的特点是,它们不仅能够产生精确定时的信号,还能够采样输入信号。并且它们是超灵活的定时器,可以处理计数、相位移动、正交编码等功能。
测试
我启动了一个调试会话,在can2040_cb()回调函数处设置了断点。然后,从我的另一台设备发送了一条CAN消息:
RP2040在断点处停止,我可以看到消息ID、DLC(长度)和有效载荷:
我附上了我的VSCode项目的ZIP文件。别忘了下载can2040源代码并设置环境变量。