ZHCT539 June   2024 CC2340R5

 

  1.   1
  2.   2
    1.     1. 摘要:
    2.     2. CC2340 UART 特性:
    3.     3. 基于 Basic_ble 例程扩展 IO 模拟 UART:
    4.     4. 任务与 Timer 运行流程:
    5.     5. 系统运行状态分析:
    6.     6. 总结:
    7.     7. 参考文献:
    8.     8. 附录

1. 摘要:

CC2340R5 --- TI 第四代低功耗蓝牙 SOC,同时支持 ZigBee 和私有 2.4GHz 协议无线功能。芯片由主处理器 M0 +、软件可编程射频处理器、电源管理和丰富的外设构成片上系统,最高达 512kB 的 Flash、36kB SRAM 以及 26 GPIOs 为用户提供了更灵活的系统设计能力,具有集成度高、宽温度范围、低成本、低功耗、优秀的射频性能等特点。

 CC2340 芯片框图 图 1 CC2340 芯片框图

本文旨在提供一种使用 IO 口模拟 UART 打印功能的方案,在 1 个硬件 UART 的基础上扩展一个额外的 UART 供用户打印和传输数据。本文基于 CC2340R5 EVM 板,根据 TI 开源的 SDK 中 basic_ble 蓝牙协议栈例程进行二次开发验证,为用户使用 IO 扩展 UART 功能提供参考。

2. CC2340 UART 特性:

 CC2340 UART Block
                    Diagram 图 2 CC2340 UART Block Diagram

CC2340 UART 最高支持 3Mbsp 波特率,支持 8 X 8 深度的 TX FIFO 和 8 X 12 深度的 RX FIFO,可以配置 FIFO深度触发中断,自带错误状态检测以及中断触发,支持 DMA 配合传输。

 CC2340 Boot UART
                    Connection 图 3 CC2340 Boot UART Connection

UART 只需要 TX 和 RX 两跟线就能实现一对一的数据通信,是一种异步全双工传输方式。本文通过一个 GPIO模拟 TX 接口进行数据的打印。

 CC2340 UART Data
                    Format 图 4 CC2340 UART Data Format

3. 基于 Basic_ble 例程扩展 IO 模拟 UART:

新建任务线程:

CC2340 蓝牙协议例程是基于 FreeRTOS 开发的,所以首先需要创建一个新任务来作为 IO 模拟 UART 的载体,如下图所示创建一个优先级为1的任务,在 FreeRTOS 系统中,优先级数字越大优先级越高。此处需要注意的是,由于蓝牙协议栈任务线程优先级原始配置为 5,所以新建任务初始化优先级需小于 5,以此保证蓝牙协议栈的最高优先级运行。

 新建任务线程 图 5 新建任务线程

Timer 启用:

想要依靠 IO 口精准的打印出数据,必须精准控制 IO 口电平的转换时序,这就需要依靠定时器来产生计时事件按 UART 通信标准进行 IO 口电平的翻转。

在 Timer 的选择上,虽然 FreeRTOS 有软件定时器的机制,但最小定时周期为 1ms 无法满足正常波特率的需求,故只能选择 CC2340 集成的 LGPTimer 硬件定时器。

 CC2340 LGPTimer资源 图 6 CC2340 LGPTimer资源

Timer工作在周期中断模式,周期性产生中断并在中断服务函数中根据数据转换 IO 电平,Timer 周期就决定了波特率(例如波特率 9600 对应 Timer 周期为 104us),如下图所示为波特率 9600 时 IO 打印 0x55 的电平状态:

 IO 电平转换时序 图 7 IO 电平转换时序

前后台工作模式:

整个功能的实现是以前后台配合的模式完成,前台为创建的任务线程,而后台则是 LGPTimer 定时器中断。前台任务主要实现数据的处理,包括对单个数据、数组进行拆分,为定时器中断做好数据打印准备,在定时器中断中尽可能少的处理事务,以减轻整个功能实现的 CPU 负荷。而定时器中断作为后台会根据波特率周期性的转换电平状态,实时获取前台任务的数据域,按照 UART 通信标准完成每一帧数据包起始位、数据位、停止位的电平输出。

 前后台控制方式 图 8 前后台控制方式

4. 任务与 Timer 运行流程:

动态任务切换:

前面提到新建任务的初始优先级设置低于蓝牙协议栈优先级,由于优先级高的任务在会优先被执行,所以在系统需要使用 IO 口传输数据时就需要将该打印任务的优先级提高到大于蓝牙优先级。在本次数据打印完成后需要将打印任务恢复为低优先级,这样可以保证打印数据的准确性也可以最大程度保证蓝牙协议栈运行的稳定性。

 任务切换流程 图 9 任务切换流程

定时器挂起:

同样的,定时器开启后进出中断会占用 CPU,所以定时器需要在打印请求建立之后再开启并在打印完成后关闭,最大程度减轻 CPU 负荷。

 任务与 Timer 执行流程 图 10 任务与 Timer 执行流程

