Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
CAN总线
RT-Thread学习营
星火一号
RT-Thread SPARK星火一号 CAN的通信内核详解
发布于 2023-09-19 13:52:05 浏览:89
订阅该版
[tocm] ## RT-Thread SPARK CAN的通信内核详解 ### CAN发送 #### 1. rt_device_write ```c rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) { /* parameter check */ RT_ASSERT(dev != RT_NULL); RT_ASSERT(rt_object_get_type(&dev->parent) == RT_Object_Class_Device); if (dev->ref_count == 0) { rt_set_errno(-RT_ERROR); return 0; } /* call device_write interface */ if (device_write != RT_NULL) { // rt_kprintf("device_write:%d\n",device_write(dev, pos, buffer, size)); return device_write(dev, pos, buffer, size); } /* set error code */ rt_set_errno(-RT_ENOSYS); return 0; } ``` 其实挺一目了然的,就是调用device的device_write接口 #### 2. device_write ```c #define device_write (dev->write) ``` ```c dev->write = rt_pipe_write; ``` 还挺能藏 #### 3. rt_pipe_write ```c static rt_size_t rt_pipe_write(rt_device_t device, rt_off_t pos, const void *buffer, rt_size_t count) { uint8_t *pbuf; rt_size_t write_bytes = 0; rt_pipe_t *pipe = (rt_pipe_t *)device; if (device == RT_NULL) { rt_set_errno(EINVAL); return 0; } if (count == 0) return 0; pbuf = (uint8_t *)buffer; rt_mutex_take(&pipe->lock, -1); while (write_bytes < count) { int len = rt_ringbuffer_put(pipe->fifo, &pbuf[write_bytes], count - write_bytes); if (len <= 0) break; write_bytes += len; } rt_mutex_release(&pipe->lock); return write_bytes; } ``` 1. 函数接受四个参数 `device`、`pos`、`buffer` 和 `count`,分别表示设备指针、写入位置(未使用)、数据缓冲区指针和要写入的数据字节数。 2. 将数据缓冲区指针 `buffer` 强制转换为 `uint8_t` 类型的指针,方便按字节操作数据。 3. 使用互斥锁 `pipe->lock` 来保护管道操作的原子性,通过调用 `rt_mutex_take` 函数获取互斥锁。 4. 进入循环,不断写入数据到管道,直到写入的字节数达到指定的 `count`。 - 调用 `rt_ringbuffer_put` 函数将数据写入管道的环形缓冲区(`pipe->fifo`)。该函数返回实际写入的字节数 `len`。 - 如果 `len` 小于等于 0,说明无法继续写入数据到管道,跳出循环。 5. 释放互斥锁,通过调用 `rt_mutex_release` 函数释放互斥锁。 6. 返回已写入的字节数 `write_bytes`。 #### 4. rt_ringbuffer_put **rt_ringbuffer**是啥? ```c /* ring buffer */ struct rt_ringbuffer { rt_uint8_t *buffer_ptr; /* use the msb of the {read,write}_index as mirror bit. You can see this as * if the buffer adds a virtual mirror and the pointers point either to the * normal or to the mirrored buffer. If the write_index has the same value * with the read_index, but in a different mirror, the buffer is full. * While if the write_index and the read_index are the same and within the * same mirror, the buffer is empty. The ASCII art of the ringbuffer is: * * mirror = 0 mirror = 1 * +---+---+---+---+---+---+---+|+~~~+~~~+~~~+~~~+~~~+~~~+~~~+ * | 0 | 1 | 2 | 3 | 4 | 5 | 6 ||| 0 | 1 | 2 | 3 | 4 | 5 | 6 | Full * +---+---+---+---+---+---+---+|+~~~+~~~+~~~+~~~+~~~+~~~+~~~+ * read_idx-^ write_idx-^ * * +---+---+---+---+---+---+---+|+~~~+~~~+~~~+~~~+~~~+~~~+~~~+ * | 0 | 1 | 2 | 3 | 4 | 5 | 6 ||| 0 | 1 | 2 | 3 | 4 | 5 | 6 | Empty * +---+---+---+---+---+---+---+|+~~~+~~~+~~~+~~~+~~~+~~~+~~~+ * read_idx-^ ^-write_idx * * The tradeoff is we could only use 32KiB of buffer for 16 bit of index. * But it should be enough for most of the cases. * * Ref: http://en.wikipedia.org/wiki/Circular_buffer#Mirroring */ rt_uint16_t read_mirror : 1; rt_uint16_t read_index : 15; rt_uint16_t write_mirror : 1; rt_uint16_t write_index : 15; /* as we use msb of index as mirror bit, the size should be signed and * could only be positive. */ rt_int16_t buffer_size; }; enum rt_ringbuffer_state { RT_RINGBUFFER_EMPTY, //环形缓冲区空 RT_RINGBUFFER_FULL, //环形缓冲区满 RT_RINGBUFFER_HALFFULL, //环形缓冲区半满 }; ``` #### 定义 环形缓冲区是嵌入式系统中十分重要的一种数据结构,比如在串口处理中,串口中断接收数据直接往环形缓冲区丢数据,而应用可以从环形缓冲区取数据进行处理,这样数据在读取和写入的时候都可以在这个缓冲区里循环进行,程序员可以根据自己需要的数据大小来决定自己使用的缓冲区大小。 #### 特点 - 使用读取索引和写入索引的高位作为镜像位,通过镜像位的不同来表示缓冲区的状态。 - 当读取索引和写入索引的值相同时,但镜像位不同时,表示缓冲区已满。read_index==write_index&& - 当读取索引和写入索引的值相同时,并且镜像位也相同时,表示缓冲区为空。 - 使用环形缓冲区的好处是当一个元素被消费时,不需要移动其他元素,因为新的元素可以直接写入到最后一个位置上,实现了一种先进先出(FIFO)的数据结构。这在很多应用中非常有用。 #### 回到实际代码 ```c rt_size_t rt_ringbuffer_put(struct rt_ringbuffer *rb, const rt_uint8_t *ptr, rt_uint16_t length) { rt_uint16_t size; RT_ASSERT(rb != RT_NULL); /* whether has enough space */ size = rt_ringbuffer_space_len(rb); /* no space */ if (size == 0) return 0; /* drop some data */ if (size < length) length = size; if (rb->buffer_size - rb->write_index > length) { /* read_index - write_index = empty space */ rt_memcpy(&rb->buffer_ptr[rb->write_index], ptr, length); /* this should not cause overflow because there is enough space for * length of data in current mirror */ rb->write_index += length; return length; } rt_memcpy(&rb->buffer_ptr[rb->write_index], &ptr[0], rb->buffer_size - rb->write_index); rt_memcpy(&rb->buffer_ptr[0], &ptr[rb->buffer_size - rb->write_index], length - (rb->buffer_size - rb->write_index)); /* we are going into the other side of the mirror */ rb->write_mirror = ~rb->write_mirror; rb->write_index = length - (rb->buffer_size - rb->write_index); return length; } ``` 该函数的目的是向环形缓冲区中写入数据,根据当前写指针的位置和可用空间的大小,判断数据是否可以连续写入当前镜像,如果不能,则跨越镜像边界继续写入。函数返回实际写入的数据长度 #### 5. 存放数据的管道结构体 ```c /** * Pipe Device */ struct rt_pipe_device { struct rt_device parent; rt_bool_t is_named; /* ring buffer in pipe device */ struct rt_ringbuffer *fifo; rt_uint16_t bufsz; rt_uint8_t readers; rt_uint8_t writers; rt_wqueue_t reader_queue; rt_wqueue_t writer_queue; struct rt_mutex lock; }; typedef struct rt_pipe_device rt_pipe_t; ``` **rt_pipe_write**函数把我们的device指针强转为**管道device** 在它的**fifo**数据缓冲区中存储数据 ### CAN接收 前面和上面差不多,直接跳到pipe层 #### rt_pipe_read ```c static rt_size_t rt_pipe_read(rt_device_t device, rt_off_t pos, void *buffer, rt_size_t count) { uint8_t *pbuf; rt_size_t read_bytes = 0; rt_pipe_t *pipe = (rt_pipe_t *)device; if (device == RT_NULL) { rt_set_errno(EINVAL); return 0; } if (count == 0) return 0; pbuf = (uint8_t *)buffer; rt_mutex_take(&(pipe->lock), RT_WAITING_FOREVER); while (read_bytes < count) { int len = rt_ringbuffer_get(pipe->fifo, &pbuf[read_bytes], count - read_bytes); if (len <= 0) break; read_bytes += len; } rt_mutex_release(&pipe->lock); return read_bytes; } ``` 调用 `rt_ringbuffer_get` 函数从管道的环形缓冲区中读取数据到缓冲区中,返回实际读取的字节数。将读取的数据追加到 `pbuf` 中,并更新已读取字节数。 #### rt_ringbuffer_get ```c /** * @brief Get data from the ring buffer. * * @param rb A pointer to the ring buffer. * @param ptr A pointer to the data buffer. * @param length The size of the data we want to read from the ring buffer. * * @return Return the data size we read from the ring buffer. */ rt_size_t rt_ringbuffer_get(struct rt_ringbuffer *rb, rt_uint8_t *ptr, rt_uint16_t length) { rt_size_t size; RT_ASSERT(rb != RT_NULL); /* whether has enough data */ size = rt_ringbuffer_data_len(rb); /* no data */ if (size == 0) return 0; /* less data */ if (size < length) length = size; if (rb->buffer_size - rb->read_index > length) { /* copy all of data */ rt_memcpy(ptr, &rb->buffer_ptr[rb->read_index], length); /* this should not cause overflow because there is enough space for * length of data in current mirror */ rb->read_index += length; return length; } rt_memcpy(&ptr[0], &rb->buffer_ptr[rb->read_index], rb->buffer_size - rb->read_index); rt_memcpy(&ptr[rb->buffer_size - rb->read_index], &rb->buffer_ptr[0], length - (rb->buffer_size - rb->read_index)); /* we are going into the other side of the mirror */ rb->read_mirror = ~rb->read_mirror; rb->read_index = length - (rb->buffer_size - rb->read_index); return length; } ``` 1. 首先通过调用 `rt_ringbuffer_data_len(rb)` 函数获取当前环形缓冲区中的数据大小。 2. 如果数据大小为0,则表示缓冲区中没有数据可读,直接返回0。 3. 如果数据大小小于要读取的长度 `length`,则将要读取的长度修改为数据大小,避免越界访问。 4. 判断从当前读取索引位置开始,到缓冲区末尾的数据是否足够读取 `length` 大小的数据。如果足够,则直接将数据复制到指定的数据缓冲区 `ptr` 中,并更新读取索引。 5. 如果从当前读取索引位置开始,到缓冲区末尾的数据不足以满足读取 `length` 大小的数据,则先将缓冲区末尾的数据复制到 `ptr` 中,然后将剩余长度的数据从缓冲区开头复制到 `ptr` 的剩余空间中。 6. 更新读取索引,将其设置为剩余数据的起始位置,并且切换读取索引的镜像位,以便正确读取数据。 ### 综上所述 SPARK的can例程看似并不涉及通俗意义的can协议,只是把一段数据层层调用接口,最终写入一个can设备的pipe强转类型的**rt_ringbuffer**环形缓冲区种,然后再由要读的设备从那里读出来。比如说can0想要向can1写数据,那么就会直接把数据写在can1的环形缓存区中。
3
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
BURRIEROW
一个小白
文章
4
回答
0
被采纳
0
关注TA
发私信
相关文章
1
我也来传一个CANOpen移植,RTT+STM32F107+CanOpenNode
2
谁有STM32裸跑的CANopen程序啊???
3
CAN驱动程序框架
4
CAN驱动接口如何规范一下
5
RTT无法进入线程.Cannot access Memory
6
编译提示arm-none-eabi/bin/ld: cannot find crt0.o: No such file o
7
rtt 2.1.0 正式版 mdk4 bsp/stm32 编译canapp.c错误
8
STM32F10XCAN驱动使用的问题
9
2.1版本stm32f10x分支bxcan驱动波特率设置的bug
10
rtthread2.1.0下,找不到can1设备
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
机器人操作系统 (ROS2) 和 RT-Thread 通信
4
五分钟玩转RT-Thread新社区
5
国产MCU移植系列教程汇总,欢迎查看!
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
AT
SPI
Bootloader
FinSH
ART-Pi
CAN总线
Hardfault
USB
文件系统
DMA
RT-Thread
SCons
线程
RT-Thread Nano
MQTT
stm32
ESP8266
rt-smart
WIZnet_W5500
RTC
flash
ota
UART
FAL
packages_软件包
I2C
freemodbus
cubemx
潘多拉开发板_Pandora
定时器
PWM
BSP
ADC
socket
AB32VG1
SDIO
keil_MDK
中断
消息队列_msg_queue
编译报错
Debug
C++_cpp
msh
QEMU
SFUD
MicroPython
本月问答贡献
出出啊
1463
个答案
324
次被采纳
小小李sunny
1379
个答案
274
次被采纳
张世争
700
个答案
155
次被采纳
crystal266
518
个答案
152
次被采纳
whj467467222
1215
个答案
146
次被采纳
本月文章贡献
出出啊
1
篇文章
11
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
4
篇文章
6
次点赞
crystal266
2
篇文章
5
次点赞
whj467467222
2
篇文章
4
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部