打开主菜单

谷雨文档中心 β

NRF52832DK基础实验

nRF52832是Nordic公司推出一款高性能无线SOC芯片,在芯片上可以运行多种协议栈,包括蓝牙BLE,NFC,ANT,802.15.4G,其中BLE协议栈可以支持到BLE5.0。为此谷雨物联推出一款基于nRF52832芯片的开发板,nRF52832DK。

nRF52832DK开发板上设计了丰富实用的外围设备。其中有4路LED,4路按键输入,一路MINI usb转UART,三路PWM RGB灯珠,一路有源蜂鸣器,一路光敏,一路振动马达,TFT显示器接口,NFC标签接口。

方便开发者,更快,更容易上手nRF52832芯片的外设操作,为此我们提供和整理nRF52832DK外围电路实验说明。见下表所示。

实验名称 实验所需外设 实验简单说明
01_LED亮灭实验 GPIO 熟悉GPIO操作
02_按键输入实验(poll) GPIO 熟悉GPIO操作
03_按键输入实验(int) GPIO边沿中断 熟悉GPIO边沿中断
04_振动马达实验 GPIO 熟悉GPIO操作
05_蜂鸣器实验 GPIO 熟悉GPIO操作
06_RGB实验 PWM 熟悉PWM操作
07_TFT实验(tft_lcd_144,tft_lcd_130) SPI 熟悉SPI操作
08_UART收发实验 UART 熟悉UART操作
09_光照度实验 ADC 熟悉ADC操作
10_温度实验 Temperture 芯片上温度传感器
11_Flash操作 NVMC 片上Flash操作
Icon-tips.png
实验源码位于百度云盘归档资料中:归档资料/1-协议栈SDK/谷雨实验源码包/,代码的使用请参考《NRF52832DK入门手册》的蓝牙协议栈SDK一节

目录

1 LED亮灭实验

LED亮灭实验是展示nRF52832的GPIO输出配置,使开发者更直观的了解GPIO输出。GPIO输出是熟悉一款MCU的开始。下面将简单的介绍并分析相关代码。在NRF52832DK评估板上有4路LED资源,分别处在PIN17,PIN18,PIN19,PIN20四个引脚上。LED电路原理图,如下图所示。其为低电平有效,即引脚为低电平时LED被点亮,引脚为高电平时LED熄灭。

 
LED 外设电路

1.1 代码分析

开发者打开谷雨物联提供的peripheral文件夹中01_led_blinkly工程(IAR工程)。

在IAR的Workspace中点开Application,双击main.c文件,打开main.c。

 1 //******************************************************************************
 2 // fn :main
 3 //
 4 // brief : 主程序入口
 5 //
 6 // param : none
 7 //
 8 // return : none
 9 int main(void)
10 {
11   
12   LED_Init();
13 
14   for(;;)
15   {
16     //循环点亮熄灭LED,间隔500ms
17     for( uint8_t i = 0; i < LEDS_NUMBER ; i++)
18     {
19       nrf_gpio_pin_toggle(Leds[i]);
20       nrf_delay_ms(500);
21     }
22   }
23 }

其中LED_Init函数用于初始化LED引脚。将四路LED引脚初始化输出模式,并置高电平,即熄灭LED。

 1 //******************************************************************************
 2 // fn :LED_Init
 3 //
 4 // brief : 初始化LED引脚为输出模式,并熄灭LED
 5 //
 6 // param : none
 7 //
 8 // return : none
 9 void LED_Init(void)
10 {
11   uint8_t i = 0;
12   
13   //配置LED引脚为输出模式
14   nrf_gpio_range_cfg_output(LED_START, LED_STOP);
15   
16   //置LED引脚为高电平,即LED灭
17   for(i = 0 ; i < LEDS_NUMBER; i++)
18   {
19     nrf_gpio_pin_set(Leds[i]);
20   }
21 }

LED_START,LED_STOP是两个宏,标记LED开始引脚到LED结束引脚范围。配合nrf_gpio_range_cfg_output函数,可实现批量设置。nrf_gpio_pin_set设置LED引脚输出高电平。

完成LED引脚配置,进入while循环。在循环中遍历所有的LED引脚,翻转引脚高低电平,达到闪烁的目的。nrf_delay_ms函数用于软件延时。nrf_gpio_pin_toggle对引脚电平进行翻转。参数是LED引脚。在nrf_gpio_pin_toggle内部,先读取引脚当前的高低电平状态,然后根据返回的状态进行取反,再设置OUT寄存器。

1.2 实验现象

硬件准备:

  1. Jlink-Lite仿真器或J-Link仿真器
  2. NRF52832DK评估板

编译工程,点击IAR IDE工具栏中绿色三角仿真按钮,IAR便会将程序下载到nRF52832中,点击全速运行即可。此时NRF52832的LED便会每500ms依次点亮LED;当四个LED全部点亮后,再以500ms依次熄灭LED,直到全部熄灭。

2 按键输入实验(poll)

01_LED亮灭实验是操作GPIO引脚输出,而本实验是操作GPIO的输入。利用GPIO输入引脚电平变化,来监测按键按下动作。在NRF52832DE评估板上,提供了四路按键资源,分别占用PIN13,PIN14,PIN15,PIN16四个引脚上。按键电路原理图,如下图所示。由原理图可知,按键是低电平有效。当按键按下引脚为低电平,释放时会高电平。注,按键引脚必须要使能引脚上拉功能,否则可能导致触发不可靠。

 
按键外设原理图

2.1 代码分析

开发者打开谷雨物联提供的peripheral文件夹中02_key_press工程(IAR工程)。

在IAR的Workspace中点开Application,双击main.c文件,打开main.c。

 1 //******************************************************************************
 2 // fn :main
 3 //
 4 // brief : 主程序入口
 5 //
 6 // param : none
 7 //
 8 // return : none
 9 int main(void)
10 {
11   
12   LED_Init();   //LED 初始化
13   BTN_Init();   //BTN 初始化
14 
15   for(;;)
16   {
17     //循环点亮熄灭LED,间隔500ms
18     for( uint8_t i = 0; i < BUTTONS_NUMBER ; i++)
19     {
20       if(BTN_state_get(i))
21       {
22         LED_On(i);
23       }
24       else
25       {
26         LED_Off(i);
27       }
28       
29     }
30     nrf_delay_ms(100);
31   }
32 }

在LED_Init函数中调用nrf_gpio_range_cfg_output,将初始化NRF52832DK评估板LED引脚。LED将GPIO引脚初始化为输出。BTN_Init函数中调用nrf_gpio_range_cfg_input按键初始化上拉输入。此实验中只是用GPIO输入,所以只能采用轮询的方式,周期性查询按键引脚电平状态。当按键按下,BTN_state_get函数返回true,否则返回false。

为增加互动性,将用4个LED分别指示4个按键状态。按键按下点亮LED,按键释放熄灭LED。其代码实现如while中的for循环。LED_On点亮指定LED,LED_Off熄灭LED。

2.2 实验现象

硬件准备:

  1. Jlink-Lite仿真器或J-Link仿真器
  2. NRF52832DK评估板

