8051 la rutina serial devuelve todos los 1s a pesar de que los datos aparentemente válidos están en el bus

2

Por lo tanto, estoy tratando de recibir datos seriales síncronos de un DS1306 en modo de 3 cables usando modo 0 de mi AT89LP51ED2 's USART. Sin embargo, mi rutina de lectura en serie está devolviendo todos los 1s en lugar de los datos válidos, aunque los datos válidos parecen estar en el bus serie de acuerdo con lo que veo en mi osciloscopio, el oscilador de 32 kHz funciona normalmente, y Vdd y Vbat están presentes. Sin embargo, escribir parece estar bien, aunque es bastante difícil saberlo con este problema de lectura. También hay un NHD-0216K3Z-FL-GBW módulo LCD en el bus configurado en modo de 3 cables / SPI. Saca la línea serial con 10k & ohm ;, y escribir en ella también funciona, usando una señal de selección de chip independiente.

¿Por qué sucede esto y cómo lo arreglo?

Marcodeserie(estamosleyendolosregistrosdehora/fechade0x0a0x6):

Bytededirección:

Primerbytededatos:

Segundobytededatos:

Tercerbytededatos:

Cuartobytededatos:

Quintobytededatos:

Sextobytededatos:

Séptimobytededatos:

Primerplanorepresentativodevanguardia(tomadodelprimerbyte):

Primerplanorepresentativodelbordeposterior(tambiéntomadodelprimerbyte):

Código,usandoSDCCparael8051(elmain()esunMWEmain()altamentesimplificado,elrestoesdirectodesdeelfirmwareconalgunaspiezasirrelevanteseliminadasycomentariosagregadosen):

#include<stdint.h>#include<stdio.h>#include<ctype.h>#include<stdnoreturn.h>#include<stdbool.h>#include<string.h>voidgpio_init();voidserial_init();voidlcd_init();voidread_and_print_rtc();//Peripheralstuff#include<at89c51ed2.h>__sfr__at(0xE6)P0M0;__sfr__at(0xE7)P0M1;__sfr__at(0xD6)P1M0;__sfr__at(0xD7)P1M1;__sfr__at(0xCE)P2M0;__sfr__at(0xCF)P2M1;__sfr__at(0xC6)P3M0;__sfr__at(0xC7)P3M1;__sfr__at(0xBE)P4M0;__sfr__at(0xBF)P4M1;__sfr__at(0x87)PCON;#defineSMOD10x80#defineSMOD00x40noreturnintmain(){gpio_init();serial_init();lcd_init();while(true)read_and_print_rtc();}typedefunion{uint8_tbuf[7];struct{uint8_tsc;uint8_tmi;uint8_thr;uint8_twd;uint8_tdy;uint8_tmo;uint8_tyr;}tm;}rw_time_t;rw_time_ttb;voidrtc_input(uint8_taddr,uint8_t*chars,intn);voidread_and_print_rtc(void){memset(tb.buf,0,sizeof(tb));//Readthetime&dateinonegotoavoidrolloverwoesrtc_input(0x00,tb.buf,7);//debugdumpoutputprintf("\f%x%x%x", tb.buf[0], tb.buf[1], tb.buf[2]);
    printf("\n%x%x%x", tb.buf[4], tb.buf[5], tb.buf[6]);
}

void gpio_init(void) __reentrant
{
    // We set up the ports as follows:
    // P0: quasi-bidir, default FF
    // P1: quasi-bidir, default FF
    // P2: totem pole, default 0
    // P3.0: quasi-bidir, default 1
    // P3.1: totem pole, default 1
    // P3.2: input only
    // P3.3-P3.5: totem pole, default 0
    // P3.6-P3.7: totem pole, default 1
    // P4.4-P4.7: totem pole, default 1

    // Everything starts up in quasi-bidir as TRIPORT is cleared
    // Our port latches also start up as 0xFF as well
    P0 = 0xFF;
    P1 = 0xFF;
    P2 = 0x0;
    P3 = 0xC7;
    P4 = 0xFF;
    P0M0 = 0x0;
    P0M1 = 0x0;
    P1M0 = 0x0;
    P1M1 = 0x0;
    P2M0 = 0x0;
    P2M1 = 0xFF;
    P3M0 = 0x04;
    P3M1 = 0xFA;
    P4M0 = 0x0;
    P4M1 = 0xFF;
}

