UART的发送与接收(RS232)
单片机通过串口发送数据时,需要选择停止位、校验位和数据位。停止位有三种选择:1、1.5、2;校验位也有三种选择:奇校验、偶校验和无校验。数据位有四种选择:8bit,7bit,6bit,5bit。一般没有特殊要求,数据位选择8bit,刚好一字节,方便数据处理。这三个信息一般配置为:1个停止位,无校验位和8位数据。传输一帧数据为9个bit。
PIC24HJ128GP506A Uart的相关寄存器:a.Uartx模式寄存器、b.Uartx状态和控制寄存器、c、Uartx发送寄存器、d、Uartx接收寄存器(只读)、Uartx波特率发送寄存器。波特率设置跟PIC24HJ所用的时钟有关,有具体的波特率计算公式,下面列出。
先说明PIC24HJ的波特率如何计算:BRGH是Uartx模式寄存器中的一位,BRGH = 0表示使用低速波特率;BRGH = 1表示使用高速波特率。高速和低速波特率计算方法稍有区别。a.BRGH = 0:
[color=rgba(0, 0, 0, 0.75)]主要根据公式1反推出公式2,假设我们要设置波特率为9600,就可以反推出UxBRG的值,UxBRG是波特率发生器寄存器,公式2计算值就是写入该寄存器。b.BRGH = 1:
可以看到区别就在于16变成了4,自然波特率就比刚才的大了。将反推出来的值赋给UxBRG寄存器即可,设置好相应的波特率。
设置UxMODE寄存器,这个寄存器一般设置停止位,数据位和校验位,自动波特率的开启和选择波特率的模式(高速或低速),该寄存器控制着UART的开启。寄存器的每一位请自行查看PIC24的数据手册;接下来就是设置UxSTA寄存器,该寄存器设置发送和接收时产生中断的方式,一般选择发送或接收一个char产生中断。然后该寄存器控制着发送功能的开启。这里有必要说明:必选先开启UART功能,然后再开启TX发送功能,在程序里面的顺序很重要,否则无法使用UART的发送功能。。UART数据接收与发送寄存器很简单,一个用来读数据,另一个用来写数据。下面给出UART的配置代码,并使用XCOM上位机通信。
// PIC24HJ128GP506A Configuration Bit Settings
// 'C' source line config statements
// FBS
#pragma config BWRP = WRPROTECT_OFF // Boot Segment Write Protect (Boot Segment may be written)
#pragma config BSS = NO_FLASH // Boot Segment Program Flash Code Protection (No Boot program Flash segment)
#pragma config RBS = NO_RAM // Boot Segment RAM Protection (No Boot RAM)
// FSS
#pragma config SWRP = WRPROTECT_OFF // Secure Segment Program Write Protect (Secure segment may be written)
#pragma config SSS = NO_FLASH // Secure Segment Program Flash Code Protection (No Secure Segment)
#pragma config RSS = NO_RAM // Secure Segment Data RAM Protection (No Secure RAM)
// FGS
#pragma config GWRP = OFF // General Code Segment Write Protect (User program memory is not write-protected)
#pragma config GSS = OFF // General Segment Code Protection (User program memory is not code-protected)
// FOSCSEL
#pragma config FNOSC = PRIPLL // Oscillator Mode (Primary Oscillator (XT, HS, EC) w/ PLL)
#pragma config IESO = ON // Two-speed Oscillator Start-Up Enable (Start up with FRC, then switch)
// FOSC
#pragma config POSCMD = HS // Primary Oscillator Source (HS Oscillator Mode)
#pragma config OSCIOFNC = OFF // OSC2 Pin Function (OSC2 pin has clock out function)
#pragma config FCKSM = CSECMD // Clock Switching and Monitor (Clock switching is enabled, Fail-Safe Clock Monitor is disabled)
// FWDT
#pragma config WDTPOST = PS32768 // Watchdog Timer Postscaler (1:32,768)
#pragma config WDTPRE = PR128 // WDT Prescaler (1:128)
#pragma config WINDIS = ON // Watchdog Timer Window (Watchdog Timer in Window mode)
#pragma config FWDTEN = OFF // Watchdog Timer Enable (Watchdog timer enabled/disabled by user software)
// FPOR
#pragma config FPWRT = PWR128 // POR Timer Value (128ms)
// FICD
#pragma config ICS = PGD1 // Comm Channel Select (Communicate on PGC1/EMUC1 and PGD1/EMUD1)
#pragma config JTAGEN = ON // JTAG Port Enable (JTAG is Enabled)
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#include <xc.h>
#define FCY 40000000
#define BAUDRATE 9600
#define BRGVAL ((FCY/BAUDRATE)/16)-1
unsigned int i;
char RecvData;
void uart_init(void)
{
//Configure System Clock.
/* Fosc = Fin * M /(N1 * N2) */
PLLFBD = 18; //M = 20
CLKDIVbits.PLLPOST = 0; //N1 = 2
CLKDIVbits.PLLPRE = 0; //N2 = 2
OSCTUN = 0;
RCONbits.SWDTEN = 0;
//Wait for PLL to lock
while(OSCCONbits.LOCK != 1);
U1MODEbits.STSEL = 0;
U1MODEbits.PDSEL = 0;
U1MODEbits.ABAUD = 0;
U1MODEbits.BRGH = 0;
U1BRG = BRGVAL; //波特率9600
U1STAbits.UTXISEL0 = 0;
U1STAbits.URXISEL = 0;
IEC0bits.U1TXIE = 1;
IEC0bits.U1RXIE = 1;
IFS0bits.U1TXIF = 0;
IPC2bits.U1RXIP = 0x02;
IPC3bits.U1TXIP = 0x01;
U1MODEbits.UARTEN = 1; //使能UART
U1STAbits.UTXEN = 1; //使能UART 发送
for(i = 0; i < 4160; i++){
Nop();
}
U1TXREG = 'a';
}
void led_init(void)
{
TRISDbits.TRISD10 = 0; //设置D7端口为输出模式
}
int main(void)
{
uart_init();
led_init(); //初始化LED
while(1){
if(U1STAbits.URXDA == 1){
RecvData = U1RXREG;
U1TXREG = RecvData;
}
if(RecvData == 'o'){
LATDbits.LATD10 = 0;
}else if(RecvData == 'c'){
LATDbits.LATD10 = 1;
}
}
return 0;
}
void __attribute__((__interrupt__, __no_auto_psv__)) _U1TXInterrupt(void)
{
IFS0bits.U1TXIF = 0;
}
void __attribute__((__interrupt__, __no_auto_psv__)) _U1RXInterrupt(void)
{
IFS0bits.U1RXIF = 0;
}
代码中宏定义 #define FCY 40000000以为指令执行速度,和上面给出公式中的Fcy是一个概念,表示当前UART使用的时钟源是40MHz,这是我设置外部时钟源得到的时钟频率,有关时钟频率的设置在我上一篇时钟设置里面有提到。在这里提醒一下自己:写完代码的时候,发现上位机下发数据,单片机接收数据变量的值,并不与上位机的值一样,找了一天代码的错误,代码怎么看都没错,最后发现硬件连接上接了一个RS485转RS232的器件,它就是罪魁祸首,有时候检查错误,错误不一定在软件上,每个地方都应该排查,不能忽视。这个程序是基于硬件RS232实现的全双工串口通信。接下来在此基础上介绍RS485半双工的串口通信。
UART实现RS485通信
其实了解RS232和RS485通信的区**,RS485很容易实现。它是半双工通信,意思就是说:在同一时段内,只能有一个发送方和接收方,不能同时发送和接收。RS485区分逻辑1和逻辑是计算两根信号的电压差值来判决逻辑1和逻辑0的。术语叫做差分信号,就是说RS485传输的是差分信号。使用TC485H芯片实现485通信。该芯片有一个控制传输方向的引脚D/R,这样就可以使用PIC24HJ一个普通I/O口来控制数据传输方向了。
这里需要注意:单片机发送数据到上位机,这时候D/R引脚默认为这个传输方向,而上位机不能下发数据到单片机,所以想要把数据传输控制权交给上位机,就需要想办法把TC485H的D/R引脚电平进行跳变。否则RS485通信就成了单工通信了。
这里给出一个粗略的解决办法:开启一个定时器,让单片机发送完数据,到时间后,跳板TC485H D/R引脚电平,将发送权交给上位机。上位机可以发送特定的数据再将发送权交给单片机。这样就实现了RS485半双工通讯。下面给出参考代码:
// PIC24HJ128GP506A Configuration Bit Settings
// 'C' source line config statements
// FBS
#pragma config BWRP = WRPROTECT_OFF // Boot Segment Write Protect (Boot Segment may be written)
#pragma config BSS = NO_FLASH // Boot Segment Program Flash Code Protection (No Boot program Flash segment)
#pragma config RBS = NO_RAM // Boot Segment RAM Protection (No Boot RAM)
// FSS
#pragma config SWRP = WRPROTECT_OFF // Secure Segment Program Write Protect (Secure segment may be written)
#pragma config SSS = NO_FLASH // Secure Segment Program Flash Code Protection (No Secure Segment)
#pragma config RSS = NO_RAM // Secure Segment Data RAM Protection (No Secure RAM)
// FGS
#pragma config GWRP = OFF // General Code Segment Write Protect (User program memory is not write-protected)
#pragma config GSS = OFF // General Segment Code Protection (User program memory is not code-protected)
// FOSCSEL
#pragma config FNOSC = PRIPLL // Oscillator Mode (Primary Oscillator (XT, HS, EC) w/ PLL)
#pragma config IESO = ON // Two-speed Oscillator Start-Up Enable (Start up with FRC, then switch)
// FOSC
#pragma config POSCMD = HS // Primary Oscillator Source (HS Oscillator Mode)
#pragma config OSCIOFNC = OFF // OSC2 Pin Function (OSC2 pin has clock out function)
#pragma config FCKSM = CSDCMD // Clock Switching and Monitor (Both Clock Switching and Fail-Safe Clock Monitor are disabled)
// FWDT
#pragma config WDTPOST = PS32768 // Watchdog Timer Postscaler (1:32,768)
#pragma config WDTPRE = PR128 // WDT Prescaler (1:128)
#pragma config WINDIS = OFF // Watchdog Timer Window (Watchdog Timer in Non-Window mode)
#pragma config FWDTEN = ON // Watchdog Timer Enable (Watchdog timer always enabled)
// FPOR
#pragma config FPWRT = PWR128 // POR Timer Value (128ms)
// FICD
#pragma config ICS = PGD1 // Comm Channel Select (Communicate on PGC1/EMUC1 and PGD1/EMUD1)
#pragma config JTAGEN = OFF // JTAG Port Enable (JTAG is Disabled)
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#include <xc.h>
#define FCY 40000000
#define BAUDRATE 9600
#define BRGVALUE ((FCY/BAUDRATE)/16)-1
void init_clk(void)
{
/* initial Fcy to 40MHz */
PLLFBD = 18; //M = 18
CLKDIVbits.PLLPOST = 0; //N1 = 2
CLKDIVbits.PLLPRE = 0; //N2 = 2
OSCTUN = 0;
RCONbits.SWDTEN = 0; //关闭看门狗时钟
while(OSCCONbits.LOCK != 1); //Wait for PLL to Lock.
}
void timer1_init(void)
{
T1CONbits.TON = 0;
T1CONbits.TCS = 0; //select internal clock 40MHz
T1CONbits.TGATE = 0;
T1CONbits.TCKPS = 0; //一分频
TMR1 = 0x00;
PR1 = 39999; //定时1ms
/* Timer1 Interrupt */
IPC0bits.T1IP = 0x01;
IFS0bits.T1IF = 0;
}
void relay_init(void)
{
TRISDbits.TRISD11 = 0;
}
void init_uart_rs485(void)
{
uint16_t i;
/* initial tc485h D/R io */
TRISFbits.TRISF6 = 0; //配置为输出状态
LATFbits.LATF6 = 0; //默认为接收,上位机先发送
U1MODEbits.STSEL = 0; //0 stop-bit
U1MODEbits.PDSEL = 0; //No Parity and 8 Data-Bits
U1MODEbits.ABAUD = 0; //auto baud close
U1MODEbits.BRGH = 0; //low baud mode
U1BRG = BRGVALUE; //set baudrate to 9600 bps
U1STAbits.URXISEL = 0; //Recv one character to interrupt.
U1STAbits.UTXISEL0 = 0; //Trans one character to interrupt.
/* Configure TX & RX interrupt */
IPC3bits.U1TXIP = 0x02;
IPC2bits.U1RXIP = 0x03;
IFS0bits.U1TXIF = 0;
IFS0bits.U1RXIF = 0;
IEC0bits.U1TXIE = 1;
IEC0bits.U1RXIE = 1;
U1MODEbits.UARTEN = 1; //Enable UART
U1STAbits.UTXEN = 1; //Enable TX
for(i = 0; i < 4160; i++){
Nop();
}
}
int main(void)
{
char Recv;
init_clk();
init_uart_rs485();
timer1_init();
relay_init();
while(1){
if(U1STAbits.URXDA == 1){
Recv = U1RXREG;
}
if(Recv == 'o'){
LATDbits.LATD11 = 1;
}
if(Recv == 't'){
LATFbits.LATF6 = 1; //rs485 TX
//定时1ms
T1CONbits.TON = 1;
IEC0bits.T1IE = 1;
}
if(Recv == 'c'){
LATDbits.LATD11 = 0;
}
}
return 0;
}
void __attribute__((__interrupt__, __no_auto_psv__)) _U1TXInterrupt(void)
{
//Clear TX IF
IFS0bits.U1TXIF = 0;
}
void __attribute__((__interrupt__, __no_auto_psv__)) _U1RXInterrupt(void)
{
//Clear RX IF
IFS0bits.U1RXIF = 0;
}
void __attribute__((__interrupt__, __no_auto_psv__)) _T1Interrupt(void)
{
static uint16_t cnt = 0;
cnt++;
if(cnt == 500)
U1TXREG = 'a';
if(cnt == 1000){
LATFbits.LATF6 = 0; //rs485 RX
//关闭定时器
T1CONbits.TON = 0;
cnt = 0;
}
//Clear T1 UP_Flow IF
IFS0bits.T1IF = 0;
}
在定时器1中断函数中一定要注意把计数器cnt的值在恰当位置清零,刚开始我没有清零,上位机下发’t’给单片机后,第一次单片机很快给上位机回复了字符a,进行第二次这样的操作,单片机回复字符a需要很长时间,大概一分钟左右,造成这个结果的原因就是cnt没有清零,cnt的数据类型是无符号整型,它的最大值为216-1,**单片机是一毫秒进一次定时器中断,**也就是说,在单片机第一次发送完字符a后,cnt的值一直朝着最大值自增,这里计算一下从零加到最大值需要多少时间,自增一次的时间是1ms,216 - 1是65535ms,折合65.5s,一分钟多一点,这还是自增到最大值,还需要加上cnt溢出后,从零开始计数到1000的时间,即1s,所以说第二次发送字符a的时间间隔为65.5s+1s=66.5秒,时间还是有点稍长。这里再次提醒自己:使用计数器一定要清零。
|