飞道的博客

STM32F103C8T6制作舵机测试仪详细图文教程 | 定时器触发ADC | DMA传输 | PWM输出 | RTC实时时钟 | USART串口输出 | OLED IIC显示

447人阅读  评论(0)

自主学习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。

具体可参考https://www.moz8.com/forum.php?mod=viewthread&tid=82875&highlight=%E8%88%B5%E6%9C%BA%E6%B5%8B%E8%AF%95%E4%BB%AA

控制无刷电调所用的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文件-包含程序说明、主函数


  
  1. /*
  2. =============舵机测试仪==============
  3. 芯片STM32F103C8T6,使用ADC读取电位器的电压采样值,0~4095转换到1000~2000,赋值给PWM输出。
  4. TIM2定时触发ADC采样,通过DMA传输给变量ch1Value,取10次进行均值滤波。
  5. 控制舵机的PWM:周期20ms,高电平时间0.5ms~2.5ms变化,可控制舵机0~180°的角度变化,
  6. 但航模舵面的实际控制中,不可能有180°变化,所以通用的高电平宽度其实是1ms~2ms
  7. 电位器:GND - PA0 - 3.3V
  8. OLED显示屏:
  9. GND 电源地
  10. VCC 接3.3v电源
  11. SCL 接PB8(SCL)
  12. SDA 接PB9(SDA)
  13. 串口USB-TTL接法:
  14. GND 电源地
  15. 3V3 接3.3v
  16. TXD 接PB7
  17. RXD 接PB6
  18. ST-LINK V2接法:
  19. GND 电源地
  20. 3V3 接3.3v
  21. SWCLK 接DCLK
  22. SWDIO 接DIO
  23. PWM输出:PB5
  24. by Bilibili 蔡子CaiZi
  25. */
  26. #include "config.h"
  27. #include "delay.h"
  28. #include "usart.h"
  29. #include "stm32f10x.h"
  30. #include "oled.h"
  31. #include "rtc.h"
  32. #include "stdio.h"
  33. #include "string.h"
  34. int main()
  35. {
  36. u8 txt[ 16]={ 0};
  37. delay_init(); //初始化延时函数
  38. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2,2位抢占优先级和2位子优先级
  39. usart_init( 115200); //初始化串口1,波特率为115200
  40. TIM3_PWM_Init( 19999, 71); //预分频72,频率1MHz,周期1us;自动装载值20 000,故PWM周期1us*20 000
  41. TIM2_Init( 499, 71); //1MHz,每500us采集一次;可设置9us以上,但过小影响OLED显示
  42. DMA1_Init(); //DMA初始化
  43. GPIOA_Init(); //PA初始化
  44. Adc_Init(); //ADC初始化
  45. RTC_Init(); //RTC初始化
  46. OLED_Init(); //初始化OLED
  47. OLED_Clear();
  48. while ( 1){
  49. itoa(PWM1value,txt, 10); //将int类型转换成10进制字符串
  50. // printf("采样值:%d\t舵量:%s\t",ch1Value,txt);
  51. // printf("当前时间:%d:%d:%d\n",calendar.hour,calendar.min,calendar.sec);
  52. //OLED_Clear();//一直清屏会造成闪烁
  53. strcat(txt, " us"); //合并字符串
  54. OLED_ShowString( 6, 3,txt, 24); //位置6,3;字符大小24*24点阵
  55. OLED_Refresh_Gram();
  56. delay_ms( 1);
  57. }
  58. }

 config.c文件-包含TIM/ GPIO/ ADC等初始化函数


  
  1. #include "config.h"
  2. #include "delay.h"
  3. #include "usart.h"
  4. #include "sys.h"
  5. #include "rtc.h"
  6. volatile u16 ch1Value[ 10]; //ADC采样值
  7. volatile u16 PWM1value; //控制PWM占空比
  8. #define ADC1_DR_Address ((u32)0x4001244C) //ADC1的地址
  9. //通用定时器2中断初始化
  10. //这里时钟选择为APB1的2倍,而APB1为36M
  11. //arr:自动重装值。
  12. //psc:时钟预分频数
  13. //这里使用的是定时器2控制ADC定时采样
  14. void TIM2_Init(u16 arr,u16 psc)
  15. {
  16. TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  17. TIM_OCInitTypeDef TIM_OCInitStructure;
  18. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //时钟使能
  19. //定时器TIM2初始化
  20. TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
  21. TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
  22. TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
  23. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
  24. TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
  25. TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
  26. TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
  27. TIM_OCInitStructure.TIM_Pulse = 9; //计数达到9产生中断
  28. TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低
  29. TIM_OC2Init(TIM2, & TIM_OCInitStructure); //初始化外设TIM2_CH2
  30. TIM_Cmd(TIM2, ENABLE); //使能TIMx
  31. TIM_CtrlPWMOutputs(TIM2, ENABLE);
  32. }
  33. //DMA1配置
  34. void DMA1_Init(void)
  35. {
  36. DMA_InitTypeDef DMA_InitStructure;
  37. NVIC_InitTypeDef NVIC_InitStructure;
  38. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //使能ADC1通道时钟
  39. //DMA1初始化
  40. DMA_DeInit(DMA1_Channel1);
  41. DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //ADC1地址
  42. DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ch1Value; //ch1Value的内存地址
  43. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //方向(从外设到内存)
  44. DMA_InitStructure.DMA_BufferSize = 10; //DMA缓存大小,存放10次采样值
  45. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址固定,接收一次数据后,设备地址禁止后移
  46. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址不固定,接收多次数据后,目标内存地址后移
  47. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord ; //外设数据单位,定义外设数据宽度为16位
  48. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord ; //内存数据单位,HalfWord就是为16位
  49. DMA_InitStructure.DMA_Mode = DMA_Mode_Circular ; //DMA模式:循环传输
  50. DMA_InitStructure.DMA_Priority = DMA_Priority_High ; //DMA优先级:高
  51. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //禁止内存到内存的传输
  52. DMA_Init(DMA1_Channel1, &DMA_InitStructure); //配置DMA1
  53. DMA_ITConfig(DMA1_Channel1,DMA_IT_TC, ENABLE); //使能传输完成中断
  54. NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
  55. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  56. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  57. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  58. NVIC_Init(&NVIC_InitStructure);
  59. DMA_Cmd(DMA1_Channel1,ENABLE);
  60. }
  61. //中断处理函数
  62. void DMA1_Channel1_IRQHandler(void)
  63. {
  64. int sum= 0;
  65. if(DMA_GetITStatus(DMA1_IT_TC1)!=RESET){
  66. //中断处理代码
  67. for( int i= 0;i< 10;i++){
  68. sum += ch1Value[i];
  69. } //均值滤波
  70. PWM1value = ( int)map(sum/ 10, 0, 4092, 1000, 2000);
  71. sum= 0;
  72. printf( "%d\t",PWM1value);
  73. printf( "当前时间:%d:%d:%d\r\n",calendar.hour,calendar.min,calendar.sec);
  74. TIM_SetCompare2(TIM3,PWM1value); //输出给PWM
  75. DMA_ClearITPendingBit(DMA1_IT_TC1); //清除标志
  76. }
  77. }
  78. //GPIO配置,PA0
  79. void GPIOA_Init(void)
  80. {
  81. GPIO_InitTypeDef GPIO_InitStructure;
  82. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
  83. //PA6 作为模拟通道输入引脚
  84. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
  85. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  86. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
  87. GPIO_Init(GPIOA, &GPIO_InitStructure);
  88. }
  89. //初始化ADC
  90. //这里我们仅以规则通道为例
  91. //我们默认将开启通道0~3
  92. void Adc_Init(void)
  93. {
  94. ADC_InitTypeDef ADC_InitStructure;
  95. RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //使能ADC1通道时钟
  96. //ADC1初始化
  97. ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立ADC模式
  98. ADC_InitStructure.ADC_ScanConvMode = DISABLE; //关闭扫描方式
  99. ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //关闭连续转换模式
  100. ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2; //使用外部触发模式
  101. ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //采集数据右对齐
  102. ADC_InitStructure.ADC_NbrOfChannel = 1; //要转换的通道数目
  103. ADC_Init(ADC1, &ADC_InitStructure);
  104. RCC_ADCCLKConfig(RCC_PCLK2_Div6); //配置ADC时钟,为PCLK2的6分频,即12MHz
  105. ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); //配置ADC1通道0为239.5个采样周期
  106. //使能ADC、DMA
  107. ADC_DMACmd(ADC1,ENABLE);
  108. ADC_Cmd(ADC1,ENABLE);
  109. ADC_ResetCalibration(ADC1); //复位校准寄存器
  110. while(ADC_GetResetCalibrationStatus(ADC1)); //等待校准寄存器复位完成
  111. ADC_StartCalibration(ADC1); //ADC校准
  112. while(ADC_GetCalibrationStatus(ADC1)); //等待校准完成
  113. ADC_ExternalTrigConvCmd(ADC1, ENABLE); //设置外部触发模式使能
  114. }
  115. //获得ADC值
  116. //ch:通道值 0~9
  117. u16 Get_Adc(u8 ch)
  118. {
  119. //设置指定ADC的规则组通道,一个序列,采样时间
  120. ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 ); //ADC1,ADC通道,采样时间为239.5个周期
  121. ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能
  122. while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC )); //等待转换结束
  123. return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果
  124. }
  125. //ch:通道值 0~9,采样times次后作均值滤波
  126. u16 Get_Adc_Average(u8 ch,u8 times)
  127. {
  128. u32 temp_val= 0;
  129. u8 t;
  130. for(t= 0;t<times;t++)
  131. {
  132. temp_val+=Get_Adc(ch);
  133. delay_ms( 5);
  134. }
  135. return temp_val/times;
  136. }
  137. //TIM3 PWM部分初始化
  138. //PWM输出初始化
  139. //arr:自动重装值
  140. //psc:时钟预分频数
  141. void TIM3_PWM_Init(u16 arr,u16 psc)
  142. {
  143. GPIO_InitTypeDef GPIO_InitStructure;
  144. TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  145. TIM_OCInitTypeDef TIM_OCInitStructure;
  146. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
  147. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
  148. GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5
  149. //设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形 GPIOB.5
  150. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
  151. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽输出
  152. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  153. GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化GPIOB
  154. //初始化TIM3
  155. TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
  156. TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
  157. TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
  158. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
  159. TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
  160. //初始化TIM3 Channel2 PWM模式
  161. TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1,计数值<自动重装载值时,输出高电平
  162. TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
  163. TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
  164. TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2
  165. TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
  166. TIM_Cmd(TIM3, ENABLE); //使能TIM3
  167. }
  168. /*函数说明:仿Arduino,将一个数字从一个范围重新映射到另一个范围
  169. 也就是说,fromLow的值将映射到toLow,fromlhigh到toHigh的值等等。
  170. */
  171. float map(float value,float fromLow,float fromHigh,float toLow,float toHigh)
  172. {
  173. return (( value-fromLow)*(toHigh-toLow)/(fromHigh-fromLow)+toLow);
  174. }

 config.h-包含函数预定义和全局变量预定义


  
  1. #ifndef __CONFIG_H
  2. #define __CONFIG_H
  3. #include "stm32f10x.h" //记得添加此头文件,因为config.c用到GPIO相关函数等
  4. #include "sys.h"
  5. extern volatile u16 ch1Value[ 10]; //ADC采样值
  6. extern volatile u16 PWM1value; //控制PWM占空比
  7. void TIM2_Init(u16 arr,u16 psc); //TIM2定时器初始化
  8. void TIM3_PWM_Init(u16 arr,u16 psc); //PB5定时器初始化
  9. void DMA1_Init(void);
  10. void GPIOA_Init(void);
  11. void Adc_Init(void); //ADC1初始化
  12. u16 Get_Adc(u8 ch); //获取一次ADC的值
  13. u16 Get_Adc_Average(u8 ch,u8 times); //ADC采样值进行均值滤波
  14. float map(float value,float fromLow,float fromHigh,float toLow,float toHigh); //映射函数
  15. #endif

 oled.c-包含各种显示函数和IIC初始化


  
  1. //////////////////////////////////////////////////////////////////////////////////
  2. // 功能描述 : 0.69寸OLED 接口演示例程(STM32F103C8T6 IIC)
  3. // 说明:
  4. // ----------------------------------------------------------------
  5. // GND 电源地
  6. // VCC 接3.3v电源
  7. // SCL 接PB8(SCL)
  8. // SDA 接PB9(SDA)
  9. //////////////////////////////////////////////////////////////////////////////////?
  10. #include "oled.h"
  11. #include "stdlib.h"
  12. #include "oledfont.h"
  13. #include "delay.h"
  14. //OLED的显存
  15. //存放格式如下.
  16. //[0]0 1 2 3 ... 127
  17. //[1]0 1 2 3 ... 127
  18. //[2]0 1 2 3 ... 127
  19. //[3]0 1 2 3 ... 127
  20. //[4]0 1 2 3 ... 127
  21. //[5]0 1 2 3 ... 127
  22. //[6]0 1 2 3 ... 127
  23. //[7]0 1 2 3 ... 127
  24. /**********************************************
  25. //IIC Start
  26. **********************************************/
  27. void IIC_Start(void)
  28. {
  29. OLED_SCLK_Set() ;
  30. OLED_SDIN_Set();
  31. OLED_SDIN_Clr();
  32. OLED_SCLK_Clr();
  33. }
  34. /**********************************************
  35. //IIC Stop
  36. **********************************************/
  37. void IIC_Stop(void)
  38. {
  39. OLED_SCLK_Set() ;
  40. // OLED_SCLK_Clr();
  41. OLED_SDIN_Clr();
  42. OLED_SDIN_Set();
  43. }
  44. void IIC_Wait_Ack(void)
  45. {
  46. OLED_SCLK_Set() ;
  47. OLED_SCLK_Clr();
  48. }
  49. /**********************************************
  50. // IIC Write byte
  51. **********************************************/
  52. void Write_IIC_Byte(unsigned char IIC_Byte)
  53. {
  54. unsigned char i;
  55. unsigned char m,da;
  56. da=IIC_Byte;
  57. OLED_SCLK_Clr();
  58. for(i= 0;i< 8;i++)
  59. {
  60. m=da;
  61. // OLED_SCLK_Clr();
  62. m=m& 0x80;
  63. if(m== 0x80)
  64. {OLED_SDIN_Set();}
  65. else OLED_SDIN_Clr();
  66. da=da<< 1;
  67. OLED_SCLK_Set();
  68. OLED_SCLK_Clr();
  69. }
  70. }
  71. /**********************************************
  72. // IIC Write Command
  73. **********************************************/
  74. void Write_IIC_Command(unsigned char IIC_Command)
  75. {
  76. IIC_Start();
  77. Write_IIC_Byte( 0x78); //Slave address,SA0=0
  78. IIC_Wait_Ack();
  79. Write_IIC_Byte( 0x00); //write command
  80. IIC_Wait_Ack();
  81. Write_IIC_Byte(IIC_Command);
  82. IIC_Wait_Ack();
  83. IIC_Stop();
  84. }
  85. /**********************************************
  86. // IIC Write Data
  87. **********************************************/
  88. void Write_IIC_Data(unsigned char IIC_Data)
  89. {
  90. IIC_Start();
  91. Write_IIC_Byte( 0x78); //D/C#=0; R/W#=0
  92. IIC_Wait_Ack();
  93. Write_IIC_Byte( 0x40); //write data
  94. IIC_Wait_Ack();
  95. Write_IIC_Byte(IIC_Data);
  96. IIC_Wait_Ack();
  97. IIC_Stop();
  98. }
  99. void OLED_WR_Byte(unsigned dat,unsigned cmd)
  100. {
  101. if(cmd){
  102. Write_IIC_Data(dat);
  103. }
  104. else {
  105. Write_IIC_Command(dat);
  106. }
  107. }
  108. /********************************************
  109. // fill_Picture
  110. ********************************************/
  111. void fill_picture(unsigned char fill_Data)
  112. {
  113. unsigned char m,n;
  114. for(m= 0;m< 8;m++)
  115. {
  116. OLED_WR_Byte( 0xb0+m, 0); //page0-page1
  117. OLED_WR_Byte( 0x00, 0); //low column start address
  118. OLED_WR_Byte( 0x10, 0); //high column start address
  119. for(n= 0;n< 128;n++)
  120. {
  121. OLED_WR_Byte(fill_Data, 1);
  122. }
  123. }
  124. }
  125. //坐标设置
  126. void OLED_Set_Pos(unsigned char x, unsigned char y)
  127. { OLED_WR_Byte( 0xb0+y,OLED_CMD);
  128. OLED_WR_Byte(((x& 0xf0)>> 4)| 0x10,OLED_CMD);
  129. OLED_WR_Byte((x& 0x0f),OLED_CMD);
  130. }
  131. //开启OLED显示
  132. void OLED_Display_On(void)
  133. {
  134. OLED_WR_Byte( 0X8D,OLED_CMD); //SET DCDC命令
  135. OLED_WR_Byte( 0X14,OLED_CMD); //DCDC ON
  136. OLED_WR_Byte( 0XAF,OLED_CMD); //DISPLAY ON
  137. }
  138. //关闭OLED显示
  139. void OLED_Display_Off(void)
  140. {
  141. OLED_WR_Byte( 0X8D,OLED_CMD); //SET DCDC命令
  142. OLED_WR_Byte( 0X10,OLED_CMD); //DCDC OFF
  143. OLED_WR_Byte( 0XAE,OLED_CMD); //DISPLAY OFF
  144. }
  145. //清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
  146. void OLED_Clear(void)
  147. {
  148. u8 i,n;
  149. for(i= 0;i< 8;i++)
  150. {
  151. OLED_WR_Byte ( 0xb0+i,OLED_CMD); //设置页地址(0~7)
  152. OLED_WR_Byte ( 0x00,OLED_CMD); //设置显示位置—列低地址
  153. OLED_WR_Byte ( 0x10,OLED_CMD); //设置显示位置—列高地址
  154. for(n= 0;n< 128;n++)OLED_WR_Byte( 0,OLED_DATA);
  155. } //更新显示
  156. }
  157. //更新显存到OLED
  158. u8 OLED_GRAM[ 128][ 8];
  159. void OLED_Refresh_Gram(void)
  160. {
  161. u8 i,n;
  162. for(i= 0;i< 8;i++)
  163. {
  164. OLED_WR_Byte ( 0xb0+i,OLED_CMD); //设置页地址(0~7)
  165. OLED_WR_Byte ( 0x00,OLED_CMD); //设置显示位置—列低地址
  166. OLED_WR_Byte ( 0x10,OLED_CMD); //设置显示位置—列高地址
  167. for(n= 0;n< 128;n++)OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA);
  168. }
  169. }
  170. //画点
  171. //x:0~127
  172. //y:0~63
  173. //t:1 填充 0,清空
  174. void OLED_DrawPoint(u8 x,u8 y,u8 t)
  175. {
  176. u8 pos,bx,temp= 0;
  177. if(x> 127||y> 63) return; //超出范围了.
  178. pos= 7-y/ 8;
  179. bx=y% 8;
  180. temp= 1<<( 7-bx);
  181. if(t)OLED_GRAM[x][pos]|=temp;
  182. else OLED_GRAM[x][pos]&=~temp;
  183. }
  184. //x1,y1,x2,y2 填充区域的对角坐标
  185. //确保x1<=x2;y1<=y2 0<=x1<=127 0<=y1<=63
  186. //dot:0,清空;1,填充
  187. void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot)
  188. {
  189. u8 x,y;
  190. for(x=x1;x<=x2;x++)
  191. {
  192. for(y=y1;y<=y2;y++)OLED_DrawPoint(x,y,dot);
  193. }
  194. OLED_Refresh_Gram(); //更新显示
  195. }
  196. //在指定位置显示一个字符,包括部分字符
  197. //x:0~127
  198. //y:0~63
  199. //mode:0,反白显示;1,正常显示
  200. //size:选择字体 12/16/24
  201. void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
  202. {
  203. u8 temp,t,t1;
  204. u8 y0=y;
  205. u8 csize=(size/ 8+((size% 8)? 1: 0))*(size/ 2); //得到字体一个字符对应点阵集所占的字节数
  206. chr=chr- ' '; //得到偏移后的值
  207. for(t= 0;t<csize;t++)
  208. {
  209. if(size== 12)temp=asc2_1206[chr][t]; //调用1206字体
  210. else if(size== 16)temp=asc2_1608[chr][t]; //调用1608字体
  211. else if(size== 24)temp=asc2_2412[chr][t]; //调用2412字体
  212. else return; //没有的字库
  213. for(t1= 0;t1< 8;t1++)
  214. {
  215. if(temp& 0x80)OLED_DrawPoint(x,y,mode);
  216. else OLED_DrawPoint(x,y,!mode);
  217. temp<<= 1;
  218. y++;
  219. if((y-y0)==size)
  220. {
  221. y=y0;
  222. x++;
  223. break;
  224. }
  225. }
  226. }
  227. }
  228. //m^n函数
  229. u32 mypow(u8 m,u8 n)
  230. {
  231. u32 result= 1;
  232. while(n--)result*=m;
  233. return result;
  234. }
  235. //显示2个数字
  236. //x,y :起点坐标
  237. //len :数字的位数
  238. //size:字体大小12/16/24
  239. //mode:模式 0,填充模式;1,叠加模式
  240. //num:数值(0~4294967295);
  241. void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size)
  242. {
  243. u8 t,temp;
  244. u8 enshow= 0;
  245. for(t= 0;t<len;t++)
  246. {
  247. temp=(num/mypow( 10,len-t -1))% 10;
  248. if(enshow== 0&&t<(len -1))
  249. {
  250. if(temp== 0)
  251. {
  252. OLED_ShowChar(x+(size/ 2)*t,y, ' ',size, 1);
  253. continue;
  254. } else enshow= 1;
  255. }
  256. OLED_ShowChar(x+(size/ 2)*t,y,temp+ '0',size, 1);
  257. }
  258. }
  259. //显示字符串
  260. //x,y:起点坐标
  261. //size:字体大小12/16/24
  262. //*p:字符串起始地址
  263. void OLED_ShowString(u8 x,u8 y, u8 *p,u8 size)
  264. {
  265. while((*p<= '~')&&(*p>= ' ')) //判断是不是非法字符!
  266. {
  267. if(x>( 128-(size/ 2))){x= 0;y+=size;}
  268. if(y>( 64-size)){y=x= 0;OLED_Clear();}
  269. OLED_ShowChar(x,y,*p,size, 1);
  270. x+=size/ 2;
  271. p++;
  272. }
  273. }
  274. //显示汉字
  275. void OLED_ShowCHinese(u8 x,u8 y,u8 no)
  276. {
  277. u8 t,adder= 0;
  278. OLED_Set_Pos(x,y);
  279. for(t= 0;t< 16;t++)
  280. {
  281. OLED_WR_Byte(Hzk[ 2*no][t],OLED_DATA);
  282. adder+= 1;
  283. }
  284. OLED_Set_Pos(x,y+ 1);
  285. for(t= 0;t< 16;t++)
  286. {
  287. OLED_WR_Byte(Hzk[ 2*no+ 1][t],OLED_DATA);
  288. adder+= 1;
  289. }
  290. }
  291. /***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7*****************/
  292. void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[])
  293. {
  294. unsigned int j= 0;
  295. unsigned char x,y;
  296. if(y1% 8== 0) y=y1/ 8;
  297. else y=y1/ 8+ 1;
  298. for(y=y0;y<y1;y++)
  299. {
  300. OLED_Set_Pos(x0,y);
  301. for(x=x0;x<x1;x++)
  302. {
  303. OLED_WR_Byte(BMP[j++],OLED_DATA);
  304. }
  305. }
  306. }
  307. //初始化SSD1306
  308. void OLED_Init(void)
  309. {
  310. GPIO_InitTypeDef GPIO_InitStructure;
  311. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE); //使能B端口和AFIO复用功能模块时钟
  312. GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE); //IIC1重映射 -> PB8,9
  313. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;
  314. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
  315. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHz
  316. GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化GPIOB8,9
  317. GPIO_SetBits(GPIOB,GPIO_Pin_8|GPIO_Pin_9);
  318. delay_ms( 800);
  319. OLED_WR_Byte( 0xAE,OLED_CMD); //--显示关闭
  320. OLED_WR_Byte( 0x00,OLED_CMD); //---设置最小列地址
  321. OLED_WR_Byte( 0x10,OLED_CMD); //---设置最大列地址
  322. OLED_WR_Byte( 0x40,OLED_CMD); //--set start line address
  323. OLED_WR_Byte( 0xB0,OLED_CMD); //--set page address
  324. OLED_WR_Byte( 0x81,OLED_CMD); // contract control
  325. OLED_WR_Byte( 0xFF,OLED_CMD); //--128
  326. OLED_WR_Byte( 0xA1,OLED_CMD); //set segment remap
  327. OLED_WR_Byte( 0xA6,OLED_CMD); //--normal / reverse
  328. OLED_WR_Byte( 0xA8,OLED_CMD); //--set multiplex ratio(1 to 64)
  329. OLED_WR_Byte( 0x3F,OLED_CMD); //--1/32 duty
  330. OLED_WR_Byte( 0xC0,OLED_CMD); //Com扫描方向,若显示的是镜对称,改为C8
  331. OLED_WR_Byte( 0xD3,OLED_CMD); //-set display offset
  332. OLED_WR_Byte( 0x00,OLED_CMD); //
  333. OLED_WR_Byte( 0xD5,OLED_CMD); //set osc division
  334. OLED_WR_Byte( 0x80,OLED_CMD); //
  335. OLED_WR_Byte( 0xD8,OLED_CMD); //set area color mode off
  336. OLED_WR_Byte( 0x05,OLED_CMD); //
  337. OLED_WR_Byte( 0xD9,OLED_CMD); //Set Pre-Charge Period
  338. OLED_WR_Byte( 0xF1,OLED_CMD); //
  339. OLED_WR_Byte( 0xDA,OLED_CMD); //set com pin configuartion
  340. OLED_WR_Byte( 0x12,OLED_CMD); //
  341. OLED_WR_Byte( 0xDB,OLED_CMD); //set Vcomh
  342. OLED_WR_Byte( 0x30,OLED_CMD); //
  343. OLED_WR_Byte( 0x8D,OLED_CMD); //set charge pump enable
  344. OLED_WR_Byte( 0x14,OLED_CMD); //
  345. OLED_WR_Byte( 0xAF,OLED_CMD); //--turn on oled panel
  346. }
  347. u8 *itoa( int num,u8 *str, int radix)
  348. {
  349. char index[]= "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //索引表
  350. unsigned unum; //存放要转换的整数的绝对值,转换的整数可能是负数
  351. int i= 0,j,k; //i用来指示设置字符串相应位,转换之后i其实就是字符串的长度;转换后顺序是逆序的,有正负的情况,k用来指示调整顺序的开始位置;j用来指示调整顺序时的交换。
  352. //获取要转换的整数的绝对值
  353. if(radix== 10&&num< 0) //要转换成十进制数并且是负数
  354. {
  355. unum=(unsigned)-num; //将num的绝对值赋给unum
  356. str[i++]= '-'; //在字符串最前面设置为'-'号,并且索引加1
  357. }
  358. else unum=(unsigned)num; //若是num为正,直接赋值给unum
  359. //转换部分,注意转换后是逆序的
  360. do
  361. {
  362. str[i++]=index[unum%(unsigned)radix]; //取unum的最后一位,并设置为str对应位,指示索引加1
  363. unum/=radix; //unum去掉最后一位
  364. } while(unum); //直至unum为0退出循环
  365. str[i]= '\0'; //在字符串最后添加'\0'字符,c语言字符串以'\0'结束。
  366. //将顺序调整过来
  367. if(str[ 0]== '-') k= 1; //如果是负数,符号不用调整,从符号后面开始调整
  368. else k= 0; //不是负数,全部都要调整
  369. u8 temp; //临时变量,交换两个值时用到
  370. for(j=k;j<=(i -1)/ 2;j++) //头尾一一对称交换,i其实就是字符串的长度,索引最大值比长度少1
  371. {
  372. temp=str[j]; //头部赋值给临时变量
  373. str[j]=str[i -1+k-j]; //尾部赋值给头部
  374. str[i -1+k-j]=temp; //将临时变量的值(其实就是之前的头部值)赋给尾部
  375. }
  376. return str; //返回转换后的字符串
  377. }

