1. 简单 LED 闪烁控制
1.1 原理
LED 闪烁控制是单片机应用中最基础的实验之一,它可以帮助初学者理解单片机的基本工作原理和编程方法。PIC16 系列单片机通过控制 GPIO(General Purpose Input/Output)端口来实现 LED 的点亮和熄灭。定时器功能可以用来实现 LED 的定时闪烁。
1.2 内容
在本节中,我们将通过一个简单的例子来说明如何使用 PIC16 单片机控制 LED 闪烁。我们将使用 PIC16F877A 单片机,通过定时器 T0 来实现 LED 的定时控制。
1.3 代码示例
首先,我们需要配置单片机的 GPIO 端口和定时器 T0。假设我们使用 RA0 端口来控制 LED。
#include <xc.h>
#include <pic16f877a.h>
// 定义 LED 端口
#define LED RA0
// 配置定时器 T0
void configureTimer0() {
// 选择定时器 T0 的时钟源为内部时钟
T0CS = 0;
// 选择定时器 T0 的预分频器为 256
PSA = 0;
T01CS = 1;
// 初始化定时器 T0 的计数值
TMR0 = 0;
// 开启定时器 T0
T0IF = 0;
T0IE = 1;
GIE = 1;
}
// 配置 GPIO 端口
void configureGPIO() {
// 设置 RA0 为输出端口
TRISA0 = 0;
// 初始化 LED 状态为熄灭
LED = 0;
}
void main() {
// 配置单片机的工作模式
CMCON = 0x07; // 禁用比较器
// 配置 GPIO 端口
configureGPIO();
// 配置定时器 T0
configureTimer0();
while (1) {
// 主循环,等待定时器中断
}
}
// 定时器 T0 中断处理函数
void interrupt ISR() {
if (T0IF) {
// 定时器 T0 中断标志位清零
T0IF = 0;
// 重新初始化定时器 T0 的计数值
TMR0 = 0;
// 切换 LED 状态
LED = !LED;
}
}
2. 温度传感器数据采集
2.1 原理
温度传感器数据采集是单片机应用中的一个常见场景。PIC16 系列单片机可以通过 ADC(Analog-to-Digital Converter)模块将温度传感器的模拟信号转换为数字信号,然后通过串行通信模块将数据发送到计算机或其他设备进行处理和显示。
2.2 内容
在本节中,我们将使用 PIC16F877A 单片机和一个常见的温度传感器(如 LM35)来实现温度数据的采集和显示。我们将使用 ADC 模块来读取温度传感器的模拟信号,并通过 UART 模块将温度数据发送到串口。
2.3 代码示例
首先,我们需要配置 ADC 和 UART 模块。
#include <xc.h>
#include <pic16f877a.h>
#include <stdio.h>
// 定义温度传感器连接的端口
#define TEMP_SENSOR RA0
// 配置 ADC 模块
void configureADC() {
// 选择 RA0 作为 ADC 输入通道
ADCON0 = 0x01;
// 选择 ADC 的时钟源为 Fosc/8
ADCON1 = 0x06;
// 开启 ADC
ADON = 1;
}
// 配置 UART 模块
void configureUART() {
// 设置波特率为 9600
SPBRG = 51; // Fosc = 4MHz, 9600 bps
// 配置 UART 为异步模式,8 位数据,无校验位
TXSTA = 0x20;
RCSTA = 0x90;
// 开启 UART
TXEN = 1;
RCEN = 1;
}
// 读取 ADC 值
unsigned int readADC() {
// 开始转换
GO = 1;
// 等待转换结束
while (GO);
// 返回转换结果
return ((ADRESH << 8) | ADRESL);
}
// 发送温度数据到 UART
void sendTemperature(unsigned int adcValue) {
float temperature = (adcValue * 5.0 / 1024.0) * 100.0;
char buffer[10];
sprintf(buffer, "Temp: %.2f C\r\n", temperature);
for (int i = 0; i < 10; i++) {
while (!TRMT); // 等待发送缓冲区为空
TXREG = buffer[i];
}
}
void main() {
// 配置单片机的工作模式
CMCON = 0x07; // 禁用比较器
// 配置 GPIO 端口
TRISA0 = 1; // 设置 RA0 为输入端口
// 配置 ADC 模块
configureADC();
// 配置 UART 模块
configureUART();
while (1) {
// 读取温度传感器的 ADC 值
unsigned int adcValue = readADC();
// 发送温度数据到 UART
sendTemperature(adcValue);
// 延时 1 秒
__delay_ms(1000);
}
}
3. RC5 编码解码
3.1 原理
RC5 编码是一种常见的红外遥控编码方式,常用于家电设备的遥控控制。PIC16 系列单片机可以通过定时器和中断处理来实现 RC5 编码的解码。解码过程中需要检测红外信号的脉冲宽度和频率,从而识别出遥控器发送的命令。
3.2 内容
在本节中,我们将使用 PIC16F877A 单片机实现 RC5 编码的解码。我们将通过定时器 T0 和外部中断 INT0 来检测红外信号的脉冲宽度,并解析出相应的命令。
3.3 代码示例
首先,我们需要配置定时器 T0 和外部中断 INT0。
#include <xc.h>
#include <pic16f877a.h>
#include <stdint.h>
// 定义红外信号输入端口
#define IR_SIGNAL RB0
// RC5 编码状态变量
uint16_t rc5_data = 0;
uint8_t rc5_bit_count = 0;
uint8_t rc5_toggle = 0;
uint8_t rc5_command = 0;
uint8_t rc5_address = 0;
// 配置定时器 T0
void configureTimer0() {
// 选择定时器 T0 的时钟源为内部时钟
T0CS = 0;
// 选择定时器 T0 的预分频器为 256
PSA = 0;
T01CS = 1;
// 初始化定时器 T0 的计数值
TMR0 = 0;
// 开启定时器 T0
T0IF = 0;
T0IE = 1;
GIE = 1;
}
// 配置外部中断 INT0
void configureINT0() {
// 设置 INT0 端口为输入端口
TRISB0 = 1;
// 配置 INT0 中断
INTCON = 0x80; // 开启外部中断,全局中断使能
INTE = 1; // 开启 INT0 中断
}
// 读取红外信号
void readIRSignal() {
// 检测脉冲宽度
if (IR_SIGNAL) {
// 脉冲高电平
if (TMR0 >= 888) { // 逻辑 1
rc5_data = (rc5_data << 1) | 1;
} else if (TMR0 >= 444) { // 逻辑 0
rc5_data = (rc5_data << 1) | 0;
}
} else {
// 脉冲低电平
TMR0 = 0; // 重置定时器
if (rc5_bit_count == 14) {
// 解码完成
rc5_toggle = (rc5_data >> 13) & 0x01;
rc5_address = (rc5_data >> 6) & 0x1F;
rc5_command = (rc5_data >> 1) & 0x1F;
rc5_bit_count = 0;
rc5_data = 0;
} else {
rc5_bit_count++;
}
}
}
void main() {
// 配置单片机的工作模式
CMCON = 0x07; // 禁用比较器
// 配置定时器 T0
configureTimer0();
// 配置外部中断 INT0
configureINT0();
while (1) {
// 主循环,等待中断
}
}
// 外部中断 INT0 中断处理函数
void interrupt ISR() {
if (INTF) {
// 外部中断标志位清零
INTF = 0;
// 读取红外信号
readIRSignal();
}
}
4. 电机控制
4.1 原理
电机控制是单片机应用中的一个重要领域,特别是在机器人和自动化设备中。PIC16 系列单片机可以通过 PWM(Pulse Width Modulation)模块来实现对电机的精确控制。PWM 信号的频率和占空比可以用来调节电机的速度和方向。
4.2 内容
在本节中,我们将使用 PIC16F877A 单片机来实现对直流电机的 PWM 控制。我们将通过 CCP(Capture/Compare/PWM)模块生成 PWM 信号,并通过定时器 T1 来控制 PWM 的频率和占空比。
4.3 代码示例
首先,我们需要配置 CCP 模块和定时器 T1。
#include <xc.h>
#include <pic16f877a.h>
#include <stdint.h>
// 定义电机控制端口
#define MOTOR_PWM RB3
// 配置 CCP 模块
void configureCCP() {
// 选择 CCP1 为 PWM 模式
CCP1CON = 0x0C;
// 设置 PWM 频率为 1kHz
PR1 = 250; // Fosc = 4MHz, 预分频器 = 16
// 设置初始占空比
CCPR1L = 125;
CCP1X = 0;
CCP1Y = 0;
// 开启 PWM
CC1EN = 1;
}
// 配置定时器 T1
void configureTimer1() {
// 选择定时器 T1 的时钟源为内部时钟
T1CKPS0 = 0;
T1CKPS1 = 1;
T1OSCEN = 0;
T1SYNC = 0;
T1CS = 0;
// 开启定时器 T1
TMR1ON = 1;
}
// 调节 PWM 占空比
void setPWM Duty(uint8_t duty) {
// 计算 CCPR1L 值
CCPR1L = (duty * 250) / 100;
}
void main() {
// 配置单片机的工作模式
CMCON = 0x07; // 禁用比较器
// 配置 GPIO 端口
TRISB3 = 0; // 设置 RB3 为输出端口
// 配置 CCP 模块
configureCCP();
// 配置定时器 T1
configureTimer1();
while (1) {
// 调节 PWM 占空比
setPWM Duty(50); // 50% 占空比
__delay_ms(1000);
setPWM Duty(75); // 75% 占空比
__delay_ms(1000);
setPWM Duty(25); // 25% 占空比
__delay_ms(1000);
}
}
5. 简单的按键控制
5.1 原理
按键控制是单片机应用中的基本功能之一。通过读取按键的状态,单片机可以执行相应的操作。PIC16 系列单片机可以通过 GPIO 端口来检测按键的状态,并通过中断处理来实现按键的去抖动。
5.2 内容
在本节中,我们将使用 PIC16F877A 单片机实现一个简单的按键控制功能。我们将通过外部中断 INT0 来检测按键的状态,并在按键按下时点亮一个 LED。
5.3 代码示例
首先,我们需要配置外部中断 INT0 和 GPIO 端口。
#include <xc.h>
#include <pic16f877a.h>
// 定义按键输入端口
#define KEY RB0
// 定义 LED 输出端口
#define LED RA0
// 配置外部中断 INT0
void configureINT0() {
// 设置 RB0 为输入端口
TRISB0 = 1;
// 配置 INT0 中断
INTCON = 0x80; // 开启外部中断,全局中断使能
INTE = 1; // 开启 INT0 中断
}
// 配置 GPIO 端口
void configureGPIO() {
// 设置 RA0 为输出端口
TRISA0 = 0;
// 初始化 LED 状态为熄灭
LED = 0;
}
void main() {
// 配置单片机的工作模式
CMCON = 0x07; // 禁用比较器
// 配置 GPIO 端口
configureGPIO();
// 配置外部中断 INT0
configureINT0();
while (1) {
// 主循环,等待中断
}
}
// 外部中断 INT0 中断处理函数
void interrupt ISR() {
if (INTF) {
// 外部中断标志位清零
INTF = 0;
// 延时去抖动
__delay_ms(20);
// 检测按键是否仍然按下
if (KEY == 0) {
// 点亮 LED
LED = 1;
} else {
// 熄灭 LED
LED = 0;
}
}
}
6. 串行通信实例
6.1 原理
串行通信是单片机与外部设备进行数据交换的重要方式。PIC16 系列单片机通常使用 UART 模块来实现串行通信。通过配置 UART 的波特率、数据格式等参数,可以实现与计算机或其他设备的通信。
6.2 内容
在本节中,我们将使用 PIC16F877A 单片机实现一个简单的串行通信示例。我们将通过 UART 模块接收来自计算机的数据,并将接收到的数据发送回计算机进行显示。
6.3 代码示例
首先,我们需要配置 UART 模块。
#include <xc.h>
#include <pic16f877a.h>
#include <stdio.h>
// 配置 UART 模块
void configureUART() {
// 设置波特率为 9600
SPBRG = 51; // Fosc = 4MHz, 9600 bps
// 配置 UART 为异步模式,8 位数据,无校验位
TXSTA = 0x20;
RCSTA = 0x90;
// 开启 UART
TXEN = 1;
RCEN = 1;
}
// 发送字符到 UART
void sendChar(char data) {
while (!TRMT); // 等待发送缓冲区为空
TXREG = data;
}
// 接收字符从 UART
char receiveChar() {
while (!RCIF); // 等待接收缓冲区不为空
return RCREG;
}
void main() {
// 配置单片机的工作模式
CMCON = 0x07; // 禁用比较器
// 配置 UART 模块
configureUART();
while (1) {
// 接收来自计算机的数据
char data = receiveChar();
// 将接收到的数据发送回计算机
sendChar(data);
}
}
7. 简单的 LCD 显示
7.1 原理
LCD 显示是单片机应用中常见的输出方式。PIC16 系列单片机可以通过 GPIO 端口和特定的驱动库来控制 LCD 显示器。常见的 LCD 显示器有 1602 和 2004 等。通过配置 GPIO 端口和发送相应的控制命令,单片机可以实现对 LCD 显示器的初始化、字符显示和光标移动等功能。
7.2 内容
在本节中,我们将使用 PIC16F877A 单片机实现一个简单的 LCD 显示示例。我们将通过 GPIO 端口控制 1602 LCD 显示器,并在 LCD 上显示简单的文本信息。我们将使用一个简单的 LCD 驱动库来简化编程过程。
7.3 代码示例
首先,我们需要配置 GPIO 端口和 LCD 驱动库。
#include <xc.h>
#include <pic16f877a.h>
#include <stdio.h>
#include "lcd.h" // 包含 LCD 驱动库
// 定义 LCD 连接的端口
#define LCD_RS RB0
#define LCD_EN RB1
#define LCD_D4 RB2
#define LCD_D5 RB3
#define LCD_D6 RB4
#define LCD_D7 RB5
// 配置 GPIO 端口
void configureGPIO() {
// 设置 LCD 控制端口为输出端口
TRISB0 = 0; // RS
TRISB1 = 0; // EN
TRISB2 = 0; // D4
TRISB3 = 0; // D5
TRISB4 = 0; // D6
TRISB5 = 0; // D7
}
// 初始化 LCD
void initializeLCD() {
// 初始化 LCD 驱动库
lcd_init(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7);
// 设置 LCD 为 2 行 16 列
lcd_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_2_LINES | LCD_CMD_FONT_5x8);
// 开启显示,关闭光标
lcd_cmd(LCD_CMD_DISPLAY_ON | LCD_CMD_CURSOR_OFF | LCD_CMD_BLINK_OFF);
// 清屏
lcd_cmd(LCD_CMD_CLEAR);
// 返回到初始位置
lcd_cmd(LCD_CMD_RETURN_HOME);
}
void main() {
// 配置单片机的工作模式
CMCON = 0x07; // 禁用比较器
// 配置 GPIO 端口
configureGPIO();
// 初始化 LCD
initializeLCD();
while (1) {
// 在 LCD 上显示文本
lcd_pos(0, 0); // 设置光标位置为第 1 行第 1 列
lcd_printf("PIC16F877A");
lcd_pos(1, 0); // 设置光标位置为第 2 行第 1 列
lcd_printf("Hello, World!");
// 延时 1 秒
__delay_ms(1000);
// 清屏
lcd_cmd(LCD_CMD_CLEAR);
// 延时 1 秒
__delay_ms(1000);
}
}
8. 实时钟(RTC)模块应用
8.1 原理
实时钟(RTC)模块可以提供精确的时间和日期信息,常用于需要时间戳的应用场景。PIC16 系列单片机可以通过 I2C 或 SPI 通信接口与外部 RTC 芯片(如 DS1307)进行通信,获取和设置时间日期信息。
8.2 内容
在本节中,我们将使用 PIC16F877A 单片机和 DS1307 RTC 芯片实现一个简单的实时钟应用。我们将通过 I2C 通信接口读取 DS1307 的时间和日期,并通过 LCD 显示器显示这些信息。
8.3 代码示例
首先,我们需要配置 I2C 模块和 LCD 驱动库。
#include <xc.h>
#include <pic16f877a.h>
#include <stdio.h>
#include "lcd.h" // 包含 LCD 驱动库
#include "i2c.h" // 包含 I2C 驱动库
// 定义 DS1307 的 I2C 地址
#define DS1307_I2C_ADDR 0x68
// 定义 LCD 连接的端口
#define LCD_RS RB0
#define LCD_EN RB1
#define LCD_D4 RB2
#define LCD_D5 RB3
#define LCD_D6 RB4
#define LCD_D7 RB5
// 定义 I2C 连接的端口
#define SDA RB4
#define SCL RB3
// 配置 GPIO 端口
void configureGPIO() {
// 设置 LCD 控制端口为输出端口
TRISB0 = 0; // RS
TRISB1 = 0; // EN
TRISB2 = 0; // D4
TRISB3 = 0; // D5
TRISB4 = 0; // D6
TRISB5 = 0; // D7
// 设置 I2C 端口为输入输出端口
TRISB4 = 1; // SDA
TRISB3 = 1; // SCL
}
// 初始化 I2C 模块
void initializeI2C() {
// 初始化 I2C 模块
i2c_init(SDA, SCL);
}
// 读取 DS1307 的时间
void readTime(uint8_t *seconds, uint8_t *minutes, uint8_t *hours) {
// 开始 I2C 通信
i2c_start();
// 发送 DS1307 地址和写操作
i2c_write(DS1307_I2C_ADDR << 1 | 0x00);
// 发送读取时间的起始地址
i2c_write(0x00);
// 重新开始 I2C 通信
i2c_start();
// 发送 DS1307 地址和读操作
i2c_write(DS1307_I2C_ADDR << 1 | 0x01);
// 读取秒、分、小时
*seconds = i2c_read(0);
*minutes = i2c_read(0);
*hours = i2c_read(1);
// 结束 I2C 通信
i2c_stop();
}
// 将 BCD 编码转换为十进制
uint8_t bcdToDec(uint8_t bcd) {
return ((bcd & 0x0F) + (bcd >> 4) * 10);
}
void main() {
// 配置单片机的工作模式
CMCON = 0x07; // 禁用比较器
// 配置 GPIO 端口
configureGPIO();
// 初始化 I2C 模块
initializeI2C();
// 初始化 LCD
initializeLCD();
while (1) {
// 读取 DS1307 的时间
uint8_t seconds, minutes, hours;
readTime(&seconds, &minutes, &hours);
// 将 BCD 编码转换为十进制
seconds = bcdToDec(seconds);
minutes = bcdToDec(minutes);
hours = bcdToDec(hours);
// 在 LCD 上显示时间
lcd_pos(0, 0); // 设置光标位置为第 1 行第 1 列
char timeStr[9];
sprintf(timeStr, "Time: %02d:%02d:%02d", hours, minutes, seconds);
lcd_printf(timeStr);
// 延时 1 秒
__delay_ms(1000);
// 清屏
lcd_cmd(LCD_CMD_CLEAR);
}
}
9. 简单的直流电机 PID 控制
9.1 原理
PID 控制(比例-积分-微分控制)是一种常用的闭环控制算法,可以实现对电机速度的精确控制。通过 PID 控制器,可以根据实际速度与目标速度的偏差来调整 PWM 信号的占空比,从而实现对电机的精确控制。
9.2 内容
在本节中,我们将使用 PIC16F877A 单片机实现一个简单的直流电机 PID 控制。我们将通过 CCP 模块生成 PWM 信号来控制电机,并通过 ADC 模块读取电机的速度反馈信号。我们将使用一个简单的 PID 控制器来调整 PWM 信号的占空比。
9.3 代码示例
首先,我们需要配置 CCP 模块、定时器 T1 和 ADC 模块。
#include <xc.h>
#include <pic16f877a.h>
#include <stdint.h>
#include <math.h>
// 定义电机控制端口
#define MOTOR_PWM RB3
// 定义速度传感器连接的端口
#define SPEED_SENSOR RA0
// PID 控制器参数
float Kp = 1.0;
float Ki = 0.1;
float Kd = 0.01;
// PID 控制器变量
float setpoint = 100.0; // 目标速度
float current_speed = 0.0; // 当前速度
float error = 0.0;
float integral = 0.0;
float derivative = 0.0;
float last_error = 0.0;
// 配置 CCP 模块
void configureCCP() {
// 选择 CCP1 为 PWM 模式
CCP1CON = 0x0C;
// 设置 PWM 频率为 1kHz
PR1 = 250; // Fosc = 4MHz, 预分频器 = 16
// 设置初始占空比
CCPR1L = 0;
CCP1X = 0;
CCP1Y = 0;
// 开启 PWM
CC1EN = 1;
}
// 配置定时器 T1
void configureTimer1() {
// 选择定时器 T1 的时钟源为内部时钟
T1CKPS0 = 0;
T1CKPS1 = 1;
T1OSCEN = 0;
T1SYNC = 0;
T1CS = 0;
// 开启定时器 T1
TMR1ON = 1;
}
// 配置 ADC 模块
void configureADC() {
// 选择 RA0 作为 ADC 输入通道
ADCON0 = 0x01;
// 选择 ADC 的时钟源为 Fosc/8
ADCON1 = 0x06;
// 开启 ADC
ADON = 1;
}
// 读取 ADC 值
unsigned int readADC() {
// 开始转换
GO = 1;
// 等待转换结束
while (GO);
// 返回转换结果
return ((ADRESH << 8) | ADRESL);
}
// 计算 PID 控制器输出
float calculatePID() {
// 计算误差
error = setpoint - current_speed;
// 积分项
integral += error;
// 微分项
derivative = error - last_error;
last_error = error;
// 计算 PID 输出
return (Kp * error + Ki * integral + Kd * derivative);
}
void main() {
// 配置单片机的工作模式
CMCON = 0x07; // 禁用比较器
// 配置 GPIO 端口
TRISB3 = 0; // 设置 RB3 为输出端口
TRISA0 = 1; // 设置 RA0 为输入端口
// 配置 CCP 模块
configureCCP();
// 配置定时器 T1
configureTimer1();
// 配置 ADC 模块
configureADC();
while (1) {
// 读取速度传感器的 ADC 值
unsigned int adcValue = readADC();
// 转换为速度值
current_speed = (adcValue * 5.0 / 1024.0) * 100.0;
// 计算 PID 控制器输出
float pid_output = calculatePID();
// 调节 PWM 占空比
uint8_t duty = (uint8_t)(pid_output * 2.55); // 将 PID 输出转换为 0-255 的占空比
if (duty > 255) {
duty = 255;
} else if (duty < 0) {
duty = 0;
}
CCPR1L = duty;
// 延时 100 毫秒
__delay_ms(100);
}
}
10. 使用 PIC16 进行简单的数据记录
10.1 原理
数据记录是单片机应用中的一个重要功能,特别是在传感器数据采集和监控系统中。PIC16 系列单片机可以通过内部 EEPROM 或外部存储器来存储数据。通过定时器和中断处理,可以实现数据的周期性记录。
10.2 内容
在本节中,我们将使用 PIC16F877A 单片机实现一个简单的数据记录功能。我们将通过 ADC 模块读取温度传感器的数据,并将这些数据存储到内部 EEPROM 中。我们将使用定时器 T0 来实现数据的周期性记录。
10.3 代码示例
首先,我们需要配置 ADC 模块和定时器 T0。
#include <xc.h>
#include <pic16f877a.h>
#include <stdint.h>
// 定义温度传感器连接的端口
#define TEMP_SENSOR RA0
// 配置 ADC 模块
void configureADC() {
// 选择 RA0 作为 ADC 输入通道
ADCON0 = 0x01;
// 选择 ADC 的时钟源为 Fosc/8
ADCON1 = 0x06;
// 开启 ADC
ADON = 1;
}
// 配置定时器 T0
void configureTimer0() {
// 选择定时器 T0 的时钟源为内部时钟
T0CS = 0;
// 选择定时器 T0 的预分频器为 256
PSA = 0;
T01CS = 1;
// 初始化定时器 T0 的计数值
TMR0 = 0;
// 开启定时器 T0
T0IF = 0;
T0IE = 1;
GIE = 1;
}
// 读取 ADC 值
unsigned int readADC() {
// 开始转换
GO = 1;
// 等待转换结束
while (GO);
// 返回转换结果
return ((ADRESH << 8) | ADRESL);
}
// 将数据写入 EEPROM
void writeEEProm(uint8_t address, uint8_t data) {
// 选择 EEPROM 操作
EECON1bits.EEPGD = 0;
// 选择数据存储
EECON1bits.CFGS = 0;
// 设置地址
EEA = address;
// 写入数据
EEDATA = data;
// 开始写操作
EECON1bits.WREN = 1;
// 写入控制字
EECON2 = 0x55;
EECON2 = 0xAA;
// 写入数据
EECON1bits.WR = 1;
// 等待写操作完成
while (EECON1bits.WR);
// 关闭写使能
EECON1bits.WREN = 0;
}
// 定时器 T0 中断处理函数
void interrupt ISR() {
if (T0IF) {
// 定时器 T0 中断标志位清零
T0IF = 0;
// 重置定时器 T0 的计数值
TMR0 = 0;
// 读取温度传感器的 ADC 值
unsigned int adcValue = readADC();
// 将 ADC 值写入 EEPROM
static uint8_t eepromAddress = 0;
writeEEProm(eepromAddress, (uint8_t)(adcValue >> 8));
writeEEProm(eepromAddress + 1, (uint8_t)(adcValue & 0xFF));
eepromAddress += 2;
}
}
void main() {
// 配置单片机的工作模式
CMCON = 0x07; // 禁用比较器
// 配置 GPIO 端口
TRISA0 = 1; // 设置 RA0 为输入端口
// 配置 ADC 模块
configureADC();
// 配置定时器 T0
configureTimer0();
while (1) {
// 主循环,等待定时器中断
}
}
11. 使用 PIC16 进行简单的无线通信
11.1 原理
无线通信模块可以实现单片机之间的无线数据传输,常用于远程控制和数据采集系统。PIC16 系列单片机可以通过 SPI 或 UART 接口与无线通信模块(如 nRF24L01)进行通信。nRF24L01 是一种常用的 2.4GHz 无线通信模块,具有低功耗和高传输速率的特点。
11.2 内容
在本节中,我们将使用 PIC16F877A 单片机和 nRF24L01 无线通信模块实现一个简单的无线通信示例。我们将通过 SPI 接口配置 nRF24L01 模块,并通过 UART 模块将接收到的数据发送到计算机进行显示。
11.3 代码示例
首先,我们需要配置 SPI 模块、UART 模块和 nRF24L01 模块。
#include <xc.h>
#include <pic16f877a.h>
#include <stdint.h>
#include <stdio.h>
#include "nRF24L01.h" // 包含 nRF24L01 驱动库
// 定义 SPI 连接的端口
#define SCK RB3
#define MOSI RB5
#define MISO RB4
#define CS RB2
#define CE RB1
// 定义 UART 连接的端口
#define TX RB7
#define RX RB6
// 配置 SPI 模块
void configureSPI() {
// 设置 SPI 模式为主模式,时钟极性为 0,时钟相位为 0
SSPCON = 0x21;
// 设置 SPI 时钟频率为 Fosc/64
SSPADD = 0x19;
// 设置 SCK 为输出
TRISB3 = 0;
// 设置 MOSI 为输出
TRISB5 = 0;
// 设置 MISO 为输入
TRISB4 = 1;
// 设置 SPI 使能
SSPSTAT = 0x80;
// 开启 SPI
SSPOV = 0;
SSPEN = 1;
}
// 配置 UART 模块
void configureUART() {
// 设置波特率为 9600
SPBRG = 51; // Fosc = 4MHz, 9600 bps
// 配置 UART 为异步模式,8 位数据,无校验位
TXSTA = 0x20;
RCSTA = 0x90;
// 开启 UART
TXEN = 1;
RCEN = 1;
}
// 发送字符到 UART
void sendChar(char data) {
while (!TRMT); // 等待发送缓冲区为空
TXREG = data;
}
// 接收字符从 UART
char receiveChar() {
while (!RCIF); // 等待接收缓冲区不为空
return RCREG;
}
// 初始化 nRF24L01 模块
void initializeNRF24L01() {
// 配置 nRF24L01 的管脚
TRISB1 = 0; // CE
TRISB2 = 0; // CS
TRISB6 = 1; // RX
TRISB7 = 0; // TX
// 初始化 nRF24L01 模块
nRF24L01_init(SCK, MOSI, MISO, CS, CE);
// 配置 nRF24L01 为接收模式
nRF24L01_setRXMode();
}
// 读取 nRF24L01 接收到的数据
uint8_t readNRF24L01() {
uint8_t data;
if (nRF24L01_available()) {
nRF24L01_read(&data, 1);
return data;
}
return 0;
}
void main() {
// 配置单片机的工作模式
CMCON = 0x07; // 禁用比较器
// 配置 GPIO 端口
TRISB3 = 0; // SCK
TRISB4 = 1; // MISO
TRISB5 = 0; // MOSI
TRISB6 = 1; // RX
TRISB7 = 0; // TX
// 配置 SPI 模块
configureSPI();
// 配置 UART 模块
configureUART();
// 初始化 nRF24L01 模块
initializeNRF24L01();
while (1) {
// 读取 nRF24L01 接收到的数据
uint8_t data = readNRF24L01();
if (data) {
// 将接收到的数据发送到 UART
sendChar(data);
}
}
}
// nRF24L01 驱动库函数
// 以下是一些基本的 nRF24L01 驱动库函数,用于初始化、配置和数据传输
void nRF24L01_init(uint8_t sck, uint8_t mosi, uint8_t miso, uint8_t cs, uint8_t ce) {
// 初始化 SPI 管脚
SCK = sck;
MOSI = mosi;
MISO = miso;
CS = cs;
CE = ce;
// 配置 nRF24L01 的基本参数
nRF24L01_writeReg(NRF24L01_CONFIG, 0x0F); // 使能 RX、TX、CRC 和自动应答
nRF24L01_writeReg(NRF24L01_EN_AA, 0x01); // 使能自动应答
nRF24L01_writeReg(NRF24L01_EN_RXADDR, 0x01); // 使能接收数据
nRF24L01_writeReg(NRF24L01_SETUP_AW, 0x03); // 设置地址宽度为 5 字节
nRF24L01_writeReg(NRF24L01_SETUP_RETR, 0x03); // 设置自动重传次数和时间
nRF24L01_writeReg(NRF24L01_RF_CH, 0x02); // 设置工作频率为 2.402GHz
nRF24L01_writeReg(NRF24L01_RF_SETUP, 0x07); // 设置传输速率为 1Mbps,输出功率为 0dBm
nRF24L01_writeReg(NRF24L01_RX_ADDR_P0, 0xE7); // 设置接收地址
nRF24L01_writeReg(NRF24L01_RX_ADDR_P1, 0xC2); // 设置接收地址
nRF24L01_writeReg(NRF24L01_TX_ADDR, 0xE7); // 设置发送地址
nRF24L01_writeReg(NRF24L01_RX_PW_P0, 0x01); // 设置接收数据长度为 1 字节
nRF24L01_writeReg(NRF24L01_RX_PW_P1, 0x01); // 设置接收数据长度为 1 字节
}
void nRF24L01_writeReg(uint8_t reg, uint8_t value) {
// 选择 nRF24L01 模块
CS = 0;
// 发送写操作命令
nRF24L01_spiWrite(reg | NRF24L01_CMD_W_REGISTER);
// 发送数据
nRF24L01_spiWrite(value);
// 释放 nRF24L01 模块
CS = 1;
}
uint8_t nRF24L01_readReg(uint8_t reg) {
uint8_t value;
// 选择 nRF24L01 模块
CS = 0;
// 发送读操作命令
nRF24L01_spiWrite(reg | NRF24L01_CMD_R_REGISTER);
// 读取数据
value = nRF24L01_spiRead();
// 释放 nRF24L01 模块
CS = 1;
return value;
}
void nRF24L01_spiWrite(uint8_t data) {
// 发送数据
SSPBUF = data;
// 等待发送完成
while (SSPSTATbits.BF);
}
uint8_t nRF24L01_spiRead() {
// 发送空数据
SSPBUF = 0x00;
// 等待接收完成
while (SSPSTATbits.BF);
// 返回接收的数据
return SSPBUF;
}
void nRF24L01_setRXMode() {
// 配置 nRF24L01 为接收模式
nRF24L01_writeReg(NRF24L01_CONFIG, 0x0F);
// 启用接收
CE = 1;
}
void nRF24L01_setTXMode() {
// 配置 nRF24L01 为发送模式
nRF24L01_writeReg(NRF24L01_CONFIG, 0x0E);
// 延时
__delay_ms(1);
}
uint8_t nRF24L01_available() {
// 检查数据是否可用
if (nRF24L01_readReg(NRF24L01_STATUS) & 0x40) {
// 清除数据接收标志
nRF24L01_writeReg(NRF24L01_STATUS, 0x40);
return 1;
}
return 0;
}
void nRF24L01_read(uint8_t *buffer, uint8_t length) {
// 选择 nRF24L01 模块
CS = 0;
// 发送读取数据命令
nRF24L01_spiWrite(NRF24L01_CMD_R_RX_PAYLOAD);
// 读取数据
for (uint8_t i = 0; i < length; i++) {
buffer[i] = nRF24L01_spiRead();
}
// 释放 nRF24L01 模块
CS = 1;
}
void nRF24L01_write(uint8_t *buffer, uint8_t length) {
// 选择 nRF24L01 模块
CS = 0;
// 发送写入数据命令
nRF24L01_spiWrite(NRF24L01_CMD_W_TX_PAYLOAD);
// 发送数据
for (uint8_t i = 0; i < length; i++) {
nRF24L01_spiWrite(buffer[i]);
}
// 释放 nRF24L01 模块
CS = 1;
// 开始发送
CE = 1;
__delay_us(10);
CE = 0;
}
12. 使用 PIC16 进行简单的数据加密
12.1 原理
数据加密是确保数据传输安全的重要手段。PIC16 系列单片机可以通过软件实现简单的数据加密算法,如 AES(Advanced Encryption Standard)或简单的异或加密。这些算法可以用于保护通过无线通信或有线通信传输的数据。
12.2 内容
在本节中,我们将使用 PIC16F877A 单片机实现一个简单的数据加密示例。我们将通过 UART 模块接收来自计算机的数据,使用简单的异或加密算法对数据进行加密,然后将加密后的数据发送回计算机。
12.3 代码示例
首先,我们需要配置 UART 模块。
#include <xc.h>
#include <pic16f877a.h>
#include <stdio.h>
// 定义加密密钥
#define ENCRYPTION_KEY 0xAA
// 配置 UART 模块
void configureUART() {
// 设置波特率为 9600
SPBRG = 51; // Fosc = 4MHz, 9600 bps
// 配置 UART 为异步模式,8 位数据,无校验位
TXSTA = 0x20;
RCSTA = 0x90;
// 开启 UART
TXEN = 1;
RCEN = 1;
}
// 发送字符到 UART
void sendChar(char data) {
while (!TRMT); // 等待发送缓冲区为空
TXREG = data;
}
// 接收字符从 UART
char receiveChar() {
while (!RCIF); // 等待接收缓冲区不为空
return RCREG;
}
// 加密数据
char encryptData(char data) {
return data ^ ENCRYPTION_KEY;
}
// 解密数据
char decryptData(char data) {
return data ^ ENCRYPTION_KEY;
}
void main() {
// 配置单片机的工作模式
CMCON = 0x07; // 禁用比较器
// 配置 UART 模块
configureUART();
while (1) {
// 接收来自计算机的数据
char data = receiveChar();
// 加密数据
char encryptedData = encryptData(data);
// 将加密后的数据发送回计算机
sendChar(encryptedData);
}
}
13. 使用 PIC16 进行简单的红外遥控
13.1 原理
红外遥控是常见的远程控制方式,广泛应用于家电设备。PIC16 系列单片机可以通过外部中断和定时器来检测红外信号的脉冲宽度和频率,从而解码出遥控器发送的命令。
13.2 内容
在本节中,我们将使用 PIC16F877A 单片机实现一个简单的红外遥控应用。我们将通过外部中断 INT0 来检测红外信号,并通过定时器 T0 来解析出遥控器发送的命令。
13.3 代码示例
首先,我们需要配置外部中断 INT0 和定时器 T0。
#include <xc.h>
#include <pic16f877a.h>
#include <stdint.h>
// 定义红外信号输入端口
#define IR_SIGNAL RB0
// 定义 LED 输出端口
#define LED RA0
// 红外信号解码状态变量
uint16_t pulse_width = 0;
uint8_t command = 0;
uint8_t state = 0;
// 配置定时器 T0
void configureTimer0() {
// 选择定时器 T0 的时钟源为内部时钟
T0CS = 0;
// 选择定时器 T0 的预分频器为 256
PSA = 0;
T01CS = 1;
// 初始化定时器 T0 的计数值
TMR0 = 0;
// 开启定时器 T0
T0IF = 0;
T0IE = 1;
GIE = 1;
}
// 配置外部中断 INT0
void configureINT0() {
// 设置 RB0 为输入端口
TRISB0 = 1;
// 配置 INT0 中断
INTCON = 0x80; // 开启外部中断,全局中断使能
INTE = 1; // 开启 INT0 中断
}
// 读取红外信号
void readIRSignal() {
// 检测脉冲宽度
if (IR_SIGNAL) {
// 脉冲高电平
if (TMR0 >= 1000) { // 逻辑 1
// 解码逻辑 1
command = (command << 1) | 1;
}
} else {
// 脉冲低电平
if (TMR0 >= 500) { // 逻辑 0
// 解码逻辑 0
command = (command << 1) | 0;
}
TMR0 = 0; // 重置定时器
if (state == 32) {
// 解码完成
state = 0;
command = 0;
} else {
state++;
}
}
}
// 外部中断 INT0 中断处理函数
void interrupt ISR() {
if (INTF) {
// 外部中断标志位清零
INTF = 0;
// 读取红外信号
readIRSignal();
}
}
void main() {
// 配置单片机的工作模式
CMCON = 0x07; // 禁用比较器
// 配置 GPIO 端口
TRISB0 = 1; // 设置 RB0 为输入端口
TRISA0 = 0; // 设置 RA0 为输出端口
LED = 0; // 初始化 LED 状态为熄灭
// 配置定时器 T0
configureTimer0();
// 配置外部中断 INT0
configureINT0();
while (1) {
// 主循环,等待中断
}
}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/2401_87715305/article/details/145273607
|
|