编译工程,点击IAR IDE工具栏中绿色三角仿真按钮,IAR便会将程序下载到nRF52832中,点击全速运行即可。此时NRF52832的LED是全灭状态。

  1. 按下SW1,LED1亮;释放SW1,LED1灭
  2. 按下SW2,LED2亮;释放SW2,LED2灭
  3. 按下SW3,LED3亮;释放SW3,LED3灭
  4. 按下SW3,LED4亮;释放SW4,LED4灭

3 按键中断输入实验(int)

02_按键输入实验是采用轮询方式不断查询引脚电平状态。引脚默认为高电平,当按下按键后,引脚会变成低电平。而此实验将使用nRF52832的GPIOTE边沿中断方式来监测按键动作。中断方式监测按键,带来了高灵敏度,高效率,同时也增加了按键按下不可靠性。主要原因按键按下会产生电平抖动。要求高可靠性可以为此要加入消抖。由02的原理图中可以见,没有硬件上的消抖,所以只能在驱动程序上进行消抖操作(此例程中没加入消抖)。

在例子中,使用了RF52832的GPIOTE功能,GPIOTE与GPIO使用上有所区别。但是如果一个引脚使用了GPIOTE功能,GPIO功能将不启作用,引脚上的所有操作将由GPIOTE支配,直到GPIOTE失能。

NRF52 系列芯片在 GPIO 的基础上引入了任务和事件(GPIOTE)的概念。GPIOTE 能让我们更方便地去操作 GPIO,同时,他还能有效地减少程序的参与、降低 CPU 的负担。

如果开发者要详细了解GPIOTE可以查看nRF52832的芯片手册《nRF52832_OPS.PDF》。《nRF52832 Objective Product Specification v0.6.3》

3.1 代码分析

开发者打开谷雨物联提供的peripheral文件夹中03_key_press_int工程(IAR工程)。

在IAR的Workspace中点开Application,双击main.c文件,打开main.c。

 1 //******************************************************************************
 2 // fn :main
 3 //
 4 // brief : 主程序入口
 5 //
 6 // param : none
 7 //
 8 // return : none
 9 int main(void)
10 {
11   GPIOTE_Init();
12   
13   LED_Init();   //LED 初始化
14   BTN_Init();   //BTN 初始化
15 
16   for(;;)
17   {
18     //循环,间隔100ms
19     nrf_delay_ms(100);
20   }
21 }

在例程中使用了GPIOTE功能,所以在main函数开始处就初始化GPIOTE驱动(在使用gpiote相关函数之前,一定要先调用gpiote初始化函数nrf_drv_gpiote_init,否则可能产生不可预知问题)。接着配置LED引脚。在代码中提供两种方式,一种与02实验一至,另一种是gpiote方式。它们通过LED_GPOITE宏进行选择编译。这里主要介绍GPIOTE方式。

 1 //******************************************************************************
 2 // fn :LED_Init
 3 //
 4 // brief : 初始化LED引脚为输出模式,并熄灭LED
 5 //
 6 // param : none
 7 //
 8 // return : none
 9 void LED_Init(void)
10 {
11 #if defined(LED_GPIOTE) 
12   uint8_t i = 0;
13   
14   //配置LED引脚为输出模式
15   nrf_drv_gpiote_out_config_t out_config = GPIOTE_CONFIG_OUT_SIMPLE(true);
16   
17   //置LED引脚为高电平,即LED灭
18   for(i = 0 ; i < LEDS_NUMBER; i++)
19   {
20     nrf_drv_gpiote_out_init(Leds[i], &out_config);
21   }
22 #else  
23   uint8_t i = 0;
24   
25   //配置LED引脚为输出模式
26   nrf_gpio_range_cfg_output(LED_START, LED_STOP);
27   
28   //置LED引脚为高电平,即LED灭
29   for(i = 0 ; i < LEDS_NUMBER; i++)
30   {
31     nrf_gpio_pin_set(Leds[i]);
32   }  
33 #endif
34 }

nRF52832的GPIOTE只有8个通道,所以每个通道都要进行配置。在LED引脚中,选择简单的输出配置即GPIOTE_CONFIG_OUT_SIMPLE,它是一个宏。是对nrf_drv_gpiote_out_config_t类型成员初始化结构体。其中参数表示引脚配置成GPIOTE时默认引脚状态。 nrf_drv_gpiote_out_init函数将对指定引脚进行GPIOTE_CONFIG_OUT_SIMPLE配置。

 1 //******************************************************************************
 2 // fn :Btn_Init
 3 //
 4 // brief : 初始化Btn引脚为输入,全边沿敏感模式
 5 //
 6 // param : none
 7 //
 8 // return : none
 9 void BTN_Init(void)
10 {
11   //创建引脚配置结构
12   nrf_drv_gpiote_in_config_t btn_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(true);
13   btn_config.pull = NRF_GPIO_PIN_PULLUP;
14   //配置Btn引脚为边沿敏感
15   for(uint8_t i = 0 ; i < BUTTONS_NUMBER ; i++)
16   {
17     nrf_drv_gpiote_in_init(Btns[i], &btn_config, BTN_pin_handler);
18     nrf_drv_gpiote_in_event_enable(Btns[i], true);
19   }
20 }

BTN_Init函数是对按键的引脚进行TOGGLE sense输入配置。其中GPIOTE_CONFIG_IN_SENSE_TOGGLE也宏,是对nrf_drv_gpiote_in_config_t类型成员初始化结构体。参数true表示使用IN_EVENT配置,而非PORT_EVENT。由于按键外部硬件没有上拉,所以这里要配置成上拉,即pull成员变量设置成NRF_GPIO_PIN_PULLUP。

nrf_drv_gpiote_in_init函数将按键引脚配置成GPIOTE_CONFIG_IN_SENSE_TOGGLE模式,同时要传入中断回调函数。最后要调用nrf_drv_gpiote_in_event_enable函数使能引脚IN_EVENT。

中断方式采集按键,是一种异步方式,所以在回调函数中要进行逻辑上的处理。然而在这个例程中,四个按键使用同一个回调函数。所以要在回调函数中要进行按按键识别,包括按键位置识别,还有按键动作。

 1 //******************************************************************************
 2 // fn :BTN_pin_handler
 3 //
 4 // brief : BTN引脚边沿中断回调函数
 5 //
 6 // param : pin -> 引脚号
 7 //         action -> 极性变化形为
 8 //
 9 // return : none
10 void BTN_pin_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
11 {
12   bool flag = false;
13   switch(pin)
14   {
15   case BUTTON_1:
16   case BUTTON_2:
17   case BUTTON_3:
18   case BUTTON_4:
19     flag = true;
20     break;
21   default:
22     break;
23   }
24   if(flag)
25   {
26     uint8_t idx = BTN_Pin_To_Idx(pin);
27     //读取pin电平状态
28     if(nrf_drv_gpiote_in_is_set(pin))
29     {
30       LED_Off(idx);   //按键释放,熄灭LED
31     }
32     else
33     {
34       LED_On(idx);   //按键按下,点亮LED
35     }
36   }
37 }

其中flag是有效按键动作的标记。只有是BUTTON_1,BUTTON_2,BUTTON_3,BUTTON_4按键才是有效动作。通过nrf_drv_gpiote_in_is_set查询引脚当前电平状态,确定按键是按下,还是释放动作。并根据按键动作,点亮或熄灭LED。

3.2 实验现象