void serial_init(void)
{
    // We put the 8051 USART in Mode 0 for this as we are talking to
    // SPI-ish devices (DS1306, SPI-ish LCD) -- SMOD0 in PCON is off
    // by default, which is good, because we don't need FE anyway; 
    // however, we need to set SMOD1 which not only means the BRG runs 
    // at 2x speed but also makes the Rx sample on the rising edge of 
    // the serial clock when SM2=1 for driving the RTC
    PCON |= SMOD1;

    // SM2 is also set to 0 by default, which means the clock idles HIGH
    // This is what the LCD expects, but the RTC expects idle LOW -- 
    // writing to the USART requires a CPOL (SM2) flip before driving SS

    // We set up the BRG for an 83kHz clock rate -- its the fastest 
    // rate we can get from the BRG that meets the 100kHz max on the LCD
    BRL = 196;
    BDRCON = BRR | TBCK | RBCK | SPD | SRC;
}

const int MAXCOLS = 16;
const int MAXROWS = 2;

void _move_cursor(int r, int c);
void _visual_bell(void);

void lcd_output(uint8_t* chars, int n);

#define CONST const __code

void putchar(char c)
{
    // Assume that the LCD has been init'ed with the screen cleared 
    // and the cursor homed the first time through
    static int row = 0, col = 0;
    // The dummy bytes are there to enforce a time delay
    CONST uint8_t cls_cmd[] = {0xFE, 0x51, 0x20, 0x20, 0x20, 0x20, 0x20,
        0x20, 0x20, 0x20, 0x20};
    // 16 spaces to clear a line
    CONST uint8_t clear_line_cmd[] = {
        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
    };
    CONST uint8_t bksp_cmd[] = {0xFE, 0x4E};
    switch (c)
    {
    // Alarm bell -- we need some sort of visual bell here
    case '\a':
        _visual_bell();
        break;
    // Backspace -- this does what you think it does, although backspacing
    // from column 0 needs to be handled specially
    case '\b':
        if (col > 0)
        {
            --col;
            lcd_output(bksp_cmd, sizeof(bksp_cmd)/sizeof(bksp_cmd[0]));
        }
        else
        {
            if (row > 0)
            {
                // Move the cursor to the end of the previous line
                _move_cursor(--row, col = MAXCOLS);
                --col;
                lcd_output(bksp_cmd, sizeof(bksp_cmd)/sizeof(bksp_cmd[0]));
            }
            else
            {
                // Backspace from 0,0 is no good -- visual bell instead
                _visual_bell();
            }
        }
        break;
    // Form feed -- clears the screen and homes the cursor
    case '\f':
        row = 0, col = 0;
        lcd_output(cls_cmd, sizeof(cls_cmd)/sizeof(cls_cmd[0]));
        // Backspace out an extra space left over from adjusting the 
        // timing on the clear screen command
        // This is a kludge -- but we don't know the exact timing on 
        // clearing the screen on this module, so it's the best we can do
        lcd_output(bksp_cmd, sizeof(bksp_cmd)/sizeof(bksp_cmd[0]));
        break;
    // Newline -- we follow the UNIX convention here as moving to the
    // next line without going back to col 0 is pretty useless and lame
    case '\n':
        row = (row + 1) % MAXROWS;
        col = 0;
        _move_cursor(row, col);
        lcd_output(clear_line_cmd,
            sizeof(clear_line_cmd)/sizeof(clear_line_cmd[0]));
        _move_cursor(row, col);
        break;
    // Carriage return -- go back to col 0 without moving lines
    case '\r':
        col = 0;
        _move_cursor(row, col);
        break;
    // VT and HT aren't supported as I have no idea what to do with them
    // Someone tried to sneak a LCD command escape through STDIO -- 
    // that's a no-no, so eat it as we can't return EOF because SDCC's
    // putchar decl is silly and non-conformant
    case '\xfe':
        return;
    // Not a character we handle, so just fling it out to the LCD
    default:
        if (col >= MAXCOLS) // wrap to the next line
        {
            row = (row + 1) % MAXROWS;
            col = 0;
            _move_cursor(row, col);
            lcd_output(clear_line_cmd,
                sizeof(clear_line_cmd)/sizeof(clear_line_cmd[0]));
            _move_cursor(row, col);
        }
        lcd_output(&c, 1);
        ++col;
        break;
    }
    return;
}

