K230大核低速驱动API参考
1 概述
1.1 概述
UART:
通用异步收发器,该总线双向通信,可以实现全双工传输和接收。在嵌入式设计中,UART用来与PC进行通信,包括与监控调试器和其它器件,如EEPROM通信。
I2C:
由Philips公司(2006年迁移到NXP)在1980年代初开发的一种简单、双线双向的同步串行总线,它利用一根时钟线和一根数据线在连接总线的两个器件之间进行信息的传递,为设备之间数据交换提供了一种简单高效的方法。每个连接到总线上的器件都有唯一的地址,任何器件既可以作为主机也可以作为从机,但同一时刻只允许有一个主机。
GPIO:
(general porpose intput output)通用输入输出端口的简称。可以通过软件控制其输出和输入,通俗来说就是常用引脚,可以控制引脚的高低电平,对其进行读取或者写入。
Hard-lock:
嘉楠自研模块,用于同核不通进程间或异核之间对共享资源的互斥而实现的硬件互斥锁,可用于对共享资源的互斥使用。
ADC:
ADC 即模拟数字转换器(Analog-to-digital converter),是指将连续变化的模拟信号转换为离散的数字信号的器件。真实世界的模拟信号,例如温度、压力、声音或者图像等,需要转换成更容易储存、处理和发射的数字形式。模数转换器可以实现这个功能,在各种不同的产品中都可以找到它的身影。
WDT: WDT 是watchdog的简称,本质上是一个定时器,软件程序需要每隔一段时间喂一次狗,如果WDT超时则可以产生一个中断信号或复位信号到CPU,由此通过软硬件结合的方式防止程序运行异常而不复位。
OTP: OTP 主要用于存储安全敏感的机密信息,例如 bootrom 的固件信息、加解密密钥、签名信息以及用户自己定义的安全信息等。OTP 集成在安全模块 PUF 中,为整个 SoC 提供安全存储功能,保护根密钥和启动代码等关键数据不被攻击者破坏。大核侧 OTP 驱动主要提供读、写两种功能,可读区域为包括生产信息在内的 24Kbits 空间;写区域为 OTP 的最后 512 bytes 空间。
TS: K230 TS(Temperature Sensor),自研温度传感器,采用 TSMC 12nm 工艺。TS 的应用场景是降频。大核侧 TS 驱动主要提供读功能,在读 TS 之前,首先需要配置 TS 寄存器使能信号、输出模式,然后才能读出芯片的结温。另外,TS 寄存器每 2.6s 读取一次芯片结温。
PWM:是一种对模拟信号电平进行数字编码的方法,通过不同频率的脉冲使用方波的占空比用来对一个具体模拟信号的电平进行编码,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替所需要波形的设备。
RTC:实时时钟可以提供精确的实时时间,它可以用于产生年、月、日、时、分、秒、星期等信息。
TIMER(HWTIMER):通过内部计数器模块对内外部脉冲信号进行计数,可以工作在定时器模式和计数器模式。
1.2 功能描述
UART驱动结构如下图:
I2C驱动结构如下图:
GPIO驱动结构如下图:
Hard-lock驱动结构如下图:
adc 驱动结构如下图:
将 adc 硬件抽象为一个设备,设备包含六个通道。支持采样电压范围为 0V~1.8V,浮动范围为 5mv。
WDT 驱动结构如下图:
OTP 驱动结构如下图:
TS 驱动结构如下图所示:
PWM 驱动结构如下图所示:
RTC 驱动结构如下图所示:
TIMER 驱动结构如下图所示:
1.3 驱动API使用方法
1.3.1 UART
内核态程序使用uart
- 通过设备节点找到设备句柄。
uart_dev = rt_device_find(“uart”)
- 以中断接收及轮询发送模式打开串口设备
rt_device_open(uart_dev, RT_DEVICE_FLAG_INT_RX)
- 设置接收回调函数
rt_device_set_rx_indicate(uart_dev,call_back)
- 发送字符串
rt_device_write(uart_dev, 0, str, (sizeof(str) - 1))
1.3.2 I2C
内核态程序使用i2c
- 通过总线名找到总线句柄
i2c_bus = rt_i2c_bus_device_find(I2C_NAME)
- 构建message
struct rt_i2c_msg msgs
msgs.addr = CHIP_ADDRESS;
msgs.flags = RT_I2C_WR;
msgs.buf = buf;
msgs.len = 2;
- 传输
rt_i2c_transfer(bus, &msgs, 1)
1.3.3 GPIO
内核态程序使用gpio
- 包含头文件
#include "drv_gpio.h"
- 设置关键输入输出模式
kd_pin_mode(LED_PIN_NUM1, GPIO_DM_OUTPUT)
kd_pin_mode(KEY1_PIN_NUM, GPIO_DM_INPUT)
- 设置高低电平
kd_pin_write(LED_PIN_NUM1, GPIO_PV_LOW)
kd_pin_write(LED_PIN_NUM1, GPIO_PV_HIGH)
- 读取电平
kd_pin_read(KEY1_PIN_NUM)
- 设置中断方式,绑定中断函数
kd_pin_attach_irq(KEY1_PIN_NUM,GPIO_PE_FALLING, key_irq, RT_NULL)
- 中断使能
kd_pin_irq_enable(KEY1_PIN_NUM, KD_GPIO_IRQ_ENABLE)
用户态程序使用gpio(仅读写)
- 打开设备
gpio_fd = open("/dev/gpio", O_RDWR)
- 配置输入输出模式
ioctl(fd, GPIO_DM_OUTPUT, &mode33)
- 输出高低电平
ioctl(fd, GPIO_WRITE_HIGH, &mode33)
- 读取电平
ioctl(fd, GPIO_READ_VALUE, &mode27)
1.3.4 Hard-lock
内核态使用hard-lock
- 包含头文件
#include "drv_hardlock.h"
- 申请使用锁
kd_request_lock(rt_uint32_t num)
- 释放锁
kd_hardlock_unlock(rt_uint32_t num)
1.3.5 ADC
1.3.5.1 用户态 msh 使用 adc
用户通过 msh 来获取 adc 采样值的步骤如下:
- 在使用设备前,需要先查找设备是否存在,可以使用命令 adc probe 后面跟注册的 ADC 设备的名称。如下所示;
msh />adc probe adc
probe adc success
- 使能设备的某个通道可以使用命令 adc enable 后面跟通道号;
msh />adc enable 0
adc channel 0 enables success
- 读取 ADC 设备某个通道的数据可以使用命令 adc read 后面跟通道号;
msh />adc read 0
adc channel 0 read value is 0x00000796
- 关闭设备的某个通道可以使用命令 adc enable 后面跟通道号;
msh />adc disable 0
adc channel 0 disable success
1.3.5.2 用户态程序使用 adc
用户态程序中获取 adc 采样值的步骤如下:
- rt_device_find 查找 adc 设备。如下所示:
rt_device_t adc_dev;
adc_dev = rt_device_find("adc");
- 通过设备句柄打开 adc 设备。如下所示:
ret = rt_device_open(adc_dev, RT_DEVICE_OFLAG_RDWR);
- 通过设备句柄使能 adc 设备的某个通道。如下所示:
uint32_t *p;
uint32_t channel = 0;
p = (uint32_t *)(intptr_t)channel;
ret = rt_device_control(adc_dev, 0, (void *)p);
- 通过设备句柄从 adc 读取数据。如下所示:
uint32_t channel = 0;
uint32_t reg_value;
rt_device_read(adc_dev, channel, (void *)®_value, sizeof(unsigned int));
- 当不再使用 adc 的某个通道时,通过设备句柄失能 adc 设备的某个通道;
uint32_t *p;
uint32_t channel = 0;
p = (uint32_t *)(intptr_t)channel;
ret = rt_device_control(adc_dev, 0, (void *)p);
1.3.5.3 内核态程序使用 adc
- 查找 adc 设备
应用程序根据 adc 设备名称获取设备句柄,进而可以操作 adc 设备,查找设备函数如下所示:
rt_device_t rt_device_find(const char* name);
- 使能 adc 通道
rt_err_t rt_adc_enable(rt_adc_device_t dev, rt_uint32_t channel);
- 读取 adc 通道采样值
rt_uint32_t rt_adc_read(rt_adc_device_t dev, rt_uint32_t channel);
- 关闭 adc 通道
rt_err_t rt_adc_disable(rt_adc_device_t dev, rt_uint32_t channel);
1.3.6 WDT
1.3.6.1 用户态程序使用WDT
- 打开设备节点
open(WDT_DEVICE_NAME, O_RDWR);
- 设置超时时间
ioctl(wdt_fd, CTRL_WDT_SET_TIMEOUT, &timeout);
- 启动看门狗计时
ioctl(wdt_fd, CTRL_WDT_START, NULL);
- 喂狗
ioctl(wdt_fd, CTRL_WDT_KEEPALIVE, NULL)
1.3.6.2 内核态程序使用WDT
- 获取设备句柄
wdt_dev = rt_device_find(WDT_DEVICE_NAME);
- 初始化设备
rt_device_init(wdt_dev);
- 设置超时时间
rt_device_control(wdt_dev, KD_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
- 启动看门狗计时
rt_device_control(wdt_dev, KD_DEVICE_CTRL_WDT_START, RT_NULL);
- 喂狗
rt_device_control(wdt_dev, KD_DEVICE_CTRL_WDT_KEEPALIVE, RT_NULL);
1.3.7 OTP
大核侧 OTP 驱动可以支持被内核态其它驱动调用、用户态应用程序调用等场景,下面,将分别介绍这两种应用场景下 OTP 驱动的使用方法、注意事项。
1.3.7.1 用户态程序使用 OTP
在 rt-smart 中,应用程序通过 I/O 设备管理接口来访问硬件设备,当实现对应的设备驱动之后,应用程序就可以访问该硬件。Rt-smart 用户层通过操作 /dev/otp
来访问 OTP 硬件,用户态应用程序使用 OTP 时,可以通过 open、read、write 等操作来访问 otp 设备。用户态应用程序使用 OTP 的具体操作如下:
- 添加头文件
rtdevice.h
; - 应用程序根据设备名称获取设备句柄;
- 获得设备句柄之后,应用程序可以打开设备;
- 初始化参数,包括偏移量、缓冲区、大小;
- 通过设备句柄和参数值从 OTP 设备中读取/写入数据;
- 利用设备句柄关闭设备。
其中,snippet 代码如下所示:
#include <rtdevice.h>
#include <rtthread.h>
// 查找设备,获取句柄
otp_dev = rt_device_find(“otp”);
// 打开设备
ret = rt_device_open(otp_dev, RT_DEVICE_OFLAG_RDWR);
// 初始化读参数并从OTP空间读值,参数分别代表读取OTP的偏移量、要读取的字节长度、缓冲区
uint32_t pos = 0x0;
uint32_t size = 0xC00;
uint32_t buffer[768] = {0};
ret = rt_device_read(otp_dev, pos, (void *)buffer, size);
// 初始化写参数并写入OTP空间,参数分别代表写入OTP的偏移量、写入的字节长度、要写入的值
pos = 0x0;
size = 0x4;
buffer[0] = 0xff11ff11;
ret = rt_device_write(otp_dev, pos, (void *)buffer, size);
// 关闭设备
rt_device_close(otp_dev);
1.3.7.2 内核态程序使用 OTP
在rt-smart中,内核态其它驱动程序想要读写 OTP 空间时,只需要调用 OTP 驱动提供的接口即可。具体的步骤如下所示:
- 添加头文件
drv_otp.h
; - 根据设备名称获取设备句柄;
- 初始化参数,包括偏移量、缓冲区、大小;
- 通过设备句柄和参数值从 OTP 设备中读取/写入数据;
- otp_device_read
- otp_device_write
其中,snippet 代码如下所示:
#include drv_otp.h
// 获取句柄
otp_dev = rt_device_find(“otp”);
// 从(otp_read_base + 0x0)地址处读取0xC00字节的数据
uint32_t pos = 0x0;
uint32_t size = 0xC00;
uint32_t buffer[768] = {0};
ret = otp_device_read(otp_dev, pos, (void *)buf, size);
// 向(otp_write_base + 0x0)地址处写 0xff11ff11
pos = 0x0;
size = 0x4;
buffer[0] = 0xff11ff11;
ret = otp_device_write(otp_dev, pos, (void *)buffer, size);
1.3.7.3 注意事项
在使用读写 API 的时候,需要注意以下几个关键点:
- 危险操作:OTP 空间为一次可编程的区域,bit 位一旦由0写为1,便不能恢复;
- 读写偏移量:读操作和写操作的基地址是不同的,OTP 区域的可读空间范围为
(otp_read_base + 0x0)~(otp_read_base + 0xc00)
,可写空间的长度为(otp_write_base + 0x0)~(otp_write_base + 0x200)
; - 读写size:size 是4的倍数。
1.3.8 TS
大核侧 TS 驱动可以支持被内核态其它驱动调用、用户态应用程序调用等场景,下面,将分别介绍这两种应用场景下 TS 驱动的使用方法、注意事项。
1.3.8.1 用户态程序使用 TS
在 rt-smart 中,应用程序通过 I/O 设备管理接口来访问硬件设备,当实现对应的设备驱动之后,应用程序就可以访问该硬件。Rt-smart 用户层通过操作 /dev/ts
来访问 TS 硬件,用户态应用程序使用 TS 时,可以通过 open
、read
、close
等操作来访问 ts 设备。用户态应用程序使用 TS 的具体操作如下:
- 添加头文件
rtdevice.h
; - 应用程序根据设备名称获取设备句柄;
- 获得设备句柄之后,应用程序可以打开设备;
- 初始化参数,包括偏移量、缓冲区、大小;
- 通过设备句柄和参数值从 TS 设备中读取数据;
- 利用设备句柄关闭设备。
其中,snippet 代码如下所示:
#include <rtdevice.h>
#include <rtthread.h>
// 查找设备,获取句柄
ts_dev = rt_device_find(“ts”);
// 打开设备
ret = rt_device_open(ts_dev, RT_DEVICE_OFLAG_RDWR);
// 初始化读参数并从TS设备中读值,参数分别代表读取TS的偏移量、要读取的字节长度、缓冲区
uint32_t pos = 0x0;
uint32_t size = 0x0;
uint32_t buffer[1] = {0};
ret = rt_device_read (ts_dev, pos, (void *)buffer, size);
ts_val = *(uint32_t *)buffer;
code = (double)(ts_val & 0xfff);
usleep(2600000);
if(ts_val >> 12)
{
temp = (1e-10 * pow(code, 4) * 1.01472 - 1e-6 * pow(code, 3) * 1.10063 + 4.36150 * 1e-3 * pow(code, 2) - 7.10128 * code + 3565.87);
printf("ts_val: 0x%x, TS = %lf C\n", ts_val, temp);
}
// 关闭设备
rt_device_close(ts_dev);
1.3.8.2 内核态程序使用 TS
在rt-smart中,内核态其它驱动程序想要读芯片结温时,只需要调用 TS 驱动提供的接口即可。具体的步骤如下所示:
- 添加头文件
drv_ts.h
; - 根据设备名称获取设备句柄;
- 初始化参数,包括偏移量、缓冲区、大小;
- 通过设备句柄和参数值从 TS 设备中读取温度数据;
- ts_device_read
其中,snippet 代码如下所示:
#include drv_ts.h
// 查找设备,获取句柄
ts_dev = rt_device_find(“ts”);
// 初始化读参数并从TS设备中读值,参数分别代表读取TS的偏移量、要读取的字节长度、缓冲区
uint32_t pos = 0x0;
uint32_t size = 0x0;
uint32_t buffer[1] = {0};
ret = ts_device_read (ts_dev, pos, (void *)buffer, size);
1.3.8.3 注意事项
在调用API读TS温度的时候,需要注意以下几个关键点:
- TS 驱动会读取 tsensor 寄存器,将寄存器的值返回,但是寄存器的值并不是真正的温度值,需要用数学公式做相应的转换,详细的公式信息如下:
// code为读取TS寄存器的值保留12位得到的结果,temp为芯片结温
code = ts_val & 0xfff;
temp = (1e-10 * pow(code, 4) * 1.01472 - 1e-6 * pow(code, 3) * 1.10063 + 4.36150 * 1e-3 * pow(code, 2) - 7.10128 * code + 3565.87);
1.3.9 PWM
目前PWM驱动仅提供了从内核态的调用方法,使用方法如下:
1.3.9.1 内核态程序使用PWM
pwm_demo:
pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME);
/* 设置PWM周期和脉冲宽度 */
period = 100000; /* PWM周期, 单位为纳秒ns */
pulse = 50000; /* PWM正脉冲宽度值, 单位为纳秒ns */
rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL_2, period, pulse);
period = 100000;
pulse = 25000;
rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL_3, period, pulse);
period = 100000;
pulse = 75000;
rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL_4, period, pulse);
/* 使能设备,同一路pwm的不同channel仅需任意channel使能一次 */
rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL_2);
rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL_4);
sleep(10);
/* disable 时同理 */
rt_pwm_disable(pwm_dev, PWM_DEV_CHANNEL_2);
rt_pwm_disable(pwm_dev, PWM_DEV_CHANNEL_4);
1.3.9.2 FinSH命令使用PWM
设置 PWM 设备的某个通道的周期和占空比
msh />pwm_set pwm1 1 500000 5000
使能 PWM 设备的某个通道
msh />pwm_enable pwm1 1
关闭 PWM 设备的某个通道
msh />pwm_disable pwm1 1
1.3.9.3 注意事项
K230共有6个PWM通道,其中channel 0 ~ 2(pwm 0 ~ 2)同属一路PWM,channel 3 ~ 5(pwm 3 ~ 5)通属一路PWM,在同时使用多通道PWM时,同属一路的PWM,仅需任意使能其中一个channel即可。 如 1.3.9.1 中的示例,channel3和channel4仅使能了channel4,此时channel3也被 使能。
1.3.10 RTC
1.3.10.1 内核态使用RTC
RTC驱动提供了内核态的调用方法,使用方法如下:
time_t now;
rt_device_t device = RT_NULL;
device = rt_device_find(RTC_NAME);
rt_device_open(device, 0);
set_date(2024, 1, 1); //设置日期
set_time(12, 30, 0); //设置时间
while(1)
{
now = time(RT_NULL); //获取当前时间
rt_kprintf("%s\n", ctime(&now)); //打印
rt_thread_mdelay(1000);
}
如果要使用K230 RTC的中断功能,请参考以下示例:
void user_alarm_callback(void)
{
rt_kprintf("[ user alarm callback function. ]\n");
}
void alarm_rtc()
{
time_t now;
uint32_t i;
struct tm p_tm;
rt_device_t dev = RT_NULL;
struct rt_alarm * alarm = RT_NULL;
struct kd_alarm_setup setup;
dev = rt_device_find(RTC_NAME);
rt_device_open(dev, 0);
set_date(2024, 1, 1);
set_time(1, 1, 0);
now = time(RT_NULL);
gmtime_r(&now,&p_tm);
setup.flag = RTC_INT_TICK_SECOND; //秒级tick中断,每秒钟都会触发,请参考宏定义
setup.tm.tm_year = p_tm.tm_year;
setup.tm.tm_mon = p_tm.tm_mon;
setup.tm.tm_mday = p_tm.tm_mday;
setup.tm.tm_wday = p_tm.tm_wday;
setup.tm.tm_hour = p_tm.tm_hour;
setup.tm.tm_min = p_tm.tm_min;
setup.tm.tm_sec = p_tm.tm_sec;
rt_device_control(dev, RT_DEVICE_CTRL_RTC_SET_CALLBACK, &user_alarm_callback); //set rtc interrupt callback
rt_device_control(dev, RT_DEVICE_CTRL_RTC_SET_ALARM, &setup); //set alarm time
for (i=0; i<5; i++)
{
now = time(RT_NULL);
rt_kprintf("%s\n", ctime(&now));
rt_thread_mdelay(1000);
}
rt_device_control(dev, RT_DEVICE_CTRL_RTC_STOP_ALARM, RT_NULL); //close interrupt
rt_device_close(dev);
}
1.3.10.2 注意事项
使用K230的RTC模块时,必须在芯片上电时激活PMU(请参考PMU相关文档),否则RTC无法工作。
以 《K230-USIP-EVB-LP3-V1.1》为例,需要将J1区域的排针io13 io14连接后上电。
K230的RTC支持两种中断模式:
1、alarm中断(定时中断),在定时时间到时只产生一次中断。最大精度:秒
2、tick中断(周期中断),如每秒钟,每分钟,每小时,每天等都会产生中断。最大精度:1/64秒