硬件准备:

  1. Jlink-Lite仿真器或J-Link仿真器
  2. NRF52832DK评估板

编译工程,点击IAR IDE工具栏中绿色三角仿真按钮,IAR便会将程序下载到nRF52832中,点击全速运行即可。此时NRF52832的LED是全灭状态。

  1. 按下SW1,LED1亮;释放SW1,LED1灭
  2. 按下SW2,LED2亮;释放SW2,LED2灭
  3. 按下SW3,LED3亮;释放SW3,LED3灭
  4. 按下SW3,LED4亮;释放SW4,LED4灭

4 振动马达实验

在NRF52832DK评估板上,设计有一路振动马达,方便开发者对蓝牙ANCS开发。其直流马达的工作原理不用多介绍,只要给它提供直流电即可工作。MOTOR端的高低电平,将会另三极管打开与关闭,从而控制振动马达工作。MOTOR是连接在nrf52832的P0.12引脚上。

4.1 代码分析

开发者打开谷雨物联提供的peripheral文件夹中04_motor工程(IAR工程)。

在IAR的Workspace中点开Application,双击main.c文件,打开main.c。

 1 //******************************************************************************
 2 // fn :main
 3 //
 4 // brief : 主程序入口
 5 //
 6 // param : none
 7 //
 8 // return : none
 9 int main(void)
10 {
11   Motor_Init();
12   LED_Init();
13 
14   for(;;)
15   {
16     //循环点亮熄灭LED,间隔500ms
17     for( uint8_t i = 0; i < LEDS_NUMBER ; i++)
18     {
19       nrf_gpio_pin_toggle(Leds[i]);
20       nrf_gpio_pin_toggle(BSP_MOTOR_0);
21       nrf_delay_ms(500);
22     }
23   }
24 }

main函数与《01_LED亮灭实验》十相似,只是在其基础上添加了MOTOR相关的代码。

Motor_Init函数是对MOTOR引脚进行输出初始化。nrf_gpio_pin_toggle函数是对MOTOR引脚的电平进行翻转,从而输出高低电平,即马达就会振动与关闭。

4.2 实验现象

硬件准备:

  1. Jlink-Lite仿真器或J-Link仿真器
  2. NRF52832DK评估板

编译工程,点击IAR IDE工具栏中绿色三角仿真按钮,IAR便会将程序下载到nRF52832中,点击全速运行即可。此时NRF52832的LED便会每500ms依次点亮LED;当四个LED全部点亮后,再以500ms依次熄灭LED,直到全部熄灭。期间,每一次LED状态改变,其马达就会工作一次或关闭一次,间隔时间是500ms。

5 蜂鸣器实验

在NRF52832DK评估板上,设计有一路有源蜂鸣器,方便开发者对蓝牙ANCS开发。其蜂鸣器的工作原理不用多介绍,只要给它提供直流电即可工作。BUZZER端的高低电平,将会另三极管打开与关闭,从而控制蜂鸣器工作。BUZZER是连接在nrf52832的P0.11引脚上。

5.1 代码分析

开发者打开谷雨物联提供的peripheral文件夹中05_buzzer工程(IAR工程)。

在IAR的Workspace中点开Application,双击main.c文件,打开main.c。

 1 //******************************************************************************
 2 // fn :main
 3 //
 4 // brief : 主程序入口
 5 //
 6 // param : none
 7 //
 8 // return : none
 9 int main(void)
10 {
11   Buzzer_Init();
12   LED_Init();
13 
14   for(;;)
15   {
16     //循环点亮熄灭LED,间隔500ms
17     for( uint8_t i = 0; i < LEDS_NUMBER ; i++)
18     {
19       nrf_gpio_pin_toggle(Leds[i]);
20       nrf_gpio_pin_toggle(BSP_BUZZER_0);
21       nrf_delay_ms(500);
22     }
23   }
24 }

main函数与《04_振动马达实验》十相似,只是将MOTOR相关代码替换成了BUZZER代码。

Buzzer_Init函数是对BUZZER引脚进行输出初始化。nrf_gpio_pin_toggle函数是对BUZZER引脚的电平进行翻转,从而输出高低电平,即蜂鸣器就会工作与关闭。

5.2 实验现象

硬件准备:

  1. Jlink-Lite仿真器或J-Link仿真器
  2. NRF52832DK评估板

编译工程,点击IAR IDE工具栏中绿色三角仿真按钮,IAR便会将程序下载到nRF52832中,点击全速运行即可。此时NRF52832的LED便会每500ms依次点亮LED;当四个LED全部点亮后,再以500ms依次熄灭LED,直到全部熄灭。期间,每一次LED状态改变,其蜂鸣器就会工作一次或关闭一次,间隔时间是500ms。

6 RGB实险

在NRF52832DK评估板上,提供一个RGB调色灯。方便开发者利用NRF52832开发蓝牙RGB灯。RGB调光灯采用三路PWM分别控制三色灯的亮度达到调色目标。在NRF52832芯片中有三个PWM外设,每个外设有4路PWM。NRF52832的PWM外设细节,可以查阅芯片手册。

从原理图可以看出,每路灯都是由三极管控制,所以控制端高电平表示打开,低电平表示关闭。

6.1 代码分析

开发者打开谷雨物联提供的peripheral文件夹中06_rgb工程(IAR工程)。

在IAR的Workspace中点开Application,双击main.c文件,打开main.c。

 1 //******************************************************************************
 2 // fn :main
 3 //
 4 // brief : 主程序入口
 5 //
 6 // param : none
 7 //
 8 // return : none
 9 int main(void)
10 {
11   GPIOTE_Init();
12   
13   LED_Init();   //LED 初始化
14   BTN_Init();   //BTN 初始化
15   RGB_PwmInit();
16   
17   for(;;)
18   {
19     //循环,间隔100ms
20     nrf_delay_ms(100);
21   }
22 }

在main函数中,RGB_PwmInit是初始化PWM函数。 在RGB_PwmInit函数中,配置了pwm输出引脚,时钟频率,计数方式等。

 1 //******************************************************************************
 2 // fn :RGB_PwmInit
 3 //
 4 // brief : 初始化RGB pwm模式
 5 //
 6 // param : none
 7 //
 8 // return : none
 9 void RGB_PwmInit(void)
10 {
11   nrf_drv_pwm_config_t const rgb_config =
12   {
13       .output_pins =
14       {
15           RGB_PWM_R , // channel 0
16           RGB_PWM_G , // channel 1
17           RGB_PWM_B , // channel 2
18           NRF_DRV_PWM_PIN_NOT_USED // channel 3
19       },
20       .irq_priority = APP_IRQ_PRIORITY_LOWEST,
21       .base_clock   = NRF_PWM_CLK_1MHz,
22       .count_mode   = NRF_PWM_MODE_UP,        //向上计数方式
23       .top_value    = NRFX_PWM_DEFAULT_CONFIG_TOP_VALUE,
24       .load_mode    = NRF_PWM_LOAD_WAVE_FORM, //WaveForm加载方式
25       .step_mode    = NRF_PWM_STEP_AUTO
26   };
27   
28   nrf_drv_pwm_init(&m_RGB, &rgb_config, rgb_pwm_handler);
29   nrf_drv_pwm_simple_playback(&m_RGB, &m_rgb_seq, 1,
30                                       NRF_DRV_PWM_FLAG_LOOP);
31 }

