如何在Milk-V duo的小核FreeRTOS中跑i2c
前言
(1)PLCT实验室实习生长期招聘:招聘信息链接
(2)如果有嵌入式企业需要招聘湖南区域日常实习生,任何区域的暑假嵌入式软件实习岗位,可C站直接私聊,或者邮件:zhangyixu02@gmail.com,此消息至2025年1月1日前均有效
(3)本来要尝试在RT-Thread
中跑i2c
,打算先Linux
中测试i2c
,再FreeRTOS
,最后RT-Thread
,最终发现卡在RT-Thread
这一步了,因为移植一个文件就马上出现另外一个文件缺失缝缝补补搞了很久最终真的无能为力了。
在duo的小核FreeRTOS跑i2c的方法
修改部分
(1)因为
duo
的版本更新比较快,我就使用比较熟悉的Duo-V1.0.5
版本进行讲解。
git clone -b Duo-V1.0.5 https://github.com/milkv-duo/duo-buildroot-sdk.git
cd duo-buildroot-sdk
cp freertos/cvitek/hal/cv180x/i2c/src/hal_dw_i2c.c freertos/cvitek/task/comm/src/riscv64/
cp freertos/cvitek/hal/cv180x/i2c/include/hal_dw_i2c.h freertos/cvitek/task/comm/include/
cp freertos/cvitek/driver/i2c/include/i2c.h freertos/cvitek/task/comm/include/
(2)进入
FreeRTOSConfig.h
文件,关闭configUSE_TICK_HOOK
这个宏。
vim freertos/cvitek/kernel/include/riscv64/FreeRTOSConfig.h
#define configUSE_TICK_HOOK 0
(3)进入的796行,将
IC3_INTR
修改为I2C3_INTR
,这里有可能是官方写错了。
vim freertos/cvitek/task/comm/src/riscv64/hal_dw_i2c.c
//修改前
request_irq(IC3_INTR, i2c_dw_isr, 0, "IC2_INTR int", &dw_i2c[i2c_id]);
//修改后
request_irq(I2C3_INTR, i2c_dw_isr, 0, "IC2_INTR int", &dw_i2c[i2c_id]);
(4)在
comm_main.c
中利用freertos
的xTaskCreate()
函数创建一个my_task_test()
的任务函数。
vim freertos/cvitek/task/comm/src/riscv64/comm_main.c
/****************************************************************************
* Function definitions
****************************************************************************/
DEFINE_CVI_SPINLOCK(mailbox_lock, SPIN_MBOX);
#include "hal_dw_i2c.h"
void my_task_test()
{
uint8_t data= 0,data_read;
hal_i2c_init(I2C0);
printf("hal_i2c_init after\n");
uint8_t *data_write =&data;
for (;;)
{
//i2c端口,从机地址,寄存器地址,7位从机地址,写入的数据,写1个数据
hal_i2c_write(I2C0, 0x01, 0x28, 1, data_write, 1);
hal_i2c_read(I2C0, 0x28, 0x17, 1, &data_read, 1);
printf("test the RTOS: %d\r\n", data);
vTaskDelay(100);
data++;
}
}
void main_cvirtos(void)
{
printf("create cvi task\n");
/* Start the tasks and timer running. */
xTaskCreate(my_task_test, "my_task", 1024 * 8, NULL, 1, NULL);
vTaskStartScheduler();
/* If all is well, the scheduler will now be running, and the following
line will never be reached. If the following line does execute, then
there was either insufficient FreeRTOS heap memory available for the idle
and/or timer tasks to be created, or vTaskStartScheduler() was called from
User mode. See the memory management section on the FreeRTOS web site for
more details on the FreeRTOS heap http://www.freertos.org/a00111.html. The
mode from which main() is called is set in the C start up code and must be
a privileged mode (not user mode). */
printf("cvi task end\n");
for (;;)
;
}
接线说明
(1)这里需要准备的材料有,一个
Typec
数据线,一个USB转TTL模块,一个逻辑分析仪。
上机测试结果
(1)对于逻辑分析仪的采样率必须达到被测信号最高频率的 5 倍以上,推荐 10 倍以上。
(2)通过阅读代码我们可以知道,I2C
的频率应该是400KHZ
,5倍以上,那么就是2MHZ
。因此逻辑分析仪的采样率设置为2MHZ
。
(3)然后我这里每隔
100ms
进行一次,所以采样深度尽量设置在200k samples
以上。我这里为了保险,设置的采样深度为1M samples
。
(4)最终发现,成功进行了
I2C
通讯。因为我们这里I2C
接口是连接的逻辑分析仪,主机给0x01
地址发送写和0x28
地址发送读信息的时候,发现找不到从机的应答信号,因此停止了通讯。
(5)细心的人肯定发现了一个问题,第二个我们明明是向0x28地址读取数据,怎么抓取到的是写指令。讲实话,这个地方我也比较懵,但是从代码中来看,似乎又应该是这样的,因为代码的
i2c_xfer_init()
函数其实就是调用的i2c_write_cmd_data()
,所以应该是写指令没有问题。再深入的具体细节我也不太想分析了,毕竟我不是他们芯片原厂的人。走到这一步已经尽力了。
(6)个人认为,他的读时序应该是采用的方式3。所以一开始是主机写数据。
测试流程
(1)这里我将会介绍我是如何测试出在
Milk-V duo
的小核FreeRTOS
中跑I2C
,方便各位学习,如果有错也欢迎各位大佬指出更正。
前期猜测和测试
(1)刚开始的时候,我看到
Milk-V duo
的FreeRTOS
中存在i2c.c
,打算直接在comm_main.c
中包含i2c.h
文件,然后引用相关的API
函数。执行编译指令,发现他说找不到对应的函数。
export MILKV_BOARD=milkv-duo
source milkv/boardconfig-milkv-duo.sh
source build/milkvsetup.sh
defconfig cv1800b_milkv_duo_sd
build_all
(2)当时我是比较纳闷的,怎么会找不到
i2c.c
的源代码呢?执行find
指令后发现,i2c.c
是在freertos/cvitek/driver/i2c/src/
路径中,按理说是有这个文件的呀。
find -name *i2c*.c
(3)后面就想,那就看看编译过程中产生的日志信息吧。因为编译过程一般都是先将所以c文件编译,但是不链接,最后都编译完成之后,再进行链接的。所以说,我这里只需要关注 -c 文件路径 就可以知道编译了那些.c文件了。但是,不幸的是,我发现
freertos/cvitek/driver/i2c/
并不在编译路径中。
build_all | tee build_all_log.txt
(4)之后尝试阅读
i2c.c
的源码,发现其实就是对hal_dw_i2c.c
中的函数进行了一次封装。
(5)因为如上原因,我打算直接将
duo
的hal
层代码复制到编译路径里面。那么就不再需要写我不那么熟悉的CMake
来添加编译路径了,哈哈哈哈。
找到官方i2c代码
(1)依旧是先执行find指令,查找到官方的
i2c
代码。我们知道Milk-V duo
是cv1800
芯片,但是从下面查找发现,只有cv181x
和cv1835
这两款芯片的hal
层i2c
代码,那么怎么办呢?
find -name *i2c*.c
(2)之后我翻阅了一下
GitHub
,发现是有cv180x
的i2c
库的,只不过他这里写的是../cv181x/i2c
,意思说cv180x
和cv181x
的i2c
库函数是一样的。
官方i2c代码可能存在的问题
(1)当我尝试将
hal_dw_i2c.c
和hal_dw_i2c.h
文件移植进编译路径的时候,发现hal_dw_i2c.c
文件的796行IC3_INTR
报错,说没有被定义。
(2)之后我看了一下,IC3_INTR
其实就是一个宏定义,然后我尝试将IC3_INTR
修改为71,编译进入芯片,发现会卡死。
#define IC3_INTR 71
(2)最后我发现还有一个
I2C3_INTR
,之后修改成I2C3_INTR
就没有问题了。所以说我猜测i2c
hal
层代码这个地方也许写错了。
编译路径查找
(1)依旧是将编译过程中的打印信息导出来。 -c 文件路径就是编译录, -I 文件路径就是头文件包含路径。
build_all | tee build_all_log.txt
API接口的使用
(1)一般
I2C
只会进行读写操作,所以我只关注了这三个函数。因为找不到官方的相关API
接口介绍函数,所以这三个函数的使用完全是我看代码猜的。(其实只要是对I2C
协议稍微熟悉一点,猜这个还是不算太难。)
void hal_i2c_init(uint8_t i2c_id);
int hal_i2c_write(uint8_t i2c_id, uint8_t dev, uint16_t addr, uint16_t alen, uint8_t *buffer, uint16_t len);
int hal_i2c_read(uint8_t i2c_id, uint8_t dev, uint16_t addr, uint16_t alen, uint8_t *buffer, uint16_t len);
hal_i2c_init()函数使用
(1)这个从名字上,就很清楚是干嘛的的。明显就是
I2C
的初始化函数,那么传入的i2c_id
是什么呢?
(2)那我们就看看源码,从源码一路追溯,就很容易发现如果要初始化I2Cx
,那么就传入I2Cx
。
I2C时序简单讲解
(1)后面几个参数就设计
I2C
时序知识了,为了防止有朋友不了解,或者说不太熟悉了。先介绍一下:
<1>I2C
在通讯的时候,先发送一个起始信号(SCL
高电平,SDA
下降沿),然后主机广播一个从机地址。I2C
的从机地址有两种,一种是7位地址,一种是10地址的。
<2>找到从机地址之后,从机发送ACK
回应信号。I2C
有两种策略,你可以自己选择,第一种是主机等待从机的ACK
回应信号,第二种是不等待,自顾自的发数据。测试结果发现Milk-V duo
的I2C
通讯似乎只能是第一种,必须等待ACK
回应信号,否则会终止通讯过程。
(这也很好的介绍了,明明代码中写了那么多数据,最终逻辑分析仪抓到的数据那么短)
<3>然后主机发送这里需要分成读写两种情况讨论,加粗部分表示主机发送的信号,没加粗部分是从机的信号
(注:Milk-V duo的手册的I2C章节时序图感觉有问题,是经典的I2C时序没错。但是Milk-V duo的代码中读时序应该是采用的第三种,主机先发数据,然后再读数据)
- 写: 起始信号 —> 从机地址+写 —> 应答信号 —> 要写入从机的寄存器地址 —> 应答信号 —> 写入的数据 —> 应答信号 —> 停止信号
- 读: 起始信号 —> 从机地址+写 —> 应答信号 —> 要读取从机的寄存器地址 —> 应答信号 —> 起始信号 —> 从机地址+读 —> 应答信号 —> 从机发送数据 —> 回应信号 —> 停止信号
hal_i2c_write()函数使用
(1)如果明白了上述所说的I2C时序,那么这个函数的传参就很好猜了。
/**
* @brief 主机I2C写入数据
*
* @param i2c_id i2c端口,传入I2Cx(x为1,2,3...)
* -dev 从机地址
* -addr 寄存器地址
* -alen 如果是7bit从机,传入1。10bit从机传入2
* -buffer 写入数据缓冲区
* -len 要写入几个字节数据
*
* @return 成功写入数据,返回0
*/
int hal_i2c_write(uint8_t i2c_id, uint8_t dev, uint16_t addr, uint16_t alen, uint8_t *buffer, uint16_t len);
hal_i2c_read()函数使用
(1)同理
/**
* @brief 主机I2C写入数据
*
* @param i2c_id i2c端口,传入I2Cx(x为1,2,3...)
* -dev 从机地址
* -addr 寄存器地址
* -alen 如果是7bit从机,传入1。10bit从机传入2
* -buffer 读取数据缓冲区
* -len 要读取几个字节数据
*
* @return 成功写入数据,返回0
*/
int hal_i2c_read(uint8_t i2c_id, uint8_t dev, uint16_t addr, uint16_t alen, uint8_t *buffer, uint16_t len);
参考文章
(1)如何自己生成fip.bin在Milkv-duo上跑freertos
(2)逻辑分析仪采样率和采样深度
(3)I2C总线协议详解(特点、通信过程、典型I2C时序)
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!