第3章 CHAPTER 3 智慧照明平台终端软件设计 智慧照明平台终端主要指本系统的实验箱,包括传感器板、中控制器即中控板和总控制器即大板,本章主要介绍传感器信息采集、本地控制和远程控制的程序设计方法和代码,包含网络传输、总控板实验程序设计、局域网网关程序设计等各模块的程序思路及主要代码的讲解。 3.1开发软件介绍及使用 本实验箱采用Keil uVision5进行开发,即Keil的第5版。 Keil公司是一家业界领先的微控制器(MCU)软件开发工具的独立供应商。Keil公司由两家私人公司联合运营,分别是德国慕尼黑的Keil Elektronik GmbH 和美国德克萨斯的Keil Software Inc。Keil公司制造和销售种类广泛的开发工具,包括ANSI C编译器、宏汇编程序、调试器、连接器、库管理器、固件和实时操作系统核心(RealTime Kernel)。有超过10万名MCU开发人员在使用这种得到业界认可的解决方案。Keil C51编译器自1988年引入市场以来已成为事实上的行业标准,并支持超过500种8051变种。MDK即RealView MDK或MDKARM(Microcontroller Development kit),是ARM公司收购Keil公司以后,基于uVision界面推出的针对ARM7、ARM9、CortexM0、CortexM1、CortexM2、CortexM3、CortexM4等ARM处理器的嵌入式软件开发工具。MDKARM集成了业内最领先的技术,包括uVision5集成开发环境与RealView编译器RVCT。它支持ARM7、ARM9和最新的CortexM3/M1/M0核处理器,可自动配置启动代码,集成Flash烧写模块,具有强大的设备模拟、性能分析等功能,与ARM之前的工具包ADS等相比,RealView编译器的最新版本可将性能改善超过20%。 Keil公司开发的ARM开发工具MDK,是用来开发基于ARM核的系列MCU的嵌入式应用程序。它适合不同层次的开发者使用,包括专业的应用程序开发工程师和嵌入式软件开发的入门者。MDK包含了工业标准的Keil C编译器、宏汇编器、调试器、实时内核等组件,支持所有基于ARM的设备,能帮助工程师按照计划完成项目。 3.1.1Keil 5的安装 1. 安装 双击如图31所示图标进行安装,进入如图32所示的安装界面,单击Next。 图31Keil 5安装包 图32安装界面 在安装过程中出现以下界面。 (1) 选中同意软件使用条约,单击Next(下一步); (2) 选择安装路径(建议选择默认C盘路径),单击Next(下一步); (3) 填写用户名(First name)与邮箱(EMail)(任意填写),单击Next(下一步); (4) 正在安装,等待安装进度条完成; (5) 去掉“Show Release Notes”对勾,安装完成,单击Finish(完成)。 2. 添加器件库芯片包 (1) 下载芯片包。 官方下载界面如图33所示。找到GD32F30x系列的芯片包,如图34所示。将芯片包下载到Keil 5的安装根目录下。 图33官方下载界面 图34GD32F30x系列的芯片包 (2) 安装芯片包。 双击芯片包.pack文件,如图35所示。 (3) 进入添加器件库安装包界面,此步骤自动搜寻MDK5软件安装路径。如图36所示,完成本步骤后单击Next。然后进入添加器件库安装包进度条,完成这一步后单击Next。进入安装,等待完成后单击Finish,如图37所示,则器件库安装完成。 (4) 打开Keil 5,新建工程,芯片包已经安装好了,如图38所示。 3. 激活MDK (1) 右击桌面上的Keil图标,如图39所示。在弹出的选项卡中选择以管理员身份运行。 图35双击GD32F30x系列的芯片包 图36自动搜寻软件安装路径 图37等待GD32F30x芯片包安装完成 图38GD32F30x芯片包安装完成 (2) 进入软件,选择File→License Management,如图310所示。 图39右击Keil图标 图310选择License Management (3) 复制ID号,如图311所示。 图311复制ID号 (4) 如需破解,需自行找到注册机,双击打开注册机软件,如图312所示。 (5) 粘贴ID号,选择ARM,单击Generate按钮,获得注册号并复制,如图313所示。 图312注册机 图313得到注册号 (6) 粘贴注册号,单击添加进行注册(出现如图314所示界面,即代表注册成功) 图314注册成功 至此,MDK5安装完成。 3.1.2下载器的安装与配置 GD32下载程序需要用仿真器进行下载,可以用STLINK也可用JLINK,本实验平台采用JLINK下载器。 1. JLINK下载器驱动安装 首先从官网下载最新的JLINK驱动软件,JLink ARM software and documentation pack,内含USB driver、JMem、JLink.exe and DLL for ARM、JFlash and JLink RDI。注意: SEGGER公司升级比较频繁,请密切留意SEGGER公司网站,下载最新驱动,以支持更多器件。安装驱动很简单,只要将下载的ZIP包解压,然后直接安装即可。默认安装是一路单击“Yes”即可,如图315所示。 图315JLINK驱动安装 安装完成后,插入JLINK硬件,系统会提示发现新硬件。一般情况下会自动安装驱动,如果没有自动安装,请选择手动指定驱动程序位置(安装目录),然后将驱动程序位置指向JLINK驱动软件安装目录下的Driver文件夹,驱动程序就在该文件夹下。安装完成后桌面出现两个快捷图标,JLink ARM可以用来进行设置和测试。 2. Keil 5开发环境对JLINK的配置方法 本实验平台的开发环境是Keil 5,要想利用JLINK向电路板中的MCU下载Keil 5中编写的程序,就需要配置JLINK。下面是Keil 5的JLINK配置流程。 图316编译项目 (1) 首先用JLINK连接电路板,打开一个项目并编译项目,如图316所示。 (2) 单击Option for target→Debug,选择JLINK/JTRACE Cortex,如图317所示,勾选Run to main(),如图318所示。 图317JLINK目标资源配置 (3) 单击Setting,会弹出另一个页面,在Debug菜单中选择Port:SW和Max:10MHz。 这里要注意Prot: SW与JTAG模式。使用JTAG模式这个可以不做改动,但使用SW模式的情况下必须将Port选择为SW,如果正确,在链接成功后会在SWDIO中显示图319所示内容。 (4) 在Flash Download菜单中勾选Program、Verify、Reset and Run,然后单击Add,添加芯片的支持包,如图320所示。 (5) 选择型号和Flash Size相符合的选项,单击Add后单击确定,如图321所示。 (6) 在Option for target→Utilities中勾选Use Debug Driver和Update Target before Debugging,实现芯片支持包的添加,如图322所示。 (7) JLINK配置完成,可以单击下载程序了,如图323所示。 图318选择JLINK/JTRACE Cortex 图319选择Port:SW和Max:10MHz 图320选择Program、Verify、Reset and Run 图321添加芯片的支持包 图322勾选Use Debug Driver和Update Target before Debugging 图323下载程序 3.1.3Keil 5的使用 1. 打开软件程序工程 在硬件连接没问题之后,打开任意一个工程,双击Project文件夹下的template可执行程序(如图324所示),如图325所示就表示成功打开了一个工程。 图324双击可执行程序 图325成功打开工程 2. 工程编辑、编译和调试下载 每个工程示例程序都是配置好的,采用JLINK下载程序调试,查看是否配置好下载工具,可以按照以下步骤。 1) 编辑 示例程序已经都编辑好,不需要再编辑代码。如果需要自己修改,需按图326所示在Project中单击需要编辑的文件,然后在主框图中编写和修改,最后单击保存按钮。 图326在箭头处编辑代码 2) 编译 如图327所示,单击编译按钮,其中,箭头1所指是部分编译按钮,箭头2所指是全部编译按钮,如果箭头3所示Error值为0,说明编译通过,可以进入下载步骤,否则还需把错误的地方改正才能进入下载步骤。 图327编译代码 3) 下载 如图328所示,单击(箭头1所指)Load,Build Output中会提示Programming Done,Verify OK,说明下载成功。 图328下载程序 4) 调试运行 如图329所示,先按箭头1所指按钮(Start/Stop Debug Session),光标就会跳到main函数,再按箭头2所指按钮(全速运行),程序就完全跑起来了,比如流水灯例程可以在主板上看到LED灯每秒依次点亮,箭头3所指按钮是当全速运行起来之后,按了Stop按钮,单步调试时才用得上。 图329调试运行 3.1.4Keil 5的常见问题排查 1. JLINK下载程序找不到CortexM器件的解决方法 JLINK下载程序的时候显示“No CortexM Device found in JTAG chain. Please check the JTAG cable and the connected devices.”,如图330所示。 图330JLINK下载程序常见问题 解决办法: 单击Option for target→Debug,选择JLINK/JTRACE Cortex,单击Settings,Port选择SW,如图331所示。 图331JLINK下载程序常见问题解决方法 2. JLINK下载提示NO JLink found问题解决方法 如图332所示,JLINK下载过程中有时会提示NO JLink found问题,解决办法: 检查JLINK与计算机之间接线,连线没问题的话,检查驱动,重新安装驱动。 图332NO JLink found 3. JLINK下载提示NO STLINK detected问题的解决方法 JLINK下载过程中,有时会提示NO STLINK detected,如图333所示。出现问题的原因是下载JLINK的时候没有正确勾选JLINK下载器,而是选择了STLink下载器。 图333NO STLINK detected 解决办法: 单击Option for target→Debug,选择JLINK/JTRACE Cortex,勾选Run to main(),如图334所示。 图334选择JLINK/JTRACE Cortex 3.2传感器端 传感器端即数据采集端,传感器采集的信息经模数转换后传送给处理器,根据传感器采集的不同数值对LED灯进行调光调色的控制。其中传感器和LED灯的颜色和亮度的值,是预置的数字,可以根据实际应用场景进行调整。 本实验套件传感器包括温湿度、红外测距、人感、声音和光敏传感器。温湿度传感器有自己特有的时序,所以单独讲解,其他四种信号均接到传感器板上处理器的ADC引脚,程序设计类似,所以统一设计和讲解。 3.2.1温湿度传感器 1. DHT11温湿度传感器 1) DHT11温湿度传感器简介 温湿度数据采集使用DHT11数字温湿度传感器。DHT11是一款温湿度一体化的数字传感器。该传感器包括一个电阻式测湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。通过单片机等微处理器简单的电路连接就能够实时采集本地湿度和温度。DHT11与单片机之间能采用简单的单总线进行通信,仅仅需要一个I/O口。传感器内部湿度和温度数据以40bit的数据一次性传给单片机,数据采用校验和方式进行校验,有效地保证数据传输的准确性。DHT11功耗很低,5V电源电压下,工作平均最大电流为0.5mA。 2) DHT11温湿度传感器数据传输 DHT11温湿度传感器采用单总线数据格式。单个数据引脚端口完成输入输出双向传输。其数据包由5字节(40位)组成。数据分小数部分和整数部分,一次完整的数据传输为40位,高位先出。 DHT11的数据格式为: 8位湿度整数数据+8位湿度小数数据+8位温度整数数据+8位温度小数数据+8位校验和。其中,校验和数据为前四个字节相加,如图335所示。 传感器数据输出的是未编码的二进制数据。数据(湿度、温度、整数、小数)之间应该分开处理。 图335DHT11的数据格式 由以上数据就可得到湿度和温度的值,计算方法: 湿度=byte4 . byte3=45.0 (%RH) 温度=byte2 . byte1=28.0 (℃) 校验=byte4+ byte3+ byte2+ byte1=73(校验正确) 3) DHT11数据发送流程 首先主机发送开始信号,即拉低数据线,保持t1(至少18ms)时间,然后拉高数据线t2(20~40μs)时间,然后读取DHT11的响应。正常情况下,DHT11会拉低数据线,保持t3(40~50μs)时间,作为响应信号,然后DHT11拉高数据线,保持t4(40~50μs)时间后,开始输出数据。DHT11数据发送流程如图336所示。 图336DHT11数据发送流程 DHT11输出数字'0'的时序,如图337所示。 图337DHT11输出数字'0'的时序 DHT11输出数字'1'的时序,如图338所示。 图338DHT11输出数字'1'的时序 2. 程序实现 (1) DHT11的输入输出模式的配置关键代码。 cmd = 1,配置为输出模式;cmd = 0,配置为输入模式。 void DHT11_Read_Out_Input(uint8_t cmd) { if(cmd == 1) { gpio_mode_set(DHT11_GPIO,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,DHT11_Pin); gpio_output_options_set(DHT11_GPIO,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,DHT11_Pin); } else { gpio_mode_set(DHT11_GPIO,GPIO_MODE_INPUT,GPIO_PUPD_PULLUP,DHT11_Pin); } } (2) 读取温湿度数据。 uint8_t DHT11_Read_Huim_Temp(uint8_t *HuimH,uint8_t *HuimL,uint8_t *TempH,uint8_t *TempL) { uint8_t i; uint8_t Data[5]; //主机发送开始信号 DHT11_Read_Out_Input(1); DHT11_Low; delay_1ms(20); //20ms DHT11_High; delay_1us(40); //DHT11响应信号 DHT11_Read_Out_Input(0); while((DHT11_Input == RESET) && (++Time <1000) ); Time = 0; while((DHT11_Input == SET) && (++Time <1000) ); Time = 0; for(i=0;i<5;i++){ Data[i] = DHT11_Read_Byte(); } delay_1ms(3); if((Data[0]+Data[1]+Data[2]+Data[3]) == Data[4]){ *HuimH = Data[0]; *HuimL = Data[1]; *TempH = Data[2]; *TempL = Data[3]; } else{ return 1; } return 0; } 3.2.2红外、人感、声音和光敏传感器 1. 数据采集简介 数据采集模块采用兆易创新公司的GD32F330F6P6微控制器作为主控芯片。GD32F330系列器件是基于Arm CortexM4处理器的32位通用微控制器。Arm CortexM4处理器包括三条AHB总线,分别称为ICODE总线、DCode总线和系统总线。CortexM4处理器的所有存储访问,根据不同的目的和目标存储空间,都会在这三条总线上执行。存储器组织采用哈佛结构,预先定义的存储器映射和高达4GB的存储空间,充分保证了系统的灵活性和可扩展性。 GD32F330F6P6主控芯片自带1个12位ADC,此处数据采集使用ADC的通道4作为A/D采集输入端,采集红外、人感、声音和光敏传感器的模拟量输出,然后再转换成数字量。 2. 程序实现 (1) 配置ADC的GPIO端口为模拟输入。 void adc_gpio_config(void) { gpio_mode_set(GPIOA,GPIO_MODE_ANALOG,GPIO_PUPD_NONE,GPIO_PIN_1); } (2) ADC配置。 void adc_config(void) { adc_deinit(); adc_channel_length_config(ADC_REGULAR_CHANNEL, 1); adc_regular_channel_config(0, ADC_CHANNEL_1, ADC_SAMPLETIME_55POINT5); adc_external_trigger_config(ADC_REGULAR_CHANNEL, ENABLE); adc_external_trigger_source_config(ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_NONE); adc_data_alignment_config(ADC_DATAALIGN_RIGHT); adc_enable(); adc_calibration_enable(); adc_special_function_config(ADC_SCAN_MODE, ENABLE); } 3. 获得ADC采集的模拟量后转换得到的数字量 adc_value = (ADC_RDATA * 3.3 / 4096); 3.3网络传输 3.3.1DALI 1. DALI协议 1) 概述 数字可寻址照明接口(digital addressable lighting interface,DALI)协议是照明控制的一个标准,协议编码简单明了,通信结构可靠。DALI协议是用于满足智能化照明控制需要的非专有标准,它定义了电子镇流器与控制模块之间进行数字化通信的接口标准。DALI协议被编入欧洲电子镇流器标准“EN60929附录E中”,利用数字化控制方式调节荧光灯的输出光通量。该协议支持“开放系统”的概念,不同制造厂商的产品只要都遵守DALI协议就可以相互连接,保证不同制造厂商生产的DALI设备能全部兼容。 DALI系统包含分布式智能模块,各个智能化DALI模块都具有数字控制和数字通信能力,地址和灯光场景信息等都存储在各个DALI模块的存储器内。DALI模块通过DALI总线进行数字通信,传递指令和状态信息,实现开/关灯、调光控制和系统设置等功能。因此,当DALI控制器位置改变时,不需要改动灯的电源线。 DALI协议是基于主从式控制模型建立的,主从设备通过DALI接口连接到2芯的总线上。操作人员通过主控制器(荧光灯调光控制器)操作整个系统,可对每个从控制器(电子镇流器)分别寻址,能够实现对连在同一条控制线上的每个荧光灯的亮度分别进行调光。 2) 电气特性 (1) 异步串行通信协议。 (2) 信息传送速率1200bit/s,半双工,双向编码。 (3) 双线连接方式。 (4) 电平标准见图339。 图339DALI电平标准 根据IEC60929标准,DALI总线上的最大电流限制为250mA,每个电子镇流器的电流消耗设定在2mA; DALI总线的线路长度不得超过300m。DALI线路上最大的电压降应确保不超过2V,如图340所示。任何时候,系统都应该保证不能超过这些限制值,否则会降低信号的安全性和完整性,系统运行也变得不稳定。出于这个原因,系统设计者不仅应考虑寻址的方便,也要考虑每个器件的电能消耗,并留有一定的余量以便日后可以进行扩展。 图340DALI安装布线图 图341DALI协议的编码方式 2. DALI协议的数据通信 1) DALI协议的编码 DALI协议采用双向曼彻斯特编码,如图341所示。值“1”和“0”表示两种不同的电平跃变,从逻辑低电平转变到高电平表示值“1”,从逻辑高电平转变到低电平表示值“0”。 DALI协议的主控单元向从控单元发出的指令数据由19位数据组成,如图342所示。第1位是起始位,第2~9位是地址位(这就决定了只能对64个从控单元进行单独编址),第10~17位是数据位,第18、19位为停止位。 图342DALI主控命令 DALI协议中,只有在主控单元查询时,从控单元才向主控单元发送数据。从控单元向主控单元发送的数据由11位数据组成,如图343所示。第1位是起始位,第2~9位是数据位,第10、11位是停止位。 图343DALI从控命令 只有符合上述指令标准的信息,DALI设备才对其做出反应,否则将不予执行。 2) DALI协议的指令信息 DALI的调光指令分为普通指令和专有指令,普通指令用于单播、组播、广播调光、地址分配、镇流器状态查询等; 专有指令主要用于整个DALI系统的参数配置,如表31所示。 表31DALI地址信息 地 址 形 式字 节 形 式使 用 说 明 短地址YAAAAAAS(AAAAAA=0~63,S=0/1)单独控制某个从控单元的个体地址 组地址Y00AAAAS(AAAA=0~15,S=0/1)成组控制的组地址指令 广播地址Y111111S(S=0/1)对所控制的所有从控单元的统一指令 专用命令Y01CCCCS(CCCC=命令码,S=0/1)专用指令,可执行特殊的命令 DALI前向帧的8位地址结构为“YAAA AAAS”。AAAAAA表示具体地址。Y为地址标志位: Y=0时,具体地址表示16位短地址; Y=1时,具体地址表示4位组地址或广播地址。S为功能标志位: S=0时,前向帧的8位命令位为调光指令,调光范围1~255; S=1时,8位命令位表示常规控制指令。 在DALI信息中,用8bit数据表示调光的亮度水平。值“00000000”表示灯没有点亮,DALI协议按对数调节规则决定灯光亮度水平,在最亮和最暗之间包含256级灯光亮度,按对数调光曲线分布。在高亮度具有高增量,在低亮度具有低增量。这样整个调光曲线在人眼里看起来像线性变化。DALI协议确定的灯光亮度水平在0.1%~100%,值“00000001”对应0.1%的亮度水平,值“11111111”对应100%的亮度水平。 3. 程序实现 (1) 程序发送函数。 void key_scan(uint8_t mode) { static uint8_t key_up = 1; if(key_up && ((RESET == gpio_input_bit_get(GPIOB, GPIO_PIN_4))\ ||(RESET == gpio_input_bit_get(GPIOB, GPIO_PIN_5))\ ||(RESET == gpio_input_bit_get(GPIOB, GPIO_PIN_6))\ ||(RESET == gpio_input_bit_get(GPIOB, GPIO_PIN_7)))) { delay_1ms(10); //按键消抖 key_up = 0; if(RESET == gpio_input_bit_get(GPIOB, GPIO_PIN_4)) { addr = 0x01; data1 += 10; dali_send_command(addr,data1); } if(RESET == gpio_input_bit_get(GPIOB, GPIO_PIN_5)) { addr = 0x02; data2 += 10; dali_send_command(addr,data2); } if(RESET == gpio_input_bit_get(GPIOB, GPIO_PIN_6)) { addr = 0x03; data3 += 10; dali_send_command(addr,data3); } if(RESET == gpio_input_bit_get(GPIOB, GPIO_PIN_7)) { addr = 0x04; 数据清0; dali_send_command(addr,data); } } return ; } (2) 接收数据处理函数。 void light_process(void) { if(flag == 0) return ; flag = 0; switch(addr) { case 01:num = (((double)data/ 0xFF) * 10000); timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_3,num); break; case 02:num = (((double)data/ 0xFF) * 10000); timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,num); break; case 03:num = (((double)data/ 0xFF) * 10000); timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_1,num); break; case 04: timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_1,0); timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,0); timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_3,0); break; } } 3.3.2DMX512 1. DMX512协议定义及格式 DMX512(digital multiplex with 512 pieces of information)。根据DMX512数据传输速率的要求及控制网络分散的特点,其物理层的设计采用RS485收发器,总线用一对双绞线实现调光台与调光器的相接。数据传输的方式是DMX512协议,但数据的交换采用RS485通信。 1) 帧结构 一个DMX控制字节称为一个指令帧,数据传输速率为250kbit/s,4μs/bit,44μs/帧。1帧数据长度为11位。第1位: 起始位,低电平(SPACE); 第2~9位: 数据位(亮度数据,表示0~255的256级亮度),从最低位到最高位(LSB~MSB); 第10、11位: 停止位,高电平(MARK)。DMX512协议的帧结构如图344所示。 图344DMX512协议的帧结构 2) 信息包 DMX512协议的信息包由一个MTBP位,一个Break位,一个MAB位,一个SC和512个数据帧组成,数据传输采用异步串行格式。DMX512字段采取顺序传输,以第0字段开始,以最后第512字段结束(最大字段数量为513)。在第一个数据字段开始发送之前,应传输暂停标志,接着传输暂停结束标志和开始码。信息包的格式如图345所示,信息包电平时间见表32。 图345DMX512协议信息包格式 表32信息包电平时间表 描述最小值典型值最大值单位 Break88881000000μs MAB4812μs 指令帧44μs 起始位4μs 停止位8μs 数据位4μs MTBP0NS1000000μs 2. 程序实现 (1) DMX512信息包起始码程序。 void dmx512_init(void) { int i; TXDData[0] = 0; for(i = 1; i<=512; i++) { TXDData[i] = i; } TXDData[1] = 0x01; TXDData[2] = 0x09; TXDData[3] = value_send_R; TXDData[4] = value_send_G; TXDData[5] = value_send_B; } (2) 主控制器发送数据。 void dmx_sendpacket(void) { dmx512_init(); pDMX_buf = 0; gpio_tx_config(0); gpio_bit_reset(GPIOB,GPIO_PIN_10); delay_lus(88);   //break gpio_bit_set(GPIOB,GPIO_PIN_10); delay_lus(13);   //MAB gpio_tx_config(1); usart_data_transmit(USART2, 0x00);   // SC while(RESET == usart_flag_get(USART2, USART_FLAG_TBE)){}; while(pDMX_buf < 10) //1~512 { usart_data_transmit(USART2, 0x0100|TXDData[pDMX_buf]); while(SET != usart_flag_get(USART2, USART_FLAG_TBE)); pDMX_buf++; } } (3) 接收完成处理函数。 void dmx512_process(void) { uint32_t tick; int len; len = dmx_data_idx; if(len == 0) { return ; } tick = get_Tick(); if(tick - dmx_last_time > 4) { if(dmx_data[1] == 0x01 && dmx_data[2] == 0x09) { timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_3,dmx_data[3]); timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,dmx_data[4]); timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_1,dmx_data[5]); } dmx_data_idx = 0; } } 3.3.3WiFi 1. WiFi简介 WiFi是一种可以将PC、手持设备(如PDA、手机)等终端以无线方式互相连接的技术。简单来说就是IEEE 802.11b的别称,是由一个名为“无线以太网相容联盟”(Wireless Ethernet Compatibility Alliance,WECA)的组织所发布的业界术语,它是一种短程无线传输技术,能够在数百米范围内支持互联网接入的无线电信号。 WiFi无线网络包括两种类型的拓扑形式: 基础网(Infra)和自组网(Adhoc)。网络中包括AP设备和STA(站点)。其中,AP是无线接入点,是一个无线网络的创建者,是网络的中心节点,一般家庭或办公室使用的无线路由器就是一个AP。每一个连接到无线网络中的终端(如笔记本电脑、PDA及其他可以联网的用户设备)都可称为一个STA。 1) 基于AP组建的基础无线网络(Infra) 由AP创建,众多STA加入所组成的无线网络为基础无线网络,这种类型网络的特点是AP是整个网络的中心,网络中所有的通信都通过AP来转发完成,拓扑结构如图346所示。 2) 基于自组网的无线网络(Adhoc) 自组网仅由两个及以上STA组成,网络中不存在AP,这种类型的网络是一种松散的结构,网络中所有的STA都可以直接通信,拓扑结构如图347所示。 图346WiFi基础无线网络 图347WiFi自组网网络 2. WiFi通信协议 根据实际照明控制需求,自行定义WiFi照明控制的通信数据协议,如图348所示。 图348WiFi通信数据协议 其中: (1) 地址码: 包括5位箱号和3位板号,网络内的最大节点数为32×8。 (2) 控制码: 包括1位群/单发、1位同/异协议、3位串行端口地址和3位指令形式。控制码默认为0x00。开发者可以根据实际需要,自行定义控制码。 (3) 调光数据: 包括8位调光信息。00000000表示PWM的占空比为0%,即为关灯指令; 11111表示PWM的占空比为100%,即为全亮指令; 在最亮和最暗之间包含256级灯光亮度。 3. 程序实现 (1) 主控制器发送数据。 void KEY_Send(uint8_t mode) { int i = 0; static uint8_t key_up=1; if(mode) { key_up=1; } if(key_up==1&&(gpio_input_bit_get(GPIOB,GPIO_PIN_4)==RESET||gpio_input_bit_get(GPIOB,GPIO_PIN_5)==RESET||gpio_input_bit_get(GPIOB,GPIO_PIN_6)==RESET||gpio_input_bit_get(GPIOB,GPIO_PIN_7)==RESET)) { delay_1ms(50); key_up=0; if(gpio_input_bit_get(GPIOB,GPIO_PIN_4) == RESET) { value_R += 10; command[0] = 0x01; command[1] = 0x09; command[2] = value_R; command[3] = value_G; command[4] = value_B; command[5] = 0x00; for(i = 0; i < 6; i++) { usart_data_transmit(USART2,(uint8_t)command[i]); delay_1ms(1); } } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_5) == RESET) { value_G += 10; 其余代码同上。 } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_6) == RESET) { value_B += 10; 其余代码同上。 } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_7) == RESET) { value_R = 0; value_G = 0; value_B = 0; 其余代码同上。 } } (2) WiFi控制器接收数据处理函数。 void USART2_IRQHandler(void) { if(usart_interrupt_flag_get(USART2,USART_INT_FLAG_RBNE) != RESET) { uint8_t data; data = usart_data_receive(USART2); if(data == 0x01) uart_data_idx = 0; uart_data[uart_data_idx++] = data; if(uart_data_idx == 5) { if(uart_data[0] == 0x01 || uart_data[0] == Add_rec) { if(uart_data[1] == 0x09) { value_rec_R = uart_data[2]; value_rec_G = uart_data[3]; value_rec_B = uart_data[4]; } } uart_data_idx = 0; } usart_interrupt_flag_clear(USART2,USART_INT_FLAG_RBNE); } } 3.3.4ZigBee 1. ZigBee简介 ZigBee是一种提供固定、便携或移动设备使用的低复杂度、低成本、低功耗、低速率的无线连接技术。ZigBee主要适用于自动控制和远程控制领域,可以嵌入在各种设备中,同时支持地理定位功能,非常适合无线传感器网络的通信协议。 ZigBee通信协议分为4层,包括应用层、网络/安全层、介质访问控制层和物理层,如图349所示。 图349ZigBee的协议栈 2. ZigBee通信协议 根据实际照明控制需求,自行定义ZigBee照明控制的通信数据协议,如图350所示。 图350ZigBee通信数据协议 其中: (1) 地址码: 包括5位箱号和3位板号,网络内的最大节点数为32×8。 (2) 控制码: 包括1位群/单发、1位同/异协议、3位串行端口地址和3位指令形式。控制码默认为0x00。开发者可以根据实际需要,自行定义控制码。 (3) 调光数据: 包括8位调光信息。00000000表示PWM的占空比为0%,即为关灯指令; 11111111表示PWM的占空比为100%,即为全亮指令; 在最亮和最暗之间包含256级灯光亮度。 3. ZigBee网络结构 ZigBee支持星型、树型和网格型等多种拓扑结构,如图351所示。ZigBee网络中包括三种节点: 协调器、路由器和终端节点。其中协调器和路由器均为全功能设备(FFD),而终端结点选用精简功能设备(RFD)。 图351ZigBee支持的网络拓扑 1) 协调器(Coordinator) 一个网络有且只有一个协调器,该设备负责启动网络、配置网络成员地址、维护网络、维护节点的绑定关系表等,需要最多的存储空间和计算能力。 2) 路由器(Router) 主要实现扩展网络及路由消息的功能,扩展网络作为网络中的潜在父节点,允许更多的设备接入网络,路由节点只有在树型网络和网格型网络中存在。 3) 终端节点(End Device) 不具备成为父节点或路由器的能力,一般作为网络的边缘设备,负责与实际的监控对象相连,这种设备只与自己的父节点主动通信,具体的信息路由则全部交由其父节点及网络中具有路由功能的协调器和路由器完成。 4. 程序实现 (1) 主控制器发送数据。 void KEY_Send(uint8_t mode) { int i = 0; static uint8_t key_up=1; if(mode) { key_up=1; } if(key_up==1&&(gpio_input_bit_get(GPIOB,GPIO_PIN_4)==RESET||gpio_input_bit_get(GPIOB,GPIO_PIN_5)==RESET||gpio_input_bit_get(GPIOB,GPIO_PIN_6)==RESET||gpio_input_bit_get(GPIOB,GPIO_PIN_7)==RESET)) { delay_1ms(50); key_up=0; if(gpio_input_bit_get(GPIOB,GPIO_PIN_4) == RESET) { value_R += 10; command[0] = 0x01; command[1] = 0x09; command[2] = value_R; command[3] = value_G; command[4] = value_B; command[5] = 0x00; for(i = 0; i < 6; i++) { usart_data_transmit(USART2,(uint8_t)command[i]); delay_1ms(1); } } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_5) == RESET) { value_G += 10; 其余代码同上。 } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_6) == RESET) { value_B += 10; 其余代码同上。 } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_7) == RESET) { value_R = 0; value_G = 0; value_B = 0; 其余代码同上。 } } (2) ZigBee控制器接收数据处理函数。 void USART2_IRQHandler(void) { if(usart_interrupt_flag_get(USART2,USART_INT_FLAG_RBNE) != RESET) { uint8_t data; data = usart_data_receive(USART2); if(data == 0x01) uart_data_idx = 0; uart_data[uart_data_idx++] = data; if(uart_data_idx == 5) { if(uart_data[0] == 0x01 || uart_data[0] == Add_rec) { if(uart_data[1] == 0x09 ) { value_rec_R = uart_data[2]; value_rec_G = uart_data[3]; value_rec_B = uart_data[4]; } } uart_data_idx = 0; } usart_interrupt_flag_clear(USART2,USART_INT_FLAG_RBNE); } } 3.3.5RS485 1. RS485协议介绍 RS485协议只是一个物理层协议,数据通信协议需要另行规定。硬件通信接口建立后,在进行数据传输的仪表之间需要约定一个数据协议,以使接收端能够解析收到的数据,这便是“协议”的概念。 RS485通信接口是一个对通信接口硬件的描述,它只需要两根通信线,就可以在两个或两个以上数据进行传输。对于这种数据传输的方式,某些芯片可以是半双工的通信方式,即在某一时刻,某设备只能进行数据的发送或者接收,采用分时复用原则。 它是能进行联网的通信接口。在RS485通信网络中一般采用主从通信方式,即一个主机带多个从机。很多情况下,连接RS485通信链路时只是简单地用一对双绞线将各个接口的“A”“B”端连接起来,即可实现主从设备的数据通信。 RS485的电气特性: 采用差分信号负逻辑,逻辑“1”以两线间的电压差为+(2~6)V表示; 逻辑"0"以两线间的电压差为-(2~6)V表示。该电平与TTL电平兼容,可方便与TTL电路连接。 2. RS485通信协议格式 根据实际照明控制需求,自行定义RS485照明控制的通信数据协议,如图352所示。 图352RS485通信数据协议 其中: (1) 地址码: 包括5位箱号和3位板号,网络内的最大节点数为32×8。 (2) 控制码: 包括1位群/单发、1位同/异协议、3位串行端口地址和3位指令形式。控制码默认为0x00。开发者可以根据实际需要,自行定义控制码。 (3) 调光数据: 包括8位调光信息。00000000表示PWM的占空比为0%,即为关灯指令; 11111111表示PWM的占空比为100%,即为全亮指令; 在最亮和最暗之间包含256级灯光亮度。 3. 程序实现 (1) 主控制器发送数据。 void KEY_Send(uint8_t mode) { int i = 0; static uint8_t key_up=1; if(mode) { key_up=1; } if(key_up==1&&(gpio_input_bit_get(GPIOB,GPIO_PIN_4)==RESET||gpio_input_bit_get(GPIOB,GPIO_PIN_5)==RESET||gpio_input_bit_get(GPIOB,GPIO_PIN_6)==RESET||gpio_input_bit_get(GPIOB,GPIO_PIN_7)==RESET)) { delay_1ms(50); key_up=0; if(gpio_input_bit_get(GPIOB,GPIO_PIN_4) == RESET) { value_R += 10; command[0] = 0x01; command[1] = 0x09; command[2] = value_R; command[3] = value_G; command[4] = value_B; command[5] = 0x00; RS485_TX; for(i = 0; i < 6; i++) { usart_data_transmit(USART2,(uint8_t)command[i]); delay_1ms(1); } RS485_RX; } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_5) == RESET) { value_G += 10; 其余代码同上。 } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_6) == RESET) { value_B += 10; 其余代码同上。 } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_7) == RESET) { value_R = 0; value_G = 0; value_B = 0; 其余代码同上。 } } (2) RS485控制器接收数据处理函数。 void USART2_IRQHandler(void) { if(usart_interrupt_flag_get(USART2,USART_INT_FLAG_RBNE) != RESET) { uint8_t data; data = usart_data_receive(USART2); if(data == 0x01) uart_data_idx = 0; uart_data[uart_data_idx++] = data; if(uart_data_idx == 5) { if(uart_data[0] == 0x01 || uart_data[0] == Add_rec) { if(uart_data[1] == 0x09) { value_rec_R = uart_data[2]; value_rec_G = uart_data[3]; value_rec_B = uart_data[4]; } } uart_data_idx = 0; } usart_interrupt_flag_clear(USART2,USART_INT_FLAG_RBNE); } } 3.4主控制器程序设计 主控制器即智慧照明实验箱中的大板,它支撑GD32的基础性实验,可以实现处理器和编程环境的认知实验,同时它还是实验箱级的网关,同时作为AP+STA,接收综合控制器的控制信息,完成控制大功率LED的功能,同时又作为网关实现WiFi到其他协议的转换。 3.4.1流水灯程序设计 1. 流水灯的设计思路 流水灯的概念是一组灯在系统控制下按照设定的顺序和时间发亮和熄灭,形成一定的视觉效果。 本实验平台流水灯的设计思路是让主控制器流水灯模块中的LED灯按设定时间依次点亮,然后同时熄灭。之后再依次全部点亮,同时熄灭,循环往复。 2. 流水灯的实现方法 LED灯即发光二极管,是一种半导体固体发光器件,它是利用固体半导体芯片作为发光材料,当两端加上正向电压,半导体中的载流子发生复合引起光子发射而产生光。 本实验平台流水灯模块的LED灯,在电路中其一端已经连接上3.3V电源,另一端需要接在GD32单片机的I/O口。如果要让接在单片机某I/O 口的LED灯点亮,只需让该I/O口的电平变为低电平,此时LED两端形成正向电压,LED灯点亮; 相反,如果要让接在单片机某I/O 口的LED灯熄灭,只需让该I/O口的电平变为高电平,此时LED两端没有正向电压,LED灯熄灭。 本实验平台流水灯的实现方法是利用GD32单片机I/O口的8个引脚分别控制8个LED灯,通过循环控制I/O口的高低电平变换,从而实现LED1~LED8流水灯点亮,再同时熄灭,循环往复。灯的点亮间隔时间通过延时函数自定。 在此,我们还应注意一点,由于人眼的视觉暂留效应以及单片机执行每条指令的时间很短,我们在控制LED灯亮灭的时候应该延时一段时间,否则我们就看不到“流水”效果了。 3. 程序实现 (1) LED的GPIO端口初始化。 void LED_init(void) { rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_GPIOC); gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15); gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3); } (2) 主函数实现。 while(1) { gpio_bit_reset(GPIOB,GPIO_PIN_12); delay_1ms(1000); gpio_bit_reset(GPIOB,GPIO_PIN_13); delay_1ms(1000); 其他GPIO端口同上; gpio_bit_set(GPIOB,GPIO_PIN_12); gpio_bit_set(GPIOB,GPIO_PIN_13); 其他GPIO端口同上; delay_1ms(1000); } 3.4.2按键输入程序设计 1. 按键简介 按键开关指轻触式按键开关,也称为轻触开关,本实验平台所用按键开关如图353所示。按键开关是一种电子开关,按键按下,此时按键开关内部保持闭合状态,如果撤销压力即手拿开,则在内部弹片作用下按键弹开,按键开关内部断开。 图353本实验平台所用 按键开关 按键开关的工作方法本质就是按键的按下与弹开,分别对应GD32单片机检测GPIO输入的两种电平状态。本实验平台按键开关4个引脚分为两组,即对角线为一组。按键的一组引脚接到单片机的I/O口上,另一组与GND连接。当按键按下时,单片机的I/O口与GND连接,端口电平被拉低。因此通过读取端口电平即可获知按键状态。 当按键按下,按键开关内部闭合,按键电路导通,此时GD32单片机检测GPIO输入为低电平。按键弹开,按键开关内部断开,按键电路不导通,此时GD32单片机检测GPIO输入为高电平。单片机内部可以通过检测GPIO输入的电平高低来判断按键是否被按下,这个判断结果即可作为单片机的输入信号。 2. 按键消抖 按键这种物理器件本身会有抖动信号,抖动信号指的是在电平由高到低也就是在按键按下时,或者电平由低到高也就是在按键抬起过程中,电平的变化不是立刻发生的,而是经过了一段时间的不稳定期才完成,在这个不稳定期间电平可能会时高时低反复变化,这个不稳定期称之为抖动,抖动期内获取按键信息是不可靠的,要想办法消抖。 消抖就是用硬件或软件方法来尽量减少抖动期对获取按键信息的影响。消抖常用两种思路: 第一种是硬件消抖,就是尽量减少抖动时间,方法是通过添加电容等元件来减少抖动; 第二种是软件消抖,就是发现一次按键按下或抬起事件后,不立即处理按键,而是延时一段时间(一般10~20ms,这就是消抖时间)后再次获取按键键值,如果此次获取的信息和上次一样是按下/抬起,那就认为真的按下/抬起了。 3. 程序实现 (1) 按键的GPIO端口初始化。 void key_init(void) { rcu_periph_clock_enable(RCU_GPIOA); gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, KEYPORT); //浮空输入模式 } (2) 按键扫描函数。 uint8_t KEY_Scan(void) { if(gpio_input_bit_get(GPIOA,GPIO_PIN_1) == RESET || gpio_input_bit_get(GPIOA,GPIO_PIN_4) == RESET || gpio_input_bit_get(GPIOA,GPIO_PIN_5) == RESET || gpio_input_bit_get(GPIOA,GPIO_PIN_6) == RESET || gpio_input_bit_get(GPIOA,GPIO_PIN_7) == RESET || gpio_input_bit_get(GPIOA,GPIO_PIN_8) == RESET || gpio_input_bit_get(GPIOA,GPIO_PIN_11) == RESET|| gpio_input_bit_get(GPIOA,GPIO_PIN_12) == RESET)//按键按下 { delay_1ms(20); //消抖 if(gpio_input_bit_get(GPIOA,GPIO_PIN_1) == RESET) return KEY1_PRES;   //KEY1按下 else if(gpio_input_bit_get(GPIOA,GPIO_PIN_4) == RESET) return KEY2_PRES;   //KEY2按下 //…KEY3,KEY4,KEY5,KEY6,KEY6,KEY7同上。 } return KEY_UNPRES; //无按键按下 } (3) 主函数实现。 while(1) { key = KEY_Scan(); switch(key) { case KEY1_PRES: gpio_bit_write(GPIOB, GPIO_PIN_12, (bit_status)(1 - gpio_output_bit_get(GPIOB, GPIO_PIN_12))); //翻转LED2的输出状态 while(gpio_input_bit_get(GPIOA,GPIO_PIN_1) == RESET); break; case KEY2_PRES: gpio_bit_write(GPIOB, GPIO_PIN_13, (bit_status)(1 - gpio_output_bit_get(GPIOB, GPIO_PIN_13))); //翻转LED3的输出状态 while(gpio_input_bit_get(GPIOA,GPIO_PIN_4) == RESET); break; //…KEY3、KEY4、KEY5、KEY6、KEY7、KEY8按键同上。 case KEY_UNPRES: break; } } 3.4.3数码管显示程序设计 1. 数码管简介 本实验平台采用4位共阴极数码管。数码管按发光二极管单元连接方式可分为共阳极数码管和共阴极数码管。共阳极数码管是指将所有发光二极管的阳极接到一起形成公共阳极(COM)的数码管,共阳极数码管在应用时应将公共极COM接到+5V,当某一字段发光二极管的阴极为低电平时,相应字段就点亮,当某一字段发光二极管的阴极为高电平时,相应字段就不亮。共阴极数码管是指将所有发光二极管的阴极接到一起形成公共阴极(COM)的数码管,共阴极数码管在应用时应将公共极COM接到地线(GND)上,当某一字段发光二极管的阳极为高电平时,相应字段就点亮,当某一字段发光二极管的阳极为低电平时,相应字段就不亮。 4位数码管是将4个1位数码管的8个显示笔画“a、b、c、d、e、f、g、dp”的同名端连在一起,另外每个数码管的公共极COM增加位选通控制电路,位选通由各自独立的I/O线控制。 2. 数码管显示 当单片机输出字形码时,所有数码管都接收到相同的字形码,但究竟哪个数码管会显示出字形,取决于单片机对位选通COM端电路的控制,所以我们只要将需要显示的数码管的位选通控制打开,该位就显示出字形,没有位选通的数码管就不会亮。通过分时轮流控制各个数码管的位选通COM端,就可使各个数码管轮流受控显示。 在轮流显示过程中,每位数码管的点亮时间为1~2ms,由于人的视觉暂留现象及发光二极管的余晖效应,尽管各位数码管并非同时点亮,但只要扫描的速度足够快,整体扫描时间(单个数码管点亮时间×数码管个数)小于10ms,给人的印象就是一组稳定的显示数据,感觉不到闪烁。 3. 程序实现 (1) 数码管端口配置函数。 void SMG_UserConfig(void) { rcu_periph_clock_enable(RCU_AF); rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_GPIOB); gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP,ENABLE); gpio_init(SMG_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SMG_PIN); gpio_init(WEI_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, WEI_PIN); gpio_bit_reset(WEI_PORT,WEI_PIN); } (2) 数码管显示函数。 void SMG_Display(uint8_t dat) { gpio_port_write(SMG_PORT,data[dat]); } (3) 主函数实现。 while(1) { if(RESET == gpio_input_bit_get(GPIOB,GPIO_PIN_8)) { //延时20ms用于消除抖动 delay_1ms(20); if(RESET == gpio_input_bit_get(GPIOB,GPIO_PIN_8)) { for(i = 0; i < 17; i++) { SMG_Display(i); delay_1ms(1000); } while(RESET == gpio_input_bit_get(GPIOB,GPIO_PIN_8)); } } } 3.4.4串口通信程序设计 1. 串口简介 串口,也称串行通信接口,是采用串行通信方式的扩展接口。串行接口是指数据一位一位地顺序传送,其特点是通信线路简单,只要一对传输线就可以实现双向通信。 本实验平台的主控制器芯片GD32F303RCT6最多支持3个USART和2个UART,工作频率高达7.5Mbps,支持异步和时钟同步串行通信模式。USART(USART0、USART1、USART2)和UART(UART3、UART4)用于在并行和串行接口之间转换数据,数据帧可以通过全双工或半双工,同步或异步的方式进行传输。USART/UART包括一个可编程波特率发生器,能够分割系统时钟,为USART发射机和接收机生成专用时钟。 USART不仅支持标准的异步收发模式,还实现了一些其他类型的串行数据交换模式,如红外编码规范,SIR,智能卡协议,LIN,半双工以及同步模式。它还支持多处理器通信和Modem流控操作(CTS/RTS)。数据帧支持从LSB或者MSB开始传输。数据位的极性和TX/RX引脚都可以灵活配置。 2. 串口通信 串口通信一般是以帧格式传输数据,即一帧一帧传输,每帧包含起始信号、数据信息、停止信息,可能还有校验信息。所以,USART数据格式一般分为启动位、数据帧、可能的奇偶校验位、停止位,如图354和图355所示。 启动位: 发送方想要发送串口数据时,必须先发送启动位。 数据帧: 发送的数据内容,数据的bit位。有8位字长和9位字长两种。 可能的奇偶校验位: 在串口通信中一种简单的检错方式,没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶数个或者奇数个逻辑高位。 停止位: 停止位不仅表示传输的结束,并且还提供计算机校正时钟同步的机会。 通常情况下,默认选择的USART数据格式为8位数据字长、无奇偶校验位、1位停止位。 图354串口数据格式(8位字长) 图355串口数据格式(9位字长) 两个串口之间的连接方式,如图356所示。 图356串口连接方式 3. USART和UART的区别 USART: 通用同步和异步收发器; UART: 通用异步收发器。 当进行异步通信时,这两者是没有区别的。区别在于USART比UART多了同步通信功能。 同步是指发送方发出数据后,等接收方发回响应以后才发下一个数据包的通信方式。 异步是指发送方发出数据后,不等接收方发回响应,就发送下个数据包的通信方式。 4. 程序实现 (1) 串口1配置函数。 void usart1_init(void) { rcu_periph_clock_enable(RCU_AF); rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_USART1); gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2); gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_3); usart_deinit(USART1); usart_baudrate_set(USART1,115200); usart_parity_config(USART1,USART_PM_NONE); usart_word_length_set(USART1,USART_WL_8BIT); usart_stop_bit_set(USART1,USART_STB_1BIT); usart_transmit_config(USART1,USART_TRANSMIT_ENABLE); usart_receive_config(USART1,USART_RECEIVE_ENABLE); usart_enable(USART1); } (2) 将C语言库函数printf重新定向到USART。 int fputc(int ch, FILE *f) { usart_data_transmit(USART1, (uint8_t)ch); while (RESET == usart_flag_get(USART1,USART_FLAG_TBE)); //USART_FLAG_TBE 传输数据缓冲区为空 return ch; } 3.4.5中断程序设计 1. 中断原理 中断是实现多任务的基础,也是I/O的一种基本工作方式。中断包括可屏蔽中断和非屏蔽中端,用户可以使用的是可屏蔽中断。可屏蔽中断包括内部中断和外部中断两种,其中定时器中断和串行口中断属于内部中断。不同的中断由中断服务程序实现其功能。 GD32集成了嵌套式矢量型中断控制器(nested vectored interrupt controller,NVIC)来实现高效的异常和中断处理。NVIC实现了低延迟的异常和中断处理,以及电源管理控制。它和内核是紧密耦合的。 EXTI(中断/事件控制器)包括20个相互独立的边沿检测电路并且能够向处理器内核产生中断请求或唤醒事件。EXTI有三种触发类型: 上升沿触发、下降沿触发和任意沿触发。EXTI中的每一个边沿检测电路都可以独立配置和屏蔽。 中断触发源包括来自I/O引脚的16根线以及来自内部模块的4根线(包括LVD、RTC闹钟、USB唤醒、以太网唤醒)。通过配置GPIO模块的AFIO_EXTISSx寄存器,所有的GPIO引脚都可以被选作EXTI的触发源。 MCU通过检测中断寄存器来判断是否有外部中断请求。如果有中断产生,则MCU暂停执行正在执行的进程,优先处理中断事件。对不同的中断事件,由于它们的性质不同,对应的处理程序不同。 2. 中断过程 中断过程: 中断的完整过程包括中断申请、中断响应、中断处理和中断返回。每一个过程的实现都有对应的寄存器支撑。例如,中断请求寄存器、中断屏蔽寄存器、中断优先级寄存器、中断标志寄存器等。 若出现中断事件,硬件就把它记录在中断请求寄存器中。中断请求寄存器的每一位与一个中断事件对应(通过引脚),当出现某中断事件后,对应的中断寄存器的对应位就被置成“1”。然后根据中断使能寄存器去查看此中断是否被使能,假设没有被使能则不能响应此中断请求; 假设中断被使能,然后看该中断优先级,假设此中断正好处于最高级,则MCU为该中断进行服务,即根据中断类型号去中断矢量表找中断服务程序的入口地址,然后去执行,完成中断服务。 在中断服务程序的最后是中断返回指令,即完成中断服务后,返回到调用中断的地方继续执行原来的任务。假设该中断优先级较低,则处于等待状态,直到该中断处于高优先级状态。本实验第一个内容是用按键按下动作模拟中断事件。第二个内容为定时器中断,通过时间计数产生中断。对其他中断事件的模拟处理,可根据各中断事件的性质确定处理原则,制定算法。 3. 外部中断程序实现 (1) 启动外部中断。 void Key_ExitConfig() { nvic_irq_enable(EXTI5_9_IRQn, 2U, 0U); gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_8); gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOB, GPIO_PIN_SOURCE_9); exti_init(EXTI_8, EXTI_INTERRUPT, EXTI_TRIG_FALLING); //下降沿触发 exti_init(EXTI_9, EXTI_INTERRUPT, EXTI_TRIG_FALLING); //下降沿触发 } (2) 中断实现数字的加减函数。 void EXTI5_9_IRQHandler(void) { if(RESET != exti_interrupt_flag_get(EXTI_8)) { i = i + 1; exti_interrupt_flag_clear(EXTI_8); } if(RESET != exti_interrupt_flag_get(EXTI_9)) { i = i - 1; exti_interrupt_flag_clear(EXTI_9); } } 4. 定时器中断程序实现 (1) 启动定时器中断。 void timer_config(void) { timer_parameter_struct timer_initpara; rcu_periph_clock_enable(RCU_TIMER1); timer_deinit(TIMER1); timer_initpara.prescaler = 11999; timer_initpara.alignedmode = TIMER_COUNTER_EDGE; timer_initpara.counterdirection = TIMER_COUNTER_UP; timer_initpara.period = 9999; timer_initpara.clockdivision = TIMER_CKDIV_DIV1; timer_initpara.repetitioncounter = 0; timer_init(TIMER1,&timer_initpara); //开启定时器中断 timer_interrupt_enable(TIMER1,TIMER_INT_UP); nvic_priority_group_set(NVIC_PRIGROUP_PRE1_SUB3); nvic_irq_enable(TIMER1_IRQn, 0, 1); timer_primary_output_config(TIMER1,ENABLE); timer_auto_reload_shadow_enable(TIMER1); timer_enable(TIMER1); } (2) 定时器计数函数。 void TIMER1_IRQHandler() { if(timer_interrupt_flag_get(TIMER1,TIMER_INT_FLAG_UP) != RESET) { i++; if(i > 60) i = 1; } timer_interrupt_flag_clear(TIMER1,TIMER_INT_FLAG_UP); } 3.4.6A/D转换程序设计 1. A/D转换原理 本实验平台的主控制器采用GD32F303RCT6芯片,MCU自带模数转换器(ADC)。所以不需扩展AD专用芯片。 ADC是一种采用逐次逼近方式的模拟数字转换器。它有18个多路复用通道,可以转换来自16个外部通道和2个内部通道的模拟信号。各种通道的A/D转换可以配置成单次、连续、扫描或间断转换模式。ADC转换的结果可以按照左对齐或右对齐的方式存储在16位数据寄存器中。片上的硬件过采样机制可以通过减少来自MCU的相关计算负担来提高性能。 2. ADC主要特征 (1) 高性能。 可配置12位、10位、8位或6位分辨率; 自校准; 可编程采样时间; 数据寄存器可配置数据对齐方式; 支持规则数据转换的DMA请求。 (2) 有18个多路复用通道。 包括16个外部模拟输入通道、1个内部温度传感通道(VSENSE)和1个内部参考电压输入通道(VREFINT)。 (3) 两种转换方式。 可以通过软件触发,也可以通过硬件触发。 (4) 包含多种转换模式。 有转换单个通道,或者扫描一序列的通道; 单次模式,每次触发转换一次选择的输入通道; 连续模式,连续转换所选择的输入通道; 间断模式; 同步模式(适用于具有两个或多个ADC的设备)。 (5) 可产生中断。 中断的产生方式有规则组转换完成、注入组转换完成和模拟看门狗事件。 (6) 过采样。 包括16位的数据寄存器; 可调整的过采样率,从2x到256x; 高达8位的可编程数据移位。 (7) ADC供电要求。 2.6~3.6V,一般电源电压为3.3V。 3. 程序实现 实验采用ADC0通道1、通道4和通道5作为A/D采集输入端。 (1) AD转换器配置。 void adc_config(void) { adc_deinit(ADC0); adc_mode_config(ADC_MODE_FREE); adc_data_alignment_config(ADC0,ADC_DATAALIGN_RIGHT); adc_special_function_config(ADC0,ADC_SCAN_MODE,ENABLE); adc_channel_length_config(ADC0,ADC_INSERTED_CHANNEL,3); adc_inserted_channel_config(ADC0, 0, ADC_CHANNEL_1, ADC_SAMPLETIME_239POINT5); adc_inserted_channel_config(ADC0, 1, ADC_CHANNEL_4, ADC_SAMPLETIME_239POINT5); adc_inserted_channel_config(ADC0, 2, ADC_CHANNEL_5, ADC_SAMPLETIME_239POINT5); adc_external_trigger_config(ADC0,ADC_INSERTED_CHANNEL,ENABLE); adc_external_trigger_source_config(ADC0, ADC_INSERTED_CHANNEL, ADC0_1_2_EXTTRIG_INSERTED_NONE); adc_enable(ADC0); delay_1ms(1); adc_calibration_enable(ADC0); } (2) PWM输出控制主控制器LED灯的亮度或颜色。 dac_value1 = (ADC_IDATA0(ADC0) * 3.3 / 4096); dac_value2 = (ADC_IDATA1(ADC0) * 3.3 / 4096); dac_value3 = (ADC_IDATA2(ADC0) * 3.3 / 4096); timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_3,dac_value1*100); timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,dac_value2*100); timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_1,dac_value3*100); 3.5局域网网关程序设计 3.5.1单控程序设计 1. LED单灯控制简介 本开发平台支持DALI、DMX12、RS485、ZigBee和WiFi五种通信协议,能够实现主控制器通过某个协议对相应中控制器进行控制的功能。 LED单灯控制是指主控制器对单个中控制器的LED灯的控制,将主控制器的按键信息发送给中控制器。中控制器需要设置为接收模式,中控制器根据接收的数据,控制RGB LED灯的开关、亮度和颜色。每按下一次主控制器的通信按键,发送一次数据,并且数据递加10。 LED单灯控制结构如图357所示。 图357LED单灯控制结构 2. LED单灯控制流程 1) LED单灯控制过程 首先,通过按下主控制器的按键,使主控制器的MCU发送相应的控制信息,经过DALI、DMX12、RS485、ZigBee和WiFi五种通信方式中的某一通信模块传输。然后,中控制器设置为接收模式,中控制器通过对应的通信模块接收该控制信息,经过中控制器的MCU处理后,实现对中控制器上RGB LED灯的开关、亮度和颜色控制。 2) LED单灯控制程序流程 通过按键选择灯的颜色和亮度,每按下一次按键,亮度增加10。LED单灯控制程序流程如图358所示。 图358LED单灯控制程序流程 3. 数据格式 根据实际照明控制需求,自行定义照明控制的通信数据协议,如图359所示。 图359LED单灯控制通信数据格式 其中: (1) 地址码: 包括5位箱号和3位板号,网络内的最大节点数为32×8。 (2) 控制码: 包括1位群/单发、1位同/异协议、3位串行端口地址和3位指令形式。控制码默认为0x00。开发者可以根据实际需要,自行定义控制码。 (3) 调光数据: 包括8位调光信息。00000000表示PWM的占空比为0%,即为关灯指令; 11111111表示PWM的占空比为100%,即为全亮指令; 在最亮和最暗之间包含256级灯光亮度。 4. 程序关键代码 (1) 主控制器发送数据函数,代码如下所示。 void KEY_Send() { if(gpio_input_bit_get(GPIOB,GPIO_PIN_4)==RESET ||gpio_input_bit_get(GPIOB,GPIO_PIN_5)==RESET ||gpio_input_bit_get(GPIOB,GPIO_PIN_6)==RESET ||gpio_input_bit_get(GPIOB,GPIO_PIN_7)==RESET)//有按键按下 { delay_1ms(50); //消抖 if(gpio_input_bit_get(GPIOB,GPIO_PIN_4) == RESET) { value_R += 10; //K1按下,红光数据加10 command[0] = 0x01; command[1] = 0x09; command[2] = value_R; command[3] = value_G; command[4] = value_B; command[5] = 0x00; //结束标志 RS485_TX; for(i = 0; i < 6; i++) { usart_data_transmit(USART2,(uint8_t)command[i]); delay_1ms(1); } RS485_RX; } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_5) == RESET) { value_G += 10; //K2按下,绿光数据加10 …; //其他代码同上 } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_6) == RESET) { value_B += 10; //K3按下,蓝光数据加10 …; //其他代码同上 } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_7) == RESET) { value_R = 0;value_G = 0;value_B = 0; //K4按下,数据清零 …; //其他代码同上 } } } (2) 中控制器接收数据函数,代码如下所示。 void USART2_IRQHandler(void) { if(usart_interrupt_flag_get(USART2,USART_INT_FLAG_RBNE) != RESET) { uint8_t data; data = usart_data_receive(USART2); if(data == 0x01) uart_data_idx = 0; uart_data[uart_data_idx++] = data; if(uart_data_idx == 5) { if(uart_data[0] == 0x01) { if(uart_data[1] == 0x09) { value_rec_R = uart_data[2]; value_rec_G = uart_data[3]; value_rec_B = uart_data[4]; } } uart_data_idx = 0; } usart_interrupt_flag_clear(USART2,USART_INT_FLAG_RBNE); } } 3.5.2群组控制程序设计 1. LED群组控制简介 本开发平台支持DALI、DMX12、RS485、ZigBee和WiFi五种通信协议,能够实现主控制器通过某个协议对相应中控制器进行控制的功能。 LED群组控制,即设置主控板为集控中心,实现对同种协议多个板子的LED灯的调光调色控制。 将主控制器设置为主设备,中控制器设置为从设备,为多个中控制器分配不同的从地址。主控制器发出控制指令,各个中控制器接收并执行指令,通过各个中控板的RGB LED灯显示被控效果,进而实现基于同种协议的LED群组控制的目的。 LED群组控制中,主控制器和中控制器的通信结构如图360所示。 图360LED群组控制中的通信结构 2. LED群组控制流程 1) LED群组控制过程 LED群组控制是指主控制器对多个中控制器的LED灯的控制,将主控制器的按键信息发送给多个中控制器。中控制器设置为接收模式,多个中控制器设置不同的地址值,中控制器根据接收的数据,控制RGB LED灯的开关、亮度和颜色。 首先,通过按下主控制器的按键,使主控制器的MCU发送相应的控制信息,经过DALI、DMX12、RS485、ZigBee和WiFi五种通信方式中的某一通信模块传输。然后,多个中控制器设置为接收模式并分配不同的地址,多个中控制器通过对应的通信模块接收该控制信息,经过MCU处理后,实现对板子上RGB LED灯的控制。 2) LED群组控制程序流程 通过按键选择灯的颜色和亮度,每按下一次按键,亮度增加10。LED群组控制程序流程如图361所示。 图361LED群组控制程序流程 3. 程序关键代码 (1) 主控制器发送数据函数,代码如下所示。 int add = 0; void KEY_Send() { if(gpio_input_bit_get(GPIOB,GPIO_PIN_4)==RESET ||gpio_input_bit_get(GPIOB,GPIO_PIN_5)==RESET ||gpio_input_bit_get(GPIOB,GPIO_PIN_6)==RESET ||gpio_input_bit_get(GPIOB,GPIO_PIN_7)==RESET)//按键按下 { delay_1ms(50); //消抖 if(gpio_input_bit_get(GPIOB,GPIO_PIN_4) == RESET) { value_R += 10; //K1按下,红光数据加10 //群组发送 for( add = 1 ; add < 3 ;add++) { command[0] = add; command[1] = 0x09; command[2] = value_R; command[3] = value_G; command[4] = value_B; command[5] = 0x00; //结束标志 RS485_TX; for(i = 0; i < 6; i++) { usart_data_transmit(USART2,(uint8_t)command[i]); delay_1ms(1); } RS485_RX; } } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_5) == RESET) { value_G += 10; //K2按下,绿光数据加10 …; //其他代码同上 } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_6) == RESET) { value_B += 10; //K3按下,蓝光数据加10 …; //其他代码同上 } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_7) == RESET) { value_R = 0;value_G = 0;value_B = 0; //K4按下,数据清零 …; //其他代码同上 } } } (2) 从设备设置地址代码。 void KeyService(void) { if(gpio_input_bit_get(GPIOB,GPIO_PIN_4) == RESET) { if(menu_One == 4 && menu_Two == 1 && flag == receive) { Group_rec +=1; } if(menu_One == 4 && menu_Two == 2 && flag == receive) { Add_rec +=1; } } (3) 从设备接收数据代码。 void USART2_IRQHandler(void) { if(usart_interrupt_flag_get(USART2,USART_INT_FLAG_RBNE) != RESET) { uint8_t data; data = usart_data_receive(USART2); if(data == 0x01) uart_data_idx = 0; uart_data[uart_data_idx++] = data; if(uart_data_idx == 5) { if(uart_data[0] == 0x01 || uart_data[0] == Add_rec) { if(uart_data[1] == 0x09) { value_rec_R = uart_data[2]; value_rec_G = uart_data[3]; value_rec_B = uart_data[4]; } } uart_data_idx = 0; } usart_interrupt_flag_clear(USART2,USART_INT_FLAG_RBNE); } } 3.5.3网络融合程序设计 1. 网络融合简介 网络融合是指多个网络协议可以相互通信,实现数据互通和信息融合,在复杂环境下对于灯光的调光调色做出更精准的控制。主控制器在网络融合实验中起到网关的功能,即实现不同协议的相互转换。本实验箱支持RS485、WiFi和ZigBee三种协议的融合。主控制器对这3个中控制器进行控制,将主控制器的按键信息发送给多个中控制器,中控制器设置为接收模式,中控制器根据接收的数据控制RGB LED灯的开关、亮度和颜色,能够通过多个中控制器上的RGB LED灯显示控制效果。 各种中控制器支持的通信协议原理、硬件设计均在前面相关章节中介绍,可参考。不同的通信协议拓扑结构和包格式均符合OSI标准。由于DALI和DMX512具有明确的协议标准,与其他协议不兼容,因此,多网络融合实验只包括RS485、ZigBee和WiFi三种协议。本开发平台制定的协议格式均是对用户数据的定义,如图362所示。 图362数据格式 2. 多网络融合实验网络构建 首先,通过杜邦线连接主控制器上的串口和RS485、WiFi、ZigBee这三个通信模块,实现主控制器的MCU能够通过以上通信模块发送信息。 其次,连接主控制器和中控制器上对应的通信模块,使它们能够正常通信。RS485是有线通信方式,需要用杜邦线连接主控制器和中控制器上的RS485模块,WiFi和ZigBee是无线通信方式,需要单独配置,详情见“WiFi模块配置方法”和“ZigBee模块配置方法”。 最后,按下主控制器上的按键,发送控制信息,通过主控制器上的通信模块进行传输。各个中控制器设置为接收模式,通过相应的通信模块接收主控制器的控制信息。 多网络融合实验网络构建如图363所示,图中主控制器即实验箱内的总控制器,起到网关的功能,每个通信协议模块通过处理器的串口与主控制器相连,实现不同协议间的通信。 图363多网络融合实验网络构建 3. 网络融合控制流程 首先,通过按下主控制器的按键,使主控制器的MCU发送相应的RGB LED灯的颜色、亮度和开关的控制信息,经过RS485、ZigBee和WiFi三种通信方式传输。然后,多个中控制器设置为接收模式,中控制器通过对应的通信模块接收该控制信息,该控制信息经过中控制器MCU的解析及处理后,实现对板子上RGB LED灯的控制。 通过按键选择灯的颜色和亮度。网络融合控制流程如图364所示。 图364网络融合控制流程 4. 程序关键代码 void key_data_send(uint8_t mode) { int i = 0; if(gpio_input_bit_get(GPIOB,GPIO_PIN_4)==RESET ||gpio_input_bit_get(GPIOB,GPIO_PIN_5)==RESET ||gpio_input_bit_get(GPIOB,GPIO_PIN_6)==RESET ||gpio_input_bit_get(GPIOB,GPIO_PIN_7)==RESET)) { delay_1ms(50); if(gpio_input_bit_get(GPIOB,GPIO_PIN_4) == RESET) { valueR += 10; //K1按下,红光数据加10 command[0] = 0x01; command[1] = 0x09; command[2] = valueR; command[3] = valueG; command[4] = valueB; command[5] = 0x00; RS485_TX; for(i = 0; i < 6; i++) { usart_data_transmit(USART1,(uint8_t)command[i]); usart_data_transmit(USART0,(uint8_t)command[i]); usart_data_transmit(USART2,(uint8_t)command[i]); delay_1ms(1); } RS485_RX; } if(gpio_input_bit_get(GPIOB,GPIO_PIN_5) == RESET) { valueG += 10; //K2按下,绿光数据加10 …; //其他代码同上 } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_6) == RESET) { valueB += 10; //K3按下,蓝光数据加10 …; //其他代码同上 } else if(gpio_input_bit_get(GPIOB,GPIO_PIN_7) == RESET) { valueR = 0;valueG = 0;valueB = 0; //K4按下,数据清零 …; //其他代码同上 } } }