nrf_drv_pwm_config_t 是初始化PWM外设配置结构体。在其中设置的pwm输出引脚,中断优先级,时钟频率,计数方式,计数长度,比较值加载方式等。调用nrf_drv_pwm_init初始化函数,传入事件回调函数rgb_pwm_handler,方便开发者监视相关事件及参数修改。在此例程中,回调数主要用来修改相应PWM通道的比较值,实现PWM的占空比从0%到100%变化。改变通道数是通过按键来修改RGB_OP_CH值。

 1 //******************************************************************************
 2 // fn :rgb_pwm_handler
 3 //
 4 // brief : rgb pwm事件回调函数
 5 //
 6 // param : none
 7 //
 8 // return : none
 9 static void rgb_pwm_handler(nrf_drv_pwm_evt_type_t event_type)
10 {
11   if (event_type == NRF_DRV_PWM_EVT_FINISHED)
12   {
13     uint16_t *p_channel = NULL;
14     //获取当前wm通道数值地址
15     switch(RGB_OP_CH)
16     {
17     case PWM_R:
18       p_channel = &m_rgb_seq_values.channel_0;
19       break;
20     case PWM_G:
21       p_channel = &m_rgb_seq_values.channel_1;
22       break;
23     case PWM_B:
24       p_channel = &m_rgb_seq_values.channel_2;
25       break;
26     default:
27       return;
28       break;
29     }
30     if(value_dir)   //控制修改数据方向。
31     {
32       //UP 
33       *p_channel += RGB_MIN_STEP;
34 
35       if(*p_channel >= m_rgb_seq_values.counter_top)
36       {
37         *p_channel = m_rgb_seq_values.counter_top;
38         value_dir = false;
39       }
40     }
41     else
42     {
43       //Down
44       *p_channel -= RGB_MIN_STEP;
45       if(*p_channel >= m_rgb_seq_values.counter_top)
46       {
47         *p_channel = 0;
48         value_dir = true;
49       }
50     }
51   }
52 }

RGB_MIN_STEP是每次变化的步长,这里设置为1。value_dir是控制修改数据的方向,当比较值达到最大值或最小值时就会更改方向,使比较值向相反的方向增加或减小。

6.2 实验现象

硬件准备:

  1. Jlink-Lite仿真器或J-Link仿真器
  2. NRF52832DK评估板

编译工程,点击IAR IDE工具栏中绿色三角仿真按钮,IAR便会将程序下载到nRF52832中,点击全速运行即可。会发现评估板的RGB灯会不断的改变颜色。原因是PWMR路的PWM的占空化不断变化所致,如下图PWR所示,而其他二路PWM占空比保持不变。

 
PWMR

按键S1,S2,S3分别控制PWM的R,G,B分量。而S4则会暂停PWM的占空比,直到S1,S2,S3按下。

7 TFT显示屏实验

NRF52832DK评估板上,提供一路LCD接口。使用NRF52832的SPI外设。在SPI外设中有两种工作模式,分别为EasyDMA方式,普通DMA模式。使用DMA模式时,开发者要注意。所有使用SPI发送的数据,都必须在RAM中,否则将发生错误。SPI的CPOL与CPHA具体参数及意义,这里将不再详述。详细的细节部分,开发者可以查阅NRF52832的芯片手册。

DIS_BL与DIS_D/C引脚,是TFT屏的控制引脚。DIS_BL是控制背光,DIS_D/C是控制收发数据的属性,是命令数据,还是颜色数据。

7.1 代码分析

开发者打开谷雨物联提供的peripheral文件夹中07_tft_spi工程(IAR工程)。

在IAR的Workspace中点开Application,双击main.c文件,打开main.c。

LCD驱动部分代码说明,在此不会进行说明。开发者可以查看《谷雨显示接口原理说明》,里面详细介绍了屏幕驱动部分。

 1 //******************************************************************************
 2 // fn :main
 3 //
 4 // brief : 主程序入口
 5 //
 6 // param : none
 7 //
 8 // return : none
 9 int main(void)
10 {
11   LED_Init();   //LED 初始化
12 
13   bsp_board_lcd_init();
14   GUI_Init();
15   for(uint8_t i = 0;;)
16   {
17     GUI_SetBkColor(color[i%3]);
18     GUI_Clear();
19     GUI_DispStringAt("SPI lcd Test\r\n",0,0);
20     LED_Toggle(i++%3);
21     nrf_delay_ms(500);
22   }
23 }

在main函数中,bsp_board_lcd_init函数用于初始化SPI外设。在此例子中没有使用EasyDMA模式,而使用普通的SPI模式,具本的宏定义配置在SDK_CONFIG.H中。bsp_board_lcd_init函数中,配置SCK,MOSI,SS引脚,时钟频率,及SPI工作模式,字节方向等。NRF_DRV_SPI_DEFAULT_CONFIG是一个宏,定义了nrf_drv_spi_config_t的结构数据。

 1 //******************************************************************************
 2 // fn : bsp_board_lcd_init
 3 // 
 4 // brife : init lcd hardware.ex spi, gpio
 5 //
 6 // param : opt -> 0 OR 1
 7 //
 8 // return : none
 9 void bsp_board_lcd_init(void)
10 {
11   nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
12   spi_config.ss_pin   = LCD_SPI_SS_PIN;
13   //spi_config.miso_pin = SPI_MISO_PIN;    //NOT USED
14   spi_config.mosi_pin = LCD_SPI_MOSI_PIN;
15   spi_config.sck_pin  = LCD_SPI_SCK_PIN;
16   spi_config.frequency = NRF_DRV_SPI_FREQ_8M;
17   
18   //block mode
19   nrf_drv_spi_init(&spi, &spi_config, NULL, NULL);
20 
21   //Config the bl and mode pin to output
22   nrf_gpio_cfg_output(LCD_BL_PIN);
23   nrf_gpio_cfg_output(LCD_MODE_PIN);
24 }

NRF_DRV_SPI_DEFAULT_CONFIG宏定义内容。

 1 #define NRF_DRV_SPI_DEFAULT_CONFIG                           \
 2 {                                                            \
 3     .sck_pin      = NRF_DRV_SPI_PIN_NOT_USED,                \
 4     .mosi_pin     = NRF_DRV_SPI_PIN_NOT_USED,                \
 5     .miso_pin     = NRF_DRV_SPI_PIN_NOT_USED,                \
 6     .ss_pin       = NRF_DRV_SPI_PIN_NOT_USED,                \
 7     .irq_priority = SPI_DEFAULT_CONFIG_IRQ_PRIORITY,         \
 8     .orc          = 0xFF,                                    \
 9     .frequency    = NRF_DRV_SPI_FREQ_4M,                     \
10     .mode         = NRF_DRV_SPI_MODE_0,                      \
11     .bit_order    = NRF_DRV_SPI_BIT_ORDER_MSB_FIRST,         \
12 }

