切换到宽版
爱科技/爱创意/爱折腾;电子/数码爱好者的家!欢迎访问新版数码之家网站
  • 8726阅读
  • 11回复

[STM]用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯 [复制链接]

上一主题 下一主题
离线楚门
 

发帖
1011
M币
3094
专家
3
粉丝
78
只看楼主 倒序阅读 我要置顶 楼主  发表于: 2016-11-15
初学者做个东西不容易,发个帖子记录一下制作过程,同时也给大家一些参考吧……
特别感谢坛友 桃源客 和 yanzeyuan
我分别用了他俩的农历计算程序和GMT时区转换程序。
做这个时钟的最初想法是因为办公室新装修换了桌子,没有台灯,所以想着做个可以用遥控器调光的台灯。说是台灯,实际上就是用单片机控制一条12v的led灯带,灯带贴在了格挡的铝合金框架上。


起初用的是四块钱的stm8s103f3,无奈io比较少,没法带我手头的几个屏幕。不过倒是通过这个小东西学了学红外解码、pwm调光和stm8的eeprom操作。帖子的最后会贴上源代码。

这是最初用stm8做的版本
灯做完之后有同学感觉不错,也想让我给做一个,再加上最近刚刚学了学HAL库函数,所以琢磨着弄个带屏幕的调光台灯。手里有好几个网友送的19264屏,只显示亮度的话着实浪费了,加个时钟功能吧,从此开始了一个多星期的折腾之路……
先看看最终的成品图吧:


先续……




用遥控器可以调国际时间,只做了几个常看的,夏令时还没来得及搞…


屏幕最上方用来显示日期,不过空间实在不足,只好做成滚动的了。


电路就是在洞洞板上搭的,挺容易的就不画电路图了。左侧带散热的是7805线性稳压,中间八脚的是从笔记本主板上拆下的mos管,这小东西真不错,vgs(th)很低而且内阻也很小,带3A负载不怎么发热。屏幕引脚掰弯,斜着插到座子中。红外接收头藏在屏幕下边。右边就是24l01了。说到24l01,我发现我手中的几个都是假冒的,而且淘宝上销量靠前的似乎也没有正品,大家可以到下面的链接中去看看:
http://zeptobars.com/en/read/Nordic-NRF24L01P-SI24R1-real-fake-copy
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯

最下面的对比图中右侧的是正品。
发送端用的是剪线的三块钱的gps模块,一个stm8s单片机和一个24l01模块,代码也在最后。
以前用stm32都是用的标准库函数进行开发,这次打算用一下最新的stm32cube,所以还要学习一下hal库函数。
首先就是对stm32的片上资源什么的配置一番。


打开stm32cube,new project,选好对应的芯片,默认就会进入到pinout的配置中。左侧的树状图就是各种片上资源以及中间件了,可以点开进行详细配置。右侧也可以单独对某一管脚进行配置。
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯

首先配置RCC HSE为晶振,然后配置SYS debug选项为串行方式,这样的话不影响下次程序下载,保留PA14 PA13的调试功能。
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
考虑到以后开发可能还需要用串口调试什么的,所以打开usb功能,用usb的模拟串口调试。
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
核心板上的pa0-pa7是紧挨着的,所以在这里我把这几个管脚设置为数据接口。再打开spi1功能,引脚自动映射到pb port上。这里我发现有个问题,pa15既可以作为TIM2CH1、SPI1NSS,也可以作为JTDI接口,如果配置成串行调试模式,失能SPI1的硬件NSS理论上是可以作为TIM2的第一通道的,但无论如何调整代码,这个引脚总是输出低电平,不知何故。以致最后我把pwm输出调整为PB6 TIM4CH1。TIM1CH1设置为红外捕获通道。24l01有一个中断引脚,这里将PB15配置为外部中断,下降沿有效。根据剩余的管脚情况,开启了adc1的第9通道作为电压检测,然后配置其他的液晶控制脚。不得不吐槽这个三片选的19264真的占用太多io了,看看人家建行的优盾,spi接口方便得很呐!
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
接下来打开第二个选项卡对时钟进行配置。input fequency设置为8,主时钟设置为HSE,软件自己带有纠错优化功能,这里也就不考虑什么节能不节能了,直接最高频率了……
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
这里还出现了一个小插曲,第一次做的时候不知何故,apb2 timer clock竟然到了144mhz,手册上不是说当apb2的分频系数不为一的时候,timer的倍频系数才为2么?不应该出现这种情况啊!而且实际我跑了一下就是这么快!莫非还能超频100%??重新新建工程解决此问题…
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
进入到第三个选项卡对外设和中间件进行详细配置。
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
根据24l01的要求,spi速度不能超过10mbps,所以详细的spi配置如图。
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯

用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
adc这部分可以用一下dma传输,开启第9通道和温度通道,模式为连续采样。手册中貌似对温度的采样周期有一些要求,所以我把采样周期设置成最大。DMA通道开启,从外设到内存,模式为circular。
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
这是gpio的详细配置情况
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
因为红外接收头要求数据口要有大于20k的上拉电阻,为了节省一枚电阻,这里将pa8配置为带上拉的输入模式。
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
在NVIC中开启需要用到的中断。
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
TIM1CH1作为红外解码的输入捕获,开启下降沿捕获功能,根据NEC的红外协议,这里将预分频值设为7199可以得到100us的最小捕获周期,正好用来判断红外指令。
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
TIM2每2ms产生中断一次,用来进行计数以及亮度调整等杂七杂八的功能。
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
这是TIM4的详细配置,用来产生PWM驱动MOS管控制led灯。这里预分频系数为23,ARR寄存器值为99,因此pwm频率为72,000/24/100=30khz。可以调节100级亮度并且人眼肯定感觉不到闪啦!
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
usb中间件也可以自己进行配置,一般默认即可。点击工具栏上的小齿轮就可以生成工程代码了。
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
我用的开发环境是MDK v5,给自己的项目起个名字。在code generator中,我建议勾上only copy the necessary library files 以及 generate peripheral initialization...这两个选项,减少空间占用还可以让代码更清晰。
代码生成完毕后点击open project就可以直接用mdk打开工程文件了。
用STM32CubeMX和HAL库函数做的GPS+24L01授时时钟加调光台灯
main文件在application/user文件夹中。可以看到生成的工程非常规范,我们只需要在其中USER CODE BEGIN 和USER CODE BEGIN中间插入要写的代码即可,其余地方无需修改。可以一边写程序一边再用cubemx对芯片进行配置,不过只有在规定区域内修改程序,下次再用cubemx生成代码时才不会被删除……
main函数中已经包含了对各种外设进行配置的步骤了,但是开启PWM输出以及开启DMA传输还是需要手动添加的。
定义一个数组用来保存ADC的数据:
  1. uint32_t ADC_Value[200];
之后就可以开启DMA传输了:
  1. HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&ADC_Value, 200);

DMA控制器会自动将采集到的ADC数据保存到数组中,因为是两个通道,所以偶数的是电压,奇数的是温度。写满200之后会自动从0开始。真是方便至极呀!
再说说屏幕驱动。先前用stc的单片机驱动屏幕一切正常,但是同样的程序挪到stm32上就发现显示成乱码了,鼓捣了好长一段时间才悟出应该是stm32速度过快,数据没能正确地传到液晶模块中。所以在拉高管脚E之后加一个小延时再拉低才算正常。HAL库函数没有WriteByte函数了,所以需要操作寄存器了。原来标准库函数中是操作ODR寄存器,但是ODR寄存器会影响除数据脚以外的其他引脚,所以更好的方法是操作BSRR寄存器。操作BSRR寄存器可以在不影响其他引脚的情况下改变某一引脚的电平状态。这是写数据的函数:
  1. void lcd_send(uint8_t data)//以pa0-7为数据输出口
  2. {
  3.         uint32_t dat,uc1;
  4.         data = reverse8(data);
  5.         uc1 = 0;
  6.         dat = (~(uc1|data)<<16) | (uc1|data);
  7.         GPIOA->BSRR = dat;
  8. }
因为液晶接口的d7对应单片机的pa0而d0对应pa7,是完全颠倒过来的(不颠倒着放实在没地方了),所以还需要一个单字节按位逆序的函数:
  1. uint8_t reverse8( uint8_t c )
  2. {
  3.      c = ( c & 0x55 ) << 1 | ( c & 0xAA ) >> 1;
  4.      c = ( c & 0x33 ) << 2 | ( c & 0xCC ) >> 2;
  5.      c = ( c & 0x0F ) << 4 | ( c & 0xF0 ) >> 4;
  6.      return c;
  7. }

