本帖最后由 蓝天-彭 于 2019-7-15 15:53 编辑
在熟悉了开发板上的功能模块和相关接口后,下一步就是用该板子来开发和验证自己的功能:
1、环境搭建
CubieBLE1 SDK是基于开源zephyr系统,为了方便用户使用,选定MDK_keil工具来开发调试。CubieBLE1开发必须使用 Keil uVision5(V5.18.0.0)及以上版本,低版本会导致SDK编译出错。
这部分详细内容可参考CubieBLE1开发板使用手册的第三节“软件开发环境搭建”。
2、熟悉SDK
下载的资料包中有适配开发板的sdk,该sdk的架构如下:
SDK 按层次结构从下至上可以分为:
• 驱动层驱动层(driver) 实现各个硬件模块的驱动。
• 内核层(kernel) 定义了基本的kernel 服务:任务调度、通信机制、内存管理等。
• 子系统层(subsys) 在内核层之上构建了功能子系统,包括shell/bluetooth 等。
• 应用层(application)。可以基于bluetooth subsys 开发各种BLE 应用。
基于该SDK,我们主要是进行应用开发,SDK中有大量的实例工程,应用开发时可以选择功能最接近的工程,在实例工程的基础上进行开发,关于实例工程的功能介绍和使用请参考CubieBLE1开发板使用手册的第四节“实例”。
应用开发模式
为了适应各种复杂度的应用开发,IC 设计时实现了以下几点:
• 设计了大容量ROM,固化大量driver/kernel/subsys(bluetooth)代码
• 支持spinor XIP
根据应用程序的复杂度和是否使用ROM,应用开发有以下三种模式:
• ROM+RAM: 对于可以使用ROM,并且比较简单应用。应用的所有代
码和数据都从spinor 中load 到ram 运行。
实例工程中的peripheral目录和bluetooth目录则使用了该机制,对应的loader工程也要使用该机制,不然无法启动。
• ROM+XIP+RAM: 对于可以使用ROM,但逻辑很复杂的应用,RAM已经不够了,此时需要使用XIP 机制,代码直接在spinor 上执行,ram中只需要存储数据。
SDK中适配开发板的工程“voice_rcu_non_hid”使用了XIP机制,对应的loader工程也要使用XIP机制,不然无法引导启动应用工程。
• XIP+RAM: 对于ROM 完全派不上用场的情况,只能使用XIP 机制。
目录结构
├─arch: soc 相关代码
│ ├─cortex_m
│ └─soc
│ └─atb110x
├─boards: 板级相关代码
├─drivers: 驱动源码,包括rom 化的驱动的初始化代码
├─ext:
│ └─lib
│ └─actions: 炬芯开发的库文件
│ ├─framework:应用框架
│ ├─hal:驱动抽象层
│ ├─include:库相关的头文件
│ ├─libAL:算法库,以lib 方式提供
│ ├─ota:ota 源码
│ └─startup:基于keil 开发zephyr 应用的startup 公共代码
├─include: kernel 和驱动相关的头文件
├─kernel: 内核源码,包括rom 化的内核的初始化代码
├─lib: 只留下必须的minimal libc 的头文件
├─samples:
│ ├─bluetooth: ble 应用示例, 包括peripheral 示例和central 示例
│ ├─loader_binaries: loader 示例:loader 根据不同的参数加载对应的文件
│ ├─peripheral: 各种设备驱动的示例
│ ├─uart_product: 用于示范如何将一个普通示例转换成一个可用于uart 量产的固件
│ └─voice_rcu: 可量产的ble 遥控器方案
├─scripts:脚本和工具
└─subsys:子系统源码,包括rom 化的子系统的初始化代码
├─bluetooth
└─shell
关于SDK架构相关的更详细内容请参考SDK源码包的doc目录下“0_2_ATB110X_ZS110A_SDK架构介绍_V1.0”。
SDK相应配置
SDK默认有两种配置方式和若干常用的配置项,如下
• 静态配置方式:采用宏定义的方式来配置,在编译期间就固定下来,运行期间无法修改。
• 动态配置方式:采用nvram 的方式来配置,配置项存放在NOR 上,运行期间可以动态修改。
通过静态配置的方式,修改文件为:\samples\voice_rcu\src\peripheral_rmc\include\autoconf.h
板级硬件相关配置主要是板子对应的GPIO 的配置,都采用静态配置的方式,通过修改:\samples\voice_rcu\src\peripheral_rmc\include\rmc_atb110*_*.h
应用层配置通常都采用静态配置的方式,主要通过修改:
\samples\voice_rcu\src\peripheral_rmc\include\autoconf_rmc.h
具体的配置项及说明参考文档“0_4_ATB110X_ZS110A_方案配置指南_V1.2”
应用设计
CubieBLE1默认的工程是模拟一个非hid的语音遥控器,使用的是BLE私有的profile(wp.c),当然在sdk中“voice_rcu”工程也适配了开发板的按键等,使用的profile是标准的BLE hid profile(hog.c)。
这里说的BLE Profile 即为GATT profile,全名Generic Attribute profile,中文名通用属性配置文件。该规程定义了服务、特征及其描述符被发现、继而用于允许BLE 设备传输数据的标准途径。该配置文件以GATT 功能为基础,描述了用例、角色和一般行为。
GATT 的层次结构
层次结构的最顶层为Profile,由完成用例时必需的一个或多个Service 组成。一个Service 由Characteristic 和对其他服务的Reference 组成。Characteristic 包括type (表现为UUID)、value 和一组指示Characteristic 所支持操作的Properties 以及一组与安全相关的Permissions。Characteristic 还可能包括一个或多个Descriptors, 即与所拥有Characteristic 相关的元数据或配置标志。
关于profile的详细介绍和使用可以参考“Bas profile”
更多信息请登录Bluetooth 官网:
• GATT Profile: https://www.bluetooth.com/specifications/gatt
• GATT Service: https://www.bluetooth.com/specifications/gatt/services
3、驱动的使用和说明
CubieBLE1是ATB1109芯片全功能扩展的一个开发板。板上的功能模块还包括:按键操作模块、LED灯模块、ADC检测模块、红外收发模块、语音接收模块,语音输出模块、电池接口模块。同时还拥有丰富的接口:1*SPI、2*UART、1*I2C、5*PWM、IRC RX/TX、ADC、GPIO,通过这些接口,可以开发各种各样的产品,下面说明一下如何使用这些接口。相应的API请参考文档“9_ATB110X_ZS110A_API_Reference_V0.1”
GPIO
GPIO是最常用的接口之一,作为输入输出口控制相关设备的使用,GPIO配置流程如下:
1)绑定GPIO设备
struct device *gpio;
gpio= device_get_binding("gpio"); if(!gpio) { printk("cannot found device gpio\n"); return; } API说明 structdevice *device_get_binding(const char *name) Retrievethe device structure for a driver by name.
Deviceobjects are created via the DEVICE_INIT() macro and placed in memory by the linker.If a driver needs to bind to another driver it can use this function to retrievethe device structure of the lower level driver by the name the driver exposes tothe system.
Return pointerto device structure; NULL if not found or cannot be used. Parameters name:device name to search for.
2)配置GPIO
配置GPIO为输出模式 gpio_pin_configure(gpio_dev,8, GPIO_DIR_OUT | GPIO_POL_INV); 配置GPIO为输入模式 ret= gpio_pin_configure(gpio_dev, KEY_1, (GPIO_DIR_IN | GPIO_INT | GPIO_INT_EDGE | GPIO_INT_ACTIVE_LOW| GPIO_PUD_PULL_UP | GPIO_INT_DEBOUNCE)); if(ret) { printk("Errorconfiguring %d!\n", KEY_1); }
配置GPIO为中断输入时,需要添加中断回调函数
void gpio_callback(struct device *port, struct gpio_callback *cb, u32_tpins) { }
void led_key_init(void) { intret; gpio_dev= device_get_binding("gpio");
if(!gpio_dev) { printk("cannot found dev gpio \n"); return; } /*KEY */ ret= gpio_pin_configure(gpio_dev, KEY_1, (GPIO_DIR_IN | GPIO_INT | GPIO_INT_EDGE | GPIO_INT_ACTIVE_LOW| GPIO_PUD_PULL_UP | GPIO_INT_DEBOUNCE)); if(ret) { printk("Error configuring %d!\n", KEY_1); }
gpio_init_callback(&gpio_cb,gpio_callback, BIT(KEY_1)); ret= gpio_add_callback(gpio_dev, &gpio_cb);
if(ret) { printk("Cannot setup callback!\n"); }
ret= gpio_pin_enable_callback(gpio_dev, KEY_1);
if(ret) { printk("Errorenabling callback!\n"); } } 相关API描述太多了,这里就不贴出来,大家查看文档即可。关于GPIO使用的更详细的内容请参考“GPIO使用实例”
UART
uart使用最多的是作为打印口和透传,通过定义宏选择哪路uart作为打印,
#define CONFIG_UART_0 1 选择uart0(GPIO2,GPIO3)作为打印口
#define CONFIG_UART_1 1 选择uart1(GPIO4,GPIO5)作为打印口
默认sdk使用的是UART0作为打印口,功能配置如下:
#define BOARD_PIN_CONFIG \
{2, 3 | GPIO_CTL_SMIT | GPIO_CTL_PADDRV_LEVEL(3)},\
{3, 3 | GPIO_CTL_SMIT | GPIO_CTL_PADDRV_LEVEL(3)},\
当用UART0作为数据传输口时配置如下:
绑定uart:
uart_pipe_dev = device_get_binding("UART_0");
发送数据:
int uart_pipe_send(const u8_t *data, int len)
{
while (len--) {
uart_poll_out(uart_pipe_dev, *data++);
}
while(!uart_irq_tx_complete(uart_pipe_dev));
return 0;
}
配置接收:
static void uart_pipe_rx(struct device *dev)
{
/* As per the API, the interrupt may be an edge so keep
* reading from the FIFO until it's empty.
*/
for (;;) {
int avail = recv_buf_len - recv_off;
int got;
got = uart_fifo_read(uart_pipe_dev, recv_buf + recv_off, avail);
if (got <= 0) {
break;
}
/*
* Call application callback with received data. Application
* may provide new buffer or alter data offset.
*/
recv_off += got;
recv_buf = app_cb(recv_buf, &recv_off);
}
}
static void uart_pipe_isr(struct device *dev)
{
uart_irq_update(dev);
if (uart_irq_is_pending(dev)) {
if (uart_irq_rx_ready(dev)) {
uart_pipe_rx(dev);
}
}
}
static void uart_pipe_setup(struct device *uart)
{
u8_t c;
uart_irq_rx_disable(uart);
uart_irq_tx_disable(uart);
/* Drain the fifo */
while (uart_fifo_read(uart, &c, 1)) {
continue;
}
uart_irq_callback_set(uart, uart_pipe_isr);
uart_irq_rx_enable(uart);
}
void uart_pipe_register(u8_t *buf, size_t len, uart_pipe_recv_cb cb)
{
recv_buf = buf;
recv_buf_len = len;
app_cb = cb;
recv_off = 0;
uart_pipe_dev = device_get_binding(CONFIG_UART_PIPE_ON_DEV_NAME);
if (uart_pipe_dev != NULL) {
uart_pipe_setup(uart_pipe_dev);
}
}
void uart_pipe_rx_abort(void)
{
if (app_cb) {
recv_buf = app_cb(recv_buf, &recv_off);
}
}
PWM
默认的sdk中使用了三路PWM来控制RGB灯,复用功能配置如下:
#define BOARD_PIN_CONFIG \
{6, 2},\
{7, 2},\
{10, 2},\
由于sdk中使用了pwm,这里就不多讲述了,API请参考文档。
I2C
I2C常用于sensor的数据传输,复用功能配置如下:
#define BOARD_PIN_CONFIG \
{8, 7 | (3<<12) | (1<<11) | (1<<8) },\
{9, 7 | (3<<12) | (1<<11) | (1<<8) },\
初始化:
struct sensor_data *data = dev->driver_data;
static const union dev_config i2c_cfg = {
.raw = 0,
.bits = {
.use_10_bit_addr = 0,
.is_master_device = 1,
.speed = I2C_SPEED_STANDARD,
},
};
data->i2c_master = device_get_binding("I2C_0");
if (!data->i2c_master) {
return -EINVAL;
}
else
printk("i2c master not found\n");
if (i2c_configure(data->i2c_master, i2c_cfg.raw)) {
printk("i2c config failed\n");
return -EINVAL;
}
else
printk("i2c config success\n");
data->i2c_slave_addr = BME280_I2C_ADDR;
读数据:static int i2c_burst_read(struct device *dev, u16_t dev_addr, u8_tstart_addr, u8_t *buf, u8_t num_bytes)
Read multiple bytes from an internal address of an I2C device.
This routine reads multiple bytes from an internal address of an I2C device synchronously.
Parameters
• dev: Pointer to the device structure for the driver instance.
• dev_addr: Address of the I2C device for reading.
• start_addr: Internal address from which the data is being read.
• buf: Memory pool that stores the retrieved data.
• num_bytes: Number of bytes being read.
Return Value
• 0: If successful.
• -EIO: General input / output error.
写数据:
static int i2c_burst_write(struct device *dev, u16_t dev_addr, u8_tstart_addr, u8_t *buf, u8_t num_bytes)
Write multiple bytes to an internal address of an I2C device.
This routine writes multiple bytes to an internal address of an I2C device synchronously.
Parameters
• dev: Pointer to the device structure for the driver instance.
• dev_addr: Address of the I2C device for writing.
• start_addr: Internal address to which the data is being written.
• buf: Memory pool from which the data is transferred.
• num_bytes: Number of bytes being written.
Return Value
• 0: If successful.
• -EIO: General input / output error.
更多的API请参考文档。
使用I2C调试bme280参考:bme280调试
SPI
SDK设置功能复用如下:
#define BOARD_PIN_CONFIG \
{8, 6},\
{9, 6},\
{26, 6},\
{27, 6},\
详细请参考:SPI调试Bme280
ADC
开发板默认使用了ADC0(10bit)组成7个按键,电路如下:
对应映射到key:
|