nrf_drv_spi_init函数是正式初始化spi函数。将配置好的config结构转入其中,第三,第四参数是回调函数参数。如果传入回调函数指针,将进行异步数据收发,调用收发函数将立即返回。数据接收完成后,调用回调函数。如果传入NULL,将进行阻塞模式,只有接收完成后,收发函数才会返回。 根据《谷雨显示接口原理说明》要求,要实现SPI发送函数,背光控制函数,数据命令控制函数等,方便显示屏驱动调用。

 1 //******************************************************************************
 2 // fn : BSP_LcdBL
 3 // 
 4 // brife : set the LCD_BL_PIN pin level
 5 //
 6 // param : opt -> 0 OR 1
 7 //
 8 // return : none
 9 void BSP_LcdBL(uint8_t opt)
10 {
11   nrf_gpio_pin_write(LCD_BL_PIN, opt? 1 : 0 );
12 }
13 //******************************************************************************
14 // fn : BSP_LcdCD
15 // 
16 // brife : set the LCD_MODE_PIN pin level
17 //
18 // param : opt -> 0 OR 1
19 //
20 // return : none
21 void BSP_LcdCD(uint8_t opt)
22 {
23   nrf_gpio_pin_write(LCD_MODE_PIN, opt? 1 : 0 );
24 }
25 //******************************************************************************
26 // fn : BSP_LcdCs
27 // 
28 // brife : set the LCD_BL_PIN pin level
29 //
30 // param : opt -> 0 OR 1
31 //
32 // return : none
33 void BSP_LcdCs(uint8_t opt)
34 {
35   //nrf_gpio_pin_write(SPI_SS_PIN, opt ? 1 : 0);
36 }
37 
38 uint8_t BSP_LcdSend(uint8_t data)
39 {
40   spi_tmp = data;
41   nrf_drv_spi_transfer(&spi, &spi_tmp, 1, NULL, 0);
42 
43   return NRF_SUCCESS;
44 }
45 
46 uint8_t BSP_LcdSendMore(uint8_t* buf, uint16_t len)
47 {
48   //单次发送最大255个字节,这里做了相应的分包。
49   uint8_t cnt_max = len >> 7;
50   uint8_t cnt_rest = len & 0x007F;
51   uint8_t i = 0;
52   if(cnt_max)
53   {
54     for( ; i < cnt_max ; i++)
55     {
56       nrf_drv_spi_transfer(&spi, buf + (i << 7), 128, NULL, 0);
57     }
58   }
59   if(cnt_rest)
60   {
61     nrf_drv_spi_transfer(&spi, buf + (i << 7), cnt_rest, NULL, 0);
62   }
63   return NRF_SUCCESS;
64 }

其中BSP_LcdSendMore函数里,将数据包进行了分包处理,因为nrf_drv_spi_transfer的最大长度为255。实现上述函数后,在lcd_gpio.c文件中进行调用。

上述函数只是,LCD驱动运行前的准备。在调用任务显示函数前,一定要先调用GUI_Init函数,它将初始化LCD驱动相关的结构。

7.2 实验现象

硬件准备:

  1. Jlink-Lite仿真器或J-Link仿真器
  2. NRF52832DK评估板
  3. TFT-LCD-144或TFT-LCD-130显示屏

编译工程,点击IAR IDE工具栏中绿色三角仿真按钮,IAR便会将程序下载到nRF52832中,点击全速运行即可。此时屏幕上,便会交替显示红,绿,蓝三色,同时第一行显示“SPI lcd Test”字样。

8 UART 收发实验

在NRF52832芯片上有一路UART外设。为此NRF52832DK评估板上,通过CH340C将UART与USB相连。这样开发者可以用PC完成UART通信收发实验。串口外设细节部分,这里不做说明。占用芯片引脚为P0.5,P0.6,P0.7,P0.8。

8.1 代码分析

开发者打开谷雨物联提供的peripheral文件夹中08_uart工程(IAR工程)。

在IAR的Workspace中点开Application,双击main.c文件,打开main.c。

 1 //******************************************************************************
 2 // fn :main
 3 //
 4 // brief : 主程序入口
 5 //
 6 // param : none
 7 //
 8 // return : none
 9 int main(void)
10 {
11   uint8_t len;
12   LED_Init();                     //LED 初始化
13   UART_Init(APP_UartEvtHandle);   //初始化串口
14 
15   
16   bsp_board_lcd_init();           //初始化LCD使用的外设,SPI,GPIO
17   GUI_Init();                     //初始化LCD
18   GUI_DispStringAt("Uart Test\r\n",0,0);  //显示字符
19   
20   //串口打印字符串
21   UART_Write("Uart Test\r\n",sizeof("Uart Test\r\n")-1);
22   
23   GUI_SetColor(GUI_BLUE);
24   for(;;)
25   {
26     switch(uart_evt.evt_type)
27     {
28       case UART_EVT_RX_TIMEOUT:
29         len = UART_Read(Buf,uart_evt.status);
30         UART_Write(Buf,len);      //从串口发出
31         Buf[len] = 0;
32         GUI_DispString((char const*)Buf);
33         uart_evt.evt_type = UART_EVT_NONE;
34         break;
35     default :
36       break;
37     }
38     
39     if(GUI_IsFull())    //判断是否为满屏
40     {
41       GUI_Clear();      //清屏
42       GUI_GotoXY(0,0);  //回到原点
43       FontColorChange();//改变字体颜色
44     }
45   }
46 }

其中,UART_Init是初始化串口函数,参数是传入的事件回调函数指针,可以为NULL。

 1 //******************************************************************************
 2 // fn : UART_Init
 3 //
 4 // brief : 初始化串口
 5 //
 6 // param : pHandle ->处理串口事件
 7 //
 8 // return :
 9 void UART_Init(uart_user_callback pHandle)
10 {
11   m_uart_callback = pHandle;
12   
13   uart_fifo_init();
14   uart_timer_init();
15   //baudrate = 115200
16   nrf_drv_uart_config_t uartConfig = NRF_DRV_UART_DEFAULT_CONFIG;
17   uartConfig.pseltxd = TX_PIN_NUMBER;
18   uartConfig.pselrxd = RX_PIN_NUMBER;
19   //uartConfig.pselcts = CTS_PIN_NUMBER;
20   //uartConfig.pselrts = RTS_PIN_NUMBER;
21 
22   nrf_drv_uart_init(&m_Uart,&uartConfig,uart_event_handler);
23 
24   nrf_drv_uart_rx(&m_Uart, rxBuf,1);
25 }

在UART_Init函数中,指定串口的TX,RX引脚,不使能流控制。且格式为115200,8,N,1。在调用nrf_drv_uart_init时,传入事件回调函数指针uart_event_handler,即使用导步模式。如果传入NULL,即使用阻塞模式。在uart_event_handler事件回调函数中,监视以下三个事件

  1. NRF_DRV_UART_EVT_RX_DONE
  2. NRF_DRV_UART_EVT_ERROR,
  3. NRF_DRV_UART_EVT_TX_DONE

NRF_DRV_UART_EVT_RX_DONE是接收完成之后产生的事件;NRF_DRV_UART_EVT_ERROR是串口发生错误时产生的事件,其中包括帧错误,无有效停止位等;NRF_DRV_UART_EVT_TX_DONE是发生完成后产生事件。

