stm32week4

news/2025/2/25 9:02:28

stm32_0">stm32学习

二.外设

12.DMA

DMA(Direct Memory Access)直接存储器存取
DMA可以提供外设和存储器或者存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
12个独立可配置的通道:DMA1(7个通道),DMA2(5个通道)
每个通道都支持软件触发和特定的硬件触发
cf103c8t6只有DMA1(7个通道)
存储器映像:

图片消失了

DMA基本结构:

图片消失了

从A传到B,若A的数据宽度为16而B为8,则B只存储A后8位的数据
若A的数据宽度为8而B为16,则B的前8位为0

图片消失了

DMA1请求映像:

图片消失了

图中的EN应该还是使能而不是指向数据选择器的

例子:
数据存储+DMA

图片消失了

ADC扫描模式+DMA

图片消失了

DMA一般是和ADC相配合的

DMA用于数据传输的代码的初始化代码:

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
 MyDMA_Size = Size;     //将Size写入到全局变量,记住参数Size
 
 /*开启时钟*/
 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);      //开启DMA的时钟
 
 /*DMA初始化*/
 DMA_InitTypeDef DMA_InitStructure;          //定义结构体变量
 DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;      //外设基地址,给定形参AddrA
 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据宽度,选择字节
 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;   //外设地址自增,选择使能
 DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;       //存储器基地址,给定形参AddrB
 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;   //存储器数据宽度,选择字节
 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;     //存储器地址自增,选择使能
 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;      //数据传输方向,选择由外设到存储器
 DMA_InitStructure.DMA_BufferSize = Size;        //转运的数据大小(转运次数)
 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;       //模式,选择正常模式
 DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;        //存储器到存储器,选择使能
 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;     //优先级,选择中等
 DMA_Init(DMA1_Channel1, &DMA_InitStructure);       //将结构体变量交给DMA_Init,配置DMA1的通道1
 
 /*DMA使能*/
 DMA_Cmd(DMA1_Channel1, DISABLE); //这里先不给使能,初始化后不会立刻工作,等后续调用Transfer后,再开始
}

启动数据传输的函数:

void MyDMA_Transfer(void)
{
 DMA_Cmd(DMA1_Channel1, DISABLE);     //DMA失能,在写入传输计数器之前,需要DMA暂停工作
 DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size); //写入传输计数器,指定将要转运的次数
 DMA_Cmd(DMA1_Channel1, ENABLE);      //DMA使能,开始工作
 
 while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); //等待DMA工作完成
 DMA_ClearFlag(DMA1_FLAG_TC1);      //清除工作完成标志位
}

DMA+AD多通道初始化:

uint16_t AD_Value[4];     //定义用于存放AD转换结果的全局数组

void AD_Init(void)
{
 /*开启时钟*/
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  //开启DMA1的时钟
 
 /*设置ADC时钟*/
 RCC_ADCCLKConfig(RCC_PCLK2_Div6);      //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
 
 /*GPIO初始化*/
 GPIO_InitTypeDef GPIO_InitStructure;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(GPIOA, &GPIO_InitStructure);     //将PA0、PA1、PA2和PA3引脚初始化为模拟输入
 
 /*规则组通道配置*/
 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置,配置为通道0
 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); //规则组序列2的位置,配置为通道1
 ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5); //规则组序列3的位置,配置为通道2
 ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5); //规则组序列4的位置,配置为通道3
 
 /*ADC初始化*/
 ADC_InitTypeDef ADC_InitStructure;           //定义结构体变量
 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;       //模式,选择独立模式,即单独使用ADC1
 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;      //数据对齐,选择右对齐
 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;   //外部触发,使用软件触发,不需要外部触发
 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;       //连续转换,使能,每转换一次规则组序列后立刻开始下一次转换
 ADC_InitStructure.ADC_ScanConvMode = ENABLE;        //扫描模式,使能,扫描规则组的序列,扫描数量由ADC_NbrOfChannel确定
 ADC_InitStructure.ADC_NbrOfChannel = 4;          //通道数,为4,扫描规则组的前4个通道
 ADC_Init(ADC1, &ADC_InitStructure);           //将结构体变量交给ADC_Init,配置ADC1
 
 /*DMA初始化*/
 DMA_InitTypeDef DMA_InitStructure;           //定义结构体变量
 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;    //外设基地址,给定形参AddrA
 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据宽度,选择半字,对应16为的ADC数据寄存器
 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;   //外设地址自增,选择失能,始终以ADC数据寄存器为源
 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;     //存储器基地址,给定存放AD转换结果的全局数组AD_Value
 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;   //存储器数据宽度,选择半字,与源数据宽度对应
 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;      //存储器地址自增,选择使能,每次转运后,数组移到下一个位置
 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;       //数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组
 DMA_InitStructure.DMA_BufferSize = 4;          //转运的数据大小(转运次数),与ADC通道数一致
 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;        //模式,选择循环模式,与ADC的连续转换一致
 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;        //存储器到存储器,选择失能,数据由ADC外设触发转运到存储器
 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;      //优先级,选择中等
 DMA_Init(DMA1_Channel1, &DMA_InitStructure);        //将结构体变量交给DMA_Init,配置DMA1的通道1
 
 /*DMA和ADC使能*/
 DMA_Cmd(DMA1_Channel1, ENABLE);       //DMA1的通道1使能
 ADC_DMACmd(ADC1, ENABLE);        //ADC1触发DMA1的信号使能
 ADC_Cmd(ADC1, ENABLE);         //ADC1使能
 
 /*ADC校准*/
 ADC_ResetCalibration(ADC1);        //固定流程,内部有电路会自动执行校准
 while (ADC_GetResetCalibrationStatus(ADC1) == SET);
 ADC_StartCalibration(ADC1);
 while (ADC_GetCalibrationStatus(ADC1) == SET);
 
 /*ADC触发*/
 ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发ADC开始工作,由于ADC处于连续转换模式,故触发一次后ADC就可以一直连续不断地工作
}

