没有串口,你会如何输出log?
cathy 在 提交
cathy 在 提交
cathy 在 提交
一、STM32用USART发送字符串
void UART_Send_Message(u8 *Data) { while(*Data!='\0') { USART_SendData(USART1, *Data); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//读取串口状态 Data++; } } void main(void) { u8 str_buf[500]; memset((char *) &str_buf, 0, sizeof(str_buf)); UART_Send_Message(str_buf); }
while(SET == USART_GetFlagStatus(USART1,USART_FLAG_RXNE));
含义是:当接收引脚有数据时,状态寄存器的USART_FLAG_RXNE就会为1,此时USART_GetFlagStatus(USART1,USART_FLAG_RXNE)的返回值就为1(SET),若无数据则为RESET。
二、USART_FLAG_TXE和USART_FLAG_TC怎么用
这里主要说的是在特殊情况下发送字符软件代码的写法。
特殊情况指的是:
1)调用发送字符串函数“发送完”本机立即掉电;
2)调用发送字符串函数“发送完”从机立即掉电;
【上面两种主要用于芯片对电源控制的项目中】
3)调用发送字符串函数“发送完”立刻进入待机或停机;
其实本文主要说的是两个标志位:USART_FLAG_TXE 和 USART_FLAG_TC.
USART_FLAG_TXE发送缓冲区空标志:说明可以往数据寄存器写入数据了,但并不代码数据发送完成了。
USART_FLAG_TC发送完成标志:这个才是代表USART在缓冲区的数据发送完成了,即从机接收到了数据。
这两个标志的区别在于:它们分别表示数据在发送过程中,在两个不同的阶段中的完成情况。TXE 表示数据被从发送缓冲区中取走,转移到的移位寄存器中,此时发送缓冲是空的,可以向其中补充新的数据了。而 TC 则表示最后放入发送缓冲区的数据已经完成了从移位寄存器向发送信号线 Tx 上的转移。所以,判定数据最终发送完成的标志是 TC,而不是 TXE。
下面讲述在不同代码写法下,得到不同实验效果【调试助手接收数据】:
常见写法一
这种写法在不是特殊(不掉电、不待机等)情况下,问题不大,USART数据会成功发送出去。但是在上面说的特殊情况下,问题就来了,代码只将数据放到了发送缓冲区,而没有发送出去就掉电或待机了,这个时候其实最后两个字符是没有发送出去的。
2、常见写法二
这种写法达到的效果和上面存在不同的就是倒数第二个数据发送出去了,也就是只有最后一个字符是没有发送出去的。
3、常见写法三
这种写法达到的效果和上面两种写法有不一样,发送了10个字符。
4、写法四
这种写法按理说可以实现功能,但实际多次试验结果确实第一字节数据丢失了。
5、写法五(正确写法)
这种写法是比较完成,为了保守起见,在特殊情况下使用该写法。。
至此,常见软件写法都给出了源代码和试验效果,请根据自己实际情况写代码验证。
来源:网络
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
1、前言
玩过Linux的朋友, 是不是对Linux无所不能的串口Shell命令控制台羡慕不已, 要是自己做的STM32F系列低档次的MCU也有这种控制交互能力, 会给调试/维护和配置省下多少麻烦事呀, 比如启动/关闭调试或自检模式, 打印调试信息, 配置系统参数, 传输文件等等, 也有相当多的朋友凭借自己出色的编程能力可以实现这些功能, 这里提出我的这个解决方案, 以作交流.
本平台(xc_shell)具备以下性能特点:
1、大量主要代码, 和具体硬件无关, 移植性强,代码文件少.
2、只有在处理用户的输入命令时, 才占用CPU资源, 且代码可裁剪到1KB SRAM和4KB Flash;
3、用户可以非常灵活的添加按模板编写的命令脚本文件, 自定义扩张能力强.
4、支持操作系统和非操作系统两种场景应用.
5、支持Ymodem文件传输协议
6、支持将Flash的扇区开辟为参数区, 可实现本地/远程升级。
7、实用Led灯信号管理, 可将65535虚拟信号灯选择输出到1个实体LED灯上, 调试时序和状态非常有用
8、拥有基础的LED管理, 调试模式设置, 命令帮助指令, 复位指令等基础功能
功能越多设计会越复杂, 为了解释清楚代码, 先向大家解释一下以上功能的基础实现原理, 并提供一个最小的的源码工程。
2、xc_shell平台介绍
2.1 如何实现硬件无关
类比Linux会发现, 设备的硬件接口往往会被虚拟成一个文件(驱动), 而Linux内核完全与硬件系统无任何字节关联, 不同平台驱动不同而已, 故而本xc_shell的串口驱动也采用了相似的思路:
1) 串口驱动用一个结构体描述, 这样只需在xc_shell.c中用指针指向这个TTYx_HANDLE结构体对象就可以将串口(tty)硬件与内核联系在一起, 聪明的朋友可能会想到, 假如我将带网络的开发板按此结构体,虚拟一个TTY对象, 岂不是就可以实现一个网络远程控制台了! 这点确实是可以的!
2) 当然诸如多TTY串口实现接口互换等, 都是一个指针和step2中的注入回调处理交换的问题。
3)用户在使用api_TxdFrame或api_TxdByte时”bsp_ttyX.c“,会驱动具体MCU的串口将数据发送出去, 收到一帧数据后,若用户设置了inj_RcvFrame回调处理方法,则会在中断中执行用户的回调处理。
/*---------------------* * 指正函数定义 *----------------------*/ typedef void (*pvFunDummy)(void); //输入整行,输出逻辑 typedef void (*pvFunVoid) (void); typedef void (*pvFunBool) (bool bVal); typedef void (*pvFunChar) (uint8_t cVal); typedef void (*pvFunShort)(uint16_t sVal); typedef void (*pvFunWord) (uint32_t wVal); //输入整行,输出逻辑 typedef bool (*pbFunVoid) (void); typedef bool (*pbFunBool) (bool bVal); typedef bool (*pbFunChar) (uint8_t cVal); typedef bool (*pbFunShort)(uint16_t sVal); typedef bool (*pbFunWord) (uint32_t wVal); //输入整形指针,输出逻辑 typedef bool (*pbFun_pVoid) (void * pVoid); typedef bool (*pbFun_pChar) (uint8_t * pStr); typedef bool (*pbFun_pShort)(uint16_t * pShor); typedef bool (*pbFun_pWord) (uint32_t * pWord); //输入数据帧,输出逻辑 typedef bool (*pbFun_Buffx)(void * pcBuff, uint16_t len ); typedef bool (*pbFun_Bytex)(uint8_t * pcByte, uint16_t len ); /*---------------------* * TTYx 句柄结构 *----------------------*/ typedef struct TTYx_HANDLE_STRUCT { const char * const name; //驱动器名 const uint16_t rxSize; //接收大小 const uint16_t txSize; //发送大小 //------------------------------------------------------ //step1: 用户可用API const pvFunWord init; //初始化. const pbFun_Bytex api_TxdFrame; //发送数据帧. (发送帧) const pbFunChar api_TxdByte; //发送数据字节 //------------------------------------------------------ //step2: 注入回调函数 pbFun_Bytex inj_RcvFrame; //(ISR)接收数据帧. (接收帧) pvFunDummy inj_TxdReady; //(ISR)发送完毕回调 //------------------------------------------------------ //step3: 接收回调函数 struct TTYx_HANDLE_STRUCT * pvNext; //连接到下一个指令 }TTYx_HANDLE;
可注入的命令脚本(CLI)实现
命令CLI也是一个结构体对象:
/*---------------------* * CLI指令 *----------------------*/ typedef struct { const char * const pcCmdStr; //指令字符串(只能为小写字母) const char * const pcHelpStr; //指令描述,必须以:"\r\n结束". 比如:"help: Returns a list\r\n". const pFunHook pxCmdHook; //指向回调函数的指针,处理成功返回真否者返回0; uint8_t ucExpParam; //指令期望的参数个数 const MEDIA_HANDLE *phStorage; //指向存储介质,没有的话填充NULL }Cmd_Typedef_t;
各位朋友可能会使用到非常多的自定义CLI命令, 格式诸如这个网卡的命令:
const Cmd_Typedef_t CLI_WizMsg= { //识别关键字 .pcCmdStr = "wiz", //帮助内容 .pcHelpStr = "[WIZ contorls]\r\n" " wiz help\r\n" " wiz rd info\r\n" " wiz reset\r\n" " wiz wr ip <D0>.<D1>.<D2>.<D3>\r\n" " wiz wr mask <D0>.<D1>.<D2>.<D3>\r\n" " wiz wr way <D0>.<D1>.<D2>.<D3>\r\n" " wiz wr mac <H0>-<H1>-<H2>-<H3>-<H4>-<H5>\r\n" " wiz wr port <udp> <bak> <vol> <pic>\r\n" " wiz wr sip <D0>.<D1>.<D2>.<D3> <port>\r\n" " wiz wr cip <D0>.<D1>.<D2>.<D3> <port>\r\n" " wiz load default\r\n" "[WIZ Test mode]\r\n" " wiz loop open\r\n" " wiz loop close\r\n" "\r\n", //处理函数 .pxCmdHook = &Shell_WIZ_Service, //见实体函数 //附带数据 .ucExpParam = 0, #ifdef SHELL_USE_YMODEM //存储介质 .phStorage = NULL, #endif }; /*---------------------* * CLI 链表节 *----------------------*/ Cmd_List_t WizList = { &CLI_WizMsg, NULL,}; //Shell指令的头
如配置IP地址输入“wiz wr ip 192.168.1.250\r\n”则可以了
3、环境准备
3.1 硬件开发环境
3.2 软件开发环境
3.3 软件配置
在xc_shell使用“/r/n”作为命令的结束符, 而SecureCRT按下Enter不是输入“/r/n”故而需要按下图设置:此外在《终端》/仿真/高级中选取【本地回显】
4、代码介绍
4.1 目录结构
□ XC_SHELL ├──┬── BSP_LIB BSP库,硬件相关驱动代买 │ ├──── bsp_ledx.c 基础LED驱动 │ └──── bsp_tty0.c 调试串口驱动 │ ├──┬──MDK-ARM 工程文件 │ └──── Project.uvproj │ ├──┬──SHELL_CFG SHELL配置头文件 │ └──── user_eval.h │ ├──┬──SHELL_CORE SHELL内核文件 │ ├──── xc_shell.c SHELL内核文件 │ ├──── xc_ymodem.c Ymodem传输协议(默认关闭,在xc_shell.h中启动) │ ├──── xc_iap.c Flash的IAP操作,需要bsp_flash.c驱动支持 │ └──── shell_iap.c shell的用户脚本模板 │ ├──┬──SHELL_INC SHELL头文件 │ ├──── bsp_type.h 驱动结构定义 │ ├──── xc_shell.h SHELL头文件 │ └──── xconfig.h 硬件无关配置文件 │ ├──┬──STM32F10x_StdPeriph_Lib_V3.5.0 STM32的标准外设库 │ └──── ...... │ └──┬──USER 用户文件 ├─ ..... └──── main.c main文件
4.2 工程设置要点
1) 设置使用微库:
2)配置包含和路径,注意使用了“--c99”标准,如图
3) 编译工程,无错误警告后烧写程序到开发板运行。
4.3 最终效果
按图输入一下指令,SHELL平台会回复相关信息。其中输入“led set 0=1”会将信号1分配到物理LED0上;输入“led set 0=2”会将信号2分配到物理LED0上。这样用户编写程序代码时相当于拥有了超级多的LED信号可用,调试时序非常有用。
5、添加自己的指令脚本
5.1 源代码示例
假设我要编写一个自己的指令脚本, 来读取MCU的关键信息,关键字为mcu, 文件命名为shell_mcu.c;当输入“mcu rd 0”时显示MCU的FLASH大小,输入“mcu rd 1”时读取MCU的唯一ID信息。shell_mcu.c源代码:
/*********************************Copyright (c)********************************* ** ** FIVE工作组 ** **---------------------------------File Info------------------------------------ ** File Name: shell_mcu.c ** Last modified Date: 2017/9/17 15:13:57 ** Last Version: V1.0 ** Description: shell测试 ** **------------------------------------------------------------------------------ ** Created By: wanxuncpx ** Created date: 2017/9/17 15:14:08 ** Version: V1.0 ** Descriptions: none **------------------------------------------------------------------------------ ** HW_CMU: STM32F103 ** Libraries: STM32F10x_StdPeriph_Lib_V3.5.0 ** version V3.5 *******************************************************************************/ /****************************************************************************** 更新说明: ******************************************************************************/ /****************************************************************************** ********************************* 编 译 控 制 ******************************** ******************************************************************************/ #define MCU_SHELL //注释掉时屏蔽iap shell功能 #include "xc_shell.h" //Shell支持文件,含bool,uint8_t..以及串口数据收发操作 /****************************************************************************** ********************************* 文件引用部分 ******************************** ******************************************************************************/ /*---------------------* * 模块驱动引用 *----------------------*/ //#include "net_w5500.h" #ifdef MCU_SHELL /****************************************************************************** ********************************** Shell实例 ********************************** ******************************************************************************/ /*---------------------* * CLI指令服务 *----------------------*/ extern bool Shell_MCU_Service(void * pcBuff, uint16_t len ); /*---------------------* * CLI 结构 *----------------------*/ const Cmd_Typedef_t CLI_McuMsg= { //识别关键字 "mcu", //帮助内容 "[mcu contorls]\r\n" " mcu rd <d>\t\t- Read FLASH information.\r\n" "\r\n", //处理函数 &Shell_MCU_Service, //附带数据 0, #ifdef SHELL_USE_YMODEM //存储介质 NULL, #endif }; /*---------------------* * CLI链表节(输出) *----------------------*/ Cmd_List_t McuList = {&CLI_McuMsg ,NULL}; //IAP指令链表 /****************************************************************************** ********************************* 函 数 声 明 ********************************* ******************************************************************************/ /****************************************************************************** / 函数功能:STM32F103控制函数 / 修改日期:2015/7/14 20:22:02 / 输入参数:none / 输出参数:none / 使用说明:需要执行约10s ******************************************************************************/ static bool FLASH_ioctl(uint8_t cmd,void * param) { #define UID_ADDR 0x1FFFF7E0 //闪存容量寄存器,值对应KB单位 #define MAC_ADDR 0x1FFFF7E8 //MCU的唯一ID号,共12个字节 #define UID_SIZE 2 //UID的字节数 #define MAC_SIZE 12 //MAC的字节数 //step1: 检查参数 if(!param)return false; //step2: 处理数据 switch(cmd){ case 0 : { //获取FLASH的的UID uint16_t * ptDst = (uint16_t *)((uint32_t)param+1); *ptDst = *(uint16_t *)UID_ADDR; *(uint8_t *)param = UID_SIZE; return true; } case 1 : { //获取芯片的MAC地址 uint32_t * ptDst = (uint32_t *)((uint32_t)param+1); uint32_t * ptSrc = (uint32_t *)MAC_ADDR; *ptDst++ = *ptSrc++; *ptDst++ = *ptSrc++; *ptDst++ = *ptSrc++; *(uint8_t *)param = MAC_SIZE; return true; } default:return false; } } /****************************************************************************** / 函数功能:文件系统Shel指令处理 / 修改日期:2013/9/10 19:04:15 / 输入参数:输入当前的程序版本 / 输出参数:none / 使用说明:none ******************************************************************************/ bool Shell_MCU_Service(void * pcBuff, uint16_t len ) { uint8_t *ptRxd; //用于接收指令处理 int i; uint16_t retval; uint8_t buff[32]; //处理指令 //-------------------------------------------------------------------------- ptRxd = (uint8_t *)pcBuff; if(StrComp(ptRxd,"rd ")) //读取FLASH信息 { int wval; if(1 != sscanf((void *)ptRxd,"%*s%d",&wval) )return false; if( wval>2 )return false; if(0==wval) { FLASH_ioctl(0,buff); retval = *(uint16_t *)(buff+1) ; printf("->Flash:\t%dKB\r\n",retval); return true; } else if(1==wval) { FLASH_ioctl(1,buff); printf("->MAC:\t "); for(i=0; i<buff[0]-1; i++){printf("%02X-",buff[i+1]);} printf("%02X\r\n",buff[i+1]); return true; } else return false; } else if(StrComp(ptRxd,"help\r\n")) //指令帮助 { shell_SendStr((void *)CLI_McuMsg.pcHelpStr); return true; } else return false; } /****************************************************************************** *********************************** END ************************************ ******************************************************************************/ #endif
5.2 实现步骤
1) 将该文件添加到工程下。
2) 在main.c中用extern 引用McuList,源代码为:
/*---------------------* * Shell指令链表 *----------------------*/ extern Cmd_List_t McuList;
3)在main.c初始化时添加:
//---------------------------------------------------------- //step1: shell初始化 shell_Init(115200,ledx_cfg); //初始化shell接口 CLI_AddCmd(&McuList); //添加模块指令到链表
4)编译工程文件。
5)下载到开发板运行即可在终端下看到新支持的CLI指令:
版权声明:本文为CSDN博主「wanxuncpx」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wanxuncpx/article/details/78009490
cathy 在 提交
NBiot模块一般都是串口接口,使用AT指令集,对接中国移动onenet平台。先用串口助手去测试,流程测试OK之后需要在MCU上重新写一遍。
STM32串口 IDLE中断
IDLE其实是空闲的意思。IDLE中断叫空闲中断,不叫帧中断。那么什么叫空闲,怎么定义空闲呢?在实际发送数据的时候,比如一串字符串,我们会采用如下方式发送:
void uart1_putc(char dat) { SBUF = dat; while (!TI); TI = 0; } void uart1_puts_n(char *str){ while (*str) uart1_putc(*str++); } void uart1_puts_n("i am handsome");
其实发送的两个字符之间间隔非常短,所以在两个字符之间不叫空闲。空闲的定义是总线上在一个字节的时间内没有再接收到数据,空闲中断是检测到有数据被接收后,总线上在一个字节的时间内没有再接收到数据的时候发生的。
而总线在什么情况时,会有一个字节时间内没有接收到数据呢?一般就只有一个数据帧发送完成的情况,所以串口的空闲中断也叫帧中断。
要怎么开启帧中断呢?
其实其他串口配置不用改变,只需要在开启串口接收中断的时候加上一句话就Ok。
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接收中断 USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);//开启串口空闲中断
然后中断函数如下
void USART2_IRQHandler(void) { //串口1中断服务程序 int clear; if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) { //字符接收中断(接收到的数据必须是0x0d 0x0a结尾) USART2_RX_BUF[length++] = USART2->DR & 0x0FF; } else if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) {//空闲帧中断 if(USART2_RX_BUF[length - 1] == 0xff) { clear = USART2->DR; clear = USART2->SR; length = clear; length = 0; USART2_RX_STA = 1; } else { ; } } }
在普通中断的时候仅仅保存数据,在帧中断的时候需要执行相应处理函数。
如果没有帧中断,必须在接收中断中判断每一个接收数据与帧头帧尾是否相符,效率极低。
之前一直以为天下串口都一样,还是有很多细节的,可以提升不少效率!
来源:博客园(博主:一郎哥哥)
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
cathy 在 提交
cathy 在 提交
波特率的计算
STM32下的波特率和串口外设时钟息息相关,USART 1的时钟来源于APB2,USART 2-5的时钟来源于APB1。在STM32中,有个波特率寄存器USART_BRR,如下:
STM32串口波特率通过USART_BRR进行设置,STM32的波特率寄存器支持分数设置,以提高精确度。USART_BRR的前4位用于表示小数,后12位用于表示整数。但是它还不是我们想要设置的波特率,想要设置我们串口的波特率大小还需要进行计算。其实有关波特率的计算是下面这一条表达式:
从上面的表达式,我们引入了一个新量USARTDIV,它表示对串口的时钟源fck进行分频。假设我们已知道了波特率和fck时钟频率的大小,那么通过上式便可以计算出USARTDIV的具体大小,然后再通过USART的值大小对波特率寄存器进行设置。
USARTDIV通过上面的表达式得出,是一个带有小数的浮点数(如27.75)。将小数部分和整数部分分开,分别得到一个整数值n(如27)和一个小数值m(如0.75)。有了这两个值我们便可以填写USART_BRR寄存器进而设置我们串口波特率大小了。
将整数部分m(27 = 0x1B)直接写入USART_BRR的后12位部分;将小数部分n乘以16后得到的整数值(如0.75 x 16 = 12 = 0xC)写入USART_BRR前4位部分,最后USART_BRR的值为0x1BC。
注意:如果小数部分乘以16之后仍带有小数,则要四舍五入去除小数部分得到一个新的整数,再将其写入USART_BRR的前四位。
为什么在计算波特率的公式中要乘以16
我们知道串口通信是通过TXD和RXD这两条线进行通信的,当接收器的RXD连接着发送器的TXD,接收器的TXD连接着发送器的RXD,接收器和发送器可以通过RXD和TXD互传数据。当接收器检测到RXD这条线的电平被拉为低电平,立即开始接收发送器发送过来的数据,刚刚那个低电平只是一个告知接收器可以接收数据的起始位而已。
在数据的传输中,信号可能受到一些干扰而产生一些抖动,如下图。如果接收端只对这些信号数据采样一次,那么它有可能采样到的是抖动的不准的数据,进而使数据传输不准确,所以接收端在采样数据线上的数据,通常都要采样多次,然后通过比较获得准确的数据。
前面已经说过,USARTDIV,它表示对串口的时钟源fck进行分频,而这16表示的正是1bit数据的采样次数。为什么呢?
将这个表达式的分子分母倒过来,可以得到下面这条表达式
每一位的传输时间只有1/TX_baud,这个总时间除以16,所以每采样一次的时间正好是T1,即新分频后的周期。而初始的串口时钟信号来自于APBx,APBx时钟信号需要经过分频才会等于T1,所以才需要分频USARTDIV。
本文转载自:网络
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
cathy 在 提交