文章目录
使用的板为TI的MSP430F5529套件
GPIO
GPIO的特性:
- 可以独立控制每个GPIO口的方向(输入/输出模式)
- 可以独立设置每个GPIO的输出状态(高/低电平)
- 所有GPIO口在复位后都有一个默认方向
P1—P4具有输入输出、中断和外部功能模块。每个I/O引脚单独配置输入或输出方向,每个引脚可以单独读或写。P1—P8端口具有中断功能,每一个I/O引脚的中断可以单独启用和配置。
实验一、单个按键对LED灯的控制
通过按键对IO口P1.0的操作实现对LED灯的亮灭控制
端口寄存器的说明
名称 | 缩写 | BIT = 1 | BIT = 0 |
---|---|---|---|
方向寄存器 | PxDIR | 输出模式 | 输入模式 |
输入寄存器 | PxIN | 输入高电平 | 输入低电平 |
输出寄存器 | PxOUT | 输出高电平 | 输出低电平 |
上下拉电阻使能寄存器 | PxREN | 使能 | 禁用 |
功能选择寄存器 | PxSEL | 外设功能 | IO端口 |
驱动强度寄存器 | PxDS | 高强度 | 低强度 |
中断使能寄存器 | PxIE | 允许中断 | 禁止中断 |
中断标志寄存器 | PxIFG | 有中断请求 | 无中断请求 |
通过上表可知,在读写IO口之前要先设置方向寄存器(PxDIR)的模式。读IO,实际上就是读输入寄存器(PxIN);写IO,实际上就是写输出寄存器(PxOUT)
根据输出电平的“强弱”分类,可将 IO 输出分为推挽/推拉输出、上拉电阻输出、下拉电阻输出 3 种。(此处有强弱1强弱0之分)
MSP430G5529.h中有如下宏定义
#define BIT0 (0x0001u)
#define BIT1 (0x0002u)
#define BIT2 (0x0004u)
#define BIT3 (0x0008u)
#define BIT4 (0x0010u)
#define BIT5 (0x0020u)
#define BIT6 (0x0040u)
#define BIT7 (0x0080u)
#define BIT8 (0x0100u)
#define BIT9 (0x0200u)
#define BITA (0x0400u)
#define BITB (0x0800u)
#define BITC (0x1000u)
#define BITD (0x2000u)
#define BITE (0x4000u)
#define BITF (0x8000u)
MSP430的按"位"操作
这里采用的"按位"操作并不是指MSP430具备了位操作的能力,按位操作实际上是对整个字节都进行了"操作",只不过对其中的7位的值没有影响而已。
写位操作
For example:
P1OUT |= 0x01; // 将 P1.0 置 1 ,按位“或”,相当于置 1
P1OUT &= ~0x02; // 将 P1.1 置0,取反后再按位“与”,相当于置 0
P1OUT ^= 0x04; // 将 P1.2 取反,按位“异或”相当于取反
由上述中的宏定义,对“位”操作也可改写为:
P1OUT |= BIT0; //按位“或”,相当于置1
P1OUT &= ~BIT1; //取反后再按位“与”,相当于置0
P1OUT ^= BIT2; //按位“异或”,相当于取反
另外,也能够通过加号对多位同时操作。例如要将 P1.0、P1.1、P1.2 均置 1,不影响其他位:
P1OUT |= BIT0 + BIT1 + BIT2;
读位操作
读位操作是通过 if 语句的判断得到的。这种方法同样不意味着MSP430能够对位进行读取,这种方法同样需要对一个字节的 8 位进行操作
unsigned char temp = 0;
if( ( P1IN & BIT1 ) == 0 )
{
P2OUT |= BIT0; // 读取 P1.1 的状态写入 P2.0
}
else
{
P2OUT &= ~BIT0;
}
if( ( P1IN & BIT0 ) == 0 )
{
temp = 1; // 读取 P1.0 的状态写入 temp
}
else
{
temp = 0;
}
单个按键对LED灯的控制
#include "msp430f5529.h"
int main(void)
{
WDTCTL = WDTPW + WDTHOLD;
P1DIR |= BIT0; // 设置 P1.0 为输出模式
P2REN |= BIT1; // 使能 P2.1 为上下拉电阻功能
P2OUT |= BIT1; // 设置 P2.1 为上拉电阻输出方式
while(1)
{
if(P2IN & BIT1) // 判断按键是否按下,按下时为 P2.1 = 0,抬起时 p2.1 = 1(看下面的原理图)
{
P1OUT |= BIT0; // 当抬起时,P1.0 输出高电平
}
else
{
P1OUT &= ~BIT0; // 当按下时,P1.0 输出低电平
}
}
}
如下图原理图所示按键按下时与 GND 相连所以相当于置 0
MSP430时钟系统
MSP430单片机的时钟分为MCLK、SMCLK和ACLK三个。三种时钟的功能区别如下:
- MCLK:主时钟(Main SystemClock),专为CPU运行提供的时钟。MCLK频率配置的越高,CPU执行的速度越快。虽然CPU速度越快功耗也越高,但高频率的MCLK可以让CPU工作时间更短。所以正确的低功耗设计并不是要尽量降低MCLK,而是在不用CPU时立刻关闭MCLK。在大部分应用中,需要CPU运算的时间都非常短,所以,间歇开启MCLK(唤醒CPU)的方法节能效果非常明显。
- SMCLK:子系统时钟(Sub-mainClock),专为一些需要高速时钟的片内外设提供服务,比如定时器和ADC采样等。当CPU休眠时,只要SMCLK开启,定时器和ADC仍可工作(一般待片内外设完成工作后触发中断,唤醒CPU去做后续工作)。
- ACLK:辅助时钟(AuxiliaryClock),辅助时钟的频率很低,所以即使一直开启功耗也不大,当然关掉也是可以的。辅助时钟可以供给那些只需低频时钟的片内外设,比如LCD控制器,还可用于产生节拍时基,与定时器配合间歇唤醒CPU。
中断
按键LED中断程序
#include <msp430.h>
int main(void)
{
WDTCTL = WDTPW + WDTHOLD;
P1DIR |= BIT0; // 设置为输出
P2OUT |= BIT1; // 设置为上拉输出
P2REN |= BIT1; // 上下拉使能
P2IE |= BIT1; // 局部中断使能(打开局部中断)
P2IES |= BIT1; // 选择边沿触发,从高电平到低电平(下降沿触发)
P2IFG &= ~BIT1; // 清除中断标志(对于多源中断来说必须先请中断标志再打开中断,也是为了确保初始化后的标志位不会触发中断)
_BIS_SR(GIE); // 启用全局中断
while (1)
{
__no_operation(); // Do nothing
}
}
// Port 1 interrupt ser-vice routine
#pragma vector=PORT2_VECTOR
__interrupt void Port_2(void)
{
P1OUT ^= BIT0; // 取反
P2IFG &= ~BIT1; // 清除中断标志
}
#pragma vector=PORT1_VECTOR
#Pragma是编译器指令,是告诉编译器将函数与中断向量连接起来。“vector=”后面是中断向量地址的宏定义,例如P1口中断就是PORT1_VECTOR,定时器中断就是TIMER0_A1_VECTOR。
__interrupt void Port_1(void)
__interrupt关键字表明这是一个中断服 务函数,CPU见到这个关键字以后就会去做中断之前的准备工作。Port_1是用户自己取的函数名称,这个名称可以任意命名。
下图为中断使能寄存器 P1IE、P2IE,0 表示禁用中断,1 表示启用中断
下图为中断边缘选择寄存器 P1IES、P2IES,0 表示 PxIFGx 标志设置为低到高的转换,1 表示 PxIFGx 标志从高到低的转换
下图为中断标志寄存器 P1IFG、P2IFG,0 表示没有待处理的中断,1 表示有中断待处理
定时器
MSP430F5529共有两类共4个定时器,分别是Timer_A定时器3个和Timer_B定时器1个,按照每个寄存器配备的捕获/比较器的个数分别命名为Timer0_A(内有5个捕获比较器)、Timer1_A(3个)、Timer2_A(3个)、Timer0_B(7个)。
在MSP430系列定时器的命名规则中,Timer后面的数字表示定时器的编号;而下划线后面的A或B表示定时器的类型;A后面的数字3表示定时器中包含3个比较/捕获单元。
Timer_A定时器是带有中断功能的,当定时器计数值到达设定的值时,将自动产生一个定时器中断信号。我们可以利用这个功能方便地制造一个周期性的中断,这样就可以每隔一段事件执行一个指令。利用这个中断也可以进行延时,并且这样的延时是不需要消耗CPU资源的。
Timer_A定时器除了完成计数以外,还“附赠”了一个重要的功能模块,叫做比较/捕获单元。顾名思义,这个单元有捕获和比较这两个功能。捕获功能的作用是可以自动捕捉一个外部信号的电平变化,并记录下该变化发生的时刻。例如一个周期性的方波,可以通过捕获单元分别记录两次上升沿(电平由低变高)或是下降沿(电平由高变低)的时刻,并将二者相减来计算出波形的周期。而比较功能最大的作用是可以自动产生脉宽调制波形(PWM),目前各种数字控制信号中很多都是通过PWM波来实现的,利用比较功能可以在不消耗CPU资源的情况下产生多路PWM波形。但需要注意的是,在同一时刻下,一个比较/捕获单元中只能在比较和捕获功能中选择一个进行使用,两个功能是无法同时使用的。
Timer_A的核心是一个16位的计数器,其最大计数值是2的16次方,即从0到65535之间计数。在每个定时器的时钟周期,计数器的值会自动加1(或减1,取决于计数模式),我们可以通过配置寄存器来选择定时器的时钟周期。
计数器的计数值存在一个名为TAR的寄存器当中。默认状态下,当计数器的值达到65535之后,计数器会自动回到0并重新开始计数——这叫做计数器溢出。当计数器溢出时,定时器可以产生一个溢出中断,Timer_A的溢出中断标志位TAIFG会被置为1。这时如果我们开启了定时器中断使能位TAIE以及全局中断使能位,则MSP430会自动进入到定时器溢出中断服务函数中。
计数器的工作模式
通过TACTL寄存器中的MCx位可以配置,其中MCx=00
为停止,另外3种模式分别是:连续计数模式(continuousmode)、向上计数模式(up mode)、向上/向下计数模式(up/downmode)。
连续计数模式:设置MCx=10
,计数器将工作在连续计数模式下。在此模式下,TAR寄存器将从0到65535连续增加,到65535后则清零重新计数。在连续计数模式下,定时器的周期仅由时钟源的频率决定,频率越高,则越快计数到65535,定时器周期越短。
向上计数模式:设置MCx=01
,计数器将工作在向上计数模式。与连续计数模式不同的是,向上计数模式时计数器的溢出值是由TACCR0寄存器设定的,计数器达到设定值后将自动清零。例如将TACCR0设为40959,则TAR的值只能在0到40959之间计数。在向上计数模式下,定时器周期不仅与时钟源有关,还与TACCR0的设定值有关。
向上/向下计数模式:设置MCx=11
,计数器将工作在向上/向下计数模式。与向上计数不同的是,当计数器到达TACCR0的设定值之后,计数器不清零,而是从递增变成递减,直到计数器的值回到0。在向上/向下计数模式下,定时器的周期是向上计数模式的两倍。
从上述3中工作模式的描述中可以看到,计数器工作时有2个关键节点,分别是计数达到CCR0和65535。与之对应的,定时器可以产生2个中断,分别是CCR0中断和溢出中断,它们对应的中断标志位分别是CC0IFG和TA0IFG。因为Timer_A定时器有3个比较/捕获单元,所以其实还可以产生CCR1和CCR2中断。
1、CCR0中断
当计数器的值达到CCR0时,定时器会产生CCR0中断。CCR0中断是定时器所有中断中优先级最高的,并且单独拥有一个中断向量。而CCR1和CCR2与定时器溢出中断共用一个中断向量。CCR0中断的使能位CCIE和标志位CCIFG都在TACCTL0寄存器中,其中断向量名是TIMER0_A0_VECTOR。
要使用CCR0中断,首先要使能CCIE使能位,然后最关键的一步就是设置CCR0寄存器的值。无论是在向上计数模式还是向上/向下计数模式,当计数器的值到达CCR0时CCIFG都会被置位。当进入CCR0的中断服 务函数以后,CCIFG标志位会自动复位,不需要手动设置。
下面是一个CCR0中断的示例程序:
int main(void)
{
...
TACCTL0 = CCIE; // CCR0 中断使能
TACCR0 = 1000; // 设置 CCR0 值
...
}
// Timer A0 CCR0 interrupt ser-vice routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A(void)
{
...
}
TACCTL0 寄存器:Timer_A 捕获/比较控制 0,控制 CCR0 中断的使能
TACCR0 寄存器:Timer_A 捕获/比较 0,设置 CCR0 的值
2、定时器溢出中断
除了CCR0中断之外,计数器还可以产生一个溢出中断。需要注意的是溢出中断发生的时刻并不是TAR的计数值等于溢出值的时刻,而是TAR的值返回到0的时刻。例如在向上计数模式下,CCR0中断信号产生的时刻是TAR等于CCR0的时刻,而溢出中断产生的时刻是TAR溢出后返回0的时刻,二者相差了一个时钟周期。
溢出中断的使能位TAIE和标志位TAIFG都在TACTL寄存器中。溢出中断与CCR1、CCR2中断共同分享一个中断向量,向量名是TIMER0_A1_VECTOR。那么如果同时开启了溢出中断和CCR1/CCR2中断,如何区分究竟是哪一个中断触发了中断向量呢?在定时器中有一个寄存器TAIV,它的第1-3位包含了中断源的信息。在中断服务函数中读取TAIV寄存器的值可以判断究竟是哪一个中断信号触发了中断。
对TAIV的读/写操作都会使当前优先级最高的一个中断标志位自动复位。如果此时还有其他中断在等待,那么该中断会在当前中断结束后执行。例如当前TACCR1和TACCR2标志位都被置位,那么读写TAIV之后,TACCR1的标志位会被自动复位,但TACCR2不会。当前中断服务函数执行完之后会直接进入TACCR2的中断。
下面是一个定时器溢出中断的示例程序。与CCR0中断相比,主要是多了读取TAIV来判断中断源的语句。
int main(void)
{
...
TACTL = TASSEL_2 + MC_2 + TAIE; // 时钟, 控制模式, 中断
...
}
// Timer_A interrupt vector (TA0IV) handler
#pragma vector=TIMER0_A1_VECTOR
__interrupt void Timer_A(void)
{
switch( TA0IV ) // 用来判断是 CCR1、CCR2、溢出中断三种中的哪一种中断
{
case 2: break; // CCR1 not used
case 4: break; // CCR2 not used
case 10: P1OUT |= 0x01; // overflow
break;
}
}
Timer_A 计数器的使用步骤:
- 选择定时器的时钟来源(TASSELx)及分频值(IDx),常用的时钟来源有ACLK和SMCLK,此外也可以用外部时钟输入到TACLK引脚作为时钟源
- 确定定时器工作模式(MCx)
- 根据定时器工作模式确定使用哪个中断,并进行初始化。如果是CCR0中断需要打开使能位并设置CCR0的值;如果是溢出中断只要打开使能位即可
- 编写对应的中断服务函数
TASSELx(Timer_A 时钟源选择)
TASSELx | 时钟 |
---|---|
00 | TACLK |
01 | ACLK |
10 | SMCLK |
11 | INCLK(INCLK 是特定于设备的,通常分配给反转的 TBCLK)(请参阅特定于设备的数据表) |
MCx(确定定时器的工作模式),即连续计数、向上计数、向上/向下计数
MCx | 模式 |
---|---|
00 | 停止模式:定时器停止。 |
01 | 向上模式:定时器计数到 TACCR0 |
10 | 连续模式:定时器计数到 0FFFFh |
11 | 向上/向下模式:定时器向上计数到 TACCR0,然后向下计数到 0000h |
下图为TACTL(Timer_A 控制寄存器)的详细参数
系统时钟的设置如下图所示
PWM输出
注意:比较模块产生的PWM波只能在特定引脚输出,且CCRx寄存器与TAx引脚一一对应。例如Timer_A0CCR1产生的PWM只能在TA0.1,Timer_A0 CCR2产生的PWM只能在TA0.2,以此类推。需要注意的是在比较模块中,CCR0是用来设定周期的,因此一个定时器最多可以产生2路不同的PWM波形。
操作步骤:
- 先配置 PWM 的输出引脚(在 MSP430 中,这些芯片用 TA0.x 或 TA1.x 来标明。TA0.0-TA0.4 是 Timer0 的输出端口,TA1.0-TA1.2 是 Timer1 的输出端口,TA2.0-TA2.2 是 Timer2 的输出端口,但是由于CCR0 在比较模式下不能用来输出 PWM,所以极限情况下一共可以输出 8 路 PWM 波)
- 设定 CCR0 和 CCRx 的值(周期由 CCR0 寄存器决定,CCR0 的值乘以定时器时钟周期就是 PWM 的周期,占空比 = CCRx/CCR0,在输出模式 7 下,CCRx 的值与占空比成正比)
- 选择比较/捕获模块的输出模式(我们一般选择输出模式 7 来生成 PWM 波,输出模式通过 TACCTLx 寄存器来设置,例如要选择 TA1.1 引脚的输出模式,就需要配置 TA1CCTL1 寄存器)
- 选择定时器的时钟源和计数模式(定时器的时钟源也决定了PWM的周期。Timer_A 定时器可以使用 SMCLK 或者 ACLK,并且可以分频。时钟和分频都由 TACTL 寄存器设定。另外这个寄存器也负责选择计数器的计数模式,我们一般选择向上计数模式)
- MSP430开始自动生成PWM波,CPU可进入低功耗模式(设置好上述步骤以后,比较模块就将开始生成PWM波。由于这样产生的PWM波完全是由定时器生成的,与CPU无关,因此现在CPU可以去处理更重要的任务,或是进入低功耗模式休息)
PWM输出程序
#include "msp430f5529.h"
int main( void )
{
// Stop watchdog timer to prevent time out reset
WDTCTL = WDTPW + WDTHOLD;
P2DIR |= BIT0; // Set P2.0 as TA1.1
P2SEL |= BIT0; // SEL寄存器:配置IO口功能
TA1CCR0 = 21000; // CCR0 设置 PWM 周期 20ms
TA1CCR1 = 2625; // CCRx 设置 PWM 占空比 0.5ms-2.5ms 525-2625
TA1CCTL1 = OUTMOD_7; // 设置 TACCTLx 寄存器的输出模式为模式 7
// 设定好 TACTL 寄存器之后,定时器就开始工作了,因此一般将设置TACTL的指令放在最后
TA1CTL = TASSEL_2 + MC_1; // 选择定时器的时钟源为 SMCLK,计数模式为向上计数模式 注意!!!!这里 SMCLK 频率应该是1.05MHz!!!!
// 上面时钟频率的参考
_BIS_SR(CPUOFF); // Enter LMP0
}
上述时钟 SMCLK 时钟频率为 1.05MHz 请转至链接
想了解更多时钟频率请参考该链接
串行通信
MSP430的串行通信外设叫做USCI(universal serialcommunication interface)。USCI又分为两个版本,USCI_A和USCI_B。USCI_A可配置为UART、LIN、IrDA、SPI模式。USCI_B可配置为SPI和I2C模式。
用USCI模块实现UART其实很简单,只要配置好初始化参数之后,将要发送的数据写入发送缓冲器TXBUF,或是从接收缓冲期RXBUF中读取收到的数据即可。
USCI_A UART 的配置流程
MSP430单片机上电以后,单片机会自动将USCI的复位控制位UCSWRST置为1。这个操作将自动将USCI中断相关的寄存器(包括UCAxRXIE、UCAxTXIE、UCAxRXIFG、UCRXERR、UCBRK、UCPE、UCOE、UCFE、UCSTOE及UCBTOE)复位。我们在使用USCI模块前要手动将UCSWRST置0,然后USCI模块才能正常工作。对USCI进行配置的推荐流程如下:
- 令 UCSWRST = 1,对 USCI 模块进行复位(先置 1 使 USCI 模块复位,然后在置 0 使 USCI 模块正常工作)
- 改写相关寄存器(UCAxCTL0、UCAxCTL1等),对 USCI 进行初始化
- 配置相关I/O口
- 令 UCSWRST = 0
- 配置 UCAxRXIE 和 UCAxTXIE 寄存器,使能 USCI 中断
注意引脚要用跳线帽短接
#include <msp430f5529.h>
void delay(int ms) // 延时函数
{
for(int i=0;i<ms;i++)
for(int j=0;j<240;j++);
}
void send_buf(unsigned char *ptr) // 发送函数
{
while(*ptr != '\0')
{
while(!(UCA1IFG & UCTXIFG));
UCA1TXBUF = *ptr;
ptr++;
delay(50);
}
}
void main(void)
{
WDTCTL = WDTPW + WDTHOLD; // 关闭看门狗
P4DIR |= BIT7; // 初始化 LED1
//串口初始化
P4SEL |= BIT4+BIT5 ; // 设置 P4.4/4.5 的引脚模式
UCA1CTL1 |= UCSWRST; // 将状态机置于复位状态(复位寄存器后再手动开启)
UCA1CTL1 |= UCSSEL_1; // 选择 ACLK 时钟
// 设置波特率
UCA1BR0 = 0x03; // 32768Hz 9600 波特率为 9600
UCA1BR1 = 0x00; // 32768Hz 9600
// UCAxMCTL 调制控制寄存器
UCA1MCTL |= UCBRS_3 + UCBRF_0; // Modulation UCBRSx=1, UCBRFx=0
UCA1CTL1 &= ~UCSWRST; // **Initialize USCI state machine**
UCA1IE |= UCRXIE; // 使能中断
// 按键点灯
P2IE |= BIT1;
P2IES |= BIT1;
P2IFG &= ~BIT1;
P2REN |= BIT1;
P2OUT |= BIT1;
_EINT(); //中断使能
while(1)
{
send_buf("串口通信\r\n");
}
}
#pragma vector = USCI_A1_VECTOR
__interrupt void USCI_ISR()
{
switch(__even_in_range(UCA1IV,4))
{
case 0:break; // Vector 0 - No interrupt
case 2: // Vector 2 - RXIFG
UCA1TXBUF = UCA1RXBUF; // 发送接收到的数据
// 如需对发送信息进行处理修改此处
break;
case 4:break; // Vector 4 - TXIFG
default: break;
}
}
#pragma vector = PORT2_VECTOR
__interrupt void P2_ISR()
{
if(P2IFG & BIT1)
{
while((P2IN & BIT1)==0);
P4OUT ^= BIT7;
send_buf("Pressed\r\n");
}
P2IFG &=~ BIT1;
}
OELD显示(四针IIC)
// P3.1 Clock (UCB0SCL)
//
// P3.0 Data (UCB0SDA)
#include <msp430.h>
#include <stdint.h>
#include <stdbool.h>
#include "oledfont.h"
//******************************************************************************
// Example Commands ************************************************************
//******************************************************************************
#define X_WIDTH 128
#define Y_WIDTH 64
#define OLED_CMD 0 //写命令
#define OLED_DATA 1 //写数据
#define SLAVE_ADDR 0x3C
/* CMD_TYPE_X_SLAVE are example commands the master sends to the slave.
* The slave will send example SlaveTypeX buffers in response.
*
* CMD_TYPE_X_MASTER are example commands the master sends to the slave.
* The slave will initialize itself to receive MasterTypeX example buffers.
* */
#define CMD_TYPE_0_SLAVE 0
#define CMD_TYPE_1_SLAVE 1
#define CMD_TYPE_2_SLAVE 2
#define CMD_TYPE_0_MASTER 3
#define CMD_TYPE_1_MASTER 4
#define CMD_TYPE_2_MASTER 5
#define TYPE_0_LENGTH 1
#define TYPE_1_LENGTH 2
#define TYPE_2_LENGTH 6
#define MAX_BUFFER_SIZE 20
/* MasterTypeX are example buffers initialized in the master, they will be
* sent by the master to the slave.
* SlaveTypeX are example buffers initialized in the slave, they will be
* sent by the slave to the master.
* */
uint8_t MasterType2 [TYPE_2_LENGTH] = {'F', '4', '1', '9', '2', 'B'};
uint8_t MasterType1 [28] = {0xae,0x00,0x10,0x40,0x81,0xcf,0xa1,0xc8,0xa6,0xa8,0x3f,0xd3,0x00,0xd5,0x80,0xd9,0xf1,0xda,0x12,0xdb,0x40,0x20,0x02,0x8d,0x14,0xa4,0xa6,0xaf};
uint8_t MasterType0 [1] = {0};
uint8_t SlaveType2 [TYPE_2_LENGTH] = {0};
uint8_t SlaveType1 [TYPE_1_LENGTH] = {0};
uint8_t SlaveType0 [TYPE_0_LENGTH] = {0};
void delay(unsigned int z)
{
unsigned int x,y;
for(x=z;x>0;x--)
for(y=5000;y>0;y--);
}
//******************************************************************************
// General I2C State Machine ***************************************************
//******************************************************************************
typedef enum I2C_ModeEnum{
IDLE_MODE,
NACK_MODE,
TX_REG_ADDRESS_MODE,
RX_REG_ADDRESS_MODE,
TX_DATA_MODE,
RX_DATA_MODE,
SWITCH_TO_RX_MODE,
SWITHC_TO_TX_MODE,
TIMEOUT_MODE
} I2C_Mode;
/* Used to track the state of the software state machine*/
I2C_Mode MasterMode = IDLE_MODE;
/* The Register Address/Command to use*/
uint8_t TransmitRegAddr = 0;
/* ReceiveBuffer: Buffer used to receive data in the ISR
* RXByteCtr: Number of bytes left to receive
* ReceiveIndex: The index of the next byte to be received in ReceiveBuffer
* TransmitBuffer: Buffer used to transmit data in the ISR
* TXByteCtr: Number of bytes left to transfer
* TransmitIndex: The index of the next byte to be transmitted in TransmitBuffer
* */
uint8_t ReceiveBuffer[MAX_BUFFER_SIZE] = {0};
uint8_t RXByteCtr = 0;
uint8_t ReceiveIndex = 0;
uint8_t TransmitBuffer[MAX_BUFFER_SIZE] = {0};
uint8_t TXByteCtr = 0;
uint8_t TransmitIndex = 0;
/* I2C Write and Read Functions */
/* For slave device with dev_addr, writes the data specified in *reg_data
*
* dev_addr: The slave device address.
* Example: SLAVE_ADDR
* reg_addr: The register or command to send to the slave.
* Example: CMD_TYPE_0_MASTER
* *reg_data: The buffer to write
* Example: MasterType0
* count: The length of *reg_data
* Example: TYPE_0_LENGTH
* */
I2C_Mode I2C_Master_WriteReg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data, uint8_t count);
/* For slave device with dev_addr, read the data specified in slaves reg_addr.
* The received data is available in ReceiveBuffer
*
* dev_addr: The slave device address.
* Example: SLAVE_ADDR
* reg_addr: The register or command to send to the slave.
* Example: CMD_TYPE_0_SLAVE
* count: The length of data to read
* Example: TYPE_0_LENGTH
* */
I2C_Mode I2C_Master_ReadReg(uint8_t dev_addr, uint8_t reg_addr, uint8_t count);
void CopyArray(uint8_t *source, uint8_t *dest, uint8_t count);
I2C_Mode I2C_Master_ReadReg(uint8_t dev_addr, uint8_t reg_addr, uint8_t count)
{
/* Initialize state machine */
MasterMode = TX_REG_ADDRESS_MODE;
TransmitRegAddr = reg_addr;
RXByteCtr = count;
TXByteCtr = 0;
ReceiveIndex = 0;
TransmitIndex = 0;
/* Initialize slave address and interrupts */
UCB0I2CSA = dev_addr;
UCB0IFG &= ~(UCTXIFG + UCRXIFG); // Clear any pending interrupts
UCB0IE &= ~UCRXIE; // Disable RX interrupt
UCB0IE |= UCTXIE; // Enable TX interrupt
UCB0CTL1 |= UCTR + UCTXSTT; // I2C TX, start condition
__bis_SR_register(LPM0_bits + GIE); // Enter LPM0 w/ interrupts
return MasterMode;
}
I2C_Mode I2C_Master_WriteReg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data, uint8_t count)
{
/* Initialize state machine */
MasterMode = TX_REG_ADDRESS_MODE;
TransmitRegAddr = reg_addr;
//Copy register data to TransmitBuffer
CopyArray(reg_data, TransmitBuffer, count);
TXByteCtr = count;
RXByteCtr = 0;
ReceiveIndex = 0;
TransmitIndex = 0;
/* Initialize slave address and interrupts */
UCB0I2CSA = dev_addr;
UCB0IFG &= ~(UCTXIFG + UCRXIFG); // Clear any pending interrupts
UCB0IE &= ~UCRXIE; // Disable RX interrupt
UCB0IE |= UCTXIE; // Enable TX interrupt
UCB0CTL1 |= UCTR + UCTXSTT; // I2C TX, start condition
__bis_SR_register(LPM0_bits + GIE); // Enter LPM0 w/ interrupts
return MasterMode;
}
void CopyArray(uint8_t *source, uint8_t *dest, uint8_t count)
{
uint8_t copyIndex = 0;
for (copyIndex = 0; copyIndex < count; copyIndex++)
{
dest[copyIndex] = source[copyIndex];
}
}
//******************************************************************************
// Device Initialization *******************************************************
//******************************************************************************
void initClockTo16MHz()
{
UCSCTL3 |= SELREF_2; // Set DCO FLL reference = REFO
UCSCTL4 |= SELA_2; // Set ACLK = REFO
__bis_SR_register(SCG0); // Disable the FLL control loop
UCSCTL0 = 0x0000; // Set lowest possible DCOx, MODx
UCSCTL1 = DCORSEL_5; // Select DCO range 16MHz operation
UCSCTL2 = FLLD_0 + 487; // Set DCO Multiplier for 16MHz
// (N + 1) * FLLRef = Fdco
// (487 + 1) * 32768 = 16MHz
// Set FLL Div = fDCOCLK
__bic_SR_register(SCG0); // Enable the FLL control loop
// Worst-case settling time for the DCO when the DCO range bits have been
// changed is n x 32 x 32 x f_MCLK / f_FLL_reference. See UCS chapter in 5xx
// UG for optimization.
// 32 x 32 x 16 MHz / 32,768 Hz = 500000 = MCLK cycles for DCO to settle
__delay_cycles(500000);//
// Loop until XT1,XT2 & DCO fault flag is cleared
do
{
UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + DCOFFG); // Clear XT2,XT1,DCO fault flags
SFRIFG1 &= ~OFIFG; // Clear fault flags
}while (SFRIFG1&OFIFG); // Test oscillator fault flag
}
void initGPIO()
{
//I2C Pins
P3SEL |= BIT0 + BIT1; // P3.0,1 option select
}
void initI2C()
{
UCB0CTL1 |= UCSWRST; // Enable SW reset(复位使能)
UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC; // Master, I2C,synchronous mode(同步模式)
UCB0CTL1 = UCSSEL_2 + UCSWRST; // Use SMCLK, keep SW reset
UCB0BR0 = 160; // fSCL = SMCLK/160 = ~100kHz
UCB0BR1 = 0;
UCB0I2CSA = SLAVE_ADDR; // Slave Address is 048h
UCB0CTL1 &= ~UCSWRST; // Clear SW reset, resume operation
UCB0IE |= UCNACKIE; //使能中断
}
void OLED_WrCmd(unsigned char IIC_Command)
{
MasterType0[0]=IIC_Command;
I2C_Master_WriteReg(SLAVE_ADDR, 0x00,MasterType0, 1);
}
void OLED_WrDat(unsigned char IIC_Data)
{
MasterType0[0]=IIC_Data;
I2C_Master_WriteReg(SLAVE_ADDR, 0x40,MasterType0, 1);
}
//******************************************************************************
// Main ************************************************************************
// Send and receive three messages containing the example commands *************
//******************************************************************************
void OLED_Fill(unsigned char bmp_dat)
{
unsigned char y,x;
for(y=0;y<8;y++)
{
OLED_WrCmd(0xb0+y);
OLED_WrCmd(0x01);
OLED_WrCmd(0x10);
for(x=0;x<128;x++)
OLED_WrDat(bmp_dat);
}
}
void OLED_Set_Pos(unsigned char x, unsigned char y)
{
OLED_WrCmd(0xb0+y);
OLED_WrCmd(((x&0xf0)>>4)|0x10);
OLED_WrCmd((x&0x0f)|0x01);
}
void OLED_Init(void)
{
unsigned char i=0;
for(;i<28;i++)
{
MasterType0[0]=MasterType1[i];
I2C_Master_WriteReg(SLAVE_ADDR, 0x00,MasterType0, 1);
}
OLED_Fill(0x00); //初始清屏
OLED_Set_Pos(0,0);
}
void OLED_WR_Byte(unsigned dat,unsigned cmd)
{
if(cmd)
{
OLED_WrDat(dat);
}
else {
OLED_WrCmd(dat);
}
}
void OLED_ShowChar(unsigned char x,unsigned char y,unsigned char chr,unsigned char Char_Size)
{
unsigned char c=0,i=0;
c=chr-' ';//得到偏移后的值
if(x>128-1){x=0;y=y+2;}
if(Char_Size ==16)
{
OLED_Set_Pos(x,y);
for(i=0;i<8;i++)
OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
OLED_Set_Pos(x,y+1);
for(i=0;i<8;i++)
OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
}
else {
OLED_Set_Pos(x,y);
for(i=0;i<6;i++)
OLED_WR_Byte(F6x8[c][i],OLED_DATA);
}
}
void OLED_U32toU16(unsigned char x,unsigned char y ,unsigned int n,unsigned char k)
{
switch(n)
{
case 0 :OLED_ShowChar(x,y,'0',k);break;
case 1 :OLED_ShowChar(x,y,'1',k);break;
case 2 :OLED_ShowChar(x,y,'2',k);break;
case 3 :OLED_ShowChar(x,y,'3',k);break;
case 4 :OLED_ShowChar(x,y,'4',k);break;
case 5 :OLED_ShowChar(x,y,'5',k);break;
case 6 :OLED_ShowChar(x,y,'6',k);break;
case 7 :OLED_ShowChar(x,y,'7',k);break;
case 8 :OLED_ShowChar(x,y,'8',k);break;
case 9 :OLED_ShowChar(x,y,'9',k);break;
}
}
void OLED_Show_Number(unsigned char x,unsigned char y ,unsigned int a,unsigned char n)
{
unsigned int b,c,d,e,f,g;
b=a/100000;
c=a%100000/10000;
d=a%10000/1000;
e=a%1000/100;
f=a%100/10;
g=a%10;
if(b!=0)
{
OLED_U32toU16(x,y,b,n);
OLED_U32toU16(x+n/2,y,c,n);
OLED_U32toU16(x+n,y,d,n);
OLED_U32toU16(x+(n/2)*3,y,e,n);
OLED_U32toU16(x+2*n,y,f,n);
OLED_U32toU16(x+(n/2)*5,y,g,n);
}else if(b==0&&c!=0)
{
OLED_U32toU16(x,y,c,n);
OLED_U32toU16(x+n/2,y,d,n);
OLED_U32toU16(x+n,y,e,n);
OLED_U32toU16(x+(n/2)*3,y,f,n);
OLED_U32toU16(x+2*n,y,g,n);
}else if(b==0&&c==0&&d!=0)
{
OLED_U32toU16(x,y,d,n);
OLED_U32toU16(x+n/2,y,e,n);
OLED_U32toU16(x+n,y,f,n);
OLED_U32toU16(x+(n/2)*3,y,g,n);
}else if(b==0&&c==0&&d==0&&e!=0)
{
OLED_U32toU16(x,y,e,n);
OLED_U32toU16(x+n/2,y,f,n);
OLED_U32toU16(x+n,y,g,n);
}else if(b==0&&c==0&&d==0&&e==0&&f!=0)
{
OLED_U32toU16(x,y,f,n);
OLED_U32toU16(x+n/2,y,g,n);
}else
{
OLED_U32toU16(x,y,g,n);
}
}
int main(void) {
WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer
initClockTo16MHz(); //配置系统时钟为16Mhz
initGPIO();
initI2C();
delay(500);
OLED_Init();
OLED_ShowChar(30, 0 , 'a', 16);
OLED_ShowChar(40, 0 , 'b', 16);
OLED_ShowChar(50, 0 , 'c', 16);
OLED_Show_Number(0, 0, 666, 16);
__bis_SR_register(LPM0_bits + GIE);
}
//******************************************************************************
// I2C Interrupt ***************************************************************
//******************************************************************************
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=USCI_B0_VECTOR
__interrupt void USCI_B0_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(USCI_B0_VECTOR))) USCI_B0_ISR (void)
#else
#error Compiler not supported!
#endif
{
//Must read from UCB0RXBUF
uint8_t rx_val = 0;
switch(__even_in_range(UCB0IV,0xC))
{
case USCI_NONE:break; // Vector 0 - no interrupt
case USCI_I2C_UCALIFG:break; // Interrupt Vector: I2C Mode: UCALIFG
case USCI_I2C_UCNACKIFG:break; // Interrupt Vector: I2C Mode: UCNACKIFG
case USCI_I2C_UCSTTIFG:break; // Interrupt Vector: I2C Mode: UCSTTIFG
case USCI_I2C_UCSTPIFG:break; // Interrupt Vector: I2C Mode: UCSTPIFG
case USCI_I2C_UCRXIFG:
rx_val = UCB0RXBUF;
if (RXByteCtr)
{
ReceiveBuffer[ReceiveIndex++] = rx_val;
RXByteCtr--;
}
if (RXByteCtr == 1)
{
UCB0CTL1 |= UCTXSTP;
}
else if (RXByteCtr == 0)
{
UCB0IE &= ~UCRXIE;
MasterMode = IDLE_MODE;
__bic_SR_register_on_exit(CPUOFF); // Exit LPM0
}
break; // Interrupt Vector: I2C Mode: UCRXIFG
case USCI_I2C_UCTXIFG:
switch (MasterMode)
{
case TX_REG_ADDRESS_MODE:
UCB0TXBUF = TransmitRegAddr;
if (RXByteCtr)
MasterMode = SWITCH_TO_RX_MODE; // Need to start receiving now
else
MasterMode = TX_DATA_MODE; // Continue to transmission with the data in Transmit Buffer
break;
case SWITCH_TO_RX_MODE:
UCB0IE |= UCRXIE; // Enable RX interrupt
UCB0IE &= ~UCTXIE; // Disable TX interrupt
UCB0CTL1 &= ~UCTR; // Switch to receiver
MasterMode = RX_DATA_MODE; // State state is to receive data
UCB0CTL1 |= UCTXSTT; // Send repeated start
if (RXByteCtr == 1)
{
//Must send stop since this is the N-1 byte
while((UCB0CTL1 & UCTXSTT));
UCB0CTL1 |= UCTXSTP; // Send stop condition
}
break;
case TX_DATA_MODE:
if (TXByteCtr)
{
UCB0TXBUF = TransmitBuffer[TransmitIndex++];
TXByteCtr--;
}
else
{
//Done with transmission
UCB0CTL1 |= UCTXSTP; // Send stop condition
MasterMode = IDLE_MODE;
UCB0IE &= ~UCTXIE; // disable TX interrupt
__bic_SR_register_on_exit(CPUOFF); // Exit LPM0
}
break;
default:
__no_operation();
break;
}
break; // Interrupt Vector: I2C Mode: UCTXIFG
default: break;
}
}