作者共发了3篇帖子。 内容转换:不转换▼
 
点击 回复
735 2
【程序】1602液晶使用FIFO缓冲区方式显示串口接收到的数据,同时6位数码管显示串口数据的累加和
一派护法 十九级
1楼 发表于:2017-3-11 20:21
#include <stdio.h>
#include <stm32f10x.h>

#define RS_0 (GPIOA->BRR = GPIO_BRR_BR0)
#define RS_1 (GPIOA->BSRR = GPIO_BSRR_BS0)
#define RW_0 (GPIOA->BRR = GPIO_BRR_BR1)
#define RW_1 (GPIOA->BSRR = GPIO_BSRR_BS1)
#define E_0 (GPIOA->BRR = GPIO_BRR_BR2)
#define E_1 (GPIOA->BSRR = GPIO_BSRR_BS2)

#define FILE_USART (FILE *)10

struct fifo
{
    uint8_t buf[127];
    uint8_t front;
    uint8_t rear;
};

uint32_t num = 0;
uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};
struct fifo usartbuf;

/*
// 正常的液晶延时函数
void delay(void)
{
    uint8_t i;
    for (i = 0; i < 10; i++);
}
*/

// 故意减慢液晶接收字符的速度
void delay(void)
{
    uint32_t i;
    for (i = 0; i < 100000; i++);
}

void delay2(void)
{
    uint32_t i;
    for (i = 0; i < 3000000; i++);
}

void fifo_init(struct fifo *f)
{
    f->front = f->rear = 0;
}

uint8_t fifo_full(struct fifo *f)
{
    return f->front == (f->rear + 1) % sizeof(f->buf);
}

uint8_t fifo_empty(struct fifo *f)
{
    return f->front == f->rear;
}

uint8_t fifo_in(struct fifo *f, uint8_t data)
{
    if (fifo_full(f))
        return 0;
    f->buf[f->rear] = data;
    f->rear = (f->rear + 1) % sizeof(f->buf);
    return 1;
}

uint8_t fifo_out(struct fifo *f, uint8_t *data)
{
    if (fifo_empty(f))
        return 0;
    *data = f->buf[f->front];
    f->front = (f->front + 1) % sizeof(f->buf);
    return 1;
}

void LCD1602_BusyWait(void)
{
    RS_0;
    RW_1;
    E_1;
    GPIOC->CRL = 0x44444444; // 读端口
    while (GPIOC->IDR & GPIO_IDR_IDR7);
    GPIOC->CRL = 0x33333333;
    E_0;
}

void LCD1602_WriteCmd(uint8_t cmd)
{
    LCD1602_BusyWait();
    RS_0;
    RW_0;
    __disable_irq();
    GPIOC->ODR = (GPIOC->ODR & 0xff00) | cmd;
    __enable_irq();
    E_1;
    delay();
    E_0;
}

void LCD1602_WriteData(uint8_t data)
{
    LCD1602_BusyWait();
    RS_1;
    RW_0;
    __disable_irq();
    GPIOC->ODR = (GPIOC->ODR & 0xff00) | data;
    __enable_irq();
    E_1;
    delay();
    E_0;
}

void LCD1602_Init(void)
{
    LCD1602_WriteCmd(0x38);
    LCD1602_WriteCmd(0x01);
    LCD1602_WriteCmd(0x0c);
    
    // 设置背光电压
    DAC->CR = DAC_CR_EN1;
    DAC->DHR8R1 = 140;
}

int fputc(int ch, FILE *fp)
{
    if (fp == &__stderr || fp == FILE_USART)
    {
        USART1->DR = ch;
        while ((USART1->SR & USART_SR_TXE) == 0);
    }
    else if (fp == &__stdout)
    {
        if (ch == '\n')
            LCD1602_WriteCmd(0x01); // 遇到\n就清屏
        else
            LCD1602_WriteData(ch);
    }
    return ch;
}

