自主学习STM32已有一周,先实现一个小demo,算是给自己一个动力叭,有目标的学习收获会更多。虽然本科也修了嵌入式课程,但那种走马观花式的学习,最后真正得到的知识实在寥寥无几。个人理解,学习STM32不只是学习编程,更多的是学习查资料、查数据手册、软件的使用和调试方法上,真正需要自己从头造的部分不是很多,吸取前人的经验,搬过来取自己所需即可。用农夫山泉的话来说就是,我们不生产代码,我们只是代码的搬运工!
这次主要跟着正点原子的开发资料进行学习,没有使用战舰开发板,而是使用STM32F103C8T6板子。一是避免自己直接把例程的代码烧进开发板,最后啥也没学到,在不同的板子间移植代码过程中,能够掌握理解更多的基础知识和调试经验;二是这个小板子廉价易得,只要十块钱,和大几百的开发板相比,它体积小、资源可观,很适合我的小项目,以后准备用来制作航模遥控器,敬请关注哈~
1.材料清单
1.STM32F103C8T6蓝色开发板*1(黑色板也可以)
2.USB转TTL模块*1
3. ST-LINK V2仿真器下载器*1(调试STM32性价比极高)
4. OLED屏幕(4管脚)*1
5.10k电位器*1(10k以上都可)
6. 杜邦线、面包板、导线、插针若干
2.电路连接
电位器:GND - PA0 - 3.3V
OLED显示屏:
GND 电源地
VCC 接3.3v电源
SCL 接PB8(SCL)
SDA 接PB9(SDA)ST-LINK V2接法:
GND 电源地
3V3 接3.3v
SWCLK 接DCLK
SWDIO 接DIO
串口USB-TTL接法:
GND 电源地
3V3 接3.3v
TXD 接PB7
RXD 接PB6
PWM输出:PB5
实物连接图如下:
3.安装keil5
安装及破解MDK(Keil5)教程 https://blog.csdn.net/weixin_42911200/article/details/81590158
注意要安装Keil.STM32F1xx_DFP.2.3.0.pack支持包,因为我们要用STM32F103C8T6芯片的库函数编写。
4.新建工程
新建keil库函数工程 https://www.cnblogs.com/zeng-1995/p/11308622.html
与链接里面不同的是以下几个设置:
点击图标按钮1,打开Manage Run-Time Environment窗口,Device如下勾选,其他栏与链接中相同;
点击图标按钮2,打开Manage Project Items窗口,Groups和 Files如下设置:
点击图标按钮3,打开Options for Target窗口,点击顶部菜单按钮切换子窗口,依次如下设置:
点击Setting,打开Cortex-M Target Driver Setup窗口,如果SWDIO里面未显示序列号,则电脑需要更新ST-LINK驱动。
解决方法见链接 https://blog.csdn.net/qq_42041980/article/details/92015997
5.程序实现
控制舵机的PWM:周期20ms,高电平时间0.5ms~2.5ms变化,可控制舵机0~180°的角度变化,即每个高电平时间都对应舵机的一个角度。但航模舵面的实际控制中,不可能有180°变化,所以通用的高电平宽度其实是1ms~2ms。
控制无刷电调所用的PWM信号高电平时间也是1ms~2ms,所以我们要实现的PWM信号周期20ms,高电平时间1ms~2ms。
我们使用ADC1读取电位器的电压采样值,并从0~4095范围的采样值转换到1000~2000,赋值给PWM输出。
TIM2定时触发ADC采样,通过DMA传输给变量所在的寄存器,取10次进行均值滤波,消除抖动。
定时器触发ADC,DMA传输 http://www.openedv.com/forum.php?mod=viewthread&tid=277863&extra=&page=1
定时器TIM触发ADC采样,DMA搬运到内存 https://blog.csdn.net/qq_38410730/article/details/89921413
TIM3定时触发产生PWM信号,预分频72,频率1MHz,周期1us;自动装载值20 000,故PWM周期1us*20 000=20ms。
主要代码如下:
main.c文件-包含程序说明、主函数
-
/*
-
=============舵机测试仪==============
-
芯片STM32F103C8T6,使用ADC读取电位器的电压采样值,0~4095转换到1000~2000,赋值给PWM输出。
-
TIM2定时触发ADC采样,通过DMA传输给变量ch1Value,取10次进行均值滤波。
-
控制舵机的PWM:周期20ms,高电平时间0.5ms~2.5ms变化,可控制舵机0~180°的角度变化,
-
但航模舵面的实际控制中,不可能有180°变化,所以通用的高电平宽度其实是1ms~2ms
-
电位器:GND - PA0 - 3.3V
-
OLED显示屏:
-
GND 电源地
-
VCC 接3.3v电源
-
SCL 接PB8(SCL)
-
SDA 接PB9(SDA)
-
串口USB-TTL接法:
-
GND 电源地
-
3V3 接3.3v
-
TXD 接PB7
-
RXD 接PB6
-
ST-LINK V2接法:
-
GND 电源地
-
3V3 接3.3v
-
SWCLK 接DCLK
-
SWDIO 接DIO
-
PWM输出:PB5
-
by Bilibili 蔡子CaiZi
-
*/
-
#include "config.h"
-
#include "delay.h"
-
#include "usart.h"
-
#include "stm32f10x.h"
-
#include "oled.h"
-
#include "rtc.h"
-
#include "stdio.h"
-
#include "string.h"
-
-
int main()
-
{
-
u8 txt[
16]={
0};
-
delay_init();
//初始化延时函数
-
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//设置NVIC中断分组2,2位抢占优先级和2位子优先级
-
usart_init(
115200);
//初始化串口1,波特率为115200
-
TIM3_PWM_Init(
19999,
71);
//预分频72,频率1MHz,周期1us;自动装载值20 000,故PWM周期1us*20 000
-
TIM2_Init(
499,
71);
//1MHz,每500us采集一次;可设置9us以上,但过小影响OLED显示
-
DMA1_Init();
//DMA初始化
-
GPIOA_Init();
//PA初始化
-
Adc_Init();
//ADC初始化
-
RTC_Init();
//RTC初始化
-
OLED_Init();
//初始化OLED
-
OLED_Clear();
-
-
while (
1){
-
itoa(PWM1value,txt,
10);
//将int类型转换成10进制字符串
-
// printf("采样值:%d\t舵量:%s\t",ch1Value,txt);
-
// printf("当前时间:%d:%d:%d\n",calendar.hour,calendar.min,calendar.sec);
-
//OLED_Clear();//一直清屏会造成闪烁
-
strcat(txt,
" us");
//合并字符串
-
OLED_ShowString(
6,
3,txt,
24);
//位置6,3;字符大小24*24点阵
-
OLED_Refresh_Gram();
-
delay_ms(
1);
-
}
-
}
config.c文件-包含TIM/ GPIO/ ADC等初始化函数
-
#include "config.h"
-
#include "delay.h"
-
#include "usart.h"
-
#include "sys.h"
-
#include "rtc.h"
-
volatile u16 ch1Value[
10];
//ADC采样值
-
volatile u16 PWM1value;
//控制PWM占空比
-
#define ADC1_DR_Address ((u32)0x4001244C) //ADC1的地址
-
//通用定时器2中断初始化
-
//这里时钟选择为APB1的2倍,而APB1为36M
-
//arr:自动重装值。
-
//psc:时钟预分频数
-
//这里使用的是定时器2控制ADC定时采样
-
void TIM2_Init(u16 arr,u16 psc)
-
{
-
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
-
TIM_OCInitTypeDef TIM_OCInitStructure;
-
-
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//时钟使能
-
-
//定时器TIM2初始化
-
TIM_TimeBaseStructure.TIM_Period = arr;
//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
-
TIM_TimeBaseStructure.TIM_Prescaler =psc;
//设置用来作为TIMx时钟频率除数的预分频值
-
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//设置时钟分割:TDTS = Tck_tim
-
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
//TIM向上计数模式
-
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
//根据指定的参数初始化TIMx的时间基数单位
-
-
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
//选择定时器模式:TIM脉冲宽度调制模式1
-
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//比较输出使能
-
TIM_OCInitStructure.TIM_Pulse =
9;
//计数达到9产生中断
-
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
//输出极性:TIM输出比较极性低
-
TIM_OC2Init(TIM2, & TIM_OCInitStructure);
//初始化外设TIM2_CH2
-
-
TIM_Cmd(TIM2, ENABLE);
//使能TIMx
-
TIM_CtrlPWMOutputs(TIM2, ENABLE);
-
}
-
-
-
//DMA1配置
-
void DMA1_Init(void)
-
{
-
DMA_InitTypeDef DMA_InitStructure;
-
NVIC_InitTypeDef NVIC_InitStructure;
-
-
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
//使能ADC1通道时钟
-
-
//DMA1初始化
-
DMA_DeInit(DMA1_Channel1);
-
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
//ADC1地址
-
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ch1Value;
//ch1Value的内存地址
-
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
//方向(从外设到内存)
-
DMA_InitStructure.DMA_BufferSize =
10;
//DMA缓存大小,存放10次采样值
-
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//外设地址固定,接收一次数据后,设备地址禁止后移
-
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
//内存地址不固定,接收多次数据后,目标内存地址后移
-
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord ;
//外设数据单位,定义外设数据宽度为16位
-
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord ;
//内存数据单位,HalfWord就是为16位
-
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular ;
//DMA模式:循环传输
-
DMA_InitStructure.DMA_Priority = DMA_Priority_High ;
//DMA优先级:高
-
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
//禁止内存到内存的传输
-
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
//配置DMA1
-
-
DMA_ITConfig(DMA1_Channel1,DMA_IT_TC, ENABLE);
//使能传输完成中断
-
-
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
-
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =
0;
-
NVIC_InitStructure.NVIC_IRQChannelSubPriority =
1;
-
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
-
NVIC_Init(&NVIC_InitStructure);
-
-
DMA_Cmd(DMA1_Channel1,ENABLE);
-
}
-
-
//中断处理函数
-
void DMA1_Channel1_IRQHandler(void)
-
{
-
int sum=
0;
-
if(DMA_GetITStatus(DMA1_IT_TC1)!=RESET){
-
//中断处理代码
-
for(
int i=
0;i<
10;i++){
-
sum += ch1Value[i];
-
}
//均值滤波
-
PWM1value = (
int)map(sum/
10,
0,
4092,
1000,
2000);
-
sum=
0;
-
printf(
"%d\t",PWM1value);
-
printf(
"当前时间:%d:%d:%d\r\n",calendar.hour,calendar.min,calendar.sec);
-
TIM_SetCompare2(TIM3,PWM1value);
//输出给PWM
-
DMA_ClearITPendingBit(DMA1_IT_TC1);
//清除标志
-
}
-
}
-
//GPIO配置,PA0
-
void GPIOA_Init(void)
-
{
-
GPIO_InitTypeDef GPIO_InitStructure;
-
-
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//使能GPIOA时钟
-
-
//PA6 作为模拟通道输入引脚
-
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
-
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
-
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
-
GPIO_Init(GPIOA, &GPIO_InitStructure);
-
}
-
-
//初始化ADC
-
//这里我们仅以规则通道为例
-
//我们默认将开启通道0~3
-
void Adc_Init(void)
-
{
-
ADC_InitTypeDef ADC_InitStructure;
-
-
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
//使能ADC1通道时钟
-
-
//ADC1初始化
-
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
//独立ADC模式
-
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
//关闭扫描方式
-
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
//关闭连续转换模式
-
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2;
//使用外部触发模式
-
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
//采集数据右对齐
-
ADC_InitStructure.ADC_NbrOfChannel =
1;
//要转换的通道数目
-
ADC_Init(ADC1, &ADC_InitStructure);
-
-
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//配置ADC时钟,为PCLK2的6分频,即12MHz
-
ADC_RegularChannelConfig(ADC1, ADC_Channel_0,
1, ADC_SampleTime_239Cycles5);
//配置ADC1通道0为239.5个采样周期
-
-
//使能ADC、DMA
-
ADC_DMACmd(ADC1,ENABLE);
-
ADC_Cmd(ADC1,ENABLE);
-
-
ADC_ResetCalibration(ADC1);
//复位校准寄存器
-
while(ADC_GetResetCalibrationStatus(ADC1));
//等待校准寄存器复位完成
-
-
ADC_StartCalibration(ADC1);
//ADC校准
-
while(ADC_GetCalibrationStatus(ADC1));
//等待校准完成
-
-
ADC_ExternalTrigConvCmd(ADC1, ENABLE);
//设置外部触发模式使能
-
}
-
-
//获得ADC值
-
//ch:通道值 0~9
-
u16 Get_Adc(u8 ch)
-
{
-
//设置指定ADC的规则组通道,一个序列,采样时间
-
ADC_RegularChannelConfig(ADC1, ch,
1, ADC_SampleTime_239Cycles5 );
//ADC1,ADC通道,采样时间为239.5个周期
-
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
//使能指定的ADC1的软件转换启动功能
-
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
//等待转换结束
-
return ADC_GetConversionValue(ADC1);
//返回最近一次ADC1规则组的转换结果
-
}
-
-
//ch:通道值 0~9,采样times次后作均值滤波
-
u16 Get_Adc_Average(u8 ch,u8 times)
-
{
-
u32 temp_val=
0;
-
u8 t;
-
for(t=
0;t<times;t++)
-
{
-
temp_val+=Get_Adc(ch);
-
delay_ms(
5);
-
}
-
return temp_val/times;
-
}
-
//TIM3 PWM部分初始化
-
//PWM输出初始化
-
//arr:自动重装值
-
//psc:时钟预分频数
-
void TIM3_PWM_Init(u16 arr,u16 psc)
-
{
-
GPIO_InitTypeDef GPIO_InitStructure;
-
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
-
TIM_OCInitTypeDef TIM_OCInitStructure;
-
-
-
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
//使能定时器3时钟
-
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
//使能GPIO外设和AFIO复用功能模块时钟
-
-
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
//Timer3部分重映射 TIM3_CH2->PB5
-
-
//设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形 GPIOB.5
-
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
//TIM_CH2
-
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//推挽输出
-
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
-
GPIO_Init(GPIOB, &GPIO_InitStructure);
//初始化GPIOB
-
-
//初始化TIM3
-
TIM_TimeBaseStructure.TIM_Period = arr;
//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
-
TIM_TimeBaseStructure.TIM_Prescaler =psc;
//设置用来作为TIMx时钟频率除数的预分频值
-
TIM_TimeBaseStructure.TIM_ClockDivision =
0;
//设置时钟分割:TDTS = Tck_tim
-
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
//TIM向上计数模式
-
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
-
-
//初始化TIM3 Channel2 PWM模式
-
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
//选择定时器模式:TIM脉冲宽度调制模式1,计数值<自动重装载值时,输出高电平
-
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//比较输出使能
-
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//输出极性:TIM输出比较极性高
-
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
//根据T指定的参数初始化外设TIM3 OC2
-
-
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
//使能TIM3在CCR2上的预装载寄存器
-
-
TIM_Cmd(TIM3, ENABLE);
//使能TIM3
-
}
-
-
-
/*函数说明:仿Arduino,将一个数字从一个范围重新映射到另一个范围
-
也就是说,fromLow的值将映射到toLow,fromlhigh到toHigh的值等等。
-
*/
-
float map(float value,float fromLow,float fromHigh,float toLow,float toHigh)
-
{
-
return ((
value-fromLow)*(toHigh-toLow)/(fromHigh-fromLow)+toLow);
-
}
-
config.h-包含函数预定义和全局变量预定义
-
#ifndef __CONFIG_H
-
#define __CONFIG_H
-
#include "stm32f10x.h" //记得添加此头文件,因为config.c用到GPIO相关函数等
-
#include "sys.h"
-
extern
volatile u16 ch1Value[
10];
//ADC采样值
-
extern
volatile u16 PWM1value;
//控制PWM占空比
-
void TIM2_Init(u16 arr,u16 psc);
//TIM2定时器初始化
-
void TIM3_PWM_Init(u16 arr,u16 psc);
//PB5定时器初始化
-
void DMA1_Init(void);
-
void GPIOA_Init(void);
-
void Adc_Init(void);
//ADC1初始化
-
u16 Get_Adc(u8 ch);
//获取一次ADC的值
-
u16 Get_Adc_Average(u8 ch,u8 times);
//ADC采样值进行均值滤波
-
float map(float value,float fromLow,float fromHigh,float toLow,float toHigh);
//映射函数
-
#endif
oled.c-包含各种显示函数和IIC初始化
-
//////////////////////////////////////////////////////////////////////////////////
-
// 功能描述 : 0.69寸OLED 接口演示例程(STM32F103C8T6 IIC)
-
// 说明:
-
// ----------------------------------------------------------------
-
// GND 电源地
-
// VCC 接3.3v电源
-
// SCL 接PB8(SCL)
-
// SDA 接PB9(SDA)
-
//////////////////////////////////////////////////////////////////////////////////?
-
-
#include "oled.h"
-
#include "stdlib.h"
-
#include "oledfont.h"
-
#include "delay.h"
-
//OLED的显存
-
//存放格式如下.
-
//[0]0 1 2 3 ... 127
-
//[1]0 1 2 3 ... 127
-
//[2]0 1 2 3 ... 127
-
//[3]0 1 2 3 ... 127
-
//[4]0 1 2 3 ... 127
-
//[5]0 1 2 3 ... 127
-
//[6]0 1 2 3 ... 127
-
//[7]0 1 2 3 ... 127
-
/**********************************************
-
//IIC Start
-
**********************************************/
-
void IIC_Start(void)
-
{
-
-
OLED_SCLK_Set() ;
-
OLED_SDIN_Set();
-
OLED_SDIN_Clr();
-
OLED_SCLK_Clr();
-
}
-
-
/**********************************************
-
//IIC Stop
-
**********************************************/
-
void IIC_Stop(void)
-
{
-
OLED_SCLK_Set() ;
-
// OLED_SCLK_Clr();
-
OLED_SDIN_Clr();
-
OLED_SDIN_Set();
-
-
}
-
-
void IIC_Wait_Ack(void)
-
{
-
OLED_SCLK_Set() ;
-
OLED_SCLK_Clr();
-
}
-
/**********************************************
-
// IIC Write byte
-
**********************************************/
-
-
void Write_IIC_Byte(unsigned char IIC_Byte)
-
{
-
unsigned
char i;
-
unsigned
char m,da;
-
da=IIC_Byte;
-
OLED_SCLK_Clr();
-
for(i=
0;i<
8;i++)
-
{
-
m=da;
-
// OLED_SCLK_Clr();
-
m=m&
0x80;
-
if(m==
0x80)
-
{OLED_SDIN_Set();}
-
else OLED_SDIN_Clr();
-
da=da<<
1;
-
OLED_SCLK_Set();
-
OLED_SCLK_Clr();
-
}
-
-
-
}
-
/**********************************************
-
// IIC Write Command
-
**********************************************/
-
void Write_IIC_Command(unsigned char IIC_Command)
-
{
-
IIC_Start();
-
Write_IIC_Byte(
0x78);
//Slave address,SA0=0
-
IIC_Wait_Ack();
-
Write_IIC_Byte(
0x00);
//write command
-
IIC_Wait_Ack();
-
Write_IIC_Byte(IIC_Command);
-
IIC_Wait_Ack();
-
IIC_Stop();
-
}
-
/**********************************************
-
// IIC Write Data
-
**********************************************/
-
void Write_IIC_Data(unsigned char IIC_Data)
-
{
-
IIC_Start();
-
Write_IIC_Byte(
0x78);
//D/C#=0; R/W#=0
-
IIC_Wait_Ack();
-
Write_IIC_Byte(
0x40);
//write data
-
IIC_Wait_Ack();
-
Write_IIC_Byte(IIC_Data);
-
IIC_Wait_Ack();
-
IIC_Stop();
-
}
-
void OLED_WR_Byte(unsigned dat,unsigned cmd)
-
{
-
if(cmd){
-
Write_IIC_Data(dat);
-
}
-
else {
-
Write_IIC_Command(dat);
-
}
-
-
-
}
-
-
-
/********************************************
-
// fill_Picture
-
********************************************/
-
void fill_picture(unsigned char fill_Data)
-
{
-
unsigned
char m,n;
-
for(m=
0;m<
8;m++)
-
{
-
OLED_WR_Byte(
0xb0+m,
0);
//page0-page1
-
OLED_WR_Byte(
0x00,
0);
//low column start address
-
OLED_WR_Byte(
0x10,
0);
//high column start address
-
for(n=
0;n<
128;n++)
-
{
-
OLED_WR_Byte(fill_Data,
1);
-
}
-
}
-
}
-
-
-
//坐标设置
-
void OLED_Set_Pos(unsigned char x, unsigned char y)
-
{ OLED_WR_Byte(
0xb0+y,OLED_CMD);
-
OLED_WR_Byte(((x&
0xf0)>>
4)|
0x10,OLED_CMD);
-
OLED_WR_Byte((x&
0x0f),OLED_CMD);
-
}
-
//开启OLED显示
-
void OLED_Display_On(void)
-
{
-
OLED_WR_Byte(
0X8D,OLED_CMD);
//SET DCDC命令
-
OLED_WR_Byte(
0X14,OLED_CMD);
//DCDC ON
-
OLED_WR_Byte(
0XAF,OLED_CMD);
//DISPLAY ON
-
}
-
//关闭OLED显示
-
void OLED_Display_Off(void)
-
{
-
OLED_WR_Byte(
0X8D,OLED_CMD);
//SET DCDC命令
-
OLED_WR_Byte(
0X10,OLED_CMD);
//DCDC OFF
-
OLED_WR_Byte(
0XAE,OLED_CMD);
//DISPLAY OFF
-
}
-
//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
-
void OLED_Clear(void)
-
{
-
u8 i,n;
-
for(i=
0;i<
8;i++)
-
{
-
OLED_WR_Byte (
0xb0+i,OLED_CMD);
//设置页地址(0~7)
-
OLED_WR_Byte (
0x00,OLED_CMD);
//设置显示位置—列低地址
-
OLED_WR_Byte (
0x10,OLED_CMD);
//设置显示位置—列高地址
-
for(n=
0;n<
128;n++)OLED_WR_Byte(
0,OLED_DATA);
-
}
//更新显示
-
}
-
//更新显存到OLED
-
u8 OLED_GRAM[
128][
8];
-
void OLED_Refresh_Gram(void)
-
{
-
u8 i,n;
-
for(i=
0;i<
8;i++)
-
{
-
OLED_WR_Byte (
0xb0+i,OLED_CMD);
//设置页地址(0~7)
-
OLED_WR_Byte (
0x00,OLED_CMD);
//设置显示位置—列低地址
-
OLED_WR_Byte (
0x10,OLED_CMD);
//设置显示位置—列高地址
-
for(n=
0;n<
128;n++)OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA);
-
}
-
}
-
-
//画点
-
//x:0~127
-
//y:0~63
-
//t:1 填充 0,清空
-
-
-
void OLED_DrawPoint(u8 x,u8 y,u8 t)
-
{
-
u8 pos,bx,temp=
0;
-
if(x>
127||y>
63)
return;
//超出范围了.
-
pos=
7-y/
8;
-
bx=y%
8;
-
temp=
1<<(
7-bx);
-
if(t)OLED_GRAM[x][pos]|=temp;
-
else OLED_GRAM[x][pos]&=~temp;
-
}
-
//x1,y1,x2,y2 填充区域的对角坐标
-
//确保x1<=x2;y1<=y2 0<=x1<=127 0<=y1<=63
-
//dot:0,清空;1,填充
-
void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot)
-
{
-
u8 x,y;
-
for(x=x1;x<=x2;x++)
-
{
-
for(y=y1;y<=y2;y++)OLED_DrawPoint(x,y,dot);
-
}
-
OLED_Refresh_Gram();
//更新显示
-
}
-
-
//在指定位置显示一个字符,包括部分字符
-
//x:0~127
-
//y:0~63
-
//mode:0,反白显示;1,正常显示
-
//size:选择字体 12/16/24
-
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
-
{
-
u8 temp,t,t1;
-
u8 y0=y;
-
u8 csize=(size/
8+((size%
8)?
1:
0))*(size/
2);
//得到字体一个字符对应点阵集所占的字节数
-
chr=chr-
' ';
//得到偏移后的值
-
for(t=
0;t<csize;t++)
-
{
-
if(size==
12)temp=asc2_1206[chr][t];
//调用1206字体
-
else
if(size==
16)temp=asc2_1608[chr][t];
//调用1608字体
-
else
if(size==
24)temp=asc2_2412[chr][t];
//调用2412字体
-
else
return;
//没有的字库
-
for(t1=
0;t1<
8;t1++)
-
{
-
if(temp&
0x80)OLED_DrawPoint(x,y,mode);
-
else OLED_DrawPoint(x,y,!mode);
-
temp<<=
1;
-
y++;
-
if((y-y0)==size)
-
{
-
y=y0;
-
x++;
-
break;
-
}
-
}
-
}
-
}
-
//m^n函数
-
u32 mypow(u8 m,u8 n)
-
{
-
u32 result=
1;
-
while(n--)result*=m;
-
return result;
-
}
-
//显示2个数字
-
//x,y :起点坐标
-
//len :数字的位数
-
//size:字体大小12/16/24
-
//mode:模式 0,填充模式;1,叠加模式
-
//num:数值(0~4294967295);
-
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size)
-
{
-
u8 t,temp;
-
u8 enshow=
0;
-
for(t=
0;t<len;t++)
-
{
-
temp=(num/mypow(
10,len-t
-1))%
10;
-
if(enshow==
0&&t<(len
-1))
-
{
-
if(temp==
0)
-
{
-
OLED_ShowChar(x+(size/
2)*t,y,
' ',size,
1);
-
continue;
-
}
else enshow=
1;
-
-
}
-
OLED_ShowChar(x+(size/
2)*t,y,temp+
'0',size,
1);
-
}
-
}
-
//显示字符串
-
//x,y:起点坐标
-
//size:字体大小12/16/24
-
//*p:字符串起始地址
-
void OLED_ShowString(u8 x,u8 y, u8 *p,u8 size)
-
{
-
while((*p<=
'~')&&(*p>=
' '))
//判断是不是非法字符!
-
{
-
if(x>(
128-(size/
2))){x=
0;y+=size;}
-
if(y>(
64-size)){y=x=
0;OLED_Clear();}
-
OLED_ShowChar(x,y,*p,size,
1);
-
x+=size/
2;
-
p++;
-
}
-
}
-
-
//显示汉字
-
void OLED_ShowCHinese(u8 x,u8 y,u8 no)
-
{
-
u8 t,adder=
0;
-
OLED_Set_Pos(x,y);
-
for(t=
0;t<
16;t++)
-
{
-
OLED_WR_Byte(Hzk[
2*no][t],OLED_DATA);
-
adder+=
1;
-
}
-
OLED_Set_Pos(x,y+
1);
-
for(t=
0;t<
16;t++)
-
{
-
OLED_WR_Byte(Hzk[
2*no+
1][t],OLED_DATA);
-
adder+=
1;
-
}
-
}
-
/***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7*****************/
-
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[])
-
{
-
unsigned
int j=
0;
-
unsigned
char x,y;
-
-
if(y1%
8==
0) y=y1/
8;
-
else y=y1/
8+
1;
-
for(y=y0;y<y1;y++)
-
{
-
OLED_Set_Pos(x0,y);
-
for(x=x0;x<x1;x++)
-
{
-
OLED_WR_Byte(BMP[j++],OLED_DATA);
-
}
-
}
-
}
-
-
//初始化SSD1306
-
void OLED_Init(void)
-
{
-
GPIO_InitTypeDef GPIO_InitStructure;
-
-
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE);
//使能B端口和AFIO复用功能模块时钟
-
-
GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE);
//IIC1重映射 -> PB8,9
-
-
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;
-
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
//推挽输出
-
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//速度50MHz
-
GPIO_Init(GPIOB, &GPIO_InitStructure);
//初始化GPIOB8,9
-
GPIO_SetBits(GPIOB,GPIO_Pin_8|GPIO_Pin_9);
-
-
-
delay_ms(
800);
-
OLED_WR_Byte(
0xAE,OLED_CMD);
//--显示关闭
-
OLED_WR_Byte(
0x00,OLED_CMD);
//---设置最小列地址
-
OLED_WR_Byte(
0x10,OLED_CMD);
//---设置最大列地址
-
OLED_WR_Byte(
0x40,OLED_CMD);
//--set start line address
-
OLED_WR_Byte(
0xB0,OLED_CMD);
//--set page address
-
OLED_WR_Byte(
0x81,OLED_CMD);
// contract control
-
OLED_WR_Byte(
0xFF,OLED_CMD);
//--128
-
OLED_WR_Byte(
0xA1,OLED_CMD);
//set segment remap
-
OLED_WR_Byte(
0xA6,OLED_CMD);
//--normal / reverse
-
OLED_WR_Byte(
0xA8,OLED_CMD);
//--set multiplex ratio(1 to 64)
-
OLED_WR_Byte(
0x3F,OLED_CMD);
//--1/32 duty
-
OLED_WR_Byte(
0xC0,OLED_CMD);
//Com扫描方向,若显示的是镜对称,改为C8
-
OLED_WR_Byte(
0xD3,OLED_CMD);
//-set display offset
-
OLED_WR_Byte(
0x00,OLED_CMD);
//
-
-
OLED_WR_Byte(
0xD5,OLED_CMD);
//set osc division
-
OLED_WR_Byte(
0x80,OLED_CMD);
//
-
-
OLED_WR_Byte(
0xD8,OLED_CMD);
//set area color mode off
-
OLED_WR_Byte(
0x05,OLED_CMD);
//
-
-
OLED_WR_Byte(
0xD9,OLED_CMD);
//Set Pre-Charge Period
-
OLED_WR_Byte(
0xF1,OLED_CMD);
//
-
-
OLED_WR_Byte(
0xDA,OLED_CMD);
//set com pin configuartion
-
OLED_WR_Byte(
0x12,OLED_CMD);
//
-
-
OLED_WR_Byte(
0xDB,OLED_CMD);
//set Vcomh
-
OLED_WR_Byte(
0x30,OLED_CMD);
//
-
-
OLED_WR_Byte(
0x8D,OLED_CMD);
//set charge pump enable
-
OLED_WR_Byte(
0x14,OLED_CMD);
//
-
-
OLED_WR_Byte(
0xAF,OLED_CMD);
//--turn on oled panel
-
}
-
-
u8 *itoa(
int num,u8 *str,
int radix)
-
{
-
char index[]=
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
//索引表
-
unsigned unum;
//存放要转换的整数的绝对值,转换的整数可能是负数
-
int i=
0,j,k;
//i用来指示设置字符串相应位,转换之后i其实就是字符串的长度;转换后顺序是逆序的,有正负的情况,k用来指示调整顺序的开始位置;j用来指示调整顺序时的交换。
-
-
//获取要转换的整数的绝对值
-
if(radix==
10&&num<
0)
//要转换成十进制数并且是负数
-
{
-
unum=(unsigned)-num;
//将num的绝对值赋给unum
-
str[i++]=
'-';
//在字符串最前面设置为'-'号,并且索引加1
-
}
-
else unum=(unsigned)num;
//若是num为正,直接赋值给unum
-
-
//转换部分,注意转换后是逆序的
-
do
-
{
-
str[i++]=index[unum%(unsigned)radix];
//取unum的最后一位,并设置为str对应位,指示索引加1
-
unum/=radix;
//unum去掉最后一位
-
-
}
while(unum);
//直至unum为0退出循环
-
-
str[i]=
'\0';
//在字符串最后添加'\0'字符,c语言字符串以'\0'结束。
-
-
//将顺序调整过来
-
if(str[
0]==
'-') k=
1;
//如果是负数,符号不用调整,从符号后面开始调整
-
else k=
0;
//不是负数,全部都要调整
-
-
u8 temp;
//临时变量,交换两个值时用到
-
for(j=k;j<=(i
-1)/
2;j++)
//头尾一一对称交换,i其实就是字符串的长度,索引最大值比长度少1
-
{
-
temp=str[j];
//头部赋值给临时变量
-
str[j]=str[i
-1+k-j];
//尾部赋值给头部
-
str[i
-1+k-j]=temp;
//将临时变量的值(其实就是之前的头部值)赋给尾部
-
}
-
-
return str;
//返回转换后的字符串
-
}
-
oled.h-包含函数预定义和OLED显示所需的宏定义
-
//////////////////////////////////////////////////////////////////////////////////
-
// 功能描述 : 0.69寸OLED 接口演示例程(STM32F103C8T6 IIC)
-
// 说明:
-
// ----------------------------------------------------------------
-
// GND 电源地
-
// VCC 接3.3v电源
-
// SCL 接PB8(SCL)
-
// SDA 接PB9(SDA)
-
// ----------------------------------------------------------------
-
//////////////////////////////////////////////////////////////////////////////////
-
#ifndef __OLED_H
-
#define __OLED_H
-
#include "sys.h"
-
#include "stdlib.h"
-
#define OLED_MODE 0
-
#define SIZE 8
-
#define XLevelL 0x00
-
#define XLevelH 0x10
-
#define Max_Column 128
-
#define Max_Row 64
-
#define Brightness 0xFF
-
#define X_WIDTH 128
-
#define Y_WIDTH 64
-
//-----------------OLED IIC端口定义----------------
-
-
#define OLED_SCLK_Clr() GPIO_ResetBits(GPIOB,GPIO_Pin_8)//SCL
-
#define OLED_SCLK_Set() GPIO_SetBits(GPIOB,GPIO_Pin_8)
-
-
#define OLED_SDIN_Clr() GPIO_ResetBits(GPIOB,GPIO_Pin_9)//SDA
-
#define OLED_SDIN_Set() GPIO_SetBits(GPIOB,GPIO_Pin_9)
-
-
-
#define OLED_CMD 0 //写命令
-
#define OLED_DATA 1 //写数据
-
-
-
//OLED控制用函数
-
void OLED_WR_Byte(unsigned dat,unsigned cmd);
-
void OLED_Display_On(void);
-
void OLED_Display_Off(void);
-
void OLED_Init(void);
-
void OLED_Clear(void);
-
void OLED_Refresh_Gram(void);
-
void OLED_DrawPoint(u8 x,u8 y,u8 t);
-
void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot);
-
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode);
-
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size);
-
void OLED_ShowString(u8 x,u8 y, u8 *p,u8 size);
-
void OLED_Set_Pos(unsigned char x, unsigned char y);
-
void OLED_ShowCHinese(u8 x,u8 y,u8 no);
-
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]);
-
void fill_picture(unsigned char fill_Data);
-
void IIC_Start(void);
-
void IIC_Stop(void);
-
void Write_IIC_Command(unsigned char IIC_Command);
-
void Write_IIC_Data(unsigned char IIC_Data);
-
void Write_IIC_Byte(unsigned char IIC_Byte);
-
void IIC_Wait_Ack(void);
-
u8 *itoa(
int num,u8 *str,
int radix);
-
#endif
其他代码基本就是正点原子官方的文件了,整个工程文件已上传天翼云盘:
https://cloud.189.cn/t/uYniA3iM3iei(访问码:g914)
6.实现效果
=====================================================
视频已上传B站:
[DIY] STM32制作舵机/电调测试仪 | Keil5(MDK)使用与学习 | 定时器触发ADC | DMA传输 | PWM输出 | OLED显示
=====================================================
串口调试助手查看串口输出
OLED显示
转载:https://blog.csdn.net/weixin_42268054/article/details/106878860