为有效控制数据帧之间的间隔,这里引入了定时器,用于监控串口接收空闲。其工作原理如下,当接收到有效数据时,打开定时器,在定时时间之内再次接收到数据时,清除定时器记数,从零重新记时。如果超时,则认为此帧数据已经结束,向上层上报接收超时事件,并携带此帧数据长度。定时器的初始化部分,不是此例程的重点,这里不做详细说明。下面将贴出定时器事件回调函数与串口事件回调函数。

 1 //******************************************************************************
 2 // fn : timer_uart_event_handler
 3 //
 4 // brief : 定时器事件回调函数
 5 //
 6 // param : event_type -> 事件类型
 7 //         p_context  -> 事件附加指针  
 8 //
 9 // return : none
10 
11 void timer_uart_event_handler(nrf_timer_event_t event_type, void* p_context)
12 {
13   switch (event_type)
14   {
15     case NRF_TIMER_EVENT_COMPARE0:
16       //串口接收超时
17       {
18         uart_user_evt.evt_type = UART_EVT_RX_TIMEOUT;   //上报超时事件
19         uart_user_evt.status = UART_BUF_DATA_LEN(&m_RxBuf); //携带数据长度
20         if(m_uart_callback != NULL)
21         {
22           m_uart_callback(&uart_user_evt);
23         }
24         
25       }
26       break;
27 
28     default:
29       //Do nothing.
30       break;
31   }
32 }
33 //串口事件回调数据
34 static void uart_event_handler(nrf_drv_uart_event_t * p_event, void* p_context)
35 {
36   switch (p_event->type)
37   {
38     case NRF_DRV_UART_EVT_RX_DONE:
39       {
40         //关闭超时定时器
41         nrf_drv_timer_disable(&m_TimerUart);
42         //查询空闲字节
43         if(UART_BUF_FREE_SAPCE_LEN(&m_RxBuf))  
44         {
45           //
46           fifo_set(&m_RxBuf,rxBuf);
47         }
48         
49         if(UART_BUF_FREE_SAPCE_LEN(&m_RxBuf))  
50         {
51           nrf_drv_uart_rx(&m_Uart, rxBuf,1);
52           //启动超时定时器
53           nrf_drv_timer_enable(&m_TimerUart);
54         }
55         else
56         {
57           //没有可用空间
58           uart_user_evt.evt_type = UART_EVT_RX_OVERFLOW;
59           uart_user_evt.status = UART_BUF_SIZE;
60           if(m_uart_callback != NULL)
61           {
62             m_uart_callback(&uart_user_evt);
63           }
64         }
65       }
66       break;
67     case NRF_DRV_UART_EVT_ERROR:
68        
69       break;
70     case NRF_DRV_UART_EVT_TX_DONE:
71       {
72         uint8_t tmp;
73         if (uart_data_get(&m_TxBuf,&tmp) == NRF_SUCCESS)
74         {
75             nrf_drv_uart_tx(&m_Uart, &tmp, 1);
76         }
77         else
78         {
79             // Last byte from FIFO transmitted, notify the application.
80             uart_user_evt.evt_type = UART_EVT_TX_EMPTY;
81             if(m_uart_callback != NULL)
82             {
83               m_uart_callback(&uart_user_evt);
84             }
85         }
86       }
87         break;
88     default:
89         break;
90   }
91 }

在timer_uart_event_handler函数中,主要监视定时器的超时事件,向上层上报串口接收超时事件。在uart_event_handler函数中,完成接收与发送工作。为配合串口收发工作,程序中引入了发送与接收缓存 buf_fifo_t结构。read_pos记录获取数据位置,write_pos记录写入数据位置。

 1 //******************************************************************************
 2 // Name : buf_fifo_t
 3 //
 4 // brief : 串口接收缓存
 5 //
 6 typedef struct
 7 {
 8   uint8_t* pbuf;
 9   volatile uint16_t  read_pos; 
10   volatile uint16_t  write_pos;
11 }buf_fifo_t;

为方便使用,同时实现fifo_get与fifo_set两函数。fifo_get 是从read_pos位置读取数据,并更改read_pos;fifo_set 是将数据写入write_pos位置,并更新write_pos。

static __INLINE void fifo_get(buf_fifo_t* pFifo, uint8_t * p_byte)
{
  *p_byte = pFifo->pbuf[pFifo->read_pos];
  
  pFifo->read_pos++;
  if(pFifo->read_pos >= UART_BUF_SIZE)
  {
    pFifo->read_pos = 0;
  }
}

static __INLINE void fifo_set(buf_fifo_t* pFifo, uint8_t * p_byte)
{
  pFifo->pbuf[pFifo->write_pos] = *p_byte;
  
  pFifo->write_pos++;
  if(pFifo->write_pos >= UART_BUF_SIZE)
  {
    pFifo->write_pos = 0;
  }
}

完成串口驱动部分后,在main函数中,只要监视UART_EVT_RX_TIMEOUT与UART_EVT_RX_OVERFLOW事件,并在事件处理结构中,对串口数据进行读取并显示。

8.2 实验现象

硬件准备:

  1. Jlink-Lite仿真器或J-Link仿真器
  2. NRF52832DK评估板
  3. TFT-LCD-144或TFT-LCD-130显示屏

软件准备:

串口助手或相类似的软件

编译工程,点击IAR IDE工具栏中绿色三角仿真按钮,IAR便会将程序下载到nRF52832中,点击全速运行即可。打开串口助手,选择相应串口号,通信格式为115200,8,N,1。在串口助手中发送数据,此时NRF52832DK上的LCD显示屏会显示发送的数据,同时串口助手本身也会收到发送的数据。

9 光照度实验

光照度是通过光敏传感器,将光照度转成电信号。在NRF52832DK评估板上,利用光敏元件,将光强度转成电压信号,通过ADC对电压信号进行采集,从而可以测量光照度。其原理图如下图所示。

当光照度增加时,流过Q6的电流变大,从而分得的电压变小;当光照度变小时,流过Q6的电流变小,从而分得的电压变大。所以光照度与电压信号成反比。

NRF52832芯片中8路ADC采集通道。可以配置成单端模式和差分模式。本例程中光敏器件是连接在P0.30引脚上,即ADC通道6。采用单端方式对电压进行采集。

9.1 代码分析

开发者打开谷雨物联提供的peripheral文件夹中09_adc工程(IAR工程)。

在IAR的Workspace中点开Application,双击main.c文件,打开main.c。

 1 int main(void)
 2 {
 3   LED_Init();           //LED 初始化
 4 
 5   bsp_board_lcd_init(); //LCD 实始化
 6   GUI_Init();
 7   GUI_SetBkColor(GUI_BLUE);//设置蓝色背景色
 8   GUI_Clear();          
 9   GUI_SetColor(GUI_RED);   //设置红色字体
10   GUI_DispString("ADC Example");
11   LUX_SaadcInit();         //ADC初始化      
12   
13   for(;;)
14   {
15 #if defined(SAADC_BLOCKING)
16     //发起ADC采集
17     if(nrf_drv_saadc_sample_convert(NRF_SAADC_INPUT_AIN6,m_buffer_pool) == NRF_SUCCESS)
18     {
19       GUI_DisprintfAt(0,32,"ADC:%04d",m_buffer_pool[0]);
20     }
21 #else
22     //发起ADC采集
23     nrf_drv_saadc_sample();    
24 #endif
25     LED_Toggle(0);      //翻转LED0
26     nrf_delay_ms(500);
27   }
28 }