void _visual_bell(void)
{
}

void _move_cursor(int r, int c)
{
    // note, this can't live in code space or be a const as we scribble
    // on it to prepare the actual command
    uint8_t move_cmd[] = {0xFE, 0x45, 0x00};
    if (r >= MAXROWS)
        r = MAXROWS;
    if (c >= MAXCOLS)
        c = MAXCOLS;
    move_cmd[2] = ((r & 0x03) << 6) | (c & 0x3F);
    lcd_output(move_cmd, sizeof(move_cmd)/sizeof(move_cmd[0]));
}

void lcd_init(void)
{
    // the display defaults ON already, but force it ON because this may
    // be called with the display OFF in a warm restart situation
    // we also clear the screen here
    CONST uint8_t cmd[] = {0xFE, 0x41, 0xFE, 0x51, 0x20, 0x20, 0x20, 0x20,
        0x20, 0x20, 0x20, 0x20};
    lcd_output(cmd, sizeof(cmd)/sizeof(cmd[0]));
}

uint8_t rev_bits(uint8_t c);
// Write a blob of data to the LCD -- this doesn't care about control
// characters etal
void lcd_output(uint8_t* chars, int n)
{
    int i = 0;
    // make sure SM2 is cleared FIRST (CPOL=idle HIGH)
    SM2 = 0;
    // drive the slave select for the LCD
    P4_6 = 0;
    for (; i < n; ++i)
    {
        // transmit a byte -- swap the bits in it as the LCD wants data
        // MSB-first, but the 8051 USART sends LSB-first only
        SBUF = rev_bits(chars[i]);
        // wait for it to fly out the door as we don't really have
        // anything better to do
        while (!TI);
        TI = 0;
    }
    // turn off the slave select
    P4_6 = 1;
}

// Reverse the bits in a byte using some clever bit fiddling
// Swaps nybbles first, then two-bit chunks, then individual adjacent bits
uint8_t rev_bits(uint8_t c) 
{
    c = (c & 0xF0) >> 4 | (c & 0x0F) << 4;
    c = (c & 0xCC) >> 2 | (c & 0x33) << 2;
    c = (c & 0xAA) >> 1 | (c & 0x55) << 1;
    return c;
}

// Note that I've tried having this code set the port pin as an input-only pin for the duration of reception from the RTC, but to no avail
// Read a blob of data from the RTC chip -- takes care of the address too
void rtc_input(uint8_t addr, uint8_t* chars, int n)
{
    int i = 0;
    // make sure SM2 is set FIRST (CPOL = idle LOW)
    SM2 = 1;
    // drive the slave select for the RTC chip
    P4_7 = 0;
    // transmit the address byte with the MSB forced to 0
    SBUF = addr & 0x7F;
    // wait for it to finish
    while (!TI);
    TI = 0;
    REN = 1;
    RI = 0;
    for (; i < n; ++i)
    {
        // wait for a byte to come in -- the RTC sends LSB-first, too
        // an interrupt driven serial routine would add complexity for
        // very little gain here -- the keyboard interrupt is the only
        // thing we have to remain responsive to
        while (!RI);
        RI = 0;
        chars[i] = SBUF;
    }
    REN = 0;
    // turn off the slave select
    P4_7 = 1;
}

