SYD8801 UART使用说明【uart轮发数据代码实现】【 uart中断发数据代码实现】【RFSleep函数对串口的影响】【中断中调用串口打印函数有可能会造成死机】
SYD8801是一款低功耗高性能蓝牙低功耗SOC,集成了高性能2.4GHz射频收发机、32位ARM Cortex-M0处理器、128kB Flash存储器、以及丰富的数字接口。SYD8801片上集成了Balun无需阻抗匹配网络、高效率DCDC降压转换器,适合用于可穿戴、物联网设备等。具体可咨询:http://www.syd-tek.com/
SYD8801 UART使用说明
下面是官方的介绍:
uart轮发数据代码实现
主函数如下:
int main()
{
uint16_t x=0;
dbg_init();
while(1)
{
dbg_printf("syd8801 debug test x : %04x \n",x);
x++;
delay_ms(500);
}
}
其中dbg_init函数调用了uart_0_init(UART_RTS_CTS_DISABLE, UART_BAUD_115200, NULL);进行串口的初始化,其源代码如下:
/*
UART0配置函数
参数: uint8_t flowctrl 硬件流时候使能 0:失能硬件流 1:使能硬件流
uint8_t baud 波特率参数 具体值查看UART_BAUD_SEL枚举定义
void * p_callback 串口中断回调函数
举例:uart_0_init(UART_RTS_CTS_DISABLE, UART_BAUD_115200, &uartRx_callback);
*/
void uart_0_init(uint8_t flowctrl, uint8_t baud, void * p_callback)
{
// pin26 & pin27 configured to uart mode.
PIN_CONFIG->PIN_26_SEL = PIN_SEL_UART_0_RXD;
PIN_CONFIG->PIN_27_SEL = PIN_SEL_UART_0_TXD;
UART_0_CTRL->BAUDSEL = baud;
UART_0_CTRL->FLOW = flowctrl;
UART_0_CTRL->INT_MASK = 1; //因为这里是debug 所以不需要输入
uartrx_callback[0] = ((void (*)(void))p_callback);
NVIC_EnableIRQ(UART0_IRQn);
}
这里没有使用硬件流,传入的第二个传输为串口波特路,串口波特率列表如下:
enum UART_BAUD_SEL {
UART_BAUD_1200 = 0x00,
UART_BAUD_2400 = 0x01,
UART_BAUD_4800 = 0x02,
UART_BAUD_9600 = 0x03,
UART_BAUD_14400 = 0x04,
UART_BAUD_19200 = 0x05,
UART_BAUD_38400 = 0x06,
UART_BAUD_57600 = 0x07,
UART_BAUD_115200 = 0x08,
UART_BAUD_230400 = 0x09,
UART_BAUD_460800 = 0x0A,
UART_BAUD_921600 = 0x0B,
};
传入的第三个参数是串口的中断回调函数,当使用了串口中断的时候MCU将调用这个函数!
串口初始化到此完毕,接下来将调用dbg_printf("syd8801 debug test x : %04x \n",x);函数把"syd8801 debug test x : %04x \n"发送出去,dbg_printf函数最终调用uart_0_write(s_formatBuffer[i]);把数据落实到串口,调用硬件,uart_0_write函数源代码如下:
/*
UART0串口写函数
参数: uint8_t data 要发送的数据
*/
void uart_0_write(uint8_t data)
{
UART_0_CTRL->SUB= data;
while(UART_0_CTRL->T1 == 0);
UART_0_CTRL->T1 = 0;
}
当串口把一个数据发送出去后会把UART_0_CTRL->T1标志位置1,所以发送函数把数据填充到移位寄存器后就等待UART_0_CTRL->T1标志位被置1,当数据发送完成后发送函数退出,等待下一次数据传输!
uart中断发数据代码实现
当要从串口上接收数据的时候,就要实现串口的中断功能了,因为SYD8801的串口发送中断和串口接收中断合在一起了,所以在中断函数中必须要处理发送和接收两种情况,再因为SYD8801在高速的全双工收发数据的时候有可能存在漏包的情况,所以这里实现一个简单的串口缓存的机制!
主函数如下:
int main()
{
uint8_t i=0;
uart_0_init(UART_RTS_CTS_DISABLE, UART_BAUD_115200, &uart0Rx_callback);
uart0_buf.header = 0;
uart0_buf.tail = 0;
__enable_irq();
while(1)
{
//串口中断接收和发送测试 - 全双工
while(uart0_buf.header != uart0_buf.tail)
{
uart_0_write(uart0_buf.data[uart0_buf.tail]);
uart0_buf.tail++;
if(uart0_buf.tail >= MAX_LENGTH)
{
uart0_buf.tail = 0;
}
}
}
}
其中串口初始函数 uart_0_init源码如下:
/*
UART0配置函数
参数: uint8_t flowctrl 硬件流时候使能 0:失能硬件流 1:使能硬件流
uint8_t baud 波特率参数 具体值查看UART_BAUD_SEL枚举定义
void * p_callback 串口中断回调函数
举例:uart_0_init(UART_RTS_CTS_DISABLE, UART_BAUD_115200, &uartRx_callback);
*/
void uart_0_init(uint8_t flowctrl, uint8_t baud, void * p_callback)
{
// pin26 & pin27 configured to uart mode.
PIN_CONFIG->PIN_26_SEL = PIN_SEL_UART_0_RXD;
PIN_CONFIG->PIN_27_SEL = PIN_SEL_UART_0_TXD;
UART_0_CTRL->BAUDSEL = baud;
UART_0_CTRL->FLOW = flowctrl;
UART_0_CTRL->INT_MASK = 0;
UART_0_CTRL->R1= 0;
UART_0_CTRL->T1 = 0;
uartrx_callback[0] = ((void (*)(void))p_callback);
NVIC_EnableIRQ(UART0_IRQn);
}
这里和上面的轮发数据的配置不一样的是这里关闭了屏蔽串口中断的功能 UART_0_CTRL->INT_MASK = 0;,也就是说这里将打开串口中断!
这里串口数据发送函数如下:
/*
UART0串口写函数
参数: uint8_t data 要发送的数据
*/
void uart_0_write(uint8_t data)
{
UART_0_CTRL->SUB= data;
tx_flag0 = 1;
while(tx_flag0 == 1);
// uint32_t i=0x10000;
// UART_1_CTRL->SUB= data;
// while((!tx1_succed_flag) && i){
// i--;
// }
// tx1_succed_flag = 0;
}
这里将不再去判断 UART_0_CTRL->T1发送完成标志位,转而判断自定义的标志位tx_flag0,该标志位在串口中断函数中的发送完成标志下被清零,串口中断如下:
/*
UART0串口中断函数
调用回调函数
*/
void UART0_IRQHandler()
{
// UART_0_CTRL->INT_MASK = 1; //
if ((UART_0_CTRL->R1) == 1)
{
UART_0_CTRL->R1 = 0;
if(uartrx_callback[0] != 0)
(*uartrx_callback[0])();
}
if ((UART_0_CTRL->T1) == 1)
{
UART_0_CTRL->T1 = 0;
tx_flag0 = 0;
}
// UART_0_CTRL->INT_MASK = 0; //
}
如果中断是因为数据发送完成将会进入 if ((UART_0_CTRL->T1) == 1)分支,这里将 tx_flag0标志位清零!整体实现了串口发送函数的流程!
对于接收,当有数据被接收的时候,进入串口中断函数将调用(*uartrx_callback[0])();函数,该函数在uart_0_init初始化中被确认为初始化时候传入的第三个变量,在这里是minc.c中的uart0Rx_callback函数,这个函数把数据接收出来,然后通过发送函数把数据发送出去!
uart中断发数据时候只是发送了第一个数据
在调试SYD8801的串口中断的时候有可能会发现串口只是发送了第一个数据,后面的数据没能够得到发送,并且程序卡死在了while(tx_flag0 == 1);循环中。这是因为KELL编译器设置了太高的优化选项,把while(tx_flag0 == 1);优化成了while(1);造成陷入了主循环。
关于KELL编译器的优化选项和说明,请看:http://blog.****.net/chengdong1314/article/details/53463183
这里的设置方法如下:
这样就解决了只发送一个数据的问题!
这里上传本文使用到的资源:http://download.****.net/detail/chengdong1314/9869059
其中的《09UART_debug》是轮发数据的工程, 《06UART—buff》是中断发数据的工程
RFSleep函数对串口的影响
在之前的博客中:蓝牙广播的实现:http://blog.****.net/chengdong1314/article/details/60871037 我们提到在广播结束的时候会调用RFSleep函数使射频模块进入休眠状态,因为串口的时钟来源于外部的16MHz晶振,在关闭射频的时候串口的时钟也会被关掉,所以串口将会一直等待下一次时钟恢复才能够正常运行!
这里有一个简单可行的办法:修改dbg_printf函数,只要保证在调用_uart_0_write函数之前RF是打开的即可,修改如下:
void dbg_printf(char *format,...)
{
uint8_t iWriteNum = 0;
uint8_t rf_state=0;
va_list ap;
if(!format)
return;
if(*((uint8_t*)(0x50001000 + 0x20)) & 0x01) rf_state=1;
if(rf_state) RFWakeup();
*((uint8_t*)(0x50001000 + 0x24)) = 0x00;
va_start(ap,format);
iWriteNum = vsprintf((char *)s_formatBuffer,format,ap);
va_end(ap);
if(iWriteNum > MAX_FORMAT_BUFFER_SIZE)
iWriteNum = MAX_FORMAT_BUFFER_SIZE;
_uart_0_write(s_formatBuffer, iWriteNum);
*((uint8_t*)(0x50001000 + 0x24)) = 0x01;
if(rf_state) RFSleep();
}
下面这种办法比较复杂,这里不建议使用:
在广播状态的整体流程如下:
1.当1S定时器(可以自行设置)来的时候唤醒射频,开启广播:
2.然后退出定时中断函数,进入主循环运行代码
3.广播数据包发送完成的时候调用RFSleep函数停止射频模块:
也就是说串口真正得到时钟的只是在2和3之间,也就是在开始广播到广播结束这段时间!下面是串口的发送函数:
也就是在广播结束之后如果串口没能够全部发送出去,那么TI标志位就得不到置位,也就是说串口将会卡在while循环中,直到下一次MCU被唤醒。
对于只是把串口当做一个调试输出,发布产品时不需要串口的情况而言,避免卡在串口里最简单的办法就是屏蔽掉广播结束时调用的RFSleep函数,当要发布产品或者要调试产品功耗的时候记得去掉串口打印功能并且放开RFSleep函数的注释即可。
但是对于一定需要到串口调试功能的方案,比如透传方案而言,就需要一直打开串口了,这里提供一个简单的办法:开设一个全局变量,在dbg_printf函数的开始的时候设置这个全局变量并且唤醒射频模块,在该函数结束的时候清除掉这个全局变量并且休眠射频模块,当然在广播结束的时候也要根据这个变量的情况来决定是否要进入睡眠!
这里提供一个示例:
首先在debug.c中增加三个函数:
“RFSleep_Uart”和“RFWakeup_Uart”函数实现一个微小的串口发送队列,首先这里约定串口打印模块的公共对外接口是“dbg_printf”和“dbg_hexdump”函数,也就是说只是对这两个函数做了保护管理,加上串口队列之后这两个函数的源代码如下:
这里在这两个函数开始打印之前调用“RFWakeup_Uart”函数,给接下来的打印工作做准备,确保打开了16M晶振,也就是UART的时钟,同时在打印完数据之后调用“RFSleep_Uart”函数释放系统资源,关闭16M晶振,进入低功耗模式!
另外在广播结束的时候如果没有在打印也是需要进入低功耗模式的,但是这种情况下并不属于队列所能够管理的范围,所以“RFSleep_Uart”函数会传入一个变量,当需要根据串口的状态判断是否需要休眠并且不需要操作串口队列(不属于UART模块的调用)的时候调用“RFSleep_Uart”函数并传入非零变量!这里存在的特殊情况是在广播结束的时候要进行UART模块之外的调用,广播结束事件处理如下:
同时,在debug.c文件里还添加了“Connect_Uart”函数,这个函数要在蓝牙连接上之后被调用,因为连接后射频模块的时钟会交给硬件控制,当连接事件之后就会自动睡眠,但是串口发送和接收的时候必须关掉自动睡眠,如连接上后事件处理如下:
最后注意:
因为“RFSleep_Uart”和“RFWakeup_Uart”函数会操作射频模块,而要操作射频模块的前提是必须调用过“ble_init”函数进行底层蓝牙的初始化,所以在主函数的初始化中必须是先调用“ble_init”再进行串口的操作,如下进行初始化:
经过上面的处理串口发送将不会存在卡顿现象和漏数据现象!
这里上传本节博客涉及到的源代码:http://download.****.net/download/chengdong1314/9895942
中断中调用串口打印函数有可能会造成死机
在SYD8801中,没有使用串口中断的发送函数源代码如下:
整体思路就是先把数据填充到移位寄存器中,然后等待串口发送完成,硬件设置寄存器标志位UART_1_CTRL->T1为1,这里发送函数就等待这个标志位被置位,然后退出发送函数!
如果串口只是在主函数中被调用这样的处理方式是没问题的,但是如果在中断函数中(包括蓝牙协议栈回调函数)有串口的调用就会出现重复调用串口发送函数的情况。可以想象,在主函数调用串口发送函数,在等待硬件置位标志位UART_1_CTRL->T1的时候,某个中断被触发,巧合这个中断函数中又有串口打印的语句,调用了同一个串口函数,进行相同的串口发送流程。从上面的串口发送函数可以看出,当退出串口发送函数的时候后标志位UART_1_CTRL->T1一定会被软件清零,也就是说当退出中断函数的时候标志位UART_1_CTRL->T1已经被中断函数软件清零了,但是主函数依旧在等待硬件置位标志位UART_1_CTRL->T1,这时候硬件再也不会置位标志位UART_1_CTRL->T1,从而进入死循环!
在不改变程序的情况下修改串口发送函数,让他不死循环判断标志位UART_1_CTRL->T1,修改后代码如下:
/*
UART1串口写函数
参数: uint8_t data 要发送的数据
*/
void uart_1_write(uint8_t data)
{
// UART_1_CTRL->SUB= data;
//
// while(UART_1_CTRL->T1==0);
// UART_1_CTRL->T1 = 0;
uint32_t i=0x10000;
UART_1_CTRL->SUB= data;
while((!UART_1_CTRL->T1) && i){
i--;
}
UART_1_CTRL->T1 = 0;
}
当然还有其他办法,比如把这些要打印的语句想办法挪动到主函数去打印,具体内容请看:http://blog.****.net/chengdong1314/article/details/60871037中的状态机说明