随后在h文件中extern一下AD_Value[4],就可以在main函数中直接用了

13.通信接口

通信的目的:将一个设备的数据传送到另一个设备,拓展硬件系统
通信协议:制定通信的规则,通信双方按照协议规则进行数据收发

图片消失了

全双工通信允许数据同时在两个方向上传输
半双工通信允许数据在两个方向上传输,但同一时刻只能在一个方向上传输
同步指发送端和接收端在数据传输时,都遵循一个共同的时钟信号
异步相反
单端信号采用单一的导线传输电信号,其参考点为地
差分信号则利用两根导线传输信号,关键在于这两根线上的电平差

简单双向串口通信有两根通信线(发送端TX和接收端RX)
TX和RX要交叉连接
当只需单向的数据传输时,可只接一根通信线
当电平标准不一致时,需要加电平转换芯片

图片消失了

电平标准是数据1和数据0的表达方式,是传输线缆中认为定义的电压与数据的对应关系,串口常用的电平标准有以下三种:

  1. TTL:+3.3或5v表示1,0v表示0、
  2. RS232:-3-15v表示1,+3+15v表示0
  3. RS485:两线压差+2+6v表示1,-2-6v表示0(差分信号)

串口参数:

  1. 波特率:串口通信的速率,单位是每秒传输码元的个数,在二进制调制下,一个码元就是一个bit,此时波特率等于比特率
  2. 起始位:标志一个数据帧的开始,固定为低电平
  3. 数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
  4. 校验位:用于数据验证,根据数据位计算得来
  5. 停止位:用于数据帧间隔,固定为高电平

串口发送数据的格式:

图片消失了

右边的是带奇偶校验的
奇校验会检测数据中的1的个数,若为偶数,则校验位为1,若为奇数,则为0
偶检验类似

例子:

图片消失了

S是起始位,固定为0,10101010为数据位,P为停止位,固定为1,没有校验位,所以传输的数据为0xAA

14.USART

USART:通用同步/异步收发器
USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器
自带波特率发生器,最高达4.5Mbit/s,可选校验位,可配置数据位长度,停止位长度
stm32f103c8t6有USART1、2、3

图片消失了

图中的TX和RX就是接收发送引脚
硬件数据流控类似流量控制
SR的TXE、RXNE标志位表示发送寄存器空、接收寄存器非空

图片消失了

USART1挂载在APB2上,也就是PCLK2的时钟,一般是72M,其它USART挂载在APB1,36M
USARTDIV是分频系数,分为整数和小数部分,分频之后还要再除16

图片消失了

数据帧的结构包含9位字长和8位字长的,有校验位和没校验位的
一般推荐9位字长加有校验位和8位字长加没校验位,这样每个帧都是一字节

图片消失了

以波特率的16倍进行采样,从出现下降沿开始,每3位中有2个0则判定为0

图片消失了

代码:

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_RxData;  //定义串口接收的数据变量
uint8_t Serial_RxFlag;  //定义串口接收的标志位变量

/**
  * 函    数:串口初始化
  * 参    数:无
  * 返 回 值:无
  */