液晶屏幕每8行为一页,一般的程序都是按页进行显示,我写了一个可以以任意行为起始进行显示的函数:
  1. void anyPosDisplay(uint8_t width, uint8_t height, uint8_t row, uint8_t column, uint8_t const *dp)
  2. {
  3.          uint8_t ii,jj,page,height_page,residual;
  4.          //uint16_t n=0;
  5.          page = row/8;//初始的页
  6.          //page_addr = 0xb8 + page;//页地址
  7.          residual = row%8;//错位的行数
  8.          if(residual == 0)
  9.          {
  10.                   height_page = (row+height)/8 - page;
  11.                  for(ii=0;ii<height_page;ii++)//页循环
  12.          {
  13.                          for(jj=0;jj<width;jj++)//列循环
  14.                          {
  15.                                  ChipSelect((jj + column)/64);//确定cs
  16.                                  CmdWrite(0xb8 + page + ii);//写页地址
  17.                                  CmdWrite(0x40 + (jj + column)%64);//写列地址
  18.                                  DataWrite(*dp++);
  19.                          }
  20.                  }
  21.          }
  22.          else
  23.          {
  24.                  height_page = (row+height)/8 - page +1;//总共的页数
  25.          //首先对页进行写入
  26.          for(ii=0;ii<height_page;ii++)//页循环
  27.          {
  28.                          for(jj=0;jj<width;jj++)//列循环
  29.                          {
  30.                                  ChipSelect((jj + column)/64);//确定cs
  31.                                  CmdWrite(0xb8 + page + ii);//写页地址
  32.                                  CmdWrite(0x40 + (jj + column)%64);//写列地址
  33.                                  if (ii == 0)//第一个页面
  34.                                  {
  35.                                     DataWrite(*dp++<<residual);
  36.                                  }//写数据
  37.                                  else
  38.                                  {
  39.                                          if(ii == height_page - 1)//最后一个页面
  40.                                          {
  41.                                                 
  42.                                                  DataWrite(*(dp++-width)>>(8-residual));
  43.                                                 
  44.                                          }
  45.                                          else//中间页面
  46.                                          {
  47.                                                  DataWrite(*dp++<<residual | *(dp-width)>>(8-residual));
  48.                                          }
  49.                                  }
  50.                          }
  51.          }
  52.    }
  53. }

由于空间有限,屏幕最上方的日期只能滚动显示,所以又写了一个字符部分显示的函数:
  1. //针对滚动屏幕设计的显示函数,显示不完整的高度为8的整数倍的字符
  2. //left为左边不显示的列数
  3. void anyPosDisplayPart(uint8_t width, uint8_t height, uint8_t row, uint8_t column, uint8_t left,uint8_t const *dp)
  4. {  
  5.          for(uint8_t ii=0;ii<height/8; ii++)
  6.          {
  7.                 anyPosDisplay(width - left, 8, ii*8, column, dp+left+ii*width);
  8.          }
  9. }

再说说24l01吧。因为有两个接收端,接收的信号是一致的,所以不用开启自动应答。按照网上流行的程序发现只有上电的时候才能正常接收,复位不行:这是因为没有清楚24l01的状态寄存器。24l01还处于中断状态。后来又发现长按复位也不好用:这是因为在初始化的过程中没有清除RXFIFO导致FIFO写满,所以在初始化过程中还要清除FIFO。另外就是HAL库函数有同时写入读取SPI数据的函数,所以不要将写入和读取分开,否则接收的数据不完整。
另外需要注意的地方就是HAL库不需要在单独的c文件中修改中断回调函数,只要根据中断类型,在main文件中重写对应的函数即可。比如24l01的外部中断回调函数:
  1. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
  2. {
  3.         if(GPIO_Pin == GPIO_PIN_15)
  4.         {
  5.                 
  6.                 RX_READY = NRF24L01_RxPacket(rxbuf,&chl);
  7.                 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
  8.                 RX_Mode();
  9.                 for(uint8_t i=0;i<6;i++)//对时间和日期进行处理
  10.                 {
  11.                         timeS[i] = rxbuf[i];
  12.                         dateS[i] = rxbuf[i+6];
  13.                 }
  14.         }
  15. }

以及TIM1CH1的中断回调函数(红外解码):
  1. void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
  2. {
  3.         if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
  4.         {
  5.                 //HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
  6.                 bu =  HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
  7.                 if(bu>130 & bu<140) {xx=0;}
  8.                 else if (bu>9 && bu<14){ ir[xx/8]>>=1;xx++;}
  9.                 else if (bu>17 && bu<27){ ir[xx/8]>>=1;ir[xx/8]|=0x80;xx++;}
  10.                 else if (bu>108 && bu<116) {lianfa=1;}
  11.                 if(xx==32){xx=0;irda_flag=1;}
  12.                 __HAL_TIM_SET_COUNTER(htim,0);
  13.         }
  14. }

以红外中断为例,可以到stm32f1xx_it.c文件中找对应的函数:
  1. void TIM1_CC_IRQHandler(void)
  2. {
  3.   /* USER CODE BEGIN TIM1_CC_IRQn 0 */
  4.   /* USER CODE END TIM1_CC_IRQn 0 */
  5.   HAL_TIM_IRQHandler(&htim1);
  6.   /* USER CODE BEGIN TIM1_CC_IRQn 1 */
  7.   /* USER CODE END TIM1_CC_IRQn 1 */
  8. }

