通讯的基本概念
1 串行通讯与并行通讯
串行通讯是指设备之间通过少量数据信号线(一般是 8 根以下),地线以及控制信号线,按数据位形式一位一位地传输数据的通讯方式。而并行通讯一般是指使用 8、 16、 32 及 64 根或更多的数据线进行传输的通讯方式
在数据传输速率相同的情况下,并行通讯传输的数据量要大得多,而串行通讯则可以节省数据线的硬件成本(特别是远距离时)以及 PCB 的布线面积
2 全双工、半双工及单工通讯
3 同步通讯与异步通讯
在同步通讯中,收发设备双方会使用一根信号线表示时钟信号,在时钟信号的驱动下双方进行协调,同步数据,见图 19-3。通讯中通常双方会统一规定在时钟信号的上升沿或下降沿对数据线进行采样。
在异步通讯中不使用时钟信号进行数据同步,它们直接在数据信号中穿插一些同步用的信号位,或者把主体数据进行打包,以数据帧的格式传输数据,见图 19-4,某些通讯中还需要双方约定数据的传输速率,以便更好地同步
在同步通讯中,数据信号所传输的内容绝大部分就是有效数据,而异步通讯中会包含有帧的各种标识符,所以同步通讯的效率更高,但是同步通讯双方的时钟允许误差较小,而异步通讯双方的时钟允许误差较大。
4 通讯速率
区分码元和二进制区别
通讯协议
1。物理层
主要讲RS-232,通讯结构如图
1.1电平标准
RS-232增加串口通讯的远距离传输及抗干扰能力,它使用15V 表示逻辑 1, +15V 表示逻辑 0。
因为控制器一般使用 TTL 电平标准,所以常常会使用 MA3232 芯片对 TTL 及 RS-232电平的信号进行互相转换。
RS-232的管教定义可以不理解
2。协议层
数据包的基本组成
2.1波特率
波特率即每个码元的长度,用于异步通信,以便对信号进行解码,异步通信没有时钟信号,用相同的波特率进行传输,上图中用虚线分开的每一格就是代表一个码元。
2.2通信的起始信号和停止信号
起始信号:一个逻辑 0 的数据位表示
终止信号:可由 0.5、 1、 1.5 或 2 个逻辑 1 的数据位表示,需通信双方约定一致
2.3有效数据
就是你要传的数据,长度可以是 5、 6、 7 或 8 位长
2.4数据校验
防止数据通信受到外部干扰导致出现偏差。常见校验方法有奇校验(odd)、偶校验(even)、 0 校验(space)、 1 校验(mark)以及无校验(noparity)
奇校验:有效数据位+校验位中“1”的个数为奇数。举例,一个 8 位长的有效数据为: 01101001,此时总共有 4 个“ 1”,为达到奇校验效果,校验位为“ 1”,最后传输的数据将是 8 位的有效数据加上 1 位的校验位总共 9 位。
偶校验:有效数据位+校验位中“1”的个数为偶数
零校验:不管有效数据中的内容是什么,校验位总为“ 0”
1 校验:不管有效数据中的内容是什么,校验位总为“ 1”
无校验:没有校验位
STM32 USART
简单区分同步和异步就是看通信时需不需要对外提供时钟输出
功能框图
1.引脚:
TX:发送数据
RX:接收数据
nRTS:n表示低电平有效,Request to send,使能RTS控制流,接收器准备好接收后nRTS置为低电平,可以接收数据。接收存储器满后,nRTS置为高电平。
nCTS:n表示低电平有效,clear to send,使能CTS控制流,发送器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。
SCLK: 发送时钟
2.数据寄存器(USART_DR)
就是上图蓝框2的位置
数据寄存器是一个地址对应了两个寄存器,既可以接收数据,也可以发送数据,相当于51里的SBUF,数据寄存器只有低九位有效。发送数据时,TDR接收来自CPU或者DMA的数据,通过移位寄存器将数据经TX发出去;接收数据时,通过RX口将数据接收进来,经移位寄存器存入RDR,在由RDR将数据返回给CPU(我觉得,单片机要是想发送数据,得先接收来自PC的程序,由CPU决策以后在将数据发送出去)
单片机是一个微型计算机,由处理器CPU、随机储存器RAM、储存器ROM、输入输出口I/O等组成,而CPU则是单片机的主核心部分。
MCU芯片全称为MicrocontrollerUnit(微控制单元),又称为单片微型计算机或者单片机。它是一个是把中央处理器的频率与规格做适当缩减,并将内存、计数器、USB、A/D转换、UART、PLC、DMA等周边接口,甚至LCD驱动电路都整合在单一芯片上,形成芯片级的计算机。
数据寄存器的数据字长由USART_CR1的M位决定
1:一个起始位, 9个数据位, n个停止位 0:一个起始位, 8个数据位, n个停止位; |
一个字符帧发送需要三个部分:起始位+数据帧+停止位。起始位时低电平,停止位是高电平。默认使用 1 个停止位。 2 个停止位适用于正常USART 模式、单线模式和调制解调器模式。 0.5 个和 1.5 个停止位用于智能卡模式。空闲帧包括了停止位。
断开帧是10位低电平,后跟停止位(当m=0时);或者11位低电平,后跟停止位(m=1时)。不可能
传输更长的断开帧(长度大于10或者11位)。
注:
1.在数据传输期间不能复位TE位,否则将破坏TX脚上的数据,因为波特率计数器停止计数。 2. TE位被激活后将发送一个空闲帧。 |
数据中还有校验位的选择,校验位则由USART_CR1的PCE、PS决定。如果PCE为0,则不需要校验,如果为1,还需要设置校验的模式,则由PS来确定
选择好校验位以后,还需检验校验是否正确,当校验结果不一样的时候,USART_CR1的PEIE会产生中断
然后由USART_SR的PE位来判断校验是否错误
3.控制器
USART 有专门控制发送的发送器、控制接收的接收器,唤醒单元、中断控制等等
数据发送接收的具体流程
发送器
配置步骤:
1、UE=1;
2、控制寄存器1的M位设置数据长度
3、控制寄存器2的13位设置停止位长度
4、采用多缓冲器通信的话,CR3的DMAR位使能DMA模式
5、BRR寄存器设置波特率
6、TE=1;发送一个空闲帧
7、发动数据到DR寄存器(此时可以清除TXE),单缓冲通信重复七次
8、等待TC位结果,TC=1,传输结束
发送流程:
首先要控制寄存器给串口使能,UE=1,给串口打开,然后在设置TE=1,给输出使能,再把从CPU或者DMA来的数据由TDR->移位寄存器从TX口一位一位的发出去。
当TX将数据传给移位寄存器以后,它里面就是空的,在状态寄存器里可以通过TXE寄存器知道数据是否传输过去。但是TX为空不能代表数据确实传完了,还要看看移位寄存器里是否空了,状态寄存器的TC位检测数据是否传输完成。
接收数据流程:
UE=1,RE=1
数据从RX来,先到移位寄存器,再由移位寄存器放到RDR,再传给CPU,也有一个检测位RXNE,为1时表示收到数据
为得到信号的真实情况,需要用比这个信号更高频率的信号检测,称为过采样。频率越高准确率越高,采样信号也越困难,选择合适的。接收器可配置为不同过采样技术
4.波特率
波特率通过USART_BRR
5.LIN(局域互联网)模式
LIN主要功能是为CAN总线网络提供辅助功能
LIN模式是通过设置USART_CR2寄存器的LINEN位选择。在LIN模式下,下列位必须保持为0:
● USART_CR2寄存器的CLKEN位
● USART_CR3寄存器的STOP[1:0], SCEN, HDSEL和IREN
LIN发送
所描述的同样步骤适用于LIN主发送,但和正常USART发送有以下区别:
● 清零M位以配置8位字长
● 置位LINEN位以进入LIN模式。这时,置位SBK将发送13位’0’作为断开符号。然后发一
位’1’,以允许对下一个开始位的检测
配置步骤:
1. 通过在USART_CR1寄存器上置位UE位来激活USART
置位LINEN位以进入LIN模式
2. 编程USART_CR1的M=0。
3. 在USART_CR2中编程停止位的位数。
4. 如果采用多缓冲器通信,配置USART_CR3中的DMA使能位(DMAT)。按多缓冲器通信中
的描述配置DMA寄存器。
5. 利用USART_BRR寄存器选择要求的波特率。
6. 设置USART_CR1中的TE位,发送一个空闲帧作为第一次数据发送。
7. 把要发送的数据写进USART_DR寄存器(此动作清除TXE位)。在只有一个缓冲器的情况
下,对每个待发送的数据重复步骤7。
8. 在USART_DR寄存器中写入最后一个数据字后,要等待TC=1,它表示最后一个数据帧的
传输结束。当需要关闭USART或需要进入停机模式之前,需要确认传输结束,避免破坏
最后一次传输。
LIN接收
当LIN模式被使能时,断开符号检测电路被激活。该检测完全独立于USART接收器。断开符号
只要一出现就能检测到,不管是在总线空闲时还是在发送某数据帧其间,数据帧还未完成,又
插入了断开符号的发送。
当接收器被激活时(USART_CR1的RE=1),电路监测RX上的起始信号。监测起始位的方法同检
测断开符号或数据是一样的。当起始位被检测到后,电路对每个接下来的位,在每个位的第8,
9, 10个 过 采 样 时 钟 点 上 进 行 采 样 。 如 果10个(当USART_CR2 的LBDL = 0)或11个(当
USART_CR2 的LBDL = 1)连续位都是’0’,并且又跟着一个定界符, USART_SR的LBD标志被
设置。如果LBDIE位=1,中断产生。在确认断开符号前,要检查定界符,因为它意味RX线已经
回到高电平。
如果在第10或11个采样点之前采样到了’1’,检测电路取消当前检测并重新寻找起始位。如果LIN
模式被禁止,接收器继续如正常USART那样工作,不需要考虑检测断开符号。
如果LIN模式没有被激活(LINEN=0),接收器仍然正常工作于USART模式,不会进行断开检测。
如果LIN模式被激活(LINEN=1),只要一发生帧错误(也就是停止位检测到’0’,这种情况出现在断
开帧),接收器就停止,直到断开符号检测电路接收到一个’1’(这种情况发生于断开符号没有完整
的发出来),或一个定界符(这种情况发生于已经检测到一个完整的断开符号)
一个起始位, 8个数据位, n个停止位
情形1:断开时间过长
情形2:数据还未发完就断开
6.利用DMA连续通信
DMA通讯原理
DMA 传输将数据从一个地址空间复制到另外一个地址空间。当CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA 传输对于高效能 嵌入式系统算法和网络是很重要的。
在实现DMA传输时,是由DMA控制器直接掌管总线,因此,存在着一个总线控制权转移问题。即DMA传输前,CPU要把总线控制权交给DMA控制器,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU。一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。
利用DMA发送
使用DMA进行发送,可以通过设置USART_CR3寄存器上的DMAT位激活。当TXE位被置为’1’
时, DMA就从指定的SRAM区传送数据到USART_DR寄存器。为USART的发送分配一个DMA
通道的步骤如下(x表示通道号):
1. 在DMA控制寄存器上将USART_DR寄存器地址配置成DMA传输的目的地址。在每个TXE
事件后,数据将被传送到这个地址。
2. 在DMA控制寄存器上将存储器地址配置成DMA传输的源地址。在每个TXE事件后,将从
此存储器区读出数据并传送到USART_DR寄存器。
3. 在DMA控制寄存器中配置要传输的总的字节数。
4. 在DMA寄存器上配置通道优先级。
5. 根据应用程序的要求,配置在传输完成一半还是全部完成时产生DMA中断。
6. 在DMA寄存器上激活该通道。
当传输完成DMA控制器指定的数据量时, DMA控制器在该DMA通道的中断向量上产生一中断。
在发送模式下,当DMA传输完所有要发送的数据时, DMA控制器设置DMA_ISR寄存器的TCIF
标志;监视USART_SR寄存器的TC标志可以确认USART通信是否结束,这样可以在关闭
USART或进入停机模式之前避免破坏最后一次传输的数据;软件需要先等待TXE=1,再等待
TC=1。
利用DMA接收
可以通过设置USART_CR3寄存器的DMAR位激活使用DMA进行接收,每次接收到一个字节,
DMA控制器就就把数据从USART_DR寄存器传送到指定的SRAM区(参考DMA相关说明)。为
USART的接收分配一个DMA通道的步骤如下(x表示通道号):
1. 通过DMA控制寄存器把USART_DR寄存器地址配置成传输的源地址。在每个RXNE事件
后,将从此地址读出数据并传输到存储器。
2. 通过DMA控制寄存器把存储器地址配置成传输的目的地址。在每个RXNE事件后,数据将
从USART_DR传输到此存储器区。
3. 在DMA控制寄存器中配置要传输的总的字节数。
4. 在DMA寄存器上配置通道优先级。
5. 根据应用程序的要求配置在传输完成一半还是全部完成时产生DMA中断。
6. 在DMA控制寄存器上激活该通道。
当接收完成DMA控制器指定的传输量时, DMA控制器在该DMA通道的中断矢量上产生一中断。
多缓冲器通信中的错误标志和中断产生
在多缓冲器通信的情况下,通信期间如果发生任何错误,在当前字节传输后将置起错误标志。
如果中断使能位被设置,将产生中断。在单个字节接收的情况下,和RXNE一起被置起的帧错
误、溢出错误和噪音标志,有单独的错误标志中断使能位;如果设置了,会在当前字节传输结
束后,产生中断
7.硬件流控制
利用nCTS输入和nRTS输出可以控制2个设备间的串行数据流。通过将UASRT_CR3中的RTSE和CTSE置位,可以分别独立地使能RTS和CTS流控制。
RTS流控制
如果RTS流控制被使能(RTSE=1),只要USART接收器准备好接收新的数据, nRTS就变成有效
(接低电平)。当接收寄存器内有数据到达时, nRTS被释放,由此表明希望在当前帧结束时停止
数据传输。
CTS流控制
如果CTS流控制被使能(CTSE=1),发送器在发送下一帧前检查nCTS输入。如果nCTS有效(被
拉成低电平),则下一个数据被发送(假设那个数据是准备发送的,也就是TXE=0),否则下一帧
数据不被发出去。若nCTS在传输期间被变成无效,当前的传输完成后停止发送。
当CTSE=1时,只要nCTS输入一变换状态,硬件就自动设置CTSIF状态位。它表明接收器是否
准备好进行通信。如果设置了USART_CT3寄存器的CTSIE位,则产生中断。
USART中断请求
USART的各种中断事件被连接到同一个中断向量(见下图),有以下各种中断事件:
● 发送期间:发送完成、清除发送、发送数据寄存器空。
● 接收期间:空闲总线检测、溢出错误、接收数据寄存器非空、校验错误、 LIN断开符号检
测、噪音标志(仅在多缓冲器通信)和帧错误(仅在多缓冲器通信)。
如果设置了对应的使能控制位,这些事件就可以产生各自的中断。
USART 初始化结构体
typedef struct
{
uint32_t USART_BaudRate; //波特率9600 19200 115200
uint16_t USART_WordLength; //数据长度,八位或九位(包含校验位)
uint16_t USART_StopBits; //停止位,默认1个,2个停止位适用于正常USART 模式、单线模
式和调制解调器模式
uint16_t USART_Mode; //输入或输出
uint16_t USART_Parity; //校验位,奇偶校验或者无校验
uint16_t USART_HardwareFlowControl; //只有在硬件流控制模式才有效,
可选有⑴使能 RTS、 ⑵使能 CTS、 ⑶同时使能 RTS 和
CTS、 ⑷不使能硬件流。
} USART_InitTypeDef;
时钟初始化(同步用)
typedef struct
{
uint16_t USART_Clock; //时钟使能,Enable,Disable两种模式,同步模式需开启时钟,异步不用
uint16_t USART_CPOL; //同步模式下时钟极性设置,可设置在空闲时SCLK 引脚为低电平
(USART_CPOL_Low)或高电平(USART_CPOL_High)。它设定 USART_CR2
寄存器的 CPOL 位的值。
uint16_t USART_CPHA; //同步模式相位设置,可设置在时钟第一个变化沿捕获数据
(USART_CPHA_1Edge)或在时钟第二个变化沿捕获数据。它设定
USART_CR2 寄存器的 CPHA 位的值。 USART_CPHA 与 USART_CPOL 配
合使用可以获得多种模式时钟关系
uint16_t USART_LastBit; //选择在发送最后一个数据位的时候时钟脉冲是否在 SCLK 引脚输出 ,
可以是不输出脉冲(USART_LastBit_Disable)、输出脉冲
(USART_LastBit_Enable)。它设定 USART_CR2 寄存器的LBCL位的值。
} USART_ClockInitTypeDef;
接收和发送
发送
编程要点:
1) 使能 RX 和 TX 引脚 GPIO 时钟和 USART 时钟;(利用宏定义)
宏定义内容:
IO口 GPIOA
PIN A9 A10
时钟使能 RCC_APB2ENR波特率 9600
2) 初始化 GPIO,并将 GPIO 复用到 USART 上;(库里有初始化函数)
3) 配置 USART 参数;
4) 配置中断控制器并使能 USART 接收中断;
5) 使能 USART;
宏定义需要定义些什么呢?
直接将例程里的串口初始化程序拷贝到自己的工程下,会看见很多报错,这些报错就是要宏定义的内容
宏定义如下
需要注意的是,这样宏定义并未完成,在bsp_usart.c里还有报错,还需宏定义DEBUG_USART_GPIO_APBxClkCmd和DEBUG_USART_APBxClkCmd
不知道如何宏定义的话,可以右键 go to defination,注释里面的写的很清楚,所以这两个宏定义是一样的
还没完,还有中断没有定义,这里插播一下,这些宏定义需要熟悉固件库,就算不熟悉也要会查找,需要知道宏定义的内容所在文件位置,比如下面这个错误,肯定是要宏定义USART的中断,需要去stm32f10x.h里找,直接查找(ctrl+F)interrupt
中断这里,解释一下这个函数,这个函数在下面彩灯实验的时候会用,用来接收串口调试助手发送的数据,控制灯的亮灭。
当 USART 有接收到数据就会执行DEBUG_USART_IRQHandler 函数。 USART_GetITStatus 函数与 USART_GetFlagStatus 函数类似用来获取标志位状态,但 USART_GetITStatus 函数是专门用来获取中断事件标志的,并返回该标志位状态。使用 if 语句来判断是否是真的产生 USART 数据接收这个中断事件,如果是真的就使用 USART 数据读取函数 USART_ReceiveData 读取数据到指定存储区。然后再调用 USART 数据发送函数 USART_SendData 把数据又发送给源设备。
和时钟一样,时钟有一个时钟使能函数,终端也有中断服务函数,还需给这个函数也定义一下
调用 RCC_AHB1PeriphClockCmd 函数开启 GPIO 端口时钟,使用 GPIO 之前必须开启
对应端口的时钟。使用 RCC_APB2PeriphClockCmd 函数开启 USART 时钟。
这个宏定义最后一行有错,多了一个n,用的时候一定删掉,不然接收不到
#define DEBUG_USART_GPIO_CLK RCC_APB2Periph_GPIOA//选择时钟总线
#define DEBUG_USART_CLK RCC_APB2Periph_USART1
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd//打开时钟,置位或清零来打开或关闭时钟
#define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10
#define DEBUG_USART_BAUDRATE 9600
#define DEBUG_USARTx USART1
#define DEBUG_USART_IRQ USART1_IRQn
#define DEBUG_USART_IRQHandler USART1_IRQnHandler
6) 在 USART 接收中断服务函数实现数据接收和发送
首先在main.c初始化串口
发送一个字节sendbyte:
通过调用库函数里的senddata实现
bsp_usart.c里写
void sendbyte(USART_TypeDef* USARTx,uint16_t byte){
USART_SendData(USARTx, byte);
}
调用完之后,还要检测数据是否传输完成,也就是检测TXE
void sendbyte(USART_TypeDef* USARTx,uint16_t byte){
USART_SendData(USARTx, byte);
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE)==RESET);
}
这里有一个问题,单字节传输调用了库里的senddata函数,示例代码改了一下第一个参数的名字
示例代码运行起来肯定是没有问题的,但是我比较好奇,所以两处的参数全用USART_TypeDef* USARTx试了一下,并没有出问题,又去问了客服,可能是为了区分,写一样的话后面写的代码长了可能会搞混
还有一个问题是main调用的时候
调试助手没数,这不是没发,是ASCII码12没对应字符,这里一开始老以为是我代码错了
换成16进制就有了
如果什么数据都没发送的话,调试助手会是这样的
接下来用同样的方法实现发送两个字节,发送一串字符串
两个字节
我用的办法是用两个参数
void send2byte(USART_TypeDef* USARTx,uint16_t byte1,uint16_t byte2){
USART_SendData(USARTx, byte1);
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE)==RESET);
USART_SendData(USARTx, byte2);
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE)==RESET);
}
实例的办法是把每一位取出来,这种方法只有一个参数,后续传字符串的话也不用一个字符一个参数,比较方便
void send2byte(USART_TypeDef* USARTx,uint8_t byte){
uint8_t temp;
temp=(byte&0xf0);
USART_SendData(USARTx, temp);
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE)==RESET);
temp=(byte<<8);
USART_SendData(USARTx, temp);
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE)==RESET);
}
这个思路是有问题的,1byte=8bit,两个字节就是,取高八位得与ff00,另外取出来要放的话,得右移一下,要不结果还带0x00;还有第八位不需要移位,直接与00ff就好
void send2byte(USART_TypeDef* USARTx,uint16_t byte){
uint8_t temp;
temp=((byte&0xff00)>>8);
USART_SendData(USARTx, temp);
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE)==RESET);
temp=(byte&0x00ff);
USART_SendData(USARTx, temp);
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE)==RESET);
}
主函数有一个问题,这种属于字符串了,在发送字符串函数里可以用这里第二个参数写十六进制数就行,比如0xaa89,0xff34,0xadbc这种
字符串
void sendstring(USART_TypeDef* USARTx,uint8_t *str){
int k=0;
while(*str!='\0'){
sendbyte(USARTx, *(str+k));
k++;
}
}
这里的一个问题是到底是写*str+k,还是*(str+k)?
str是指针,str+1就是地址+1,也就是地址从0到1了,*(str+1)的意思就是我把str+1这个地址里的数据拿出来了,*str是数,是str这个地址里存的数据,*str+1是给这个数据加一了,地址没有变化
字符串里有个问题,main里会有这个警告,但是运行以后没有警告没有错误,运行结果也是对的
客服说不重要,不报错就行,后面等我想明白在解答
还有个问题,是pdf里字符串这块是这么写的
例程不是,我把例程里的类型改为char以后,会有报错
这里感觉是SendData的问题,SendData的参数类型是uint16_t,就不是char型(不确定)
数组
注意发送函数的正确使用
void sendarray(USART_TypeDef* USARTx,uint8_t *array,uint8_t num){
int i=0;
for(;i<num;i++){
USART_SendData(USARTx, array[i]);
}
while(USART_GetFlagStatus(USARTx, USART_FLAG_TC)==RESET);
}
void sendarray(USART_TypeDef* USARTx,uint8_t *array,uint8_t num){
int i=0;
for(;i<num;i++){
sendbyte(USARTx, array[i]);
}
while(USART_GetFlagStatus(USARTx, USART_FLAG_TC)==RESET);
}
其实在所有的这些函数里,我一直有个疑问,为什么用的是sendbyte,不是senddata,试了一下,字符串数组这些用了senddata以后只能输出最后一个字符,原因是什么还需要再想一下,但是肯定是不能用的,senddata和sendbyte都是可以发送一个字节,他俩的区别,有的代码底层是senddata,有的底层是sendbyte
printf
重定向:
要想printf()和scanf() 函数工作,我们需要把printf()和scanf() 重新定向到串口中。重定向是指用户可以自己重写C 的库函数,当连接器检查到用户编写了与C 库函数相同名字的函数时,优先采用用户编写的函数,这样用户就可以实现对库的修改了。为了实现重定向printf()和scanf() 函数,我们需要分别重写fputc()和fgetc() 这两个C 标准库函数。
MicroLib的stdio.h中,fputc()函数的原型为:int fputc(int ch, FILE* stream)
此函数原本是将字符ch打印到文件指针stream所指向的文件流去的,现在我们不需要打印到文件流,而是打印到串口1。
#include <stdio.h>
int fputc(int ch, FILE* stream)
{
//USART_SendData(USART1, (unsigned char) ch);
//while (!(USART1->SR & USART_FLAG_TXE));
USART_SendChar(USART1, (uint8_t)ch);
return ch;
}
scanf
C 库函数 int fgetc(FILE *stream) 从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。
fgetc ()函数的功能是从文件指针指定的文件中读入一个字符,该字符的ASCII值作为函数的返回值,若返回值为EOF,说明文件结束,EOF是文件结束标志,值为-1。 语句“c=fgetc (fp);”是从文件指针fp指定的文件中读一个字符并存人c 变量 中,c是字符型变量。
//filegetchar
int fgetc(FILE*f){
while(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE)==RESET);
return (int)USART_ReceiveData(DEBUG_USARTx);
}
换串口
换串口的话,只需将USART1的东西换成其他的就行,但是串口不同,搭载的时钟和GPIO口也不同,这里展示一下串口5,串口5需要同时打开两个GPIO时钟
//串口5
#define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD)
#define DEBUG_USART_CLK RCC_APB1Periph_USART5
#define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd//打开时钟,置位或清零来打开或关闭时钟
#define DEBUG_USART_APBxClkCmd RCC_APB1PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT GPIOC
#define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_12
#define DEBUG_USART_RX_GPIO_PORT GPIOD
#define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_2
#define DEBUG_USART_BAUDRATE 9600
#define DEBUG_USARTx USART5
#define DEBUG_USART_IRQ USART5_IRQn
#define DEBUG_USART_IRQHandler USART5_IRQnHandler
视频里条件定义的方式我很喜欢
前面记错的
#define USART1 1
#define USART2 0
#define USART3 0
#define USART4 0
#define USART5 0
正确的
#define DEBUG_USART1 1
#define DEBUG_USART2 0
#define DEBUG_USART3 0
#define DEBUG_USART4 0
#define DEBUG_USART5 0
接收
接收很简单啊,前面步骤都做完以后,直接在it.c文件里加入中断服务函数就好,中断服务函数在例程里,复制粘贴即可
前面宏定义的时候最后一行写错了,导致一直接收不了需要注意代码的书写问题
控制RGB彩灯
1. 编程要点
1) 初始化配置 RGB 彩色灯 GPIO;
2) 使能 RX 和 TX 引脚 GPIO 时钟和 USART 时钟;
3) 初始化 GPIO,并将 GPIO 复用到 USART 上;
4) 配置 USART 参数;
5) 使能 USART;
6) 获取指令输入,根据指令控制 RGB 彩色灯
注意:这一部分的接收用的是重定向的getchar,所以需要把中断相关部分全部注释掉,不然可能会产生冲突
最终效果:
串口调试助手发1,绿灯亮;2,红灯亮;3蓝灯亮
需要在bsp_led.h里宏定义,每个人的宏定义可能不一样,我的和例程也不一样,有自己的思路的话,按照自己的思路来就可以了,后面会放上我自己的工程的链接。
利用switch函数
switch里放什么?
switch(USART_ReceiveData(DEBUG_USARTx))不用中断,不放这个
switch(scanf("%d",&a))scanf
还有一个C语言的知识问题,switch括号里不能写判断语句
switch(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE)==RESET)
正确代码
int main(void)
{
uint8_t ch;
USART_Config();
LED_GPIO_Config();
//printf("嗨嗨嗨");
//GPIO_ResetBits(GPIOB,LED_G);
//LED_ON(GPIOB,LED_G);
GPIO_ResetBits(GPIOB,GPIO_Pin_1);
while(1){
//ch = getchar();
//printf( "ch=%c\n",ch );
switch(getchar()){
case '1':LED_GON;
break;
case '2':LED_RON;
break;
case '3':LED_BON;
break;
default: LED_ALLOFF ;
break;
}
// scanf("%c",&ch);
//
// printf( "ch=%c\n",ch );
}
}
这里可以看到我注释了scanf的测试,在这个例程里,用的不是中断,是重定向的函数,去看代码可以发现底层是senddata,所以只能传输单个字节,不能传输一整句话。
还有一点问题在bsp_led.h里,换行符号,main.c里会报错
报错一直说表达式后面需要;这里的报错说的是bsp_led.h里需要;改成下面这样就不报错了,需要注意的是,有时候main里的报错不一定是main里的问题,解决错误的话需要灵活的方法,
关于换行符 "\"这个“;”到底要加在哪我不太确定,野火例程里都只有一个“;”,就没有问题,但是我这边需要两个;
以上就是野火的stm32的串口通信的全部内容,我手边还有一块TI的板子,用这个试一下
感觉TI的要比野火的难一点,毕竟有好多奇怪的报错
新建工程参照TMS570LC43xx的SCI串口输出_之江小林的博客-CSDN博客_enable sci driver
HALCoGEN勾选界面如下
main.c里代码
int main(void)
{
/* USER CODE BEGIN (3) */
gioInit();
sciInit();
_enable_IRQ_interrupt_();
//sciSendByte(sciREG1, 'a');
/*uint8 ch;
ch=getchar();
printf("ch=%c\n",ch);*/
//sciReceiveByte(sciREG1);
printf("哈哈哈\n");
/* USER CODE END */
while(1);
//return 0;
}
这个博客,写的很详细,也可以先建CCS工程,再生成HALCoGEN,这样位置这里可以直接选到所建的CCS工程目录下
左侧工具栏找不到咋办
Window->Show View->Project Explorer(如果没有Project Explorer选项,则Window->Show View->other->Project Explorer)找不到就对话栏里直接打就有了
哪个找不到,就在这个位置搜就行
照着做的话一般不会有问题,如果是找不到头文件的话,HALCoGEN下的每一个.c文件都会报错,添加一下头文件路径即可,和keil是一样的
还有就是头文件记得添加全,这里有三个
HALCoGEN自带的help里面有例程,可以看一下,还蛮好用的,在这个目录里,找对应的芯片
C:\ti\Hercules\HALCoGen\v04.07.01\help
博客的main.c里写了中断,如果是发送的话,不写中断一样不影响发送,可以注释掉,或者直接不写这行代码
/* Include Files */
#include "HL_sci.h"
#include "HL_system.h"
#include "HL_sys_common.h"
int main(void)
{
//_enable_IRQ_interrupt_();
sciInit();
sciSend(sciREG1, 5, (uint8 *)"12345");
return 0;
}
配置好环境以后呢,就是debug调试
发送数据
HL_sci.c一共有两个发送函数
sciSendByte(sciBASE_t *sci, uint8 byte)和
sciSend(sciBASE_t *sci, uint32 length, uint8 * data)
sciSendByte和stm32里的sendbyte一样,sciSend需要注意的是,第二个参数是uint8* data,发了一个指定长度的字符
sciSend(sciREG1, 5, (uint8 *)"12345");
sciSend(sciREG1, 5, (uint8 *)"abcde");//'abcde'不行,会有警告,too many characters in character literal
//"abcde"是可以发的,但是也有警告,是拼写不正确,abcde是一串字符串,用senstr会更好
仿照32可以写TI的发送两个字节的函数
void sciSend2Byte(sciBASE_t *sci, uint16 byte){
uint8_t temp;
temp=((byte&0xff00)>>8);
sciSendByte(sci, temp);
temp=(byte&0x00ff);
sciSendByte(sci, temp);
}
发送字符串,需要看TMS570LC43x的手册,因为需要等待数据传输完成
发送字符串的底层代码都是sendbyte,指南者里是直到传输完成一直在循环,需要标志位,这里是TC,在底层sendbyte里是TX,在TMS570也要找标志位
void sendstr(USART_TypeDef* USARTx,uint8_t *str){
uint8_t k=0;
while(*(str+k)!='\0'){
sendbyte(USARTx, *(str+k));
k++;
}
while(USART_GetFlagStatus(USARTx, USART_FLAG_TC)==RESET);
}
在手册找到寄存器,可以看到所有的标志位,我没有看到像指南者一样分TX和TC,应该只是TX RDY就可以,
从芯片的库函数来看标志位,标志位应该也只有TX
sci.c里面也是在等待TX,所以应该是这么写
void sendstr(sciBASE_t *sci,uint8 *str){
uint8 k=0;
while(*(str+k)!='\0'){
sciSendByte(sci, *(str+k));
k++;
}
while ((sci->FLR & (uint32)SCI_TX_INT) == 0U);
}
下面这个代码不对,只等待传输完成即可,不要把内容放到循环里,刚才看错了,sendbyte传输的内容也没在while里
void sendstr(sciBASE_t *sci,uint8 *str){
while ((sci->FLR & (uint32)SCI_TX_INT) == 0U){
uint8 k=0;
while(*(str+k)!='\0'){
sciSendByte(sci, *(str+k));
k++;
}
}
}
插一嘴, U 表示 无符号整型,参考http://t.csdn.cn/3OmPt 写的很详细
不写U后缀,默认为:int, 即,有符号整数。
1.数值常数有:整型常数、浮点常数;
2.只有数值常数才有后缀说明;
3.数值常数后缀不区分字母大小写。
(1)整型常数的表示形式有:十进制形式、以0开头的八进制形式、以0x开头的十六进制形式,无二进制形式。 整型常数默认是signed int的。 对整型常数进行类型转换的后缀只有:u或U(unsigned)、l或L(long)、u/U与l/L的组合(如:ul、lu、Lu等)。例:100u; -123u; 0x123l;
(2)浮点常数的表示形式有:科学计数形式和小数点形式。 浮点常数默认是double的。 对浮点常数进行类型转换的后缀只有:f或F(单精度浮点数)、l或L(长双精度浮点数)。(注:因浮点型常数总是有符号的,故没有u或U后缀)
printf和scanf重定向
TMS570将printf打印到串口需要额外的操作,配置参考http://t.csdn.cn/kWLIs但是代码不要,这个代码是打印到console窗口里
而且会报错,一开始我没找到库里对于fputc的定义位置,直接用了博客里的,后来我发现无论如何都不能打印到串口调试助手里,后面还有报错,后来发现博客里的和库里的不一样,用库里的就可以打印了
我希望是打印到串口调试助手里,关键是要在库里找到fputc和putc两个函数,然后仿照指南者重定向,在stido.h里
//printf
int fputc(int _c, FILE *_fp){
sciSendByte(sciREG1,(uint8)_c);
while((sciREG1->FLR & (uint32)SCI_TX_INT) == 0U);
return (_c);
}
//scanf
int putc(int _x, FILE *_fp){
while((sciREG1->FLR & (uint32)SCI_TX_INT) == 0U);
return (int)sciReceiveByte(sciREG1);
}
在函数里看到很多
/* USER CODE BEGIN (9) */
/* USER CODE END */
代码必须写在这种代码块内,否则会被HALCoGen自动生成功能给覆盖掉;
接收数据
和指南者一样,接收数据需要用到中断函数,HALCoGEN配置和发送一样,只需在CCS里改代码即可
这块板子的接收用的是回调函数,将接收到的数据通过sendbyte发送给串口调试助手了
void sciNotification(sciBASE_t *sci,uint32_t flags)
{
unsigned char byte = sci->RD;
sciSendByte(sci,byte);
}
sci.c里找到 void lin1HighLevelInterrupt(void) 函数,在11Ubu注释掉除中断进入函数外的其他部分
其余部分不需要改动,调试助手发送数据可以在接收端接收到
下面试试用串口控制灯亮
在这有个问题是,指南者并没有用中断接收数据,用的是重定向的scanf,但是我在CCS里试了一下scanf,就会一直在等待接收,串口调试助手发的东西并没有接受到,通过回调函数可以看出将接收到的数据通过sendbyte发送给串口调试助手了,那么找一个东西接收这个数据,接收到的数据在main.c里继续操作
led的GPIO接口
对应的HALCoGEN
初始化里需要把这里全改为0,否则初始化灯会直接亮
点灯的代码
gioToggleBit(gioPORTB, 6);//led2亮
gioToggleBit(gioPORTB, 7);//led3亮
在指南者里用的是重定向的getchar函数来接收串口调试助手的指令的,如果在CCS里用同样的代码的话,debug的时候会一直卡在getchar函数里(enable interrupt注掉了)如果用中断的方式接收呢,也没有接收到,接受指令这块还需要再思考一下。