void Serial_Init(void)
{
 /*开启时钟*/
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
 
 /*GPIO初始化*/
 GPIO_InitTypeDef GPIO_InitStructure;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(GPIOA, &GPIO_InitStructure);     //将PA9引脚初始化为复用推挽输出
 
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(GPIOA, &GPIO_InitStructure);     //将PA10引脚初始化为上拉输入
 
 /*USART初始化*/
 USART_InitTypeDef USART_InitStructure;     //定义结构体变量
 USART_InitStructure.USART_BaudRate = 9600;    //波特率
 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
 USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
 USART_InitStructure.USART_Parity = USART_Parity_No;  //奇偶校验,不需要
 USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
 USART_InitStructure.USART_WordLength = USART_WordLength_8b;  //字长,选择8位
 USART_Init(USART1, &USART_InitStructure);    //将结构体变量交给USART_Init,配置USART1
 
 /*中断输出配置*/
 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);   //开启串口接收数据的中断
 
 /*NVIC中断分组*/
 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);   //配置NVIC为分组2
 
 /*NVIC配置*/
 NVIC_InitTypeDef NVIC_InitStructure;     //定义结构体变量
 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;  //选择配置NVIC的USART1线
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   //指定NVIC线路使能
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //指定NVIC线路的抢占优先级为1
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //指定NVIC线路的响应优先级为1
 NVIC_Init(&NVIC_InitStructure);       //将结构体变量交给NVIC_Init,配置NVIC外设
 
 /*USART使能*/
 USART_Cmd(USART1, ENABLE);        //使能USART1,串口开始运行
}

/**
  * 函    数:串口发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  */
void Serial_SendByte(uint8_t Byte)
{
 USART_SendData(USART1, Byte);  //将字节数据写入数据寄存器,写入后USART自动生成时序波形
 while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
 /*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}

/**
  * 函    数:串口发送一个数组
  * 参    数:Array 要发送数组的首地址
  * 参    数:Length 要发送数组的长度
  * 返 回 值:无
  */
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
 uint16_t i;
 for (i = 0; i < Length; i ++)  //遍历数组
 {
  Serial_SendByte(Array[i]);  //依次调用Serial_SendByte发送每个字节数据
 }
}

/**
  * 函    数:串口发送一个字符串
  * 参    数:String 要发送字符串的首地址
  * 返 回 值:无
  */
void Serial_SendString(char *String)
{
 uint8_t i;
 for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
 {
  Serial_SendByte(String[i]);  //依次调用Serial_SendByte发送每个字节数据
 }
}

/**
  * 函    数:次方函数(内部使用)
  * 返 回 值:返回值等于X的Y次方
  */
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
 uint32_t Result = 1; //设置结果初值为1
 while (Y --)   //执行Y次
 {
  Result *= X;  //将X累乘到结果
 }
 return Result;
}

/**
  * 函    数:串口发送数字
  * 参    数:Number 要发送的数字,范围:0~4294967295
  * 参    数:Length 要发送数字的长度,范围:0~10
  * 返 回 值:无
  */
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
 uint8_t i;
 for (i = 0; i < Length; i ++)  //根据数字长度遍历数字的每一位
 {
  Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0'); //依次调用Serial_SendByte发送每位数字
 }
}

/**
  * 函    数:使用printf需要重定向的底层函数
  * 参    数:保持原始格式即可,无需变动
  * 返 回 值:保持原始格式即可,无需变动
  */
int fputc(int ch, FILE *f)
{
 Serial_SendByte(ch);   //将printf的底层重定向到自己的发送字节函数
 return ch;
}

/**
  * 函    数:自己封装的prinf函数
  * 参    数:format 格式化字符串
  * 参    数:... 可变的参数列表
  * 返 回 值:无
  */
void Serial_Printf(char *format, ...)
{
 char String[100];    //定义字符数组
 va_list arg;     //定义可变参数列表数据类型的变量arg
 va_start(arg, format);   //从format开始,接收参数列表到arg变量
 vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中
 va_end(arg);     //结束变量arg
 Serial_SendString(String);  //串口发送字符数组(字符串)
}

/**
  * 函    数:获取串口接收标志位
  * 参    数:无
  * 返 回 值:串口接收标志位,范围:0~1,接收到数据后,标志位置1,读取后标志位自动清零
  */
uint8_t Serial_GetRxFlag(void)
{
 if (Serial_RxFlag == 1)   //如果标志位为1
 {
  Serial_RxFlag = 0;
  return 1;     //则返回1,并自动清零标志位
 }
 return 0;      //如果标志位为0,则返回0
}