Crédito adicional: ¿por qué hay un pulso runt al final del byte de la dirección y cómo puedo deshacerme de él?

    
pregunta ThreePhaseEel

1 respuesta

0

Y es por esto que SPI es solo una convención ...

El USART AT89LP51ED2, a pesar de todo lo que tiene bajo la manga, simplemente no se puede convencer para que muestre el medio ciclo de la línea de datos en serie (es decir, mientras SCK está sólidamente ALTO), que es lo que se necesita para recibir los datos correctos de los DS1306 3 - Modo cableado. La única solución que pude encontrar, después de horas de trabajo tenaz con el Bus Pirate y el MWE del ejemplo anterior, fue golpear la recepción en serie, como en el código a continuación, que es una versión modificada del MWE anterior. (El muestreo con SMOD0 = 0 produjo resultados que fueron desactivados en un bit o dos, al igual que en el intento de muestrear los bordes mientras se intercambian bits). Por supuesto, ni la hoja de datos DS1306 ni Appnote 3510 de Máxima di esto, de hecho, ¡el código que aparece en las muestras de borde de appnote!

#include <stdint.h>
#include <stdio.h>
#include <ctype.h>
#include <stdnoreturn.h>
#include <stdbool.h>
#include <string.h>


// Peripheral stuff
#include <at89c51ed2.h>
__sfr __at (0xE6) P0M0;
__sfr __at (0xE7) P0M1;
__sfr __at (0xD6) P1M0;
__sfr __at (0xD7) P1M1;
__sfr __at (0xCE) P2M0;
__sfr __at (0xCF) P2M1;
__sfr __at (0xC6) P3M0;
__sfr __at (0xC7) P3M1;
__sfr __at (0xBE) P4M0;
__sfr __at (0xBF) P4M1;

__sfr __at (0x87) PCON;
    #define SMOD1 0x80
    #define SMOD0 0x40

void gpio_init();
void serial_init();
void lcd_init();
void read_and_print_rtc();

noreturn int main()
{
  uint16_t i;
  gpio_init();
  serial_init();
  lcd_init();

  while (true)
  {
    read_and_print_rtc();
    for (i = 1; i; ++i);
  }
}

typedef union 
{
    uint8_t buf[7];
    struct
    {
        uint8_t sc;
        uint8_t mi;
        uint8_t hr;
        uint8_t wd;
        uint8_t dy;
        uint8_t mo;
        uint8_t yr;
    } tm;
} rw_time_t;

rw_time_t tb;

void rtc_input(uint8_t addr, uint8_t* chars, int n);

void read_and_print_rtc(void)
{
    memset(tb.buf, 0, sizeof(tb));
    // Read the time & date in one go to avoid rollover woes
    rtc_input(0x00, tb.buf, 7);
    // debug dump output
    printf("\f %2x%2x%2x", tb.buf[0], tb.buf[1], tb.buf[2]);
    printf("%2x%2x%2x", tb.buf[4], tb.buf[5], tb.buf[6]);
}

void gpio_init(void) __reentrant
{
    // We set up the ports as follows:
    // P0: quasi-bidir, default FF
    // P1: quasi-bidir, default FF
    // P2: totem pole, default 0
    // P3.0: quasi-bidir, default 1
    // P3.1: totem pole, default 1
    // P3.2: input only
    // P3.3-P3.5: totem pole, default 0
    // P3.6-P3.7: totem pole, default 1
    // P4.4-P4.7: totem pole, default 1

    // Everything starts up in quasi-bidir as TRIPORT is cleared
    // Our port latches also start up as 0xFF as well
    P0 = 0xFF;
    P1 = 0xFF;
    P2 = 0x0;
    P3 = 0xC7;
    P4 = 0xFF;
    P0M0 = 0x0;
    P0M1 = 0x0;
    P1M0 = 0x0;
    P1M1 = 0x0;
    P2M0 = 0x0;
    P2M1 = 0xFF;
    P3M0 = 0x04;
    P3M1 = 0xFA;
    P4M0 = 0x0;
    P4M1 = 0xFF;
}

