RCC: reset and clock control 复位和时钟控制
配置系统时钟,开启/关闭各个外设的时钟电源。
一般首要操作就是通过RCC去打开相应外设的时钟,比如:
1
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
|
通过RCC打开APB2总线外设的GPIOC的时钟,使他开启(ENABLE)。
GPIO_InitTypeDef 结构体
对GPIO进行设置/初始化之前,要先定义一个结构体,这个模板里规定了:如果你想配置 GPIO,你必须提供 模式、速度 和 引脚号 这三样东西。
1 2 3 4
| GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Mode=GPIO_Mode_PP; GPIO_InitStruct.GPIO_Pin=GPIO_Pin_13; GPIO_InitStryct.GPIO_Speed=GPIO_Speed_50MHz;
|
然后再去对GPIO进行配置
1
| GPIO_InitStryct.GPIO_Speed=GPIO_Speed_50MHz;
|
GPIO_SetBits/GPIO_ResetBits
1 2 3 4
| GPIO_SetBits(GPIO_TypeDef* GPIOX , GPIO_Pin);
GPIO_ResetBits(GPIO_TypeDef* GPIOX , GPIO_Pin);
|
GPIO_WriteBits
1 2 3
| GPIO_WriteBits(GPIO_TypeDef* GPIOX , GPIO_Pin , BitVal);
|
同时初始化多个GPIO要用或(|)而不是与(&)
1
| GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1 | GPIO_Pin_2;
|
因为:
1 2 3 4 5
| 0000 0000 0000 0001 (Pin_0) | 0000 0000 0000 0010 (Pin_1) | 0000 0000 0000 0100 (Pin_2) ----------------------- = 0000 0000 0000 0111 (结果是 0x0007)
|
这是计算过程,如果是与的话,就是:
1 2 3 4 5
| 0000 0000 0000 0001 (Pin_0) & 0000 0000 0000 0010 (Pin_1) & 0000 0000 0000 0100 (Pin_2) ----------------------- = 0000 0000 0000 0000 (结果是 0x0000)
|
因为这三个数的 1 都在不同的位置上,没有任何重叠,所以按位与的结果是 全 0,相当于啥也没干。
推挽输出/开漏输出驱动GPIO
推挽输出:
1
| GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
|
这使得GPIO输出高低电平都有驱动能力。
开漏输出:
1
| GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_OD;
|
这使得GPIO输出高电平时没有驱动能力,
输出低电平时才有驱动能力。
上拉输入
1
| GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;
|
上拉输入模式,默认保持高电平。
接一个开关按钮,一端接GND,一端接GPIO端口。
输入寄存器默认高电平,也就是默认1(因为是上拉输入模式),如果通过GPIO读取函数读到寄存器的某一位为0,意味着按钮接通了,按钮的一端GND会导致GPIO端口被输入强下拉信号,也就是低电平,因此当检测到输入寄存器的电平为0时,说明按钮被按下。
1 2 3
| if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0){ }
|
按键封装函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void key_Init(){ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50Hz; GPIO_Init(GPIOB,&GPIO_InitStructure); }
uint8_t Key_GetNum(void){ uint8_t KeyNum=0; if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0){ Delay(30); while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0){} Delay(30); KeyNum=1; } return KeyNum; }
|
GPIO初始化固定模版
1 2 3 4 5 6
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; GPIO_Init(GPIOB, &GPIO_InitStructure);
|
GPIO读取函数
GPIO作为输入引脚
1 2
| GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15)
|
示例应用(eg:通过读取光敏电阻输入的高低电平来控制蜂鸣器)
1 2 3 4
| if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15)==1) { GPIO_ResetBits(GPIOB, GPIO_Pin_12); }
|
GPIO作为输出引脚
1 2
| GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_2)
|
示例应用(eg:摁钮控制LED灯,翻转他的电平,让他亮或灭)
1 2 3 4 5 6 7 8 9
| if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15)==0) { Delay(50); while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15)==0){} if(GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_2)==1) GPIO_ResetBits(GPIOB,GPIO_Pin_2); else{ GPIO_SetBits(GPIOB,GPIO_Pin_2); } }
|
中断配置

