本文主要是分享了熟悉代码的一个过程,先看懂官方的应用工程,才可以有保障的将应用功能移植到其它的平台。
在安信可官方提供了NodeMCU-BU1开发板基于STM32的出厂固件AT源代码,可以到https://docs.ai-thinker.com/uwb这个链接去下载;在解压这个工程文件后,打开KEIL工程,就习惯性的从main.c文件开始研读,对于main函数之前的那些我们先选择性跳过;在main函数中,我们看到对于外设的一初始化,对DW1000复位,SPI速率的设置,然后就是根据TAG_ID判断是不是ANCHOR_TAG做不同的处理和测距离;
main函数如下所示:
int main(void)
{
uint8 anthor_index = 0;
uint8 tag_index = 0;
uint8 Semaphore_Enable = 0 ;
uint8 Waiting_TAG_Release_Semaphore = 0;
int8 frame_len = 0;
//lable1:
/* Start with board specific hardware init. */
peripherals_init();
printf("hello dwm1000!\r\n");
/* Reset and initialise DW1000.
* For initialisation, DW1000 clocks must be temporarily set to crystal speed. After initialisation SPI rate can be increased for optimum
* performance. */
reset_DW1000(); /* Target specific drive of RSTn line into DW1000 low for a period. */
spi_set_rate_low();
if(dwt_initialise(DWT_LOADUCODE) == -1)
{
printf("dwm1000 init fail!\r\n");
OLED_ShowString(0,0,"INIT FAIL");
while (1)
{
//STM_EVAL_LEDOn(LED1);
GPIO_SetBits(GPIOC,GPIO_Pin_13);
deca_sleep(1000);
//STM_EVAL_LEDOff(LED1);
GPIO_ResetBits(GPIOC,GPIO_Pin_13);
deca_sleep(1000);
}
}
spi_set_rate_high();
/* Configure DW1000. See NOTE 6 below. */
dwt_configure(&config);
dwt_setleds(1);
/* Apply default antenna delay value. See NOTE 1 below. */
dwt_setrxantennadelay(RX_ANT_DLY);
dwt_settxantennadelay(TX_ANT_DLY);
OLED_ShowString(0,0,"INIT PASS");
printf("init pass!\r\n");
printf("AIT-BU01-DB V100 T2020-5-17\r\n");
AnchorList[0].x =0.12;
AnchorList[0].y =0.34;
AnchorList[0].z =0;
AnchorList[1].x =0.25;
AnchorList[1].y =0;
AnchorList[1].z =0;
AnchorList[2].x =0;
AnchorList[2].y =0;
AnchorList[2].z =0;
int rx_ant_delay =32880;
int index = 0 ;
extern UserSet UserSetNow;
uint16_t buff[3]={1,0,0xff};//ĬèÏÖμ
FLASH_ReadMoreData(USER_FLASH_BASE,buff,3);
if(buff[0]==1)
{
UserSetNow.ANCHOR_TAG=1;
}
else if(buff[0]==0)
{
UserSetNow.ANCHOR_TAG=0;
}
else
{
UserSetNow.ANCHOR_TAG=1;
}
//#ifdef ANTHOR
if(UserSetNow.ANCHOR_TAG==1)
{
if(buff[1]>=0 && buff[1]<=255)
{
UserSetNow.ID=buff[1];
ANCHOR_IND=UserSetNow.ID;
}
printf("device:anchor ID:%d\r\n",ANCHOR_IND);
Anchor_Array_Init();
/* Loop forever initiating ranging exchanges. */
OLED_ShowString(0,0,"DS TWR ANTHOR");
//OLED_ShowString(0,2,"Distance:");
//KalMan_PramInit();
ANTHOR_MEASURE();
}
//#endif
//#ifdef TAG
if(UserSetNow.ANCHOR_TAG==0)
{
if(buff[1]>=0 && buff[1]<=255)
{
UserSetNow.ID=buff[1];
TAG_ID=UserSetNow.ID;
MASTER_TAG=TAG_ID;
}
printf("device:TAG ID:%d\r\n",UserSetNow.ID);
if(TAG_ID == MASTER_TAG)
{
OLED_ShowString(0,0,"DS MASTER TAG:");
}
else
{
OLED_ShowString(0,0,"DS SLAVE TAG:");
}
/* Set expected response's delay and timeout. See NOTE 4 and 5 below.
* As this example only handles one incoming frame with always the same delay and timeout, those values can be set here once for all. */
dwt_setrxaftertxdelay(POLL_TX_TO_RESP_RX_DLY_UUS);
dwt_setrxtimeout(RESP_RX_TIMEOUT_UUS);
//OLED_ShowString(0,2,"Distance:"); chnage by johhn
if(TAG_ID == MASTER_TAG)
{
Semaphore_Enable = 1 ;
Semaphore_Init();
Waiting_TAG_Release_Semaphore = 0;
}
else
{
Semaphore_Enable = 0 ;
}
//Master TAG0
TAG_MEASURE();
}
//#endif
}
看到这边就已经有些疑问了,NodeMCU-BU01开发板除了搭载了BU01定位模块之外,还有不少的外围器件,比如温湿度传感器、加速度传感器、独立按键、LED灯等外围器件。从程序的结构上来并没有找到相应的模块化编程具有的外设驱动程序,也没有在初始化的时候看到对外围器件的初始化……还看到一个LIB的库文件,感觉有种不好的预感,不会是应用实现的功能都被封装在这个LIB文件里了吧……
我们还是先来看一下main函数中最开始对于外设的初始化都有些什么吧……
void peripherals_init (void)
{
RCC_APB2PeriphClockCmd(
RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD |
RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO,
ENABLE);
/* Enable SPI1 clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
/* Enable SPI2 clock */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
//rcc_init();
gpio_init();
systick_init();
spi_peripheral_init();
usartinit();
//lcd_init();
#ifdef STM32F10X_MD
MX_I2C1_Init();
#endif
drv_lis2dh12_init();
SHT20_init();
OLED_Init(); //3õê¼»ˉOLED
OLED_Clear() ;
}
我们看到了有GPIO、SYSTICK、SPI、USART、I2C、SHT20、OLED等,这里我们看到我部分我们想要的,SHT20温湿度传感器、OLED虽然在这个开发板上没有配置,但它作为可选的外接设备存在;还有就是I2C的初始化,我们联想到加速度传感器,还有就是SPI,这正好是BU01定位模块的通讯接口……而USART的初始化配置,则想到了这个应用程序所支持的AT指令功能;但这么大的代码量,而且又不熟悉的代码,我们到哪里去找AT实现的部分呢?这时想到了在初始化USART时,有配置NVIC中断,我们打开STARTUP文件,通过USART的中断向量表函数,定位到了USART中断的实现函数:
void USART1_IRQHandler(void)
{
//printf("hello dwm1000!\r\n");
char buff;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)//如果寄存器中有数据
{
/* Read one byte from the receive data register */
buff=0;
buff = USART_ReceiveData(USART1);
#define TAG
#ifdef TAG
at_cmd_recv(buff);
#endif
//printf("hello dwm1000 %c!\r\n",buff);
}
}
在这个中断函数中,我们看到了at_cmd_recv这个函数,一看就跟AT指令功能相关了,我们顺藤摸瓜,在at_cmd_recv函数中,我们看到了AT指令的解析函数at_cmd_parse,接着在at_cmd_parse函数中,我们看到了at_table这样一个结构体数组:
at_cmd_t at_table[] = {
{"AT+switchdis", AT_CmdFunc_ondis},
{"AT+interval", AT_CmdFunc_interval},
{"AT+version", AT_CmdFunc_version},
{"AT+RST", AT_CmdFunc_RST},
{"AT+tem_hum", AT_CmdFunc_TemHum},
{"AT+xyz", AT_CmdFunc_XYZ},
{"AT+anchor_tag", AT_CmdFunc_ANCHOR_TAG},
{"AT+GPIOTEST", AT_CmdFunc_ANCHOR_GPIOTEST},
{"AT+FACTORY", AT_CmdFunc_FACTORY},
{"AT", AT_CmdFunc_AT},
};
此时对应的AT指令和相对应所调用的函数就一一对应上了;将这些AT调用的函数全都熟悉一遍,到此整个程序的框架结构才算是相对了解一点了;
在刚拿到这个代码的时候,真心不知道他是怎么设计的,代码书写错乱;完全没有模块化设计的概念,对于I2C同一个驱动的所有外设,都混在一个文件里面去实现,阅读起来真心的困难,要不是报着烧录不对的程序到开发板中去试一试的心态,曾经一度认为这个程序和所拿到的开发板中的不是同一个程序,还麻烦EEWORLD工作人员去询问安信可的工作人员,真的是怪我自己了草率了!但说回代码本身,对于模块的应用开发者,还是建议官方将代码规范化、模块化,易于理解其设计理念、更便利使用者在产品应用迭代开发,不然仅仅看这一份代码,想移植到其它平台,就够看一段时间的了。
代码还是要规范化比较好,不仅有利于自己管理,同时也利于开发者使用。