void serial_init(void)
{
    // We put the 8051 USART in Mode 0 for this as we are talking to
    // SPI-ish devices (DS1306, SPI-ish LCD) -- SMOD0 in PCON is off
    // by default, which is good, because we don't need FE anyway; 
    // however, we need to set SMOD1 which not only means the BRG runs 
    // at 2x speed but also makes the Rx sample on the rising edge of 
    // the serial clock when SM2=1 for driving the RTC
    PCON |= SMOD1;

    // SM2 is also set to 0 by default, which means the clock idles HIGH
    // This is what the LCD expects, but the RTC expects idle LOW -- 
    // writing to the USART requires a CPOL (SM2) flip before driving SS

    // We set up the BRG for an 83kHz clock rate -- its the fastest 
    // rate we can get from the BRG that meets the 100kHz max on the LCD
    BRL = 196;
    BDRCON = BRR | TBCK | RBCK | SPD | SRC;
}

const int MAXCOLS = 16;
const int MAXROWS = 2;

void _move_cursor(int r, int c);
void _visual_bell(void);

void lcd_output(uint8_t* chars, int n);

#define CONST const __code

void putchar(char c)
{
    // Assume that the LCD has been init'ed with the screen cleared 
    // and the cursor homed the first time through
    static int row = 0, col = 0;
    // The dummy bytes are there to enforce a time delay
    CONST uint8_t cls_cmd[] = {0xFE, 0x51, 0x20, 0x20, 0x20, 0x20, 0x20,
        0x20, 0x20, 0x20, 0x20};
    // 16 spaces to clear a line
    CONST uint8_t clear_line_cmd[] = {
        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
        0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
    };
    CONST uint8_t bksp_cmd[] = {0xFE, 0x4E};
    switch (c)
    {
    // Alarm bell -- we need some sort of visual bell here
    case '\a':
        _visual_bell();
        break;
    // Backspace -- this does what you think it does, although backspacing
    // from column 0 needs to be handled specially
    case '\b':
        if (col > 0)
        {
            --col;
            lcd_output(bksp_cmd, sizeof(bksp_cmd)/sizeof(bksp_cmd[0]));
        }
        else
        {
            if (row > 0)
            {
                // Move the cursor to the end of the previous line
                _move_cursor(--row, col = MAXCOLS);
                --col;
                lcd_output(bksp_cmd, sizeof(bksp_cmd)/sizeof(bksp_cmd[0]));
            }
            else
            {
                // Backspace from 0,0 is no good -- visual bell instead
                _visual_bell();
            }
        }
        break;
    // Form feed -- clears the screen and homes the cursor
    case '\f':
        row = 0, col = 0;
        lcd_output(cls_cmd, sizeof(cls_cmd)/sizeof(cls_cmd[0]));
        // Backspace out an extra space left over from adjusting the 
        // timing on the clear screen command
        // This is a kludge -- but we don't know the exact timing on 
        // clearing the screen on this module, so it's the best we can do
        lcd_output(bksp_cmd, sizeof(bksp_cmd)/sizeof(bksp_cmd[0]));
        break;
    // Newline -- we follow the UNIX convention here as moving to the
    // next line without going back to col 0 is pretty useless and lame
    case '\n':
        row = (row + 1) % MAXROWS;
        col = 0;
        _move_cursor(row, col);
        lcd_output(clear_line_cmd,
            sizeof(clear_line_cmd)/sizeof(clear_line_cmd[0]));
        _move_cursor(row, col);
        break;
    // Carriage return -- go back to col 0 without moving lines
    case '\r':
        col = 0;
        _move_cursor(row, col);
        break;
    // VT and HT aren't supported as I have no idea what to do with them
    // Someone tried to sneak a LCD command escape through STDIO -- 
    // that's a no-no, so eat it as we can't return EOF because SDCC's
    // putchar decl is silly and non-conformant
    case '\xfe':
        return;
    // Not a character we handle, so just fling it out to the LCD
    default:
        if (col >= MAXCOLS) // wrap to the next line
        {
            row = (row + 1) % MAXROWS;
            col = 0;
            _move_cursor(row, col);
            lcd_output(clear_line_cmd,
                sizeof(clear_line_cmd)/sizeof(clear_line_cmd[0]));
            _move_cursor(row, col);
        }
        lcd_output(&c, 1);
        ++col;
        break;
    }
    return;
}

