Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
CAN总线
RT-Thread学习营
星火1号_spark_星火一号_开发板
RT-Thread SPARK星火一号 CAN的通信内核详解
发布于 2023-09-19 13:52:05 浏览:771
订阅该版
[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
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
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
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
UART
WIZnet_W5500
ota在线升级
PWM
cubemx
flash
freemodbus
BSP
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
编译报错
Debug
rt_mq_消息队列_msg_queue
SFUD
msh
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
出出啊
1517
个答案
342
次被采纳
小小李sunny
1444
个答案
290
次被采纳
张世争
812
个答案
177
次被采纳
crystal266
547
个答案
161
次被采纳
whj467467222
1222
个答案
148
次被采纳
本月文章贡献
出出啊
1
篇文章
2
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
2
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部