如上图所示,整个 IO 打印功能的实现流程包括任务优先级切换、定时器开启、IO 电平输出、任务优先级恢复、定时器关闭五个步骤。

5. 系统运行状态分析:

使用串口助手接收 IO 打印的数据验证数据的准确性,将 IO 口通过 CC2340 EVM 板载 UART 接口与 PC 连接:

 EVM连接 图 11 EVM连接

蓝牙协议栈一组广播包通常由 30 多个字节组成,为模拟蓝牙日志打印,在代码中创建一个 45 字节的数组来通过 IO 口进行打印:

 Demo 数组定义 图 12 Demo 数组定义

不同波特率 IO 打印数据验证:

波特率4800:

 4800 串口助手接收页 图 13 4800 串口助手接收页

IO 在 4800 波特率下打印数据,串口助手接收的数据与代码中定义的数据一致。

 4800 波特率 Sniffer
                    蓝牙空中抓包 图 14 4800 波特率 Sniffer 蓝牙空中抓包

IO 在 4800 波特率下打印数据,空中抓取的蓝牙广播包保持在正常水平,没有出现明显丢包。

 9600 串口助手接收页 图 15 9600 串口助手接收页

IO 在 9600 波特率下打印数据,串口助手接收的数据与代码中定义的数据一致。

 9600 波特率  Sniffer
                    蓝牙空中抓包 图 16 9600 波特率 Sniffer 蓝牙空中抓包

IO 在 9600 波特率下打印数据,空中抓取的蓝牙广播包保持在正常水平,没有出现明显丢包。

波特率 14400:

 14400 波特率串口助手接收页 图 17 14400 波特率串口助手接收页

IO 在 14400 波特率下打印数据,串口助手接收的数据会存在间接性丢失或错乱。

 14400 波特率  Sniffer
                    蓝牙空中抓包 图 18 14400 波特率 Sniffer 蓝牙空中抓包

IO 在 14400 波特率下打印数据,空中抓取的蓝牙广播包存在间接性广播间隔异常、丢包。

6. 总结:

根据 Demo 测试验证结果来看,CC2340 基于蓝牙协议栈扩展 IO 口模拟 UART 打印功能是能够实现的,但如果波特率过高会产生高频的中断处理,从而影响蓝牙协议栈的稳定运行,上述方案综合实验验证在波特率 9600 内可以保证 IO 打印数据的准确性,同时可以确保蓝牙广播维持在正常状态。

本文基于 CC2340 验证了 IO 模拟 UART 的可行性,根据现有例程进行扩展,在有限的样本和实验手法下进行了系统分析,用户在实际应用过程中需结合实际系统验证最终效果。

8. 附录

8.1 部分伪代码

a.前台任务数据处理函数

void UARTIO_bufwrite(uint8_t *buffers, uint32_t size)
{
    static uint8_t *p_buf;
if(!cntsend)
{
        p_buf = buffers;
        bufsize = size;
        g_flag_batch = 0;
        LGPTimerLPF3_start(lgptHandle, LGPTimerLPF3_CTL_MODE_UP_PER);
    }
while(!g_flag_batch)
{
        if((!bufsend) && (!g_flag_batch) && (!sequence))
        {
            if(bufsize)
            {
                bufsend = *p_buf;
                p_buf++;
                cntsend++;
                flag_send = 1;
            }
        }
    }
}

b.Timer 电平控制函数

void UARTIO_write1(void)
{
    buffer11 = bufsend;
    if((sequence < 3) && buffer11 && (!g_flag_batch)){
        switch(sequence){
            case 0:
                GPIO_write(CONFIG_GPIO_IOUART, CONFIG_GPIO_LED_OFF);
                sequence +=1;
                break;
            case 1:
                if(bit < 8){
                    CONFIG_GPIO_LEVEL = (buffer11 >> bit) & 0x01;
                    GPIO_write(CONFIG_GPIO_IOUART, CONFIG_GPIO_LEVEL);
                    bit++;
                    if(bit == 8){
                        sequence +=1;
                        bit = 0;
                    }
                }
                break;
            case 2:
                GPIO_write(CONFIG_GPIO_IOUART, CONFIG_GPIO_LED_ON);
                bufsize --;
                bufsend = 0;
                sequence = 0;
                if(bufsize == 0){
                    g_flag_batch = 1;
                    cntsend = 0;
                    g_flag_button = 0;
                    LGPTimerLPF3_stop(lgptHandle);
                }
        }
    }
}

b.Timer 电平控制函数

pthread_attr_init(&attrs);
priParam.sched_priority = 1;
retc  = pthread_attr_setschedparam(&attrs, &priParam);
retc |= pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);
retc |= pthread_attr_setstacksize(&attrs, THREADSTACKSIZE);
if (retc != 0)
{
    while (1) {}
}
retc = pthread_create(&thread, &attrs, MyIOThread, NULL);
if (retc != 0)
{
    while (1) {}
}