GPIO——AFIO——EXTI——NVIC
模块化编程,单开一个文件,简历库函数和中断函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| void countsenser_init() { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_StructInit(&GPIO_InitStructure); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource3); EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line3; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); void EXTI3_IRQHandler() { if (EXTI_GetITStatus(EXTI_Line3) != RESET) { } EXTI_ClearITPendingBit(EXTI_Line3); }
|
定时器配置中断

按照上图顺序从左到右依次配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| void Timer_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period = 10000-1; TIM_TimeBaseStructure.TIM_Prescaler = 7200-1; TIM_TimeBaseStructure.TIM_RepetitionCounter=0; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_ClearFlag(TIM2, TIM_IT_Update); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM_Cmd(TIM2, ENABLE); }
void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }
|
上方是使用内部时钟模式
下方是使用ETR外部触发输入来计数。
也就是把:
TIM_InternalClockConfig(TIM2);
换成:
TIM_ETRClockMode2Config();
具体如下:
1
| TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x03);
|
四个参数:
TIM2:选择定时器2
TIM_ExtTRGPSC_OFF:不分频,外部触发一次,计数一次。
TIM_ExtTRGPolarity_NonInverted:上升沿或高电平触发。
0x03:信号滤波,一般设0x03就行。
1 2
| TIM_TimeBaseStructure.TIM_Period = 10-1; TIM_TimeBaseStructure.TIM_Prescaler = 1-1;
|
TIM_Period = 10-1,意味着计数器记到10就触发中断。
TIM_Prescaler = 1-1,意味着外部触发信号来一次,计数器就记一次。而上一个代码用内部时钟计数时,为啥要写7200-1,是因为内部时钟触发信号相当于一秒来72,000,000次,所以你必须先分频7200次,相当于每秒来10000次,而同时TIM_Period设置为10000-1,即计数器加到10000才触发一次中断,这样就实现了一秒触发一次中断。
PWM实现呼吸灯
PWM主要靠定时器,不需要中断,所以不用配置NVIC,EXTI等等,需要配置定时器和GPIO。

PWM初始化函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| void PWM_Init() { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2); TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period = 100-1; TIM_TimeBaseStructure.TIM_Prescaler = 720-1; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_RepetitionCounter=0; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_OCInitTypeDef TIM_OCInitStructure; TIM_OCStructInit(&TIM_OCInitStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse= 0; TIM_OC1Init(TIM2,&TIM_OCInitStructure); TIM_OC2Init(TIM2,&TIM_OCInitStructure); TIM_OC3Init(TIM2,&TIM_OCInitStructure); TIM_OC4Init(TIM2,&TIM_OCInitStructure);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_Cmd(TIM2, ENABLE); }
void PWM_SetCompare1(uint16_t Compare) { TIM_SetCompare1(TIM2, Compare); }
void PWM_SetPSC(uint16_t PSC) { TIM_PrescalerConfig(TIM2,PSC,TIM_PSCReloadMode_Immediate); }
|
到这里实现了LED灯以百分之20的功率输出。但我们要实现呼吸灯的话,还要实时调整CCR的值,让他在0-100之间均匀的变化。
有一个库函数可以设置/写入CCR的值,TIM_SetCompare4(TIM2,X);,这个函数可以实时指定输出比较单元的CCR。
1 2 3 4 5
| TIM_SetCompare1(TIM2,X); TIM_SetCompare2(TIM2,X); TIM_SetCompare3(TIM2,X); TIM_SetCompare4(TIM2,X);
|
因此可以把TIM_SetCompare4(TIM2,X);放到主程序的for循环中,这样就可以实时增加/减少PWM的占空比,从而实现呼吸灯。
具体如下:
1 2 3 4 5 6 7 8 9 10 11
| void main(){ for(i=0;i<100;i++) { TIM_SetCompare4(TIM2,i); } for(j=0;j<100;j++) { TIM_SetCompare4(TIM2,100-i); } }
|
引脚重映射(暂时了解)
针对TIM2的引脚重映射