main函数中,对LED,ADC,LCD进行初始化与设置。ADC具体的初始化在LUX_SaadcInit函数中,对ADC通道进行配置。main函数for循环中,使用nrf_delay_ms延时函数,周期性地触发ADC采集,同时翻转LED0进行指示。 例子中,采用SAADC_BLOCKING宏定义进行编译区分ADC同步采集和ADC异步采集。完成ADC转换后,将结果通过显示屏显示出。

 1 void LUX_SaadcInit(void)
 2 {
 3 
 4   nrf_saadc_channel_config_t channel_config =
 5       NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN6);
 6   nrf_drv_saadc_init(NULL, saadc_callback);
 7 
 8   nrf_drv_saadc_channel_init(6, &channel_config);
 9   
10 #ifndef  SAADC_BLOCKING 
11   nrf_drv_saadc_buffer_convert(m_buffer_pool, SAMPLES_IN_BUFFER);
12 #endif
13 }

LUX_SaadcInit函数中,调用nrf_drv_saadc_init函数初始化ADC,并传入saadc_callback事件回调函数(异步ADC采集使用)。nrf_drv_saadc_channel_init函数对ADC通道6,进行默认配置。其中包括参考源,增益,单端模式,引脚等配置。下面是saadc_callback回调函数的实现,在其中会监视NRF_DRV_SAADC_EVT_DONE事件(ADC采集转转换完成),并将结果显示在屏幕上。此回调函数调用,只在进行ADC异步转换时,才会被调用。

 1 void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
 2 {
 3     if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
 4     {
 5       nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAMPLES_IN_BUFFER);
 6       
 7     }
 8     GUI_DisprintfAt(0,32,"ADC:%04d\r\n",p_event->data.done.p_buffer[0]);
 9     for(uint8_t i = 1 ; i < SAMPLES_IN_BUFFER ; i++)
10     {
11       GUI_Disprintf("ADC:%04d\r\n",p_event->data.done.p_buffer[i]);
12     }
13 }

9.2 实验现象

硬件准备:

  1. Jlink-Lite仿真器或J-Link仿真器
  2. NRF52832DK评估板
  3. TFT-LCD-144或TFT-LCD-130显示屏

编译工程,点击IAR IDE工具栏中绿色三角仿真按钮,IAR便会将程序下载到nRF52832中,点击全速运行即可。此时NRF52832DK上的LCD显示屏会显示光敏器件电压的ADC结果,同时每转换一次,LED0会状态会翻转一次。

10 温度传感器实验

NRF52832芯片内部,自带一个温度传感器,并有线性补尝。它最大的分辨率为0.25度。本实验利用温度传感测量芯片周围环境的温度。它工作不需要外围电路。

温度测量通过触发TASK_START,当完成测量时DATARDY事件将会产生。通过读取TEMP寄存器,读取当前温度。

10.1 代码分析

开发者打开谷雨物联提供的peripheral文件夹中10_temp工程(IAR工程)。

在IAR的Workspace中点开Application,双击main.c文件,打开main.c。

 1 int main(void)
 2 {
 3   int32_t volatile temp = INT32_MAX;
 4   LED_Init();           //LED 初始化
 5 
 6   bsp_board_lcd_init(); //LCD 实始化
 7   GUI_Init();
 8   
 9   UpdateLCD(temp);
10   
11   for(;;)
12   {
13     NRF_TEMP->TASKS_START = 1;  //启动温度测量任务
14     while (NRF_TEMP->EVENTS_DATARDY == 0)
15     {
16         // Do nothing.
17     }
18     NRF_TEMP->EVENTS_DATARDY = 0;
19     //读取温度值
20     temp = (nrf_temp_read() / 4);
21     NRF_TEMP->TASKS_STOP = 1;    //停止测量任务
22     UpdateLCD(temp);
23     
24     LED_Toggle(0);               //翻转LED0
25     nrf_delay_ms(500);
26   }
27 }

在main函数前部,对用到的外设进行初始化,包括LED,LCD。在for循环中,启动,停止温度测量。RNF_TEMP->TASKS_START置1,将启动温度测量,NRF_TEMP->EVENTS_DATARDY是测量完成事件,如果DATARDY事件置1,表明温度测量已经完成。nrf_temp_read函数将读取TEMP寄存器返回当前温度值。其值除以4,即丢弃小数部分。 UpdateLCD函数,用于改变屏幕的内部。其输入参数为当前的温度值。在其内部对温度区间进行划分,小于20度属于NORMAL,显示屏会显示绿色背景;在20到27度之间,属于WARN,显示屏会显示黄色;大于27度,属于EMERGENT,显示屏会显示红色。同时也会显示当前温度值。

1 typedef enum
2 {
3   TEMP_DEFAULT,
4   TEMP_NORMAL,
5   TEMP_WARN,
6   TEMP_EMERGENT
7 }TEMP_RANGE;
 1 void UpdateLCD(int32_t temp)
 2 {
 3   static TEMP_RANGE LEVEL = TEMP_DEFAULT;
 4   bool isChange = false;
 5   if(temp == INT32_MAX)
 6   {
 7     LEVEL = TEMP_DEFAULT;
 8     isChange = true ;
 9   }
10   else
11   {
12     if(temp < 20)
13     {
14       if(LEVEL != TEMP_NORMAL)
15       {
16         LEVEL = TEMP_NORMAL;
17         isChange = true;
18       }
19       
20     }
21     else if(temp < 27)
22     {
23       if(LEVEL != TEMP_WARN)
24       {
25         LEVEL = TEMP_WARN;
26         isChange = true;
27       }
28     }
29     else
30     {
31       if(LEVEL != TEMP_EMERGENT)
32       {
33         LEVEL = TEMP_EMERGENT;
34         isChange = true;
35       }
36     }
37   }
38   if(isChange)
39   {
40     switch(LEVEL)
41     {
42       case TEMP_DEFAULT: 
43         GUI_SetBkColor(GUI_WHITE);//设置蓝色背景色
44         break;
45       case TEMP_NORMAL:
46         GUI_SetBkColor(GUI_GREEN);//设置蓝色背景色   
47         break;
48       case TEMP_WARN:
49         GUI_SetBkColor(GUI_YELLOW);//设置绿色背景色
50         break;
51       default:
52         GUI_SetBkColor(GUI_RED);//设置红色背景色
53         break;
54     }
55     GUI_Clear();
56     GUI_DispString("Temperture\r\nExample"); 
57     isChange = false;
58   }
59   if(LEVEL)
60   {
61     GUI_DisprintfAt(0,32,"Temp:%dC",temp);
62   }
63 }

10.2 实验现象

硬件准备:

  1. Jlink-Lite仿真器或J-Link仿真器
  2. NRF52832DK评估板
  3. TFT-LCD-144或TFT-LCD-130显示屏

编译工程,点击IAR IDE工具栏中绿色三角仿真按钮,IAR便会将程序下载到nRF52832中,点击全速运行即可。此时NRF52832DK上的LCD显示屏会显示当前的温度值。并根据温度值,显示屏的背景色也会改变。

11 Flash读写操作

一般MCU都有两种类型的存储单元:易失性存储单元和非易失性存储单元。易失性存储单元一般俗称RAM,非易失性存储单元一般俗称ROM。它们的特性这里不做详细说明。本例程中主要描述与操作对象为FLASH,即ROM。在NRF52832芯片中,操作FLASH是通过NVMC控制器进行管理。它们都会被芯片映射到地址空间中,如下图所示。

