- 2022-05-06
-
回复了主题帖:
【平头哥RVB2601创意应用开发】4. 运用RTC来记录时间
lugl4313820 发表于 2022-5-4 17:55
你是很早用RTC的呀,项目快结束了吧,补给你点个赞先。
-
回复了主题帖:
【平头哥RVB2601创意应用开发】3. 板载按键实验
lugl4313820 发表于 2022-5-4 17:56
现在看到兄弟,没人给你回帖,给你补一个,最近项目完成得怎么样了?
还没做完
- 2022-03-21
-
回复了主题帖:
基于平头哥RSIC-V RVB2601的网络收音机设计(2)
墨文@ 发表于 2022-3-21 13:16
我没有用lvgl了,使用打点阵的方式显示文字。
好的,我也打算这么做,不然SRAM不够用呀
-
回复了主题帖:
基于平头哥RSIC-V RVB2601的网络收音机设计(2)
你用了网络功能之后还能使用lvgl作为显示吗?我这边显示SRAM溢出了
- 2022-03-07
-
回复了主题帖:
【GD32L233C-START评测】3.移植FreeRTOS到GD32L233
xiaoming668 发表于 2022-3-7 09:46 定时器初始化的时候忘了清标志位了,不过我有点疑惑,普通定时器中断不是外部事件,为啥会影响到他休眠呢 ...
如果你不清除定时器标志的话,定时器相当于会一直触发,进入休眠之前应该不允许有中断存在,一般在单片机休眠之前都需要将不用的中断都关掉,只留下需要作为唤醒源的中断
-
回复了主题帖:
【平头哥RVB2601创意应用开发】2. 试试控制板载RGB
Jacktang 发表于 2022-3-7 07:29
程序代码正确,哪个灯不亮就是硬件连接的问题了
整的不错
是的,给我发的板子跳线帽接错了,找了半天问题
- 2022-03-06
-
发表了主题帖:
【平头哥RVB2601创意应用开发】4. 运用RTC来记录时间
本帖最后由 hehung 于 2022-3-6 19:28 编辑
前言
RTC用来记录时间是一个很好的选择,不用外接时钟芯片,还可以达到获取精确时钟的目的,因为我需要做的项目是网络时钟,需要用到内部RTC,所以本文将作为记录讲解如何使用RTC。
RTC接口分析
直接说代码如何实现的,因为平头哥提供的资料没有讲解如何使用RTC,我也不确定这款单片能不能使用RTC,先做了尝试。
RVB2601使用的是阿里的YOC物联网系统,所以参考文档中的描述使用RTC,参考文档路径:https://yoc.docs.t-head.cn/yocbook/Chapter3-AliOS/CSI%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E6%8E%A5%E5%8F%A3/CSI2/RTC.html
我们主要使用的三个函数见下图,可以跳转到网址查看具体操作方式。
这里做简单的介绍
csi_rtc_init:用来初始化RTC;
csi_rtc_set_time:用来设置时间,这是时间需要注意范围与格式,年的取值范围是70-199,真实年数值-1900,如果想设置为2022年,该成员设置为122.
月是从0开始的,真实月份需要加1,比如想设置3月,该成员需要设置为2。
csi_rtc_get_time:获取时间,只要时间设置要了,可以随时获取时间。获取的年需要+1900才是真实年份,月份需要+1才是真实月份。
代码实现
知道了直接的工作以及实现方式,现在可以来编写代码了。
变量定义
需要定义三个变量,用了初始化,设置时间和获取时间。
如下:
rtc是句柄;
time_init是初始化时间变量;
current_time是获取时间的变量,其实和time_init变量可以设置为同一个,这里为了区分与可读性设置为了两个;
csi_rtc_t rtc;
csi_rtc_time_t time_init;
csi_rtc_time_t current_time;
初始化与设置时间
如下:先初始化,然后设置时间,设置的时间是2022年3月6日17点49分,50秒。
printf("===%s, %d\n", __FUNCTION__, __LINE__);的意思是打印当前的函数与行数,当出现故障的时候方便定位使用。
if(CSI_OK != csi_rtc_init(&rtc, 0))
{
printf("===%s, %d\n", __FUNCTION__, __LINE__);
}
time_init.tm_year = 122; /* 2022年 */
time_init.tm_mon = 2; /* 3月 */
time_init.tm_mday = 6; /* 6日 */
time_init.tm_hour = 17;
time_init.tm_min = 49;
time_init.tm_sec = 50;
if(CSI_OK != csi_rtc_set_time(&rtc, &time_init))
{
printf("===%s, %d\n", __FUNCTION__, __LINE__);
}
获取时间并通过串口打印
下面的代码是获取时间,然后通过串口打印出来。
if(CSI_OK != csi_rtc_get_time(&rtc, ¤t_time))
{
printf("===%s, %d\n", __FUNCTION__, __LINE__);
}
printf("Time:%d-%d-%d ", current_time.tm_year+1900, current_time.tm_mon+1, current_time.tm_mday);
printf("%d:%d:%d\n", current_time.tm_hour, current_time.tm_min, current_time.tm_sec);
效果演示
通过上面的方式,成功实现了RTC功能,我在代码中1s中获取一次时间并通过串口打印出来,效果如下:
-
发表了日志:
【平头哥RVB2601创意应用开发】3. 运用RTC来记录时间
-
发表了主题帖:
【平头哥RVB2601创意应用开发】3. 板载按键实验
本帖最后由 hehung 于 2022-3-6 13:07 编辑
前言
上一篇讲解了使用官方例程来操作RGB,本篇将使用上一篇的工程加上按键控制。
目的:按下key1红灯亮,按下key2绿灯亮,key1和key2都按下了蓝灯亮,没有按下任何按键RGB都不亮。
原理图
我们先看一下按键和LED的原理图。
按键原理图如下,原理上写得是K4和K5,与板载上的按键名称不一致,我们在看看端口复用表确定是哪个按键,下面会详细说明。
从原理图上看,按下按键之后,GPIO端口电平会变低,如果没有按下按键,GPIO端口是3.3V高电平。
端口复用表如下图,可以知道,PA11连接到了KEY1,PA12连接到了KEY2.
关于LED的电路图,如下图,作为普通IO控制的话,LED GPIO断如果为高电平,LED灭,如果为低电平,LED亮。上一篇帖子已经讲解了RGB上LED的电路图连接,这里不在赘述,如下。
蓝色 -- PA4 -- PWM4
绿色 -- PA25 -- PWM2
红色 -- PA7 -- PWM7
代码编辑
修改RGB操作方式
上一篇RGB操作中,对RGB是使用的PWM方式操作的,这一篇将使用普通GPIO来操作,打开app_config.h文件,修改为如下,RGB作为普通GPIO控制代码在main函数中,这里不在赘述
初始化KEY
初始化PA11,PA12作为普通GPIO的输入端口使用。
static csi_gpio_pin_t key1;
static csi_gpio_pin_t key2;
static void key_init(void)
{
csi_pin_set_mux(PA11, PIN_FUNC_GPIO);
csi_pin_set_mux(PA12, PIN_FUNC_GPIO);
csi_gpio_pin_init(&key1, PA11);
csi_gpio_pin_dir(&key1, GPIO_DIRECTION_INPUT);
csi_gpio_pin_init(&key2, PA12);
csi_gpio_pin_dir(&key2, GPIO_DIRECTION_INPUT);
}
按键控制逻辑
按键控制逻辑如下:
按下key1红灯亮,按下key2绿灯亮,key1和key2都按下了蓝灯亮,没有按下任何按键RGB都不亮。
代码实现如下:
static void key_Lgc(void)
{
// printf("%d---%d\n",csi_gpio_pin_read(&key1), csi_gpio_pin_read(&key2));
/* Key1 和 Key2都按下,蓝灯亮 */
if(GPIO_PIN_LOW == csi_gpio_pin_read(&key1) && GPIO_PIN_LOW == csi_gpio_pin_read(&key2))
{
csi_gpio_pin_write(&r, GPIO_PIN_HIGH);
csi_gpio_pin_write(&g, GPIO_PIN_HIGH);
csi_gpio_pin_write(&b, GPIO_PIN_LOW);
}
/* Key1按下,红灯亮 */
else if(GPIO_PIN_LOW == csi_gpio_pin_read(&key1))
{
csi_gpio_pin_write(&r, GPIO_PIN_LOW);
csi_gpio_pin_write(&g, GPIO_PIN_HIGH);
csi_gpio_pin_write(&b, GPIO_PIN_HIGH);
}
/* Key2按下,绿灯亮 */
else if(GPIO_PIN_LOW == csi_gpio_pin_read(&key2))
{
csi_gpio_pin_write(&r, GPIO_PIN_HIGH);
csi_gpio_pin_write(&g, GPIO_PIN_LOW);
csi_gpio_pin_write(&b, GPIO_PIN_HIGH);
}
/* 都没按下,灭 */
else
{
csi_gpio_pin_write(&r, GPIO_PIN_HIGH);
csi_gpio_pin_write(&g, GPIO_PIN_HIGH);
csi_gpio_pin_write(&b, GPIO_PIN_HIGH);
}
}
demo_task函数修改
在while循环之前添加key_init()初始化按键相关的GPIO。
在while循环体中,将led_refresh删除,修改任务周期为10ms,将key_lgc()放在while体中判断按键是否按下并控制相应的RGB灯亮灭。
static void demo_task(void *arg)
{
lv_init();
oled_init();
lable_test();
led_pinmux_init();
key_init();
while (1)
{
lv_task_handler();
udelay(1000 * 10);
lv_tick_inc(1);
// led_refresh();
key_Lgc();
}
}
效果演示
实现效果达到预期,如下:
-
发表了日志:
【平头哥RVB2601创意应用开发】3. 板载按键实验
- 2022-03-05
-
发表了主题帖:
【平头哥RVB2601创意应用开发】2. 试试控制板载RGB
本帖最后由 hehung 于 2022-3-5 21:58 编辑
前言
本文尝试了RVB2601的RGB彩灯。
因为RGB的操作官方已经做了,本文将在官方的例程上进行二次开发,以及对源码的解析。
加载工程
打开CDK,如果不在welcome界面,点击左上角的home按钮回到welcome界面;
在首页,点击新建工程按钮,搜索ch2601_marquee,搜索到ch2601_marquee_demo工程;
点击创建工程按钮,输入名字,我取名为RGB_Test,然后点击下载方案,等待下载完成。
代码分析
例程加载好了之后在左边的工程文件夹中,主要操作逻辑在app下面,这下面的文件都是用户自己创建的,其他目录下的文件都是系统文件;
我们需要关注的文件是led.c和main.c,init文件夹以及lvgl_porting文件夹是对初始化以及OLED的操作,我们现在无需关心;
看代码先从main函数入手,main函数代码如下。
board_yoc_init是初始化函数;aos_task_new是OS任务创建函数,创建了一个demo_task任务来操作RGB输出。
board_yoc_init是板载资源的初始化,包括了串口,OS等,这块我们暂时不用关心,主要任务是看看如何初始化GPIO。
找到任务函数demo_task,如下图,前三个函数lv_init();oled_init();lable_test();的调用都是关于OLED的,初始化OLED,并且显示一个label,我们重点看函数led_pinmux_init()
找到函数led_pinmux_init(),在代码中,led_pinmux_init()在两个地方都有,一个是main.c函数里面,是包含在如果宏定义CONFIG_GPIO_MODE里面的,表示使用普通GPIO控制RGB,另一个在led.c里面,包含在如果宏定义CONFIG_PWM_MODE里面的,表示使用PWM控制RGB。CONFIG_GPIO_MODE和CONFIG_PWM_MODE的宏在app_config.h里面,默认使用的是宏定义CONFIG_PWM_MODE,也就是PWM方式。我们也主要按照PWM方式分析,我们可以看到,这里初始化了三个引脚,并且都初始化为PWM。
三个引脚分别为PA7,PA25,PA4.
看原理图,我们可以看到如下图,RGB三个LED通过跳线帽连接到了PA7,PA25,PA4.
对应关系:
蓝色 -- PA4 -- PWM4
绿色 -- PA25 -- PWM2
红色 -- PA7 -- PWM7
关于PWM的操作详见YOC教程:https://yoc.docs.t-head.cn/yocbook/Chapter3-AliOS/CSI%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E6%8E%A5%E5%8F%A3/CSI2/PWM.html
该文档教程详细描述了如何使用Yoc平台的接口。
回到demo_task函数,在while循环中led_refresh()就是控制函数,通过分析我们可以知道,g_ctr首先会加到1,所以先是绿灯亮后灭,然后是蓝灯亮后灭,最后是红灯亮后灭,之后交替亮灭,交替时间是1000*1000us,也就是1s。
关于GPIO的操作可以参考:https://yoc.docs.t-head.cn/yocbook/Chapter3-AliOS/CSI%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E6%8E%A5%E5%8F%A3/CSI2/GPIO.html
结果验证
分析完代码,我们直接编译下载验证。最后可以看到,和我们分析结果一致,后边可以自己修改代码,控制RGB按照我们自己的逻辑运行。
下载了软件之后,发现先是白光一闪,然后绿色LED亮,接着绿LED灭,然后蓝色LED亮,接着蓝色LED灭,最后都不亮,交替时间为1s,循环往复。
为什么红色LED不亮呢?经过长时间问题查找,发现是硬件跳线帽没有连接,PA7的跳线帽需要连接上,我这里用的是一根杜邦线连接的,然后三个LED均可以正常工作了。
关于使用GPIO的操作逻辑,可以将app_config中的//#define CONFIG_GPIO_MODE打开,#define CONFIG_PWM_MODE注释掉即可。
-
发表了日志:
【平头哥RVB2601创意应用开发】1. 试试控制板载RGB
-
发表了主题帖:
【平头哥RVB2601创意应用开发】1. 开箱啦
# 前言
拿到板子已经有一段时间了,忙完手头的工作开始好好学习了解一下平头哥的这款板子。这是第一次使用平头哥的板子,也是第一次用平头哥的开发工具CDK。
# 开箱
拿到快递是一个板子和两个usb线,快递盒子有点破损,但是里面的板子完好无损。
如上图,板子正面是一块128*64分辨率的OLED屏幕,一个用户可操作性三色LED,一个串口通信LED,一个串口通信Error报警LED。两个用户按键,一个RST按键,一个调试器复位按键以及很多扩展引脚,左右上角分别为一个麦克风,共两个,可以做语音识别。
如下图,背面是一个喇叭,可以输出语音。
上电开机,会播放语音以及OLED显示,因为重刷了程序,原始程序已经不在了就不演示了。
# 资料下载
根据官方提供的下载路径,下载资料,不得不说,资料有点少。
[http://www.eeworld.com.cn/huodong/Thead_RISCVcontest_202201/index.html#5](http://www.eeworld.com.cn/huodong/Thead_RISCVcontest_202201/index.html#5)
# 安装CDK以及串口驱动
参考《RVB2601开发板快速上手指南.pdf》中的操作,下载串口驱动以及CDK文件,不在赘述,文档中已经描述的很清楚了。
这里说一些注意事项:
1. 我安装的CDK的版本是V2.8.4,官方推荐V2.12.1,可以都试一下,有些小伙伴安装了高版本反而用不了;
2. 安装之前把杀毒软件关了,不然CDK里面很多程序都会被识别为危险软件,可能导致安装失败或者一些重要程序丢失;
3. 在安装好了之后使用的时候也尽量不要开启杀毒软件,也会被视为病毒,如果要开杀毒软件,每次有文件被查杀之后去病毒库中把文件添加到白名单就ok了
# 验证程序
然后就是验证,参考教程中的helloworld程序,编译下载。不在赘述,直说一些重要的:
1. 第一次编译的过程很慢,需要几分钟,可能和电脑有关系,耐心等待一下,不要急着关CDK;
2. 第一次使用这个软件最好先看看教程,里面的很多操作与之前使用的KEIL,IAR等软件差别比较大。点击help->CDK help有中文参考手册。
程序编译好了之后,连根USB线都连上,打开一个串口调试助手,任何软件都可以,然后下载程序,就可以看到下位机在移植发送app Hello world! Yoc
# 吐槽
1. CDK的快捷键有些用不了;
2. 很多程序运行的时候总是被杀毒软件杀,像是打开软件,软件点击了home之后网页打不开;
3. 板子比较难以扩展资源,板载的资源都被板子使用了,如果需要使用wifi,OLED,喇叭,麦克风,LED,按键等这些资源的话,板子就没法在外界其他设备了,GPIO基本上已经被这些板载资源占完了;
虽然现在还有一些不足,但是我相信平头哥会越做越好,这也需要我们这些玩家来完善。
- 2022-02-28
-
回复了主题帖:
一点干货,RVB2601开发板程序下载自动运行及OLED屏幕保护
很棒
- 2022-02-27
-
发表了主题帖:
【GD32L233C-START评测】17. 作品完成之室内环境监控设备
之前的发帖内容请跳转
[【GD32L233C-START评测】1.开箱 ](http://bbs.eeworld.com.cn/thread-1192136-1-1.html "【GD32L233C-START评测】1.开箱 ")
[【GD32L233C-START评测】2.手把手创建新工程](http://bbs.eeworld.com.cn/thread-1192155-1-1.html "【GD32L233C-START评测】2.手把手创建新工程")
[ 【GD32L233C-START评测】3.移植FreeRTOS到GD32L233](http://bbs.eeworld.com.cn/thread-1192482-1-1.html " 【GD32L233C-START评测】3.移植FreeRTOS到GD32L233")
[ 【GD32L233C-START评测】4. 移植RT-Thread到GD32L233](http://bbs.eeworld.com.cn/thread-1192592-1-1.html " 【GD32L233C-START评测】4. 移植RT-Thread到GD32L233")
[【GD32L233C-START评测】5. IIC驱动OLED](http://bbs.eeworld.com.cn/thread-1192898-1-1.html "【GD32L233C-START评测】5. IIC驱动OLED")
[ 【GD32L233C-START评测】6. 获取RTC时间并通过OLED显示](http://bbs.eeworld.com.cn/thread-1192907-1-1.html " 【GD32L233C-START评测】6. 获取RTC时间并通过OLED显示")
[【GD32L233C-START评测】7. PWM驱动LED](http://bbs.eeworld.com.cn/thread-1193516-1-1.html "【GD32L233C-START评测】7. PWM驱动LED")
[【GD32L233C-START评测】8. TRNG真随机数生成](http://bbs.eeworld.com.cn/thread-1193518-1-1.html "【GD32L233C-START评测】8. TRNG真随机数生成")
[ 【GD32L233C-START评测】9. CRC检验](http://bbs.eeworld.com.cn/thread-1193662-1-1.html " 【GD32L233C-START评测】9. CRC检验")
[【GD32L233C-START评测】10. ADC读取芯片内部温度](http://bbs.eeworld.com.cn/thread-1193760-1-1.html "【GD32L233C-START评测】10. ADC读取芯片内部温度")
[ 【GD32L233C-START评测】11.DAC输出电压值_ADC读取外部电压值](http://bbs.eeworld.com.cn/thread-1193765-1-1.html " 【GD32L233C-START评测】11.DAC输出电压值_ADC读取外部电压值")
[【GD32L233C-START评测】12. 硬件IIC驱动OLED](http://bbs.eeworld.com.cn/thread-1193778-1-1.html "【GD32L233C-START评测】12. 硬件IIC驱动OLED")
[【GD32L233C-START评测】13. CAU加密算法之DES/TDES](http://bbs.eeworld.com.cn/thread-1194132-1-1.html "【GD32L233C-START评测】13. CAU加密算法之DES/TDES")
[ 【GD32L233C-START评测】14. CAU加密算法之AES](http://bbs.eeworld.com.cn/thread-1194242-1-1.html " 【GD32L233C-START评测】14. CAU加密算法之AES")
[【GD32L233C-START评测】15. flash擦写操作,将FLASH当做EEPROM使用](http://bbs.eeworld.com.cn/thread-1194416-1-1.html "【GD32L233C-START评测】15. flash擦写操作,将FLASH当做EEPROM使用")
[ 【GD32L233C-START评测】16. IIC获取x-nucleo-iks01a3板子温湿度以及大气压](http://bbs.eeworld.com.cn/thread-1195070-1-1.html " 【GD32L233C-START评测】16. IIC获取x-nucleo-iks01a3板子温湿度以及大气压")
# 前言
使用GD32L233制作了一个室内环境监控装置,使用了嵌入式软件系统RT-Thread。可以实现时间日期显示,温湿度显示,气压显示,可燃气体监控以及蜂鸣器报警等功能,能够对室内环境实时监控,用户可以自行设置可燃气体报警阈值,阈值存储在Flash中,掉电不会丢失。
# 使用板载资源
1. 普通IO输入输出 - 用于LED输出控制以及按键采样输入;
2. PWM - 用于LED呼吸输出以及蜂鸣器输出控制;
3. ADC - 用于可燃气体传感器采样以及模拟按键采样;
4. IIC - 用于温湿度,气压传感器数据采集以及OLED显示控制;
5. RTC - 用于时间输出;
6. FMC - 用于保存可燃气体阈值,上电时读出阈值,设置阈值时保存早指定FLASH地址;
7. 串口 - 用于RT-Thread嵌入式系统的shell;
8. 嵌入式系统RT-Thread。
# 硬件连接以及功能介绍
## LED功能
本作品使用了两个LED,一个由PWM控制(控制周期性呼吸),一个是普通IO控制(控制周期性闪烁),主要是用于表示环境监控装置是否正常工作。有一个线程专门控制LED呼吸以及闪烁,线程周期是500ms,线程优先级为5。
LED使用了板载的LED1以及LED4:
-- LED1作为普通IO口,每500ms翻转一次;
-- LED4为PWM控制,呼吸周期为12.5s。
### 线路连接
**LED1 --> PA7
LED4 --> PC7**
**PC7的端口复用关系如下,使用了Timer2 CH1,复用关系为AF1**:
## 按键功能
按键共有三个,其中一个是板载的按键,位普通IO控制,另两个为外部连接的按键,这两个按键是模拟按键,两个按键接在同一个ADC口上。按键采样程序有单独的线程控制,线程周期是40ms,优先级为3。
### 数字按键
数字按键为板载按键。**电路连接:PA0**
**定义数字按键为按键1**,模拟按键分别为按键2和按键3。
### 模拟按键
模拟按键原理图如下,两个按键接在同一个模拟信号上。
实物图:,**定义左边的按键为按键2,右边的按键为按键3**:
通过计算就可以算出来每个按键按下的时候switch端口会采集到的电压。
Switch端电压计算(使用12bit精度):
--S1按下:Switch = (3.3V)*[1/(4.7+1)] = 0.58V,转换为12bit精度数字为0.58/3.3 * 4096 ≈ **718.6**
--S2按下:Switch = (3.3V)*[2/(4.7+2)] = 0.99V,转换为12bit精度数字为0.99/3.3 * 4096 ≈ **1222.7**
实测结果也如上所示。
硬件连接:**switch连接到PA1, 使用的是ADC_IN1**
## 蜂鸣器
蜂鸣器主要用于报警用,当可燃气体传感器检测到可燃气体超过阈值只有就会报警。使用的是无源蜂鸣器,需要用PWM驱动,人耳能听到的声音频率为20Hz到20kHz,在设置PWM频率的时候要注意保持在这个频率,而且频率不能太高,过高的频率声音很尖锐刺耳,本作品使用的频率为1000Hz,驱动占空比为50%,为最大音量。
蜂鸣器控制有专门的线程,优先级为2.
蜂鸣器根据可燃气体监测的浓度输出两种不同间断的声音,当为一级警报(见可燃气体传感器说明)时,输出声音间断周期为1s;当为二级警报(见可燃气体传感器说明)时,输出声音的间断周期为100ms。
蜂鸣器电路图如下:
实物图:
**BEEP引脚连接到了PA3**.
**PA3复用为Timer1 CH3,复用关系为AF1**,如下图.
## 可燃气体传感器MQ-2
可燃气体传感器使用的是MQ2,可以高灵敏度的监测大部分可燃气体以及烟雾,其使用ADC输出监测结果,当检测到的可燃气体(如甲烷)溶度越高,输出的ADC值越大。
软件中使用12bit ADC,然后将采集到的值转换成百分比形式,为0%~100%。
可燃气体检测有专门的线程,优先级为1,因为检测可燃气体为最优任务,线程周期为1s。
可燃气体报警阈值设置了两个,一个为一级阈值,一个为二级阈值,一级阈值对应的浓度较低,只是可能存在可燃气体泄漏;二级阈值对应浓度较高,肯定可燃气体存在泄漏的情况,这两个数值可以使用按键进行设置。
**注意:MQ2的供电电压为5V**,最开始我接的是3.3V导致ADC输出总是为0,为此花了很多时间排查问题。
电路图如下:
实物图:
**ADC引脚连接到了PA2,即ADC_IN2,采样精度为12bit**
## 温度、湿度、气压传感器
- 温度传感器使用的是STTS751,用来采集温度;
- 湿度传感器使用的是HTS221,用来采集湿度;
- 气压传感器使用的是LPS22HH,用来采集大气压强。
上面的传感器使用的详细介绍请参考我的帖子,这里不在赘述:
[【GD32L233C-START评测】16. IIC获取x-nucleo-iks01a3板子温湿度以及大气压](http://bbs.eeworld.com.cn/thread-1195070-1-1.html "【GD32L233C-START评测】16. IIC获取x-nucleo-iks01a3板子温湿度以及大气压")
实物图:
硬件连接:这个三个传感器都使用了IIC控制,使用的IIC接线为:
**SCL - PB10
SDA - PB11**
## OLED显示屏
OLED显示屏单色,分辨率为128*64,控制方式为IIC。
### 功能
1. 时间显示
间隔读出RTC输出的时钟信息并显示出来。
2. 传感器数值显示
显示温度值,湿度值,气压值,可燃气体浓度值。
3. 设置
显示设置界面,用于设置可燃气体报警阈值,初始一级报警阈值为30,二级报警阈值为45;可以调整参数。
4. 设置完成
在设置完成阈值之后会显示这个界面,表示设置成功。
硬件连接:
**SCL - PB10
SDA - PB11**
## RTC
使用RTC实时时钟组件来获取当前的时间并在显示屏上显示出来。
可以参考帖子:[【GD32L233C-START评测】6. 获取RTC时间并通过OLED显示](http://bbs.eeworld.com.cn/thread-1192907-1-1.html "【GD32L233C-START评测】6. 获取RTC时间并通过OLED显示")
## FMC flash擦写控制
主要用于将可燃气体的报警阈值写入到FLASH的固定地址中。上电时先从改地址中读取数值,如果全是0xFFFF,表示没有数据写入,读取默认的阈值,一级阈值30,二级阈值45;如果读出来的数值不是0xFFFF,就将读出来的数值装载到一级阈值以及二级阈值的变量中。
当用于通过按键重新设置了阈值之后,擦除指定FLASH page,然后将阈值数据重新写入到指定地址中。
阈值写入地址:
**1. 一级阈值:0x0803E000;**
**2. 二级阈值:0x0803E001。**
0x0803E000地址对应的FLASH page为62,当用户重新设置了阈值之后,先擦除FLASH的62页,然后再将两个阈值重新入到0x0803E000以及0x0803E001中。
FMC的操作参考帖子:[【GD32L233C-START评测】15. flash擦写操作,将FLASH当做EEPROM使用](http://bbs.eeworld.com.cn/thread-1194416-1-1.html "【GD32L233C-START评测】15. flash擦写操作,将FLASH当做EEPROM使用")
# 功能描述
功能是基于OLED显示界面而有所区分的。
## 上电时
上面时会显示 环境监控仪界面,持续一秒钟。
。
## 时间显示
时间显示界面会显示从RTC读出来的时间,也会显示日期(年月日)在最下面,左上角显示当前温度,右上角显示当前湿度。
**
在这个界面下按下按键3,可以切换到【环境监控界面】
在这个界面下按下按键2,可以切换到【设置界面】
**
## 环境监控
环境监控显示传感器读取到的温度,湿度,气压,可燃气体浓度等数据并显示出来。
**
在该界面下,按下按键3可以切换到【设置界面】
在该界面下,按下按键2可以切换到【时间显示界面】
**
## 设置
设置界面可以对可燃气体的浓度进行设置,在开始进入到这个界面的时候,光标在最下面的【开始】上,表示还没有进入到对阈值的设置逻辑中,如下图。
**
在此界面下,按下按键3可以切换到【时间显示界面】
在此界面下,按下按键2可以切换到【环境监控界面】
在此界面下,按下按键1(短按,时间不超过1s)光标会切换到一级阈值设置处。
**
当光标在一级阈值设置处,如下图
**
按下按键3阈值+1,当阈值等于二级阈值-1时,再次按下会变为101,
按下按键2阈值-1,当阈值=10时,再次按下会变为二级阈值-1.
按下按键1(短按,时间不超过1s)光标会切换到二级阈值设置处。
**
当光标在二级阈值设置处。
**
按下按键3阈值+1,当阈值等于99时,再次按下阈值会变为一级阈值+1或者20,
按下按键2阈值-1,当阈值=20或者等于一级阈值+1时,再次按下会变为99.
按下按键1(短按,时间不超过1s)光标会切换到【确认】上。
**
当光标在【确认】处。
**
按键2和按键3将失效。
按键1短按(时间不超过1s),光标会切换到一级阈值设置处
按键1长按(时间超过1s),切换到【设置成功界面】,开始存储新的阈值到FLASH中
**
## 设置完成
设置完成界面,表示阈值设置成功,新的阈值值已经存入了FLASH中,一秒钟之后退回到【设置界面】。光标处变为【开始】,此时可以按照【设置界面】中的描述来做。
# 显示效果
显示视频在Bilibili中:https://www.bilibili.com/video/BV1pT4y1Q7sc/
[点击跳转](https://www.bilibili.com/video/BV1pT4y1Q7sc/ "点击跳转")
# 代码
代码放在了码云中:https://gitee.com/hehung/EnvironmentalMonitoring
[点击跳转](https://gitee.com/hehung/EnvironmentalMonitoring "点击跳转")
- 2022-02-26
-
发表了日志:
【GD32L233C-START评测】16. IIC获取x-nucleo-iks01a3板子温湿度以及大气压
-
发表了主题帖:
【GD32L233C-START评测】16. IIC获取x-nucleo-iks01a3板子温湿度以及大气压
之前的帖子可以参考:
【GD32L233C-START评测】1.开箱
【GD32L233C-START评测】2.手把手创建新工程
【GD32L233C-START评测】3.移植FreeRTOS到GD32L233
【GD32L233C-START评测】4. 移植RT-Thread到GD32L233
【GD32L233C-START评测】5. IIC驱动OLED
【GD32L233C-START评测】6. 获取RTC时间并通过OLED显示
【GD32L233C-START评测】7. PWM驱动LED
【GD32L233C-START评测】8. TRNG真随机数生成
【GD32L233C-START评测】9. CRC检验
【GD32L233C-START评测】10. ADC读取芯片内部温度
【GD32L233C-START评测】11.DAC输出电压值_ADC读取外部电压值
【GD32L233C-START评测】12. 硬件IIC驱动OLED
【GD32L233C-START评测】13. CAU加密算法之DES/TDES
【GD32L233C-START评测】14. CAU加密算法之AES
【GD32L233C-START评测】15. flash擦写操作,将FLASH当做EEPROM使用
一、前言
本帖将讲解使用GD32L233C的硬件IIC来驱动x-nucleo-iks01a3传感器板子中的温度传感器STTS751,湿度传感器HTS221以及气压传感器LPS22HH。
硬件接线:
PB10 - SCL
PB11 - SDA
本帖的代码是在RT-Thread的基础上开发的,单独创建了一个线程用来1s中打印一次温湿度以及气压值。
二、传感器手册
1. STTS751
STTS751是一个温度传感器,其原理很简单,直接从温度寄存器中取出温度值就可以了,寄存器中存储的温度值就是实际温度,不需要进行转换,使用起来很方便,这里不在赘述。
STTS751的主要信息如下:
IIC地址:0x94
温度寄存器地址:温度整数部分:0x00,温度小数部分:0x02
2. LPS22HH
LPS22HH是一个大气压强传感器,可以获取大气压强的值,有三个寄存器来获取大气压强的高位,中位和低位,然后除以40960的大气压强单位就是kPA。
LPS22HH中也提供了温度传感器,但是我们已经使用了STTS751,这里就不需要在使用了,但是可以读取出来对大气压强做校准。
IIC地址:0xBA
其中有几个比较重要级的寄存器分别如下:
(1)0x0E:
这个寄存器用于设置传输方式,我们使用的是I2C传输,所以将该寄存器设置为0x0A
(2)0x10:这个寄存器用来设置采样频率以及方式,我们使用连续采样,采样频率为1Hz,所以该值设置为0x18。
(3)0x28,0x29,0x2A: 这三个寄存器是大气压强寄存器,读取这三个寄存器的值后就可以直接计算出大气压强的值。
0x2A是存取的气压值的最高位,0x29存取的是气压值的中位,0x28存取的是寄存器的低位。
获取了这三个值之后在处于4096就是气压值,其单位为hPa, 我们一般转为kPa只用,在除以10就行了。
计算公式的说明见下图:
3. HTS221
HTS221是一个温湿度传感器,可以检测出温湿度等信息,但是该寄存器操作起来计较复杂,其校准信息也存储在特定的寄存器中。校准数值由两个采样值和两个湿度值存在4个寄存器中,根据这四个值计算出一个线性方程公式,再将我们实际采集到的湿度值带入方程中计算出真实的湿度百分比值,其温度值和湿度值一样,也需要根据校准公式推导,方式一样,本贴中没有进行使用,温度值使用的是STTS751的值。
该传感器使用之前必须要初始化,不然不会工作。
IIC地址:0xBE
关键寄存器信息如下:
(1)0x10:配置温湿度平均采样寄存器,本帖配置为0x09
(2)0x20:配置采样率以及电源开启关闭等信息,设置为0x81,激活传感器以及采样率设置为1Hz
(3)0x28,0x29:读取的未转换的湿度值,该数值需要转换之后才是真是的湿度值。
0x29为高位,0x28为低位。
(4)0x30,0x31,(0x36,0x37),(0x3A,0x3B):校准值,这些值在传感器初始化完成之后读取一次就行了,这是固定在寄存器中的值。
寄存器描述以及计算方式如下:
从上面的寄存器介绍中可以看到,寄存器符号以及作用,其实是一个线性方程,公式为: y=k*x+b
其中,y就是湿度的百分比的2倍,计算出来之后还要除以2
x就是独取出来的寄存器0x28, 0x29的值。
我们在初始化的时候还需要做的一件事就是计算出k和b的值,方式如下:
1. 读取校准寄存器的值, x1 = 寄存器0x36,0x37的值,即H0_T0_OUT
2. 读取校准寄存器的值, x2 = 寄存器0x3A,0x3B的值,即H1_T0_OUT
3. 读取校准寄存器的值, y1 = 寄存器0x30的值,即H0_rH_x2
4. 读取校准寄存器的值, y2 = 寄存器0x31的值,即H1_rH_x2
可以计算出k = (y2-y1)/ (x2-x1), b = y1 - k*x1 = y1 - x1*(y2-y1)/(x2-x1)
之后将我们读取的寄存器0x28,0x29的值带入进去就可以算出来真实的湿度值了,注意要除以2才是真实值。
三、代码实现
代码实现才考附件。
其中关于I2C硬件初始化以及串口初始化的代码都放在了RT-Thread的board.c这个文件中的rt_hw_board_init()函数里面,因为RT-Thread中main函数也是一个线程,所以最好不要将硬件初始化的代码放在main里面,而是放在RT-Thread提供的硬件初始化接口函数rt_hw_board_init()里面。
传感器的数字通过串口打印,1s中打印一次,见文件iks01a3.c中,如下:
void BEEP_Thread_Init(void)
{
iks01a3_thr = rt_thread_create(IKS01A3_THREAD_LABEL,
IKS01A3_ThreadEntry, /*线程入口函数*/
RT_NULL, /*线程入口函数参数*/
IKS01A3_THREAD_SIZE,
IKS01A3_THREAD_PRIO,
IKS01A3_THREAD_TIME_SLICE);
if (RT_NULL != iks01a3_thr)
rt_thread_startup (iks01a3_thr);
else
rt_kprintf("\r\n ERROR: iks01a3_thr thread initializd failed! \r\n");
}
static void IKS01A3_ThreadEntry(void *parameter)
{
/* Initialize HTS221 */
HTS221_Init();
/* Initialize LPS22HH */
LPS22HH_Init();
while (1)
{
printf("Temperature:%d.%dC\r\n", STTS751_GetTempH(), STTS751_GetTempL());
printf("Humidity:%f%%\r\n", HTS221_GetHumidity());
printf("Pressure:%fkPa\r\n", LPS22HH_GetPressure());
printf("------------------------\r\n");
rt_thread_mdelay(1000);
}
}
IIC接口代码,写IIC数据以及读IIC数据同样在iks01a3.c中
void IKS01A3_IicWrite(uint8_t addr, uint8_t IIC_Reg, uint8_t IIC_Data)
{
rt_enter_critical();
#if (IIC_TYPE == IIC_TYPE_HW)
uint8_t i2c_transmitter[2]={IIC_Reg, IIC_Data};
uint8_t i;
I2C_SetSlaveConfig(addr, I2C_MASTER_TRANSMIT, 2);
i2c_transmitter[0] = IIC_Reg;
i2c_transmitter[1] = IIC_Data;
/* wait until I2C bus is idle */
while(i2c_flag_get(I2C1, I2C_FLAG_I2CBSY)){};
/* send a start condition to I2C bus */
i2c_start_on_bus(I2C1);
/* wait until the transmit data buffer is empty */
I2C_STAT(I2C1) |= I2C_STAT_TBE;
while(!i2c_flag_get(I2C1, I2C_FLAG_TBE)){};
for(i = 0; i < 2; i++)
{
/* data transmission */
i2c_data_transmit(I2C1, i2c_transmitter);
/* wait until the TI bit is set */
while(!i2c_flag_get(I2C1, I2C_FLAG_TI));
}
/* wait for transfer complete flag */
while(!i2c_flag_get(I2C1, I2C_FLAG_TC)){};
/* send a stop condition to I2C bus */
i2c_stop_on_bus(I2C1);
/* wait until stop condition generate */
while(!i2c_flag_get(I2C1, I2C_FLAG_STPDET)){};
/* clear the STPDET bit */
i2c_flag_clear(I2C1, I2C_FLAG_STPDET);
#elif (IIC_TYPE == IIC_TYPE_SW)
IIC_Start();
IIC_Send_Byte(addr);
IIC_Wait_Ack();
IIC_Send_Byte(IIC_Reg); //write data
IIC_Wait_Ack();
IIC_Send_Byte(IIC_Data);
IIC_Wait_Ack();
IIC_Stop();
#endif
rt_exit_critical();
}
uint8_t IKS01A3_IicRead(uint8_t addr, uint8_t IIC_Reg)
{
char ret = 0;
uint8_t ret_suc = 1;
rt_enter_critical();
#if (IIC_TYPE == IIC_TYPE_HW)
uint8_t i2c_receiver = 0;
uint8_t i;
uint8_t i2c_transmitter = IIC_Reg;
I2C_SetSlaveConfig(addr, I2C_MASTER_TRANSMIT, 1);
i2c_transmitter = IIC_Reg;
/* wait until I2C bus is idle */
while(i2c_flag_get(I2C1, I2C_FLAG_I2CBSY)){};
/* send a start condition to I2C bus */
i2c_start_on_bus(I2C1);
/* wait until the transmit data buffer is empty */
I2C_STAT(I2C1) |= I2C_STAT_TBE;
while(!i2c_flag_get(I2C1, I2C_FLAG_TBE)){};
/* data transmission */
i2c_data_transmit(I2C1, i2c_transmitter);
/* wait until the TI bit is set */
while(!i2c_flag_get(I2C1, I2C_FLAG_TI)){};
/* wait for transfer complete flag */
while(!i2c_flag_get(I2C1, I2C_FLAG_TC)){};
I2C_SetSlaveConfig(addr, I2C_MASTER_RECEIVE, 1);
/* wait until I2C bus is idle */
while(i2c_flag_get(I2C1, I2C_FLAG_I2CBSY)){};
/* send a start condition to I2C bus */
i2c_start_on_bus(I2C1);
/* wait until the RBNE bit is set */
while(!i2c_flag_get(I2C1, I2C_FLAG_RBNE)){};
/* read a data from I2C_DATA */
i2c_receiver = i2c_data_receive(I2C1);
/* wait for transfer complete flag */
while(!i2c_flag_get(I2C1, I2C_FLAG_TC)){};
/* send a stop condition to I2C bus */
i2c_stop_on_bus(I2C1);
/* wait until stop condition generate */
while(!i2c_flag_get(I2C1, I2C_FLAG_STPDET)){};
/* clear the STPDET bit */
i2c_flag_clear(I2C1, I2C_FLAG_STPDET);
ret = i2c_receiver;
#elif (IIC_TYPE == IIC_TYPE_SW)
IIC_Start();
IIC_Send_Byte(addr & 0xfe);
ret_suc = IIC_Wait_Ack();
if(ret_suc == 1)
return 1; //失败
IIC_Send_Byte(IIC_Reg);
ret_suc = IIC_Wait_Ack();
if(ret_suc == 1)
return 1; //失败
IIC_Start();
IIC_Send_Byte(addr | 0x01);
ret_suc = IIC_Wait_Ack();
ret=IIC_Read_Byte(1);
IIC_Stop();
#endif
rt_exit_critical();
return (uint8_t)ret;
}
四、结果验证
如下图:
可以看到温湿度信息以及气压信息都可以成功打印出来
五、附件
代码仅供参考。
- 2022-02-25
-
回复了主题帖:
【GD32L233C-START评测】3.移植FreeRTOS到GD32L233
xiaoming668 发表于 2022-2-24 11:19
大佬,可以试下这个工程能进深度睡眠模式吗,我配置了个定时器加上,就进不去了
你看下是不是定时器影响了休眠,把定时器作为了唤醒源,让休眠进不去了,或者是已休眠就被唤醒了,
- 2022-02-19
-
发表了日志:
15. flash擦写操作,将FLASH当做EEPROM使用
-
发表了主题帖:
【GD32L233C-START评测】15. flash擦写操作,将FLASH当做EEPROM使用
本帖最后由 hehung 于 2022-2-19 16:24 编辑
之前的帖子可以参考:
【GD32L233C-START评测】1.开箱
【GD32L233C-START评测】2.手把手创建新工程
【GD32L233C-START评测】3.移植FreeRTOS到GD32L233
【GD32L233C-START评测】4. 移植RT-Thread到GD32L233
【GD32L233C-START评测】5. IIC驱动OLED
【GD32L233C-START评测】6. 获取RTC时间并通过OLED显示
【GD32L233C-START评测】7. PWM驱动LED
【GD32L233C-START评测】8. TRNG真随机数生成
【GD32L233C-START评测】9. CRC检验
【GD32L233C-START评测】10. ADC读取芯片内部温度
【GD32L233C-START评测】11.DAC输出电压值_ADC读取外部电压值
【GD32L233C-START评测】12. 硬件IIC驱动OLED
【GD32L233C-START评测】13. CAU加密算法之DES/TDES
【GD32L233C-START评测】14. CAU加密算法之AES
一、前言
我们平时在开发单片机程序的时候经常会有这样的情况:需要将某些数据保存起来掉电不丢失,我们一般会选择使用EEPROM来保存数据,但是有些单片机没有提供内部EEPROM,则需要我们外接EEPROM,这样增大了开发成本,如果我们不接外设EEPROM,需要如何操作呢?
这就需要用到我们的FLASH,将FLASH当做一个EEPROM一样进行操作,但是FLASH操作的时候也有一个缺点,就是FLASH在擦除的时候只能一页一页的擦除,不能单独擦除几个字节,所以我们在使用FLASH保存数据的时候需要注意不要将我们的代码程序擦除了,或者我们不期望擦除的程序擦除了。
一般使用FLASH来模拟EEPROM的时候,我们在擦除FLASH页的时候都需要现将数据备份,然后将待写入的数据更新,然后将擦除的整个页写入到指定的地址中去。
FLASH也是我们保存代码的地方,代码一般存储在FLASH的前面,后面一部分用不到,我们就可以用来保存我们的数据,但是如何确保我们的数据不会在下载代码的时候被覆盖呢,我们需要修改一下地址范围,在下面说明。
二、FLASH操作原理
在前言中,我们了解了FLASH作为EEPROM使用的一些基本知识之后,我们还需要知道怎么在GD32L233C中实现这个操作。
GD32L233C的FMC的操作在《用户手册》第2章 2. 闪存控制器( 闪存控制器(FMC )
1. FLASH大小
我们需要了解GD32L233C的FLASH的大小,在用户手册中没有描述,参考《数据手册》第2章 2.1. Device information
如下所示,GD32L233C的FLASH大小位256KB
2. FLASH结构
了解FLASH结构,对代码编辑必不可少,如下图,256KB大小的FLASH被分为了64页,每页4KBytes。
FLASH在擦除的时候一次性只能擦除4KBytes。
如下截图有一处笔误? 第63页,地址范围应该是0x0803F000 - 0x0803FFFF?3. FLASH解锁
在对FLASH进行操作之前需要对Flash进行解锁,操作完毕之后需要上锁。
参考《数据手册》2.3.3. FMC_CTL 寄存器解锁
本帖不在做详细描述,因为GD32L233x的库文件已经提供了解锁和上锁的函数,可以直接调用,不需要我们自己再去操作寄存器
4. FLASH页擦除
FLASH页擦除参考《用户手册》2.3.4. 页擦除
本帖不做详细描述,库文件中提供的相应的接口,直接调用即可。
注意:针对GD32L233C这款芯片,一页是4KByes,在擦除的时候谨慎操作,不要将代码区域的代码夜擦除了。
5. FLASH写入
这是本文的重点内容,可以参考《用户手册》2.3.6. 主存储闪存块编程 以及 2.3.7. 主存储闪存块 快速编程
GD32L233x提供了两种写FLASH的方式,需要注意这两种写入方式的操作逻辑以及一次写得字节大小。
对于寄存器相关的操作逻辑了解即可,GD32官方库文件已经提供了写FLASH接口,直接调用即可。
下面重要说一下这两种写入方式的区别:
(1)FLASH普通写入
使用这种方式,一次写入的数据是32位或者16位,也就是4字节或者2字节,对于C语言的unsigned int类型和unsigned short类型,在编程的时候一定要注意
本帖中提供的函数接口只适用了32位的形式,为了方便操作。
看《用户手册》中已经写得比较清楚了,如果用在写32位的时候你传入的数据不是32位的,flash不会写进去,到时候不要出现了问题不知道为什么。
模拟EEPROM使用一般就是用的这种方式,可以写入一个16位或者32位的数据
(2)FLASH快速写入
对于快速写入方式,一次性写一行,一行是32个双字,一定要注意是32个双字,不能是64个字。
应为GD32L233x是32位的单片机,所以一个字是4个字节,双字也就是8个字节,对应的数据类型是unsigned long int
所以在使用快速方式写入flash的时候,需要定义一个32个元素的数据,每个数据是8字节的,这种方式一般是用来刷新FLASH的,可以快速的写入,比如我们编写bootloader程序来更新我们的程序就可以使用这种方式来编写下载flash算法。
对于使用中的注意事项,《用户手册》中也有比较详细的描述,如下:
三、如何防止代码更新擦除了我们写在FLASH中的数据
看了《用户手册》以及《数据手册》之后,我们知道了FLASH的范围,也知道的FLASH擦写的操作逻辑以及一些注意事项,同时为了避免我们写在flash中的数据被下载代码的时候更新,我们需要对编译器进行设置一下。
前言中已经说了代码一般保存在FLASH的前面,我们要保存数据在FLASH中就写在FLASH的后边,再加上FLASH擦除是以页为单位的,所以至少也要用到一页的FLASH空间,本帖中的代码使用了2页,操作涉及到的地址范围是:0x0803E000~0x0803FFFF。
所以修改FLASH代码区域的长度修改为0x3E000,原来是0x40000,如下:
四、代码实现
1. 宏定义
宏定义中定义了FLASH的起始地址与结束地址,以及一页的数据大小,以及两种FLASH写入方式
E_OK以及E_NOT_OK是FLASH擦除校验是否成功的返回值
FMC_PROGRAM_TYPE_WORD表示FLASH的普通写入模式
FMC_PROGRAM_TYPE_FAST表示FLASH的快速写入模式
FMC_PAGE_SIZE表示FLASH一页的大小,GD32L233C是4KBytes,也就是4096,对应16进制的0x1000
FMC_START_ADDRESS是FLASH开始地址,为0x08000000
FMC_END_ADDRESS是FLASH结束地址,为0x0803FFFF
#define E_OK ((uint8_t)0U)
#define E_NOT_OK ((uint8_t)1U)
#define FMC_PROGRAM_TYPE_WORD ((uint8_t)0x00U)
#define FMC_PROGRAM_TYPE_FAST ((uint8_t)0x01U)
#define FMC_PAGE_SIZE ((uint16_t)0x1000U)
#define FMC_START_ADDRESS ((uint32_t)0x08000000U)
#define FMC_END_ADDRESS ((uint32_t)0x0803FFFFU)
2. 全局变量定义
全局变量定义了我们期望写到FLASH中的数据
data0和data1是使用普通模式写FLASH的32位数据
data_buffer是使用快速模式写入的32个双字的数组
uint32_t data0 = 0x01234567U;
uint32_t data1 = 0xd583179bU;
/* data buffer for fast programming */
static uint64_t data_buffer[DOUBLE_WORDS_CNT_IN_ROW] = {
0x0000000000000000U, 0x1111111111111111U, 0x2222222222222222U, 0x3333333333333333U,
0x4444444444444444U, 0x5555555555555555U, 0x6666666666666666U, 0x7777777777777777U,
0x8888888888888888U, 0x9999999999999999U, 0xAAAAAAAAAAAAAAAAU, 0xBBBBBBBBBBBBBBBBU,
0xCCCCCCCCCCCCCCCCU, 0xDDDDDDDDDDDDDDDDU, 0xEEEEEEEEEEEEEEEEU, 0xFFFFFFFFFFFFFFFFU,
0x0011001100110011U, 0x2233223322332233U, 0x4455445544554455U, 0x6677667766776677U,
0x8899889988998899U, 0xAABBAABBAABBAABBU, 0xCCDDCCDDCCDDCCDDU, 0xEEFFEEFFEEFFEEFFU,
0x2200220022002200U, 0x3311331133113311U, 0x6644664466446644U, 0x7755775577557755U,
0xAA88AA88AA88AA88U, 0xBB99BB99BB99BB99U, 0xEECCEECCEECCEECCU, 0xFFDDFFDDFFDDFFDDU
};
3. 擦FLASH
FLASH擦除是以页位单位的,所以传入的参数就是第几页,从数据手册中我们知道把FLASH区域分为了64页,所以可传入的参数就是0-63
该函数会擦除指定页,当传入参数是63的时候,debug会报错,见最后。
uint8_t fmc_erase_pages(uint8_t page_num)
{
uint32_t s_addr = 0;
if(page_num < 64)
{
s_addr = FMC_START_ADDRESS + (FMC_PAGE_SIZE * page_num);
/* unlock the flash program/erase controller */
fmc_unlock();
/* clear all pending flags */
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
/* erase the flash pages */
fmc_page_erase(s_addr);
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
/* lock the main FMC after the erase operation */
fmc_lock();
/* check whether erased successful */
return fmc_erase_pages_check(s_addr);
}
return E_NOT_OK;
}
4. 擦除数据校验
该函数用于检查数据指定页擦除之后数据是否全部都是0xFF,如果不是,说明没有擦除完整。
static uint8_t fmc_erase_pages_check(uint32_t s_addr)
{
uint32_t i;
uint32_t *ptrd;
uint8_t ret = E_OK;
ptrd = (uint32_t *)s_addr;
/* check flash whether has been erased */
for(i = 0; i < (FMC_PAGE_SIZE >> 2); i++)
{
/* check 4Bytse every time */
if(0xFFFFFFFF != (*ptrd))
{
ret = E_NOT_OK;
break;
}
else
{
ptrd++;
}
}
return ret;
}
5. FLASH普通模式写入数据
普通模式写入上面已经说过了,一次性写入一个32位的数据,也就是UInt32_t类型的数据,参数data就是期望写入的32位数据。
s_addr是指定写入的地址,地址范围最好使用fmc_erase_pages() 擦除过的页地址范围里面,避免没有擦除就写数据上报总线错误。
static void fmc_program_word(uint32_t s_addr, uint32_t data)
{
/* unlock the flash program/erase controller */
fmc_unlock();
/* program flash */
fmc_word_program(s_addr, (uint32_t)data);
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
/* lock the main FMC after the program operation */
fmc_lock();
}
6. FLASH快速写入模式写入数据
快速模式上面已经说过了,一次性写入32个双字,参数data就是期望写入的32个双字数据
s_addr是指定写入的地址,地址范围最好使用fmc_erase_pages() 擦除过的页地址范围里面,避免没有擦除就写数据上报总线错误。
static void fmc_program_fast(uint32_t s_addr, uint64_t* data)
{
/* unlock the flash program/erase controller */
fmc_unlock();
/* program flash */
fmc_fast_program(s_addr, data_buffer);
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR | FMC_FLAG_PGERR);
/* lock the main FMC after the program operation */
fmc_lock();
}
7. main函数
如下:
先擦除63页,地址范围是0x0803E000 ~ 0x0803EFFF
然后将data0写入地址0x0803E000
将data1写入地址0x0803E010
将数组data_buffer写入地址0x0803E014
int main(void)
{
// fmc_erase_pages(63); /* Bubug will error */
fmc_erase_pages(62);
fmc_program_word(0x0803E000, data0);
fmc_program_word(0x0803E010, data1);
fmc_program_fast(0x0803E014, data_buffer);
while(1)
{
}
}
五、检验效果
使用在线Debug的方式来运行我们的代码,然后再memory窗口中查看flash地址的数据,如下,测试成功
0x0803E000地址被写入了0x01234567,数据是反着的是因为编码方式是小端模式
0x0803E010地址被写入了0xd583179b,数据是反着的是因为编码方式是小端模式
0x0803E014地址被写入了数组data_buffer的内容
六、代码bug
在擦除最后一页flash,也就是64页的时候debug程序会报错,不知道为什么?有知道的小伙伴可以解答一下,谢谢。
如下图,也不知道是我的代码bug还是官方库的bug,等我后边再细细研究下。