cruelfox

  • 2019-07-20
  • 加入了学习《DIY作品演示》,观看 基于MQTT消息通讯的IoT入门实例

  • 2019-07-19
  • 加入了学习《基于GD32E231的金鱼自动投喂机器人设计》,观看 基于GD32E231的金鱼自动投喂机器人设计

  • 2019-07-17
  • 回复了主题帖: 单端音频和差分音频有何区别?一个是单声道一个是立体声?

    单端信号是以公共GND为参考表示幅度的,就是1条信号线传。 差分信号是用两条信号线的电压差表示幅度的,用2条信号线传。

  • 2019-07-16
  • 回复了主题帖: 获奖名单:参与 PI 节能小测试,贡献工程师的力量,一起守护地球!

    确认信息无误,请换成E金币。

  • 2019-07-15
  • 发表了主题帖: 【CC1352P测评】小结

      LAUNCHXL_CC1352P1 这个板子的用料“豪华”,在于板载的调试器部分(MCU的外围除了必要的晶振,以及天线相关的射频元件,仅配了一片SPI Flash)。调试器是一片TI的TM4C系列MCU做主控,使用了外置USB高速PHY,USB传输吞吐量足够大。不过,板子上相当密集的元件是主要为了实现测量电流的 EnergyTrace 功能。这部分电路设计可不简单,虽然核心就是 ADS127L01 高速高精度Sigma-Delta ADC,模拟放大用的两个运放还要使用正负12V的电源,再加上 ADC 使用的三组电源和基准电压源,光电源电路就很多元件了。不仅如此,它还设计有校准电路,因此还有一些模拟开关器件。   EnergyTrace 软件已经集成在 CCS 里面了,我试了一下。从电流测量图线上看来,似乎噪声比较大,不知道能量计算的误差如何。显示的纵坐标尺度是 nA 单位,但是这个图一个格子已经是 500uA 了,对于几个 uA 级别的能测得怎样呢?把纵坐标放大,我看到最小的一个点大约是 4uA. 如果要测 standby 模式的电流恐怕不能胜任。   CC1352 的软件开发最重要特点是 SDK 自带 TI-RTOS, 无线协议栈依赖 RTOS 工作。这个 TI-RTOS 接管了省电模式,所以使低功耗设计变得容易,不需要进行很多的硬件细节操作。只要在 SDK 提供的例子上进行修改,上手还是容易的。TI的 SDK 文档也比较详尽。   另外,MCU 的软件有一部分函数在 ROM 中实现,编译时会自动使用,对开发者也不必了解到哪些调用使用了 ROM 中的代码。运用 ROM 可以节省 Flash 的开销,也对系统的执行效率有好处。因为 ROM 存在以及 TI-RTOS 的作用,软件的结构会变得复杂,但是实现的功能是 bare-metal 方式开发难以比拟的。   我感到的缺憾是 SDK 对 GCC 的支持不完整,比如重要的 BLE、15.4 无线协议栈都未提供 GCC 需要的文件。

  • 2019-07-14
  • 发表了主题帖: 【CC1352P测评】Sensor controller是个啥

      CC1352 内部包含了一个 Sensor Controller. 在介绍中只叙述了它是一个单独的低功耗处理器,配备了若干个外设,用于低功耗运行状态下的模拟采集、外部通信等工作。它自己能脱离主系统独立运行,当然功耗要比 M4 主 CPU 更低才合算。   不过,Sensor controller 并不是 ARM 核的,它是一个 16-bit CPU核,但它也不是 MSP430. 在 CC1352 参考手册中有对其核心寄存器和指令集的详细描述。看得出来,为了省电,这个 CPU 的功能并不强。   在总线连接上,Sensor controller 属于 AUX domain. 这是一个子系统,除了 CPU 之外还有一些单独的硬件设备,通过总线桥梁这些设备都可以被 M4 主 CPU 访问,但是反过来不可以。   Sensor controller 的指令和数据都是存放在一块 4kB 容量的 SRAM 里的。于是,它必须由主 CPU 去初始化,把代码装载到这个 SRAM 中才能工作。   Sensor controller 需要一个单独的开发工具:Sensor controller studio, 使用类似C语言的编程语言。安装这个软件以后,我随手试了一个LED blink的例子,没有找到 compile 菜单在哪里,结果是它飞快地自动就执行了编译。如果开发板连在电脑上,就可以连接板子然后运行,可以 debug 跟踪执行。而且,这个过程并不需要 CCS 参与。   我把板子再拔下来,发现原来 flash 里面的程序并没有受影响。这说明程序是直接被调试器写到 AUX domain 的 SRAM 里面的。那么,编译过程生成了什么?     在输出的目录下只有一些源文件,没有二进制文件。检查了一番,我发现这些文件是给主 CPU 的开发用的(不过不像有GCC支持的版本),应该是需要集成到应用的工程里面去。   在代码里面有这样的: /// Firmware image to be uploaded to the AUX RAM static const uint16_t pAuxRamImage[] = { /*0x0000*/ 0x140E, 0x0417, 0x140E, 0x0438, 0x140E, 0x0442, 0x140E, 0x045F, 0x140E, 0x0468, 0x140E, 0x0471, 0x140E, 0x047A, 0x8953, 0x9954, /*0x0020*/ 0x8D29, 0xBEFD, 0x4553, 0x2554, 0xAEFE, 0x445C, 0xADB7, 0x745B, 0x545B, 0x7000, 0x7CA2, 0x68AB, 0x009F, 0x1431, 0x68AC, 0x00A0, /*0x0040*/ 0x1431, 0x68AD, 0x00A1, 0x1431, 0x78A2, 0xF801, 0xFA01, 0xBEF2, 0x78A9, 0x68AB, 0xFD0E, 0x68AD, 0xED92, 0xFD06, 0x7CA9, 0x6440,   也就是 Sensor controller studio 把编译结果写在这里了,由 C 程序自己去装入执行用的 SRAM. 这种方法也是不二之选,比如说,(1) 这个16-bit CPU的程序开发和ARM工具链没有任何关系,它也不能访问片上其它设备,不需要CC1352 SDK,开发环境是与主系统独立的。(2) Sensor controller的代码和主系统牵扯少,适合两边分别开发各自编译。(3) 若把 Sensor controller 看作是一个功能定制的硬件,开发应用程序的时候就不需要关心它的软件实现了。

  • 发表了主题帖: 【CC1352P测评】无线CPU和主CPU的通信方式

      CC1352 的无线部分由一颗 ARM Cortex-m0 的 CPU 负责,那么它和主 CPU 是怎么使用片上资源的,又如何协同工作的呢?   SDK 中并没有包含任何 Cortex-m0 CPU 的代码相关的任何东西,因此不需要对它进行编程。与 ST STM32WB55 不同的是,CC1352 的这个 Cortex-m0 CPU 并不使用系统 Flash 来存放程序——它使用自己的 ROM. 这样就不支持软件升级,也不怕被误操作破坏固件。   手册上的这个图显示了两个 CPU 的子系统如何连接和通信。   按照手册的说法,系统 RAM 和 Radio RAM 是两个CPU都可以访问的,数据可以放在任何一块 RAM 中共享,以便数据传递(也可以由DMA在它们之间搬运数据)。Radio RAM 有一部分是可以在低功耗模式下保持数据的,因此可以实现主 CPU、SRAM 掉电后无线部分以极低功耗待机,并在需要的时候唤醒系统。   Radio Doorbell 提供了两个 CPU 之间的消息通信渠道,因为 Doorbell 可以向两个 CPU 分别产生 IRQ. 在 Cortex-m4 主CPU这一边有4个IRQ: RF_CPE0, RF_CPE1, RF_HW 以及 RF_CMD_ACK. 寄存器数量不多,除了与中断相关的,有意思的是 CMDR 和 CMDSTA 两个寄存器。 这里“命令”(CMD)的含义,并不是给 Doorbell 这个硬件设备的命令,而是要向无线子系统发出的命令,即给 Cortex-m0 CPU 传递的命令。寄存器表示的命令内容只有软件才可以解析,不过仅仅这一个 32-bit 寄存器,如何包括命令的全部内容?再细看 CMDR 的说明就大致明白了: 也就是,CMDR 可以传递一个“直接”的命令,或者一个 32-bit 对齐了的地址。联想到我在分析 rfEasylink_nortos 例子的时候见到的 RF 命令结构体,可以猜想一定就是把 RF 命令结构变量的地址写到了 Doorbell.      不妨再找 SDK 中 RF 驱动代码来看看。在 RFCC26X2_multiMode.c 中,RF_dispatchNextCmd() 函数里有如下的代码片段: /* Decode the radio operation itself. */ RF_Op* pOp = (RF_Op*)pNextCmd->pOp; /* Send the radio operation to the RF core. */ RFCDoorbellSendTo((uint32_t)pOp); 而 RFCDoorbellSendTo() 函数是这样实现的: uint32_t RFCDoorbellSendTo(uint32_t pOp) { // Wait until the doorbell becomes available while(HWREG(RFC_DBELL_BASE + RFC_DBELL_O_CMDR) != 0); RFCAckIntClear(); // Submit the command to the CM0 through the doorbell HWREG(RFC_DBELL_BASE + RFC_DBELL_O_CMDR) = pOp; // Wait until the CM0 starts to parse the command while(!HWREG(RFC_DBELL_BASE + RFC_DBELL_O_RFACKIFG)); RFCAckIntClear(); // Return with the content of status register return(HWREG(RFC_DBELL_BASE + RFC_DBELL_O_CMDSTA)); } 没错,的确就是把 RF_Op 类型指针写到了 CMDR 寄存器里面。然后无线专用的 CPU 会根据地址去解析命令、读取命令包含的数据。   CC13x2 的手册中用了一百多页的篇幅列出了各类命令的数据包结构定义。虽然开发比如 BLE, Zigbee 这样的应用不需要去了解到如此底层的细节,TI将MCU作为一个器件,其手册的完备性是可圈可点。

  • 回复了主题帖: 【CC1352P测评】BLE程序的框架

    图片重新贴一下。

  • 发表了主题帖: 【CC1352P测评】BLE程序的框架

      从 SDK 例子的目录树结构上看,CC1352 的 BLE 程序是必须要用到 TI-RTOS 的,而且 SDK 的例子里面没有 BLE 的 GCC 工程。于是我不得已只能用慢吞吞的 CCS 来编译一个试试。尽管 "Simple peripheral" 编译和下载都成功了,手机也能发现和连接板子生成的 BLE 设备,我遇到了一个大问题——CCS中无法设置断点调试,只能单步指令执行(还不如GDB呢,但是CCS生成的ELF和GCC不一样),所以跟踪执行过程来分析代码就行不通了。   我阅读了 BLE SDK 的文档,其中并未提到支持GCC, 那我就不要试图用 GCC 去处理这个工程了。有些遗憾,下面只能直接看代码分析。   和已经分析过的例子比起来,Simple_periphral 的主函数多了一些东西: int main() { /* Register Application callback to trap asserts raised in the Stack */ RegisterAssertCback(AssertHandler); Board_initGeneral(); // Enable iCache prefetching VIMSConfigure(VIMS_BASE, TRUE, TRUE); // Enable cache VIMSModeSet(VIMS_BASE, VIMS_MODE_ENABLED); /* Update User Configuration of the stack */ user0Cfg.appServiceInfo->timerTickPeriod = Clock_tickPeriod; user0Cfg.appServiceInfo->timerMaxMillisecond = ICall_getMaxMSecs(); /* Initialize ICall module */ ICall_init(); /* Start tasks of external images - Priority 5 */ ICall_createRemoteTasks(); SimplePeripheral_createTask(); /* enable interrupts and start SYS/BIOS */ BIOS_start(); return 0; }   主要是多了 ICall_init() 以及类似的其它 ICall_... 的东西。ICall 是个啥只有看文档才知道,这里先不管。后面 SimplePeripheral_createTask() 才是要创建这个 BLE 应用的任务,它在另外一个源文件中。 void SimplePeripheral_createTask(void) { Task_Params taskParams; // Configure task Task_Params_init(&taskParams); taskParams.stack = spTaskStack; taskParams.stackSize = SP_TASK_STACK_SIZE; taskParams.priority = SP_TASK_PRIORITY; Task_construct(&spTask, SimplePeripheral_taskFxn, &taskParams, NULL); }   显然,任务的主函数是 SimplePeripheral_taskFxn(), 还好写得并不长: static void SimplePeripheral_taskFxn(UArg a0, UArg a1) { // Initialize application SimplePeripheral_init(); // Application main loop for (;;) { uint32_t events; // Waits for an event to be posted associated with the calling thread. // Note that an event associated with a thread is posted when a // message is queued to the message receive queue of the thread events = Event_pend(syncEvent, Event_Id_NONE, SP_ALL_EVENTS, ICALL_TIMEOUT_FOREVER); if (events) { ICall_EntityID dest; ICall_ServiceEnum src; ICall_HciExtEvt *pMsg = NULL; // Fetch any available messages that might have been sent from the stack if (ICall_fetchServiceMsg(&src, &dest, (void **)&pMsg) == ICALL_ERRNO_SUCCESS) { uint8 safeToDealloc = TRUE; if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity)) { ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg; // Check for BLE stack events first if (pEvt->signature != 0xffff) { // Process inter-task message safeToDealloc = SimplePeripheral_processStackMsg((ICall_Hdr *)pMsg); } } if (pMsg && safeToDealloc) { ICall_freeMsg(pMsg); } } // If RTOS queue is not empty, process app message. if (events & SP_QUEUE_EVT) { while (!Queue_empty(appMsgQueueHandle)) { spEvt_t *pMsg = (spEvt_t *)Util_dequeueMsg(appMsgQueueHandle); if (pMsg) { // Process message. SimplePeripheral_processAppMsg(pMsg); // Free the space from the message. ICall_free(pMsg); } } } } } }   先是执行一个初始化函数,后面就进入循环,等待事件并处理事件。不究细节的话,这个程序的结构看起来还是简单的。实际上,把另外一个例子 "Simple central" 拿来比较,它们的 main 函数和任务主函数写法是相同的。在这里又遇到 ICall_... 的东西了,等等再看吧。先看下初始化做了哪些工作。   SimplePeripheral_init() 这个函数比较长,没有必要贴出来。在当中首先是执行了   ICall_registerApp(&selfEntity, &syncEvent);   两个参数都是指针,分别指向两个全局变量,调用后它们即被初始化,在后面程序中会用到。下一步是   appMsgQueueHandle = Util_constructQueue(&appMsgQueue);   字面意思是创建消息队列。后一个操作是   Util_constructClock(&clkPeriodic, SimplePeripheral_clockHandler, SP_PERIODIC_EVT_PERIOD, 0, false, (UArg)&argPeriodic);   这里创建了一个软件时钟。   再后面就开始 GAP, GATT 服务的配置了,有很多个调用。来看一个   GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, attDeviceName);   的具体实现:它在 BLE stack 的源代码 icall_api.c 中定义: bStatus_t GGS_SetParameter(uint8 param, uint8 len, void *value) { return profileSetParameter(param, len, value, DISPATCH_GAP_GATT_SERV, DISPATCH_PROFILE_SET_PARAM, matchGGSSetParamCS); } static bStatus_t profileSetParameter(uint8 param, uint8 len, void *pValue, uint8_t subgrp, uint8_t cmdId, ICall_MsgMatchFn matchCSFn) { // Allocate message buffer space ICall_ProfileSetParam *msg = (ICall_ProfileSetParam *)ICall_allocMsg(sizeof(ICall_ProfileSetParam)); if (msg) { setDispatchCmdEvtHdr(&msg->hdr, subgrp, cmdId); // copy param ID, len, value msg->paramIdLenVal.paramId = param; msg->paramIdLenVal.len = len; msg->paramIdLenVal.pValue = pValue; // Send the message return sendWaitMatchCS(ICall_getEntityId(), msg, matchCSFn); } return MSG_BUFFER_NOT_AVAIL; }   它是通过 ICall 先分配了一个消息,再按参数填写消息内容,最后发送消息。其中用的 sendWaitMatchCS() 函数里面调用了 ICall_sendServiceMsg() 函数。这个函数定义为 static ICall_Errno ICall_sendServiceMsg(ICall_EntityID src, ICall_ServiceEnum dest, ICall_MSGFormat format, void *msg) { ICall_SendArgs args; args.hdr.service = ICALL_SERVICE_CLASS_PRIMITIVE; args.hdr.func = ICALL_PRIMITIVE_FUNC_SEND_SERV_MSG; args.src = src; args.dest.servId = dest; args.format = format; args.msg = msg; return ICall_dispatcher((ICall_FuncArgsHdr *)&args); }   也就是最终将BLE服务请求通过 ICall_dispatcher() 发送。那么,消息的接收者是谁呢?ICall, 这到底是个啥?在 BLE stack 源码的 icall.c 中,可以找到很多相关函数的实现。例如在 main() 中就用到的 ICall_init()  void ICall_init(void) { size_t i; for (i = 0; i < ICALL_MAX_NUM_TASKS; i++) { ICall_tasks.task = NULL; ICall_tasks.queue = NULL; } for (i = 0; i < ICALL_MAX_NUM_ENTITIES; i++) { ICall_entities.service = ICALL_SERVICE_CLASS_INVALID_ENTRY; } #ifndef ICALL_JT /* Initialize primitive service */ ICall_initPrim(); #else /* Initialize heap */ ICall_heapInit(); #endif }   这暗示了,ICall 背后是若干个 TI-RTOS 的任务。在 SDK BLE5 stack 文档中有一个图举了这样的例子: ICall 是一个中间层,应用程序对 BLE stack 的访问都通过 ICall 来实现。因为有 TI-RTOS 作为基础,应用程序就成了消息(事件)驱动型的。协议栈的部分也由 ICall 管理了,我们不用去管它需要创建多少 RTOS 任务,不用直接和那些任务通信,更不用管底层的两个CPU之间的信息交互(无线功能有一个Cortex-m0核在负责)。     因为有 ICall 在,基于 CC1352 (也包括CC26xx等其它MCU)进行 BLE 开发的时候,只要按照 SDK 例子做一个程序框架,再编写 BLE 事件处理程序就可以了,整个工程只需要少数几个源文件(其余都是库)。SDK 提供的几个工程例子代码读起来不难,虽然内容太多了不能熟练到从零开始自己写,TI 的 BLE stack 文档还是很详细的。看懂一两个例子之后再改写一个自己的 BLE 设备难度不会很大。  

  • 2019-07-10
  • 回复了主题帖: 问下2596前面加共模电感的作用

    qwqwqw2088 发表于 2019-7-10 07:57 2596是国半早期的开关芯片,内部开关频率只有150KHz。外部电容选择不好,输入旁路电容时要其实很小心的,包 ...
    为什么要共模? 我看不出这个共模电流的回路在哪里……

  • 2019-07-08
  • 回复了主题帖: 热水器里有个小东西,不注意一个月能耗电1000度???

    热水器怎么可能一直在加热?那样早沸腾炸了

  • 回复了主题帖: LIS25BA骨震动传感器的驱动问题

    用F769的SAI硬件啊,不要自己生成时钟。

  • 2019-07-07
  • 发表了主题帖: 【CC1352P测评】TI-RTOS uartecho小试

      为了理解 TI-RTOS 的工作方式,我找了 SDK 的例子——rtos下面driver目录中的 uartecho 工程来分析。这个例子的作用很简单,就是从 UART 接收字符再发送出去,从开发板的虚拟串口可以测试(回显效果)。   SDK 中不包含 TI-RTOS 部分的预编译库代码,所以需要事先编译一下。如果是使用 CCS 开发环境,导入这个工程以后会自动处理。我用命令行直接编译工程,就需要到 SDK 的 kernel/tirtos/builds/CC1352P1_LAUNCHXL/release/gcc 目录下去手动 make 一下。除了GCC的路径,xdc_tools 的路径还需要在 imports.mak 中设置(小改一下,虽然它是随SDK安装的)。     这个例子不用到无线部分,于是文件也简单一些,一共是 uartecho.obj main_tirtos.obj CC1352P1_LAUNCHXL.obj CC1352P1_LAUNCHXL_fxns.obj ccfg.obj 这五个模块。我们只需要重点关注前两个。   主函数做的也就是初始化 RTOS 环境,比我分析过的 nortos 例子多些代码: int main(void) { pthread_t thread; pthread_attr_t attrs; struct sched_param priParam; int retc; /* Call driver init functions */ Board_initGeneral(); /* Initialize the attributes structure with default values */ pthread_attr_init(&attrs); /* Set priority, detach state, and stack size attributes */ 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) { /* failed to set attributes */ while (1) {} } retc = pthread_create(&thread, &attrs, mainThread, NULL); if (retc != 0) { /* pthread_create() failed */ while (1) {} } BIOS_start(); return (0); }   照样先用 Board_initGeneral() 初始化板子,这个函数实现和我分析过的 rfEasyLink_nortos 例子当中的一样。然后,是设置线程的属性,使用了 pthread_attr_setschedparam(), pthread_attr_setdetachstate(), pthread_attr_setstacksize() 三个函数,接下来用 pthread_create() 创建线程。最后调用 BIOS_start() 开始运行。   mainThread() 函数看起来就是常规操作了,没有用到线程相关的 API. void *mainThread(void *arg0) { char input; const char echoPrompt[] = "Echoing characters:\r\n"; UART_Handle uart; UART_Params uartParams; /* Call driver init functions */ GPIO_init(); UART_init(); /* Configure the LED pin */ GPIO_setConfig(Board_GPIO_LED0, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW); /* Turn on user LED */ GPIO_write(Board_GPIO_LED0, Board_GPIO_LED_ON); /* Create a UART with data processing off. */ UART_Params_init(&uartParams); uartParams.writeDataMode = UART_DATA_BINARY; uartParams.readDataMode = UART_DATA_BINARY; uartParams.readReturnMode = UART_RETURN_FULL; uartParams.readEcho = UART_ECHO_OFF; uartParams.baudRate = 115200; uart = UART_open(Board_UART0, &uartParams); if (uart == NULL) { /* UART_open() failed */ while (1); } UART_write(uart, echoPrompt, sizeof(echoPrompt)); /* Loop forever echoing */ while (1) { UART_read(uart, &input, 1); UART_write(uart, &input, 1); } }   那么,用 RTOS 的意义怎么体现?其实在 UART_read() 和 UART_write() 函数内部,它们并不是简单地对硬件设备寄存器进行读写操作。比如看 UART_read 是这样实现的: int_fast32_t UART_read(UART_Handle handle, void *buffer, size_t size) { return (handle->fxnTablePtr->readFxn(handle, buffer, size)); } UART_Handle 类型是指向 struct UART_Config_ 类型的指针, typedef struct UART_Config_ { /*! Pointer to a table of driver-specific implementations of UART APIs */ UART_FxnTable const *fxnTablePtr; /*! Pointer to a driver specific data object */ void *object; /*! Pointer to a driver specific hardware attributes structure */ void const *hwAttrs; } UART_Config; 在主线程中事先对 UART 进行初始化,这里初始化了所有的 UART,是通过 SDK API UART_init(). void UART_init(void) { uint_least8_t i; uint_fast32_t key; key = HwiP_disable(); if (!isInitialized) { isInitialized = (bool) true; /* Call each driver's init function */ for (i = 0; i < UART_count; i++) { UART_config[i].fxnTablePtr->initFxn((UART_Handle) &(UART_config[i])); } } HwiP_restore(key); }   值得注意的是,UART_init() 要用到的 UART_config[] 结构数组,以及 UART_config 变量都是在 CC1352P1_LAUNCHXL.c 当中定义的: const UART_Config UART_config[CC1352P1_LAUNCHXL_UARTCOUNT] = { { .fxnTablePtr = &UARTCC26XX_fxnTable, .object = &uartCC26XXObjects[CC1352P1_LAUNCHXL_UART0], .hwAttrs = &uartCC26XXHWAttrs[CC1352P1_LAUNCHXL_UART0] }, { .fxnTablePtr = &UARTCC26XX_fxnTable, .object = &uartCC26XXObjects[CC1352P1_LAUNCHXL_UART1], .hwAttrs = &uartCC26XXHWAttrs[CC1352P1_LAUNCHXL_UART1] }, };   因此 fxnTablePtr 指针是系统预设的常量,指向 UARTCC26XX_fxnTable ——它是driver代码中定义的常量: const UART_FxnTable UARTCC26XX_fxnTable = { UARTCC26XX_close, UARTCC26XX_control, UARTCC26XX_init, UARTCC26XX_open, UARTCC26XX_read, UARTCC26XX_readPolling, UARTCC26XX_readCancel, UARTCC26XX_write, UARTCC26XX_writePolling, UARTCC26XX_writeCancel };   所以在主线程调用的 UART_read(), 效果是调用了 UARTCC26XX_read() 函数,其源文件在 UARTCC26XX.c 中,写得还蛮长的,因此就不贴在这里了。其中用到了信号量(Semaphore)相关的函数,已经涉及操作系统方面的特性了。其实 SDK 的 UART 驱动是和 nortos 程序共用的,nortos 程序可以看成 TI-RTOS 的单线程简化版本。为什么还要把单线程程序做这么复杂?为了降低功耗,让 SDK 底层代码去管理省电模式。     作为 RTOS 的测试,不妨改写一个点灯的的程序吧。增加一个线程 testThread 如下: #include <ti/sysbios/knl/Task.h> void *testThread(void *arg0) { Task_sleep(100000); GPIO_setConfig(Board_GPIO_LED1, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW); for(;;) { GPIO_write(Board_GPIO_LED1, Board_GPIO_LED_ON); Task_sleep(100000); GPIO_write(Board_GPIO_LED1, Board_GPIO_LED_OFF); Task_sleep(100000); } } 在 main() 中也多加一行代码来创建它:  retc |= pthread_create(&thread1, &attrs, testThread, NULL);   重新编译以后下载,就实现了原来的 uartecho 附带 LED1 间歇亮灭的效果。 此内容由EEWORLD论坛网友cruelfox原创,如需转载或用于商业用途需征得作者同意并注明出处

  • 发表了主题帖: 【CC1352P测评】rfEasyLinkTx运行过程简析

      SDK 带的例子里面 NoRTOS-rfEasyLinkTx 这个工程看起来是最简单的一个无线应用了,它只是间歇性地发送数据包。代码还算不复杂,所以成了我选择的第一个剖析例子。   根据 makefile 内容,除去库文件,算是工程自己的代码产生的目标文件一共有8个: EasyLink_nortos.obj main_nortos.obj smartrf_settings.obj smartrf_settings_predefined.obj rfEasyLinkTx_nortos.obj CC1352P1_LAUNCHXL_fxns.obj ccfg.obj CC1352P1_LAUNCHXL.obj 它们也的确对应了工程目录里面的8个.c文件。从文件名看上去,这些文件除了执行代码之外还有相当部分是用来提供配置参数的。     main_nortos.c 这个文件很短,就包含一个短小的主函数: int main(void) { /* Call driver init functions */ Board_initGeneral(); /* Start NoRTOS */ NoRTOS_start(); /* Call mainThread function */ mainThread(NULL); while (1); }   main() 是被 localProgramStart() 函数调用的,后者在 SDK 提供的编译好的启动代码里面(意思是开发者不要随意去修改 localProgramStart() 函数),而且这个启动代码完成的事情并不仅仅是通常MCU的初始化代码那样初始化C运行库的环境,如前面我的帖子所述,它调用 SetupTrimDevice() 进行了硬件相关的处理,我们开发应用可以不理会它。   main() 函数的三个调用逻辑很清晰:一是把板子的硬件初始化;二是把 NoRTOS 环境启动;三是执行主线程,就是应用程序。   (1) Board_initGeneral() 实际是换名字成 CC1352P1_LAUNCHXL_initGeneral(),这个函数在 CC1352P1_LAUNCHXL.c 里面: void CC1352P1_LAUNCHXL_initGeneral(void) { Power_init(); if (PIN_init(BoardGpioInitTable) != PIN_SUCCESS) { /* Error with PIN_init */ while (1); } /* Perform board-specific initialization */ Board_initHook(); }   Power_init() 函数是 SDK 驱动程序库中的。尽管这个函数不需要参数,在 SDK 文档 driver 部分也没有叙述需要额外提供什么配置数据,根据本人的试验,它需要一个全局常量(结构) PowerCC26X2_config 提供配置信息。这个结构常量在工程文件 CC1352P1_LAUNCHXL.c 中提供了。 const PowerCC26X2_Config PowerCC26X2_config = { .policyInitFxn = NULL, .policyFxn = &PowerCC26XX_standbyPolicy, .calibrateFxn = &PowerCC26XX_calibrate, .enablePolicy = true, .calibrateRCOSC_LF = true, .calibrateRCOSC_HF = true, };   接着调用的 PIN_init() 的调用是初始化引脚的,它使用了 CC1352P1_LAUNCHXL.c 中的 BoardGpioInitTable 数组作为参数: const PIN_Config BoardGpioInitTable[] = { CC1352P1_LAUNCHXL_PIN_RLED | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, /* LED initially off */ CC1352P1_LAUNCHXL_PIN_GLED | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, /* LED initially off */ CC1352P1_LAUNCHXL_PIN_BTN1 | PIN_INPUT_EN | PIN_PULLUP | PIN_IRQ_BOTHEDGES | PIN_HYSTERESIS, /* Button is active low */ CC1352P1_LAUNCHXL_PIN_BTN2 | PIN_INPUT_EN | PIN_PULLUP | PIN_IRQ_BOTHEDGES | PIN_HYSTERESIS, /* Button is active low */ CC1352P1_LAUNCHXL_SPI_FLASH_CS | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_DRVSTR_MIN, /* External flash chip select */ CC1352P1_LAUNCHXL_UART0_RX | PIN_INPUT_EN | PIN_PULLDOWN, /* UART RX via debugger back channel */ CC1352P1_LAUNCHXL_UART0_TX | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL, /* UART TX via debugger back channel */ CC1352P1_LAUNCHXL_SPI0_MOSI | PIN_INPUT_EN | PIN_PULLDOWN, /* SPI master out - slave in */ CC1352P1_LAUNCHXL_SPI0_MISO | PIN_INPUT_EN | PIN_PULLDOWN, /* SPI master in - slave out */ CC1352P1_LAUNCHXL_SPI0_CLK | PIN_INPUT_EN | PIN_PULLDOWN, /* SPI clock */ CC1352P1_LAUNCHXL_DIO28_RF_24GHZ | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, /* Path disabled */ CC1352P1_LAUNCHXL_DIO29_RF_HIGH_PA | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, /* Path disabled */ CC1352P1_LAUNCHXL_DIO30_RF_SUB1GHZ | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, /* Path disabled */ PIN_TERMINATE };   这个初始化方式很独特,一次调用初始化若干个引脚,每个引脚的指定和设置包含在一个32-bit整数里面。但是我看这里还没有引脚功能复用的定义,只是指定了输入输出类型等基本信息。   在 SDK 的 source/drivers/pin/PINCC26XX.c 里面有 PIN_init() 的具体实现,还不光是设置I/O寄存器那么简单。   Board_initHook() 在 CC1352P1_LAUNCHXL_fxns.c 里面,包含了两个函数调用:initAntennaSwitch() 和 CC1352P1_LAUNCHXL_shutDownExtFlash(), 它们的实现也在同一个C文件当中。   (2) NoRTOS_start() 就是 SDK 的库函数了。找出源代码来看,居然相当简单: void NoRTOS_start() { HwiP_enable(); }   我想大概是,毕竟没有多任务执行,就没有太多要初始化的,那么暂且就不管了。   (3) mainThread() 在 rfEasyLinkTx_nortos.c 中,是本应用的主体。它调用的无线操作相关函数在 easylink/EasyLink_nortos.c 中实现。我整理了一下,主要有这几个:EasyLink_init(), EasyLink_setRfPower(), EasyLink_getAbsTime(), EasyLink_transmitAsync() 和 EasyLink_abort(). 这些函数再去调用 SDK RF driver 提供的函数。     分析一下 EasyLink_transmitAsync() 这个异步操作单数, 每当发送数据包的时候,mainThread 会执行         EasyLink_transmitAsync(&txPacket, txDoneCb); 第一个参数是结构指针,指向下面数据包描述类型的变量 typedef struct { uint8_t dstAddr[8]; //!< Destination address uint32_t absTime; //!< Absolute time to Tx packet (0 for immediate) //!< Layer will use last SeqNum used + 1 uint8_t len; //!< Payload Length uint8_t payload[EASYLINK_MAX_DATA_LENGTH]; //!< Payload } EasyLink_TxPacket; 第二个参数是一个回调函数入口地址,当发送结束后会调用指定的函数。   在函数中实现发送操作的RF driver API调用是         asyncCmdHndl = RF_postCmd(rfHandle, (RF_Op*)&EasyLink_cmdPropTx,             RF_PriorityHigh, txDoneCallback, EASYLINK_RF_EVENT_MASK); 这是在RF命令队列中加入了一个命令,由第二个参数指定命令的具体内容,它是属于面这个结构定义的类型(这是TI私有协议发送命令专用,其它命令会有所不同,不过都是从 rfc_radioOp_t 基本类型扩展而来) struct __RFC_STRUCT rfc_CMD_PROP_TX_s { uint16_t commandNo; uint16_t status; rfc_radioOp_t *pNextOp; ratmr_t startTime; struct { uint8_t triggerType:4; uint8_t bEnaCmd:1; uint8_t triggerNo:2; uint8_t pastTrig:1; } startTrigger; struct { uint8_t rule:4; uint8_t nSkip:4; } condition; struct { uint8_t bFsOff:1; uint8_t :2; uint8_t bUseCrc:1; uint8_t bVarLen:1; } pktConf; uint8_t pktLen; uint32_t syncWord; uint8_t* pPkt; } __RFC_STRUCT_ATTR; EasyLink_cmdPropTx 这是一个全局变量,在 EasyLink_init() 中进行了初始化,于是在 EasyLink_transmitAsync() 被调用的时候仅改变其中和数据包有关的成员。在 smartrf_settings.c 中(以及 smartrf_settings_predefined.c 中还有个相同的)定义了一个设置,会被 EasyLink_init() 利用。 rfc_CMD_PROP_TX_t RF_cmdPropTx = { .commandNo = 0x3801, .status = 0x0000, .pNextOp = 0, // INSERT APPLICABLE POINTER: (uint8_t*)&xxx .startTime = 0x00000000, .startTrigger.triggerType = 0x0, .startTrigger.bEnaCmd = 0x0, .startTrigger.triggerNo = 0x0, .startTrigger.pastTrig = 0x0, .condition.rule = 0x1, .condition.nSkip = 0x0, .pktConf.bFsOff = 0x0, .pktConf.bUseCrc = 0x1, .pktConf.bVarLen = 0x1, .pktLen = 0x1E, // SET APPLICATION PAYLOAD LENGTH .syncWord = 0x930B51DE, .pPkt = 0, // INSERT APPLICABLE POINTER: (uint8_t*)&xxx };   命令格式的细节需要对无线部分深入了解才能掌握,目前用不到我就不深入分析了。比较有意思的是 RF_postCmd() 里面是如何实现的?这是库函数,可以看代码: RF_CmdHandle RF_postCmd(RF_Handle h, RF_Op* pOp, RF_Priority ePri, RF_Callback pCb, RF_EventMask bmEvent) { /* Assert */ DebugP_assert(h != NULL); DebugP_assert(pOp != NULL); /* Local pointer to a radio commands */ RF_CmdHandle cmdHandle = (RF_CmdHandle)RF_ALLOC_ERROR; /* Enter critical section */ uint32_t key = HwiP_disable(); /* Try to allocate container */ RF_Cmd* pCmd = RF_cmdAlloc(); /* If allocation failed */ if (pCmd) { /* Stop inactivity clock if running */ ClockP_stop(&h->state.clkInactivity); /* Increment the sequence number and mask the value */ RF_cmdQ.nSeqPost = (RF_cmdQ.nSeqPost + 1) & N_CMD_MODMASK; /* Populate container with reset values */ pCmd->pOp = pOp; pCmd->ePri = ePri; pCmd->pCb = pCb; pCmd->ch = RF_cmdQ.nSeqPost; pCmd->pClient = h; pCmd->bmEvent = (bmEvent | RFC_DBELL_RFCPEIFG_LAST_COMMAND_DONE_M) & ~RF_INTERNAL_IFG_MASK; pCmd->pastifg = 0; pCmd->flags = RF_CMD_ALLOC_FLAG; /* Cancel ongoing yielding */ h->state.bYielded = false; /* Submit to pending command to the queue. */ List_put(&RF_cmdQ.pPend, (List_Elem*)pCmd); /* Trigger dispatcher HWI if there is no running command */ RF_triggDispatcher(); /* Return with the command handle as success */ cmdHandle = pCmd->ch; } /* Exit critical section */ HwiP_restore(key); /* Return with an error code */ return(cmdHandle); }   先给这个 RF 命令分配空间,把参数复制进去(注意 RF_Op* pOp 只是指针拷贝,命令的内容仍然在函数调用者那里),然后将命令插入队列(pending的列表),最后调 RF_triggDispatcher() 将命令发出去……   如何发出去?在 SDK RF driver 的 RFCC26X2_multiMode.c 中还有相当多的代码在处理相关事务。要详细了解,就不得不去理解 HWI, SWI, RAT 这几样东西了……感觉要掉入一个坑。作为评测活动,到此打住!     作为 CC1352 的应用开发者,不需要去了解到太底层的实现细节,用上层的协议有关的 API 就差不多了。这个 rfEasyTx 的例子从顶层看起来(就到 mainThread 实现这一级)还挺简单的。   但是,作为一个很简单的 demo, 要用到 8 个C源文件,也不能算精简了,想入门的看一看并非一目了然。EasyLink_nortos.c 算作是中间件,不能由开发者自己编写;smartrf_settings.c 和 smartrf_settings_predefined.c 是由 SmartRF Studio 软件生成的配置信息,都不是手工编写;CC1352P1_LAUNCHXL...前缀的文件是和开发板资源有关的,包含了很多实际上没有用到(编译过程中会被优化丢掉)的数据。若要做一个新项目以此为模板的开始的话,我觉得仍有点冗余。   此内容由EEWORLD论坛网友cruelfox原创,如需转载或用于商业用途需征得作者同意并注明出处

  • 2019-07-04
  • 加入了学习《智能房车控制系统作品演示》,观看 作品演示

  • 2019-07-03
  • 回复了主题帖: 帮我看看这句话翻译的对吗?

    我觉得这句英文的语法有问题呢。

  • 2019-06-30
  • 加入了学习《《小水电站自动控制》项目演示》,观看 《家庭智能监控》项目演示

  • 加入了学习《色卡密码锁》,观看 色卡密码锁

  • 加入了学习《GD32E231 DIY》,观看 基于2G GPRS+短信报警功能的山洪预警广播系统监测终端设计

  • 加入了学习《GE32E231_DIY:基于GD32E231的无线氛围灯平滑调光系统》,观看 作品演示

最近访客

< 1/6 >

统计信息

已有489人来访过

  • 芯币:5959
  • 好友:10
  • 主题:116
  • 回复:931
  • 课时:1
  • 资源:1

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言