NRF52832使用Code区域的低地址空间映射Flash,作为代码区域。除了可以存储CPU执行程序外,也可以用于存储数据常量。NRF52832片上共有512K字节大小的Flash,分为128页,每页大小为4K。每页分为8个块,每块大小512字节。

对Flash进行写数据,要先擦除,然后再写入(必须要字对齐)。

11.1 代码分析

开发者打开谷雨物联提供的peripheral文件夹中11_flash_readwrite工程(IAR工程)。

在IAR的Workspace中点开Application,双击main.c文件,打开main.c。

 1 int main(void)
 2 {
 3   clock_config();                          //初始化时钟
 4   
 5   LED_Init();                              //LED 初始化
 6   UART_Init(APP_UartEvtHandle);            //初始化串口
 7   Flash_info_init();
 8   bsp_board_lcd_init();                    //初始化LCD使用的外设,SPI,GPIO
 9   GUI_Init();                              //初始化LCD
10   GUI_DispStringAt("Flash Test\r\n",0,0);  //显示字符
11   GUI_SetColor(GUI_BLUE);
12   //串口打印字符串
13   printf("Flash Test\r\n");
14   Flash_info_out();
15   printf(CLI_PROMPT_STRINGS);
16   for(;;)
17   {
18     APP_UartPoll();
19   }
20 }

main函数中,对用到的外设和软件模块进行初始化。其中flash_info_out函数获取芯片的flash信息,包括页数,页大小,flash大小,RAM大小等。

 1 static void Flash_info_out(void)
 2 {
 3   uint32_t codesize = NRF_FICR->CODESIZE;
 4   uint32_t codepagesize = NRF_FICR->CODEPAGESIZE;
 5   uint32_t ramsize = NRF_FICR->INFO.RAM;
 6   uint32_t romsize = NRF_FICR->INFO.FLASH;
 7   uint32_t deviceid0 = NRF_FICR->DEVICEID[0];
 8   uint32_t deviceid1 = NRF_FICR->DEVICEID[1];
 9   
10   
11   //打印芯片信息
12   printf("FLASH INFO:\r\npage num :%d\r\npage size:%d\r\nram size:%dk\r\nflash size:%dk\r\ndevice id0:0x%08X\r\ndevice id1:0x%08X\r\n", 
13            codesize,
14            codepagesize,
15            ramsize,
16            romsize,
17            deviceid0,
18            deviceid1
19            );
20  
21   GUI_Disprintf("\r\nPNum:%d\r\n",flash_info.page_num);
22   GUI_Disprintf("PSize:%d\r\n",flash_info.page_size);
23   GUI_Disprintf("Addr:0x%X\r\n",flash_info.opt_addr);
24 }

CLI_PROMPT_STRINGS是命令行提示字符,用于提示命令输入。

#define CLI_PROMPT_STRINGS   "\r\nNRF_Flash#:"

所有串口命令处理都在APP_UartPoll函数中。有关串口低层收发处理,此例程中不作说明,开发者可以查看《08_UART收发实验》例程。

 1 static int APP_UartPoll(void)
 2 {
 3   uint8_t len = 0;
 4   switch(uart_evt.evt_type)
 5   {
 6     case UART_EVT_RX_TIMEOUT:
 7     case UART_EVT_RX_OVERFLOW:
 8       //检查是否将会溢出
 9       if((cmd_buf.len + uart_evt.status) > (USER_CMD_MAX_LEN -1))
10       {
11         len = UART_BUF_SIZE - 1 - cmd_buf.len;
12       }
13       else
14       {
15         len = uart_evt.status;
16       }
17       //回显
18       len = UART_Read(cmd_buf.Buf + cmd_buf.len,len);
19       UART_Write(cmd_buf.Buf + cmd_buf.len,len);
20       cmd_buf.len += len;
21       cmd_buf.Buf[cmd_buf.len] = 0;
22       
23       //进行命令解析
24       if((cmd_buf.len >= USER_CMD_MAX_LEN) || (strchr((char const*)cmd_buf.Buf,'\r')))
25       {
26         printf("\r\n");
27         if(CLI_Process(cmd_buf.Buf))
28         {
29           printf("error\r\n");
30         }
31         cmd_buf.len = 0;
32         printf(CLI_PROMPT_STRINGS);
33       }
34       
35       uart_evt.evt_type = UART_EVT_NONE;
36       break;
37   default :
38     break;
39   }
40   return len;
41 }

串口在收到控制台发来数据后,通过UART_EVT_RX_TIMEOUT事件回调到上层,并将收到数据回显到控制台,以显示开发者输入字符。接下来就是对命令数据进行结束识别。结束条件分为两种:一是最大长度为USER_CMD_MAX_LEN;二是回车换行符。 最终对命令进行解析的是CLI_Process函数。利用strtok库函数对命令数据进行分割,分割字符为空格。所以开发者在命令输入时,要以空格作为分隔。最后将分割的数据与个数传入CMD_ExcuteHandle函数。

 1 CMD_CODE_T CLI_Process(uint8_t* buf)
 2 {
 3   char *pTmp = NULL;
 4   char* argv[10];
 5   size_t count = 0;
 6   
 7   if(buf == NULL)
 8   {
 9     //数据内容为空,直接返回
10     return CMD_BAD_BUF;
11   }
12   
13   pTmp = (char*)strchr((char const*)buf,'\r');
14   if(pTmp)
15   {
16     *pTmp = '\0';
17   }
18   pTmp = (char*)buf;
19   
20   //提取指令返回的参数,此时buf已经遭到破坏
21   while((argv[count] = strtok(pTmp," ")) != NULL)
22   {
23     count++;
24     pTmp = NULL;
25     if(count >=10)
26     {
27       break;
28     }
29   }
30   return CMD_ExcuteHandle(count,argv);
31   
32 }

CMD_ExcuteHandle函数会根据输入参数查找命令库。如果查找到将执行命令,否则返回CMD_NOT_FIND。 与命令相关的数据结构如下。

typedef void (*cmd_handler)(size_t argc, char **argv);

typedef struct
{
  const char*  cmd;       //cmd
  cmd_handler  handler;   //handler
}cmd_item_t;

其中cmd是命令名字,handler是命令执行函数。所以开发者,想要实现命令,只要向cmd_item中添加命令名字与处理函数即可,如下。

1 //增加命令条目
2 const cmd_item_t cmd_item[] = 
3 {
4   {STRINGIFY(read),Flash_read_cmd},
5   {STRINGIFY(write),Flash_write_cmd},
6   {STRINGIFY(erase),Flash_erase_cmd},
7   {NULL,NULL}
8 };

11.2 实验现象

硬件准备:

  1. Jlink-Lite仿真器或J-Link仿真器
  2. NRF52832DK评估板
  3. TFT-LCD-144或TFT-LCD-130显示屏

编译工程,点击IAR IDE工具栏中绿色三角仿真按钮,IAR便会将程序下载到nRF52832中,点击全速运行即可。此时NRF52832DK上的LCD显示屏会显地当前操作Flash信息,包括页号,此页大小及所在地址。而此时控制台也会收到相应的信息。

开发者可以根据命令条目,输入命令进行简单调试。read -> write -> read。