自主學習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;//返回轉換後的字符串
}
config.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.實現效果
串口調試助手查看串口輸出
OLED顯示