!注:重映射的配置寄存器位于 AFIO 模块内:重映射的配置信息(比如把 TIM2_CH1 从 PA0 重映射到 PA15)是存储在 AFIO 模块内部的寄存器(如 AFIO_MAPR)中的。
1 2 3
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
|
PWM控制直流电机转速(不包括方向)
主要是C语言逻辑这一块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| uint8_t i; uint8_t j=30; while (1) { i=Key_GetNum(); if (i==1) { j-=10; GPIO_SetBits(GPIOA, GPIO_Pin_4); GPIO_ResetBits(GPIOA, GPIO_Pin_5); if (j==0) { j=30; } PWM_SetCompare3(j); } OLED_ShowSignedNum(1, 7, j, 3); }
|
PWMI模版
输入捕获部分,任何从外部输入的信号想要进入定时器,都需要配置TIM_ICInit。


先配置GPIO,然后时基单元(TIM),然后输入捕获单元(IC)初始化(包括选择通道,滤波器,边沿检测,极性选择,分频器),然后触发源选择,从模式选择,启动定时器。
计数值存在CCR1中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| void IC_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;; GPIO_Init(GPIOA, &GPIO_InitStructure); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_InternalClockConfig(TIM3); TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Prescaler = 72-1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period = 65536-1; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICFilter=0x03; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInit(TIM3, &TIM_ICInitStructure); TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset); TIM_Cmd(TIM3, ENABLE); } uint16_t IC_GetFreq(){ return 1000000/TIM_GetCapture1(TIM3); }
|
有一个函数可以实时读取CCR的值,就是TIM_GetCapture1(TIMX);结果会返回一个uint16_t的整型常量。
1 2 3 4 5
| TIM_GetCapture1(TIM3); TIM_GetCapture2(TIM3); TIM_GetCapture3(TIM3); TIM_GetCapture4(TIM3);
|
PWMI双通道
前面GPIO,TIM基本不变,主要是输入捕获部分要多设置一个通道。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICFilter=0x03; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM_ICInitStructure.TIM_ICFilter=0x03; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset); TIM_Cmd(TIM3, ENABLE);
|
获取频率和占空比的函数封装
1 2 3 4 5 6 7
| uint16_t IC_GetFreq() { return 1000000/TIM_GetCapture1(TIM3); } uint16_t IC_GetDuty() { return TIM_GetCapture2(TIM3)*100/TIM_GetCapture1(TIM3); }
|
双通道测频率和占空比原理
对于上图用来捕获频率和PWM占空比。输入捕获部分设置了两路通道,一路上升沿触发,一路下降沿,而触发源则特定选择了通道一,来触发从模式Reset(计数清零)。所以每次上升沿来了,计数都会清零。但清零前,STM32硬件电路会把计数器CNT的值存到CCR1中,这是硬件电路自己完成的,不需要软件操作。这样我们就通过CNT的计数,知道了一个周期的时间。而通道二下降沿来临,计数器CNT的值会计入到CCR2中。这样就通过CCR2的计数知道了高电平占总周期的比例,即占空比。
定时器的编码器接口及其库函数

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| void Encoder_Interface_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; GPIO_Init(GPIOA, &GPIO_InitStructure); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Prescaler = 1-1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period = 65536-1; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICStructInit(&TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICFilter=0x0A; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInit(TIM3, &TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICFilter=0x0A; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInit(TIM3, &TIM_ICInitStructure); TIM_EncoderInterfaceConfig(TIM3,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising); TIM_Cmd(TIM3, ENABLE); }
int16_t GetCounter(void) { int16_t Temp =0; Temp=TIM_GetCounter(TIM3); TIM_SetCounter(TIM3,0); return Temp; }
void TIM2_IRQHandler() { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { counter=TIM_GetCounter(TIM3); TIM_SetCounter(TIM3,0); TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }
|
ADC

DMA工作的三个条件:
- 传输计数器不为0。
- DMA使能。
- 触发源(软件触发/硬件触发)有信号。
单通道,非连续转换,非扫描模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| void AD_Init(){ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_28Cycles5 ); ADC_InitTypeDef ADC_InitStruct; ADC_InitStruct.ADC_Mode=ADC_Mode_Independent; ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStruct.ADC_ScanConvMode = DISABLE; ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; ADC_InitStruct.ADC_NbrOfChannel=1; ADC_Init(ADC1, &ADC_InitStruct); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)==SET); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)==SET); }
uint16_t AD_GetValue(void){ ADC_SoftwareStartConvCmd(ADC1,ENABLE); while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET){} return ADC_GetConversionValue(ADC1); }
|
单通道,连续转换,非扫描模式
把上述代码AD_Init函数中的结构体配置中ADC_ContinuousConvMode由DISABLE修改为ENABLE。
并且自始至终只需要一次软件触发开启AD转换,因此直接把这个指令塞进初始化函数里即可。
1 2
| ADC_InitStruct.ADC_ContinuousConvMode = ENABLE; ADC_SoftwareStartConvCmd(ADC1,ENABLE);
|
而下面的uint16_t AD_GetValue(void)函数只需要return ADC_GetConversionValue(ADC1);即可,不需要判断EOC标志位是否被置位,因为全程连续转换,并不是单次转换。
1 2 3 4
| uint16_t AD_GetValue(void){ return ADC_GetConversionValue(ADC1); }
|
单通道模式实现多通道模式,非连续转换,非扫描模式(不用硬件电路,而是用代码实现)
在AD_Init函数中配置通道的函数:ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_28Cycles5 );把第二个参数ADC_Channel_1变成一个变量,然后在循环中不断输入不同的通道,因为每次转换都在几微秒内完成,因此在人眼来看,就是4个通道同时进行。
具体实现,先把ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_28Cycles5 );拿出来,然后封装一个需要输入形参的函数,然后把形参输入到第二个变量位置,即可实现。
1 2 3 4 5 6 7
| uint16_t AD_GetValue(uint8_t ADC_Channel) { ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_28Cycles5 ); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)==RESET){} return ADC_GetConversionValue(ADC1); }
|
然后在主函数的循环中,写入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| while (1) { AD1=AD_GetValue(ADC_Channel_1); AD2=AD_GetValue(ADC_Channel_2); AD3=AD_GetValue(ADC_Channel_3); AD4=AD_GetValue(ADC_Channel_4); OLED_ShowString(1,1,"AD1:"); OLED_ShowString(2,1,"AD2:"); OLED_ShowString(3,1,"AD3:"); OLED_ShowString(4,1,"AD4:"); OLED_ShowNum(1,5,AD1,5); OLED_ShowNum(2,5,AD2,5); OLED_ShowNum(3,5,AD3,5); OLED_ShowNum(4,5,AD4,5); }
|
表示变量地址