void _visual_bell(void)
{
}

void _move_cursor(int r, int c)
{
    // note, this can't live in code space or be a const as we scribble
    // on it to prepare the actual command
    uint8_t move_cmd[] = {0xFE, 0x45, 0x00};
    if (r >= MAXROWS)
        r = MAXROWS;
    if (c >= MAXCOLS)
        c = MAXCOLS;
    move_cmd[2] = ((r & 0x03) << 6) | (c & 0x3F);
    lcd_output(move_cmd, sizeof(move_cmd)/sizeof(move_cmd[0]));
}

void lcd_init(void)
{
    // the display defaults ON already, but force it ON because this may
    // be called with the display OFF in a warm restart situation
    // we also clear the screen here
    CONST uint8_t cmd[] = {0xFE, 0x41, 0xFE, 0x51, 0x20, 0x20, 0x20, 0x20,
        0x20, 0x20, 0x20, 0x20};
    lcd_output(cmd, sizeof(cmd)/sizeof(cmd[0]));
}

uint8_t rev_bits(uint8_t c);
// Write a blob of data to the LCD -- this doesn't care about control
// characters etal
void lcd_output(uint8_t* chars, int n)
{
    int i = 0;
    // make sure SM2 is cleared FIRST (CPOL=idle HIGH)
    SM2 = 0;
    // drive the slave select for the LCD
    P4_6 = 0;
    for (; i < n; ++i)
    {
        // transmit a byte -- swap the bits in it as the LCD wants data
        // MSB-first, but the 8051 USART sends LSB-first only
        SBUF = rev_bits(chars[i]);
        // wait for it to fly out the door as we don't really have
        // anything better to do
        while (!TI);
        TI = 0;
    }
    // turn off the slave select
    P4_6 = 1;
}

// Reverse the bits in a byte using some clever bit fiddling
// Swaps nybbles first, then two-bit chunks, then individual adjacent bits
uint8_t rev_bits(uint8_t c) 
{
    c = (c & 0xF0) >> 4 | (c & 0x0F) << 4;
    c = (c & 0xCC) >> 2 | (c & 0x33) << 2;
    c = (c & 0xAA) >> 1 | (c & 0x55) << 1;
    return c;
}

// Read a blob of data from the RTC chip -- takes care of the address too
void rtc_input(uint8_t addr, uint8_t* chars, int n)
{
    int i = 0, j;
    uint8_t r = 0, t;
    // drive the slave select for the RTC chip
    P3_1 = 0;
    P4_7 = 0;
    // transmit the address byte with the MSB forced to 0
    addr &= 0x7F;
    for (j = 0; j < 8; ++j)
    {
        P3_0 = 0;
        if (addr & 0x1)
            P3_0 = 1;
        P3_1 = 1;
        P3_1 = 0;
        addr >>= 1;
    }
    for (; i < n; ++i)
    {
        // Bitbang a byte in, we can't use the hardware serial support
        // for this because there's no way to make the USART sample in
        // the middle of a SCK cycle
        P3_0 = 1;
        for (j = 0; j < 8; ++j)
        {
            P3_1 = 1;
            t = (uint8_t) P3_0;
            t <<= 7;
            r >>= 1;
            r |= t;
            P3_1 = 0;
        }
        chars[i] = r;
        r = 0;
    }
    // turn off the slave select
    P4_7 = 1;
    P3_1 = 1;
}
    
respondido por el ThreePhaseEel

Lea otras preguntas en las etiquetas