之后再go to definition of HAL_TIM_IRQHandler...可以看到相关代码:
  1. /* Capture compare 1 event */
  2.   if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1) != RESET)
  3.   {
  4.     if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC1) !=RESET)
  5.     {
  6.       {
  7.         __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1);
  8.         htim->Channel = HAL_TIM_ACTIVE_CHANNEL_1;
  9.         /* Input capture event */
  10.         if((htim->Instance->CCMR1 & TIM_CCMR1_CC1S) != 0x00)
  11.         {
  12.           HAL_TIM_IC_CaptureCallback(htim);
  13.         }
  14.         /* Output compare event */
  15.         else
  16.         {
  17.           HAL_TIM_OC_DelayElapsedCallback(htim);
  18.           HAL_TIM_PWM_PulseFinishedCallback(htim);
  19.         }
  20.         htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
  21.       }
  22.     }
  23.   }

第一行已经帮我们清楚中断标志位了,再看回调函数:HAL_TIM_IC_CaptureCallback(htim),用 go to definition功能可以看到它在stm32f1xx_hal_tim.c中4298行已经被预先定义了:
  1. __weak void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)

是一个weak类型,所以我们在main文件中直接重新定义一个HAL_TIM_IC_CaptureCallback函数即可。
其他部分编写就和stc单片机没啥区别了,大家如果想搞的话可以参考我的代码。代码不够完善,希望多提意见和建议~!

以上就是我的一些心得感受,大家参考一下。代码在此:
链接: http://pan.baidu.com/s/1hsHQ3L6 密码: 9j7t



[ 此帖被楚门在2016-11-15 15:46重新编辑 ]
本文内容包含图片或附件,获取更多资讯,请 登录 后查看;或者 注册 成为会员获得更多权限
本帖提到的人: @桃源客
本帖最近打赏记录:共9条打赏M币+27
M币换购:工善必利器,您的拆机好伴侣~数码之家X22精密型螺丝刀套装(拆客必备系列)
 
离线楚门

发帖
1011
M币
3094
专家
3
粉丝
78
只看该作者 1楼 发表于: 2016-11-15
发表主题贴只给奖励1M……还不如那些天天水论坛优惠购的……
离线江小g

发帖
559
M币
1033
专家
15
粉丝
53
只看该作者 2楼 发表于: 2016-11-15
3元的GPS???有没有链接
离线楚门

发帖
1011
M币
3094
专家
3
粉丝
78
只看该作者 3楼 发表于: 2016-11-15
回 江小g 的帖子
江小g:3元的GPS???有没有链接 (2016-11-15 15:21) 回 江小g 的帖子

淘宝搜剪线gps 选二手 应该就有了,不过刚看了一下没有三块的了,五块六块的还是有的
离线chenlei1910

发帖
11032
M币
1624
专家
2
粉丝
40
只看该作者 4楼 发表于: 2016-11-15
回 江小g 的帖子
江小g:3元的GPS???有没有链接 (2016-11-15 15:21) 回 江小g 的帖子

我来给你一个,https://item.taobao.com/item.htm?spm=a1z10.3-c-s.w4002-14443867699.9.uc4E30&id=529630107222
离线慕名而来

发帖
1698
M币
6349
专家
11
粉丝
67
只看该作者 5楼 发表于: 2016-11-15
最近安装了stm32cube还没琢磨咋用呢楼主就及时出现了,谢谢楼主详细的分享。
本帖最近打赏记录:共1条打赏M币+3
离线bg4rff

发帖
1518
M币
1237
专家
8
粉丝
31
只看该作者 6楼 发表于: 2016-11-15
HAL库函数没有WriteByte函数了,所以需要操作寄存器了。
是的,这个的确不方便
http://www.stm32cube.com/question/69
本帖最近打赏记录:共1条打赏M币+3
离线2545889167

发帖
13268
M币
21234
专家
302
粉丝
4805
只看该作者 7楼 发表于: 2016-11-15
回 慕名而来 的帖子
慕名而来:最近安装了stm32cube还没琢磨咋用呢楼主就及时出现了,谢谢楼主详细的分享。 (2016-11-15 20:24) 回 慕名而来 的帖子

没有下载器?不会编程?人人都可以玩——一根数据线吊打pos内stm32f4单片机(教程)|http://bbs.mydigit.cn/read.php?tid=1638363
离线2545889167

发帖
13268
M币
21234
专家
302
粉丝
4805
只看该作者 8楼 发表于: 2016-11-15
不过用多了感觉我还是习惯用标准库
本帖最近打赏记录:共1条打赏M币+3
离线楚门

发帖
1011
M币
3094
专家
3
粉丝
78
只看该作者 9楼 发表于: 2016-11-16
俺用stm32标准库函数没有模板的话初始化起来总是容易丢东西……
快速回复
限80 字节
温馨提示:欢迎交流讨论,请勿发布纯表情、纯引用等灌水帖子;以免被删除
 
上一个 下一个