oled.h-包含函数预定义和OLED显示所需的宏定义 


  
  1. //////////////////////////////////////////////////////////////////////////////////
  2. // 功能描述 : 0.69寸OLED 接口演示例程(STM32F103C8T6 IIC)
  3. // 说明:
  4. // ----------------------------------------------------------------
  5. // GND 电源地
  6. // VCC 接3.3v电源
  7. // SCL 接PB8(SCL)
  8. // SDA 接PB9(SDA)
  9. // ----------------------------------------------------------------
  10. //////////////////////////////////////////////////////////////////////////////////
  11. #ifndef __OLED_H
  12. #define __OLED_H
  13. #include "sys.h"
  14. #include "stdlib.h"
  15. #define OLED_MODE 0
  16. #define SIZE 8
  17. #define XLevelL 0x00
  18. #define XLevelH 0x10
  19. #define Max_Column 128
  20. #define Max_Row 64
  21. #define Brightness 0xFF
  22. #define X_WIDTH 128
  23. #define Y_WIDTH 64
  24. //-----------------OLED IIC端口定义----------------
  25. #define OLED_SCLK_Clr() GPIO_ResetBits(GPIOB,GPIO_Pin_8)//SCL
  26. #define OLED_SCLK_Set() GPIO_SetBits(GPIOB,GPIO_Pin_8)
  27. #define OLED_SDIN_Clr() GPIO_ResetBits(GPIOB,GPIO_Pin_9)//SDA
  28. #define OLED_SDIN_Set() GPIO_SetBits(GPIOB,GPIO_Pin_9)
  29. #define OLED_CMD 0 //写命令
  30. #define OLED_DATA 1 //写数据
  31. //OLED控制用函数
  32. void OLED_WR_Byte(unsigned dat,unsigned cmd);
  33. void OLED_Display_On(void);
  34. void OLED_Display_Off(void);
  35. void OLED_Init(void);
  36. void OLED_Clear(void);
  37. void OLED_Refresh_Gram(void);
  38. void OLED_DrawPoint(u8 x,u8 y,u8 t);
  39. void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot);
  40. void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode);
  41. void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size);
  42. void OLED_ShowString(u8 x,u8 y, u8 *p,u8 size);
  43. void OLED_Set_Pos(unsigned char x, unsigned char y);
  44. void OLED_ShowCHinese(u8 x,u8 y,u8 no);
  45. void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]);
  46. void fill_picture(unsigned char fill_Data);
  47. void IIC_Start(void);
  48. void IIC_Stop(void);
  49. void Write_IIC_Command(unsigned char IIC_Command);
  50. void Write_IIC_Data(unsigned char IIC_Data);
  51. void Write_IIC_Byte(unsigned char IIC_Byte);
  52. void IIC_Wait_Ack(void);
  53. u8 *itoa( int num,u8 *str, int radix);
  54. #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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场