/**
  * 函    数:获取串口接收的数据
  * 参    数:无
  * 返 回 值:接收的数据,范围:0~255
  */
uint8_t Serial_GetRxData(void)
{
 return Serial_RxData;   //返回接收的数据变量
}

/**
  * 函    数:USART1中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void USART1_IRQHandler(void)
{
 if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)  //判断是否是USART1的接收事件触发的中断
 {
  Serial_RxData = USART_ReceiveData(USART1);    //读取数据寄存器,存放在接收的数据变量
  Serial_RxFlag = 1;          //置接收标志位变量为1
  USART_ClearITPendingBit(USART1, USART_IT_RXNE);   //清除USART1的RXNE标志位
                //读取数据寄存器会自动清除此标志位
                //如果已经读取了数据寄存器,也可以不执行此代码
 }
}

主程序代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

uint8_t RxData;   //定义用于接收串口数据的变量

int main(void)
{
 /*模块初始化*/
 OLED_Init();  //OLED初始化
 
 /*显示静态字符串*/
 OLED_ShowString(1, 1, "RxData:");
 
 /*串口初始化*/
 Serial_Init();  //串口初始化
 
 while (1)
 {
  if (Serial_GetRxFlag() == 1)   //检查串口接收数据的标志位
  {
   RxData = Serial_GetRxData();  //获取串口接收的数据
   Serial_SendByte(RxData);   //串口将收到的数据回传回去,用于测试
   OLED_ShowHexNum(1, 8, RxData, 2); //显示串口接收的数据
  }
 }
}


http://www.niftyadmin.cn/n/5865289.html

相关文章

Linux提权之sudo提权(十四)

sudo 是一种权限管理机制&#xff0c;管理员可以授权于一些普通用户去执行一些 root 执行的操作&#xff0c;而不需要知道 root 的密码。 首先通过信息收集&#xff0c;查看是否存在sudo配置不当的可能。如果存在&#xff0c;寻找低权限sudo用户的密码&#xff0c;进而提权。 …

开源项目austin学习day01

尝试本地运行项目&#xff0c;遇到如下几个报错&#xff1a; 1.om.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server. Attempted reconnect 3 times. Giving up. 问题&#xff1a;jdbc版本与数据库版本不…

游戏引擎学习第120天

仓库:https://gitee.com/mrxiao_com/2d_game_3 上次回顾&#xff1a;周期计数代码 我们正在进行一个项目的代码优化工作&#xff0c;目标是提高性能。当前正在优化某个特定的代码片段&#xff0c;已经将其执行周期减少到48个周期。为了实现这一目标&#xff0c;我们设计了一个…

Word(2010)排版技巧

设置标题样式 选择需要设置的标题 如下图所示。选择文字后&#xff0c;点击对应的样式即可设置。 设置标题格式 设置字体格式 设置段落格式 显示所有样式 标题样式展示 建议 建议新建一个正文样式&#xff0c;可以命名为正文1&#xff0c;因为所有的样式参考的“样式基准…

同步降压DC/DC高效率高精度可调输出电压转换器宽电压输入7-30v高连续输出电流高达15A

WD5030芯片核心内容概述 WD5030是一款高效率的单片同步降压型DC/DC转换器&#xff0c;该芯片采用抖动频率、平均电流模式控制架构&#xff0c;具有出色的线路和负载调节能力&#xff0c;能够提供高达15A的连续负载电流。它适用于可充电便携式设备、网络系统和分布式电源系统等…

初识.git文件泄露

.git 文件泄露 当在一个空目录执行 git init 时&#xff0c;Git 会创建一个 .git 目录。 这个目录包含所有的 Git 存储和操作的对象。 如果想备份或复制一个版本库&#xff0c;只需把这个目录拷贝至另一处就可以了 这是一种常见的安全漏洞&#xff0c;指的是网站的 .git 目录…

自驾游拼团小程序的设计与实现(ssm论文源码调试讲解)

第4章 系统设计 4.1系统功能结构设计 本系统采用前台用户、发起人操作&#xff0c;后台管理员操作的方式进行设计&#xff0c;用户在前台需要注册登录&#xff0c;注册登录后可以浏览资讯信息、旅游拼团信息、旅游景点信息&#xff0c;然后参团和发布帖子等。管理员负责前台信…

力扣hot100——岛屿数量 岛屿问题经典dfs总结

给你一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的的二维网格&#xff0c;请你计算网格中岛屿的数量。 岛屿总是被水包围&#xff0c;并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外&#xff0c;你可以假设该网格的四条边…