DMA,Direct Memory Access,存储器直接访问,一种高速数据传输操作,允许外设与存储器、外设与外设之间直接交换数据。
CPU 和 DMA 控制器的传输过程处于并行操作状态,大大提高整个计算机系统效率。
适用于一些高速的I/O设备(kBps),例如磁盘存取、图像处理、高速数据采集、同步通信中的收/发信号。
DMAC,DMA控制器,负责DMA传送全过程控制的硬件电路。
参考文章:DMA总结
通信方式
数据传输,按照数据流方向可分为三种传输方式:
单工通信只支持单方向传输数据,任何时候不能改变传输方向。为了保证数据不失真,需要校验位,校验出错时通过监控信道发送请求重发信号。
适用于数据收集系统,例如计算机和打印机,只有计算机向打印机传输数据。
半双工通信,支持两个方向传输,但同一时刻只能存在一个方向上的传输,是一种可调换方向的单工通信,例如对讲机。
全双工通信,允许数据同时在两个方向上传输,有两个传输道路,是两个单工通信结合。
全双工通信效率高,控制简单,造价高,例如手机、计算机。
LIN总线
Local Interconnect Network,本地互联网络总线。
LIN主要功能是为CAN总线网络提供辅助功能:
usart文件夹包括 .c 文件和 .h 文件,针对串口1进行了初始化和中断接收,用其他串口时需要更改。主要包括两个函数:
下面来解剖这两个函数
1.uart_init 函数
void uart_init(u32 bound)
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA
|RCC_APB2Periph_AFIO, ENABLE);
//使能 USART1,GPIOA 时钟
//以及复用功能时钟
使用一个内置外设的时候,要首先使能相应的GPIO时钟,然后使能复用功能时钟和内置外设时钟
//USART1_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
//PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//端口速度
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//复用推挽输出模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
//初始化 GPIOA.9 发送端
//USART1_RX PA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
//PA.10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
//初始化 GPIOA.10 接收端
GPIO配置步骤:
如何查找GPIO端口应该配置为什么模式
在《STM32中文参考手册》中搜索“外设的GPIO配置”,得到以下几个表格(以下不全):
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
//对应中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;
//抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
//子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//IRQ 通道使能
NVIC_Init(&NVIC_InitStructure);
//中断优先级配置
NVIC_IRQChannel:
定义初始化的是哪个中断,这个我们可以在 stm32f10x.h 中找到每个中断对应的名字。
例如 USART1_IRQn。
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;
//波特率;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
//字长为 8 位
USART_InitStructure.USART_StopBits = USART_StopBits_1;
//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;
//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl=
USART_HardwareFlowControl_None;
//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
//收发
USART_Init(USART1, &USART_InitStructure);
//初始化串口
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//开启中断
USART_Cmd(USART1, ENABLE);
//使能串口
串口配置的一般步骤
- 串口时钟使能,GPIO时钟使能
- 串口复位
- GPIO端口模式设置
- 串口参数初始化
- 开启中断,初始化NVIC
- 使能串口
- 中断处理函数
几个配置需要的库函数:
1. 串口时钟使能
查询系统架构图可知,USART1挂在APB2下,需要开启时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1);
2. 串口复位
复位用于设备异常时重新配置,系统刚开始工作时也需要复位。
USART_DeInit(USART1); //复位串口 1
3. 串口参数初始化
需要初始化的参数是:波特率、字长、停止位、奇偶校验位、硬件数据流控制、收发模式
通过初始化函数完成:
USART_Init(USART1, &USART_InitStructure);
结构体的成员变量配置示例如下
USART_InitStructure.USART_BaudRate = bound;
//波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
//字长为 8 位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;
//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;
//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl
= USART_HardwareFlowControl_None;
//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
//收发模式
USART_Init(USART1, &USART_InitStructure);
//初始化串口
4. 数据发送与接收
STM32的发送接收通过寄存器USART_DR实现,这是一个双寄存器,包含了TDR和RDR,写数据时串口就自动发送,收到数据时存储在内
发数据:
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
读数据:
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
5. 串口状态
通过寄存器USART_SR读取串口状态,一共有32位只取前10位,一般我们只关注第5、6位RXNE和TC:
RXNE(读数据寄存器非空):该位为1的时候,说明有数据被接收到,并且可读。此时需要尽快读取USART_DR,然后将RXNE清零或者直接置0清除
TC(发送完成):该位被置位时,USART_DR数据已经发送完成,可以设置中断。也有两种清零方式:读USART_SR,写USART_DR;直接将TC写0
读取串口状态的库函数:
USART_GetFlagStatus(USART1, USART_FLAG_RXNE);
USART_GetFlagStatus(USART1, USART_FLAG_TC);
串口的状态是通过宏定义实现的:
#define USART_IT_PE ((uint16_t)0x0028)
#define USART_IT_TXE ((uint16_t)0x0727)
#define USART_IT_TC ((uint16_t)0x0626)
#define USART_IT_RXNE ((uint16_t)0x0525)
#define USART_IT_IDLE ((uint16_t)0x0424)
#define USART_IT_LBD ((uint16_t)0x0846)
#define USART_IT_CTS ((uint16_t)0x096A)
#define USART_IT_ERR ((uint16_t)0x0060)
#define USART_IT_ORE ((uint16_t)0x0360)
#define USART_IT_NE ((uint16_t)0x0260)
#define USART_IT_FE ((uint16_t)0x0160)
6. 串口使能
通过函数USART_Cmd()
实现:
USART_Cmd(USART1, ENABLE);
//使能串口
7. 开启串口响应中断
当我们需要开启串口中断的时候,需要使能,例如:
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//开启中断,接收到数据中断
此函数的第二个入口参数是使能串口的类型,例如此例中我们需要在接收到数据的时候产生中断,就需要开启RXNE的中断USART_IT_RXNE
如果需要在发送数据结束的时候产生中断,则:
USART_ITConfig(USART1,USART_IT_TC,ENABLE);
//数据发送结束产生串口中断
8. 获取中断状态
比如我们使能了某个串口发生中断,当中断发生了,可以调用函数判断是否完成中断:
USART_GetITStatus(USART1, USART_IT_TC)
返回值是SET,则串口发送完成中断
2. USART1_IRQHandler
USART1_IRQHandler函数是串口1的中断响应函数,串口1发生中断时会跳转到其中去执行
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
用上面列举的常用库函数中第八句(获取中断状态),判断是否接受中断,如果接收了中断则读取串口接收到的数据:
Res = USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
数据赋给变量Res。读到数据之后就对数据进行分析。
这里有一个接收协议,利用数组USART_RX_BUF[]
,接收状态“寄存器”USART_RX_STA
(实为一个全局变量,但是起到类似寄存器的作用),实现对串口数据的接收管理。
USART_RX_BUF[] 的长度由USART_REC_LEN定义:
u8 USART_RX_BUF[USART_REC_LEN];
USART_REC_LEN
是位于 usart.h中定义的一个全局参数,定义最大接收的字节数
USART_RX_STA 是一个接收状态寄存器,定义表如下:
当接收到数据时,把数据保存在USART_RX_BUF[]中,同时在接收状态寄存器(USART_RX_STA)中计数接收到的有效数据个数。
当接收到回车( 回车由0X0D和0X0A组成 ) 的第一个字节0X0D (0x0D,asc码是13,指的是回车\r,把光标置于本行行首) 时,停止计数;
等待0X0A (0x0A,asc码是10,指的是换行 \n,把光标置于下一行的同一列) ,标记USART_RX_STA的第15位接收完成标志,完成一次接收,等待第15位被清除后完成一次接收。
如果0X0D回车来迟,而数据超过USART_REC_LEN时,会丢弃前面的数据重新接收
配置示例
void USART1_IRQHandler(void) //串口 1 中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
//接收中断(接收到的数据必须是 0x0d 0x0a 结尾)
{
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了 0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到 0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;
//接收数据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS
OSIntExit();
#endif
}
关于OS操作系统的研究日后再进行
查看原理图:
USART1的RXD和TXD位于PA10和PA9,再次查找得到电气连接方式:
这里发现串口1的TXD和RXD需要用跳线帽跟PA9、PA10连接在一起
在usart.h文件中可以引入"stdio.h"
头文件,并且加入一段代码,即可提供printf()函数支持,直接利用printf函数向串口发送我们需要的内容。原理及操作见下文:
STM32使用printf打印串口
我们来看一下主函数设计的几个示例及要点:
1. 接收数据部分
if(USART_RX_STA & 0x8000)
{
len = USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n 您发送的消息为:\r\n");
for(t=0;t<len;t++){
USART1->DR = USART_RX_BUF[t];
while((USART1->SR & 0X40) == 0);}//等待发送结束
printf("\r\n\r\n");//插入换行
USART_RX_STA = 0;
}
USART_RX_STA的bit15表示接收完成标志,bit14表示接收到0X0D
USART_RX_STA&0x8000,即bit15位比较,若为1则接受完成,之后再接收判断长度。
判断长度就是剩余14位比较,USART_RX_STA&0x3fff,0x3fff即0011 1111 1111 1111,bit相同则为1否则为0,便可得到USART_RX_STA的低14位的值,便得到其长度
2. 发送数据部分
else{
times ++;
if(times % 5000 == 0){
printf("\r\n123456789\r\n");
printf("asdfghjkl\r\n\r\n\r\n");}
if(times % 200 == 0)printf("hello world\r\n");
if(times % 30 == 0)LED0 =! LED0;
//LED闪烁指示系统还在运行
delay_ms(10);
}