uint8_t PCF8591_ADC(void)
{
    uint8_t temp;
    I2C1->CR1 |= I2C_CR1_START; // 开始信号
    while ((I2C1->SR1 & I2C_SR1_SB) == 0);
    I2C1->DR = 0x90; // 地址码(写)
    while ((I2C1->SR1 & I2C_SR1_ADDR) == 0); // PB6~7必须接外部上拉电阻, 否则程序会卡死在这里
    temp = I2C1->SR2; // 读SR2, 清ADDR
    I2C1->DR = 0x04; // 四路单输入, 通道0, 自动增益
    while ((I2C1->SR1 & I2C_SR1_TXE) == 0);
    
    I2C1->CR1 |= I2C_CR1_START; // 重新发送开始信号
    while ((I2C1->SR1 & I2C_SR1_SB) == 0);
    I2C1->DR = 0x91; // 地址码(读)
    while ((I2C1->SR1 & I2C_SR1_ADDR) == 0);
    temp = I2C1->SR2; // 读SR2, 清ADDR
    
    while ((I2C1->SR1 & I2C_SR1_RXNE) == 0);
    temp = I2C1->DR;
    I2C1->CR1 |= I2C_CR1_STOP; // 停止信号
    while (I2C1->CR1 & I2C_CR1_STOP);
    return temp;
}

int main(void)
{
    uint8_t value;
    uint8_t pos = 0;
    uint8_t display_finish = 1;
    RCC->APB1ENR = RCC_APB1ENR_DACEN | RCC_APB1ENR_I2C1EN | RCC_APB1ENR_TIM2EN;
    RCC->APB2ENR = RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_USART1EN;
    
    GPIOA->CRL = 0x00000333; // 1602液晶控制端
    GPIOA->CRH = 0x000004b0; // USART端口
    GPIOB->CRL = 0xff000000; // I2C总线: PB6~7设为复用开漏50MHz输出
    GPIOB->CRH = 0x33333333; // 数码管段选
    GPIOC->CRL = 0x33333333; // 1602液晶数据端
    GPIOC->ODR = 0x3000; // 熄灭数码管
    GPIOC->CRH = 0x00333333; // 数码管位选
    
    // 数码管扫描中断配置
    TIM2->ARR = 29;
    TIM2->PSC = 7199;
    TIM2->EGR = TIM_EGR_UG; // 立即触发TIM2中断, 点亮数码管
    TIM2->DIER = TIM_DIER_UIE;
    NVIC_EnableIRQ(TIM2_IRQn); // 该函数位于core_cm3.h, 是MDK自带的函数, 不是库函数
    TIM2->CR1 |= TIM_CR1_CEN;
    
    fifo_init(&usartbuf);
    
    // MAX232的电源必须接3.3V, 否则PC端会收到很多奇怪的数据, 例如0xfb, 0xff, 0xbf等
    USART1->BRR = 0x271;
    USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE;
    NVIC_SetPriority(USART1_IRQn, 1); // 数码管中断的优先级为0, 因此可以抢占串口接收中断, 防止数码管闪烁
    NVIC_EnableIRQ(USART1_IRQn);
    
    I2C1->CR2 = 36; // FREQ=100100(36MHz)
    I2C1->CCR = 90; // CCR=FREQ/2f, f=200kHz
    I2C1->CR1 = I2C_CR1_PE; // 启动总线
    
    LCD1602_Init();
    perror("Hello World!");
    while (1)
    {
        while (fifo_out(&usartbuf, &value))
        {
            display_finish = 0;
            if (value == '\n')
                pos = 0;
            putchar(value);
            pos++;
            if (pos == 16)
                LCD1602_WriteCmd(0xc0);
            else if (pos == 32)
            {
                pos = 0;
                LCD1602_WriteCmd(0x80);
            }
        }
        
        if (display_finish == 0)
        {
            // 液晶字符显示完毕时, 向串口发送AD转换值
            value = PCF8591_ADC();
            fprintf(FILE_USART, "ADC Value:%d\r\nusartbuf.front=%d, usartbuf.rear=%d\r\n", value, usartbuf.front, usartbuf.rear); // 必须用\r\n才能正确换行
            display_finish = 1;
        }
    }
}