在stm32中,所有的地址都是16进制的,也就是0x……。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const uint8_t a=66; uint8_t b=77; int main(void) { OLED_Init(); while (1) { OLED_ShowNum(1,1,a,3); OLED_ShowHexNum(2,1,(uint32_t)&a,8); OLED_ShowNum(3,1,b,3); OLED_ShowHexNum(4,1,(uint32_t)&b,8); } }
|
现象如下:

由上图结合代码可知,在变量a前加入const,那么a就被存储到了Flash中,成为了常量。而b作为uint8_t的变量,则被存储到了SRAM中。
所以如果有一个很大的表或者数据库,一般不需要修改,那就在前面加上const,让他存放在FLash中,而非SRAM中。
ADC1->DR 是“去那个地址把数据拿回来”
&ADC1->DR 才是“那个地址本身”。
DMA转运数据

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| uint8_t Transfer_Size; void My_DMAInit(uint32_t AddrA , uint32_t AddrB , uint8_t Size) { Transfer_Size=Size; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; DMA_InitStructure.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_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_High; DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, DISABLE); }
void DMA_Transfer(void) { DMA_Cmd(DMA1_Channel1, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel1, Transfer_Size); DMA_Cmd(DMA1_Channel1, ENABLE); while (DMA_GetFlagStatus(DMA1_FLAG_TC1==RESET)); DMA_ClearFlag(DMA1_FLAG_TC1); }
|
ADC扫描模式+DMA
串口USART通信
配置流程图如下:

TX设置复用输出,RX设置输入。图片中间部分全部通过结构体配置。
拆分数字的思路
12345/10000%10=1
12345/1000%10=2
12345/100%10=3
12345/10%10=4
12345/1%10=5
想取出某个数的第x位,就把这个数除以(10的(x-1)次方),再对10取余。