// 数码管动态扫描(高优先级中断)
void TIM2_IRQHandler(void)
{
    static uint8_t pos = 5;
    static uint32_t numbuf;
    if (TIM2->SR & TIM_SR_UIF) // 定时器2溢出
    {
        TIM2->SR &= ~TIM_SR_UIF;
        if (pos == 5)
            numbuf = num;
        else
            numbuf /= 10;
        
        GPIOC->ODR = (GPIOC->ODR & 0xf0ff) | 0x3000; // 关闭位选
        GPIOB->ODR = (GPIOB->ODR & 0xff) | (seg8[numbuf % 10] << 8); // 设置显示字符
        if (pos <= 3)
            GPIOC->ODR |= 1 << (pos + 8); // 位选直连I/O端
        else
            GPIOC->ODR &= ~(1 << (pos + 8)); // 三极管驱动的位选端
        if (pos == 0)
            pos = 5;
        else
            pos--;
    }
}

// 串口接收中断(较低优先级中断)
void USART1_IRQHandler(void)
{
    uint8_t data;
    if (USART1->SR & USART_SR_RXNE)
    {
        data = USART1->DR;
        num = (num + data) % 1000000;
        fifo_in(&usartbuf, data);
    }
}
一派护法 十九级
2楼 发表于:2017-3-11 21:32
【I2C部分的程序有一点错误, 纠正:】
uint8_t PCF8591_ADC(void)
{
    uint8_t temp;
    //uint8_t __rxne;
    I2C1->CR1 |= I2C_CR1_START; // 开始信号
    while ((I2C1->SR1 & I2C_SR1_SB) == 0);
    //__rxne = ((I2C1->SR1 & I2C_SR1_RXNE) != 0); // 检查SB置位后RxNE的状态, 正常情况下应该为0
   
    I2C1->DR = 0x90; // 地址码(写)
    while ((I2C1->SR1 & I2C_SR1_ADDR) == 0); // PB6~7必须接外部上拉电阻, 否则程序会卡死在这里
    temp = I2C1->SR2; // 读SR2, 清ADDR
    I2C1->DR = 0x04; // 四路单输入, 通道0, 自动增益
    while ((I2C1->SR1 & I2C_SR1_TXE) == 0);
   
    I2C1->CR1 |= I2C_CR1_START; // 重新发送开始信号
    while ((I2C1->SR1 & I2C_SR1_SB) == 0);
    I2C1->DR = 0x91; // 地址码(读)
    while ((I2C1->SR1 & I2C_SR1_ADDR) == 0);
    temp = I2C1->SR2; // 读SR2, 清ADDR
   
    I2C1->CR1 |= I2C_CR1_STOP; // 接收最后一个数据时就应该将STOP置位, 否则RxNE将不能正常清零
    while (I2C1->CR1 & I2C_CR1_STOP);
   
    while ((I2C1->SR1 & I2C_SR1_RXNE) == 0);
    temp = I2C1->DR;
    //fprintf(FILE_USART, "RxNE=%d\r\n", __rxne);
    return temp;
}
一派护法 十九级
3楼 发表于:2017-3-11 21:54
主函数中还有一处要纠正的地方:
int main(void)
{
    // ...............
    while (1)
    {
        while (fifo_out(&usartbuf, &value))
        {
            display_finish = 0;
            putchar(value);
            if (value == '\n')
                pos = 0; // 收到换行符时pos不应该加1, 否则液晶的第一行的最后一个字符空间无法利用
            else
                pos++;
            if (pos == 16)
                LCD1602_WriteCmd(0xc0);
            else if (pos == 32)
            {
                pos = 0;
                LCD1602_WriteCmd(0x80);
            }
        }
        // ...............
    }
}

回复帖子

内容:
用户名: 您目前是匿名发表
验证码:
(快捷键:Ctrl+Enter)
 

本帖信息

点击数:735 回复数:2
评论数: ?
作者:巨大八爪鱼
最后回复:巨大八爪鱼
最后回复时间:2017-3-11 21:54
 
©2010-2024 Arslanbar Ver2.0
除非另有声明,本站采用知识共享署名-相同方式共享 3.0 Unported许可协议进行许可。